Search code examples
c++3dhlsldirect3d11

What is the correct way to set global parameters in hlsl shader?


What is the correct way of setting global params in hlsl shader? If I have the following global params:

float4x4 World;
float4x4 View;
float4x4 Projection;

And I use them within a vertex shader:

void VertexShaderFunction( in float4 inputPosition : POSITION, in float4 colorIn : COLOR, out float4 posOut : SV_POSITION, out float4 colorOut : COLOUR)
{   
    //Set values for output
    float4 worldPosition = mul(inputPosition, World);
    float4 viewPosition = mul(worldPosition, View);
    float4 position = mul(viewPosition, Projection);


    posOut = position;
    colorOut = colorIn;
}

Then how do I set these global values from c++ code f.e. when camera is moved? Should I create another shader, which just sets these values that I can access as buffer like this?

void SetProjectionMatrix(float4x4 inputMatrix : MATRIX){
    Projection = inputMatrix;
}

Please tell me what is the proper way to achieve this.


Solution

  • First, in your shader you will want to put your matrices into a constant buffer:

    cbuffer CameraBuffer : register( b0 ) {
        float4x4 World;
        float4x4 View;
        float4x4 Projection;
    }
    

    If you don't declare a constant buffer, they are created for you, but it is much better to declare them explicitly and group them by frequency of update. For example, group all constants that are updated per frame together and all constants that are only set once together. This allows you to only update the constants that you need to update without sending extra data to the GPU.

    Even though they are inside this cbuffer structure, they are still accessed the same way within your shader.

    In your C++ code you will want to declare a similar structure to store your matrices:

    struct CameraConstants {
        XMFLOAT4X4 world;
        XMFLOAT4X4 view;
        XMFLOAT4X4 projection;
    };
    

    It's very important to take care with the packing rules for constant variables. This structure will have no problems, but in some cases you may need to add extra padding to your C++ structures to account for the fact that the shader cbuffers pack data so that it does not cross 16 byte boundaries due to the 16 byte nature of the GPU registers.

    During initialization, you'll also need to create a constant buffer. The process is the same as a vertex buffer except you'll need to use the following flags to declare a constant buffer that is writable by the CPU:

    cbDesc.Usage = D3D11_USAGE_DYNAMIC;
    cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
    cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
    

    Whenever you update your matrices, you need to upload them to the GPU. To do this, you map the constant buffer to the CPU, and copy over your CameraConstants structure:

    D3D11_MAPPED_SUBRESOURCE resource;
    m_deviceContext->Map( cameraCbuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource );
    memcpy( resource.pData, cameraConstants, sizeof( CameraConstants ) );
    m_deviceContext->Unmap( cameraCbuffer, 0 );
    

    Now just bind the constant buffer to your vertex shader:

    m_deviceContext->VSSetConstantBuffers( 0, 1, &cameraCbuffer );
    

    Note that the first parameter maps to the register you used in your shader cbuffer declaration (b0 in this case).

    One more thing, matrices in hlsl are column major by default. If your matrices are row major in C++ (probably), then you need to either transpose them before sending to the GPU or declare your matrices as row_major in the shader.

    Check out the DirectX samples for some source code for all of this: https://code.msdn.microsoft.com/windowsdesktop/Direct3D-Tutorial-Win32-829979ef