Formeln

Thursday, April 25, 2013

Procedural Meshes: The Box

Introduction


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

In this tutorial I will show how to create a box with parameters for width, height and depth.


Creating the Vertex Buffer


The center of the box is in the origin of the coordinate system (0,0,0). The width of the box corresponds with its extension in direction of the x-axis, the height expands along the the y-axis and the depth of the box expands along the z-axis.

In order to create the vertex buffer, we need eight vertices, which are created with the appropriate offsets:

// half length of an edge
float offsetWidth = width / 2.0f;
float offsetHeight = height / 2.0f;
float offsetDepth = depth / 2.0f;

vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
numVertices = 8;
vertexBufferSizeInBytes = vertexStride * numVertices;

vertices = new DataStream(vertexBufferSizeInBytes, true, true);

vertices.Write(new Vector3(+offsetWidth, +offsetHeight, +offsetDepth)); // 0
vertices.Write(new Vector3(+offsetWidth, +offsetHeight, -offsetDepth)); // 1
vertices.Write(new Vector3(-offsetWidth, +offsetHeight, -offsetDepth)); // 2
vertices.Write(new Vector3(-offsetWidth, +offsetHeight, +offsetDepth)); // 3

vertices.Write(new Vector3(-offsetWidth, -offsetHeight, +offsetDepth)); // 4
vertices.Write(new Vector3(+offsetWidth, -offsetHeight, +offsetDepth)); // 5
vertices.Write(new Vector3(+offsetWidth, -offsetHeight, -offsetDepth)); // 6
vertices.Write(new Vector3(-offsetWidth, -offsetHeight, -offsetDepth)); // 7

vertices.Position = 0;

vertexBuffer = new SlimDX.Direct3D11.Buffer(
  DeviceManager.Instance.device,
  vertices,
  vertexBufferSizeInBytes,
  ResourceUsage.Default,
  BindFlags.VertexBuffer,
  CpuAccessFlags.None,
  ResourceOptionFlags.None,
  0);

This picture shows the indices of the vertices. The indices written in the sources code above in the comments.




Creating the Index Buffer


We need 36 indices for the box, as each face of the box consists of two triangles and we have six sides:

6 sides * 2 triangles * 3 indices = 36 indices

The triangles are defined by enumerating the indices is a clockwise order:

numIndices = 36;
indexStride = Marshal.SizeOf(typeof(short)); // 2 bytes
indexBufferSizeInBytes = numIndices * indexStride;

indices = new DataStream(indexBufferSizeInBytes, true, true);

// Cube has 6 sides: top, bottom, left, right, front, back

// top
indices.WriteRange(new short[] { 0, 1, 2 });
indices.WriteRange(new short[] { 2, 3, 0 });

// right
indices.WriteRange(new short[] { 0, 5, 6 });
indices.WriteRange(new short[] { 6, 1, 0 });

// left
indices.WriteRange(new short[] { 2, 7, 4 });
indices.WriteRange(new short[] { 4, 3, 2 });

// front
indices.WriteRange(new short[] { 1, 6, 7 });
indices.WriteRange(new short[] { 7, 2, 1 });

// back
indices.WriteRange(new short[] { 3, 4, 5 });
indices.WriteRange(new short[] { 5, 0, 3 });

// bottom
indices.WriteRange(new short[] { 6, 5, 4 });
indices.WriteRange(new short[] { 4, 7, 6 });

indices.Position = 0;

indexBuffer = new SlimDX.Direct3D11.Buffer(
    DeviceManager.Instance.device,
    indices,
    indexBufferSizeInBytes,
    ResourceUsage.Default,
    BindFlags.IndexBuffer,
    CpuAccessFlags.None,
    ResourceOptionFlags.None,
    0);


Source Code


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

namespace Apparat.Renderables
{
  public class Box : Renderable
  {
    ShaderSignature inputSignature;
    EffectTechnique technique;
    EffectPass pass;

    Effect effect;

    InputLayout layout;
    SlimDX.Direct3D11.Buffer vertexBuffer;
    SlimDX.Direct3D11.Buffer indexBuffer;
    DataStream vertices;
    DataStream indices;

    int vertexStride = 0;
    int numVertices = 0;
    int indexStride = 0;
    int numIndices = 0;

    int vertexBufferSizeInBytes = 0;
    int indexBufferSizeInBytes = 0;

    EffectMatrixVariable tmat;
    EffectVectorVariable mCol;
    EffectVectorVariable wfCol;

    public Box(float width, float height, float depth)
    {
      try
      {
        using (ShaderBytecode effectByteCode = ShaderBytecode.CompileFromFile(
          "Shaders/transformEffectWireframe.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());
       }

       var elements = new[] { 
         new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
         };
       layout = new InputLayout(DeviceManager.Instance.device, inputSignature, elements);

       tmat = effect.GetVariableByName("gWVP").AsMatrix();
       wfCol = effect.GetVariableByName("colorWireframe").AsVector();
       mCol = effect.GetVariableByName("colorSolid").AsVector();
       Vector4 col = new Vector4(0, 0, 0, 1);
       mCol.Set(new Color4(1, 0, 1, 0));
       wfCol.Set(col);

       // half length of an edge
       float offsetWidth = width / 2.0f;
       float offsetHeight = height / 2.0f;
       float offsetDepth = depth / 2.0f;

       vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
       numVertices = 8;
       vertexBufferSizeInBytes = vertexStride * numVertices;

       vertices = new DataStream(vertexBufferSizeInBytes, true, true);

       vertices.Write(new Vector3(+offsetWidth, +offsetHeight, +offsetDepth)); // 0
       vertices.Write(new Vector3(+offsetWidth, +offsetHeight, -offsetDepth)); // 1
       vertices.Write(new Vector3(-offsetWidth, +offsetHeight, -offsetDepth)); // 2
       vertices.Write(new Vector3(-offsetWidth, +offsetHeight, +offsetDepth)); // 3

       vertices.Write(new Vector3(-offsetWidth, -offsetHeight, +offsetDepth)); // 4
       vertices.Write(new Vector3(+offsetWidth, -offsetHeight, +offsetDepth)); // 5
       vertices.Write(new Vector3(+offsetWidth, -offsetHeight, -offsetDepth)); // 6
       vertices.Write(new Vector3(-offsetWidth, -offsetHeight, -offsetDepth)); // 7

       vertices.Position = 0;

       vertexBuffer = new SlimDX.Direct3D11.Buffer(
         DeviceManager.Instance.device,
         vertices,
         vertexBufferSizeInBytes,
         ResourceUsage.Default,
         BindFlags.VertexBuffer,
         CpuAccessFlags.None,
         ResourceOptionFlags.None,
         0);

       numIndices = 36;
       indexStride = Marshal.SizeOf(typeof(short)); // 2 bytes
       indexBufferSizeInBytes = numIndices * indexStride;

       indices = new DataStream(indexBufferSizeInBytes, true, true);

       // Cube has 6 sides: top, bottom, left, right, front, back

       // top
       indices.WriteRange(new short[] { 0, 1, 2 });
       indices.WriteRange(new short[] { 2, 3, 0 });

       // right
       indices.WriteRange(new short[] { 0, 5, 6 });
       indices.WriteRange(new short[] { 6, 1, 0 });

       // left
       indices.WriteRange(new short[] { 2, 7, 4 });
       indices.WriteRange(new short[] { 4, 3, 2 });

       // front
       indices.WriteRange(new short[] { 1, 6, 7 });
       indices.WriteRange(new short[] { 7, 2, 1 });

       // back
       indices.WriteRange(new short[] { 3, 4, 5 });
       indices.WriteRange(new short[] { 5, 0, 3 });

       // bottom
       indices.WriteRange(new short[] { 6, 5, 4 });
       indices.WriteRange(new short[] { 4, 7, 6 });

       indices.Position = 0;

       indexBuffer = new SlimDX.Direct3D11.Buffer(
         DeviceManager.Instance.device,
         indices,
         indexBufferSizeInBytes,
         ResourceUsage.Default,
         BindFlags.IndexBuffer,
         CpuAccessFlags.None,
         ResourceOptionFlags.None,
         0);

      }

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

        DeviceManager.Instance.context.InputAssembler.InputLayout = layout;
        DeviceManager.Instance.context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
        DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, vertexStride, 0));
        DeviceManager.Instance.context.InputAssembler.SetIndexBuffer(indexBuffer, Format.R16_UInt, 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.DrawIndexed(numIndices, 0, 0);
        }
      }

      public override void dispose()
      {
        effect.Dispose();
        inputSignature.Dispose();
        vertexBuffer.Dispose();
        layout.Dispose();
      }
   }
}


Conclusion


This is a box, created with the parameters width = 2.0, height = 1.0 and depth = 1.0:


You can download the source code to this tutorial here.

No comments:

Post a Comment