您的位置:首页 > 其它

Parallax Cubemap for Planar Reflection - Remember Me反射系统学习笔记及实现

2016-11-07 16:17 543 查看
前段时间美术给提了个需求,希望游戏里面的水面反射能够准确的对应岸上的物体,因为游戏的新场景是江南小镇夜景,各种灯笼等人造光源比较多,水面很多并且形状复杂,如果倒影对不上的话会很难看。以前的方法都是使用一个反射球放到水面的中心处,然后直接将Cubemap贴到水面上就凑活了,因为手机上跑实时平面反射实在是压力太大,尤其是游戏的场景做得比较复杂,而且因为Forward Rendering的原因,SSR又不好用上。

在想解决方法的时候突然想起Remember Me的反射系统,以前粗略的看过相关的技术分享文章,但并没有仔细研究,对于其中反射系统的印象仅停留在使用Cubemap上 = = 这次仔细研读后觉得正好运用在手游上:专门适合Forward Rendering,使用低开销的Baked Cubemap,能够基本保证平面反射效果与原物体对应。将这个系统应用在自己的游戏后效果确实不错,虽然不能与实时平面反射算法的效果相比,但是其效果也已经基本满足美术的要求,并且开销很低,能够在当前的手游中使用。下面是做出来的反射效果截图。



另,Remember Me的分享中还包括反射系统与PBR的结合,这个在此不作分析。

下面分析下这个算法以及具体实现用遇到的一些问题,关于某些具体算法细节这里可能讲的不太详细,尤其是Remember Me里面已经写得非常详细的地方,所以,这里主要对于原文一些说的不是那么明白的地方,以及具体实践中遇到的问题做下解释。

这个算法主要解决了两个问题(也不能算是一个算法了,可以说是模块或系统)。一个是如何使用Cubemap这种提前烘焙好的贴图(因为开销低)来实现镜面反射效果。一个是如何在大场景中使用多个反射球以保证各个地方的反射效果精度。

先说后一个,就是如何在大场景中使用多个反射球。先说说为什么要多个反射球(Cubemap),这个还是比较好理解的,因为Cubemap的分辨率是有限的嘛,比如我们手游的Cubemap分辨率一般是64x64x6这个样子(已经不小了= =),这种级别的分辨率哪怕烘焙离的比较近的小物体的时候都很有可能丢失掉,就不用提游戏中看离八丈远的物体反射了。另一个方面,如果是一个小池塘这样的形状相对规则,面积不大(或者使用个大点分辨率的Cubemap)的反射平面的话,可以只使用一个反射球。

那么到底如何使用多个Cubemap呢,直觉基本是摄像机/主角走到什么地方,使用离那个地方最近的Cubemap来做反射,离两个反射球差不多一样远的情况下就Blend一下两个反射球。相关的算法其实很多,在我们游戏中直接用了Remember Me里面的那种,简单有效。具体算法可以直接参考下面给的链接,里面有完整的伪代码及解释,说的很清楚,照抄就行了= =输入就是所有Cubemap及摄像机的位置,输出就是需要融合的Cubemap球及每个球所占的权重。如果Cubemap在场景中很多的话可以考虑使用八叉树等场景管理算法加快速度。

再说说本篇文章的重头戏,Parallax Cubemap,也就是让Cubemap能够应用在镜面反射上。先说说直接给镜面/水面贴上Cubemap为什么不行,这是因为Cubemap的默认原理是反射无限远的场景信息的,也就是天空+远景,并不能准确地反射出近景物体。



如图所示,看A点的时候,Cubemap会反射A'的内容,当你往前走,看B点的时候,正确的应该是反射B'点的内容,但是Cubemap会自动以B点为中心做采样,这样结果还是反射的是A'的内容,所以这种镜面反射的结果是错的,实际应用的时候效果会很别扭,移动的时候发现镜面上反射的东西跟着移动,只有远景才是对的(想象下,因为是无限远,所以不管怎么走,都可以将当前反射点当做Cubemap的中心点)

下面这张图是引用Remember Me的图,可以看出反射对不上了。



那么如何能够正确的反射local object呢,自然是取到正确的Cubemap的指向坐标,即texCUBE(Cube, direction)的这个direction。这个坐标到底是啥?



视角向量反射R可以求出来,然后在场景中套一个box作为模拟右边那个黄色真实物体的边界,也就是Cubemap采集信息的边界,这样R与box的交点P就知道了(求向量与矩形交点),这个P是正确的反射采样点,那么又知道Cubemap的坐标C,这样P-C就是采样Cubemap时需要的向量值。
类似这样的算法有很多种,原理都是使用一个geometry proxy(比如box, sphere等)来模拟local object的位置,以此找到正确的采样坐标。

其实说到这里,已经能够实现想要的结果了,就是对于每个需要Blend的Cubemap,做一遍上述过程,其中C这个坐标需要每次重新输入,然后根据校正过后的向量来采样Cubemap,将结果乘以各个Cubemap的权重,再相加就行了。这样已经比实时平面反射快很多了,但是还可以继续改进。

首先,这个过程是在pixel shader中完成的(因为需要根据每个点的法线来取到R向量),这样每个像素都要这么校正计算一遍,并且,反射的屏幕所占面积越大越费。另外,由于是在具体反射物体中完成的校正,那么使用box还是sphere还是其他的geometry proxy就和具体布景方式耦合在了一起,非常麻烦,同时也只能使用这种简单模型的作为校正基准,复杂一点的场景就不好弄了。

所以,这个过程经过逐步优化(具体过程请查看Remember Me),最后是这么处理的:将具体矫正过程从反射物体本身的shader中提出来,和最开始的Cubemap blend放到一起,也就是在Blend的过程中,校正下采样。这样对于具体反射物体来说,shader不需要改变,也并不需要知道做了校正。在校正采样的过程中,使用实时平面渲染的方式,即,根据反射平面翻转摄像机(参考下图),然后将Cubemap的贴图贴在Geometry
Proxy上,再用翻转过来的摄像机render一下,在渲染Main Pass中,反射物体使用的就是这个翻转过得摄像机render的贴图作为反射贴图(这个时候已经是实时平面渲染那种的2D Texture了,不是Cubemap了,所有反射物体的反射算法都是用实时平面渲染那一套方案)。同时,这个Geometry Proxy也不限于box什么的简单模型了,任意Mesh都可以(凹多边形会有点问题,这个后面会具体说)。



关于这个算法的具体解释,Remember Me的那篇文章有些许问题,它还是像原来的算法一样,让这个翻转的摄像机朝不同方向渲六遍,以组成Cubemap,其实已经不用了,直接按照实时平面渲染的那种算法渲一遍就好了,投影矩阵使用Oblique Projection,这个在GPU Pro 4这本书中有相关解释。

下面补充一个我个人对这个算法的理解:为什么可以使用任意Mesh作为Geometry Proxy呢,可以想象为如果你把这个Mesh无限细化,最终就可以细化为最原始的需要反射的模型(就像上图中那个黄色的物体),Geometry Proxy之所以叫做Proxy,其实就是为了简化这个复杂模型,所以根本不用局限为box或者sphere什么的,并且,如果仅仅局限于最简单的模型的话,校正的效果会非常不好,因为原模型,布景有可能非常复杂奇葩,不是一个box就能模拟的了的(比如我们游戏的江南水乡小镇,这叫个复杂,远比老外那些方方正正的实验室,走廊什么的复杂多了)。所以,因为在这里使用的其实就是实时反射算法,完全可以理解为我就是在实时反射一个Mesh到Render
Texture上,这个Mesh我可以使用最原始的复杂模型,也可以使用LOD后的超简化模型,甚至可以LOD到一个方块。然后拿这个反射好的Render Texture直接按照实时平面反射的算法那样用在Main Pass的反射物体上(关于实时平面反射算法,这里就不具体讲了,自研引擎的同学应该都会写吧,使用Unity的同学请参考Unity的水面例子,里面用的就是这个算法)。

所以换句话说,这个校正算法现在变成了变种的实时平面反射算法,和传统的实时平面反射算法相比,区别就在于怎么去渲那个Render Texture,由于很多实时平面反射算法同样使用了LOD来渲来提升速度,和Geometry Proxy原理一样了,所以关键点最后仅仅在于如何将原始的Cubemap校正后,正确的贴到Geometry Proxy上。与以前一样,就是如何得到正确的Cubemap采样Vector,上图已经画的比较明显了,就是P-C,C已知,P并不用和以前一样逐个像素点计算(想算也没法算,还没到Main
Pass呢,哪来的像素点),而是直接使用Geometry Proxy的Vertex来当P就好,然后P-C用光栅化一插值,全都有了。这个过程在Vertex Shader中就可以完成,省多了。

不要忘了,我们还需要Blend多个Cubemap,那么这个Blend过程怎么与渲反射需要的Render Texture结合呢?并不需要Remember Me里面说的那种多遍Pass,然后每遍Pass结果Alpha Blend到当前的Render Texture上去。将所有Cubemap扔到shader中去,pixel shader中直接采样多个,按权重相加就好,省了draw
call也省了alpha blend,同时解决了另一个问题:如果你的Geometry proxy是凹多边形(我觉得其实很有可能)的话,Alpha Blend会让Geometry Proxy自己和自己发生混合,没法控制一个mesh各个三角面片的渲染顺序,穿帮非常明显。另,如果是凹多边形的话,在拐角处有可能会看到接缝,不过不是很明显,只有纯镜面的时候才看的到。

在具体实践的过程中还发现一个问题,就是Geometry Proxy到底需要怎么布置,按照上面画的那几个示意图,可能凭直觉的话,感觉应该是每个Cubemap对应一个Geometry Proxy,这个Proxy应该围绕在Cubemap周围,然后换一个Cubemap就换一个对应的Geometry Proxy来渲。这个想法其实有点问题,首先并没有换一个Cubemap的概念,虽然确实会存在离一个Cubemap很近的话,全部的反射信息都是使用这个cubemap的(也就是权重为1),但是应该理解为场景中所有Cubemap都在起作用,只不过大部分的作用是0(权重为0),这样也就没有说摄像机/主角是在某个Cubemap的势力范围下了,所以不管在什么情况下,所需要渲染的Geometry
Proxy都应该是一样的(按照实时平面渲染的概念也很好理解,你不能说我移动一下,反射物体Geometry Proxy就变样子了),虽然在Remember Me里面,将Geometry Proxy与Cubemap关联起来了,但也是每个Geometry Proxy都一样以避免错误。虽然其用意应该是为了更加简化Geometry Proxy,即,只渲染当前能看到的反射物体,看不见的不用管,这样做确实得将Cubemap和Geometry Proxy一一对应,但是在实际的操作中,如果这样做的话,首先需要每个Geometry
Proxy重叠的部分完全一样,如果不一样的话,一切换主Cubemap,就会发生画面跳变,其次,必须保证当前正在使用的Geometry Proxy能够满足视野范围内看到的任何东西,这个在复杂场景中非常难做到。所以最后我直接就是一个全场景的Geometry Proxy,直接用当前水面的面片往上一拉就行了(水面已经提前按照岸边切好),然后再修一修减减面什么的,这样Geometry Proxy也就上百面,还是可以接受的。

所以最后的消耗就是实时渲染了一个平面反射:一个draw call,百面的物体,shader极简(vertex中做个减法,pixel中采样两三次Cubemap),在手游中还是可以接受的。

Remember Me除了上面的内容,还在GPU Pro 4里面补充添加了一些内容,包括动态物体(比如主角)如何反射,cubemap采样不到的小物体怎么反射等,有兴趣的可以参考。

参考:
1. https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/ 2. GPU Pro 4, Practical Planar Reflections Using Cubemaps and Image Proxies

9747
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐