Unity下阴影的实现--模仿<模拟城市>全局阴影 模拟城市>
游戏中为了表现更多的细节,实时阴影可能是我们开发过程中常见的需求。随着现在手游质量的越来越高, 很多游戏中也都把阴影作为一个比较大的亮点来展示。比如两年前上线的<功夫熊猫>, 大规模的为全屏角色采用动态阴影,在当时看起来效果还是很赞的。
当然根据不同类型的游戏类型不同, 在Unity上采用的实时阴影的方法也各有不同, 常用的大致有如下几种。
- LightMap : 优点:对于一些静态的场景这个方案可能是性价比最高的一个方案, 缺点:只能是静态烘培,对一些大的场景LightMap体贴占内存可能会很大。
- Projector :优点:可以很自由的控制, 缺点:绘制影子的对象无法Batch
- ShadowMap:优点:获取深度图之后可以对更大范围的物体进行投影 缺点:实现代码量较多
那回到我们如上标题的目标诉求上来说,如果想实现模拟城市的类似效果, 我们该如何选择 ?
需求分析
既然我们的目标是类似<模拟城市>的效果, 那最好的办法就是看下它是如何实现的。这里我们借助AdrenoProfiler对手机上的SimCity<模拟城市>进行抓帧取样, 看下具体的实现过程。这里细节不介绍, 想了解这个工具的看下这里。
在其中绘制地面的一个Shader中我们看到这样一段:
1 | lowp float shadowTest(highp vec3 texCoord) |
有经验的开发者看到这段代码就能猜出它的实现方式,没错确实是Shadowmap。结合下面我们在导出的Texture中的这样图, 基本确认无疑(大家注意看图片的边缘留了一个像素左右的白边, 后面讲原因):
这张是当前视窗下的深度图, 根据这个图上渠道的RGB对渲染的Fragment做颜色计算, 也就是下面这段(关键代码):
1 | void main(void) |
实现过程
那大致明白了目标游戏的实现方法了, 我们得在Unity下实现类似的流程。大致细分为如下这几步:
- 如何获屏幕内容的深度图 ?
- 如何对深度图进行采样计算阴影面积 ?
问题1:如何获屏幕内容的深度图 ?
Unity下获取深度的方式有很多种,有很多文章可以参考。这里不细讲,主要讲下这几点考虑:
1, 使用Unity自身提供的_CameraDepthTexture可以很方便的获取到深度图,但是开启深度渲染会导致场景内DrawCall翻倍,场景内物件较多这个方法不考虑。
2, 用两个摄像机结合Camera1.RenderTexture.DepthBuffer + Camera2.RenderTexture.Color组合起来, 最后将结果Blit到屏幕上,这个没有尝试。
3, 使用独立一个摄像机,在灯光坐标系下对整个场景渲染深度图
我这个采用的是第三种方法, 关键的实现代码如下:
对获取深度信息的摄像机挂载脚本, 使用RenderWithShader在Update中获取场景实时渲染的深度图。这里要注意计算最小视椎体可以提高阴影的质量, 还可以看下对RenderTexture的Anti-Aliasing进行设置:
1 | v2f vert(appdata_base v) |
在C#中对主摄像机的视椎体进行计算, 然后初始化一个最小的Orthogrphic 远近Clip Plane。主要是UpdateClipPlane 和 UpdateDepthCamera函数中有实现。
在C#下将光源坐标ViewProjection坐标系传递到Shader中, 方便做采样时坐标系的转换:
1 | Matrix4x4 world2View = m_DepthCamera.worldToCameraMatrix; |
对需要投影的屏平面或物体获取深度图像素信息之后进行计算:
1 | //Comppute in light space. |
这里是个小技巧, 如果不做Discard border 的处理会出现如下的效果:
Unity中对RenderTexture的Clamp是如下定义:
1 | TextureWrapMode.Clamp |
因为RenderTexture的Wrap Mode选择了Clamp之后, 如果碰到超出UV范围的边界获取颜色, 就会默认取最边缘的像素。之前版本中提供的SetBorderColor已经飞起, 所以无法像OpenGL中设置texture的边缘颜色, 也是上面提到<模拟城市>的深度贴图对边缘都做了一像素的留白效果也也正是这个方法。这里我们做个小技巧, 超过边缘范围的默认depth = 1。
最终效果如下:
遗留问题
还有很多细节的问题篇幅问题没有在这里讨论, 后续计划再详细展开几篇对阴影这块的实现,这里感觉还是蛮有料的。例如:Planar Shadow、Shadow Volume的实现,已经Shaodw Ance、Peter Panning等问题。
PS:实现的过程中也再次发现已经眼高手低的问题, 很多细节都是一知半解。通过这些实现感觉可以把一些流程理清楚很多,一点点积累把!
Talk Is Cheap !