如何相对于视图中心旋转OpenGL视图而不是显示对象的中心?

时间:2012-02-20 20:45:58

标签: objective-c opengl

我正在研究fork of Pleasant3D

当旋转正在显示的对象时,即使该点不在视图的中心,对象也始终围绕相对于自身的相同点旋转(例如,因为用户已平移以在视图中移动对象)。 / p>

我想更改此设置,以便视图始终围绕视图中心的点旋转对象,因为它对用户而不是对象的中心显示。

以下是当前代码围绕其中心旋转对象的核心(略微简化)(来自here):

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以相反的顺序执行矩阵乘法”(来自here)。这意味着要应用的第一个转换实际上是上面代码中的最后一个转换。它将视图的中心(0,0)移动到对象的中心。

然后将此点用作接下来两次转换(旋转)的旋转中心。

最后,midPlatform翻译反向完成,将中心移回原始位置,并应用用户完成的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 FAQ(http://www.opengl.org/resources/faq/technical/transformations.htm)中找到了我认为的一些线索,例如:

  

9.070如何围绕固定坐标系而不是对象的局部坐标系转换对象?

     

如果围绕Y轴旋转对象,您会发现X轴和Z轴随对象旋转。围绕其中一个轴的后续旋转围绕新变换的轴而不是原始轴旋转。通常需要在固定坐标系中执行变换而不是对象的局部坐标系。

     

问题的根本原因是OpenGL矩阵运算后置到矩阵堆栈上,从而导致在对象空间中发生变换。要影响屏幕空间转换,您需要预乘。 OpenGL不为矩阵乘法的顺序提供模式切换,因此您需要手动预乘。应用程序可以通过在每个帧之后检索当前矩阵来实现此目的。应用程序将下一帧的新变换乘以单位矩阵,并使用glMultMatrix()将累积的当前变换(从最后一帧)乘以这些变换。

     

您需要注意,每帧检索一次ModelView矩阵可能会对应用程序的性能产生不利影响。但是,您需要对此操作进行基准测试,因为性能会因实现而异。

  

9.120如何找到仅由ModelView矩阵转换的顶点的坐标?

     

获取顶点的眼睛坐标空间值(即由ModelView矩阵变换的对象空间顶点)通常很有用。您可以通过检索当前的ModelView矩阵并执行简单的向量/矩阵乘法来获得此结果。

但我不确定如何在我的情况下应用这些。

1 个答案:

答案 0 :(得分:4)

您需要将“视点中心”点转换/转换为原点,旋转,然后将该转换反转,返回到对象的变换。这在线性代数中称为 basis change

如果你有一个合适的3d数学库(我假设你有一个),这样更容易使用,这也有助于远离已弃用的固定管道API。 (稍后会详细介绍)。

我是这样做的:

  1. 找到世界坐标中视点中心的变换(弄清楚,然后绘制它以确保它是正确的,x,y,z轴也是如此,因为轴应该是正确的wrt风景)。如果使用视点中心点和旋转(通常是相机旋转的倒数),这将是从世界原点到视图中心的转换。将其存储在4x4矩阵变换中。

  2. 应用上述变换的逆,使其成为原点。 glMultMatrixfv(center_of_view_tf.inverse());

  3. 根据需要旋转此点(glRotate()

  4. 将所有内容转换回世界空间(glMultMatrixfv(center_of_view_tf);

  5. 应用对象自己的世界变换(glTranslate/glRotateglMultMatrix)并绘制它。


  6. 关于固定功能管道

    回到过去,有一些单独的晶体管用于转换顶点(或它的纹理坐标),计算光线相对于它应用光线(最多8个)和以多种不同方式纹理碎片的位置。简单地说,glEnable(),启用固定的硅块,在硬件图形管道中进行一些计算。随着性能的提高,芯片尺寸缩小,人们需要更多功能,专用芯片的数量也在增长,其中大部分都没有使用。

    最终,它变得非常先进,你可以用相当猥亵的方式编程(任何人都可以注册组合器)。然后,为所有顶点级转换实际上传一个小的汇编程序变得可行。然后,它意味着在那里保留大量的硅只做一件事(特别是因为你可以使用这些晶体管来使可编程的东西更快),所以一切都变得可编程。如果调用“固定功能”渲染,则驱动程序只是将状态(X光,纹理投影等)转换为着色器代码并将其作为顶点着色器上传。

    所以,目前,甚至片段处理都是可编程的,只有很多固定功能选项被大量的OpenGL应用程序使用,但GPU上的芯片只运行着色器(而且很多都是,并行)。

    ...

    为了使OpenGL更高效,驱动程序体积更小,硬件更简单,可在移动/控制台设备上使用,并充分利用OpenGL目前运行的可编程硬件,现在标记了API中的许多功能弃用。它们不适用于OpenGL ES 2.0及更高版本(移动),即使在台式机系统上也无法获得最佳性能(在未来的日子里它们仍将处于驱动程序中,同时提供同样古老的代码库回到加速的3D图形的曙光)

    固定功能主要涉及如何通过OpenGL中的“默认”(即glEnable(GL_LIGHTING))完成变换/照明/纹理等,而不是在自定义着色器中指定这些操作。

    在新的可编程OpenGL中,变换矩阵只是着色器中的制服。任何rotate / translate / mult / inverse(如上所述)应该在上传到OpenGL之前由客户端代码(您的代码)完成。 (仅使用 glLoadMatrix是开始考虑它的一种方法,但不是在着色器中使用gl_ModelViewProjectionMatrix和同类,而是使用自己的制服。)

    这有点麻烦,因为你必须实现之前GL驱动程序所做的相当多的工作,但是如果你有自己的对象列表/图形,其中包含变换和某个变换等,那就不是那么多工作。 (OTOH,如果你的代码中有很多glTranslate / glRotate,它可能是......)。正如我所说,一个好的3D数学库在这里是不可或缺的。

    - ..

    因此,要将上面的代码更改为“可编程管道”样式,您只需在自己的代码中执行所有这些矩阵乘法(而不是GL驱动程序,仍然在CPU上),然后发送生成的矩阵在激活着色器并从VBO中绘制对象之前,将其打开为制服。

    (请注意,现代卡片没有固定功能代码,驱动程序中只有很多代码可以将固定功能渲染状态编译为完成工作的着色器。难怪“经典”GL驱动程序非常庞大.. 。)

    ...

    有关此流程的一些信息,请访问Tom's Hardware Guide,也可能是Google。