火焰材质



概述

       3D游戏中,火焰特效是一种3D游戏中的一种常见特效,通常是使用粒子播放器播放序列帧或者直接使用粒子模拟,在本文中将实现一个区别于这两种方法的火焰效果(如下图),这个火焰的效果来源于一个朋友推荐给我的文章,是一个老外的作品(演示效果和源代码),效果非常不错,还模拟了风的效果,美中不足的是这个shader只支持target 3.0以上,鉴于此我做一些修改,并做了一定的优化,右图就是本文实现的效果,使之能支持target 2.0。

unity材质和SP unity材质效果_unity材质和SP


unity材质和SP unity材质效果_unity材质和SP_02





原理

       原作者火焰实现的效果主要使用了三张贴图(如下图),一张颜色梯度图,一张噪波图,以及一张alpha图;

       其shader主要通过几个个步骤实现火焰效果:

  • 将噪波贴图做了三次不同的uv位移动画采样,并将采样的颜色值叠加作为uv值,对颜色梯度图采样,实现火焰的流动效果以及火焰的絮状效果
  • 同时在vert函数中使用tex2Dlod函数(此函数需要target3.0以上支持)采样到噪波图的颜色值,对模型的顶点进行扰动,实现火焰的膨胀震动效果
  • 使用一个传入的向量结合模型的顶点色对模型顶点做偏移效果,实现火焰随风摆动的效果。
  • 第一步采样的噪波图颜色值做为uv值对alpha图采样作为火焰的alpha值,,当然还需要乘以一个边缘光的值使火焰的边缘透明。

       我实现的效果思路和这个基本一样,只是将第二步的顶点扰动效果,使用了unity内置的函数对模型顶点做的扰动,以保证对target 3.0以下版本的支持。

unity材质和SP unity材质效果_unity材质和SP_03




shader实现

       在开始实现火焰效果时首先需要建一个火焰的模型,如下图所示,其中刷了顶点色,需要使用到顶点色的红色通道和alpha透明通道。

unity材质和SP unity材质效果_unity材质和SP_04


       为了便于调整火焰的颜色以及形态,需要开放了一些参数:

  • _WindFreqScale:控制模型波动的频率;
  • _WindPower :xyz分量控制风的方向,w分量控制风的强度;
  • _FlameColor  :  xyz分量控制火焰的颜色比重以及大小火焰扰动的效果,w分量控制火焰流动的速度。

       下面就从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 ) );     
}



       该函数可以生成循环波动效果,通过控制输入的参数,以及对输出值的修改,可以模拟出比较自然的随机波动效果,用于Unity Terrain系统植物随风摆动的效果,效率据说要比正弦或者余弦函数要高(未求证),在这里我把它直接用于模拟火焰律动的效果,代码如下:


//将风的方向向量转换到世界坐标空间,并使用顶点色的红色通道控制
		_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;



        vert函数最后一段代码,是用来控制模型的边缘透明的,和边缘光材质方法是一样的,只不过在这里是用来控制透明度,并且这里还使用顶点色的alpha通道控制模型的透明,代码如下:


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;



        frag函数承接从vert函数传过来的参数,先对噪波图采样,得到的颜色值转化成颜色梯度图的uv值,求出火焰的颜色以及透明质,主要代码如下:


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的代码了。


下载链接

       火焰材质源代码