阴影是光线被阻挡的结果,在实时渲染中,我们最常使用的是一种名为Shadow Map阴影映射的技术,效果不错,而且相对容易实现。
阴影映射原理
我们以光的位置为视角进行渲染,我们能看到的东西都将被点亮,看不见的一定是在阴影之中了
那如何知道物体是看得见还是看不见呢?
我们使用阴影映射纹理,它的本质是一张深度图,记录了从光源的位置出发、能看到的场景中距离它最近的表面位置.
在计算阴影纹理映射时,如何判定距离它最近的表面位置呢?
一种方法是:先把摄像机位置放到光源的位置上,按正常的渲染流程,调用Base Pass和Additional Pass来更新深度信息.这样会造成性能上的浪费,因为我们仅仅需要深度信息,因此,Unity使用一个额外的Pass来专门更新光源的阴影映射纹理.这个Pass就是LightMode标签被设置为ShadowCaster的Pass.
这个方法就是在正常渲染的Pass中把顶点位置变换到光源空间下,得到它在光源空间中的三维位置信息,然后使用xy分量对阴影映射纹理进行采样,得到阴影映射纹理中该位置的深度信息,如果该深度值小于该顶点的深度值,说明该点位于阴影中.
第二种方法是:屏幕空间的阴影映射技术
Unity通过调用LightMode为ShadowCaster的Pass来得到可投射阴影的光源的阴影映射纹理以及摄像机的深度纹理,
然后根据光源的阴影映射纹理和摄像机的深度纹理来得到屏幕空间的阴影图,如果摄像机的深度图中记录的表面深度大于转换到阴影映射纹理中的深度值,说明该表面虽然是可见的,但却处于光源的阴影中.
不透明物体的阴影
不透明物体的阴影投射
正常情况下,如果你想要实现阴影,我们可以直接使用FallBack语义
Fallback "Diffuse"
这样Unity就会寻找内置的VertexLit,这个着色器定义了LightMode为ShadowCaster的Pass自动为我们实现了阴影.
让物体接受阴影
为了让物体接受阴影,我们要使用三个宏: SHADOW_COORDS
,TRANSFER_SHADOW
,UNITY_LIGHT_ATTENUATION
.
这三个宏帮助我们计算阴影,它们声明在AutoLight.cginc
中
SHADOW_COORDS
声明了一个名为_ShadowCoord
的阴影纹理坐标变量
#define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
注意:由于它在末尾已经添加了分号,所以我们直接可以使用这个宏不加分号
TRANSFER_SHADOW
会根据平台的不同有所差异:
如果定义了UNITY_NO_SCREENSPACE_SHADOWS
也就是不支持屏幕空间的阴影映射技术,会使用传统的阴影映射技术
#if defined(UNITY_NO_SCREENSPACE_SHADOWS)
UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_WorldToShadow[0], mul( unity_ObjectToWorld, v.vertex ) );
如果支持屏幕空间的阴影映射技术,就会使用内置的ComputeScreenPos来计算 _ShadowCoord:
#define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);
注意:由于我们使用了宏,这些宏根据上下文变量进行计算,我们必须保证:a2v结构体顶点坐标变量名为vertex,顶点着色器输入结构体a2v必须命名为v, v2f顶点位置变量必须命名为Pos
代码实现如下:
Shader "Unlit/Shadow"
{
Properties
{
_Diffuse("Diffuse",Color) = (1,1,1,1)
_Specular("Specular",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 8.0
}
SubShader
{
Tags {
"RenderType"="Opaque" }
LOD 100
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 _Diffuse;
fixed4 _Specular;
fixed _Gloss;
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal:NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos:TEXCOORD1;
float3 vertexLight : TEXCOORD2;
SHADOW_COORDS(3)
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
#ifdef LIGHTMAP_OFF
float shLight = ShadeSH9(float4(v.normal,1.0));
o.vertexLight = shLight;
#ifdef VERTEXLIGHT_ON
float3 vertexLight = Shade4PointLights(unity_4LightPosX0,unity_4LightPosY0,unity_4LightPosZ0,
unity_LightColor[0].rgb,unity_LightColor[1].rgb,unity_LightColor[2].rgb,unity_LightColor[3].rgb,
unity_4LightAtten0,o.worldPos,o.worldNormal);
o.vertexLight += vertexLight;
#endif
#endif
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