在上篇文章中,通过Screen Space Decal的方式实现了云的阴影,还介绍了一种AAA游戏大世界中渲染云影的方式。
Screen Space Deacl 表现云影的缺点
Screen Space Deacl的好处是实现起来简单,但是也有一些难以避免的缺点:
1.不好处理云影和其他阴影的融合,如下图中标注区域,会出现不自然的阴影叠加。
本质原因是因为在Decal shader中通过alpha blend的方式,这种模拟的阴影一旦和场景阴影叠加便会出现不自然的融合。
2.在处理立体高度区域阴影会出现不自然的拉伸。这种是因为Decal投影方式造成的,虽然可以通过各种方式减轻不自然的拉伸,但是还是还是会出现。
其他表现云影的方式
- 在投影区域上空添加一个云层plan,通过噪声贴图和clip的方式生成对应的mesh,模拟生成一片的云层。
这种方式的坏处是通过mesh生成的云影非常硬,这对于大部分项目是难以接受的。
2.通过后处理的方式,实际上还是存在与decal相同的混合问题。
原因同上,两张图通过alpha blend叠加造成的。
3.接下来就是我即将要介绍的方式,通过类似Screen Space Shadow的处理方式,来做云的阴影,正确处理阴影的融合。
Screen Space Shadow原理
我单独写了一篇文章。
Cloud Shadow 实现思路
首先讲Shader
Vertex Shader就是个FullscreenVert,可以直接拿官方的来用,没什么好说的。
Fragment Shader中实现cloud Shadow本身的方式和Decal中没区别,通过一张或多张noise噪声来生成。
需要注意的是我们需要从深度图里重建屏幕的世界坐标,随后把其转换到阴影(光照)的坐标系下,做这一点的原因在Screen Space Shadow原理中已经讲的很清楚了。
// To calculate the UV coordinates for sampling the depth buffer,
// divide the pixel location by the render target resolution
// _ScaledScreenParams.
float2 uv = input.positionCS.xy / _ScaledScreenParams.xy;
// Sample the depth from the Camera depth texture.
#if UNITY_REVERSED_Z
float depth = SampleSceneDepth(uv);
#else
// Adjust Z to match NDC for OpenGL ([-1, 1])
float depth = lerp(UNITY_NEAR_CLIP_VALUE, 1, SampleSceneDepth(uv));
#endif
// Reconstruct the world space positions.
float3 worldPos = ComputeWorldSpacePosition(uv, depth, UNITY_MATRIX_I_VP);
#if UNITY_REVERSED_Z
// Case for platforms with REVERSED_Z, such as D3D.
if (depth < 0.0001) return half4(0, 0, 0, 1);
#else
// Case for platforms without REVERSED_Z, such as OpenGL.
if (depth > 0.9999) return half4(0, 0, 0, 1);
#endif
//shadow attenuation
float shadow = MainLightRealtimeShadow(TransformWorldToShadowCoord(worldPos));
最后得到的阴影应该是 max(shadow,cloud shadow)。
Renderfeature中我们把shader得到的结果最终输入到_ScreenSpaceShadowMapTexture,启用ScreenSpaceShadow相关Keyword,关闭其他的阴影Keyword,大功告成。
class ScreenSpaceCloudShadowsRenderPass : ScriptableRenderPass
{
...
CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.MainLightShadows, false);
CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.MainLightShadowCascades, false);
CoreUtils.SetKeyword(cmd, ShaderKeywordStrings.MainLightShadowScreen, true);
...
}
另外提一句,我们这种做法实际上是在不修改管线的前提下走了URP ScreenSpaceShadow 原本的流程,所以启用上面的RenderFeature后需要禁用URP的ScreenSpaceShadow Pass,而且原本URP的ScreenSpaceShadow Pass是无法对半透物体生效的,所以我们还需要额外添加一个Pass执行这个逻辑,在渲染半透明物体之前禁用相关Keywords,启用正常的阴影。如果项目组不介意修改管线这样最好,直接在原本的Screen Space Shadow Pass的基础上修改即可。