XNA UK User Group
A helping hand for bedroom coders throughout the land.
Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse

Blogs

RandomChaos

Syndication

http://www.youtube.com/v/2n7_hXgoQdE <p><a href="http://www.youtube.com/v/2n7_hXgoQdE">http://www.youtube.com/v/2n7_hXgoQdE</a></p>

All my HLSL and lighting knowledge has come pretty much from one book Programming Vertex and Pixel Shaders by Wolfgang Engel, which I insist you should buy if you are getting into shaders as it takes you from the very basics of where the Vertex and Pixel shaders fit into the flow of graphics pipeline as well as take you onto more advance techniques.

What I intend to do here in this series is post my understanding of what this book has given me as well as my experience of playing with shaders in general.

I will assume you know how to setup a vertex structure, load shaders and models up in XNA and how to pass parameters to shaders and will focus on just the HLSL code her. The downloadable samples will have full XNA 3.0 solutions in them showing how they are applied so if you don't know this; you can check out the code.


Ambient Light

We will start with the simplest form of lighting I know (though the hardest to explain :P), ambient light. Ambient light is light that has no real source, it's everywhere. I see it as the light generated by the environment around the object, so this light would be generated from say the sun, but it is being bounced off the objects around you, not direct sunlight. Ambient light has a colour and an intensity, but no direction.

This is a very simple shader, we  have 3 parameters to pass to the shader, the world * view * projection matrix, a light intensity and a light colour.

 

float4x4 wvp : WorldViewProjection;
float AmbientIntensity = 1;
float4 AmbientColor : AMBIENT = float4(.5,.5,.5,1);

As you can see, I have also used semantics so FX Composer (I have version 1.8, my lappy wont run the new one) can interpret the parameters and given default values. I then define three structures, one for the data coming into the vertex shader, one for the data leaving the vertex shader (same structure used for data entering the pixel shader) and finally one for the output from the pixel shader.

struct VS_IN
{
    float4 Position : POSITION;
};
struct VS_OUT
{
    float4 Position : POSITION;
};
struct PS_OUT
{
    float4 Color : COLOR;
};

Simple stuff, all we need for this shader is the vertex position, and the color coming out of the pixel shader. The vertex shader sets up the return structure, takes the position data, transforms it and then passes it onto the pixel shader.

VS_OUT VS_Ambient(VS_IN input)
{
    VS_OUT output = (VS_OUT)0;
    output.Position = mul(input.Position,wvp);
    return output;
}

The pixel shader then sets up the output structure then populates the Color member with the AmbientIntensity with the AmbientColor, so lighting the model.

PS_OUT PS_Ambient(VS_OUT input)
{
    PS_OUT output = (PS_OUT)0;
    output.Color = AmbientIntensity * AmbientColor;
    return output;
}

In the shader you can see I am setting the ambient colour to a simple gray, in code I pass the shader parameter the color CornflowerBlue, the same as the clear colour, but I set the intensity to .5 giving the following image.

So, as well as introduce the basic ambient lighting effect you also see how I have formed the shader. You don't have to have a VS_IN structure, all that data can be in the VS_Ambient method parameters and you don't have to have a PS_OUT structure you can just return a float4 and add the semantic COLOR to the end of the function header. I use these structures as it helps me structure my shader in a uniform manner.

Diffuse Light

Diffuse light; is light that has a direction and is a very simple lighting shader. In this sample I wont cover light intensity and colour, as we covered that in the Ambient Light shader. As we have a light direction we also need to know the normal of the surface the light will be bouncing off. The amount of light that is getting bounced off the surface will be proportional to the angle the light is hitting the face. So if the light is positioned directly over the surface, directly inline with the normal the diffuse light value will be 1, if it is directly opposite to the normal it will be 0.

NOTE: In this shader we are passing the light DIRECTION, not the light POSITION. The light direction relative to the model can be obtained by subtracting the models position from the light position, e.g. LightDir = obj.Position - light.Position. You will see this in the download sample.

We have two new parameters to add to the shader, a world; inverse; transpose matrix and a light direction.

float4x4 itw : WorldInverseTranspose;
float3 LightDirection : Direction = float3(0,50,10);

In VS_IN we need to know the vertex normal so we add a normal to the structure. In VS_OUT we add two new members, one to hold the normalized light direction and one to hold the transformed normal, PS_OUT is unchanged.

struct VS_IN
{
    float4 Position : POSITION;
    float3 Normal : NORMAL;
};

struct VS_OUT
{
    float4 Position : POSITION;
    float3 Light : TEXCOORD0;
    float3 Normal : TEXCOORD1;
};

struct PS_OUT
{
    float4 Color : COLOR;
};

Now, as well as transforming the vertex position we need to transform the normal, this is done by multiplying it by the WorldInverseTranspose matrix. Transforming normals is different to transforming a position as they are just directions in space and all we need is to rotate them to get the correct direction for the transformed model, the data for this rotation is held in the WorldInverseTranspose which is then normalized.

VS_OUT VS_Diffuse(VS_IN input)
{
    VS_OUT output = (VS_OUT)0;
    output.Position = mul(input.Position,wvp);
    output.Light = normalize(LightDirection);
    output.Normal = normalize(mul(itw,input.Normal));
    return output;
}

We can now make our lighting calculation by finding the dot product of the light direction and the normal. The dot product gives the angle between two vectors. We  than use the saturate function to clamp the value between 0 and 1.

PS_OUT PS_Diffuse(VS_OUT input)
{
    PS_OUT output = (PS_OUT)0;
    output.Color = saturate(dot(input.Light,input.Normal));   
    return output;
}

The diffuse shader results in the following image

And so combining these shaders gives us this result

In the solution you can move around all three shaders at once, you can also move the light position around and see the models lit from different angles as can be seen in the clip above as well as alter the ambient intensity.

As ever you C&C are welcome.

You can download the solution to this sample here.


Posted Tue, Nov 11 2008 11:09 AM by Nemo Krad

Comments

Simon (Darkside) Jackson wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Wed, Nov 12 2008 10:36 AM

Fantastic stuff Charles.

making my shader learning process all that much easier, are there any other references your using to build your knowledge?

Nemo Krad wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Wed, Nov 12 2008 11:18 AM

As a reference I have "The COMPLETE Effect And HLSL Guide" which is great for HLSL.

There is a nice little shader tut (well some shaders) that comes with the DirectX SDK and covers stuff like hemispherical shading, environment reflection and also simple vertex animation in the shader.

Microsoft.com is also good (as ever) and then there is the slew of books I have like GPUGems, ShaderX, Game Programming Gems, Reimers Recipes etc... just got to read them all ;P

steve wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Tue, Dec 23 2008 5:26 AM

Could you maybe expand a bit on the WorldViewProjection and WorldInverseTranspose to explain how they should be calculated?

Nemo Krad wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Tue, Dec 23 2008 9:18 AM

OK,

The WorldViewProjection is exactly that, a matrix created from the objects World matrix, multiplied by the Cameras projection and View Matrix.

The WorldInverseTranspose matrix is again the objects World matrix but then you use the Matrix.Inverse() and Matrix.Transpose() methods to get the desired WorldInverseTranspose.

If you take a look at the sample code in the download you will how I am doing this in the Draw call of the MyModelClass.

Just got the exact lines for you:

World Matrix looks like this:-

Matrix world = Matrix.CreateScale(Scale) * Matrix.CreateFromQuaternion(Rotation) * Matrix.CreateTranslation(Position);

WorldViewProjection like this:-

Matrix wvp = world * Camera.View * Camera.Projection;

WorldInverseTranspose like this:-

Matrix.Invert(Matrix.Transpose(world))

Davis wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Sat, Jan 17 2009 6:06 PM

I didnt use inverse transpose matrix. i just used world matrix, and the results are OK.

Nemo Krad wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Sun, Jan 18 2009 9:40 AM

See the next post in this tutorial....

Xaul wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Wed, Jan 28 2009 9:15 PM

Did this tutorial, but for some reason the light works incorrectly on x-axis. The light comes from opposite direction of the light source and if I put if for example in middle of two objects they receive light from left and right, but not from the middle.

Nemo Krad wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Wed, Jan 28 2009 9:37 PM

Are you getting this with the sample unchanged?

If it is your own code, then I will need a code sample to look at. Post it here if you like, or PM me and I'll take a look.

Odd issue though, as you can see in the clip at the top of the post, the lighting is right no matter where I put the source..

pinkfish wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Tue, Feb 3 2009 1:47 AM

I have a question:

to transform the position u use:

output.Position = mul(input.Position,wvp);

// mul(Vector, Matrix)

to transform the normal u use:

output.Normal = normalize(mul(itw,input.Normal));

// mul(Matrix, Vector)

can you explain why? I am confused...

Nemo Krad wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Wed, Feb 4 2009 2:10 PM

OK,

The intrinsic HLSL function mul() multiplies 2 matricies, if the first param is a Vector then it is treated as a row vector, if the second is a Vector then it is treated as a Column Vector.

The Transpose of the world matrix turns the matrix on it's side so we need to use the Colun Vector rather than a Row Vector.

Thats how I understand it working.

Jay wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Fri, Feb 6 2009 3:49 AM

Tested in RenderMonkey not working. Would be nice if you got your code right before posting it on the internet.

Nemo Krad wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Fri, Feb 6 2009 9:08 AM

Thanks for such a constructive and informative comment.

Maybe, when you copy and paste code and find it does not work out side of the sample it was written for, it might be nice if you can find out why it's not working as you expect, then you can post your solution to the issue and help others out that might be having the same problem.

I suspect this may well be user error. At time of writing this response, 298 people have downloaded this sample, you are the first one to have it not work......

Chr0n1x wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Fri, Feb 6 2009 10:36 AM

Nemo,

Excellent work with the series, they explain the concepts well.

Jay,

I cannot find any issue with this sample myself. I have tested this in FX Composer, XNA and Native DirectX. (Unfortunately RenderMonkey decided to BlueScreen my Win7 install)

RenderMonkey is rather more complex though, and so I would highly suggest you triple check your settings before making such a strong statement. It makes it look like you are trolling, I hope you aren't.

Remember that Vertex Shader and Pixel Shaders in Rendermonkey are separate, and also remember to change the Entry point for the two shaders.

Jay wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Fri, Feb 6 2009 6:03 PM

Me and my big f**king mouth. Turns out I'm just a noob with Rendermonkey, big surprise there. I grovellingly withdraw my previous statement.

Good day to you sir ^^

Nemo Krad wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Fri, Feb 6 2009 8:25 PM

No worries, glad you were not trolling.

If you do find issues though, and I am sure you will, just let me know and I will fix them.

Happy coding :)

dawchiks wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Sat, Aug 22 2009 3:14 PM

nice tutorial, thx.

but with WorldInverseTranspose its just all black.

if i use just World matrix it is OK.

Nemo Krad wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Sat, Aug 22 2009 5:30 PM

Odd, is that with my code or your implementation of it?

As you will see in later posts you don't have to. Glad you like the tuts.

EvilWeebl wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Tue, Feb 2 2010 7:00 PM

Hiya just wanted to say great tutorial. Just one problem that I drove myself mad with for half a day for no reason is that when rendering the skull with diffuse it was just black but the sphere and teapot worked fine. After comparing the skull, teapot, and sphere in directx viewer I noticed the skull had no normals!

Im using directx9 instead of xna and spent ages wondering why yours was clearly working by the pictures, I imagine on loading a .x mesh xna checks for normals and create its own if neccesary whereas I have to do that manually in directx9.

Anyway, brilliant tutorial all the same and will be sure to move on to the next ones.

Nemo Krad wrote re: Basic HLSL Lighting Techniques - Episode 1: Ambient and Diffuse
on Wed, Feb 3 2010 9:13 AM

Yes the content importer may well do that, I know that mesh has no UV map on it too.

Glad you are finding them useful :)