Exemplo n.º 1
0
        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 );
            }
        }
Exemplo n.º 2
0
        /// <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;
        }