Smooth Sprite Animation using 3D Textures in OpenGL
Sprite animations can be rendered in various ways. More commonly used techniques use 2D textures and discretely display one frame at a time (for example on a quad). This is usually done by loading each frame of the animation into a seperate 2D texture and binding them as required, or by putting all frames into a single 2D texture and moving texture coordinates. Smooth animations require a high number of frames this way.
Alternatively, you can use a 3D texture, which gives you linear filtering between frames, i.e. frames are blended into each other. A 3D texture can increase visual quality and create smooth animations with fewer frames. The following describes the idea and shows a few examples.
How It Works
Imagine the 3D texture as a stack of 2D textures where the animation frames are layered on top of each other in the order of the animation. You then progress through the animation by moving the R-texture coordinate from 0 to 1. While 2D textures only apply linear filtering between pixels in the S and T dimension, a 3D texture also filters in the R dimension. Moving from one frame to next does not just display 100% of frame 1 and then 100% of frame 2. It dispays 100% of frame 1, then maybe 80% of frame 1 and 20% of frame 2, then 50% of both and so on. The previous frame slowly fades out while the next fades in.
2D vs. 3D
3D textures are not suitable for all kinds of animations. Fast moving animations usually look bad and start flickering. This is because between two frames, both are barely visible and only overlapping parts are fully visible.

Top: animation state of a rotating goblet with discrete frames
Bottom: animation state somewhere between two frames in a 3D texture

With regular discrete 2D textures the animation looks much clearer.

Organic animations, like fire, on the other hand look very choppy and static if a single frame is displayed at a time.
This is where 3D textures can shine. They are great for animations like explosions, fire, pulsating slime and other special effects. While the previous frame fades out, the next one fades in and the animation look very smooth. The effect is hard to capture in an animated GIF, so you best see it rendered in realtime.
Click here to launch the Java Web Start comparison of 2D vs. 3D texture animation
(requires: Java, OpenGL 1.2, Win/Linux/Mac OS X/Solaris)

The top two quads are rendered with 2D textures and the bottom two quads are rendered with a 3D texture. Key W speeds the animation up and S slows it down. Space bar toggles between displaying both, 2D-only and 3D-only. There are three animations which can be switched by pressing keys 1, 2 and 3:
- 1 is a 16 frame fire
- 2 is 32 frame explosion
- 3 is 19 frame rotating goblet
On an interesting side note, rendering the 3D-textured quads is not slower than the 2D alternative, at least on my computer.
Code example (in Java/LWJGL)
Using 3D textures to animate your sprites is very easy. The following steps are written in Java and use the Lightweight Java Game Library (LWJGL), but they are essentially the same in any OpenGL library.
-
If you want to use animations in which the number of frames is not a power of two, you have to check whether the OpenGL driver supports the extension GL_ARB_texture_non_power_of_two. This is necessary because the number of levels - the R dimension, the animation frames - in a 3D texture follows the same rules as the S and T dimensions.
ContextCapabilities capabilities = GLContext.getCapabilities(); if (!capabilities.GL_ARB_texture_non_power_of_two) { ... // Don't use animations with non-POT frame-count } -
Load the frames into a ByteBuffer that will be passed to OpenGL's glTexImage3D. Simply take the data of each frame and append it to one buffer in the order of the animation:
ByteBuffer threeDTextureBuffer = null; for (int i = 1; i < frames + 1; i++) { ByteBuffer currentFrameData = ... // Load frame i if (threeDTextureData== null) { threeDTextureData = BufferUtils.createByteBuffer(currentFrameData.limit() * frames); } threeDTextureBuffer.put(imageBuffer); } threeDTextureBuffer.rewind(); -
Create a 3D texture:
IntBuffer texNameBuffer = BufferUtils.createIntBuffer(1); GL11.glGenTextures(texNameBuffer); int threeDTexture = texNameBuffer.get(0); GL11.glBindTexture(GL12.GL_TEXTURE_3D, threeDTexture); GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR); GL11.glTexParameteri(GL12.GL_TEXTURE_3D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR); GL12.glTexImage3D(GL12.GL_TEXTURE_3D, 0, GL11.GL_RGBA, frameWidth, frameHeight, frames, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, threeDTextureBuffer); GL11.glEnable(GL12.GL_TEXTURE_3D);
Setting the filter to GL_LINEAR is important to achieve the desired effect.
-
Animate texture coordinate R:
r = (r + rIncreasePerMillisecond * timeSinceLastFrame) % 1.0f;
If you have a fixed frame rate, you can just advance r by a fixed value.
-
Draw the texture on a quad:
GL11.glBegin(GL11.GL_QUADS); GL11.glTexCoord3f(0.0f, 0.0f, r); GL11.glVertex3f(0.0f, 0.0f, 0.0f); GL11.glTexCoord3f(0.0f, 1.0f, r); GL11.glVertex3f(0.0f, 1.0f, 0.0f); GL11.glTexCoord3f(1.0f, 1.0f, r); GL11.glVertex3f(1.0f, 1.0f, 0.0f); GL11.glTexCoord3f(1.0f, 0.0f, r); GL11.glVertex3f(1.0f, 0.0f, 0.0f); GL11.glEnd();
That is all you have to do.