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.