UnityShader实例18:火焰材质

火焰材质



概述

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






原理

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

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

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

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





shader实现

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



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

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


下载链接

       火焰材质源代码







  • 9
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
代码在文件 Properties { _Color ("Main Color", Color) = (1,1,1,1) _MainTex ("Diffuse (RGB) Alpha (A)", 2D) = "white" {} _SpecularTex ("Specular (R) Gloss (G) Anisotropic Mask (B)", 2D) = "gray" {} _BumpMap ("Normal (Normal)", 2D) = "bump" {} _AnisoTex ("Anisotropic Direction (RGB)", 2D) = "bump" {} _AnisoOffset ("Anisotropic Highlight Offset", Range(-1,1)) = -0.2 _Cutoff ("Alpha Cut-Off Threshold", Range(0,1)) = 0.5 } SubShader{ Tags { "RenderType" = "Opaque" } CGPROGRAM struct SurfaceOutputAniso { fixed3 Albedo; fixed3 Normal; fixed4 AnisoDir; fixed3 Emission; half Specular; fixed Gloss; fixed Alpha; }; float _AnisoOffset, _Cutoff; inline fixed4 LightingAniso (SurfaceOutputAniso s, fixed3 lightDir, fixed3 viewDir, fixed atten) { fixed3 h = normalize(normalize(lightDir) + normalize(viewDir)); float NdotL = saturate(dot(s.Normal, lightDir)); fixed HdotA = dot(normalize(s.Normal + s.AnisoDir.rgb), h); float aniso = max(0, sin(radians((HdotA + _AnisoOffset) * 180))); float spec = saturate(dot(s.Normal, h)); spec = saturate(pow(lerp(spec, aniso, s.AnisoDir.a), s.Gloss * 128) * s.Specular); fixed4 c; c.rgb = ((s.Albedo * _LightColor0.rgb * NdotL) + (_LightColor0.rgb * spec)) * (atten * 2); c.a = 1; clip(s.Alpha - _Cutoff); return c; } #pragma surface surf Aniso #pragma target 3.0 struct Input { float2 uv_MainTex; float2 uv_AnisoTex; }; sampler2D _MainTex, _SpecularTex, _BumpMap, _AnisoTex; void surf (Input IN, inout SurfaceOutputAniso o) { fixed4 albedo = tex2D(_MainTex, IN.uv_MainTex); o.Albedo = albedo.rgb; o.Alpha = albedo.a; o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_MainTex)); fixed3 spec = tex2D(_SpecularTex, IN.uv_MainTex).rgb; o.Specular = spec.r; o.Gloss = spec.g; o.AnisoDir = fixed4(tex2D(_AnisoTex, IN.uv_AnisoTex).rgb, spec.b); } ENDCG } FallBack "Transparent/Cutout/VertexLit" }[/code]
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值