Frame-rate independent drawing

Today I got around to beginning on the very early implementation of the renderer for ItsyScape. There’s going to be several stages: shadows, water, and scene, some of which with several passes (deferred and forward). The scene itself is a typical hierarchical scene graph. The graph is walked and nodes are sorted by materials to reduce state changes.

But that’s not the point!

One of the bigger features of the renderer is frame-rate independence. Like the game it’s based on, logic runs much slower than the playable framerate (it’ll probably be somewhere between 10 to 20 updates per second, which is must faster than the 600 ms ticks in OtherScape).

However, that’s not a problem! I store the current transform properties and the previous transform properties in the SceneNodeTransform object. Every time a logic tick occurs, all SceneNodeTransform objects push their current state to their previous state, and then the current transforms are updated by the game logic.

Here’s an example update function:

TICK_RATE = 1 / 10
function love.update(delta) — Accumulator. Stores time until next tick. Instance.time = Instance.time + delta

— Only update at TICK_RATE intervals. while Instance.time > TICK_RATE do Instance.SceneRoot:tick() — Rotate cube 360 degrees every 2 seconds Instance.SceneRoot:getTransform():rotateByAxisAngle(Vector(0, 1, 0), TICK_RATE * math.pi) — Handle cases where ‘delta’ exceeds TICK_RATE Instance.time = Instance.time – TICK_RATE — Store the previous frame time. Instance.previousTickTime = love.timer.getTime() end end

The rendering function then calculates a delta, in the range of 0 to 1 inclusive, between the last frame and the current time. All properties are then transformed by this delta:

function love.draw() local currentTime = love.timer.getTime() local previousTime = Instance.previousTickTime

— Generate a delta (0 .. 1 inclusive) between the current and previous frames local delta = (currentTime – previousTime) / TICK_RATE

local width, height = love.window.getMode() Instance.Renderer:getCamera():setWidth(width) Instance.Renderer:getCamera():setHeight(height)

— Draw the scene. Instance.Renderer:draw(Instance.SceneRoot, delta)

And voila…!

Buttery smooth, despite the rotation only being updated 10 times a second.