レンダリングエンジン進捗まとめ

 前回のポエムが長くなり過ぎたので二回に分けた。

 

 現在、レンダリングエンジンに対するコミット数は104。

 簡単に振り返ってみる。

 

1日目あたり

 最初の方のコミットは、なんというか、コピペコードばかりだ。酷過ぎる。とりあえず動くところ見るためとはいえ、よそ様のブログのコードをコピペって動かしているのがよくわかる。一応は自分なりに考えてレンダラ、シーン、ビューのクラスを作って雰囲気的に設計を考えてるようだが、かなりお粗末だ。

 

 まず最初に、メッシュという概念を定義して、その派生クラスとして三角形プリミティブメッシュを作ったようだ。そして、それを描画するための一通りの初期化コードと、コンパイルしたシェーダを読み込むためのバイナリローダ、およびリソースハンドラを作っている。リソースの所有権を誰が持つかは当時まったく決めていなかったようで、なぜかレンダラがシェーダの所有権を持っている。

 

 このレベルだと3Dですらない。画面上に三角形を出すだけだ。シェーダも、頂点シェーダは頂点情報をピクセルシェーダに渡すだけだし、ピクセルシェーダも頂点カラーを出力しているだけである。まあ、初期のレンダリングエンジンの足掛かりなんてこんなものだろう。

 

2~5日目あたり

 一つのオブジェクトにつき一つ作られるドローエレメントという描画用クラスを定義したり、シーンクラスを真面目につくったり、少しずつ地盤を固めている。

 そして、そろそろテクスチャを描画したくなったのか、libpngライブラリを導入し、pngローダを作成した。どうやらこの時期にようやくカメラという概念を導入し、ビュー変換、パースペクティブ変換を実装し、3D的な描画ができるようになったみたいだ。といっても、2Dポリゴンを傾けるようなレベルだが。

 

 この次辺りでなぜかファイルキャッシュの仕組みを作っている。他にやるべきことがあるだろうに。

 

6日目あたり

 ここでようやく、インデックスバッファを使った描画に対応し始めた。

 

 これ以降からやっとレンダリングエンジンらしい開発を始めている。これまでは、レンダリングエンジンというか、ただのDirectXチュートリアルみたいな状態だった。

 

7日目あたり

 3D描画のテストのためには、描画用のモデルローダを作る必要がある。最も多くの無料モデルが配布されていると言っても過言ではない、PMDモデル(MikuMikuDanceモデル)に着目し、最初にローダを実装した。現在の主流はFBXだが、アレは仕様が複雑怪奇すぎるので、いまだに実装をしていない。Autodeskが配布しているライブラリに依存するのも嫌なので、後回しにしている。

 

 とりあえず頂点とインデックスリストだけを読み込むだけなら、30分程度で作れた記憶がある。意外と簡単にモデルのディテールが表示されて、謎の達成感に包まれた。なにせ、やっと3Dの世界にきたという実感があったからだ。(一応、立方体の描画とかはやっていたが)

 

7日目あたり

 次に、やっとライティングに手をつけた。まずディレクショナルライトを追加し、最も簡単なランバート拡散反射を実装している。正規化頂点法線と正規化光源ベクトルの内積で、そのなす角が求まるので、直接光が当たってるかどうかわかるよねってだけのアルゴリズムだ。ポイントライトも追加しているが、ディレクショナルライトに距離減衰と光源位置を追加しただけのものなので、拡張は楽だった。

 

8~11日目あたり

 最初の挫折はここで味わった記憶がある。ディレクショナルライトのシャドウの実装だ。驚くほど動かなかった。

 アルゴリズムとしては、シーンを覆える視野のライトカメラを仮定して、ライトカメラから見えるシーンすべてのオブジェクトの深度を、ステンシルビューを使って描画する。あとは、実際のレンダリングで、対象の頂点とライトカメラの距離と、深度バッファの保存してある深度を比較し、必要ならシャドウをつける。

 GPU処理の基本、すべてのデータをテクスチャに焼きこんで、複数のパスで共有して使う。その最初の一歩だった。テクスチャには16ビット型無しフォーマットとして深度を描画し、それをシェーダーリソースとしてR8G8符号なしテクスチャとして読み込む。またビュー座標系からテクスチャ座標系へのマッピングも忘れてはならない。

 他にも、カメラから深度バッファを書き込むときはもちろん、実際のレンダリングのときにも、再度頂点をカメラの位置からビュー変換しなくてはならないので、定数バッファにライトの数だけライトのビュー・パースペクティブ行列を格納しなければならない。

 今まで雰囲気でやっていたシェーダ処理に、途端に厳密な実装を要求され、手探りでいろいろと試していた記憶がある。結果的に3~4日かかってしまった。動いたときはそれはもう感動したものだ。

 

14日目あたり

 今更ポイントライトの修正をしたり、あとはマウス操作によるカメラ移動を実装している。インタラクティブな要素を入れた途端、リアルタイムレンダリング感が出た。

 

15日目あたり

 DirectXリソースのメモリリークを解消している。実はこれをするまで、尋常じゃないくらいメモリリークを起こしていて、Warningを吐いていた。全部無視していたが、やっと観念して修正したようだ。

 

 そして、PMDモデルの材質リストに着手するとともに、一つのメッシュが複数のマテリアルを持つという当たり前のことが想定できていなかったせいで、大幅なレンダラのリファクタリングを強いられている。PMDモデルのテクスチャは基本BMPなので、BMPローダの実装も行った。無圧縮フォーマットなので実装は楽勝だった。やっと色付きの初音ミクの表示ができたのである。

 

17~20日目あたり

 鏡面反射を実装している。ここで、ようやく本格的な物理ベースレンダリングのコードを描き始めたことになる。基本的にはクック・トランスの反射モデルを採用し、今まで通り直接光しか計算していない。材質のメタリック値を上げると、スペキュラが3Dモデルに現れるようになった。割と、現実に近い挙動をする。真面目な物理モデルを実装したので。だが、実際のそのアルゴリズムを紹介するには、ここの余白は狭すぎる。というか、自分が参考にしたQiita記事が最強だったので、DirectXの実装的な補足を加えつつその記事を紹介する記事を書きたいものだ。

 

基礎からはじめる物理ベースレンダリング - Qiita

 

 21日目あたり

 PMDモデルだけでは限界を感じてきたので、.xモデルのローダも作成した。バイナリフォーマットは面倒くさそうだし、MSDNの資料も微妙だったので、テキストフォーマットにだけ対応することにした。テキストフォーマットなら、仕様は実際のファイルから読み取ることができる。ダーティーなハックになるが、いろんなファイルをかき集めて、ロードに失敗するたびにファイルの中身を確認し、想定漏れの仕様等を毎回カバーする形で実装していけば、いずれ完全なローダになる。

 

 C++のクソみたいな文字列処理で頑張って実装してもよかったのだが、いい機会だったのでパーサコンビネータを作成した。Boost.Spirit.Qiを採用したが、最終的なアウトプットはまあそこそこ美しい反面、あまりにも開発が苦痛だった。なにせちょっと構文や型を間違えるだけで尋常じゃない量のコンパイルエラーを吐くからだ。人類には早すぎるライブラリな気もする。ただ、ユーザー定義構造体をタプルにバインドして、美しく型安全な構文ルールを定義し、パースする仕組み。なるほど、うまく使えればとても気持ちが良い。私自身はTMPが大好きなので、正直使ってて興奮した。

 

22日目~35日目

 リアルが立て込んだせいで日付が一気に飛んでいるが、主にひたすら.xモデルの仕様網羅と最適化をしていた。

 .xモデルの仕様として、頂点リスト-面リスト-材質リストという順に定義されている。面リストは、一つの面につき複数の頂点への参照を持つ。つまりインデックスだ。そして、材質リストは、面リストすべてのマテリアルを定義する。つまり、面一つ一つが独自のマテリアルを持つことが可能(仕様上)であり、そのまま愚直に実装すると、ドローコールが面の数だけ発生してしまう。

 描画最適化のために、まず面リストを同種のマテリアルでグループ化し、グループ化された面リストを一つのインデックスリストに展開するようにした。こうすれば、ドローコールはマテリアルの数だけで済む。PMDモデルと一緒だ。ただし、面の順序を変える形になるので、面が連続ではなくなる。そのため、ストリップ描画をすると、離れた面へ描画していくとき、その軌跡に謎の三角形が生じてしまう。なので、頂点数4以上の面は、複数の三角形に分解し、三角形リストとして描画することにした。描画が爆速になった。

 

 あとは、モデルによっては頂点法線がデータに入っていないことがあったので、その際の計算も行うようにした。頂点が所属するすべての面の法線を合成した正規化ベクトルが頂点法線になる。ただし、三角形の面の法線はある点から他の二点への位置ベクトルの外積で求まるが、四角形はそうはいかない。対角ベクトル二本の外積を求める必要がある。五角形以上は知らん。

 

現在

 環境キューブマップの実装をしている。描画に使う、6面の周囲情報をベイクしたテクスチャのことだ。これを使うと、ポイントライトの全方位シャドウや、リフレクションが実装可能になる。ポイントライトのシャドウは結構面倒なのだ。また、リフレクションが実装できれば、鏡面に周りのオブジェクトの映り込みが実現できるので、おそらく更に”それっぽさ”が向上するだろう。

 

 

 一か月ちょっとを駆け抜けてみた。結構いろんなことをやったようで、まだ何もしてないようにも思える。シャドウの品質を上げるためのキャスケードシャドウや、間接光計算実現のためのフォトンマッピングアンビエントオクルージョン、そもそもディファードレンダリングすら実装できていない。そのほか、ライトの色情報や、その輝度だったり、そもそもライトが面積を持つことも想定していない。というか、こういう細かいことを挙げていたらキリがない。まともな見栄えのレンダリングができるまで、少なく見積もってもあと半年はかかると思っている。

 

 最終的な目標は、国産のPMX・PMDデフォルト対応の高品質レンダリングエンジンだが、まあ、数年がかりになるだろうし、そこまで到達できるとは思っていない。

 

 あくまで、今は自分が楽しんで、学ぶために開発している。そして、それがほかのレンダリングに興味のある人間の刺激になればなおよい。

 

 振り返りはここまで。次からは、ちゃんとした開発日記をつけていこうと思う。