使用深度立方体贴图的全向阴影映射

9
我正在处理全向点光源。我已经使用 cubemap 纹理作为 6 个帧缓冲的颜色附加物,实现了阴影映射,并将从光到片段距离编码在其每个像素中。 现在,如果可能的话,我想改变我的实现方式: 1. 将深度立方体纹理附加到我的帧缓冲区的深度缓冲区,而不是颜色。 2. 只渲染深度,在此过程中不写入颜色。 3. 在主过程中,从 cubemap 纹理中读取深度,将其转换为距离,并检查当前片段是否被光遮挡或未被遮挡。 当将一个深度值从 cubemap 转换回距离时,我的问题出现了。我使用光线到片段向量(在世界空间中)来获取我的 cubemap 中的深度值。此时,我不知道正在使用哪个六个面,也不知道匹配深度值的 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);

帧缓冲构建:

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);
}

我正在尝试编写的片段着色器可以实现以下代码:
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;
}

深度值转距离函数是我的实际问题。

"我的问题有解决方案吗?" 将距离转换为深度,与cubemap中存储的深度进行比较。 "不适用于立方体贴图阴影" 我认为5到7年前有人叫做“ron frazier”撰写了一篇关于使用cubemaps进行阴影映射的文章。 - SigTerm
@SigTerm,我认为你在谈论的是http://www.ronfrazier.net/apparition/index.html?appmain=research/index.html。 - John Riselvato
@JohnRiselvato:是的。我认为这就是这篇文章。使用现代OpenGL/更好的精度显然可以做类似的事情(有浮点纹理、GLSL、*shadow采样器等)-只是我没有工作代码片段,并且不想写一个。 - SigTerm
尽管这个页面相当古老,但它给出了我应该进行距离到深度转换而不是反向转换的原因:“不幸的是,深度缓冲区使用相机空间z距离作为深度值,而不是笛卡尔距离”。从世界空间向量中找到与之匹配的“相机z距离”进入其中一个立方体贴图面很容易。我现在已经成功让我的阴影工作了。当我清理了一下我的代码后,我会整理问题并发布答案。感谢提供链接。 - Benlitz
2个回答

11

因此,解决方案是将从光线到片段的向量转换为深度值,而不是将从天空盒中读取的深度转换为距离。

以下是修改后的着色器代码:

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;
}

对于VectorToDepthValue(vec3 Vec)函数的解释:

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

如果您查看维基百科文章,您将理解接下来的数学公式(我将其保留为正式形式以便于理解),该公式简单地将LocalZComp转换为标准化的Z值(范围在[-1..1]之间),然后映射到[0..1],这是深度缓冲区值的实际范围。(假设您没有更改它)。nf是用于生成立方体贴图的截锥体的近和远值。

ComputeShadowFactor然后仅与来自立方体贴图的深度值与从片段到光线向量(此处命名为VertToLightWS)计算出的深度值进行比较,还添加了一个小的深度偏差(问题中缺失了),如果片段未被光源遮挡,则返回1。


5
我希望能够添加有关推导的更多细节。
V为从光源指向片段的方向向量。
正如Benlitz所说,在相应的立方体侧面截头锥/"眼空间"中,Z值可以通过取V各分量的绝对值的最大值来计算。
Z = max(abs(V.x),abs(V.y),abs(V.z))

然后,准确来说,我们应该否定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

然后我们需要进行透视除法,即将 z 除以 w (Z),这样就得到了:

z' = (f+n) / (f-n) - 2*f*n / (Z* (f-n))

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

z_depth_buffer_compatible = (z' + 1.0) * 0.5

进一步说明:

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

  • V需要处于世界空间,因为阴影立方体贴图通常在世界空间中轴对齐,因此"max(abs(V.x),abs(V.y),abs(V.z))"部分只有在V是世界空间方向向量时才有效。


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