Atmospheric scattering (O'Niel)

Started by
2 comments, last by elurahu 16 years, 10 months ago
I managed to implement the Preetham algoritm a couple of days ago, but didn't really like the result all that much. I am now trying to implement Sean O'Niels method as explained in GPU Gems 2 chapter 16. Since I'm only using the method to color my skydome I don't really need the ability to simulate the camera height. I'm having some problems getting the colors right though, and was hoping someone would be kind enough to help out. My setup: A geosphere with radius 1 centered around the camera with normals inverted. The sphere will always stay cented on the camera. The HLSL shader used to draw the skydome:
[source lang=cpp]
float4x4 worldViewProj;

float3 lightPosition;	// Light direction

float3 invWavelength;	// 1 / pow(wavelength, 4) for RGB channels

// Scattering parameters
float KrESun;			// Kr * ESun
float KmESun;			// Km * ESun
float Kr4PI;			// Kr * 4 * PI
float Km4PI;			// Km * 4 * PI

// Phase function
float g;
float g2;

float scale;			// 1 / (outerRadius - innerRadius) = 4 here
float scaleDepth;		// Where the average atmosphere density is found
float scaleOverScaleDepth;	// scale / scaleDepth

int numSamples;
float samples;

// Application to vertex structure
struct a2v
{
	float4 Position : POSITION0;
};

// Vertex to pixel shader structure
struct v2p
{
	float4 Position			: POSITION0;
	float4 RayleighColor	: COLOR0;
	float4 MieColor			: COLOR1;
	float3 Direction		: TEXCOORD0;
};

float scaleAngle(float cos)
{
	float x = 1.0 - cos;
	return scaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));
}

void RenderSkyVS(in a2v IN, out v2p OUT)
{
	// Transform to clipspace
	OUT.Position = mul(IN.Position, worldViewProj);
	
	// Get the ray from the camera to the vertex, and it's length (far point)
	float3 ray = IN.Position;
	float far = length(ray);
	ray /= far;
			
	// Init loop variables
	float sampleLength = far / samples;
	float scaledLength = sampleLength * scale;
	float3 sampleRay = ray * sampleLength;
	float3 samplePoint = sampleRay * 0.5f;
	
	// Loop the ray
	float3 color;
	for (int i = 0; i < numSamples; i++)
	{
		float height = length(samplePoint);
		float depth = exp(scaleOverScaleDepth * -height);
		
		float lightAngle = dot(lightPosition, samplePoint) / height;
		float cameraAngle = dot(ray, samplePoint) / height;
		
		float scatter = (depth * (scaleAngle(lightAngle) - scaleAngle(cameraAngle)));
		float3 attenuate = exp(-scatter * (invWavelength * Kr4PI + Km4PI));
		
		// Accumulate color
		color += attenuate * (depth * scaledLength);
		
		// Next sample point
		samplePoint += sampleRay;
	}
	
	// Finally, scale the Mie and Rayleigh colors
	OUT.RayleighColor.xyz = color * (invWavelength * KrESun);
	OUT.RayleighColor.w = 1.0f;
	
	OUT.MieColor.xyz = color * KmESun;
	OUT.MieColor.w = 1.0f;

	OUT.Direction = -IN.Position;
}

float4 RenderSkyPS(in v2p IN) : COLOR0
{
	float cos = dot(lightPosition, IN.Direction) / length(IN.Direction);
	
	float rayleighPhase = 0.75f * (1.0f + cos*cos);
	float miePhase = 1.5f * ((1.0f - g2) / (2.0f + g2)) *
					 (1.0f + cos*cos) / pow(1.0f + g2 - 2.0f * g * cos, 1.5f);
	
	return rayleighPhase * IN.RayleighColor + miePhase * IN.MieColor;
}

technique RenderSky
{
	pass p0
	{	
		VertexShader = compile vs_2_0 RenderSkyVS();
		PixelShader = compile ps_2_0 RenderSkyPS();
		
		ZWriteEnable = false;
	}	
}


The host application code:
[source lang=csharp]
    public class Atmosphere
    {
        // General
        private BaseGame m_game;

        // Atmosphere settings        
        private Vector3 m_lightPosition;
        private Vector3 m_lightDirection;

        private float[] m_wavelength;
        private float[] m_wavelength4;
        private float[] m_invWavelength4;
        
        private int m_numSamples;
        
        private float m_Kr, m_Kr4PI;
        private float m_Km, m_Km4PI;
        
        private float m_ESun;
        private float m_g;

        private float m_rayleighScaleDepth;

        // Geometry
        private Model m_skyDome;        

        public Atmosphere(BaseGame game)
        {
            m_game = game;

            // Setup the atmosphere
            m_lightPosition = new Vector3(1000, 1000, 1000);
            m_lightDirection = m_lightPosition / m_lightPosition.Length();

            m_wavelength = new float[3];
            m_wavelength[0] = 0.650f; // 650nm for red
            m_wavelength[1] = 0.570f; // 570nm for green
            m_wavelength[2] = 0.475f; // 475nm for blue

            m_wavelength4 = new float[3];
            m_wavelength4[0] = (float)Math.Pow(m_wavelength[0], 4);
            m_wavelength4[1] = (float)Math.Pow(m_wavelength[1], 4);
            m_wavelength4[2] = (float)Math.Pow(m_wavelength[2], 4);

            m_invWavelength4 = new float[3];
            m_invWavelength4[0] = 1 / m_wavelength4[0];
            m_invWavelength4[1] = 1 / m_wavelength4[1];
            m_invWavelength4[2] = 1 / m_wavelength4[2];

            m_numSamples = 4;		// Number of sample rays to use in integral equation

            m_rayleighScaleDepth = 0.25f;

            m_Kr = 0.0025f;		// Rayleigh scattering constant
            m_Kr4PI = m_Kr * 4.0f * MathHelper.Pi;
            
            m_Km = 0.0015f;		// Mie scattering constant
            m_Km4PI = m_Km * 4.0f * MathHelper.Pi;
            
            m_ESun = 15.0f;		// Sun brightness constant
            m_g = -0.95f;		// The Mie phase asymmetry factor

            // Load sky effect
            m_skyDome = m_game.ContentManager.Load<Model>("Content/Models/SkyDome");
        }

        public void Draw(GameTime gameTime)
        {
            foreach (ModelMesh mesh in m_skyDome.Meshes)
            {
                foreach (Effect effect in mesh.Effects)
                {
                    effect.Parameters["worldViewProj"].SetValue(Matrix.CreateTranslation(Camera.ActiveCamera.Position) * 
                                                                Camera.ActiveCamera.ViewMatrix *
                                                                Camera.ActiveCamera.ProjectionMatrix);

                    effect.Parameters["lightPosition"].SetValue(m_lightDirection);

                    effect.Parameters["invWavelength"].SetValue(m_invWavelength4);
                                        
                    effect.Parameters["KrESun"].SetValue(m_Kr * m_ESun);
                    effect.Parameters["KmESun"].SetValue(m_Km * m_ESun);
                    effect.Parameters["Kr4PI"].SetValue(m_Kr4PI);
                    effect.Parameters["Km4PI"].SetValue(m_Km4PI);

                    effect.Parameters["scale"].SetValue(4);
                    effect.Parameters["scaleDepth"].SetValue(m_rayleighScaleDepth);
                    effect.Parameters["scaleOverScaleDepth"].SetValue(4 / m_rayleighScaleDepth);
                    
                    effect.Parameters["g"].SetValue(m_g);
                    effect.Parameters["g2"].SetValue(m_g * m_g);

                    effect.Parameters["numSamples"].SetValue(m_numSamples);
                    effect.Parameters["samples"].SetValue(m_numSamples);
                }

                mesh.Draw();
            }
        }
    }


This is more a less a direct port from Sean O'niels reference implementation found on the books CD. The only difference is I've removed all references of the camera height and inner / outer radius of the planet. Problem is the dome shows up as black when running this! However - if I change: // Accumulate color color += attenuate * (depth * scaledLength); to this: // Accumulate color color += attenuate; I get a blue sky (with two suns which is still not right) as shown here I really hope some of the math wizards found here can help me out.
Advertisement
Keep InnerRadius/OuterRadius and CameraHeight, they are needed even if you stay on ground.

for your dome :
OuterRadius = 1.0f; // = your dome's radius
InnerRadius = OuterRadius * 0.975609f;

or

InnerRadius = 1.0f;
OuterRadius = InnerRadius * 1.025f; // = your dome's radius

set your camera (constant if you stay on ground):
Eye.x = 0.0f;
Eye.y = InnerRadius + 1.0e-6f;
Eye.z = 0.0f;

CameraHeight = Eye.y;

PS: in your example scale ~= 40 not 4;
Interesting. Thank you (again) filousnt. I'll repport back later when I've had the chance to implement it.
Looks like I got it work. Thank you for the help.

This topic is closed to new replies.

Advertisement