Formeln

Tuesday, March 19, 2013

Setting Transformations in a Shader with the Effect Framework

In the previous tutorial I showed how to set a transformation in a shader via a Contant Buffer, resulting
in a rotating triangle. In this tutorial I will implement the same functionality with the Effect Framework.

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 TriangleEF : Renderable
    {
        ShaderSignature inputSignature;
        EffectTechnique technique;
        EffectPass pass;

        Effect effect;

        InputLayout layout;
        SlimDX.Direct3D11.Buffer vertexBuffer;

        EffectMatrixVariable tmat;

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

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

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

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

        float rot = 0.0f;
        Matrix rotMat;

        public override void render()
        {
            rot += 0.01f;
            rotMat = Matrix.RotationY(rot);
            tmat.SetMatrix(rotMat);
           
            // 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));
            
            technique = effect.GetTechniqueByName("Render");

            EffectTechniqueDescription techDesc;
            techDesc = technique.Description;

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

Shader Source Code


matrix gWVP;

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

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

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

Explaining the Shader Source Code

In contrast to the previous shader in the last tutorial I do not declare the matrix variable gWVP in a
ConstantBuffer but directly as a matrix.

Also. when using the Effect Framework you have to define a Technique with at least one Pass:


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

The Technique is your interface to your shader from your code and in the Pass the shaders are set. I will explain in the next section how to interface with your shader.

Explaining the Triangle Source Code

In the last tutorial you had to load the VertexShader and the PixelShader seperately. With the Effect Framework you just have to load the ShaderBytecode for the effect:

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

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

When compiling the ShaderBytecode you have to set the name of the Technique as a parameter, in this case "Render". You have to create the effect by calling the constructor of the Effect Class with the device and the ShaderBytecode as parameters. Also you have access the technique and pass to get the InputSignature of the shader.

In order to access the matrix variable in your shader, you have to use the getVariableByName function of the effect. The matrix from the effect file is asigned to a matrix called tmat, which is of the type EffectMatrixVariable. We will use the variable tmat again in the render function of the triangle to set the transformation of the triangle:

public override void render()
{
  rot += 0.01f;
  rotMat = Matrix.RotationY(rot);
  tmat.SetMatrix(rotMat);
           
  // 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));
            
  technique = effect.GetTechniqueByName("Render");

  EffectTechniqueDescription techDesc;
  techDesc = technique.Description;

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

The statement tmat.SetMatrix(rotMat) sets the variable for the transformation matrix in the effect.

Now we get a rotating triangle again:


Observe, that the triangle is rotating counter-clockwise, while the triangle in the previous tutorial was rotating clockwise. As far as i know, DirectX uses per default a left-handed coordinate system and the triangle should rotate clockwise with positively growing values for rotation around the y-axis. This can be resolved by transposing the matrix before passing it to the Effect Framework: tmat.SetMatrix(Matrix.Transpose(rotMat)) and the triangle is rotating clockwise again.

You can download the source code here.


2 comments:

  1. Finally a working example of how to set shader variables in SlimDX. Thank You. This example is great!!!

    ReplyDelete
  2. Thank you so much for this - excellent stuff!!

    ReplyDelete