「POOL」のコンセプト・技術解説
2019.9.19
2019/09/12~25までFabCafe TokyoでおこなれたNEORTの展示「NEORT:Neo Visual Art Collection」 のために制作した作品「 POOL」のコンセプトや技術について解説します。
展示ではフレームレートが安定せずに動きが少しカクついていたので、動画にしたものも置いておきます。 (テスト不足でした。申し訳ないです。)
今回制作した「POOL」は過去にNEORTに投稿した作品で試してきた表現や技術を使用しているので、それらの作品も適宜紹介していきます。
コンセプトらしいコンセプトもないのですが、強いて書くとすれば「3DのBoidsをフォトリアリスティックにかっこよくレンダリングする、あとジェネっぽい要素も入れる」になります。要は自分の好きなもの(シミュレーション, 3D CG, Generative Art)を全部盛り込んだ作品ということです。
Boidsは群れの振る舞いをシミュレーションする昔からあるアルゴリズムです。「POOL」ではうねうね動いている黒い生物の動きに使用しています。
Boids (Flocks, Herds, and Schools: a Distributed Behavioral Model)
Boidsの各個体は一定距離内にいる他の個体のみを考慮して以下の3つのルールに従い移動します。
各個体がシンプルなルールに従いミクロなインタラクションをおこなうことで、群れというマクロなダイナミクスを生み出すのがBoidsの魅力になります。
Boidsに興味があり自分でも試してみたい方は、ProcessingのFlockingの実装がわかりやすく参考になると思います。
Flocking \ Examples \ Processing.org
個人的にはBoidsに限らずシミュレーション全般が好きで、SPHやStable Fluidsなどの流体シミュレーションや反応拡散シミュレーションなどをいろいろ試してみます。シミュレーションの面白いところはシンプルなルールから予測できない複雑なダイナミクスが生まれる点だと思っています。
以下は私がNEORTに投稿したシミュレーションを利用した作品です。
自戒を込めて書くと、シミュレーションのアルゴリズムをただ単に実装しただけでは単なる技術デモにしかなりません。「POOL」ではシミュレーションをどうやって作品に落とし込むかを考えながら作成しました。
個人的には、せっかくリアルタイムCGでやっているので、今後はもっとインタラクティブな要素の入った作品を作れればと思っています。
「POOL」では壁面の模様をGenerative Artっぽく作っています。
Generative Artについて詳しく知りたい方は、私が解説するよりもわかりやすいので以下の記事を読むといいと思います。
ジェネラティブ・アートを愛する理由 - lab.sugimototatsuo.com
Generative Artの醍醐味はこの記事に書いてあるコントロールされた偶然性
ではないかと考えています。ある程度、出てくるイメージを想像しながらプログラムを書き、最後は偶発性にまかせて自分が想像した以上の絵がでてくると嬉しいですね。
もともとは、ずっとProcessing(p5.js)でジェネっぽいことをやっていましたが、Processingと比較してWebGL(GLSL)でやるGenerative Artは綺麗なグラデーションをリアルタイムに生成できるところが面白いです。
以下はこれまでにNEORTに投稿したGenerative Artっぽいの作品の一部です。
「POOL」はWebGLを使ったリアルタイム3D CGの作品になっています。
リアルタイム3Dは非リアルタイムなプリレンダーCGとは異なり、1フレームを短時間で描画しなければならないため、ライティングなどに簡易な方法が使われ、いかにもCGっぽい絵になりがちです(といってもお金をかけて開発されたゲームなどは多分にリアルですし、CGっぽささが逆によかったりもしますが...)。
「POOL」ではプリレンダーっぽいフォトリアリスティックな雰囲気を目指しました。個人的にプリレンダーっぽくなる要素として映り込みが重要だと考えていて、「POOL」では映り込みをそこそこ真面目に計算しています。黒い生物の表面が天井の光源や壁面の模様を反射していることがわかると思います。
この映り込みは、レイトレーシングのプログラムを自分で書いたことがある人にはわかると思いますが、鏡のような滑らかな表面では鏡面反射方向の光のみを考慮すればいいので割と簡単ですが、ざらついた金属のような表面では色々な方向から入射する光を考慮しなければならないためリアルタイムに計算するのは難しくなります。
「POOL」でも鏡面反射方向の光のみを考慮して映り込みを計算していますが、フレネル反射率を考慮することで車のコーティングのような光沢感のある質感をだしています。
フレネル反射率は物体表面に入射した光が反射する割合を表しており、一般的に浅い角度から入射すると反射率が大きくなり深い角度から入射すると反射率が小さくなります。水面を上から見ると水の底が見えますが、横から見ると底が見えないのと同じ理屈です。
フレネル反射率について - OLD hanecci’s blog : 旧 はねっちブログ
これまでにもフレネル反射による映り込みを考慮した作品をNEORTに投稿しています。
「POOL」の技術的な部分について解説していきます。
ソースコードはGitHubに置いておきました。
aadebdeb/NEORT_FabCafe_2019: A Work for NEORT Exibition 2019 at FabCafe Tokyo
「POOL」はWebGLを利用してTypeScriptで書いています。Three.jsなどの3Dライブラリを使用せずにフルスクラッチで制作しています。
私もThree.jsやUnity、Touch Designerなどよく使われているライブラリやツールは使ってみたことがあるのですが、結局趣味で利用する分には生WebGLでいいやとなりました。以下、私がWebGLを選択している理由で箇条書きで書いておきます。
日本語のWebGL情報が充実しているのも魅力ですね。
TypeScriptはWebGLかどうかを問わず、ある程度の規模のJavaScriptプロジェクトであれば導入したほうがいいと思います。
うねうね動いている黒い生物について説明します。
黒い生物の移動自体は先に述べたようにBoidsのアルゴリズムにより決定しているのですが、Boidsの各個体は体積のない点として計算しているので画面上に表示するためには点の位置に3Dメッシュを描画する必要があります。
「POOL」ではGPU Trailsという手法を使用しています。GPU Trailsは2次元テクスチャのx方向を各個体、y方向をフレームとして各個体の最新数十フレームでの位置や速度をテクスチャに記録しておき、シェーダーを使ってテクスチャに格納された位置や速度を更新していく手法です。描画時には頂点シェーダーでテクスチャから位置を取得することで適切な位置にメッシュの頂点を配置します。
GPU Trailsを使用している都合でBoidsの計算もシェーダー内でおこなっています。GPU Trailsの先頭位置をBoidsの点の位置として計算していることになります。Boidsの近傍探索は単純に実装すると全探索になるためO(n^2)
の計算量がかかります。計算量を減らす方法はありますが、今回は単純に全探索で実装しています。
GPU Trailsの欠点はテクスチャにフレーム単位で位置を記録するのでフレームレートによりtrailの長さが変わる点です(レンダリングループで使用するrequestAnimationFrame
はフレームレートが可変なので、環境(ディスプレイ)によりフレームレートが変わります)。「POOL」ではこれを避けるためにGPU Trailsの更新は常に60fpsでおこなうようにしています(そして、おそらくこれがカクツキの原因だと思います...)。
GPU Trailsを使用した作品をNEORTに投稿しているので興味がある方はそちらも参照してください。解説記事もあります。
先に書いたように「POOL」ではプリレンダーっぽい質感を目指すために映り込みにこだわっています。映り込みを計算するための壁との交差判定や壁の模様の決定などは負荷の大きい処理だと想定されました。そのため「POOL」ではDeferred Renderingという手法を採用しました。
一般的には各3Dメッシュごとに頂点シェーダーで頂点位置を計算して、フラグメントシェーダーでその都度ライティングするForward Renderingが使用されます。
Deferred Renderingでは各3Dメッシュごとにはライティングを行わず、ライティングに必要な色や法線の情報をGBufferと呼ばれるバッファに格納しておきます。すべての3Dメッシュの描画が終わってからGBufferに格納された情報をもとにスクリーンスペースでライティングを行います。
Forward Renderingではピクセルが重なっている場合に下にあるピクセルのライティング処理が無駄になります。Deferred Renderingはスクリーンスペースでライティングを行うことで確実に画面に表示されるところのみライティングを行うので無駄な処理が走らなくなります。
以下は、Deferred Renderingの法線バッファのスクリーンショットです。
「POOL」のDeferred Renderingでは、GBufferの構造が汎用的なものとは異なっており、どのオブジェクト(天井・床・壁面・黒い生物)を描画するかという情報を格納しています。ライティング時にはその情報をもとに個々の要素ごとに違う処理を加えています。天井であれば光源として白い四角を描いています。壁面であればジェネっぽい絵を描いています。黒い生物または床であれば法線とカメラ位置から反射方向を計算し、部屋の壁との交差判定をしてぶつかった壁の模様を映り込みに利用しています。
WebGLでDeferred Renderingをする方法について以前に解説記事を書いたので、気になるかは参考にしてください。
Deferred Renderingでライティングした絵にポストエフェクトをかけています。いくつかポストエフェクトをかけていますがメインはブルームエフェクトと被写界深度エフェクトです。
ブルームは明るいところの光が外側まで溢れているように見える現象を再現するエフェクトです。 被写界深度はカメラのボケのような焦点からはずれている箇所をぼやけさせるエフェクトです。
ブルームエフェクトと被写界深度エフェクトの両方をかけていないと以下のようにパきっとしていて味気のない絵になります。
これにブルームエフェクトをかけると以下のようになります。特に天井に書かれた白い四角形の周りにうっすらと光が溢れてライトっぽくなっているのがわかると思います。
さらに被写界深度エフェクトをかけたものが以下のようになります。遠くがうっすらとぼやけて絵に奥行き感が出ています。下のスクリーンショットではわからないですが、手前側も近づきすぎるとボケるようになっています。
黒い生物をカメラで追尾するように撮影するモードのときには、以下のよう被写界深度エフェクトの焦点範囲を狭めてよりぼける範囲が大きくなるようにしています。
ブルームエフェクトの実装は川瀬式MGFという手法を使用しています。川瀬式MGFは複数の縮小バッファを使用する手法で低コストで強いブルームを作成することができます。
被写界深度エフェクトもブラーをかけたバッファを用意して深度に応じて元の絵と合成するすることで実現しています。
どちらの実装でもブラーをかけたバッファが必要になります。WebGLでブラーをかける方法について以前記事を書いたので興味のある方は参照してください。
「POOL」のコンセプト・技術について解説しました。
読んでもらうとわかるように今までで試してきたことの組み合わせになっており、期せずして集大成的な作品になりました。
You can support this creator by paying money.
Commercial use NG