diff --git a/docs/main/filament.html b/docs/main/filament.html index d90e4c9eb9..eb304edd9c 100644 --- a/docs/main/filament.html +++ b/docs/main/filament.html @@ -504,9 +504,9 @@ D_{GGX}(h,\alpha) = \frac{\aa}{\pi ( (\NoH)^2 (\aa - 1) + 1)^2} \end{equation}$$
The GLSL implementation of the NDF, shown in listing 1, is simple and efficient. -
float D_GGX(float NoH, float roughness) {
- float a = NoH * roughness;
- float k = roughness / (1.0 - NoH * NoH + a * a);
+float D_GGX(float NoH, float roughness) {
+ float a = NoH * roughness;
+ float k = roughness / (1.0 - NoH * NoH + a * a);
return k * k * (1.0 / PI);
}
@@ -590,10 +590,10 @@ V(v,l,\alpha) = \frac{0.5}{\NoL \sqrt{(\NoV)^2 (1 - \aa) + \aa} + \NoV \sqrt{(\N
\end{equation}$$
The GLSL implementation of the visibility term, shown in listing 3, is a bit more expensive than we would like since it requires two sqrt operations.
-
float V_SmithGGXCorrelated(float NoV, float NoL, float roughness) {
- float a2 = roughness * roughness;
- float GGXV = NoL * sqrt(NoV * NoV * (1.0 - a2) + a2);
- float GGXL = NoV * sqrt(NoL * NoL * (1.0 - a2) + a2);
+float V_SmithGGXCorrelated(float NoV, float NoL, float roughness) {
+ float a2 = roughness * roughness;
+ float GGXV = NoL * sqrt(NoV * NoV * (1.0 - a2) + a2);
+ float GGXL = NoV * sqrt(NoL * NoL * (1.0 - a2) + a2);
return 0.5 / (GGXV + GGXL);
}
@@ -604,10 +604,10 @@ V(v,l,\alpha) = \frac{0.5}{\NoL (\NoV (1 - \alpha) + \alpha) + \NoV (\NoL (1 - \
\end{equation}$$
This approximation is mathematically wrong but saves two square root operations and is good enough for real-time mobile applications, as shown in listing 4.
-
float V_SmithGGXCorrelatedFast(float NoV, float NoL, float roughness) {
- float a = roughness;
- float GGXV = NoL * (NoV * (1.0 - a) + a);
- float GGXL = NoV * (NoL * (1.0 - a) + a);
+float V_SmithGGXCorrelatedFast(float NoV, float NoL, float roughness) {
+ float a = roughness;
+ float GGXV = NoL * (NoV * (1.0 - a) + a);
+ float GGXL = NoV * (NoL * (1.0 - a) + a);
return 0.5 / (GGXV + GGXL);
}
@@ -659,7 +659,7 @@ $$\begin{equation}
\end{equation}$$
In practice, the diffuse reflectance \(\sigma\) is multiplied later, as shown in listing 8.
-
float Fd_Lambert() {
+float Fd_Lambert() {
return 1.0 / PI;
}
@@ -680,14 +680,14 @@ Where:
$$\begin{equation}
\fGrazing=0.5 + 2 \cdot \alpha cos^2(\theta_d)
\end{equation}$$
-float F_Schlick(float u, float f0, float f90) {
- return f0 + (f90 - f0) * pow(1.0 - u, 5.0);
+float F_Schlick(float u, float f0, float f90) {
+ return f0 + (f90 - f0) * pow(1.0 - u, 5.0);
}
-float Fd_Burley(float NoV, float NoL, float LoH, float roughness) {
- float f90 = 0.5 + 2.0 * roughness * LoH * LoH;
- float lightScatter = F_Schlick(NoL, 1.0, f90);
- float viewScatter = F_Schlick(NoV, 1.0, f90);
+float Fd_Burley(float NoV, float NoL, float LoH, float roughness) {
+ float f90 = 0.5 + 2.0 * roughness * LoH * LoH;
+ float lightScatter = F_Schlick(NoL, 1.0, f90);
+ float viewScatter = F_Schlick(NoV, 1.0, f90);
return lightScatter * viewScatter * (1.0 / PI);
}
@@ -704,47 +704,47 @@ We could allow artists/developers to choose the Disney diffuse BRDF depending on
Diffuse term: a Lambertian diffuse model.
The full GLSL implementation of the standard model is shown in listing 9.
-
float D_GGX(float NoH, float a) {
- float a2 = a * a;
- float f = (NoH * a2 - NoH) * NoH + 1.0;
+float D_GGX(float NoH, float a) {
+ float a2 = a * a;
+ float f = (NoH * a2 - NoH) * NoH + 1.0;
return a2 / (PI * f * f);
}
-vec3 F_Schlick(float u, vec3 f0) {
- return f0 + (vec3(1.0) - f0) * pow(1.0 - u, 5.0);
+vec3 F_Schlick(float u, vec3 f0) {
+ return f0 + (vec3(1.0) - f0) * pow(1.0 - u, 5.0);
}
-float V_SmithGGXCorrelated(float NoV, float NoL, float a) {
- float a2 = a * a;
- float GGXL = NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);
- float GGXV = NoL * sqrt((-NoV * a2 + NoV) * NoV + a2);
+float V_SmithGGXCorrelated(float NoV, float NoL, float a) {
+ float a2 = a * a;
+ float GGXL = NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);
+ float GGXV = NoL * sqrt((-NoV * a2 + NoV) * NoV + a2);
return 0.5 / (GGXV + GGXL);
}
-float Fd_Lambert() {
+float Fd_Lambert() {
return 1.0 / PI;
}
-void BRDF(...) {
- vec3 h = normalize(v + l);
+void BRDF(...) {
+ vec3 h = normalize(v + l);
- float NoV = abs(dot(n, v)) + 1e-5;
- float NoL = clamp(dot(n, l), 0.0, 1.0);
- float NoH = clamp(dot(n, h), 0.0, 1.0);
- float LoH = clamp(dot(l, h), 0.0, 1.0);
+ float NoV = abs(dot(n, v)) + 1e-5;
+ float NoL = clamp(dot(n, l), 0.0, 1.0);
+ float NoH = clamp(dot(n, h), 0.0, 1.0);
+ float LoH = clamp(dot(l, h), 0.0, 1.0);
// perceptually linear roughness to roughness (see parameterization)
- float roughness = perceptualRoughness * perceptualRoughness;
+ float roughness = perceptualRoughness * perceptualRoughness;
- float D = D_GGX(NoH, roughness);
- vec3 F = F_Schlick(LoH, f0);
- float V = V_SmithGGXCorrelated(NoV, NoL, roughness);
+ float D = D_GGX(NoH, roughness);
+ vec3 F = F_Schlick(LoH, f0);
+ float V = V_SmithGGXCorrelated(NoV, NoL, roughness);
// specular BRDF
- vec3 Fr = (D * V) * F;
+ vec3 Fr = (D * V) * F;
// diffuse BRDF
- vec3 Fd = diffuseColor * Fd_Lambert();
+ vec3 Fd = diffuseColor * Fd_Lambert();
// apply lighting...
}
@@ -965,7 +965,7 @@ $$\begin{equation}
\end{equation}$$
Listing 12 shows how \(\fNormal\) is computed for both dielectric and metallic materials. It shows that the color of the specular reflectance is derived from the base color in the metallic case.
-
vec3 f0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + baseColor * metallic;
+vec3 f0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + baseColor * metallic;
Roughness remapping and clamping
The roughness set by the user, called perceptualRoughness here, is remapped to a perceptually linear range using the following formulation:
@@ -1054,7 +1054,7 @@ V(l,h) = \frac{1}{4(\LoH)^2}
This masking-shadowing function is not physically based, as shown in [Heitz14], but its simplicity makes it desirable for real-time rendering.
In summary, our clear coat BRDF is a Cook-Torrance specular microfacet model, with a GGX normal distribution function, a Kelemen visibility function, and a Schlick Fresnel function. Listing 13 shows how trivial the GLSL implementation is.
-
float V_Kelemen(float LoH) {
+float V_Kelemen(float LoH) {
return 0.25 / (LoH * LoH);
}
@@ -1097,18 +1097,18 @@ The clear coat roughness parameter is remapped and clamped in a similar way to t
Listing 14 shows the GLSL implementation of the clear coat material model after remapping, parameterization and integration in the standard surface response.
-
void BRDF(...) {
+void BRDF(...) {
// compute Fd and Fr from standard model
// remapping and linearization of clear coat roughness
- clearCoatPerceptualRoughness = clamp(clearCoatPerceptualRoughness, 0.089, 1.0);
+ clearCoatPerceptualRoughness = clamp(clearCoatPerceptualRoughness, 0.089, 1.0);
clearCoatRoughness = clearCoatPerceptualRoughness * clearCoatPerceptualRoughness;
// clear coat BRDF
- float Dc = D_GGX(clearCoatRoughness, NoH);
- float Vc = V_Kelemen(clearCoatRoughness, LoH);
- float Fc = F_Schlick(0.04, LoH) * clearCoat; // clear coat strength
- float Frc = (Dc * Vc) * Fc;
+ float Dc = D_GGX(clearCoatRoughness, NoH);
+ float Vc = V_Kelemen(clearCoatRoughness, LoH);
+ float Fc = F_Schlick(0.04, LoH) * clearCoat; // clear coat strength
+ float Frc = (Dc * Vc) * Fc;
// account for energy loss in the base layer
return color * ((Fd + Fr * (1.0 - Fc)) * (1.0 - Fc) + Frc);
@@ -1294,14 +1294,14 @@ f_{r}(v,h,\alpha) = \frac{D_{velvet}(v,h,\alpha)}{4(\NoL + \NoV - (\NoL)(\NoV))}
\end{equation}$$
The implementation of the velvet NDF is presented in listing 17, optimized to properly fit in half float formats and to avoid computing a costly cotangent, relying instead on trigonometric identities. Note that we removed the Fresnel component from this BRDF.
-
float D_Ashikhmin(float roughness, float NoH) {
+float D_Ashikhmin(float roughness, float NoH) {
// Ashikhmin 2007, "Distribution-based BRDFs"
- float a2 = roughness * roughness;
- float cos2h = NoH * NoH;
- float sin2h = max(1.0 - cos2h, 0.0078125); // 2^(-14/2), so sin2h^2 > 0 in fp16
- float sin4h = sin2h * sin2h;
- float cot2 = -cos2h / (a2 * sin2h);
- return 1.0 / (PI * (4.0 * a2 + 1.0) * sin4h) * (4.0 * exp(cot2) + sin4h);
+ float a2 = roughness * roughness;
+ float cos2h = NoH * NoH;
+ float sin2h = max(1.0 - cos2h, 0.0078125); // 2^(-14/2), so sin2h^2 > 0 in fp16
+ float sin4h = sin2h * sin2h;
+ float cot2 = -cos2h / (a2 * sin2h);
+ return 1.0 / (PI * (4.0 * a2 + 1.0) * sin4h) * (4.0 * exp(cot2) + sin4h);
}
In [Estevez17] Estevez and Kulla propose a different NDF (called the “Charlie” sheen) that is based on an exponentiated sinusoidal instead of an inverted Gaussian. This NDF is appealing for several reasons: its parameterization feels more natural and intuitive, it provides a softer appearance and, as shown in equation (\ref{charlieNDF}), its implementation is simpler:
@@ -1312,11 +1312,11 @@ D(m) = \frac{(2 + \frac{1}{\alpha}) sin(\theta)^{\frac{1}{\alpha}}}{2 \pi}
[Estevez17] also presents a new shadowing term that we omit here because of its cost. We instead rely on the visibility term from [Neubelt13] (shown in equation \(\ref{clothSpecularBRDF}\) above).
The implementation of this NDF is presented in listing 18, optimized to properly fit in half float formats.
-
float D_Charlie(float roughness, float NoH) {
+float D_Charlie(float roughness, float NoH) {
// Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF"
- float invAlpha = 1.0 / roughness;
- float cos2h = NoH * NoH;
- float sin2h = max(1.0 - cos2h, 0.0078125); // 2^(-14/2), so sin2h^2 > 0 in fp16
+ float invAlpha = 1.0 / roughness;
+ float cos2h = NoH * NoH;
+ float sin2h = max(1.0 - cos2h, 0.0078125); // 2^(-14/2), so sin2h^2 > 0 in fp16
return (2.0 + invAlpha) * pow(sin2h, invAlpha * 0.5) / (2.0 * PI);
}
Sheen color
@@ -1745,21 +1745,21 @@ The photometric attenuation function can be easily implemented in GLSL by adding
}
The light intensity is computed CPU-side (listing 23) and depends on whether the photometric profile is used as a mask.
-float multiplier;
+float multiplier;
// Photometric profile used as a mask
-if (photometricLight.isMasked()) {
+if (photometricLight.isMasked()) {
// The desired intensity is set by the artist
// The integrated intensity comes from a Monte-Carlo
// integration over the unit sphere around the luminaire
- multiplier = photometricLight.getDesiredIntensity() /
- photometricLight.getIntegratedIntensity();
+ multiplier = photometricLight.getDesiredIntensity() /
+ photometricLight.getIntegratedIntensity();
} else {
// Multiplier provided for convenience, set to 1.0 by default
- multiplier = photometricLight.getMultiplier();
+ multiplier = photometricLight.getMultiplier();
}
// The max intensity in cd comes from the IES profile
-float lightIntensity = photometricLight.getMaxIntensity() * multiplier;
+float lightIntensity = photometricLight.getMaxIntensity() * multiplier;
4 The XArrow profile declares a luminous intensity of 1,750 lm but a Monte-Carlo integration shows an intensity of only 350 lm.
@@ -2058,19 +2058,19 @@ In practice only 4 or 9 coefficients (i.e.: 2 or 3 bands) are enough for \(\cosT
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:
-
vec3 irradianceSH(vec3 n) {
- // uniform vec3 sphericalHarmonics[9]
- // We can use only the first 2 bands for better performance
- return
- sphericalHarmonics[0]
- + sphericalHarmonics[1] * (n.y)
- + sphericalHarmonics[2] * (n.z)
- + sphericalHarmonics[3] * (n.x)
- + sphericalHarmonics[4] * (n.y * n.x)
- + sphericalHarmonics[5] * (n.y * n.z)
- + sphericalHarmonics[6] * (3.0 * n.z * n.z - 1.0)
- + sphericalHarmonics[7] * (n.z * n.x)
- + sphericalHarmonics[8] * (n.x * n.x - n.y * n.y);
+vec3 irradianceSH(vec3 n) {
+ // uniform vec3 sphericalHarmonics[9]
+ // We can use only the first 2 bands for better performance
+ return
+ sphericalHarmonics[0]
+ + sphericalHarmonics[1] * (n.y)
+ + sphericalHarmonics[2] * (n.z)
+ + sphericalHarmonics[3] * (n.x)
+ + sphericalHarmonics[4] * (n.y * n.x)
+ + sphericalHarmonics[5] * (n.y * n.z)
+ + sphericalHarmonics[6] * (3.0 * n.z * n.z - 1.0)
+ + sphericalHarmonics[7] * (n.z * n.x)
+ + sphericalHarmonics[8] * (n.x * n.x - n.y * n.y);
}
Note that with 2 bands, the computation above becomes a single (4 \times 4) matrix-by-vector multiply.
@@ -2443,7 +2443,7 @@ LD(n, \alpha) &= \frac{\sum_i^N V(l_i, n,
$$
These 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);
+float Fc = pow(1 - VoH, 5.0f);
r.x += Gv * Fc;
r.y += Gv;
@@ -2507,11 +2507,11 @@ using an environment made of colored vertical stripes (skybox hidden).
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.
// 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;
+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;
+iblSpecular *= sq(1.0 - Fc);
+iblSpecular += specularIBL(r, clearCoatPerceptualRoughness) * Fc;
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.
@@ -2550,17 +2550,17 @@ The DG term is generated using uniform sampling as recommended in [
Figure 62: 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.
-
float diffuse = Fd_Lambert() * ambientOcclusion;
-#if defined(SHADING_MODEL_CLOTH)
-#if defined(MATERIAL_HAS_SUBSURFACE_COLOR)
-diffuse *= saturate((NoV + 0.5) / 2.25);
-#endif
-#endif
+float diffuse = Fd_Lambert() * ambientOcclusion;
+#if defined(SHADING_MODEL_CLOTH)
+#if defined(MATERIAL_HAS_SUBSURFACE_COLOR)
+diffuse *= saturate((NoV + 0.5) / 2.25);
+#endif
+#endif
-vec3 indirectDiffuse = irradianceIBL(n) * diffuse;
-#if defined(SHADING_MODEL_CLOTH) && defined(MATERIAL_HAS_SUBSURFACE_COLOR)
-indirectDiffuse *= saturate(subsurfaceColor + NoV);
-#endif
+vec3 indirectDiffuse = irradianceIBL(n) * diffuse;
+#if defined(SHADING_MODEL_CLOTH) && defined(MATERIAL_HAS_SUBSURFACE_COLOR)
+indirectDiffuse *= saturate(subsurfaceColor + NoV);
+#endif
vec3 ibl = diffuseColor * indirectDiffuse + indirectSpecular * specularColor;
@@ -2982,20 +2982,20 @@ L_{max} &= 2^{EV_{100}} \times 1.2
// aperture in f-stops
// shutterSpeed in seconds
// sensitivity in ISO
-float exposureSettings(float aperture, float shutterSpeed, float sensitivity) {
+float exposureSettings(float aperture, float shutterSpeed, float sensitivity) {
return log2((aperture * aperture) / shutterSpeed * 100.0 / sensitivity);
}
// Computes the exposure normalization factor from
// the camera's EV100
-float exposure(float ev100) {
- return 1.0 / (pow(2.0, ev100) * 1.2);
+float exposure(float ev100) {
+ return 1.0 / (pow(2.0, ev100) * 1.2);
}
-float ev100 = exposureSettings(aperture, shutterSpeed, sensitivity);
-float exposure = exposure(ev100);
+float ev100 = exposureSettings(aperture, shutterSpeed, sensitivity);
+float exposure = exposure(ev100);
-vec4 color = evaluateLighting();
+vec4 color = evaluateLighting();
color.rgb *= exposure;
In practice the exposure factor can be pre-computed on the CPU to save shader instructions.
@@ -3466,9 +3466,9 @@ Our implementation is presented in listing&
// Data source:
// http://cvrl.ioo.ucl.ac.uk/cmfs.htm
// http://cvrl.ioo.ucl.ac.uk/database/text/cmfs/ciexyz31.htm
-const size_t CIE_XYZ_START = 360;
-const size_t CIE_XYZ_COUNT = 471;
-const float3 CIE_XYZ[CIE_XYZ_COUNT] = { ... };
+const size_t CIE_XYZ_START = 360;
+const size_t CIE_XYZ_COUNT = 471;
+const float3 CIE_XYZ[CIE_XYZ_COUNT] = { ... };
// CIE Standard Illuminant D65 relative spectral power distribution,
// from 300nm to 830, at 5nm intervals
@@ -3476,51 +3476,51 @@ Our implementation is presented in listing&
// Data source:
// https://en.wikipedia.org/wiki/Illuminant_D65
// https://cielab.xyz/pdf/CIE_sel_colorimetric_tables.xls
-const size_t CIE_D65_INTERVAL = 5;
-const size_t CIE_D65_START = 300;
-const size_t CIE_D65_END = 830;
-const size_t CIE_D65_COUNT = 107;
-const float CIE_D65[CIE_D65_COUNT] = { ... };
+const size_t CIE_D65_INTERVAL = 5;
+const size_t CIE_D65_START = 300;
+const size_t CIE_D65_END = 830;
+const size_t CIE_D65_COUNT = 107;
+const float CIE_D65[CIE_D65_COUNT] = { ... };
-struct Sample {
- float w = 0.0f; // wavelength
- std::complex<float> ior; // complex IOR, n + ik
+struct Sample {
+ float w = 0.0f; // wavelength
+ std::complex<float> ior; // complex IOR, n + ik
};
-static float illuminantD65(float w) {
- auto i0 = size_t((w - CIE_D65_START) / CIE_D65_INTERVAL);
- uint2 indexBounds{i0, std::min(i0 + 1, CIE_D65_END)};
+static float illuminantD65(float w) {
+ auto i0 = size_t((w - CIE_D65_START) / CIE_D65_INTERVAL);
+ uint2 indexBounds{i0, std::min(i0 + 1, CIE_D65_END)};
float2 wavelengthBounds = CIE_D65_START + float2{indexBounds} * CIE_D65_INTERVAL;
- float t = (w - wavelengthBounds.x) / (wavelengthBounds.y - wavelengthBounds.x);
- return lerp(CIE_D65[indexBounds.x], CIE_D65[indexBounds.y], t);
+ float t = (w - wavelengthBounds.x) / (wavelengthBounds.y - wavelengthBounds.x);
+ return lerp(CIE_D65[indexBounds.x], CIE_D65[indexBounds.y], t);
}
// For std::lower_bound
-bool operator<(const Sample& lhs, const Sample& rhs) {
+bool operator<(const Sample& lhs, const Sample& rhs) {
return lhs.w < rhs.w;
}
// The wavelength w must be between 360nm and 830nm
-static std::complex<float> findSample(const std::vector<sample>& samples, float w) {
- auto i1 = std::lower_bound(
- samples.begin(), samples.end(), Sample{w, 0.0f + 0.0if});
+static std::complex<float> findSample(const std::vector<sample>& samples, float w) {
+ auto i1 = std::lower_bound(
+ samples.begin(), samples.end(), Sample{w, 0.0f + 0.0if});
auto i0 = i1 - 1;
// Interpolate the complex IORs
- float t = (w - i0->w) / (i1->w - i0->w);
- float n = lerp(i0->ior.real(), i1->ior.real(), t);
- float k = lerp(i0->ior.imag(), i1->ior.imag(), t);
+ float t = (w - i0->w) / (i1->w - i0->w);
+ float n = lerp(i0->ior.real(), i1->ior.real(), t);
+ float k = lerp(i0->ior.imag(), i1->ior.imag(), t);
return { n, k };
}
-static float fresnel(const std::complex<float>& sample) {
- return (((sample - (1.0f + 0if)) * (std::conj(sample) - (1.0f + 0if))) /
- ((sample + (1.0f + 0if)) * (std::conj(sample) + (1.0f + 0if)))).real();
+static float fresnel(const std::complex<float>& sample) {
+ return (((sample - (1.0f + 0if)) * (std::conj(sample) - (1.0f + 0if))) /
+ ((sample + (1.0f + 0if)) * (std::conj(sample) + (1.0f + 0if)))).real();
}
-static float3 XYZ_to_sRGB(const float3& v) {
- const mat3f XYZ_sRGB{
+static float3 XYZ_to_sRGB(const float3& v) {
+ const mat3f XYZ_sRGB{
3.2404542f, -0.9692660f, 0.0556434f,
-1.5371385f, 1.8760108f, -0.2040259f,
-0.4985314f, 0.0415560f, 1.0572252f
@@ -3529,21 +3529,21 @@ Our implementation is presented in listing&
}
// Outputs a linear sRGB color
-static float3 computeColor(const std::vector<sample>& samples) {
+static float3 computeColor(const std::vector<sample>& samples) {
float3 xyz{0.0f};
- float y = 0.0f;
+ float y = 0.0f;
- for (size_t i = 0; i < CIE_XYZ_COUNT; i++) {
+ for (size_t i = 0; i < CIE_XYZ_COUNT; i++) {
// Current wavelength
- float w = CIE_XYZ_START + i;
+ float w = CIE_XYZ_START + i;
// Find most appropriate CIE XYZ sample for the wavelength
- auto sample = findSample(samples, w);
+ auto sample = findSample(samples, w);
// Compute Fresnel reflectance at normal incidence
- float f0 = fresnel(sample);
+ float f0 = fresnel(sample);
// We need to multiply by the spectral power distribution of the illuminant
- float d65 = illuminantD65(w);
+ float d65 = illuminantD65(w);
xyz += f0 * CIE_XYZ[i] * d65;
y += CIE_XYZ[i].y * d65;
@@ -3552,10 +3552,10 @@ Our implementation is presented in listing&
// Normalize so that 100% reflectance at every wavelength yields Y=1
xyz /= y;
- float3 linear = XYZ_to_sRGB(xyz);
+ float3 linear = XYZ_to_sRGB(xyz);
// Normalize out-of-gamut values
- if (any(greaterThan(linear, float3{1.0f}))) linear *= 1.0f / max(linear);
+ if (any(greaterThan(linear, float3{1.0f}))) linear *= 1.0f / max(linear);
return linear;
}
@@ -3735,47 +3735,47 @@ l &= \{ cos\phi sin\theta,sin\phi sin\theta,cos\theta \}
vec2f hammersley(uint i, float numSamples) {
uint bits = i;
bits = (bits << 16) | (bits >> 16);
- bits = ((bits & 0x55555555) << 1) | ((bits & 0xAAAAAAAA) >> 1);
- bits = ((bits & 0x33333333) << 2) | ((bits & 0xCCCCCCCC) >> 2);
- bits = ((bits & 0x0F0F0F0F) << 4) | ((bits & 0xF0F0F0F0) >> 4);
- bits = ((bits & 0x00FF00FF) << 8) | ((bits & 0xFF00FF00) >> 8);
- return vec2f(i / numSamples, bits / exp2(32));
+ bits = ((bits & 0x55555555) << 1) | ((bits & 0xAAAAAAAA) >> 1);
+ bits = ((bits & 0x33333333) << 2) | ((bits & 0xCCCCCCCC) >> 2);
+ 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
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).
-float GDFG(float NoV, float NoL, float a) {
- float a2 = a * a;
- float GGXL = NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);
- float GGXV = NoL * sqrt((-NoV * a2 + NoV) * NoV + a2);
+float GDFG(float NoV, float NoL, float a) {
+ float a2 = a * a;
+ float GGXL = NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);
+ float GGXV = NoL * sqrt((-NoV * a2 + NoV) * NoV + a2);
return (2 * NoL) / (GGXV + GGXL);
}
-float2 DFG(float NoV, float a) {
+float2 DFG(float NoV, float a) {
float3 V;
- V.x = sqrt(1.0f - NoV*NoV);
- V.y = 0.0f;
+ V.x = sqrt(1.0f - NoV*NoV);
+ V.y = 0.0f;
V.z = NoV;
- float2 r = 0.0f;
- for (uint i = 0; i < sampleCount; i++) {
- float2 Xi = hammersley(i, sampleCount);
- float3 H = importanceSampleGGX(Xi, a, N);
- float3 L = 2.0f * dot(V, H) * H - V;
+ float2 r = 0.0f;
+ for (uint i = 0; i < sampleCount; i++) {
+ float2 Xi = hammersley(i, sampleCount);
+ float3 H = importanceSampleGGX(Xi, a, N);
+ float3 L = 2.0f * dot(V, H) * H - V;
- float VoH = saturate(dot(V, H));
- float NoL = saturate(L.z);
- float NoH = saturate(H.z);
+ float VoH = saturate(dot(V, H));
+ float NoL = saturate(L.z);
+ float NoH = saturate(H.z);
- if (NoL > 0.0f) {
- float G = GDFG(NoV, NoL, a);
- float Gv = G * VoH / NoH;
- float Fc = pow(1 - VoH, 5.0f);
+ if (NoL > 0.0f) {
+ float G = GDFG(NoV, NoL, a);
+ float Gv = G * VoH / NoH;
+ float Fc = pow(1 - VoH, 5.0f);
r.x += Gv * (1 - Fc);
r.y += Gv * Fc;
}
}
- return r * (1.0f / sampleCount);
+ return r * (1.0f / sampleCount);
}
C++ implementation of the \( L_{DFG} \) term
Spherical Harmonics
@@ -3851,56 +3851,56 @@ sin(m \phi + \phi) &= sin(m \phi) cos(\phi) + cos(m \phi) sin(\phi) \Leftrightar
\end{align*}$$
Listing 47 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) {
+static inline size_t SHindex(ssize_t m, size_t l) {
return l * (l + 1) + m;
}
-void computeShBasis(
- double* const SHb,
- size_t numBands,
- const vec3& s)
+void computeShBasis(
+ double* const SHb,
+ size_t numBands,
+ const vec3& s)
{
// handle m=0 separately, since it produces only one coefficient
- double Pml_2 = 0;
- double Pml_1 = 1;
+ double Pml_2 = 0;
+ double Pml_1 = 1;
SHb[0] = Pml_1;
- for (ssize_t l = 1; l < numBands; l++) {
- double Pml = ((2 * l - 1) * Pml_1 * s.z - (l - 1) * Pml_2) / l;
+ for (ssize_t l = 1; l < numBands; l++) {
+ double Pml = ((2 * l - 1) * Pml_1 * s.z - (l - 1) * Pml_2) / l;
Pml_2 = Pml_1;
Pml_1 = Pml;
- SHb[SHindex(0, l)] = Pml;
+ SHb[SHindex(0, l)] = Pml;
}
- double Pmm = 1;
- for (ssize_t m = 1; m < numBands ; m++) {
+ double Pmm = 1;
+ for (ssize_t m = 1; m < numBands ; m++) {
Pmm = (1 - 2 * m) * Pmm;
- double Pml_2 = Pmm;
- double Pml_1 = (2 * m + 1)*Pmm*s.z;
+ double Pml_2 = Pmm;
+ double Pml_1 = (2 * m + 1)*Pmm*s.z;
// l == m
- SHb[SHindex(-m, m)] = Pml_2;
- SHb[SHindex( m, m)] = Pml_2;
+ SHb[SHindex(-m, m)] = Pml_2;
+ SHb[SHindex( m, m)] = Pml_2;
if (m + 1 < numBands) {
// l == m+1
- SHb[SHindex(-m, m + 1)] = Pml_1;
- SHb[SHindex( m, m + 1)] = Pml_1;
- for (ssize_t l = m + 2; l < numBands; l++) {
- double Pml = ((2 * l - 1) * Pml_1 * s.z - (l + m - 1) * Pml_2)
+ SHb[SHindex(-m, m + 1)] = Pml_1;
+ SHb[SHindex( m, m + 1)] = Pml_1;
+ for (ssize_t l = m + 2; l < numBands; l++) {
+ double Pml = ((2 * l - 1) * Pml_1 * s.z - (l + m - 1) * Pml_2)
/ (l - m);
Pml_2 = Pml_1;
Pml_1 = Pml;
- SHb[SHindex(-m, l)] = Pml;
- SHb[SHindex( m, l)] = Pml;
+ SHb[SHindex(-m, l)] = Pml;
+ SHb[SHindex( m, l)] = Pml;
}
}
}
- double Cm = s.x;
- double Sm = s.y;
- for (ssize_t m = 1; m <= numBands ; m++) {
- for (ssize_t l = m; l < numBands ; l++) {
- SHb[SHindex(-m, l)] *= Sm;
- SHb[SHindex( m, l)] *= Cm;
+ double Cm = s.x;
+ double Sm = s.y;
+ for (ssize_t m = 1; m <= numBands ; m++) {
+ for (ssize_t l = m; l < numBands ; l++) {
+ SHb[SHindex(-m, l)] *= Sm;
+ SHb[SHindex( m, l)] *= Cm;
}
- double Cm1 = Cm * s.x - Sm * s.y;
- double Sm1 = Sm * s.x + Cm * s.y;
+ double Cm1 = Cm * s.x - Sm * s.y;
+ double Sm1 = Sm * s.x + Cm * s.y;
Cm = Cm1;
Sm = Sm1;
}
@@ -3979,10 +3979,10 @@ $$\begin{equation}
\end{equation}$$
Here is the C++ code to compute \(\hat{C}_l\):
-
static double factorial(size_t n, size_t d = 1);
+static double factorial(size_t n, size_t d = 1);
// < cos(theta) > SH coefficients pre-multiplied by 1 / K(0,l)
-double computeTruncatedCosSh(size_t l) {
+double computeTruncatedCosSh(size_t l) {
if (l == 0) {
return M_PI;
} else if (l == 1) {
@@ -3990,17 +3990,17 @@ Here is the C++ code to compute \(\hat{C}_l\):
} else if (l & 1) {
return 0;
}
- const size_t l_2 = l / 2;
- double A0 = ((l_2 & 1) ? 1.0 : -1.0) / ((l + 2) * (l - 1));
- double A1 = factorial(l, l_2) / (factorial(l_2) * (1 << l));
+ const size_t l_2 = l / 2;
+ double A0 = ((l_2 & 1) ? 1.0 : -1.0) / ((l + 2) * (l - 1));
+ double A1 = factorial(l, l_2) / (factorial(l_2) * (1 << l));
return 2 * M_PI * A0 * A1;
}
// returns n! / d!
-double factorial(size_t n, size_t d ) {
- d = std::max(size_t(1), d);
- n = std::max(size_t(1), n);
- double r = 1.0;
+double factorial(size_t n, size_t d ) {
+ d = std::max(size_t(1), d);
+ n = std::max(size_t(1), n);
+ double r = 1.0;
if (n == d) {
// intentionally left blank
} else if (n > d) {
diff --git a/docs/wip/sky/index.html b/docs/wip/sky/index.html
index 8717228449..c8207efc64 100644
--- a/docs/wip/sky/index.html
+++ b/docs/wip/sky/index.html
@@ -32,8 +32,8 @@
-
-
+
+