Skip to main content
GameDev.net gamedev.net
🔒 Locked 🎮 Unity

Atmospheric Scattering and dark sky

Started by Volgut Oct 6, 2010 at 10:38 AM 1 replies 5.1k views
Original Post
Volgut
Volgut
Hello,

I am trying to implement atmospheric scattering effect based on O'Neils implementation from GPUGems2. Almost everything seems to be fine, but the sky colour is too dark. I already spent several hours trying to understand the problem and I am running out of ideas.

To render the effect, I create a sphere of a radius of 6370997.0f * 1.025f (radius of earth with atmosphere) and then I move it down by 6370997.0f in the shader (earth radius). It means that my camera is located on the North Pole.

static const float EARTH_RADIUS = 6370997.0f;static const float EARTH_ATMOSPHERE_RADIUS = EARTH_RADIUS * 1.025f;


From my experience I know that this atmospheric scattering algorithm is very fragile to input parameters which need to be exactly in the right range or otherwise the sky will look completely wrong. Here are my parameters:

float fRayleighScatteringConstant = 0.0025f;float fMieScatteringConstant = 0.0015f;float fSunBrightness = 18.0f;float fRayleighScaleDepth = 0.25f;float fMiePhaseAsymmetryFactor = -0.98f;Vector3 vWaveLength = Vector3( 0.650f, 0.570f, 0.475f );


This is how I pass all parameters to the shader:

const float fKr    = cAtmosphericScattering.fRayleighScatteringConstant; // Rayleigh scattering constantconst float fKr4PI = fKr * 4.0f * SkMath::Pi();const float fKm    = cAtmosphericScattering.fMieScatteringConstant; // Mie scattering constantconst float fKm4PI = fKm * 4.0f * SkMath::Pi();const float fScale = 1 / (EARTH_ATMOSPHERE_RADIUS - EARTH_RADIUS);Vector3 vInvWavelength;vInvWavelength.x = 1.0f / SkMath::Pow( cAtmosphericScattering.vWaveLength.x, 4.0f ); // 650 nm for redvInvWavelength.y = 1.0f / SkMath::Pow( cAtmosphericScattering.vWaveLength.y, 4.0f ); // 570 nm for greenvInvWavelength.z = 1.0f / SkMath::Pow( cAtmosphericScattering.vWaveLength.z, 4.0f ); // 475 nm for bluepEffect->SetVector( "v3InvWavelength", vInvWavelength );pEffect->SetFloat( "fInnerRadius", EARTH_RADIUS );pEffect->SetFloat( "fKrESun", fKr*cAtmosphericScattering.fSunBrightness );pEffect->SetFloat( "fKmESun", fKm*cAtmosphericScattering.fSunBrightness );pEffect->SetFloat( "fKr4PI", fKr4PI );pEffect->SetFloat( "fKm4PI", fKm4PI );pEffect->SetFloat( "fScale", fScale );pEffect->SetFloat( "fScaleDepth", cAtmosphericScattering.fRayleighScaleDepth );pEffect->SetFloat( "fScaleOverScaleDepth", fScale / cAtmosphericScattering.fRayleighScaleDepth );pEffect->SetFloat( "g", cAtmosphericScattering.fMiePhaseAsymmetryFactor );pEffect->SetFloat( "g2", cAtmosphericScattering.fMiePhaseAsymmetryFactor * cAtmosphericScattering.fMiePhaseAsymmetryFactor );float fCameraHeight = EARTH_RADIUS + vCameraPosition.y;		// vCameraPosition is my camera position in meterspEffect->SetVector( "v3CameraPos", SkVector3( 0.0f, fCameraHeight, 0.0f ) );pEffect->SetVector( "v3LightPos", vSunDirection );			// vSunDirection is my light vectorpEffect->SetFloat( "fCameraHeight", fCameraHeight );


Functions used by the shader:

float ExpScale(float fCos){	float x = 1.0 - fCos;	return fScaleDepth * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));}float GetRayleighPhase( float fCos2 ){	return 0.75 * (1.0 + fCos2);}float GetMiePhase( float fCos, float fCos2 ){	return 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + fCos2) / pow(1.0 + g2 - 2.0*g*fCos, 1.5);}


Vertex shader:
#define INTEGRAL_EQUATION_NUM_SAMPLES 2		// Number of sample rays to use in integral equationSVSOutput VS_Sky( float4 i_Position : POSITION ){	SVSOutput sOutput;		// This is a vertex position center earth center at (0,0,0)	float3 vPositionEarthCenter = i_Position.xyz;		// Transform vertex to the screen space	i_Position.y -= fInnerRadius;			// Here I move the mesh down	sOutput.vPosition = mul( i_Position, g_WorldViewProjection );		// Get the ray from the camera to the vertex, and its length (which is the far point of the ray passing through the atmosphere)	float3 v3Ray = vPositionEarthCenter - v3CameraPos;	float fFar = length(v3Ray);	v3Ray /= fFar;	// Calculate the ray's starting position, then calculate its scattering offset	float3 v3Start = v3CameraPos;	float fHeight = length(v3Start);	float fDepth = exp(fScaleOverScaleDepth * (fInnerRadius - fCameraHeight));	float fStartAngle = dot(v3Ray, v3Start) / fHeight;	float fStartOffset = fDepth*ExpScale(fStartAngle);	// Initialize the scattering loop variables	float fSampleLength = fFar / float( INTEGRAL_EQUATION_NUM_SAMPLES );	float fScaledLength = fSampleLength * fScale;	float3 v3SampleRay = v3Ray * fSampleLength;	float3 v3SamplePoint = v3Start + v3SampleRay * 0.5;	// Now loop through the sample rays	float3 v3FrontColor = float3(0.0, 0.0, 0.0);	for(int i=0; i< INTEGRAL_EQUATION_NUM_SAMPLES; i++)	{		float fHeight = length(v3SamplePoint);		float fDepth = exp(fScaleOverScaleDepth * (fInnerRadius - fHeight));		float fLightAngle = dot(v3LightPos, v3SamplePoint) / fHeight;		float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight;		float fScatter = (fStartOffset + fDepth*(ExpScale(fLightAngle) - ExpScale(fCameraAngle)));		float3 v3Attenuate = exp(-fScatter * (v3InvWavelength * fKr4PI + fKm4PI));		v3FrontColor += v3Attenuate * (fDepth * fScaledLength);		v3SamplePoint += v3SampleRay;	}	// Finally, scale the Mie and Rayleigh colours and set up the varying variables for the pixel shader	sOutput.vMieColor = v3FrontColor * fKmESun;	sOutput.vRayleighColor = v3FrontColor * (v3InvWavelength * fKrESun);	sOutput.vCameraToVertexDir = v3CameraPos - vPositionEarthCenter;}


Pixel shader:
SPSOutput PS_Sky( SVSOutput sInput ){	SPSOutput sOutput;	float fCos =dot(v3LightPos, sInput.vCameraToVertexDir) / length(sInput.vCameraToVertexDir);	float fCos2 = fCos * fCos;	float fRayleighPhase = GetRayleighPhase( fCos2 * fff );	float fMiePhase = GetMiePhase( fCos, fCos2 );	sOutput.vColor = float4( fRayleighPhase * sInput.vRayleighColor + fMiePhase * sInput.vMieColor, 1.0f );}


And a picture showing my sky (120deg FOV to show more sky):


I also should mention that I render my sky to HDR texture and I use following equation to convert colors to LDR:

vColor = 1.0 - exp( -2.0 * vColor )


Did anyone who implemented atmospheric scattering based on O'Neils algorithm have the same problem? Do you have any ideas how to fix my sky?

The easiest way to make the sky brighter is to increase sun brightness parameter (fSunBrightness in my code), but for values above 20.0f it creates very strong "halo" effect around the horizon.
terra0nova
terra0nova
Your HDR to LDR equation maps 0-infinity to 0-1, but this means that only infinite values will be mapped to white, and (1,1,1) will be mapped to (.865,.865,.865) gray. True HDR to LDR conversions take into account the average luminance in a neighborhood scale based on that so that dark areas will be lightened more, and light areas will be darkened more. However, to fix the dark sky, just use a different hdr to ldr conversion. Personally, I would just try gamma correction, replace vColor = 1.0 - exp( -2.0 * vColor ); with vColor = pow(vColor,1.0/2.2); This is still not a perfect hdr to ldr conversion, but it will brighten up the scene in a nice looking way.
smasherprog
smasherprog
I used a similar example to create mine, here are my variables in the shader. I will post the shader code as well if this info doesnt fix your problem.

const float InnerRadius = 6356.7523142;
const float OuterRadius = 6356.7523142 * 1.015;
const float fScale = 1.0 / (6452.103598913 - 6356.7523142);
const float KrESun = 0.0025 * 20.0;
const float KmESun = 0.0010 * 20.0;
const float Kr4PI = 0.0025 * 4.0 * 3.141592653;
const float Km4PI = 0.0010 * 4.0 * 3.141592653;
const int tNumSamples = 50;
const float invtNumSamples = 1.0f/50;
const int iNumSamples = 20;
const float inviNumSamples = 1.0f/20.0f;
const float2 v2dRayleighMieScaleHeight= float2( 0.25, 0.1 );
const float2 invv2dRayleighMieScaleHeight= float2( 1.0f/0.25, 1.0f/0.1 );
const float3 WavelengthMie = float3( pow( 0.650, -0.84 ), pow( 0.570, -0.84 ), pow( 0.475, -0.84 ) );
const float InvOpticalDepthN = 1.0 / 256.0;
const float3 v3HG = float3( 1.5f * ( (1.0f - (-0.995*-0.995)) / (2.0f + (-0.995*-0.995)) ), 1.0f + (-0.995*-0.995), 2.0f * -0.995 );
const float InvOpticalDepthNLessOne = 1.0 / ( 256.0 - 1.0 );
const float2 InvRayleighMieN = float2( 1.0 / 256.0, 1.0 / 128.0 );
const float2 InvRayleighMieNLessOne = float2( 1.0 / (256.0 - 1.0), 1.0 / (128.0 - 1.0) );
const float HalfTexelOpticalDepthN = float( 0.5 / 256.0 );
const float3 InvWavelength4 = float3( 1.0 / pow( 0.650, 4 ), 1.0 / pow( 0.570, 4 ), 1.0 / pow( 0.475, 4 ) );
const float OuterRadius2 = 6356.7523142 * 1.015 * 6356.7523142 * 1.015;
const float inv180 = 1.0f/180.0f;
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.