3Dのプログラムを始める時、最初に説明されているのがグラフィックスパイプラインで、こんなフローを見ることになる。
![]() |
| Graphics Pipeline |
最初に扱うのも今まで扱ってきたのもVSとPSのみ。VSだけっていうこともあった。
他のステージはよくわからないけどとりあえずそんなのもあるんだ程度で、もしかするとずっと使わないかもしれないと思ってた。
シェーダ以外の灰色の枠に関しては関与できず、パラメータを渡すのみ。
InputAssemblerに関しては、IAから始まる関数IASetPrimitiveTopology、IASetVertexBuffers、IASetIndexBufferを通して頂点バッファ、インデックスバッファを渡している。
今回の話題はTesselatoerの部分。
テッセレータ
頂点のデータをこのテッセレータが細かくしてくれるらしい。
どういうふうに細かくするかを決めるのがHull Shaderで、Hull Shaderの出力結果を使ってテッセレータが細かくする方針を立ててくれて、Domain Shaderで実際に決定する流れみたい。
だからテッセレータを使う場合はHSとDSはセットで用意する必要がある。
HSのみセットして実行すると怒られる。
D3D12 ERROR: ID3D12Device::CreateGraphicsPipelineState: When using tesselation, both the Hull Shader and Domain Shader must be set. Otherwise, both must be NULL. [ STATE_CREATION ERROR #667: CREATEGRAPHICSPIPELINESTATE_HS_XOR_DS_MISMATCH]
Hull Shader
ハルシェーダは2つに分かれる。
1つは頂点データを設定するメイン関数。
もう1つは、分割の細かさを設定するパッチ定数関数。
頂点バッファをパッチという単位にして、1パッチ毎に呼ばれるのが定数関数で、パッチ&頂点単位に呼ばれるのがメイン関数。
メイン関数定義
[domain("quad")]
[partitioning("integer")]
[outputtopology("triangle_ccw")]
[outputcontrolpoints(4)]
[patchconstantfunc("CHSMain")]
HSOutput HSMain( InputPatch<VSOutput, 4> IP, uint CP : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID )
{
HSOutput Out ;
Out.Pos = IP[CP].Pos ;
Out.UV = IP[CP].UV ;
return Out ;
}
メイン関数の先頭で色々な設定をする。
domain
domainに設定できる値は、quad(四角形)、tri(三角形)、isoline(線分)の3つ。
この値が決まると、outputcontrolpointsと、関数の引数InputPachの第二引数と、IASetPrimitiveTopologyの引数が決まるっぽい。
"quad"の場合は4、TopologyはD3D_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST。
"tri"の場合は3、TopologyはD3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST。
"isoline"の場合は2、TopologyはD3D_PRIMITIVE_TOPOLOGY_2_CONTROL_POINT_PATCHLIST。
"isoline"の場合は2、TopologyはD3D_PRIMITIVE_TOPOLOGY_2_CONTROL_POINT_PATCHLIST。
この法則を崩して別な呼び方もあるかもしれないけど、今のところこの呼び方だけ確認した。コントロールポイントの定義は32まである。
partitioning
partitioningに設定できる値、integer、fractional_even、fractional_odd、pow2。
分割時の係数を整数(integer、pow2)で見るのか、小数(fractional_even、fractional_odd)で見るのかの違いと、係数の最小値の違いがあったけど、integerとpow2の違いはよくわからなかった。
outputtopology
outputtopologyに設定できる値はpoint(点)、line(線)、triangle_cw(面:時計回り)、triangle_ccw(面:反時計回り)
点と線を作り出すパターンはよくわからず。
domainを"tri"にした場合は"triangle_cw"で、"quad"にした場合は"triangle_ccw"にしないと、元々の面と向きが反対になった。
outputcontrolpoints
出力するコントロールポイントの数。入力のコントロールポイントの数と異なっても良いらしいが、そのパターンは未確認。
patchconstantfunc
パッチ定数関数名を指定する。
引数
InputPatch<VSの出力構造体, 入力コントロールポイント数>
この引数が必須で後はSV_系の引数がオプションで使える。
使えるセマンティックはSV_PrimitiveIDとSV_OutputControlPointID。
コントロールポイントが3の場合、SV_PrimitiveIDとSV_OutputControlPointIDは、(0,0),(0,1)(0,2),(1,0),(1,1)(1,2)...という値を受け取る。
パッチ定数関数定義
struct CHSOutput
{
float Edge[4] : SV_TessFactor ;
float Inside[2] : SV_InsideTessFactor ;
} ;
CHSOutput CHSMain( InputPatch<VSOutput, 4> IP, uint PatchID : SV_PrimitiveID )
{
CHSOutput Out ;
Out.Edge[0] = Out.Edge[1] = Out.Edge[2] = Out.Edge[3] = gHSParam.EdgeFactor ;
Out.Inside[0] = Out.Inside[1] = gHSParam.InsideFactor ;
return Out ;
}
引数
InputPatch<VSの出力構造体, 入力コントロールポイント数>
メイン関数と同様の引数。それ以外のSV_系の引数はSV_PrimitiveIDが使える。
呼ばれる単位はパッチID単位なので、SV_OutputControlPointIDを指定するとコンパイルエラーになる。
戻り値
これは、メイン関数のdomainにより決まる。
"quad"にした場合
float Edge[4] : SV_TessFactor ;
float Inside[2] : SV_InsideTessFactor ;
"tri"にした場合
float Edge[3] : SV_TessFactor ; float Inside : SV_InsideTessFactor ;
分割係数を設定する際のヘルパ関数
ProcessQuadTessFactorsAvg
ProcessQuadTessFactorsMax
ProcessQuadTessFactorsMin
Process2DQuadTessFactorsAvg
Process2DQuadTessFactorsMax
Process2DQuadTessFactorsMin
ProcessQuadTessFactorsMax
ProcessQuadTessFactorsMin
Process2DQuadTessFactorsAvg
Process2DQuadTessFactorsMax
Process2DQuadTessFactorsMin
"isoline"にした場合
float Edge[2] : SV_TessFactor ;
分割係数を設定する際のヘルパ関数
試しにProcessQuadTessFactorsAvgを使ってみたけど、RawEdgeFactorsに各辺の分割数、InsideScaleに1を指定すると、RoundedInsideTessFactorsには各辺の分割数がそのまま返ってくる感じだった。各辺の分割数がバラバラの値を指定した時、平均に分割されたように見える値に調整してくれるのかな?
Domain Shader
[domain("quad")]
DSOutput DSMain( CHSOutput In, const OutputPatch<HSOutput, 4> OP, float2 DL : SV_DomainLocation )
{
DSOutput Out ;
float4 p1 = lerp( OP[1].Pos, OP[0].Pos, DL.x ) ;
float4 p2 = lerp( OP[3].Pos, OP[2].Pos, DL.x ) ;
Out.Pos = lerp( p1, p2, DL.y ) ;
float2 t1 = lerp( OP[1].UV, OP[0].UV, DL.x ) ;
float2 t2 = lerp( OP[3].UV, OP[2].UV, DL.x ) ;
Out.UV = lerp( t1, t2, DL.y ) ;
return Out ;
}
引数
CHSOutput In
ハルシェーダのパッチ定数関数の出力型。
const OutputPatch<HSの出力構造体, 入力コントロールポイント数>
ハルシェーダの出力をOutputPatchで囲った型。
float2 DL : SV_DomainLocation
テッセレータの出力結果?
この引数もdomain設定により決まる。
"quad"にした場合float2、"tri"にした場合float3、"isoline"にした場合float2。
この値を元に、出力の頂点を補間する。
quadの動作結果
domainを"quad"、partitioningを"integer"で動作させた結果。
EdgeFactor、InsideFactor共に1を指定した場合、分割は行われず元のまま。
最大64分割まで可能で、それ以上の値を指定しても64分割となる。
整数なので、1.1と2も同じ扱いで、小数点以下は切り上げられるようだ。
InsideFactorを1ずつ上げていくとこの様に内側に四角が出来てくる。辺の場合は数えられたが、Insideの方はどこをどう数えればいいのか分からない。
EdgeFactorとInsideFactor両方とも同じ様に上げていくと、全体がきれいな分割になる。最大64分割まで可能で、それ以上の値を指定しても64分割となる。
![]() |
| fractional_even |
![]() |
| fractional_odd |
fractional_evenの方は、EdgeFactorの最小値が2となり、1を指定しても2を指定してもこの形になる。2を超えたところから変化が始まる。
fractional_even
EdgeFactorのみ、InsideFactorのみ、EdgeFactorとInsideFactorの値を動かしたときの分割結果。
fractional_odd
EdgeFactorのみ、InsideFactorのみ、EdgeFactorとInsideFactorの値を動かしたときの分割結果。InsideFactorが1のままEdgeFactorを上昇させても変化しない為、EdgeFactorのみ上昇時はInsideFactorは2に設定。
triの動作結果
fractional_even
EdgeFactorのみ、InsideFactorのみ、EdgeFactorとInsideFactorの値を動かしたときの分割結果。
fractional_odd
EdgeFactorのみ、InsideFactorのみ、EdgeFactorとInsideFactorの値を動かしたときの分割結果。InsideFactorが1のままEdgeFactorを上昇させても変化しない為、EdgeFactorのみ上昇時はInsideFactorは2に設定。
今回の嵌まりポイント
ハルシェーダのパッチ定数関数を定義して、patchconstantfuncでも指定しているにも関わらず、コンパイルエラーで定義されていないと言われ続けた。
関数の引数は構造体で定義して1つだけにしてスッキリさせていたんだけど、ハルシェーダとドメインシェーダの場合どうやら対応してないみたいで、引数にInputPatch、OutputPatchの引数があることが条件になっているのかもしれない。
サンプルの通り引数を並べてみたらコンパイルが通った。
















0 件のコメント:
コメントを投稿