Running on Empty

The few things I know, I like to share.

XNA Framework GameEngine Development. (Part 17, Hoffman Atmosphere)

Introduction

Welcome to Part17 of the XNA Framework GameEngine Development series.  In this article I will be introducing non-renderable scene objects to the engine.   Non-renderable scene objects are a unique subset of the SceneObject class that contain no geometry.  This is not necessarily interesting, accept that they are loaded into the Scene Management system as a normal renderable scene object and do take their turn with the renderer.  Mainly, I use this type of scene object to set up special shader commands, enter the Hoffman scattering algorithm.

part17.jpg

Release

Here is the code, I really want to hear your ideas to make the engine better, please leave a comment.

Hoffman Light Scattering

Hoffman/Preetham light scattering is an algorithmic approach to model how light will deflect off gas particles in the atmosphere.  The main goal of the algorithm is to define two angles of approach for light particles entering the eye.  In the Hoffman simulation these angles are determined analytically based on some pretty intense research that is beyond the scope of this series.  What this article will focus on is making it look good and render fast in a gameing situation.

Non-Geometric SceneObjects

This is a new concept in the engine, essentially a Non-Geometric SceneObject is exactly that, a scene object that does not have geometry to render.  This type of scene object will give us access to use all of the events that a normal scene object would have, but without actually sending polygons to the graphics card.  As a side effect, which I will show in an upcoming article, this class of sceneobject will allow us to share shader information between geometric sceneobjects.
Now that we have this new concept available to use we can create Atmosphere objects.  An atmoshpere object will be used to set up outdoor lighting and fogging, almost every object in an outdoor environment can use the atmosphere to render major light reflections.

Enter the HoffmanAtmosphere, this Non-Geometric Sceneobject will set up all of the maths necessary to compute the two attenuation angles required in the Hoffman light scattering algorithm, as well as store some important variables.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using RoeEngine2.Shaders;
using RoeEngine2.Managers;

#if !XBOX360
using System.ComponentModel;
using RoeEngine2.Helpers;
#endif

namespace RoeEngine2.SceneObject.NonGeometricObjects
{
    public class HoffmanAtmosphere : RoeSceneObject
    {
        private float _sunDirection = 0;
        [PropertyAttribute(typeof(float), 0, 180, 1)]
        public float SunDirection
        {
            get { return _sunDirection; }
            set
            {
                _sunDirection = value;
                if (_sunDirection > 180.0f)
                    _sunDirection = 0;
                else if (_sunDirection < 0.0f)
                    _sunDirection = 0;
                Position = new Vector3(0.0f, (float)Math.Sin(MathHelper.ToRadians(value)), (float)Math.Cos(MathHelper.ToRadians(value)));
            }
        }

        private float _sunIntensity = 1.0f;
        &#91;PropertyAttribute(typeof(float), 0, 10, 1)&#93;
        public float SunIntensity
        {
            get { return _sunIntensity; }
            set { _sunIntensity = value; }
        }

        private float _turbitity = 1.0f;
        &#91;PropertyAttribute(typeof(float), 1, 10, 1)&#93;
        public float Turbitity
        {
            get { return _turbitity; }
            set { _turbitity = value; }
        }

        private Vector3 _hGg = new Vector3(0.9f, 0.9f, 0.9f);
        public Vector3 HGg
        {
            get { return _hGg; }
            set { _hGg = value; }
        }

        private float _inscatteringMultiplier = 1.0f;
        &#91;PropertyAttribute(typeof(float), 0, 10, 1)&#93;
        public float InscatteringMultiplier
        {
            get { return _inscatteringMultiplier; }
            set { _inscatteringMultiplier = value; }
        }

        private float _betaRayMultiplier = 8.0f;
        &#91;PropertyAttribute(typeof(float), 0, 20, 1)&#93;
        public float BetaRayMultiplier
        {
            get { return _betaRayMultiplier; }
            set { _betaRayMultiplier = value; }
        }

        private float _betaMieMultiplier = 0.00005f;
        &#91;PropertyAttribute(typeof(float), 0, 10, 100000)&#93;
        public float BetaMieMultiplier
        {
            get { return _betaMieMultiplier; }
            set { _betaMieMultiplier = value; }
        }

        private Vector3 _betaRPlusBetaM;
        private Vector3 _betaDashR;
        private Vector3 _betaDashM;
        private Vector3 _oneOverBetaRPlusBetaM;
        private Vector4 _multipliers;
        private Vector4 _sunColorAndIntensity;

        private Vector3 _betaRay;
        private Vector3 _betaDashRay;
        private Vector3 _betaMie;
        private Vector3 _betaDashMie;

        public override string ToString()
        {
            return "Atmosphere";
        }

        public HoffmanAtmosphere()
        {
            hoffmanshaderEffect effect = new hoffmanshaderEffect();

            ShaderManager.AddShader(effect, "hoffman");
            Material.Shader = "hoffman";

            ReadyToRender = true;

            SunDirection = 0;

            const float n = 1.0003f;
            const float N = 2.545e25f;
            const float pn = 0.035f;

            float&#91;&#93; lambda = new float&#91;3&#93;;
            float&#91;&#93; lambda2 = new float&#91;3&#93;;
            float&#91;&#93; lambda4 = new float&#91;3&#93;;

            lambda&#91;0&#93; = 1.0f / 650e-9f;   // red
            lambda&#91;1&#93; = 1.0f / 570e-9f;   // green
            lambda&#91;2&#93; = 1.0f / 475e-9f;   // blue

            for (int i = 0; i < 3; ++i)
            {
                lambda2&#91;i&#93; = lambda&#91;i&#93; * lambda&#91;i&#93;;
                lambda4&#91;i&#93; = lambda2&#91;i&#93; * lambda2&#91;i&#93;;
            }

            Vector3 vLambda2 = new Vector3(lambda2&#91;0&#93;, lambda2&#91;1&#93;, lambda2&#91;2&#93;);
            Vector3 vLambda4 = new Vector3(lambda4&#91;0&#93;, lambda4&#91;1&#93;, lambda4&#91;2&#93;);

            // Rayleigh scattering constants

            const float temp = (float)(Math.PI * Math.PI * (n * n - 1.0f) * (n * n - 1.0f) * (6.0f + 3.0f * pn) / (6.0f - 7.0f * pn) / N);
            const float beta = (float)(8.0f * temp * Math.PI / 3.0f);

            _betaRay = beta * vLambda4;

            const float betaDash = temp / 2.0f;

            _betaDashRay = betaDash * vLambda4;

            // Mie scattering constants

            const float T = 2.0f;
            const float c = (6.544f * T - 6.51f) * 1e-17f;
            const float temp2 = (float)(0.434f * c * (2.0f * Math.PI) * (2.0f * Math.PI) * 0.5f);

            _betaDashMie = temp2 * vLambda2;

            float&#91;&#93; K = new float&#91;3&#93; { 0.685f, 0.679f, 0.670f };
            const float temp3 = (float)(0.434f * c * Math.PI * (2.0f * Math.PI) * (2.0f * Math.PI));

            Vector3 vBetaMieTemp = new Vector3(K&#91;0&#93; * lambda2&#91;0&#93;, K&#91;1&#93; * lambda2&#91;1&#93;, K&#91;2&#93; * lambda2&#91;2&#93;);

            _betaMie = temp3 * vBetaMieTemp;
        }

        public override void Draw(GameTime gameTime)
        {
            if (!ReadyToRender)
            {
                ReadyToRender = ShaderManager.GetShader("hoffman").ReadyToRender;
            }
            else
            {
                Vector3 vZenith = new Vector3(0.0f, 1.0f, 0.0f);

                float thetaS = Vector3.Dot(Position, vZenith);
                thetaS = (float)(Math.Acos(thetaS));

                ComputeAttenuation(thetaS);
                SetMaterialProperties();
            }
        }

        private void ComputeAttenuation(float thetaS)
        {
            float beta = 0.04608365822050f * _turbitity - 0.04586025928522f;
            float tauR, tauA;
            float&#91;&#93; fTau = new float&#91;3&#93;;
            float m = (float)(1.0f / (Math.Cos(thetaS) + 0.15f * Math.Pow(93.885f - thetaS / Math.PI * 180.0f, -1.253f)));  // Relative Optical Mass
            float&#91;&#93; lambda = new float&#91;3&#93; { 0.65f, 0.57f, 0.475f };

            for (int i = 0; i < 3; ++i)
            {
                // Rayleigh Scattering
                // lambda in um.
                tauR = (float)(Math.Exp(-m * 0.008735f * Math.Pow(lambda&#91;i&#93;, -4.08f)));

                // Aerosal (water + dust) attenuation
                // beta - amount of aerosols present
                // alpha - ratio of small to large particle sizes. (0:4,usually 1.3)
                const float alpha = 1.3f;
                tauA = (float)(Math.Exp(-m * beta * Math.Pow(lambda&#91;i&#93;, -alpha)));  // lambda should be in um

                fTau&#91;i&#93; = tauR * tauA;
            }

            _sunColorAndIntensity = new Vector4(fTau&#91;0&#93;, fTau&#91;1&#93;, fTau&#91;2&#93;, _sunIntensity * 100.0f);

        }

        private void SetMaterialProperties()
        {
            float reflectance = 0.1f;

            Vector3 vecBetaR = _betaRay * _betaRayMultiplier;
            _betaDashR = _betaDashRay * _betaRayMultiplier;
            Vector3 vecBetaM = _betaMie * _betaMieMultiplier;
            _betaDashM = _betaDashMie * _betaMieMultiplier;
            _betaRPlusBetaM = vecBetaR + vecBetaM;
            _oneOverBetaRPlusBetaM = new Vector3(1.0f / _betaRPlusBetaM.X, 1.0f / _betaRPlusBetaM.Y, 1.0f / _betaRPlusBetaM.Z);
            Vector3 vecG = new Vector3(1.0f - _hGg.X * _hGg.X, 1.0f + _hGg.X * _hGg.X, 2.0f * _hGg.X);
            _multipliers = new Vector4(_inscatteringMultiplier, 0.138f * reflectance, 0.113f * reflectance, 0.08f * reflectance);

            hoffmanshaderEffect hoffmanShader = ShaderManager.GetShader(Material.Shader) as hoffmanshaderEffect;

            hoffmanShader.SunDirection = Position;
            hoffmanShader.BetaRPlusBetaM = _betaRPlusBetaM;
            hoffmanShader.HGg = HGg;
            hoffmanShader.BetaDashR = _betaDashR;
            hoffmanShader.BetaDashM = _betaDashM;
            hoffmanShader.OneOverBetaRPlusBetaM = _oneOverBetaRPlusBetaM;
            hoffmanShader.Multipliers = _multipliers;
            hoffmanShader.SunColorAndIntensity = _sunColorAndIntensity;
        }
    }
}&#91;/sourcecode&#93;<strong>Introduction to HLSL - YEAH RIGHT!</strong>

I feel pretty bad about using this shader as the first example of High Level Shader Language (HLSL), I will most likely write a sub-series of articles to help introduce shaders when I am finished with the major pieces of the engine.  This shader has been discussed at length on the GameDev.net site so I will not bore you with the details here, ignore the terrain stuff for now, I will be demonstrating that soon.

float4x4 worldViewProject;
float4x4 worldView;

float3 sunDirection;
float3 betaRPlusBetaM;
float3 hGg;
float3 betaDashR;
float3 betaDashM;
float3 oneOverBetaRPlusBetaM;
float4 multipliers;
float4 sunColorAndIntensity;

float3 groundCursorPosition;
bool showGroundCursor;

texture terrainAlpha, terrainBreakup, terrainOne, terrainTwo, terrainThree, terrainFour, groundCursor;

sampler samplergroundCursor = sampler_state
{
   Texture = <groundCursor>;
   ADDRESSU = CLAMP;
   ADDRESSV = CLAMP;
   MAGFILTER = LINEAR;
   MINFILTER = LINEAR;
   MIPFILTER = LINEAR;
};

sampler samplerAlpha = sampler_state
{
 texture = <terrainAlpha>;
 MINFILTER = ANISOTROPIC;
 MAGFILTER = ANISOTROPIC;
 MIPFILTER = ANISOTROPIC;
};

sampler samplerOne = sampler_state
{
 texture = <terrainOne>;
 MINFILTER = ANISOTROPIC;
 MAGFILTER = ANISOTROPIC;
 MIPFILTER = ANISOTROPIC;
 ADDRESSU = WRAP;
 ADDRESSV = WRAP;
};

sampler samplerTwo = sampler_state
{
 texture = <terrainTwo>;
 MINFILTER = ANISOTROPIC;
 MAGFILTER = ANISOTROPIC;
 MIPFILTER = ANISOTROPIC;
 ADDRESSU = WRAP;
 ADDRESSV = WRAP;
};

sampler samplerThree = sampler_state
{
 texture = <terrainThree>;
 MINFILTER = ANISOTROPIC;
 MAGFILTER = ANISOTROPIC;
 MIPFILTER = ANISOTROPIC;
 ADDRESSU = WRAP;
 ADDRESSV = WRAP;
};

sampler samplerFour = sampler_state
{
 texture = <terrainFour>;
 MINFILTER = ANISOTROPIC;
 MAGFILTER = ANISOTROPIC;
 MIPFILTER = ANISOTROPIC;
 ADDRESSU = WRAP;
 ADDRESSV = WRAP;
};

float4 constants = { 0.25f, 1.4426950f, 0.5f, 0.0f };

struct VS_INPUT
{
 float4 Position : POSITION0;
 float3 Normal : NORMAL;
 float2 TexCoord : TEXCOORD0;
};

struct VS_OUTPUT
{
 float4 Position     : POSITION;
 float2 TerrainCoord : TEXCOORD0;
 float3 Normal  : TEXCOORD1;
 float3 Lin          : COLOR0;
 float3 Fex          : COLOR1;
};

VS_OUTPUT HoffmanShader(VS_INPUT Input)
{ 
 float4 worldPos = mul(Input.Position, worldView);
 float3 viewDir = normalize(worldPos.xyz);
 float distance = length(worldPos.xyz);
 
 float3 sunDir= normalize(mul(float4(sunDirection, 0.0), worldView ).xyz);
 
 float theta = dot(sunDir, viewDir);
 
 // 
 // Phase1 and Phase2
 //

 float phase1 = 1.0 + theta * theta;
 float phase2 = pow( rsqrt( hGg.y - hGg.z * theta ), 3 ) * hGg.x;

 //
 // Extinction term
 //

     float3 extinction      = exp( -betaRPlusBetaM * distance * constants.x );
     float3 totalExtinction = extinction * multipliers.yzw;
    
 //
 // Inscattering term
 //

 float3 betaRay = betaDashR * phase1;
 float3 betaMie = betaDashM * phase2;

 float3 inscatter = (betaRay + betaMie) * oneOverBetaRPlusBetaM * (1.0 - extinction);

 //
 // Apply inscattering contribution factors
 //

 inscatter *= multipliers.x;
 //
 // Scale with sun color & intensity
 //

 inscatter       *= sunColorAndIntensity.xyz * sunColorAndIntensity.w;
 totalExtinction *= sunColorAndIntensity.xyz * sunColorAndIntensity.w;

 VS_OUTPUT Output;
 Output.Position = mul(Input.Position, worldViewProject);
  Output.TerrainCoord = Input.TexCoord.xy;
  Output.Normal = Input.Normal;
 Output.Lin = inscatter;
 Output.Fex = totalExtinction;

 return Output;
};

struct PS_INPUT
{
 float4 Position : POSITION;
 float2 TerrainCoord : TEXCOORD0;
 float3 Normal : TEXCOORD1;
 float3 Lin : COLOR0;
 float3 Fex : COLOR1;
};

float4 SkyShader(PS_INPUT Input) : COLOR0
{
 return float4(Input.Lin, 1.0f);
};

float4 TerrainShader(PS_INPUT Input) : COLOR0
{
 Input.Normal = normalize(Input.Normal);

 vector alphaSamp = tex2D(samplerAlpha, Input.TerrainCoord);
 vector oneSamp = tex2D(samplerOne, float2(Input.TerrainCoord.x * 2048, Input.TerrainCoord.y * 2048));
 vector twoSamp = tex2D(samplerTwo, float2(Input.TerrainCoord.x * 2048, Input.TerrainCoord.y * 2048));
 vector threeSamp = tex2D(samplerThree, float2(Input.TerrainCoord.x * 2048, Input.TerrainCoord.y * 2048));
 vector fourSamp = tex2D(samplerFour, float2(Input.TerrainCoord.x * 2048, Input.TerrainCoord.y * 2048));
 
 float4 tester1 = 1.0 - alphaSamp.a;
 float4 tester2 = 1.0 - alphaSamp.b;
 float4 tester3 = 1.0 - alphaSamp.g;
 float4 tester4 = 1.0 - alphaSamp.r;
 
 float4 tester = lerp(threeSamp, oneSamp, saturate(dot(float3(0, 1, 0), Input.Normal) * 2));
 
 vector l = alphaSamp.a * oneSamp + tester1 * tester;
 vector m = alphaSamp.b * twoSamp + tester2 * l;
 vector o = alphaSamp.g * threeSamp + tester3 * m;
 vector p = alphaSamp.r * fourSamp + tester4 * o;
  
 float4 albedo = saturate((dot(normalize(sunDirection), Input.Normal) + .9f)) * p;
 
 albedo *= float4(Input.Fex, 1.0f);
 albedo += float4(Input.Lin, 1.0f);
 
 return albedo;
};

float4 TerrainShaderWithCursor(PS_INPUT Input) : COLOR0
{
 Input.Normal = normalize(Input.Normal);

 vector alphaSamp = tex2D(samplerAlpha, Input.TerrainCoord);
 vector oneSamp = tex2D(samplerOne, float2(Input.TerrainCoord.x * 2048, Input.TerrainCoord.y * 2048));
 vector twoSamp = tex2D(samplerTwo, float2(Input.TerrainCoord.x * 2048, Input.TerrainCoord.y * 2048));
 vector threeSamp = tex2D(samplerThree, float2(Input.TerrainCoord.x * 2048, Input.TerrainCoord.y * 2048));
 vector fourSamp = tex2D(samplerFour, float2(Input.TerrainCoord.x * 2048, Input.TerrainCoord.y * 2048));
 
 float4 tester1 = 1.0 - alphaSamp.a;
 float4 tester2 = 1.0 - alphaSamp.b;
 float4 tester3 = 1.0 - alphaSamp.g;
 float4 tester4 = 1.0 - alphaSamp.r;
 
 float4 tester0 = lerp(threeSamp, oneSamp, saturate(dot(float3(0, 1, 0), Input.Normal) * 2));
 
 vector l = alphaSamp.a * oneSamp + tester1 * tester0;
 vector m = alphaSamp.b * twoSamp + tester2 * l;
 vector o = alphaSamp.g * threeSamp + tester3 * m;
 vector p = alphaSamp.r * fourSamp + tester4 * o;
  
 float4 albedo = saturate((dot(normalize(sunDirection), Input.Normal) + .9f)) * p;
 
 albedo *= float4(Input.Fex, 1.0f);
 albedo += float4(Input.Lin, 1.0f);
 
 if(showGroundCursor)
 {
  float cursorScale = 40.0f;
  albedo += tex2D(samplergroundCursor,
   (Input.TerrainCoord * (cursorScale)) -
   (groundCursorPosition.xz * (cursorScale)) + 0.5f);
 }
 
 return albedo;
};

float4 Wireframe( PS_INPUT Input ) : COLOR0
{
    return float4( 1, 1, 1, 1 );
};

technique Sky
{
 pass P0
 {
  CullMode = CCW;
  FillMode = Solid;
  VertexShader = compile vs_3_0 HoffmanShader();
  PixelShader = compile ps_3_0 SkyShader(); 
 }
};

technique SkyWireframe
{
 pass P0
 {
  CullMode = CCW;
  FillMode = Solid;
  VertexShader = compile vs_3_0 HoffmanShader();
  PixelShader = compile ps_3_0 SkyShader(); 
 }
 pass P1
 {
  CullMode = CCW;
  FillMode = Wireframe;
  VertexShader = compile vs_3_0 HoffmanShader();
  PixelShader = compile ps_1_1 Wireframe();
 }
};

technique Terrain
{
 pass P0
 {
  CullMode = CCW;
  FillMode = Solid;
  VertexShader = compile vs_3_0 HoffmanShader();
  PixelShader = compile ps_3_0 TerrainShader();  
 }
};

technique TerrainWithCursor
{
 pass P0
 {
  CullMode = CCW;
  FillMode = Solid;
  VertexShader = compile vs_3_0 HoffmanShader();
  PixelShader = compile ps_3_0 TerrainShaderWithCursor();  
 }
};

technique TerrainWireframe
{
 pass P0
 {
  CullMode = CCW;
  FillMode = Solid;
  VertexShader = compile vs_3_0 HoffmanShader();
  PixelShader = compile ps_3_0 TerrainShader(); 
  
 }
  pass P1
 {
  CullMode = CCW;
  FillMode = Wireframe;
  VertexShader = compile vs_3_0 HoffmanShader();
  PixelShader = compile ps_1_1 Wireframe();
 }
};

Now that we have a working shader, it is time to create our first shader proxy.  Remember way back to Part4 of this series, we created an effect code generator.  Pretty much that is an executable that will create a shader file for us when we drag a .fx file onto the executable.  You will get something like the following.

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using RoeEngine2.Interfaces;

namespace RoeEngine2.Shaders
{
 public class hoffmanshaderEffect : IRoeShader
 {
  public enum Techniques
  {
   Sky,
   SkyWireframe,
   Terrain,
   TerrainWithCursor,
   TerrainWireframe,
  }

  private Effect _baseEffect;
  ///<summary>
  ///Gets the underlying Effect.
  ///</summary>
  public Effect BaseEffect
  {
   get { return _baseEffect; }
  }

  private bool _readyToRender = false;
  ///<summary>
  ///Is the shader ready to be rendered.
  ///</summary>
  public bool ReadyToRender
  {
   get { return _readyToRender; }
  }

  #region Effect Parameters

  private EffectParameter _worldViewProjectParam;
  public Matrix WorldViewProject
  {
   get
   {
    if (_worldViewProjectParam == null)
     throw new Exception("Cannot get value of WorldViewProject; WorldViewProject EffectParameter is null.");
    return _worldViewProjectParam.GetValueMatrix();
   }
   set
   {
    if (_worldViewProjectParam == null)
     throw new Exception("Cannot set value of WorldViewProject; WorldViewProject EffectParameter is null.");
    _worldViewProjectParam.SetValue(value);
   }
  }

  private EffectParameter _worldViewParam;
  public Matrix WorldView
  {
   get
   {
    if (_worldViewParam == null)
     throw new Exception("Cannot get value of WorldView; WorldView EffectParameter is null.");
    return _worldViewParam.GetValueMatrix();
   }
   set
   {
    if (_worldViewParam == null)
     throw new Exception("Cannot set value of WorldView; WorldView EffectParameter is null.");
    _worldViewParam.SetValue(value);
   }
  }

  private EffectParameter _sunDirectionParam;
  public Vector3 SunDirection
  {
   get
   {
    if (_sunDirectionParam == null)
     throw new Exception("Cannot get value of SunDirection; SunDirection EffectParameter is null.");
    return _sunDirectionParam.GetValueVector3();
   }
   set
   {
    if (_sunDirectionParam == null)
     throw new Exception("Cannot set value of SunDirection; SunDirection EffectParameter is null.");
    _sunDirectionParam.SetValue(value);
   }
  }

  private EffectParameter _betaRPlusBetaMParam;
  public Vector3 BetaRPlusBetaM
  {
   get
   {
    if (_betaRPlusBetaMParam == null)
     throw new Exception("Cannot get value of BetaRPlusBetaM; BetaRPlusBetaM EffectParameter is null.");
    return _betaRPlusBetaMParam.GetValueVector3();
   }
   set
   {
    if (_betaRPlusBetaMParam == null)
     throw new Exception("Cannot set value of BetaRPlusBetaM; BetaRPlusBetaM EffectParameter is null.");
    _betaRPlusBetaMParam.SetValue(value);
   }
  }

  private EffectParameter _hGgParam;
  public Vector3 HGg
  {
   get
   {
    if (_hGgParam == null)
     throw new Exception("Cannot get value of HGg; HGg EffectParameter is null.");
    return _hGgParam.GetValueVector3();
   }
   set
   {
    if (_hGgParam == null)
     throw new Exception("Cannot set value of HGg; HGg EffectParameter is null.");
    _hGgParam.SetValue(value);
   }
  }

  private EffectParameter _betaDashRParam;
  public Vector3 BetaDashR
  {
   get
   {
    if (_betaDashRParam == null)
     throw new Exception("Cannot get value of BetaDashR; BetaDashR EffectParameter is null.");
    return _betaDashRParam.GetValueVector3();
   }
   set
   {
    if (_betaDashRParam == null)
     throw new Exception("Cannot set value of BetaDashR; BetaDashR EffectParameter is null.");
    _betaDashRParam.SetValue(value);
   }
  }

  private EffectParameter _betaDashMParam;
  public Vector3 BetaDashM
  {
   get
   {
    if (_betaDashMParam == null)
     throw new Exception("Cannot get value of BetaDashM; BetaDashM EffectParameter is null.");
    return _betaDashMParam.GetValueVector3();
   }
   set
   {
    if (_betaDashMParam == null)
     throw new Exception("Cannot set value of BetaDashM; BetaDashM EffectParameter is null.");
    _betaDashMParam.SetValue(value);
   }
  }

  private EffectParameter _oneOverBetaRPlusBetaMParam;
  public Vector3 OneOverBetaRPlusBetaM
  {
   get
   {
    if (_oneOverBetaRPlusBetaMParam == null)
     throw new Exception("Cannot get value of OneOverBetaRPlusBetaM; OneOverBetaRPlusBetaM EffectParameter is null.");
    return _oneOverBetaRPlusBetaMParam.GetValueVector3();
   }
   set
   {
    if (_oneOverBetaRPlusBetaMParam == null)
     throw new Exception("Cannot set value of OneOverBetaRPlusBetaM; OneOverBetaRPlusBetaM EffectParameter is null.");
    _oneOverBetaRPlusBetaMParam.SetValue(value);
   }
  }

  private EffectParameter _multipliersParam;
  public Vector4 Multipliers
  {
   get
   {
    if (_multipliersParam == null)
     throw new Exception("Cannot get value of Multipliers; Multipliers EffectParameter is null.");
    return _multipliersParam.GetValueVector4();
   }
   set
   {
    if (_multipliersParam == null)
     throw new Exception("Cannot set value of Multipliers; Multipliers EffectParameter is null.");
    _multipliersParam.SetValue(value);
   }
  }

  private EffectParameter _sunColorAndIntensityParam;
  public Vector4 SunColorAndIntensity
  {
   get
   {
    if (_sunColorAndIntensityParam == null)
     throw new Exception("Cannot get value of SunColorAndIntensity; SunColorAndIntensity EffectParameter is null.");
    return _sunColorAndIntensityParam.GetValueVector4();
   }
   set
   {
    if (_sunColorAndIntensityParam == null)
     throw new Exception("Cannot set value of SunColorAndIntensity; SunColorAndIntensity EffectParameter is null.");
    _sunColorAndIntensityParam.SetValue(value);
   }
  }

  private EffectParameter _groundCursorPositionParam;
  public Vector3 GroundCursorPosition
  {
   get
   {
    if (_groundCursorPositionParam == null)
     throw new Exception("Cannot get value of GroundCursorPosition; GroundCursorPosition EffectParameter is null.");
    return _groundCursorPositionParam.GetValueVector3();
   }
   set
   {
    if (_groundCursorPositionParam == null)
     throw new Exception("Cannot set value of GroundCursorPosition; GroundCursorPosition EffectParameter is null.");
    _groundCursorPositionParam.SetValue(value);
   }
  }

  private EffectParameter _showGroundCursorParam;
  public bool ShowGroundCursor
  {
   get
   {
    if (_showGroundCursorParam == null)
     throw new Exception("Cannot get value of ShowGroundCursor; ShowGroundCursor EffectParameter is null.");
    return _showGroundCursorParam.GetValueBoolean();
   }
   set
   {
    if (_showGroundCursorParam == null)
     throw new Exception("Cannot set value of ShowGroundCursor; ShowGroundCursor EffectParameter is null.");
    _showGroundCursorParam.SetValue(value);
   }
  }

  private EffectParameter _terrainAlphaParam;
  public Texture2D TerrainAlpha
  {
   get
   {
    if (_terrainAlphaParam == null)
     throw new Exception("Cannot get value of TerrainAlpha; TerrainAlpha EffectParameter is null.");
    return _terrainAlphaParam.GetValueTexture2D();
   }
   set
   {
    if (_terrainAlphaParam == null)
     throw new Exception("Cannot set value of TerrainAlpha; TerrainAlpha EffectParameter is null.");
    _terrainAlphaParam.SetValue(value);
   }
  }

  private EffectParameter _terrainBreakupParam;
  public Texture2D TerrainBreakup
  {
   get
   {
    if (_terrainBreakupParam == null)
     throw new Exception("Cannot get value of TerrainBreakup; TerrainBreakup EffectParameter is null.");
    return _terrainBreakupParam.GetValueTexture2D();
   }
   set
   {
    if (_terrainBreakupParam == null)
     throw new Exception("Cannot set value of TerrainBreakup; TerrainBreakup EffectParameter is null.");
    _terrainBreakupParam.SetValue(value);
   }
  }

  private EffectParameter _terrainOneParam;
  public Texture2D TerrainOne
  {
   get
   {
    if (_terrainOneParam == null)
     throw new Exception("Cannot get value of TerrainOne; TerrainOne EffectParameter is null.");
    return _terrainOneParam.GetValueTexture2D();
   }
   set
   {
    if (_terrainOneParam == null)
     throw new Exception("Cannot set value of TerrainOne; TerrainOne EffectParameter is null.");
    _terrainOneParam.SetValue(value);
   }
  }

  private EffectParameter _terrainTwoParam;
  public Texture2D TerrainTwo
  {
   get
   {
    if (_terrainTwoParam == null)
     throw new Exception("Cannot get value of TerrainTwo; TerrainTwo EffectParameter is null.");
    return _terrainTwoParam.GetValueTexture2D();
   }
   set
   {
    if (_terrainTwoParam == null)
     throw new Exception("Cannot set value of TerrainTwo; TerrainTwo EffectParameter is null.");
    _terrainTwoParam.SetValue(value);
   }
  }

  private EffectParameter _terrainThreeParam;
  public Texture2D TerrainThree
  {
   get
   {
    if (_terrainThreeParam == null)
     throw new Exception("Cannot get value of TerrainThree; TerrainThree EffectParameter is null.");
    return _terrainThreeParam.GetValueTexture2D();
   }
   set
   {
    if (_terrainThreeParam == null)
     throw new Exception("Cannot set value of TerrainThree; TerrainThree EffectParameter is null.");
    _terrainThreeParam.SetValue(value);
   }
  }

  private EffectParameter _terrainFourParam;
  public Texture2D TerrainFour
  {
   get
   {
    if (_terrainFourParam == null)
     throw new Exception("Cannot get value of TerrainFour; TerrainFour EffectParameter is null.");
    return _terrainFourParam.GetValueTexture2D();
   }
   set
   {
    if (_terrainFourParam == null)
     throw new Exception("Cannot set value of TerrainFour; TerrainFour EffectParameter is null.");
    _terrainFourParam.SetValue(value);
   }
  }

  private EffectParameter _groundCursorParam;
  public Texture2D GroundCursor
  {
   get
   {
    if (_groundCursorParam == null)
     throw new Exception("Cannot get value of GroundCursor; GroundCursor EffectParameter is null.");
    return _groundCursorParam.GetValueTexture2D();
   }
   set
   {
    if (_groundCursorParam == null)
     throw new Exception("Cannot set value of GroundCursor; GroundCursor EffectParameter is null.");
    _groundCursorParam.SetValue(value);
   }
  }

  private EffectParameter _constantsParam;
  public Vector4 Constants
  {
   get
   {
    if (_constantsParam == null)
     throw new Exception("Cannot get value of Constants; Constants EffectParameter is null.");
    return _constantsParam.GetValueVector4();
   }
   set
   {
    if (_constantsParam == null)
     throw new Exception("Cannot set value of Constants; Constants EffectParameter is null.");
    _constantsParam.SetValue(value);
   }
  }

  #endregion

  #region Effect Techniques

  private EffectTechnique _SkyTechnique;

  private EffectTechnique _SkyWireframeTechnique;

  private EffectTechnique _TerrainTechnique;

  private EffectTechnique _TerrainWithCursorTechnique;

  private EffectTechnique _TerrainWireframeTechnique;

  #endregion

  #region Initialize Methods

  ///<summary>
  ///Initializes the Effect from byte code for the given GraphicsDevice.
  ///</summary
  ///<param name="graphicsDevice">The GraphicsDevice for which the effect is being created.</param>
  public void Initialize(GraphicsDevice graphicsDevice)
  {
   Initialize(graphicsDevice, CompilerOptions.None, null);
  }

  ///<summary>
  ///Initializes the Effect from byte code for the given GraphicsDevice and CompilerOptions.
  ///</summary
  ///<param name="graphicsDevice">The GraphicsDevice for which the effect is being created.</param>
  ///<param name="compilerOptions">The CompilerOptions to use when creating the effect.</param>
  public void Initialize(GraphicsDevice graphicsDevice, CompilerOptions compilerOptions)
  {
   Initialize(graphicsDevice, compilerOptions, null);
  }

  ///<summary>
  ///Initializes the Effect from byte code for the given GraphicsDevice, CompilerOptions, and EffectPool.
  ///</summary
  ///<param name="graphicsDevice">The GraphicsDevice for which the effect is being created.</param>
  ///<param name="compilerOptions">The CompilerOptions to use when creating the effect.</param>
  ///<param name="effectPools">The EffectPool to use with the effect.</param>
  public void Initialize(GraphicsDevice graphicsDevice, CompilerOptions compilerOptions, EffectPool effectPool)
  {
   _baseEffect = new Effect(graphicsDevice, byteCode, compilerOptions, effectPool);
   _readyToRender = true;

   _worldViewProjectParam = _baseEffect.Parameters["worldViewProject"];
   _worldViewParam = _baseEffect.Parameters["worldView"];
   _sunDirectionParam = _baseEffect.Parameters["sunDirection"];
   _betaRPlusBetaMParam = _baseEffect.Parameters["betaRPlusBetaM"];
   _hGgParam = _baseEffect.Parameters["hGg"];
   _betaDashRParam = _baseEffect.Parameters["betaDashR"];
   _betaDashMParam = _baseEffect.Parameters["betaDashM"];
   _oneOverBetaRPlusBetaMParam = _baseEffect.Parameters["oneOverBetaRPlusBetaM"];
   _multipliersParam = _baseEffect.Parameters["multipliers"];
   _sunColorAndIntensityParam = _baseEffect.Parameters["sunColorAndIntensity"];
   _groundCursorPositionParam = _baseEffect.Parameters["groundCursorPosition"];
   _showGroundCursorParam = _baseEffect.Parameters["showGroundCursor"];
   _terrainAlphaParam = _baseEffect.Parameters["terrainAlpha"];
   _terrainBreakupParam = _baseEffect.Parameters["terrainBreakup"];
   _terrainOneParam = _baseEffect.Parameters["terrainOne"];
   _terrainTwoParam = _baseEffect.Parameters["terrainTwo"];
   _terrainThreeParam = _baseEffect.Parameters["terrainThree"];
   _terrainFourParam = _baseEffect.Parameters["terrainFour"];
   _groundCursorParam = _baseEffect.Parameters["groundCursor"];
   _constantsParam = _baseEffect.Parameters["constants"];

   _SkyTechnique = _baseEffect.Techniques["Sky"];
   _SkyWireframeTechnique = _baseEffect.Techniques["SkyWireframe"];
   _TerrainTechnique = _baseEffect.Techniques["Terrain"];
   _TerrainWithCursorTechnique = _baseEffect.Techniques["TerrainWithCursor"];
   _TerrainWireframeTechnique = _baseEffect.Techniques["TerrainWireframe"];
  }

  #endregion

  ///<summary>
  ///Sets the current technique for the effect.
  ///</summary>
  ///<param name="technique">The technique to use for the current technique.</param>
  public void SetCurrentTechnique(hoffmanshaderEffect.Techniques technique)
  {
   switch (technique)
   {
    case hoffmanshaderEffect.Techniques.Sky:
     _baseEffect.CurrentTechnique = _SkyTechnique;
     break;

    case hoffmanshaderEffect.Techniques.SkyWireframe:
     _baseEffect.CurrentTechnique = _SkyWireframeTechnique;
     break;

    case hoffmanshaderEffect.Techniques.Terrain:
     _baseEffect.CurrentTechnique = _TerrainTechnique;
     break;

    case hoffmanshaderEffect.Techniques.TerrainWithCursor:
     _baseEffect.CurrentTechnique = _TerrainWithCursorTechnique;
     break;

    case hoffmanshaderEffect.Techniques.TerrainWireframe:
     _baseEffect.CurrentTechnique = _TerrainWireframeTechnique;
     break;

   }
  }
//omitted compiled byte code, takes up too much space!
 }
}

Conclusion

In this article I demonstrated the Hoffman light scattering algorithm.  In the released code, by pressing the up/down arrow, you can adjust the time of day and watch the sun go through a 12 hour cycle.

Please leave comments or suggestions, I would very much enjoy hearing ideas on how to improve on the shader here.

March 11, 2008 - Posted by | C#, XBOX360, XNA

11 Comments »

  1. Great stuff, I’ve seen this stuff before but never attempted to implement any of it. I’ve always considered it more of a “polish and shine” area, for when a game is near completion. But looks good, and glad to have it in the series. Good to see things from theory put to use, especially here in the land of XNA. Keep up the great work.

    Comment by KaBaL | March 11, 2008 | Reply

  2. KaBaL, yeah I do like polish and shine early in an engine. Being able to look at something gives a sense of accomplishment, but you are right, I could spend more time on performance over shine.

    Comment by roecode | March 12, 2008 | Reply

  3. very nice and easy to follow tutorial. I have a question, I see that you hard coded the shader as a static array, can you post the readable(.fx file) shader file, or you don’t want for privacy reasons ?

    Comment by Karim Kenawy | March 23, 2008 | Reply

  4. Forgive me :), I read only the last part of the tutorial. I found the shader. But why you embed the shader as a compiled code, why not through the pipeline ?

    Comment by Karim Kenawy | March 23, 2008 | Reply

  5. I do use the pipeline, just like to have a wrapper class for my effect files.

    Comment by roecode | March 23, 2008 | Reply

  6. Hi, just curious, what would be the best way to extend this to
    a 24 hour period?

    Comment by aker71 | December 13, 2008 | Reply

    • Currenly, I do not have a night time simulation here. There are very few articles on night time atmospheric scattering. I have seen a few, most of them go something like this.
      1) Map a star field texture to a sky plane.
      2) Map a moon texture to a billboard, use hdr, bloom, or whatever method to get the halo effect.
      3) At dusk or dawn, slowly fade from the day time simulation, into the night time simulation.

      I will probably take this up in the new series.

      Comment by roecode | December 15, 2008 | Reply

  7. I’ve got a question about HLSL file.

    There is:
    float phase2 = pow( rsqrt( hGg.y – hGg.z * theta ), 3 ) * hGg.x;

    But due to the Henyey-Greenstein equation it should be:
    float phase2 = hGg.x/pow( rsqrt( hGg.y – hGg.z * theta ), 3 );

    So why do you use this “changed” version?

    Comment by rtshadow | March 12, 2009 | Reply

    • The results look nicer in my opinion.

      Comment by roecode | March 12, 2009 | Reply

  8. I’m soory about my english…
    I’d like what you were doing here!
    In part 16, you said that there is a lot ways to draw a sky.
    I thought on anouter way that I think it’s the “cheaper”(from the CPU and GPU) and with the most quality, but I don’t understand how to do it.
    Instand of draw a 3D sky-mesh to 2D and calculate the color of the sky in 3D, just draw at each pixel of the screen the color of the sky, becouse it simple to find the 3D radius vector(don’t forget, we talk about a sphere) from the 2D point(pixel) on the screen!
    And the Z-buffer is cool becouse we Draw the 2D sky that converted from 3D 🙂 !
    And you don’t need more vertex to more quality!
    Think about that 🙂

    Comment by Tal | June 30, 2009 | Reply

  9. outdoor lightings should be energy efficient, that is why we should use compact flurescent lamps or sodium vapor lamps.`*

    Comment by Compact Fluorescent Bulb : | October 29, 2010 | Reply


Leave a reply to KaBaL Cancel reply