基于babylon3D模型研究3D骨骼动画(1)

 3D骨骼动画是实现较为复杂3D场景的重要技术,Babylon.js引擎内置了对骨骼动画的支持,但Babylon.js使用的骨骼动画的模型多是从3DsMax、Blender等3D建模工具转换而来,骨骼动画的具体生成方式被透明化。本文从babylon格式的3D模型文件入手,对骨骼动画数据的生成方式进行具体分析,并尝试建立一个简易的3D骨骼动画生成工具。 一、模型文件分析 我们从Babylon.js官方网站上的一个骨骼动画示例开始分析: (示例地址:https://www.babylonjs-playground.com/frame.html#DMLMIP#1) 下载示例中的3D模型dummy3.babylon文件后,在JSON工具中展开: (使用的JSON工具是bejson在线工具,地址:https://www.bejson.com/jsoneditoronline/) 可以看到,这个模型文件中包含了作者信息、背景色、雾效、物理效果、相机、光照、网格、声音、材质、粒子系统、光晕效果、阴影效果、骨骼、行为响应、额外信息、异步碰撞运算标志等场景信息。(也许称之为“场景文件”更合适) 我们主要关注其中与骨骼动画有关的网格数据和骨骼数据,展开网格数据: 其中,positions保存每个顶点在网格自身坐标系中的位置(数组中的每三个元素对应一个顶点),normals保存每个顶点对应的法线方向,uvs是顶点的纹理坐标,indices是顶点的绘制索引。 matricesIndices中保存每一个顶点属于哪一块骨骼,在这个模型里matricesIndices数组每个元素都是数字索引,但是从Babylon.js的这一段代码可以看出: View Code 数组的一个元素也可以保存四个骨骼索引,并且可以使用扩展模式使一个顶点同时和八块骨骼关联。 matricesWeights数组保存每个顶点默认的四块骨骼对顶点姿态影响的权重。 展开骨骼数据: 可以看出同一个场景文件中可以包含多套不同id的骨骼,通过网格的skeletonId属性可以标示使用哪一套骨骼。网格的ranges属性里保存不同动作对应的动画帧数范围,比如第127帧到148帧动画对应机器人奔跑的动作。 展开一个骨骼元素: 经过试验得知,网格的matricesIndices属性中应该保存bones数组的自然索引,而不是bone的index元素。 bone的parentBoneIndex属性表示这个骨骼的“父骨骼”的索引,parentBoneIndex为-1表示这块骨头没有父骨骼(经过试验,Babylon.js只支持一个parentBoneIndex为-1的“根骨骼”,且根骨骼在骨骼数组中的位置应先于所有其他网格,所以可以添加一个不包含动画变化和顶点关联的“空骨骼”作为唯一的根骨骼,以避免出现多个根骨骼) matrix属性是和骨头关联的顶点在进行了一系列变化之后,最终附加的一个在骨骼当前坐标系中的姿态变化矩阵(?)。 每一块骨骼有一个animation属性保存这个骨骼的动画信息,animation中的dataType=3表示这个动画是对矩阵类型的值进行动态变化,framePerSecond=30表示默认每秒播放30帧,keys里保存了每一个关键帧对应的矩阵值: 在关键帧时和这块骨骼关联的顶点会在骨骼的自身坐标系里进行values矩阵所表示的姿态变化,其父骨骼的姿态变化会和它的姿态变化叠加。 可以看出这个动画的每一个帧都是关键帧,这些数据应该是通过动作捕捉技术获取的。 二、生成并导出骨骼模型: 1、html文件: 复制代码 1 2 3 4 5 最简单元素测试 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
23 24
25
26 27 28 29 30 77 复制代码 引用的文件中stat.js用来生成窗口右上方的帧数显示; Events.js、FileText.js、View.js里有一些导出模型文件时用到的方法,具体说明可以参考http://www.cnblogs.com/ljzc002/p/5511510.html,当然你也可以使用别的方式导出场景文件; bones6_sc.js里是建立3D场景的代码; bones6_br.js是建立渲染循环的代码; arr_bones.js里是一个直接定义的bones数组; ExportBabylonBones.js用来生成babylon格式的JSON数据。 52行到71行使用Babylon.js的gui功能在场景中绘制了一个导出按钮(gui操作可以参考http://www.cnblogs.com/ljzc002/p/7699162.html),点击导出按钮时执行场景文件导出操作,较旧版本的Babylon.js需要额外引用dat.gui.min.js和gui_br.js库来支持gui功能,最新的版本则集成了gui功能。 需要注意的是,如果导出操作中包含对顶点位置的计算,则ExportMesh3方法必须在场景开始正常渲染后执行,否则顶点的矩阵信息可能还没有初始化,计算会发生错误,将这一操作放在gui按钮的响应方法里是一个可行的解决方案,因为gui按钮可以点击时,场景一定已经处于正常渲染的状态了。 2、bones6_br.js 复制代码 1 /** 2 * Created by Administrator on 2017/8/30. 3 */ 4 //在这里做每一帧之前要做的事,特别是控制输入,要放在这里!! 5 //var flag_export=0; 6 function MyBeforeRender() 7 { 8 scene.registerBeforeRender(function() { 9 if(scene.isReady()) 10 { 11 12 } 13 }); 14 engine.runRenderLoop(function () { 15 engine.hideLoadingUI(); 16 if (divFps) { 17 // Fps 18 divFps.innerHTML = engine.getFps().toFixed() + " fps"; 19 } 20 scene.render(); 21 /*if(flag_export==0) 22 { 23 ExportMesh3(arr_mesh); 24 flag_export=1; 25 }*/ 26 }); 27 } 复制代码 渲染循环,没什么特殊的。 3、bones6_sc.js 复制代码 1 /** 2 * Created by Administrator on 2017/8/30. 3 */ 4 var arr_mesh=[]; 5 var createScene = function (engine) { 6 scene = new BABYLON.Scene(engine); 7 camera0 =new BABYLON.FreeCamera("FreeCamera", new BABYLON.Vector3(0, 0, 0), scene); 8 camera0.position=new BABYLON.Vector3(0, 0, -80); 9 camera0.attachControl(canvas, true); 10 light0 = new BABYLON.HemisphericLight("Hemi0", new BABYLON.Vector3(0, 1, 0), scene); 11 12 var mat_frame = new BABYLON.StandardMaterial("mat_frame", scene); 13 mat_frame.wireframe = true; 14 15 //头部 16 var vd_head=new BABYLON.VertexData.CreateSphere({diameter:8,diameterY:64,segments:16}); 17 var data_pos=vd_head.positions; 18 var len =data_pos.length/3; 19 arr_index=[]; 20 for(var i=0;i16) 24 { 25 arr_index.push(2); 26 } 27 else if(posy>0) 28 { 29 arr_index.push(0); 30 } 31 else if(posy>-16) 32 { 33 arr_index.push(1); 34 } 35 else{ 36 arr_index.push(3); 37 } 38 39 } 40 BABYLON.VertexData.ComputeNormals(vd_head.positions, vd_head.indices, vd_head.normals);//计算法线 41 var mesh_head=new BABYLON.Mesh("mesh_head",scene); 42 vd_head.applyToMesh(mesh_head, true); 43 mesh_head.vertexData=vd_head; 44 mesh_head.material=mat_frame; 45 //mesh_head.position.y=58.5; 46 //mesh_head.position.y=0; 47 //mesh_head.index_bone=[0,1,2,3]; 48 //mesh_head.index_parentbone=[-1,1,2,3]; 49 50 51 var mesh_root=new BABYLON.Mesh("mesh_root",scene); 52 arr_mesh.push(mesh_root); 53 mesh_root.index_bone=[-1]; 54 55 mesh_head.parent=mesh_root; 56 arr_mesh.push(mesh_head); 57 58 //ExportMesh3(arr_mesh); 59 60 return scene; 61 }; 复制代码 其中arr_mesh是保存场景中所有网格的数组; 场景中包含一个自由相机、一个半球形光源; mat_frame是一个只显示网格线的材质对象; VertexData是Babylon.js定义的“顶点数据类”,这里建立了一个椭球体顶点数据对象,球的直径是8,Y轴直径是64,曲面细分度是16; 取得顶点数据对象中的顶点位置数据,按照顶点的Y坐标将其分为四类; arr_index对应网格的matricesIndices,这里根据高度的不同将顶点和不同的骨骼关联起来; 接下来用这个顶点数据生成一个网格mesh_head,模仿dummy3.babylon的设置,这里还建立了空网格mesh_root作为mesh_head的父网格。需要注意的是“父网格”和“父骨骼”是两回事,父子网格之间的运动传递和父子骨骼之间的运动传递互不影响。 4、arr_bones.js 复制代码 1 /** 2 * Created by lz on 2018/4/17. 3 */ 4 var vec_temp2=BABYLON.Vector3.TransformCoordinates(new BABYLON.Vector3(0,16,0),BABYLON.Matrix.RotationX(Math.PI/3)) 5 .add(BABYLON.Vector3.TransformCoordinates(new BABYLON.Vector3(0,16,0),BABYLON.Matrix.RotationX(Math.PI/6)).negate()) 6 .negate(); 7 8 var arr_bones1=[ 9 {//根骨骼 10 'animation':{ 11 dataType:3, 12 framePerSecond:30, 13 keys:[{ 14 frame:0, 15 values:BABYLON.Matrix.Identity().toArray() 16 },{ 17 frame:120, 18 values:BABYLON.Matrix.RotationX(Math.PI/6).toArray() 19 },{ 20 frame:240, 21 values:BABYLON.Matrix.Identity().toArray() 22 }], 23 loopBehavior:1, 24 name:'_bone'+0+'Animation', 25 property:'_matrix' 26 }, 27 'index':0, 28 'matrix':BABYLON.Matrix.Identity().toArray(),//首先尝试把每个基本变换矩阵都设为单位阵 29 /*这时同一mesh里的不同骨骼不会叠加运动,整个网格表现为一个整体*/ 30 //'matrix':mesh.parent._worldMatrix.clone().invert().multiply(mesh._worldMatrix.clone()).toArray(),//尝试矩阵变化量 31 //'matrix':mesh._worldMatrix.clone().toArray(), 32 'name':'_bone'+0, 33 'parentBoneIndex':-1//是否要求它的父骨骼必须先出现?根骨骼需要最先出现 34 35 }, 36 { 37 'animation':{ 38 dataType:3, 39 framePerSecond:30, 40 keys:[{ 41 frame:0, 42 values:BABYLON.Matrix.Identity().toArray() 43 },{ 44 frame:120, 45 values:BABYLON.Matrix.RotationX(Math.PI/6).toArray() 46 },{ 47 frame:240, 48 values:BABYLON.Matrix.Identity().toArray() 49 }], 50 loopBehavior:1, 51 name:'_bone'+1+'Animation', 52 property:'_matrix' 53 }, 54 'index':1, 55 //'matrix':BABYLON.Matrix.Identity().multiply(BABYLON.Matrix.Translation(0, 16, 0)).toArray(),//首先尝试把每个基本变换矩阵都设为单位阵 56 'matrix':BABYLON.Matrix.Identity().toArray(), 57 /*这时同一mesh里的不同骨骼不会叠加运动,整个网格表现为一个整体*/ 58 //'matrix':mesh.parent._worldMatrix.clone().invert().multiply(mesh._worldMatrix.clone()).toArray(),//尝试矩阵变化量 59 //'matrix':mesh._worldMatrix.clone().toArray(), 60 'name':'_bone'+1, 61 'parentBoneIndex':0//所谓的根骨骼只能有一个?还是根必须最先出现? 62 63 }, 64 65 {//最上面一节 66 'animation':{ 67 dataType:3, 68 framePerSecond:30, 69 keys:[{ 70 frame:0, 71 values:BABYLON.Matrix.Identity().toArray() 72 },{ 73 frame:120, 74 /*values:BABYLON.Matrix.RotationX(Math.PI/6).multiply( 75 BABYLON.Matrix.Translation(vec_temp2.x,vec_temp2.y,vec_temp2.z) 76 ) 77 .toArray()*/ 78 values:BABYLON.Matrix.RotationX(Math.PI/6).toArray() 79 },{ 80 frame:240, 81 values:BABYLON.Matrix.Identity().toArray() 82 }], 83 loopBehavior:1, 84 name:'_bone'+2+'Animation', 85 property:'_matrix' 86 }, 87 'index':2, 88 'matrix':BABYLON.Matrix.Identity().toArray(),//首先尝试把每个基本变换矩阵都设为单位阵 89 /*这时同一mesh里的不同骨骼不会叠加运动,整个网格表现为一个整体*/ 90 //'matrix':mesh.parent._worldMatrix.clone().invert().multiply(mesh._worldMatrix.clone()).toArray(),//尝试矩阵变化量 91 //'matrix':mesh._worldMatrix.clone().toArray(), 92 'name':'_bone'+2, 93 'parentBoneIndex':0 94 95 }, 96 {//最下面一节 97 'animation':{ 98 dataType:3, 99 framePerSecond:30, 100 keys:[{ 101 frame:0, 102 values:BABYLON.Matrix.Identity().toArray() 103 },{ 104 frame:120, 105 values:BABYLON.Matrix.RotationX(Math.PI/6).toArray() 106 },{ 107 frame:240, 108 values:BABYLON.Matrix.Identity().toArray() 109 }], 110 loopBehavior:1, 111 name:'_bone'+3+'Animation', 112 property:'_matrix' 113 }, 114 'index':3, 115 'matrix':BABYLON.Matrix.Identity().toArray(),//首先尝试把每个基本变换矩阵都设为单位阵 116 /*这时同一mesh里的不同骨骼不会叠加运动,整个网格表现为一个整体*/ 117 //'matrix':mesh.parent._worldMatrix.clone().invert().multiply(mesh._worldMatrix.clone()).toArray(),//尝试矩阵变化量 118 //'matrix':mesh._worldMatrix.clone().toArray(), 119 'name':'_bone'+3, 120 'parentBoneIndex':1 121 122 } 123 ] 复制代码 arr_bones1中定义了4块骨骼,参考bones6_sc.js可知,中间偏上的顶点与根骨骼关联,上面和中间偏下的顶点关联的骨骼以根骨骼为父骨骼,下面的顶点关联的骨骼以中间偏下的骨骼为父骨骼。每个骨骼的动画都是从原始姿态起绕x轴旋转30度再返回原始姿态。 5、ExportBabylonBones.js 复制代码 1 //重复一个小数组若干次,用来形成巨型数组 2 function repeatArr(arr,times) 3 { 4 var arr_result=[]; 5 for(var i=0;i
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信