用于平铺渲染的 OpenGL 矩阵设置

OpenGL matrix setup for tiled rendering

阅读 datenwolf's 2011 answer concerning tile-based render setup in OpenGL 后,我尝试实施他的解决方案。源图像看起来像这样(800 x 600)

生成的图像有 2x2 个图块,每个图块的尺寸为 800 x 600,如下所示。

如您所见,它们并不完全匹配,但我可以看出发生了一些隐约有趣的事情。我确定我在某处犯了一个基本错误,但我不太明白。

我正在做 4 遍,其中:

w, h are 2,2 (2x2 tiles)    
x, y are (0,0) (1,0) (0,1) and (1,1) in each of the 4 passes    
MyFov is 1.30899692 (75 degrees)
MyWindowWidth, MyWindowHeight are 800, 600
MyNearPlane, MyFarPlane are 0.1, 200.0

计算每个图块的平截头体的算法是:

auto aspect = static_cast<float>(MyWindowWidth) / static_cast<float>(MyWindowHeight);
auto right = -0.5f * Math::Tan(MyFov) * MyShaderData.Camera_NearPlane;
auto left = -right;
auto top = aspect * right;
auto bottom = -top;
auto shift_X = (right - left) / static_cast<float>(w);
auto shift_Y = (top - bottom) / static_cast<float>(h);
auto frustum = Math::Frustum(left   + shift_X * static_cast<float>(x),
                             left   + shift_X * static_cast<float>(x + 1), 
                             bottom + shift_Y * static_cast<float>(y),
                             bottom + shift_Y * static_cast<float>(y + 1),
                             MyShaderData.Camera_NearPlane,
                             MyShaderData.Camera_FarPlane);

,其中 Math::Frustum 是:

template<class T>
Matrix4x4<T> Frustum(T left, T right, T bottom, T top, T nearPlane, T farPlane)
{
    Matrix4x4<T> r(InitialiseAs::InitialiseZero);

    r.m11 = (static_cast<T>(2) * nearPlane) / (right - left);
    r.m22 = (static_cast<T>(2) * nearPlane) / (top - bottom);
    r.m31 = (right + left) / (right - left);
    r.m32 = (top + bottom) / (top - bottom);
    r.m33 = -(farPlane + nearPlane) / (farPlane - nearPlane);
    r.m34 =   static_cast<T>(-1);
    r.m43 = -(static_cast<T>(2) * farPlane * nearPlane) / (farPlane - nearPlane);

    return r;
}

为了完整起见,我的 Matrx4x4 布局是:

struct
{
    T m11, m12, m13, m14;
    T m21, m22, m23, m24;
    T m31, m32, m33, m34;
    T m41, m42, m43, m44;
};

谁能发现我的错误?

编辑:

所以 derhass 向我解释了 - 一种更简单的做事方式是简单地缩放和平移投影矩阵。为了进行测试,我修改了我的翻译矩阵,按比例放大了 2 倍,如下所示(为每个图块更改翻译):

auto scale = Math::Scale(2.f, 2.f, 1.f);
auto translate = Math::Translate(0.5f, 0.5f, 0.f);
auto projection = Math::Perspective(MyFov,
                                    static_cast<float>(MyWindowWidth) / static_cast<float>(MyWindowHeight),
                                    MyShaderData.Camera_NearPlane,
                                    MyShaderData.Camera_FarPlane);          

MyShaderData.Camera_Projection = scale * translate * projection;

生成的图像如下(将 4 个拼接在一起)- 我认为图像中的不连续是由 post 处理造成的,所以这是我可能在某个时候必须处理的另一个问题。

这不是问题的真正答案,但对于您在此处尝试解决的问题,它可能是一种有用的替代方法。在我看来,datenwolf 在他对您所指的 Whosebug question 的回答中的解决方案比它需要的更复杂。所以我在这里展示我的替代方案。

Forword:我采用标准的 OpenGL 矩阵约定,因此使用矩阵 M 的顶点变换完成为 v'= M *v(就像固定功能管道所做的那样)。

当使用某些投影矩阵渲染场景时 P,您可以通过应用缩放和变换操作来提取所述场景的任何轴对齐的子矩形 after 应用了投影矩阵。

重点是在NDCspace中,观看体积被定义为[-1,1]^3立方体。剪辑 space(P 将数据转换为)只是该体积的同质表示。由于典型的 4x4 变换矩阵都在齐次 space 中工作,我们根本不需要关心 w 并且可以简单地定义变换,就好像我们在 NDC space.

由于您只需要一些 2D 平铺,z 应该保持原样,只需要在 xy 中进行一些缩放和平移。当将转换 AB 组合成单个矩阵 C 作为 C=A*B 时,遵循上述约定,这会导致首先应用 B,然后 [=21] =] 最后(自 C*v == A*B*v == A*(B*v))。所以要修改 after 投影的结果,我们必须 pre-乘以一些变换到 P 我们就完成了:

P'=S(sx,sy,1) * T(tx,ty,0) * P

P' 的构造将适用于任何有效的投影矩阵 P,无论它是透视变换还是正交变换。在正交情况下,它的作用非常清楚。在透视的情况下,这实际上改变了视野,也将平截头体变成了不对称的。

当您想要将图像平铺成 mn 段的网格时。很明显sx=msy=n。正如我确实使用 S * T 顺序(通过选择)一样,T 在比例之前应用,因此对于每个图块,(tx,ty) 只是将图块的中心移动到新的矢量中心(这将是原点)。由于 NDC space 是 2 个单位宽和高,对于一个瓦片 x,y,变换是

tx= - (-1 + 2/(2*m) + (2/m) * x)
ty= - (-1 + 2/(2*n) + (2/n) * y)
//     ^     ^         ^    
//     |     |         |
//     |     |         +- size of of each tile in NDC space
//     |     |
//     |     +- half the size (as the center offset)
//     |
//     +- left/bottom border of NDC space