[2D Shaders]How does one create a blackhole effect using 2D Shaders?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By RoomofRR
:warning: Old Version Published before Godot 3 was released.

How does one create a black hole effect like https://medium.com/@mariosubspace/blackhole-shader-in-unity-eb7d93af9577#.s5rlh23kq but on a 2D Circle Sprite?

Thanks in advance :smiley:

:bust_in_silhouette: Reply From: Zylann

I think you can do that using a screen-reading shader, on a node covering the screen (because any light can be distorted). For that you need to put a big sprite with the shader on it, and that shader will only reproduce the pixels of the game world and add the black hole distortion to them.

I did an experiment like that too by starting from the official Screen-Reading shaders demo, and then I added my own black hole shader to them. It’s in no way a “physically correct” black hole, but it looks like one:

You can download the project here to have a look:

Possible improvements: the way I did it doesn’t use a node for the black hole itself, but it can certainly be improved so that a simple Node2D with a script can tell the screen-reading shader where to bend the light. Also it’s not black inside because the shader doesn’t cuts to black above a distance (and also the texture returns buggy coloured pixels beyond the screen). Finally, it can be optimized so that only a subset of the screen is concerned, to reduce the amount of pixels to process (doing that on the whole screen has a cost).

Sorry if it’s not that user-friendly, it’s a rough test but that’s a start^^ (the fact is, yes, you can do black holes :p)
I hope this helps :slight_smile:

Edit: comments for the shader:

// Center of the black hole in normalized screen coordinates
vec2 center = vec2(0.5, 0.5);

// Some factor to set the size of the black hole animating over time,
// you can set it to a constant value really
float p = abs(sin(4.0*TIME)) * 0.01;

// Get direction and distance from the center using texture coordinates
vec2 diff = center - SCREEN_UV;
float d = length(diff);
vec2 dir = normalize(diff);

// Distort the texture coordinates of the screen:
// Warning, unchecked experimental formula.
// the main idea is that we want to "attract" light to the center the closer we are to it,
// which will distort UVs towards the center of the black hole.
// This appears to work well, and in fact looks like gravity attraction formula applied to pixels
vec2 uv = SCREEN_UV + dir * (p / (d*d + 0.01));

// Finally, get the color of the pixel at the distorted location,
// and output it at the current location.
vec3 col = texscreen(uv);
COLOR.rgb = col;

If you understand the tutorial you linked, you can surely reproduce it using Godot’s shaders and some adjustments.

This is almost perfect to what I need. On the line “vec2 center = vec2(0.5, 0.5);” how can you get it so it centers on the sprite/node itself than camera?

RoomofRR | 2017-03-09 03:18

(0.5, 0.5) is the center of the screen in normalized coordinates. So that means (0,0) will be top-left, (1,1) will be lower-right etc.
So to choose where it is it has to be converted.

However I believe there is a way to simply move the node on which the black hole is rendered, but I never tested this.

Here is a better shader, which is wayyy easier to use. Just smash it on a TextureFrame node. When you move it the black hole will follow, and affect pixels only within its rectangle :wink:

// Be gentle on this one
uniform float strength = 0.01;

float sq(float x) {
	return x*x;

float shelf_curve(float x) {
	// Simple parabola. Could use a smoothstep instead?
	return clamp(1.0 - sq(2.0*x), 0, 1);

// Get direction and distance to the black hole center
vec2 diff = vec2(0.5, 0.5) - vec2(UV.x, 1.0-UV.y);
float d = length(diff);
vec2 dir = normalize(diff);

// This is a 0..1 value that will nullify displacement around the bounds of the effect,
// for a seamless transition between the effect's area and the unaffected world pixels.
float shelf = shelf_curve(length(UV-vec2(0.5, 0.5)));

// Calculate displacement amount
float displacement = strength / (d*d + 0.01);

// Calculate distorted screen-space texture coordinates
vec2 uv = SCREEN_UV + dir * (displacement * shelf);

// Output pixels from the screen using distorted UVs
vec3 col = texscreen(uv);
COLOR.rgb = col;


Zylann | 2017-03-09 20:17

Perfect. The problem was I did put the shader as “TextureFrame node”. Thanks Alot :smiley:

RoomofRR | 2017-03-09 22:13

Can you update this code ?? texscreen() is replaced by texture() which will not take vec2()

muthu0914 | 2019-09-28 09:54

Use texture(SCREEN_TEXTURE, uv);

Zylann | 2019-09-28 15:26