三言两语说shader(五)轮廓描边

这也是个常见的shader,可以实现类似动漫《枪之国度》的那种轮廓描边卡通画风。

在我手头的项目里用来给怪物加“霸体”效果,所谓霸体意思是怪物此时无法被击倒。

用途诸如此类。


下面放效果图:

三言两语说shader(五)轮廓描边_第1张图片


原谅我在Asset Store里找了半天最后找到这么个丑陋的模型,不过刚好它不是很精细,很好的暴露出了缺点,模型表面法向量突变(也就是不光滑)的地方描边会断掉。这个shader用在cube上时这个问题会更清晰的突显出来。

下面这篇文章更全面地介绍了描边实现的几种不同方式:

【风宇冲】Unity3D教程宝典之Shader篇:第二十五讲描边及外发光

我这里只讲“法线外拓”这一种。


先贴代码:

Shader "MyShader/Outline" 
{
	Properties 
	{
		_MainTex ("Base (RGB)", 2D) = "white" { }
		_OutlineColor ("Outline Color", Color) = (0,0,0,1)
		_OutlineWidth ("Outline width", Range (0.0, 0.1)) = .005
	}

	SubShader 
	{		
		Pass 
		{
			ZWrite Off	//注一

			CGPROGRAM
			#include "UnityCG.cginc"
			#pragma vertex vert
			#pragma fragment frag

			struct appdata {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : POSITION;
			};
			
			uniform float _OutlineWidth;
			uniform float4 _OutlineColor;
			
			v2f vert(appdata v) {
				v2f o;			
				v.vertex.xyz += v.normal * _OutlineWidth;    //注二	
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				return o;
			}
			
			half4 frag(v2f i) : COLOR {
				return _OutlineColor;
			}
			
			ENDCG
		}
		
		Pass    //注三 
		{
			CGPROGRAM
			#include "UnityCG.cginc"
            #pragma vertex vert_img	
            #pragma fragment frag			

            sampler2D _MainTex;
 
            float4 frag (v2f_img i) : COLOR {
                    float4 col = tex2D(_MainTex, i.uv);	
                    return col;
            }
            ENDCG
		}
	}
	Fallback "Diffuse"
}


基本原理:

画两遍,叠加得到最终结果。

第一遍,先把整个模型的顶点位置向法线方向移动一定值,用轮廓色涂满。

第二遍,以正常方式画原来的模型。


注一:

相对前篇里的黑屏shader,这个ZWrite Off算是个新概念。属于ShaderLab Pass里的Render State Setup标志语句。

貌似由此shader的控制范畴不再局限于我之前说的顶点和片段着色步骤了,此时已然延伸到对遮挡剔除环节的控制。

详细的说明官方文档里都有,这里ZWirte Off的意思是忽视深度信息,我猜测忽视的结果就是深度信息实际变成最底层最深了。

不写这句的结果,就是第一遍渲染的黑胖子会把第二遍渲染的正常人物整个包在里面看不见。


注二:

这一句是关键语句,把顶点位置外拓_OutlineWidth。

天下shader一大抄。在网上不难找到各种outline shader的版本,我贴出的这个版本是经过了简化修改的。

原参考版本这里是这样计算的:

v2f vert(appdata v) 
{
	v2f o;
	o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
	float3 norm   = mul ((float3x3)UNITY_MATRIX_IT_MV, v.normal);
	float2 offset = TransformViewToProjection(norm.xy);
	o.pos.xy += offset * o.pos.z * _Outline;
	o.color = _OutlineColor;
	return o;
}
我之前说看了半天矩阵变换看不明白就是指这里,把轮廓的宽度与z距离相乘作适当调整倒不难理解。

但先乘MV的逆矩阵,然后单独作一个P变换有什么玄机,就完全想不出来了。

我感觉我参考Unity Surface Shader Examples里的“膨胖”效果写的式子明显更简单明了。

但是很权威的wiki里抄出来的也是这么个代码,所以不敢妄言其中没有道理,假如有高人知道其中奥妙,请不吝赐教。

注三:

所谓画两遍,就是SubShader里有两个Pass。

我思考了一下两个Pass是并列关系,还是有执行先后顺序的问题,然后做了个试验,把第二个Pass移到第一个前面,发现整个人变黑了。这说明是有先后关系的。

原参考代码的第二遍Pass是这样的:

Pass 
{
	Name "BASE"
	ZWrite On
	ZTest LEqual
	Blend SrcAlpha OneMinusSrcAlpha
	Material {
		Diffuse [_Color]
		Ambient [_Color]
	}
	Lighting On
	SetTexture [_MainTex] {
		ConstantColor [_Color]
		Combine texture * constant
	}
	SetTexture [_MainTex] {
		Combine previous * primary DOUBLE
	}
}
这里头用了很多个Render State Setup标识语句就把一个渲染流程搞定了。

但是我看了下官方文档,已经遗弃这种方式,所以我也懒得去细细理解了。自己copy了前一个黑屏效果的代码放在这第二个Pass里,可能过于简化连光照都没有了,但不影响主题。


你可能感兴趣的:(shader)