iOS开垦学习OpenGL,用编制程序情势编写Babylon格式

时间:2019-05-03 08:05来源:亚洲城ca88唯一官方网站
使用上一篇文章( 原文地址:http://blog.csdn.net/liulong1567/article/details/50616523 从数学上说,变换就是在两个坐标系之间转换顶点坐标。每个坐标系都是相对于其他的参照坐标系定义的。对

  使用上一篇文章(  

原文地址:http://blog.csdn.net/liulong1567/article/details/50616523

从数学上说,变换就是在两个坐标系之间转换顶点坐标。每个坐标系都是相对于其他的参照坐标系定义的。对于OpenGL ES来说,最终的参照坐标系是在一个像素颜色渲染缓存中的像素位置的2D数组。

  我们首先对3D模型的轮廓进行估计,然后制作一个拥有足够多顶点的、与模型轮廓近似的网格对象(这里选用条带类网格对象),接着对网格的部分顶点进行位置变换以产生模型的细节,最后为模型设置一个材质。

转换矩阵

模型网格的三维空间位置都是由它们的顶点坐标决定的,如果每次想要移动一下模型位置都要依次改变每个网格的顶点坐标,这将一件非常头疼的事,要是遇上需要显示动画效果那就更糟了。为了解决这个问题,大部分的三维系统都会提供转换操作,这个操作原理是整体移动网格,这样网格与世界坐标就有一个相对转换,而不需要去改变每一个顶点的坐标值。其中,转换操作包括:移动、旋转、缩放,这些操作都是针对网格整体相对世界坐标系的,而不是特定的每一个顶点。
图3-3中展示了转换操作,图中有三个立方体,每一个立方体都是由一个立方体网格组成,它们都包含相同的顶点,在我们进行移动、旋转、缩放操作的时候不需要改变这些顶点的坐标值,而是给立方体网格赋予一个转换操作。左边红色的立方体向右移动了4个单位(进行了[-4,0,0]操作),然后又相对x和y轴进行了旋转(这里注意一下,我们这里角度的单位是弧度,即一弧度等于360度除以2PI)。右边蓝色的立方体向右移动了4个单位,然后对三个方向都放大了1.5倍,中间绿色立方体就是最初始位置。

亚洲城ca88唯一官方网站 1

*
图3-3
我们用一个矩阵来代表转换操作,这个矩阵中保存着一个数组,通过这个数组进行一些数学计算就可以得到转换以后的顶点坐标值。大部分的转换矩阵用44的数组表示,这个数组包含4行4列一共16个数。图3-4就是一个44数组的示意图,其中m12、m13、m14用来操作移动,m0、m5、m10用来操作缩放,m1和m2、m4和m6、m8和m9分别用来操作相对x、y、z轴的旋转,转换矩阵乘以顶点坐标就是转换之后的坐标。

亚洲城ca88唯一官方网站 2

图3-4
如果你同我一样是个线性代数极客,我这么讲你肯定听得懂,如果你不熟悉线性代数也没关系,Unity3D以及其它工具中已经将这些操作都封装好了,我们只需要正确调用它们的API即可,但是了解一下这些操作的底层计算过程总还是好的。

OpenGL ES中的四种基本变换:平移(translation)、旋转(rotation)。基本变换的联合决定了在一个新的坐标系中的每一个顶点位置是怎么转换为参考坐标系中的一个位置的。四个基本变换足以产生无穷的坐标系。

  当然Babylon.js还支持更复杂的纹理类型,我翻译了Babylon.js官方教程中关于反射与折射,反射探查,地图纹理,多重材质,动态纹理,高亮描边的文档(部分文档翻译的不明确,因为官方文档本身的表述也不是很明确),可以在

每个基本变换对应于矩阵的一个简单变化。定义一个与参考坐标系相同的坐标系的矩阵叫做单位矩阵。任意两个矩阵可以在一个联级(又叫矩阵乘法)操作中结合起来以产生一个新矩阵,这个新矩阵包含了两个矩阵的所有变换。实际上,每个基本变换会产生一个简单的矩阵,然后把这个简单的矩阵与当前变换矩阵连接起来以产生一个新的当前矩阵。

  1、从顶部看,估计飞船的首尾长度为30单位,船体最宽处半径为7单位,船头处呈圆滑的锥形;从船头方向看,船体顶部为较扁的圆弧,船底部边缘圆滑中间平直(有点像上个世纪的航天飞机)。草图如下:

定义全局

亚洲城ca88唯一官方网站 3

平移
通过相对于参考坐标系的原点移动新坐标系的原点、平移定义了一个新的坐标系。平移不会影响坐标轴的单位长度,平移不会改变坐标轴相对于参考坐标系的方向。
GLKit提供了GLKMatrix4MakeTranslation(float x,float y,float z)函数,这个函数通过平移一个单位矩阵来返回一个定义了坐标系的新矩阵。x、y、z参数指定了新坐标原点沿着当前参考坐标系的每个轴移动的单位数。

  对于船体上部,高度低于2的部分直接使用半径为7的圆弧作为仓壁,高于2的部分则将高度削减二分之一;对于船体下部,将大致形状设为压扁到四分之一的半圆,再将高度低于-1的部分设为平直的船底。

修改代码,看一下GLKit提供的GLKMatrix4MakeTranslation函数实现平移效果。

  规定船体沿x轴方向摆放,船体中心位于世界坐标系原点,船头朝向x轴负方向,船顶朝向y轴正方向。

在顶点着色器中新增了一个uniform mat4 transform。 mat4这个类型前文有提到过,4X4的矩阵。它是Shader内置的类型,支持直接加减乘等操作。使用矩阵会产生更少的运算指令,GPU可以更好的优化运算过程。

  事实上,在编写3D模型时固定的长度数值并没有决定性的意义(当然过大或过小可能导致物体脱出视场),决定模型形状的关键是各处尺寸之间的比例关系,具体的尺寸大小都可以在载入模型后根据需要进行缩放,这里将船体长度设为30单位是为了在预设的编辑场景里方便查看。

attribute vec4 position;
attribute vec4 color;
varying vec4 fColor;
uniform mat4 transform;
void main(void) {
    fColor = color;
    gl_Position = transform * position;
}

  然后开始构建一个符合上述轮廓的条带网格。

因为我们的数据更新在update里,而赋值绘制在glkView:(GLKView *)view drawInRect:(CGRect)rect中,所以需要定义为全局:

  2、开始编写条带网格的路径(顶点数组),首先生成一个半径是7的圆形路径,规定圆弧由128个顶点组成(事实上最终生成的路径有129个顶点):

GLKMatrix4 transformMatrix;

// 初始化为单位矩阵,不对图形产生任何变换
transformMatrix = GLKMatrix4Identity;
 1 function MakeRing(radius,sumpoint)//两个参数分别是圆形的半径和圆形由多少个顶点组成
 2     {
 3         var arr_point=[];//顶点数组
 4         var radp=Math.PI*2/sumpoint;//每一个顶点在圆弧上转过的角度
 5         for(var i=0.0;i<sumpoint;i  )
 6         {
 7             var x=0;
 8             var rad=radp*i;
 9         //算出顶点的y、z坐标
10             var y=radius*Math.sin(rad);
11             var z=radius*Math.cos(rad);
12             arr_point.push(new BABYLON.Vector3(x,y,z));
13         }
14         arr_point.push(arr_point[0].clone());//为了保持首尾相连,要再添加一次第一个顶点
15         return arr_point;
16     }

这里的GLKMatrix4Identity相当于下面的矩阵:

  计算y、z坐标的示意图如下:

亚洲城ca88唯一官方网站 4

亚洲城ca88唯一官方网站 5

接下来在应用中创建一个平移变换矩阵:

  y和z的计算需要用到初中数学的三角函数知识。

// 定义一个在值域-1和1之间的局部变量
GLfloat elValue = sinf(changeValue);

// 创建平移变换矩阵,这里只改变了X坐标
GLKMatrix4 translateMatrix = 
GLKMatrix4MakeTranslation(elValue, 0.0, 0.0);

  接下来使用“var arr1=TranceRing1(MakeRing(7,128));”将圆形路径变成我们设计的船体截面路径,TranceRing1方法代码如下:

把这个变换矩阵赋值到uniform mat4 transform上:

 1 //上下挤压,对于每个顶点都生效的变换尽量只执行一次
 2 function TranceRing1(arr)
 3 {
 4     var len=arr.length;
 5     for(var j=0;j<len;j  )
 6     {
 7         var obj=arr[j];
 8         if(obj.y<0)
 9         {
10             obj.y=obj.y/4;
11             if(obj.y<-1)
12             {
13                 obj.y=-1;
14             }
15         }
16         else if(obj.y>2)
17         {
18             obj.y=(obj.y-2)/2 2;
19         }
20     }
21     return arr;
22 }
GLuint transformUniformLocation = glGetUniformLocation(program, "transform");
glUniformMatrix4fv(transformUniformLocation, 1, 0, transformMatrix.m);

  这里的算法很简单,遍历路径中的每个顶点,然后根据上面的设计进行逻辑判断即可。

绘制之后看下效果:

  3、将上面生成的一条路径克隆为多条路径,规定每两条路径之间的距离为0.25:

亚洲城ca88唯一官方网站 6

1 arr_path=[];//路径数组
2     var xstartl=-15;//设置船头(也就是第一个圆环路径)在x轴上的位置    
3     var arr1=TranceRing1(MakeRing(7,128));
4     for(var i=0;i<121;i  )
5     {
6         var arr_point=CloneArrPoint(arr1);//克隆一条路径
7         arr_path.push(MoveX(arr_point,i*0.25 xstartl));//将克隆出的路径沿x轴方向平移
8     }

因为我们只是平移了坐标X值,Y和Z值没有变,所以三角形只是沿着X轴平行移动。

  路径克隆的示意图如下:

上面的elValue值域为[-1,1],先看一下为-1的时候,我们取其中一个顶点(0,0.5,0)经过(-1,0,0)的平移之后为(-1,0.5,0),我们看一下矩阵计算:

亚洲城ca88唯一官方网站 7

亚洲城ca88唯一官方网站 8

  克隆路径和x轴平移的方法如下:

如果平移经过(1,0,0)的平移之后就为:(1 0.5 0 1),这就是我们上面看到的三角形的上顶点在屏幕之间移动了。

 1 //克隆复制对象数组
 2     function CloneArrPoint(arr)
 3     {
 4         var arr2=[];
 5         var len=arr.length;
 6         for(var i=0;i<len;i  )
 7         {
 8             arr2.push(arr[i].clone());
 9         }
10         return arr2;
11     }
12     //平移x轴
13     function MoveX(path,dis)
14     {
15         var len=path.length;
16         for(var i=0;i<len;i  )
17         {
18             path[i].x =dis;
19         }
20         return path;
21     }

通过上面的矩阵计算可以发现,平移矩阵就是在一个4x4的变换矩阵中填充第4行前面三个位置:

  4、使用上一篇文章中提到的方法生成条带网格:

亚洲城ca88唯一官方网站 9

1 var arr7=MakePointPath(new BABYLON.Vector3(15,0,0),129);//用一个点封口
2     arr_path.push(arr7);
3 
4     mesh_origin=BABYLON.MeshBuilder.CreateRibbon("mesh_origin",{pathArray:arr_path
5         ,updatable:true,closePath:false,closeArray:false});
6     mesh_origin.material=mat_frame;

缩放
缩放是通过相对于参考坐标系的坐标轴的单位长度改变新坐标系的坐标轴的单位长度来定义一个新坐标系。缩放坐标系与参考坐标系使用同一个原点。坐标轴的方向通常不会改变。不过,通过一个负值所做的缩放就会翻转坐标轴的方向。

  这里的arr7是位于同一个位置的129个顶点,用来给敞开的船尾封口(使用多余的顶点算是条带网格模型的一个缺点,但这个缺点和条带网格的易用性比起来可以接受)至于船首的封口则由后面的网格变换负责。

GLKit提供了GLKMatrix4MakeScale(float x,float y,float z)函数,这个函数会通过扩大或者缩小一个单位矩阵的任意坐标轴的单位长度来返回一个定义了坐标系的矩阵。x、y和z参数指定了用来扩大或者缩小每个轴的单位长度的因素。GLKMatrix4Scale(float x,float y,float z)函数通过按指定的因数缩放作为参数传入矩阵来返回一个定义了坐标系的新矩阵。

  MakePointPath代码如下:

GLKMatrix4 scaleMatrix = GLKMatrix4MakeScale(elValue, elValue, 1.0);
// 很明显这个矩阵会改变顶点坐标的x、y值
 1 //用一个重合点路径封口
 2 function MakePointPath(vec,size)
 3 {
 4     var arr_point=[];
 5     for(var i=0;i<size;i  )
 6     {
 7         arr_point.push(vec.clone());
 8     }
 9     return arr_point;
10 }

看一下最终效果:

  生成的轮廓网格如下图:

亚洲城ca88唯一官方网站 10

亚洲城ca88唯一官方网站 11

简单分析:elValue为正弦函数的值,所以在-1和1之间,在elValue为1是,不会对原数据有影响,当为-1时,就会翻转坐标轴的方向。所以最终效果就是上图那样,从原图缩小到0,然后负数增大,就是倒过来了。

  5、通过顶点变换生成锥形的船头:

假定还是点(0.0,0.5,0.0)的x和y值在缩小0.5之后为(0.0,0.25,0.0),看下矩阵计算:

  按照设计,从顶部俯视船体的前半部分是一个z向半径为7、x向半径为15的“圆弧形”,从侧面看船头的上部是y向半径为3.25、x向半径为5的圆弧形,船头的下部是y向半径为1、x向半径为2的圆弧形。

亚洲城ca88唯一官方网站 12

  侧面示意图如下:

旋转
旋转是通过相对于参考坐标系坐标轴的方向旋转新坐标系的坐标轴来定义一个新坐标系。旋转的坐标系会与参考坐标系使用同一个原点。旋转不会影响坐标轴的单位长度,只有坐标轴的方向会发生变化。

亚洲城ca88唯一官方网站 13

GLKit提供了GLKMatrix4MakeRotation(float angleRedians, float x, float y, float z)函数,这个函数通过旋转一个单位矩阵来返回一个定义了坐标系的新矩阵。angleRedians参数指定了要旋转的弧度数。使用GLKMathDegreesToRadians(float degrees)函数可以把角度转换成弧度。x、y和z参数用于指定当前坐标系的哪一个轴作为旋转的轮毂。

  船首的变形代码如下:

例如:GLKMatrix4MakeRotation(GLKMathDegreesToRadians(60.0) , 1.0, 0.0, 0.0)会沿着一个特定的坐标系的X轴旋转60度来产生一个新的坐标系。

 1 //有的顶点变换会受到周围顶点的影响,所以要在已经构造好的基础上进行变换
 2 function TransCraft()
 3 {
 4     var len=arr_path.length;
 5     //遍历每个点,用程序判断这个点是否符合某些标准,并进行相应变化
 6     for(var i=0;i<len;i  )
 7     {
 8         var arr_point=arr_path[i];
 9         var len2=arr_point.length;
10         for(var j=0;j<len2;j  )
11         {
12             var obj=arr_point[j];
13             //var x=obj.x;
14             //var y=obj.y;
15             //var z=obj.z;
16             //船首呈椎体状
17             if(obj.x<-13&&obj.y<0)//从侧面看的船首下部
18             {
19                 var rate=Math.sin(Math.acos((-13-obj.x)/2/1));//y轴方向缩放系数
20                 obj.y=obj.y*rate;
21             }
22             if(obj.x<-10&&obj.y>0)//从侧面看的船首上部
23             {
24                 var rate=Math.sin(Math.acos((-10-obj.x)/(5/3.25)/3.25));//y轴方向缩放系数
25                 obj.y=obj.y*rate;
26             }
27             if(obj.x<0)//从顶部看的船首
28             {
29                 var rate=Math.sin(Math.acos((-obj.x)/(15/7)/7));//y轴方向缩放系数
30                 obj.z=obj.z*rate;
31             }

相对于平移和缩放,旋转要复杂一些。旋转包含两个重要元素,旋转的角度,绕什么轴旋转。

  用不同的比例对路径进行压缩,将原来尺寸相同的路径变成尺寸渐变的路径,路径连成的条带网格就会呈现椎体的形状,那么问题就在于如何计算这个缩放的比例,使得椎体的表面呈现为圆滑的弧形。

GLKMatrix4 rotateMatrix = GLKMatrix4MakeRotation(elValue , 0.0, 0.0, 1.0);
// 绕Z轴旋转

  我将圆弧定义为拉伸的正圆形的一部分,然后由x坐标值计算出对应路径的缩放比例,原理图如下(以“从侧面看的船首上部”为例):

看下效果图:

亚洲城ca88唯一官方网站 14

亚洲城ca88唯一官方网站 15

  首先将从侧面看船头上部的中间截面通过将x坐标除以(5/3.25)的方式变换为正圆的一部分,用(-10-obj.x)/(5/3.25)计算出“xsize”的长度,因为y轴缩放比例等于在这个截面上顶点高度(y值)和半径(r)的比等于sin(a),所以只需求出角a的大小即可算出比例,而角a的大小可以由(xsize/r)的反余弦得出。如此得出y方向的缩放比例。

除了利用GLKit提供的GLKMatrix4MakeRotation函数直接生成,我们还可以自己定义4x4的旋转矩阵:

  从顶部看的缩放比例也是如此计算,这时计算得到的是z轴方向的缩放比例。

// 绕x轴
    transformMatrix = GLKMatrix4Make(1.0, 0.0,           0.0,          0.0,
                                     0.0, cos(elValue), -sin(elValue), 0.0,
                                     0.0, sin(elValue),  cos(elValue), 0.0,
                                     0.0, 0.0,           0.0,          1.0);

    // 绕y轴
    transformMatrix = GLKMatrix4Make(cos(elValue),0.0, sin(elValue), 0.0,
                                     0.0,         1.0, 0.0,          0.0,
                                    -sin(elValue),0.0, cos(elValue), 0.0,
                                     0.0,         0.0, 0.0,          1.0);

    // 绕z轴
    transformMatrix = GLKMatrix4Make(cos(elValue),-sin(elValue), 0.0, 0.0,
                                     sin(elValue), cos(elValue), 0.0, 0.0,
                                     0.0,           0.0,         1.0, 0.0,
                                     0.0,           0.0,         0.0, 1.0);

  缩放后的显示效果如下:

关于旋转矩阵可以参考这篇三维空间中的旋转:旋转矩阵、欧拉角

亚洲城ca88唯一官方网站 16

下面结合三种变换,直接相乘就可以,但是要注意顺序:平移旋转缩放。这样可以保证先缩放,再旋转,最后平移。

  可以看到船头的129个顶点被缩放到了同一位置,船头呈现圆滑的弧线。

// 缩放
GLKMatrix4 scaleMatrix = GLKMatrix4MakeScale(elValue, elValue, 1.0);

// 旋转
GLKMatrix4 rotateMatrix = GLKMatrix4MakeRotation(elValue , 0.0, 0.0, 1.0);

// 平移
GLKMatrix4 translateMatrix = GLKMatrix4MakeTranslation(elValue, elValue, 0.0);

/*
transformMatrix = translateMatrix * rotateMatrix * scaleMatrix
矩阵会按照从右到左的顺序应用到position上。也就是先缩放(scale),再旋转(rotate),最后平移(translate)
如果这个顺序反过来,就完全不同了。从线性代数角度来讲,就是矩阵A乘以矩阵B不等于矩阵B乘以矩阵A。
*/
transformMatrix = GLKMatrix4Multiply(translateMatrix, rotateMatrix);
transformMatrix = GLKMatrix4Multiply(transformMatrix, scaleMatrix);

  6、生成飞船的后掠翼,生成原理与船首类似:

看下最终包含了平移、缩放、旋转的效果:

 1 //后掠翼,具有圆弧状的边缘
 2             if(obj.x>0&&obj.y>0&&obj.y<1)
 3             {
 4                 //这一层翼面和最小翼面的边缘差值
 5                 var rate=Math.cos(Math.asin(Math.abs(0.5-obj.y)/(0.5/1)/1));
 6                 var size1=1*rate;
 7                 var h=14 size1;
 8                 var w=6.5 size1;
 9                 if((15-obj.x)<h)
10                 {
11                     var rate2=Math.cos(Math.asin(Math.abs(15-obj.x)/(h/w)/w));
12                     if(obj.z>0)
13                     {
14                         obj.z =w*rate2;
15                     }
16                     else if(obj.z<0)
17                     {
18                         obj.z-=w*rate2;
19                     }
20                     var rate3=3/(15-Math.abs(obj.z))
21                     obj.x =rate3;
22                 }
23 
24             }

亚洲城ca88唯一官方网站 17

亚洲城ca88唯一官方网站,  想象翼面在y方向由多层相互重叠的结构组成,每一片的尺寸不同,因此页面可以具有两重的圆弧边缘,示意图如下:

关于OpenGL ES的三种基本变换就说到这里,其实还有两个重要的投影变换 -- 透视投影和正交投影。就不在本篇继续说了,尽量控制一下篇幅,就在下一篇讲吧!

亚洲城ca88唯一官方网站 18

本例代码:LearningOpenGL ES GitHub

 

  认为翼面由多层组成,参考下图,最大的一层宽度为7.5,最小的一层宽度为6.5,其中某一层与最小层的宽度差为size1,使用和船头圆弧类似的方法算出size1的值,进而算出这一层的尺寸。

   然后参考上图,在一个短轴为w长轴为h的拉伸扇形中计算每个顶点向左或右侧的偏移量。

  随后编写一个方法让机翼向后倾斜,距机身越远的顶点向后移动的距离越大。

  尾翼的生成方式和水平翼相似。

  执行效果如下:

亚洲城ca88唯一官方网站 19

  附实际开发时使用的草图:

亚洲城ca88唯一官方网站 20 

  7、在控制台执行ChangeMaterial(mesh_origin,mat_blue)可以将材质转化为纯蓝色,因为条带网格的法线方向默认指向飞船内部,这时飞船外部将不能显示光照的镜面反射效果,解决办法是在初始化材质时设置:

 

1 mat_blue.twoSidedLighting=true;//双面光照选项

  执行ChangeMaterial(mesh_origin,mat_alpha)可以将材质转化为半透明,同样需要对mat_alpha设置上述属性,否则将只有飞船的内表面可见,半透明效果如下图:

亚洲城ca88唯一官方网站 21

 

  

    

编辑:亚洲城ca88唯一官方网站 本文来源:iOS开垦学习OpenGL,用编制程序情势编写Babylon格式

关键词: 亚洲城ca88