Notifications
Article
在Unity 后处理栈实现逼真的下雨效果
Published a year ago
1.1 K
0
在Unity 后处理栈实现逼真的下雨效果
在上次Cinemachine课程上,有不少同学问到场景中逼真的下雨效果是如何实现的。其实这个效果是一个自定义的后处理效果,不过和通常的平面后处理效果有所不同的是,雨水有距离感,有光感,也有从天而降的倾泻感 ,所以显得很真实。接下来,本文就会通过一个简单的示例,介绍这个效果的实现原理。
场景搭建
在Neon项目的场景中,我们看到当雨水穿越光线的效果。为了模拟出这样的效果,我们在示例场景中也需要添加相应的光源和雾效。需要说明的是,该场景中使用的体积光使用了Unity提供的开源组件VolumeLighting。场景搭建的主要步骤如下:
1)在MainCamera中添加VolumetricFog组件
首先添加VolumeFog组件,可以方便查看后面添加的体积光的范围。添加好后,可以在组件中调整全局雾密度等相关参数。
2)添加区域光和管状光,并调整灯光的范围。
在VolumetricLighting文件夹里面可以找到AreaLight和TubeLight的预制件,可以直接加入到场景中。下图是一个AreaLight的例子,我们可以调整他的范围以及光线强度等参数。
场景搭建完成后,我们就可以看到下图所示的效果了。
2. 实现自定义后期处理类
Unity最新的后处理栈V2版本提供了多种后期处理效果,包括Bloom,AutoExposure,MotionBlur,ColorGrading等多种特效。使用也非常的方便,主要就是PostProcessVolume,和PostProcessLayer两个组件。具体使用可以参考快速上手指南。除此之外,我们还可以很方便的添加自定义的后处理效果,这里有一个自定义的场景灰度的例子。下面我们就来实现我们的后期处理类吧,主要有下面两步。
1) 实现自定义特效的设置
首先需要创建一个类PPRainFX 并继承于 PostProcessEffectSettings。然后重载函数IsEnableAndSupported,主要是为了确保在场景中存在PPRain实例的时候特效才能被使用。
PPRain是特效的主要实现脚本。由于该下雨特效的参数我们都放到PPRain中去配置了,所以在此处,我们并没有定义设置相关的参数。
2) 实现自定义特效的渲染
还是先创建一个类PPRainFXRenderer,继承于PostProcessEffectRenderer<PPRainFX>。然后重载Renderer函数,在该函数中,我们主要是把PostProcessRendererContext中保存的摄像机,渲染源,渲染目标,以及命令缓存传递给PPRain的Render函数。后面我们会具体解释Render函数的实现。
下面这张图是PPRainFX.cs的内容,包含了后期处理类的完整实现。
3. 渲染下雨动画
下雨动画的实现,很自然的想法就是把雨水画到屏幕上,然后利用UV动画实现下雨效果。
可是在3d的场景中,随着镜头的旋转,雨水的视觉效果会相应的变化,尤其是当镜头向上的时候,你会觉得雨水是从一个很远的点发射出来,如下图所示。
为了实现这样雨水从天而降的倾泻感,我们把雨水的贴图贴到一个类似于纺锤体的模型上。
说完基本原理,接下来我们就讲一下具体的实现吧,主要有以下两步。
1)创建PPRain类,实现参数配置以及模型绘制功能
先看下目前所需要的一些设置参数,如下图所示,变量rainShader, rainMesh, rainTexture用于绑定着色器,模型,贴图,rainData0是用于调整UV动画的参数。
再看一下Render函数,该函数实现了模型的绘制,会被之前说的后处理栈的render函数调用。在该函数中,首先会设置shader的参数,然后通过drawMesh绘制模型。xform定义了模型的坐标变换,可以看到模型会随着摄像机移动,也就是说可以一直在镜头中看到下雨到效果。
2)创建PPRainShader,实现UV动画。
下图是PPRainShader的frag函数,注意uv坐标计算部分的代码,我们使用_UVData0对uv实现比例和位移变换,从而控制雨滴的大小以及x和y方向的位移速度。经过变换后的uv0就是实际用来贴图采样的坐标。
4. 实现雨水的距离感
通过上面的步骤,我们已经基本实现了下雨的动画效果。但是为了更加真实,我们还需要更近一步,让雨水有距离感。也就是说近距离的雨水会更加明亮,远距离的会变暗,而且会被近距离的物体所遮挡。为了实现这样的效果,我们主要需要以下三步:
1) 在shader中获得当前像素点的深度信息用于判断遮挡关系
如下图所示,这是frag函数中的一段代码,通过内置的_CameraDepthTexture变量可以获取到当前Camera的深度信息,并使用UnityCG中定义的Linear01Depth函数将其转换为线性范围,然后再乘以远剪裁面的值进行放大便于后续使用。最后得到的linearViewDepth就是我们需要的当前像素的深度值。
2)计算雨水的深度
如下图所示,我们其实在制做贴图的时候通过g分量来表示了雨水的深度,layerDistances.x 和layerDistances.y分别表示雨水的最近和最远距离。所以最后我们可以计算出雨水的深度值layerDistance。
3)计算雨水明暗度以及处理遮挡
继续看上图中depthscale的计算,当物体的深度(sceneViewDepth)超过了雨水的深度(layerDistance)一定范围后,depthscale为1,显示雨水,反之为0,雨水被遮挡。在明暗变化方面,我们用了贴图的r分量(rainAndDepth.r),实际意义和之前的rainAndDepth.g是一样的。最后我们得到的output.a实际上就是包含了雨水的明暗变化以及遮挡关系。
5. 实现光感
最后,我们还需要考虑当雨水穿过场景中的体积光时,颜色的变化。这部分的实现在InjectedLight函数中,如下图所示。

首先,我们需要计算在雨水在体积雾中的深度值z。在z的计算公式中,我们看到了_CameraFarOverMaxFar, _NearOverFarClip这个几个变量,它们是在VolumetricFog中设置到shader中的全局变量。我们把z的计算公式稍微化简后得到下面的公式
z = (linear01Depth * Camera.farClipPlane - VolumetricFog.nearClip) / (VolumetricFog.farClip - VolumetricFog.nearClip)
从化简后的公式中,我们可以看出z最后被转化到在VolumetricFog剪裁空间坐标。有了坐标后,我们就可以通过Tex3D函数取得体积光中的颜色值了。_VolumeScatter也是在VolumetricFog中定义的保存体积光的3d贴图。
通过上面几个步骤,我们大概了解了如何在后处理栈中实现较为真实的下雨效果。我们看到了Unity的后处理栈不光有很多内置的效果,也可以很方便的加入自定义效果。也看到了Unity 提供的开源插件VolumetricLight可以帮助我们实现多种体积光效果。在雨水效果的真实感方面,我们考虑了视角,距离,光感等因素,但其实还可以做更多,比如更多层次感,风力的模拟等等。希望通过本文的抛砖引玉,让大家都有兴趣去实现自己的后处理效果。
示例工程地址:
https://github.com/chengliang-u3d/PPRain

Tags:
ChengLiang
Product EvangeList - Educator
7
Comments