SIGGRAPH 2003 で、Pradeep SenらによるShadow Silhouette Mapsが公開されます。
この論文のすばらしいところは、影の精度を高めるということではなく、シャドウマップとシャドウボリュームの融合を果たしたところです。
さらに言えば、サブピクセルの扱い方に新しい道を切り開きました。
ここでは、Shadow Silhouette Maps の考え方をテクスチャフィルタに応用してみました。
In SIGGRAPH 2003, Shadow Silhouette Maps by Pradeep Sen and others is exhibited. The wonderful point of this paper is not raising the accuracy of a shadow map, and just achieved fusion of a shadow map and shadow volume. Furthermore, the new way was opened to how to treat a sub pixel. Here, the view of Shadow Silhouette Maps was applied to the texture filter.
今回のプログラムは、次のものです。
This program is as follows.
まぁ、いつものように適当にファイルが入っています。
APP WIZARD から出力されるフレームワークのファイルは紹介を省かせていただきます。
The file is suitably contained so that it may be usual. The file of the framework outputted from APP WIZARD will exclude showing.
hlsl.fx | シェーダの入ったエフェクトファイル |
main.h | アプリケーションのヘッダ |
main.cpp | アプリケーションのソース |
あと、実行ファイル、プロジェクトファイルが入っています。
The execution file and the project file are contained.
テクスチャを使う時には、いつもサイズというものを考慮しなくてはなりません。
メモリやフィルが大量にあって、大きなテクスチャを使える時は何も問題が無いのですが、
現実はそんな都合よくできてはいなくて、なるべく小さなテクスチャにそれらしい絵を描かなくてはなりません。
といっても限度があります。例えば、4x4のサイズのテクスチャに円を描いてみましょう。
When using a texture, you always have to take size into consideration. Although anything does not have a problem when a memory and fillrate have in large quantities and can use a big texture, such convenience is not improved by reality and the picture appropriate for it must be drawn on as small a texture as possible. But it is limited. For example, let's draw a circle on the texture of the size of 4x4.
さすがにこのサイズになると、バイリニアフィルタを使おうが、それらしくは見えません(ゼビウスの時代の弾などはこのサイズですが、そのころの職人も減ってますしね)。
if it becomes this size truly, although I will use a bi-linear filter, it is not visible as it is (although the size of the bullets of the "xevious" is this size, the craftsmen at the time are also decreasing in number).
いくらテクスチャメモリが多くなったといっても、このような細かい構造は存在しますし、ポリゴンが目の前に来てテクスチャが非常に拡大されてしまうときは、ままあります。
これらは、テクスチャのサイズが決まっているので、仕方が無いのですが、
この問題を解消するためのヒントが提示されました。それがシルエットマップです。
シルエットマップとは、深度テクスチャのエッジを結ぶ頂点を格納したテクスチャのことです。
もちろん、深度テクスチャだけに限る必要は無いです。デカールテクスチャの模様の輪郭線に適用できることも容易に想像でき、そのときには、今まではポリゴンでしか表現できなかったキレのある直線を引くことができます。
Although the texture memory increased how much, such fine structure exists, and when a polygon comes at hand and a texture is expanded very much, there is often. Since the size of a texture was decided, although these were unavoidable one, the hint for solving this problem was shown. It is a silhouette map. A silhouette map is the texture which stored the peak to which the edge of a depth texture is connected. Of course, there is no necessity of restricting only to a depth texture. It can imagine easily that it is also applicable to the outline of the pattern of a decale texture, and, then, the existing sharp straight line which has been expressed only by the polygon can be drawn until now.
具体的には、ピクセルデータを描画するときに、
適当に作ったシルエットマップの点(上の図では緑色)を隣のテクセルのシルエットマップの点とつないで、それらの線の中か外にあるかを判定して色を決定します。
実際には、テクセルの色は、テクセルの頂点にあるものと考え、
シルエットマップの隣の4テクセルとの連結線について、
これから描画しようとするピクセルが、どの領域にいるかを計算して、
領域の頂点の色をサンプリングデータとして出力します。
Specifically, when drawing fragments, the point (green in the upper figure) of the silhouette map made suitably is connected with the point of the silhouette map of next texel, it judges whether it is in the inside of those lines, or outside, and a color is determined. In fact, it thinks that the color of texel is at the top of texel, and about a connection line with 4 texels of the next door of a silhouette map, the pixel which is going to draw from now on calculates in which domain it is, and outputs the color of the peak of a domain as sampling data.
今回は、シルエットマップのデータをテクスチャのアルファ成分に格納します。
最初に、シルエットマップの位置座標をピクセル隅からの相対位置で求めます。
それらをアルファ成分の上位と下位の4ビットずつに格納します。
This time, the data of a silhouette map is stored in the alpha component of a texture. First, the position coordinates of a silhouette map are searched for in the relative position from a pixel corner. They are stored in every 4 bits of the higher rank of an alpha ingredient, and a low rank.
実際にピクセルを求めるときには、円とピクセルの対角線2つの交点を計算して、 交点が1つならその場所を、2つあればその平均値を使いました (といっても、この計算を少し間違っているようで、おかしな場所にシルエットポイントが出ています)。
When actually calculating for fragments, the intersection of two diagonal lines of a circle and a pixel was calculated and if the number of intersections was two, the average value was used (even if it says, as it has made a mistake in a part of this calculation, the silhouette point has come out to the strange place).
他にも輪郭線が複数交わっているところはその交点を使えばいいでしょう。
交点が無いときには、中心にしています。
将来的には、大きなサイズのテクスチャから輪郭抽出によって、
適切なシルエットポイントを抽出する方法を構築しなくてはいけないでしょう。
Probably, the place at which two or more outline otherwise crosses should just use the intersection. It is made the center when there is no intersection. In the future, probably, how outline extraction extracts a suitable silhouette point must be built from the texture of big size.
ということで、今回のシェーダプログラムを紹介してきましょう。
今回は、非常に長いシェーダプログラムで、下手な手を入れると、コンパイルが通らないので、
応用を仕様と考えている人は覚悟してください。
By saying, let's introduce this shader program. This time, if it is a very long shader program and a hand is put in, without carrying out deep consideration, since compile does not pass, please those who consider application to be specification be determined.
最初にテクスチャを読み込みます。
今回必要なテクスチャはシルエットマップの領域を選定するための中心とその周り計5テクセルと、
色のサンプリングの元のテクセルの右下のテクセルc5になります。
The first is reading a texture. A texture required this time is texel c5 at the lower right of the center for selecting the domain of a silhouette map, the surroundings a total of five texel, and original texel of a sampling of a color.
hlsl.fx 0040: const float TEX_SIZE = 5.0; 0041: const float TEX_SIZE_INV = 1.0/5.0; 0042: 0043: float4 PS(VS_OUTPUT In) : COLOR 0044: { 0045: float4 t0 = In.Tex / In.Tex.w; 0046: float2 t1 = t0 + float2(-TEX_SIZE_INV, 0); 0047: float2 t2 = t0 + float2( TEX_SIZE_INV, 0); 0048: float2 t3 = t0 + float2( 0,-TEX_SIZE_INV); 0049: float2 t4 = t0 + float2( 0, TEX_SIZE_INV); 0050: float2 t5 = t0 + float2( TEX_SIZE_INV, TEX_SIZE_INV); 0051: 0052: float4 c0 = tex2D( SrcSamp, t0 ); 0053: float4 c1 = tex2D( SrcSamp, t1 ); 0054: float4 c2 = tex2D( SrcSamp, t2 ); 0055: float4 c3 = tex2D( SrcSamp, t3 ); 0056: float4 c4 = tex2D( SrcSamp, t4 ); 0057: float4 c5 = tex2D( SrcSamp, t5 ); 0058:
具体的な、c0~c5の値は次のとおりになります。
The concrete value of c0-c5 is as follows.
次にシルエットマップの点を復元します。
Next, the point of a silhouette map is restored.
hlsl.fx 0059: float3 uv0, uv1, uv2, uv3, uv4; 0060: 0061: uv0.y = frac( 16 * c0.a ); 0062: uv1.y = frac( 16 * c1.a ); 0063: uv2.y = frac( 16 * c2.a ); 0064: uv3.y = frac( 16 * c3.a ); 0065: uv4.y = frac( 16 * c4.a ); 0066: uv0.x = frac( c0.a ) - uv0.y/16; 0067: uv1.x = frac( c1.a ) - uv1.y/16; 0068: uv2.x = frac( c2.a ) - uv2.y/16; 0069: uv3.x = frac( c3.a ) - uv3.y/16; 0070: uv4.x = frac( c4.a ) - uv4.y/16; 0071: 0072: uv0.z = uv1.z = uv2.z = uv3.z = uv4.z = 0; 0073: 0074: uv1.xy += float2(-1, 0); 0075: uv2.xy += float2( 1, 0); 0076: uv3.xy += float2( 0,-1); 0077: uv4.xy += float2( 0, 1); 0078: 0079: float3 dt = frac(TEX_SIZE*t0) - uv0; 0080:
我々がほしいのは、ピクセルのあるシルエットポイントからの相対ベクトルになります。
It is the relative vector from a silhouette point with a pixel which has us want.
シルエットポイントは、
Although it is like being stored by
a = v+u*16 (0<=u, v<16)
によって格納されていて、もともとは8ビットなので
being
v = a&15 u = a/16
で、求めるようなものですが、浮動小数点数なので、16倍して、その小数部を求める方法で、y軸方向を求め、 その残りでx成分を求めます。
being
v = 少数部(16*a) u = a - v/16
あとは、これらのデータの復元から相対ベクトルを求めます。
since it is 8 bits also as a basis, and asking for it, since it is floating decimal mark, 16, a silhouette point is the method of asking for the decimal part, after doubling, asks for the direction of a y-axis, and asks for x ingredients by the remainder.
実際には、次のようになります。(それぞれ、オフセット(float2(a, b)やdtのuv0)を考慮する前の値です)
In fact, it is as follows. (It is the value before taking into consideration offset (float2 (a and b) or uv0 for dt), respectively)
次に、ピクセルがどの位置にあるのかを計算します。
こんなときには、外積を使うと、ポイントが、境界のどちらにあるかを判定できるので、
それをそのまま使い、隣接するシルエットポイントと、に関してピクセルがどちらの面にあるのか調べます。
Next, it calculates in which position a pixel is. Such at the time, if an cross product is used, since a point can judge in which bordering it is, it will be used as it is and it will investigate in which field a pixel is about an adjoining silhouette point.
hlsl.fx 0081: float frag1 = cross(uv1-uv0, dt).z; 0082: float frag2 = cross(uv2-uv0, dt).z; 0083: float frag3 = cross(uv3-uv0, dt).z; 0084: float frag4 = cross(uv4-uv0, dt).z; 0085:
具体的な値は、次のようになります(白が正)。
A concrete value is as follows (white is positive).
最後に、flagの符号から、ピクセルがどの位置にあるのか判定します。
Finally, it judges in which position a pixel is from the mark of flag.
hlsl.fx 0086: float4 ret = c5; 0087: if(0<frag1){ 0088: if(frag3<0) ret = c0; 0089: }else{ 0090: if(0 <frag4) ret = c4; 0091: } 0092: if(frag2<=0 && 0<frag3) ret = c2; 0093: 0094: return ret; 0095: }
ちなみに、計算結果は次のとおりになり、赤い部分が左上のテクセル、青が右上、緑が左下、そして、黒が右下のテクセルをサンプリングします。
Incidentally a calculation result is as follows, we sample a the upper right texel from the blue and the lower right from the black and the upper[lower] texel from the red [green].
あとは、c0~c5の色が実際に反映されれば、キレのある円が作成されます。
If the color of c0-c5 is actually reflected, the existing sharp circle will be created by the back.
残るは、シルエットマップの作成です。
テクスチャの生成自体は普通にRGBA各8ビットのテクスチャを作成します。
The remainder is creation of a silhouette map. The generation of a texture itself creates the texture of 8 bit each of RGBAs ordinarily.
main.cpp 0262: HRESULT CMyD3DApplication::RestoreDeviceObjects() 0263: { 0264: HRESULT hr; 0265: 0266: if( FAILED(hr = m_pd3dDevice->CreateTexture(MAP_SIZE, MAP_SIZE, 1 0267: , D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8 0268: , D3DPOOL_DEFAULT, &m_pTex, NULL))) 0269: return hr; 0270: if( FAILED(hr = D3DXFillTexture(m_pTex, FillTex, NULL))) 0271: return hr; 0272: m_pEffect->SetTexture( "tSrc", m_pTex ); 0273:
テクスチャは読み込まないで、D3DXFillTextureで作成します。
円の方程式と、テクセルの中心を通る2つの直線を連立して、
交点がテクセルの4角形の中に入っていればその点をシルエットポイントとみなします。
A texture is created by D3DXFillTexture without reading. Two straight lines passing through the center of texel and a circle are allied, and if the intersection is contained in four square shapes of texel, it will be considered that the point is a silhouette point.
具体的な連立の方法を示すのは避けますが、ソースは次のようになりますので、各人で計算してください。
Although that the method of concrete alliance is shown avoids, since spurce is as follows, please calculate to everybody.
main.cpp 0074: VOID WINAPI FillTex (D3DXVECTOR4* pOut, CONST D3DXVECTOR2* pTexCoord, 0075: CONST D3DXVECTOR2* pTexelSize, LPVOID pData) 0076: { 0077: FLOAT cx = 0.4f; 0078: FLOAT cy = 0.4f; 0079: FLOAT r = 0.4f-0.0000001f; 0080: 0081: // 色成分を求める 0082: FLOAT x = pTexCoord->x-0.5f*pTexelSize->x - cx; 0083: FLOAT y = pTexCoord->y-0.5f*pTexelSize->y - cy; 0084: FLOAT col = (x*x+y*y<r*r) ? 0.0 : 1.0; 0085: 0086: // シルエットマップを求める 0087: FLOAT Dx = cx - pTexCoord->x; 0088: FLOAT Dy = cy - pTexCoord->y; 0089: FLOAT D0 = 2*r*r+2*Dx*Dy-Dx*Dx-Dy*Dy;// 判別式 y=+(x-px)+py 0090: FLOAT D1 = 2*r*r-2*Dx*Dy-Dx*Dx-Dy*Dy;// 判別式 y=-(x-px)+py 0091: 0092: FLOAT U = 0.5f; 0093: FLOAT V = 0.5f; 0094: int hit0=FALSE; 0095: if(0<=D0){ 0096: FLOAT tp = 0.5f*((Dx+Dy)+sqrt(D0)); 0097: FLOAT tn = 0.5f*((Dx+Dy)-sqrt(D0)); 0098: if(-0.5f/MAP_SIZE <= tp && tp <= +0.5f/MAP_SIZE){ 0099: U=0.5f+MAP_SIZE*tp; 0100: V=0.5f+MAP_SIZE*tp; 0101: hit0=TRUE; 0102: }else 0103: if(-0.5f/MAP_SIZE <= tn && tn <= +0.5f/MAP_SIZE){ 0104: U=0.5f+MAP_SIZE*tn; 0105: V=0.5f+MAP_SIZE*tn; 0106: hit0=TRUE; 0107: } 0108: } 0109: if(0<=D1){ 0110: FLOAT tp = 0.5f*((Dx-Dy)+sqrt(D1)); 0111: FLOAT tn = 0.5f*((Dx-Dy)-sqrt(D1)); 0112: if(-0.5f/MAP_SIZE <= tp && tp <= +0.5f/MAP_SIZE){ 0113: if(hit0){ 0114: U=0.5f*(U + 0.5f+MAP_SIZE*tp); 0115: V=0.5f*(V + 0.5f-MAP_SIZE*tp); 0116: }else{ 0117: U=0.5f+MAP_SIZE*tp; 0118: V=0.5f-MAP_SIZE*tp; 0119: } 0120: }else 0121: if(-0.5f/MAP_SIZE <= tn && tn <= +0.5f/MAP_SIZE){ 0122: if(hit0){ 0123: U=0.5f*(U + 0.5f+MAP_SIZE*tn); 0124: V=0.5f*(V + 0.5f-MAP_SIZE*tn); 0125: }else{ 0126: U=0.5f+MAP_SIZE*tn; 0127: V=0.5f-MAP_SIZE*tn; 0128: } 0129: } 0130: } 0133: 0134: // シルエットマップをアルファ成分に格納する 0135: int iu = 16.0f*U; 0136: int iv = 16.0f*V; 0137: int alpha = iv+iu*16; 0138: 0139: pOut->x = pOut->y = pOut->z = col; 0140: pOut->w = (1.0f/256.0f)*(FLOAT)alpha; 0144: }
バグがあるようですが、なかなか綺麗に輪郭が出ますね。
実際にゲームなどに使うには、テクスチャを作成するオーサリングツール等が必要になるので、気軽には導入されないでしょうが、
確実に未来への道の1つになると思います。
他の方法への応用も可能でしょう。
例えば、より綺麗な法線マップを作成したり、
線形補間に使ってサブピクセル単位で重み付けをしたサンプリングをすることが可能になるでしょう。
ちなみに、今回の処理ですが、ピクセルシェーダ命令を多く使っていることもあって、非常に負荷が高いです。余裕のあるときにしか現状は使えないでしょう。
たとえば、私のPCでは、次のfps (Frames Per Second)が得られました。
ポリゴン表示なし 1520 fps ポイントサンプリング 1475 fps バイリニアフィルタ 1248 fps 今回の方法 477 fps
今回のプログラムを作るのに、 Beeさんや、Monshoさんによる Shadow Silhouette Maps の解説が非常に役に立ちました。ありがとうございました。
Although it seems that there are something and a bug, a silhouette come out finely.
Since the authoring tool which creates a texture is needed in order to actually use for a game etc.,
although it will not be introduced freely, I think that it is certainly set to one of the ways to the future.
Probably, the other methods will also be possible.
For example, the sampling which carried out dignity attachment for this method per sub pixel using linear filtering will be made,
or creation of more beautiful normal maps etc. will be attained.
Although it is this processing, since very long pixel shader commands are used, load is very high.
The present application could be used only when generous.
For example, the next fps (Frames Per Second) was obtained in my PC.
No polygone 1520 fps Point Sampling 1475 fps Linear Filter 1248 fps Silhouette map 477 fps
Explanations of the paper of the Shadow Silhouette Maps by Bee or Monsho was very helpful for making this program. Thank you.