Skip to content

Commit

Permalink
Refactor clearcoat extension to look similar to Appendix B.
Browse files Browse the repository at this point in the history
  • Loading branch information
proog128 committed Aug 19, 2020
1 parent aa7d8e0 commit 2ce983a
Showing 1 changed file with 83 additions and 11 deletions.
94 changes: 83 additions & 11 deletions extensions/2.0/Khronos/KHR_materials_clearcoat/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,28 +61,100 @@ All implementations should use the same calculations for the BRDF inputs. Implem
|**clearcoatRoughnessFactor** | `number` | The clearcoat layer roughness. | No, default: `0.0` |
|**clearcoatRoughnessTexture** | [`textureInfo`](/specification/2.0/README.md#reference-textureInfo) | The clearcoat layer roughness texture. | No |
|**clearcoatNormalTexture** | [`normalTextureInfo`](/specification/2.0/README.md#reference-normaltextureinfo) | The clearcoat normal map texture. | No |

The clearcoat formula `f_clearcoat` is computed using the specular term from the glTF 2.0 Metallic-Roughness material, defined in [Appendix B](/specification/2.0/README.md#appendix-b-brdf-implementation). Use specular F0 equal `0.04`, base color black `0.0, 0.0, 0.0`, metallic value `0.0`, and the clearcoat roughness value defined in this extension as follows:

If `clearcoatFactor` (in the extension) is zero, the whole clearcoat layer is disabled.

The values for clearcoat layer intensity and clearcoat roughness can be defined using factors, textures, or both. If the `clearcoatTexture` or `clearcoatRoughnessTexture` is not given, respective texture components are assumed to have a value of 1.0. All clearcoat textures contain RGB components in linear space. If both factors and textures are present, the factor value acts as a linear multiplier for the corresponding texture values.

```
clearcoat = clearcoatFactor * clearcoatTexture.r
clearcoatRoughness = clearcoatRoughnessFactor * clearcoatRoughnessTexture.g
```

The following abstract code describes how the base and clearcoat layers should be blended together:

If `clearcoatNormalTexture` is not given, no normal mapping is applied to the clear coat layer, even if normal mapping is applied to the base material. Otherwise, `clearcoatNormalTexture` may be a reference to the same normal map used by the base material, or any other compatible normal map.

The clearcoat 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:

```
# from glTF 2.0 Metallic-Roughness material (core)
material = mix(dielectric_brdf, metal_brdf, metallic)
# clearcoat
clearcoat_brdf = specular_brdf(
normal = clearcoatNormal,
α = clearcoatRoughness^2)
# layering
coated_material =
fresnel_coat(
normal = clearcoatNormal,
ior = 1.5,
weight = clearcoat,
base = material,
layer = clearcoat_brdf)
```
clearcoatBlendFactor = clearcoatTexture.r * clearcoatFactor
clearcoatFresnel = fresnel(0.04, NdotV)

color = (f_emissive + f_diffuse + f_specular) * (1.0 - clearcoatBlendFactor * clearcoatFresnel) +
f_clearcoat * clearcoatBlendFactor
The `fresnel_coat` function adds a BRDF as a layer on top of another BSDF according to `weight` and a Fresnel term. The layer is weighted with `weight*fresnel(ior)`. The base is weighted with `1-(weight*fresnel(ior))`. `normal` is a float3 vector that affects the top layer, but not the base.

### Implementation

*This section is non-normative.*

Implementations of the BRDF or the layering operator can vary based on device performance and resource constraints.

#### Clearcoat BRDF

The specular BRDF for the clearcoat layer `clearcoat_brdf` is computed using the specular term from the glTF 2.0 Metallic-Roughness material, defined in [Appendix B](/specification/2.0/README.md#appendix-b-brdf-implementation). Roughness and normal are taken from `clearcoatNormal` and `clearcoatRoughness`.

#### Layering

The `fresnel_coat` function is computed using the Schlick Fresnel term from the glTF 2.0 Metallic-Roughness material, defined in [Appendix B](/specification/2.0/README.md#appendix-b-brdf-implementation).

```
function fresnel_coat(normal, ior, weight, base, layer) {
f0 = ((1-ior)/(1+ior))^2
fr = f0 + (1 - f0)*(1 - abs(NdotV))^5 // N = normal
return mix(base, layer, weight * fr)
}
```

If `clearcoatFactor` (in the extension) is zero, the whole clearcoat layer is disabled.
Applying the functions we arrive at the coated material

The values for clearcoat layer intensity and clearcoat roughness can be defined using factors, textures, or both. If the `clearcoatTexture` or `clearcoatRoughnessTexture` is not given, respective texture components are assumed to have a value of 1.0. All clearcoat textures contain RGB components in linear space. If both factors and textures are present, the factor value acts as a linear multiplier for the corresponding texture values.
```
coated_material = mix(material, clearcoat_brdf(clearcoatRughness^2), clearcoat * (0.04 + (1 - 0.04) * (1 - NdotV)^5))
```

If `clearcoatNormalTexture` is not given, no normal mapping is applied to the clear coat layer, even if normal mapping is applied to the base material. Otherwise, `clearcoatNormalTexture` may be a reference to the same normal map used by the base material, or any other compatible normal map.
and finally, substituting and simplifying, using some symbols from [Appendix B](/specification/2.0/README.md#appendix-b-brdf-implementation) and `Nc` for the clearcoat normal:

```
clearcoatFresnel = 0.04 + (1 - 0.04) * (1 - abs(VdotNc))^5
clearcoatAlpha = clearcoatRoughness^2
f_clearcoat = clearcoatFresnel * D(clearcoatAlpha) * G / (4 * abs(VdotNc) * abs(LdotNc))
coated_material = (f_diffuse + f_specular) * (1 - clearcoat * clearcoatFresnel) +
f_clearcoat * clearcoat
```

#### Emission

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

```
coated_emission = emission * (0.04 + (1 - 0.04) * (1 - NdotV)^5)
```

#### Discussion

In order to make the material energy conserving with a simple layering function, we compute the microfacet Fresnel term with `NdotV` instead of `VdotH`. That means that we ignore the orientation of the microsurface. As the clearcoat roughness is usually very low the microfacets orientation is very close to the normal direction, and `NdotV ≈ NdotL`.

The simple layering function ignores many effects that occur between clearcoat and base layer. For example:
* The clearcoat layer is assumed to be infinitely thin. There is no refraction.
* The index of refraction of clearcoat and base layer do not influence each other. The Fresnel terms are computed independently.
* There is no scattering between layers.
* There is no diffraction.

More sophisticated layering techniques that improve the accuracy of the renderings are described in [Appendix B](/specification/2.0/README.md#appendix-b-brdf-implementation).

## Schema

Expand Down

0 comments on commit 2ce983a

Please sign in to comment.