Unity3D教程:实现水面渲染(一)
水面渲染在很多游戏项目中都会碰到,下面就分四篇文章去给大家介绍在Unity3D中水面渲染的实现方法,现在介绍的水面渲染(一)。
一、前言
本文旨在与大家一起探讨学习新知识,如有疏漏或者谬误,请大家不吝指出。
二、概述
在讲之前,我们先来看下最终渲染效果是什么样子的:(示例程序见附件)
我们来对水效做一下简单的效果分解。
一般地,像很多网上所说的那样,水效=反射+折射+菲涅尔效应。这个公式是最简单的,也是最基础的。当然,到真正开发水效时需要考虑的东西就比较多了,在上面这张截图里,我考虑到的效果有:水波的模拟、反射、折射、菲涅尔效应、水的散射、大气的散射以及辉光。做过水效的同学可能应该清楚,除了以上的效果,还有Caustic(译作焦散)没有考虑进去。另外水底和水面的渲染在公式、参数和渲染实现上都有或多或少的差别,不能一概而论。这里我们只讨论水面的渲染,至于水底的,大同小异,但是需要准备另外一套shader,这里暂且不提。
下面会将各个效果单独实现,再整合成到一起,每个效果都会开一个文章说明。另外需要注意的是,水波的模拟我之前有篇文章有提过,这里就不写了。链接在这里:
http://gad.qq.com/article/detail/7159284
Gerstner波是一种方法来实现水波模拟;另外还有基于FFT的,可能就需要有真实水波数据来经过FFT分解成若干正弦余弦波,这个我倒没有做过;还有就是基于noise的,将多个noise图根据分形噪声思想进行叠加,也可以实现出很漂亮的波形。在ShaderToy上有一个例子写的非常棒,大家可以看一看:
https://www.shadertoy.com/view/Ms2SD1
三、水面反射的实现
关于水面反射,一般的做法是使用镜面反射,高级一点的可能会用屏幕空间反射技术。我们这里简单点,用镜面反射,效果也是可以的。
关于镜面反射部分,之前也有写过相关的文章发表在GAD平台,读者可以参考一下,这里不再赘述:
http://gad.qq.com/article/detail/7157554
http://gad.qq.com/article/detail/7157769
第一篇是镜面反射基本原理,第二篇是用来处理第一篇中提到的bug。
另外需要考虑到的是,镜面反射是假设水面是一个平面,这导致了我们用镜面反射取到的反射图并不随着水波的流动而变动。为了让效果更逼真,这里使用了一个小trick,即让镜面反射进行UV扰动。具体做法如下:
1 2 3 | float2 offsets =worldNormal.xz*viewVector.y; float4 reflectionColor =tex2D(_ReflTexture, screenUV+offsets*_Params.x); |
做法很简单,在进行纹理查询的时候对UV进行扰动,扰动的参数跟法线和视角高度相关,即让反射贴图沿着法线方向扰动。_Params.x是一个控制扰动大小的参数。
完成镜面反射后的效果图应该是这样的:
一张是未进行UV扰动,一张是使用了UV扰动的。
这里需要注意的第二个问题是,进行反射贴图渲染的相机我设置的是Solid Color模式,并且背景色设置为纯黑,Alpha通道也是0.为什么这么做?我们在上面讲过,反射的真正实现方式是靠reflect函数,只不过为了简化效果和操作,我们使用了镜面反射,这是一种无可奈何的方式,因为并不能找到十全十美解决这个问题的办法。但是对于水面反射天空盒来说,我们有更真实可信的方式,即使用reflect函数。只要我们能获取的天空盒子的Cubemap以及水面法线,这其实做起来比上面的办法更简单,效果更好。
1 2 3 | float3 reflUV =reflect( viewVector, worldNormal); float3 skyColor= texCUBE(_Skybox, reflUV); |
在获取到skyColor后,单单只是天空盒,反射的效果是这样的:
然后只要将镜面反射和天空盒反射根据镜面反射贴图的Alpha通道进行混合就OK了:(这就是为什么反射相机的Alpha通道要为0)
1 | reflectionColor.xyz= lerp(skyColor, reflectionColor.xyz, reflectionColor.a); |
最后的反射效果是这样的:
代码将在这一系列文章都写完后给出。大家也可以试着自己先去实现。有可能大家会觉得效果不好,当然,这是肯定的,毕竟我们才迈出了第一步,下一篇文章我们来通过法线贴图给水面增加点细节。