一.体积爆炸
游戏开发的艺术是现实主义和效率之间的巧妙平衡。爆炸尤其如此;它们是许多游戏的核心,但它们背后的物理特性往往超出了现代机器的计算能力。爆炸本质上只不过是非常热的气球;因此,正确模拟它们的唯一方法是将流体模拟集成到游戏中。我们可以想象,这对于运行时应用程序来讲是不可行的,并且许多游戏仅使用粒子模拟它们。当物体爆炸时,通常简单地实例化火焰,烟雾和碎片颗粒,这样它们一起可以获得较为真实地效果:体积爆炸。这个概念背后地想法时爆炸不像一堆粒子那样对待,它们正在发展3D物体,而不仅是平面2D纹理。
a.创建一个新的Shader命名为VolumetricExplosion
b.创建一个新的材质命名为VolumetricExplosionMat
c.把材质附属给球。我们可以创建一个球。
以下方法适用于标准Unity Sphere,但如果需要大爆炸,则可能需要使用高聚球。实际上,顶点函数只能修改网格的顶点,所有其他点将使用附近顶点的位置进行插值。较少的顶点意味着爆炸的分辨率较低。
d.对于这个Shader,我们还需要一个渐变纹理,该渐变纹理在渐变中具有我们爆炸所需要的所有颜色。我们可以使用GIMP或Photoshop创建如下截图的纹理。
e.一旦我们找好图片,导入Unity。然后在Inspector,确保它的Filter Mode被设置为Binlinear,WrapMode设置为Clamp。
这两个设置确保平滑采样斜坡纹理
h.最后我们需要一个噪声纹理,我们可以从网上下载,也可以在文章后面下载。
i.此效果分两步完成:用于更改几何体的顶点函数河用于赋予其正确颜色的曲面函数。
j.移动当前的属性并且添加下面属性’
Properties
{
_RampTex("Color Ramp",2D) = "white" {}
_RampOffset("Ramp offset",Range(-0.5,0.5)) = 0
_NoiseTex("Noise Texture",2D) = "gray" {}
_Period("Period",Range(0,1)) = 0.5
_Amount("_Amount",Range(0,1.0)) = 0.1
_ClipRange("ClipRange",Range(0,1)) = 1
}
k.添加相关变量用来Shader的Cg代码能够访问它们
sampler2D _RampTex;
half _RampOffset;
sampler2D _NoiseTex;
float _Period;
half _Amount;
half _ClipRange;
j.添加下面的输入函数
struct Input
{
float2 uv_NoiseTex;
};
k.添加下面的vertex 函数
void vert(inout appdata_full v)
{
float3 disp = tex2Dlod(_NoiseTex, float4(v.texcoord.xy, 0, 0));
float time = sin(_Time[3] * _Period + disp.r * 10);
v.vertex.xyz += v.normal * disp.r * _Amount * time;
}
l.加入下面的表面函数
void surf(Input IN, inout SurfaceOutput o)
{
float3 noise = tex2D(_NoiseTex, IN.uv_NoiseTex);
float n = staturate(noise.r + _RampOffset);
clip(_ClipRange - n);
half4 c = tex2D(_RampTex, float2(n, 0.5));
o.Albedo = c.rgb;
o.Emission = c.rgb * c.a;
}
j.我们在#pragma指令中指定了顶点函数。添加了nolightmap参数以防止Unity为爆炸添加真实的光照。
#pragma surface surf Lambert vertex:vert nolightmap
k.最后一步是选中材质,并在相对应的地方附加纹理。
l.这是动画材质,意味着它会随着时间的推移而发展。我们可以单击场景中的Scene窗口中的Animated Material来查看编辑器中的材质更改。
效果图如图所示
2.它是如何运行的
这种效果背后的主要思想是以一种看似混乱的方式改变球体的几何形状,就像在真实的爆炸中一样,一下屏幕截图显示了编辑器中爆炸的样子。我们可以看到原始网格已经严重变形。
顶点函数是在本章的挤出模型配方中引入的称为正常挤出的技术的变体。这里的不同之处在于挤出量由时间和噪声纹理决定。
小提示:当我们在Unity中需要随机数时,我们可以依赖Random.Range()函数。在shader中没有标准的方法获取随机数,因此最简单的方法是对噪声纹理进行采样。
达到这种效果并没有标准的方法。所以我们暂以下面的方法为例:
我们应该使用这些数字和变量,直到找到我们满意的运动模式。
内置的-Time[3]变量用于从Shader中获取当前时间,disp.r噪声纹理的红色通道用于确保每个顶点独立移动。sin()函数使顶点上下移动,模拟爆炸的混沌行为。然后,进行正常挤出;
v.vertex.xyz += v.normal * disp.r * _Amount * time
我们应该使用这些数字和变量,直到我们找到满意的运动模式。
效果的最后一部分是通过表面功能实现的。这里,噪声纹理用于从斜坡纹理中采样随机颜色。但是,还有两个方面值得注意。第一个是_RampOffset的介绍。他的使用强制爆炸从纹理的左侧或右侧采样颜色。在正值的情况下,爆炸的表面往往会显示出更多的灰色调-正是它在溶解时会发生什么,我们可以使用_RampOffset来确定爆炸中应该有火焰或烟雾。表面函数中引入的第二个方面是clip()的用法。clip()的作用是从渲染管道中剪切(移除)像素。使用负值调用时,不会绘制当前像素,此效果由_ClipRange控制,它确定体积爆炸的哪些像素将是透明的。
通过控制_RampOffset和_ClipRange,我们可以完全控制并确定爆炸的行为和溶解情况。
3.了解更多
上面的项目中呈现的Shader使球体看起来像爆炸。如果我们真的想要使用它,我们应该将它与一些脚本结合起来,以便充分利用它。最好的办法是创建一个爆炸对象并将其制作成预制件,以便我们可以每次需要时重复利用它。我们可以通过将球体拖回项目窗口来完成此操作。完成后,我们可以使用Instantiate()函数创建任意数量的爆炸。
然而,值得注意的是,具有相同材料的所有对象具有相同的外观,如果我们同时有多次爆炸,则不应使用相同的材料。在实例化新爆炸时,还应复制其材质。我们可以使用以下代码轻松完成此操作。
GameObject explosion = Instantiate(explosionPrefab) as GameObject;
Renderer renderer = explosion.GetComponent<Renderer>();
Material material = new Material(renderer.sharedMaterial);
renderer.material = material;
最后,如果要以逼真的方式使用此Shader,则应根据要重新创建的爆炸类型为其添加更改其大小的脚本_RampOffset和_ClipRange.
以上均基于Unity2018.1.0f源码可见我的GitHub:
https://github.com/xiaoshuivv/ShadersUnity2018.1.0f.git