Volumetric Light Beam Tutorial
This guide is intended to act as an introduction to material techniques for creating a convincing volumetric light beam. We will use an unlit translucent material applied to a cone static mesh, using falloff to hide the edges.
Game engines have been using similar techniques to fake the appearance of volumetric lightbeams for many years now, but the most common techniques leave much to be desired. How do you make a fake light-beam look correct from all angles? What if the player walks through the light beam? What if the beam intersects with world geometry? With the help of a few powerful material expressions, Unreal Engine 3 can solve all these problems with ease - and a bit of tweaking.
What to Expect
This tutorial will recreate the Master Lightbeam material found in the EngineVolumetrics package.
The full name of the finished material is: \\Build\UnrealEngine3-Build\Engine\Content\EngineVolumetrics.LightBeam.Materials.M_EV_Lightbeam_Master_01
Also note: \\Build\UnrealEngine3-Build\Engine\Content\EngineVolumetrics.LightBeam.Materials.M_EV_Lightbeam_Master_01_1sided_01 should be used when it's not necessary to see the interior of the lightbeam. We end up using the 1sided version most often for performance reasons.
EngineVolumetrics is a package containing four groups to contain the four types of volumetrics: FalloffSpheres, FogEnvironments, Fogsheets and Lightbeams. Of all the volumetric primitive types, the lightbeam was chosen as the tutorial material because it combines most of the techniques from all of the types into one material. The page explaining the various types of volumetric primitives and how to use them is the VolumetricLightingGuide.
Since this material is to be used as the parent material of many instances, this tutorial will also show you how to configure a material with parameters for instancing. For a complete explanation material instancing, see the InstancedMaterials page.
There are limitless possibilities to what can be created, but we will start with a simple conical falloff type beam. These can be used in many different ways; they can be small and subtle to give a glow coming off a light source or scaled up to a god-beam-like-ray of light emanating through a hole in the clouds. They should match the existing atmosphere of a level; a level with dense distance fog should use denser lightbeams than a level with a clear atmosphere. Of course, this is entirely at the whim of the artist.
There are three steps to creating these light-beams.
1) Create the Static Mesh that defines the shape of the beam.
2) Create the falloff texture.
3) Create the Material and tweak values until all artifacts are gone.
Creating the Static Mesh
The EngineVolumetrics package already has three cone variants with different angles, but they all have the same UV’s. This section will tell you what you need to do in order to make more custom cone meshes to work with this material.
Create a cylinder with a tapered edge and unwrap it so that there is only one seam in the UV’s. It is important to have clean UV’s since the material calculations are done in tangent space and sloppy UV’s could cause visual distortion later on. The more tessellated your mesh is, the smoother the falloff will be for the end result (a 16-sided cylinder with 4 vertical slices is fine).
The three cone variants already in EngineVolumetrics should be enough for most cases.
Creating the Material
The creation of the material is the most involved part of the process. First we will get the light-beam functioning at its most basic level, then we will solve each one of the artifacts one at a time.
First we need to create a falloff texture. The appearance of the falloff texture is largely responsible for the appearance of the end result. This falloff texture has been created entirely with the Render->Light Effects filter in Photoshop. One fuzzy black line is drawn on top.
The important qualities to take note of are that the texture is completely black around all edges and the top of the texture fades to black. This helps to hide the position of the top edges of the Static Mesh. Make sure the texture properties are set to Clamp. The texture sample for this in the material will be a TextureSample2DParameter so that you can use a variety of different falloff textures on your lightbeams.
Setting up the Material
Now we are ready to start creating the material. Create a new material and set the following options in the material settings:
We will only be using two of the material input fields: Emissive and Opacity. The emissive field should contain only the color information of the beam. For now a simple VectorParameter (1, 1, 1, 1) is used for the emissive. The Alpha channel of the VectorParameter is multiplied by the RGB. This lets you use the Alpha as to modify the Brightness so that you can use the color picker to choose RGB values (which only supports values <1) and still make the lightbeam insanely emissive if you want to. Being able to change luminosity independent of color is often very useful.
The opacity field is where most of the magic occurs. It will handle the falloff of the beam and may contain texture information to add dust or scrolling smoke.
We will use the falloff texture shown above as the opacity. For the texture coordinates of the falloff texture, we use a Reflection Vector to generate the X component while the Y component is supplied by the Static Mesh’s UV’s. This will orient the texture to face the camera along the X axis, relative to the static mesh’s rotation. Generating the X component takes a bit of work.
Reflection vectors output a range of numbers from -1 to 1. We want a range of numbers from 0-1 since we will be using the results as texture coordinates. We can change the range of a Reflection vector to be 0-1 simply by multiplying by 0.5, and then adding 0.5.
Once the range is correct, the AppendVector expression combines our Generated X coordinate with the Y coordinate in from the Static Mesh’s UV’s. The output of this AppendVector is ready to supply UV’s to our FalloffTexture
Here is what our light-beam now looks like:
While this is a good starting point, our lightbeam material is by no means complete. Take a look at the artifacts we can introduce into the scene:
As I said in the introduction to this guide, each of these problems can be solved by using the proper material expressions.
Fixing the Artifacts
Looks bad viewed from Underneath
The method we are currently using (strictly X channel falloff) makes our beam look great when viewed from the side, but offers little help when the beam is viewed from an underneath angle. To correct the way the beam looks when viewed from underneath, we will use a second falloff texture and multiply it with our existing falloff texture. I used a blurry dot for the second falloff texture.
Make sure the texture properties are set to Clamp.
Much like before, we use a reflection vector to generate texture coordinates for the second falloff texture. For the texture coordinates of this texture, we will use the X and Y components from the reflection vector. The ReflectionVector(X,Y) gets multiplied by a constant to adjust it slightly to better hide the edges. I used 0.3 but other values will work too.
Here is what the light-beam should now look like when viewed from underneath:
No more hard edges should be visible. If you still see hard edges, you simply need to tweak the falloff texture or the multiple of the ReflectionVector(X,Y).
Using the PixelDepth node, we can sample the depth of any pixel within a translucent material. We will use this to fade out the pixels that are immediately in front of the camera. We can control the distance at which the fading occurs by multiplying the depth by a constant. For the constant, use a scalar parameter named ‘Distance’ so that it can be changed per-instance. Typically, you will want to change this value based on the size of the lightbeam you place. A very large beam should fade out from further back, and a small beam should allow the camera to get fairly close before starting to fade. A good default it 0.005.
It is important to clamp this result to a max of one. Simply multiply by the previous falloff texture result.
Before and After the distance fading has been applied:
World Objects Intersect Light-beam
We can use DepthBiasedAlpha to fade the lightbeam along an intersection with world geometry.
Add the DepthBiasedBlendAlpha node and set its BiasScale to something around 500. A scalar parameter named ‘Density’ is used in the Bias input of the node, allowing further tweaking of the depth bias via material instances.
Since this material is meant to be the parent material for instances, and depthbiasedalpha is a fairly expensive expression, we are going to give it a StaticSwitchParameter so that it can be toggled on and off. It is set up so that the default is for it to be OFF.
While the DepthBiasedAlpha technique is not perfect (it does not cause the lightbeam to be volumetrically shadowed), it does an excellent job of removing obvious artifacts from the scene.
Adding finishing touches
We will also use a StaticSwitchParameter to give the option to enable a panning smoke texture within the beam. This is one reason that if you make additional beam meshes the UV’s should be neatly cylindrically unwrapped from 0 to 1 so that a smoke texture can pan without a seam where it tiles.
A texture parameter is added. It has several other parameters to control it’s behavior. Since lightbeams are often scaled along their length, the texture has separate parameters for X and Y scale. It also has parameters for panning speed and contrast.
The parameters are:
TextureContrast: Linear interpolates the texture with constant 1.
TextureScaleX: scales along X
TextureScaleY: scales along Y
PanningSpeed: multiplies time to control panning speed
Here is a screenshot:
Lastly, we will be adding an additional multiply and a Scalar Parameter named “Opacity” to give final control of the beam’s opacity to the material instance.
Here is what the final material looks like. At 21 instructions, this light-beam is still far cheaper than most lit materials. Turning on UseDepthBiasedAlpha and UseTextureOverlay bring the instruction count up to 39, so use those StaticSwitchParameters wisely.
With so much attention paid to the lighting features of next-gen engines, it is important not to underestimate the power of unlit materials.