●Tessellator のタイプ
Tessellator の出力には 3種類のタイプがあります。
[domain(type)]: isoline TessFactor[2] 出力 point line tri TessFactor[3] InsideTessFactor[1] 出力 pointlinetriangle quad TessFactor[4] InsideTessFactor[2] 出力 pointlinetriangle
2008-11-17追記:間違いを修正しました。上記赤字と、tri/quad では line を出力できませんでした。
quad, tri はそれぞれ四角パッチ、三角パッチを意味しています。
isoline は面を均等に割った 2次元頂点を出力するようです。
TessFactor[1] が 1.0 の場合、線分の分割に相当します。
例えば isoline で、TessFactor=8,4 を与えると下記の点を出力します。
Tessellator が出力するプリミティブの形状は次の4種類です。
[outputtopology(type)]: point line triangle_cw triangle_ccw
これらは Hull Shader のアトリビュートで指定します。
出力トポロジの影響を直接受けるのは Geometry Shader 以降ですが、その設定も
Domain Shader ではなく HullShader が行うので注意。
ちなみに DomainShader は、プリミティブの形状 (outputtopology) を判別する
手段がありません。
DomainShader は TessFactor を参照できるため、上の domain のみ指定する必要
があります。
isoline では triangle を出力できません。
line を指定すると、単なる横線が複数出力されました。
●TessFactor の種類
partitioning は TessFactor による分割方法を指定します。
[partitioning(type)]: integer fractional_even fractional_odd pow2
fractional だと、TessFactor が実数値の場合間を補間します。
LOD のつながりがよりスムーズになると考えられます。
outputtopology も、partitioning もマニュアルに書いてあることと違います。
まだまだ間違いが多いようです。
●アウトラインフォントの描画
テセレータパイプラインは、HullShader と DomainShader のおかげでかなり自由度が
高くなっています。固定機能の制限は小さく、さまざまな応用が考えられます。
以前「3D だけでなく、ベクタフォントを GPU で直接描画するなど 2D 面でも応用できる
かもしれません。」 などと書いてしまったので実際にやってみました。
拡大とワイヤー表示は次の通り
実際の補間は DomainShader が行っているため、分割したポリゴンのエッジだけ
細分化することは思ったより簡単にできそうです。
quiad や triangle で特定のエッジだけ割っても良いのですが、内部に出来た頂点が
無駄になるし、割ったエッジに影響が出ないよう一点に集めないといけないので
少々複雑です。
isoline があるので、テセレータの段階では輪郭をそのまま割るだけにします。
ポリゴン化は GeometryShader で行います。
簡単にするため、データは前処理で専用のものを用意します。
3次ベジェ のアウトラインを使っています。
各区間、A-B や B-C はそれぞれ 2点の頂点座標(アンカー)+ハンドル2点 で表現
できます。各区間を 4点の制御座標でデータ化します。
このとき隣接するアンカーポイント(頂点)は共有可能です。
A-B : 頂点A, 制御点A0, 制御点A1, 頂点B B-C : 頂点B, 制御点B0, 制御点B1, 頂点C
これをそのまま D3D11_PRIMITIVE_TOPOLOGY_4_CONTROL_POINT_PATCHLIST
で渡し、domain(“isoline”)、outputtopology(“line”) で描画すれば容易にアウトラインの
線描画ができます。DomainShader でベジェカーブを求めるだけです。
フォントのアウトラインそのままで追加情報は不要です。
塗りつぶしは少々手間がかかります。上図の X, Y のように前処理で描画用の
追加点が必要です。
A-B は A-X-B の 3角形、B-C は B-X-Y-C の 4角形を描画します。
処理を簡単にするため、A-X-B は X が2頂点重なってると見なしてすべて四角形で行います。
これらの追加頂点を分割面 (A-X-B や B-X-Y-C) 毎に保持し、GeometryShader まで
情報が届くようにします。
必要な座標は 2次元 x,y のみです。
float4 にすると z,w にもう 1つ座標を入れられます。
アンカーポイントは共有される可能性があるため、面の情報はハンドル(制御点)側の
頂点 zw に入れます。
B-C の例、4頂点 pointB.xy = 頂点B pointB.zw = 未使用 ctrlB0.xy = 制御点B0 ctrlB0.zw = 追加の頂点、追加点X ctrlB1.xy = 制御点B1 ctrlB1.zw = 追加の頂点、追加点Y pointC.xy = 頂点C pointC.zw = 未使用
頂点フォーマットは単純な float4 のみ
D3D11_INPUT_ELEMENT_DESC ldesc[]= { { "VPOS", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, }; iDevice->CreateInputLayout( ldesc, 1, codeblob->GetBufferPointer(), codeblob->GetBufferSize(), &iLayout );
VertexShader はデータにスケーリング補正してるだけでそのまま流します。
// vs.hlsl struct VS_INPUT { float4 vPos : VPOS; }; float4 main( VS_INPUT idata ) : VSPOS { float4 pos= idata.vPos; const float SCALE= 0.08; const float2 CENTER= float2(137,212); pos-= CENTER.xyxy; pos*= SCALE.xxxx; return pos; }
HullShader もテセレータの設定のみ。
分割は線分で行うので domain(“isoline”) を使い、GeometryShader で 2点のつながり
が必要なので出力は outputtopology(“line”)。
コントロールポイントはそのままスルーで DomainShader に任せます。
// hs.hlsl struct VS_OUTPUT { float4 vPos : VSPOS; }; struct HS_DATA { float Edge[2] : SV_TessFactor; }; HS_DATA func() { HS_DATA pdata; pdata.Edge[0]= 8.0; // ここで分割数の決定、値が大きいとなめらか pdata.Edge[1]= 1.0; return pdata; } [domain("isoline")] [partitioning("integer")] [outputtopology("line")] [outputcontrolpoints(4)] [patchconstantfunc("func")] float4 main( InputPatchip, uint id : SV_OutputControlPointID ) : TSPOS { return ip[id].vPos; }
DomainShader で分割点のベジェ補間を行い、GeometryShader に渡すための頂点を
構築します。
追加点は 2番目と 3番目の zw に入っているので、それらも取り出して頂点に入れます。
uv は Geometry Shader で線分の最後かどうか判断するために使います。
// ds.hlsl struct HS_DATA { float Edge[2] : SV_TessFactor; }; struct HS_OUTPUT { float4 vPos : TSPOS; }; cbuffer perFrame { float4x4 WVP; }; float4 Bezier0( float t ) { float t2= 1.0-t; return float4( t2*t2*t2, t2*t2* t*3, t2* t* t*3, t* t* t ); } float2 UVtoPosition( const OutputPatchp, float2 uv ) { float4 u= Bezier0( uv.x ); return u.x*p[0].vPos.xy +u.y*p[1].vPos.xy +u.z*p[2].vPos.xy +u.w*p[3].vPos.xy; } struct DS_OUTPUT { float4 nPos[2] : NPOS; float uv : UV; float4 vPos : SV_Position; }; [domain("isoline")] DS_OUTPUT main( HS_DATA ip, float2 uv : SV_DomainLocation, const OutputPatch bpatch ) { DS_OUTPUT dsout; float2 pos= UVtoPosition( bpatch, uv ); dsout.vPos= mul( float4( pos.xy, 0, 1 ), WVP ); // 追加点 2つを各頂点に埋め込む dsout.nPos[0]= mul( float4( bpatch[1].vPos.zw, 0, 1 ), WVP ); dsout.nPos[1]= mul( float4( bpatch[2].vPos.zw, 0, 1 ), WVP ); dsout.uv= uv.x; // Geometry Shader で使う return dsout; }
GeometryShader は頂点情報から Triangle を作ります。
// gs.hlsl struct DS_OUTPUT { float4 nPos[2] : NPOS; float uv : UV; float4 vPos : SV_Position; }; [maxvertexcount(6)] void main( line DS_OUTPUT input[2], inout TriangleStreamstream ) { stream.Append( input[0] ); stream.Append( input[1] ); DS_OUTPUT dout2= input[0]; dout2.vPos= input[0].nPos[0]; stream.Append( dout2 ); stream.RestartStrip(); if( input[1].uv >= 0.9998f ){ DS_OUTPUT dout3= input[1]; dout3.vPos= input[0].nPos[1]; stream.Append( input[1] ); stream.Append( dout2 ); stream.Append( dout3 ); stream.RestartStrip(); } }
uv で判断しているのは最後だけ 1ポリゴン追加する必要があるためです。
下図でいう 4 番。
描画自体は簡単にできました。
元となるデータを準備する方が大変です。
中心に位置する追加点を求める何らかの手段が必要となりそうです。
関連エントリ
・Direct3D11/DirectX11 (8) テセレータの動作
・Direct3D11/DirectX11 (7) テセレータの流れの基本部分
・Direct3D11/DirectX11 (6) D3D11 の ComputeShader を使ってみる
・Direct3D11/DirectX11 (5) WARP の試し方、Dynamic Shader Linkage
・Direct3D11/DirectX11 (4) FeatureLevel と旧 GPU の互換性、テクスチャ形式など
・Direct3D11 (DirectX11) シェーダーの書き込み RWBuffer 他
・Direct3D11 の遅延描画、スレッド対応機能、シェーダー命令
・Direct3D11 Technical Preview D3D11の互換性、WARP Driver