Simsure's Avatar

Simsure

@simsure.bsky.social

๐Ÿ‡ฎ๐Ÿ‡น ๐Ÿ‘พIndie game dev โ˜•http://ko-fi.com/simsure โšกhttp://gamejolt.com/@Simsure ๐ŸŽฎhttp://simsure.itch.io

3 Followers  |  5 Following  |  21 Posts  |  Joined: 28.11.2024  |  1.9763

Latest posts by simsure.bsky.social on Bluesky

Preview
Whirlpool Shader Breakdown A shader that uses polar coordinates to offset vertices as well as sample a seamless noise texture to create a swirling effect

the original tutorial by @cyanilux.bsky.social

www.cyanilux.com/tutorials/wh...

07.08.2025 17:59 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0

That's it, hope this can help making cool whirlpool or other effects in your games. ๐ŸŒ€โค๏ธ

07.08.2025 17:59 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
Video thumbnail

And the final touch is making this render three times, tweaking the uniforms to have smaller, faster and more transparent foam under the main one

07.08.2025 17:59 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
Video thumbnail

The actual color comes not from the shader but from the background texture, you can see how it gives a much better look.

The shader could be modified to make it darker the center, this is just how we did it in our game.

07.08.2025 17:59 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
Video thumbnail

The result

07.08.2025 17:59 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
	// Multiply the inverted swirl with the fading circle
	vec4 fadedSwirl = vec4(animSwirlStepInv.rgb, 1.0) * fadeCircleInv;

	// Discard any non opaque pixel
	if(fadedSwirl.r < 0.1)
		discard;

	// Get a fading value where the center is more transparent
	float fadedSwirlInvValue = 1.0 - ((fadedSwirl.r + fadedSwirl.g + fadedSwirl.b) / 3.0);

	// Add an external opacity value
	float finalValue = fadedSwirlInvValue * u_opacity;

	// Keep the final value above 0 transparency
	finalValue = max(finalValue, 0.05);
	
	gl_FragColor = vec4(1.0, 1.0, 1.0,  finalValue);

// Multiply the inverted swirl with the fading circle vec4 fadedSwirl = vec4(animSwirlStepInv.rgb, 1.0) * fadeCircleInv; // Discard any non opaque pixel if(fadedSwirl.r < 0.1) discard; // Get a fading value where the center is more transparent float fadedSwirlInvValue = 1.0 - ((fadedSwirl.r + fadedSwirl.g + fadedSwirl.b) / 3.0); // Add an external opacity value float finalValue = fadedSwirlInvValue * u_opacity; // Keep the final value above 0 transparency finalValue = max(finalValue, 0.05); gl_FragColor = vec4(1.0, 1.0, 1.0, finalValue);

The final steps consist in removing transparent pixels to have a opaque foam, (thanks to the previous step we have a perfect circle and not a square)

With some final inversion and multiplication we get a single float value of our swirling foam, fading as it goes to the center

07.08.2025 17:59 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
Video thumbnail

The result

07.08.2025 17:59 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
	// Use the modified polar coordinates to sample a noise texture
	vec4 animSwirl = texture2D(u_noise, animUv);

	// Use the step function to get only the white shape of the whirling "foam"
	float animStep = step(u_scale, animSwirl.r);
	vec4 animSwirlStep = vec4(animStep);
	
	// Create a fade circle image, darker on the inside and lighter on the outside.
	float fadeValue = pow((polarColor.r/1.0), 2.0);
	vec4 fadeCircle = vec4(fadeValue, fadeValue, fadeValue, 1.0);

	// Invert both the swirl step and the fade circle
	vec4 animSwirlStepInv = vec4(1.0 - animSwirlStep.rgb, 1.0);
	vec4 fadeCircleInv = vec4(1.0 - fadeCircle.rgb, 1.0);

	// Multiply the inverted swirl with the fading circle
	vec4 fadedSwirl = vec4(animSwirlStepInv.rgb, 1.0) * fadeCircleInv;


	gl_FragColor = fadedSwirl;

// Use the modified polar coordinates to sample a noise texture vec4 animSwirl = texture2D(u_noise, animUv); // Use the step function to get only the white shape of the whirling "foam" float animStep = step(u_scale, animSwirl.r); vec4 animSwirlStep = vec4(animStep); // Create a fade circle image, darker on the inside and lighter on the outside. float fadeValue = pow((polarColor.r/1.0), 2.0); vec4 fadeCircle = vec4(fadeValue, fadeValue, fadeValue, 1.0); // Invert both the swirl step and the fade circle vec4 animSwirlStepInv = vec4(1.0 - animSwirlStep.rgb, 1.0); vec4 fadeCircleInv = vec4(1.0 - fadeCircle.rgb, 1.0); // Multiply the inverted swirl with the fading circle vec4 fadedSwirl = vec4(animSwirlStepInv.rgb, 1.0) * fadeCircleInv; gl_FragColor = fadedSwirl;

Now using the polarColor.r from before we generate a fading circle image, that will be used to make the edge of the whirlpool blend with the surroundings.
Get the final image by multiplying the inverted circle with the inverted image from the last step

07.08.2025 17:59 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
Video thumbnail

The result

07.08.2025 17:59 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
	// Use the modified polar coordinates to sample a noise texture
	vec4 animSwirl = texture2D(u_noise, animUv);

	// Use the step function to get only the white shape of the whirling "foam"
	float animStep = step(u_scale, animSwirl.r);
	vec4 animSwirlStep = vec4(animStep);

	gl_FragColor = animSwirlStep;

// Use the modified polar coordinates to sample a noise texture vec4 animSwirl = texture2D(u_noise, animUv); // Use the step function to get only the white shape of the whirling "foam" float animStep = step(u_scale, animSwirl.r); vec4 animSwirlStep = vec4(animStep); gl_FragColor = animSwirlStep;

With a step function let's get only the white "foam" form the rotating noise image

07.08.2025 17:59 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
Video thumbnail

The result, we can already see the whirlpoolness

07.08.2025 17:59 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
	float animR = (polarColor.r * 1.5) + time;
	float animG = (polarColor.r * 1.5) + polarColor.g;

	vec2 animUv = vec2(animR, animG);
	animUv = vec2(fract(animR), fract(animG));

	// Use the modified polar coordinates to sample a noise texture
	vec4 animSwirl = texture2D(u_noise, animUv);

	gl_FragColor = animSwirl;

float animR = (polarColor.r * 1.5) + time; float animG = (polarColor.r * 1.5) + polarColor.g; vec2 animUv = vec2(animR, animG); animUv = vec2(fract(animR), fract(animG)); // Use the modified polar coordinates to sample a noise texture vec4 animSwirl = texture2D(u_noise, animUv); gl_FragColor = animSwirl;

Now we use a noise texture to sample the image that will be the canvas for our whirlpool,
i used a generic noise image (cloud like) found online.

The UV to use for sampling are based on the polar coordinates after some passes.

07.08.2025 17:59 โ€” ๐Ÿ‘ 1    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
Post image

The result

07.08.2025 17:59 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
#define PI 3.1415926538

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform sampler2D u_noise;
uniform float u_time;
uniform float u_scale;
uniform float u_opacity;



// Function to calculate the polar coordinate in a specific point
vec2 polar(vec2 UV, vec2 center, float radialScale, float lengthScale) 
{
    vec2 delta = UV - center;
    float radius = length(delta) * 2.0 * radialScale;
	
	float angleRad = atan(delta.y, delta.x);
	float angle = (angleRad + PI) / (2.0 * PI) * lengthScale;
	
    return vec2(radius, angle);
}


void main()
{
	// Slow down the time
	float time = u_time / 40000.0;
	
	// Center of the UV
	vec2 center = vec2(0.5,0.5);
	
	// Mirror the UV (this change the whirlpool spin direction)
	vec2 coord = v_vTexcoord;
	coord.y = 1.0 - coord.y;

	// Get poolar coordinate of this point
	vec2 polarColor = polar(coord, center, 1., 1.);
	
	gl_FragColor = vec4(polarColor.r, polarColor.g, 0.0, 1.0);
}

#define PI 3.1415926538 varying vec2 v_vTexcoord; varying vec4 v_vColour; uniform sampler2D u_noise; uniform float u_time; uniform float u_scale; uniform float u_opacity; // Function to calculate the polar coordinate in a specific point vec2 polar(vec2 UV, vec2 center, float radialScale, float lengthScale) { vec2 delta = UV - center; float radius = length(delta) * 2.0 * radialScale; float angleRad = atan(delta.y, delta.x); float angle = (angleRad + PI) / (2.0 * PI) * lengthScale; return vec2(radius, angle); } void main() { // Slow down the time float time = u_time / 40000.0; // Center of the UV vec2 center = vec2(0.5,0.5); // Mirror the UV (this change the whirlpool spin direction) vec2 coord = v_vTexcoord; coord.y = 1.0 - coord.y; // Get poolar coordinate of this point vec2 polarColor = polar(coord, center, 1., 1.); gl_FragColor = vec4(polarColor.r, polarColor.g, 0.0, 1.0); }

The starting point is a polar coordinate texture, we use this custom function found online that i changed a bit to make it work correctly in GMS

07.08.2025 17:59 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0

First of all i want to thanks @cyanilux.bsky.social, my version is a Game Maker adaptation of his whirlpool shader, and i started by following his tutorial, he make a lot of other cool stuff.
This tutorial is specific for making it work in Game Maker Studio 2
๐Ÿงต

07.08.2025 17:59 โ€” ๐Ÿ‘ 1    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
Video thumbnail

Here's a little #tutorial for the whirlpool #shader we used in Whirloop.๐ŸŒ€

๐๐ซ๐ž๐ฆ๐ข๐ฌ๐ž: it was made pretty fast for a game jam, it can probably be way more optimized and clean, i just fixed it a bit for this tutorial

๐Ÿ‘‡๐Ÿงต

07.08.2025 17:59 โ€” ๐Ÿ‘ 2    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0
Video thumbnail

The magic of sprite stacking.

#GMTK2025 #gmtkjam #spritestack

06.08.2025 15:23 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0

The last layer of every stack is drawn slightly bigger and with a similar color of the water tile, when the last layer change, it looks like the water adapt perfecly to the object, like an outline.

05.08.2025 16:28 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
Video thumbnail

A simple trick in Whirloop to give the illusion of water around objects.

itch.io/jam/gmtk-202...
pixeltentacles.itch.io/whirloop

#GMTKJam #GMTK2025 #spritestack #loop

05.08.2025 16:28 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 1    ๐Ÿ“Œ 0

The last layer of every stack is drawn slightly bigger and with a similar color of the water tile, when the last layer change, it looks like the water adapt perfecly to the object, like an outline.

05.08.2025 16:27 โ€” ๐Ÿ‘ 0    ๐Ÿ” 0    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
Video thumbnail

Me an Wah made this little game for the #GMTKJam
You can try and rate it on the jam page.
Has been fun i'm really happy with the final result of just two days of work.

itch.io/jam/gmtk-202...
pixeltentacles.itch.io/whirloop

#GMTK2025 #spritestack #loop

04.08.2025 16:26 โ€” ๐Ÿ‘ 3    ๐Ÿ” 1    ๐Ÿ’ฌ 0    ๐Ÿ“Œ 0
Video thumbnail

Dinosaur game! I'm putting it on hold for now, but I had a lot of fun animating all these guys.

02.03.2025 12:19 โ€” ๐Ÿ‘ 592    ๐Ÿ” 144    ๐Ÿ’ฌ 18    ๐Ÿ“Œ 4

@simsure is following 4 prominent accounts