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;67vec4 Value3D_Deriv( vec3 P ) {8 // https://github.com/BrianSharpe/Wombat/blob/master/Value3D_Deriv.glsl910 // establish our grid cell and unit position11 vec3 Pi = floor(P);12 vec3 Pf = P - Pi;13 vec3 Pf_min1 = Pf - 1.0;1415 // clamp the domain16 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 );1819 // calculate the hash20 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 );2627 // blend the results and return28 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}3637void main () {38 vec2 myIndex = vec2((index + 0.5)/1024.,1.0);3940 vec4 pos = texture2D( positions, myIndex);4142 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;4546 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;5051}
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 px2vec2 spriteSize = vec2(256, 256.0); // In px3float index = 1.0; // Sprite index in sprite sheet4float w = spriteSheetSize.x;5float h = spriteSheetSize.y;67// Normalize sprite size (0.0-1.0)8float dx = spriteSize.x / w;9float dy = spriteSize.y / h;1011// Figure out number of tile cols of sprite sheet12float cols = w / spriteSize.x;1314// From linear index to row/col pair15float col = mod(index, cols);16float row = floor(index / cols);1718// Finally to UV texture coordinates19vec2 uv = vec2(dx * gl_PointCoord.x + col * dx, 1.0 - dy - row * dy + dy * gl_PointCoord.y);2021float alpha = texture2D(orb, uv).a;2223//If transparent, don't draw24if (alpha < 0.01) discard;252627vec2 tile = floor(uv);2829vec2 center = tile + vec2(0.5, 0.5);3031vec2 randomRotatedTileUV = rotateUV(uv , rand(tile, 2.0) * 20.0, center);3233vec4 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);45vec3 lightDirection = normalize(directionalLightDirection);67float diffuse = max(dot(vNormal, lightDirection), 0.0);8vec3 diffuseColor = color.rgb * directionalLightColor;910vec3 outputColor = diffuseColor + ambientLightColor;11float noise = Perlin3D(color.rgb);
And the final bit:
1float edgeWidth = 2.0;2float softness = 15.0;34float depthDiff = length(vec3(dFdx(vDepth), dFdy(vDepth), edgeWidth));56float fadeFactor = smoothstep(0.0, softness, depthDiff);78gl_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.