I began to tackle shaders and although I can understand the concept through the examples very well, I got stuck understanding the logic behind the swirl shader.

uniform float rotation=3.0;

void fragment() {
vec2 uv = SCREEN_UV;
vec2 rel = uv-vec2(0.5,0.5);
float angle = length(rel)*rotation;
mat2 rot = mat2(vec2(cos(angle),-sin(angle)),vec2(sin(angle),cos(angle)));
rel = rot * rel;
uv = clamp(rel + vec2(0.5,0.5),vec2(0,0),vec2(1,1));
COLOR.rgb= textureLod(SCREEN_TEXTURE,uv,0.0).rgb;
}

First of all, why does the product of a 2x2 matrix and a Vector2 return a Vector2, makes very little sense to me, since I expected the returned value to be a 2x2 matrix. Second, I have a hard time understanding the angle and rotation matrix calculation itself. Could somebody maybe explain this a little?

Thanks!

in Engine

We can describe swirl as transformation that rotates pixel around center, in such way that rotation angle is proportional to distance from center. Knowing what shader should do lets dissect it line by line.

vec2 uv = SCREEN_UV; - find out where on the screen currently process pixel is displayed. UV normally is a vec2 containing values from 0 to 1

vec2 rel = uv-vec2(0.5,0.5); - because SCREEN_UV's center is (0.5,0.5), and we need to rotate pixels around it, we change our origin (all next operation will be computed relative to center)

float angle = length(rel)*rotation; - pixel further away from center (have greater length ) should be more transformed that pixel closer to center

mat2 rot = mat2(vec2(cos(angle),-sin(angle)),vec2(sin(angle),cos(angle))); - calculate transform matrix that will rotate pixel by computed angle, why it looks like this you can learn on wikipedia

rel = rot * rel; - lets rotate our pixel by applying our rotation matrix, for matrix vector multiplication look here

uv = clamp(rel + vec2(0.5,0.5),vec2(0,0),vec2(1,1)); - because previous operation were relative to center of screen we need to convert it back to original reference system and make sure it is inside boundary of picture (that whats clamp is here for)

COLOR.rgb= textureLod(SCREEN_TEXTURE,uv,0.0).rgb; - finally we have our rotated pixel position so we know from where in original picture we should copy pixel to our output

by (1,022 points)
selected

Much clearer now! Just one additional question:

I noticed that when you multiply two Transform2Ds, that the origin is not according to how I understood matrix-matrix product works:

var t1 = Transform2D(Vector2(1, 1), Vector2(1, 1), Vector2(1, 1))
var t2 = Transform2D(Vector2(1, 1), Vector2(1, 1), Vector2(0, 0))
print(t1 * t2)

Output: Transform2D(Vector2(2, 2), Vector2(2, 2), Vector2(1, 1))

Calculation (stacked parentheses represent one parenthese in matrix,
I appended the third row with zeros, to illustrate that it's actually missing for a
proper matrix-matrix product):

(1 1 1)        (1 1 0)
*
(1 1 1)        (1 1 0)

(0 0 0)   *    (0 0 0)

=
(1 * 1 + 1 * 1 + 0 * 1        1 * 1 + 1 * 1 + 0 * 1       0 * 1 + 0 * 1 + 0 * 1)
(1 * 1 + 1 * 1 + 0 * 1        1 * 1 + 1 * 1 + 0 * 1       0 * 1 + 0 * 1 + 0 * 1)
=
(2  2  0)
(2  2  0)

Why is that?

thats because the virtual third row should be [0,0,1] and not [0,0,0]
for the future if you came up with such long topic it is better, to create separate question than to ask it in comment. Because other people searching for similar things will be able find them easier.