Skip to content

Volumetric Effect Shaders

Shaders for atmospheric and volumetric effects including fog, god rays, and underwater rendering.

import {
volumetricFogShader,
godRaysVertexShader,
godRaysFragmentShader,
underwaterShader,
} from '@strata-game-library/shaders';

Height and distance-based fog with optional animation:

UniformTypeDescription
uTimefloatAnimation time
uCameraPosvec3Camera world position
uFogColorvec3Fog color
uDensityfloatOverall fog density
uHeightFallofffloatVertical density falloff
uBaseHeightfloatFog floor height
uMaxHeightfloatFog ceiling height
uNoiseScalefloatNoise pattern scale
uWindDirectionvec3Fog movement direction
import { volumetricFogShader } from '@strata-game-library/shaders';
const fogMaterial = new THREE.ShaderMaterial({
vertexShader: volumetricFogShader.vertex,
fragmentShader: volumetricFogShader.fragment,
uniforms: {
uTime: { value: 0 },
uCameraPos: { value: camera.position },
uFogColor: { value: new THREE.Color(0x8899aa) },
uDensity: { value: 0.02 },
uHeightFalloff: { value: 0.1 },
uBaseHeight: { value: 0 },
uMaxHeight: { value: 50 },
uNoiseScale: { value: 0.05 },
uWindDirection: { value: new THREE.Vector3(1, 0, 0.5) },
},
transparent: true,
depthWrite: false,
});

Volumetric light shafts from sun or other light sources:

UniformTypeDescription
uLightPositionvec3Light source position
uLightColorvec3Light color
uIntensityfloatRay intensity
uDecayfloatDistance falloff
uDensityfloatRay density
uWeightfloatSample weight
uSamplesintNumber of samples
uSceneTexturesampler2DScene render
uOcclusionTexturesampler2DOcclusion mask
import { godRaysVertexShader, godRaysFragmentShader } from '@strata-game-library/shaders';
const godRaysMaterial = new THREE.ShaderMaterial({
vertexShader: godRaysVertexShader,
fragmentShader: godRaysFragmentShader,
uniforms: {
uLightPosition: { value: new THREE.Vector3(100, 100, 0) },
uLightColor: { value: new THREE.Color(1, 0.95, 0.8) },
uIntensity: { value: 0.5 },
uDecay: { value: 0.95 },
uDensity: { value: 0.5 },
uWeight: { value: 0.4 },
uSamples: { value: 60 },
uSceneTexture: { value: sceneRT.texture },
uOcclusionTexture: { value: occlusionRT.texture },
},
transparent: true,
blending: THREE.AdditiveBlending,
});

Complete underwater overlay with fog, caustics, and color absorption:

UniformTypeDescription
uTimefloatAnimation time
uFogColorvec3Underwater fog color
uFogDensityfloatFog density
uAbsorptionColorvec3Color at depth
uAbsorptionRatefloatDepth color shift rate
uCausticsTexturesampler2DCaustics pattern
uCausticsScalefloatCaustics size
uCausticsSpeedfloatCaustics animation
uCausticsIntensityfloatCaustics brightness
uDepthfloatCurrent depth
import { underwaterShader } from '@strata-game-library/shaders';
const underwaterMaterial = new THREE.ShaderMaterial({
vertexShader: underwaterShader.vertex,
fragmentShader: underwaterShader.fragment,
uniforms: {
uTime: { value: 0 },
uFogColor: { value: new THREE.Color(0x003366) },
uFogDensity: { value: 0.03 },
uAbsorptionColor: { value: new THREE.Color(0x001122) },
uAbsorptionRate: { value: 0.1 },
uCausticsTexture: { value: causticsTexture },
uCausticsScale: { value: 0.5 },
uCausticsSpeed: { value: 0.3 },
uCausticsIntensity: { value: 0.4 },
uDepth: { value: 10 },
},
transparent: true,
});
float heightFog(vec3 worldPos, vec3 cameraPos) {
float rayLength = length(worldPos - cameraPos);
vec3 rayDir = normalize(worldPos - cameraPos);
// Integrate density along ray
float fogAmount = 0.0;
float stepSize = rayLength / 32.0;
for (int i = 0; i < 32; i++) {
vec3 samplePos = cameraPos + rayDir * stepSize * float(i);
float height = samplePos.y;
// Height-based density
float density = uDensity * exp(-height * uHeightFalloff);
density *= smoothstep(uMaxHeight, uBaseHeight, height);
fogAmount += density * stepSize;
}
return 1.0 - exp(-fogAmount);
}
vec3 godRays(vec2 uv, vec2 lightPos) {
vec2 deltaUV = (uv - lightPos) / float(uSamples) * uDensity;
vec3 color = vec3(0.0);
float illuminationDecay = 1.0;
vec2 sampleUV = uv;
for (int i = 0; i < uSamples; i++) {
sampleUV -= deltaUV;
vec3 sampleColor = texture2D(uOcclusionTexture, sampleUV).rgb;
sampleColor *= illuminationDecay * uWeight;
color += sampleColor;
illuminationDecay *= uDecay;
}
return color * uIntensity * uLightColor;
}
vec3 caustics(vec2 uv, float time) {
vec2 uv1 = uv * uCausticsScale + time * uCausticsSpeed * vec2(1.0, 0.5);
vec2 uv2 = uv * uCausticsScale * 1.3 + time * uCausticsSpeed * vec2(-0.5, 0.8);
float c1 = texture2D(uCausticsTexture, uv1).r;
float c2 = texture2D(uCausticsTexture, uv2).r;
float caustic = min(c1, c2);
return vec3(caustic) * uCausticsIntensity;
}