在我的业余爱好者基于着色器(非 FFP)的 GL(3.2+ 核心)“引擎”中,世界空间和模型空间中的一切都是“左撇子”(并保持这种方式),所以 X 轴从 -1(“左”)到 1(“右”),Y 从 -1(“下”)到 1(“上”),Z 从 -1(“近”)到 1(“远”) .
现在,默认情况下,在 OpenGL 中,NDC 空间的工作方式相同,但剪辑空间没有,据我所知,这里的 z 从 1(“近”)延伸到 -1(“远”)。
同时,我想理想地继续使用“有点像非官方准标准”的矩阵函数来进行观察和透视,目前定义为:
func (me *Mat4) Lookat(eyePos, lookTarget, upVec *Vec3) {
l := lookTarget.Sub(eyePos)
l.Normalize()
s := l.Cross(upVec)
s.Normalize()
u := s.Cross(l)
me[0], me[4], me[8], me[12] = s.X, u.X, -l.X, -eyePos.X
me[1], me[5], me[9], me[13] = s.Y, u.Y, -l.Y, -eyePos.Y
me[2], me[6], me[10], me[14] = s.Z, u.Z, -l.Z, -eyePos.Z
me[3], me[7], me[11], me[15] = 0, 0, 0, 1
}
// a: aspect ratio. n: near-plane. f: far-plane.
func (me *Mat4) Perspective(fovY, a, n, f float64) {
s := 1 / math.Tan(DegToRad(fovY)/2) // scaling
me[0], me[4], me[8], me[12] = s/a, 0, 0, 0
me[1], me[5], me[9], me[13] = 0, s, 0, 0
me[2], me[6], me[10], me[14] = 0, 0, (f+n)/(n-f), (2*f*n)/(n-f)
me[3], me[7], me[11], me[15] = 0, 0, -1, 0
}
因此,为了让我的世界空间相机(正-Z)与查看(负-Z)一起工作,按照这个伪代码:
// world-space position:
camPos := cam.Pos
// normalized direction-vector, up/right/forward are 1 not -1:
camTarget := cam.Dir
// lookat-target:
camTarget.Add(&camPos)
// invert both Z:
camPos.Z, camTarget.Z = -camPos.Z, -camTarget.Z
// compute lookat-matrix:
cam.mat.Lookat(&camPos, &camTarget, &Vec3{0, 1, 0})
效果很好。在所有 6 个自由度中移动相机会产生正确的屏幕移动和正确的新相机世界空间坐标。
但是几何图形仍然在 Z 轴上反转。当我定位两个框时,A 在 (-2, 1, -2) 处出现在靠近左侧,B (2, 1, 2) 出现在最右侧,然后 A 出现在最左侧,B 出现在靠近右侧. Z 在这里仍然是倒置的。
现在,这些节点有自己的世界空间坐标,并从他们自己的模型到世界矩阵更新。我不应该在那里反转 posZ,因为它们形成了一个子节点的层次结构,它们与它们的父变换以及所有这些相乘。他们仍然在模型或世界空间中,根据我的法令,它们是左撇子。
他们从世界到相机的计算发生在我最后的 CPU 上,而不是在只获得一个最终(mvp/剪辑空间)矩阵的顶点着色器中。
当这种情况发生时——世界空间对象矩阵与剪辑空间观察和投影矩阵的乘法——那时我需要以某种方式反转 Z。
最好的方法是什么?或者,更一般地说,什么是常见的工作方式?我是否必须修改投影以接受左手但输出到 GL 右手?如果是这样,怎么做?然后我是否也必须修改lookat?有没有一种聪明的方法来做这一切,而不必修改有点标准的观察/投影矩阵,同时也保持左手坐标的模型变换矩阵?