Formeln

Saturday, November 10, 2012

Setting Transformations in a Shader with a Constant Buffer

In this tutorial I will show you, how you can set Transformations in a Shader via the Constant Buffer.
We will render a Triangle and perform a rotation on it while rendering.

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

namespace Apparat.Renderables
{
    public class TriangleCB : Renderable
    {
        ShaderSignature inputSignature;
        VertexShader vertexShader;
        PixelShader pixelShader;

        InputLayout layout;
        SlimDX.Direct3D11.Buffer vertexBuffer;

        public TriangleCB()  
        {

            #region shader and triangle

            try
            {
                // load and compile the vertex shader
                using (var bytecode = ShaderBytecode.CompileFromFile("transform.fx", "VShader", "vs_4_0", ShaderFlags.None, EffectFlags.None))
                {
                    inputSignature = ShaderSignature.GetInputSignature(bytecode);
                    vertexShader = new VertexShader(DeviceManager.Instance.device, bytecode);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            // load and compile the pixel shader
            using (var bytecode = ShaderBytecode.CompileFromFile("transform.fx", "PShader", "ps_4_0", ShaderFlags.None, EffectFlags.None))
            {
                pixelShader = new PixelShader(DeviceManager.Instance.device, bytecode);
            }

            // create test vertex data, making sure to rewind the stream afterward
            var vertices = new DataStream(12 * 3, true, true);
            vertices.Write(new Vector3(0.0f, 0.5f, 0.5f));
            vertices.Write(new Vector3(0.5f, -0.5f, 0.5f));
            vertices.Write(new Vector3(-0.5f, -0.5f, 0.5f));
            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,
                12 * 3,
                ResourceUsage.Default,
                BindFlags.VertexBuffer,
                CpuAccessFlags.None,
                ResourceOptionFlags.None,
                0);
            #endregion
        }

        public override void dispose()
        {
            pixelShader.Dispose();
            vertexShader.Dispose();
            inputSignature.Dispose();
        }

        float rot = 0.0f;
        Matrix rotMat; 

        public override void render()
        {
            rot += 0.01f;
            rotMat = Matrix.RotationY(rot);

            var matStream = new DataStream(64, true, true);
            matStream.Write(rotMat);
            matStream.Position = 0;

            using (SlimDX.Direct3D11.Buffer matBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device,     //Device
                                                             matStream, //Stream
                                                             64,         // Size                
                                                                // Flags
                                                             ResourceUsage.Dynamic,
                                                             BindFlags.ConstantBuffer,
                                                             CpuAccessFlags.Write,
                                                             ResourceOptionFlags.None,
                                                             4))
            {
                DeviceManager.Instance.context.VertexShader.SetConstantBuffer(matBuffer, 0);
            }

            

            // 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, 12, 0));

            //// set the shaders
            DeviceManager.Instance.context.VertexShader.Set(vertexShader);
            DeviceManager.Instance.context.PixelShader.Set(pixelShader);

            // render the triangle
            DeviceManager.Instance.context.Draw(3, 0);
        }


    }
}

Shader Source Code


cbuffer ConstBuffer : register(c0)
{
 float4x4 gWVP;
}

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

float4 PShader(float4 position : SV_POSITION) : SV_Target
{
 return float4(1.0f, 0.0f, 0.0f, 1.0f);
}

Explanation of the Shader Source Code

In order to set transformations, we need to have a variable in the Shader to assign the transformation.
In this shader I call the variable gWVP and it is declared in the ConstantBuffer:


cbuffer ConstBuffer : register(c0)
{
 float4x4 gWVP;
}


To apply the transformation to each vertex of our triangle, we have to apply a Multiplication with
the Transformation Matrix.

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


Explanation of the Triangle Source Code

If you compare the Constructor of the TriangleCB class above with the Constructor of the Triangle class
in the "Rendering a Triangle" Tutorial, everything remains the same:

We create a VertexShader, a Pixel Shader, we write the Vertices of the Triangle to a DataStream and create a VertexBuffer.

I added two Variables for the rotation of the Triangle and a Matrix to hold the Transformation Matrix:


float rot = 0.0f;
Matrix rotMat; 

The interesting part happens in the render Function of our Triangle. Let's have a short look at our original Version of our Triangle:

public override void render()
{
  // 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, 12, 0));

  // set the shaders
  DeviceManager.Instance.context.VertexShader.Set(vertexShader);
  DeviceManager.Instance.context.PixelShader.Set(pixelShader);

  // render the triangle
  DeviceManager.Instance.context.Draw(3, 0);
}

The Input Assembler Stage is set with an InputLayout, we provide a Primitive Topology and the Vertex Buffer is set. The Vertex and Pixel Shader are set in the Device and we can finally draw the Triangle.

Our new render function looks like this:



public override void render()
{
  rot += 0.01f;
  rotMat = Matrix.RotationY(rot);

  var matStream = new DataStream(64, true, true);
  matStream.Write(rotMat);
  matStream.Position = 0;

  using (SlimDX.Direct3D11.Buffer matBuffer = new SlimDX.Direct3D11.Buffer(
    DeviceManager.Instance.device,     //Device
    matStream, //Stream
    64,         // Size                
    // Flags
    ResourceUsage.Dynamic,
    BindFlags.ConstantBuffer,
    CpuAccessFlags.Write,
    ResourceOptionFlags.None,
    4)){             DeviceManager.Instance.context.VertexShader.SetConstantBuffer(matBuffer, 0);
  }

  // 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, 12, 0));

  // set the shaders
  DeviceManager.Instance.context.VertexShader.Set(vertexShader);
  DeviceManager.Instance.context.PixelShader.Set(pixelShader);

  // render the triangle
  DeviceManager.Instance.context.Draw(3, 0);
}

The lower part of the Source Code is exactly the same. In the upper part I add 0.01f of rotation in every frame. Then this rotation value is used to create a Rotation Matrix, that performs a Rotation around the Y-Axis:

rot += 0.01f;
rotMat = Matrix.RotationY(rot);

Next I create a DataStream, to write the Rotation Matrix to:

var matStream = new DataStream(64, true, true);
matStream.Write(rotMat);
matStream.Position = 0;

The SlimDX DataStream Class has the following Constructor:

public DataStream(long sizeInBytes, bool canRead, bool canWrite);

So when creating the DataStream the Size of the Matrix in Bytes has to be given as a Parameter.
A Matrix is a struct with 4x4 float, a float consists of 4 Bytes, so the Matrix struct has the size of 64 Bytes, thus we have to create the DataStream with a size of 64 Bytes. And we assign the Matrix rotMat by writing it to the stream. Finally we have to rewind the Stream by setting its Position to 0.

Finally we have to assign the Transformation Matrix to the VertexShader:

using (SlimDX.Direct3D11.Buffer matBuffer = new SlimDX.Direct3D11.Buffer(DeviceManager.Instance.device, //Device
  matStream, //Stream
  64,        // Size                
  // Flags
  ResourceUsage.Dynamic,
  BindFlags.ConstantBuffer,
  CpuAccessFlags.Write,
  ResourceOptionFlags.None,
  4))
{
  DeviceManager.Instance.context.VertexShader.SetConstantBuffer(matBuffer, 0);
}

This is done by creating a Buffer that holds the DataStream with the Data of the Rotation Matrix. Then we use this Buffer to set the ConstantBuffer of the VertexShader.

When Creating the Triangle, adding it to the Scene and let the Program run, you a get a rotating red Triangle:





You can download the Project here:
http://apparat.codeplex.com/SourceControl/changeset/9d048db48302

Sunday, November 4, 2012

Creating a UserControl for the Render Engine

In this tutorial I will show you, how you can create a User Control that you can add to new projects just by dragging and dropping it from your toolbox to your form.

Adding a UserControl


Start by Right-Clicking on your Apparat Project and select Add/UserControl:



In the following Dialogue select User Control. I will name this Control RenderControl. To add the Control, click Add on the right bottom of the Dialogue:


Functions in the UserControl

Let's take a look at our class Form1 in the MDX11Form Project:


namespace MDX11Form
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            DeviceManager.Instance.createDeviceAndSwapChain(this);
            RenderManager.Instance.init();

            Triangle triangle = new Triangle();
            Scene.Instance.addRenderObject(triangle);
        }

        public void shutDown()
        {
            RenderManager.Instance.shutDown();
            DeviceManager.Instance.shutDown();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            shutDown();
        }
    }
}


Observe, that the function createDeviceAndSwapChain has the following signature:
public void createDeviceAndSwapChain(System.Windows.Forms.Control form)

All User Controls inherit from System.Windows.Controls.Control and we can reuse this code
in our RenderControl. Furthermore I move the shutDown Method into the User Control.
To edit the RenderControl Right-Click on it in the Apparat-Project and select View Code.

Visual Studio created the following Code for us, when we added the User Control:






namespace Apparat
{
    public partial class RenderControl : UserControl
    {
        public RenderControl()
        {
            InitializeComponent();
        }
    }
}

Copying the above mentioned code, and moving the code to create the DeviceManager and RenderManager to an init function, the code in our RenderControl now looks like this:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Apparat.Renderables;

namespace Apparat
{
    public partial class RenderControl : UserControl
    {
        public RenderControl()
        {
            InitializeComponent();
           
        }

        public void init()
        {
            DeviceManager.Instance.createDeviceAndSwapChain(this);
            RenderManager.Instance.init();

            Triangle triangle = new Triangle();
            Scene.Instance.addRenderObject(triangle);
        }

        public void shutDown()
        {
            RenderManager.Instance.shutDown();
            DeviceManager.Instance.shutDown();
        }
    }
}

In order to make the RenderControl visible, when I drag it to a Form, I set the BackColor of the
RenderControl to Orange in the Properties of the RenderControl.
And we are done with the User Control!
At this stage you have to Rebuild the Apparat Library.

Adding the RenderControl to the Form

Now Double-Click the Form1 in the MDX11Form Project. If you select the Toolbox of Visual Studio,
the RenderControl shows up in our Toolbox:



Click the RenderControl in the Toolbox and click somewhere in the Form1 to drop our RenderControl there, or you can use Drag&Drop from the Toolbox. Adjusting the sizes of the Form1 and the RenderControl, the Form1 looks now like this:


If your compile the MDX11Form Project right now, you see nothing but the orange rectangle in the Form1.
Double-Click in the Designer on the Form1 to edit the Handler for the Load event of the Form.
Visual Studio created a renderControl1 object of our RenderControl, when we added it to the Form.
We can access the functions via this renderControl1 object.

All you have to do, to get the RenderControl working, is to call its init function. I do it in the Handler
of the Load event. To shut down, properly, I call the shutDown Method in the Handler of the FormClosing event:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Apparat;
using Apparat.Renderables;

namespace MDX11Form
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            renderControl1.shutDown();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            renderControl1.init();
        }
    }
}

If you start the Solution now, you can see that our Render Engine is running in our UserControl:


You can download the Solution here:
http://apparat.codeplex.com/SourceControl/changeset/dc772f719b14