在UnrealEngine中用Custom節點實現描邊效果


在《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本人也有些許啟發,比如做上面的邊緣虛化效果可以使用多次邊緣檢測。這樣比直接用均值模糊效果好。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM