joe.z

Ludum Dare 48

How We Created The Monitor Effect For Egress

If you have checked our Ludum Dare entry, Egress you might be wondering how we created the glow and screen-warping effect.

Egress (DEBUG) 2021-04-30 4em11/em52 PM.png

If you haven't played it yet, give it a go! https://lunatic-games.itch.io/egress

The Glow Effect

To create the glow, we applied a Gaussian Blur additively to the screen. By doing it additively, it created a glow effect rather than just blurring the entire screen. While attempting to optimize the shader code, I discovered that if it was applied in steps, 4 pixels in this case, it created a diode look:

egress.png

The complete code (written in Godot's native shading language): ``` shadertype canvasitem;

uniform float radius = 8.0; // Radius of pixels to consider uniform float stepsize = 4.0; // Larger steps give it a more pixel-y look uniform float sd = 10.0; // See Gaussian Blur algorithm uniform float additivestrength = 24.0; // How much to include of the blur output uniform float center_strength = 1.0; // How much to include of the un-blurred pixel color

void fragment() { float pi = 3.141592653; float e = 2.71828; float multiplier = 1.0 / (2.0 * pi * pow(sd, 2)); vec4 sum = vec4(0.0); for (float x = -radius; x <= radius; x += stepsize) { for (float y = -radius; y <= radius; y += stepsize) { vec4 value = texture(SCREENTEXTURE, SCREENUV + SCREENPIXELSIZE * vec2(x, y)); float p = -(pow(x, 2) + pow(y, 2)) / (2.0 * pow(sd, 2)); sum += value * multiplier * pow(e, p); } } COLOR = sum * additivestrength + texture(SCREENTEXTURE, SCREENUV) * centerstrength; } ```

The Screen-Warping Effect

It took a bit of testing to find the best functions to use for creating a convincing screen warp. We ended up offsetting the UV coordinates of the screen using two different cosine waves: one based off the distance from the horizontal center, and another one using the distance from the vertical center. If the UV coordinates ended up being outside the boundaries, the screen was simply colored black.

The complete code: ``` shadertype canvasitem;

uniform float margin; uniform vec2 strength = vec2(10.0, 1.0); uniform vec4 backgroundcolor: hintcolor;

void fragment() { float x = SCREENUV.x - 0.5; float y = SCREENUV.y - 0.5; y *= strength.y * (1.0 - cos(x)); // How much to offset the y UV coordinate x *= strength.x * (1.0 - cos(y)); // How much to offset the x UV coordinate if (SCREENUV.y + y > 1.0 - margin * SCREENPIXELSIZE.y || SCREENUV.y + y < margin * SCREENPIXELSIZE.y || SCREENUV.x + x > 1.0 - margin * SCREENPIXELSIZE.x || SCREENUV.x + x < margin * SCREENPIXELSIZE.x) { COLOR = backgroundcolor; } else { COLOR = texture(SCREENTEXTURE, SCREEN_UV + vec2(x, y)); }

} ```

If you haven't checked out our game yet, we would love for you to give a shot and give some feedback!

It can be played in your browser here: https://lunatic-games.itch.io/egress

How We Made Our Program Visual For Egress!

When you making a game about hacking, you obviously need some sort of unrealistic visualization of the process. That's just a given. For our game, Egress, we wrote a shader program using Godot's native shading language that would display an cyclical-bar-visualizer-thingamajig:

program.gif

The first step in the process was figuring out how to display the circle as individual bars that could be animated and scaled independently. This was done by getting the angle of a given UV coordinate with respect to the centre of the circle: vec2 xy = UV - 0.5; float angle = (atan(xy.y / xy.x)); Then, this angle was sorted into a section of the circle with a span equal to PI / number_of_bars float section = angle - mod(angle, 3.14159 / float(bars)); Now that we have the section it falls into, we can then use this to generate a random value unique to that section by using a random function (thanks Stack Overflow!): ``` uniform float seed; // Can be used to seed the random number generation

// https://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl float rand(float value) { return fract(sin(dot(vec2(value, seed), vec2(12.9898,78.233))) * 43758.5453); } ```

Using this function, we can generate a random height for each bar and have that height also fluctuate using the same random value: float radius = min_length + rand(section) * (max_length - min_length); radius += pulsate_strength * sin(rand(section) * 6.282 + TIME * pulsate_speed);

The last piece of the puzzle is using this radius for rendering the object. This is done by determining if the UV coordinate lies within a circle of that radius, using the implicit function of a circle: if (pow(xy.x, 2) + pow(xy.y, 2) < pow(radius, 2) && pow(xy.x, 2) + pow(xy.y, 2) > pow(inner_radius, 2)) { COLOR = vec4(1.0); } else { COLOR = vec4(0.0); } As the output colour is white, we can then modulate the resulting texture to be whatever colour we want!

The Complete Code

``` shadertype canvasitem;

uniform float innerradius : hintrange(0, 0.5); uniform float minlength : hintrange(0, 0.5) = 0.2; uniform float maxlength : hintrange(0, 0.5) = 0.5; uniform int bars : hintrange(2, 256); uniform float seed; uniform float rotationspeed; uniform float pulsatespeed; uniform float pulsatestrength;

// https://stackoverflow.com/questions/4200224/random-noise-functions-for-glsl float rand(float value) { return fract(sin(dot(vec2(value, seed), vec2(12.9898,78.233))) * 43758.5453); }

vec2 rotate(vec2 uv, float rotation) { vec2 rotated; rotated.x = uv.x * cos(rotation) - uv.y * sin(rotation); rotated.y = uv.x * sin(rotation) + uv.y * cos(rotation); return rotated; }

void fragment() { vec2 xy = rotate(UV - 0.5, TIME * rotation_speed);

float angle = (atan(xy.y / xy.x));
float section = angle - mod(angle, 3.14159 / float(bars));
float radius = min_length + rand(section) * (max_length - min_length);
radius += pulsate_strength * sin(rand(section) * 6.282 + TIME * pulsate_speed);
if (pow(xy.x, 2) + pow(xy.y, 2) < pow(radius, 2) &&
        pow(xy.x, 2) + pow(xy.y, 2) > pow(inner_radius, 2)) {
    COLOR = vec4(1.0);
} else {
    COLOR = vec4(0.0);
}

} ``` If you haven't checked out our game yet, we would love for you to give a shot and give some feedback!

It can be played in your browser here: https://lunatic-games.itch.io/egress