Summary
Make a Atlas Material to allow rendering meshes with different material but with the same shading model (Standard, PBR) to be merged or rendered in the same bind.
Motivation
Rendering on web suffers from performance issues, there has been disucssions for years about reducing “draw calls”, which is draw commands sent from cpu to gpu to draw a mesh, and considered the major bottleneck towards high FPS on web.
But recent benchmarks show that binding materials can take considerable amount of time, even more than draw calls.
On this sample with the NodePerformanceTest.glb on sandbox, bindForSubMesh takes half the CPU time, while drawElements, the actual draw call, takes 0.8% the CPU time.
Tools like glTF-Transform can detect and merge materials with exactly the same props, and merge materials with baseColor only.
But merging materials isn’t always the option, since materials can have different prop, or even textures, the easist way to merge masterials with textures is Texture atlas, but this can not handle meshs with uvs out of the 0~1 range with customized wrapping.
Also, making texture atlas in models can lead to uv precision loss, especially for quantized attributes.
Goals
Make a material that can be constructed from multiple materials with the same shading model, generates texture atlas at runtime, and bind textures once per render for all meshes, and then dispatch draw calls for meshes.
The layout of underlying texture atlas should be generated with efficient algorithm like potpack.
The texture atlas should be constructed in GPU with copyTextureToTexture() or copyTexSubImage2D() whenever possible, and mipmaps should be copied from source texture whenever possible.
UV wrapping and transformation can be applied per-mesh, and transformed in fragment stage.
Underlying material should have the same texture channels, or missing texture channel could be filled with colors that makes shading result looks like without the channel.
Existing per-material props and per-mesh props can be addressed using a mesh texture which contains per-mesh rendering options, collected for each mesh, binded and updated once per frame.
UV process pesudo code
in vec2 uv;
void main() {
vec2 actualUV = applyTextureTransfrom(applyAtlas(applyWrapping(uv)));
vec4 color = texture(baseColor, actualUV);
}
Rendering process pesudo code
for each mesh:
collectMeshInfo();
updateMeshTexture();
bindMeshTexture();
bindMaterial()
bindLightAndShadow();
for each mesh:
bindGeometry();
bindMeshId();
dispatchDraw();
Non-Goals
- ShaderMaterial
- NodeMaterial
- MultiMaterial
- CustomMaterial
- MixMaterial
Alternatives
- Use bindless texture so it’s possible to render without the need to bind, but this is only for webgpu without even a draft spec.
- Unwrap the UV and bake it offline so all materials can become one, but this is complex and time consuming.
- Create texture atlas and merge materials before importing models, causing uv warpping issues and uv precision loss.
- Use BABYLON.TexturePacker to merge textures, and merge materials somehow.
Footnote
I know this change is huge and may not land in years, so I’ll just keep this as “draft”.



