Eric van Feggelen

Hello everyone,

I've been playing around with XNA for a while now, and I can't seem to get the lightning right on a mesh i'm dynamically creating. I've created a simple GameComponent that initializes a VertexBuffer and an IndexBuffer in the LoadGraphicsContent method. The Draw method uses a BasicEffect (with default lightning enabled) to draw the vertices using the TriangleList primitive. I've used the DrawIndexedPrimitives method for this.

The lightning on the mesh is totally wrong, i've uploaded a screenshot at the following location to see the effect here:

http://www.fegelein.com/projects/xna_fegelein/WeirdLightningTorus.png

Also, you can download the complete sourcecode here:

http://www.fegelein.com/projects/xna_fegelein/07 - Creating a Torus mesh.rar

The code to create the vertexbuffer and indexbuffer is as follows:

    protected void InitializeVertexBuffer(GraphicsDevice graphicsDevice)
    {
      // Localize properties (as float for easy math)
      float radius = this.SegmentRadius;
      float tradius = this.TubeRadius;
      float segments = this.Segments;
      float tubes = this.Tubes;

      // Create doubles for our xyz coordinates
      double x = 0;
      double y = 0;
      double z = 0;

      // Init vertexList and indexList
      List verticesList = new List();
      List indexList = new List();

      // Init temp lists with tubes and segments
      List<LIST> tubeList = new List<LIST>();
      List segmentList = new List();

      // Calculate slice offset for
      float tubeSlice = MathHelper.Pi / (tubes / 2);
      float segmentSlice = MathHelper.Pi / (segments / 2);

      // Loop through number of tubes
      for (int i = 0; i < tubes; i++)
      {
        // Create a new segment loopup list
        segmentList = new List();

        // Loop through number of segments
        for (int j = 0; j < segments; j++)
        {
          // Calculate X, Y, Z .. 
          x = (tradius + radius * Math.Cos(j * segmentSlice)) * Math.Cos(i * tubeSlice);
          y = (tradius + radius * Math.Cos(j * segmentSlice)) * Math.Sin(i * tubeSlice);
          z = radius * Math.Sin(j * segmentSlice);

          // Create a new vertex element
          VertexPositionColor vertex = new VertexPositionColor(new Vector3((float)x, (float)y, (float)z), Color.White);

          // Add the vertex to global vertex list
          verticesList.Add(vertex);
          segmentList.Add(vertex);
        }

        tubeList.Add(segmentList);
      }

      //for (int n = 1, i = 0; i < tubeList.Count; n = ++i % tubeList.Count)
      for (int i = 0; i < tubeList.Count; i++)
      {
        int n = (i + 1) % tubeList.Count;

        List currentSegment = tubeListIdea;
        List nextSegment = tubeListNo;

        for (int j = 0; j < currentSegment.Count; j++)
        {
          int m = (j + 1) % currentSegment.Count;

          indexList.Add((short)verticesList.IndexOf(currentSegment[j]));
          indexList.Add((short)verticesList.IndexOf(nextSegment[m]));
          indexList.Add((short)verticesList.IndexOf(currentSegment[m]));
           
          indexList.Add((short)verticesList.IndexOf(currentSegment[j]));
          indexList.Add((short)verticesList.IndexOf(nextSegment[j]));
          indexList.Add((short)verticesList.IndexOf(nextSegment[m]));
        }

      }

      // Create vertex buffer
      VertexPositionColor[] vertexData = new VertexPositionColor[this.totalVertices];
      verticesList.CopyTo(vertexData);

      this.vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionColor.SizeInBytes * this.totalVertices, ResourceUsage.None);
      this.vertexBuffer.SetData(vertexData);

      short[] indexData = new short[this.totalIndices];
      indexList.CopyTo(indexData);

      // Create the index buffer
      this.indexBuffer = new IndexBuffer(graphicsDevice, sizeof(short) * this.totalIndices, ResourceUsage.None, IndexElementSize.SixteenBits);
      this.indexBuffer.SetData(indexData);
    }
If you have any suggestions or comments, thanks very much in advance!!
 


Re: XNA Game Studio Express Weird lightning on custom mesh

azound

Lighting in XNA is dependent on the vertices containing normal information. Without normals the framework doesn't know when part of a mesh is facing a light and by how much, so it simply doesn't do any dynamic lighting.

You should change your array of VertexPositionColor elements to a VertexPositionNormalColor (or is it VertexPositionColorNormal ). Then you need to fill in the Normal information on each vertex. Don't forget to change anything that mentions VertexPositionColor to be VertexPositionNormalColor.

The normal is a unit vector facing directly outward from the vertex. If you need any help with calculating normals, don't be afraid to ask.

Hope that helps :)




Re: XNA Game Studio Express Weird lightning on custom mesh

Eric van Feggelen

Allright,

I knew about normals, but figured that when drawing the triangles clockwise Direct3d would automatically figure out which way it's pointing, just like with culling. But what you say makes sense, can you help me find the VertexPositionNormalColor struct I can only find the VertexPositionNormalTexture ... :)

Thanks again .. Eric





Re: XNA Game Studio Express Weird lightning on custom mesh

dczraptor

VertexPositionNormalColor does not exist. You can, however, create your own struct to do that, but it may require some work. Here's a short tutorial: http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series3/Vertex_format.php






Re: XNA Game Studio Express Weird lightning on custom mesh

Eric van Feggelen

Okay ...

So I've created my own vertex format, including a vertexElement that has the element usage set to VertexElementUsage.Normal.
All I need now, is calculate a normal for each vertex. Does anyone have more background on that

Thanks





Re: XNA Game Studio Express Weird lightning on custom mesh

Eric van Feggelen

Hmmm ...

I've been trying to calculate the vertex normals automatically, but without any luck. I've discovered that a vertex normal can be made of the sum of all attached face normals. So I've expanded my code with the following piece:

      // Loop through every start of each triangle
      for (int i = 0; i < indexData.Length; i += 3)
      {
        // Find all 3 vertices
        VertexPositionNormalColor v0 = vertexData[indexDataIdea];
        VertexPositionNormalColor v1 = vertexData[indexData[i + 1]];
        VertexPositionNormalColor v2 = vertexData[indexData[i + 2]];

        // Calculate the triangle normal
        Vector3 vt1 = v0.Position - v1.Position;
        Vector3 vt2 = v2.Position - v1.Position;
        Vector3 triangleNormal = Vector3.Cross(vt1, vt2);
        triangleNormal.Normalize();

        // Add the triangle normal to the vertices in the triangle
        vertexData[indexDataIdea].Normal += triangleNormal;
        vertexData[indexData[i + 1]].Normal += triangleNormal;
        vertexData[indexData[i + 2]].Normal += triangleNormal;
      }
      // Loop past all the vertices and normalize the vertex normals
      for (int i = 0; i < vertexData.Length; i++)
        vertexDataIdea.Normal.Normalize();

As you can see I've looped through all the vertices and added the facenormal to the vertex normal. At the end the vertex normals are all normalized. Which should be enough for my simple torus object. But still, it doesn't work .. I'm getting the same weird lightning results ...

... just don't understand what could be wrong, here's the total code:

    /// 
    /// Method to initialize the vertex buffer
    /// 
    /// 
    protected void InitializeVertexBuffer(GraphicsDevice graphicsDevice)
    {
      // Localize properties (as float for easy math)
      float radius = this.SegmentRadius;
      float tradius = this.TubeRadius;
      float segments = this.Segments;
      float tubes = this.Tubes;

      // Create doubles for our xyz coordinates
      double x = 0;
      double y = 0;
      double z = 0;

      // Init vertexList and indexList
      List verticesList = new List();
      List indexList = new List();

      // Init temp lists with tubes and segments
      List<LIST> tubeList = new List<LIST>();
      List segmentList = new List();

      // Calculate slice offset for
      float tubeSlice = MathHelper.Pi / (tubes / 2);
      float segmentSlice = MathHelper.Pi / (segments / 2);

      // Loop through number of tubes
      for (int i = 0; i < tubes; i++)
      {
        // Create a new segment loopup list
        segmentList = new List();

        // Loop through number of segments
        for (int j = 0; j < segments; j++)
        {
          // Calculate X, Y, Z coordinates.. 
          x = (tradius + radius * Math.Cos(j * segmentSlice)) * Math.Cos(i * tubeSlice);
          y = (tradius + radius * Math.Cos(j * segmentSlice)) * Math.Sin(i * tubeSlice);
          z = radius * Math.Sin(j * segmentSlice);

          // Create a new vertex element
          VertexPositionNormalColor vertex = new VertexPositionNormalColor(new Vector3((float)x, (float)y, (float)z), Color.White, new Vector3(0, 0, 0));

          // Add the vertex to global vertex list
          verticesList.Add(vertex);

          // Add the vertex to the current segment
          segmentList.Add(vertex);
        }

        // Add the segment to the tubelist
        tubeList.Add(segmentList);
      }

      // Loop through all of the tubes
      //for (int n = 1, i = 0; i < tubeList.Count; n = ++i % tubeList.Count)
      for (int i = 0; i < tubeList.Count; i++)
      {
        // Find next (or first) tube offset
        int n = (i + 1) % tubeList.Count;

        // Find current and next segments
        List currentSegment = tubeListIdea;
        List nextSegment = tubeListNo;

        // Loop through the segments
        for (int j = 0; j < currentSegment.Count; j++)
        {
          // Find next (or first) segment offset
          int m = (j + 1) % currentSegment.Count;

          // Draw the first triangle
          indexList.Add((short)verticesList.IndexOf(currentSegment[j]));
          indexList.Add((short)verticesList.IndexOf(currentSegment[m]));
          indexList.Add((short)verticesList.IndexOf(nextSegment[m]));
          
          // Finish the quad
          indexList.Add((short)verticesList.IndexOf(nextSegment[m]));
          indexList.Add((short)verticesList.IndexOf(nextSegment[j]));
          indexList.Add((short)verticesList.IndexOf(currentSegment[j]));
        }
      }

      // Copy the vertices to an VertexPositionNormalColor array for VertexBuffer
      VertexPositionNormalColor[] vertexData = new VertexPositionNormalColor[this.totalVertices];
      verticesList.CopyTo(vertexData);

      // Copy the indices to an array
      short[] indexData = new short[this.totalIndices];
      indexList.CopyTo(indexData);

      // Loop through every start of each triangle
      for (int i = 0; i < indexData.Length; i += 3)
      {
        // Find all 3 vertices
        VertexPositionNormalColor v0 = vertexData[indexDataIdea];
        VertexPositionNormalColor v1 = vertexData[indexData[i + 1]];
        VertexPositionNormalColor v2 = vertexData[indexData[i + 2]];

        // Calculate the triangle normal
        Vector3 vt1 = v0.Position - v1.Position;
        Vector3 vt2 = v2.Position - v1.Position;
        Vector3 triangleNormal = Vector3.Cross(vt1, vt2);
        triangleNormal.Normalize();

        // Add the triangle normal to the vertices in the triangle
        vertexData[indexDataIdea].Normal += triangleNormal;
        vertexData[indexData[i + 1]].Normal += triangleNormal;
        vertexData[indexData[i + 2]].Normal += triangleNormal;
      }

      // Loop past all the vertices and normalize the vertex normals
      for (int i = 0; i < vertexData.Length; i++)
        vertexDataIdea.Normal.Normalize();

      // Initialize the VertexBuffer, and insert the data
      this.vertexBuffer = new VertexBuffer(graphicsDevice, VertexPositionNormalColor.SizeInBytes * this.totalVertices, ResourceUsage.None);
      this.vertexBuffer.SetData(vertexData);

      // Initialize the IndexBuffer, and insert the data
      this.indexBuffer = new IndexBuffer(graphicsDevice, sizeof(short) * this.totalIndices, ResourceUsage.None, IndexElementSize.SixteenBits);
      this.indexBuffer.SetData(indexData);
    }




Re: XNA Game Studio Express Weird lightning on custom mesh

Shawn Hargreaves - MSFT

Could you post the code for your VertexPositionNormalColor type

Also, have you tried running this with the debug DirectX runtime Does that give you any useful warning output (it can often help to pinpoint what is going wrong with this kind of problem)





Re: XNA Game Studio Express Weird lightning on custom mesh

Eric van Feggelen

Hello Shawn,

Thanks for the reply, here's the custom vertex format I've created:

  /// 
  /// Custom vertex format with normal vector for lightning
  /// 
  struct VertexPositionNormalColor
  {
    /// 
    /// Public members
    /// 
    public Vector3 Position;
    public Color Color;
    public Vector3 Normal;

    /// 
    /// VertexElements for use with vertex declaration
    /// 
    public static VertexElement[] VertexElements = 
    {
      new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
      new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0 ),
      new VertexElement( 0, sizeof(float) * 4, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 )
    };

    /// 
    /// Size in bytes
    /// 
    public static int SizeInBytes = sizeof(float) * 7; 
    
    /// 
    /// Default constructor
    /// 
    public VertexPositionNormalColor(Vector3 position, Color color, Vector3 normal)
    {
      this.Position = position;
      this.Normal = normal;
      this.Color = color;
    }
  }

Also, the full source code to what I've been creating can be found here:

http://www.fegelein.com/projects/xna_fegelein/07 - Creating a Torus mesh v1.1.rar

If you have any comments or suggestions please let me know... Hope you have a solution ... didn't think this would cause this long...
Thanks,

Eric





Re: XNA Game Studio Express Weird lightning on custom mesh

Shawn Hargreaves - MSFT

Turns out there are three problems with your code:
  • You were passing totalIndices as one of the parameters to DrawIndexedPrimitives, when this should be totalVertices
  • You were only setting your vertex declaration in LoadGraphicsContent, rather than each time you draw the torus. That means it will get overwritten by the vertex declaration used for your text, so you end up drawing with the wrong declaration
  • With the above two problems fixed, lighting is correct, but your light is behind the torus so you'll need to move it to actually see any shading
In the interest of teaching a man to fish, here's how I found the above problems:

First off, I ran your app with the debug DirectX runtime. This immediately crashes on your first call to DrawIndexedPrimitives inside the torus code, with the debug spew:

3560: Direct3D9: (ERROR) :Stream 0 size is too small
3560:
3560: Direct3D9: (ERROR) :DrawIndexedPrimitive failed.

So, I immediately know to concentrate on the various size parameters relating to that draw call. When I spot that you are passing in the wrong number of vertices, and fix that problem, the debug DirectX no longer prints any warnings, but it still doesn't render correctly.

Next, I grabbed a capture of one frame of your app using PIX for Windows. I found the draw call that was going wrong, opened up the device properties relating to it, and looked at all the various states that were set on the device at the time. This made it easy to spot that the wrong vertex declaration was active at the time.





Re: XNA Game Studio Express Weird lightning on custom mesh

Eric van Feggelen

Hello Shawn,

Thanks for the help, your solutions were exactly right! Also, thanks a lot for telling me how you found the problems, your help is very appreciated. I found some other bugs in my code that were still messing up the lightning, such as the normal calculations. I'm going to do some more reading on various topics and concepts before continuing.

If you have time, and like to criticise my code some more ... please tell me anything that comes off you mind ...
Have a nice day ... and again thanks!

Eric