OpenGL ES 13 – 在 3D中移动
(译者:那个。其实,实际上,我又跳了一章,12节,如何判断在2d世界里点中一个物体,因为我觉得我们这大部分的游戏开发者都已经使用了花生大大的代码,也比较容易的在2d世界里开发游戏了,这个章节对我们的用处不大。所以我又偷懒了。)
禽流感,忙碌,母亲节,和其他一些东西,这就意味着,我不在此博客一段时间。所以,现在是时候做事情了。
3D中的移动
我的时间很紧迫,所以今天会是一个真正的快速入门。我们将开始建立一个“真正的”3d世界,从地板开始建立一切的东西。然而,在我开始之前,我像先介绍如何在3d中移动。
今天,我们将开始一些新的代码,围绕着地板,进行触摸及移动。在使用触摸,我们可以转左,转右,前进及后退。没有跑,没有抬头看,仅仅是走,因为这更加的容易。我不要实现他们是为了没有iPod及iPhone的朋友也可以在模拟器上实现它。
在开始之前,你先下载下面的开始函数:
OpenGL ES 13 - Starter
这里没有太多的代码,只是让我们知道我们要做什么。
虚拟的照相机
我想我跟你提过了,人们用照相机在3d世界中观察事物,虽然OpenGL中实际上不存在照相机的。所以你在屏幕中移动的时候,你实际在移动所有的对象,运动的感觉的产生不是因为创建了一个照相机来看电影,而是因为将世界中的所有对象以(0,0,0)为相对坐标移动。
听上去好像这样的工作量非常的大,其实没有。因为根据你的应用,会执行很多的优化工作,即使很大的世界也没有太多的工作量。我以后会提到它。
为了工作更方便,我为本教程带来了帮手,来自于OpenGL ES的大哥 GL U的library中的函数:gluLookAt()。
通常我不提及OpenGL的优点,但我想大部分的人都知道什么是GLU library。遗憾的是OpenGL ES不支持这个函数库,这意味了我们少了很多有用的功能。移植这些功能不需要移植整个library,只需要实现少许我们需要的功能。
我找到了gluLookAt()的源代码,所以这些的代码的版权不是我的。
使用 gluLookAt()
这个函数是如此的简单使用,以至于你只要了解它就能使用,让我们来看看原型:
void gluLookAt( GLfloat eyex,
GLfloat eyey,
GLfloat eyez,
GLfloat centerx,
GLfloat centery,
GLfloat centerz,
GLfloat upx,
GLfloat upy,
GLfloat upz)
我知道9个参数看起来有点令人生畏,但你可以分解。前3个是指眼睛的位置,在这里你要找的,只是x,y,z的坐标。
这第2组3个参数是指你想看哪里,又一个x,y,z的坐标。
最后,我们看最后3个“up”的向量。前2个坐标就是我想要的效果。
所以,眼睛的坐标就是神秘的照相机的位置,它们在你的世界坐标里。从这里看你的世界。那个“中心”坐标就是你面向那里。如果中心的y坐标高于你眼睛的y坐标,那么你就朝上看,如果低于呢,你就朝下看。
所以,在我们开始的工程里,我们就已经准备号设置如何移动了。现在我们看看绘制好的地板。
首先让我们使用下glLookAt()。到drawView,添加下面函数:
glLoadIdentity();
gluLookAt(5.0, 1.5, 2.0, // Eye location, look “from”
-5.0, 1.5, -10.0, // Target location, look “to”
0.0, 1.0, 0.0); // Ignore for now
单一的功能,我们从一个地方,看到另一个地方,改变参数,看看会发生什么。
3D中的运动
现在你又把握处理gluLookAt(),让我们用它来模拟在地板上行走,我们只能移动(X,Z),也就是说,不要移动y,就是高度。
所以,思路会到gluLookAt(),想想,在3d世界中移动,你需要什么信息?
你需要:
你观察的位置或者说眼
你面向的位置或者说“中心”
一旦你知道了这两项,你就要准备好用户输入,以让用户控制移动。
设置以下变量来改变眼睛及中心
GLfloat eye[3];// Where we are viewing from
GLfloat center[3];// Where we are looking towards
回到 initWithCoder: 在调用 gluLookAt()之前,初始化下:
eye[0] = 5.0;
eye[1] = 1.5;
eye[2] = 2.0;
center[0] = -5.0;
center[1] = 1.5;
center[2] = -10.0;
到 drawView: 调用 gluLookAt():
gluLookAt(eye[0], eye[1], eye[2], center[0], center[1], center[2],
0.0, 1.0, 0.0);
为运动做的准备
在我们开始执行触摸事件并在世界中移动之前,我们需要在头文件中设置一些东西。来到头文件,我们需要创建一个新的枚举类型。
首先,设置我们的步行速度及转身速度:
#define WALK_SPEED 0.005
#define TURN_SPEED 0.01
下一步,我们创建一个枚举,来表示我们正在做什么,如下:
typedef enum __MOVMENT_TYPE {
MTNone = 0,
MTWalkForward,
MTWAlkBackward,
MTTurnLeft,
MTTurnRight
} MovementType;
所以,在我们运行app的时候,我们可以一直是站着,或者前面,或者后退,或者转左,或者转右。
最后,我们定义个变量保存但前的运动状态:
MovementType currentMovement;
别忘了到 initWithCoder: 函数里面去设置 currentMovement 的默认值:
currentMovement = MTNone;
获得触摸
好的,我们已经进行了基本的介绍,让我们开始实际的处理工作。如果你还记得我之前的教程,你应该知道触摸状态四种,今天我们使用
touchesBegan 和 touchesEnded.
为了确定行动的种类,我们把屏幕分为四部分。
开始进行触摸的函数:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *t = [[touches allObjects] objectAtIndex:0];
CGPoint touchPos = [t locationInView:t.view];
// Determine the location on the screen. We are interested in iPhone
// Screen co-ordinates only, not the world co-ordinates
// because we are just trying to handle movement.
//
// (0, 0)
// +-----------+
// | |
// | 160 |
// |-----------| 160
// | | |
// | | |
// |-----------| 320
// | |
// | |
// +-----------+ (320, 480)
//
if (touchPos.y < 160) {
// We are moving forward
currentMovement = MTWalkForward;
} else if (touchPos.y > 320) {
// We are moving backward
currentMovement = MTWAlkBackward;
} else if (touchPos.x < 160) {
// Turn left
currentMovement = MTTurnLeft;
} else {
// Turn Right
currentMovement = MTTurnRight;
}
}
在玩家点击屏幕的任意位置后,就会将运动状态设置为指定的状态,同时,在松手时释放这样的状态:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
currentMovement = MTNone;
}
这些时方法的实现。最后,我们需要一个来处理这些触摸事件,这一次,我们需要添加一个方法声明的接口,回到头文件,添加下面的方法:
- (void)handleTouches;
然后,切换回来,实现这个方法,就是这个方法,我们要通过计算来执行我们运动的3d世界。
3D空间的移动理论
让我们来看看第一次走路。当用户告诉我们向前走,我们需要了解的不仅是我们朝向的位置,同时还要了解我们的目标位置。这个朝向位置就是我们的当前位置,而看到的目标就是我们的目标位置,我们需要从当前位置向目标位置移动。
一图胜千言,下面的图片很好的说明了我们的当前位置和我们预定的目标位置。
在运动函数里面,我们知道这两点之间,x及z坐标的距离。我们要做的就是,将当前坐标乘以”速度”获得新的x,z坐标。像这样:
我们可以很容易的得到红色点的坐标.
我们先设计目标位置的相对坐标 deltaX 和 deltaZ:
deltaX = 1.5 - 1.0 = 0.5
deltaZ = -10 - (- 5.0) = -5.0
所以现在乘上我们的步行速度:
xDisplacement = deltaX * WALK_SPEED
= 0.5 * 0.01
= 0.005
zDisplacement = deltaZ * WALK_SPEED
= -5.0 * 0.01
= 0.05
因此,那个红色的新点位置就是:
eyeC + CDisplacement
(eyex + xDisplacement, eyey, eyez + zDisplacement)
= (0.005+1.0, eyey,(-10)+ 0.05)
= (1.005, eyey, -9.95)
现在,这种方法不是没有缺点。主要的问题是,如果目标与当前位置距离越远,那么“步行速度”也就越快。
现在,让我们看下转左和转右。
因为我们在模拟旋转。实际上目标位置就是在一个圆上移动。
这里的关键在于,你要明白我说的是什么。你可以把当前所在位置想像为圆心,目标位置是圆边。转向就是在圆边上移动。
换句话说,这就是:
newX = eyeX + radius * cos(TURN_SPEED)*deltaX -
sin(TURN_SPEED)*deltaZ
newZ = eyeZ + radius * sin(TURN_SPEED)* deltaX +
cos(TURN_SPEED)*deltaZ
处理触摸及涉及到运动
现在,让我们把这个转化为实践。
首先,实现开始的方法和一些基本知识:
- (void)handleTouches {
if (currentMovement == MTNone) {
// We're going nowhere, nothing to do here
return;
}
首先,看看我们是不是在移动,如果没有操作,就什么事情也不做。
然后,无论我们是在移动还是转弯,我们都需要知道相对距离 deltaX 和 deltaZ 的值。我找一个临时数组把我们保存起来。
GLfloat vector[3];
vector[0] = center[0] - eye[0];
vector[1] = center[1] - eye[1];
vector[2] = center[2] - eye[2];
我没有计算y的相对值,因为是没有意义的。
现在我们用一个switch来判断当前状态并进行处理
switch (currentMovement) {
case MTWalkForward:
eye[0] += vector[0] * WALK_SPEED;
eye[2] += vector[2] * WALK_SPEED;
center[0] += vector[0] * WALK_SPEED;
center[2] += vector[2] * WALK_SPEED;
break;
case MTWAlkBackward:
eye[0] -= vector[0] * WALK_SPEED;
eye[2] -= vector[2] * WALK_SPEED;
center[0] -= vector[0] * WALK_SPEED;
center[2] -= vector[2] * WALK_SPEED;
break;
case MTTurnLeft:
center[0] = eye[0] + cos(-TURN_SPEED)*vector[0] -
sin(-TURN_SPEED)*vector[2];
center[2] = eye[2] + sin(-TURN_SPEED)*vector[0] +
cos(-TURN_SPEED)*vector[2];
break;
case MTTurnRight:
center[0] = eye[0] + cos(TURN_SPEED)*vector[0] - sin(TURN_SPEED)*vector[2];
center[2] = eye[2] + sin(TURN_SPEED)*vector[0] + cos(TURN_SPEED)*vector[2];
break;
}
}
这里实现的算法就是我刚才说的。
使这一切关联起来
回到 drawView 函数,并在 gluLookAt()函数下面添加这行:
[self handleTouches];
And that’s it, we’re done!
Hit “Build and Go” now! Right now!!
(译者:那个。其实,实际上,我又跳了一章,12节,如何判断在2d世界里点中一个物体,因为我觉得我们这大部分的游戏开发者都已经使用了花生大大的代码,也比较容易的在2d世界里开发游戏了,这个章节对我们的用处不大。所以我又偷懒了。)
禽流感,忙碌,母亲节,和其他一些东西,这就意味着,我不在此博客一段时间。所以,现在是时候做事情了。
3D中的移动
我的时间很紧迫,所以今天会是一个真正的快速入门。我们将开始建立一个“真正的”3d世界,从地板开始建立一切的东西。然而,在我开始之前,我像先介绍如何在3d中移动。
今天,我们将开始一些新的代码,围绕着地板,进行触摸及移动。在使用触摸,我们可以转左,转右,前进及后退。没有跑,没有抬头看,仅仅是走,因为这更加的容易。我不要实现他们是为了没有iPod及iPhone的朋友也可以在模拟器上实现它。
在开始之前,你先下载下面的开始函数:
OpenGL ES 13 - Starter
这里没有太多的代码,只是让我们知道我们要做什么。
虚拟的照相机
我想我跟你提过了,人们用照相机在3d世界中观察事物,虽然OpenGL中实际上不存在照相机的。所以你在屏幕中移动的时候,你实际在移动所有的对象,运动的感觉的产生不是因为创建了一个照相机来看电影,而是因为将世界中的所有对象以(0,0,0)为相对坐标移动。
听上去好像这样的工作量非常的大,其实没有。因为根据你的应用,会执行很多的优化工作,即使很大的世界也没有太多的工作量。我以后会提到它。
为了工作更方便,我为本教程带来了帮手,来自于OpenGL ES的大哥 GL U的library中的函数:gluLookAt()。
通常我不提及OpenGL的优点,但我想大部分的人都知道什么是GLU library。遗憾的是OpenGL ES不支持这个函数库,这意味了我们少了很多有用的功能。移植这些功能不需要移植整个library,只需要实现少许我们需要的功能。
我找到了gluLookAt()的源代码,所以这些的代码的版权不是我的。
使用 gluLookAt()
这个函数是如此的简单使用,以至于你只要了解它就能使用,让我们来看看原型:
void gluLookAt( GLfloat eyex,
GLfloat eyey,
GLfloat eyez,
GLfloat centerx,
GLfloat centery,
GLfloat centerz,
GLfloat upx,
GLfloat upy,
GLfloat upz)
我知道9个参数看起来有点令人生畏,但你可以分解。前3个是指眼睛的位置,在这里你要找的,只是x,y,z的坐标。
这第2组3个参数是指你想看哪里,又一个x,y,z的坐标。
最后,我们看最后3个“up”的向量。前2个坐标就是我想要的效果。
所以,眼睛的坐标就是神秘的照相机的位置,它们在你的世界坐标里。从这里看你的世界。那个“中心”坐标就是你面向那里。如果中心的y坐标高于你眼睛的y坐标,那么你就朝上看,如果低于呢,你就朝下看。
所以,在我们开始的工程里,我们就已经准备号设置如何移动了。现在我们看看绘制好的地板。
首先让我们使用下glLookAt()。到drawView,添加下面函数:
glLoadIdentity();
gluLookAt(5.0, 1.5, 2.0, // Eye location, look “from”
-5.0, 1.5, -10.0, // Target location, look “to”
0.0, 1.0, 0.0); // Ignore for now
单一的功能,我们从一个地方,看到另一个地方,改变参数,看看会发生什么。
3D中的运动
现在你又把握处理gluLookAt(),让我们用它来模拟在地板上行走,我们只能移动(X,Z),也就是说,不要移动y,就是高度。
所以,思路会到gluLookAt(),想想,在3d世界中移动,你需要什么信息?
你需要:
你观察的位置或者说眼
你面向的位置或者说“中心”
一旦你知道了这两项,你就要准备好用户输入,以让用户控制移动。
设置以下变量来改变眼睛及中心
GLfloat eye[3];// Where we are viewing from
GLfloat center[3];// Where we are looking towards
回到 initWithCoder: 在调用 gluLookAt()之前,初始化下:
eye[0] = 5.0;
eye[1] = 1.5;
eye[2] = 2.0;
center[0] = -5.0;
center[1] = 1.5;
center[2] = -10.0;
到 drawView: 调用 gluLookAt():
gluLookAt(eye[0], eye[1], eye[2], center[0], center[1], center[2],
0.0, 1.0, 0.0);
为运动做的准备
在我们开始执行触摸事件并在世界中移动之前,我们需要在头文件中设置一些东西。来到头文件,我们需要创建一个新的枚举类型。
首先,设置我们的步行速度及转身速度:
#define WALK_SPEED 0.005
#define TURN_SPEED 0.01
下一步,我们创建一个枚举,来表示我们正在做什么,如下:
typedef enum __MOVMENT_TYPE {
MTNone = 0,
MTWalkForward,
MTWAlkBackward,
MTTurnLeft,
MTTurnRight
} MovementType;
所以,在我们运行app的时候,我们可以一直是站着,或者前面,或者后退,或者转左,或者转右。
最后,我们定义个变量保存但前的运动状态:
MovementType currentMovement;
别忘了到 initWithCoder: 函数里面去设置 currentMovement 的默认值:
currentMovement = MTNone;
获得触摸
好的,我们已经进行了基本的介绍,让我们开始实际的处理工作。如果你还记得我之前的教程,你应该知道触摸状态四种,今天我们使用
touchesBegan 和 touchesEnded.
为了确定行动的种类,我们把屏幕分为四部分。
开始进行触摸的函数:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *t = [[touches allObjects] objectAtIndex:0];
CGPoint touchPos = [t locationInView:t.view];
// Determine the location on the screen. We are interested in iPhone
// Screen co-ordinates only, not the world co-ordinates
// because we are just trying to handle movement.
//
// (0, 0)
// +-----------+
// | |
// | 160 |
// |-----------| 160
// | | |
// | | |
// |-----------| 320
// | |
// | |
// +-----------+ (320, 480)
//
if (touchPos.y < 160) {
// We are moving forward
currentMovement = MTWalkForward;
} else if (touchPos.y > 320) {
// We are moving backward
currentMovement = MTWAlkBackward;
} else if (touchPos.x < 160) {
// Turn left
currentMovement = MTTurnLeft;
} else {
// Turn Right
currentMovement = MTTurnRight;
}
}
在玩家点击屏幕的任意位置后,就会将运动状态设置为指定的状态,同时,在松手时释放这样的状态:
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
currentMovement = MTNone;
}
这些时方法的实现。最后,我们需要一个来处理这些触摸事件,这一次,我们需要添加一个方法声明的接口,回到头文件,添加下面的方法:
- (void)handleTouches;
然后,切换回来,实现这个方法,就是这个方法,我们要通过计算来执行我们运动的3d世界。
3D空间的移动理论
让我们来看看第一次走路。当用户告诉我们向前走,我们需要了解的不仅是我们朝向的位置,同时还要了解我们的目标位置。这个朝向位置就是我们的当前位置,而看到的目标就是我们的目标位置,我们需要从当前位置向目标位置移动。
一图胜千言,下面的图片很好的说明了我们的当前位置和我们预定的目标位置。
在运动函数里面,我们知道这两点之间,x及z坐标的距离。我们要做的就是,将当前坐标乘以”速度”获得新的x,z坐标。像这样:
我们可以很容易的得到红色点的坐标.
我们先设计目标位置的相对坐标 deltaX 和 deltaZ:
deltaX = 1.5 - 1.0 = 0.5
deltaZ = -10 - (- 5.0) = -5.0
所以现在乘上我们的步行速度:
xDisplacement = deltaX * WALK_SPEED
= 0.5 * 0.01
= 0.005
zDisplacement = deltaZ * WALK_SPEED
= -5.0 * 0.01
= 0.05
因此,那个红色的新点位置就是:
eyeC + CDisplacement
(eyex + xDisplacement, eyey, eyez + zDisplacement)
= (0.005+1.0, eyey,(-10)+ 0.05)
= (1.005, eyey, -9.95)
现在,这种方法不是没有缺点。主要的问题是,如果目标与当前位置距离越远,那么“步行速度”也就越快。
现在,让我们看下转左和转右。
因为我们在模拟旋转。实际上目标位置就是在一个圆上移动。
这里的关键在于,你要明白我说的是什么。你可以把当前所在位置想像为圆心,目标位置是圆边。转向就是在圆边上移动。
换句话说,这就是:
newX = eyeX + radius * cos(TURN_SPEED)*deltaX -
sin(TURN_SPEED)*deltaZ
newZ = eyeZ + radius * sin(TURN_SPEED)* deltaX +
cos(TURN_SPEED)*deltaZ
处理触摸及涉及到运动
现在,让我们把这个转化为实践。
首先,实现开始的方法和一些基本知识:
- (void)handleTouches {
if (currentMovement == MTNone) {
// We're going nowhere, nothing to do here
return;
}
首先,看看我们是不是在移动,如果没有操作,就什么事情也不做。
然后,无论我们是在移动还是转弯,我们都需要知道相对距离 deltaX 和 deltaZ 的值。我找一个临时数组把我们保存起来。
GLfloat vector[3];
vector[0] = center[0] - eye[0];
vector[1] = center[1] - eye[1];
vector[2] = center[2] - eye[2];
我没有计算y的相对值,因为是没有意义的。
现在我们用一个switch来判断当前状态并进行处理
switch (currentMovement) {
case MTWalkForward:
eye[0] += vector[0] * WALK_SPEED;
eye[2] += vector[2] * WALK_SPEED;
center[0] += vector[0] * WALK_SPEED;
center[2] += vector[2] * WALK_SPEED;
break;
case MTWAlkBackward:
eye[0] -= vector[0] * WALK_SPEED;
eye[2] -= vector[2] * WALK_SPEED;
center[0] -= vector[0] * WALK_SPEED;
center[2] -= vector[2] * WALK_SPEED;
break;
case MTTurnLeft:
center[0] = eye[0] + cos(-TURN_SPEED)*vector[0] -
sin(-TURN_SPEED)*vector[2];
center[2] = eye[2] + sin(-TURN_SPEED)*vector[0] +
cos(-TURN_SPEED)*vector[2];
break;
case MTTurnRight:
center[0] = eye[0] + cos(TURN_SPEED)*vector[0] - sin(TURN_SPEED)*vector[2];
center[2] = eye[2] + sin(TURN_SPEED)*vector[0] + cos(TURN_SPEED)*vector[2];
break;
}
}
这里实现的算法就是我刚才说的。
使这一切关联起来
回到 drawView 函数,并在 gluLookAt()函数下面添加这行:
[self handleTouches];
And that’s it, we’re done!
Hit “Build and Go” now! Right now!!