FDNavigate back to the homepage

Sublime Soft Particle Fog Effect

Rick
May 21st, 2023 · 1 min read

This was the cumulation of lots of research and code snippets from around various blogs and SO answers, to many to name here and examining various aspects of graphics to produce a reasonable soft particle fog effect.

This could be a useful way of creating a fake volumetric effect. Mixing sprites and colours at different depths, to give the overall effect of fog.

As you can see the effect is a mixture of this article (which creates positions for particles in a image) and uses noise to mix depths and colors together.

A quick run through of the shaders

The vertex shader is what I have done a few times before via storing positions inside of an image’s pixels and this allows you to easily spread the sprites across the ground of the grid helper.

1uniform sampler2D positions;
2varying vec2 vUv;
3varying vec3 vNormal;
4varying float vDepth;
5attribute float index;
6
7vec4 Value3D_Deriv( vec3 P ) {
8 // https://github.com/BrianSharpe/Wombat/blob/master/Value3D_Deriv.glsl
9
10 // establish our grid cell and unit position
11 vec3 Pi = floor(P);
12 vec3 Pf = P - Pi;
13 vec3 Pf_min1 = Pf - 1.0;
14
15 // clamp the domain
16 Pi.xyz = Pi.xyz - floor(Pi.xyz * ( 1.0 / 69.0 )) * 69.0;
17 vec3 Pi_inc1 = step( Pi, vec3( 69.0 - 1.5 ) ) * ( Pi + 1.0 );
18
19 // calculate the hash
20 vec4 Pt = vec4( Pi.xy, Pi_inc1.xy ) + vec2( 50.0, 161.0 ).xyxy;
21 Pt *= Pt;
22 Pt = Pt.xzxz * Pt.yyww;
23 vec2 hash_mod = vec2( 1.0 / ( 635.298681 + vec2( Pi.z, Pi_inc1.z ) * 48.500388 ) );
24 vec4 hash_lowz = fract( Pt * hash_mod.xxxx );
25 vec4 hash_highz = fract( Pt * hash_mod.yyyy );
26
27 // blend the results and return
28 vec3 blend = Pf * Pf * Pf * (Pf * (Pf * 6.0 - 15.0) + 10.0);
29 vec3 blendDeriv = Pf * Pf * (Pf * (Pf * 30.0 - 60.0) + 30.0);
30 vec4 res0 = mix( hash_lowz, hash_highz, blend.z );
31 vec4 res1 = mix( res0.xyxz, res0.zwyw, blend.yyxx );
32 vec4 res3 = mix( vec4( hash_lowz.xy, hash_highz.xy ), vec4( hash_lowz.zw, hash_highz.zw ), blend.y );
33 vec2 res4 = mix( res3.xz, res3.yw, blend.x );
34 return vec4( res1.x, 0.0, 0.0, 0.0 ) + ( vec4( res1.yyw, res4.y ) - vec4( res1.xxz, res4.x ) ) * vec4( blend.x, blendDeriv );
35}
36
37void main () {
38 vec2 myIndex = vec2((index + 0.5)/1024.,1.0);
39
40 vec4 pos = texture2D( positions, myIndex);
41
42 float x = (pos.x - 0.5) * 10.0;
43 float y = (pos.y - 0.5) * 10.0;
44 float z = (pos.z - 0.5) * 10.0;
45
46 gl_PointSize = 150.0 * Value3D_Deriv(vec3(x,y,z)).r;
47 gl_Position = projectionMatrix * modelViewMatrix * vec4(x,y,z, 1.0) ;
48 vNormal = normal;
49 vDepth = gl_Position.z / gl_Position.w;
50
51}

Im not going into great detail about this, but essential converting positional data sampled from the image in the range 0-1 to the range that was defined in blender.

Also adding some random noise to the size of the points. See how we get the fog effect but with a drastic reduction of computational power. And its super quick! as youd expect with sampling and just points.

The fog fragment shader

The first part of the shader is to generate uvs and get the sprite we want to use from our sprite sheet.

1vec2 spriteSheetSize = vec2(1280.0, 768.0); // In px
2vec2 spriteSize = vec2(256, 256.0); // In px
3float index = 1.0; // Sprite index in sprite sheet
4float w = spriteSheetSize.x;
5float h = spriteSheetSize.y;
6
7// Normalize sprite size (0.0-1.0)
8float dx = spriteSize.x / w;
9float dy = spriteSize.y / h;
10
11// Figure out number of tile cols of sprite sheet
12float cols = w / spriteSize.x;
13
14// From linear index to row/col pair
15float col = mod(index, cols);
16float row = floor(index / cols);
17
18// Finally to UV texture coordinates
19vec2 uv = vec2(dx * gl_PointCoord.x + col * dx, 1.0 - dy - row * dy + dy * gl_PointCoord.y);
20
21float alpha = texture2D(orb, uv).a;
22
23//If transparent, don't draw
24if (alpha < 0.01) discard;
25
26
27vec2 tile = floor(uv);
28
29vec2 center = tile + vec2(0.5, 0.5);
30
31vec2 randomRotatedTileUV = rotateUV(uv , rand(tile, 2.0) * 20.0, center);
32
33vec4 color = texture2D(orb, uv);

So we get the uv, randomly rotate so we dont get uniform look and feel. And then sample the sprite texture. A way to improve this is to random generate the index and get different sprites per point from the sprite sheet.

Light calculations are pretty basic here as I dont have a indepth grasp of complex lighting.

1vec3 directionalLightDirection = vec3(1.0,0.0,0.1);
2vec3 directionalLightColor = vec3(0.2,0.2,0.2);
3vec3 ambientLightColor = vec3(0.3,0.4,0.4);
4
5vec3 lightDirection = normalize(directionalLightDirection);
6
7float diffuse = max(dot(vNormal, lightDirection), 0.0);
8vec3 diffuseColor = color.rgb * directionalLightColor;
9
10vec3 outputColor = diffuseColor + ambientLightColor;
11float noise = Perlin3D(color.rgb);

And the final bit:

1float edgeWidth = 2.0;
2float softness = 15.0;
3
4float depthDiff = length(vec3(dFdx(vDepth), dFdy(vDepth), edgeWidth));
5
6float fadeFactor = smoothstep(0.0, softness, depthDiff);
7
8gl_FragColor = vec4(outputColor, 1.0 * fadeFactor * gl_FragCoord.z);

Softens the edges of the sprites which gives a more natural effect and then spits out the output color along with the alpha combined with the depth and edge softener.

Why?

Why is this needed or wanted?

Two reasons -

The process of creating positions or animations is super simple with my script so animating fog is now easier. Go have alook at the amazing export article on particles from blender.

And efficiency! This is magnitudes quicker than real time volumetric clouds, or atleast the attempt I made 2-3 years ago.

More articles from theFrontDev

Floodfill Growth Patterns with Decals and Dynamic Shaders

Immerse yourself in a world of captivating growth patterns through a shadertoy ported floodfill-powered solution. Seamlessly blending custom meshes, intricate decals, offscreen rendering and dynamic rendering techniques, the innovative approach unlocks a new level of visual artistry. Unleash your creativity as you harness the power of custom meshes and decals to shape and influence the growth, creating unique visuals. Discover the potential of floodfill-driven shaders and elevate your projects with this dynamic and immersive experience.

May 20th, 2023 · 2 min read

Dynamic Flow Map Painter in R3F - Create Stunning Visualizations with Real-Time Interactivity

Discover the power of a dynamic flow map painter in @react-three/fiber, the cutting-edge JavaScript technique for 3D graphics. With real-time interactivity, you can create stunning visualizations of your flowing entities. All done in real time!

April 29th, 2023 · 2 min read
© 2021–2024 theFrontDev
Link to $https://twitter.com/TheFrontDevLink to $https://github.com/Richard-ThompsonLink to $https://www.linkedin.com/in/richard-thompson-248ba3111/