使用Chrome控制台進行3D模型編輯的嘗試


前言:3D模型編輯的核心是對頂點位置和紋理顏色的編輯,這個研究的目的在於尋找一種通過編程方式直接對模型進行編輯的方法,這種編輯方法和時下流行的通過鼠標點選、拖拽進行編輯的方法之間的關系,和前端編程中“程序員編寫靜態網頁”與“美工進行網頁切圖”之間的關系很相似。

一、工具用法:

1、訪問 https://ljzc002.github.io/SnowMiku/HTML/MakeRibbon.html打開條帶網格生成器頁面

在場景世界坐標系的(0,-10,0),(0,0,0),(0,10,0)處各有一個綠色小球作為參考點,使用上下左右和鼠標拖動可以進行場景漫游。

2、按F12鍵打開Chrome控制台,在控制台中輸入:MakeRibbon(MakeRing(5,12),-10,2,11,"mesh_ribbon")回車:

在場景中繪制了一個半徑為5,曲面細分度為12,左端位於-10,每兩個圓環間距2,共由11個圓環組成的圓柱面。

拉近查看:

3、輸入ShowNormals(mesh_origin)將用紅色線段顯示每個頂點的法線方向

輸入DeleteMeshes([lines_normal])可以刪除所有的法線,輸入DeleteMeshes([mesh_origin])則刪除圓柱面網格。

4、鼠標移入網格上的三角形,會顯示三角形的頂點信息:

其中“1:2-5”表示這是三角形的第一個頂點,這個頂點位於索引是2的圓環上(第三個圓環),這個頂點在圓環中的索引是5(也就是第六個頂點)。

5、輸入PickPoints([[2,5],[3,5],[2,6]],mesh_origin)可以選定這些頂點

被選中頂點所影響的所有邊框線標示為黃色,這個“選中”只是改變外觀而已。

6、輸入TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.Translation(0,0,-10))將所選的頂點向z軸負方向移動10,被移動的頂點和前面選中的頂點其實沒有關系,其中arr_ij也可以直接用索引數組[[2,5],[3,5],[2,6]]代替。

另一類變形可以通過輸入:TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.RotationX(Math.PI/2))實現,這可以把被選中的頂點繞X中旋轉90度。

輸入DeleteMeshes([lines_inpicked])取消被選中的效果,輸入ChangeMaterial(mesh_origin,mat_blue)將邊框換成藍色紋理:

可以看到變形后的效果,接下來還可以繼續選擇頂點並變形

7、輸入ExportMesh("1",mat_blue),以txt格式導出babylon模型文件,文件名為“1.txt”

 8、將導出的txt改名為9.babylon后放入網站目錄中,訪問https://ljzc002.github.io/SnowMiku/HTML/LoadBabylon.html模型預覽頁面,在控制台輸入ImportMesh("","../ASSETS/SCENE/","9.babylon")即可加載剛才導出的模型。

二、編程思路:

1、首先要建立一個可以進行各種測試的基礎場景,使用的代碼如下:

  1 <!DOCTYPE html>
  2 <html lang="en">
  3 <head>
  4     <meta charset="UTF-8">
  5     <title>建立一個條帶網格生成器,能夠輸入參數生成起始條帶,然后通過命令行選取並修改pathArray,最后導出生成的條帶</title>
  6     <link href="../CSS/newland.css" rel="stylesheet">
  7     <link href="../CSS/stat.css" rel="stylesheet">
  8     <script src="../JS/MYLIB/Events.js"></script>
  9     <script src="../JS/MYLIB/FileText.js"></script>
 10     <script src="../JS/MYLIB/View.js"></script>
 11     <script src="../JS/LIB/babylon.32.all.maxs.js"></script><!--V3.2的穩定版本-->
 12     <script src="../JS/MYLIB/newland.js"></script>
 13     <script src="../JS/LIB/stat.js"></script>
 14 </head>
 15 <body>
 16 <div id="div_allbase">
 17     <canvas id="renderCanvas"></canvas>
 18     <div id="fps" style="z-index: 301;"></div>
 19 </div>
 20 </body>
 21 <script>
 22     var VERSION=1.0,AUTHOR="lz_newland@163.com";
 23     var machine,canvas,engine,scene,gl,MyGame={};
 24     canvas = document.getElementById("renderCanvas");
 25     engine = new BABYLON.Engine(canvas, true);
 26     gl=engine._gl;//可以結合使用原生OpenGL和Babylon.js;
 27     scene = new BABYLON.Scene(engine);
 28     var divFps = document.getElementById("fps");
 29 
 30     window.onload=beforewebGL;
 31     function beforewebGL()
 32     {
 33         if(engine._webGLVersion==2.0)//輸出ES版本
 34         {
 35             console.log("ES3.0");
 36         }
 37         else{
 38             console.log("ES2.0");
 39         }
 40         //MyGame=new Game(0,"first_pick","","http://127.0.0.1:8082/");
 41         /*0-startWebGL
 42          * */
 43         webGLStart();
 44     }
 45     //從下面開始分成簡單測試和對象框架兩種架構
 46     //簡單測試
 47     //全局對象
 48     var light0//全局光源
 49             ,camera0//主相機
 50             ;
 51     //四種常用材質
 52     var mat_frame = new BABYLON.StandardMaterial("mat_frame", scene);
 53     mat_frame.wireframe = true;
 54     var mat_red = new BABYLON.StandardMaterial("mat_red", scene);
 55     mat_red.diffuseColor = new BABYLON.Color3(1, 0, 0);
 56     mat_red.backFaceCulling=false;
 57     var mat_green = new BABYLON.StandardMaterial("mat_green", scene);
 58     mat_green.diffuseColor = new BABYLON.Color3(0, 1, 0);
 59     mat_green.backFaceCulling=false;
 60     var mat_blue = new BABYLON.StandardMaterial("mat_blue", scene);
 61     mat_blue.diffuseColor = new BABYLON.Color3(0, 0, 1);
 62     mat_blue.backFaceCulling=false;
 63     var mesh_origin;
 64     var advancedTexture=BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("ui1");//全屏GUI
 65     function webGLStart()
 66     {
 67         window.addEventListener("resize", function () {//自動調整視口尺寸
 68             engine.resize();
 69         });
 70         camera0 =new BABYLON.FreeCamera("FreeCamera", new BABYLON.Vector3(0, 0, -80), scene);
 71         camera0.attachControl(canvas, true);
 72         camera0.speed=0.5;//相機移動速度是默認速度的一半
 73         camera0.minZ=0.01;//相機位置距前視錐截面的距離,也就是說到相機距離小於0.01的圖元都不會顯示,這個值不能過小,否則Babylon.js內置的鼠標選取將失效
 74         camera0.layerMask=2;//相機的遮罩層次,這個相機將只能顯示遮罩層次同為2的網格,如果不設置這個屬性,似乎可以顯示所有遮罩層次的網格
 75         scene.activeCameras.push(camera0);//將相機加入活躍相機列表,默認情況下Babylon.js只使用一個活躍相機,但是也可以強行使用多個
 76         light0 = new BABYLON.HemisphericLight("Hemi0", new BABYLON.Vector3(0, 1, 0), scene);//半球光源
 77      //三個參照物,MeshBuilder是新版Babylon.js中使用的網格構建對象,之前翻譯入門教程時還沒有這個對象,它的特點是把一大堆參數統一整理到一個option參數中
 78         var mesh_base=new BABYLON.MeshBuilder.CreateSphere("mesh_base",{diameter:1},scene);
 79         mesh_base.material=mat_green;
 80         mesh_base.position.x=0;
 81         mesh_base.layerMask=2;
 82         var mesh_base1=new BABYLON.MeshBuilder.CreateSphere("mesh_base1",{diameter:1},scene);
 83         mesh_base1.position.y=10;
 84         mesh_base1.position.x=0;
 85         mesh_base1.material=mat_green;
 86         mesh_base1.layerMask=2;
 87         var mesh_base2=new BABYLON.MeshBuilder.CreateSphere("mesh_base2",{diameter:1},scene);
 88         mesh_base2.position.y=-10;
 89         mesh_base2.position.x=0;
 90         mesh_base2.material=mat_green;
 91         mesh_base2.layerMask=2;
 92       。
 93       。
 94       。
 95         MyBeforeRender();
 96     }
 97     function MyBeforeRender()
 98     {
 99         scene.registerBeforeRender(function() {
100             if(scene.isReady())
101             {
102                 
103 
104 105 106 107 108             }
109         });
110         engine.runRenderLoop(function () {
111             engine.hideLoadingUI();
112             if (divFps) {
113                 // Fps
114                 divFps.innerHTML = engine.getFps().toFixed() + " fps";
115             }
116             scene.render();
117         });
118 
119     } 
120 </script>
121 </html>   

這個3D場景包括了簡單測試所需要的一些基本元素,這里使用的是包含全部組件的未壓縮版Babylon.js庫,在實際使用中考慮到節省帶寬,可以使用Babylon.js官網提供的工具定制精簡版或壓縮版的Babylon.js庫

2、建立一個基礎網格

計划通過對一個基礎網格進行頂點變換來產生各種各樣的簡單模型,在Babylon.js中“條帶”是一種非常適合頂點變換的網格類型,Babylon.js官方教程中有關於條帶構造和變形的文檔,可以在這里下載中文翻譯http://down.51cto.com/data/2449757。

用來建立基礎網格的代碼如下:

 1     //下面這些函數都通過控制台調用
 2     //在ZoY平面里建立一個圓環路徑
 3     //radius:半徑,sumpoint:使用幾個點
 4     function MakeRing(radius,sumpoint)
 5     {
 6         var arr_point=[];
 7         var radp=Math.PI*2/sumpoint;
 8         for(var i=0.0;i<sumpoint;i++)
 9         {
10             var x=0;
11             var rad=radp*i;
12             //var y=sswr(radius*Math.sin(rad),null,5);//在這里需要降低一些精確度?否則Babylon.js在計算頂點數據時可能和這里不一致?
13             //var z=sswr(radius*Math.cos(rad),null,5);
14             var y=radius*Math.sin(rad);
15             var z=radius*Math.cos(rad);
16             arr_point.push(new BABYLON.Vector3(x,y,z));
17         }
18         arr_point.push(arr_point[0].clone());//首尾相連,不能這樣相連,否則變形時會多出一個頂點!!,看來這個多出的頂點無法去掉,只能在選取時額外處理它
19         return arr_point;
20     }
21     var arr_path=[];//核心數據
22     //arr_point:單個路徑的點數組,xstartl:第一個扁平路徑放在最左側,spacing:路徑的間距,sumpath:一共使用幾條路徑,
23     function MakeRibbon(arr_point,xstartl,spacing,sumpath,name)
24     {//將一條圓環路徑擴展成相互平行的多個圓環路徑,然后使用這些路徑生成條帶
25         arr_path=[];
26         for(var i=0;i<sumpath;i++)//對於每一條路徑
27         {
28             var x=xstartl+spacing*i;
29             //var arr=arr_point.concat(null);//為什么拷貝失靈了?
30             //var [ ...arr ] = arr_point;//ES6的新擴展運算符?-》也不好使,因為數組里的元素是指針?!!
31             var len=arr_point.length;
32             var arr=[];
33             for(var j=0;j<len;j++)
34             {
35                 var obj=arr_point[j].clone();
36                 obj.x=x;
37                 //
38                 arr.push(obj);
39             }
40             arr_path.push(arr);
41             arr=null;
42         }
43         mesh_origin.dispose();
44         mesh_origin=BABYLON.MeshBuilder.CreateRibbon(name,{pathArray:arr_path,updatable:true,closePath:false,closeArray:false});
45         //mesh_origin=mesh;//用一個全局變量保存最終會被導出的mesh
46         mesh_origin.sideOrientation=BABYLON.Mesh.DOUBLESIDE;//顯示網格的前后兩面
47         mesh_origin.material=mat_frame;
48         mesh_origin.layerMask=2;
49     }

編程中遇到的幾個問題:

a、路徑中設置的坐標值在實際顯示時可能發生微小的變化,比如5可能變成4.999999999999999999,但是似乎沒什么影響。

b、雖然圓環路徑分成12段應該由12個頂點組成,但是如果只有12個頂點,那么在調用CreateRibbon方法時,如果把closePath參數設為true則Babylon.js會自動添加一個不受控制的頂點來閉合圓環路徑,如果設為false,則路徑無法閉合。為此在第18行再添加一個與起始點

重合的頂點使圓環路徑閉合。

c、原計划直接使用數組的concat方法復制arr_point數組產生更多的圓環路徑,但concat方法似乎只能對一維數組使用(棧?),而arr_point的每個元素都是一個BABYLON.Vector3對象,所以只好用for循環一個點一個點的生成路徑

d、MakeRing是一個生成圓環路徑的方法,使用者可以根據需要編寫各種其他類型的路徑生成方法。

3、調整網格的屬性:

可以對網格的屬性進行一些調整,但是這些調整只在這個編輯器里生效,並不會被導出。

比如對網格材質的調整:

1 function ChangeMaterial(mesh,mat)
2     {
3         mesh.material=mat;
4     }

4、顯示網格頂點法線方向

代碼如下:

 1 var lines_normal={};
 2     /*ShowNormals(mesh_origin)
 3      DeleteMeshes([lines_normal]);
 4     * */
 5     //顯示所有的頂點法線
 6     function ShowNormals(mesh)
 7     {
 8         //DeleteMeshes(arr_line_normal);
 9         if(lines_normal.dispose)
10         {
11             lines_normal.dispose();
12         }
13         //遍歷頂點
14         var vb=mesh.geometry._vertexBuffers;
15         var data_pos=vb.position._buffer._data;//頂點數據
16         var data_mormal=vb.normal._buffer._data;//法線數據
17         var len=data_pos.length;
18         var lines=[];
19         for(var i=0;i<len;i+=3)
20         {//CreateLineSystem使用一個網格包含很多分立的線段(路徑),CreateLines則是一條首尾相連的路徑
21             //
22             var vec=new BABYLON.Vector3(data_pos[i],data_pos[i+1],data_pos[i+2]);
23             var vec2=vec.clone().add(new BABYLON.Vector3(data_mormal[i],data_mormal[i+1],data_mormal[i+2]).normalize().scale(1));
24             lines.push([vec,vec2]);
25         }
26         lines_normal=new BABYLON.MeshBuilder.CreateLineSystem("lines_normal",{lines:lines,updatable:false},scene);
27         lines_normal.color=new BABYLON.Color3(1, 0, 0);
28     }

Babylon.js內置的CreateLineSystem方法可以方便的建立一個由很多條線段組成的網格。

5、刪除網格

代碼如下:

 1 function DeleteMeshes(arr)//假設一個數組里的都是mesh,徹底清空它
 2     {
 3         var len=arr.length;
 4         for(var i=0;i<len;i++)
 5         {
 6             if(arr[i].dispose)
 7                 arr[i].dispose();
 8         }
 9         arr=[];
10     }

值得注意的是,直接在控制台里執行mesh.dispose()並不生效。

這里也體現出CreateLineSystem的方便之處——刪除法線時只需要dispose一個對象。

6、鼠標移入時顯示三角形信息

鼠標動作監聽代碼:

 1 mesh_origin=new BABYLON.Mesh("mesh_origin",scene);//建立一個空的初始網格對象
 2         mesh_surface=new BABYLON.Mesh("mesh_surface",scene);
 3         mesh_surface0=new BABYLON.Mesh("mesh_surface0",scene);
 4         if(true)
 5         {//初始化三個GUI標簽
 6             label_index1=new BABYLON.GUI.TextBlock();
 7             label_index1.text = "label_index1";
 8             label_index1.color="white";
 9             label_index1.isVisible=false;//初始化時標簽不可見
10             //label_index1.linkWithMesh(mesh_surface0);//TextBlock並不是頂層元素
11             advancedTexture.addControl(label_index1);
12             label_index2=new BABYLON.GUI.TextBlock();
13             label_index2.text = "label_index2";
14             label_index2.color="white";
15             label_index2.isVisible=false;
16             //label_index2.linkWithMesh(mesh_surface0);
17             advancedTexture.addControl(label_index2);
18             label_index3=new BABYLON.GUI.TextBlock();
19             label_index3.text = "label_index3";
20             label_index3.color="white";
21             label_index3.isVisible=false;
22             //label_index3.linkWithMesh(mesh_surface0);
23             advancedTexture.addControl(label_index3);
24         }
25 //監聽鼠標移動
26 canvas.addEventListener("mousemove", function(evt){
27 var pickInfo = scene.pick(scene.pointerX, scene.pointerY,null,null,camera0);//如果同時有多個激活的相機,則要明確的指出使用哪個相機
28             //cancelPropagation(evt);
29             //cancelEvent(evt);
30             label_index1.isVisible=false;
31             label_index2.isVisible=false;
32             label_index3.isVisible=false;
33             if(mesh_surface.dispose)
34             {
35                 mesh_surface.dispose();
36             }
37             if(mesh_surface0.dispose)
38             {
39                 mesh_surface0.dispose();
40             }
41             if(pickInfo.hit&&(pickInfo.pickedMesh.name=="mesh_origin"||pickInfo.pickedMesh.name=="mesh_ribbon"))//找到鼠標所在的三角形並重繪之
42             {
43           //在鼠標所指的地方繪制一個三角形,表示被選中的三角形
44                 var faceId=pickInfo.faceId;
45                 var pickedMesh=pickInfo.pickedMesh;
46                 var indices=[pickedMesh.geometry._indices[faceId*3]
47                     ,pickedMesh.geometry._indices[faceId*3+1],pickedMesh.geometry._indices[faceId*3+2]];
48                 var vb=pickedMesh.geometry._vertexBuffers;
49                 var position=vb.position._buffer._data;
50                 var normal=vb.normal._buffer._data;
51                 var uv=vb.uv._buffer._data;
52                 var len=arr_path[0].length;
53                 var p1={index:indices[0],position:[position[indices[0]*3],position[indices[0]*3+1],position[indices[0]*3+2]]
54                     ,normal:[normal[indices[0]*3],normal[indices[0]*3+1],normal[indices[0]*3+2]]
55                     ,uv:[uv[indices[0]*2],uv[indices[0]*2+1]],ppi:("1:"+(Math.round(indices[0]/len)+0))+"-"+(indices[0]%len)};//pathpointindex
56                 var p2={index:indices[1],position:[position[indices[1]*3],position[indices[1]*3+1],position[indices[1]*3+2]]
57                     ,normal:[normal[indices[1]*3],normal[indices[1]*3+1],normal[indices[1]*3+2]]
58                     ,uv:[uv[indices[1]*2],uv[indices[1]*2+1]],ppi:("2:"+(Math.round(indices[1]/len)+0))+"-"+(indices[1]%len)};
59                 var p3={index:indices[2],position:[position[indices[2]*3],position[indices[2]*3+1],position[indices[2]*3+2]]
60                     ,normal:[normal[indices[2]*3],normal[indices[2]*3+1],normal[indices[2]*3+2]]
61                     ,uv:[uv[indices[2]*2],uv[indices[2]*2+1]],ppi:("3:"+(Math.round(indices[2]/len)+0))+"-"+(indices[2]%len)};
62                 var po=[(p1.position[0]+p2.position[0]+p3.position[0])/3,(p1.position[1]+p2.position[1]+p3.position[1])/3,(p1.position[2]+p2.position[2]+p3.position[2])/3];
63                 var numm=2;
64                 mesh_surface0=newland.make_tryangle(p1,p2,p3,"mesh_surface1",scene);//使用三個點繪制一個三角形
65                 mesh_surface0.material=mat_green;
66                 mesh_surface0.sideOrientation==BABYLON.Mesh.DOUBLESIDE;
67                 mesh_surface0.layerMask=2;
68                 label_index1.isVisible=true;
69                 //label_index1.layerMask=2;//這個屬性對於gui被管對象並不生效?
70                 label_index1.text=p1.ppi;//ppi表示這個頂點是三角形的第幾個頂點,以及這個頂點位於第幾條路徑的第幾個位置
71                 var pos1=new BABYLON.Vector3(p1.position[0],p1.position[1],p1.position[2]);
72                 label_index1.moveToVector3(pos1,scene);
73                 label_index1.itspos=pos1;
74                 label_index2.isVisible=true;
75                 label_index2.text=p2.ppi;
76                 var pos2=new BABYLON.Vector3(p2.position[0],p2.position[1],p2.position[2]);
77                 label_index2.moveToVector3(pos2,scene);
78                 label_index2.itspos=pos2;
79                 label_index3.isVisible=true;
80                 label_index3.text=p3.ppi;
81                 var pos3=new BABYLON.Vector3(p3.position[0],p3.position[1],p3.position[2]);
82                 label_index3.moveToVector3(pos3,scene);
83                 label_index3.itspos=pos3;
84                 //將三個點移動到場景的中心
85                 pt=[(p1.position[0]-po[0])*numm,(p1.position[1]-po[1])*numm,(p1.position[2]-po[2])*numm];
86                 p1.position=pt;
87                 pt=[(p2.position[0]-po[0])*numm,(p2.position[1]-po[1])*numm,(p2.position[2]-po[2])*numm];
88                 p2.position=pt;
89                 pt=[(p3.position[0]-po[0])*numm,(p3.position[1]-po[1])*numm,(p3.position[2]-po[2])*numm];
90                 p3.position=pt;
91           //在場景的中心再繪制一個大一倍的三角形
92                 mesh_surface=newland.make_tryangle(p1,p2,p3,"mesh_surface",scene);
93                 mesh_surface.material=mat_green;
94                 mesh_surface.sideOrientation==BABYLON.Mesh.DOUBLESIDE;
95                 mesh_surface.layerMask=1;//遮罩層次是1
96 
97             }
98 
99         },false);

需要注意的有以下幾點:

a、鼠標指向時顯示的是這個點在arr_path中的索引,而不是在“頂點數據對象”中的索引!

b、最初的計划是像魔獸爭霸3一樣在窗口的左下角建立一個遮罩層次為1的小視口,用來特寫被選中的三角形(mesh_surface),但是后來發現Babylon.js的GUI不能設置遮罩層次,也不能像鼠標選取一樣設置相對於哪一個相機,所以取消了左下角的小視口。

如果需要添加視口,代碼如下:

 1 var mm = new BABYLON.FreeCamera("minimap", new BABYLON.Vector3(0,0,-20), scene);
 2         mm.layerMask = 1;
 3         var xstart = 0.0,
 4                 ystart = 0.1;//意為占屏幕寬高的比例
 5         var width = 0.2,
 6                 height = 0.2;
 7         //視口邊界從左下角開始
 8         mm.viewport = new BABYLON.Viewport(
 9                 xstart,
10                 ystart,
11                 width,
12                 height
13         );
14         scene.activeCameras.push(mm);

c、需要注意的是GUI是一次性繪制在視口上的,默認情況下即使相關的網格位置發生變化,GUI仍然會保持在視口上最初的位置。linkWithMesh方法可以將GUI和網格綁定起來,但是似乎只能對部分“底層的”GUI類型生效,為了使得標簽跟隨頂點移動,需要手動在每一幀渲染之前更新標簽的位置:

 1 scene.registerBeforeRender(function() {
 2             if(scene.isReady())
 3             {
 4                 if(label_index1.isVisible==true)//不斷刷新gui的位置
 5                 {//在具有多個激活相機時選擇了錯誤的參考相機?-》會強制選擇最新建立的相機
 6                     label_index1.moveToVector3(label_index1.itspos,scene);
 7                     label_index2.moveToVector3(label_index2.itspos,scene);
 8                     label_index3.moveToVector3(label_index3.itspos,scene);
 9                 }
10             }
11         });

7、選取頂點:

3DsMax和Blender(Babylon.js庫在3D模型部分參考了Blender的許多設計)都使用鼠標在網格中選取需要修改的區域,我不是很習慣這種方式,所以考慮用JS函數代替鼠標操作:

 1 var lines_inpicked={};//線段系統對象,表示所有被選中點影響的線段
 2     var arr_ij=[];//記錄被選中的點在arr_path中的索引的數組
 3     /*DeleteMeshes([lines_inpicked]);
 4     * PickPoints([[0,0],[0,12],[1,1]],mesh_origin)
 5     * */
 6     //選定一些頂點
 7     function PickPoints(arr,mesh)
 8     {
 9         if(arr_path.length==0)
10         {
11             alert("尚未生成路徑數組!");
12             return;
13         }
14         if(lines_inpicked.dispose)
15         {
16             lines_inpicked.dispose();
17         }
18         //arr_ij=[];
19         //為了后面考慮,需要先對arr整體遍歷一遍,把首尾接口處關聯起來
20         arr_ij=arr;//把路徑和點的位置記錄下來
21         var vb=mesh.geometry._vertexBuffers;
22         var data_pos=vb.position._buffer._data;
23         var len_pos=data_pos.length;
24         var data_index=mesh.geometry._indices;
25         var len_index=data_index.length;
26         var len=arr.length;
27         var lines=[];
28         for(var i=0;i<len;i++)//對於每一個需要顯示的被選擇點
29         {
30             var int0=arr[i][0];
31             var int1=arr[i][1];
32             var vec=arr_path[int0][int1];//獲取到路徑數組中的一個Vector3對象
33             //假設路徑數組和頂點數據是一一對應的?同時假設每一條路徑的長度都和第一條相同
34             var arr_index=[int0*arr_path[0].length+int1];//所有位於所選位置的頂點在頂點數據對象中的索引
35             //下面遍歷網格的繪制索引,找出這個被選中的頂點每一次的使用情況
36             for(var j=0;j<len_index;j+=3)//根據頂點在三角形中的位置分三種情況討論
37             {//繪制出和這個頂點相關的所有線段,這樣繪制會有很嚴重的線段重合!觀察是否對性能有很大的影響
38                 var len2=arr_index.length;
39                 for(var k=0;k<len2;k++)
40                 {
41                     if(arr_index[k]==data_index[j])//三角形的第一個頂點
42                     {//把這個頂點和三角形中的另兩個頂點用黃線連起來
43                         var num2=data_index[j+1]*3;
44                         var num3=data_index[j+2]*3;
45                         var vec2=new BABYLON.Vector3(data_pos[num2],data_pos[num2+1],data_pos[num2+2]);
46                         var vec3=new BABYLON.Vector3(data_pos[num3],data_pos[num3+1],data_pos[num3+2]);
47                         lines.push([vec,vec2]);
48                         lines.push([vec,vec3]);
49                     }
50                     else if(arr_index[k]==data_index[j+1])//三角形的第一個頂點
51                     {
52                         var num2=data_index[j]*3;
53                         var num3=data_index[j+2]*3;
54                         var vec2=new BABYLON.Vector3(data_pos[num2],data_pos[num2+1],data_pos[num2+2]);
55                         var vec3=new BABYLON.Vector3(data_pos[num3],data_pos[num3+1],data_pos[num3+2]);
56                         lines.push([vec,vec2]);
57                         lines.push([vec,vec3]);
58                     }
59                     else if(arr_index[k]==data_index[j+2])//三角形的第一個頂點
60                     {
61                         var num2=data_index[j]*3;
62                         var num3=data_index[j+1]*3;
63                         var vec2=new BABYLON.Vector3(data_pos[num2],data_pos[num2+1],data_pos[num2+2]);
64                         var vec3=new BABYLON.Vector3(data_pos[num3],data_pos[num3+1],data_pos[num3+2]);
65                         lines.push([vec,vec2]);
66                         lines.push([vec,vec3]);
67                     }
68                 }
69             }
70         }
71         lines_inpicked=new BABYLON.MeshBuilder.CreateLineSystem("lines_normal",{lines:lines,updatable:false},scene);
72         lines_inpicked.color=new BABYLON.Color3(1, 1, 0);
73     }
74     //需要一些選擇選定頂點的方法

有以下幾處需要注意:

a、因為MakeRing生成的路徑里包括兩個重合的點(首尾),為了保證變形后的網格仍然連續,在選擇時這兩個重合的點必須同時被選中,經過考慮,規定這個確保首尾重合點同時選中的操作在生成arr數組參數時進行。

b、因為基礎網格mesh_origin的每一個頂點恰好位置不同,所以一開始我以為“與被選中的點的位置相同的點”應該具有與被選中點相同的變形效果,但是事實上經過網格變形后,完全可能出現兩個不相干的頂點位於同一位置的情況,這時應用相同的變形效果會出現錯誤,比如人可以把手放在腿上,這時手和腿的接觸點具有相同的位置,但如果因此對這兩個點應用相同的變形效果,就很詭異了。同時如前文所說,有時arr_path中存儲的位置和頂點數據中存儲的位置可能出現微小的偏差。

最后采取的方法是將arr_path中的數組轉化為頂點數據對象中的數組。

c、這里直接選取了幾個點進行變形,還需要編寫一些按照某種規則批量選取點的方法,也希望大家能幫我想一想怎樣選取頂點比較方便。

8、網格變形:

代碼如下:

 1 /*
 2     * TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.RotationX(Math.PI/2))
 3     *TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.Translation(0.5,0,0))
 4     * */
 5     //移動選定的頂點,同時更新網格、選取線、法線
 6     function TransVertex(mesh,arr,matrix)
 7     {
 8         var len=arr.length;
 9         for(var i=0;i<len;i++)//移動路徑數組里的每個頂點
10         {
11             //var vec=arr_path[arr[i][0]][arr[i][1]];
12             arr_path[arr[i][0]][arr[i][1]]=BABYLON.Vector3.TransformCoordinates(arr_path[arr[i][0]][arr[i][1]],matrix);
13         }
14         //更新網格,發現設置了closePath:true之后在頂點數據里還是會自動補上一個接續點,這還不如一開始自己添加!起碼自己添加可以保證路徑數組和頂點數據的一致性!!!!
15         mesh=BABYLON.MeshBuilder.CreateRibbon(mesh.name,{pathArray:arr_path,updatable:true,instance:mesh,closePath:false,closeArray:false});
16         //上面的更新重繪是默認重算法線方向的,這與直接修改頂點數據不同
17         //清空法線
18         DeleteMeshes([lines_normal]);
19         //更新選取頂點表示
20         PickPoints(arr,mesh);
21     }
22     //需要一些生成變化矩陣的方法

其實這里移動的頂點和前面選擇的點沒有關系,不選擇也可以直接變形,只不過不會有黃線顯示罷了。這里也同樣需要一些生成更復雜的變換矩陣的方法。

9、導出:

導出方法的調用如下:

 1 /*ExportMesh("1",mat_blue)*/
 2     function ExportMesh(filename,mat)
 3     {
 4         try{
 5             newland.ExportBabylonMesh([mesh_origin],filename,mat);
 6         }
 7         catch(e)
 8         {
 9             console.error(e)
10         }
11     }

函數的第一個參數是導出后的文件名(不包括擴展名),第二個參數是網格使用的材質對象(暫時只支持簡單的顏色材質)。

其中ExportBabylonMesh是我編寫的newland庫中的一個方法,這個方法在前面關與3D模型的文章中也有提到過(https://www.cnblogs.com/ljzc002/p/6884252.html),這個方法整體上沒有太大的改變。但是在舊版的Babylon.js中,頂點數據對象中的data屬性是數組類型(?),而在新版中data屬性則是typedArray類型,雖然這兩種數據類型看起來很像,但在使用JSON.stringify(arr)轉化為JSON字符串時,對於同樣的數據[1,2,3],前者會轉化為"[1,2,3]"后者則是"{"0":1,"1":2,"2":3}"!

在導入模型文件時后者會報錯:“attempt to access out of range vertices in attribute 0”,這意味着在導入模型時頂點數據數組沒有正確的載入,我的解決方案是導出前將data屬性強制轉化為數組類型:

 1 //將TypedArray轉化為普通array
 2 newland.BuffertoArray2=function(arr)
 3 {
 4     var arr2=[];
 5     var len=arr.length;
 6     for(var i=0;i<len;i++)
 7     {
 8         arr2.push(arr[i]);
 9     }
10     return arr2;
11 }

對於舊版的Babylon.js應該可以不加修改的使用這個方法,因為typedArray也具有數組的大部分功能。

10、導入:

在另一個頁面里實現模型導入功能,這個頁面同樣在“基礎場景”的基礎上編寫,其中導入方法如下:

 1 function ImportMesh(objname,filepath,filename)
 2     {
 3 
 4         BABYLON.SceneLoader.ImportMesh(objname, filepath, filename, scene
 5                 , function (newMeshes, particleSystems, skeletons)
 6                 {//載入完成的回調函數
 7                     if(mesh_origin&&mesh_origin.dispose)
 8                     {
 9                         mesh_origin.dispose();
10                     }
11                     mesh_origin=newMeshes[0];
12                     //mesh_origin.material=mat_frame;
13                     //mesh_origin.layerMask=2;
14                 }
15         );
16     }

 

20180807修改

使用上述方法建立了一個“農夫山泉4L裝塑料桶”的網格,可以通過http://ljzc002.github.io/SnowMiku/HTML/NongFuSpring.html訪問。在新增的Make.js文件中編寫了幾個生成所需頂點路徑的方法,包括:生成正多邊形路徑、生成指定軸對稱路徑、生成圓弧表面;以及調整已有頂點位置的方法,包括:產生溝壑、按正弦曲線規律變換頂點路徑。

最終產生的網格如下圖:

實踐證明,對於具有一定排布規律的多頂點網格來說,使用編程方法產生網格比較方便。

 

20180808修改

通過查看文檔,發現雖然高級動態紋理在建立時無法指定在哪個相機中進行顯示,但他的屬性支持多相機設置,需要設置的屬性是advancedTexture.layer.layerMask!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM