9
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

[Three.js] ボーンアニメーションやモーフアニメーションをするメッシュの頂点座標を取得する

Last updated at Posted at 2018-07-01

Stack Overflowで質問したところ、素晴らしい回答をgman氏から頂けたので転載する。
ちなみにStack Overflow上のコードは特段の記述が無い限り、CC BY-SA 3.0でライセンスされる
gman氏はさらに自身のコードをCC0でライセンスすると明言しているので、この記事にライセンス上の問題は特にない。

問題

例えばこの動作サンプルにおける剣士メッシュを構成する各頂点の座標を取得するにはどうすればよいか?

ボーンアニメーションやモーフアニメーションをしないメッシュの頂点座標

まず前提知識として。
こちらは簡単に取得できる。

meshのvertexIndex番目の頂点のローカル座標を取得する
const localPosition = mesh.geometry.vertices[vertexIndex];
meshのvertexIndex番目の頂点のグローバル座標を取得する
mesh.updateMatrixWorld();
const globalPosition = mesh.geometry.vertices[vertexIndex].clone();
globalPosition.applyMatrix4(mesh.matrixWorld);

先の例の剣士メッシュはボーンアニメーション、モーフアニメーションを行っているのでこの方法では頂点座標を取得できない。
ボーンアニメーションやモーフアニメーションの効果が及ぶ前の頂点座標が取得できるのみである。

ボーンアニメーションやモーフアニメーションをするメッシュの頂点座標

そもそもの作りとして、Three.jsはJavaScriptのコード中でGLSLのプログラムを自動生成し、描画処理の大半を自動生成したGLSLに任せている。
ボーンアニメーションやモーフアニメーションをするメッシュの頂点座標を計算する処理もGLSLの中で行われており、JavaScript側で計算結果を取得することは原則できない。

そこでgman氏はGLSL中の頂点座標計算処理をJavaScriptに翻訳した。
なお、あらゆるケースで完璧に動く保証まではしないので、うまく動かないケースに当たったら頑張れということである。

meshの頂点の座標を取得する
// 各種パラメータを最新の状態にしておく
mesh.updateMatrixWorld();
mesh.skeleton.update();

// バッファ
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();

// 頂点座標計算処理
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(mesh.bindMatrix);
  tempSkinned.set(0, 0, 0);

  const skinIndices = mesh.geometry.skinIndices[vndx];
  const skinWeights = mesh.geometry.skinWeights[vndx];

  for (let i = 0; i < 4; ++i) {
    const boneNdx = skinIndices.getComponent(i);
    const weight = skinWeights.getComponent(i);
    tempBoneMatrix.fromArray(mesh.skeleton.boneMatrices, boneNdx * 16);
    temp1.copy(tempSkinnedVertex);
    tempSkinned.add(temp1.applyMatrix4(tempBoneMatrix).multiplyScalar(weight));
  }

  transformed.copy(tempSkinned).applyMatrix4(mesh.bindMatrixInverse);

  // この時点でのtransformedがmeshのvndx番目の頂点のローカル座標

  transformed.applyMatrix4(mesh.matrixWorld);

  // この時点でのtransformedがmeshのvndx番目の頂点のグローバル座標
}

動作サンプル

サンプル

剣士メッシュを構成する各頂点の座標に赤い箱を設置している。
正しく頂点座標を取得できていることが分かる。
ただ、本来GPUでやる処理をCPUでやっているのでカクカクにはなってしまう。

おまけ: 描画に使われているGLSLのコードの取得方法

あるmaterialで描画に使われている頂点シェーダのソースはrenderer.context.getShaderSource(material.program.vertexShader)、ピクセルシェーダのソースはrenderer.context.getShaderSource(material.program.fragmentShader)で見れる模様。

9
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?