static void Main( string[] args ) { try { // Analyze arguments if ( args.Length != 3 ) throw new Exception( "Usage: BRDFLafortuneFitting \"Path to MERL BRDF\" \"Path to Lafortune Coeffs file\" AmountOfCosineLobes" ); FileInfo SourceBRDF = new FileInfo( args[0] ); if ( !SourceBRDF.Exists ) throw new Exception( "Source BRDF file \"" + SourceBRDF.FullName + "\" does not exist!" ); FileInfo TargetFile = new FileInfo( args[1] ); if ( !TargetFile.Directory.Exists ) throw new Exception( "Target coefficient file's \"" + TargetFile.FullName + "\" directory does not exist!" ); int LobesCount = 0; if ( !int.TryParse( args[2], out LobesCount ) ) throw new Exception( "3rd argument must be an integer number!" ); if ( LobesCount <= 0 || LobesCount > 100 ) throw new Exception( "Number of lobes must be in [1,100]!" ); // Attempt to create the target file first, just to ensure we don't bump into a problem after the lengthy minimization process... try { using ( TargetFile.Create() ) {} } catch ( Exception _e ) { throw new Exception( "Failed to create the target coefficients file: " + _e.Message ); } // Load the BRDF double[][] BRDF = LoadBRDF( SourceBRDF ); // // DEBUG CHECK // { // Generate a bunch of incoming/outgoing directions, get their Half/Diff angles then regenerate back incoming/outgoing directions from these angles and check the relative incoming/outgoing directions are conserved // // This is important to ensure we sample only the relevant (i.e. changing) parts of the BRDF in our minimization scheme // // (I want to actually sample the BRDF using the half/diff angles and generate incoming/outgoing vectors from these, rather than sample all the possible 4D space) // // // Random TempRNG = new Random( 1 ); // Vector3 TempIn = new Vector3(), TempOut = new Vector3(); // double MinThetaHalf = double.MaxValue, MaxThetaHalf = -double.MaxValue; // double MinPhiHalf = double.MaxValue, MaxPhiHalf = -double.MaxValue; // double MinThetaDiff = double.MaxValue, MaxThetaDiff = -double.MaxValue; // double MinPhiDiff = double.MaxValue, MaxPhiDiff = -double.MaxValue; // for ( int i=0; i < 10000; i++ ) // { // double Phi_i = 2.0 * Math.PI * (TempRNG.NextDouble() - 0.5); // double Theta_i = 0.5 * Math.PI * TempRNG.NextDouble(); // double Phi_r = 2.0 * Math.PI * (TempRNG.NextDouble() - 0.5); // double Theta_r = 0.5 * Math.PI * TempRNG.NextDouble(); // // double Theta_half, Phi_half, Theta_diff, Phi_diff; // std_coords_to_half_diff_coords( Theta_i, Phi_i, Theta_r, Phi_r, out Theta_half, out Phi_half, out Theta_diff, out Phi_diff ); // // // // MinThetaHalf = Math.Min( MinThetaHalf, Theta_half ); // // MaxThetaHalf = Math.Max( MaxThetaHalf, Theta_half ); // // MinThetaDiff = Math.Min( MinThetaDiff, Theta_diff ); // // MaxThetaDiff = Math.Max( MaxThetaDiff, Theta_diff ); // // MinPhiHalf = Math.Min( MinPhiHalf, Phi_half ); // // MaxPhiHalf = Math.Max( MaxPhiHalf, Phi_half ); // // MinPhiDiff = Math.Min( MinPhiDiff, Phi_diff ); // // MaxPhiDiff = Math.Max( MaxPhiDiff, Phi_diff ); // // // if ( Theta_half > MaxThetaHalf ) // // { // // MaxThetaHalf = Theta_half; // // MaxPhiHalf = Phi_half; // // MaxThetaDiff = Theta_diff; // // MaxPhiDiff = Phi_diff; // // } // // // Convert back... // double NewTheta_i, NewPhi_i, NewTheta_r, NewPhi_r; // half_diff_coords_to_std_coords( Theta_half, Phi_half, Theta_diff, Phi_diff, out NewTheta_i, out NewPhi_i, out NewTheta_r, out NewPhi_r ); // // // Convert back into directions // half_diff_coords_to_std_coords( Theta_half, Phi_half, Theta_diff, Phi_diff, ref TempIn, ref TempOut ); // // // Check // const double Tol = 1e-4; // if ( Math.Abs( NewTheta_i - Theta_i ) > Tol // || Math.Abs( NewTheta_r - Theta_r ) > Tol ) // throw new Exception( "ARGH THETA!" ); // if ( Math.Abs( NewPhi_i - Phi_i ) > Tol // || Math.Abs( NewPhi_r - Phi_r ) > Tol ) // throw new Exception( "ARGH PHI!" ); // // if ( NewTheta_i > 0.5 * Math.PI ) // throw new Exception( "Incoming direction below surface!" ); // if ( NewTheta_r > 0.5 * Math.PI ) // throw new Exception( "Outgoing direction below surface!" ); // if ( TempIn.z < 0.0 ) // throw new Exception( "VECTOR Incoming direction below surface!" ); // if ( TempOut.z < 0.0 ) // throw new Exception( "VECTOR Outgoing direction below surface!" ); // } // } // // DEBUG CHECK // DEBUG CHECK // I can't understand the purpose of certain "wrong angles" in the BRDF table // When Theta_half is near PI/2, and difference angles make the incoming or outgoing directions go BELOW the f*****g surface, what does it mean???? // { // double ThetaHalf = 0.5*Math.PI; // double ThetaDiff = 0.1*Math.PI; // This should make the incoming direction go BELOW the surface... // double PhiDiff = 0.0; // // // Check it's below the surface // Vector3 TempIn = new Vector3(), TempOut = new Vector3(); // half_diff_coords_to_std_coords( ThetaHalf, 0, ThetaDiff, PhiDiff, ref TempIn, ref TempOut ); // // // Get BRDF index // int TableIndex = PhiDiff_index( PhiDiff ); // TableIndex += (BRDF_SAMPLING_RES_PHI_D / 2) * ThetaDiff_index( ThetaDiff ); // TableIndex += (BRDF_SAMPLING_RES_THETA_D*BRDF_SAMPLING_RES_PHI_D / 2) * ThetaHalf_index( ThetaHalf ); // // // What the f**k is there?? // double Value = BRDF[0][TableIndex]; // // // A tiny negative value... // } // DEBUG CHECK // DEBUG Clear out BRDF values to make sure we only read positive samples. If we find a negative sample then there is an error! // for ( int PhiDiffIndex=0; PhiDiffIndex < BRDF_SAMPLING_RES_PHI_D/2; PhiDiffIndex++ ) // for ( int ThetaDiffIndex=0; ThetaDiffIndex < BRDF_SAMPLING_RES_THETA_D; ThetaDiffIndex++ ) // for ( int ThetaHalfIndex=0; ThetaHalfIndex < BRDF_SAMPLING_RES_THETA_H; ThetaHalfIndex++ ) // { // int TableIndex = PhiDiffIndex; // TableIndex += (BRDF_SAMPLING_RES_PHI_D / 2) * ThetaDiffIndex; // TableIndex += (BRDF_SAMPLING_RES_THETA_D*BRDF_SAMPLING_RES_PHI_D / 2) * ThetaHalfIndex; // // BRDF[0][TableIndex] = -1.0f; // } // DEBUG // Generate the sampling base // => We generate and store as many samples as possible in the 90*90*360/2 source array // => We generate the associated incoming/outgoing direction // => We store the 3 dot product coefficients for the sample, which we will use to evaluate the cosine lobe // Random RNG = new Random( 1 ); double dPhi = Math.PI / (2*SAMPLES_COUNT_THETA); double dTheta = 0.5*Math.PI / SAMPLES_COUNT_THETA; Vector3 MinValues = new Vector3() { x=+double.MaxValue, y=+double.MaxValue, z=+double.MaxValue }; Vector3 MaxValues = new Vector3() { x=-double.MaxValue, y=-double.MaxValue, z=-double.MaxValue }; Vector3 In = new Vector3(); Vector3 Out = new Vector3(); List<BRDFSample> Samples = new List<BRDFSample>(); for ( int PhiDiffIndex=0; PhiDiffIndex < 2*SAMPLES_COUNT_THETA; PhiDiffIndex++ ) { for ( int ThetaDiffIndex=0; ThetaDiffIndex < SAMPLES_COUNT_THETA; ThetaDiffIndex++ ) { for ( int ThetaHalfIndex=0; ThetaHalfIndex < SAMPLES_COUNT_THETA; ThetaHalfIndex++ ) { // Generate random stratified samples double PhiDiff = dPhi * (PhiDiffIndex + RNG.NextDouble()); double ThetaDiff = dTheta * (ThetaDiffIndex + RNG.NextDouble()); double ThetaHalf = dTheta * (ThetaHalfIndex + RNG.NextDouble()); // double PhiDiff = dPhi * PhiDiffIndex; // double ThetaDiff = dTheta * ThetaDiffIndex; // double ThetaHalf = dTheta * ThetaHalfIndex; // Retrieve incoming/outgoing vectors half_diff_coords_to_std_coords( ThetaHalf, 0.0, ThetaDiff, PhiDiff, ref In, ref Out ); // Build the general BRDF index int TableIndex = PhiDiff_index( PhiDiff ); TableIndex += (BRDF_SAMPLING_RES_PHI_D / 2) * ThetaDiff_index( ThetaDiff ); TableIndex += (BRDF_SAMPLING_RES_THETA_D*BRDF_SAMPLING_RES_PHI_D / 2) * ThetaHalf_index( ThetaHalf ); // Check the in & out directions are valid (i.e. ABOVE the surface) const double Z_TOL = 0.001; if ( In.z <= Z_TOL || Out.z <= Z_TOL ) continue; // Invalid sample... ////////////////////////////////////////////////////////////////////////// // Replace any invalid value double R = BRDF[0][TableIndex]; double G = BRDF[1][TableIndex]; double B = BRDF[2][TableIndex]; double SumValidValues = 0.0; int ValidValuesCount = 0; if ( IsValid( R ) ) { // Red is valid SumValidValues += R; ValidValuesCount++; } if ( IsValid( G ) ) { // Green is valid SumValidValues += G; ValidValuesCount++; } if ( IsValid( B ) ) { // Blue is valid SumValidValues += B; ValidValuesCount++; } if ( ValidValuesCount != 3 ) { SumValidValues /= Math.Max( 1, ValidValuesCount ); // Get the average of valid values if ( !IsValid( R ) ) R = SumValidValues; // Replace Red by average... if ( !IsValid( G ) ) G = SumValidValues; // Replace Green by average... if ( !IsValid( B ) ) B = SumValidValues; // Replace Blue by average... BRDF[0][TableIndex] = R; BRDF[1][TableIndex] = G; BRDF[2][TableIndex] = B; } ////////////////////////////////////////////////////////////////////////// BRDFSample Sample = new BRDFSample(); Samples.Add( Sample ); Sample.m_BRDFIndex = TableIndex; // Store the cos(ThetaIn) Sample.m_CosThetaIn = In.z; // Build the dot product coefficients used by the Lafortune model Sample.m_DotProduct.Set( In.x*Out.x, In.y*Out.y, In.z*Out.z ); // DEBUG Keep min/max values to have an idea of what we're manipulating here... MinValues.x = Math.Min( MinValues.x, R ); MaxValues.x = Math.Max( MaxValues.x, R ); MinValues.y = Math.Min( MinValues.y, G ); MaxValues.y = Math.Max( MaxValues.y, G ); MinValues.z = Math.Min( MinValues.z, B ); MaxValues.z = Math.Max( MaxValues.z, B ); // DEBUG // DEBUG // Patch the BRDF to make it look like an obvious cosine lobe from a standard Phong reflection // { // Vector3 Reflect = new Vector3() { x=-In.x, y=-In.y, z=In.z }; // double Dot = Reflect.Dot( ref Out ); // // // Vector3 Half = new Vector3() { x=In.x+Out.x, y=In.y+Out.y, z=In.z+Out.z }; // // Half.Normalize(); // // double Dot = Half.z; // Dot = Math.Max( 0, Dot ); // Dot = Math.Pow( Dot, 17.2 ); // The exponent is very particular // // Dot /= In.z; // / cos(ThetaIn) // // BRDF[0][TableIndex] = Dot; // } // DEBUG } } } // We got our samples! ms_BRDFSamples = Samples.ToArray(); double PercentageOfTableUsed = (double) ms_BRDFSamples.Length / (BRDF_SAMPLING_RES_THETA_H*BRDF_SAMPLING_RES_THETA_D*BRDF_SAMPLING_RES_PHI_D / 2); // Show modeless progress form ms_ProgressForm = new ProgressForm(); ms_ProgressForm.Show(); ms_ProgressForm.BRDFComponentIndex = 0; ms_ProgressForm.Progress = 0.0; // Build a list of initial guesses CosineLobe[] InitialGuesses = new CosineLobe[10]; InitialGuesses[0] = new CosineLobe( new Vector3() { x=-1, y=-1, z=1 }, // Standard Phong reflection // new Vector3() { x=0, y=0, z=1 }, // Dumb test // 17.2 ); 1.0 ); for ( int i=1; i < InitialGuesses.Length; i++ ) { Vector3 Direction = new Vector3() { x=2.0*RNG.NextDouble()-1.0, y=2.0*RNG.NextDouble()-1.0, z=RNG.NextDouble() }; InitialGuesses[i] = new CosineLobe( Direction, 1.0 ); } // Perform local minimization for each R,G,B component CosineLobe[][] CosineLobes = new CosineLobe[3][]; double[][] RMSErrors = new double[3][]; try { for ( int ComponentIndex=0; ComponentIndex < 3; ComponentIndex++ ) { CosineLobes[ComponentIndex] = new CosineLobe[LobesCount]; RMSErrors[ComponentIndex] = new double[LobesCount]; ms_ProgressForm.BRDFComponentIndex = ComponentIndex; FitBRDF( BRDF[ComponentIndex], CosineLobes[ComponentIndex], InitialGuesses, BFGS_CONVERGENCE_TOLERANCE, RMSErrors[ComponentIndex], ShowProgress ); // // CHECK We get the same result starting from another initial guess // CosineLobe[] TempLobes = new CosineLobe[LobesCount]; // InitialGuesses[0] = new CosineLobe( new Vector3() { x=0, y=0, z=1 }, 1 ); // FitBRDF( BRDF[ComponentIndex], TempLobes, InitialGuesses, BFGS_CONVERGENCE_TOLERANCE, RMSErrors[ComponentIndex], ShowProgress ); // // CHECK } } catch ( Exception _e ) { throw new Exception( "BRDF Fitting failed: " + _e.Message ); } ms_ProgressForm.Dispose(); // Save the result try { using ( FileStream Stream = TargetFile.Create() ) using ( BinaryWriter Writer = new BinaryWriter( Stream ) ) { // Write the amount of lobes Writer.Write( LobesCount ); // Write the coefficients for ( int ComponentIndex=0; ComponentIndex < 3; ComponentIndex++ ) { for ( int LobeIndex=0; LobeIndex < LobesCount; LobeIndex++ ) { CosineLobe Lobe = CosineLobes[ComponentIndex][LobeIndex]; Writer.Write( Lobe.C.x ); Writer.Write( Lobe.C.y ); Writer.Write( Lobe.C.z ); Writer.Write( Lobe.N ); } } } } catch ( Exception _e ) { throw new Exception( "Error writing the result cosine lobe coefficients: " + _e.Message ); } } catch ( Exception _e ) { MessageBox.Show( "An error occurred!\r\n\r\n" + _e.Message + "\r\n\r\n" + _e.StackTrace, "BRDF Fitting", MessageBoxButtons.OK, MessageBoxIcon.Warning ); } }
/// <summary> /// Computes the square difference between a current cosine lobe estimate and a goal BRDF given a set of samples /// </summary> /// <param name="_SamplesCollection">The collection of samples to use for the computation</param> /// <param name="_Normalizer">The normalizer for the final result</param> /// <param name="_GoalBDRF">The goal BRDF function to compute square difference from</param> /// <param name="_LobeEstimates">The cosine lobes matching the BRDF</param> /// <returns>The square difference between goal and estimate</returns> private static double ComputeSummedDifferences( BRDFSample[] _Samples, double _Normalizer, double[] _GoalBDRF, CosineLobe[] _LobeEstimates ) { // Sum differences between current ZH estimates and current SH goal estimates double SumSquareDifference = 0.0; double GoalValue, CurrentValue, TempLobeDot; int SamplesCount = _Samples.Length; int LobesCount = _LobeEstimates.Length; for ( int SampleIndex=0; SampleIndex < SamplesCount; SampleIndex++ ) { BRDFSample Sample = _Samples[SampleIndex]; // Get BRDF value for that sample GoalValue = _GoalBDRF[Sample.m_BRDFIndex]; // if ( GoalValue < 0.0 ) // throw new Exception( "Unexpected negative value!" ); if ( double.IsNaN( GoalValue ) ) throw new Exception( "Unexpected NaN!" ); // DEBUG => Multiply by cos(ThetaIn) to test if it gets us a better conditioning GoalValue *= Sample.m_CosThetaIn; // Estimate cosine lobe value in that direction CurrentValue = 0.0; for ( int LobeIndex=0; LobeIndex < LobesCount; LobeIndex++ ) { CosineLobe Lobe = _LobeEstimates[LobeIndex]; TempLobeDot = Lobe.C.x * Sample.m_DotProduct.x + Lobe.C.y * Sample.m_DotProduct.y + Lobe.C.z * Sample.m_DotProduct.z; TempLobeDot = Math.Max( 0.0, TempLobeDot ); TempLobeDot = Math.Pow( TempLobeDot, Lobe.N ); CurrentValue += TempLobeDot; } // Sum difference between estimate and goal SumSquareDifference += (CurrentValue - GoalValue) * (CurrentValue - GoalValue); // SumSquareDifference += Math.Abs( CurrentValue - GoalValue ); // Just to avoid super large numbers! } // Normalize SumSquareDifference *= _Normalizer; return SumSquareDifference; }
/// <summary> /// Performs mapping of a BRDF into N sets of cosine lobes coefficients /// WARNING: Takes hell of a time to compute ! /// </summary> /// <param name="_BRDF">The BRDF to fit into cosine lobes</param> /// <param name="_Lobes">The array of resulting cosine lobes</param> /// <param name="_InitialGuesses">An array of initial lobe coefficients</param> /// <param name="_BFGSConvergenceTolerance">The convergence tolerance for the BFGS algorithm (the lower the tolerance, the longer it will compute)</param> /// <param name="_RMS">The resulting array of RMS errors for each cosine lobe</param> /// <param name="_Delegate">An optional delegate to pass the method to get feedback about the mapping as it can be a lengthy process (!!)</param> /// <returns>The global minimum for all the cosine lobes put together</returns> private static double FitBRDF( double[] _BRDF, CosineLobe[] _Lobes, CosineLobe[] _InitialGuesses, double _BFGSConvergenceTolerance, double[] _RMS, BRDFMappingFeedback _Delegate ) { int LobesCount = _Lobes.GetLength( 0 ); // Build the local function evaluation context BRDFFitEvaluationContext Context = new BRDFFitEvaluationContext(); Context.m_Lobes = new CosineLobe[1] { new CosineLobe() }; Context.m_BRDF = new double[_BRDF.Length]; _BRDF.CopyTo( Context.m_BRDF, 0 ); // Duplicate BRDF as we will modify it for each new lobe // Prepare feedback data float fCurrentProgress = 0.0f; float fProgressDelta = 1.0f / (LobesCount * _InitialGuesses.Length); int FeedbackCount = 0; int FeedbackThreshold = (LobesCount * _InitialGuesses.Length) / 100; // Notify every percent ////////////////////////////////////////////////////////////////////////// // 1] Compute the best fit for each lobe int CrashesCount = 0; double[] LocalLobeCoefficients = new double[1+4]; // Don't forget the BFGS function annoyingly uses indices starting from 1! List<CosineLobe> BestFits = new List<CosineLobe>( _InitialGuesses.Length ); for ( int LobeIndex=0; LobeIndex < LobesCount; LobeIndex++ ) { BestFits.Clear(); // 1.1] Perform minification using several attempts with different initial coefficients and keep the best fit double MinError = double.MaxValue; for ( int AttemptIndex = 0; AttemptIndex < _InitialGuesses.Length; AttemptIndex++ ) { // Update external feedback on progression if ( _Delegate != null ) { fCurrentProgress += fProgressDelta; FeedbackCount++; if ( FeedbackCount > FeedbackThreshold ) { // Send feedback FeedbackCount = 0; _Delegate( fCurrentProgress ); } } // 1.1.1] Set the initial lobe coefficients Context.m_Lobes[0].CopyFrom( _InitialGuesses[AttemptIndex] ); // 1.1.2] Copy coefficients into working array LocalLobeCoefficients[1+0] = Context.m_Lobes[0].C.x; LocalLobeCoefficients[1+1] = Context.m_Lobes[0].C.y; LocalLobeCoefficients[1+2] = Context.m_Lobes[0].C.z; LocalLobeCoefficients[1+3] = Context.m_Lobes[0].N; ////////////////////////////////////////////////////////////////////////// // At this point, we have a fixed direction and the best estimated ZH coefficients to map the provided SH in this direction. // // We then need to apply BFGS minimization to optimize the ZH coefficients yielding the smallest possible error... // // 1.1.3] Apply BFGS minimization int IterationsCount = 0; double FunctionMinimum = 0; try { FunctionMinimum = dfpmin( LocalLobeCoefficients, _BFGSConvergenceTolerance, out IterationsCount, new BFGSFunctionEval( BRDFMappingLocalFunctionEval ), new BFGSFunctionGradientEval( BRDFMappingLocalFunctionGradientEval ), Context ); } catch ( Exception ) { CrashesCount++; continue; } if ( FunctionMinimum >= MinError ) continue; // Larger error than best candidate so far... MinError = FunctionMinimum; // Save that "optimal" lobe data _Lobes[LobeIndex].C.Set( Context.m_Lobes[0].C.x, Context.m_Lobes[0].C.y, Context.m_Lobes[0].C.z ); _Lobes[LobeIndex].N = Context.m_Lobes[0].N; _Lobes[LobeIndex].Error = FunctionMinimum; // Keep in the list of best fits BestFits.Insert( 0, _Lobes[LobeIndex] ); _RMS[LobeIndex] = FunctionMinimum; } ////////////////////////////////////////////////////////////////////////// // 1.2] At this point, we have the "best" cosine lobe fit for the given BRDF // We must subtract the influence of that lobe from the current BRDF and restart fitting with a new lobe... // double OldMaxValue = -double.MaxValue; double NewMaxValue = -double.MaxValue; CosineLobe LobeToSubtract = _Lobes[LobeIndex]; for ( int SampleIndex=0; SampleIndex < ms_BRDFSamples.Length; SampleIndex++ ) { BRDFSample Sample = ms_BRDFSamples[SampleIndex]; double LobeInfluence = Sample.m_DotProduct.x*LobeToSubtract.C.x + Sample.m_DotProduct.y*LobeToSubtract.C.y + Sample.m_DotProduct.z*LobeToSubtract.C.z; LobeInfluence = Math.Max( 0.0, LobeInfluence ); LobeInfluence = Math.Pow( LobeInfluence, LobeToSubtract.N ); double CurrentBRDFValue = Context.m_BRDF[Sample.m_BRDFIndex]; CurrentBRDFValue *= Sample.m_CosThetaIn; // if ( CurrentBRDFValue > 100.0 ) // return 1; OldMaxValue = Math.Max( OldMaxValue, CurrentBRDFValue ); CurrentBRDFValue -= LobeInfluence; CurrentBRDFValue = Math.Max( 0, CurrentBRDFValue ); // Constrain to positive values only NewMaxValue = Math.Max( NewMaxValue, CurrentBRDFValue ); Context.m_BRDF[Sample.m_BRDFIndex] = CurrentBRDFValue; } } ////////////////////////////////////////////////////////////////////////// // 2] At this point, we have a set of SH lobes that are individual best fits to the goal SH coefficients // We will finally apply a global BFGS minimzation using all of the total cosine lobes // double[] GlobalLobeCoefficients = new double[1+4*_Lobes.Length]; // Don't forget the BFGS function annoyingly uses indices starting from 1! ms_TempCoefficientsGlobal = new double[1+4*_Lobes.Length]; // 2.1] Re-assign the original BRDF to which we compare to Context.m_BRDF = _BRDF; // 2.2] Re-assign the best lobes as initial best guess Context.m_Lobes = _Lobes; for ( int LobeIndex=0; LobeIndex < _Lobes.Length; LobeIndex++ ) { CosineLobe SourceLobe = _Lobes[LobeIndex]; GlobalLobeCoefficients[1+4*LobeIndex+0] = SourceLobe.C.x; GlobalLobeCoefficients[1+4*LobeIndex+1] = SourceLobe.C.y; GlobalLobeCoefficients[1+4*LobeIndex+2] = SourceLobe.C.z; GlobalLobeCoefficients[1+4*LobeIndex+3] = SourceLobe.N; } // 2.3] Apply BFGS minimzation to the entire set of coefficients int IterationsCountGlobal = 0; double FunctionMinimumGlobal = double.MaxValue; try { FunctionMinimumGlobal = dfpmin( GlobalLobeCoefficients, _BFGSConvergenceTolerance, out IterationsCountGlobal, new BFGSFunctionEval( BRDFMappingGlobalFunctionEval ), new BFGSFunctionGradientEval( BRDFMappingGlobalFunctionGradientEval ), Context ); } catch ( Exception ) { CrashesCount++; } // 2.4] Save the final optimized results for ( int LobeIndex=0; LobeIndex < _Lobes.Length; LobeIndex++ ) { CosineLobe TargetLobe = _Lobes[LobeIndex]; TargetLobe.C.Set( GlobalLobeCoefficients[1+4*LobeIndex+0], GlobalLobeCoefficients[1+4*LobeIndex+1], GlobalLobeCoefficients[1+4*LobeIndex+2] ); TargetLobe.N = GlobalLobeCoefficients[1+4*LobeIndex+3]; } // Give final 100% feedback if ( _Delegate != null ) _Delegate( 1.0f ); return FunctionMinimumGlobal; }
public void CopyFrom( CosineLobe _Source ) { C.x = _Source.C.x; C.y = _Source.C.y; C.z = _Source.C.z; N = _Source.N; Error = _Source.Error; }