在将截锥体从相机空间转换为光空间以进行阴影映射时遇到重大问题

Posted

技术标签:

【中文标题】在将截锥体从相机空间转换为光空间以进行阴影映射时遇到重大问题【英文标题】:Having major problems transforming frustum from camera space to light space for shadow mapping 【发布时间】:2017-02-01 00:44:29 【问题描述】:

我正在尝试在我的 OpenGL/C++ 渲染器中实现级联阴影映射。当正交矩阵由任意数字构建并保持在场景的原点时,我已经成功地实现了定向阴影映射。从我所看到的一切来看,我遇到的问题是根据视锥体的切片来决定正交矩阵的边界。

我找到了两种计算方法,两者都是相关的;第一种方法使用当前切片的近/远平面创建投影矩阵,然后将其反转。然后我在 NDC 空间中取一个立方体的角(每个轴的范围为 [-1:+1]),通过倒置投影矩阵、倒置视图矩阵、然后是光空间矩阵将其相乘;然后将整个东西除以角的'w'。

vec4 corners[8] =
    
        // Near plane
         1, 1, 1, 1 ,
         -1, 1, 1, 1 ,
         1, -1, 1, 1 ,
         -1, -1, 1, 1 ,
        // Far plane
         1, 1, -1, 1 ,
         -1, 1, -1, 1 ,
         1, -1, -1, 1 ,
         -1, -1, -1, 1 
    ;

    // Create a projection matrix for this cascade.
    // This will transform the corner from camera space to view space.
    mat4 proj = mat4::perspective(fov, aspect, near_plane, far_plane);

    // B.inverted()*A.inverted() == C.inverted() == (A*B).inverted()
    // This is now an inverted viewproj matrix, which transforms the corner from camera space to world space.
    mat4 inv_viewproj = (proj * view_matrix).inverted();

    // This is the matrix that transforms each corner from camera space to light space.
    // (this_frame.view is the light-space view matrix)
    mat4 matrix = this_frame.view * matrix;
    
    float minx = FLT_MAX;
    float maxx = -FLT_MAX;
    float miny = FLT_MAX;
    float maxy = -FLT_MAX;
    float minz = FLT_MAX;
    float maxz = -FLT_MAX;

    // For each frustum corner...
    for (uint32_t i = 0; i < 8; ++i)
    
        corners[i] = matrix * corners[i];
        corners[i] /= corners[i].w;

        minx = min(minx, corners[i].x);
        maxx = max(maxx, corners[i].x);
        miny = min(miny, corners[i].y);
        maxy = max(maxy, corners[i].y);
        minz = min(minz, corners[i].z);
        maxz = max(maxz, corners[i].z);
    

    this_frame.proj = mat4::ortho(minx, maxx, miny, maxy, minz, maxz);

第二种方式类似,它从视图空间中的一组角开始,减少除以 w 的需要并节省一些矩阵求逆。

mat4 inv_view = view_matrix.inverted();
float tan_half_hfov = tanf(to_radians(fov * 0.5f));
float tan_half_vfov = tanf(to_radians((fov * 0.5f) / aspect));
//...

    float xn = near_plane * tan_half_hfov;
    float yn = near_plane * tan_half_vfov;
    float xf = far_plane * tan_half_hfov;
    float yf = far_plane * tan_half_vfov;

    // These are the corners of the screen in view space.
    // We'll be transforming these in order to get the dimensions of the view frustum.
    vec4 corners[8] =
    
        // Near plane
         xn, yn, -near_plane, 1 ,
         -xn, yn, -near_plane, 1 ,
         xn, -yn, -near_plane, 1 ,
         -xn, -yn, -near_plane, 1 ,
        // Far plane
         xf, yf, -far_plane, 1 ,
         -xf, yf, -far_plane, 1 ,
         xf, -yf, -far_plane, 1 ,
         -xf, -yf, -far_plane, 1 
    ;

    // This is the matrix that transforms each corner from camera space to light space.
    mat4 matrix = this_frame.view * inv_view;
    
    float minx = FLT_MAX;
    float maxx = -FLT_MAX;
    float miny = FLT_MAX;
    float maxy = -FLT_MAX;
    float minz = FLT_MAX;
    float maxz = -FLT_MAX;

    // For each frustum corner...
    for (uint32_t i = 0; i < 8; ++i)
    
        corners[i] = matrix * corners[i];

        minx = min(minx, corners[i].x);
        maxx = max(maxx, corners[i].x);
        miny = min(miny, corners[i].y);
        maxy = max(maxy, corners[i].y);
        minz = min(minz, corners[i].z);
        maxz = max(maxz, corners[i].z);
    

    this_frame.proj = mat4::ortho(minx, maxx, miny, maxy, minz, maxz);

两者都产生不正确但有点接近正确的结果。第二种方法似乎产生了一个更紧密的盒子。

有趣的是,当相机在光照空间中沿 x 轴移动时,两者似乎都主要工作。每当相机在光照空间中沿 y/z 轴移动时,都会得到一些非常错误的结果。

这是我正在谈论的一些图像。每对中的第一个是根据正常相机视角的场景,第二个是从灯光的角度来看的(第一个级联,我什至不会尝试第二个级联,直到第一个工作正常)。红色区域应该包含在第一个级联中,绿色区域是后面的级联,无阴影区域位于视锥体之外。

(这些屏幕截图是使用方法 #1 拍摄的)

1: http://imgur.com/FCxvDy1, http://imgur.com/WUFoEV4

2: http://imgur.com/wUTzkwO, http://imgur.com/fVvTTlG

到目前为止一切都很好,对吧?嗯……

3: http://imgur.com/Mxunbwg, http://imgur.com/yeFYxVp

4: http://imgur.com/J1FAuKv, http://imgur.com/bzrorwe

有一段时间我认为问题可能出在我的 mat4::inverted() 函数中,但我尝试了 2 种不同的实现,它们都给出了相同的结果。我目前使用的是:

mat4 mat4::inverted() const

float invdet = 1.0f / determinant();

// Note: This constructor for mat4 accepts a series of 16 floats as a row-major matrix,
// as that's simply more natural to write out,
// then converts it automatically to the correct column-major storage.
return 
    (m12*m23*m31 - m13*m22*m31 + m13*m21*m32 - m11*m23*m32 - m12*m21*m33 + m11*m22*m33) * invdet,
    (m03*m22*m31 - m02*m23*m31 - m03*m21*m32 + m01*m23*m32 + m02*m21*m33 - m01*m22*m33) * invdet,
    (m02*m13*m31 - m03*m12*m31 + m03*m11*m32 - m01*m13*m32 - m02*m11*m33 + m01*m12*m33) * invdet,
    (m03*m12*m21 - m02*m13*m21 - m03*m11*m22 + m01*m13*m22 + m02*m11*m23 - m01*m12*m23) * invdet,
    (m13*m22*m30 - m12*m23*m30 - m13*m20*m32 + m10*m23*m32 + m12*m20*m33 - m10*m22*m33) * invdet,
    (m02*m23*m30 - m03*m22*m30 + m03*m20*m32 - m00*m23*m32 - m02*m20*m33 + m00*m22*m33) * invdet,
    (m03*m12*m30 - m02*m13*m30 - m03*m10*m32 + m00*m13*m32 + m02*m10*m33 - m00*m12*m33) * invdet,
    (m02*m13*m20 - m03*m12*m20 + m03*m10*m22 - m00*m13*m22 - m02*m10*m23 + m00*m12*m23) * invdet,
    (m11*m23*m30 - m13*m21*m30 + m13*m20*m31 - m10*m23*m31 - m11*m20*m33 + m10*m21*m33) * invdet,
    (m03*m21*m30 - m01*m23*m30 - m03*m20*m31 + m00*m23*m31 + m01*m20*m33 - m00*m21*m33) * invdet,
    (m01*m13*m30 - m03*m11*m30 + m03*m10*m31 - m00*m13*m31 - m01*m10*m33 + m00*m11*m33) * invdet,
    (m03*m11*m20 - m01*m13*m20 - m03*m10*m21 + m00*m13*m21 + m01*m10*m23 - m00*m11*m23) * invdet,
    (m12*m21*m30 - m11*m22*m30 - m12*m20*m31 + m10*m22*m31 + m11*m20*m32 - m10*m21*m32) * invdet,
    (m01*m22*m30 - m02*m21*m30 + m02*m20*m31 - m00*m22*m31 - m01*m20*m32 + m00*m21*m32) * invdet,
    (m02*m11*m30 - m01*m12*m30 - m02*m10*m31 + m00*m12*m31 + m01*m10*m32 - m00*m11*m32) * invdet,
    (m01*m12*m20 - m02*m11*m20 + m02*m10*m21 - m00*m12*m21 - m01*m10*m22 + m00*m11*m22) * invdet ;



float mat4::determinant() const

    return 
        m03*m12*m21*m30 - m02*m13*m21*m30 - m03*m11*m22*m30 + m01*m13*m22*m30 +
        m02*m11*m23*m30 - m01*m12*m23*m30 - m03*m12*m20*m31 + m02*m13*m20*m31 +
        m03*m10*m22*m31 - m00*m13*m22*m31 - m02*m10*m23*m31 + m00*m12*m23*m31 +
        m03*m11*m20*m32 - m01*m13*m20*m32 - m03*m10*m21*m32 + m00*m13*m21*m32 +
        m01*m10*m23*m32 - m00*m11*m23*m32 - m02*m11*m20*m33 + m01*m12*m20*m33 +
        m02*m10*m21*m33 - m00*m12*m21*m33 - m01*m10*m22*m33 + m00*m11*m22*m33;

我做错了什么?

【问题讨论】:

如果您不确定矩阵运算的实现,您可以为其编写一些快速的单元/健全性测试代码。 您是否在 ZBuffer 中使用了 inversed-Z 值? 单独测试基本数学内容和/或使用其他人的代码,看看它是否给出相同的结果。 【参考方案1】:
vec4 corners[8] =
    
        // Near plane
         1, 1, 1, 1 ,
         -1, 1, 1, 1 ,
         1, -1, 1, 1 ,
         -1, -1, 1, 1 ,
        // Far plane
         1, 1, -1, 1 ,
         -1, 1, -1, 1 ,
         1, -1, -1, 1 ,
         -1, -1, -1, 1 
    ;

让我告诉你为什么这里是错误的。你正在寻找一个屏幕。近是深度 0,远是深度 1。

vec4 corners[8] =
    
        // Near plane
         1, 1, 0, 1 ,
         -1, 1, 0, 1 ,
         1, -1, 0, 1 ,
         -1, -1, 0, 1 ,
        // Far plane
         1, 1, 1, 1 ,
         -1, 1, 1, 1 ,
         1, -1, 1, 1 ,
         -1, -1, 1, 1 
    ;

.....// somestuff

    // For each frustum corner...
    for (uint32_t i = 0; i < 8; ++i)
    
        corners[i] = invVP * corners[i];
        corners[i] /= corners[i].w;

        ... go on....

    

如果可行,请告诉我。

【讨论】:

以上是关于在将截锥体从相机空间转换为光空间以进行阴影映射时遇到重大问题的主要内容,如果未能解决你的问题,请参考以下文章

ShaderLab-坐标转换

[ue4] 级联阴影CSM

[ue4] 级联阴影CSM

[ue4] 级联阴影CSM

带有立方体贴图的 OpenGL 点光阴影映射

[ue4] 级联阴影CSM