概述
再次说明一下,最近的几篇“练习项目”,都是在学习这个Github项目,使用URP重新实现一遍。对于本篇的内容,我也纠结了很久,纠结要不要写本篇博客。因为本篇的内容没有什么知识点,只是介绍了一些比较受限的阴影实现。最后还是决定要写下来,让这个“练习项目”完整一点。
一、用面片实现阴影
说起来也是缘分,几年前刚毕业进入一家游戏公司,那时候做的是横版2D游戏,游戏内角色的阴影就是用这种方式实现的。后来也有几款游戏延续了这种实现方式。
这种方式很简单,用一个圆形的面片代表阴影,放在要产生阴影的物体下方,跟随该物体。至于阴影面片的高度,当时的做法是:从产生阴影的物体往下面的地面发射射线,检测到地面,得到的高度就是阴影面片的高度。
这种方式比较受限,只适用于平整的地面。
也不需要写新的Shader,使用Sprite Renderer组件就能实现效果。
二、平面阴影
之前在听ILRuntime作者在Unity线上技术大会的演讲时,提到了平面阴影的概念(19:30开始)。
目前自己在做的游戏使用了Unity自带的Shadowmap实现的阴影。在Unity里面看起来效果还行,但是打包到手机上之后,效果很差,会有各种锯齿、抖动。后来按照上面的演讲,试着实现了一版平面阴影,发现阴影的质量真的非常高,在移动端也没有问题。但是发现平面阴影与场景烘焙出的阴影,在融合上有些问题,就暂时没有采用平面阴影。
下面简单介绍一下平面阴影的实现。主要参考了知乎上面的几篇博客,感谢博主的分享。
与上面的演讲的区别是,这里并没有使用Renderer Feature来实现平面阴影,而是使用了多Pass来实现平面阴影。
第一个Pass用来渲染物体的本身,第二个Pass用来渲染阴影。
渲染阴影的思路:考虑在二维平面系中,已知地面的高度\(h\),光线的方向\((L_x,L_y)\),顶点的世界坐标为\((V_x,V_y)\),那么要求的就是顶点在地面的投影点\((P_x,P_y)\)。由于投影点在地面上,所以\(P_y = h\)。从顶点到投影点的向量为\((P_x-V_x,P_y-V_y)\),由于是光线经过顶点的投影,所以该向量和光线的方向平行,所以: \[ \frac{P_y-V_y}{P_x -V_x} = \frac{L_y}{L_x} \]
所以可以得到: \[ P_x = \frac{L_x*(P_y-V_y)}{L_y} + V_x \]
\[ P_y = h \]
效果如下图:
当然,如果想改成Renderer Feature实现平面阴影也很简单,只要把阴影Pass的LightMode改成自定义值,然后在ForwardRenderer里面添加新的Render Objects即可,然后在Filters设置合适的Layer Mask和Light Mode Tags即可。注意,本项目的URP版本是8.2.0,不用的版本可能有些名称不同。
三、球体阴影
不知道用“球体阴影”描述是否合适。这种实现方式主要适用于场景中只有球体产生阴影的情况。场景中其它的物体都“知道”球体的坐标和半径,从而可以判断是否在球体的阴影里面。
如下图所示,点L为光源位置,点A为场景中的物体的顶点,B为球形的圆心。点A到球形圆心的向量和点A到光源的向量之间的夹角为\(\alpha\);C点是垂直于AB向量的圆周上的一点,AB和AC之间的夹角为\(\beta\)。那么,判断点A是否在球形的阴影里面,就是判断\(\alpha\)是否小于\(\beta\)。
得到的效果如下:
四、总结
上面介绍的几种阴影的实现方式,可能并不是很通用,但可能对于某种类型的游戏,或者某种情况下,上面的实现也会是一种比较优的方案。这里,只是简单介绍了一下实现的方式,有需要可以继续深入研究。
参考
- [1] 使用顶点投射的方法制作实时阴影