我正在研究Pleasant3D的一个分支。
当旋转正在显示的对象时,对象总是相对于自身围绕同一点旋转,即使该点不在视图的中心(例如,因为用户已平移以移动视图中的对象(。
我想更改此设置,以便视图始终围绕用户显示的视图中心的点而不是对象的中心旋转对象。
以下是围绕其中心旋转对象的当前代码的核心(略微简化((从这里开始(:
glLoadIdentity();
// midPlatform is the offset to reach the "middle" of the object (or more specifically the platform on which the object sits) in the x/y dimension.
// This the point around which the view is currently rotated.
Vector3 *midPlatform = [self.currentMachine calcMidBuildPlatform];
glTranslatef((GLfloat)cameraTranslateX - midPlatform.x,
(GLfloat)cameraTranslateY - midPlatform.y,
(GLfloat)cameraOffset);
// trackBallRotation and worldRotation come from trackball.h/c which appears to be
// from an Apple OpenGL sample.
if (trackBallRotation[0] != 0.0f) {
glRotatef (trackBallRotation[0], trackBallRotation[1], trackBallRotation[2], trackBallRotation[3]);
}
// accumlated world rotation via trackball
glRotatef (worldRotation[0], worldRotation[1], worldRotation[2], worldRotation[3]);
glTranslatef(midPlatform.x, midPlatform.y, 0.);
// Now draw object...
我需要以什么顺序应用哪些转换才能获得我想要的效果?
到目前为止我尝试过的一些内容
据我了解,这就是当前代码的作用:
"如果将多个变换应用于一个顶点,OpenGL 以相反的顺序执行矩阵乘法"(从这里开始(。这意味着要应用的第一个转换实际上是上面代码中的最后一个转换。它将视图的中心 (0,0( 移动到对象的中心。
然后将该点用作接下来两次变换(旋转(的旋转中心。
最后,反向完成中间平台平移,将中心移回原始位置,并应用用户完成的XY平移(平移(。在这里,"相机"也从对象移动到正确的位置(由cameraOffset指示(。
这似乎很简单。所以我需要改变的是,而不是将视图的中心转换为对象的中心(midPlatform
(,我需要将其转换为用户看到的视图的当前中心,对吧?
不幸的是,这就是转换开始以有趣的方式相互影响的地方,我遇到了麻烦。
我尝试将代码更改为以下内容:
glLoadIdentity();
glTranslatef(0,
0,
(GLfloat)cameraOffset);
if (trackBallRotation[0] != 0.0f) {
glRotatef (trackBallRotation[0], trackBallRotation[1], trackBallRotation[2], trackBallRotation[3]);
}
// accumlated world rotation via trackball
glRotatef (worldRotation[0], worldRotation[1], worldRotation[2], worldRotation[3]);
glTranslatef(cameraTranslateX, cameraTranslateY, 0.);
换句话说,我将视图的中心平移到上一个中心,围绕该中心旋转,然后应用相机偏移将相机移动到正确的位置。这使得旋转的行为完全符合我想要的方式,但它引入了一个新问题。现在,用户完成的任何平移都是相对于对象的。例如,如果旋转对象以使摄像机沿 X 轴端到端地看,如果用户从左向右平移,则对象似乎正在远离用户,而不是向左或向右移动。
我想我可以理解为什么是(旋转前应用 XY 相机平移(,我认为我需要做的是想办法在旋转后取消旋转前的平移(以避免奇怪的平移效果(,然后做另一个相对于查看器(眼睛坐标空间(而不是对象(对象坐标空间(的平移,但我不确定具体如何这样做。
我在OpenGL常见问题解答(http://www.opengl.org/resources/faq/technical/transformations.htm(中找到了一些我认为是线索,例如:
9.070 如何围绕固定坐标系而不是对象的局部坐标系变换对象?
如果围绕对象的 Y 轴旋转对象,您会发现 X 轴和 Z 轴随对象一起旋转。围绕其中一个轴的后续旋转将围绕新转换的轴旋转,而不是围绕原始轴旋转。通常需要在固定坐标系而不是对象的局部坐标系中执行变换。
问题的根本原因是 OpenGL 矩阵操作在矩阵堆栈上后乘,从而导致对象空间中发生转换。若要影响屏幕空间转换,需要预乘。OpenGL 不提供矩阵乘法顺序的模式开关,因此您需要手动预乘法。应用程序可以通过在每一帧后检索当前矩阵来实现这一点。该应用程序在单位矩阵之上为下一帧乘以新的转换,并使用glMultMatrix((将累积的当前转换(从最后一帧开始(乘以这些转换。
您需要知道,每帧检索一次 ModelView 矩阵可能会对应用程序的性能产生不利影响。但是,您需要对此操作进行基准测试,因为性能因实现而异。
和
9.120 如何找到仅由模型视图矩阵转换的顶点的坐标?
获取顶点(即由 ModelView 矩阵转换的对象空间顶点(的眼图坐标空间值通常很有用。您可以通过检索当前模型视图矩阵并执行简单的向量/矩阵乘法来获得此目的。
但我不确定如何在我的情况下应用这些。
将"视中心"点转换/平移为原点,旋转,然后反转该平移,回到对象的变换。这被称为线性代数中的基变化。
如果你有一个合适的 3D 数学库(我假设你有一个(,这也有助于远离弃用的固定管道 API。(稍后会详细介绍(。
以下是我的做法:
在世界坐标中找到视点中心的变换(弄清楚,然后绘制它以确保它是正确的,x,y,z 轴也是如此,因为 axii 应该是正确的 w.r.t. 视图(。如果使用视中心点和旋转(通常是摄像机旋转的反转(,这将是从世界原点到视中心的变换。将其存储在 4x4 矩阵转换中。
应用上述变换的逆变换,使其成为原点。
glMultMatrixfv(center_of_view_tf.inverse());
随心所欲地围绕此点旋转 (
glRotate()
(将一切变换回世界空间 (
glMultMatrixfv(center_of_view_tf);
(应用对象自己的世界变换(
glTranslate/glRotate
或glMultMatrix
(并绘制它。
关于固定函数管道
在过去,有单独的晶体管用于转换顶点(或其纹理坐标(,计算光与它的关系,应用光(最多 8 个(并以许多不同的方式对碎片进行纹理处理。简单地说,glEnable((使固定的硅块能够在硬件图形管道中进行一些计算。随着性能的提高,芯片尺寸的缩小,人们需要更多的功能,专用硅的数量也在增长,其中大部分没有被使用。
最终,它变得如此先进,以至于你可以用相当淫秽的方式对它进行编程(注册任何人的合并器(。然后,为所有顶点级转换实际上传一个小型汇编程序变得可行。然后,在那里保留大量只做一件事的硅是有意义的(特别是因为你可以使用这些晶体管来使可编程的东西更快(,所以一切都变得可编程。如果需要"固定函数"呈现,驱动程序只需将状态(X 光源、纹理投影等(转换为着色器代码,并将其作为顶点着色器上传。
因此,目前,即使是片段处理也是可编程的,大量的OpenGL应用程序也使用了很多固定功能选项,但GPU上的芯片只是运行着色器(而且很多是并行的(。
。
为了使 OpenGL更高效,驱动程序体积更小,硬件更简单且可在移动/控制台设备上使用,并充分利用 OpenGL 目前运行的可编程硬件,API 中的许多函数现在被标记为已弃用。它们在OpenGL ES 2.0及更高版本(移动(上不可用,即使在桌面系统上,您也无法从中获得最佳性能(它们仍将在未来很长一段时间内存在于驱动程序中,为同样古老的代码库提供服务,这些代码库可以追溯到加速3D图形的黎明(
固定功能主要涉及如何在OpenGL中"默认"完成变换/照明/纹理等(即 glEnable(GL_LIGHTING)
(,而不是在自定义着色器中指定这些操作。
在新的可编程OpenGL中,变换矩阵只是着色器中的均匀矩阵。任何旋转/平移/多/反转(如上所述(都应该在上传到OpenGL之前由客户端代码(您的代码(完成。(仅使用 glLoadMatrix 是开始考虑它的一种方法,但不要在着色器中使用gl_ModelViewProjectionMatrix之类的东西,而是使用您自己的制服。
这有点麻烦,因为您必须实现之前由 GL 驱动程序完成的相当多的事情,但是如果您有自己的对象列表/图形,其中包含转换和某处的转换等,那就没有那么多工作了。(OTOH,如果你的代码中有很多glTranslate/glRotate,它可能是......正如我所说,一个好的3D数学库在这里是必不可少的。
-..
因此,要将上面的代码更改为"可编程管道"样式,您只需在自己的代码中执行所有这些矩阵乘法(而不是 GL 驱动程序仍在 CPU 上执行此操作(,然后将生成的矩阵作为统一发送到 opengl,然后再激活着色器并从 VBO 绘制对象。
(请注意,现代卡片没有固定函数代码,只有驱动程序中的大量代码,用于将固定函数呈现状态编译为执行工作的着色器。难怪"经典"GL 驱动程序很大......
。
有关此过程的一些信息可在Tom's Hardware Guide和Google上找到。