Get/Set Time value from Unity shader Graph

2.9k views Asked by At

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.

2

There are 2 answers

1
GrayedFox On BEST ANSWER

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));

1
Immersive On

I think the issue is that you can't guarantee what the value of Time is when the shader starts being rendered. This also means that providing an offset is moot as you don't know the value that offset needs to be (and it probably changes every time). To do what you're asking for requires a shader property that you set with a scripted MaterialPropertyBlock.