Creating Your Own Scriptable Render Pipeline on Unity for Mobile Devices: Introduction to SRP

Creating Your Own Scriptable Render Pipeline on Unity for Mobile Devices: Introduction to SRP

Create our own rendering systems to control game optimization on Unity

Unity, one of the leading game and application development platforms, provides developers with flexible tools to create high quality graphics. Scriptable Render Pipeline (SRP) is a powerful mechanism that allows you to customize the rendering process in Unity to achieve specific visualization goals. One common use of SRP is to optimize rendering performance for mobile devices. In the last article we took a closer look at how rendering works in Unity and GPU optimization practice.

In this article, we will look at creating our own Scriptable Render Pipeline optimized for mobile devices on the Unity platform. We'll delve into the basics of working with SRP, develop a basic example and look at optimization techniques to ensure high performance on mobile devices.

Introduction to Scriptable Render Pipeline

The Scriptable Render Pipeline (SRP) in Unity is a powerful tool that allows developers to customize the rendering process to achieve specific goals. It is a modular system that divides rendering into individual steps such as rendering geometry, lighting, effects, etc. This gives you flexibility and control over your rendering, allowing you to optimize it for different platforms and improve visual quality.

Basically SRP includes several predefined types:

  • Built-in Render Pipeline (BRP): This is Unity's standard built-in rendering pipeline. It provides a good combination of performance and graphics quality, but may not be efficient enough for mobile devices.

  • Universal Render Pipeline (URP): This pipeline provides an optimized solution for most platforms, including mobile devices. It provides a good combination of performance and quality, but may require additional tuning to maximize optimization for specific devices.

  • High Definition Render Pipeline (HDRP): HDRP is designed to create high quality visual effects such as photorealistic graphics, physically correct lighting, etc. It requires higher computational resources and may not be efficient on mobile devices, but good for PC and Consoles.

Creating your own Scriptable Render Pipeline allows developers to create customizable solutions optimized for specific project requirements and target platforms.

Planning and Designing SRP for Mobile Devices

Before we start building our own SRP for mobile devices, it is important to think about its planning and design. This will help us identify the key features we want to include and ensure optimal performance.

Definition of Objectives

The first step is to define the goals of our SRP for mobile devices. Some of the common goals may include:

  • High performance: Ensure smooth and stable frame time on mobile devices.

  • Resource Efficient: Minimize memory and CPU usage to maximize performance.

  • Good graphics quality: Providing acceptable visual quality given the limitations of mobile devices.

Architecture and Components

Next, we must define the architecture and components of our SRP. Some of the key components may include:

  • Renderer: The main component responsible for rendering the scene. We can optimize it for mobile devices, taking into account their characteristics.

  • Lighting: Controls the lighting of the scene, including dynamic and static lighting.

  • Shading: Implementing various shading techniques to achieve the desired visual style.

  • Post-processing: Applying post-processing to the resulting image to improve its quality.

Optimization for Mobile Devices

Finally, we must think about optimization techniques that will help us achieve high performance on mobile devices. Some of these include:

  • Reducing the number of rendered objects: Use techniques such as Level of Detail (LOD) and Frustum Culling to reduce the load on the GPU.

  • Shader Optimization: Use simple and efficient shaders with a minimum number of passes.

  • Lighting Optimization: Use pre-calculated lighting and techniques such as Light Probes to reduce computational load.

  • Memory Management: Efficient use of textures and buffers to minimize memory usage.

Creating a Basic SRP Example for Mobile Devices

Now that we have defined the basic principles of our SRP for mobile devices, let's create a basic example to demonstrate their implementation.

Step 1: Project Setup

Let's start by creating a new Unity project and selecting settings optimized for mobile devices. We can also use the Universal Render Pipeline (URP) as the basis for our SRP, as it provides a good foundation for achieving a combination of performance and graphics quality for mobile devices.

Step 2: Creating Renderer

Let's create the main component, the Renderer, which will be responsible for rendering the scene. We can start with a simple Renderer that supports basic rendering functions such as rendering geometry and applying materials.

using UnityEngine;
using UnityEngine.Rendering;

// Our Mobile Renderer
public class MobileRenderer : ScriptableRenderer
{
    public MobileRenderer(ScriptableRendererData data) : base(data) {}

    public override void Setup(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        base.Setup(context, ref renderingData);
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        base.Execute(context, ref renderingData);
    }
}

Step 3: Setting up Lighting

Let's add lighting support to our Renderer. We can use a simple approach based on a single directional light source, which will provide acceptable lighting quality with minimal load on GPU.

using UnityEngine;
using UnityEngine.Rendering;

public class MobileRenderer : ScriptableRenderer
{
    public Light mainLight;

    public MobileRenderer(ScriptableRendererData data) : base(data) {}

    public override void Setup(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        base.Setup(context, ref renderingData);
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        base.Execute(context, ref renderingData);

        ConfigureLights();
    }

    void ConfigureLights()
    {
        CommandBuffer cmd = CommandBufferPool.Get("Setup Lights");
        if (mainLight != null && mainLight.isActiveAndEnabled)
        {
            cmd.SetGlobalVector("_MainLightDirection", -mainLight.transform.forward);
            cmd.SetGlobalColor("_MainLightColor", mainLight.color);
        }
        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
    }
}

Step 4: Applying Post-processing

Finally, let's add support for post-processing to improve the quality of the resulting image.

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

public class MobileRenderer : ScriptableRenderer
{
    public Light mainLight;
    public PostProcessVolume postProcessVolume;

    public MobileRenderer(ScriptableRendererData data) : base(data) {}

    public override void Setup(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        base.Setup(context, ref renderingData);
    }

    public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)
    {
        base.Execute(context, ref renderingData);

        ConfigureLights();
        ApplyPostProcessing(context, renderingData.cameraData.camera);
    }

    void ConfigureLights()
    {
        CommandBuffer cmd = CommandBufferPool.Get("Setup Lights");
        if (mainLight != null && mainLight.isActiveAndEnabled)
        {
            cmd.SetGlobalVector("_MainLightDirection", -mainLight.transform.forward);
            cmd.SetGlobalColor("_MainLightColor", mainLight.color);
        }
        context.ExecuteCommandBuffer(cmd);
        CommandBufferPool.Release(cmd);
    }

    void ApplyPostProcessing(ScriptableRenderContext context, Camera camera)
    {
        if (postProcessVolume != null)
        {
            postProcessVolume.sharedProfile.TryGetSettings(out Bloom bloom);
            if (bloom != null)
            {
                CommandBuffer cmd = CommandBufferPool.Get("Apply Bloom");
                cmd.Blit(cameraColorTarget, cameraColorTarget, bloom);
                context.ExecuteCommandBuffer(cmd);
                CommandBufferPool.Release(cmd);
            }
        }
    }
}

In this way we created a basic loop with render, light and post processing. You can then use other components to adjust the performance of your SRP.

Optimization and Testing

Once the basic example is complete, we can start optimizing and testing our SRP for mobile devices. We can use Unity's profiling tools to identify bottlenecks and optimize performance.

Examples of optimizations:

  • Polygon Reduction: Use optimized models and LOD techniques to reduce the number of polygons rendered. Keep the vertex count below 200K and 3M per frame when building for PC (depending on the target GPU);

  • Shader simplification: Use simple and efficient shaders with a minimum number of passes. Minimize use of complex mathematical operations such as pow, sin and cos in pixel shaders;

  • Texture Optimization: Use texture compression and reduce texture resolution to save memory. Combine textures using atlases;

  • Profiling and optimization: Use Unity's profiling tools to identify bottlenecks and optimize performance.

Testing on Mobile Devices

Once the optimization is complete, we can test our SRP on various mobile devices to make sure it delivers the performance and graphics quality we need.

Conclusion

Creating your own Scriptable Render Pipeline for mobile devices on the Unity platform is a powerful way to optimize rendering performance and improve the visual quality of your game or app. Proper planning, design, and optimization can help you achieve the results you want and provide a great experience for mobile users.

And of course thank you for reading the article, I would be happy to discuss various aspects of optimization with you.


You can also support writing tutorials, articles and see ready-made solutions for your projects:

My Discord | My Blog | My GitHub | Buy me a Beer

BTC: bc1qef2d34r4xkrm48zknjdjt7c0ea92ay9m2a7q55

ETH: 0x1112a2Ef850711DF4dE9c432376F255f416ef5d0