Formeln

Sunday, November 3, 2013

Making a Screenshot with SlimDX

Making a Screenshot with SlimDX


Making a screenshot is rather simple with SlimDX. In general, you take
the render surface, read its content and save it to a file.
Luckily, there is a function for this in the SDK:

SlimDX.Direct3D11.Resource.SaveTextureToFile

This is a code fragment using this method:


 SlimDX.Direct3D11.Resource.SaveTextureToFile(dm.context,
          dm.renderTarget.Resource,
          SlimDX.Direct3D11.ImageFileFormat.Jpg,
          "screenshot.jpg");

You can make a function to encapsulate the call and call this function at the end of your render loop.
I made it this way:


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);

    if (makeScreenshot)
    {
      screenShots.MakeScreenshot(DeviceManager.Instance, ImageFileFormat.Jpg);
      makeScreenshot = false;
    }
  }
}

The boolean value makeScreenshot has to be triggered from outside this class, for example
from you GUI code. The screenshot is made with the current width and height of the render window.

If you want to capture the frame in an other resolution, you have to render the scene to the render surface,
reset and resize the buffers to the wanted resolution, write the screenshot and then resize the buffers
back to the current resolution of the window you are rendering to.

This is, how my helper method looks, for rendering a screenshot with a different resolution:


public void MakeScreenshot(DeviceManager dm, Scene scene, int width, int height, SlimDX.Direct3D11.ImageFileFormat format, string fileName)
{
  try
  {
    Viewport OriginalViewport = dm.viewport;
    dm.Resize(width, height);
    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.Render();

    SlimDX.Direct3D11.Resource.SaveTextureToFile(dm.context,
      dm.renderTarget.Resource,
      format,
      fileName +  "." + format.ToString());

      dm.Resize((int)OriginalViewport.Width, (int)OriginalViewport.Height);
    }
    catch (Exception ex)
    {
      Console.WriteLine(ex.ToString());
    }
  }
}

This is, for example, a screenshot with a resolution of 3000x2000:


When using the png format, the colors are somehow false. I don't know if it is problem in the
SDK or if I am using a wrong format.

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.