Search code examples
shadermonogamehlsllightshadow-mapping

Monogame and HLSL - Point Light Shadow Mapping artifacts


I am making a game in Monogame. I am using a forward rendering and I've decided to write a shader that will callculate lightning using Blinn-phong shading model. I have implemented this model to work with three types of Light - directional Light, point light and spot light. After this, there was time to add shadows to our game. I've decided to use Shadow Mapping with Percentage Closer Filtering technique. I've implemented it, but unfortunatelly, there is a problem with casting shadows for point lights. I am rendering my scene to a cube map from the point light perspective (six ViewProjectionMatrices, each facing one direction), which then I compare to currently rendered object. I am facing two problems:

  1. There are some strange artifacts near the flat surface's edges. There is a rounded part of surface, that is not being shadowed, despite being covered by another surface. A screenshot with the issue: Point Light Shadow Mapping Issue #1 - note rounded light artifacts (this grey sphere is the position of the light)

  2. For this shadowing to work, I have to have some kind of light boundaries object, which is positioned the most further from the point light, where it's seen by the light's frustum. If I won't draw this object, It will cause some kind of reversed shadowing - I will see light on the surface, in the position of another object that is BEHIND this surface. A screenshot for better understanding: Point Light Shadow Mapping Issue #2 - no light boundaries - scene is not lightened, the character, which shape is visible, is located behind the wall (again, grey sphere is the position of the light

Here is my HLSL code (I will replace parts of code unrelated directly to point lights with just short comment + "here"):

#define MAX_DIRECTIONAL_LIGHTS 3
#define MAX_POINT_LIGHTS 4
#define MAX_SPOT_LIGHTS 4

matrix worldMatrix;
matrix viewProjectionMatrix;
matrix currentLightVievProjectionMatrix;
float4 currentLightPosition;

float4 cameraPosition;

texture diffuseTexture;
texture normalTexture;
texture specularTexture;
texture opacityTexture;

float4 globalAmbient;

//Directional Lights related variables here

int currentPointLightsNumber;
float4 pointLightPosition[MAX_POINT_LIGHTS];
float4 pointLightAmbientColor[MAX_POINT_LIGHTS];
float4 pointLightDiffuseColor[MAX_POINT_LIGHTS];
float4 pointLightSpecularColor[MAX_POINT_LIGHTS];
float pointLightRadius[MAX_POINT_LIGHTS];
float pointLightTexelSize[MAX_POINT_LIGHTS];
matrix pointLightViewProjection0;
matrix pointLightViewProjection1;
matrix pointLightViewProjection2;
matrix pointLightViewProjection3;
texture pointLightShadowMap0;
texture pointLightShadowMap1;
texture pointLightShadowMap2;
texture pointLightShadowMap3;

//Spot lights related variables here

float materialShininessFactor;
float DepthBias = float(0.0004F);

sampler2D DiffuseMapSampler = sampler_state
{
    Texture = <diffuseTexture>;
    MinFilter = Anisotropic;
    MagFilter = Linear;
    MipFilter = Linear;
    AddressU = wrap;
    AddressV = wrap;
    MaxAnisotropy = 16;
};

sampler2D NormalMapSampler = sampler_state
{
    Texture = <normalTexture>;
    MinFilter = Anisotropic;
    MagFilter = Linear;
    MipFilter = Linear;
    AddressU = wrap;
    AddressV = wrap;
    MaxAnisotropy = 4;
};

sampler2D SecularMapSampler = sampler_state
{
    Texture = <specularTexture>;
    MinFilter = Linear;
    MagFilter = Linear;
    MipFilter = Linear;
    AddressU = wrap;
    AddressV = wrap;
};

sampler2D OpacityMapSampler = sampler_state
{
    Texture = <opacityTexture>;
    MinFilter = Linear;
    MagFilter = Linear;
    MipFilter = Linear;
    AddressU = wrap;
    AddressV = wrap;
};

//Directional light shadow map samplers here

samplerCUBE PointLightShadowMapSampler0 = sampler_state
{
    Texture = <pointLightShadowMap0>;
    MinFilter = Point;
    MagFilter = Point;
    MipFilter = None;
    AddressU = clamp;
    AddressV = clamp;
};

samplerCUBE PointLightShadowMapSampler1 = sampler_state
{
    Texture = <pointLightShadowMap1>;
    MinFilter = Point;
    MagFilter = Point;
    MipFilter = None;
    AddressU = clamp;
    AddressV = clamp;
};

samplerCUBE PointLightShadowMapSampler2 = sampler_state
{
    Texture = <pointLightShadowMap2>;
    MinFilter = Point;
    MagFilter = Point;
    MipFilter = None;
    AddressU = clamp;
    AddressV = clamp;
};

samplerCUBE PointLightShadowMapSampler3 = sampler_state
{
    Texture = <pointLightShadowMap3>;
    MinFilter = Point;
    MagFilter = Point;
    MipFilter = None;
    AddressU = clamp;
    AddressV = clamp;
};

//Spot light shadow map samplers here

struct BlinnPhongVertexShaderInput
{
    float4 position : POSITION;
    float2 textureCoordinates : TEXCOORD;
    float3 normal : NORMAL;
    float3 tangent : TANGENT;
    float3 binormal : BINORMAL;
};

struct BlinnPhongPixelShaderInput
{
    float4 position : SV_POSITION;
    float4 worldPosition : TEXCOORD0;
    float2 textureCoordinates : TEXCOORD1;
    float4 viewDirection : TEXCOORD2;
    float3 normal : TEXCOORD3;
    float3 tangent : TANGENT;
    float3 binormal : BINORMAL;
};

struct CreateShadowMapPixelShaderInput
{
    float4 Position : POSITION;
    float Depth : TEXCOORD0;
};

//Vertex shader for directional and spot lights here

CreateShadowMapPixelShaderInput CreateShadowMapForPointLightVertexShaderFunction(float4 Position : POSITION)
{
    CreateShadowMapPixelShaderInput OUT;
    OUT.Position = mul(Position, worldMatrix);
    OUT.Depth = length(OUT.Position.xyz - currentLightPosition.xyz);
    OUT.Position = mul(OUT.Position, currentLightVievProjectionMatrix);
    return OUT;
}

float4 CreateShadowMapPixelShaderFunction(CreateShadowMapPixelShaderInput input) : COLOR
{
    return float4(input.Depth, 0.0F, 0.0F, 0.0F);
}

BlinnPhongPixelShaderInput BlinnPhongVertexShaderFunction(BlinnPhongVertexShaderInput input)
{
    BlinnPhongPixelShaderInput output;

    float4 worldPosition = mul(input.position, worldMatrix);
    output.position = mul(worldPosition, viewProjectionMatrix);
    output.worldPosition = worldPosition;

    output.textureCoordinates = input.textureCoordinates;
    output.viewDirection = cameraPosition - output.worldPosition;

    output.normal = mul(input.normal, (float3x3)worldMatrix);
    output.tangent = mul(input.tangent, (float3x3)worldMatrix);
    output.binormal = mul(input.binormal, (float3x3)worldMatrix);

    return output;
}

//ShadowMapLookups for directional and spot lights here

float PointLightShadowMapLookup(samplerCUBE shadowMap, float3 shadowTexCoord, float3 offset, float ourDepth, float texelSize)
{
    return (texCUBE(shadowMap, shadowTexCoord + offset * texelSize).r < ourDepth) ? 0.1f : 1.0f;
}

float4 BlinnPhongPixelShaderFunction(BlinnPhongPixelShaderInput input) : COLOR0
{
    float4 color = globalAmbient;
    float4 specularColor = float4(0.0F, 0.0F, 0.0F, 0.0F);

    float3 V = normalize(input.viewDirection.xyz);

    float3 L;
    float3 H;
    float NDotL;
    float NDotH;

    float attenuation;
    float power;

    float4 normalMap = tex2D(NormalMapSampler, input.textureCoordinates);
    normalMap = (normalMap * 2.0F) - 1.0F;

    float3 N = normalize((normalMap.x * normalize(input.tangent)) + (normalMap.y * normalize(input.binormal)) + (normalMap.z * normalize(input.normal)));

    float4 specularMap;
    specularMap = tex2D(SecularMapSampler, input.textureCoordinates);

    float4 lightingPosition;
    float2 ShadowTexCoord;
    float3 PointLightShadowTexCoord;
    float ourdepth;
    float shadowOcclusion;

    //Directional lights lightning callculations here

    for (int j = 0; j < currentPointLightsNumber; ++j)
    {
        L = (pointLightPosition[j].xyz - input.worldPosition.xyz) / pointLightRadius[j];
        attenuation = saturate(1.0F - dot(L, L));

        L = normalize(L);
        H = normalize(L + V);

        NDotL = saturate(dot(N, L));
        NDotH = saturate(dot(N, H));

        power = (NDotL == 0.0F) ? 0.0F : saturate(pow(NDotH, materialShininessFactor / specularMap.a));

        ourdepth = length((pointLightPosition[j].xyz - input.worldPosition.xyz) * 0.98);

        PointLightShadowTexCoord = -normalize(pointLightPosition[j].xyz - input.worldPosition.xyz);

        shadowOcclusion = 0.0F;

        if (j == 0)
        {
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 0.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 0.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 0.0f, 0.0f), ourdepth, pointLightTexelSize[j]);

            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 1.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 1.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 1.0f, 0.0f), ourdepth, pointLightTexelSize[j]);

            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 2.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 2.0f, 0.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 2.0f, 0.0f), ourdepth, pointLightTexelSize[j]);

            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 0.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 0.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 0.0f, 1.0f), ourdepth, pointLightTexelSize[j]);

            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 1.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 1.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 1.0f, 1.0f), ourdepth, pointLightTexelSize[j]);

            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 2.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 2.0f, 1.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 2.0f, 1.0f), ourdepth, pointLightTexelSize[j]);

            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 0.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 0.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 0.0f, 2.0f), ourdepth, pointLightTexelSize[j]);

            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 1.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 1.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 1.0f, 2.0f), ourdepth, pointLightTexelSize[j]);

            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(0.0f, 2.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(1.0f, 2.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
            shadowOcclusion += PointLightShadowMapLookup(PointLightShadowMapSampler0, PointLightShadowTexCoord, float3(2.0f, 2.0f, 2.0f), ourdepth, pointLightTexelSize[j]);
        }
        else if (j == 1)
        {
            //Same code for second point light
        }
        else if (j == 2)
        {
            //Same code for third point light
        }
        else
        {
            //Same code for fourth point light
        }

        shadowOcclusion /= 27.0F;

        color += (pointLightAmbientColor[j] * attenuation) + (pointLightDiffuseColor[j] * NDotL * attenuation * shadowOcclusion);
        specularColor += (pointLightSpecularColor[j] * power * attenuation * specularMap * shadowOcclusion);
    }

    //Spot lights lightning callculations here

    color = saturate(color * tex2D(DiffuseMapSampler, input.textureCoordinates) + specularColor);
    color.a = (float1)tex2D(OpacityMapSampler, input.textureCoordinates);
    return color;
}

//technique for directional and spot lights shadow mapping here

technique CreateShadowMapForPointLight
{
    pass Pass1
    {
        VertexShader = compile vs_4_0 CreateShadowMapForPointLightVertexShaderFunction();
        PixelShader = compile ps_4_0 CreateShadowMapPixelShaderFunction();
    }
}

technique BlinnPhong
{
    pass Pass1
    {
        VertexShader = compile vs_4_0 BlinnPhongVertexShaderFunction();
        PixelShader = compile ps_4_0 BlinnPhongPixelShaderFunction();
    }
}

I know it looks bad, but let me explain. I was not able to store light vievProjection matrices in an array, because their values were net updating in runtime, so i had to split them to single matrices for each light. As for the textures, there was a warning that shader compiler is forcing loops to unroll, so I just wanted to be sure that everything will be fine, so I split them too. Don't know if the Matrices issue is Monogame or HLSL think.

Going back to shadow issues, is there any issue with the hlsl i presented? On the possible need, I will provide code for my clases responsible for shadow map rendering.

Here is how my cube map and vievProjection matrix looks like:

RenderTargetCube ShadowMapRenderTarget = new RenderTargetCube(GameObject.Scene.SceneManager.GameEngine.GraphicsDevice,
                            1024,
                            false,
                            SurfaceFormat.Single,
                            DepthFormat.Depth24);
ProjectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver2, 1.0F, 0.1F, Radius) * Matrix.CreateScale(-1, 1, 1);

public void CreateViewMatrix(Vector3 targetVector)
{
     Vector3 upVector;
     if (targetVector.Y > 0)
     {
         upVector = Vector3.Forward;
     }
     else if (targetVector.Y < 0)
     {
         upVector = Vector3.Backward;
     }
     else
     {
         upVector = Vector3.Up;
     }
     ViewMatrix = Matrix.CreateLookAt(GameObject.Transform.Position,GameObject.Transform.Position + targetVector, upVector);
}

public override void CreateViewProjectionMatrix()
{
    ViewProjectionFrustum.Matrix = ViewMatrix * ProjectionMatrix;
}

During the draw call:

        foreach (PointLight pointLight in PointLights)
        {
            foreach (CubeMapFace cubeMapFace in Enum.GetValues(typeof(CubeMapFace)))
            {
                pointLight.CreateViewMatrix(cubeMapFace.GetDirection());
                pointLight.CreateViewProjectionMatrix();
                SceneManager.GameEngine.GraphicsDevice.SetRenderTarget(pointLight.ShadowMapRenderTarget, cubeMapFace);
                SceneManager.GameEngine.GraphicsDevice.Clear(Color.White);
                foreach (DrawShadowMapDelegateType DrawComponent in ComponentsDrawShadowMapForPointLightMethods)
                {
                    DrawComponent(pointLight);
                }
            }
        }

Is there an easy way or simple explanation why does such thinks happen? Will there be way to fix it or will I be forced to try to implement dual-paraboloid shadow mapping? If so, is there any sample implementation in hlsl succesfully connected to monogame or xna?

Thank you in advance for any suggestions and for your time.


Solution

  • Have encountered the first problem when implementing shadow mapping by myself. The reason was me calculating the depth in the vertex shader, instead of fragment shader, while rendering shadow map. So if you have a polygon perpendicular to the light source every vertex will get the same depth. I solved it by using a varying vector(world fragment position) and then setting depth in fragment shader. Not the best solution, since its a bad practise for performance.