DirectX12で1からフレームワーク制作(5)
はじめに
こんにちは、はろ~です。
今回は、モデルのロードから描画までしていきます。
開発環境はVisualStudio2022です。
Github:https://github.com/nakanoyui/DirectX12Framework
バグの修正
Heap.hのCreate関数の実装がバグを引き起こす可能性がありますので、修正しておいてください。
座標変換
まず現在は座標変換を全く行っていません。座標変換ってなんやねんという話ですが、順を追って説明していきます。
射影行列
最終的にディスプレイに表示される画面は射影座標という座標系にテクスチャが描画されている状態になります。
Blenderで簡易的に表現してみましたがこんな感じです。X軸は-1~1・Y軸は-1~1・Z軸は0~1です。まさにこのモデルです。で現在メッシュで座標を指定していますが、この座標が射影座標系の座標になります。
Blenderで表すとこんな感じです。
手前のポリゴンに今はテクスチャは張り付けてる状態です。
で、ポリゴン張り付けるだけとかならこれでもいいんですけどこれからモデルを描画するってなっていく中でこのやり方でやってると座標を1以上使うことが出来なくなります(モデルが画面外に👋)。
なので、モデルを射影座標に変換するための行列を作成してShader側(GPU)で計算してあげます。
定数バッファ
ここでShaderに数値データを送る時に便利なリソース定数バッファを使います。CPU側で数値の変更を行い、GPUにそのデータを伝える役目を果たします。ただ前回にCBVSRVUAVHeapというものを作成しているので、今回はこのヒープを使ってデータの書き込みとバッファーの作成処理だけ行います。
Graphicsフォルダ以下にCBufferAllocaterフォルダを作成し、CBufferAllocater.cpp/hを追加してください。
CBufferAllocater.hの実装は以下の通りです。
CBufferAllocater.cppの実装は以下の通りです。
で、ビルドするとエラーが何点か出るので対応していきます。
CBVSRVUAVHeapにGetUseCount関数とGetHep関数がないと怒られるので追加していきます。
これでエラーは取れたはずです。次に定数バッファーのアクセスを基本どこでも使いたいのでSystem.hに追加していおきましょう。
それでは、さっそく使っていきたいのでGraphicsDeviceにCBufferAllocaterを追加しましょう。
前方宣言の追加
変数宣言の追加
取得関数の追加
GraphicsDevice.cppのInitで作成を行いましょう。
これで定数バッファを使用する事ができます。さっそく使用していきましょう。
Application.cppのShaderのRangeTypeにCBVを追加、プロジェクション(射影)行列の作成、
CBufferAllocaterの使用番号の初期化、プロジェクション行列情報のバインドの四つを加えてください。
これでCPU側の処理は終了です。Shader(GPU)側を更新していきましょう。
inc_SimpleShader.hlsliで定数バッファの受け取りを行います。
SimpleShader_VS.hlslでプロジェクション行列を合成し、座標変換を行います。
これで実行してみてください。
何も描画されなくなりました。結構この辺りは出力にエラーも出ないので陥りやすいバグなので、わざと引き起こしました。皆さんにも考えながら読んで欲しいのですが、現在CBVを1つ、SRVを1つ使用しています。現在の設計的にCBVをヒープにバインドした後にSRVをバインドUAV…となっていくのですが、SRVのバインド数字がCBV分の考慮がされていないためSRVの情報を取ってこれていません。なのでCBVの個数を記憶しておかないといけません。
Shader.hにCBVの個数を記憶するためのカウンタ変数と取得関数を追加しましょう。
Shader.cppのRootSignature作成関数の引数にcbvCountを参照させて渡しましょう。
RootSignature.hの作成関数の宣言に引数を追加しましょう。
RootSignature.cppの作成関数の定義にも引数を追加。
RootSignature.cppの作成関数のローカル変数cbvCountを削除しましょう。
ここまで出来たらApplication.cppのテクスチャをセットしているところにCBVカウントを足してあげてください。
実行してみてください。バックバッファカラーではなくなりましたが、私の場合は真っ黒になってしまいました。
これは単純にメッシュのZが0の地点にあって視点から近すぎて拡大されて表示されているからです。ここでビュー(カメラ)行列を作成して対応しましょう。
ビュー行列
ビュー行列はカメラ行列とも言いますが、実際にカメラが存在する訳ではありません。簡単に言うと疑似的なカメラを作成しているだけです。例えば、Xに+1したカメラがあったらモデルはX-1の位置に表示されますよね?
Blender表すとこんなイメージ(モデルのZ軸は一旦無視してください。)
実際に動いてるのはモデルだけど、カメラが動いてるように見えます。これを利用するとあたかもカメラが動いてるように見えるという感じです。
先程作成したCBufferAllocaterフォルダ以下にCBufferDataフォルダを作成し、CBufferData.hを追加してください。
ここに今度から定数バッファで使用するデータを書いていくようにします。
CBufferData.hの実装は以下の通りです。
System.hにCBufferData.hを追加しておいてください。
Application.cppにビュー行列と定数バッファデータのカメラを追加して両方に代入しておいてください。
送るデータをプロジェクション行列からカメラ情報に変更してください。
Shader(GPU)側の受け皿を変更します。inc_SimpleShader.hlsliを更新してください。
SimpleShader_VS.hlslの行列計算も更新してください。
ここまで出来たら実行してください。ちゃんとテクスチャが描画されたら成功です。
モデル描画
ここまでの実装が長すぎて大変でしたね。ただネイティブでここまで書いた皆さんなら分かると思いますが、「モデルの描画」が簡単に終わる訳がないのだ。。。と言うわけでやっていきましょう。
assimp
モデルのロードに際してassimpと言う神ライブラリを使用させて頂きます。
概要:.obj/.fbx/.gltfなどメジャーなモデル形式からマイナー形式までほとんど全てに対応しています。詳しくは公式をご覧ください。URL:https://github.com/assimp/assimp
でいつのもようにNugetでライブラリを導入したいところですが、執筆時点ではVisualStudio2022に対応したものが私の確認した限りでは見つかりませんでした。(あったらすみません。共有してくださると助かります。)
まず外部ライブラリの導入をしたことがないも人いると思うので、結構丁寧に説明しながら進めていきますので、不必要な人は読み飛ばして頂いて構いません。
assimpを使うには.libと.dllと言うものが必要になります。両方ともライブラリという部分では同じなのですが、libはコンパイル時.exeに取り込まれて、.dllはコンパイル時に.exeとは分離します。
とまぁとりあえずこの二つが必要なので入手していきます。
CMake
CMakeのインストールが必要です。こちらのURL:https://cmake.org/ を踏んでください。
Donwloadをクリック
Windows x64 Installer:cmake-3.27.0-rc3-windows-x86_64.msiをクリック
Setupがダウンロード出来たら起動してください。
Nextをクリック
Licenseに許諾してNextをクリック
Add CMake to the system Path for all usersにチェックを入れてNextをクリック
自身の好きな場所を選択してNextをクリック
Installをクリック
Finishをクリック
CMakeをインストール出来たらコマンドプロンプトを開いてください。
まず一応使えるか確認しましょう。
「cmake --version」でcmakeのバージョンが出てきたら使用可能です。
それでは順番にビルドしていきましょう。
「cd Downloads」→「git clone https://github.com/assimp/assimp.git」 →「cd assimp」→「mkdir build」→「cd build」→「cmake ..」→「cmake --build . --config Debug」→「cmake --build . --config Release」以上です。
ビルドが両方終わったらダウンロードにassimpというフォルダが作成されていると思います。
①:assimp→build→lib→Debug→「assimp-vc143-mtd.lib」
②:assimp→build→lib→Release→「assimp-vc143-mt.lib」
③:assimp→build→bin→Debug→「assimp-vc143-mtd.dll」
④:assimp→build→bin→Relase→「assimp-vc143-mt.dll」
⑤:assimp→build→include→assimp→「config.h」
⑥:assimp→include→assimp(フォルダ)
⑦:assimp→contrib→utf8cpp(フォルダ)
この7つを使います。ソリューションがあるディレクトリにLibraryフォルダを作成してください。Libraryフォルダ構成を以下のようにしてください。
ライブラリの導入
それではここからは実際にライブラリを導入していきます。
ソリューションのプロパティを開いてください。
構成プロパティ→C/C++→全般→追加のインクルードディレクトリにLibrary/assimp/includeを追加
構成プロパティ→リンカー→全般→
追加のライブラリディレクトリにLibrary/assimp/build/lib/$(Configuration)を追加
構成をDebugに変更してください。
構成プロパティ→リンカー→入力→DLLの遅延読み込みにassimp-vc143-mtd.dllを追加
構成をRelaseに変更してください。
構成プロパティ→リンカー→入力→DLLの遅延読み込みにassimp-vc143-mt.dllを追加
構成をすべての構成に戻しておいてください。これで準備完了です。
ModelLoader
ここからはモデルのローダーを実装していきます。
まず最初にMeshクラスが板ポリ描画以外に対応できないので、変更を加えます。
メッシュの頂点情報に関しては今後様々なパターンが予想されるので、まとめておきます。
Meshフォルダの中にMeshDataフォルダを作成し、MeshData.hを追加してください。
MeshData.hの実装は以下の通りです。
Mesh.hを更新してください。
Mesh.cppを更新してください。
Graphicsフォルダ以下にModelフォルダを作成し、Model.cpp/hとModelLoader.cpp/hを追加してください。
Model.hの実装は以下の通りです。
Model.cppの実装は以下の通りです。
ModelLoader.hの実装は以下の通りです。
ModelLoader.cppの実装は以下の通りです。
System.hにModel.hを追加しておいてください。
ここで一度ビルドをしてください。当たり前ですがエラーがいくつか出ているので対応していきます。
Application.cppを更新してください。
次はShaderクラスの更新を行いますが、この後モデルを描画するのでついでにモデルの描画機能も追加しておきます。
Shader.hを更新してください。
Shader.cppの更新をしてください。
メッシュの描画をする際にインスタンス数が必要でしたが、記憶していなかったのでMeshクラスにインスタンス数を覚えておく変数と取得関数を追加しましょう。
Mesh.hを更新してください。
Mesh.cppを更新してください。
ディレクトリパスを取得するための関数に関してはUtilityに追加しておきましょう。
Utility.hを更新してください。
残り二つのエラーに関しては私側でマクロを勝手に作成しました。
と言うことでマクロを追加していきますが、こちらは自身のソースではなくassimpのソースファイルのmaterial.hに追加します。Library/assimp/include/assimp/material.hを開いてください。
material.hを更新してください。
ここまで実装するととりあえずビルドに成功すると思います。モデルデータを持っている人はそのモデルを使ってください。
持っていない方はGithub:https://github.com/nakanoyui/DirectX12Framework こちらにモデルデータのCube.gltfがあるのでお使いください。
描画準備
しばらく時間を空けていましたので、忘れている方もいるかもですが.dllの対応が実はまだ終わっていません。.dllの説明の時に「.dllはコンパイル時に.exeとは分離します」と言いました通り.dllは.exeに埋め込まれる訳ではないので、同じディレクトリに置くかその他の対応が必要です。以前に.dllの遅延読み込み設定を行いましたが、.dllを使用する処理が通る際に読み込みが走りますが今のままでは読み込みに失敗します。実際に実行してみると例外スローが吐かれると思います。なので、対応します。
Application.hを更新してください。
Application.cppを更新してください。
これで実行すると例外スローは吐かれなくなるはずです。まぁ他の部分で怒られるので動きませんが。
さてモデルの準備やら.dllの対応が出来たので描画と行きたいところですが、Shader側の対応とApplication部分の改造をまだしないといけませんのでやっていきましょう。
Application.cppを更新してください。
inc_SimpleShader.hlsliを更新してください。
SimpleShader_VS.hlslを更新してください。
SimpleShader_PS.hlslを更新してください。
ここまで実装出来たら実行してみてください。Cubeが描画されていたら完璧です👍
ワールド行列
最後にワールド行列(モデル行列)を実装しておきましょう。モデル毎に行列がある方が自然ですしね。
Application.cppを更新してください。
受け皿も作成しましょう。inc_SimpleShader.hlsliを更新してください。
SimpleShader.hlslを更新してください。
実行するとCubeがぐるぐる回っていると思います。これで終わりです。
最後に
お疲れ様でした。今回はかなりのボリュームでしたが、無事付いてくる事ができましたでしょうか?
次回の予定はアニメーションを実装しようと考えていますのでお楽しみに~。
今後も続きを記事にするつもりですが、更新頻度に関しては不定期になります。(気が向いたら更新します)
著者は学生なので(保険)、多々間違える事があると思いますがその時は温かい目で見ていただき指摘してくださると助かります。
Discussion