OpenGL ES 2.0游戏开发(下卷)
上QQ阅读APP看书,第一时间看更新

1.5 二维扭曲

本章前面几节的案例都是在3D空间中对顶点位置进行的变换,但本节将给出一个在2D空间中基于顶点位置变换进行二维扭曲的案例。

提示

本质上可以采用本节介绍的技术扭曲任何2D形状,为了便于介绍,本节将基于等边三角形进行介绍。

1.5.1 基本原理

介绍本节案例的具体开发之前,首先需要了解一下二维扭曲的基本情况,如图1-15所示。

▲图1-15 扭动的三角形线框图

▲图1-16 扭动的三角形原理图

从图1-15中可以看出,左侧的原始三角形经过扭曲处理后产生了右侧奇异的形状,犹如一个风车。同时从图1-15中可以看出,要想能对原始三角形实现扭曲处理,必须将大三角形切分为很多的小三角形。下面就简单介绍一下扭曲的计算思路(如图1-16所示),具体步骤如下。

(1)设扭动的中心点为点O,其坐标为(X0,Y0);绕中心点被扭动的点为D,其坐标为(X1,Y1)。

(2)设D点在x方向上的偏移为XSpan,y方向上的偏移为YSpan,则有如下结论。

XSpan= X1- X0

YSpan= Y1- Y0

(3)接着就可以求出OD与x轴正方向的夹角θ,具体情况如下。

● 如果XSpan=0,并且YSpan大于0,那么θ=π/2。

● 如果XSpan=0,并且YSpan小于0,那么θ=3 π/2。

● 如果XSpan不等于0,那么θ=atan(YSpan/ XSpan)。

(4)然后计算旋转后的D点与x轴正方向的夹角θ'。

θ'=θ+ratio× OD

说明

其中ratio表示与当前总体旋转角度线性相关的一个系数,用于将距离转化为当前考察点的旋转角度。

(5)计算出旋转后的夹角后,就可以求出旋转后点的xy坐标了。

X1'= X0+ OD×cos(θ')

Y1'= Y0+ OD ×sin(θ')

只需要用顶点着色器实现上述算法即可得到非常漂亮的二维扭曲效果。

1.5.2 开发步骤

上一小节中介绍了二维扭曲的基本原理以及注意事项,本小节将给出一个三角形二维扭曲的案例Sample1_5,其运行效果如图1-17所示。

▲图1-17 案例Sample1_5的运行效果图

了解了案例的运行效果后,接下来将对本案例的具体开发过程进行简要介绍。由于本案例中的大部分类和前面章节很多案例中的非常类似,因此在这里只给出本案例中比较有代表性的部分,具体内容如下所列。

(1)从前面的介绍中已经知道,本案例中的大三角形实际上是由很多小三角形构成的。因此,下面首先给出的是自动生成各个小三角形顶点数据的initVertexData方法,其来自MultiTrangle类,具体代码如下。

代码位置:见随书光盘中源代码/第1章/Sample1_5/com/bn/Sample1_5目录下的MultiTrangle.java。

      1    public void initVertexData(float edgeLength,int levelNum){
      2          ArrayList<Float> al_vertex=new ArrayList<Float>();  //顶点坐标数据列表
      3          ArrayList<Float> al_texture=new ArrayList<Float>(); //纹理坐标数据列表
      4          float perLength = edgeLength/levelNum;               //小三角形的边长
      5          for(int i=0;i<levelNum;i++){                         //循环每一层生成小三角形
      6               int currTopEdgeNum=i;                           //当前层顶端边数
      7               int currBottomEdgeNum=i+1;                      //当前层底端边数
      8               float currTrangleHeight=(float)(perLength*Math.sin(Math.PI/3));
                                                                        //每个三角形的高度
      9               float topEdgeFirstPointX=-perLength*currTopEdgeNum/2;
                                                                    //当前层顶端最左边点的x坐标
      10              float topEdgeFirstPointY=-i*currTrangleHeight;//当前层顶端最左边点的y坐标
      11              float topEdgeFirstPointZ=0;                //当前层顶端最左边点的 z坐标
      12              float bottomEdgeFirstPointX=-perLength*currBottomEdgeNum/2;//当前层底端最
      13              float bottomEdgeFirstPointY=-(i+1)*currTrangleHeight;   //左边点的 x、
      14              float bottomEdgeFirstPointZ=0;                  //yz坐标
      15              float horSpan=1/(float)levelNum;                //横向纹理的偏移量
      16              float verSpan=1/(float)levelNum;                //纵向纹理的偏移量
      17              float topFirstS=0.5f-currTopEdgeNum*horSpan/2; //当前层顶端最左边点的
      18              float topFirstT=i*verSpan;                      //纹理ST坐标
      19              float bottomFirstS=0.5f-currBottomEdgeNum*horSpan/2;
                                                                        //当前层底端最左边点的
      20              float bottomFirstT=(i+1)*verSpan;               //纹理ST坐标
      21              for(int j=0;j<currBottomEdgeNum;j++){//循环产生当前层各个上三角形的顶点数据
      22                    float topX=topEdgeFirstPointX+j*perLength;    //当前三角形顶端点的
      23                    float topY=topEdgeFirstPointY;                 //xyz坐标
      24                    float topZ=topEdgeFirstPointZ;
      25                    float topS=topFirstS+j*horSpan;                //当前三角形顶端点的
      26                    float topT=topFirstT;                          //S、T纹理坐标
      27                    float leftBottomX=bottomEdgeFirstPointX+j*perLength;
                                                                              //当前三角形左下侧
      28                    float leftBottomY=bottomEdgeFirstPointY;      //点的 xyz坐标
      29                    float leftBottomZ=bottomEdgeFirstPointZ;
      30                    float leftBottomS=bottomFirstS+j*horSpan;     //当前三角形左下侧
      31                    float leftBottomT=bottomFirstT;                //点的S、T纹理坐标
      32                    float rightBottomX=leftBottomX+perLength;     //当前三角形右下侧
      33                    float rightBottomY=bottomEdgeFirstPointY;     //点的 xyz坐标
      34                    float rightBottomZ=bottomEdgeFirstPointZ;
      35                    float rightBottomS=leftBottomS+horSpan;       //当前三角形右下侧
      36                    float rightBottomT=leftBottomT;                //点的S、T纹理坐标
      37                    //将当前三角形顶点数据按照逆时针顺序送入顶点坐标、纹理坐标列表
      38                    al_vertex.add(topX);al_vertex.add(topY);al_vertex.add(topZ);
      39                   al_vertex.add(leftBottomX);al_vertex.add(leftBottomY);al_vertex.
                                  add(leftBottomZ);
      40                    al_vertex.add(rightBottomX);al_vertex.add(rightBottomY);al_vertex.
                                  add(rightBottomZ);
      41                    al_texture.add(topS);al_texture.add(topT);
      42                    al_texture.add(leftBottomS);al_texture.add(leftBottomT);
      43                    al_texture.add(rightBottomS);al_texture.add(rightBottomT);
      44             }
      45              for(int k=0;k<currTopEdgeNum;k++){ //循环产生当前层各个下三角形的顶点数据
      46                   float leftTopX=topEdgeFirstPointX+k*perLength; //当前三角形左上侧
      47                    float leftTopY=topEdgeFirstPointY;             //点的 xyz坐标
      48                    float leftTopZ=topEdgeFirstPointZ;
      49                    float leftTopS=topFirstS+k*horSpan;            //当前三角形左上侧
      50                    float leftTopT=topFirstT;                      //点的S、T纹理坐标
      51                    float bottomX=bottomEdgeFirstPointX+(k+1)*perLength;
                                                                              //当前三角形底端点
      52                    float bottomY=bottomEdgeFirstPointY;           //的 xyz坐标
      53                    float bottomZ=bottomEdgeFirstPointZ;
      54                    float bottomS=bottomFirstS+(k+1)*horSpan;     //当前三角形右底端点
      55                    float bottomT=bottomFirstT;                    //的S、T纹理坐标
      56                    float rightTopX=leftTopX+perLength;            //当前三角形右上侧
      57                    float rightTopY=leftTopY;                      //点的 xyz坐标
      58                    float rightTopZ=leftTopZ;
      59                    float rightTopS=leftTopS+horSpan;              //当前三角形右上侧
      60                    float rightTopT=topFirstT;                     //点的S、T纹理坐标
      61                    al_vertex.add(leftTopX);al_vertex.add(leftTopY);al_vertex.
                                  add(leftTopZ); //逆时针卷绕
      62                    al_vertex.add(bottomX);al_vertex.add(bottomY);al_vertex.add(bottomZ);
      63                    al_vertex.add(rightTopX);al_vertex.add(rightTopY);al_vertex.
                                  add(rightTopZ);
      64                    al_texture.add(leftTopS);al_texture.add(leftTopT);
      65                    al_texture.add(bottomS);al_texture.add(bottomT);
      66                    al_texture.add(rightTopS);al_texture.add(rightTopT);
      67              }}
      68              ……//此处省略部分代码,需要的读者请参考光盘
      69   }

说明

上述代码根据传入的大三角形边长及分层数量自动计算出了每一层中各个三角形的顶点坐标、纹理坐标。每一层中的三角形分为上三角形与下三角形,顶点计算方法不同,如图1-18所示。

▲图1-18 每层的上三角形与下三角形

(2)要能够呈现出扭动的三角形就需要在绘制每一帧时将不同的、连续变化的整体扭动角度因子(就是上一小节算法中的ratio)传入渲染管线。由于这部分代码非常简单,这里不再赘述,需要的读者请参考随书光盘。

(3)接着介绍的是实现二维扭曲算法的顶点着色器,其代码如下。

代码位置:见随书光盘中源代码/第1章/Sample1_5/ assets目录下的vertex_tex.sh。

      1    uniform mat4 uMVPMatrix;                              //总变换矩阵
      2    attribute vec3 aPosition;                             //顶点位置
      3    attribute vec2 aTexCoor;                          //顶点纹理坐标
      4    varying vec2 vTextureCoord;                       //用于传递给片元着色器的纹理坐标
      6    uniform float ratio;                              //当前整体扭动角度因子
      7    void main(){
      8          float pi = 3.1415926;                       //圆周率
      9          float centerX=0.0;                           //中心点 x坐标
      10         float centerY=-5.0;                         //中心点 y坐标
      11         float currX = aPosition.x;                  //当前点 x坐标
      12         float currY = aPosition.y;                  //当前点 y坐标
      13         float spanX = currX - centerX;              //当前X偏移量
      14         float spanY = currY - centerY;              //当前Y偏移量
      15         float currRadius = sqrt(spanX * spanX + spanY * spanY);  //计算距离
      16         float currRadians;                      //当前点与 x轴正方向的夹角,用弧度表示
      17         if(spanX != 0.0){                      //计算当前点与 x轴正方向的夹角
      18              currRadians = atan(spanY , spanX); //一般情况
      19         }else{
      20              currRadians = spanY > 0.0 ? pi/2.0 : 3.0*pi/2.0; //0.5π和1.5π的特殊情况
      21         }
      22         float resultRadians = currRadians + ratio*currRadius;    //计算出扭曲后的角度
      23         float resultX = centerX + currRadius * cos(resultRadians); //计算结果点的x坐标
      24         float resultY = centerY + currRadius * sin(resultRadians);//计算结果点的y坐标
      25         //构造结果点,并根据总变换矩阵计算此次绘制此顶点的位置
      26         gl_Position = uMVPMatrix * vec4(resultX,resultY,0.0,1);
      27         vTextureCoord = aTexCoor;               //将接收的纹理坐标传递给片元着色器
      28   }

说明

上述顶点着色器实现了上一小节介绍的二维扭曲算法,通过应用此顶点着色器可以对任意的二维物体进行扭曲,并不一定是三角形。

进行二维扭曲效果的开发时,有一个需要特别注意的地方。那就是提供给渲染管线的模型一定要分得比较细,如果分得比较粗糙,最终的结果就会比较差,如图1-19所示。

▲图1-19 模型切分得很粗导致扭曲效果很差

说明

图1-19给出了将本案例中的大三角形切分得比较粗糙后的运行效果以及线框图。从图1-19中可以看出,切分得很粗糙后实际扭曲的效果与期望的效果之间就有较大的差距了。