A fairly common method of creating fire is to use a particle system, however many transparent quads produces overdraw which can affect performance, especially for VR and mobile platforms.
When rendering opaque geometry, Unity renders objects front-to-back (objects closer are rendered first). This allows z-testing to take place, where objects further away can discard fragments from being rendered if they will be covered by a closer object to avoid unnecessary calculations. However in the transparent queue, Unity has to render those objects back-to-front instead in order to achieve the correct alpha blending. Rendering many objects on top of each other, like transparent quads from the particle system, is what is meant by overdraw – and this happens with UI and post-processing effects too.
Rather than using a particle system, a different approach to fire is a quad which faces the camera, where a noise texture is used to warp the UVs for sampling another texture (or generated ellipse / teardrop-like shape). This is a technique that I first saw from RiME.
I’ve had a version of this shader for a while but never got around to writing a proper breakdown. It’s a bit different from the RiME talk and I’m also using the Simple Noise node, but a seamless noise texture could work just as well and might be more performant for VR/mobile. (I don’t really work with those platforms though, so test & profile!)
This was written for Universal RP, probably also works in HDRP though.
In order to create the scrolling (aka moving) noise needed for the effect, we’ll use the Time node and Multiply it by a value to control the speed of the scrolling. (I’ve hardcoded this as 0.5, but you could use a Vector1 property if you want to be able to control it from the material inspector). Since we want it to scroll upwards we need to Negate this and put it into the Y input on a Vector2 node, leaving X as 0. This is used as the Offset input to a Tiling And Offset node.
We’ll also provide a Vector2 property to control the “Noise Scale“, which will be put into the Tiling input – allowing us to change the scale of our UVs per-axis. The output from our Tiling And Offset will then be put into a Simple Noise node with a Scale of 30-ish.
This noise will be used to offset the UVs for effects later. Unlike offsetting by a constant value or time (which changes, but is the same value for every UV coordinate), we are shifting each UV coordinate by a different value, which produces a distortion (aka warping) effect. We also should Subtract 0.5, so the distortion is centered and doesn’t offset more in one direction than the other.
Multiply this with a Vector1 property named “Distortion Strength“. We also want the top of the fire to be distorted more than the bottom, so we also Multiply with the Y axis of the UV (G output from Split). We put this result into the Y axis of a Vector2, then into the Offset input on a Tiling And Offset node to apply our distortion, only in the vertical direction.
The distortion will mean our UVs could go outside of the 0-1 range, so parts of the texture/ellipse later could be cut-off by the size of the quad. To help fix this, we’ll also include a Vector1 property called “Y Scale” and put it into the Y of a Vector2, with X as 1, into the Tiling input. This will allow us to scale down the effect in the vertical direction, to try to keep the fire contained to the quad.
From here, I’m splitting the graph into two versions – a stylised version that uses Ellipse nodes to create a fireball shape, and another simpler version which uses a texture, specifically a flame texture from Kenney’s Particle Pack, which is a bit more realistic looking (though could probably look equally as stylised with an effect like Posterize also applied). With a texture it’s a bit easier to control the shape of the effect, but I’ve also used the ellipse method before so felt like sharing it too.
We could also go further and provide a Flipbook to animate the fire if we had an appropriate spritesheet-like texture, or maybe sample two different fire textures and Lerp between them with a Sine of time, remapped into the 0-1 range. I won’t be doing any of that here, but maybe that provides some examples if the fire texture is a bit too static.
Be sure to also check after both versions below for a section about producing a billboard effect.
For the texture version, we mainly only need a Sample Texture 2D node. As the texture I’m using is greyscale I’m taking the R output and Multiply it by 10 so it’s a bit brighter. We then can Multiply with a Color property (which is also set to HDR mode and has an Intensity of 1.5) to apply the fire colour. This is put into the Color input on the Unlit Master node which should also be set to Transparent via the small cog on the node. You can also choose which blending type you prefer, e.g. Alpha/Additive.
Rather than sampling a texture, another approach is generating a shape from math. Shadergraph provides an Ellipse node which helps with this.
While we could take our UVs and directly put them into an Ellipse node, I’d like to apply a bit more offset first, to warp it into a more tear-drop like shape. There might be a few ways to achieve this, but I’m taking the Y axis (G output from a Split), using One Minus to flip it upside-down and putting the result into a Power node with a B value of 2. (I guess this could also just be a Multiply, not sure if the compiler is clever enough to optimise this automatically or not). The next One Minus flips the vertical component back, but since the ellipse is centered and symmetrical it shouldn’t really matter.
With our edited UVs, we’ll put them into two Ellipse nodes. The first with a Width of 0.7 and Height of 0.5, and the other with 1 and 1.
We’ll also take the output from the Power in the previous step, Add 0.3, Multiply by 1.5 and put the result into a Posterize with 3 Steps. These values were mostly trial and error to ensure the fire had a nice amount of coloured bands, to help with a more stylised look. This is added with the smaller Ellipse output, then we can tint the fire by multiplying with a Color property (which is also set to HDR mode and has an intensity of 1.5) and plug the result into the Color input on the Unlit Master node.
Be sure to set the surface type to Transparent via the small cog on the node.
The larger Ellipse is used as the Alpha input on the Master node if using Alpha blending. If you prefer Additive blending, Multiply this with the current colour result and use that as the Color input instead, as black areas will show as transparent.
For a 2D game this shader will work, but when applied to a flat quad in a 3D game the fire is obviously two dimensional if the player can walk around the side of it. One way to fix this is to have the quad always face the camera, which is known as billboarding. We could do this in a C# using Transform.LookAt in the Update/LateUpdate method, but we can also handle it inside the shader instead.
The following graph is based off this forum answer by bgolus, and can be put into the Vertex Position input on the Master node to produce the billboard effect.
Alternatively I suppose the shader could be used in conjunction with the Particle System component, as a way to apply a distortion effect on the existing particles which already handle a billboarding effect. You’d also likely want to swap the Color property out for the Vertex Color node if you do that.
I’ve also applied the shader to a line/trail renderer before which again already does a billboarding effect for you, that was for a fireball spell effect. (wow this is an old tweet!) :
Thanks for reading this!
If you have any comments, questions or suggestions please drop me a tweet @Cyanilux, and if you enjoyed reading please consider sharing a link with others!