`

在OpenGL ES 2.0中通过触摸来旋转3D对象

 
阅读更多

 

在这个教程中,你将学到用OpenGL ES 2.0GLKit通过触摸来旋转一个3D物体。

我们先从简单开始,首先向你介绍随着用户的拖拽,怎样通过沿着x轴或y轴旋转一定数量的度数来旋转一个3D物体。然后使用四元数介绍一个更高级的技术。

这个教程会从Beginning OpenGL ES 2.0 with GLKit Tutorial提取示例工程,如果你没有准备好这个工程请下载

请牢记我是自学的,我的数学相当生疏,所以如果犯了错或者没有很正确的解释一些事情请抱歉。如果有人有更好的或更正确的方法来说明,请指出!

言归正传,让我们开始旋转。

简单旋转:尝试1

开始,运行sample project,你会看到立方体已经在固定的旋转了。

如果你打开HelloGLKitViewController.m,找到update方法,你会发现下面的代码进行了固定的旋转

_rotation += 90 * self.timeSinceLastUpdate;
modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, GLKMathDegreesToRadians(25), 1, 0, 0);
modelViewMatrix = GLKMatrix4Rotate(modelViewMatrix, GLKMathDegreesToRadians(_rotation), 0, 1, 0);

我们的目标是替换上面代码,使用户能通过拖动鼠标来旋转立方体,而不是一成不变的旋转。

我们首先尝试一下通过移动旋转模型到一个实例变量,将它改为用户拖拽的方式。对HelloGLKitViewController.m作以下修改:

// Add to HelloGLKitViewController private variables
GLKMatrix4 _rotMatrix;

// Add to bottom of setupGL
_rotMatrix = GLKMatrix4Identity;

// In update, replace the 3 rotation lines shown in the previous snippet with this
modelViewMatrix = GLKMatrix4Multiply(modelViewMatrix, _rotMatrix);

// Remove everything inside touchesBegan
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

}

// Add new touchesMoved method
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

    UITouch * touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.view];
    CGPoint lastLoc = [touch previousLocationInView:self.view];
    CGPoint diff = CGPointMake(lastLoc.x - location.x, lastLoc.y - location.y);

    float rotX = -1 * GLKMathDegreesToRadians(diff.y / 2.0);
    float rotY = -1 * GLKMathDegreesToRadians(diff.x / 2.0);

    GLKVector3 xAxis = GLKVector3Make(1, 0, 0);
    _rotMatrix = GLKMatrix4Rotate(_rotMatrix, rotX, xAxis.x, xAxis.y, xAxis.z);
    GLKVector3 yAxis = GLKVector3Make(0, 1, 0);
    _rotMatrix = GLKMatrix4Rotate(_rotMatrix, rotY, yAxis.x, yAxis.y, yAxis.z);

}

好了,这里我们初始化旋转模型到本体(没有变化),随着用户的拖拽,我们利用GLKMatrix4Rotate来对立方体进行一定度数的旋转。用户每移动一个像素,立方体旋转12度。

记住x轴是水平穿过屏幕的,y轴是垂直的。所以当用户从左拖到右,我们实际上是想要围着y轴旋转(rotY),反过来也是这样。

编译运行,你会注意到立方体刚开始旋转正常,但是不久后它就脱离了你的预期,奇怪的往斜方向旋转。

简单旋转:尝试2

当你对一个物体运用变换,你可以把它当作物体到一个世界新空间的活动坐标系。

要理解我所说的,运行app,从立方体的右上角扫过立方体中间。你已经按如下方法有效的修改了物体的坐标系统,使其位于不同的世界空间:

_rotMatrix是一种将物体移动到新空间的变换。当用户移动时代码会修改_rotMatrix,所以当我们告诉它围着x轴或y轴旋转,它就会在物体空间围着xy轴旋转。

尝试着自己实现它:做另外一个移动,从右到左(注意不是上移或下移),你会看到它是怎么围绕新的y轴旋转的。

但是如果我们是想沿着世界空间x轴和y轴旋转,而不是物体空间的x轴和y轴,怎样计算新坐标系统世界的x轴和y轴?

答案是简单的:如果_rotMatrix转换一个物体向量到它应该在世界空间的位置,_rotMatrix反变换转换一个世界向量到世界空间。

你可以看下面的等式来帮助理解:

_rotMatrix * object vector = world vector

两边同时乘以 (_rotMatrix)-1 (与 _rotMatrix相反), 结果如下:

(_rotMatrix)-1 * _rotMatrix * object vector = (rotMatrix-1) * world vector

由于(_rotMatrix)-1 * _rotMatrix = 1, 结果为:

object vector = (_rotMatrix)-1 * world vector

让我们试着实现!将touchesMoved修改成下面这样

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {

    UITouch * touch = [touches anyObject];
    CGPoint location = [touch locationInView:self.view];
    CGPoint lastLoc = [touch previousLocationInView:self.view];
    CGPoint diff = CGPointMake(lastLoc.x - location.x, lastLoc.y - location.y);

    float rotX = -1 * GLKMathDegreesToRadians(diff.y / 2.0);
    float rotY = -1 * GLKMathDegreesToRadians(diff.x / 2.0);

    bool isInvertible;
    GLKVector3 xAxis = GLKMatrix4MultiplyVector3(GLKMatrix4Invert(_rotMatrix, &isInvertible),
        GLKVector3Make(1, 0, 0));
    _rotMatrix = GLKMatrix4Rotate(_rotMatrix, rotX, xAxis.x, xAxis.y, xAxis.z);
    GLKVector3 yAxis = GLKMatrix4MultiplyVector3(GLKMatrix4Invert(_rotMatrix, &isInvertible),
        GLKVector3Make(0, 1, 0));
    _rotMatrix = GLKMatrix4Rotate(_rotMatrix, rotY, yAxis.x, yAxis.y, yAxis.z);

}

编译运行,现在是按照预期运行了!

四元数弧球旋转

概述

另外一种流行的易于旋转3D对象的方式是由Ken Shoemake普及的弧球旋转算法

让我来告诉你一种简单的关于弧球旋转算法的基本信息:

1.映射到球体。创建一个你想要旋转对象的虚拟球体。当使用者触摸时,你要计算出所处范围内最近的点,与触摸的点相一致(开始),且和触摸移动的点(当前)相似。

2.计算当前的旋转。从开始到当前范围内的一个旋转的点,你可以认为它是通过转旋坐标轴或者一些角度而得到的。计算旋转/坐标轴从开始/当前的位置。

3.更新全部的旋转。更新全部由第三步得到当前旋转的对象

步骤3最神奇的地方是把数学概念里的四元数引入进来。我不打算在后面讨论它们,但是我想指出3个四元数的高等级属性:

1. Quaternion=axis+angle of rotation.四元数可以代表一个坐标轴和一个旋转的角度。一个四元数可以代表任何旋转。

2. Multiply quaternion = combine rotations.如果你要使2个四元数相乘,它们代表旋转的联合

3. Can convert quaternion to matrices.通过一些数学方法你可以使一个四元数转换到一个旋转矩阵里去。

一个关于GLKit的有意思的事情是你可以使用四元数而不需要明白它的后台是如何工作和运转的。你可以象下面这样使用它的功能:

GLKQuaternionMakeWithAngleAndVector3Axis: 

创建一个坐标轴或旋转角度的quaternion

 

GLKQuaternionMultiply:

一个四元数乘以另外一个四元数(联合旋转)

 

GLKMatrix4MakeWithQuaternion:

quaternion转换成一个旋转矩阵

如果你仍然有一些小困惑关于它是如何工作的,不用担心,我们将在下面的章节里一步一步的告诉你!

1) 映射到球体.

假设在我们的对象周围有一个虚拟的范围,半径有屏幕的1/3之宽。这个范围的中心就是我们选中对象的中心。我们想让使用者“grab and drag”这些范围来旋转对象。所以第一步是要计算出将一个2D的点转换成一个虚拟3D范围内的点。这里有一个最简单的方式。首先,让我们来学习一些基础的东西。

我们知道x和y的位置是用户点击的那个点,所以我们能够通过Pythagorean Theorem计算出斜边的长度

我们现在知道范围的中心到用户点击的x和y坐标的矢量距离,但是不知道z坐标。

然后,我们知道z坐标必须和范围相交,如果你绘制一条线段从范围的中心到任意一点,它会有一条半径。

目前,我们有另外一个正确的三角形可以通过pythagorean thorem来解决它!我们可以得到:

r^2 = (sqrt(p.x^2 + p.y^2))^2 + z^2

r^2 = (p.x^2 + p.y^2) + z^2

r^2 - (p.x^2 + p.y^2) = z^2

这里有个小技巧是如果用户点击到了半径的范围之外,如果真的发生了,那么我们只能用离它最近的点去替代它。

让我们看下面代码里的方法!并添加正确的方法在touchesBegan之前:

- (GLKVector3) projectOntoSurface:(GLKVector3) touchPoint
{
    float radius = self.view.bounds.size.width/3;
    GLKVector3 center = GLKVector3Make(self.view.bounds.size.width/2, self.view.bounds.size.height/2, 0);
    GLKVector3 P = GLKVector3Subtract(touchPoint, center);

    // Flip the y-axis because pixel coords increase toward the bottom.
    P = GLKVector3Make(P.x, P.y * -1, P.z);

    float radius2 = radius * radius;
    float length2 = P.x*P.x + P.y*P.y;

    if (length2 <= radius2)
        P.z = sqrt(radius2 - length2);
    else
    {
        P.x *= radius / sqrt(length2);
        P.y *= radius / sqrt(length2);
        P.z = 0;
    }

    return GLKVector3Normalize(P);
}

这个数学方法和我们上面讨论的一样,注意在结尾我们要使用规格化的矢量,因为当计算旋转时方向是大于长度的。现在我们开始移动它。在HelloGLKitViewController.m里做如下的改变:

// Add to the private interface
GLKVector3 _anchor_position;
GLKVector3 _current_position;

// Add to bottom of touchesBegan
UITouch * touch = [touches anyObject];
CGPoint location = [touch locationInView:self.view];

_anchor_position = GLKVector3Make(location.x, location.y, 0);
_anchor_position = [self projectOntoSurface:_anchor_position];

_current_position = _anchor_position;

// Add to bottom of touchesMoved
_current_position = GLKVector3Make(location.x, location.y, 0);
_current_position = [self projectOntoSurface:_current_position];

现在我们开始规则化开始和结束范围内的点以此来和用户鼠标移动的动作保持一致。现在我们使用他们来计算坐标和旋转的角度

2) 计算当前旋转

计算坐标和旋转的角度,我们可以使用我们的dot product和cross product。如果这里你没有弄太明白,我强烈建议你去读David Rosen写的《Linear Algebra for Game Developers》。这篇文章介绍了大量的基础东西。但是如果你不想这么做,我可以给你在这里做一个简单的总结:

corss product允许你有2各矢量,它会给矢量一个垂直矢量。这将是旋转的坐标,但是现在我们需要计算出从一个矢量到另外一个旋转的数量。

长话短说,你可以使用dot product来帮助你决定2个矢量之间的角度。这个角度是acos(如果A和B不是相同矢量的话)。并且请记住在projectOntoSurface里规格化它们。

一旦我们决定了坐标和角度,我们就可以创造一个四元数来存储它们通过使用GLKit GLKQuaternionMakeWithAngleAndVector3Axis功能。让我们来试试吧,在HelloGLKitViewController.m里做如下修改:

// Add new method above touchesBegan
- (void)computeIncremental {

    GLKVector3 axis = GLKVector3CrossProduct(_anchor_position, _current_position);
    float dot = GLKVector3DotProduct(_anchor_position, _current_position);
    float angle = acosf(dot);

    GLKQuaternion Q_rot = GLKQuaternionMakeWithAngleAndVector3Axis(angle * 2, axis);
    Q_rot = GLKQuaternionNormalize(Q_rot);

    // TODO: Do something with Q_rot...

}

// Call it at end of touchesEnded
[self computeIncremental];

现在我们有一个四元数来代替旋转的对象,从开始的触摸点到当前的触摸点

3) 更新全部旋转

最后一步,我们需要应用旋转的对象,这么做时我们需要追踪2个旋转:原始的旋转对象和当前的旋转对象

这是非常简单的,在HelloGLKitViewController.m里做如下改变:

// Add new variables to private interface
GLKQuaternion _quatStart;
GLKQuaternion _quat;

// Initialize them at bottom of setupGL
_quat = GLKQuaternionMake(0, 0, 0, 1);
_quatStart = GLKQuaternionMake(0, 0, 0, 1);

// Set _quat at bottom of computeIncremental
_quat = GLKQuaternionMultiply(Q_rot, _quatStart);

// Set _quatStart at bottom of touchesBegan
_quatStart = _quat;

// In update, replace GLKMatrix4Multiply(modelViewMatrix, rotation) line with this:
GLKMatrix4 rotation = GLKMatrix4MakeWithQuaternion(_quat);
modelViewMatrix = GLKMatrix4Multiply(modelViewMatrix, rotation);

这里_quatStart代表原始的旋转对象(在用户没有触碰之前),_quat时当前的旋转对象。

在computeIncremental的最后,我们把原始的与当前的进行相乘(点的数据是2者相乘的乘积)。

我们把四元数转成一个旋转矩阵,把它应用到视觉模型矩阵里。编译并运行,现在可以旋转这个立方体了

额外奖励: 另一个可选择的projectOntoSurface方法

注意,按照目前的实现,如果你在球体外部拖拽,它会映射到在球体上最近的点,而不是继续旋转。为了使旋转可以继续,将projectOntoSurface方法中的else部分替换成下面的代码:

P.z = radius2 / (2.0 * sqrt(length2));
float length = sqrt(length2 + P.z * P.z);
P = GLKVector3DivideScalar(P, length);

额外奖励: 四元数的更多乐趣

你可能会想弄明白用四元数能做其它什么事情。一件很酷的事情是:它们可以提供一种简单的方法在两个旋转位置间随着时间推移进行插值。

为了弄清楚我说的,让我们来试试看,加一些代码令我们双击后,让立方体执行动画旋转回最初的方向。按照下面修改HelloGLKitViewController.m:

// Add new private instance variables
BOOL _slerping;
float _slerpCur;
float _slerpMax;
GLKQuaternion _slerpStart;
GLKQuaternion _slerpEnd;

// Add to bottom of setupGL
UITapGestureRecognizer * dtRec = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(doubleTap:)];
    dtRec.numberOfTapsRequired = 2;
    [self.view addGestureRecognizer:dtRec];

// Add new method
- (void)doubleTap:(UITapGestureRecognizer *)tap {

    _slerping = YES;
    _slerpCur = 0;
    _slerpMax = 1.0;
    _slerpStart = _quat;
    _slerpEnd = GLKQuaternionMake(0, 0, 0, 1);

}

// Add inside update method, right before declaration of modelViewMatrix
if (_slerping) {

    _slerpCur += self.timeSinceLastUpdate;
    float slerpAmt = _slerpCur / _slerpMax;
    if (slerpAmt > 1.0) {
        slerpAmt = 1.0;
        _slerping = NO;
    }

    _quat = GLKQuaternionSlerp(_slerpStart, _slerpEnd, slerpAmt);
}

当用户双击后,我们将起始旋转位置设置为当前方向(_quat),将最终旋转位置设置为恒等式旋转(没有任何旋转)。

然后在update方法中,判断如果当前状态是睡眠,我们先获得动画目前所处的进度。随后,我们使用内置的GLK四元数lerp方法,根据当前时间计算出slerpStart和slerpEnd之间的合适的旋转角度。

编译运行,旋转物体,然后双击使其旋转回初始位置。这个技术通常用于3D物体的关键帧动画和其它类似的地方。

从这里可以去什么地方?

这里有一个示例工程,包含了此教程中所有的代码。

希望这篇教程可以帮助到那些想学习一些触摸旋转3D物体方面的知识,以及复习一些3D数学概念的人。

如果任何人有疑问,更正或是更好更简单的办法来解释事情,请加入下面的论坛讨论!

转载自:

http://article.ityran.com/archives/1354

http://www.raywenderlich.com/5223/beginning-opengl-es-2-0-with-glkit-part-1

http://www.raywenderlich.com/5235/beginning-opengl-es-2-0-with-glkit-part-2

 

分享到:
评论

相关推荐

    OpenGLES 2.0 for android 开发教程 从入门到精通和Demo

    在Android系统中,OpenGLES 2.0是用于图形渲染的重要框架,支持3D图形和复杂的视觉效果。本教程旨在帮助开发者从零基础开始掌握OpenGLES 2.0,并通过实例Demo加深理解。 入门篇: 1. **环境配置**:首先,你需要...

    Opengles2.0地球

    这个"Opengles2.0地球"项目显然涉及到使用OpenGL ES 2.0 API来创建一个三维的地球模型,可能包括地球的旋转、光照效果、纹理映射等特性。以下是对OpenGL ES 2.0和如何利用它来构建地球模型的详细说明: 1. **OpenGL...

    android opengl es2.0 立方体

    在android 环境下实现用opengl es 2.0画立方体 并且实现手动触摸旋转 完整代码

    《OpenGL ES 2.0 开发向导》源码

    本教程向您展示了如何创建一个简单的Android应用程序,该应用程序使用OpenGL ES 2.0 API来执行一些基本的图形操作。你将学习如何: - 使用GLSurfaceView和GLSurfaceView. renderer创建一个活动 - 创建并绘制图形对象 ...

    Android.3D游戏开发技术宝典:OpenGL.ES.2.0【part2】

    通过《Android 3D游戏开发技术宝典:OpenGL ES 2.0【part2】》,开发者将能够掌握使用OpenGL ES 2.0进行3D游戏开发的高级技巧,从而创造出更复杂、更具视觉冲击力的游戏体验。无论是初学者还是有经验的开发者,都能...

    Wrox.Game.and.Graphics.Programming.for.iOS.and.Android.with.OpenGL.ES.2.0.2012

    《Wrox.Game.and.Graphics.Programming.for.iOS.and.Android.with.OpenGL.ES.2.0.2012》这本书深入探讨了在iOS和Android平台上使用OpenGL ES 2.0进行游戏和图形编程的关键技术和实践。OpenGL ES是OpenGL的一个轻量级...

    Android-3D自动旋转的旋转木马

    在这个旋转木马的实现中,`OpenGL ES 2.0`或更高版本可能会被用到,因为我们需要使用顶点着色器和片段着色器来处理3D变换和渲染。 为了构建3D旋转木马,我们需要以下关键步骤: 1. **布局设计**:首先,在XML布局...

    android 3D立体图片旋转

    在Android中,我们可以使用OpenGL ES 2.0或更高版本来创建3D场景和动画,包括立体图片旋转。 3D立体图片旋转的核心是利用OpenGL ES的顶点着色器和片段着色器来变换和渲染图片。顶点着色器负责处理几何形状的顶点,...

    Android随手势进行3D旋转的源码.zip

    在Android中,我们可以使用GLSurfaceView来创建一个可以绘制OpenGL内容的视图,并通过其关联的GLSurfaceView.Renderer接口来实现3D场景的渲染。 在Renderer的onDrawFrame()方法中,我们会更新3D模型的旋转状态。这...

    Android 3D魔方源码

    在这个项目中,OpenGL被用来创建和操作3D对象,如魔方的各个面,使得用户能够通过触摸屏幕进行旋转和解谜。 首先,我们需要了解OpenGL ES(OpenGL for Embedded Systems),这是OpenGL的一个精简版本,特别针对移动...

    Android 随手势进行3D旋转的源码.zip

    在这个项目中,我们可能用到了OpenGL ES 2.0或更高版本,因为它们支持更高级的着色语言(GLSL),能实现复杂的3D效果。 3. OpenGL ES:要实现3D旋转,你需要创建一个顶点着色器和一个片段着色器。顶点着色器负责...

    Android源码——随手势进行3D旋转的源码_new_71.zip

    这个压缩包"Android源码——随手势进行3D旋转的源码_new_71.zip"包含了相关的图片资源和源代码,可以帮助我们理解如何在Android应用中实现实时的手势识别和3D对象的动态旋转效果。 首先,我们需要了解Android中的...

    安卓翻页效果相关-android用opengl实现电子书翻书效果代码.rar

    在Android平台上,开发一款具有逼真翻页效果的电子书应用是一项挑战,但可以通过使用OpenGL ES来实现。OpenGL ES(OpenGL for Embedded Systems)是OpenGL的一个轻量级版本,专为嵌入式设备如手机和平板设计,用于...

    Android.3D游戏开发技术宝典

    《Android.3D游戏开发技术宝典》是深入探讨Android平台上3D游戏开发的专业书籍,主要聚焦于使用OpenGL ES 2.0进行图形渲染。在3D游戏开发领域,OpenGL ES是一个至关重要的图形库,特别是在移动设备上,它是实现高...

    Barrage_OpenGLES游戏源码

    在这个项目中,开发者使用OpenGL ES来构建一个弹幕射击类的游戏,这类游戏通常需要高效地渲染大量移动对象,对性能和图形处理有较高要求。 【描述】中提到,"Barrage_OpenGLES源码"是一个有价值的Android代码示例,...

    小程序源码 opengl小怪兽3D源码.zip

    通过分析这个项目的源码,我们可以学习到如何在小程序环境中集成3D图形,以及使用OpenGL ES进行3D渲染和动画处理。这不仅有助于提升我们的编程技能,也为开发更复杂、更具交互性的移动应用程序打下坚实的基础。

    html5_3d多个旋转地球materials

    WebGL是一个JavaScript API,它基于OpenGL ES 2.0标准,能够在任何兼容的现代浏览器上提供硬件加速的3D图形渲染。要创建3D对象,如旋转的地球,我们需要理解基本的几何形状、材质和光照等概念。 1. 几何形状:在3D...

    【Android开发API】应用程序资源-OpenGL-OpenGL.pdf

    在Android应用中使用OpenGL需要理解这些基本概念,并且可能需要处理触摸事件,比如通过继承`GLSurfaceView`并实现触摸事件监听器来实现。如果你对J2ME的JSR239 OpenGL ES API有了解,需要注意Android提供的API虽然...

    安卓3d魔方(包含源码及apk)

    总的来说,【安卓3D魔方】是一个集趣味性与教育性于一体的项目,它展示了如何在Android平台上利用OpenGL ES 2.0技术实现3D交互式应用。对于学习Android开发、图形编程和算法的人来说,这是一个很好的实践案例。

    Android 使用opengl写动态壁纸的类库.zip

    在Android中,OpenGL ES通常通过Java的Android OpenGL ES API或者NDK中的C/C++ API来使用。 动态壁纸在Android中是一种特殊类型的壁纸,它可以展示实时交互的动画效果。与静态壁纸相比,动态壁纸可以提供更加生动和...

Global site tag (gtag.js) - Google Analytics