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:
3. create the "atmospheric_scattering.c" script with following code:
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); }