具有深度立方体贴图的全方位阴影贴图

Ben*_*itz 7 opengl shader glsl shadow

我正在使用全向点光源.我已经使用立方体贴图纹理实现阴影贴图作为6个帧缓冲区的颜色附加,并在其每个像素中编码光到片段的距离.

现在我希望,如果可以的话,以这种方式改变我的实现:

  • 1)将深度立方体贴图纹理附加到我的帧缓冲区的深度缓冲区,而不是颜色.
  • 2)仅渲染深度,不要在此过程中写入颜色
  • 3)在主要通道中,从立方体贴图纹理中读取深度,将其转换为距离,并检查当前片段是否被光遮挡.

将立方体贴图中的深度值转换回距离时出现问题.我使用光到片段向量(在世界空间中)来获取立方体贴图中的深度值.此时,我不知道正在使用六个面中的哪一个,也不知道2D纹理坐标与我正在读取的深度值相匹配.那么如何将该深度值转换为距离?

以下是我的代码片段来说明:

深度纹理:

glGenTextures(1, &TextureHandle);
glBindTexture(GL_TEXTURE_CUBE_MAP, TextureHandle);
for (int i = 0; i < 6; ++i)
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT,
              Width, Height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, 0);

glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

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);
Run Code Online (Sandbox Code Playgroud)

帧缓冲结构:

for (int i = 0; i < 6; ++i)
{
    glGenFramebuffers(1, &FBO->FrameBufferID);
    glBindFramebuffer(GL_FRAMEBUFFER, FBO->FrameBufferID);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
            GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, TextureHandle, 0);
    glDrawBuffer(GL_NONE);
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试编写片段着色器来实现我的代码:

float ComputeShadowFactor(samplerCubeShadow ShadowCubeMap, vec3 VertToLightWS)
{   
    float ShadowVec = texture(ShadowCubeMap, vec4(VertToLightWS, 1.0));
    ShadowVec = DepthValueToDistance(ShadowVec);
    if (ShadowVec * ShadowVec > dot(VertToLightWS, VertToLightWS))
        return 1.0;

    return 0.0;
}
Run Code Online (Sandbox Code Playgroud)

DepthValueToDistance函数是我的实际问题.

Ben*_*itz 10

因此,解决方案是将光到片段矢量转换为深度值,而不是将从立方体贴图读取的深度转换为距离.

这是修改后的着色器代码:

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))
        return 1.0;

    return 0.0;
}
Run Code Online (Sandbox Code Playgroud)

解释VectorToDepthValue(vec3 Vec):

LocalZComp对应于给定Vec的立方图的匹配平截头体中的Z分量.它实际上是最大的组件Vec(例如,如果Vec.y是最大的组件,我们将查看立方体贴图的Y +或Y-面).

如果你看一下这个维基百科的文章,你会理解之后的数学(我把它保存在正式的形式中以便理解),它只是简单地将其LocalZComp转换为标准化的Z值(介于[-1..1]之间)然后映射它进入[0..1],这是深度缓冲值的实际范围.(假设你没有改变它).n并且f是用于生成立方体贴图的截头体的近和远值.

ComputeShadowFactor然后只是将立方体贴图的深度值与从片段到光矢量(VertToLightWS此处命名)计算的深度值进行比较,同时添加一个小的深度偏差(问题中缺少),如果片段不是,则返回1光被遮挡.


Max*_*FPS 5

我想添加更多关于推导的细节。

V为光到片段的方向向量。

正如 Benlitz 已经说过的,可以通过取V分量的绝对值的最大值来计算各个立方体侧视锥体/“眼睛空间”中的Z值。

Z = max(abs(V.x),abs(V.y),abs(V.z))
Run Code Online (Sandbox Code Playgroud)

然后,准确地说,我们应该否定Z,因为在 OpenGL 中,负 Z 轴指向屏幕/视锥体。

现在我们想要获取该-Z的深度缓冲区“兼容”值。

查看OpenGL透视矩阵...

http://www.songho.ca/opengl/files/gl_projectionmatrix_eq16.png

http://i.stack.imgur.com/mN7ke.png(备份链接)

...我们看到,对于与该矩阵相乘的任何齐次向量,结果 z 值完全独立于向量的 x 和 y 分量。

所以我们可以简单地将这个矩阵与齐次向量 (0,0, -Z ,1)相乘,我们得到向量(分量):

x = 0
y = 0
z = (-Z * -(f+n) / (f-n)) + (-2*f*n / (f-n))
w = Z
Run Code Online (Sandbox Code Playgroud)

然后我们需要做透视除法,所以我们将 z 除以 w ( Z ) 这给我们:

z' = (f+n) / (f-n) - 2*f*n / (Z* (f-n))
Run Code Online (Sandbox Code Playgroud)

这个 z' 在 OpenGL 的归一化设备坐标 (NDC) 范围 [-1,1] 中,需要转换为 [0,1] 的深度缓冲区兼容范围:

z_depth_buffer_compatible = (z' + 1.0) * 0.5
Run Code Online (Sandbox Code Playgroud)

补充说明:

  • 将 (f+n)、(fn) 和 (f*n) 的结果作为着色器统一上传以节省计算可能是有意义的。

  • V需要在世界空间中,因为阴影立方体贴图在世界空间中通常是轴对齐的,因此“ max(abs(Vx),abs(Vy),abs(Vz)) ”部分仅在V是世界空间时才有效方向向量。