This chapter introduces tuning practices around Unity's graphics capabilities.
In the rendering pipeline, the cost of fragment shaders increases in proportion to the resolution at which they are rendered.Especially with the high display resolutions of today's mobile devices, it is necessary to adjust the rendering resolution to an appropriate value.
If Resolution Scaling Mode, which is included in the resolution-related section of Player Settings for mobile platforms, is set to Fixed DPI, thespecific DPI (dots per inch) The resolution can be reduced to target a specific DPI (dots per inch).
Figure 7.1: Resolution Scaling Mode
The final resolution is determined by multiplying the Target DPI value by the Resolution Scaling DPI Scale Factor value in the Quality Settings.
Figure 7.2: Resolution Scaling DPI Scale Factor
To dynamically change the drawing resolution from a script, call Screen.SetResolution.
The current resolution can be obtained at Screen.width or Screen.height, and DPI can be obtained at Screen.dpi.
List 7.1: Screen.SetResolution
1: public void SetupResolution()
2: {
3: var factor = 0.8f;
4:
5: // Get current resolution with Screen.width, Screen.height
6: var width = (int)(Screen.width * factor);
7: var height = (int)(Screen.height * factor);
8:
9: // Set Resolution
10: Screen.SetResolution(width, height, true);
11: }
Resolution settings at Screen.SetResolution are reflected only on the actual device.
Note that changes are not reflected in the Editor.
The use of translucent materials is controlled by the overdraw overdraw.Overdraw is the drawing of a fragment multiple times per pixel on the screen, and it affects performance in proportion to the load on the fragment shader.
Particularly when a large number of translucent particles are generated, such as in a particle system, a large amount of overdraw is often generated.
The following methods can be used to reduce the increased drawing load caused by overdraws.
In the Editor of the Built-in Render Pipeline, set the Scene view mode to Overdraw in the Editor of the Built-in Render Pipeline, which is useful as a basis for adjusting overdraw.
Figure 7.3: Overdraw mode
The Universal Render Pipeline supports the Scene Debug View Modes implemented in the Universal Render Pipeline since Unity 2021.2.
Increasing the number of draw calls often affects the CPU load.Unity has several features to reduce the number of draw calls.
Dynamic batching is a feature for batching dynamic objects at runtime.This feature can be used to consolidate and reduce draw calls on dynamic objects that use the same material.
To use it, go to Player Settings and select Dynamic Batching item in the Player Settings.
Also, in the Universal Render Pipeline, you can enable Dynamic Batching item in the Universal Render Pipeline Asset.However, the use of Dynamic Batching is deprecated in the Universal Render Pipeline.
Figure 7.4: Dynamic Batching Settings
Because dynamic batching is a CPU-intensive process, many conditions must be met before it can be applied to an object.The main conditions are listed below.
Dynamic batching may not be recommended because of its impact on steady CPU load.See below. SRP Batcher described below can be used to achieve an effect similar to dynamic batching.
Static batching is a function for batching objects that do not move in the scene.This feature can be used to reduce draw calls on static objects using the same material.
Similarly to dynamic batching, from the Player Settings, click on the Static Batching from the Player Settings.
Figure 7.5: Static Batching Settings
To make an object eligible for static batching, set the object's static flag flag of the object must be enabled.Specifically, the Batching Static sub-flag in the static flag must be enabled.
Figure 7.6: Batching Static
Static batching differs from dynamic batching in that it does not involve vertex conversion processing at runtime, so it can be performed with a lower load.However, it should be noted that it consumes a lot of memory to store the mesh information combined by batch processing.
GPU instancing is a function for efficiently drawing objects of the same mesh and material.It is expected to reduce draw calls when drawing the same mesh multiple times, such as grass or trees.
To use GPU instancing, go to the material's Inspector and click on Enable Instancing from the material's Inspector.
Figure 7.7: Enable Instancing
Creating shaders that can use GPU instancing requires some special handling.Below is an example shader code with a minimal implementation for using GPU instancing in a built-in render pipeline.
List 7.2: Shaders that support GPU instancing
1: Shader "SimpleInstancing"
2: {
3: Properties
4: {
5: _Color ("Color", Color) = (1, 1, 1, 1)
6: }
7:
8: CGINCLUDE
9:
10: #include "UnityCG.cginc"
11:
12: struct appdata
13: {
14: float4 vertex : POSITION;
15: UNITY_VERTEX_INPUT_INSTANCE_ID
16: };
17:
18: struct v2f
19: {
20: float4 vertex : SV_POSITION;
21: // Required only when accessing INSTANCED_PROP in fragment shaders
22: UNITY_VERTEX_INPUT_INSTANCE_ID
23: };
24:
25: UNITY_INSTANCING_BUFFER_START(Props)
26: UNITY_DEFINE_INSTANCED_PROP(float4, _Color)
27: UNITY_INSTANCING_BUFFER_END(Props)
28:
29: v2f vert(appdata v)
30: {
31: v2f o;
32:
33: UNITY_SETUP_INSTANCE_ID(v);
34:
35: // Required only when accessing INSTANCED_PROP in fragment shaders
36: UNITY_TRANSFER_INSTANCE_ID(v, o);
37:
38: o.vertex = UnityObjectToClipPos(v.vertex);
39: return o;
40: }
41:
42: fixed4 frag(v2f i) : SV_Target
43: {
44: // Only required when accessing INSTANCED_PROP with fragment shaders
45: UNITY_SETUP_INSTANCE_ID(i);
46:
47: float4 color = UNITY_ACCESS_INSTANCED_PROP(Props, _Color);
48: return color;
49: }
50:
51: ENDCG
52:
53: SubShader
54: {
55: Tags { "RenderType"="Opaque" }
56: LOD 100
57:
58: Pass
59: {
60: CGPROGRAM
61: #pragma vertex vert
62: #pragma fragment frag
63: #pragma multi_compile_instancing
64: ENDCG
65: }
66: }
67: }
GPU instancing only works on objects that reference the same material, but you can set properties for each instance.You can set the target property as a property to be changed individually by enclosing it with UNITY_INSTANCING_BUFFER_START(Props) and UNITY_INSTANCING_BUFFER_END(Props), as in the shader code above.
This property can then be set in C# to MaterialPropertyBlock API in C# to set properties such as individual colors.Just be careful not to use MaterialPropertyBlock for too many instances, as accessing the MaterialPropertyBlock may affect CPU performance.
SRP Batcher is a Scriptable Render Pipeline (SRP) that is used in the Scriptable Render Pipeline is a feature to reduce the CPU cost of rendering that is only available in the Scriptable Render Pipeline.This feature allows multiple shader set-pass calls that use the same shader variant to be processed together.
To use the SRP Batcher, you need to add the Scriptable Render Pipeline Asset from the Inspector of the SRP Batcher from the Inspector of the Scriptable Render Pipeline Asset.
Figure 7.8: Enabling the SRP Batcher
You can also enable or disable the SRP Batcher at runtime with the following C# code
List 7.3: Enabling SRP Batcher
1: GraphicsSettings.useScriptableRenderPipelineBatching = true;
The following two conditions must be met to make shaders compatible with SRP Batcher
For UnityPerDraw Universal Render Pipeline and other shaders basically support it by default, but you need to set up your own CBUFFER forUnityPerMaterial.
Surround the properties for each material with CBUFFER_START(UnityPerMaterial) and CBUFFER_END as shown below.
List 7.4: UnityPerMaterial
1: Properties
2: {
3: _Color1 ("Color 1", Color) = (1,1,1,1)
4: _Color2 ("Color 2", Color) = (1,1,1,1)
5: }
6:
7: CBUFFER_START(UnityPerMaterial)
8:
9: float4 _Color1;
10: float4 _Color2;
11:
12: CBUFFER_END
With the above actions, you can create a shader that supports SRP Batcher, but you can also check if the shader in question supports SRP Batcher fromInspector.
In the Inspector of the shader, click on the SRP Batcher item in the shader's Inspector is compatible the shader is compatible with SRP Batcher, andIf it is "not compatible is not compatible, it means it is not supported.
Figure 7.9: Shaders that are compatible with SRP Batcher
2D games and UIs often use many sprites to build the screen.In such cases, a function to avoid generating a large number of draw calls is SpriteAtlas to avoid a large number of draw calls in such cases.
SpriteAtlas reduces draw calls by combining multiple sprites into a single texture.
To create a SpriteAtlas, first go to the Package Manager and click on 2D Sprite must first be installed in the project from the Package Manager.
Figure 7.10: 2D Sprite
After installation, right click in the Project view and select "Create -> 2D -> Sprite Atlas" to create the SpriteAtlas asset.
Figure 7.11: Creating SpriteAtlas
To specify the sprites that will be made into an atlas, go to the SpriteAtlas inspector and select Objects for Packing item of the SpriteAtlas inspector to specify the sprite or the folder that contains the sprite.
Figure 7.12: Setting up Objects for Packing
With the above settings, the sprite will be atlased during build and playback in the Unity Editor, and the integrated SpriteAtlas texture will be referenced when drawing the target sprite.
Sprites can also be obtained directly from SpriteAtlas with the following code.
List 7.5: Loading a Sprite from SpriteAtlas
1: [SerializeField]
2: private SpriteAtlas atlas;
3:
4: public Sprite LoadSprite(string spriteName)
5: {
6: // Obtain a Sprite from SpriteAtlas with the Sprite name as an argument
7: var sprite = atlas.GetSprite(spriteName);
8: return sprite;
9: }
Loading a single Sprite in the SpriteAtlas consumes more memory than loading just one, since the texture of the entire atlas is loaded.Therefore, the SpriteAtlas should be used with care and divided appropriately.
This section is written targeting SpriteAtlas V1.SpriteAtlas V2 may have significant changes in operation, such as not being able to specify the folder of the sprite to be atlased.
In Unity, to omit in advance the processing of the parts that will not be displayed on the screen in the final version. culling process is used to eliminate the part of the image that will not ultimately be displayed on the screen in advance.
Visual Culling is a process that omits objects outside of the camera's rendering area, the viewing cone, from the rendering.This prevents objects outside the camera's range from being calculated for rendering.
Visual cone culling is performed by default without any settings.For vertex shader-intensive objects, culling can be applied by dividing the mesh appropriately to reduce the cost of rendering.
Rear culling is the process of omitting from rendering the backside of polygons that are (supposed to be) invisible to the camera.Most meshes are closed (only the front polygons are visible to the camera), so the backs of polygons do not need to be drawn.
In Unity, if you do not specify this in the shader, the back side of the polygon is subject to culling, but you can switch the culling setting by specifying it in the shader.The following is described in the SubShader.
List 7.6: Culling setting
1: SubShader
2: {
3: Tags { "RenderType"="Opaque" }
4: LOD 100
5:
6: Cull Back // Front, Off
7:
8: Pass
9: {
10: CGPROGRAM
11: #pragma vertex vert
12: #pragma fragment frag
13: ENDCG
14: }
15: }
There are three settings: Back, Front, and Off. The effect of each setting is as follows.
Occlusion culling is the process of omitting objects from the rendering that are not visible to the camera because they are occluded by objects.This function uses pre-baked occlusion data to determine if an object is occluded at run-time and removes the occluded object from the rendering.
To make an object eligible for occlusion culling, set the inspector's static flag to Occluder Static or Occludee Static from the inspector static flag.If Occluder Static is disabled and Occludee Static is enabled, the object will no longer be considered as the occluder, but only as the occluded object.In the opposite case, the object is no longer considered as a cloaked object and is processed as a cloaked object only.
Figure 7.13: static flag for occlusion culling
To pre-bake for occlusion culling, the Occlusion Culling window is displayed to pre-bake for occlusion culling.In this window, you can change the static flags for each object, change the bake settings, etc., and press the Bake button Bake can be performed by pressing the Bake button.
Figure 7.14: Occlusion Culling Window
Occlusion culling reduces rendering cost, but at the same time, it puts more load on the CPU for the culling process, so it is necessary to balance each load and make appropriate settings.
Only the object rendering process is reduced by occlusion culling, while processes such as real-time shadow rendering remain unchanged.
Shaders are very effective for graphics, but they often cause performance problems.
GPUs (especially on mobile platforms) compute faster with smaller data types than with larger ones.Therefore, floating-point types should be replaced with float type (32bit) to half type (16bit) is effective when it is possible to replace the floating-point type.
The float type should be used when precision is required, such as in depth calculations, but in color calculations, even if the precision is reduced, it is difficult to cause a large difference in the resulting appearance.
The vertex shader is executed for the number of vertices in the mesh, and the fragment shader is executed for the number of pixels that will eventually be written.In general, vertex shaders are often executed less frequently than fragment shaders, so it is best to perform complex calculations in the vertex shader whenever possible.
The vertex shader calculation results are passed to the fragment shader via shader semantics, but it should be noted that the values passed are interpolated and may look different than if they were calculated in the fragment shader.
List 7.7: Precomputation with vertex shaders
1: CGPROGRAM
2: #pragma vertex vert
3: #pragma fragment frag
4:
5: #include "UnityCG.cginc"
6:
7: struct appdata
8: {
9: float4 vertex : POSITION;
10: float2 uv : TEXCOORD0;
11: };
12:
13: struct v2f
14: {
15: float2 uv : TEXCOORD0;
16: float3 factor : TEXCOORD1;
17: float4 vertex : SV_POSITION;
18: };
19:
20: sampler2D _MainTex;
21: float4 _MainTex_ST;
22:
23: v2f vert (appdata v)
24: {
25: v2f o;
26: o.vertex = UnityObjectToClipPos(v.vertex);
27: o.uv = TRANSFORM_TEX(v.uv, _MainTex);
28:
29: // Complex precomputations.
30: o.factor = CalculateFactor();
31:
32: return o;
33: }
34:
35: fixed4 frag (v2f i) : SV_Target
36: {
37: fixed4 col = tex2D(_MainTex, i.uv);
38:
39: // Values computed in the vertex shader are used in the fragment shader
40: col *= i.factor;
41:
42: return col;
43: }
44: ENDCG
If the results of complex calculations in the shader are not affected by external values, then storing the pre-calculated results as elements in the texture is an effective way to do so.
This can be done by implementing a dedicated texture generation tool in Unity or as an extension to various DCC tools.If the alpha channel of a texture already in use is not being used, it is a good idea to write to it or prepare a dedicated texture.
For example, the LUT (color correspondence table) used for color grading, for example, will pre-correct the texture so that the coordinates of each pixel correspond to each color.By sampling the texture in the shader based on the original color, the result is almost the same as if the pre-correction had been applied to the original color.
Figure 7.15: LUT texture (1024x32) before color correction
ShaderVariantCollection can be used to compile shaders before they are used to prevent spikes.
ShaderVariantColletion allows you to keep a list of shader variants used in your game as assets.It is created by selecting "Create -> Shader -> Shader Variant Collection" from the Project view.
Figure 7.16: Creating a ShaderVariantCollection
From the Inspector view of the created ShaderVariantCollection, press Add Shader to add the target shader, and then select which variants to add for the shader.
Figure 7.17: ShaderVariantCollection Inspector
ShaderVariantCollection is added to the Graphics Settings Shader preloading in the Graphics Settings. Preloaded Shaders in the Shader preloading section of the Graphics Settings,to set the shader variants to be compiled at application startup.
Figure 7.18: Preloaded Shaders
You can also set the shader variants from a script by calling ShaderVariantCollection.WarmUp() from a script to explicitly precompile the shader variants contained in the corresponding ShaderVariantCollection.
List 7.8: ShaderVariantCollection.
1: public void PreloadShaderVariants(ShaderVariantCollection collection)
2: {
3: // Explicitly precompile shader variants
4: if (!collection.isWarmedUp)
5: {
6: collection.WarmUp();
7: }
8: }
Lighting is one of the most important aspects of a game's artistic expression, but it often has a significant impact on performance.
Generating real-time shadows consumes a large amount of draw call and fill rate.Therefore, careful consideration should be given to settings when using real-time shadows.
The following policies can be used to reduce draw calls for shadow generation.
There are several ways to reduce the number of objects dropping shadows, but a simple method is to use the MeshRenderer's Cast Shadows setting in MeshRenderer to off.This will remove the object from the shadow draw call.This setting is usually turned on in Unity and should be noted in projects that use shadows.
Figure 7.19: Cast Shadows
It is also useful to reduce the maximum distance an object can be drawn in the shadow map.In the Quality Settings Shadow Distance in the Quality Settings to reduce the number of objects that cast shadows to the minimum necessary.Adjusting this setting will also reduce the resolution of the shadows, since shadows will be drawn at the minimum range for the resolution of the shadow map.
Figure 7.20: Shadow Distance
As with normal rendering, shadow rendering can be subject to batching to reduce draw calls.See "7.3 Reducing Draw Calls" for more information on batching techniques.
The fill rate of a shadow depends on both the rendering of the shadow map and the rendering of the object affected by the shadow.
The respective fill rates can be saved by adjusting several settings in the Shadows section of the Quality Settings.
Figure 7.21: Quality Settings -> Shadows
The Shadows section allows you to change the format of the shadows, and the Hard Shadows will produce a clear shadow border, but with a relatively low load, while Soft Shadows is more expensive, but it can produce blurred shadow borders.
Shadow Resolution and Shadow Cascades items affect the resolution of the shadow map, with larger settings increasing the resolution of the shadow map and consuming more fill rate.However, since these settings have a great deal to do with the quality of the shadows, they should be carefully adjusted to strike a balance between performance and quality.
Some settings can be adjusted using the Light component's Inspector, so it is possible to change the settings for individual lights.
Figure 7.22: Shadow settings for the Light component
Depending on the game genre or art style, it may be effective to use plate polygons or other materials to simulate the shadows of objects.Although this method has strong usage restrictions and is not highly flexible, it is far lighter than the usual real-time shadow rendering method.
Figure 7.23: Pseudo-shadow using plate polygons
By baking lighting effects and shadows into textures in advance, quality lighting expressions can be achieved with considerably lower load than real-time generation.
To bake a lightmap, first add a Light component placed in the scene Mode item of the Light component placed in the scene to Mixed or Baked Mixed or Baked.
Figure 7.24: Mode setting for Light
Also, activate the static flag of the object to be baked.
Figure 7.25: Enable static
In this state, select "Window -> Rendering -> Lighting" from the menu to display the Lighting view.
The default setting is Lighting Settings asset is not specified, we need to change the settings by clicking on New Lighting Settings button to create a new one.
Figure 7.26: New Lighting Settings
The main settings for lightmaps are Lightmapping Settings tab.
Figure 7.27: Lightmapping Settings
There are many settings that can be adjusted to change the speed and quality of lightmap baking.Therefore, these settings should be adjusted appropriately for the desired speed and quality.
Of these settings, the one that has the greatest impact on performance is Lightmap Resolution This setting has the largest impact on performance.This setting determines how many lightmap texels are allocated per unit in Unity, and since the final lightmap size varies depending on this value, it has a significant impact on storage and memory capacity, texture access speed, and other factors.
Figure 7.28: Lightmap Resolution
Finally, at the bottom of the Inspector view, the Generate Lighting button at the bottom of the Inspector view to bake the lightmap.Once baking is complete, you will see the baked lightmap stored in a folder with the same name as the scene.
Figure 7.29: Generate Lighting
Figure 7.30: Baked lightmap
It is inefficient to render objects that are far away from the camera in high-polygon, high-definition.Level of Detail (LOD) method can be used to reduce the level of detail of an object depending on its distance from the camera.
In Unity, objects are assigned to a LOD Group component to the object.
Figure 7.31: LOD Group
By placing a renderer with a mesh of each LOD level on a child of a GameObject to which a LOD Group is attached and setting each LOD level in the LOD Group, the LOD level can be switched according to the camera.It is also possible to set which LOD level is assigned to each LOD Group in relation to the camera's distance.
Using LOD generally reduces the drawing load, but one should be aware of memory and storage pressures since all meshes for each LOD level are loaded.
Unity's Texture Streaming can be used to reduce the memory footprint and load time required for textures.Texture streaming is a feature that saves GPU memory by loading mipmaps based on the camera position in the scene.
To enable this feature, go to the Quality Settings Texture Streaming in the Quality Settings.
Figure 7.32: Texture Streaming
In addition, the texture import settings must be changed to allow streaming of texture mipmaps.Open the texture inspector and select Advanced Streaming Mipmaps Streaming Mipmaps in the Advanced settings.
Figure 7.33: Streaming Mipmaps
These settings enable streaming mipmaps for the specified texture.Also, in the Quality Settings Memory Budget under Quality Settings to limit the total memory usage of the loaded textures.The texture streaming system will load the mipmaps without exceeding the amount of memory set here.