|
|
|
Return to Code Snippets
by croxis » Mon Mar 28, 2011 6:33 pm
I finally did it! Real time atmospheric scattering from space! This method is based on Sean O'Neil's work posted on his website and GPU Gems 2. This specific shader is only designed to be viewed from outside the atmosphere. This sample uses default camera controls, so the camera must be zoomed out first.
Uploaded with ImageShack.us
The only issue I found so far is that the atmosphere does not dissipate with altitude and quite white when viewed from the sun side. If anyone has any ideas on how to fix it I would love the input.
Python code. Requires the planet model found in the solar system sample as well as a sphere mesh with the normals inverted. You can use the planet model with a negative scale, but the atmosphere would be on the wrong side.
- Code: Select all
from pandac.PandaModules import loadPrcFileData
loadPrcFileData( '', 'frame-rate-meter-scale 0.035' ) loadPrcFileData( '', 'frame-rate-meter-side-margin 0.1' ) loadPrcFileData( '', 'show-frame-rate-meter 1' ) loadPrcFileData( '', 'window-title Atmosphere Demo' ) loadPrcFileData('', "sync-video 0")
from direct.directbase.DirectStart import * from direct.task import Task from panda3d.core import Shader, PointLight import math base.setBackgroundColor(0.0, 0.0, 0.0)
light = render.attachNewNode( PointLight( "sunPointLight" ) ) light.setPos(1000,0,0) render.setLight( light ) sun = loader.loadModel("planet_sphere") sun.reparentTo(render) sun.setPos(1050,0,0) sun.setScale(5)
earth = loader.loadModel("planet_sphere") earth.reparentTo(render)
# This is a sphere with the normals flipped atmo = loader.loadModel("atmo") atmo.reparentTo(earth) atmo.reparentTo(render) atmo.setScale(1.025)
outerRadius = abs((earth.getScale().getX() * atmo.getScale().getX())) scale = 1/(outerRadius - earth.getScale().getX()) atmo.setShaderInput("fOuterRadius", outerRadius) atmo.setShaderInput("fInnerRadius", earth.getScale().getX()) atmo.setShaderInput("fOuterRadius2", outerRadius * outerRadius) atmo.setShaderInput("fInnerRadius2", earth.getScale().getX() * earth.getScale().getX())
atmo.setShaderInput("fKr4PI", 0.0025 * 4 * 3.14159) atmo.setShaderInput("fKm4PI", 0.0015 * 4 * 3.14159)
atmo.setShaderInput("fScale", scale) atmo.setShaderInput("fScaleDepth", 0.25) atmo.setShaderInput("fScaleOverScaleDepth", scale/0.5)
# Currently hardcoded in shader atmo.setShaderInput("fSamples", 3.0) atmo.setShaderInput("nSamples", 3)
# These do sunsets and sky colors # Brightness of sun ESun = 15 # Reyleight Scattering (Main sky colors) atmo.setShaderInput("fKrESun", 0.0025 * ESun) # Mie Scattering -- Haze and sun halos atmo.setShaderInput("fKmESun", 0.0015 * ESun) # Color of sun atmo.setShaderInput("v3InvWavelength", 1.0 / math.pow(0.650, 4), 1.0 / math.pow(0.570, 4), 1.0 / math.pow(0.475, 4)) atmo.setShaderInput("v3CameraPos", base.camera.getPos().getX(), base.camera.getPos().getY(), base.camera.getPos().getZ()) # Light vector from center of planet. lightv = light.getPos() lightdir = lightv / lightv.length() atmo.setShaderInput("v3LightPos", lightdir[0], lightdir[1], lightdir[2]) atmo.setShaderInput("fCameraHeight", base.camera.getPos().length()) atmo.setShaderInput("fCameraHeight2", base.camera.getPos().length()*base.camera.getPos().length())
atmo.setShaderInput("g", 0.90) atmo.setShaderInput("g2", 0.81) atmo.setShaderInput("float", 2)
atmoShader = Shader.load("shader.cg") atmo.setShader(atmoShader)
def shaderUpdate(task): atmo.setShaderInput("v3CameraPos", base.camera.getPos().getX(), base.camera.getPos().getY(), base.camera.getPos().getZ()) atmo.setShaderInput("fCameraHeight", base.camera.getPos().length()) atmo.setShaderInput("fCameraHeight2", base.camera.getPos().length()*base.camera.getPos().length()) return task.cont
taskMgr.add(shaderUpdate, 'shaderUpdate') run()
shader.cg - Code: Select all
//Cg // http://stainlessbeer.weebly.com/planets-9-atmospheric-scattering.html // http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html // http://sponeil.net/ float g =-0.90f; float g2 = 0.81f; float fExposure =2;
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))));
} void vshader(float4 vtx_position:POSITION, uniform float4 k_v3LightPos, // The light's current position in uniform float4 k_v3CameraPos, // The camera's current position in uniform float4 k_v3InvWavelength, // 1 / pow(wavelength, 4) for RGB in uniform float4 k_fCameraHeight, // The camera's current height in uniform float4 k_fCameraHeight2, // fCameraHeight^2 in uniform float4 k_fOuterRadius, // The outer (atmosphere) radius in uniform float4 k_fOuterRadius2, // fOuterRadius^2 in uniform float4 k_fInnerRadius, // The inner (planetary) radius in uniform float4 k_fInnerRadius2, // fInnerRadius^2 in uniform float4 k_fKrESun, // Kr * ESun in uniform float4 k_fKmESun, // Km * ESun in uniform float4 k_fKr4PI, // Kr * 4 * PI in uniform float4 k_fKm4PI, // Km * 4 * PI in uniform float4 k_fScale, // 1 / (fOuterRadius - fInnerRadius) in uniform float4 k_fScaleOverScaleDepth, // fScale / fScaleDepth in uniform float4 k_fScaleDepth, // The scale depth (i.e. the altitude at which the atmosphere's average density is found) in uniform float4 k_fSamples, in uniform float4 k_nSamples, uniform float4x4 mat_modelproj, out float4 l_position : POSITION, out float4 l_color0 : COLOR0, out float4 l_color1 : COLOR1, out float3 l_v3Direction: TEXCOORD0 ) { float3 v3Pos = vtx_position.xyz; float3 v3Ray = v3Pos - k_v3CameraPos.xyz; float fFar = length(v3Ray); v3Ray /= fFar; // Calculate the closest intersection of the ray with the outer atmosphere // (which is the near point of the ray passing through the atmosphere) float B = 2.0 * dot(k_v3CameraPos.xyz, v3Ray); float C = k_fCameraHeight2.x - k_fOuterRadius2.x; float fDet = max(0.0, B*B - 4.0 * C); float fNear = 0.5 * (-B - sqrt(fDet)); // Calculate the ray's start and end positions in the atmosphere, // then calculate its scattering offset float3 v3Start = k_v3CameraPos.xyz + v3Ray * fNear; fFar -= fNear; // Initialize the scattering loop variables float fSampleLength = fFar / k_fSamples.x; float fScaledLength = fSampleLength * k_fScale.x; float3 v3SampleRay = v3Ray * fSampleLength; float3 v3SamplePoint = v3Start + (v3SampleRay * 0.5); float fHeight = length(v3SamplePoint); float fStartAngle = dot(v3Ray, v3Start) / k_fOuterRadius.x; float fStartDepth = exp(-1.0 / k_fScaleDepth.x); float fStartOffset = fStartDepth * scale(fStartAngle); // Now loop through the sample points float3 v3FrontColor = float3(0, 0.0, 0.0); for(int i=0; i<3; i++) { float fHeight = length(v3SamplePoint); float fDepth = exp(k_fScaleOverScaleDepth.x * (k_fInnerRadius.x-fHeight.x)); float fLightAngle = dot(k_v3LightPos.xyz, v3SamplePoint) / fHeight; float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight; float fScatter = (fStartOffset + fDepth * (scale(fLightAngle) - scale(fCameraAngle))); float3 v3Attenuate = exp(-fScatter *(k_v3InvWavelength.xyz * k_fKr4PI.x + k_fKm4PI.x)); v3FrontColor += v3Attenuate * (fDepth * fScaledLength); v3SamplePoint += v3SampleRay; } l_color0.rgb = v3FrontColor.xyz * k_v3InvWavelength.xyz * k_fKrESun.x; l_color1.rgb=v3FrontColor*k_fKmESun.x; l_position=mul(mat_modelproj, vtx_position); l_v3Direction =k_v3CameraPos.xyz - v3Pos; } void fshader(in float4 l_position : POSITION, in float4 l_color0 : COLOR0, in float4 l_color1 : COLOR1, in float3 l_v3Direction : TEXCOORD0 , uniform float4 k_v3LightPos, out float4 o_color:COLOR) { float fCos = dot(k_v3LightPos, l_v3Direction) / length(l_v3Direction); float fRayleighPhase = 0.75 * (1.0 + fCos*fCos); float fMiePhase= 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + fCos*fCos) / pow(1.0 + g2 - 2.0*g*fCos, 1.5); //This causes a brightness reduction when viewing at 90 degrees //float4 col=(fRayleighPhase * l_color0) + (fMiePhase * l_color1); float4 col=(l_color0) + (fMiePhase * l_color1); col.a = col.b; //In some versions, not in others. Not sure what impact it has //o_color = 1 - exp(-fExposure * col); o_color = col; }
David - Proud to be saving the world sense 1984
-
croxis
-
- Posts: 408
- Joined: Thu Feb 12, 2009 4:13 pm
- Location: Portland, OR
-
by ThomasEgi » Tue Mar 29, 2011 6:40 am
you could multiply the result with 1 - dot product between view and surface normal. which would fade it out towards the sky. ...at least i think it should.
-

ThomasEgi
-
- Posts: 2147
- Joined: Fri Jul 28, 2006 10:43 am
- Location: Germany,Koblenz
by rootWOLF » Thu Apr 07, 2011 8:20 am
looks pretty good!
-

rootWOLF
- Troll
-
- Posts: 79
- Joined: Wed Jan 12, 2011 4:54 pm
by aeiou » Tue Jan 03, 2012 9:50 am
I may have got this working a bit better, by fine-tuning the input parameters, and changing a bit of the shader code.
However, it only seems to work with the solar_sky_sphere model from the Solar-System sample. This could be due to the fact that I don't know how to properly create .egg models. Also, the earth scale must be 1.0 and the atmo scale must be around 1.025. It fails at other sizes unfortunately (even when using the 2.5% ratio specified by O'Neil)
- Code: Select all
from pandac.PandaModules import loadPrcFileData
loadPrcFileData( '', 'frame-rate-meter-scale 0.035' ) loadPrcFileData( '', 'frame-rate-meter-side-margin 0.1' ) loadPrcFileData( '', 'show-frame-rate-meter 1' ) loadPrcFileData( '', 'window-title Atmosphere Demo' ) loadPrcFileData('', "sync-video 0")
from direct.directbase.DirectStart import * from direct.task import Task from panda3d.core import Shader, PointLight import math base.setBackgroundColor(0.0, 0.0, 0.0)
light = render.attachNewNode( PointLight( "sunPointLight" ) ) light.setPos(1000,0,0) render.setLight( light ) sun = loader.loadModel("models/solar_sky_sphere") sun.reparentTo(render) sun.setPos(1050,0,0) sun.setScale(5)
earth = loader.loadModel("models/planet_sphere") earth.reparentTo(render)
# This is a sphere with the normals flipped atmo = loader.loadModel("models/solar_sky_sphere") #atmo.reparentTo(earth) atmo.reparentTo(render) atmo.setScale(1.025)
#outerRadius = abs((earth.getScale().getX() * atmo.getScale().getX())) # ????? outerRadius = atmo.getScale().getX() scale = 1/(outerRadius - earth.getScale().getX()) atmo.setShaderInput("fOuterRadius", outerRadius) atmo.setShaderInput("fInnerRadius", earth.getScale().getX()) atmo.setShaderInput("fOuterRadius2", outerRadius * outerRadius) atmo.setShaderInput("fInnerRadius2", earth.getScale().getX() * earth.getScale().getX())
atmo.setShaderInput("fKr4PI", 0.000055 * 4 * 3.14159) atmo.setShaderInput("fKm4PI", 0.000015 * 4 * 3.14159)
atmo.setShaderInput("fScale", scale) atmo.setShaderInput("fScaleDepth", 0.5) atmo.setShaderInput("fScaleOverScaleDepth", scale/0.5)
# Currently hardcoded in shader atmo.setShaderInput("fSamples", 10.0) atmo.setShaderInput("nSamples", 10)
# These do sunsets and sky colors # Brightness of sun ESun = 15 # Reyleight Scattering (Main sky colors) atmo.setShaderInput("fKrESun", 0.000055 * ESun) # Mie Scattering -- Haze and sun halos atmo.setShaderInput("fKmESun", 0.000015 * ESun) # Color of sun atmo.setShaderInput("v3InvWavelength", 1.0 / math.pow(0.650, 4), 1.0 / math.pow(0.570, 4), 1.0 / math.pow(0.465, 4)) atmo.setShaderInput("v3CameraPos", base.camera.getPos().getX(), base.camera.getPos().getY(), base.camera.getPos().getZ()) # Light vector from center of planet. lightv = light.getPos() lightdir = lightv / lightv.length()
atmo.setShaderInput("v3LightPos", lightdir[0], lightdir[1], lightdir[2]) atmo.setShaderInput("fCameraHeight", base.camera.getPos().length()) atmo.setShaderInput("fCameraHeight2", base.camera.getPos().length()*base.camera.getPos().length())
atmo.setShaderInput("g", 0.90) atmo.setShaderInput("g2", 0.81) atmo.setShaderInput("float", 2)
atmoShader = Shader.load("shader.cg") atmo.setShader(atmoShader)
def shaderUpdate(task): atmo.setShaderInput("v3CameraPos", base.camera.getPos().getX(), base.camera.getPos().getY(), base.camera.getPos().getZ()) atmo.setShaderInput("fCameraHeight", base.camera.getPos().length()) atmo.setShaderInput("fCameraHeight2", base.camera.getPos().length()*base.camera.getPos().length()) return task.cont
taskMgr.add(shaderUpdate, 'shaderUpdate') run()
shader.cg - Code: Select all
//Cg // http://stainlessbeer.weebly.com/planets-9-atmospheric-scattering.html // http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter16.html // http://sponeil.net/ float g =-0.90f; float g2 = 0.81f; float fExposure =.6;
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)))); } void vshader(float4 vtx_position:POSITION, float4 vtx_color: COLOR, in uniform float4 k_v3LightPos, // The light's current position in uniform float4 k_v3CameraPos, // The camera's current position in uniform float4 k_v3InvWavelength, // 1 / pow(wavelength, 4) for RGB in uniform float4 k_fCameraHeight, // The camera's current height in uniform float4 k_fCameraHeight2, // fCameraHeight^2 in uniform float4 k_fOuterRadius, // The outer (atmosphere) radius in uniform float4 k_fOuterRadius2, // fOuterRadius^2 in uniform float4 k_fInnerRadius, // The inner (planetary) radius in uniform float4 k_fInnerRadius2, // fInnerRadius^2 in uniform float4 k_fKrESun, // Kr * ESun in uniform float4 k_fKmESun, // Km * ESun in uniform float4 k_fKr4PI, // Kr * 4 * PI in uniform float4 k_fKm4PI, // Km * 4 * PI in uniform float4 k_fScale, // 1 / (fOuterRadius - fInnerRadius) in uniform float4 k_fScaleOverScaleDepth, // fScale / fScaleDepth in uniform float4 k_fScaleDepth, // The scale depth (i.e. the altitude at which the atmosphere's average density is found) in uniform float4 k_fSamples, in uniform float4 k_nSamples, uniform float4x4 mat_modelproj, out float4 l_position : POSITION, out float4 l_color0 : COLOR0, out float4 l_color1 : COLOR1, out float3 l_v3Direction: TEXCOORD0 ) { float3 v3Pos = vtx_position.xyz; float3 v3Ray = v3Pos - k_v3CameraPos.xyz; float fFar = length(v3Ray); v3Ray /= fFar; // Calculate the closest intersection of the ray with the outer atmosphere // (which is the near point of the ray passing through the atmosphere) float B = 2.0 * dot(k_v3CameraPos.xyz, v3Ray); float C = k_fCameraHeight2 - k_fOuterRadius2; float fDet = max(0.0, B*B - 4.0 * C); float fNear = 0.5 * (-B - sqrt(fDet)); // Calculate the ray's start and end positions in the atmosphere, // then calculate its scattering offset float3 v3Start = k_v3CameraPos.xyz + v3Ray * fNear; fFar -= fNear; // Initialize the scattering loop variables float fSampleLength = fFar / 15.0; //float fSampleLength = fFar / 1.0 float fScaledLength = fSampleLength * k_fScale; float3 v3SampleRay = v3Ray * fSampleLength; float3 v3SamplePoint = v3Start + (v3SampleRay * 0.5); float fHeight = length(v3SamplePoint); //float fStartAngle = dot(v3Ray, v3Start) / k_fOuterRadius.x; float fStartAngle = dot(v3Ray, v3Start) / fHeight; //float fStartDepth = exp(-1.0 / k_fScaleDepth.x); float fStartDepth = exp(k_fScaleOverScaleDepth * (k_fInnerRadius - fHeight)); float fStartOffset = fStartDepth * scale(fStartAngle); // Now loop through the sample points float3 v3FrontColor = float3(0.0, 0.0, 0.0); for(int i=0; i<14; i++) { float fHeight = length(v3SamplePoint); float fDepth = exp(k_fScaleOverScaleDepth * (k_fInnerRadius - fHeight)); float fLightAngle = dot(k_v3LightPos.xyz, v3SamplePoint) / fHeight; float fCameraAngle = dot(v3Ray, v3SamplePoint) / fHeight; float fScatter = (fStartOffset + fDepth * (scale(fLightAngle) - scale(fCameraAngle))); float3 v3Attenuate = exp(-fScatter *(k_v3InvWavelength.xyz * k_fKr4PI.x + k_fKm4PI.x)); v3FrontColor += v3Attenuate * (fDepth * fScaledLength); v3SamplePoint += v3SampleRay; } l_position=mul(mat_modelproj, vtx_position); l_color0.rgb = v3FrontColor.xyz * k_v3InvWavelength.xyz * k_fKrESun.x; l_color1.rgb=v3FrontColor*k_fKmESun.x; l_v3Direction =k_v3CameraPos.xyz - v3Pos; }
void fshader(in float4 l_position : POSITION, in float4 l_color0 : COLOR0, in float4 l_color1 : COLOR1, in float3 l_v3Direction : TEXCOORD0, uniform float4 k_v3LightPos, out float4 o_color:COLOR) { float fCos = dot(k_v3LightPos, l_v3Direction) / length(l_v3Direction); float fRayleighPhase = 0.75 * (1.0 + fCos*fCos); //float fRayleighPhase = .9 * (1.0 + fCos*fCos); float fMiePhase= 1.5 * ((1.0 - g2) / (2.0 + g2)) * (1.0 + fCos*fCos) / pow(1.0 + g2 - 2.0*g*fCos, 1.5); //float fMiePhase = 1.5 * 1.0 / (0.0015 * 4 * 3.14159) * (1.0 - g2) * pow(1.0 + (g2) - 2.0*g*fCos, -3.0/2.0) * (1.0 +fCos * fCos) / (2.0 + g2); //This causes a brightness reduction when viewing at 90 degrees float4 col=(fRayleighPhase * l_color0) + (fMiePhase * l_color1); //float4 col=(l_color0) + (fMiePhase * l_color1); //col.a = col.b; col.a=max(col.b,col.r); //In some versions, not in others. Not sure what impact it has o_color = 1 - exp(-fExposure * col); //o_color = col; }
Be sure to zoom out if you get it running. The sky is a faint blue. I guess higher-res sphere models may solve the remaining issues, but it's just a guess.
-
aeiou
-
- Posts: 4
- Joined: Tue Jan 03, 2012 8:58 am
- Location: Australia
by aeiou » Wed Jan 04, 2012 9:29 pm
...and here is a screenshot:

-
aeiou
-
- Posts: 4
- Joined: Tue Jan 03, 2012 8:58 am
- Location: Australia
by Sothh » Thu Jan 05, 2012 4:42 pm
You only need to add one more thing; make the atmosphere fade out. After that, I will totally use this for the space shooter I am working on.
-
Sothh
-
- Posts: 235
- Joined: Sat Oct 24, 2009 10:33 am
- Location: Virginia, USA
-
by aeiou » Fri Jan 06, 2012 2:30 am
do you mean like this?
The fading effect is small but noticeable. This is using a sphere with many more polygons plus some other changes:
fScaleDepth = 0.25
fScaleOverScaleDepth = scale/0.25
It still does not work when I change the scale from 1.025 to something larger. oh well.
-
aeiou
-
- Posts: 4
- Joined: Tue Jan 03, 2012 8:58 am
- Location: Australia
by aeiou » Fri Jan 06, 2012 2:40 am
I should thank croxis (op) for the work done in porting the shader code for use with panda3D. Good Job! 
-
aeiou
-
- Posts: 4
- Joined: Tue Jan 03, 2012 8:58 am
- Location: Australia
by croxis » Fri Jan 06, 2012 11:35 am
You are welcome, but you really did an amazing job getting it to work!
David - Proud to be saving the world sense 1984
-
croxis
-
- Posts: 408
- Joined: Thu Feb 12, 2009 4:13 pm
- Location: Portland, OR
-
Return to Code Snippets
Who is online
Users browsing this forum: No registered users and 1 guest
| | |