Shaderey――非真實渲染
Shaderey――非真實渲染
本文版權歸原作者所有,僅供個人學習使用,請勿轉載,勿用于任何商業用途。
由于本人水平有限,難免出錯,不清楚的地方請大家以原著為準。歡迎大家和我多多交流。
作者:Aras Pranckevicius
翻譯:clayman
Blog:http://blog.csdn.net/soilwork
clayman_joe@yahoo.com.cn
本文描述了以非真實渲染(none-photorealistic rendering)風格,對戶外場景進行著色的技術。在2003年秋天的Beyond3D/ATI shader compititon中,Shaderey程序最先使用了這些技術來進行渲染。在Shaderey的戶外場景中,包含了地形,云,樹木,房屋,天空頂,以及湖水,如圖所示:
確切的說,這里使用的NPR技術都是在圖片空間(image space)進行的操作,它依賴于場景中兩張重要的圖片:一張包含了顏色信息,一張包含法線和深度信息。處理過程分為兩部分:
渲染: 把場景渲染到顏色和法線/深度目標中。
后期處理: 在圖片空間進行一系列過濾操作,獲得最終的非真實效果。
后期處理包括:HSV空間下的顏色扭曲,屏幕空間中簡單“陰影線(hatching)”的渲染,以及在法線/深度不連續處的輪廓線繪制。我們將在后面詳細討論這些過濾操作。首先,先來看看Shaderey的場景渲染方式。
場景渲染
場景中所有的樹木和房屋都經過了可視體裁剪(frustum-culled)。地形是一張512 x 512的高度圖,但分為若干尺寸固定的(32 x 32)小塊(chunk)。所有通過視見體裁剪的地形小塊都沒有進行任何形式的LOD。整個場景使用了一張1024 x 1024的陰影帖圖。房屋和樹木都將產生陰影,并且投影到地面上。場景中的樹木和木屋投射陰影,而地形接收這些影子。我們使用pick-nearest采樣器,對陰影貼圖進行四次有偏移的采樣,然后再shader中對這些值進行均值采樣,以提高影子邊界上的質量。陰影貼圖并不需要覆蓋整個地形的大小,在我們的實現中,它將隨觀察者的位置移動,以保證觀察者前方總是有正確的陰影。
為了模擬湖面的簡單反射效果,可以把攝像機反轉到水面之下,把場景渲染為一張較小的平面反射貼圖。我們把這張陰影貼圖投影到水面上,另外使用兩張卷動的EMBM風格的凹凸貼圖來模擬波紋。為了減少幾何數據,渲染到反射貼圖中的地形將使用較低的LOD層次。對所有物體來說,大氣光照散射效果都是在頂點級別計算的。
除了把顏色渲染到后備緩沖之外,還需要把場景中物體的法線和深度渲染到一張和屏幕大小相同的A8R8B8G8紋理中。世界坐標下的法線信息保存在RGB通道中,深度值的導數保存在alpha通道中。
下面是在vertex shader中,使用HLSL正確計算法線和深度值倒數的代碼:
//output normal in RGB, sort-of-depth in A, p – final ( clip space) position, n—world space normal
static inline float4 gNormalZ( float4 p, float3 n)
{
float4 o;
o.xyz = n * 0.5 + 0.5; // in to 0….1 range
o.w = 100.0 / ( p.w + 100 ); // kind-of-depth
}
如果支持DirectX 9中的Multiple Render Target(MRT),可以在渲染場景顏色的同時,渲染法線和深度。如果不支持MRT,則需要分兩次渲染(譯注:從demo來看,使用MRT將會嚴重影響渲染質量,應該是由于MRT不支持多重采樣造成的)。當把地形渲染到法線/深度紋理中時,需要使用<< Non-Photorealistic Rendering with Pixel and Vertex Shader>>中所描述的方法,在pixel shader中對陰影貼圖進行采樣,對陰影中的像素來說,需要對插值之后深度值取反(譯注:在Non-Photo原文中是對法線值取反)。這樣做的原因在后面描述后期處理的部分會講解。
圖片后期處理
目前已經把場景渲染為顏色和法線/深度圖片了,接下來就可以對這些圖片進行一系列處理了,包括把顏色轉到HSV顏色空間下進行風格化處理,繪制邊緣輪廓線,實現陰影線。
顏色失真
圖片處理的第一步是進行顏色失真,獲得風格化的樣式。
1.降低采樣率,把圖片縮為一張521x512的紋理。
2.把顏色從RGB空間轉換到HSV空間,并且量化(quantize)顏色值。顏色空間的轉換將通過對一張體積材質的查找來實現。把原像素的RGB值作為立方紋理坐標。立方紋理中的像素為HSV顏色空間。這里我們將使用一張32x32x32的紋理,并且不進行任何過濾,所以顏色轉換的同時將會量化顏色值。
3.使用2D偏移紋理,對同一紋理中當前像素的兩個偏移位置進行采樣。用來訪問偏移紋理的紋理坐標由程序控制,它們將和觀察者的位置有關(觀察點的yaw值將在水平方向影響偏移,pitch值在垂直方向影響)。這些額外的采樣顏色也必須轉換到HSV空間。
4.替換圖片中的顏色。目前我們有2個額外的偏移采樣。首先,我們檢察兩個偏移值之間差分的差值,如果小于某個限制,就什么也不做。如果它們之間的差別足夠大,則輸出S和V通道的均值,保留中心原像素的H值。這個方法能高效的在顏色區域邊緣替換原像素的飽和度。
5.再使用一張立方紋理把顏色轉換回HSV空間。
第2~5步的pixel shader代碼如下,需要pixel shader 2.0的支持。
struct PS_INPUT
{
float2 uv[2] : TEXCOORD0; //base uv,displace uv
};
float4 psMain( PS_INTPUT i) : COLOR
{
//sample rgb,convert into hsv
half base = tex2D( smpBase, i.uv[0] ).rgb;
base = tex3D( smpRGB2HSV, base ).rgb;
//get 2 displaced sample locations
half2 bleedB = tex2D ( smpBleedB, i.uv[1] ).rg * 2 -1;
half2 bleedC = tex2D ( smpBleedC, i.uv[1] ).rg * 2 -1;
float2 uvB = i.uv[0] + bleedB * (8.0/512);
float2 uvC = i.uv[0] + bleedC * (-7.0/512);
//sample base at displaced locations ,convert to hsv
half3 baseB = tex2D( smpBase,uvB).rgb;
baseB = tex3D( smpRGB2HSV,baseB);
half3 baseC = tex2D( smpBase, uvC).rgb;
baseC = tex3D( smpRGB2HSV,baseC);
half3 bleed = baseB * 0.5 + baseC * 0.5;
//final color is base if differences in hsv values are smller than tresholds
//else average of displace values
half3 diff = abs(base - baseC) - half( 1/8.0,1/3.0,1/3.0)
half3 final = all( diff < float3 ( 0,0,0) ? base : bleed;
//leave original hue channel
final.r = base.r;
//convert back to rgb
return tex3D ( smpHSV2RGB),final);
}
邊緣檢測和輪廓線
為了獲得NPR風格的樣式,必須在圖片上渲染出深色的輪廓線和陰影線,表現出場景的著色效果。在Shaderey中,我們將同時繪制邊緣輪廓線和陰影線。這里需要使用之前計算的法線/深度圖來計算邊緣,用光線和法線的點積來計算那些區域需要繪制陰影線。陰影線是一張簡單的紋理。在這一步處理中,邊緣和輪廓線都是白色。最終合成時,進行反色處理,輪廓線變為純黑色,輪廓線顏色根據場景的著色進行衰減。
以下是繪制輪廓線和陰影線的pixel shader代碼:
half4 psMain ( float2 uv[3]:TEXCOORD): COLOR
{
//sample center and 2 neightbours
half4 cbase = tex2D( smpBase, i.uv[0]);
half4 cb1 = tex2D(smpBase, i.uv[1]);
half4 cb3 = tex2D(smpBase, i.uv[2]);
//normal into -1..1 range
half3 nbase = cbase.xyz * 2 -1;
half3 nb1 = cb1.xyz * 2 - 1;
half3 nb3 = cb3.xyz * 2 - 1;
//edges from normals
half2 ndiff;
ndiff.x = dot( nbase,nb1);
ndiff.y = dot( nbase,nb3);
ndiff -= 0.6;
ndiff = ndiff > half2(0,0) ? half2(0,0):half2(1,1);
half ndiff1 = ndiff.x + ndiff.y;
//edges from z
float2 zdiff;
zdiff.x = cbase.a - cd1.a;
zdiff.y = cbase.a - cb3.a;
adiff = abs(zdiff) - 0.02;
zdiff = zdiff > half2(0,0) ? half2(1,1) : half2(0,0);
//sampler hatch
half4 chatch = tex2D( smpHatch, i.uv[0]);
//dot normal with light
half dotNL = dot( nbase, vLightDir);
//hatch blend factor
half factor = saturate( (1.0 - 0.9 - dotNL) * 2);
chatch *= factor;
return chatch + ndiff1 + dot(zdiff,half2(1,1));
}
最終合成
在處理完了兩張圖片之后,把失真之后的顏色與反轉之后的邊緣/輪廓線進行調制,合成出最終圖像。
點擊這里下載完成程序和代碼。
posted on 2008-08-26 17:47 狂爛球 閱讀(862) 評論(0) 編輯 收藏 引用 所屬分類: 圖形編程