あんさんのコード間違ってる。うーん皆苦労してんだなあ。GPUメモリ内で連続である確証が得られたのはよかった。私も同様のことをしたいと思っていたから。環境マップの作成のためには、当然ながら立方体六面のすべての面の描画が必要になるわけだが、残念ながらTextureCubeを使ってバーン!と描画するわけにはいかない。そもそも根本的に描画する対象が違うからだ。法線、深度、もろもろの情報をMRTを使って一度の描画キックで描画するディファードレンダリングとは根本的に用途が違う。6面描画するなら6回描画しなければならない。個別のテクスチャとして描画する必要がある。具体的には6方向のビュー行列を作って、それを定数バッファに格納して6回頂点シェーダを起動する。一般的にはZバッファ...Directx11ではDepthStencilViewの描画テクスチャを、シェーダーリソースとして再利用する形式が一般的。ピクセルシェーダとレンダーターゲットはnullptrを渡せばよい。_viewMatrixes;}で、あとはこれで得られるビュー行列の配列と、共通の視野角90度・アスペクト比1:1のパースペクティブ行列で描画すれば、6枚分の深度テクスチャが得られるが....これをCubeTextureに再パックしたい。nullptr);}唐突に出てくる変数名とかは雰囲気で。つまるところ、CopySubresourceRegionを使ってテクスチャを一枚一枚TextureCubeに先頭からコピーしていく。TextureCubeのミップレベルの構造がよくわからんかったので自信がなかったが、うまく動いた。+X-X+Y-Y...と一通りのミップレベル0のテクスチャの後に、ミップレベル1...2...と続いていく形式じゃないのかと不安だった。ただの6枚のテクスチャ配列らしい。これでCubeTexture自体は実装できたので、さっそくシャドウを実装したいところだが、先にTextureCubeビューワーを作る。兎にも角にも、バグ発生時にシャドウの計算と、深度環境マップの作成のどちらでバグっているのかが分からなかったら話にならないからだ。HLSLでは、TextureCubeのSampleメソッドはこういうシグニチャになっている。float3);Texture2DArrayと同じシグニチャ。第二引数の三次元ベクトルは、x、yはテクスチャUVで、Zがインデックスだな?と思ったら全然違ったあまりのも情報が少なすぎるので、DirectxのサンプルをあさりまくってTextureCubeを実際に使っているプログラムを探す。すると、引数に渡している引数の変数に代入している式に見覚えのある数式が....あ、これ反射ベクトルの計算式だ。いうわけで、TextureCubeの第二引数はUVではなく反射ベクトルだった。わ、わかりづれぇ..ちがった。反射ベクトルを使うのはリフレクション用途で環境マップにアクセスするとき。シンプルに、立方体の中心から面に向かう3次元ベクトルでアクセスする。これは、単純なテクスチャの表示には不便そうだ。テクスチャビューアーはTextureCubeが渡された時には内部で展開して一枚のテクスチャにする機能を加えることにする。さっきのプログラムの真逆のことをするだけでいい。nullptr);これで深度環境マップが正しく描画されているか確認できるようになった。下六枚が部屋の中心に置いたポイントライトのシャドウマップ。左上のは遥か上空にあるディレクショナルライトのシャドウマップ。単純計算で全方位シャドウは6倍のコストがかかる。うーん、これはほいほいと毎フレーム計算したくないな。-Zの面なので、雰囲気的にそれらしく描画はできているようだ。シャドウそのものを実装したわけではないので、確信はないが。今週中にポイントライトの実際のシャドウの計算をHLSLを使って実装していきたい。00:54HLSLのTextureCubeの情報が少なすぎるTweet広告を非表示にするもっと読むコメントを書く2018-03-02レンダリングエンジン進捗まとめ前回のポエムが長くなり過ぎたので二回に分けた。現在、レンダリングエンジンに対するコミット数は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の実装的な補足を加えつつその記事を紹介する記事を書きたいものだ。Qiita21日目あたりPMDモデルだけでは限界を感じてきたので、.xモデルのローダも作成した。バイナリフォーマットは面倒くさそうだし、MSDNの資料も微妙だったので、テキストフォーマットにだけ対応することにした。テキストフォーマットなら、仕様は実際のファイルから読み取ることができる。ダーティーなハックになるが、いろんなファイルをかき集めて、ロードに失敗するたびにファイルの中身を確認し、想定漏れの仕様等を毎回カバーする形で実装していけば、いずれ完全なローダになる。C++のクソみたいな文字列処理で頑張って実装してもよかったのだが、いい機会だったのでパーサコンビネータを作成した。Boost.Spirit.Qiを採用したが、最終的なアウトプットはまあそこそこ美しい反面、あまりにも開発が苦痛だった。構文や型を間違えるだけで尋常じゃない量のコンパイルエラーを吐くからだ。人類には早すぎるライブラリな気もする。ユーザー定義構造体をタプルにバインドして、美しく型安全な構文ルールを定義し、パースする仕組み。うまく使えればとても気持ちが良い。私自身はTMPが大好きなので、正直使ってて興奮した。22日目~35日目リアルが立て込んだせいで日付が一気に飛んでいるが、主にひたすら.xモデルの仕様網羅と最適化をしていた。.xモデルの仕様として、頂点リスト-面リスト-材質リストという順に定義されている。面リストは、一つの面につき複数の頂点への参照を持つ。材質リストは、面リストすべてのマテリアルを定義する。面一つ一つが独自のマテリアルを持つことが可能(仕様上)であり、そのまま愚直に実装すると、ドローコールが面の数だけ発生してしまう。描画最適化のために、まず面リストを同種のマテリアルでグループ化し、グループ化された面リストを一つのインデックスリストに展開するようにした。すれば、ドローコールはマテリアルの数だけで済む。PMDモデルと一緒だ。面の順序を変える形になるので、面が連続ではなくなる。そのため、ストリップ描画をすると、離れた面へ描画していくとき、その軌跡に謎の三角形が生じてしまう。頂点数4以上の面は、複数の三角形に分解し、三角形リストとして描画することにした。あとは、モデルによっては頂点法線がデータに入っていないことがあったので、その際の計算も行うようにした。頂点が所属するすべての面の法線を合成した正規化ベクトルが頂点法線になる。三角形の面の法線はある点から他の二点への位置ベクトルの外積で求まるが、四角形はそうはいかない。対角ベクトル二本の外積を求める必要がある。環境キューブマップの実装をしている。描画に使う、6面の周囲情報をベイクしたテクスチャのことだ。これを使うと、ポイントライトの全方位シャドウや、リフレクションが実装可能になる。ポイントライトのシャドウは結構面倒なのだ。リフレクションが実装できれば、鏡面に周りのオブジェクトの映り込みが実現できるので、おそらく更に”それっぽさ”が向上するだろう。一か月ちょっとを駆け抜けてみた。いろんなことをやったようで、まだ何もしてないようにも思える。シャドウの品質を上げるためのキャスケードシャドウや、間接光計算実現のためのフォトンマッピング、アンビエントオクルージョン、そもそもディファードレンダリングすら実装できていない。そのほか、ライトの色情報や、その輝度だったり、そもそもライトが面積を持つことも想定していない。いうか、こういう細かいことを挙げていたらキリがない。まともな見栄えのレンダリングができるまで、少なく見積もってもあと半年はかかると思っている。最終的な目標は、国産のPMX・PMDデフォルト対応の高品質レンダリングエンジンだが、まあ、数年がかりになるだろうし、そこまで到達できるとは思っていない。今は自分が楽しんで、学ぶために開発している。それがほかのレンダリングに興味のある人間の刺激になればなおよい。次からは、ちゃんとした開発日記をつけていこうと思う。20:56レンダリングエンジン進捗まとめTweet広告を非表示にするもっと読むコメントを書く次のページプロフィールid:riyaaaaasan東京で働くゲームエンジニア。ブログを報告するスターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。