【Three.js】shaderでBox(オブジェクト) を移動・回転・拡縮させる【shader基礎】

Shader

shaderを使って、オブジェクトを移動・回転・拡縮させていこうと思います。通常、Meshだったりの移動・回転・拡縮は、position・rotation・scaleを使えば容易に変更可能ですが、shaderを使えば便利な場合もあります。備忘録も兼ねて紹介していこうかと思います。

Demo : https://misora.main.jp/cubeshader001/

1.Cube(BoxGeometry&ShaderMaterial)の作成

 まず最初に、BoxGeometry&ShaderMaterialでCube(Boxオブジェクト)を作ろうと思います。Three.jsのコードになります。shaderMaterialのGLSL部分は、別ファイルにして読み込ませています。

three.js

//
const geometry = new THREE.BoxGeometry( 2, 2, 2 );

//
const material = new THREE.ShaderMaterial({
  uniforms : { 
    positionMoveX: { value: 0.0 },
    positionMoveY: { value: 0.0 },
    positionMoveZ: { value: 0.0 },
    scaleSizeX: { value: 1.0 },
    scaleSizeY: { value: 1.0 },
    scaleSizeZ: { value: 1.0 },
    rotationAngleX: { value: 0.0 },
    rotationAngleY: { value: 0.0 },
    rotationAngleZ: { value: 0.0 },
  },
  vertexShader:Vertex,
 fragmentShader: Fragment,
  vertexColors: true,
  blending: THREE.NormalBlending,
  //side:THREE.DoubleSide,
  wireframe:true,
  transparent:true,
});

const Cube = new THREE.Mesh(geometry, material);
Cube.position.set(0, 0, 0);
scene.add( Cube );

vertex.glsl

//vertex.glsl

uniform float positionMoveX;
uniform float positionMoveY;
uniform float positionMoveZ;
uniform float scaleSizeX;
uniform float scaleSizeY;
uniform float scaleSizeZ;
uniform float rotationAngleX;
uniform float rotationAngleY;
uniform float rotationAngleZ;

varying vec2 vUv;
varying vec3 vColor;

void main() {
  vUv = uv;
  vColor = color;

  //
  vec4 worldPosition = modelMatrix * vec4(position, 1.0) ;
  vec4 mvPosition =  viewMatrix * worldPosition;
  
  gl_Position = projectionMatrix * mvPosition;

}

fragment.glsl

//fragment.glsl

varying vec2 vUv;
varying vec3 vColor;

void main()
{
  //
  vec3 color = vColor;
  // 出力
  gl_FragColor = vec4(color, 1.0);
}

ワイヤーフレームのCube(Box)ができると思います。

2.移動・回転・拡縮

 shaderでの移動・回転・拡縮は、vertex(頂点)側で、Positionの値を変更すれば可能ですね。

行列の知識が必要になります。以下のサイトが端的でわかりやすかったです。高校・大学で学習して忘れている方が大半だと思います。わたしもそうでした。

回転行列、拡大縮小行列、平行移動行列(三次元座標の場合)
二次元座標(X,Y座標)の場合のアフィン変換行列についてはこちらで説明しましたが、今回は三次元座標(X,Y,Z座標)のアフィン変換となります。三次元座標の場合、まず座標軸の定義、回転方向の定義を明確に覚えます。この座標は右手座標系と呼ばれま...

行列の式をvertex.glslに適応させていきましょう。

vertex.glsl

//vertex.glsl

uniform float positionMoveX;
uniform float positionMoveY;
uniform float positionMoveZ;
uniform float scaleSizeX;
uniform float scaleSizeY;
uniform float scaleSizeZ;
uniform float rotationAngleX;
uniform float rotationAngleY;
uniform float rotationAngleZ;

varying vec2 vUv;
varying vec3 vColor;


vec3 rot3D(vec3 p, vec3 axis, float angle){
  return mix( dot(axis, p) * axis, p, cos(angle) )
  + cross(axis, p) * sin(angle);
}

mat2 rot2D(float angle){
  float s = sin(angle);
  float c = cos(angle);
  return mat2(c,-s,s,c);
}


void main() {
  vUv = uv;
  vColor = color;

  //平行移動行列
  mat4 MoveMatrix = mat4(
    1, 0, 0, positionMoveX,
    0, 1, 0, positionMoveY,
    0, 0, 1, positionMoveZ,
    0, 0, 0, 1
  );
  //拡大縮小行列
  mat4 ScaleMatrix = mat4(
    scaleSizeX, 0, 0, 0,
    0, scaleSizeY, 0, 0,
    0, 0, scaleSizeZ, 0,
    0, 0, 0, 1
  );
  //X軸回転
  mat4 rotationMatrixX = mat4(
    1, 0, 0, 0,
    0, cos(rotationAngleX), -sin(rotationAngleX), 0,
    0, sin(rotationAngleX), cos(rotationAngleX), 0,
    0, 0, 0, 1
  );
  //Y軸回転
  mat4 rotationMatrixY = mat4(
    cos(rotationAngleY), 0, sin(rotationAngleY), 0,
    0, 1, 0, 0,
    -sin(rotationAngleY), 0, cos(rotationAngleY), 0,
    0, 0, 0, 1
  );
  //Z軸回転
  mat4 rotationMatrixZ = mat4(
    cos(rotationAngleZ), -sin(rotationAngleZ), 0, 0,
    sin(rotationAngleZ), cos(rotationAngleZ), 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1
  );

  //PostionChange Rotation * position * move * scale 
  vec4 R_Position = rotationMatrixX * rotationMatrixY * rotationMatrixZ * vec4(position, 1.0) * MoveMatrix * ScaleMatrix;
  //
  vec4 worldPosition = modelMatrix * R_Position;
  vec4 mvPosition =  viewMatrix * worldPosition;
  
  gl_Position = projectionMatrix * mvPosition;

}

これを、three.js側で適切な値を代入させて回転させていきます。アニメーションにGSAPを使います。npmでインストールしましょう。

gsap
GSAP is a framework-agnostic JavaScript animation library that turns developers into animation superheroes. Build high-p...

HTMLのボタンをクリックしたら、おのおのが実行するように設定しています。

three.js(移動・回転・拡縮)

import gsap from "gsap";

~

/* HTML側で、document.getElementByIdに対応するタグを作成ください。*/
/* move */
document.getElementById("moveUp-Btn").addEventListener("click", () => function(){ 

  let Hold = 0;

  gsap.to((Cube), 0.4,{ //0.4sのアニメーション
    onStart: function() {},
    onUpdate: function() {
      const  add = 2.0 * this.progress() - Hold; //2.0 移動します。
      Hold = 2.0 * this.progress() ;
      Cube.material.uniforms.positionMoveY.value += add;
    },
    onRepeat: function() {},
    onComplete:function(){},
  });
}());

/* Rotation */
document.getElementById("up-Btn").addEventListener("click", () => function(){ 

  let Hold = 0;
  
  gsap.to(Cube, { 
    duration: 0.4, //0.4sのアニメーション
    onStart: function() {},
    onUpdate: function() {
      const  add = ( Math.PI / 2 ) * this.progress() - Hold; //90度回転します。
      Hold = ( Math.PI / 2 ) * this.progress();
      Cube.material.uniforms.rotationAngleX.value += add;
    },
    onRepeat: function() {},
    onComplete:function(){},
  });
}());

/*Scale*/
document.getElementById("scaleU-Btn").addEventListener("click", () => function(){ 

  let Hold = 0;

  gsap.to((Cube), 0.4,{ //0.4sのアニメーション
    onStart: function() {},
    onUpdate: function() {
      const  add = 1.0 * this.progress() - Hold; //1.0が拡大率
      Hold = 1.0 * this.progress();
      Cube.material.uniforms.scaleSizeX.value += add;
      Cube.material.uniforms.scaleSizeY.value += add;
      Cube.material.uniforms.scaleSizeZ.value += add;
    },
    onRepeat: function() {},
    onComplete:function(){},
  });

}());

HTML側で、document.getElementByIdに対応するタグを作成ください。

これで、0.4sでアニメーションして移動・回転・拡大してくれます。移動ではpositionMoveYをX、Zに変えて方向を調整ください。回転もrotationAngleXをY、Zに変更することで変更できます。おのおので追加していってください。

3.完成!

非常に簡単でしたね!

同じマテリアルなら、適応させたMeshすべて同じ動き(移動・回転・拡縮)をします。使いどこによっては、非常に一体感がある良い動きになりそうです。大量のオブジェクトを動かしたりする案件で、CPUをなるべく使いたくないときに、取り入れてはいかがでしょうか?

ということで、shaderの基礎って感じでしたね。

初音ミク・プログラミングコンテストの際に、作ったのですが出番がありませんでした。備忘録して、また使えるときがあれば使いたい基礎テクニックですね。

Demo : https://misora.main.jp/cubeshader001/

元の位置(Meshでに位置)を動かていないので、カメラの判定で描画されないことがあります。おって修正いたします。

初音ミク「マジカルミライ 2023」 プログラミング・コンテスト
プログラミングの力で創作文化に参加できる!初音ミク「マジカルミライ 2023」プログラミング・コンテストを実施!

タイトルとURLをコピーしました