home

Simple Volumetric Rendering with Raymarching

Clouds are cool! I made this to learn how volumetric rendering works. I use a custom material on a box geometry to render a ball of cloud inside of it. Most of the work is done in the fragment shader where raymarching is used to step through the volume of a sphere (defined with a signed distance function).

Raymarching allows us to be efficient since we can take very large steps to get to our sphere volume and then use the rest of our computational power on actually rendering inside of the sphere. At each step the density and luminance are calculated for the volume. An approximation of Mie scattering is also used to help with making the lighting more realistic.

fragment.glsl
vec4 cloudRayMarch( vec3 rayOrigin, vec3 rayDir ) {
vec4 cloudColor = vec4( 0.0, 0.0, 0.0, 1.0 );
float dist = 0.0;
for ( int i = 0; i < MAX_STEPS; i++ ) {
// ray shot from our camera view
vec3 samplePos = rayOrigin + ( rayDir * dist );
// the defined sphere volume
float sdf = sdSphere( samplePos, 0.5 * u_scale );
// are we inside of the sphere? { sdf < EPSILON } is considered to be negative.
if ( sdf < EPS ) {
// calculate density + use noise to make it more interesting
float density = u_thickness * max( 0.0, cnoise( u_freq * samplePos + u_time ) ) * smoothstep(EPS, -0.25, sdf);
float transmittance = exp( -SIGMA_E * MARCH_STEP * density );
//calculate lighting at current step
vec3 luminance = evaluateLight( samplePos ) * ( u_is_scatter ? phaseFunction() : 1.0 );
vec3 integScatter = luminance - luminance * transmittance;
cloudColor.rgb += integScatter * cloudColor.a;
cloudColor.a *= transmittance;
// break the loop when alpha is too small i.e. we cannot see any further so no point doing extra steps/calculations
if (cloudColor.a < 0.001) {
cloudColor.a = 0.0;
break;
}
}
// if intersecting the sphere, we step by MARCH_STEP
// otherwise we step by sdf's returned value (if it is bigger than MARCH_STEP)
dist += sdf < EPS ? MARCH_STEP : max(sdf, MARCH_STEP);
}
return vec4(cloudColor.rgb, 1.0 - cloudColor.a);
}