uv panning / scrolling on really simple shaders ?!

i am trying to make some fallback shaders for some really advanced ones i made with strumpys shader editor !
i modified some really simple unlit shaders from the unify wiki !

the only thing i can NOT figure out is how add texture panning / scrolling to these guys ( imagine i want to make a waterfall or convey-belt )

in the “pirates of new horizons” prototype we used that texture-offset script which is ridiculously expansive ! (according to the profiler 50-60% of our frame render time was wasted on only that script !)
so naturally we deemed it too prohibitively expansive for us to use.

strumpys shader editor has a uvpan node and i know how to set it up with the time node and everything but i cant find any documentation on how to do panning on these low end shaderlab guys:

Shader "ese_opaque_vertexlit" 
{
	Properties 
	{
        _MainTex ("_MainTex", 2D) = "white" {}
	}
	SubShader 
	{
		Tags { "Queue"="Geometry" }
		Pass 
		{
        ColorMaterial AmbientAndDiffuse
        Lighting On
        SetTexture [_MainTex] { Combine texture * primary DOUBLE }
		}
	}
Fallback "ese_opaque_unlit"
}
Shader "ese_opaque_unlit"
{
	Properties 
	{
		_MainTex ("_MainTex", 2D) = "white" {}
	}  
    SubShader
	{
	    Tags { "Queue"="Geometry" }
		BindChannels { Bind "Color", color }
        Pass 
		{
		SetTexture [_MainTex] { Combine texture * primary }
        }
	}
Fallback "VertexLit"
}

please tell me its possible at all to add uv panning to these without requiring shader model 2.0-3.0 ?! and of course how ?!

Well, you can do it with just scripting and the renderer.material.SetTextureOffset command. No need to do it from the shader.

but thats exactly what i am trieng to avoid because of performance

shader > script

1 Like

Really? That seems awfully weird. Are you sure there wasn’t anything wrong with the script itself?

using System.Collections;
using UnityEngine;


public class ScrollingTextures : MonoBehaviour
{

    public float horizontalScrollSpeed = 0.25f;
    public float verticalScrollSpeed = 0.25f;

    private bool scroll = true;

    public void FixedUpdate()
    {
        if (scroll)
        {
            float verticalOffset = Time.time * verticalScrollSpeed;
            float horizontalOffset = Time.time * horizontalScrollSpeed;
            renderer.material.mainTextureOffset = new Vector2(horizontalOffset, verticalOffset);
        }
    }

    public void DoActivateTrigger()
    {
        scroll = !scroll;
    }

}

thats the script that we used !

but in every other engine that i have ever used it was always cheaper do do this kind of stuff via shader it would go against the laws of physics ^^ that doing this via script should be better for performance … at least in my head ^^

so yes i am hell bend on doing it via shader !

1 Like

Should that be in the FixedUpdate? Not that it would make a massive difference. Just tried your script on some game objects in my test scene and had no change in framerate at all. How many objects are you using this on? I added the script to 100 objects and there was still no change in the framerate. Wonder what is going on.

id say around 10 per scene i guess

so i tried update instead of fixedupdate that makes no difference in performance but commenting out this line gives me an extra 100 fps

// renderer.material.mainTextureOffset = new Vector2(horizontalOffset, verticalOffset);

( there are 1000 objects in the scene with the shader / script for debugging purposes)

edit: i also just tried:

unlit/texture (the cheapest shader) + script

vs

100 instructions sse shader model 3 shader with panning and no script

and its again a 100 fps difference in favor of the shader !

it is proven for scrolling purposes:
shader > script

now how do we enable this on low end machines with shaderlab though ?

Interesting it must be a Hardware restriction or opengl/directx difference. I just tested a scene that alters renderer.material.mainTextureOffset 10,000 * FixedUpdate rate and my scene goes from 78fps to 72fps with the scroll turned on. I did cache the material in Start mind.

Anyway that doesn’t get the question answered does it :slight_smile:

hardware wise it must be possible to do scrolling on lets say some intel integrated graphics chip:

rayman2 which is stone old has scrolling water falls you guys think they did that via code ?

That sounds absurd. Were you running any other scripts? How many milliseconds per frame are you talking?

of course i was running and rendering the entire game !

the game ran 60 fps on my machine at the time so id say 8 ms ?!

As far as I know, accessing renderer.material.someProperty causes Unity to internally create a new material. Therefore it makes perfect sense that shaders are far more effective.

How many objects were UV panning? That is a very long time to spend modifying materials.

Actually, now that I think of it: have you tried moving that code to Update()? That’s the only time the effects will be seen, anyway. I have also seen odd situations where accessing properties from Update or FixedUpdate takes way longer than doing so in the other.

Accessing Renderer.material instances the material the first time it is called for a given renderer. After that, though, there is no more memory being allocated. The only real overhead should be from GPU state changes, which wouldn’t actually be traced back to the script by the profiler.

A lot of discussion on the topic here, but how do you actually do UV scrolling in a shader??? I to need to do this as I cannot attach scripts to asset bundles, which we use to load in items to our environment at run time.

2 Likes

To clarify where your slowdown probably comes from:

Using “renderer.whatever” will search the GameObject for a Renderer component in a rather expensive way. Instead do this:

Renderer _renderer;

public void Awake()
{
  _renderer = renderer;
}

public void Update()
{
  _renderer.whatever.doWhatever();
}

In your case you could even store the material.

And I’d also like to know how to scroll the texture via shader, since in my case I need it for about 500 objects at once :slight_smile:

Grave dig here but this is what came up 1st on google so I’ll add to it.

What the OP wanted as a UV scrolling solution in a shader. The C# method is just bad.

In a vertex/fragment cg shader simply do this in your vertex function:

VertexOutput vert(VertexInput i)
{
    VertexOutput o;
    o.vertex = mul(UNITY_MATRIX_MVP, i.vertex);
    i.texcoord.x += _Time * _speedU;
    i.texcoord.y += _Time * _speedV;
    o.texcoord = i.texcoord;
    return o;
}

_speedU and _speedV are passed in to the shader.

2 Likes

Firstly _Time is not a single value, it’s a float4. The code above while compile for some platforms but not all as conversion from float4 to float isn’t always handled. The behavior when it does work is also not what you expect as _Time.x, the value that will be used in most cases where the above compiles, is actually “time / 20”. The value you want is actually _Time.y, which is just time unmodified.

See: Unity - Manual: Built-in shader variables

Secondly adding any value from _Time to the texture coordinates straight is a bad idea. At first it’ll be fine, but if your game runs for long enough that value from _Time is going to be a very large number and there are going to start being precision issues that will present themselves as the texture starting to look pixelated. You want to wrap the time value, after multiplying it by the speed scale, to stay with in a 0 to 1 range using frac().

Thirdly when ever possible if you can do math operations in a shader using a vector instead of multiple scalers (i.e.: float4 instead of four floats) you should. Most GPUs are designed to do vector math, so something like float4 * float4 takes as much time as float * float, so doing the later 4 times is 4 times slower than the single float4 * float4. Shader compilers can sometimes be smart enough to combine these, but don’t count on it.

So, the above lines 5 though 7 should be condensed to the following single line.

o.texcoord.xy = i.texcoord.xy + frac(_Time.y * float2(_speedX, _speedY));

Edit: Some additional information. The above thread before David_Biggs’ post is talking about texture scrolling with fixed function shaders. In that case the use of a script is the only answer. However fixed function shaders are effectively deprecated in Unity 5.0 and there’s not a lot of reason to use them in 4.0. In 5.0 they get converted to vert / frag style shaders when actually used so there’s no good reason to keep using the fixed function forms.

6 Likes

Thanks bgolos, I’d realised _Time was a vector soon after posting this. The frac() call is a great idea too,

In case anyone comes still looking for this like I did, here’s a way to integrate UV scrolling/panning into Unity 5’s default editable standard shader. (To give due credit: this code is a slight modification of the helpful tutorial here).

  1. Add these input variables to the Properties { } section at the top:
        _ScrollXSpeed("X Scroll Speed",Range(0,1)) = 0.1
        _ScrollYSpeed("Y Scroll Speed",Range(0,1)) = 0.1
  1. Add equivalent variables to the declarations in the SubShader { } section, somewhere around sampler2D _MainTex:
        fixed _ScrollXSpeed;
        fixed _ScrollYSpeed;
  1. Finally, modify your void surf() function to include the following starred lines. Basically the added code is grabbing your MainTex, adding your scrollspeed multiplied by time (so it changes each frame), and then passes that updated texture data into your tex2D instead of MainTex.
      void surf (Input IN, inout SurfaceOutputStandard o) {

            fixed2 scrolledUV = IN.uv_MainTex; //***

            fixed xScrollValue = frac(_ScrollXSpeed * _Time.y); //***
            fixed yScrollValue = frac(_ScrollYSpeed * _Time.y); //***
            scrolledUV += fixed2(xScrollValue, yScrollValue); //***

            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, scrolledUV) * _Color; //***
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
4 Likes