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

内容预览:
  • 网格的ranges属性里保存不同动作对应的动画帧数范围,比如第127帧到148...~
  • bone的parentBoneIndex属性表示这个骨骼的“父骨骼”的索引,parentBoneIn...~
  • 2、bones6_br.js 1 /** 2 * Created by Administrator on 2017/8/30. 3 ...~

  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的这一段代码可以看出:

 1                 if (parsedGeometry.matricesIndices) {
2 if (!parsedGeometry.matricesIndices._isExpanded) {
3 var floatIndices = [];
4 for (var i = 0; i < parsedGeometry.matricesIndices.length; i++) {
5 var matricesIndex = parsedGeometry.matricesIndices[i];
6 floatIndices.push(matricesIndex & 0x000000FF);
7 floatIndices.push((matricesIndex & 0x0000FF00) >> 8);
8 floatIndices.push((matricesIndex & 0x00FF0000) >> 16);
9 floatIndices.push(matricesIndex >> 24);
10 }
11 mesh.setVerticesData(BABYLON.VertexBuffer.MatricesIndicesKind, floatIndices, parsedGeometry.matricesIndices._updatable);
12 }
13 else {
14 delete parsedGeometry.matricesIndices._isExpanded;
15 mesh.setVerticesData(BABYLON.VertexBuffer.MatricesIndicesKind, parsedGeometry.matricesIndices, parsedGeometry.matricesIndices._updatable);
16 }
17 }
18 if (parsedGeometry.matricesIndicesExtra) {
19 if (!parsedGeometry.matricesIndicesExtra._isExpanded) {
20 var floatIndices = [];
21 for (var i = 0; i < parsedGeometry.matricesIndicesExtra.length; i++) {
22 var matricesIndex = parsedGeometry.matricesIndicesExtra[i];
23 floatIndices.push(matricesIndex & 0x000000FF);
24 floatIndices.push((matricesIndex & 0x0000FF00) >> 8);
25 floatIndices.push((matricesIndex & 0x00FF0000) >> 16);
26 floatIndices.push(matricesIndex >> 24);
27 }
28 mesh.setVerticesData(BABYLON.VertexBuffer.MatricesIndicesExtraKind, floatIndices, parsedGeometry.matricesIndicesExtra._updatable);
29 }
30 else {
31 delete parsedGeometry.matricesIndices._isExpanded;
32 mesh.setVerticesData(BABYLON.VertexBuffer.MatricesIndicesExtraKind, parsedGeometry.matricesIndicesExtra, parsedGeometry.matricesIndicesExtra._updatable);
33 }
34 }

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 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>最简单元素测试</title>
6 <link href="../../CSS/newland.css" rel="stylesheet">
7 <link href="../../CSS/cannon_cloth.css" rel="stylesheet">
8 <script src="../../JS/LIB/jquery-1.11.3.min.js"></script>
9 <script src="../../JS/LIB/stat.js"></script>
10 <script src="../../JS/LIB/babylon.32.all.max.js"></script><!--这个是3.1-->
11 <!--script src="../../JS/LIB/numeric-1.2.6.min.js"></script-->
12 <script src="../../JS/MYLIB/Events.js"></script>
13 <script src="../../JS/MYLIB/FileText.js"></script>
14 <script src="../../JS/MYLIB/View.js"></script>
15 <script src="bones6_sc.js"></script>
16 <script src="bones6_br.js"></script>
17 <script src="arr_bones.js"></script>
18 <script src="ExportBabylonBones.js"></script>
19 <!--script src="../../JS/MYLIB/exportbabylon.js"></script-->
20 </head>
21 <body>
22 <div id="all_base" style="">
23 <canvas id="renderCanvas"></canvas>
24 <div id="fps" style="z-index: 301;"></div>
25 </div>
26 </body>
27
28 <!--script src="../../JS/LIB/dat.gui.min.js"></script-->
29 <!--script src="gui_br.js"></script-->
30 <script>
31 var canvas,engine,scene,gl;
32 canvas = document.getElementById("renderCanvas");
33 engine = new BABYLON.Engine(canvas, true);
34 BABYLON.SceneLoader.ShowLoadingScreen = false;
35 engine.displayLoadingUI();
36 var divFps = document.getElementById("fps");
37 //全局对象
38 var light0//全局光源
39 ,camera0//主相机
40 ;
41
42 window.onload=webGLStart;
43 window.addEventListener("resize", function () {
44 engine.resize();
45 });
46
47 function webGLStart()
48 {
49 //alert("放置断点!");
50 gl=engine._gl;
51 createScene();
52 var advancedTexture = BABYLON.GUI.AdvancedDynamicTexture.CreateFullscreenUI("UI");
53 var UiPanel = new BABYLON.GUI.StackPanel();
54 UiPanel.width = "220px";
55 UiPanel.fontSize = "14px";
56 UiPanel.horizontalAlignment = BABYLON.GUI.Control.HORIZONTAL_ALIGNMENT_RIGHT;
57 UiPanel.verticalAlignment = BABYLON.GUI.Control.VERTICAL_ALIGNMENT_CENTER;
58 advancedTexture.addControl(UiPanel);
59 // ..
60 var button = BABYLON.GUI.Button.CreateSimpleButton("but1", "Play Idle");
61 button.paddingTop = "10px";
62 button.width = "100px";
63 button.height = "50px";
64 button.color = "white";
65 button.background = "green";
66 button.onPointerDownObservable.add(function(state,info,coordinates) {
67 if (state) {
68 ExportMesh3(arr_mesh);
69 }
70 });
71 UiPanel.addControl(button);
72
73 MyBeforeRender();
74 //ExportMesh3(arr_mesh);//这时导出可能还没有进行矩阵计算!!
75 }
76 </script>
77 </html>

引用的文件中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();

17 var data_pos=vd_head.positions;
18 var len =data_pos.length/3;
19 arr_index=[];
20 for(var i=0;i<len;i++)
21 {
22 var posy=data_pos[i*3+1];
23 if(posy>16)
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<times;i++)
6 {
7 arr_result=arr_result.concat(arr.concat());
8 }
9 return arr_result;
10 }
11 function ExportMesh3(arr_mesh)
12 {
13 obj_scene=
14 {
15 'autoClear': true,
16 'clearColor': [0,0,0],
17 'ambientColor': [0,0,0],
18 'gravity': [0,-9.81,0],
19 'cameras':[],
20 'activeCamera': null,
21 'lights':[],
22 'materials':[{
23 'name': 'mat_frame',
24 'id': 'mat_frame',
25 'ambient': [1,1,1],
26 'diffuse': [1,1,1],
27 'specular': [1,1,1],
28 'specularPower': 50,
29 'emissive': [0,0,0],
30 'alpha': 1,
31 'backFaceCulling': true,
32 'diffuseTexture': { },
33 'wireframe':true
34 }],
35 'geometries': {},
36 'meshes': [],
37 'multiMaterials': [],
38 'shadowGenerators': [],
39 'skeletons': [],

40 ‘sounds’: [],
41 ‘metadata’:{‘walkabilityMatrix’:[]}
42
43 }
44 var len=arr_mesh.length;
45 //推入每一个网格
46 for(var i=0;i<len;i++)
47 {
48 var obj_mesh={};
49 var mesh=arr_mesh[i];
50
51 obj_mesh.name=mesh.name;
52 obj_mesh.id=mesh.id;
53 obj_mesh.materialId=’mat_frame’;
54 obj_mesh.position=[mesh.position.x,mesh.position.y,mesh.position.z];
55 obj_mesh.rotation=[mesh.rotation.x,mesh.rotation.y,mesh.rotation.z];
56 obj_mesh.scaling=[mesh.scaling.x,mesh.scaling.y,mesh.scaling.z];
57 obj_mesh.isVisible=true;
58 obj_mesh.isEnabled=true;
59 obj_mesh.checkCollisions=false;
60 obj_mesh.billboardMode=0;
61 obj_mesh.receiveShadows=true;
62 if(mesh.geometry)//是有实体的网格
63 {
64 var vb=mesh.geometry._vertexBuffers;
65 obj_mesh.positions=vb.position._buffer._data;
66 obj_mesh.normals=vb.normal._buffer._data;
67 obj_mesh.uvs= vb.uv._buffer._data;
68 obj_mesh.indices=mesh.geometry._indices;
69 obj_mesh.subMeshes=[{
70 ‘materialIndex’: 0,
71 ‘verticesStart’: 0,
72 ‘verticesCount’: vb.position._buffer._data.length,
73 ‘indexStart’: 0,
74 ‘indexCount’: mesh.geometry._indices.length
75 }];
76 //这里简单规定每个网格区块对应一块骨骼
77 //假设所有的区块都绑定到同一个网格上!
78 //obj_mesh.matricesIndices=repeatArr([mesh.index_bone],vb.position._buffer._data.length/3);
79 obj_mesh.matricesIndices=arr_index;//repeatArr([mesh.index_bone],vb.position._buffer._data.length/3);
80 obj_mesh.matricesWeights=repeatArr([1,0,0,0],vb.position._buffer._data.length/3);
81 obj_mesh.metadata={‘rate_depart’:0.5};
82 //InsertBone2(mesh);
83
84 }
85 else
86 {
87 obj_mesh.positions=[];
88 obj_mesh.normals=[];
89 obj_mesh.uvs=[];
90 obj_mesh.indices=[];
91 obj_mesh.subMeshes=[{
92 ‘materialIndex’: 0,
93 ‘verticesStart’: 0,
94 ‘verticesCount’: 0,
95 ‘indexStart’: 0,
96 ‘indexCount’: 0
97 }];
98 obj_mesh.matricesIndices=[];
99 obj_mesh.matricesWeights=[];
100 obj_mesh.metadata={‘rate_depart’:0};
101 }
102
103 if(!mesh.parent)//如果是最顶层元素
104 {
105 obj_mesh.parentId=null;
106 obj_mesh.skeletonId=-1;
107 }
108 else
109 {
110 obj_mesh.parentId=mesh.parent.id;
111 obj_mesh.skeletonId=0;
112 }
113 obj_scene.meshes.push(obj_mesh);
114 }
115 obj_scene.skeletons[0].bones=arr_bones1;//装填事先预制的骨骼系统
116 var str_data=JSON.stringify(obj_scene);
117 DownloadText(MakeDateStr()+”testscene”,str_data,”.babylon”);
118 }

以上就是:基于babylon3D模型研究3D骨骼动画(1) 的全部内容。

本站部分内容来源于互联网和用户投稿,如有侵权请联系我们删除,谢谢。
Email:[email protected]


0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论