Optimize Rendering Performance
WebGL1.0 or WebGL2.0
WebGL2.0 is largely compatible with WebGL1.0, but it is not fully backward compatible with WebGL1.0.
WebGL2.0 has system requirements, and its user share and compatibility are not as good as WebGL1.0.
For Unity, the Shaders generated by WebGL1.0 and WebGL2.0 are different, each with pros and cons.
WebGL2.0 uses Uniform Buffer to manage the UnityPerDraw and UnityPerMaterial properties. Unity's built-in Shaders have a large number of redundant properties that a game may never use.
WebGL1.0 removes material properties that are not used.
If the game content is limited, the materials are relatively simple, and the game does not depend on WebGL2.0 features such as GPU Instancing, then WebGL1.0 is sufficient.
If the game needs to depend on WebGL2.0 features such as GPU Instancing, then WebGL2.0 must be used.
Using WebGL2.0 does not necessarily perform better than WebGL1.0—it's not guaranteed. If conditions allow, it is recommended to build the corresponding packages separately and compare performance.
Which Render Pipeline to Choose
For a newly developed game, how should you choose among Unity's built-in pipeline, SRP, URP, and HDRP?
Built-in pipeline, suitable for minigames Advantages: the default option, ready to use, full-featured. Disadvantages: fixed, inflexible, targets all platforms, not streamlined enough.
SRP (Scriptable RP), suitable for minigames Advantages: flexible, fully customizes the render pipeline through C# scripts, very little redundant functionality. Disadvantages: cannot be used directly; you need to develop the render pipeline through C# scripts, which has high requirements for developers.
URP (Universal RP), a general-purpose render pipeline based on SRP, suitable for minigames Advantages: ready to use, requires only simple configuration. Disadvantages: general-purpose, has more redundant functionality, not streamlined enough.
HDRP high-definition render pipeline, suitable for console platforms, not suitable for minigames For most developers, URP is recommended. Of course, use URP's complex features as little as possible, because minigames have relatively high performance requirements. For features you don't use, you can customize on top of URP.
For developers who have high performance requirements and are familiar with rendering, you can use SRP to customize the pipeline.
Linear Color Space or Gamma Color Space
Currently Unity only supports linear color space with WebGL2.0; if you choose WebGL1.0, it will necessarily be Gamma color space.
So, if you have chosen WebGL2.0, should you use linear or Gamma color space?
Lighting calculations, especially PBR, need to be performed in linear color space, so if you use PBR rendering, linear color space is more convenient.
But with linear color space, Unity adds an extra full-screen Gamma correction Pass, which has a relatively noticeable impact on performance.
This issue has been reported to Unity; as of at least Unity 2021.3.23, the problem still exists.
Therefore, unless you use PBR lighting, Gamma color space is recommended for everything else.
Some Recommendations for Rendering Performance Optimization
If there is no need for lighting and shadows, confirm that the game's lighting (including ambient light) and shadows are all turned off. Avoid using lighting whenever possible; for materials, refer to URP's Unlit shader.
If you do need lighting and shadows, try to use baked Lightmaps and avoid real-time lighting. When you need real-time light, it is generally recommended to use only one directional light and not additional real-time lights.
Additional lighting information can be baked into the Lightmap. Keep lighting materials as simple as possible and avoid using PBR lighting; refer to URP's SimpleLit Shader.
Unity's shadows use the Cascade Shadow Map solution by default.
Set the shadow distance (from the camera) as small as possible; shadows on distant objects are hard to notice anyway, so the shadow map can be smaller.
The smaller the shadow map, the better; it is recommended to start with 256 to see if it fits, then gradually increase it, preferably not exceeding 1024.
The number of Cascade levels is also recommended to be 1~2; it is best if 1 level meets the requirements.
Separate objects that cast shadows from those that receive shadows; only set objects that need to cast or receive shadows.
If the ground is flat, use projected planar shadows to represent shadows instead of Unity's shadow solution; this performs better.
Baking the Lightmap may generate environment reflection cubemaps; if you already have a Lightmap, try not to also use environment reflection cubemaps.
Materials and Shaders
Avoid using Unity's default materials. When the material in the Inspector window is missing, Unity automatically uses the built-in default material.
You can download the corresponding version of the builtin_shaders source code from the Unity official website.
Look at what the default material Shader is, copy one and simplify it as much as possible, and use it to create a material to replace the default material.
The benefit is: default materials generally have some redundant settings, while simplified materials perform better and are easier to modify.
Avoid using Mask materials (such as the Unity Mask and RectMask2D components).
A common case is using a circle to create a Mask for avatars.
Masks may usually break the batching of the UI system, or be unfriendly to hardware optimization.
As an alternative, you can use a meshed circle, which performs better on mobile.
The material Shader may have Stencil settings, generally used to achieve effects such as masking. If not needed, it is recommended to comment out the Stencil-related code in the Shader; otherwise there may be additional API call overhead.
- On iOS 15.4, using Stencil may cause some rendering anomalies.
Avoid using structural statements such as if and for.
- The if statement can try to use the step built-in function as a replacement. If it really cannot be replaced and the if statement is short, it can also be used, but if the if statement is complex, it is not recommended.
If you use Shader Graph, it is recommended to check the finally generated shader code and avoid statements such as if and for mentioned above.
Pay attention to floating-point precision usage. If half precision (half) can be used, use it whenever possible. Generally, coordinate values and uv definitions or calculations may need full precision, while others such as colors and normals are generally fine with half precision.
Reduce unnecessary material variants.
Check the shader code's #pragma multi_compile, #pragma multi_compile_local, #pragma shader_feature, and shader_feature_local statements, and remember to delete any keywords that are not used.
Texture Settings
It is recommended to keep the number of textures referenced by a material within 5.
The smaller the texture size, the better, of course. For a single image, try not to exceed 512; for an atlas, try not to exceed 1024.
Use compressed textures whenever possible; astc compression is recommended. It has multiple compression formats; while meeting quality requirements, choose the format with the higher compression ratio.
- For example, if astc88 has a higher compression ratio than astc44 and meets quality requirements, then choose astc8*8.
UI generally prioritizes visual quality and does not use compressed textures, but some UI textures may not require high fineness and can try compressed textures, which improves memory and game loading speed.
For small images such as icons, try to combine them into an Atlas.
If WebGL2.0 is enabled, for textures of the same size, consider combining them into a Texture2D Array.
Mesh and Skinning
If the game scene is relatively complex and the total face count is high, for example over 500,000 faces, then be careful whether vertex shading becomes a bottleneck.
You can build an App package. If it is an iOS package, you can use the XCode frame-capture tool to analyze the time spent on Vertex Shading and Pixel Shading of a scene frame. Generally Pixel Shading accounts for the majority; if the time of Vertex Shading is close to that of Pixel Shading, it means there is room to optimize Vertex Shading, and you can try to optimize the vertex Shader or reduce the total face count.
If there is vertex skinning, Unity WebGL currently does not support GPU skin, nor does it support CPU multithreading. If there are too many vertices or bones, CPU skin can easily become a bottleneck.
Ways to avoid bottlenecks include: reducing the model face count (you can use Mesh LOD), reducing the number of bones, or reducing the number of bone weights affecting each vertex.
Later Unity versions may support GPU skin or multithreading, but you still need to pay attention to skin overhead.
DrawCall
It is recommended to keep the number of Draw Calls within 200; for large games this can be relaxed appropriately, but try not to exceed 250.
Unity's statistics have the concept of SetPass Call, which is different from Draw Call. SetPass Call refers to the number of times the render state is switched. SetPass Call should be significantly smaller than the number of Draw Calls, which indicates a high probability of material reuse.
If you use WebGL2.0, try to use GPU Instancing to merge Draw Calls.
If you use WebGL2.0, URP enables the SRP Batcher by default. It cannot reduce the number of Draw Calls; its purpose is to reduce render state switching. If there are many render states, the benefit of the SRP Batcher should be relatively noticeable. Developers are advised to specifically test the benefit of the SRP Batcher (comparing frame rate, CPU utilization, and memory usage).
Camera
Avoid enabling more than 2 Cameras at the same time; generally just the main Camera, plus at most one additional UI Camera.
If only one Camera is used to draw the main scene and UI, that is best, as it reduces rendering overhead.
Rendering Resolution
If the phone heats up severely, you can appropriately lower the rendering resolution to see whether the heating improves.
Taking iOS as an example, if the dpr (Device Pixel Ratio) defaults to 3.0, you can try setting it to [2.0 ~ 2.5].
Post-processing
The overhead of post-processing is not small, and WebGL games have a gap in available memory and performance compared with APPs.
It is not recommended to use post-processing in minigames, unless post-processing is critical to visual quality or the game's performance can support the overhead of post-processing.
Effects
Avoid Overdraw caused by a large number of large-screen semi-transparent effects overlapping, especially when releasing skills in combat, where this situation easily occurs and needs attention.
