Faking Volumetric Effects

steam_plume_result

Introduction

Volumetric effects such as fog, steam, smoke, and the rays of light that can occur within them help a great deal to add realism to a gaming experience. Traditionally these elements have often be created with particles, but there are drawback and limitations to this approach. Particles can be computationally expensive, especially when they are lit, and self shadows cast within a particle system are generally not sensible for real-time. Also, it can be difficult or impossible to get particles to maintain a certain shape, and often they seem to give a lumpy, fragmented feeling to something that should seem more unified and whole. There are certain effects, such as light rays and spotlight cones for which particles are by their very nature entirely unsuited. Because of these reasons it is often worthwhile to pursue other solutions. This document will discuss alternative methods for achieving volumetric effects using static meshes and complex materials. Different volumetric effects will require different approaches, so this document is divided up into several examples. Many techniques are shared between them however, so each successive example builds upon information written in the previous ones. In turn, this entire document builds upon the foundations laid out in Epic’s Volumetric Light Beam Tutorial, which should be read first before proceeding. A login to UDN will be required.

Light Cone – High Poly

We will begin by further discussing and refining the light cone described in the above-mentioned Epic tutorial.

Smoothing Artifacts

One unfortunate aspect of this cone is the faceted nature of it, which is what causes the need for subdivision within the geometry, increasing the poly count. The source of this issue requires some explanation. Unreal can interpolate curvature on surfaces that share the same smoothing group, but despite it having a per-pixel renderer, the smoothing interpolation is still done on a per-vertex basis. What this means is that curvature values are only computed for each vertex, and the faces are computed by linearly blending between the values for its bordering vertices. This works relatively well with shapes that have curvature in only one dimension, such as a true cylinder, but for this cone we shrink the top, distorting the face-to-face smoothing relationship in such a way that the per-vertex smoothing breaks down. The image below demonstrates this difference.

cone_vs_cyl

This affects not only lighting for standard Phong lit materials, but also the Reflection vector, which is the problem here. The image above shows the red channel of the reflection vector applied to the emissive and opacity of unlit additive surfaces. The usual solution for this vertex-based smoothing problem on regular lit materials is to use a normal map projected from a much higher poly model, but that does not work here, because unlit materials cannot accept normal maps. Epic chose for optimization reasons for that part of the shader to simply not compile unless the surface is lit. Because of this we need to add cross sections when using a cone, but not when the effect uses a cylinder instead. This distinction will come in useful later. While the Epic tutorial mentions the need for cross sections, it does not address the distribution of them. We are trying to reduce the height to width ratio of each face. Evenly spaced cross sections are not ideal for this. Because the top tapers so much, this would result in proportionally long faces near the top, and more squat ones at the bottom. A better distribution is shown in the image below. Notice how each level of faces as the cone tapers upward has approximately the same proportions, merely different sizes. As you can see, this results in a cone with relatively little smoothing artifacts. Of course, the more cross sections, the smoother it will be, but poly count considerations should also be kept in mind.

cone_cross-sections

Geometry for Above and Below

Another drawback to the light cone described in the Epic document is that it fades out when seen from above or below, because the angle of the faces in regards to the camera increases until they are culled by the same operations that we use to make the soft edge. This issue is particularly pronounced on cones that are long and thin, as opposed to squat and short. To alleviate this problem we can add a second, smaller cone at the top that has more squat proportions. Because this cone has a less steep angle, it can been seen from higher or lower than the first without fading as much, but from the side it is fainter than the steep cone, which faces the camera more directly. By extending the new cone slightly beyond the bounds of the first, we can make it seem as wide as the original cone from high angles. Feel free to experiment with size, placement, and angle for this cone.

squat_cone

Shader Refinements

The shader setup described in the Epic tutorial is a good starting point, but we will make some changes and additions to it. Here is the end result that we will create.

volumetric_cone_heavy_mat

The textures called in this shader are displayed below.

volumetric_cone_heavy_textures

The Power of Powers

The Power expression can be very useful for modulating the falloff of the edges. Here we have added a power to change general falloff, and a secondary one to affect only the opacity (more on the reason for that later). By changing the exponent constants we can change the softness of the edges. The image below shows the effect of different values entered into the “Falloff Constant” power exponent of the above shader. Generally somewhere in the 0.5 – 0.8 range is good.

power_of_powers

DepthBiasedBlend Issues

The [DepthBiasedBlend] expression used to keep the hard edges from showing when intersecting with objects behaves differently depending on the blending mode. With regular unlit translucent objects, it is fine to apply the DepthBiasedBlend only to the diffuse and emissive inputs, but with the Additive blend mode it is necessary to apply it to the opacity as well. Unfortunately this can cause problems sometimes. See the image below. The object floating beneath the cone blends fine, but on the image to the left the light cone leaves a noticeable bright circle where it intersects the floor. This is due to an imbalance between the way the emissive and the opacity are blended by the DepthBiasedBlend, which seems to be a bug (or at least an unintended outcome) of the renderer.

depth_bias_artifact

It is possible however to rebalance these by hand. To do this we branch the DepthBiasedBlend output through a Power expression before connecting to opacity. By tweaking the value for this power’s exponent and the Falloff Constant for the first power, we can do away with the artifacting, as seen in the cone to the right.

opacity_power

Another issue to note regarding the DepthBiasedBlend is a rendering error that occurs whenever something using this expression is displayed in front of an empty background. Since emptiness has no numerical depth (it is essentially infinite in depth), the DepthBiasedBlend cannot compute the interaction between the shader and its background, and therefore the surface disappears. The image below demonstrates this. For practical purposes this does not matter, because the game will always have something in the background, even if it is just a skydome. This issue should be kept in mind however when constructing and viewing assets in incomplete levels.

bias_to_infinity

Moving Dust

Another improvement to this shader is the addition of dust moving through it. For this we create a tiled seamless texture in Photoshop and import it. To create the effect of roiling turbulence, we pan this texture in two opposite directions at once, and add together the result. This keeps it from pulling the eye in one direction or the other, giving the impression of dust floating around in the air but not really blowing anywhere. The tiling on these two textures should be set to slightly different values, so that the textures never momentarily align with each other. Here we have tiled one of them 3 times, and the other 2.5. Once this has been done, the result might be too stark. To make the dust more subtle, we can add it on top of a constant, and compensate for that value by adjusting the general Intensity constant.

cone_done

Light Cone – Low Poly

The solution detailed above works well for light cones that we can walk beneath and see from above. But sometimes this is not necessary, and a beam of light is just a decorative feature seen from certain angles. When this is the case, it is possible to greatly optimize the poly count on these assets. The smoothing issue that forced us to put cross-sections on the cone does not apply when using a cylinder. It is possible to apply a triangular shaped glow texture to the cylinder in order to fake a light cone.

volumetric_cone_on_cyl_result

Keep in mind that since this does not actually taper to a point, the illusion falls apart completely if the player walks below or above it. But this effect works well for cones that are only seen from the side, and takes about one fifth of the polys. The shader for it shares most of the elements of the previous light cone, with some small changes.

volumetric_cone_on_cyl_mat

Here are the textures that it calls.

volumetric_cone_on_cyl_textures

Notice that the falloff image for this one has already been bent into the proper triangular shape, as opposed to inheriting the shape from the geometry.

A noteworthy feature of this shader is that it lacks the extra falloff texture that had been applied to Red and Green channels of the Reflection vector in the previous example, because this is only intended for viewing from the side.

Another method for creating light beams that one does not need to see from above or below would be to use intersecting planes, a technique with the versatility to be used for a much greater range of volumetric effects, as we shall see in the following examples.

Cloud of Steam

Large areas of persistent turbulent steam or smoke can be made with extremely low poly static meshes, saving hugely on the performance costs that would otherwise be incurred by particles. This technique involves projecting the lighting from modeled cloud geometry onto planes. Besides the performance gains, this also allows us to more carefully shape the volumes to fit their settings. Another benefit is that we can bake light and shadows to the cloud that could potentially be far more realistic than real-time lighting of particles. This last feature will be demonstrated to much greater effect later in the Steam Plume section. Here we will examine the creation of a volumetric cloud. The example can be found in the cloud_test.upk Cloud Test package.

Modeling

The first step is to model geometry for use in projecting to the planes. The image below shows the result of distorting and detailing a GeoSphere using ZBrush. Right now it looks more like a lump of clay than a cloud, but the important thing is that it has the right shape, including irregular edges from every angle, and a great deal of turbulent noise along its surface. The particular shape of it will of course be dependent upon its intended use. The softness will come later.

lumpy

Plane Setup

In 3DS Max, arrange a group of planes to intersect through the cloud, so that there is always one plane facing in every direction. Here we have distributed them in 45 degree angles, but if you require a cloud with a truly 3 dimensional feel, more planes will be necessary (see the Steam Plume below for an example of this). Merge all the planes, duplicate them, and flip the surfaces on the duplicate, so that each plane is matched by a twin facing in the opposite direction. Merge once again, and apply a Push modifier to separate the planes slightly from their twins. This is to avoid a rendering error that can occur in Unreal when the engine tries to calculate the reflection vector of planes that occupy the same space, even with a one-sided material. UV map them so that every plane calls a different parts of the texture sheet.

cloud_plane_setup

Lighting and Rendering

Now we will light the scene. The cloud material will be unlit because true lit transparency is so computationally expensive, so instead we will be baking lighting from Max. Set this cluster of planes to not cast shadows (Right click > Properties > General > Rendering Control >Cast Shadows), so it won’t interfere with the lighting on the cloud. Distribute spot lights or omni lights around the cloud however you wish it to be lit. Now use the Render to Texture feature to project the cloud’s lighting and opacity. In the options dialog uncheck the “Use Cage” option to force it to project directly along the normal of each plane. The result should look something like the image below.

cloud_lump_render

Photoshop Alteration

In Photoshop, soften the edges of the opacity mask inward using a combination of the Filter > Other > Minimum command and a Gaussian blur. Duplicate the lit cloud layer several times and apply blurs of various radii to them, layering them upon each other with different opacities. Change the harshness of the lighting with a Curves or Levels adjustment layer if necessary. The result should be something softer and more cloud-like, such as the example below.

cloud_lump_altered

The Material

Now import the textures and the cluster of planes into Unreal. The unlit translucent shader that we will be building is displayed below.

cloud_test_mat

Notice that it share most of its features with the volumetric shaders discussed above. This includes:

• a clamped version of the Screen Position’s depth component to fade the surface away before the camera intersects it

• a noise texture panning in two opposite directions at once to simulate turbulence

• the use of the reflection vector to fade out geometry as it approaches an edge-on angle to the camera, attenuated by a power expression to control how much they fade off

• a DepthBiasedBlend to keep it from having ugly hard edges where it intersect other geometry. Notice however that since this is translucent rather than additive, the DepthBiasedBlend is not necessary on the Opacity channel, only on the emissive.

Changing the harshness of the panning texture, the Brightness value, and the Opacity Constant can shift this between being a thick cloud to a diffused, wispy one, depending on what the situation calls for.

cloud_result

When seen up close, deployed amongst game assets, the wispy variation works well for low lying volumetric effects. Despite the fact that it is only a few planes that have no inherent depth of their own, the high value given to the DepthBiasedBlend makes them blend smoothly through the scene. The lighting and shape that was baked in stays constant, but the panning textures give it enough turbulence that the illusion of fluid movement is maintained.

demoshack_steamy

Billowing Plumes of Steam

A similar technique can be used with some alteration to make thicker, more fluid volumetric effects, such as a billowing plume of steam or smoke. While performance gains over particles are certainly an issue here, perhaps the greatest benefit to using this technique is the ability to light the plume in such a way that it really seems 3D, displaying lighting of different colors from different angles and casting shadows upon itself, something that sprite-based particles are incapable of. All of this can be achieved while keeping the material unlit, and therefore highly optimized.

Modeling

Just as in the last example, the first step here is to model geometry with ZBrush to use for projecting onto planes. For much of the smaller scale details, it can be particularly useful to import a noisy image, such as one generated in Photoshop with the Filter > Render > Clouds command, and use it as a displacement map. It is important to keep the object in a roughly cylindrical shape, regardless of the twists and expansions that may take place in the final asset. The reason for this is that we will be creating a seamless panning texture to simulate the steam billowing upwards. This requires that the texture can be properly moved in one direction, and therefore be straight. We will make it truly seamless later in Photoshop.

plume_log

Plane Setup

Now import the object to 3DS Max and arrange planes rotated around its central axis, as shown in the image below. For now, do not worry about the other directions. Using many planes will help improve the 3 dimensionality of it, because the difference as one plane fades to another when the angle changes will be very slight. However this must be balanced with the need to conserve texture space. The image below shows 16 planes (including ones facing opposite to others) which is generally a good number for this. UV map them so that all the planes lay side by side on the texture sheet, stretching from top to bottom. This will allow us to pan the texture upwards later.

steam_plume_render_planes

Lighting and Rendering

Light the plume in the same way that the Cloud example was lit. Here we have used white, yellow, red and blue lights to further distinguish the different sides, and to simulate conditions where highlight, ambient lights, and shadow are all factors. Use the Render to Texture feature to project the lighting onto the planes. The result should look like the image below.

steam_plume_render

Photoshop Alteration

To make this seem more like steam it will be necessary to alter it in Photoshop in much the same way that we did in the Cloud example. It is best to eliminate the chance of any of the background showing through the altered opacity mask, so we bleed the diffuse over its edges. To do this, make the alpha (opacity) the selection, invert, and delete the selection from the diffuse, leaving behind only the plume itself. Then duplicate the diffuse, reinvert the selection, and nudge the lower version around while holding the Alt key, thereby leaving behind duplicated versions that are slightly offset, poking out below the original layer which has remained in place. The edging is still a bit too clean, not as wispy as we might like. To correct this, use Filter > Render > Clouds with black and white to make noise that we can add for wisps. Mix this with the regular opacity mask along the edges so that there are incursions of darkness into the white, and wispy edges of white floating outside the main bounds in the black. Now blur a copy of the diffuse by a very large amount, and alter its levels and saturation to approximately match the regular diffuse. Now use the rendered clouds that are altering the opacity to mask this blurred diffuse, leaving behind wisps at the edges, but showing the regular diffuse through in all other places. When this is finished, create a merged version of the diffuse and the opacity, and use the Filter > Other > Offset command to shift it upwards, wrapping across the seam. Mask out the new seam softly, letting the image below show through, eliminating the seam. Here’s a close-up of what the final images should look like.

closeup_w_edging

Now apply these images to the planes in Max. Use a Push modifier to separate the planes from their opposite-facing twins, so as to avoid rendering errors in Unreal. Insert cross sections and bend the plume into whatever shape is appropriate. Generally such plumes expand outwards as they move from the source, and they bend with the wind. Depending on how drastically the shape is altered it might be necessary to bisect the planes from top to bottom as well to prevent major distortion of the UVs. Plumes of many varying shapes can be made in this way, all calling the same textures and material, because the unique shape is reliant upon geometry only, not the texture, which merely goes in a straight line.

steam_geo_bent

The final geometry will require extra planes for certain angles, but before we can know where to place those, we should set up the shader.

The Material

Here is the unlit translucent material we will be making.

steam_plume_w_planes_mat

Some aspects of this material are similar to what we have used before in the other volumetric assets, including:

• An edge falloff driven by the isolated blue channel from a reflection vector, attenuated by a power expression

• A clamped version of the Screen Position’s depth component to fade the surface away before the camera intersects it

• A DepthBiasedBlend on the opacity and emissive to keep it soft where it intersects other objects.

There is however no secondary panning turbulence texture as there was in the previous examples, because this time we are panning the entire thing. Distortion along the UVs as it moves and the overlapping effect of the various planes on top of each other as they move will provide plenty of turbulence.

The group of expressions at the bottom of this shader tree isolates out a vertical gradient from a Texture Coordinate, and transforms it so that the top fades off to black, but most of the surface remains white. This makes the steam plume fade away as it rises upward. While this could have been done with a painted gradient falloff texture, doing it mathematically this way avoids the need for another texture call.

Another falloff is shown here, created by a circular gradient texture assigned to Coordinate Index 1 (the second UV channel). The reason for this will be explained below. For now, however, bypass this extra falloff.

Notice that the plume looks good from only certain angles, but fades away from many others. This is because the material culls out any faces that are not pointing towards the camera, and from certain angles there are none. For this we must create more geometry.

Extra Planes

Return to 3DS Max and create extra faces by selecting faces from the plume and detaching them as clones, then rotating. The key is to choose faces that display a part of the texture that is appropriate for the angle that is to be displayed. When seen face on, they should have approximately the same lighting and panning direction as the main body of the plume they are representing.

planes_added

Initially, these will of course have hard edges where the texture continues off them. For this we will apply a second set of UVs to the planes, for use with a circular fall off texture that does not pan the way the other textures do. Since we don’t want this fall off texture to apply to the main body of the plume, scale all of it’s 2nd set of UVs down very small and place them at the center of the image, where the falloff is completely white and has no effect.

planes_uv_set_2

Do not underestimate the importance or difficulty of placing these planes to supplement the main plume. It takes a great deal of tweaking, checking in UnrealEd, then tweaking again, etc. Too many planes at the wrong angles can drown out the flowing shape of the original plume. It is best to identify particular angles that need planes, and custom make them for this purpose. Then check if they aversely affect the view from other angles, and fix as necessary. Another good way to balance this problem is to change the Edge Falloff constant in the material. Too high a number will cull out a great deal of them, whereas too low a number won’t cull out enough. Experiment until a decent number is found, and the plume looks good from all angles.

steam_plume_result

Instances

Since this material can be used on variously shaped geometry, it may prove useful to apply it in many different circumstances. Its versatility can be increased even more by the use of Material Instance Constants, where an instance of the material is created, but certain constants such as brightness or a Saturation expression can be changed on a per-instance basis, without having to recompile as a totally different shader.



No Comment