http://blog.csdn.net/candycat1992/article/details/40212735
三个月以前,在一篇讲卡通风格的Shader的最后,我们说到在Surface Shader中实现描边效果的弊端,也就是只对表面平缓的模型有效。这是因为我们是依赖法线和视角的点乘结果来进行描边判断的,因此,对于那些平整的表面,它们的法线通常是一个常量或者会发生突变(例如立方体的每个面),这样就会导致最后的效果并非如我们所愿。如下图所示:
因此,我们有一个更好的方法来实现描边效果,也就是通过两个pass进行渲染——首先渲染对象的背面,用黑色略微向外扩展一点,就是我们的描边效果;然后正常渲染正面即可。而我们应该知道,surface shader是不可以使用pass的。
如果我们想要使用上述方法实现描边,我们就需要写另一种shader——fragment shader。和surface shader相比,这种shader需要我们编写更多的代码,处理更多的事情,但也可以让我们更加了解shader是如何工作的。而之前的一篇文章也分析过,其实surface shader的背后也是生成了对应的vertex&fragment shader。
这篇文章主要参考了Unity Gems里的一篇文章,但正如文章评论里所说,有些技术比如求attenuation稳重方法已经“过时”,因此本文会对这类问题以及一些作者没有说清的问题给予说明。在查资料的时候,发现由于Unity背后做了太多事,定义了很多变量、函数和宏,而又没有给出详尽的使用说明,写起来实在太头大了。。。同样,本篇内容仅供参考。
Vertex & Fragment Shaders
Vertex & Fragment Shaders的工作流程如下图所示(简略版,来自Unity Gems):
所以,看起来也没那么难啦~我们只需要编写两个函数就可以喽~
我们来分析下它的流程。首先,vertex program收到系统传递给它的模型数据,然后把这些处理成我们后续需要的数据(但至少要包含这些顶点的位置信息)进行输出。其他的输出数据比如有,纹理的UV坐标以及其他需要传递给fragment program的数据。然后,系统对vertex program输出的顶点数据进行插值,并将插值结果传递给fragment program。最后,fragment program根据这些插值结果计算最后屏幕上的像素颜色。
在本篇文章,我们首先会学习编写一个简单的diffuse & diffuse bumped shader。然后再来具体看如何编写一个具有多个passes的shader。
Diffuse, Vertex Lit Fragment Shader
开始的开始,我们首先需要在SubShader中使用Pass {}关键字定义一个pass。一个Pass可以为该阶段定义一系列的tags。例如,我们可以剔除(Cull)背面或者正面,控制是否写入Z buffer等。我们的diffuser shader将会剔除背面。具体可见官网。
下面是我们的Pass定义:
- Pass {
- Tags { "LightMode" = "Vertex" }
- Cull Back
- Lighting OnCGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
- #pragma multi_compile_fwdbase
- #include "UnityCG.cginc"
- // More code here
- ENDCG
- }
在上面的代码里,我们定义了一个pass,设定LightMode为Vertex,告诉它打开光源并且剔除背面。然后,我们定义了CG程序的开头部分,指定了vertex和fragment programs的名字。最后,我们包含了Unity定义的一个文件,以便在后面的CG程序中可以使用某些函数和变量。
LightMode是个非常重要的选项,因为它将决定该pass中光源的各变量的值。如果一个pass没有指定任何LightMode tag,那么我们就会得到上一个对象残留下来的光照值,这并不是我们想要的。其他各个LightMode的具体含义可以参见官网(很重要,一定要去看,特别是对于每个Pass的细节解释,一定要点进去看!!!),这里做一个简单的解释。
- LightMode=Vertex:会设置4个光源,并按亮度从明到暗进行排序,它们的值会存储在unity_LightColor[n], unity_LightPosition[n], unity_LightAtten[n]这些数组中。因此,[0]总会得到最亮的光源。
- LightMode=ForwardBase: _LightColor0将会是主要的directional light的颜色。
- LightMode=ForwardAdd:和上面一样, _LightColor0将是该逐像素光源的颜色。
Vertex Lit是什么
在我们写shader的时候有很多选择——我们可以定义多个passes,其中每一个pass处理一个光源,这样来处理所有的光源;或者我们选择逐顶点处理所有的光源(在一个pass里处理掉),然后再对它们进行插值。很明显,后面这种方式会快很多,因为它仅仅需要一个pass就可以了,而前一个方式需要更多的passes。
如果我们写了一个Vertex Lit shader,那么我们就会按照第二种方式那样,一次考虑所有的光源对顶点的影响。如果我们写了一个多passes的shader,那么它就会被多次调用,每次针对一个光源,考虑该光源对模型的影响。
对于Vertex Lit,Unity已经为我们编写了一些辅助函数,我们会在后面看到。
The Vertex Program
下面,我们正式开始编写代码。首先,我们需要定义vertex program。而它需要得到模型的相关信息作为输入,因此,我们定义下面的结构:
- struct a2v {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- float4 texcoord : TEXCOORD0;
- };
这个结构定义依赖某些语法,即那些“:XXX”样子的值。我们的变量叫什么并不重要,但这些“:XXX”语法则说明系统将使用哪些值去填充它们。这里,我们通过上述代码可以得到了model space中的顶点位置、法线方向以及纹理坐标。
- 在model space中,坐标是相对于网格的原点(0,0,0)定义的。我们的vertex function需要把这些坐标转换到projection space中,即相对于摄像机的、真正被渲染的地方。
- 在tangent space中,坐标是相对于模型的正面定义的——在处理法线纹理时我们使用这个space,这在后面会具体讲到。
- 在world space中,坐标是相对于世界的原点(0,0,0)定义的。
- 在projection space中,坐标是相对于摄像机定义的,因此在这个space中,摄像机的位置就是(0,0,0)。
那么,在定义了vertex program的输入后,我们还需要定义它的输出。之前我们说过,vertex program的输出将会被插值用于生成像素,而这些插值后的值就是fragment program的输入。
- struct v2f {
- float4 pos : POSITION;
- float2 uv : TEXCOORD0;
- float3 color : TEXCOORD1;
- };
上面就是我们的输出。在这里,之前所说的语义就没有那么重要了——只有一个是必须的,即用POSITION标识的变量,这是把顶点坐标转换到projection space后的位置。我们输出的所有值(并且没有uniform限定词)都将在fragment program之前被插值。
注意:但对于DX11和Xbox360来说,必须要有语义说明,否则会报错。即需要为变量指定TEXCOORD1等位置。
下面是真正的vertex function,它把输入a2v转换成输出v2f(也是fragment function的输入)。
- v2f vert(a2v v) {
- v2f o;
- o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
- o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
- o.color = ShadeVertexLights(v.vertex, v.normal);
- return o;
- }
第一行,我们定义了输出v2f的一个实例。然后把顶点的位置和Unity提前定义的一个矩阵UNITY_MATRIX_MVP(在UnityShaderVariables.cginc里定义)相乘,从而把顶点位置从model space转换到projection space。我们使用了矩阵乘法操作mul来执行这个步骤。
第二行,我们为给定的纹理计算其uv坐标,即根据mesh上的uv坐标来计算真正的纹理上对应的位置。我们使用了Unity.CG.cginc中的宏TRANSFORM_TEX来实现。
最后,我们计算得到顶点的初始颜色——即光源对该顶点的影响。在我们的第一个shader中,我们使用一个名为ShadeVertexLights的函数,它的输入为模型的顶点和法线。这是一个内置的函数,它将考虑4个距离最近(若距离相等则按光源类型排序)的光源以及一个环境光(在Edit->Render Settings->Ambient Light里设置)。它的实现可以在UnityCG.cginc里找到。其他辅助函数可以详见官网。
The Fragment Shader
根据上述过程,系统会在每个顶点上调用vertex program,并将其输出在同一个几何图元上进行插值。下面,我们根据这些插值后的值来得到对应的像素值。下面是真正的fragment program:
- float4 frag(v2f i) : COLOR {
- float4 c = tex2D(_MainTex, i.uv);
- c.rgb = c.rgb * i.color * 2;
- return c;
- }
上述代码使用了surface shader中也很常见的纹理采样操作,来得到对应的纹理像素值。然后,将该纹理颜色和插值后的vertex function输出的顶点光颜色进行相乘,并把结果乘以2(否则颜色会太暗。)。最后,返回得到的像素值。
完整代码
最后,完整的Vertex Lit Diffuse代码如下:
- Shader "Custom/VertexLit" {
- Properties {
- _MainTex ("Base (RGB)", 2D) = "white" {}
- }
- SubShader {
- Tags { "RenderType"="Opaque" }
- LOD 300
- Pass {
- Tags { "LightMode" = "Vertex" }
- Cull Back
- Lighting On
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
- #include "UnityCG.cginc"
- sampler _MainTex;
- float4 _MainTex_ST;
- struct a2v {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- float4 texcoord : TEXCOORD0;
- };
- struct v2f {
- float4 pos : POSITION;
- float2 uv : TEXCOORD0;
- float3 color : TEXCOORD1;
- };
- v2f vert(a2v v) {
- v2f o;o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
- o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
- o.color = ShadeVertexLights(v.vertex, v.normal);
- return o;
- }
- float4 frag(v2f i) : COLOR {
- float4 c = tex2D(_MainTex, i.uv);
- c.rgb = c.rgb * i.color * 2;
- return c;
- }
- ENDCG
- }
- }
- FallBack "Diffuse"
- }
这样,我们就完成了第一个vertex & fragment shader。上述效果如果用surface shader可能只要几句话,但你渐渐会发现,虽然使用vertex & fragment shader会增加更多的代码量,但它能做的真是太多了!
上述shader的效果如下(啦啦啦,又是小苹果+呆萌小怪兽的组合~~~):
Diffuse Normal Map Shader
下面我们要向shader添加一个非常常见的法线纹理(Normal Texture)。
Normal Maps
如果你在Unity里使用过法线纹理的话,你应该知道在使用之前,你需要先把该纹理的类型设置成Normal,对吧?那么,到底为什么要这样呢?法线纹理跟其他纹理有什么不一样呢?
法线纹理具有以下性质
- 它存储了模型表面的法线方向。有基于model space(肉眼看起来颜色比较丰富,有红色蓝色等)和基于tangent space(通常都是蓝色的)的两种法线纹理,而Unity常见的是后面一种法线纹理。
- 由于法线向量中每一维的范围在(-1,1),因此我们需要把它重新映射到(0,255)。具体做法是把原值除以2再偏移0.5,最后乘以255。
- 在存储的时候是压缩存储。因为法线纹理都是被正则化的,即是单位向量,模为1,所以实际上只需要存储该向量的两个维度就可以了,第三维可以用前两个推导出来。
- 由于上一点,每一个维度占用16 bits,即每个rgba包含了两个维度的值。
在Unity里转换到tangent space是比较容易的。下面,我们不会使用逐顶点的光照处理函数ShadeVertexLights,而是逐像素的处理光照。
照亮我们的模型
但是,光源在哪里呢?
Unity为我们提供了那些对模型有影响的光源(按重要度排序,例如距离远近、光照类型等)的位置、颜色和衰减等信息。
Unity使用了三个数据来定义顶点光源:unity_LightPosition,unity_LightAtten和unity_LightColor。例如[0]表示最重要的光源。
当我们编写一个multi-pass的光照模型(正如我们下面写的那样)时,我们只需要一次处理一个单独的光源,这种情况下,Unity同样定义了一个名为_WorldSpaceLightPos0的值,来帮助我们得到它的位置,并且还提供了一个非常有用的函数ObjSpaceLightDir,它可以计算得到该光源的方向。而为了得到该光源的颜色,我们可以在程序中包含“Lighting.cginc”文件,然后使用_LightColor0进行访问。
Forward Lighting(而非Vertex Lit)
在第一个shader里我们使用了vertex lights,而现在,我们来看下怎么为光源定义多个passes。那么,开始吧!
首先,我们需要更改Tags中的LightMode,让其值为ForwardBase,来让Unity我们设置光源数据。
- Pass {
- Tags { "LightMode" = "ForwardBase" }
然后,我们还需要添加#pragma指令:
- #pragma multi_compile_fwdbase
这都是为了能让Unity各种内置数据、宏定义等可以正常工作。真的是很头大啊,至今官方也没有给出详细的参考资料。。。(Rant!!!)
然后,为了使用法线纹理我们需要定义两个变量,一个是名为_XXX的sampler2D变量,一个是名为_XXX_ST的float4变量(当然你还需要在Properties中定义一个名为_XXX的新属性)。
现在我们需要为vertex program定义新的输入:
- struct a2v {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- float4 texcoord : TEXCOORD0;
- float4 tangent : TANGENT;
- };
这里我们添加了一个新的变量,其语义是:TANGENT。我们会在把光源方向转换到tangent space中时需要这个变量。
Tangent Space转换
为了把向量从object space转换到tangent space,我们需要为顶点定义另外两个向量。通常对一个顶点来说,我们知道它的法线normal,而其中一个向量tangent是和normal正交的,另一个向量binormal则是normal和tangent的叉乘结果。有了这三个向量,我们就可以定义一个矩阵来执行到tangent space的转换。
幸运地是,UnityCG.cginc里定义了一个名为TANGENT_SPACE_ROTATION的宏,它提供了一个名为rotation的矩阵来把object space下的坐标转换到tangent space中。
Vertex到Fragment Programs的输出
在知道转换的方法后,我们需要在vertex function里计算tangent space下的光源方向,然后对其进行插值后传递给fragment function。因此,我们需要在vertex function的输出里添加新的变量——光源方向。
- struct v2f {
- float4 pos : POSITION;
- float2 uv : TEXCOORD0;
- float2 uv2 : TEXCOORD1;
- float3 lightDirection : TEXCOORD2;
- LIGHTING_COORDS(3,4)
- };
lightDirection将会存储插值后的光源方向向量。uv2将会存储法线纹理的纹理坐标。最后的LIGHTING_COORDS(3,4)是在AutoLight.cginc里定义的宏,它负责创建光源坐标,用于某些内置的光照计算。在下面计算光源的attenuation时,我们会需要这些值。
The Vertex Program
- v2f vert(a2v v) {
- v2f o;
- TANGENT_SPACE_ROTATION;
- o.lightDirection = mul(rotation, ObjSpaceLightDir(v.vertex));
- o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
- o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
- o.uv2 = TRANSFORM_TEX(v.texcoord, _MainTex);
- TRANSFER_VERTEX_TO_FRAGMENT(o);
- return o;
- }
在vertex program里,我们使用了宏TANGENT_SPACE_ROTATION(在UnityCG.cginc里定义)来创建一个名为rotation的矩阵,并使用它把object space转换到tangent space中。
然后,我们使用内置函数ObjSpaceLightDir(v.vertex)计算了在object space中光源(这时指的就是最重要的那个光源)的方向。随后,我们再把结果和新的rotation矩阵相乘,从而把方向从object space又转换到了tangent space。
下面几行,我们计算得到顶点在projection space中的位置以及纹理的uv坐标。
最后,我们使用了名为的TRANSFER_VERTEX_TO_FRAGMENT宏,它同样在AutoLight.cginc里定义,和上面v2f中的宏LIGHTING_COORDS协同工作,它会根据该pass处理的光源类型(spot?point?or directional?)来计算光源坐标的具体值,以及进行和shadow相关的计算等。
Directional和Point Lights
Unity把光源的位置存储在float4类型的_WorldSpaceLightPos0里,即_WorldSpaceLightPos0包含了4个元素。如果这个光源是directional,那么xyz就是这个光源的方向,而w(即最后一个元素)则是0;如果这时一个point light,那么xyz将表示光源的位置,而w则是1。那么,这些有什么影响呢?
这其实方便了ObjSpaceLightDir函数的计算过程。它首先将顶点的位置乘以光源位置的w元素,然后再用光源位置减去顶点的位置,来得到光源方向。因此,如果是一个directional light,我们相乘后就会得到0,即返回光源的xyz值(实际上就是光源的方向);如果是一个point light,我们就会得到顶点到光源的一个方向向量。
The Fragment Function
- float4 frag(v2f i) : COLOR {
- float4 c = tex2D(_MainTex, i.uv);
- float3 n = UnpackNormal(tex2D(_BumpTex, i.uv2));
- float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
- float atten = LIGHT_ATTENUATION(i);
- // Angle to the light
- float diff = saturate(dot(n, normalize(i.lightDirection)));
- lightColor += _LightColor0.rgb * (diff * atten);
- c.rgb = lightColor * c.rgb * 2;
- return c;
- }
在fragment function里,我们首先从法线纹理里解压出法线。然后,我们使用Unity设置的环境光作为初始颜色值。随后,我们计算了衰减值,即光源距离的远近。这里,我们同样使用了AutoLight.cginc里的宏,即LIGHT_ATTENUATION,它同样会判断该pass处理的光源类型,然后得到光源的衰减率。
然后,我们把法线和光源方向进行点乘得到漫反射值,再和光源颜色以及衰减值结合起来,叠加到像素值上。为了得到光源的颜色,我们使用了_LightColor0——这需要我们在shader中包含“Lighting.cginc”文件。或者,我们也可以在shader中定义一个名为_LightColor0的变量,Unity会自行填充它的值。
- uniform float4 _LightColor0;
完整代码
最后完整的代码如下:
- Shader "Custom/DiffuseNormal" {
- Properties {
- _MainTex ("Base (RGB)", 2D) = "white" {}
- _BumpTex ("Bump Texture", 2D) = "white" {}
- }
- SubShader {
- Tags { "RenderType"="Opaque" }
- LOD 300
- Pass {
- Tags { "LightMode" = "ForwardBase" }
- Cull Back
- Lighting On
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
- #pragma multi_compile_fwdbase
- #include "UnityCG.cginc"
- #include "Lighting.cginc"
- #include "AutoLight.cginc"
- sampler _MainTex;
- sampler _BumpTex;
- float4 _MainTex_ST;
- float4 _BumpTex_ST;
- struct a2v {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- float4 texcoord : TEXCOORD0;
- float4 tangent : TANGENT;
- };
- struct v2f {
- float4 pos : POSITION;
- float2 uv : TEXCOORD0;
- float2 uv2 : TEXCOORD1;
- float3 lightDirection : TEXCOORD2;
- LIGHTING_COORDS(3,4)
- };
- v2f vert(a2v v) {
- v2f o;
- TANGENT_SPACE_ROTATION;
- o.lightDirection = mul(rotation, ObjSpaceLightDir(v.vertex));
- o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
- o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
- o.uv2 = TRANSFORM_TEX(v.texcoord, _MainTex);
- TRANSFER_VERTEX_TO_FRAGMENT(o);
- return o;
- }
- float4 frag(v2f i) : COLOR {
- float4 c = tex2D(_MainTex, i.uv);
- float3 n = UnpackNormal(tex2D(_BumpTex, i.uv2));
- float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
- float atten = LIGHT_ATTENUATION(i);
- // Angle to the light
- float diff = saturate(dot(n, normalize(i.lightDirection)));
- lightColor += _LightColor0.rgb * (diff * atten);
- c.rgb = lightColor * c.rgb * 2;
- return c;
- }
- ENDCG
- }
- }
- FallBack "Diffuse"
- }
Shader效果如下:
在Forward Mode中处理Multiple Lights
通过上面的学习,我们已经学会了如何处理一个光源,但仅仅是一个。要处理多光源,我们就需要编写另一个pass,并且使用新的tags来告诉Unity我们想要逐个处理光源。
这基本上只需要两步:
- 一个pass处理第一个光源,就像我们上面做的那样
- 然后定义更多的pass,来处理后续的光源,并把结果添加(add on)到前面的结果上
- Tags { "LightMode" = "ForwardAdd" }
- #pragma multi_compile_fwdadd
然后添加一个新的命令来告诉Unity怎样混合前后两个pass的值:
- Blend One One
然后,我们移除掉第二个pass对UNITY_LIGHTMODEL_AMBIENT的处理,因为我们已经在第一个pass中处理过这个值了。我们最后的代码如下:
- Shader "Custom/DiffuseNormal" {
- Properties {
- _MainTex ("Base (RGB)", 2D) = "white" {}
- _BumpTex ("Bump Texture", 2D) = "white" {}
- }
- SubShader {
- Tags { "RenderType"="Opaque" }
- LOD 300
- Pass {
- Tags { "LightMode" = "ForwardBase" }
- Cull Back
- Lighting On
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
- #pragma multi_compile_fwdbase
- #include "UnityCG.cginc"
- #include "Lighting.cginc"
- #include "AutoLight.cginc"
- sampler _MainTex;
- sampler _BumpTex;
- float4 _MainTex_ST;
- float4 _BumpTex_ST;
- struct a2v {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- float4 texcoord : TEXCOORD0;
- float4 tangent : TANGENT;
- };
- struct v2f {
- float4 pos : POSITION;
- float2 uv : TEXCOORD0;
- float2 uv2 : TEXCOORD1;
- float3 lightDirection : TEXCOORD2;
- LIGHTING_COORDS(3,4)
- };
- v2f vert(a2v v) {
- v2f o;
- TANGENT_SPACE_ROTATION;
- o.lightDirection = mul(rotation, ObjSpaceLightDir(v.vertex));
- o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
- o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
- o.uv2 = TRANSFORM_TEX(v.texcoord, _MainTex);
- TRANSFER_VERTEX_TO_FRAGMENT(o);
- return o;
- }
- float4 frag(v2f i) : COLOR {
- float4 c = tex2D(_MainTex, i.uv);
- float3 n = UnpackNormal(tex2D(_BumpTex, i.uv2));
- float3 lightColor = UNITY_LIGHTMODEL_AMBIENT.xyz;
- float atten = LIGHT_ATTENUATION(i);
- // Angle to the light
- float diff = saturate(dot(n, normalize(i.lightDirection)));
- lightColor += _LightColor0.rgb * (diff * atten);
- c.rgb = lightColor * c.rgb * 2;
- return c;
- }
- ENDCG
- }
- Pass {
- Tags { "LightMode" = "ForwardAdd" }
- Cull Back
- Lighting On
- Blend One One
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
- #pragma multi_compile_fwdadd
- #include "UnityCG.cginc"
- #include "Lighting.cginc"
- #include "AutoLight.cginc"
- sampler _MainTex;
- sampler _BumpTex;
- float4 _MainTex_ST;
- float4 _BumpTex_ST;
- struct a2v {
- float4 vertex : POSITION;
- float3 normal : NORMAL;
- float4 texcoord : TEXCOORD0;
- float4 tangent : TANGENT;
- };
- struct v2f {
- float4 pos : POSITION;
- float2 uv : TEXCOORD0;
- float2 uv2 : TEXCOORD1;
- float3 lightDirection : TEXCOORD2;
- LIGHTING_COORDS(3,4)
- };
- v2f vert(a2v v) {
- v2f o;
- TANGENT_SPACE_ROTATION;
- o.lightDirection = mul(rotation, ObjSpaceLightDir(v.vertex));
- o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
- o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
- o.uv2 = TRANSFORM_TEX(v.texcoord, _MainTex);
- TRANSFER_VERTEX_TO_FRAGMENT(o);
- return o;
- }
- float4 frag(v2f i) : COLOR {
- float4 c = tex2D(_MainTex, i.uv);
- float3 n = UnpackNormal(tex2D(_BumpTex, i.uv2));
- float3 lightColor = float3(0);
- float lengthSq = dot(i.lightDirection, i.lightDirection);
- float atten = LIGHT_ATTENUATION(i);
- // Angle to the light
- float diff = saturate(dot(n, normalize(i.lightDirection)));
- lightColor += _LightColor0.rgb * (diff * atten);
- c.rgb = lightColor * c.rgb * 2;
- return c;
- }
- ENDCG
- }
- }
- FallBack "Diffuse"
- }
写在最后
本文里对处理光源attenuation的方法和Unity Gems里的方法不同,按原文里的做法在Unity 4.5(更早的版本不清楚)是无法得到正确的attenuation的,即把点光源拉进拉远不会对模型有任何影响,除非拉出了光源范围,这时会有一个不正常的明暗突变。为了找正确的方法真是麻烦啊。。。Unity关于shader的文档的确需要加强,而且在Unity里写Vertex & Fragment Shader绝对比想象中的难,有一条准则就是,如果它提供给里某些功能的函数(比如这里计算attenuation的方法,要4个步骤,#pragma multi_compile_fwdadd + LIGHTING_COORDS + TRANSFER_VERTEX_TO_FRAGMENT+LIGHT_ATTENUATION),那么千万不要自己尝试去写一个函数出来。。。某些内置的变量实在是不知道它们什么时候工作、怎么工作。。。