Optimizing scenes for better WebGL performance

Here we recommend some optimization techniques that proved to work well for creating web-based interactive experiences. This chapter is mostly based on Soft8Soft's presentation at the conference Verge3Day Europe 2019.

Geometry / Meshes

Geometry lies at the root of a 3D application as it forms the main shape of a model. To get smoother reflections and faster rendering you should keep the mesh as regular as possible. In the beginning, you should decide on the level of details you want to have in your scene, and stick to that when modeling.

Recommended topology for WebGL applications

When modeling creases, better use smooth groups instead of adding more polygons.

Using smooth groups to implement geometry creases

When working on a cylindrical model, make effort to reduce the number of polygons by its center.

Optimizing cylindrical models for WebGL

Do not overload a model with extra details that the user won't see anyway. As shown on the picture below, the edge highlighted with orange defines the level of details for the whole model, so you can use at as reference.

Managing level of detail for real-time

Normal Maps

A common way to optimize WebGL performance is to reduce the amount of polygons by baking a normal map from a high-poly model to a low-poly model.

Using normal maps for optimizing WebGL scenes

However, normal maps may produce visible artifacts due to the limited precision of a 8 bit image. Some possible solutions are pictured below, but they are rather impracticable: using a higher precision image would produce a heavier file, while the second approach is rather time-consuming and does not guarantee a clean result. The third approach however may work in some cases: if you have rather rough surfaces we recommend to add noise to your materials to reduce those artifacts.

Normal map artifacts and possible solutions

From our experience, we found that the best solution for glossy objects would be using middle-poly geometry with smooth groups, and without any normal map.

Recommended way to deal with normal map artifacts

Finally, here are some cases when you might want to use a normal map rather than a highly detailed mesh:

When to use normal maps with WebGL applications

Texturing

Here is a typical set of textures used in the PBR pipeline (and in general).

Common textures used in WebGL apps

As you can see, most of them are black and white. Therefore you may combine b/w textures into the RGBA channels of a single image, up to 4 maps per image.

Packing textures into RGBA channels of a single image

If you only have one b/w texture you may combine it with any existing RGB texture by packing it into the alpha channel. Finally, if you have no image to combine with, you can convert your b/w image into jpeg format with 95% compressing and the grayscale mode enabled.

Packing black/white textures into alpha channel

Another way to reduce the size of texture is to optimize the UV space. The more compact is your UV unwrapping, the more effectively your image will use the texture space. Therefore you can have smaller images without losing any quality.

Optimizing UV space for better WebGL performance

Vertex Colors

Using vertex colors instead of images is an efficient way to speed up the loading and improve the overall performance of your WebGL applications. Although it comes at the expense of additional edges which you have to add to your model in order to separate different vertex colors.

Using vertex colors to optimize WebGL performance

You can also use vertex colors to define roughness, metalness or specular surfaces, or any other parameters. Below you can see an example of such a material where only vertex colors are used.

Using vertex colors in PBR pipeline

Number of Shaders

This is very beneficial to have less different materials/shaders in your scene. Shader processing in WebGL leads to prolonged loading, which is especially noticeable on Windows. Also if you have less shaders, the engine will spend less time on switching between them while rendering, thus improving the performance.

If you have similar materials that only differ by textures, you can use only one material and load/swap its textures at run time. For this, you can use the replace texture puzzle or do it with JavaScript. This not only will optimize the number of shaders but also will reduce the number of images loaded at application startup.

Replacing textures at run time with Verge3D Puzzles

Here's an example of such the optimization. All these tires are represented by only one material and configured by swapping its textures.

Example of replacing textures at run time

In order to reduce the number of shaders, you can combine 2 or more simple materials into one bigger material. This technique is especially effective if you plan to switch between these materials (e.g. you are making a configurator app), because it works faster this way and also allows for animated transitions.

Mixing shaders in WebGL

Draw Calls

In addition, there is another important aspect - the number of draw calls. This number can be obtained from the Geometry Buffers section of the print performance info puzzle's output. This roughly corresponds to the number of separate objects if only one material is assigned per object, while multi-material objects require even more draw calls to render them.

Therefore, you should strive to join meshes when possible, and use less unique materials, in order to reduce the number of draw calls and improve the performance.

Reducing the number of materials for WebGL

If you have an animated object, you can still join its parts together and use bones for animation, which is sometimes even more convenient when animating separate objects.

Using armatures to reduce the number of draw calls in your 3D app

HDR Lighting

It helps a lot improve the performance if you lighten your scene by an HDR image only, without using any light sources. An HDR file may weight less than 1 Mb.

Using HDR environment texture instead of light sources for better WebGL performance

Shadows

Use dynamic shadows only when they help present your object nicely. On the picture below, dynamic shadows used in the Industrial Robot demo emphasize the shape of the robot model. The model itself is allowed to be rotated so the shadows cannot hide any part of the object from the user. On the other hand, shadows in the Scooter demo would cloud many details.

Comparison of dynamic and baked shadows in different cases

If your object does not move in your app, you may bake shadow and ambient occlusion maps and apply them to the plane under the object.

Using baked textures as replacement for ambient occlusion and shadows

See Also

Check out the Performance Bottlenecks section to learn how to spot performance bottlenecks in your app and the Asset Compression section to find how to make your scenes even more compact.

Got Questions?

Feel free to ask on the forums!