Volumetric Rendering
Advanced Computer Graphics
LTAT.05.035
Raimond Tunnel
What is Volumetric Rendering?
2 / 77
3 / 77
4 / 77
5 / 77
6 / 77
Volumetric Rendering
7 / 77
Volumetric Rendering
8 / 77
Volumetric Rendering
9 / 77
Volumetric Rendering
10 / 77
Δs
Volumetric Rendering
11 / 77
Δs
PHOTON DOES�NOT TRANSMIT
PHOTON TRANSMITS
Ray Marching a Volume
12 / 77
vec4 raymarch(vec3 samplePos, vec3 marchDir, int stepCount, float Δs) {
float transmittance = 1.0;
float illumination = 0.0;
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;
transmittance *= exp(-density(samplePos) * σextinction * Δs);
...� illumination += transmittance * currentLight * Δs;
}
return vec4(vec3(illumination), 1.0 - transmittance);
}
Ray Marching a Volume
13 / 77
vec4 raymarch(vec3 samplePos, vec3 marchDir, int stepCount, float Δs) {
float transmittance = 1.0;
float illumination = 0.0;
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;
transmittance *= exp(-density(samplePos) * σextinction * Δs);
...� illumination += transmittance * currentLight * Δs;
}
return vec4(vec3(illumination), 1.0 - transmittance);
}
Ray Marching a Volume
14 / 77
vec4 raymarch(vec3 samplePos, vec3 marchDir, int stepCount, float Δs) {
float transmittance = 1.0;
float illumination = 0.0;
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;
transmittance *= exp(-density(samplePos) * σextinction * Δs);
...� illumination += transmittance * currentLight * Δs;
}
return vec4(vec3(illumination), 1.0 - transmittance);
}
Ray Marching a Volume
15 / 77
vec4 raymarch(vec3 samplePos, vec3 marchDir, int stepCount, float Δs) {
float transmittance = 1.0;
float illumination = 0.0;
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;
transmittance *= exp(-density(samplePos) * σextinction * Δs);
...� illumination += transmittance * currentLight * Δs;
}
return vec4(vec3(illumination), 1.0 - transmittance);
}
Ray Marching a Volume
16 / 77
vec4 raymarch(vec3 samplePos, vec3 marchDir, int stepCount, float Δs) {
float transmittance = 1.0;
float illumination = 0.0;
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;
transmittance *= exp(-density(samplePos) * σextinction * Δs);
...� illumination += transmittance * currentLight * Δs;
}
return vec4(vec3(illumination), 1.0 - transmittance);
}
Ray Marching a Volume
17 / 77
transmittance *= exp(-density(samplePos) * σextinction * Δs);
Ray Marching a Volume
18 / 77
transmittance *= exp(-density(samplePos) * σextinction * Δs);
Current density
Ray Marching a Volume
19 / 77
transmittance *= exp(-density(samplePos) * σextinction * Δs);
Distance
travelled
The Extinction Coefficient
20 / 77
transmittance *= exp(-density(samplePos) * σextinction * Δs);
Material / medium�coefficient
The Extinction Coefficient
21 / 77
transmittance *= exp(-density(samplePos) * σextinction * Δs);
σextinction = σabsorption + σscattering
(unit-1)
The Extinction Coefficient
22 / 77
transmittance *= exp(-density(samplePos) * σextinction * Δs);
How much light is absorbed?
σextinction = σabsorption + σscattering
(unit-1)
The Extinction Coefficient
23 / 77
transmittance *= exp(-density(samplePos) * σextinction * Δs);
How much light is scattered?
σextinction = σabsorption + σscattering
(unit-1)
The Extinction Coefficient
24 / 77
transmittance *= exp(-density(samplePos) * σextinction * Δs);
For a dense cloud the extinction coefficient could be about 0.04 m-1 = 40 km-1. [ref]
σextinction = σabsorption + σscattering
(unit-1)
Beer’s Law
25 / 77
transmittance *= exp(-density(samplePos) * σextinction * Δs);
Beer’s Law
26 / 77
transmittance *= exp(-density(samplePos) * σextinction * Δs);
Beer’s Law
Beer’s Law
27 / 77
transmittance *= exp(-density(samplePos) * σextinction * Δs);
Beer’s Law
Beer’s Law
28 / 77
transmittance *= exp(-density(samplePos) * σextinction * Δs);
Beer’s Law
Given some light extinction value for the medium, �how much light gets transmitted over a distance (step)?
Ray Marching a Volume
29 / 77
vec4 raymarch(vec3 samplePos, vec3 marchDir, int stepCount, float Δs) {
float transmittance = 1.0;
float illumination = 0.0;
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;
transmittance *= exp(-density(samplePos) * σextinction * Δs);
...� illumination += transmittance * currentLight * Δs;
}
return vec4(vec3(illumination), 1.0 - transmittance);
}
Ray Marching a Volume
30 / 77
illumination += transmittance * currentLight * Δs;
Ray Marching a Volume
31 / 77
illumination += transmittance * currentLight * Δs;
Ray Marching a Volume
32 / 77
illumination += transmittance * currentLight * Δs;
Δs
Δs
Ray Marching a Volume
33 / 77
illumination += transmittance * currentLight * Δs;
Δs
Ray Marching a Volume
34 / 77
vec4 raymarch(vec3 samplePos, vec3 marchDir, int stepCount, float Δs) {
float transmittance = 1.0;
float illumination = 0.0;
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;
transmittance *= exp(-density(samplePos) * σextinction * Δs);
...� illumination += transmittance * currentLight * Δs;
}
return vec4(vec3(illumination), 1.0 - transmittance);
}
Ray Marching a Volume
35 / 77
vec4 raymarch(vec3 samplePos, vec3 marchDir, int stepCount, float Δs) {
float transmittance = 1.0;
float illumination = 0.0;
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;
transmittance *= exp(-density(samplePos) * σextinction * Δs);
...� illumination += transmittance * currentLight * Δs;
}
return vec4(vec3(illumination), 1.0 - transmittance);
}
In-Scattering
36 / 77
Out-Scattering
37 / 77
Ambient Light
38 / 77
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;�� float currentDensity = density(samplePosition);
transmittance *= exp(-currentDensity * σextinction * Δs);
float inScattering = ambientLight;
float outScattering = σscattering * currentDensity;
vec3 currentLight = inScattering * outScattering;� illumination += transmittance * currentLight * Δs;
}
Ambient Light
39 / 77
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;�� float currentDensity = density(samplePosition);
transmittance *= exp(-currentDensity * σextinction * Δs);
float inScattering = ambientLight;
float outScattering = σscattering * currentDensity;
vec3 currentLight = inScattering * outScattering;� illumination += transmittance * currentLight * Δs;
}
Ambient Light
40 / 77
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;�� float currentDensity = density(samplePosition);
transmittance *= exp(-currentDensity * σextinction * Δs);
float inScattering = ambientLight;
float outScattering = σscattering * currentDensity;
vec3 currentLight = inScattering * outScattering;� illumination += transmittance * currentLight * Δs;
}
Ambient Light
41 / 77
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;�� float currentDensity = density(samplePosition);
transmittance *= exp(-currentDensity * σextinction * Δs);
float inScattering = ambientLight;
float outScattering = σscattering * currentDensity;
vec3 currentLight = inScattering * outScattering;� illumination += transmittance * currentLight * Δs;
}
Ambient Light
42 / 77
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;�� float currentDensity = density(samplePosition);
transmittance *= exp(-currentDensity * σextinction * Δs);
float inScattering = ambientLight;
float outScattering = σscattering * currentDensity;
vec3 currentLight = inScattering * outScattering;� illumination += transmittance * currentLight * Δs;
}
Ambient Light
43 / 77
Direct Light
44 / 77
Direct Light
45 / 77
Direct Light
46 / 77
Phase Function
47 / 77
Tells how much light coming at an incident direction scatters towards another direction at angle θ.
θ
Phase Function
48 / 77
Henyey-Greenstein phase function
θ
θ
Phase Function
49 / 77
Henyey-Greenstein phase function
Schlick’s approximation
θ
θ
θ
θ
Phase Function
50 / 77
Henyey-Greenstein phase function
Schlick’s approximation
θ
θ
θ
θ
(AN)ISOTROPY PARAMETER
Direct Light
52 / 77
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;�� float currentDensity = density(samplePosition);
transmittance *= exp(-currentDensity * σextinction * Δs);
float inScattering = ambientLight + directLight * phase(v, -l);
float outScattering = σscattering * currentDensity;
vec3 currentLight = inScattering * outScattering;� illumination += transmittance * currentLight * Δs;
}
Direct Light
53 / 77
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;�� float currentDensity = density(samplePosition);
transmittance *= exp(-currentDensity * σextinction * Δs);
float inScattering = ambientLight + directLight * phase(v, -l);
float outScattering = σscattering * currentDensity;
vec3 currentLight = inScattering * outScattering;� illumination += transmittance * currentLight * Δs;
}
Direct Light
54 / 77
Direct Light
55 / 77
Opacity Shadow Map
56 / 77
Opacity Shadow Map
57 / 77
Opacity Shadow Map
58 / 77
…
Opacity Shadow Map
59 / 77
Opacity Shadow Map
60 / 77
vec3 pos = projectPointToPlane(fragPos,
vec3(0.0, 0.0, -zmin), // Origin of the plane in camera space
vec3(0.0, 0.0, -1.0) // Camera plane normal in camera space
);
vec3 localPos = (inverse(modelViewMatrix) * vec4(pos, 1.0)).xyz;
float Δz = (zmax - zmin) / float(sliceCount);
...
for (int i = 0; i < sliceCount; i++) {
currentDensity = density(localPos - float(i) * l * Δz);
totalDesnity += currentDensity;
layer[i] = exp(-totalDesnity * Δz * σextinction);
}
Opacity Shadow Map
61 / 77
vec3 pos = projectPointToPlane(fragPos,
vec3(0.0, 0.0, -zmin), // Origin of the plane in camera space
vec3(0.0, 0.0, -1.0) // Camera plane normal in camera space
);
vec3 localPos = (inverse(modelViewMatrix) * vec4(pos, 1.0)).xyz;
float Δz = (zmax - zmin) / float(sliceCount);
...
for (int i = 0; i < sliceCount; i++) {
currentDensity = density(localPos - float(i) * l * Δz);
totalDesnity += currentDensity;
layer[i] = exp(-totalDesnity * Δz * σextinction);
}
Opacity Shadow Map
62 / 77
Opacity Shadow Map
63 / 77
vec3 pos = projectPointToPlane(fragPos,
vec3(0.0, 0.0, -zmin), // Origin of the plane in camera space
vec3(0.0, 0.0, -1.0) // Camera plane normal in camera space
);
vec3 localPos = (inverse(modelViewMatrix) * vec4(pos, 1.0)).xyz;
float Δz = (zmax - zmin) / float(sliceCount);
...
for (int i = 0; i < sliceCount; i++) {
currentDensity = density(localPos - float(i) * l * Δz);
totalDesnity += currentDensity;
layer[i] = exp(-totalDesnity * Δz * σextinction);
}
Opacity Shadow Map
64 / 77
vec3 pos = projectPointToPlane(fragPos,
vec3(0.0, 0.0, -zmin), // Origin of the plane in camera space
vec3(0.0, 0.0, -1.0) // Camera plane normal in camera space
);
vec3 localPos = (inverse(modelViewMatrix) * vec4(pos, 1.0)).xyz;
float Δz = (zmax - zmin) / float(sliceCount);
...
for (int i = 0; i < sliceCount; i++) {
currentDensity = density(localPos - float(i) * l * Δz);
totalDesnity += currentDensity;
layer[i] = exp(-totalDesnity * Δz * σextinction);
}
Opacity Shadow Map
65 / 77
vec3 pos = projectPointToPlane(fragPos,
vec3(0.0, 0.0, -zmin), // Origin of the plane in camera space
vec3(0.0, 0.0, -1.0) // Camera plane normal in camera space
);
vec3 localPos = (inverse(modelViewMatrix) * vec4(pos, 1.0)).xyz;
float Δz = (zmax - zmin) / float(sliceCount);
...
for (int i = 0; i < sliceCount; i++) {
currentDensity = density(localPos - float(i) * l * Δz);
totalDesnity += currentDensity;
layer[i] = exp(-totalDesnity * Δz * σextinction);
}
Opacity Shadow Map
66 / 77
vec3 pos = projectPointToPlane(fragPos,
vec3(0.0, 0.0, -zmin), // Origin of the plane in camera space
vec3(0.0, 0.0, -1.0) // Camera plane normal in camera space
);
vec3 localPos = (inverse(modelViewMatrix) * vec4(pos, 1.0)).xyz;
float Δz = (zmax - zmin) / float(sliceCount);
...
for (int i = 0; i < sliceCount; i++) {
currentDensity = density(localPos - float(i) * l * Δz);
totalDesnity += currentDensity;
layer[i] = exp(-totalDesnity * Δz * σextinction);
}
Opacity Shadow Map
67 / 77
vec3 pos = projectPointToPlane(fragPos,
vec3(0.0, 0.0, -zmin), // Origin of the plane in camera space
vec3(0.0, 0.0, -1.0) // Camera plane normal in camera space
);
vec3 localPos = (inverse(modelViewMatrix) * vec4(pos, 1.0)).xyz;
float Δz = (zmax - zmin) / float(sliceCount);
...
for (int i = 0; i < sliceCount; i++) {
currentDensity = density(localPos - float(i) * l * Δz);
totalDesnity += currentDensity;
layer[i] = exp(-totalDesnity * Δz * σextinction);
}
Opacity Shadow Map
68 / 77
layout(location = 0) out vec4 outColor0;
...
layout(location = 7) out vec4 outColor7;
void main() {
float layer[8] = float[](0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
...
outColor0 = vec4(layer[0]);
...
outColor7 = vec4(layer[7]);
}
69 / 77
Direct Light
70 / 77
for (int i = 0; i < stepCount; i++) {
samplePos += marchDir * Δs;�� float currentDensity = density(samplePosition);
transmittance *= exp(-currentDensity * σextinction * Δs);
float inScattering = ambientLight + directLight * phase(v, -l) *� sampleInScattering(samplePos);
float outScattering = σscattering * currentDensity;
vec3 currentLight = inScattering * outScattering;� illumination += transmittance * currentLight * Δs;
}
Volumetric Rendering
71 / 77
72 / 77
DEMO
References
73 / 77
Presentation Slides
Volumetric Lighting for Many Lights in Lords of the Fallen (2014) – Benjamin Glatzel
Volume and Participating Media (2009) – Yung-Yu Chuang
Volumetric Fog (2014) – Bartlomiej Wronski, Ubisoft Montreal
Learning Materials
Real-Time Volumetric Rendering (2013) – Benoit Mayaux
Volume Rendering for Developers: Foundations – Scratchapixel
Physically-Based Rendering: 11. Volume Scattering (2018) – Matt Pharr et al
Physically-Based Rendering: 15 Light Transport II: Volume Rendering (2018) – Matt Pharr � et al
References
74 / 77
Theses
Volumetric Fog Rendering (2019) – Siim Raudsepp
Volumetric Clouds Rendering (2019) – Jaagup Kuhi
Volumetric Atmospheric Effects Rendering (2018) – Dean Babić
Real-Time Volumetric Shadows (2011) – Alexandru–Teodor Voicu
Projects
References
75 / 77
Papers
Physically Based Sky, Atmosphere and Cloud Rendering in Frostbite (2016) – Sébastien � Hillaire
�Monte Carlo Methods for Volumetric Light Transport Simulation (2018) – Jan Novák et al�
Assessment of cloud optical parameters in the solar region: Retrievals from airborne measurements of scattering phase functions (2019) – Olivier Jourdan et al
76 / 77
Thank You
Bugs
(or Art?)
77 / 77