この記事はThree.jsテクスチャの部分切り抜きや回転・反転・伸縮 の続編になります。Three.jsやWebGLのテクスチャの仕組みは前回に詳しく書いていますので一読してみてください。
マイクラのアニメーションするブロックのテクスチャファイルを見ると、縦にタイル状に並んだテクスチャが使われているのがわかります。
こういった画像を使ってアニメーションする場合、物理的に切り貼りしてテクスチャイメージを動的に読み直したりはせずに、CSSの background-image のoffset のように、見せたい部分だけをずらして表示させるとメモリ軽減になります。
See the Pen Minecraft Smoker Animation by Urushibara (@pneuma01) on CodePen.
[CodePen - Minecraft Smorker Animation](https://codepen.io/pneuma01/pen/RwwQQRW)ここで表示させているのは、実際にMinecraftで使われている models/smoker.json (読込用にBlockbenchで変換)を使って生成した3Dオブジェクトです。
3Dオブジェクトは独自クラス化したコードで作成したものなので省きますが、基本的にはTHREE.BoxGeometryなどと同じ構造だと思ってください。
faceVertexUvs のVector2の値をずらすだけ
見てほしいのは、このアニメーションを実現させている箇所です。
let animate = (mesh) => {
vectorAdd(mesh.geometry.faceVertexUvs[0][10], 16, 48, 0, 16);
vectorAdd(mesh.geometry.faceVertexUvs[0][11], 16, 48, 0, 16);
mesh.geometry.uvsNeedUpdate = true;
}
function vectorAdd(face, width, height, x, y)
{
//Vector2をリアル座標系に変換してから座標を加算する関数
let add = vector => {
let xx = vector.x * width + x;
let yy = (1 - vector.y) * height + y;
return {x: xx, y: yy};
}
//すべての座標をリアル座標になおして加算
let vexs = []; //Vector2の配列
let maxX = 0, maxY = 0;
face.forEach(vector => {
let pos = add(vector)
vexs.push(pos)
//はみ出しチェック用
maxX = Math.max(maxX, pos.x)
maxY = Math.max(maxY, pos.y)
});
//最大幅よりもはみ出してたらマイナスする
let minusX = maxX > width ? width: 0;
let minusY = maxY > height ? height: 0;
//座標系を変換して実際のfaceVertexUvsに代入
for(let i=0; i < face.length; i++){
face[i].x = (vexs[i].x - minusX) / width
face[i].y = 1 - (vexs[i].y - minusY) / height
}
}
少し複雑に見えるかもしれませんが、実際にやっていることはこんな感じです。
- テクスチャ座標系からリアル座標に戻す(実際のテクスチャのWidth/Heightを掛ける)
- 指定されたピクセルを加算
- テクスチャ座標系にもどす(実際のテクスチャのWidth/Heightで割る)
- faceVertexUvsに代入
前回に説明しましたが、テクスチャ座標系の場合はy軸が上下逆になるので注意。
クロスフェード?

追記:
複数のテクスチャの合成は現在(rev120時点)のTHREE.jsが提供する機能では再現できないようです。
これを実現するには Canvasオブジェクトにwebglで2枚の画像を指定した値で合成するfragment shaderを書いて、THREE.CanvasTextureでテクスチャに割り当てる方法が考えられます。
THREE.ShaderMaterialでもfragment shaderを扱えますが、こちらはライティング処理も自力で書く必要があるためテクスチャの合成を行うだけの場合には向いてなさそうです。