澳门新浦京娱乐场网站-www.146.net-新浦京娱乐场官网
做最好的网站

在WebGL场景中使用2DA,使用Chrome控制台进行3D模型

前言:3D模型编辑的核心是对顶点位置和纹理颜色的编辑,这个研究的目的在于寻找一种通过编程方式直接对模型进行编辑的方法,这种编辑方法和时下流行的通过鼠标点选、拖拽进行编辑的方法之间的关系,和前端编程中“程序员编写静态网页”与“美工进行网页切图”之间的关系很相似。

在WebGL场景中使用2DA*寻路,webgl2da

     这篇文章将讨论如何在一个自定义的地面网格上进行简单的2D寻路,以及确定路径后如何使用基于物理引擎的运动方式使物体沿路径到达目标地点。读者需要预先对WebGL和Babylonjs知识有一些了解,可以参考我录制的WebGL入门视频教程和翻译的官方入门文档,当然也可以用自己喜欢的其他方式来学习。

  文章主要分成如下几部分:

  1、自定义地面网格与寻路矩阵

  2、生成Babylon格式3D模型

  3、使用pathfinding库进行2D寻路

  4、基于cannon.js物理引擎使物体沿路径移动

  场景可以通过

  场景如下图:

澳门新浦京娱乐场网站 1

  使用WASD控制自由相机位置,移动鼠标控制视角,右键点击地面会在地面上放置一个“目标方块”,然后标有“农民”标志的小球会向目标方块移动。

  场景中使用了2DA*寻路算法,如下图所示:

澳门新浦京娱乐场网站 2

  当目标方块位于障碍的另一边时,农民会尽量寻找最短的路径绕开障碍物前往目标方块。

1、自定义地面网格与寻路矩阵:

a、在Babylon.js渲染引擎中自定义地面网格

 1     var vdata_ground=new BABYLON.VertexData.CreateGround({width:198,height:198,subdivisionsX:99,subdivisionsY:99});//分成了99段,一条边上100个顶点
 2     var arr_vposition=vdata_ground.positions;
 3     var map=[];
 4     var len=arr_vposition.length/3;//和挨个遍历比起来,似乎有目的的去找更好
 5     for(var i=20;i<81;i  )//对于这个范围内的行
 6     {
 7         for(var j=20;j<23;j  )//对于这个范围内的列
 8         {
 9             arr_vposition[(j 100*i)*3 1]=1;
10         }
11     }
12     for(var i=20;i<81;i  )//对于这个范围内的行
13     {
14         for(var j=40;j<43;j  )//对于这个范围内的列
15         {
16             arr_vposition[(j 100*i)*3 1]=2;
17         }
18     }
19     for(var i=20;i<81;i  )//对于这个范围内的行
20     {
21         for(var j=60;j<63;j  )//对于这个范围内的列
22         {
23             arr_vposition[(j 100*i)*3 1]=4;
24         }
25     }
26     BABYLON.VertexData._ComputeSides(0, arr_vposition, vdata_ground.indices, vdata_ground.normals
27         , vdata_ground.uvs);
28     var mesh_ground=new BABYLON.Mesh("mesh_ground",scene);
29     mesh_ground.renderingGroupId=2;
30     vdata_ground.applyToMesh(mesh_ground, true);

   第一行建立了一个Ground类型的Babylonjs“顶点数据”对象,这个对象包含了建立地面网格所需的顶点位置、法线、纹理坐标、顶点索引数据(建议读者亲自用调试模式看一下这个对象的结构),构造函数中的两个198表示地面的长宽是198,两个99表示每一条边被分为99段(由100个顶点组成,每两个顶点之间的距离为2),至于为什么设置为99段后文会有说明,这时的地面网格如果渲染出来将是一个平面。

  要让地面变得凹凸不平有两种思路:在高度变化的点的比例较大时,可以尝试对每个顶点进行遍历,然后按照某种规则改变顶点的高度;在比例较小时建议直接在缓存数组中找到这些顶点进行改变,显然后者速度更快。

  第26行根据顶点数据对每个面的正反进行计算,在对网格的顶点信息进行修改后一般都要执行这一条语句,而另一条经常在它之前执行的语句是:“

BABYLON.VertexData.ComputeNormals(positions, indices, normals);

”,它的作用是在顶点数据变化后重新计算法线方向。这里不执行这条语句的原因是Babylonjs中的地面网格是一种“简化”的网格

  如图所示:

澳门新浦京娱乐场网站 3

  同样表示两个方块,简化的方式使用六个顶点,顶点之间的片元数据由顶点数据插值而成,因为左边的方块和右边的方块共用了两个顶点,所以这两个方块的法线方向和纹理坐标必定是连续的。而用非简化的方式表示这两个方块,则需要使用八个顶点,缺点是增加了对性能的消耗,优点是法线方向和纹理坐标不必连续,可以进行截然不同的变化。

  因为采用了简化的方式,地面网格的同一个顶点处于多个不同的平面中,使用ComputeNormals计算地面网格的顶点的法线方向也就失去了意义,事实上Babylonjs把地面网格顶点的法线方向都默认为竖直向上。

  后面的代码建立了一个空的网格,将网格的渲染组设为2,将顶点数据交给这个空网格对象。

  生成的网格如下图所示:

澳门新浦京娱乐场网站 4

 

b、建立2D寻路矩阵

  在寻路场景中使用的pathfinding库需要用一个矩阵(二维数组)来定义障碍物的位置,其中零元素表示这个地块可以通行,不为零的元素表示无法通行,下面是建立这个数组的方法:

 1     mesh_ground.mydata={};
 2     mesh_ground.mydata.walkabilityMatrix=MakewalkabilityMatrix(arr_vposition,99,99,2);
 3     mesh_ground.mydata.len_x=99;
 4     mesh_ground.mydata.len_y=99;
 5     mesh_ground.mydata.len_s=2;
 6     
 7     。
 8     。
 9     。
10     
11 //对每个正方形区块的倾斜程度进行计算,得出是否可以通行
12 //顶点数据,寻路空间宽度,寻路空间高度,每个方格区域的边长
13 function MakewalkabilityMatrix(arr,len_x,len_y,len_s)
14 {
15     var arr_Matrix=numeric.rep([len_y,len_x],0);
16     var len_s2=len_s*0.707;//求得平均点到其中一个边线点的水平距离0.7071067811865476
17     //var len_s2a=len_s2;
18     //var len_s2b=len_s2;
19     //var len_s2c=len_s2;
20     for(var i=0;i<len_y;i  )//对于每一行寻路单元格
21     {
22         for(var j=0;j<len_x;j  )//对于这一行里的每一个单元格
23         {
24             var int1=j i*(len_x 1);
25             var int2=j i*(len_x 1) 1;
26             var int3=j (i 1)*(len_x 1);
27             var int4=j (i 1)*(len_x 1) 1;
28             var y1=arr[int1*3 1];
29             var y2=arr[int2*3 1];
30             var y3=arr[int3*3 1];
31             var y4=arr[int4*3 1];
32             var ya=(y1 y2 y3 y4)/4;
33             var yb=Math.max(Math.abs(y1-ya),Math.abs(y2-ya),Math.abs(y3-ya),Math.abs(y4-ya));
34             arr_Matrix[i][j]=parseInt((yb)/0.707);//高度超过了水平距离几倍就认为是几倍的障碍物
35         }
36     }
37     return arr_Matrix;
38 }

  这一段代码的思路是:将地面网格垂直方向的正投影作为寻路单元格,取每一个寻路单元格的四个顶点,算出这四个顶点的平均高度与每个顶点高度的差的最大值与寻路单元格中心点到顶点的水平距离的比,将这个比值作为“障碍程度”(简单来说就是顶点高度变化的越剧烈,障碍就越难跨越)。然后为网格添加一个mydata属性(JavaScript语言的优势),把和寻路矩阵有关的信息放到这个属性里。

  这里用到了numeric数学库,可以在

  2、生成babylon格式的3D模型:

  网格生成完毕后需要拿到其他程序中使用,这里我选择把它保存为babylon格式的3D模型,babylon是一种json字符串模型文件,其优点是结构简单功能全面。

  Babylon.js的官方网站上有完整的格式说明和例子:

  以下是生成对应json的代码:

  1 /**
  2  * Created by Administrator on 2017/7/14.
  3  */
  4 function Export_mesh(arr_mesh,PngName)//用Babylon格式导出模型
  5 {
  6     //场景对象
  7     var obj_scene=
  8     {
  9         'autoClear': true,
 10         'clearColor': [0,0,0],
 11         'ambientColor': [0,0,0],
 12         'gravity': [0,-9.81,0],
 13         'cameras': [{
 14             'name': 'Camera',
 15             'id': 'Camera',
 16             'position': [7.4811,5.3437,-6.5076],
 17             'target': [-0.3174,0.8953,0.3125],
 18             'fov': 0.8576,
 19             'minZ': 0.1,
 20             'maxZ': 100,
 21             'speed': 1,
 22             'inertia': 0.9,
 23             'checkCollisions': false,
 24             'applyGravity': false,
 25             'ellipsoid': [0.2,0.9,0.2]
 26         }],
 27         'activeCamera': 'Camera',
 28         'lights': [{
 29             'name': 'Sun',
 30             'id': 'Sun',
 31             'type': 1,
 32             'position': [0.926,7.3608,14.1829],
 33             'direction': [-0.347,-0.4916,-0.7987],
 34             'intensity': 1,
 35             'diffuse': [1,1,1],
 36             'specular': [1,1,1]
 37         }],
 38         'materials':[{
 39             'name': 'mball',
 40             'id': 'mball',
 41             'ambient': [1,1,1],
 42             'diffuse': [1,1,1],
 43             'specular': [1,1,1],
 44             'specularPower': 50,
 45             'emissive': [0,0,0],
 46             'alpha': 1,
 47             'backFaceCulling': true,
 48             'diffuseTexture': {
 49                 'name': PngName?PngName:'snow2.jpg',
 50                 'level': 1,
 51                 'hasAlpha': 1,
 52                 'coordinatesMode': 0,
 53                 'uOffset': 0,
 54                 'vOffset': 0,
 55                 'uScale': 1,
 56                 'vScale': 1,
 57                 'uAng': 0,
 58                 'vAng': 0,
 59                 'wAng': 0,
 60                 'wrapU': true,
 61                 'wrapV': true,
 62                 'coordinatesIndex': 0
 63             }
 64         }],
 65         'geometries': {},
 66         'meshes': [],
 67         'multiMaterials': [],
 68         'shadowGenerators': [],
 69         'skeletons': [],
 70         'sounds': [],
 71         'mydata':{'walkabilityMatrix':[]}
 72     };
 73     //所有模型组件的父物体
 74     var obj_allbase=
 75     {
 76         'name': 'allbase',
 77         'id': 'allbase',
 78         'materialId': 'mball',
 79         'position': [0,0,0],
 80         'rotation': [0,0,0],
 81         'scaling': [1,1,1],
 82         'isVisible': true,
 83         'isEnabled': true,
 84         'checkCollisions': false,
 85         'billboardMode': 0,
 86         'receiveShadows': true,
 87         'positions': [],
 88         'normals': [],
 89         'uvs': [],
 90         'indices': [],
 91         'subMeshes': [{
 92             'materialIndex': 0,
 93             'verticesStart': 0,
 94             'verticesCount': 0,
 95             'indexStart': 0,
 96             'indexCount': 0
 97         }]
 98     };
 99     obj_scene.meshes.push(obj_allbase);
100     var len=arr_mesh.length;
101     var all_x=0;
102     var all_y=0;
103     var all_z=0;
104     for(var i=0;i<len;i  )
105     {
106         var obj_child={};
107         if(arr_mesh[i].geometry._vertexBuffers!=null)
108         {
109             var child=arr_mesh[i];
110             if(!child.mydata)
111             {
112                 child.mydata={}
113             }
114             var vb=child.geometry._vertexBuffers;
115             all_x =child.position.x;
116             all_y =child.position.y;
117             all_z =child.position.z;
118             obj_child=
119             {
120                 'name': child.name,
121                 'id': child.id,
122                 'parentID': 'allbase',
123                 'materialId': 'mball',
124                 'position': [child.position.x,child.position.y,child.position.z],
125                 'rotation': [child.rotation.x,child.rotation.y,child.rotation.z],
126                 'scaling': [child.scaling.x,child.scaling.y,child.scaling.z],
127                 'isVisible': true,
128                 'isEnabled': true,
129                 'checkCollisions': false,
130                 'billboardMode': 0,
131                 'receiveShadows': true,
132                 'positions': vb.position._buffer._data,
133                 'normals': vb.normal._buffer._data,
134                 'uvs': vb.uv._buffer._data,
135                 'indices': child.geometry._indices,
136                 'subMeshes': [{
137                     'materialIndex': 0,
138                     'verticesStart': 0,
139                     'verticesCount': vb.position._buffer._data.length,
140                     'indexStart': 0,
141                     'indexCount': child.geometry._indices.length
142                 }],
143                 'mydata':child.mydata
144             };
145             obj_scene.meshes.push(obj_child);
146         }
147     }
148     //不能让模型的主体过于偏离模型的中心
149     all_x=all_x/len;
150     all_y=all_y/len;
151     all_z=all_z/len;
152     for(var i=1;i<len 1;i  )
153     {
154         obj_scene.meshes[i].position[0]-=all_x;
155         obj_scene.meshes[i].position[1]-=all_y;
156         obj_scene.meshes[i].position[2]-=all_z;
157     }
158     var str_data=JSON.stringify(obj_scene);
159     DownloadText(MakeDateStr() "testscene",str_data,".babylon");
160 }

  可以看出,一个babylon文件可以包含多个网格对象,除了网格对象之外这个模型文件还可以存储场景、光照、相机、动画、骨骼等信息,这些功能可以选择性使用。方法的最后使用DownloadText方法将json文本导出,DownloadText是我参考网络资料编写的字符下载方法,如果不使用DownloadText,直接在Chrome浏览器的调试模式下的命令行里输入“console.log(str_data)”也能得到json字符串。

  DownloadText内容如下:

澳门新浦京娱乐场网站 5

  1 /**
  2  * Created by Administrator on 2015/3/2.
  3  */
  4 /**
  5  * 将指定字符写入指定名称的文本文件中,并可以选择本地保存目录,兼容IE11和谷歌浏览器
  6  */
  7 function DownloadText(filename,content,filetype)
  8 {
  9     if(filetype==null)
 10     {
 11         filetype=".txt";
 12     }
 13     if(document.createElement("a").download!=null)//谷歌和火狐
 14     {
 15         var aLink = document.createElement('a');
 16         var datatype="data:text/plain;charset=UTF-8,";
 17         if(filetype==".xml")
 18         {
 19             datatype="data:text/xml;charset=UTF-8,";
 20         }
 21         if(filetype==".babylon")
 22         {//浏览器还没有支持babylon的mime类型!!
 23             datatype="data:text/plain;charset=UTF-8,";
 24         }
 25         if(filetype==".png"||filetype==".jpeg")
 26         {
 27             datatype="";
 28         }
 29         if(content.length<1000000)
 30         {
 31             aLink.href = datatype content;//dataurl格式的字符串"
 32         }
 33         else
 34         {//对于过大的文件普通dataURL不支持,所以使用“二进制流大对象”
 35             aLink.href=URL.createObjectURL(new Blob([content],{type:"text/plain"}));
 36         }
 37         aLink.download = filename;
 38         aLink.innerHTML=filename;
 39         //aLink.setAttribute("onclick","");
 40         aLink.onclick=function()
 41         {
 42             document.getElementById("div_choose").style.display="none";
 43             //delete_div('div_choose');
 44             delete_div('div_mask');
 45         }
 46         //aLink.style.display="none";
 47         //document.body.appendChild(aLink);
 48         /*var evt = document.createEvent("HTMLEvents");//建立一个事件
 49         evt.initEvent("click", false, false);//这是一个单击事件
 50         evt.eventType = 'message';
 51         aLink.dispatchEvent(evt);//触发事件*/
 52         //chrome认为点击超链接下载文件是超链接标签的“默认属性”,谷歌认为默认属性不可以用脚本来触发,所以从M53版本开始dispatchEvent无法触发超链接下载
 53         //window.open(datatype content, "_blank");
 54         //document.write(datatype content);
 55         delete_div('div_choose');
 56         delete_div('div_mask');
 57         var evt=evt||window.event;
 58         cancelPropagation(evt);
 59         var obj=evt.currentTarget?evt.currentTarget:evt.srcElement;
 60 
 61         Open_div("", "div_choose", 240, 180, 400, 80, "", "",1,401);//打开一个带遮罩的弹出框
 62         var div_choose=$("#div_choose")[0];
 63         div_choose.style.border="1px solid";
 64         div_choose.innerHTML="谷歌浏览器专用文件生成完毕,请点击下面的文件名下载文件。<br>"
 65         div_choose.appendChild(aLink);
 66         drag(div_choose);//让弹出框可以被拖拽
 67         aLink.onmousedown=function()
 68         {
 69             var evt=evt||window.event;
 70             cancelPropagation(evt);
 71         }
 72     }
 73     else//IE
 74     {
 75         var Folder=BrowseFolder();
 76         if(Folder=="false")
 77         {
 78             alert("保存失败!");
 79         }
 80         else
 81         {
 82             var fso, tf;
 83             fso = new ActiveXObject("Scripting.FileSystemObject");//创建文件系统对象
 84             tf = fso.CreateTextFile(Folder   filename filetype, true,true);//创建一个文件
 85             tf.write(content);
 86             tf.Close();
 87             alert("保存完毕!");
 88         }
 89     }
 90 }
 91 function BrowseFolder()
 92 {//使用ActiveX控件
 93     try
 94     {
 95         var Message = "请选择保存文件夹";  //选择框提示信息
 96         var Shell = new ActiveXObject( "Shell.Application" );
 97         var Folder = Shell.BrowseForFolder(0,Message,0x0040,0x11);//起始目录为:我的电脑
 98         //var Folder = Shell.BrowseForFolder(0,Message,0); //起始目录为:桌面//选择桌面会报错!!
 99 
100         if(Folder != null)
101         {
102             Folder = Folder.items();  // 返回 FolderItems 对象
103             Folder = Folder.item();  // 返回 Folderitem 对象
104             Folder = Folder.Path;   // 返回路径
105             if(Folder.charAt(Folder.length-1) != "\")
106             {
107                  Folder = Folder   "\";
108             }
109             //document.all.savePath.value=Folder;
110             return Folder;
111         }
112     }
113     catch(e)
114     {
115         return "false";
116         alert(e.message);
117     }
118 }

View Code

  接下来,我们要在另一个程序中使用上面生成的模型文件,使用Babylonjs的资源管理器加载网格:

 1     this.loader =  new BABYLON.AssetsManager(this.scene);//资源管理器
 2 
 3     // 资源数组
 4     this.assets = {};
 5     //为资源管理器分配一个任务
 6     var meshTask = this.loader.addMeshTask("gun", "", "./assets/", "gun.babylon");
 7     meshTask.onSuccess = function(task) {//这个任务完成
 8         _this._initMesh(task);
 9     };    
//第一个参数表示task的name,第二个参数表示加载模型文件中的哪个网格,为空则用数组形式加载全部,第三个参数表示路径,第四个参数是文件名
10     var meshTask2 = this.loader.addMeshTask("mesh_ground", "", "./assets/arena/", "2017810_14_12_59testscene.babylon");
11     meshTask2.onSuccess = function(task) {
12         _this._initMesh(task);
13     };
14 
15     this.loader.onFinish = function (tasks)//所有任务完成
16     {
17     。。。
18     }
19     
20     。
21     。
22     。
23     
24     _initMesh : function(task)
25     {
26         this.assets[task.name] = task.loadedMeshes;
27         for (var i=0; i<task.loadedMeshes.length; i   ){
28             var mesh = task.loadedMeshes[i];
29             mesh.isVisible = false;
30             //预先把所有资源加载下来,但不显示,当需要时再把它显示在需要的位置,或者在需要的位置,建立一个资源的实例(克隆)
31         }
32     }
33     

  这时,会发生一个小问题:Babylonjs并不支持我们夹带在mesh中的mydata属性。解决方法是在babylon.30.all.max.js的21272行附近修改:

1             if (parsedMesh.metadata !== undefined) {
2                 mesh.metadata = parsedMesh.metadata;
3             }
4             if (parsedMesh.mydata !== undefined) {
5                 mesh.mydata = parsedMesh.mydata;
6             }    

  仿照metadata的写法加上对mydata的支持,当然,也可以考虑把mydata夹带到其他被mesh所支持的属性里。

   3、使用pathfinding库进行2D寻路

  pathfindingjs是一个开源2D寻路库,可以在

  pathfinding的基本用法如下:

 1 var finder = new PF.AStarFinder({//“寻路器”
 2     diagonalMovement: 3
 3 });
 4 
 5 。
 6 this.grid=new PF.Grid(this.len_x,this.len_y,this.walkabilityMatrix);//生成寻路网格 7 。


 8 
 9 function FindWaytogo(pickResult)//pickResult是Babylonjs中定义的“鼠标选取结果”对象
10 {
11     var faceId=pickResult.faceId;//点击了网格中的第几个面
12     var pickedMesh=pickResult.pickedMesh;//被点击的网格
13     var px=MyGame.player.mesh.position.x;//被控对象在场景中的水平位置
14     var py=MyGame.player.mesh.position.z;
15 
16     var len_x=MyGame.arena.len_x;//寻路网格的格数和每格的长度
17     var len_y=MyGame.arena.len_y;
18     var len_s=MyGame.arena.len_s;
19     if(px>-len_x*len_s/2&&px<len_x*len_s/2&&py>-len_y*len_s/2&&py<len_y*len_s/2&&MyGame.arena.grid)//如果使用了pathfinder的障碍矩阵
20     {
21         var arr_matrix=MyGame.arena.walkabilityMatrix;//寻路矩阵
22         var count=parseInt(faceId/2);//第几个方格
23         //接下来要把网格的面转换为寻路方格的坐标,后面还要把寻路方格的坐标转换为scene中的位置
24         //面数转换为方格坐标
25         var count_y=parseInt(count/len_x);
26         var count_x=count%len_x;
27         //场景坐标转化为方格坐标
28         var count_x0=parseInt(px/len_s len_x/2);
29         var count_y0=parseInt(-py/len_s len_y/2);
30 
31         //寻路,返回一个由方格坐标组成的数组
32         var path = finder.findPath(count_x0,count_y0 , count_x,count_y, MyGame.arena.grid.clone());//这些是寻路网格坐标
33         var len=path.length;
34         for(var i=0;i<len;i  )
35         {//把方格坐标转化为场景坐标
36             var obj=path[i];
37             obj[0]=(obj[0]-len_x/2)*len_s;
38             obj[1]=(-obj[1] len_y/2)*len_s;
39         }
40 
41         path.push([pickResult.pickedPoint.x,pickResult.pickedPoint.z]);
42         MyGame.player.path_goto=path;//在使用时在生成高度
43         MyGame.player.positiontogo=[pickResult.pickedPoint.x,pickResult.pickedPoint.z];
44         path.shift();//把第一个出发节点去掉
45         console.log("生成路径,起点:[" px "," py "],终点:[" pickResult.pickedPoint.x "," pickResult.pickedPoint.z "]");
46     }
47 }

  这样,我们就把场景中的位置对应成了寻路网格中的位置,然后使用pathfinding生成了2D路径。需要注意的是pathfinding中的grid对象只能使用一次,再次寻路时需要重新生成grid或者使用grid的克隆对象。

 

  4、基于cannon.js物理引擎沿路径移动 

  接下来需要让被控物体沿着指定的路径运动,为了能让物体在凹凸不平的地形中运动时保持紧贴地面,我在这里使用了cannonjs物理引擎(关于物理引擎的用法可以参考上一篇文章)。经过试验,这个版本的cannonjs的单个物理仿真器最多支持对10000个顶点的物理仿真,所以前文没法把地面网格分成更多段。

  我们在这个场景中监听“右键点击地面”的事件,代码如下:

 1 canvas.addEventListener("click", function(evt) {
 2             var width = engine.getRenderWidth();
 3             var height = engine.getRenderHeight();
 4             var pickInfo = scene.pick(width/2, height/2, null, false, _this.camera);//点击信息
 5             if(evt.button==2)//右键单击
 6             {
 7                 cancelEvent(evt);//阻止默认响应
 8                 if(pickInfo.hit&&pickInfo.pickedMesh.name=="mesh_ground")//点击到了地面上
 9                 {
10                     MyGame.player.mesh.physicsImpostor.setMass(70);//给被控物体赋予质量,这样它才可以下落
11                     FindWaytogo(pickInfo);//在玩家到点击目的地之间找到一条路径
12                     var mesh_togo=BABYLON.Mesh.CreateBox("box", 1, scene);//目标方块
13                     mesh_togo.position = pickInfo.pickedPoint.clone();//pickResult.pickedPoint
14                     mesh_togo.renderingGroupId=2;
15                     MyGame.player.mesh_togo=mesh_togo;
16                 }
17             }
18 
19 
20 
21         }, false);

  然后在每次渲染之前执行以下运动方法:

 1         scene.registerBeforeRender(function() {
 2             if(MyGame.flag_startr==1)//如果开始渲染了
 3             {
 4                 if(MyGame.flag_view=="first"||MyGame.flag_view=="third")
 5                 {
 6                     physics20170725(MyGame.player);
 7                 }
 8                 if(MyGame.flag_view=="free")
 9                 {
10                     pathgoto20170808(MyGame.player);
11                 }
12             }
13         });

 

 1 function pathgoto20170808(obj)//obj是player
 2 {
 3     if(true)
 4     //if(obj.standonTheGround==1)//站在地面上时考虑将质量设为0?
 5     {
 6         if(obj.path_goto!="sleep"&&obj.path_goto!="lose")
 7         {
 8             var len_x=MyGame.arena.len_x;
 9             var len_y=MyGame.arena.len_y;
10             var len_s=MyGame.arena.len_s;
11             var vl_now=obj.mesh.physicsImpostor.getLinearVelocity();
12 
13             if(obj.path_goto.length>0)
14             {
15                 var px=obj.mesh.position.x;//全是场景坐标!!
16                 var py=obj.mesh.position.z;
17                 var count_x0=px;
18                 var count_y0=py;
19                 var count_x=obj.path_goto[0][0];
20                 var count_y=obj.path_goto[0][1];
21                 var len=obj.path_goto.length;
22                 var count_x2=obj.path_goto[len-1][0];
23                 var count_y2=obj.path_goto[len-1][1];
24                 var y_obj=obj.mesh.position.y;
25                 if((Math.pow(count_x0-count_x2,2) Math.pow(count_y0-count_y2,2))<0.25*len_s*len_s)
26                 {//在移动过程中因未知原因跳到距终点0.5以内距离的地方,直接寻找最终点
27                     console.log("在最终格内");
28                     if((Math.pow(count_x0-count_x2,2) Math.pow(count_y0-count_y2,2))<0.01*len_s*len_s)//到达0.1距离以内的地方,认为到达最终目标,直接定位
29                     {
30 
31                         obj.mesh.position.x=count_x;
32                         obj.mesh.position.z=count_y;
33                         obj.path_goto="sleep";
34                         console.log("到达最终目标:[" obj.mesh.position.x "," obj.mesh.position.y "," obj.mesh.position.z "]");
35                         obj.mesh.physicsImpostor.setMass(0);//质量设为零将不会下落
36                         obj.mesh_togo.dispose();
37                         obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
38                         obj.mesh.physicsImpostor.setLinearVelocity(new BABYLON.Vector3(0,0,0));
39                         
40                     }
41                     else{
42                         var v_temp=new BABYLON.Vector3(count_x2,0,count_y2).subtract(new BABYLON.Vector3(obj.mesh.position.x,0,obj.mesh.position.z)).normalize().scaleInPlace(obj.vm.forward);
43                         v_temp.y=vl_now.y<=0?vl_now.y:0;//这个单位应该脚踏实地的平稳运动
44                         obj.mesh.physicsImpostor.setLinearVelocity(v_temp);
45                         //obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
46                         if(obj.path_goto.length>1)
47                         {
48                             obj.path_goto=[obj.path_goto[len-1]];//只剩一个最终目标
49                         }
50                     }
51                 }
52                 else if((Math.pow(count_x0-count_x,2) Math.pow(count_y0-count_y,2))>4*len_s*len_s)
53                 {//在移动过程中因未知原因跳到距下个目标格2以外距离的地方,需要重新寻路,这种计算可能耗时较大,不能每帧执行!!
54                     obj.path_goto="lose";
55                     //obj.mesh.physicsImpostor.setMass(0);//不掉落
56                     obj.mesh.physicsImpostor.setLinearVelocity(new BABYLON.Vector3(0,0,0));
57                     obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
58                     return false;
59                 }
60                 else if(obj.path_goto.length>1&&(Math.pow(count_x0-count_x,2) Math.pow(count_y0-count_y,2))<0.25*len_s*len_s)
61                 {//距离下一寻路格足够近,切换下一寻路格
62 
63                     obj.path_goto.shift();
64                     count_x=obj.path_goto[0][0];
65                     count_y=obj.path_goto[0][1];
66                     console.log("切换下一个寻路单元格:[" count_x "," count_y "]");
67                     var v_temp=new BABYLON.Vector3(count_x,0,count_y).subtract(new BABYLON.Vector3(obj.mesh.position.x,0,obj.mesh.position.z)).normalize().scaleInPlace(obj.vm.forward);
68                     v_temp.y=vl_now.y<=0?vl_now.y:0;
69                     obj.mesh.physicsImpostor.setLinearVelocity(v_temp);
70                     //obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
71                 }
72                 else//正常向目标寻路格移动
73                 {
74                     console.log("普通寻路");
75                     var v_temp=new BABYLON.Vector3(count_x,0,count_y).subtract(new BABYLON.Vector3(obj.mesh.position.x,0,obj.mesh.position.z)).normalize().scaleInPlace(obj.vm.forward);
76                     v_temp.y=vl_now.y<=0?vl_now.y:0;
77                     obj.mesh.physicsImpostor.setLinearVelocity(v_temp);
78                     //obj.mesh.physicsImpostor.setAngularVelocity(new BABYLON.Vector3(0,0,0));//停下
79                 }
80 
81             }
82         }
83         else
84         {
85             //obj.mesh.physicsImpostor.setMass(0);//不掉落
86         }
87     }
88 
89 
90 
91 }

  这里分几种可能发生的运动情况(最常见的几种)分别设置被控物体的线速度,使得物体平稳的沿着路径运动,当物体到达目标时将进入sleep状态,当物体偏离路径时将进入lose状态,程序每秒钟检查一下物体是否lose,如果lose则重新寻路(没有测试过):

 1 _this.currentframet=new Date().getTime();
 2                 _this.DeltaTime=_this.currentframet-_this.lastframet;//取得两帧之间的时间
 3                 _this.lastframet=_this.currentframet;
 4                 _this.nohurry =_this.DeltaTime;
 5                 if(MyGame&&_this.nohurry>1000)//每一秒进行一次导航修正
 6                 {
 7                     _this.nohurry=0;
 8                     if(_this.player.path_goto=="lose")//发现迷失了路途
 9                     {
10                         console.log("发现迷路,重新规划路径");
11                         var len_x=MyGame.arena.len_x;
12                         var len_y=MyGame.arena.len_y;
13                         var len_s=MyGame.arena.len_s;
14                         //场景坐标转化为方格坐标
15                         var count_x0=parseInt(_this.player.mesh.position.x/len_s len_x/2);
16                         var count_y0=parseInt(-_this.player.mesh.position.z/len_s len_y/2);
17                         var count_x=parseInt(_this.player.positiontogo[0]/len_s len_x/2);
18                         var count_y=parseInt(-_this.player.positiontogo[1]/len_s len_y/2);
19                         var path = finder.findPath(count_x0,count_y0 , count_x,count_y, MyGame.arena.grid.clone());//这些是寻路网格坐标
20                         var len=path.length;
21                         for(var i=0;i<len;i  )
22                         {//把方格坐标转化为场景坐标
23                             var obj=path[i];
24                             obj[0]=(obj[0]-len_x/2)*len_s;
25                             obj[1]=(-obj[1] len_y/2)*len_s;
26                         }
27                         path.push(MyGame.player.positiontogo);
28                         path.shift();//把第一个出发节点去掉
29                         MyGame.player.path_goto=path;//在使用时在生成高度
30                         console.log("生成路径,起点:[" _this.player.mesh.position.x "," _this.player.mesh.position.z "]"  
31                             ",终点:[" _this.player.positiontogo[0] "," _this.player.positiontogo[1] "]");
32                     }
33                 }

 

  这样,我们就成功的完成了在WebGL场景中寻路的目标,接下来可以尝试修改pathfinding使之能根据不同地形进行加权寻路,以及控制多个单位进行寻路行为。

  

  

这篇文章将讨论如何在一个自定义的地面网格上进行简单的2D寻路,以及确定路径后如何使用基于物理引...

前言:

一、工具用法:

Unity自带几种简单的模型,如cube等;一般情况下,其余模型有3D建模软件生成,以合适的文件格式导入unity中;而mesh(以我目前很粗浅的了解)的一般用途就是:对现有的模型进行变形,以达到各种奇幻酷炫的表现效果。

1、访问 

但是由于自己的项目需要,需要由数据(外部解释stl文件获得)按照特定情况以及要求实时地产生各种几何模型,故需要用Mesh进行建模。所以开始研究学习Mesh。

澳门新浦京娱乐场网站 6


在场景世界坐标系的(0,-10,0),(0,0,0),(0,10,0)处各有一个绿色小球作为参考点,使用上下左右和鼠标拖动可以进行场景漫游。

一、引入

2、按F12键打开Chrome控制台,在控制台中输入:MakeRibbon(MakeRing(5,12),-10,2,11,"mesh_ribbon")回车:

3D世界中任何的面都是由三角形绘制完成的,因为任何无规则的集合图形都可以由三角形来组成。比如四边形,无论是正四边形还是无规则四边形都可以由两个三角形拼接而成。如下图,模型上的一个个小网格就是Mesh,这些Mesh有不同的三维顶点(Vector3),共同组成了一个3D模型。

澳门新浦京娱乐场网站 7

澳门新浦京娱乐场网站 8

在场景中绘制了一个半径为5,曲面细分度为12,左端位于-10,每两个圆环间距2,共由11个圆环组成的圆柱面。


拉近查看:

 

澳门新浦京娱乐场网站 9

二、正文

3、输入ShowNormals(mesh_origin)将用红色线段显示每个顶点的法线方向

知识点说明:

澳门新浦京娱乐场网站 10

1.Mesh、MeshFilter、MeshRenderer关系整理

在Unity3D中创建一个Cube,在Inspector可以看到其中含有MeshFilter、MeshRenderer组件。

MeshFilter含有一个Public成员 Mesh。

在Mesh中存储着三维模型的数据:vertices(顶点数据数组Vector3[])、triangles(三角形顶点索引数组,int[])、normals(法线向量数组,Vector3[])、uv(纹理坐标数组,Vector2[])。

输入DeleteMeshes([lines_normal])可以删除所有的法线,输入DeleteMeshes([mesh_origin])则删除圆柱面网格。

2.

 Unity3D中Mesh的基本单位是三角形,故而从最基本最简单的等腰三角形开始画起。

 Mesh是Unity内的一个组件,称为网格组件。

  • Mesh 网格
  • MeshFilter 网格过滤器
  • Mesh Renderer 网格渲染器

Mesh:是指模型的网格,建模就是建网格。细看Mesh,可以知道Mesh的主要属性内容包括顶点坐标法线纹理坐标三角形绘制序列等其他有用属性和功能。因此建网格,就是画三角形;画三角形就是定位三个点。

Mesh Filter:内包含一个Mesh组件,可以根据MeshFilter获得模型网格的组件,也可以为MeshFilter设置Mesh内容。

Mesh Render:是用于把网格渲染出来的组件。MeshFilter的作用就是把Mesh扔给MeshRender将模型或者说是几何体绘制显示出来。 

它们之间的关系大概就是Unity中的对象就是GameObject,每个GameObject都可以有一个MeshFilter组件(也可以没有),该组件又有Mesh属性(这个一定有),而该属性又有顶点坐标,法线等属性。而如果GameObject里有MeshFilter,则必须要Mesh Renderer才能将此网格渲染出来,不然是看不见该网格的。


 

 Mesh的属性

  • 顶点坐标(vertex)
  • 法线(normal)
  • 纹理坐标(uv)
  • 三角形序列(triangle)

顶点坐标:顶点坐标数组存放Mesh的每个顶点的空间坐标,假设某mesh有n个顶点,则vertex的size为n

法线:法线数组存放mesh每个顶点的法线,大小与顶点坐标对应,normal[i]对应顶点vertex[i]的法线

纹理坐标:它定义了图片上每个点的位置的信息. 这些点与3D模型是相互联系的, 以决定表面纹理贴图的位置. UV就是将图像上每一个点精确对应到模型物体的表面. uv[i]对应vertex[i]

三角形序列澳门新浦京娱乐场网站,:每个mesh都由若干个三角形组成,而三角形的三个点就是顶点坐标里的点,三角形的数组的size = 三角形个数 * 3.     说白了:规定绘制的顺序(点连接的顺序)

例如:某mesh有四个顶点0,1,2,3,

V0(1, 1, 0),
V1(-1, 1, 0),
V2(1, -1, 0),
V3(-1, -1, 0)

那么它们可以组成这样的一个网格,

tri[0] = ver[0],ver[3],ver[1],tri[1] = ver[0],ver[2],ver[3],

澳门新浦京娱乐场网站 11

注意:三角形的顶点顺序必须是顺时针,顺时针表示正面,逆时针表示背面,而unity3d在渲染时默认只渲染正面,背面是看不见的。

那么该三角形可以表示为:

tri  = new int[2 * 3]{0, 3, 1,   0, 2, 3};

如何要获取第N个三角形对应的三个顶点坐标,则:v1 = tri[N*3 0], v2 = tri[N*3 1], v3 = tir[N*3 2]


参照的案例(配合上述的知识点)【此事例参照他人】

1.创建一个GameObject并添加MeshFilter以及MeshRender组件,并创建一个“CreateMesh.cs”脚本给它。

2.获取该对象的filter组件,并创建一个mesh给它。

3.为该mesh设置属性,这里先设置顶点,然后将三角形与顶点绑定

using UnityEngine;
using System.Collections;

public class CreateMesh : MonoBehaviour {

    private MeshFilter filter;
    private Mesh mesh;

    // Use this for initialization
    void Start () {
        // 获取GameObject的Filter组件
        filter = GetComponent<MeshFilter>();
        // 并新建一个mesh给它
        mesh = new Mesh();
        filter.mesh = mesh;

        // 初始化网格
        InitMesh();
    }

    // Update is called once per frame
    void Update () {

    }

    /// <summary>
    /// Inits the mesh.
    /// </summary>
    void InitMesh()
    {
        mesh.name = "MyMesh";

        // 为网格创建顶点数组
        Vector3[] vertices = new Vector3[4]{
            new Vector3(1, 1, 0),
            new Vector3(-1, 1, 0),
            new Vector3(1, -1, 0),
            new Vector3(-1, -1, 0)
        };

        mesh.vertices = vertices;

        // 通过顶点为网格创建三角形
        int[] triangles = new int[2 * 3]{
            0, 3, 1,   0, 2, 3
        };

        mesh.triangles = triangles;
    }
}

效果如图:

澳门新浦京娱乐场网站 12

3.网格已经成功生成,接下来该给网格贴图了,在Inspector视图里选中Mesh Render,并拖一个材质给它,

Mesh Render是负责渲染的,将Mesh Filter里的mesh通过自身的Materials渲染出来。

设置完材质后,我们需要将纹理贴图与网格顶点一一对应起来,这样才能渲染出来。

// 为mesh设置纹理贴图坐标
        Vector2[] uv = new Vector2[4]{
            new Vector2(1, 1),
            new Vector2(0, 1),
            new Vector2(1, 0),
            new Vector2(0, 0)
        };

        mesh.uv = uv;

 效果如图:

澳门新浦京娱乐场网站 13

4.mesh还有两个重要的属性,法线和颜色,这两个我不是很懂,暂时没加入,

不过看了下自带的cube模型的mesh,每个顶点的法线好像就是设置为那个顶点所在的面的法线。

不过肯定不是这样的,毕竟要是两个不在同一面的面共有一个顶点,那就不成立了。


 

自己做的案例:(最简单粗暴地显示出一个三角形)

 1 using UnityEngine;
 2 using System.Collections;
 3 [RequireComponent(typeof(MeshRenderer), typeof(MeshFilter))]
 4 public class triangle_mesh : MonoBehaviour
 5 {
 6     /*
 7     creat a triangle by using Mesh 
 8      2016/11/19
 9                  ————Carl    
10     */
11 
12     void Start()
13     {
14         MeshFilter meshfilter = new MeshFilter();
15         meshfilter = GetComponent<MeshFilter>();
16         Mesh mesh = new Mesh();
17         Vector3[] vertices = new Vector3[3];
18         vertices[0] = new Vector3(0, 0, 0);
19         vertices[1] = new Vector3(0, 0, 1);
20         vertices[2] = new Vector3(1, 0, 0);
21         int[] tris = new int[3];
22         tris[0] = 0; tris[1] = 1; tris[2] = 2;
23         Vector2[] uvs = new Vector2[vertices.Length];
24         for (int i = 0; i < uvs.Length; i  )
25         {
26             uvs[i] = Vector2.zero;
27         }
28 
29         mesh.vertices = vertices;
30         mesh.triangles = tris;
31         mesh.uv = uvs;
32         meshfilter.mesh = mesh;
33     }
34 }

效果图:

 澳门新浦京娱乐场网站 14

 

程序说明:

1)

 澳门新浦京娱乐场网站 15

澳门新浦京娱乐场网站 16

澳门新浦京娱乐场网站 17

澳门新浦京娱乐场网站 18

澳门新浦京娱乐场网站 19

澳门新浦京娱乐场网站 20

澳门新浦京娱乐场网站 21

 

2)

RequireComponent这一行,表示我们需要MeshRenderer和MeshFilter这两个组件,当我们将TestTriangle的代码挂在GameObject上的时候,会自动添加这两个组件。而我们要移除MeshRenderer或MeshFilter的时候,编辑器就会提示不能移除。

ExecuteInEditMode表示会在编辑器模式下运行。

uv暂时我们用不到,所以全部设为零。在后面文章中将介绍uv的用法。



总结:

用Unity绘制三角形

首先,creat a gameobject

其次,挂在一个脚本,并且,添加 MeshRenderer,MeshFilter组件

再其次:给出Mesh的: mesh.vertices = vertices;//点 mesh.triangles = tris;//索引点 mesh.uv = uvs;//纹理

最后:meshfilter.mesh = mesh;//添加于MeshFilter组件



 

【欢迎转载】

 转载请表明出处: 乐学习

4、鼠标移入网格上的三角形,会显示三角形的顶点信息:

澳门新浦京娱乐场网站 22

其中“1:2-5”表示这是三角形的第一个顶点,这个顶点位于索引是2的圆环上(第三个圆环),这个顶点在圆环中的索引是5(也就是第六个顶点)。

5、输入PickPoints([[2,5],[3,5],[2,6]],mesh_origin)可以选定这些顶点

澳门新浦京娱乐场网站 23

被选中顶点所影响的所有边框线标示为黄色,这个“选中”只是改变外观而已。

6、输入TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.Translation(0,0,-10))将所选的顶点向z轴负方向移动10,被移动的顶点和前面选中的顶点其实没有关系,其中arr_ij也可以直接用索引数组[[2,5],[3,5],[2,6]]代替。

澳门新浦京娱乐场网站 24

另一类变形可以通过输入:TransVertex(mesh_origin,arr_ij,BABYLON.Matrix.RotationX(Math.PI/2))实现,这可以把被选中的顶点绕X中旋转90度。

输入DeleteMeshes([lines_inpicked])取消被选中的效果,输入ChangeMaterial(mesh_origin,mat_blue)将边框换成蓝色纹理:

澳门新浦京娱乐场网站 25

可以看到变形后的效果,接下来还可以继续选择顶点并变形

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

澳门新浦京娱乐场网站 26

 8、将导出的txt改名为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官方教程中有关于条带构造和变形的文档,可以在这里下载中文翻译

用来建立基础网格的代码如下:

 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不能设置遮罩层次,也不能像鼠标选取一样设置相对于哪一个相机,所以取消了左下角的小视口。

在WebGL场景中使用2DA,使用Chrome控制台进行3D模型编辑的尝试。如果需要添加视口,代码如下:

 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     }

函数的第一个参数是导出后的文件名(不包括扩展名),第二个参数是网格使用的材质对象(暂时只支持简单的颜色材质)。

在WebGL场景中使用2DA,使用Chrome控制台进行3D模型编辑的尝试。其中ExportBabylonMesh是我编写的newland库中的一个方法,这个方法在前面关与3D模型的文章中也有提到过(

在导入模型文件时后者会报错:“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装塑料桶”的网格,可以通过

最终产生的网格如下图:

澳门新浦京娱乐场网站 27

实践证明,对于具有一定排布规律的多顶点网格来说,使用编程方法产生网格比较方便。

 

20180808修改

通过查看文档,发现虽然高级动态纹理在建立时无法指定在哪个相机中进行显示,但他的属性支持多相机设置,需要设置的属性是advancedTexture.layer.layerMask!

 

本文由澳门新浦京娱乐场网站发布于新浦京娱乐场官网,转载请注明出处:在WebGL场景中使用2DA,使用Chrome控制台进行3D模型