Unity shader阴影
参考书籍:《Unity Shader入门精要》
阴影是如何实现的
在实时渲染中,我们最常使用的是一种名为Shadow Map的技术。这种技术的原理是将摄像机放到与光源重合的位置上,那么场景中该光源的阴影区域就是那些摄像机看不到的地方。Unity使用的就是这种技术。
在前向渲染路径中,如果场景中最重要的平行光开启了阴影,unity就会为该光源计算它的阴影映射纹理(shadowMap)。这张阴影映射纹理本质上也是一张深度图,它记录了从该光源的位置出发、能看到的场景中距离它最近的表面位置。
在计算阴影映射纹理时,unity选择使用一个额外的Pass来专门更新光源的阴影映射纹理,这个Pass就是标签为ShadowCaster的Pass。这个Pass的渲染目标不是帧缓存,而是阴影映射纹理。当光源开启了阴影效果后,渲染引擎首先会在当前渲染物体的Unity Shader中查找标签为ShadowCaster的Pass,如果没有找到,就在Fallback中查找,如果最终仍未找到,那么该物体就无法向其他物体投射阴影。
Unity的阴影映射纹理是通过屏幕空间的阴影映射技术来实现的。unity使用这种技术需要显卡支持MRT。
*屏幕空间的阴影映射技术:当使用这种技术时,Unity会首先通过调用LightMode为shadowCaster的Pass来得到可投射阴影的光源的阴影映射纹理以及摄像机的深度纹理。然后,根据光源的阴影映射纹理和摄像机的深度纹理来得到屏幕空间的阴影图。如果摄像机的深度图中记录的表面深度大于转换得到阴影映射纹理中的深度值,就说明该表面虽然是可见的,但是却处于该光源的阴影中。通过这样的方式,阴影图就包含了屏幕空间中所有有阴影的区域。如果我们想要一个物体接收来自其他物体的阴影,只需要在Shader中对阴影图进行采样。由于阴影图是屏幕空间下的,因此,我们首先需要把表面坐标从模型空间变换到屏幕空间中,然后使用这个坐标对阴影图进行采样即可。
总结:
一个物体接受来自其他物体的阴影,以及它向其他物体投射阴影是两个过程:
- 如果我们想要一个物体接受来自其他物体的阴影,就必须在shader中对阴影映射纹理进行采样,最后把采样结果和最后的光照结果相乘来得到阴影效果
- 如果我们想要让物体向其他物体投射阴影,就必须把该物体加入光源的阴影映射纹理的计算中,从而让其他的物体能对阴影映射纹理采样时得到该物体的信息,这个过程是通过为该物体执行LightMode为ShadowCaster的Pass来实现的。
让物体投射阴影
修改Mesh Renderer下的Cast Shadows来控制物体能否投射阴影,选择“On”代表能投射阴影,此时unity会把该物体计算到光源的阴影映射纹理中。选择“Off”表示不会投射。
而Receive Shadow则代表能否接收阴影。勾选后投射的阴影细节需要通过shader中来实现。
*通常unity会在Fallback中的回调shader中自动帮我们实现
unity内置的LightMode为ShadowCaster的Pass:
Pass{
Name "ShadowCaster"
Tags {"LightMode"="ShadowCaster"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_shadowcaster
#include "UnityCG.cginc"
struct a2f{
V2F_SHADOW_CASTER;
}
v2f vert(appdata_base v){
v2f o;
TRANSFER_SHADOW_CASTER_NORMALODDSET(o);
return o;
}
float4 frag(v2f i):SV_Target{
SHADOW_CASTER_FRAGMENT(i);
}
ENDCG
}
让物体可以接收阴影
在前向渲染下的shader上进行实现,代码如下:
Shader "Unlit/Chapter9-AttenuationAndShadowUseBuildInFuncitons"
{
Properties
{
//材质属性 BlinnPhong模型中需要的三个常量参数
_Diffuse("Diffuse",Color)=(1,1,1,1) //漫反射中所需的diffuse值(漫反射系数)
_Specular("Specular",Color)=(1,1,1,1) //高光反射中所需的specular值(高光反射系数)
_Gloss("Gloss",Range(8.0,256))=20 //高光反射中所需的材质光泽度
}
SubShader
{
Pass{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
//使用该指令能确保使用光照衰减等光照变量能被正确赋值
#pragma multi_compile_fwdbase
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
//添加该内置文件以便于使用计算阴影用的宏
#include "AutoLight.cginc"
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
struct a2v{
float4 vertex:POSITION;
float4 normal:NORMAL;
};
struct v2f{
float4 pos:SV_POSITION;
float3 worldNormal:TEXCOORD0;
float3 worldPos:TEXCOORD1;
//添加一个内置宏来声明一个用于对阴影纹理采样的坐标,括号中的参数需要是下一个可用的插值寄存器的索引值,比如上一个texcoord1,改插值寄存器索引值就为2
SHADOW_COORDS(2)
};
v2f vert(a2v v){
v2f o;
//得到裁剪空间下的顶点坐标
o.pos=UnityObjectToClipPos(v.vertex);
//得到世界坐标系下的模型法线和顶点坐标
o.worldNormal=mul(v.normal,(float3x3)unity_WorldToObject);
o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
//添加该宏用于在顶点着色器中计算上一步声明的阴影纹理坐标
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i):SV_Target{
//计算环境光
fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
//利用兰伯特公式计算漫反射
fixed3 worldNormal=normalize(i.worldNormal);
fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse=_LightColor0.rgb*_Diffuse.rgb*saturate(dot(worldNormal,worldLightDir));
//计算高光反射
fixed3 viewDir=normalize(_WorldSpaceCameraPos-i.worldPos.xyz);
fixed3 halfDir=normalize(viewDir+worldLightDir);
fixed3 specular=_LightColor0.rgb*_Specular*pow(saturate(dot(worldNormal,halfDir)),_Gloss);
//计算阴影值
//fixed shadow=SHADOW_ATTENUATION(i);
//计算光照衰减
//fixed atten=1.0;
//使用内置宏来计算光照衰减和阴影
UNITY_LIGHT_ATTENUATION(atten,i,i.worldPos);
//然后来计算最终产生的阴影
return fixed4(ambient+(diffuse+specular)*atten,1.0);
}
ENDCG
}
Pass{
Tags{"LightMode"="ForwardAdd"}
Blend One One
CGPROGRAM
#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;
float4 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=mul(v.normal,(float3x3)unity_WorldToObject);
o.worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
//TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i):SV_Target{
//fixed3 ambient=UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal=normalize(i.worldNormal);
//fixed3 worldLightDir=normalize(_WorldSpaceLightPos0.xyz);
#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*saturate(dot(worldNormal,worldLightDir));
fixed3 viewDir=normalize(_WorldSpaceCameraPos-i.worldPos.xyz);
fixed3 halfDir=normalize(viewDir+worldLightDir);
fixed3 specular=_LightColor0.rgb*_Specular*pow(saturate(dot(worldNormal,halfDir)),_Gloss);
// fixed atten=1.0;
#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
//fixed shadow=SHADOW_ATTENUATION(i);
return fixed4((diffuse+specular)*atten,1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}