I’ve recently tried to reproduce this technique in order to add global volumetric fog to my three.js scene. Everything is working as expected when using FBM with a simplex noise function as described in the video. At the end of the tutorial though, right here, Simon is mentioning an optimization technique I’m really curios about.
Basically, as far as I understood it’s better to use fractional Brownian motion by sampling a noise texture in order to avoid doing some extra work in the fragment shader (snoise function), which actually makes sense. Unfortunately, I’m not an expert in GLSL
shaders, so after trying for a while, I’ve come out with a result I’m pretty happy about, and now I’m wondering if this is a proper way to do it, or if I was just lucky.
Or maybe, even if my solution worked quite well, there’s even a better way to implement this. I’d really like to learn as much as possible about FBM and different ways an purposes to implement noise algorithms in shaders, so any help / advice will be much appreciated! And here is the code I’ve come out with.
JS override of three.js default fog shaders:
ShaderChunk.fog_pars_fragment = Config.Settings.bakedFog
? `#define USE_BAKED_FOG\n\n${parsFrag}` : parsFrag;
ShaderChunk.fog_pars_vertex = parsVert;
ShaderChunk.fog_fragment = fogFrag;
ShaderChunk.fog_vertex = fogVert;
“fog_pars_fragment” shader:
#ifdef USE_FOG
#include ../fBm;
uniform float fogTime;
uniform vec3 fogColor;
varying vec3 vWorldPosition;
#ifdef FOG_EXP2
uniform float fogDensity;
#else
uniform float fogNear;
uniform float fogFar;
#endif
#endif
“fog_fragment” shader:
#ifdef USE_FOG
float noiseSample;
const int FBM_OCTAVES = 6;
const float fogHeight = 0.5;
vec3 fogOrigin = cameraPosition;
vec3 sampleCoord = vWorldPosition * 0.00025;
float fogDepth = distance(vWorldPosition, fogOrigin);
vec3 fogDirection = normalize(vWorldPosition - fogOrigin);
#ifdef USE_BAKED_FOG
sampleCoord += vec3(fogTime * 0.025, 0.0, fogTime * 0.025);
noiseSample = fBmTex(
sampleCoord + fBmTex(sampleCoord)
);
#else
sampleCoord += vec3(0.0, 0.0, fogTime * 0.05);
noiseSample = fBm(sampleCoord + fBm(
sampleCoord, FBM_OCTAVES
), FBM_OCTAVES
);
#endif
fogDepth *= mix(noiseSample * 0.5 + 0.5, 1.0, saturate((fogDepth - 5000.0) / 5000.0));
fogDepth *= fogDepth;
float fogFactor = fogHeight * exp(-fogOrigin.y * fogDensity) * (
1.0 - exp(-fogDepth * fogDirection.y * fogDensity)
);
gl_FragColor.rgb = mix(
gl_FragColor.rgb, fogColor, saturate(
saturate(fogFactor / fogDirection.y)
) * 0.75
);
#endif
And this is fBm.glsl
with fBm
& fBmTex
functions, included in “fog_pars_fragment” shader:
// Fractional Brownian Motion by Inigo Quilez:
// https://www.iquilezles.org/www/articles/fbm/fbm.htm
// https://www.iquilezles.org/www/articles/warp/warp.htm
precision highp float;
#ifdef USE_BAKED_FOG
uniform sampler2D noise; // simplex noise texture
#else
#include snoise; // https://github.com/ashima/webgl-noise/blob/ff3b5d34eafb7ad97985e896235546ceaea5b2cf/src/noise3D.glsl
#endif
#ifndef USE_BAKED_FOG
float fBm (vec3 point, int octaves) {
float value = 0.0;
float amplitude = 0.5;
for (int o = 0; o < octaves; o++) {
value += amplitude * snoise(point);
point = point * 2.0 + 100.0;
amplitude *= 0.5;
}
return value;
}
#else
// Is this is all for baked FBM ???
float fBmTex (vec3 point) {
return texture(noise, point.zx).y * 0.5;
}
#endif