OpenGL:先缩放还是先平移?如何操作?

23

我有一些2D几何图形。我想在我的几何图形周围取一个边界框,然后在平面上的其他地方渲染一个更小的版本。以下是用于进行缩放和平移的代码:

// source and dest are arbitrary rectangles.
float scaleX = dest.width / source.width;
float scaleY = dest.height / source.height;
float translateX = dest.x - source.x;
float translateY = dest.y - source.y;

glScalef(scaleX, scaleY, 0.0);
glTranslatef(translateX, translateY, 0.0);
// Draw geometry in question with its normal verts.

当目标原点为0时,对于给定维度,这可以完全按预期工作。但是,如果例如x的原点是非零的,则结果仍然按比例缩放,但看起来像是在该轴上被转换到接近零的某个位置 - 实际上它与dest.x为零时有所不同。

有人能指出我忽略的显而易见的问题吗?

谢谢!

最终更新 根据Bahbar和Marcus下面的答案,我进行了更多实验并解决了这个问题。Adam Bowen的评论是提示。我遗漏了两个关键事实:

  1. 我需要围绕我关心的几何形状的中心进行缩放。
  2. 我需要按直觉的相反顺序应用变换(对我而言)。

第一个回顾起来有些显然。但对于其他像我一样的优秀程序员/糟糕的数学家来说:事实证明我的直觉是基于Red Book中称为“大型,固定坐标系统”的概念,其中存在一个绝对平面,你的几何形状在该平面上使用变换移动。这是可以的,但考虑到将多个变换堆叠成一个矩阵背后的数学性质,它与实际情况相反(请参见下面的答案或Red Book了解更多信息)。基本上,变换是按照与它们出现在代码中的顺序相反的“逆序”“应用”的。以下是最终的工作解决方案:

// source and dest are arbitrary rectangles.
float scaleX = dest.width / source.width;
float scaleY = dest.height / source.height;
Point sourceCenter = centerPointOfRect(source);
Point destCenter = centerPointOfRect(dest);

glTranslatef(destCenter.x, destCenter.y, 0.0);
glScalef(scaleX, scaleY, 0.0);
glTranslatef(sourceCenter.x * -1.0, sourceCenter.y * -1.0, 0.0);
// Draw geometry in question with its normal verts.
3个回答

20
在OpenGL中,您指定的矩阵会乘以现有矩阵的右侧,并且顶点位于表达式的最右侧。
因此,您指定的最后一个操作是几何体本身的坐标系。 (第一个通常是视图变换,即相机到世界变换的反转。)
Bahbar提出了一个很好的观点,您需要考虑缩放的中心点。 (或旋转的枢轴点。)通常,您会在那里进行平移,旋转/缩放,然后返回平移。(或者一般地,应用基础变换,操作,然后求逆。)这被称为Change of Basis,您可能想要阅读相关资料。
无论如何,为了对其工作方式有所了解,请尝试使用一些简单的值(零等),然后稍微修改它们(可能是动画),并查看输出会发生什么。然后,您就可以更容易地看到您的变换实际上正在对您的几何体做什么。
更新
关于顺序与直觉相反的问题,在初学OpenGL编码的人中间非常普遍。我一直在辅导计算机图形学课程,许多人的反应都很相似。如果您考虑在呈现变换和几何树(场景图)时使用pushmatrix / popmatrix的用途,则更容易思考OpenGL的工作方式。然后,当前的事情顺序变得相当自然,相反会使得获得有用结果变得非常困难。

1
简而言之,将顺序更改为glTranslate(dest); glScale(scale); glTranslate(source); - Adam Bowen
Adam:这似乎不像你所建议的那样工作,虽然我很感兴趣--你能详细说明为什么排序会像那样,并可能提供一个更明确的想法,相对于现有的代码来看怎么样?也许我只是误解了你的伪代码。谢谢! - Ben Zotto
没关系:在这种情况下,“不起作用”的原因是一个打错的值。修复后,我们可以开始工作了。请查看更新的问题以获取更多信息。谢谢! - Ben Zotto

6

缩放和旋转一样,是以原点为中心进行的。因此,如果您将跨越[10:20](例如在X轴上)的对象缩小一半,则会得到[5:10]。因此,该对象被缩小并移动到离原点更近的位置。这正是您观察到的。

这就是为什么通常先应用缩放(因为对象往往是围绕0定义的)。

因此,如果您想在中心点周围缩放对象,则可以将对象从中心点平移到原点,进行缩放,然后再平移回来。

顺便说一下,如果您先进行平移,然后再进行缩放,则您的缩放将应用于之前的平移,这就是您可能使用此方法时遇到问题的原因。


谢谢您的回复。您能否为一个矩阵数学白痴详细说明一下,我应该执行哪些操作来进行适当的原点平移、缩放和返回平移?我尝试在绘制几何图形之前依次堆叠这三个操作(先平移源原点的负值,然后按比例缩放,最后按目标原点平移),但是没有看到任何可见结果。 - Ben Zotto
啊,也许我需要相对于矩形中心进行翻译,而不是相对于原点。我会尝试这样做。 - Ben Zotto
1
不行。不能通过“源的负中心翻译-缩放-目标的正中心翻译”来进行翻译。 - Ben Zotto

1

我还没有使用过OpenGL ES,只是稍微用了一下OpenGL。

听起来你想要从不同的位置进行变换,而不是从原点开始,不太确定,但你可以尝试在glPushMatrix()和glPopMatrix()之间进行变换和绘制。

e.g.

// source and dest are arbitrary rectangles.
float scaleX = dest.width / source.width;
float scaleY = dest.height / source.height;
float translateX = dest.x - source.x;
float translateY = dest.y - source.y;

glPushMatrix();
  glScalef(scaleX, scaleY, 0.0);
  glTranslatef(translateX, translateY, 0.0);
  // Draw geometry in question with its normal verts.
  //as if it were drawn from 0,0
glPopMatrix();

这是我写的一个简单的Processing草图,用来说明这一点:
import processing.opengl.*;
import javax.media.opengl.*;


void setup() {
  size(500, 400, OPENGL);
}

void draw() {
  background(255);
  PGraphicsOpenGL pgl = (PGraphicsOpenGL) g;
  GL gl = pgl.beginGL();  


  gl.glPushMatrix();
    //transform the 'pivot'
    gl.glTranslatef(100,100,0);
    gl.glScalef(10,10,10);
    //draw something from the 'pivot'
    gl.glColor3f(0, 0.77, 0);
    drawTriangle(gl);
  gl.glPopMatrix();
  //matrix poped, we're back to orginin(0,0,0), continue as normal
  gl.glColor3f(0.77, 0, 0);
  drawTriangle(gl);
  pgl.endGL();
}

void drawTriangle(GL gl){
  gl.glBegin(GL.GL_TRIANGLES);
  gl.glVertex2i(10, 0);
  gl.glVertex2i(0, 20);
  gl.glVertex2i(20, 20);
  gl.glEnd();
}

这里是运行草图的图像,绘制了相同的绿色三角形,并应用了平移和缩放,然后是红色三角形,在 push/pop“块”之外,因此不受变换的影响:

alt text

希望有所帮助, 乔治


谢谢George,但是push/pop并不是问题所在;我已经在代码之外进行了push/pop。不过感谢你用图片进行说明!这是一个很好的可视化。 - Ben Zotto

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接