Texture Atlases in SceneJS
A texture atlas is a large image that contains many sub-images, each of which is used as a texture for a different geometry, or different parts of the same geometry. The sub-textures are applied by mapping the geometries’ texture coordinates (UV) to different regions of the atlas.
In a scene where there are many small textures, this has the benefit of reducing state changes on the graphics hardware by binding once, instead of for each individual texture. It also reduces the number of HTTP requests for texture image files.
SceneJS state-sorts its draw list by shader (which is normally auto-generated by SceneJS), then by texture, so as long as each of the geometry nodes within the texture's subtree inherit the same configuration of parent node states, and can therefore share the same shader, the draw list will bind the texture once for all the geometries.
- Read the Wikipedia page on texture atlases
- Learn more about texture atlases in this white paper from nVIDIA.
Example
To show how texture atlases are done in SceneJS, we'll define a texture containing two sub-textures that will be applied to two separate quads, as shown below. You can run the live example here.
Shown below is our atlas texture, texture-atlas.jpg. The guy on the left is General Zod, from Superman:
Now we'll define a fragment of scene graph. To start with, we have a material node, which wraps a texture, which is our atlas:
{
type: "material",
baseColor: { r: 1.0, g: 1.0, b: 1.0 },
specularColor: { r: 1.0, g: 1.0, b: 1.0 },
specular: 0.9,
shine: 6.0,
nodes: [
/*------------------------------------------------------------------
* Our texture atlas
*
* The atlas is the General Zod portrait and the brickwall pattern,
* arranged side-by-side within the same image file.
*-----------------------------------------------------------------*/
{
type: "texture",
layers: [
{
uri:"texture-atlas.jpg",
applyTo:"baseColor",
blendMode: "multiply"
}
],Next, we have a couple of rotate nodes, just to tilt our quads a little bit:
nodes: [
/*------------------------------------------------------------------
* Rotate our geometry a little bit to make it more interesting
*-----------------------------------------------------------------*/
{
type: "rotate",
angle: -30.0,
x : 1.0,
nodes: [
{
type: "rotate",
angle: -30.0,
y : 1.0,Next, we have the quads themselves, arranged along the X-axis with a couple of translate nodes. Although SceneJS has a special quad node, we'll manually define our quads in order to demonstrate how UV coordinates map to the textures. The first quad's UV coordinates map to the left half of the texture, to texture it with General Zod's image:
nodes: [
/* General Zod
*/
{
type: "translate",
x: 1.5,
nodes: [
{
type: "geometry",
positions : [ 1, 1, 0, - 1, 1, 0, -1, -1, 0, 1, -1, 0],
normals : [ 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1 ],
/* UV coords map to left half of texture image
*/
uv : [ 1, 1, .5, 1, .5, 0, 1, 0 ],
indices : [ 0, 1, 2,0, 2, 3]
}
]
},The second quad's UV coordinates map to the right half of the texture, to texture it with the brick pattern:
/* Brick wall
*/
{
type: "translate",
x: -1.5,
nodes: [
{
type: "geometry",
positions : [1, 1, 0, - 1, 1, 0, -1, -1, 0, 1, -1, 0 ],
normals : [ 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1 ],
/* UV coords map to right half of texture image
*/
uv : [ .5, 1, 0, 1, 0, 0, .5, 0 ],
indices : [ 0, 1, 2,0, 2, 3]
}
]
}
]
}
]
}
]
}
]
}So there we have it - a useful technique to improve rendering speed by reducing WebGL state changes, while also reducing loading time by reducing the number of HTTP requests.
Note that we can have textures of various sizes in an atlas and use UV coordinates to scale them to fit the geometry vertices.
Limitations
Take care if using mipmaps to arrange the textures so as to avoid sub-images being affected by their neighbours.
Note how SceneJS texture layers configure what geometry/material aspects their texture applies to (eg. "baseColor", "normals" etc). Therefore we can't currently have mixtures of those configurations within the same atlas, so we must have separate atlases for color-mapping, emission-mapping, normal mapping and so on.
Future Work
- It would be very cool to make a server-side utility in Python or Node.js to optimise a SceneJS JSON scene by by aggregating it's textures into atlases.
