After Effects: Volume Light

by Peter Torpey

Adobe After Effects' 3D space presents a reasonably sophisticated lighting and rendering system. Lights in after effects can illuminate 3D layers, cast shadows, and project images. However, the lights themselves cannot be seen. Only the effect of a light can be rendered. Often times, it is desirable to have a light source rendered into the image as well as the volumentric cone as light passes through the air. While plug-ins are available for AE that do just this, in this tutorial, we'll look how the features of After Effects 6.5 and standard effects included with the Professional version can enable you to “see the light.” If you do not have the Pro version effects, you can still complete much of the tutorial.

The goal of this tutorial will be to create volumetric lighting using only the features included with After Effects. We'll use expressions to rig the setup so that it can easily be controlled and respond to camera movement in 3D in order to add visual interest to all sorts of projects. In fact, controlling the effect will be no more complicated than using the controls already offered by lights. I will presume that the reader has a good amount of experience using After Effects. Along the way, I'll discuss how some of the more elaborate expressions work, for those interested or new to expressions. There will be some coordinate space transforms and a little bit of vector arithmetic, as well. Of course, the code will be provided, so you needn't think too hard.

Setting the Stage

Let's begin by creating a simple environment in which to place our light. Create a square pixel comp. I'm using 720 × 540.

To see the effect of the light, we would like to have it create a pool of illumination on a surface, such as a floor. So, to add a floor, create a new white solid of sufficient size and name the resulting layer “Floor”. To put Floor into perspective, turn on the layer's 3D switch and rotate it back 90° along the X-axis: set the X Rotation to –90° or the first (X) Orientation field to 270°. Choosing between setting Rotation and Orientation in this instance would probably depend upon whether you were planning to animate Floor's rotation. To give us some more space in which to work, let's move Floor downward along the world Z-axis so that it's Position is [360, 440, 0].

While we're here, feel free to tweak the Material Options for the layer. However, the defaults should be fine. Be certain that Accepts Lights is set to On.

Lights, Camera…

Now that we have a surface upon which to shine some light, we need to add a light to the comp. Create a new light layer. The default name “Light 1” is suitable for our purposes. Make sure that the Light Type is Spot. Set the Cone Angel to a reasonable 43° or so. To add some interest, lets pick a color other than white. How about a faint blue [170, 204, 255].

The light should be shining down from above, so move it upward and back so that it is above the center of Floor with a Position of approximately [540, 40, –60].

The Point of Interest for After Effect's spot lights can be tricky to use. It translates along with the light's position and can be difficult to manipulate in the Comp Window. To make things easier, let's wire the Point of Interest to a null. Create a new Null Object and name it “Light 1 POI” and make it a 3D layer. Select the null press P to reveal the Position property. Also, make sure that the transform properties for the light are twirled down. Alt+Click on the stop watch for Point of Interest to add an expression. Using the pick whip, select the null's Position. Now, we can move Light 1 POI to set the position of Light 1's Point of Interest. Also, moving the light itself, won't change its target.

To make things a bit simpler when animating this scene later on, I like to remove a degree of freedom from the Light 1 POI's position. I want to constrain the Point of Interest to the Floor plane. Twirl down the Position property of the Floor layer and Alt+Click on the null's Position stop watch to create an expression for this property.
Change the default expression to: [position[0], , position[2]]; With the cursor just before the second comma, use the pick whip to select just the Y-value of the Floor's position yielding [position[0], thisComp.layer("Floor").position[1], position[2]]; Notice how the null jumps to the floor plane. We are using the X and Z coordinates of the null, but the Y coordinate of Floor. Now we can move the null forward and backward (Z) and left and right (X) across Floor, but not up or down (Y). Doing this can make animating the position a bit easier and will also help us control the volumetric lighting, later on.

There is one more 3D object that we need to add: the camera. Simply create a new Camera layer with your preferred settings.

That done, choose the Orbit Camera Tool and take a look at the world we have created thus far. We see that After Effects does a beautiful job of rendering a pool of light on the ground. It is up to us, however, to add the volumetric and light source effects. Interestingly, we'll be augmenting the three-dimensional objects by applying effects to two-dimensional layers that sit atop our scene.

One Bright Spot

Looking at a point light source, we may be able to see the bulb or even the filament or arc, if the intensity of the light is very low. Aiming a camera lens at a relatively bright light source, would produce a very different image. The light would appear to bloom larger than the actual source due to the atmosphere, halation as well as reflections within the lens system. Thankfully, After Effects Pro provides an effect that simulates this complicated phenomenon: Render > Lens Flare. Yes, lens flare effects have been over-used, but we are going to use it in a somewhat subtle and realistic fashion (…more realistic that, say, revealing every line of text in a furniture store advertisement).

Create a comp-sized black solid at the top of the layer stack and name it Flare. Add the Render > Lens Flare effect. If you have other lens flare plug-ins, feel free to use those, as well. If you're using the stock plug-in, choose the 105mm Prime Lens Type, as it tends to look a bit better with our blueish light.

The lens flare is aligned with the light source.Now we have a nice bright spot, but we want it to be positioned over the actual light source that is motivating it. What's more is that we need the lens flare to follow the light source as it moves and as the camera moves… in 3D! It's time for another expression. This time, we'll be using the layer space transform functions that After Effects provides to do the business of finding the projection of a point in 3D space onto the image plane. From here on out, you may use the pick whip to fill in references to other layer properties. I'll just provide the finished code, which should work if your layers are similarly named.

Enable an expression for Lens Flare's Flare Center. This property expects a 2D coordinate, so we'll use the toComp() layer space transform to compute the point [0, 0, 0] (the position of the light relative to itself) from Light 1's coordinate system in the composition's coordinates: thisComp.layer("Light 1").toComp([0,0,0]); I'll refer you to the After Effects documentation and Dan Ebbert's web site www.motionscript.com for more information on layer space transforms.

If we orbit the camera around, now, we find that the lens flare is locked to the light source.

In the real world, lamps are enclosed in some sort of housing. The housing has an aperture (and often a lens) from which the light escapes causing an otherwise omnidirectional light source to become a spot light, as the beam of light extends outward in a conic fashion. The housing prevents us from directly seeing the lamp unless we are looking into the opening, not from behind or the sides. To simulate this in our scene, we need to constrain the presence of the lens flare to the position of the camera relative to the spot light's "aperture".

Diagram showing the angle between the vector from the light to the camera and the light's axis.Hold on to your hats. We're about to pull out our trusty vector arithmetic and write the longest expression for this project. First, I'll define the problem more explicitly. As the camera view becomes closer to the axis of the spot light, the lens flare will grow brighter. As the camera continues to move off-axis, the lens flare will dim. Essentially, we want to know the angle between the vector from the light to the camera and the light's axis, which is the vector from the light to it's Point of Interest. The brightness of the flare will be inversely proportional to this angle. We'll also want the brightness of the flare to be proportional to the intensity of the light, so we can control both using the light's Intensity property.

Start by adding an expression to the Flare Brightness property of the Lens Flare effect and defining some variables to simplify the code: var cp = thisComp.activeCamera.position; var lp = thisComp.layer("Light 1").position; var pp = thisComp.layer("Light 1 POI").position; These variables merely hold the 3D coordinates of Camera 1, Light 1, and Light 1 POI in world space.

Now we need to find the vectors from Light 1 to Camera 1 and from Light 1 to Light 1 POI in the light's 3D space (positions relative to the light) by subtracting. We normalize the vectors so that we can easily find the angle between them with out regard to their length. var cv = normalize(sub(cp, lp)); var pv = normalize(sub(pp, lp)); Taking the dot product of our two vectors, which is essentially the projection of one onto the other, we can find the angle θ between them. var theta = Math.acos(dot(cv, pv)); The value of θ tells us how far off-axis the camera is from the light. We need to invert the sense so that we know how far on-axis the camera is. To do this, we'll subtract θ from its maximum value of π/2. To combine this value with the intensity of Light 1, we multiply. thisComp.layer("Light 1").intensity * (Math.PI / 2 - theta); Whew! That should do it. For a more accurate simulation, we could take into account other variables, such as the imaginary housing length and cone angle. However, for our artistic purposes, this will serve us well.

Orbit the camera around again and view your handy work. You may notice that the pool of light cast on Floor is hidden by our black Flare layer. We can solve this by setting the Blending Mode for the layer to Add. There, that's better.

A Beam Come True

Given sufficient contrast between a focused light and ambient illumination, as well as a relatively dense concentration of airborne particulates, we see a cone of light through the air. This phenomenon is often referred to as volumetric lighting. Now, let's create such a beam of light in our composition.

Bring in a second instance of the black solid we created earlier and place it in the comp's layer stack just below Flare. Name this layer "Beam" and change its Blend Mode to Screen.

We need some way to create a beam of light that starts small and grows as the cone of light expands. Hmm… what effect might do that? I know: Render > Beam! Yes, Beam, that seldom-used effect intended to replicate the futuristic weaponry of cinematic sci-fi lore. Add the effect to our new black solid layer. Now we have to change a number of the defaults from that blue-green dash in the center of our frame. We want the beam to extend completely between the two endpoints so set the Length property to 100%. The Starting Thickness should correspond to the opening in our imaginary lamp housing. A value of 3.00 will suffice. Of course, we'll want to change the colors. Make Inside Color and Outside Color both white ([255, 255, 255]).

Again, to make animation easier later on, we need to rig the effect with some expressions. Just as we did with the Flare Center point, lets create an expression for Beam's Starting Point and wire it to the projection of Light 1 on to the image plane using the toComp() layer space transform: thisComp.layer("Light 1").toComp([0,0,0]); We want to add a similar expression to the Ending Point property, only this time we want the project position of the light's Point of Interest: thisComp.layer("Light 1 POI").toComp([0,0,0]);

The Softness property of our Beam is roughly analogous to the light's Cone Feather property, so lets create an expression to link the two: thisComp.layer("Light 1").coneFeather;

Diagram showing the the radius of the light cone at a distance with respect to the cone angle.Here comes another tricky expression. The beam needs to get wider as the light continues to expand outward from the lamp. The radius of our beam at it's Ending Point should then correspond to the radius of the cone at the Point of Interest, but we don't know this value. So, let's use what we do know, the light's Cone Angle and the positions of the light and its Point of Interest, to find the radius.

We can take a cross section of the cone of light and split that along the axis of the light, revealing a right triangle. Using some trigonometry, we find that the radius r of the beam is proportional to the distance l from the light. This is just the tangent of the cone angle times l. Computing the radius in 2D, like this, is easier. However, we need to take into account the camera's field of view, as the projection of the 3D radius will be proportional to the camera's focal length and distance. We introduce an approximation d which will scale the radius by the distance to the image plane (a value related to focal length and field of view) and distance to Light 1 POI (from which the radius is calculated). Since the Beam effect's Softness value grows the beam outward by half, we remove that much from our radius. var l = length(thisComp.layer("Light 1 POI").position, thisComp.layer("Light 1").position); var r = l * Math.tan( degreesToRadians( thisComp.layer("Light 1").coneAngle) / 1.1); var d = thisComp.activeCamera.zoom / length(thisComp.activeCamera.position, thisComp.layer("Light 1 POI").position); d * r * (1.0 - effect("Beam")("Softness") / 200); The observant expression reader might notice a divide by 1.1. This is just an arbitrary operation that I found makes the Beam effect more closely align with the elliptical pool of light, in most instances. Also note that, our beam might disappear at extremely wide cone angles (such as over 98°). It is left as an exercise to the reader to fix the problem, if needed.

Now we have a garish white beamy blobby thing that is starting to take the shape of a visible light cone. We're not too concerned with how this looks. What we're really intersted in is the alpha that the Beam effect has generated for us. The next step is to attenuate the light. To accomplish this, we'll apply the Render > Ramp effect to the same solid. While Beam provided the alpha, Ramp will fill the color value of the pixels with a gradient that darkens as the cone of light extends further from the light source. In the real world, light falls off in accordance with the inverse square law. Without altering the output from Ramp, we won't get quite an accurate simulation. Again, this is art and we can bend the rules and stick with Ramp's more intuitive linear gradient.

Apply Render > Ramp to the solid. It should appear below (be processed after) Beam in the effects stack. Change the Ramp Shape property from Linear to Radial. Once more, we will add expressions to wire the Start of Ramp point and End of Ramp point to the position of the light and the Point of Interest. For the Start of Ramp: thisComp.layer("Light 1").toComp([0,0,0]); For the End of Ramp: thisComp.layer("Light 1 POI").toComp([0,0,0]); Since Ramp is filling in the color value of the pixels, we want it to use the light's color. Pick whip the Ramp Start Color to the Color property for Light 1 and multiply it by the normalized intensity of the light: thisComp.layer("Light 1").color * thisComp.layer("Light 1").intensity / 100; Also, to fade the ramp out (since the layer is in Screen mode), set the End Color to black.

My, my. That is looking better! Suppose the density of particulates in the air is non-uniform. We can add such subtle variation to our beam by adding the Noise > Fractal Noise effect to the solid below Ramp. In the reality the noise would not be a 2D plane, as Fractal Noise creates, but exist throughout the volume of the space. Since we can't accomplish this readily in After Effects, we'll do the next best thing and wire some of the noise parameters to respond to the camera's position. The effect won't be perfect, but at the subtle level at which we are using Fractal Noise, it will appear as though the noise is static with respect to the space and that the light and/or camera are moving through it.

Twirl open the Transform group for Fractal Noise and create an expression for the Offset Turbulence property. You guessed it, the projected position of the light: thisComp.layer("Light 1").toComp([0,0,0]); In certain instances, depending upon the position of the light, this value may need to be negated to achieve the proper look. Feel free to animate Evolution ever so slightly to simulate moving particulates.

Final rendered image of the light beam.Thankfully, Fractal Noise provides its own compositing controls, so we can tone down our noise without introducing another layer. Set the effect's Opacity to 15% or so and change the Blending Mode property to Overlay so that the noise becomes a subtle component of the beam.

There you have it folks! Move the camera around and view your handywork.

Wrapping Up

Since we wired all of the properties for our beam and flare to those of the light, we don't need to touch the two solid layers while animating. So, to clean up our timeline window, turn on the Shy switch for the Beam and Flare layers and enable Shy for the comp.

Take some time to scrub or animate the Light's properties and enjoy your volumentric light.

Having read through this tutorial, I hope that perhaps you have learned something new with regards to expressions, as well as a technique for creating volumetric lighting. As a reward, I have wrapped up the procedure as an After Effects script. Download the script into After Effect's scripts directory. When you need to add volumetric lighting, select the light you want to use, and go to File > Run Script and choose VolumeLight_1_1.jsx.

Copyright ©2006 Peter Torpey. All rights reserved.
No part of this document may be reproduced without express written permission from the author.