前言
熟悉3d渲染的同学应该对SSR比较熟悉,又是深度图又是RayMarching的,相对来说,2d的反射就简单多了,虽然没有深度关系,但我们也想模拟出屏幕反射的效果,比如光滑的大理石地面,比如水面,那本文就以水面为载体,做一个动态的2D屏幕的反射效果。
原理
2D的反射,没有深度概念,那其实就是对反射物体的基于自己y方向的一个镜像,可是我们也不能简单的实例化一个自己,然后scale的y给-1,貌似是镜像了,但我们真正想要的,是这个镜像被渲染到接收反射物的物体上去,比如水面,而且不想除了水面的其他物体接受反射,比如陆地,所以我们需要利用后处理,把想要反射的物体单独绘制成一张图,然后对这张图一顿骚操作,最后输出到水面的材质里,作为水面渲染的一部分。
反射物体
第一步,就是要渲染反射物体,原理部分说了,2D反射就是基于反射物y方向的一个镜像,获得镜像的方法也可以是如原理所说的去实例化它,但实例化要占cpu,且自定义材质也会打断动态合批,所以我这里要用Mesh去做这件事,而不是Sprite。
首先,创建一个cocos自带的3d的mesh片-Quad,作为反射物的子物体,Scale要和反射物的width和height一致,如果你想反射效果拉长或缩短,那就调整它的height。给它一个自定义的Layer,我这里叫REFLECT,后面再说它,然后我们自定义个材质,赋予它反射的魔法。
我们要对它做y方向的镜像,其实就是在模型空间对它的顶点的y值取反,然后再转到世界空间,shader的顶点着色器代码如下:
如上所示,vec3(1, -1, 1) * a_position;就是对模型空间y值的取反。现在它就已经倒过来了,转到世界空间后,我们要给他个坐标偏移,因为锚点在图的中心,所以倒过来也是基于这个锚点翻转的,所以我们要让它向下偏移,偏移到脚对脚,一般来说,这个Offset是图的height。
片段着色器没啥好说的,就是把反射物的图给它,采样就行了。
这里要提一个mesh的好处,就是勾选USE INSTANCING,可以让我们的相同材质相同mesh的物体做gpuInstancing,而且不会打断其他sprite物体的动态合批。
反射相机
做完上一步,我们就获得了物体的反射效果,原理中说了,我们要把这些反射效果绘制到一张图中,然后对这个图一顿操作,所以这个图应该只有反射的倒影,那就需要一个Camera去专门做这个事情了。这也就是我上面为什么给我们的Quad一个Layer–REFLECT。记住主相机的Visibility不要加REFLECT。这时候,我们的反射相机大概会渲染成这样。
反射图拿到了,我们要对它骚操作了。
自定义反射效果
我们要模拟好看的反射效果,就要考虑你现实中看到的反射是啥样的,答案是跟接受反射的物体相关,比如地砖等相对光滑的物体,你能看到相对清晰的倒影,而稍微粗糙点的表面,可能看到模糊并且淡淡的倒影,而对水流的倒影,你还会看到它随着水流来回抖动。那我们就先定义它的清晰度和模糊度。
到这一步,我们就要用到自定义后处理了,有关自定义后处理的知识,cocos官方和个人的介绍文章很多,如果不了解的请自行查找,本文不做介绍。我们到目前为止已经拿到了反射图,自定义清晰度其实很简单,就是调整图的Alpha就行了。
_ReflectPPRt是反射图,_Intesity就是清晰度。
模糊呢,可能很多人会想到高斯,那太复杂了,毕竟是反射的模糊,不要求那么完美,简单的对周围像素求个均值就行。Shader如下:
cc_nativeSize,它是系统内置的vec4变量,xy是屏幕的宽高,zw就是宽高的倒数,也就是屏幕rt的纹素,_BlurSize是模糊度,越大越模糊,这里是对当前像素相邻四个角的像素取平均值。
Shader里我会拆分成2个Pass,就如同上面两个代码截图,第一个pass是没有模糊的,第二个pass是有模糊的。然后在后处理的代码里,我通过是否勾选模糊来判断走哪个Pass。
后处理编辑器如图:
Intensity就是清晰度,Open Blur是判断是否开启模糊,会影响上面走哪个Pass,Blur Size就是模糊度。
到这一步,我们自定义了反射图的模糊和清晰度,如果是地砖等静态的模糊,我们做的工作就差不多完成了,只需要把反射图赋给地砖做渲染就行了,而如果做水的反射,那我们还得做动态扭曲。
把它交给水吧
反射Camera经过自定义后处理,我们把它赋给自定义的RenderTexture,我起名叫reflect-rt,然后就把它交给水做最后的渲染吧。
我这里的水就是一个静图,本文不做水体效果,那得另开个文章,只做反射在水面的效果。话不多说,直接上代码,下面是水的Shader。
主要看USE_REFLECT_MAP这个宏定义的部分,这是水是否有反射效果的开关,而USE_REFLECT_NOISE这个宏,是反射是否开启抖动的开关。我先说说我们要干吗,我们要反射图抖动起来,那采样反射图时它的uv就得是抖动的,怎么让它的uv抖动呢,我们得用一张噪声图去影响这个uv,然后让噪声图动起来,然后就发生了连锁反应。
我们来看这行代码,
vec2 noiseUv = v_uv_reflect * _ReflectUvScale - cc_time.x * _ReflectNoiseSpeed;
v_uv_reflect是水的当前像素在屏幕上的uv,乘上_ReflectUvScale是控制这个uv的大小,_ReflectNoiseSpeed是噪声的速度,所以这行代码是搞噪声图uv的,让噪声图的uv动起来。然后用这个动态的uv采样噪声图_ReflectNoiseTex,得到了动态的噪声,我们只关心noiseCol的r值就行,它是从0到1的,_ReflectNoiseStrength是抖动的程度,最后我们把这个噪声加到反射图的uv–reflectUv上去,再采样反射图_ReflectTex,我们就得到了反射图的抖动效果。
最后,我们用反射图的Alpha值对水的颜色做一个混合,反射图的Alpha大家还记得是什么吗,就是反射图的清晰度啊,这个值越小,反射就越淡。
我们看一下效果。
这是清晰度为0.5,模糊度为1的效果,扭曲参数如下图:
看英文名字应该就能对号入座了。不多解释。
如果不要扭曲抖动,取消USE_REFLECT_NOISE勾选即可,那些抖动相关的参数也不用设置了,比如下面的地板反射效果
到此为止就是全部的内容了,如果有不明白的还请留言探讨,事例工程我后续会上架cocos store,届时会把链接贴到这里,如有需要,敬请关注。
往期文章:
2D光影系统-光源一全局方向光
2D光影系统-光源二Sprite光
2D光影系统-阴影
2D描边-内描边
2D描边-外描边