使用上一篇文章(https://www.cnblogs.com/ljzc002/p/9353101.html)中提出的方法,編寫一個簡單的宇宙飛船3D模型,在這篇文章中對模型製作流程和數學計算步驟進行介紹,併為模型添加簡單的材質。 我們首先對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個頂點):
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 }
計算y、z坐標的示意圖如下:
y和z的計算需要用到初中數學的三角函數知識。
接下來使用“var arr1=TranceRing1(MakeRing(7,128));”將圓形路徑變成我們設計的船體截面路徑,TranceRing1方法代碼如下:
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 }
這裡的演算法很簡單,遍歷路徑中的每個頂點,然後根據上面的設計進行邏輯判斷即可。
3、將上面生成的一條路徑克隆為多條路徑,規定每兩條路徑之間的距離為0.25:
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軸平移的方法如下:
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 }
4、使用上一篇文章中提到的方法生成條帶網格:
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個頂點,用來給敞開的船尾封口(使用多餘的頂點算是條帶網格模型的一個缺點,但這個缺點和條帶網格的易用性比起來可以接受)至於船首的封口則由後面的網格變換負責。
MakePointPath代碼如下:
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 }
生成的輪廓網格如下圖:
5、通過頂點變換生成錐形的船頭:
按照設計,從頂部俯視船體的前半部分是一個z向半徑為7、x向半徑為15的“圓弧形”,從側面看船頭的上部是y向半徑為3.25、x向半徑為5的圓弧形,船頭的下部是y向半徑為1、x向半徑為2的圓弧形。
側面示意圖如下:
船首的變形代碼如下:
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 }
用不同的比例對路徑進行壓縮,將原來尺寸相同的路徑變成尺寸漸變的路徑,路徑連成的條帶網格就會呈現椎體的形狀,那麼問題就在於如何計算這個縮放的比例,使得椎體的錶面呈現為圓滑的弧形。
我將圓弧定義為拉伸的正圓形的一部分,然後由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、生成飛船的後掠翼,生成原理與船首類似:
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 }
想象翼面在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設置上述屬性,否則將只有飛船的內錶面可見,半透明效果如下圖: