We optimize assets for WebGL correctly

  • Tutorial
It often happens that optimization techniques that work well for regular desktop or mobile graphics do not always give the desired effect in the case of WebGL. In this article, I collected (or rather translated into Russian and outlined in text form our presentation with Verge3Day) those productivity methods that worked well for creating interactive web applications.



Geometry / Meshes


Correct geometry is the key to the performance of any 3D application. To get even shading and fast rendering, you should keep the polygon mesh as uniform as possible. At the very beginning of the work, you should determine the level of detail of your scene and stick to it when modeling.



When modeling folds, it is better to use shading groups rather than adding more polygons.



When working with a cylindrical model, try to reduce the number of polygons closer to the center.



Do not overload the model with additional parts that the user still will not see. As shown in the figure below, the edge highlighted in orange determines the level of detail for the entire model, so you can use it as a guide.



Normal Maps


A common way to optimize WebGL performance is to reduce the number of polygons by creating normal maps.



However, normal maps can create visible artifacts due to the limited accuracy of the 8-bit image. Possible solutions to this problem are shown in the picture below, but they are rather difficult to implement: using the image with higher accuracy will lead to an increase in the size of the downloaded file, while the second approach is rather time-consuming and does not guarantee a clean result. The third approach sometimes works in the case of rough surfaces. In this case, we recommend adding noise to your materials to reduce possible artifacts.



Based on our experience, we concluded that the best solution for glossy objects is to use medium complexity geometry with smooth vertex groups, without using any normal map.



Finally, here are a few cases where you can use a normal map, rather than a very detailed mesh:

  • Your object consists of many different surfaces.
  • You have a rough surface that does not give visible defects.
  • Your objects are so distant or small that the user is not able to notice any artifacts.



Texturing


Here is a typical set of textures used in the modern PBR lighting model (and not only).



As you can see, most of them are black and white. Therefore, you can combine b / w textures in the RGBA channels of one image (up to 4 cards in total per image).



If you have only one b / w texture, you can combine it with any existing RGB texture by packing it in an alpha channel. Finally, if you don’t have an image to combine, you can convert your black and white image to jpeg format with 95% compression and grayscale enabled.



Another way to reduce texture size is to optimize the UV scan. The more compact your scan, the more efficient your image will use texture space. This allows you to have more lightweight images without loss of quality.



Vertex colors


Using vertex colors instead of images is an effective way to speed up loading and increase the overall performance of your WebGL applications. The only drawback is that you have to add a few extra edges to your model to separate the colors of the same vertices.



You can also use vertex colors to determine roughness, metallicity or mirror surfaces, or any other parameters. Below you can see an example of such a material, which uses only the colors of the vertices.



Number of shaders


It is very important that there are fewer different materials / shaders on your stage. Compiling shaders in WebGL leads to a long load, which is especially noticeable in Windows. In addition, if you have fewer shaders, the engine will spend less time switching between them during rendering, thereby improving performance.

If you have similar materials that differ only in textures, you can use only one material and load / change its textures at runtime. To do this, you can use JavaScript or take the visual logic editor available in some WebGL frameworks. This not only optimizes the number of shaders, but also reduces the number of images loaded when the application starts.



Here is an example of such optimization. All four varieties of one tire are represented by one material, and are configured by replacing textures.



To reduce the number of shaders, you can combine 2 or more simple materials into one more complex one. This method is especially effective if you plan to switch between these materials (for example, create a configurator application), and not just switch, but smoothly and beautifully animate the transition from one material to another.



Draw calls


In addition, there is another important aspect - the number of Draw calls (they are also draw calls and draw calls). This roughly corresponds to the number of individual objects, if only one material is assigned to each object, while objects with several materials require even more calls to visualize them.

Therefore, you should strive to merge grids whenever possible and use less unique materials to reduce the number of draw calls and improve performance.



If you have an animated object, you can still connect its parts and use the bones to animate, which is sometimes even more convenient when animating individual objects.



HDR lighting


This helps to significantly improve performance if you only illuminate your scene with an HDR image, without using any light sources. An HDR file can weigh less than 1 MB.



Shadows


Use dynamic shadows only when they help present your object in the best possible way. The image below shows the dynamic shadows from our Industrial Robot demo . Since the model itself rotates in this application, and not the camera, the shadows cannot hide any part of the object from the user and perfectly emphasize the shape of the robot. On the other hand, the same shadows in the Scooter demo will obscure many details of the model.



We conclude from this: if your object does not move in the application, you can bake shadow and ambient occlusion maps and assign them to the plane below the object. Such a solution will be more productive and look better than when using dynamic shadows.



That's all. If you have any other tips that might help with WebGL performance, write in the comments.

Also popular now: