Global Variables & Properties Scope

Hey fellow Shader Artists!

I’ve read here and there about how confusing and frustrating it can be to deal with global properties in Shader Graph and decided to gather some information here to the best of my knowledge and understanding.

I might be wrong and will correct and/or add more as I learn new things.
Please reply to this thread if you have any questions or information you want to share on the topic.

Global VS static
Static variables can be used in both ShaderLab and Shader Graph (using a Custom Function node), but they cannot be read nor assigned to from outside shader code.
They are, by definition, shared between all material instances.

Unlike static variables, globals are variables handled in C#, that will be assigned to all materials with a matching definition (name & type).

Global VS Local (Per Material)
In Shader Graph, a property is made local when set as exposed.
If you check Override Property Declaration and set the Shader Declaration dropdown to Global, it will still be a Local (Per Material) property if exposed is checked.

In ShaderLab, a global property is a variable that is declared within the HLSLPROGRAM body.
float _MyCustomProperty;
The variable is made local when it is associated with a Property:
_MyCustomProperty("MyCustomProperty", Float) = 0

Global
A Global variable can be read and assigned to from C# using Shader.GetGlobalFloat(“_MyFloat”) and Shader.SetGlobalFloat(“_MyFloat”, value).
This will change the value in all material instances, except those where the value was overridden using a Property Block.
It cannot be set at material instance level with Material.SetFloat().

Since HLSL doesn’t allow for variable initialization, global variables have no default value, and must be initialized from C#, or else they will always default to zero/black.

Shader Graph allows changing the default value of global variables.
While it allows previewing the effect of a global in Shader Graph, this only gives a preview of what will happen when the variable is assigned to.

Local (Per Material)
A Local variable can be read and assigned to from C# at material instance level, using material.GetFloat(“_MyFloat”) and material.SetFloat(“_MyFloat”, value).

This will change the material’s property value, unless overridden using a Property Block.
It cannot be set at global level with Shader.SetGlobalFloat().

Default Values
Global
Global properties have no default value. They are not serialized and require explicit initialization with Shader.SetGlobalFloat(“_MyFloat”, value).

Local (Per Material)
Changing the default value of an exposed (local) property will not propagate to existing material instances.
That is similar to changing the default value of a MonoBehaviour field, which neither propagates to existing instances of that MonoBehaviour.
Making Material Instances as Material Variants of the Shader Graph (nested) Material allows them to inherit the properties they don’t override, just like Prefabs allows the same for MonoBehaviours.

Reading from Shader
Although a variable can be modified from within shader code, it is worth noting that a variable value read from C# will always returned the last value it was assigned from C#.

In short: what happens on the GPU stays on the GPU.
Say you have a shader global (not exposed) float “_MyFloat”.
Calling Shader.GetGlobalFloat(“_MyFloat”) will only return the last value it was assigned from C#, but not any value that it was assigned from within Shader code.

DOTS Instancing
DOTS allows for another type of property declaration : Hybrid Per Instance.

If you check Override Property Declaration and set the Shader Declaration dropdown to Hybrid Per Instance, the effect of the exposed checkbox differs. Since those variables are by definition per instance, they cannot be global.
Unchecking the exposed checkbox will simply just add a [HideInInpector] attribute to the property, hiding it from the material inspector.

Whether it is visible or not, the property can be read and assigned to using material.GetFloat(“_MyFloat”) and material.SetFloat(“_MyFloat”, value).
This will change the material’s property value, unless overridden using a Property Block.

For more information, see:
Entities Graphics | 1.0.11
Manual: DOTS Instancing shaders

Exceptions / Limitations
Gradients
HLSL doesn’t feature a value type for Gradients. As a result, they can be added to the Blackboard for the purpose of editing them once and use them in several places in a graph, but cannot be exposed in the Material Inspector.

Matrices
ShaderLab doesn’t feature a Property Type for Matrices (float4x4, float3x3, float2x2). As a result, their value cannot be exposed in the Material Inspector.
As with Globals, the preview value is only used within Shader Graph. Material Instances need to be initialized with Material.SetMatrix(), MaterialPropertyBlock.SetMatrix() or Shader.SetGlobalMatrix().

Sampler States
ShaderLab doesn’t feature a Property Type for Sampler States. As a result, they cannot expose their value fields in the Material Inspector.

Array Types
Although ShaderLab allows for arrays (Floats, Vectors and Matrices) that can be set with Shader.SetGlobalFloatArray(), Shader.SetGlobalVectorArray() and Shader.SetGlobalMatrixArray(), Shader Graph doesn’t feature typed arrays properties.
In general, arrays become useful with iterators and are otherwise less performant than Vectors or Matrices.
In other words, it’s good practice to pack floats and vectors in vectors or matrices.

UX Improvements
Shader Graph Property Scope & Visibility settings
We are planning to rework the UX to make the scope and visibility settings easier to work with and enable hiding local properties.

If this is important to you, or want to add comment, please let us know on the Product Board:
https://portal.productboard.com/unity/1-unity-platform-rendering-visual-effects/c/2239-property-scope-visibility

Shader Globals
Having to write a C# component for the sole purpose of initializing shader globals isn’t ideal.
We are considering adding Shader Globals to Project Settings so that users can easily initialize them.

Please let us know what you think about this here on the Product Board.
https://portal.productboard.com/unity/1-unity-platform-rendering-visual-effects/c/2256-shader-globals

9 Likes

Shadergraph:
i uncheck exposed to get global declarations to work. And they do. They also update continuously (ie from update) and globally if I tell them to via script, across all shaders and materials using that global parameter.

Except the texture2Ds. The texture2Ds, (generated) i found only work once if applied globally. If I generated a new one, it would be black… and this killed me for days trying to figure out why, until i gave up and made them local. I can get render textures to apply globally, though.

But this, (it has been a while since i gave up on the idea of generated global texture2Ds) might be related to how I defined them as static in C#… …although they are still accessible as static through script)

I have not played with “override property declaration” yet… …mayhaps I will again and tackle this again.

1 Like

Thanks @Ne0mega ,

I’ll look into this Texture2D exception to learn if this is a limitation or a bug.

Hi @Ne0mega ,

I took some time to experiment with Shader.SetGlobalTexture and Texture2D and could not reproduce what you mentioned above.

You’d usually create one texture and use SetGlobalTexture once, then write to that same texture.
Here’s a test script that generates an 8x8 texture and sets it to a Global upon Start, then applies some random colors to its pixels every frame.

But if for some reason you need to create a new texture, it also works, as you can test with the component’s context menu “Create New Texture”.

Please let me know if that works out for you, or if the issue you mentioned is different.

using UnityEngine;

public class GlobalTexGen : MonoBehaviour
{
    [SerializeField] string _referenceName = "_Texture";
    [SerializeField] Color32 _color1 = Color.red;
    [SerializeField] Color32 _color2 = Color.blue;

    Texture2D texture;
    Color32[] colors = new Color32[64];

    void Start()
    {
        CreateNewTexture();
    }

    void Update()
    {
        for (int i = 0; i < 64; i++)
            colors[i] = Color32.Lerp(_color1, _color2, Random.Range(0f, 1f));

        texture.SetPixels32(colors);
        texture.Apply();
    }

    [ContextMenu("Create New Texture")]
    void CreateNewTexture()
    {
        texture = new Texture2D(8, 8);
        Shader.SetGlobalTexture(_referenceName, texture);
    }

#if DEBUG
    Rect drawRect = new Rect(0, 0, 64, 64);
    private void OnGUI()
    {
        GUI.DrawTexture(drawRect, texture);
    }
#endif
}
1 Like

I appreciate it, and ill try later.

I didnt really expect you to try to reproduce though, since there are alot of other things in my project that could be causing the blackout.

But thank you.

Edit:. I dont know what context menu is, but if it is an editor thing i am not using editor. This texture2D is generated once, at beginning of scene. Creating a new texture, then Apply(true, true).

I tried just now with not exposed in shadergraph, not exposed with global override, as well as exposed with override global, and the global texture did not work, but like I said global colors and floats work, all defined at the same time, but not updated, so for some reason there is some kind of disconnect. I will maybe try a coroutine to wait a frame then apply the texture, as perhaps global textures ate sent to GPU earlier than floats and colors.

Edit 2:. The waitforendofframe then apply global texture2d coroutine worked. So textures must be defined globally in a different and earlier stage of a frame than colors and floats. This is all occuring in the first frame of the scene.

Hi @Ne0mega ,

I was referring to the [ContextMenu("Create New Texture")] I added on the void CreateNewTexture() method I put in the test MonoBehaviour above.

So it is indeed in the Editor, but I tested at Runtime in a Build also.
Although, I did not test instantiating renderers after the texture was created, everything happens in the same scene that already contains renderers with the material applied.
But Shader.SetGlobalTexture() is called before texture.Apply()

Anyway, thanks for sharing your learnings. I want to know if this is a limitation we should document or a bug we should fix.

In my experience, this seems to be the opposite of how it works? When using functions in C# like Shader.SetGlobalVector(), the ShaderGraph will only pick up on the variable when exposed is unchecked like @Ne0mega mentions. Is it possible to get a confirmation on what the intended use is here, and if it is a bug otherwise?

Thanks for the heads up @AndreasWang . You can never have too many proofreaders :slight_smile:

1 Like

Hi,

as part of our initiative to improve on UX, we changed the Properties Scope and Visibility settings. It’s landed a couple of weeks ago in 2023.3.0a11.

To give you a heads up, and until we update the docs, here’s what we’ve changed.

We’ve reworked the multiple combinations of Exposed + Override Property Declaration + Declaration type, into one drop down, which can only output reliable ShaderLab compliant shader code.

Global
When the scope is set to Global (same as having Exposed unchecked before), only a Uniform will be added to the Shader. Making this modifiable with Shader.SetGlobalFloat().
Since no property will be added for a Global, “Show In Inspector” is disabled.
And as a default value is only saved with a material property, the value field reads “Preview Value”, to indicate this is only to preview the effect within Shader Graph previews.

Local (Per Material or Per Instance)
When the scope is set to Per Material, or Per Instance, a Uniform will be added to the Shader within the appropriate Constant Buffer.
A material property will also be added, storing the default value.
Making this modifiable with Material.SetFloat().
If “Show In Inspector” is unchecked, a [HideInInspector] attribute is added to the property so that it is only accessible from C# and not visible in the Material Inspector.

This adds the ability to hide a local property (which many users had requested).
It also improves on understanding the outcome and prevents meaningless combinations of output.

We have also updated the UX/UI for Keywords declaration, but I’ll touch on that in a dedicated thread.

As always, your feedback is welcome.
Thanks

9460154--1328867--SG-Prop-Global.png 9460154--1328870--SG-Prop-Local.png

5 Likes