【翻译】安卓opengl ES教程之三——变换

在上一篇教程中咱们主要讲的是关于创建一个多边形。这一篇教程全都是关于变换的——如何移动一个多边形到任意位置。我会接着上一篇教程而继续讲下去,因此你能够继续使用上一篇的源码或者它的副本。java

在这一篇里你可能会嫌我叨叨一大堆数学知识,可是我认为这些对于了解opengl ES渲染网格过程当中对全部的顶点乘以一个矩阵是重要的。你作的全部变换实际上都是经过不一样的方式去修改顶点的矩阵。你能够把矩阵看做一张纸,在开始绘制以前,你没有移动过笔,因此你会始终绘制在纸的中央位置。可是经过变换,你能够移动这张纸和中心点。旋转操做就好像把这张纸绕着中心点旋转。缩放操做有点难以理解,姑且理解为你绘制网格的单位大小改变了。一般咱们所说的变换是基于网格而不是基于世界坐标,这是很重要的一点。
android

坐标系

opengl使用所谓的“右手坐标系”。一个坐标系,若是你沿坐标轴,从正方向向着原点望去,逆时针的旋转被认为是正向旋转,那么这个坐标系就被称为右手坐标系。当你初始化了你的视图,而且没有作任何变换的时候,坐标轴处于这样的状态:X轴从左向右,Y轴从下向上,Z轴从屏幕里向外。
编程

平移

public abstract void glTranslatef(float x, float y, float z)

对矩阵的平移表现为矩阵的网格被移动。平移是沿着坐标轴,没有任何旋转操做,坐标轴处于默认的状态。平移对一个多边形的全部顶点在同一坐标轴方向上偏移一样的距离,它是对当前值的简单加减操做。下图展现了一个二维的平移操做。缓存

起点是{x:-2,y:1},咱们想要移动到{x:1,y:3},因此咱们增长{x:3,y:2},即一个简单的加法操做:
框架

{x:-2, y:1} + {x:3, y:2} = {x:-2 + 3, y:1 + 2} = {x:1, y:3}google

一样,在三维中,咱们若是定位在{x:1, y:1, z:0},咱们想要向屏幕里移动3个单位,因而咱们加上{x:0, y:0, z:-3}获得{x:1, y:1, z:-3}。spa

在上篇教程中,为了看到绘制的四边形,咱们把它向屏幕里移动了4个单位,咱们的作法是在当前位置上加上{x:0, y:0, z:-4} ,这是咱们曾经用过的代码:.net

// 向屏幕里移动4个单位
gl.glTranslatef(0, 0, -4);

若是你沿着x,y,z轴作一系列的平移操做,顺序并非很重要,可是当咱们作旋转操做的时候,顺序就很是重要了。
code

记住坐标轴的放置方式有点困难,好在咱们有个便于记忆的方法。向下图同样伸出你的左手。每一个手指的方向表明着坐标轴的正方向。在我一开始接触3D编程的时候,我曾经在个人这几个手指上写上X,Y,Z。
orm

旋转

public abstract void glRotatef(float angle, float x, float y, float z)

旋转顾名思义,你对一个矩阵作旋转操做,会表现为网格被旋转了。在旋转以前若是没有任何平移操做的话,那么就是绕着原点旋转。x,y,z三个值定义了旋转操做的中心点,angle表明旋转的角度值。

若是你记住下面三点,你将会很容易的进行旋转操做:

  • 旋转的单位是角度值

    好多框架和数学方法使用弧度制,可是opengl使用角度制

  • 若是进行一系列旋转操做,顺序很重要

若是你要还原一个旋转操做,只须要把角度值或者三个坐标值取负就能够,例如glRotatef(angle, x, y, z) 能够被 glRotatef(angle, -x, -y, -z)或者glRotatef(-angle, x, y, z)复位。

可是若是你像下面这样作了一系列的旋转操做:

gl.glRotatef(90f, 1.0f, 0.0f, 0.0f);
gl.glRotatef(90f, 0.0f, 1.0f, 0.0f);
gl.glRotatef(90f, 0.0f, 0.0f, 1.0f);

这时候想要复位到原始位置,你不能向下面这样仅仅对坐标值(原文中是的“对角度”,我的认为做者这里有笔误——译者注)取负:

gl.glRotatef(90f, -1.0f, 0.0f, 0.0f);
gl.glRotatef(90f, 0.0f, -1.0f, 0.0f);
gl.glRotatef(90f, 0.0f, 0.0f, -1.0f);

你还须要逆序执行,以下:

gl.glRotatef(90f, 0.0f, 0.0f, -1.0f);
gl.glRotatef(90f, 0.0f, -1.0f, 0.0f);
gl.glRotatef(90f, -1.0f, 0.0f, 0.0f);

一系列旋转操做的顺序是很是重要的。

  • 若是你沿着坐标轴从正方向向原点看去,此坐标轴的旋转正方向是逆时针的

若是你握着一支铅笔,笔头和你的大拇指方向相同,以下图,将铅笔放在x轴上,让笔头指向坐标轴的正方向,你的其他四指握起来的方向就是沿着这个坐标轴旋转的正方向。

平移和旋转

因为平移和旋转操做都是基于网格本身的坐标系统,因此记住平移和旋转操做的顺序是很重要的事情。

若是你先对网格进行了平移操做,而后又进行旋转,那么平移是基于当前的网格坐标状态,而旋转则是基于新的坐标。

若是你先进行了旋转操做,而后进行平移,那么平移操做将会在旋转以后的坐标基础上进行。

缩放

public abstract void glScalef(float x, float y, float z)

缩放,见文之一,不解释。它可沿任意坐标轴方向上独立进行。缩放和全部的顶点都乘以一个缩放因子的效果是同样的。在下图中,咱们用gl.glScalef(2f, 2f, 2f)进行缩放,这也就至关于全部的顶点坐标都乘以2.

平移和缩放

缩放和平移的相互顺序也很重要。若是你在缩放以前进行了平移,那么平移的结果不受影响。以下面的例子,咱们先平移了两个单位,而后缩放0.5个单位。

gl.glTranslatef(2, 0, 0);
gl.glScalef(0.5f, 0.5f, 0.5f);

可是若是你先进行了缩放,而后平移,你会获得一个彻底不一样的结果。由于你先缩放了网格的坐标系统,而后才平移,因此你平移操做不会和以前那样移动一样的尺度。因此若是你先缩放了0.5个单位,而后平移两个单位,结果将会表现为像平移了一个单位同样。

gl.glScalef(0.5f, 0.5f, 0.5f);
gl.glTranslatef(2, 0, 0);

初始化,push,pop矩阵

当你进行平移,旋转,缩放操做的时候,这些操做并非在同一坐标条件下进行的,每次变换都会基于前一次变换,因此你可能须要复位位置。

glLoadIdentity

public abstract void glLoadIdentity()

glLoadIdentity这个方法是替换当前矩阵为初始矩阵。这和经过glLoadMatrix来加载下面这个矩阵是同样的效果:

1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1

然而有些场景下,你可能不想要初始化整个模型矩阵,你可能须要回到上一次变换以前的状态。

glPushMatrix

public abstract void glPushMatrix()

glPushMatrix是备份当前的矩阵,放入栈中。这也就是说你在执行glPushMatrix以后,进行的任何变换都是基于这个副本的。

glPopMatrix

public abstract void glPopMatrix()

这个方法是把你以前经过glPushMatrix放到栈里的矩阵拿回来。

一个好的实践就是在每一帧的开始调用glLoadIdentity,而在以后使用glPushMatrix和glPopMatrix。

组装

如今让咱们基于这个新知识点作个例子。先作三个四边形A,B,C。先对他们进行缩放,使得B比A小一半,C比B小一半。而后让A在屏幕中心绕逆时针方向旋转。B逆时针绕着A旋转,C顺时针绕着B旋转,同时逆时针高速自转。

public void onDrawFrame(GL10 gl) {
	// 清空屏幕和深度缓存
	gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
	// 替换当前矩阵为初始矩阵
	gl.glLoadIdentity();
	// 像屏幕里移动10个单位
	gl.glTranslatef(0, 0, -10);

	// 四边形 A
	// 保留当前矩阵
	gl.glPushMatrix();
	// 逆时针旋转A
	gl.glRotatef(angle, 0, 0, 1);
	// 绘制A
	square.draw(gl);
	// 回复到变换前的状态
	gl.glPopMatrix();

	// 四边形 B
	// 保留当前矩阵
	gl.glPushMatrix();
	//旋转B,而后平移,使它绕着A运动
	gl.glRotatef(-angle, 0, 0, 1);
	// 平移 B.
	gl.glTranslatef(2, 0, 0);
	// 缩小到A的50%
	gl.glScalef(.5f, .5f, .5f);
	// 绘制B
	square.draw(gl);

	// 四边形 C
	//保留当前矩阵
	gl.glPushMatrix();
	// 使其绕着B旋转
	gl.glRotatef(-angle, 0, 0, 1);
	gl.glTranslatef(2, 0, 0);
	// 缩小到B的50%
	gl.glScalef(.5f, .5f, .5f);
	// 自转
	gl.glRotatef(angle*10, 0, 0, 1);
	// 绘制C
	square.draw(gl);

	// 回复到C以前的状态
	gl.glPopMatrix();
	//回复到B以前的状态
	gl.glPopMatrix();

	// 角度自增
	angle++;
}

最后不要忘了增长角度这个变量,谢谢提醒。

public class OpenGLRenderer implements Renderer {
	private Square square;
	private float angle; // 不要忘了添加这个变量
        ...

引用

这篇教程引用以下文献:

Android Developers

OpenGL ES 1.1 Reference Pages

你能够下载教程的源码:Tutorial_Part_III

你也能够检出代码:code.google.com

前一篇教程:安卓opengl ES教程之二——建立多边形

后一篇教程:安卓opengl ES教程之四——添加颜色

相关文章
相关标签/搜索