级联阴影贴图不太正确
Cascaded Shadow maps not quite right
好的。所以,上周我一直在弄乱我的游戏引擎中的阴影。我主要实现了级联阴影贴图 (CSM),但我遇到了一些我似乎无法解决的阴影问题。
此场景中唯一的光是定向光(太阳),指向 {-0.1 -0.25 -0.65}。我使用以下代码为我的 CSM 的四个拆分计算了 4 组平截头体边界:
// each projection matrix calculated with same near plane, different far
Frustum make_worldFrustum(const glm::mat4& _invProjView) {
Frustum fr; glm::vec4 temp;
temp = _invProjView * glm::vec4(-1, -1, -1, 1);
fr.xyz = glm::vec3(temp) / temp.w;
temp = _invProjView * glm::vec4(-1, -1, 1, 1);
fr.xyZ = glm::vec3(temp) / temp.w;
...etc 6 more times for ndc cube
return fr;
}
对于灯光,我得到了这样的视图矩阵:
glm::mat4 viewMat = glm::lookAt(cam.pos, cam.pos + lightDir, {0,0,1});
然后我根据每个平截头体的边界创建每个正交矩阵:
lightMatVec.clear();
for (auto& frus : cam.frusVec) {
glm::vec3 arr[8] {
glm::vec3(viewMat * glm::vec4(frus.xyz, 1)),
glm::vec3(viewMat * glm::vec4(frus.xyZ, 1)),
etc...
};
glm::vec3 minO = {INFINITY, INFINITY, INFINITY};
glm::vec3 maxO = {-INFINITY, -INFINITY, -INFINITY};
for (auto& vec : arr) {
minO = glm::min(minO, vec);
maxO = glm::max(maxO, vec);
}
glm::mat4 projMat = glm::ortho(minO.x, maxO.x, minO.y, maxO.y, minO.z, maxO.z);
lightMatVec.push_back(projMat * viewMat);
}
我有一个 4 层 TEXTURE_2D_ARRAY 绑定到 4 个帧缓冲区,我使用一个非常简单的顶点着色器(碎片禁用或 punchthrough alpha)将场景绘制到其中。
然后我画最后的场景。顶点着色器输出四个阴影纹理坐标:
out vec3 slShadcrd[4];
// stuff
for (int i = 0; i < 4; i++) {
vec4 sc = WorldBlock.skylMatArr[i] * vec4(world_pos, 1);
slShadcrd[i] = sc.xyz / sc.w * 0.5f + 0.5f;
}
还有一个片段着色器,它确定要使用的拆分:
int csmIndex = 0;
for (uint i = 0u; i < CameraBlock.csmCnt; i++) {
if (-view_pos.z > CameraBlock.csmSplits[i]) index++;
else break;
}
并使用此函数对阴影贴图数组进行采样:
float sample_shadow(vec3 _sc, int _csmIndex, sampler2DArrayShadow _tex) {
return texture(_tex, vec4(_sc.xy, _csmIndex, _sc.z)).r;
}
而且,这是我得到的场景(每个分割都有轻微的着色,并叠加了 4 个深度层):
伟大的!看起来不错。
但是,如果我稍微向右转动相机:
然后阴影开始消失(并且根据角度,出现在它们不应该出现的地方)。
我启用了 GL_DEPTH_CLAMP,所以这不是问题所在。我正在剔除正面,但关闭它对这个问题没有影响。
我错过了什么?我觉得这是我的一个预测的问题,但它们对我来说都是正确的。谢谢!
编辑:
灯光的所有四个截锥体都已绘制。它们都在那里,但只有 z 相对于相机发生变化(见下面的评论):
编辑:
可能更有用,这是当我只更新一次时,当相机位于 (0,0,0) 并指向前方 (0,1,0) 时,截锥体的样子。这次我还通过深度测试绘制了它们。
重要编辑:
看来这个问题和light的view matrix直接相关,目前:
glm::mat4 viewMat = glm::lookAt(cam.pos, cam.pos + lightDir, {0,0,1});
更改眼睛和目标的值似乎会影响错误的阴影。但我不知道我实际上应该将其设置为什么?对于比我更了解的人来说应该很容易:D
解决了!确实是灯的视图矩阵有问题!我所要做的就是将 camPos
替换为每个平截头体的中心点!这意味着每个拆分的光矩阵需要不同的视图矩阵。所以我只是像这样创建每个视图矩阵...
glm::mat4 viewMat = glm::lookAt(frusCentre, frusCentre+lightDir, {0,0,1});
然后简单地获取 frusCentre...
glm::vec3 calc_frusCentre(const Frustum& _frus) {
glm::vec3 min(INFINITY, INFINITY, INFINITY);
glm::vec3 max(-INFINITY, -INFINITY, -INFINITY);
for (auto& vec : {_frus.xyz, _frus.xyZ, _frus.xYz, _frus.xYZ,
_frus.Xyz, _frus.XyZ, _frus.XYz, _frus.XYZ}) {
min = glm::min(min, vec);
max = glm::max(max, vec);
}
return (min + max) / 2.f;
}
砰!一切都很棒!
编辑(最后一个!):
我所拥有的并不相当正确。视图矩阵实际上应该是:
glm::lookAt(frusCentre-lightDir, frusCentre, {0,0,1});
好的。所以,上周我一直在弄乱我的游戏引擎中的阴影。我主要实现了级联阴影贴图 (CSM),但我遇到了一些我似乎无法解决的阴影问题。
此场景中唯一的光是定向光(太阳),指向 {-0.1 -0.25 -0.65}。我使用以下代码为我的 CSM 的四个拆分计算了 4 组平截头体边界:
// each projection matrix calculated with same near plane, different far
Frustum make_worldFrustum(const glm::mat4& _invProjView) {
Frustum fr; glm::vec4 temp;
temp = _invProjView * glm::vec4(-1, -1, -1, 1);
fr.xyz = glm::vec3(temp) / temp.w;
temp = _invProjView * glm::vec4(-1, -1, 1, 1);
fr.xyZ = glm::vec3(temp) / temp.w;
...etc 6 more times for ndc cube
return fr;
}
对于灯光,我得到了这样的视图矩阵:
glm::mat4 viewMat = glm::lookAt(cam.pos, cam.pos + lightDir, {0,0,1});
然后我根据每个平截头体的边界创建每个正交矩阵:
lightMatVec.clear();
for (auto& frus : cam.frusVec) {
glm::vec3 arr[8] {
glm::vec3(viewMat * glm::vec4(frus.xyz, 1)),
glm::vec3(viewMat * glm::vec4(frus.xyZ, 1)),
etc...
};
glm::vec3 minO = {INFINITY, INFINITY, INFINITY};
glm::vec3 maxO = {-INFINITY, -INFINITY, -INFINITY};
for (auto& vec : arr) {
minO = glm::min(minO, vec);
maxO = glm::max(maxO, vec);
}
glm::mat4 projMat = glm::ortho(minO.x, maxO.x, minO.y, maxO.y, minO.z, maxO.z);
lightMatVec.push_back(projMat * viewMat);
}
我有一个 4 层 TEXTURE_2D_ARRAY 绑定到 4 个帧缓冲区,我使用一个非常简单的顶点着色器(碎片禁用或 punchthrough alpha)将场景绘制到其中。
然后我画最后的场景。顶点着色器输出四个阴影纹理坐标:
out vec3 slShadcrd[4];
// stuff
for (int i = 0; i < 4; i++) {
vec4 sc = WorldBlock.skylMatArr[i] * vec4(world_pos, 1);
slShadcrd[i] = sc.xyz / sc.w * 0.5f + 0.5f;
}
还有一个片段着色器,它确定要使用的拆分:
int csmIndex = 0;
for (uint i = 0u; i < CameraBlock.csmCnt; i++) {
if (-view_pos.z > CameraBlock.csmSplits[i]) index++;
else break;
}
并使用此函数对阴影贴图数组进行采样:
float sample_shadow(vec3 _sc, int _csmIndex, sampler2DArrayShadow _tex) {
return texture(_tex, vec4(_sc.xy, _csmIndex, _sc.z)).r;
}
而且,这是我得到的场景(每个分割都有轻微的着色,并叠加了 4 个深度层):
但是,如果我稍微向右转动相机:
我启用了 GL_DEPTH_CLAMP,所以这不是问题所在。我正在剔除正面,但关闭它对这个问题没有影响。
我错过了什么?我觉得这是我的一个预测的问题,但它们对我来说都是正确的。谢谢!
编辑:
灯光的所有四个截锥体都已绘制。它们都在那里,但只有 z 相对于相机发生变化(见下面的评论):
编辑:
可能更有用,这是当我只更新一次时,当相机位于 (0,0,0) 并指向前方 (0,1,0) 时,截锥体的样子。这次我还通过深度测试绘制了它们。
重要编辑: 看来这个问题和light的view matrix直接相关,目前:
glm::mat4 viewMat = glm::lookAt(cam.pos, cam.pos + lightDir, {0,0,1});
更改眼睛和目标的值似乎会影响错误的阴影。但我不知道我实际上应该将其设置为什么?对于比我更了解的人来说应该很容易:D
解决了!确实是灯的视图矩阵有问题!我所要做的就是将 camPos
替换为每个平截头体的中心点!这意味着每个拆分的光矩阵需要不同的视图矩阵。所以我只是像这样创建每个视图矩阵...
glm::mat4 viewMat = glm::lookAt(frusCentre, frusCentre+lightDir, {0,0,1});
然后简单地获取 frusCentre...
glm::vec3 calc_frusCentre(const Frustum& _frus) {
glm::vec3 min(INFINITY, INFINITY, INFINITY);
glm::vec3 max(-INFINITY, -INFINITY, -INFINITY);
for (auto& vec : {_frus.xyz, _frus.xyZ, _frus.xYz, _frus.xYZ,
_frus.Xyz, _frus.XyZ, _frus.XYz, _frus.XYZ}) {
min = glm::min(min, vec);
max = glm::max(max, vec);
}
return (min + max) / 2.f;
}
砰!一切都很棒!
编辑(最后一个!): 我所拥有的并不相当正确。视图矩阵实际上应该是:
glm::lookAt(frusCentre-lightDir, frusCentre, {0,0,1});