c++directxfragment-shadervertex-shaderdirectx-12

d3d12: when adding a constant buffer, the Output structure changes


light strip, this is related to float3 worldPos For some reason it does not change along the x axis enter image description here

coloring a pixel depending on worldPos enter image description here worldPos depends on object coordinates and not on pixel coordinates

what the colors should look like based on worldPos enter image description here

vertex shader:

cbuffer rot : register(b0)
{
    matrix modelViewProj;
    matrix model;
};

struct Output
{
    float3 worldPos : Position;
    float3 n : Normal;
    float4 pos : SV_Position;
    float2 tc : Texcoord;
};

Output main(float3 pos : Position, float3 n : Normal, float2 tc : Texcoord)
{
    Output vertexOut;

    vertexOut.worldPos = (float3)mul(float4(pos, 1.0f), model);
    vertexOut.pos = mul(float4(pos, 1.0f), modelViewProj);
    vertexOut.n = mul(n, (float3x3) model);
    vertexOut.tc = tc;
    
    return vertexOut;
}

pixel shader:

struct ObjectCBuf
{
    float3 materialColor;
};

cbuffer LightCBuf : register(b1)
{
    float3 lightPos;
    float3 ambient;
    float3 diffuseColor;
    float diffuseIntensity;
    float attConst;
    float attLin;
    float attQuad;
    ObjectCBuf object;
};

float4 main(float3 worldPos : Position, float3 n : Normal, float4 pos : SV_Position) : SV_TARGET
{
    const float3 vToL = lightPos - worldPos;
    const float distToL = length(vToL);
    const float3 dirToL = vToL / distToL;
    // diffuse attenuation
    const float att = 1.0f / (attConst + attLin * distToL + attQuad * (distToL * distToL));
    // diffuse intensity
    const float3 diffuse = diffuseColor * diffuseIntensity * att * max(0.0f, dot(dirToL, n));
    // final color
    //return float4(object.materialColor, 1.0);
    return float4(saturate((diffuse + ambient) * object.materialColor), 1.0f);
}

when I don't add a constant buffer in c++ code then worldPos behaves fine


Solution

  • There's a memory alignment issue. For float2 to float4 vectors for constant buffers, you need to make sure they are aligned to float4 boundaries, or it gets "torn up". The same goes for D3D11. (But additionally, for D3D12, a constant buffer needs to have a total size aligned to 256 bytes, that's 64 floats, or 16 float4s)

    In your case, you have float3 lightPos and float3 ambient right next to each other, this is how they will be placed in memory:

    0 1 2 3
    lightPos.x lightPos.y lightPos.z ambient.x
    ambient.y ambient.z diffuseColor.x diffuseColor.y

    do you see the issue here? float3 ambient (as well as float3 diffuseColor) have their elements "on different rows", float* vectors must be read as a whole in hlsl, and hlsl can only read "on the same row" for each vector. Value ambient.x is (probably) read correctly, but value ambient.y and ambient.z is lost, and they will probably end up being 0.0f. I say "probably" because it's undefined behavior, there's no guarantee that this is what will end up happening on every hardware.

    This is how I would change the code:

    cbuffer LightCBuf : register(b1)
    {
        // row 0
        float3 lightPos;
        float diffuseIntensity;
    
        // row 1
        float3 ambient;
        float attConst;
    
        // row 2
        float3 diffuseColor;
        float attLin;
    
        // row 3
        float attQuad;
        ObjectCBuf object;
    };
    

    notice from the code above, that I've placed a float alongside each float3, filling an entire 4 floats' length of memory before moving on to the next element, so that each float3 will end up being "on the same row".