##### By Victor Gridnevsky Dec. 17, 2019

In this article, I will discuss basic operations on 2D space, which are easy to write and useful when one needs to manipulate output image.

First, we need some background to change. Let's calculate it instead of using a texture; I will use a grid to show the examples. Doing this will tell us more about the math we will need later.

By manipulating Cartesian coordinate values, we will scale, rotate and shift our grid. Possible with shifting pixel coordinates, but less convenient.

We should not forget about aspect ratio. Our screen's width and height is different. So when we have our formulas for `X` and `Y` to get the cells, these will appear stretched. To fix that, we should calculate `UV` coordinates differently from ShaderToy's basic example.

We divide wider area by the narrower and multiply the narrower one. Our screens are horizontally wide, so `UV` calculation code changes.

``````// horizontal resolution is iResolution.x
// vertical resolution is iResolution.y
uv.x = uv.x * (horizontal_resolution / vertical_resolution);``````

We can also write normalized coordinates vector with aspect ratio considered from the beginning, though it is not as compact as before.

``````vec2 uv = vec2(
fragCoord.x / iResolution.y,
fragCoord.y / iResolution.y
);``````

Now that we fixed the aspect ratio, you might wonder how do we even draw the grid.

And to do that, we check if a given square inside our `UV` coordinates, i.e. `X` and `Y`, is even or odd, or black and white colors.

Let's take a look at the Cartesian coordinates we are working with (original image taken from Wikipedia article). We can use only horizontal or vertical coordinate, for example, `2` for `(2, 3)`, but that's how we'll get stripes. Instead, we can simply sum `X` and `Y` coordinates and check if the number is odd or even by dividing the result by two and checking the remainder.

If the division remainder, i.e. the result of modulo operator is zero, it's an even number.

``````// Even / odd detector for coloring a grid cell
float f = mod( p.x + p.y, 2.0 );``````

By using a floor operator, we take integer part of the coordinates. If it's written like `floor(uv)`, and we'll only have our first square separated from everything else.

``````vec2 p = floor(uv);
vec3 col = vec3(p.x);`````` So instead, we write multiply `uv` inside `floor` operator like this to get more different integer values: `floor( uv * 10.0 )`.

Let me show you the new range we get by dividing integer values, but I won't focus on that too much.

``````vec2 p = floor(uv*10.0);
vec3 col = vec3((p.x+p.y)*0.035);`````` Now we have a basic idea about drawing black-and-white grid.

``````void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// UV with aspect ratio checked
vec2 uv = vec2(
fragCoord.x / iResolution.y,
fragCoord.y / iResolution.y
);
// Multiplied integer values for each point
// on Cartesian coordinates
vec2 p = floor(uv*10.0);
// Even / odd detector for coloring a grid cell
float f = mod( p.x + p.y, 2.0 );
// Color output
fragColor = vec4(vec3(f), 1.0);
}`````` We might also want to make colors more interesting. So, I picked a color and make its brightness shift. I picked a minimum “level” of this color, `0.6` and control the remainder (`0.4`) by multiplying with a sin function from time.

``vec3 col = vec3(0.27,0.46,0.43) * (0.6 + abs(sin(iTime))*0.4);``

In the end, we have this minimal code:

``````void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
// Normalized pixel coordinates
vec2 uv = vec2(
fragCoord.x / iResolution.y,
fragCoord.y / iResolution.y
);
// Multiplied integer values for each point
// on Cartesian coordinates
vec2 p = floor(uv*10.0);
// Even / odd detector for coloring a grid cell
float f = mod( p.x + p.y, 2.0 );
vec3 col = vec3(1.,1.,0.1) * (0.6 + abs(sin(iTime))*0.4);
// Store point's color
fragColor.rgb = vec3( f*col );
}``````

### Shift

Now, we can finally start doing something with this code! Let’s try to shift the grid horizontally!

How to do that? Well, right after UV definition, we pick `uv.x`, a horizontal component of UV vector, and add something.

I am adding `sin(iTime)`, because it will give more or less smooth shifts all the time. The line that interests us is:

``````// Sinusoidal shift by X axis
uv.x += sin(iTime);``````

It is also possible to get the absolute value for the sine function using `abs(sin(iTime))`, so there’s no negative output.

The full example looks like this:

``````void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates
vec2 uv = vec2(
fragCoord.x / iResolution.y,
fragCoord.y / iResolution.y
);

// Shifts
uv.x += sin(iTime);
// alternative:
// uv.x += abs(sin(iTime));

// Multiplied integer values for each point
// on Cartesian coordinates
vec2 p = floor(uv * 10.0);

// Even / odd detector for coloring a grid cell
float f = mod( p.x + p.y, 2.0 );

vec3 col = vec3(1.,1.,0.1) * (0.6 + abs(sin(iTime))*0.4);
// Store point's color
fragColor = vec4(f * col, 1.0);
}``````

Now it's your turn to play around: try to change this code in ShaderToy. Use `uv.y` in pan, shifting an image using `cos` or any other periodic function.

Done? Good.

### Circular shifting

Moreover, it's possible to shift the area around a point in a circular motion using basic trigonometry: `sin` for X axis value, `cos` for Y axis value and vice versa for the reverse direction.

``````void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates
vec2 uv = vec2(
fragCoord.x / iResolution.y,
fragCoord.y / iResolution.y
);

// Shifts
uv.x += sin(iTime);
uv.y += cos(iTime);

// Multiplied integer values for each point
// on Cartesian coordinates
vec2 p = floor(uv * 10.0);
// Even / odd detector for coloring a grid cell
float f = mod( p.x + p.y, 2.0 );

vec3 col = vec3(1.,1.,0.1) * (0.6 + abs(sin(iTime))*0.4);
// Store point's color
fragColor = vec4(f * col, 1.0);
}``````

#### Testing

Do you have any doubts about shifting the space using trigonometry? If so, let’s show the same concept with another piece of code. It has a function to draw a circle at a given center, it can draw both empty and filled circles.

It will move our circle using sin and cos from `iTime`.

This code is drawing a circle, trajectory and background separately and then returns the overlapped color for each viewport pixel.

``````// Draws a circle.
// Returns a float value for a given pixel color.
float circle(vec2 uv, vec2 p, float r, float blur) {
float d = length(uv-p);
float c = smoothstep(r, r-blur, d);
return c;
}

// Draws a disc using circles, based on a given circle center,
float disc(vec2 uv, vec2 p, float r, float blur, float width) {
float disc = circle(uv, p, r, blur) - circle(uv, p, r-width, blur);
return disc;
}

void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
// Normalized pixel coordinates
vec2 uv = vec2(
fragCoord.x / iResolution.y,
fragCoord.y / iResolution.y
);

// Center of image with
// aspect ration taken into account
vec2 center = vec2(
0.5*(iResolution.x / iResolution.y),
.5
);

// Draws a border to show circle movement
float border = disc(uv, center, 0.26, 0.01, 0.008);

// Indicator, positioned at sin and cos of iTime
vec2 indicator_coord = center + vec2(sin(iTime)*0.25, cos(iTime)*0.25);
float indicator = circle(uv, indicator_coord, 0.025, 0.01);

// Resulting color
vec3 col = vec3(.0) + border + indicator;

// Store point's color
fragColor.rgb = vec3( col );
}``````

### Rotation

In previous examples, the area spins, but it can spin more: we haven’t done the image rotation itself yet.

The way we are doing the rotation is not that different from the shifts before. While doing the rotation, we recalculate coordinates so position of grid sectors changes.

We define rotation for UV like that:

``````#define ROT(x) mat2(cos(x), -sin(x), sin(x), cos(x))
uv *= ROT(sin(iTime * .2)* PI);``````

Let’s apply it to the code:

``````#define PI 3.1415926535
#define ROT(x) mat2(cos(x), -sin(x), sin(x), cos(x))

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates
vec2 uv = vec2(
fragCoord.x / iResolution.y,
fragCoord.y / iResolution.y
);

// Spin
uv *= ROT(sin(iTime * .2)* 2.0*PI);

// Multiplied integer values for each point
// on Cartesian coordinates
vec2 p = floor(uv * 10.0);
// Even / odd detector for coloring a grid cell
float f = mod( p.x + p.y, 2.0 );

vec3 col = vec3(1.,1.,0.1) * (0.6 + abs(sin(iTime))*0.4);

// Store point's color
fragColor.rgb = vec3( f * col );
}``````

We are using `2π`, because trigonometric operators in GLSL expect radian values. For example, OpenGL manual tells us this about the sine function: sin returns the trigonometric sine of angle and now, about the angle itself: the quantity, in radians, of which to return the sine.

It is also easy to rotate around the center. To do that, we'd need to shift UV coordinates.

``````#define PI 3.1415926535
#define ROT(x) mat2(cos(x), -sin(x), sin(x), cos(x))

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates
vec2 uv = (fragCoord - .5*iResolution.xy) / min(iResolution.x, iResolution.y);

// Spin
uv *= ROT(iTime*0.1 * 2.0*PI);

// Multiplied integer values for each point
// on Cartesian coordinates
vec2 p = floor(uv * 10.0);
// Even / odd detector for coloring a grid cell
float f = mod( p.x + p.y, 2.0 );

vec3 col = vec3(1.,1.,0.1) * (0.6 + abs(sin(iTime))*0.4);
// Store point's color
fragColor.rgb = vec3(f * col);
}``````

### Zoom

Now, we can also zoom an image by simply multiplying or dividing uv.x and uv.y values, used by our grid. This way, even / odd detector will calculate cell as wider or smaller, filling its tone accordingly.

Let’s go with the same principle by multiplying both `uv.x` and `uv.y` by `sin(iTime)`. It will stretch our image by a given axis.

``````void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates
vec2 uv = vec2(
fragCoord.x / iResolution.y,
fragCoord.y / iResolution.y
);

// Zoom
uv *= sin(iTime);

// Multiplied integer values for each point
// on Cartesian coordinates
vec2 p = floor(uv * 10.0);
// Even / odd detector for coloring a grid cell
float f = mod( p.x + p.y, 2.0 );

vec3 col = vec3(1.,1.,0.1) * (0.6 + abs(sin(iTime))*0.4);

// Store point's color
fragColor.rgb = vec3(f * col);
}``````

### Stretching

Since I mentioned stretching above, I decided I'd add it. As I said, to stretch `X` or `Y` coordinate, you multiply it with a number. Let's use `abs(sin(iTime))` for a moment, because time-dependent values from 0 to 1 are nice.

``````// Stretching
uv.x *= sin(iTime);``````
``````void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates
vec2 uv = vec2(
fragCoord.x / iResolution.y,
fragCoord.y / iResolution.y
);

// Stretching
uv.y *= sin(iTime);

// Multiplied integer values for each point
// on Cartesian coordinates
vec2 p = floor(uv * 10.0);
// Even / odd detector for coloring a grid cell
float f = mod( p.x + p.y, 2.0 );

vec3 col = vec3(1.,1.,0.1) * (0.6 + abs(sin(iTime))*0.4);

// Store point's color
fragColor.rgb = vec3(f * col);
}``````

There's also an interesting way to stretch our images: use initial coordinates inside a sine function to make stretching coordinate-dependent!

``````// Coordinate-dependent stretching
uv.x *= sin(iTime + uv.x);``````
``````// Coordinate-dependent stretching
uv.x *= sin(iTime + uv.y);``````
``````// Coordinate-dependent stretching
uv.y *= sin(iTime + uv.x);``````
``````// Coordinate-dependent stretching
uv.y *= sin(iTime + uv.y);``````

I am showing a full code for one of the results I like: ``````void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates
vec2 uv = vec2(
fragCoord.x / iResolution.y,
fragCoord.y / iResolution.y
);

// Stretching
uv.y *= sin(iTime + uv.x);

// Multiplied integer values for each point
// on Cartesian coordinates
vec2 p = floor(uv * 10.0);
// Even / odd detector for coloring a grid cell
float f = mod( p.x + p.y, 2.0 );

vec3 col = vec3(1.,1.,0.1) * (0.6 + abs(sin(iTime))*0.4);

// Store point's color
fragColor.rgb = vec3(f * col);
}``````
``````// Stretching
uv.y *= sin(iTime);``````

### Tilt effect

It's very easy to add a tilt effect. Just shift each X point by Y coordinate.

I will use `sin(iTime)` multiplication to show this effect in more detail.

``````void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
// Normalized pixel coordinates
vec2 uv = vec2(
fragCoord.x / iResolution.y,
fragCoord.y / iResolution.y
);

// Tilt
uv.x += uv.y * sin(iTime);

// Multiplied integer values for each point
// on Cartesian coordinates
vec2 p = floor( uv*10.0 );

// Even / odd detector for coloring a grid cell
float f = mod( p.x + p.y, 2.0 );

vec3 col = vec3(1.,1.,0.1) * (0.6 + abs(sin(iTime))*0.4);

// Store point's color
fragColor.rgb = vec3(f * col);
}``````

### Multiple manipulations

We can mix multiple manipulations

``````#define PI 3.1415926535
#define ROT(x) mat2(cos(x), -sin(x), sin(x), cos(x))

void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
// Normalized pixel coordinates
vec2 uv = vec2(
fragCoord.x / iResolution.y,
fragCoord.y / iResolution.y
);

// Spin
uv *= ROT(sin(iTime * .2)* PI);
// Tilt
uv.x += uv.y * sin(iTime + 1.);
// Zoom
uv *= sin(iTime);
// Shifts
uv.x += sin(iTime);
uv.y += cos(iTime);

// Multiplied integer values for each point
// on Cartesian coordinates
vec2 p = floor(uv * 10.0);
// Even / odd detector for coloring a grid cell
float f = mod( p.x + p.y, 2.0 );

vec3 col = vec3(1.,1.,0.1) * (0.6 + abs(sin(iTime))*0.4);
// Store point's color
fragColor.rgb = vec3(f * col);
}``````

### Conclusion

We experimented with several effects you can apply for 2D.

We know that we can use these effects in shader graphics (stretching, zooming, rotating ans so on) using a texture similarly, i.e. by changing 2D coordinates we draw things on.

Shaders give you a lot of possibilities for 2D and 3D effects, so I can't recommend enough to look at ShaderToy sources for inspiration. For example, papdis looks fun: it stretches and rotates a texture with different parameters, shifting from the center.