forked from google/filament
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFilament.html
6286 lines (4223 loc) · 456 KB
/
Filament.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<html><head><meta charset="utf-8">
<style>img { max-width: 100%; }</style>
</head><body style="visibility: visible;"><meta charset="UTF-8"><meta http-equiv="content-type" content="text/html;charset=UTF-8"><style>body{max-width:680px;margin:auto;padding:20px;text-align:justify;line-height:140%; -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-smoothing:antialiased;color:#222;font-family:Palatino,Georgia,"Times New Roman",serif}</style><style>body{counter-reset: h1 h2 h3 h4 h5 h6 paragraph}@page{margin:0;size:auto}.md code,pre{font-family:Menlo,Consolas,monospace;font-size:13.099975100467727px;line-height:140%}.md div.title{font-size:26px;font-weight:800;line-height:120%;text-align:center}.md div.afterTitles{height:10px}.md div.subtitle{text-align:center}.md .image{display:inline-block}.md img{max-width:100%;page-break-inside:avoid}.md li{text-align:left;text-indent:0}.md pre.listing {tab-size:4;-moz-tab-size:4;-o-tab-size:4;counter-reset:line}.md pre.listing .linenumbers span.line:before{width:30px;margin-left:-52px;font-size:80%;text-align:right;counter-increment:line;content:counter(line);display:inline-block;padding-right:13px;margin-right:8px;color:#ccc}.md div.tilde{margin:20px 0 -10px;text-align:center}.md div.imagecaption,.md div.tablecaption,.md div.listingcaption{margin:7px 5px 12px;text-align: justify;font-style:italic}.md div.imagecaption{margin-bottom:0}.md blockquote.fancyquote{margin:25px 0 25px;text-align:left;line-height:160%}.md blockquote.fancyquote::before{content:"“";color:#DDD;font-family:Times New Roman;font-size:45px;line-height:0;margin-right:6px;vertical-align:-0.3em}.md span.fancyquote{font-size:118%;color:#777;font-style:italic}.md span.fancyquote::after{content:"”";font-style:normal;color:#DDD;font-family:Times New Roman;font-size:45px;line-height:0;margin-left:6px;vertical-align:-0.3em}.md blockquote.fancyquote .author{width:100%;margin-top:10px;display:inline-block;text-align:right}.md small{font-size:60%}.md big{font-size:150%}.md div.title,contents,.md .tocHeader,h1,h2,h3,h4,h5,h6,.md .shortTOC,.md .mediumTOC,.nonumberh1,.nonumberh2,.nonumberh3,.nonumberh4,.nonumberh5,.nonumberh6{font-family:Verdana,Helvetica,Arial,sans-serif;margin:13.4px 0 13.4px;padding:15px 0 3px;border-top:none;clear:both}.md h1,.md h2,.md h3,.md h4,.md h5,.md h6,.md .nonumberh1,.md .nonumberh2,.md .nonumberh3,.md .nonumberh4,.md .nonumberh5,.md .nonumberh6{page-break-after:avoid;break-after:avoid}.md svg.diagram{display:block;font-family:Menlo,Consolas,monospace;font-size:13.099975100467727px;text-align:center;stroke-linecap:round;stroke-width:2px;page-break-inside:avoid;stroke:#000;fill:#000}.md svg.diagram .opendot{fill:#FFF}.md svg.diagram text{stroke:none}@media print{@page{margin:1in 5mm;transform: scale(150%)}}@media print{.md .pagebreak{page-break-after:always;visibility:hidden}}.md a{font-family:Georgia,Palatino,'Times New Roman'}.md h1,.md .tocHeader,.md .nonumberh1{border-bottom:3px solid;font-size:20px;font-weight:bold;}.md h1,.md .nonumberh1{counter-reset: h2 h3 h4 h5 h6}.md h2,.md .nonumberh2{counter-reset: h3 h4 h5 h6;border-bottom:2px solid #999;color:#555;font-weight:bold;font-size:18px;}.md h3,.md h4,.md h5,.md h6,.md .nonumberh3,.md .nonumberh4,.md .nonumberh5,.md .nonumberh6{font-family:Helvetica,Arial,sans-serif;color:#555;font-size:16px;}.md h3{counter-reset:h4 h5 h6}.md h4{counter-reset:h5 h6}.md h5{counter-reset:h6}.md div.table{margin:16px 0 16px 0}.md table{border-collapse:collapse;line-height:140%;page-break-inside:avoid}.md table.table{margin:auto}.md table.calendar{width:100%;margin:auto;font-size:11px;font-family:Helvetica,Arial,sans-serif}.md table.calendar th{font-size:16px}.md .today{background:#ECF8FA}.md .calendar .parenthesized{color:#999;font-style:italic}.md div.tablecaption{text-align:center}.md table.table th{color:#FFF;background-color:#AAA;border:1px solid #888;padding:8px 15px 8px 15px}.md table.table td{padding:5px 15px 5px 15px;border:1px solid #888}.md table.table tr:nth-child(even){background:#EEE}.md pre.tilde{border-top: 1px solid #CCC;border-bottom: 1px solid #CCC;padding: 5px 0 5px 20px;margin:0 0 0 0;background:#FCFCFC;page-break-inside:avoid}.md a.target{width:0px;height:0px;visibility:hidden;font-size:0px;display:inline-block}.md a:link, .md a:visited{color:#38A;text-decoration:none}.md a:link:hover{text-decoration:underline}.md dt{font-weight:700}.md dl>dd{margin-top:-8px; margin-bottom:8px}.md dl>table{margin:35px 0 30px}.md code{white-space:pre-wrap;overflow-wrap:break-word;text-align:left;page-break-inside:avoid}.md .endnote{font-size:13px;line-height:15px;padding-left:10px;text-indent:-10px}.md .bib{padding-left:80px;text-indent:-80px;text-align:left}.markdeepFooter{font-size:9px;text-align:right;padding-top:80px;color:#999}.md .mediumTOC{float:right;font-size:12px;line-height:15px;border-left:1px solid #CCC;padding-left:15px;margin:15px 0px 15px 25px}.md .mediumTOC .level1{font-weight:600}.md .longTOC .level1{font-weight:600;display:block;padding-top:12px;margin:0 0 -20px}.md .shortTOC{text-align:center;font-weight:bold;margin-top:15px;font-size:14px}.md .admonition{position:relative;margin:1em 0;padding:.4rem 1rem;border-radius:.2rem;border-left:2.5rem solid rgba(68,138,255,.4);background-color:rgba(68,138,255,.15);}.md .admonition-title{font-weight:bold;border-bottom:solid 1px rgba(68,138,255,.4);padding-bottom:4px;margin-bottom:4px;margin-left: -1rem;padding-left:1rem;margin-right:-1rem;border-color:rgba(68,138,255,.4)}.md .admonition.tip{border-left:2.5rem solid rgba(50,255,90,.4);background-color:rgba(50,255,90,.15)}.md .admonition.tip::before{content:"\24d8";font-weight:bold;font-size:150%;position:relative;top:3px;color:rgba(26,128,46,.8);left:-2.95rem;display:block;width:0;height:0}.md .admonition.tip>.admonition-title{border-color:rgba(50,255,90,.4)}.md .admonition.warn,.md .admonition.warning{border-left:2.5rem solid rgba(255,145,0,.4);background-color:rgba(255,145,0,.15)}.md .admonition.warn::before,.md .admonition.warning::before{content:"\26A0";font-weight:bold;font-size:150%;position:relative;top:2px;color:rgba(128,73,0,.8);left:-2.95rem;display:block;width:0;height:0}.md .admonition.warn>.admonition-title,.md .admonition.warning>.admonition-title{border-color:rgba(255,145,0,.4)}.md .admonition.error{border-left: 2.5rem solid rgba(255,23,68,.4);background-color:rgba(255,23,68,.15)}.md .admonition.error>.admonition-title{border-color:rgba(255,23,68,.4)}.md .admonition.error::before{content: "\2612";font-family:"Arial";font-size:200%;position:relative;color:rgba(128,12,34,.8);top:-2px;left:-3rem;display:block;width:0;height:0}.md .admonition p:last-child{margin-bottom:0}.md li.checked,.md li.unchecked{list-style:none;overflow:visible;text-indent:-1.2em}.md li.checked:before,.md li.unchecked:before{content:"\2611";display:block;float:left;width:1em;font-size:120%}.md li.unchecked:before{content:"\2610"}</style><style>.md h1::before {
content:counter(h1) " ";
counter-increment: h1;margin-right:10px}.md h2::before {
content:counter(h1) "."counter(h2) " ";
counter-increment: h2;margin-right:10px}.md h3::before {
content:counter(h1) "."counter(h2) "."counter(h3) " ";
counter-increment: h3;margin-right:10px}.md h4::before {
content:counter(h1) "."counter(h2) "."counter(h3) "."counter(h4) " ";
counter-increment: h4;margin-right:10px}.md h5::before {
content:counter(h1) "."counter(h2) "."counter(h3) "."counter(h4) "."counter(h5) " ";
counter-increment: h5;margin-right:10px}.md h6::before {
content:counter(h1) "."counter(h2) "."counter(h3) "."counter(h4) "."counter(h5) "."counter(h6) " ";
counter-increment: h6;margin-right:10px}</style><style>.hljs{display:block;overflow-x:auto;padding:0.5em;background:#fff;color:#000;-webkit-text-size-adjust:none}.hljs-comment{color:#006a00}.hljs-keyword{color:#02E}.hljs-literal,.nginx .hljs-title{color:#aa0d91}.method,.hljs-list .hljs-title,.hljs-tag .hljs-title,.setting .hljs-value,.hljs-winutils,.tex .hljs-command,.http .hljs-title,.hljs-request,.hljs-status,.hljs-name{color:#008}.hljs-envvar,.tex .hljs-special{color:#660}.hljs-string{color:#c41a16}.hljs-tag .hljs-value,.hljs-cdata,.hljs-filter .hljs-argument,.hljs-attr_selector,.apache .hljs-cbracket,.hljs-date,.hljs-regexp{color:#080}.hljs-sub .hljs-identifier,.hljs-pi,.hljs-tag,.hljs-tag .hljs-keyword,.hljs-decorator,.ini .hljs-title,.hljs-shebang,.hljs-prompt,.hljs-hexcolor,.hljs-rule .hljs-value,.hljs-symbol,.hljs-symbol .hljs-string,.hljs-number,.css .hljs-function,.hljs-function .hljs-title,.coffeescript .hljs-attribute{color:#A0C}.hljs-function .hljs-title{font-weight:bold;color:#000}.hljs-class .hljs-title,.smalltalk .hljs-class,.hljs-type,.hljs-typename,.hljs-tag .hljs-attribute,.hljs-doctype,.hljs-class .hljs-id,.hljs-built_in,.setting,.hljs-params,.clojure .hljs-attribute{color:#5c2699}.hljs-variable{color:#3f6e74}.css .hljs-tag,.hljs-rule .hljs-property,.hljs-pseudo,.hljs-subst{color:#000}.css .hljs-class,.css .hljs-id{color:#9b703f}.hljs-value .hljs-important{color:#ff7700;font-weight:bold}.hljs-rule .hljs-keyword{color:#c5af75}.hljs-annotation,.apache .hljs-sqbracket,.nginx .hljs-built_in{color:#9b859d}.hljs-preprocessor,.hljs-preprocessor *,.hljs-pragma{color:#643820}.tex .hljs-formula{background-color:#eee;font-style:italic}.diff .hljs-header,.hljs-chunk{color:#808080;font-weight:bold}.diff .hljs-change{background-color:#bccff9}.hljs-addition{background-color:#baeeba}.hljs-deletion{background-color:#ffc8bd}.hljs-comment .hljs-doctag{font-weight:bold}.method .hljs-id{color:#000}</style><style>div.title { padding-top: 40px; } div.afterTitles { height: 15px; }</style><meta charset="utf-8">
<style>img { max-width: 100%; }</style>
<script type="text/x-mathjax-config">MathJax.Hub.Config({ TeX: { equationNumbers: {autoNumber: "AMS"} } });</script><span style="display:none">$$\newcommand{\n}{\hat{n}}\newcommand{\thetai}{\theta_\mathrm{i}}\newcommand{\thetao}{\theta_\mathrm{o}}\newcommand{\d}[1]{\mathrm{d}#1}\newcommand{\w}{\hat{\omega}}\newcommand{\wi}{\w_\mathrm{i}}\newcommand{\wo}{\w_\mathrm{o}}\newcommand{\wh}{\w_\mathrm{h}}\newcommand{\Li}{L_\mathrm{i}}\newcommand{\Lo}{L_\mathrm{o}}\newcommand{\Le}{L_\mathrm{e}}\newcommand{\Lr}{L_\mathrm{r}}\newcommand{\Lt}{L_\mathrm{t}}\newcommand{\O}{\mathrm{O}}\newcommand{\degrees}{{^{\large\circ}}}\newcommand{\T}{\mathsf{T}}\newcommand{\mathset}[1]{\mathbb{#1}}\newcommand{\Real}{\mathset{R}}\newcommand{\Integer}{\mathset{Z}}\newcommand{\Boolean}{\mathset{B}}\newcommand{\Complex}{\mathset{C}}\newcommand{\un}[1]{\,\mathrm{#1}}$$
</span>
<span class="md"><p><title>Physically Based Rendering in Filament</title></p><div class="title"> Physically Based Rendering in Filament </div>
<div class="afterTitles"></div>
<p></p><p>
</p><center><a href="images/filament_logo.png" target="_blank"><img class="markdeep" src="images/filament_logo.png"></a></center>
<p></p>
<div class="longTOC"><div class="tocHeader">Contents</div><p><a href="#about" class="level1"><span class="tocNumber">1  </span>About</a><br>
  <a href="#about/authors" class="level2"><span class="tocNumber">1.1  </span>Authors</a><br>
<a href="#overview" class="level1"><span class="tocNumber">2  </span>Overview</a><br>
  <a href="#overview/principles" class="level2"><span class="tocNumber">2.1  </span>Principles</a><br>
  <a href="#overview/physicallybasedrendering" class="level2"><span class="tocNumber">2.2  </span>Physically based rendering</a><br>
<a href="#notation" class="level1"><span class="tocNumber">3  </span>Notation</a><br>
<a href="#materialsystem" class="level1"><span class="tocNumber">4  </span>Material system</a><br>
  <a href="#materialsystem/standardmodel" class="level2"><span class="tocNumber">4.1  </span>Standard model</a><br>
  <a href="#materialsystem/dielectricsandconductors" class="level2"><span class="tocNumber">4.2  </span>Dielectrics and conductors</a><br>
  <a href="#materialsystem/energyconservation" class="level2"><span class="tocNumber">4.3  </span>Energy conservation</a><br>
  <a href="#materialsystem/specularbrdf" class="level2"><span class="tocNumber">4.4  </span>Specular BRDF</a><br>
    <a href="#materialsystem/specularbrdf/normaldistributionfunction(speculard)" class="level3"><span class="tocNumber">4.4.1  </span>Normal distribution function (specular D)</a><br>
    <a href="#materialsystem/specularbrdf/geometricshadowing(specularg)" class="level3"><span class="tocNumber">4.4.2  </span>Geometric shadowing (specular G)</a><br>
    <a href="#materialsystem/specularbrdf/fresnel(specularf)" class="level3"><span class="tocNumber">4.4.3  </span>Fresnel (specular F)</a><br>
  <a href="#materialsystem/diffusebrdf" class="level2"><span class="tocNumber">4.5  </span>Diffuse BRDF</a><br>
  <a href="#materialsystem/standardmodelsummary" class="level2"><span class="tocNumber">4.6  </span>Standard model summary</a><br>
  <a href="#materialsystem/improvingthebrdfs" class="level2"><span class="tocNumber">4.7  </span>Improving the BRDFs</a><br>
    <a href="#materialsystem/improvingthebrdfs/energygainindiffusereflectance" class="level3"><span class="tocNumber">4.7.1  </span>Energy gain in diffuse reflectance</a><br>
    <a href="#materialsystem/improvingthebrdfs/energylossinspecularreflectance" class="level3"><span class="tocNumber">4.7.2  </span>Energy loss in specular reflectance</a><br>
  <a href="#materialsystem/parameterization" class="level2"><span class="tocNumber">4.8  </span>Parameterization</a><br>
    <a href="#materialsystem/parameterization/standardparameters" class="level3"><span class="tocNumber">4.8.1  </span>Standard parameters</a><br>
    <a href="#materialsystem/parameterization/typesandranges" class="level3"><span class="tocNumber">4.8.2  </span>Types and ranges</a><br>
    <a href="#materialsystem/parameterization/remapping" class="level3"><span class="tocNumber">4.8.3  </span>Remapping</a><br>
    <a href="#materialsystem/parameterization/blendingandlayering" class="level3"><span class="tocNumber">4.8.4  </span>Blending and layering</a><br>
    <a href="#materialsystem/parameterization/craftingphysicallybasedmaterials" class="level3"><span class="tocNumber">4.8.5  </span>Crafting physically based materials</a><br>
  <a href="#materialsystem/clearcoatmodel" class="level2"><span class="tocNumber">4.9  </span>Clear coat model</a><br>
    <a href="#materialsystem/clearcoatmodel/clearcoatspecularbrdf" class="level3"><span class="tocNumber">4.9.1  </span>Clear coat specular BRDF</a><br>
    <a href="#materialsystem/clearcoatmodel/integrationinthesurfaceresponse" class="level3"><span class="tocNumber">4.9.2  </span>Integration in the surface response</a><br>
    <a href="#materialsystem/clearcoatmodel/clearcoatparameterization" class="level3"><span class="tocNumber">4.9.3  </span>Clear coat parameterization</a><br>
    <a href="#materialsystem/clearcoatmodel/baselayermodification" class="level3"><span class="tocNumber">4.9.4  </span>Base layer modification</a><br>
  <a href="#materialsystem/anisotropicmodel" class="level2"><span class="tocNumber">4.10  </span>Anisotropic model</a><br>
    <a href="#materialsystem/anisotropicmodel/anisotropicspecularbrdf" class="level3"><span class="tocNumber">4.10.1  </span>Anisotropic specular BRDF</a><br>
    <a href="#materialsystem/anisotropicmodel/anisotropicparameterization" class="level3"><span class="tocNumber">4.10.2  </span>Anisotropic parameterization</a><br>
  <a href="#materialsystem/subsurfacemodel" class="level2"><span class="tocNumber">4.11  </span>Subsurface model</a><br>
    <a href="#materialsystem/subsurfacemodel/subsurfacespecularbrdf" class="level3"><span class="tocNumber">4.11.1  </span>Subsurface specular BRDF</a><br>
    <a href="#materialsystem/subsurfacemodel/subsurfaceparameterization" class="level3"><span class="tocNumber">4.11.2  </span>Subsurface parameterization</a><br>
  <a href="#materialsystem/clothmodel" class="level2"><span class="tocNumber">4.12  </span>Cloth model</a><br>
    <a href="#materialsystem/clothmodel/clothspecularbrdf" class="level3"><span class="tocNumber">4.12.1  </span>Cloth specular BRDF</a><br>
    <a href="#materialsystem/clothmodel/clothdiffusebrdf" class="level3"><span class="tocNumber">4.12.2  </span>Cloth diffuse BRDF</a><br>
    <a href="#materialsystem/clothmodel/clothparameterization" class="level3"><span class="tocNumber">4.12.3  </span>Cloth parameterization</a><br>
<a href="#lighting" class="level1"><span class="tocNumber">5  </span>Lighting</a><br>
  <a href="#lighting/units" class="level2"><span class="tocNumber">5.1  </span>Units</a><br>
    <a href="#lighting/units/lightunitsvalidation" class="level3"><span class="tocNumber">5.1.1  </span>Light units validation</a><br>
  <a href="#lighting/directlighting" class="level2"><span class="tocNumber">5.2  </span>Direct lighting</a><br>
    <a href="#lighting/directlighting/directionallights" class="level3"><span class="tocNumber">5.2.1  </span>Directional lights</a><br>
    <a href="#lighting/directlighting/punctuallights" class="level3"><span class="tocNumber">5.2.2  </span>Punctual lights</a><br>
    <a href="#lighting/directlighting/photometriclights" class="level3"><span class="tocNumber">5.2.3  </span>Photometric lights</a><br>
    <a href="#lighting/directlighting/arealights" class="level3"><span class="tocNumber">5.2.4  </span>Area lights</a><br>
    <a href="#lighting/directlighting/lightsparameterization" class="level3"><span class="tocNumber">5.2.5  </span>Lights parameterization</a><br>
    <a href="#lighting/directlighting/pre-exposedlights" class="level3"><span class="tocNumber">5.2.6  </span>Pre-exposed lights</a><br>
  <a href="#lighting/imagebasedlights" class="level2"><span class="tocNumber">5.3  </span>Image based lights</a><br>
    <a href="#lighting/imagebasedlights/ibltypes" class="level3"><span class="tocNumber">5.3.1  </span>IBL Types</a><br>
    <a href="#lighting/imagebasedlights/iblunit" class="level3"><span class="tocNumber">5.3.2  </span>IBL Unit</a><br>
    <a href="#lighting/imagebasedlights/processinglightprobes" class="level3"><span class="tocNumber">5.3.3  </span>Processing light probes</a><br>
    <a href="#lighting/imagebasedlights/distantlightprobes" class="level3"><span class="tocNumber">5.3.4  </span>Distant light probes</a><br>
    <a href="#lighting/imagebasedlights/clearcoat" class="level3"><span class="tocNumber">5.3.5  </span>Clear coat</a><br>
    <a href="#lighting/imagebasedlights/anisotropy" class="level3"><span class="tocNumber">5.3.6  </span>Anisotropy</a><br>
    <a href="#lighting/imagebasedlights/subsurface" class="level3"><span class="tocNumber">5.3.7  </span>Subsurface</a><br>
    <a href="#lighting/imagebasedlights/cloth" class="level3"><span class="tocNumber">5.3.8  </span>Cloth</a><br>
  <a href="#lighting/staticlighting" class="level2"><span class="tocNumber">5.4  </span>Static lighting</a><br>
  <a href="#lighting/transparencyandtranslucencylighting" class="level2"><span class="tocNumber">5.5  </span>Transparency and translucency lighting</a><br>
    <a href="#lighting/transparencyandtranslucencylighting/transparency" class="level3"><span class="tocNumber">5.5.1  </span>Transparency</a><br>
    <a href="#lighting/transparencyandtranslucencylighting/translucency" class="level3"><span class="tocNumber">5.5.2  </span>Translucency</a><br>
  <a href="#lighting/occlusion" class="level2"><span class="tocNumber">5.6  </span>Occlusion</a><br>
    <a href="#lighting/occlusion/diffuseocclusion" class="level3"><span class="tocNumber">5.6.1  </span>Diffuse occlusion</a><br>
    <a href="#lighting/occlusion/specularocclusion" class="level3"><span class="tocNumber">5.6.2  </span>Specular occlusion</a><br>
  <a href="#lighting/normalmapping" class="level2"><span class="tocNumber">5.7  </span>Normal mapping</a><br>
    <a href="#lighting/normalmapping/reorientednormalmapping" class="level3"><span class="tocNumber">5.7.1  </span>Reoriented normal mapping</a><br>
    <a href="#lighting/normalmapping/udnblending" class="level3"><span class="tocNumber">5.7.2  </span>UDN blending</a><br>
<a href="#volumetriceffects" class="level1"><span class="tocNumber">6  </span>Volumetric effects</a><br>
  <a href="#volumetriceffects/exponentialheightfog" class="level2"><span class="tocNumber">6.1  </span>Exponential height fog</a><br>
<a href="#anti-aliasing" class="level1"><span class="tocNumber">7  </span>Anti-aliasing</a><br>
<a href="#imagingpipeline" class="level1"><span class="tocNumber">8  </span>Imaging pipeline</a><br>
  <a href="#imagingpipeline/physicallybasedcamera" class="level2"><span class="tocNumber">8.1  </span>Physically based camera</a><br>
    <a href="#imagingpipeline/physicallybasedcamera/exposuresettings" class="level3"><span class="tocNumber">8.1.1  </span>Exposure settings</a><br>
    <a href="#imagingpipeline/physicallybasedcamera/exposurevalue" class="level3"><span class="tocNumber">8.1.2  </span>Exposure value</a><br>
    <a href="#imagingpipeline/physicallybasedcamera/exposure" class="level3"><span class="tocNumber">8.1.3  </span>Exposure</a><br>
    <a href="#imagingpipeline/physicallybasedcamera/automaticexposure" class="level3"><span class="tocNumber">8.1.4  </span>Automatic exposure</a><br>
    <a href="#imagingpipeline/physicallybasedcamera/bloom" class="level3"><span class="tocNumber">8.1.5  </span>Bloom</a><br>
  <a href="#imagingpipeline/opticspost-processing" class="level2"><span class="tocNumber">8.2  </span>Optics post-processing</a><br>
    <a href="#imagingpipeline/opticspost-processing/colorfringing" class="level3"><span class="tocNumber">8.2.1  </span>Color fringing</a><br>
    <a href="#imagingpipeline/opticspost-processing/lensflares" class="level3"><span class="tocNumber">8.2.2  </span>Lens flares</a><br>
  <a href="#imagingpipeline/filmicpost-processing" class="level2"><span class="tocNumber">8.3  </span>Filmic post-processing</a><br>
    <a href="#imagingpipeline/filmicpost-processing/contrast" class="level3"><span class="tocNumber">8.3.1  </span>Contrast</a><br>
    <a href="#imagingpipeline/filmicpost-processing/curves" class="level3"><span class="tocNumber">8.3.2  </span>Curves</a><br>
    <a href="#imagingpipeline/filmicpost-processing/levels" class="level3"><span class="tocNumber">8.3.3  </span>Levels</a><br>
    <a href="#imagingpipeline/filmicpost-processing/colorgrading" class="level3"><span class="tocNumber">8.3.4  </span>Color grading</a><br>
  <a href="#imagingpipeline/lightpath" class="level2"><span class="tocNumber">8.4  </span>Light path</a><br>
    <a href="#imagingpipeline/lightpath/clusteredforwardrendering" class="level3"><span class="tocNumber">8.4.1  </span>Clustered Forward Rendering</a><br>
    <a href="#imagingpipeline/lightpath/implementationnotes" class="level3"><span class="tocNumber">8.4.2  </span>Implementation notes</a><br>
  <a href="#imagingpipeline/validation" class="level2"><span class="tocNumber">8.5  </span>Validation</a><br>
    <a href="#imagingpipeline/validation/scenereferredvisualization" class="level3"><span class="tocNumber">8.5.1  </span>Scene referred visualization</a><br>
    <a href="#imagingpipeline/validation/referencerenderings" class="level3"><span class="tocNumber">8.5.2  </span>Reference renderings</a><br>
  <a href="#imagingpipeline/coordinatessystems" class="level2"><span class="tocNumber">8.6  </span>Coordinates systems</a><br>
    <a href="#imagingpipeline/coordinatessystems/worldcoordinatessystem" class="level3"><span class="tocNumber">8.6.1  </span>World coordinates system</a><br>
    <a href="#imagingpipeline/coordinatessystems/cameracoordinatessystem" class="level3"><span class="tocNumber">8.6.2  </span>Camera coordinates system</a><br>
    <a href="#imagingpipeline/coordinatessystems/cubemapscoordinatessystem" class="level3"><span class="tocNumber">8.6.3  </span>Cubemaps coordinates system</a><br>
<a href="#annex" class="level1"><span class="tocNumber">9  </span>Annex</a><br>
  <a href="#annex/specularcolor" class="level2"><span class="tocNumber">9.1  </span>Specular color</a><br>
  <a href="#annex/importancesamplingfortheibl" class="level2"><span class="tocNumber">9.2  </span>Importance sampling for the IBL</a><br>
    <a href="#annex/importancesamplingfortheibl/choosingimportantdirections" class="level3"><span class="tocNumber">9.2.1  </span>Choosing important directions</a><br>
    <a href="#annex/importancesamplingfortheibl/pre-filteredimportancesampling" class="level3"><span class="tocNumber">9.2.2  </span>Pre-filtered importance sampling</a><br>
  <a href="#annex/choosingimportantdirectionsforsamplingthebrdf" class="level2"><span class="tocNumber">9.3  </span>Choosing important directions for sampling the BRDF</a><br>
  <a href="#annex/hammersleysequence" class="level2"><span class="tocNumber">9.4  </span>Hammersley sequence</a><br>
  <a href="#annex/precomputinglforimage-basedlighting" class="level2"><span class="tocNumber">9.5  </span>Precomputing L for image-based lighting</a><br>
  <a href="#annex/sphericalharmonics" class="level2"><span class="tocNumber">9.6  </span>Spherical Harmonics</a><br>
    <a href="#annex/sphericalharmonics/basisfunctions" class="level3"><span class="tocNumber">9.6.1  </span>Basis functions</a><br>
    <a href="#annex/sphericalharmonics/decompositionandreconstruction" class="level3"><span class="tocNumber">9.6.2  </span>Decomposition and reconstruction</a><br>
    <a href="#annex/sphericalharmonics/decompositionof%5C(%5Cleft%5C)" class="level3"><span class="tocNumber">9.6.3  </span>Decomposition of \(\left< cos \theta \right>\)</a><br>
    <a href="#annex/sphericalharmonics/convolution" class="level3"><span class="tocNumber">9.6.4  </span>Convolution</a><br>
  <a href="#annex/samplevalidationsceneformitsuba" class="level2"><span class="tocNumber">9.7  </span>Sample validation scene for Mitsuba</a><br>
  <a href="#annex/lightassignmentwithfroxels" class="level2"><span class="tocNumber">9.8  </span>Light assignment with froxels</a><br>
<a href="#revisions" class="level1"><span class="tocNumber">10  </span>Revisions</a><br>
<a href="#bibliography" class="level1"><span class="tocNumber">11  </span>Bibliography</a><br>
</p></div><a class="target" name="about"> </a><a class="target" name="about"> </a><a class="target" name="toc1"> </a><h1>About</h1>
<p>
This document is part of the <a href="https://github.com/google/filament">Filament project</a>. To report errors in this document please use the <a href="https://github.com/google/filament/issues">project's issue tracker</a>.
</p>
<a class="target" name="authors"> </a><a class="target" name="about/authors"> </a><a class="target" name="toc1.1"> </a><h2>Authors</h2>
<p>
</p><ul>
<li class="minus"><a href="https://github.com/romainguy">Romain Guy</a>, <a href="https://twitter.com/romainguy">@romainguy</a>
</li>
<li class="minus"><a href="https://github.com/pixelflinger">Mathias Agopian</a>, <a href="https://twitter.com/darthmoosious">@darthmoosious</a></li></ul>
<p></p>
<a class="target" name="overview"> </a><a class="target" name="overview"> </a><a class="target" name="toc2"> </a><h1>Overview</h1>
<p>
Filament is a physically based rendering (PBR) engine for Android. The goal of Filament is to offer a set of tools and APIs for Android developers that will enable them to create high quality 2D and 3D rendering with ease.
</p><p>
The goal of this document is to explain the equations and theory behind the material and lighting models used in Filament. This document is intended as a reference for contributors to Filament or developers interested in the inner workings of the engine. We will provide code snippets as needed to make the relationship between theory and practice as clear as possible.
</p><p>
This document is not intended as a design document. It focuses solely on algorithms and its content could be used to implement PBR in any engine. However, this document explains why we chose specific algorithms/models over others.
</p><p>
Unless noted otherwise, all the 3D renderings present in this document have been generated in-engine (prototype or production). Many of these 3D renderings were captured during the early stages of development of Filament and do not reflect the final quality.
</p>
<a class="target" name="principles"> </a><a class="target" name="overview/principles"> </a><a class="target" name="toc2.1"> </a><h2>Principles</h2>
<p>
Real-time rendering is an active area of research and there is a large number of equations, algorithms and implementation to choose from for every single feature that needs to be implemented (the book <em class="asterisk">Rendering real-time shadows</em>, for instance, is a 400 pages summary of dozens of shadows rendering techniques). As such, we must first define our goals (or principles, to follow Brent Burley's seminal paper Physically-based shading at Disney [<a href="#citation-burley12">Burley12</a>]) before we can make informed decisions.
</p><p>
</p><dl><dt>Real-time mobile performance</dt><dd><p> Our primary goal is to design and implement a rendering system able to perform efficiently on mobile platforms. The primary target will be OpenGL ES 3.x class GPUs.
</p></dd><dt>Quality</dt><dd><p> Our rendering system will emphasize overall picture quality. We will however accept quality compromises to support low and medium performance GPUs.
</p></dd><dt>Ease of use</dt><dd><p> Artists need to be able to iterate often and quickly on their assets and our rendering system must allow them to do so intuitively. We must therefore provide parameters that are easy to understand (for instance, no specular power, no index of refraction…).
</p><p>
We also understand that not all developers have the luxury to work with artists. The physically based approach of our system will allow developers to craft visually plausible materials without the need to understand the theory behind our implementation.
</p><p>
For both artists and developers, our system will rely on as few parameters as possible to reduce trial and error and allow users to quickly master the material model.
</p><p>
In addition, any combination of parameter values should lead to physically plausible results. Physically implausible materials must be hard to create.
</p></dd><dt>Familiarity</dt><dd><p> Our system should use physical units everywhere possible: distances in meters or centimeters, color temperatures in Kelvin, light units in lumens or candelas, etc.
</p></dd><dt>Flexibility</dt><dd><p> A physically based approach must not preclude non-realistic rendering. User interfaces for instance will need unlit materials.
</p></dd><dt>Deployment size</dt><dd><p> While not directly related to the content of this document, it bears emphasizing our desire to keep the rendering library as small as possible so any application can bundle it without increasing the binary to undesirable sizes.
</p></dd></dl><p></p>
<a class="target" name="physicallybasedrendering"> </a><a class="target" name="overview/physicallybasedrendering"> </a><a class="target" name="toc2.2"> </a><h2>Physically based rendering</h2>
<p>
We chose to adopt PBR for its benefits from an artistic and production efficient standpoints, and because it is compatible with our goals.
</p><p>
Physically based rendering is a rendering method that provides a more accurate representation of materials and how they interact with light when compared to traditional real-time models. The separation of materials and lighting at the core of the PBR method makes it easier to create realistic assets that look accurate in all lighting conditions.
</p>
<a class="target" name="notation"> </a><a class="target" name="notation"> </a><a class="target" name="toc3"> </a><h1>Notation</h1>
<p>
$$
\newcommand{NoL}{n \cdot l}
\newcommand{NoV}{n \cdot v}
\newcommand{NoH}{n \cdot h}
\newcommand{VoH}{v \cdot h}
\newcommand{LoH}{l \cdot h}
\newcommand{fNormal}{f_{0}}
\newcommand{fDiffuse}{f_d}
\newcommand{fSpecular}{f_r}
\newcommand{fX}{f_x}
\newcommand{aa}{\alpha^2}
\newcommand{fGrazing}{f_{90}}
\newcommand{schlick}{F_{Schlick}}
\newcommand{nior}{n_{ior}}
\newcommand{Ed}{E_d}
\newcommand{Lt}{L_{\bot}}
\newcommand{Lout}{L_{out}}
\newcommand{cosTheta}{\left< \cos \theta \right> }
$$
</p><p>
The equations found throughout this document use the symbols described in <a href="#table_symbols">table 1</a>.
</p><p>
</p><div class="table">
<table class="table"><tbody><tr><th style="text-align:center"> Symbol </th><th style="text-align:left"> Definition </th></tr>
<tr><td style="text-align:center"> \(v\) </td><td style="text-align:left"> View unit vector </td></tr>
<tr><td style="text-align:center"> \(l\) </td><td style="text-align:left"> Incident light unit vector </td></tr>
<tr><td style="text-align:center"> \(n\) </td><td style="text-align:left"> Surface normal unit vector </td></tr>
<tr><td style="text-align:center"> \(h\) </td><td style="text-align:left"> Half unit vector between \(l\) and \(v\) </td></tr>
<tr><td style="text-align:center"> \(f\) </td><td style="text-align:left"> BRDF </td></tr>
<tr><td style="text-align:center"> \(\fDiffuse\) </td><td style="text-align:left"> Diffuse component of a BRDF </td></tr>
<tr><td style="text-align:center"> \(\fSpecular\) </td><td style="text-align:left"> Specular component of a BRDF </td></tr>
<tr><td style="text-align:center"> \(\alpha\) </td><td style="text-align:left"> Roughness, remapped from using input <code>perceptualRoughness</code> </td></tr>
<tr><td style="text-align:center"> \(\sigma\) </td><td style="text-align:left"> Diffuse reflectance </td></tr>
<tr><td style="text-align:center"> \(\Omega\) </td><td style="text-align:left"> Spherical domain </td></tr>
<tr><td style="text-align:center"> \(\fNormal\) </td><td style="text-align:left"> Reflectance at normal incidence </td></tr>
<tr><td style="text-align:center"> \(\fGrazing\) </td><td style="text-align:left"> Reflectance at grazing angle </td></tr>
<tr><td style="text-align:center"> \(\chi^+(a)\) </td><td style="text-align:left"> Heaviside function (1 if \(a > 0\) and 0 otherwise) </td></tr>
<tr><td style="text-align:center"> \(n_{ior}\) </td><td style="text-align:left"> Index of refraction (IOR) of an interface </td></tr>
<tr><td style="text-align:center"> \(\left< \NoL \right>\) </td><td style="text-align:left"> Dot product clamped to [0..1] </td></tr>
<tr><td style="text-align:center"> \(\left< a \right>\) </td><td style="text-align:left"> Saturated value (clamped to [0..1]) </td></tr>
</tbody></table><div class="tablecaption"><a class="target" name="table_symbols"> </a><b style="font-style:normal;">Table 1:</b> Symbols definitions</div></div>
<p></p>
<a class="target" name="materialsystem"> </a><a class="target" name="materialsystem"> </a><a class="target" name="toc4"> </a><h1>Material system</h1>
<p>
The sections below describe multiple material models to simplify the description of various surface features such as anisotropy or the clear coat layer. In practice however some of these models are condensed into a single one. For instance, the standard model, the clear coat model and the anisotropic model can be combined to form a single, more flexible and powerful model. Please refer to the <a href="./Materials.md.html">Materials documentation</a> to get a description of the material models as implemented in Filament.
</p>
<a class="target" name="standardmodel"> </a><a class="target" name="materialsystem/standardmodel"> </a><a class="target" name="toc4.1"> </a><h2>Standard model</h2>
<p>
The goal of our model is to represent standard material appearances. A material model is described mathematically by a BSDF (Bidirectional Scattering Distribution Function), which is itself composed of two other functions: the BRDF (Bidirectional Reflectance Distribution Function) and the BTDF (Bidirectional Transmittance Function).
</p><p>
Since we aim to model commonly encountered surfaces, our standard material model will focus on the BRDF and ignore the BTDF, or approximate it greatly. Our standard model will therefore only be able to correctly mimic reflective, isotropic, dielectric or conductive surfaces with short mean free paths.
</p><p>
The BRDF describes the surface response of a standard material as a function made of two terms:
</p><p>
</p><ul>
<li class="minus">A diffuse component, or \(f_d\)
</li>
<li class="minus">A specular component, or \(f_r\)</li></ul>
<p></p><p>
The relationship between a surface, the surface normal, incident light and these terms is shown in <a href="#figure_frfd">figure 1</a> (we ignore subsurface scattering for now):
</p><p>
</p><center><div class="image" style><a href="images/diagram_fr_fd.png" target="_blank"><img class="markdeep" src="images/diagram_fr_fd.png"></a><div class="imagecaption"><a class="target" name="figure_frfd"> </a><b style="font-style:normal;">Figure 1:</b> Interaction of the light with a surface using BRDF model with a diffuse term \( f_d \) and a specular term \( f_r \)</div></div></center>
<p></p><p>
The complete surface response can be expressed as such:
</p><p>
$$\begin{equation}\label{brdf}
f(v,l)=f_d(v,l)+f_r(v,l)
\end{equation}$$
</p><p>
This equation characterizes the surface response for incident light from a single direction. The full rendering equation would require to integrate \(l\) over the entire hemisphere.
</p><p>
Commonly encountered surfaces are usually not made of a flat interface so we need a model that can characterize the interaction of light with an irregular interface.
</p><p>
A microfacet BRDF is a good physically plausible BRDF for that purpose. Such BRDF states that surfaces are not smooth at a micro level, but made of a large number of randomly aligned planar surface fragments, called microfacets. <a href="#figure_microfacetvsflat">Figure 2</a> shows the difference between a flat interface and an irregular interface at a micro level:
</p><p>
</p><center><div class="image" style><a href="images/diagram_microfacet.png" target="_blank"><img class="markdeep" src="images/diagram_microfacet.png"></a><div class="imagecaption"><a class="target" name="figure_microfacetvsflat"> </a><b style="font-style:normal;">Figure 2:</b> Irregular interface as modeled by a microfacet model (left) and flat interface (right)</div></div></center>
<p></p><p>
Only the microfacets whose normal is oriented halfway between the light direction and the view direction will reflect visible light, as shown in <a href="#figure_microfacets">figure 3</a>.
</p><p>
</p><center><div class="image" style><a href="images/diagram_macrosurface.png" target="_blank"><img class="markdeep" src="images/diagram_macrosurface.png"></a><div class="imagecaption"><a class="target" name="figure_microfacets"> </a><b style="font-style:normal;">Figure 3:</b> Microfacets</div></div></center>
<p></p><p>
However, not all microfacets with a properly oriented normal will contribute reflected light as the BRDF takes into account masking and shadowing. This is illustrated in <a href="#figure_microfacetshadowing">figure 4</a>.
</p><p>
</p><center><div class="image" style><a href="images/diagram_shadowing_masking.png" target="_blank"><img class="markdeep" src="images/diagram_shadowing_masking.png"></a><div class="imagecaption"><a class="target" name="figure_microfacetshadowing"> </a><b style="font-style:normal;">Figure 4:</b> Masking and shadowing of microfacets</div></div></center>
<p></p><p>
A microfacet BRDF is heavily influenced by a <em class="underscore">roughness</em> parameter which describes how smooth (low roughness) or how rough (high roughness) a surface is at a micro level. The smoother the surface, the more facets are aligned and the more pronounced the reflected light is. The rougher the surface, the fewer facets are oriented towards the camera and incoming light is scattered away from the camera after reflection, giving a blurry aspect to the specular highlights.
</p><p>
<a href="#figure_roughness">Figure 5</a> shows surfaces of different roughness and how light interacts with them.
</p><p>
</p><center><div class="image" style><a href="images/diagram_roughness.png" target="_blank"><img class="markdeep" src="images/diagram_roughness.png"></a><div class="imagecaption"><a class="target" name="figure_roughness"> </a><b style="font-style:normal;">Figure 5:</b> Varying roughness (from left to right, rough to smooth) and the resulting BRDF specular component lobe</div></div></center>
<p></p><p>
</p><div class="admonition note"><div class="admonition-title"> About roughness</div>
<p></p><p>
The roughness parameter as set by the user is called <code>perceptualRoughness</code> in the shader snippets throughout this document. The variable called <code>roughness</code> is the <code>perceptualRoughness</code> with a remapping explained in section <a href="#toc4.8">4.8</a>.</p></div>
<p></p><p>
A microfacet model is described by the following equation (where x stands for the specular or diffuse component):
</p><p>
$$\begin{equation}
\fX(v,l) = \frac{1}{| \NoV | | \NoL |}
\int_\Omega D(m,\alpha) G(v,l,m) f_m(v,l,m) (v \cdot m) (l \cdot m) dm
\end{equation}$$
</p><p>
The term \(D\) models the distribution of the microfacets (this term is also referred to as the NDF or Normal Distribution Function). This term plays a primordial role in the appearance of surfaces as shown in <a href="#figure_roughness">figure 5</a>.
</p><p>
The term \(G\) models the visibility (or occlusion or shadow-masking) of the microfacets.
</p><p>
Since this equation is valid for both the specular and diffuse components, the difference lies in the microfacet BRDF \(f_m\).
</p><p>
It is important to note that this equation is used to integrate over the hemisphere at a <em class="underscore">micro level</em>:
</p><p>
</p><center><div class="image" style><a href="images/diagram_micro_vs_macro.png" target="_blank"><img class="markdeep" src="images/diagram_micro_vs_macro.png"></a><div class="imagecaption"><a class="target" name="figure_microlevel"> </a><b style="font-style:normal;">Figure 6:</b> Modeling the surface response at a single point requires an integration at the micro level</div></div></center>
<p></p><p>
The diagram above shows that at a macro level, the surfaces is considered flat. This helps simplify our equations by assuming that a shaded fragment lit from a single direction corresponds to a single point at the surface.
</p><p>
At a micro level however, the surface is not flat and we cannot assume a single ray of light anymore (we can however assume that the incident rays are parallel). Since the micro facets will scatter the light in different directions given a bundle of parallel incident rays, we must integrate the surface response over a hemisphere, noted m in the above diagram.
</p><p>
It is obviously not practical to compute the full integration over the microfacets hemisphere for each shaded fragment. We will therefore rely on approximations of the integration for both the specular and diffuse components.
</p>
<a class="target" name="dielectricsandconductors"> </a><a class="target" name="materialsystem/dielectricsandconductors"> </a><a class="target" name="toc4.2"> </a><h2>Dielectrics and conductors</h2>
<p>
To better understand some of the equations and behaviors shown below, we must first clearly understand the difference between metallic (conductor) and non-metallic (dielectric) surfaces.
</p><p>
We saw earlier that when incident light hits a surface governed by a BRDF, the light is reflected as two separate components: the diffuse reflectance and the specular reflectance. The modelization of this behavior is straightforward as shown in <a href="#figure_bsdfbrdf">figure 7</a>.
</p><p>
</p><center><div class="image" style><a href="images/diagram_fr_fd.png" target="_blank"><img class="markdeep" src="images/diagram_fr_fd.png"></a><div class="imagecaption"><a class="target" name="figure_bsdfbrdf"> </a><b style="font-style:normal;">Figure 7:</b> Modelization of the BRDF part of a BSDF</div></div></center>
<p></p><p>
This modelization is a simplification of how the light actually interacts with the surface. In reality, part of the incident light will penetrate the surface, scatter inside, and exit the surface again as diffuse reflectance. This phenomenon is illustrated in <a href="#figure_diffusescattering">figure 8</a>.
</p><p>
</p><center><div class="image" style><a href="images/diagram_scattering.png" target="_blank"><img class="markdeep" src="images/diagram_scattering.png"></a><div class="imagecaption"><a class="target" name="figure_diffusescattering"> </a><b style="font-style:normal;">Figure 8:</b> Scattering of diffuse light</div></div></center>
<p></p><p>
Here lies the difference between conductors and dielectrics. There is no subsurface scattering occurring with purely metallic materials, which means there is no diffuse component (and we will see later that this has an influence on the perceived color of the specular component). Scattering happens in dielectrics, which means they have both specular and diffuse components.
</p><p>
To properly modelize the BRDF we must therefore distinguish between dielectrics and conductors (scattering not shown for clarity), as shown in <a href="#figure_dielectricconductor">figure 9</a>.
</p><p>
</p><center><div class="image" style><a href="images/diagram_brdf_dielectric_conductor.png" target="_blank"><img class="markdeep" src="images/diagram_brdf_dielectric_conductor.png"></a><div class="imagecaption"><a class="target" name="figure_dielectricconductor"> </a><b style="font-style:normal;">Figure 9:</b> BRDF modelization for dielectric and conductor surfaces</div></div></center>
<p></p>
<a class="target" name="energyconservation"> </a><a class="target" name="materialsystem/energyconservation"> </a><a class="target" name="toc4.3"> </a><h2>Energy conservation</h2>
<p>
Energy conservation is one of the key components of a good BRDF for physically based rendering. An energy conservative BRDF states that the total amount of specular and diffuse reflectance energy is less than the total amount of incident energy. Without an energy conservative BRDF, artists must manually ensure that the light reflected off a surface is never more intense than the incident light.
</p>
<a class="target" name="specularbrdf"> </a><a class="target" name="materialsystem/specularbrdf"> </a><a class="target" name="toc4.4"> </a><h2>Specular BRDF</h2>
<p>
For the specular term, \(f_m\) is a mirror BRDF that can be modeled with the Fresnel law, noted \(F\) in the Cook-Torrance approximation of the microfacet model integration:
</p><p>
$$\begin{equation}
f_r(v,l) = \frac{D(h, \alpha) G(v, l, \alpha) F(v, h, f0)}{4(\NoV)(\NoL)}
\end{equation}$$
</p><p>
Given our real-time constraints, we must use an approximation for the three terms \(D\), \(G\) and \(F\). [<a href="#citation-karis13">Karis13</a>] has compiled a great list of formulations for these three terms that can be used with the Cook-Torrance specular BRDF. The sections that follow describe the equations we picked for these terms.
</p>
<a class="target" name="normaldistributionfunction(speculard)"> </a><a class="target" name="materialsystem/specularbrdf/normaldistributionfunction(speculard)"> </a><a class="target" name="toc4.4.1"> </a><h3>Normal distribution function (specular D)</h3>
<p>
[<a href="#citation-burley12">Burley12</a>] observed that long-tailed normal distribution functions (NDF) are a good fit for real-world surfaces. The GGX distribution described in [<a href="#citation-walter07">Walter07</a>] is a distribution with long-tailed falloff and short peak in the highlights, with a simple formulation suitable for real-time implementations. It is also a popular model, equivalent to the Trowbridge-Reitz distribution, in modern physically based renderers.
</p><p>
$$\begin{equation}
D_{GGX}(h,\alpha) = \frac{\aa}{\pi ( (\NoH)^2 (\aa - 1) + 1)^2}
\end{equation}$$
</p><p>
The GLSL implementation of the NDF, shown in <a href="#listing_speculard">listing 1</a>, is simple and efficient.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">D_GGX</span><span class="hljs-params">(<span class="hljs-keyword">float</span> NoH, <span class="hljs-keyword">float</span> roughness)</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">float</span> a = NoH * roughness;</span>
<span class="line"> <span class="hljs-keyword">float</span> k = roughness / (<span class="hljs-number">1.0</span> - NoH * NoH + a * a);</span>
<span class="line"> <span class="hljs-keyword">return</span> k * k * (<span class="hljs-number">1.0</span> / PI);</span>
<span class="line">}</span></code></pre><div class="listingcaption tilde"><a class="target" name="listing_speculard"> </a><b style="font-style:normal;">Listing 1:</b> Implementation of the specular D term in GLSL</div>
<p>
We can improve this implementation by using half precision floats. This optimization requires changes to the original equation as there are two problems when computing \(1 - (\NoH)^2\) in half-floats. First, this computation suffers from floating point cancellation when \((\NoH)^2\) is close to 1 (highlights). Secondly \(\NoH\) does not have enough precision around 1.
</p><p>
The solution involves Lagrange's identity:
</p><p>
$$\begin{equation}
| a \times b |^2 = |a|^2 |b|^2 - (a \cdot b)^2
\end{equation}$$
</p><p>
Since both \(n\) and \(h\) are unit vectors, \(|n \times h|^2 = 1 - (\NoH)^2\). This allows us to compute \(1 - (\NoH)^2\) directly with half precision floats by using a simple cross product. <a href="#listing_speculardfp16">Listing 2</a> shows the final optimized implementation.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-meta">#define MEDIUMP_FLT_MAX 65504.0</span></span>
<span class="line"><span class="hljs-meta">#define saturateMediump(x) min(x, MEDIUMP_FLT_MAX)</span></span>
<span class="line"></span>
<span class="line"><span class="hljs-type">float</span> D_GGX(<span class="hljs-type">float</span> roughness, <span class="hljs-type">float</span> NoH, <span class="hljs-keyword">const</span> <span class="hljs-type">vec3</span> n, <span class="hljs-keyword">const</span> <span class="hljs-type">vec3</span> h) {</span>
<span class="line"> <span class="hljs-type">vec3</span> NxH = <span class="hljs-built_in">cross</span>(n, h);</span>
<span class="line"> <span class="hljs-type">float</span> a = NoH * roughness;</span>
<span class="line"> <span class="hljs-type">float</span> k = roughness / (<span class="hljs-built_in">dot</span>(NxH, NxH) + a * a);</span>
<span class="line"> <span class="hljs-type">float</span> d = k * k * (<span class="hljs-number">1.0</span> / PI);</span>
<span class="line"> <span class="hljs-keyword">return</span> saturateMediump(d);</span>
<span class="line">}</span></code></pre><div class="listingcaption tilde"><a class="target" name="listing_speculardfp16"> </a><b style="font-style:normal;">Listing 2:</b> Implementation of the specular D term in GLSL optimized for fp16</div>
<a class="target" name="geometricshadowing(specularg)"> </a><a class="target" name="materialsystem/specularbrdf/geometricshadowing(specularg)"> </a><a class="target" name="toc4.4.2"> </a><h3>Geometric shadowing (specular G)</h3>
<p>
Eric Heitz showed in [<a href="#citation-heitz14">Heitz14</a>] that the Smith geometric shadowing function is the correct and exact \(G\) term to use. The Smith formulation is the following:
</p><p>
$$\begin{equation}
G(v,l,\alpha) = G_1(l,\alpha) G_1(v,\alpha)
\end{equation}$$
</p><p>
\(G_1\) can in turn follow several models, and is commonly set to the GGX formulation:
</p><p>
$$\begin{equation}
G_1(v,\alpha) = G_{GGX}(v,\alpha) = \frac{2 (\NoV)}{\NoV + \sqrt{\aa + (1 - \aa) (\NoV)^2}}
\end{equation}$$
</p><p>
The full Smith-GGX formulation thus becomes:
</p><p>
$$\begin{equation}
G(v,l,\alpha) = \frac{2 (\NoL)}{\NoL + \sqrt{\aa + (1 - \aa) (\NoL)^2}} \frac{2 (\NoV)}{\NoV + \sqrt{\aa + (1 - \aa) (\NoV)^2}}
\end{equation}$$
</p><p>
We can observe that the dividends \(2 (\NoL)\) and \(2 (n \cdot v)\) allow us to simplify the original function \(f_r\) by introducing a visibility function \(V\):
</p><p>
$$\begin{equation}
f_r(v,l) = D(h, \alpha) V(v, l, \alpha) F(v, h, f_0)
\end{equation}$$
</p><p>
Where:
</p><p>
$$\begin{equation}
V(v,l,\alpha) = \frac{G(v, l, \alpha)}{4 (\NoV) (\NoL)} = V_1(l,\alpha) V_1(v,\alpha)
\end{equation}$$
</p><p>
And:
</p><p>
$$\begin{equation}
V_1(v,\alpha) = \frac{1}{\NoV + \sqrt{\aa + (1 - \aa) (\NoV)^2}}
\end{equation}$$
</p><p>
Heitz notes however that taking the height of the microfacets into account to correlate masking and shadowing leads to more accurate results. He defines the height-correlated Smith function thusly:
</p><p>
$$\begin{equation}
G(v,l,h,\alpha) = \frac{\chi^+(\VoH) \chi^+(\LoH)}{1 + \Lambda(v) + \Lambda(l)}
\end{equation}$$
</p><p>
$$\begin{equation}
\Lambda(m) = \frac{-1 + \sqrt{1 + \aa tan^2(\theta_m)}}{2} = \frac{-1 + \sqrt{1 + \aa \frac{(1 - cos^2(\theta_m))}{cos^2(\theta_m)}}}{2}
\end{equation}$$
</p><p>
Replacing \(\theta_m\) by \(\NoV\), we obtain:
</p><p>
$$\begin{equation}
\Lambda(v) = \frac{1}{2} \left( \frac{\sqrt{\aa + (1 - \aa)(\NoV)^2}}{\NoV} - 1 \right)
\end{equation}$$
</p><p>
From which we can derive the visibility function:
</p><p>
$$\begin{equation}
V(v,l,\alpha) = \frac{0.5}{\NoL \sqrt{(\NoV)^2 (1 - \aa) + \aa} + \NoV \sqrt{(\NoL)^2 (1 - \aa) + \aa}}
\end{equation}$$
</p><p>
The GLSL implementation of the visibility term, shown in <a href="#listing_specularv">listing 3</a>, is a bit more expensive than we would like since it requires two <code>sqrt</code> operations.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">V_SmithGGXCorrelated</span><span class="hljs-params">(<span class="hljs-keyword">float</span> NoV, <span class="hljs-keyword">float</span> NoL, <span class="hljs-keyword">float</span> roughness)</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">float</span> a2 = roughness * roughness;</span>
<span class="line"> <span class="hljs-keyword">float</span> GGXV = NoL * <span class="hljs-built_in">sqrt</span>(NoV * NoV * (<span class="hljs-number">1.0</span> - a2) + a2);</span>
<span class="line"> <span class="hljs-keyword">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>(NoL * NoL * (<span class="hljs-number">1.0</span> - a2) + a2);</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span>
<span class="line">}</span></code></pre><div class="listingcaption tilde"><a class="target" name="listing_specularv"> </a><b style="font-style:normal;">Listing 3:</b> Implementation of the specular V term in GLSL</div>
<p>
We can optimize this visibility function by using an approximation after noticing that all the terms under the square roots are squares and that all the terms are in the \([0..1]\) range:
</p><p>
$$\begin{equation}
V(v,l,\alpha) = \frac{0.5}{\NoL (\NoV (1 - \alpha) + \alpha) + \NoV (\NoL (1 - \alpha) + \alpha)}
\end{equation}$$
</p><p>
This approximation is mathematically wrong but saves two square root operations and is good enough for real-time mobile applications, as shown in <a href="#listing_approximatedspecularv">listing 4</a>.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">V_SmithGGXCorrelatedFast</span><span class="hljs-params">(<span class="hljs-keyword">float</span> NoV, <span class="hljs-keyword">float</span> NoL, <span class="hljs-keyword">float</span> roughness)</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">float</span> a = roughness;</span>
<span class="line"> <span class="hljs-keyword">float</span> GGXV = NoL * (NoV * (<span class="hljs-number">1.0</span> - a) + a);</span>
<span class="line"> <span class="hljs-keyword">float</span> GGXL = NoV * (NoL * (<span class="hljs-number">1.0</span> - a) + a);</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span>
<span class="line">}</span></code></pre><div class="listingcaption tilde"><a class="target" name="listing_approximatedspecularv"> </a><b style="font-style:normal;">Listing 4:</b> Implementation of the approximated specular V term in GLSL</div>
<p>
[<a href="#citation-hammon17">Hammon17</a>] proposes the same approximation based on the same observation that the square root can be removed. It does so by rewriting the expressions as <em class="underscore">lerps</em>:
</p><p>
$$\begin{equation}
V(v,l,\alpha) = \frac{0.5}{lerp(2 (\NoL) (\NoV), \NoL + \NoV, \alpha)}
\end{equation}$$
</p>
<a class="target" name="fresnel(specularf)"> </a><a class="target" name="materialsystem/specularbrdf/fresnel(specularf)"> </a><a class="target" name="toc4.4.3"> </a><h3>Fresnel (specular F)</h3>
<p>
The Fresnel effect plays an important role in the appearance of physically based materials. This effect models the fact that the amount of light the viewer sees reflected from a surface depends on the viewing angle. Large bodies of water are a perfect way to experience this phenomenon, as shown in <a href="#figure_fresnellake">figure 10</a>. When looking at the water straight down (at normal incidence) you can see through the water. However, when looking further out in the distance (at grazing angle, where perceived light rays are getting parallel to the surface), you will see the specular reflections on the water become more intense.
</p><p>
The amount of light reflected depends not only on the viewing angle, but also on the index of refraction (IOR) of the material. At normal incidence (perpendicular to the surface, or 0° angle), the amount of light reflected back is noted \(\fNormal\) and can be derived from the IOR as we will see in section <a href="#toc4.8.3.2">4.8.3.2</a>. The amount of light reflected back at grazing angle is noted \(\fGrazing\) and approaches 100% for smooth materials.
</p><p>
</p><center><div class="image" style><a href="images/photo_fresnel_lake.jpg" target="_blank"><img class="markdeep" src="images/photo_fresnel_lake.jpg"></a><div class="imagecaption"><a class="target" name="figure_fresnellake"> </a><b style="font-style:normal;">Figure 10:</b> The Fresnel effect is particularly evident on large bodies of water</div></div></center>
<p></p><p>
More formally, the Fresnel term defines how light reflects and refracts at the interface between two different media, or the ratio of reflected and transmitted energy. [<a href="#citation-schlick94">Schlick94</a>] describes an inexpensive approximation of the Fresnel term for the Cook-Torrance specular BRDF:
</p><p>
$$\begin{equation}
F_{Schlick}(v,h,\fNormal,\fGrazing) = \fNormal + (\fGrazing - \fNormal)(1 - \VoH)^5
\end{equation}$$
</p><p>
The constant \(\fNormal\) represents the specular reflectance at normal incidence and is achromatic for dielectrics, and chromatic for metals. The actual value depends on the index of refraction of the interface. The GLSL implementation of this term requires the use of a <code>pow</code>, as shown in <a href="#listing_specularf">listing 5</a>, which can be replaced by a few multiplications.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">vec3</span> F_Schlick(<span class="hljs-type">float</span> VoH, <span class="hljs-type">vec3</span> f0, <span class="hljs-type">float</span> f90) {</span>
<span class="line"> <span class="hljs-keyword">return</span> f0 + (<span class="hljs-type">vec3</span>(f90) - f0) * <span class="hljs-built_in">pow</span>(<span class="hljs-number">1.0</span> - VoH, <span class="hljs-number">5.0</span>);</span>
<span class="line">}</span></code></pre><div class="listingcaption tilde"><a class="target" name="listing_specularf"> </a><b style="font-style:normal;">Listing 5:</b> Implementation of the specular F term in GLSL</div>
<p>
This Fresnel function can be seen as interpolating between the incident specular reflectance and the reflectance at grazing angles, represented here by \(\fGrazing\). Observation of real world materials show that both dielectrics and conductors exhibit achromatic specular reflectance at grazing angles and that the Fresnel reflectance is 1.0 at 90°. A more correct \(\fGrazing\) is discussed in section <a href="#toc5.6.2">5.6.2</a>.
</p><p>
Using \(\fGrazing\) set to 1, the Schlick approximation for the Fresnel term can be optimized for scalar operations by refactoring the code slightly. The result is shown in <a href="#listing_scalarspecularf">listing 6</a>.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">vec3</span> F_Schlick(<span class="hljs-type">float</span> VoH, <span class="hljs-type">vec3</span> f0) {</span>
<span class="line"> <span class="hljs-type">float</span> f = <span class="hljs-built_in">pow</span>(<span class="hljs-number">1.0</span> - VoH, <span class="hljs-number">5.0</span>);</span>
<span class="line"> <span class="hljs-keyword">return</span> f + f0 * (<span class="hljs-number">1.0</span> - f);</span>
<span class="line">}</span></code></pre><div class="listingcaption tilde"><a class="target" name="listing_scalarspecularf"> </a><b style="font-style:normal;">Listing 6:</b> Scalar optimization of the specular F term in GLSL</div>
<a class="target" name="diffusebrdf"> </a><a class="target" name="materialsystem/diffusebrdf"> </a><a class="target" name="toc4.5"> </a><h2>Diffuse BRDF</h2>
<p>
In the diffuse term, \(f_m\) is a Lambertian function and the diffuse term of the BRDF becomes:
</p><p>
$$\begin{equation}
\fDiffuse(v,l) = \frac{\sigma}{\pi} \frac{1}{| \NoV | | \NoL |}
\int_\Omega D(m,\alpha) G(v,l,m) (v \cdot m) (l \cdot m) dm
\end{equation}$$
</p><p>
Our implementation will instead use a simple Lambertian BRDF that assumes a uniform diffuse response over the microfacets hemisphere:
</p><p>
$$\begin{equation}
\fDiffuse(v,l) = \frac{\sigma}{\pi}
\end{equation}$$
</p><p>
In practice, the diffuse reflectance \(\sigma\) is multiplied later, as shown in <a href="#listing_diffusebrdf">listing 8</a>.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">Fd_Lambert</span><span class="hljs-params">()</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / PI;</span>
<span class="line">}</span>
<span class="line"></span>
<span class="line">vec3 Fd = diffuseColor * Fd_Lambert();</span></code></pre><div class="listingcaption tilde"><a class="target" name="listing_diffusebrdf"> </a><b style="font-style:normal;">Listing 7:</b> Implementation of the diffuse Lambertian BRDF in GLSL</div>
<p>
The Lambertian BRDF is obviously extremely efficient and delivers results close enough to more complex models.
</p><p>
However, the diffuse part would ideally be coherent with the specular term and take into account the surface roughness. Both the Disney diffuse BRDF [<a href="#citation-burley12">Burley12</a>] and Oren-Nayar model [<a href="#citation-oren94">Oren94</a>] take the roughness into account and create some retro-reflection at grazing angles. Given our constraints we decided that the extra runtime cost does not justify the slight increase in quality. This sophisticated diffuse model also renders image-based and spherical harmonics more difficult to express and implement.
</p><p>
For completeness, the Disney diffuse BRDF expressed in [<a href="#citation-burley12">Burley12</a>] is the following:
</p><p>
$$\begin{equation}
\fDiffuse(v,l) = \frac{\sigma}{\pi} \schlick(n,l,1,\fGrazing) \schlick(n,v,1,\fGrazing)
\end{equation}$$
</p><p>
Where:
</p><p>
$$\begin{equation}
\fGrazing=0.5 + 2 \cdot \alpha cos^2(\theta_d)
\end{equation}$$
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">F_Schlick</span><span class="hljs-params">(<span class="hljs-keyword">float</span> VoH, <span class="hljs-keyword">float</span> f0, <span class="hljs-keyword">float</span> f90)</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">return</span> f0 + (f90 - f0) * <span class="hljs-built_in">pow</span>(<span class="hljs-number">1.0</span> - VoH, <span class="hljs-number">5.0</span>);</span>
<span class="line">}</span>
<span class="line"></span>
<span class="line"><span class="hljs-function"><span class="hljs-keyword">float</span> <span class="hljs-title">Fd_Burley</span><span class="hljs-params">(<span class="hljs-keyword">float</span> NoV, <span class="hljs-keyword">float</span> NoL, <span class="hljs-keyword">float</span> LoH, <span class="hljs-keyword">float</span> roughness)</span> </span>{</span>
<span class="line"> <span class="hljs-keyword">float</span> f90 = <span class="hljs-number">0.5</span> + <span class="hljs-number">2.0</span> * roughness * LoH * LoH;</span>
<span class="line"> <span class="hljs-keyword">float</span> lightScatter = F_Schlick(NoL, <span class="hljs-number">1.0</span>, f90);</span>
<span class="line"> <span class="hljs-keyword">float</span> viewScatter = F_Schlick(NoV, <span class="hljs-number">1.0</span>, f90);</span>
<span class="line"> <span class="hljs-keyword">return</span> lightScatter * viewScatter * (<span class="hljs-number">1.0</span> / PI);</span>
<span class="line">}</span></code></pre><div class="listingcaption tilde"><a class="target" name="listing_diffusebrdf"> </a><b style="font-style:normal;">Listing 8:</b> Implementation of the diffuse Disney BRDF in GLSL</div>
<p>
<a href="#figure_lambert_vs_disney">Figure 11</a> shows a comparison between a simple Lambertian diffuse BRDF and the higher quality Disney diffuse BRDF, using a fully rough dielectric material. For comparison purposes, the right sphere was mirrored. The surface response is very similar with both BRDFs but the Disney one exhibits some nice retro-reflections at grazing angles (look closely at the left edge of the spheres).
</p><p>
</p><center><div class="image" style><a href="images/diagram_lambert_vs_disney.png" target="_blank"><img class="markdeep" src="images/diagram_lambert_vs_disney.png"></a><div class="imagecaption"><a class="target" name="figure_lambert_vs_disney"> </a><b style="font-style:normal;">Figure 11:</b> Comparison between the Lambertian diffuse BRDF (left) and the Disney diffuse BRDF (right)</div></div></center>
<p></p><p>
We could allow artists/developers to choose the Disney diffuse BRDF depending on the quality they desire and the performance of the target device. It is important to note however that the Disney diffuse BRDF is not energy conserving as expressed here.
</p>
<a class="target" name="standardmodelsummary"> </a><a class="target" name="materialsystem/standardmodelsummary"> </a><a class="target" name="toc4.6"> </a><h2>Standard model summary</h2>
<p>
<strong class="asterisk">Specular term</strong>: a Cook-Torrance specular microfacet model, with a GGX normal distribution function, a Smith-GGX height-correlated visibility function, and a Schlick Fresnel function.
</p><p>
<strong class="asterisk">Diffuse term</strong>: a Lambertian diffuse model.
</p><p>
The full GLSL implementation of the standard model is shown in <a href="#listing_glslbrdf">listing 9</a>.
</p><pre class="listing tilde"><code><span class="line"><span class="hljs-type">float</span> D_GGX(<span class="hljs-type">float</span> NoH, <span class="hljs-type">float</span> a) {</span>
<span class="line"> <span class="hljs-type">float</span> a2 = a * a;</span>
<span class="line"> <span class="hljs-type">float</span> f = (NoH * a2 - NoH) * NoH + <span class="hljs-number">1.0</span>;</span>
<span class="line"> <span class="hljs-keyword">return</span> a2 / (PI * f * f);</span>
<span class="line">}</span>
<span class="line"></span>
<span class="line"><span class="hljs-type">vec3</span> F_Schlick(<span class="hljs-type">float</span> VoH, <span class="hljs-type">vec3</span> f0) {</span>
<span class="line"> <span class="hljs-keyword">return</span> f0 + (<span class="hljs-type">vec3</span>(<span class="hljs-number">1.0</span>) - f0) * <span class="hljs-built_in">pow</span>(<span class="hljs-number">1.0</span> - VoH, <span class="hljs-number">5.0</span>);</span>
<span class="line">}</span>
<span class="line"></span>
<span class="line"><span class="hljs-type">float</span> V_SmithGGXCorrelated(<span class="hljs-type">float</span> NoV, <span class="hljs-type">float</span> NoL, <span class="hljs-type">float</span> a) {</span>
<span class="line"> <span class="hljs-type">float</span> a2 = a * a;</span>
<span class="line"> <span class="hljs-type">float</span> GGXL = NoV * <span class="hljs-built_in">sqrt</span>((-NoL * a2 + NoL) * NoL + a2);</span>
<span class="line"> <span class="hljs-type">float</span> GGXV = NoL * <span class="hljs-built_in">sqrt</span>((-NoV * a2 + NoV) * NoV + a2);</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">0.5</span> / (GGXV + GGXL);</span>
<span class="line">}</span>
<span class="line"></span>
<span class="line"><span class="hljs-type">float</span> Fd_Lambert() {</span>
<span class="line"> <span class="hljs-keyword">return</span> <span class="hljs-number">1.0</span> / PI;</span>
<span class="line">}</span>
<span class="line"></span>
<span class="line"><span class="hljs-type">void</span> BRDF(...) {</span>
<span class="line"> <span class="hljs-type">vec3</span> h = <span class="hljs-built_in">normalize</span>(v + l);</span>
<span class="line"></span>
<span class="line"> <span class="hljs-type">float</span> NoV = <span class="hljs-built_in">abs</span>(<span class="hljs-built_in">dot</span>(n, v)) + <span class="hljs-number">1e-5</span>;</span>
<span class="line"> <span class="hljs-type">float</span> NoL = <span class="hljs-built_in">clamp</span>(<span class="hljs-built_in">dot</span>(n, l), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
<span class="line"> <span class="hljs-type">float</span> NoH = <span class="hljs-built_in">clamp</span>(<span class="hljs-built_in">dot</span>(n, h), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
<span class="line"> <span class="hljs-type">float</span> LoH = <span class="hljs-built_in">clamp</span>(<span class="hljs-built_in">dot</span>(l, h), <span class="hljs-number">0.0</span>, <span class="hljs-number">1.0</span>);</span>
<span class="line"></span>
<span class="line"> <span class="hljs-comment">// perceptually linear roughness to roughness (see parameterization)</span></span>
<span class="line"> <span class="hljs-type">float</span> roughness = perceptualRoughness * perceptualRoughness;</span>
<span class="line"></span>
<span class="line"> <span class="hljs-type">float</span> D = D_GGX(NoH, a);</span>
<span class="line"> <span class="hljs-type">vec3</span> F = F_Schlick(LoH, f0);</span>
<span class="line"> <span class="hljs-type">float</span> V = V_SmithGGXCorrelated(NoV, NoL, roughness);</span>
<span class="line"></span>
<span class="line"> <span class="hljs-comment">// specular BRDF</span></span>
<span class="line"> <span class="hljs-type">vec3</span> Fr = (D * V) * F;</span>
<span class="line"></span>
<span class="line"> <span class="hljs-comment">// diffuse BRDF</span></span>
<span class="line"> <span class="hljs-type">vec3</span> Fd = diffuseColor * Fd_Lambert();</span>
<span class="line"></span>
<span class="line"> <span class="hljs-comment">// apply lighting...</span></span>
<span class="line">}</span></code></pre><div class="listingcaption tilde"><a class="target" name="listing_glslbrdf"> </a><b style="font-style:normal;">Listing 9:</b> Evaluation of the BRDF in GLSL</div>
<a class="target" name="improvingthebrdfs"> </a><a class="target" name="materialsystem/improvingthebrdfs"> </a><a class="target" name="toc4.7"> </a><h2>Improving the BRDFs</h2>
<p>
We mentioned in section <a href="#toc4.3">4.3</a> that energy conservation is one of the key components of a good BRDF. Unfortunately the BRDFs explored previously suffer from two problems that we will examine below.
</p>
<a class="target" name="energygainindiffusereflectance"> </a><a class="target" name="materialsystem/improvingthebrdfs/energygainindiffusereflectance"> </a><a class="target" name="toc4.7.1"> </a><h3>Energy gain in diffuse reflectance</h3>
<p>
The Lambert diffuse BRDF does not account for the light that reflects at the surface and that is therefore not able to participate in the diffuse scattering event.
</p><p>
[TODO: talk about the issue with fr+fd]
</p>
<a class="target" name="energylossinspecularreflectance"> </a><a class="target" name="materialsystem/improvingthebrdfs/energylossinspecularreflectance"> </a><a class="target" name="toc4.7.2"> </a><h3>Energy loss in specular reflectance</h3>
<p>
The Cook-Torrance BRDF we presented earlier attempts to model several events at the microfacet level but does so by accounting for a single bounce of light. This approximation can cause a loss of energy at high roughness, the surface is not energy preserving. <a href="#figure_singlevsmultibounce">Figure 12</a> shows why this loss of energy occurs. In the single bounce (or single scattering) model, a ray of light hitting the surface can be reflected back onto another microfacet and thus be discarded because of the masking and shadowing term. If we however account for multiple bounces (multiscattering), the same ray of light might escape the microfacet field and be reflected back towards the viewer.
</p><p>
</p><center><div class="image" style><a href="images/diagram_single_vs_multi_scatter.png" target="_blank"><img class="markdeep" src="images/diagram_single_vs_multi_scatter.png"></a><div class="imagecaption"><a class="target" name="figure_singlevsmultibounce"> </a><b style="font-style:normal;">Figure 12:</b> Single scattering (left) vs multiscattering</div></div></center>
<p></p><p>
Based on this simple explanation, we can intuitively deduce that the rougher a surface is, the higher the chances are that energy gets lost because of the failure to account for multiple scattering events. This loss of energy appears to darken rough materials. Metallic surfaces are particularly affected because all of their reflectance is specular. This darkening effect is illustrated in <a href="#figure_metallicroughenergyloss">figure 13</a>. With multiscattering, energy preservation can be achieved, as shown in <a href="#figure_metallicroughenergypreservation">figure 14</a>.
</p><p>
</p><center><div class="image" style><a href="images/material_metallic_energy_loss.png" target="_blank"><img class="markdeep" src="images/material_metallic_energy_loss.png"></a><div class="imagecaption"><a class="target" name="figure_metallicroughenergyloss"> </a><b style="font-style:normal;">Figure 13:</b> Darkening increases with roughness due to single scattering</div></div></center>
<p></p><p>
</p><center><div class="image" style><a href="images/material_metallic_energy_preservation.png" target="_blank"><img class="markdeep" src="images/material_metallic_energy_preservation.png"></a><div class="imagecaption"><a class="target" name="figure_metallicroughenergypreservation"> </a><b style="font-style:normal;">Figure 14:</b> Energy preservation with multiscattering</div></div></center>
<p></p><p>
We can use a white furnace, a uniform lighting environment set to pure white, to validate the energy preservation property of a BRDF. When energy preservation is achieved, a purely reflective metallic surface (\(\fNormal = 1\)) should be indistinguishable from the background, no matter the roughness of said surface. <a href="#figure_whitefurnaceloss">Figure 15</a> shows what such a surface looks like with the specular BRDF presented in the previous sections. The loss of energy as the roughness increases is obvious. In contrast, <a href="#figure_whitefurnacepreservation">figure 16</a> shows that accounting for multiscattering events addresses the energy loss.
</p><p>
</p><center><div class="image" style><a href="images/material_furnace_energy_loss.png" target="_blank"><img class="markdeep" src="images/material_furnace_energy_loss.png"></a><div class="imagecaption"><a class="target" name="figure_whitefurnaceloss"> </a><b style="font-style:normal;">Figure 15:</b> Darkening increases with roughness due to single scattering</div></div></center>
<p></p><p>
</p><center><div class="image" style><a href="images/material_furnace_energy_preservation.png" target="_blank"><img class="markdeep" src="images/material_furnace_energy_preservation.png"></a><div class="imagecaption"><a class="target" name="figure_whitefurnacepreservation"> </a><b style="font-style:normal;">Figure 16:</b> Energy preservation with multiscattering</div></div></center>
<p></p><p>
Multiple-scattering microfacet BRDFs are discussed in depth in [<a href="#citation-heitz16">Heitz16</a>]. Unfortunately this paper only presents a stochastic evaluation of the multiscattering BRDF. This solution is therefore not suitable for real-time rendering. Kulla and Conty present a different approach in [<a href="#citation-kulla17">Kulla17</a>]. Their idea is to add an energy compensation term as an additional BRDF lobe shown in equation \(\ref{energyCompensationLobe}\):
</p><p>
$$\begin{equation}\label{energyCompensationLobe}
f_{ms}(l,v) = \frac{(1 - E(l)) (1 - E(v)) F_{avg}^2 E_{avg}}{\pi (1 - E_{avg}) (1 - F_{avg}(1 - E_{avg}))}
\end{equation}$$
</p><p>
Where \(E\) is the directional albedo of the specular BRDF \(f_r\), with \(\fNormal\) set to 1:
</p><p>
$$\begin{equation}
E(l) = \int_{\Omega} f(l,v) (\NoV) dv
\end{equation}$$
</p><p>
The term \(E_{avg}\) is the cosine-weighted average of \(E\):
</p><p>
$$\begin{equation}
E_{avg} = 2 \int_0^1 E(\mu) \mu d\mu
\end{equation}$$
</p><p>
Similarly, \(F_{avg}\) is the cosine-weighted average of the Fresnel term:
</p><p>
$$\begin{equation}
F_{avg} = 2 \int_0^1 F(\mu) \mu d\mu
\end{equation}$$
</p><p>
Both terms \(E\) and \(E_{avg}\) can be precomputed and stored in lookup tables. while \(F_{avg}\) can be greatly simplified when the Schlick approximation is used:
</p><p>
$$\begin{equation}\label{averageFresnel}
F_{avg} = \frac{1 + 20 \fNormal}{21}
\end{equation}$$
</p><p>
This new lobe is combined with the original single scattering lobe, previously noted \(f_r\):
</p><p>
$$\begin{equation}
f_{r}(l,v) = f_{ss}(l,v) + f_{ms}(l,v)
\end{equation}$$
</p><p>
In [<a href="#citation-lagarde18">Lagarde18</a>], with credit to Emmanuel Turquin, Lagarde and Golubev make the observation that equation \(\ref{averageFresnel}\) can be simplified to \(\fNormal\). They also propose to apply energy compensation by adding a scaled GGX specular lobe:
</p><p>
$$\begin{equation}\label{energyCompensation}
f_{ms}(l,v) = \fNormal \frac{1 - E(l)}{E(l)} f_{ss}(l,v)
\end{equation}$$
</p><p>
The key insight is that \(E(l)\) can not only be precomputed but also shared with image-based lighting pre-integration. The multiscattering energy compensation formula thus becomes:
</p><p>
$$\begin{equation}\label{scaledEnergyCompensationLobe}
f_r(l,v) = f_{ss}(l,v) + \fNormal \left( \frac{1}{r} - 1 \right) f_{ss}(l,v)
\end{equation}$$
</p><p>
Where \(r\) is defined as:
</p><p>
$$\begin{equation}
r = \int_{\Omega} D(l,v) V(l,v) \left< \NoL \right> dl
\end{equation}$$