• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

2D bloom/godray's effect?

Niels

Member
How would something like this be archieved in gamemaker?


I guess it's done with some sort of bloom shader that's set on a background layer, but beyond that I'm clueless.
 
Shaders and surfaces. I'm uncertain about god rays, but the bloom is like a blur that blackens the darks and brightens the lights. It's also best if there are shadows or shaded backgrounds with illumination to make that bloom rather than brighter environments.

Someone else can fill you in on god rays, it's more of a light angle vs obstructions to create shadows. There are quite a few similar god rays and blooms in the marketplace. It takes a while to master.
 
M

MishMash

Guest
A simple, but effective god ray shader can be implemented using simple ray-marching.
There are a few common approaches, but generally, you can either implement something that for each pixel, walks towards the centre of the lightsource, adding colours as it goes.

The idea is that you run the shader on a surface without a background, so that as you the ray marches across "empty" spots or white spots, you accumulate much more brightness than if you walk over the scene.
Depending on whether you want colour bleeding or not, you can simply just sample the alpha channel (alpha 0 = fully white, alpha 1 = black - no contribution).

Bit of simple pseudo code would look something like this:

Code:
uniform vec2 lightpos; // screenspace position of lightsource
#define STEPS 24
...
// Calculate vector towards light
vec2 v_towards_light = (lightpos - v_vTexcoord)/STEPS ; // Get the unit vector that will reach the centre of our light source in the given number of steps
vec4 accumulated_colour = vec4(0.0,0.0,0.0,0.0);
vec2 sample_coord = v_vtexcoord; // Our current sample coordinate

for(int i=0; i < STEPS ; i++){
           // Sample at current location, then step closer to the light
           vec4 sample = texture2D(gm_BaseTexture, sample_coord);
           accumulated_colour  += sample.rgb*(1.0-sample.a);  // Weight sample based on inverse alpha -- We want the gaps in the background to be "bright spots"
           sample_coord  += v_towards_light;
}
gl_FragColor = accumulated_colour*0.5  / float(SAMPLES); // Need to multiply the end result by some factor to reduce brightness. You would need to play around with this. (Also weighted by sample count)
A more advanced version could weight the samples based on how close they are to the light -- you can basically keep on throwing in features like this to adjust the reuslt
Code:
uniform vec2 lightpos; // screenspace position of lightsource
#define STEPS 24
...
// Calculate vector towards light
vec2 v_towards_light = (lightpos - v_vTexcoord)/STEPS ; // Get the unit vector that will reach the centre of our light source in the given number of steps
vec4 accumulated_colour = vec4(0.0f,0.0f,0.0f,0.0f);
vec2 sample_coord = v_vtexcoord; // Our current sample coordinate
float intensity = 0.0f;

for(int i=0; i < STEPS ; i++){
           // Increase sample contribution
           intensity += 1.0f/float(STEPS);
           // Sample at current location, then step closer to the light
           vec4 sample = texture2D(gm_BaseTexture, sample_coord);
           accumulated_colour  += sample.rgb*(1.0f-sample.a);
           sample_coord  += v_towards_light;
}
gl_FragColor = accumulated_colour*0.5  / float(SAMPLES); // Need to multiply the end result by some factor to reduce brightness. You would nede to play around with this
If we wanted a version with colour bleeding, you could adjust the accumulation process to allow partial colour in:
Code:
uniform vec2 lightpos; // screenspace position of lightsource
#define STEPS 24
...
// Calculate vector towards light
vec2 v_towards_light = (lightpos - v_vTexcoord)/STEPS ; // Get the unit vector that will reach the centre of our light source in the given number of steps
vec4 accumulated_colour = vec4(0.0f,0.0f,0.0f,0.0f);
vec2 sample_coord = v_vtexcoord; // Our current sample coordinate
float intensity = 0.0f;

for(int i=0; i < STEPS ; i++){
           // Increase sample contribution
           intensity += 1.0f/float(STEPS);
           // Sample at current location, then step closer to the light
           vec4 sample = texture2D(gm_BaseTexture, sample_coord);
           accumulated_colour  += sample.rgb*(1.5f-sample.a);
           sample_coord  += v_towards_light;
}
gl_FragColor = accumulated_colour*0.5  / float(SAMPLES); // Need to multiply the end result by some factor to reduce brightness. You would nede to play around with this
This is the sort of thing where you really have to play around with all of your inputs and your process until you get the desired result. Its a relatively simple concept, but there are lots of small details you can just to change the softness/hardness, colours, brightness, intensity etc; of the result. The only key performance indicator is that the greater the value of SAMPLES, the higher quality the result, but the worse the performance.
 

NightFrost

Member
Interesting stuff. I suppose the STEPS count should be kept well in check as surface size increases. I've noticed, when playing around with blur shaders, that looping getting out of hand is a great way to tank performance.
 
M

MishMash

Guest
Interesting stuff. I suppose the STEPS count should be kept well in check as surface size increases. I've noticed, when playing around with blur shaders, that looping getting out of hand is a great way to tank performance.
Yep definitely, there are a few considerations here, total number of steps affects the computational performance, however, if your distance to the source is too large, then the gap between segments becomes relevant. If this is too large, then you will hit a texture cache crash, where too many samples occur outside of local texture space. (Ideally, lots of similar samples will share similar results within the cache).
(Locally near by fragments will execute in SIMD, meaning that as they are likely to sample the same areas as they raymarch, the first fragment will pull a block of texture data into the cache, and others will be able to sample that as well. -- This is why performance is generally okay in this case, despite it being a raymarch technique).

A more efficient version of this could involve not stepping towards the light completely, but rather within a radius of the light, so we could make our step-size half of what it is. Godrays would still retain direction in that instance.
There are plenty of other ways to improve efficiency, but this should be a starting point.
 
Top