使用上一篇文章 (https://www.cnblogs.com/ljzc002/p/9353101.html) 中提出的方法, 编写一个简单的宇宙飞船 3D 模型, 在这篇文章中对模型制作流程和数学计算步骤进行介绍, 并为模型添加简单的材质.
我们首先对 3D 模型的轮廓进行估计, 然后制作一个拥有足够多顶点的, 与模型轮廓近似的网格对象(这里选用条带类网格对象), 接着对网格的部分顶点进行位置变换以产生模型的细节, 最后为模型设置一个材质.
当然 Babylon.js 还支持更复杂的纹理类型, 我翻译了 Babylon.js 官方教程中关于反射与折射, 反射探查, 地图纹理, 多重材质, 动态纹理, 高亮描边的文档(部分文档翻译的不明确, 因为官方文档本身的表述也不是很明确), 可以在 http://down.51cto.com/data/2450646 下载.
1, 从顶部看, 估计飞船的首尾长度为 30 单位, 船体最宽处半径为 7 单位, 船头处呈圆滑的锥形; 从船头方向看, 船体顶部为较扁的圆弧, 船底部边缘圆滑中间平直(有点像上个世纪的航天飞机). 草图如下:
对于船体上部, 高度低于 2 的部分直接使用半径为 7 的圆弧作为仓壁, 高于 2 的部分则将高度削减二分之一; 对于船体下部, 将大致形状设为压扁到四分之一的半圆, 再将高度低于 - 1 的部分设为平直的船底.
规定船体沿 x 轴方向摆放, 船体中心位于世界坐标系原点, 船头朝向 x 轴负方向, 船顶朝向 y 轴正方向.
事实上, 在编写 3D 模型时固定的长度数值并没有决定性的意义(当然过大或过小可能导致物体脱出视场), 决定模型形状的关键是各处尺寸之间的比例关系, 具体的尺寸大小都可以在载入模型后根据需要进行缩放, 这里将船体长度设为 30 单位是为了在预设的编辑场景里方便查看.
然后开始构建一个符合上述轮廓的条带网格.
2, 开始编写条带网格的路径(顶点数组), 首先生成一个半径是 7 的圆形路径, 规定圆弧由 128 个顶点组成(事实上最终生成的路径有 129 个顶点):
- function MakeRing(radius,sumpoint)// 两个参数分别是圆形的半径和圆形由多少个顶点组成
- {
- var arr_point=[];// 顶点数组
- var radp=Math.PI*2/sumpoint;// 每一个顶点在圆弧上转过的角度
- for(var i=0.0;i<sumpoint;i++)
- {
- var x=0;
- var rad=radp*i;
- // 算出顶点的 y,z 坐标
- var y=radius*Math.sin(rad);
- var z=radius*Math.cos(rad);
- arr_point.push(new BABYLON.Vector3(x,y,z));
- }
- arr_point.push(arr_point[0].clone());// 为了保持首尾相连, 要再添加一次第一个顶点
- return arr_point;
- }
计算 y,z 坐标的示意图如下:
y 和 z 的计算需要用到初中数学的三角函数知识.
接下来使用 "var arr1=TranceRing1(MakeRing(7,128));" 将圆形路径变成我们设计的船体截面路径, TranceRing1 方法代码如下:
- // 上下挤压, 对于每个顶点都生效的变换尽量只执行一次
- function TranceRing1(arr)
- {
- var len=arr.length;
- for(var j=0;j<len;j++)
- {
- var obj=arr[j];
- if(obj.y<0)
- {
- obj.y=obj.y/4;
- if(obj.y<-1)
- {
- obj.y=-1;
- }
- }
- else if(obj.y>2)
- {
- obj.y=(obj.y-2)/2+2;
- }
- }
- return arr;
- }
这里的算法很简单, 遍历路径中的每个顶点, 然后根据上面的设计进行逻辑判断即可.
3, 将上面生成的一条路径克隆为多条路径, 规定每两条路径之间的距离为 0.25:
- arr_path=[];// 路径数组
- var xstartl=-15;// 设置船头 (也就是第一个圆环路径) 在 x 轴上的位置
- var arr1=TranceRing1(MakeRing(7,128));
- for(var i=0;i<121;i++)
- {
- var arr_point=CloneArrPoint(arr1);// 克隆一条路径
- arr_path.push(MoveX(arr_point,i*0.25+xstartl));// 将克隆出的路径沿 x 轴方向平移
- }
路径克隆的示意图如下:
克隆路径和 x 轴平移的方法如下:
- // 克隆复制对象数组
- function CloneArrPoint(arr)
- {
- var arr2=[];
- var len=arr.length;
- for(var i=0;i<len;i++)
- {
- arr2.push(arr[i].clone());
- }
- return arr2;
- }
- // 平移 x 轴
- function MoveX(path,dis)
- {
- var len=path.length;
- for(var i=0;i<len;i++)
- {
- path[i].x+=dis;
- }
- return path;
- }
4, 使用上一篇文章中提到的方法生成条带网格:
- var arr7=MakePointPath(new BABYLON.Vector3(15,0,0),129);// 用一个点封口
- arr_path.push(arr7);
- mesh_origin=BABYLON.MeshBuilder.CreateRibbon("mesh_origin",{pathArray:arr_path
- ,updatable:true,closePath:false,closeArray:false});
- mesh_origin.material=mat_frame;
这里的 arr7 是位于同一个位置的 129 个顶点, 用来给敞开的船尾封口 (使用多余的顶点算是条带网格模型的一个缺点, 但这个缺点和条带网格的易用性比起来可以接受) 至于船首的封口则由后面的网格变换负责.
MakePointPath 代码如下:
- // 用一个重合点路径封口
- function MakePointPath(vec,size)
- {
- var arr_point=[];
- for(var i=0;i<size;i++)
- {
- arr_point.push(vec.clone());
- }
- return arr_point;
- }
生成的轮廓网格如下图:
5, 通过顶点变换生成锥形的船头:
按照设计, 从顶部俯视船体的前半部分是一个 z 向半径为 7,x 向半径为 15 的 "圆弧形", 从侧面看船头的上部是 y 向半径为 3.25,x 向半径为 5 的圆弧形, 船头的下部是 y 向半径为 1,x 向半径为 2 的圆弧形.
侧面示意图如下:
船首的变形代码如下:
- // 有的顶点变换会受到周围顶点的影响, 所以要在已经构造好的基础上进行变换
- function TransCraft()
- {
- var len=arr_path.length;
- // 遍历每个点, 用程序判断这个点是否符合某些标准, 并进行相应变化
- for(var i=0;i<len;i++)
- {
- var arr_point=arr_path[i];
- var len2=arr_point.length;
- for(var j=0;j<len2;j++)
- {
- var obj=arr_point[j];
- //var x=obj.x;
- //var y=obj.y;
- //var z=obj.z;
- // 船首呈椎体状
- if(obj.x<-13&&obj.y<0)// 从侧面看的船首下部
- {
- var rate=Math.sin(Math.acos((-13-obj.x)/2/1));//y 轴方向缩放系数
- obj.y=obj.y*rate;
- }
- if(obj.x<-10&&obj.y>0)// 从侧面看的船首上部
- {
- var rate=Math.sin(Math.acos((-10-obj.x)/(5/3.25)/3.25));//y 轴方向缩放系数
- obj.y=obj.y*rate;
- }
- if(obj.x<0)// 从顶部看的船首
- {
- var rate=Math.sin(Math.acos((-obj.x)/(15/7)/7));//y 轴方向缩放系数
- obj.z=obj.z*rate;
- }
用不同的比例对路径进行压缩, 将原来尺寸相同的路径变成尺寸渐变的路径, 路径连成的条带网格就会呈现椎体的形状, 那么问题就在于如何计算这个缩放的比例, 使得椎体的表面呈现为圆滑的弧形.
我将圆弧定义为拉伸的正圆形的一部分, 然后由 x 坐标值计算出对应路径的缩放比例, 原理图如下(以 "从侧面看的船首上部" 为例):
首先将从侧面看船头上部的中间截面通过将 x 坐标除以 (5/3.25) 的方式变换为正圆的一部分, 用 (-10-obj.x)/(5/3.25) 计算出 "xsize" 的长度, 因为 y 轴缩放比例等于在这个截面上顶点高度 (y 值) 和半径 (r) 的比等于 sin(a), 所以只需求出角 a 的大小即可算出比例, 而角 a 的大小可以由 (xsize/r) 的反余弦得出. 如此得出 y 方向的缩放比例.
从顶部看的缩放比例也是如此计算, 这时计算得到的是 z 轴方向的缩放比例.
缩放后的显示效果如下:
可以看到船头的 129 个顶点被缩放到了同一位置, 船头呈现圆滑的弧线.
6, 生成飞船的后掠翼, 生成原理与船首类似:
- // 后掠翼, 具有圆弧状的边缘
- if(obj.x>0&&obj.y>0&&obj.y<1)
- {
- // 这一层翼面和最小翼面的边缘差值
- var rate=Math.cos(Math.asin(Math.abs(0.5-obj.y)/(0.5/1)/1));
- var size1=1*rate;
- var h=14+size1;
- var w=6.5+size1;
- if((15-obj.x)<h)
- {
- var rate2=Math.cos(Math.asin(Math.abs(15-obj.x)/(h/w)/w));
- if(obj.z>0)
- {
- obj.z+=w*rate2;
- }
- else if(obj.z<0)
- {
- obj.z-=w*rate2;
- }
- var rate3=3/(15-Math.abs(obj.z))
- obj.x+=rate3;
- }
- }
想象翼面在 y 方向由多层相互重叠的结构组成, 每一片的尺寸不同, 因此页面可以具有两重的圆弧边缘, 示意图如下:
认为翼面由多层组成, 参考下图, 最大的一层宽度为 7.5, 最小的一层宽度为 6.5, 其中某一层与最小层的宽度差为 size1, 使用和船头圆弧类似的方法算出 size1 的值, 进而算出这一层的尺寸.
然后参考上图, 在一个短轴为 w 长轴为 h 的拉伸扇形中计算每个顶点向左或右侧的偏移量.
随后编写一个方法让机翼向后倾斜, 距机身越远的顶点向后移动的距离越大.
尾翼的生成方式和水平翼相似.
执行效果如下:
附实际开发时使用的草图:
7, 在控制台执行 ChangeMaterial(mesh_origin,mat_blue)可以将材质转化为纯蓝色, 因为条带网格的法线方向默认指向飞船内部, 这时飞船外部将不能显示光照的镜面反射效果, 解决办法是在初始化材质时设置:
1 mat_blue.twoSidedLighting=true;// 双面光照选项
执行 ChangeMaterial(mesh_origin,mat_alpha)可以将材质转化为半透明, 同样需要对 mat_alpha 设置上述属性, 否则将只有飞船的内表面可见, 半透明效果如下图:
来源: https://www.cnblogs.com/ljzc002/p/9473438.html