|
23 | 23 | <meta name="viewport" content="width=device-width, initial-scale=1">
|
24 | 24 | <link rel="shortcut icon" type="image/png" href="../assets/favicon.png"/>
|
25 | 25 | <link type="text/css" href="../styles/examples.css" rel="stylesheet" />
|
26 |
| - <script type='module' src='https://modelviewer.dev/node_modules/@google/model-viewer/dist/model-viewer.min.js'></script> |
| 26 | + <script type='module' src='../../../node_modules/@google/model-viewer/dist/model-viewer.js'></script> |
27 | 27 | <script defer src="https://web3dsurvey.com/collector.js"></script>
|
28 | 28 | <script>
|
29 | 29 | window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
|
@@ -146,19 +146,19 @@ <h3 id="purpose">The purpose of tone mapping</h3>
|
146 | 146 | and even things like sepia can all be combined into a single resulting
|
147 | 147 | function that here we're calling tone mapping. However, we are only
|
148 | 148 | interested in hue-neutral functions and those are the only type of
|
149 |
| - tone mapping functions we'll be discussing here. Therefore the focus will be |
150 |
| - primarily on luma, or brightness.</p> |
| 149 | + tone mapping functions we'll be discussing here.</p> |
151 | 150 |
|
152 |
| - <p>The tone mapping function used by <code><model-viewer></code> is |
153 |
| - ACES, which is a standard developed by the film industry and is widely used |
154 |
| - for 3D rendering. Like most tone mapping curves, it is fairly linear in the |
155 |
| - central focus of its contrast range, then asymptotes out to smoothly |
| 151 | + <p>The default tone mapping function used by |
| 152 | + <code><model-viewer></code> has been ACES, which is a standard |
| 153 | + developed by the film industry and is widely used for 3D rendering, though |
| 154 | + it has some problems. Like most tone mapping curves, it is fairly linear in |
| 155 | + the central focus of its contrast range, then asymptotes out to smoothly |
156 | 156 | compress the long tails of brights and darks into the required zero to one
|
157 | 157 | output range, the idea being that humans perceive less difference between
|
158 | 158 | over-bright and over-dark zones as compared to the bulk of the scene.
|
159 | 159 | However, since some output range is reserved for these extra-bright
|
160 | 160 | highlights, the range left over to represent the input range of matte
|
161 |
| - baseColors is also reduced somewhat. This is why the paper-white sphere does |
| 161 | + baseColors is also reduced. This is why a paper-white material does |
162 | 162 | not produce white pixels.</p>
|
163 | 163 |
|
164 | 164 | <p>Sometimes when working with matte objects and trying to compare output
|
@@ -186,28 +186,30 @@ <h3 id="purpose">The purpose of tone mapping</h3>
|
186 | 186 | <figcaption>Toggle ACES tone mapping to see the difference it makes.</figcaption>
|
187 | 187 | </figure>
|
188 | 188 |
|
189 |
| - <p>Note that once again the shiny and matte white plastic spheres are |
190 |
| - indistinguishable, even without cranking up the exposure. Since half of the |
191 |
| - matte white sphere is now rendering pure white, there is no headroom for |
192 |
| - shiny highlights. Likewise, the top half of the sphere loses its 3D |
193 |
| - appearance since the shading was removed by clamping the values. Tick the |
194 |
| - checkbox to go back to ACES tone mapping for a quick comparison. Remember to |
195 |
| - look away and back again after switching; another trick of human perception |
196 |
| - is how dependent it is on anchoring. The yellow will look washed-out |
197 |
| - immediately after switching from saturated yellow, but this perception fades |
198 |
| - after looking around.</p> |
| 189 | + <p>This model has six spheres with uniform materials: The top row are white |
| 190 | + (baseColor RGB: [1, 1, 1]), while the bottom row are yellow (baseColor RGB: |
| 191 | + [1, 1, 0]). From left to right they are shiny metal (metalness: 1, |
| 192 | + roughness: 0), shiny plastic (metalness: 0, roughness: 0), and matte plastic |
| 193 | + (metalness: 0, roughness: 1). The left-most can be thought of approximately |
| 194 | + as polished silver and gold.</p> |
| 195 | + |
| 196 | + <p>Tick the checkbox to remove tone mapping for a quick comparison. Note |
| 197 | + that the shiny and matte white plastic spheres are now indistinguishable. |
| 198 | + Since half of the matte white sphere is now rendering pure white, there is |
| 199 | + no headroom for shiny highlights. Likewise, the top half of the sphere loses |
| 200 | + its 3D appearance since the shading was removed by clamping the values. |
| 201 | + </p> |
199 | 202 |
|
200 | 203 | <p>This example also highlights a second key element of good tone mapping
|
201 | 204 | functions: desaturating overexposed colors. Look at the golden sphere
|
202 |
| - (lower-left) and compare to the previous version with ACES tone mapping |
203 |
| - applied. The baseColor of a metal multiplies the incoming light, so a white |
204 |
| - light on a golden sphere produces a yellow reflection (fully saturated |
205 |
| - yellow, in this case of a fully saturated baseColor). With clamped tone |
206 |
| - mapping, the highlight is indeed saturated yellow, but this does not look |
207 |
| - perceptually right, even though you could make the argument it is physically |
208 |
| - correct.</p> |
209 |
| - |
210 |
| - <p>Good tone mapping curves like ACES not only compress the luma, but also |
| 205 | + (lower-left) and compare to when ACES tone mapping is applied. The baseColor |
| 206 | + of a metal multiplies the incoming light, so a white light on a golden |
| 207 | + sphere produces a yellow reflection (fully saturated yellow, in this case of |
| 208 | + a fully saturated baseColor). With clamped tone mapping, the highlight is |
| 209 | + indeed saturated yellow, but this does not look perceptually right, even |
| 210 | + though you could make the argument it is physically correct.</p> |
| 211 | + |
| 212 | + <p>Tone mapping curves like ACES not only compress the luma, but also |
211 | 213 | push colors toward white the brighter they are. This is why the highlights
|
212 | 214 | on the golden sphere become white instead of yellow. This follows both the
|
213 | 215 | behavior of camera sensors and our eyes when responding to overexposed
|
@@ -270,13 +272,20 @@ <h3 id="#tradeoffs">Tradeoffs</h3>
|
270 | 272 |
|
271 | 273 | <figure>
|
272 | 274 | <model-viewer
|
273 |
| - src="../../shared-assets/models/silver-gold.gltf" |
274 |
| - tone-mapping="aces" |
| 275 | + id="reachable" |
| 276 | + src="../assets/ACESset.glb" |
| 277 | + camera-orbit="150deg auto auto" |
275 | 278 | camera-controls
|
276 |
| - alt="3D model of six example material spheres" |
| 279 | + alt="3D model of ACES/Commerce tone mapping reachable colors." |
277 | 280 | >
|
| 281 | + <p> |
| 282 | + <select id="set"> |
| 283 | + <option value="../assets/ACESset.glb">ACES</option> |
| 284 | + <option value="../assets/CommerceSet.glb">Commerce</option> |
| 285 | + </select>Tone Mapping Function |
| 286 | + </p> |
278 | 287 | </model-viewer>
|
279 |
| - <figcaption>The ACES reachable colors.</figcaption> |
| 288 | + <figcaption>Comparison of the ACES and Commerce tone mapping reachable colors. The cube represents the [0, 1] space in linear light - no sRGB curve has been applied.</figcaption> |
280 | 289 | </figure>
|
281 | 290 |
|
282 | 291 | <p>Note that canary yellow, bright greens and blues are all impossible to
|
@@ -398,7 +407,10 @@ <h3 id="#commerce">Commerce tone mapper</h3>
|
398 | 407 | only other parameter in this tone mapper controls the rate of desaturation,
|
399 | 408 | which I chose as 0.15, which is significantly slower to approach its
|
400 | 409 | asymptote than the compression function. This is what helps produce our
|
401 |
| - smoother gradients and hide the aggressiveness of our compression.</p> |
| 410 | + smoother gradients and hide the aggressiveness of our compression. In some |
| 411 | + sense I am replacing the lost brightness with desaturation, thus giving the |
| 412 | + brain an alternate perceptual cue, which smoothly encodes several orders of |
| 413 | + magnitude more brightness than is available in the output screen.</p> |
402 | 414 |
|
403 | 415 | <p>The complete shader code is quite small, with only three divides and
|
404 | 416 | those only applied to colors over the 1:1 limit:<br/>
|
@@ -447,7 +459,7 @@ <h3 id="#commerce">Commerce tone mapper</h3>
|
447 | 459 | <figure>
|
448 | 460 | <model-viewer
|
449 | 461 | id="demo"
|
450 |
| - src="https://cdn.glitch.global/93f14439-9407-4d48-8b56-aa0afd9b2886/MacbethBalls.glb?v=1704772991939" |
| 462 | + src="../../shared-assets/models/MacbethBalls.glb" |
451 | 463 | tone-mapping="commerce"
|
452 | 464 | ar
|
453 | 465 | camera-controls
|
@@ -479,6 +491,50 @@ <h3 id="#commerce">Commerce tone mapper</h3>
|
479 | 491 | <figcaption>Tone mapper test demo.</figcaption>
|
480 | 492 | </figure>
|
481 | 493 |
|
| 494 | + <h3 id="#validation">Validation</h3> |
| 495 | + |
| 496 | + <p>The best end-to-end validation we have for color accuracy is to apply an |
| 497 | + unrealistic, analytic lighting environment: a white furnace test, where the |
| 498 | + lighting is exactly uniform [1, 1, 1] white everywhere. This allows us to |
| 499 | + expect a nearly-exact reproduction of baseColor to the output render, and |
| 500 | + thus ensure our tone mapping function is not introducing further |
| 501 | + changes.</p> |
| 502 | + |
| 503 | + <p>Our 3D Macbeth chart model is ideal for this validation because tone |
| 504 | + mapping is not applied at all to unlit materials, so the unlit spheres serve |
| 505 | + as ground truth color comparisons for the PBR spheres. As you can see, they match |
| 506 | + very well, in fact as close as is possible to match for PBR: there are |
| 507 | + two expected sources of difference.</p> |
| 508 | + |
| 509 | + <figure> |
| 510 | + <model-viewer |
| 511 | + id="demo" |
| 512 | + src="../../shared-assets/models/MacbethBalls.glb" |
| 513 | + skybox-image="../../shared-assets/environments/white_furnace.hdr" |
| 514 | + tone-mapping="commerce" |
| 515 | + ar |
| 516 | + camera-controls |
| 517 | + shadow-intensity="1" |
| 518 | + > |
| 519 | + </model-viewer> |
| 520 | + <figcaption>Commerce tone mapper validation.</figcaption> |
| 521 | + </figure> |
| 522 | + |
| 523 | + <p>The first difference is from the Fresnel effect: on shiny materials, the |
| 524 | + reflection loses material color near grazing angles. This is a physical |
| 525 | + reality and causes the white halos on the edges of the shiny (back) spheres. |
| 526 | + If you turn the model until the unlit spheres overlap the middle of the |
| 527 | + shiny spheres, you'll see that the color match is exact at normal (center) |
| 528 | + reflection.</p> |
| 529 | + |
| 530 | + <p>The second effect is multi-scattering, which causes the dark-colored |
| 531 | + matte (front) spheres to be slightly darker than their unlit comparisons. |
| 532 | + This is also intentional, as matte materials are rough, thus forming |
| 533 | + microscopic cavities that cause slight ambient occlusion and allow dark |
| 534 | + materials more light bounces to absorb energy. Accurate PBR renderers |
| 535 | + include this effect because a single material will in fact become brighter |
| 536 | + as it is polished.</p> |
| 537 | + |
482 | 538 | <h3 id="#white">White point</h3>
|
483 | 539 |
|
484 | 540 | <p>The tl;dr of this section is that you can safely skip it. It is a
|
@@ -583,21 +639,10 @@ <h3 id="#white">White point</h3>
|
583 | 639 | updateToneMapper(toneMV, checkbox.checked ? "4" : "1");
|
584 | 640 | });
|
585 | 641 |
|
586 |
| - const envMV = document.querySelector("#environments"); |
587 |
| - const envCycle = [ |
588 |
| - "../../shared-assets/environments/spruit_sunrise_1k_HDR.hdr", |
589 |
| - "../../shared-assets/environments/whipple_creek_regional_park_04_1k.hdr", |
590 |
| - "../../shared-assets/environments/lebombo_1k.hdr", |
591 |
| - "../../shared-assets/environments/aircraft_workshop_01_1k.hdr", |
592 |
| - "../../shared-assets/environments/music_hall_01_1k.hdr", |
593 |
| - "../../shared-assets/environments/pillars_1k.hdr", |
594 |
| - "../../shared-assets/environments/neutral.hdr" |
595 |
| - ]; |
596 |
| - |
597 |
| - setInterval(() => { |
598 |
| - const cycleIndex = envCycle.indexOf(envMV.skyboxImage); |
599 |
| - envMV.skyboxImage = envCycle[(cycleIndex + 1) % envCycle.length]; |
600 |
| - }, 3000); |
| 642 | + const reachMV = document.querySelector("#reachable"); |
| 643 | + document.querySelector('#set').addEventListener("input", (event) => { |
| 644 | + reachMV.src = event.target.value; |
| 645 | + }); |
601 | 646 | </script>
|
602 | 647 | </body>
|
603 | 648 | </html>
|
0 commit comments