Hi All,

I've just started a new series of XNA demos on my website. So far I have 3 demos: XNA Per-Pixel Lighting Demo, XNA Normal Mapping Demo, and XNA Parallax Normal Mapping Demo.

I haven't included any pre built executables with the demos, so you'll have to build them yourself from the source provided with each demo. The demos target the Windows platform, but should run with little or no modifications for the Xbox 360. I chose to only target Windows to reduce the complexity of the source code.

The per-pixel lighting demo implements per-pixel Blinn-Phong lighting (including specular lighting). The normal mapping demo implements tangent-space normal mapping. The demo also shows you how to calculate the tangent space basis vectors (tangent, bitangent (aka binormal), and normal vectors). Finally the parallax normal mapping demo extends the normal mapping demo by adding parallax mapping with offset limiting.

All demos include an effect (.fx) file with techniques for directional, point, and spotlight lighting.

Hopefully the code is clean and clear enough for it to be of some use.

For further details and download links please visit http://www.dhpoware.com

Re: XNA Framework XNA Demos Released


I havn't downloaded your demo yet, but a quick question..  Are the screenshots suppose to be the same

And you probably want to edit your post to change this thread to an announcement instead of a question.

Re: XNA Framework XNA Demos Released

Glenn Wilson

Remember to make sure when you make announcements to mark the post as a comment and not a Question.

Re: XNA Framework XNA Demos Released


Well yes and no. All 3 demos are of a brick textured cube. The demos differ in the shader effects applied to the model. So in the per-pixel demo it does per-pixel lighting on the cube. Then in the normal mapping demo it performs tangent space normal mapping on the cube. Finally in the parallax mapping demo it does parallax mapping on the cube. Each screenshot is different but it's hard to tell due to the size of the screenshot.

Re: XNA Framework XNA Demos Released


As someone who is struggling to get these sort of lighting fx, this is very useful. Thank you :)

Re: XNA Framework XNA Demos Released


I'm glad you found the demos helpful

The next XNA demos in the series will cover cameras: first person style, flight simulator style, and third person style. They're essentially C# XNA conversions of the C++ Direct3D version also available on my website. They should be up soonish.

Re: XNA Framework XNA Demos Released


can't wait to see 'em. I just spent the last hour incoprerating your stuff into my code, and my engine now has beautiful per pixel lighting. I'm a very happy man :) *Buys everyone a virtual pint*

Re: XNA Framework XNA Demos Released


I've succesfully integerated all three lighting modes into my engine, but your tangent code has a bug, that's way beyond my skill level to fix.

For some reason, normals are inverted. Like for instance if I used it on my level, I get some walls that are lit from the back, while others are correct.

And i just tried it on my soldier mesh, same deal, left leg, right arm = perfect, the rest = inverted. (I.e if the light is behind the head, the face lights up, if it's in the front, the back of the head lights up)

If were a simple case of all tris being inverted i could just inverse it myself, but it seems to be random, not toally random, there are *no* random tris that are wrong, just groups of them. IT's a single mesh.

Have you encountered this would you like my source code so you can investigate Your code is 99.9% perfect so I hope you do, I'm loving the lighting possiblities it brings in theory. :)

Re: XNA Framework XNA Demos Released


Try including the demo code that draws the tangent space vectors - that will tell you which tangent vectors are incorrect. It sounds like some of the textures are being applied backwards on some of the triangles on your models. In the CalcTangentSpaceVectors() method, calculate the triangle's surface normal using edge1 and edge2. Then at the end of the method check to see if the textures were being applied backwards on the triangle. Take the dot product between the calculated tangent space normal vector and the surface normal. If this is less than zero then the two normals are pointing in opposite directions. In that case reverse the tangent and bitangent vectors. Also check out this Nvidia presentation - it's quite old now but the content still applies: http://developer.nvidia.com/object/texture_space_bump_mapping.html.

Here's the updated CalcTangentSpaceVectors() method (I've removed all but the most relevant code comments to save space):

private void CalcTangentSpaceVectors(ref Vector3 pos1,
                                     ref Vector3 pos2,
                                     ref Vector3 pos3,
                                     ref Vector2 texCoord1,
                                     ref Vector2 texCoord2,
                                     ref Vector2 texCoord3,
                                     out Vector3 tangent,
                                     out Vector3 bitangent,
                                     out Vector3 normal)
    Vector3 edge1 = pos2 - pos1;
    Vector3 edge2 = pos3 - pos1;

    // Calculate the surface normal.
    Vector3 surfaceNormal = Vector3.Normalize(Vector3.Cross(edge1, edge2));

    Vector2 texEdge1 = texCoord2 - texCoord1;
    Vector2 texEdge2 = texCoord3 - texCoord1;

    float detA = (texEdge1.X * texEdge2.Y) - (texEdge1.Y * texEdge2.X);

    if ((float)Math.Abs(detA) < 1e-6f)    // almost equal to zero
        tangent = Vector3.UnitX;
        bitangent = Vector3.UnitY;
        normal = Vector3.UnitZ;
        Vector3 t = (1.0f / detA) * (texEdge2.Y * edge1 - texEdge1.Y * edge2);
        Vector3 b = (1.0f / detA) * (-texEdge2.X * edge1 + texEdge1.X * edge2);


        Vector3 n = Vector3.Cross(b, t);

        Matrix tbnMatrix;
        Matrix m = new Matrix(t.X, b.X, n.X, 0.0f,
                              t.Y, b.Y, n.Y, 0.0f,
                              t.Z, b.Z, n.Z, 0.0f,
                              0.0f, 0.0f, 0.0f, 1.0f);

        Matrix.Invert(ref m, out tbnMatrix);

        tangent = new Vector3(tbnMatrix.M11, tbnMatrix.M12, tbnMatrix.M13);
        bitangent = new Vector3(tbnMatrix.M21, tbnMatrix.M22, tbnMatrix.M23);
        normal = new Vector3(tbnMatrix.M31, tbnMatrix.M32, tbnMatrix.M33);


        // Check to see if the textures were applied backwards.
        if (Vector3.Dot(surfaceNormal, normal) < 0.0f)
            tangent = Vector3.Negate(tangent);
            normal = surfaceNormal;
            bitangent = Vector3.Cross(tangent, normal);

Re: XNA Framework XNA Demos Released


Thanks for the demos. Quick question - in the per-pixel lighting demo how would you use mulitple lights, for example a directional and point, at the same time

Re: XNA Framework XNA Demos Released


Modifying the per-pixel lighting demo to support multiple lights should be fairly straight forward.

Here are some suggestions:

  1. First create a new HLSL function that will serve as the entry point for the per-pixel shader. Say call it PS_LightingMain().
  2. Next create a global uniform array of Light structures. This array contains all the possible lights that can be enabled at one. Think of it as the same as the max lights setting as used in the fixed function pipeline in Direct3D. This array replaces the existing single Light variable.
  3. Also setup another array of the same size as the Lights array for the Material structures that will correspond to each Light in the new lights array.
  4. Setup another global uniform variable that indicates which lights are enabled. This can be say an array of offsets that identify which lights are enabled. Or it might be a single value that indicates all lights at an offset equal to or less than this indicated offset is enabled.
  5. The Light structure needs to be modified to include what type of light it is: direction light, point light, or spotlight. There are a few ways to do this. The easiest is probably to include an integer variable that serves as the type of light this Light structure is. Setup some constants to indicate the light type e.g., 1 = directional light, 2 = point light, and 3 = spotlight.
  6. In the PS_LightingMain() function we make use of multiple lights in the scene by looping through all of the enabled lights and for each enabled light we call the appropriate lighting function - PS_DirLighting(), PS_PointLighting(), or PS_SpotLighting(). Each of these lighting functions are modified to accept an offset indicating which Light and Material to use. The color value returned by each of these lighting functions is summed. Once all enabled lights are evaluated the summed color value is returned as the final color for the current pixel.