V7投稿|2D基于屏幕的反射

WeChate7177ce87adee2cc11b4048a52c72173

前言

熟悉3d渲染的同学应该对SSR比较熟悉,又是深度图又是RayMarching的,相对来说,2d的反射就简单多了,虽然没有深度关系,但我们也想模拟出屏幕反射的效果,比如光滑的大理石地面,比如水面,那本文就以水面为载体,做一个动态的2D屏幕的反射效果。

reflect1

原理

2D的反射,没有深度概念,那其实就是对反射物体的基于自己y方向的一个镜像,可是我们也不能简单的实例化一个自己,然后scale的y给-1,貌似是镜像了,但我们真正想要的,是这个镜像被渲染到接收反射物的物体上去,比如水面,而且不想除了水面的其他物体接受反射,比如陆地,所以我们需要利用后处理,把想要反射的物体单独绘制成一张图,然后对这张图一顿骚操作,最后输出到水面的材质里,作为水面渲染的一部分。

反射物体

第一步,就是要渲染反射物体,原理部分说了,2D反射就是基于反射物y方向的一个镜像,获得镜像的方法也可以是如原理所说的去实例化它,但实例化要占cpu,且自定义材质也会打断动态合批,所以我这里要用Mesh去做这件事,而不是Sprite。

首先,创建一个cocos自带的3d的mesh片-Quad,作为反射物的子物体,Scale要和反射物的width和height一致,如果你想反射效果拉长或缩短,那就调整它的height。给它一个自定义的Layer,我这里叫REFLECT,后面再说它,然后我们自定义个材质,赋予它反射的魔法。

WeChatf028ce9fb61cf0e5dca5f64b27ae064e

我们要对它做y方向的镜像,其实就是在模型空间对它的顶点的y值取反,然后再转到世界空间,shader的顶点着色器代码如下:

WeChat3f440981205368fee845d63795dbccbd

如上所示,vec3(1, -1, 1) * a_position;就是对模型空间y值的取反。现在它就已经倒过来了,转到世界空间后,我们要给他个坐标偏移,因为锚点在图的中心,所以倒过来也是基于这个锚点翻转的,所以我们要让它向下偏移,偏移到脚对脚,一般来说,这个Offset是图的height。

片段着色器没啥好说的,就是把反射物的图给它,采样就行了。

这里要提一个mesh的好处,就是勾选USE INSTANCING,可以让我们的相同材质相同mesh的物体做gpuInstancing,而且不会打断其他sprite物体的动态合批。

WeChatec3efd211c7ca6b383266c6583f67435

反射相机

做完上一步,我们就获得了物体的反射效果,原理中说了,我们要把这些反射效果绘制到一张图中,然后对这个图一顿操作,所以这个图应该只有反射的倒影,那就需要一个Camera去专门做这个事情了。这也就是我上面为什么给我们的Quad一个Layer–REFLECT。记住主相机的Visibility不要加REFLECT。这时候,我们的反射相机大概会渲染成这样。

WeChatf347c2305dc5fee397f25f8cfd4c2c00

反射图拿到了,我们要对它骚操作了。

自定义反射效果

我们要模拟好看的反射效果,就要考虑你现实中看到的反射是啥样的,答案是跟接受反射的物体相关,比如地砖等相对光滑的物体,你能看到相对清晰的倒影,而稍微粗糙点的表面,可能看到模糊并且淡淡的倒影,而对水流的倒影,你还会看到它随着水流来回抖动。那我们就先定义它的清晰度和模糊度。

到这一步,我们就要用到自定义后处理了,有关自定义后处理的知识,cocos官方和个人的介绍文章很多,如果不了解的请自行查找,本文不做介绍。我们到目前为止已经拿到了反射图,自定义清晰度其实很简单,就是调整图的Alpha就行了。

WeChat24365e3e5b9e0af4f20e74d3c31ea484

_ReflectPPRt是反射图,_Intesity就是清晰度。

模糊呢,可能很多人会想到高斯,那太复杂了,毕竟是反射的模糊,不要求那么完美,简单的对周围像素求个均值就行。Shader如下:

WeChat180ca6f1115fa065c91ae3d1b934d358

cc_nativeSize,它是系统内置的vec4变量,xy是屏幕的宽高,zw就是宽高的倒数,也就是屏幕rt的纹素,_BlurSize是模糊度,越大越模糊,这里是对当前像素相邻四个角的像素取平均值。

Shader里我会拆分成2个Pass,就如同上面两个代码截图,第一个pass是没有模糊的,第二个pass是有模糊的。然后在后处理的代码里,我通过是否勾选模糊来判断走哪个Pass。

WeChat2f038ac44a2afa48d620357004503648

后处理编辑器如图:

WeChat84456a36db1219ead2f92106425451d3

Intensity就是清晰度,Open Blur是判断是否开启模糊,会影响上面走哪个Pass,Blur Size就是模糊度。

到这一步,我们自定义了反射图的模糊和清晰度,如果是地砖等静态的模糊,我们做的工作就差不多完成了,只需要把反射图赋给地砖做渲染就行了,而如果做水的反射,那我们还得做动态扭曲。

把它交给水吧

反射Camera经过自定义后处理,我们把它赋给自定义的RenderTexture,我起名叫reflect-rt,然后就把它交给水做最后的渲染吧。

我这里的水就是一个静图,本文不做水体效果,那得另开个文章,只做反射在水面的效果。话不多说,直接上代码,下面是水的Shader。

WeChat7aaabc3638af9adfa12b62633533adf1

主要看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大家还记得是什么吗,就是反射图的清晰度啊,这个值越小,反射就越淡。

我们看一下效果。

rr

这是清晰度为0.5,模糊度为1的效果,扭曲参数如下图:

WeChat7335e46dc83df906a1dc32205df15330

看英文名字应该就能对号入座了。不多解释。

如果不要扭曲抖动,取消USE_REFLECT_NOISE勾选即可,那些抖动相关的参数也不用设置了,比如下面的地板反射效果

WeChat6f5bca7ee3fd3dd09e100f8c153cf8e5

到此为止就是全部的内容了,如果有不明白的还请留言探讨,事例工程我后续会上架cocos store,届时会把链接贴到这里,如有需要,敬请关注。

往期文章:
2D光影系统-光源一全局方向光
2D光影系统-光源二Sprite光
2D光影系统-阴影
2D描边-内描边
2D描边-外描边

15赞

炫酷吊炸天 :+1:

事例工程已上架cocos store:2D基于屏幕的反射,有需要的童鞋请自取

这文章写的好呀
排版也很养眼

多谢夸奖 :pray: