こんにちわ、Pocolです。
これは,レイトレ合宿2022のアドベントカレンダー7/24の記事です。
これからしょうもないことを書くので,罪滅ぼしとして役に立つかもしれない記事も用意しておきました(こちら)。予め謝っておきます。ごめんなさい。
さて,前回のレイトレ合宿からGPU実装をする方が増えてきたので,この波に乗るために今回はDXRを用いた基本的なパストレーシングの実装を紹介しようと思います。
1. 準備
今回は,Shader Model 6.6以降を使って実装を行います。そのため,最新版のDirectX Shader Compiler (dxc)とAgility SDKを自前のプロジェクトに導入してください。Agility SDKの導入方法については,「Direct3D 12ゲームグラフィックス実践ガイド」の巻末付録 Appendix Bにも簡単に書いてありますので,そちらをご参照ください。(ヨシ!自然な感じで宣伝できたぞ)
DXRの基本的なセットアップ処理等については,「DirectX Raytracing入門」でも書きましたが,最近一個ハマりポイントがありました。
まず,DXRを使ったプログラムを実行する際にハードウェアがDXR対応しているかどうかをチェックする必要があります。チェックするコードは下記のようになります。
//----------------------------------------------------------------------------- // DXRがサポートされているかどうかチェックします. //----------------------------------------------------------------------------- bool IsSupportDXR(ID3D12Device6* pDevice) { D3D12_FEATURE_DATA_D3D12_OPTIONS5 options = {}; auto hr = pDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5, &options, sizeof(options)); if (FAILED(hr)) { return false; } return options.RaytracingTier != D3D12_RAYTRACING_TIER_NOT_SUPPORTED; }
このコード自体は問題ないのですが,なんとID3D12Deviceの作成の仕方によって,このコードが正しく動作しない場合があるということが分かりました。発覚の発端となったのは,自宅にあるデスクトップPCだとこのIsSupportDXR()がtrueで動作するのですが,レイトレ合宿の現地にもっていくように新たに調達したRTXのノートPCだと,falseを返してDXRが動作しないという問題が発生しました。で,試しにMicrosoftのサンプルを実行してみると正しく動作する状態。
なぜだ?と思って,ソースコードを比較してみた結果,前述したとおりにID3D12Deviceの作成の仕方が異なっており,チェックが通らないようでした。チェックが通らない状態のときの実装コードは下記のようになっていました。
asdx::RefPtr<ID3D12Device> device; auto hr = D3D12CreateDevice( nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(device.GetAddress()) ); if (FAILED(hr)) { ELOG("Error : D3D12CreateDevice() Failed. errcode = 0x%x", hr); return false; }
これを,下記のように変更することでIsSupportDXR()が期待通りにtrueを返すようになりました。
asdx::RefPtr<ID3D12Device> device; auto hr = D3D12CreateDevice( m_pAdapter.GetPtr(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(device.GetAddress()) ); if (FAILED(hr)) { ELOG("Error : D3D12CreateDevice() Failed. errcode = 0x%x", hr); return false; }
IDXGIAdapter1を第1引数に渡すと期待通りの動作結果になりました。そして,この肝心のIDXGAIAdapter1ですが,下記のように生成しています。
// DXGIアダプター生成. { RefPtr<IDXGIAdapter1> pAdapter; for(auto adapterId=0; DXGI_ERROR_NOT_FOUND != m_pFactory->EnumAdapterByGpuPreference(adapterId, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, IID_PPV_ARGS(pAdapter.GetAddress())); adapterId++) { DXGI_ADAPTER_DESC1 desc; auto hr = pAdapter->GetDesc1(&desc); if (FAILED(hr)) { continue; } hr = D3D12CreateDevice(pAdapter.GetPtr(), D3D_FEATURE_LEVEL_11_0, __uuidof(ID3D12Device), nullptr); if (SUCCEEDED(hr)) { m_pAdapter = pAdapter.Detach(); break; } } }
IDXGIFactory7からEnumAdapterByGpuPreference()をDXGI_GPU_PREFERENCE_HIGH_PERFORMANCEで指定して呼び出し,最も高い処理実行を行うGPUを列挙して,そのなかでD3D12CreateDevice()が成功したものをIDXGIAdapter1として採用します。どうもこのDXGI_GPU_PREFERENCE_HIGH_PERFORMANCEというのがミソのようで,このようにMicrosoftのサンプルは実装していました。…ということで,ハマりポイントなので皆さん気を付けてください。
つづいて,ShaderModel 6.6以降に対応しているかどうもチェックしてください。チェックコードは次のような感じです。
// ShaderModel 6.6以降対応かどうかチェック. { D3D12_FEATURE_DATA_SHADER_MODEL shaderModel = { D3D_SHADER_MODEL_6_6 }; auto hr = pDevice->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel)); if (FAILED(hr) || (shaderModel.HighestShaderModel < D3D_SHADER_MODEL_6_6)) { ELOG("Error : Shader Model 6.6 is not supported."); return false; } }
これでDXRの使用がOKで,ShaderModel6.6も使用可能ということが確認できるので基本的な準備は完了です。
2. GPU実装
まずは,パストレ用のシェーダを用意します。下記のような感じです。
//----------------------------------------------------------------------------- // レイ生成シェーダです. //----------------------------------------------------------------------------- [shader("raygeneration")] void OnGenerateRay() { // 乱数初期化. uint4 seed = SetSeed(DispatchRaysIndex().xy, SceneParam.FrameIndex); float2 pixel = float2(DispatchRaysIndex().xy); const float2 resolution = float2(DispatchRaysDimensions().xy); // アンリエイリアシング. float2 offset = float2(Random(seed), Random(seed)); pixel += lerp(-0.5f.xx, 0.5f.xx, offset); float2 uv = (pixel + 0.5f) / resolution; uv.y = 1.0f - uv.y; pixel = uv * 2.0f - 1.0f; // ペイロード初期化. Payload payload = (Payload)0; // レイを設定. RayDesc ray = GeneratePinholeCameraRay(pixel); float3 W = float3(1.0f, 1.0f, 1.0f); // 重み. float3 L = float3(0.0f, 0.0f, 0.0f); // 放射輝度. for(int bounce=0; bounce<SceneParam.MaxBounce; ++bounce) { // 交差判定. TraceRay( SceneAS, RAY_FLAG_NONE, 0xFF, STANDARD_RAY_INDEX, 0, STANDARD_RAY_INDEX, ray, payload); if (!payload.HasHit()) { #if FURNANCE_TEST L += W * float3(0.5f, 0.5f, 0.5f); // Furnance Test. #else L += W * SampleIBL(ray.Direction); #endif break; } // 頂点データ取得. Vertex vertex = GetVertex(payload.InstanceId, payload.PrimitiveId, payload.Barycentrics); // マテリアル取得. Material material = GetMaterial(payload.InstanceId, vertex.TexCoord, 0.0f); float3 B = normalize(cross(vertex.Tangent, vertex.Normal)); float3 N = FromTangentSpaceToWorld(material.Normal, vertex.Tangent, B, vertex.Normal); float3 geometryNormal = vertex.GeometryNormal; float3 V = -ray.Direction; if (dot(geometryNormal, V) < 0.0f) { geometryNormal = -geometryNormal; } // 面の下に潜っている場合は反転. if (dot(geometryNormal, N) < 0.0f) { N = -N; } // 自己発光による放射輝度. L += W * material.Emissive; // 最後のバウンスであれば早期終了(BRDFをサンプルしてもロジック的に反映されないため). if (bounce == SceneParam.MaxBounce - 1) { break; } // シェーディング処理. float3 u = float3(Random(seed), Random(seed), Random(seed)); float3 dir; float pdf; float3 brdfWeight = EvaluateMaterial(V, N, u, material, dir, pdf); // ロシアンルーレット. if (bounce > SceneParam.MinBounce) { float p = min(0.95f, Luminance(brdfWeight)); if (p > Random(seed)) { break; } } W *= brdfWeight / pdf; // 重みがゼロに成ったら以降の更新は無駄なので打ち切りにする. if (all(W < (1e-6f).xxx)) { break; } // レイを更新. ray.Origin = OffsetRay(vertex.Position, geometryNormal); ray.Direction = dir; } uint2 launchId = DispatchRaysIndex().xy; float3 prevL = Canvas[launchId].rgb; float3 color = (SceneParam.EnableAccumulation) ? (prevL + L) : L; Canvas[launchId] = float4(color, 1.0f); } //----------------------------------------------------------------------------- // ヒット時のシェーダ. //----------------------------------------------------------------------------- [shader("closesthit")] void OnClosestHit(inout Payload payload, in HitArgs args) { payload.InstanceId = InstanceID(); payload.PrimitiveId = PrimitiveIndex(); payload.Barycentrics = args.barycentrics; } //----------------------------------------------------------------------------- // ミスシェーダです. //----------------------------------------------------------------------------- [shader("miss")] void OnMiss(inout Payload payload) { payload.InstanceId = INVALID_ID; }
今回は,Next Event Estimation(NEE)等は使わない単純な実装にしてあります。
まず,パストレ用の乱数ですが,これはRaytracing Gems2の「Chapter 14. THE REFERENCE PATH TRACER」([Boksansky and Marrs 2021])を元に,PCG(Permuated Conguential Genetor)で実装しています。
乱数のシードは次の関数で設定します。
//----------------------------------------------------------------------------- // 乱数のシード値を設定します.. //----------------------------------------------------------------------------- uint4 SetSeed(uint2 pixelCoords, uint frameIndex) { return uint4(pixelCoords.xy, frameIndex, 0); }
乱数の生成は,次のコードで行います。
//----------------------------------------------------------------------------- // Permuted Congruential Generator (PCG) //----------------------------------------------------------------------------- uint4 PCG(uint4 v) { v = v * 1664525u + 101390422u; v.x += v.y * v.w; v.y += v.z * v.x; v.z += v.x * v.y; v.w += v.y * v.z; v = v ^ (v >> 16u); v.x += v.y * v.w; v.y += v.z * v.x; v.z += v.x * v.y; v.w += v.y * v.z; return v; } //----------------------------------------------------------------------------- // floatに変換します. //----------------------------------------------------------------------------- float ToFloat(uint x) { return asfloat(0x3f800000 | (x >> 9)) - 1.0f; } //----------------------------------------------------------------------------- // 疑似乱数を取得します. //----------------------------------------------------------------------------- float Random(inout uint4 seed) { seed.w++; return ToFloat(PCG(seed).x); }
PCG4dで乱数を実装しています。PCG4Dについては[Jarzynski and Olano 2020]で説明されているようなので,そちらの論文を参照してください。
GeneratePinholeCameraRay()ですが,この関数の定義は次のようになっています。
//----------------------------------------------------------------------------- // スクリーン上へのレイを求めます. //----------------------------------------------------------------------------- RayDesc GeneratePinholeCameraRay(float2 pixel) { float4 orig = float4(0.0f, 0.0f, 0.0f, 1.0f); // カメラの位置. float4 screen = float4(pixel, 0.0f, 1.0f); // スクリーンの位置. orig = mul(SceneParam.InvView, orig); screen = mul(SceneParam.InvViewProj, screen); screen.xyz /= screen.w; RayDesc ray; ray.Origin = orig.xyz; ray.Direction = normalize(screen.xyz - orig.xyz); ray.TMin = 0.0f; ray.TMax = FLT_MAX; return ray; }
動作は正常にしますが,行列の掛け算が重いので,基底ベクトルを元にレイを飛ばす実装にした方がパフォーマンスは稼げそうなので,[hole 2014]等を参考にここは書き換えた方が良いかと思います。
あとは,まあ見ればざっくりしたところはわかると思うので,一番話したい内容である,インスタンスデータやマテリアルデータの管理について説明します。
3. インスタンスデータとマテリアルデータの管理
頂点データとマテリアルデータはメッシュやメッシュに設定されているマテリアルによって異なるため,複数のデータが存在することになります。一般的なラスタライザーを使ったレンダリングでは,マテリアル単位ごとにドローコールを分け,シェーダやテクスチャをバインドして描画することを行いますが,レイトレーシングを用いる場合は,様々なサーフェイスにヒットする可能性があるため,そのたびにマテリアルシェーダやテクスチャをバインドし直す必要がありますが,残念ながらGPU側からCPUコードを実行することはできないため,適切なタイミングでバインドし直すことが不可能です。そのため,頂点とマテリアルデータをどうやって適宜変更するかということが実装上の問題になってきます。幸いにして,Direct3D 12ではローカルルートシグニチャを用いて変更することが可能です。これについては「シェーダテーブル」で紹介しました。残念ながら,Vulkanにはこの機能はありません。また,ローカルルートシグニチャがあるとはいえ,設定等が面倒です。そこで,一般的にはBindless Resourceを用いた実装を行うことが多いです。これは前述した[Boksansky and Marrs 2021]でもBindless Resourceによる実装が行われています。
実装例としては下記のようにシェーダ上で配列として宣言する感じです。
// Bindless materials, geometry, and texture buffers (for all the scene geometry) StructuredBuffer<MaterialData> materials : register(t1); ByteAddressBuffer indices[MAX_INSTANCES_COUNT] : register(t0, space1); ByteAddressBuffer vertices[MAX_INSTANCES_COUNT] : register(t0, space2); Texture2D<float4> textures[MAX_TEXTURES_COUNT] : register(t0, space3);
配列として使用するためには連続メモリである必要があるため,ディスクリプタを連続メモリとして確保しておく必要があり,これが実装上で面倒になることもあります。
そこで,今回はShader Model 6.6から導入されたDynamic Resourcesを用いて,実装を行うことにしました。Dynamic Resourcesについては「バインドレスリソース」の記事で説明しています。この機能なざっくり言うと,シェーダ上でインデックスを指定するだけで,テクスチャやバッファといったリソースにアクセスすることが可能となる機能で,CPUからコマンドにてバインドを指定する必要がありません。そこで,テクスチャにアクセスするための番号をテクスチャ情報を格納し,これをStructuredBufferとして表現し,マテリアルIDからStructuredBufferにアクセス⇒次にテクスチャ情報からテクスチャを取得という流れで,マテリアルを表現するように実装しました。
同様にして,インタンスデータもインスタンスデータが格納されているStructuredBufferを用意しておき,レイトレーシングして,交差情報から取得できるインスタンスIDを用いて,データにアクセスするという感じです。
実装は下記のような感じです。今回の実装では,StructuredBufferではなく,ByteAddressBufferとして実装しています。
/////////////////////////////////////////////////////////////////////////////// // MeshVertex structure /////////////////////////////////////////////////////////////////////////////// struct MeshVertex { float3 Position; float3 Normal; float3 Tangent; float2 TexCoord; }; /////////////////////////////////////////////////////////////////////////////// // MeshMaterial structure /////////////////////////////////////////////////////////////////////////////// struct MeshMaterial { uint4 Textures; // x:BaseColorMap, y:NormalMap, z:OrmMap, w:EmissiveMap. }; /////////////////////////////////////////////////////////////////////////////// // Instance structure /////////////////////////////////////////////////////////////////////////////// struct Instance { uint VertexId; // 頂点番号. uint IndexId; // 頂点インデックス番号. uint MaterialId; // マテリアル番号. }; /////////////////////////////////////////////////////////////////////////////// // Material structure /////////////////////////////////////////////////////////////////////////////// struct Material { float4 BaseColor; // ベースカラー. float3 Normal; // 法線ベクトル. float Roughness; // ラフネス. float Metalness; // メタルネス. float3 Emissive; // エミッシブ. }; ByteAddressBuffer Instances : register(t1); ByteAddressBuffer Materials : register(t2); ByteAddressBuffer Transforms : register(t3); #define VERTEX_STRIDE (sizeof(MeshVertex)) #define INDEX_STRIDE (sizeof(uint3)) #define MATERIAL_STRIDE (sizeof(MeshMaterial)) #define INSTANCE_STRIDE (sizeof(Instance)) #define TRANSFORM_STRIDE (sizeof(float3x4)) //----------------------------------------------------------------------------- // 頂点インデックスを取得します. //----------------------------------------------------------------------------- uint3 GetIndices(uint indexId, uint triangleIndex) { uint address = triangleIndex * INDEX_STRIDE; ByteAddressBuffer indices = ResourceDescriptorHeap[indexId]; return indices.Load3(address); } //----------------------------------------------------------------------------- // 頂点データを取得します. //----------------------------------------------------------------------------- Vertex GetVertex(uint instanceId, uint triangleIndex, float2 barycentrices) { uint2 id = Instances.Load2(instanceId * INSTANCE_STRIDE); uint3 indices = GetIndices(id.y, triangleIndex); Vertex vertex = (Vertex)0; // 重心座標を求める. float3 factor = float3( 1.0f - barycentrices.x - barycentrices.y, barycentrices.x, barycentrices.y); ByteAddressBuffer vertices = ResourceDescriptorHeap[id.x]; float3 v[3]; float4 row0 = Transforms.Load4(instanceId * TRANSFORM_STRIDE); float4 row1 = Transforms.Load4(instanceId * TRANSFORM_STRIDE + 16); float4 row2 = Transforms.Load4(instanceId * TRANSFORM_STRIDE + 16); float3x4 world = float3x4(row0, row1, row2); [unroll] for(uint i=0; i<3; ++i) { uint address = indices[i] * VERTEX_STRIDE; v[i] = asfloat(vertices.Load3(address)); float4 pos = float4(v[i], 1.0f); vertex.Position += mul(world, pos).xyz * factor[i]; vertex.Normal += asfloat(vertices.Load3(address + NORMAL_OFFSET)) * factor[i]; vertex.Tangent += asfloat(vertices.Load3(address + TANGENT_OFFSET)) * factor[i]; vertex.TexCoord += asfloat(vertices.Load2(address + TEXCOORD_OFFSET)) * factor[i]; } vertex.Normal = normalize(mul(world, float4(vertex.Normal, 0.0f)).xyz); vertex.Tangent = normalize(mul(world, float4(vertex.Tangent, 0.0f)).xyz); float3 e0 = v[2] - v[0]; float3 e1 = v[1] - v[0]; vertex.GeometryNormal = normalize(cross(e0, e1)); return vertex; } //----------------------------------------------------------------------------- // マテリアルを取得します. //----------------------------------------------------------------------------- Material GetMaterial(uint instanceId, float2 uv, float mip) { uint materialId = Instances.Load(instanceId * INSTANCE_STRIDE + 8); uint address = materialId * MATERIAL_STRIDE; uint4 data = Materials.Load4(address); Texture2D<float4> baseColorMap = ResourceDescriptorHeap[data.x]; float4 bc = baseColorMap.SampleLevel(LinearWrap, uv, mip); Texture2D<float4> normalMap = ResourceDescriptorHeap[data.y]; float3 n = normalMap.SampleLevel(LinearWrap, uv, mip).xyz * 2.0f - 1.0f; n = normalize(n); Texture2D<float4> ormMap = ResourceDescriptorHeap[data.z]; float3 orm = ormMap.SampleLevel(LinearWrap, uv, mip).rgb; Texture2D<float4> emissiveMap = ResourceDescriptorHeap[data.w]; float3 e = emissiveMap.SampleLevel(LinearWrap, uv, mip).rgb; Material param; param.BaseColor = bc; param.Normal = n; param.Roughness = orm.y; param.Metalness = orm.z; param.Emissive = e; return param; }
ベースカラーマップや法線マップ等,一部テクスチャが無いものについて事前にデフォルトテクスチャを作成しておき,このデフォルトテクスチャを参照するようにしています。
このようにDynamic Resourcesを使うことで,インスタンスやマテリアル周りで設計を悩むことが少なくなり,非常にスッキリ書けるようになるのでおすすめです。
4. CPU実装
続いて,CPU実装ですが… 先ほど紹介したようにByteAddressBufferにインスタンスやマテリアルデータを詰め込む必要があるため,これを管理するクラスを使って,そこにpush_back()していく感じで作りました。
クラスの宣言は次のような感じです。
/////////////////////////////////////////////////////////////////////////////// // Vertex structure /////////////////////////////////////////////////////////////////////////////// struct Vertex { asdx::Vector3 Position; asdx::Vector3 Normal; asdx::Vector3 Tangent; asdx::Vector2 TexCoord; }; /////////////////////////////////////////////////////////////////////////////// // Mesh structure /////////////////////////////////////////////////////////////////////////////// struct Mesh { uint32_t VertexCount; //!< 頂点数です. uint32_t IndexCount; //!< インデックス数です. const Vertex* Vertices; //!< 頂点データです. const uint32_t* Indices; //!< インデックスデータです. }; /////////////////////////////////////////////////////////////////////////////// // Material structure /////////////////////////////////////////////////////////////////////////////// struct Material { uint32_t BaseColor; //!< ベースカラー. uint32_t Normal; //!< 法線マップ. uint32_t ORM; //!< オクルージョン/ラフネス/メタリック. uint32_t Emissive; //!< エミッシブ. }; /////////////////////////////////////////////////////////////////////////////// // GeometryHandle structure /////////////////////////////////////////////////////////////////////////////// struct GeometryHandle { D3D12_GPU_VIRTUAL_ADDRESS AddressVB = 0; //!< 頂点バッファのGPU仮想アドレスです. D3D12_GPU_VIRTUAL_ADDRESS AddressIB = 0; //!< インデックスバッファのGPU仮想アドレスです. uint32_t IndexVB = 0; //!< 頂点バッファのハンドルです. uint32_t IndexIB = 0; //!< インデックスバッファのハンドルです. }; /////////////////////////////////////////////////////////////////////////////// // Instance structure /////////////////////////////////////////////////////////////////////////////// struct Instance { uint32_t VertexBufferId; //!< 頂点バッファのハンドルです. uint32_t IndexBufferId; //!< インデックスバッファの uint32_t MaterialId; //!< マテリアルID. }; /////////////////////////////////////////////////////////////////////////////// // InstanceHandle structure /////////////////////////////////////////////////////////////////////////////// struct InstanceHandle { uint32_t InstanceId; //!< インスタンスID. D3D12_GPU_VIRTUAL_ADDRESS AddressTB; //!< トランスフォームバッファのGPU仮想アドレスです. }; /////////////////////////////////////////////////////////////////////////////// // ModelMgr class /////////////////////////////////////////////////////////////////////////////// class ModelMgr { //========================================================================= // list of friend classes and methods. //========================================================================= /* NOTHING */ public: //========================================================================= // public variables. //========================================================================= /* NOTHING */ //========================================================================= // public methods. //========================================================================= //------------------------------------------------------------------------- //! @brief コンストラクタ //------------------------------------------------------------------------- ModelMgr() = default; //------------------------------------------------------------------------- //! @brief デストラクタ. //------------------------------------------------------------------------- ~ModelMgr() = default; //------------------------------------------------------------------------- //! @brief 初期化処理を行います. //! //! @param[in] maxInstanceCount 最大インスタンス数です. //! @param[in] maxMaterialCount 最大マテリアル数です. //! @retval true 初期化に成功. //! @retval false 初期化に失敗. //------------------------------------------------------------------------- bool Init( asdx::CommandList& cmdList, uint32_t maxInstanceCount, uint32_t maxMaterialCount); //------------------------------------------------------------------------- //! @brief 終了処理を行います. //------------------------------------------------------------------------- void Term(); //------------------------------------------------------------------------- //! @brief メモリマッピングを解除します. //------------------------------------------------------------------------- void Fixed(); //------------------------------------------------------------------------- //! @brief メッシュを登録します. //! //! @param[in] mesh 登録するメッシュ. //! @return ジオメトリハンドルを返却します. //------------------------------------------------------------------------- GeometryHandle AddMesh(const Mesh& mesh); //------------------------------------------------------------------------- //! @brief インスタンスを登録します. //! //! @param[in] instance インスタンスデータ. //! @param[in] transform 変換行列. //! @return インスタンスハンドルを返却します. //------------------------------------------------------------------------- InstanceHandle AddInstance(const Instance& instance, const asdx::Transform3x4& transform); //------------------------------------------------------------------------- //! @brief マテリアルを登録します. //! //! @param[in] ptr マテリアルデータ. //! @param[in] count マテリアル数. //! @return GPU仮想アドレスを返却します. //------------------------------------------------------------------------- D3D12_GPU_VIRTUAL_ADDRESS AddMaterials(const Material* ptr, uint32_t count); //------------------------------------------------------------------------- //! @brief インスタンスバッファのシェーダリソースビューを取得します. //------------------------------------------------------------------------- asdx::IShaderResourceView* GetIB() const; //------------------------------------------------------------------------- //! @brief トランスフォームバッファのシェーダリソースビューを取得します. //------------------------------------------------------------------------- asdx::IShaderResourceView* GetTB() const; //------------------------------------------------------------------------- //! @brief マテリアルバッファのシェーダリソースビューを取得します. //------------------------------------------------------------------------- asdx::IShaderResourceView* GetMB() const; //-------------------------------------------------------------------------- //! @brief インスタンスバッファのGPU仮想アドレスを取得します. //-------------------------------------------------------------------------- D3D12_GPU_VIRTUAL_ADDRESS GetAddressIB() const; //-------------------------------------------------------------------------- //! @brief トランスフォームバッファのGPU仮想アドレスを取得します. //-------------------------------------------------------------------------- D3D12_GPU_VIRTUAL_ADDRESS GetAddressTB() const; //-------------------------------------------------------------------------- //! @brief マテリアルバッファのGPU仮想アドレスを取得します. //-------------------------------------------------------------------------- D3D12_GPU_VIRTUAL_ADDRESS GetAddressMB() const; //------------------------------------------------------------------------- //! @brief インスタンスバッファサイズを取得します. //------------------------------------------------------------------------- uint32_t GetSizeIB() const; //------------------------------------------------------------------------- //! @brief トランスフォームバッファサイズを取得します. //------------------------------------------------------------------------- uint32_t GetSizeTB() const; //------------------------------------------------------------------------- //! @brief マテリアルバッファサイズを取得します. //------------------------------------------------------------------------- uint32_t GetSizeMB() const; private: /////////////////////////////////////////////////////////////////////////// // MeshBuffer structure /////////////////////////////////////////////////////////////////////////// struct MeshBuffer { asdx::RefPtr<ID3D12Resource> VB; asdx::RefPtr<ID3D12Resource> IB; asdx::RefPtr<asdx::IShaderResourceView> VB_SRV; asdx::RefPtr<asdx::IShaderResourceView> IB_SRV; }; //========================================================================= // private variables. //========================================================================= asdx::RefPtr<ID3D12Resource> m_IB; //!< インスタンスバッファ. asdx::RefPtr<ID3D12Resource> m_TB; //!< トランスフォームバッファ. asdx::RefPtr<ID3D12Resource> m_MB; //!< マテリアルバッファ. asdx::RefPtr<asdx::IShaderResourceView> m_IB_SRV; asdx::RefPtr<asdx::IShaderResourceView> m_TB_SRV; asdx::RefPtr<asdx::IShaderResourceView> m_MB_SRV; std::vector<MeshBuffer> m_Meshes; uint32_t m_OffsetInstance = 0; uint32_t m_OffsetMaterial = 0; uint32_t m_MaxInstanceCount; uint32_t m_MaxMaterialCount; Instance* m_pInstances = nullptr; asdx::Transform3x4* m_pTransforms = nullptr; Material* m_pMaterials = nullptr; D3D12_GPU_VIRTUAL_ADDRESS m_AddressIB = 0; D3D12_GPU_VIRTUAL_ADDRESS m_AddressTB = 0; D3D12_GPU_VIRTUAL_ADDRESS m_AddressMB = 0; asdx::Texture m_DefaultBaseColor; asdx::Texture m_DefaultNormal; asdx::Texture m_DefaultORM; asdx::Texture m_DefaultEmissive; //========================================================================= // private methods. //========================================================================= uint32_t GetBaseColor(uint32_t handle); uint32_t GetNormal(uint32_t handle); uint32_t GetOrm(uint32_t handle); uint32_t GetEmissive(uint32_t handle); };
で,実際の処理内容は下記のような感じです。
/////////////////////////////////////////////////////////////////////////////// // ModelMgr class /////////////////////////////////////////////////////////////////////////////// //----------------------------------------------------------------------------- // 初期化処理を行います. //----------------------------------------------------------------------------- bool ModelMgr::Init ( asdx::CommandList& cmdList, uint32_t maxInstanceCount, uint32_t maxMaterialCount ) { auto pDevice = asdx::GetD3D12Device(); const auto sizeIB = maxInstanceCount * sizeof(Instance); const auto sizeTB = maxInstanceCount * sizeof(asdx::Transform3x4); const auto sizeMB = maxMaterialCount * sizeof(Material); m_MaxInstanceCount = maxInstanceCount; m_MaxMaterialCount = maxMaterialCount; m_OffsetInstance = 0; m_OffsetMaterial = 0; // デフォルトベースカラー生成. { asdx::ResTexture res; res.Dimension = asdx::TEXTURE_DIMENSION_2D; res.Width = 32; res.Height = 32; res.Depth = 0; res.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB; res.MipMapCount = 1; res.SurfaceCount = 1; res.pResources = new asdx::SubResource[1]; auto& subRes = res.pResources[0]; subRes.Width = 32; subRes.Height = 32; subRes.MipIndex = 0; subRes.Pitch = sizeof(uint8_t) * 4 * subRes.Width; subRes.SlicePitch = subRes.Pitch * subRes.Height; subRes.pPixels = new uint8_t [subRes.SlicePitch]; for(auto y=0u; y<res.Height; y++) { for(auto x=0u; x<res.Width; x++) { auto idx = y * 4 * res.Width + x * 4; #if 0 // 18%グレー. subRes.pPixels[idx + 0] = 119; subRes.pPixels[idx + 1] = 119; subRes.pPixels[idx + 2] = 119; subRes.pPixels[idx + 3] = 255; #else // White subRes.pPixels[idx + 0] = 255; subRes.pPixels[idx + 1] = 255; subRes.pPixels[idx + 2] = 255; subRes.pPixels[idx + 3] = 255; #endif } } if (!m_DefaultBaseColor.Init(cmdList, res)) { ELOGA("Error : Default Base Color Init Failed."); res.Dispose(); return false; } res.Dispose(); } // フラット法線生成. { asdx::ResTexture res; res.Dimension = asdx::TEXTURE_DIMENSION_2D; res.Width = 32; res.Height = 32; res.Depth = 0; res.Format = DXGI_FORMAT_R8G8B8A8_UNORM; res.MipMapCount = 1; res.SurfaceCount = 1; res.pResources = new asdx::SubResource[1]; auto& subRes = res.pResources[0]; subRes.Width = 32; subRes.Height = 32; subRes.MipIndex = 0; subRes.Pitch = sizeof(uint8_t) * 4 * subRes.Width; subRes.SlicePitch = subRes.Pitch * subRes.Height; subRes.pPixels = new uint8_t [subRes.SlicePitch]; for(auto y=0u; y<res.Height; y++) { for(auto x=0u; x<res.Width; x++) { auto idx = y * 4 * res.Width + x * 4; subRes.pPixels[idx + 0] = 128; subRes.pPixels[idx + 1] = 128; subRes.pPixels[idx + 2] = 255; subRes.pPixels[idx + 3] = 255; } } if (!m_DefaultNormal.Init(cmdList, res)) { ELOGA("Error : Default Normal Init Failed."); res.Dispose(); return false; } res.Dispose(); } // デフォルトORM生成. { asdx::ResTexture res; res.Dimension = asdx::TEXTURE_DIMENSION_2D; res.Width = 32; res.Height = 32; res.Depth = 0; res.Format = DXGI_FORMAT_R8G8B8A8_UNORM; res.MipMapCount = 1; res.SurfaceCount = 1; res.pResources = new asdx::SubResource[1]; auto& subRes = res.pResources[0]; subRes.Width = 32; subRes.Height = 32; subRes.MipIndex = 0; subRes.Pitch = sizeof(uint8_t) * 4 * subRes.Width; subRes.SlicePitch = subRes.Pitch * subRes.Height; subRes.pPixels = new uint8_t [subRes.SlicePitch]; for(auto y=0u; y<res.Height; y++) { for(auto x=0u; x<res.Width; x++) { auto idx = y * 4 * res.Width + x * 4; subRes.pPixels[idx + 0] = 255; subRes.pPixels[idx + 1] = 128; subRes.pPixels[idx + 2] = 0; subRes.pPixels[idx + 3] = 255; } } if (!m_DefaultORM.Init(cmdList, res)) { ELOGA("Error : Default ORM Init Failed."); res.Dispose(); return false; } res.Dispose(); } // デフォルトエミッシブ生成. { asdx::ResTexture res; res.Dimension = asdx::TEXTURE_DIMENSION_2D; res.Width = 32; res.Height = 32; res.Depth = 0; res.Format = DXGI_FORMAT_R8G8B8A8_UNORM; res.MipMapCount = 1; res.SurfaceCount = 1; res.pResources = new asdx::SubResource[1]; auto& subRes = res.pResources[0]; subRes.Width = 32; subRes.Height = 32; subRes.MipIndex = 0; subRes.Pitch = sizeof(uint8_t) * 4 * subRes.Width; subRes.SlicePitch = subRes.Pitch * subRes.Height; subRes.pPixels = new uint8_t [subRes.SlicePitch]; for(auto y=0u; y<res.Height; y++) { for(auto x=0u; x<res.Width; x++) { auto idx = y * 4 * res.Width + x * 4; subRes.pPixels[idx + 0] = 0; subRes.pPixels[idx + 1] = 0; subRes.pPixels[idx + 2] = 0; subRes.pPixels[idx + 3] = 0; } } if (!m_DefaultEmissive.Init(cmdList, res)) { ELOGA("Error : Default Emissive Init Failed."); res.Dispose(); return false; } res.Dispose(); } //-------------------- // インスタンスデータ. //-------------------- if (!asdx::CreateUploadBuffer(pDevice, sizeIB, m_IB.GetAddress())) { ELOGA("Error : InstanceBuffer Create Failed."); return false; } if (!asdx::CreateBufferSRV(pDevice, m_IB.GetPtr(), UINT(sizeIB / 4), 0, m_IB_SRV.GetAddress())) { ELOGA("Error : InstanceBuffer SRV Create Failed."); return false; } { m_AddressIB = m_IB->GetGPUVirtualAddress(); auto hr = m_IB->Map(0, nullptr, reinterpret_cast<void**>(&m_pInstances)); if (FAILED(hr)) { ELOGA("Error : ID3D12Resource::Map() Failed. errcode = 0x%x", hr); return false; } } //-------------------- // トランスフォームデータ. //-------------------- if (!asdx::CreateUploadBuffer(pDevice, sizeTB, m_TB.GetAddress())) { ELOGA("Error : TransformBuffer Create Failed."); return false; } if (!asdx::CreateBufferSRV(pDevice, m_TB.GetPtr(), UINT(sizeTB / 4), 0, m_TB_SRV.GetAddress())) { ELOGA("Error : Transform SRV Create Failed."); return false; } { m_AddressTB = m_TB->GetGPUVirtualAddress(); auto hr = m_TB->Map(0, nullptr, reinterpret_cast<void**>(&m_pTransforms)); if (FAILED(hr)) { ELOGA("Error : ID3D12Resource::Map() Failed. errcode = 0x%x", hr); return false; } } //-------------------- // マテリアルデータ. //-------------------- if (!asdx::CreateUploadBuffer(pDevice, sizeMB, m_MB.GetAddress())) { ELOGA("Error : MaterialBuffer Create Failed."); return false; } if (!asdx::CreateBufferSRV(pDevice, m_MB.GetPtr(), UINT(sizeMB / 4), 0, m_MB_SRV.GetAddress())) { ELOGA("Error : Material SRV Create Failed."); return false; } { m_AddressMB = m_MB->GetGPUVirtualAddress(); auto hr = m_MB->Map(0, nullptr, reinterpret_cast<void**>(&m_pMaterials)); if (FAILED(hr)) { ELOGA("Error : ID3D12Resource::Map() Failed. errcode = 0x%x", hr); return false; } } return true; } //----------------------------------------------------------------------------- // 終了処理を行います. //----------------------------------------------------------------------------- void ModelMgr::Term() { m_IB_SRV.Reset(); m_TB_SRV.Reset(); m_MB_SRV.Reset(); m_IB.Reset(); m_TB.Reset(); m_MB.Reset(); m_OffsetInstance = 0; m_OffsetMaterial = 0; m_pInstances = nullptr; m_pTransforms = nullptr; m_pMaterials = nullptr; for(size_t i=0; i<m_Meshes.size(); ++i) { m_Meshes[i].VB.Reset(); m_Meshes[i].IB.Reset(); m_Meshes[i].VB_SRV.Reset(); m_Meshes[i].IB_SRV.Reset(); } m_Meshes.clear(); m_Meshes.shrink_to_fit(); m_DefaultBaseColor .Term(); m_DefaultNormal .Term(); m_DefaultORM .Term(); m_DefaultEmissive .Term(); } //----------------------------------------------------------------------------- // メモリマッピングを解除します. //----------------------------------------------------------------------------- void ModelMgr::Fixed() { m_IB->Unmap(0, nullptr); m_pInstances = nullptr; m_TB->Unmap(0, nullptr); m_pTransforms = nullptr; m_MB->Unmap(0, nullptr); m_pMaterials = nullptr; } //----------------------------------------------------------------------------- // メッシュを登録します. //----------------------------------------------------------------------------- GeometryHandle ModelMgr::AddMesh(const Mesh& mesh) { auto pDevice = asdx::GetD3D12Device(); MeshBuffer item; GeometryHandle result = {}; // 頂点バッファ生成. { auto vbSize = mesh.VertexCount * sizeof(Vertex); if (!asdx::CreateUploadBuffer(pDevice, vbSize, item.VB.GetAddress())) { ELOGA("Error : CreateUploadBuffer() Failed."); return result; } if (!asdx::CreateBufferSRV(pDevice, item.VB.GetPtr(), UINT(vbSize/4), 0, item.VB_SRV.GetAddress())) { ELOGA("Error : CreateBufferSRV() Failed."); return result; } uint8_t* ptr = nullptr; auto hr = item.VB->Map(0, nullptr, reinterpret_cast<void**>(&ptr)); if (FAILED(hr)) { ELOGA("Error : ID3D12Resource::Map() Failed. errcode = 0x%x", hr); return result; } memcpy(ptr, mesh.Vertices, vbSize); item.VB->Unmap(0, nullptr); } // インデックスバッファ生成. { auto ibSize = mesh.VertexCount * sizeof(uint32_t); if (!asdx::CreateUploadBuffer(pDevice, ibSize, item.IB.GetAddress())) { ELOGA("Error : CreateUploadBuffer() Failed."); return result; } if (!asdx::CreateBufferSRV(pDevice, item.IB.GetPtr(), UINT(ibSize/4), 0, item.IB_SRV.GetAddress())) { ELOGA("Error : CreateBufferSRV() Failed."); return result; } uint8_t* ptr = nullptr; auto hr = item.IB->Map(0, nullptr, reinterpret_cast<void**>(&ptr)); if (FAILED(hr)) { ELOGA("Error : ID3D12Resource::Map() Failed. errcode = 0x%x", hr); return result; } memcpy(ptr, mesh.Indices, ibSize); item.IB->Unmap(0, nullptr); } result.AddressVB = item.VB->GetGPUVirtualAddress(); result.AddressIB = item.IB->GetGPUVirtualAddress(); result.IndexVB = item.VB_SRV->GetDescriptorIndex(); result.IndexIB = item.IB_SRV->GetDescriptorIndex(); m_Meshes.emplace_back(item); return result; } //----------------------------------------------------------------------------- // インスタンスを登録します. //----------------------------------------------------------------------------- InstanceHandle ModelMgr::AddInstance(const Instance& instance, const asdx::Transform3x4& transform) { assert(m_OffsetInstance + 1 < m_MaxInstanceCount); auto idx = m_OffsetInstance; m_pInstances [idx] = instance; m_pTransforms[idx] = transform; m_OffsetInstance++; InstanceHandle result = {}; result.InstanceId = idx; result.AddressTB = m_AddressTB + idx * sizeof(asdx::Transform3x4); return result; } //----------------------------------------------------------------------------- // マテリアルデータを追加します. //----------------------------------------------------------------------------- D3D12_GPU_VIRTUAL_ADDRESS ModelMgr::AddMaterials(const Material* ptr, uint32_t count) { assert(m_OffsetMaterial + count < m_MaxMaterialCount); D3D12_GPU_VIRTUAL_ADDRESS result = m_AddressMB + m_OffsetMaterial * sizeof(Material); for(uint32_t i=0; i<count; ++i) { auto& src = ptr[i]; auto& dst = m_pMaterials[m_OffsetMaterial + i]; dst.BaseColor = GetBaseColor(src.BaseColor); dst.Normal = GetNormal(src.Normal); dst.ORM = GetOrm(src.ORM); dst.Emissive = GetEmissive(src.Emissive); } m_OffsetMaterial += count; return result; } //----------------------------------------------------------------------------- // インスタンスバッファのシェーダリソースビューを取得します. //----------------------------------------------------------------------------- asdx::IShaderResourceView* ModelMgr::GetIB() const { return m_IB_SRV.GetPtr(); } //----------------------------------------------------------------------------- // トランスフォームバッファのシェーダリソースビューを取得します. //----------------------------------------------------------------------------- asdx::IShaderResourceView* ModelMgr::GetTB() const { return m_TB_SRV.GetPtr(); } //----------------------------------------------------------------------------- // マテリアルバッファのシェーダリソースビューを取得します. //----------------------------------------------------------------------------- asdx::IShaderResourceView* ModelMgr::GetMB() const { return m_MB_SRV.GetPtr(); } //----------------------------------------------------------------------------- // インスタンスバッファのGPU仮想アドレスを取得します. //----------------------------------------------------------------------------- D3D12_GPU_VIRTUAL_ADDRESS ModelMgr::GetAddressIB() const { return m_AddressIB; } //----------------------------------------------------------------------------- // トランスフォームバッファのGPU仮想アドレスを取得します. //----------------------------------------------------------------------------- D3D12_GPU_VIRTUAL_ADDRESS ModelMgr::GetAddressTB() const { return m_AddressTB; } //----------------------------------------------------------------------------- // マテリアルバッファのGPU仮想アドレスを取得します. //----------------------------------------------------------------------------- D3D12_GPU_VIRTUAL_ADDRESS ModelMgr::GetAddressMB() const { return m_AddressMB; } //----------------------------------------------------------------------------- // インスタンスバッファサイズを取得します. //----------------------------------------------------------------------------- uint32_t ModelMgr::GetSizeIB() const { return m_MaxInstanceCount * sizeof(Instance); } //----------------------------------------------------------------------------- // トランスフォームバッファサイズを取得します. //----------------------------------------------------------------------------- uint32_t ModelMgr::GetSizeTB() const { return m_MaxInstanceCount * sizeof(asdx::Transform3x4); } //----------------------------------------------------------------------------- // マテリアルバッファサイズを取得します. //----------------------------------------------------------------------------- uint32_t ModelMgr::GetSizeMB() const { return m_MaxMaterialCount * sizeof(Material); } //----------------------------------------------------------------------------- // ベースカラーハンドルを取得します. //----------------------------------------------------------------------------- uint32_t ModelMgr::GetBaseColor(uint32_t handle) { return (handle == INVALID_MATERIAL_MAP) ? m_DefaultBaseColor.GetView()->GetDescriptorIndex() : handle; } //----------------------------------------------------------------------------- // 法線ハンドルを取得します. //----------------------------------------------------------------------------- uint32_t ModelMgr::GetNormal(uint32_t handle) { return (handle == INVALID_MATERIAL_MAP) ? m_DefaultNormal.GetView()->GetDescriptorIndex() : handle; } //----------------------------------------------------------------------------- // ORMハンドルを取得します. //----------------------------------------------------------------------------- uint32_t ModelMgr::GetOrm(uint32_t handle) { return (handle == INVALID_MATERIAL_MAP) ? m_DefaultORM.GetView()->GetDescriptorIndex() : handle; } //----------------------------------------------------------------------------- // エミッシブハンドルを取得します. //----------------------------------------------------------------------------- uint32_t ModelMgr::GetEmissive(uint32_t handle) { return (handle == INVALID_MATERIAL_MAP) ? m_DefaultEmissive.GetView()->GetDescriptorIndex() : handle; }
やっていることとしては,Map()したメモリにmemcpy()して突っ込んでいるだけで大したことない処理です。
上記の処理コードで出てくるDescriptorIndexはディスクリプタヒープを作成する際に,連番を割り当てLinkedListに使っていないものを突っ込んで管理しており,単に初期化時に割り当てた番号をreturnするだけです。
//----------------------------------------------------------------------------- // 初期化処理を行います. //----------------------------------------------------------------------------- bool DescriptorHeap::Init(ID3D12Device* pDevice, const D3D12_DESCRIPTOR_HEAP_DESC* pDesc) { if (pDevice == nullptr || pDesc == nullptr) { return false; } if (pDesc->NumDescriptors == 0) { return true; } auto hr = pDevice->CreateDescriptorHeap(pDesc, IID_PPV_ARGS(&m_pHeap)); if ( FAILED(hr) ) { ELOG("Error : ID3D12Device::CreateDescriptorHeap() Failed. errcode = 0x%x", hr); return false; } m_pHeap->SetName(L"asdxDescriptorHeap"); // インクリメントサイズを取得. m_IncrementSize = pDevice->GetDescriptorHandleIncrementSize(pDesc->Type); // ディスクリプタ生成. m_Descriptors = new Descriptor[pDesc->NumDescriptors]; auto hasHandleGPU = (pDesc->Flags == D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE); for(auto i=0u; i<pDesc->NumDescriptors; ++i) { // ヒープを設定. m_Descriptors[i].m_pHeap = this; // Dynamic Resource 用の番号を割り当て. m_Descriptors[i].m_Index = i; // CPUハンドルをディスクリプタを割り当て. { auto handleCPU = m_pHeap->GetCPUDescriptorHandleForHeapStart(); handleCPU.ptr += UINT64(m_IncrementSize) * i; m_Descriptors[i].m_HandleCPU = handleCPU; } // GPUハンドルをディスクリプタを割り当て if (hasHandleGPU) { auto handleGPU = m_pHeap->GetGPUDescriptorHandleForHeapStart(); handleGPU.ptr += UINT64(m_IncrementSize) * i; m_Descriptors[i].m_HandleGPU = handleGPU; } // 未使用リストに追加. m_FreeList.PushBack(&m_Descriptors[i]); } return true; }
あとはDynamic Resourceが使えるように,ルートシグニチャのフラグでD3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXEDを忘れずに指定してあげてください。
// レイトレ用ルートシグニチャ生成. { asdx::DescriptorSetLayout<7, 1> layout; layout.SetTableUAV(0, asdx::SV_ALL, 0); layout.SetSRV (1, asdx::SV_ALL, 0); layout.SetSRV (2, asdx::SV_ALL, 1); layout.SetSRV (3, asdx::SV_ALL, 2); layout.SetSRV (4, asdx::SV_ALL, 3); layout.SetTableSRV(5, asdx::SV_ALL, 4); layout.SetCBV (6, asdx::SV_ALL, 0); layout.SetStaticSampler(0, asdx::SV_ALL, asdx::STATIC_SAMPLER_LINEAR_WRAP, 0); layout.SetFlags(D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED); if (!m_RayTracingRootSig.Init(pDevice, layout.GetDesc())) { ELOGA("Error : RayTracing RootSignature Failed."); return false; } }
あとは,NEEを実装するなり,ReSTIRを実装するなり,デノイザーを実装するなり,リッチにしていってみてください。
5. その他
この記事書くためのスクリーンショットを撮るまで気づいていなかったのですが,アーティファクトが発生していました。
プログラムのバグか?と思って,色々と調べていたのですが,レンダーターゲットのフォーマットが悪かったようです。
R16G16B16A16_FLOATフォーマットからR32G32B32A32_FLOATに変更したら収まりました。
精度には気を付けましょう。
References
- [Jarzynski and Olano 2020] Mark Jarzynski, Marc Olano, “Hash Functions for GPU Rendering”, Jornal of Computer Graphics Techniques, Vol.9, No.3, 2020.
- [hole 2014] hole, “物理ベースレンダラ edupt解説”, https://www.slideshare.net/h013/edupt-kaisetsu-22852235
- [Boksansky and Marrs 2021] Jakub Boksansky, Adam Marrs, “THE REFERENCE PATH TRACER”, Raytracing Gems Ⅱ, Chapter 14, pp.161-187. 2021.