用编程方式编写Babylon格式的宇宙飞船3D模型
使用上一篇文章(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)可以将材质转化为纯蓝色,因为条带网格的法线方向默认指向飞船内部,这时飞船外部将不能显示光照的镜面反射效果,解决办法是在初始化材质时设置:
mat_blue.twoSidedLighting=true;//双面光照选项
执行ChangeMaterial(mesh_origin,mat_alpha)可以将材质转化为半透明,同样需要对mat_alpha设置上述属性,否则将只有飞船的内表面可见,半透明效果如下图:
用编程方式编写Babylon格式的宇宙飞船3D模型的更多相关文章
- EF三种编程方式的区别Database first ,Model first ,code first
首先对于EF中先出现的datebase first和model first两种编程方式,其的区别根据字面意思很容易能够理解. datebase first就是代表数据库优先,那么前提就是先创建数据 ...
- mac学习Python第二天:开发工具安装、编程方式、中文编码、syntaxError语法错误、注释、语法格式
一.python集成开发工具Visual Studio Code安装配置 1.官网下载安装VSCode 官网地址 https://code.visualstudio.com/下载软件包 VSCode ...
- 以编程方式使用 Microsoft Office Visio 2003 ActiveX 控件
以编程方式使用 Microsoft Office Visio 2003 ActiveX 控件 2007/10/29 Mark BukovecEmpire Down Development 适用于:Mi ...
- uglifyjs压缩js文件(指令压缩/ 批量压缩/ 编程方式压缩)
一.指令压缩 1.安装node,npm——详细见nodejs安装与使用入门 2.安装 uglifyjs——npm install -g uglify-js 3.压缩例子:1)uglifyjs mai ...
- 解决在编程方式下无法访问Spark Master问题
我们可以选择使用spark-shell,spark-submit或者编写代码的方式运行Spark.在产品环境下,利用spark-submit将jar提交到spark,是较为常见的做法.但是在开发期间, ...
- 在 ASP.NET 网页中不经过回发而以编程方式实现客户端回调
在 ASP.NET 网页的默认模型中,用户会与页交互,单击按钮或执行导致回发的一些其他操作.此时将重新创建页及其控件,并在服务器上运行页代码,且新版本的页被呈现到浏览器.但是,在有些情况下,需要从客户 ...
- EF三种编程方式详细图文教程(C#+EF)之Code First
Code First Code First模式我们称之为“代码优先”模式,是从EF4.1开始新建加入的功能.使用Code First模式进行EF开发时开发人员只需要编写对应的数据类(其实就是领域模型的 ...
- Entity Framework 5.0系列之EF概览-三种编程方式
概述 在开发面向数据的软件时我们常常为了解决业务问题实体.关系和逻辑构建模型而费尽心机,ORM的产生为我们提供了一种优雅的解决方案.ADO.NET Entity Framework是.NET开发中一种 ...
- YTU 2596: 编程题B-日期格式
2596: 编程题B-日期格式 时间限制: 1 Sec 内存限制: 128 MB 提交: 981 解决: 74 题目描述 注:本题只需要提交编写的函数部分的代码即可. 将输入的日期格式 月/日/年 ...
随机推荐
- 第一次项目冲刺(Alpha版本)2017/11/17
一.当天站立式会议 会议内容 1.对数据库的设计的进一步讨论 2.讨论SSH一些配置细节 3.分配今天的任务 二.任务分解图 三.燃尽图 四.心得 刚接触冲刺,一开始任务没有分布很多,大家要一些熟悉的 ...
- TensorFlow函数(九)tf.add_to_collection()、tf.get_collection() 和 tf.add_n()
tf.add_to_collection(name, value) 此函数将元素添加到列表中 参数: name:列表名.如果不存在,创建一个新的列表 value:元素 tf.get_collectio ...
- virtualbox+vagrant学习-2(command cli)-11-vagrant PowerShell命令
PowerShell 格式: vagrant powershell [-- extra powershell args] 这将在主机上打开PowerShell提示符,进入正在运行的vagrant机器. ...
- Python中乘法
1.numpy乘法运算中"*"或multiply(),是数组元素逐个计算,具体代码如下: import numpy as np # 2-D array: 2 x 3 two_dim ...
- NYOJ2—括号配对问题
括号配对问题 时间限制:3000 ms | 内存限制:65535 KB 难度:3 描述 现在,有一行括号序列,请你检查这行括号是否配对. 输入 第一行输入一个数N(0<N<=1 ...
- cloudstack-kvm-libvirtd
2.4.libvirtd日志和VM的日志 在运行libvirtd的时候,我们需要获得lbivirtd的运行信息.所以我们需要找到他的日志文件.一般情况下,它是在/var/log/libvirt/lib ...
- Hadoop应用开发,常见错误
错误1:在windows执行mr Exception in thread "main" java.lang.UnsatisfiedLinkError: org.apache.had ...
- springboot集成elk实现分布式日志管理
1.安装elk https://www.cnblogs.com/xuaa/p/10769759.html 2.idea创建springboot项目 File -> New -> Proje ...
- SQL进阶语法的多表操作
AS别名 多张表联合操作,如果表多,字段名长,不方便阅读.这里我们可以使用 as 关键字来对字段名设置别名. as也可以省略,看个人喜好,在这里我还是支持把 as 写上,这样我们在面对复杂的SQL ...
- JavaScript中的数据属性和访问器属性
在学习JavaScript原型(prototype)和原型链(prototype chain)知识的时候,发现数据属性和访问器属性的重要性,通过不断的查找相关知识,浅显理解如下,若有差错,希望不吝赐教 ...