反射效果(Reflections)实现技术综述
最近在做反射相关的工作,而由于此前对反射技术的现状处于半懂非懂的状态,因此准备尝试对现有的反射技术进行归纳与总结,尝试从中找到一种适用于当前项目的反射方案。
请各位知悉,下面所列举的方案并未严格按照发明年代来排序,给各位阅读带来不便敬请谅解。
Horizontal Mirroring Reflections
沿着反射平面的边界线,将上方的像素结果按照镜像投射到下方的反射平面位置。多用在2D视角的游戏中。
Cubemap Reflections
方案特点:
- 优点
1.1 对于远景物体具有较好的反射质量,性能消耗低,可以通过mipmap来实现反射效果的抗锯齿
1.2 实施简单 - 缺点[graphics runner][3]:
2.1. cubemap捕获的所有物件的距离都被假设为无穷远,因此无法正确表现近景物件之间的相互遮挡与穿插关系(如下图中的反射球实际上是与地表穿插的,而现在的效果看起来却是浮在半空中)
2.2. 因为无穷远假设对于近景物件的不合理性,因此反射效果上也会存在一定的扭曲
2.3. 不支持移动视差,不支持高阶反射(如对反射物体的反射),动态物件反射丢失
2.4 Cubemap之间的过渡也会有问题
cubemap的采集是在固定某一点完成的,只有当相机与此采集点重合的时候,得到的反射结果才是最正确的,但是平时我们使用cubemap的时候,都是直接将相机所在点当成cubemap的中心点也就是cubemap采集点来看待的,当cubemap采集的数据都是天空之类的远景数据,这个假设是可以近似认为是成立的,但是如果使用cubemap来表示房间内的数据,这个假设就会有问题了。如果使用这个假设来计算反射效果,就会发现随着相机的移动,反射效果也是同步移动的,跟真实表现有较大的差距,这就是上面cubemap缺点中列举的不支持移动视差的问题,针对这个问题,有人提出了一种基于视差修正的cubemap(parallax corrected cubemaps)方案,这种方案与普通的cubemap方案在反射效果的表现对比可以参考这个视频。
Billboard Reflections
与cubemap不用,billboard针对的是近景物件的反射效果,这种方法通过为每个待反射物体生成一个billboard proxy(如果追求更高效果,可能还需要一个depth billboard),之后在运行时,直接使用这个billboard替代原始物体参与到reflection射线的相交检测当中来。
实现步骤,总的来说可以归结为如下几步:
1. 绘制reflection billboard,对于每个需要参与到反射中的物体,都需要为之绘制一个反射视角billboard,对于这个物体,我们会为之规划一个imposter,这个imposter对应的是特定角度下的2D图像。
1.1 相机位置确定,根据这个物体的boundingbox的8个顶点,我们计算得到反射视角相机的位置——8个顶点的中心往后拉伸一个距离(相机lookat点对应着8个顶点的中心)。
1.2. 计算屏幕空间boundingbox,将上一步中的boundingbox的8个顶点投影到相机空间,并计算出投影后的8个屏幕空间的点组成的boundingbox
1.3. imposter形状确认,将屏幕空间boundingbox反投影回世界空间,得到物件的imposter范围
1.4. billboard绘制,将此物件绘制到一个RT上,RT对应的贴图将被用作imposter的贴图。此处的绘制使用的是正交相机,绘制前RT会用0 alpha进行清理,绘制时使用的是1 alpha。
2. 反射物体绘制,反射效果的绘制比较简单:对于每条需要计算反射颜色的射线,将之与imposter数组中的每个元素进行相交检测,如果存在碰撞,就将交点的颜色取出来并根据alpha值与已有颜色(比如cubemap采样数据)进行混合(这里需要考虑远近关系,每次只取最近的有颜色的数据),并输出结果。
实现效果,还是用之前的场景,效果如下图所示:
方案特点:
- 优点:
1.1 计算快,消耗低,支持各种复杂形状的reflectors,支持高阶反射(通过在反射性的imposter中存储法线,在相交检测的时候,触发第二条射线进行新一轮的相交检测实现) - 缺点:
2.1 不能实现移动视差(motion parallax)
2.2 无法表现复杂模型之间的穿插效果
2.3 不支持self-reflection
2.4 每个物体都需要提前准备一个imposter,流程稍微繁琐(当物件与反射平面之间的位置关系发生变化时,还需要对imposter进行重绘)
这里的问题可以通过depth-imposter与non-pinhole imposter方案来解决,其中depth-imposter可以修正移动视差与模型穿插问题,而non-pinhole imposter则是在depth-imposter方案基础上再叠加一层额外信息,允许我们获取到某个固定视角下对应物体的全貌(而depth-imposter由于遮挡关系的存在只能看到前景数据)
Screen Space Reflections(SSR)
屏幕空间反射通过对正常渲染输出的framebuffer中的颜色数据reprojection到反射相机空间来实现反射效果,相对于按照反射相机从头进行一遍渲染,这种方案的实施效率无疑要更高一些,但是其质量也有所下降。SSR方案有如下一些特点[3]:
- 优点
1.1 不需要美术同学做额外的场景编辑工作
1.2 性能相对于一遍完全的渲染流程有着较大的提升
1.3 对内存消耗的要求较低 - 缺点
2.1 只能捕获到屏幕空间中存在的像素数据,超出屏幕边界的信息(反射视角可见)将会丢失
2.2 相机移动或者物件移动时,将会导致结果闪烁
除了单纯的SSR方法,还有人尝试过SSR+Cubemap的方案,这种方案有如下的特点[3]:
- 优点
1.1 实施简单
1.2 性能优秀 - 缺点
2.1 未能完全解决cubemap导致的物体悬浮的问题
2.2 未能完全解决cubemap动态物件丢失的问题
2.3 cubemap之间的切换问题依然存在
2.4 SSR在动态场景下的闪烁问题依然存在
2.5 如果cubemap是通过scene capture得到的,那么每当场景发生变化时,都需要重新capture,对开发迭代流程有一定的约束(这应该是cubemap的通用问题)。
Pixel Projected Reflections
Planar Reflection方案
这是UE原生平面反射(Planar Reflection)的实现方案。通过使用反射视角对场景进行一遍绘制来得到平面反射的效果,之后根据反射材质上各点的法线朝向对反射贴图进行采样。这种方式可以得到不错的反射结果,但是还是有着一些不足之处:
- 优点:
1.1 具有较好的反射效果,不会出现物件悬浮,动态物件缺失等问题 - 缺点
2.1 需要按照反射视角对场景进行一遍渲染,消耗要高于SSR等后处理方案
2.2 曲面以及粗糙表面的反射效果其实是通过法线扰动近似求取得到的,跟实际的反射结果有一定的偏差,只是大多数情况下这个偏差并不容易察觉到。
Cone Tracing方案
Cone Tracing方案通过将场景体素化(使用GPU完成),并在体素化后的场景中计算各个体素的光照遮挡信息(这些信息会存入3D贴图中),之后在计算某点的光照的时候通过在给定方向上按照逐渐增长的步长对3D贴图的mipmap进行采样来得到全局光照数据,用这些光照数据累加的结果来近似模拟此点的反射结果。
这种方案有如下的一些特点:
- 优点
1.1 没有物体悬浮的瑕疵
1.2 不会出现动态物体的缺漏
1.3 表现鲁棒,不会随着相机或者物体的变化而闪烁
1.4 不需要额外的编辑工作 - 缺点
2.1 需要在运行时对场景进行体素化(算法较为复杂,消耗较高)
2.2 对内存有较高的要求(体素数据存储)
2.3 在场景更新以及cone tracing计算的时候,都需要消耗较高的GPU
---未完待续
参考文献:
[1]. Reflected-Scene Impostors for Realistic Reflections at Interactive Rates - EUROGRAPHICS 2006
[2]. Graphics runner
[3].Real-time Reflection in Mafia III - GDC2018