前言
gltf骨骼动画分为两部分,一部分是骨骼动画的结构信息,一部分是buffer数据
|asset | |
|accessors|--|
|animations| |
|bufferViews|是一个数组,记录了所有item具体的buffer使用情况
|buffers|buffer的长度 buffer的地址url
gltf |meshes|
|nodes|记录了所有节点信息
|scene|含有几个场景
|scenes|列出所有场景信息
|skins|
|
----------------------------------------------------------------------------------------------
|byteLength |buffer的字节长度
buffers |uri |buffer二进制文件的地址
| ---------- |
buffers文件里存储的是字节流,一个字节一个字节的
这个文件是根据一颗渲染树生成的,这比较简单,就是将各种属性打包成字节流,通过偏移地址和长度来标记他们
--------------------------------------------------------------------------------------------
| buffer | 使用的buffer的索引,在buffer数组中,具体使用那段buffer
bufferViews[]| byteLength|当前使用的buffer的字节长度
| byteOffset|起始地址字节偏移量
|target |在显存中的缓冲类型(顶点缓冲或者索引缓冲)
-------------------------------------------------------------------------------------------
| bufferView | 在bufferViews数组中的索引
| componentType|每一个数据有几个字节组成
| count | 一共有多少个单位数据
accessors[] | type |一个单位数据有多少个数据组成
| max |在所有的数据中最大值
| min |在所有的数据中最小值
-------------------------------------------------------------------------------------------
|name | 网格的名字
meshes[] |primitives | 绘制信息
|------------|
|attributes |顶点属性
primitives |indices |存储的值是属性accessors数组索引 索引属性
|------------|
| JOINTS_0 |存储的值是属性accessors数组索引,存储了骨骼节点
| NORMAL |存储的值是属性accessors数组索引,存储了法线
| POSITION |存储的值是属性accessors数组索引,存储了位置信息
attributes | TANGENT |存储的值是属性accessors数组索引,存储了切线
| TEXCOORD_0|存储的值是属性accessors数组索引,存储了纹理
| WEIGHTS_0 |存储的值是属性accessors数组索引,权重数据
网格有三角形组成,而三角形就是简单的三个顶点组成,所以网格数据可以看做就是一堆顶点数据
顶点数据就包含了上面六种:
JOINTS_0:顶点受到哪些骨骼节点的制约
NORMAL : 顶点的法线
POSITION:顶点的具体位置
TANGENT:顶点的切线
TEXCOORD_0:顶点的uv坐标
WEIGHTS_0:顶点受到骨骼节点制约的权重,这个权重和JOINTS_0数组里的骨骼节点是一一对应的
-------------------------------------------------------------------------------------------
|children[] | 该节点本身含有那些子节点
|name | 该节点的名字
nodes[]--|rotation | 旋转的信息,如果是四位,那就是四元数
|translation | 平移信息
|scale | 缩放信息
|mesh | 网格信息 这里存放的是网格索引,具体查看meshes
|skin | 蒙皮信息,这里存放的是蒙皮索引, 具体查看skins
看到这个nodes,是个迷惑点,这个nodes指的是骨骼节点,虽然它也是一个节点,但它是特殊的节点
我们的蒙皮就是使用骨骼节点的空间矩阵来创建的
看一份源文件中nodes的构造{0,1,2,3,4,5,6,7,8,9,10,11}
继承关系如下
| 0 |
| 1 |
obj| 2 | camera
| 3 | light
| 5 | sun
1 | 4 | 该节点含有skin和mesh信息
| 6 | 骨骼节点
| 7 | 骨骼节点
6 | 8 | 骨骼节点
| 9 | 骨骼节点
9 | 10 | 骨骼节点
10| 11 | 骨骼节点
-----------------------------------------------------------------------------------------
scene |这个文件里一共含有多少个场景
-----------------------------------------------------------------------------------------
scenes[] |name|场景的名字
|nodes[]|场景含有那些节点,存储的值是nodes的索引
scenes它表示一个场景数组,表示含有若干个数组
场景scene是一个根节点,nodes是一个节点数组,他们是插入到场景节点的一级节点
----------------------------------------------------------------------------------------
|inverseBindMatrices|存储的值是属性accessors数组索引
skins[] |joints[] |存储的值是属性nodes数组索引,骨骼节点
|skeleton |
首先skins是一个数组,它代表这一个模型可以有若干个蒙皮,注意这里的皮指的不是uv贴图,它指的是由骨骼节点组成的皮
每一个蒙皮,由若干个骨骼节点组成,每一个骨骼节点根据自身的平移四元数旋转以及缩放这三种数据可以构造一个空间矩阵,
每一个空间矩阵占16个元素,就是蒙皮纹理的一行(RGBA RGBA RGBA RGBA),那若干个骨骼节点就是下面这种
RGBA RGBA RGBA RGBA
RGBA RGBA RGBA RGBA
RGBA RGBA RGBA RGBA
RGBA RGBA RGBA RGBA
......
RGBA RGBA RGBA RGBA
OK,一张蒙皮不就生成了啊,这就是蒙皮,蒙皮就是由骨骼节点的空间坐标系所组成
在上面的参数中,有一个inverseBindMatrices属性,其实它和蒙皮中骨骼是一一对应的,而我们在buffer中存储也是它的值
它是初始骨骼矩阵的逆矩阵,我们每一个骨骼都会在nodes中记录,它有自身的旋转缩放平移,那么就可以动态的生成空间矩阵,
但我们仍然需要一个初始骨骼矩阵的逆矩阵,为啥呢?这个原因就是骨骼节点分布的初始位置不同,你比如说在头位置放置一个
骨骼节点,在同样位置放置一个骨骼矩阵,设这个骨骼节点对头位置影响的权重位1.0,此时头位置距离地面的距离位2,
那这两个矩阵相乘,其实人家骨骼节点动也没动,一下子,头就要距离地面为4了,这显然是不对的,所以我们在放置骨骼节点
的时候,一定要记住当时的位置,因为第一次放置,对周围的顶点是没有任何影响的,只有以后骨骼节点的变换才会影响顶点的位置
,所以我们需要记录第一次骨骼绑定的形态信息,这就是骨骼绑定姿势的逆矩阵,哪些被骨骼节点影响的顶点每一次乘以骨骼矩阵
之前都要乘以这个绑定姿势的逆矩阵,它可以抵消第一次绑定的时候的数据所带来的变化
再举一个形象的例子,假如你老婆告诉你,只要你今后身上多出钱,那就可以留一半在身上,但是截至到今天为止,你身上的钱不作数
,必须全额上缴
矩阵由16个元素组成,每个元素的类型是float,也就是4个字节
---------------------------------------------------------------------------------------
结构信息
属性accessors是一个访问buffer数据的中间件
bufferView:指明了在bufferViews中的索引
componentType:指明了每一个数据有几个字节组成,常见的类型如下
‘5120’: Int8Array, // gl.BYTE
‘5121’: Uint8Array, // gl.UNSIGNED_BYTE
‘5122’: Int16Array, // gl.SHORT
‘5123’: Uint16Array, // gl.UNSIGNED_SHORT
‘5124’: Int32Array, // gl.INT
‘5125’: Uint32Array, // gl.UNSIGNED_INT
‘5126’: Float32Array, // gl.FLOAT
count:指的是一共有多少个单位数据
type:指的是一个单位数据有几个数据组成,常见的类型如下
‘SCALAR’: 1,
‘VEC2’: 2,
‘VEC3’: 3,
‘VEC4’: 4, //
‘MAT2’: 4,
‘MAT3’: 9,
‘MAT4’: 16, //4维矩阵占用16个元素
max:表示最大数据
min:表示最小数据
上面的六个属性,其实就是为了组成一个数组,通过bufferView我们从buffer中取出的数据是一个一个字节的,现在要把它放到一个新的数组中,componentType指明了数组类型,count 乘以 type 指明了数组长度
"accessors" : [
{
"bufferView" : 0,
"componentType" : 5123,
"count" : 11994,
"max" : [
2284
],
"min" : [
0
],
"type" : "SCALAR"
},
{
"bufferView" : 1,
"componentType" : 5126,
"count" : 2285,
"max" : [
2.3011670112609863,
2.933537244796753,
3.3537864685058594
],
"min" : [
-2.3011670112609863,
-0.96541428565979,
-6.518695831298828
],
"type" : "VEC3"
},
......
]
属性bufferViews指明了数据在buffer中的具体位置信息
buffer:指的是在buffer数据数组中的索引,我们取到的 buffer数据是一个数组
byteLength:这段数据的长度
byteOffset:这段数据在指定数据流中的长度
target:要在GPU显存中绑定的数据缓冲类型现在
"bufferViews" : [
{
"buffer" : 0,
"byteLength" : 23988,
"byteOffset" : 0,
"target" : 34963
},
{
"buffer" : 0,
"byteLength" : 27420,
"byteOffset" : 23988,
"target" : 34962
},
......
]
属性buffers指明了buffer数据的地址,我们可以从这里加载到buffer数据
byteLength:字节流长度
uri:字节流地址
"buffers" : [
{
"byteLength" : 193428,
"uri" : "whale.CYCLES.bin"
}
],
属性meshes指明了所有网格数据
primitives:绘制信息
JOINTS_0:存储的值是属性accessors数组索引,存储了骨骼节点,
NORMAL:存储的值是属性accessors数组索引,法线数据
POSITION:存储的值是属性accessors数组索引,位置数据
TANGENT:存储的值是属性accessors数组索引,切线数据
TEXCOORD_0:存储的值是属性accessors数组索引,纹理数据
WEIGHTS_0:存储的值是属性accessors数组索引,权重数据
indices:存储的值是属性accessors数组索引,索引数据
总结:上面的意思就是当前这个网格的顶点有哪些,法线有哪些,切线有哪些,纹理uv有哪些,受哪些骨骼影响,以及每个骨骼对这些顶点的权重是多少
"meshes" : [
{
"name" : "orca",
"primitives" : [
{
"attributes" : {
"JOINTS_0" : 5,
"NORMAL" : 2,
"POSITION" : 1,
"TANGENT" : 3,
"TEXCOORD_0" : 4,
"WEIGHTS_0" : 6
},
"indices" : 0
}
]
}
]
属性skins这个记录的是蒙皮数据,蒙皮指的不是皮肤
inverseBindMatrices:存储的值是属性accessors数组索引
joints:这里存的是骨骼节点索引,他们位于属性nodes中
每一个骨骼节点他有一个唯一的节点空间坐标系4x4
一个蒙皮纹理由若干个骨骼节点组成
当外界修改骨骼节点的平移缩放旋转,那就会去修改骨骼节点的空间坐标系,进而影响到受他影响的顶点
我们在顶点shader中进行空间变换的时候,会加一个空间坐标系skinMatrix,通常情况,一个点我们设置能够影响它的骨骼空间坐标系的数量为4,不能设置太多,设置太多数据计算量都会增多
'mat4 getBoneMatrix(float jointNdx) {' +
'float v = (jointNdx + 0.5) / u_numJoints;' + //算出行
'return mat4(' + //s
'texture2D(u_jointTexture, vec2(((0.5 + 0.0) / 4.), v)),' + //0.125
'texture2D(u_jointTexture, vec2(((0.5 + 1.0) / 4.), v)),' + //0.375
'texture2D(u_jointTexture, vec2(((0.5 + 2.0) / 4.), v)),' + //0.625
'texture2D(u_jointTexture, vec2(((0.5 + 3.0) / 4.), v)));' + //0.875
'}' +
'void main() {' +
'mat4 skinMatrix = getBoneMatrix(a_JOINTS_0[0]) * a_WEIGHTS_0[0] +' +
'getBoneMatrix(a_JOINTS_0[1]) * a_WEIGHTS_0[1] +' +
'getBoneMatrix(a_JOINTS_0[2]) * a_WEIGHTS_0[2] +' +
'getBoneMatrix(a_JOINTS_0[3]) * a_WEIGHTS_0[3];' +
'mat4 world = u_world * skinMatrix;' +
'gl_Position = u_projection * u_view * world * a_POSITION;' +
}
总结:其实它的意思就是骨骼节点来造一张纹理,形如
//一共有6个骨骼矩阵
//0 1 2 3 4 5
//每个顶点受到4个骨骼矩阵的影响
/**
RGBA RGBA RGBA RGBA --矩阵1 16 骨骼节点1
RGBA RGBA RGBA RGBA --矩阵2 16 骨骼节点2
RGBA RGBA RGBA RGBA --矩阵3 16 骨骼节点3
RGBA RGBA RGBA RGBA --矩阵4 16 骨骼节点4
RGBA RGBA RGBA RGBA --矩阵5 16 骨骼节点5
RGBA RGBA RGBA RGBA --矩阵6 16 骨骼节点6
*/
所谓的蒙皮指的就是骨骼纹理
"skins" : [
{
"inverseBindMatrices" : 7,
"joints" : [
6,
7,
8,
9,
10,
11
],
"skeleton" : 1
}
]
属性nodes这个就是记录用到的节点,可以把显示节点看成一个空间坐标系,所谓的节点继承其实不过就是为了完成空间矩阵的相乘,进而达到空间坐标系相互影响的目的,骨骼节点也是一种节点,这是为什么说骨骼动画是影响顶点动画的原因
name:节点的名称
children:这个节点又包含那些子节点,这里存储的是节点的索引
rotation,scale,translation:这三个是当前节点的旋转缩放平移,这个是构成节点空间坐标系的参数,你也可以理解为这是当前节点的空间坐标系的变化信息
mesh:这里存的是属性meshes索引
skin: 这里存的是数学skins索引
"nodes" : [
{
"children" : [
4,
6
],
"name" : "Armature.001",
"rotation" : [
-0.7193397283554077,
0.0,
1.5972550578955585e-16,
0.6946584582328796
],
"scale" : [
2.6016106605529785,
2.6016106605529785,
2.6016106605529785
],
"translation" : [
-2.672288363424831e-17,
-0.12034916877746582,
3.479184865951538
]
},
{
"mesh" : 0,
"name" : "orca",
"rotation" : [
0.7193397283554077,
-9.52618489497488e-24,
-1.5972550578955585e-16,
0.6946584582328796
],
"scale" : [
0.38437727093696594,
0.38437727093696594,
0.38437727093696594
],
"skin" : 0,
"translation" : [
3.278448433886058e-16,
1.3348904848098755,
0.09290271252393723
]
},
......