光学中用辐照度来量化光。光线由光源发射出来以后会与一些物体相交,结果为散射或者吸收。散射分为折射和透射。
为了区分不同的散射方向,在光照模型中用不同的部分计算:高光反射和漫反射。
着色指的是根据材质属性(如漫反射属性等),光源信息(如光源方向、折射度),使用一个等式去计算沿某个观察方向的出射度的过程,我们也把这个等式称为光照模型。
(1).环境光:环境光模拟间接光照。
(2).自发光:材质的自发光颜色,自发光的表面不会照亮周围的表面。
(3).漫反射:漫反射中视角的位置不一样,反射是完全随机的。漫反射符合兰伯特定律,反射光的强度与表面法线和光源方向之间的夹角的余弦值成正比.
(4).高光反射:计算高光反射需要的信息比较多,如表面法线,视角方向,光源方向,反射方向等。常用的有Phong模型,Blinn模型
逐像素光照:在片元着色器中计算为逐像素光照,以每个像素为基础,得到它的法线(可以是对顶点法线插值得到的,也可以是从法线纹理中采样得到的),然后进行光照模型的计算,这种在面片之间对顶点法线进行查值得技术为Phong着色,PHONG插值或法线插值着色技术
逐像素光照:在顶点着色器中计算为逐顶点光照,也称为高洛得着色,我们会在每个顶点上计算光照,然后在渲染图元内部进行线性插值,最后输出像素颜色。
1.Unity环境光在window->lighting->Ambient Source/Ambient Color/Ambient Intensity中控制,在shader中通过UNITY_LIGHTMODEL_AMBIENT可以得到环境光的颜色和强度信息
2.saturate函数(Cg中提供的函数),防止一个标量或矢量为负值,转为[0,1]
saturate(x) x可以为float2 float3 float4
3 reflect(i,n)函数 法线方向n和入射方向i,计算反射方向的函数reflect(i,n)
4.UnityCG.cginc中有一些常用的帮助函数
_WorldSpaceLightPos0 获得当前世界空间的光照
光源方向 normalize(_WorldSpaceLightPos0.xyz);
视角方向 normalize(_WorldSpaceCameraPos.xyz-v.pos.xyz)
float3 WorldSpaceViewDir(float4 v) 输入一个模型空间中的顶点位置,返回世界空间中从该顶点到摄像机的观察方向,内部实现用UnityWorldSpaceViewDir
float3UnityWorldSpaceViewDir(float4 v) 输入一个世界空间中的顶点位置,返回世界空间中从该顶点到摄像机的观察方向
float3 UnityWorldSpaceViewDir(float4 v){
return _WorldCameraPos.xyz - worldPos;
}
float3 ObjSpaceViewDir(float4 v) 输入一个模型空间中的顶点位置,返回模型空间中从该顶点到摄像机的观察方向
float3 WorldSpaceLightDir(float4 v)输入一个模型空间的顶点位置,返回世界空间中该点到到光源的方向,内部实现用的UnityWorldSpaceLightDir,没有归一化
float3 UnityWorldSpaceLightDir(float4 v)输入一个世界空间的顶点位置,返回世界空间中该点到到光源的方向,没有归一化
float3 ObjSpaceLightDir(float4 v)输入一个模型空间的顶点位置,返回模型空间到光源的光照方向,没有归一化
float3 UnityObjectToWorldNormal(float3 normal)把法线从模型空间转换到世界空间
float3 UnityObjectToWorldDir(float3 dir)把方向矢量从模型空间转化到世界空间
float3 UnityWorldToObjectDir(float3 dir)把方向矢量从世界空间到模型空间
这些函数一般没有归一化
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)//控制材质的漫反射颜色
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);//mul(UNITY_MATRIX_MVP,v.vertex) 坐标转换 模型空间到
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Transform the normal from object space to world space
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));//法线,转换到世界坐标,并且归一化
// Get the light direction in world space
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
o.color = ambient + diffuse;
return o;
}
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
Shader "Unity Shaders Book/Chapter 6/Diffuse Pixel-Level" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
};
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
// Transform the normal from object space to world space
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Get the normal in world space
fixed3 worldNormal = normalize(i.worldNormal);
// Get the light direction in world space
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
高光反射模型需要4个参数:入射光线的颜色和强度,材质的高光反射系数,视角方向及反射方向
Cg中提供了根据法线方向n和入射方向i,计算反射方向的函数reflect(i,n)
Shader "Custom/SpecularVexttest" {
Properties {
_Diffuse("Diffuse",Color) = (1,1,1,1);
_Specular("Specular",Color) = (1,1,1,1);
_Gloss("Gloss",Range(8.0,256)) = 20;
}
SubShader {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
fixed4 vertex : POSITION;
fixed4 normal : NORMAL;
};
struct v2f{
fixed4 pos : SV_POSIOTION;//顶点着色器输出的
fixed4 color : COLOR;
};
v2f vert(a2v v){
v2f o;
o.pos = mul(UNITY_POSITION_MVP,v.vertex);
float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldlight = normalize(_WorldLightPos0.xyz);
fixed3 worldnormal = normalize(mul(v.normal,(float3*3)_World2Object))
fixed3 dif = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldnormal,worldlight));
fixed3 reflectDir = normalize(reflect(-worldlight,worldnormal));
fixed3 viewDir = normalize(_WorldCameraPos.xyz - mul(_Object2World,v,vertex).xyz));//_WorldCameraPos得到的是世界空间下摄像机的位置,把顶点位置从模型空间变换到世界空间下,再通过和_WorldCameraPos相减即可得到世界空间下视角方向
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,viewDir)),_Gloss);
o.color = ambient+diffuse+specular;
return o;
}
fixed4 frag(v2f i):SV_Target{
return fixed4(i.color,1.0);
}
ENDCG
}
FallBack "Specular"
}
高光反射部分的计算是非线性的,而在顶点着色器中计算光照在进行插值的过程是线性的,破坏了原计算的非线性关系,就会出现较大的问题。
Shader "Custom/SpecularVexttest" {
Properties {
_Diffuse("Diffuse",Color) = (1,1,1,1);
_Specular("Specular",Color) = (1,1,1,1);
_Gloss("Gloss",Range(8.0,256)) = 20;
}
SubShader {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
fixed4 vertex : POSITION;
fixed4 normal : NORMAL;
};
struct v2f{
fixed4 pos : SV_POSIOTION;//顶点着色器输出的
fixed3 worldNormal : TEXCOORD0;
fixed3 worldPos : TEXCOORD1;
};
v2f vert(a2v v){
v2f o;
o.pos = mul(UNITY_POSITION_MVP,v.vertex);
o.worldNormal = mul(v.normal,(float3*3)_World2Object);
o.worldPos = mul(_Object2World,v.vertex).xyz;
return o;
}
fixed4 frag(v2f i):SV_Target{
fixed3 ambient = UNITY_LIGHTMODLE_AMBIENT.xyz;
fixed3 worldnormal = normalize(i.worldNormal);
fixed3 worldlight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 dif = LightColor.rgb * _Diffuse.rgb * saturate(worldnormal,worldlight);
fixed3 reflectDir = reflect(-worldlight,worlfnormal);
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz-v.pos.xyz);
fixed specular = LightColor0.rgb * Specular.rgb * pow(saturate(viewDir,reflectDir) ,_Gloss);
return fixed4(ambient + dif + specular,1.0);
}
ENDCG
}
FallBack "Specular"
}
Pass中的Lightmode用来指定渲染路径
LightMode标签支持的渲染路径设置选项
Always 不管使用哪种渲染路径,该Pass总会被渲染,但不会计算任何光照
ForwardBase 用于前向渲染 该Pass会计算环境光,平行光,逐顶点/SH光源和Lightmaps
ForwardAdd 用于前向渲染 该Pass会计算额外的逐像素光源,每一个Pass对应一个光源
Deferred 用于延迟渲染,该Pass会渲染G缓冲
ShadowCaster 把物体的深度信息渲染到阴影映射纹理或一张深度纹理中
PrepassBase 用于遗留的延迟渲染,Pass会渲染法线和高光反射的指数部分
PrepassFinal 用于遗留的延迟渲染,Pass会合并纹理、光照和自发光来渲染得到最后的颜色
Vertex VertexLMRGBM VertexLM 用于遗留的顶点照明渲染
每一次完整的前向渲染,都需要渲染该对象的渲染图元,并计算两个缓冲区的信息:颜色缓冲区和深度缓冲区。深度缓冲区决定是否可见。 如果一个物体在多个逐像素光源的影响区域,那么该物体需要执行多个Pass,每个Pass计算一个逐像素的光源的光照结果,帧缓冲中把这些光照结果混合起来得到最终的颜色值。场景中N个物体,M个光源,渲染整个场景需要N* M个Pass。
前向渲染处理三种光照:逐顶点处理,逐像素处理,球谐函数处理。
如果在Unity设置中把一个光源设置为important,Unity会把它当作逐像素光源来处理。
场景中最亮的平行光总是按照逐像素处理的
渲染模式设置为not important的光源,会按逐顶点或者SH处理
如果根据以上规则得到的光源数量小于Quality Setting中的逐像素光源数量,会有更多的光源以像素的方式进行渲染。
前向渲染分为Base Pass 和Additional Pass。
Base Pass 中渲染的平行光默认支持阴影。而Additional Pass中渲染的光源默认没有阴影。可以在Additional Pass中使用#pragma multi _complie_fwadd_fullshadows 代替#pragma multi_complie_fwadd编译指令,为点光源和聚光灯开启阴影效果。
环境光和自发光是在Base Pass 中计算的,因为环境光和自发光之计算一次,如果在 Addtional Pass中会计算多次
在Additional Pass中开启和设置混合模式,希望每个Additional Pass与上一次的光照结果在帧缓存中进行叠加,最终得到有多个光照的渲染效果。如果没有开启和设置混合模式,最后一个渲染结果会覆盖之前的渲染结果,看起来只受该光源影响。通常选择的混合模式是Blend One One
Base Pass 只会执行一次,除非定义多个,Additional Pass会执行多次,每一个逐像素光源会执行一次。
前向渲染可以使用的内置光照变量
_LightColor0 float4 该Pass处理的逐像素光源的颜色
_WorldSpaceLightPos0 float4 _WorldSpaceLightPos0.xyz 逐像素光源的位置,如果是平行光w分量是0,否则为1
_LightMatrix0 float4*4 世界空间到光源空间的变换矩阵
unity_4LightPosX0 unity_4LightPosY0 unity_4LightPosZ0 仅用于Base Pass 前4个非常重要的点光源的位置
unity_4LightAtten0 float4 仅用于Base Pass 前4个非常重要的点光源的衰减因子
unity_LightColor half4[4] 仅用于Base Pass 前4个非常重要的点光源的颜色
前向渲染可以使用的内置光照函数
float3 WorldSpaceLightDir(float4 v) 输入的是模型空间的位置
float3 UnityWorldSpaceLightDir(float4 v) 输入的是世界空间的位置
float3 ObjSpaceLightDir(float4 v) 输入模型空间的顶点位置,输出模型空间点到光源的方向
float3 ShadePointLights(...) 计算四个点光源的光照
顶点照明渲染路径是对硬件配置要求更少,运算性能最高,但同时也是得到的效果最差的一种类型,不支持那些逐像素才能得到的效果,顶点照明渲染路径中实现的功能都可以在前向渲染路径中完成。
Unity中的顶点照明渲染路径通常在一个Pass中就可以完成,我们关心所有光源对该物体的照明,并且这个计算是按逐顶点处理的。这是Unity中最快速的渲染路径。Unity5.0中只保存了一部分,以后可能会被移除。
当光源较多了,前向渲染的性能会急速下降,延迟渲染用来解决这个问题(仅进行一次光照计算)。除了前向渲染中使用的颜色缓冲和深度缓冲外,延迟渲染还会利用额外的缓冲区,为G缓冲区(Geometry)。G缓冲区存储了我们关系的表面的其他信息,例如该表面的法线、位置,用于光照计算的材质属性等。
延迟渲染的实现原理:包含两个Pass,第一个Pass不进行任何光照计算,仅计算哪些片元是可以见的,主要通过深度缓冲技术实现,如果可见,就存入G缓冲区中,我们会把物体的漫反射颜色、高光反射颜色、平滑度、法线、自发光和深度等信息渲染到屏幕空间的G缓冲区中,每个物体这个Pass只执行一次;第二个Pass利用G缓冲区的各个片元信息,进行真正的光照计算。与光源数无关。
延迟渲染的优点:一遍渲染多个光源光照方法中,所有光照运算都在一个着色器中进行。但一个着色器有指令数量的限制,所以这个技术只适用于光源数量较少的情况。在某些游戏中,只需要少量光源,例如室外白天场景,这就是个较好的选择。这个技术的缺点是不能支持光源数量较多的情况。多遍渲染多个光源这种方法物体光照的计算只在当前光源着色器中进行。这会导致非常高的batch数量(调用Draw的次数),最坏的情况会达到光源数量乘以物体数量。某些操作会重复多次,例如顶点的转换。延迟渲染这主要是因为大部分模型在渲染的时候并不需要光照计算,而在渲染场景已经接近完成的时候,才会使用光照信息就好像在渲染一个二维图像一样。在这个阶段所做的改变通常被称为在屏幕空间进行了一些计算。知道这一点以后,我们可以说延迟渲染的光照是发生在屏幕空间。
延迟渲染的缺点:
不支持真正的抗锯齿功能
不能处理半透明物体
对显卡有一定的要求,显卡必须支持MRT,shader mode 3.0及以上,深度渲染纹理以及双面的模板缓冲。
最重要的是,在大多数情况下,移动设备上延迟渲染的性能会比前向渲染的性能要差一些。这是因为每一帧渲染的时候都需要增加一次额外的渲染。如果你的场景中只有一个光源,那么使用延迟渲染可能是不划算的。另外一方面,增加额外的灯光带来的计算消耗也非常低。在最坏的情况下性能的下降也是与光源的数目成正比的,而且与前向渲染相比较,延迟渲染的性能是与场景中的物体数量无关的。
默认的G缓冲区,包含了以下几种渲染纹理:
RT0:格式是ARGB32 RGB适用于存储漫反射颜色,A通道没有被使用
RT1:格式是ARGB32 RGB用于存储高光反射颜色,A通道用于存储高光反射的指数部分
RT2:格式是ARGB2101010,RGB通道用于储存法线,A通道没有被使用
RT3:格式是ARGB32或ARGBHalf(HDR),用于存储自发光+lightmap+反射探针
深度缓冲和模板缓冲
平行光:光照强度不会随着距离而发生改变。Type:Directional
点光源:点光源的照亮空间是有限的,它是由空间中的一个球体定义的。点光源可以由一个点发出的,向所有的方向延伸的光。球体的半径由Range控制,Type:Point
聚光灯:是空间中的一块锥形区域定义的,聚光灯可以用于表示由一个特定位置出发、向特定方向延伸的光。 type:spot
Unity Shader访问5个属性:位置、方向、颜色、强度和衰减
Shader "Unity Shaders Book/Chapter 6/Blinn-Phong" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase //base Pass
#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;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
// Transform the normal from object space to world space
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
// Transform the vertex from object spacet to world space
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
// Get the view direction in world space
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// Get the half direction in world space
fixed3 halfDir = normalize(worldLightDir + viewDir);
// Compute specular term
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
fixed atten = 1.0;
return fixed4(ambient + (diffuse + specular)*atten, 1.0);
}
ENDCG
}
Pass {
Tags { "LightMode"="ForwardAdd" }
Blend One One //混合系数设定 addpass就是将计算得到的光照结果与之间帧缓存中的光照结果进行混合
CGPROGRAM
#pragma multi_compile_fwdadd //base Pass
#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;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
// Transform the normal from object space to world space
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
// Transform the vertex from object spacet to world space
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
#ifdef USING_DIRECTIONAL_LIGHT
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed atten = 1.0;
#else
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);//_WorldSpaceLightPos0.xyz世界空间下光源的位置,不是平行光就需要减去世界空间下顶点的位置
float3 lightCoord = mul(_LightMatrix0,float4(i.worldPos,1)).xyz;
fixed atten = tex2D(_LightTexture0,dot(lightCoord,lightCoord).rr).UNITY_ATTEN_CHANNEL;
//Unity选择一张纹理作为查找表(LUT),以片元着色器中得到的光源的衰减。
#endif
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * max(0, dot(worldNormal, worldLightDir));
// Get the view direction in world space
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
// Get the half direction in world space
fixed3 halfDir = normalize(worldLightDir + viewDir);
// Compute specular term
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
return fixed4(ambient +( diffuse + specular)*atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
利用光照计算纹理:
Unity可以根据预处理得到的纹理采样,利用查找表计算逐像素光照衰减。纹理的大小会影响衰减的精度
Unity内部一张名为_LightTexture0的纹理计算光源的衰减,_LightMatrix0和世界空间中的顶点坐标相乘即可得到光源空间中的相应位置:
float3 lightcoord = mul(_LightMatrix,float4(i.worldPos,1)).xyz;
fixed atten = tex2D(_LightTexture0,dot(lightcoord,lightcoord).rr).UNITY_ATTEN_CHANNEL;
利用数学公式计算纹理:
float distance = length(_WorldSpaceLightPos0.xyz - i.worldPosition.xyz);
atten = 1.0/distance;
Shadow Map 阴影是把摄像机放在和光源同样的位置,阴影区就是摄像机看不到的地方。
在前向渲染路径中,如果场景中最重要的平行光开启了阴影,Unity就会为该光源生成阴影映射纹理,本质上就是一张深度图,记录了从该光源的位置出发,能看到的场景中距离它最近的表面位置(深度信息)。
LightMode 设置为 ShadowCaster时,该Pass渲染目标不是帧缓存,而是阴影映射纹理(或深度纹理)。Unity渲染时,引擎会先在当前的渲染物体的Shader中找到LightMode为ShadowCaster的Pass,如果没有,查找Fallback,如果也不可以,该物体就无法向其他物体投射阴影,当找到了这样的Pass,会使用该Pass更新光源的阴影映射纹理。
一般的计算为使用x y分量对纹理进行采样,得到映射纹理中该点的深度信息,当深度值(z分量)小于该点的深度值,说明该点位于阴影中。
Unity中使用了屏幕空间阴影映射技术。Unity得到了阴影映射纹理和摄像机的深度纹理得到屏幕空间阴影图,如果深度图中记录的深度大于转换到阴影映射纹理中的深度值,说明该表面是可见的,但是却处于阴影中。这样阴影图包含了所有阴影的区域。
总结:
如果一个物体接收来自其他物体的阴影,就必须在Shader中对阴影进行采样,把采样结果和光照结果进行相乘来看阴影效果。
如果一个物体像其他物体投射阴影,就必须把物体加入到光源的阴影映射纹理中,让其他物体在对阴影进行采样时可以得到该物体的相关信息。
实现:
在Unity中可以使用Mesh Render中的 Cast Shadows(决定了该物体是否加到光源的阴影映射纹理计算中) 和 Receive Shadows属性来实现。
Fallback Specular会继续回调,调用到VertexLit,会调用到ShadowCaster,让物体产生阴影。
物体接收投影
SHADOW_COORDS(2)//对纹理阴影坐标进行采样
TRANSFER_SHADOW(o);//这个用来计算上一部声明的阴影纹理坐标
SHADOW_ATTENUATION(i);//使用_ShaderCoord对相关的纹理进行采样
// Upgrade NOTE: replaced '_LightMatrix0' with 'unity_WorldToLight'
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
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" }//Transpartent 用于透明着色器 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);//使用_ShaderCoord对相关的纹理进行采样
//UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);//Unity内置的宏,用于计算光照衰减和阴影,会帮我们声明atten,i的值用来计算SHADOW_ATTENUATION阴影值,第三个是世界空间坐标,对光照纹理坐标进行衰减
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
//UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);//Unity内置的宏,用于计算光照衰减和阴影,会帮我们声明atten,i的值用来计算SHADOW_ATTENUATION阴影值,第三个是世界空间坐标,对光照纹理坐标进行衰减
return fixed4((diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
Opaque: 用于大多数着色器(法线着色器、自发光着色器、反射着色器以及地形的着色器)。
Transparent:用于半透明着色器(透明着色器、粒子着色器、字体着色器、地形额外通道的着色器)。
TransparentCutout: 蒙皮透明着色器(Transparent Cutout,两个通道的植被着色器)。
Background: Skybox shaders. 天空盒着色器。
Overlay: GUITexture, Halo, Flare shaders. 光晕着色器、闪光着色器。
TreeOpaque: terrain engine tree bark. 地形引擎中的树皮。
TreeTransparentCutout: terrain engine tree leaves. 地形引擎中的树叶。
TreeBillboard: terrain engine billboarded trees. 地形引擎中的广告牌树。
Grass: terrain engine grass. 地形引擎中的草。
GrassBillboard: terrain engine billboarded grass. 地形引擎何中的广告牌草
如果物体接收阴影,在 Addtional Pass中
#pragma multi_compile_fwdadd 改为 #pragma multi_compile_fullshadows
对于大多数不透明的物体,Fallback VertexLit就可以获得到正确的阴影。
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 9/Alpha Test With Shadow" {
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
_Cutoff ("Alpha Cutoff", Range(0, 1)) = 0.5
}
SubShader {
Tags {"Queue"="AlphaTest" "IgnoreProjector"="True" "RenderType"="TransparentCutout"}
Pass {
Tags { "LightMode"="ForwardBase" }
Cull Off
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 _Cutoff;
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)
};
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);
clip (texColor.a - _Cutoff);
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, 1.0);
}
ENDCG
}
}
FallBack "Transparent/Cutout/VertexLit"//ShadowCaster Pass也计算了透明度测试 VertexLit中利用了_Cutoff属性来进行透明度测试,因此我们的shader中也利用_Cutoff属性,即可得到正确的阴影结果
}