You can solve this quite cleanly by combining a tiny “generic ScriptableObject‐singleton” pattern with either the built-in Resources system (for simplicity) or the Addressables system (for true GUID-based referencing). Below are two approaches:
-
Define your SO class
public class ComputeShaderDatabase : ScriptableObject
private static ComputeShaderDatabase _instance;
// 1) All your compute shaders as inspector fields:
[Header("Your Compute Shaders")]
public ComputeShader blitShader;
public ComputeShader particleSim;
public ComputeShader sdfGenerate;
/// Lazily loads the one-and-only instance from Resources/ComputeShaderDatabase.asset
public static ComputeShaderDatabase Instance
_instance = Resources.Load<ComputeShaderDatabase>("ComputeShaderDatabase");
-
Create the asset
- In your Editor, right-click in a Resources folder → Create → Compute Shader Database (you can add a
[CreateAssetMenu] attribute if you like).
- Fill in all your shader references in the inspector.
-
Fetch anywhere
var cs = ComputeShaderDatabase.Instance.particleSim;
- Pros: super simple; no async; direct references → no typos.
- Cons: you still have to call
Resources.Load with the exact SO path; if you rename or move the asset you’ll need to update that string.
If you want zero string-based lookups (and true GUID-based assets), Addressables are the way to go:
-
Mark your SO as Addressable
- Put your
ComputeShaderDatabase.asset anywhere in your project.
- In the Addressables window give it a unique address (e.g.
"ComputeShaderDB").
-
Change your singleton to use Addressables
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using System.Threading.Tasks;
public class ComputeShaderDatabase : ScriptableObject
private static ComputeShaderDatabase _instance;
public static async Task<ComputeShaderDatabase> GetAsync()
var handle = Addressables.LoadAssetAsync<ComputeShaderDatabase>("ComputeShaderDB");
_instance = handle.Result;
// … your ComputeShader fields here …
-
Usage
var db = await ComputeShaderDatabase.GetAsync();
- Pros: you never type asset names or paths; Addressables uses the asset’s GUID under the hood → renaming or moving in your project won’t break anything.
- Cons: async pattern; slight Addressables overhead (though trivial for a single small SO).
- If you already use a DI framework (Zenject, VContainer, etc.) you can bind your
ComputeShaderDatabase as a singleton and inject it into any class that needs it—still using one of the above loading strategies under the hood.
- If you only ever need these shaders on start-up, you can preload the SO in a tiny bootstrapper scene/monobehaviour (so your gameplay code never sees any
Load calls at all).
- For ultra-advanced workflows you can write an Editor post-processor that scans your project for any
ComputeShaderDatabase assets, grabs its GUID, and auto‐generates a tiny static C# class with that GUID baked in—so your runtime code can do:
AssetReference reference = new AssetReference(ComputeShaderDbGuidHolder.Guid);
But Addressables already does exactly this for you.
– For 80% of projects, stick a [CreateAssetMenu] SO in a Resources folder and use the generic singleton above.
– If you hate any string‐based lookups and want proper GUID resolution, switch to Addressables and use an SO as your “database.”
Both patterns get you a single, reliable place to keep references to all your compute shaders, exactly like HDRP does with its defaultResources asset.