本节书摘来异步社区《OpenGL ES 3.x游戏开发(下卷)》一书中的第2章,第2.1节,作者: 吴亚峰 责编: 张涛,更多章节内容可以访问云栖社区“异步社区”公众号查看。
飘扬的旗帜是本章要介绍的第一个案例,通过此技术可以实现类似旗帜迎风飘扬的效果,也可以实现类似水面起伏的效果。
介绍本案例的具体开发步骤之前首先需要了解实现旗帜飘扬的基本原理,如图2-1所示。
提示
图2-1中左图为原始情况下旗帜的顶点位置情况,右图为顶点着色器根据参数计算后某一帧画面中旗帜的顶点位置情况。
从图2-1中可以看出,矩形的旗帜与以前案例中绘制的矩形不同,不再是仅由两个三角形组成的整体,而是由大量的小三角形组成的。这样只要在绘制一帧画面时由顶点着色器根据一定的规则变换各个顶点的位置,即可得到旗帜迎风飘动的效果。
为了使旗帜的飘动过程比较平滑,本案例采用的是基于正弦曲线的顶点位置变换规则,具体情况如图2-2所示。
说明
图2-2给出的是旗帜面向z轴正方向,即顶点沿z轴上下振动,形成的波浪沿x轴传播的情况。同时注意,观察的方向是沿y轴的方向。
从图2-2中可以看出,传入顶点着色器的原始顶点的z坐标都是相同的(本案例中为0),经过顶点着色器变换后顶点的z坐标是根据正弦曲线分布的。具体的计算方法如下。
首先计算当前处理顶点的x坐标与最左侧顶点x坐标的差值,即X距离。然后根据距离与角度的换算率将x距离换算为当前顶点与最左侧顶点的角度差(tempAngle)。
提示
所谓距离与角度的换算率指的是由开发人员人为设定的一个值,将距离乘以它之后就可以换算成角度值。例如可以规定,X方向上距离4等于2π,则换算公式为:X距离2π/4。
接着将tempAngle加上最左侧顶点的对应角度(startAngle)即可得到当前顶点的对应角度(currAngle)。最后通过求currAngle的正弦值即可得到当前顶点变换后的z坐标。可以想象出,只要在绘制每帧画面时传入不同的startAngle值(例如在0~2π连续变化),即可得到平滑的基于正弦曲线的旗帜飘扬的动画了。
上一小节介绍了飘扬旗帜的基本原理,本小节将基于此原理开发一个旗帜迎风飘扬的案例Sample2_1,其运行效果如图2-3所示。
提示
图2-3中所示从左到右分别为X方向波浪、斜向下方向波浪和XY双向波浪的效果。由于插图是灰度印刷且是静态的,因此可能看得不是很清楚,建议读者用真机运行本案例。
了解了案例的运行效果后,接下来简要介绍本案例的具体开发过程。由于本案例中的大部分类和前面章节很多案例中的类非常相似,因此这里只给出本案例中比较有代表性的部分,具体内容如下所列。
(1)首先需要简单说明的是,表示旗帜的纹理矩形类TextureRect,其大部分代码与本书前面的很多案例基本一致。主要区别是需要增加将起始角度和角度总跨度等数据传入渲染管线的相关代码,有了这些数据后顶点着色器在绘制每帧画面前就可以顺利地对顶点位置进行变换了。
提示
增加的代码非常简单,需要的读者请自行查阅随书源代码。
(2)从案例效果图中可以看出,本案例中的波浪方向有3种选择,因此需要3套着色器来实现不同的波浪方向。首先给出最简单的实现X方向波浪的顶点着色器,其代码如下。
1 #version 300 es 2 uniform mat4 uMVPMatrix; //总变换矩阵 3 uniform float uStartAngle; //本帧起始角度(即最左侧顶点的对应角度) 4 uniform float uWidthSpan; //横向长度总跨度 5 in vec3 aPosition; //顶点位置 6 in vec2 aTexCoor; //顶点纹理坐标 7 out vec2 vTextureCoord; //用于传递给片元着色器的纹理坐标 8 void main(){ 9 float angleSpanH=4.0*3.14159265; //横向角度总跨度,用于进行X距离与角度的换算 10 float startX=-uWidthSpan/2.0; //起始x坐标(即最左侧顶点的x坐标) 11 //根据横向角度总跨度、横向长度总跨度及当前点的x坐标折算出当前顶点x坐标对应的角度 12 float currAngle=uStartAngle+((aPosition.x-startX)/uWidthSpan)*angleSpanH; 13 float tz=sin(currAngle)*0.1; //通过正弦函数求出当前点的Z坐标 14 //根据总变换矩阵计算此次绘制此顶点的位置 15 gl_Position = uMVPMatrix * vec4(aPosition.x,aPosition.y,tz,1); 16 vTextureCoord = aTexCoor; //将接收的纹理坐标传递给片元着色器 17 }说明
上述顶点着色器实现了上一小节中所介绍的基于正弦曲线的X方向波浪,其中第9行的变量angleSpanH可以用来控制波浪的密度,其值越大,波浪密度越大。
(3)接着给出实现斜向下方向波浪的顶点着色器,其代码如下。
1 #version 300 es 2 uniform mat4 uMVPMatrix; //总变换矩阵 3 uniform float uStartAngle; //本帧起始角度(即最左侧顶点的对应角度) 4 uniform float uWidthSpan; //横向长度总跨度 5 in vec3 aPosition; //顶点位置 6 in vec2 aTexCoor; //顶点纹理坐标 7 out vec2 vTextureCoord; //用于传递给片元着色器的纹理坐标 8 void main(){ 9 float angleSpanH=4.0*3.14159265; //横向角度总跨度,用于进行X距离与角度的换算 10 float startX=-uWidthSpan/2.0; //起始x坐标(即最左侧顶点的x坐标) 11 //根据横向角度总跨度、横向长度总跨度及当前点的x坐标折算出当前顶点x坐标对应的角度 12 float currAngleH=uStartAngle+((aPosition.x-startX)/uWidthSpan)*angleSpanH; 13 float angleSpanZ=4.0*3.14159265; //纵向角度总跨度,用于进行Y距离与角度的换算 14 float uHeightSpan=0.75*uWidthSpan; //纵向长度总跨度 15 float startY=-uHeightSpan/2.0; //起始y坐标(即最上侧顶点的y坐标) 16 //根据纵向角度总跨度、纵向长度总跨度及当前点y坐标折算出当前顶点y坐标对应的角度 17 float currAngleZ=((aPosition.y-startY)/uHeightSpan)*angleSpanZ; 18 float tzH=sin(currAngleH-currAngleZ)*0.1; //通过正弦函数求出当前点的z坐标 19 //根据总变换矩阵计算此次绘制此顶点的位置 20 gl_Position = uMVPMatrix * vec4(aPosition.x,aPosition.y,tzH,1); 21 vTextureCoord = aTexCoor; //将接收的纹理坐标传递给片元着色器 22 }说明
本质上讲,上述斜向下方向波浪的顶点着色器与前面的X方向波浪的顶点着色器没有本质区别,仅仅是在计算当前顶点的对应角度时增加了y轴方向的计算,不再是仅考虑x轴的坐标。因此,形成的波浪方向就是斜向下的。
(4)最后给出的是沿X、Y两个方向各自传播的波浪效果叠加的顶点着色器,其代码如下。
1 #version 300 es 2 uniform mat4 uMVPMatrix; //总变换矩阵 3 uniform float uStartAngle; //本帧起始角度(X、Y两个方向都是其值) 4 uniform float uWidthSpan; //横向长度总跨度 5 in vec3 aPosition; //顶点位置 6 in vec2 aTexCoor; //顶点纹理坐标 7 out vec2 vTextureCoord; //用于传递给片元着色器的纹理坐标 8 void main(){ 9 //首先计算当前顶点X方向波浪对应的Z坐标 10 float angleSpanH=4.0*3.14159265; //横向角度总跨度,用于进行X距离与角度的换算 11 float startX=-uWidthSpan/2.0; //起始x坐标(即最左侧顶点的x坐标) 12 //根据横向角度总跨度、横向长度总跨度及当前点的x坐标折算出当前顶点x坐标对应的角度 13 float currAngleH=uStartAngle+((aPosition.x-startX)/uWidthSpan)*angleSpanH; 14 float tzH=sin(currAngleH)*0.1; //X方向波浪对应的z坐标 15 //接着计算当前顶点Y方向波浪对应的Z坐标 16 float angleSpanZ=4.0*3.14159265; //纵向角度总跨度,用于进行Y距离与角度的换算 17 float uHeightSpan=0.75*uWidthSpan; //纵向长度总跨度 18 float startY=-uHeightSpan/2.0; //起始y坐标(即最上侧顶点的y坐标) 19 //根据纵向角度总跨度、纵向长度总跨度及当前点的y坐标折算出当前顶点y坐标对应的角度 20 float currAngleZ=uStartAngle+3.14159265/3.0+ 21 ((aPosition.y-startY)/uHeightSpan)*angleSpanZ; 22 float tzZ=sin(currAngleZ)*0.1; //Y方向波浪对应的z坐标 23 //根据总变换矩阵计算此次绘制此顶点的位置 24 gl_Position = uMVPMatrix * vec4(aPosition.x,aPosition.y,tzH+tzZ,1); 25 vTextureCoord = aTexCoor; //将接收的纹理坐标传递给片元着色器 26 }提示
本质上讲,上述X、Y双向波浪的顶点着色器与前面的X方向波浪的顶点着色器没有本质区别,仅仅是首先分别计算了X方向和Y方向波浪在当前顶点位置的z坐标,最后将两个z坐标叠加,从而实现了波的叠加。因此,运行案例时看到的波浪就是X、Y两个方向的了。
本案例3套着色器中的片元着色器都是一样的,并且采用的都是普通的纹理采样片元着色器,前面很多案例中已经出现过,因此这里不再赘述。
相关资源:Android 3D游戏开发技术宝典——OpenGL ES 2.0 (吴亚峰) 源代码