Formeln

Saturday, April 13, 2013

GridMesh: Creating the Vertex Buffer and Rendering as PointList

Grid Mesh

This tutorial is one part of a series of tutorials about generating procedural meshes. See here for an outline.

This tutorial deals with the creation of a vertex buffer for a mesh and rendering this mesh with the PointList primitive. We will start with a mesh for a rectangular grid of points. The grid extends along the x-axis and the y-axis and has three parameters:


  • width: number of cells in direction of the x-axis
  • height: number of cells in direction of the y-axis
  • cell size: length of each edge of a cell

A grid with the width of 4 and a height of 3 is shown in the following picture:
Grid with 4 Cells in x and 3 Cells in y.
In our coordinate system the x-axis goes to the right and the positive y-axis extends up.

Vertices

Vertices are the basis of meshes. Vertices can hold information about position, color, normals, texture coordinates and any other information you need for your shaders.
To keep things simple, we will start with vertices, that only have have a position. In order to create the mesh, we need a grid of vertices, as shown in the following picture:
Because one cell consists of 4 vertices, we need one more vertex for the width and height of the mesh, than we have cells. The 4 (width) x 3 (height) grid above needs therefore 5 vertices for each row and 4 vertices for each column of the mesh.

Iterative Creation of Vertices

How can we create such a mesh? One option is do define each vertex by hand. This is a valid approach, if you have simple objects like a cell made of two triangles that serves as a surface for a texture. For larger meshes and a flexible ways to create these (for example a grid with parameters for width and height) we need a different approach, which is called procedural mesh creation. A procedural mesh is created with a function that takes some parameters and computes the according mesh.

We start by filling the vertex buffer of the mesh. As we need a simple grid, we use a double nested for-loop to create the vertices iteratively.

We use the outer loop to iterate on the y value and the inner loop for the x value:

int width = 5;
int height = 4;

for (int y = 0; y < height; y++)
{
  for (int x = 0; x < width; x++)
  {
    Console.Write(y.ToString() + "," + x.ToString() + "\t");
  }
  Console.WriteLine();
}

This is the output on the console:


This gives us so far the indices of the positions. The first index is the row index (y) and the second index is the column index (x):
Vertices with Indices according to their position in row and column.

Because the VertexBuffer is not a matrix but an array, we need to linearize these indices, meaning, we have to match the indices in this matrix to an according array. We can do this by multiplying the y index of the outer loop with the width of the inner loop and adding the current x index to it:

int width = 5;
int height = 4;

for (int y = 0; y < height; y++)
{
  for (int x = 0; x < width; x++)
  {
    int index = x + y * width;
    Console.Write(index.ToString() + "\t");
  }
  Console.WriteLine();
}






We do not need to access the indices in this tutorial, but this will come in handy, when we have to triangulate the mesh.

Source Code

In this code of the grid mesh, I use the double nested for loop to create the vertices. Because it is convenient to place the center of the mesh in the origin of the coordinate system, I start in the upper left to write the vertices in the buffer. Also note, that I write the vertices from top to bottom, starting at the top row and going to the lowest row, by decreasing the y-position of the vertex:

float posX, posY;
float startX = -witdh * stepSize  / 2.0f;
float startY = height * stepSize  / 2.0f;
            

for (int y = 0; y < numVerticesHeight; y++)
{
  for (int x = 0; x < numVerticesWidth; x++)
  {
    posX = startX + x * stepSize;
    posY = startY - y * stepSize;

    vertices.Write( new Vector3(posX, posY, 0  ));
  }
}

The decision to iterate top down and from left to right is completely arbitrary but I think it is easier this way to imagine the access to the indices for further triangulation of the mesh.

This is the complete code of the grid:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SlimDX.D3DCompiler;
using SlimDX;
using SlimDX.Direct3D11;
using SlimDX.DXGI;

namespace Apparat.Renderables
{
    public class GridMesh : Renderable
    {
        SlimDX.Direct3D11.Buffer vertexBuffer;
        DataStream vertices;

        InputLayout layout;

        int numVertices = 0;
        int stride;

        ShaderSignature inputSignature;
        EffectTechnique technique;
        EffectPass pass;

        Effect effect;
        EffectMatrixVariable tmat;

        float stepSize;
        
        public GridMesh(int witdh, int height, float stepSize )
        {
            int numVerticesWidth = witdh + 1;
            int numVerticesHeight = height + 1;

            this.stepSize = stepSize;

            numVertices = numVerticesWidth * numVerticesHeight;

            try
            {
                using (ShaderBytecode effectByteCode = ShaderBytecode.CompileFromFile(
                    "transformEffect.fx",
                    "Render",
                    "fx_5_0",
                    ShaderFlags.EnableStrictness,
                    EffectFlags.None))
                {
                    effect = new Effect(DeviceManager.Instance.device, effectByteCode);
                    technique = effect.GetTechniqueByIndex(0);
                    pass = technique.GetPassByIndex(0);
                    inputSignature = pass.Description.Signature;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            tmat = effect.GetVariableByName("gWVP").AsMatrix();

            stride = 12;
            int sizeInBytes = stride * numVertices;
            vertices = new DataStream(sizeInBytes, true, true);

            float posX, posY;
            float startX = -witdh * stepSize  / 2.0f;
            float startY = height * stepSize  / 2.0f;

            for (int y = 0; y < numVerticesHeight; y++)
            {
                for (int x = 0; x < numVerticesWidth; x++)
                {
                    posX = startX + x * stepSize;
                    posY = startY - y * stepSize;

                    vertices.Write( new Vector3(posX, posY, 0  ));
                }
            }

            vertices.Position = 0;

            // create the vertex layout and buffer
            var elements = new[] { new InputElement("POSITION", 0, Format.R32G32B32_Float, 0) };
            layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);
            vertexBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device, vertices, sizeInBytes, ResourceUsage.Default, BindFlags.VertexBuffer, CpuAccessFlags.None, ResourceOptionFlags.None, 0);
        }

        public override void render()
        {
            Matrix ViewPerspective = CameraManager.Instance.ViewPerspective;
            tmat.SetMatrix(ViewPerspective);

            // configure the Input Assembler portion of the pipeline with the vertex data
            DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
            DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;
            DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, stride, 0));
            
            technique = effect.GetTechniqueByName("Render");

            EffectTechniqueDescription techDesc;
            techDesc = technique.Description;

            for (int p = 0; p < techDesc.PassCount; ++p)
            {
                technique.GetPassByIndex(p).Apply(DeviceManager.Instance.context);
                DeviceManager.Instance.context.Draw(numVertices, 0);
            }
        }

        public override void dispose()
        {
        }
    }
}


Rendering

The only thing new at the render Method is, that I use the PointList primitive to render the points of the grid:

DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.PointList;
DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, 12, 0));

Conclusion

In order to make the points of the grid more visible, I modified the pixel shader to paint green pixels and set the background to black. If you enlarge the picture, you might be able to see the green dots ;)



You can download the source code to this tutorial here.

No comments:

Post a Comment