DirectX12 でのテッセレーションを確認してみています。テッセレーション自体は DirectX11 から使えるようになっているものですが、あまり事例を見かけないかなと思います。頂点シェーダーやピクセルシェーダーのように必須のものではありませんし、色々と制御が難しかったりしています。今回はテッセレーションの基本的な部分について、自分のメモです。
テッセレーションステージ
テッセレーションのステージは、ハルシェーダー、テッセレーター、ドメインシェーダーによって構成されています。このうちテッセレーターは固定機能となっています。
ハルシェーダーでは入力されたパッチの細分割のための係数情報を求めるのが仕事です。ドメインシェーダーではテッセレーターから出てきた分割位置で頂点を計算し、出力するのが仕事です。これらの流れは次のようになります。
この図は DirectX12 Programming Vol.3 で紹介しているものです。テッセレーションを使うときには、頂点シェーダーも必要になっているのですが、実際の処理としては頂点属性をそのままハルシェーダーに流す程度のものとなっています。
テッセレーションによって分割する種類が色々あります。マニュアル上は次のものが設定可能と記述されています。
- fractional_odd
- fractional_even
- integer
- pow2
fractional_odd/even は浮動小数で分割数を指定し、その他のものは整数値で分割を指定します。分割数が連続的に変化する分 fractional_odd/even を使っておくとスムーズに変化します。integer を指定しておくと、しっかりと切り替わりがわかります。
気になるのは pow2 の指定なのですが、動きを確認してみたところ integer の動きと同じでドキュメントに記載されているような、2ベキでの分割にはなりませんでした。
ドメインシェーダー
ドメインシェーダーでは分割された頂点を処理しますが、頂点シェーダーのように頂点そのものが入力されるわけではありません。入力されたパッチのうち、どの部分に頂点を生成すべきといった情報がテッセレーターから渡されます。 SV_DomainLocation というセマンティクスで渡ってくるので、このドメインUVの位置で頂点を計算して出力します。
PSInput mainDS(HSParameters In,
float2 loc : SV_DomainLocation,
const OutputPatch<DSInput, NUM_CONTROL_POINTS> patch)
{
float3 p0 = lerp(patch[0].Position, patch[1].Position, loc.x).xyz;
float3 p1 = lerp(patch[2].Position, patch[3].Position, loc.x).xyz;
float3 pos = lerp(p0, p1, loc.y);
float4 c0 = lerp(patch[0].Color, patch[1].Color, loc.x);
float4 c1 = lerp(patch[2].Color, patch[3].Color, loc.x);
float4 col = lerp(c0, c1, loc.y);
PSInput result;
result.Position = mul(float4(pos, 1), worldViewProj);
...
}
まとめ
どこでなにをやっているか、何が責務なのかが分かればそれほど難しい処理ステップはなさそうです。あるとしたらどのように割るのかを指示するのに、うまくいく方法は何なのか、といったところでしょうか。
あとは、どうもドメインUVの割り方というか方向というかがしっくりこないです。マニュアルなどで説明されているUVの方向やテッセレーション係数の場所がどうも。ここは個人的な感覚の問題なのかもしれませんが。