Atmospheric Scattering + Sun Shader

Posted By: oliver2s

Atmospheric Scattering + Sun Shader - 12/16/13 15:29

This code/shader simulates atmospheric scattering and renders the sun into the sky.

Because it's a bit tricky to set up everything right, I made a test level where you can directly see the results in realtime (and play with sun angles)

Download testlevel and source files:
http://www.stonehill-games.de/downloads/atmospheric_scattering_A8.rar

If you want to get real sun positions, use this script:
http://www.opserver.de/ubb7/ubbthreads.php?ubb=showflat&Number=434181

Shader code is taken from Sean O'Neil's GPU Gems 2:
http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html




EDIT: for those who want to set up the code by themselfs. Here you go:

1. create s centered sphere "sky_sphere.mdl" in MED with radius = 1 quant with inverted normals.

2. create the "atmospheric_scattering.fx" file:
Code:
#define PI 3.14159f

#define m_hdrExposure 1.25f

// Difference between inner and ounter radius. Must be 2.5%
#define m_outerScaleFactor 1.025f	

// Wave length of sun light
#define m_waveLength float3(0.65f, 0.57f, 0.475f) 

// Sun brightness constant
#define m_ESun 20.0f 			

// Rayleigh scattering constant
#define m_kr 0.0035f 	

// Mie scattering constant		
#define m_km 0.0010f 			

// The Mie phase asymmetry factor, must be between 0.999 to -0.999
#define m_g -0.99f			

// The scale depth (i.e. the altitude at which the atmosphere's average density is found)
#define m_scaleDepth 0.25f 	

#define radius vecSkill1.x


float4x4 matWorldViewProj;
float4x4 matWorld;
float4 vecViewPos;
float4 vecSunDir;
float4 vecSkill1;

float3 v3InvWavelength;
float fOuterRadius;				// The outer (planetary) radius
float fOuterRadius2;				// fOuterRadius^2
float fInnerRadius;				// The inner (planetary) radius
float fInnerRadius2;				// fInnerRadius^2
float fKrESun;						// Kr * ESun
float fKmESun;						// Km * ESun
float fKr4PI;						// Kr * 4 * PI
float fKm4PI;						// Km * 4 * PI
float fScale;						// 1 / (fOuterRadius - fInnerRadius)
float fScaleOverScaleDepth;	// fScale / fScaleDepth
float fHdrExposure;				// HDR exposure

struct v2f 
{
	float4 pos : POSITION;
	float2 uv : TEXCOORD0;
	float3 t0 : TEXCOORD1;
	float3 c0 : COLOR0;
	float3 c1 : COLOR1;
};

float scale(float fCos)
{
	float x = 1.0 - fCos;
	return 0.25 * exp(-0.00287 + x*(0.459 + x*(3.83 + x*(-6.80 + x*5.25))));
}

v2f VS (
float4 inPos : POSITION,
float2 inTexCoord : TEXCOORD0
)
{
	v2f Out;
	
	Out.pos = mul(inPos,matWorldViewProj);
	
	v3InvWavelength = float3(1.0f / pow(m_waveLength.x, 4.0f), 1.0f / pow(m_waveLength.y, 4.0f), 1.0f / pow(m_waveLength.z, 4.0f));
	fOuterRadius = radius * m_outerScaleFactor;
	fOuterRadius2 = fOuterRadius*fOuterRadius;		
	fInnerRadius = radius;			
	fInnerRadius2 = fInnerRadius*fInnerRadius;	
	fKrESun = m_kr*m_ESun;				
	fKmESun = m_km*m_ESun;				
	fKr4PI = m_kr*4.0f*PI;			
	fKm4PI = m_km*4.0f*PI;	
	fScale = (1.f / (fOuterRadius - fInnerRadius));					
	fScaleOverScaleDepth = fScale/m_scaleDepth;		

	float3 v3CameraPos = float3(0.f,radius,0.f); 								// The camera's current position
	float fCameraHeight = radius;													// The camera's current height
	//float fCameraHeight2 = fCameraHeight*fCameraHeight;		// fCameraHeight^2
	
	// 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 v3Pos = mul(matWorld, inPos).xyz;
	float3 v3Ray = v3Pos - 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 *scale(fStartAngle);
	
	float fSamples = 2.0;
	
	// Initialize the scattering loop variables
	float fSampleLength = fFar / fSamples;
	float fScaledLength = fSampleLength * fScale;
	float3 v3SampleRay = v3Ray * fSampleLength;
	float3 v3SamplePoint = v3Start + v3SampleRay * 0.5f;
	
	// Now loop through the sample rays
	float3 v3FrontColor = float3(0.0, 0.0, 0.0);
	for(int i=0; i<int(fSamples); i++)
	{
		float fHeight = length(v3SamplePoint);
		float fDepth = exp(fScaleOverScaleDepth * (fInnerRadius - fHeight));
		float fLightAngle = dot(vecSunDir.xyz*(-1.f), v3SamplePoint) / fHeight;
		float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight;
		float fScatter = (fStartOffset + fDepth*(scale(fLightAngle) - scale(fCameraAngle)));
		float3 v3Attenuate = exp(-fScatter * (v3InvWavelength * fKr4PI + fKm4PI));
		v3FrontColor += v3Attenuate * fDepth * fScaledLength;
		v3SamplePoint += v3SampleRay;
	}
	
	Out.uv = inTexCoord.xy;
	
	// Finally, scale the Mie and Rayleigh colors and set up the varying variables for the pixel shader
	Out.c0 = v3FrontColor * v3InvWavelength * fKrESun;
	Out.c1 = v3FrontColor * fKmESun;
	Out.t0 = v3CameraPos - v3Pos;

	return Out;
}

// Calculates the Mie phase function
float getMiePhase(float fCos, float fCos2, float g, float g2)
{
	return 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + fCos2) / pow(1.0 + g2 - 2.0*g*fCos, 1.5);
}

// Calculates the Rayleigh phase function
float getRayleighPhase(float fCos2)
{
	return 0.75 + 0.75*fCos2;
}

float4 PS(v2f IN) : COLOR
{

	float fCos = dot(vecSunDir.xyz*(-1.f), IN.t0) / length(IN.t0);
	float fCos2 = pow(fCos,2);
	float3 col = getRayleighPhase(fCos2) * IN.c0 + getMiePhase(fCos, fCos2, m_g, m_g*m_g) * IN.c1;

	//Adjust color from HDR
	fHdrExposure = m_hdrExposure;	
	col = 1.0 - exp(col * -fHdrExposure);

	return float4(col,1.0f);
}

technique tMain
{
	pass 
	{
		VertexShader = compile vs_3_0 VS(); 
		PixelShader = compile ps_3_0 PS();
	}
}



3. create the "atmospheric_scattering.c" script with following code:
Code:
#include <acknex.h>
#include <default.c>

var mtl_atmospheric_scattaring_radius=50000;

//atmospheric scattering material
MATERIAL* mtl_atmospheric_scattering =
{
	effect="atmospheric_scattering.fx";	
}

void init_atmospheric_scattering(ENTITY* sky_)
{
	//scale sky sphere to defined radius multiplied by factor 1.025 (difference of inner and outer atmosphere radius
	vec_scale(sky_.scale_x,mtl_atmospheric_scattaring_radius);
	vec_scale(sky_.scale_x,1.025);
	
	//pass sky sphere radius to material
	mtl_atmospheric_scattering.skill1=floatv(mtl_atmospheric_scattaring_radius);
}

void atmospheric_scattering_startup()
{
	//wait until level is loaded
	while(!level_ent){wait(1);}
	
	//create sky sphere
	ENTITY* sky_=ent_create("sky_sphere.mdl",nullvector,NULL);
	
	//set flags
	set(sky_,PASSABLE|UNTOUCHABLE|NOFOG);
	
	//assign material
	sky_.material=mtl_atmospheric_scattering;
	
	//update scale
	init_atmospheric_scattering(sky_);
	
	while(1)
	{
		//sky sphere needs to have always same position as camera minus sky sphere radius
		vec_set(sky_.x,camera.x);
		sky_.z-=mtl_atmospheric_scattaring_radius;

		wait(1);
	}
}




void main()
{
	video_screen=2;
	video_mode=8;

	//load level
	level_load(NULL);
}

Posted By: sivan

Re: Atmospheric Scattering + Sun Shader - 12/16/13 15:42

looks nice. is it okay only with a sphere sky model? maybe I will try it together with some other sky entities using alpha to blend them...
Posted By: oliver2s

Re: Atmospheric Scattering + Sun Shader - 12/16/13 15:44

Originally Posted By: sivan
is it okay only with a sphere sky model?

What do mean by "okay"? It's only working with a sphere sky model. But not defined as Sky Entity but as regular Entity (see code).
Posted By: jenGs

Re: Atmospheric Scattering + Sun Shader - 12/16/13 16:31

Isn't it possible to set the SCENE flag, so the sky never could clip other geometry?
Posted By: oliver2s

Re: Atmospheric Scattering + Sun Shader - 12/16/13 16:42

No that doesn't work, this was already discussed in another thread I found while facing this problem.
Posted By: sivan

Re: Atmospheric Scattering + Sun Shader - 12/17/13 08:10

I meant that thanks laugh
there are several bad things in 3dgs sky system I rreported earlier, but no correction can be expected:
- SCENE models are clipped
- image based sky cube covers world at around sqrt(2)*clip_far distance
- sky domes have no material assigned
anyway it is another great contribution, I'm sure it can be integrated with other skies hiding model edges by fog, dof or other effects.
Posted By: MasterQ32

Re: Atmospheric Scattering + Sun Shader - 12/17/13 10:47

Wow, nice contribution! Also the other terrain stuff is awesome.
I always wanted to have such a shader in some of my projects.
Posted By: PadMalcom

Re: Atmospheric Scattering + Sun Shader - 12/18/13 13:10

Do you plan to integrate that into IceX3?
Posted By: oliver2s

Re: Atmospheric Scattering + Sun Shader - 12/18/13 13:19

Yes, it will be integrated in IceX3.
Posted By: Slin

Re: Atmospheric Scattering + Sun Shader - 12/18/13 15:42

Nice that you went through with it laugh
When I read the article after seeing your post I decided to delay better sky rendering for now tongue
The results seem pretty good, but the code to get there is by far not as straight forward as I would like it to be.
Also I would like to do this on a per fragment basis instead of doing most things per vertex using a sky sphere with an even polygon distribution for clean results.

From just a quick look at your code, I do recommend you to use a define for fSamples. I know that without at least the glsl compiler on my mac wonīt unroll the loop and probably even take additional time for the cast to int. Especially real (as in not unrolled, dynamic) loops, have a serious performance impact.

Also about sky rendering in general: To make the sky appear behind everything you usually render the sky before everything else without depth writing and only applying the cameras rotation and projection matrix and if needed the skies rotation matrix. As a result it will appear behind everything else, wonīt be clipped and wonīt clip anything and just replace the clear color as it should.
As far as I know gamestudio does this by default, but since you messed with it more than I did you probably know better how you can or canīt use this shader with gamestudio sky system or replace it with your own custom sky rendering.
Posted By: oliver2s

Re: Atmospheric Scattering + Sun Shader - 12/18/13 17:13

Thanks for your suggestions.

Originally Posted By: Slin
Also about sky rendering in general: To make the sky appear behind everything you usually render the sky before everything else without depth writing and only applying the cameras rotation and projection matrix and if needed the skies rotation matrix. As a result it will appear behind everything else, wonīt be clipped and wonīt clip anything and just replace the clear color as it should.


Do you a tutorial or example how to do this?
Posted By: Slin

Re: Atmospheric Scattering + Sun Shader - 12/18/13 17:57

After looking at the manual, it looks like gamestudio renders the sky after all solid entities, which does have the advantage of less/no overdraw for the sky.
So in your case you may just want to disable writing to the depth buffer for the sky and render it with a kinda small radius.

Code:
technique tMain
{
	pass 
	{
		ZEnable = true;
		ZWriteEnable = false;
		VertexShader = compile vs_3_0 VS(); 
		PixelShader = compile ps_3_0 PS();
	}
}


and just use a radius of 1 or whatever you want. It should work just fine, unless I am messing things up...

And no, I have no idea if there are any tutorials about this.

Edit: Thatīs only if you want the sky to behave the same as a gamestudio sky cube. But then you also have to move it with the camera (what you already do), or make the shader ignore the translations.
Posted By: oliver2s

Re: Atmospheric Scattering + Sun Shader - 12/19/13 11:50

Originally Posted By: Slin

Code:
technique tMain
{
	pass 
	{
		ZEnable = true;
		ZWriteEnable = false;
		VertexShader = compile vs_3_0 VS(); 
		PixelShader = compile ps_3_0 PS();
	}
}



This doesn't work. Sky sphere is still rendered as before.
Posted By: oliver2s

Re: Atmospheric Scattering + Sun Shader - 12/19/13 22:24

I found a way to render the sky sphere behind everthing else. Set screen_color and sky_color to (0,0,0).

Then give the sky sphere FLAG1 and create another view which only renders the sky behind the level:

Code:
vec_set(sky_color,nullvector);
vec_set(screen_color,nullvector);
	
VIEW* camera_sky=view_create(-999999);
camera_sky.size_x=camera.size_x;
camera_sky.size_y=camera.size_y;
set(camera_sky,NOENT|SHOW|UNTOUCHABLE|NOPARTICLE|NOWORLD|NOFOG|NOSKY);
camera_sky.genius=ent_sky_sphere;

set(camera,NOFLAG1);
	
while(1)
{
	vec_set(camera_sky.x,camera.x);
	vec_set(camera_sky.pan,camera.pan);
		
	wait(1);	
}

© 2024 lite-C Forums