【UnityShader】常用效果内外发光、 描边

这篇博客记录一下常用的几种效果:内发光、外发光以及描边的几种实现方法

内发光

实现方法

一般实现方法就是使用Empricial菲涅尔近似公式来实现:

F (v,n)=saturate(base + pow(scale * (1 - v·n), power))
  • base为基础反射率,scale为菲涅尔效应强弱,power为菲涅尔效应作用角度范围大小(即power越大,反射率会在越小的范围内迅速变为1)
  • 在计算菲涅尔效应时,使用F对反射和基础颜色值进行插值混合,在这里计算内发光,就对发光颜色和基础颜色进行插值混合。
half shine = max(pow(1-dot(normal, viewDir), _ShinePower * 10), 0.001) * _ShineScale;
col.rgb = lerp(col.rgb, _ShineColor.rgb, shine);

实现代码

Shader "Custom/Effect/Shine_Out"
{
     
    Properties
    {
     
        _Color ("Main Color", Color) = (1,1,1,1)
		_ShineColor ("Shine Color", Color) = (1,1,1,1)
		_ShineScale ("Shine Scale", Range(0, 1)) = 0.5
		_ShinePower ("Shine Power", Range(0, 1)) = 0.5
    }
    SubShader
    {
     
        Tags {
      "Queue"="Transparent" }

        LOD 200

        Pass
        {
     
			Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
			#include "Lighting.cginc"

			half4 _Color;
			half4 _ShineColor;
			float _ShineScale;
			float _ShinePower;

            struct a2v
            {
     
                float4 vertex : POSITION;
				float3 normal : NORMAL;
            };

            struct v2f
            {
     
				float4 pos : SV_POSITION;
				float3 normal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
            };


            v2f vert (a2v v)
            {
     
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
				o.normal = UnityObjectToWorldNormal(v.normal);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
     
				half3 normal = normalize(i.normal);
				half3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
				half3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

				fixed4 col = fixed4(0,0,0,1);
				col.rgb = _LightColor0.rgb * _Color.rgb * (dot(lightDir, normal)*0.5+0.5);
				half shine = max(pow(1-dot(normal, viewDir), _ShinePower * 10), 0.001) * _ShineScale;
				col.rgb = lerp(col.rgb, _ShineColor.rgb, shine);
				
                return col;
            }
            ENDCG
        }
    }
}

描边

内发光方法实现

  • 沿用上文写的内发光实现方法,仅在内发光的值上做mask或者数值限制。这里仅使用step做限制,需要更风格化的效果需要制作mask做描边的限制。
half shine = max(pow(1-dot(normal, viewDir), _ShinePower * 10), 0.001) * _ShineScale;
shine = step(_Threshold, shine);
col.rgb = lerp(col.rgb, _ShineColor.rgb, shine);
  • 阈值为新增变量,当内发光的渐变范围值大于阈值时,显示为描边颜色,小于阈值则显示为原颜色
  • 为了显示清楚描边,图中去掉了原本的光照计算
    【UnityShader】常用效果内外发光、 描边_第1张图片
  • 优点
    • 实现简单,可以在一个Pass里实现,性能消耗小
  • 缺点
    • 只能向内描边,且描边宽度受视角与法线方向影响,不易控制

沿法线外扩背面方法实现

  • 这种方法比较常用,用两个Pass实现,一个Pass只渲染背面,且把物体背面沿法线方向向外扩张,第二个Pass正常渲染物体。

    v2f vert (appdata v)
    {
           
        v2f o;
    	float4 viewPos = float4(UnityObjectToViewPos(v.vertex), 1.0);
    	float3 viewNormal = mul(UNITY_MATRIX_IT_MV, v.normal);
    
    	viewNormal.z = -0.5;//防止内凹模型背面扩张后遮挡正面
    	float3 normal = normalize(viewNormal);
    	viewPos += float4(normal, 1.0) * _OutlineWidth;
    
    	o.pos = mul(UNITY_MATRIX_P, viewPos);
    	o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    	o.normal = UnityObjectToWorldNormal(v.normal);
    
        return o;
    }
    
  • 效果图如下
    【UnityShader】常用效果内外发光、 描边_第2张图片

  • 优点

    • 实现简单,大部分模型的描边效果都很不错
  • 缺点

    • 对于例如正方体这样平整且转折锐利的物体无法使用
    • 多使用了一个Pass,渲染性能消耗提高了不少
  • 全部代码如下

    Shader "Custom/Effect/Outline"
    {
           
        Properties
        {
           
            _Color ("Main Color", Color) = (1,1,1,1)
    		[HDR]_OutlineColor ("Outline Color", Color) = (1,1,1,1)
    		_OutlineWidth ("Outline Width", Range(0, 2)) = 1
        }
        SubShader
        {
           
            Tags {
            "Queue"="Transparent" }
    
            LOD 200
    
            Pass
            {
           
    			Cull Front
    			Blend SrcAlpha OneMinusSrcAlpha
    
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    
                struct appdata
                {
           
                    float4 vertex : POSITION;
    				float3 normal : NORMAL;
                };
    
                struct v2f
                {
           
                    float4 pos : SV_POSITION;
                };
    
    			half4 _OutlineColor;
    			float _OutlineWidth;
    
                v2f vert (appdata v)
                {
           
                    v2f o;
    				float4 viewPos = float4(UnityObjectToViewPos(v.vertex), 1.0);
    				float3 viewNormal = mul(UNITY_MATRIX_IT_MV, v.normal);
    
    				viewNormal.z = -0.5;
    				float3 normal = normalize(viewNormal);
    				viewPos += float4(normal, 1.0) * _OutlineWidth;
    
    				o.pos = mul(UNITY_MATRIX_P, viewPos);
    
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
           
                    return _OutlineColor;
                }
                ENDCG
            }
    
    		Pass
            {
           
                CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
    
                #include "UnityCG.cginc"
    
                struct appdata
                {
           
                    float4 vertex : POSITION;
                };
    
                struct v2f
                {
           
                    float4 pos : SV_POSITION;
                };
    
                half4 _Color;
    
                v2f vert (appdata v)
                {
           
                    v2f o;
    				o.pos = UnityObjectToClipPos(v.vertex);
    
                    return o;
                }
    
                fixed4 frag (v2f i) : SV_Target
                {
           
                    fixed4 col = _Color;
    
                    return col;
                }
                ENDCG
            }
        }
    }
    

图像处理方法实现

  • 基于后处理图像处理的边缘检测方法:常用的一般是Sobel算子和Roberts算子,边缘处的梯度绝对值会较大,也就是计算每个像素的梯度,并用梯度对初始颜色和描边颜色进行插值,也就完成了描边的操作。

    half Sobel(v2f i)
    {
           
    	const half Gx[9] = {
            -1,-2,-1,
    						  0, 0, 0,
    						  1, 2, 1 };
    
    	const half Gy[9] = {
            -1, 0, 1,
    						 -2, 0, 2,
    						 -1, 0, 1 };
    
    	half texColor;
    	half edgeX = 0;
    	half edgeY = 0;
    	for (int it = 0; it < 9; it++)
    	{
           
    		texColor = luminance(tex2D(_MainTex, i.uv[it]));
    		edgeX += texColor * Gx[it];
    		edgeY += texColor * Gy[it];
    	}
    
    	half edge = lerp(0, abs(edgeX) + abs(edgeY), _EdgeWidth);
    
    	return edge;
    }
    
  • 效果如下
    【UnityShader】常用效果内外发光、 描边_第3张图片

  • 优点

    • 适用于任何模型
    • 不用增加一个Pass的渲染性能消耗
  • 缺点

    • 增加了后处理的性能消耗
    • 无法单独对一个物体进行描边
    • 一些变化很小的变换难以检测
  • 全部代码如下

    Shader "Custom/Postprocess/EdgeDetection"
    {
           
    	Properties
    	{
           
    		_MainTex ("Texture", 2D) = "white" {
           }
    		_EdgeColor ("EdgeColor", Color) = (1,0,0,1)
    		_BackgroundColor ("BackgroundColor", Color) = (1,1,1,1)
    		_EdgeOnly ("Edge Only", Range(0,1)) = 0.5
    		_EdgeWidth ("Edge Width", Range(0,1)) = 1
    	}
    	SubShader
    	{
           
    
    		Pass
    		{
           
    			ZTest Always Cull Off ZWrite Off
    
    			CGPROGRAM
    			#pragma vertex vert
    			#pragma fragment frag
    			
    			#include "UnityCG.cginc"
    
    			struct v2f
    			{
           				
    				float4 vertex : SV_POSITION;
    				half2 uv[9] : TEXCOORD0;
    			};
    
    			sampler2D _MainTex;
    			float2 _MainTex_TexelSize;
    			fixed4 _EdgeColor;
    			fixed4 _BackgroundColor;
    			fixed _EdgeOnly;
    			fixed _EdgeWidth;
    		
    			v2f vert (appdata_img v)
    			{
           
    				v2f o;
    				o.vertex = UnityObjectToClipPos(v.vertex);
    				half2 uv = v.texcoord;
    
    				o.uv[0] = uv + half2(_MainTex_TexelSize.x *-1, _MainTex_TexelSize.y *-1);
    				o.uv[1] = uv + half2(_MainTex_TexelSize.x * 0, _MainTex_TexelSize.y *-1);
    				o.uv[2] = uv + half2(_MainTex_TexelSize.x * 1, _MainTex_TexelSize.y *-1);
    				o.uv[3] = uv + half2(_MainTex_TexelSize.x *-1, _MainTex_TexelSize.y * 0);
    				o.uv[4] = uv + half2(_MainTex_TexelSize.x * 0, _MainTex_TexelSize.y * 0);
    				o.uv[5] = uv + half2(_MainTex_TexelSize.x * 1, _MainTex_TexelSize.y * 0);
    				o.uv[6] = uv + half2(_MainTex_TexelSize.x *-1, _MainTex_TexelSize.y * 1);
    				o.uv[7] = uv + half2(_MainTex_TexelSize.x * 0, _MainTex_TexelSize.y * 1);
    				o.uv[8] = uv + half2(_MainTex_TexelSize.x * 1, _MainTex_TexelSize.y * 1);
    
    				return o;
    			}
    			
    			fixed luminance(fixed4 color)
    			{
           
    				return 0.2125 * color.r + 0.7154 * color.g + 0.0721 * color.b;
    			}
    
    			half Sobel(v2f i)
    			{
           
    				const half Gx[9] = {
            -1,-2,-1,
    									  0, 0, 0,
    									  1, 2, 1 };
    
    				const half Gy[9] = {
            -1, 0, 1,
    									 -2, 0, 2,
    									 -1, 0, 1 };
    
    				half texColor;
    				half edgeX = 0;
    				half edgeY = 0;
    				for (int it = 0; it < 9; it++)
    				{
           
    					texColor = luminance(tex2D(_MainTex, i.uv[it]));
    					edgeX += texColor * Gx[it];
    					edgeY += texColor * Gy[it];
    				}
    
    				half edge = lerp(0, abs(edgeX) + abs(edgeY), _EdgeWidth);
    
    				return edge;
    			}
    
    			fixed4 frag (v2f i) : SV_Target
    			{
           
    				half edge = Sobel(i);
    
    				fixed4 withEdgeColor = lerp(tex2D(_MainTex, i.uv[4]), _EdgeColor, edge);
    				fixed4 onlyEdgeColor = lerp(_BackgroundColor, _EdgeColor, edge);
    
    				half4 col = lerp(withEdgeColor, onlyEdgeColor, _EdgeOnly);
    
    				return col;
    			}
    			ENDCG
    		}
    	}
    }
    

外发光

Bloom

  • 后处理非常常用的Bloom效果,也是最为常用的外发光渲染方案。
  • 基本思路为,提取图像中的亮部,渲染为RT,再对这张RT进行模糊,最后再把模糊后的亮部RT叠加回原图,就得到了发光物体外一层光晕的效果。
  • HDR可以让颜色亮度高于1,与Bloom配合可以让暗部不会也随着Bloom效果一起发光,仅仅让亮部有Bloom效果
  • 实现代码有很多,之前的一篇写后处理的博客也有写过,就不贴代码了
  • 效果图
    【UnityShader】常用效果内外发光、 描边_第4张图片

沿法线外扩背面方法实现

  • 在上文的沿法线外扩背面实现的描边方法基础上,给背面的alpha像内发光一样,乘以视角与法线点积。不过要稍作改动,让背面从中心向外逐渐透明,模拟光晕的效果。

    float ndotv = max(pow(-dot(normal, viewDir),_OutlineFeather), 0.001);
    fixed4 col = _OutlineColor;
    col.a *= ndotv;
    

  • 注意:

    • 因为是背面的法线,所以点积的范围在[-1,0],要乘以-1让它在0到1之间。
    • 内发光是从中心到边缘逐渐变大,而此处是需要让alpha从中心到边缘逐渐变小,所以之前是1-ndotv,现在直接使用ndotv计算
  • 全部代码如下

Shader "Custom/Effect/Shine_In"
{
     
    Properties
    {
     
        _Color ("Main Color", Color) = (1,1,1,1)
		[HDR]_ShineColor ("Shine Color", Color) = (1,1,1,1)
		_ShineWidth ("Shine Width", Range(0, 2)) = 1
		_ShineFeather ("Shine Feather", Range(0, 10)) = 0
    }
    SubShader
    {
     
        Tags {
      "Queue"="Transparent" }

        LOD 200

        Pass
        {
     
			Cull Front
			Blend SrcAlpha OneMinusSrcAlpha

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
     
                float4 vertex : POSITION;
				float3 normal : NORMAL;
            };

            struct v2f
            {
     
                float4 pos : SV_POSITION;
				float3 worldPos : TEXCOORD0;
				float3 normal : TEXCOORD1;
            };

			half4 _ShineColor;
			float _ShineWidth;
			float _ShineFeather;

            v2f vert (appdata v)
            {
     
                v2f o;
				float4 viewPos = float4(UnityObjectToViewPos(v.vertex), 1.0);
				float3 viewNormal = mul(UNITY_MATRIX_IT_MV, v.normal);

				viewNormal.z = -0.5;
				float3 normal = normalize(viewNormal);
				viewPos += float4(normal, 1.0) * _ShineWidth;

				o.pos = mul(UNITY_MATRIX_P, viewPos);
				o.worldPos = mul(unity_ObjectToWorld, v.vertex);
				o.normal = UnityObjectToWorldNormal(v.normal);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
     
				float3 normal = normalize(i.normal);
				float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

				float ndotv = max(pow(-dot(normal, viewDir),_ShineFeather), 0.001);
                fixed4 col = _ShineColor;
				col.a *= ndotv;

                return col;
            }
            ENDCG
        }

		Pass
        {
     
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata
            {
     
                float4 vertex : POSITION;
            };

            struct v2f
            {
     
                float4 pos : SV_POSITION;
            };

            half4 _Color;

            v2f vert (appdata v)
            {
     
                v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);

                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
     
                fixed4 col = _Color;

                return col;
            }
            ENDCG
        }
    }
}

你可能感兴趣的:(Shader,shader,unity,图形学,游戏开发,边缘检测)