XNA: Adding fog to a LPP (or deferred) pipeline

Hi folks,

After a long delay, here is one more gift for you: how to add fog to your scene. I will talk about global fog, although it can be easily extended to local fog volumes like spheres, cubes, etc.

Fog is a great visual feature in any rendering. It helps not only creating atmosphere and environment cues, but also improves the depth perception of your scene. As a bonus, you can use it to camouflage the unpleasant pop in/out side effect of distant object culling.

Here we have same view with and without fog:

The effect is basically a color interpolation based on the depth of the current pixel. You can have a look here and here for some good tutorials, as I won’t enter the gritty details of the physics behind it (it’s not that hard, though: don’t be scared to learn the real deal).

In the fixed pipeline era, it was really simple to add fog to your scene: just enable a flag, setup two or three parameters and voilà, it’s done. Now, in the programmable age, you should create it by yourself, creating lots of shader permutations (or maybe using dynamic branching) to support the fog formula at the end of the processing.

But hey: what did come to your mind when I said “the depth of the current pixel”? After lots of posts talking about our beloved GBuffer, you should know that we already have the depth buffer as a render target.

With the depth buffer at hand, all you need to do is perform a full-screen pass reading the depth value of each pixel, compute the interpolation formula and alpha blending the fog color.

Sounds easy, heh? Indeed, it is. I’ve implemented both the EXP and EXP2 formulas explained in the links above, just change the fog type to see the difference (check LightPrePass.cs).

Here is a snippet of the shader:


float4 PixelShaderFunctionExp(VertexShaderOutput input) : COLOR0{

float depthValue = tex2D(depthSampler, input.TexCoord).r;
 clip(-depthValue + 0.9999f);

 float mix = saturate((1 - exp( -depthValue * FogDensity )));

 return float4(FogColor.rgb, mix );
}

I’m doing a “clip” to avoid fogging up the skybox or background, where the depth buffer is still the far plane. You can use stencil buffer or another trick if you wish so.

The issue with this approach is that it doesn’t work for objects that don’t output to the depth buffer, like particle effects and other transparent objects. In this case, you can change those shaders to use the global fog parameters and do the same formula stated above (this is how I’m doing in my personal project).

Anyway, here is the sample source with all the code and assets to play around. Use it at your own risk!!

Ah, by the way, some folks asked me how to add game objects in my framework by code instead of the XML. I put some examples in the initialization of this sample, so take a look if you want.

That is it, see you around!

-J.Coluna

About jcoluna

Game developer and musician
This entry was posted in XNA and tagged , , , , . Bookmark the permalink.

39 Responses to XNA: Adding fog to a LPP (or deferred) pipeline

  1. Tiba says:

    Hey mate, Nice work as always!.
    I added Fog to my branch of your LPP framework a while back and i ran in to some problems coz my terrain shader had to many Samplers because i went a lil crazy with multi texture terrain so i do my fog like this:

    shader vars:
    float xFogStart = 2000;
    float xFogEnd = 25000;
    float3 xFogColor; // not in use

    in the VS:
    output.Depth = output.Position.z;
    in the PS:
    float l = saturate((input.Depth-xFogStart)/(xFogEnd-xFogStart));
    return float4(lerp(color,AmbientColor *1.5,l),1);

    Im also working on grass, its comming along very nice.
    Right now im just trying to work out if i want to draw it HW Instanced or put all the grass verts in one buffer and chunk it eg. let cpu build the grass buffers on a different thread and just pass the gpu one big buffer or HW Instanced grass……. both have about the same draw time so idk what to run with

    If anyone wants the code ill be happy to pass it a round, it takes 10-18ms to refreash the grass so i use a 2nd thread and let the main game loop pull vert buffers from the thread(its all synced up nice )

  2. @ JCuluna Nice work – you finally convinced me to move my fog from linear to exp2. Although I actually use a shader parameter for the power, so that the power curve can change at runtime, find it useful for different weather effects.

    @ Tiba – I’d instance it, with nice big chunks of grass together. I don’t think you don’t want small chunks, as too many instances are bad.

  3. Tiba says:

    Hi again, Ok so running with Instanced grass.

    I think my terrain shader is costing way to mush 😦

    Here are some SS so you can see the grass and multi-texture terrain:

  4. Looking nice 🙂 I like the triplanar projection on the cliffs, and the look of the grass and the trees. Are you on Twitter?

  5. Tiba says:

    Hey mate TY very much for the kind words, it still need some work but im very happy with the progress and the SS dont rly do it justist because your missing the dynamic clouds\sky.

    Next thing on the list is volumetric particles with lighting(will be hard as i cant find any info on it for XNA) but thats y i love this!!

    Na mate not on twitter but its something i have been thinking of

  6. I think you should join twitter – other devs like me would enjoy sharing your journey, what you are working on is interesting! It is a great way for us all to communicate, and share ideas like JColuna did with the exponential fog. Add me, and I’ll introduce you to some other devs too 🙂

  7. jaimo says:

    Thanks a lot. The problem I had was understanding how to put objects in the scene, and now it’s been solved. You’re the best!!! 🙂

  8. Tiba says:

    here is a video of my work

    • jcoluna says:

      That’s awesome!! Congrats! Do you have it running on xbox too?
      See ya!
      -J.Coluna

    • Looking great! You’ve gotta feel pretty satisfied with this 🙂

    • Chris says:

      That’s quite nice! Have you tamed your heightmaps yet? I’ve just started to use them in my engine and for reasonably large ones, you really need some quadtree/LOD algorithm to get them to run smoothly.

      • Tiba says:

        Yer mate i have a quad tree…… but i broke it when i did my rewrite because im using a smaller scale the bounding box’s are not in the right place\wrong size so im just brute forceing it atm.

        im using this http://code.google.com/p/billod-xna4/

        i understand most of it as i have had to mkae lots of changes to fin the LPP pipeline but i cant find away to make it any smaller.

        i use to run a scale of 1 for the terrain and it worked well but i had floating point problems because of the land size so i rebuilt it all at a scale of 0.01 for the terrain and now the bounding box’s are the wrong size.

      • Chris says:

        I took a look at that code and tried it out. Pretty effective, but still a lot to dig through. I think it’s using a ROAM algorithm? For me, I think I’ll pass that and use geo mipmapping instead. It looks easier to implement, while still taking advantage of quadtrees. Just a matter of fitting each chunk perfectly in the code.
        But I have to wonder with this approach, would it matter to put all the chunks in one dynamic vertex buffer and update the no. of primitives to draw on each update, or just putting each chunk in its own vertex buffer is good enough.

  9. Gary Ruddock says:

    It looks very, very good. I’d love to achieve something similar, Well done.

  10. RS says:

    Happy new year Mr. Coluna.
    Was messing with the new version and had trouble with something. Kind of silly, but I cannot find where the directional light is for the life of me. I was messing around with the pre-component based version and was used to it being another light to be added. Is there a directional light folded into an environment object or…? : /

    Also, if you don’t mind my asking. Is the ultimate goal of your engine to help with your own game or is the engine being built for its own sake?

  11. Chris says:

    J.Coluna, I really enjoy the work you’ve done so far. I’m making my own custom engine but using some of your ideas to help me along. Have you considered cascaded shadow blending? I have implemented it, showing better results, and an article that talks about it here: http://electronicmeteor.wordpress.com/2013/01/12/done-fixing-the-shadow-maps/.

    Screenshots are old, though. I moved on to Poisson disk sampling which does a fine job for approximating soft shadows. I will probably post shader code later on.

  12. Tiba says:

    @Chris Hey mate i would almost give u my 1st born to see that shader code for the CSM blending.

    I had a look at the guys shader cod ein that link u gae but i cant get my head around it.

    Ps. i should have a new video up in a few days

  13. Tiba says:

    Hello again! here is a new water shader i have been working on

  14. I was very ecstatic to uncover this site on yahoo.I needed to say thank you to you with regard to this fantastic publish!! I surelyloved each little bit of it and I have you bookmarked to look into new stuff you post.

  15. I’ve been browsing on-line greater than 3 hours today, but I by no means uncovered any fascinating post like yours. It’s pretty worth enough for me. In my see, if all webmasters and bloggers produced excellent content material as you in all probability did, the net will probably be a lot more useful than ever before.

  16. aharabada says:

    Are you still working on this project? I love it would be a pity if you wouldn’t work on it any more.

  17. aharabada says:

    Hey,
    It’s me again. Did I allready told you, that I’m using your Libraries in my own game-engine? If I didn’t you know it now. It works great but I have one big problem, I don’t know how to make/edit a cubemap. Every time when I changed it and want to load it, the game crashes and tells me that it can’t load a Texture2D as an CubeMap.

    • jcoluna says:

      Hey, you should use a specific tool that saves the 6 textures as a cubemap. I used an old ATI cubemapgen or something, microsoft/dx also have some tools for doing that.
      Thanks for visiting and using my lib, I’m honored!
      -J.Coluna

    • Tiba says:

      If you use directX 11 you can use a geometry shader to output all 6 faces in one draw call, hit me up and I can post some code.

  18. Tiba says:

    How is it going mate? long time no see!

    So I’m done with XNA and I’m moving on to SharpDX and directX 11, my engine’s shadowing is still based of your implementation(best I can find) but I’m having problems with getting the shadows to draw correctly….. I think my problem is the lightviewprojection setup or how the shadow map look up happens in the shader.

    I don’t know if you still use this blog but if u could offer any help?

    • jcoluna says:

      Hey buddy!! I’m also done with it, maybe I’m porting the engine to MonoGame. Do you have a link so I can see the code and some screenshots? See ya!

      • tiba3195 says:

        Ok so I found my problem, with DX11 you can draw a full screen triangle without setting a vertex buffer and just calculate the textcoords and vertex positions in the shader but that seems to not play very well with the getfrusumray function in the directional light shader so I’m just drawing a full screen quad for the directional light and shadows work also the boundingfrustum in SharpDX has a different set to xna so u need to swap the last 4 points around once u call make a call to getfrustumcorners.

        I looked at mono game and I tried to port my stuff over to it but in the end I found slimDX and then SharpDX to be much better, once I worked out the api most of my code and even shaders just ported over. I have a C++ project set up to compile shaders and I have it set with the compatibility flag so I can get stuff drawing and port the shaders to DX11 one by one.

        The only really hard part was getting a content loader build so I’m using assimp.net as the base and there model class is a perfect match, it even works as a drop in for XNA.

        I would love to help u out with code but its all super messy atm as I’m still getting my head around DX11 but I can post the shadow rendering class for u to look at, it should give u an idea of how to set up rendertardets and stuff with sharpDX, ill try get the main render class looking nice so I can pass it on to you.

        To tell u the truth if u want to port you engine over to sharpDX ill be happy to help but I think you will only need to see how I load models and set things up for rendering and u will be set.

  19. tiba3195 says:

    here we go, shadow render class its only for the directional light but it works and its simple.

    I work in VB.net but I’m sure u can find a converter to get it to C#

    Public Class ShadowRenderer

    Public ShadowBuffer As Texture2D
    Public ShadowBufferView As ShaderResourceView
    Public ShadowBufferRT As RenderTargetView
    Public LightViewProjectionMatrices As Matrix() = New Matrix(NUM_CSM_SPLITS – 1) {}
    Public LightClipPlanes As Vector4() = New Vector4(NUM_CSM_SPLITS – 1) {}

    Public Const BASE_CASCADE_SHADOW_RESOLUTION As Integer = 4096

    Public Const CASCADE_SHADOW_RESOLUTION As Integer = BASE_CASCADE_SHADOW_RESOLUTION / NUM_CSM_SPLITS

    Public Const NUM_CSM_SPLITS As Integer = 3

    ‘temporary variables to help on cascade shadow maps
    Private splitDepthsTmp As Single() = New Single(NUM_CSM_SPLITS) {}
    Private frustumCornersWS As Vector3() = New Vector3(7) {}

    Private MainfrustumCornersWS As Vector3() = New Vector3(7) {}
    Private frustumCornersVS As Vector3() = New Vector3(7) {}
    Private splitFrustumCornersVS As Vector3() = New Vector3(7) {}
    ”’
    ”’ Store the frustum as planes, as we are tweaking the far plane
    ”’
    Private _directionalClippingPlanes As Plane() = New Plane(5) {}

    Public Sub New()
    Dim ShadowFormat As Format
    Dim ShadowRTrDesc As Texture2DDescription
    ShadowFormat = Format.R32_Float
    ShadowRTrDesc = New Texture2DDescription() With {
    .ArraySize = 1,
    .BindFlags = BindFlags.RenderTarget Or BindFlags.ShaderResource,
    .CpuAccessFlags = CpuAccessFlags.None,
    .Format = ShadowFormat,
    .Height = CASCADE_SHADOW_RESOLUTION,
    .Width = BASE_CASCADE_SHADOW_RESOLUTION,
    .MipLevels = 1,
    .OptionFlags = ResourceOptionFlags.None,
    .SampleDescription = New SampleDescription(1, 0),
    .Usage = ResourceUsage.Default
    }
    ShadowBuffer = Engine.CreateTexture(“ShadowBuffer”, ShadowRTrDesc)
    ShadowBufferRT = Engine.CreateRenderTargetView(“ShadowBufferRT”, ShadowBuffer)
    ShadowBufferView = Engine.CreateShaderResourceView(“ShadowBufferView”, ShadowBuffer)

    Dim depthStencilDesc = New Texture2DDescription() With {
    .Width = BASE_CASCADE_SHADOW_RESOLUTION,
    .Height = CASCADE_SHADOW_RESOLUTION,
    .MipLevels = 1,
    .ArraySize = 1,
    .Format = Format.R32_Typeless,
    .SampleDescription = New SampleDescription(1, 0),
    .Usage = ResourceUsage.Default,
    .BindFlags = BindFlags.ShaderResource Or BindFlags.DepthStencil,
    .CpuAccessFlags = CpuAccessFlags.None,
    .OptionFlags = ResourceOptionFlags.None
    }

    Dim dsvDesc = New DepthStencilViewDescription() With {
    .Flags = DepthStencilViewFlags.None,
    .Format = Format.D32_Float,
    .Dimension = DepthStencilViewDimension.Texture2D
    }

    DepthStencilBuffer = New Texture2D(Engine.Device, depthStencilDesc)
    DepthStencilView = New DepthStencilView(Engine.Device, DepthStencilBuffer, dsvDesc)
    End Sub
    Private DepthStencilView As DepthStencilView
    Private DepthStencilShaderView As ShaderResourceView
    Private DepthStencilBuffer As Texture2D
    ”’
    ”’ Generate the cascade shadow map for a given directional light
    ”’
    ”’
    ”’ All meshes in the world
    ”’
    ”’
    ”’
    Public Sub GenerateShadowTextureDirectionalLight(light As Light, _context As DeviceContext)

    _context.OutputMerger.SetTargets(DepthStencilView, ShadowBufferRT)
    _context.ClearRenderTargetView(ShadowBufferRT, New Color4(1, 1, 1, 1))
    _context.ClearDepthStencilView(DepthStencilView, DepthStencilClearFlags.Depth, 1, 0)
    ‘ Get the corners of the frustum
    _context.Rasterizer.State = Engine.GraphicsDevice.RasterizerStates.CullBack

    Engine.Camera.Frustum.GetCorners(MainfrustumCornersWS)
    Dim eyeTransform As Matrix = Engine.Camera.EyeTransform

    For i As Integer = 0 To frustumCornersWS.Length – 1
    Vector3.Transform(MainfrustumCornersWS(i), eyeTransform, frustumCornersVS(i))
    Next

    Dim near As Single = Engine.Camera.NearClip, far As Single = MathHelper.Min(Engine.Camera.FarClip, light.ShadowDistance)

    splitDepthsTmp(0) = near
    splitDepthsTmp(NUM_CSM_SPLITS) = far

    ‘compute each distance the way you like…
    For i As Integer = 1 To splitDepthsTmp.Length – 2
    splitDepthsTmp(i) = near + (far – near) * CSng(Math.Pow((i / CSng(NUM_CSM_SPLITS)), 1.9F))
    Next

    Dim splitViewport As New Viewport

    Dim lightDir As Vector3 = -Vector3.Normalize(light.Transform.Forward)

    _context.OutputMerger.DepthStencilState = Engine.GraphicsDevice.DepthStencilStates.Default
    _context.OutputMerger.BlendState = Engine.GraphicsDevice.BlendStates.Opaque
    _context.Rasterizer.State = Engine.GraphicsDevice.RasterizerStates.CullNone
    For i As Integer = 0 To NUM_CSM_SPLITS – 1

    LightClipPlanes(i).X = -splitDepthsTmp(i)
    LightClipPlanes(i).Y = -splitDepthsTmp(i + 1)
    LightClipPlanes(i).Z = 0
    LightClipPlanes(i).W = 0
    LightViewProjectionMatrices(i) = CreateLightViewProjectionMatrix(lightDir, splitDepthsTmp(i), splitDepthsTmp(i + 1), i)
    Dim viewProj As Matrix = LightViewProjectionMatrices(i)

    ‘the near clipping plane is set inside the CreateLightViewProjectionMatrix method, keep it

    ‘ Set the viewport for the current split
    splitViewport.MinDepth = 0
    splitViewport.MaxDepth = 1
    splitViewport.Width = CASCADE_SHADOW_RESOLUTION
    splitViewport.Height = CASCADE_SHADOW_RESOLUTION
    splitViewport.X = i * CASCADE_SHADOW_RESOLUTION
    splitViewport.Y = 0

    _context.Rasterizer.SetViewport(splitViewport)

    Engine.Renderer.terrain.drawTessShadows(viewProj, _context)
    Engine.meshrenderer.drawShadow(_context, viewProj)
    Next

    _context.Rasterizer.SetViewport(Engine.Camera.Viewport)

    End Sub

    ‘ Private GussBlur As New GaussianBlur
    ”’
    ”’ Creates the WorldViewProjection matrix from the perspective of the
    ”’ light using the cameras bounding frustum to determine what is visible
    ”’ in the scene.
    ”’
    ”’ The WorldViewProjection for the light
    Private Function CreateLightViewProjectionMatrix(lightDir As Vector3, minZ As Single, maxZ As Single, index As Integer) As Matrix
    For i As Integer = 0 To 3
    splitFrustumCornersVS(i) = frustumCornersVS(i + 4) * (minZ / Engine.Camera.FarClip)
    Next

    For i As Integer = 4 To 7
    splitFrustumCornersVS(i) = frustumCornersVS(i) * (maxZ / Engine.Camera.FarClip)
    Next

    Dim cameraMat As Matrix = Engine.Camera.Transform

    For i As Integer = 0 To frustumCornersWS.Length – 1
    Vector3.Transform(splitFrustumCornersVS(i), cameraMat, frustumCornersWS(i))
    Next

    ‘ Matrix with that will rotate in points the direction of the light
    Dim cameraUpVector As Vector3 = Vector3.Up
    If Math.Abs(Vector3.Dot(cameraUpVector, lightDir)) > 0.9F Then
    cameraUpVector = Vector3.ForwardRH
    End If

    Dim lightRotation As Matrix = Matrix.LookAtRH(Vector3.Zero, -lightDir, cameraUpVector)

    ‘ Transform the positions of the corners into the direction of the light
    For i As Integer = 0 To frustumCornersWS.Length – 1
    Vector3.Transform(frustumCornersWS(i), lightRotation, frustumCornersWS(i))
    Next

    ‘ Find the smallest box around the points
    Dim mins As Vector3 = frustumCornersWS(0), maxes As Vector3 = frustumCornersWS(0)
    For i As Integer = 1 To frustumCornersWS.Length – 1
    Dim p As Vector3 = frustumCornersWS(i)
    If p.X < mins.X Then
    mins.X = p.X
    End If
    If p.Y < mins.Y Then
    mins.Y = p.Y
    End If
    If p.Z maxes.X Then
    maxes.X = p.X
    End If
    If p.Y > maxes.Y Then
    maxes.Y = p.Y
    End If
    If p.Z > maxes.Z Then
    maxes.Z = p.Z
    End If
    Next

    ‘ Find the smallest box around the points in view space
    Dim minsVS As Vector3 = splitFrustumCornersVS(0), maxesVS As Vector3 = splitFrustumCornersVS(0)
    For i As Integer = 1 To splitFrustumCornersVS.Length – 1
    Dim p As Vector3 = splitFrustumCornersVS(i)
    If p.X < minsVS.X Then
    minsVS.X = p.X
    End If
    If p.Y < minsVS.Y Then
    minsVS.Y = p.Y
    End If
    If p.Z maxesVS.X Then
    maxesVS.X = p.X
    End If
    If p.Y > maxesVS.Y Then
    maxesVS.Y = p.Y
    End If
    If p.Z > maxesVS.Z Then
    maxesVS.Z = p.Z
    End If
    Next
    Dim _lightBox As New BoundingBox(mins, maxes)

    Dim boxSize As Vector3 = _lightBox.Maximum – _lightBox.Minimum
    If boxSize.X = 0 OrElse boxSize.Y = 0 OrElse boxSize.Z = 0 Then
    boxSize = Vector3.One
    End If
    Dim halfBoxSize As Vector3 = boxSize * 0.5F

    ‘ The position of the light should be in the center of the back
    ‘ pannel of the box.
    Dim lightPosition As Vector3 = _lightBox.Minimum + halfBoxSize
    lightPosition.Z = _lightBox.Minimum.Z

    ‘ We need the position back in world coordinates so we transform
    ‘ the light position by the inverse of the lights rotation
    lightPosition = Vector3.Transform(lightPosition, Matrix.Invert(lightRotation))

    ‘ Create the view matrix for the light
    Dim lightView As Matrix = Matrix.LookAtRH(lightPosition, lightPosition – lightDir, cameraUpVector)

    ‘ Create the projection matrix for the light
    ‘ The projection is orthographic since we are using a directional light
    Dim lightProjection As Matrix = Matrix.OrthoRH(boxSize.X, boxSize.Y, -boxSize.Z, 0)

    Dim lightDirVS As Vector3 = Vector3.TransformNormal(-lightDir, Engine.Camera.EyeTransform)
    ‘check if the light is in the same direction as the camera
    If lightDirVS.Z > 0 Then
    ‘use the far point as clipping plane
    Dim p As New Plane(-Vector3.ForwardRH, maxesVS.Z)
    Plane.Transform(p, cameraMat, _directionalClippingPlanes(5))
    Else
    ‘lightDirVS.Z < 0
    'use the closest point as clipping plane
    Dim p As New Plane(Vector3.ForwardRH, minsVS.Z)
    Plane.Transform(p, cameraMat, _directionalClippingPlanes(5))
    End If

    Return lightView * lightProjection
    End Function
    End Class

  20. angrywasp says:

    Could you please reupload your code. Dropbox links are all broken

    Thanks

  21. angrywasp says:

    Yep, they work fine now. Thanks. I have read through the articles and i find it an interesting concept. The code will help to visualize the process a lot better, even though i don’t have XNA installed.

  22. angrywasp says:

    I have been looking at the code, and there is one little bit of HLSL i was hoping you could explain to me. In LightingLPP.fx DirectionalLightShadowPS there is the following statement

    float3 weights = ( pos.z < CascadeDistances );

    I can figure out what the result of this statement is. You are comparing a float value to a float3 and directly assigning it to a float3. How does that work? what values will end up in the weights variable?

    Thanks

    • jcoluna says:

      IIRC it will assign to weights the individual comparisons, ie
      weights.x = pos.z < CascadeDistances.x
      weights.y = pos.z < CascadeDistances.y
      weights.z = pos.z < CascadeDistances.z

      The results will be either 0.0f (false) or 1.0f (true)

Leave a reply to jcoluna Cancel reply