在邊緣高亮效果中我提到過兩種方法, 各有優缺點吧
映像空間域的邊緣檢測效果比較好, 中間沒有多餘的線條. 缺點是PS中計算比較慢
第二種把模型"放大"(其實是變胖)的做法, 可以在VS中完成, 不需要額外的RenderTarget, 適合低端顯卡使用, 適應性好. 不如果模型法線資訊不對的話, 會造成畫面錯亂. 實際使用時可以根據W值(不用Z深度)來畫出遠近粗細一樣的線條
這次提到的基於法線的方法, 其實跟2D的空間域邊緣檢測很相似, 如果要求結果是繪製物體的線條圖而不僅僅是一個邊緣輪廓時, 它就派上用場了. (還是要用PS去算, 實際使用時要注意效能問題)
基本的渲染流程(2 pass):
|
第一個pass用於產生法線圖到一張RenderTarget上, 第二個pass跟據這張法線圖來做邊緣檢測. 實際使用時可以採用Multi-RenderTarget來加速 |
法線資訊要在pixel shader裡進行向量化, 不然會在一些面上出塊很淡的顏色. 如果對品質要求不高, 可以在VS中進行向量化.
RenderTarget要Clear成單位化的值, 我用的(0,0,1), 即純藍色
/*********************VS*********************/<br />float4x4 matViewProjection;</p><p>struct VS_INPUT<br />{<br /> float4 Position : POSITION0;<br /> float3 Normal : NORMAL0;<br />};</p><p>struct VS_OUTPUT<br />{<br /> float4 Position : POSITION0;<br /> float3 Normal : TEXCOORD0;<br />};</p><p>VS_OUTPUT vs_main( VS_INPUT Input )<br />{<br /> VS_OUTPUT Output;</p><p> Output.Position = mul( Input.Position, matViewProjection );<br /> Output.Normal = mul( Input.Normal, matViewProjection );</p><p> return( Output );</p><p>}<br />/*********************PS*********************/<br />float4 ps_main(float3 normal : TEXCOORD0) : COLOR0<br />{<br /> return(float4(normalize(normal), 1.0f));<br />}<br />
注意法線圖的格式是浮點數格式, 我用的是D3DFMT_A16B16G16R16F(因為法線有負值, 你也可以自己壓縮到[0,1]再解開)
有了這張法線圖就很好辦了, 對每個像素計算它與周圍像素的法線夾角餘弦值的和, 再取反(1-degree), 這樣就能計算出來邊緣了
依據就是邊緣處的法線夾角比較大, 餘弦值更接近0甚至為負值.
sampler TexNormal;<br />float2 fInverseViewportDimensions;</p><p>float2 PixelKernel[4] =<br />{<br /> { 0, 1},<br /> { 1, 0},<br /> { 0, -1},<br /> {-1, 0}<br />};</p><p>float4 ps_main(float2 texCoord : TEXCOORD0) : COLOR0<br />{<br /> float4 origin = tex2D(TexNormal, texCoord);<br /> float3 sum = 0;<br /> for (int i = 0; i < 4; i++)<br /> {<br /> float2 texel = texCoord + PixelKernel[i] * fInverseViewportDimensions;<br /> sum += saturate(1.0f - dot(origin.xyz, tex2D(TexNormal, texel).xyz));<br /> }</p><p> return float4(sum, 1.0f);<br />}<br />
最終效果: