文章目录
- Unity的渲染路径
- 前向渲染路径
- 前向渲染路径原理
- Unity中的前向渲染
- BassPass
- Additional Pass
- 内置的光照变量和函数
- 延迟渲染路径
- 延迟渲染的原理
- Unity中的延迟渲染
- 可访问的内置变量和函数
- Unity的光源类型
- 光源类型有什么影响
- 平行光
- 点光源
- 聚光灯
- 在前向渲染中处理不同的光源类型
- Unity的光照衰减
- 用于光照衰减的纹理
- 使用数学公式计算衰减
- Unity的阴影
- 阴影是如何实现的
- 不透明物体的阴影
- 让物体接收阴影
- 统一管理光照衰减和阴影
- 透明度物体的阴影
- BumpedDiffuse
- BumpedSpecular
Unity的渲染路径
渲染路径(Rendering Path) 决定了光照是如何应用到Unity Shader中的,为此我们需要为每个Pass指定它使用的渲染路径。
Unity支持多种类型的渲染路径,主要有三种:前向渲染路径(Forward Rendering Path),延迟渲染路径(Deferred Rendering Path) 和顶点照明渲染路径(Vertex Lit Rendering Path)(5.0版本后被抛弃)
大多情况下,一个项目只是用一种渲染路径,但有时也希望可以使用多个渲染路径。可以在Editor -> Project Setting -> Graphics窗口可设置默认的渲染路径,每个Camera可覆盖默认的渲染路径。
如果当前显卡不支持所选渲染路径,则Unity将自动使用更低一级的渲染路径。
LightMode标签支持的渲染路径设置选项
标签名 | 描述 |
---|---|
Always | 不管使用那种渲染路径,该Pass总是会被渲染,但不会计算任何光照 |
ForwardBase | 用于前向渲染。该Pass会计算环境光,最重要的平行光,逐顶点/SH光源和Lightmaps |
ForwardAdd | 用于前向渲染。该Pass会计算额外的逐像素光源,每个Pass对应一个光源 |
Deferred | 用于延迟渲染。该Pass会渲染G缓冲(G-buffer) |
ShadowCaster | 把物体的深度信息渲染到阴影映射纹理(shadowmap)或一张深度纹理中 |
前向渲染路径
前向渲染路径原理
每进行一次完整的前向渲染,我们需要渲染该对象的渲染图元,并计算两个缓冲区的信息:颜色缓冲区和深度缓冲区。通过深度缓冲区来决定一个片元是否可见,可见则更新颜色缓冲区的颜色值。
//伪代码描述前向渲染过程
Pass{
for(each primitive in this model){//遍历每个模型中的图元
for(each fragment covered by this primitive){//遍历每个片元
if(failed in depth test){
//如果没有通过深度测试 说明该片元是不可见的
discard;
}else{
//如果该片元可见 就进行光照计算
float4 color = Shading(materialInfo, pos, normal, lightDir, viewDir);
//更新帧缓冲
writeFrameBuffer(fragment,color);
}
}
}
}
对于每个逐像素光源,都需要进行上面一次完整的流程。如果一个物体在多个逐像素光源的影响区域内,那么该物体就需要执行多个Pass,每个Pass计算一个逐像素光源的结果。然后在缓冲区中把这些光照结果混合起来得到最终的颜色值。如果有大量的逐像素光照,那么需要执行的Pass数目也会很大。因此渲染引擎通常会限制每个物体的逐像素光照的数目。
Unity中的前向渲染
一个Pass不仅仅可以用来计算逐像素光照,也可以用来计算逐顶点和其他光照。取决于光照计算所处的流水线阶段以及计算时使用的数学模型。
在Unity中,前向渲染路径有3中处理光照的方式:逐顶点处理、逐像素处理,球谐函数(Spherical Harmonics,HS)处理。而决定一个光源使用哪种处理模式取决于他的类型和渲染模式。
光源类型指的是该光源是平行光还是其他类型的光源
光源的渲染模式指的是该光源是否是重要的(Important)(设置之后会被当成逐像素光源来处理)
当我们渲染一个物体时,Unity会根据场景中各个光源的设置以及这些光源对物体的影响程度(比如远近、光源强度),对这些光源进行一个重要度排序。这其中一定数目的光源会按照逐像素的方式处理,然后最多有4个光源按逐顶点的方式处理,剩下的光源按SH方式处理。
- Unity的判断规则:
- 场景中最亮的平行光总是按逐像素处理的。
- 渲染模式被设置成Not Important的光源,会按逐顶点或者SH处理
- 渲染模式被设置成Important的光源,会按逐像素处理
- 如果根据以上规则得到的逐像素光源数量小于Quality Setting中的逐像素光源数量(Pixel Light Count),就会有更多的光源以逐像素的方式进行渲染。
链接:深入了解Unity的QualitySettings类
前向渲染有两种Pass:Base Pass和Additional Pass
BassPass
可实现光照纹理,环境光,自发光,阴影(平行光的阴影)。
光照计算方面,需要计算一个逐像素的平行光以及所有逐顶点和SH光源。
渲染设置:
Tags{ "LightMode" = "ForwardBase" }
#pragma multi_compile_fwdbase
Additional Pass
默认情况下不支持阴影,但可以通过使用#pragma multi_compile_fwdadd_fullshadows
编译指令来开启阴影(这会需要Unity在内部使用更多的Shader变种)
光照计算方面,需要对其他影响该物体的逐像素光源的每个光源执行一次Pass
渲染设置:
Tags{ "LightMode" = "ForwardAdd" }
Blend One One
#pragma multi_compile_fwdadd
在渲染设置中开启了混合模式(Blend One One
),因为希望每个Additional Pass都可以与上一次的光照结果在帧缓存中进行叠加,来得到最终有多个光照的渲染模式。如果不开启和设置,那么渲染结果会覆盖掉之前的渲染结果。
根据官方文档中的相关解释,我们可以知道这些(#pragma multi_compile_fwdbase
#pragma multi_compile_fwdadd
)编译指令会保证Unity可以为相应类型的Pass生成所有需要的Shader变种,这些变种会处理不同条件下的渲染逻辑。
环境光和自发光是在Base Pass中计算的,因为如果在AdditionPass中计算这两种光照,会造成叠加多次环境光和自发光。
对于前向渲染,一个UnityShader通常会定义一个Base Pass(也可定义多次)以及一个Additional Pass。一个Base Pass金辉执行一次(除非定义了多个),而一个Additional Pass会根据影响该物体的其他逐像素光源的数目被多次调用(每个逐像素光源会执行一次Additional Pass)
内置的光照变量和函数
前向渲染可以使用的内置光照变量
名称 | 类型 | 描述 |
---|---|---|
_LightColor() | float4 | 该Pass处理的逐像素光源的颜色 |
_WorldSpaceLightPos0 | float4 | _WorldSpaceLightPos0.xyz是该Pass处理的逐像素光源的位置。如果该光源是平行光,那么_WorldSpaceLightPos0.w是0,其他光源类型w值为1 |
_LightMatrix0 | float4x4 | 从世界空间到光源空间的变换矩阵。可以用于采样cookie和光强衰减(attenuation)纹理 |
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0 | float4 | 仅用于Base Pass。前4个非重要的点光源在世界空间中的位置 |
unity_4LightAtten0 | float4 | 仅用于Base Pass。存储了前4个非重要的点光源的衰减因子 |
unity_LightColor | half[4] | 仅用于Base Pass。存储了前4个非重要的点光源的颜色 |
向前渲染可以使用的内置光照函数
函数名 | 描述 |
---|---|
float3 WorldSpaceLightDir(float4 v) | 仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。内部实现使用了UnityWorldSpaceLightDir函数。没有被归一化 |
float3 UnityWorldSpaceLightDir(float4 v) | 仅可用于前向渲染中。输入一个世界空间中的顶点位置,返回世界空间中从该点到光源的光照方向。没有被归一化 |
float3 ObjSpaceLightDir(float4 v) | 仅可用于前向渲染中。输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向。没有被归一化 |
float3 Shade4PointLights(…) | 仅可用于前向渲染中。计算四个点光源的光照,参数是已经打包进矢量的光照数据,通常就是上表中的内置变量unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0, unity_LightColor和unity_4LightAtten0等。前向渲染通常会使用这个函数来计算逐顶点光照。 |
延迟渲染路径
前向渲染的问题是当场景中包含大量实施光源是,前向渲染的性能会急速下降。因为如果在一块区域放置了多个光源,这些光源的影响区域互相叠加会为该区域的每个物体执行多个Pass来计算不同光源对物体的光照,但实际上很多计算都是重复的。
延迟渲染除了前向渲染中使用的颜色缓冲和深度缓冲外,还会利用额外的缓冲区——G缓冲(Geometry buffer,G-buffer)。
G缓冲区中存储了我们所关心的表面(通常指的是离摄像机最近的表面)的其他信息,例如该表面的法线、位置、用于光照计算的材质属性等。
延迟渲染的原理
主要包含两个Pass。
第一个Pass不进行任何光照计算,只计算哪些片元可见。主要是通过深度缓冲技术来实现的,当发现一个片元是可见的,就把它相关信息存储到G缓冲区中。
//伪代码
Pass1{
//第一个Pass不进行真正的光照计算 仅把光照计算需要的信息存储到G缓冲区中
for(each primitive in this model){
for(each fragment covered by this primitive){
if(failed in depth test){
//如果没有通过深度测试,说明该片元是不可见的
discard;
}else{
//如果该片元可见 就把需要的信息存储到G缓冲区中
writeGBuffer(materialInfo, pos, normal);
}
}
}
}
第二个Pass利用G缓冲区中的各个片元信息,例如表面法线、视角方向、漫反射系数等,进行光照计算。\
//伪代码
Pass2{
//利用G缓冲中的信息进行真正的光照计算
for(each pixel in the screen){
if(the pixel is valid){
//如果该像素是有效的 读取它对应的G缓冲中的信息
readGBuffer(pixel, materialInfo, pos, normal);
//根据读取到的信息进行光照计算
float4 color = Shading(materialInfo, pos, normal, lightDir, viewDir);
//更新帧缓冲
writeFrameBuffer(pixel, color);
}
}
}
延迟渲染的效率不依赖于场景的复杂度,而是与屏幕大小有关。
Unity中的延迟渲染
对于延迟渲染来说,他最适合在场景中光源数目很多、使用向前渲染会造成瓶颈的情况下使用。并且延迟渲染路径中的每个光源都可以按逐像素的方式处理。
延迟渲染的缺点:不支持真正的抗锯齿功能(anti-aliasing)功能、不能处理半透明物体、对显卡有一定要求(必须支持MRT(Multiple Render Targets)、Shader Mode 3.0 及以上、深度渲染纹理以及双面的模板缓冲)。
默认的G缓冲区(不同Unity版本的渲染纹理储存内容会有所不同)包含了以下几个渲染纹理(Render Texture,RT)。
- RT0:格式为ARGB32,RGB通道用于存储漫反射颜色,A通道没有被使用
- RT1:格式为ARGB32,RGB通道用于存高光反射颜色,A通道用于存储高光反射的指数部分
- RT2:格式为ARGB2101010,RGB通道用于存储法线,A通道没有被使用
- RT3:格式为ARGB2101010(非 HDR)或 ARGBHalf (HDR) ,用于存储自发光+lightmap+反射探针(reflection probes)。
- 深度缓冲和模板缓冲
在第二个Pass中计算光照时,默认情况下仅可以使用Unity内置的Standard光照模型。
具体参考官方文档:延迟着色渲染路径
可访问的内置变量和函数
可以在UnityDeferredLibrary.cginc中找到声明
名称 | 类型 | 描述 |
---|---|---|
_LightColor | float4 | 光源颜色 |
_LightMatrix0 | float4x4 | 从世界空间到光源空间的变换矩阵。可以用于采样cookie和光强衰减纹理 |
Unity的光源类型
光源类型有什么影响
最常使用的光源属性有光源的位置、到某点的方向、颜色、强度以及到某点的衰减。
平行光
平行光没有位置,因此没有衰减的概念。几何属性只有方向,并且到所有点的方向都是一致的。
点光源
点光源的照亮空间是有限的。光源的位置、到某点的方向、颜色、强度以及到某点的衰减都有。
聚光灯
和点光源差不多一样,只不过一个是球体一个是椎体
在前向渲染中处理不同的光源类型
Shader "Unity Shaders Book/Chapter 9/Forward Rendering" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Tags { "RenderType"="Opaque" }
//计算环境光和自发光
Pass {
// Pass for ambient light & first pixel light (directional light)
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdbase//保证使用光照衰减等光照变量可以被正确赋值。不可缺少
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
//使用了Blinn-Phong光照模型
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);//没有位置 可以直接使用_WorldSpaceLightPos0
//环境光和自发光(本例无)先计算一次 保证在AdditionalPass中不再计算此部分
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
fixed atten = 1.0;//平行光没有衰减 直接为1
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
ENDCG
}
//为其他逐像素光源定义Additional Pass
Pass {
// Pass for other pixel lights
Tags { "LightMode"="ForwardAdd" }
//开启和设置了混合模式 目的是希望该pass计算得到的光照结果可以在帧缓存中与之前的光照结果叠加
Blend One One//也可用Blend SrcAlpha One
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdadd
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
//判断逐像素光源的类型
#ifdef USING_DIRECTIONAL_LIGHT//是平行光
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);//直接使用xyz
#else//是点光源或者聚光灯
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);//使用位置相减
#endif
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
//处理不同光源的衰减
#ifdef USING_DIRECTIONAL_LIGHT//是平行光
fixed atten = 1.0;
#else//是点光源或者聚光灯
#if defined (POINT)
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT)
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#else
fixed atten = 1.0;
#endif
#endif
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
Unity的光照衰减
使用纹理查找计算衰减的弊端:
- 需要预处理得到采样纹理,而且纹理的大小也会影响衰减的精度
- 不直观 也不方便。因为一旦把数据存储到查找表中,我们就无法使用其他数学公式来计算衰减。
但由于这种方法可以在一定程度上提升性能,而且得到的效果在大部分情况下都是好的。因此Unity默认使用这种纹理查找的方式来计算逐像素的点光源和聚光灯的衰减。
用于光照衰减的纹理
Unity在内部是使用一张名为_LightTexture0的纹理来计算光源衰减。我们通常只关心_LightTexture0对角线上的纹理颜色值,这些值表明了在光源空间中不同位置的点的衰减值。(0,0)表明了与光远位置重合的点的衰减值(1,1)表明在光源空间中所关心的距离最远的点的衰减
通过得到某点在光源空间中的位置,可以得到_LightTexture0纹理采样中某点到光源的衰减值。
float3 lightCoord = mul(_LightMatrix0,float4(i.worldPosition,1)).xyz;
_LightMatrix0可以把顶点从世界空间变换到光源空间
然后,使用坐标的模的平方对衰减纹理进行采样,得到衰减值
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
由点积得到光源的距离平方,这是一个标量,我们对这个变量进行.rr操作相当于构建了一个二维矢量,这个二维矢量每个分量的值都是这个标量值,由此得到一个二维采样坐标。
UNITY_ATTEN_CHANNEL是衰减值所在的纹理通道,可以在内置的HLSLSupport.cginc文件中查看。一般PC和主机平台的话UNITY_ATTEN_CHANNEL是r通道,移动平台的话是a通道
使用数学公式计算衰减
float distance = length(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);
atten = 1.0 / distance;
由于Unity文档中没有给出内置衰减计算的相关说明,因此无法在shader中通过内置变量得到光源的范围、聚光灯的朝向、张开角度等信息,因此得到的效果往往不尽人意,尤其是在物体离开光源的照明范围时会发生突变。因为如果物体不在该光源的照明范围内,Unity就不会为物体执行一个Additional Pass。
Unity的阴影
阴影是如何实现的
当一个光源发射的一条光线遇到一个不透明物体时,这条光线就不可以再继续照亮其他物体(这里不考虑光线反射)。因此这个物体就会向它旁边的物体投射阴影,那些阴影区域的产生是因为光线无法到达这些区域。
在实时渲染中,最常使用的是Shadow Map的技术。他首先会把摄像机的位置放在与光源重合的位置上,那么场景中光源的阴影区域就是摄像机看不到的地方。Unity就是使用的这种技术。
在前向渲染中,如果平行光开启了阴影,Unity就会为该光源计算阴影映射纹理(shadowmap)。这张阴影映射纹理本质上是一张深度图,记录了从光源位置出发,能看到的场景中距离它最近的表面位置(深度信息)。
Unity使用一个额外的Pass来专门更新光源的阴影映射纹理,这个Pass就是LightMode标签被设为ShadowCaster的Pass。
Unity会首先把摄像机放到光源的位置上,然后调用该Pass,通过对顶点变换后得到光源空间下的位置,并据此来输出深度信息到阴影映射纹理中。因此当开启了光源的阴影效果之后,底层渲染引擎首先会在当前渲染物体的UnityShader中找到LightMode为ShadowCaster的Pass,如果没有,他就会在Fallback指定的UnityShader中继续找,如果还没有,这个物体就无法向其他物体投射阴影(但仍可接收来自其他物体的阴影)。若找到了,就会使用该Pass来更新光源的阴影映射纹理。
传统映射纹理:在Pass中把顶点位置变换到光源空间下,使用xy分量对阴影映射纹理进行采样,得到阴影映射纹理中该位置的深度信息。若该深度值小于顶点的深度值(通常由z分量得到),说明该点在阴影中
屏幕空间的阴影映射技术:(需要显卡支持MRT)Unity首先通过调用Pass来得到可投射阴影光源的阴影映射纹理以及摄像机的深度纹理,然后据此来得到屏幕空间的阴影图。如果摄像机的深度图中记录的表面深度大于转换到阴影映射纹理中的深度值,就说明虽然表面可见,但是处在该光源的阴影中。
- 如果我们想要一个物体接收来自其他物体的阴影:在Shader中对阴影映射纹理(包括屏幕空间的阴影图)进行采样,把采样结果和最后的光照相乘来产生阴影效果。
- 如果我们想要一个物体向其他物体投射阴影,就必须把该物体加入到光源的阴影映射纹理的计算中,从而让其他物体在对阴影映射纹理采样时可以得到该物体的相关信息。在Unity中这个过程是通过为该物体执行LightMode为ShadowCaster的Pass来实现的
不透明物体的阴影
让平行光可以收集阴影信息
设置让一个物体投射或接收阴影
CastShadows设置为Two Sided可以允许所有面都计算阴影信息
让物体接收阴影
Shader "Unity Shaders Book/Chapter 9/Shadow" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
// Pass for ambient light & first pixel light (directional light)
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
// Need these files to get built-in macros
#include "Lighting.cginc"
#include "AutoLight.cginc" //计算阴影时所用的宏会在这个文件中声明
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
SHADOW_COORDS(2) //内置宏 声明一个用于对纹理采样的坐标 参数是下一个可用的插值寄存器的索引值
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
// Pass shadow coordinates to pixel shader
TRANSFER_SHADOW(o); //用于在顶点着色器中计算上一步中声明的阴影纹理坐标
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
fixed atten = 1.0;
fixed shadow = SHADOW_ATTENUATION(i);//使用_ShadowCoord对相关纹理进行采样,得到阴影信息。
return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
}
ENDCG
}
Pass {
// Pass for other pixel lights
Tags { "LightMode"="ForwardAdd" }
Blend One One
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdadd
// Use the line below to add shadows for point and spot lights
// #pragma multi_compile_fwdadd_fullshadows
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 position : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v) {
v2f o;
o.position = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPos.xyz);
#endif
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
#ifdef USING_DIRECTIONAL_LIGHT
fixed atten = 1.0;
#else
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#endif
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
SHADOW_COORDS实际上就是声明了一个名为_ShadowCoord的阴影纹理坐标变量。而TRANSFER_SHADOW的实现会根据平台不同而有所差异。如果当前平台可以使用屏幕空间的阴影映射技术,它会调用内置的ComputeScreenPos函数来计算_ShadowCoord;如果不支持,就会使用传统映射技术。
由于这些宏会使用上下文变量来进行相关计算,例如TRANSFER_SHADOW会使用v.vertex或a.pos来计算坐标,我们需要保证:a2v结构体的顶点坐标变量名必须是vertex,顶点着色器的输入结构体v2f必须命名为v,且v2f中的顶点位置变量必须命名为pos
统一管理光照衰减和阴影
通过内置的UNITY_LIGHT_ATTENUATION宏可实现同时计算光照衰减和阴影。
在这里插入代码片Shader "Unity Shaders Book/Chapter 9/Attenuation And Shadow Use Build-in Functions" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Tags { "RenderType"="Opaque" }
Pass {
// Pass for ambient light & first pixel light (directional light)
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
// Need these files to get built-in macros
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
SHADOW_COORDS(2)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
// Pass shadow coordinates to pixel shader
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
ENDCG
}
Pass {
// Pass for other pixel lights
Tags { "LightMode"="ForwardAdd" }
Blend One One
CGPROGRAM
// Apparently need to add this declaration
#pragma multi_compile_fwdadd
// Use the line below to add shadows for point and spot lights
// #pragma multi_compile_fwdadd_fullshadows
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
SHADOW_COORDS(2)//使用内置宏SHADOW_COORDS声明阴影坐标
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
// Pass shadow coordinates to pixel shader
TRANSFER_SHADOW(o);//使用内置宏计算并向片元着色器传递阴影坐标
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
//参数1 atten未声明 因为UNITY_LIGHT_ATTENUATION会声明这个变量
//参数2 用来计算阴影值 参数3用来计算光源空间下的坐标
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);//计算光照衰减和阴影
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
如果希望可以在Additional Pass中添加阴影效果,需要用#pragma multi_compile_fwdadd_fullshadows
编译指令代替Additional Pass中的#pragma multi_compile_fwdadd
指令
透明度物体的阴影
对于大多数不透明武替来说,把Fallback设为VertexLit就可以得到正确的阴影。但对于透明武替来说,透明物体的实现通常会使用透明度测试或透明度混合,需要小心设置Fallback。
由于透明度测试会在片元着色器中舍弃片元,所以如果直接使用VertexLit、Diffuse、Specular,往往无法得到正确的投影。
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 9/Alpha Blend With Shadow" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_AlphaScale ("Alpha Scale", Range(0, 1)) = 1
}
SubShader {
Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
Pass {
Tags { "LightMode"="ForwardBase" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed _AlphaScale;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv : TEXCOORD2;
SHADOW_COORDS(3)//由于已经使用了TEXCOORD0 1 2所以要传入3 意味着占用第四个寄存器TEXCOORD3
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
// Pass shadow coordinates to pixel shader
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed4 texColor = tex2D(_MainTex, i.uv);
fixed3 albedo = texColor.rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir));
// UNITY_LIGHT_ATTENUATION not only compute attenuation, but also shadow infos
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
return fixed4(ambient + diffuse * atten, texColor.a * _AlphaScale);
}
ENDCG
}
}
FallBack "VertexLit"
//FallBack "Transparent/Cutout/VertexLit"
}
FallBack "VertexLit"
没有进行任何透明度测试的计算。
FallBack "Transparent/Cutout/VertexLit"
计算了透明度测试,但默认情况下把物体渲染到深度图和阴影纹理映射中仅考虑了正面 需要把cast shadow属性设为two sided,强制计算所有面的深度信息。
需要注意的是由于FallBack "Transparent/Cutout/VertexLit"
中计算透明度测试时,使用了_Cutoff的属性来进行透明度测试。所以我们的Shader中也必须提供名为_Culloff的属性
BumpedDiffuse
使用了Phong光照模型
Shader "Unity Shaders Book/Common/Bumped Diffuse" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
return fixed4(ambient + diffuse * atten, 1.0);
}
ENDCG
}
Pass {
Tags { "LightMode"="ForwardAdd" }
Blend One One
CGPROGRAM
#pragma multi_compile_fwdadd
// Use the line below to add shadows for point and spot lights
// #pragma multi_compile_fwdadd_fullshadows
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
return fixed4(diffuse * atten, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
BumpedSpecular
使用了Blinn-Phong光照模型
Shader "Unity Shaders Book/Common/Bumped Specular" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_BumpMap ("Normal Map", 2D) = "bump" {}
_Specular ("Specular Color", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry"}
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
TANGENT_SPACE_ROTATION;
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
fixed3 halfDir = normalize(lightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
ENDCG
}
Pass {
Tags { "LightMode"="ForwardAdd" }
Blend One One
CGPROGRAM
#pragma multi_compile_fwdadd
// Use the line below to add shadows for point and spot lights
// #pragma multi_compile_fwdadd_fullshadows
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i) : SV_Target {
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
fixed3 halfDir = normalize(lightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
UNITY_LIGHT_ATTENUATION(atten, i, worldPos);
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}