级联阴影贴图意外行为

时间:2019-03-30 01:51:35

标签: c++ opengl-es opengl-es-3.0 shadow-mapping

我正在实现级联阴影贴图技术,但结果出乎意料

enter image description here

首先,我初始化缓冲区和纹理:

glGenFramebuffers(1, &m_fbo);
glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
glGenTextures(NUM_CASCADES, m_shadowMap);
for (uint i = 0; i < NUM_CASCADES; i++) {
    glBindTexture(GL_TEXTURE_2D, m_shadowMap[i]);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32F, size, size, 0, GL_DEPTH_COMPONENT,
                 GL_FLOAT, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_EQUAL);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap[i], 0);
}
glDrawBuffers(1, GL_NONE);
glReadBuffer(GL_NONE);
glBindTexture(GL_TEXTURE_2D, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);

然后我渲染到深度缓冲区: 在这里,我为每个级联计算光投影视图矩阵,然后从光透视图渲染场景。

   glUseProgram(programID);
GLfloat minDistance = 0.0f;
GLfloat nearClip = camera->getProjection().getNear();
GLfloat farClip = camera->getProjection().getFar();

GLfloat cascadeSplits[NUM_CASCADES+1] = {nearClip,(farClip-nearClip)*0.08f,(farClip-nearClip)*0.2f,(farClip-nearClip)*0.6f,farClip};

for (unsigned int cascadeIterator = 0; cascadeIterator < NUM_CASCADES; ++cascadeIterator) {

    GLfloat prevSplitDistance =
            cascadeIterator == 0 ? minDistance : cascadeSplits[cascadeIterator - 1];
    GLfloat splitDistance = cascadeSplits[cascadeIterator];

    glm::vec3 frustumCornersWS[8] = {glm::vec3(-1.0f, 1.0f, -1.0f),
                                     glm::vec3(1.0f, 1.0f, -1.0f),
                                     glm::vec3(1.0f, -1.0f, -1.0f),
                                     glm::vec3(-1.0f, -1.0f, -1.0f),
                                     glm::vec3(-1.0f, 1.0f, 1.0f),
                                     glm::vec3(1.0f, 1.0f, 1.0f),
                                     glm::vec3(1.0f, -1.0f, 1.0f),
                                     glm::vec3(-1.0f, -1.0f, 1.0f),};

    glm::mat4 invViewProj = glm::inverse(
            camera->getProjection().getProjectionMatrix() * camera->getView().getViewMatrix());
    for (unsigned int i = 0; i < 8; ++i) {
        glm::vec4 inversePoint = invViewProj * glm::vec4(frustumCornersWS[i], 1.0f);
        frustumCornersWS[i] = glm::vec3(inversePoint / inversePoint.w);
    }

    for (unsigned int i = 0; i < 4; ++i) {
        glm::vec3 cornerRay = frustumCornersWS[i + 4] - frustumCornersWS[i];
        glm::vec3 nearCornerRay = cornerRay * prevSplitDistance;
        glm::vec3 farCornerRay = cornerRay * splitDistance;
        frustumCornersWS[i + 4] = frustumCornersWS[i] + farCornerRay;
        frustumCornersWS[i] = frustumCornersWS[i] + nearCornerRay;
    }

    glm::vec3 frustumCenter = glm::vec3(0.0f);
    for (unsigned int i = 0; i < 8; ++i)
        frustumCenter += frustumCornersWS[i];
    frustumCenter /= 8.0f;

    GLfloat radius = 0.0f;
    for (unsigned int i = 0; i < 8; ++i) {
        GLfloat distance = glm::length(frustumCornersWS[i] - frustumCenter);
        radius = glm::max(radius, distance);
    }
    radius = std::ceil(radius * 16.0f) / 16.0f;

    glm::vec3 maxExtents = glm::vec3(radius, radius, radius);
    glm::vec3 minExtents = -maxExtents;

    //Position the viewmatrix looking down the center of the frustum with an arbitrary lighht direction
    glm::vec3 lightDirection =
            frustumCenter - glm::normalize(light->getDirection()) * -minExtents.z;
    glm::mat4 lightViewMatrix = glm::mat4(1.0f);
    lightViewMatrix = glm::lookAt(lightDirection, frustumCenter, glm::vec3(0.0f, 1.0f, 0.0f));

    glm::vec3 cascadeExtents = maxExtents - minExtents;

    glm::mat4 lightOrthoMatrix = glm::ortho(minExtents.x, maxExtents.x, minExtents.y,
                                            maxExtents.y, 0.0f, cascadeExtents.z);

    // The rounding matrix that ensures that shadow edges do not shimmer
    glm::mat4 shadowMatrix = lightOrthoMatrix * lightViewMatrix;
    glm::vec4 shadowOrigin = glm::vec4(0.0f, 0.0f, 0.0f, 1.0f);
    shadowOrigin = shadowMatrix * shadowOrigin;
    float mShadowMapSize = static_cast<float>(size);
    shadowOrigin = shadowOrigin * mShadowMapSize / 2.0f;

    glm::vec4 roundedOrigin = glm::round(shadowOrigin);
    glm::vec4 roundOffset = roundedOrigin - shadowOrigin;
    roundOffset = roundOffset * 2.0f / mShadowMapSize;
    roundOffset.z = 0.0f;
    roundOffset.w = 0.0f;

    glm::mat4 shadowProj = lightOrthoMatrix;
    shadowProj[3] += roundOffset;
    lightOrthoMatrix = shadowProj;

    //Store the split distances and the relevant matrices
    const float clipDist = farClip - nearClip;
    cascadeEndSpace[cascadeIterator] = (nearClip + splitDistance * clipDist) * -1.0f;

    lightProjectionView[cascadeIterator] = lightOrthoMatrix * lightViewMatrix;

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, m_fbo);
    glViewport(0, 0, mShadowMapSize, mShadowMapSize);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowMap[cascadeIterator],0);

    glClear(GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    glCullFace(GL_FRONT);
    glUniformMatrix4fv(glGetUniformLocation(programID, "lightProjectionView"), 1, GL_FALSE,
                       glm::value_ptr(lightProjectionView[cascadeIterator]));
    for (Geometry::Object *object:objects) {
        object->RenderToDepth(programID);
    }

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
glUseProgram(0);

最后我渲染场景:

    for (uint i = 0; i < NUM_CASCADES; i++) {
    glActiveTexture(GL_TEXTURE4 + i);
    glBindTexture(GL_TEXTURE_2D, m_shadowMap[i]);
    const char *shadowLoc = (const char *) ("map_shadow[" + Tools::ToString(i)+"]").c_str();
    glUniform1i(glGetUniformLocation(programID, shadowLoc), 4 + i);
    const char *lightLoc = (const char *) ("lightProjectionView[" + Tools::ToString(i) +
                                           "]").c_str();
    glUniformMatrix4fv(glGetUniformLocation(programID, lightLoc), 1, GL_FALSE,
                       glm::value_ptr(lightProjectionView[i]));
    int cascadeSpaceLoc = glGetUniformLocation(programID, (const char *) ("cascadeEndSpace[" +
                                                                          Tools::ToString(i) +
                                                                          "]").c_str());
    glUniform1f(cascadeSpaceLoc, cascadeEndSpace[i]);
}

最后,Shader方法是:

            "float readShadowMap(){"
            "    float positiveViewSpaceZ = FViewPos.z;"
            "    int cascadeIdx = 0;"
            "    for(int i = 0; i < NUM_CASCADES - 1; ++i){"
            "        if(positiveViewSpaceZ < cascadeEndSpace[i]){"
            "            cascadeIdx = i + 1;"
            "        }"
            "    }"
            "    vec4 fragmentShadowPosition = LightSpacePos[cascadeIdx];"
            "    vec3 projCoords = fragmentShadowPosition.xyz / fragmentShadowPosition.w;"
            "    projCoords = projCoords * 0.5f + 0.5f;"
            "    float currentDepth = projCoords.z;"
            "    float pcfDepth = 0.0f;"
            "    if(cascadeIdx == 0)"
            "       pcfDepth = texture(map_shadow[0], projCoords.xy).x;"
            "    else if(cascadeIdx == 1)"
            "       pcfDepth = texture(map_shadow[1], projCoords.xy).x;"
            "    else if(cascadeIdx == 2)"
            "       pcfDepth = texture(map_shadow[2], projCoords.xy).x;"
            "    float shadow = currentDepth + 0.00001  > pcfDepth ? 0.5  : 1.0;"
            "    return shadow;"
            "}"

我尝试更改纹理参数,但没有任何改变。

enter image description here

1 个答案:

答案 0 :(得分:1)

您正在寻找的是一个通常被称为“阴影痤疮”的假象。您可以找到带有插图here的很好的解释。基本上,由于有限的分辨率和精度,发生的事情是对象表面的某些部分最终在自身上投射了阴影。对于从摄影机角度渲染的每个片段,将其位置投影到阴影贴图中并比较深度值。除非相机图像和阴影贴图的采样率完全匹配(除非进行大规模的过度采样,否则常规采样基本上不可能实现),否则会有多个片段最终投影到同一阴影贴图像素的区域。您的片段全部来自一个平面,该平面通常相对于相机以与朝向光线的角度不同。因此,您最终得到多个相邻片段,这些片段的深度都略有不同,但是所有贴图都映射到相同的阴影贴图像素,即与相同的深度值进行比较。这些片段中大约一半的深度小于阴影图中的像素,大约大于阴影图中的像素的一半。再加上一些舍入误差噪声,您将获得上面发布的图像。

解决此问题的经典方法是在渲染阴影贴图或相机图像时(例如,使用glPolygonOffset)应用倾斜比例的深度偏差:

glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1.0f, 1.0f);

// render shadow map

glDisable(GL_POLYGON_OFFSET_FILL);

// render scene with shadows