/// <summary> /// Builds the atmosphere lookup texture /// </summary> public AtmosphereBuildOutputs Build( ) { AtmosphereBuildProgress progress = new AtmosphereBuildProgress( ); progress.SliceCompleted += delegate( float p ) { m_Worker.ReportProgress( ( int )( p * 100.0f ) ); if ( m_Worker.CancellationPending ) { progress.Cancel = true; } }; return m_Atmosphere.Build( m_AtmosphereModel, m_AtmosphereBuildParameters, progress ); }
/// <summary> /// Builds a 3D lookup texture containing the scattering values for a parameterization of /// the view frame and sun position. /// </summary> /// <param name="parameters">Parameters for the build process</param> /// <param name="progress">Progress object</param> /// <param name="voxels">3d texture voxels</param> /// <returns>true if build completed (wasn't cancelled)</returns> private unsafe bool BuildScatteringTexture( AtmosphereBuildParameters parameters, byte* voxels, AtmosphereBuildProgress progress ) { float viewAngle = Constants.Pi; float viewAngleInc = Constants.Pi / ( parameters.ViewAngleSamples - 1 ); float heightRange = ( m_OuterRadius - m_InnerRadius ); float heightInc = ( heightRange * 0.9999f ) / ( parameters.HeightSamples - 1 ); // Push height range in slightly to allow simplification of sphere intersections float sunAngleInc = ( Constants.Pi ) / ( parameters.SunAngleSamples - 1 ); for ( int viewAngleSample = 0; viewAngleSample < parameters.ViewAngleSamples; ++viewAngleSample, viewAngle -= viewAngleInc ) { Vector3 viewDir = new Vector3( Functions.Sin( viewAngle ), Functions.Cos( viewAngle ), 0 ); float sunAngle = 0; for ( int sunAngleSample = 0; sunAngleSample < parameters.SunAngleSamples; ++sunAngleSample, sunAngle += sunAngleInc ) { Vector3 sunDir = new Vector3( Functions.Sin( sunAngle ), Functions.Cos( sunAngle ), 0 ); float height = m_InnerRadius; for ( int heightSample = 0; heightSample < parameters.HeightSamples; ++heightSample, height += heightInc ) { ComputeScattering( viewDir, sunDir, height, voxels ); voxels += 4; } } if ( progress != null ) { progress.OnSliceCompleted( viewAngleSample / ( float )( parameters.ViewAngleSamples - 1 ) ); if ( progress.Cancel ) { return false; } } } return true; }
/// <summary> /// Builds a 2D lookup texture containing optical depth for a paramerization of the view frame /// </summary> /// <param name="buildParams">Atmosphere build parameters</param> /// <param name="pixels">Atmosphere texture pixels</param> /// <param name="progress">Progress indicator</param> /// <returns>Returns true if the build completed (wasn't cancelled)</returns> private unsafe bool BuildOpticalDepthTexture( AtmosphereBuildParameters buildParams, byte* pixels, AtmosphereBuildProgress progress ) { // TODO: AP: Because we know that the view to pos angle range will never be > pi, can optimise this later int viewSamples = buildParams.OpticalDepthResolution; int heightSamples = buildParams.OpticalDepthResolution; float viewAngleInc = Constants.Pi / ( viewSamples - 1 ); float heightRange = ( m_OuterRadius - m_InnerRadius ); float heightInc = ( heightRange * 0.9999f ) / ( heightSamples - 1 ); // Push height range in slightly to allow simplification of sphere intersections float* rAccum = stackalloc float[ 3 ]; float* mAccum = stackalloc float[ 3 ]; float height = m_InnerRadius; for ( int heightSample = 0; heightSample < heightSamples; ++heightSample, height += heightInc ) { Point3 pos = new Point3( 0, height, 0 ); // Start the view angle at pi, and count down to 0. This is because it is quickest to address // the 2D texture using the dot of the view vector and the view position, saving a (1-th) operation float viewAngle = Constants.Pi; Point3 lastAtmInt = pos; for ( int viewSample = 0; viewSample < viewSamples; ++viewSample, viewAngle -= viewAngleInc ) { Vector3 viewDir = new Vector3( Functions.Sin( viewAngle ), Functions.Cos( viewAngle ), 0 ); // NOTE: If ray intersection fails, the previous intersection position is used... Point3 atmInt; // if ( !GetRayPlanetAndAtmosphereIntersection( pos, viewDir, out atmInt ) ) if ( !GetRayAtmosphereIntersection( pos, viewDir, out atmInt ) ) { atmInt = lastAtmInt; } else { lastAtmInt = atmInt; } Vector3 step = ( atmInt - pos ) / m_AttenuationSamples; rAccum[ 0 ] = rAccum[ 1 ] = rAccum[ 2 ] = 0; mAccum[ 0 ] = mAccum[ 1 ] = mAccum[ 2 ] = 0; CalculateOutScatter( pos, step, rAccum, mAccum ); // float oR = CalculateCombinedOutScatter( pos, step, m_RayleighCoefficients[ 0 ], m_MieCoefficients[ 0 ] ); // float oG = CalculateCombinedOutScatter( pos, step, m_RayleighCoefficients[ 1 ], m_MieCoefficients[ 1 ] ); // float oB = CalculateCombinedOutScatter( pos, step, m_RayleighCoefficients[ 2 ], m_MieCoefficients[ 2 ] ); float attR = ExtinctionCoefficient( rAccum[ 0 ], mAccum[ 0 ] ); float attG = ExtinctionCoefficient( rAccum[ 1 ], mAccum[ 1 ] ); float attB = ExtinctionCoefficient( rAccum[ 2 ], mAccum[ 2 ] ); pixels[ 2 ] = ( byte )( Utils.Clamp( attR, 0, 1 ) * 255.0f ); pixels[ 1 ] = ( byte )( Utils.Clamp( attG, 0, 1 ) * 255.0f ); pixels[ 0 ] = ( byte )( Utils.Clamp( attB, 0, 1 ) * 255.0f ); pixels += 3; } if ( progress != null ) { progress.OnSliceCompleted( heightSample / ( float )( heightSamples - 1 ) ); if ( progress.Cancel ) { return false; } } } return true; }
// // Discrepancies in atmospheric modelling papers: // - HG: // - Neilsen: HG(t,g) = (1-g2)/(4pi(1 + g2 - 2cos(t))^1.5) // - Wolfram: HG(t,g) = (1-g2)/(1 + g2 - 2.g.cos(t))^1.5 // - O'Neil (adapted HG): HG(t,g) = 3(1-g2)/2(2+g2) . (1+t2)/(1+g2 - 2gt)^1.5 // - O'Neil and Wolfram work well. Neilsen doesn't (denominator can evalute to < 0 before raising to 3/2 power) // - Scattering coefficients: (# is wavelength) // - Neilsen Rayleigh coefficient: 8pi^2(n2-1)^2/3N#^4 // - Neilsen Mie coefficient: 0.434c.pi.4pi^2/#^2K // - K=non-wavelength dependent fudge factor for Bm: ~0.67 // - c=concentration factor(0.6544T-0.6510).10e-16 (T=turbidity. ~555nm) // // // // // Realtime rendering of atmospheric scattering for flight simulators: // http://www2.imm.dtu.dk/pubdb/views/edoc_download.php/2554/pdf/imm2554.pdf // // Display of The Earth Taking into Account Atmospheric Scattering: (Nishita) // http://nis-lab.is.s.u-tokyo.ac.jp/~nis/abs_sig.html#sig93 // // Implementation of Nishita's paper: // http://www.gamedev.net/reference/articles/article2093.asp // // A practical analytical model of daylight: // http://www.cs.utah.edu/vissim/papers/sunsky/sunsky.pdf // // Discussion about atmospheric shaders: // http://www.gamedev.net/community/forums/topic.asp?topic_id=335023 // // Paper that this model is based on: // http://www.vis.uni-stuttgart.de/~schafhts/HomePage/pubs/wscg07-schafhitzel.pdf // // Heyney-Greenstein on Mathworld: // http://scienceworld.wolfram.com/physics/Heyney-GreensteinPhaseFunction.html // /// <summary> /// Builds a 3D lookup texture /// </summary> /// <param name="model">The atmosphere model</param> /// <param name="parameters">Parameters for the build process</param> /// <param name="progress">Optional progress object</param> /// <returns>Returns a 3d texture data. Returns null if cancel was flagged in the progress object</returns> public unsafe AtmosphereBuildOutputs Build( AtmosphereBuildModel model, AtmosphereBuildParameters parameters, AtmosphereBuildProgress progress ) { Texture2dData opticalDepthTexture = new Texture2dData( ); Texture3dData scatteringTexture = new Texture3dData( ); scatteringTexture.Create( parameters.ViewAngleSamples, parameters.SunAngleSamples, parameters.HeightSamples, TextureFormat.R8G8B8A8 ); SetupModelAndParameters( parameters, model ); progress = progress ?? new AtmosphereBuildProgress( ); fixed ( byte* voxels = scatteringTexture.Bytes ) { if ( !BuildScatteringTexture( parameters, voxels, progress ) ) { return null; } } if ( progress.Cancel ) { return null; } opticalDepthTexture.Create( parameters.OpticalDepthResolution, parameters.OpticalDepthResolution, TextureFormat.R8G8B8 ); fixed ( byte* pixels = opticalDepthTexture.Bytes ) { if ( !BuildOpticalDepthTexture( parameters, pixels, progress ) ) { return null; } } return new AtmosphereBuildOutputs( scatteringTexture, opticalDepthTexture ); }
/// <summary> /// Builds the atmosphere /// </summary> private void BuildWorkItem( object sender, DoWorkEventArgs args ) { AtmosphereBuildProgress progress = new AtmosphereBuildProgress( ); progress.SliceCompleted += delegate( float p ) { m_Worker.ReportProgress( ( int )( p * 100.0f ) ); if ( m_Worker.CancellationPending ) { progress.Cancel = true; } }; AtmosphereBuildOutputs outputs = m_AtmosphereBuilder.Build( m_AtmosphereModel, m_AtmosphereBuildParameters, progress ); args.Result = outputs; }
/// <summary> /// Builds the atmosphere outputs /// </summary> public void Build( AtmosphereBuildProgress progress ) { m_AtmosphereOutputs = new AtmosphereBuilder( ).Build( AtmosphereModel, AtmosphereParameters, progress ); OnTemplateChanged( ); }