連載
» 2012年10月04日 00時00分 公開

Webグラフィックをハックする(5):多彩な表現力のWebGLを扱いやすくする「Three.js」 (4/5)

[伊藤千光,WebOS Goodies]

特殊効果

 まるでコンソールゲーム機のような、派手な特殊効果もWebGLが得意とするところです。もちろんThree.jsにも、そうした特殊効果をサポートする機能が搭載されています。ここでは、それらの中から代表的なものを4つ解説します。

  • パーティクルシステム
  • フォグ
  • レンズフレア
  • ポストプロセス

 これらの特殊効果を取り入れることで、よりインパクトが強く見栄えの良い映像表現ができます。いずれも簡単に使えるので、ぜひ活用してください。

パーティクルシステム

 パーティクルシステムは、細かいスプライトを大量に表示して各種効果を実現します。WebGLは表示処理のほとんどを高速なGPUで実行するので、他のAPIでは不可能なほど膨大な数のパーティクルがいとも簡単に表示できます。

 Three.jsでパーティクルを表示するには、Geometryインスタンスのverticesプロパティに全パーティクルの座標を格納した上で、専用の物体(ParticleSystem)とマテリアル(ParticleBasicMaterial)を組み合わせます。こうすることで、個々の頂点がパーティクルとして扱われます。以下に例を示します。

// 形状データを作成
var geometry = new THREE.Geometry();
var numParticles = 200000;
for(var i = 0 ; i < numParticles ; i++) {
  geometry.vertices.push(new THREE.Vector3(
    Math.random() * 2000 - 1000,
    Math.random() * 2000 - 1000,
    Math.random() * 2000 - 1000));
}
 
// マテリアルを作成
var texture =THREE.ImageUtils.loadTexture('images/particle1.png');
var material = new THREE.ParticleBasicMaterial({
  size: 10, color: 0xff8888, blending: THREE.AdditiveBlending,
  transparent: true, depthTest: false, map: texture });
 
// 物体を作成
var mesh = new THREE.ParticleSystem(geometry, material);
mesh.position = new THREE.Vector3(0, 0, -1200);
mesh.sortParticles = false;
scene.add(mesh);
サンプルの実行結果 サンプルの実行結果 実際のサンプルを見る

 パーティクルを多数描画するポイントは、マテリアルを加算合成(blending:THREE.AdditiveBlending)に設定し、ParticleSystemのsortParticlesプロパティをfalseにすることです。これでCPUによるソートがスキップされ、GPUの性能がフルに発揮できます。

 逆に、パーティクルを通常の半透明合成(アルファブレンディング)で描画すると、JavaScriptでのソート処理(sortParticles=true)がボトルネックとなり、パーティクル数が大きく制限されます。CPUの性能にもよりますが、筆者の環境では2万個を超えるあたりからフレーム落ちが発生し始めました。

フォグ

 視点から遠くにある物体を特定の色に近づけることで、シーン全体に霞が掛かっているような効果を実現するのが「フォグ」機能です。Three.jsでは、シーンのfogプロパティにFog、もしくはFogExp2インスタンスを設定し、マテリアルのfogパラメータをtrueにすることで利用できます。上のパーティクルのサンプルにフォグをかけるコードを以下に示します。

// マテリアルを作成
var texture =THREE.ImageUtils.loadTexture('images/particle1.png');
var material = new THREE.ParticleBasicMaterial({
  size: 10, color: 0xff8888, blending: THREE.AdditiveBlending,
  transparent: true, depthTest: false, map: texture, fog: true });
scene.fog = new THREE.FogExp2(0x0000ff, 0.00035);
サンプルの実行結果 サンプルの実行結果 実際のサンプルを見る

 効果がわかりやすいように、奥に行くほどパーティクルが青くなるようにしています。通常は、背景色に近づくように設定し、霧の中に溶け込んでいくような効果を出します。

 Fog、およびFogExp2のコンストラクタの書式は以下になります。

Fog、FogExp2コンストラクタ Fog、FogExp2コンストラクタ

 いずれも距離が離れるに従って物体の色がcolorで指定した色に近付いていきます。Fogの場合はnearに指定した距離で効果が出始め、線形に変化してfarの距離で完全にcolorの色になります。FogExp2は距離0から指数的に色が変化していき、densityの値が大きいほど変化が急峻になります。

 FogExp2の方が現実に即した効果になりますが、制御が難しいという欠点があります。効果の出始めと終わりを明確に指定したい時はFogを使うと良いでしょう。

レンズフレア

 カメラの視野に明るい光源が入った時に発生する現象が「レンズフレア」です。ゲームなどでもよく使われていますね。Three.jsにはレンズフレアを再現するクラスが実装されており、この効果を簡単に再現できます。以下にレンズフレアを描画するコードを示します。

// レンズフレアを遮る球体を生成
var mesh = new THREE.Object3D();
mesh.position = new THREE.Vector3(0, 0, - 60);
scene.add(mesh);
 
var geometry = new THREE.SphereGeometry(1, 16, 8);
var material = new THREE.MeshPhongMaterial({
  color: 0xffffff, specular: 0xcccccc, shininess:50, ambient: 0xffffff,
  map: THREE.ImageUtils.loadTexture('images/earth.jpg') });
for(var i = 0 ; i < 1000 ; i++) {
  var sphere = new THREE.Mesh(geometry, material);
  sphere.position = new THREE.Vector3(
    Math.random() * 100 - 50,
    Math.random() * 100 - 50,
    Math.random() * 100 - 50);
  mesh.add(sphere);
}
 
// レンズフレアを作成
var flare = new THREE.LensFlare(
  THREE.ImageUtils.loadTexture('images/lensflare1.png'),
  300, 0, THREE.AdditiveBlending);
flare.position = new THREE.Vector3(-7, 7, -100);
scene.add(flare);
 
var ghostTex = THREE.ImageUtils.loadTexture('images/lensflare2.png');
var ghostColor = new THREE.Color(0x221100);
flare.add(ghostTex,  50, 0.3, THREE.AdditiveBlending, ghostColor);
flare.add(ghostTex,  70, 0.4, THREE.AdditiveBlending, ghostColor);
flare.add(ghostTex, 100, 0.5, THREE.AdditiveBlending, ghostColor);
flare.add(ghostTex,  90, 0.8, THREE.AdditiveBlending, ghostColor);
サンプルの実行結果 サンプルの実行結果 実際のサンプルを見る

 画面左上の方にレンズフレア(の発生源)があり、そこから画面中心を通る線上に円形のゴーストも描画されます。レンズフレアが移動する球体に隠れると、ゴーストも含めて消えるのが確認できるはずです(ただし、古いグラフィックデバイスでは表示されたままかもしれません)。このように、Three.jsのレンズフレアエフェクトは、遮蔽の検出も含んだ本格的なものになっています。

 LensFlareインスタンスもシーン上のオブジェクトの1つなので、作成してシーンに追加することで表示されます。コンストラクタ引数は以下の通りで、一般的にdistanceは0、blendingはAdditiveBlendingを指定することになるでしょう。colorは省略すると白(0xffffff)になります。

LensFlareコンストラクタ LensFlareコンストラクタ

 ゴーストも表示する場合は、add()メソッドで1つずつ追加します。引数はLensFlareコンストラクタと同じですが、第3引数に0〜1の値を指定して、ゴーストの表示位置(レンズフレア本体からの距離)を調整します。

ポストプロセス

 シーンの描画終了後に、画面全体をフィルタリングするのがポストプロセス・エフェクトです。全体をぼかしたり、色調を調整するなどが代表的な例ですね。

 Three.jsの本体(three.min.js)にはポストプロセス機能はありませんが、リポジトリのsrc/examples/js以下にそれを追加するコードが含まれています。基本的なフィルタがプリセットされていますし、自分でフィルタを追加するフレームワークとしても利用できます。

 ここでは例として、このページの最初に掲載したパーティクルのサンプルにブルームフィルタをかける方法を解説します。まずはthree.min.jsの読み込み直後に以下のscriptタグを追加して、ポストプロセスの拡張コードを読み込みます(jsフォルダ以下にThree.jsリポジトリのsrc/examples/jsフォルダの内容をコピーしておいてください)。

<script src="js/postprocessing/EffectComposer.js"></script>
<script src="js/postprocessing/MaskPass.js"></script>
<script src="js/postprocessing/RenderPass.js"></script>
<script src="js/postprocessing/ShaderPass.js"></script>
<script src="js/postprocessing/BloomPass.js"></script>
<script src="js/shaders/CopyShader.js"></script>
<script src="js/shaders/ConvolutionShader.js"></script>s

 そして、レンダリング部分のコードを以下のように変更します。

// ポストプロセスの設定
var composer = new THREE.EffectComposer(renderer);
composer.addPass(new THREE.RenderPass(scene, camera));
composer.addPass(new THREE.BloomPass(4.0, 25, 2.0, 512));
var toScreen = new THREE.ShaderPass(THREE.CopyShader);
toScreen.renderToScreen = true;
composer.addPass(toScreen);
 
// レンダリング
var baseTime = +new Date;
function render() {
  requestAnimationFrame(render);
  mesh.rotation.y = 0.3 * (+new Date - baseTime) / 1000;
  composer.render();
};
render();
サンプルの実行結果 サンプルの実行結果 実際のサンプルを見る

 画面全体が少しぼやけて、パーティクルの密度が増したように感じられるでしょう。適度に画面をぼかすことで、パーティクルエフェクトをより効果的に見せることができます。

 具体的にポストプロセスを追加する方法を見ていきましょう。まずEffectComposerを作成し、コンストラクタ引数としてレンダラを渡しています。ポストプロセスを描画する場合、EffectComposerがレンダラをラップする形で、シーンの描画を管理します。

 ポストプロセスとして実行するフィルタは、EffectComposerのaddPass()で追加していきます。最初のRenderPassは、コンストラクタで指定された情報を基にシーンを描画するものです。ほとんどの場合、このフィルタを最初に実行します。

 2つ目がブルームフィルタ本体です。前のフィルタの出力にぼかしを掛けて、次のフィルタに渡すという動作をします。コンストラクタ引数は以下の通りで、strengthは結果を合成する際の係数(値が大きいほど明るくなる)、resolutionは合成のために使用するオフスクリーンバッファの解像度です。

BloomPassコンストラクタ BloomPassコンストラクタ

 最後に追加しているShaderPassにより、フィルタの結果が最終的な表示画面にレンダリングされます(renderToScreenにtrueを設定しているのがミソです)。

 実際に画面をレンダリングする際は、レンダラのrender()の代わりにEffectComposerのrender()を呼び出します。これにより、追加した各フィルタが実行され、結果が画面に表示されます。

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。