原理
阴影渲染的原理是将相机放在光源的位置,相机看不到的地方也就是光源无法照射的地方,即阴影.
我们有两个步骤需要实现,分别是
1.向物体投射阴影
2.接收其他物体投射的阴影
阴影投射
我们首先需要打开光源的阴影投射,和物体的阴影投射,组件中的设置会影响shader中是否计算阴影.
我们以普通的高光反射为基础,测试阴影投射
细心的人一定发现了我注释了Fallback,这是因为即便是我们没写阴影投射的方法,Fallback还是会从Diffuse中找到阴影投射的方法.
高光反射代码
Shader "Unity/Custom/NewSurfaceShader" {
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//在forwardBase中获取正确的光照变量
#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 f;
f.pos = UnityObjectToClipPos(v.vertex);//模型空间到剪裁空间转换
f.worldNormal = UnityObjectToWorldNormal(v.normal);//模型空间到世界坐标系法线
f.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;//模型空间到世界坐标系位置
return f;
}
fixed4 frag(v2f f) : SV_Target{
fixed3 worldNormal = normalize(f.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 - f.worldPos.xyz);//朝向相机的方向
fixed3 halfDir = normalize(worldLightDir + viewDir);//h方向 用于计算高光反射补偿度
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);//高光反射
fixed atten = 1.0;//光照衰减 在这个pass中是平行光 没有衰减
return fixed4(ambient + (diffuse + specular) * atten, 1.0);
}
ENDCG
}
}
//FallBack "Diffuse"
}
效果
关闭了fallBack 此时的效果没有阴影
阴影投射代码
我们需要定义一个pass,并使用Tags{ “LightMode” = “ShadowCaster” }来进行阴影投射的预处理
pass {
Tags{ "LightMode" = "ShadowCaster" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
struct v2f {
V2F_SHADOW_CASTER;
};
v2f vert(appdata_base v) {
v2f f;
TRANSFER_SHADOW_CASTER_NORMALOFFSET(f);
return f;
}
float4 frag(v2f i) :SV_Target{
SHADOW_CASTER_FRAGMENT(i);
}
ENDCG
}
效果
我们把阴影投射的pass加入到高光反射的代码中,物体恢复了阴影投射的效果.
另外需要注意的是此时的Plane使用的是默认的shader,里面有接收阴影的处理,如果我们使用的shader中没有接收阴影的代码,Plane将不接收阴影.
总结
我们通常只需要设置一个带有阴影投射的Fallback就可以实现阴影投射了,除非我们有更特别的需求,才会改写上面的代码.
阴影接收
阴影接收的步骤如下
1.声明阴影接收相关的函数库
#include "AutoLight.cginc"//阴影投射相关的宏在这个库中
2.在v2f中声明一个变量用于阴影纹理采样.
struct v2f {
float4 pos : SV_POSITION;//输出给剪裁空间的顶点坐标 在阴影接收的宏中规定了pos这个字段 名称不可以改
float3 worldNormal : TEXCOORD0;//世界坐标系法线
float3 worldPos : TEXCOORD1;//世界坐标系坐标
SHADOW_COORDS(2)//声明一个坐标用于阴影纹理采样 2为下一个可用的寄存器索引值 也就是TEXCOORD2
};
3.在vert函数中计算阴影纹理坐标
v2f vert(a2v v) {
v2f f;
f.pos = UnityObjectToClipPos(v.vertex);//模型空间到剪裁空间转换
f.worldNormal = UnityObjectToWorldNormal(v.normal);//模型空间到世界坐标系法线
f.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;//模型空间到世界坐标系位置
TRANSFER_SHADOW(f);//计算阴影纹理坐标
return f;
}
4.在frag函数中计算阴影值,并与高光反射,漫反射相乘
fixed4 frag(v2f f) : SV_Target{
fixed shadow = SHADOW_ATTENUATION(f);//计算阴影值
fixed3 worldNormal = normalize(f.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 - f.worldPos.xyz);//朝向相机的方向
fixed3 halfDir = normalize(worldLightDir + viewDir);//h方向 用于计算高光反射补偿度
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);//高光反射
fixed atten = 1.0;//光照衰减 在这个pass中是平行光 没有衰减
return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
}
阴影接收完整代码
Shader "Unity/Custom/NewSurfaceShader" {
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//在forwardBase中获取正确的光照变量
#pragma vertex vert//声明顶点处理方法
#pragma fragment frag//声明片段处理方法
#include "Lighting.cginc"//调用光照函数库
#include "AutoLight.cginc"//阴影投射相关的宏在这个库中
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v {
float4 vertex : POSITION;//顶点的模型空间位置 在阴影接收的宏中规定了vertex这个字段 名称不可以改
float3 normal : NORMAL;//模型空间法线
};
struct v2f {
float4 pos : SV_POSITION;//输出给剪裁空间的顶点坐标 在阴影接收的宏中规定了pos这个字段 名称不可以改
float3 worldNormal : TEXCOORD0;//世界坐标系法线
float3 worldPos : TEXCOORD1;//世界坐标系坐标
SHADOW_COORDS(2)//声明一个坐标用于阴影纹理采样 2为下一个可用的寄存器索引值 也就是TEXCOORD2
};
v2f vert(a2v v) {
v2f f;
f.pos = UnityObjectToClipPos(v.vertex);//模型空间到剪裁空间转换
f.worldNormal = UnityObjectToWorldNormal(v.normal);//模型空间到世界坐标系法线
f.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;//模型空间到世界坐标系位置
TRANSFER_SHADOW(f);//计算阴影纹理坐标
return f;
}
fixed4 frag(v2f f) : SV_Target{
fixed shadow = SHADOW_ATTENUATION(f);//计算阴影值
fixed3 worldNormal = normalize(f.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 - f.worldPos.xyz);//朝向相机的方向
fixed3 halfDir = normalize(worldLightDir + viewDir);//h方向 用于计算高光反射补偿度
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);//高光反射
fixed atten = 1.0;//光照衰减 在这个pass中是平行光 没有衰减
return fixed4(ambient + (diffuse + specular) * atten * shadow, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}