背景
我正在使用C++和现代OpenGL(3.3)开发一个3D游戏。目前,我正在处理光照和阴影渲染,并且已经成功地实现了定向阴影映射。在阅读游戏要求后,我决定需要点光源阴影映射。经过一些研究,我发现为了进行全向阴影映射,我将执行类似于定向阴影映射的操作,但是将使用立方体贴图。
我之前没有关于立方体贴图的知识,但我的理解是它是六个无缝连接的纹理。
我寻找了一些相关的资料,但很遗憾我很难找到一份完整的现代OpenGL教程。我首先寻找能够从头到尾解释的教程,因为我很难从代码片段或概念中学习,但我尝试过了。
目前的理解
以下是我的一般理解,排除了技术细节。请纠正我。
- 对于每个点光源,设置一个帧缓冲区,就像定向阴影映射一样。
- 然后生成单个立方体贴图纹理,并使用
glBindTexture(GL_TEXTURE_CUBE_MAP, shadowmap)
进行绑定。 设置立方体贴图的以下属性:
glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP_ARB, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
(这也类似于方向阴影贴图)
现在
glTexImage2D()
会迭代六次,每个面一次。我是这样做的:
for (int face = 0; face < 6; face++) // Fill each face of the shadow cubemap
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, GL_DEPTH_COMPONENT32F , 1024, 1024, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
纹理通过调用以下函数附加到帧缓冲区: glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, shadowmap, 0);
渲染场景时,会像方向阴影映射一样进行两次渲染。
glCullFace(GL_FRONT)
。计算所有六个视角的光照视图矩阵。我是通过创建一个 glm::mat4 的向量并使用 push_back()
函数将矩阵添加到其中实现的,如下所示:
// Create the six view matrices for all six sides
for (int i = 0; i < renderedObjects.size(); i++) // Iterate through all rendered objects
{
renderedObjects[i]->bindBuffers(); // Bind buffers for rendering with it
glm::mat4 depthModelMatrix = renderedObjects[i]->getModelMatrix(); // Set up model matrix
for (int i = 0; i < 6; i++) // Draw for each side of the light
{
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, shadowmap, 0);
glClear(GL_DEPTH_BUFFER_BIT); // Clear depth buffer
// Send MVP for shadow map
glm::mat4 depthMVP = depthProjectionMatrix * depthViewMatrices[i] * depthModelMatrix;
glUniformMatrix4fv(glGetUniformLocation(shadowMappingProgram, "depthMVP"), 1, GL_FALSE, glm::value_ptr(depthMVP));
glUniformMatrix4fv(glGetUniformLocation(shadowMappingProgram, "lightViewMatrix"), 1, GL_FALSE, glm::value_ptr(depthViewMatrices[i]));
glUniformMatrix4fv(glGetUniformLocation(shadowMappingProgram, "lightProjectionMatrix"), 1, GL_FALSE, glm::value_ptr(depthProjectionMatrix));
glDrawElements(renderedObjects[i]->getDrawType(), renderedObjects[i]->getElementSize(), GL_UNSIGNED_INT, 0);
}
}
默认的帧缓冲区已绑定,场景正常绘制。
问题
现在来看着色器。这就是我理解不足的地方。我完全不确定该做什么,我的研究似乎彼此矛盾,因为它们是针对不同版本的。最终,我只能简单地从随机来源中复制和粘贴代码,并希望它能实现除黑屏以外的其他东西。我知道这很糟糕,但似乎没有清晰的定义要做什么。我应该在哪些空间里工作?我是否需要单独的阴影着色器,就像我在指向点光源时使用的那样?做为遮罩立方体图像的类型到底用什么?samplerCube?samplerCubeShadow?如何正确地采样这个立方体贴图?我希望有人能澄清一下,并提供一个好的解释。
我目前对着色器的理解是:
- 当场景被渲染到立方体贴图中时,顶点着色器只需使用我在C++代码中计算的depthMVP uniform并通过它们转换输入顶点。
- 立方体贴图传递的片段着色器仅将单个输出值分配给gl_FragCoord.z
。(这一部分与我实现定向阴影映射时没有改变。我认为在立方体图形映射中会是相同的,因为着色器甚至不与立方体贴图交互-OpenGL只是将它们的输出呈现到立方体贴图中,对吧?因为它是一个帧缓冲区?)
- 正常渲染的顶点着色器未更改。
- 在正常渲染的片段着色器中,使用光照的投影和视图矩阵将顶点位置转换为光照空间。
- 这样以某种方式用于立方体贴图纹理查找。???
- 一旦使用神奇的方法检索了深度,就将其与距离顶点的光线进行比较,就像定向阴影映射一样。如果小于,则该点必须被遮挡,反之亦然。
这并不是很理解。我不知道顶点是如何被转换和用于查找立方体贴图的,所以我要粘贴我的着色器源代码,希望人们能澄清这一点。请注意,这些代码大部分都是盲目地复制和粘贴的,我没有改变任何东西以免危及任何理解。
阴影顶点着色器:
#version 150
in vec3 position;
uniform mat4 depthMVP;
void main()
{
gl_Position = depthMVP * vec4(position, 1);
}
阴影片段着色器:
#version 150
out float fragmentDepth;
void main()
{
fragmentDepth = gl_FragCoord.z;
}
标准顶点着色器:
#version 150
in vec3 position;
in vec3 normal;
in vec2 texcoord;
uniform mat3 modelInverseTranspose;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
out vec3 fragnormal;
out vec3 fragnormaldirection;
out vec2 fragtexcoord;
out vec4 fragposition;
out vec4 fragshadowcoord;
void main()
{
fragposition = modelMatrix * vec4(position, 1.0);
fragtexcoord = texcoord;
fragnormaldirection = normalize(modelInverseTranspose * normal);
fragnormal = normalize(normal);
fragshadowcoord = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
}
标准片段着色器:
#version 150
out vec4 outColour;
in vec3 fragnormaldirection;
in vec2 fragtexcoord;
in vec3 fragnormal;
in vec4 fragposition;
in vec4 fragshadowcoord;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat4 projectionMatrix;
uniform mat4 viewMatrixInversed;
uniform mat4 lightViewMatrix;
uniform mat4 lightProjectionMatrix;
uniform sampler2D tex;
uniform samplerCubeShadow shadowmap;
float VectorToDepthValue(vec3 Vec)
{
vec3 AbsVec = abs(Vec);
float LocalZcomp = max(AbsVec.x, max(AbsVec.y, AbsVec.z));
const float f = 2048.0;
const float n = 1.0;
float NormZComp = (f+n) / (f-n) - (2*f*n)/(f-n)/LocalZcomp;
return (NormZComp + 1.0) * 0.5;
}
float ComputeShadowFactor(samplerCubeShadow ShadowCubeMap, vec3 VertToLightWS)
{
float ShadowVec = texture(ShadowCubeMap, vec4(VertToLightWS, 1.0));
if (ShadowVec + 0.0001 > VectorToDepthValue(VertToLightWS)) // To avoid self shadowing, I guess
return 1.0;
return 0.7;
}
void main()
{
vec3 light_position = vec3(0.0, 0.0, 0.0);
vec3 VertToLightWS = light_position - fragposition.xyz;
outColour = texture(tex, fragtexcoord) * ComputeShadowFactor(shadowmap, VertToLightWS);
}
我无法记得ComputerShadowFactor和VectorToDepthValue函数代码来源,因为我是在我的笔记本电脑上进行研究的,而现在我无法接触到它,但这些着色器的结果如下:
这是一个未被阴影覆盖的小正方形,周围都是有阴影的空间。
很明显我在这里做错了很多事情,可能集中在我的着色器上,因为我对这个主题的知识缺乏,而且我发现除了教程之外很难学习,对此我非常抱歉。我迷茫了,如果有人能提供清晰的解释,说明我做错了什么,为什么错了,如何修复以及一些代码,那将是太棒了。我认为问题可能是因为我在错误的空间中工作。