Real time atmospheric scattering shader -- Space view

Return to Code Snippets

Real time atmospheric scattering shader -- Space view

Postby 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.

Image

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.

Image

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 since 1984
croxis
 
Posts: 474
Joined: Thu Feb 12, 2009 4:13 pm
Location: Portland, OR

Postby 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.
User avatar
ThomasEgi
 
Posts: 2149
Joined: Fri Jul 28, 2006 10:43 am
Location: Germany,Koblenz

Postby rootWOLF » Thu Apr 07, 2011 8:20 am

looks pretty good!
User avatar
rootWOLF
Troll
 
Posts: 79
Joined: Wed Jan 12, 2011 4:54 pm

Postby 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

Postby aeiou » Wed Jan 04, 2012 9:29 pm

...and here is a screenshot:

Image
aeiou
 
Posts: 4
Joined: Tue Jan 03, 2012 8:58 am
Location: Australia

Postby 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

Postby aeiou » Fri Jan 06, 2012 2:30 am

do you mean like this?
Image

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

Postby 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! :D
aeiou
 
Posts: 4
Joined: Tue Jan 03, 2012 8:58 am
Location: Australia

Postby 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 since 1984
croxis
 
Posts: 474
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 0 guests

cron