1. Welcome to VRCat! Please note that this message board is not related to VRChat in any official manner. It is run entirely by the community, and moderated by the community. Any content present has not been reviewed by VRChat moderators or staff. Your use of these forums implies understanding of this message. Thanks!
    Dismiss Notice

Worlds SetPass, Draw Calls, and Batches Explanation

Discussion in 'Tutorials and Tools' started by TCL987, Sep 16, 2018.

  1. TCL987

    TCL987 New Member

    Nov 15, 2017
    Likes Received:
    This is a capture of an impromptu tutorial/info dump I wrote in the #world-optimization channel on the Official VRChat Discord.

    There isn't a recommended number of materials for a world like there is for a avatars because you generally do not see all of the materials for a world at at once.
    Instead you should use the Profiler (Window->Profiler) Rendering section (click Rendering on the left-hand side).
    The main values you need to understand are SetPass Calls, Draw Calls, Total Batches, and the Batched Draw Calls and Batches sections.
    The total verts and tris counts are also useful as some things like real-time lighting and shadows render the same mesh multiple times so this value can be much higher than the sum of all of the verts and tris in your scene.
    Draw Calls are issued every time Unity renders a mesh, but it's not a simple as one draw call per mesh. Materials split meshes into sub-meshes which are drawn separately.
    A SetPass calls is issued every time Unity needs to change the shader pass that is being used. It's important to note that this not the same as the number of unique shaders in view as most shaders have a few passes for handling different parts of the rendering. The main three are the ForwardBase, ForwardAdd, and ShadowCaster passes, but there are others (see https://docs.unity3d.com/Manual/SL-PassTags.html). The ForwardBase pass renders the model with lightmaps, light probes, vertex lights, ambient light, and the main directional light. The ForwardAdd pass is run once per real-time point, spot, or extra directional light. The ShadowCaster pass renders depth information for shadow maps, it is run for every light with real-time shadows, multiple times in the case of point lights and the main directional light.(edited)
    Additionally Unity renders opaque meshes in front-to-back order which means it will often have to switch back-and-forth between shader passes.
    Taking a look at this example scene consisting of 27 Unity cubes 26 of which are using the same material and one is not.
    With dynamic batching disabled this is 29 Draw Calls and 5 SetPass Calls. 2 Draw Calls and SetPass Calls are from Unity.
    So our 27 cubes are 27 Draw Calls and 3 SetPass Calls.
    There are only two materials and shaders in the scene (excluding the skybox) but it requires three SetPass Calls because Unity has to switch from the plain material to the tiled material and back.
    Adding a point light (no shadows) to the scene increase the Draw Calls to 56 and the SetPass Calls to 32. The Draw Calls for our 27 cubes have doubled because each cube is being rendered in two passes (one ForwardBase and one ForwardAdd). The SetPass Calls have increased from 3 to 30 because Unity is setting the ForwardAdd pass separately for each cube.

    Enabling shadows (soft or hard) increases the Draw Calls from 56 to 83-110 and SetPass Calls to 34-41, the exact values vary depending on the light position. This increase is due to Unity running ShadowCaster passes for each of the cubes inside its range (in this case all 27). The number varies even with all of the meshes in its range because this is a point light so it renders separate shadow maps for each direction (forward/back, left/right, up/down) which means some meshes may be rendered multiple times if they are visible from multiple directions.
    Enabling the Directional Light without shadows does not increase the number of SetPass or Draw Calls because the main directional light is rendered as part of the ForwardBase pass which is already being run.
    Enabling shadows on the Directional Light increases the Draw Calls to 231 and the SetPass Calls to 52.
    108 of the additional Draw Calls are from the directional light's four Shadow Cascades (4x27=108)
    In order to provide detailed shadows close up without wasting resolution in the distance directional light shadows are rendered multiple times at different distances (aka cascades) and are blended together over distance. The other 28 Draw Calls are for rendering the Main Camera's Depth Texture which is used to add screen space self-shadowing from the directional light and is automatically enabled when directional light real-time shadows are enabled. The extra 11 SetPass Calls are to set the various shaders used for the Shadow Cascades, Depth Texture, and Screen Space Shadowmap.

    Now there are a number of ways to reduce the number of SetPass and Draw Calls in your world.
    Dynamic Batching combines small meshes that share a material automatically at runtime, however due to the CPU cost of combining the meshes it is only used for meshes with very few vertices (usually 300 or less depending on the shader). This is enabled by default and is enabled in VRChat but is not usable for most meshes. These cubes have very few vertices so they are able to be combined by static batching when they share a material.
    This has decreased the Draw Calls from 231 -> 76.
    However if I replace the cubes with higher poly spheres they are no longer dynamically batched and the scene is back to 231 Draw Calls.
    If these spheres are static objects (they do not move) then I can mark them as Static Batching from the drop-down next to the static checkbox. This reduces the Draw Calls from 231 to 62. For more information on batching see: https://docs.unity3d.com/560/Documentation/Manual/DrawCallBatching.html
    Marking the spheres as Lightmap Static and baking Lightmaps reduces the Draw Calls and SetPass Calls to 4. This is because the lightmaps are read as part of the Forwardbase pass and with all of the lights baked it is no longer necessary to run any ForwardAdd or ShadowCaster passes. Baked lighting is its own topic so I won't go into it further but Xiexe has a good tutorial on it here: https://vrcat.club/threads/xiexes-lighting-tutorial-how-to-get-good-at-baked-lighting-101.2081/
    Reverting the scene back to our previous static batched meshes in real-time lights and shadows and adding another mesh as a wall between the camera and spheres gives us 45 SetPass and 67 Draw Calls.
    Marking only the wall mesh as Occluder Static and baking occlusion culling (Window->Occlusion) drops this to 17 SetPass calls and 24 Draw Calls.(edited)
    Note the spheres are not marked as Occludee Static, they are being dynamically occluded by the Occluder Static wall.
    Moving the camera behind the wall causes the spheres to appear and the SetPass and Draw Calls to increase to 46 and 65 respectively.
    Marking the spheres Occludee Static doesn't affect the SetPass or Draw Calls but should reduce the CPU cost of culling.
    The SetPass and Draw Calls when the spheres are occluded (17 and 24) are still higher than when they are disabled (13 and 13) because they are only occluded from the Main Camera's view, they are still visible in some of the shadow maps.

    Overall you want to try to minimize the number of SetPass and Draw Calls using a combination of batching, baked lighting, and occlusion culling.
    World optimization is a very deep topic so this isn't an exhaustive explanation. SetPass and Draw Calls are only part of the equation as they are a CPU cost. VRChat is usually bounded by the CPU, specifically Unity's main thread. However it can become GPU bound in some situations.

    Also if you want more details into what each draw call is doing the Frame Debugger will provide you with a detailed breakdown of the draw calls in your scene. You can find it under Window->Frame Debugger.
    #1 TCL987, Sep 16, 2018
    Last edited: Sep 16, 2018
    OwlBoy, Lakuza and shotariya like this.