Strategy for shared shaders used across multiple projects and addressables bundles

I’m curious what the suggested strategies are for shaders that are shared across multiple Unity projects that are building assets in different bundles for the same game.

What I want to do is build a shader Addressables bundle that is used by multiple teams on multiple Unity projects which will ultimately deliver multiple Addressables assetbundles and catalogs to the same game. I don’t want to have copies of the shaders in each of this project’s produced Addressables assetbundles. I don’t really see a way to do this with the current system.

I have tried copying the shaders and the Addressables groups to each of the projects and unchecking the “Include in Build” option on the shaders group with the thought that the other groups would see the shaders in this shader group and not include copies of the shaders into the new group assetbundles, but that is not what happens. If the “Include in Build” is not checked, I end up with copies of the shaders in each assetbundle. If the “Include in Build” option is checked on the shaders group then I end up with a different shaders bundle out of every project and each catalog has an entry for a shader bundle.

On the runtime side I have been able to make all of these separate project assetbundles work together using the manual catalog loading as described here, so I know this system is intended to support this use case:

https://docs.unity3d.com/Packages/com.unity.addressables@1.20/manual/MultiProject.html

My current thought is to make a new material asset class that references shaders using AssetReference instead of a direct link, but extending the Material class is a huge pain and I’m not sure it will ultimately work the way I want. I have already had a lot of success with the AssetReference system on other shared assets (meshes, audio clips, etc.) across projects, but the shaders are stumping me.

Maybe I just live with duplicate shaders in each of the sub-project bundles, but it seems like a terrible waste and shader compilation is one of the most time consuming things in the assetbundle build process. Reducing this to a single shared shader assetbundle seems to make the most sense to me.

I hope someone has some thoughts on strategies here. Thanks in advance!

Update on my progress.

I made a Shader AssetReference class like this:

    [System.Serializable]
    public class AssetReferenceShader : AssetReferenceT<Shader> {
        public AssetReferenceShader(string guid) : base(guid) { }
    }

Then I made another class that can combine a material with a nulled out shader with the runtime loaded shader from the Addressables assetbundle:

    [CreateAssetMenu(fileName = "MaterialWithAddressableShader", menuName = "ScriptableObjects/MaterialWithAddressableShader", order = 1)]
    public class MaterialWithAddressableShader : ScriptableObject {

        public Material material;
        public AssetReferenceShader shaderReference;

        protected AsyncOperationHandle<Shader> shaderHandle;
        public IEnumerator LoadShaderFromReference() {
            var validateAddress = Addressables.LoadResourceLocationsAsync(shaderReference, typeof(Shader));
            yield return validateAddress.Task;
            if (validateAddress.Status == AsyncOperationStatus.Succeeded && validateAddress.Result.Count > 0) {
                shaderHandle = Addressables.LoadAssetAsync<Shader>(shaderReference);
                yield return shaderHandle;
                material.shader = shaderHandle.Result;
            } else {
                Debug.Log("Invalid shader reference address " + shaderReference);
            }
        }
    }

Not shown is the code that initializes the Addressables system and runtime and calls LoadShaderFromReference, but that is pretty standard and works as expected. Also not shown is my system for setting the material’s shader to null before it is packed by the Addressables system.

This all works as expected. I can runtime load the shader from the Addressables assetbundle for the shaders into the material. And the Addressables assetbundle that contains the material does not include a copy of the shader.

What doesn’t work, however, is the serialization of the material. When the shader is nulled out on the material, none of the original shader’s properties gets serialized. That makes perfect sense since the material needs to know what properties the shader needs. I was hoping it would just keep all of them. I know they are in there in the editor because when the shader gets reassigned, they are all still set properly. It seems this is an optimization to remove unused properties for packing into bundles or the game asset files.

So I am back to square one. I don’t see any easy way around this except to develop my own material system that supports shaders by reference and runtime load. I would love some insights from the Unity team. For now I will just live with shader duplication in all the bundles.

Can someone working on addressables step in?
I’m really interested in this as well, as I’m relying heavily on Addressables, and this will define several years of development/maintenance after releasing a game we’re working on, so even stuff like this can help us make informed decisions early.
I’m mostly interested in an official of avoiding this duplication, or ant pathways for workarounds.
@unity_shane