【Unity Shader 描边效果_案例分享】

1.实现逻辑

描边效果Shader有多种实现方式,可以通过后处理MatCap实现。
这次主要想展示的是通过两个Pass实现。
当Shader中有多个Pass时,渲染流程会安装顺序依次执行,于是后面的Pass效果会覆盖前面Pass所绘制的图像。
实现的逻辑是:

1.1
  • 第一层,将模型的顶点沿着法线方向膨胀一段距离,膨胀的距离就是描边的宽度,膨胀的距离可以作为属性开放到材质面板上,方便调用。然后再为膨胀之后的模型指定一个纯色进行着色,着色不需要参与任何光照交互,膨胀之后的纯色就是描边的颜色。将颜色也作为属性开放到材质面板,方便后期更换颜色。
1.2
  • 第二层,模型以正常效果进行显示。也就是Surface Shader,并使用Standard Specular光照模型。
1.3
  • 在正常情况下,第一层膨胀后的模型会将第二层的模型完全笼罩,第二层的模型是无法通过深度测试的,最终只会显示第一层膨胀后的效果,解决这个就需要让第二次的模型通过深度测试。
1.4
  • 办法就是关闭第一层的深度写入,这样就没有第一层模型的深度值,第二层模型自然就可以通过深度测试了。
1.5
  • 这样会出现一种情况,当物体关闭深度写入之后,在膨胀着一层后面的物体就不知道自己是被遮挡的,本该被遮挡,因为没有深度写入,现在反而通过了速度测试,因此就会覆盖掉膨胀的这一层模型。
- 避免这一状况出现

需要使该物体在所有不透明物体绘制完成之后再进行绘制,因此更改Shader的渲染队列为Transparent,如此一来绘制的图像就不会被后面的物体覆盖了。
在这里插入图片描述

2.编写Shader

理解了实现逻辑,那么接下来按照逻辑思路开始编写Shader。

2.1.开放属性

正常效果相关属性:

		[Header(Texture Group)]
		[Space(10)]
		_Albedo ("Albedo" , 2D) = "white" {}
		[NoScaleOffset]_Specular ("Specular (RGB - A)" , 2D) = "black" {}
		[NoScaleOffset]_Normal("Normal" , 2D) = "bump" {}
		[NoScaleOffset]_AO ("Ambient Occlusion" , 2D) = "white" {}
讲解:

Properties代码块开放了常规Specular工作流所需要的贴图有Albedo、Specular、Normal 和 AO,至于Smoothness属性,本案例参照Unity内置的Standard Specular Shader,将其合并到Specular贴图的alpha通道中。
通过Albedo纹理贴图的Tiling和Offset控制所有的纹理贴图,也就是使用Albedo的纹理坐标对所有纹理贴图进行采样,因此除了Albedo,其他所有纹理贴图的Tiling和Offset都会失效,如是在这些属性前添加[NoScaleOffset]指令以隐藏Tiling和Offset。
为了对属性进行类别区分,在这些属性之前添加[Header(Texture Group)]指令,从而在材质面板上显示“Texture Group”文字以起到分组提醒的作用。

描边相关属性:
        [Header(Outline Properties)]
        [Space(10)]
        _OutlineColor (“OutlineColor” , Color) = (1,0,1,1)
        _OutlineWidth (“OutlineWidth” , Range(0,0.1)) = 0.01

讲解:
除了开放正常效果所需的贴图属性,还开放了两个用于控制描边效果的属性,分别为:描边颜色 _Outlinecolor 描边宽度_OutlineWidth。
为了对属性进行类别区分,同样在这些属性之前添加[Header(Outline Properties)]指令,从而在材质面板上显示“Outline Properties”文字以起到分组提醒的作用。

2.2.SubShader标签
代码:
        Tags { "RenderType"="Opaque" "Queue"="Transparent" }
讲解:

属于不透明效果,因此渲染类型设置为“Opaque”。为了使物体在所有不透明物体之后再进行绘制,还需要将渲染队列设置为“Transparent”。

2.3.描边Pass
代码:
        Pass
        {
            ZWrite Off
            
            CGPROGRAM
    
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            fixed4 _OutlineColor;
            fixed _OutlineWidth;

            v2f vert (appdata_base v)
            {
                v2f o;
                v.vertex.xyz += v.normal * _OutlineWidth;
                o.vertex = UnityObjectToClipPos(v.vertex);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return _OutlineColor;
            }

	        ENDCG

        }
讲解:

在描边效果的Pass中,为了不遮挡后面的Pass,需要将深度写入关闭。
在顶点着色器中,需要将顶点的法线向量乘以_OutlineWidth属性,用于控制法线向量的长度,乘积与顶点相加,使顶点沿着法线偏移,从而产生膨胀效果。
在片段着色器中不需要做任何计算,直接返回 _OutlineColor属性即可。

2.3.描边Pass
代码:
	        CGPROGRAM
	
	        #pragma surface surf StandardSpecular fullforwardshadows
	
	        struct Input
	        {
	            float2 uv_Albedo;
	        };
	
	        sampler2D _Albedo;
	        sampler2D _Specular;
	        sampler2D _Normal;
	        sampler2D _AO;
	
	        void surf (Input IN , inout SurfaceOutputStandardSpecular o)
	        {
	            fixed4 c = tex2D (_Albedo , IN.uv_Albedo);
	            o.Albedo = c.rgb;
	
	            fixed4 specular = tex2D(_Specular , IN.uv_Albedo);
	            o.Specular = specular.rgb;
	            o.Smoothness = specular.a;
	
	            o.Normal = UnpackNormal(tex2D (_Normal, IN.uv_Albedo));
	            o.Occlusion = tex2D(_AO, IN.uv_Albedo);
	        }
讲解:

使用Surface Shader编写模型的正常效果。在编译指令中,定义光照模型为StandarSpecular,然后添加fullforwardshadows指令,使物体支持所有灯光类型的投影。
在表面着色器中使用Albedo的纹理坐标uv_Albedo 对所有的贴图进行采样,然后分别输出到对应的表面属性上。其中Smoothness被合并在Specular纹理的Alpha通道中,因此将specular.a输出到Smoothness。

下面是完整代码:

Shader "Sample/Outline"
{
    Properties
    {
        [Header(Texture Group)]
        [Space(10)]
        _Albedo ("Albedo", 2D) = "white" {}
        [NoScaleOffset]_Specular ("Specular (RGB - A)" , 2D) = "black" {}
        [NoScaleOffset]_Normal("Normal" , 2D) = "bump" {}
        [NoScaleOffset]_AO ("Ambient Occlusion" , 2D) = "white" {}

        [Header(Outline Properties)]
        [Space(10)]
        _OutlineColor ("OutlineColor" , Color) = (1,0,1,1)
        _OutlineWidth ("OutlineWidth" , Range(0,1)) = 0.01
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" "Queue"="Transparent" }
        LOD 100

        Pass
        {
            ZWrite Off
            
            CGPROGRAM
    
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct v2f
            {
                float4 vertex : SV_POSITION;
            };

            fixed4 _OutlineColor;
            fixed _OutlineWidth;

            v2f vert (appdata_base v)
            {
                v2f o;
                v.vertex.xyz += v.normal * _OutlineWidth;
                o.vertex = UnityObjectToClipPos(v.vertex);

                return o;
            }

            fixed4 frag(v2f i) : SV_Target
            {
                return _OutlineColor;
            }

        ENDCG

        }

        //----------Regular layer----------
	    CGPROGRAM
	
	    #pragma surface surf StandardSpecular fullforwardshadows
	
	    struct Input
	    {
	        float2 uv_Albedo;
	    };
	
	    sampler2D _Albedo;
	    sampler2D _Specular;
	    sampler2D _Normal;
	    sampler2D _AO;
	
	    void surf (Input IN , inout SurfaceOutputStandardSpecular o)
	    {
	        fixed4 c = tex2D (_Albedo , IN.uv_Albedo);
	        o.Albedo = c.rgb;
	
	        fixed4 specular = tex2D(_Specular , IN.uv_Albedo);
	        o.Specular = specular.rgb;
	        o.Smoothness = specular.a;
	
	        o.Normal = UnpackNormal(tex2D (_Normal, IN.uv_Albedo));
	        o.Occlusion = tex2D(_AO, IN.uv_Albedo);
	    }
	    ENDCG
    }
}
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暴走约伯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值