返回列表
查看: 803|回复: 0

[转载] 基于ShadowMap的场景静态阴影

[复制链接]

900

主题

900

帖子

3417

积分

超级版主

Rank: 8Rank: 8

积分
3417
发表于 2018-10-11 20:34:17 | 显示全部楼层 |阅读模式
发帖
封面:
前言

手游中多数情况,场景的灯光信息会由场景美术预先通过烘焙的方式生成好,这样虽然带来了性能上的高效,但是无法达到一些表现的需求,例如通过lightmapmap提前烘焙好的场景阴影,当角色步入阴影当中的时候无法达到一个逐渐进入的效果,入下图
a190-1.gif (图1)烘焙好的场景阴影贴图

a190-2.gif (图2)预期的效果

介绍

有了需求,接下来就是制定解决方案,同时还要考虑移动端性能限制。Shadowmap是现在主流的阴影解决方案,包括Unity的阴影也是基于此,但是我们项目中不需要对动态物体采用这种方式产生阴影,只需要对静态的场景物体,即设置为static的产生这种阴影区域,所以最终我们采取在进入场景前,提前缓存好一张shadowmap,由动态物体接收这个阴影来达到图2的效果。

  • ShadowMap原理
  • 从产生阴影的光源(在通常项目中为Directional Light)放置一个相机,渲染一次场景,获取深度贴图。
  • 场景内物体渲染时,将自身世界坐标转入刚才的阴影相机的投影坐标得到统一坐标系下的深度值然后和对应深度贴图中保存的深度值比较,(在d3d11下)如果小于保存的深度值代表此像素处于阴影。
  • 获取ShadowMap
  • 有了原理,我们接下来第一步就是要保存场景的ShadowMap。首先是建立一个Camera用来生成ShadowMap,这个camera必须和场景的主光源,通常为Directional Light,有一样的Forward朝向(Directional Light的话,需开启正交相机)。Camera的视窗决定了那些区域能产生阴影,通常有两种方案来决定视窗范围:

(1)FitScene

顾名思义,阴影摄像机视锥覆盖到场景内所有角色可行走范围即可,区域外由于角色走不到,不会产生阴影交互,生成也是浪费。比较适合小场景。对于只计算静态阴影,只需生成一次即可。缺点是当场景较大的时候,摄像机视锥很大导致ShadowMap纹理很大或者分辨率较低。


a190-3.png
(2)FitView

阴影摄像机视锥覆盖观察摄像机视锥,即阴影相机只渲染主观察相机能看到的地方,相对FitScene,FitView的阴影区域生成利用率更高,因为处于可行走区域但是不被主观察相机渲染的地方是不会被生成到ShadowMap中的,但是主相机的参数只要一改变,对应阴影摄像机的视锥参数也会改变,所以需要实时调整阴影摄像机的视锥以匹配观察摄像机的视锥。

a190-11.png
由于项目和移动端性能考虑,我们项目采用了FitScene的方式来生成ShadowMap。在场景搭建好之后,美术或者策划只要在场景内适当位置摆放好一个用来生成的ShadowMap的摄像机即可,我们这里为其编写了一些工具,可简化美术或者策划端的工作。

a190-4.png
只要提供行走区域和主光源即可算出FitScene的包围盒

有了正确的阴影摄像机参数设置,我们就可以在游戏开始前对场景生成ShadowMap。这里我们采用Camera自带的方法RenderWithShader,渲染一次到rendertexture,然后即可关闭这个相机。下面给出ShadowMapCapture(阴影捕获Shader)主体。
a190-5.png

通过将float depth = i.depth.x / i.depth.y我们可以获取到某个像素在阴影摄像机裁切空间的深度,然后通过UnityCginc中提供的EncodeFloatRGBA方法将一个Float值存储在float4值当中,在frag函数中这个float4被当做颜色最终输出。这里要注意的一点是裁剪空间里OpenGL的z是[-1, 1],而D3D是[0, 1],而EncodeFloatRGBA方法只接受[0..1)范围的输入值,所以这里我们还得考虑手机平台的OpenGL,将depth值进行一次映射。实际效果如下图。

a190-6.png
选中阴影摄像机的预览图

选中阴影摄像机的预览图通过shader获取的ShadowMap纹理 a190-7.png
  • 应用ShadowMap


有了ShadowMap纹理之后,我们需要做的是如何让我们的角色shader支持。上面这张ShadowMap纹理中保存的是在阴影摄像机裁切空间坐标系下的Z深度,所以要进行比较,我们角色需要将自身像素的坐标转换到该坐标系下比较才有意义,好在Unity提供了很方便的坐标系转换API。下面给出C#里如何获取坐标系转换的代码。
a190-8.png

在Shader中获取到到转换坐标系后,就可以在frag函数中将像素的worldPos转换成到ShadowMap纹理UV并采样到该像素对应在ShadowMap中的深度值。然后纹理中存储的深度和该像素的深度做比较可以得出该像素是否处于阴影中,然后自己做相应的修改,这里要注意的是在d3d和移动端OpenGl的坐标系不同,会导致Z值判断不同,用UnityCG.cginc中定义的UNITY_REVERSED_Z来判断即可。下面给出ShadowMapReceiver(阴影接受Shader)中Frag中相关代码
a190-9.png

最终,对于场景内静态区域的阴影,我们可以得到如下图的效果。
a190-10.png

来源:游戏蛮牛

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回列表 客服中心 搜索