This article explores the shadow rendering mechanism in three.js and shares some practical tips for performance and effect optimization, helping developers make the best trade-offs in different scenarios.
In 3D web applications, high-quality shadow rendering is crucial to creating a realistic scene. As one of the widely adopted WebGL frameworks, three.js provides developers with a variety of shadow rendering options, making it possible to create vivid and realistic lighting and shadow effects. However, achieving these visual enhancements often comes with a performance overhead, especially when dealing with complex scenes or running on low-end devices. Therefore, optimizing shadow rendering while ensuring image quality to enhance the user experience and maintain smoothness has become a core challenge. This article will analyze the shadow rendering mechanism in three.js and provide a series of practical optimization strategies to help developers achieve the best balance in different application scenarios.
When developing an avatar, the appropriate use of shadows can significantly enhance the model's three-dimensionality and realism. Adding shadows to the ground not only provides a reference point for the viewer's spatial orientation, but also significantly enhances the scene's spatial hierarchy and immersive experience.
Next, we will explore optimization methods for global shadows and specific implementations for ground shadows.
Global shadows rely heavily on the shadowMap provided by three.js. Setting up shadows is easy with just a few steps: enabling the shadowMap feature in your WebGLRenderer, defining the light source that casts shadows, and specifying which objects are responsible for casting or receiving shadows.
Using only the default shadow settings in three.js is easy, but the results are often unsatisfactory. Especially when developing for mobile platforms, due to performance limitations, it's necessary to further explore the shadow features of three.js:
In three.js, there are two main types of shadows: hard shadows and soft shadows. Hard shadows have sharp edges and are typically used to simulate scenarios with small light sources or when the light source is close to the object. Soft shadows have blurred edges, more closely resembling real-world shadow effects. These two shadow effects are achieved through different shadow map types. Below are the common shadow types:
Features: This is the most basic type of shadow, with fast computation speed and low performance overhead, but the effect is relatively simple. The generated shadow has no soft edges and presents a hard boundary.
Usage: Used for scenes with high performance requirements but not very concerned about shadow effects.
Features: Default shadow type, with relatively soft edges. A simple filtering technique was used to smooth out the edges of shadows.
Usage: Recommended for most cases, with good results and acceptable performance overhead.
Features: Based on PCFShadowMap, the softness of shadows has been further optimized to provide softer shadow edge effects, but the performance overhead will be higher.
Purpose: Used for scenes that require high-quality shadow effects.
Features: Using variance shadow mapping algorithm, it can generate high-quality and jagged soft shadows. Compared to PCF technology, it can produce smoother effects and avoid common shadow sampling problems. But this technology may produce a "halo" phenomenon.
Purpose: Suitable for high-quality shadow scenes, especially those that require soft gradient shadow effects.
From the preview image above, it can be seen that for BasicShadowMap and PCFShadowMap, the edges of the shadows have a lot of jagged edges. However, for PCFSoftShadowMap, in addition to having more performance overhead, there will also be obvious flickering of the edges when the character moves, and the edge blur radius is too large, resulting in less noticeable shadow effects. Although using VSMShadowMap can achieve relatively good results, it can lead to serious ghosting issues. Although this can be solved by adjusting the bias value of the shadow, excessive bias values can cause the depth test results of the shadow to shift too much, resulting in the shadow being incorrectly rendered too far away and producing unnatural visual effects.
As an H5 page on a mobile phone, in addition to ensuring basic visual effects, it also needs to optimize performance to run on more devices. In order to achieve the initial display effect without increasing performance costs, we have the following optimization ideas.
To achieve a good shadow effect, hard shadows cannot be used first, so BasicShadowMap is excluded;
Due to the high performance overhead of PCFSoftShadowMap and the limited improvement in its effectiveness, it has also been excluded; Finally, due to the difficulty in controlling artifacts, we chose to optimize based on PCFShadowMap.
In order to obtain better shadow edges, it is possible to optimize by increasing the resolution of shadowMap. However, increasing the resolution will inevitably lead to increased performance overhead. How to improve the quality of shadow edges without increasing the mapping resolution?
We all know that with the same resolution on screens of different sizes, the smaller the screen, the finer the display effect. DirectionalLight uses an orthogonal camera to determine the area where the shadow is rendered when generating shadows. The four boundaries of this camera (left, right, top, bottom) define the range of shadow maps. By narrowing these boundaries, the pixels of the shadow map can be more concentrated in the area where the shadow needs to be rendered, thereby improving the clarity of the shadow. In fact, in the virtual human scene, the user's main attention is focused on the head area, so as long as the shadow camera is focused on the head area, there is no need to obtain global shadows.
In the initial animation (Figure 2), in addition to the shadow on the face, there is also a shadow on the ground. Obviously, the ground shadow cannot be obtained by shining a beam of light specifically on the feet, which would make the overall light and shadow appear strange. So, how is the ground shadow achieved.
We have adopted different optimization methods for global shadows and ground shadows:
By selecting a reasonable rendering method for shadows, optimizing the field of view of shadow cameras, and optimizing the resolution of shadow maps, the quality of shadows can be significantly improved without significantly improving performance;
By obtaining depth information from the bottom perspective and combining it with a custom shader to generate ground shadows, there is no significant loss in page performance while achieving a relatively good effect.
In the future, by inserting model information into the webview, the performance of the phone can be graded based on the model, and targeted rendering solutions can be called to further improve the performance of the page while running smoothly. In order to achieve better shadow effects, the shadow camera of Three.js can also be extended to achieve multi camera shadowMap and other capabilities, further improving the shadow effect without adding too much load.