• 在UnrealEngine中用Custom节点实现描边效果


    转自:https://www.cnblogs.com/blueroses/p/6511751.html

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

  • 相关阅读:
    gcc 编译
    UltraEdit 添加到右键菜单
    linux 编译错误:undefined reference to `__gxx_personality_v0'
    UltraEdit 取消生成.bak文件
    容器
    Windows CEvent事件
    Windows _beginthreadex 线程类与线程池
    C++ 工厂模式
    Mutex linux线程锁
    windows CRITICAL_SECTION 线程锁
  • 原文地址:https://www.cnblogs.com/sevenyuan/p/7825723.html
Copyright © 2020-2023  润新知