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.
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
//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;
}