Formeln

Sunday, April 28, 2013

Resizing the RenderControl and Buffers

Problem

Up to now we did not the address resizing the buffer of the render target and depth/stencil buffer when resizing the control we are rendering on. This means the buffers stay the same when resizing the control and after a resize the rendered image looks pixelated and has the wrong aspect ratio which leads to a distorted image:





When resizing, we have to perform several steps:

  1. Dispose resources bound to the render target
  2. Dipose the render target itself
  3. Dispose the depth buffer
  4. Resize the buffers of the swap chain
  5. Create a new render target buffer from the swap chain
  6. Create a new render target
  7. Create a new depth/stencil buffer
  8. Create a new viewport and assign it to the rasterizer
  9. Set the render target buffer and the depth/stencil buffer in the output merger
  10. Take care, that the new aspect ratio is used, when creating the perspective matrix of the camera
As you can see, these are all critical resources when rendering. So we also have to take care that resizing is performed, while none of these resources are used during the rendering process. For example: disposing the render target while writing to it in the render thread is not a good idea and leads to an exception and might crash your application.


In order to prevent accessing these resources while rendering, I check if a resize is needed in every render cycle before the actual rendering happens.

Concept

There are mainly three objects involved for a resize operation:

  1. The RenderControl: the control I am rendering to
  2. DeviceManager: responsible for managing the device and its buffers
  3. RenderManager: responsible for rendering the scene and where the render thread is



If a resize of the RenderControl occurs, I set the resize variable in the RenderManager true. This resize variable is checked in every frame. If it is true, I call the Resize method of the DeviceManager and the buffers are resized there. The DeviceManager holds a reference to the RenderControl, so we know about the new width and height of the RenderControl.

The resize variable in the RenderManager is set back to false and we can continue rendering with  new buffers.

RenderControl

All we have to do in the RenderControl is to handle the resize event and to set the resize variable in the RenderManager from here to true:

private void RenderControl_Resize(object sender, EventArgs e)
{
  RenderManager.Instance.resize = true;
}

RenderManager

If the resize event in the RenderControl is triggered and the resize variable is set to true, we call the Resize method of the DeviceManager from here. This prevents disposing and creating critical resources while rendering, which would lead to an exception. The actual rendering is performed in the line Scene.Instance.render(), where I iterate through all objects of the scene and render them.

public bool resize = false;

public void RenderScene()
{
  while (true)
  {
    if (resize)
    {
      DeviceManager.Instance.Resize();
      resize = false;
    }

    fc.Count();

    DeviceManager dm = DeviceManager.Instance;
    dm.context.ClearDepthStencilView(dm.depthStencil,
      DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil,
      1.0f,
      0);

    dm.context.ClearRenderTargetView(dm.renderTarget,
      new Color4(0.75f, 0.75f, 0.75f));

    Scene.Instance.render();

    dm.swapChain.Present(syncInterval, PresentFlags.None);
  }
}

DeviceManager

The Resize method of the DeviceManager executes the disposing of the buffers and resources and recreates them.

Texture2D resource;

internal void Resize()
{
  try
  {
    if (device == null)
      return;

    float aspectRatio = (float)form.Width / (float)form.Height;
    CameraManager.Instance.currentCamera.setPerspective((float)Math.PI / 4, aspectRatio, 0.1f, 1000.0f);

    // Dispose before resizing.
    if (renderTarget != null)
      renderTarget.Dispose();

    if (resource != null)
      resource.Dispose();

    if (depthStencil != null)
      depthStencil.Dispose();

    swapChain.ResizeBuffers(1,
      form.ClientSize.Width,
      form.ClientSize.Height,
      Format.R8G8B8A8_UNorm,
      SwapChainFlags.AllowModeSwitch);

    resource = Texture2D.FromSwapChain(swapChain, 0);
    renderTarget = new RenderTargetView(device, resource);

    CreateDepthStencilBuffer(form);

    viewport = new Viewport(0.0f, 0.0f, form.ClientSize.Width, form.ClientSize.Height);
    context.Rasterizer.SetViewports(viewport);
    context.OutputMerger.SetTargets(depthStencil, renderTarget);
  }
  catch (Exception ex)
  {
    Console.WriteLine(ex.ToString());
  }
}

I call the method CreateDepthStencilBuffer in the code above and paste it here, for the sake of completeness. Here the depth/stencil buffer is created again and the DepthStencilState is set again: 

public void CreateDepthStencilBuffer(System.Windows.Forms.Control form)
{
  Texture2D DSTexture = new Texture2D(
      device,
      new Texture2DDescription()
      {
        ArraySize = 1,
        MipLevels = 1,
        Format = Format.D32_Float,
        Width = form.ClientSize.Width,
        Height = form.ClientSize.Height,
        BindFlags = BindFlags.DepthStencil,
        CpuAccessFlags = CpuAccessFlags.None,
        SampleDescription = new SampleDescription(1, 0),
        Usage = ResourceUsage.Default
      }
  );

  depthStencil = new DepthStencilView(
     device,
     DSTexture,
     new DepthStencilViewDescription()
     {
       ArraySize = 0,
       FirstArraySlice = 0,
       MipSlice = 0,
       Format = Format.D32_Float,
       Dimension = DepthStencilViewDimension.Texture2D
     }
 );

  context.OutputMerger.DepthStencilState = DepthStencilState.FromDescription(
      device,
      new DepthStencilStateDescription()
      {
        DepthComparison = Comparison.Always,
        DepthWriteMask = DepthWriteMask.All,
        IsDepthEnabled = true,
        IsStencilEnabled = false
      }
  );

  context.OutputMerger.SetTargets(depthStencil, renderTarget);

  DepthStencilStateDescription dssd = new DepthStencilStateDescription
  {
    IsDepthEnabled = true,
    IsStencilEnabled = false,
    DepthWriteMask = DepthWriteMask.All,
    DepthComparison = Comparison.Less,
  };

  DepthStencilState depthStencilStateNormal;
  depthStencilStateNormal = DepthStencilState.FromDescription(DeviceManager.Instance.device, dssd);
  DeviceManager.Instance.context.OutputMerger.DepthStencilState = depthStencilStateNormal;
}


Result


The following two pictures show two windows with resized buffers, corresponding to the controls width and height.





You can download the source code for this tutorial here.

Saturday, April 27, 2013

Procedural Meshes: The Superellipsoid

Introduction


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

The superellipsoid is basically a sphere with additional exponents n1 and n2  for manipulating the trigonometric functions. You can create cubes with round edges, a cube, a sphere, a cylinder and many other geometric objects with it, when using the according values for n1 and n2.



The Formula

I took the formula for the superellipsoid from:
http://paulbourke.net/geometry/superellipse/

x = radius * Cos^n1(theta) * Cos^n2(phi)
y = radius * Cos^n1(theta) * Sin^n2(phi)
z = radius * Sin^n1(theta)

Basically, this is the same formula as for the sphere, with two additional exponents n1 and n2. Both, n1 and n2 have to be bigger than 0 to produce valid meshes. Although values over 10 don't add any significant change to the appearence of the mesh. Play around with the parameters and see for yourself ;)


Creating the Vertex Buffer

Theta runs in the outer loop from PI/2 to -PI/2 and phi runs from zero to 2*PI.
I think the only obscure thing in the code is use of the Math.Pow function.
At first, I check if cosTheta, sinTheta, cosPhi and sinPhi are zero. If they are zero, 0 is asigned to the according powX variables. Then I have to check, if for example cosTheta is a negative value.
Small negative values can produce a NaN (Not A Number) value from the Math.Pow function.

Instead of 
  Math.Pow( - littleNumber, anyOtherNumber ), 
I am calculating:
- Math.Pow(   littleNumber, anyOtherNumber )




int numHorizontalVertices = numVerticesPerLayer + 1;
int numVerticalVertices = numVerticesPerLayer + 1;

numVertices = numHorizontalVertices * numVerticalVertices;
vertexSizeInBytes = 12;
int vertexBufferSizeInBytes = vertexSizeInBytes * numVertices;

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

float theta = 0.0f;
float phi = 0.0f;

float verticalStep = ((float)Math.PI) / (float)numVerticesPerLayer;
float horizontalStep = ((float)Math.PI * 2) / (float)numVerticesPerLayer;

for (int verticalIt = 0; verticalIt < numVerticalVertices; verticalIt++)
{

  theta = ((float)Math.PI / 2) - verticalStep * verticalIt;

  for (int horizontalIt = 0; horizontalIt < numHorizontalVertices; horizontalIt++)
  {

    phi = horizontalStep * horizontalIt;

    double cosTheta = Math.Cos(theta);
    double sinTheta = Math.Sin(theta);
    double cosPhi = Math.Cos(phi);
    double sinPhi = Math.Sin(phi);

    double powCosTheta = cosTheta == 0.0f ? 0 : Math.Sign(cosTheta) == -1 ? -Math.Pow(-cosTheta, n1) : Math.Pow(cosTheta, n1);
    double powSinTheta = sinTheta == 0.0f ? 0 : Math.Sign(sinTheta) == -1 ? -Math.Pow(-sinTheta, n1) : Math.Pow(sinTheta, n1);
    double powCosPhi  = cosPhi == 0.0f ? 0 : Math.Sign(cosPhi) == -1 ? -Math.Pow(-cosPhi, n2) : Math.Pow(cosPhi, n2);
    double powSinPhi = sinPhi == 0.0f ? 0 : Math.Sign(sinPhi) == -1 ? -Math.Pow(-sinPhi, n2) : Math.Pow(sinPhi, n2);
 

    double x = radius * powCosTheta * powCosPhi;
    double y = radius * powCosTheta * powSinPhi;
    double z = radius * powSinTheta;

    Vector3 v = new Vector3((float)x, (float)z, (float)y);
   
    vertices.Write(v);
  }
}

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,
  vertexBufferSizeInBytes,
  ResourceUsage.Default,
  BindFlags.VertexBuffer,
  CpuAccessFlags.None,
  ResourceOptionFlags.None, 0);



Creating the Index Buffer

Nothing new here, please refer to the grid mesh tutorial for details.

numIndices = 6 * numVertices;
indices = new DataStream(2 * numIndices, true, true);

try
{
  for (int verticalIt = 0; verticalIt < numVerticesPerLayer; verticalIt++)
  {

    for (int horizontalIt = 0; horizontalIt < numVerticesPerLayer; horizontalIt++)
    {
      short lu = (short)(horizontalIt + verticalIt * (numHorizontalVertices));
      short ru = (short)((horizontalIt + 1) + verticalIt * (numHorizontalVertices));

      short ld = (short)(horizontalIt + (verticalIt + 1) * (numHorizontalVertices));
      short rd = (short)((horizontalIt + 1) + (verticalIt + 1) * (numHorizontalVertices));

      indices.Write(lu);
      indices.Write(rd);
      indices.Write(ld);
      

      indices.Write(rd);
      indices.Write(lu);
      indices.Write(ru);
    }
  }
}
catch (Exception ex)
{
}

indices.Position = 0;

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




Source Code




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

namespace Apparat.Renderables
{
  public class SuperEllipsoid : Renderable
  {
    SlimDX.Direct3D11.Buffer vertexBuffer;
    SlimDX.Direct3D11.Buffer indexBuffer;
    DataStream vertices;
    DataStream indices;
    int vertexSizeInBytes;

    InputLayout layout;

    int numVertices = 0;
    int numIndices = 0;

    ShaderSignature inputSignature;
    EffectTechnique technique;
    EffectPass pass;

    Effect effect;
    EffectMatrixVariable tmat;
    EffectVectorVariable mCol;
    EffectVectorVariable wfCol;

    double n1 = 0;
    double n2 = 0;

    float radius = 0.0f;

    public SuperEllipsoid(int numVerticesPerLayer, float radius, float n1, float n2)
    {
      try
      {
        this.n1 = n1;
        this.n2 = n2;
        this.radius = radius;
        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());
      }

      tmat = effect.GetVariableByName("gWVP").AsMatrix();
 
      mCol = effect.GetVariableByName("colorSolid").AsVector();
      wfCol = effect.GetVariableByName("colorWireframe").AsVector();

      mCol.Set(new Color4(1, 0, 1, 0));
      wfCol.Set(new Color4(1, 0, 0, 0));

      int numHorizontalVertices = numVerticesPerLayer + 1;
      int numVerticalVertices = numVerticesPerLayer + 1;

      numVertices = numHorizontalVertices * numVerticalVertices;
      vertexSizeInBytes = 12;
      int vertexBufferSizeInBytes = vertexSizeInBytes * numVertices;

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

      float theta = 0.0f;
      float phi = 0.0f;

      float verticalStep = ((float)Math.PI) / (float)numVerticesPerLayer;
      float horizontalStep = ((float)Math.PI * 2) / (float)numVerticesPerLayer;

      for (int verticalIt = 0; verticalIt < numVerticalVertices; verticalIt++)
      {

        theta = -((float)Math.PI / 2) + verticalStep * verticalIt;

        for (int horizontalIt = 0; horizontalIt < numHorizontalVertices; horizontalIt++)
        {

          phi = horizontalStep * horizontalIt;

          double cosTheta = Math.Cos(theta);
          double sinTheta = Math.Sin(theta);
          double cosPhi = Math.Cos(phi);
          double sinPhi = Math.Sin(phi);

          double powCosTheta = cosTheta == 0.0f ? 0 : Math.Sign(cosTheta) == -1 ? -Math.Pow(-cosTheta, n1) : Math.Pow(cosTheta, n1);
          double powSinTheta = sinTheta == 0.0f ? 0 : Math.Sign(sinTheta) == -1 ? -Math.Pow(-sinTheta, n1) : Math.Pow(sinTheta, n1);
          double powCosPhi  = cosPhi == 0.0f ? 0 : Math.Sign(cosPhi) == -1 ? -Math.Pow(-cosPhi, n2) : Math.Pow(cosPhi, n2);
          double powSinPhi = sinPhi == 0.0f ? 0 : Math.Sign(sinPhi) == -1 ? -Math.Pow(-sinPhi, n2) : Math.Pow(sinPhi, n2);
       

          double x = radius * powCosTheta * powCosPhi;
          double y = radius * powCosTheta * powSinPhi;
          double z = radius * powSinTheta;

          Vector3 v = new Vector3((float)x, (float)z, (float)y);
         
          vertices.Write(v);
        }
      }

      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,
        vertexBufferSizeInBytes,
        ResourceUsage.Default,
        BindFlags.VertexBuffer,
        CpuAccessFlags.None,
        ResourceOptionFlags.None, 0);

      // creating the index buffer
      numIndices = 6 * numVertices;
      indices = new DataStream(2 * numIndices, true, true);
   
      try
      {
        for (int verticalIt = 0; verticalIt < numVerticesPerLayer; verticalIt++)
        {

          for (int horizontalIt = 0; horizontalIt < numVerticesPerLayer; horizontalIt++)
          {
            short lu = (short)(horizontalIt + verticalIt * (numHorizontalVertices));
            short ru = (short)((horizontalIt + 1) + verticalIt * (numHorizontalVertices));

            short ld = (short)(horizontalIt + (verticalIt + 1) * (numHorizontalVertices));
            short rd = (short)((horizontalIt + 1) + (verticalIt + 1) * (numHorizontalVertices));

            indices.Write(lu);
            indices.Write(rd);
            indices.Write(ld);
     
            indices.Write(rd);
            indices.Write(lu);
            indices.Write(ru);
          }
        }
      }
      catch (Exception ex)
      {
      }

      indices.Position = 0;

      indexBuffer = new SlimDX.Direct3D11.Buffer(
          DeviceManager.Instance.device,
          indices,
          2 * numIndices,
          ResourceUsage.Default,
          BindFlags.IndexBuffer,
          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.TriangleList;
      DeviceManager.Instance.context.InputAssembler.SetVertexBuffers(0, new VertexBufferBinding(vertexBuffer, vertexSizeInBytes, 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()
    {
      indexBuffer.Dispose();
      vertexBuffer.Dispose();
      effect.Dispose();
    }
  }
}



Result


All meshes shown below were created with radius = 1.0 and 32 * 32 vertices mesh.
Only the values for n1 and n2 differ.


n1 = 0 and n2 = 0:


n1 = 0 and n2 = 1:


n1 = 1 and n2 = 0:


n1 = 1 and n2 = 1:


n1 = 0.4 and n2 = 0.4:


n1 = 2 and n2 = 2:


n1 = 4 and n2 = 4:



I added a second class SuperEllipsoidCol which has the same colors as the color cube:

With 32 x 32 vertices, radius 0.5, n1 = 0.2 and n2 = 0.2:




You can download the source code for this tutorial here. You can play around with the parameters in the RenderControl.cs class.

Have fun!

Procedural Meshes: The Torus

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 you how to create a torus. The torus has two radii: a radius from the center of the torus to the middle of the ring section and the ring radius:





Further parameters are number of sides and number of rings. These parameters are needed for the creation of the vertex and index buffers in order to determine how many vertices and indices are needed. The number of rings tells, how many sections the torus has. The number of sides is the number of sections for each ring.

The Formula

x = Cos(theta) * (radius + ringRadius * Cos(phi))
y = Sin(theta) * (radius + ringRadius * Cos(phi))
z = ringRadius * Sin(phi)


Both, theta and phi run from 0 to 2 * PI.

Creating the Vertex Buffer

I create the vertex buffer with a double nested for loop. The outer loop iterates over the number of sections and the variable theta is used to increment the angle of the current circle of vertices that is created. The inner for loop iterates over the number of vertices the torus will have per section. Here, the variable phi is incremented in the inner loop, to calculate the position of the current vertex.

int numVerticesPerRow = sides + 1;
int numVerticesPerColumn = rings + 1;

numVertices = numVerticesPerRow * numVerticesPerColumn;

vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
int SizeOfVertexBufferInBytes = numVertices * vertexStride;

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

float theta = 0.0f;
float phi = 0.0f;

float verticalAngularStride = (float)(Math.PI * 2.0f) / (float)rings;
float horizontalAngularStride = ((float)Math.PI * 2.0f) / (float)sides;

for (int verticalIt = 0; verticalIt < numVerticesPerColumn; verticalIt++)
{
    theta = verticalAngularStride * verticalIt;

    for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
    {
        phi = horizontalAngularStride * horizontalIt;

        // position
        float x = (float)Math.Cos(theta) * (radius + ringRadius * (float)Math.Cos(phi));
        float y = (float)Math.Sin(theta) * (radius + ringRadius * (float)Math.Cos(phi));
        float z = ringRadius * (float)Math.Sin(phi);

        Vector3 position = new Vector3(x, z, y);
        vertices.Write(position);
    }
}

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,
  SizeOfVertexBufferInBytes,
  ResourceUsage.Default,
  BindFlags.VertexBuffer,
  CpuAccessFlags.None,
  ResourceOptionFlags.None,
  0);


Creating the Index Buffer

If you followed the tutorial on procedural meshes so far, the method of creating the index buffer will not as a surprise. I iterate again in a double nested for loop: the outer loop is for iterating the ring sections and the inner loop for iterating the rings themselves. The iterators are one less than the iterators in the double nested for loop above, because at each position of the innerloop, I create a patch of 2 triangles, looking one vertex index ahead.


numIndices = sides * rings * 6;
indices = new DataStream(2 * numIndices, true, true);

for (int verticalIt = 0; verticalIt < rings; verticalIt++)
{
    for (int horizontalIt = 0; horizontalIt < sides; horizontalIt++)
    {
        short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
        short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));

        short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
        short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));

        indices.Write(lt);
        indices.Write(rt);
        indices.Write(lb);

        indices.Write(rt);
        indices.Write(rb);
        indices.Write(lb);
    }
}

indices.Position = 0;

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


Source Code

Complete sources code of the class Torus:

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

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

    InputLayout layout;

    int numVertices = 0;
    int numIndices = 0;

    int vertexStride = 0;

    ShaderSignature inputSignature;
    EffectTechnique technique;
    EffectPass pass;

    Effect effect;
    EffectMatrixVariable tmat;
    EffectVectorVariable mCol;
    EffectVectorVariable wfCol;

    public Torus(float radius, float ringRadius, int sides, int rings)
    {
      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());
      }

      tmat = effect.GetVariableByName("gWVP").AsMatrix();
      mCol = effect.GetVariableByName("colorSolid").AsVector();
      wfCol = effect.GetVariableByName("colorWireframe").AsVector();

      mCol.Set(new Color4(1, 0, 1, 0));
      wfCol.Set(new Color4(1, 0, 0, 0));

      int numVerticesPerRow = sides + 1;
      int numVerticesPerColumn = rings + 1;

      numVertices = numVerticesPerRow * numVerticesPerColumn;

      vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
      int SizeOfVertexBufferInBytes = numVertices * vertexStride;

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

      float theta = 0.0f;
      float phi = 0.0f;

      float verticalAngularStride = (float)(Math.PI * 2.0f) / (float)rings;
      float horizontalAngularStride = ((float)Math.PI * 2.0f) / (float)sides;

      for (int verticalIt = 0; verticalIt < numVerticesPerColumn; verticalIt++)
      {
        theta = verticalAngularStride * verticalIt;

        for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
        {
          phi = horizontalAngularStride * horizontalIt;

          // position
          float x = (float)Math.Cos(theta) * (radius + ringRadius * (float)Math.Cos(phi));
          float y = (float)Math.Sin(theta) * (radius + ringRadius * (float)Math.Cos(phi));
          float z = ringRadius * (float)Math.Sin(phi);

          Vector3 position = new Vector3(x, z, y);
          vertices.Write(position);
        }
      }

      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,
        SizeOfVertexBufferInBytes,
        ResourceUsage.Default,
        BindFlags.VertexBuffer,
        CpuAccessFlags.None,
        ResourceOptionFlags.None,
        0);

      numIndices = sides * rings * 6;
      indices = new DataStream(2 * numIndices, true, true);

      for (int verticalIt = 0; verticalIt < rings; verticalIt++)
      {
        for (int horizontalIt = 0; horizontalIt < sides; horizontalIt++)
        {
          short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
          short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));

          short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
          short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));

          indices.Write(lt);
          indices.Write(rt);
          indices.Write(lb);

          indices.Write(rt);
          indices.Write(rb);
          indices.Write(lb);
        }
      }

      indices.Position = 0;

      indexBuffer = new SlimDX.Direct3D11.Buffer(
          DeviceManager.Instance.device,
          indices,
          2 * numIndices,
          ResourceUsage.Default,
          BindFlags.IndexBuffer,
          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.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()
    {

    }
  }
}


Result

This torus was created with the parameters radius = 1, ring radius = 0.25, number of sides = 36 and number of rings = 36.






This torus was created with the parameters radius = 1, ring radius = 0.5, number of sides = 8 and number of rings = 16.






You can download the code to this tutorial here.

Friday, April 26, 2013

Procdural Meshes: The Cylinder

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 you how to create procedurally a cylinder mesh. The cylinders bottom is in the x-z plane and the top at a given height. You can also parametrize the radius of the cylinders top and bottom.


The idea behind generating the cylinder is quite simple: create two circles of vertices and afterwards create the indices for the triangles between the two circles. This leaves us with an open top and bottom of the cylinder. So we need to append two vertices to the vertex buffer: one vertex at the bottom of the cylinder and one vertex at the top of the cylinder. Finally, we can create the indices for the top and bottom  of the cylinder.

The Formula

The formula for creating the circle is as follows:

x = radius * Cos(theta)
y = radius * Sin(theta)
z = height

Theta runs from 0 to 2 * PI.

Creating the Vertex Buffer

At first I create the vertices for the upper circle and then the vertices at the bottom of the cylinder are created. After that, I append the two vertices at the top and the bottom to the vertex buffer, which are needed to close the mesh at the top and bottom.

int numVerticesPerRow = slices + 1;

numVertices = numVerticesPerRow * 2 + 2;

vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
int SizeOfVertexBufferInBytes = numVertices * vertexStride;

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

float theta = 0.0f;
float horizontalAngularStride = ((float)Math.PI * 2) / (float)slices;

for (int verticalIt = 0; verticalIt < 2; verticalIt++)
{
  for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
  {
    float x;
    float y;
    float z;

    theta = (horizontalAngularStride * horizontalIt);

    if (verticalIt == 0)
    {
      // upper circle
      x = radiusTop * (float)Math.Cos(theta);
      y = radiusTop * (float)Math.Sin(theta);
      z = height;
    }
    else
    {
      // lower circle
      x = radiusBottom * (float)Math.Cos(theta);
      y = radiusBottom * (float)Math.Sin(theta);
      z = 0;
    }

    Vector3 position = new Vector3(x, z, y);
    vertices.Write(position);
  }
}

vertices.Write(new Vector3(0, height, 0));
vertices.Write(new Vector3(0, 0, 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,
    SizeOfVertexBufferInBytes,
    ResourceUsage.Default,
    BindFlags.VertexBuffer,
    CpuAccessFlags.None,
    ResourceOptionFlags.None,
    0);


Creating the Index Buffer

At first the two circles are filled with triangles, by patching the mesh with triangles between the upper circle of vertices and the lower circle of vertices. After that, the triangles at the top and the bottom are created in the index buffer with the help of the two vertices at the middle of the top and the bottom of the cylinder.


numIndices = slices * 2 * 6;
indices = new DataStream(2 * numIndices, true, true);

for (int verticalIt = 0; verticalIt < 1; verticalIt++)
{
  for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
  {
    short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
    short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));

    short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
    short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));

    indices.Write(lt);
    indices.Write(rt);
    indices.Write(lb);

    indices.Write(rt);
    indices.Write(rb);
    indices.Write(lb);
  }
}

for (int verticalIt = 0; verticalIt < 1; verticalIt++)
{
  for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
  {
    short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
    short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));

    short patchIndexTop = (short)(numVerticesPerRow * 2);

    indices.Write(lt);
    indices.Write(patchIndexTop);
    indices.Write(rt);
  }
}

for (int verticalIt = 0; verticalIt < 1; verticalIt++)
{
  for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
  {
    short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
    short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));


    short patchIndexBottom = (short)(numVerticesPerRow * 2 + 1);
    indices.Write(lb);
    indices.Write(rb);
    indices.Write(patchIndexBottom);
  }
}

indices.Position = 0;

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


Source Code



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

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

    InputLayout layout;

    int numVertices = 0;
    int numIndices = 0;

    int vertexStride = 0;

    ShaderSignature inputSignature;
    EffectTechnique technique;
    EffectPass pass;

    Effect effect;
    EffectMatrixVariable tmat;
    EffectVectorVariable mCol;
    EffectVectorVariable wfCol;

    float radius = 0;

    public Cylinder(float height, float radiusBottom, float radiusTop, int slices)
    {
      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());
      }

      tmat = effect.GetVariableByName("gWVP").AsMatrix();
      mCol = effect.GetVariableByName("colorSolid").AsVector();
      wfCol = effect.GetVariableByName("colorWireframe").AsVector();

      mCol.Set(new Color4(1, 0, 1, 0));
      wfCol.Set(new Color4(1, 0, 0, 0));

      int numVerticesPerRow = slices + 1;

      numVertices = numVerticesPerRow * 2 + 2;

      vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
      int SizeOfVertexBufferInBytes = numVertices * vertexStride;

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

      float theta = 0.0f;
      float horizontalAngularStride = ((float)Math.PI * 2) / (float)slices;

      for (int verticalIt = 0; verticalIt < 2; verticalIt++)
      {
        for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
        {
          float x;
          float y;
          float z;

          theta = (horizontalAngularStride * horizontalIt);

          if (verticalIt == 0)
          {
            // upper circle
            x = radiusTop * (float)Math.Cos(theta);
            y = radiusTop * (float)Math.Sin(theta);
            z = height;
          }
          else
          {
            // lower circle
            x = radiusBottom * (float)Math.Cos(theta);
            y = radiusBottom * (float)Math.Sin(theta);
            z = 0;
          }

          Vector3 position = new Vector3(x, z, y);
          vertices.Write(position);
        }
      }
      
      vertices.Write(new Vector3(0, height, 0));
      vertices.Write(new Vector3(0, 0, 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,
          SizeOfVertexBufferInBytes,
          ResourceUsage.Default,
          BindFlags.VertexBuffer,
          CpuAccessFlags.None,
          ResourceOptionFlags.None,
          0);

      numIndices = slices * 2 * 6;
      indices = new DataStream(2 * numIndices, true, true);

      for (int verticalIt = 0; verticalIt < 1; verticalIt++)
      {
        for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
        {
          short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
          short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));

          short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
          short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));

          indices.Write(lt);
          indices.Write(rt);
          indices.Write(lb);

          indices.Write(rt);
          indices.Write(rb);
          indices.Write(lb);
        }
      }

      for (int verticalIt = 0; verticalIt < 1; verticalIt++)
      {
        for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
        {
          short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
          short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));

          short patchIndexTop = (short)(numVerticesPerRow * 2);

          indices.Write(lt);
          indices.Write(patchIndexTop);
          indices.Write(rt);
        }
      }

      for (int verticalIt = 0; verticalIt < 1; verticalIt++)
      {
        for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
        {
          short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
          short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));


          short patchIndexBottom = (short)(numVerticesPerRow * 2 + 1);
          indices.Write(lb);
          indices.Write(rb);
          indices.Write(patchIndexBottom);
        }
      }

      indices.Position = 0;

      indexBuffer = new SlimDX.Direct3D11.Buffer(
          DeviceManager.Instance.device,
          indices,
          2 * numIndices,
          ResourceUsage.Default,
          BindFlags.IndexBuffer,
          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.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()
    {

    }
  }
}


Result


For this cylinder, I used the following parameters: height = 2, radius at the bottom = 1, radius at the top = 1 and slices = 16.





If you want to create a tip, you can set the radius at to top to zero:




You can download the source code for this tutorial here.

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.

Procedural Meshes: The Sphere

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 sphere mesh procedurally.

Formula

I use the following formula to create the vertices of the sphere:

x = radius * Cos(theta) * Cos(phi)
y = radius * Cos(theta) * Sin(phi)
z = radius * Sin(theta)

If you are looking at the origin an down the positive z-axis, theta is the angle between the x-axis and the line made from this angle. Theta runs in the interval from PI/2 to -PI/2 which corresponds to the latitude if you compare it to a globe:




If you look from top down on the sphere, phi is the angle between the x-axis and the line from the angle. Phi runs from 0 to 2 * PI, which corresponds to the longitude, compared with a globe:



If you fill in the values in radian for theta and phi, you will get the according point on the sphere.

From here on it is pretty straight forward: have a double nested for-loop and iterate from the top to the bottom of the sphere in the outer loop (theta) and create the vertices to on this circle in the inner loop by circling once around the y-axis (phi).

Creating the Vertex Buffer

This is, how it looks in source code. Please note, that I swap the y and z values when creating the vertices. I find it easier to do the math in a coordinate system where the z-axis points up, while in 3D coordinate systems used in computer graphics the y-axis points up.

int numVerticesPerRow = slices + 1;
int numVerticesPerColumn = stacks + 1;

numVertices = numVerticesPerRow * numVerticesPerColumn;

vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
int SizeOfVertexBufferInBytes = numVertices * vertexStride;

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

float theta = 0.0f;
float phi = 0.0f;

float verticalAngularStride = (float)Math.PI / (float)stacks;
float horizontalAngularStride = ((float)Math.PI * 2) / (float)slices;

for (int verticalIt = 0; verticalIt < numVerticesPerColumn; verticalIt++)
{
  // beginning on top of the sphere:
  theta = ((float)Math.PI / 2.0f) - verticalAngularStride * verticalIt;

  for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
  {
    phi = horizontalAngularStride * horizontalIt;

    // position
    float x = radius * (float)Math.Cos(theta) * (float)Math.Cos(phi);
    float y = radius * (float)Math.Cos(theta) * (float)Math.Sin(phi);
    float z = radius * (float)Math.Sin(theta);

    Vector3 position = new Vector3(x, z, y);
    vertices.Write(position);
  }
}

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,
    SizeOfVertexBufferInBytes,
    ResourceUsage.Default,
    BindFlags.VertexBuffer,
    CpuAccessFlags.None,
    ResourceOptionFlags.None,
    0);


Creating the Index Buffer

Just like in the tutorial for the grid mesh, I am iterating through the rows of vertices and create two triangles at each position.

numIndices = slices * stacks * 6;
indices = new DataStream(2 * numIndices, true, true);

for (int verticalIt = 0; verticalIt < stacks; verticalIt++)
{
    for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
    {
        short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
        short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));

        short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
        short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));
     
        indices.Write(lt);
        indices.Write(rt);
        indices.Write(lb);

        indices.Write(rt);
        indices.Write(rb);
        indices.Write(lb);
    }
}

indices.Position = 0;

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



Source Code

This is the source code of the sphere:

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

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

        InputLayout layout;

        int numVertices = 0;
        int numIndices = 0;

        int vertexStride = 0;

        ShaderSignature inputSignature;
        EffectTechnique technique;
        EffectPass pass;

        Effect effect;
        EffectMatrixVariable tmat;
        EffectVectorVariable mCol;
        EffectVectorVariable wfCol;

        float radius = 0;

        public Sphere(float radius, int slices, int stacks )
        {
            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());
            }

            this.radius = radius;

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

            int numVerticesPerRow = slices + 1;
            int numVerticesPerColumn = stacks + 1;

            numVertices = numVerticesPerRow * numVerticesPerColumn;

            vertexStride = Marshal.SizeOf(typeof(Vector3)); // 12 bytes
            int SizeOfVertexBufferInBytes = numVertices * vertexStride;

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

            float theta = 0.0f;
            float phi = 0.0f;

            float verticalAngularStride = (float)Math.PI / (float)stacks;
            float horizontalAngularStride = ((float)Math.PI * 2) / (float)slices;

            for (int verticalIt = 0; verticalIt < numVerticesPerColumn; verticalIt++)
            {
                // beginning on top of the sphere:
                theta = ((float)Math.PI / 2.0f) - verticalAngularStride * verticalIt;

                for (int horizontalIt = 0; horizontalIt < numVerticesPerRow; horizontalIt++)
                {
                    phi = horizontalAngularStride * horizontalIt;

                    // position
                    float x = radius * (float)Math.Cos(theta) * (float)Math.Cos(phi);
                    float y = radius * (float)Math.Cos(theta) * (float)Math.Sin(phi);
                    float z = radius * (float)Math.Sin(theta);

                    Vector3 position = new Vector3(x, z, y);
                    vertices.Write(position);
                }
            }

            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,
                SizeOfVertexBufferInBytes,
                ResourceUsage.Default,
                BindFlags.VertexBuffer,
                CpuAccessFlags.None,
                ResourceOptionFlags.None,
                0);

            numIndices = slices * stacks * 6;
            indices = new DataStream(2 * numIndices, true, true);

            for (int verticalIt = 0; verticalIt < stacks; verticalIt++)
            {
                for (int horizontalIt = 0; horizontalIt < slices; horizontalIt++)
                {
                    short lt = (short)(horizontalIt + verticalIt * (numVerticesPerRow));
                    short rt = (short)((horizontalIt + 1) + verticalIt * (numVerticesPerRow));

                    short lb = (short)(horizontalIt + (verticalIt + 1) * (numVerticesPerRow));
                    short rb = (short)((horizontalIt + 1) + (verticalIt + 1) * (numVerticesPerRow));
                 
                    indices.Write(lt);
                    indices.Write(rt);
                    indices.Write(lb);

                    indices.Write(rt);
                    indices.Write(rb);
                    indices.Write(lb);
                }
            }

            indices.Position = 0;

            indexBuffer = new SlimDX.Direct3D11.Buffer(
                DeviceManager.Instance.device,
                indices,
                2 * numIndices,
                ResourceUsage.Default,
                BindFlags.IndexBuffer,
                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.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()
        {

        }
    }
}

As I am using a different shader than before which sets an uniform color for the mesh and a color for the wireframe, I will post the complete code of the shader used here. I will use this shader also for the next tutorial in which I will show how to create other geometric primitives. I use the variables mCol and wfCol to set the colors of the mesh and the wireframe in the code above via the effect framework.


matrix gWVP;
float4 colorSolid;
float4 colorWireframe;

float4 VShader(float4 position : POSITION) : SV_POSITION
{
  return mul( position, gWVP);
}

float4 PShader(float4 position : SV_POSITION) : SV_Target
{
  return colorSolid;
}

float4 PShaderWireframe(float4 position : SV_POSITION) : SV_Target
{
  return colorWireframe;
}

RasterizerState SolidState
{
  FillMode = Solid;
};

RasterizerState WireframeState
{
  FillMode = Wireframe;
  SlopeScaledDepthBias = -0.5f;
};

technique10 Render
{
  pass P0
  {
    SetVertexShader( CompileShader( vs_4_0, VShader() ));
    SetGeometryShader( NULL );
    SetPixelShader( CompileShader( ps_4_0, PShader() ));
    SetRasterizerState(SolidState);
  }

  pass P1
  {
    SetVertexShader( CompileShader( vs_4_0, VShader() ));
    SetGeometryShader( NULL );
    SetPixelShader( CompileShader( ps_4_0, PShaderWireframe() ));
    SetRasterizerState(WireframeState);
  }
}



Conclusion


This picture shows a sphere created with 8 slices and 8 stacks:


This sphere was created with 32 slices and 32 stacks:


You can download the code to this tutorial here.

Thursday, April 18, 2013

Depth/Stencil Buffer

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

Problem

So far, we haven't cared about setting up and using the depth buffer. This results in the following problem, when rendering more than one object: the objects that is drawn last is painted over all other objects, regardless of their real order in 3D space.


In the picture above the grid is drawn first and then the cube is drawn. Because the cube is painted over the grid, the line of the gird marked in the red rectangle is not visible.


Conversely, if the grid is drawn last, the grid is always visible and the cube looks transparent. This looks cool when playing around with the camera, but is not exactly what we want in every situation ;)

The solution for painting objects in the right order to the screen according to their 3D positions is using a depth buffer.

Depth/Stencil Buffer 

A depth buffer, also called z-buffer, is a texture with the same size as the render target. The depth buffer is applied in one of the last stages of the pipeline, namely the output merger stage. What happens at the output merger stage?

To explain this, we need to take a step back in the rending pipeline and look at the rasterizer stage. (See here for the MSDN documentation on the Direct3D 11 graphics pipeline) When you provided some geometry and applied all the transformations to finally render your objects to the screen, your objects have to be rasterized. This means the scene gets sampled and discretized into pixels, or in other words: a raster is laid over your scene and each cell of this raster represents a pixel. This pixel is written to the render target.


At this point the depth buffer and the output merger stage comes into play. The depth buffer has the same height and width as you render target, because each pixel in the depth buffer corresponds to a pixel on the render target. When a object is rasterized into pixels and painted to the render target, the GPU has information about the depth of this pixel, or how far it is away from the camera. The depth buffer is for holding the depth of the current pixel on the render target. If the pixel of the current object in nearer to the camera than the pixel that is already at the according position, the old pixel is overwritten on the render target and the depth value in the depth buffer is updated to the new pixels depth. If the depth of a pixel is higher than an existing pixel, the old pixel will not be overwritten.
To sum up: the depth buffer keeps track of the depth of the pixels on the render target during the rasterization process. Only the nearest pixels of the scene make it to the final render target buffer.

As we don't need the stencil buffer right now, I won't go into much detail. Depth and stencil buffer are often combined in one texture. A common format is using 24 bit for the depth buffer and 8 bit for the stencil buffer.
The stencil is, as its name implies, a  template that can be applied in the output merger stage. This buffer can be used in various ways according to the flags set in the GPU. You can use it as a mask and don't allow rending to these pixels or you can use the buffer to count your write accesses to this pixel in one render cycle.

Creating the Depth/Stencil Buffer

Like I mentioned above, we need a texture for the depth buffer. This is initialized with the width and height of the control, we are rendering on. With this texture we can now create the depth/stencil buffer, which is in SlimDX a class called DepthStencilView:


Texture2D DSTexture = new Texture2D(
  device,
  new Texture2DDescription()
  {
    ArraySize = 1,
    MipLevels = 1,
    Format = Format.D32_Float,
    Width = form.ClientSize.Width,
    Height = form.ClientSize.Height,
    BindFlags = BindFlags.DepthStencil,
    CpuAccessFlags = CpuAccessFlags.None,
    SampleDescription = new SampleDescription(1, 0),
    Usage = ResourceUsage.Default
  }
);

depthStencil = new DepthStencilView(
  device,
  DSTexture,
  new DepthStencilViewDescription()
  {
    ArraySize = 0,
    FirstArraySlice = 0,
    MipSlice = 0,
    Format = Format.D32_Float,
    Dimension = DepthStencilViewDimension.Texture2D
  }
);

Setting the DepthStencil State

Now that we have set up the depth buffer, we need to create a depth/stencil state and assign it to the DepthStencilState of the output merger stage.

context.OutputMerger.DepthStencilState = DepthStencilState.FromDescription(
  device,
  new DepthStencilStateDescription()
  {
    DepthComparison = Comparison.Always,
    DepthWriteMask = DepthWriteMask.All,
    IsDepthEnabled = true,
    IsStencilEnabled = false
  }
);

context.OutputMerger.SetTargets(depthStencil, renderTarget);

DepthStencilStateDescription dssd = new DepthStencilStateDescription
{
  IsDepthEnabled = true,
  IsStencilEnabled = false,
  DepthWriteMask = DepthWriteMask.All,
  DepthComparison = Comparison.Less,
};

DepthStencilState depthStencilStateNormal;
depthStencilStateNormal = DepthStencilState.FromDescription(DeviceManager.Instance.device, dssd);
DeviceManager.Instance.context.OutputMerger.DepthStencilState = depthStencilStateNormal;

Putting it all together

This is the complete code of my method to create the depth/stencil buffer and to set the state:


public void CreateDepthStencilBuffer(System.Windows.Forms.Control form)
{
  Texture2D DSTexture = new Texture2D(
    device,
    new Texture2DDescription()
    {
      ArraySize = 1,
      MipLevels = 1,
      Format = Format.D32_Float,
      Width = form.ClientSize.Width,
      Height = form.ClientSize.Height,
      BindFlags = BindFlags.DepthStencil,
      CpuAccessFlags = CpuAccessFlags.None,
      SampleDescription = new SampleDescription(1, 0),
      Usage = ResourceUsage.Default
    }
  );

  depthStencil = new DepthStencilView(
    device,
    DSTexture,
    new DepthStencilViewDescription()
    {
      ArraySize = 0,
      FirstArraySlice = 0,
      MipSlice = 0,
      Format = Format.D32_Float,
      Dimension = DepthStencilViewDimension.Texture2D
    }
   );

  context.OutputMerger.DepthStencilState = DepthStencilState.FromDescription(
    device,
    new DepthStencilStateDescription()
    {
      DepthComparison = Comparison.Always,
      DepthWriteMask = DepthWriteMask.All,
      IsDepthEnabled = true,
      IsStencilEnabled = false
    }
  );

  context.OutputMerger.SetTargets(depthStencil, renderTarget);

  DepthStencilStateDescription dssd = new DepthStencilStateDescription
  {
    IsDepthEnabled = true,
    IsStencilEnabled = false,
    DepthWriteMask = DepthWriteMask.All,
    DepthComparison = Comparison.Less,
  };

  DepthStencilState depthStencilStateNormal;
  depthStencilStateNormal = DepthStencilState.FromDescription(DeviceManager.Instance.device, dssd);
  DeviceManager.Instance.context.OutputMerger.DepthStencilState = depthStencilStateNormal;
}


Clearing the Depth/Stencil Buffer

Because the GPU writes in every render cycle to the depth/stencil buffer, we need to clear this buffer in every render cycle. This is done with ClearDepthStencilView.

public void RenderScene()
{
  while (true)
  {
    fc.Count();
       
    DeviceManager dm = DeviceManager.Instance;
    dm.context.ClearDepthStencilView(dm.depthStencil, DepthStencilClearFlags.Depth | DepthStencilClearFlags.Stencil, 1.0f, 0);
    dm.context.ClearRenderTargetView(dm.renderTarget, new Color4(0.75f, 0.75f, 0.75f));

    Scene.Instance.render();

    dm.swapChain.Present(syncInterval, PresentFlags.None);
  }
}


Camera

The next thing to do is to check, if you are setting the values for znear and zfar right, when setting up the perspective for the camera. When using the depth buffer, these values come into play in order to decide in which interval (from znear to zfar) objects are rendered.

I create my perspective matrix this way:

perspective = Matrix.PerspectiveFovLH((float)Math.PI / 4, 1.3f, 0.1f, 1000.0f);


Result

As you can see in the picture in the green rectangle, the grid is now rendered above the cube, if a line is in front of the cube. 


If you take a look at the right rectangle, you can see that the wireframe on the cube seems to be painted in dotted lines. But this is not how it is supposed to look. What is happening here is called z-buffer fighting. Because the cube and the wireframe are rendered on base of the same geometry, the output merger stage can't decide which pixel to render, because both pixels have the same depth.

To avoid this z-fighting, I add a depth bias to the wireframe in the rasterizer state of the shader:

RasterizerState WireframeState
{
  FillMode = Wireframe;
  SlopeScaledDepthBias = -0.5f;
};

Here is the documentation on MSDN on depth bias:
http://msdn.microsoft.com/en-us/library/windows/desktop/cc308048(v=vs.85).aspx

Now everything looks OK:



You can download the source code to this tutorial here.