起因:
最近学习了Unity内的实时阴影的计算,所以这里总结收录一下,加深一下印象。下面分别介绍ShadowMap和屏幕空间阴影和联级阴影的计算流程。
阴影计算流程:
- 首先获得当前摄像机观察到深度纹理。在延迟渲染中,这张深度图Unity已经帮忙计算好了,前向渲染中,我们则需要等待场景都被渲染了一遍,把深度渲染到深度图中。
- 在从“光源处”出发得到从该光源处观察到的深度纹理,也被称为这个光源的ShadowMap。
- 在屏幕空间中做一次阴影收集计算(Shadow Collector),得到一张屏幕空间的阴影纹理。
- 在为正常物体渲染阴影时,只需要在片元着色器中对步骤3得到的屏幕空间阴影采样即可。
详细介绍:
下面详细介绍下各个步骤的过程。
shader代码
我们先简单写一个仅会产生阴影的Shadow
Shader "Unlit/PhongJian"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "AutoLight.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 pos : SV_POSITION;
SHADOW_COORDS(1) // 声明一个用于阴影纹理采样的坐标
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
TRANSFER_SHADOW(o); // 用于计算上一步申明的阴影纹理坐标
return o;
}
fixed4 frag (v2f i) : SV_Target
{
half shadow = SHADOW_ATTENUATION(i);
return fixed4(shadow.xxx, 1);
}
ENDCG
}
}
Fallback "Diffuse"
}
其中注意,必须在末尾写一个带有ShaowCaster实现的shader,不然我们就需要自己实现了;appdata输入的顶点必须叫vertex;v2f输出的顶点必须名为pos,不然TRANSFER_SHADOW宏编译会报错
传统实时阴影
因为旨在了解传统实时阴影的生成流程,我们先将Unity内置的联级阴影给关掉。
将阴影距离调整到10,这样阴影看的更清楚
将摄像机调整到前向渲染,也是默认的渲染模式
我们可以看到,它经历了名为ShaowCaster,输出了光源处的阴影贴图,此时你旋转平行光,会发现这张贴图也会改变,原理是因为是在光源处对物体进行观察得到的ShadowMap
此时我们可以看到,在绘制Mesh时,它传入了上一步得到的阴影,最终将它们绘制到屏幕上去
屏幕空间阴影
先在Project Settings/Graphics打开联级阴影的设置
然后在Project Settings/Quality中取消联级阴影
我们可以看到它首先绘制了一张摄像机视角下的深度图
然后绘制一张从光源处出发的深度图
然后在屏幕空间做一次阴影收集计算(Shadows Collector),这里把每个像素根据它在摄像机深度纹理中的深度值得到世界空间坐标,再把坐标从世界空间转化到光源空间中,和光源空间中的ShadowMap里的深度值做对比,如果大于ShadowMap中的深度距离,说明光源无法照射到
最后在绘制Mesh,它传入上一步得到的屏幕空间阴影,最终混合并绘制到屏幕上去
联级阴影
打开联级阴影的设置
联级阴影流程可以认为是在屏幕空间阴影上做了优化,流程基本大同小异,除了会随着观察距离的远近,生成多张光源处贴图,我们看到移动到特定角度,生成了3张贴图
打开Shadows Cascades,我们确实观察到此时处于绿色范围内
最终输出效果也更加柔和