WebGLはウェブページに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入門」にあり、ソース部分をコピーペーストするだけで簡単に3D空間が作成できます。
デモ
※画面内をクリックすると、プリンが揺れます
シェーダーの書き方
シェーダーであるGLSL言語はテキストで記述します。JavaScript内には文字列としてプログラムを記載します。バッククオートを使いテンプレート文字列を利用してプログラムを文字列として記述します。
▼ shaderFragment.js
▼ main.js
シェーダーファイルの読み込み
// language=GLSL
export const shaderFragment = `
// バーテックスシェーダーから送られた値
varying vec3 vNormal;
varying vec3 mvPosition;
varying vec2 vUv;
// ・・・以下省略
`:
WebStorm等のIDEであれば、プラグインを追加することでテンプレート文字列内のGLSL言語をシンタックスハイライトがされた状態で扱えます。
バーテックスシェーダー
バーテックスシェーダーでは3D空間上に配置された頂点が画面上のどの位置にあるかを計算します。このシェーダー上では、頂点の情報を参照・変更することが可能です。
プリンを揺らす前の計算
バーテックスシェーダーの必要最低限の計算は以下となります。頂点位置(position
)をモデル行列(modelViewMatrix
)でワールド座標に変換し、ビュー行列(projectionMatrix
)でスクリーン座標に変換します。この計算を基準としてプリンを揺らす処理を記述します。
void main(){
gl_Position = projectionMatrix * modelViewMatrix * position;
}
プリンの揺らし方
頂点位置に当たるposition
を調整します。時間の経過によって揺れを表現しますが、シェーダー内ではコンテンツの経過時間のような外部の変数を保持することができないため、各フレーム毎にその値を送る必要があります。外部から取り込むシェーダー内の共通の値はuniform
という識別子をつけてThree.js経由で値を送ります。今回はframe
(時間の経過)、modelHeight
(3Dモデルの高さ)、swingVec
(揺れの方向)、swingStrength
(揺れの大きさ)をThree.js経由で送っています。
上記図を見ながら揺れの計算を説明します。
【手順1】まず、プリン型の3Dモデルの{x:0,y:0,z:0}
の位置は中央のため底の位置を0として合わせるために modelHeight/2
(3Dモデルの高さの半分)」分ずらします。また、頂点位置の情報を0
〜1.0
の値へ合わせると後ほど計算しやすいので「modelHeihgt」分で割ります。
【手順2】揺れの大きさを決めます。swingStrength
に1.で決めた高さの0〜1.0
で合わせた値で掛けます。そうすると、底部分が0の為どんな値を掛けても0のまま固定され、上部になるほど揺れが大きくなるといった動きを表現できます。
【手順3】揺れの早さの指定を行っています。sin()
関数を使用し、波の形で揺れの形を指定しています。frame
の値が早く大きくなるほど揺れの振動が早くなります。具体的にはsin()
の周期が-1π〜1π
で1周期なので2π
分時間が経つと揺れが一番上まで到達します。図の3では1π
分上に移動しています。
【手順4】3Dモデル内の揺れの個数の計算をしています。揺れの個数とは3Dモデル内で何度振動があるかのことです。positionNormalized(高さ0〜1.0の値) * waveNum(揺れの個数) * PI * 2
と計算しています。この値をsin()
関数に入れているので波の形はsin(0〜2π)
で表せられるカーブの形になっています。waveNum
を1
にし、最後に掛け合わせるstrength
の値を10
と大きめに設定すると下記図5のような形になります。waveNumは大きめの値を入れるとぐにゃぐにゃと揺れて楽しいです。
【手順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モデルへはTHREE.ShaderMaterial
というマテリアルを指定することで自作したシェーダーを適用できます。Three.jsのTHREE.ShaderMaterial
経由でシェーダーのテキスト、uniform
等を指定します。
const 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
}
// 一部抜粋
};
const shaderMaterialPudding = new THREE.ShaderMaterial({
uniforms: uniformsPudding,
// シェーダーを割り当てる
vertexShader: shaderVertex,
fragmentShader: shaderFragment,
});
メッシュ作成時にシェーダーマテリアルを指定します。
const geometry = new THREE.CylinderGeometry(
8.4, // 天盤
11.2, // 底面
11.2, // 高さ
100, // 円柱状の分割
30, // 縦方向の分割
);
const cylinder = new THREE.Mesh(geometry, shaderMaterialPudding);
おわりに
今回の記事ではバーテックスシェーダーで3Dモデルの各頂点の位置をずらすことで揺れを表現してみました。この方法を用いると3Dモデルをさまざまな形に変形させることができます。みなさんも一緒にGLSLを使って表現力アップしてみませんか?
※この記事が公開されたのは9年前ですが、1年前の2023年2月に内容をメンテナンスしています。