Skip to content

Commit 6cb2cb8

Browse files
committed
move around implementation descriptions
1 parent b9c5773 commit 6cb2cb8

2 files changed

Lines changed: 67 additions & 62 deletions

File tree

extensions/2.0/Khronos/KHR_materials_coat/README.md

Lines changed: 66 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ coatAnisotropyRotation += atan(coatAnisotropyTexture.g, coatAnisotropyTexture.r)
111111

112112
## Basic Coating
113113

114-
The coat effect is modeled via a microfacet BRDF. The BRDF is layered on top of the glTF 2.0 Metallic-Roughness material, excluding emission, using a new `fresnel_coat` function:
114+
The coat effect is modeled via a microfacet BRDF. The BRDF is layered on top of the glTF 2.0 Metallic-Roughness material, including emission and all extensions, using a new `fresnel_coat` function:
115115

116116
```
117117
# base material (from glTF 2.0 Metallic-Roughness)
@@ -151,7 +151,11 @@ function fresnel_coat(normal, ior, weight, base, layer) {
151151

152152
#### Emission
153153

154-
Emission is assumed to be on top of the coat layer in the layering stack. Consequently, the emission is not affected by coat.
154+
The clearcoat layer is on top of emission in the layering stack. Consequently, the emission is darkened by the Fresnel term.
155+
156+
```
157+
coated_emission = emission * (1 - clearcoat * clearcoat_fresnel)
158+
```
155159

156160
## Index of Refraction
157161

@@ -193,31 +197,6 @@ Note that this view-dependent absorption is more pronounced when the ratio of IO
193197
</figcaption>
194198
</figure>
195199

196-
## Implementation
197-
198-
*This section is non-normative.*
199-
200-
At normal incidence, `coatColor` can simply be multiplied by the light coming from the underlying base layer. However, we need to modify this based on the incident angle:
201-
202-
```
203-
// cos_i is the non-negative cosine of the unrefracted view angle
204-
sin2_i = 1.0f - cos_i * cos_i
205-
// eta is the ratio of IOR's of the coat and surrounding medium. i.e. coat_ior / 1.0 for air.
206-
sin2_t = sin2_i / (eta * eta)
207-
// Total internal reflection. i.e. no light makes it out of coating.
208-
if (sin2_t >= 1.0)
209-
{
210-
final_coat_color = vec3(0.0f)
211-
}
212-
// Compute cosine of transmition angle.
213-
cos_t = sqrt(1.0f - sin2_t);
214-
// Calculate the path length through the coating, relative to normal incidence.
215-
path_length = 1.0 / cos_t;
216-
// Use the path length to calculate the final tinting.
217-
final_coat_color = pow(coatColor, path_length)
218-
```
219-
220-
221200
## Darkening Control
222201

223202
The coating will naturally darken the appearance of the underlying surface due to internal reflections. However, when this physically-correct behavior is not be desired, the the `coatDarkeningFactor` can be used to control the amount of darkening:
@@ -243,32 +222,6 @@ Things that affect the amount of darkening observed:
243222

244223
1. Roughness Modulation: Rougher coats scatter light internally, reducing the coherent reflection effect. Increased diffuse internal reflections results in less darkening.
245224

246-
### Implementation
247-
248-
*This section is non-normative.*
249-
250-
Some renderers (such as path-tracers) may already model this darkening behaviour by the nature of their light transport algorithms. In these cases, implementing support for this extension will involve adding a term to compensate for the energy loss when the darknening value is < 1.0. The [specification of OpenPBR](https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat/darkening) contains a detailed description of what this entails.
251-
252-
Other renderers (such as rasterizers) will most likely need to add logic to approximate the loss of energy due to these internal reflections when the darkening value is > 0.0. We provide some example logic to do this below:
253-
254-
#### Real-time Implementation
255-
256-
We want to calculate a multiplier that represents the amount of transmitted light that makes its way through the coat and impacts the underlying layer. To approximate this, we want to find the average reflectance, $R$, of the coat and then model an infinite number of reflections using a geometric series to get the total transmission, $T$.
257-
258-
$T = (1-R) / (1 + R + R² + R³ + ...)$<br>
259-
$T = (1-R) / (1/(1-R))$</br>
260-
$T = (1-R)²$
261-
262-
The $(1-R)$ in the numerator represents the initial transmission of light through the coat and the denominator accounts for the infinite reflections within the coating. This converges to $1/(1-R)$.
263-
264-
By "average reflectance", we mean the average reflectance for the round trip (light in, view out) so it can be computed as the sum of the Schlick Fresnel approximation for $dot(N, V)$ and $dot(N, L)$ and then dividing by 2.
265-
$$R_{directlight} = (fresnel(N, V) + fresnel(N, L)) * 0.5$$
266-
For environment lighting (IBL), however, light is coming from all angles so we calculate the hemisphere-averaged reflectance as $F_0 + 0.5 * F_{90}$ which gives a value halfway between $F_0$ and $F_{90}$. We then add to this the Schlick Fresnel approximation for $dot(N, V)$ and divide by 2.
267-
$$R_{IBL} = (fresnel(N, V) + F_0 + 0.5 * F_{90}) * 0.5$$
268-
269-
270-
When coat roughness increases, light is diffused and darkening decreases. We can approximate this empirically by scaling the average reflectance, $R$, by $1.0 - M_c * 0.5$ where $M_c$ is the roughness of the clearcoat.
271-
272225
## Anisotropy
273226

274227
The coating can exhibit anisotropic behavior where specular reflection is stretched in one direction, similar to brushed metal or directionally polished surfaces.Anisotropy modifies the microfacet distribution of the coating layer such that the specular lobe is stretched along a direction relative to the surface tangents.
@@ -341,6 +294,66 @@ More sophisticated layering models may be implemented for improved accuracy.
341294
- `KHR_materials_ior`: Sets base layer IOR; `coatIor` is independent
342295
- Other material extensions: Generally apply to the base layer, with the coat layered on top
343296

297+
## Implementation
298+
299+
*This section is non-normative.*
300+
301+
### Colored Absorption
302+
303+
At normal incidence, `coatColor` can simply be multiplied by the light coming from the underlying base layer. However, we need to modify this based on the incident angle:
304+
305+
```
306+
// cos_i is the non-negative cosine of the unrefracted view angle
307+
sin2_i = 1.0f - cos_i * cos_i
308+
// eta is the ratio of IOR's of the coat and surrounding medium. i.e. coat_ior / 1.0 for air.
309+
sin2_t = sin2_i / (eta * eta)
310+
// Total internal reflection. i.e. no light makes it out of coating.
311+
if (sin2_t >= 1.0)
312+
{
313+
final_coat_color = vec3(0.0f)
314+
}
315+
// Compute cosine of transmition angle.
316+
cos_t = sqrt(1.0f - sin2_t);
317+
// Calculate the path length through the coating, relative to normal incidence.
318+
path_length = 1.0 / cos_t;
319+
// Use the path length to calculate the final tinting.
320+
final_coat_color = pow(coatColor, path_length)
321+
```
322+
323+
### Darkening
324+
325+
Some renderers (such as path-tracers) may already model this darkening behaviour by the nature of their light transport algorithms. In these cases, implementing support for this extension will involve adding a term to compensate for the energy loss when the darknening value is < 1.0. The [specification of OpenPBR](https://academysoftwarefoundation.github.io/OpenPBR/index.html#model/coat/darkening) contains a detailed description of what this entails.
326+
327+
Other renderers (such as rasterizers) will most likely need to add logic to approximate the loss of energy due to these internal reflections when the darkening value is > 0.0. We provide some example logic to do this below:
328+
329+
#### Real-time Implementation
330+
331+
We want to calculate a multiplier that represents the amount of transmitted light that makes its way through the coat and impacts the underlying layer. To approximate this, we want to find the average reflectance, $R$, of the coat and then model an infinite number of reflections using a geometric series to get the total transmission, $T$.
332+
333+
$T = (1-R) / (1 + R + R² + R³ + ...)$<br>
334+
$T = (1-R) / (1/(1-R))$</br>
335+
$T = (1-R)²$
336+
337+
The $(1-R)$ in the numerator represents the initial transmission of light through the coat and the denominator accounts for the infinite reflections within the coating. This converges to $1/(1-R)$.
338+
339+
By "average reflectance", we mean the average reflectance for the round trip (light in, view out) so it can be computed as the sum of the Schlick Fresnel approximation for $dot(N, V)$ and $dot(N, L)$ and then dividing by 2.
340+
$$R_{directlight} = (fresnel(N, V) + fresnel(N, L)) * 0.5$$
341+
For environment lighting (IBL), however, light is coming from all angles so we calculate the hemisphere-averaged reflectance as $F_0 + 0.5 * F_{90}$ which gives a value halfway between $F_0$ and $F_{90}$. We then add to this the Schlick Fresnel approximation for $dot(N, V)$ and divide by 2.
342+
$$R_{IBL} = (fresnel(N, V) + F_0 + 0.5 * F_{90}) * 0.5$$
343+
344+
When coat roughness increases, light is diffused and darkening decreases. We can approximate this empirically by scaling the average reflectance, $R$, by $1.0 - M_c * 0.5$ where $M_c$ is the roughness of the clearcoat.
345+
346+
## Conversion from KHR_materials_clearcoat
347+
348+
The parameters from KHR_materials_clearcoat are fully transferable to this extension, with no changes.
349+
`clearcoatFactor` -> `coatFactor`
350+
`clearcoatTexture` -> `coatTexture`
351+
`clearcoatRoughnessFactor` -> `coatRoughnessFactor`
352+
`clearcoatRoughnessTexture` -> `coatRoughnessTexture`
353+
`clearcoatNormalTexture` -> `coatNormalTexture`
354+
355+
The default values of the new parameters should, for the most part, result in fully backwards-compatible behaviour. The one exception is `coatDarkeningFactor` where the default value is `1.0` which enables the physically-correct darkening. This may or may not match a renderer's handling of the `KHR_materials_clearcoat` extension as modeling these internal reflections was not required by that extension. Therefore it is left up to the implementor of conversion tools whether to set this property to `0.0` during conversion.
356+
344357
## Schema
345358

346359
- [material.KHR_materials_coat.schema.json](schema/material.KHR_materials_coat.schema.json)

extensions/2.0/Khronos/KHR_materials_coat/schema/material.KHR_materials_coat.schema.json

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,7 @@
4040
"type": "number",
4141
"description": "The index of refraction of the coat layer.",
4242
"default": 1.5,
43-
"oneOf": [
44-
{
45-
"minimum": 0.0,
46-
"maximum": 0.0
47-
},
48-
{
49-
"minimum": 1.0
50-
}
51-
],
43+
"minimum": 1.0,
5244
"gltf_detailedDescription": "The index of refraction (IOR) of the coat layer is a measured physical number usually in the range between 1 and 2 that determines how much the path of light is bent, or refracted, when entering the coat material. It also influences the ratio between reflected and transmitted light in the coat layer, calculated from the Fresnel equations."
5345
},
5446
"coatColorFactor": {

0 commit comments

Comments
 (0)