可以围绕物体自身的轴旋转,而不是围绕基座标轴旋转吗?

14

我正在按照谷歌提供的OpenGL ES旋转示例来旋转我的Android应用程序中的简单正方形(不是立方体),例如这段代码:

gl.glRotatef(xrot, 1.0f, 0.0f, 0.0f);   //X
gl.glRotatef(yrot, 0.0f, 1.0f, 0.0f);   //Y
gl.glRotatef(zrot, 0.0f, 0.0f, 1.0f);   //Z

如果您只围绕一个轴旋转,则可以正常工作。

但是,如果您先围绕一个轴旋转,然后再围绕另一个轴旋转,则旋转效果不佳。我的意思是旋转会在基础(全局)坐标系的轴上进行,而不是正方形自己的坐标系中。

使用代码编辑器为 Shahbaz 编辑:

public void onDrawFrame(GL10 gl) {
    //Limpiamos pantalla y Depth Buffer
    gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);    
    gl.glLoadIdentity();
    //Dibujado
    gl.glTranslatef(0.0f, 0.0f, z);         //Move z units into the screen
    gl.glScalef(0.8f, 0.8f, 0.8f);          //Escalamos para que quepa en la pantalla
    //Rotamos sobre los ejes.
    gl.glRotatef(xrot, 1.0f, 0.0f, 0.0f);   //X
    gl.glRotatef(yrot, 0.0f, 1.0f, 0.0f);   //Y
    gl.glRotatef(zrot, 0.0f, 0.0f, 1.0f);   //Z
    //Dibujamos el cuadrado
    square.draw(gl);    
    //Factores de rotación.
    xrot += xspeed;
    yrot += yspeed;
}

正方形的绘制:

    public void draw(GL10 gl) {
    gl.glFrontFace(GL10.GL_CCW);
    //gl.glEnable(GL10.GL_BLEND);
    //Bind our only previously generated texture in this case
    gl.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
    //Point to our vertex buffer
    gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);
    gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
    //Enable vertex buffer
    gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    //Draw the vertices as triangle strip
    gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);
    //Disable the client state before leaving
    gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
    gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
    //gl.glDisable(GL10.GL_BLEND);

}

顶点缓冲区数值:

private FloatBuffer vertexBuffer;

private float vertices[] = 
{ 
    -1.0f, -1.0f, 0.0f,     //Bottom Left
    1.0f, -1.0f, 0.0f,      //Bottom Right
    -1.0f, 1.0f, 0.0f,      //Top Left
    1.0f, 1.0f, 0.0f        //Top Right
};
.
.
.
public Square(int resourceId) {
        ByteBuffer byteBuf = ByteBuffer.allocateDirect(vertices.length * 4);
        byteBuf.order(ByteOrder.nativeOrder());
        vertexBuffer = byteBuf.asFloatBuffer();
        vertexBuffer.put(vertices);
        vertexBuffer.position(0);
.
.
.
5个回答

28

你首先要知道的是,在OpenGL中,变换矩阵是从右边乘起来的。这意味着你最后写的变换会首先应用于对象。

因此,让我们看一下你的代码:

gl.glScalef(0.8f, 0.8f, 0.8f);
gl.glTranslatef(0.0f, 0.0f, -z);
gl.glRotatef(xrot, 1.0f, 0.0f, 0.0f);   //X
gl.glRotatef(yrot, 0.0f, 1.0f, 0.0f);   //Y
gl.glRotatef(zrot, 0.0f, 0.0f, 1.0f);   //Z
gl.glTranslatef(0.0f, 0.0f, z);

square.draw(gl);    
这意味着首先将对象移动到(0.0f, 0.0f, z),然后围绕 Z、Y、X 轴旋转,并最后通过(0.0f, 0.0f, -z)移动并进行缩放。
你正确地实现了缩放。你把它放在第一位,所以它最后被应用。你也做对了。
gl.glTranslatef(0.0f, 0.0f, -z);

在正确的位置上,因为您首先希望旋转该对象,然后再移动它。请注意,当您旋转一个对象时,它始终围绕基本坐标(0,0,0)旋转。如果您想围绕对象自身的轴旋转该对象,则对象本身应位于(0,0,0)。

因此,在编写前面的代码之前,请确保将对象放置在正确的位置上以实现所需的旋转效果。

square.draw(gl);

你应该有旋转变换。现在你的代码方式是移动对象(通过编写)

gl.glTranslatef(0.0f, 0.0f, z);

square.draw(gl);之前执行旋转会导致混乱。删除该行代码可以更接近你所需要的效果。因此,你的代码将如下所示:

gl.glScalef(0.8f, 0.8f, 0.8f);
gl.glTranslatef(0.0f, 0.0f, -z);
gl.glRotatef(xrot, 1.0f, 0.0f, 0.0f);   //X
gl.glRotatef(yrot, 0.0f, 1.0f, 0.0f);   //Y
gl.glRotatef(zrot, 0.0f, 0.0f, 1.0f);   //Z

square.draw(gl);    
现在正方形应该在原地旋转。
注意:运行后,您会发现正方形的旋转可能相当尴尬。例如,如果您围绕z轴旋转90度,那么绕x轴旋转看起来就像绕y轴旋转一样,因为之前进行了旋转。目前,这可能对您来说还可以,但如果您想让它看起来真的很好,您应该这样做:
想象一下,您不是在旋转物体,而是在绕物体旋转摄像机,观察物体。通过改变 xrot、yrot 和 zrot,您将摄像机移动到物体周围的球体上。然后,找出摄像机的位置后,您可以计算正确的参数调用 glRotatef 和 glTranslatef,或者使用 gluLookAt。
这需要一些数学和3D想象力的理解。所以如果您第一天没有理解,请不要感到沮丧。
编辑:这就是如何沿旋转的对象坐标轴旋转的想法;
首先,假设您围绕z轴旋转。因此您有
gl.glRotatef(zrot, 0.0f, 0.0f, 1.0f);   //Z

现在,全局的Y轴单位向量显然是(0, 1, 0),但是物体已经旋转因此它的Y轴单位向量也随之旋转了。该向量由以下公式给出:

[cos(zrot) -sin(zrot) 0]   [0]   [-sin(zrot)]
[sin(zrot)  cos(zrot) 0] x [1] = [ cos(zrot)]
[0          0         1]   [0]   [ 0        ]

因此,你围绕 y 轴的旋转应该像这样:

gl.glRotatef(yrot, -sin(zrot), cos(zrot), 0.0f);   //Y-object

到目前为止,您可以尝试这个方法(禁用围绕x的旋转),并且可以看到它看起来像您想要的方式(我已经尝试过,它有效)。

现在对于x,事情变得非常复杂。为什么?因为X单位向量不仅首先围绕z向量旋转,而且在它围绕(-sin(zrot), cos(zrot), 0)向量后,再次旋转。

因此,现在对象坐标系中的X单位向量是

                   [cos(zrot) -sin(zrot) 0]   [1]                      [cos(zrot)]
Rot_around_new_y * [sin(zrot)  cos(zrot) 0] x [0] = Rot_around_new_y * [sin(zrot)]
                   [0          0         1]   [0]                      [0        ]

我们把这个向量称为(u_x, u_y, u_z)。那么你最后的旋转(绕X轴)会像这样:

gl.glRotatef(xrot, u_x, u_y, u_z);   //X-object

那么如何找到矩阵Rot_around_new_y呢?请参考这里中关于任意轴旋转的部分6.2,获得第一个矩阵的3*3子矩阵旋转(忽略与平移相关的最右边一列),并将(-sin(zrot), cos(zrot), 0)作为(u, v, w)轴,将yrot作为theta

我不会在这里进行数学计算,因为需要付出很多努力,最终我也可能会在某个地方犯错误。但是,如果你非常仔细并准备好多次检查它们,你可以将其写下来并进行矩阵乘法。


额外说明:另一个计算Rot_around_new_y的方法也可以使用四元数。四元数定义为4维向量[xs,ys,zs,c],它对应于围绕[x,y,z]的旋转,其sinscosc

其中[x,y,z]是我们的“新Y”,即[-sin(zrot),cos(zrot),0]。角度为yrot。 围绕Y轴旋转的四元数因此给出为:

q_Y = [-sin(zrot)*sin(yrot), cos(zrot)*sin(yrot), 0, cos(yrot)]

最后,如果您有一个四元数[a, b, c, d],相应的旋转矩阵如下所示:

[1 - 2b^2 - 2c^2        2ab + 2cd           2ac - 2bd   ]
[   2ab - 2cd        1 - 2a^2 - 2c^2        2bc - 2ad   ]
[   2ac - 2bd           2bc + 2ad        1 - 2a^2 - 2b^2]

@Shahbaz:感谢您的回答。我已经计算出了X轴旋转所需的向量,它运行良好。然而,当立方体翻转时,即X = 180度时,Z轴旋转的方向也会翻转(顺时针变为逆时针,反之亦然)。有什么建议吗? - Iceman
@iceman,我已经在答案中的注释中提到了这一点。原因是变换,包括旋转,按照你编写函数的顺序逐个应用,从后往前。所以你首先顺时针旋转,然后翻转对象,这使它看起来像是逆时针旋转。解决方案,正如我所说,是考虑所有旋转而进行一次旋转,而不是逐个轴进行旋转。我建议使用gluLookAt - Shahbaz
1
就整体来看,关于一次旋转,它意味着根据 xrotyrotzrot 参数找到一个向量,并围绕该向量旋转对象。这并不是一件简单的事情,除了纯粹的数学趣味之外,我不建议实际去做这个。就像我说的那样,gluLookAt 是最简单的方法。无论如何,即使你更愿意手动计算,最好还是保存其他参数。与使用 gluLookAt 类似,你可以保持一个指向球体中心的表面点,并使用它生成变换矩阵。 - Shahbaz
1
@Iceman,那我肯定会尝试保留三个参数,分别为横滚、俯仰和偏航,并将它们累加起来。一旦你得到了这些值的累积值,只需在每个轴上应用一次即可。例如,如果输入是 +roll, +pitch, +roll, +yaw, -pitch,它们可以聚合为 +2*roll, +yaw。应用前5个操作的效果与应用后2个操作的效果不同。然而,我认为第二种方法(聚合输入,一次应用)实际上是任何人都期望从模拟器中得到的结果。 - Shahbaz
1
@iceman,是的,你可以自己应用变换(我忘记函数名了)。此外,我相信现代OpenGL方法也是要自己计算转换矩阵。 - Shahbaz
显示剩余23条评论

4
我对OpenGL知之甚少,但我认为将其翻译为0,旋转,然后再翻译回来应该可以解决问题...
gl.glTranslatef(-x, -y, -z);
gl.glRotatef(xrot, 1.0f, 0.0f, 0.0f);   //X
gl.glRotatef(yrot, 0.0f, 1.0f, 0.0f);   //Y
gl.glRotatef(zrot, 0.0f, 0.0f, 1.0f);   //Z
gl.glTranslatef(x, y, z);

将多边形平移回(0,0,z)后,不要再旋转它。只在(0,0,0)处进行旋转。 - Ilian
我正在进行这个操作,我只是移动了Z轴,并在旋转之前将其翻译为0,0,0。 - NullPointerException
Z是多边形坐标中唯一变化的值。 - NullPointerException
请检查我在编辑的问题中的代码,它无法工作...这不是解决方案。 - NullPointerException
是的,没错,正方形位置中唯一改变的值就是Z值,用于放大和缩小。 - NullPointerException
显示剩余4条评论

3
我认为你需要四元数来完成你想做的事情。围绕坐标轴旋转有时是有效的,但最终会遇到“万向锁”问题。当所需旋转经过坐标轴附近并且所需旋转接近180度时,会发生不必要的陀螺运动。
四元数是表示围绕任意轴定义为3D向量的旋转的数学对象。要在openGL中使用它,您需要从四元数生成矩阵,并将其乘以模型视图矩阵。这将使您的世界坐标旋转。
您可以在此处获取更多信息http://content.gpwiki.org/index.php/OpenGL:Tutorials:Using_Quaternions_to_represent_rotation 如果需要,我可以发送给您一个C ++四元数类。

0

尝试添加

glMatrixMode(GL_MODELVIEW);
glPushMatrix();

在渲染正在旋转的单个立方体的代码之前,然后...
glPopMatrix();

在渲染完成后,它会为您提供一个额外的视图矩阵,而不会影响您的主要模型视图矩阵。
基本上,这样做的目的是创建一个新的模型视图相机,进行渲染,然后销毁它。

你能给我解释一下在哪里放这些代码行吗:glMatrixMode(GL_MODELVIEW); glPushMatrix(); - NullPointerException
这应该立即放在旋转、顶点等任何模型相关调用之前。当你完成时,弹出矩阵。就像打开一个新的编辑空间并关闭它以封存更改一样。 - Sean

0

我正在使用OpenTK,不过方法是一样的。 首先将对象的所有尺寸减半,然后旋转并移回原位:

model = Matrix4.CreateTranslation(new Vector3(-width/2, -height / 2, -depth / 2)) * 

Matrix4.CreateRotationX(rotationX) * 

Matrix4.CreateRotationY(rotationY) * 

Matrix4.CreateRotationZ(rotationZ) *

Matrix4.CreateTranslation(new Vector3(width / 2, height / 2, depth / 2));

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