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;
}