Search code examples
unity-game-enginetimeshadershader-graph

Get/Set Time value from Unity shader Graph


I have creating a pulsating shiny effect with the following as nodes:

Abs(Time * speed) => sine

I have an oscillating value between 0 and 1 but the problem is that the value may start from anything between 0 and 1 depending on the Time value.

I am trying to figure out a way where it would be possible to get that value outside the shader so the script can set a property to offset:

float offset = GetTimeFromShader();
material.SetFloat("_Offset", offset);

and the shader ends up with:

Abs((Time - offset) * speed) => sine

This would ensure the pulsation to start from 0.

The other possibility would be to create an increasing vector1 in the shader simulating:

temp += Time.deltaTime * speed;

But Shader graph prevents node loop.

I could do it in script and pass it down to a shader property but if there were a way to keep it all in the shader.


Solution

  • I have an oscillating value between 0 and 1 but the problem is that the value may start from anything between 0 and 1 depending on the Time value.

    That's your problem right there. You could quite easily change your shader to instead have the oscillation start at 0 and be independent of the built in shader time, no? This will mean driving the shader from a script, which you don't want, but I don't think that's bad practice in this case, since it sounds like you want the oscillation to be directly tied to a specific event inside your game anyway - so trying super hard to decouple the shader code from the application code is here a bit superfluous - they will need to be linked somewhere, somehow.

    To decouple your shader from the built in shader time, instead of Abs(Time * speed) replace Time with a property you control, i.e. Abs(phase * speed).

    I use the following code to play a shock wave effect and always make sure it starts from exactly where I want it to:

    // ensures shockwave plays once and does a full wave over duration
    IEnumerator PlayShockwave(float duration, Renderer shockwave)
    {
        float timeElapsed = 0f;
        float speed = Mathf.Abs(shockwave.material.GetFloat("_Speed"));
        float phase = shockwave.material.GetFloat("_Phase");
        float targetPhase = 1 / speed;
    
        while (timeElapsed <= duration)
        {
            timeElapsed += Time.deltaTime;
            shockwave.material.SetFloat("_Phase", Mathf.Lerp(phase, targetPhase, timeElapsed / duration));
            yield return new WaitForEndOfFrame();
        }
    }
    

    And call it with StartCoroutine(PlayShockwave(2.0f, YourRenderer));