- Description: Occlude objects with beams of light!
- GM Version: GM 8
- Registered: Yes (usage of surfaces)
- File Type: *.gmk
- File Size: 36.6 kB (~0.04 MB)
- File Link: Download here from Host-A.
- Credit: Just put ' Volumentric Lighting by Phantom107 ' in the credits, please.
An introduction for Volumetric Lighting. What does it mean?
Volumetric lighting is a technique used in computer graphics to add lighting effects to a rendered scene. It allows the viewer to see beams of light shining through the environment; seeing sunbeams streaming through an open window is an example of volumetric lighting. The term seems to have been introduced from cinematography and is now widely applied to graphics and rendering. In volumetric lighting, the light cone emitted by a light source is modeled as a transparent object and considered as a container of a "volume": as a result, light has the capability to give the effect of passing through an actual three dimensional medium (such as fog, dust, smoke, or steam) that is inside its volume, just like in the real world.
Why should this be used in video games?
Volume lights can greatly enhance the quality of almost any game scene. It enhances a certain sense of immersiveness and ambience, because of the way the light interacts with the environment. And video games are all about interaction, right? People often underestimate the power of game lighting. Imagine a forest where the sun shines through the tree leafs, or a hefty explosion where the objects are occluded by lighting caused by the bright flash. There are a lot of possibilities. Have you ever witnessed huge volumes of sunlight pass through gaps in the clouds? Such effect can be achieved with this technique.
So how does it look?
Here are two screenshots from the example to (hopefully) get your attention:
What about efficiency? Will my computer catch fire?
The example is setup to be pretty efficient. By using surfaces, only 1 render of the scene is required per step, as you'd normally do anyway. Computation speed varies on your quality preference. In the example I'm using 15 samples of the volume light-surface, which is in my opinion not much at all.
How it works
A quick note...
You will be required to be pretty fluent with GML. I'm using surfaces so it's not an easy copy-paste trick. You have to understand what everything does, but I'll do my best to explain it. This is not intended for beginners, but if you are one, I think this will be quite helpful, as it deals with arrays, surfaces, and overruling GM's drawing order.
Step 1 - Rendering the scene to a surface
Every step (or frame, if you will) the scene needs to be rendered to a seperate surface. I'm intentionally seperating the game objects from the background, so the Volumetric Lighting doesn't affect the background, which is undesirable, unless you use parrallax backgrounds. But even then it's completely compatible with this technique.
In the controller's create event, 2 surface are created. One to render the game objects to, and another one to compute the volume light-image:
// Create surface to render to render_surface = surface_create(960, 600); // Create the volumetric lighting surface volumetric_surface = surface_create(960, 600);
In the step event, the game objects are drawn to the first surface. All game objects have been set to not-visible, so GM won't draw them again in the draw event. They can be any shape or any kind of sprite, even with different alpha values. This gives you great freedom, and even allows for semi-transparent windows with different kinds of colored glass scattered all over. Imagine the possibilities!
A "with" statement is used to "force"-draw the game objects on the surface. It's not required to set texture interpolation to true, but in the examples the objects are rotating constantly so they required a smooth look. Smooth sprites make the light look a bit smoother aswell!
// start rendering to the surface surface_set_target(render_surface); // clear surface to have absoltely nothing on it draw_clear_alpha(0, 0); // draw the objects in the scene on the surface texture_set_interpolation(1); with Object draw_sprite_ext(sprite_index, 0, x, y, 1, 1, image_angle, c_white, 1); texture_set_interpolation(0); // finish rendering to the surface and let GM draw to the game window again surface_reset_target();
It will look like this internally:
Note that the gray squares in the background are to indicate 100% transparency.
Step 2 - Computing the volume light-image
Computing the volume light-image is essential in this technique. Basically it creates the image that is "sampled" on top of the scene in the draw event.
It is computed like so:
// start rendering to the surface surface_set_target(volumetric_surface); // clear surface to have absoltely nothing on it draw_clear_alpha(0, 0); // draw the light draw_sprite_ext(SprLightGlow, 0, mouse_x, mouse_y, 6, 6, 0, c_white, 1); // draw the rendered scene in black (this is without (!!!) background elements, or it won't work) draw_surface_ext(render_surface, 0, 0, 1, 1, 0, 0, 1); // finish rendering to the surface and let GM draw to the game window again surface_reset_target();
You might be wondering why I'm drawing the rendered scene on top of it in black. This is what creates the "gaps" in the light. By using an additive blend mode, all the white colors are drawn while the black ones are not. This way I'm able to only draw the light.
Using the additive blend mode, the computed light volume-image would look like this:
Notice the "gaps" in the light created by the objects!
Step 3 - Drawing the lighting in the draw event
The drawing order needs to be fixed:
1. Draw the game's background (this was not rendered to the surface in the step event!). I don't have an actual background in the example, so I'm using draw_clear(c_gray) to use a gray color as the background.
2. Draw the rendered game objects image in the step event (the first surface).
3. Draw the volume light-image in a "sampled" manner. This means the image is drawn multiple times. In this case the image is also scaled, to create the beams of light.
I think the code is pretty much self-explanatory:
// first draw the background of the scene draw_set_color(c_white); draw_clear(c_gray); // draw the surface with the scene objects draw_surface(render_surface, 0, 0); // from the light's position, draw the volumetric lighting in an expanding way texture_set_interpolation(1); draw_set_blend_mode(bm_add); // use additive blend mode so white will be drawn and black won't a = 1; repeat 15 // initiate loop ... higher repeat count means higher quality begin a += 0.05; // increase scale // draw surface, positioned and scaled accordingly to the focus point draw_surface_ext(volumetric_surface, (mouse_x - a*mouse_x), (mouse_y - a*mouse_y), a, a, 0, c_white, 0.05); end; draw_set_blend_mode(bm_normal); // set default blend mode texture_set_interpolation(0);
Notice how the "sampled" surfaces are drawn with an alpha of just 0.05.... this is to make sure the light isn't too bright.
The result looks like this: