Android OpenGL ES(六)----进入三维在代码中创建投影矩阵和旋转矩阵

简介: Android OpenGL ES(六)----进入三维在代码中创建投影矩阵和旋转矩阵

我们现在准备好在代码中添加透视投影了。Android的Matrix类为它准备了两个方法------frustumM()和perspectiveM()。不幸的是,frustumM()的个缺陷,它会影响某些类型的投影,而perspectiveM()只是从Android的ICS版本开始才被引入,在早期的Android版本里并没有这个方法。我们可以简单地支持ICS及其以上的版本,但是这样会丢掉很大一部分市场,一些用户依然运行早期的Android版本。


作为替代,我们可以创建我们自己的方法来实现投影矩阵。


1.创建自己的perspectiveM


在工具包中创建新的类MatrixHelper,在开始处加入如下方法签名:

public static void perspectiveM(float[] m,float yFovInDegress,float aspect,float n,float f){


计算焦距


我们要做的第一件事就是计算焦距,这将基于在Y轴上的视野。就在方法签名之后加入如下代码:

final float angleInRadians=(float)(yFovInDegress*Math.PI/180.0);
 final float a=(float)(1.0/Math.tan(angleInRadians/2.0));

我们使用Java的Math类计算那个正切函数,因为它需要弧度角,所以我们把视野从度转换为弧度。接着计算焦距。


输出矩阵


我们现在可以更具上一节的数学证明直接写出矩阵,增加如下代码:

m[0]=a/aspect;
 m[1]=0f;
 m[2]=0f;
 m[3]=0f;
 m[4]=0f;
 m[5]=a;
 m[6]=0f;
 m[7]=0f;
 m[8]=0f;
 m[9]=0f;
 m[10]=-((f+n)/(f-n));
 m[11]=-1f;
 m[12]=0f;
 m[13]=0f;
 m[14]=-((2f*f*n)/(f-n));
 m[15]=0f;


这就是把矩阵数据存到了参数m定义的浮点数组中,这个数组需要至少16个元素。OpenGL把矩阵数据按照以列为主的顺序存储,这就意味着我们一次写一列数据,而不是一次写一行。前四个值是第一列,下一组四个数是第二列,以此类推。


2.开始使用投影矩阵


我们现在将转而使用那个透视投影矩阵了。打开你的渲染类,并从onSurfaceChanged()去掉所有的代码,只保留glViewPort()调用,加入如下代码:

MatrixHelper.perspectiveM(projectionMatrix, 45, (float) width / (float) height, 1f, 10f);


这会用45度的视野创建一个透视投影。 这个视椎体从Z值为-1的位置开始,在Z值为-10的位置结束。


加入MatrixHelper的导入后,继续前面的程序运行,你可以会发现桌面不见了,因为我们没有给桌子指定Z的位置,默认情况下Z在为0的位置。因为这个视椎体是从Z值为-1的位置开始的,除非把它移动到那个距离内,否则我们无法看到桌子。


不要硬编码Z的值,在使用投影矩阵进行投影之前,让我们使用一个平移矩阵把桌子移出来。依照惯例,我们把这个矩阵称为模型矩阵。


利用模型矩阵移动物体


在类的顶部加入如下矩阵的定义:

private final float[] modelMatrix=new float[16];


我们要使用这个矩阵把桌面移动到那个距离内。在onSurfaceChanged()结尾处,加入如下代码:

Matrix.setIdentityM(modelMatrix, 0);
 Matrix.translateM(modelMatrix,0,0f,0f,-2f);


这就是把模型矩阵设为单位矩阵,在沿着Z轴平移-2。当我们把桌面的坐标与这个矩阵相乘的时候,那些坐标最终会沿着Z轴负方向移动2个单位。


相乘一次还是相乘两次


我们现在要做一个选择:我们依然需要把这个矩阵应用于每个顶点,因此第一个选项是给顶点着色器新增一个额外的矩阵。我们把每个顶点都与这个模型矩阵相乘,让它们沿着Z轴负方向移动2个单位,接下来把每个顶点与投影矩阵相乘。这样,OpenGL就可以做透视除法,并把这些顶点变换到归一化设备坐标了。


如果不想这么麻烦,还有一个更好的方式:我们可以把模型矩阵与投影矩阵相乘,得到一个矩阵,然后把这个矩阵传递给顶点着色器。通过这种方式我们就可以在着色器中仅保留一个矩阵。


选择适当的顺序


为了弄清楚我们应该使用那种顺序,让我们看一下只使用投影矩阵的数学运算:

20.png

VerteXeye代表场景中的顶点与投影矩阵相乘之前的位置。我们一旦加入模型矩阵来移动那个桌子,这个数学运算看起来就像这样。


21.png

VerteXmodel代表顶点在模型矩阵放进场景中之前的位置。把这两个表达式合并在一起,最后得到的公式如下:


22.png

为了使用一个矩阵替换这两个矩阵,我们不得不把投影矩阵乘以模型矩阵,就是把投影矩阵放在左边,把模型矩阵放在右边。


更新代码使用一个矩阵


让我们把这个新的矩阵代码封装一下,把下面的代码加到onSurfaceChanged()中的translate()调用后面:

final float[] temp=new float[16];
 Matrix.multiplyMM(temp,0,projectionMatrix,0,modelMatrix,0);
 System.arraycopy(temp, 0, projectionMatrix, 0, temp.length);

不论什么时候把两个矩阵相乘,都需要一个临时变量来存储其结果。如果尝试直接写入这个结果,这个结果将是未定义的。


我们首先创建了一个临时的浮点数组用来存储其临时结果;然后调用multiplyMM()把投影矩阵和模型矩阵相乘,其结果存进这个临时数组。下一步,我们调用System.arraycopy()把结果存回projectMatrix,它现在包含模型矩阵与投影矩阵的组合效应。


如果我们现在运行这个程序,会发现这依然是个平面的桌面。


3.增加旋转


既然我们已经有一个配置好的投影矩阵和一个可以移动的桌子的模型矩阵,那么我们需要做的就是旋转这张桌子,以便可以从某个角度观察它。如果使用旋转矩阵,我们只需要一行代码就可以做到。我们还从来没有用过旋转,现在花一些时间了解一下这些旋转是怎么工作的。


需要弄清楚一件时是我们需要围绕哪个轴旋转以及旋转多少度。让我们再看下图:

23.png

要搞清楚一个物体是怎么围绕一个给定的轴旋转,我们将使用右手坐标规则:伸出你的右手,握拳,让大拇指指向正轴方向。倘若是一个正角度的旋转,卷曲的手指会告诉你一个物体是怎么围绕那个轴旋转的。观察上图,当你把大拇指指向X轴正方向时,看看旋转的方向是怎么跟随手指的绕轴线卷曲的。


分别用X轴,Y轴和Z轴试一下这个旋转。如果绕Y轴旋转,桌子会绕着它的顶端和低端水平旋转。如果绕着Z轴旋转,桌子会在一个圆圈内旋转。我们想要做的是让桌子绕着X轴向后旋转,因为这会让桌子看起来更有层次。


旋转矩阵


我们将使用一个旋转矩阵去做实际的旋转。旋转矩阵使用正弦和余弦三角函数把旋转转换成缩放因子。下面就是绕X轴旋转所用矩阵定义:


24.png

然后是绕Y轴旋转所用的矩阵:


25.png

最后,还有一个绕Z轴旋转所用的矩阵:


26.png


把所有矩阵合并为一个通用的旋转矩阵,使其可以基于任意一个角度和向量旋转,这也是可能的。


作为一个测试,让我们试试绕着X轴旋转。我们从一个点开始,它在原点上面一个单位,也就是Y值是1。把它绕X轴旋转90度。首先,让我们准备这个旋转矩阵:


27.png

让把这个矩阵与这个点的位置向量相乘,看看得到了什么:


28.png

这个点从(0,1,0)被移动到了(0,0,1)。如果我们回过头来看一下上面那个旋转坐标轴,并对X轴使用右手规则,我们可以看到正向旋转是如何把一个点沿着一个绕X轴的圈移动的。


4.在代码中加入旋转


我们现在准备好把这个旋转加入代码了。回到onSurfaceChanged(),调整那个平移矩阵,并加入一个旋转矩阵,如下:

Matrix.translateM(modelMatrix,0,0f,0f,-2.5f);
 Matrix.rotateM(modelMatrix,0,-60f,1f,0f,0f);

我们把这张桌子放得更远了一点,因为我们一旦把它旋转了,它的底部会距离我们更近。我们接着把它绕X轴旋转-60度,这会让桌子处于一个很好的角度,就像我们站在它前面一样。


这张桌子现在看起来应该如下图所示:

29.png


相关文章
|
27天前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
Android远程连接和登录FTPS服务代码(commons.net库)
22 1
|
1月前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异:从代码到用户体验
【10月更文挑战第5天】在移动应用开发的广阔天地中,安卓和iOS两大平台各占半壁江山。它们在技术架构、开发环境及用户体验上有着根本的不同。本文通过比较这两种平台的开发过程,揭示背后的设计理念和技术选择如何影响最终产品。我们将深入探讨各自平台的代码示例,理解开发者面临的挑战,以及这些差异如何塑造用户的日常体验。
|
2月前
|
存储 Java Android开发
🔥Android开发大神揭秘:从菜鸟到高手,你的代码为何总是慢人一步?💻
在Android开发中,每位开发者都渴望应用响应迅速、体验流畅。然而,代码执行缓慢却是常见问题。本文将跟随一位大神的脚步,剖析三大典型案例:主线程阻塞导致卡顿、内存泄漏引发性能下降及不合理布局引起的渲染问题,并提供优化方案。通过学习这些技巧,你将能够显著提升应用性能,从新手蜕变为高手。
28 2
|
3月前
|
JSON JavaScript 前端开发
Android调用Vue中的JavaScript代码
Android调用Vue中的JavaScript代码
36 3
|
3月前
|
C# Windows 开发者
当WPF遇见OpenGL:一场关于如何在Windows Presentation Foundation中融入高性能跨平台图形处理技术的精彩碰撞——详解集成步骤与实战代码示例
【8月更文挑战第31天】本文详细介绍了如何在Windows Presentation Foundation (WPF) 中集成OpenGL,以实现高性能的跨平台图形处理。通过具体示例代码,展示了使用SharpGL库在WPF应用中创建并渲染OpenGL图形的过程,包括开发环境搭建、OpenGL渲染窗口创建及控件集成等关键步骤,帮助开发者更好地理解和应用OpenGL技术。
246 0
|
3月前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
很多文章都介绍了FTPClient如何连接ftp服务器,但却很少有人说如何连接一台开了SSL认证的ftp服务器,现在代码来了。
100 2
|
4月前
|
存储 Java Android开发
🔥Android开发大神揭秘:从菜鸟到高手,你的代码为何总是慢人一步?💻
【7月更文挑战第28天】在Android开发中,每位开发者都追求极致的用户体验。然而,“代码执行慢”的问题时常困扰着开发者。通过案例分析,我们可探索从新手到高手的成长路径。
40 3
|
3月前
|
Java Android开发
Android项目架构设计问题之要提升代码的可读性和管理性如何解决
Android项目架构设计问题之要提升代码的可读性和管理性如何解决
40 0
|
4月前
|
API Android开发
Android 监听Notification 被清除实例代码
Android 监听Notification 被清除实例代码
|
5月前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。