ライティング処理はピクセルシェーダーでピクセルごとに処理するのが最も一般的で精度が高くなるので、全ピクセルで行われる。メッシュ上の全てのピクセルについて、かつ環境マップの解像度に比例した膨大な量の光源についてライティングをしなくてはならない。そんなもの、今日のGPUでもFPS60/30を保って処理するのは不可能である。そのため、今回は、屈折ベクトル及び反射ベクトルの先にあるテクセルの2つを光源として、しかもそのまま雑にピクセルカラーに加算するという、PBRもへったくれもない近似手法でごまかすことにしよう。この近似は、完全鏡面でかつ、全ての光源が無限遠に存在しかつ減衰しないという仮定に即したものとなる。いうわけで、既存のディレクショナル・ポイントライトの拡散・鏡面反射の計算の後に大胆に以下のコードを挿入することにする。0.2f);実行すると以下のようになる。球のモデルが実はサッカーボールなせいで地味に凹凸がうまれている。PRTか、ランタイムシェーダーコンパイルか....うーむ。Reflectionを実装したTweet広告を非表示にするもっと読むコメントを書く2018-03-09SkyBoxを実装したSkyBoxを実装した。いっても、リフレクションの前哨戦みたいなものだが。リフレクションは基本的には環境マップで実装される。理想的には、そのオブジェクトを中心とする環境マップを一個一個作ることだが、動的な環境マップとなるとあまりにも現実的ではない。そのため、環境マップを入れ子構造にし、リフレクションの精度を高めたいところに小さな環境マップを作成するという方針が、近年のゲームエンジンの基本戦略である。そんな入れ子構造の最も外側の環境マップ、それがスカイボックスだ。環境マップとしてだけではなく、シーンの背景としても活用される。実装としてはいたって単純で、キューブテクスチャ―と、頂点法線と面の向きを反転した内側向きのボックスを用意する。それをシーンを包むように巨大にスケールさせ、ボックスにキューブテクスチャをマッピングすれば完成だ。今回の実装にあたって一番苦労したのはボックスモデルの用意だ。手元にちょうど良い.xモデルを吐き出せる3Dモデリングソフトがなかったので、手打ちで作った。しても、書くことがないので、仕方なしに文字数を稼ぐために、DirectXのキューブテクスチャの初期化処理でも張ることにする。こんな手続きじみたもの、なんの技術的な価値もないが....。}ああ...パラメータをDirectXの列挙体やフラグに変換する関数の説明が面倒くさい...(開発中のエンジンからコピペってきたので)....ので省略する。大事なのはサブリソースの配列にそれぞれのテクスチャの先頭アドレスを突っ込み、それでテクスチャを初期化することだ。テクスチャのMiscFlag、サブリソースビューのViewDimension、それぞれにCubeTextureの設定をする必要がある。そのほかは普通のテクスチャの初期化と一緒だろうか?ちなみにHLSLでは前回も説明した通り、TextureCubeには位置ベクトルでアクセスする。IN.posw.xyz);}スカイボックスにはライティングもなにもないので、これだけになる。poswはワールド変換後の頂点座標だ。スカイボックスはワールドの原点に配置するので、ワールド座標がそのまま位置ベクトルとして使える。レンダリングするとこうなる。360度どこを向いても背景がついた。現在ライティングがまだまだPBRのレベルに達していないので、実際の写真を使っているスカイボックスを使うと違和感がある....まあ仕方なし。宙に浮いている謎の球体は、次回スカイボックスのリフレクションに使う。この球体にスカイボックスを映していこうと思う。Mapping(全方位シャドウマップ)を実装した全方位シャドウマップを実装した。資料が少なくて実装が結構手間だった。いうときよく頼りにしているもんしょという方のブログでは、全方位マップは双方物曲面かつディファードレンダリングでの実装だったので、参考にできなかった。私はキューブマップとフォワードレンダリングで実装している。双方物曲面で実装したほうが効率的だが、キューブマップの方がアルゴリズムとしては単純なのでこちらを優先して実装した。気が向いたら差し替えようと思う。前回も触りだけ紹介したが、全方位シャドウマップとは、ポイントライトのシャドウ実装に使われるアルゴリズムだ。ディレクショナルライトやスポットライトは、指向性があるので、そのライトの影響下にある全てのオブジェクトを内含するAABBを定義してテクスチャに深度を書き込み、実際のレンダリングパスでそのテクスチャから深度をフェッチしてきて比較することでシャドウを実装できる。ポイントライトは全方向に光を放つので、ライトの影響下のオブジェクトを一つのテクスチャに収めるということはできない。そのため、どうにかして全方向の深度を書き込む必要があるのだが、その手法としてキューブマップを使う方法がある。ポイントライトを中心とする立方体の6面のテクスチャを定義して、X軸、Y軸、Z軸それぞれの方向に向かって描画し、全方向の深度情報を保存する方法だ。実際のカメラから描画、1~6で作ったキューブテクスチャを使いライティングとなる。ポイントライトを置くだけで本来1回の描画キックで済んでいたパスが一気に7倍に膨れ上がる。ちなみに双方物曲面を使った場合はもっと少なく済む。ライティングでシャドウを計算するときは、各軸に垂直な正立方体テクスチャという性質を利用して、テクスチャからフェッチしてきた深度と、ライトから対象のピクセルのワールド座標の差分ベクトルの、X、Y、Zの最大値と比較すればよい。パースペクティブ行列による視推台の対応を考慮する。HLSLで実装するとなると、以下のようになる。0.005f;}相変わらずいきなり出てくる変数名は雰囲気で察してほしい。デプスステンシルバッファに書き込まれる深度は、その際に使ったパースペクティブ行列により圧縮された深度が格納されるので、再現する必要がある。計算式上では、normZCompの計算式がそれにあたる。100がfar-planeで、0.1がnear-planeだ。この計算は厳密にやるなら定数バッファからビュー・パースペクティブ行列を与え計算すべきではあるだろうが、深度のみの計算だけで十分な上、ディレクショナルライトとは違い、この値は基本一定である(と勝手に思っている)ので、直接計算式をぶち込んだ。この方が高速であろう。計算式の意味が分からん人は実際にDirectXのパースペクティブ行列を導出してみるべし。このブーリアンを返す関数を使ってライティング処理をするかしないかをピクセルごとに判定すれば、全方位シャドウの実装は完了だ。実際にライトの周辺にいろいろおいてレンダリングしてみるとこうなる。蛇足になるが、テクスチャからフェッチしてきた深度と、光源とピクセルの距離のXYZの最大値を比較するという発想に至るまでもそこそこ時間はかかったのだが、それ以上に昔からずっと存在していたっぽいバグにはまった。頂点シェーダで頂点情報を描きだす際、ワールド座標からビュー座標に変換するのはいつも通りなのだが法線も雑にワールド変換行列をかけて計算してしまっていた。1.0f);(やたらとfloat4とfloat3を相互変換しているのは、wの値が不定な可能性があるため。そのうちちゃんと1.0fで初期化されるよう定数バッファの処理をリファクタしたい)これがバグを起こしたダメなコード。法線をワールド変換しているが、これは明らかに間違いである。法線はそもそも純粋な「向き」である。うち、影響を受けるべきなのは拡大縮小・回転だけだ。R)をかけることで正しく変換される。この辺の説明はほかのしっかりしたサイトを見たほうが早い。ワールド変換行列から都合よくそんな成分を抜き出せるかどうか、と言われると、実はめっちゃ簡単なのだ。ワールド変換行列の転置行列の逆行列がそれにあたる。端的に言えば、転置行列で平行移動成分が消え、逆行列でスケール成分が逆数になる。この行列を定数バッファから与えて法線にかけてあげればよい。考えれば当たり前だが、こんな致命的な頂点シェーダのバグを残したまま大きな機能を実装していたせいで、発生したバグの原因の追究に無駄に時間を取られた。基礎の実装のデバッグを重点的にやっていこう。次はスカイボックスとリフレクションを実装したいと思う。コードをカオスになってきたので、リファクタも本格的にやっていきたいところだ。ネックになっているのは、やはりシェーダか。その性質ゆえ、シェーダの頂点レイアウトやバッファ変更が大きくプログラムに影響してしまう。生産的なのは、「プログラム側でシェーダを生成しランタイムコンパイルすること」だ。このシステム構築にはとんでもない手間がかかるが、必ず必要となるはずだ。やるべきことが山積みだが、一つ大きな機能の実装を終えたことでとりあえずひと段落ついた感じはある。Delete300くらいになってた。差分が膨れ上がるのも、拡張性が低いクソ設計になっているのが原因なのだが。あーリファクタしたい。CPUでCubeMap読み取りたいんだけどバグる。そもそもマイクロソフトの資料がうんちすぎて、そもそもTextureCubeの6面がメモリ用で連続なのかもわからん。常識的に考えて連続やと思うんだけど。