Search Unity

How to use ShaderLibrary and Shaders directories in packages?

Discussion in 'Package Manager' started by LevelExThew, Sep 27, 2021.

  1. LevelExThew

    LevelExThew

    Joined:
    Sep 29, 2017
    Posts:
    9
    Several of Unity's packages split shaders out into separate directories, with common includes in ShaderLibrary and material shaders in Shaders:


    Each directory contains .hlsl files, an assembly definition, and a dummy .cs file which apparently exists only to ensure that the asmdef isn't empty

    This structure is undocumented, but it's very convenient and I would like to use it for my own packages.

    PROBLEM:
    I have replicated this setup in a package, with ShaderLibrary and Shaders directories with their own asmdefs and a dummy class in each one.
    Everything works perfectly in editor, but the shaders to not seem to be included in builds.
    Shader.Find() fails to locate anything and just returns null.


    What is Unity doing differently in their official packages to allow this structure to work?
     

    Attached Files:

  2. TBone0xFF

    TBone0xFF

    Joined:
    Mar 13, 2015
    Posts:
    4
    There is nothing special or magical about that folder structure, and it's largely irrelevant to the issue you're experiencing. ShaderLibrary and Shaders are just folders. the reason some of them have asmdefs with a dummy cs file is so that project files will be generated for them making them show up in IDEs - which is convenient, but doesn't affect Unity functionality.

    The shaders are not included in builds _because you're not using them_. This would be exactly the same if you put these folders inside Assets/ as well. Shaders are assets, and only assets directly or indirectly used are included in standalone builds. This is in contrast to the Editor which always has access to all assets.

    I think the question you should be asking yourself is: why do I want to use Shader.Find as opposed to serializing a reference to the shader / material that I care about?
     
  3. LevelExThew

    LevelExThew

    Joined:
    Sep 29, 2017
    Posts:
    9
    I understand this. My question is: what are Unity's URP/Core RP/etc packages doing in order to ensure that their shaders are "used" and therefore included in builds?

    Shader.Find isn't the important part here. If the shader file ultimately gets included in the build, via a serialized reference or a Resources folder (lol) or some other trick, Find will work.
    The "serialized reference or some other trick" aspect is the thing I'm trying to figure out.


    FURTHER CONTEXT:
    A lot of Unity's SRP stuff uses scriptableobjects that hold serialized references to shaders/textures/other resources. For example: PostProcessData.cs
    This ensures the resources end up in the build, and other parts of the code can just grab what they need from the SO without needing any asset paths or filenames.

    However, not everything works this way. URP's ambient occlusion pass, for example, just calls Shader.Find with a string path, with the assumption that the corresponding shader is already referenced somewhere else.

    The thing is, I can't FIND that Somewhere Else. I can't find a serialized reference to the SSAO shader anywhere in the package. But clearly it's included in builds somehow. If it's a serialized ref, where is it? And if it's some other technique, how does it work?
     
  4. TBone0xFF

    TBone0xFF

    Joined:
    Mar 13, 2015
    Posts:
    4
    There's not really any trick beyond what you've already discovered. Things are either held by ScriptableObjects, or used by Materials.

    That specific Shader.Find is only called at feature create time - which in the common case will only be in the editor, once, when you add the feature to the renderer. The result is serialized in m_Shader in that scriptable object and included as part of the renderer object in the standalone build.

    (One might argue that this particular setup is an oversight since you wouldn't be able to create this feature procedurally at runtime if there wasn't one bundled with the player - but that's probably not a very common use case)
     
    Last edited: Sep 29, 2021
  5. peterbay

    peterbay

    Unity Technologies

    Joined:
    Nov 2, 2017
    Posts:
    100
    Anything that needs to be included is referenced somewhere in a ScriptableObject that has a connection to the build -- in our case that is generally the render pipeline asset.

    We use ResourceReloader to automatically hook up these references: Graphics/ResourceReloader.cs at master · Unity-Technologies/Graphics (github.com)

    SSAO is using a slightly different mechanism, but it is still serializing the reference in m_Shader. GetMaterial gets called via OnEnable and OnValidate, so it should always run before a build and thus the reference gets set.

    The reason that we use references is that the alternative is to put it in a Resources folder, and that will cause the shader to _always_ get included in builds. This can be fine for many project-specific shaders, but for the render pipelines we need to only include the shaders for the current pipeline.