總述:大部分3D編程都涉及到地面元素,在場景中我們使用地面作為其他物體的承載基礎,同時也用地面限制場景使用者的移動範圍,還可以通過設置地塊的屬性為場景的不同位置設置對應的計算規則。本文在WebGL平臺上藉助Babylon.js庫探索並實現了兩種地面構造方法,除了兩種確定的構造方法外,本文還包含了對一 ...
總述:
大部分3D編程都涉及到地面元素,在場景中我們使用地面作為其他物體的承載基礎,同時也用地面限制場景使用者的移動範圍,還可以通過設置地塊的屬性為場景的不同位置設置對應的計算規則。本文在WebGL平臺上藉助Babylon.js庫探索並實現了兩種地面構造方法,除了兩種確定的構造方法外,本文還包含了對一些其他選擇的探討和一些對電子游戲藝術的看法。建議在閱讀本文前,先學習3D編程入門知識和Babylon.js的官方入門教程,前者可以在 https://space.bilibili.com/25346426/channel/detail?cid=14552找到一些介紹基礎概念的視頻教程,後者可以在https://github.com/ljzc002/ljzc002.github.io/tree/master/BABYLON101找到英中對照版本,本篇文章所用到的代碼可以在https://github.com/ljzc002/ljzc002.github.io/tree/master/EmptyTalk下載。
一、方法一——使用標準地塊拼裝構造地面
1、我們先製作5種標準地塊:
標準地塊都是邊長為1的正方體,每一種標準地塊使用對應的紋理表示特定的地貌,我們可以用這些標準地塊的複製體拼接成複雜的地面構造。在本文中我使用正方體作為標準地塊,垂直排布的生成地形,你也可以使用六棱柱等其他形狀作為標準地塊,或者將地塊繞y軸旋轉一些角度後進行排布。
製作標準地塊的代碼如下:
1 var size_per=1;//每個單元格的尺寸 2 var obj_landtype={}; 3 //建立網格 4 var box_grass=new BABYLON.MeshBuilder.CreateBox("box_grass",{size:size_per},scene); 5 var box_tree=new BABYLON.MeshBuilder.CreateBox("box_tree",{size:size_per},scene); 6 var box_stone=new BABYLON.MeshBuilder.CreateBox("box_stone",{size:size_per},scene); 7 var box_shallowwater=new BABYLON.MeshBuilder.CreateBox("box_shallowwater",{size:size_per},scene); 8 var box_deepwater=new BABYLON.MeshBuilder.CreateBox("box_deepwater",{size:size_per},scene); 9 box_grass.renderingGroupId = 2; 10 box_tree.renderingGroupId = 2; 11 box_stone.renderingGroupId = 2; 12 box_shallowwater.renderingGroupId = 2; 13 box_deepwater.renderingGroupId = 2; 14 box_grass.position.y=-100*size_per; 15 box_tree.position.y=-101*size_per; 16 box_stone.position.y=-102*size_per; 17 box_shallowwater.position.y=-103*size_per; 18 box_deepwater.position.y=-104*size_per; 19 obj_landtype.box_grass=box_grass; 20 obj_landtype.box_tree=box_tree; 21 obj_landtype.box_stone=box_stone; 22 obj_landtype.box_shallowwater=box_shallowwater; 23 obj_landtype.box_deepwater=box_deepwater; 24 OptimizeMesh(box_grass); 25 OptimizeMesh(box_tree); 26 OptimizeMesh(box_stone); 27 OptimizeMesh(box_shallowwater); 28 OptimizeMesh(box_deepwater); 29 //建立材質 30 var mat_grass = new BABYLON.StandardMaterial("mat_grass", scene);//1 31 mat_grass.diffuseTexture = new BABYLON.Texture("../../ASSETS/IMAGE/LANDTYPE/grass.jpg", scene); 32 mat_grass.freeze(); 33 box_grass.material=mat_grass; 34 var mat_tree = new BABYLON.StandardMaterial("mat_tree", scene);//1 35 mat_tree.diffuseTexture = new BABYLON.Texture("../../ASSETS/IMAGE/LANDTYPE/yulin.png", scene); 36 mat_tree.freeze(); 37 box_tree.material=mat_tree; 38 var mat_stone = new BABYLON.StandardMaterial("mat_stone", scene);//1 39 mat_stone.diffuseTexture = new BABYLON.Texture("../../ASSETS/IMAGE/LANDTYPE/stone.png", scene); 40 mat_stone.freeze(); 41 box_stone.material=mat_stone; 42 var mat_shallowwater = new BABYLON.StandardMaterial("mat_shallowwater", scene);//1 43 mat_shallowwater.diffuseTexture = new BABYLON.Texture("../../ASSETS/IMAGE/LANDTYPE/lake.png", scene); 44 mat_shallowwater.freeze(); 45 box_shallowwater.material=mat_shallowwater; 46 var mat_deepwater = new BABYLON.StandardMaterial("mat_deepwater", scene);//1 47 mat_deepwater.diffuseTexture = new BABYLON.Texture("../../ASSETS/IMAGE/LANDTYPE/sea.png", scene); 48 mat_deepwater.freeze(); 49 box_deepwater.material=mat_deepwater;
這段代碼製作了“草地”、“森林”、“岩石”、“淺水”、“深水”五種標準地塊,對於地塊的網格,使用OptimizeMesh方法進行了一些顯示優化,OptimizeMesh方法內容如下:
1 function OptimizeMesh(mesh) 2 { 3 mesh.convertToFlatShadedMesh();//使用頂點顏色計算代替片元顏色計算 4 mesh.freezeWorldMatrix();//凍結世界坐標系 5 // mesh.material.needDepthPrePass = true;//啟用深度預通過 6 //mesh.convertToUnIndexedMesh();//使用三角形繪製代替索引繪製 7 }
對於地塊材質,使用freeze方法凍結了材質對象的屬性,避免渲染引擎頻繁刷新材質狀態。
2、接下來我們用草地地塊拼接一個最簡單的平原地形
地形渲染效果如下:
這片草原是由10201個草地地塊拼接而成的,這裡我使用了OpenGL的“多實例渲染”技術,來降低繪製大量重覆對象對計算性能的消耗,Babylon.js庫在createInstance方法中封裝了這一技術:
1 var arr_instance=[]; 2 var segs_x=100;//橫向分段次數 3 var segs_y=100;//縱向分段次數 4 5 //以高度0為海平面,以xy00為大地原點 6 //形成初始地塊:101*101個格子,中心格的中心是原點 7 for(var i=0;i<=segs_x;i++) 8 { 9 arr_instance[i]=[]; 10 for(var j=0;j<=segs_y;j++) 11 { 12 arr_instance[i][j]=[]; 13 var instance=obj_landtype.box_grass.createInstance("ground_"+i+"_"+j+"_0"); 14 instance.mydata={i:i,j:j,k:0,landclass:obj_landtype.box_grass}; 15 instance.position=new BABYLON.Vector3((i-(segs_x/2))*size_per,0,(j-(segs_y/2))*size_per);//xz方向上都是從負向正堆疊 16 arr_instance[i][j].push(instance);//把每個實例用全局對象保存起來 17 } 18 }
這裡我們為每個實例對象設置了一個mydata屬性,將地塊的一些信息保存到這個屬性里,以備之後的場景交互使用。
3、為單元格標記xz方向上的索引
現在每個地塊都是沿著x軸和z軸整齊排列的,為方便區分,我們將xz平面上的每個方塊位置叫做“單元格”,每個單元格中可能有多個地塊實例。每個單元格的位置以其x、z軸上的索引表示,我們現在需要一種方式將這一索引值顯示出來。
這裡我們先嘗試為每個單元格顯示一個索引文本,渲染效果如下:(可以訪問https://ljzc002.github.io/EmptyTalk/HTML/TEST/testfloor.html查看)
可以看出標記效果並不是很理想,同時數以萬計的索引文本也降低了場景的渲染速度,這種方法可能並不適用於當前的單元格標記需求,但包含的技術可能用在其他地方:
a、首先建立一個精靈管理器以及一張包含數字和減號的圖片
1 //準備十種數字以及減號的紋理 2 var can_temp=document.createElement("canvas"); 3 can_temp.width=132//264; 4 can_temp.height=24; 5 var context=can_temp.getContext("2d"); 6 context.fillStyle="rgba(0,0,0,0)";//完全透明的背景 7 context.fillRect(0,0,can_temp.width,can_temp.height); 8 context.fillStyle = "#ffffff"; 9 context.font = "bold 24px monospace"; 10 for(var i=0;i<10;i++) 11 { 12 context.fillText(i,i*12,24); 13 } 14 context.fillText("-",120,24); 15 //context.fillText("0123456789-",0,24);//預設為半形,為了在作為精靈使用時整齊的分塊必須一個一個單獨繪製 16 var png=can_temp.toDataURL("image/png");//生成PNG圖片 17 //建立精靈管理器 18 var spriteManager = new BABYLON.SpriteManager("spriteManager", png, (segs_x+1)*(segs_y+1)*7, 24, scene); 19 spriteManager.renderingGroupId=2; 20 spriteManager.cellWidth=12; 21 spriteManager.cellHeight=24;
b、在生成地塊實例的迴圈裡加入精靈生成代碼:
1 //添加精靈,估計地圖最大為1000*1000 2 var number1 = new BABYLON.Sprite("number1", spriteManager); 3 var number2 = new BABYLON.Sprite("number2", spriteManager); 4 var number3 = new BABYLON.Sprite("number3", spriteManager); 5 var number4 = new BABYLON.Sprite("number4", spriteManager); 6 var number5 = new BABYLON.Sprite("number5", spriteManager); 7 var number6 = new BABYLON.Sprite("number6", spriteManager); 8 var number7 = new BABYLON.Sprite("number7", spriteManager); 9 //為缺少的數位填充0,生成三位數字 10 stri=(i+1000+"").substr(1); 11 strj=(j+1000+"").substr(1); 12 13 number1.cellIndex=parseInt(stri[0]); 14 number2.cellIndex=parseInt(stri[1]); 15 number3.cellIndex=parseInt(stri[2]); 16 number4.cellIndex=10;//減號 17 number5.cellIndex=parseInt(strj[0]); 18 number6.cellIndex=parseInt(strj[1]); 19 number7.cellIndex=parseInt(strj[2]); 20 //定位精靈,7個精靈垂直排列作為一條文本 21 number1.size=0.2*size_per; 22 number1.position=instance.position.clone(); 23 number1.position.y=2*size_per; 24 number1.position.x+=0.3*size_per; 25 number1.position.z+=0.3*size_per; 26 27 number2.size=0.2*size_per; 28 number2.position=instance.position.clone(); 29 number2.position.y=1.8*size_per; 30 number2.position.x+=0.3*size_per; 31 number2.position.z+=0.3*size_per; 32 number3.size=0.2*size_per; 33 number3.position=instance.position.clone(); 34 number3.position.y=1.6*size_per; 35 number3.position.x+=0.3*size_per; 36 number3.position.z+=0.3*size_per; 37 number4.size=0.2*size_per; 38 number4.position=instance.position.clone(); 39 number4.position.y=1.4*size_per; 40 number4.position.x+=0.3*size_per; 41 number4.position.z+=0.3*size_per; 42 number5.size=0.2*size_per; 43 number5.position=instance.position.clone(); 44 number5.position.y=1.2*size_per; 45 number5.position.x+=0.3*size_per; 46 number5.position.z+=0.3*size_per; 47 number6.size=0.2*size_per; 48 number6.position=instance.position.clone(); 49 number6.position.y=1.0*size_per; 50 number6.position.x+=0.3*size_per; 51 number6.position.z+=0.3*size_per; 52 number7.size=0.2*size_per; 53 number7.position=instance.position.clone(); 54 number7.position.y=0.8*size_per; 55 number7.position.x+=0.3*size_per; 56 number7.position.z+=0.3*size_per;
考慮到計算性能,這裡使用精靈作為文本的載體(但建立了7萬多個精靈之後,幀率還是降低了很多),因為水平排列的精靈在相機水平移動時會相互遮擋,所以垂直排列精靈來降低影響,也許可以通過重設精靈的旋轉軸位置來徹底解決這一問題。
除了為每個地塊標註索引之外,我們還可以使用帶有索引的小地圖、在單獨的視口中顯示選定地塊的特寫、在選定地塊旁邊生成標記等等方式來標明地塊的索引,後文將使用在場景中放置參考物的方式來標示地塊索引。
4、生成地形起伏
我們抬升了xz平面中左下角的兩格單元格,並將這兩個單元設為“岩石”地貌:
a、首先建立兩個工具方法:
1 //disposeCube(0,0) 2 function disposeCube(i,j)//移除一個xz位置上的所有可能存在的方塊 3 { 4 var len=arr_instance[i][j].length; 5 for(var k=0;k<len;k++) 6 { 7 var instance=arr_instance[i][j][k]; 8 instance.dispose(); 9 instance=null; 10 } 11 12 } 13 //在指定單元格、指定高度建立指定類型的地塊 14 //createCube(0,0,2,obj_landtype.box_stone) 15 //i,j必定是整數,k可能是小數,都表示單位長度的數量 16 function createCube(i,j,k,landclass) 17 { 18 var instance=landclass.createInstance("ground_"+i+"_"+j+"_"+k); 19 instance.mydata={i:i,j:j,k:k,landclass:landclass}; 20 instance.position=new BABYLON.Vector3((i-(segs_x/2))*size_per,k*size_per,(j-(segs_y/2))*size_per);//都是從負向正堆疊?-》規定每個單元格的地塊數組都是從低到高排列 21 //arr_instance[i][j].push(instance); 22 arr_instance[i][j].unshift(instance); 23 }
b、接下來修改一些被選中的單元格
我們把“被選中的單元格”放在一個數組裡,將這個數組命名為“配置數組”。
1 //用對應的方塊填充一條路徑上所有的xz單元格,先清空單元格內原有方塊,然後在指定高度建立一個方塊 2 // ,接著比對所有周圍方塊的高度(比對四個方向),填補漏出的部分,在填補時註意越低的方塊在數組中越靠前。 3 //createCubePath([{i:0,j:0,k:1,landclass:obj_landtype.box_stone},{i:1,j:1,k:2.5,landclass:obj_landtype.box_stone}]) 4 5 function createCubePath(cubepath) 6 { 7 var len=cubepath.length; 8 for(var i=0;i<len;i++)//對於每一個xz單元格 9 { 10 var cube=cubepath[i]; 11 disposeCube(cube.i,cube.j); 12 createCube(cube.i,cube.j,cube.k,cube.landclass); 13 } 14 //初次繪製後進行二次對比,初次繪製的必定是xz單元格中的最高點 15 for(var index=0;index<len;index++) 16 { 17 var cube=cubepath[index]; 18 var i=cube.i; 19 var j=cube.j; 20 var k=cube.k; 21 //上右下左 22 //取四方的最高 23 var k1=999; 24 if(arr_instance[i]) 25 { 26 var arr1=arr_instance[i][j+1]; 27 if(arr1) 28 { 29 var ins_cube1=arr1[arr1.length-1]; 30 k1=ins_cube1.mydata.k; 31 } 32 } 33 var k2=999; 34 if(arr_instance[i+1]) 35 { 36 var arr2=arr_instance[i+1][j]; 37 if(arr2) { 38 var ins_cube2 = arr2[arr2.length - 1]; 39 k2=ins_cube2.mydata.k; 40 } 41 } 42 var k3=999; 43 if(arr_instance[i]) 44 { 45 var arr3=arr_instance[i][j-1]; 46 if(arr3) { 47 var ins_cube3=arr3[arr3.length-1]; 48 k3=ins_cube3.mydata.k; 49 } 50 } 51 var k4=999; 52 if(arr_instance[i-1]) 53 { 54 var arr4=arr_instance[i-1][j+1]; 55 if(arr4) { 56 var ins_cube4=arr4[arr4.length-1]; 57 k4=ins_cube4.mydata.k; 58 } 59 } 60 61 //在四方最高中找最低 62 var mink=Math.min(k1,k2,k3,k4); 63 64 var len2=Math.floor((k-mink)/size_per); 65 for(var index2=1;index2<=len2;index2++) 66 { 67 createCube(i,j,k-index2,cube.landclass); 68 //arr_instance[i][j].unshift() 69 } 70 } 71 }
這段代碼包含兩個迴圈,第一個迴圈負責放置選中單元格中最高的那個地塊,第二個迴圈則負責填充最高地塊下麵的支撐,比如高山的山體或者深谷的谷壁。這種填充是靠比較選中的單元格和四面單元格的高度實現的,這個演算法的一個缺點是在需要生成低谷時,谷地周圍的一圈高度不變的平地也需要放入配置數組,否則地形會出現斷裂。
直接在瀏覽器控制臺中執行createCubePath([{i:0,j:0,k:1,landclass:obj_landtype.box_stone},{i:1,j:1,k:2.5,landclass:obj_landtype.box_stone}])命令即可改變地形。這裡你可能想要看到那種“在場景中拖動滑鼠地形隨之起伏”的效果,但我認為這種運行時代碼註入的控制方式反而是WebGL技術相對於傳統桌面3D程式的一大優勢,藉此我們可能實現遠超傳統ui的精細化控制。
可以訪問https://ljzc002.github.io/EmptyTalk/HTML/TEST/testfloor2.html進行測試
5、小結
綜上我們編寫了一個簡單的地形生成方法,但還有更多的工作沒有做,我們需要一些根據某種規則生成配置數組的方法、一些根據規則在同一單元格的不同高度分配不同地塊的方法(關於地形規則的制定也許可以參考這篇隨機生成行星錶面地形的文章https://www.cnblogs.com/ljzc002/p/9134272.html),對於不習慣控制台輸入的使用者還要考慮編寫實用的交互ui。這種空間上離散的地形比較適合編寫時間上離散的回合制3D場景,接下來我們將要討論更適合即時3D場景的連續地形。
二、第二種方法——改進的地面網格
1、Babylon.js內置地面網格的不足
Babylon.js內置了一種平面地面網格和一種高度圖地面網格,但這兩鐘網格存在一些不足:
甲:只能設置相同的x向分段數和z向分段數
乙:地面網格裡沒有xz索引信息,只能通過修改底層頂點位置改變地形
丙:無法表現垂直斷崖和反斜面之類變化劇烈的地形
詳細解釋一下問題丙,地面網格的頂點排布如下圖所示:
使用者將發現他無法在HAOG單元格和ABCO單元格之間生成垂直斷崖,因為地面網格使用的是“簡化的網格”,在AO兩處都各只有一個頂點,無法表現懸崖的上下兩邊,這時使用者只好使用儘量小的單元格生成儘量陡的斜坡來模擬懸崖(這一點正好和無法生成斜坡的地塊拼接法完全相反)。另一方面,每個頂點(比如頂點O)周圍的六條楞線夾角並不均勻,難以生成端正的形狀。
計劃用“條帶網格”替換地面網格來解決問題甲和問題乙,但條帶網格的頂點排布規律與地面網格類似,問題丙仍然存在。
經過觀察,問題丙只在地形變化劇烈時表現明顯,所以決定用條帶網格表現較為平緩的地形大勢(代碼里命名為ground_base),把一些專門定製的“地形附著物網格”(也就是模型)放置在ground_base之上表現劇烈變化的地形,如果需要,再使用某種方式將ground_base與地形附著物融合在一起。
2、生成類似方法一的平坦草原地形,並標註xz索引
渲染效果如下:(可以訪問https://ljzc002.github.io/EmptyTalk/HTML/TEST/testframe2.html查看效果)
括弧中是當前指示物坐標,括弧後是xz索引值
a、設置紋理重覆
與地塊拼接法不同,條帶網預設用一張紋理圖包覆全體頂點,為了能像方法一一樣一格一格的顯示地塊,對地塊材質代碼做如下修改:
1 mat_grass = new BABYLON.StandardMaterial("mat_grass", scene);//1 2 mat_grass.diffuseTexture = new BABYLON.Texture("../../ASSETS/IMAGE/LANDTYPE/grass.jpg", scene); 3 mat_grass.diffuseTexture.uScale = segs_x+1;//紋理重覆效果 4 mat_grass.diffuseTexture.vScale = segs_z+1; 5 mat_grass.freeze();
b、生成條帶網格地面
1 var arr_path=[];//路徑數組 2 for(var i=0;i<=segs_x+1;i++) 3 { 4 var posx=(i-((segs_x+1)/2))*size_per; 5 var path=[]; 6 for(var j=0;j<=segs_z+1;j++) 7 { 8 var posz=(j-((segs_z+1)/2))*size_per; 9 path.push(new BABYLON.Vector3(posx,0,posz)); 10 } 11 arr_path.push(path); 12 } 13 ground_base=BABYLON.MeshBuilder.CreateRibbon("ground_base" 14 ,{pathArray:arr_path,updatable:true,closePath:false,closeArray:false,sideOrientation:BABYLON.Mesh.DOUBLESIDE}); 15 ground_base.sideOrientation=BABYLON.Mesh.DOUBLESIDE; 16 ground_base.material=mat_grass; 17 ground_base.renderingGroupId=2; 18 ground_base.metadata={}; 19 ground_base.metadata.arr_path=arr_path; 20 obj_ground.ground_base=ground_base;
註意需要把CreateRibbon方法參數中的updatable屬性設為true,否則建立條帶網格之後將不能修改地形。
c、製作一些藍色小球作為地形參照物:
1 //5個藍色小球 2 var mesh_sphereup=new BABYLON.MeshBuilder.CreateSphere("mesh_sphereup",{diameter:0.5},scene); 3 mesh_sphereup.material=mat_blue; 4 mesh_sphereup.renderingGroupId=2; 5 mesh_sphereup.direction=new BABYLON.Vector3(0,-1,2); 6 mesh_sphereup.isPickable=false; 7 mesh_sphereup.rayHelper = null; 8 obj_plane.mesh_sphereup=mesh_sphereup; 9 var mesh_sphereright=new BABYLON.MeshBuilder.CreateSphere("mesh_sphereright",{diameter:0.5},scene); 10 mesh_sphereright.material=mat_blue; 11 mesh_sphereright.renderingGroupId=2; 12 mesh_sphereright.direction=new BABYLON.Vector3(2,-1,0); 13 mesh_sphereright.isPickable=false; 14 mesh_sphereright.rayHelper = null; 15 obj_plane.mesh_sphereright=mesh_sphereright; 16 var mesh_spheredown=new BABYLON.MeshBuilder.CreateSphere("mesh_spheredown",{diameter:0.5},scene); 17 mesh_spheredown.material=mat_blue; 18 mesh_spheredown.renderingGroupId=2; 19 mesh_spheredown.direction=new BABYLON.Vector3(0,-1,-2); 20 mesh_spheredown.isPickable=false; 21 mesh_spheredown.rayHelper = null; 22 obj_plane.mesh_spheredown=mesh_spheredown; 23 var mesh_sphereleft=new BABYLON.MeshBuilder.CreateSphere("mesh_sphereleft",{diameter:0.5},scene); 24 mesh_sphereleft.material=mat_blue; 25 mesh_sphereleft.renderingGroupId=2; 26 mesh_sphereleft.direction=new BABYLON.Vector3(-2,-1,0); 27 mesh_sphereleft.isPickable=false; 28 mesh_sphereleft.rayHelper = null; 29 obj_plane.mesh_sphereleft=mesh_sphereleft; 30 var mesh_spheremiddle=new BABYLON.MeshBuilder.CreateSphere("mesh_spheremiddle",{diameter:0.5},scene); 31 mesh_spheremiddle.material=mat_blue; 32 mesh_spheremiddle.renderingGroupId=2; 33 mesh_spheremiddle.direction=new BABYLON.Vector3(0,-1,0); 34 mesh_spheremiddle.isPickable=false; 35 mesh_spheremiddle.rayHelper = null; 36 obj_plane.mesh_spheremiddle=mesh_spheremiddle; 37 //為每個小球綁定一個gui標簽 38 for(var key in obj_plane) 39 { 40 var label = new BABYLON.GUI.Rectangle(key); 41 label.background = "black"; 42 label.height = "30px"; 43 label.alpha = 0.5; 44 label.width = "240px"; 45 label.cornerRadius = 20; 46 label.thickness = 1; 47 label.linkOffsetY = 30;//位置偏移量?? 48 fsUI.addControl(label); 49 label.linkWithMesh(obj_plane[key]); 50 var text1 = new BABYLON.GUI.TextBlock(); 51 text1.text = ""; 52 text1.color = "white"; 53 label.addControl(text1); 54 label.isVisible=true; 55 //label.layerMask=2; 56 label.text=text1; 57 obj_plane[key].lab=label; 58 }
可以訪問https://www.cnblogs.com/ljzc002/p/7699162.html 查看Babylon.js gui功能的中文文檔
d、根據相機的位置修改路標的位置:
1 scene.registerAfterRender( 2 function() { 3 //更新5個標記球的位置 4 var origin=camera0.position; 5 var length=200; 6 for(key in obj_plane) 7 { 8 var mesh=obj_plane[key]; 9 var direction=mesh.direction; 10 var ray = new BABYLON.Ray(origin, direction, length); 11 /*if(mesh.rayHelper) 12 { 13 mesh.rayHelper.dispose(); 14 }*/ 15 //mesh.rayHelper = new BABYLON.RayHelper(ray);//這時還沒有_renderLine屬性 16 //mesh.rayHelper._renderLine.renderingGroupId=2; 17 //mesh.rayHelper.show(scene);//連續使用兩次show會崩潰? 18 //難道一幀里只能用一個pick? 19 //console.log(key); 20 var hit = scene.pickWithRay(ray,predicate); 21 if (hit.pickedMesh){ 22 //console.log(key+"2"); 23 mesh.isVisible=true; 24 var posp=hit.pickedPoint; 25 mesh.position=posp.clone(); 26 mesh.lab.isVisible=true; 27 //顯示命中點的坐標以及命中點所在方塊的左下角的兩層索引 28 var index_x=Math.floor((posp.x+(segs_x+1)*size_per/2)/size_per); 29 var index_z=Math.floor((posp.z+(segs_z+1)*size_per/2)/size_per); 30 mesh.lab.text.text="("+posp.x.toFixed(2)+","+posp.y.toFixed(2)+","+posp.z.toFixed(2)+")*" 31 +index_x+"-"+index_z; 32 } 33 else 34 {//如果沒命中地面則不顯示路標 35 mesh.lab.isVisible=false; 36 mesh.isVisible=false; 37 } 38 } 39 40 41 42 } 43 ) 44 function predicate(mesh){//過濾網格,只允許射線擊中地面系網格, 45 if (mesh.name.substr(0,6)=="ground"){ 46 return true; 47 } 48 else 49 { 50 return false; 51 } 52 53 }
這段代碼在每次渲染後執行,從相機出發向五個方位發射5條射線,將射線和地面的交點做為路標放置點,同時修改gui文本內容。曾經嘗試在這裡使用RayHelper功能顯示射線,但發現在未渲染前RayHelper無法設置渲染組,而渲染後的RayHelper又需立刻換成新對象,舊的渲染組屬性作廢,希望官方能夠優化rayHelper的用法,如果確實需要顯示射線,也許可以用Line功能代替rayHelper。
e、對一些選定的頂點施加矩陣變化:
在控制臺中執行TransVertex(obj_ground.ground_base,[[0,0],[0,1],[1,0]],BABYLON.Matrix.Translation(0,2,0))抬起了左下角的三個頂點: