バレンタインデー企画ページの解説

glamp

screen shot

豪勢山盛仕様

当テキストは Happy Valentine's Day + WebGL にて公開したデモ、glamp の解説記事です。

バレンタインデー企画では、自分のほかにも @yomotsu さん、@cx20 さん、そして @chimanaco さんが参加してくれました。いずれも WebGL を利用したデモになっていますので、ぜひ上記のリンクから特設サイトで作品をご覧になってみてください。こういうのは定期的にやっていきたいですね。うん。

さて、先述のとおり、本テキストはバレンタインデー企画で作ったデモ、glamp の解説テキストになります。とは言っても、要はすでに当サイトにて解説してきた技術の組み合わせだけでほとんど成り立っているようなものなので、当サイトの該当するテキストへのリンクを載せつつ要所だけ解説していこうと思います。

Twitter API

今回のデモでは、Twitter API を利用してリアルタイムにデータを取得して表示する手法をとっています。これは、WebGL というブラウザで動作するテクノロジーを利用するからこそできる、ある意味でインタラクティブなデモにしたいと考えたからです。

Twitter API 自体は、今回の場合 PHP で処理してしまったことと、直接 WebGL とは関係ない部分ということもあり詳細は解説しません。ウェブ上には割と膨大に情報があるので、案外簡単にできると思います。興味のある方は調べてみてはいかがでしょうか。

今回の場合、PHP で Twitter API を叩いてそのレスポンスを HTML の中に直接埋め込んでいます。本来であれば Ajax とか使ったほうがスマートなんですが、要は javascript に対して Twitter API の結果を渡すことさえできれば OK です。ちょっと手抜きな実装ですが時間がなかったので許してやって……

Twitter API を叩く際に、今回は[ i love you ]という検索ワードを指定してリクエストを発行しています。一応、バレンタインデーにあやかってという形です。取得したレスポンスから、Twitter のユーザー ID と tweet のテキストを抜き出して javascript 側に渡しています。

法線マップの生成

Twitter から取得した情報を元に、スクリーン上には見えない部分で canvas に対していくつかの事前準備を行います。ひとつは法線マップの生成、そしてもうひとつが tweet 本文の canvas へのレンダリングです。法線マップの生成は別途テキストを書きましたので、そちらをご覧いただくとわかりやすいと思います。

参考:法線マップの生成

法線マップの生成を行う過程では、まず対象の canvas を黒く塗りつぶした後、そこへ白の文字でユーザー ID を描き込みます。これがハイトマップ(高さを格納したマップ)の代わりとなり、法線マップの生成が行えるという寸法です。Twitter API から取得した情報を使って、リアルタイムにその場で法線マップを生成するわけですね。

本文のほうに関しては、最初から canvas に対して最終的な色合いを考慮して描き込みを行います。tweet 本文が描き込まれた canvas は、そのままテクスチャの元となるイメージとして使われることになります。

法線マップとして処理したユーザー ID

法線マップとして処理したユーザーID

テクスチャに利用できる canvas

以前、テクスチャマッピングのテキストでも解説しましたが、WebGL で用いるテクスチャには画像以外にも直接 canvas のイメージデータを利用することができます。今回のデモの場合は、法線マップを出力した canvas と tweet 本文を出力した canvas の、そのいずれも WebGL 側でテクスチャとして利用しています。

WebGL 自体には、文字を出力できる仕組みは備わっていません。もちろん、フラグメントシェーダが自由に使えるわけですから工夫すればなんだってできますが、あくまでも文字列をレンダリングする機能というものは備わっていないのですね。しかし、今回のデモのように、いったん canvas を経由する形にはなりますが、文字列を柔軟に利用する手段がないわけではありません。

このあたりはプログラマの腕次第というところですね。

JSON を利用したモデルデータ

さて続いてはモデルデータです。

モデルデータに関しては、事前にツールなどを用いて JSON に出力して準備しておくのが一番簡単でいいと思います。

今回のデモで利用しているモデルデータは、以下のような感じになっておりいずれも OBJ ファイルからコンバートして JSON に変換しました。

  • ハート型のモデルデータ
  • 電球のガラス球殻
  • 電球の口金(付け根)部分

OBJ 形式のデータから JSON へのエンコードができるツールは探せばいろいろあります。最近ではモデリングツールもだいぶ対応が進んできていますしね。また完全ではありませんが、単に頂点位置や法線、テクスチャ座標だけでいいのなら当サイトの objson.js を使うことである程度は自前で処理することもできます。

参考:objson.js リファレンス

上記ページからは、実際にコンバータとして利用できるデモページへのリンクがありますのでちょっと試してみるくらいにはいいかもしれません。自分でテストした感じでは大抵のファイルは普通に処理できるのですが、ごく一部崩れてしまうものもありました(特に巨大ファイル)ので、あまり過信はできませんのであしからず。

今回のデモでは、OBJ ファイルから上記のコンバータを使って出力した JSON データを、単体の js ファイルとしてページのロードと同時に読み込んでいます。モデルデータが巨大な場合には当然読み込みに時間がかかるので web workers などを併用するといいかもしれません。

シェーダ周辺

今回のデモでは、なんとシェーダを六組も使っています。なんとも豪勢ですね。

ただ、冒頭でも触れたように、既に当サイトのテキストで解説したものか、あるいはそれを応用したものしか使っていません。

  • バンプマッピングシェーダ
  • キューブ環境マッピングシェーダ
  • ライティングを行うシェーダ
  • 反射光だけを出力するライティングシェーダ
  • 正射影による投影シェーダ
  • ガウシアンブラーシェーダ

こうして一覧にしてみただけでは、我ながらよくわからないですね。

実際のレンダリング手順を追いつつ、見ていきます。

まず最初に行っているのは、キューブマッピングのためのキューブマップテクスチャを作る処理です。

キューブマップ用に生成したフレームバッファを WebGL にバインドしたら、上下左右、そして前後のすべての方向の様子をフレームバッファにレンダリングします。ここでは具体的なコードを載せるよりも過去のテキストを参照してもらったほうがわかりやすいでしょう。今回やっているのはいわゆる動的なキューブ環境マッピングですね。

参考:キューブ環境マッピング

参考:動的キューブマッピング

動的キューブマッピングで、電球の周辺に浮かぶハートのオブジェクトとカードに見立てた板ポリゴンをフレームバッファにあらかじめレンダリングしておきます。ここで作られたキューブマップテクスチャは、最終的なシーンで電球のガラス部分に映り込む景色に利用します。

続いて、さらにもう一度、今度は別のフレームバッファに対するレンダリングを行います。

これは最終的なシーンで光の溢れ、つまりグレアを表現するための準備です。今度はキューブマップ用ではなく通常の 2D のフレームバッファをバインドし、電球の中に収まるひときわ赤い、少し小さめのハートをレンダリングします。このとき、ハートは単純に明る目の赤で塗りつぶします。さらに、電球のガラス部分だけを特殊なシェーダでレンダリングしてやります。

このガラス部分を特別にレンダリングするシェーダは、ライティングにおける反射光による照明効果のみを描き込むというちょっと特殊なシェーダです。これも、過去に扱ったグレアフィルタで同様の技術を利用しています。

参考:グレアフィルタ

グレアを適用した最終シーン

グレアを適用した最終シーン

上の画像を見るとわかるように、ハートと電球の左上隅のあたりにはぼんやりと露光したような処理が施されていますね。この光のあふれを表現するために、いったんフレームバッファに中央のハートをベタ塗りで赤くレンダリングしたものと、電球のガラス部分の反射光のみをレンダリングしたものを用意しているわけです。

もちろん、このときレンダリングしたフレームバッファをそのまま最終シーンに合成しても、光が溢れているような効果は得られません。同バッファに対してはガウシアンブラー処理も施しておきます。最終シーンでは、このブラーをかけた状態のバッファを加算合成することになるわけですね。

参考:gaussian フィルタ

最終シーンへの道のりは遠いのであった

さて、前項まででフレームバッファに対するレンダリングは完了しました。最終シーンを完成させるために、さらにシェーダを切り替えつつ処理していきます。

最終シーンに対するレンダリングでは、まず最初にバンプマッピングを適用したハートたちを電球の周囲の空間上にレンダリングしていきます。

実は、ここでハートのオブジェクトに対して適用されるバンプマップシェーダは、実際の javascript コードで登場する順序としては一番最初に出てきます。キューブマッピング用の背景のレンダリングを行っている部分でも使われているからですね。最終的なシーンをレンダリングするためにここで再度登場します。

バンプマッピングを行う上では、冒頭で説明した Twitter API のデータから生成した法線マップを適用しつつ、ハート型のモデルをレンダリングしていきます。バンプマッピングでは特殊なベクトルの算出が必要になったりするので、これも詳しくは過去のテキストを見てもらうのがわかりやすいかと思います。

参考:バンプマッピング

周囲のハートをレンダリングしたら続いて電球の部分をレンダリングしていくわけですが、注意すべき点として、ガラスの球殻部分は一番最後にレンダリングするようにします。勘のいい方ならおわかりかと思いますが、ガラス部分は透明なオブジェクトとして扱いたいわけです。3D においては基本的なことですが、半透明や透明となるオブジェクトは、その後方に位置するすべてのモデルをレンダリングしてから最後にレンダリングするようにしなければなりません。※深度値が影響してレンダリング結果がおかしなことになってしまうのを防ぐためですね。

ガラスの球殻をレンダリングする際には、先ほど生成した動的なキューブマップをテクスチャとして適用し、周囲の光景が映り込むような効果が出るようにします。映り込みの強度などは、主にシェーダ側で調整することになりますね。

さて、ここまでくると必要なオブジェクトはほぼ画面上にレンダリングされた状態になりました。あとは、スクリーン全体を覆う板ポリゴンを使って、ふたつのポストエフェクトを適用します。

ふたつのエフェクト処理

ひとつは、フレームバッファに対してレンダリングして事前に用意しておいた、ブラーを適用したグレア用のシーンです。これを最終シーンに加算合成することによって、ぼんやりと光が滲み出てきているような効果が得られます。

もうひとつは、まるでブラウン管テレビのように画面全体に横線のノイズを発生させる処理です。ここは、実は過去のテキストで唯一登場していないものだったりします。やっていることとしては、いわゆるサイン波を応用したような処理ですね。ここはシェーダのコードから当該部分を抜粋して見てみましょう。

ブラウン管風味ノイズ

lineNoise = sin((gl_FragCoord.t - times) * 2.0);

上記の times は uniform 変数として入ってくる float 型のデータです。実際には、1フレームごとにインクリメントしている値を持ってきているだけだったりします。

サイン波の仕組みを使って、出力される色成分に調整を掛けているわけですね。この横線ノイズは、グレアに対してガウシアンブラーを掛けているシェーダで一緒に処理しています。最終的にグレアと一緒に最終シーンに対して加算合成されることになりますので、色が明るくなりすぎないように調整しています。

ノイズが適用された様子の拡大図

ノイズが適用された様子の拡大図

まとめ

今回のデモは、いろいろな小細工を組み合わせてなんとかカッコイイ映像は作り出せないだろうかという安直な考えで作られたわけですが、よくよく振り返ってみればそれなりにいろいろなことをやってる感じだったかもしれません。

こういった、複数のシェーダを組み合わせたり、あるいは特殊な合成方法を用いたエフェクトを掛けたりといった処理では、ブレンドモードの切り替え・シェーダプログラムの正しい選択・バインドするバッファやテクスチャを正しいタイミングで……などなど、注意すべき点が多いです。また、ソースコードが冗長になりやすいため、適宜処理を切り分けるなり関数化するなりしないと、コードを書いている自分自身がわけのわからない状態になってきます。

コメントを適宜入れるなどして開発を行うようにしたほうがいいことは言うまでもなく、また可能な限り、フレームバッファに正しくレンダリングが行われているかどうかなど、開発中にチェックしやすいようなコードを書いたほうがいいでしょう。

大規模な WebGL プログラムは、どうしても見通しの悪い膨大なコードと向き合わなくてなりません。その分、開発には集中力とあきらめない忍耐力が必要になってしまいます。ライブラリなどの力を借りながら実装するのもいいと思いますが、たまにこういったストイックなコードを書くこともいいものです。

本テキストがなにかの参考になったとしたら幸いです。

entry

PR

press Z key