当前位置: 代码网 > 移动>游戏>手游 > 基于屏幕的2D实时反射

基于屏幕的2D实时反射

2024年08月04日 手游 我要评论
前言 提到屏幕空间的反射,熟悉 3D 渲染的同学应该第一时间会想到 SSR,需要深度图、法线、RayMarching。 相对来说,2D 的反射就简单多了,不需要复杂的算法,我们就能实现反射效果。 比如光滑的大理石地面、水面等。 本文就以水面为题,做一个动态的2D屏幕的反射效果。 原理 2D的反射,不需要深度相关信息,其实就是对反射物体的基于自己y方向的一个镜像,可是我们也不能简单的实例化一个自己,然后scale的y给-1。这种方式虽然...

前言

提到屏幕空间的反射,熟悉 3d 渲染的同学应该第一时间会想到 ssr,需要深度图、法线、raymarching。

相对来说,2d 的反射就简单多了,不需要复杂的算法,我们就能实现反射效果。

比如光滑的大理石地面、水面等。

本文就以水面为题,做一个动态的2d屏幕的反射效果。

原理

2d的反射,不需要深度相关信息,其实就是对反射物体的基于自己y方向的一个镜像,可是我们也不能简单的实例化一个自己,然后scale的y给-1。这种方式虽然也能实现镜像,但是会让场景的渲染开销成倍增加,并且达不到我们想要的细节效果。

但我们真正想要的,是这个镜像被渲染到接收反射物的物体上去,比如水面,而且不想除了水面的其他物体接受反射,比如陆地,石头等非水面区域。

所以我们需要利用后处理,把想要反射的物体单独绘制成一张图,然后对这张图进行处理,最后输出到水面的材质里,作为水面渲染的一部分。

反射物体渲染

第一步,创建一个 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 合批。

反射相机

做完上一步,我们就获得了物体的反射效果,原理中说了,我们要把这些反射效果绘制到一张图中,然后对这个图一顿操作,所以这个图应该只有反射的倒影,那就需要一个 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 是判断是否开启模糊,会影响上面走哪个 passblur 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动起来。
  • _reflectnoisetex 噪声图,动态的uv采样得到动态的噪声,我们只关心noisecol的r值就行,它是从0到1的
  • _reflectnoisestrength 是抖动的程度,最后我们把这个噪声加到反射图的uv--reflectuv上去
  • _reflecttex 反射图

这样,我们就得到了反射图的抖动效果。

最后,我们用反射图的alpha值对水的颜色做一个混合,反射图的alpha大家还记得是什么吗,就是反射图的清晰度啊,这个值越小,反射就越淡。

我们看一下效果。

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

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

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

到此为止就是全部的内容了,如果有不明白的欢迎留言探讨。

往期文章

  • 2d光影系统-光源一:全局方向光
    • https://forum.cocos.org/t/topic/156363
  • 2d光影系统-光源二:sprite光
    • https://forum.cocos.org/t/topic/156390
  • 2d光影系统-阴影
    • https://forum.cocos.org/t/topic/156536
  • 2d描边-内描边
    • https://forum.cocos.org/t/topic/156861
  • 2d描边-外描边
    • https://forum.cocos.org/t/topic/156878

欢迎关注 cocos 引擎官方公众号
官方信息、技术干货、行业资讯

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com