4
\$\begingroup\$

I'm writing a shader that animates "pinching" an image and releasing it. It's similar to this shader on shadertoy.

One issue I have is that my shader should only "animate" once - meaning the image should appear to "pinch", then "unpinch", but then stop. Like this example, it'll keep going since the animation ia time-driven.

Since the shader is called for each pixel I don't see any easy way to track how many "animation iterations" there have been for the entire image.

I can handle animation via a Unity Update method, where I can easily control when to stop, but I'd rather do this entirely in-shader if at all possible.

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord.xy / iResolution.xy;

    vec2 center = vec2( .5 );
    vec2 dir = normalize( center - uv );
    float d = length( center - uv );
    float factor = .5 * sin( iTime );
    float f = exp( factor * ( d - .5 ) ) - 1.;
    if( d > .5 ) f = 0.;

    fragColor = texture( iChannel0, uv + f * dir );
}
\$\endgroup\$

2 Answers 2

4
\$\begingroup\$

All you need to do is clamp your time value, so that it holds a constant value after it's gone through the desired number of iterations.

// Constants to declare elsewhere.
#define TWOPI 6.28318530718
#define maxCycles 1.0f

// These lines go inside your mainImage shader.
float phase = (iTime - startTime)/TWOPI;
phase = min(phase, maxCycles);
float factor = .5 * sin(TWOPI * phase);

This effectively stops time after the desired number of cycles of the sine function.

Note that you'll need to provide a startTime so it knows when the pinch began. You can use a float uniform for this and set it once at the beginning of the effect.

\$\endgroup\$
3
  • \$\begingroup\$ I'm probably missing something simple but can you look at my attempt at your suggestion: shadertoy.com/view/4tXfWj - it doesn't work, and while I believe I correctly implemented this, I broke it. \$\endgroup\$
    – BotskoNet
    Commented Dec 14, 2017 at 5:04
  • \$\begingroup\$ Comment out the line where you change startTime. Shaders (at least when used in this way) don't retain local variable changes from frame to frame, so when this function executes, startTime is ALWAYS zero, and so it always gets set to the current time, and so the phase is always zero, so nothing ever animates. In ShaderToy you can write to a buffer if you want to keep some calculated data persistent for the next frame to read. Or you can just use the rewind button to start over from time = 0 ;) \$\endgroup\$
    – DMGregory
    Commented Dec 14, 2017 at 5:17
  • \$\begingroup\$ That was it, I didn't realize that's how it worked. I'll be adapting this to unity so hopefully I don't run into issues there. \$\endgroup\$
    – BotskoNet
    Commented Dec 14, 2017 at 15:36
2
\$\begingroup\$

@DMGregory Answer is correct but you mentioned that didn't reailze his answer,I just explain more here:

for counting shader animation iterations you should use periodic function. if you want understand you can see below functions on GraphToy.

sin(clamp(x,0,(2*PI)))
sin(clamp(x,0,(2*PI)*2))
sin(clamp(x,0,(2*PI)*3))

Image

this is simple example:

    #define PI 3.14159265359

    void mainImage(out vec4 fragColor, in vec2 fragCoord) {

        float Iterate = 2.0;
        float Periodic = sin(clamp(iTime,0.,(2.0*PI)*Iterate));
        fragColor =  vec4(1.0) * Periodic;
    }

so you can iterate your shader animation easily:

#define PI 3.14159265359

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
    vec2 uv = fragCoord.xy / iResolution.xy;
    float Iterate = 2.0;
    float Periodic = sin(clamp(iTime,0.0,(2.0*PI)*Iterate));
    vec2 center = vec2( .5 );
    vec2 dir = normalize( center - uv );
    float d = length( center - uv );
    float factor = .5 * Periodic;
    float f = exp( factor * ( d - .5 ) ) - 1.;
    if( d > .5 ) f = 0.;

    fragColor = texture( iChannel0, uv + f * dir );

}

this is unity version of your shader that converted by shadertoy to unity tool.

Shader"ShaderMan/Periodic"{
Properties{
_MainTex("_MainTex", 2D) = "white"{}
_Iterate("Iterate",Float) = 2

}
SubShader{
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma fragmentoption ARB_precision_hint_fastest
#include "UnityCG.cginc"
struct appdata{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _MainTex;

struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};

v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = v.uv;
return o;
}
#define PI 3.14159265359
float _Iterate;

fixed4 frag(v2f i) : SV_Target{

{
    float Periodic = sin(clamp(_Time.y,0.0,(2.0*PI)*_Iterate));
    fixed2 uv = i.uv;
    fixed2 center = fixed2( .5 , .5 );
    fixed2 dir = normalize( center - uv );
    fixed d = length( center - uv );
    fixed factor = .5 * Periodic;
    fixed f = exp( factor * ( d - .5 ) ) - 1.;
    if( d > .5 ) f = 0.;

    return tex2D( _MainTex, uv + f * dir );
}
}ENDCG
}
}
}
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .