Unity3D中的水特现模拟雨天
这篇文章主要介绍"Unity3D中的水特现模拟雨天",在日常操作中,相信很多人在Unity3D中的水特现模拟雨天问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答"Unity3D中的水特现模拟雨天"的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
在写实类游戏制作时,常需要下雨场景的制作,由于日常生活中几乎所有物体都会被淋湿,所以下雨的制作其实需要考虑的方面有很多,我们将从粒子,材质,脚本控制等方面,分析一下应该如何渲染一个下雨的场景。
材质高光:
Unity的Standard Lighting中,使用GGX作为BRDF的高光算法,GGX具有拖尾感,可以较好的模拟潮湿物体表面的反光效果,首先我们来看一下这张故宫下雨时的照片(图侵删):
在普通游客的眼里,地面变得"湿滑"了,然而在渲染工程师的眼里,我们应该将这个"湿滑"的效果在PBR中分成四部分:Smoothness的提高,Specular Color的变亮以及GI Occlusion的降低和法线贴图比重的降低。首先,提高Smoothness是毫无疑问的,要让物体表面光滑首先要降低粗糙度,但是仅仅降低粗糙度是不行的,水停留在物体表面,这时反射光线的是水而不是物体本身,因此物体本身的漫反射会被降低,因此被淋水的物体看起来颜色会变深,根据物理反射定律,物体本身色彩无变化,即光线反射比例无变化的情况下,高光程度应提高,所以会表现出高光率提高的现象。同样,因为积水在物体表面的停留,物体受环境光影响会增大,这时我们应该适当降低Occlusion Map的影响,并降低法线贴图的偏移强度,这些在Unity的Standard Shader中都有提供,不需要手动写Shader,当然,如果要处理一个大场景,希望通过全局变量控制,还是应该手写shader进行精确优化的,因此,希望读者能够不依赖Shader Forge, Unity Surface Shader等辅助功能,独立编写基于PBR Shader。至于反射图像的处理,我们一般通过Reflection Probe, Screen Space Reflection & Planar Reflection等方法实现,这并不在本文讨论的范围内,我们将会在本专栏的其他文章中详细讨论。
接下来就是雨水的制作了,雨水本身的粒子效果制作虽然属于比较初级的粒子制作,甚至Assets Store上也有大量的资源,但是对美术制作能力有比较高的要求。如果像我一样,美术功底奇差无比,完全可以直接买一个效果实现#手动斜眼#,然后在粒子发射器上绑定一个脚本,使其始终在摄像机上方悬停,可以看到在示例中,我们使用粒子碰撞防止穿帮:
在摄像机第一视角效果如下:
到此,一个简单的雨水渲染就出来了,然而,整个画面看起来僵硬死板,这是因为我们没有表现出雨滴打在地上的效果,因此,我们需要模拟一个动态的法线贴图,让地面的法线"动起来",解决的方法有许多,最简单的方法就是序列帧,在CG软件(如Houdini,Substance Painter等)中制作序列帧并且渲染,然后在Unity中播放,这当然是一种比较简单的方法,但是同样也无法实现真正的实时与随机,我们这里则是使用Unity自带的CommandBuffer进行比较底层的图形绘制,实现随机的雨点特效。
学习过渲染管线基础的朋友都知道,Unity的摄像机其实并不是绘制RenderTexture的唯一方法,它只是封装的比较上层的方法,其实摄像机的工作流程就是(剔除->绘制网格->后处理)这三部分,无论是Forward path或是Deferred Shading path,亦或是Unity 2018最新提供的HDRP和LWRP,本质上都是这三部,区别仅仅在于,deferred shading会将光照作为后处理运算,而forward path会直接将灯光信息传入shader中进行光影运算并直接输出色彩,而我们这里并不需要动态剔除,只需要使用command buffer在一个指定的Render Target上进行GPU Instance,使用指定的材质绘制大量面片即可。有朋友问我为何不使用Unity 2017推出的CustomRenderTexture进行绘制,我认为,CustomRenderTexture只是给不会渲染底层的程序提供的一个上层封装,实际功能不如使用Graphics类或CommandBuffer直接进行绘制,后者虽然门槛较高但是功能更加强大,大概相当于美图秀秀和PhotoShop的关系(只是个人看法,别怼别怼)。
首先我们需要手动生成一个正方形Mesh,并将indexBuffer设置为四边形绘制,实现非常简单,代码如下:
由于我们是直接往屏幕上绘制的,所以根本不需要考虑ViewProjectionMatrix的问题,直接用NDC坐标(-1, 1)进行绘制即可,如果直接将这个mesh绘制到RenderTarget上,就是一个覆盖全屏的Mesh。
接下来我们要让这个mesh缩小并且随机分布在RenderTarget上,实现雨滴随机散落的效果,这时候就需要使用矩阵进行变换了,然而,雨滴数量众多,在本例中我们绘制了1023个雨点,所以很难依靠CPU进行迭代绘制,无论是计算还是Drawcall,消耗都是难以接受的。所以我们使用Compute Shader与Gpu Instance进行绘制,大幅度提高运算效率。
首先是Compute Shader,这里不赘述如何使用Compute Shader,只是提供Compute Shader的实现目标与过程。实现目标:生成1023个随机分配位置的矩阵并执行1023个计时器。为何要用计时器呢,原因很简单,当一个雨点散落到地上时,涟漪应该是越来越浅直到消失的,在涟漪消失时更新位置信息,使面片在另一个位置绘制。实现代码如下:
这里来解释一下这段代码的意义,MatrixBuffer是我们需要使用的1023个坐标矩阵,而timeSliceBuffer则是我们需要使用的计时器,其中float2的x值是计时器数值而y值是计时器速度。_DeltaFlashSpeed则是由脚本传入的每帧的更新,即Time.DeltaTime * X; 然后是两个LocalRand函数,使用魔数运算输出一个伪随机数。其中第一个函数会输出一个(-1, 1)区间的float2随机数,用于随机生成一个平面位置,而第二个函数则会输出一个(0, 1)区间的float随机数,用于生成一个随机的计时器速度。
下面的CSMain函数就比较简单了,当计时器数值>1时,归0并重新生成随机的速度与位置。根据线代基础,矩阵的M03, M13决定了xy轴的位置,M00,M11则决定了xy轴的Scale,而这里为了偷懒,果断省略了雨滴大小的随机,直接用同样大小的面片。
在ComputeShader中运算完毕后,就可以在脚本里获取计算的结果,并且使用运算结果进行绘制了,当然,在此之前我们需要先进行初始化:
这里初始化了Compute Shader,Compute Buffer以及需要用到的GPU Instance材质与高斯模糊材质(之后会用到)。
接下来就是调用Compute Shader并使用CommandBuffer进行绘制:
首先,指定renderTarget并初始化为(0.5,0.5,1)也就是标准的法线贴图格式,然后使用Compute Shader输出的矩阵进行Gpu Instance,最后经过高斯高斯模糊,使画面顺滑一些。
有了输入的计时器与输入的矩阵,就可以开始绘制波纹了,波纹绘制实际非常简单,直接用Alpha Blend实现减弱效果,用三角函数实现波动即可,直接上代码:
Shader "Unlit/Wave"
{
SubShader
{
Tags { "RenderType"="Opaque" }
ZWrite Off
ZTest Always
Cull Off
Blend oneMinusSrcAlpha srcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_instancing
#include "UnityCG.cginc"
#pragma target 5.0
#define MAXCOUNT 1023
StructuredBuffer timeSliceBuffer;
struct appdata
{
float4 vertex : POSITION;
float4 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
};
struct v2f
{
float4 vertex : SV_POSITION;
float timeSlice : TEXCOORD0;
float2 uv : TEXCOORD1;
};
v2f vert(appdata v, uint instanceID : SV_InstanceID)
{
v2f o;
UNITY_SETUP_INSTANCE_ID(v);
o.vertex = mul(unity_ObjectToWorld, v.vertex);
o.timeSlice = timeSliceBuffer[instanceID].x;
o.uv = v.uv;
return o;
}
#define PI 18.84955592153876
float4 frag(v2f i) : SV_Target
{
float4 c = 1;
float2 dir = i.uv - 0.5;
float len = length(dir);
bool ignore = len > 0.5;
dir /= max(len, 1e-5);
c.xy = (dir * sin(-i.timeSlice * PI + len * 20)) * 0.5 + 0.5;
c.a = ignore ? 1 : i.timeSlice;
return c;
}
ENDCG
}
}
}
shader非常简单,只是绘制了一个大致效果,最后生成的法线贴图效果如下:
可以看到,这张对密集恐患者非常友好的图片,已经有了深浅不一的涟漪花纹(虽然比较难看),我们将这张renderTarget放到地面上,效果如下:
可以看到,地面已经有了法线的涟漪,最近放假回国探亲,只能用家里的古董笔记本写文章,不过从粒子到动图绘制,在这台古董上也只需要4ms左右的运算时间,drawcall也因为gpu instance的原因并没有额外增加,可以说性能表现比较令人满意。
到此,关于"Unity3D中的水特现模拟雨天"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!