WebGLを手軽に使えるThree.js
を使って色々と作っていく過程で得たものを備忘録として残していく記事です。
自分が実際に触ったり、Three.jsに最初からついてるサンプルをいじりながら自分が理解したものなので、 中には誤解していたり勘違いしている情報もあるかも しれないので、参考にする場合は自己責任でお願いします;
まず手始めに、簡単なモデルデータの読み込み、morph animation、影の表示をやってみました。
実際に動いているものはjsdo.itにあげています。
モデルデータはBlenderに最初から用意されている猿?を使用しました。
##Three.jsで影を表示する
Three.jsで影を表示するにはLight
、Renderer
、Mesh
のそれぞれで影を有効にしないとなりません。
今回のサンプルでいうと以下のコード断片です。
###Lightの設定
globalLight = new THREE.DirectionalLight( 0xefefff, 2 );
globalLight.position.set( 1, 1, 1 ).normalize();
globalLight.castShadow = true;
globalLight.shadowMapWidth = 2048;
globalLight.shadowMapHeight = 2048;
var d = 1000;
globalLight.shadowCameraLeft = -d;
globalLight.shadowCameraRight = d;
globalLight.shadowCameraTop = d;
globalLight.shadowCameraBottom = -d;
globalLight.shadowCameraNear = 1;
globalLight.shadowCameraFar = 1000;
globalLight.shadowCameraFov = 40;
//globalLight.shadowCameraVisible = false;
globalLight.shadowBias = 0.0001;
globalLight.shadowDarkness = 0.5;
scene.add(globalLight);
まず平行光源を作成します。(THREE.DirectionalLight
)
ポイントは、shadow
がついているプロパティです。
castShadowをtrueにすることでライトに対して影を生成させることを伝えます。
その他の設定は影がどう落ちるかの設定です。
shadowMap*は(おそらく)影の解像度です。
これを小さくすると影が若干ぼけたようになります。
shadowCamera*は、ライトから見た視点座標の設定でしょう。
(おそらくデプスバッファシャドウ?)
シャドウマッピングについてはこちらの記事(シャドウマッピング | wgld.org)が参考になります。
要は、ライトから見た視点から影の付き具合を計算する方法です。
設定項目については普通のカメラと似たようなものになります。
また、shadowCameraVisible = true
にすると、どこに影が落ちるのか視覚的に確認することができます。(主にデバッグ目的)
これをtrueにした状態で色々値をいじるとどう変化するかが分かるかと思います。
###Meshの設定
mesh.castShadow = true;
mesh.receiveShadow = true;
Meshに対する設定は非常にシンプルです。
そのMeshが影を落とすのか(castShadow
)、影を受けるのか(receiveShadow
)の設定のみです。
ちなみに、castShadowとreceiveShadow双方をtrue
にすると、自分自身にも影が落ちるようになります。(例えば手の影が自分の体に影を落とす、みたいな)
###Rendererの設定
renderer.shadowMapEnabled = true;
rendererへの設定はさらにシンプルです。
shadowMapEnabled = true
を設定するだけです。
ただし、これを有効にした場合、少なくともひとつ以上のライトに対して適切にshadowの設定をしないと画面が真っ暗になってなにもレンダリングされない(というか、多分全部影になってる?)ので注意が必要です。
影を計算させる場合は、必ず上記3つにshadowの設定をしましょう。
##モデルデータの読み込み
今回はTrhee.jsについてくるBlenderのexporterを使って、Blenderで作成したモデルをJSON形式に書き出し、それをJSONLoaderで読み込ませて使用しています。
var loader = new THREE.JSONLoader(true);
loader.load('models/monkey.js', createScene);
まずはJSONLoader
クラスを使って書きだしたJSONを読み込みます。(ファイルの拡張子は.js
)
function createScene(geometry, materials) {
var material;
if (materials) {
for (var i = 0, l = materials.length; i < l; i++) {
materials[i].morphTargets = true;
}
material = new THREE.MeshFaceMaterial(materials);
}
else {
material = new THREE.MeshLambertMaterial( { color: 0x606060, morphTargets: true } )
}
mesh = new THREE.SkinnedMesh(geometry, material);
mesh.scale.set(100, 100, 100);
mesh.position.y += 40;
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
}
JSONLoaderで読み込んだデータをコールバックを使ってThree.jsで使える状態にします。
基本的にはLoaderクラス側で基本的なセットアップをしてくれるので、あとは使用するSceneに合わせて初期化します。
今回の例では全体的にモデルデータが小さいのでスケールを100倍し、さらに影の設定とpositionの設定を加えたあとSceneに追加しています。
3Dレンダリングの基本だと思いますが、シーンに描けるオブジェクトをMesh
と呼び、Mesh
はGeometry
(形状)と、Material
(質感)を持っています。
そのため、Loaderクラスは読み込んだモデルデータのGeometry(形状)と、モデルデータ内で定義されているMaterial(質感)を構築した上でそれらを引数としてコールバックが呼び出されます。
あとはそれを利用してMeshを生成し、シーンに追加すればシーンにBlenderなどで作成したモデルが表示される、というわけです。
##Morph animation
今回はBlenderでちょっとしたアニメーションを作りそれを再生しています。
ここはまだ曖昧な部分でもあるので、こうしたら動いた、というような情報です。
まず、アニメーションを有効にするために以下の処理をします。
for (var i = 0, l = materials.length; i < l; i++) {
materials[i].morphTargets = true;
}
material = new THREE.MeshFaceMaterial(materials);
JSONLoaderのコールバックで指定していた部分です。
materials[i].morphTargets = true
がアニメーションをONにしている部分です。
こうすることで、アニメーションが実行されるようになります。
今回はモーフアニメーションという、頂点位置を補完したものをアニメーションさせる、コマアニメ風のアニメーションです。
アニメーション部分は以下の指定で行います。
var duration = 1000;
var keyframes = 30;
var interpolation = duration / keyframes;
var lastKeyframe = 0;
var currentKeyframe = 0;
var time = Date.now() % duration;
var keyframe = Math.floor( time / interpolation );
if ( keyframe != currentKeyframe ) {
mesh.morphTargetInfluences[ lastKeyframe ] = 0;
mesh.morphTargetInfluences[ currentKeyframe ] = 1;
mesh.morphTargetInfluences[ keyframe ] = 0;
lastKeyframe = currentKeyframe;
currentKeyframe = keyframe;
}
mesh.morphTargetInfluences[keyframe] = ( time % interpolation ) / interpolation;
mesh.morphTargetInfluences[lastKeyframe] = 1 - mesh.morphTargetInfluences[ keyframe ];
duration
やkeyframes
など、アニメーションの基本的な部分を設定します。
(今回は30フレームのアニメーションです)
重要な部分は、 mesh.morphTargetInfluences配列を適切にスイッチしていく という部分です。
ここが曖昧な部分なんですが、morphTargetInfluences
は作成したアニメーションのフレーム数と同じ数のインデックスがあります(今回だと30)。
値を設定している部分を見ると、0
と1
を切り替えていることから、ここで指定されたフレームに紐づく頂点位置を利用してアニメーションしているのでは、と思っています。(ここは引き続き調査予定)
###別のアニメーション方法
今回はmorph animationでアニメーションを行いましたが、これは予めキーフレームごとの頂点位置を補完し、その補完された全頂点データを保存して頂点情報を切り替えながらアニメーションさせる、というものです。
そのため、複雑なモデルやアニメーション自体が長くなると当然、データ量が爆発的に増えていきます。
今回のサンプルのように簡単なアニメーションとかなら問題ないですが、普通はここまでシンプルなモデルはあまりないですよね。
そこで、これ以外のアニメーション方法として「ボーンアニメーション」というものがあります。
これは、モデルにボーン(骨)を設定し、そのボーンの動きだけを保存し、そのボーンの状態に応じて頂点位置を計算する、というものです。
逆にこちらは計算負荷が高くなってしまいますが、最初に読み込むデータ量が少なくなるのがメリットです。
こちらについてはまた別の機会に書きたいと思います。
##Three.jsがデフォルトで提供している定数一覧
THREE.ShaderMaterial
を使うと、Three.jsはデフォルトでいくつかの引数を渡してくれます。それが以下に示す変数群です。
###For vertex shader
変数定義 | 説明 |
---|---|
uniform mat4 modelMatrix | モデル座標系からワールド座標系への変換行列 |
uniform mat4 viewMatrix | ワールド座標系からビュー座標系への変換行列 |
uniform mat4 modelViewMatrix | modelMatrixとviewMatrixを乗算したもの |
uniform mat4 projectionMatrix | 透視変換行列 |
uniform mat3 normalMatrix | 法線をビュー座標系に変換する行列 |
uniform vec3 cameraPosition | ワールド座標系でのカメラ座標 |
attribute vec3 position | 頂点座標 |
attribute vec3 normal | 頂点法線 |
attribute vec2 uv | UV座標 |
###For fragment shader
変数定義 | 説明 |
---|---|
uniform mat4 viewMatrix | ワールド座標系からビュー座標系への変換行列 |
uniform vec3 cameraPosition | ワールド座標系でのカメラ座標 |
##Three.jsのShaderMaterialで使う変数Type
type値 | 値のクラス |
---|---|
i | 数値(int型) |
f | 数値(float型) |
c | THREE.Color |
v2 | THREE.Vector2 |
v3 | THREE.Vector3 |
v4 | THREE.Vector4 |
m4 | THREE.Matrix4 |
t | THREE.Texture |
iv1 | 整数の配列 |
iv | 整数の配列(長さは3の倍数) |
fv1 | 浮動小数点値の配列 |
fv | 浮動小数点値の配列(長さは3の倍数) |
v2v | THREE.Vector2の配列 |
v3v | THREE.Vector3の配列 |
v4v | THREE.Vector4の配列 |
m4v | THREE.Matrix4の配列 |
tv | THREE.Textureの配列 |