WebGLはWebページに3D表現を組み込むための技術です。そのWebGLを扱いやすくしたJSライブラリの「Three.js」。Theee.jsとシェーダー言語GLSLを組み合わせてプリンが揺れるデモを作成しました。本記事では、Three.jsでシェーダー言語を利用する手順を解説します。

使用技術について

GLSLとはOpenGL Shading Languageの略でその名の通り、OpenGL(WebGL)で使用できるシェーディング言語です。WebGLではライブラリを使用しない場合は、このシェーディング言語を使用してシェーダー(3D描画のための一連の計算セット)を自力で作成しなければなりません。WebGLのシェーダーは2種類あり、バーテックスシェーダーでは頂点の情報を画面上に反映し、フラグメントシェーダーではピクセル単位での描画を行います。今回はバーテックスシェーダーで頂点の位置をずらしてプリンの揺れを表現します。

Three.jsはJavaScriptで扱える3DのJavaScriptライブラリです。Three.js自体の基本的な使い方自体は公式サイト「Three.js – documentation」にあり、説明自体は英語ですがソース部分をコピーペーストするだけで簡単に3D空間が作成できます。

デモ

※画面内をクリックすると、プリンが揺れます

シェーダーのロード

シェーダーはテキストで記述するためJSに直接記入することが可能です。ですがファイルが分かれているとソースが綺麗に保てるのでメインのJavaScriptファイルとは別にしています。シェーダーの読み込みのために「Shader Loader」というライブラリを使用してみました。また、Shader LoaderはjQueryを必要とするのでjQueryも使用しています。

index.html

ソースコードを参照

main.js シェーダーファイルの読み込み

function preload(){
  // シェーダーファイルのプリロード
  SHADER_LOADER.load(
    // ロード完了後のコールバック関数
    function (data)
    {
      // 'myShader'は'index.html,scriptタグのdata-name'で指定したもの
      // バーテックスシェーダー
      var vs = data.myShader.vertex;
      // フラグメントシェーダー
      var fs = data.myShader.fragment;

      // Three.jsの初期化処理を行う
      setUp(vs, fs);
    }
  );
}

バーテックスシェーダー

バーテックスシェーダーでは3D空間上に配置された頂点が画面上のどの位置にあるかを計算します。このシェーダー上では、頂点の情報を参照・変更することが可能です。

プリンを揺らす前の計算

バーテックスシェーダーの必要最低限の計算は以下となります。頂点位置(position)をモデル行列(modelViewMatrix)でワールド座標に変換し、ビュー行列(projectionMatrix)でスクリーン座標に変換します。この計算を基準としてプリンを揺らす処理を記述していきます。

void main(){
  gl_Position = projectionMatrix * modelViewMatrix * position;
}

プリンの揺らし方

頂点位置に当たるpositionを調整します。時間の経過によって揺れを表現しますが、シェーダー内ではコンテンツの経過時間のような外部の変数を保持することができないため、各フレーム毎にその値を送る必要があります。外部から取り込むシェーダー内の共通の値はuniformという識別子をつけてThree.js経由で値を送ります。今回はframe(時間の経過)、modelHeight(3Dモデルの高さ)、swingVec(揺れの方向)、swingStrength(揺れの大きさ)をThree.js経由で送っています。
141212_プリン説明テキスト
上記図を見ながら揺れの計算を説明していきます。

  1. まず、プリン型の3Dモデルの{x:0,y:0,z:0}の位置は中央のため底の位置を0として合わせるために modelHeight/2(3Dモデルの高さの半分)」分ずらします。また、頂点位置の情報を01.0の値へ合わせると後ほど計算しやすいので 「modelHeihgt」分で割ります。
  2. 揺れの大きさを決めます。swingStrength1.で決めた高さの0〜1.0で合わせた値で掛けます。そうすると、底部分が0の為どんな値を掛けても0のまま固定され、上部になるほど揺れが大きくなるといった動きを表現できます。
  3. 揺れの早さの指定を行っています。sin()関数を使用し、波の形で揺れの形を指定しています。frameの値が早く大きくなるほど揺れの振動が早くなります。具体的にはsin()の周期が-1π〜1πで1周期なので分時間が経つと揺れが一番上まで到達します。図の3では分上に移動しています。
  4. 3Dモデル内の揺れの個数の計算をしています。揺れの個数とは3Dモデル内で何度振動があるかのことです。positionNormalized(高さ0〜1.0の値) * waveNum(揺れの個数) * PI * 2と計算しています。この値をsin()関数に入れているので波の形はsin(0〜2π)で表せられるカーブの形になっています。waveNum1にし最後に掛け合わせるstrengthの値を10と大きめに設定すると下記図5のような形になります。waveNumは大きめの値を入れるとぐにゃぐにゃと揺れて楽しいです。
    141212_プリン揺れの回数
  5. 最後に揺れの方向を決定します。swingVecというvec3形式の値を今までの計算で得た値に掛けあわせます。この方向を変えると揺れる形も変わっていきます。例えばデモで一番最初に落ちてくるときのswingVecは縦方向に揺らすため{x:0,y:1,z:0}になっています。

vertex.js

// Three.jsから送られた値
uniform float frame;
uniform float modelHeight;
uniform vec3 swingVec;
uniform float swingStrength;
void main(){
  //PIは定義されていないようなので自分で定義する
  const float PI = 3.14159265359;
  float waveNum = 2.;
  // 1.位置を0〜1.0の位置に合わせる
  float fit0Position = position.y + modelHeight/2.0;
  float positionNormalized = fit0Position / modelHeight;

  // 2.揺れ幅の調整を行う
  float strength = swingStrength * positionNormalized;
  // 3.揺れの早さ(frame) 4.3Dモデル内の揺れの個数を指定する(positionNormalized * waveNum * PI )
  float wave = sin(frame + positionNormalized * waveNum*PI) * strength; //

  // 5.新しい頂点位置の生成
  vec3 newPosition = position + (swingVec * wave);
  ...

  // 頂点位置の出力
  gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
}

3Dモデルにシェーダーを適用する

3DモデルへはShaderMaterialというマテリアルを指定することで自作したシェーダーを適用することが出来ます。Three.jsのShaderMaterial経由でシェーダーのテキスト、uniform等を指定します。

var uniformsPudding = {
  frame:{
    type: 'f',// float型
    value:0.0
  },
  modelHeight:{
    type:"f", // float型
    value:5
  },
  swingVec: {
    type:"v3", // Vector3型
    value:swingVec
  },
  swingStrength: {
    type:"f", // float型
    value:swingStrength
  }
};
function setUp(vs,fs) {
  // ...
  // ShaderMaterialの作成
  shaderMaterialPudding = new THREE.ShaderMaterial({
    uniforms:uniformsPudding,
    // シェーダーを割り当てる
    fragmentShader: fs,
    vertexShader: vs,
  });

メッシュ作成時にシェーダーマテリアルを指定します。

function createPudding() {
  cylinderGeometry = new THREE.CylinderGeometry(8.4, 11.2, 11.2, 100, 30);
  cylinder = new THREE.Mesh(cylinderGeometry, shaderMaterialPudding);

  cylinder.position.set(0, 21, 0);
  return cylinder;
}

おわりに

今回の記事ではバーテックスシェーダーで3Dモデルの各頂点の位置をずらすことで揺れを表現してみました。この方法を用いると3Dモデルを様々な形に変形させることができます。みなさんも一緒にGLSLを使って表現力アップしてみませんか?