如何在 Three.js 中获取蒙皮网格顶点的全局位置? [英] How to get the global position of a vertex of a skinned mesh in Three.js?
问题描述
在 Three.js 中,由于
看顶点着色器,着色器的相关部分是
#ifdef USE_SKINNINGmat4 boneMatX = getBoneMatrix( skinIndex.x );mat4 boneMatY = getBoneMatrix( skinIndex.y );mat4 boneMatZ = getBoneMatrix( skinIndex.z );mat4 boneMatW = getBoneMatrix( skinIndex.w );#万一#ifdef USE_SKINNINGmat4 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;#万一vec3 变换法线 = normalMatrix * objectNormal;#ifdef FLIP_SIDED变形法线 = - 变形法线;#万一vec3 变换 = vec3( 位置);#ifdef USE_MORPHTARGETS变形 += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];变形 += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];转换 += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];转换 += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];#ifndef USE_MORPHNORMALS转换 += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];变形 += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];变形 += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];变形 += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];#万一#万一#ifdef USE_SKINNINGvec4 skinVertex = bindMatrix * vec4(transformed, 1.0);vec4 蒙皮 = vec4( 0.0 );skinned += boneMatX * skinVertex * skinWeight.x;skinned += boneMatY * skinVertex * skinWeight.y;skinned += boneMatZ * skinVertex * skinWeight.z;skinned += boneMatW * skinVertex * skinWeight.w;变换 = ( bindMatrixInverse * skinned ).xyz;#万一
所以我想把它翻译成 JavaScript
首先你需要场景拥有它的所有世界矩阵并更新了骨架.
scene.updateMatrixWorld();骨架.更新();
然后
//这些是为了避免分配//在内部循环中.const position = new THREE.Vector3();const 转换 = 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 (让 vndx = 0; vndx
示例:
const scene = new THREE.Scene();const camera = new THREE.PerspectiveCamera(75, 2, 0.1, 200);相机.位置.z = 3;相机.位置.y = 2;相机.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(geometry.vertices的const顶点){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({剥皮:真的,发射:0xffffff,线框:真实,变形目标:真});const mesh = new THREE.SkinnedMesh(geometry, material);const 骨骼 = [new THREE.Bone(), new THREE.Bone()];for (const 骨骼骨骼) {网格.添加(骨骼);}const 骨骼 = 新的 THREE.Skeleton(bones);网格绑定(骨架);骨骼 [0].position.x = -2;骨骼[1].position.x = 2;mesh.morphTargetInfluences[0] = 1;场景.添加(网格);const putMeshesAtSkinnedVertices = (function() {//这些是为了避免进行分配//在内部循环中.const position = new THREE.Vector3();const 转换 = new THREE.Vector3();const temp1 = new THREE.Vector3();const tempBoneMatrix = new THREE.Matrix4();const tempSkinnedVertex = new THREE.Vector3();const tempSkinned = new THREE.Vector3();返回函数 putMarkersAtSkinnedVertices(网格,场景,标记,标记材料,标记){const bindMatrix = mesh.bindMatrix;const bindMatrixInverse = mesh.bindMatrixInverse;const 几何 = 网格几何;const vertices = geometry.vertices;const morphTargets = geometry.morphTargets;const 骨架 = 网状骨架;for (让 vndx = 0; vndx
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/94/three.min.js"></script><canvas></canvas>
我确实用
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?
For example, how can I print (2.5, 1.5, 0.5)
in the following situation?
mesh.geometry.vertices[0]
is originally at (0.5, 0.5, 0.5)
.
Then, bones[1]
moves the vertex to (2.5, 0.5, 0.5)
.
Finally, morphing moves the vertex to (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
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
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.
这篇关于如何在 Three.js 中获取蒙皮网格顶点的全局位置?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!