Point light shadowmapping

Started by
2 comments, last by KaiserJohan 9 years, 8 months ago

I'm having some issues getting shadowmapping to work for point lights. I've done this in OpenGL4, and the shader code seems to translate pretty much 1-to-1. From debugging the depth stencil views after the shadowpass in Visual Studio, it seems the depth textures are simply empty.

Here's the overall point light pass:


    void DX11PointLightPass::Render(ID3D11DeviceContext* context, const RenderQueue& renderQueue, std::vector<DX11MeshPtr>& meshes, ID3D11DepthStencilView* gbufferDSV, const RenderableLighting::PointLight& pointLight)
    {
        // preserve current state
        ID3D11RasterizerStatePtr prevRasterizerState = nullptr;
        ID3D11DepthStencilStatePtr prevDepthStencilState = nullptr;
        D3D11_VIEWPORT prevViewport;
        uint32_t numViewports = 1;
        context->RSGetState(&prevRasterizerState);
        context->OMGetDepthStencilState(&prevDepthStencilState, 0);
        context->RSGetViewports(&numViewports, &prevViewport);

        //
        // shadow pass
        //
        context->VSSetShader(mNullVertexShader, NULL, NULL);
        context->PSSetShader(NULL, NULL, NULL);

        context->OMSetDepthStencilState(NULL, 0);       // defaults to depth rendering/testing
        context->RSSetState(mRSCullFront);
        context->RSSetViewports(1, &mShadowPassViewport);
        for (uint32_t face = 0; face < TEXTURE_CUBE_NUM_FACES; face++)
        {
            context->OMSetRenderTargets(0, NULL, mShadowmapView[face]);
              context->ClearDepthStencilView(mShadowmapView[face], D3D11_CLEAR_DEPTH, 1.0f, 0);
            Mat4 lightViewMatrix = glm::lookAt(pointLight.mLightPosition, pointLight.mLightPosition + CUBEMAP_DIRECTION_VECTORS[face], CUBEMAP_UP_VECTORS[face]);
            Mat4 lightProjMatrix = PerspectiveMatrixFov(90.0f, 1.0f, Z_NEAR, Z_FAR);
            DepthPass(context, renderQueue, meshes, lightProjMatrix * lightViewMatrix);   // draws all geometry using light view-projection matrix
        }

        //
        // stencil pass
        //

        // restore rendering to the backbuffer
        mBackbuffer.BindForShadingStage(context, gbufferDSV);       // refactor the call?

        context->OMSetDepthStencilState(mDSSStencilPass, 0);
        context->RSSetState(mRSNoCulling);

        // restore screen viewport
        context->RSSetViewports(1, &prevViewport);

        mNullCBuffer.SetData(NullCBuffer(pointLight.mWVPMatrix), context, 0);
        mSphereMesh.Draw(context);

        //
        // shading pass
        //
        context->OMSetDepthStencilState(mDSSShadingPass, 0);
        context->RSSetState(mRSCullFront);
        context->PSSetShaderResources(DX11Texture::SHADER_TEXTURE_SLOT_DEPTH, 1, &mShadowmapSRV.p);

        context->VSSetShader(mShadingVertexShader, NULL, NULL);
        context->PSSetShader(mPixelShader, NULL, NULL);

        mPointLightCBuffer.SetData(PointLightCBuffer(pointLight.mWVPMatrix, pointLight.mLightColor, Vec4(pointLight.mLightPosition, 1.0), pointLight.mLightIntensity, pointLight.mMaxDistance), context, 0);
        mSphereMesh.Draw(context);

        // restore state
        context->PSSetShaderResources(DX11Texture::SHADER_TEXTURE_SLOT_DEPTH, 1, &gNullSrv);
        context->OMSetDepthStencilState(prevDepthStencilState, 0);
        context->RSSetState(prevRasterizerState);
    }

Some of the constants used:


    const uint32_t TEXTURE_CUBE_NUM_FACES = 6;

    const Vec3 CUBEMAP_DIRECTION_VECTORS[TEXTURE_CUBE_NUM_FACES] = { Vec3(1.0f, 0.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f),
                                                                     Vec3(0.0f, -1.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f), Vec3(0.0f, 0.0f, -1.0f) };

    const Vec3 CUBEMAP_UP_VECTORS[TEXTURE_CUBE_NUM_FACES] = { Vec3(0.0f, -1.0f, 0.0f), Vec3(0.0f, -1.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f),
                                                              Vec3(0.0f, 0.0f, -1.0f), Vec3(0.0f, -1.0f, 0.0f), Vec3(0.0f, -1.0f, 0.0f) };

Here's the overall setup for the d3d resources:


        // rasterize for front-face culling due to light volumes
        D3D11_RASTERIZER_DESC rasterizerDesc;
        ZeroMemory(&rasterizerDesc, sizeof(D3D11_RASTERIZER_DESC));
        rasterizerDesc.FillMode = D3D11_FILL_SOLID;
        rasterizerDesc.CullMode = D3D11_CULL_FRONT;
        rasterizerDesc.FrontCounterClockwise = true;
        rasterizerDesc.DepthClipEnable = true;
        rasterizerDesc.ScissorEnable = false;
        rasterizerDesc.MultisampleEnable = false;
        rasterizerDesc.AntialiasedLineEnable = false;
        DXCALL(device->CreateRasterizerState(&rasterizerDesc, &mRSCullFront));

        // rasterize with no culling for stencil pass
        ZeroMemory(&rasterizerDesc, sizeof(D3D11_RASTERIZER_DESC));
        rasterizerDesc.FillMode = D3D11_FILL_SOLID;
        rasterizerDesc.CullMode = D3D11_CULL_NONE;
        rasterizerDesc.FrontCounterClockwise = true;
        rasterizerDesc.DepthClipEnable = true;
        rasterizerDesc.ScissorEnable = false;
        rasterizerDesc.MultisampleEnable = false;
        rasterizerDesc.AntialiasedLineEnable = false;
        DXCALL(device->CreateRasterizerState(&rasterizerDesc, &mRSNoCulling));

        // DSS stencil pass
        D3D11_DEPTH_STENCIL_DESC depthStencilDesc;
        ZeroMemory(&depthStencilDesc, sizeof(D3D11_DEPTH_STENCIL_DESC));
        depthStencilDesc.DepthEnable = true;
        depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;
        depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;
        depthStencilDesc.StencilEnable = true;
        depthStencilDesc.StencilReadMask = 0;
        depthStencilDesc.StencilWriteMask = 0;
        depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
        depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INVERT;
        depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
        depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
        depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
        depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_INVERT;
        depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
        depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;
        DXCALL(device->CreateDepthStencilState(&depthStencilDesc, &mDSSStencilPass));

        // DSS shading pass
        ZeroMemory(&depthStencilDesc, sizeof(D3D11_DEPTH_STENCIL_DESC));
        depthStencilDesc.DepthEnable = false;
        depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ZERO;
        depthStencilDesc.DepthFunc = D3D11_COMPARISON_ALWAYS;
        depthStencilDesc.StencilEnable = true;
        depthStencilDesc.StencilReadMask = 0xFF;
        depthStencilDesc.StencilWriteMask = 0xFF;
        depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
        depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
        depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
        depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_EQUAL;
        depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP;
        depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_KEEP;
        depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP;
        depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_EQUAL;
        DXCALL(device->CreateDepthStencilState(&depthStencilDesc, &mDSSShadingPass));

        // create shadowmap texture/view/srv
        D3D11_TEXTURE2D_DESC depthBufferDesc;
        ZeroMemory(&depthBufferDesc, sizeof(D3D11_TEXTURE2D_DESC));
        depthBufferDesc.ArraySize = TEXTURE_CUBE_NUM_FACES;
        depthBufferDesc.Format = DXGI_FORMAT_R32_TYPELESS;
        depthBufferDesc.Width = shadowmapSize;
        depthBufferDesc.Height = shadowmapSize;
        depthBufferDesc.MipLevels = 1;
        depthBufferDesc.SampleDesc.Count = 1;
        depthBufferDesc.Usage = D3D11_USAGE_DEFAULT;
        depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;
        depthBufferDesc.MiscFlags = D3D11_RESOURCE_MISC_TEXTURECUBE;
        DXCALL(device->CreateTexture2D(&depthBufferDesc, NULL, &mShadowmapTexture));

        D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
        ZeroMemory(&dsvDesc, sizeof(D3D11_DEPTH_STENCIL_VIEW_DESC));
        dsvDesc.Format = DXGI_FORMAT_D32_FLOAT;
        dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
        dsvDesc.Texture2D.MipSlice = 0;
        //dsvDesc.Texture2DArray.ArraySize = 1;
        //dsvDesc.Texture2DArray.MipSlice = 0;
        for (uint32_t face = 0; face < TEXTURE_CUBE_NUM_FACES; face++)
        {
            //dsvDesc.Texture2DArray.FirstArraySlice = face;
            DXCALL(device->CreateDepthStencilView(mShadowmapTexture, &dsvDesc, &mShadowmapView[face]));
        }

        D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
        ZeroMemory(&srvDesc, sizeof(D3D11_SHADER_RESOURCE_VIEW_DESC));
        srvDesc.Format = DXGI_FORMAT_R32_FLOAT;
        srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURECUBE;
        srvDesc.TextureCube.MipLevels = 1;
        srvDesc.TextureCube.MostDetailedMip = 0;
        DXCALL(device->CreateShaderResourceView(mShadowmapTexture, &srvDesc, &mShadowmapSRV));

        // viewport used during shadow pass
        ZeroMemory(&mShadowPassViewport, sizeof(D3D11_VIEWPORT));
        mShadowPassViewport.TopLeftX = 0;
        mShadowPassViewport.TopLeftY = 0;
        mShadowPassViewport.Width = static_cast<float>(shadowmapSize);
        mShadowPassViewport.Height = static_cast<float>(shadowmapSize);
        mShadowPassViewport.MinDepth = 0.0f;
        mShadowPassViewport.MaxDepth = 1.0f;

        // shaders used
        DXCALL(device->CreateVertexShader(gPointLightVertexShader, sizeof(gPointLightVertexShader), NULL, &mShadingVertexShader));
        DXCALL(device->CreateVertexShader(gNullVertexShader, sizeof(gNullVertexShader), NULL, &mNullVertexShader));
        DXCALL(device->CreatePixelShader(gPointLightPixelShader, sizeof(gPointLightPixelShader), NULL, &mPixelShader));

The shadow and stencil pass has no pixel shader, only a vertex shader which renders geometry from lights point of view. The point light pixel shader looks like this:


cbuffer PointLightConstants : register(b0)
{
    float4x4 gLightWVPMatrix;
    float4 gLightColor;
    float4 gLightPosition;
    float gLightIntensity;
    float gMaxDistance;
};

Texture2D gPositionTexture : register(t0);
Texture2D gDiffuseTexture : register(t1);
Texture2D gNormalTexture : register(t2);
TextureCube gShadowmap : register(t3);
SamplerState gSampler : register(s0);


float VectorToDepthValue(float3 Vec)
{
    float3 AbsVec = abs(Vec);
    float LocalZcomp = max(AbsVec.x, max(AbsVec.y, AbsVec.z));

    const float f = 100.0;      // TODO: TEMP - use constant buffer values!
    const float n = 0.1;
    float NormZComp = (f + n) / (f - n) - (2 * f * n) / (f - n) / LocalZcomp;

    return (NormZComp + 1.0) * 0.5;
}

float4 ps_main(float4 position : SV_Position) : SV_Target0
{
    float4 worldPosition = gPositionTexture[uint2(position.xy)];
    float4 diffuse = gDiffuseTexture[uint2(position.xy)];
    float3 normal = (float3)gNormalTexture[uint2(position.xy)];
    float3 positionDiff = (float3)(gLightPosition - worldPosition);

    // shadowmapping
    float3 cubemapDir = (float3)(worldPosition - gLightPosition);
    float storedDepth = gShadowmap.Sample(gSampler, cubemapDir).r;
    float visibility = 0.0;
    if (storedDepth + 0.0001 > VectorToDepthValue(cubemapDir))
        visibility = 1.0;

    // light attenuation
    float distance = length(positionDiff);
    float attenuation = clamp(1.0 - distance * distance * (1 / (gMaxDistance * gMaxDistance)), 0.0, 1.0);
    attenuation *= attenuation;

    float3 lightDir = normalize(positionDiff);
    float angleNormal = clamp(dot(normalize(normal), lightDir), 0, 1);

    return visibility * diffuse * angleNormal * attenuation * gLightIntensity * gLightColor;
}

Again, I dont believe the issue is in the pixel shader, but rather in the shadow pass... I might be concerned the somehow the texture cube/depth stencil views or alike are setup improperly as, atleast when I debug in visual studio, the texture cube seems to be completely black on all 6 sides. Any ideas?

EDIT: worth mentioning, Im using frontface = CCW and glm math library (column major, right handed)

EDIT2: actually, the first side is filled white; the rest 5 are black

Advertisement

It looks like you're not setting up your depth stencil views correctly. If you want 6 DSV's where each one targets a single face of your cube map, then you should use D3D11_DSV_DIMENSION_TEXTURE2DARRAY and re-enable the code that you commented out for specifying the array slice.

True; the following works fine:


        D3D11_DEPTH_STENCIL_VIEW_DESC dsvDesc;
        ZeroMemory(&dsvDesc, sizeof(D3D11_DEPTH_STENCIL_VIEW_DESC));
        dsvDesc.Format = DXGI_FORMAT_D32_FLOAT;
        dsvDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY;
        //dsvDesc.Texture2D.MipSlice = 0;
        dsvDesc.Texture2DArray.ArraySize = 1;
        dsvDesc.Texture2DArray.MipSlice = 0;
        for (uint32_t face = 0; face < TEXTURE_CUBE_NUM_FACES; face++)
        {
            dsvDesc.Texture2DArray.FirstArraySlice = face;
            DXCALL(device->CreateDepthStencilView(mShadowmapTexture, &dsvDesc, &mShadowmapView.at(face)));
        }

The depth gets rendered to all of them, the problem I have now is I believe the up-vectors used must be wrong. It's funny, because I've had the exact same problem in OpenGL, which I solved by flipping some of the up-vectors, but now I can't reproduce the solution.

These are the up-vectors I use in OpenGL which works fine:


    const Vec3 CUBEMAP_UP_VECTORS[DX11PointLightPass::TEXTURE_CUBE_NUM_FACES] = { Vec3(0.0f, -1.0f, 0.0f), Vec3(0.0f, -1.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f),
                                                                                  Vec3(0.0f, 0.0f, -1.0f), Vec3(0.0f, -1.0f, 0.0f), Vec3(0.0f, -1.0f, 0.0f) };

Using the same for directx gives the following (which I had in OpenGL before I changed the up vectors) - see attachments

"pic1.png" shows light going away from cube (towards +X) and that the -Y cube face is shadowed correctly, but the -X face is not shadowed at all (red rectangle approximates where it should be shadowed)

"pic2.png" shows light going away from cube (-Z - i'm using right handed coordinates with GLM) and the -Y cube face should be MIRRORED from where it is currently, and the +Z (approximated by red rectangle) is not shadowed at all

The irony is I had the EXACT problem in OpenGL and fixed it with altering the up-vectors, and I am using the same math library (GLM), same up-vectors, same light view-matrix generation and the same shader code as in OpenGL! But now, no combinations I've managed to get it right. The only difference is the projection-matrix. OpenGL goes from [-1, 1] while DirectX goes from [0, 1], so rather than using glms projection-matrix function I am using this:


    // right handed, so it fits with rest of glm
    Mat4 PerspectiveMatrixFov(const float fovDegrees, const float aspectRatio, const float zNear, const float zFar)
    {
        const float fovRadians = glm::radians(fovDegrees);
        const float yScale = 1 / tan(fovRadians / 2.0f);
        const float xScale = yScale / aspectRatio;

        Mat4 ret(0.0f);

        ret[0][0] = xScale;
        ret[1][1] = yScale;
        ret[2][2] = zFar / (zNear - zFar);
        ret[2][3] = -1;
        ret[3][2] = zNear * zFar / (zNear - zFar);

        return ret;
    }

I am positive the look-up vector in the pixel shader is correct. I've sampled each side of the cube (+x, -x, +z, -z) using Visual Studio graphics debugger and the look-up vector is legit. For reminder, here's the sampling of the cubemap:


    // shadowmapping
    float3 cubemapDir = (float3)(worldPosition - gLightPosition);
    float storedDepth = gShadowmap.Sample(gSampler, cubemapDir).r;
    float visibility = 0.0;
    if (storedDepth + 0.0001 > VectorToDepthValue(cubemapDir))
        visibility = 1.0;

Thought I had got rid of this issue once and for all :| any ideas?

Fixed it... in case it helps anyone else:

The TextureCube follows Left-hand coordinate system in DirectX, while it is right-handed in OpenGL. I am using right-handed matrices due to GLM, so It required flipping the +/-Z tex cube faces:



    const Vec3 CUBEMAP_DIRECTION_VECTORS[DX11PointLightPass::TEXTURE_CUBE_NUM_FACES] = { Vec3(1.0f, 0.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f),
                                                                                         Vec3(0.0f, -1.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(0.0f, 0.0f, 1.0f) };

    const Vec3 CUBEMAP_UP_VECTORS[DX11PointLightPass::TEXTURE_CUBE_NUM_FACES] = { Vec3(0.0f, 1.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f),
                                                                                  Vec3(0.0f, 0.0f, -1.0f), Vec3(0.0f, 1.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f) };

Likewise, the projection matrix is different from OpenGL to DirectX, as the range of NDC is different. The old VectorToDepth function I used in OpenGL I altered to this:


float VectorToDepthValue(float3 Vec)
{
    float3 AbsVec = abs(Vec);
    float LocalZcomp = max(AbsVec.x, max(AbsVec.y, AbsVec.z));

    const float f = 100.0;
    const float n = 0.1;

    float NormZComp = -(f / (n - f) - (n * f) / (n - f) / LocalZcomp);

    return NormZComp;
}

ALTHOUGH I had to add an extra instruction to the pixel shader:


    float3 cubemapDir = (float3)(worldPosition - gLightPosition);
    cubemapDir.z = -cubemapDir.z;       // TODO: any way to remove this extra instruction?
    float storedDepth = gShadowmap.Sample(gShadowmapSampler, cubemapDir).r;
    float visibility = 0.0;
    if (storedDepth + 0.0001 > VectorToDepthValue(cubemapDir))
        visibility = 1.0;

Any input on how to optimize this away would be awesome

This topic is closed to new replies.

Advertisement