Search code examples
c++renderingdirectx-11

DX11 Shader executes properly only when Constant Buffer is set to unused slot


I'm having an issue where my shader code only executes properly if I set the pixel shader constant buffer at registerb1 to the struct below. I've been looking all over for answers and I've probably spent a collective 8 hours trying to solve this. It seems that the variable time from ConstantBuffer is getting put in either of the variables of CameraBuffer. I thought __declspec(align(16)) fixed the padding issue. I believe I've also padded things correctly anyway. I've placed comments throughout my code.

// A buffer for lighting
__declspec(align(16))
    struct CameraBuffer {
    Vec3f position{};
    Vec3f direction{};
    Vec2f pad{};
};

// The buffer required for proper results, even though it shouldn't be required.
__declspec(align(16))
struct ConstantBuffer {
    float time;
    float period;
    float amplitude;
};

// Method for drawing a sphere
void DXGLApp::drawMaterialSphere(Vec3f position, dxgl::SP_DXGLMaterial material) {
    // transform data
    dxgl::buffer::TransformBuffer tbuff1{};
    tbuff1.world.setIdentity();
    tbuff1.world.setTranslation(position);
    tbuff1.view = m_camera->view();
    tbuff1.proj = m_camera->proj();
    m_cbLandscape->update(&tbuff1);

    // setting vertex data
    render()->stageInput()->setInputLayout(m_layout);
    render()->stageInput()->setVertexBuffers(1, &m_mesh1->getVertexBuffer());
    render()->stageInput()->setIndexBuffer(m_mesh1->getIndexBuffer());

    // setting shaders
    render()->stageShader()->VS_setShader(m_vs);
    render()->stageShader()->PS_setShader(m_ps);

    // setting cbuffers
    render()->stageShader()->VS_setCBuffer(0, 1, m_cbLandscape->get());
    render()->stageShader()->PS_setCBuffer(0, 1, m_cbCamera->get());

    // This cbuffer, 'm_cbPostProcess' is not used in the currently bound shader,
    // yet the results produced without it are incorrect.
    render()->stageShader()->PS_setCBuffer(1, 1, m_cbPostProcess->get());

    // sets texture slots
    render()->stageShader()->PS_setMaterial(0, 6, material);

    // draw
    render()->stageInput()->drawIndexedTriangleList(m_mesh1->getIndices().size(), 0, 0);

    // unbind
    //render()->stageShader()->VS_setShader(0);
    //render()->stageShader()->PS_setShader(0);
    //render()->stageShader()->VS_setCBuffer(0, 0, 0);
    //render()->stageShader()->PS_setCBuffer(0, 0, 0);
}

void update(long double delta) {
    // updating m_cbCamera
    dxgl::buffer::CameraBuffer camBuff{};
    camBuff.position = m_camera->world().getTranslation();
    camBuff.direction = m_camera->world().getZDirection();
    m_cbCamera->update(&camBuff);

    // updating m_cbPostProcess
    ConstantBuffer dvbuff{};
    dvbuff.time = t / 4.0f;
    dvbuff.period = 0;// 8.0f;
    dvbuff.amplitude = 0;// 0.0125f;
    m_cbPostProcess->update(&dvbuff);
}

Here's my shader code. I'm trying to implement PBR.

VERTEX SHADER

struct VS_Input {
    float4 position: POSITION;
    float2 texcoord: TEXCOORD;
    float3 normal:   NORMAL;
    float3 tangent:  TANGENT;
};

struct PS_Input {
    float4 position: SV_POSITION;
    float2 texcoord: TEXCOORD;
    float3 normal:   NORMAL;
    float3 tangent:  TANGENT;

    float3 pixelPosition: POSITION;
};

cbuffer transform: register(b0) {
    row_major float4x4 world;
    row_major float4x4 view;
    row_major float4x4 proj;
};

PS_Input main(VS_Input input) {
    PS_Input output = (PS_Input)0;

    output.position = mul(input.position, world);
    output.position = mul(output.position, view);
    output.position = mul(output.position, proj);

    output.pixelPosition = mul(input.position, world);

    output.texcoord = input.texcoord;

    output.normal = mul(input.normal, world);
    output.tangent = mul(input.tangent, world);

    return output;
}

PIXEL SHADER

struct PS_Input {
    float4 position: SV_POSITION;
    float2 texcoord: TEXCOORD;
    float3 normal:   NORMAL;
    float3 tangent:  TANGENT;

    float3 pixelPosition: POSITION;
};

cbuffer camera: register(b0) {
    float4 cameraPosition;
    float4 cameraDirection;
}

float PI = 3.14159265359f;

SamplerState textureSampler: register(s0);

Texture2D tex_normalMap:    register(t0);
Texture2D tex_heightMap:    register(t1);
Texture2D tex_albedoMap:    register(t2);
Texture2D tex_metallicMap:  register(t3);
Texture2D tex_roughnessMap: register(t4);
Texture2D tex_aoMap:        register(t5);

float3 calculateNormals(PS_Input input) {

    float3 normal = normalize(input.normal);
    float3 tangent = normalize(input.tangent);
    tangent = normalize(input.tangent - dot(input.tangent, input.normal) * input.normal);
    float3 bitangent = normalize(cross(normal, tangent));

    const float3x3 TBN = float3x3(tangent, bitangent, normal);

    float3 normalSample = tex_normalMap.Sample(textureSampler, input.texcoord).rgb;
    normalSample.x = 2.0f * normalSample.r - 1.0f;
    normalSample.y = -2.0f * normalSample.g + 1.0f;
    normalSample.z = normalSample.b;
    normal = mul(normalSample, TBN);

    return normal;
}

float3 fresnelSchlick(float cosTheta, float3 F0) {
    if (cosTheta > 1.0f) {
        cosTheta = 1.0f;
    }
    float p = pow(1.0f - cosTheta, 5.0f);
    return F0 + (1.0f - F0) * p;
}

float DistributionGGX(float3 N, float3 H, float roughness) {
    float a = roughness * roughness;
    float a2 = a * a;
    float NdotH = max(dot(N, H), 0.0f);
    float NdotH2 = NdotH * NdotH;

    float num = a2;
    float denom = (NdotH2 * (a2 - 1.0f) + 1.0f);
    denom = PI * denom * denom;

    return num / denom;
}

float GeometrySchlickGGX(float NdotV, float roughness) {
    float r = (roughness + 1.0f);
    float k = (r * r) / 8.0f;

    float num = NdotV;
    float denom = NdotV * (1.0f - k) + k;

    return num / denom;
}

float GeometrySmith(float3 N, float3 V, float3 L, float roughness) {
    float NdotV = max(dot(N, V), 0.0f);
    float NdotL = max(dot(N, L), 0.0f);
    float ggx2 = GeometrySchlickGGX(NdotV, roughness);
    float ggx1 = GeometrySchlickGGX(NdotL, roughness);

    return ggx1 * ggx2;
}

float3 getPBRLighting(PS_Input input) {
    float3 color = float3(0.0f, 0.0f, 0.0f);
    float3 albedoSample = pow(tex_albedoMap.Sample(textureSampler, input.texcoord).rgb, 2.2333f);
    float metallicSample = tex_metallicMap.Sample(textureSampler, input.texcoord).r;
    float roughnessSample = tex_roughnessMap.Sample(textureSampler, input.texcoord).r;
    float aoSample = tex_aoMap.Sample(textureSampler, input.texcoord).r;

    float3 N = normalize(input.normal);
    float3 V = normalize(cameraPosition - input.pixelPosition);

    float3 F0 = float3(0.04f, 0.04f, 0.04f);
    F0 = lerp(F0, albedoSample, metallicSample);

    float3 lightPositions[6] = {
        float3(-12.0f, 0.0f, -3.0f),
        float3( -6.0f, 0.0f, -3.0f),
        float3(  0.0f, 0.0f, -3.0f),
        float3(  6.0f, 0.0f, -3.0f),
        float3( 12.0f, 0.0f, -3.0f),
        float3( 18.0f, 0.0f, -3.0f),
    };
    float3 lightColor = float3(1.0f, 1.0f, 1.0f);

    // reflectance equation
    float3 Lo = float3(0.0f, 0.0f, 0.0f);
    for (int i = 0; i < 6; i++) {
        // calculate per-light radiance
        float3 L = normalize(lightPositions[i] - input.pixelPosition);
        float3 H = normalize(V + L);
        float distance = length(lightPositions[i] - input.pixelPosition);
        float attenuation = 1.0f / (distance * distance);
        float3 radiance = lightColor * attenuation;

        // cook-torrance brdf
        float NDF = DistributionGGX(N, H, roughnessSample);
        float G = GeometrySmith(N, V, L, roughnessSample);
        float3 F = fresnelSchlick(max(dot(H, V), 0.0f), F0);

        float3 kS = F;
        float3 kD = float3(1.0f, 1.0f, 1.0f) - kS;
        kD *= 1.0f - metallicSample;

        float3 numerator = NDF * G * F;
        float denominator = 4.0f * max(dot(N, V), 0.0f) * max(dot(N, L), 0.0f) + 0.0001f;
        float3 specular = numerator / denominator;

        // add to outgoing radiance Lo
        float NdotL = max(dot(N, L), 0.0f);
        Lo += (kD * albedoSample / PI + specular) * radiance * NdotL;
    }

    float3 ambient = float3(0.01f, 0.01f, 0.01f) * albedoSample * aoSample;
    color = ambient + Lo;
    return color;
}

float4 main(PS_Input input) : SV_TARGET {
    input.normal = calculateNormals(input);

    float3 color = getPBRLighting(input);

    // HDR tone mapping and gamma correction
    float exposure = 10.0f;
    float gamma = 1.0f;
    float3 toneMap = float3(1.0f, 1.0f, 1.0f) - exp((-color) * exposure);
    float3 toneMappedColor = pow(toneMap, float3(gamma, gamma, gamma));

    return float4(toneMappedColor, 1.0f);
}

Everything always compiles. I can not figure out for the life of me why the constant buffers aren't functioning properly.

Result with m_cbPostProcess included: https://gyazo.com/22bc8169fc5e3e772f4dafac2a78e4ed

Result commented out: https://gyazo.com/07359dd66e1d4a9dce55677b046c657d

I know I've included a lot of code here, but I figured more is better than less. Any help or suggestions are appreciated.


Solution

  • If a variable is not declared static const, it is automatically embedded in a "hidden" constant buffer (which is internally called $Globals) (and it will use the first available slot).

    So if I take this code :

    cbuffer cb_color : register(b0)
    {
        float4 color;
    }
    
    float myValue = 12.0f;
    
    float4 PS(float4 position_screen : SV_Position) : SV_Target
    {
        return color*myValue;
    };
    

    is equivalent to

    cbuffer cb_color : register(b0)
    {
        float4 color;
    }
    
    cbuffer cb_globals : register(b1)
    {
        float myValue = 12.0f;
    }
    
    float4 PS(float4 position_screen : SV_Position) : SV_Target
    {
        return color*myValue;
    };
    

    here it uses b1 as b0 is already explicitly used.

    please also note that any variable not declared in a constant buffer will be merged into a single buffer:

    cbuffer cb_color : register(b0)
    {
        float4 color;
    }
    
    float myValue = 12.0f;
    float4 myOtherValue;
    
    float4 PS(float4 position_screen : SV_Position) : SV_Target
    {
        return color*myValue*myOtherValue;
    };
    

    will be equivalent to :

    cbuffer cb_color : register(b0)
    {
        float4 color;
    }
    
    cbuffer cb_globals : register(b1)
    {
       float myValue = 12.0f;
       float3 pad0; //cbuffer members alignment rules
       float4 myOtherValue;
    }
    
    float4 PS(float4 position_screen : SV_Position) : SV_Target
    {
        return color*myValue*myOtherValue;
    };