Skip to content

Commit 7f48710

Browse files
proog128abbaswasim
authored andcommitted
Fix reference BRDF implementation (KhronosGroup#2386) (KhronosGroup#2392)
* Fix reference BRDF implementation (KhronosGroup#2386) * Fix clearcoat emisison and other improvements * Revert removal of trailing space in unrelated section * Use mix function to blend between values * Revert usage of mix for Fresnel terms * Fix inconsistent usage of snake case.
1 parent 501b6a7 commit 7f48710

File tree

6 files changed

+77
-79
lines changed

6 files changed

+77
-79
lines changed

extensions/2.0/Khronos/KHR_materials_clearcoat/README.md

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,35 +117,33 @@ The `fresnel_coat` function is computed using the Schlick Fresnel term from the
117117
```
118118
function fresnel_coat(normal, ior, weight, base, layer) {
119119
f0 = ((1-ior)/(1+ior))^2
120-
fr = f0 + (1 - f0)*(1 - abs(NdotV))^5 // N = normal
120+
fr = f0 + (1 - f0)*(1 - abs(dot(V, normal)))^5
121121
return mix(base, layer, weight * fr)
122122
}
123123
```
124124

125125
Applying the functions we arrive at the coated material
126126

127127
```
128-
coated_material = mix(material, clearcoat_brdf(clearcoatRughness^2), clearcoat * (0.04 + (1 - 0.04) * (1 - NdotV)^5))
128+
coated_material = mix(material, clearcoat_brdf(clearcoatRoughness^2), clearcoat * (0.04 + (1 - 0.04) * (1 - VdotNc)^5))
129129
```
130130

131131
and finally, substituting and simplifying, using some symbols from [Appendix B](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#appendix-b-brdf-implementation) and `Nc` for the clearcoat normal:
132132

133133
```
134-
clearcoatFresnel = 0.04 + (1 - 0.04) * (1 - abs(VdotNc))^5
135-
clearcoatAlpha = clearcoatRoughness^2
134+
clearcoat_fresnel = 0.04 + (1 - 0.04) * (1 - abs(VdotNc))^5
135+
clearcoat_alpha = clearcoatRoughness^2
136+
clearcoat_brdf = D(clearcoat_alpha) * G(clearcoat_alpha) / (4 * abs(VdotNc) * abs(LdotNc))
136137
137-
f_clearcoat = clearcoatFresnel * D(clearcoatAlpha) * G / (4 * abs(VdotNc) * abs(LdotNc))
138-
139-
coated_material = (f_diffuse + f_specular) * (1 - clearcoat * clearcoatFresnel) +
140-
f_clearcoat * clearcoat
138+
coated_material = mix(material, clearcoat_brdf, clearcoat * clearcoat_fresnel)
141139
```
142140

143141
#### Emission
144142

145143
The clearcoat layer is on top of emission in the layering stack. Consequently, the emission is darkened by the Fresnel term.
146144

147145
```
148-
coated_emission = emission * (0.04 + (1 - 0.04) * (1 - NdotV)^5)
146+
coated_emission = emission * (1 - clearcoat * clearcoat_fresnel)
149147
```
150148

151149
#### Discussion

extensions/2.0/Khronos/KHR_materials_ior/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,10 @@ Valid values for `ior` are numbers greater than or equal to 1. In addition, a va
103103
The extension changes the computation of the Fresnel term defined in [Appendix B](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#appendix-b-brdf-implementation) to the following:
104104

105105
```
106-
const dielectricSpecular = ((ior - 1)/(ior + 1))^2
106+
dielectric_f0 = ((ior - 1)/(ior + 1))^2
107107
```
108108

109-
Note that for the default index of refraction `ior = 1.5` this term evaluates to `dielectricSpecular = 0.04`.
109+
Note that for the default index of refraction `ior = 1.5` this term evaluates to `dielectric_f0 = 0.04`.
110110

111111
## Interaction with other extensions
112112

extensions/2.0/Khronos/KHR_materials_sheen/README.md

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -91,82 +91,82 @@ Not all incoming light is reflected at a micro-fiber. Some of the light may hit
9191

9292
All implementations should use the same calculations for the BRDF inputs. See [Appendix B](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#appendix-b-brdf-implementation) for more details on the BRDF calculations.
9393

94-
The sheen formula `f_sheen` follows the common microfacet form:
94+
The sheen formula follows the common microfacet form with visibility term $\mathcal{V}_s$:
95+
96+
$$
97+
\text{SheenBRDF} = \frac{G_S D_S}{4 \, \left|N \cdot L \right| \, \left| N \cdot V \right|} = \mathcal{V}_S D_S
98+
$$
9599

96-
*f*<sub>*sheen*</sub> = *sheenColor* * *sheenFresnel* * *sheenDistribution* * *sheenVisibility* = *sheenColor* * *F*<sub>*S*</sub> * *G*<sub>*S*</sub> * *D*<sub>*S*</sub> / (4 * abs(dot(*N*, *L*)) * abs(dot(*N*, *V*)))
97100

98101
### Sheen distribution
99102

100-
The sheen distribution follows the "Charlie" sheen definition from ImageWorks [Conty and Kulla (2017)](#ContyKulla2017):
103+
The sheen distribution $D_s$ follows the "Charlie" sheen definition from ImageWorks [Conty and Kulla (2017)](#ContyKulla2017):
101104

102105
```glsl
103-
alphaG = sheenRoughness * sheenRoughness
104-
invR = 1 / alphaG
106+
alpha_g = sheenRoughness * sheenRoughness
107+
inv_r = 1 / alpha_g
105108
cos2h = NdotH * NdotH
106109
sin2h = 1 - cos2h
107-
sheenDistribution = (2 + invR) * pow(sin2h, invR * 0.5) / (2 * PI);
110+
sheen_distribution = (2 + inv_r) * pow(sin2h, inv_r * 0.5) / (2 * PI);
108111
```
109112

110113
### Sheen visibility
111114

112-
The "Charlie" sheen visibility is also defined in the same document:
115+
The "Charlie" sheen visibility $\mathcal{V}_s = \frac{G_s}{4 \, \left|N \cdot L \right| \, \left| N \cdot V \right|}$ is also defined in the same document:
113116

114117
```glsl
115-
float l(float x, float alphaG)
118+
float l(float x, float alpha_g)
116119
{
117-
float oneMinusAlphaSq = (1.0 - alphaG) * (1.0 - alphaG);
118-
float a = mix(21.5473, 25.3245, oneMinusAlphaSq);
119-
float b = mix(3.82987, 3.32435, oneMinusAlphaSq);
120-
float c = mix(0.19823, 0.16801, oneMinusAlphaSq);
121-
float d = mix(-1.97760, -1.27393, oneMinusAlphaSq);
122-
float e = mix(-4.32054, -4.85967, oneMinusAlphaSq);
120+
float one_minus_alpha_sq = (1.0 - alpha_g) * (1.0 - alpha_g);
121+
float a = mix(21.5473, 25.3245, one_minus_alpha_sq);
122+
float b = mix(3.82987, 3.32435, one_minus_alpha_sq);
123+
float c = mix(0.19823, 0.16801, one_minus_alpha_sq);
124+
float d = mix(-1.97760, -1.27393, one_minus_alpha_sq);
125+
float e = mix(-4.32054, -4.85967, one_minus_alpha_sq);
123126
return a / (1.0 + b * pow(x, c)) + d * x + e;
124127
}
125128
126-
float lambdaSheen(float cosTheta, float alphaG)
129+
float lambda_sheen(float cos_theta, float alpha_g)
127130
{
128-
return abs(cosTheta) < 0.5 ? exp(l(cosTheta, alphaG)) : exp(2.0 * l(0.5, alphaG) - l(1.0 - cosTheta, alphaG));
131+
return abs(cos_theta) < 0.5 ? exp(l(cos_theta, alpha_g)) : exp(2.0 * l(0.5, alpha_g) - l(1.0 - cos_theta, alpha_g));
129132
}
130133
131-
sheenVisibility = 1.0 / ((1.0 + lambdaSheen(NdotV, alphaG) + lambdaSheen(NdotL, alphaG)) * (4.0 * NdotV * NdotL));
134+
sheen_visibility = 1.0 / ((1.0 + lambda_sheen(NdotV, alpha_g) + lambda_sheen(NdotL, alpha_g)) * (4.0 * NdotV * NdotL));
132135
```
133136

134137
However, depending on device performance and resource constraints, one can use a simpler visibility term, like the one defined by [Ashikhmin and Premoze (2007)](#AshikhminPremoze2007) (but that will make the BRDF not energy conserving when using the albedo-scaling technique described below):
138+
135139
```glsl
136-
sheenVisibility = 1 / (4 * (NdotL + NdotV - NdotL * NdotV))
140+
sheen_visibility = 1 / (4 * (NdotL + NdotV - NdotL * NdotV))
137141
```
138142

139-
### Sheen Fresnel
140-
141-
The Fresnel term may be omitted, i.e., *F* = 1.
142-
143143
### Sheen layering
144144

145145
#### Albedo-scaling technique
146146

147-
The sheen layer can be combined with the base layer with an albedo-scaling technique described in [Conty and Kulla (2017)](#ContyKulla2017). The base layer *f*<sub>*diffuse*</sub> + *f*<sub>*specular*</sub> from [Appendix B](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#appendix-b-brdf-implementation) is scaled with *sheenAlbedoScaling* to avoid energy gain.
148-
149-
*f* = *f*<sub>*sheen*</sub> + (*f*<sub>*diffuse*</sub> + *f*<sub>*specular*</sub>) * *sheenAlbedoScaling*
147+
The sheen layer can be combined with the base layer with an albedo-scaling technique described in [Conty and Kulla (2017)](#ContyKulla2017). The base layer `material = mix(dielectric_brdf, metal_brdf, metallic)` from [Appendix B](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#appendix-b-brdf-implementation) is scaled with `sheen_albedo_scaling` to avoid energy gain.
150148

151149
```glsl
152150
float max3(vec3 v) { return max(max(v.x, v.y), v.z); }
151+
sheen_albedo_scaling = min(1.0 - max3(sheenColor) * E(VdotN), 1.0 - max3(sheenColor) * E(LdotN))
153152
154-
sheenAlbedoScaling = min(1.0 - max3(sheenColor) * E(VdotN), 1.0 - max3(sheenColor) * E(LdotN))
153+
sheen_material = sheenColor * sheen_brdf + material * sheen_albedo_scaling
155154
```
156155

157156
The values `E(x)` can be looked up in a table which can be found in section 6.2.3 of [Enterprise PBR Shading Model](#theory-documentation-and-implementations) if you use the "Charlie" visibility term. If you use Ashikhmin instead, you can get the lookup table by using the [cmgen tool from Filament](#theory-documentation-and-implementations), with the `--ibl-dfg` and `--ibl-dfg-cloth` flags: the table is in the blue channel of the generated picture. The lookup must be done with `x = VdotN` and `y = sheenRoughness`.
158157

159158
If you want to trade a bit of accuracy for more performance, you can use the `VdotN` term only and thus avoid doing multiple lookups for `LdotN`. The albedo scaling term is simplified to:
160159
```glsl
161-
sheenAlbedoScaling = 1.0 - max3(sheenColor) * E(VdotN)
160+
sheen_albedo_scaling = 1.0 - max3(sheenColor) * E(VdotN)
162161
```
163162

164163
In this simplified form, it can be used to scale the base layer for both direct and indirect lights:
164+
165165
```glsl
166-
specular_direct *= sheenAlbedoScaling;
167-
diffuse_direct *= sheenAlbedoScaling;
168-
environmentIrradiance_indirect *= sheenAlbedoScaling
169-
specularEnvironmentReflectance_indirect *= sheenAlbedoScaling
166+
specular_direct *= sheen_albedo_scaling;
167+
diffuse_direct *= sheen_albedo_scaling;
168+
environment_irradiance_indirect *= sheen_albedo_scaling
169+
specular_environment_reflectance_indirect *= sheen_albedo_scaling
170170
```
171171

172172
## Schema

extensions/2.0/Khronos/KHR_materials_specular/README.md

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,11 @@ Factor and texture are combined by multiplication to describe a single value.
8484
| **specularColorFactor** | `number[3]` | The F0 color of the specular reflection (linear RGB). | No, default: `[1.0, 1.0, 1.0]`|
8585
| **specularColorTexture** | [`textureInfo`](https://www.khronos.org/registry/glTF/specs/2.0/glTF-2.0.html#reference-textureinfo) | A texture that defines the F0 color of the specular reflection, stored in the `RGB` channels and encoded in sRGB. This texture will be multiplied by specularColorFactor. | No |
8686

87+
If a texture is defined:
88+
89+
- The specular color is computed with : `specularColor = specularColorFactor * sampleLinear(specularColorTexture).rgb`.
90+
- The specular strength is computed with : `specular = specularFactor * sample(specularTexture).a`.
91+
8792
The `specular` and `specularColor` parameters affect the `dielectric_brdf` of the glTF 2.0 metallic-roughness material.
8893

8994
```
@@ -134,41 +139,35 @@ function fresnel_mix(f0_color, ior, weight, base, layer) {
134139
}
135140
```
136141

137-
Therefore, the Fresnel term `F` in the final BRDF of the material changes to
142+
Therefore, the Fresnel term `dielectric_fresnel` in the final BRDF of the material changes to
138143

139144
```
140-
dielectricSpecularF0 = min(0.04 * specularColorFactor * specularColorTexture.rgb, float3(1.0)) *
141-
specularFactor * specularTexture.a
142-
dielectricSpecularF90 = specularFactor * specularTexture.a
143-
144-
F0 = lerp(dielectricSpecularF0, baseColor.rgb, metallic)
145-
F90 = lerp(dielectricSpecularF90, 1, metallic)
146-
147-
F = F0 + (F90 - F0) * (1 - VdotH)^5
145+
dielectric_f0 = min(0.04 * specularColor, float3(1.0)) * specular
146+
dielectric_f90 = specular
147+
dielectric_fresnel = mix(dielectric_f0, dielectric_f90, fresnel_w)
148148
```
149149

150-
Note that in `dielectricSpecularF0` we clamp the product of specular color and f0 reflectance from IOR (`0.04`), before multiplying by specular.
150+
Note that in `dielectric_f0` we clamp the product of specular color and f0 reflectance from IOR (`0.04`), before multiplying by `specular`.
151151

152-
In the diffuse component we have to account for the fact that `F` is now an RGB value.
152+
In the diffuse component we have to account for the fact that `dielectric_fresnel` is now an RGB value. Thus we redefine `dielectric_brdf` as follows:
153153

154154
```
155-
c_diff = lerp(baseColor.rgb, black, metallic)
156-
diffuse = c_diff / PI
157-
f_diffuse = (1 - max(F.r, F.g, F.b)) * diffuse
155+
dielectric_fresnel_max = max_value(dielectric_fresnel)
156+
dielectric_brdf = dielectric_fresnel * specular_brdf + (1 - dielectric_fresnel_max) * diffuse_brdf
158157
```
159158

160159
## Interaction with other extensions
161160

162161
If `KHR_materials_ior` is used in combination with `KHR_materials_specular`, the constant `0.04` is replaced by the value computed from the IOR.
163162

164163
```
165-
dielectricSpecularF0 = min(((ior - outside_ior) / (ior + outside_ior))^2 * specularColorFactor * specularColorTexture.rgb, float3(1.0)) * specularFactor * specularTexture.a
166-
dielectricSpecularF90 = specularFactor * specularTexture.a
164+
dielectric_f0 = min(((ior - outside_ior) / (ior + outside_ior))^2 * specularColor, float3(1.0)) * specular
165+
dielectric_f90 = specular
167166
```
168167

169168
`outside_ior` is typically set to 1.0, the index of refraction of air.
170169

171-
If `KHR_materials_transmission` is used in combination with `KHR_materials_specular`, the ratio of transmission and reflection computed from the Fresnel term also depends on `dielectricSpecularF0` and `dielectricSpecularF90`. The following images show a thin, transmissive material.
170+
If `KHR_materials_transmission` is used in combination with `KHR_materials_specular`, the ratio of transmission and reflection computed from the Fresnel term also depends on `dielectric_f0` and `dielectric_f90`. The following images show a thin, transmissive material.
172171

173172
Specular from 0 to 1:
174173

@@ -179,7 +178,7 @@ Specular color from [0,0,0] to [1,1,1] (top) and [0,0,0] to [1,0,0]:
179178
![](figures/specular-color-thin.png)
180179
![](figures/specular-color-thin-2.png)
181180

182-
If `KHR_materials_transmission` and `KHR_materials_volume` are used in combination with `KHR_materials_specular`, specular factor and specular color have no effect on the refraction angle. The direction of the refracted light ray is only based on the index of refraction defined in `KHR_materials_ior`. The ratio of transmission and reflection computed from the Fresnel term still depends on `dielectricSpecularF0` and `dielectricSpecularF90`. The following images show a refractive material.
181+
If `KHR_materials_transmission` and `KHR_materials_volume` are used in combination with `KHR_materials_specular`, specular factor and specular color have no effect on the refraction angle. The direction of the refracted light ray is only based on the index of refraction defined in `KHR_materials_ior`. The ratio of transmission and reflection computed from the Fresnel term still depends on `dielectric_f0` and `dielectric_f90`. The following images show a refractive material.
183182

184183
Specular from 0 to 1:
185184

@@ -197,7 +196,7 @@ Specular color from [0,0,0] to [1,1,1] (top) and [0,0,0] to [1,0,0]:
197196
Material models that define F0 in terms of reflectance at normal incidence can be converted by encoding the reflectance in the specular color parameters. Typically, the reflectance ranges from 0% to 8%, given as a value in range [0,1], with 0.5 (=4%) being the default. F0 is computed from `reflectance` in the following way:
198197

199198
```
200-
dielectricSpecularF0 = 0.08 * reflectance
199+
dielectric_f0 = 0.08 * reflectance
201200
```
202201

203202
In contrast, `KHR_materials_specular` defines a constant factor of 0.04 to compute F0, as this corresponds to glTF's default IOR of 1.5. Therefore, by encoding an additional constant factor of 2 in `specularColorFactor`, we can convert from reflectance to specular color without any loss.

extensions/2.0/Khronos/KHR_materials_transmission/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -207,26 +207,26 @@ With the step function $\chi^+$ we ensure that the microsurface is only visible
207207
Introducing the visibility function
208208

209209
$$
210-
V_T = \frac{G_T}{4 \left| N \cdot L \right| \left| N \cdot V \right|}
210+
\mathcal{V}_T = \frac{G_T}{4 \left| N \cdot L \right| \left| N \cdot V \right|}
211211
$$
212212

213213
simplifies the original microfacet BTDF to
214214

215215
$$
216-
\text{MicrofacetBTDF} = V_T D_T
216+
\text{MicrofacetBTDF} = \mathcal{V}_T D_T
217217
$$
218218

219219
with
220220

221221
$$
222-
V_T = \frac{\chi^+\left(\frac{H_T \cdot L}{N \cdot L}\right)}{\left| N \cdot L\right| + \sqrt{\alpha^2 + (1 - \alpha^2) (N \cdot L)^2}} \frac{\chi^+\left(\frac{H_T \cdot V}{N \cdot V}\right)}{\left| N \cdot V \right| + \sqrt{\alpha^2 + (1 - \alpha^2) (N \cdot V)^2}}
222+
\mathcal{V}_T = \frac{\chi^+\left(\frac{H_T \cdot L}{N \cdot L}\right)}{\left| N \cdot L\right| + \sqrt{\alpha^2 + (1 - \alpha^2) (N \cdot L)^2}} \frac{\chi^+\left(\frac{H_T \cdot V}{N \cdot V}\right)}{\left| N \cdot V \right| + \sqrt{\alpha^2 + (1 - \alpha^2) (N \cdot V)^2}}
223223
$$
224224

225225
Thus we have the function
226226

227227
```
228228
function specular_btdf(α) {
229-
return V_T * D_T
229+
return Vis_T * D_T
230230
}
231231
```
232232

specification/2.0/Specification.adoc

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3058,33 +3058,33 @@ G = \frac{2 \, \left| N \cdot L \right| \, \chi^{+}(H \cdot L)}{\left| N \cdot L
30583058

30593059
where χ^+^(*x*) denotes the Heaviside function: 1 if *x* > 0 and 0 if *x* <= 0. See <<Heitz2014,Heitz (2014)>> for a derivation of the formulas.
30603060

3061-
Introducing the visibility function
3061+
Introducing the visibility function $\mathcal{V}$
30623062

30633063
[latexmath]
30643064
++++
3065-
V = \frac{G}{4 \, \left| N \cdot L \right| \, \left| N \cdot V \right|}
3065+
\mathcal{V} = \frac{G}{4 \, \left| N \cdot L \right| \, \left| N \cdot V \right|}
30663066
++++
30673067

30683068
simplifies the original microfacet BRDF to
30693069

30703070
[latexmath]
30713071
++++
3072-
\text{MicrofacetBRDF} = V D
3072+
\text{MicrofacetBRDF} = \mathcal{V} D
30733073
++++
30743074

30753075
with
30763076

30773077
[latexmath]
30783078
++++
3079-
V = \frac{\, \chi^{+}(H \cdot L)}{\left| N \cdot L\right| + \sqrt{\alpha^2 + (1 - \alpha^2) (N \cdot L)^2}} \frac{\, \chi^{+}(H \cdot V)}{\left| N \cdot V \right| + \sqrt{\alpha^2 + (1 - \alpha^2) (N \cdot V)^2}}
3079+
\mathcal{V} = \frac{\, \chi^{+}(H \cdot L)}{\left| N \cdot L\right| + \sqrt{\alpha^2 + (1 - \alpha^2) (N \cdot L)^2}} \frac{\, \chi^{+}(H \cdot V)}{\left| N \cdot V \right| + \sqrt{\alpha^2 + (1 - \alpha^2) (N \cdot V)^2}}
30803080
++++
30813081

30823082
Thus, we have the function
30833083

30843084
[source,c]
30853085
----
30863086
function specular_brdf(α) {
3087-
return V * D
3087+
return Vis * D
30883088
}
30893089
----
30903090

@@ -3178,22 +3178,23 @@ Metal and dielectric are mixed according to the metalness:
31783178
material = mix(dielectric_brdf, metal_brdf, metallic)
31793179
----
31803180

3181-
Taking advantage of the fact that `roughness` is shared between metal and dielectric and that the Schlick Fresnel is used, we can simplify the mix and arrive at the final BRDF for the material:
3181+
The full code is given below:
31823182

31833183
[source,c]
31843184
----
3185-
const black = 0
3185+
fresnel_w = (1 - abs(VdotH))^5
31863186
3187-
c_diff = lerp(baseColor.rgb, black, metallic)
3188-
f0 = lerp(0.04, baseColor.rgb, metallic)
3189-
α = roughness^2
3187+
diffuse_brdf = (1 / π) * baseColor.rgb
3188+
specular_brdf = D(roughness^2) * G(roughness^2) / (4 * abs(VdotN) * abs(LdotN))
31903189
3191-
F = f0 + (1 - f0) * (1 - abs(VdotH))^5
3190+
dielectric_f0 = 0.04
3191+
dielectric_fresnel = dielectric_f0 + (1 - dielectric_f0) * fresnel_w
3192+
dielectric_brdf = mix(diffuse_brdf, specular_brdf, dielectric_fresnel)
31923193
3193-
f_diffuse = (1 - F) * (1 / π) * c_diff
3194-
f_specular = F * D(α) * G(α) / (4 * abs(VdotN) * abs(LdotN))
3194+
metal_fresnel = baseColor.rgb + (1 - baseColor.rgb) * fresnel_w
3195+
metal_brdf = metal_fresnel * specular_brdf
31953196
3196-
material = f_diffuse + f_specular
3197+
material = mix(dielectric_brdf, metal_brdf, metallic)
31973198
----
31983199

31993200

0 commit comments

Comments
 (0)