From 73393f56ae576ae0f99167f3468a414b4b287e36 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Sat, 18 May 2024 15:07:35 -0400 Subject: [PATCH 01/21] Add Sandcastle for testing KHR_Materials_clearcoat --- .../development/glTF PBR Clearcoat.html | 418 ++++++++++++++++++ .../engine/Source/Scene/Model/ModelUtility.js | 1 + 2 files changed, 419 insertions(+) create mode 100644 Apps/Sandcastle/gallery/development/glTF PBR Clearcoat.html diff --git a/Apps/Sandcastle/gallery/development/glTF PBR Clearcoat.html b/Apps/Sandcastle/gallery/development/glTF PBR Clearcoat.html new file mode 100644 index 000000000000..d34e912b7c01 --- /dev/null +++ b/Apps/Sandcastle/gallery/development/glTF PBR Clearcoat.html @@ -0,0 +1,418 @@ + + + + + + + + + Cesium Demo + + + + + +
+

Loading...

+
+ + + + + + + +
Luminance at Zenith + + +
+
+
+
+ + + diff --git a/packages/engine/Source/Scene/Model/ModelUtility.js b/packages/engine/Source/Scene/Model/ModelUtility.js index 5c0089da25c7..3e24e068b6c6 100644 --- a/packages/engine/Source/Scene/Model/ModelUtility.js +++ b/packages/engine/Source/Scene/Model/ModelUtility.js @@ -359,6 +359,7 @@ ModelUtility.supportedExtensions = { KHR_materials_pbrSpecularGlossiness: true, KHR_materials_specular: true, KHR_materials_anisotropy: true, + KHR_materials_clearcoat: true, KHR_materials_unlit: true, KHR_mesh_quantization: true, KHR_texture_basisu: true, From f65ad0c9dd56f42773e96f24b3547abfc156547c Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Sat, 18 May 2024 17:50:28 -0400 Subject: [PATCH 02/21] Load data for KHR_materials_clearcoat --- .../Models/glTF-2.0/BoxClearcoat/README.md | 11 + .../BoxClearcoat/glTF/BoxClearcoat.bin | Bin 0 -> 840 bytes .../BoxClearcoat/glTF/BoxClearcoat.gltf | 204 ++++++++++++++++++ .../BoxClearcoat/glTF/CesiumLogoFlat.png | Bin 0 -> 23516 bytes packages/engine/Source/Scene/GltfLoader.js | 42 ++++ .../Scene/Model/MaterialPipelineStage.js | 116 ++++++++++ .../engine/Source/Scene/ModelComponents.js | 69 +++++- packages/engine/Specs/Scene/GltfLoaderSpec.js | 21 ++ .../Scene/Model/MaterialPipelineStageSpec.js | 55 +++++ 9 files changed, 515 insertions(+), 3 deletions(-) create mode 100644 Specs/Data/Models/glTF-2.0/BoxClearcoat/README.md create mode 100644 Specs/Data/Models/glTF-2.0/BoxClearcoat/glTF/BoxClearcoat.bin create mode 100644 Specs/Data/Models/glTF-2.0/BoxClearcoat/glTF/BoxClearcoat.gltf create mode 100644 Specs/Data/Models/glTF-2.0/BoxClearcoat/glTF/CesiumLogoFlat.png diff --git a/Specs/Data/Models/glTF-2.0/BoxClearcoat/README.md b/Specs/Data/Models/glTF-2.0/BoxClearcoat/README.md new file mode 100644 index 000000000000..4f5fb2f7cc3a --- /dev/null +++ b/Specs/Data/Models/glTF-2.0/BoxClearcoat/README.md @@ -0,0 +1,11 @@ +# Box Clearcoat + +## Screenshot + +![screenshot](screenshot/screenshot.png) + +## License Information + +Developed by Cesium for testing the KHR_materials_clearcoat extension. Please follow the [Cesium Trademark Terms and Conditions](https://github.com/AnalyticalGraphicsInc/cesium/wiki/CesiumTrademark.pdf). + +This model is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/). diff --git a/Specs/Data/Models/glTF-2.0/BoxClearcoat/glTF/BoxClearcoat.bin b/Specs/Data/Models/glTF-2.0/BoxClearcoat/glTF/BoxClearcoat.bin new file mode 100644 index 0000000000000000000000000000000000000000..d2a73551f9456732c7ca85b9d70895f5d9ab8171 GIT binary patch literal 840 zcma)4TMmLS5FAugR8)K(r@fgRg`3F9)Sc2X4N+i|$-K6U9|D@%NdZH8sMCdXhgs?; z>8CE)+Yvq1hwmphbb0bSz9n3Qv{?B+?(fNy1()2HW=AbfwKB4_dS!i9PnJ%1er4>H zi~S!ZIHJM{XG4VxuDIcDxZ(qtxm=$B literal 0 HcmV?d00001 diff --git a/Specs/Data/Models/glTF-2.0/BoxClearcoat/glTF/BoxClearcoat.gltf b/Specs/Data/Models/glTF-2.0/BoxClearcoat/glTF/BoxClearcoat.gltf new file mode 100644 index 000000000000..ccea6548bc7b --- /dev/null +++ b/Specs/Data/Models/glTF-2.0/BoxClearcoat/glTF/BoxClearcoat.gltf @@ -0,0 +1,204 @@ +{ + "asset": { + "generator": "COLLADA2GLTF", + "version": "2.0" + }, + "scene": 0, + "scenes": [ + { + "nodes": [ + 0 + ] + } + ], + "nodes": [ + { + "children": [ + 1 + ], + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 2, + "TEXCOORD_0": 3 + }, + "indices": 0, + "mode": 4, + "material": 0 + } + ], + "name": "Mesh" + } + ], + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5123, + "count": 36, + "max": [ + 23 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "max": [ + 1.0, + 1.0, + 1.0 + ], + "min": [ + -1.0, + -1.0, + -1.0 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 288, + "componentType": 5126, + "count": 24, + "max": [ + 0.5, + 0.5, + 0.5 + ], + "min": [ + -0.5, + -0.5, + -0.5 + ], + "type": "VEC3" + }, + { + "bufferView": 2, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "max": [ + 6.0, + 1.0 + ], + "min": [ + 0.0, + 0.0 + ], + "type": "VEC2" + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 0 + }, + "metallicFactor": 0.0 + }, + "name": "Texture", + "extensions": { + "KHR_materials_clearcoat": { + "clearcoatFactor": 0.5, + "clearcoatRoughnessFactor": 0.2, + "clearcoatTexture": { + "index": 0 + }, + "clearcoatRoughnessTexture": { + "index": 0 + }, + "clearcoatNormalTexture": { + "index": 0 + } + } + } + } + ], + "textures": [ + { + "sampler": 0, + "source": 0 + } + ], + "images": [ + { + "uri": "CesiumLogoFlat.png" + } + ], + "samplers": [ + { + "magFilter": 9729, + "minFilter": 9986, + "wrapS": 10497, + "wrapT": 10497 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 768, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 576, + "byteStride": 12, + "target": 34962 + }, + { + "buffer": 0, + "byteOffset": 576, + "byteLength": 192, + "byteStride": 8, + "target": 34962 + } + ], + "buffers": [ + { + "byteLength": 840, + "uri": "BoxClearcoat.bin" + } + ], + "extensionsRequired": [ + "KHR_draco_mesh_compression", + "KHR_materials_clearcoat" + ], + "extensionsUsed": [ + "KHR_draco_mesh_compression", + "KHR_materials_clearcoat" + ] +} diff --git a/Specs/Data/Models/glTF-2.0/BoxClearcoat/glTF/CesiumLogoFlat.png b/Specs/Data/Models/glTF-2.0/BoxClearcoat/glTF/CesiumLogoFlat.png new file mode 100644 index 0000000000000000000000000000000000000000..8159c4c4afd64aa84c363b66f09e75881ff890eb GIT binary patch literal 23516 zcmY(q19T=)vo8F`nb^+cjcwbuF|lpiww+9@iIa(K8xz~MZ@zQRfBt)WtzNymx^|&g z?cGmRt&UKT6NiVvfdK#j@RAZDN&o=J_YedC4f%c1b}Tam0KnlbgoPC(g@u6%j&`ON z)+PV|M@)>Wjyawx8ehAU88OL}vNcPHa~VH8DSVO|%kRcEzl{wx+f+%me?-T|kC#*F zqf;Er?H|A8MHRdmPqWYly8rqEDIO5l_!{X`U`YQh04ewypMw;Rqe{*)xS2C7+Uhn< zF1_z^u5!IDTg|Q@j$11y*oYvdP1` zHT~KW?(@=HUEKK0`}0=}A06mGVAyb21{)p+a$TxA?h4}hnn1vRs`c~H@c1vIzyQ5x z%Z|}72yXOU+|TI_8T=T>CukjfOJJ-p4dC|u9b~XGv4-EYE}X=KVnqAuPY4)Qr<=&Obilo8pUe|%YtXj9X+~7SCZND;a zh8^E*lyh&74o+D!$Gtp6-d{UBBp!Vn^EfX~ypQjKQ)!{zi_?;W&7C~t!d4mJ*9G731A-!*A*>TdRs#&#m_h(@3uDE+JO}}j3GELWcBI~K zQ%u~W9-dpS#*E3{uD;~YkrBP1mzJqUD&sJ|K#Y2v_t+|od6S#U5N#t=eqS1PP<}FO zAj)dcv@Vn6V}b({2$4r5kL+U-UOlSu)$59swWux+`_e~8G_z7kmgoj&V|%{-7c-TN zw(@cn4MKA$@i^rhgih!CkY1hk2@Shq^DcHyoBGs?-pkoL(9B z_IjEAb88JJH&q-P$UThf*^I%6LHs+1n|*aeG2hhDjH8-+9Ue_PIs~z*G>BKr7d33T z7?G;`p!={!noe>bijSerNuM#FNuMd7ULDW`?(v+xt{pmi0w=wpN6R}Ltt~GM53Ijt zC#hCdQiR0T=sz(W``C6A4VZ4p;h>F#6juUL|D3&Hw9Qp;%oqP zx3RW$;&SID`Y#Ty@9}@g^hCh_VsWp01DeVngChpSm+pt_+WrQAdjQ5DVLIn z*#AubzT+h_cXqbtqNjIrbE9)(rn7T2qi5vg-xj6F@5&cKe{~rHsr?Z9W|F6l`>3`h%c98x*Pv{xx z80i03_V-kt|4_N)9W6}0HUCFGA0y9yasEHF|Ix!k{~zW5mofkC>3>nbUFCz}q5t34 z#s?$QN3IC~2mmBS1XbKYE;`-Zjn!SrA1k#f@?kjHxsLF#GbcIr?K$ugnRmmb_T-tV zy*g7rM*ZA7^Jr_9oB4iGk5gOGUPxq)U{4{4B8(a!Y)q16S-S$ckMQD|6$C(1g8Yuq zBV%j}0;m=$!@Wi=Rot(-yrw&k)|29PCNh{A%SwRxKn8L`{p_R$wInqp1*92ZKD7To zXc$l@EYL{6#Ll3)vO1CixWEMnL;xooh$!IjdbhHi8kzz$|It6N2QSp02;k&?v$%i` zJ^=$Bf(Qaus?RX6!MWerIIuO4MbAI%m+K7Do!*DKKVGvBZx<`;o$$=}xyrEkCxX1J z8v^hx5=KA4INSj|cJNEC@6de0AE}NBjgbh271;N5tCOrg6ka@A<{e91>n+FfLKnMu zPV>nhA@Q#MVztdL_Q{$kp1daWZbg*9DCz-E|4+}ubyx7zPFGj} z*}p?KmqX|WC`_Li#3I?|@YfJhG=P|pcwG>+!vM_tHZmP4JOc?%9BKP1pthr6dze09 z12;NRKRzF6He$FIso{Wh(i+s-lvwY5{Hci0KGTSTS=``g3&r<}L3F3_np+wT10_s^ zUknA35Gce$);9mD^0iM6js`L^?vjwC`x%9t5Oa;xD-H??2N`~BfD1WHg!LfKD0luC z=DviwYRD)!FG4)jAnOK$T4N_TgB1~kglZw!)$y-A>uW%=zi9d&bBIQ|f*6(JB}1OtM&8v7d?Rp)%+}|c$$HShNmAzf=Ub#E6WCR+Y{`wFS z6ee@7cR}-zh%tp(?L!*NVK{=iqClBNptH&qiosFYD58Wyb3*O#?P|vijjnUOaz~5# zQ`(O)1A|5tte{}9S_GK~fU@Xc#F#g=kwTIi=Uxv8(c&Bmf5Tn?q zLC47Yu%iDY9w1h}pw`)X6x}s)4g`*J43US61QOJ}7Es_u{Z{RRD>bI`8dOdS03Fb*Rh9bU_Ft&dRQ<4;t2l+nM5{kbH16EB1R|2UZ z{RrC1FmOUmgnWlhWgZa}G6ji*S0AQtJXfanweL_5uWp+vq05OxMz+(ummE#^gx6AT z;}0RXnRV$@{;WmcSSBFO3j*?@LE|!l}$XG zhvII?=;OE;`fk2lwMwtdUx*lc$RE<(+|dCJ+Z;ZqI3sMd)!_xjEJc)v2VBTaDp~m7 zt3?9Y*6}sGgD$H&4W<*MCW71tZG&YM{+XC1Vz3qDCK=ULRgP&`iIuPd-HymBCiWK?Q2xfn@{}-}9i9w3~O{dCfjC%|gQNb1# zbHSp~0-JF4Bx-qZG+`knWnFlG)*K+R0>T$XTa_8j3&XIO?+sL{gztfN{#; z`jH_h1I^)%^wtBSg73O*$DY_4!o|594bTP(Bm5BCj?b!wvLf2G$~U`>*mbQxG&He8 z$MI&vTTo!T+61J))4@?47E?yp5Rt#mQqdPr$lMW%GP-NkP{h zK*!Y_MO~S)?mu_9wa)VLF4xdu@1OXVpH|ZE+xF{eexuEAJvbExR%=XXAL%$tPzcD0 zdgz&T2QIL?9OS%-ZCYl7+4MSGwrz!f2pJKsmPc01^VFYrhWjjchbaV9!&by-jvJWi zZPF1Ssx7CA3}q@j)S(F0ujWRZv;YM<7Lv>y?+@|^PO*USFkSx$b~(gV`|30hicD8Mi-)7R z&onT85z?JjUw!|A-d8f%B~^&y0ra?S84a{UP?O8L0QfVjesg<<2IQ1JgEr6U5L(51Z~>P0XhMTs znW3FH9DCayiPE3IiK2AF+pO{1t#yGX?rEhC7}9BhzhPk@DLqEse@es+q&?scCe%pC z`VEiZqjCWfGqG#r2)tx?Cm<03WtCaRfr@iax1ngx`zU)bH7(eJ9i#_ANJtm#x|9HM zYN|5&+976UbY_mof2ayVqNJ^dn{>F<@S!Z2x;v06%1AA!;C;?SOw7WwyOyv&En)H_ z6GHnaF|4S^$|d)6Th3i)I-U+yVYQ$#jx*~DU|ynJaL?H38%BcHsq4*jJI#kukeLHj zaH6^;7@G@-0QDlfP3#z~I~sWqS9*;4<~(q}Pb(;)i$DVyH=ycI;ar%xeC%F6It+sX8Pe| zFg$COuL{nzfC}6l9SkUtNsC|E$w#K93l~f<(*D~c1E<4rydsQ(yqEE~2a)u*S*Zhk zq`NU95(HFH#XFPy1}yP_Gr%qa?w_Bqy6vW-R(=4;dK;-#_NbhE+YAiQ`cjeB^)rO+ zhf32%51A?pV4#`783ar!V8k$Z^f3>g#pnp0%fUV9M#D!FbrB7m2>`OHMT7w!M0jq= zYT+gSn14PfWCI}y5IICdz9#pa;8+<|(8VZ4i=+GIt_KCVWLb$C=H_VSa2+(@W$$j~)_M!V55>Q>T6k4?9oepxq z51a0h_dMs?X4~{d&hMZc5r&2qf3@H~rq>TntL_gG0Kd%!Fc^?OR1TM4@b@Xm{?$8h znC$nd31tBR*#XDm>sbN0T(#W`&J*wrBCq-3Z*0QJt>2rVOWbx6Yf(axZ~Xbbso><} zjKaV&@-Jlg6>fsnVu=a%fLZ1?5fDB>U-xcve~oJM<*#n>cIWY(25toVKNp5mFbsHt z6V>Yy{2m5AId)Y*-X#K{3%9PAcM9cTc|L0oEsEtnv$AA5$L*j;bW=J4`P7np?lLoa zMZ{<|XbCn#?_vSau#4jZIRtKNGwdicJnv|?M^B;Zg>O{gC|+z>`D^xJIiuo}9@vG6 z>fi$Ldb=-i%6on}iasO--^&J#4SWRNIS$;RXe*MfCPz|qAcxjZYZD2;_|%GR_lNZE z*l3h2^q#ED{L^1LMnn+E3XSxo8O9I^3ud6LpNQkOlC9n=>>&&wX}nzr5LZEyAnJIl zO4wQA%If5FBQebK9h)Hs!h+F=_3{2hB%X@9Im`w?heVTKv+QZG&kUoD@oLIfBF z`Y(_tchY)waPag$EXE}ia?U4w+SLV>jq)$hl{AEXGNT3Dtkq&X~8Tty39puJEWm#*DhMY#t zgYur)pL6W#S|{Fm6xH|Rz-!gz7eo+;f{5EZX!L^u>a?m3NW++x6)8gUgkspvAIR}6 z511`SxYFqs>BS@2RVt+DRgSZc5&(@dr-^)0mFsx}p)uxiHg-Z_b{@aXz|sA91X_%V z8b_5F{0W5SkmhjRG-@9M{>7gG)Pz#l9IR)~(*9^ndj~q|N~fUr`ZY%(K=8xWQ2=%m;1J4&X`k>m`XBWvRJ8y>@65b4lXbOEQQi2@q)Hb4_+Pr z95~{-J@EW2I7Q#jQMc`l98y|uS_x^IL?Byfxzh#G_LNsUc>9{5nQ;$eJEJan4_K8#W}CQHI;z!_(~rugd&xof6eGh{BZzJniY=5>+{<-KM_#K7`l8ltiMoD4 zapTlg%qFNbnG#~*W=Ypom!p-g4P6(0@M2{FK|`R)h{@5QLuR=Qdqf@isK+!@ImT%# z)c~5$`PrW|^Bc@`#U1rLzaCn$g$3jsETk=Mp6~2fkYA(|g?wAd9p=U}>O6D7zFucN zCo1dBvqcK@TC3x6RPm30g2lQqK8Yfr1!xKiYC6rnM!7yowRgxwTGt~=2GlI zc7Y@<4v7i1w*0U$*X`DgRY?BOls;Q60j|?6%;L>tY4CswGZLr(Xi5QM`4jX9(z5u( zb}qKr0(z_3!VHWMAqJ7mH;q_lQmT#5X&6N+w7+3qQ5_D#t!?Th)t<=n6W zG=^rG8`?Nv<-Z;osjFi$&9970x@ksPLpnerUCwvUbbp!7uDdfciYEmiu##e=mn%{m zqX0Qj2ur;K5QNggXn9zexwE1Zu&|ry4SPsE-A zIwy&Tn^}+{lg&HDe}=$@&k^SP(e#;XLXH{R3w=*3h~k_q_-Q-DQ#sF%yGGWXw^IyF zfk2;Ot+kDWOnz5HmedgbL_1pt#*#yDlCn#b;gx#{E^u7q&Kw~uOkUXhuKi4d^fe(U z@lb^bZVq{0+xQFeiAl(rHN`t|Fy9I#`v-CY3EV3e=Ls<7BxBpgnG{gp@~?+;B`HWv zO|tW(`_D9;QYRV}IL$g1${06E77>^%Qg@bSas7c{S^%YYv9zGvjV;eyzequ2e$;Z~ zCb)_^C@@E|>?(gc-$VetG}&xztp2J(smfMg6GzbZy%=HYXAQNG} z5fTnUwk;B*O;E=B)9am0`{|#?mrSbY_s8iMA@IC16|P}13UR8{?!y5iZ=mA2x78r~ z^uAY`H2+?&O4brao8a$Eo9U8EZd*WfHQ%wLxV;TWRo&NM8ln=*E{Z& zP+rxZ3?ha^BZ|QVUqzb3onV8dC1C~+ox04HHY)XJ`S3F#0O)HwhV9bn;W^#Nk8YEl zg|M1$Y!{^w9(r>68bvJJ(oXO1h1ptL7P?D(XYvc18Mx{jT#U)aFA;`yAqsAe52wGi zsOT-H6F7iA*5p*o7)I6B(O9n-qO^<_X3*xe90HIiQa`irs4azgk#&H70|h(kqP;kc{*Ot!-{s^ED zBRYw}{o&Z}ATzP(vUAN@yr==<$oVvLa?{;dO~@w-zj*@dY=w~_VrVpy?L_2>t_}z; zt_GAva+5I9-1bCNdKnRtT0d)=%~~`YKZtu;Y_71}pLKDsjT({5TqV+LNq%LlC{P0I z)W8KS(_$3QwnniI0Ltty%_Thh(B z;y4upMD&TgVvMCPOLE$`Pz5jxSH;Zb&~g8+jHrs3NJ%TyVvhetI@m;x3Omoi%E_;g ziq`s_6bi$AdaPx2;2YmbtQ!}xU}o#N@sGwfb1O&^Q==1KqJbE2K8VUzI6gd91--s` zl>JI`NIB|e_FFu2ze7*Orr&U>+e#lDPw*9mOKio)^bd4N_I=>{t@q-99N? zDm=0k_xjWo8j2k~O48gF?udRd=7d-H>lg&$W7r;nz68rGe2{J3dz7o zw+#!XJ!76wsf+(u{T|z3a5G!|O(a`IB2}N3F!b#wluVmX@E?CWQAjw+`-7+nwq!_2 zbI`7WSD>?jg9oqr62EJ zKPYM+-xq9QhSFnAdsaF_l-oY6>(QT4zKNT=^RB)g!oV&~yIBq@;hJI#ikn%cPpX}6 zoBn-KA_EvY-79>$C4oUp!yN8Ms9vfIlp=@6y=Ia@R-ag+zNh});iB$lQ4n_tP z-ZlW$E*GnZ^su|Z+|fWur##9L4uqcbuT=dbB0c>u`*I5fmlkVh{LlV+ruoD_3&B9q zK^4vbO=N$;Zfu{43v)JOvcZO*0<)Efg5Hs@*LM%?xRv(PD3}Se2wqA|UAl$gIeLfK4pOj)D}K7Y_)RAYiwO39`+n6VJnymXaJP{J|f*DIubx)P>dZpfWMl za%=+ad;MfZXG5~gVyyK3RkWwVgKezRnu$H02e<2{wsN#$uG;s%!jsl4^VZgj?+&(K zHYL-PsDRNo`linzFi86^benlO->K?RlOZr^9d~aXR*}c)v?Y*eKHIO*aYa=734 zIIvdPQe>iu-Wgtm(LqbRwEeTGsY>2fMy#ny<}C;!8K!$vusnI`o54-wk5}cR-UKQP$y2&z|tjkMNEU9VVPLU)|+5^N;!Q?cayVU ztK7Tob)jQx{QoxXqcrK2+eZwGyZsBned$f^(0(S~CM*qS`WYE00!(4ESN-IYrCf>0 zpC3{J-&yelKIN9gu4?W6{ORjYrmq}LEbpm0ze*dGM+2pq;Hhm02eGQ^Fj^T@F;Rc{iOB)c z$50miQcI!Yg*-eg-Lf^H#~v7HLu>L(hKNx2U>1eTJPSKx*=&)t6fTfBFYnn2i^mEmC2E32J!yOI~y< zheIgm!Um;bIo;g9GLDPDS%;5r^eNFam?E1O_hDBjb&-_pQ zLS&el+U}Wjw)2wGl+dac|L-P8caC#0?-;H^_BA}3`FNmRgznq{(= zIY>M()w+(YT*kC{nGDSgUgec{biO;J>s=Dev>9=7#s}6?>YBp^G>QXiHJS1<3@)bP zxjp50QaqKj&O{I!PxBF3Qeau4!iX&+B5U-lDsl6uK0{37MZH5I>57lB@%@vU$O*l$ zrkR#zaOuTFYsADi&i!?kbzbp=JIiW!)iuhdt|ZTM$q&5)X3M4Y0pSHSZ zBug`KJm-tgF%pQ-(j^A@i#nBN7fPOyE*u?jeNA;pG1MHCZ$%VX^w|`$KXVc5HcChal;`+)SvBD-J9L-28sKyw(4SrxO)a*aK+5io}tj4OI zr%JR$;_mk-`Xh5V6J;O5tS-IKl&v%)GMKtipB%r!vGUz;pJKL!)*SBP9}EZfLZ;>S za{qHWNWE=~s%>AYu^^%b58A8MEpAwa!&hI17{QJ9_}&v9c1 zJC6N*(jLyB9=5;k%W9cGkRg<6h2SIwAdO@)fKZpLm7WJVJzd$`S*Y8|@Pb zh!Yqf#a?eVf9q}M2!HwGe&Eae(-1|CF$!dDZ>5^07%vy?z=DS>T!-+!3h3_r?w&{e zgQ43GRvwNT=(sXQQ&W3k<}XBkM({>i}2|5N8H%-wQ0Pv2or?B4Bx z5HB)KMqb;uw7Z1=W1jzG>+0TU*UO_iIOpdjqa`2U_G7<}q?$UoJMcX**?+suY<_$n9~X=j(2)dmw<>&O$Ruhg6jz!6Yin)Yd1m)`~1CMo{d4L1fH+ zf2{Ht8%w~)g*T#yXEO}L%^p@$fffKN%$ zw)oVMlW9PHk!OdA#>*Yygk-Xk6*abIGjm;7uGU&}SLMpbpxyP&s-D8=GM-*Xa{L!r z_CyH^3K9Vy_Y;rq*S?4I(|G7isU!xmq|l437=jchVV`Mq8Mln{rC( ztxjB_aI(wi3|6e(zp$;#w>Ydvxa+J1)|UuuEI6|IPqp3C>w#XzI|?%r<~h+|K7g+y zNM;GmO(W?tyX3ZE@gL$hSqgf z&=)e4+y$95d%S5JyIoolS)2k|ym29l6b*Tn<@|Sd%>?%115s(OyPojDKkLMk-|vHh z?|FCG{H$FFLAc}Dw3|I7f5b8t0;;uoloUGW#bUsV5YPuAal|~Id{UB?wMEk9$47n7 z)3H9-hu_$Fve1_RM9B8R%*%Vj63#^)vw5`$7PZi@_|tEP@E*tE{3x6whNsu9bqK%G zRghgie1YRudJp0TS%V~j%UOC6l>>)Z({UHl@LcS5%=MVF5`rk-8CX#d(Qs@~@$1<7 z5VzPRLd~C~)Hb)lgornywq;%@;wE53R|Ao^m9jB&+x5i5}Q zK59s-ecRV7T9>`3S zSY+N?7H&Gc?{YODyylq!!5}dcLGV?m>nReLU{Rs|A>VVrVmoqK}Da5Dc5j-%_L7qee zK!Mse!@m`>bolcp;bBQCW4R{#r2d}aWe{1^`1T30)4wons}RL38(bi+t2DIw7_4sx zDOP7Sh@JlhJ&^Fjk}LXGBWeWA|$_RH~|=OQeP@ghyV7 zFPvAeW2%3TCdKt0Fn@QwO%&Pg5$tHkgq4K`5+6fY(j#QSq&xd>s&MI%d*WbA1R|D? zfz2G}H7V_B@8RQ%yz@^H2bEyD!OY4;6V|=o4B5R$TGgw`02*)){#(E_q)cit&445<{(MRjenE>!h47 zbh_poq7eD1S)~Ojg|W!VvUP3`yP4Y+5W)ppN%{F^<1$}}H zagIYkdz*aGpa+iZ|MXl6K#F)@I4FFco$?E*NW{p+ece+QI-@T+Da+;ZFLG7P)n<`$ zJE)n}ZO;G){s2v%E8ANAV&dcaP*zDh#;r@c2TFjSAHsM$zCvW@W9>dZlK7lOV><5f zH$;41sHDQ!F=Dp*1Lk!m&ESW;PN?exMFivzFSk#gZf-Zbg9){JeIGV6{#IYAIFSF< zb-0_4#`D*vx)otiOkis;>gKFR+j96~(q;;EkvVP6N%1BT49dND@ZlfqZMaH;Cn#KE z3(L@J@t{$`>vXvmf8M<}Ym-%cH1>%2p;tO+jN9-79=(3U4MQQ~_Bd`&fZs zc(vL!r|&bV-M;mX*)#vtEV64JD+pE1-&n!L6tqWow<;lD!?$^0#QOf)%TxyLCcn*K9GYi%CxdREhlJIX;z7TuhHBLp$7E3*HHKUWmdt;>XSQr9iXOKYe* z@x!B<1yT05{QRuh+Z@bB#bacm8k2y}^WZNIZwvCp!RS|+{(1V=rtpN1IFiZUNf|&3 z)V}jG(}v>D%O8v{tJvGOEIQiD)6|QKR=C1uU#uf1TXO1?8qo(m*nt^$Lk|>9Ec5)^ zCLJ!@m=j#i!h3V~SZUcf1uwqICx0fBfeTRd0^({ zc!UY^9i32W{T@a+`8PZP?jMJm(U(|S>`yA9Ji$_#@$U?G;_RsQv{V}#>%>6&i|(A~ zlgGj%xT@PXi+DqI0a~#U1yd`I(fl|MOOY+zHtpYs&=#q}ajvMGwH}E@$?oStn{%#N z4)pE#4w-6dw1)Oi|8T|~uXRd0%krSfH-hGilrl25Gjj@&zIB-*u z4Ki;c-cFYo7VD6IoUF0gGfkJz=YZt%mn-x;xS zx0K)=_xWSzN1a0`uJikGY^^^}Q8Axt?b!V_yga;XF}Y69=lgQmR%@am3MsidXIp96QzSD!mv_&To86^;Jsumm$e4fd z+=I!B>_NMTsMyJh=a&>_U)GnnbWn>!)Aw!$iiO?Zw&l%3F)$4U2f+1;*XCqa6@>;U z>ZB}~IM>X`K$#r@wJ1V>Z(Z~` zrG7+62{<-GlXQ{#2~y&*;&n*A;&m>2kKjh)wb!^j+MMDYh&Ze2P7GVB@ywTiVf7F9 z?tPfCBuGy@_Qt^S1GBy$F8AX2ePZ}`i!?Ku*${ki^Z@m-JrA$v1IUam8yYnFNaJw%yO0$P>E+RQK! z6U*o`7Na2_Q@i4r;2PjWX>C(t5sz~xWV?HMW{7c&FFjtD0AX-%{f9TL_AW}@;rjM$ z0hbo|c>t-VM9HC9M@$I{RH5OPnYbS(+(zqDn_AGFo>6URmEu38MRxl}vb$ziAHT@d zJJLn}T--a!9jJ%>$^8U5s>>5OUn%ZHy#X!1XLx7=eL|lozGmR8hQusH75$`|A$v1} zswB75haW+~@9*#YWbAky_3Mi`vnyQx*AtUTjO7rBkQIed=?YD-3VP-4@_dcUy~^PE*IxJ=UZsMG5W&Ll&$#aVUv7W!Y@FZOckS`M zyI#R!;hlso{FL}hDp1!^9zC0~s8wXr=41NiyMqVuf~oTB2E0En%Z28PZiIekhnx;S zbQ!Z|J?Y|wG?(1UB&!ybgcxi>1^~^0TAtC57DqpYtTP|TGCh&YjBp@gm8HSM`x#l9k_ASnW($#`KI-Di)dqT2&(2AAB$U zWc;?%2EJ^0c2yjuMfa2^)D58v`pF$L@3-Ei{{p65?YzH|mkH0P1j+~(F-SZ@F;jdA zxC|adc^u6JH;UGp+24gaG*o>2xl6`PL5@?iMNUMl?2Z1d*J!X-W3PeBx*uO39wK|m>{OQSJ*%Noe4jYO-$!zGTyR0oEut%8k;Mv zq*-k4mFV(wFP;a3?#un)@rBb2Sf%qreY1q89ZD9`uG{v(ygvaO>3-o(%LrCopq>-5 z@w#J%yzL3r&YTY*#t^}{Bg+^?b917kB}GAL(KDW*&yR3Td;R@223xIZqomUJ`MOJG zt3t{cLPTQtBvYd?Cg0H^JSzfll5v8~xtKBa%1h5Fa@`7&IU_x(ytlsSRKM8_NV{1b zOw;V9chmaCdz5@EEk{Ht>Du)H%?qJ7n1b+e3l$#faaI7sBM@v^`yCi8P=ld=ljcLt-y{9@m96^SG;wR?=h!{A1BPkKBJ@c0(QL_VB@%rnSBJ0y zYCR(nQ|lM^h_VOjjKvaskFv01+qK6~#`MyRAC^ygd&f=cl%lYYp$-Ys~_E<;rrq8;|{&uCdnrSCIkFpDn_%dchtyD4J2rIQXqA>?#Y zdqM)7B&mp3?}J z|9LO`Q&ZCQvXzFv_$XGHB4wQ@8d_Lg4qbly1lJKZ8*P??clmggvjnuZgh!b%E!Fyu zGJ4w|qh~@-&+h)#vtK?XgYq@Qe_-aaTsDIx`v(6tw#6gvn8KN7Wrg3H%t)qtWLMKy z+VgIooR*rW`@hr5csaXl$Jg&V6gscw?QE9hm2YcpAI^ao5y%J-B7PrEZm7`i5cD#? zKQ0{Kv+4UEoY%4L7#(sw;6tk6&sWNjdmN*Z*96a-M5{e>LNU#?%rd z8>YF9@-sZ

zcp)H$DRwobos^_z9*b)Py+BG1;?9=LPB%^d^Ph;NN08T-c4v`K2Kf#j@~z>hw@=m- z-b#9 z0x)=s^$V-Q<2fq>>+zmy{$1-1I5n3a+|i!KVUct+jCa3w5-Q~tQxnoy_8wC1Bmgp! zlmL?|No0T`eKw1-ijrz=GjoI7%K-P-a?qB^>GFim}v#pSYG<#lj z7446`x2?gGN^uw5wtQjuN_yU|-(u7QRG%QL)>@<#kwDxGl*3FX$Iix43@QPp6Q z0Lv;zO^nhY4lZ^~TYN9m2%%7!Z9^CAw zF6ojDAmNZXls&wD6B_m`S(tP%)~UI0Sw5VsT@7Zy}~L zd9C6$(UTm4M29{RFD_=n;~EDw&)ffUr?Tzw)8!QOr+xuOgwTvr-nvEnE@hkOT<<)i zMOUmK-)%=+hMvLIsQNZ6c-fbf-f#C(&Z0LT61JiKRyY@vC{`hvRbP;eHz7i;s$u2fc)U2UDn8+pm*W$3Ya2!ecDCsD`w3MY=C$tYocsAX zd>`@JQR<|o{2nMJxU>7y$oE5$$c!w@{3E9ki+6%%$SUh$vX$Os`w{Pjw)+0ukj1)d z<*ge=cE@pY!HoMnaajRomt^{PsR#gk#U;4zc|SadZ-b0Q1m%*2ap+yL{`0P^N!Isx zo?l1qQ&}BhCnW9@&No$x3HQMXGUIbJ2rdtpwCkhd77t-AI84TTeOOs9{qV!Su&uN5?ZEwa0a)$`8l} zB9-I43`bO_6iP+@->nUhs%MWD8sx*gB$nh8R!xE56=x?Rw=BHP_trUJX%5#Da(Hdm zeCKMp?;fW0vrg3(PsM~K@IjY^9kq9Uq6Eb2_m+tzKDZy8m*nKYE0f6wb0LYMO0xO_ zKW{xSbiLPzm>mfu^?%cQFFJlDy#3Y_CfpH|`|U z0BI=^%gMWzOKi7Yh$<7fbFqv^e1G0<{x-+v5#9A}&~dXW@9+)rg(M!#cNvs*4STQb zDhGLCN2Q#Gi-o}0LY-cMUp z8mu~SwG^*>H-!6!Qc&5@qhTeAHNfrWlLdAEg%T9IYG5c#@Bf_pDt=U^*?nQ6bMX_f zF_(_aPd`h;J?>SHjnWaXjXIf~(&jFp7C6;*<>>{*i=&|wPHva0zaPmYT{GSq9qmr- z6_>ZavNpl0&*dt-@y}XK-ZB$A#xxKkMfCk>i8Maq$T}-f(cn5^RzJ)~h=~>W?oxtf z9L&_zh1HFih;6lGJkHjNg@nLn!I%o=6Wt>If?$rSE}lDNqzX+{5+ree@MKw)`smMb zzew_%A^M1Bj70Z9N-r2*uU~_j>>$0nxoOp7C@aE%UyM@tRUL;#%B^o8OL@jVz_L#gDb?nC)g-!M*A1?x-`l1_;IW_sFp1ZnH39H}#Q z3{(mMHTIRE+Y+~8jGAJoc&e#b1|agLX9jDajH(Re#H-3>hKblrT98&U@^MU(M;S3I zt*=Sc^kJJ*xx+uapLe?1_aI*Qcgg>O0PE2u|2QZ9$L2KI{sTI z?ELu?^$V6W=sw7DpC{!5D6)-sMJu@RVu07d5||(mY%)@a#HrS<5Blu*2M@^=#j$j< zs+jPNuJU=J^t|S174P@T{H7neo%buX_W9AplU65yjr{tP;0g3!mL0Y#>=qY zF$M$hqsASooZz5pVt18l;=-T{WlgT^lNdV5PBZ)riSY+64>vXY0gO9nC_cv!@^RBa z1l=EYvyAq!5q6{!GBvpk^YY;wdcWGBRb$0|_LNf}C>;VCEpF1G8S$A4e$td1$hr(^ z^N*`+7^%#_dG=$$ilujNg>z-Xo@-m2kF*Dr|hF2upo!~am0ZK zBP12_mI4|kS2GO?RA^@#p}ZgNNg2PtYm6fPzvo8D<`%s08ZP8{0MZu4Ra7<$w}4QM zb+SdaN_2Dx$@qFd{cJfwXTC%!-noi#8uKCwpq8%U%yEXVM@7VUX=6HJ+5Y_>x4pR( z{|Ec1%5)Cw)(<9$#U1L6H*Cl^Bk!vlzo5{Hg{F!5UMUXI(K+fG7%`M3^#M^yLIh6? zpA1X5fNPE^ZQfuv#LsZgDx`$GTQSJ+36<%kvvw@a&yv$c3R+6yJ))BPg&zR398Xk^ zkU)dyj)`#hU#LZx*!=S&SFoPfqc?8Xv!i*6R@8!gFfVq$A{+Uod_Z7z%C559NZ}KQ!A?0l2jBF6wH^ zXT|;t9A-%XnDBvq;t@W;w4SvUvPr|1VE#)VHP^sMDD3(Ru%y1W>y41 zZj=*Olny>-*cDkMTP;MbZTCPcUfJ_=YHi==0v49mHd}Dg^Eno?9qv)!)06KqF~&!F zcAfRUf;_l>yZ3p#ee^Fd&)H1FL-zKrZj&Xxi8|E5*#BC`5{kbSoW<-8;Py&r^nG$&tpzv!#DL%2_m4Pw zc@NDD6>+&g@k0Uq6{2NiZi1Eb?}6QBPb{hurNRr3I{b`k-f*>V;#RM zR8ljpz2M~jIIJc7*JQ9_{rm@63cNHe4t&-GAhGP4E`TN!iJ{I^6Lx3F(}xd_yo#Mi z|1X5-I3fWos$~}f7Mcd#lQtHK{6FULWRwf=c!%-HvA6L0{=dQaI2}Tm^|Vl_ zck+vze^&}8W-*r2Z05nQKK?VN6WhshWA6ML=$9js?8G5lY(0VZSibx4mDe?7k-B+> z+N7R?-ReS1-6AajC|&_*_OsE20E`TeLG%HcX#Sv=!6`2j`Tr4J1J}s%=;NE7iLuU! zKFK!IO^A_Qjrf1x{2lB&{w^ag^HUs%&`y4-{RG}U@SpMGXMe}M0rPcrX3E8ShofHl z-85j$qAl9v)8)L=A^-{7dm!Lv=9D0*tG*i504p!PviI-t#=f7Sd+>_3fC+s-E`YC| zIROC@0h7rBW2h1T@ZR_E{=t_Jo`n}$rr~+^#sR#%=O6L*7k`Il=lo1RE-U>=a0I0hD+5Q;_ZgSL_6~L6mplIor>`8Q+}X!|^(_s4 zCxEE$lZSwbwdkVZI=r#}pD;MugJ&Q4KI+OBB;P@Xu9Uco4tYw+L#qHVp-kYSlY6Ra5vHNpT5|w3jyFhV8xvf{C*#t zPVq~gon&#X>l_nN9A;SWPJDLZpIL~ck!%h(k~~1Q{Dc74#$u{u^k4@|kpC?PJgw;N z@4@C(_hLcAQp()=W_+3&2nLMFx-!wM_$0*Q$Hlj&j=?bWdQ3C zs#X{f5D`sm>qoEr76%Dg|(pP;qpGLfArpN!PfPKb>P`KMZ{!ud`lHdjdt z^F)E`r3YN@0?~huP@-EV^4_j z@O<7ZiONjR%!l8@gz2KHt<1f#h`tT!%l?Tu=|%vgLs|GBm3Bje!!X!;G46B2JMN`? z)`xCZSnVC?!qCVlhCRJ-d%IY!tC!T=PdU-bdqvKdmukfU0a;6?iK7=@fMQ--&qWNm z-T30nAym2QP+wikG*%6)XWxk0iUv5#DzrmG2!mWd?i*p|sd2LRZuAUvvQkfncK_f= zFNVi^G0MH;4Gi%aCQTt$ym}(h{ztL0WecjApV7?3pW%o;r@!noT?v3BRfsaJQ%6rL zJ~{s?BL#ib;``}0K8#`3=5vqVp!+BnjYquMSgmPiO)7<#tV5ADGWv`mkXM-dV!(5Q z2f+~-dzi(xq#os^En4jcm!pOp0khiDgluJv`*1XapzIYKoz9mGo_J?|~bF4jHHDDn&FZ-4z2Be$DNbME{fue(eCLYI7UJk6N+s@jE3$=WPnVx6H34lyMbOALi z!f^kx?=g?LcyiJ8OQ{1gy|$|V0)l{~`VO&P;gcKx3JsOn3v9{`0iZ6xLLcv@$|cyb z?pc&rs#p&`-&0B#(C$$~EocZxh_l2&ten3ME1I^^<=2&Q**^iWtPlY42@ozo(nM`r z`WP#kE@oT+orN-3OCT{p^;bb4uOUEAIErypmNjC>nrGm&v)s0(o6io5oD~8PUjUXf zqC-QqZ86p^+=jY}WsLe#TtHrLPBplULmMs957l(v1Q>?Skt_n+dn6$m|0K2JAp-f1I#GF z;@WlCzWh0QJNDh_QuV!pKoLS924AS1fj&RL>Xy5h6{$9BTL0UDnH#brR)JVhOLDNP z={{^+_$(EKM(P9TAE26oKoLVgvwV@+ZLZmbyO)21%J3H2|AShRlw3?ZM+6`~RjDUe zURsGKwtOE|&UspcKb6YH04!TQF>v~*u$70YGIDFKj)3lXpw zZP?iSIMy$rJmf4#Fr@qga=9tgv-O97IKmmtJy_hZ9*?a0W35tBE>Hf%40BEZWFV3x z(qVOB`?9C8Y2lMF+XfI}0TI;{1d0O!28=Nw&eFQ4v18TKsC3llOy<8Go_Rq4WKa_K zTwlHzTbDkHO$+{rxlfr#Fn6NS?Ql|`6$EAt0dJrcOB=W2?iG(QLC!kv^&EKo++p;( zrv<;mbCUWx7z$uv%_@XeJjHb4ooMSjM;!qZ3eJ%{@JRtlT~t62puwAgK9yz5aX z=$7AQim$ujFxDWtsh@{9lo*Dp>&5<0vX&)oN4YGK5oDC>o*rXWy25RihU64Gd1SqdBIp4?)rW$> zWC#euDp`Fk78BMi-hxL~eqVEV6Ma@bX#FRTRo>^74&dhCSQUj{%4ODSY+m>%LcuT& zUWlQ0_%xFxEXYeh-WW6RP<$Ua4||GW<#mW#HSS!+-=1%W(+fY|yCreUmYc?R28K1GH0I?c^DKFsXI z_ooVyJSG6~sf40_)H#-5%aRB}F(3AwdV`u(shcLh=O<88T?BP~5kNo`)rJ6;G~SQ< zRz6Lo-#x67Em5xd-SScga4X0REHUn5mej@Aw&*ErUHO29{Hn;vtx!^*atHwp`GKm6 zay+r&U$PL+mV!Wjfmi_$fcV0S&vdP`2@kFKK2|SYPa+UuU3tIa0dlzS)uScjb%fG= z0Oc+lzIWeWqorm!qLKV@^qFam3Wfkk>O`Z#glZ;Fd}7_7;+|DcFvYkP-auzwDpzI# zy!yScAt1tMmQh7jM-dxlfB`>Jx2$p(y;OTDk%w05_)) z2zyamvPjFSVzHXB>+s7gJ{q9)P_(hiI&c$G>YaWNh)1VcEoDq?zZnm%eoiy>6@s?^ zPB17~0wCN#h&k1rmO5-%@Hi>59dGXc8G=DCHJcJZbf+n+?-c}Q2Z1OfRt?lcZD0R2 z+_&U8RF^hQvh&Zb&s^VCKuW-=K_w!A!&-s0bQ}J|2mc4uvaXsp+@|>oOnry?RzV;| z2=L(d2HW5$apGI|{xNne{e4uF)RWnaa|9{&mRFY>&YU?@__LU>0|cTTwD(`bXXoF* zvDV+f7_EiX%!oqO6a;1o0rBn|4|lMT%|lx0u4Rpzw7?#oJN#eFeTB_x7O)5~c}0Z} z0PLo6tZ3dyq_@FYQjL>s?_t#A=4Y4E3rq%=dOtG+c)CjtKZB`<4rtF&*Yp^c&s)z@ z_Z3=HU%Z3DMt+$?kr06RazvR{oMIY1u{OiysD#aK#Hp)?F+3K}E+q1sY6=1~K_G6* zi^1urz@qvccx2skSX{S8%T*yEy~WguL0uWy+LHJNOfF92U>^|`l&a}P5 zJnRiHvrM^a3Iex7K$Lp^a61;%-GfIqeHW|h?uU($r;@a`SX&VjfH)4l%7qqmDm8l(_O!5cw}bzf$EUcNv-K+nEMR{l4*cg2$|SW|-%vvY!HOmL1_ zA6CX6SX4TITcH)F+QJ1`3?-;5cVg?}M^RZ?hjX2u^E;v?}{D*ab=z#6=hA>TK8?NUAlp$zI#w=sbsZkU(wG$ zF~K=O0A#Wv$A#%RXkeo8dCoPc+qw`{SE_L8+Cg*`MZR9jQzxY0mT>1c! zFwKNTtnm3N1m@;9;piCFFMa?!RzHIkwc9m&|J;NGw|jAT0?uyvL_rvi1{p*YLbv-W z_Feup4xfLAdVvZ?89L|GNiaJI7RX)H;f13<1j7AT*t`bcSo_~$VclBR=PP3bX<^0h z>kaWaO#lR#LIjLPBh!xgwQ59NLpN~f%Imn;xd*IX&Nzb>MF{lXby@5$Kwpqh%#Haq z8}Q(o-$QfdN|vCTry)P30cwXL+8)T_kn-wLb4CYnC#Z{+ARGz9Y_OrJVkxR!4Kxu} zV_E%OINx~?S9j4ZduDOI}_GxQV5>9M*Ew zxaMKg%DZUSe+*7r4b8la5fNA3;&teoHN+JGm;rRMwc;mB1g*m-=|HrTro&xKJYuG@ zP*pCT0eqSNSu^W}$?kV!Y10nev+^5Q)AS(h^x`ubO>o4TqB!GECvB8n1;uD;U0CP%X1NzyFbUJt`Fe$0HxL{ znh=$HaSC8f?oyuglJn6Y?$fGSET~;aug=FXzj8gQ%BwXO?-GlX&OCF&g*Q2%&3u(2 z05ikrD_0015GNudl}+wo7rF*p(LK(qw$m?2E`Y*ERb|fcDEv6an}W z6tjCH#Yzm!Jzy|KFzoHX*^UFa)_xs>OhPj1@54BihT>jolxwtWMH8j8QBy-(^evKV zOO$r3vTHKIVXsDcNuw6Wv9Ng|<@~j1rVDQgMXpddhzKi2sWy8EC;~8h*nQw`<=;7>l4KmXKYp(EvpV;y~1}UW17cRMF;3X6FiLC6>-x zNi*IyEUa43ly6M!M!+R(_+ME7^+n1MPy`@lkR^9Rgt^Itg%uCOVQ&|%4Ian&wgb4* zc?3THDD?!@ns^`^o>U{bPK)g-Veuzy_oC3VQKQ#Vx1LD98S9!JK~33w!pWvNV8uDD zISQu?uDc=t83S&@Pe77O2H_8l!yoX&OXTkvyNc@r$8nX)!;XQgWMvtL$h~c4Qd5hm zlpCIoMn$;S#TGAIP$)9MnhtcZk4vYNb8jsUg1x2oehU zPc{7^pa_8eF#Sr83DH0-2vX&EJmkj6_#j5dM=<6c!+5}rL2oNMhtHv7a6g7T5xOCf zC{TB3GKhImPC-#RiZU_(DMP|WV z39Y;Q6&KtXMm4d=VsY>UuSKc72CkA?xa{>zv{Az>yJe^>uRwK09jcuTsI)h8%w}fa z5pBQpp^$#&-J|aniU8;f&sp^*h|4_2)CthMXp)j=eiDQ>42}1Y8|cO0NI!;0he;Sb zOkCpS@8SeAt~tX9;)sYL8E)hyr>EG$<9dZSd58~cGUp)gg{7YSU1I2yS?$CbJn`Lx z@-l7$HIqfRYYICli7mblR+EI++IXrvQ0{V}y1W*36-}sf%}14k^1RVbWapDLxnKPD zi$CR70;>7?R}p}GU73k-eTl+6+2y1i_IG0>(2ZXA4GemEs8<-LI59+V!mWuE{$PMe zMl911Jb}Q{*{z5@(fya--XsM#-wA<;f0BQNNHM=6<0HySojlE58ge`APO|n@^uM2v zy0QhD@4ZA;Chm+sb@H+M>UpPtPSy3{B-q4#^{64 zPYz*>brMIl>m?v2z+87y*IwLc1)+l>KcnLUJjKnhSw;311h;FhwBk-{vy{-@?qd2Z zCu~$s+RSu$mQ!C-Qc4~LqPd73G<`~_w8j}|PEV7GND7lvmu4LTDy}u_AW!dsq72j! zkfOrP)0utpgM5~=))^~D1=CuK@{Kl#`1eF^@j(#wTM%8EnBe5S+$VuEa>B=R9*9oC zNF^kZT9l9S;_nIFg*nKoNifT{tyt1pz%Epa_5-P*i;=2oxv;6agsEg;T>;5YPhx|39AG V_9gbHUhx0`002ovPDHLkV1k&E=!gIS literal 0 HcmV?d00001 diff --git a/packages/engine/Source/Scene/GltfLoader.js b/packages/engine/Source/Scene/GltfLoader.js index 527b49ff51ba..b46733d809e8 100644 --- a/packages/engine/Source/Scene/GltfLoader.js +++ b/packages/engine/Source/Scene/GltfLoader.js @@ -55,6 +55,7 @@ const { SpecularGlossiness, Specular, Anisotropy, + Clearcoat, Material, } = ModelComponents; @@ -1587,6 +1588,43 @@ function loadAnisotropy(loader, anisotropyInfo, frameState) { return anisotropy; } +function loadClearcoat(loader, clearcoatInfo, frameState) { + const { + clearcoatFactor = Clearcoat.DEFAULT_CLEARCOAT_FACTOR, + clearcoatTexture, + clearcoatRoughnessFactor = Clearcoat.DEFAULT_CLEARCOAT_ROUGHNESS_FACTOR, + clearcoatRoughnessTexture, + clearcoatNormalTexture, + } = clearcoatInfo; + + const clearcoat = new Clearcoat(); + if (defined(clearcoatTexture)) { + clearcoat.clearcoatTexture = loadTexture( + loader, + clearcoatTexture, + frameState + ); + } + if (defined(clearcoatRoughnessTexture)) { + clearcoat.clearcoatRoughnessTexture = loadTexture( + loader, + clearcoatRoughnessTexture, + frameState + ); + } + if (defined(clearcoatNormalTexture)) { + clearcoat.clearcoatNormalTexture = loadTexture( + loader, + clearcoatNormalTexture, + frameState + ); + } + clearcoat.clearcoatFactor = clearcoatFactor; + clearcoat.clearcoatRoughnessFactor = clearcoatRoughnessFactor; + + return clearcoat; +} + /** * Load textures and parse factors and flags for a glTF material * @@ -1606,6 +1644,7 @@ function loadMaterial(loader, gltfMaterial, frameState) { const pbrSpecularGlossiness = extensions.KHR_materials_pbrSpecularGlossiness; const pbrSpecular = extensions.KHR_materials_specular; const pbrAnisotropy = extensions.KHR_materials_anisotropy; + const pbrClearcoat = extensions.KHR_materials_clearcoat; const pbrMetallicRoughness = gltfMaterial.pbrMetallicRoughness; material.unlit = defined(extensions.KHR_materials_unlit); @@ -1630,6 +1669,9 @@ function loadMaterial(loader, gltfMaterial, frameState) { if (defined(pbrAnisotropy) && material.unlit === false) { material.anisotropy = loadAnisotropy(loader, pbrAnisotropy, frameState); } + if (defined(pbrClearcoat) && material.unlit === false) { + material.clearcoat = loadClearcoat(loader, pbrClearcoat, frameState); + } } // Top level textures diff --git a/packages/engine/Source/Scene/Model/MaterialPipelineStage.js b/packages/engine/Source/Scene/Model/MaterialPipelineStage.js index 85765fec3e66..1f4f533a98cf 100644 --- a/packages/engine/Source/Scene/Model/MaterialPipelineStage.js +++ b/packages/engine/Source/Scene/Model/MaterialPipelineStage.js @@ -17,6 +17,7 @@ const { MetallicRoughness, SpecularGlossiness, Specular, + Clearcoat, } = ModelComponents; /** @@ -120,6 +121,18 @@ MaterialPipelineStage.process = function ( disableTextures ); } + if ( + defined(material.clearcoat) && + ModelUtility.supportedExtensions.KHR_materials_clearcoat === true + ) { + processClearcoatUniforms( + material.clearcoat, + uniformMap, + shaderBuilder, + defaultTexture, + disableTextures + ); + } processMetallicRoughnessUniforms( material.metallicRoughness, uniformMap, @@ -597,6 +610,109 @@ function processAnisotropyUniforms( }; } +/** + * Add uniforms and defines for the KHR_materials_clearcoat extension + * + * @param {ModelComponents.Clearcoat} clearcoat + * @param {Object} uniformMap The uniform map to modify. + * @param {ShaderBuilder} shaderBuilder + * @param {Texture} defaultTexture + * @param {boolean} disableTextures + * @private + */ +function processClearcoatUniforms( + clearcoat, + uniformMap, + shaderBuilder, + defaultTexture, + disableTextures +) { + const { + clearcoatFactor, + clearcoatTexture, + clearcoatRoughnessFactor, + clearcoatRoughnessTexture, + clearcoatNormalTexture, + } = clearcoat; + + shaderBuilder.addDefine( + "USE_CLEARCOAT", + undefined, + ShaderDestination.FRAGMENT + ); + + if ( + defined(clearcoatFactor) && + clearcoatFactor !== Clearcoat.DEFAULT_CLEARCOAT_FACTOR + ) { + shaderBuilder.addUniform( + "float", + "u_clearcoatFactor", + ShaderDestination.FRAGMENT + ); + uniformMap.u_clearcoatFactor = function () { + return clearcoat.clearcoatFactor; + }; + shaderBuilder.addDefine( + "HAS_CLEARCOAT_FACTOR", + undefined, + ShaderDestination.FRAGMENT + ); + } + + if (defined(clearcoatTexture) && !disableTextures) { + processTexture( + shaderBuilder, + uniformMap, + clearcoatTexture, + "u_clearcoatTexture", + "CLEARCOAT", + defaultTexture + ); + } + + if ( + defined(clearcoatRoughnessFactor) && + clearcoatFactor !== Clearcoat.DEFAULT_CLEARCOAT_ROUGHNESS_FACTOR + ) { + shaderBuilder.addUniform( + "float", + "u_clearcoatRoughnessFactor", + ShaderDestination.FRAGMENT + ); + uniformMap.u_clearcoatRoughnessFactor = function () { + return clearcoat.clearcoatRoughnessFactor; + }; + shaderBuilder.addDefine( + "HAS_CLEARCOAT_ROUGHNESS_FACTOR", + undefined, + ShaderDestination.FRAGMENT + ); + } + + if (defined(clearcoatRoughnessTexture) && !disableTextures) { + processTexture( + shaderBuilder, + uniformMap, + clearcoatRoughnessTexture, + "u_clearcoatRoughnessTexture", + "CLEARCOAT_ROUGHNESS", + defaultTexture + ); + } + + if (defined(clearcoatNormalTexture) && !disableTextures) { + processTexture( + shaderBuilder, + uniformMap, + clearcoatNormalTexture, + "u_clearcoatNormalTexture", + "CLEARCOAT_NORMAL", + defaultTexture + ); + } +} + /** * Add uniforms and defines for the PBR metallic roughness model * diff --git a/packages/engine/Source/Scene/ModelComponents.js b/packages/engine/Source/Scene/ModelComponents.js index 6eaec0918c2d..c81792038073 100644 --- a/packages/engine/Source/Scene/ModelComponents.js +++ b/packages/engine/Source/Scene/ModelComponents.js @@ -1406,7 +1406,7 @@ function Anisotropy() { this.anisotropyRotation = Anisotropy.DEFAULT_ANISOTROPY_ROTATION; /** - * The anisotropy texture reader + * The anisotropy texture reader. * * @type {ModelComponents.TextureReader} * @private @@ -1424,6 +1424,60 @@ Anisotropy.DEFAULT_ANISOTROPY_STRENGTH = 0.0; */ Anisotropy.DEFAULT_ANISOTROPY_ROTATION = 0.0; +function Clearcoat() { + /** + * The clearcoat layer intensity. + * + * @type {number} + * @default 0.0 + * @private + */ + this.clearcoatFactor = Clearcoat.DEFAULT_CLEARCOAT_FACTOR; + + /** + * The clearcoat layer intensity texture reader. + * + * @type {ModelComponents.TextureReader} + * @private + */ + this.clearcoatTexture = undefined; + + /** + * The clearcoat layer roughness. + * + * @type {number} + * @default 0.0 + * @private + */ + this.clearcoatRoughnessFactor = Clearcoat.DEFAULT_CLEARCOAT_ROUGHNESS_FACTOR; + + /** + * The clearcoat layer roughness texture. + * + * @type {ModelComponents.TextureReader} + * @private + */ + this.clearcoatRoughnessTexture = undefined; + + /** + * The clearcoat normal map texture. + * + * @type {ModelComponents.TextureReader} + * @private + */ + this.clearcoatNormalTexture = undefined; +} + +/** + * @private + */ +Clearcoat.DEFAULT_CLEARCOAT_FACTOR = 0.0; + +/** + * @private + */ +Clearcoat.DEFAULT_CLEARCOAT_ROUGHNESS_FACTOR = 0.0; + /** * The material appearance of a primitive. * @@ -1458,13 +1512,21 @@ function Material() { this.specular = undefined; /** - * Material properties for the PBR anisotropy shading model + * Material properties for the PBR anisotropy shading model. * - * @type {ModelComponents.anisotropy} + * @type {ModelComponents.Anisotropy} * @private */ this.anisotropy = undefined; + /** + * Material properties for the PBR clearcoat shading model. + * + * @type {ModelComponents.Clearcoat} + * @private + */ + this.clearcoat = undefined; + /** * The emissive texture reader. * @@ -1566,6 +1628,7 @@ ModelComponents.MetallicRoughness = MetallicRoughness; ModelComponents.SpecularGlossiness = SpecularGlossiness; ModelComponents.Specular = Specular; ModelComponents.Anisotropy = Anisotropy; +ModelComponents.Clearcoat = Clearcoat; ModelComponents.Material = Material; export default ModelComponents; diff --git a/packages/engine/Specs/Scene/GltfLoaderSpec.js b/packages/engine/Specs/Scene/GltfLoaderSpec.js index d976d1ec22c3..cfcab67f19d3 100644 --- a/packages/engine/Specs/Scene/GltfLoaderSpec.js +++ b/packages/engine/Specs/Scene/GltfLoaderSpec.js @@ -126,6 +126,8 @@ describe( "./Data/Models/glTF-2.0/BoxSpecular/glTF/BoxSpecular.gltf"; const anisotropyTestData = "./Data/Models/glTF-2.0/BoxAnisotropy/glTF/BoxAnisotropy.gltf"; + const clearcoatTestData = + "./Data/Models/glTF-2.0/BoxClearcoat/glTF/BoxClearcoat.gltf"; let scene; const gltfLoaders = []; @@ -4170,6 +4172,25 @@ describe( expect(anisotropyTexture.texture.width).toBe(256); }); + it("loads model with KHR_materials_clearcoat extension", async function () { + const gltfLoader = await loadGltf(clearcoatTestData); + + const { material } = gltfLoader.components.nodes[1].primitives[0]; + const { + clearcoatFactor, + clearcoatTexture, + clearcoatRoughnessFactor, + clearcoatRoughnessTexture, + clearcoatNormalTexture, + } = material.clearcoat; + + expect(clearcoatFactor).toBe(0.5); + expect(clearcoatTexture.texture.width).toBe(256); + expect(clearcoatRoughnessFactor).toBe(0.2); + expect(clearcoatRoughnessTexture.texture.width).toBe(256); + expect(clearcoatNormalTexture.texture.width).toBe(256); + }); + it("parses copyright field", function () { return loadGltf(boxWithCredits).then(function (gltfLoader) { const components = gltfLoader.components; diff --git a/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js index 1e42b3d0b16d..61355d8e7713 100644 --- a/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js @@ -89,6 +89,8 @@ describe( "./Data/Models/glTF-2.0/BoxSpecular/glTF/BoxSpecular.gltf"; const anisotropyTestData = "./Data/Models/glTF-2.0/BoxAnisotropy/glTF/BoxAnisotropy.gltf"; + const clearcoatTestData = + "./Data/Models/glTF-2.0/BoxClearcoat/glTF/BoxClearcoat.gltf"; function expectUniformMap(uniformMap, expected) { for (const key in expected) { @@ -523,6 +525,59 @@ describe( expectUniformMap(uniformMap, expectedUniforms); }); + it("adds uniforms and defines for KHR_materials_clearcoat", async function () { + const gltfLoader = await loadGltf(clearcoatTestData); + + const primitive = gltfLoader.components.nodes[1].primitives[0]; + const renderResources = mockRenderResources(); + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + const { shaderBuilder, uniformMap } = renderResources; + + ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform float u_metallicFactor;", + "uniform float u_clearcoatFactor;", + "uniform float u_clearcoatRoughnessFactor;", + "uniform sampler2D u_baseColorTexture;", + "uniform sampler2D u_clearcoatTexture;", + "uniform sampler2D u_clearcoatRoughnessTexture;", + "uniform sampler2D u_clearcoatNormalTexture;", + ]); + + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_BASE_COLOR_TEXTURE", + "HAS_CLEARCOAT_FACTOR", + "HAS_CLEARCOAT_NORMAL_TEXTURE", + "HAS_CLEARCOAT_ROUGHNESS_FACTOR", + "HAS_CLEARCOAT_ROUGHNESS_TEXTURE", + "HAS_CLEARCOAT_TEXTURE", + "HAS_METALLIC_FACTOR", + "TEXCOORD_CLEARCOAT v_texCoord_0", + "TEXCOORD_CLEARCOAT_ROUGHNESS v_texCoord_0", + "TEXCOORD_CLEARCOAT_NORMAL v_texCoord_0", + "TEXCOORD_BASE_COLOR v_texCoord_0", + "USE_CLEARCOAT", + "USE_METALLIC_ROUGHNESS", + ]); + + const { + clearcoatFactor, + clearcoatRoughnessFactor, + clearcoatTexture, + clearcoatRoughnessTexture, + clearcoatNormalTexture, + } = primitive.material.clearcoat; + const expectedUniforms = { + u_clearcoatFactor: clearcoatFactor, + u_clearcoatRoughnessFactor: clearcoatRoughnessFactor, + u_clearcoatTexture: clearcoatTexture.texture, + u_clearcoatRoughnessTexture: clearcoatRoughnessTexture.texture, + u_clearcoatNormalTexture: clearcoatNormalTexture.texture, + }; + expectUniformMap(uniformMap, expectedUniforms); + }); + it("doesn't add texture uniforms for classification models", function () { return loadGltf(boomBox).then(function (gltfLoader) { const components = gltfLoader.components; From 8f21b065a902f77d2bbf9fb31a4cbd0f3cebf8dd Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Sat, 18 May 2024 20:05:10 -0400 Subject: [PATCH 03/21] Process KHR_materials_clearcoat data in shader (WIP) --- .../Builtin/Structs/modelMaterial.glsl | 6 +++ .../Source/Shaders/Model/MaterialStageFS.glsl | 47 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl b/packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl index 0a61b46b1b8e..fc286607cf8a 100644 --- a/packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl +++ b/packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl @@ -34,4 +34,10 @@ struct czm_modelMaterial { vec3 anisotropicB; float anisotropyStrength; #endif +#ifdef USE_CLEARCOAT + float clearcoatFactor; + float clearcoatRoughness; + vec3 clearcoatNormal; + // TODO: Add clearcoatF0 when KHR_materials_ior is implemented +#endif }; diff --git a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl index b48aef027cea..9033139f6d52 100644 --- a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl @@ -337,6 +337,50 @@ void setAnisotropy(inout czm_modelMaterial material, in NormalInfo normalInfo) material.anisotropyStrength = anisotropyStrength; } #endif +#ifdef USE_CLEARCOAT +void setClearcoat(inout czm_modelMaterial material) +{ + #ifdef HAS_CLEARCOAT_TEXTURE + vec2 clearcoatTexCoords = TEXCOORD_CLEARCOAT; + #ifdef HAS_CLEARCOAT_TEXTURE_TRANSFORM + clearcoatTexCoords = computeTextureTransform(clearcoatTexCoords, u_clearcoatTextureTransform); + #endif + float clearcoatFactor = texture(u_clearcoatTexture, clearcoatTexCoords).a; + #ifdef HAS_CLEARCOAT_FACTOR + clearcoatFactor *= u_clearcoatFactor; + #endif + #else + #ifdef HAS_CLEARCOAT_FACTOR + float clearcoatFactor = u_clearcoatFactor; + #else + // TODO: this case should turn the whole extension off + float clearcoatFactor = 0.0; + #endif + #endif + + #ifdef HAS_CLEARCOAT_ROUGHNESS_TEXTURE + vec2 clearcoatRoughnessTexCoords = TEXCOORD_CLEARCOAT_ROUGHNESS; + #ifdef HAS_CLEARCOAT_ROUGHNESS_TEXTURE_TRANSFORM + clearcoatRoughnessTexCoords = computeTextureTransform(clearcoatRoughnessTexCoords, u_clearcoatRoughnessTextureTransform); + #endif + float clearcoatRoughness = texture(u_clearcoatRoughnessTexture, clearcoatRoughnessTexCoords).a; + #ifdef HAS_CLEARCOAT_ROUGHNESS_FACTOR + clearcoatRoughness *= u_clearcoatRoughnessFactor; + #endif + #else + #ifdef HAS_CLEARCOAT_ROUGHNESS_FACTOR + float clearcoatRoughness = u_clearcoatRoughnessFactor; + #else + float clearcoatRoughness = 0.0; + #endif + #endif + + material.clearcoatFactor = clearcoatFactor; + material.clearcoatRoughness = clearcoatRoughness; + // TODO: read clear coat normals from a texture (if supplied) + material.clearcoatNormal = material.normalEC; +} +#endif #endif void materialStage(inout czm_modelMaterial material, ProcessedAttributes attributes, SelectedFeature feature) @@ -398,5 +442,8 @@ void materialStage(inout czm_modelMaterial material, ProcessedAttributes attribu #ifdef USE_ANISOTROPY setAnisotropy(material, normalInfo); #endif + #ifdef USE_CLEARCOAT + setClearcoat(material); + #endif #endif } From c4da3f4bfa9b8cb77d137cc1455176b7f7ddf1c9 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 21 May 2024 05:00:35 -0400 Subject: [PATCH 04/21] Simplify PBR lighting shaders --- .../Builtin/Functions/defaultPbrMaterial.glsl | 18 -------- .../Builtin/Functions/pbrLighting.glsl | 41 ++++++++---------- .../pbrMetallicRoughnessMaterial.glsl | 37 ---------------- .../pbrSpecularGlossinessMaterial.glsl | 30 ------------- .../Builtin/Structs/modelMaterial.glsl | 2 + .../Builtin/Structs/pbrParameters.glsl | 24 ----------- .../Model/ImageBasedLightingStageFS.glsl | 40 ++++++++--------- .../Source/Shaders/Model/LightingStageFS.glsl | 17 +------- .../Source/Shaders/Model/MaterialStageFS.glsl | 43 +++++++++++-------- 9 files changed, 67 insertions(+), 185 deletions(-) delete mode 100644 packages/engine/Source/Shaders/Builtin/Functions/defaultPbrMaterial.glsl delete mode 100644 packages/engine/Source/Shaders/Builtin/Functions/pbrMetallicRoughnessMaterial.glsl delete mode 100644 packages/engine/Source/Shaders/Builtin/Functions/pbrSpecularGlossinessMaterial.glsl delete mode 100644 packages/engine/Source/Shaders/Builtin/Structs/pbrParameters.glsl diff --git a/packages/engine/Source/Shaders/Builtin/Functions/defaultPbrMaterial.glsl b/packages/engine/Source/Shaders/Builtin/Functions/defaultPbrMaterial.glsl deleted file mode 100644 index 52ebc925c7fe..000000000000 --- a/packages/engine/Source/Shaders/Builtin/Functions/defaultPbrMaterial.glsl +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Get default parameters for physically based rendering. These defaults - * describe a rough dielectric (non-metal) surface (e.g. rough plastic). - * - * @return {czm_pbrParameters} Default parameters for {@link czm_pbrLighting} - */ -czm_pbrParameters czm_defaultPbrMaterial() -{ - czm_pbrParameters results; - results.diffuseColor = vec3(1.0); - results.roughness = 1.0; -#if defined(USE_SPECULAR) - results.specularWeight = 1.0; -#endif - const vec3 REFLECTANCE_DIELECTRIC = vec3(0.04); - results.f0 = REFLECTANCE_DIELECTRIC; - return results; -} diff --git a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl index 467423b1cbd8..4eeeca5b4033 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl @@ -48,6 +48,10 @@ float smithVisibilityG1(float NdotV, float roughness) return NdotV / (NdotV * (1.0 - k) + k); } +/** + * geometric shadowing function + * TODO: explain + */ float smithVisibilityGGX(float roughness, float NdotL, float NdotV) { return ( @@ -56,6 +60,10 @@ float smithVisibilityGGX(float roughness, float NdotL, float NdotV) ) / (4.0 * NdotL * NdotV); } +/** + * microfacet distribution function + * TODO: explain + */ float GGX(float roughness, float NdotH) { float roughnessSquared = roughness * roughness; @@ -69,38 +77,25 @@ float GGX(float roughness, float NdotH) * rendering. This function only handles direct lighting. *

* This function only handles the lighting calculations. Metallic/roughness - * and specular/glossy must be handled separately. See {@czm_pbrMetallicRoughnessMaterial}, {@czm_pbrSpecularGlossinessMaterial} and {@czm_defaultPbrMaterial} + * and specular/glossy must be handled separately. See {@MaterialStageFS} *

* - * @name czm_pbrlighting + * @name czm_pbrLighting * @glslFunction * * @param {vec3} positionEC The position of the fragment in eye coordinates * @param {vec3} normalEC The surface normal in eye coordinates * @param {vec3} lightDirectionEC Unit vector pointing to the light source in eye coordinates. * @param {vec3} lightColorHdr radiance of the light source. This is a HDR value. - * @param {czm_pbrParameters} The computed PBR parameters. + * @param {czm_modelMaterial} The material properties. * @return {vec3} The computed HDR color - * - * @example - * czm_pbrParameters pbrParameters = czm_pbrMetallicRoughnessMaterial( - * baseColor, - * metallic, - * roughness - * ); - * vec3 color = czm_pbrlighting( - * positionEC, - * normalEC, - * lightDirectionEC, - * lightColorHdr, - * pbrParameters); */ vec3 czm_pbrLighting( vec3 positionEC, vec3 normalEC, vec3 lightDirectionEC, vec3 lightColorHdr, - czm_pbrParameters pbrParameters + czm_modelMaterial material ) { vec3 v = -normalize(positionEC); @@ -109,7 +104,7 @@ vec3 czm_pbrLighting( vec3 n = normalEC; float VdotH = clamp(dot(v, h), 0.0, 1.0); - vec3 f0 = pbrParameters.f0; + vec3 f0 = material.specular; float reflectance = max(max(f0.r, f0.g), f0.b); // Typical dielectrics will have reflectance 0.04, so f90 will be 1.0. // In this case, at grazing angle, all incident energy is reflected. @@ -117,16 +112,16 @@ vec3 czm_pbrLighting( vec3 F = fresnelSchlick2(f0, f90, VdotH); #if defined(USE_SPECULAR) - F *= pbrParameters.specularWeight; + F *= material.specularWeight; #endif - float alpha = pbrParameters.roughness; + float alpha = material.roughness; #ifdef USE_ANISOTROPY - mat3 tbn = mat3(pbrParameters.anisotropicT, pbrParameters.anisotropicB, n); + mat3 tbn = mat3(material.anisotropicT, material.anisotropicB, n); vec3 lightDirection = l * tbn; vec3 viewDirection = v * tbn; vec3 halfwayDirection = h * tbn; - float anisotropyStrength = pbrParameters.anisotropyStrength; + float anisotropyStrength = material.anisotropyStrength; float tangentialRoughness = mix(alpha, 1.0, anisotropyStrength * anisotropyStrength); float G = smithVisibilityGGX_anisotropic(alpha, tangentialRoughness, lightDirection, viewDirection); float D = GGX_anisotropic(alpha, tangentialRoughness, halfwayDirection); @@ -141,7 +136,7 @@ vec3 czm_pbrLighting( vec3 specularContribution = F * G * D; - vec3 diffuseColor = pbrParameters.diffuseColor; + vec3 diffuseColor = material.diffuse; // F here represents the specular contribution vec3 diffuseContribution = (1.0 - F) * lambertianDiffuse(diffuseColor); diff --git a/packages/engine/Source/Shaders/Builtin/Functions/pbrMetallicRoughnessMaterial.glsl b/packages/engine/Source/Shaders/Builtin/Functions/pbrMetallicRoughnessMaterial.glsl deleted file mode 100644 index 4ae262e867cb..000000000000 --- a/packages/engine/Source/Shaders/Builtin/Functions/pbrMetallicRoughnessMaterial.glsl +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Compute parameters for physically based rendering using the - * metallic/roughness workflow. All inputs are linear; sRGB texture values must - * be decoded beforehand - * - * @name czm_pbrMetallicRoughnessMaterial - * @glslFunction - * - * @param {vec3} baseColor For dielectrics, this is the base color. For metals, this is the f0 value (reflectance at normal incidence) - * @param {float} metallic 0.0 indicates dielectric. 1.0 indicates metal. Values in between are allowed (e.g. to model rust or dirt); - * @param {float} roughness A value between 0.0 and 1.0 - * @return {czm_pbrParameters} parameters to pass into {@link czm_pbrLighting} - */ -czm_pbrParameters czm_pbrMetallicRoughnessMaterial( - vec3 baseColor, - float metallic, - float roughness -) -{ - czm_pbrParameters results; - - // roughness is authored as perceptual roughness - // square it to get material roughness - roughness = clamp(roughness, 0.0, 1.0); - results.roughness = roughness * roughness; - - // dielectrics use f0 = 0.04, metals use albedo as f0 - metallic = clamp(metallic, 0.0, 1.0); - const vec3 REFLECTANCE_DIELECTRIC = vec3(0.04); - vec3 f0 = mix(REFLECTANCE_DIELECTRIC, baseColor, metallic); - results.f0 = f0; - - // diffuse only applies to dielectrics. - results.diffuseColor = baseColor * (1.0 - f0) * (1.0 - metallic); - - return results; -} diff --git a/packages/engine/Source/Shaders/Builtin/Functions/pbrSpecularGlossinessMaterial.glsl b/packages/engine/Source/Shaders/Builtin/Functions/pbrSpecularGlossinessMaterial.glsl deleted file mode 100644 index 01dc7a5102a3..000000000000 --- a/packages/engine/Source/Shaders/Builtin/Functions/pbrSpecularGlossinessMaterial.glsl +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Compute parameters for physically based rendering using the - * specular/glossy workflow. All inputs are linear; sRGB texture values must - * be decoded beforehand - * - * @name czm_pbrSpecularGlossinessMaterial - * @glslFunction - * - * @param {vec3} diffuse The diffuse color for dielectrics (non-metals) - * @param {vec3} specular The reflectance at normal incidence (f0) - * @param {float} glossiness A number from 0.0 to 1.0 indicating how smooth the surface is. - * @return {czm_pbrParameters} parameters to pass into {@link czm_pbrLighting} - */ -czm_pbrParameters czm_pbrSpecularGlossinessMaterial( - vec3 diffuse, - vec3 specular, - float glossiness -) -{ - czm_pbrParameters results; - - // glossiness is the opposite of roughness, but easier for artists to use. - float roughness = 1.0 - glossiness; - results.roughness = roughness * roughness; - - results.diffuseColor = diffuse * (1.0 - max(max(specular.r, specular.g), specular.b)); - results.f0 = specular; - - return results; -} diff --git a/packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl b/packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl index fc286607cf8a..af5dac2ad51d 100644 --- a/packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl +++ b/packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl @@ -10,6 +10,8 @@ * @name czm_modelMaterial * @glslStruct * + * TODO: is this used externally? Can we rename diffuse and specular? + * * @property {vec3} diffuse Incoming light that scatters evenly in all directions. * @property {float} alpha Alpha of this material. 0.0 is completely transparent; 1.0 is completely opaque. * @property {vec3} specular Color of reflected light at normal incidence in PBR materials. This is sometimes referred to as f0 in the literature. diff --git a/packages/engine/Source/Shaders/Builtin/Structs/pbrParameters.glsl b/packages/engine/Source/Shaders/Builtin/Structs/pbrParameters.glsl deleted file mode 100644 index 31ed199d9133..000000000000 --- a/packages/engine/Source/Shaders/Builtin/Structs/pbrParameters.glsl +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Parameters for {@link czm_pbrLighting} - * - * @name czm_material - * @glslStruct - * - * @property {vec3} diffuseColor the diffuse color of the material for the lambert term of the rendering equation - * @property {float} roughness a value from 0.0 to 1.0 that indicates how rough the surface of the material is. - * @property {vec3} f0 The reflectance of the material at normal incidence - */ -struct czm_pbrParameters -{ - vec3 diffuseColor; - float roughness; - vec3 f0; -#ifdef USE_SPECULAR - float specularWeight; -#endif -#ifdef USE_ANISOTROPY - vec3 anisotropicT; - vec3 anisotropicB; - float anisotropyStrength; -#endif -}; diff --git a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl index e50d3eb45f8a..c6d9e76e8cbd 100644 --- a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl @@ -3,7 +3,7 @@ vec3 proceduralIBL( vec3 normalEC, vec3 lightDirectionEC, vec3 lightColorHdr, - czm_pbrParameters pbrParameters + czm_modelMaterial material ) { vec3 v = -normalize(positionEC); vec3 positionWC = vec3(czm_inverseView * vec4(positionEC, 1.0)); @@ -24,9 +24,9 @@ vec3 proceduralIBL( r = -normalize(czm_temeToPseudoFixed * r); r.x = -r.x; - vec3 diffuseColor = pbrParameters.diffuseColor; - float roughness = pbrParameters.roughness; - vec3 f0 = pbrParameters.f0; + vec3 diffuseColor = material.diffuse; + float roughness = material.roughness; + vec3 f0 = material.specular; float inverseRoughness = 1.04 - roughness; inverseRoughness *= inverseRoughness; @@ -71,7 +71,7 @@ vec3 proceduralIBL( vec3 specularColor = czm_srgbToLinear(f0 * brdfLut.x + brdfLut.y); vec3 specularContribution = specularIrradiance * specularColor * model_iblFactor.y; #ifdef USE_SPECULAR - specularContribution *= pbrParameters.specularWeight; + specularContribution *= material.specularWeight; #endif vec3 diffuseContribution = diffuseIrradiance * diffuseColor * model_iblFactor.x; vec3 iblColor = specularContribution + diffuseContribution; @@ -87,14 +87,14 @@ vec3 proceduralIBL( } #ifdef DIFFUSE_IBL -vec3 computeDiffuseIBL(czm_pbrParameters pbrParameters, vec3 cubeDir) +vec3 computeDiffuseIBL(czm_modelMaterial material, vec3 cubeDir) { #ifdef CUSTOM_SPHERICAL_HARMONICS vec3 diffuseIrradiance = czm_sphericalHarmonics(cubeDir, model_sphericalHarmonicCoefficients); #else vec3 diffuseIrradiance = czm_sphericalHarmonics(cubeDir, czm_sphericalHarmonicCoefficients); #endif - return diffuseIrradiance * pbrParameters.diffuseColor; + return diffuseIrradiance * material.diffuse; } #endif @@ -111,10 +111,10 @@ vec3 sampleSpecularEnvironment(vec3 cubeDir, float roughness) return czm_sampleOctahedralProjection(czm_specularEnvironmentMaps, czm_specularEnvironmentMapSize, cubeDir, lod, maxLod); #endif } -vec3 computeSpecularIBL(czm_pbrParameters pbrParameters, vec3 cubeDir, float NdotV, float VdotH) +vec3 computeSpecularIBL(czm_modelMaterial material, vec3 cubeDir, float NdotV, float VdotH) { - float roughness = pbrParameters.roughness; - vec3 f0 = pbrParameters.f0; + float roughness = material.roughness; + vec3 f0 = material.specular; float reflectance = max(max(f0.r, f0.g), f0.b); vec3 f90 = vec3(clamp(reflectance * 25.0, 0.0, 1.0)); @@ -125,7 +125,7 @@ vec3 computeSpecularIBL(czm_pbrParameters pbrParameters, vec3 cubeDir, float Ndo specularIBL *= F * brdfLut.x + brdfLut.y; #ifdef USE_SPECULAR - specularIBL *= pbrParameters.specularWeight; + specularIBL *= material.specularWeight; #endif return f0 * specularIBL; @@ -137,7 +137,7 @@ vec3 textureIBL( vec3 positionEC, vec3 normalEC, vec3 lightDirectionEC, - czm_pbrParameters pbrParameters + czm_modelMaterial material ) { vec3 v = -normalize(positionEC); vec3 n = normalEC; @@ -157,16 +157,16 @@ vec3 textureIBL( vec3 cubeDir = normalize(cubeDirTransform * normalize(reflect(-v, n))); #ifdef DIFFUSE_IBL - vec3 diffuseContribution = computeDiffuseIBL(pbrParameters, cubeDir); + vec3 diffuseContribution = computeDiffuseIBL(material, cubeDir); #else vec3 diffuseContribution = vec3(0.0); #endif #ifdef USE_ANISOTROPY // Update environment map sampling direction to account for anisotropic distortion of specular reflection - float roughness = pbrParameters.roughness; - vec3 anisotropyDirection = pbrParameters.anisotropicB; - float anisotropyStrength = pbrParameters.anisotropyStrength; + float roughness = material.roughness; + vec3 anisotropyDirection = material.anisotropicB; + float anisotropyStrength = material.anisotropyStrength; vec3 anisotropicTangent = cross(anisotropyDirection, v); vec3 anisotropicNormal = cross(anisotropicTangent, anisotropyDirection); @@ -177,7 +177,7 @@ vec3 textureIBL( #endif #ifdef SPECULAR_IBL - vec3 specularContribution = computeSpecularIBL(pbrParameters, cubeDir, NdotV, VdotH); + vec3 specularContribution = computeSpecularIBL(material, cubeDir, NdotV, VdotH); #else vec3 specularContribution = vec3(0.0); #endif @@ -191,7 +191,7 @@ vec3 imageBasedLightingStage( vec3 normalEC, vec3 lightDirectionEC, vec3 lightColorHdr, - czm_pbrParameters pbrParameters + czm_modelMaterial material ) { #if defined(DIFFUSE_IBL) || defined(SPECULAR_IBL) // Environment maps were provided, use them for IBL @@ -199,7 +199,7 @@ vec3 imageBasedLightingStage( positionEC, normalEC, lightDirectionEC, - pbrParameters + material ); #else // Use the procedural IBL if there are no environment maps @@ -208,7 +208,7 @@ vec3 imageBasedLightingStage( normalEC, lightDirectionEC, lightColorHdr, - pbrParameters + material ); #endif } \ No newline at end of file diff --git a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl index 49b07b660724..1da272a69790 100644 --- a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl @@ -1,19 +1,6 @@ #ifdef LIGHTING_PBR vec3 computePbrLighting(czm_modelMaterial inputMaterial, ProcessedAttributes attributes) { - czm_pbrParameters pbrParameters; - pbrParameters.diffuseColor = inputMaterial.diffuse; - pbrParameters.f0 = inputMaterial.specular; - pbrParameters.roughness = inputMaterial.roughness; - #ifdef USE_SPECULAR - pbrParameters.specularWeight = inputMaterial.specularWeight; - #endif - #ifdef USE_ANISOTROPY - pbrParameters.anisotropicT = inputMaterial.anisotropicT; - pbrParameters.anisotropicB = inputMaterial.anisotropicB; - pbrParameters.anisotropyStrength = inputMaterial.anisotropyStrength; - #endif - #ifdef USE_CUSTOM_LIGHT_COLOR vec3 lightColorHdr = model_lightColorHdr; #else @@ -27,7 +14,7 @@ vec3 computePbrLighting(czm_modelMaterial inputMaterial, ProcessedAttributes att inputMaterial.normalEC, czm_lightDirectionEC, lightColorHdr, - pbrParameters + inputMaterial ); #ifdef USE_IBL_LIGHTING @@ -36,7 +23,7 @@ vec3 computePbrLighting(czm_modelMaterial inputMaterial, ProcessedAttributes att inputMaterial.normalEC, czm_lightDirectionEC, lightColorHdr, - pbrParameters + inputMaterial ); #endif #endif diff --git a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl index 9033139f6d52..20d171399917 100644 --- a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl @@ -213,17 +213,17 @@ void setSpecularGlossiness(inout czm_modelMaterial material) #else vec4 diffuse = vec4(1.0); #endif - czm_pbrParameters parameters = czm_pbrSpecularGlossinessMaterial( - diffuse.rgb, - specular, - glossiness - ); - material.diffuse = parameters.diffuseColor; + + material.diffuse = diffuse.rgb * (1.0 - max(max(specular.r, specular.g), specular.b)); // the specular glossiness extension's alpha overrides anything set // by the base material. material.alpha = diffuse.a; - material.specular = parameters.f0; - material.roughness = parameters.roughness; + + material.specular = specular; + + // glossiness is the opposite of roughness, but easier for artists to use. + float roughness = 1.0 - glossiness; + material.roughness = roughness * roughness; } #elif defined(LIGHTING_PBR) float setMetallicRoughness(inout czm_modelMaterial material) @@ -236,13 +236,14 @@ float setMetallicRoughness(inout czm_modelMaterial material) vec3 metallicRoughness = texture(u_metallicRoughnessTexture, metallicRoughnessTexCoords).rgb; float metalness = clamp(metallicRoughness.b, 0.0, 1.0); + // TODO: WHY is roughness clamped to 0.04 ?? float roughness = clamp(metallicRoughness.g, 0.04, 1.0); #ifdef HAS_METALLIC_FACTOR - metalness *= u_metallicFactor; + metalness = clamp(metalness * u_metallicFactor, 0.0, 1.0); #endif #ifdef HAS_ROUGHNESS_FACTOR - roughness *= u_roughnessFactor; + roughness = clamp(roughness * u_roughnessFactor, 0.0, 1.0); #endif #else #ifdef HAS_METALLIC_FACTOR @@ -252,19 +253,25 @@ float setMetallicRoughness(inout czm_modelMaterial material) #endif #ifdef HAS_ROUGHNESS_FACTOR + // TODO: WHY is roughness clamped to 0.04 ?? float roughness = clamp(u_roughnessFactor, 0.04, 1.0); #else float roughness = 1.0; #endif #endif - czm_pbrParameters parameters = czm_pbrMetallicRoughnessMaterial( - material.diffuse, - metalness, - roughness - ); - material.diffuse = parameters.diffuseColor; - material.specular = parameters.f0; - material.roughness = parameters.roughness; + + // dielectrics use f0 = 0.04, metals use albedo as f0 + const vec3 REFLECTANCE_DIELECTRIC = vec3(0.04); + vec3 f0 = mix(REFLECTANCE_DIELECTRIC, material.diffuse, metalness); + + material.specular = f0; + + // diffuse only applies to dielectrics. + material.diffuse = material.diffuse * (1.0 - f0) * (1.0 - metalness); + + // roughness is authored as perceptual roughness + // square it to get material roughness + material.roughness = roughness * roughness; return metalness; } From d03bb1a01965732cebf001bb01437f69bc45ab9a Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Fri, 24 May 2024 22:14:34 -0400 Subject: [PATCH 05/21] Refactor LightingStageFS and related functions --- .../Builtin/Functions/pbrLighting.glsl | 25 +++--- .../Model/ImageBasedLightingStageFS.glsl | 81 ++++++++----------- .../Source/Shaders/Model/LightingStageFS.glsl | 60 ++++++++------ 3 files changed, 82 insertions(+), 84 deletions(-) diff --git a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl index 4eeeca5b4033..2a30c23bedd7 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl @@ -83,7 +83,7 @@ float GGX(float roughness, float NdotH) * @name czm_pbrLighting * @glslFunction * - * @param {vec3} positionEC The position of the fragment in eye coordinates + * @param {vec3} viewDirectionEC Unit vector pointing from the fragment to the eye position * @param {vec3} normalEC The surface normal in eye coordinates * @param {vec3} lightDirectionEC Unit vector pointing to the light source in eye coordinates. * @param {vec3} lightColorHdr radiance of the light source. This is a HDR value. @@ -91,18 +91,15 @@ float GGX(float roughness, float NdotH) * @return {vec3} The computed HDR color */ vec3 czm_pbrLighting( - vec3 positionEC, + vec3 viewDirectionEC, vec3 normalEC, vec3 lightDirectionEC, vec3 lightColorHdr, czm_modelMaterial material ) { - vec3 v = -normalize(positionEC); - vec3 l = normalize(lightDirectionEC); - vec3 h = normalize(v + l); - vec3 n = normalEC; - float VdotH = clamp(dot(v, h), 0.0, 1.0); + vec3 halfwayDirectionEC = normalize(viewDirectionEC + lightDirectionEC); + float VdotH = clamp(dot(viewDirectionEC, halfwayDirectionEC), 0.0, 1.0); vec3 f0 = material.specular; float reflectance = max(max(f0.r, f0.g), f0.b); @@ -117,19 +114,19 @@ vec3 czm_pbrLighting( float alpha = material.roughness; #ifdef USE_ANISOTROPY - mat3 tbn = mat3(material.anisotropicT, material.anisotropicB, n); - vec3 lightDirection = l * tbn; - vec3 viewDirection = v * tbn; - vec3 halfwayDirection = h * tbn; + mat3 tbn = mat3(material.anisotropicT, material.anisotropicB, normalEC); + vec3 lightDirection = lightDirectionEC * tbn; + vec3 viewDirection = viewDirectionEC * tbn; + vec3 halfwayDirection = halfwayDirectionEC * tbn; float anisotropyStrength = material.anisotropyStrength; float tangentialRoughness = mix(alpha, 1.0, anisotropyStrength * anisotropyStrength); float G = smithVisibilityGGX_anisotropic(alpha, tangentialRoughness, lightDirection, viewDirection); float D = GGX_anisotropic(alpha, tangentialRoughness, halfwayDirection); float NdotL = clamp(lightDirection.z, 0.001, 1.0); #else - float NdotL = clamp(dot(n, l), 0.001, 1.0); - float NdotV = abs(dot(n, v)) + 0.001; - float NdotH = clamp(dot(n, h), 0.0, 1.0); + float NdotL = clamp(dot(normalEC, lightDirectionEC), 0.001, 1.0); + float NdotV = abs(dot(normalEC, viewDirectionEC)) + 0.001; + float NdotH = clamp(dot(normalEC, halfwayDirectionEC), 0.0, 1.0); float G = smithVisibilityGGX(alpha, NdotL, NdotV); float D = GGX(alpha, NdotH); #endif diff --git a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl index c6d9e76e8cbd..7145bfbcffa9 100644 --- a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl @@ -1,19 +1,27 @@ -vec3 proceduralIBL( +/** + * Compute the light contribution from a procedural sky model + * + * @param {vec3} positionEC The position of the fragment in eye coordinates + * @param {vec3} normalEC The surface normal in eye coordinates + * @param {vec3} lightDirectionEC Unit vector pointing to the light source in eye coordinates. + * @param {vec3} lightColorHdr radiance of the light source. This is a HDR value. + * @param {czm_modelMaterial} The material properties. + * @return {vec3} The computed HDR color + */ + vec3 proceduralIBL( vec3 positionEC, vec3 normalEC, vec3 lightDirectionEC, vec3 lightColorHdr, czm_modelMaterial material ) { - vec3 v = -normalize(positionEC); + vec3 viewDirectionEC = -normalize(positionEC); vec3 positionWC = vec3(czm_inverseView * vec4(positionEC, 1.0)); vec3 vWC = -normalize(positionWC); - vec3 l = normalize(lightDirectionEC); - vec3 n = normalEC; - vec3 r = normalize(czm_inverseViewRotation * normalize(reflect(v, n))); + vec3 r = normalize(czm_inverseViewRotation * normalize(reflect(viewDirectionEC, normalEC))); - float NdotL = clamp(dot(n, l), 0.001, 1.0); - float NdotV = abs(dot(n, v)) + 0.001; + float NdotL = clamp(dot(normalEC, lightDirectionEC), 0.001, 1.0); + float NdotV = abs(dot(normalEC, viewDirectionEC)) + 0.001; // Figure out if the reflection vector hits the ellipsoid float vertexRadius = length(positionWC); @@ -56,10 +64,10 @@ vec3 proceduralIBL( #ifdef USE_SUN_LUMINANCE // See the "CIE Clear Sky Model" referenced on page 40 of https://3dvar.com/Green2003Spherical.pdf // Angle between sun and zenith. - float LdotZenith = clamp(dot(normalize(czm_inverseViewRotation * l), vWC), 0.001, 1.0); + float LdotZenith = clamp(dot(normalize(czm_inverseViewRotation * lightDirectionEC), vWC), 0.001, 1.0); float S = acos(LdotZenith); // Angle between zenith and current pixel - float NdotZenith = clamp(dot(normalize(czm_inverseViewRotation * n), vWC), 0.001, 1.0); + float NdotZenith = clamp(dot(normalize(czm_inverseViewRotation * normalEC), vWC), 0.001, 1.0); // Angle between sun and current pixel float gamma = acos(NdotL); float numerator = ((0.91 + 10.0 * exp(-3.0 * gamma) + 0.45 * NdotL * NdotL) * (1.0 - exp(-0.32 / NdotZenith))); @@ -133,19 +141,25 @@ vec3 computeSpecularIBL(czm_modelMaterial material, vec3 cubeDir, float NdotV, f #endif #if defined(DIFFUSE_IBL) || defined(SPECULAR_IBL) +/** + * Compute the light contributions from environment maps and spherical harmonic coefficients + * + * @param {vec3} viewDirectionEC Unit vector pointing from the fragment to the eye position + * @param {vec3} normalEC The surface normal in eye coordinates + * @param {vec3} lightDirectionEC Unit vector pointing to the light source in eye coordinates. + * @param {czm_modelMaterial} The material properties. + * @return {vec3} The computed HDR color + */ vec3 textureIBL( - vec3 positionEC, + vec3 viewDirectionEC, vec3 normalEC, vec3 lightDirectionEC, czm_modelMaterial material ) { - vec3 v = -normalize(positionEC); - vec3 n = normalEC; - vec3 l = normalize(lightDirectionEC); - vec3 h = normalize(v + l); + vec3 halfwayDirectionEC = normalize(viewDirectionEC + lightDirectionEC); - float NdotV = abs(dot(n, v)) + 0.001; - float VdotH = clamp(dot(v, h), 0.0, 1.0); + float NdotV = abs(dot(normalEC, viewDirectionEC)) + 0.001; + float VdotH = clamp(dot(viewDirectionEC, halfwayDirectionEC), 0.0, 1.0); // Find the direction in which to sample the environment map const mat3 yUpToZUp = mat3( @@ -154,7 +168,7 @@ vec3 textureIBL( 0.0, 1.0, 0.0 ); mat3 cubeDirTransform = yUpToZUp * model_iblReferenceFrameMatrix; - vec3 cubeDir = normalize(cubeDirTransform * normalize(reflect(-v, n))); + vec3 cubeDir = normalize(cubeDirTransform * normalize(reflect(-viewDirectionEC, normalEC))); #ifdef DIFFUSE_IBL vec3 diffuseContribution = computeDiffuseIBL(material, cubeDir); @@ -168,12 +182,12 @@ vec3 textureIBL( vec3 anisotropyDirection = material.anisotropicB; float anisotropyStrength = material.anisotropyStrength; - vec3 anisotropicTangent = cross(anisotropyDirection, v); + vec3 anisotropicTangent = cross(anisotropyDirection, viewDirectionEC); vec3 anisotropicNormal = cross(anisotropicTangent, anisotropyDirection); float bendFactor = 1.0 - anisotropyStrength * (1.0 - roughness); float bendFactorPow4 = bendFactor * bendFactor * bendFactor * bendFactor; - vec3 bentNormal = normalize(mix(anisotropicNormal, n, bendFactorPow4)); - cubeDir = normalize(cubeDirTransform * normalize(reflect(-v, bentNormal))); + vec3 bentNormal = normalize(mix(anisotropicNormal, normalEC, bendFactorPow4)); + cubeDir = normalize(cubeDirTransform * normalize(reflect(-viewDirectionEC, bentNormal))); #endif #ifdef SPECULAR_IBL @@ -185,30 +199,3 @@ vec3 textureIBL( return diffuseContribution + specularContribution; } #endif - -vec3 imageBasedLightingStage( - vec3 positionEC, - vec3 normalEC, - vec3 lightDirectionEC, - vec3 lightColorHdr, - czm_modelMaterial material -) { - #if defined(DIFFUSE_IBL) || defined(SPECULAR_IBL) - // Environment maps were provided, use them for IBL - return textureIBL( - positionEC, - normalEC, - lightDirectionEC, - material - ); - #else - // Use the procedural IBL if there are no environment maps - return proceduralIBL( - positionEC, - normalEC, - lightDirectionEC, - lightColorHdr, - material - ); - #endif -} \ No newline at end of file diff --git a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl index 1da272a69790..c37845bfdab2 100644 --- a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl @@ -2,30 +2,44 @@ vec3 computePbrLighting(czm_modelMaterial inputMaterial, ProcessedAttributes attributes) { #ifdef USE_CUSTOM_LIGHT_COLOR - vec3 lightColorHdr = model_lightColorHdr; + vec3 lightColorHdr = model_lightColorHdr; #else - vec3 lightColorHdr = czm_lightColorHdr; + vec3 lightColorHdr = czm_lightColorHdr; #endif vec3 color = inputMaterial.diffuse; + #ifdef HAS_NORMALS - color = czm_pbrLighting( - attributes.positionEC, - inputMaterial.normalEC, - czm_lightDirectionEC, - lightColorHdr, - inputMaterial - ); + vec3 viewDirection = -normalize(attributes.positionEC); + vec3 normal = inputMaterial.normalEC; + vec3 lightDirection = normalize(czm_lightDirectionEC); - #ifdef USE_IBL_LIGHTING - color += imageBasedLightingStage( - attributes.positionEC, - inputMaterial.normalEC, - czm_lightDirectionEC, + color = czm_pbrLighting( + viewDirection, + normal, + lightDirection, lightColorHdr, inputMaterial ); - #endif + + #if defined(DIFFUSE_IBL) || defined(SPECULAR_IBL) + // Environment maps were provided, use them for IBL + color += textureIBL( + viewDirection, + normal, + lightDirection, + inputMaterial + ); + #elif defined(USE_IBL_LIGHTING) + // Use procedural IBL if there are no environment maps + color += proceduralIBL( + attributes.positionEC, + normal, + lightDirection, + lightColorHdr, + inputMaterial + ); + #endif #endif color *= inputMaterial.occlusion; @@ -36,7 +50,7 @@ vec3 computePbrLighting(czm_modelMaterial inputMaterial, ProcessedAttributes att // tonemapping. However, if HDR is not enabled, we must tonemap else large // values may be clamped to 1.0 #ifndef HDR - color = czm_acesTonemapping(color); + color = czm_acesTonemapping(color); #endif return color; @@ -50,18 +64,18 @@ void lightingStage(inout czm_modelMaterial material, ProcessedAttributes attribu vec3 color = vec3(0.0); #ifdef LIGHTING_PBR - color = computePbrLighting(material, attributes); + color = computePbrLighting(material, attributes); #else // unlit - color = material.diffuse; + color = material.diffuse; #endif #ifdef HAS_POINT_CLOUD_COLOR_STYLE - // The colors resulting from point cloud styles are adjusted differently. - color = czm_gammaCorrect(color); + // The colors resulting from point cloud styles are adjusted differently. + color = czm_gammaCorrect(color); #elif !defined(HDR) - // If HDR is not enabled, the frame buffer stores sRGB colors rather than - // linear colors so the linear value must be converted. - color = czm_linearToSrgb(color); + // If HDR is not enabled, the frame buffer stores sRGB colors rather than + // linear colors so the linear value must be converted. + color = czm_linearToSrgb(color); #endif material.diffuse = color; From 3b6ca76a0572440240e9da7815fb311a1143705c Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Sat, 25 May 2024 19:36:10 -0400 Subject: [PATCH 06/21] Simplify light color handling in PBR rendering --- .../Scene/Model/LightingPipelineStage.js | 6 +-- .../Builtin/Functions/maximumComponent.glsl | 21 ++++++++++ .../Builtin/Functions/pbrLighting.glsl | 14 +++---- packages/engine/Source/Shaders/GlobeFS.glsl | 2 +- .../Model/ImageBasedLightingStageFS.glsl | 7 +--- .../Source/Shaders/Model/LightingStageFS.glsl | 40 +++++++------------ .../Source/Shaders/Model/MaterialStageFS.glsl | 2 +- 7 files changed, 46 insertions(+), 46 deletions(-) create mode 100644 packages/engine/Source/Shaders/Builtin/Functions/maximumComponent.glsl diff --git a/packages/engine/Source/Scene/Model/LightingPipelineStage.js b/packages/engine/Source/Scene/Model/LightingPipelineStage.js index 6ed8f4aad1ae..a2f466484ee4 100644 --- a/packages/engine/Source/Scene/Model/LightingPipelineStage.js +++ b/packages/engine/Source/Scene/Model/LightingPipelineStage.js @@ -28,9 +28,7 @@ const LightingPipelineStage = { * @private */ LightingPipelineStage.process = function (renderResources, primitive) { - const model = renderResources.model; - const lightingOptions = renderResources.lightingOptions; - const shaderBuilder = renderResources.shaderBuilder; + const { model, lightingOptions, shaderBuilder } = renderResources; if (defined(model.lightColor)) { shaderBuilder.addDefine( @@ -53,7 +51,7 @@ LightingPipelineStage.process = function (renderResources, primitive) { // The lighting model is always set by the material. However, custom shaders // can override this. - const lightingModel = lightingOptions.lightingModel; + const { lightingModel } = lightingOptions; if (lightingModel === LightingModel.PBR) { shaderBuilder.addDefine( diff --git a/packages/engine/Source/Shaders/Builtin/Functions/maximumComponent.glsl b/packages/engine/Source/Shaders/Builtin/Functions/maximumComponent.glsl new file mode 100644 index 000000000000..40f51216d7dc --- /dev/null +++ b/packages/engine/Source/Shaders/Builtin/Functions/maximumComponent.glsl @@ -0,0 +1,21 @@ +/** + * Find the maximum component of a vector. + * + * @name czm_maximumComponent + * @glslFunction + * + * @param {vec2|vec3|vec4} v The input vector. + * @returns {float} The value of the largest component. + */ +float czm_maximumComponent(vec2 v) +{ + return max(v.x, v.y); +} +float czm_maximumComponent(vec3 v) +{ + return max(max(v.x, v.y), v.z); +} +float czm_maximumComponent(vec4 v) +{ + return max(max(max(v.x, v.y), v.z), v.w); +} diff --git a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl index 2a30c23bedd7..6c6e13179f2a 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl @@ -86,23 +86,21 @@ float GGX(float roughness, float NdotH) * @param {vec3} viewDirectionEC Unit vector pointing from the fragment to the eye position * @param {vec3} normalEC The surface normal in eye coordinates * @param {vec3} lightDirectionEC Unit vector pointing to the light source in eye coordinates. - * @param {vec3} lightColorHdr radiance of the light source. This is a HDR value. * @param {czm_modelMaterial} The material properties. * @return {vec3} The computed HDR color */ vec3 czm_pbrLighting( - vec3 viewDirectionEC, - vec3 normalEC, - vec3 lightDirectionEC, - vec3 lightColorHdr, - czm_modelMaterial material + in vec3 viewDirectionEC, + in vec3 normalEC, + in vec3 lightDirectionEC, + in czm_modelMaterial material ) { vec3 halfwayDirectionEC = normalize(viewDirectionEC + lightDirectionEC); float VdotH = clamp(dot(viewDirectionEC, halfwayDirectionEC), 0.0, 1.0); vec3 f0 = material.specular; - float reflectance = max(max(f0.r, f0.g), f0.b); + float reflectance = czm_maximumComponent(f0); // Typical dielectrics will have reflectance 0.04, so f90 will be 1.0. // In this case, at grazing angle, all incident energy is reflected. vec3 f90 = vec3(clamp(reflectance * 25.0, 0.0, 1.0)); @@ -138,5 +136,5 @@ vec3 czm_pbrLighting( vec3 diffuseContribution = (1.0 - F) * lambertianDiffuse(diffuseColor); // Lo = (diffuse + specular) * Li * NdotL - return (diffuseContribution + specularContribution) * NdotL * lightColorHdr; + return (diffuseContribution + specularContribution) * NdotL; } diff --git a/packages/engine/Source/Shaders/GlobeFS.glsl b/packages/engine/Source/Shaders/GlobeFS.glsl index 11072943caf6..74ad34d886e0 100644 --- a/packages/engine/Source/Shaders/GlobeFS.glsl +++ b/packages/engine/Source/Shaders/GlobeFS.glsl @@ -207,7 +207,7 @@ vec4 sampleAndBlend( #ifdef APPLY_COLOR_TO_ALPHA vec3 colorDiff = abs(color.rgb - colorToAlpha.rgb); - colorDiff.r = max(max(colorDiff.r, colorDiff.g), colorDiff.b); + colorDiff.r = czm_maximumComponent(colorDiff); alpha = czm_branchFreeTernary(colorDiff.r < colorToAlpha.a, 0.0, alpha); #endif diff --git a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl index 7145bfbcffa9..b41e412d18e7 100644 --- a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl @@ -4,7 +4,6 @@ * @param {vec3} positionEC The position of the fragment in eye coordinates * @param {vec3} normalEC The surface normal in eye coordinates * @param {vec3} lightDirectionEC Unit vector pointing to the light source in eye coordinates. - * @param {vec3} lightColorHdr radiance of the light source. This is a HDR value. * @param {czm_modelMaterial} The material properties. * @return {vec3} The computed HDR color */ @@ -12,7 +11,6 @@ vec3 positionEC, vec3 normalEC, vec3 lightDirectionEC, - vec3 lightColorHdr, czm_modelMaterial material ) { vec3 viewDirectionEC = -normalize(positionEC); @@ -83,9 +81,6 @@ #endif vec3 diffuseContribution = diffuseIrradiance * diffuseColor * model_iblFactor.x; vec3 iblColor = specularContribution + diffuseContribution; - float maximumComponent = max(max(lightColorHdr.x, lightColorHdr.y), lightColorHdr.z); - vec3 lightColor = lightColorHdr / max(maximumComponent, 1.0); - iblColor *= lightColor; #ifdef USE_SUN_LUMINANCE iblColor *= luminance; @@ -124,7 +119,7 @@ vec3 computeSpecularIBL(czm_modelMaterial material, vec3 cubeDir, float NdotV, f float roughness = material.roughness; vec3 f0 = material.specular; - float reflectance = max(max(f0.r, f0.g), f0.b); + float reflectance = czm_maximumComponent(f0); vec3 f90 = vec3(clamp(reflectance * 25.0, 0.0, 1.0)); vec3 F = fresnelSchlick2(f0, f90, VdotH); diff --git a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl index c37845bfdab2..3aef31c95993 100644 --- a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl @@ -1,5 +1,5 @@ #ifdef LIGHTING_PBR -vec3 computePbrLighting(czm_modelMaterial inputMaterial, ProcessedAttributes attributes) +vec3 computePbrLighting(czm_modelMaterial material, ProcessedAttributes attributes) { #ifdef USE_CUSTOM_LIGHT_COLOR vec3 lightColorHdr = model_lightColorHdr; @@ -7,43 +7,31 @@ vec3 computePbrLighting(czm_modelMaterial inputMaterial, ProcessedAttributes att vec3 lightColorHdr = czm_lightColorHdr; #endif - vec3 color = inputMaterial.diffuse; + vec3 color = material.diffuse; #ifdef HAS_NORMALS - vec3 viewDirection = -normalize(attributes.positionEC); - vec3 normal = inputMaterial.normalEC; + vec3 position = attributes.positionEC; + vec3 viewDirection = -normalize(position); + vec3 normal = material.normalEC; vec3 lightDirection = normalize(czm_lightDirectionEC); - color = czm_pbrLighting( - viewDirection, - normal, - lightDirection, - lightColorHdr, - inputMaterial - ); + vec3 directLighting = czm_pbrLighting(viewDirection, normal, lightDirection, material); + color = lightColorHdr * directLighting; #if defined(DIFFUSE_IBL) || defined(SPECULAR_IBL) // Environment maps were provided, use them for IBL - color += textureIBL( - viewDirection, - normal, - lightDirection, - inputMaterial - ); + color += textureIBL(viewDirection, normal, lightDirection, material); #elif defined(USE_IBL_LIGHTING) // Use procedural IBL if there are no environment maps - color += proceduralIBL( - attributes.positionEC, - normal, - lightDirection, - lightColorHdr, - inputMaterial - ); + vec3 imageBasedLighting = proceduralIBL(position, normal, lightDirection, material); + float maximumComponent = czm_maximumComponent(lightColorHdr); + vec3 clampedLightColor = lightColorHdr / max(maximumComponent, 1.0); + color += clampedLightColor * imageBasedLighting; #endif #endif - color *= inputMaterial.occlusion; - color += inputMaterial.emissive; + color *= material.occlusion; + color += material.emissive; // In HDR mode, the frame buffer is in linear color space. The // post-processing stages (see PostProcessStageCollection) will handle diff --git a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl index 20d171399917..33ca8696c988 100644 --- a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl @@ -214,7 +214,7 @@ void setSpecularGlossiness(inout czm_modelMaterial material) vec4 diffuse = vec4(1.0); #endif - material.diffuse = diffuse.rgb * (1.0 - max(max(specular.r, specular.g), specular.b)); + material.diffuse = diffuse.rgb * (1.0 - czm_maximumComponent(specular)); // the specular glossiness extension's alpha overrides anything set // by the base material. material.alpha = diffuse.a; From ebfe3bd466ce1d1561e0b8998672b57c5f6c86be Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Sat, 25 May 2024 22:38:44 -0400 Subject: [PATCH 07/21] Refactor LightingStageFS --- .../Source/Shaders/Model/LightingStageFS.glsl | 72 ++++++++++--------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl index 3aef31c95993..4ee491cfce55 100644 --- a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl @@ -1,5 +1,5 @@ -#ifdef LIGHTING_PBR -vec3 computePbrLighting(czm_modelMaterial material, ProcessedAttributes attributes) +#if defined(LIGHTING_PBR) && defined(HAS_NORMALS) +vec3 computePbrLighting(in czm_modelMaterial material, in vec3 position) { #ifdef USE_CUSTOM_LIGHT_COLOR vec3 lightColorHdr = model_lightColorHdr; @@ -7,54 +7,56 @@ vec3 computePbrLighting(czm_modelMaterial material, ProcessedAttributes attribut vec3 lightColorHdr = czm_lightColorHdr; #endif - vec3 color = material.diffuse; + vec3 viewDirection = -normalize(position); + vec3 normal = material.normalEC; + vec3 lightDirection = normalize(czm_lightDirectionEC); - #ifdef HAS_NORMALS - vec3 position = attributes.positionEC; - vec3 viewDirection = -normalize(position); - vec3 normal = material.normalEC; - vec3 lightDirection = normalize(czm_lightDirectionEC); + vec3 directLighting = czm_pbrLighting(viewDirection, normal, lightDirection, material); + vec3 color = lightColorHdr * directLighting; - vec3 directLighting = czm_pbrLighting(viewDirection, normal, lightDirection, material); - color = lightColorHdr * directLighting; - - #if defined(DIFFUSE_IBL) || defined(SPECULAR_IBL) - // Environment maps were provided, use them for IBL - color += textureIBL(viewDirection, normal, lightDirection, material); - #elif defined(USE_IBL_LIGHTING) - // Use procedural IBL if there are no environment maps - vec3 imageBasedLighting = proceduralIBL(position, normal, lightDirection, material); - float maximumComponent = czm_maximumComponent(lightColorHdr); - vec3 clampedLightColor = lightColorHdr / max(maximumComponent, 1.0); - color += clampedLightColor * imageBasedLighting; - #endif + #if defined(DIFFUSE_IBL) || defined(SPECULAR_IBL) + // Environment maps were provided, use them for IBL + color += textureIBL(viewDirection, normal, lightDirection, material); + #elif defined(USE_IBL_LIGHTING) + // Use procedural IBL if there are no environment maps + vec3 imageBasedLighting = proceduralIBL(position, normal, lightDirection, material); + float maximumComponent = czm_maximumComponent(lightColorHdr); + vec3 clampedLightColor = lightColorHdr / max(maximumComponent, 1.0); + color += clampedLightColor * imageBasedLighting; #endif color *= material.occlusion; color += material.emissive; - // In HDR mode, the frame buffer is in linear color space. The - // post-processing stages (see PostProcessStageCollection) will handle - // tonemapping. However, if HDR is not enabled, we must tonemap else large - // values may be clamped to 1.0 - #ifndef HDR - color = czm_acesTonemapping(color); - #endif - return color; } #endif +/** + * Compute the material color under the current lighting conditions. + * All other material properties are passed through so further stages + * have access to them. + * + * @param {czm_modelMaterial} material The material properties from {@MaterialStageFS} + * @param {ProcessedAttributes} attributes + */ void lightingStage(inout czm_modelMaterial material, ProcessedAttributes attributes) { - // Even though the lighting will only set the diffuse color, - // pass all other properties so further stages have access to them. - vec3 color = vec3(0.0); - #ifdef LIGHTING_PBR - color = computePbrLighting(material, attributes); + #ifdef HAS_NORMALS + vec3 color = computePbrLighting(material, attributes.positionEC); + #else + vec3 color = material.diffuse * material.occlusion + material.emissive; + #endif + // In HDR mode, the frame buffer is in linear color space. The + // post-processing stages (see PostProcessStageCollection) will handle + // tonemapping. However, if HDR is not enabled, we must tonemap else large + // values may be clamped to 1.0 + #ifndef HDR + color = czm_acesTonemapping(color); + #endif #else // unlit - color = material.diffuse; + vec3 color = material.diffuse; #endif #ifdef HAS_POINT_CLOUD_COLOR_STYLE From 038fbc89cc8366d4b12e501f589aab10590d5aba Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Mon, 27 May 2024 23:48:32 -0400 Subject: [PATCH 08/21] Partial implementation of clearcoat for direct lighting --- .../Builtin/Functions/pbrLighting.glsl | 29 ++++--- .../Source/Shaders/Model/LightingStageFS.glsl | 77 ++++++++++++++++--- .../Source/Shaders/Model/MaterialStageFS.glsl | 6 +- 3 files changed, 89 insertions(+), 23 deletions(-) diff --git a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl index 6c6e13179f2a..322c8ed6d894 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl @@ -39,7 +39,8 @@ float GGX_anisotropic(float roughness, float tangentialRoughness, vec3 halfwayDi float w2 = roughnessSquared / dot(f, f); return roughnessSquared * w2 * w2 / czm_pi; } -#else +#endif + float smithVisibilityG1(float NdotV, float roughness) { // this is the k value for direct lighting. @@ -70,7 +71,19 @@ float GGX(float roughness, float NdotH) float f = (NdotH * roughnessSquared - NdotH) * NdotH + 1.0; return roughnessSquared / (czm_pi * f * f); } -#endif + +// TODO: rename to emphasize this is for direct lighting only (not IBL) +float computeSpecularStrength(vec3 normal, vec3 lightDirection, vec3 viewDirection, vec3 halfwayDirection, float roughness) +{ + // TODO: why 0.001 and not 0.0? + float NdotL = clamp(dot(normal, lightDirection), 0.001, 1.0); + // TODO: why abs here and clamp on the others? What does this do with backside reflections? + float NdotV = abs(dot(normal, viewDirection)) + 0.001; + float NdotH = clamp(dot(normal, halfwayDirection), 0.0, 1.0); + float G = smithVisibilityGGX(roughness, NdotL, NdotV); + float D = GGX(roughness, NdotH); + return G * D; +} /** * Compute the diffuse and specular contributions using physically based @@ -98,6 +111,7 @@ vec3 czm_pbrLighting( { vec3 halfwayDirectionEC = normalize(viewDirectionEC + lightDirectionEC); float VdotH = clamp(dot(viewDirectionEC, halfwayDirectionEC), 0.0, 1.0); + float NdotL = clamp(dot(normalEC, lightDirectionEC), 0.001, 1.0); vec3 f0 = material.specular; float reflectance = czm_maximumComponent(f0); @@ -120,17 +134,12 @@ vec3 czm_pbrLighting( float tangentialRoughness = mix(alpha, 1.0, anisotropyStrength * anisotropyStrength); float G = smithVisibilityGGX_anisotropic(alpha, tangentialRoughness, lightDirection, viewDirection); float D = GGX_anisotropic(alpha, tangentialRoughness, halfwayDirection); - float NdotL = clamp(lightDirection.z, 0.001, 1.0); + vec3 specularContribution = F * G * D; #else - float NdotL = clamp(dot(normalEC, lightDirectionEC), 0.001, 1.0); - float NdotV = abs(dot(normalEC, viewDirectionEC)) + 0.001; - float NdotH = clamp(dot(normalEC, halfwayDirectionEC), 0.0, 1.0); - float G = smithVisibilityGGX(alpha, NdotL, NdotV); - float D = GGX(alpha, NdotH); + float specularStrength = computeSpecularStrength(normalEC, lightDirectionEC, viewDirectionEC, halfwayDirectionEC, alpha); + vec3 specularContribution = F * specularStrength; #endif - vec3 specularContribution = F * G * D; - vec3 diffuseColor = material.diffuse; // F here represents the specular contribution vec3 diffuseContribution = (1.0 - F) * lambertianDiffuse(diffuseColor); diff --git a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl index 4ee491cfce55..7855afd2075f 100644 --- a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl @@ -1,3 +1,59 @@ +#ifdef USE_IBL_LIGHTING +vec3 computeIBL(vec3 position, vec3 normal, vec3 lightDirection, vec3 lightColorHdr, czm_modelMaterial material) +{ + #if defined(DIFFUSE_IBL) || defined(SPECULAR_IBL) + // Environment maps were provided, use them for IBL + vec3 viewDirection = -normalize(position); + vec3 iblColor = textureIBL(viewDirection, normal, lightDirection, material); + #else + // Use procedural IBL if there are no environment maps + vec3 imageBasedLighting = proceduralIBL(position, normal, lightDirection, material); + float maximumComponent = czm_maximumComponent(lightColorHdr); + vec3 clampedLightColor = lightColorHdr / max(maximumComponent, 1.0); + vec3 iblColor = clampedLightColor * imageBasedLighting; + #endif + return iblColor * material.occlusion; +} +#endif + +#ifdef USE_CLEARCOAT +// TODO: make sure KHR_materials_specular properties are not being used in any functions called by this method. +vec3 addClearcoatReflection(vec3 baseLayerColor, vec3 position, vec3 lightDirection, vec3 lightColorHdr, czm_modelMaterial material) +{ + vec3 viewDirection = -normalize(position); + vec3 halfwayDirection = normalize(viewDirection + lightDirection); + vec3 normal = material.clearcoatNormal; + // TODO: why clamp to 0.001?? + float NdotL = clamp(dot(normal, lightDirection), 0.001, 1.0); + + // clearcoatF0 = vec3(pow((ior - 1.0) / (ior + 1.0), 2.0)), but without KHR_materials_ior, ior is a constant 1.5. + vec3 f0 = vec3(0.04); + vec3 f90 = vec3(1.0); + // Note: clearcoat Fresnel computed with dot(n, v) instead of dot(v, h). + // This is to make it energy conserving with a simple layering function. + float NdotV = clamp(dot(normal, viewDirection), 0.0, 1.0); + vec3 F = fresnelSchlick2(f0, f90, NdotV); + + // compute specular reflection from direct lighting + float directStrength = computeSpecularStrength(normal, lightDirection, viewDirection, halfwayDirection, material.clearcoatRoughness); + vec3 directReflection = F * directStrength * NdotL; + vec3 directColor = lightColorHdr * directReflection; + + #ifdef USE_IBL_LIGHTING + // TODO: compute specular reflection from IBL + // NOTE: can't use computeIBL (above) because we need to force isotropic reflection (even if base layer is anisotropic) + // Also if USE_IBL_LIGHTING but !SPECULAR_IBL then the clearcoat has no IBL + // scale by occlusion + #endif + + float clearcoatFactor = material.clearcoatFactor; + vec3 clearcoatColor = directColor * clearcoatFactor; + + // Dim base layer based on transmission loss through clearcoat + return baseLayerColor * (1.0 - clearcoatFactor * F) + clearcoatColor; +} +#endif + #if defined(LIGHTING_PBR) && defined(HAS_NORMALS) vec3 computePbrLighting(in czm_modelMaterial material, in vec3 position) { @@ -12,21 +68,18 @@ vec3 computePbrLighting(in czm_modelMaterial material, in vec3 position) vec3 lightDirection = normalize(czm_lightDirectionEC); vec3 directLighting = czm_pbrLighting(viewDirection, normal, lightDirection, material); - vec3 color = lightColorHdr * directLighting; + vec3 directColor = lightColorHdr * directLighting; - #if defined(DIFFUSE_IBL) || defined(SPECULAR_IBL) - // Environment maps were provided, use them for IBL - color += textureIBL(viewDirection, normal, lightDirection, material); - #elif defined(USE_IBL_LIGHTING) - // Use procedural IBL if there are no environment maps - vec3 imageBasedLighting = proceduralIBL(position, normal, lightDirection, material); - float maximumComponent = czm_maximumComponent(lightColorHdr); - vec3 clampedLightColor = lightColorHdr / max(maximumComponent, 1.0); - color += clampedLightColor * imageBasedLighting; + #ifdef USE_IBL_LIGHTING + vec3 iblColor = computeIBL(position, normal, lightDirection, lightColorHdr, material); #endif - color *= material.occlusion; - color += material.emissive; + // Accumulate colors from base layer + vec3 color = directColor + iblColor + material.emissive; + + #ifdef USE_CLEARCOAT + color = addClearcoatReflection(color, position, lightDirection, lightColorHdr, material); + #endif return color; } diff --git a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl index 33ca8696c988..9ced0cd89274 100644 --- a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl @@ -271,6 +271,7 @@ float setMetallicRoughness(inout czm_modelMaterial material) // roughness is authored as perceptual roughness // square it to get material roughness + // TODO: do we need perceptual roughness for IBL? material.roughness = roughness * roughness; return metalness; @@ -383,7 +384,10 @@ void setClearcoat(inout czm_modelMaterial material) #endif material.clearcoatFactor = clearcoatFactor; - material.clearcoatRoughness = clearcoatRoughness; + // roughness is authored as perceptual roughness + // square it to get material roughness + // TODO: do we need perceptual roughness for IBL? + material.clearcoatRoughness = clearcoatRoughness * clearcoatRoughness; // TODO: read clear coat normals from a texture (if supplied) material.clearcoatNormal = material.normalEC; } From 52ed2c76322fea17273ea998e564937dfac651f3 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 28 May 2024 14:51:55 -0400 Subject: [PATCH 09/21] Fix bug in image-based lighting, and apply IBL to clearcoat --- packages/engine/Source/Scene/GltfLoader.js | 4 +- .../Model/ImageBasedLightingStageFS.glsl | 48 ++++++++----------- .../Source/Shaders/Model/LightingStageFS.glsl | 23 ++++++--- .../Source/Shaders/Model/MaterialStageFS.glsl | 4 +- 4 files changed, 42 insertions(+), 37 deletions(-) diff --git a/packages/engine/Source/Scene/GltfLoader.js b/packages/engine/Source/Scene/GltfLoader.js index b46733d809e8..59b83ec2bc99 100644 --- a/packages/engine/Source/Scene/GltfLoader.js +++ b/packages/engine/Source/Scene/GltfLoader.js @@ -1544,9 +1544,9 @@ function loadMetallicRoughness(loader, metallicRoughnessInfo, frameState) { function loadSpecular(loader, specularInfo, frameState) { const { - specularFactor = Specular.DEFAULT_SPECULAR_FACTOR, + specularFactor, specularTexture, - specularColorFactor = Specular.DEFAULT_SPECULAR_COLOR_FACTOR, + specularColorFactor, specularColorTexture, } = specularInfo; diff --git a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl index b41e412d18e7..33ae7b47a7c9 100644 --- a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl @@ -90,14 +90,13 @@ } #ifdef DIFFUSE_IBL -vec3 computeDiffuseIBL(czm_modelMaterial material, vec3 cubeDir) +vec3 computeDiffuseIBL(vec3 cubeDir) { #ifdef CUSTOM_SPHERICAL_HARMONICS - vec3 diffuseIrradiance = czm_sphericalHarmonics(cubeDir, model_sphericalHarmonicCoefficients); + return czm_sphericalHarmonics(cubeDir, model_sphericalHarmonicCoefficients); #else - vec3 diffuseIrradiance = czm_sphericalHarmonics(cubeDir, czm_sphericalHarmonicCoefficients); - #endif - return diffuseIrradiance * material.diffuse; + return czm_sphericalHarmonics(cubeDir, czm_sphericalHarmonicCoefficients); + #endif } #endif @@ -114,24 +113,17 @@ vec3 sampleSpecularEnvironment(vec3 cubeDir, float roughness) return czm_sampleOctahedralProjection(czm_specularEnvironmentMaps, czm_specularEnvironmentMapSize, cubeDir, lod, maxLod); #endif } -vec3 computeSpecularIBL(czm_modelMaterial material, vec3 cubeDir, float NdotV, float VdotH) +vec3 computeSpecularIBL(vec3 cubeDir, float NdotV, float VdotH, vec3 f0, float roughness) { - float roughness = material.roughness; - vec3 f0 = material.specular; - float reflectance = czm_maximumComponent(f0); vec3 f90 = vec3(clamp(reflectance * 25.0, 0.0, 1.0)); vec3 F = fresnelSchlick2(f0, f90, VdotH); + // TODO: should this be using perceptual roughness instead of alpha roughness (which is squared)? vec2 brdfLut = texture(czm_brdfLut, vec2(NdotV, roughness)).rg; - vec3 specularIBL = sampleSpecularEnvironment(cubeDir, roughness); - specularIBL *= F * brdfLut.x + brdfLut.y; + vec3 specularSample = sampleSpecularEnvironment(cubeDir, roughness); - #ifdef USE_SPECULAR - specularIBL *= material.specularWeight; - #endif - - return f0 * specularIBL; + return specularSample * (F * brdfLut.x + brdfLut.y); } #endif @@ -151,11 +143,6 @@ vec3 textureIBL( vec3 lightDirectionEC, czm_modelMaterial material ) { - vec3 halfwayDirectionEC = normalize(viewDirectionEC + lightDirectionEC); - - float NdotV = abs(dot(normalEC, viewDirectionEC)) + 0.001; - float VdotH = clamp(dot(viewDirectionEC, halfwayDirectionEC), 0.0, 1.0); - // Find the direction in which to sample the environment map const mat3 yUpToZUp = mat3( -1.0, 0.0, 0.0, @@ -166,31 +153,38 @@ vec3 textureIBL( vec3 cubeDir = normalize(cubeDirTransform * normalize(reflect(-viewDirectionEC, normalEC))); #ifdef DIFFUSE_IBL - vec3 diffuseContribution = computeDiffuseIBL(material, cubeDir); + vec3 diffuseContribution = computeDiffuseIBL(cubeDir) * material.diffuse; #else vec3 diffuseContribution = vec3(0.0); #endif + float roughness = material.roughness; + #ifdef USE_ANISOTROPY // Update environment map sampling direction to account for anisotropic distortion of specular reflection - float roughness = material.roughness; vec3 anisotropyDirection = material.anisotropicB; - float anisotropyStrength = material.anisotropyStrength; - vec3 anisotropicTangent = cross(anisotropyDirection, viewDirectionEC); vec3 anisotropicNormal = cross(anisotropicTangent, anisotropyDirection); - float bendFactor = 1.0 - anisotropyStrength * (1.0 - roughness); + float bendFactor = 1.0 - material.anisotropyStrength * (1.0 - roughness); float bendFactorPow4 = bendFactor * bendFactor * bendFactor * bendFactor; vec3 bentNormal = normalize(mix(anisotropicNormal, normalEC, bendFactorPow4)); cubeDir = normalize(cubeDirTransform * normalize(reflect(-viewDirectionEC, bentNormal))); #endif #ifdef SPECULAR_IBL - vec3 specularContribution = computeSpecularIBL(material, cubeDir, NdotV, VdotH); + float NdotV = abs(dot(normalEC, viewDirectionEC)) + 0.001; + vec3 halfwayDirectionEC = normalize(viewDirectionEC + lightDirectionEC); + float VdotH = clamp(dot(viewDirectionEC, halfwayDirectionEC), 0.0, 1.0); + vec3 f0 = material.specular; + vec3 specularContribution = computeSpecularIBL(cubeDir, NdotV, VdotH, f0, roughness); #else vec3 specularContribution = vec3(0.0); #endif + #ifdef USE_SPECULAR + specularContribution *= material.specularWeight; + #endif + return diffuseContribution + specularContribution; } #endif diff --git a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl index 7855afd2075f..a1f556e96203 100644 --- a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl @@ -35,19 +35,30 @@ vec3 addClearcoatReflection(vec3 baseLayerColor, vec3 position, vec3 lightDirect vec3 F = fresnelSchlick2(f0, f90, NdotV); // compute specular reflection from direct lighting - float directStrength = computeSpecularStrength(normal, lightDirection, viewDirection, halfwayDirection, material.clearcoatRoughness); + float roughness = material.clearcoatRoughness; + float directStrength = computeSpecularStrength(normal, lightDirection, viewDirection, halfwayDirection, roughness); vec3 directReflection = F * directStrength * NdotL; - vec3 directColor = lightColorHdr * directReflection; - - #ifdef USE_IBL_LIGHTING + vec3 color = lightColorHdr * directReflection; + + #ifdef SPECULAR_IBL + // Find the direction in which to sample the environment map + const mat3 yUpToZUp = mat3( + -1.0, 0.0, 0.0, + 0.0, 0.0, -1.0, + 0.0, 1.0, 0.0 + ); + mat3 cubeDirTransform = yUpToZUp * model_iblReferenceFrameMatrix; + vec3 cubeDir = normalize(cubeDirTransform * normalize(reflect(-viewDirection, normal))); + vec3 iblColor = computeSpecularIBL(cubeDir, NdotV, NdotV, f0, roughness); + color += iblColor * material.occlusion; + #elif defined(USE_IBL_LIGHTING) // TODO: compute specular reflection from IBL // NOTE: can't use computeIBL (above) because we need to force isotropic reflection (even if base layer is anisotropic) - // Also if USE_IBL_LIGHTING but !SPECULAR_IBL then the clearcoat has no IBL // scale by occlusion #endif float clearcoatFactor = material.clearcoatFactor; - vec3 clearcoatColor = directColor * clearcoatFactor; + vec3 clearcoatColor = color * clearcoatFactor; // Dim base layer based on transmission loss through clearcoat return baseLayerColor * (1.0 - clearcoatFactor * F) + clearcoatColor; diff --git a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl index 9ced0cd89274..02a6807ef40e 100644 --- a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl @@ -353,7 +353,7 @@ void setClearcoat(inout czm_modelMaterial material) #ifdef HAS_CLEARCOAT_TEXTURE_TRANSFORM clearcoatTexCoords = computeTextureTransform(clearcoatTexCoords, u_clearcoatTextureTransform); #endif - float clearcoatFactor = texture(u_clearcoatTexture, clearcoatTexCoords).a; + float clearcoatFactor = texture(u_clearcoatTexture, clearcoatTexCoords).r; #ifdef HAS_CLEARCOAT_FACTOR clearcoatFactor *= u_clearcoatFactor; #endif @@ -371,7 +371,7 @@ void setClearcoat(inout czm_modelMaterial material) #ifdef HAS_CLEARCOAT_ROUGHNESS_TEXTURE_TRANSFORM clearcoatRoughnessTexCoords = computeTextureTransform(clearcoatRoughnessTexCoords, u_clearcoatRoughnessTextureTransform); #endif - float clearcoatRoughness = texture(u_clearcoatRoughnessTexture, clearcoatRoughnessTexCoords).a; + float clearcoatRoughness = texture(u_clearcoatRoughnessTexture, clearcoatRoughnessTexCoords).g; #ifdef HAS_CLEARCOAT_ROUGHNESS_FACTOR clearcoatRoughness *= u_clearcoatRoughnessFactor; #endif From 4e3c5f72ec1b634ba1005c5d89d410c1c7045392 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 28 May 2024 15:32:52 -0400 Subject: [PATCH 10/21] Compute IBL sampling transform on CPU --- packages/engine/Source/Scene/Model/Model.js | 11 ++++++++--- .../Shaders/Model/ImageBasedLightingStageFS.glsl | 10 ++-------- .../engine/Source/Shaders/Model/LightingStageFS.glsl | 8 +------- 3 files changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/engine/Source/Scene/Model/Model.js b/packages/engine/Source/Scene/Model/Model.js index 5cc4449ef316..32b66a706500 100644 --- a/packages/engine/Source/Scene/Model/Model.js +++ b/packages/engine/Source/Scene/Model/Model.js @@ -2250,6 +2250,10 @@ function updatePickIds(model) { } } +// Matrix3 is a row-major constructor. +// The same constructor in GLSL will produce the transpose of this. +const yUpToZUp = new Matrix3(-1, 0, 0, 0, 0, 1, 0, -1, 0); + function updateReferenceMatrices(model, frameState) { const modelMatrix = defined(model._clampedModelMatrix) ? model._clampedModelMatrix @@ -2267,15 +2271,16 @@ function updateReferenceMatrices(model, frameState) { referenceMatrix, iblReferenceFrameMatrix4 ); - iblReferenceFrameMatrix3 = Matrix4.getMatrix3( + iblReferenceFrameMatrix3 = Matrix4.getRotation( iblReferenceFrameMatrix4, iblReferenceFrameMatrix3 ); - iblReferenceFrameMatrix3 = Matrix3.getRotation( + iblReferenceFrameMatrix3 = Matrix3.transpose( iblReferenceFrameMatrix3, iblReferenceFrameMatrix3 ); - model._iblReferenceFrameMatrix = Matrix3.transpose( + model._iblReferenceFrameMatrix = Matrix3.multiply( + yUpToZUp, iblReferenceFrameMatrix3, model._iblReferenceFrameMatrix ); diff --git a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl index 33ae7b47a7c9..40d0052440b7 100644 --- a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl @@ -144,13 +144,7 @@ vec3 textureIBL( czm_modelMaterial material ) { // Find the direction in which to sample the environment map - const mat3 yUpToZUp = mat3( - -1.0, 0.0, 0.0, - 0.0, 0.0, -1.0, - 0.0, 1.0, 0.0 - ); - mat3 cubeDirTransform = yUpToZUp * model_iblReferenceFrameMatrix; - vec3 cubeDir = normalize(cubeDirTransform * normalize(reflect(-viewDirectionEC, normalEC))); + vec3 cubeDir = normalize(model_iblReferenceFrameMatrix * normalize(reflect(-viewDirectionEC, normalEC))); #ifdef DIFFUSE_IBL vec3 diffuseContribution = computeDiffuseIBL(cubeDir) * material.diffuse; @@ -168,7 +162,7 @@ vec3 textureIBL( float bendFactor = 1.0 - material.anisotropyStrength * (1.0 - roughness); float bendFactorPow4 = bendFactor * bendFactor * bendFactor * bendFactor; vec3 bentNormal = normalize(mix(anisotropicNormal, normalEC, bendFactorPow4)); - cubeDir = normalize(cubeDirTransform * normalize(reflect(-viewDirectionEC, bentNormal))); + cubeDir = normalize(model_iblReferenceFrameMatrix * normalize(reflect(-viewDirectionEC, bentNormal))); #endif #ifdef SPECULAR_IBL diff --git a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl index a1f556e96203..75dd79d9c3dd 100644 --- a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl @@ -42,13 +42,7 @@ vec3 addClearcoatReflection(vec3 baseLayerColor, vec3 position, vec3 lightDirect #ifdef SPECULAR_IBL // Find the direction in which to sample the environment map - const mat3 yUpToZUp = mat3( - -1.0, 0.0, 0.0, - 0.0, 0.0, -1.0, - 0.0, 1.0, 0.0 - ); - mat3 cubeDirTransform = yUpToZUp * model_iblReferenceFrameMatrix; - vec3 cubeDir = normalize(cubeDirTransform * normalize(reflect(-viewDirection, normal))); + vec3 cubeDir = normalize(model_iblReferenceFrameMatrix * normalize(reflect(-viewDirection, normal))); vec3 iblColor = computeSpecularIBL(cubeDir, NdotV, NdotV, f0, roughness); color += iblColor * material.occlusion; #elif defined(USE_IBL_LIGHTING) From 2b1639f79fb0a4b63cd3231b3c64e0c42af1424f Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Tue, 28 May 2024 19:46:40 -0400 Subject: [PATCH 11/21] Decouple diffuse and specular in procedural IBL --- .../Model/ImageBasedLightingStageFS.glsl | 141 +++++++++++------- 1 file changed, 89 insertions(+), 52 deletions(-) diff --git a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl index 40d0052440b7..bf1164e09dbc 100644 --- a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl @@ -1,89 +1,126 @@ -/** - * Compute the light contribution from a procedural sky model - * - * @param {vec3} positionEC The position of the fragment in eye coordinates - * @param {vec3} normalEC The surface normal in eye coordinates - * @param {vec3} lightDirectionEC Unit vector pointing to the light source in eye coordinates. - * @param {czm_modelMaterial} The material properties. - * @return {vec3} The computed HDR color - */ - vec3 proceduralIBL( - vec3 positionEC, - vec3 normalEC, - vec3 lightDirectionEC, - czm_modelMaterial material -) { - vec3 viewDirectionEC = -normalize(positionEC); - vec3 positionWC = vec3(czm_inverseView * vec4(positionEC, 1.0)); - vec3 vWC = -normalize(positionWC); - vec3 r = normalize(czm_inverseViewRotation * normalize(reflect(viewDirectionEC, normalEC))); - - float NdotL = clamp(dot(normalEC, lightDirectionEC), 0.001, 1.0); - float NdotV = abs(dot(normalEC, viewDirectionEC)) + 0.001; +vec3 getDiffuseIrradiance(float horizonDotNadir, float reflectionDotNadir, float smoothstepHeight) +{ + vec3 blueSkyDiffuseColor = vec3(0.7, 0.85, 0.9); + float diffuseIrradianceFromEarth = (1.0 - horizonDotNadir) * (reflectionDotNadir * 0.25 + 0.75) * smoothstepHeight; + float diffuseIrradianceFromSky = (1.0 - smoothstepHeight) * (1.0 - (reflectionDotNadir * 0.25 + 0.25)); + return blueSkyDiffuseColor * clamp(diffuseIrradianceFromEarth + diffuseIrradianceFromSky, 0.0, 1.0); +} - // Figure out if the reflection vector hits the ellipsoid - float vertexRadius = length(positionWC); - float horizonDotNadir = 1.0 - min(1.0, czm_ellipsoidRadii.x / vertexRadius); - float reflectionDotNadir = dot(r, normalize(positionWC)); +vec3 getSpecularIrradiance(vec3 reflectionWC, float horizonDotNadir, float reflectionDotNadir, float smoothstepHeight, float roughness) +{ // Flipping the X vector is a cheap way to get the inverse of czm_temeToPseudoFixed, since that's a rotation about Z. - r.x = -r.x; - r = -normalize(czm_temeToPseudoFixed * r); - r.x = -r.x; - - vec3 diffuseColor = material.diffuse; - float roughness = material.roughness; - vec3 f0 = material.specular; + reflectionWC.x = -reflectionWC.x; + reflectionWC = -normalize(czm_temeToPseudoFixed * reflectionWC); + reflectionWC.x = -reflectionWC.x; float inverseRoughness = 1.04 - roughness; inverseRoughness *= inverseRoughness; - vec3 sceneSkyBox = czm_textureCube(czm_environmentMap, r).rgb * inverseRoughness; + vec3 sceneSkyBox = czm_textureCube(czm_environmentMap, reflectionWC).rgb * inverseRoughness; - float atmosphereHeight = 0.05; - float blendRegionSize = 0.1 * ((1.0 - inverseRoughness) * 8.0 + 1.1 - horizonDotNadir); - float blendRegionOffset = roughness * -1.0; - float farAboveHorizon = clamp(horizonDotNadir - blendRegionSize * 0.5 + blendRegionOffset, 1.0e-10 - blendRegionSize, 0.99999); - float aroundHorizon = clamp(horizonDotNadir + blendRegionSize * 0.5, 1.0e-10 - blendRegionSize, 0.99999); - float farBelowHorizon = clamp(horizonDotNadir + blendRegionSize * 1.5, 1.0e-10 - blendRegionSize, 0.99999); - float smoothstepHeight = smoothstep(0.0, atmosphereHeight, horizonDotNadir); + // Compute colors at different angles relative to the horizon vec3 belowHorizonColor = mix(vec3(0.1, 0.15, 0.25), vec3(0.4, 0.7, 0.9), smoothstepHeight); vec3 nadirColor = belowHorizonColor * 0.5; vec3 aboveHorizonColor = mix(vec3(0.9, 1.0, 1.2), belowHorizonColor, roughness * 0.5); vec3 blueSkyColor = mix(vec3(0.18, 0.26, 0.48), aboveHorizonColor, reflectionDotNadir * inverseRoughness * 0.5 + 0.75); vec3 zenithColor = mix(blueSkyColor, sceneSkyBox, smoothstepHeight); - vec3 blueSkyDiffuseColor = vec3(0.7, 0.85, 0.9); - float diffuseIrradianceFromEarth = (1.0 - horizonDotNadir) * (reflectionDotNadir * 0.25 + 0.75) * smoothstepHeight; - float diffuseIrradianceFromSky = (1.0 - smoothstepHeight) * (1.0 - (reflectionDotNadir * 0.25 + 0.25)); - vec3 diffuseIrradiance = blueSkyDiffuseColor * clamp(diffuseIrradianceFromEarth + diffuseIrradianceFromSky, 0.0, 1.0); + + // Compute blend zones + float blendRegionSize = 0.1 * ((1.0 - inverseRoughness) * 8.0 + 1.1 - horizonDotNadir); + float blendRegionOffset = roughness * -1.0; + float farAboveHorizon = clamp(horizonDotNadir - blendRegionSize * 0.5 + blendRegionOffset, 1.0e-10 - blendRegionSize, 0.99999); + float aroundHorizon = clamp(horizonDotNadir + blendRegionSize * 0.5, 1.0e-10 - blendRegionSize, 0.99999); + float farBelowHorizon = clamp(horizonDotNadir + blendRegionSize * 1.5, 1.0e-10 - blendRegionSize, 0.99999); + + // Blend colors float notDistantRough = (1.0 - horizonDotNadir * roughness * 0.8); vec3 specularIrradiance = mix(zenithColor, aboveHorizonColor, smoothstep(farAboveHorizon, aroundHorizon, reflectionDotNadir) * notDistantRough); specularIrradiance = mix(specularIrradiance, belowHorizonColor, smoothstep(aroundHorizon, farBelowHorizon, reflectionDotNadir) * inverseRoughness); specularIrradiance = mix(specularIrradiance, nadirColor, smoothstep(farBelowHorizon, 1.0, reflectionDotNadir) * inverseRoughness); - #ifdef USE_SUN_LUMINANCE - // See the "CIE Clear Sky Model" referenced on page 40 of https://3dvar.com/Green2003Spherical.pdf + return specularIrradiance; +} + +#ifdef USE_SUN_LUMINANCE +float clampedDot(vec3 x, vec3 y) +{ + return clamp(dot(x, y), 0.001, 1.0); +} +/** + * Sun luminance following the "CIE Clear Sky Model" + * See page 40 of https://3dvar.com/Green2003Spherical.pdf + * + * @param {vec3} positionWC The position of the fragment in world coordinates. + * @param {vec3} normalEC The surface normal in eye coordinates. + * @param {vec3} lightDirectionEC Unit vector pointing to the light source in eye coordinates. + * @return {float} The computed sun luminance. + */ +float getSunLuminance(vec3 positionWC, vec3 normalEC, vec3 lightDirectionEC) +{ + vec3 normalWC = normalize(czm_inverseViewRotation * normalEC); + vec3 lightDirectionWC = normalize(czm_inverseViewRotation * lightDirectionEC); + vec3 vWC = -normalize(positionWC); + // Angle between sun and zenith. - float LdotZenith = clamp(dot(normalize(czm_inverseViewRotation * lightDirectionEC), vWC), 0.001, 1.0); + float LdotZenith = clampedDot(lightDirectionWC, vWC); float S = acos(LdotZenith); // Angle between zenith and current pixel - float NdotZenith = clamp(dot(normalize(czm_inverseViewRotation * normalEC), vWC), 0.001, 1.0); + float NdotZenith = clampedDot(normalWC, vWC); // Angle between sun and current pixel + float NdotL = clampedDot(normalEC, lightDirectionEC); float gamma = acos(NdotL); + float numerator = ((0.91 + 10.0 * exp(-3.0 * gamma) + 0.45 * NdotL * NdotL) * (1.0 - exp(-0.32 / NdotZenith))); float denominator = (0.91 + 10.0 * exp(-3.0 * S) + 0.45 * LdotZenith * LdotZenith) * (1.0 - exp(-0.32)); - float luminance = model_luminanceAtZenith * (numerator / denominator); - #endif + return model_luminanceAtZenith * (numerator / denominator); +} +#endif +/** + * Compute the light contribution from a procedural sky model + * + * @param {vec3} positionEC The position of the fragment in eye coordinates. + * @param {vec3} normalEC The surface normal in eye coordinates. + * @param {vec3} lightDirectionEC Unit vector pointing to the light source in eye coordinates. + * @param {czm_modelMaterial} The material properties. + * @return {vec3} The computed HDR color + */ + vec3 proceduralIBL( + vec3 positionEC, + vec3 normalEC, + vec3 lightDirectionEC, + czm_modelMaterial material +) { + vec3 viewDirectionEC = -normalize(positionEC); + vec3 positionWC = vec3(czm_inverseView * vec4(positionEC, 1.0)); + vec3 reflectionWC = normalize(czm_inverseViewRotation * normalize(reflect(viewDirectionEC, normalEC))); + + // Figure out if the reflection vector hits the ellipsoid + // TODO: use both ellipsoid radii, and the direction of positionWC? + float horizonDotNadir = 1.0 - min(1.0, czm_ellipsoidRadii.x / length(positionWC)); + float reflectionDotNadir = dot(reflectionWC, normalize(positionWC)); + float atmosphereHeight = 0.05; + float smoothstepHeight = smoothstep(0.0, atmosphereHeight, horizonDotNadir); + + float roughness = material.roughness; + vec3 f0 = material.specular; + + vec3 specularIrradiance = getSpecularIrradiance(reflectionWC, horizonDotNadir, reflectionDotNadir, smoothstepHeight, roughness); + float NdotV = abs(dot(normalEC, viewDirectionEC)) + 0.001; vec2 brdfLut = texture(czm_brdfLut, vec2(NdotV, roughness)).rg; vec3 specularColor = czm_srgbToLinear(f0 * brdfLut.x + brdfLut.y); vec3 specularContribution = specularIrradiance * specularColor * model_iblFactor.y; #ifdef USE_SPECULAR specularContribution *= material.specularWeight; #endif + + vec3 diffuseIrradiance = getDiffuseIrradiance(horizonDotNadir, reflectionDotNadir, smoothstepHeight); + vec3 diffuseColor = material.diffuse; vec3 diffuseContribution = diffuseIrradiance * diffuseColor * model_iblFactor.x; + vec3 iblColor = specularContribution + diffuseContribution; #ifdef USE_SUN_LUMINANCE - iblColor *= luminance; + iblColor *= getSunLuminance(positionWC, normalEC, lightDirectionEC); #endif return iblColor; From 3897c5dbd05334a67f6ec60fd9af58adb1092f15 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Wed, 29 May 2024 13:12:28 -0400 Subject: [PATCH 12/21] Apply procedural IBL to clearcoat --- .../Model/ImageBasedLightingStageFS.glsl | 72 ++++++++++++------- .../Source/Shaders/Model/LightingStageFS.glsl | 15 +++- 2 files changed, 60 insertions(+), 27 deletions(-) diff --git a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl index bf1164e09dbc..d198750b808d 100644 --- a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl @@ -1,12 +1,42 @@ -vec3 getDiffuseIrradiance(float horizonDotNadir, float reflectionDotNadir, float smoothstepHeight) +/** + * Compute some metrics for a procedural sky lighting model + * + * @param {vec3} positionWC The position of the fragment in world coordinates. + * @param {vec3} reflectionWC A unit vector in the direction of the reflection, in world coordinates. + * @return {vec3} The dot products of the horizon and reflection directions with the nadir, and an atmosphere boundary distance. + */ +vec3 getSkyMetrics(vec3 positionWC, vec3 reflectionWC) +{ + // Figure out if the reflection vector hits the ellipsoid + // TODO: use both ellipsoid radii, and the direction of positionWC? + float horizonDotNadir = 1.0 - min(1.0, czm_ellipsoidRadii.x / length(positionWC)); + float reflectionDotNadir = dot(reflectionWC, normalize(positionWC)); + float atmosphereHeight = 0.05; + float smoothstepHeight = smoothstep(0.0, atmosphereHeight, horizonDotNadir); + return vec3(horizonDotNadir, reflectionDotNadir, smoothstepHeight); +} + +/** + * Compute the diffuse irradiance for a procedural sky lighting model + * + * @param {vec3} skyMetrics The dot products of the horizon and reflection directions with the nadir, and an atmosphere boundary distance. + * @return {vec3} The computed diffuse irradiance + */ +vec3 getDiffuseIrradiance(vec3 skyMetrics) { vec3 blueSkyDiffuseColor = vec3(0.7, 0.85, 0.9); - float diffuseIrradianceFromEarth = (1.0 - horizonDotNadir) * (reflectionDotNadir * 0.25 + 0.75) * smoothstepHeight; - float diffuseIrradianceFromSky = (1.0 - smoothstepHeight) * (1.0 - (reflectionDotNadir * 0.25 + 0.25)); + float diffuseIrradianceFromEarth = (1.0 - skyMetrics.x) * (skyMetrics.y * 0.25 + 0.75) * skyMetrics.z; + float diffuseIrradianceFromSky = (1.0 - skyMetrics.z) * (1.0 - (skyMetrics.y * 0.25 + 0.25)); return blueSkyDiffuseColor * clamp(diffuseIrradianceFromEarth + diffuseIrradianceFromSky, 0.0, 1.0); } -vec3 getSpecularIrradiance(vec3 reflectionWC, float horizonDotNadir, float reflectionDotNadir, float smoothstepHeight, float roughness) +/** + * Compute the specular irradiance for a procedural sky lighting model + * + * @param {vec3} skyMetrics The dot products of the horizon and reflection directions with the nadir, and an atmosphere boundary distance. + * @return {vec3} The computed specular irradiance + */ +vec3 getSpecularIrradiance(vec3 reflectionWC, vec3 skyMetrics, float roughness) { // Flipping the X vector is a cheap way to get the inverse of czm_temeToPseudoFixed, since that's a rotation about Z. reflectionWC.x = -reflectionWC.x; @@ -18,24 +48,24 @@ vec3 getSpecularIrradiance(vec3 reflectionWC, float horizonDotNadir, float refle vec3 sceneSkyBox = czm_textureCube(czm_environmentMap, reflectionWC).rgb * inverseRoughness; // Compute colors at different angles relative to the horizon - vec3 belowHorizonColor = mix(vec3(0.1, 0.15, 0.25), vec3(0.4, 0.7, 0.9), smoothstepHeight); + vec3 belowHorizonColor = mix(vec3(0.1, 0.15, 0.25), vec3(0.4, 0.7, 0.9), skyMetrics.z); vec3 nadirColor = belowHorizonColor * 0.5; vec3 aboveHorizonColor = mix(vec3(0.9, 1.0, 1.2), belowHorizonColor, roughness * 0.5); - vec3 blueSkyColor = mix(vec3(0.18, 0.26, 0.48), aboveHorizonColor, reflectionDotNadir * inverseRoughness * 0.5 + 0.75); - vec3 zenithColor = mix(blueSkyColor, sceneSkyBox, smoothstepHeight); + vec3 blueSkyColor = mix(vec3(0.18, 0.26, 0.48), aboveHorizonColor, skyMetrics.y * inverseRoughness * 0.5 + 0.75); + vec3 zenithColor = mix(blueSkyColor, sceneSkyBox, skyMetrics.z); // Compute blend zones - float blendRegionSize = 0.1 * ((1.0 - inverseRoughness) * 8.0 + 1.1 - horizonDotNadir); + float blendRegionSize = 0.1 * ((1.0 - inverseRoughness) * 8.0 + 1.1 - skyMetrics.x); float blendRegionOffset = roughness * -1.0; - float farAboveHorizon = clamp(horizonDotNadir - blendRegionSize * 0.5 + blendRegionOffset, 1.0e-10 - blendRegionSize, 0.99999); - float aroundHorizon = clamp(horizonDotNadir + blendRegionSize * 0.5, 1.0e-10 - blendRegionSize, 0.99999); - float farBelowHorizon = clamp(horizonDotNadir + blendRegionSize * 1.5, 1.0e-10 - blendRegionSize, 0.99999); + float farAboveHorizon = clamp(skyMetrics.x - blendRegionSize * 0.5 + blendRegionOffset, 1.0e-10 - blendRegionSize, 0.99999); + float aroundHorizon = clamp(skyMetrics.x + blendRegionSize * 0.5, 1.0e-10 - blendRegionSize, 0.99999); + float farBelowHorizon = clamp(skyMetrics.x + blendRegionSize * 1.5, 1.0e-10 - blendRegionSize, 0.99999); // Blend colors - float notDistantRough = (1.0 - horizonDotNadir * roughness * 0.8); - vec3 specularIrradiance = mix(zenithColor, aboveHorizonColor, smoothstep(farAboveHorizon, aroundHorizon, reflectionDotNadir) * notDistantRough); - specularIrradiance = mix(specularIrradiance, belowHorizonColor, smoothstep(aroundHorizon, farBelowHorizon, reflectionDotNadir) * inverseRoughness); - specularIrradiance = mix(specularIrradiance, nadirColor, smoothstep(farBelowHorizon, 1.0, reflectionDotNadir) * inverseRoughness); + float notDistantRough = (1.0 - skyMetrics.x * roughness * 0.8); + vec3 specularIrradiance = mix(zenithColor, aboveHorizonColor, smoothstep(farAboveHorizon, aroundHorizon, skyMetrics.y) * notDistantRough); + specularIrradiance = mix(specularIrradiance, belowHorizonColor, smoothstep(aroundHorizon, farBelowHorizon, skyMetrics.y) * inverseRoughness); + specularIrradiance = mix(specularIrradiance, nadirColor, smoothstep(farBelowHorizon, 1.0, skyMetrics.y) * inverseRoughness); return specularIrradiance; } @@ -93,18 +123,12 @@ float getSunLuminance(vec3 positionWC, vec3 normalEC, vec3 lightDirectionEC) vec3 viewDirectionEC = -normalize(positionEC); vec3 positionWC = vec3(czm_inverseView * vec4(positionEC, 1.0)); vec3 reflectionWC = normalize(czm_inverseViewRotation * normalize(reflect(viewDirectionEC, normalEC))); - - // Figure out if the reflection vector hits the ellipsoid - // TODO: use both ellipsoid radii, and the direction of positionWC? - float horizonDotNadir = 1.0 - min(1.0, czm_ellipsoidRadii.x / length(positionWC)); - float reflectionDotNadir = dot(reflectionWC, normalize(positionWC)); - float atmosphereHeight = 0.05; - float smoothstepHeight = smoothstep(0.0, atmosphereHeight, horizonDotNadir); + vec3 skyMetrics = getSkyMetrics(positionWC, reflectionWC); float roughness = material.roughness; vec3 f0 = material.specular; - vec3 specularIrradiance = getSpecularIrradiance(reflectionWC, horizonDotNadir, reflectionDotNadir, smoothstepHeight, roughness); + vec3 specularIrradiance = getSpecularIrradiance(reflectionWC, skyMetrics, roughness); float NdotV = abs(dot(normalEC, viewDirectionEC)) + 0.001; vec2 brdfLut = texture(czm_brdfLut, vec2(NdotV, roughness)).rg; vec3 specularColor = czm_srgbToLinear(f0 * brdfLut.x + brdfLut.y); @@ -113,7 +137,7 @@ float getSunLuminance(vec3 positionWC, vec3 normalEC, vec3 lightDirectionEC) specularContribution *= material.specularWeight; #endif - vec3 diffuseIrradiance = getDiffuseIrradiance(horizonDotNadir, reflectionDotNadir, smoothstepHeight); + vec3 diffuseIrradiance = getDiffuseIrradiance(skyMetrics); vec3 diffuseColor = material.diffuse; vec3 diffuseContribution = diffuseIrradiance * diffuseColor * model_iblFactor.x; diff --git a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl index 75dd79d9c3dd..116b02c03d26 100644 --- a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl @@ -46,9 +46,18 @@ vec3 addClearcoatReflection(vec3 baseLayerColor, vec3 position, vec3 lightDirect vec3 iblColor = computeSpecularIBL(cubeDir, NdotV, NdotV, f0, roughness); color += iblColor * material.occlusion; #elif defined(USE_IBL_LIGHTING) - // TODO: compute specular reflection from IBL - // NOTE: can't use computeIBL (above) because we need to force isotropic reflection (even if base layer is anisotropic) - // scale by occlusion + vec3 positionWC = vec3(czm_inverseView * vec4(position, 1.0)); + vec3 reflectionWC = normalize(czm_inverseViewRotation * normalize(reflect(viewDirection, normal))); + vec3 skyMetrics = getSkyMetrics(positionWC, reflectionWC); + + vec3 specularIrradiance = getSpecularIrradiance(reflectionWC, skyMetrics, roughness); + vec2 brdfLut = texture(czm_brdfLut, vec2(NdotV, roughness)).rg; + vec3 specularColor = czm_srgbToLinear(f0 * brdfLut.x + brdfLut.y); + vec3 iblColor = specularIrradiance * specularColor * model_iblFactor.y; + #ifdef USE_SUN_LUMINANCE + iblColor *= getSunLuminance(positionWC, normal, lightDirection); + #endif + color += iblColor * material.occlusion; #endif float clearcoatFactor = material.clearcoatFactor; From d17af9a740066943bca9ea3534ec5b883b5fa219 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Wed, 29 May 2024 13:31:05 -0400 Subject: [PATCH 13/21] Fix light color for procedural IBL on clearcoat --- .../Shaders/Model/ImageBasedLightingStageFS.glsl | 12 ++++++------ .../engine/Source/Shaders/Model/LightingStageFS.glsl | 8 +++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl index d198750b808d..127ca1b72081 100644 --- a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl @@ -5,7 +5,7 @@ * @param {vec3} reflectionWC A unit vector in the direction of the reflection, in world coordinates. * @return {vec3} The dot products of the horizon and reflection directions with the nadir, and an atmosphere boundary distance. */ -vec3 getSkyMetrics(vec3 positionWC, vec3 reflectionWC) +vec3 getProceduralSkyMetrics(vec3 positionWC, vec3 reflectionWC) { // Figure out if the reflection vector hits the ellipsoid // TODO: use both ellipsoid radii, and the direction of positionWC? @@ -22,7 +22,7 @@ vec3 getSkyMetrics(vec3 positionWC, vec3 reflectionWC) * @param {vec3} skyMetrics The dot products of the horizon and reflection directions with the nadir, and an atmosphere boundary distance. * @return {vec3} The computed diffuse irradiance */ -vec3 getDiffuseIrradiance(vec3 skyMetrics) +vec3 getProceduralDiffuseIrradiance(vec3 skyMetrics) { vec3 blueSkyDiffuseColor = vec3(0.7, 0.85, 0.9); float diffuseIrradianceFromEarth = (1.0 - skyMetrics.x) * (skyMetrics.y * 0.25 + 0.75) * skyMetrics.z; @@ -36,7 +36,7 @@ vec3 getDiffuseIrradiance(vec3 skyMetrics) * @param {vec3} skyMetrics The dot products of the horizon and reflection directions with the nadir, and an atmosphere boundary distance. * @return {vec3} The computed specular irradiance */ -vec3 getSpecularIrradiance(vec3 reflectionWC, vec3 skyMetrics, float roughness) +vec3 getProceduralSpecularIrradiance(vec3 reflectionWC, vec3 skyMetrics, float roughness) { // Flipping the X vector is a cheap way to get the inverse of czm_temeToPseudoFixed, since that's a rotation about Z. reflectionWC.x = -reflectionWC.x; @@ -123,12 +123,12 @@ float getSunLuminance(vec3 positionWC, vec3 normalEC, vec3 lightDirectionEC) vec3 viewDirectionEC = -normalize(positionEC); vec3 positionWC = vec3(czm_inverseView * vec4(positionEC, 1.0)); vec3 reflectionWC = normalize(czm_inverseViewRotation * normalize(reflect(viewDirectionEC, normalEC))); - vec3 skyMetrics = getSkyMetrics(positionWC, reflectionWC); + vec3 skyMetrics = getProceduralSkyMetrics(positionWC, reflectionWC); float roughness = material.roughness; vec3 f0 = material.specular; - vec3 specularIrradiance = getSpecularIrradiance(reflectionWC, skyMetrics, roughness); + vec3 specularIrradiance = getProceduralSpecularIrradiance(reflectionWC, skyMetrics, roughness); float NdotV = abs(dot(normalEC, viewDirectionEC)) + 0.001; vec2 brdfLut = texture(czm_brdfLut, vec2(NdotV, roughness)).rg; vec3 specularColor = czm_srgbToLinear(f0 * brdfLut.x + brdfLut.y); @@ -137,7 +137,7 @@ float getSunLuminance(vec3 positionWC, vec3 normalEC, vec3 lightDirectionEC) specularContribution *= material.specularWeight; #endif - vec3 diffuseIrradiance = getDiffuseIrradiance(skyMetrics); + vec3 diffuseIrradiance = getProceduralDiffuseIrradiance(skyMetrics); vec3 diffuseColor = material.diffuse; vec3 diffuseContribution = diffuseIrradiance * diffuseColor * model_iblFactor.x; diff --git a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl index 116b02c03d26..4b4318899d9a 100644 --- a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl @@ -48,16 +48,18 @@ vec3 addClearcoatReflection(vec3 baseLayerColor, vec3 position, vec3 lightDirect #elif defined(USE_IBL_LIGHTING) vec3 positionWC = vec3(czm_inverseView * vec4(position, 1.0)); vec3 reflectionWC = normalize(czm_inverseViewRotation * normalize(reflect(viewDirection, normal))); - vec3 skyMetrics = getSkyMetrics(positionWC, reflectionWC); + vec3 skyMetrics = getProceduralSkyMetrics(positionWC, reflectionWC); - vec3 specularIrradiance = getSpecularIrradiance(reflectionWC, skyMetrics, roughness); + vec3 specularIrradiance = getProceduralSpecularIrradiance(reflectionWC, skyMetrics, roughness); vec2 brdfLut = texture(czm_brdfLut, vec2(NdotV, roughness)).rg; vec3 specularColor = czm_srgbToLinear(f0 * brdfLut.x + brdfLut.y); vec3 iblColor = specularIrradiance * specularColor * model_iblFactor.y; #ifdef USE_SUN_LUMINANCE iblColor *= getSunLuminance(positionWC, normal, lightDirection); #endif - color += iblColor * material.occlusion; + float maximumComponent = czm_maximumComponent(lightColorHdr); + vec3 clampedLightColor = lightColorHdr / max(maximumComponent, 1.0); + color += clampedLightColor* iblColor * material.occlusion; #endif float clearcoatFactor = material.clearcoatFactor; From 4cbeb617c64dc62e526fce35732dfc7d04313f92 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Wed, 29 May 2024 14:56:49 -0400 Subject: [PATCH 14/21] Use clearcoat normal map when supplied --- .../Source/Shaders/Model/MaterialStageFS.glsl | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl index 301e9b676b65..1e4fc5bb5bff 100644 --- a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl @@ -25,7 +25,9 @@ vec2 getNormalTexCoords() #endif return texCoord; } +#endif +#if defined(HAS_NORMAL_TEXTURE) || defined(HAS_CLEARCOAT_NORMAL_TEXTURE) vec3 computeTangent(in vec3 position, in vec2 normalTexCoords) { vec2 tex_dx = dFdx(normalTexCoords); @@ -110,6 +112,32 @@ vec3 getNormalFromTexture(ProcessedAttributes attributes, vec3 geometryNormal) } #endif +#ifdef HAS_CLEARCOAT_NORMAL_TEXTURE +vec3 getClearcoatNormalFromTexture(ProcessedAttributes attributes, vec3 geometryNormal) +{ + vec2 normalTexCoords = TEXCOORD_CLEARCOAT_NORMAL; + #ifdef HAS_CLEARCOAT_NORMAL_TEXTURE_TRANSFORM + normalTexCoords = vec2(u_clearcoatNormalTextureTransform * vec3(normalTexCoords, 1.0)); + #endif + + // If HAS_BITANGENTS is set, then HAS_TANGENTS is also set + #ifdef HAS_BITANGENTS + vec3 t = attributes.tangentEC; + vec3 b = attributes.bitangentEC; + #else + vec3 t = computeTangent(attributes.positionEC, normalTexCoords); + t = normalize(t - geometryNormal * dot(geometryNormal, t)); + vec3 b = normalize(cross(geometryNormal, t)); + #endif + + mat3 tbn = mat3(t, b, geometryNormal); + vec3 n = texture(u_clearcoatNormalTexture, normalTexCoords).rgb; + vec3 textureNormal = normalize(tbn * (2.0 * n - 1.0)); + + return textureNormal; +} +#endif + #ifdef HAS_NORMALS vec3 computeNormal(ProcessedAttributes attributes) { @@ -347,7 +375,7 @@ void setAnisotropy(inout czm_modelMaterial material, in NormalInfo normalInfo) } #endif #ifdef USE_CLEARCOAT -void setClearcoat(inout czm_modelMaterial material) +void setClearcoat(inout czm_modelMaterial material, in ProcessedAttributes attributes) { #ifdef HAS_CLEARCOAT_TEXTURE vec2 clearcoatTexCoords = TEXCOORD_CLEARCOAT; @@ -389,8 +417,11 @@ void setClearcoat(inout czm_modelMaterial material) // square it to get material roughness // TODO: do we need perceptual roughness for IBL? material.clearcoatRoughness = clearcoatRoughness * clearcoatRoughness; - // TODO: read clear coat normals from a texture (if supplied) - material.clearcoatNormal = material.normalEC; + #ifdef HAS_CLEARCOAT_NORMAL_TEXTURE + material.clearcoatNormal = getClearcoatNormalFromTexture(attributes, attributes.normalEC); + #else + material.clearcoatNormal = attributes.normalEC; + #endif } #endif #endif @@ -455,7 +486,7 @@ void materialStage(inout czm_modelMaterial material, ProcessedAttributes attribu setAnisotropy(material, normalInfo); #endif #ifdef USE_CLEARCOAT - setClearcoat(material); + setClearcoat(material, attributes); #endif #endif } From cca3ddb2b23a30d2b1ec57e1655225147faf6fa9 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 30 May 2024 11:42:55 -0400 Subject: [PATCH 15/21] Address TODO comments --- .../Builtin/Functions/pbrLighting.glsl | 33 ++++++++++++++----- .../Builtin/Structs/modelMaterial.glsl | 4 +-- .../Model/ImageBasedLightingStageFS.glsl | 2 -- .../Source/Shaders/Model/LightingStageFS.glsl | 4 +-- .../Source/Shaders/Model/MaterialStageFS.glsl | 6 +--- 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl index 322c8ed6d894..5dad8f33f87c 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl @@ -50,8 +50,11 @@ float smithVisibilityG1(float NdotV, float roughness) } /** - * geometric shadowing function - * TODO: explain + * Estimate the self-shadowing of the microfacets in a surface + * + * @param {float} roughness The roughness of the material. + * @param {float} NdotL The cosine of the angle between the surface normal and the direction to the light source. + * @param {float} NdotV The cosine of the angle between the surface normal and the direction to the camera. */ float smithVisibilityGGX(float roughness, float NdotL, float NdotV) { @@ -62,8 +65,13 @@ float smithVisibilityGGX(float roughness, float NdotL, float NdotV) } /** - * microfacet distribution function - * TODO: explain + * Estimate the fraction of the microfacets in a surface that are aligned with + * the halfway vector, which is aligned halfway between the directions from + * the fragment to the camera and from the fragment to the light source. + * + * @param {float} roughness The roughness of the material. + * @param {float} NdotH The cosine of the angle between the surface normal and the halfway vector. + * @return {float} The fraction of microfacets aligned to the halfway vector. */ float GGX(float roughness, float NdotH) { @@ -72,12 +80,19 @@ float GGX(float roughness, float NdotH) return roughnessSquared / (czm_pi * f * f); } -// TODO: rename to emphasize this is for direct lighting only (not IBL) -float computeSpecularStrength(vec3 normal, vec3 lightDirection, vec3 viewDirection, vec3 halfwayDirection, float roughness) +/** + * Compute the strength of the specular reflection due to direct lighting. + * + * @param {vec3} normal The surface normal. + * @param {vec3} lightDirection The unit vector pointing from the fragment to the light source. + * @param {vec3} viewDirection The unit vector pointing from the fragment to the camera. + * @param {vec3} halfwayDirection The unit vector pointing from the fragment to halfway between the light source and the camera. + * @param {float} roughness The roughness of the material. + * @return {float} The strength of the specular reflection. + */ +float computeDirectSpecularStrength(vec3 normal, vec3 lightDirection, vec3 viewDirection, vec3 halfwayDirection, float roughness) { - // TODO: why 0.001 and not 0.0? float NdotL = clamp(dot(normal, lightDirection), 0.001, 1.0); - // TODO: why abs here and clamp on the others? What does this do with backside reflections? float NdotV = abs(dot(normal, viewDirection)) + 0.001; float NdotH = clamp(dot(normal, halfwayDirection), 0.0, 1.0); float G = smithVisibilityGGX(roughness, NdotL, NdotV); @@ -136,7 +151,7 @@ vec3 czm_pbrLighting( float D = GGX_anisotropic(alpha, tangentialRoughness, halfwayDirection); vec3 specularContribution = F * G * D; #else - float specularStrength = computeSpecularStrength(normalEC, lightDirectionEC, viewDirectionEC, halfwayDirectionEC, alpha); + float specularStrength = computeDirectSpecularStrength(normalEC, lightDirectionEC, viewDirectionEC, halfwayDirectionEC, alpha); vec3 specularContribution = F * specularStrength; #endif diff --git a/packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl b/packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl index af5dac2ad51d..3802a9a9a068 100644 --- a/packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl +++ b/packages/engine/Source/Shaders/Builtin/Structs/modelMaterial.glsl @@ -10,8 +10,6 @@ * @name czm_modelMaterial * @glslStruct * - * TODO: is this used externally? Can we rename diffuse and specular? - * * @property {vec3} diffuse Incoming light that scatters evenly in all directions. * @property {float} alpha Alpha of this material. 0.0 is completely transparent; 1.0 is completely opaque. * @property {vec3} specular Color of reflected light at normal incidence in PBR materials. This is sometimes referred to as f0 in the literature. @@ -40,6 +38,6 @@ struct czm_modelMaterial { float clearcoatFactor; float clearcoatRoughness; vec3 clearcoatNormal; - // TODO: Add clearcoatF0 when KHR_materials_ior is implemented + // Add clearcoatF0 when KHR_materials_ior is implemented #endif }; diff --git a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl index 127ca1b72081..e94ffd0a0dcc 100644 --- a/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/ImageBasedLightingStageFS.glsl @@ -8,7 +8,6 @@ vec3 getProceduralSkyMetrics(vec3 positionWC, vec3 reflectionWC) { // Figure out if the reflection vector hits the ellipsoid - // TODO: use both ellipsoid radii, and the direction of positionWC? float horizonDotNadir = 1.0 - min(1.0, czm_ellipsoidRadii.x / length(positionWC)); float reflectionDotNadir = dot(reflectionWC, normalize(positionWC)); float atmosphereHeight = 0.05; @@ -180,7 +179,6 @@ vec3 computeSpecularIBL(vec3 cubeDir, float NdotV, float VdotH, vec3 f0, float r vec3 f90 = vec3(clamp(reflectance * 25.0, 0.0, 1.0)); vec3 F = fresnelSchlick2(f0, f90, VdotH); - // TODO: should this be using perceptual roughness instead of alpha roughness (which is squared)? vec2 brdfLut = texture(czm_brdfLut, vec2(NdotV, roughness)).rg; vec3 specularSample = sampleSpecularEnvironment(cubeDir, roughness); diff --git a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl index 4b4318899d9a..0eb76640c611 100644 --- a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl @@ -17,13 +17,11 @@ vec3 computeIBL(vec3 position, vec3 normal, vec3 lightDirection, vec3 lightColor #endif #ifdef USE_CLEARCOAT -// TODO: make sure KHR_materials_specular properties are not being used in any functions called by this method. vec3 addClearcoatReflection(vec3 baseLayerColor, vec3 position, vec3 lightDirection, vec3 lightColorHdr, czm_modelMaterial material) { vec3 viewDirection = -normalize(position); vec3 halfwayDirection = normalize(viewDirection + lightDirection); vec3 normal = material.clearcoatNormal; - // TODO: why clamp to 0.001?? float NdotL = clamp(dot(normal, lightDirection), 0.001, 1.0); // clearcoatF0 = vec3(pow((ior - 1.0) / (ior + 1.0), 2.0)), but without KHR_materials_ior, ior is a constant 1.5. @@ -36,7 +34,7 @@ vec3 addClearcoatReflection(vec3 baseLayerColor, vec3 position, vec3 lightDirect // compute specular reflection from direct lighting float roughness = material.clearcoatRoughness; - float directStrength = computeSpecularStrength(normal, lightDirection, viewDirection, halfwayDirection, roughness); + float directStrength = computeDirectSpecularStrength(normal, lightDirection, viewDirection, halfwayDirection, roughness); vec3 directReflection = F * directStrength * NdotL; vec3 color = lightColorHdr * directReflection; diff --git a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl index 1e4fc5bb5bff..b4326757bd38 100644 --- a/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/MaterialStageFS.glsl @@ -264,7 +264,6 @@ float setMetallicRoughness(inout czm_modelMaterial material) vec3 metallicRoughness = texture(u_metallicRoughnessTexture, metallicRoughnessTexCoords).rgb; float metalness = clamp(metallicRoughness.b, 0.0, 1.0); - // TODO: WHY is roughness clamped to 0.04 ?? float roughness = clamp(metallicRoughness.g, 0.04, 1.0); #ifdef HAS_METALLIC_FACTOR metalness = clamp(metalness * u_metallicFactor, 0.0, 1.0); @@ -281,7 +280,6 @@ float setMetallicRoughness(inout czm_modelMaterial material) #endif #ifdef HAS_ROUGHNESS_FACTOR - // TODO: WHY is roughness clamped to 0.04 ?? float roughness = clamp(u_roughnessFactor, 0.04, 1.0); #else float roughness = 1.0; @@ -299,7 +297,6 @@ float setMetallicRoughness(inout czm_modelMaterial material) // roughness is authored as perceptual roughness // square it to get material roughness - // TODO: do we need perceptual roughness for IBL? material.roughness = roughness * roughness; return metalness; @@ -390,7 +387,7 @@ void setClearcoat(inout czm_modelMaterial material, in ProcessedAttributes attri #ifdef HAS_CLEARCOAT_FACTOR float clearcoatFactor = u_clearcoatFactor; #else - // TODO: this case should turn the whole extension off + // PERFORMANCE_IDEA: this case should turn the whole extension off float clearcoatFactor = 0.0; #endif #endif @@ -415,7 +412,6 @@ void setClearcoat(inout czm_modelMaterial material, in ProcessedAttributes attri material.clearcoatFactor = clearcoatFactor; // roughness is authored as perceptual roughness // square it to get material roughness - // TODO: do we need perceptual roughness for IBL? material.clearcoatRoughness = clearcoatRoughness * clearcoatRoughness; #ifdef HAS_CLEARCOAT_NORMAL_TEXTURE material.clearcoatNormal = getClearcoatNormalFromTexture(attributes, attributes.normalEC); From 6eb7cd01ad7d1773e553ada111077b05a148f292 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 30 May 2024 12:00:23 -0400 Subject: [PATCH 16/21] Update MaterialPipelineStageSpec to ES6 --- .../Scene/Model/MaterialPipelineStageSpec.js | 1076 ++++++++--------- 1 file changed, 471 insertions(+), 605 deletions(-) diff --git a/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js b/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js index 69428c20b785..389be73969a9 100644 --- a/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js +++ b/packages/engine/Specs/Scene/Model/MaterialPipelineStageSpec.js @@ -117,319 +117,277 @@ describe( }; } - it("processes default material", function () { - return loadGltf(triangle).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[0].primitives[0]; - - const renderResources = mockRenderResources(); - const shaderBuilder = renderResources.shaderBuilder; - const uniformMap = renderResources.uniformMap; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - - ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, []); - - ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ - "USE_METALLIC_ROUGHNESS", - ]); - - const expectedUniforms = {}; - expectUniformMap(uniformMap, expectedUniforms); - }); + it("processes default material", async function () { + const gltfLoader = await loadGltf(triangle); + const primitive = gltfLoader.components.nodes[0].primitives[0]; + + const renderResources = mockRenderResources(); + const { shaderBuilder, uniformMap } = renderResources; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + + ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, []); + + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "USE_METALLIC_ROUGHNESS", + ]); + + const expectedUniforms = {}; + expectUniformMap(uniformMap, expectedUniforms); }); - it("adds material and metallic roughness uniforms", function () { - return loadGltf(boomBox).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[0].primitives[0]; - const renderResources = mockRenderResources(); - const shaderBuilder = renderResources.shaderBuilder; - const uniformMap = renderResources.uniformMap; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - - ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ - "uniform sampler2D u_baseColorTexture;", - "uniform sampler2D u_emissiveTexture;", - "uniform sampler2D u_metallicRoughnessTexture;", - "uniform sampler2D u_normalTexture;", - "uniform sampler2D u_occlusionTexture;", - "uniform vec3 u_emissiveFactor;", - ]); - - ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ - "HAS_BASE_COLOR_TEXTURE", - "HAS_EMISSIVE_FACTOR", - "HAS_EMISSIVE_TEXTURE", - "HAS_METALLIC_ROUGHNESS_TEXTURE", - "HAS_NORMAL_TEXTURE", - "HAS_OCCLUSION_TEXTURE", - "TEXCOORD_BASE_COLOR v_texCoord_0", - "TEXCOORD_EMISSIVE v_texCoord_0", - "TEXCOORD_METALLIC_ROUGHNESS v_texCoord_0", - "TEXCOORD_NORMAL v_texCoord_0", - "TEXCOORD_OCCLUSION v_texCoord_0", - "USE_METALLIC_ROUGHNESS", - ]); - - const metallicRoughness = primitive.material.metallicRoughness; - const material = primitive.material; - const expectedUniforms = { - u_emissiveTexture: material.emissiveTexture.texture, - u_emissiveFactor: material.emissiveFactor, - u_normalTexture: material.normalTexture.texture, - u_occlusionTexture: material.occlusionTexture.texture, - u_baseColorTexture: metallicRoughness.baseColorTexture.texture, - u_metallicRoughnessTexture: - metallicRoughness.metallicRoughnessTexture.texture, - }; - expectUniformMap(uniformMap, expectedUniforms); - }); + it("adds material and metallic roughness uniforms", async function () { + const gltfLoader = await loadGltf(boomBox); + const primitive = gltfLoader.components.nodes[0].primitives[0]; + const renderResources = mockRenderResources(); + const { shaderBuilder, uniformMap } = renderResources; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + + ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform sampler2D u_baseColorTexture;", + "uniform sampler2D u_emissiveTexture;", + "uniform sampler2D u_metallicRoughnessTexture;", + "uniform sampler2D u_normalTexture;", + "uniform sampler2D u_occlusionTexture;", + "uniform vec3 u_emissiveFactor;", + ]); + + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_BASE_COLOR_TEXTURE", + "HAS_EMISSIVE_FACTOR", + "HAS_EMISSIVE_TEXTURE", + "HAS_METALLIC_ROUGHNESS_TEXTURE", + "HAS_NORMAL_TEXTURE", + "HAS_OCCLUSION_TEXTURE", + "TEXCOORD_BASE_COLOR v_texCoord_0", + "TEXCOORD_EMISSIVE v_texCoord_0", + "TEXCOORD_METALLIC_ROUGHNESS v_texCoord_0", + "TEXCOORD_NORMAL v_texCoord_0", + "TEXCOORD_OCCLUSION v_texCoord_0", + "USE_METALLIC_ROUGHNESS", + ]); + + const metallicRoughness = primitive.material.metallicRoughness; + const material = primitive.material; + const expectedUniforms = { + u_emissiveTexture: material.emissiveTexture.texture, + u_emissiveFactor: material.emissiveFactor, + u_normalTexture: material.normalTexture.texture, + u_occlusionTexture: material.occlusionTexture.texture, + u_baseColorTexture: metallicRoughness.baseColorTexture.texture, + u_metallicRoughnessTexture: + metallicRoughness.metallicRoughnessTexture.texture, + }; + expectUniformMap(uniformMap, expectedUniforms); }); - it("adds metallic roughness uniforms without defaults", function () { - return loadGltf(boomBox).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[0].primitives[0]; - - const metallicRoughness = primitive.material.metallicRoughness; - metallicRoughness.baseColorFactor = new Cartesian4(0.5, 0.5, 0.5, 0.5); - metallicRoughness.metallicFactor = 0.5; - metallicRoughness.roughnessFactor = 0.5; - - const renderResources = mockRenderResources(); - const shaderBuilder = renderResources.shaderBuilder; - const uniformMap = renderResources.uniformMap; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - - ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ - "uniform float u_metallicFactor;", - "uniform float u_roughnessFactor;", - "uniform sampler2D u_baseColorTexture;", - "uniform sampler2D u_emissiveTexture;", - "uniform sampler2D u_metallicRoughnessTexture;", - "uniform sampler2D u_normalTexture;", - "uniform sampler2D u_occlusionTexture;", - "uniform vec3 u_emissiveFactor;", - "uniform vec4 u_baseColorFactor;", - ]); - - ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ - "HAS_BASE_COLOR_FACTOR", - "HAS_BASE_COLOR_TEXTURE", - "HAS_EMISSIVE_FACTOR", - "HAS_EMISSIVE_TEXTURE", - "HAS_METALLIC_FACTOR", - "HAS_METALLIC_ROUGHNESS_TEXTURE", - "HAS_NORMAL_TEXTURE", - "HAS_OCCLUSION_TEXTURE", - "HAS_ROUGHNESS_FACTOR", - "TEXCOORD_BASE_COLOR v_texCoord_0", - "TEXCOORD_EMISSIVE v_texCoord_0", - "TEXCOORD_METALLIC_ROUGHNESS v_texCoord_0", - "TEXCOORD_NORMAL v_texCoord_0", - "TEXCOORD_OCCLUSION v_texCoord_0", - "USE_METALLIC_ROUGHNESS", - ]); - - const expectedUniforms = { - u_baseColorTexture: metallicRoughness.baseColorTexture.texture, - u_baseColorFactor: metallicRoughness.baseColorFactor, - u_metallicRoughnessTexture: - metallicRoughness.metallicRoughnessTexture.texture, - u_metallicFactor: metallicRoughness.metallicFactor, - u_roughnessFactor: metallicRoughness.roughnessFactor, - }; - expectUniformMap(uniformMap, expectedUniforms); - }); + it("adds metallic roughness uniforms without defaults", async function () { + const gltfLoader = await loadGltf(boomBox); + const primitive = gltfLoader.components.nodes[0].primitives[0]; + + const metallicRoughness = primitive.material.metallicRoughness; + metallicRoughness.baseColorFactor = new Cartesian4(0.5, 0.5, 0.5, 0.5); + metallicRoughness.metallicFactor = 0.5; + metallicRoughness.roughnessFactor = 0.5; + + const renderResources = mockRenderResources(); + const { shaderBuilder, uniformMap } = renderResources; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + + ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform float u_metallicFactor;", + "uniform float u_roughnessFactor;", + "uniform sampler2D u_baseColorTexture;", + "uniform sampler2D u_emissiveTexture;", + "uniform sampler2D u_metallicRoughnessTexture;", + "uniform sampler2D u_normalTexture;", + "uniform sampler2D u_occlusionTexture;", + "uniform vec3 u_emissiveFactor;", + "uniform vec4 u_baseColorFactor;", + ]); + + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_BASE_COLOR_FACTOR", + "HAS_BASE_COLOR_TEXTURE", + "HAS_EMISSIVE_FACTOR", + "HAS_EMISSIVE_TEXTURE", + "HAS_METALLIC_FACTOR", + "HAS_METALLIC_ROUGHNESS_TEXTURE", + "HAS_NORMAL_TEXTURE", + "HAS_OCCLUSION_TEXTURE", + "HAS_ROUGHNESS_FACTOR", + "TEXCOORD_BASE_COLOR v_texCoord_0", + "TEXCOORD_EMISSIVE v_texCoord_0", + "TEXCOORD_METALLIC_ROUGHNESS v_texCoord_0", + "TEXCOORD_NORMAL v_texCoord_0", + "TEXCOORD_OCCLUSION v_texCoord_0", + "USE_METALLIC_ROUGHNESS", + ]); + + const expectedUniforms = { + u_baseColorTexture: metallicRoughness.baseColorTexture.texture, + u_baseColorFactor: metallicRoughness.baseColorFactor, + u_metallicRoughnessTexture: + metallicRoughness.metallicRoughnessTexture.texture, + u_metallicFactor: metallicRoughness.metallicFactor, + u_roughnessFactor: metallicRoughness.roughnessFactor, + }; + expectUniformMap(uniformMap, expectedUniforms); }); - it("doesn't add emissive uniforms when emissive factor is default", function () { - return loadGltf(boomBox).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[0].primitives[0]; - - const material = primitive.material; - material.emissiveFactor = Cartesian3.clone( - ModelComponents.Material.DEFAULT_EMISSIVE_FACTOR - ); - const metallicRoughness = material.metallicRoughness; - - const renderResources = mockRenderResources(); - const shaderBuilder = renderResources.shaderBuilder; - const uniformMap = renderResources.uniformMap; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - - ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ - "uniform sampler2D u_baseColorTexture;", - "uniform sampler2D u_metallicRoughnessTexture;", - "uniform sampler2D u_normalTexture;", - "uniform sampler2D u_occlusionTexture;", - ]); - - ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ - "HAS_BASE_COLOR_TEXTURE", - "HAS_METALLIC_ROUGHNESS_TEXTURE", - "HAS_NORMAL_TEXTURE", - "HAS_OCCLUSION_TEXTURE", - "TEXCOORD_BASE_COLOR v_texCoord_0", - "TEXCOORD_METALLIC_ROUGHNESS v_texCoord_0", - "TEXCOORD_NORMAL v_texCoord_0", - "TEXCOORD_OCCLUSION v_texCoord_0", - "USE_METALLIC_ROUGHNESS", - ]); - - const expectedUniforms = { - u_normalTexture: material.normalTexture.texture, - u_occlusionTexture: material.occlusionTexture.texture, - u_baseColorTexture: metallicRoughness.baseColorTexture.texture, - u_metallicRoughnessTexture: - metallicRoughness.metallicRoughnessTexture.texture, - }; - expectUniformMap(uniformMap, expectedUniforms); - }); + it("doesn't add emissive uniforms when emissive factor is default", async function () { + const gltfLoader = await loadGltf(boomBox); + const primitive = gltfLoader.components.nodes[0].primitives[0]; + + const material = primitive.material; + material.emissiveFactor = Cartesian3.clone( + ModelComponents.Material.DEFAULT_EMISSIVE_FACTOR + ); + const metallicRoughness = material.metallicRoughness; + + const renderResources = mockRenderResources(); + const { shaderBuilder, uniformMap } = renderResources; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + + ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform sampler2D u_baseColorTexture;", + "uniform sampler2D u_metallicRoughnessTexture;", + "uniform sampler2D u_normalTexture;", + "uniform sampler2D u_occlusionTexture;", + ]); + + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_BASE_COLOR_TEXTURE", + "HAS_METALLIC_ROUGHNESS_TEXTURE", + "HAS_NORMAL_TEXTURE", + "HAS_OCCLUSION_TEXTURE", + "TEXCOORD_BASE_COLOR v_texCoord_0", + "TEXCOORD_METALLIC_ROUGHNESS v_texCoord_0", + "TEXCOORD_NORMAL v_texCoord_0", + "TEXCOORD_OCCLUSION v_texCoord_0", + "USE_METALLIC_ROUGHNESS", + ]); + + const expectedUniforms = { + u_normalTexture: material.normalTexture.texture, + u_occlusionTexture: material.occlusionTexture.texture, + u_baseColorTexture: metallicRoughness.baseColorTexture.texture, + u_metallicRoughnessTexture: + metallicRoughness.metallicRoughnessTexture.texture, + }; + expectUniformMap(uniformMap, expectedUniforms); }); - it("adds specular glossiness uniforms", function () { - return loadGltf(boomBoxSpecularGlossiness).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[0].primitives[0]; - const renderResources = mockRenderResources(); - const shaderBuilder = renderResources.shaderBuilder; - const uniformMap = renderResources.uniformMap; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ - "uniform float u_glossinessFactor;", - "uniform sampler2D u_diffuseTexture;", - "uniform sampler2D u_emissiveTexture;", - "uniform sampler2D u_normalTexture;", - "uniform sampler2D u_occlusionTexture;", - "uniform sampler2D u_specularGlossinessTexture;", - "uniform vec3 u_emissiveFactor;", - ]); - - ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ - "HAS_DIFFUSE_TEXTURE", - "HAS_EMISSIVE_FACTOR", - "HAS_EMISSIVE_TEXTURE", - "HAS_GLOSSINESS_FACTOR", - "HAS_NORMAL_TEXTURE", - "HAS_OCCLUSION_TEXTURE", - "HAS_SPECULAR_GLOSSINESS_TEXTURE", - "TEXCOORD_DIFFUSE v_texCoord_0", - "TEXCOORD_EMISSIVE v_texCoord_0", - "TEXCOORD_NORMAL v_texCoord_0", - "TEXCOORD_OCCLUSION v_texCoord_0", - "TEXCOORD_SPECULAR_GLOSSINESS v_texCoord_0", - "USE_SPECULAR_GLOSSINESS", - ]); - - const specularGlossiness = primitive.material.specularGlossiness; - const expectedUniforms = { - u_diffuseTexture: specularGlossiness.diffuseTexture.texture, - u_specularGlossinessTexture: - specularGlossiness.specularGlossinessTexture.texture, - u_glossinessFactor: specularGlossiness.glossinessFactor, - }; - expectUniformMap(uniformMap, expectedUniforms); - }); + it("adds specular glossiness uniforms", async function () { + const gltfLoader = await loadGltf(boomBoxSpecularGlossiness); + const primitive = gltfLoader.components.nodes[0].primitives[0]; + const renderResources = mockRenderResources(); + const { shaderBuilder, uniformMap } = renderResources; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform float u_glossinessFactor;", + "uniform sampler2D u_diffuseTexture;", + "uniform sampler2D u_emissiveTexture;", + "uniform sampler2D u_normalTexture;", + "uniform sampler2D u_occlusionTexture;", + "uniform sampler2D u_specularGlossinessTexture;", + "uniform vec3 u_emissiveFactor;", + ]); + + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_DIFFUSE_TEXTURE", + "HAS_EMISSIVE_FACTOR", + "HAS_EMISSIVE_TEXTURE", + "HAS_GLOSSINESS_FACTOR", + "HAS_NORMAL_TEXTURE", + "HAS_OCCLUSION_TEXTURE", + "HAS_SPECULAR_GLOSSINESS_TEXTURE", + "TEXCOORD_DIFFUSE v_texCoord_0", + "TEXCOORD_EMISSIVE v_texCoord_0", + "TEXCOORD_NORMAL v_texCoord_0", + "TEXCOORD_OCCLUSION v_texCoord_0", + "TEXCOORD_SPECULAR_GLOSSINESS v_texCoord_0", + "USE_SPECULAR_GLOSSINESS", + ]); + + const specularGlossiness = primitive.material.specularGlossiness; + const expectedUniforms = { + u_diffuseTexture: specularGlossiness.diffuseTexture.texture, + u_specularGlossinessTexture: + specularGlossiness.specularGlossinessTexture.texture, + u_glossinessFactor: specularGlossiness.glossinessFactor, + }; + expectUniformMap(uniformMap, expectedUniforms); }); - it("adds specular glossiness uniforms without defaults", function () { - return loadGltf(boomBoxSpecularGlossiness).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[0].primitives[0]; - - const specularGlossiness = primitive.material.specularGlossiness; - specularGlossiness.diffuseFactor = new Cartesian4(0.5, 0.5, 0.5, 0.5); - specularGlossiness.specularFactor = new Cartesian3(0.5, 0.5, 0.5); - - const renderResources = mockRenderResources(); - const shaderBuilder = renderResources.shaderBuilder; - const uniformMap = renderResources.uniformMap; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - - ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ - "uniform float u_glossinessFactor;", - "uniform sampler2D u_diffuseTexture;", - "uniform sampler2D u_emissiveTexture;", - "uniform sampler2D u_normalTexture;", - "uniform sampler2D u_occlusionTexture;", - "uniform sampler2D u_specularGlossinessTexture;", - "uniform vec3 u_emissiveFactor;", - "uniform vec3 u_legacySpecularFactor;", - "uniform vec4 u_diffuseFactor;", - ]); - - ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ - "HAS_DIFFUSE_FACTOR", - "HAS_DIFFUSE_TEXTURE", - "HAS_EMISSIVE_FACTOR", - "HAS_EMISSIVE_TEXTURE", - "HAS_GLOSSINESS_FACTOR", - "HAS_NORMAL_TEXTURE", - "HAS_OCCLUSION_TEXTURE", - "HAS_LEGACY_SPECULAR_FACTOR", - "HAS_SPECULAR_GLOSSINESS_TEXTURE", - "TEXCOORD_DIFFUSE v_texCoord_0", - "TEXCOORD_EMISSIVE v_texCoord_0", - "TEXCOORD_NORMAL v_texCoord_0", - "TEXCOORD_OCCLUSION v_texCoord_0", - "TEXCOORD_SPECULAR_GLOSSINESS v_texCoord_0", - "USE_SPECULAR_GLOSSINESS", - ]); - - const expectedUniforms = { - u_diffuseTexture: specularGlossiness.diffuseTexture.texture, - u_diffuseFactor: specularGlossiness.diffuseFactor, - u_specularGlossinessTexture: - specularGlossiness.specularGlossinessTexture.texture, - u_legacySpecularFactor: specularGlossiness.specularFactor, - u_glossinessFactor: specularGlossiness.glossinessFactor, - }; - expectUniformMap(uniformMap, expectedUniforms); - }); + it("adds specular glossiness uniforms without defaults", async function () { + const gltfLoader = await loadGltf(boomBoxSpecularGlossiness); + const primitive = gltfLoader.components.nodes[0].primitives[0]; + + const specularGlossiness = primitive.material.specularGlossiness; + specularGlossiness.diffuseFactor = new Cartesian4(0.5, 0.5, 0.5, 0.5); + specularGlossiness.specularFactor = new Cartesian3(0.5, 0.5, 0.5); + + const renderResources = mockRenderResources(); + const { shaderBuilder, uniformMap } = renderResources; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + + ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform float u_glossinessFactor;", + "uniform sampler2D u_diffuseTexture;", + "uniform sampler2D u_emissiveTexture;", + "uniform sampler2D u_normalTexture;", + "uniform sampler2D u_occlusionTexture;", + "uniform sampler2D u_specularGlossinessTexture;", + "uniform vec3 u_emissiveFactor;", + "uniform vec3 u_legacySpecularFactor;", + "uniform vec4 u_diffuseFactor;", + ]); + + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_DIFFUSE_FACTOR", + "HAS_DIFFUSE_TEXTURE", + "HAS_EMISSIVE_FACTOR", + "HAS_EMISSIVE_TEXTURE", + "HAS_GLOSSINESS_FACTOR", + "HAS_NORMAL_TEXTURE", + "HAS_OCCLUSION_TEXTURE", + "HAS_LEGACY_SPECULAR_FACTOR", + "HAS_SPECULAR_GLOSSINESS_TEXTURE", + "TEXCOORD_DIFFUSE v_texCoord_0", + "TEXCOORD_EMISSIVE v_texCoord_0", + "TEXCOORD_NORMAL v_texCoord_0", + "TEXCOORD_OCCLUSION v_texCoord_0", + "TEXCOORD_SPECULAR_GLOSSINESS v_texCoord_0", + "USE_SPECULAR_GLOSSINESS", + ]); + + const expectedUniforms = { + u_diffuseTexture: specularGlossiness.diffuseTexture.texture, + u_diffuseFactor: specularGlossiness.diffuseFactor, + u_specularGlossinessTexture: + specularGlossiness.specularGlossinessTexture.texture, + u_legacySpecularFactor: specularGlossiness.specularFactor, + u_glossinessFactor: specularGlossiness.glossinessFactor, + }; + expectUniformMap(uniformMap, expectedUniforms); }); it("adds uniforms and defines for KHR_materials_specular", async function () { @@ -581,341 +539,249 @@ describe( expectUniformMap(uniformMap, expectedUniforms); }); - it("doesn't add texture uniforms for classification models", function () { - return loadGltf(boomBox).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[0].primitives[0]; - const renderResources = mockRenderResources(ClassificationType.BOTH); - const shaderBuilder = renderResources.shaderBuilder; - const uniformMap = renderResources.uniformMap; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - - ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ - "uniform vec3 u_emissiveFactor;", - ]); - - ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ - "HAS_EMISSIVE_FACTOR", - "USE_METALLIC_ROUGHNESS", - ]); - const material = primitive.material; - const expectedUniforms = { - u_emissiveFactor: material.emissiveFactor, - }; - expectUniformMap(uniformMap, expectedUniforms); - }); + it("doesn't add texture uniforms for classification models", async function () { + const gltfLoader = await loadGltf(boomBox); + const primitive = gltfLoader.components.nodes[0].primitives[0]; + const renderResources = mockRenderResources(ClassificationType.BOTH); + const { shaderBuilder, uniformMap } = renderResources; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + + ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform vec3 u_emissiveFactor;", + ]); + + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_EMISSIVE_FACTOR", + "USE_METALLIC_ROUGHNESS", + ]); + const material = primitive.material; + const expectedUniforms = { + u_emissiveFactor: material.emissiveFactor, + }; + expectUniformMap(uniformMap, expectedUniforms); }); - it("doesn't add metallic roughness textures for classification models", function () { - return loadGltf(boomBox).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[0].primitives[0]; - - const metallicRoughness = primitive.material.metallicRoughness; - metallicRoughness.baseColorFactor = new Cartesian4(0.5, 0.5, 0.5, 0.5); - metallicRoughness.metallicFactor = 0.5; - metallicRoughness.roughnessFactor = 0.5; - - const renderResources = mockRenderResources(); - const shaderBuilder = renderResources.shaderBuilder; - const uniformMap = renderResources.uniformMap; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - - ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ - "uniform float u_metallicFactor;", - "uniform float u_roughnessFactor;", - "uniform sampler2D u_baseColorTexture;", - "uniform sampler2D u_emissiveTexture;", - "uniform sampler2D u_metallicRoughnessTexture;", - "uniform sampler2D u_normalTexture;", - "uniform sampler2D u_occlusionTexture;", - "uniform vec3 u_emissiveFactor;", - "uniform vec4 u_baseColorFactor;", - ]); - - ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); - ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ - "HAS_BASE_COLOR_FACTOR", - "HAS_BASE_COLOR_TEXTURE", - "HAS_EMISSIVE_FACTOR", - "HAS_EMISSIVE_TEXTURE", - "HAS_METALLIC_FACTOR", - "HAS_METALLIC_ROUGHNESS_TEXTURE", - "HAS_NORMAL_TEXTURE", - "HAS_OCCLUSION_TEXTURE", - "HAS_ROUGHNESS_FACTOR", - "TEXCOORD_BASE_COLOR v_texCoord_0", - "TEXCOORD_EMISSIVE v_texCoord_0", - "TEXCOORD_METALLIC_ROUGHNESS v_texCoord_0", - "TEXCOORD_NORMAL v_texCoord_0", - "TEXCOORD_OCCLUSION v_texCoord_0", - "USE_METALLIC_ROUGHNESS", - ]); - - const expectedUniforms = { - u_baseColorFactor: metallicRoughness.baseColorFactor, - u_metallicFactor: metallicRoughness.metallicFactor, - u_roughnessFactor: metallicRoughness.roughnessFactor, - }; - expectUniformMap(uniformMap, expectedUniforms); - }); + it("doesn't add metallic roughness textures for classification models", async function () { + const gltfLoader = await loadGltf(boomBox); + const primitive = gltfLoader.components.nodes[0].primitives[0]; + + const metallicRoughness = primitive.material.metallicRoughness; + metallicRoughness.baseColorFactor = new Cartesian4(0.5, 0.5, 0.5, 0.5); + metallicRoughness.metallicFactor = 0.5; + metallicRoughness.roughnessFactor = 0.5; + + const renderResources = mockRenderResources(); + const { shaderBuilder, uniformMap } = renderResources; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + + ShaderBuilderTester.expectHasVertexUniforms(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentUniforms(shaderBuilder, [ + "uniform float u_metallicFactor;", + "uniform float u_roughnessFactor;", + "uniform sampler2D u_baseColorTexture;", + "uniform sampler2D u_emissiveTexture;", + "uniform sampler2D u_metallicRoughnessTexture;", + "uniform sampler2D u_normalTexture;", + "uniform sampler2D u_occlusionTexture;", + "uniform vec3 u_emissiveFactor;", + "uniform vec4 u_baseColorFactor;", + ]); + + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, []); + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_BASE_COLOR_FACTOR", + "HAS_BASE_COLOR_TEXTURE", + "HAS_EMISSIVE_FACTOR", + "HAS_EMISSIVE_TEXTURE", + "HAS_METALLIC_FACTOR", + "HAS_METALLIC_ROUGHNESS_TEXTURE", + "HAS_NORMAL_TEXTURE", + "HAS_OCCLUSION_TEXTURE", + "HAS_ROUGHNESS_FACTOR", + "TEXCOORD_BASE_COLOR v_texCoord_0", + "TEXCOORD_EMISSIVE v_texCoord_0", + "TEXCOORD_METALLIC_ROUGHNESS v_texCoord_0", + "TEXCOORD_NORMAL v_texCoord_0", + "TEXCOORD_OCCLUSION v_texCoord_0", + "USE_METALLIC_ROUGHNESS", + ]); + + const expectedUniforms = { + u_baseColorFactor: metallicRoughness.baseColorFactor, + u_metallicFactor: metallicRoughness.metallicFactor, + u_roughnessFactor: metallicRoughness.roughnessFactor, + }; + expectUniformMap(uniformMap, expectedUniforms); }); - it("enables PBR lighting for metallic roughness materials", function () { - return loadGltf(boomBox).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[0].primitives[0]; - const renderResources = mockRenderResources(); - const lightingOptions = renderResources.lightingOptions; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - expect(lightingOptions.lightingModel).toBe(LightingModel.PBR); - }); + it("enables PBR lighting for metallic roughness materials", async function () { + const gltfLoader = await loadGltf(boomBox); + const primitive = gltfLoader.components.nodes[0].primitives[0]; + const renderResources = mockRenderResources(); + const lightingOptions = renderResources.lightingOptions; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + expect(lightingOptions.lightingModel).toBe(LightingModel.PBR); }); - it("enables PBR lighting for specular glossiness materials", function () { - return loadGltf(boomBoxSpecularGlossiness).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[0].primitives[0]; - const renderResources = mockRenderResources(); - const lightingOptions = renderResources.lightingOptions; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - expect(lightingOptions.lightingModel).toBe(LightingModel.PBR); - }); + it("enables PBR lighting for specular glossiness materials", async function () { + const gltfLoader = await loadGltf(boomBox); + const primitive = gltfLoader.components.nodes[0].primitives[0]; + const renderResources = mockRenderResources(); + const lightingOptions = renderResources.lightingOptions; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + expect(lightingOptions.lightingModel).toBe(LightingModel.PBR); }); - it("enables unlit lighting when KHR_materials_unlit is present", function () { - return loadGltf(boxUnlit).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[1].primitives[0]; - const renderResources = mockRenderResources(); - const lightingOptions = renderResources.lightingOptions; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - expect(lightingOptions.lightingModel).toBe(LightingModel.UNLIT); - }); + it("enables unlit lighting when KHR_materials_unlit is present", async function () { + const gltfLoader = await loadGltf(boxUnlit); + const primitive = gltfLoader.components.nodes[1].primitives[0]; + const renderResources = mockRenderResources(); + const lightingOptions = renderResources.lightingOptions; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + expect(lightingOptions.lightingModel).toBe(LightingModel.UNLIT); }); - it("enables unlit lighting for classification models", function () { - return loadGltf(boxUnlit).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[1].primitives[0]; - const renderResources = mockRenderResources(ClassificationType.BOTH); - const lightingOptions = renderResources.lightingOptions; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - expect(lightingOptions.lightingModel).toBe(LightingModel.UNLIT); - }); + it("enables unlit lighting for classification models", async function () { + const gltfLoader = await loadGltf(boxUnlit); + const primitive = gltfLoader.components.nodes[1].primitives[0]; + const renderResources = mockRenderResources(ClassificationType.BOTH); + const lightingOptions = renderResources.lightingOptions; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + expect(lightingOptions.lightingModel).toBe(LightingModel.UNLIT); }); - it("gracefully falls back to unlit shading for models without normals", function () { - return loadGltf(boxNoNormals).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[1].primitives[0]; - const renderResources = mockRenderResources(); - const lightingOptions = renderResources.lightingOptions; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - expect(lightingOptions.lightingModel).toBe(LightingModel.UNLIT); - }); + it("gracefully falls back to unlit shading for models without normals", async function () { + const gltfLoader = await loadGltf(boxNoNormals); + const primitive = gltfLoader.components.nodes[1].primitives[0]; + const renderResources = mockRenderResources(); + const lightingOptions = renderResources.lightingOptions; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + expect(lightingOptions.lightingModel).toBe(LightingModel.UNLIT); }); - it("handles opaque material", function () { - return loadGltf(boomBox).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[0].primitives[0]; - const renderResources = mockRenderResources(); + it("handles opaque material", async function () { + const gltfLoader = await loadGltf(boomBox); + const primitive = gltfLoader.components.nodes[0].primitives[0]; + const renderResources = mockRenderResources(); - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); - expect(renderResources.alphaOptions.pass).not.toBeDefined(); - expect(renderResources.alphaOptions.alphaCutoff).not.toBeDefined(); - }); + expect(renderResources.alphaOptions.pass).not.toBeDefined(); + expect(renderResources.alphaOptions.alphaCutoff).not.toBeDefined(); }); - it("handles alpha mask material", function () { - return loadGltf(boomBox).then(function (gltfLoader) { - const cutoff = 0.6; - const components = gltfLoader.components; - const primitive = components.nodes[0].primitives[0]; - primitive.material.alphaMode = AlphaMode.MASK; - primitive.material.alphaCutoff = cutoff; - - const renderResources = mockRenderResources(); - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - - expect(renderResources.alphaOptions.pass).not.toBeDefined(); - expect(renderResources.alphaOptions.alphaCutoff).toBe(cutoff); - }); + it("handles alpha mask material", async function () { + const gltfLoader = await loadGltf(boomBox); + const cutoff = 0.6; + const primitive = gltfLoader.components.nodes[0].primitives[0]; + primitive.material.alphaMode = AlphaMode.MASK; + primitive.material.alphaCutoff = cutoff; + + const renderResources = mockRenderResources(); + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + + expect(renderResources.alphaOptions.pass).not.toBeDefined(); + expect(renderResources.alphaOptions.alphaCutoff).toBe(cutoff); }); - it("handles translucent material", function () { - return loadGltf(boomBox).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[0].primitives[0]; - primitive.material.alphaMode = AlphaMode.BLEND; + it("handles translucent material", async function () { + const gltfLoader = await loadGltf(boomBox); + const primitive = gltfLoader.components.nodes[0].primitives[0]; + primitive.material.alphaMode = AlphaMode.BLEND; - const renderResources = mockRenderResources(); - renderResources.pass = Pass.OPAQUE; + const renderResources = mockRenderResources(); + renderResources.pass = Pass.OPAQUE; - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); - expect(renderResources.alphaOptions.pass).toBe(Pass.TRANSLUCENT); - expect(renderResources.alphaOptions.alphaCutoff).not.toBeDefined(); - }); + expect(renderResources.alphaOptions.pass).toBe(Pass.TRANSLUCENT); + expect(renderResources.alphaOptions.alphaCutoff).not.toBeDefined(); }); - it("disables back-face culling if model.backFaceCulling is false", function () { - return loadGltf(boxUnlit).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[1].primitives[0]; - - const renderResources = mockRenderResources(); - const renderStateOptions = renderResources.renderStateOptions; - renderResources.model.backFaceCulling = false; - renderResources.cull = true; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - expect(renderStateOptions.cull.enabled).toBe(false); - }); + it("disables back-face culling if model.backFaceCulling is false", async function () { + const gltfLoader = await loadGltf(boxUnlit); + const primitive = gltfLoader.components.nodes[1].primitives[0]; + + const renderResources = mockRenderResources(); + const renderStateOptions = renderResources.renderStateOptions; + renderResources.model.backFaceCulling = false; + renderResources.cull = true; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + expect(renderStateOptions.cull.enabled).toBe(false); }); - it("enables back-face culling if material is not double-sided", function () { - return loadGltf(boxUnlit).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[1].primitives[0]; - const renderResources = mockRenderResources(); - const renderStateOptions = renderResources.renderStateOptions; - renderResources.model.backFaceCulling = true; - renderResources.cull = true; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - expect(renderStateOptions.cull.enabled).toBe(true); - }); + it("enables back-face culling if material is not double-sided", async function () { + const gltfLoader = await loadGltf(boxUnlit); + const primitive = gltfLoader.components.nodes[1].primitives[0]; + const renderResources = mockRenderResources(); + const renderStateOptions = renderResources.renderStateOptions; + renderResources.model.backFaceCulling = true; + renderResources.cull = true; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + expect(renderStateOptions.cull.enabled).toBe(true); }); - it("disables back-face culling if material is double-sided", function () { - return loadGltf(boxUnlit).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[1].primitives[0]; - const renderResources = mockRenderResources(); - renderResources.model.backFaceCulling = true; - const renderStateOptions = renderResources.renderStateOptions; - - primitive.material.doubleSided = true; - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - - expect(renderStateOptions.cull.enabled).toBe(false); - }); + it("disables back-face culling if material is double-sided", async function () { + const gltfLoader = await loadGltf(boxUnlit); + const primitive = gltfLoader.components.nodes[1].primitives[0]; + const renderResources = mockRenderResources(); + renderResources.model.backFaceCulling = true; + const renderStateOptions = renderResources.renderStateOptions; + + primitive.material.doubleSided = true; + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + + expect(renderStateOptions.cull.enabled).toBe(false); }); - it("adds material stage functions to the fragment shader", function () { - return loadGltf(boxUnlit).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[1].primitives[0]; - primitive.material.doubleSided = true; - - const renderResources = mockRenderResources(); - const shaderBuilder = renderResources.shaderBuilder; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - - ShaderBuilderTester.expectVertexLinesEqual(shaderBuilder, []); - ShaderBuilderTester.expectFragmentLinesEqual(shaderBuilder, [ - _shadersMaterialStageFS, - ]); - }); + it("adds material stage functions to the fragment shader", async function () { + const gltfLoader = await loadGltf(boxUnlit); + const primitive = gltfLoader.components.nodes[1].primitives[0]; + primitive.material.doubleSided = true; + + const renderResources = mockRenderResources(); + const shaderBuilder = renderResources.shaderBuilder; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + + ShaderBuilderTester.expectVertexLinesEqual(shaderBuilder, []); + ShaderBuilderTester.expectFragmentLinesEqual(shaderBuilder, [ + _shadersMaterialStageFS, + ]); }); - it("adds define to shader if material is double-sided", function () { - return loadGltf(twoSidedPlane).then(function (gltfLoader) { - const components = gltfLoader.components; - const primitive = components.nodes[0].primitives[0]; - const renderResources = mockRenderResources(); - const shaderBuilder = renderResources.shaderBuilder; - - MaterialPipelineStage.process( - renderResources, - primitive, - mockFrameState - ); - - ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [ - "HAS_DOUBLE_SIDED_MATERIAL", - ]); - ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ - "HAS_BASE_COLOR_TEXTURE", - "HAS_DOUBLE_SIDED_MATERIAL", - "HAS_METALLIC_ROUGHNESS_TEXTURE", - "HAS_NORMAL_TEXTURE", - "TEXCOORD_BASE_COLOR v_texCoord_0", - "TEXCOORD_METALLIC_ROUGHNESS v_texCoord_0", - "TEXCOORD_NORMAL v_texCoord_0", - "USE_METALLIC_ROUGHNESS", - ]); - }); + it("adds define to shader if material is double-sided", async function () { + const gltfLoader = await loadGltf(twoSidedPlane); + const primitive = gltfLoader.components.nodes[0].primitives[0]; + const renderResources = mockRenderResources(); + const shaderBuilder = renderResources.shaderBuilder; + + MaterialPipelineStage.process(renderResources, primitive, mockFrameState); + + ShaderBuilderTester.expectHasVertexDefines(shaderBuilder, [ + "HAS_DOUBLE_SIDED_MATERIAL", + ]); + ShaderBuilderTester.expectHasFragmentDefines(shaderBuilder, [ + "HAS_BASE_COLOR_TEXTURE", + "HAS_DOUBLE_SIDED_MATERIAL", + "HAS_METALLIC_ROUGHNESS_TEXTURE", + "HAS_NORMAL_TEXTURE", + "TEXCOORD_BASE_COLOR v_texCoord_0", + "TEXCOORD_METALLIC_ROUGHNESS v_texCoord_0", + "TEXCOORD_NORMAL v_texCoord_0", + "USE_METALLIC_ROUGHNESS", + ]); }); it("_processTextureTransform updates the shader and uniform map", function () { From ce128c7f8fdb72da0ad95fe95bef113c10f89626 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 30 May 2024 13:39:29 -0400 Subject: [PATCH 17/21] Add rendering test for KHR_materials_clearcoat --- packages/engine/Specs/Scene/Model/ModelSpec.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/engine/Specs/Scene/Model/ModelSpec.js b/packages/engine/Specs/Scene/Model/ModelSpec.js index fec7b5ea0444..7fc682127e03 100644 --- a/packages/engine/Specs/Scene/Model/ModelSpec.js +++ b/packages/engine/Specs/Scene/Model/ModelSpec.js @@ -110,6 +110,8 @@ describe( "./Data/Models/glTF-2.0/BoxSpecular/glTF/BoxSpecular.gltf"; const boxAnisotropyUrl = "./Data/Models/glTF-2.0/BoxAnisotropy/glTF/BoxAnisotropy.gltf"; + const boxClearcoatUrl = + "./Data/Models/glTF-2.0/BoxClearcoat/glTF/BoxClearcoat.gltf"; const riggedFigureUrl = "./Data/Models/glTF-2.0/RiggedFigureTest/glTF/RiggedFigureTest.gltf"; const dracoCesiumManUrl = @@ -736,6 +738,19 @@ describe( verifyRender(model, true); }); + it("renders model with the KHR_materials_clearcoat extension", async function () { + const resource = Resource.createIfNeeded(boxClearcoatUrl); + const gltf = await resource.fetchJson(); + const model = await loadAndZoomToModelAsync( + { + gltf: gltf, + basePath: boxClearcoatUrl, + }, + scene + ); + verifyRender(model, true); + }); + it("transforms property textures with KHR_texture_transform", async function () { const resource = Resource.createIfNeeded( propertyTextureWithTextureTransformUrl From 61bb866bffdf8d4c7d98de5bc0368a77ed8d459f Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Thu, 30 May 2024 16:22:45 -0400 Subject: [PATCH 18/21] Update CHANGES.md, PR feedback --- CHANGES.md | 4 +++- packages/engine/Source/Scene/GltfLoader.js | 2 +- packages/engine/Source/Scene/Model/MaterialPipelineStage.js | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 213c9c87c1fc..791e0abd1383 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ - Added support for glTF models with the [KHR_materials_specular extension](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_specular). [#11970](https://github.com/CesiumGS/cesium/pull/11970) - Added support for glTF models with the [KHR_materials_anisotropy extension](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_anisotropy/README.md). [#11988](https://github.com/CesiumGS/cesium/pull/11988) +- Added support for glTF models with the [KHR_materials_clearcoat extension](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_clearcoat/README.md). [#12006](https://github.com/CesiumGS/cesium/pull/12006) #### Fixes :wrench: @@ -15,7 +16,8 @@ - Fixed a bug where `TaskProcessor` worker loading would check the worker module ID rather than the absolute URL when determining if it is cross-origin. [#11833](https://github.com/CesiumGS/cesium/pull/11833) - Fixed a bug where cross-origin workers would error when loaded with the CommonJS `importScripts` shim instead of an ESM `import`. [#11833](https://github.com/CesiumGS/cesium/pull/11833) - Corrected the Typescript types for `Billboard.id` and `Label.id` to be `any` [#11973](https://github.com/CesiumGS/cesium/issues/11973) -- Fixed a normalization error in image-based lighting [#11994](https://github.com/CesiumGS/cesium/issues/11994) +- Fixed a normalization error in image-based lighting. [#11994](https://github.com/CesiumGS/cesium/issues/11994) +- Fixed an error in the specular reflection calculations for image-based lighting from supplied environment maps. [#12008](https://github.com/CesiumGS/cesium/issues/12008) ### 1.117 - 2024-05-01 diff --git a/packages/engine/Source/Scene/GltfLoader.js b/packages/engine/Source/Scene/GltfLoader.js index 1bdc8e22458b..18f825a6ae4e 100644 --- a/packages/engine/Source/Scene/GltfLoader.js +++ b/packages/engine/Source/Scene/GltfLoader.js @@ -1669,7 +1669,7 @@ function loadMaterial(loader, gltfMaterial, frameState) { if (defined(pbrAnisotropy) && !material.unlit) { material.anisotropy = loadAnisotropy(loader, pbrAnisotropy, frameState); } - if (defined(pbrClearcoat) && material.unlit === false) { + if (defined(pbrClearcoat) && !material.unlit) { material.clearcoat = loadClearcoat(loader, pbrClearcoat, frameState); } } diff --git a/packages/engine/Source/Scene/Model/MaterialPipelineStage.js b/packages/engine/Source/Scene/Model/MaterialPipelineStage.js index 0540644e246a..99f630f990d4 100644 --- a/packages/engine/Source/Scene/Model/MaterialPipelineStage.js +++ b/packages/engine/Source/Scene/Model/MaterialPipelineStage.js @@ -123,7 +123,7 @@ MaterialPipelineStage.process = function ( } if ( defined(material.clearcoat) && - ModelUtility.supportedExtensions.KHR_materials_clearcoat === true + ModelUtility.supportedExtensions.KHR_materials_clearcoat ) { processClearcoatUniforms( material.clearcoat, From f1ef1092ad26385063ac4fd5f9132d10e97f8d58 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Fri, 31 May 2024 13:27:38 -0400 Subject: [PATCH 19/21] Clarify argument cleaning in smithVisibilityGGX --- .../Shaders/Builtin/Functions/pbrLighting.glsl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl index 5dad8f33f87c..40c2d4c4428d 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl @@ -50,7 +50,8 @@ float smithVisibilityG1(float NdotV, float roughness) } /** - * Estimate the self-shadowing of the microfacets in a surface + * Estimate the geometric self-shadowing of the microfacets in a surface, + * using the Schlick GGX approximation of a Smith visibility function. * * @param {float} roughness The roughness of the material. * @param {float} NdotL The cosine of the angle between the surface normal and the direction to the light source. @@ -58,6 +59,9 @@ float smithVisibilityG1(float NdotV, float roughness) */ float smithVisibilityGGX(float roughness, float NdotL, float NdotV) { + // Avoid divide-by-zero errors + NdotL = clamp(NdotL, 0.001, 1.0); + NdotV += 0.001; return ( smithVisibilityG1(NdotL, roughness) * smithVisibilityG1(NdotV, roughness) @@ -92,10 +96,10 @@ float GGX(float roughness, float NdotH) */ float computeDirectSpecularStrength(vec3 normal, vec3 lightDirection, vec3 viewDirection, vec3 halfwayDirection, float roughness) { - float NdotL = clamp(dot(normal, lightDirection), 0.001, 1.0); - float NdotV = abs(dot(normal, viewDirection)) + 0.001; - float NdotH = clamp(dot(normal, halfwayDirection), 0.0, 1.0); + float NdotL = dot(normal, lightDirection); + float NdotV = abs(dot(normal, viewDirection)); float G = smithVisibilityGGX(roughness, NdotL, NdotV); + float NdotH = clamp(dot(normal, halfwayDirection), 0.0, 1.0); float D = GGX(roughness, NdotH); return G * D; } From 424d6a420f571b99d7800310621f1c79b361a5f3 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Fri, 31 May 2024 14:06:40 -0400 Subject: [PATCH 20/21] Fix bug for models with no IBL --- packages/engine/Source/Shaders/Model/LightingStageFS.glsl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl index 0eb76640c611..2b04c1708713 100644 --- a/packages/engine/Source/Shaders/Model/LightingStageFS.glsl +++ b/packages/engine/Source/Shaders/Model/LightingStageFS.glsl @@ -84,13 +84,12 @@ vec3 computePbrLighting(in czm_modelMaterial material, in vec3 position) vec3 directLighting = czm_pbrLighting(viewDirection, normal, lightDirection, material); vec3 directColor = lightColorHdr * directLighting; + // Accumulate colors from base layer + vec3 color = directColor + material.emissive; #ifdef USE_IBL_LIGHTING - vec3 iblColor = computeIBL(position, normal, lightDirection, lightColorHdr, material); + color += computeIBL(position, normal, lightDirection, lightColorHdr, material); #endif - // Accumulate colors from base layer - vec3 color = directColor + iblColor + material.emissive; - #ifdef USE_CLEARCOAT color = addClearcoatReflection(color, position, lightDirection, lightColorHdr, material); #endif From bd92351f4161e08fad4ec77a7934753514d05096 Mon Sep 17 00:00:00 2001 From: Jeshurun Hembd Date: Fri, 31 May 2024 14:20:04 -0400 Subject: [PATCH 21/21] Fix shader compilation problem --- .../Source/Shaders/Builtin/Functions/pbrLighting.glsl | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl index 40c2d4c4428d..ad8b7a809a76 100644 --- a/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl +++ b/packages/engine/Source/Shaders/Builtin/Functions/pbrLighting.glsl @@ -121,12 +121,7 @@ float computeDirectSpecularStrength(vec3 normal, vec3 lightDirection, vec3 viewD * @param {czm_modelMaterial} The material properties. * @return {vec3} The computed HDR color */ -vec3 czm_pbrLighting( - in vec3 viewDirectionEC, - in vec3 normalEC, - in vec3 lightDirectionEC, - in czm_modelMaterial material -) +vec3 czm_pbrLighting(vec3 viewDirectionEC, vec3 normalEC, vec3 lightDirectionEC, czm_modelMaterial material) { vec3 halfwayDirectionEC = normalize(viewDirectionEC + lightDirectionEC); float VdotH = clamp(dot(viewDirectionEC, halfwayDirectionEC), 0.0, 1.0);