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、待续
通过卡通渲染描边shader来学习Unity的Shader写法,布布扣,bubuko.com
通过卡通渲染描边shader来学习Unity的Shader写法
原文:http://blog.csdn.net/langresser_king/article/details/22696387