【问题标题】:How do I rotate an OpenGL view relative to the center of the view as opposed to the center of the object being displayed?如何相对于视图中心而不是正在显示的对象的中心旋转 OpenGL 视图?
【发布时间】:2025-12-26 12:00:11
【问题描述】:

我正在处理fork of Pleasant3D

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

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

这是当前围绕其中心旋转对象的代码的核心(略微简化)(来自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 常见问题解答 (http://www.opengl.org/resources/faq/technical/transformations.htm) 中找到了我认为的一些线索,例如:

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

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

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

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

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

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

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

【问题讨论】:

  • 呃.... 已弃用的功能和固定功能管道。你只求麻烦……
  • 我愿意考虑从头开始重写这个逻辑,如果我可能能够正确地做到这一点并且不会在项目的这一部分上花费过多的时间(特别是如果我现在可以只换掉转换逻辑并保持绘图逻辑不变)。请记住,这是我第一次涉足 OpenGL,是否有关于如何在不使用“[d] 弃用功能和固定功能管道”的情况下正确执行此操作的好教程? (如果它解释了固定功能管道是什么以及为什么它是一件坏事,则可以加分。)
  • (老实说,我更喜欢基于现有代码的解决方案正确方法的教程。)

标签: objective-c opengl


【解决方案1】:

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

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

我会这样做:

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

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

  3. 随心所欲地围绕这一点旋转 (glRotate())

  4. 将一切转换回世界空间 (glMultMatrixfv(center_of_view_tf);)

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


关于固定功能管道

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

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

因此,目前,即使片段处理也是可编程的,只有大量固定功能选项被大量 OpenGL 应用程序使用,但 GPU 上的芯片只运行着色器(还有很多,并行)。

...

为了使 OpenGL 更高效,驱动程序更小,硬件更简单,可在移动/控制台设备上使用,并充分利用 OpenGL 当今运行的可编程硬件,API 中的许多功能现在都被标记了已弃用。它们在 OpenGL ES 2.0 及更高版本(移动)上不可用,即使在桌面系统上,您也无法从中获得最佳性能(它们仍将在未来的驱动程序中,服务于同样古老的代码库回到加速 3D 图形的曙光)

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

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

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

-..

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

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

...

有关此过程的一些信息可在 Tom's Hardware Guide 和 Google 上获得。

【讨论】:

  • 这看起来像是我需要的信息。非常感谢。一旦我在我的代码中实现它并确保它有效,我就会接受。固定功能管道信息也很有趣,但在我理解你所说的很多内容之前,我必须做更多的研究/学习。我确实知道现在应该在代码中完成翻译/旋转以生成矩阵,然后将这些矩阵馈送到 OpenGL。
  • 我还没有完成这项工作,但这肯定对我有帮助,所以我会继续接受它。我想我已经弄清楚了如何在视图中心找到顶点(基于 Open GL FAQ 9.120),但还没有弄清楚如何包含正确的相机平移。
最近更新 更多