Fog environment

JeanSwamp

Member
Hello there,

Been digging now and then for possible ways to create environment mood to the levels. I think Dead Cells is a good example on this to pixel art games.

What's a good approach to create fog into a 2d platformer in a way that can be used for different feelings, for example some water-ish atmosphere for rain, dust for a desert, and snow-ish for some sort of ice-level.

Thanks in advance!
 

trentallain

Member
Hello there,

Been digging now and then for possible ways to create environment mood to the levels. I think Dead Cells is a good example on this to pixel art games.

What's a good approach to create fog into a 2d platformer in a way that can be used for different feelings, for example some water-ish atmosphere for rain, dust for a desert, and snow-ish for some sort of ice-level.

Thanks in advance!
You could draw a slightly alpha coloured rectangle to a surface in the colour you like to represent the mood, and then subtract a circle around the player from the surface to make it easier to see around you.

For dust and fog, you could create some large sprites from them that just hover around at the edges of the screen. You can use all of the above in addition to some rain/snow particle effects too
 

NightFrost

Member
I've made mist with tileable sprite that was based on photoshop cloud filter plus some motion blur horizontally and turning the color gradient into alpha. (I forget how I tiled it, as PS clouds normally don't.) I recall I had it drawn twice in GMS, first pass getting an offset that moved right, the second getting an offset that moved left. I ended up with that after studying how some old 2D JRPGs drew their mist. For sand- and snowstorms, I suppose you can draw just one pass and make it move pretty fast and tint it with sprite_draw_ext, and add some single pixel-sized particles for extra effect.
 

JeanSwamp

Member
The thing is drawing a static sprite will feel a bit blend. I'm looking for something more in this matter:

 

pruto

Member
1. Put like 100 of this type of sprite
(maybe irregular shape with angular speed is better) on a surface and let them move randomly. Or you want the fog move slowly towards left, you can let them move left with random speed and drag them back to the right when they are out of the screen.
2. Use shader to enhance visual effect. For example:
Aggregating alpha 0-0.2 to 0.1, 0.2-0.4 to 0.3 and so on to generate fog layer effect
Set a random value from 0-4, and if texture pixel position mod 5 equals that value, then increase this pixel's alpha. This can generate dither effect. Or choose 1 pixel from 3x3 pixels to do that, whatever you want
Get those coords of light source, enhance the color and alpha of fog near them
 
1. Put like 100 of this type of sprite
(maybe irregular shape with angular speed is better) on a surface and let them move randomly. Or you want the fog move slowly towards left, you can let them move left with random speed and drag them back to the right when they are out of the screen.
2. Use shader to enhance visual effect. For example:
Aggregating alpha 0-0.2 to 0.1, 0.2-0.4 to 0.3 and so on to generate fog layer effect
Set a random value from 0-4, and if texture pixel position mod 5 equals that value, then increase this pixel's alpha. This can generate dither effect. Or choose 1 pixel from 3x3 pixels to do that, whatever you want
Get those coords of light source, enhance the color and alpha of fog near them
i thought i would give this fog thing a go myself as i like to try create other peoples problems aswell as my own as it helps me to learn different aspects im yet to reach myself. i quite liked the sound of this idea and i managed to create a very small basic fog demo. its very demanding on the fps but an effect none the less.

 
Here's something you could try, if you are okay using a shader.

Basically you just draw a sprite covering the areas where you want fog. And you use the shader to sample the fog texture at multiple positions that change over time. You can multiply together the resultant samples to determine the fog's opacity.

Make sure you turn texture repeating on for this texture. And make the fog sprite on its own texture page.

Example fog texture sprite:
upload_2019-5-14_11-24-3.png

See spoiler for implementation details:
If your fog sprite follows the view, then you don't want the texture to appear to move along with the camera. One relatively easy way to do that is to set the texture coordinates based on the current vertex position. In the vertex shader:
Code:
attribute vec3 in_Position;
varying vec2 v_vTexcoord;
void main() {
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4(in_Position,1.0);
    v_vTexcoord = in_Position.xy / 512.0;
}
Fragment shader:
Code:
    varying vec2 v_vTexcoord;
    uniform float time;
    const vec3 fog_color = vec3( 1.0, 1.0, 1.0);
    //velocity vectors
    const vec2 A_dir = vec2(0.76604,0.64279);
    const vec2 B_dir = vec2(0.58207,-0.48841);
    const vec2 C_dir = vec2(0.54253,0.19747);
    const vec2 D_dir = vec2(0.41224,-0.15004);
    const vec2 E_dir = vec2(0.33333,0.00000);
    void main() {
                                                                            //scale
        float A = texture2D( gm_BaseTexture, (v_vTexcoord + A_dir * time) * 1.000000000).r;
        float B = texture2D( gm_BaseTexture, (v_vTexcoord + B_dir * time) * 0.759835686).r;
        float C = texture2D( gm_BaseTexture, (v_vTexcoord + C_dir * time) * 0.577350269).r;
        float D = texture2D( gm_BaseTexture, (v_vTexcoord + D_dir * time) * 0.438691338).r;
        float E = texture2D( gm_BaseTexture, (v_vTexcoord + E_dir * time) * 0.333333333).r;
 
        //changes how much each component affects the fog density.
        E = pow(max(0.0,E),1.000000000); //affects most
        D = pow(max(0.0,D),0.759835686);
        C = pow(max(0.0,C),0.577350269);
        B = pow(max(0.0,B),0.438691338);
        A = pow(max(0.0,A),0.333333333); //affects least
 
        float fog = A*B*C*D*E;
 
        //At this point there are any number of operations you can perform on "float fog"
        //in order to increase or reduce the level of fog
        //or to make the fog appear more patchy or more uniform
        //For example, to make the fog much thinner, you could multiply "fog" by itself a few times:
        //fog = fog * fog * fog;
 
        gl_FragColor = vec4(fog_color,fog);
    }
An example of one way to draw the fog:
Code:
shader_set(sh_fog);
shader_set_uniform_f(shader_get_uniform(sh_fog,"time"),current_time/20000);  //note: probably should use your own time variable instead of current_time.
draw_sprite_stretched(spr_fog,0,view_xview,view_yview,view_wview,view_hview);  //if using GMS2, subsitute view_* with GMS2 equivalents.
shader_reset();
If you have trouble fitting a fog sprite to a rotating view, let me know, because there are easy ways to overcome that problem.
 
Last edited:

pruto

Member
i thought i would give this fog thing a go myself as i like to try create other peoples problems aswell as my own as it helps me to learn different aspects im yet to reach myself. i quite liked the sound of this idea and i managed to create a very small basic fog demo. its very demanding on the fps but an effect none the less.
I tried my method myself, and found it not bad. The fps of an empty project on my mac is 7000 while my fog demo is 4000-5000. And I found water effect appears when I scale the fog sprite.
I draw 120 of this sprite with different speed and scale on a surface, and applied my shader on that surface to generate fog layer effect. Might be better after adjusting some vars.

and it can be better in performance with perlin-noise picture. Good idea, i'll give it a try. Thanks to flyingsaucerinvasion
 
Last edited by a moderator:

pruto

Member
Here is the implementation of flyingsaucerinvasion's idea with the perlin noise sprite stretched to 512x512. Fps is 5000-6000 on my mac and the effect is cool.
I have been working on my game for 5 months, and fog & water effects await to be done. This is really an enlightenment for me.
 
I've found that if you take the basic random noise premise like we've been talking about in the least few posts, and adjust it in the following way, you can get some pretty cool vortex looking results. What you do is you compute a random value twice. Compute it once, and then feed it in as a random offset for a second value. The consequence is your uv positition is stretched and squished in ways that vary over time, which sort of looks like the random vortices in fog. The downside is it is rather difficult to control. It takes a lot of tweaking.
 
I tried my method myself, and found it not bad. The fps of an empty project on my mac is 7000 while my fog demo is 4000-5000. And I found water effect appears when I scale the fog sprite.
I draw 120 of this sprite with different speed and scale on a surface, and applied my shader on that surface to generate fog layer effect. Might be better after adjusting some vars.

and it can be better in performance with perlin-noise picture. Good idea, i'll give it a try. Thanks to flyingsaucerinvasion
man what an amazing outcome. this is such a nice thread a real eye opener.
 

NightFrost

Member
Ooh perlin noise. What's the base size since you say stretched? I recall reading on, and playing around with some perlin shaders one time I was looking for a cloud-ish effect, and they seemed pretty heavy on the GPU. Not sure how optimized the stuff was, back then I didn't really know much about shaders so it was for the most part copy-paste testing.
 

pruto

Member
Ooh perlin noise. What's the base size since you say stretched? I recall reading on, and playing around with some perlin shaders one time I was looking for a cloud-ish effect, and they seemed pretty heavy on the GPU. Not sure how optimized the stuff was, back then I didn't really know much about shaders so it was for the most part copy-paste testing.
I stretched that noise picture from 100x100 to 512x512 for better use in shader.
Don't try to generate perlin noise with algorithm in gamemaker, it's a disaster cause gamemaker performs poor in calculating. Shader is good at handling pixel. Assume there are several noise sprites with different speed on the stage and use shader to calculate every pixel color based on the pixel color of those overlayed noise sprite in that position. Then a mixed foglike view is generated. Coding in the shader, you can pretend there are several sprites and calculate the pixel color of them with 'time' variable and their speed, while only call draw_sprite function once. That's how that fog shader works
 

NightFrost

Member
Oh ok, I was getting the idea that the noise was generated on the fly. The stuff I tested had the perlin noise generated realtime in a shader, and that didn't seem very powerful either.
 
Here's something you could try, if you are okay using a shader.

Basically you just draw a sprite covering the areas where you want fog. And you use the shader to sample the fog texture at multiple positions that change over time. You can multiply together the resultant samples to determine the fog's opacity.

Make sure you turn texture repeating on for this texture. And make the fog sprite on its own texture page.

Example fog texture sprite:
View attachment 24760

See spoiler for implementation details:
If your fog sprite follows the view, then you don't want the texture to appear to move along with the camera. One relatively easy way to do that is to set the texture coordinates based on the current vertex position. In the vertex shader:
Code:
attribute vec3 in_Position;
varying vec2 v_vTexcoord;
void main() {
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4(in_Position,1.0);
    v_vTexcoord = in_Position.xy / 512.0;
}
Fragment shader:
Code:
    varying vec2 v_vTexcoord;
    uniform float time;
    const vec3 fog_color = vec3( 1.0, 1.0, 1.0);
    //velocity vectors
    const vec2 A_dir = vec2(0.76604,0.64279);
    const vec2 B_dir = vec2(0.58207,-0.48841);
    const vec2 C_dir = vec2(0.54253,0.19747);
    const vec2 D_dir = vec2(0.41224,-0.15004);
    const vec2 E_dir = vec2(0.33333,0.00000);
    void main() {
                                                                            //scale
        float A = texture2D( gm_BaseTexture, (v_vTexcoord + A_dir * time) * 1.000000000).r;
        float B = texture2D( gm_BaseTexture, (v_vTexcoord + B_dir * time) * 0.759835686).r;
        float C = texture2D( gm_BaseTexture, (v_vTexcoord + C_dir * time) * 0.577350269).r;
        float D = texture2D( gm_BaseTexture, (v_vTexcoord + D_dir * time) * 0.438691338).r;
        float E = texture2D( gm_BaseTexture, (v_vTexcoord + E_dir * time) * 0.333333333).r;

        //changes how much each component affects the fog density.
        E = pow(max(0.0,E),1.000000000); //affects most
        D = pow(max(0.0,D),0.759835686);
        C = pow(max(0.0,C),0.577350269);
        B = pow(max(0.0,B),0.438691338);
        A = pow(max(0.0,A),0.333333333); //affects least

        float fog = A*B*C*D*E;

        //At this point there are any number of operations you can perform on "float fog"
        //in order to increase or reduce the level of fog
        //or to make the fog appear more patchy or more uniform
        //For example, to make the fog much thinner, you could multiply "fog" by itself a few times:
        //fog = fog * fog * fog;

        gl_FragColor = vec4(fog_color,fog);
    }
An example of one way to draw the fog:
Code:
shader_set(sh_fog);
shader_set_uniform_f(shader_get_uniform(sh_fog,"time"),current_time/20000);  //note: probably should use your own time variable instead of current_time.
draw_sprite_stretched(spr_fog,0,view_xview,view_yview,view_wview,view_hview);  //if using GMS2, subsitute view_* with GMS2 equivalents.
shader_reset();
If you have trouble fitting a fog sprite to a rotating view, let me know, because there are easy ways to overcome that problem.
Hey, thank you for this code. I am unable to use it in GMS2, unfortunately (even using the corresponding equivalents to view_xview etc).
The image does not appear tiled at all and seems to shrink itself until it disappears completely.

Can someone look at what might I be doing wrong?
The code is written below.

Or if anyone knows a tutorial on how to make this, that would be great.

Thank you in advance, guys.

Shader:
sh_fog.vsh

attribute vec3 in_Position;
varying vec2 v_vTexcoord;
void main() {
gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * vec4(in_Position,1.0);
v_vTexcoord = in_Position.xy / 512.0;
}

sh_fog.fsh

varying vec2 v_vTexcoord;
uniform float time;
const vec3 fog_color = vec3( 1.0, 1.0, 1.0);
//velocity vectors
const vec2 A_dir = vec2(0.76604,0.64279);
const vec2 B_dir = vec2(0.58207,-0.48841);
const vec2 C_dir = vec2(0.54253,0.19747);
const vec2 D_dir = vec2(0.41224,-0.15004);
const vec2 E_dir = vec2(0.33333,0.00000);
void main() {
//scale
float A = texture2D( gm_BaseTexture, (v_vTexcoord + A_dir * time) * 1.000000000).r;
float B = texture2D( gm_BaseTexture, (v_vTexcoord + B_dir * time) * 0.759835686).r;
float C = texture2D( gm_BaseTexture, (v_vTexcoord + C_dir * time) * 0.577350269).r;
float D = texture2D( gm_BaseTexture, (v_vTexcoord + D_dir * time) * 0.438691338).r;
float E = texture2D( gm_BaseTexture, (v_vTexcoord + E_dir * time) * 0.333333333).r;

//changes how much each component affects the fog density.
E = pow(max(0.0,E),1.000000000); //affects most
D = pow(max(0.0,D),0.759835686);
C = pow(max(0.0,C),0.577350269);
B = pow(max(0.0,B),0.438691338);
A = pow(max(0.0,A),0.333333333); //affects least

float fog = A*B*C*D*E;

//At this point there are any number of operations you can perform on "float fog"
//in order to increase or reduce the level of fog
//or to make the fog appear more patchy or more uniform
//For example, to make the fog much thinner, you could multiply "fog" by itself a few times:
//fog = fog * fog * fog;

gl_FragColor = vec4(fog_color,fog);
}

obj_fog (dropped inside room)

Draw Event

var gx = camera_get_view_x(view_camera[0]);
var gy = camera_get_view_y(view_camera[0]);
var gw = camera_get_view_width(view_camera[0]);
var gh = camera_get_view_height(view_camera[0]);

shader_set(sh_fog);
shader_set_uniform_f(shader_get_uniform(sh_fog,"time"),current_time/100000); //note: probably should use your own time variable instead of current_time.
draw_sprite_stretched(spr_fog,0,gx,gy,gw,gh); //if using GMS2, subsitute view_* with GMS2 equivalents.
shader_reset();
 

muki

Member
Whether you use 100s of sprites, or a scrolling alpha blended surface, a large factor that makes those kinds of effects work is lighting. You need some kind of lighting, even hacked. Just areas of light and dark that you can place so that the fog patterns shift in and out of "volume".
 
Thank you for the reply, muki.

I didn't see any reference to a lighting system on the videos in this thread nor in the examples given. I have used shaders before and didn't think that having a lighting system was a requirement.
I just thought something was lost between the adaptation code from GMS1 to GMS2.

Following flyingsaucerinvasion 's code, what I get is the fog sprite appearing on the screen itself (square) not filling the screen and it shrinks according to the time set (current_time/200000) until it disappears.
The sprite does not appear tiled at all, and does not fill the screen (see first image).

If I use the draw_sprite_stretched command without the rest of the shader code in the draw event, it fills the screen, streched as it should. (see second image)

Fog Shader.jpgFog Shader 2.jpg

Any insights on what is happening?
 

Attachments

muki

Member
By lighting system, I meant it could be as simple as handplacing additive sprites in key areas, representing ambient glows and halos, and multiplying/modulating the fog/mist with those.
 

NightFrost

Member
Side note: since the posts I've written above, I have read about Perlin noise and tested it a lot. I can tell that a properly written noise shader is a very efficient way to generate mist.
 
Thanks you for the reply, guys.

All very nice insights about this stuff.

I did a version of this with the in game engine particle system and it looks really nice. The best of it is that I can select where I want to place the fog.
And the fps don't drop that much. Still have to test it in other less powerful computers.

Muki, I will try to add some lighting system to the lamps in hope that it will emphasize the fog a bit.

Wish you guys a great day.
 
Last edited:

Yal

🍋 *lemon noises*
GMC Elder
Hello there,

Been digging now and then for possible ways to create environment mood to the levels. I think Dead Cells is a good example on this to pixel art games.

What's a good approach to create fog into a 2d platformer in a way that can be used for different feelings, for example some water-ish atmosphere for rain, dust for a desert, and snow-ish for some sort of ice-level.

Thanks in advance!
Draw a scrolling texture to a surface (it can have multiple semi-transparent layers that scrolls at different speeds for more depth), then erase bits of it by first changing the draw mode to only affect the alpha channel, then applying masks around anything that is not affected by the fog. Finally draw the surface to the screen.
(Surface/alpha interactions are a bit messy so it might be easier to have a secondary grayscale surface to use as an alpha mask, then pass them both into a shader that mixes them together on the fly, than to edit the alpha directly)

The masks usually are smooth circles that start at full transparency and changes to 0 transparency with a radial gradient, but doesn't necessarily need to be. For instance, the fog might feel more like desert sand if you use a rough dithering pattern instead of a smooth gradient.

Also the texture you use obviously matters as well, pure black makes it work like a darkness effect, while outdoors fog looks more realistic if it's got a cloud texture and also caps out at 0.5 opacity instead of being fully opaque. (Or even better if the texture is a screenshot of the background without effects, then faded towards white and blurred with gaussian bilinear blur, so you can still see background details but they're foggy). Snow would ditch the "a single texture" approach and draw hundreds of individual scrolling snowflakes (actually multiple layers with different scale that scrolls at different speeds in mostly the same direction) so there's clear gaps between the flakes, but it still impacts visibility.
 
Top