diff --git a/docs/dup/intro.html b/docs/dup/intro.html index 4f8f04909a..b133a6004d 100644 --- a/docs/dup/intro.html +++ b/docs/dup/intro.html @@ -181,7 +181,7 @@ important for matc (material compiler).

} dependencies { - implementation 'com.google.android.filament:filament-android:1.66.2' + implementation 'com.google.android.filament:filament-android:1.67.1' }

Here are all the libraries available in the group com.google.android.filament:

@@ -196,7 +196,7 @@ dependencies {

iOS

iOS projects can use CocoaPods to install the latest release:

-
pod 'Filament', '~> 1.66.2'
+
pod 'Filament', '~> 1.67.1'
 

Documentation

-The term \( \lambda(l) \) in equations \( \ref{spotAbsorber} \) and \( \ref{spotReflector} \) is the spot's angle attenuation factor described in equation - \( \ref{spotAngleAtt} \) below. +The ⟨L:1430⟩term \( \lambda(l) \) in equations \( \ref{spotAbsorber} \) and \( \ref{spotReflector} \) is the spot's angle attenuation factor described in equation + \( ⟨L:1431⟩\ref{spotAngleAtt} \) below.

-$$\begin{equation}\label{spotAngleAtt} -\lambda(l) = \frac{l \cdot spotDirection - cos\theta_{outer}}{cos\theta_{inner} - cos\theta_{outer}} -\end{equation}$$ +$$\begin{equation}\label{spotAngleAtt}⟨L:1433⟩ +\lambda(l) ⟨L:1434⟩= \frac{l \cdot spotDirection - cos\theta_{outer}}{cos\theta_{inner} - cos\theta_{outer}} +\end{equation}$$⟨L:1435⟩

-   

Attenuation function

+
   

⟨L:1437⟩Attenuation function

-

A proper evaluation of the inverse square law attenuation factor is mandatory for physically based punctual lights. The simple mathematical formulation is unfortunately impractical for implementation purposes:

+

A ⟨L:1439⟩proper evaluation of the inverse square law attenuation factor is mandatory for physically based punctual lights. The simple mathematical formulation is unfortunately impractical for implementation purposes:

  1. The division by the squared distance can lead to divides by 0 when objects intersect or “touch” light sources. @@ -1636,21 +1654,21 @@ $$\begin{equation}\label{spotAngleAtt}
  2. The influence sphere of each light is infinite (\( \frac{I}{d^2} \) is asymptotic, it never reaches 0) which means that to correctly shade a pixel we need to evaluate every light in the world.

-The first issue can be solved easily by setting the assumption that punctual lights are not truly punctual but instead small area lights. To do this we can simply treat punctual lights as spheres of 1 cm radius, as show in equation \(\ref{finitePunctualLight}\). +The ⟨L:1446⟩first issue can be solved easily by setting the assumption that punctual lights are not truly punctual but instead small area lights. To do this we can simply treat punctual lights as spheres of 1 cm radius, as show in equation \(\ref{finitePunctualLight}\).

-$$\begin{equation}\label{finitePunctualLight} -E = \frac{I}{max(d^2, {0.01}^2)} -\end{equation}$$ +$$\begin{equation}\label{finitePunctualLight}⟨L:1448⟩ +E ⟨L:1449⟩= \frac{I}{max(d^2, {0.01}^2)} +\end{equation}$$⟨L:1450⟩

-We can solve the second issue by introducing an influence radius for each light. There are several advantages to this solution. Tools can quickly show artists what parts of the world will be influenced by every light (the tool just needs to draw a sphere centered on each light). The rendering engine can cull lights more aggressively using this extra piece of information and artists/developers can assist the engine by manually tweaking the influence radius of a light. +We ⟨L:1452⟩can solve the second issue by introducing an influence radius for each light. There are several advantages to this solution. Tools can quickly show artists what parts of the world will be influenced by every light (the tool just needs to draw a sphere centered on each light). The rendering engine can cull lights more aggressively using this extra piece of information and artists/developers can assist the engine by manually tweaking the influence radius of a light.

-Mathematically, the illuminance of a light should smoothly reach zero at the limit defined by the influence radius. [Karis13b] proposes to window the inverse square function in such a way that the majority of the light's influence remains unaffected. The proposed windowing is described in equation \(\ref{attenuationWindowing}\), where \(r\) is the light's radius of influence. +Mathematically, ⟨L:1454⟩the illuminance of a light should smoothly reach zero at the limit defined by the influence radius. [Karis13b] proposes to window the inverse square function in such a way that the majority of the light's influence remains unaffected. The proposed windowing is described in equation \(\ref{attenuationWindowing}\), where \(r\) is the light's radius of influence.

-$$\begin{equation}\label{attenuationWindowing} -E = \frac{I}{max(d^2, {0.01}^2)} \left< 1 - \frac{d^4}{r^4} \right>^2 -\end{equation}$$ +$$\begin{equation}\label{attenuationWindowing}⟨L:1456⟩ +E ⟨L:1457⟩= \frac{I}{max(d^2, {0.01}^2)} \left< 1 - \frac{d^4}{r^4} \right>^2 +\end{equation}$$⟨L:1458⟩

-Listing 21 demonstrates how to implement physically based punctual lights in GLSL. Note that the light intensity used in this piece of code is the luminous intensity \(I\) in \(cd\), converted from the luminous power CPU-side. This snippet is not optimized and some of the computations can be offloaded to the CPU (for instance the square of the light's inverse falloff radius, or the spot scale and angle). +Listing ⟨L:1460⟩[glslPunctualLight] demonstrates how to implement physically based punctual lights in GLSL. Note that the light intensity used in this piece of code is the luminous intensity \(I\) in \(cd\), converted from the luminous power CPU-side. This snippet is not optimized and some of the computations can be offloaded to the CPU (for instance the square of the light's inverse falloff radius, or the spot scale and angle).

 
float getSquareFalloffAttenuation(vec3 posToLight, float lightInvRadius) {
     float distanceSquare = dot(posToLight, posToLight);
     float factor = distanceSquare * lightInvRadius * lightInvRadius;
@@ -1682,9 +1700,9 @@ E = \frac{I}{max(d^2, {0.01}^2)} \left< 1 - \frac{d^4}{r^4} \right>^2
     vec3 luminance = (BSDF(v, l) * lightIntensity * attenuation * NoL) * lightColor;
     return luminance;
 }
Listing 21: Implementation of punctual lights in GLSL
-   

Photometric lights

+
   

⟨L:1497⟩Photometric lights

-

Punctual lights are an extremely practical and efficient way to light a scene but do not give artists enough control over the light distribution. The field of architectural lighting design concerns itself with designing lighting systems to serve humans needs by taking into account:

+

Punctual ⟨L:1499⟩lights are an extremely practical and efficient way to light a scene but do not give artists enough control over the light distribution. The field of architectural lighting design concerns itself with designing lighting systems to serve humans needs by taking into account:

  • The amount of light provided @@ -1693,37 +1711,37 @@ E = \frac{I}{max(d^2, {0.01}^2)} \left< 1 - \frac{d^4}{r^4} \right>^2
  • The distribution of light within the space

-The lighting system we have described so far can easily address the first two points but we need a way to define the distribution of light within the space. Light distribution is especially important for indoor scenes or for some types of outdoor scenes or even road lighting. Figure 41 shows scenes where the light distribution is controlled by the artist. This type of distribution control is widely used when putting objects on display (museums, stores or galleries for instance). +The ⟨L:1505⟩lighting system we have described so far can easily address the first two points but we need a way to define the distribution of light within the space. Light distribution is especially important for indoor scenes or for some types of outdoor scenes or even road lighting. Figure ? shows scenes where the light distribution is controlled by the artist. This type of distribution control is widely used when putting objects on display (museums, stores or galleries for instance).

-

 
Figure 41: Controlling the distribution of a point light
+
Figure ⟨L:1507⟩[lightDistributionTest]: Controlling the distribution of a point light

-Photometric lights use a photometric profile to describe their intensity distribution. There are two commonly used formats, IES (Illuminating Engineering Society) and EULUMDAT (European Lumen Data format) but we will focus on the former. IES profiles are supported by many tools and engines, such as Unreal Engine 4, Frostbite, Renderman, Maya and Killzone. In addition, IES light profiles are commonly made available by bulbs and luminaires manufacturers (Philips offers an extensive array of IES files for download for instance). Photometric profiles are particularly useful when they measure a luminaire or light fixture, in which the light source is partially covered. The luminaire will block the light emitted in certain directions, thus shaping the light distribution. +Photometric ⟨L:1509⟩lights use a photometric profile to describe their intensity distribution. There are two commonly used formats, IES (Illuminating Engineering Society) and EULUMDAT (European Lumen Data format) but we will focus on the former. IES profiles are supported by many tools and engines, such as Unreal Engine 4, Frostbite, Renderman, Maya and Killzone. In addition, IES light profiles are commonly made available by bulbs and luminaires manufacturers (Philips offers an extensive array of IES files for download for instance). Photometric profiles are particularly useful when they measure a luminaire or light fixture, in which the light source is partially covered. The luminaire will block the light emitted in certain directions, thus shaping the light distribution.

-

Example of a real world luminaires that can be described by photometric profiles
+
Example ⟨L:1511⟩of a real world luminaires that can be described by photometric profiles

-An IES profile stores luminous intensity for various angles on a sphere around the measured light source. This spherical coordinate system is usually referred to as the photometric web, which can be visualized using specialized tools such as IESviewer. Figure 42 below shows the photometric web of the XArrow IES profile provided by Pixar for use with Renderman. This picture also shows a rendering in 3D space of the XArrow IES profile by our tool lightgen. +An ⟨L:1513⟩IES profile stores luminous intensity for various angles on a sphere around the measured light source. This spherical coordinate system is usually referred to as the photometric web, which can be visualized using specialized tools such as IESviewer. Figure ? below shows the photometric web of the XArrow IES profile provided by Pixar for use with Renderman. This picture also shows a rendering in 3D space of the XArrow IES profile by our tool lightgen.

-

 
Figure 42: The XArrow IES profile rendered as a photometric web and as a point light in 3D space
+
Figure ⟨L:1515⟩[xarrow]: The XArrow IES profile rendered as a photometric web and as a point light in 3D space

-The IES format is poorly documented and it is not uncommon to find syntax variations between files found on the Internet. The best resource to understand IES profile is Ian Ashdown's “Parsing the IESNA LM-63 photometric data file” document [Ashdown98]. Succinctly, an IES profiles stores luminous intensities in candela at various angles around the light source. For each measured horizontal angle, a series of luminous intensities at different vertical angles is provided. It is however fairly common for measured light sources to be horizontally symmetrical. The XArrow profile shown above is a good example: intensities vary with vertical angles (vertical axis) but are symmetrical on the horizontal axis. The range of vertical angles in an IES profile is 0 to 180° and the range of horizontal angles is 0 to 360°. +The ⟨L:1517⟩IES format is poorly documented and it is not uncommon to find syntax variations between files found on the Internet. The best resource to understand IES profile is Ian Ashdown's “Parsing the IESNA LM-63 photometric data file” document [Ashdown98]. Succinctly, an IES profiles stores luminous intensities in candela at various angles around the light source. For each measured horizontal angle, a series of luminous intensities at different vertical angles is provided. It is however fairly common for measured light sources to be horizontally symmetrical. The XArrow profile shown above is a good example: intensities vary with vertical angles (vertical axis) but are symmetrical on the horizontal axis. The range of vertical angles in an IES profile is 0 to 180° and the range of horizontal angles is 0 to 360°.

-Figure 43 shows the series of IES profiles provided by Pixar for Renderman, rendered using our lightgen tool. +Figure ⟨L:1519⟩[lightenSamples] shows the series of IES profiles provided by Pixar for Renderman, rendered using our lightgen tool.

-

 
Figure 43: Series of IES light profiles rendered with lightgen
+
Figure ⟨L:1521⟩[lightenSamples]: Series of IES light profiles rendered with lightgen

-IES profiles can be applied directly to any punctual light, point or spot. To do so, we must first process the IES profile and generate a photometric profile as a texture. For performance considerations, the photometric profile we generate is a 1D texture that represents the average luminous intensity for all horizontal angles at a specific vertical angle (i.e., each pixel represents a vertical angle). To truly represent a photometric light, we should use a 2D texture but since most lights are fully, or mostly, symmetrical on the horizontal plane, we can accept this approximation. The values stored in the texture are normalized by the inverse maximum intensity defined in the IES profile. This allows us to easily store the texture in any float format or, at the cost of a bit of precision, in a luminance 8-bit texture (grayscale PNG for instance). Storing normalized values also allows us to treat photometric profiles as a mask: +IES ⟨L:1523⟩profiles can be applied directly to any punctual light, point or spot. To do so, we must first process the IES profile and generate a photometric profile as a texture. For performance considerations, the photometric profile we generate is a 1D texture that represents the average luminous intensity for all horizontal angles at a specific vertical angle (i.e., each pixel represents a vertical angle). To truly represent a photometric light, we should use a 2D texture but since most lights are fully, or mostly, symmetrical on the horizontal plane, we can accept this approximation. The values stored in the texture are normalized by the inverse maximum intensity defined in the IES profile. This allows us to easily store the texture in any float format or, at the cost of a bit of precision, in a luminance 8-bit texture (grayscale PNG for instance). Storing normalized values also allows us to treat photometric profiles as a mask:

-

Photometric profile as a mask

The luminous intensity is defined by the artist by setting the luminous power of the light, as with any other punctual light. The artist defined intensity is divided by the intensity of the light computed from the IES profile. IES profiles contain a luminous intensity but it is only valid for a bare light bulb whereas the measured intensity values take into account the light fixture. To measure the intensity of the luminaire, instead of the bulb, we perform a Monte-Carlo integration of the unit sphere using the intensities from the profile4. -

Photometric profile

The luminous intensity comes from the profile itself. All the values sampled from the 1D texture are simply multiplied by the maximum intensity. We also provide a multiplier for convenience. -

The photometric profile can be applied at rendering time as a simple attenuation. The luminance equation \( \ref{photometricLightEvaluation} \) describes the photometric point light evaluation function. +
 Photometric ⟨L:1525⟩profile as a mask

⟨L:1526⟩The luminous intensity is defined by the artist by setting the luminous power of the light, as with any other punctual light. The artist defined intensity is divided by the intensity of the light computed from the IES profile. IES profiles contain a luminous intensity but it is only valid for a bare light bulb whereas the measured intensity values take into account the light fixture. To measure the intensity of the luminaire, instead of the bulb, we perform a Monte-Carlo integration of the unit sphere using the intensities from the profile4. +

 Photometric ⟨L:1528⟩profile

⟨L:1529⟩The luminous intensity comes from the profile itself. All the values sampled from the 1D texture are simply multiplied by the maximum intensity. We also provide a multiplier for convenience. +

The ⟨L:1531⟩photometric profile can be applied at rendering time as a simple attenuation. The luminance equation \( \ref{photometricLightEvaluation} \) describes the photometric point light evaluation function.

-$$\begin{equation}\label{photometricLightEvaluation} -L_{out} = f(v,l) \frac{I}{d^2} \left< \NoL \right> \Psi(l) -\end{equation}$$ +$$\begin{equation}\label{photometricLightEvaluation}⟨L:1533⟩ +L_{out} ⟨L:1534⟩= f(v,l) \frac{I}{d^2} \left< \NoL \right> \Psi(l) +\end{equation}$$⟨L:1535⟩

-The term \( \Psi(l) \) is the photometric attenuation function. It depends on the light vector, but also on the direction of the light. Spot lights already possess a direction vector but we need to introduce one for photometric point lights as well. +The ⟨L:1537⟩term \( \Psi(l) \) is the photometric attenuation function. It depends on the light vector, but also on the direction of the light. Spot lights already possess a direction vector but we need to introduce one for photometric point lights as well.

-The photometric attenuation function can be easily implemented in GLSL by adding a new attenuation factor to the implementation of punctual lights (listing 21). The modified implementation is show in listing 22. +The ⟨L:1539⟩photometric attenuation function can be easily implemented in GLSL by adding a new attenuation factor to the implementation of punctual lights (listing 21). The modified implementation is show in listing 22.

 
float getPhotometricAttenuation(vec3 posToLight, vec3 lightDir) {
     float cosTheta = dot(-posToLight, lightDir);
     float angle = acos(cosTheta) * (1.0 / PI);
@@ -1744,7 +1762,7 @@ The photometric attenuation function can be easily implemented in GLSL by adding
     return luminance;
 }
Listing 22: Implementation of attenuation from photometric profiles in GLSL

-

The light intensity is computed CPU-side (listing 23) and depends on whether the photometric profile is used as a mask.

+

The ⟨L:1564⟩light intensity is computed CPU-side (listing 23) and depends on whether the photometric profile is used as a mask.

 
float multiplier;
 // Photometric profile used as a mask
 if (photometricLight.isMasked()) {
@@ -1764,113 +1782,113 @@ The photometric attenuation function can be easily implemented in GLSL by adding
 
 4 The XArrow profile declares a luminous intensity of 1,750 lm but a Monte-Carlo integration shows an intensity of only 350 lm.

-   

Area lights

+
   

⟨L:1587⟩Area lights

-

[TODO]

+

[TODO]⟨L:1589⟩

-   

Lights parameterization

+
   

⟨L:1591⟩Lights parameterization

-

Similarly to the parameterization of the standard material model, our goal is to make lights parameterization intuitive and easy to use for artists and developers alike. In that spirit, we decided to separate the light color (or hue) from the light intensity. A light color will therefore be defined as a linear RGB color (or sRGB in the tools UI for convenience).

+

Similarly ⟨L:1593⟩to the parameterization of the standard material model, our goal is to make lights parameterization intuitive and easy to use for artists and developers alike. In that spirit, we decided to separate the light color (or hue) from the light intensity. A light color will therefore be defined as a linear RGB color (or sRGB in the tools UI for convenience).

-The full list of light parameters is presented in table 13. +The ⟨L:1595⟩full list of light parameters is presented in table 13.

-  - - - - - - - - - - - - + 
Parameter Definition
Type Directional, point, spot or area
Direction Used for directional lights, spot lights, photometric point lights, and linear and tubular area lights (orientation)
Color The color of emitted light, as a linear RGB color. Can be specified as an sRGB color or a color temperature in the tools
Intensity The light's brightness. The unit depends on the type of light
Falloff radius Maximum distance of influence
Inner angle Angle of the inner cone for spot lights, in degrees
Outer angle Angle of the outer cone for spot lights, in degrees
Length Length of the area light, used to create linear or tubular lights
Radius Radius of the area light, used to create spherical or tubular lights
Photometric profile Texture representing a photometric light profile, works only for punctual lights
Masked profile Boolean indicating whether the IES profile is used as a mask or not. When used as a mask, the light's brightness will be multiplied by the ratio between the user specified intensity and the integrated IES profile intensity. When not used as a mask, the user specified intensity is ignored but the IES multiplier is used instead
Photometric multiplier Brightness multiplier for photometric lights (if IES as mask is turned off)
+ + + + + + + + + + + +
Parameter ⟨L:1598⟩ Definition
Type ⟨L:1600⟩ Directional, point, spot or area
Direction ⟨L:1601⟩ Used for directional lights, spot lights, photometric point lights, and linear and tubular area lights (orientation)
Color ⟨L:1602⟩ The color of emitted light, as a linear RGB color. Can be specified as an sRGB color or a color temperature in the tools
Intensity ⟨L:1603⟩ The light's brightness. The unit depends on the type of light
Falloff ⟨L:1604⟩radius Maximum distance of influence
Inner ⟨L:1605⟩angle Angle of the inner cone for spot lights, in degrees
Outer ⟨L:1606⟩angle Angle of the outer cone for spot lights, in degrees
Length ⟨L:1607⟩ Length of the area light, used to create linear or tubular lights
Radius ⟨L:1608⟩ Radius of the area light, used to create spherical or tubular lights
Photometric ⟨L:1609⟩profile Texture representing a photometric light profile, works only for punctual lights
Masked ⟨L:1610⟩profile Boolean indicating whether the IES profile is used as a mask or not. When used as a mask, the light's brightness will be multiplied by the ratio between the user specified intensity and the integrated IES profile intensity. When not used as a mask, the user specified intensity is ignored but the IES multiplier is used instead
Photometric ⟨L:1611⟩multiplier Brightness multiplier for photometric lights (if IES as mask is turned off)
Table 13: Light types parameters

-Note: to simplify the implementation, all luminous powers will converted to luminous intensities (\(cd\)) before being sent to the shader. The conversion is light dependent and is explained in the previous sections. +Note: ⟨L:1614⟩to simplify the implementation, all luminous powers will converted to luminous intensities (\(cd\)) before being sent to the shader. The conversion is light dependent and is explained in the previous sections.

-Note: the light type can be inferred from other parameters (e.g. a point light has a length, radius, inner angle and outer angle of 0). +Note: ⟨L:1616⟩the light type can be inferred from other parameters (e.g. a point light has a length, radius, inner angle and outer angle of 0).

-   

Color temperature

+
   

⟨L:1618⟩Color temperature

-

However, real-world artificial lights are often defined by their color temperature, measured in Kelvin (K). The color temperature of a light source is the temperature of an ideal black-body radiator that radiates light of comparable hue to that of the light source. For convenience, the tools should allow the artist to specify the hue of a light source as a color temperature (a meaningful range is 1,000 K to 12,500 K).

+

However, ⟨L:1620⟩real-world artificial lights are often defined by their color temperature, measured in Kelvin (K). The color temperature of a light source is the temperature of an ideal black-body radiator that radiates light of comparable hue to that of the light source. For convenience, the tools should allow the artist to specify the hue of a light source as a color temperature (a meaningful range is 1,000 K to 12,500 K).

-To compute RGB values from a temperature, we can use the Planckian locus, shown in figure 44. This locus is the path that the color of an incandescent black body takes in a chromaticity space as the body's temperature changes. +To ⟨L:1622⟩compute RGB values from a temperature, we can use the Planckian locus, shown in figure ?. This locus is the path that the color of an incandescent black body takes in a chromaticity space as the body's temperature changes.

-

 
Figure 44: The Planckian locus visualized on a CIE 1931 chromaticity diagram (source: Wikipedia)
+
Figure ⟨L:1624⟩[planckianLocus]: The Planckian locus visualized on a CIE 1931 chromaticity diagram (source: Wikipedia)

-The easiest way to compute RGB values from this locus is to use the formula described in [Krystek85]. Krystek's algorithm (equation \(\ref{krystek}\)) works in the CIE 1960 (UCS) space, using the following formula where \(T\) is the desired temperature, and \(u\) and \(v\) the coordinates in UCS. +The ⟨L:1626⟩easiest way to compute RGB values from this locus is to use the formula described in [Krystek85]. Krystek's algorithm (equation \(\ref{krystek}\)) works in the CIE 1960 (UCS) space, using the following formula where \(T\) is the desired temperature, and \(u\) and \(v\) the coordinates in UCS.

-$$\begin{equation}\label{krystek} -u(T) = \frac{0.860117757 + 1.54118254 \times 10^{-4}T + 1.28641212 \times 10^{-7}T^2}{1 + 8.42420235 \times 10^{-4}T + 7.08145163 \times 10^{-7}T^2} \\ -v(T) = \frac{0.317398726 + 4.22806245 - \times 10^{-5}T + 4.20481691 \times 10^{-8}T^2}{1 - 2.89741816 - \times 10^{-5}T + 1.61456053 \times 10^{-7}T^2} -\end{equation}$$ +$$\begin{equation}\label{krystek}⟨L:1628⟩ +u(T) ⟨L:1629⟩= \frac{0.860117757 + 1.54118254 \times 10^{-4}T + 1.28641212 \times 10^{-7}T^2}{1 + 8.42420235 \times 10^{-4}T + 7.08145163 \times 10^{-7}T^2} \\ +v(T) ⟨L:1630⟩= \frac{0.317398726 + 4.22806245 + \times ⟨L:1631⟩10^{-5}T + 4.20481691 \times 10^{-8}T^2}{1 - 2.89741816 + \times ⟨L:1632⟩10^{-5}T + 1.61456053 \times 10^{-7}T^2} +\end{equation}$$⟨L:1633⟩

-This approximation is accurate to roughly \( 9 \times 10^{-5} \) in the range 1,000K to 15,000K. From the CIE 1960 space we can compute the coordinates in xyY space (CIES 1931), using the formula from equation \(\ref{cieToxyY}\). +This ⟨L:1635⟩approximation is accurate to roughly \( 9 \times 10^{-5} \) in the range 1,000K to 15,000K. From the CIE 1960 space we can compute the coordinates in xyY space (CIES 1931), using the formula from equation \(\ref{cieToxyY}\).

-$$\begin{equation}\label{cieToxyY} -x = \frac{3u}{2u - 8v + 4} \\ -y = \frac{2v}{2u - 8v + 4} -\end{equation}$$ +$$\begin{equation}\label{cieToxyY}⟨L:1637⟩ +x ⟨L:1638⟩= \frac{3u}{2u - 8v + 4} \\ +y ⟨L:1639⟩= \frac{2v}{2u - 8v + 4} +\end{equation}$$⟨L:1640⟩

-The formulas above are valid for black body color temperatures, and therefore correlated color temperatures of standard illuminants. If we wish to compute the precise chromaticity coordinates of standard CIE illuminants in the D series we can use equation \(\ref{seriesDtoxyY}\). +The ⟨L:1642⟩formulas above are valid for black body color temperatures, and therefore correlated color temperatures of standard illuminants. If we wish to compute the precise chromaticity coordinates of standard CIE illuminants in the D series we can use equation \(\ref{seriesDtoxyY}\).

-$$\begin{equation}\label{seriesDtoxyY} -x = \begin{cases} 0.244063 + 0.09911 \frac{10^3}{T} + 2.9678 \frac{10^6}{T^2} - 4.6070 \frac{10^9}{T^3} & 4,000K \le T \le 7,000K \\ -0.237040 + 0.24748 \frac{10^3}{T} + 1.9018 \frac{10^6}{T^2} - 2.0064 \frac{10^9}{T^3} & 7,000K \le T \le 25,000K \end{cases} \\ -y = -3x^2 + 2.87 x - 0.275 -\end{equation}$$ +$$\begin{equation}\label{seriesDtoxyY}⟨L:1644⟩ +x ⟨L:1645⟩= \begin{cases} 0.244063 + 0.09911 \frac{10^3}{T} + 2.9678 \frac{10^6}{T^2} - 4.6070 \frac{10^9}{T^3} & 4,000K \le T \le 7,000K \\ +0.237040 ⟨L:1646⟩+ 0.24748 \frac{10^3}{T} + 1.9018 \frac{10^6}{T^2} - 2.0064 \frac{10^9}{T^3} & 7,000K \le T \le 25,000K \end{cases} \\ +y ⟨L:1647⟩= -3x^2 + 2.87 x - 0.275 +\end{equation}$$⟨L:1648⟩

-From the xyY space, we can then convert to the CIE XYZ space (equation \(\ref{xyYtoXYZ}\)). +From ⟨L:1650⟩the xyY space, we can then convert to the CIE XYZ space (equation \(\ref{xyYtoXYZ}\)).

-$$\begin{equation}\label{xyYtoXYZ} -X = \frac{xY}{y} \\ -Z = \frac{(1 - x - y)Y}{y} -\end{equation}$$ +$$\begin{equation}\label{xyYtoXYZ}⟨L:1652⟩ +X ⟨L:1653⟩= \frac{xY}{y} \\ +Z ⟨L:1654⟩= \frac{(1 - x - y)Y}{y} +\end{equation}$$⟨L:1655⟩

-For our needs, we will fix \(Y = 1\). This allows us to convert from the XYZ space to linear RGB with a simple 3×3 matrix, as shown in equation \(\ref{XYZtoRGB}\). +For ⟨L:1657⟩our needs, we will fix \(Y = 1\). This allows us to convert from the XYZ space to linear RGB with a simple 3×3 matrix, as shown in equation \(\ref{XYZtoRGB}\).

-$$\begin{equation}\label{XYZtoRGB} -\left[ \begin{matrix} R \\ G \\ B \end{matrix} \right] = M^{-1} \left[ \begin{matrix} X \\ Y \\ Z \end{matrix} \right] -\end{equation}$$ +$$\begin{equation}\label{XYZtoRGB}⟨L:1659⟩ +\left[ ⟨L:1660⟩\begin{matrix} R \\ G \\ B \end{matrix} \right] = M^{-1} \left[ \begin{matrix} X \\ Y \\ Z \end{matrix} \right] +\end{equation}$$⟨L:1661⟩

-The transformation matrix M is calculated from the target RGB color space primaries. Equation \( \ref{XYZtoRGBValues} \) shows the conversion using the inverse matrix for the sRGB color space. +The ⟨L:1663⟩transformation matrix M is calculated from the target RGB color space primaries. Equation \( \ref{XYZtoRGBValues} \) shows the conversion using the inverse matrix for the sRGB color space.

-$$\begin{equation}\label{XYZtoRGBValues} -\left[ \begin{matrix} R \\ G \\ B \end{matrix} \right] = \left[ \begin{matrix} 3.2404542 & -1.5371385 & -0.4985314 \\ -0.9692660 & 1.8760108 & 0.0415560 \\ 0.0556434 & -0.2040259 & 1.0572252 \end{matrix} \right] \left[ \begin{matrix} X \\ Y \\ Z \end{matrix} \right] -\end{equation}$$ +$$\begin{equation}\label{XYZtoRGBValues}⟨L:1665⟩ +\left[ ⟨L:1666⟩\begin{matrix} R \\ G \\ B \end{matrix} \right] = \left[ \begin{matrix} 3.2404542 & -1.5371385 & -0.4985314 \\ -0.9692660 & 1.8760108 & 0.0415560 \\ 0.0556434 & -0.2040259 & 1.0572252 \end{matrix} \right] \left[ \begin{matrix} X \\ Y \\ Z \end{matrix} \right] +\end{equation}$$⟨L:1667⟩

-The result of these operations is a linear RGB triplet in the sRGB color space. Since we care about the chromaticity of the results, we must apply a normalization step to avoid clamping values greater than 1.0 and distort resulting colors: +The ⟨L:1669⟩result of these operations is a linear RGB triplet in the sRGB color space. Since we care about the chromaticity of the results, we must apply a normalization step to avoid clamping values greater than 1.0 and distort resulting colors:

-$$\begin{equation}\label{normalizedRGB} -\hat{C}_{linear} = \frac{C_{linear}}{max(C_{linear})} -\end{equation}$$ +$$\begin{equation}\label{normalizedRGB}⟨L:1671⟩ +\hat{C}_{linear} ⟨L:1672⟩= \frac{C_{linear}}{max(C_{linear})} +\end{equation}$$⟨L:1673⟩

-We must finally apply the sRGB opto-electronic conversion function (OECF, shown in equation \( \ref{OECFsRGB} \)) to obtain a displayable value (the value should remain linear if passed to the renderer for shading). +We ⟨L:1675⟩must finally apply the sRGB opto-electronic conversion function (OECF, shown in equation \( \ref{OECFsRGB} \)) to obtain a displayable value (the value should remain linear if passed to the renderer for shading).

-$$\begin{equation}\label{OECFsRGB} -C_{sRGB} = \begin{cases} 12.92 \times \hat{C}_{linear} & \hat{C}_{linear} \le 0.0031308 \\ -1.055 \times \hat{C}_{linear}^{\frac{1}{2.4}} - 0.055 & \hat{C}_{linear} \gt 0.0031308 \end{cases} -\end{equation}$$ +$$\begin{equation}\label{OECFsRGB}⟨L:1677⟩ +C_{sRGB} ⟨L:1678⟩= \begin{cases} 12.92 \times \hat{C}_{linear} & \hat{C}_{linear} \le 0.0031308 \\ +1.055 ⟨L:1679⟩\times \hat{C}_{linear}^{\frac{1}{2.4}} - 0.055 & \hat{C}_{linear} \gt 0.0031308 \end{cases} +\end{equation}$$⟨L:1680⟩

-For convenience, figure 45 shows the range of correlated color temperatures from 1,000K to 12,500K. All the colors used below assume CIE \( D_{65} \) as the white point (as is the case in the sRGB color space). +For ⟨L:1682⟩convenience, figure ? shows the range of correlated color temperatures from 1,000K to 12,500K. All the colors used below assume CIE \( D_{65} \) as the white point (as is the case in the sRGB color space).

-

 
Figure 45: Scale of correlated color temperatures
+
Figure ⟨L:1684⟩[colorTemperatureScaleCCT]: Scale of correlated color temperatures

-Similarly, figure 46 shows the range of CIE standard illuminants series D from 1,000K to 12,500K. +Similarly, ⟨L:1686⟩figure ? shows the range of CIE standard illuminants series D from 1,000K to 12,500K.

-

 
Figure 46: Scale of CIE standard illuminants series D
+
Figure ⟨L:1688⟩[colorTemperatureScaleCIE]: Scale of CIE standard illuminants series D

-For reference, figure 47 shows the range of correlated color temperatures without the normalization step presented in equation \(\ref{normalizedRGB}\). +For ⟨L:1690⟩reference, figure ? shows the range of correlated color temperatures without the normalization step presented in equation \(\ref{normalizedRGB}\).

-

 
Figure 47: Unnormalized scale of correlated color temperatures
+
Figure ⟨L:1692⟩[colorTemperatureScaleCCTClamped]: Unnormalized scale of correlated color temperatures

-Table 14 presents the correlated color temperature of various common light sources as sRGB color swatches. These colors are relative to the \( D_{65} \) white point, so their perceived hue might vary based on your display's white point. See What colour is the Sun? for more information. +Table ⟨L:1694⟩[colorTemperatureSamples] presents the correlated color temperature of various common light sources as sRGB color swatches. These colors are relative to the \( D_{65} \) white point, so their perceived hue might vary based on your display's white point. See What colour is the Sun? for more information.

  @@ -1892,12 +1910,12 @@ For reference,
Temperature (K) Light source Color
8,000-10,000 Partly cloudy sky
 

-
   

Pre-exposed lights

+
   

⟨L:1717⟩Pre-exposed lights

-

Physically based rendering and physical light units pose an interesting challenge: how to store and handle the large range of values produced by the lighting code? Assuming computations performed at full precision in the shaders, we still want to be able to store the linear output of the lighting pass in a reasonably sized buffer (RGB16F or equivalent). The most obvious and easiest way to achieve this is to simply apply the camera exposure (see the Physically based camera section for more information) before writing out the result of the lighting pass. This simple step is shown in listing 24:

+

Physically ⟨L:1719⟩based rendering and physical light units pose an interesting challenge: how to store and handle the large range of values produced by the lighting code? Assuming computations performed at full precision in the shaders, we still want to be able to store the linear output of the lighting pass in a reasonably sized buffer (RGB16F or equivalent). The most obvious and easiest way to achieve this is to simply apply the camera exposure (see the Physically based camera section for more information) before writing out the result of the lighting pass. This simple step is shown in listing 24:

 
fragColor = luminance * camera.exposure;
Listing 24: The output of the lighting pass is pre-exposed to fit in half-float buffers

-

This solution solves the storage problem but requires intermediate computations to be performed with single precision floats. We would instead prefer to perform all (or at least most) of the lighting work using half precision floats instead. Doing so can greatly improve performance and power usage, particularly on mobile devices. Half precision floats are however ill-suited for this kind of work as common illuminance and luminance values (for the sun for instance) can exceed their range. The solution is to simply pre-expose the lights themselves instead of the result of the lighting pass. This can be done efficiently on the CPU if updating a light's constant buffer is cheap. This can also be done on the GPU, as shown in listing 25.

+

This ⟨L:1726⟩solution solves the storage problem but requires intermediate computations to be performed with single precision floats. We would instead prefer to perform all (or at least most) of the lighting work using half precision floats instead. Doing so can greatly improve performance and power usage, particularly on mobile devices. Half precision floats are however ill-suited for this kind of work as common illuminance and luminance values (for the sun for instance) can exceed their range. The solution is to simply pre-expose the lights themselves instead of the result of the lighting pass. This can be done efficiently on the CPU if updating a light's constant buffer is cheap. This can also be done on the GPU, as shown in listing 25.

 
// The inputs must be highp/single precision,
 // both for range (intensity) and precision (exposure)
 // The output is mediump/half precision
@@ -1919,7 +1937,7 @@ For reference, 
     return light;
 }

-

In practice we pre-expose the following lights:

+

In ⟨L:1752⟩practice we pre-expose the following lights:

-
   

Image based lights

+
   

⟨L:1758⟩Image based lights

-

In real life, light comes from every direction either directly from light sources or indirectly after bouncing off objects in the environment, being partially absorbed in the process. In a way the whole environment around an object can be seen as a light source. Images, in particular cubemaps, are a great way to encode such an “environment light”. This is called Image Based Lighting (IBL) or sometimes Indirect Lighting.

+

In ⟨L:1760⟩real life, light comes from every direction either directly from light sources or indirectly after bouncing off objects in the environment, being partially absorbed in the process. In a way the whole environment around an object can be seen as a light source. Images, in particular cubemaps, are a great way to encode such an “environment light”. This is called Image Based Lighting (IBL) or sometimes Indirect Lighting.

-

 
Figure 48: The object shown here is lit only by image-encoded environment lights. Notice the subtle lighting effects that can be applied using this technique.
+
Figure ⟨L:1762⟩[iblBall]: The object shown here is lit only by image-encoded environment lights. Notice the subtle lighting effects that can be applied using this technique.

-There are limitations with image-based lighting. Obviously the environment image must be acquired somehow and as we'll see below it needs to be pre-processed before it can be used for lighting. Typically, the environment image is acquired offline in the real world, or generated by the engine either offline or at run time; either way, local or distant probes are used. +There ⟨L:1764⟩are limitations with image-based lighting. Obviously the environment image must be acquired somehow and as we'll see below it needs to be pre-processed before it can be used for lighting. Typically, the environment image is acquired offline in the real world, or generated by the engine either offline or at run time; either way, local or distant probes are used.

-These probes can be used to acquire the distant or local environment. In this document, we're focusing on distant environment probes, where the light is assumed to come from infinitely far away (which means every point on the object's surface uses the same environment map). +These ⟨L:1766⟩probes can be used to acquire the distant or local environment. In this document, we're focusing on distant environment probes, where the light is assumed to come from infinitely far away (which means every point on the object's surface uses the same environment map).

-The whole environment contributes light to a given point on the object's surface; this is called irradiance (\(E\)). The resulting light bouncing off of the object is called radiance (\(L_{out}\)). Incident lighting must be applied consistently to the diffuse and specular parts of the BRDF. +The ⟨L:1768⟩whole environment contributes light to a given point on the object's surface; this is called irradiance (\(E\)). The resulting light bouncing off of the object is called radiance (\(L_{out}\)). Incident lighting must be applied consistently to the diffuse and specular parts of the BRDF.

-The radiance \(L_{out}\) resulting from the interaction between an image based light's (IBL) irradiance and a material model (BRDF) \(f(\Theta)\)5 is computed as follows: +The ⟨L:1770⟩radiance \(L_{out}\) resulting from the interaction between an image based light's (IBL) irradiance and a material model (BRDF) \(f(\Theta)\)5 is computed as follows:

-$$\begin{equation} -L_{out}(n, v, \Theta) = \int_\Omega f(l, v, \Theta) L_{\bot}(l) \left< \NoL \right> dl -\end{equation}$$ +$$\begin{equation}⟨L:1772⟩ +L_{out}(n, ⟨L:1773⟩v, \Theta) = \int_\Omega f(l, v, \Theta) L_{\bot}(l) \left< \NoL \right> dl +\end{equation}$$ ⟨L:1774⟩

-Note that here we're looking at the behavior of the surface at macro level (not to be confused with the micro level equation), which is why it only depends on \(\vec n\) and \(\vec v\). Essentially, we're applying the BRDF to “point-lights” coming from all directions and encoded in the IBL. +Note ⟨L:1776⟩that here we're looking at the behavior of the surface at macro level (not to be confused with the micro level equation), which is why it only depends on \(\vec n\) and \(\vec v\). Essentially, we're applying the BRDF to “point-lights” coming from all directions and encoded in the IBL.

-   

IBL Types

+
   

⟨L:1778⟩IBL Types

-

There are four common types of IBLs used in modern rendering engines:

+

There ⟨L:1780⟩are four common types of IBLs used in modern rendering engines:

  • Distant light probes, used to capture lighting information at “infinity”, where parallax can be ignored. Distant probes typically contain the sky, distant landscape features or buildings, etc. They are either captured by the engine or acquired from a camera as high dynamic range images (HDRI). @@ -1966,15 +1984,15 @@ Note that here we're looking at the behavior of the surface at Screen space reflection, used to capture reflections based on the rendered scene (using the previous frame for instance) by ray-marching in the depth buffer. SSR gives great result but can be very expensive.

-In addition we must distinguish between static and dynamic IBLs. Implementing a fully dynamic day/night cycle requires for instance to recompute the distant light probes dynamically6. Both planar and screen space reflections are inherently dynamic. +In ⟨L:1790⟩addition we must distinguish between static and dynamic IBLs. Implementing a fully dynamic day/night cycle requires for instance to recompute the distant light probes dynamically6. Both planar and screen space reflections are inherently dynamic.

-   

IBL Unit

+
   

⟨L:1792⟩IBL Unit

-

As discussed previously in the direct lighting section, all our lights must use physical units. As such our IBLs will use the luminance unit (\frac{cd}{m^2}), which is also the output unit of all our direct lighting equations. Using the luminance unit is straightforward for light probes captures by the engine (dynamically or statically offline).

+

As ⟨L:1794⟩discussed previously in the direct lighting section, all our lights must use physical units. As such our IBLs will use the luminance unit (\frac{cd}{m^2}), which is also the output unit of all our direct lighting equations. Using the luminance unit is straightforward for light probes captures by the engine (dynamically or statically offline).

-High dynamic range images are a bit more delicate to handle however. Cameras do not record measured luminance but a device-dependent value that is only related to the original scene luminance. As such, we must provide artists with a multiplier that allows them to recover, or at the very least closely approximate, the original absolute luminance. +High ⟨L:1796⟩dynamic range images are a bit more delicate to handle however. Cameras do not record measured luminance but a device-dependent value that is only related to the original scene luminance. As such, we must provide artists with a multiplier that allows them to recover, or at the very least closely approximate, the original absolute luminance.

-To properly reconstruct the luminance of an HDRI for IBL, artists must do more than simply take photos of the environment and record extra information: +To ⟨L:1798⟩properly reconstruct the luminance of an HDRI for IBL, artists must do more than simply take photos of the environment and record extra information:

  • Color calibration: using a gray card or a MacBeth ColorChecker @@ -1985,13 +2003,13 @@ To properly reconstruct the luminance of an HDRI for IBL, artists must do more t
  • Luminance samples: using a spot/luminance meter

-[TODO] Measure and list common luminance values (clear sky, interior, etc.) +[TODO] ⟨L:1806⟩Measure and list common luminance values (clear sky, interior, etc.)

-   

Processing light probes

+
   

⟨L:1808⟩Processing light probes

-

We saw previously that the radiance of an IBL is computed by integrating over the surface's hemisphere. Since this would obviously be too expensive to do in real-time, we must first pre-process our light probes to convert them into a format better suited for real-time interactions.

+

We ⟨L:1810⟩saw previously that the radiance of an IBL is computed by integrating over the surface's hemisphere. Since this would obviously be too expensive to do in real-time, we must first pre-process our light probes to convert them into a format better suited for real-time interactions.

-The sections below will discuss the techniques used to accelerate the evaluation of light probes: +The ⟨L:1812⟩sections below will discuss the techniques used to accelerate the evaluation of light probes:

  • Specular reflectance: pre-filtered importance sampling and split-sum approximation @@ -1999,32 +2017,38 @@ The sections below will discuss the techniques used to accelerate the evaluation
  • Diffuse reflectance: irradiance map and spherical harmonics

-   

Distant light probes

-   

Diffuse BRDF integration

+
   

⟨L:1818⟩Distant light probes

+
   

⟨L:1820⟩Diffuse BRDF integration

-

Using the Lambertian BRDF7, we get the radiance:

+

Using ⟨L:1822⟩the Lambertian BRDF7, we get the radiance:

$$ -\begin{align*} - f_d(\sigma) &= \frac{\sigma}{\pi} \\ -L_d(n, \sigma) &= \int_{\Omega} f_d(\sigma) L_{\bot}(l) \left< \NoL \right> dl \\ - &= \frac{\sigma}{\pi} \int_{\Omega} L_{\bot}(l) \left< \NoL \right> dl \\ - &= \frac{\sigma}{\pi} E_d(n) \quad \text{with the irradiance} \; - E_d(n) = \int_{\Omega} L_{\bot}(l) \left< \NoL \right> dl -\end{align*} +\begin{align*}⟨L:1825⟩ + f_d(\sigma) ⟨L:1826⟩&= \frac{\sigma}{\pi} \\ +L_d(n, ⟨L:1827⟩\sigma) &= \int_{\Omega} f_d(\sigma) L_{\bot}(l) \left< \NoL \right> dl \\ + &= ⟨L:1828⟩\frac{\sigma}{\pi} \int_{\Omega} L_{\bot}(l) \left< \NoL \right> dl \\ + &= ⟨L:1829⟩\frac{\sigma}{\pi} E_d(n) \quad \text{with the irradiance} \; + E_d(n) ⟨L:1830⟩= \int_{\Omega} L_{\bot}(l) \left< \NoL \right> dl +\end{align*}⟨L:1831⟩ $$

-Or in the discrete domain: +Or ⟨L:1834⟩in the discrete domain:

-$$ E_d(n) \equiv \sum_{\forall \, i \in image} L_{\bot}(s_i) \left< n \cdot s_i \right> \Omega_s $$ +$$ ⟨L:1836⟩E_d(n) \equiv \sum_{\forall \, i \in image} L_{\bot}(s_i) \left< n \cdot s_i \right> \Omega_s $$

-\(\Omega_s\) is the solid-angle8 associated to sample \(i\). +\(\Omega_s\) ⟨L:1838⟩is the solid-angle8 associated to sample \(i\).

-The irradiance integral \(\Ed\) can be trivially, albeit slowly9, precomputed and stored into a cubemap for efficient access at runtime. Typically, image is a cubemap or an equirectangular image. The term \( \frac{\sigma}{\pi} \) is independent of the IBL and is added at runtime to obtain the radiance. +The ⟨L:1840⟩irradiance integral \(\Ed\) can be trivially, albeit slowly9, precomputed and stored into a cubemap for efficient access at runtime. Typically, image is a cubemap or an equirectangular image. The term \( \frac{\sigma}{\pi} \) is independent of the IBL and is added at runtime to obtain the radiance.

-

 
Figure 49: Image-based environment
+

-

 
Figure 50: Image-based irradiance map using the Lambertian BRDF
+
Figure ⟨L:1842⟩[iblOriginal]: Image-based environment
+

+

+

+

Figure ⟨L:1844⟩[iblIrradiance]: Image-based irradiance map using the Lambertian BRDF
+

+

 5 \(\Theta\) represents the parameters of the material model \(f\), i.e.: roughness, albedo and so on...
@@ -2041,9 +2065,9 @@ The irradiance integral \(\Ed\) can be trivially, albeit slowly 9 \(O(12\,n^2\,m^2)\), with \(n\) and \(m\) respectively the dimensions of the environment and the precomputed cubemap

-However, the irradiance can also be approximated very closely by a decomposition into Spherical Harmonics (SH, described in more details in the Spherical Harmonics section) and calculated at runtime cheaply. It is usually best to avoid texture fetches on mobile and free-up a texture unit. Even if it is stored into a cubemap, it is orders of magnitude faster to pre-compute the integral using SH decomposition followed by a rendering. +However, ⟨L:1858⟩the irradiance can also be approximated very closely by a decomposition into Spherical Harmonics (SH, described in more details in the Spherical Harmonics section) and calculated at runtime cheaply. It is usually best to avoid texture fetches on mobile and free-up a texture unit. Even if it is stored into a cubemap, it is orders of magnitude faster to pre-compute the integral using SH decomposition followed by a rendering.

-SH decomposition is similar in concept to a Fourier transform, it expresses the signal over an orthonormal base in the frequency domain. The properties that interests us most are: +SH ⟨L:1860⟩decomposition is similar in concept to a Fourier transform, it expresses the signal over an orthonormal base in the frequency domain. The properties that interests us most are:

  • Very few coefficients are needed to encode \(\cosTheta\) @@ -2051,13 +2075,19 @@ SH decomposition is similar in concept to a Fourier transform, it expresses the
  • Convolutions by a kernel that has a circular symmetry are very inexpensive and become products in SH space

-In practice only 4 or 9 coefficients (i.e.: 2 or 3 bands) are enough for \(\cosTheta\) meaning we don't need more either for \(\Lt\). +In ⟨L:1866⟩practice only 4 or 9 coefficients (i.e.: 2 or 3 bands) are enough for \(\cosTheta\) meaning we don't need more either for \(\Lt\).

-

 
Figure 51: 3 bands (9 coefficients)
+

-

 
Figure 52: 2 bands (4 coefficients)
+
Figure ⟨L:1868⟩[iblSH3]: 3 bands (9 coefficients)

-In practice we pre-convolve \(\Lt\) with \(\cosTheta\) and pre-scale these coefficients by the basis scaling factors \(K_l^m\) so that the reconstruction code is as simple as possible in the shader: +

+

+

Figure ⟨L:1870⟩[iblSH2]: 2 bands (4 coefficients)
+

+

+

+In ⟨L:1873⟩practice we pre-convolve \(\Lt\) with \(\cosTheta\) and pre-scale these coefficients by the basis scaling factors \(K_l^m\) so that the reconstruction code is as simple as possible in the shader:

 
vec3 irradianceSH(vec3 n) {
     // uniform vec3 sphericalHarmonics[9]
     // We can use only the first 2 bands for better performance
@@ -2073,44 +2103,44 @@ In practice we pre-convolve \(\Lt\) with \(\cosTheta\) and pre-scale these coeff
         + sphericalHarmonics[8] * (n.x * n.x - n.y * n.y);
 }
Listing 26: GLSL code to reconstruct the irradiance from the pre-scaled SH

-

Note that with 2 bands, the computation above becomes a single (4 \times 4) matrix-by-vector multiply.

+

Note ⟨L:1893⟩that with 2 bands, the computation above becomes a single (4 \times 4) matrix-by-vector multiply.

-Additionally, because of the pre-scaling by \(K_l^m\), the SH coefficients can be thought of as colors, in particular sphericalHarmonics[0] is directly the average irradiance. +Additionally, ⟨L:1895⟩because of the pre-scaling by \(K_l^m\), the SH coefficients can be thought of as colors, in particular sphericalHarmonics[0] is directly the average irradiance.

-   

Specular BRDF integration

+
   

⟨L:1898⟩Specular BRDF integration

-

As we've seen above, the radiance (\Lout) resulting from the interaction between an IBL's irradiance and a BRDF is:

+

As ⟨L:1900⟩we've seen above, the radiance (\Lout) resulting from the interaction between an IBL's irradiance and a BRDF is:

-$$\begin{equation}\label{specularBRDFIntegration} -\Lout(n, v, \Theta) = \int_\Omega f(l, v, \Theta) \Lt(l) \left< \NoL \right> \partial l -\end{equation}$$ +$$\begin{equation}\label{specularBRDFIntegration}⟨L:1902⟩ +\Lout(n, ⟨L:1903⟩v, \Theta) = \int_\Omega f(l, v, \Theta) \Lt(l) \left< \NoL \right> \partial l +\end{equation}$$ ⟨L:1904⟩

-We recognize the convolution of \(\Lt\) by \(f(l, v, \Theta) \left< \NoL \right>\), -i.e.: the environment is filtered using the BRDF as a kernel. Indeed at higher roughness, -specular reflections look more blurry. +We ⟨L:1906⟩recognize the convolution of \(\Lt\) by \(f(l, v, \Theta) \left< \NoL \right>\), +i.e.: ⟨L:1907⟩the environment is filtered using the BRDF as a kernel. Indeed at higher roughness, +specular ⟨L:1908⟩reflections look more blurry.

-Plugging the expression of \(f\) in equation \(\ref{specularBRDFIntegration}\), we obtain: +Plugging ⟨L:1910⟩the expression of \(f\) in equation \(\ref{specularBRDFIntegration}\), we obtain:

-$$\begin{equation} -\Lout(n,v,\Theta) = \int_\Omega D(l, v, \alpha) F(l, v, f_0, f_{90}) V(l, v, \alpha) \left< \NoL \right> \Lt(l) \partial l -\end{equation}$$ +$$\begin{equation}⟨L:1912⟩ +\Lout(n,v,\Theta) ⟨L:1913⟩= \int_\Omega D(l, v, \alpha) F(l, v, f_0, f_{90}) V(l, v, \alpha) \left< \NoL \right> \Lt(l) \partial l +\end{equation}$$ ⟨L:1914⟩

-This expression depends on \(v\), \(\alpha\), \(f_0\) and \(f_{90}\) inside the integral, -which makes its evaluation extremely costly and unsuitable for real-time on mobile -(even using pre-filtered importance sampling). +This ⟨L:1916⟩expression depends on \(v\), \(\alpha\), \(f_0\) and \(f_{90}\) inside the integral, +which ⟨L:1917⟩makes its evaluation extremely costly and unsuitable for real-time on mobile +(even ⟨L:1918⟩using pre-filtered importance sampling).

-   
Simplifying the BRDF integration
+
   
⟨L:1920⟩Simplifying the BRDF integration

-

Since there is no closed-form solution or an easy way to compute the (\Lout) integral, we use a simplified -equation instead: (\hat{I}), whereby we assume that (v = n), that is the view direction (v) is always -equal to the surface normal (n). Clearly, this assumption will break all view-dependant effects of -the convolution, such as the increased blur in reflections closer to the viewer -(a.k.a. stretchy reflections).

+

Since ⟨L:1922⟩there is no closed-form solution or an easy way to compute the (\Lout) integral, we use a simplified +equation ⟨L:1923⟩instead: (\hat{I}), whereby we assume that (v = n), that is the view direction (v) is always +equal ⟨L:1924⟩to the surface normal (n). Clearly, this assumption will break all view-dependant effects of +the ⟨L:1925⟩convolution, such as the increased blur in reflections closer to the viewer +(a.k.a. ⟨L:1926⟩stretchy reflections).

-Such a simplification would also have a severe impact on constant environments, such as the white -furnace, because it would affect the magnitude of the constant (i.e. DC) term of the result. We -can at least correct for that by using a scale factor, \(K\), in our simplified integral, which -will make sure the average irradiance stay correct when chosen properly. +Such ⟨L:1928⟩a simplification would also have a severe impact on constant environments, such as the white +furnace, ⟨L:1929⟩because it would affect the magnitude of the constant (i.e. DC) term of the result. We +can ⟨L:1930⟩at least correct for that by using a scale factor, \(K\), in our simplified integral, which +will ⟨L:1931⟩make sure the average irradiance stay correct when chosen properly.

  • \(I\) is our original integral, i.e.: \(I(g) = \int_\Omega g(l) \left< \NoL \right> \partial l\) @@ -2121,250 +2151,234 @@ will make sure the average irradiance stay correct when chosen properly.
  • \(\tilde{I}\) is our final approximation of \(I\), \(\tilde{I} = \hat{I} \times K\)

-Because \(I\) is an integral multiplications can be distributed over it. i.e.: \(I(g()f()) = I(g())I(f())\). +Because ⟨L:1939⟩\(I\) is an integral multiplications can be distributed over it. i.e.: \(I(g()f()) = I(g())I(f())\).

-Armed with that, +Armed ⟨L:1941⟩with that,

-$$\begin{equation} -I( f(\Theta) \Lt ) \approx \tilde{I}( f(\Theta) \Lt ) \\ -\tilde{I}( f(\Theta) \Lt ) = K \times \hat{I}( f(\Theta) \Lt ) \\ -K = \frac{I(f(\Theta))}{\hat{I}(f(\Theta))} -\end{equation}$$ +$$\begin{equation}⟨L:1943⟩ +I( ⟨L:1944⟩f(\Theta) \Lt ) \approx \tilde{I}( f(\Theta) \Lt ) \\ +\tilde{I}( ⟨L:1945⟩f(\Theta) \Lt ) = K \times \hat{I}( f(\Theta) \Lt ) \\ +K ⟨L:1946⟩= \frac{I(f(\Theta))}{\hat{I}(f(\Theta))} +\end{equation}$$ ⟨L:1947⟩

-From the equation above we can see that \(\tilde{I}\) is equivalent to \(I\) when \(\Lt\) is a constant, -and yields the correct result: +From ⟨L:1950⟩the equation above we can see that \(\tilde{I}\) is equivalent to \(I\) when \(\Lt\) is a constant, +and ⟨L:1951⟩yields the correct result:

-$$\begin{align*} -\tilde{I}(f(\Theta)\Lt^{constant}) &= \Lt^{constant} \hat{I}(f(\Theta)) \frac{I(f(\Theta))}{\hat{I}(f(\Theta))} \\ - &= \Lt^{constant} I(f(\Theta)) \\ - &= I(f(\Theta)\Lt^{constant}) -\end{align*}$$ +$$\begin{align*}⟨L:1953⟩ +\tilde{I}(f(\Theta)\Lt^{constant}) ⟨L:1954⟩&= \Lt^{constant} \hat{I}(f(\Theta)) \frac{I(f(\Theta))}{\hat{I}(f(\Theta))} \\ + &= ⟨L:1955⟩\Lt^{constant} I(f(\Theta)) \\ + &= ⟨L:1956⟩I(f(\Theta)\Lt^{constant}) +\end{align*}$$⟨L:1957⟩

-Similarly, we can also demonstrate that the result is correct when \(v = n\), since in that case \(I = \hat{I}\): +Similarly, ⟨L:1960⟩we can also demonstrate that the result is correct when \(v = n\), since in that case \(I = \hat{I}\):

-$$\begin{align*} -\tilde{I}(f(\Theta)\Lt) &= I(f(\Theta)\Lt) \frac{I(f(\Theta))}{I(f(\Theta))} \\ - &= I(f(\Theta)\Lt) -\end{align*}$$ +$$\begin{align*}⟨L:1962⟩ +\tilde{I}(f(\Theta)\Lt) ⟨L:1963⟩&= I(f(\Theta)\Lt) \frac{I(f(\Theta))}{I(f(\Theta))} \\ + &= ⟨L:1964⟩I(f(\Theta)\Lt) +\end{align*}$$⟨L:1965⟩

-Finally, we can show that the scale factor \(K\) satisfies our average irradiance (\(\bar{\Lt}\)) -requirement by plugging \(\Lt = \bar{\Lt} + (\Lt - \bar{\Lt}) = \bar{\Lt} + \Delta\Lt\) into \(\tilde{I}\): +Finally, ⟨L:1967⟩we can show that the scale factor \(K\) satisfies our average irradiance (\(\bar{\Lt}\)) +requirement ⟨L:1968⟩by plugging \(\Lt = \bar{\Lt} + (\Lt - \bar{\Lt}) = \bar{\Lt} + \Delta\Lt\) into \(\tilde{I}\):

-$$\begin{align*} -\tilde{I}(f(\Theta)\Lt) &= \tilde{I}\left[f\left(\Theta\right) \left(\bar{\Lt} + \Delta\Lt\right)\right] \\ - &= K \times \hat{I}\left[f\left(\Theta\right) \left(\bar{\Lt} + \Delta\Lt\right)\right] \\ - &= K \times \left[\hat{I}\left(f\left(\Theta\right)\bar{\Lt}\right) + \hat{I}\left(f\left(\Theta\right)\Delta\Lt\right)\right] \\ - &= K \times \hat{I}\left(f\left(\Theta\right)\bar{\Lt}\right) + K \times \hat{I}\left(f\left(\Theta\right) \Delta\Lt\right) \\ - &= \tilde{I}\left(f\left(\Theta\right)\bar{\Lt}\right) + \tilde{I}\left(f\left(\Theta\right) \Delta\Lt\right) \\ - &= I\left(f\left(\Theta\right)\bar{\Lt}\right) + \tilde{I}\left(f\left(\Theta\right) \Delta\Lt\right) -\end{align*}$$ +$$\begin{align*}⟨L:1970⟩ +\tilde{I}(f(\Theta)\Lt) ⟨L:1971⟩&= \tilde{I}\left[f\left(\Theta\right) \left(\bar{\Lt} + \Delta\Lt\right)\right] \\ + &= ⟨L:1972⟩K \times \hat{I}\left[f\left(\Theta\right) \left(\bar{\Lt} + \Delta\Lt\right)\right] \\ + &= ⟨L:1973⟩K \times \left[\hat{I}\left(f\left(\Theta\right)\bar{\Lt}\right) + \hat{I}\left(f\left(\Theta\right)\Delta\Lt\right)\right] \\ + &= ⟨L:1974⟩K \times \hat{I}\left(f\left(\Theta\right)\bar{\Lt}\right) + K \times \hat{I}\left(f\left(\Theta\right) \Delta\Lt\right) \\ + &= ⟨L:1975⟩\tilde{I}\left(f\left(\Theta\right)\bar{\Lt}\right) + \tilde{I}\left(f\left(\Theta\right) \Delta\Lt\right) \\ + &= ⟨L:1976⟩I\left(f\left(\Theta\right)\bar{\Lt}\right) + \tilde{I}\left(f\left(\Theta\right) \Delta\Lt\right) +\end{align*}$$⟨L:1977⟩

-The above result shows that the average irradiance is computed correctly, i.e.: \(I(f(\Theta)\bar{\Lt})\). +The ⟨L:1979⟩above result shows that the average irradiance is computed correctly, i.e.: \(I(f(\Theta)\bar{\Lt})\).

-A way to think about this approximation is that it splits the radiance \(\Lt\) in two parts, -the average \(\bar{\Lt}\) and the delta from the average \(\Delta\Lt\) and computes the correct -integration of the average part then adds the simplified integration of the delta part: +A ⟨L:1981⟩way to think about this approximation is that it splits the radiance \(\Lt\) in two parts, +the ⟨L:1982⟩average \(\bar{\Lt}\) and the delta from the average \(\Delta\Lt\) and computes the correct +integration ⟨L:1983⟩of the average part then adds the simplified integration of the delta part:

-$$\begin{equation} -approximation(\Lt) = correct(\bar{\Lt}) + simplified(\Lt - \bar{\Lt}) -\end{equation}$$ +$$\begin{equation}⟨L:1985⟩ +approximation(\Lt) ⟨L:1986⟩= correct(\bar{\Lt}) + simplified(\Lt - \bar{\Lt}) +\end{equation}$$ ⟨L:1987⟩

-Now, let's look at each term: +Now, ⟨L:1991⟩let's look at each term:

-$$\begin{equation}\label{iblPartialEquations} -\hat{I}(f(n, \alpha) \Lt) = \int_\Omega f(l, n, \alpha) \Lt(l) \left< \NoL \right> \partial l \\ -\hat{I}(f(n, \alpha)) = \int_\Omega f(l, n, \alpha) \left< \NoL \right> \partial l \\ -I(f(n, v, \alpha)) = \int_\Omega f(l, n, v, \alpha) \left< \NoL \right> \partial l -\end{equation}$$ +$$\begin{equation}\label{iblPartialEquations}⟨L:1993⟩ +\hat{I}(f(n, ⟨L:1994⟩\alpha) \Lt) = \int_\Omega f(l, n, \alpha) \Lt(l) \left< \NoL \right> \partial l \\ +\hat{I}(f(n, ⟨L:1995⟩\alpha)) = \int_\Omega f(l, n, \alpha) \left< \NoL \right> \partial l \\ +I(f(n, ⟨L:1996⟩v, \alpha)) = \int_\Omega f(l, n, v, \alpha) \left< \NoL \right> \partial l +\end{equation}$$⟨L:1997⟩

-All three of these equations can be easily pre-calculated and stored in look-up tables, as explained -below. +All ⟨L:2000⟩three of these equations can be easily pre-calculated and stored in look-up tables, as explained +below.⟨L:2001⟩

-   
Discrete Domain
+
   
⟨L:2004⟩Discrete Domain

-

In the discrete domain the equations in \ref{iblPartialEquations} become:

+

In ⟨L:2006⟩the discrete domain the equations in \ref{iblPartialEquations} become:

-$$\begin{equation} -\hat{I}(f(n, \alpha) \Lt) \equiv \frac{1}{N}\sum_{\forall \, i \in image} f(l_i, n, \alpha) \Lt(l_i) \left<\NoL\right> \\ -\hat{I}(f(n, \alpha)) \equiv \frac{1}{N}\sum_{\forall \, i \in image} f(l_i, n, \alpha) \left<\NoL\right> \\ -I(f(n, v, \alpha)) \equiv \frac{1}{N}\sum_{\forall \, i \in image} f(l_i, n, v, \alpha) \left<\NoL\right> -\end{equation}$$ +$$\begin{equation}⟨L:2008⟩ +\hat{I}(f(n, ⟨L:2009⟩\alpha) \Lt) \equiv \frac{1}{N}\sum_{\forall \, i \in image} f(l_i, n, \alpha) \Lt(l_i) \left<\NoL\right> \\ +\hat{I}(f(n, ⟨L:2010⟩\alpha)) \equiv \frac{1}{N}\sum_{\forall \, i \in image} f(l_i, n, \alpha) \left<\NoL\right> \\ +I(f(n, ⟨L:2011⟩v, \alpha)) \equiv \frac{1}{N}\sum_{\forall \, i \in image} f(l_i, n, v, \alpha) \left<\NoL\right> +\end{equation}$$⟨L:2012⟩

-However, in practice we're using importance sampling which needs to take the \(pdf\) of the distribution -into account and adds a term \(\frac{4\left<\VoH\right>}{D(h_i, \alpha)\left<\NoH\right>}\). -See Importance Sampling For The IBL section: +However, ⟨L:2014⟩in practice we're using importance sampling which needs to take the \(pdf\) of the distribution +into ⟨L:2015⟩account and adds a term \(\frac{4\left<\VoH\right>}{D(h_i, \alpha)\left<\NoH\right>}\). +See ⟨L:2016⟩Importance Sampling For The IBL section:

-$$\begin{equation}\label{iblImportanceSampling} -\hat{I}(f(n, \alpha) \Lt) \equiv \frac{4}{N}\sum_i^N f(l_i, n, \alpha) \frac{\left<\VoH\right>}{D(h_i, \alpha)\left<\NoH\right>} \Lt(l_i) \left<\NoL\right> \\ -\hat{I}(f(n, \alpha)) \equiv \frac{4}{N}\sum_i^N f(l_i, n, \alpha) \frac{\left<\VoH\right>}{D(h_i, \alpha)\left<\NoH\right>} \left<\NoL\right> \\ -I(f(n, v, \alpha)) \equiv \frac{4}{N}\sum_i^N f(l_i, n, v, \alpha) \frac{\left<\VoH\right>}{D(h_i, \alpha)\left<\NoH\right>} \left<\NoL\right> -\end{equation}$$ +$$\begin{equation}\label{iblImportanceSampling}⟨L:2018⟩ +\hat{I}(f(n, ⟨L:2019⟩\alpha) \Lt) \equiv \frac{4}{N}\sum_i^N f(l_i, n, \alpha) \frac{\left<\VoH\right>}{D(h_i, \alpha)\left<\NoH\right>} \Lt(l_i) \left<\NoL\right> \\ +\hat{I}(f(n, ⟨L:2020⟩\alpha)) \equiv \frac{4}{N}\sum_i^N f(l_i, n, \alpha) \frac{\left<\VoH\right>}{D(h_i, \alpha)\left<\NoH\right>} \left<\NoL\right> \\ +I(f(n, ⟨L:2021⟩v, \alpha)) \equiv \frac{4}{N}\sum_i^N f(l_i, n, v, \alpha) \frac{\left<\VoH\right>}{D(h_i, \alpha)\left<\NoH\right>} \left<\NoL\right> +\end{equation}$$⟨L:2022⟩

-Recalling that for \(\hat{I}\), we assume that \(v = n\), equations \ref{iblImportanceSampling}, -simplifies to: +Recalling ⟨L:2025⟩that for \(\hat{I}\), we assume that \(v = n\), equations \ref{iblImportanceSampling}, +simplifies ⟨L:2026⟩to:

-$$\begin{equation} -\hat{I}(f(n, \alpha) \Lt) \equiv \frac{4}{N}\sum_i^N \frac{f(l_i, n, \alpha)}{D(h_i, \alpha)} \Lt(l_i) \left<\NoL\right> \\ -\hat{I}(f(n, \alpha)) \equiv \frac{4}{N}\sum_i^N \frac{f(l_i, n, \alpha)}{D(h_i, \alpha)} \left<\NoL\right> \\ -I(f(n, v, \alpha)) \equiv \frac{4}{N}\sum_i^N \frac{f(l_i, n, v, \alpha)}{D(h_i, \alpha)} \frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> -\end{equation}$$ +$$\begin{equation}⟨L:2028⟩ +\hat{I}(f(n, ⟨L:2029⟩\alpha) \Lt) \equiv \frac{4}{N}\sum_i^N \frac{f(l_i, n, \alpha)}{D(h_i, \alpha)} \Lt(l_i) \left<\NoL\right> \\ +\hat{I}(f(n, ⟨L:2030⟩\alpha)) \equiv \frac{4}{N}\sum_i^N \frac{f(l_i, n, \alpha)}{D(h_i, \alpha)} \left<\NoL\right> \\ +I(f(n, ⟨L:2031⟩v, \alpha)) \equiv \frac{4}{N}\sum_i^N \frac{f(l_i, n, v, \alpha)}{D(h_i, \alpha)} \frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> +\end{equation}$$⟨L:2032⟩

-Then, the first two equations can be merged together such that \(LD(n, \alpha) = \frac{\hat{I}(f(n, \alpha) \Lt)}{\hat{I}(f(n, \alpha))}\) +Then, ⟨L:2034⟩the first two equations can be merged together such that \(LD(n, \alpha) = \frac{\hat{I}(f(n, \alpha) \Lt)}{\hat{I}(f(n, \alpha))}\)

-$$\begin{equation}\label{iblLD} -LD(n, \alpha) \equiv \frac{\sum_i^N \frac{f(l_i, n, \alpha)}{D(h_i, \alpha)} \Lt(l_i) \left<\NoL\right>}{\sum_i^N \frac{f(l_i, n, \alpha)}{D(h_i, \alpha)}\left<\NoL\right>} -\end{equation}$$ -$$\begin{equation}\label{iblDFV} -I(f(n, v, \alpha)) \equiv \frac{4}{N}\sum_i^N \frac{f(l_i, n, v, \alpha)}{D(h_i, \alpha)} \frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> -\end{equation}$$ +$$\begin{equation}\label{iblLD}⟨L:2036⟩ +LD(n, ⟨L:2037⟩\alpha) \equiv \frac{\sum_i^N \frac{f(l_i, n, \alpha)}{D(h_i, \alpha)} \Lt(l_i) \left<\NoL\right>}{\sum_i^N \frac{f(l_i, n, \alpha)}{D(h_i, \alpha)}\left<\NoL\right>} +\end{equation}$$⟨L:2038⟩ +$$\begin{equation}\label{iblDFV}⟨L:2039⟩ +I(f(n, ⟨L:2040⟩v, \alpha)) \equiv \frac{4}{N}\sum_i^N \frac{f(l_i, n, v, \alpha)}{D(h_i, \alpha)} \frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> +\end{equation}$$⟨L:2041⟩

-Note that at this point, we could almost compute both remaining equations off-line. The only difficulty -is that we don't know \(f_0\) nor \(f_{90}\) when we precompute those integrals. We will see below that -we can incorporate these terms at runtime for equation \ref{iblDFV}, alas, this is not possible for -equation \ref{iblLD} and we have to assume \(f_0 = f_{90} = 1\) (i.e.: the fresnel term always evaluates to 1). +Note ⟨L:2043⟩that at this point, we could almost compute both remaining equations off-line. The only difficulty +is ⟨L:2044⟩that we don't know \(f_0\) nor \(f_{90}\) when we precompute those integrals. We will see below that +we ⟨L:2045⟩can incorporate these terms at runtime for equation \ref{iblDFV}, alas, this is not possible for +equation ⟨L:2046⟩\ref{iblLD} and we have to assume \(f_0 = f_{90} = 1\) (i.e.: the fresnel term always evaluates to 1).

-We also have to deal with the visibility term of the brdf, in practice keeping it yields to slightly -worst results compared to the ground truth, so we also set \(V = 1\). +We ⟨L:2048⟩also have to deal with the visibility term of the brdf, in practice keeping it yields to slightly +worst ⟨L:2049⟩results compared to the ground truth, so we also set \(V = 1\).

-Let's substitute \(f\) in equations \ref{iblLD} and \ref{iblDFV}: +Let's ⟨L:2051⟩substitute \(f\) in equations \ref{iblLD} and \ref{iblDFV}:

-$$\begin{equation} -f(l_i, n, \alpha) = D(h_i, \alpha)F(f_0, f_{90}, \left<\VoH\right>)V(l_i, v, \alpha) -\end{equation}$$ +$$\begin{equation}⟨L:2053⟩ +f(l_i, ⟨L:2054⟩n, \alpha) = D(h_i, \alpha)F(f_0, f_{90}, \left<\VoH\right>)V(l_i, v, \alpha) +\end{equation}$$⟨L:2055⟩

-The first simplification is that the term \(D(h_i, \alpha)\) in the brdf cancels out with the -denominator (which came from the \(pdf\) due to importance sampling) and F and V disappear since we -assume their value is 1. +The ⟨L:2057⟩first simplification is that the term \(D(h_i, \alpha)\) in the brdf cancels out with the +denominator ⟨L:2058⟩(which came from the \(pdf\) due to importance sampling) and F and V disappear since we +assume ⟨L:2059⟩their value is 1.

-$$\begin{equation} -LD(n, \alpha) \equiv \frac{\sum_i^N V(l_i, v, \alpha)\left<\NoL\right>\Lt(l_i) }{\sum_i^N \left<\NoL\right>} -\end{equation}$$ -$$\begin{equation}\label{iblFV} -I(f(n, v, \alpha)) \equiv \frac{4}{N}\sum_i^N \color{green}{F(f_0, f_{90}, \left<\VoH\right>)} V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> -\end{equation}$$ +$$\begin{equation}⟨L:2061⟩ +LD(n, ⟨L:2062⟩\alpha) \equiv \frac{\sum_i^N V(l_i, v, \alpha)\left<\NoL\right>\Lt(l_i) }{\sum_i^N \left<\NoL\right>} +\end{equation}$$⟨L:2063⟩ +$$\begin{equation}\label{iblFV}⟨L:2064⟩ +I(f(n, ⟨L:2065⟩v, \alpha)) \equiv \frac{4}{N}\sum_i^N \color{green}{F(f_0, f_{90}, \left<\VoH\right>)} V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> +\end{equation}$$⟨L:2066⟩

-Now, let's substitute the fresnel term into equation \ref{iblFV}: +Now, ⟨L:2068⟩let's substitute the fresnel term into equation \ref{iblFV}:

-$$\begin{equation} -F(f_0, f_{90}, \left<\VoH\right>) = f_0 (1 - F_c(\left<\VoH\right>)) + f_{90} F_c(\left<\VoH\right>) \\ -F_c(\left<\VoH\right>) = (1 - \left<\VoH\right>)^5 -\end{equation}$$ +$$\begin{equation}⟨L:2070⟩ +F(f_0, ⟨L:2071⟩f_{90}, \left<\VoH\right>) = f_0 (1 - F_c(\left<\VoH\right>)) + f_{90} F_c(\left<\VoH\right>) \\ +F_c(\left<\VoH\right>) ⟨L:2072⟩= (1 - \left<\VoH\right>)^5 +\end{equation}$$⟨L:2073⟩

-$$\begin{equation} -I(f(n, v, \alpha)) \equiv \frac{4}{N}\sum_i^N \left[\color{green}{f_0 (1 - F_c(\left<\VoH\right>)) + f_{90} F_c(\left<\VoH\right>)}\right] V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ -\end{equation}$$ +$$\begin{equation}⟨L:2076⟩ +I(f(n, ⟨L:2077⟩v, \alpha)) \equiv \frac{4}{N}\sum_i^N \left[\color{green}{f_0 (1 - F_c(\left<\VoH\right>)) + f_{90} F_c(\left<\VoH\right>)}\right] V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ +\end{equation}$$⟨L:2078⟩

$$ -\begin{align*} -I(f(n, v, \alpha)) \equiv & \color{green}{f_0 } \frac{4}{N}\sum_i^N \color{green}{(1 - F_c(\left<\VoH\right>))} V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ +\begin{align*}⟨L:2081⟩ +I(f(n, ⟨L:2082⟩v, \alpha)) \equiv & \color{green}{f_0 } \frac{4}{N}\sum_i^N \color{green}{(1 - F_c(\left<\VoH\right>))} V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ + & \color{green}{f_{90}} \frac{4}{N}\sum_i^N \color{green}{ F_c(\left<\VoH\right>) } V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> -\end{align*} +\end{align*}⟨L:2084⟩ $$

-And finally, we extract the equations that can be calculated off-line (i.e.: the part that doesn't -depend on the runtime parameters \(f_0\) and \(f_{90}\)): +And ⟨L:2088⟩finally, we extract the equations that can be calculated off-line (i.e.: the part that doesn't +depend ⟨L:2089⟩on the runtime parameters \(f_0\) and \(f_{90}\)):

-$$\begin{equation}\label{iblAllEquations} -DFG_1(\alpha, \left<\NoV\right>) = \frac{4}{N}\sum_i^N \color{green}{(1 - F_c(\left<\VoH\right>))} V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ -DFG_2(\alpha, \left<\NoV\right>) = \frac{4}{N}\sum_i^N \color{green}{ F_c(\left<\VoH\right>) } V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ -I(f(n, v, \alpha)) \equiv \color{green}{f_0} \color{red}{DFG_1(\alpha, \left<\NoV\right>)} + \color{green}{f_{90}} \color{red}{DFG_2(\alpha, \left<\NoV\right>)} -\end{equation}$$ +$$\begin{equation}\label{iblAllEquations}⟨L:2091⟩ +DFG_1(\alpha, ⟨L:2092⟩\left<\NoV\right>) = \frac{4}{N}\sum_i^N \color{green}{(1 - F_c(\left<\VoH\right>))} V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ +DFG_2(\alpha, ⟨L:2093⟩\left<\NoV\right>) = \frac{4}{N}\sum_i^N \color{green}{ F_c(\left<\VoH\right>) } V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ +I(f(n, ⟨L:2094⟩v, \alpha)) \equiv \color{green}{f_0} \color{red}{DFG_1(\alpha, \left<\NoV\right>)} + \color{green}{f_{90}} \color{red}{DFG_2(\alpha, \left<\NoV\right>)} +\end{equation}$$⟨L:2095⟩

-Notice that \(DFG_1\) and \(DFG_2\) only depend on \(\NoV\), that is the angle between the normal \(n\) and -the view direction \(v\). This is true because the integral is symmetrical with respect to \(n\). -When integrating, we can choose any \(v\) we please as long as it satisfies \(\NoV\) -(e.g.: when calculating \(\VoH\)). +Notice ⟨L:2098⟩that \(DFG_1\) and \(DFG_2\) only depend on \(\NoV\), that is the angle between the normal \(n\) and +the ⟨L:2099⟩view direction \(v\). This is true because the integral is symmetrical with respect to \(n\). +When ⟨L:2100⟩integrating, we can choose any \(v\) we please as long as it satisfies \(\NoV\) +(e.g.: ⟨L:2101⟩when calculating \(\VoH\)).

-Putting everything back together: +Putting ⟨L:2104⟩everything back together:

$$ -\begin{align*} -\Lout(n,v,\alpha,f_0,f_{90}) &\simeq \big[ f_0 \color{red}{DFG_1(\NoV, \alpha)} + f_{90} \color{red}{DFG_2(\NoV, \alpha)} \big] \times LD(n, \alpha) \\ -DFG_1(\alpha, \left<\NoV\right>) &= \frac{4}{N}\sum_i^N \color{green}{(1 - F_c(\left<\VoH\right>))} V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ -DFG_2(\alpha, \left<\NoV\right>) &= \frac{4}{N}\sum_i^N \color{green}{ F_c(\left<\VoH\right>) } V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ -LD(n, \alpha) &= \frac{\sum_i^N V(l_i, n, \alpha)\left<\NoL\right>\Lt(l_i) }{\sum_i^N \left<\NoL\right>} -\end{align*} +\begin{align*}⟨L:2107⟩ +\Lout(n,v,\alpha,f_0,f_{90}) ⟨L:2108⟩&\simeq \big[ f_0 \color{red}{DFG_1(\NoV, \alpha)} + f_{90} \color{red}{DFG_2(\NoV, \alpha)} \big] \times LD(n, \alpha) \\ +DFG_1(\alpha, ⟨L:2109⟩\left<\NoV\right>) &= \frac{4}{N}\sum_i^N \color{green}{(1 - F_c(\left<\VoH\right>))} V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ +DFG_2(\alpha, ⟨L:2110⟩\left<\NoV\right>) &= \frac{4}{N}\sum_i^N \color{green}{ F_c(\left<\VoH\right>) } V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ +LD(n, ⟨L:2111⟩\alpha) &= \frac{\sum_i^N V(l_i, n, \alpha)\left<\NoL\right>\Lt(l_i) }{\sum_i^N \left<\NoL\right>} +\end{align*} ⟨L:2112⟩ $$

-   

The \(DFG_1\) and \(DFG_2\) term visualized

+
   

⟨L:2115⟩The \(DFG_1\) and \(DFG_2\) term visualized

-

Both (DFG_1) and (DFG_2) can either be pre-calculated in a regular 2D texture indexed by ((\NoV, \alpha)) -and sampled bilinearly, or computed at runtime using an analytic approximation of the surfaces. -See sample code in the annex. -The pre-calculated textures are shown in table 15. -A C++ implementation of the pre-computation can be found in section 9.5.

+

Both ⟨L:2117⟩(DFG_1) and (DFG_2) can either be pre-calculated in a regular 2D texture indexed by ((\NoV, \alpha)) +and ⟨L:2118⟩sampled bilinearly, or computed at runtime using an analytic approximation of the surfaces. +See ⟨L:2119⟩sample code in the annex. +The ⟨L:2120⟩pre-calculated textures are shown in table 15. +A ⟨L:2121⟩C++ implementation of the pre-computation can be found in section 9.5.

 
\(DFG_1\) \(DFG_2\) \({ DFG_1, DFG_2, 0 }\)
Table 15: Y axis: \(\alpha\). X axis: \(cos \theta\)

-\(DFG_1\) and \(DFG_2\) are conveniently within the \([0, 1]\) range, however 8-bits textures don't have -enough precision and will cause problems. -Unfortunately, on mobile, 16-bits or float textures are not ubiquitous and there are a limited -number of samplers. -Despite the attractive simplicity of the shader code using a texture, it might be better to use an -analytic approximation. Note however that since we only need to store two terms, -OpenGL ES 3.0's RG16F texture format is a good candidate. +\(DFG_1\) ⟨L:2130⟩and \(DFG_2\) are conveniently within the \([0, 1]\) range, however 8-bits textures don't have +enough ⟨L:2131⟩precision and will cause problems. +Unfortunately, ⟨L:2132⟩on mobile, 16-bits or float textures are not ubiquitous and there are a limited +number ⟨L:2133⟩of samplers. +Despite ⟨L:2134⟩the attractive simplicity of the shader code using a texture, it might be better to use an +analytic ⟨L:2135⟩approximation. Note however that since we only need to store two terms, +OpenGL ⟨L:2136⟩ES 3.0's RG16F texture format is a good candidate.

-Such analytic approximation is described in [Karis14], itself based on [Lazarov13]. -[Narkowicz14] is another interesting approximation. Note that these two approximations are not -compatible with the energy compensation term presented in section 5.3.4.7. -Table 16 presents a visual representation of these approximations. +Such ⟨L:2138⟩analytic approximation is described in [Karis14], itself based on [Lazarov13]. +[Narkowicz14] ⟨L:2139⟩is another interesting approximation. Note that these two approximations are not +compatible ⟨L:2140⟩with the energy compensation term presented in section 5.3.4.7. +Table ⟨L:2141⟩[textureApproxDFG] presents a visual representation of these approximations.

 
\(DFG_1\) \(DFG_2\) \({ DFG_1, DFG_2, 0 }\)
Table 16: Y axis: \(\alpha\). X axis: \(cos \theta\)

-   

The \(LD\) term visualized

+
   

⟨L:2149⟩The \(LD\) term visualized

-

(LD) is the convolution of the environment by a function that only depends on the (\alpha) parameter -(itself related to the roughness, see section 4.8.3.3). -(LD) can conveniently be stored in a mip-mapped cubemap where increasing LODs receive the environment -pre-filtered with increasing roughness. This works well because this convolution is a -powerful low-pass filter. To make good use of each mipmap level, it is necessary to remap -(\alpha); we find that using a power remapping with (\gamma = 2) works well and is convenient.

+

(LD) ⟨L:2151⟩is the convolution of the environment by a function that only depends on the (\alpha) parameter +(itself ⟨L:2152⟩related to the roughness, see section 4.8.3.3). +(LD) ⟨L:2153⟩can conveniently be stored in a mip-mapped cubemap where increasing LODs receive the environment +pre-filtered ⟨L:2154⟩with increasing roughness. This works well because this convolution is a +powerful ⟨L:2155⟩low-pass filter. To make good use of each mipmap level, it is necessary to remap +(\alpha); ⟨L:2156⟩we find that using a power remapping with (\gamma = 2) works well and is convenient.

$$ -\begin{align*} - \alpha &= perceptualRoughness^2 \\ - lod_{\alpha} &= \alpha^{\frac{1}{2}} = perceptualRoughness \\ -\end{align*} +\begin{align*}⟨L:2159⟩ + \alpha ⟨L:2160⟩&= perceptualRoughness^2 \\ + lod_{\alpha} ⟨L:2161⟩&= \alpha^{\frac{1}{2}} = perceptualRoughness \\ +\end{align*}⟨L:2162⟩ $$

-See an example below: +See ⟨L:2165⟩an example below:

-
-

-

\(\alpha=0.0\)
-

-

-

-

\(\alpha=0.2\)
-

-

-

-

\(\alpha=0.4\)
-

-

-

-

\(0.6\)
-

-

-

-

\(0.8\)
-

-

+

\(\alpha=0.0\)
+
\(\alpha=0.2\)
+
\(\alpha=0.4\)
+
\(0.6\)
+
\(0.8\)

-   

Indirect specular and indirect diffuse components visualized

+
   

⟨L:2174⟩Indirect specular and indirect diffuse components visualized

-

Figure 53 shows how indirect lighting interacts with dielectrics and conductors. Direct lighting was removed for illustration purposes.

+

Figure ⟨L:2176⟩[iblVisualized] shows how indirect lighting interacts with dielectrics and conductors. Direct lighting was removed for illustration purposes.

-

 
Figure 53: Indirect diffuse and specular decomposition
+
Figure ⟨L:2178⟩[iblVisualized]: Indirect diffuse and specular decomposition

-   

IBL evaluation implementation

+
   

⟨L:2180⟩IBL evaluation implementation

-

Listing 27 presents a GLSL implementation to evaluate the IBL, using the various textures described in the previous sections.

+

Listing ⟨L:2182⟩[iblEvaluation] presents a GLSL implementation to evaluate the IBL, using the various textures described in the previous sections.

 
vec3 ibl(vec3 n, vec3 v, vec3 diffuseColor, vec3 f0, vec3 f90,
         float perceptualRoughness) {
     vec3 r = reflect(n);
@@ -2376,8 +2390,8 @@ See an example below:
     return Ld + Lr;
 }
Listing 27: GLSL implementation of image based lighting evaluation

-

We can however save a couple of texture lookups by using Spherical Harmonics instead of an -irradiance cubemap and the analytical approximation of the (DFG) LUT, as shown in listing 28.

+

We ⟨L:2198⟩can however save a couple of texture lookups by using Spherical Harmonics instead of an +irradiance ⟨L:2199⟩cubemap and the analytical approximation of the (DFG) LUT, as shown in listing 28.

 
vec3 irradianceSH(vec3 n) {
     // uniform vec3 sphericalHarmonics[9]
     // We can use only the first 2 bands for better performance
@@ -2427,66 +2441,66 @@ irradiance cubemap and the analytical approximation of the (DFG) LUT, as shown i
     // Indirect contribution
     return diffuseColor * indirectDiffuse + indirectSpecular * specularColor;
 }
Listing 28: GLSL implementation of image based lighting evaluation
-   

Pre-integration for multiscattering

+
   

⟨L:2255⟩Pre-integration for multiscattering

-

In section 4.7.2 we discussed how to use a second scaled specular lobe -to compensate for the energy loss due to only accounting for a single scattering event in our BRDF. -This energy compensation lobe is scaled by a term that depends on (r) defined in the following way:

+

In ⟨L:2257⟩section 4.7.2 we discussed how to use a second scaled specular lobe +to ⟨L:2258⟩compensate for the energy loss due to only accounting for a single scattering event in our BRDF. +This ⟨L:2259⟩energy compensation lobe is scaled by a term that depends on (r) defined in the following way:

-$$\begin{equation} -r = \int_{\Omega} D(l,v) V(l,v) \left< \NoL \right> \partial l -\end{equation}$$ +$$\begin{equation}⟨L:2261⟩ +r ⟨L:2262⟩= \int_{\Omega} D(l,v) V(l,v) \left< \NoL \right> \partial l +\end{equation}$$⟨L:2263⟩

-Or, evaluated with importance sampling (See Importance Sampling For The IBL section): +Or, ⟨L:2265⟩evaluated with importance sampling (See Importance Sampling For The IBL section):

-$$\begin{equation} -r \equiv \frac{4}{N}\sum_i^N V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> -\end{equation}$$ +$$\begin{equation}⟨L:2267⟩ +r ⟨L:2268⟩\equiv \frac{4}{N}\sum_i^N V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> +\end{equation}$$⟨L:2269⟩

-This equality is very similar to the terms \(DFG_1\) and \(DFG_2\) seen in equation \(\ref{iblAllEquations}\). -In fact, it's the same, except without the Fresnel term. +This ⟨L:2271⟩equality is very similar to the terms \(DFG_1\) and \(DFG_2\) seen in equation \(\ref{iblAllEquations}\). +In ⟨L:2272⟩fact, it's the same, except without the Fresnel term.

-By making the further assumption that \(f_{90} = 1\), we can rewrite \(DFG_1\) and \(DFG_2\) and the -\(\Lout\) reconstruction: +By ⟨L:2274⟩making the further assumption that \(f_{90} = 1\), we can rewrite \(DFG_1\) and \(DFG_2\) and the +\(\Lout\) ⟨L:2275⟩reconstruction:

$$ -\begin{align*} -\Lout(n,v,\alpha,f_0) &\simeq \big[ (1 - f_0) \color{red}{DFG_1^{multiscatter}(\NoV, \alpha)} + f_0 \color{red}{DFG_2^{multiscatter}(\NoV, \alpha)} \big] \times LD(n, \alpha) \\ -DFG_1^{multiscatter}(\alpha, \left<\NoV\right>) &= \frac{4}{N}\sum_i^N \color{green}{F_c(\left<\VoH\right>)} V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ -DFG_2^{multiscatter}(\alpha, \left<\NoV\right>) &= \frac{4}{N}\sum_i^N V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ -LD(n, \alpha) &= \frac{\sum_i^N V(l_i, n, \alpha)\left<\NoL\right>\Lt(l_i) }{\sum_i^N V(l_i, n, \alpha)\left<\NoL\right>} -\end{align*} +\begin{align*}⟨L:2278⟩ +\Lout(n,v,\alpha,f_0) ⟨L:2279⟩&\simeq \big[ (1 - f_0) \color{red}{DFG_1^{multiscatter}(\NoV, \alpha)} + f_0 \color{red}{DFG_2^{multiscatter}(\NoV, \alpha)} \big] \times LD(n, \alpha) \\ +DFG_1^{multiscatter}(\alpha, ⟨L:2280⟩\left<\NoV\right>) &= \frac{4}{N}\sum_i^N \color{green}{F_c(\left<\VoH\right>)} V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ +DFG_2^{multiscatter}(\alpha, ⟨L:2281⟩\left<\NoV\right>) &= \frac{4}{N}\sum_i^N V(l_i, v, \alpha)\frac{\left<\VoH\right>}{\left<\NoH\right>} \left<\NoL\right> \\ +LD(n, ⟨L:2282⟩\alpha) &= \frac{\sum_i^N V(l_i, n, \alpha)\left<\NoL\right>\Lt(l_i) }{\sum_i^N V(l_i, n, \alpha)\left<\NoL\right>} +\end{align*} ⟨L:2283⟩ $$

-These two new \(DFG\) terms simply need to replace the ones used in the implementation shown in section 9.5: +These ⟨L:2286⟩two new \(DFG\) terms simply need to replace the ones used in the implementation shown in section 9.5:

 
float Fc = pow(1 - VoH, 5.0f);
 r.x += Gv * Fc;
 r.y += Gv;
Listing 29: C++ implementation of the \(L_{DFG}\) term for multiscattering

-

To perform the reconstruction we need to slightly modify listing 30:

+

To ⟨L:2295⟩perform the reconstruction we need to slightly modify listing 30:

 
vec2 dfg = textureLod(dfgLut, vec2(dot(n, v), perceptualRoughness), 0.0).xy;
 // (1 - f0) * dfg.x + f0 * dfg.y
 vec3 specularColor = mix(dfg.xxx, dfg.yyy, f0);
Listing 30: GLSL implementation of image based lighting evaluation, with multiscattering LUT
-   

Summary

+
   

⟨L:2305⟩Summary

-

In order to calculate the specular contribution of distant image-based lights, we had to make a few -approximations and compromises:

+

In ⟨L:2307⟩order to calculate the specular contribution of distant image-based lights, we had to make a few +approximations ⟨L:2308⟩and compromises:

  • \(v = n\), by far the assumption contributing to the largest error when integrating the - non-constant part of the IBL. This results in the complete loss of roughness anisotropy - with respect to the view point. + non-constant ⟨L:2311⟩part of the IBL. This results in the complete loss of roughness anisotropy + with ⟨L:2312⟩respect to the view point.

  • Roughness contribution for the non-constant part of the IBL is quantized and trilinear filtering - is used to interpolate between these levels. This is most visible at low roughnes (e.g.: around 0.0625 - for a 9 LODs cubemap). + is ⟨L:2315⟩used to interpolate between these levels. This is most visible at low roughnes (e.g.: around 0.0625 + for ⟨L:2316⟩a 9 LODs cubemap).

  • Because mipmap levels are used to store the pre-integrated environment, they can't be used for - texture minification, as they ought to. This can causes aliasing or moiré artifacts in high frequency - regions or the environment at low roughness and/or distant or small objects. - This can also impact performance due to the resulting poor cache access pattern. + texture ⟨L:2319⟩minification, as they ought to. This can causes aliasing or moiré artifacts in high frequency + regions ⟨L:2320⟩or the environment at low roughness and/or distant or small objects. + This ⟨L:2321⟩can also impact performance due to the resulting poor cache access pattern.

  • No Fresnel for the non-constant part of the IBL. @@ -2500,68 +2514,90 @@ approximations and compromises:

  • \(f_{90} = 1\) in the multiscattering case.

-

 
Figure 54: -
+

-

 
Figure 55: -
+
Figure ⟨L:2332⟩[iblPrefilterVsImportanceSampling]: +Comparison ⟨L:2333⟩between importance-sampled reference (top) and prefiltered IBL (middle).

-

 
Figure 56: -
+

-

 
Figure 57: -
+
Figure ⟨L:2335⟩[iblStretchyReflectionLoss]: +Error ⟨L:2336⟩in reflections due to assuming \(v = n\) (bottom) — loss of “stretchy reflections”.

-

 
Figure 58: -
+
+

+

Figure ⟨L:2338⟩[iblRoughnessInLods0]: +Error ⟨L:2339⟩due to storing the roughness in cubemaps LODs at roughness = 0.0625 (i.e.: sampling exactly between levels). +Notice ⟨L:2340⟩how instead of bluring we see a “cross-fade” between two blurs.
+

+

+

+

Figure ⟨L:2342⟩[iblRoughnessInLods1]: +Error ⟨L:2343⟩due to storing the roughness in cubemaps LODs at roughness = 0.125 (i.e.: sampling exactly level 1). +When ⟨L:2344⟩the roughness closely matches a LOD, the error due to trilinear filtering in the cubemap is +reduced. ⟨L:2345⟩Notice the errors due to \(v = n\) at grazing angles.
+

+

+

+

Figure ⟨L:2347⟩[iblMoirePattern]: +Moiré ⟨L:2348⟩pattern due to texture minification on a metallic sphere at \(\alpha = 0\) +using ⟨L:2349⟩an environment made of colored vertical stripes (skybox hidden).
+

+

-   

Clear coat

+
   

⟨L:2352⟩Clear coat

-

When sampling the IBL, the clear coat layer is calculated as a second specular lobe. This specular lobe is oriented along the view direction since we cannot reasonably integrate over the hemisphere. Listing 31 demonstrates this approximation in practice. It also shows the energy conservation step. It is important to note that this second specular lobe is computed exactly the same way as the main specular lobe, using the same DFG approximation.

+

When ⟨L:2354⟩sampling the IBL, the clear coat layer is calculated as a second specular lobe. This specular lobe is oriented along the view direction since we cannot reasonably integrate over the hemisphere. Listing 31 demonstrates this approximation in practice. It also shows the energy conservation step. It is important to note that this second specular lobe is computed exactly the same way as the main specular lobe, using the same DFG approximation.

 
// clearCoat_NoV == shading_NoV if the clear coat layer doesn't have its own normal map
 float Fc = F_Schlick(0.04, 1.0, clearCoat_NoV) * clearCoat;
 // base layer attenuation for energy compensation
 iblDiffuse  *= 1.0 - Fc;
 iblSpecular *= sq(1.0 - Fc);
 iblSpecular += specularIBL(r, clearCoatPerceptualRoughness) * Fc;
Listing 31: GLSL implementation of the clear coat specular lobe for image-based lighting
-   

Anisotropy

+
   

⟨L:2366⟩Anisotropy

-

[McAuley15] describes a technique called “bent reflection vector”, based [Revie12]. The bent reflection vector is a rough approximation of anisotropic lighting but the alternative is to use importance sampling. This approximation is sufficiently cheap to compute and provides good results, as shown in figure 59 and figure 60.

+

[McAuley15] ⟨L:2368⟩describes a technique called “bent reflection vector”, based [Revie12]. The bent reflection vector is a rough approximation of anisotropic lighting but the alternative is to use importance sampling. This approximation is sufficiently cheap to compute and provides good results, as shown in figure ? and figure ?.

-

 
Figure 59: Anisotropic indirect specular reflections using bent normals (left: roughness 0.3, right: roughness: 0.0; both: anisotropy 1.0)
+

-

 
Figure 60: Anisotropic reflections with varying roughness, metallicness, etc.
+
Figure ⟨L:2370⟩[anisotropicIBL1]: Anisotropic indirect specular reflections using bent normals (left: roughness 0.3, right: roughness: 0.0; both: anisotropy 1.0)

-The implementation of this technique is straightforward, as demonstrated in listing 32. +

+

+

Figure ⟨L:2372⟩[anisotropicIBL2]: Anisotropic reflections with varying roughness, metallicness, etc.
+

+

+

+The ⟨L:2374⟩implementation of this technique is straightforward, as demonstrated in listing 32.

 
vec3 anisotropicTangent = cross(bitangent, v);
 vec3 anisotropicNormal = cross(anisotropicTangent, bitangent);
 vec3 bentNormal = normalize(mix(n, anisotropicNormal, anisotropy));
 vec3 r = reflect(-v, bentNormal);
Listing 32: GLSL implementation of the bent reflection vector

-

This technique can be made more useful by accepting negative anisotropy values, as shown in listing 33. When the anisotropy is negative, the highlights are not in the direction of the tangent, but in the direction of the bitangent instead.

+

This ⟨L:2384⟩technique can be made more useful by accepting negative anisotropy values, as shown in listing 33. When the anisotropy is negative, the highlights are not in the direction of the tangent, but in the direction of the bitangent instead.

 
vec3 anisotropicDirection = anisotropy >= 0.0 ? bitangent : tangent;
 vec3 anisotropicTangent = cross(anisotropicDirection, v);
 vec3 anisotropicNormal = cross(anisotropicTangent, anisotropicDirection);
 vec3 bentNormal = normalize(mix(n, anisotropicNormal, anisotropy));
 vec3 r = reflect(-v, bentNormal);
Listing 33: GLSL implementation of the bent reflection vector

-

Figure 61 demonstrates this modified implementation in practice.

+

Figure ⟨L:2395⟩[anisotropicDirection] demonstrates this modified implementation in practice.

-

 
Figure 61: Control of the anisotropy direction using positive (left) and negative (right) values
+
Figure ⟨L:2397⟩[anisotropicDirection]: Control of the anisotropy direction using positive (left) and negative (right) values

-   

Subsurface

+
   

⟨L:2399⟩Subsurface

-

[TODO] Explain subsurface and IBL

+

[TODO] ⟨L:2401⟩Explain subsurface and IBL

-   

Cloth

+
   

⟨L:2403⟩Cloth

-

The IBL implementation for the cloth material model is more complicated than for the other material models. The main difference stems from the use of a different NDF (“Charlie” vs height-correlated Smith GGX). As described in this section, we use the split-sum approximation to compute the DFG term of the BRDF when computing an IBL. This DFG term is designed for a different BRDF and cannot be used for the cloth BRDF. Since we designed our cloth BRDF to not need a Fresnel term, we can generate a single DG term in the 3rd channel of the DFG LUT. The result is shown in figure 62.

+

The ⟨L:2405⟩IBL implementation for the cloth material model is more complicated than for the other material models. The main difference stems from the use of a different NDF (“Charlie” vs height-correlated Smith GGX). As described in this section, we use the split-sum approximation to compute the DFG term of the BRDF when computing an IBL. This DFG term is designed for a different BRDF and cannot be used for the cloth BRDF. Since we designed our cloth BRDF to not need a Fresnel term, we can generate a single DG term in the 3rd channel of the DFG LUT. The result is shown in figure ?.

-The DG term is generated using uniform sampling as recommended in [Estevez17]. With uniform sampling the \(pdf\) is simply \(\frac{1}{2\pi}\) and we must still use the Jacobian \(\frac{1}{4\left< \VoH \right>}\). +The ⟨L:2407⟩DG term is generated using uniform sampling as recommended in [Estevez17]. With uniform sampling the \(pdf\) is simply \(\frac{1}{2\pi}\) and we must still use the Jacobian \(\frac{1}{4\left< \VoH \right>}\).

-

 
Figure 62: DFG LUT with a 3rd channel encoding the DG term of the cloth BRDF
+
Figure ⟨L:2409⟩[dfgClothLUT]: DFG LUT with a 3rd channel encoding the DG term of the cloth BRDF

-The remainder of the image-based lighting implementation follows the same steps as the implementation of regular lights, including the optional subsurface scattering term and its wrap diffuse component. Just as with the clear coat IBL implementation, we cannot integrate over the hemisphere and use the view direction as the dominant light direction to compute the wrap diffuse component. +The ⟨L:2411⟩remainder of the image-based lighting implementation follows the same steps as the implementation of regular lights, including the optional subsurface scattering term and its wrap diffuse component. Just as with the clear coat IBL implementation, we cannot integrate over the hemisphere and use the view direction as the dominant light direction to compute the wrap diffuse component.

 
float diffuse = Fd_Lambert() * ambientOcclusion;
 #if defined(SHADING_MODEL_CLOTH)
 #if defined(MATERIAL_HAS_SUBSURFACE_COLOR)
@@ -2576,39 +2612,45 @@ The remainder of the image-based lighting implementation follows the same steps
 
 vec3 ibl = diffuseColor * indirectDiffuse + indirectSpecular * specularColor;
Listing 34: GLSL implementation of the DFG approximation for the cloth NDF

-

It is important to note that this only addresses part of the IBL problem. The pre-filtered specular environment maps described earlier are convolved with the standard shading model's BRDF, which differs from the cloth BRDF. To get accurate result we should in theory provide one set of IBLs per BRDF used in the engine. Providing a second set of IBLs is however not practical for our use case so we decided to rely on the existing IBLs instead.

+

It ⟨L:2430⟩is important to note that this only addresses part of the IBL problem. The pre-filtered specular environment maps described earlier are convolved with the standard shading model's BRDF, which differs from the cloth BRDF. To get accurate result we should in theory provide one set of IBLs per BRDF used in the engine. Providing a second set of IBLs is however not practical for our use case so we decided to rely on the existing IBLs instead.

-   

Static lighting

+
   

⟨L:2432⟩Static lighting

-

[TODO] Spherical-harmonics or spherical-gaussian lightmaps, irradiance volumes, PRT?…

+

[TODO] ⟨L:2434⟩Spherical-harmonics or spherical-gaussian lightmaps, irradiance volumes, PRT?…

-   

Transparency and translucency lighting

+
   

⟨L:2436⟩Transparency and translucency lighting

-

Transparent and translucent materials are important to add realism and correctness to scenes. Filament must therefore provide lighting models for both types of materials to allow artists to properly recreate realistic scenes. Translucency can also be used effectively in a number of non-realistic settings.

+

Transparent ⟨L:2438⟩and translucent materials are important to add realism and correctness to scenes. Filament must therefore provide lighting models for both types of materials to allow artists to properly recreate realistic scenes. Translucency can also be used effectively in a number of non-realistic settings.

-   

Transparency

+
   

⟨L:2440⟩Transparency

-

To properly light a transparent surface, we must first understand how the material's opacity is applied. Observe a window and you will see that the diffuse reflectance is transparent. On the other hand, the brighter the specular reflectance, the less opaque the window appears. This effect can be seen in figure 63: the scene is properly reflected onto the glass surfaces but the specular highlight of the sun is bright enough to appear opaque.

+

To ⟨L:2442⟩properly light a transparent surface, we must first understand how the material's opacity is applied. Observe a window and you will see that the diffuse reflectance is transparent. On the other hand, the brighter the specular reflectance, the less opaque the window appears. This effect can be seen in figure ?: the scene is properly reflected onto the glass surfaces but the specular highlight of the sun is bright enough to appear opaque.

-

 
Figure 63: Example of a complex object where lit surface transparency plays an important role
+

-

 
Figure 64: Example of a complex object where lit surface transparency plays an important role
+
Figure ⟨L:2444⟩[cameraTransparency]: Example of a complex object where lit surface transparency plays an important role

-To properly implement opacity, we will use the premultiplied alpha format. Given a desired opacity noted \( \alpha_{opacity} \) and a diffuse color \( \sigma \) (linear, unpremultiplied), we can compute the effective opacity of a fragment. +

-$$\begin{align*} -color &= \sigma * \alpha_{opacity} \\ -opacity &= \alpha_{opacity} -\end{align*}$$ +

Figure ⟨L:2446⟩[litCar]: Example of a complex object where lit surface transparency plays an important role

-The physical interpretation is that the RGB components of the source color define how much light is emitted by the pixel, whereas the alpha component defines how much of the light behind the pixel is blocked by said pixel. We must therefore use the following blending functions: +

-$$\begin{align*} -Blend_{src} &= 1 \\ -Blend_{dst} &= 1 - src_{\alpha} -\end{align*}$$ +To ⟨L:2448⟩properly implement opacity, we will use the premultiplied alpha format. Given a desired opacity noted \( \alpha_{opacity} \) and a diffuse color \( \sigma \) (linear, unpremultiplied), we can compute the effective opacity of a fragment.

-The GLSL implementation of these equations is presented in listing 35. +$$\begin{align*}⟨L:2450⟩ +color ⟨L:2451⟩&= \sigma * \alpha_{opacity} \\ +opacity ⟨L:2452⟩&= \alpha_{opacity} +\end{align*}$$⟨L:2453⟩ +

+The ⟨L:2455⟩physical interpretation is that the RGB components of the source color define how much light is emitted by the pixel, whereas the alpha component defines how much of the light behind the pixel is blocked by said pixel. We must therefore use the following blending functions: +

+$$\begin{align*}⟨L:2457⟩ +Blend_{src} ⟨L:2458⟩&= 1 \\ +Blend_{dst} ⟨L:2459⟩&= 1 - src_{\alpha} +\end{align*}$$⟨L:2460⟩ +

+The ⟨L:2462⟩GLSL implementation of these equations is presented in listing 35.

 
// baseColor has already been premultiplied
 vec4 shadeSurface(vec4 baseColor) {
     float alpha = baseColor.a;
@@ -2618,87 +2660,87 @@ The GLSL implementation of these equations is presented in 
     return vec4(diffuseColor + specularColor, alpha);
 }
-   

Translucency

+
   

⟨L:2477⟩Translucency

-

Translucent materials can be divided into two categories:

+

Translucent ⟨L:2479⟩materials can be divided into two categories:

  • Surface translucency
  • Volume translucency

-Volume translucency is useful to light particle systems, for instance clouds or smoke. Surface translucency can be used to imitate materials with transmitted scattering such as wax, marble, skin, etc. +Volume ⟨L:2483⟩translucency is useful to light particle systems, for instance clouds or smoke. Surface translucency can be used to imitate materials with transmitted scattering such as wax, marble, skin, etc.

-[TODO] Surface translucency (BRDF+BTDF, BSSRDF) +[TODO] ⟨L:2485⟩Surface translucency (BRDF+BTDF, BSSRDF)

-

 
Figure 65: Front-lit translucent object (left) and back-lit translucent object (right), using approximated BTDF and BSSRDF. Model: Lucy from the Stanford University Computer Graphics Laboratory
+
Figure ⟨L:2487⟩[translucency]: Front-lit translucent object (left) and back-lit translucent object (right), using approximated BTDF and BSSRDF. Model: Lucy from the Stanford University Computer Graphics Laboratory

-   

Occlusion

+
   

⟨L:2489⟩Occlusion

-

Occlusion is an important darkening factor used to recreate shadowing at various scales:

+

Occlusion ⟨L:2491⟩is an important darkening factor used to recreate shadowing at various scales:

-

Small scale

Micro-occlusion used to handle creases, cracks and cavities. -

Medium scale

Macro-occlusion used to handle occlusion by an object's own geometry or by geometry baked in normal maps (bricks, etc.). -

Large scale

Occlusion coming from contact between objects, or from an object's own geometry. -

We currently ignore micro-occlusion, which is often exposed in tools and engines under the form of a “cavity map”. Sébastien Lagarde offers an interesting discussion in [Lagarde14] on how micro-occlusion is handled in Frostbite: diffuse micro-occlusion is pre-baked in diffuse maps and specular micro-occlusion is pre-baked in reflectance textures. -In our system, micro-occlusion can simply be baked in the base color map. This must be done knowing that the specular light will not be affected by micro-occlusion. +
 Small scale⟨L:2493⟩

⟨L:2494⟩Micro-occlusion used to handle creases, cracks and cavities. +

 Medium scale⟨L:2496⟩

⟨L:2497⟩Macro-occlusion used to handle occlusion by an object's own geometry or by geometry baked in normal maps (bricks, etc.). +

 Large scale⟨L:2499⟩

⟨L:2500⟩Occlusion coming from contact between objects, or from an object's own geometry. +

We ⟨L:2502⟩currently ignore micro-occlusion, which is often exposed in tools and engines under the form of a “cavity map”. Sébastien Lagarde offers an interesting discussion in [Lagarde14] on how micro-occlusion is handled in Frostbite: diffuse micro-occlusion is pre-baked in diffuse maps and specular micro-occlusion is pre-baked in reflectance textures. +In ⟨L:2503⟩our system, micro-occlusion can simply be baked in the base color map. This must be done knowing that the specular light will not be affected by micro-occlusion.

-Medium scale ambient occlusion is pre-baked in ambient occlusion maps, exposed as a material parameter, as seen in the material parameterization section earlier. +Medium ⟨L:2505⟩scale ambient occlusion is pre-baked in ambient occlusion maps, exposed as a material parameter, as seen in the material parameterization section earlier.

-Large scale ambient occlusion is often computed using screen-space techniques such as SSAO (screen-space ambient occlusion), HBAO (horizon based ambient occlusion), etc. Note that these techniques can also contribute to medium scale ambient occlusion when the camera is close enough to surfaces. +Large ⟨L:2507⟩scale ambient occlusion is often computed using screen-space techniques such as SSAO (screen-space ambient occlusion), HBAO (horizon based ambient occlusion), etc. Note that these techniques can also contribute to medium scale ambient occlusion when the camera is close enough to surfaces.

-Note: to prevent over darkening when using both medium and large scale occlusion, Lagarde recommends to use \(min({AO}_{medium}, {AO}_{large})\). +Note: ⟨L:2509⟩to prevent over darkening when using both medium and large scale occlusion, Lagarde recommends to use \(min({AO}_{medium}, {AO}_{large})\).

-   

Diffuse occlusion

+
   

⟨L:2511⟩Diffuse occlusion

-

Morgan McGuire formalizes ambient occlusion in the context of physically based rendering in [McGuire10]. In his formulation, McGuire defines an ambient illumination function ( L_a ), which in our case is encoded with spherical harmonics. He also defines a visibility function (V), with (V(l)=1) if there is an unoccluded line of sight from the surface in direction (l), and 0 otherwise.

+

Morgan ⟨L:2513⟩McGuire formalizes ambient occlusion in the context of physically based rendering in [McGuire10]. In his formulation, McGuire defines an ambient illumination function ( L_a ), which in our case is encoded with spherical harmonics. He also defines a visibility function (V), with (V(l)=1) if there is an unoccluded line of sight from the surface in direction (l), and 0 otherwise.

-With these two functions, the ambient term of the rendering equation can be expressed as shown in equation \(\ref{diffuseAO}\). +With ⟨L:2515⟩these two functions, the ambient term of the rendering equation can be expressed as shown in equation \(\ref{diffuseAO}\).

-$$\begin{equation}\label{diffuseAO} -L(l,v) = \int_{\Omega} f(l,v) L_a(l) V(l) \left< \NoL \right> dl -\end{equation}$$ +$$\begin{equation}\label{diffuseAO}⟨L:2517⟩ +L(l,v) ⟨L:2518⟩= \int_{\Omega} f(l,v) L_a(l) V(l) \left< \NoL \right> dl +\end{equation}$$⟨L:2519⟩

-This expression can be approximated by separating the visibility term from the illumination function, as shown in equation \(\ref{diffuseAOApprox}\). +This ⟨L:2521⟩expression can be approximated by separating the visibility term from the illumination function, as shown in equation \(\ref{diffuseAOApprox}\).

-$$\begin{equation}\label{diffuseAOApprox} -L(l,v) \approx \left( \pi \int_{\Omega} f(l,v) L_a(l) dl \right) \left( \frac{1}{\pi} \int_{\Omega} V(l) \left< \NoL \right> dl \right) -\end{equation}$$ +$$\begin{equation}\label{diffuseAOApprox}⟨L:2523⟩ +L(l,v) ⟨L:2524⟩\approx \left( \pi \int_{\Omega} f(l,v) L_a(l) dl \right) \left( \frac{1}{\pi} \int_{\Omega} V(l) \left< \NoL \right> dl \right) +\end{equation}$$⟨L:2525⟩

-This approximation is only exact when the distant light \( L_a \) is constant and \(f\) is a Lambertian term. McGuire states however that this approximation is reasonable if both functions are relatively smooth over most of the sphere. This happens to be the case with a distant light probe (IBL). +This ⟨L:2527⟩approximation is only exact when the distant light \( L_a \) is constant and \(f\) is a Lambertian term. McGuire states however that this approximation is reasonable if both functions are relatively smooth over most of the sphere. This happens to be the case with a distant light probe (IBL).

-The left term of this approximation is the pre-computed diffuse component of our IBL. The right term is a scalar factor between 0 and 1 that indicates the fractional accessibility of a point. Its opposite is the diffuse ambient occlusion term, show in equation \(\ref{diffuseAOTerm}\). +The ⟨L:2529⟩left term of this approximation is the pre-computed diffuse component of our IBL. The right term is a scalar factor between 0 and 1 that indicates the fractional accessibility of a point. Its opposite is the diffuse ambient occlusion term, show in equation \(\ref{diffuseAOTerm}\).

-$$\begin{equation}\label{diffuseAOTerm} -{AO} = 1 - \frac{1}{\pi} \int_{\Omega} V(l) \left< \NoL \right> dl -\end{equation}$$ +$$\begin{equation}\label{diffuseAOTerm}⟨L:2531⟩ +{AO} ⟨L:2532⟩= 1 - \frac{1}{\pi} \int_{\Omega} V(l) \left< \NoL \right> dl +\end{equation}$$⟨L:2533⟩

-Since we use a pre-computed diffuse term, we cannot compute the exact accessibility of shaded points at runtime. To compensate for this lack of information in our precomputed term, we partially reconstruct incident lighting by applying an ambient occlusion factor specific to the surface's material at the shaded point. +Since ⟨L:2535⟩we use a pre-computed diffuse term, we cannot compute the exact accessibility of shaded points at runtime. To compensate for this lack of information in our precomputed term, we partially reconstruct incident lighting by applying an ambient occlusion factor specific to the surface's material at the shaded point.

-In practice, baked ambient occlusion is stored as a grayscale texture which can often be lower resolution than other textures (base color or normals for instance). It is important to note that the ambient occlusion property of our material model intends to recreate macro-level diffuse ambient occlusion. While this approximation is not physically correct, it constitutes an acceptable tradeoff of quality vs performance. +In ⟨L:2537⟩practice, baked ambient occlusion is stored as a grayscale texture which can often be lower resolution than other textures (base color or normals for instance). It is important to note that the ambient occlusion property of our material model intends to recreate macro-level diffuse ambient occlusion. While this approximation is not physically correct, it constitutes an acceptable tradeoff of quality vs performance.

-Figure 66 shows two different materials without and with diffuse ambient occlusion. Notice how the material ambient occlusion is used to recreate the natural shadowing that occurs between the different tiles. Without ambient occlusion, both materials appear too flat. +Figure ⟨L:2539⟩[aoComparison] shows two different materials without and with diffuse ambient occlusion. Notice how the material ambient occlusion is used to recreate the natural shadowing that occurs between the different tiles. Without ambient occlusion, both materials appear too flat.

-

 
Figure 66: Comparison of materials without diffuse ambient occlusion (left) and with (right)
+
Figure ⟨L:2541⟩[aoComparison]: Comparison of materials without diffuse ambient occlusion (left) and with (right)

-Applying baked diffuse ambient occlusion in a GLSL shader is straightforward, as shown in listing 36. +Applying ⟨L:2543⟩baked diffuse ambient occlusion in a GLSL shader is straightforward, as shown in listing 36.

 
// diffuse indirect
 vec3 indirectDiffuse = max(irradianceSH(n), 0.0) * Fd_Lambert();
 // ambient occlusion
 indirectDiffuse *= texture2D(aoMap, outUV).r;
Listing 36: Implementation of baked diffuse ambient occlusion in GLSL

-

Note how the ambient occlusion term is only applied to indirect lighting.

+

Note ⟨L:2553⟩how the ambient occlusion term is only applied to indirect lighting.

-   

Specular occlusion

+
   

⟨L:2555⟩Specular occlusion

-

Specular micro-occlusion can be derived from (\fNormal), itself derived from the diffuse color. The derivation is based on the knowledge that no real-world material has a reflectance lower than 2%. Values in the 0-2% range can therefore be treated as pre-baked specular occlusion used to smoothly extinguish the Fresnel term.

+

Specular ⟨L:2557⟩micro-occlusion can be derived from (\fNormal), itself derived from the diffuse color. The derivation is based on the knowledge that no real-world material has a reflectance lower than 2%. Values in the 0-2% range can therefore be treated as pre-baked specular occlusion used to smoothly extinguish the Fresnel term.

 
float f90 = clamp(dot(f0, 50.0 * 0.33), 0.0, 1.0);
 // cheap luminance approximation
 float f90 = clamp(50.0 * f0.g, 0.0, 1.0);
Listing 37: Pre-baked specular occlusion in GLSL

-

The derivations mentioned earlier for ambient occlusion assume Lambertian surfaces and are only valid for indirect diffuse lighting. The lack of information about surface accessibility is particularly harmful to the reconstruction of indirect specular lighting. It usually manifests itself as light leaks.

+

The ⟨L:2566⟩derivations mentioned earlier for ambient occlusion assume Lambertian surfaces and are only valid for indirect diffuse lighting. The lack of information about surface accessibility is particularly harmful to the reconstruction of indirect specular lighting. It usually manifests itself as light leaks.

-Sébastien Lagarde proposes an empirical approach to derive the specular occlusion term from the diffuse occlusion term in [Lagarde14]. The result does not have any physical basis but produces visually pleasant results. The goal of his formulation is return the diffuse occlusion term unmodified for rough surfaces. For smooth surfaces, the formulation, implemented in listing 38, reduces the influence of occlusion at normal incidence and increases it at grazing angles. +Sébastien ⟨L:2568⟩Lagarde proposes an empirical approach to derive the specular occlusion term from the diffuse occlusion term in [Lagarde14]. The result does not have any physical basis but produces visually pleasant results. The goal of his formulation is return the diffuse occlusion term unmodified for rough surfaces. For smooth surfaces, the formulation, implemented in listing 38, reduces the influence of occlusion at normal incidence and increases it at grazing angles.

 
float computeSpecularAO(float NoV, float ao, float roughness) {
     return clamp(pow(NoV + ao, exp2(-16.0 * roughness - 1.0)) - 1.0 + ao, 0.0, 1.0);
 }
@@ -2709,13 +2751,13 @@ Sébastien Lagarde proposes an empirical approach to derive the specular occlusi
 float ao = texture2D(aoMap, outUV).r;
 indirectSpecular *= computeSpecularAO(NoV, ao, roughness);
Listing 38: Implementation of Lagarde's specular occlusion factor in GLSL

-

Note how the specular occlusion factor is only applied to indirect lighting.

+

Note ⟨L:2583⟩how the specular occlusion factor is only applied to indirect lighting.

-   

Horizon specular occlusion

+
   

⟨L:2585⟩Horizon specular occlusion

-

When computing the specular IBL contribution for a surface that uses a normal map, it is possible to end up with a reflection vector pointing towards the surface. If this reflection vector is used for shading directly, the surface will be lit in places where it should not be lit (assuming opaque surfaces). This is another occurrence of light leaking that can easily be minimized using a simple technique described by Jeff Russell [Russell15].

+

When ⟨L:2587⟩computing the specular IBL contribution for a surface that uses a normal map, it is possible to end up with a reflection vector pointing towards the surface. If this reflection vector is used for shading directly, the surface will be lit in places where it should not be lit (assuming opaque surfaces). This is another occurrence of light leaking that can easily be minimized using a simple technique described by Jeff Russell [Russell15].

-The key idea is to occlude light coming from behind the surface. This can easily be achieved since a negative dot product between the reflected vector and the surface's normal indicates a reflection vector pointing towards the surface. Our implementation shown in listing 39 is similar to Russell's, albeit without the artist controlled horizon fading factor. +The ⟨L:2589⟩key idea is to occlude light coming from behind the surface. This can easily be achieved since a negative dot product between the reflected vector and the surface's normal indicates a reflection vector pointing towards the surface. Our implementation shown in listing 39 is similar to Russell's, albeit without the artist controlled horizon fading factor.

 
// specular indirect
 vec3 indirectSpecular = evaluateSpecularIBL(r, perceptualRoughness);
 
@@ -2723,70 +2765,82 @@ The key idea is to occlude light coming from behind the surface. This can easily
 float horizon = min(1.0 + dot(r, n), 1.0);
 indirectSpecular *= horizon * horizon;
Listing 39: Implementation of horizon specular occlusion in GLSL

-

Horizon specular occlusion fading is cheap but can easily be omitted to improve performance as needed.

+

Horizon ⟨L:2601⟩specular occlusion fading is cheap but can easily be omitted to improve performance as needed.

-   

Normal mapping

+
   

⟨L:2603⟩Normal mapping

-

There are two common use cases of normal maps: replacing high-poly meshes with low-poly meshes (using a base map) and adding surface details (using a detail map).

+

There ⟨L:2605⟩are two common use cases of normal maps: replacing high-poly meshes with low-poly meshes (using a base map) and adding surface details (using a detail map).

-Let's imagine that we want to render a piece of furniture covered in tufted leather. Modeling the geometry to accurately represent the tufted pattern would require too many triangles so we instead bake a high-poly mesh into a normal map. Once the base map is applied to a simplified mesh (in this case, a quad), we get the result in figure 67. The base map used to create this effect is shown in figure 68. +Let's ⟨L:2607⟩imagine that we want to render a piece of furniture covered in tufted leather. Modeling the geometry to accurately represent the tufted pattern would require too many triangles so we instead bake a high-poly mesh into a normal map. Once the base map is applied to a simplified mesh (in this case, a quad), we get the result in figure ?. The base map used to create this effect is shown in figure ?.

-

 
Figure 67: Low-poly mesh without normal mapping (left) and with (right)
+

-

 
Figure 68: Normal map used as a base map
+
Figure ⟨L:2609⟩[normalMapped]: Low-poly mesh without normal mapping (left) and with (right)

-A simple problem arises if we now want to combine this base map with a second normal map. For instance, let's use the detail map shown in figure 69 to add cracks in the leather. +

-

 
Figure 69: Normal map used as a detail map
+
Figure ⟨L:2611⟩[baseNormalMap]: Normal map used as a base map

-Given the nature of normal maps (XYZ components stored in tangent space), it is fairly obvious that naive approaches such as linear or overlay blending cannot work. We will use two more advanced techniques: a mathematically correct one and an approximation suitable for real-time shading. +

+

+A ⟨L:2613⟩simple problem arises if we now want to combine this base map with a second normal map. For instance, let's use the detail map shown in figure ? to add cracks in the leather. +

+

Figure ⟨L:2615⟩[detailNormalMap]: Normal map used as a detail map
+

+Given ⟨L:2617⟩the nature of normal maps (XYZ components stored in tangent space), it is fairly obvious that naive approaches such as linear or overlay blending cannot work. We will use two more advanced techniques: a mathematically correct one and an approximation suitable for real-time shading.

-   

Reoriented normal mapping

+
   

⟨L:2619⟩Reoriented normal mapping

-

Colin Barré-Brisebois and Stephen Hill propose in [Hill12] a mathematically sound solution called Reoriented Normal Mapping, which consists in rotating the basis of the detail map onto the normal from the base map. This technique relies on the shortest arc quaternion to apply the rotation, which greatly simplifies thanks to the properties of the tangent space.

+

Colin ⟨L:2621⟩Barré-Brisebois and Stephen Hill propose in [Hill12] a mathematically sound solution called Reoriented Normal Mapping, which consists in rotating the basis of the detail map onto the normal from the base map. This technique relies on the shortest arc quaternion to apply the rotation, which greatly simplifies thanks to the properties of the tangent space.

-Following the simplifications described in [Hill12], we can produce the GLSL implementation shown in listing 40. +Following ⟨L:2623⟩the simplifications described in [Hill12], we can produce the GLSL implementation shown in listing 40.

 
vec3 t = texture(baseMap,   uv).xyz * vec3( 2.0,  2.0, 2.0) + vec3(-1.0, -1.0,  0.0);
 vec3 u = texture(detailMap, uv).xyz * vec3(-2.0, -2.0, 2.0) + vec3( 1.0,  1.0, -1.0);
 vec3 r = normalize(t * dot(t, u) - u * t.z);
 return r;
Listing 40: Implementation of reoriented normal mapping in GLSL

-

Note that this implementation assumes that the normals are stored uncompressed and in the [0..1] range in the source textures.

+

Note ⟨L:2633⟩that this implementation assumes that the normals are stored uncompressed and in the [0..1] range in the source textures.

-The normalization step is not strictly necessary and can be skipped if the technique is used at runtime. If so, the computation of r becomes t * dot(t, u) / t.z - u. +The ⟨L:2635⟩normalization step is not strictly necessary and can be skipped if the technique is used at runtime. If so, the computation of r becomes t * dot(t, u) / t.z - u.

-Since this technique is slightly more expensive than the one described below, we will mostly use it offline. We therefore provide a simple offline tool to combine two normal maps. Figure 70 presents the output of the tool with the base map and the detail map shown previously. +Since ⟨L:2637⟩this technique is slightly more expensive than the one described below, we will mostly use it offline. We therefore provide a simple offline tool to combine two normal maps. Figure ? presents the output of the tool with the base map and the detail map shown previously.

-

 
Figure 70: Blended normal and detail map (left) and resulting render when combined with a diffuse map (right)
+
Figure ⟨L:2639⟩[blendedNormalMaps]: Blended normal and detail map (left) and resulting render when combined with a diffuse map (right)

-   

UDN blending

+
   

⟨L:2641⟩UDN blending

-

The technique called UDN blending, described in [Hill12], is a variant of the partial derivative blending technique. Its main advantage is the low number of shader instructions it requires (see listing 41). While it leads to a reduction in details over flat areas, UDN blending is interesting if blending must be performed at runtime.

+

The ⟨L:2643⟩technique called UDN blending, described in [Hill12], is a variant of the partial derivative blending technique. Its main advantage is the low number of shader instructions it requires (see listing 41). While it leads to a reduction in details over flat areas, UDN blending is interesting if blending must be performed at runtime.

 
vec3 t = texture(baseMap,   uv).xyz * 2.0 - 1.0;
 vec3 u = texture(detailMap, uv).xyz * 2.0 - 1.0;
 vec3 r = normalize(t.xy + u.xy, t.z);
 return r;
Listing 41: Implementation of UDN blending in GLSL

-

The results are visually close to Reoriented Normal Mapping but a careful comparison of the data shows that UDN is indeed less correct. Figure 71 presents the result of the UDN blending approach using the same source data as in the previous examples.

+

The ⟨L:2653⟩results are visually close to Reoriented Normal Mapping but a careful comparison of the data shows that UDN is indeed less correct. Figure ? presents the result of the UDN blending approach using the same source data as in the previous examples.

-

 
Figure 71: Blended normal and detail map using the UDN blending technique
+
Figure ⟨L:2655⟩[blendedNormalMapsUDN]: Blended normal and detail map using the UDN blending technique

-   

Volumetric effects

-   

Exponential height fog

+
   

⟨L:2657⟩Volumetric effects

+
   

⟨L:2659⟩Exponential height fog

-

 
Figure 72: Example of directional in-scattering with exponential height fog
+

-

 
Figure 73: Example of directional in-scattering with exponential height fog
-

-   

Anti-aliasing

-

-

[TODO] MSAA, geometric AA (normals and roughness), shader anti-aliasing (object-space shading?)

-

-   

Imaging pipeline

-

-

The lighting section of this document describes how light interacts with surfaces in the scene in a physically based manner. To achieve plausible results, we must go a step further and consider the transformations necessary to convert the scene luminance, as computed by our lighting equations, into displayable pixel values.

+
Figure ⟨L:2661⟩[exponentialHeightFog1]: Example of directional in-scattering with exponential height fog

-The series of transformations we are going to use form the following imaging pipeline: +

+

+

Figure ⟨L:2663⟩[exponentialHeightFog2]: Example of directional in-scattering with exponential height fog
+

+

+

+
   

⟨L:2665⟩Anti-aliasing

+

+

[TODO] ⟨L:2667⟩MSAA, geometric AA (normals and roughness), shader anti-aliasing (object-space shading?)

+

+
   

⟨L:2669⟩Imaging pipeline

+

+

The ⟨L:2671⟩lighting section of this document describes how light interacts with surfaces in the scene in a physically based manner. To achieve plausible results, we must go a step further and consider the transformations necessary to convert the scene luminance, as computed by our lighting equations, into displayable pixel values.

+

+The ⟨L:2673⟩series of transformations we are going to use form the following imaging pipeline:

@@ -2831,19 +2885,19 @@ The series of transformations we are going to use form the following imaging pip SceneNormalizedluminanceluminanceWhitebalance(HDR)ColorgradingTonemappingPixelOETFvalue(LDR)

-Note: the OETF step is the application of the opto-electronic transfer function of the target color space. For clarity this diagram does not include post-processing steps such as vignette, bloom, etc. These effects will be discussed separately. +Note: ⟨L:2704⟩the OETF step is the application of the opto-electronic transfer function of the target color space. For clarity this diagram does not include post-processing steps such as vignette, bloom, etc. These effects will be discussed separately.

-[TODO] Color spaces (ACES, sRGB, Rec. 709, Rec. 2020, etc.), gamma/linear, etc. +[TODO] ⟨L:2706⟩Color spaces (ACES, sRGB, Rec. 709, Rec. 2020, etc.), gamma/linear, etc.

-   

Physically based camera

+
   

⟨L:2708⟩Physically based camera

-

The first step in the image transformation process is to use a physically based camera to properly expose the scene's outgoing luminance.

+

The ⟨L:2710⟩first step in the image transformation process is to use a physically based camera to properly expose the scene's outgoing luminance.

-   

Exposure settings

+
   

⟨L:2712⟩Exposure settings

-

Because we use photometric units throughout the lighting pipeline, the light reaching the camera is an energy expressed in luminance (L), in (cd.m^{-2}). Light incident to the camera sensor can cover a large range of values, from (10^{-5}cd.m^{-2}) for starlight to (10^{9}cd.m^{-2}) for the sun. Since we obviously cannot manipulate and even less record such a large range of values, we need to remap them.

+

Because ⟨L:2714⟩we use photometric units throughout the lighting pipeline, the light reaching the camera is an energy expressed in luminance (L), in (cd.m^{-2}). Light incident to the camera sensor can cover a large range of values, from (10^{-5}cd.m^{-2}) for starlight to (10^{9}cd.m^{-2}) for the sun. Since we obviously cannot manipulate and even less record such a large range of values, we need to remap them.

-This range remapping is done in a camera by exposing the sensor for a certain time. To maximize the use of the limited range of the sensor, the scene's light range is centered around the “middle gray”, a value halfway between black and white. The exposition is therefore achieved by manipulating, either manually or automatically, 3 settings: +This ⟨L:2716⟩range remapping is done in a camera by exposing the sensor for a certain time. To maximize the use of the limited range of the sensor, the scene's light range is centered around the “middle gray”, a value halfway between black and white. The exposition is therefore achieved by manipulating, either manually or automatically, 3 settings:

  • Aperture @@ -2852,144 +2906,144 @@ This range remapping is done in a camera by exposing the sensor for a certain ti
  • Sensitivity (also called gain)

-

Aperture

Noted \(N\) and expressed in f-stops ƒ, this setting controls how open or closed the camera system's aperture is. Since an f-stop indicate the ratio of the lens' focal length to the diameter of the entrance pupil, high-values (ƒ/16) indicate a small aperture and small values (ƒ/1.4) indicate a wide aperture. In addition to the exposition, the aperture setting controls the depth of field. -

Shutter speed

Noted \(t\) and expressed in seconds \(s\), this setting controls how long the aperture remains opened (it also controls the timing of the sensor shutter(s), whether electronic or mechanical). In addition to the exposition, the shutter speed controls motion blur. -

Sensitivity

Noted \(S\) and expressed in ISO, this setting controls how the light reaching the sensor is quantized. Because of its unit, this setting is often referred to as simply the “ISO” or “ISO setting”. In addition to the exposition, the sensitivity setting controls the amount of noise. +

 Aperture⟨L:2722⟩

⟨L:2723⟩Noted \(N\) and expressed in f-stops ƒ, this setting controls how open or closed the camera system's aperture is. Since an f-stop indicate the ratio of the lens' focal length to the diameter of the entrance pupil, high-values (ƒ/16) indicate a small aperture and small values (ƒ/1.4) indicate a wide aperture. In addition to the exposition, the aperture setting controls the depth of field. +

 Shutter ⟨L:2725⟩speed

⟨L:2726⟩Noted \(t\) and expressed in seconds \(s\), this setting controls how long the aperture remains opened (it also controls the timing of the sensor shutter(s), whether electronic or mechanical). In addition to the exposition, the shutter speed controls motion blur. +

 Sensitivity⟨L:2728⟩

⟨L:2729⟩Noted \(S\) and expressed in ISO, this setting controls how the light reaching the sensor is quantized. Because of its unit, this setting is often referred to as simply the “ISO” or “ISO setting”. In addition to the exposition, the sensitivity setting controls the amount of noise.

-   

Exposure value

+
   

⟨L:2731⟩Exposure value

-

Since referring to these 3 settings in our equations would be unwieldy, we instead summarize the “exposure triangle” by an exposure value, noted EV10.

+

Since ⟨L:2733⟩referring to these 3 settings in our equations would be unwieldy, we instead summarize the “exposure triangle” by an exposure value, noted EV10.

-The EV is expressed in a base-2 logarithmic scale, with a difference of 1 EV called a stop. One positive stop (+1 EV) corresponds to a factor of two in luminance and one negative stop (−1 EV) corresponds to a factor of half in luminance. +The ⟨L:2735⟩EV is expressed in a base-2 logarithmic scale, with a difference of 1 EV called a stop. One positive stop (+1 EV) corresponds to a factor of two in luminance and one negative stop (−1 EV) corresponds to a factor of half in luminance.

-Equation \( \ref{ev} \) shows the formal definition of EV. +Equation ⟨L:2737⟩\( \ref{ev} \) shows the formal definition of EV.

-$$\begin{equation}\label{ev} -EV = log_2(\frac{N^2}{t}) -\end{equation}$$ +$$\begin{equation}\label{ev}⟨L:2739⟩ +EV ⟨L:2740⟩= log_2(\frac{N^2}{t}) +\end{equation}$$⟨L:2741⟩

-Note that this definition is only function of the aperture and shutter speed, but not the sensitivity. An exposure value is by convention defined for ISO 100, or \( EV_{100} \), and because we wish to work with this convention, we need to be able to express \( EV_{100} \) as a function of the sensitivity. +Note ⟨L:2743⟩that this definition is only function of the aperture and shutter speed, but not the sensitivity. An exposure value is by convention defined for ISO 100, or \( EV_{100} \), and because we wish to work with this convention, we need to be able to express \( EV_{100} \) as a function of the sensitivity.

-Since we know that EV is a base-2 logarithmic scale in which each stop increases or decreases the brightness by a factor of 2, we can formally define \( EV_{S} \), the exposure value at given sensitivity (equation \(\ref{evS}\)). +Since ⟨L:2745⟩we know that EV is a base-2 logarithmic scale in which each stop increases or decreases the brightness by a factor of 2, we can formally define \( EV_{S} \), the exposure value at given sensitivity (equation \(\ref{evS}\)).

-$$\begin{equation}\label{evS} -{EV}_S = EV_{100} + log_2(\frac{S}{100}) -\end{equation}$$ +$$\begin{equation}\label{evS}⟨L:2747⟩ +{EV}_S ⟨L:2748⟩= EV_{100} + log_2(\frac{S}{100}) +\end{equation}$$⟨L:2749⟩

-Calculating the \( EV_{100} \) as a function of the 3 camera settings is trivial, as shown in \(\ref{ev100}\). +Calculating ⟨L:2751⟩the \( EV_{100} \) as a function of the 3 camera settings is trivial, as shown in \(\ref{ev100}\).

-$$\begin{equation}\label{ev100} -{EV}_{100} = EV_{S} - log_2(\frac{S}{100}) = log_2(\frac{N^2}{t}) - log_2(\frac{S}{100}) -\end{equation}$$ +$$\begin{equation}\label{ev100}⟨L:2753⟩ +{EV}_{100} ⟨L:2754⟩= EV_{S} - log_2(\frac{S}{100}) = log_2(\frac{N^2}{t}) - log_2(\frac{S}{100}) +\end{equation}$$⟨L:2755⟩

-Note that the operator (photographer, etc.) can achieve the same exposure (and therefore EV) with several combinations of aperture, shutter speed and sensitivity. This allows some artistic control in the process (depth of field vs motion blur vs grain). +Note ⟨L:2757⟩that the operator (photographer, etc.) can achieve the same exposure (and therefore EV) with several combinations of aperture, shutter speed and sensitivity. This allows some artistic control in the process (depth of field vs motion blur vs grain).

 10 We assume a digital sensor, which means we don't need to take reciprocity failure into account

-   

Exposure value and luminance

+
   

⟨L:2761⟩Exposure value and luminance

-

A camera, similar to a spot meter, is able to measure the average luminance of a scene and convert it into EV to achieve automatic exposure, or at the very least offer the user exposure guidance.

+

A ⟨L:2763⟩camera, similar to a spot meter, is able to measure the average luminance of a scene and convert it into EV to achieve automatic exposure, or at the very least offer the user exposure guidance.

-It is possible to define EV as a function of the scene luminance \(L\), given a per-device calibration constant \(K\) (equation \( \ref{evK} \)). +It ⟨L:2765⟩is possible to define EV as a function of the scene luminance \(L\), given a per-device calibration constant \(K\) (equation \( \ref{evK} \)).

-$$\begin{equation}\label{evK} -EV = log_2(\frac{L \times S}{K}) -\end{equation}$$ +$$\begin{equation}\label{evK}⟨L:2767⟩ +EV ⟨L:2768⟩= log_2(\frac{L \times S}{K}) +\end{equation}$$⟨L:2769⟩

-That constant \(K\) is the reflected-light meter constant, which varies between manufacturers. We could find two common values for this constant: 12.5, used by Canon, Nikon and Sekonic, and 14, used by Pentax and Minolta. Given the wide availability of Canon and Nikon cameras, as well as our own usage of Sekonic light meters, we will choose to use \( K = 12.5 \). +That ⟨L:2771⟩constant \(K\) is the reflected-light meter constant, which varies between manufacturers. We could find two common values for this constant: 12.5, used by Canon, Nikon and Sekonic, and 14, used by Pentax and Minolta. Given the wide availability of Canon and Nikon cameras, as well as our own usage of Sekonic light meters, we will choose to use \( K = 12.5 \).

-Since we want to work with \( EV_{100} \), we can substitute \(K\) and \(S\) in equation \( \ref{evK} \) to obtain equation \( \ref{ev100L} \). +Since ⟨L:2773⟩we want to work with \( EV_{100} \), we can substitute \(K\) and \(S\) in equation \( \ref{evK} \) to obtain equation \( \ref{ev100L} \).

-$$\begin{equation}\label{ev100L} -EV = log_2(L \frac{100}{12.5}) -\end{equation}$$ +$$\begin{equation}\label{ev100L}⟨L:2775⟩ +EV ⟨L:2776⟩= log_2(L \frac{100}{12.5}) +\end{equation}$$⟨L:2777⟩

-Given this relationship, it would be possible to implement automatic exposure in our engine by first measuring the average luminance of a frame. An easy way to achieve this is to simply downsample a luminance buffer down to 1 pixel and read the remaining value. This technique is unfortunately rarely stable and can easily be affected by extreme values. Many games use a different approach which consists in using a luminance histogram to remove extreme values. +Given ⟨L:2779⟩this relationship, it would be possible to implement automatic exposure in our engine by first measuring the average luminance of a frame. An easy way to achieve this is to simply downsample a luminance buffer down to 1 pixel and read the remaining value. This technique is unfortunately rarely stable and can easily be affected by extreme values. Many games use a different approach which consists in using a luminance histogram to remove extreme values.

-For validation and testing purposes, the luminance can be computed from a given EV: +For ⟨L:2781⟩validation and testing purposes, the luminance can be computed from a given EV:

-$$\begin{equation} -L = 2^{EV_{100}} \times \frac{12.5}{100} = 2^{EV_{100} - 3} -\end{equation}$$ +$$\begin{equation}⟨L:2783⟩ +L ⟨L:2784⟩= 2^{EV_{100}} \times \frac{12.5}{100} = 2^{EV_{100} - 3} +\end{equation}$$⟨L:2785⟩

-   

Exposure value and illuminance

+
   

⟨L:2787⟩Exposure value and illuminance

-

It is possible to define EV as a function of the illuminance (E), given a per-device calibration constant (C):

+

It ⟨L:2789⟩is possible to define EV as a function of the illuminance (E), given a per-device calibration constant (C):

-$$\begin{equation}\label{evC} -EV = log_2(\frac{E \times S}{C}) -\end{equation}$$ +$$\begin{equation}\label{evC}⟨L:2791⟩ +EV ⟨L:2792⟩= log_2(\frac{E \times S}{C}) +\end{equation}$$⟨L:2793⟩

-The constant \(C\) is the incident-light meter constant, which varies between manufacturers and/or types of sensors. There are two common types of sensors: flat and hemispherical. For flat sensors, a common value is 250. With hemispherical sensors, we could find two common values: 320, used by Minolta, and 340, used by Sekonic. +The ⟨L:2795⟩constant \(C\) is the incident-light meter constant, which varies between manufacturers and/or types of sensors. There are two common types of sensors: flat and hemispherical. For flat sensors, a common value is 250. With hemispherical sensors, we could find two common values: 320, used by Minolta, and 340, used by Sekonic.

-Since we want to work with \( EV_{100} \), we can substitute \(S\) \( \ref{evC} \) to obtain equation \( \ref{ev100C} \). +Since ⟨L:2797⟩we want to work with \( EV_{100} \), we can substitute \(S\) \( \ref{evC} \) to obtain equation \( \ref{ev100C} \).

-$$\begin{equation}\label{ev100C} -EV = log_2(E \frac{100}{C}) -\end{equation}$$ +$$\begin{equation}\label{ev100C}⟨L:2799⟩ +EV ⟨L:2800⟩= log_2(E \frac{100}{C}) +\end{equation}$$⟨L:2801⟩

-The illuminance can then be computed from a given EV. For a flat sensor with \( C = 250 \) we obtain equation \( \ref{eFlatSensor} \). +The ⟨L:2803⟩illuminance can then be computed from a given EV. For a flat sensor with \( C = 250 \) we obtain equation \( \ref{eFlatSensor} \).

-$$\begin{equation}\label{eFlatSensor} -E = 2^{EV_{100}} \times 2.5 -\end{equation}$$ +$$\begin{equation}\label{eFlatSensor}⟨L:2805⟩ +E ⟨L:2806⟩= 2^{EV_{100}} \times 2.5 +\end{equation}$$⟨L:2807⟩

-For a hemispherical sensor with \( C = 340 \) we obtain equation \( \ref{eHemisphereSensor} \) +For ⟨L:2809⟩a hemispherical sensor with \( C = 340 \) we obtain equation \( \ref{eHemisphereSensor} \)

-$$\begin{equation}\label{eHemisphereSensor} -E = 2^{EV_{100}} \times 3.4 -\end{equation}$$ +$$\begin{equation}\label{eHemisphereSensor}⟨L:2811⟩ +E ⟨L:2812⟩= 2^{EV_{100}} \times 3.4 +\end{equation}$$⟨L:2813⟩

-   

Exposure compensation

+
   

⟨L:2815⟩Exposure compensation

-

Even though an exposure value actually indicates combinations of camera settings, it is often used by photographers to describe light intensity. This is why cameras let photographers apply an exposure compensation to over or under-expose an image. This setting can be used for artistic control but also to achieve proper exposure (snow for instance will be exposed for as 18% middle-gray).

+

Even ⟨L:2817⟩though an exposure value actually indicates combinations of camera settings, it is often used by photographers to describe light intensity. This is why cameras let photographers apply an exposure compensation to over or under-expose an image. This setting can be used for artistic control but also to achieve proper exposure (snow for instance will be exposed for as 18% middle-gray).

-Applying an exposure compensation \(EC\) is a simple as adding an offset to the exposure value, as shown in equation \( \ref{ec} \). +Applying ⟨L:2819⟩an exposure compensation \(EC\) is a simple as adding an offset to the exposure value, as shown in equation \( \ref{ec} \).

-$$\begin{equation}\label{ec} -EV_{100}' = EV_{100} - EC -\end{equation}$$ +$$\begin{equation}\label{ec}⟨L:2821⟩ +EV_{100}' ⟨L:2822⟩= EV_{100} - EC +\end{equation}$$⟨L:2823⟩

-This equation uses a negative sign because we are using \(EC\) in f-stops to adjust the final exposure. Increasing the EV is akin to closing down the aperture of the lens (or reducing shutter speed or reducing sensitivity). A higher EV will produce darker images. +This ⟨L:2825⟩equation uses a negative sign because we are using \(EC\) in f-stops to adjust the final exposure. Increasing the EV is akin to closing down the aperture of the lens (or reducing shutter speed or reducing sensitivity). A higher EV will produce darker images.

-   

Exposure

+
   

⟨L:2827⟩Exposure

-

To convert the scene luminance into normalized luminance, we must use the photometric exposure (or luminous exposure), or amount of scene luminance that reaches the camera sensor. The photometric exposure, expressed in lux seconds and noted (H), is given by equation ( \ref{photometricExposure} ).

+

To ⟨L:2829⟩convert the scene luminance into normalized luminance, we must use the photometric exposure (or luminous exposure), or amount of scene luminance that reaches the camera sensor. The photometric exposure, expressed in lux seconds and noted (H), is given by equation ( \ref{photometricExposure} ).

-$$\begin{equation}\label{photometricExposure} -H = \frac{q \cdot t}{N^2} L -\end{equation}$$ +$$\begin{equation}\label{photometricExposure}⟨L:2831⟩ +H ⟨L:2832⟩= \frac{q \cdot t}{N^2} L +\end{equation}$$⟨L:2833⟩

-Where \(L\) is the luminance of the scene, \(t\) the shutter speed, \(N\) the aperture and \(q\) the lens and vignetting attenuation (typically \( q = 0.65 \)11). This definition does not take the sensor sensitivity into account. To do so, we must use one of the three ways to relate photometric exposure and sensitivity: saturation-based speed, noise-based speed and standard output sensitivity. +Where ⟨L:2835⟩\(L\) is the luminance of the scene, \(t\) the shutter speed, \(N\) the aperture and \(q\) the lens and vignetting attenuation (typically \( q = 0.65 \)11). This definition does not take the sensor sensitivity into account. To do so, we must use one of the three ways to relate photometric exposure and sensitivity: saturation-based speed, noise-based speed and standard output sensitivity.

-We choose the saturation-based speed relation, which gives us \( H_{sat} \), the maximum possible exposure that does not lead to clipped or bloomed camera output (equation \( \ref{hSat} \)). +We ⟨L:2837⟩choose the saturation-based speed relation, which gives us \( H_{sat} \), the maximum possible exposure that does not lead to clipped or bloomed camera output (equation \( \ref{hSat} \)).

-$$\begin{equation}\label{hSat} -H_{sat} = \frac{78}{S_{sat}} -\end{equation}$$ +$$\begin{equation}\label{hSat}⟨L:2839⟩ +H_{sat} ⟨L:2840⟩= \frac{78}{S_{sat}} +\end{equation}$$⟨L:2841⟩

-We combine equations \( \ref{hSat} \) and \( \ref{photometricExposure} \) in equation \( \ref{lmax} \) to compute the maximum luminance \( L_{max} \) that will saturate the sensor given exposure settings \(S\), \(N\) and \(t\). +We ⟨L:2843⟩combine equations \( \ref{hSat} \) and \( \ref{photometricExposure} \) in equation \( \ref{lmax} \) to compute the maximum luminance \( L_{max} \) that will saturate the sensor given exposure settings \(S\), \(N\) and \(t\).

-$$\begin{equation}\label{lmax} -L_{max} = \frac{N^2}{q \cdot t} \frac{78}{S} -\end{equation}$$ +$$\begin{equation}\label{lmax}⟨L:2845⟩ +L_{max} ⟨L:2846⟩= \frac{N^2}{q \cdot t} \frac{78}{S} +\end{equation}$$⟨L:2847⟩

-This maximum luminance can then be used to normalize incident luminance \(L\) as shown in equation \( \ref{normalizedLuminance} \). +This ⟨L:2849⟩maximum luminance can then be used to normalize incident luminance \(L\) as shown in equation \( \ref{normalizedLuminance} \).

-$$\begin{equation}\label{normalizedLuminance} -L' = L \frac{1}{L_{max}} -\end{equation}$$ +$$\begin{equation}\label{normalizedLuminance}⟨L:2851⟩ +L' ⟨L:2852⟩= L \frac{1}{L_{max}} +\end{equation}$$⟨L:2853⟩

-\( L_{max} \) can be simplified using equation \( \ref{ev} \), \( S = 100 \) and \( q = 0.65 \): +\( ⟨L:2855⟩L_{max} \) can be simplified using equation \( \ref{ev} \), \( S = 100 \) and \( q = 0.65 \):

-$$\begin{align*} -L_{max} &= \frac{N^2}{t} \frac{78}{q \cdot S} \\ -L_{max} &= 2^{EV_{100}} \frac{78}{q \cdot S} \\ -L_{max} &= 2^{EV_{100}} \times 1.2 -\end{align*}$$ +$$\begin{align*}⟨L:2857⟩ +L_{max} ⟨L:2858⟩&= \frac{N^2}{t} \frac{78}{q \cdot S} \\ +L_{max} ⟨L:2859⟩&= 2^{EV_{100}} \frac{78}{q \cdot S} \\ +L_{max} ⟨L:2860⟩&= 2^{EV_{100}} \times 1.2 +\end{align*}$$⟨L:2861⟩

-Listing 42 shows how the exposure term can be applied directly to the pixel color computed in a fragment shader. +Listing ⟨L:2863⟩[fragmentExposure] shows how the exposure term can be applied directly to the pixel color computed in a fragment shader.

 
// Computes the camera's EV100 from exposure settings
 // aperture in f-stops
 // shutterSpeed in seconds
@@ -3010,72 +3064,68 @@ L_{max} &= 2^{EV_{100}} \times 1.2
 vec4 color = evaluateLighting();
 color.rgb *= exposure;
Listing 42: Implementation of exposure in GLSL

-

In practice the exposure factor can be pre-computed on the CPU to save shader instructions.

+

In ⟨L:2888⟩practice the exposure factor can be pre-computed on the CPU to save shader instructions.

 11 See Film Speed, Measurements and calculations on Wikipedia (https://en.wikipedia.org/wiki/Film_speed)

-   

Automatic exposure

+
   

⟨L:2892⟩Automatic exposure

-

The process described above relies on artists setting the camera exposure settings manually. This can prove cumbersome in practice since camera movements and/or dynamic effects can greatly affect the scene's luminance. Since we know how to compute the exposure value from a given luminance (see section 8.1.2.1), we can transform our camera into a spot meter. To do so, we need to measure the scene's luminance.

+

The ⟨L:2894⟩process described above relies on artists setting the camera exposure settings manually. This can prove cumbersome in practice since camera movements and/or dynamic effects can greatly affect the scene's luminance. Since we know how to compute the exposure value from a given luminance (see section 8.1.2.1), we can transform our camera into a spot meter. To do so, we need to measure the scene's luminance.

-There are two common techniques used to measure the scene's luminance: +There ⟨L:2896⟩are two common techniques used to measure the scene's luminance:

  • Luminance downsampling, by downsampling the previous frame successively until obtaining a 1×1 log luminance buffer that can be read on the CPU (this could also be achieved using a compute shader). The result is the average log luminance of the scene. The first downsampling must extract the luminance of each pixel first. This technique can be unstable and its output should be smoothed over time.
  • Using a luminance histogram, to find the average log luminance. This technique has an advantage over the previous one as it allows to ignore extreme values and offers more stable results.

-Note that both methods will find the average luminance after multiplication by the albedo. This is not entirely correct but the alternative is to keep a luminance buffer that contains the luminance of each pixel before multiplication by the surface albedo. This is expensive both computationally and memory-wise. +Note ⟨L:2901⟩that both methods will find the average luminance after multiplication by the albedo. This is not entirely correct but the alternative is to keep a luminance buffer that contains the luminance of each pixel before multiplication by the surface albedo. This is expensive both computationally and memory-wise.

-These two techniques also limit the metering system to average metering, where each pixel has the same influence (or weight) over the final exposure. Cameras typically offer 3 modes of metering: +These ⟨L:2903⟩two techniques also limit the metering system to average metering, where each pixel has the same influence (or weight) over the final exposure. Cameras typically offer 3 modes of metering:

-

Spot metering

In which only a small circle in the center of the image contributes to the final exposure. That circle is usually 1 to 5% of the total image size. -

Center-weighted metering

Gives more influence to scene luminance values located in the center of the screen. -

Multi-zone or matrix metering

A metering mode that differs for each manufacturer. The goal of this mode is to prioritize exposure for the most important parts of the scene. This is often achieved by splitting the image into a grid and by classifying each cell (using focus information, min/max luminance, etc.). Advanced implementations attempt to compare the scene to a known dataset to achieve proper exposure (backlit sunset, overcast snowy day, etc.). +

 Spot ⟨L:2905⟩metering

⟨L:2906⟩In which only a small circle in the center of the image contributes to the final exposure. That circle is usually 1 to 5% of the total image size. +

 Center-weighted ⟨L:2908⟩metering

⟨L:2909⟩Gives more influence to scene luminance values located in the center of the screen. +

 Multi-zone ⟨L:2911⟩or matrix metering

⟨L:2912⟩A metering mode that differs for each manufacturer. The goal of this mode is to prioritize exposure for the most important parts of the scene. This is often achieved by splitting the image into a grid and by classifying each cell (using focus information, min/max luminance, etc.). Advanced implementations attempt to compare the scene to a known dataset to achieve proper exposure (backlit sunset, overcast snowy day, etc.).

-   

Spot metering

+
   

⟨L:2914⟩Spot metering

-

The weight (w) of each luminance value to use when computing the scene luminance is given by equation ( \ref{spotMetering} ).

+

The ⟨L:2916⟩weight (w) of each luminance value to use when computing the scene luminance is given by equation ( \ref{spotMetering} ).

-$$\begin{equation}\label{spotMetering} +$$\begin{equation}\label{spotMetering}⟨L:2918⟩ w(x,y) = \begin{cases} 1 & \left| p_{x,y} - s_{x,y} \right| \le s_r \\ 0 & \left| p_{x,y} - s_{x,y} \right| \gt s_r \end{cases} -\end{equation}$$ +\end{equation}$$⟨L:2920⟩

-Where \(p\) is the position of the pixel, \(s\) the center of the spot and \( s_r \) the radius of the spot. +Where ⟨L:2922⟩\(p\) is the position of the pixel, \(s\) the center of the spot and \( s_r \) the radius of the spot.

-   

Center-weighted metering

+
   

⟨L:2924⟩Center-weighted metering

-

$$\begin{equation}\label{centerMetering} +

$$\begin{equation}\label{centerMetering}⟨L:2926⟩ w(x,y) = smooth(\left| p_{x,y} - c \right| \times \frac{2}{width} ) -\end{equation}$$

+\end{equation}$$⟨L:2928⟩

-Where \(c\) is the center of the time and \( smooth() \) a smoothing function such as GLSL's smoothstep(). +Where ⟨L:2930⟩\(c\) is the center of the time and \( smooth() \) a smoothing function such as GLSL's smoothstep().

-   

Adaptation

+
   

⟨L:2932⟩Adaptation

-

To smooth the result of the metering, we can use equation ( \ref{adaptation} ), an exponential feedback loop as described by Pattanaik et al. in [Pattanaik00].

-

-$$\begin{equation}\label{adaptation} -L_{avg} = L_{avg} + (L - L_{avg}) \times (1 - e^{-\Delta t \cdot \tau}) -\end{equation}$$ -

-Where \( \Delta t \) is the delta time from the previous frame and \(\tau\) a constant that controls the adaptation rate. +

To ⟨L:2934⟩smooth the result of the metering, we can use equation ( \ref{adaptation} (, an exponential feedback loop as described by Pattanaik et al. in [Pattanaik00]. +$$\begin{equation}\label{adaptation}⟨L:2936⟩ +L_{avg} ⟨L:2937⟩= L_{avg} + (L - L_{avg}) \times (1 - e^{-\Delta t \cdot \tau}) +\end{equation}$$⟨L:2938⟩ +Where ⟨L:2940⟩) \Delta t ) is the delta time from the previous frame and (\tau) a constant that controls the adaptation rate.

-   

Bloom

+
   

⟨L:2942⟩Bloom

-

Because the EV scale is almost perceptually linear, the exposure value is also often used as a light unit. This means we could let artists specify the intensity of lights or emissive surfaces using exposure compensation as a unit. The intensity of emitted light would therefore be relative to the exposure settings. Using exposure compensation as a light unit should be avoided whenever possible but can be useful to force (or cancel) a bloom effect around emissive surfaces independently of the camera settings (for instance, a lightsaber in a game should always bloom).

+

Because ⟨L:2944⟩the EV scale is almost perceptually linear, the exposure value is also often used as a light unit. This means we could let artists specify the intensity of lights or emissive surfaces using exposure compensation as a unit. The intensity of emitted light would therefore be relative to the exposure settings. Using exposure compensation as a light unit should be avoided whenever possible but can be useful to force (or cancel) a bloom effect around emissive surfaces independently of the camera settings (for instance, a lightsaber in a game should always bloom).

-

 
Figure 74: Saturated photosites on a sensor create a blooming effect in the bright parts of the scene
+
Figure ⟨L:2946⟩[bloom]: Saturated photosites on a sensor create a blooming effect in the bright parts of the scene

-With \(c\) the bloom color and \( EV_{100} \) the current exposure value, we can easily compute the luminance of the bloom value as show in equation \( \ref{bloomEV} \). -

-$$\begin{equation}\label{bloomEV} -EV_{bloom} = EV_{100} + EC \\ -L_{bloom} = c \times 2^{EV_{bloom} - 3} -\end{equation}$$ -

-Equation \( \ref{bloomEV} \) can be used in a fragment shader to implement emissive blooms, as shown in listing 43. +With ⟨L:2948⟩\(c\) the bloom color and \( EV_{100} \) the current exposure value, we can easily compute the luminance of the bloom value as show in equation \( \ref{bloomEV} \(. +$$\begin{equation}\label{bloomEV}⟨L:2950⟩ +EV_{bloom} ⟨L:2951⟩= EV_{100} + EC \\ +L_{bloom} ⟨L:2952⟩= c \times 2^{EV_{bloom} - 3} +\end{equation}$$⟨L:2953⟩ +Equation ⟨L:2955⟩\) \ref{bloomEV} \) can be used in a fragment shader to implement emissive blooms, as shown in listing 43.

 
vec4 surfaceShading() {
     vec4 color = evaluateLights();
     // rgb = color, w = exposure compensation
@@ -3084,39 +3134,39 @@ Equation \( \ref{bloomEV} \) can be used in a fragment shader to implement emiss
     color.rgb *= exposure;
     return color;
 }
Listing 43: Implementation of emissive bloom in GLSL
-   

Optics post-processing

-   

Color fringing

+
   

⟨L:2969⟩Optics post-processing

+
   

⟨L:2971⟩Color fringing

-

[TODO]

+

[TODO]⟨L:2973⟩

-

 
Figure 75: Example of color fringing: look at the ear on the left or the chin at the bottom.
+
Figure ⟨L:2975⟩[fringing]: Example of color fringing: look at the ear on the left or the chin at the bottom.

-   

Lens flares

+
   

⟨L:2977⟩Lens flares

-

[TODO] Notes: there is a physically based approach to generating lens flares, by tracing rays through the optical assembly of the lens, but we are going to use an image-based approach. This approach is cheaper and has a few welcome benefits such as free emitters occlusion and unlimited light sources support.

+

[TODO] ⟨L:2979⟩Notes: there is a physically based approach to generating lens flares, by tracing rays through the optical assembly of the lens, but we are going to use an image-based approach. This approach is cheaper and has a few welcome benefits such as free emitters occlusion and unlimited light sources support.

-   

Filmic post-processing

+
   

⟨L:2981⟩Filmic post-processing

-

[TODO] Perform post-processing on the scene referred data (linear space, before tone-mapping) as much as possible

+

[TODO] ⟨L:2983⟩Perform post-processing on the scene referred data (linear space, before tone-mapping) as much as possible

-It is important to provide color correction tools to give artists greater artistic control over the final image. These tools are found in every photo or video processing application, such as Adobe Photoshop or Adobe After Effects. +It ⟨L:2985⟩is important to provide color correction tools to give artists greater artistic control over the final image. These tools are found in every photo or video processing application, such as Adobe Photoshop or Adobe After Effects.

-   

Contrast

-   

Curves

-   

Levels

-   

Color grading

-   

Light path

+
   

⟨L:2987⟩Contrast

+
   

⟨L:2989⟩Curves

+
   

⟨L:2991⟩Levels

+
   

⟨L:2993⟩Color grading

+
   

⟨L:2995⟩Light path

-

The light path, or rendering method, used by the engine can have serious performance implications and may impose strong limitations on how many lights can be used in a scene. There are traditionally two different rendering methods used by 3D engines forward and deferred rendering.

+

The ⟨L:2997⟩light path, or rendering method, used by the engine can have serious performance implications and may impose strong limitations on how many lights can be used in a scene. There are traditionally two different rendering methods used by 3D engines forward and deferred rendering.

-Our goal is to use a rendering method that obeys the following constraints: +Our ⟨L:2999⟩goal is to use a rendering method that obeys the following constraints:

  • Low bandwidth requirements
  • Multiple dynamic lights per pixel

-Additionally, we would like to easily support: +Additionally, ⟨L:3004⟩we would like to easily support:

  • MSAA @@ -3125,111 +3175,117 @@ Additionally, we would like to easily support:
  • Multiple material models

-Deferred rendering is used by many modern 3D rendering engines to easily support dozens, hundreds or even thousands of light source (amongst other benefits). This method is unfortunately very expensive in terms of bandwidth. With our default PBR material model, our G-buffer would use between 160 and 192 bits per pixel, which would translate directly to rather high bandwidth requirements. +Deferred ⟨L:3010⟩rendering is used by many modern 3D rendering engines to easily support dozens, hundreds or even thousands of light source (amongst other benefits). This method is unfortunately very expensive in terms of bandwidth. With our default PBR material model, our G-buffer would use between 160 and 192 bits per pixel, which would translate directly to rather high bandwidth requirements.

-Forward rendering methods on the other hand have historically been bad at handling multiple lights. A common implementation is to render the scene multiple times, once per visible light, and to blend (add) the results. Another technique consists in assigning a fixed maximum of lights to each object in the scene. This is however impractical when objects occupy a vast amount of space in the world (building, road, etc.). +Forward ⟨L:3012⟩rendering methods on the other hand have historically been bad at handling multiple lights. A common implementation is to render the scene multiple times, once per visible light, and to blend (add) the results. Another technique consists in assigning a fixed maximum of lights to each object in the scene. This is however impractical when objects occupy a vast amount of space in the world (building, road, etc.).

-Tiled shading can be applied to both forward and deferred rendering methods. The idea is to split the screen in a grid of tiles and for each tile, find the list of lights that affect the pixels within that tile. This has the advantage of reducing overdraw (in deferred rendering) and shading computations of large objects (in forward rendering). This technique suffers however from depth discontinuities issues that can lead to large amounts of extraneous work. +Tiled ⟨L:3014⟩shading can be applied to both forward and deferred rendering methods. The idea is to split the screen in a grid of tiles and for each tile, find the list of lights that affect the pixels within that tile. This has the advantage of reducing overdraw (in deferred rendering) and shading computations of large objects (in forward rendering). This technique suffers however from depth discontinuities issues that can lead to large amounts of extraneous work.

-The scene displayed in figure 76 was rendered using clustered forward rendering. +The ⟨L:3016⟩scene displayed in figure ? was rendered using clustered forward rendering.

-

 
Figure 76: Clustered forward rendering with dozens of dynamic lights and MSAA
+
Figure ⟨L:3018⟩[sponza]: Clustered forward rendering with dozens of dynamic lights and MSAA

-Figure 77 shows the same scene split in tiles (in this case, a 1280×720 render target with 80×80px tiles). +Figure ⟨L:3020⟩[sponzaTiles] shows the same scene split in tiles (in this case, a 1280×720 render target with 80×80px tiles).

-

 
Figure 77: Tiled shading (16×9 tiles)
+
Figure ⟨L:3022⟩[sponzaTiles]: Tiled shading (16×9 tiles)

-   

Clustered Forward Rendering

+
   

⟨L:3024⟩Clustered Forward Rendering

-

We decided to explore another method called Clustered Shading, in its forward variant. Clustered shading expands on the idea of tiled rendering but adds a segmentation on the 3rd axis. The “clustering” is done in view space, by splitting the frustum into a 3D grid.

+

We ⟨L:3026⟩decided to explore another method called Clustered Shading, in its forward variant. Clustered shading expands on the idea of tiled rendering but adds a segmentation on the 3rd axis. The “clustering” is done in view space, by splitting the frustum into a 3D grid.

-The frustum is first sliced on the depth axis as show in figure 78. +The ⟨L:3028⟩frustum is first sliced on the depth axis as show in figure ?.

-

 
Figure 78: Depth slicing (16 slices)
+
Figure ⟨L:3030⟩[sponzaSlices]: Depth slicing (16 slices)

-And the depth slices are then combined with the screen tiles to “voxelize” the frustum. We call each cluster a froxel as it makes it clear what they represent (a voxel in frustum space). The result of the “froxelization” pass is shown in figure 79 and figure 80. +And ⟨L:3032⟩the depth slices are then combined with the screen tiles to “voxelize” the frustum. We call each cluster a froxel as it makes it clear what they represent (a voxel in frustum space). The result of the “froxelization” pass is shown in figure ? and figure ?.

-

 
Figure 79: Frustum voxelization (5×3 tiles, 8 depth slices)
+

-

 
Figure 80: Frustum voxelization (5×3 tiles, 8 depth slices)
+
Figure ⟨L:3034⟩[froxel1]: Frustum voxelization (5×3 tiles, 8 depth slices)

-Before rendering a frame, each light in the scene is assigned to any froxel it intersects with. The result of the lights assignment pass is a list of lights for each froxel. During the rendering pass, we can compute the ID of the froxel a fragment belongs to and therefore the list of lights that can affect that fragment. +

-The depth slicing is not linear, but exponential. In a typical scene, there will be more pixels close to the near plane than to the far plane. An exponential grid of froxels will therefore improve the assignment of lights where it matters the most. +

Figure ⟨L:3036⟩[froxel2]: Frustum voxelization (5×3 tiles, 8 depth slices)

-Figure 81 shows how much world space unit each depth slice uses with exponential slicing. +

-

 
Figure 81: Near: 0.1m, Far: 100m, 16 slices
+Before ⟨L:3038⟩rendering a frame, each light in the scene is assigned to any froxel it intersects with. The result of the lights assignment pass is a list of lights for each froxel. During the rendering pass, we can compute the ID of the froxel a fragment belongs to and therefore the list of lights that can affect that fragment.

-A simple exponential voxelization is unfortunately not enough. The graphic above clearly illustrates how world space is distributed across slices but it fails to show what happens close to the near plane. If we examine the same distribution in a smaller range (0.1m to 7m) we can see an interesting problem appear as shown in figure 82. +The ⟨L:3040⟩depth slicing is not linear, but exponential. In a typical scene, there will be more pixels close to the near plane than to the far plane. An exponential grid of froxels will therefore improve the assignment of lights where it matters the most.

-

 
Figure 82: Depth distribution in the 0.1-7m range
+Figure ⟨L:3042⟩[froxelDistribution] shows how much world space unit each depth slice uses with exponential slicing.

-This graphic shows that a simple exponential distribution uses up half of the slices very close to the camera. In this particular case, we use 8 slices out of 16in the first 5 meters. Since dynamic world lights are either point lights (spheres) or spot lights (cones), such a fine resolution is completely unnecessary so close to the near plane. +

Figure ⟨L:3044⟩[froxelDistribution]: Near: 0.1m, Far: 100m, 16 slices

-Our solution is to manually tweak the size of the first froxel depending on the scene and the near and far planes. By doing so, we can better distribute the remaining froxels across the frustum. Figure 83 shows for instance what happens when we use a special froxel between 0.1m and 5m. +A ⟨L:3046⟩simple exponential voxelization is unfortunately not enough. The graphic above clearly illustrates how world space is distributed across slices but it fails to show what happens close to the near plane. If we examine the same distribution in a smaller range (0.1m to 7m) we can see an interesting problem appear as shown in figure ?.

-

 
Figure 83: Near: 0.1, Far: 100m, 16 slices, Special froxel: 0.1-5m
+
Figure ⟨L:3048⟩[froxelDistributionClose]: Depth distribution in the 0.1-7m range

-This new distribution is much more efficient and allows a better assignment of the lights throughout the entire frustum. +This ⟨L:3050⟩graphic shows that a simple exponential distribution uses up half of the slices very close to the camera. In this particular case, we use 8 slices out of 16in the first 5 meters. Since dynamic world lights are either point lights (spheres) or spot lights (cones), such a fine resolution is completely unnecessary so close to the near plane. +

+Our ⟨L:3052⟩solution is to manually tweak the size of the first froxel depending on the scene and the near and far planes. By doing so, we can better distribute the remaining froxels across the frustum. Figure ? shows for instance what happens when we use a special froxel between 0.1m and 5m. +

+

Figure ⟨L:3054⟩[froxelDistributionExp]: Near: 0.1, Far: 100m, 16 slices, Special froxel: 0.1-5m
+

+This ⟨L:3056⟩new distribution is much more efficient and allows a better assignment of the lights throughout the entire frustum.

-   

Implementation notes

+
   

⟨L:3058⟩Implementation notes

-

Lights assignment can be done in two different ways, on the GPU or on the CPU.

+

Lights ⟨L:3060⟩assignment can be done in two different ways, on the GPU or on the CPU.

-   

GPU lights assignment

+
   

⟨L:3062⟩GPU lights assignment

-

This implementation requires OpenGL ES 3.1 and support for compute shaders. The lights are stored in Shader Storage Buffer Objects (SSBO) and passed to a compute shader that assigns each light to the corresponding froxels.

+

This ⟨L:3064⟩implementation requires OpenGL ES 3.1 and support for compute shaders. The lights are stored in Shader Storage Buffer Objects (SSBO) and passed to a compute shader that assigns each light to the corresponding froxels.

-The frustum voxelization can be executed only once by a first compute shader (as long as the projection matrix does not change), and the lights assignment can be performed each frame by another compute shader. +The ⟨L:3066⟩frustum voxelization can be executed only once by a first compute shader (as long as the projection matrix does not change), and the lights assignment can be performed each frame by another compute shader.

-The threading model of compute shaders is particularly well suited for this task. We simply invoke as many workgroups as we have froxels (we can directly map the X, Y and Z workgroup counts to our froxel grid resolution). Each workground will in turn be threaded and traverse all the lights to assign. +The ⟨L:3068⟩threading model of compute shaders is particularly well suited for this task. We simply invoke as many workgroups as we have froxels (we can directly map the X, Y and Z workgroup counts to our froxel grid resolution). Each workground will in turn be threaded and traverse all the lights to assign.

-Intersection tests imply simple sphere/frustum or cone/frustum tests. +Intersection ⟨L:3070⟩tests imply simple sphere/frustum or cone/frustum tests.

-See the annex for the source code of a GPU implementation (point lights only). +See ⟨L:3072⟩the annex for the source code of a GPU implementation (point lights only).

-   

CPU lights assignment

+
   

⟨L:3074⟩CPU lights assignment

-

On non-OpenGL ES 3.1 devices, lights assignment can be performed efficiently on the CPU. The algorithm is different from the GPU implementation. Instead of iterating over every light for each froxel, the engine will “rasterize” each light as froxels. For instance, given a point light’s center and radius, it is trivial to compute the list of froxels it intersects with.

+

On ⟨L:3076⟩non-OpenGL ES 3.1 devices, lights assignment can be performed efficiently on the CPU. The algorithm is different from the GPU implementation. Instead of iterating over every light for each froxel, the engine will “rasterize” each light as froxels. For instance, given a point light’s center and radius, it is trivial to compute the list of froxels it intersects with.

-This technique has the added benefit of providing tighter culling than in the GPU variant. The CPU implementation can also more easily generate a packed list of lights. +This ⟨L:3078⟩technique has the added benefit of providing tighter culling than in the GPU variant. The CPU implementation can also more easily generate a packed list of lights.

-   

Shading

+
   

⟨L:3080⟩Shading

-

The list of lights per froxel can be passed to the fragment shader either as an SSBO (OpenGL ES 3.1) or a texture.

+

The ⟨L:3082⟩list of lights per froxel can be passed to the fragment shader either as an SSBO (OpenGL ES 3.1) or a texture.

-   

From depth to froxel

+
   

⟨L:3084⟩From depth to froxel

-

Given a near plane (n), a far plane (f), a maximum number of depth slices (m) and a linear depth value (z) in the range [0..1], equation (\ref{zToCluster}) can be used to compute the index of the cluster for a given position.

+

Given ⟨L:3086⟩a near plane (n), a far plane (f), a maximum number of depth slices (m) and a linear depth value (z) in the range [0..1], equation (\ref{zToCluster}) can be used to compute the index of the cluster for a given position.

-$$\begin{equation}\label{zToCluster} -zToCluster(z,n,f,m)=floor \left( max \left( log2(z) \frac{m}{-log2(\frac{n}{f})} + m, 0 \right) \right) -\end{equation}$$ +$$\begin{equation}\label{zToCluster}⟨L:3088⟩ +zToCluster(z,n,f,m)=floor ⟨L:3089⟩\left( max \left( log2(z) \frac{m}{-log2(\frac{n}{f})} + m, 0 \right) \right) +\end{equation}$$⟨L:3090⟩

-This formula suffers however from the resolution issue mentioned previously. We can fix it by introducing \(sn\), a special near value that defines the extent of the first froxel (the first froxel occupies the range [n..sn], the remaining froxels [sn..f]). +This ⟨L:3092⟩formula suffers however from the resolution issue mentioned previously. We can fix it by introducing \(sn\), a special near value that defines the extent of the first froxel (the first froxel occupies the range [n..sn], the remaining froxels [sn..f]).

-$$\begin{equation}\label{zToClusterFix} -zToCluster(z,n,sn,f,m)=floor \left( max \left( log2(z) \frac{m-1}{-log2(\frac{sn}{f})} + m, 0 \right) \right) -\end{equation}$$ +$$\begin{equation}\label{zToClusterFix}⟨L:3094⟩ +zToCluster(z,n,sn,f,m)=floor ⟨L:3095⟩\left( max \left( log2(z) \frac{m-1}{-log2(\frac{sn}{f})} + m, 0 \right) \right) +\end{equation}$$⟨L:3096⟩

-Equation \(\ref{linearZ}\) can be used to compute a linear depth value from gl_FragCoord.z (assuming a standard OpenGL projection matrix). +Equation ⟨L:3098⟩\(\ref{linearZ}\) can be used to compute a linear depth value from gl_FragCoord.z (assuming a standard OpenGL projection matrix).

-$$\begin{equation}\label{linearZ} -linearZ(z)=\frac{n}{f+z(n-f)} -\end{equation}$$ +$$\begin{equation}\label{linearZ}⟨L:3100⟩ +linearZ(z)=\frac{n}{f+z(n-f)}⟨L:3101⟩ +\end{equation}$$⟨L:3102⟩

-This equation can be simplified by pre-computing two terms \(c0\) and \(c1\), as shown in equation \(\ref{linearZFix}\). +This ⟨L:3104⟩equation can be simplified by pre-computing two terms \(c0\) and \(c1\), as shown in equation \(\ref{linearZFix}\).

-$$\begin{equation}\label{linearZFix} -c1 = \frac{f}{n} \\ -c0 = 1 - c1 \\ -linearZ(z)=\frac{1}{z \cdot c0 + c1} -\end{equation}$$ +$$\begin{equation}\label{linearZFix}⟨L:3106⟩ +c1 ⟨L:3107⟩= \frac{f}{n} \\ +c0 ⟨L:3108⟩= 1 - c1 \\ +linearZ(z)=\frac{1}{z ⟨L:3109⟩\cdot c0 + c1} +\end{equation}$$⟨L:3110⟩

-This simplification is important because we pass the linear z value to a log2 in \(\ref{zToClusterFix}\). Since the division becomes a negation under a logarithmic, we can avoid a division by using \(-log2(z \cdot c0 + c1)\) instead. +This ⟨L:3112⟩simplification is important because we pass the linear z value to a log2 in \(\ref{zToClusterFix}\). Since the division becomes a negation under a logarithmic, we can avoid a division by using \(-log2(z \cdot c0 + c1)\) instead.

-All put together, computing the froxel index of a given fragment can be implemented fairly easily as shown in listing 44. +All ⟨L:3114⟩put together, computing the froxel index of a given fragment can be implemented fairly easily as shown in listing 44.

 
#define MAX_LIGHT_COUNT 16 // max number of lights per froxel
 
 uniform uvec4 froxels; // res x, res y, count y, count y
@@ -3254,7 +3310,7 @@ All put together, computing the froxel index of a given fragment can be implemen
 
 // Compute lighting...
Listing 44: GLSL implementation to compute a froxel index from a fragment's screen coordinates

-

Several uniforms must be pre-computed for perform the index evaluation efficiently. The code used to pre-compute these uniforms can be found in listing ?.

+

Several ⟨L:3143⟩uniforms must be pre-computed for perform the index evaluation efficiently. The code used to pre-compute these uniforms can be found in listing ?.

froxels[0] = TILE_RESOLUTION_IN_PX;
 froxels[1] = TILE_RESOLUTION_IN_PX;
 froxels[2] = numberOfTilesInX;
@@ -3263,34 +3319,34 @@ All put together, computing the froxel index of a given fragment can be implemen
 zParams[0] = 1.0f - Z_FAR / Z_NEAR;
 zParams[1] = Z_FAR / Z_NEAR;
 zParams[2] = (MAX_DEPTH_SLICES - 1) / log2(Z_SPECIAL_NEAR / Z_FAR);
-zParams[3] = MAX_DEPTH_SLICES;
[Listing ?]
-   

From froxel to depth

+zParams[3] = MAX_DEPTH_SLICES;
[Listing ⟨L:3156⟩[froxelIndexPrecomputation]]
+
   

⟨L:3158⟩From froxel to depth

-

Given a froxel index (i), a special near plane (sn), a far plane (f) and a maximum number of depth slices (m), equation (\ref{clusterToZ}) computes the minimum depth of a given froxel.

+

Given ⟨L:3160⟩a froxel index (i), a special near plane (sn), a far plane (f) and a maximum number of depth slices (m), equation (\ref{clusterToZ}) computes the minimum depth of a given froxel.

-$$\begin{equation}\label{clusterToZ} -clusterToZ(i \ge 1,sn,f,m)=2^{(i-m) \frac{-log2(\frac{sn}{f})}{m-1}} -\end{equation}$$ +$$\begin{equation}\label{clusterToZ}⟨L:3162⟩ +clusterToZ(i ⟨L:3163⟩\ge 1,sn,f,m)=2^{(i-m) \frac{-log2(\frac{sn}{f})}{m-1}} +\end{equation}$$⟨L:3164⟩

-For \(i=0\), the z value is 0. The result of this equation is in the [0..1] range and should be multiplied by \(f\) to get a distance in world units. +For ⟨L:3166⟩\(i=0\), the z value is 0. The result of this equation is in the [0..1] range and should be multiplied by \(f\) to get a distance in world units.

-The compute shader implementation should use exp2 instead of a pow. The division can be precomputed and passed as a uniform. +The ⟨L:3168⟩compute shader implementation should use exp2 instead of a pow. The division can be precomputed and passed as a uniform.

-   

Validation

+
   

⟨L:3170⟩Validation

-

Given the complexity of our lighting system, it is important to validate our implementation. We will do so in several ways: using reference renderings, light measurements and data visualization.

+

Given ⟨L:3172⟩the complexity of our lighting system, it is important to validate our implementation. We will do so in several ways: using reference renderings, light measurements and data visualization.

-[TODO] Explain light measurement validation (reading EV from the render target and comparing against values measure with light meters/cameras, etc.) +[TODO] ⟨L:3174⟩Explain light measurement validation (reading EV from the render target and comparing against values measure with light meters/cameras, etc.)

-   

Scene referred visualization

+
   

⟨L:3176⟩Scene referred visualization

-

A quick and easy way to validate a scene's lighting is to modify the shader to output colors that provide an intuitive mapping to relevant data. This can easily be done by using a custom debug tone-mapping operator that outputs fake colors.

+

A ⟨L:3178⟩quick and easy way to validate a scene's lighting is to modify the shader to output colors that provide an intuitive mapping to relevant data. This can easily be done by using a custom debug tone-mapping operator that outputs fake colors.

-   

Luminance stops

+
   

⟨L:3180⟩Luminance stops

-

With emissive materials and IBLs, it is fairly easy to obtain a scene in which specular highlights are brighter than their apparent caster. This type of issue can be difficult to observe after tone-mapping and quantization but is fairly obvious in the scene-referred space. Figure 84 shows how the custom operator described in listing 45 is used to show the exposed luminance of a scene.

+

With ⟨L:3182⟩emissive materials and IBLs, it is fairly easy to obtain a scene in which specular highlights are brighter than their apparent caster. This type of issue can be difficult to observe after tone-mapping and quantization but is fairly obvious in the scene-referred space. Figure ? shows how the custom operator described in listing 45 is used to show the exposed luminance of a scene.

-

 
Figure 84: Visualizing luminance by color coding the stops: cyan is middle gray, blue is 1 stop darker, green 1 stop brighter, etc.
+
Figure ⟨L:3184⟩[luminanceViz]: Visualizing luminance by color coding the stops: cyan is middle gray, blue is 1 stop darker, green 1 stop brighter, etc.

 
vec3 Tonemap_DisplayRange(const vec3 x) {
     // The 5th color in the array (cyan) represents middle gray (18%)
     // Every stop above or below middle gray causes a color shift
@@ -3318,19 +3374,25 @@ The compute shader implementation should use exp2 instead of a      vec3(0.6, 0.3333, 0.7882),   // purple
      vec3(1.0, 1.0, 1.0)          // white
 );
Listing 45: GLSL implementation of a custom debug tone-mapping operator for luminance visualization
-   

Reference renderings

+
   

⟨L:3217⟩Reference renderings

-

To validate our implementation against reference renderings, we will use a commercial-grade Open Source physically based offline path tracer called Mitsuba. Mitsuba offers many different integrators, samplers and material models, which should allow us to provide fair comparisons with our real-time renderer. This path tracer also relies on a simple XML scene description format that should be easy to automatically generate from our own scene descriptions.

+

To ⟨L:3219⟩validate our implementation against reference renderings, we will use a commercial-grade Open Source physically based offline path tracer called Mitsuba. Mitsuba offers many different integrators, samplers and material models, which should allow us to provide fair comparisons with our real-time renderer. This path tracer also relies on a simple XML scene description format that should be easy to automatically generate from our own scene descriptions.

-Figure 85 and figure 86 show a simple scene, a perfectly smooth dielectric sphere, rendered respectively with Mitsuba and Filament. +Figure ⟨L:3221⟩[mitsubaReference] and figure ? show a simple scene, a perfectly smooth dielectric sphere, rendered respectively with Mitsuba and Filament.

-

 
Figure 85: Rendered in 2048×1440 in 1 minute and 42 seconds on a 12 core 2013 MacPro
+

-

 
Figure 86: Rendered in 2048×1440 with MSAA 4x at 60 fps on a Nexus 9 device (Tegra K1 GPU)
+
Figure ⟨L:3223⟩[mitsubaReference]: Rendered in 2048×1440 in 1 minute and 42 seconds on a 12 core 2013 MacPro

-The parameters used to render both scenes are the following: +

-Filament +

Figure ⟨L:3225⟩[filamentReference]: Rendered in 2048×1440 with MSAA 4x at 60 fps on a Nexus 9 device (Tegra K1 GPU)
+

+

+

+The ⟨L:3227⟩parameters used to render both scenes are the following: +

+Filament⟨L:3229⟩

  • Material @@ -3363,7 +3425,7 @@ The parameters used to render both scenes are the following:
  • ISO: 100

-Mitsuba +Mitsuba⟨L:3247⟩

  • BSDF: roughplastic @@ -3394,84 +3456,84 @@ The parameters used to render both scenes are the following:
    • Sample count: 256

-The full Mitsuba scene can be found as an annex. Both scenes were rendered at the same resolution (2048×1440). +The ⟨L:3264⟩full Mitsuba scene can be found as an annex. Both scenes were rendered at the same resolution (2048×1440).

-   

Comparison

+
   

⟨L:3266⟩Comparison

-

The slight differences between the two renderings come from the various approximations used by Filament: RGBM 256×256 reflection probe, RGBM 1024×1024 background map, Lambert diffuse, split-sum approximation, analytical approximation of the DFG term, etc.

+

The ⟨L:3268⟩slight differences between the two renderings come from the various approximations used by Filament: RGBM 256×256 reflection probe, RGBM 1024×1024 background map, Lambert diffuse, split-sum approximation, analytical approximation of the DFG term, etc.

-Figure 87 shows the luminance gradient of the images produced by both engines. The comparison was performed on LDR images. +Figure ⟨L:3270⟩[referenceComparison] shows the luminance gradient of the images produced by both engines. The comparison was performed on LDR images.

-

 
Figure 87: Luminance gradients from Mitsuba (left) and Filament (right)
+
Figure ⟨L:3272⟩[referenceComparison]: Luminance gradients from Mitsuba (left) and Filament (right)

-The biggest difference is visible at grazing angles, which is most likely explained by Filament's use of a Lambertian diffuse term. The Disney diffuse term and its grazing retro-reflections would move Filament closer to Mitsuba. +The ⟨L:3274⟩biggest difference is visible at grazing angles, which is most likely explained by Filament's use of a Lambertian diffuse term. The Disney diffuse term and its grazing retro-reflections would move Filament closer to Mitsuba.

-   

Coordinates systems

-   

World coordinates system

+
   

⟨L:3276⟩Coordinates systems

+
   

⟨L:3278⟩World coordinates system

-

Filament uses a Y-up, right-handed coordinate system.

+

Filament ⟨L:3280⟩uses a Y-up, right-handed coordinate system.

-

 
Figure 88: Red +X, green +Y, blue +Z (rendered in Marmoset Toolbag).
+
Figure ⟨L:3282⟩[coordinates]: Red +X, green +Y, blue +Z (rendered in Marmoset Toolbag).

-   

Camera coordinates system

+
   

⟨L:3285⟩Camera coordinates system

-

Filament's Camera looks towards its local -Z axis. That is, when placing a camera in the world -without any transform applied to it, the camera looks down the world's -Z axis.

+

Filament's ⟨L:3287⟩Camera looks towards its local -Z axis. That is, when placing a camera in the world +without ⟨L:3288⟩any transform applied to it, the camera looks down the world's -Z axis.

-   

Cubemaps coordinates system

+
   

⟨L:3291⟩Cubemaps coordinates system

-

All cubemaps used in Filament follow the OpenGL convention for face -alignment shown in figure 89.

+

All ⟨L:3293⟩cubemaps used in Filament follow the OpenGL convention for face +alignment ⟨L:3294⟩shown in figure ?.

-

 
Figure 89: Horizontal cross representation of a cubemap following the OpenGL faces alignment convention.
+
Figure ⟨L:3296⟩[cubemapCoordinates]: Horizontal cross representation of a cubemap following the OpenGL faces alignment convention.

-Note that environment background and reflection probes are mirrored (see section 8.6.3.1). +Note ⟨L:3298⟩that environment background and reflection probes are mirrored (see section 8.6.3.1).

-   

Mirroring

+
   

⟨L:3301⟩Mirroring

-

To simplify the rendering of reflections, IBL cubemaps are stored mirrored on the X axis. This is -the default behaviour of the cmgen tool. This means that an IBL cubemap used as environment -background needs to be mirrored again at runtime. -An easy way to achieve this for skyboxes is to use textured back faces. Filament does -this by default.

+

To ⟨L:3303⟩simplify the rendering of reflections, IBL cubemaps are stored mirrored on the X axis. This is +the ⟨L:3304⟩default behaviour of the cmgen tool. This means that an IBL cubemap used as environment +background ⟨L:3305⟩needs to be mirrored again at runtime. +An ⟨L:3306⟩easy way to achieve this for skyboxes is to use textured back faces. Filament does +this ⟨L:3307⟩by default.

-   

Equirectangular environment maps

+
   

⟨L:3310⟩Equirectangular environment maps

-

To convert equirectangular environment maps to horizontal/vertical cross cubemaps we position the -+Z face in the center of the source rectilinear environment map.

+

To ⟨L:3312⟩convert equirectangular environment maps to horizontal/vertical cross cubemaps we position the ++Z ⟨L:3313⟩face in the center of the source rectilinear environment map.

-   

World space orientation of environment maps and Skyboxes

+
   

⟨L:3316⟩World space orientation of environment maps and Skyboxes

-

When specifying a skybox or an IBL in Filament, the specified cubemap is oriented such that its --Z face points towards the +Z axis of the world (this is because filament assumes mirrored cubemaps, -see section 8.6.3.1). However, because environments and skyboxes are expected to be pre-mirrored, -their -Z (back) face points towards the world's -Z axis as expected (and the camera looks toward that -direction by default, see section 8.6.2).

+

When ⟨L:3318⟩specifying a skybox or an IBL in Filament, the specified cubemap is oriented such that its +-Z ⟨L:3319⟩face points towards the +Z axis of the world (this is because filament assumes mirrored cubemaps, +see ⟨L:3320⟩section 8.6.3.1). However, because environments and skyboxes are expected to be pre-mirrored, +their ⟨L:3321⟩-Z (back) face points towards the world's -Z axis as expected (and the camera looks toward that +direction ⟨L:3322⟩by default, see section 8.6.2).

-   

Annex

-   

Specular color

+
   

⟨L:3325⟩Annex

+
   

⟨L:3327⟩Specular color

-

The specular color of a metallic surface, or (\fNormal), can be computed directly from measured spectral data. Online databases such as Refractive Index provide tables of complex IOR measured at different wavelengths for various materials.

+

The ⟨L:3329⟩specular color of a metallic surface, or (\fNormal), can be computed directly from measured spectral data. Online databases such as Refractive Index provide tables of complex IOR measured at different wavelengths for various materials.

-Earlier in this document, we presented equation \(\ref{fresnelEquation}\) to compute the Fresnel reflectance at normal incidence for a dielectric surface given its IOR. The same equation can be rewritten for conductors by using complex numbers to represent the surface's IOR: +Earlier ⟨L:3331⟩in this document, we presented equation \(\ref{fresnelEquation}\) to compute the Fresnel reflectance at normal incidence for a dielectric surface given its IOR. The same equation can be rewritten for conductors by using complex numbers to represent the surface's IOR:

-$$\begin{equation} -c_{ior} = n_{ior} + ik -\end{equation}$$ +$$\begin{equation}⟨L:3333⟩ +c_{ior} ⟨L:3334⟩= n_{ior} + ik +\end{equation}$$⟨L:3335⟩

-Equation \(\ref{fresnelComplexIOR}\) presents the resulting Fresnel formula, where \(c^*\) is the conjugate of the complex number \(c\): +Equation ⟨L:3337⟩\(\ref{fresnelComplexIOR}\) presents the resulting Fresnel formula, where \(c^*\) is the conjugate of the complex number \(c\):

-$$\begin{equation}\label{fresnelComplexIOR} -\fNormal(c_{ior}) = \frac{(c_{ior} - 1)(c_{ior}^* - 1)}{(c_{ior} + 1)(c_{ior}^* + 1)} -\end{equation}$$ +$$\begin{equation}\label{fresnelComplexIOR}⟨L:3339⟩ +\fNormal(c_{ior}) ⟨L:3340⟩= \frac{(c_{ior} - 1)(c_{ior}^* - 1)}{(c_{ior} + 1)(c_{ior}^* + 1)} +\end{equation}$$⟨L:3341⟩

-To compute the specular color of a material we need to evaluate the complex Fresnel equation at each spectral sample of complex IOR over the visible spectrum. For each spectral sample, we obtain a spectral reflectance sample. To find the RGB color at normal incidence, we must multiply each sample by the CIE XYZ CMFs (color matching functions) and the spectral power distribution of the desired illuminant. We choose the standard illuminant D65 because we want to compute a color in the sRGB color space. +To ⟨L:3343⟩compute the specular color of a material we need to evaluate the complex Fresnel equation at each spectral sample of complex IOR over the visible spectrum. For each spectral sample, we obtain a spectral reflectance sample. To find the RGB color at normal incidence, we must multiply each sample by the CIE XYZ CMFs (color matching functions) and the spectral power distribution of the desired illuminant. We choose the standard illuminant D65 because we want to compute a color in the sRGB color space.

-We then sum (integrate) and normalize all the samples to obtain \(\fNormal\) in the XYZ color space. From there, a simple color space conversion yields a linear sRGB color or a non-linear sRGB color after applying the opto-electronic transfer function (OETF, commonly known as “gamma” curve). Note that for some materials such as gold the final sRGB color might fall out of gamut. We use a simple normalization step as a cheap form of gamut remapping but it would be interesting to consider computing values in a color space with a wider gamut (for instance BT.2020). +We ⟨L:3345⟩then sum (integrate) and normalize all the samples to obtain \(\fNormal\) in the XYZ color space. From there, a simple color space conversion yields a linear sRGB color or a non-linear sRGB color after applying the opto-electronic transfer function (OETF, commonly known as “gamma” curve). Note that for some materials such as gold the final sRGB color might fall out of gamut. We use a simple normalization step as a cheap form of gamut remapping but it would be interesting to consider computing values in a color space with a wider gamut (for instance BT.2020).

-To achieve the desired result we used the ICE 1931 2° CMFs, from 360nm to 830nm at 1nm intervals (source), and the CIE Standard Illuminant D65 relative spectral power distribution, from 300nm to 830nm, at 5nm intervals (source). +To ⟨L:3347⟩achieve the desired result we used the ICE 1931 2° CMFs, from 360nm to 830nm at 1nm intervals (source), and the CIE Standard Illuminant D65 relative spectral power distribution, from 300nm to 830nm, at 5nm intervals (source).

-Our implementation is presented in listing 46, with the actual data omitted for brevity. +Our ⟨L:3349⟩implementation is presented in listing 46, with the actual data omitted for brevity.

 
// CIE 1931 2-deg color matching functions (CMFs), from 360nm to 830nm,
 // at 1nm intervals
 //
@@ -3572,84 +3634,84 @@ Our implementation is presented in     return linear;
 }

-

Special thanks to Naty Hoffman for his valuable help on this topic.

+

Special ⟨L:3454⟩thanks to Naty Hoffman for his valuable help on this topic.

-
   

Importance sampling for the IBL

+
   

⟨L:3456⟩Importance sampling for the IBL

-

In the discrete domain, the integral can be approximated with sampling as defined in equation (\ref{iblSampling}).

+

In ⟨L:3458⟩the discrete domain, the integral can be approximated with sampling as defined in equation (\ref{iblSampling}).

-$$\begin{equation}\label{iblSampling} -\Lout(n,v,\Theta) \equiv \frac{1}{N} \sum_{i}^{N} f(l_{i}^{uniform},v,\Theta) L_{\perp}(l_i) \left< n \cdot l_i^{uniform} \right> -\end{equation}$$ +$$\begin{equation}\label{iblSampling}⟨L:3460⟩ +\Lout(n,v,\Theta) ⟨L:3461⟩\equiv \frac{1}{N} \sum_{i}^{N} f(l_{i}^{uniform},v,\Theta) L_{\perp}(l_i) \left< n \cdot l_i^{uniform} \right> +\end{equation}$$⟨L:3462⟩

-Unfortunately, we would need too many samples to evaluate this integral. A technique commonly used -is to choose samples that are more “important” more often, this is called importance sampling. -In our case we'll use the distribution of micro-facets normals, \(D_{ggx}\), as the distribution of -important samples. +Unfortunately, ⟨L:3464⟩we would need too many samples to evaluate this integral. A technique commonly used +is ⟨L:3465⟩to choose samples that are more “important” more often, this is called importance sampling. +In ⟨L:3466⟩our case we'll use the distribution of micro-facets normals, \(D_{ggx}\), as the distribution of +important ⟨L:3467⟩samples.

-The evaluation of \( \Lout(n,v,\Theta) \) with importance sampling is presented in equation \(\ref{annexIblImportanceSampling}\). +The ⟨L:3469⟩evaluation of \( \Lout(n,v,\Theta) \) with importance sampling is presented in equation \(\ref{annexIblImportanceSampling}\).

-$$\begin{equation}\label{annexIblImportanceSampling} -\Lout(n,v,\Theta) \equiv \frac{1}{N} \sum_{i}^{N} \frac{f(l_{i},v,\Theta)}{p(l_i,v,\Theta)} L_{\perp}(l_i) \left< n \cdot l_i \right> -\end{equation}$$ +$$\begin{equation}\label{annexIblImportanceSampling}⟨L:3471⟩ +\Lout(n,v,\Theta) ⟨L:3472⟩\equiv \frac{1}{N} \sum_{i}^{N} \frac{f(l_{i},v,\Theta)}{p(l_i,v,\Theta)} L_{\perp}(l_i) \left< n \cdot l_i \right> +\end{equation}$$⟨L:3473⟩

-In equation \(\ref{annexIblImportanceSampling}\), \(p\) is the probability density function (PDF) of the -distribution of important direction samples \(l_i\). These samples depend on \(h_i\), \(v\) and \(\alpha\). -The definition of the PDF is shown in equation \(\ref{iblPDF}\). +In ⟨L:3475⟩equation \(\ref{annexIblImportanceSampling}\), \(p\) is the probability density function (PDF) of the +distribution ⟨L:3476⟩of important direction samples \(l_i\). These samples depend on \(h_i\), \(v\) and \(\alpha\). +The ⟨L:3477⟩definition of the PDF is shown in equation \(\ref{iblPDF}\).

-\(h_i\) is given by the distribution we chose, see section 9.2.1 for more details. +\(h_i\) ⟨L:3479⟩is given by the distribution we chose, see section 9.2.1 for more details.

-The important direction samples \(l_i\) are calculated as the reflection of \(v\) around \(h_i\), and therefore -do not have the same PDF as \(h_i\). The PDF of a transformed distribution is given by: +The ⟨L:3481⟩important direction samples \(l_i\) are calculated as the reflection of \(v\) around \(h_i\), and therefore +do ⟨L:3482⟩not have the same PDF as \(h_i\). The PDF of a transformed distribution is given by:

-$$\begin{equation} +$$\begin{equation}⟨L:3484⟩ p(T_r(x)) = p(x) |J(T_r)|^{-1} -\end{equation}$$ +\end{equation}$$⟨L:3486⟩

Where \(|J(T_r)|\) is the determinant of the Jacobian of the transform. In our case we're considering -the transform from \(h_i\) to \(l_i\) and the determinant of its Jacobian is given in \ref{iblPDF}. +the ⟨L:3489⟩transform from \(h_i\) to \(l_i\) and the determinant of its Jacobian is given in \ref{iblPDF}.

-$$\begin{equation}\label{iblPDF} +$$\begin{equation}\label{iblPDF}⟨L:3491⟩ p(l,v,\Theta) = D(h,\alpha) \left< \NoH \right> |J_{h \rightarrow l}|^{-1} \\ |J_{h \rightarrow l}| = 4 \left< \VoH \right> -\end{equation}$$ +\end{equation}$$⟨L:3494⟩

-   

Choosing important directions

+
   

⟨L:3496⟩Choosing important directions

-

Refer to section 9.3 for more details. Given a uniform distribution ((\zeta_{\phi},\zeta_{\theta})) the important direction (l) is defined by equation (\ref{importantDirection}).

+

Refer ⟨L:3498⟩to section 9.3 for more details. Given a uniform distribution ((\zeta_{\phi},\zeta_{\theta})) the important direction (l) is defined by equation (\ref{importantDirection}).

-$$\begin{equation}\label{importantDirection} -\phi = 2 \pi \zeta_{\phi} \\ -\theta = cos^{-1} \sqrt{\frac{1 - \zeta_{\theta}}{(\alpha^2 - 1)\zeta_{\theta}+1}} \\ -l = \{ cos \phi sin \theta, sin \phi sin \theta, cos \theta \} -\end{equation}$$ +$$\begin{equation}\label{importantDirection}⟨L:3500⟩ +\phi ⟨L:3501⟩= 2 \pi \zeta_{\phi} \\ +\theta ⟨L:3502⟩= cos^{-1} \sqrt{\frac{1 - \zeta_{\theta}}{(\alpha^2 - 1)\zeta_{\theta}+1}} \\ +l ⟨L:3503⟩= \{ cos \phi sin \theta, sin \phi sin \theta, cos \theta \} +\end{equation}$$⟨L:3504⟩

-Typically, \( (\zeta_{\phi},\zeta_{\theta}) \) are chosen using the Hammersley uniform distribution algorithm described in section 9.4. +Typically, ⟨L:3506⟩\( (\zeta_{\phi},\zeta_{\theta}) \) are chosen using the Hammersley uniform distribution algorithm described in section 9.4.

-   

Pre-filtered importance sampling

+
   

⟨L:3508⟩Pre-filtered importance sampling

-

Importance sampling considers only the PDF to generate important directions; in particular, it is oblivious to the actual content of the IBL. If the latter contains high frequencies in areas without a lot of samples, the integration won’t be accurate. This can be somewhat mitigated by using a technique called pre-filtered importance sampling, in addition this allows the integral to converge with many fewer samples.

+

Importance ⟨L:3510⟩sampling considers only the PDF to generate important directions; in particular, it is oblivious to the actual content of the IBL. If the latter contains high frequencies in areas without a lot of samples, the integration won’t be accurate. This can be somewhat mitigated by using a technique called pre-filtered importance sampling, in addition this allows the integral to converge with many fewer samples.

-Pre-filtered importance sampling uses several images of the environment increasingly low-pass filtered. This is typically implemented very efficiently with mipmaps and a box filter. The LOD is selected based on the sample importance, that is, low probability samples use a higher LOD index (more filtered). +Pre-filtered ⟨L:3512⟩importance sampling uses several images of the environment increasingly low-pass filtered. This is typically implemented very efficiently with mipmaps and a box filter. The LOD is selected based on the sample importance, that is, low probability samples use a higher LOD index (more filtered).

-This technique is described in details in [Krivanek08]. +This ⟨L:3514⟩technique is described in details in [Krivanek08].

-The cubemap LOD is determined in the following way: +The ⟨L:3516⟩cubemap LOD is determined in the following way:

-$$\begin{align*} -lod &= log_4 \left( K\frac{\Omega_s}{\Omega_p} \right) \\ -K &= 4.0 \\ -\Omega_s &= \frac{1}{N \cdot p(l_i)} \\ -\Omega_p &\approx \frac{4\pi}{6 \cdot width \cdot height} -\end{align*}$$ +$$\begin{align*}⟨L:3518⟩ +lod ⟨L:3519⟩&= log_4 \left( K\frac{\Omega_s}{\Omega_p} \right) \\ +K ⟨L:3520⟩&= 4.0 \\ +\Omega_s ⟨L:3521⟩&= \frac{1}{N \cdot p(l_i)} \\ +\Omega_p ⟨L:3522⟩&\approx \frac{4\pi}{6 \cdot width \cdot height} +\end{align*}$$⟨L:3523⟩

-Where \(K\) is a constant determined empirically, \(p\) the PDF of the BRDF, \( \Omega_{s} \) the solid angle associated to the sample and \(\Omega_p\) the solid angle associated with the texel in the cubemap. +Where ⟨L:3525⟩\(K\) is a constant determined empirically, \(p\) the PDF of the BRDF, \( \Omega_{s} \) the solid angle associated to the sample and \(\Omega_p\) the solid angle associated with the texel in the cubemap.

-Cubemap sampling is done using seamless trilinear filtering. It is extremely important to sample the cubemap correctly across faces using OpenGL's seamless sampling feature or any other technique that avoids/reduces seams. +Cubemap ⟨L:3527⟩sampling is done using seamless trilinear filtering. It is extremely important to sample the cubemap correctly across faces using OpenGL's seamless sampling feature or any other technique that avoids/reduces seams.

-Table 17 shows a comparison between importance sampling and pre-filtered importance sampling when applied to figure 90. +Table ⟨L:3529⟩[importanceSamplingViz] shows a comparison between importance sampling and pre-filtered importance sampling when applied to figure ?.

-

 
Figure 90: Importance sampling image reference
+
Figure ⟨L:3531⟩[importanceSamplingRef]: Importance sampling image reference

  @@ -3658,92 +3720,63 @@ Cubemap sampling is done using seamless trilinear filtering. It is extremely imp
Samples Importance sampling Pre-filtered importance sampling
32
Table 17: Importance sampling vs pre-filtered importance sampling with \(\alpha = 0.4\)

-The reference renderer used in the comparison below performs no approximation. In particular, it does not assume \(v = n\) and does not perform the split sum approximation. The pre-filtered renderer uses all the techniques discussed in this section: pre-filtered cubemaps, the analytic formulation of the DFG term, and of course the split sum approximation. +The ⟨L:3541⟩reference renderer used in the comparison below performs no approximation. In particular, it does not assume \(v = n\) and does not perform the split sum approximation. The pre-filtered renderer uses all the techniques discussed in this section: pre-filtered cubemaps, the analytic formulation of the DFG term, and of course the split sum approximation.

-Left: reference renderer, right: pre-filtered importance sampling. +Left: ⟨L:3543⟩reference renderer, right: pre-filtered importance sampling.

-
-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

-

+ ⟨L:3545⟩ + ⟨L:3546⟩ + ⟨L:3547⟩ + ⟨L:3548⟩

-   

Choosing important directions for sampling the BRDF

+
   

⟨L:3550⟩Choosing important directions for sampling the BRDF

-

For simplicity we use the ( D ) term of the BRDF as the PDF, however the PDF must be normalized such that the integral over the hemisphere is 1:

+

For ⟨L:3552⟩simplicity we use the ( D ) term of the BRDF as the PDF, however the PDF must be normalized such that the integral over the hemisphere is 1:

-$$\begin{equation} -\int_{\Omega}p(m)dm = 1 \\ -\int_{\Omega}D(m)(n \cdot m)dm = 1 \\ -\int_{\phi=0}^{2\pi}\int_{\theta=0}^{\frac{\pi}{2}}D(\theta,\phi) cos \theta sin \theta d\theta d\phi = 1 \\ -\end{equation}$$ +$$\begin{equation}⟨L:3554⟩ +\int_{\Omega}p(m)dm ⟨L:3555⟩= 1 \\ +\int_{\Omega}D(m)(n ⟨L:3556⟩\cdot m)dm = 1 \\ +\int_{\phi=0}^{2\pi}\int_{\theta=0}^{\frac{\pi}{2}}D(\theta,\phi) ⟨L:3557⟩cos \theta sin \theta d\theta d\phi = 1 \\ +\end{equation}$$⟨L:3558⟩

-The PDF of the BRDF can therefore be expressed as in equation \(\ref{importantPDF}\): +The ⟨L:3560⟩PDF of the BRDF can therefore be expressed as in equation \(\ref{importantPDF}\):

-$$\begin{equation}\label{importantPDF} -p(\theta,\phi) = \frac{\alpha^2}{\pi(cos^2\theta (\alpha^2-1) + 1)^2} cos\theta sin\theta -\end{equation}$$ +$$\begin{equation}\label{importantPDF}⟨L:3562⟩ +p(\theta,\phi) ⟨L:3563⟩= \frac{\alpha^2}{\pi(cos^2\theta (\alpha^2-1) + 1)^2} cos\theta sin\theta +\end{equation}$$⟨L:3564⟩

-The term \(sin\theta\) comes from the differential solid angle \(sin\theta d\phi d\theta\) since we integrate over a sphere. We sample \(\theta\) and \(\phi\) independently: +The ⟨L:3566⟩term \(sin\theta\) comes from the differential solid angle \(sin\theta d\phi d\theta\) since we integrate over a sphere. We sample \(\theta\) and \(\phi\) independently:

-$$\begin{align*} -p(\theta) &= \int_0^{2\pi} p(\theta,\phi) d\phi = \frac{2\alpha^2}{(cos^2\theta (\alpha^2-1) + 1)^2} cos\theta sin\theta \\ -p(\phi) &= \frac{p(\theta,\phi)}{p(\phi)} = \frac{1}{2\pi} -\end{align*}$$ +$$\begin{align*}⟨L:3568⟩ +p(\theta) ⟨L:3569⟩&= \int_0^{2\pi} p(\theta,\phi) d\phi = \frac{2\alpha^2}{(cos^2\theta (\alpha^2-1) + 1)^2} cos\theta sin\theta \\ +p(\phi) ⟨L:3570⟩&= \frac{p(\theta,\phi)}{p(\phi)} = \frac{1}{2\pi} +\end{align*}$$⟨L:3571⟩

-The expression of \( p(\phi) \) is true for an isotropic distribution of normals. +The ⟨L:3573⟩expression of \( p(\phi) \) is true for an isotropic distribution of normals.

-We then calculate the cumulative distribution function (CDF) for each variable: +We ⟨L:3575⟩then calculate the cumulative distribution function (CDF) for each variable:

-$$\begin{align*} -P(s_{\phi}) &= \int_{0}^{s_{\phi}} p(\phi) d\phi = \frac{s_{\phi}}{2\pi} \\ -P(s_{\theta}) &= \int_{0}^{s_{\theta}} p(\theta) d\theta = 2 \alpha^2 \left( \frac{1}{(2\alpha^4-4\alpha^2+2) cos(s_{\theta})^2 + 2\alpha^2 - 2} - \frac{1}{2\alpha^4-2\alpha^2} \right) -\end{align*}$$ +$$\begin{align*}⟨L:3577⟩ +P(s_{\phi}) ⟨L:3578⟩&= \int_{0}^{s_{\phi}} p(\phi) d\phi = \frac{s_{\phi}}{2\pi} \\ +P(s_{\theta}) ⟨L:3579⟩&= \int_{0}^{s_{\theta}} p(\theta) d\theta = 2 \alpha^2 \left( \frac{1}{(2\alpha^4-4\alpha^2+2) cos(s_{\theta})^2 + 2\alpha^2 - 2} - \frac{1}{2\alpha^4-2\alpha^2} \right) +\end{align*}$$⟨L:3580⟩

-We set \( P(s_{\phi}) \) and \( P(s_{\theta}) \) to random variables \( \zeta_{\phi} \) and \( \zeta_{\theta} \) and solve for \( s_{\phi} \) and \( s_{\theta} \) respectively: +We ⟨L:3582⟩set \( P(s_{\phi}) \) and \( P(s_{\theta}) \) to random variables \( \zeta_{\phi} \) and \( \zeta_{\theta} \) and solve for \( s_{\phi} \) and \( s_{\theta} \) respectively:

-$$\begin{align*} -P(s_{\phi}) &= \zeta_{\phi} \rightarrow s_{\phi} = 2\pi\zeta_{\phi} \\ -P(s_{\theta}) &= \zeta_{\theta} \rightarrow s_{\theta} = cos^{-1} \sqrt{\frac{1-\zeta_{\theta}}{(\alpha^2-1)\zeta_{\theta}+1}} -\end{align*}$$ +$$\begin{align*}⟨L:3584⟩ +P(s_{\phi}) ⟨L:3585⟩&= \zeta_{\phi} \rightarrow s_{\phi} = 2\pi\zeta_{\phi} \\ +P(s_{\theta}) ⟨L:3586⟩&= \zeta_{\theta} \rightarrow s_{\theta} = cos^{-1} \sqrt{\frac{1-\zeta_{\theta}}{(\alpha^2-1)\zeta_{\theta}+1}} +\end{align*}$$⟨L:3587⟩

-So given a uniform distribution \( (\zeta_{\phi},\zeta_{\theta}) \), our important direction \(l\) is defined as: +So ⟨L:3589⟩given a uniform distribution \( (\zeta_{\phi},\zeta_{\theta}) \), our important direction \(l\) is defined as:

-$$\begin{align*} -\phi &= 2\pi\zeta_{\phi} \\ -\theta &= cos^{-1} \sqrt{\frac{1-\zeta_{\theta}}{(\alpha^2-1)\zeta_{\theta}+1}} \\ -l &= \{ cos\phi sin\theta,sin\phi sin\theta,cos\theta \} -\end{align*}$$ +$$\begin{align*}⟨L:3591⟩ +\phi ⟨L:3592⟩&= 2\pi\zeta_{\phi} \\ +\theta ⟨L:3593⟩&= cos^{-1} \sqrt{\frac{1-\zeta_{\theta}}{(\alpha^2-1)\zeta_{\theta}+1}} \\ +l ⟨L:3594⟩&= \{ cos\phi sin\theta,sin\phi sin\theta,cos\theta \} +\end{align*}$$⟨L:3595⟩

-   

Hammersley sequence

+
   

⟨L:3597⟩Hammersley sequence

vec2f hammersley(uint i, float numSamples) {
     uint bits = i;
     bits = (bits << 16) | (bits >> 16);
@@ -3752,10 +3785,10 @@ l &= \{ cos\phi sin\theta,sin\phi sin\theta,cos\theta \}
     bits = ((bits & 0x0F0F0F0F) << 4) | ((bits & 0xF0F0F0F0) >> 4);
     bits = ((bits & 0x00FF00FF) << 8) | ((bits & 0xFF00FF00) >> 8);
     return vec2f(i / numSamples, bits / exp2(32));
-}
[C++ implementation of a Hammersley sequence generator]
-   

Precomputing L for image-based lighting

+}
[C++ ⟨L:3610⟩implementation of a Hammersley sequence generator]
+
   

⟨L:3612⟩Precomputing L for image-based lighting

-

The term ( L_{DFG} ) is only dependent on ( \NoV ). Below, the normal is arbitrarily set to ( n=\left[0, 0, 1\right] ) and (v) is chosen to satisfy ( \NoV ). The vector ( h_i ) is the ( D_{GGX}(\alpha) ) important direction sample (i).

+

The ⟨L:3614⟩term ( L_{DFG} ) is only dependent on ( \NoV ). Below, the normal is arbitrarily set to ( n=\left[0, 0, 1\right] ) and (v) is chosen to satisfy ( \NoV ). The vector ( h_i ) is the ( D_{GGX}(\alpha) ) important direction sample (i).

float GDFG(float NoV, float NoL, float a) {
     float a2 = a * a;
     float GGXL = NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);
@@ -3788,81 +3821,81 @@ l &= \{ cos\phi sin\theta,sin\phi sin\theta,cos\theta \}
         }
     }
     return r * (1.0f / sampleCount);
-}
[C++ implementation of the \( L_{DFG} \) term]
-   

Spherical Harmonics

+}
[C++ ⟨L:3651⟩implementation of the \( L_{DFG} \) term]
+
   

⟨L:3653⟩Spherical Harmonics

-  - - - - + 
Symbol Definition
\(K^m_l\) Normalization factors
\(P^m_l(x)\) Associated Legendre polynomials
\(y^m_l\) Spherical harmonics bases, or SH bases
\(L^m_l\) SH coefficients of the \(L(s)\) function defined on the unit sphere
+ + + +
Symbol ⟨L:3655⟩ Definition
\(K^m_l\) ⟨L:3657⟩ Normalization factors
\(P^m_l(x)\) ⟨L:3658⟩ Associated Legendre polynomials
\(y^m_l\) ⟨L:3659⟩ Spherical harmonics bases, or SH bases
\(L^m_l\) ⟨L:3660⟩ SH coefficients of the \(L(s)\) function defined on the unit sphere
Table 18: Spherical harmonics symbols definitions

-   

Basis functions

+
   

⟨L:3663⟩Basis functions

-

Spherical parameterization of points on the surface of the unit sphere:

+

Spherical ⟨L:3665⟩parameterization of points on the surface of the unit sphere:

-$$\begin{equation} -\{ x, y, z \} = \{ cos \phi sin \theta, sin \phi sin \theta, cos \theta \} -\end{equation}$$ +$$\begin{equation}⟨L:3667⟩ +\{ ⟨L:3668⟩x, y, z \} = \{ cos \phi sin \theta, sin \phi sin \theta, cos \theta \} +\end{equation}$$⟨L:3669⟩

-The complex spherical harmonics bases are given by: +The ⟨L:3671⟩complex spherical harmonics bases are given by:

-$$\begin{equation} +$$\begin{equation}⟨L:3673⟩ Y^m_l(\theta, \phi) = K^m_l e^{im\theta} P^{|m|}_l(cos \theta), l \in N, -l <= m <= l -\end{equation}$$ +\end{equation}$$⟨L:3675⟩

-However we only need the real bases: +However ⟨L:3677⟩we only need the real bases:

-$$\begin{align*} -y^{m > 0}_l &= \sqrt{2} K^m_l cos(m \phi) P^m_l(cos \theta) \\ +$$\begin{align*}⟨L:3679⟩ +y^{m ⟨L:3680⟩> 0}_l &= \sqrt{2} K^m_l cos(m \phi) P^m_l(cos \theta) \\ y^{m < 0}_l &= \sqrt{2} K^m_l sin(|m| \phi) P^{|m|}_l(cos \theta) \\ -y^0_l &= K^0_l P^0_l(cos \theta) -\end{align*}$$ +y^0_l ⟨L:3682⟩&= K^0_l P^0_l(cos \theta) +\end{align*}$$⟨L:3683⟩

-The normalization factors are given by: +The ⟨L:3685⟩normalization factors are given by:

-$$\begin{equation} +$$\begin{equation}⟨L:3687⟩ K^m_l = \sqrt{\frac{(2l + 1)(l - |m|)!}{4 \pi (l + |m|)!}} -\end{equation}$$ +\end{equation}$$⟨L:3689⟩

The associated Legendre polynomials \(P^{|m|}_l\) can be calculated from the following recursions:

-$$\begin{equation}\label{shRecursions} -P^0_0(x) = 1 \\ -P^0_1(x) = x \\ -P^l_l(x) = (-1)^l (2l - 1)!! (1 - x^2)^{\frac{l}{2}} \\ -P^m_l(x) = \frac{((2l - 1) x P^m_{l - 1} - (l + m - 1) P^m_{l - 2})}{l - m} \\ -\end{equation}$$ +$$\begin{equation}\label{shRecursions}⟨L:3693⟩ +P^0_0(x) ⟨L:3694⟩= 1 \\ +P^0_1(x) ⟨L:3695⟩= x \\ +P^l_l(x) ⟨L:3696⟩= (-1)^l (2l - 1)!! (1 - x^2)^{\frac{l}{2}} \\ +P^m_l(x) ⟨L:3697⟩= \frac{((2l - 1) x P^m_{l - 1} - (l + m - 1) P^m_{l - 2})}{l - m} \\ +\end{equation}$$⟨L:3698⟩

Computing \(y^{|m|}_l\) requires to compute \(P^{|m|}_l(z)\) first. -This can be accomplished fairly easily using the recursions in equation \(\ref{shRecursions}\). -The third recursion can be used to “move diagonally” in table 20, i.e. calculating \(y^0_0\), \(y^1_1\), \(y^2_2\) etc. -Then, the fourth recursion can be used to move vertically. +This ⟨L:3701⟩can be accomplished fairly easily using the recursions in equation \(\ref{shRecursions}\). +The ⟨L:3702⟩third recursion can be used to “move diagonally” in table 20, i.e. calculating \(y^0_0\), \(y^1_1\), \(y^2_2\) etc. +Then, ⟨L:3703⟩the fourth recursion can be used to move vertically.

-  - - - + 
Band index Basis functions \(-l <= m <= l\)
\(l = 0\) \(y^0_0\)
\(l = 1\) \(y^{-1}_1\) \(y^0_1\) \(y^1_1\)
\(l = 2\) \(y^{-2}_2\) \(y^{-1}_2\) \(y^0_2\) \(y^1_2\) \(y^2_2\)
+ + +
Band ⟨L:3705⟩index Basis functions \(-l <= m <= l\)
\(l ⟨L:3707⟩= 0\) \(y^0_0\)
\(l ⟨L:3708⟩= 1\) \(y^{-1}_1\) \(y^0_1\) \(y^1_1\)
\(l ⟨L:3709⟩= 2\) \(y^{-2}_2\) \(y^{-1}_2\) \(y^0_2\) \(y^1_2\) \(y^2_2\)
Table 19: Basis functions per band

-It’s also fairly easy to compute the trigonometric terms recursively: +It’s ⟨L:3712⟩also fairly easy to compute the trigonometric terms recursively:

-$$\begin{align*} -C_m &\equiv cos(m \phi)sin(\theta)^m \\ -S_m &\equiv sin(m \phi)sin(\theta)^m \\ -\{ x, y, z \} &= \{ cos \phi sin \theta, sin \phi sin \theta, cos \theta \} -\end{align*}$$ +$$\begin{align*}⟨L:3714⟩ +C_m ⟨L:3715⟩&\equiv cos(m \phi)sin(\theta)^m \\ +S_m ⟨L:3716⟩&\equiv sin(m \phi)sin(\theta)^m \\ +\{ ⟨L:3717⟩x, y, z \} &= \{ cos \phi sin \theta, sin \phi sin \theta, cos \theta \} +\end{align*}$$⟨L:3718⟩

-Using the angle sum trigonometric identities: +Using ⟨L:3720⟩the angle sum trigonometric identities:

-$$\begin{align*} -cos(m \phi + \phi) &= cos(m \phi) cos(\phi) - sin(m \phi) sin(\phi) \Leftrightarrow C_{m + 1} = x C_m - y S_m \\ -sin(m \phi + \phi) &= sin(m \phi) cos(\phi) + cos(m \phi) sin(\phi) \Leftrightarrow S_{m + 1} = x S_m - y C_m -\end{align*}$$ +$$\begin{align*}⟨L:3722⟩ +cos(m ⟨L:3723⟩\phi + \phi) &= cos(m \phi) cos(\phi) - sin(m \phi) sin(\phi) \Leftrightarrow C_{m + 1} = x C_m - y S_m \\ +sin(m ⟨L:3724⟩\phi + \phi) &= sin(m \phi) cos(\phi) + cos(m \phi) sin(\phi) \Leftrightarrow S_{m + 1} = x S_m - y C_m +\end{align*}$$⟨L:3725⟩

-Listing 47 shows the C++ code to compute the non-normalized SH basis \(\frac{y^m_l(s)}{\sqrt{2} K^m_l}\): +Listing ⟨L:3728⟩[nonNormalizedSHBasis] shows the C++ code to compute the non-normalized SH basis \(\frac{y^m_l(s)}{\sqrt{2} K^m_l}\):

 
static inline size_t SHindex(ssize_t m, size_t l) {
     return l * (l + 1) + m;
 }
@@ -3918,7 +3951,7 @@ sin(m \phi + \phi) &= sin(m \phi) cos(\phi) + cos(m \phi) sin(\phi) \Leftrightar
     }
 }
Listing 47: C++ implementation to compute a non-normalized SH basis

-

Normalized SH basis functions (y^m_l(s)) for the first 3 bands:

+

Normalized ⟨L:3788⟩SH basis functions (y^m_l(s)) for the first 3 bands:

  @@ -3926,71 +3959,71 @@ sin(m \phi + \phi) &= sin(m \phi) cos(\phi) + cos(m \phi) sin(\phi) \Leftrightar
Band \(m = -2\) \(m = -1\) \(m = 0\) \(m = 1\) \(m = 2\)
\(l = 0\) \(\frac{1}{2}\sqrt{\frac{1}{\pi}}\)
\(l = 2\) \(\frac{1}{2}\sqrt{\frac{15}{\pi}}xy\) \(-\frac{1}{2}\sqrt{\frac{15}{\pi}}yz\) \(\frac{1}{4}\sqrt{\frac{5}{\pi}}(2z^2 - x^2 - y^2)\) \(-\frac{1}{2}\sqrt{\frac{15}{\pi}}xz\) \(\frac{1}{4}\sqrt{\frac{15}{\pi}}(x^2 - y^2)\)
Table 20: Normalized basis functions per band

-   

Decomposition and reconstruction

+
   

⟨L:3797⟩Decomposition and reconstruction

-

A function (L(s)) defined on a sphere is projected to the SH basis as follows:

+

A ⟨L:3799⟩function (L(s)) defined on a sphere is projected to the SH basis as follows:

-$$\begin{equation} -L^m_l = \int_\Omega L(s) y^m_l(s) ds \\ -L^m_l = \int_{\theta = 0}^{\pi} \int_{\phi = 0}^{2\pi} L(\theta, \phi) y^m_l(\theta, \phi) sin \theta d\theta d\phi -\end{equation}$$ +$$\begin{equation}⟨L:3801⟩ +L^m_l ⟨L:3802⟩= \int_\Omega L(s) y^m_l(s) ds \\ +L^m_l ⟨L:3803⟩= \int_{\theta = 0}^{\pi} \int_{\phi = 0}^{2\pi} L(\theta, \phi) y^m_l(\theta, \phi) sin \theta d\theta d\phi +\end{equation}$$⟨L:3804⟩

-Note that each \(L^m_l\) is a vector of 3 values, one for each RGB color channel. +Note ⟨L:3806⟩that each \(L^m_l\) is a vector of 3 values, one for each RGB color channel.

-The inverse transformation, or reconstruction, or rendering, from the SH coefficients is given by: +The ⟨L:3808⟩inverse transformation, or reconstruction, or rendering, from the SH coefficients is given by:

-$$\begin{equation} -\hat{L}(s) = \sum_l \sum_{m = -l}^l L^m_l y^m_l(s) -\end{equation}$$ +$$\begin{equation}⟨L:3810⟩ +\hat{L}(s) ⟨L:3811⟩= \sum_l \sum_{m = -l}^l L^m_l y^m_l(s) +\end{equation}$$⟨L:3812⟩

-   

Decomposition of \(\left< cos \theta \right>\)

+
   

⟨L:3814⟩Decomposition of \(\left< cos \theta \right>\)

-

Since (\left< cos \theta \right>) does not depend on (\phi) (azimuthal independence), the integral simplifies to:

+

Since ⟨L:3816⟩(\left< cos \theta \right>) does not depend on (\phi) (azimuthal independence), the integral simplifies to:

-$$\begin{align*} -C^0_l &= 2\pi \int_0^{\pi} \left< cos \theta \right> y^0_l(\theta) sin \theta d\theta \\ -C^0_l &= 2\pi K^m_l \int_0^{\frac{\pi}{2}} P^0_l(cos \theta) cos \theta sin \theta d\theta \\ -C^m_l &= 0, m != 0 -\end{align*}$$ +$$\begin{align*}⟨L:3818⟩ +C^0_l ⟨L:3819⟩&= 2\pi \int_0^{\pi} \left< cos \theta \right> y^0_l(\theta) sin \theta d\theta \\ +C^0_l ⟨L:3820⟩&= 2\pi K^m_l \int_0^{\frac{\pi}{2}} P^0_l(cos \theta) cos \theta sin \theta d\theta \\ +C^m_l ⟨L:3821⟩&= 0, m != 0 +\end{align*}$$⟨L:3822⟩

-In [Ramamoorthi01] an analytical solution to the integral is described: +In ⟨L:3824⟩[Ramamoorthi01] an analytical solution to the integral is described:

-$$\begin{align*} -C_1 &= \sqrt{\frac{\pi}{3}} \\ -C_{odd} &= 0 \\ -C_{l, even} &= 2\pi \sqrt{\frac{2l + 1}{4\pi}} \frac{(-1)^{\frac{l}{2} - 1}}{(l + 2)(l - 1)} \frac{l!}{2^l (\frac{l!}{2})^2} -\end{align*}$$ +$$\begin{align*}⟨L:3826⟩ +C_1 ⟨L:3827⟩&= \sqrt{\frac{\pi}{3}} \\ +C_{odd} ⟨L:3828⟩&= 0 \\ +C_{l, ⟨L:3829⟩even} &= 2\pi \sqrt{\frac{2l + 1}{4\pi}} \frac{(-1)^{\frac{l}{2} - 1}}{(l + 2)(l - 1)} \frac{l!}{2^l (\frac{l!}{2})^2} +\end{align*}$$⟨L:3830⟩

-The first few coefficients are: +The ⟨L:3832⟩first few coefficients are:

-$$\begin{align*} -C_0 &= +0.88623 \\ -C_1 &= +1.02333 \\ -C_2 &= +0.49542 \\ -C_3 &= +0.00000 \\ -C_4 &= -0.11078 -\end{align*}$$ +$$\begin{align*}⟨L:3834⟩ +C_0 ⟨L:3835⟩&= +0.88623 \\ +C_1 ⟨L:3836⟩&= +1.02333 \\ +C_2 ⟨L:3837⟩&= +0.49542 \\ +C_3 ⟨L:3838⟩&= +0.00000 \\ +C_4 ⟨L:3839⟩&= -0.11078 +\end{align*}$$⟨L:3840⟩

-Very few coefficients are needed to reasonably approximate \(\left< cos \theta \right>\), as shown in figure 91. +Very ⟨L:3842⟩few coefficients are needed to reasonably approximate \(\left< cos \theta \right>\), as shown in figure ?.

-

 
Figure 91: Approximation of \(cos \theta\) with SH coefficients
+
Figure ⟨L:3844⟩[shCosThetaApprox]: Approximation of \(cos \theta\) with SH coefficients

-   

Convolution

+
   

⟨L:3846⟩Convolution

-

Convolutions by a kernel (h) that has a circular symmetry can be applied directly and easily in SH space:

+

Convolutions ⟨L:3848⟩by a kernel (h) that has a circular symmetry can be applied directly and easily in SH space:

-$$\begin{equation} -(h * f)^m_l = \sqrt{\frac{4\pi}{2l + 1}} h^0_l(s) f^m_l(s) -\end{equation}$$ +$$\begin{equation}⟨L:3850⟩ +(h ⟨L:3851⟩* f)^m_l = \sqrt{\frac{4\pi}{2l + 1}} h^0_l(s) f^m_l(s) +\end{equation}$$⟨L:3852⟩

-Conveniently, \(\sqrt{\frac{4\pi}{2l + 1}} = \frac{1}{K^0_l}\), so in practice we pre-multiply \(C_l\) by \(\frac{1}{K^0_l}\) and we get a simpler expression: +Conveniently, ⟨L:3854⟩\(\sqrt{\frac{4\pi}{2l + 1}} = \frac{1}{K^0_l}\), so in practice we pre-multiply \(C_l\) by \(\frac{1}{K^0_l}\) and we get a simpler expression:

-$$\begin{equation} -\hat{C}_{l, even} = 2\pi \frac{(-1)^{\frac{l}{2} - 1}}{(l + 2)(l - 1)} \frac{l!}{2^l (\frac{l!}{2})^2} \\ -\hat{C}_1 = \frac{2\pi}{3} -\end{equation}$$ +$$\begin{equation}⟨L:3856⟩ +\hat{C}_{l, ⟨L:3857⟩even} = 2\pi \frac{(-1)^{\frac{l}{2} - 1}}{(l + 2)(l - 1)} \frac{l!}{2^l (\frac{l!}{2})^2} \\ +\hat{C}_1 ⟨L:3858⟩= \frac{2\pi}{3} +\end{equation}$$⟨L:3859⟩

-Here is the C++ code to compute \(\hat{C}_l\): +Here ⟨L:3861⟩is the C++ code to compute \(\hat{C}_l\):

static double factorial(size_t n, size_t d = 1);
 
 // < cos(theta) > SH coefficients pre-multiplied by 1 / K(0,l)
@@ -4027,7 +4060,7 @@ Here is the C++ code to compute \(\hat{C}_l\):
    }
    return r;
 }
-   

Sample validation scene for Mitsuba

+
   

⟨L:3902⟩Sample validation scene for Mitsuba

<scene version="0.5.0">
     <integrator type="path"/>
 
@@ -4076,14 +4109,14 @@ Here is the C++ code to compute \(\hat{C}_l\):
         </film>
     </sensor>
 </scene>
-   

Light assignment with froxels

+
   

⟨L:3955⟩Light assignment with froxels

-

Assigning lights to froxels can be implemented on the GPU using two compute shaders. The first one, shown in listing 48, creates the froxels data (4 planes + a min Z and max Z per froxel) in an SSBO and needs to be run only once. The shader requires the following uniforms:

+

Assigning ⟨L:3957⟩lights to froxels can be implemented on the GPU using two compute shaders. The first one, shown in listing 48, creates the froxels data (4 planes + a min Z and max Z per froxel) in an SSBO and needs to be run only once. The shader requires the following uniforms:

-

Projection matrix

The projection matrix used to render the scene (view space to clip space transformation). -

Inverse projection matrix

The inverse of the projection matrix used to render the scene (clip space to view space transformation). -

Depth parameters

\(-log2(\frac{z_{lighnear}}{z_{far}}) \frac{1}{maxSlices-1}\), maximum number of depth slices, Z near and Z far. -

Clip space size

\(\frac{F_x \times F_r}{w} \times 2\), with \(F_x\) the number of tiles on the X axis, \(F_r\) the resolution in pixels of a tile and w the width in pixels of the render target. +

 Projection matrix⟨L:3959⟩

⟨L:3960⟩The projection matrix used to render the scene (view space to clip space transformation). +

 Inverse projection matrix⟨L:3962⟩

⟨L:3963⟩The inverse of the projection matrix used to render the scene (clip space to view space transformation). +

 Depth parameters⟨L:3965⟩

⟨L:3966⟩\(-log2(\frac{z_{lighnear}}{z_{far}}) \frac{1}{maxSlices-1}\), maximum number of depth slices, Z near and Z far. +

 Clip space size⟨L:3968⟩

⟨L:3969⟩\(\frac{F_x \times F_r}{w} \times 2\), with \(F_x\) the number of tiles on the X axis, \(F_r\) the resolution in pixels of a tile and w the width in pixels of the render target.

 
#version 310 es
 
 precision highp float;
@@ -4191,12 +4224,12 @@ Here is the C++ code to compute \(\hat{C}_l\):
     }
 }
Listing 48: GLSL implementation of froxels data generation (compute shader)

-

The second compute shader, shown in listing 49, runs every frame (if the camera and/or lights have changed) and assigns all the lights to their respective froxels. This shader relies only on a couple of uniforms (the number of point/spot lights and the view matrix) and four SSBOs:

+

The ⟨L:4081⟩second compute shader, shown in listing 49, runs every frame (if the camera and/or lights have changed) and assigns all the lights to their respective froxels. This shader relies only on a couple of uniforms (the number of point/spot lights and the view matrix) and four SSBOs:

-

Light index buffer

For each froxel, the index of each light that affects said froxel. The indices for point lights are written first and if there is enough space left, the indices for spot lights are written as well. A sentinel of value 0×7fffffffu separates point and spot lights and/or marks the end of the froxel's list of lights. Each froxel has a maximum number of lights (point + spot). -

Point lights buffer

Array of structures describing the scene's point lights. -

Spot lights buffer

Array of structures describing the scene's spot lights. -

Froxels buffer

The list of froxels represented by planes, created by the previous compute shader. +

 Light ⟨L:4083⟩index buffer

⟨L:4084⟩For each froxel, the index of each light that affects said froxel. The indices for point lights are written first and if there is enough space left, the indices for spot lights are written as well. A sentinel of value 0×7fffffffu separates point and spot lights and/or marks the end of the froxel's list of lights. Each froxel has a maximum number of lights (point + spot). +

 Point ⟨L:4086⟩lights buffer

⟨L:4087⟩Array of structures describing the scene's point lights. +

 Spot ⟨L:4089⟩lights buffer

⟨L:4090⟩Array of structures describing the scene's spot lights. +

 Froxels ⟨L:4092⟩buffer

⟨L:4093⟩The list of froxels represented by planes, created by the previous compute shader.

 
#version 310 es
 precision highp float;
 precision highp int;
@@ -4322,45 +4355,43 @@ Here is the C++ code to compute \(\hat{C}_l\):
         }
     }
 }
Listing 49: GLSL implementation of assigning lights to froxels (compute shader)
-   

Revisions

+
   

⟨L:4224⟩Revisions

-
 Friday
3 August 2018
First public version
-

-

 Tuesday
7 August 2018
Cloth model
-

-

    -
  • Added description of the “Charlie” NDF
-

-

 Thursday
9 August 2018
Lighting
-

-

    -
  • Added explanation about pre-exposed lights
-

-

 Wednesday
15 August 2018
Fresnel
-

-

    -
  • Added a description of the Fresnel effect in section 4.4.3
-

-

 Friday
17 August 2018
Specular color
-

-

    -
  • Added section 9.1 to explain how the base color of various metals is computed
-

-

 Tuesday
21 August 2018
Multiscattering
-

-

    -
  • Added section 4.7.2 on how to compensate for energy loss in single scattering BRDFs
-

-

 Wednesday
20 February 2019
Cloth shading
+

February ⟨L:4226⟩20, 2019: Cloth shading

  • Removed Fresnel term from the cloth BRDF
  • Removed cloth DFG approximations, replaced with a new channel in the DFG LUT

-

+August ⟨L:4230⟩21, 2018: Multiscattering +

+

    +
  • Added section 4.7.2 on how to compensate for energy loss in single scattering BRDFs
+

+August ⟨L:4233⟩17, 2018: Specular color +

+

    +
  • Added section 9.1 to explain how the base color of various metals is computed
+

+August ⟨L:4236⟩15, 2018: Fresnel +

+

    +
  • Added a description of the Fresnel effect in section 4.4.3
+

+August ⟨L:4239⟩9, 2018: Lighting +

+

    +
  • Added explanation about pre-exposed lights
+

+August ⟨L:4242⟩7, 2018: Cloth model +

+

    +
  • Added description of the “Charlie” NDF
+

+August ⟨L:4245⟩3, 2018: First public version

-   

Bibliography

+
   

⟨L:4247⟩Bibliography

[ Ashdown98] Ian Ashdown. 1998. Parsing the IESNA LM-63 photometric data file. http://lumen.iee.put.poznan.pl/kw/iesna.txt
[ Ashikhmin00] Michael Ashikhmin, Simon Premoze and Peter Shirley. A Microfacet-based BRDF Generator. SIGGRAPH '00 Proceedings, 65-74. @@ -4369,7 +4400,7 @@ Here is the C++ code to compute \(\hat{C}_l\):
[ Estevez17] Alejandro Conty Estevez and Christopher Kulla. 2017. Production Friendly Microfacet Sheen BRDF. ACM SIGGRAPH 2017.
[ Hammon17] Earl Hammon. 217. PBR Diffuse Lighting for GGX+Smith Microsurfaces. GDC 2017.
[ Heitz14] Eric Heitz. 2014. Understanding the Masking-Shadowing Function -in Microfacet-Based BRDFs. Journal of Computer Graphics Techniques, 3 (2). +in ⟨L:4262⟩Microfacet-Based BRDFs. Journal of Computer Graphics Techniques, 3 (2).
[ Heitz16] Eric Heitz et al. 2016. Multiple-Scattering Microfacet BSDFs with the Smith Model. ACM SIGGRAPH 2016.
[ Hill12] Colin Barré-Brisebois and Stephen Hill. 2012. Blending in Detail. http://blog.selfshadow.com/publications/blending-in-detail/
[ Karis13a] Brian Karis. 2013. Specular BRDF Reference. http://graphicrants.blogspot.com/2013/08/specular-brdf-reference.html @@ -4388,14 +4419,14 @@ in Microfacet-Based BRDFs. Journal of Computer Graphics Tec
[ Neubelt13] David Neubelt and Matt Pettineo. 2013. Crafting a Next-Gen Material Pipeline for The Order: 1886. Physically Based Shading in Theory and Practice, ACM SIGGRAPH 2013 Courses.
[ Oren94] Michael Oren and Shree K. Nayar. 1994. Generalization of lambert's reflectance model. SIGGRAPH, 239–246. ACM.
[ Pattanaik00] Sumanta Pattanaik00 et al. 2000. Time-Dependent Visual Adaptation -For Fast Realistic Image Display. SIGGRAPH '00 Proceedings of the 27th annual conference on Computer graphics and interactive techniques, 47-54. +For ⟨L:4299⟩Fast Realistic Image Display. SIGGRAPH '00 Proceedings of the 27th annual conference on Computer graphics and interactive techniques, 47-54.
[ Ramamoorthi01] Ravi Ramamoorthi and Pat Hanrahan. 2001. On the relationship between radiance and irradiance: determining the illumination from images of a convex Lambertian object. Journal of the Optical Society of America, Volume 18, Number 10, October 2001.
[ Revie12] Donald Revie. 2012. Implementing Fur in Deferred Shading. GPU Pro 2, Chapter 2.
[ Russell15] Jeff Russell. 2015. Horizon Occlusion for Normal Mapped Reflections. http://marmosetco.tumblr.com/post/81245981087
[ Schlick94] Christophe Schlick. 1994. An Inexpensive BRDF Model for Physically-Based Rendering. Computer Graphics Forum, 13 (3), 233–246.
[ Walter07] Bruce Walter et al. 2007. Microfacet Models for Refraction through Rough Surfaces. Proceedings of the Eurographics Symposium on Rendering.
-

formatted by Markdeep 1.18  
+⟨L:4311⟩

formatted by Markdeep 1.18