DEV Community

Cover image for My Animal Mart (Part 1) - The outline shader problem.
Long Châu
Long Châu

Posted on • Updated on

My Animal Mart (Part 1) - The outline shader problem.

Last year, I developed a new simulation game called My Animal Mart. It’s a fantastic game, and I’ve learned a lot from the experience.

Foreword

The reason we decided to create this game was because we needed to generate more revenue. One day, my PO called the team together and said we had to make a new move. After spending a year working on hyper casual games such as Popit Fidget 3D, Kick the Rainbow Friend, and Save the Dog, we decided to venture into the simulation genre.

The Background Story

We took inspiration from My Mini Mart. At first glance, there weren’t any games quite like it, especially with its unique outline shader. Some competitors didn’t use this technique, likely due to performance concerns.

The Technique I Used

At that time, we used Quibli for outline and cartoon shading. Writing an outline shader isn’t particularly difficult; it typically involves two passes:

Pass
{
    Name "ForwardLit"
    Tags
    {
        "LightMode" = "UniversalForwardOnly"
    }

    // ... shader code ...
}

Pass
{
    Name "Outline"
    Tags
    {
        //"LightMode" = "SRPDefaultUnlit"
        "LightMode"="Outline"
    }
    Cull Front

    // ... shader code ...
}
Enter fullscreen mode Exit fullscreen mode

As you can see, it’s a multi-pass shader that Unity cannot batch. For instance, if it renders 10 apples, it will first render each apple individually, then render the outline for each apple, resulting in 20 draw calls.

Here’s the detailed draw call in the frame debugger:

Draw Call Detail

Draw Call Detail

The game had a GPU bottleneck. With the release date approaching, the optimization task was handed to me. Since we were using URP, I knew we could customize the render pipeline. I decided to try using a Rendering Feature. I created one and called it RenderOutlineFeature.

using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;

public class RenderOutlineFeature : ScriptableRendererFeature
{
    private RenderOutlinePass renderOutlinePass;
    public Setting featureSetting = new Setting();

    [System.Serializable]
    public class Setting
    {
        public RenderPassEvent renderPassEvent = RenderPassEvent.AfterRenderingOpaques;
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(renderOutlinePass);
    }

    public override void Create()
    {
        renderOutlinePass = new RenderOutlinePass();
        renderOutlinePass.renderPassEvent = featureSetting.renderPassEvent;
    }

    class RenderOutlinePass : ScriptableRenderPass
    {
        ShaderTagId outlineTag = new ShaderTagId("Outline");
        FilteringSettings filteringSettings = new FilteringSettings(RenderQueueRange.opaque);

        public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
        {
            DrawingSettings drawingSettings = CreateDrawingSettings(outlineTag, ref renderingData, SortingCriteria.OptimizeStateChanges);
            context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref filteringSettings);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Renderer Feature

We then went to the Forward Renderer, selected Add Renderer Feature, and chose Render Outline Feature.

After applying the Render Outline Feature, we achieved efficient Unity batching.

Total draw calls:
Total Draw Calls

Opaque apple draw calls:
Opaque Draw Calls

Outline apple draw calls:
Outline Draw Calls

Result

I was thrilled to solve this issue. The GPU bottleneck was eliminated.

Side Story

I contacted Quibli's publisher, DustyRoom, to ask if there was any way to optimize the shader.

Conversation with Publisher

Unfortunately, Unity does not support multipass batching (as I knew).

Publisher Response

I tried using an outline image effect, but it worsened the game’s performance.

After finding the solution, I shared my idea with them.

Sharing the Solution

Publisher's Agreement

They agreed with what I had done.


Unity Version: 2021.3.11f1

Link to the game: My Animal Mart

Top comments (0)