OpenGL ES 2.0中使用立方体贴图实现阴影映射

3

我对OpenGL ES很新,正在尝试在我的当前场景中添加一些阴影。我决定使用立方体贴图来实现。我正在使用OpenGL es 2.0,所以几何着色器或gl_FragDepth变量对我不可用。我谷歌了一些,所以我对这个主题有了一些见解,读了这个(http://www.cg.tuwien.ac.at/courses/Realtime/repetitorium/2010/OmnidirShadows.pdf)证明非常有用。基本上我依靠这个链接的文档。

但我的代码有问题,因为在渲染的场景中每个像素都在阴影下。我认为问题可以在我的着色器中找到,但我在此粘贴了所有相关代码,以清楚地看到所有内容。

设置帧缓冲和创建立方体贴图:

GLuint FBO;
GLuint cubeTexture;

glGenFramebuffers(1, &FBO);
glBindFramebuffer(GL_FRAMEBUFFER, FBO);

// Depth texture
glGenTextures(1, &cubeTexture);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture);

glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); // no GL_TEXTURE_WRAP_R

// right, left, top, bottom, front, back
for (int face = 0; face < 6; ++face) {
    // create space for the textures, content need not to be specified (last parameter is 0)
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_DEPTH_COMPONENT16, 1024, 768, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);
}

glBindTexture(GL_TEXTURE_CUBE_MAP, 0);

glBindFramebuffer(GL_FRAMEBUFFER, 0);

在 Display 函数中,我试图实现 6 个渲染通道来填充阴影贴图(立方体的 6 个面)。
渲染:
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepthf(1.0f);

glm::vec3 camPos = ... // position of the camera, it is in world space
glm::vec4 lightPos = ... // position of the light source, it is in world space

// 1. render into texture

float zNear = 0.1f;
float zFar = 100.0f;
// or should I use ortho instead of perspective?
glm::mat4 projMatrix = glm::perspective(90.0f, (float)esContext->width / esContext->height, zNear, zFar);

// The 6 cameras have to be placed to the light source and they need the proper view matrices 

glm::mat4 cubeMapMatrices[6]; // contains six basic and defined rotation matrices for the six directions 
cubeMapMatrices[0] = glm::make_mat4(rotPositiveX);
cubeMapMatrices[1] = glm::make_mat4(rotNegativeX);
cubeMapMatrices[2] = glm::make_mat4(rotPositiveY);
cubeMapMatrices[3] = glm::make_mat4(rotNegativeY);
cubeMapMatrices[4] = glm::make_mat4(rotPositiveZ);
cubeMapMatrices[5] = glm::make_mat4(rotNegativeZ);

glm::vec4 translation = lightPos;

glBindTexture(GL_TEXTURE_CUBE_MAP, cubeTexture);

glBindFramebuffer(GL_FRAMEBUFFER, FBO);

for (int face = 0; face < 6; ++face) {

    glClear(GL_DEPTH_BUFFER_BIT); // do I need this here?

    cubeMapMatrices[face][3] = translation; // the translation part is the same for all
    cubeMapMatrices[face] = projMatrix * cubeMapMatrices[face]; // now it's an mvp matrix

    glUniformMatrix4fv(cubeProjectionMatrixID, 1, GL_FALSE, glm::value_ptr(cubeMapMatrices[face]));

    // Attach depth cubemap texture to FBO's depth attachment point
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, cubeTexture, 0);

    int err = glCheckFramebufferStatus(GL_FRAMEBUFFER);
    if (err != GL_FRAMEBUFFER_COMPLETE) {
        std::cout << "Framebuffer error, status: " << err << std::endl;
    }

    RenderScene(); // do the drawing 
}

glBindFramebuffer(GL_FRAMEBUFFER, 0);

// 2. render into screen

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

RenderScene(); // do the drawing

// swap the buffers

所以这里有不同的着色器。我有一个顶点着色器和一个片段着色器用于深度计算,还有一个顶点着色器和一个片段着色器用于屏幕渲染。我的问题是,如果帧缓冲正在使用中,我不确定如何写入到立方体贴图中,那么gl_Position会确定给定立方体面上的坐标吗?
深度计算的顶点着色器:
in vec3 vPosition;

uniform mat4 mModel;
uniform mat4 mCubeProjection;
uniform vec4 vLight;

out vec4 vFrag Position; // world space
out vec4 vLightPosition; // mvp transformed

void main()
{
    vec4 position = vec4(vPosition, 1.0);
    vFragPosition  = mModel* position;
    vLightPosition = vLight;
    gl_Position = mCubeProjection * vFragPosition;
}

用于深度计算的片元着色器:

in vec4 vFragPosition; // world space
in vec4 vLightPosition; // world space

out float depthValue;

void main()
{
    depthValue = distance(vFragPosition.xyz, vLightPosition.xyz); // need normalization?
}

用于屏幕渲染的顶点着色器:

uniform mat4 mModel;
uniform mat4 mView;
uniform mat4 mProjection;
uniform vec4 vLight; // it is in world space

out vec3 lw; // light position in world space
out vec3 pw; // pixel position in world space

void main()
{
    vec4 position = vec4(vPosition, 1.0);
    lw = vLight.xyz;
    pw = (mModel* position).xyz;
    gl_Position  = mProjection* mView * mModel* position;
}

用于屏幕渲染的片段着色器:

in vec3 lw;
in vec3 pw;

uniform samplerCube cubeMap;

out vec4 outputColor;

void main()
{
    vec3 lookup = pw - lw;
    float smValue = texture(cubeMap, lookup).r; // retrieves texels from a texture (d, d, d, 1.0)
    float distance = length(lookup); // dist from the fragment to the light

    float eps = 0.1;
    float shadowVal = 1.0;

    if (smValue + eps < distance) {
        shadowVal = 0.1; // in shadow
    }

    // here comes the lighting stuff
    // ...

    outputColor =  outputColor * shadowVal;
}

所以问题在于每个像素都处于阴影之下。从代码中我排除了一些传递到着色器的统一通行证,但它们是正常的。你能给我一个建议,在代码中应该修复什么?我的着色器(特别是第一遍)是否正确,我是否正确设置了立方体映射的转换?谢谢。

P.S:这是我在这里的第一个问题,我希望它足够清晰,并满足正确发布问题的要求。

1个回答

1
基本上,您的问题非常简单:您正在为立方体贴图使用深度附件,并且深度缓冲区存储透视深度。
您的着色器希望在阴影映射中看到的是从光源到最近片段的非透视距离。实际上,您已经在片段着色器中计算了这个值用于创建阴影贴图,但将其输出到颜色缓冲区(未连接任何内容)而不是深度缓冲区。
这个问题至少有三种可能的解决方案(按性能顺序排列,最差的首先):
1. 写入 gl_FragDepth 而不是 depthValue (实际上是颜色缓冲目标)。 2. 将您的立方体贴图附加到 GL_COLOR_ATTACHMENT0 并使用可渲染颜色格式而不是 GL_DEPTH_COMPONENT 。 3. 从您的着色器中删除 depthValue 写透视深度。 选项1绝对糟糕,尽管我看到有人引用建议使用此选项的教程。如果您写入gl_FragDepth,则会禁用许多硬件上的深度缓冲优化,这将使生成阴影贴图的处理过程性能更差。 选项2更好,因为它受益于硬件Z缓冲优化,但仍需要大量内存带宽,因为您实际上在存储两个不同的深度值(一个在颜色附件中,一个在深度附件中)。 选项3虽然最复杂,但通常也是性能最好的。这是因为您可以通过仅存储硬件深度将计算阴影贴图所需的内存带宽减少一半。
如果您想了解更多关于选项3的信息,我建议您查看this related question。与比较距离不同,您实际上将计算透视深度以用于比较。当应用阴影时,在片段着色器中进行少量额外计算,以换取创建阴影贴图时更少的内存带宽。
现在为了用最少的工作解决您的问题,您应该选择选项2。将选项3留待未来优化;最好不要在至少有一些可行方案之前就进行优化。

感谢您的回答。选项3不可行,因为在es 2.0中无法访问gl_FragDepth。所以就像您建议的那样,我选择了选项2。 - Terrordrone
我将相关行更改为glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,0,GL_RGB,1024,768,0,GL_RGB,GL_UNSIGNED_SHORT_5_6_5,0);和glFramebufferTexture2D(GL_FRAMEBUFFER,GL_COLOR_ATTACHMENT0,GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,cubeTexture,0);但是当我在第二个片段着色器中调用“texture”函数时仍然只有零。对于格式不佳的问题我表示抱歉。 - Terrordrone
我有一个类似的情况,并且发现了一些奇怪的事情。如果我只写入gl_FragDepth,而不在我的shadow fs中输出任何内容,那么似乎cubemap就什么也得不到。如果我写入长度,我可以看到它以红色正确地被写入(我还将颜色附件附加到它上面)。为什么会出现这种情况? - ChaoSXDemon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接