[英]How to get the global position of a vertex of a skinned mesh in Three.js?
In Three.js, we are now able to get the global position of a vertex of a non-skinned mesh thanks to this question , but how can I get the global position of a vertex of a skinned mesh with bones and morph targets? 由于这个问题 ,在Three.js中,我们现在能够获得非蒙皮网格物体的顶点的全局位置,但是如何获得带有骨骼和变形目标的蒙皮网格物体的顶点的全局位置呢?
For example, how can I print (2.5, 1.5, 0.5)
in the following situation? 例如,在以下情况下如何打印
(2.5, 1.5, 0.5)
? mesh.geometry.vertices[0]
is originally at (0.5, 0.5, 0.5)
. mesh.geometry.vertices[0]
最初位于(0.5, 0.5, 0.5)
mesh.geometry.vertices[0]
(0.5, 0.5, 0.5)
。 Then, bones[1]
moves the vertex to (2.5, 0.5, 0.5)
. 然后,
bones[1]
将顶点移动到(2.5, 0.5, 0.5)
。 Finally, morphing moves the vertex to (2.5, 1.5, 0.5)
. 最后,变形将顶点移动到
(2.5, 1.5, 0.5)
。
const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 200); camera.position.z = 3; camera.position.y = 2; camera.lookAt(0, 0, 0); const renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); const geometry = new THREE.BoxGeometry(1, 1, 1); geometry.morphTargets.push({name: "morph", vertices: []}); for (const vertex of geometry.vertices) { geometry.skinIndices.push(new THREE.Vector4(vertex.x < 0 ? 0 : 1, 0, 0, 0)); geometry.skinWeights.push(new THREE.Vector4(1, 0, 0, 0)); geometry.morphTargets[0].vertices.push(vertex.clone().add(new THREE.Vector3(0, 1, 0))); } const material = new THREE.MeshPhongMaterial({ skinning: true, emissive: 0xffffff, wireframe: true, morphTargets: true }); const mesh = new THREE.SkinnedMesh(geometry, material); const bones = [new THREE.Bone(), new THREE.Bone()]; for (const bone of bones) { mesh.add(bone); } const skeleton = new THREE.Skeleton(bones); mesh.bind(skeleton); bones[0].position.x = -2; bones[1].position.x = 2; mesh.morphTargetInfluences[0] = 1; scene.add(mesh); // This code assigns (0.5, 0.5, 0.5) to pos, // but I want to know the code which assigns (2.5, 1.5, 0.5) to pos. const pos = mesh.geometry.vertices[0].clone().applyMatrix4(mesh.matrixWorld); console.log(`(${pos.x}, ${pos.y}, ${pos.z})`); (function render() { requestAnimationFrame(render); renderer.render(scene, camera); })();
body { margin: 0; overflow: hidden; } canvas { width: 100%; height: 100%; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.min.js"></script>
So this is what I did to try to figure this out. 所以这就是我试图找出答案的方法。 I'm too lazy to check if it works for all cases but ...
我懒得检查它是否适用于所有情况,但是...
First I used the Shader Editor extension and then I ran a skinned example and using the shader editor I looked at the generated shader 首先,我使用了Shader Editor扩展 ,然后运行了一个带有皮肤的示例,并使用shader编辑器查看了生成的shader
Looking at the vertex shader the relevant part of the shader is 看一下顶点着色器,着色器的相关部分是
#ifdef USE_SKINNING
mat4 boneMatX = getBoneMatrix( skinIndex.x );
mat4 boneMatY = getBoneMatrix( skinIndex.y );
mat4 boneMatZ = getBoneMatrix( skinIndex.z );
mat4 boneMatW = getBoneMatrix( skinIndex.w );
#endif
#ifdef USE_SKINNING
mat4 skinMatrix = mat4( 0.0 );
skinMatrix += skinWeight.x * boneMatX;
skinMatrix += skinWeight.y * boneMatY;
skinMatrix += skinWeight.z * boneMatZ;
skinMatrix += skinWeight.w * boneMatW;
skinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;
objectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;
#endif
vec3 transformedNormal = normalMatrix * objectNormal;
#ifdef FLIP_SIDED
transformedNormal = - transformedNormal;
#endif
vec3 transformed = vec3( position );
#ifdef USE_MORPHTARGETS
transformed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];
transformed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];
transformed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];
transformed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];
#ifndef USE_MORPHNORMALS
transformed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];
transformed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];
transformed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];
transformed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];
#endif
#endif
#ifdef USE_SKINNING
vec4 skinVertex = bindMatrix * vec4( transformed, 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;
transformed = ( bindMatrixInverse * skinned ).xyz;
#endif
So translating that into JavaScript here's what I came up with 所以将其翻译成JavaScript,这就是我想到的
First off you need the scene to have all its world matrices and the skeleton updated. 首先,您需要对场景进行所有世界矩阵和骨架更新。
scene.updateMatrixWorld();
skeleton.update();
Then 然后
// These are so we can avoid doing allocations
// in the inner loop.
const position = new THREE.Vector3();
const transformed = new THREE.Vector3();
const temp1 = new THREE.Vector3();
const tempBoneMatrix = new THREE.Matrix4();
const tempSkinnedVertex = new THREE.Vector3();
const tempSkinned = new THREE.Vector3();
const bindMatrix = mesh.bindMatrix;
const bindMatrixInverse = mesh.bindMatrixInverse;
for (let vndx = 0; vndx < mesh.geometry.vertices.length; ++vndx) {
position.copy(mesh.geometry.vertices[vndx]);
transformed.copy(position);
for (let i = 0; i < mesh.geometry.morphTargets.length; ++i) {
temp1.copy(mesh.geometry.morphTargets[i].vertices[vndx]);
transformed.add(temp1.sub(position).multiplyScalar(mesh.morphTargetInfluences[i]));
}
tempSkinnedVertex.copy(transformed).applyMatrix4(bindMatrix);
tempSkinned.set(0, 0, 0);
const skinIndices = geometry.skinIndices[vndx];
const skinWeights = geometry.skinWeights[vndx];
for (let i = 0; i < 4; ++i) {
const boneNdx = skinIndices.getComponent(i);
const weight = skinWeights.getComponent(i);
tempBoneMatrix.fromArray(skeleton.boneMatrices, boneNdx * 16);
temp1.copy(tempSkinnedVertex);
tempSkinned.add(temp1.applyMatrix4(tempBoneMatrix).multiplyScalar(weight));
}
transformed.copy(tempSkinned).applyMatrix4(bindMatrixInverse);
transformed.applyMatrix4(mesh.matrixWorld);
Example: 例:
const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, 2, 0.1, 200); camera.position.z = 3; camera.position.y = 2; camera.lookAt(0, 0, 0); const renderer = new THREE.WebGLRenderer({canvas:document.querySelector('canvas')}); renderer.setSize(512, 256, false); document.body.appendChild(renderer.domElement); const geometry = new THREE.BoxGeometry(1, 1, 1); geometry.morphTargets.push({name: "morph", vertices: []}); for (const vertex of geometry.vertices) { geometry.skinIndices.push(new THREE.Vector4(vertex.x < 0 ? 0 : 1, 0, 0, 0)); geometry.skinWeights.push(new THREE.Vector4(1, 0, 0, 0)); geometry.morphTargets[0].vertices.push(vertex.clone().add(new THREE.Vector3(0, 1, 0))); } const material = new THREE.MeshPhongMaterial({ skinning: true, emissive: 0xffffff, wireframe: true, morphTargets: true }); const mesh = new THREE.SkinnedMesh(geometry, material); const bones = [new THREE.Bone(), new THREE.Bone()]; for (const bone of bones) { mesh.add(bone); } const skeleton = new THREE.Skeleton(bones); mesh.bind(skeleton); bones[0].position.x = -2; bones[1].position.x = 2; mesh.morphTargetInfluences[0] = 1; scene.add(mesh); const putMeshesAtSkinnedVertices = (function() { // These are so we can avoid doing allocations // in the inner loop. const position = new THREE.Vector3(); const transformed = new THREE.Vector3(); const temp1 = new THREE.Vector3(); const tempBoneMatrix = new THREE.Matrix4(); const tempSkinnedVertex = new THREE.Vector3(); const tempSkinned = new THREE.Vector3(); return function putMarkersAtSkinnedVertices(mesh, scene, marker, markerMaterial, markers) { const bindMatrix = mesh.bindMatrix; const bindMatrixInverse = mesh.bindMatrixInverse; const geometry = mesh.geometry; const vertices = geometry.vertices; const morphTargets = geometry.morphTargets; const skeleton = mesh.skeleton; for (let vndx = 0; vndx < vertices.length; ++vndx) { position.copy(vertices[vndx]); transformed.copy(position); for (let i = 0; i < morphTargets.length; ++i) { temp1.copy(morphTargets[i].vertices[vndx]); transformed.add(temp1.sub(position).multiplyScalar(mesh.morphTargetInfluences[i])); } tempSkinnedVertex.copy(transformed).applyMatrix4(bindMatrix); tempSkinned.set(0, 0, 0); const skinIndices = geometry.skinIndices[vndx]; const skinWeights = geometry.skinWeights[vndx]; for (let i = 0; i < 4; ++i) { const boneNdx = skinIndices.getComponent(i); const weight = skinWeights.getComponent(i); tempBoneMatrix.fromArray(skeleton.boneMatrices, boneNdx * 16); temp1.copy(tempSkinnedVertex); tempSkinned.add(temp1.applyMatrix4(tempBoneMatrix).multiplyScalar(weight)); } transformed.copy(tempSkinned).applyMatrix4(bindMatrixInverse); transformed.applyMatrix4(mesh.matrixWorld); // create them the first time this is called let markerMesh = markers[vndx]; if (!markerMesh) { markerMesh = new THREE.Mesh(marker, markerMaterial); markers[vndx] = markerMesh; scene.add(markerMesh); } markerMesh.position.copy(transformed); } } }()); // Make sure all matrices are up to date scene.updateMatrixWorld(); skeleton.update(); const marker = new THREE.BoxBufferGeometry(0.1, 0.1, 0.1); const markerMaterial = new THREE.MeshBasicMaterial({color: 0xFF0000}); const markers = []; putMeshesAtSkinnedVertices(mesh, scene, marker, markerMaterial, markers); (function render() { // requestAnimationFrame(render); renderer.render(scene, camera); })();
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.min.js"></script> <canvas></canvas>
I did test it with this sample and it seemed to work. 我确实使用此样本对其进行了测试,并且似乎可以正常工作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.