Неожиданное поведение карты теней - PullRequest
0 голосов
/ 30 марта 2019

Я использую технику Cascaded Shadow Map, я получаю неожиданный результат

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]);
}

Наконец, метод шейдера:

            "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 Ответ

1 голос
/ 30 марта 2019

То, на что вы смотрите, - это артефакт сглаживания, часто называемый «теневыми угрями».Вы можете найти хорошее объяснение с некоторыми иллюстрациями здесь .По сути, происходит то, что из-за конечного разрешения и точности части поверхностей вашего объекта в конечном итоге отбрасывают тени на себя.Для каждого фрагмента, который вы визуализируете с точки зрения камеры, вы проецируете его положение на карту теней и сравниваете значения глубины.Если частоты дискретизации изображения с вашей камеры и карты теней не будут идеально согласованы (чего в принципе невозможно добиться с помощью регулярной выборки, если вы не будете чрезмерно выполнять избыточную выборку), будут области, в которых несколько фрагментов будут проецироваться в один и тот же пиксель карты теней.Все ваши фрагменты исходят от плоской поверхности, которая обычно ориентирована под другим углом к ​​камере, чем к свету.Таким образом, вы получите несколько соседних фрагментов, которые имеют слегка различную глубину, но все будут отображены в один и тот же пиксель карты теней, т. Е. Сравниваются с одним и тем же значением глубины.Около половины этих фрагментов будут иметь глубину меньше пикселя на карте теней, примерно на половину больше, чем пиксель на карте теней.Добавьте к этому некоторый шум из-за ошибки округления, и вы получите изображение, которое вы разместили выше.

Классическое решение этой проблемы - применить масштабированное по наклону смещение глубины при рендеринге вашей карты теней или изображения с камеры,например, используя glPolygonOffset :

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

// render shadow map

glDisable(GL_POLYGON_OFFSET_FILL);

// render scene with shadows
...