在《Real Time Rendering, third edition》一書中,作者把描邊算法分成了5種類型。
1、基於觀察角度與表面法線的輪廓渲染。缺點很明顯。
2、過程式幾何輪廓渲染。即先渲染背面,通過頂點壓平等手段,渲染輪廓線,之后渲染正面。優點:快速有效,適合大多數模型,缺點:不合適和立方體之類的平整模型。
3、基於圖像處理的輪廓線渲染。通過邊緣監測來判斷輪廓。
4、基於輪廓檢測的輪廓線渲染。同時監測相鄰的2個面法線值得正負是否相反。
5、以上方法結合。
除此之外還有:
6、沿法線方向放大模型(vs)並用描邊色渲染(ps)正常渲染模型
7、直接模糊模板
其中2、4、6在材質編輯器中是做不到,接下來本人將會分享剩下幾種方法的代碼。方法2在材質編輯器里無法實現因為TwoSideSign無法用於世界位移(通過攝像機向量與法向量點乘判斷的方式也被不行)
其實是可以做到的,不過比較蛋疼,那就是復制一個模型,設Mask為0,同時沿着法線方向放大。從而得到放大的模板。具體做法不再復述,如果不會做,可以留言問我。
4.15版本的項目設置中多了這個,應該可以解決邊緣抖動的問題,所以推薦使用4.15版本
然后Epic的程序員竟然忘記給Custom Stencil加這個功能,這就導致了我的部分效果會出現抖動的問題,我能說MDZZ么?
首先是方法3。邊緣檢測有以下幾種檢測算子(摘自UntiyShader入門精要),不過查了網上的資料,感覺還是Sobel比較好,所以別的2種不搞了。
當然還有別的,在此就不深入了。
虛幻4案例里就是用這個方法以及檢測算子Prewitt(www.tomlooman.com里的案例),通過邊緣檢測深度的方式來。以下是我轉化的HLSL代碼:
//input SceneTexSize //input UV //input NotUse //input OutLineSize //input MaxZ //input Alpha //input OutLineColor float Depth=0; float2 Sampler[]={float2(-1,-1),float2(-1,0),float2(-1,1), float2(0,-1),float2(0,1), float2(1,-1),float2(1,0),float2(1,1)}; for(int i=0;i<8 ;i++) { Depth+=SceneTextureLookup(UV+Sampler[i]*SceneTexSize*OutLineSize,13,false).x; } //Normalize Depth to 0.0-1.0 Range,規整化 Depth=MaxZ/(clamp(Depth,0,MaxZ)+MaxZ); //自定義深度物體的遮罩 float Mask=MaxZ/(SceneTextureLookup(UV,13,false).x+MaxZ); //減去自定義深度物體部分,也就是得到輪廓,0.2是因子,可以設置變量來 調節 Depth+=-MaxZ/(SceneTextureLookup(UV,13,false).x+MaxZ)+0.2; //深度部分到此為止,以下物體透視部分 //被物體遮擋了 float Check=SceneTextureLookup(UV,13,false).x-SceneTextureLookup(UV,1,false).x; float KeepOut=floor(Mask*2); if(Check>0) { Check=clamp(KeepOut,0,1); }else { Check=0; } KeepOut*=Alpha*Check; Depth=clamp(-Depth+KeepOut,0,1); return lerp(SceneTextureLookup(UV,14,false),OutLineColor,Depth);
這段代碼還包含了透視效果,如果只需要描邊可以自己編輯。感覺和傳統邊緣檢測不一樣。
官方的風格化渲染用的是Roberts檢測算子,以下是對應HLSL代碼(因為Sphere
Mask不是HLSL中的原生函數,所以去掉了,而且感覺不太好理解,就沒有深入),略有修改:
//input SceneTexSize //input UV //input NotUse //input OutLineSize //input OutLineColor //input PostProcessBlendWeight float4 Depth=0; float2 Sampler[]={float2(-1,0),float2(0,-1), float2(0,1),float2(1,0)}; for(int i=0;i<4 ;i++) { Depth+=SceneTextureLookup(UV,1,false)-SceneTextureLookup(UV+Sampler[i]*SceneTexSize*OutLineSize,1,false); } Depth=clamp((1-clamp(Depth/-300,0,1))*2,0,1); Depth=(1-Depth).x*lerp(clamp(1-SceneTextureLookup(UV,1,false).x/6000,0.25,1),clamp(1-SceneTextureLookup(UV,1,false).x/90000,0,1),PostProcessBlendWeight); return lerp(SceneTextureLookup(UV,14,false),OutLineColor,Depth);
不過需要注意的是最后的輪廓往往是半透明的,所以需要在倒數第二行增加:
if(Depth>OutLineDepth) { Depth=1; }
通過判斷深度的方式強行讓Depth=1,從而實現讓輪廓變實。(OutLineDepth為自己設置的變量)
Sobel檢測算子HLSL代碼,基於亮度檢測:
//input SceneTexSize //input UV //input NotUse //input OutLineSize //input MaxZ //input OutLineColor float3 w=float3(0.2125,0.7154,0.0721); float2 Sampler[]={float2(-1,-1),float2(-2,0),float2(-1,1), float2(0,-2),float2(0,0),float2(0,2), float2(1,-1),float2(2,0),float2(1,1)}; float2 UVOffset[]={float2(-1,-1),float2(0,-1),float2(1,-1), float2(-1,0),float2(0,0),float2(1,0), float2(-1,1),float2(0,1),float2(1,1),}; float2 Edge=0; for(int i=0;i<9 ;i++) { Edge+=Sampler[i]*dot(SceneTextureLookup(UV+UVOffset[i]*SceneTexSize*OutLineSize,14,false).xyz,w); } //最后的length可以改成1-abs(Edge.x)-abs(Edge.y),這樣可以減少運算量 return lerp( SceneTextureLookup(UV,14,false),OutLineColor,length(Edge));
方法5法線與深度相配合的邊緣檢測:因為用SceneTextureLookup(UV,8,false);會有Bug,所以暫時空着,直接用節點寫。
方法7代碼(模糊用的是之前寫的代碼,我懶得改了,這個模糊其實有點問題):
int UVOfferset[]={-3,-2,-1,0,1,2,3}; float Weights[]= { 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1, 1,1,1,1,1,1,1 }; float3 OutColor={0.0,0.0,0.0}; for(int i=0;i<=7;i++) { for(int j=0;j<=7;j++) { OutColor+=Weights[i*7+j]*SceneTextureLookup(UV+float2(UVOfferset[j]*SceneTexSize.x*OutLineSize,UVOfferset[i]*SceneTexSize.y*OutLineSize),24,false); } } float Alpha=(float4(OutColor,1.0f)/49-SceneTextureLookup(UV,24,false)).x; Alpha=smoothstep(0,Max,Alpha); return lerp(SceneTextureLookup(UV,14,false).xyz,OutLineColor,Alpha);
官方論壇上還有幾個案例:
https://forums.unrealengine.com/showthread.php?127151-Custom-Stencil-Radial-Silhouette-Post-Process-Materiel-(HLSL)-(PC)-(Full-code)-(4-13)&highlight=SceneTextureLookup
這個本質上還是用的是Sobel檢測算子,不過讀了他的HLSL本人也有些許啟發,比如做上面的邊緣虛化效果可以使用多次邊緣檢測。這樣比直接用均值模糊效果好。