`
yanfaguanli
  • 浏览: 658114 次
文章分类
社区版块
存档分类
最新评论

通过卡通渲染描边shader来学习Unity的Shader写法

 
阅读更多

Unity自带了很多shader,其中就包含卡通渲染和描边的shader。但是我在实际开发游戏的过程中还是遇到了这些shader无法解决的问题。

于是,我们需要理解如何写Unity的shader,并且按照我们自己的需求编写一个新的shader。

大多数情况下,我们遇到的实际问题要比一个酷炫的demo复杂和恶心。就像Daikon Forge GUI插件非常完善的Atlas图集功能却因为我们的GUI图片元素过多而变得非常鸡肋,甚至不得不做修改。 或者明明很正常的Dynamic Font功能却因为我们要渲染的文字繁而多,变得bug频频。

我们因为游戏风格和玩法的特殊需求,所以我们需要这样一个shader:

1、卡通渲染(这个主要是使用卡通渲染掩盖本身模型和贴图的不给力)

2、支持透明贴图 (我们很多模型使用了透明贴图,当时主美的说法是使用透明贴图可以减少很多面数,但是现在看来,如果我为了支持透明贴图而禁用了剔除,说不定反而是得不偿失的做法)

3、不依赖灯光 (毕竟我们的游戏不是3D的MMO,而且即便是火炬之光似乎在人物渲染的时候也是不依赖灯光的,对于我们的游戏风格而言,如果因为灯光造成明显的明暗区分是会削弱表现的)

4、最好有灯光影响 (这个跟上一点不冲突,灯光可以在shader中计算,场景中无论有没有灯光,人物都会有一定的明暗区分,很多情况下这个是加分的)

5、能够正确处理好透明和剔除 (如果处理不正确,要么会使人物表现错乱,甚至无法分辨出哪条腿在前那条退在后,要么会因为背面剔除而造成一部分部件无法显示)

我先贴出修改后的shader(基础是Unity自带的Toon shader),shader还没有最终修改完,等修改完毕再做更新。

一、卡通渲染的shader

Shader "Toon/Basic" {
	Properties {
		_Color ("Main Color", Color) = (.5,.5,.5,1)
		_MainTex ("Base (RGB)", 2D) = "white" {}
		_ToonShade ("ToonShader Cubemap(RGB)", CUBE) = "" { Texgen CubeNormal }
		_Cutoff ("Alpha cutoff", Range(0,1)) = 0.9
	}

	SubShader {
		Tags { "RenderType"="Opaque" }
		Pass {
			Name "BASE"
			Cull Off
			
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#pragma fragmentoption ARB_precision_hint_fastest 

			#include "UnityCG.cginc"

			sampler2D _MainTex;
			samplerCUBE _ToonShade;
			float4 _MainTex_ST;
			float4 _Color;
			fixed _Cutoff;

			struct appdata {
				float4 vertex : POSITION;
				float2 texcoord : TEXCOORD0;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : POSITION;
				float2 texcoord : TEXCOORD0;
				float3 cubenormal : TEXCOORD1;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.pos = mul (UNITY_MATRIX_MVP, v.vertex);
				o.texcoord = TRANSFORM_TEX(v.texcoord, _MainTex);
				o.cubenormal = mul (UNITY_MATRIX_MV, float4(v.normal,0));
				return o;
			}

			float4 frag (v2f i) : COLOR
			{
				float4 col = _Color * tex2D(_MainTex, i.texcoord);
				float4 cube = texCUBE(_ToonShade, i.cubenormal);
				clip(col.a - _Cutoff);
				return float4(2.0f * cube.rgb * col.rgb, col.a);
			}
			ENDCG			
		}
	} 

//	SubShader {
//		Tags { "RenderType"="Opaque" }
//		Pass {
//			Name "BASE"
//			Cull Off
//			SetTexture [_MainTex] {
//				constantColor [_Color]
//				Combine texture * constant
//			} 
//			SetTexture [_ToonShade] {
//				combine texture * previous DOUBLE, previous
//			}
//		}
//	} 
	
	Fallback "VertexLit"
}
一些说明:

1、第一行指明了shader的名字,它有两个用处,首先可以在Material中通过这个名字来指定shader,代码中也可以使用Shader.Find来查找对应shader;其次在使用UsePass复用Pass的时候必须使用这个名字来指定shader

2、Unity的shader的基本结构包含了Properties和Subshader,Properties是shader的入口参数,所有参数都可以在Unity编辑器中设置和修改。它的类型只有固定几种。

Subshader可以有n个。Subshader中包含Tags和Pass。Tags指定了一些基础属性。这些属性都是Unity预先定义好的,需要根据文档设置。它可以控制shader的渲染次序等等。

Pass就是shader的基础渲染单元。每个Pass都可以指定一个Name以便复用代码。比如下面带描边的shader就使用了 UsePass "Toon/Basic/BASE"指定了复用卡通渲染的shader。Pass内还有一些指令可以控制灯光、剔除、深度测试等等。这个非常重要,同样需要仔细查阅文档。

Pass中CGPROGRAM和ENDCG括起来的部分就是常规意义上的shader,它包含顶点处理函数和像素处理函数,计算每个顶点和像素的处理。

#pragma vertex vert和#pragma fragment frag 这两行分别指定了顶点处理函数和像素处理函数为vert和frag。

我们在Properties里面指定的属性不能直接使用,那个是为编辑器准备的,我们必须在CG代码中声明才能正常使用,比如sampler2D _MainTex。

UnityCG.cginc中包含一些自定义的顶点结构和一些函数,可以省去我们一些基础的操作。

appdata是程序传入的数据信息,可以包含顶点坐标 纹理坐标 法线等等 v2f是我们自己定义的结构,这个概念上与glsl的shader基本一致。
3、后面注掉的一大段貌似是固定渲染管线的shader代码,也就是不支持vert和frag这样真正的shader代码的时候的处理函数。一般情况下用不到,删掉了事。

4、最后一行包含了一个Fallback,就是说如果你这个牛b的shader无法在这个显卡上面执行则指定替代的shader。

5、一些细节实现的说明。 这个shader通过一个cubemap实现了类似灯光的明暗效果(当然我们不需要一个实际的灯光来照亮物体) 我添加了一个clip的调用来cutoff掉透明部分实现透明贴图的效果。 Cull off这条指令指定了禁用剔除以避免部分穿帮。这个会有损效率,因为正常来说背面是不用渲染的,但是现在无论正面还是背面都需要进行渲染。


二、卡通渲染带描边的shader

Shader "Toon/Basic Outline" {
	Properties {
		_Color ("Main Color", Color) = (.5,.5,.5,1)
		_OutlineColor ("Outline Color", Color) = (0,0,0,1)
		_Outline ("Outline width", Range (.002, 0.03)) = .005
		_MainTex ("Base (RGB)", 2D) = "white" { }
		_ToonShade ("ToonShader Cubemap(RGB)", CUBE) = "" { Texgen CubeNormal }
		_Cutoff ("Alpha cutoff", Range(0,1)) = 0.9
	}
	
	CGINCLUDE
	#include "UnityCG.cginc"
	
	struct appdata {
		float4 vertex : POSITION;
		float3 normal : NORMAL;
	};

	struct v2f {
		float4 pos : POSITION;
		float4 color : COLOR;
	};
	
	uniform float _Outline;
	uniform float4 _OutlineColor;
	
	v2f vert(appdata v) {
		v2f o;
		o.pos = mul(UNITY_MATRIX_MVP, v.vertex);

		float3 normal = mul((float3x3)UNITY_MATRIX_IT_MV, v.normal);
		float2 offset = TransformViewToProjection(normal.xy);

		o.pos.xy += (offset * o.pos.z * _Outline) / mul(UNITY_MATRIX_MVP, v.vertex).z;
		return o;
	}
	ENDCG

	SubShader {
		Tags { "RenderType"="Opaque" }
		UsePass "Toon/Basic/BASE"
		Pass {
			Name "OUTLINE"
			Tags { "LightMode" = "Always" }
			Cull Front
			
			ZWrite On
			ColorMask RGB
			Blend SrcAlpha OneMinusSrcAlpha

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			half4 frag(v2f i) :COLOR { return _OutlineColor; }
			ENDCG
		}
	}
//
//	SubShader {
//		Tags { "RenderType"="Opaque" }
//		UsePass "Toon/Basic/BASE"
//		Pass {
//			Name "OUTLINE"
//			Tags { "LightMode" = "Always" }
//			Cull Front
//			//Cull off
//			ZWrite On
//			ColorMask RGB
//			Blend SrcAlpha OneMinusSrcAlpha
//
//			CGPROGRAM
//			#pragma vertex vert
//			#pragma exclude_renderers shaderonly
//			ENDCG
//			SetTexture [_MainTex] { combine primary }
//		}
//	}
//	
	Fallback "Toon/Basic"
}

说明:

1、描边效果并不是很理想。 描边时要注意考虑摄像机远近,如果不注意的话,摄像机拉远后会看见一大坨黑影

2、待续

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics