提交 889fa9ff 编写于 作者: I Ian Kerr

This commit re-introduces THREE.Skeleton to decouple bone/object transformations

from the transforms that bind a THREE.Skeleton to a THREE.SkinnedMesh.  This
allows us to animate multiple THREE.SkinnedMesh instances with a single
THREE.Skeleton.

Here is a summary of the major changes:

extras/helpers/SkeletonHelper.js
* Fixed code mistakes (missing 'THREE').

objects/Bone.js
* Removed duplicate code that is already in THREE.Object3D.

objects/Skeleton.js
* Not a descendant of THREE.Object3D (previous version was).
* Stores boneMatrices and boneTexture.
* Independent of THREE.SkinnedMesh.

objects/SkinnedMesh.js
* Refactored boneMatrices and boneTexture related code into THREE.Skeleton.
* Added two bind matrix modes: "attached" and "detached".

renderers/WebGLRenderer.js
renderers/webgl/WebGLProgram.js
* Attach bindMatrix and bindMatrixInverse to shaders.

renderers/shaders/ShaderChunk.js
renderers/shaders/ShaderLib.js
* Use bind matrices during skinning.
上级 c18a8b9a
......@@ -2,6 +2,7 @@
* @author Sean Griffin / http://twitter.com/sgrif
* @author Michael Guerrero / http://realitymeltdown.com
* @author mrdoob / http://mrdoob.com/
* @author ikerr / http://verold.com
*/
THREE.SkeletonHelper = function ( object ) {
......@@ -65,9 +66,9 @@ THREE.SkeletonHelper.prototype.update = function () {
var geometry = this.geometry;
var matrixWorldInv = new Matrix4().getInverse( this.root.matrixWorld );
var matrixWorldInv = new THREE.Matrix4().getInverse( this.root.matrixWorld );
var boneMatrix = new Matrix4();
var boneMatrix = new THREE.Matrix4();
var j = 0;
......@@ -81,7 +82,7 @@ THREE.SkeletonHelper.prototype.update = function () {
geometry.vertices[ j ].setFromMatrixPosition( boneMatrix );
boneMatrix.multiplyMatrices( matrixWorldInv, bone.parent.matrixWorld );
geometry.vertices[ j + 1 ].setFromMatrixPosition( parentMatrix );
geometry.vertices[ j + 1 ].setFromMatrixPosition( boneMatrix );
j += 2;
......
/**
* @author mikael emtinger / http://gomo.se/
* @author alteredq / http://alteredqualia.com/
* @author ikerr / http://verold.com
*/
THREE.Bone = function( belongsToSkin ) {
......@@ -17,38 +18,15 @@ THREE.Bone = function( belongsToSkin ) {
THREE.Bone.prototype = Object.create( THREE.Object3D.prototype );
THREE.Bone.prototype.update = function ( forceUpdate ) {
THREE.Bone.prototype.updateMatrixWorld = function ( force ) {
// update local
THREE.Object3D.prototype.updateMatrixWorld.call( this, force );
if ( this.matrixAutoUpdate ) {
// Reset weights to be re-accumulated in the next frame
forceUpdate |= this.updateMatrix();
}
// update skin matrix
if ( forceUpdate || this.matrixWorldNeedsUpdate ) {
this.matrixWorldNeedsUpdate = false;
forceUpdate = true;
// Reset weights to be re-accumulated in the next frame
this.accumulatedRotWeight = 0;
this.accumulatedPosWeight = 0;
this.accumulatedSclWeight = 0;
}
// update children
for ( var i = 0, l = this.children.length; i < l; i ++ ) {
this.children[ i ].update( forceUpdate );
}
this.accumulatedRotWeight = 0;
this.accumulatedPosWeight = 0;
this.accumulatedSclWeight = 0;
};
/**
* @author mikael emtinger / http://gomo.se/
* @author alteredq / http://alteredqualia.com/
* @author michael guerrero / http://realitymeltdown.com
* @author ikerr / http://verold.com
*/
THREE.Skeleton = function ( bones, boneInverses, useVertexTexture ) {
this.useVertexTexture = useVertexTexture !== undefined ? useVertexTexture : true;
this.identityMatrix = new THREE.Matrix4();
// copy the bone array
bones = bones || [];
this.bones = bones.slice( 0 );
// create a bone texture or an array of floats
if ( this.useVertexTexture ) {
// layout (1 matrix = 4 pixels)
// RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
// with 8x8 pixel texture max 16 bones (8 * 8 / 4)
// 16x16 pixel texture max 64 bones (16 * 16 / 4)
// 32x32 pixel texture max 256 bones (32 * 32 / 4)
// 64x64 pixel texture max 1024 bones (64 * 64 / 4)
var size;
if ( this.bones.length > 256 )
size = 64;
else if ( this.bones.length > 64 )
size = 32;
else if ( this.bones.length > 16 )
size = 16;
else
size = 8;
this.boneTextureWidth = size;
this.boneTextureHeight = size;
this.boneMatrices = new Float32Array( this.boneTextureWidth * this.boneTextureHeight * 4 ); // 4 floats per RGBA pixel
this.boneTexture = new THREE.DataTexture( this.boneMatrices, this.boneTextureWidth, this.boneTextureHeight, THREE.RGBAFormat, THREE.FloatType );
this.boneTexture.minFilter = THREE.NearestFilter;
this.boneTexture.magFilter = THREE.NearestFilter;
this.boneTexture.generateMipmaps = false;
this.boneTexture.flipY = false;
} else {
this.boneMatrices = new Float32Array( 16 * this.bones.length );
}
// use the supplied bone inverses or calculate the inverses
if ( boneInverses === undefined ) {
this.calculateInverses();
}
else {
if ( this.bones.length === boneInverses.length ) {
this.boneInverses = boneInverses.slice( 0 );
}
else {
console.warn( 'THREE.Skeleton bonInverses is the wrong length.' );
this.boneInverses = [];
for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) {
this.boneInverses.push( new THREE.Matrix4() );
}
}
}
};
THREE.Skeleton.prototype.calculateInverses = function () {
this.boneInverses = [];
for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) {
var inverse = new THREE.Matrix4();
if ( this.bones[ b ] ) {
inverse.getInverse( this.bones[ b ].matrixWorld );
}
this.boneInverses.push( inverse );
}
};
THREE.Skeleton.prototype.pose = function () {
var bone;
// recover the bind-time world matrices
for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) {
bone = this.bones[ b ];
if ( bone ) {
bone.matrixWorld.getInverse( this.boneInverses[ b ] );
}
}
// compute the local matrices, positions, rotations and scales
for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) {
bone = this.bones[ b ];
if ( bone ) {
if ( bone.parent ) {
bone.matrix.getInverse( bone.parent.matrixWorld );
bone.matrix.multiply( bone.matrixWorld );
}
else {
bone.matrix.copy( bone.matrixWorld );
}
bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
}
}
};
THREE.Skeleton.prototype.update = function () {
var offsetMatrix = new THREE.Matrix4();
// flatten bone matrices to array
for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) {
// compute the offset between the current and the original transform
var matrix = this.bones[ b ] ? this.bones[ b ].matrixWorld : this.identityMatrix;
offsetMatrix.multiplyMatrices( matrix, this.boneInverses[ b ] );
offsetMatrix.flattenToArrayOffset( this.boneMatrices, b * 16 );
}
if ( this.useVertexTexture ) {
this.boneTexture.needsUpdate = true;
}
};
/**
* @author mikael emtinger / http://gomo.se/
* @author alteredq / http://alteredqualia.com/
* @author ikerr / http://verold.com
*/
THREE.SkinnedMesh = function ( geometry, material, useVertexTexture ) {
THREE.Mesh.call( this, geometry, material );
this.useVertexTexture = useVertexTexture;
this.identityMatrix = new THREE.Matrix4();
this.bones = [];
this.boneMatrices = null;
this.bindMode = "attached";
this.bindMatrix = new THREE.Matrix4();
this.bindMatrixInverse = new THREE.Matrix4();
// init bones
// TODO: remove bone creation as there is no reason (other than
// convenience) for THREE.SkinnedMesh to do this.
var bone, gbone, p, q, s;
var bones = [];
if ( this.geometry && this.geometry.bones !== undefined ) {
var bone, gbone, p, q, s;
for ( var b = 0, bl = this.geometry.bones.length; b < bl; ++b ) {
gbone = this.geometry.bones[ b ];
......@@ -34,7 +31,7 @@ THREE.SkinnedMesh = function ( geometry, material, useVertexTexture ) {
q = gbone.rotq;
s = gbone.scl;
bone = new Bone( this );
bone = new THREE.Bone( this );
bones.push( bone );
bone.name = gbone.name;
......@@ -61,160 +58,46 @@ THREE.SkinnedMesh = function ( geometry, material, useVertexTexture ) {
bones[ gbone.parent ].add( bones[ b ] );
}
}
}
this.bind( bones );
};
THREE.SkinnedMesh.prototype = Object.create( THREE.Mesh.prototype );
THREE.SkinnedMesh.prototype.bind = function( bones ) {
bones = bones || [];
this.bones = bones.slice( 0 );
this.boneInverses = undefined;
// update bone matrices and/or texture
this.initBoneMatrices();
// update bone inverses
this.pose();
};
THREE.SkinnedMesh.prototype.initBoneInverses = function () {
this.boneInverses = [];
for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) {
var inverse = new THREE.Matrix4();
} else {
if ( this.bones[ b ] ) {
this.add( bones[ b ] );
inverse.getInverse( this.bones[ b ].matrixWorld );
}
}
inverse.multiply( this.matrixWorld );
this.boneInverses.push( inverse );
}
};
THREE.SkinnedMesh.prototype.initBoneMatrices = function () {
var nBones = this.bones.length;
// clear the old bone texture and float array
this.boneMatrices = null;
this.boneTextureWidth = undefined;
this.boneTextureHeight = undefined;
if ( this.boneTexture ) {
this.boneTexture.dispose();
this.boneTexture = undefined;
}
// create a bone texture or an array of floats
if ( this.useVertexTexture ) {
// layout (1 matrix = 4 pixels)
// RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
// with 8x8 pixel texture max 16 bones (8 * 8 / 4)
// 16x16 pixel texture max 64 bones (16 * 16 / 4)
// 32x32 pixel texture max 256 bones (32 * 32 / 4)
// 64x64 pixel texture max 1024 bones (64 * 64 / 4)
var size;
if ( nBones > 256 )
size = 64;
else if ( nBones > 64 )
size = 32;
else if ( nBones > 16 )
size = 16;
else
size = 8;
this.boneTextureWidth = size;
this.boneTextureHeight = size;
this.boneMatrices = new Float32Array( this.boneTextureWidth * this.boneTextureHeight * 4 ); // 4 floats per RGBA pixel
this.boneTexture = new THREE.DataTexture( this.boneMatrices, this.boneTextureWidth, this.boneTextureHeight, THREE.RGBAFormat, THREE.FloatType );
this.boneTexture.minFilter = THREE.NearestFilter;
this.boneTexture.magFilter = THREE.NearestFilter;
this.boneTexture.generateMipmaps = false;
this.boneTexture.flipY = false;
} else {
this.boneMatrices = new Float32Array( 16 * nBones );
this.normalizeSkinWeights();
}
this.updateMatrixWorld( true );
this.bind( new THREE.Skeleton( bones, undefined, useVertexTexture ) );
};
THREE.SkinnedMesh.prototype.updateBoneMatrices = function () {
var offsetMatrix = new THREE.Matrix4();
var invMatrixWorld = new THREE.Matrix4().getInverse( this.matrixWorld );
// make a snapshot of the bones' rest position
if ( this.boneInverses === undefined ) {
this.initBoneInverses();
}
THREE.SkinnedMesh.prototype = Object.create( THREE.Mesh.prototype );
// flatten bone matrices to array
THREE.SkinnedMesh.prototype.bind = function( skeleton, bindMatrix ) {
for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) {
this.skeleton = skeleton;
// compute the offset between the current and the original transform;
if ( bindMatrix === undefined ) {
var matrix = this.bones[ b ] ? this.bones[ b ].matrixWorld : this.identityMatrix;
this.updateMatrixWorld( true );
offsetMatrix.multiplyMatrices( invMatrixWorld, matrix );
offsetMatrix.multiply( this.boneInverses[ b ] );
offsetMatrix.flattenToArrayOffset( this.boneMatrices, b * 16 );
bindMatrix = this.matrixWorld;
}
if ( this.useVertexTexture ) {
this.boneTexture.needsUpdate = true;
}
this.bindMatrix.copy( bindMatrix );
this.bindMatrixInverse.getInverse( bindMatrix );
};
THREE.SkinnedMesh.prototype.pose = function () {
this.updateMatrixWorld( true );
// force recomputation of bone inverses
this.boneInverses = undefined;
this.updateBoneMatrices();
this.normalizeSkinWeights();
this.skeleton.pose();
};
......@@ -248,7 +131,27 @@ THREE.SkinnedMesh.prototype.normalizeSkinWeights = function () {
};
THREE.SkinnedMesh.prototype.clone = function ( object ) {
THREE.SkinnedMesh.prototype.updateMatrixWorld = function( force ) {
THREE.Mesh.prototype.updateMatrixWorld.call( this, true );
if ( this.bindMode === "attached" ) {
this.bindMatrixInverse.getInverse( this.matrixWorld );
} else if ( this.bindMode === "detached" ) {
this.bindMatrixInverse.getInverse( this.bindMatrix );
} else {
console.warn( 'THREE.SkinnedMesh unreckognized bindMode: ' + this.bindMode );
}
};
THREE.SkinnedMesh.prototype.clone = function( object ) {
if ( object === undefined ) {
......
......@@ -3255,24 +3255,24 @@ THREE.WebGLRenderer = function ( parameters ) {
if ( camera.parent === undefined ) camera.updateMatrixWorld();
// update SkinnedMesh objects
function updateSkinnedMesh( object ) {
// update Skeleton objects
function updateSkeletons( object ) {
if ( object instanceof THREE.SkinnedMesh ) {
object.updateBoneMatrices();
object.skeleton.update();
}
for ( var i = 0, l = object.children.length; i < l; i ++ ) {
updateSkinnedMesh( object.children[ i ] );
updateSkeletons( object.children[ i ] );
}
}
updateSkinnedMesh( scene );
updateSkeletons( scene );
camera.matrixWorldInverse.getInverse( camera.matrixWorld );
......@@ -4127,7 +4127,7 @@ THREE.WebGLRenderer = function ( parameters ) {
skinning: material.skinning,
maxBones: maxBones,
useVertexTexture: _supportsBoneTextures && object && object.useVertexTexture,
useVertexTexture: _supportsBoneTextures && object && object.skeleton && object.skeleton.useVertexTexture,
morphTargets: material.morphTargets,
morphNormals: material.morphNormals,
......@@ -4340,34 +4340,46 @@ THREE.WebGLRenderer = function ( parameters ) {
if ( material.skinning ) {
if ( _supportsBoneTextures && object.useVertexTexture ) {
if ( object.bindMatrix && p_uniforms.bindMatrix !== null ) {
_gl.uniformMatrix4fv( p_uniforms.bindMatrix, false, object.bindMatrix.elements );
}
if ( object.bindMatrixInverse && p_uniforms.bindMatrixInverse !== null ) {
_gl.uniformMatrix4fv( p_uniforms.bindMatrixInverse, false, object.bindMatrixInverse.elements );
}
if ( _supportsBoneTextures && object.skeleton && object.skeleton.useVertexTexture ) {
if ( p_uniforms.boneTexture !== null ) {
var textureUnit = getTextureUnit();
_gl.uniform1i( p_uniforms.boneTexture, textureUnit );
_this.setTexture( object.boneTexture, textureUnit );
_this.setTexture( object.skeleton.boneTexture, textureUnit );
}
if ( p_uniforms.boneTextureWidth !== null ) {
_gl.uniform1i( p_uniforms.boneTextureWidth, object.boneTextureWidth );
_gl.uniform1i( p_uniforms.boneTextureWidth, object.skeleton.boneTextureWidth );
}
if ( p_uniforms.boneTextureHeight !== null ) {
_gl.uniform1i( p_uniforms.boneTextureHeight, object.boneTextureHeight );
_gl.uniform1i( p_uniforms.boneTextureHeight, object.skeleton.boneTextureHeight );
}
} else {
} else if ( object.skeleton && object.skeleton.boneMatrices ) {
if ( p_uniforms.boneGlobalMatrices !== null ) {
_gl.uniformMatrix4fv( p_uniforms.boneGlobalMatrices, false, object.boneMatrices );
_gl.uniformMatrix4fv( p_uniforms.boneGlobalMatrices, false, object.skeleton.boneMatrices );
}
......@@ -6016,7 +6028,7 @@ THREE.WebGLRenderer = function ( parameters ) {
function allocateBones ( object ) {
if ( _supportsBoneTextures && object && object.useVertexTexture ) {
if ( _supportsBoneTextures && object && object.skeleton && object.skeleton.useVertexTexture ) {
return 1024;
......@@ -6037,11 +6049,11 @@ THREE.WebGLRenderer = function ( parameters ) {
if ( object !== undefined && object instanceof THREE.SkinnedMesh ) {
maxBones = Math.min( object.bones.length, maxBones );
maxBones = Math.min( object.skeleton.bones.length, maxBones );
if ( maxBones < object.bones.length ) {
if ( maxBones < object.skeleton.bones.length ) {
console.warn( "WebGLRenderer: too many bones - " + object.bones.length + ", this GPU supports just " + maxBones + " (try OpenGL instead of ANGLE)" );
console.warn( "WebGLRenderer: too many bones - " + object.skeleton.bones.length + ", this GPU supports just " + maxBones + " (try OpenGL instead of ANGLE)" );
}
......
......@@ -1130,6 +1130,9 @@ THREE.ShaderChunk = {
"#ifdef USE_SKINNING",
" uniform mat4 bindMatrix;",
" uniform mat4 bindMatrixInverse;",
" #ifdef BONE_TEXTURE",
" uniform sampler2D boneTexture;",
......@@ -1194,11 +1197,11 @@ THREE.ShaderChunk = {
" #ifdef USE_MORPHTARGETS",
" vec4 skinVertex = vec4( morphed, 1.0 );",
" vec4 skinVertex = bindMatrix * vec4( morphed, 1.0 );",
" #else",
" vec4 skinVertex = vec4( position, 1.0 );",
" vec4 skinVertex = bindMatrix * vec4( position, 1.0 );",
" #endif",
......@@ -1207,6 +1210,7 @@ THREE.ShaderChunk = {
" skinned += boneMatY * skinVertex * skinWeight.y;",
" skinned += boneMatZ * skinVertex * skinWeight.z;",
" skinned += boneMatW * skinVertex * skinWeight.w;",
" skinned = bindMatrixInverse * skinned;",
"#endif"
......@@ -1309,6 +1313,7 @@ THREE.ShaderChunk = {
" skinMatrix += skinWeight.y * boneMatY;",
" skinMatrix += skinWeight.z * boneMatZ;",
" skinMatrix += skinWeight.w * boneMatW;",
" skinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;",
" #ifdef USE_MORPHNORMALS",
......
......@@ -1121,13 +1121,14 @@ THREE.ShaderLib = {
" #ifdef USE_SKINNING",
" vec4 skinVertex = vec4( position, 1.0 );",
" vec4 skinVertex = bindMatrix * vec4( position, 1.0 );",
" vec4 skinned = vec4( 0.0 );",
" skinned += boneMatX * skinVertex * skinWeight.x;",
" skinned += boneMatY * skinVertex * skinWeight.y;",
" skinned += boneMatZ * skinVertex * skinWeight.z;",
" skinned += boneMatW * skinVertex * skinWeight.w;",
" skinned = bindMatrixInverse * skinned;",
" displacedPosition = skinned.xyz;",
......@@ -1143,13 +1144,14 @@ THREE.ShaderLib = {
" #ifdef USE_SKINNING",
" vec4 skinVertex = vec4( position, 1.0 );",
" vec4 skinVertex = bindMatrix * vec4( position, 1.0 );",
" vec4 skinned = vec4( 0.0 );",
" skinned += boneMatX * skinVertex * skinWeight.x;",
" skinned += boneMatY * skinVertex * skinWeight.y;",
" skinned += boneMatZ * skinVertex * skinWeight.z;",
" skinned += boneMatW * skinVertex * skinWeight.w;",
" skinned = bindMatrixInverse * skinned;",
" displacedPosition = skinned.xyz;",
......
......@@ -298,8 +298,7 @@ THREE.WebGLProgram = ( function () {
var identifiers = [
'viewMatrix', 'modelViewMatrix', 'projectionMatrix', 'normalMatrix', 'modelMatrix', 'cameraPosition',
'morphTargetInfluences'
'viewMatrix', 'modelViewMatrix', 'projectionMatrix', 'normalMatrix', 'modelMatrix', 'cameraPosition', 'morphTargetInfluences', 'bindMatrix', 'bindMatrixInverse'
];
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册