3D游戏中,火焰特效是一种3D游戏中的一种常见特效,通常是使用粒子播放器播放序列帧或者直接使用粒子模拟,在本文中将实现一个区别于这两种方法的火焰效果(如下图),这个火焰的效果来源于一个朋友推荐给我的文章,是一个老外的作品(演示效果和源代码),效果非常不错,还模拟了风的效果,美中不足的是这个shader只支持target 3.0以上,鉴于此我做一些修改,并做了一定的优化,右图就是本文实现的效果,使之能支持target 2.0。
原作者火焰实现的效果主要使用了三张贴图(如下图),一张颜色梯度图,一张噪波图,以及一张alpha图;
其shader主要通过几个个步骤实现火焰效果:
我实现的效果思路和这个基本一样,只是将第二步的顶点扰动效果,使用了unity内置的函数对模型顶点做的扰动,以保证对target 3.0以下版本的支持。
在开始实现火焰效果时首先需要建一个火焰的模型,如下图所示,其中刷了顶点色,需要使用到顶点色的红色通道和alpha透明通道。
为了便于调整火焰的颜色以及形态,需要开放了一些参数:
下面就从Vert函数讲起,首先是处理噪波贴图的uv,在这里分了三层,每层的uv比例缩放大小和移动速度都不一样,以丰富火焰效果的层次,并且在这里使用了_FlameColor的w分量控制uv流动的速度,代码如下:
VerttoFrag o;
o.uvCoord = v.uvCoord;
half3 scrollSpeedY = ((-0.41 * _Time.y),(-0.96 * _Time.y),(-2.36 * _Time.y))*_FlameColor.w;
half scrollSpeedX = _Time.z*_FlameColor.w;
// half3 scales = (1, 2, 3);
o.transUV1 = v.uvCoord.xy * 1 ;
o.transUV1.y = o.transUV1.y + scrollSpeedY.x ;
o.transUV1.x = o.transUV1.x + scrollSpeedX ;
o.transUV2 = v.uvCoord.xy * 2 ;
o.transUV2.y = o.transUV2.y + scrollSpeedY.y ;
o.transUV2.x = o.transUV2.x + (-0.8 * scrollSpeedX) ;
o.transUV3 = v.uvCoord.xy * 3 ;
o.transUV3.y = o.transUV3.y + scrollSpeedY.z ;
o.transUV3.x = o.transUV3.x + (0.37 * scrollSpeedX) ;
然后处理火焰模型的顶点律动效果以及随风摆动的效果,原作者是通过对噪波图的颜色采样获得随机律动的效果,在这里我使用了unity的内置函数SmoothTriangleWave( ) ,这个函数的函数原型在TerrainEngine.cginc里实现(源代码如下):
float4 SmoothCurve( float4 x ) {
return x * x *( 3.0 - 2.0 * x );
}
float4 TriangleWave( float4 x ) {
return abs( frac( x + 0.5 ) * 2.0 - 1.0 );
}
float4 SmoothTriangleWave( float4 x ) {
return SmoothCurve( TriangleWave( x ) );
}
//将风的方向向量转换到世界坐标空间,并使用顶点色的红色通道控制
_WindPower.xyz =normalize( mul((float3x3)_World2Object,_WindPower.xyz));
_WindPower.w *=v.vertColor.r;
//控制风的律动频率
float windTime = _Time.y * _WindFreqScale;
float4 vWaves = (frac( windTime.xxxx * float4(1.975, 0.793, 0.375, 0.193) ) * 2.0 - 1.0);
vWaves = SmoothTriangleWave( vWaves );
float2 vWavesSum = vWaves.xz + vWaves.yw;
//bellyGradient控制火焰膨胀的梭状形体
fixed bellyGradient = pow(v.uvCoord.y * (1-v.uvCoord.y),1.7);
//使火焰模型的顶点有规律的震动,和随风摆动
v.vertex.xyz += normalize(v.inNormal.xyz) * bellyGradient * vWavesSum.xyx;
v.vertex.xyz += _WindPower.xyz * _WindPower.w * (vWavesSum.y+0.5)*(bellyGradient+0.2);
o.outPos = mul(UNITY_MATRIX_MVP,v.vertex);
//displayV用于传入frag函数,控制火焰随形体膨胀而忽明忽暗的效果
o.displaceV = vWavesSum.y*vWavesSum.x;
half4 worldPos = mul(_Object2World , v.vertex );
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - worldPos.xyz);
fixed3 normalDir = normalize(mul(_Object2World ,fixed4(v.inNormal.xyz,0) ).xyz);
o.edgeAlpha = pow(abs(dot(normalDir,viewDir)*1.7),2) * v.vertColor.a;
fixed2 noiseColor1 = tex2D(_NoiseTex,i.transUV1.xy).xy;
fixed2 noiseColor2 = tex2D(_NoiseTex,i.transUV2.xy).xy;
fixed2 noiseColor3 = tex2D(_NoiseTex,i.transUV3.xy).xy;
noiseColor1.y *= _FlameColor.x;
noiseColor2.y *= _FlameColor.y;
noiseColor3.y *= _FlameColor.z;
half2 noiseCoords = noiseColor1 + noiseColor2 + noiseColor3;
noiseCoords += i.uvCoord.xy;
fixed4 baseColor = tex2D(_ColorTex,clamp(noiseCoords,0.05,0.97));
fixed4 texColor = baseColor + baseColor * (i.displaceV - 0.2) * 0.2;
fixed Alpha1 = tex2D(_AlphaTex,i.uvCoord.xy).r;
fixed Alpha2 = tex2D(_AlphaTex,clamp(noiseCoords,0.05,0.98)).r;
texColor.a = Alpha1 * Alpha2 * i.edgeAlpha ;
return texColor;
这个火焰效果总体来说还可以,但是不大适合顶视的效果,容易穿帮,这个是由模型的形状决定的;相对于原作者的代码,做了不少的优化,在差不多效果的情况下,本文的火焰效果可以支持target 2.0,当然还有进一步的优化空间,比如可以把颜色梯度图的尺寸从256*256改成2*256,并设置成真彩色,这么可以减少很多的贴图大小,同样对alpha图也可以做相对应的处理,另外一种方式是可以把alpha图放到噪波图其中的一个通道,可以节省一张贴图的使用,当然与此相对应的就得修改shader的代码了。
火焰材质源代码