DirectXの話 第155回

DirectX Raytracing FallbackでCornell Box その2 18/07/04 up

サンプルはこちらの Sample03 です。

https://github.com/Monsho/DXRSamples

複数 Hit Group の振り分け

第154回の記事では BottomAS の作成までやりました。

このあとは当然 TopAS の作成が待っているわけですが、その前に Hit Group の振り分けについて解説します。

Sample02 ではすでに複数の Hit Group を実装していましたが、こちらは TopAS に登録したインスタンスごとに別々に割り振っただけです。

今回は Intersection Shader が存在するため、Intersection ShaderClosest Hit Shader の組み合わせ分だけ Hit Group が必要になります。

では、シェーダコードである test.r.hlsl を見てみましょう。注目は Intersection Shader と Closest Hit Shader です。

  • Intersection Shader

    • void IntersectionSphereProcessor()

    • void IntersectionInnerBoxProcessor()

  • Closest Hit Shader

    • void ClosestHitSphereProcessor()

    • void ClosestHitInnerBoxProcessor()

名前の通り、Intersection Shader は球用とAABB用の2種類です。

Closest Hit Shader も同じく球用とAABB用の2種類です。

ここからわかることは、Hit Group の組み合わせは球用とAABB用の2つだけということです。

しかしよく見るともう1つの Closest Hit Shader があります。

  • void ClosestHitShadowProcessor()

名前の通り、シャドウ用です。

実は先に紹介した球用とAABB用の Closest Hit Shader はシェーディングをするだけではなく、衝突点からライト方向にレイを飛ばしてシャドウイングされているかどうかを調べるための TraceRay() 命令が発行されているのです。

test.r.hlsl の 210行目、及び249行目を見てください。コードはどちらも同じものです。

float shadow = 1.0;{ float3 origin = WorldRayOrigin() + WorldRayDirection() * RayTCurrent(); RayDesc ray = { origin, 1e-4, lightDir, 10000.0f }; HitData shadow_payload = { float4(0, 0, 0, 0) }; TraceRay(Scene, RAY_FLAG_CULL_BACK_FACING_TRIANGLES | RAY_FLAG_ACCEPT_FIRST_HIT_AND_END_SEARCH, ~0, 1, 1, 1, ray, shadow_payload); shadow = shadow_payload.color.x;}

TraceRay() 命令の第4引数が、Ray Generation Shader では 0 を指定していましたが、ここでは 1 を指定しています。

これは第153回で解説したとおり、Shader Table のインデックスを求めるための措置です。

シェーダテーブルの話の中で TraceRay() によって実行される Hit Group のインデックスを求める方法を解説していますが、第4引数は RayContributionToHitGroupIndex というパラメータになります。

InstanceContributionToHitGroupIndex は TopAS のインスタンスごとに設定するインデックスで、通常はそのインスタンスにカメラからのレイがあたった場合に処理される Hit Group のインデックスを指定します。

ここの TraceRay() ではそこで指定されたインデックスに対して +1 したインデックスの Hit Group を呼び出すように指示しているわけです。

つまり、今回の Hit Group は以下のようになっているということです。

Sample03.cpp の 1232行目からの TopAS 用インスタンスバッファを作成する部分を見てください。

InstanceContributionToHitGroupIndex は球用のものが 0、AABB(Innter Box)用のものが 2 を指定しています。

シャドウ用の Hit Group はこの数値に +1 したものとなりますので、シャドウトレース用の Hit Group はそれぞれ 13 となり、上記のシェーダテーブルと一致しているというわけです。

Hit Group の振り分けはこのように実装することが可能ですが、複雑な振り分けを行う場合は Shader Table を正しく把握する必要があります。

MSのサンプルではデバッグ出力で Shader Table 情報を出力ウィンドウに表示していますが、このような措置をとったほうが不具合が出たときわかりやすいのではないかと思います。

複数 Miss Shader の振り分け

今回やった最後の1つがこちらです。

前述のコードを見ていただければわかると思いますが、Ray Generation Shader で実行した TraceRay() 命令と違う部分が他にもあります。

第6引数を見てください。ここが 0 ではなく 1 になっています。

この引数はやはりシェーダテーブルの Miss Shader のインデックスを示す値です。

Miss Shaderインデックスは Hit Group のものと違って相対ではなく絶対のインデックスになります。

これは Hit Group の Shader Table と Miss Shader のシェーダテーブルが別物であり、また、Miss Shader は基本的に、レイを飛ばした理由によって切り替えはしても、レイを飛ばした対象によって切り替えたりすることはないからです。

今回、Miss Shaderは実は3つあります。

  • void MissProcessor()

  • カメラから飛ばしたレイが衝突したなかった場合の処理。青色を返す。

  • void MissShadowProcessor()

  • メッシュにレイが衝突した後、ライト方向にシャドウトレースした際に衝突しなかった場合の処理。白を返す。

  • void MissReflectionProcessor()

  • メッシュにレイが衝突したあと、レイの反射方向にトレースした際に衝突しなかった場合の処理。黒を返す。

AABB(Inner Box)の Closest Hit Shader 内では反射を求めるための TraceRay() を発行しています。

シェーダコードの 263行目からがその処理です。

if (payload.color.a > 0){ float3 origin = WorldRayOrigin() + WorldRayDirection() * RayTCurrent(); float3 reflection = dot(attr.normal, -WorldRayDirection()) * 2.0 * attr.normal + WorldRayDirection(); RayDesc ray = { origin, 1e-5, reflection, 10000.0f }; HitData refl_payload = { float4(0, 0, 0, 0) }; TraceRay(Scene, RAY_FLAG_CULL_BACK_FACING_TRIANGLES, ~0, 0, 1, 2, ray, refl_payload); NoL = saturate(dot(attr.normal, reflection)); finalColor += aabb.color.rgb * refl_payload.color.rgb * NoL;}

反射用のトレースをするかどうかは入力された Payload のアルファ値が0より大きいかどうかで判断しています。

これは反射計算を1回で止めるための措置です。

この分岐が存在しないとレイが衝突しなくなるまで処理がループし続けるので、最悪無限ループに陥って死にます。

Payload は出力だけでなく入力としても使用できるので、レイトレースを実行した命令から各種シェーダに情報を渡すことができるというわけです。

アルファ値に反射回数を入れてやり、反射用の TraceRay() を発行するたびに -1 していく、という方法もありでしょう。

また、Payload による情報渡しを行わなくても、反射処理を行わない Closest Hit Shader を用意して、反射用の Hit Group を作成するという手もあります。

ただ、ライティング計算やシャドウ計算は全く同一のものでしたので、そんな面倒なことはやめたというだけです。

というわけで、2回に分ける理由があったのか微妙な記事でしたがいかがだったでしょうか?

興味がある方は反射回数を増やして見るのもありだと思います。

ただ、増やしすぎてドライバクラッシュとかしないように注意しましょう。

次回はまだまだ使ったことがない Any Hit Shader とかやってみようかな、と思っています。