OpenGL 阴影映射怪异

OpenGL shadow mapping weirdness

我一直在研究 OpenGL 和着色器,并开始研究阴影贴图。

尝试按照 Internet 上的教程(ogldev 和 learnopengl)进行操作,得到了一些意想不到的结果。

这个问题最好用几张截图来描述(我添加了一个带有深度帧缓冲区的静态四边形用于调试):

我设法用静态光 (this commit) 在地面四边形上渲染了一次阴影。但是阴影图案又是不正确的。我强烈怀疑关于此的模型 t运行sformation 矩阵计算:

我渲染场景的方式非常简单:

  1. 创建管道:
  1. 填充深度帧缓冲区:使用阴影映射管道,从光点渲染场景,使用正交投影
  2. 渲染阴影场景:使用渲染管道和深度帧缓冲区绑定作为第一个纹理,从相机点渲染场景,使用透视投影

看起来就像所有那些关于阴影贴图的教程中的算法。然而,我没有看到穆雷效应(就像在所有教程中那样),而是在底部平面上没有任何阴影,并且在 3D(鸡)模型上没有出现奇怪的人工制品(不正确的阴影贴图)。

有趣的是,如果我不渲染(阴影贴图和最终渲染通道)鸡模型,平面会以相同的怪异模式照亮:

我还必须从片段着色器中删除任何正常的 t运行sformations 并禁用面剔除以使地平面发光。使用正面剔除,平面不会出现在阴影贴图(深度缓冲区)中。

我认为可能是以下原因导致了此问题:

不幸的是,我 运行 甚至对如何评估上述假设都没有想法。

寻求有关此事的任何帮助(也可以随意批评我的任何方法,包括 C++、CMake、OpenGL 和计算机图形学)。

完整的解决方案源代码可用 on GitHub,但为方便起见,我将经过大量删减的源代码放在下面。

shadow-mapping.vert:

#version 410

layout (location = 0) in vec3 vertexPosition;

out gl_PerVertex
{
    vec4 gl_Position;
};

uniform mat4 lightSpaceMatrix;
uniform mat4 modelTransformation;

void main()
{
    gl_Position = lightSpaceMatrix * modelTransformation * vec4(vertexPosition, 1.0);
}

shadow-mapping.frag:

#version 410

layout (location = 0) out float fragmentDepth;

void main()
{
  fragmentDepth = gl_FragCoord.z;
}

shadow-rendering.vert:

#version 410

layout (location = 0) in vec3 vertexPosition;
layout (location = 1) in vec3 vertexNormal;
layout (location = 2) in vec2 vertexTextureCoord;

out VS_OUT
{
    vec3 fragmentPosition;
    vec3 normal;
    vec2 textureCoord;
    vec4 fragmentPositionInLightSpace;
} vsOut;

out gl_PerVertex {
    vec4 gl_Position;
};

uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
uniform mat4 lightSpaceMatrix;

void main()
{
    vsOut.fragmentPosition = vec3(model * vec4(vertexPosition, 1.0));
    vsOut.normal = transpose(inverse(mat3(model))) * vertexNormal;
    vsOut.textureCoord = vertexTextureCoord;
    vsOut.fragmentPositionInLightSpace = lightSpaceMatrix * model * vec4(vertexPosition, 1.0);

    gl_Position = projection * view * model * vec4(vertexPosition, 1.0);
}

shadow-rendering.frag:

#version 410

layout (location = 0) out vec4 fragmentColor;

in VS_OUT {
    vec3 fragmentPosition;
    vec3 normal;
    vec2 textureCoord;
    vec4 fragmentPositionInLightSpace;
} fsIn;

uniform sampler2D shadowMap;
uniform sampler2D diffuseTexture;

uniform vec3 lightPosition;
uniform vec3 lightColor;
uniform vec3 cameraPosition;

float shadowCalculation()
{
    vec2 shadowMapCoord = fsIn.fragmentPositionInLightSpace.xy * 0.5 + 0.5;
    float occluderDepth = texture(shadowMap, shadowMapCoord).r;

    float thisDepth = fsIn.fragmentPositionInLightSpace.z * 0.5 + 0.5;

    return occluderDepth < thisDepth ? 1.0 : 0.0;
}

void main()
{
    vec3 color = texture(diffuseTexture, fsIn.textureCoord).rgb;
    vec3 normal = normalize(fsIn.normal);

    // ambient
    vec3 ambient = 0.3 * color;

    // diffuse
    vec3 lightDirection = normalize(lightPosition - fsIn.fragmentPosition);
    float diff = max(dot(lightDirection, normal), 0.0);
    vec3 diffuse = diff * lightColor;

    // specular
    vec3 viewDirection = normalize(cameraPosition - fsIn.fragmentPosition);
    vec3 halfwayDirection = normalize(lightDirection + viewDirection);
    float spec = pow(max(dot(normal, halfwayDirection), 0.0), 64.0);
    vec3 specular = spec * lightColor;

    // calculate shadow
    float shadow = shadowCalculation();

    vec3 lighting = ((shadow * (diffuse + specular)) + ambient) * color;

    fragmentColor = vec4(lighting, 1.0);
}

main.cpp,设置着色器和帧缓冲区:

// loading the shadow mapping shaders
auto shadowMappingVertexProgram = ...;
auto shadowMappingFragmentProgram = ...;

auto shadowMappingLightSpaceUniform = shadowMappingVertexProgram->getUniform<glm::mat4>("lightSpaceMatrix");
auto shadowMappingModelTransformationUniform = shadowMappingVertexProgram->getUniform<glm::mat4>("modelTransformation");

auto shadowMappingPipeline = std::make_unique<globjects::ProgramPipeline>();
shadowMappingPipeline->useStages(shadowMappingVertexProgram.get(), gl::GL_VERTEX_SHADER_BIT);
shadowMappingPipeline->useStages(shadowMappingFragmentProgram.get(), gl::GL_FRAGMENT_SHADER_BIT);

// (omitted) loading the depth frame buffer debugging shaders and creating a pipeline here

// loading the rendering shaders
auto shadowRenderingVertexProgram = ...;
auto shadowRenderingFragmentProgram = ...;

auto shadowRenderingModelTransformationUniform = shadowRenderingVertexProgram->getUniform<glm::mat4>("model");
auto shadowRenderingViewTransformationUniform = shadowRenderingVertexProgram->getUniform<glm::mat4>("view");
auto shadowRenderingProjectionTransformationUniform = shadowRenderingVertexProgram->getUniform<glm::mat4>("projection");
auto shadowRenderingLightSpaceMatrixUniform = shadowRenderingVertexProgram->getUniform<glm::mat4>("lightSpaceMatrix");

auto shadowRenderingLightPositionUniform = shadowRenderingFragmentProgram->getUniform<glm::vec3>("lightPosition");
auto shadowRenderingLightColorUniform = shadowRenderingFragmentProgram->getUniform<glm::vec3>("lightColor");
auto shadowRenderingCameraPositionUniform = shadowRenderingFragmentProgram->getUniform<glm::vec3>("cameraPosition");

auto shadowRenderingPipeline = std::make_unique<globjects::ProgramPipeline>();
shadowRenderingPipeline->useStages(shadowRenderingVertexProgram.get(), gl::GL_VERTEX_SHADER_BIT);
shadowRenderingPipeline->useStages(shadowRenderingFragmentProgram.get(), gl::GL_FRAGMENT_SHADER_BIT);

// loading the chicken model
auto chickenModel = Model::fromAiNode(chickenScene, chickenScene->mRootNode, { "media" });

// INFO: this transformation is hard-coded specifically for Chicken.3ds model
chickenModel->setTransformation(glm::rotate(glm::scale(glm::mat4(1.0f), glm::vec3(0.01f)), glm::radians(-90.0f), glm::vec3(1.0f, 0, 0)));

// loading the quad model
auto quadModel = Model::fromAiNode(quadScene, quadScene->mRootNode);

// INFO: this transformation is hard-coded specifically for quad.obj model
quadModel->setTransformation(glm::rotate(glm::scale(glm::translate(glm::mat4(1.0f), glm::vec3(-5, 0, 5)), glm::vec3(10.0f, 0, 10.0f)), glm::radians(-90.0f), glm::vec3(1.0f, 0, 0)));

// loading the floor texture
sf::Image textureImage = ...;

auto defaultTexture = std::make_unique<globjects::Texture>(static_cast<gl::GLenum>(GL_TEXTURE_2D));
defaultTexture->setParameter(static_cast<gl::GLenum>(GL_TEXTURE_MIN_FILTER), static_cast<GLint>(GL_LINEAR));
defaultTexture->setParameter(static_cast<gl::GLenum>(GL_TEXTURE_MAG_FILTER), static_cast<GLint>(GL_LINEAR));
defaultTexture->image2D(0, static_cast<gl::GLenum>(GL_RGBA8), glm::vec2(textureImage.getSize().x, textureImage.getSize().y), 0, static_cast<gl::GLenum>(GL_RGBA), static_cast<gl::GLenum>(GL_UNSIGNED_BYTE), reinterpret_cast<const gl::GLvoid*>(textureImage.getPixelsPtr()));

// initializing the depth frame buffer
auto shadowMapTexture = std::make_unique<globjects::Texture>(static_cast<gl::GLenum>(GL_TEXTURE_2D));
shadowMapTexture->setParameter(static_cast<gl::GLenum>(GL_TEXTURE_MIN_FILTER), static_cast<gl::GLenum>(GL_LINEAR));
shadowMapTexture->setParameter(static_cast<gl::GLenum>(GL_TEXTURE_MAG_FILTER), static_cast<gl::GLenum>(GL_LINEAR));
shadowMapTexture->setParameter(static_cast<gl::GLenum>(GL_TEXTURE_WRAP_S), static_cast<gl::GLenum>(GL_CLAMP_TO_BORDER));
shadowMapTexture->setParameter(static_cast<gl::GLenum>(GL_TEXTURE_WRAP_T), static_cast<gl::GLenum>(GL_CLAMP_TO_BORDER));
shadowMapTexture->setParameter(static_cast<gl::GLenum>(GL_TEXTURE_BORDER_COLOR), glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
shadowMapTexture->image2D(0, static_cast<gl::GLenum>(GL_DEPTH_COMPONENT), glm::vec2(window.getSize().x, window.getSize().y), 0, static_cast<gl::GLenum>(GL_DEPTH_COMPONENT), static_cast<gl::GLenum>(GL_FLOAT), nullptr);

auto framebuffer = std::make_unique<globjects::Framebuffer>();
framebuffer->attachTexture(static_cast<gl::GLenum>(GL_DEPTH_ATTACHMENT), shadowMapTexture.get());

main.cpp,渲染(主循环):

// (omitted) event handling, camera updates go here

glm::mat4 cameraProjection = glm::perspective(glm::radians(fov), (float) window.getSize().x / (float) window.getSize().y, 0.1f, 100.0f);
glm::mat4 cameraView = glm::lookAt(cameraPos, cameraPos + cameraForward, cameraUp);

// moving light together with the camera, for debugging purposes
glm::vec3 lightPosition = cameraPos;

// light settings
const float nearPlane = 1.0f;
const float farPlane = 10.0f;
glm::mat4 lightProjection = glm::ortho(-5.0f, 5.0f, -5.0f, 5.0f, nearPlane, farPlane);
glm::mat4 lightView = glm::lookAt(lightPosition, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));
glm::mat4 lightSpaceMatrix = lightProjection * lightView;

::glViewport(0, 0, static_cast<GLsizei>(window.getSize().x), static_cast<GLsizei>(window.getSize().y));

// first render pass - shadow mapping

framebuffer->bind();

::glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
::glClear(GL_DEPTH_BUFFER_BIT);
framebuffer->clearBuffer(static_cast<gl::GLenum>(GL_DEPTH), 0, glm::vec4(1.0f));

glEnable(GL_DEPTH_TEST);

// cull front faces to prevent peter panning the generated shadow map
glCullFace(GL_FRONT);

shadowMappingPipeline->use();

shadowMappingLightSpaceUniform->set(lightSpaceMatrix);
shadowMappingModelTransformationUniform->set(chickenModel->getTransformation());
chickenModel->draw();

shadowMappingModelTransformationUniform->set(quadModel->getTransformation());
quadModel->draw();

framebuffer->unbind();

shadowMappingPipeline->release();

glCullFace(GL_BACK);

// second pass - switch to normal shader and render picture with depth information to the viewport

glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

shadowRenderingPipeline->use();

shadowRenderingLightPositionUniform->set(lightPosition);
shadowRenderingLightColorUniform->set(glm::vec3(1.0, 1.0, 1.0));
shadowRenderingCameraPositionUniform->set(cameraPos);

shadowRenderingProjectionTransformationUniform->set(cameraProjection);
shadowRenderingViewTransformationUniform->set(cameraView);
shadowRenderingLightSpaceMatrixUniform->set(lightSpaceMatrix);

// draw chicken

shadowMapTexture->bind();

shadowRenderingModelTransformationUniform->set(chickenModel->getTransformation());
chickenModel->draw();

shadowRenderingModelTransformationUniform->set(quadModel->getTransformation());
defaultTexture->bind();
quadModel->draw();
defaultTexture->unbind();

shadowMapTexture->unbind();

shadowRenderingPipeline->release();

// (omitted) render the debugging quad with depth (shadow) map

window.display();

尽管这可能是可耻的,但问题是绑定了错误的纹理。

我使用的 globjects 库在 OpenGL 上有一些不错的 (-r) 抽象实际上没有提供围绕纹理绑定的智能逻辑(正如我盲目假设的那样)。因此仅使用 Texture::bind()Texture::unbind() 不会自动跟踪绑定了多少纹理并增加索引。

例如它不是表现(大致)像这样:

static int boundTextureIndex = -1;

void Texture::bind() {
  glBindTexture(this->textureType, this->textureId);
  glActivateTexture(GL_TEXTURE0 + (++boundTextureIndex));
}

void Texture::unbind() {
  --boundTextureIndex;
}

所以在将 texture->bind() 更改为 texture->bindActive(0) 然后 shaderProgram->setUniform("texture", 0) 之后,我终于得到了 mouray 效果和正确的阴影贴图:

完整更改在 this commit