Directional Lights

    There are three common types of lights: directional, point lights, and spot lights. In this chapter we will see how to add a directional light to our renderer. Directional lights are lights which equally illuminate all objects from a given direction. Because all objects are equally illuminated, the computations for directional lights need to be done on all pixels on the screen. This makes these types of lights expensive, so it is recommended to have a small number of directional lights. This limitation will not exist for other types of lights.

    To apply directional lights, we will apply a full-screen post processing pass. Here, we’ll take each pixel, and based on the normals and position read from the G-Buffer, we compute the amount of illumination. We will create an effect file for this, named DirectionalLight.fx. Applying this shader will generate a lightmap that will later be combined with the colors from the G-Buffer. For this, we will output the diffuse light, (which may be colored) to the rgb channels, and the specular light, (which we will always consider white) to the alpha channel. When combining these in the end, we will use the equation FinalColor = DiffuseColor * DiffuseLight + SpecularLight.

    Since we are applying shading as a post processing, we need to make sure we properly align pixels to texels. To understand why we need to to this, you may want to read Directly Mapping Texels to Pixels, on MSDN. We’ll add a member for this in DeferredRenderer, and initialize it to half the size of a pixel.

private Vector2 halfPixel;
    [...]
    protected override void LoadContent()
    {
        halfPixel.X = 0.5f / (float)GraphicsDevice.PresentationParameters.BackBufferWidth;
        halfPixel.Y = 0.5f / (float)GraphicsDevice.PresentationParameters.BackBufferHeight;
        [...]
    }

    Back to the shader, inside DirectionalLight.fx, as properties of a directional light, we need parameters for the light direction, and light color. For specular computations, we need to know the position of the camera. Finally, in order to compute the world position of a pixel when knowing the screen depth, we need the inverse matrix of the ViewProjection matrix. We also need parameters for the textures in the G-Buffer: color, normal and depth.

//direction of the light
float3 lightDirection;
//color of the light 
float3 Color; 
//position of the camera, for specular light
float3 cameraPosition; 
//this is used to compute the world-position
float4x4 InvertViewProjection; 
// diffuse color, and specularIntensity in the alpha channel
texture colorMap; 
// normals, and specularPower in the alpha channel
texture normalMap;
//depth
texture depthMap;
sampler colorSampler = sampler_state
{
    Texture = (colorMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = LINEAR;
    MinFilter = LINEAR;
    Mipfilter = LINEAR;
};
sampler depthSampler = sampler_state
{
    Texture = (depthMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = POINT;
    MinFilter = POINT;
    Mipfilter = POINT;
};
sampler normalSampler = sampler_state
{
    Texture = (normalMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = POINT;
    MinFilter = POINT;
    Mipfilter = POINT;
};

    The vertex input and outputs are positions and texture coordinates. We will be using Ziggyware’s QuadRenderer again, which sets the vertex positions directly in screen-space, so the vertex shader will not transform them in any way. The texture coordinates will just be passed forward after we align them, using one half of a pixel.

struct VertexShaderInput
{
    float3 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
};
struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
};
float2 halfPixel;
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;
    output.Position = float4(input.Position,1);
    //align texture coordinates
    output.TexCoord = input.TexCoord - halfPixel;
    return output;
}

    Now we need to write the pixel shader. First we need to get the data we need out of the G-Buffer. Most of it is straightforward, like normals and specular coefficients.

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    //get normal data from the normalMap
    float4 normalData = tex2D(normalSampler,input.TexCoord);
    //tranform normal back into [-1,1] range
    float3 normal = 2.0f * normalData.xyz - 1.0f;
    //get specular power, and get it into [0,255] range]
    float specularPower = normalData.a * 255;
    //get specular intensity from the colorMap
    float specularIntensity = tex2D(colorSampler, input.TexCoord).a;

    This would be enough for diffuse lighting. But for specular lighting we need to have the vector from the camera to the point being shaded. And for this, we need the position of the point. Right now, we have the depth in the depthMap, and position on the screen in the [0,1][0,1] range, which comes from the texture coordinates. We will move this into screen coordinates, which are in [-1,1][1,-1] range, and then using the InverseViewProjection matrix, we can get them back into world coordinates. If you need to better understand the different coordinate spaces, like world space, view space, screen space, check the Shader Series on creators.xna.com.

//read depth
 float depthVal = tex2D(depthSampler,input.TexCoord).r;
 //compute screen-space position
 float4 position;
 position.x = input.TexCoord.x * 2.0f - 1.0f;
 position.y = -(input.TexCoord.y * 2.0f - 1.0f);
 position.z = depthVal;
 position.w = 1.0f;
 //transform to world space
 position = mul(position, InvertViewProjection);
 position /= position.w;

    After we compute the vector from the surface towards the light (which is in this case the negated lightDirection), we compute the diffuse light with the dot product between the normal and the light vector. The specular light is computed using the dot product between the light reflection vector and the camera-to-object vector. The output will contain the diffuse light in the RGB channels, and the specular light in the A channel. The technique will use these shaders, and compile them with SM2.0

//surface-to-light vector
    float3 lightVector = -normalize(lightDirection);
    //compute diffuse light
    float NdL = max(0,dot(normal,lightVector));
    float3 diffuseLight = NdL * Color.rgb;
    //reflexion vector
    float3 reflectionVector = normalize(reflect(lightVector, normal));
    //camera-to-surface vector
    float3 directionToCamera = normalize(cameraPosition - position);
    //compute specular light
    float specularLight = specularIntensity * pow( saturate(dot(reflectionVector, directionToCamera)), specularPower);
    //output the two lights
    return float4(diffuseLight.rgb, specularLight) ;
}
technique Technique0
{
    pass Pass0
    {
        VertexShader = compile vs_2_0 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

    To use this shader, let’s go in the DeferredRenderer class, and add a new member to hold our effect. This is then initialized in LoadContent.

private Effect directionalLightEffect;
[...]
protected override void LoadContent()
{
    [...]
    directionalLightEffect = Game.Content.Load<Effect>("DirectionalLight");
}

    We will add a new function in the DeferredRenderer class, that draws a directional light. The parameters for this function will be the direction and color of this light. Inside this function, we will just set-up the effect and render a full-screen quad.

private void DrawDirectionalLight(Vector3 lightDirection, Color color)
{
    //set all parameters
    directionalLightEffect.Parameters["colorMap"].SetValue(colorRT.GetTexture());
    directionalLightEffect.Parameters["normalMap"].SetValue(normalRT.GetTexture());
    directionalLightEffect.Parameters["depthMap"].SetValue(depthRT.GetTexture());
    directionalLightEffect.Parameters["lightDirection"].SetValue(lightDirection);
    directionalLightEffect.Parameters["Color"].SetValue(color.ToVector3());
    directionalLightEffect.Parameters["cameraPosition"].SetValue(camera.Position);
    directionalLightEffect.Parameters["InvertViewProjection"].SetValue(Matrix.Invert(camera.View * camera.Projection));
    directionalLightEffect.Parameters["halfPixel"].SetValue(halfPixel);
    directionalLightEffect.Begin();
    directionalLightEffect.Techniques[0].Passes[0].Begin();
    //draw a full-screen quad
    quadRenderer.Render(Vector2.One * -1, Vector2.One);
    directionalLightEffect.Techniques[0].Passes[0].End();
    directionalLightEffect.End(); 
}

    At this point, if we add a call to this function in the Draw function, we should see on the screen the lighting of the object, without colors. We will only see the diffuse lighting, because the specular light is in the alpha channel.

public override void Draw(GameTime gameTime)
{
    SetGBuffer();
    GraphicsDevice.Clear(Color.Gray);
    scene.DrawScene(camera,gameTime);
    ResolveGBuffer();
    GraphicsDevice.Clear(Color.Black);
    DrawDirectionalLight(new Vector3(1,-1,0),Color.White);
    base.Draw(gameTime);
}

lightMap

    In this moment, we have a few problems:

  • If we try to call DrawDirectionalLight more than once, only the last light will be drawn. We will fix this next.
  • We want to combine this lightmap with the object colors. We will also deal with this very soon.
  • Some parts of the objects (the wings) are not lit right. This is because this model has some normals set wrong. In a later chapter, when we add normal mapping and other objects, this will go away.

Drawing more lights

    Even though we saw that a deferred renderer should not use too many directional lights, we will modify the code to support many lights, because this will also be needed for the point lights and spot lights. We will move all code for drawing lights in a new function, called DrawLights, which will be called from the Draw function.

public override void Draw(GameTime gameTime)
{
    SetGBuffer();
    ClearGBuffer();
    scene.DrawScene(camera, gameTime);
    ResolveGBuffer();
    DrawLights(gameTime);
    base.Draw(gameTime);
}

    In order to have multiple lights, we will draw them using alpha blending. Since lighting an object with two lights results in a brighter object, we will use Additive blending. Our alpha channel also contains useful data, so we need to apply the same operation on the alpha channel. Finally, after adding 3 directional lights, the DrawLights code would look like this. By using Additive blending, all lights are added together, to create the final light.

private void DrawLights(GameTime gameTime)
{
    //clear all components to 0
    GraphicsDevice.Clear(Color.TransparentBlack);
    GraphicsDevice.RenderState.AlphaBlendEnable = true;
    //use additive blending, and make sure the blending factors are as we need them
    GraphicsDevice.RenderState.AlphaBlendOperation = BlendFunction.Add;
    GraphicsDevice.RenderState.SourceBlend = Blend.One;
    GraphicsDevice.RenderState.DestinationBlend = Blend.One;
    //use the same operation on the alpha channel
    GraphicsDevice.RenderState.SeparateAlphaBlendEnabled = false;
    //draw some lights
    DrawDirectionalLight(new Vector3(0, -1, 0), Color.White);
    DrawDirectionalLight(new Vector3(-1, 0, 0), Color.Crimson);
    DrawDirectionalLight(new Vector3(1, 0, 0), Color.SkyBlue);
    GraphicsDevice.RenderState.AlphaBlendEnable = false;
}

Running the code now should show something like this:

mulitpleLights

Composing the final image

    Now that we have the lightmap, we need to combine this with the colors, to obtain the final image. To do this, we will render the lights into another RenderTarget, which will then be used as an input to an Effect that composes the final image. We add the new rendertarget, initialize it, and set it up when drawing the lights.

private RenderTarget2D lightRT;
   [...]
   protected override void LoadContent()
   {
       [...]
       lightRT = new RenderTarget2D(GraphicsDevice, backBufferWidth,
                                                           backBufferHeight, 1, SurfaceFormat.Color);
       [...]
   }
   private void DrawLights(GameTime gameTime)
   {
       GraphicsDevice.SetRenderTarget(0, lightRT);
       //code to setup alpha blending and draw lights
       [...]
       GraphicsDevice.SetRenderTarget(0, null);
   }

    Next, we will add a new Effect file to the Content project, named CombineFinal.fx. In this effect file, we will need the color map, and the light map. The vertex shader inputs and outputs are the position and texture coordinates. As with DirectionalLight.fx, the vertex shader outputs the transformed coordinates.

texture colorMap;
texture lightMap;
sampler colorSampler = sampler_state
{
    Texture = (colorMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = LINEAR;
    MinFilter = LINEAR;
    Mipfilter = LINEAR;
};
sampler lightSampler = sampler_state
{
    Texture = (lightMap);
    AddressU = CLAMP;
    AddressV = CLAMP;
    MagFilter = LINEAR;
    MinFilter = LINEAR;
    Mipfilter = LINEAR;
};
struct VertexShaderInput
{
    float3 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
};
struct VertexShaderOutput
{
    float4 Position : POSITION0;
    float2 TexCoord : TEXCOORD0;
};
float2 halfPixel;
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
    VertexShaderOutput output;
    output.Position = float4(input.Position,1);
    output.TexCoord = input.TexCoord - halfPixel;
    return output;
}

    In the pixel shader, we use the formula mentioned earlier to obtain the final color: FinalColor = DiffuseColor * DiffuseLight + SpecularLight

float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
{
    float3 diffuseColor = tex2D(colorSampler,input.TexCoord).rgb;
    float4 light = tex2D(lightSampler,input.TexCoord);
    float3 diffuseLight = light.rgb;
    float specularLight = light.a;
    return float4((diffuseColor * diffuseLight + specularLight),1);
}
technique Technique1
{
    pass Pass1
    {
        VertexShader = compile vs_2_0 VertexShaderFunction();
        PixelShader = compile ps_2_0 PixelShaderFunction();
    }
}

    Back in the DeferredRenderer class, we’ll need to load this effect, and draw a full screen quad using it.

private Effect finalCombineEffect;
[...]
protected override void LoadContent()
{
    [...]
    finalCombineEffect = Game.Content.Load<Effect>("CombineFinal");
}
private void DrawLights(GameTime gameTime)
{
    GraphicsDevice.SetRenderTarget(0, lightRT);
    //draw lights to the lightMap
    [...]
    GraphicsDevice.SetRenderTarget(0, null);
    //set the effect parameters
    finalCombineEffect.Parameters["colorMap"].SetValue(colorRT.GetTexture());
    finalCombineEffect.Parameters["lightMap"].SetValue(lightRT.GetTexture());
    finalCombineEffect.Parameters["halfPixel"].SetValue(halfPixel);
    finalCombineEffect.Begin();
    finalCombineEffect.Techniques[0].Passes[0].Begin();
    //render a full-screen quad
    quadRenderer.Render(Vector2.One * -1, Vector2.One);
    finalCombineEffect.Techniques[0].Passes[0].End();
    finalCombineEffect.End();
}

    If we try to run the code, the final image will appear.

composedImage

    In this chapter, we saw how to add directional lights to the renderer, how to support multiple lights through alpha blending, and how to obtain the final image. Next we will see how we can add point lights, and spot lights. You can download the code for this chapter here: Chapter3.zip

  • MeantikeHaize

    Hi

    As a fresh http://www.catalinzima.com user i just wanted to say hello to everyone else who uses this forum 8-)

  • http://www.catalinzima.com Catalin Zima

    Hello :)

  • mnorgovudkka

    Hy my name is mnorgovudkka
    Im from mongolia
    Buy

  • poephepsy

    What is bumburbia?

  • Viekengenia

    Hello all!

    I’m new here.

    So i’d like to ask you if someone of you or your frineds was fired because of a financial crisis?

  • http://prostor99.ru ooo-prostor

    Фирма Простор занимается художественным оформлением любых помещений. От офисов и ресторанов до коттеджей и квартир.
    Стоит отметить, что предоставляемые услуги в корне отличаются от московских аналогов. Лучшие московские художники, скульпторы, резчики, граверы будут заниматься оформлением Вашего помещения
    http://prostor99.ru

  • Gen Mark

    Hey, nice tutorial.

    One problem, what is the easiest way to stop the extra specular highlight pixels getting lit when for example you look towards the light position?

    Some sort of test on the depth value perhaps – so we don’t end up lighting the far plane? I can’t seem to get that to work…

    Or do I have a blending mode set somewhere that I shouldn’t?

  • Martin

    Hello, i recognize the problem that Gen Mark is discribing. hope that someone knows of a solution for this unwanted effect, i also can’t get that to work…

    thanks in advance.

  • Martin

    the answer for me is self-shadowing, in case someone else is looking into this: color = ambient + selfshadow*(diffuse + specular) ; where selfshadow = saturate(dot(normal, lightdir))

  • http://fxcritic.blogspot.com Chance

    Thanks for another great tutorial :)

    Just a quick note. For this lesson to work on XNA 3.1 I had to turn off the depth buffer before drawing any lights:

    GraphicsDevice.RenderState.DepthBufferEnable = false;

    Or else pixels would be culled out of the post-render pass for some reason.

  • Pingback: Optimering och strukturering « En projektdagbok

  • ZWabbit

    Don’t know if you’re still checking comments for this, but I wanted to ask if there was a reason you got the coordinates for the DirectionalLight pixel shader through the texture coordinate instead of using something like VPOS, ignoring the fact you’re using shader model 2.

  • http://www.catalinzima.com Catalin Zima

    I wanted to keep it SM2 compatible.

  • Camus

    Just a little issue, and I know that is only a demo, but, you are using new on this:

    new Vector3(0, -1, 0),

    That lines are in the render loop, each frame the memory increases linearly, maybe 12Bytes, but thinking on 60fps about a minute, you’ll have 43200Bytes, taken in account that there are 3 news (each for vector) is about 127Kbytes more per minute.

    Well is nothing to worry about if is only a demo :)

  • Chris

    I am trying to follow this tutorial and convert it to Xna 4.0. I have it working somewhat up to this tutorial. I have been encountering a depth issue from the start when rendering the ship. The ship is drawing parts from behind on top of the parts in the front.

    I thought it had to do with renderstates and what the spritebatch does to them. So I set them to the default before drawing the ship via:

    game.GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
    game.GraphicsDevice.DepthStencilState = DepthStencilState.Default;
    game.GraphicsDevice.BlendState = BlendState.Opaque;
    game.GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;

    Now according to Shawn Hargreaves article on render states in Xna 4.0 this should set everything back to default for rendering. I still have the depth issues with this.

    I thought after it didn’t change anything that I would move on with the tutorial and see if the problem would correct itself. This part of the tutorial doesn’t use a sprite batch so it shouldn’t be messing with the states. I still left the above code in place, but I am still having the issue with depth.

    I have tried rendering the ship in another project with no problems using the BasicEffect shader. I am going to ask about it on the Xna creators forums as well once they are back up from maintenance, thought I would ask you here first.

  • Chris

    I have fixed the problem I was having with depth. Found out that you need to specify a depth format when creating your renderTargets in Xna4.0 when you want to store the depth. Now everything is working fine. Here is how you need to setup your renderTargets for those trying to convert this over to Xna4.0.

    colorRT = new RenderTarget2D(GraphicsDevice, backBufferWidth, backBufferHeight, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8);
    normalRT = new RenderTarget2D(GraphicsDevice, backBufferWidth, backBufferHeight, false, SurfaceFormat.Color, DepthFormat.None);
    depthRT = new RenderTarget2D(GraphicsDevice, backBufferWidth, backBufferHeight, false, SurfaceFormat.Single, DepthFormat.None);
    lightRT = new RenderTarget2D(GraphicsDevice, backBufferWidth, backBufferHeight, false, SurfaceFormat.Color, DepthFormat.None);

    It only needs to be set on the color rendertarget seeing as that is were the depth target is getting the depth info from(at least I think it works that way). Hope this helps anyone following this tutorial with Xna4.0.

  • http://Website DC

    Hey Catalin,
    don’t know if you’re still reading the comments, but I’ve got everything working fine in 4.0 except one thing:

    http://goo.gl/L0IAe

    I don’t know where the light ray in the background is coming from. I experimented with every depth/color options possible, none of them showed another image.
    How to avoid this?

  • http://perfunction.net Josh

    To get the blended lighting to work in XNA 4.0 you have to create your own blending state in LoadContent and use it in your DrawLights function.

    additiveBlend = new BlendState();
    additiveBlend.AlphaBlendFunction = BlendFunction.Add;
    additiveBlend.AlphaSourceBlend = Blend.One;
    additiveBlend.AlphaDestinationBlend = Blend.One;
    additiveBlend.ColorBlendFunction = BlendFunction.Add;
    additiveBlend.ColorSourceBlend = Blend.One;
    additiveBlend.ColorDestinationBlend = Blend.One;

  • http://Website flassiree

    Лучшая кинолента на ваш взгляд ?

    Пользователи форума http://www.catalinzima.com поделитесь

    Мой – Форрест Гамп

  • http://Website Luchescasse

    hi there amazing thread we have going there.

  • http://Website Luchescasse

    Hi nice topic you have going here.

  • http://Website fleefilkpam

    article template

  • http://Website Luchescasse

    hi great thread you have going here.

  • http://Website RafaelaKarst

    I am here to say hello to every person.
    Happy to join you hear.

  • http://Website andreeahealth

    The holidays provide prodigality of opportunities as a service to overindulgence: extravagant dinners, tantalizing treats, etc. Occasionally the lead on can be difficult to curb – resulting in holiday power gain. But around implementing a useful weight loss game, losing those leave of absence pounds – and orderly lifetime influence control – can be well within your reach.

  • http://Website grouccumb

    Tory Burch Flats,what i have dreamed of them for so long time.Everyday i wake up,the first thing is to see my banlenciaga handbag,i cannot believe it is belongs to me,i am so excited.Chanel sunglasses,that is good thing when you travel or take pictures.If you have a paire of Ray Ban sunglasses,you will know what is the great happiness,not only for men,but also for women.Ed Hardy is a brand which can let you know what is the real artist.Do you want to let you be sexy and perfet?Herve dress is the best gift for women.Beauty or grace,just for the women who has Balenciaga bags.Birkenstock shoes,what is the best choice for you,just let you feel the shoes are not just the shoes,are your feet.Ghd hair straighteners,let you say goodbye to the past.Having ghd straighteners,you will know what is the most beautiful hair,and you will be sexy for your beautiful hair.

  • http://Website Jadyskiddic

    honourable gonna phrase hello…

  • Deniz

    Hello, thanks for the great tutorial.

    I’m having a bit of a problem in directional lights. When I include specular lights
    return float4((diffuseColor * diffuseLight + specularLight),1);
    I get a full white screen.
    When I don’t
    return float4(diffuseColor * diffuseLight),1);
    I see the lit object without the specular components.
    I rechecked both DirectionalLight.fx and CombineFinal.fx but I can’t seem to find the miscalculation in specular lights.
    Do you have any idea on how to solve this?
    Thanks again.

  • http://www.celinebagsoutletcc.com/ carpinteyrohsa

    Hello there, You have performed an excellent job. I’ll definitely digg it and in my view suggest to my friends. I’m confident they’ll be benefited from this site.

  • http://www.newestgadgetsinfo.com PSShaun

    Newest Gadgets Info Blog provide all information about smartphones, tablets, e-readers and gadgets such as iPhone, Android, Kindle, Blackberry, Smartphones, Tablets, Tech Gadgets. Find more information about Latest Gadgets. http://www.newestgadgetsinfo.com

  • #bestcost[XAGAGGXXXGGG]

    Best service I ever had on dental implants