基于Babylon.js编写简单的骨骼动画生成器

 使用骨骼动画技术可以将网格的顶点分配给若干骨头,通过给骨头设定关键帧和父子关系,可以赋予网格高度动态并具有传递性的变形 效果。这里结合之前的相关研究在网页端使用JavaScript实现了一个简单的骨骼动画编辑和模型生成工具。     一、显示效果: 1、访问https://ljzc002.github.io/Bones/HTML/CstestSpaceCraft2.html查看测试页面:   屏幕右侧的Babylon.js场景中是一个初始网格。 2、在Chrome浏览器控制台输入“ImportMesh("","../ASSETS/SCENE/","SpaceCraft.babylon")”,载入之前编写的一个宇宙飞船模型,关于这个模型的编写方式可以参考https://www.cnblogs.com/ljzc002/p/9473438.html 3、点击“新增骨骼”按钮,会在左侧建立一个可折叠的骨头编辑区(标签的类名是div_flexible),一个编辑区分为六行,每行包括四个文本框。 4、在第一行的四个文本框中输入-1、0、0、0,点击这个编辑区的刷新按钮,将在场景中建立一个朝向(-1,0,0)方向距原点距离为0的平面,所有包含平面正面(或平面上)顶点的线会被标为绿色(“正面”可以理解为从平面出发,沿着平面法线方向移动可以到达这个顶点,数学上可以说“这个顶点到平面的距离为正”)   当顶点的数量较多时,上述计算会花费一定时间,控制台里会打印出当前的查找进度。   在编辑区的第二行输入0、1、0、-3(表示沿法线的反方向到原点的距离为3)会建立另一个平面,同时处于两个平面正面的顶点会被选中,最多可以建立6个这样的区分平面。 5、选定这些顶点作为一号骨头后,点击“编辑关键帧”按钮将打开一号骨头的关键帧编辑对话框   其中父骨骼索引设为0号骨骼,0号骨骼可以理解为模型的原点,在整个动画过程中保持不变;关节点坐标由三个文本框组成,表示这一块骨头和父骨头的连接点的位置,这里一号骨头的关节点设为(0,0,0)。下面的文本框里是表示关键帧矩阵的脚本,解读规则为“帧数@矩阵对象#帧数@矩阵对象”,其中的ms.xx是简写的Babylon.js矩阵构造函数,其对应关系如下:(代码位于CookBones.js文件中) 复制代码 1 //在这里写对关键帧脚本的处理和骨骼模型导出 2 //定义一种简单的脚本简化输入 3 var ms={}//MatrixScript 4 ms.rx=function(rad)//绕x轴旋转 5 { 6 return BABYLON.Matrix.RotationX(rad); 7 } 8 ms.ry=function(rad)//绕y轴旋转 9 { 10 return BABYLON.Matrix.RotationY(rad); 11 } 12 ms.rz=function(rad)//绕z轴旋转 13 { 14 return BABYLON.Matrix.RotationZ(rad); 15 } 16 ms.m1=function(){//生成一个单位阵 17 return BABYLON.Matrix.Identity(); 18 } 19 ms.sc=function(x,y,z)//缩放,因为做了矩阵标准化,在现在的场景里缩放不会起作用!! 20 { 21 return BABYLON.Matrix.Scaling(x,y,z); 22 } 23 ms.tr=function(x,y,z)//位移 24 { 25 return BABYLON.Matrix.Translation(x,y,z); 26 } 27 //0@ms.m1()#120@ms.rx(2)#240@ms.m1() 28 ms.fa=function(arr)//从数组生成矩阵 29 { 30 return BABYLON.Matrix.FromArray(arr); 31 } 32 33 var vs={}//VectorScript 34 vs.tr=function(vec3,matrix)//对向量进行矩阵变化 35 { 36 return BABYLON.Vector3.TransformCoordinates(vec3.clone(),matrix); 37 } 38 var pi=Math.PI; 复制代码   点击“写入初始关键帧”,则关键帧设置被保存,同时编辑区上的复选框会被选中。 6、再给宇宙飞船的翅膀设置骨骼:   设置关键帧   因为是取z值小于等于-6的顶点组成骨头2,所以将关节点位置设为(0,0,-6),当然,也可以把关节点设在其他位置,如过这样做翅膀的运动方式将有所不同。   对称的设置右侧的翅膀,生成骨头3。 7、设置完成后,点击“预览模型”按钮,将在场景的x方向显示骨骼动画效果:   这里体现出当前工具的一个缺点:尚未允许同一顶点绑定多块骨头,对于重复选取的顶点,后设置的骨头会覆盖之前的设置。   因为博客园对图片大小有限制,只能截取骨骼动画的一部分。   点击“导出模型”则可以文本方式导出上述含有骨骼动画的模型。 8、父骨头对子骨头的影响: 可以访问https://ljzc002.github.io/Bones/HTML/Cstest2.html页面,刷新并编辑三块骨头的关键帧可以看到骨骼动画的传递效果。   二、代码实现 1、工程结构: 其中ClickButton.js里是除矩阵计算外所有和按钮响应相关的代码,ComputeMatrix.js里是所有和矩阵计算有关的代码,Flex.js没用。 2、html2D网页绘制(并不是重点)。 html文件:(其中包括建立一个基础Babylon.js场景的js代码) View Code css文件: View Code 控制编辑区展缩的JavaScript代码:(位于ClickButton.js文件中) View Code 3、模型对象的初始化: Babylon.js格式模型的层次结构可以参考https://www.cnblogs.com/ljzc002/p/8927221.html a、在场景中建立一个用来导出3D模型的对象: 建立这个对象的方法在newland.js文件中: 复制代码 1 //返回一个最简单的Babylon.js场景格式 2 newland.CreateObjScene=function() 3 { 4 var obj_scene= 5 { 6 'autoClear': true, 7 'clearColor': [0,0,0], 8 'ambientColor': [0,0,0], 9 'gravity': [0,-9.81,0], 10 'cameras':[], 11 'activeCamera': null, 12 'lights':[], 13 'materials':[], 14 'geometries': {}, 15 'meshes': [], 16 'multiMaterials': [], 17 'shadowGenerators': [], 18 'skeletons': [], 19 'sounds': [] 20 }; 21 return obj_scene; 22 } 复制代码 b、向模型中添加一个网格: 将网格对象的各种属性交给模型对象 复制代码 1 //向场景格式中加入一个网格对象 2 newland.AddMesh2Model=function(obj_scene,mesh,name) 3 { 4 var obj_mesh={}; 5 obj_mesh.name=name?name:mesh.name;//防止在本页面加载导致网格重名 6 obj_mesh.id=name?name:mesh.id; 7 //obj_mesh.materialId=mat.id;//为避免出现重名材质,先不添加这个属性 8 obj_mesh.position=[mesh.position.x,mesh.position.y,mesh.position.z]; 9 obj_mesh.rotation=[mesh.rotation.x,mesh.rotation.y,mesh.rotation.z]; 10 obj_mesh.scaling=[mesh.scaling.x,mesh.scaling.y,mesh.scaling.z]; 11 obj_mesh.isVisible=true; 12 obj_mesh.isEnabled=true; 13 obj_mesh.checkCollisions=false; 14 obj_mesh.billboardMode=0; 15 obj_mesh.receiveShadows=true; 16 obj_mesh.metadata=mesh.metadata; 17 if(mesh.matricesIndices) 18 { 19 obj_mesh.matricesIndices=mesh.matricesIndices; 20 obj_mesh.matricesWeights=mesh.matricesWeights; 21 obj_mesh.skeletonId=mesh.skeletonId; 22 } 23 if(mesh.geometry)//是有实体的网格 24 { 25 var vb=mesh.geometry._vertexBuffers; 26 obj_mesh.positions=newland.BuffertoArray2(vb.position._buffer._data); 27 obj_mesh.normals=newland.BuffertoArray2(vb.normal._buffer._data); 28 obj_mesh.uvs= newland.BuffertoArray2(vb.uv._buffer._data); 29 obj_mesh.indices=newland.BuffertoArray2(mesh.geometry._indices); 30 obj_mesh.subMeshes=[{ 31 'materialIndex': 0, 32 'verticesStart': 0, 33 'verticesCount': mesh.geometry._vertexBuffers.position._buffer._data.length,//mesh.geometry._totalVertices, 34 'indexStart': 0, 35 'indexCount': mesh.geometry._indices.length 36 }]; 37 obj_mesh.parentId=mesh.parent?mesh.parent.id:null; 38 } 39 else 40 { 41 obj_mesh.positions=[]; 42 obj_mesh.normals=[]; 43 obj_mesh.uvs=[]; 44 obj_mesh.indices=[]; 45 obj_mesh.subMeshes=[{ 46 'materialIndex': 0, 47 'verticesStart': 0, 48 'verticesCount': 0, 49 'indexStart': 0, 50 'indexCount': 0 51 }]; 52 obj_mesh.parentId=null; 53 } 54 obj_scene.meshes.push(obj_mesh); 55 } 复制代码 c、向模型中添加骨骼并向骨骼中添加骨头: 复制代码 1 newland.AddSK2Model=function(obj_scene,skname) 2 { 3 var obj_sk={id:obj_scene.skeletons.length,name:skname,bones:[],ranges:[] 4 ,needInitialSkinMatrix:false} 5 obj_scene.skeletons.push(obj_sk); 6 } 7 newland.AddBone2SK=function(obj_scene,i,bone) 8 { 9 obj_scene.skeletons[i].bones.push(bone)//也许应该用splice?? 10 } 复制代码 d、用上述方法初始化网格与模型:(在html文件里) 复制代码 1 //在这里设置一个初始的默认网格, 2 mesh_origin=new BABYLON.MeshBuilder.CreateSphere("mesh_origin",{diameter:8,diameterY:64,segments:16},scene); 3 mesh_origin.material=mat_frame; 4 var vb=mesh_origin.geometry._vertexBuffers; 5 var data_pos=vb.position._buffer._data; 6 var len_pos=data_pos.length; 7 mesh_origin.matricesIndices=newland.repeatArr([0],len_pos/3);//顶点的骨头索引 8 mesh_origin.matricesWeights=newland.repeatArr([1,0,0,0],len_pos/3);//顶点的骨头权重 9 mesh_origin.skeletonId=0; 10 obj_scene=newland.CreateObjScene(); 11 newland.AddMesh2Model(obj_scene,mesh_origin,"mesh_origin2"); 12 newland.AddSK2Model(obj_scene,"sk_test1");//向模型中添加骨骼 13 var bone={ 14 'animation':{ 15 dataType:3, 16 framePerSecond:num_fps, 17 keys:[], 18 loopBehavior:1, 19 name:'_bone'+0+'Animation', 20 property:'_matrix' 21 }, 22 'index':0, 23 'matrix':BABYLON.Matrix.Identity().toArray(), 24 'name':'_bone'+0, 25 'parentBoneIndex':-1 26 }; 27 //bone. 28 newland.ExtendKeys(bone,sum_frame);//初始扩展根骨骼的关键帧,认为根骨骼是一直保持不变的 29 newland.AddBone2SK(obj_scene,0,bone);// 向骨骼中添加骨头 30 arr_bone=obj_scene.skeletons[0].bones; 31 BABYLON.Animation.AllowMatricesInterpolation = true;//动画矩阵插值 复制代码   这里建立了骨头0作为所有骨骼最底层的根骨骼,它保持不变,不参加后面的各项设置。 e、添加一个编辑区(一块骨头):(在ClickButton.js文件中) 复制代码 1 function addBone()//向列表里添加一块骨骼 2 { 3 var container=document.getElementById("div_flexcontainer"); 4 container.appendChild(document.querySelectorAll("#div_hiden .div_flexible")[0].cloneNode(true)); 5 var divs=container.querySelectorAll(".div_flexible"); 6 var len=divs.length; 7 divs[len-1].number=len;//这个属性并不能准确的使用 8 divs[len-1].querySelectorAll(".str_flexlen")[0].innerHTML=len+""; 9 var bone={ 10 'animation':{ 11 dataType:3, 12 framePerSecond:num_fps, 13 keys:[], 14 loopBehavior:1, 15 name:'_bone'+len+'Animation', 16 property:'_matrix' 17 }, 18 'index':len, 19 'matrix':BABYLON.Matrix.Identity().toArray(), 20 'name':'_bone'+len, 21 'parentBoneIndex':0 22 } 23 newland.AddBone2SK(obj_scene,0,bone); 24 } 复制代码 4、导入其他模型 作为模型编辑工具不可能只处理初始模型,使用ImportMesh方法导入其他的Babylon.js模型代替初始模型: 复制代码 1 /* 2 * ImportMesh("","../ASSETS/SCENE/","10.babylon") 3 * ImportMesh("","../ASSETS/SCENE/","SpaceCraft.babylon") 4 * */ 5 function ImportMesh(objname,filepath,filename) 6 { 7 8 BABYLON.SceneLoader.ImportMesh(objname, filepath, filename, scene 9 , function (newMeshes, particleSystems, skeletons) 10 {//载入完成的回调函数 11 newland.ClearMeshinModel(obj_scene); 12 if(mesh_origin&&mesh_origin.dispose) 13 { 14 mesh_origin.dispose(); 15 } 16 mesh_origin=newMeshes[0]; 17 mesh_origin.material=mat_frame; 18 //mesh_origin.layerMask=2; 19 var vb=mesh_origin.geometry._vertexBuffers; 20 var data_pos=vb.position._buffer._data; 21 var len_pos=data_pos.length; 22 mesh_origin.matricesIndices=newland.repeatArr([0],len_pos/3); 23 mesh_origin.matricesWeights=newland.repeatArr([1,0,0,0],len_pos/3); 24 mesh_origin.skeletonId=0; 25 newland.AddMesh2Model(obj_scene,mesh_origin,"mesh_origin2"); 26 } 27 ); 28 } 复制代码 5、骨骼划分: a、点击刷新按钮时根据编辑区的输入建立平面: 复制代码 1 function ClearAllClip()//只清理所有的斜面,不处理突出的顶点 2 { 3 var len=arr_plane.length; 4 for(var i=0;i
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信