Sign up for the KDAB Newsletter
Stay on top of the latest news, publications, events and more.
Go to Sign-up
Many new features were added to Qt 3D in the 5.10 release. One of them is the support for sprite sheets, contributed by KDAB, as provided by QSpriteGrid
and QSpriteSheet
and their respective QML items.
One way of animating things is to switch between many different versions of the same object at different points in time, like the flip books we all enjoyed as kids. If you flip fast enough, you get the illusion of animation.
In the context of OpenGL and Qt 3D, images are simply textures and are very commonly used to add details to 3d models. The naive approach to animating texture would be to use lots of them. However, switching textures has a very high cost so traditionally modellers will use texture atlases, where all the images are arranged into a single texture. This does complicate modelling slightly as the original texture coordinates need to be modified to point to the portion of the atlas that now contains the relevant image.
In effect, sprite sheets are simplified atlases that take care of this for you. They are commonly used in 2d or 2.5d applications to animate effects or characters.
Simple sprite sheets are just regular grids of images.
However, as with general atlases, all individual images need not be the same size or be arranged in a grid. In that case, individual sprites need to be specified by their bounding rectangle within the texture.
There's a number of applications that can be used to create them. Blender can output steps in animations to a sprite sheet. TexturePacker can also be used to assemble pre-existing images.
In the simplest case, a sprite will be mapped on to planar surfaces, a simple rectangle. When applying textures to a surface, you of course need to provide texture coordinates, mapping points to pixels in the texture. In the case of the PlanarMesh, precomputed texture coordinates will map to textures such as it covers the entirety of the surface.
However, if the source texture is a sprite sheet, then the texture coordinates do not work any more as we want to cover the specific sprite, not the entire sheet.
One thing you do NOT want to do is replace the texture coordinates every time you want to change sprite.
In effect, texture coordinates need to be:
This transformation can easily be encoded in a 3x3 matrix which is applied to texture coordinates in the vertex shader. In order to support this, QTextureLoader
has been extended with a textureTransform
parameter which is passed to the shader as a uniform.
A sprite sheet is conceptually very simple in Qt 3D. It has:
So simple animations can be achieved by changing the current sprite index and binding the texture transform to the matching property in the QTextureLoader
instance.
QSpriteGrid
is used when sprites are arranged in regular grid.
Entity {
PlaneMesh {
id: mesh
}
TextureMaterial {
id: material
texture: TextureLoader {
id: textureLoader
source: "spritegrid.png"
mirrored: false
}
textureTransform: spriteGrid.textureTransform
}
SpriteGrid {
id: spriteGrid
rows: 2; columns: 6
texture: textureLoader
}
components: [ mesh, material ]
}
Images in the grid are assumed to be arranged in row major order. So currentIndex
must remain between 0 and rows * columns
.
The texture
property points to the image containing the sprites. The current index, number of rows and columns and the actual size of the texture are all used to compute the texture transform.
QSpriteSheet
is used when the sprites are not all the same size and/or are not organised in a grid. You then need specify the extent of each sprite.
Entity {
PlaneMesh {
id: mesh
}
TextureMaterial {
id: material
texture: TextureLoader {
id: textureLoader
source: "spritegrid.png"
mirrored: false
}
textureTransform: spriteGrid.textureTransform
}
SpriteSheet {
id: spriteSheet
texture: textureLoader
SpriteItem { x: 0; y: 0; width: 250; height: 172 }
SpriteItem { x: 276; y: 0; width: 250; height: 172 }
SpriteItem { x: 550; y: 0; width: 250; height: 172 }
//...
}
components: [ mesh, material ]
}
The currentIndex
must remain between 0 and the number to QSpriteItem
children.
In this example, we use a sprite grid which contains frames of an explosion. A timer is used to change the current index. While the animation runs, the object is faded out.
Looking straight on, you see a rather nice effect. The illusion becomes clear when you look at it sideways and see the plane on which the texture is mapped.
Notes:
QSpriteSheet
doesn't currently support rotated sprites.QTextureMaterial
does not currently support alpha blending, so transparent portions of the textures will appear black. This can worked around by building a custom material and will be fixed in 5.11.QTexture2DArray
. However, in this case all images need to be the same size. But the only limit then would be the maximum texture size and the amount of texture memory available...Stay on top of the latest news, publications, events and more.
Go to Sign-up
Upgrade your applications from Qt 5 to Qt 6 with KDAB’s migration services. Get a free migration assessment and join a hands-on workshop to prepare your team for a successful transition!
Learn more
7 Comments
27 - Mar - 2018
Will
Happy to see some info on this. The API docs are still a bit spartan so I couldn't figure out how to use it when I ran across it. Would also love to see these examples fleshed out with C++ example code for these kinds of posts! I sometimes find myself scratching my head trying to figure out how to translate QML to C++ because I am not as familiar with QML.
28 - Mar - 2018
Mike Krus
The example I built for this is indeed in QML, mostly because I'm using QtQuick to drive the animation. Time permitting I'd like to write a C++ version which would use Qt3D's own animation engine to do the work...
28 - Mar - 2018
Elvis Stansvik
Nice write-up. One nitpick:
"So currentIndex must remain between 0 and rows * columns."
I think you mean rows * columns - 1 right? Or maybe write it as "in range [0, rows * columns)".
Also, the way it's written ("So...") you make it sound like it's because they are in row major, but it would hold even if it's column major order, so I suggest dropping to "So".
28 - Mar - 2018
Mike Krus
yes, you are correct about the range
30 - Mar - 2018
Pavel
What is the difference between SpriteGrid and AnimatedSprite qml types? They look very similar. Does SpriteGrid provide significant performance benefit?
31 - Mar - 2018
Mike Krus
While they may appear to do the same thing, they are completely different beasts. AnimatedSprite is to use with QtQuick, SpriteGrid can only be used in Qt 3D scenes.
12 - Apr - 2019
Marcel
Thanks, this is really cool! I'm wondering: Is there a way to create dynamic textures from a QImage? I tried to use a TextureLoader with a source pointing to a custom QQuickImageProvider, but nothing appears on screen.