My Animal Mart (Part 1) - The outline shader problem.
Tags: gamedev, unity3d
Published: 2024-06-10 07:49:07

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](https://assetstore.unity.com/packages/vfx/quibli-anime-shaders-and-tools-203178) for outline and cartoon shading. Writing an outline shader isn’t particularly difficult; it typically involves two passes:

```shader
Pass
{
    Name "ForwardLit"
    Tags
    {
        "LightMode" = "UniversalForwardOnly"
    }
    
    // ... shader code ...
}

Pass
{
    Name "Outline"
    Tags
    {
        //"LightMode" = "SRPDefaultUnlit"
        "LightMode"="Outline"
    }
    Cull Front
    
    // ... shader code ...
}
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mz8vv4ls6eqvxksnzay7.png)

![Draw Call Detail](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjmz11cha5ooebxs9twx.png)

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`.

```csharp
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);
        }
    }
}
```

![Renderer Feature](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bq4e9jfa53865sd83yp6.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0tcpk77haeaiip7qpshm.png)

Opaque apple draw calls:
![Opaque Draw Calls](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u3ow0v4m3tgmcwwzuj33.png)

Outline apple draw calls:
![Outline Draw Calls](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5sr4dca9ezby75gm9zoh.png)

## 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sg9mg66xetqn62y5o5m7.png)

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

![Publisher Response](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gsg1493tyd5aso2x1xm7.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mt4ovh5o4pkeyacfvvf0.png)

![Publisher's Agreement](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2jnhr0l0qhredrmxwbfh.png)

They agreed with what I had done.

---

**Unity Version:** 2021.3.11f1  
**Link to the game:** [My Animal Mart](https://play.google.com/store/apps/details?id=com.idle.minimart.animal.tycoon&hl=en_US)