(IN-92796) Global shader variables _Time and _TimeParameters appear to be wrong

According to the Unity Shader Variables documentation, the variable _Time is supposed to represent the “time since the level was loaded” or UnityEngine.Time.timeSinceLevelLoad so to speak.

The ShaderGraph Time node documentation indicates that it uses _Time.y for its time calculations. After checking the shader code, I discovered that it uses a global variable called _TimeParameters.

The issue arises in Universal Render Pipeline (URP), where _Time and _TimeParameters do not reset when a scene is loaded. As a result, _Time.y does not reflect UnityEngine.Time.timeSinceLevelLoad, it reflects Time.time instead!

It’s worth noting that _Time.y correctly represents UnityEngine.Time.timeSinceLevelLoad when using the built-in Render Pipeline.

This leads to the question: Is the documentation incorrect, or is there a problem with the implementation in Universal Render Pipeline?

Recently someone asked “How do i reset time in shader graph” and because I was sure the time should actually reset when scene is loaded I assumed for some reason that Time.time is reset on scene load.
Actually the reason might be the fact I use Time.time and _Time to create animations in my shaders (to find time difference), but you made me realize that I have two different pictures in my head xD

The question is whatever the case, how they plan to fix it? because it’s breaking change, I would need to update all my code to use Time.timeSinceLevelLoad, so would that be fixed in all previous versions or they are going to change that for next LTS.
I think they should change it, because without reset that number most likely loses a lot of precision over time.

Yep, that’s my stance on the issue as well :smiley:

Hi @Peter77,

can you please log this as a bug so we can address it?

Thanks!

It’s bug-report IN-92796

The QA team successfully reproduced the issue, which can now be found here. Thank you for your quick handling of the bug report, your efforts are greatly appreciated! :clap:

I had identified this issue back in 2019 (URP 7), but figured it was just a fluke! Glad this is being addressed :slightly_smiling_face:

Hello!

Yeah it looks like there’s a difference here between URP/HDRP and the Built-In RP. Unfortunately we can’t really change it at this point, because some users might be relying on the “new” behavior.

It’s not too difficult to override it tho! Here’s an example of how to do that in URP with a ScriptableRendererFeature that you can use in your project if you want the BiRP behavior:

using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Rendering.Universal;
using UnityEngine.Rendering.RenderGraphModule;

public class ShaderTimeOverride : ScriptableRendererFeature
{
    ShaderTimeOverridePass m_ScriptablePass;
    
    public override void Create()
    {
        m_ScriptablePass = new ShaderTimeOverridePass
        {
            renderPassEvent = RenderPassEvent.BeforeRenderingPrePasses
        };
    }

    public override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)
    {
        renderer.EnqueuePass(m_ScriptablePass);
    }

    class ShaderTimeOverridePass : ScriptableRenderPass
    {
        // Shader properties:
        static readonly int timeNameID = Shader.PropertyToID("_Time");
        static readonly int sinTimeNameID = Shader.PropertyToID("_SinTime");
        static readonly int cosTimeNameID = Shader.PropertyToID("_CosTime");
        static readonly int deltaTimeNameID = Shader.PropertyToID("unity_DeltaTime");
        static readonly int timeParametersNameID = Shader.PropertyToID("_TimeParameters");
        static readonly int lastTimeParametersNameID = Shader.PropertyToID("_LastTimeParameters");
        
        float savedTimeLastFrame;

        private class PassData
        {
            public Vector4 timeVector;
            public Vector4 sinTimeVector;
            public Vector4 cosTimeVector;
            public Vector4 deltaTimeVector;
            public Vector4 timeParametersVector;
            public Vector4 lastTimeParametersVector;
        }

        static void Execute(PassData data, UnsafeGraphContext rgContext)
        {
            UnsafeCommandBuffer cmd = rgContext.cmd;

            cmd.SetGlobalVector(timeNameID, data.timeVector);
            cmd.SetGlobalVector(sinTimeNameID, data.sinTimeVector);
            cmd.SetGlobalVector(cosTimeNameID, data.cosTimeVector);
            cmd.SetGlobalVector(deltaTimeNameID, data.deltaTimeVector);
            cmd.SetGlobalVector(timeParametersNameID, data.timeParametersVector);
            cmd.SetGlobalVector(lastTimeParametersNameID, data.lastTimeParametersVector);
        }
        
        public override void RecordRenderGraph(RenderGraph renderGraph, ContextContainer frameData)
        {
            const string passName = "Override Shader Time";
            
            using (var builder = renderGraph.AddUnsafePass<PassData>(passName, out var passData))
            {
                builder.AllowPassCulling(false);

                float time = Time.timeSinceLevelLoad; // <-- Uses Time.timeSinceLevelLoad instead of Time.time!
                float deltaTime = Time.deltaTime;
                float smoothDeltaTime = Time.smoothDeltaTime;
                float timeLastFrame = savedTimeLastFrame;
                
                float timeEights = time / 8f;
                float timeFourth = time / 4f;
                float timeHalf = time / 2f;

                float sinTime = math.sin(time);
                float cosTime = math.cos(time);
                
                passData.timeVector = time * new Vector4(1f / 20f, 1f, 2f, 3f);
                passData.sinTimeVector = new Vector4(math.sin(timeEights), math.sin(timeFourth), math.sin(timeHalf), sinTime);
                passData.cosTimeVector = new Vector4(math.cos(timeEights), math.cos(timeFourth), math.cos(timeHalf), cosTime);
                passData.deltaTimeVector = new Vector4(deltaTime, 1f / deltaTime, smoothDeltaTime, 1f / smoothDeltaTime);
                passData.timeParametersVector = new Vector4(time, sinTime, cosTime, 0.0f);
                passData.lastTimeParametersVector = new Vector4(timeLastFrame, math.sin(timeLastFrame), math.cos(timeLastFrame), 0.0f);

                savedTimeLastFrame = time;

                builder.SetRenderFunc((PassData data, UnsafeGraphContext rgContext) => Execute(data, rgContext));
            }
        }
    }
}

Unity can’t change the time used in shaders, which will probably don’t affect anything, but it can change the urp and hdrp shader code and render api every few months without any issues