1、Fallback的作用
新建一个材质球,将其的Shader设置为之前编写的多种光源综合实现Shader
并将该材质球赋值给较大的立方体使用,我们会发现该立方体不再投射阴影也不再接受阴影
(1)不投射阴影的原因
该Shader中没有LightMode为ShaderCaster的Pass,无法参与光源的阴影映射纹理的计算
(2)不接收阴影的原因
该Shader并没有对阴影映射相关纹理进行采样,没有进行阴影相关颜色运算
Unity会寻找LightMode为ShaderCaster的Pass来进行处理,如果该Shader没有该Pass,会在它FallBack指定的Shader中寻找,直到找到为止
在该Shader 最后加上FallBack "Specular",便可以让该立方体投射阴影(但没有接收阴影)
2、让物体投射阴影
物体向其它物体投射阴影的关键点是:
(1)需要实现 LightMode(灯光模式) 为 ShadowCaster(阴影投射) 的 Pass(渲染通道),这样该物体才能参与到光源的阴影映射纹理计算中
(2)一个编译指令,一个内置文件,三个关键宏
编译指令:#pragma multi_compile_shadowcaster
该编译指令时告诉Unity编译器生成多个着色器变体,用于支持不同类型的阴影(SM,SSSM等等),可以确保着色器能够在所有可能的阴影投射模式下正确渲染
内置文件:#include "UnityCG.cginc",其中包含了关键的阴影计算相关的宏三个关键宏:
2 - 1.V2F_SHADOW_CASTER
顶点到片元着色器阴影投射结构体数据宏,这个宏定义了一些标准的成员变量,这些变量用于在阴影投射路径中传递顶点数据到片元着色器,我们主要在结构体中使用
2 - 2.TRANSFER_SHADOW_CASTER_NORMALOFFSET
转移阴影投射器法线偏移宏,用于在顶点着色器中计算和传递阴影投射所需的变量,我们主要在顶点着色器中使用,主要做了
- 将对象空间的顶点位置转换为裁剪空间的位置
- 考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
- 传递顶点的投影空间位置,用于后续的阴影计算
2 - 3.SHADOW_CASTER_FRAGMENT
阴影投射片元宏,将深度值写入到阴影映射纹理中,我们主要在片元着色器中使用
Shader "ShaderProj/4/ShadownNormal"
{
Properties
{
_MainColor("MainColor", Color) = (1,1,1,1)
_SpecularColor("SpecularColor", Color) = (1,1,1,1)
_SpecularNum("SpecularNum", Range(0, 20)) = 1
}
SubShader
{
//Base Pass 基础渲染通道
Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _MainColor;
fixed4 _SpecularColor;
float _SpecularNum;
struct v2f
{
float4 pos:SV_POSITION;
float3 wNormal:NORMAL;
float3 wPos:TEXCOORD0;
};
fixed3 getLambertColor(in float3 wNormal)
{
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0, dot(wNormal, lightDir));
return color;
}
fixed3 getSpecularColor(in float3 wPos, in float3 wNormal)
{
float3 viewDir = normalize(UnityWorldSpaceViewDir(wPos));
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 halfA = normalize(viewDir + lightDir);
fixed3 color = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(wNormal, halfA)), _SpecularNum);
return color;
}
v2f vert (appdata_base v)
{
v2f data;
data.pos = UnityObjectToClipPos(v.vertex);
data.wNormal = UnityObjectToWorldNormal(v.normal);
data.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return data;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 lambertColor = getLambertColor(i.wNormal);
fixed3 specularColor = getSpecularColor(i.wPos, i.wNormal);
fixed atten = 1; // 衰减值
//衰减值 会和 漫反射颜色 + 高光反射颜色 后 再进行乘法运算
fixed3 BlinnPhongColor = UNITY_LIGHTMODEL_AMBIENT.rgb + (lambertColor + specularColor) * atten;
return fixed4(BlinnPhongColor, 1);
}
ENDCG
}
//Additional Pass 附加渲染通道
Pass
{
Tags { "LightMode"="ForwardAdd" }
//线性减淡的效果 进行 光照颜色混合
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _MainColor;
fixed4 _SpecularColor;
float _SpecularNum;
struct v2f
{
float4 pos:SV_POSITION;
float3 wNormal:NORMAL;
float3 wPos:TEXCOORD0;
};
v2f vert (appdata_base v)
{
v2f data;
data.pos = UnityObjectToClipPos(v.vertex);
data.wNormal = UnityObjectToWorldNormal(v.normal);
data.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return data;
}
fixed4 frag (v2f i) : SV_Target
{
//兰伯特漫反射
fixed3 worldNormal = normalize(i.wNormal);
#if defined(_DIRECTIONAL_LIGHT) //平行光 光的方向 其实就是它的位置
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else //点光源和聚光灯 光的方向 是 光的位置 - 顶点位置
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.wPos);
#endif
fixed3 diffuse = _LightColor0.rgb * _MainColor.rgb * max(0, dot(worldNormal, worldLightDir));
//BlinnPhong高光反射
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.wPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(worldNormal, halfDir)), _SpecularNum);
// 衰减值
#if defined(_DIRECTIONAL_LIGHT)
fixed atten = 1;
#elif defined(_POINT_LIGHT)
float3 lightCoord = mul(unity_WorldToLight, float4(i.wPos ,1)).xyz
//利用这个坐标得到距离的平方 然后再再光源纹理中隐射得到衰减值
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).xx).UNITY.ATTEN_CHANNEL;
#elif defined(_SPOT_LIGHT)
//将世界坐标系下顶点转到光源空间下 聚光灯需要用w参与后续计算
float4 lightCoord = mul(unity_WorldToLight, float4(i.wPos, 1));
fixed4 atten = (lightCoord.z > 0) * //判断在聚光灯前面吗
tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * //映射到大图中进行采样
tex2D(_LightTextureB0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL; //距离的平方采样
#else
fixed atten = 1;
#endif
//在附加渲染通道中不需要在加上环境光颜色了 因为它只需要计算一次 在基础渲染通道中已经计算了
return fixed4((diffuse + specular)*atten, 1);
}
ENDCG
}
//主要用于进行阴影投影 主要是用来计算阴影映射纹理的
Pass {
Tags {"LightMode" = "ShadowCaster"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 该编译指令时告诉Unity编译器生成多个着色器变体
// 用于支持不同类型的阴影(SM,SSSM等等)
// 可以确保着色器能够在所有可能的阴影投射模式下正确渲染
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
struct v2f
{
//顶点到片元着色器阴影投射结构体数据宏
//这个宏定义了一些标准的成员变量
//这些变量用于在阴影投射路径中传递顶点数据到片元着色器
//我们主要在结构体中使用
V2F_SHADOW_CASTER;
};
v2f vert(appdata_base v)
{
v2f data;
//转移阴影投射器法线偏移宏
//用于在顶点着色器中计算和传递阴影投射所需的变量
//主要做了
//2-2-1.将对象空间的顶点位置转换为裁剪空间的位置
//2-2-2.考虑法线偏移,以减轻阴影失真问题,尤其是在处理自阴影时
//2-2-3.传递顶点的投影空间位置,用于后续的阴影计算
//我们主要在顶点着色器中使用
TRANSFER_SHADOW_CASTER_NORMALOFFSET(data);
return data;
}
float4 frag(v2f i):SV_TARGET
{
//阴影投射片元宏
//将深度值写入到阴影映射纹理中
//我们主要在片元着色器中使用
SHADOW_CASTER_FRAGMENT(i);
}
ENDCG
}
}
FallBack "Specular"
}
3、让物体接收阴影
所谓的投射阴影,其实就是让物体参与到光源的阴影映射纹理计算中,最终才能影响其他物体在接收阴影时的采样结果
由此可见让物体接收阴影的主要思路其实就是要从阴影隐射纹理中进行采样,然后将采样结果用于最终的颜色计算中
总体的流程就是:
- 在顶点着色器中进行顶点坐标转换(将顶点坐标 转换为 阴影映射纹理坐标)
- 在片元着色器中使用阴影映射纹理坐标在阴影映射纹理中进行采样,通过得到的深度值判断片元(像素)是否在阴影中,以计算出阴影衰减值
- 将采样结果参与到最终的颜色计算中
实现物体接收阴影效果
(1)接受阴影的三剑客(三个宏)
首先我们需要在Base Pass当中引用包含内置文件:# include "AutoLight.cginc",该内置文件中,有用于计算阴影时需要使用的三剑客
1 - 1.SHADOW_COORDS(阴影坐标宏)
该宏在v2f结构体(顶点着色器返回值)中使用
本质上就是声明了一个用于对阴影纹理进行采样的坐标
在内部实际上就是声明了一个名为_ShadowCoord的阴影纹理坐标变量
需要注意的是:
在使用时 SHADOW_COORDS(2) 传入参数2
表示需要时下一个可用的插值寄存器的索引值
1 - 2.TRANSFER_SHADOW(转移阴影宏)
该宏在顶点着色器函数中调用,传入对应的v2f结构体对象
该宏会在内部自己判断应该使用哪种阴影映射技术(SM、SSSM)
最终的目的就是将顶点进行坐标转换并存储到_ShadowCoord阴影纹理坐标变量中
需要注意的是:
- 该宏会在内部使用顶点着色器中传入的结构体,该结构体中顶点的命名必须是vertex
- 该宏会在内部使用顶点着色器的返回结构体,其中的顶点位置命名必须是pos
1 - 3.SHADOW_ATTENUATION(阴影衰减宏)
该宏在片元着色器中调用,传入对应的v2f结构体对象
该宏会在内部利用v2f中的 阴影纹理坐标变量(ShadowCoord)对相关纹理进行采样
将采样得到的深度值进行比较,以计算出一个fixed3的阴影衰减值
我们只需要使用它返回的结果和(漫反射 + 高光反射) 的结果相乘即可
(2)注意
目前处理的方式只是大致了解接受阴影的流程,还没有对 Additional Pass 附加渲染通道进行处理
Shader "ShaderProj/4/ShadownNormalReceiver"
{
Properties
{
_MainColor("MainColor", Color) = (1,1,1,1)
_SpecularColor("SpecularColor", Color) = (1,1,1,1)
_SpecularNum("SpecularNum", Range(0, 20)) = 1
}
SubShader
{
//Base Pass 基础渲染通道
Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "Lighting.cginc"
//引用包含该内置文件 其中有计算阴影会用到的三个宏(阴影三剑客)
#include "AutoLight.cginc"
fixed4 _MainColor;
fixed4 _SpecularColor;
float _SpecularNum;
struct v2f
{
float4 pos:SV_POSITION;
float3 wNormal:NORMAL;
float3 wPos:TEXCOORD0;
// 该宏在v2f结构体(顶点着色器返回值)中使用
// 本质上就是声明了一个用于对阴影纹理进行采样的坐标
// 在内部实际上就是声明了一个名为_ShadowCoord的阴影纹理坐标变量
// 需要注意的是:
// 在使用时 SHADOW_COORDS(2) 传入参数2
// 表示需要时下一个可用的插值寄存器的索引值
// 阴影坐标宏 主要用于存储阴影纹理坐标
SHADOW_COORDS(2)
};
fixed3 getLambertColor(in float3 wNormal)
{
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 color = _LightColor0.rgb * _MainColor.rgb * max(0, dot(wNormal, lightDir));
return color;
}
fixed3 getSpecularColor(in float3 wPos, in float3 wNormal)
{
float3 viewDir = normalize(UnityWorldSpaceViewDir(wPos));
float3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
float3 halfA = normalize(viewDir + lightDir);
fixed3 color = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(wNormal, halfA)), _SpecularNum);
return color;
}
v2f vert (appdata_base v)
{
v2f data;
data.pos = UnityObjectToClipPos(v.vertex);
data.wNormal = UnityObjectToWorldNormal(v.normal);
data.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
// 该宏在顶点着色器函数中调用,传入对应的v2f结构体对象
// 该宏会在内部自己判断应该使用哪种阴影映射技术(SM、SSSM)
// 最终的目的就是将顶点进行坐标转换并存储到_ShadowCoord阴影纹理坐标变量中
// 需要注意的是:
// 1.该宏会在内部使用顶点着色器中传入的结构体
// 该结构体中顶点的命名必须是vertex
// 2.该宏会在内部使用顶点着色器的返回结构体
// 其中的顶点位置命名必须是pos
//计算阴影映射纹理坐标 它会在内部去进行计算 然后将其存入 v2f中的SHADOW_COORDS中
TRANSFER_SHADOW(data)
return data;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 lambertColor = getLambertColor(i.wNormal);
fixed3 specularColor = getSpecularColor(i.wPos, i.wNormal);
// 得到阴影衰减值
// 该宏在片元着色器中调用,传入对应的v2f结构体对象
// 该宏会在内部利用v2f中的 阴影纹理坐标变量(ShadowCoord)对相关纹理进行采样
// 将采样得到的深度值进行比较,以计算出一个fixed3的阴影衰减值
// 我们只需要使用它返回的结果和 (漫反射+高光反射) 的结果相乘即可
float3 shadow = SHADOW_ATTENUATION(i);
fixed atten = 1; // 衰减值
//衰减值 会和 漫反射颜色 + 高光反射颜色 后 再进行乘法运算
fixed3 BlinnPhongColor = UNITY_LIGHTMODEL_AMBIENT.rgb + (lambertColor + specularColor) * atten * shadow;
return fixed4(BlinnPhongColor, 1);
}
ENDCG
}
//Additional Pass 附加渲染通道
Pass
{
Tags { "LightMode"="ForwardAdd" }
//线性减淡的效果 进行 光照颜色混合
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _MainColor;
fixed4 _SpecularColor;
float _SpecularNum;
struct v2f
{
float4 pos:SV_POSITION;
float3 wNormal:NORMAL;
float3 wPos:TEXCOORD0;
};
v2f vert (appdata_base v)
{
v2f data;
data.pos = UnityObjectToClipPos(v.vertex);
data.wNormal = UnityObjectToWorldNormal(v.normal);
data.wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
return data;
}
fixed4 frag (v2f i) : SV_Target
{
//兰伯特漫反射
fixed3 worldNormal = normalize(i.wNormal);
#if defined(_DIRECTIONAL_LIGHT) //平行光 光的方向 其实就是它的位置
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
#else //点光源和聚光灯 光的方向 是 光的位置 - 顶点位置
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz - i.wPos);
#endif
fixed3 diffuse = _LightColor0.rgb * _MainColor.rgb * max(0, dot(worldNormal, worldLightDir));
//BlinnPhong高光反射
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.wPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(worldNormal, halfDir)), _SpecularNum);
// 衰减值
#if defined(_DIRECTIONAL_LIGHT)
fixed atten = 1;
#elif defined(_POINT_LIGHT)
float3 lightCoord = mul(unity_WorldToLight, float4(i.wPos ,1)).xyz
//利用这个坐标得到距离的平方 然后再再光源纹理中隐射得到衰减值
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).xx).UNITY.ATTEN_CHANNEL;
#elif defined(_SPOT_LIGHT)
//将世界坐标系下顶点转到光源空间下 聚光灯需要用w参与后续计算
float4 lightCoord = mul(unity_WorldToLight, float4(i.wPos, 1));
fixed4 atten = (lightCoord.z > 0) * //判断在聚光灯前面吗
tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * //映射到大图中进行采样
tex2D(_LightTextureB0, dot(lightCoord,lightCoord).xx).UNITY_ATTEN_CHANNEL; //距离的平方采样
#else
fixed atten = 1;
#endif
//在附加渲染通道中不需要在加上环境光颜色了 因为它只需要计算一次 在基础渲染通道中已经计算了
return fixed4((diffuse + specular)*atten, 1);
}
ENDCG
}
}
FallBack "Specular"
}