zoukankan      html  css  js  c++  java
  • 在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本人也有些许启发,比如做上面的边缘虚化效果可以使用多次边缘检测。这样比直接用均值模糊效果好。

  • 相关阅读:
    Java实现 LeetCode 50 Pow(x,n)
    Java实现 LeetCode 50 Pow(x,n)
    Java实现 LeetCode 49 字母异位词分组
    Java实现 LeetCode 49 字母异位词分组
    Java实现 LeetCode 49 字母异位词分组
    Java实现 LeetCode 48 旋转图像
    Java实现 LeetCode 48 旋转图像
    Java实现 LeetCode 48 旋转图像
    Java实现 LeetCode 47 全排列 II(二)
    Java实现 LeetCode 47 全排列 II(二)
  • 原文地址:https://www.cnblogs.com/blueroses/p/6511751.html
Copyright © 2011-2022 走看看