I spent some time looking for alternatives that could provide the same, or similar, level of flexibility and control, without the speed implications. My research led me eventually to Lua. I'd already looked at Lua some time ago, in fact I've already written a Lua binding once in the past (but forgot to back it up, so couldn't find it this time round).
I've spent some time working on it this time before posting, so much that I have a workable animation framework well and truly underway. There is a lot of 'legwork' to get all the necessary features in, but each part of the framework has be at least partially tested and proven, and what's more Lua is proving to be incredibly fast.
The framework I'm working on bears not a passing resemblance to Steve May's AL, intentionally, as this has been a goal of mine almost as long as I've been working on Aqsis. I was drawn to AL at around the same time as I was drawn to RenderMan, but the RenderMan side took precedence.
As it stands, the framework is able to run relatively simple scripts to produce RIB (and OpenGL to a degree) animation output. Much like AL, the system has a number of key technologies/advantages that make it more usable than a naive RenderMan binding.
- Support for time dependent variables called avars. These look like any other variable when used in the script, except their value changes depending on the current time. This is the key concept to animation in the framework, by using these avars judiciously in the scene script, animation happens automatically.
- Automatic motion blur. There is no need in the scene script to specify anything related to motion blur, unlike in a naive binding. The framework automatically determines which parts of the scene are changing over time, validates that this change is within the constraints of RenderMan, i.e. it's not legal to have the number of points in a mesh change over time, and wraps the relevant parts in motion blocks.
As a quick example, here is a simple script that produces a procedural animation of a "scissor mechanism". The key points to note, over a traditional animation system, are how easy it is to adjust any part of the model, from the number of segments, to the width and thickness of the parts. Simply changing numbers results in the whole thing being recalculated next update.
function createBlade(length, width, thickness)
blade = Model("blade")
function blade:body(time)
local left = - width / 2
local right = width / 2
local top = - thickness / 2
local bottom = thickness / 2
TransformBegin()
Translate(0, 0, -length/2)
TransformBegin()
Rotate(90, 1, 0, 0)
Rotate(180, 0, 0, 1)
Cylinder(width/2, top, bottom, 180)
Disk(top, width/2, 180)
Disk(bottom, width/2, 180)
TransformEnd()
Polygon({ P = {left, top, 0, left, top, length, right, top, length, right, top, 0}})
Polygon({ P = {right, top, 0, right, top, length, right, bottom, length, right, bottom, 0}})
Polygon({ P = {right, bottom, 0, right, bottom, length, left, bottom, length, left, bottom, 0}})
Polygon({ P = {left, bottom, 0, left, bottom, length, left, top, length, left, top, 0}})
TransformBegin()
Translate(0,0,length)
Rotate(90, 1, 0, 0)
Cylinder(width/2, top, bottom, 180)
Disk(top, width/2, 180)
Disk(bottom, width/2, 180)
TransformEnd()
TransformEnd()
end
return blade
end
scissor = Model("scissor")
scissor.extension = scissor:avar("extension", {{0.0, 0.0}, {30.0, 1.0}})
scissor.segments = 5
scissor.bladelength = 2
scissor.bladewidth = 0.3
scissor.bladethickness = 0.1
function scissor:body(time)
local minangle = (self.bladewidth / self.bladelength) * (180/math.pi)
local maxangle = 90 - minangle
local blade = createBlade(self.bladelength, self.bladewidth, self.bladethickness)
for segment = 0, self.segments do
local angle = 90 - math.min(math.max((90 * self.extension(time)), minangle), maxangle)
local offset = self.bladelength * math.sin(math.rad(90 - angle))
TransformBegin()
Rotate(-angle, 0, 1, 0)
blade()
Rotate(2 * angle, 0, 1, 0)
Translate(0, self.bladethickness, 0)
blade()
TransformEnd()
Translate(0, 0, offset)
end
end
theWorld = World()
function theWorld:body(time)
scissor()
end
theCamera = Camera("main")
theCamera.pan = theCamera:avar("pan", {{0.0, 180.0}, {30.0, 270.0}})
theCamera.dolly = theCamera:avar("dolly", {{0.0, 4.0}, {30.0, 8.0}})
function theCamera:body(time)
Projection("perspective", {fov = 50})
Translate(0,0,self.dolly(time))
Rotate(-50, 1,1,0)
Rotate(self.pan(time),0,1,0)
Translate(0,0,-7)
end
theRenderer = RenderMan:create("aqsis")
theRenderer:renderIt{world=theWorld, camera=theCamera,
start=0, stop=30, incr=1,-- motion_blur=0.5,
ribfile="scissor.rib",
display_name="scissor_~s.tif",
display_type="framebuffer",
display_mode="rgb",
xres = 320,
yres = 240}
The script produces the following animation when rendered with Aqsis. Note: this version doesn't have motion blur, because it has uncovered a bug with Aqsis relating to camera motion blur.
I'm currently working on a GUI for this system that will allow the user to focus on just writing the code for the model/world/camera bodies, and leaving the boilerplate stuff to the system. It also includes an OpenGL renderer that renders from the same internal representation, so it's possible to have realtime preview of the animation, but that's the subject of another post.
If anyone is interested in this framework, and wants to play with it, or get involved, please respond to this post and I'll get in touch.
Paul