Sideband #52: POV-Ray Animation

I mentioned last time that a big draw for me with POV-Ray is the ability to create three-dimensional scenes and move around them. Having lots of camera positions is part of that; I want to see my scene from multiple angles. (Moving about a 3D space was often a big part of what little interest I ever had in video games. I especially liked flying games.)

From the very beginning, knowing that POV has support for animation, I’ve wanted to take it to the next level and make 3D movies. Rather than frozen snapshots taken from a bunch of (hopefully) well-chosen points, I wanted a fluid movement through the space.

Today I thought I’d write about some tricks I use to do that.

So,… do you find animated GIFs as annoying as I do?

Creating an animation involves all the creative decisions you make designing the scene, plus it involves all sorts of decisions you must make regarding the animation.

Does the camera move its position? Does it change what it looks at (or how it looks at it)? Do any of the objects in the scene move?

One very simple form of animation is a “spinner” like the headline image of this article and the one on the left. The only thing animated is the object, which is set to rotate in place. In POV-Ray, this is extremely simple to set up.

When you tell POV to create an animation, it sets the clock variable to a new value in each frame rendered.

To make an object spin, just use the clock variable to set the object’s rotation progressively in each frame.

By default the clock’s value is the normalized range zero to one. For the object to rotate fully, it needs to move 360 degrees, so the actual code might look like this:

#local angle = 360 * clock;
object {MySpinningThing rotate y * angle}

That will give the object a (single) full rotation. To insure a smooth rotation, be sure to set Cyclic_Animation to “On” in the INI file that kicks off the animation. (I’ll get back to INI files in a bit.)

Creating an object where only certain parts move is a little more involved, but mostly follows the same pattern.

If some of the moving parts rotate, they’re special cases of the example above. The angle may need to be set to not begin at zero and/or not end at 360. The general equation for a specific range is:

#local adj_clk = start + ((endstart) * clock);

This gives you adj_clk (adjusted clock) that runs from start to end during the cycle of animation. You could turn it into a function:

#declare adjclock = function(start,end) {…}

You could also turn it into a generic “range” function by adding a third “current point” parameter and passing the clock variable.

#declare moverange = function(start,end,curr) {…}

The adjusted clock values go in various rotate and translate statements to move object parts as necessary. I go into this “range” idea in detail, because it’s crucial to what comes next.

Scenes like this one are what are killing my movie hopes. This single frame took over 90 minutes to render, and it’s just one of several hundred. It’s the wavy glass monolith that’s a big part of the render time.
[click for (do I really need to keep saying it?) the big version]

Deciding to animate an object — or part of an object — is one thing.

At least the camera angle is fixed, and so is the background and lighting. When we start moving the camera around, now we’re talking cinematography! Now we have to consider a changing background and possibly changing lighting conditions.

Cinematography is not a subject I’m particularly qualified to write about, but I can show you the tricks I use to make moving and pointing the camera as easy as possible. As with the CAM.INI protocol, this is the culmination of previous versions that evolved to this.

So far I’m liking it a lot!

The goal of any language or protocol is to be as expressive as possible, so that designers can focus on the design and not on the details of making the design.

This protocol seems to do that pretty well considering that neither POV scene description nor macro language are particularly expressive languages.

The first step adds to what I showed you last time about how I pick a camera position. Leaving off the bit that insures CameraIndex exists, the bit that sets the render camera looks like this:

#ifndef (studio_animation)
camera {CameraSet[CameraIndex]}
#else
…animation stuff goes here!…
#end

The studio_animation variable, if defined, puts the render into animation mode.

(Which, as you see, disables the whole camera-selection protocol previously discussed; we’re not in Kansas anymore!)

The studio_animation variable does more than just switch to animation mode. The first level of structure inside the #else above is a #switch statement with a #case for each segment of the planned animation:

#switch (studio_animation)
#case (1)
…segment #1…
#break
#case (2)
…segment #2…
#break
…etc…
#end

A “movie” (a long animation) consists of multiple animation segments. This is mainly a complexity management technique.

Would it be ironic to use the clock variable to animate an actual clock or is that just me?
[click… (you know the rest)]

In POV, you can have the clock run from any value to any value, so it’s possible to create a single long animation.

But you still end up with a #switch structure to allow different kinds of movement. Each segment sees a different clock range, which requires a bit of normalization.

I find it easier to just have separate animations (there are some output considerations that also make this a good idea).

Within each segment the camera can move, and independently its point of view can also change. My segments usually consist of a series of both in some combination. The camera might be moving forward while turning to look right and then back forward. That’s three sub-segments: turn right, look right, turn forward (all while moving forward).

Any distinct “phrase,” any movement with a distinct beginning and end, is a sub-segment. As the example above illustrates, there is a single camera “phrase” above (moving forward), but three point of view phrases.

Animations tend to look better when these are very independent. I accomplish this with the next level of structure; what’s inside the #case statements:

#case (1)
…important local vars!…
#switch (clock)
…set camera location…
#end
#switch (clock)
…set point of view…
#end
#break

Those inner #switch statements are exactly what I meant about still needing to break down individual phrases of movement. No way ’round lots of #switches!

To understand what’s inside the #switch statements, it’s necessary to understand that bit labeled important local vars.

Each animation segment (each #case like the one above), defines two sets of important variables: The camera positions within the segment, and the time slices that comprise the segment. They might look something like this:

#local point_0 = <-150,  0.0>;
#local point_1 = < -40, -4.0>;
#local point_2 = < -24, -3.7>;
#local time_1 = <0.00, 0.33>;
#local time_2 = <0.33, 0.66>;
#local time_3 = <0.66, 1.00>;

Now what’s inside the those inner #switch statements will make a bit more sense. (As for why one starts numbering at zero and the other at one, there is an explanation, but it’s lame and irrelevant anyway.)

Camera movements turn out to be very simple. Sometimes the camera is just standing in one spot for a sub-segment.

Other times it’s moving from point ‘A’ to point ‘B’. The first inner #switch statement, the camera movement one, might start looking like this:

#switch (clock)
// 1,2,3 – move to entry door
#range (time_1.u, time_3.v)
#local clk = adjclock(time_1.u, time_3.v);
MoveCAM(clk, point_0, point_1)
#break
// 4 – stand while entry door opens
#range (time_4.u, time_4.v)
#declare cam_x = point_1.u;
#declare cam_z = point_1.v;
#break
…etc…
#end

Here we use #range clauses in the #switch to catch slices of clock time. Now you see the reason for the time_N variables. They’re designed for the #range clauses. You also see how the adjclock() function normalizes the sub-segment. I’ll discuss the MoveCAM() macro below.

Or you could sit down and draw all the frames yourself…

Notice that the first sub-segment actually covers three sub-segments.

This would be the case if, for example, the move to the door included a (1) turn away, (2) steady gaze away and (3) turn back sub-segments.

(As you move to the door, you might look at something else for a moment and then turn back.)

What’s crucial is that each sub-segment set cam_x and cam_z to position the camera. The height of the camera, cam_y, gets a default value (5 feet) way upstream, but any sub-segment can set it.

That would be necessary to change the camera’s height, say if going up a flight of stairs.

The point of view portion is a bit more complex, but follows the same principle of using #range clauses to break the segment into phrases of movement.

#switch (clock)
// 1 – steady gaze South
#range (time_1.u, time_1.v)
#declare pov_x = cam_x;
#declare pov_z = cam_z – 10;
#break
// 2 – turn CW to West
#range (time_2.u, time_2.v)
#local clk = adjclock(time_2.u, time_2.v);
#local ang = radians(270 – (90 * clk));
#declare pov_x = cam_x + (10 * cos(ang));
#declare pov_z = cam_z + (10 * sin(ang));
#break
// 3 – turn to look at Enterprise
#range (time_3.u, time_3.v)
#local clk = adjclock(time_3.u, time_3.v);
ShiftPOV(clk, <cam_x-10,cam_y,cam_z>, enterprise_pov)
#break
…etc…
#end

Most importantly, just as the camera position part did, it must set the point of view. Specifically, it must set the pov_x, pov_y and pov_z variables. (Under the assumption that looking level is a common default, pov_y is also set upstream to be identical to camera height.)

There are other ways of moving the camera around…

Because the camera variables are set first, it makes it very easy to set the point of view relative to the camera, either for a steady gaze or for a rotating gaze. The trickiest thing is shifting the point of view from one specific coordinate to another specific coordinate.

The look_at attribute of the camera object makes it easy to fix the camera’s point of view on a specific point. If you move the camera, it naturally turns to keep its gaze on that point. To shift smoothly from (or to) a specific POV, you need to calculate a series of POVs that take you smoothly from one to the other.

Which brings me to the MoveCAM and ShiftPOV macros.  The first one is very simple. It’s just an extension of the “adjusted range” thing. There’s a start point and an end point, and an evenly distributed series of points between. It looks like this:

#macro MoveCAM (clk, p1, p2)
#local start_x = p1.u;
#local start_z = p1.v;
#local end_x = p2.u;
#local end_z = p2.v;
#declare cam_x = start_x + (clk * (end_xstart_x));
#declare cam_z = start_z + (clk * (end_zstart_z));
#end

The second one is a bit more involved and involves trigonometry (which, contrary to popular belief, is both easy and fun (and useful))! It looks like this:

#macro ShiftPOV (clk, p1, p2)
#local v_p1 = p1 – <cam_x,cam_y,cam_z>;
#local v_p2 = p2 – <cam_x,cam_y,cam_z>;
#local p1_d = vlength(<v_p1.x, 0, v_p1.z>);
#local p2_d = vlength(<v_p2.x, 0, v_p2.z>);
#local pov_d = (p1_d * (1-clk)) + (p2_d * clk);
#local p1_a = atan2(v_p1.z, v_p1.x);
#local p2_a = atan2(v_p2.z, v_p2.x);
#local tot_a = p2_ap1_a;
#local pov_a = p1_a + (tot_a * clk);
#declare pov_x = cam_x + (pov_d * cos(pov_a));
#declare pov_z = cam_z + (pov_d * sin(pov_a));
#declare pov_y = p1.y + (clk * (p2.y – p1.y));
#end

This one generates two vectors from the camera position to the two points of view, determines the horizontal angle between them and uses the clock to set the point of view accordingly (as a fraction of the total angle).  It also adjusts the height of the POV smoothly from point to point.

3D modeling and animation has advanced to amazing heights. It would be a blast to play with the really advanced tools like the Pixar people do!

Something to recognize is that these macros are highly coupled to animation code.

For one thing, the first sets the camera position and the second sets the point of view (hence the use of #declare).

Also, the second one depends on knowing the camera position, since the view shift is relative to it! At the same time, if you use the same animation protocol consistently, you can re-use these macros.

To build a complex animation, break the whole into sensible segments, and break those into phrases (sub-segments) of individual movements. Use the tools above to move the camera and its point of view.

There is enough more to talk about, the ANI.INI file, what to do with the output “frames” and how I make MP4 movies, that I think this should be continued next time. (Also, I have a couple special announcements!)

Stay animated, my friends!

About Wyrd Smythe

The canonical fool on the hill watching the sunset and the rotation of the planet and thinking what he imagines are large thoughts. View all posts by Wyrd Smythe