火焰在我们的日常生活中十分常见,我们都知道火焰有内外焰之分,在现实世界中内外焰的颜色区别并不是十分明显,而在大多数游戏尤其是卡通渲染风格的游戏中火焰的内外焰颜色有明显的区别(我十分喜爱的一款国产游戏:蜡烛人)
接下来我们来实现一个简易的火焰shader
先上最终效果图
整体思路:分别渲染火焰的内焰和外焰,叠加后得到最终效果
首先我们来实现火焰的主体也就是内焰的效果,为了表现出火焰扭曲且流动的特性,我们需要使用一张噪音纹理和一张控制扭曲的纹理,关键步骤是使用扭曲纹理来采样这张流动的噪音纹理,我们这里选择使用扭曲纹理的rgb通道中的两个通道来对噪音纹理进行采样:
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _NoiseTex);
o.uv2 = TRANSFORM_TEX(v.uv, _DistortTex);
return o;
}
fixed4 distort = tex2D(_DistortTex, i.uv2) * _Distort;
fixed4 noise = tex2D(_NoiseTex,fixed2((i.uv.x + _Time.x * _SpeedX) + distort.g ,(i.uv.y + _Time.x * _SpeedY) + distort.r));
这里为防止偏移速度过快使用_Time.x控制流动速度,_Distort控制扭曲的强度
我们同样可以用两张噪音纹理完成这种效果,先采样一张噪音纹理,对其加上偏移和强度控制后在用于采样第二张噪音纹理,可以达成类似效果此处不再演示
得到的效果如下:
好的,已经初步有扰乱和流动的感觉了,但是火焰不可能是全屏的,应当还有渐变效果,这里我们自然可以想到使用一张从上到下由黑变白的渐变纹理与其叠加从而产生一种渐变的效果
float4 gradientBlend = lerp(float4(1,1,1,1), float4(0, 0, 0, 0), (i.uv2.y + _Height));
noise += gradientBlend;
float4 flame = float4(noise.rgb, saturate(noise.a * _MainAlphaControl));
注意,这里的叠加次数越多,得到的效果就更加平滑,这里我选择叠加两次,由于多次叠加,叠加后用saturate将alpha钳制,使用_Height来控制渐变的范围,_AlphaControl用于控制边缘
得到效果如下:
恩不错,现在乘上我们的颜色就能得到内焰的效果了,现在考虑外焰效果,我们可以将外焰考虑为只是在内焰的边缘向外延伸一段距离的火焰,所以可以将flame的alpha值加上一个阈值后再减去原本的而得到
float4 flameedge = saturate((flame + _Edge) * _EdgeAlphaControl) - flame;
flameedge.a = 1 - flameedge.a;
这样我们就得到了外焰的效果,乘上外焰颜色再和内焰相加即可得到最终的效果了,由于叠加的渐变纹理alpha自身就是渐变的,所以我们最终得到的颜色也带有渐变的效果
完整的shader如下:
Shader "Myshader/FireTest"
{
Properties
{
//噪音纹理流动速度
_SpeedX("SpeedX", Range(-10,10)) = 1
_SpeedY("SpeedY", Range(-10,10)) = 1
_NoiseTex("Noise Texture", 2D) = "white" {}
_DistortTex("Distort Texture", 2D) = "white" {}
//控制整体的alpha值
_MainAlphaControl("MainAlphaControl", Range(1,20)) = 1
_EdgeAlphaControl("EdgeAlphaControl", Range(1,50)) = 1
//控制渐变纹理
_Height("Height", Range(-4,10)) = 1
//火焰边缘大小
_Edge("Edge", Range(0.02,0.3)) = 0.1
//控制扭曲强度
_Distort("Distort", Range(0,1)) = 0.2
_MainColor("MainColor",Color) = (1,1,1,1)
_EdgeColor("EdgeColor",Color) = (1,1,1,1)
}
SubShader
{
Tags{ "RenderType" = "Transparent" "Queue" = "Transparent" }
LOD 100
Zwrite Off Cull Off
Blend SrcAlpha OneMinusSrcAlpha
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD1;
float2 uv2 : TEXCOORD2;
float4 vertex : SV_POSITION;
};
float _SpeedX, _SpeedY;
sampler2D _NoiseTex, _DistortTex;
float4 _DistortTex_ST, _NoiseTex_ST;
float _Height, _Edge, _Distort, _MainAlphaControl, _EdgeAlphaControl;
float4 _MainColor;
float4 _EdgeColor;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _NoiseTex);
o.uv2 = TRANSFORM_TEX(v.uv, _DistortTex);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//可控制的渐变纹理
float4 gradientBlend = lerp(float4(2,2,2,2), float4(0,0,0,0), (i.uv2.y + _Height));
fixed4 distort = tex2D(_DistortTex, i.uv2) * _Distort;
//使用扭曲纹理来取样噪音纹理
fixed4 noise = tex2D(_NoiseTex,fixed2((i.uv.x + _Time.x * _SpeedX) + distort.g ,(i.uv.y + _Time.x * _SpeedY) + distort.r));
noise += gradientBlend;
noise += gradientBlend;
float4 flame = float4(noise.rgb, saturate(noise.a * _MainAlphaControl));
float4 flamecolor = flame * _MainColor;
float4 flameedge = saturate((flame + _Edge) * _EdgeAlphaControl) - flame;
flameedge.a = 1 - flameedge.a;
float4 edgecolor = flameedge * _EdgeColor;
float4 finalcolor = flamecolor + edgecolor;
return flameedge;
}
ENDCG
}
}
}
学生党一枚,文章中的错误,不足诚请各位指点!!
参考:https://www.patreon.com/posts/quick-game-art-17021975
更新:
最近在学UE4,这里也提供一种类似火焰实现的效果
同样先上最终效果图
这里选择用溶解的方法实现火焰效果,原理和之前提到的类似。混合模式为translucent,无光照。
首先是UV处理部分,简单的实现UV流动。
其次我们来实现火焰的主体,这里我们使用减法实现溶解。贴图首先乘上给定的EdgeSharp变量,用于控制外焰边缘的软硬程度,从上图看即其值越大火焰边缘的值越白。然后用减法减去变量值,并由Mask过通道的UV来控制。最左边减EdgeSharp保证其小于等于0。最右边则减去-1,这是因为我们要保证最右边都为白色,所以减-1可以保证右侧原本为0值的黑色部分都变白。然后再通过add一个变量值用来控制火焰整体的移动,就和之前unity中实现的height控制一样。最后的图像有小于0的部分也有大于1的部分所以再通过clamp钳制。
最后再加上折射效果,最左侧的折射率为1即不折射,靠近右侧的折射率由参数控制即可。然后再加上法线贴图和颜色控制即可完成火焰效果。