aerial perspective

Hey everyone,

this afternoon I’ve implemented aerial perspective (rayleigh and mie scattering in the atmosphere) as described in
http://ati.amd.com/developer/dx9/ATI-LightScattering.pdf. I am not sure if I understood all of it, but here is what I got:

sunset (0 degree light angle):

afternoon (40 degree light angle):

noon (80 degree light angle):

aerial_perspective.py:

from math import sin, pi

from pandac.PandaModules import GeoMipTerrain, Shader
from pandac.PandaModules import AmbientLight, DirectionalLight, Filename
import direct.directbase.DirectStart

# camera
base.disableMouse()
base.camera.setPos(0, 6000, 3500)
base.camera.lookAt(0, 0, 2000)

# sun
dlight = DirectionalLight('sun')
dlight.setColor((0.9, 0.9, 0.9, 1))
dlnp = render.attachNewNode(dlight)
dlnp.setHpr(0, -80, 0)
render.setLight(dlnp)

# ambient light
alight = AmbientLight('ambient')
# make ambient stronger with angle
ambient_strength = abs(0.2 * sin(dlnp.getP() * pi / 180)) 
alight.setColor((ambient_strength, ambient_strength, ambient_strength, 1.0))
alnp = render.attachNewNode(alight)
render.setLight(alnp)

# terrain
terrain = GeoMipTerrain("Terrain")
terrain.setHeightfield(Filename("perlin-noise.png"))
terrain.setBruteforce(True)
root = terrain.getRoot()
root.reparentTo(render)
root.setScale(128, 128, 5000)    # make it biiig, so that the blue-shift appears
root.setPos(-10240, -10240, 0)
root.setShader(Shader.load("aerial_perspective.sha"))
root.setShaderInput("sun", dlnp)
root.setShaderInput("ambient", alnp)
root.setColor((0.8, 0.8, 0.8, 0.8))
terrain.generate()

run()

aerial_perspective.sha

//Cg
//
//Cg profile arbvp1 arbfp1

#define BETA_RAYLEIGH_RED 0.00000695
#define BETA_RAYLEIGH_GREEN 0.0000118
#define BETA_RAYLEIGH_BLUE 0.0000244

#define BETA_MIE_RED 0.0000004
#define BETA_MIE_GREEN 0.0000006
#define BETA_MIE_BLUE 0.0000024

#define PHASE_ECC 0.0
#define PI 3.1416
#define SUN_IRR 0.3

float3 Fex(float distance)
{
    float3 fex;
    fex.x = exp(-(BETA_RAYLEIGH_RED + BETA_MIE_RED) * distance);
    fex.y = exp(-(BETA_RAYLEIGH_GREEN + BETA_MIE_GREEN) * distance);
    fex.z = exp(-(BETA_RAYLEIGH_BLUE + BETA_MIE_BLUE) * distance);
    return fex;
}

float3 betaRayleigh(float angle)
{
    float3 betarayleigh;
    float k = 0.1875 * PI * (1 + pow(cos(angle), 2));
    betarayleigh.x = k * BETA_RAYLEIGH_RED;
    betarayleigh.y = k * BETA_RAYLEIGH_GREEN;
    betarayleigh.z = k * BETA_RAYLEIGH_BLUE;
    return betarayleigh;
}

float3 betaMie(float angle)
{
    float3 betamie;
    float k = 0.25 * PI * pow((1 - PHASE_ECC), 2) / (1 + pow(PHASE_ECC, 2) - 2 * PHASE_ECC * pow(cos(angle), 1.5));
    betamie.x = k * BETA_MIE_RED;
    betamie.y = k * BETA_MIE_GREEN;
    betamie.z = k * BETA_MIE_BLUE;
    return betamie;
}

float3 Lin(float distance, float angle)
{
    float3 lin;
    float3 betarayleigh = betaRayleigh(angle);
    float3 betamie = betaMie(angle);
    float3 fex = Fex(distance);
    lin.x = (betarayleigh.x + betamie.x) / (BETA_RAYLEIGH_RED + BETA_MIE_RED) * SUN_IRR * (1 - fex.x);
    lin.y = (betarayleigh.y + betamie.y) / (BETA_RAYLEIGH_GREEN + BETA_MIE_GREEN) * SUN_IRR * (1 - fex.y);
    lin.z = (betarayleigh.z + betamie.z) / (BETA_RAYLEIGH_BLUE + BETA_MIE_BLUE) * SUN_IRR * (1 - fex.z);
    return lin;
}

void vshader(
    in float3 vtx_position : POSITION,
    in float3 vtx_normal : NORMAL,
    in float4 vtx_color : COLOR,
    in uniform float4x4 mat_modelproj,
    in uniform float4x4 trans_model_to_view,
    in uniform float4x4 dlight_sun_to_world,
    out float4 l_color : COLOR,
    out float3 l_fex,
    out float3 l_lin,
    out float4 l_position  : POSITION)
{
    l_position = mul(mat_modelproj, float4(vtx_position,1));

    // scattering
    float distance = mul(trans_model_to_view, float4(vtx_position,1)).y * 2;   // make the bluish effect bigger
    float angle = asin(normalize(dlight_sun_to_world[2]).z);
    l_fex = Fex(distance);
    l_lin = Lin(distance, angle);

    // diffuse lightening
    vtx_normal.z /= 39;         // make up for scaling
    float3 normal = normalize(vtx_normal);
    float3 lightvector = normalize(dlight_sun_to_world[2]).xyz;
    float brightness = saturate(dot(normal, lightvector));

    l_color = vtx_color * brightness;
}


void fshader(
    in float4 l_color : COLOR,
    in float3 l_fex,
    in float3 l_lin,
    in uniform float4 alight_ambient,
    out float4 o_color : COLOR)
{
    o_color.xyz = saturate(l_color.xyz * l_fex.zyx + l_lin.zyx + alight_ambient.xyz);
    o_color.a = 1.0;
}

A few notes:

  • I know it’s not optimized or anything, I just got it to work. Any helpful comment is very welcome!
  • It assumes viewpoint and object close to the ground (won’t work for flight simulation or planet views)
  • I don’t guarantee that it is correct, it just looks ok :slight_smile:
  • Play around with the constants to get it to look like you want.

I leave it as an exercise to the inclined reader to make Ralph roam through the terrain :slight_smile:

Nice! Ive never attempted something like this myself. Thanks for sharing!