OpenGL.Shader: 12-Shadow implementation-Solve shadow distortion

 OpenGL.Shader: 12-Shadow implementation-Solve shadow distortion

Immediately after the above, how to solve the problem of shadow distortion? These problems are actually unavoidable. Modern technology can only optimize the effect as much as possible to achieve the fake effect. First, return to the depth texture function renderDepthFBO, the floor LandShadow can actually not participate in the depth test of shadow occlusion, thus saving a large part of the occlusion test.

    depthFBO.begin();
    {
        glEnable(GL_DEPTH_TEST);
        glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
        //glEnable(GL_CULL_FACE);
        //glCullFace(GL_FRONT);
        // 地板不参与阴影遮挡的深度测试 避免造成阴影失真
        //landShadow.render(mLightProjectionMatrix,mLightViewMatrix,
        //                  mLightPosition,
        //                  mLightProjectionMatrix,mLightViewMatrix);
        cubeShadow.render(mLightProjectionMatrix,mLightViewMatrix,
                          mLightPosition,
                          mLightProjectionMatrix,mLightViewMatrix);
        //glCullFace(GL_BACK);
        //glDisable(GL_CULL_FACE);
    }
    depthFBO.end();

After logging off the depth test of the floor, the effect is probably like this. But there is still distortion on the cube, now we have to carefully understand why there is shadow distortion

The shadow map is limited by the resolution. When the light source is far away, multiple fragments may be sampled from the same value of the depth map. Each slope of the image represents a single texel of the depth map. As you can see, multiple fragments are sampled from the same depth value.

Although it is not a problem most of the time, when the light source faces the surface at an angle, there will be a problem. In this case, the depth map is also rendered from an angle. Multiple fragments will be sampled from the depth texels of the same slope, some on the floor and some under the floor; in this way, the shadows we get are different. Because of this, some fragments are considered to be in shadow and some are absent, resulting in the striped pattern in the picture. 

We can use called shadow offset techniques (shadow bias) to solve this problem, we simply offset a depth (or depth maps) surface applications, such fragments would not be wrong to think that the surface of the Down.

After using the offset, all the sampling points get a depth value smaller than the surface depth, so that the entire surface is correctly illuminated without any shadows. We can implement this offset in the shader like this:

float bias = 0.0005;
float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;

Choosing the correct offset value requires some slight adjustments like this in different scenarios, but in most cases, it is actually a problem of increasing the offset until all distortion is removed.

After adjustment, the effect is roughly as follows:

But are there three "disharmonious" shadows on the left, right, and back of the floor?

This is because the light has an area, beyond which it becomes a shadow; this area actually represents the size of the depth map, which is projected onto the floor. The reason for this is that we set the surround mode of the depth map to GL_REPEAT when we created the FBO.

We would rather let the depth range of all coordinates beyond the depth map be 1.0, so that the coordinates beyond will never be in the shadow. We can set the texture wrapping option of the depth map to GL_CLAMP_TO_EDGE:

    void    createDepthTexture()
    {
        glGenTextures(1, &_depthTexId);
        glBindTexture(GL_TEXTURE_2D, _depthTexId);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, _width, _height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, 0);
    }

But this can only solve the shadows on the left and right sides. As for the shadows on the back, it is caused by another reason.

The coordinates there are beyond the far plane of the orthogonal viewing cone of light. You can see that this black area always appears very far from the frustum of the light source. When a point is farther than the far plane of the light, the z coordinate of its projection coordinate is greater than 1.0. In this case, the GL_CLAMP_TO_EDGE wrapping method does not work, because we compare the z element of the coordinate with the value of the depth map; it always returns true for z greater than 1.0. Solving this problem is also very simple, as long as the z coordinate of the projection vector is greater than 1.0, we will force the shadow value to 0.0:

float ShadowCalculation(vec4 fragPosLightSpace)
{
        [...]
        if(projCoords.z > 1.0) shadow = 0.0;
        return shadow;
}

This means that only the projected fragment coordinates within the depth map range will have shadows, so anything out of range will have no shadows.

This effect should be the most handsome one, see if the effect is like this?

In fact, we can also make the position of the light source a dynamic effect, which is very close to the effect experience of the game.

Reference code:   ShadowFBORender.cpp in https://github.com/MrZhaozhirong/NativeCppApp project CubeShdow.hpp/LandShadow.hpp IlluminateWithShadow.hpp FramebufferObject.hpp

Guess you like

Origin blog.csdn.net/a360940265a/article/details/102523814