Skip to content

Commit

Permalink
Coat roughening convolution formula (#172)
Browse files Browse the repository at this point in the history
Essentially, an improved version of the Standard Surface roughening formula. We need to suggest some approximation, as implementations need to account for this somehow. As discussed on Slack, thinking about the roughening as a convolution suggests an "as simple as possible" physically-based formula for the effect. I assume this is closer to the ground truth than the ad-hoc Standard-Surface formula, but verifying that would require more work.
  • Loading branch information
portsmouth authored May 14, 2024
1 parent 8d83f86 commit e7ec2c7
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 21 deletions.
27 changes: 19 additions & 8 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -864,16 +864,14 @@
* +-------------------------------------------------+ *
*******************************************************

The absorption of the medium $V_\mathrm{coat}$ is parametrized by **`coat_color`**, which is assumed to specify the _square_ of the transmittance $T_\mathrm{coat}$ of the coat at normal incidence (i.e. $T^2_\mathrm{coat}$ = **`coat_color`**). Thus at normal incidence, the observed tint color of the underlying base due to absorption in the coat is approximately given by **`coat_color`** due to the absorption along both the incident and outgoing rays (note that the specular reflection from the coat itself is _not_ tinted).
The absorption of the medium $V_\mathrm{coat}$ is parametrized by **`coat_color`**, which is assumed to specify the _square_ of the transmittance $T_\mathrm{coat}$ of the coat at normal incidence (i.e. $T^2_\mathrm{coat}$ = **`coat_color`**). Thus at normal incidence, the observed tint color of the underlying base due to absorption in the coat is approximately given by **`coat_color`** due to the absorption along the incident and outgoing rays (note that the specular reflection from the coat itself is _not_ tinted).

The IOR $n_\mathrm{coat} = \mathtt{coat\_ior}$ of the coat medium $V_\mathrm{coat}$ will alter the Fresnel factor of both the coat top interface and the underlying metal or dielectric. If there is a fractional $\mathtt{coat\_weight}$ $\mathtt{C}$, then the surrounding IOR of the base dielectric or metal varies statistically across the surface depending on whether the coat is locally present (and the fuzz layer can be assumed to have the ambient IOR $n_\mathrm{ambient}$). The ratio between the specular IOR $n_\mathrm{specular} = \mathtt{specular\_ior}$ and the surrounding medium can thus reasonably be approximated as
\begin{equation} \label{specular_ior_ratio}
\eta_s = \mathrm{lerp}(n_\mathrm{specular}/n_\mathrm{ambient}, n_\mathrm{specular}/n_\mathrm{coat}, \mathtt{C}) \ .
\end{equation}
This ratio then determines the specular Fresnel factor, as in equation [modulated_ior].



Coat params | Label | Type | Range | Norm | Default | Description
--------------------------------|------------|----------|:---------------:|:----------:|:-------------:|----------------------------------------------
**`coat_weight`** | Weight | `float` | $ [0, 1] $ | | $ 0 $ | Coverage weight of coat slab
Expand All @@ -883,19 +881,32 @@
**`coat_ior`** | IOR | `float` | $ (0, \infty) $ | $ [1, 3] $ | $ 1.6 $ | Refractive index of $V_\mathrm{coat}$
**`coat_darkening`** | Darkening | `float` | $ [0, 1] $ | | $ 0 $ | Modulates the physical coat [darkening](index.html#model/coat/darkening) effect.


![](images/coat_0.png width=90% align=right) ![](images/coat_1.png width=90% align=left)
<div class="shifted-caption">
![Figure [coat]: Coat adds a secondary specular highlight and optional absorption tint](dummy)
</div>

In the full light transport, the observed color of the coated base is darkened and saturated due to multiple internal reflections from the inside of the coat, which causes light to strike the underlying material multiple times and undergo more absorption, and the observed **`coat_color`** tint also darkens as the incidence angle changes due to the change in path length in the medium. Also, the presence of a rough coat will increase the apparent roughness of the BSDF lobes of the underlying base. We assume that in the ground truth appearance, all these physical effects are accounted for. In the following sub-sections, we detail recommendations for implementation of them.
In the full light transport, the observed color of the coated base is darkened and saturated due to multiple internal reflections from the inside of the coat, which causes light to strike the underlying material multiple times and undergo more absorption, and the observed **`coat_color`** tint also darkens as the incidence angle changes due to the change in path length in the medium. Also, the presence of a rough coat will increase the apparent roughness of the BSDF lobes of the underlying base.

We assume that in the ground truth appearance, all these physical effects are accounted for [^porosity]. In the following sub-sections, we detail recommendations for implementation of them.


### Roughening

If the coat is rough, the microfacet BSDF lobes of the underlying base substrate (metal and dielectric) are also effectively roughened. If this is not otherwise accounted for by the light transport, it can instead be reasonably approximated by directly altering the NDF of the base BSDFs.

A formula we recommend for this is obtained by identifying the NDF of each microfacet lobe as corresponding approximately to a Gaussian in slope-space with variance given by $\alpha_t^2 + \alpha_b^2 = r^4$ (in the notation of the [Microfacet model](index.html#model/microfacetmodel) section). Modeling the effect of the roughening as the convolution of these Gaussian NDFs (and double counting the coat variance since the reflection passes through the coat boundary twice), the resulting modified roughness of the base, $r'_\mathrm{B}$, (taking into account the presence weight of the coat, $\mathtt{C}=$ **`coat_weight`**) is given by
\begin{equation}
r'_\mathrm{B} = \mathrm{lerp}\Bigl( r_\mathrm{B}, \mathrm{min} \bigl(1, r^4_\mathrm{B} + 2 r^4_\mathrm{C} \bigr)^\frac{1}{4}, \mathtt{C} \Bigr)
\end{equation}
where $r_\mathrm{B}=$ **`specular_roughness`** and $r_\mathrm{C}=$ **`coat_roughness`**.


### Darkening

Figure [coat_darkening_grid] shows the physically-correct change in appearance (at normal incidence) of a textured diffuse base with a wood texture and smooth clear-coat as the IOR of the clear-coat is varied, exhibiting darkening due to the internal reflections which increases as IOR increases. [^porosity]
Figure [coat_darkening_grid] shows the physically-correct change in appearance (at normal incidence) of a textured diffuse base with a wood texture and smooth clear-coat as the IOR of the clear-coat is varied, exhibiting darkening of the base -- due to the internal reflections in the coat -- which increases as IOR increases.

![Figure [coat_darkening_grid]: Diffuse base with a clear-coat, for coats of different coat IOR $\eta_c$](images/coat_darkening_grid.png width="60%")
![Figure [coat_darkening_grid]: Diffuse base with a clear-coat, for coats of different relative IOR $\eta_c$](images/coat_darkening_grid.png width="60%")

However this darkening may not always be desirable artistically, as in some applications it is beneficial for the observed color of the coated color to "match" the input base color (in a sense defined more precisely below in equation [undarkened_coat_albedo]). We allow for this by introducing a **`coat_darkening`** parameter, $\delta$. In the case **`coat_darkening`** $\delta$ = $1$, the physically correct darkening effect due to internal reflections occurs as normal. In the case **`coat_darkening`** $\delta$ = $0$ (the default), the base albedo is instead _boosted_ uniformly by just enough to counteract the darkening effect. The boost factor is reduced to 1 linearly as $\delta \rightarrow 1$.

Expand Down Expand Up @@ -1503,7 +1514,7 @@
E_F(\eta) = 1 - \eta^2 \bigl(1 - E_F(1/\eta)\bigr) \ .
\end{equation}

[^absorption_effect_on_K]: Technically, the coefficient $K$ should also be modified to account for the absorption, but the effect of this can reasonably be ignored.
[^absorption_effect_on_K]: Technically, the _internal diffuse reflection coefficient_ $K$ described in the Darkening section should also be modified to account for the absorption, but the effect of this can reasonably be ignored.

[^BSDF_BSSRDF_sum]: This sum of BSDF and BSSRDF can be justified mathematically by interpreting a BSDF as the special case of a BSSRDF restricted to equal exit and entry points.

Expand Down
43 changes: 30 additions & 13 deletions reference/open_pbr_surface.mtlx
Original file line number Diff line number Diff line change
Expand Up @@ -95,23 +95,40 @@
-->
<nodegraph name="NG_open_pbr_surface_surfaceshader" nodedef="ND_open_pbr_surface_surfaceshader">

<!-- Roughness influence by coat-->
<!-- Calculate main specular roughness -->
<multiply name="coat_affect_roughness_multiply1" type="float">
<input name="in1" type="float" value="1.0" />
<input name="in2" type="float" interfacename="coat_weight" />
</multiply>
<multiply name="coat_affect_roughness_multiply2" type="float">
<input name="in1" type="float" nodename="coat_affect_roughness_multiply1" />
<input name="in2" type="float" interfacename="coat_roughness" />
<!-- Roughening due to coat-->
<power name="coat_roughness_to_power_4" type="float">
<input name="in1" type="float" interfacename="coat_roughness" />
<input name="in2" type="float" value="4.0" />
</power>
<multiply name="two_times_coat_roughness_to_power_4" type="float">
<input name="in1" type="float" nodename="coat_roughness_to_power_4" />
<input name="in2" type="float" value="2.0" />
</multiply>
<mix name="coat_affected_roughness" type="float">
<input name="fg" type="float" value="1.0" />
<power name="specular_roughness_to_power_4" type="float">
<input name="in1" type="float" interfacename="specular_roughness" />
<input name="in2" type="float" value="4.0" />
</power>
<add name="add_coat_and_spec_roughnesses_to_power_4" type="float">
<input name="in1" type="float" nodename="two_times_coat_roughness_to_power_4" />
<input name="in2" type="float" nodename="specular_roughness_to_power_4" />
</add>
<min name="min_1_add_coat_and_spec_roughnesses_to_power_4" type="float">
<input name="in1" type="float" value="1.0" />
<input name="in2" type="float" nodename="add_coat_and_spec_roughnesses_to_power_4" />
</min>
<power name="coat_affected_specular_roughness" type="float">
<input name="in1" type="float" nodename="min_1_add_coat_and_spec_roughnesses_to_power_4" />
<input name="in2" type="float" value="0.25" />
</power>
<mix name="effective_specular_roughness" type="float">
<input name="fg" type="float" nodename="coat_affected_specular_roughness" />
<input name="bg" type="float" interfacename="specular_roughness" />
<input name="mix" type="float" nodename="coat_affect_roughness_multiply2" />
<input name="mix" type="float" interfacename="coat_weight" />
</mix>

<!-- Calculate main specular roughness -->
<open_pbr_anisotropy name="main_roughness" type="vector2">
<input name="roughness" type="float" nodename="coat_affected_roughness" />
<input name="roughness" type="float" nodename="effective_specular_roughness" />
<input name="anisotropy" type="float" interfacename="specular_roughness_anisotropy" />
</open_pbr_anisotropy>

Expand Down

0 comments on commit e7ec2c7

Please sign in to comment.