阴影实现过程
在UniversalRenderPipeline中(以下简称URP),默认使用的是屏幕空间阴影(Screen Space Shadow), 通过 ScreenSpaceShadowResolvePass 这个类来实现,如果不想使用该阴影的话,就需要修改URP的代码。
首先要明确屏幕空间阴影的实现过程,
- 生成阴影图,_MainLightShadowmapTexture
- 生成屏幕空间深度图,_CameraDepthTexture
- 根据_CameraDepthTexture和裁切空间坐标(ClipSpacePos)重建出世界空间坐标(WorldSpacePos)
- 根据WorldSpacePos 和 _MainLightWorldToShadow矩阵把坐标转换到阴影空间中,生成 _ScreenSpaceShadowmapTexture
- 在RenderOpaques阶段对_ScreenSpaceShadowmapTexture进行采样,计算阴影值
然后要确定修改后的实现过程,
- 生成阴影图,_MainLightShadowmapTexture
- 根据 WorldSpacePos 和 _MainLightWorldToShadow 矩阵把坐标转换到阴影空间中
- 在RenderOpaques阶段对 _MainLightShadowmapTexture 进行采样,计算阴影值
对比两个实现过程可以发现屏幕空间阴影比常规阴影的实现过程多了两个步骤,
- 生成屏幕空间深度图阶段,即Depth Prepass阶段
- 生成屏幕空间阴影图阶段,即Resolve Shadow阶段
由此推测,屏幕空间阴影可能比常规阴影更耗时。
具体修改内容
考虑到增加的是一个切换屏幕空间阴影和常规阴影的开关,因此使用者希望的应该就是一个checkbox之类的勾选框,只有两种状态,清晰明了。在把Camera组件的参数看了一遍以后,我觉着这个开关应该加在Render Shadows选项后面,位置如图:
Inspector面板的修改比较常规,参考已有的Camera属性即可,其中 Render Shadows 选项在代码中对应的属性是 UniversalAdditionalCameraData 类的 renderShadows 属性,如图:
在ForwardRenderer的Setup方法中设置是否需要DepthPrepass阶段和ResolveScreenSpaceShadow阶段,
当选项勾选时开启关键字 _SCREEN_SPACE_SHADOW, 用来在Shader中区分是用哪种阴影,在MainLightShadowCasterPass中设置,
Shader中所作的修改有:
1. 在Lit.shader的UniversalForward pass中定义关键字 _SCREEN_SPACE_SHADOW
2.使用常规阴影时需要在片元阶段用到世界空间坐标,因此需要修改vert 到 frag的结构体
3. 在LitForwardPass.hlsl文件的 InitializeInputData 方法中修改给ShadowCoord属性的赋值逻辑,当使用屏幕空间阴影时ShadowCoord在顶点着色器中计算,使用常规阴影时在片元着色器中计算,
顶点着色器中的修改:
片元着色器中的修改:
4. 修改SHADOWS_SCREEN宏的判断条件
耗时对比
做完这些修改后就可以实现切换屏幕空间阴影和常规阴影了,使用URP自带的测试场景SampleScene,打包到Android设备上(小米8SE)运行,(打包过程有些困扰,感谢车征大佬提供debug思路),使用Profiler查看渲染耗时,只看RenderCamera事件,屏幕空间阴影在13ms到17ms,常规阴影在7ms到15ms,可以看出常规阴影比屏幕空间阴影用时少一些。以下是同样场景,同样摄像机角度保持不变只是切换阴影开关时的15帧数据,只列出两种阴影有差异的一些渲染事件耗时:
总结
根据在真机上运行的profiler数据,可以看出常规阴影的耗时比屏幕空间阴影更少,基本可以达到8~9ms的级别,而且还可以少使用一张RT,即 _ScreenSpaceShadowmapTexture,如果后续效果中不需要使用屏幕空间深度的,还可以把_CameraDepthTexture这张RT也省掉,同时渲染时不执行Depth Prepass阶段,耗时进一步减少。