├── LICENSE ├── README.md ├── derivation ├── jacobian2d.jpg └── jacobian2d.nb ├── math_utils.h ├── microsurface_transformations.h ├── teaser.jpg └── test.cpp /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Chaos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Microsurface transformations teaser 2 | 3 | # Microsurface Transformations 4 | 5 | Code accompanying the paper ["Microsurface Transformations"](https://docs.chaos.com/display/RESEARCH/Microsurface+Transformations) by Asen Atanasov, Vladimir Koylazov, Rossen Dimov and Alexander Wilkie from EGSR 2022. 6 | 7 | ## Derivation 8 | 9 | In the folder *derivation* we provide the [Mathematica](https://www.wolfram.com/mathematica/) notebook for the derivation of the Jacobian that ensures normalization of linearly transformed microfacet distributions. 10 | 11 | ## Numerical Validation 12 | 13 | In *microsurface_transformations.h* we provide the implementations of a variety of microsurfaces: [GTR](https://disneyanimation.com/publications/physically-based-shading-at-disney/), [Anisotropic GGX and Beckmann](https://jcgt.org/published/0003/02/03/), [STD](https://hal.archives-ouvertes.fr/hal-01535614), [Phong](https://www.cs.cornell.edu/~srm/publications/EGSR07-btdf.html), [Sheen](http://www.aconty.com/pdf/s2017_pbs_imageworks_sheen.pdf) and [Discrete GGX](https://www.cs.cornell.edu/projects/stochastic-sg14/). In *test.cpp* we instantiate all of these microsurfaces with random parameters and verify that they fullfill normalization and shadowing constraints. Then these instances are transformed using random tangential transformations and the constraints are verified once again. -------------------------------------------------------------------------------- /derivation/jacobian2d.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChaosGroup/microsurface-transformations/00971ccadccfff9d9ee3d6ce6e66accb8b121cf2/derivation/jacobian2d.jpg -------------------------------------------------------------------------------- /derivation/jacobian2d.nb: -------------------------------------------------------------------------------- 1 | (* Content-type: application/vnd.wolfram.mathematica *) 2 | 3 | (*** Wolfram Notebook File ***) 4 | (* http://www.wolfram.com/nb *) 5 | 6 | (* CreatedBy='Mathematica 10.4' *) 7 | 8 | (*CacheID: 234*) 9 | (* Internal cache information: 10 | NotebookFileLineBreakTest 11 | NotebookFileLineBreakTest 12 | NotebookDataPosition[ 158, 7] 13 | NotebookDataLength[ 15147, 463] 14 | NotebookOptionsPosition[ 14293, 429] 15 | NotebookOutlinePosition[ 14640, 444] 16 | CellTagsIndexPosition[ 14597, 441] 17 | WindowFrame->Normal*) 18 | 19 | (* Beginning of Notebook Content *) 20 | Notebook[{ 21 | Cell[BoxData[ 22 | RowBox[{ 23 | RowBox[{"(*", " ", 24 | RowBox[{ 25 | RowBox[{ 26 | "Notebook", " ", "for", " ", "the", " ", "paper", " ", 27 | "\"\\"", " ", "by", " ", "Asen", " ", 28 | "Atanasov"}], ",", " ", 29 | RowBox[{"Vladimir", " ", "Koylazov"}], ",", " ", 30 | RowBox[{ 31 | "Rossen", " ", "Dimov", " ", "and", " ", "Alexander", " ", "Wilkie"}], 32 | ",", " ", "EGSR`22"}], " ", "*)"}], "\[IndentingNewLine]", 33 | RowBox[{"(*", " ", 34 | RowBox[{"Author", ":", " ", 35 | RowBox[{"Asen", " ", "Atanasov"}]}], " ", "*)"}], "\[IndentingNewLine]", 36 | RowBox[{"(*", " ", 37 | RowBox[{ 38 | RowBox[{"Derivation", " ", "of", " ", "Equations", " ", "17"}], "-", 39 | RowBox[{"21", " ", "from", " ", "the", " ", "paper"}]}], " ", "*)"}], 40 | "\[IndentingNewLine]", "\[IndentingNewLine]", 41 | RowBox[{"(*", " ", 42 | RowBox[{ 43 | RowBox[{ 44 | RowBox[{"Equation", " ", "14"}], ":", " ", 45 | RowBox[{ 46 | RowBox[{ 47 | RowBox[{ 48 | "Length", " ", "of", " ", "the", " ", "transformed", " ", "micro"}], 49 | "-", "normal"}], " ", "||", 50 | RowBox[{"M", "^", "Tm"}], "||"}]}], ",", " ", 51 | RowBox[{ 52 | RowBox[{"where", " ", 53 | RowBox[{"mz", "^", "2"}]}], "=", 54 | RowBox[{"1", "-", 55 | RowBox[{"mx", "^", "2"}], "-", 56 | RowBox[{"my", "^", "2"}]}]}]}], " ", "*)"}], "\[IndentingNewLine]", 57 | RowBox[{ 58 | RowBox[{ 59 | RowBox[{"len", "[", 60 | RowBox[{ 61 | "mx_", ",", " ", "my_", ",", " ", "a_", ",", " ", "b_", ",", " ", "c_", 62 | ",", " ", "d_"}], "]"}], " ", ":=", " ", 63 | RowBox[{"Sqrt", "[", 64 | RowBox[{ 65 | RowBox[{ 66 | RowBox[{"(", 67 | RowBox[{ 68 | RowBox[{"a", "*", "mx"}], "+", 69 | RowBox[{"b", "*", "my"}]}], ")"}], "^", "2"}], "+", 70 | RowBox[{ 71 | RowBox[{"(", 72 | RowBox[{ 73 | RowBox[{"c", "*", "mx"}], "+", 74 | RowBox[{"d", "*", "my"}]}], ")"}], "^", "2"}], "+", "1", "-", 75 | RowBox[{"mx", "^", "2"}], "-", 76 | RowBox[{"my", "^", "2"}]}], "]"}]}], "\[IndentingNewLine]", 77 | "\[IndentingNewLine]", 78 | RowBox[{"(*", " ", 79 | RowBox[{ 80 | RowBox[{ 81 | RowBox[{"Equation", " ", "13"}], ":", " ", 82 | RowBox[{ 83 | RowBox[{ 84 | "Components", " ", "of", " ", "the", " ", "transformed", " ", 85 | "normalized", " ", "micro"}], "-", 86 | RowBox[{"normal", " ", "u"}]}]}], ",", " ", 87 | RowBox[{ 88 | RowBox[{"where", " ", 89 | RowBox[{"uz", "^", "2"}]}], "=", 90 | RowBox[{"1", "-", 91 | RowBox[{"ux", "^", "2"}], "-", 92 | RowBox[{"uy", "^", "2"}]}]}]}], " ", "*)"}], "\[IndentingNewLine]", 93 | RowBox[{ 94 | RowBox[{"ux", "[", 95 | RowBox[{ 96 | "mx_", ",", "my_", ",", "a_", ",", " ", "b_", ",", " ", "c_", ",", " ", 97 | "d_"}], "]"}], " ", ":=", " ", 98 | RowBox[{ 99 | RowBox[{"(", 100 | RowBox[{ 101 | RowBox[{"a", "*", "mx"}], "+", 102 | RowBox[{"b", "*", "my"}]}], ")"}], "/", 103 | RowBox[{"len", "[", 104 | RowBox[{ 105 | "mx", ",", "my", ",", "a", ",", " ", "b", ",", " ", "c", ",", " ", 106 | "d"}], "]"}]}]}], "\[IndentingNewLine]", 107 | RowBox[{ 108 | RowBox[{"uy", "[", 109 | RowBox[{ 110 | "mx_", ",", "my_", ",", "a_", ",", " ", "b_", ",", " ", "c_", ",", " ", 111 | "d_"}], "]"}], " ", ":=", " ", 112 | RowBox[{ 113 | RowBox[{"(", 114 | RowBox[{ 115 | RowBox[{"c", "*", "mx"}], "+", 116 | RowBox[{"d", "*", "my"}]}], ")"}], "/", 117 | RowBox[{"len", "[", 118 | RowBox[{ 119 | "mx", ",", "my", ",", "a", ",", " ", "b", ",", " ", "c", ",", " ", 120 | "d"}], "]"}]}]}]}]}]], "Input", 121 | CellChangeTimes->{{3.858084488266597*^9, 3.8580846106305957`*^9}, { 122 | 3.8580848057727575`*^9, 3.858084807141835*^9}, {3.858084965226877*^9, 123 | 3.8580849861840763`*^9}, {3.864620640852806*^9, 3.86462067344267*^9}, { 124 | 3.864620708526677*^9, 3.864620819890046*^9}, {3.864621012118041*^9, 125 | 3.864621152454068*^9}, {3.8646212061251373`*^9, 3.864621563157559*^9}, { 126 | 3.8646218485738835`*^9, 3.8646218490859127`*^9}, {3.8646219425352583`*^9, 127 | 3.8646219620093718`*^9}, {3.8646221760056114`*^9, 3.8646222109586105`*^9}}], 128 | 129 | Cell[CellGroupData[{ 130 | 131 | Cell[BoxData[ 132 | RowBox[{ 133 | RowBox[{"(*", " ", 134 | RowBox[{ 135 | RowBox[{ 136 | RowBox[{"Equations", " ", "17"}], "-", "20"}], ":", " ", 137 | RowBox[{"Partial", " ", "derivatives", " ", "of", " ", "u"}]}], " ", 138 | "*)"}], "\[IndentingNewLine]", 139 | RowBox[{"duxdmx", " ", "=", " ", 140 | RowBox[{"Simplify", "[", 141 | RowBox[{"D", "[", 142 | RowBox[{ 143 | RowBox[{"ux", "[", 144 | RowBox[{ 145 | "mx", ",", "my", ",", "a", ",", " ", "b", ",", " ", "c", ",", " ", 146 | "d"}], "]"}], ",", " ", "mx"}], "]"}], "]"}]}]}]], "Input", 147 | CellChangeTimes->{{3.858249518679796*^9, 3.858249525443183*^9}, { 148 | 3.858249634950447*^9, 3.858249645430046*^9}, {3.864620825668377*^9, 149 | 3.8646208799524813`*^9}, {3.8646215822066483`*^9, 3.864621608570156*^9}, { 150 | 3.8646216554128356`*^9, 3.8646216855255575`*^9}}], 151 | 152 | Cell[BoxData[ 153 | FractionBox[ 154 | RowBox[{ 155 | RowBox[{"b", " ", "my", " ", 156 | RowBox[{"(", 157 | RowBox[{"mx", "-", 158 | RowBox[{ 159 | SuperscriptBox["c", "2"], " ", "mx"}], "-", 160 | RowBox[{"c", " ", "d", " ", "my"}]}], ")"}]}], "+", 161 | RowBox[{"a", " ", 162 | RowBox[{"(", 163 | RowBox[{"1", "+", 164 | RowBox[{"c", " ", "d", " ", "mx", " ", "my"}], "+", 165 | RowBox[{ 166 | RowBox[{"(", 167 | RowBox[{ 168 | RowBox[{"-", "1"}], "+", 169 | SuperscriptBox["d", "2"]}], ")"}], " ", 170 | SuperscriptBox["my", "2"]}]}], ")"}]}]}], 171 | SuperscriptBox[ 172 | RowBox[{"(", 173 | RowBox[{"1", "-", 174 | SuperscriptBox["mx", "2"], "-", 175 | SuperscriptBox["my", "2"], "+", 176 | SuperscriptBox[ 177 | RowBox[{"(", 178 | RowBox[{ 179 | RowBox[{"a", " ", "mx"}], "+", 180 | RowBox[{"b", " ", "my"}]}], ")"}], "2"], "+", 181 | SuperscriptBox[ 182 | RowBox[{"(", 183 | RowBox[{ 184 | RowBox[{"c", " ", "mx"}], "+", 185 | RowBox[{"d", " ", "my"}]}], ")"}], "2"]}], ")"}], 186 | RowBox[{"3", "/", "2"}]]]], "Output", 187 | CellChangeTimes->{3.858249414267824*^9, 3.8582495285953636`*^9, 188 | 3.8582496518354125`*^9, 3.8582497701991825`*^9, 3.858249810827506*^9, 189 | 3.864621041784738*^9, 3.8646218592134924`*^9, 3.8646220301762705`*^9, 190 | 3.8646223359617605`*^9}] 191 | }, Open ]], 192 | 193 | Cell[CellGroupData[{ 194 | 195 | Cell[BoxData[ 196 | RowBox[{"duydmy", " ", "=", " ", 197 | RowBox[{"Simplify", "[", 198 | RowBox[{"D", "[", 199 | RowBox[{ 200 | RowBox[{"uy", "[", 201 | RowBox[{ 202 | "mx", ",", "my", ",", "a", ",", " ", "b", ",", " ", "c", ",", " ", 203 | "d"}], "]"}], ",", "my"}], "]"}], "]"}]}]], "Input", 204 | CellChangeTimes->{{3.858249463996669*^9, 3.8582494711240764`*^9}, { 205 | 3.8582496563646717`*^9, 3.8582496612229495`*^9}, {3.8646208877849293`*^9, 206 | 3.864620911678296*^9}}], 207 | 208 | Cell[BoxData[ 209 | FractionBox[ 210 | RowBox[{ 211 | RowBox[{"c", " ", "mx", " ", 212 | RowBox[{"(", 213 | RowBox[{ 214 | RowBox[{ 215 | RowBox[{"-", "a"}], " ", "b", " ", "mx"}], "+", "my", "-", 216 | RowBox[{ 217 | SuperscriptBox["b", "2"], " ", "my"}]}], ")"}]}], "+", 218 | RowBox[{"d", " ", 219 | RowBox[{"(", 220 | RowBox[{"1", "+", 221 | RowBox[{ 222 | RowBox[{"(", 223 | RowBox[{ 224 | RowBox[{"-", "1"}], "+", 225 | SuperscriptBox["a", "2"]}], ")"}], " ", 226 | SuperscriptBox["mx", "2"]}], "+", 227 | RowBox[{"a", " ", "b", " ", "mx", " ", "my"}]}], ")"}]}]}], 228 | SuperscriptBox[ 229 | RowBox[{"(", 230 | RowBox[{"1", "-", 231 | SuperscriptBox["mx", "2"], "-", 232 | SuperscriptBox["my", "2"], "+", 233 | SuperscriptBox[ 234 | RowBox[{"(", 235 | RowBox[{ 236 | RowBox[{"a", " ", "mx"}], "+", 237 | RowBox[{"b", " ", "my"}]}], ")"}], "2"], "+", 238 | SuperscriptBox[ 239 | RowBox[{"(", 240 | RowBox[{ 241 | RowBox[{"c", " ", "mx"}], "+", 242 | RowBox[{"d", " ", "my"}]}], ")"}], "2"]}], ")"}], 243 | RowBox[{"3", "/", "2"}]]]], "Output", 244 | CellChangeTimes->{{3.858249447095702*^9, 3.8582494739952407`*^9}, 245 | 3.858249664744151*^9, 3.858249770207183*^9, 3.858249810834507*^9, 246 | 3.8646210418447413`*^9, 3.8646218592204924`*^9, 3.864622030184271*^9, 247 | 3.864622335969761*^9}] 248 | }, Open ]], 249 | 250 | Cell[CellGroupData[{ 251 | 252 | Cell[BoxData[ 253 | RowBox[{"duxdmy", " ", "=", " ", 254 | RowBox[{"Simplify", "[", 255 | RowBox[{"D", "[", 256 | RowBox[{ 257 | RowBox[{"ux", "[", 258 | RowBox[{ 259 | "mx", ",", "my", ",", "a", ",", " ", "b", ",", " ", "c", ",", " ", 260 | "d"}], "]"}], ",", " ", "my"}], "]"}], "]"}]}]], "Input", 261 | CellChangeTimes->{{3.858249558029047*^9, 3.858249562204286*^9}, { 262 | 3.8582496703334703`*^9, 3.858249672923619*^9}, {3.864620920030774*^9, 263 | 3.8646209326604967`*^9}}], 264 | 265 | Cell[BoxData[ 266 | FractionBox[ 267 | RowBox[{ 268 | RowBox[{"a", " ", "mx", " ", 269 | RowBox[{"(", 270 | RowBox[{ 271 | RowBox[{ 272 | RowBox[{"-", "c"}], " ", "d", " ", "mx"}], "+", "my", "-", 273 | RowBox[{ 274 | SuperscriptBox["d", "2"], " ", "my"}]}], ")"}]}], "+", 275 | RowBox[{"b", " ", 276 | RowBox[{"(", 277 | RowBox[{"1", "+", 278 | RowBox[{ 279 | RowBox[{"(", 280 | RowBox[{ 281 | RowBox[{"-", "1"}], "+", 282 | SuperscriptBox["c", "2"]}], ")"}], " ", 283 | SuperscriptBox["mx", "2"]}], "+", 284 | RowBox[{"c", " ", "d", " ", "mx", " ", "my"}]}], ")"}]}]}], 285 | SuperscriptBox[ 286 | RowBox[{"(", 287 | RowBox[{"1", "-", 288 | SuperscriptBox["mx", "2"], "-", 289 | SuperscriptBox["my", "2"], "+", 290 | SuperscriptBox[ 291 | RowBox[{"(", 292 | RowBox[{ 293 | RowBox[{"a", " ", "mx"}], "+", 294 | RowBox[{"b", " ", "my"}]}], ")"}], "2"], "+", 295 | SuperscriptBox[ 296 | RowBox[{"(", 297 | RowBox[{ 298 | RowBox[{"c", " ", "mx"}], "+", 299 | RowBox[{"d", " ", "my"}]}], ")"}], "2"]}], ")"}], 300 | RowBox[{"3", "/", "2"}]]]], "Output", 301 | CellChangeTimes->{{3.8582495548928676`*^9, 3.858249565563478*^9}, 302 | 3.858249676187805*^9, 3.8582497702141833`*^9, 3.858249810841507*^9, 303 | 3.8646210418937445`*^9, 3.864621859226493*^9, 3.864622030191272*^9, 304 | 3.864622335976762*^9}] 305 | }, Open ]], 306 | 307 | Cell[CellGroupData[{ 308 | 309 | Cell[BoxData[ 310 | RowBox[{"duydmx", " ", "=", " ", 311 | RowBox[{"Simplify", "[", 312 | RowBox[{"D", "[", 313 | RowBox[{ 314 | RowBox[{"uy", "[", 315 | RowBox[{ 316 | "mx", ",", "my", ",", "a", ",", " ", "b", ",", " ", "c", ",", " ", 317 | "d"}], "]"}], ",", "mx"}], "]"}], "]"}]}]], "Input", 318 | CellChangeTimes->{{3.8582495873227224`*^9, 3.858249592157999*^9}, { 319 | 3.858249682189149*^9, 3.8582496853903317`*^9}, {3.864620936967743*^9, 320 | 3.864620948847422*^9}}], 321 | 322 | Cell[BoxData[ 323 | FractionBox[ 324 | RowBox[{ 325 | RowBox[{"d", " ", "my", " ", 326 | RowBox[{"(", 327 | RowBox[{"mx", "-", 328 | RowBox[{ 329 | SuperscriptBox["a", "2"], " ", "mx"}], "-", 330 | RowBox[{"a", " ", "b", " ", "my"}]}], ")"}]}], "+", 331 | RowBox[{"c", " ", 332 | RowBox[{"(", 333 | RowBox[{"1", "+", 334 | RowBox[{"a", " ", "b", " ", "mx", " ", "my"}], "+", 335 | RowBox[{ 336 | RowBox[{"(", 337 | RowBox[{ 338 | RowBox[{"-", "1"}], "+", 339 | SuperscriptBox["b", "2"]}], ")"}], " ", 340 | SuperscriptBox["my", "2"]}]}], ")"}]}]}], 341 | SuperscriptBox[ 342 | RowBox[{"(", 343 | RowBox[{"1", "-", 344 | SuperscriptBox["mx", "2"], "-", 345 | SuperscriptBox["my", "2"], "+", 346 | SuperscriptBox[ 347 | RowBox[{"(", 348 | RowBox[{ 349 | RowBox[{"a", " ", "mx"}], "+", 350 | RowBox[{"b", " ", "my"}]}], ")"}], "2"], "+", 351 | SuperscriptBox[ 352 | RowBox[{"(", 353 | RowBox[{ 354 | RowBox[{"c", " ", "mx"}], "+", 355 | RowBox[{"d", " ", "my"}]}], ")"}], "2"]}], ")"}], 356 | RowBox[{"3", "/", "2"}]]]], "Output", 357 | CellChangeTimes->{{3.858249583453501*^9, 3.8582496001714573`*^9}, 358 | 3.858249690976651*^9, 3.858249770222184*^9, 3.858249810848508*^9, 359 | 3.864621041943747*^9, 3.8646218592344933`*^9, 3.8646220301982718`*^9, 360 | 3.864622335983762*^9}] 361 | }, Open ]], 362 | 363 | Cell[CellGroupData[{ 364 | 365 | Cell[BoxData[ 366 | RowBox[{ 367 | RowBox[{"(*", " ", 368 | RowBox[{ 369 | RowBox[{"Equation", " ", "21"}], ":", " ", 370 | RowBox[{"Jacobian", " ", "determinant", " ", "of", " ", "u"}]}], " ", 371 | "*)"}], "\[IndentingNewLine]", 372 | RowBox[{"(*", " ", 373 | RowBox[{ 374 | RowBox[{ 375 | "Notice", " ", "that", " ", "the", " ", "determinant", " ", "of", " ", 376 | "M", " ", "is", " ", "in", " ", "the", " ", "numerator", " ", "and", " ", 377 | "the", " ", "length", " ", "of", " ", "the", " ", "transformed", " ", 378 | "micro"}], "-", 379 | RowBox[{"normal", " ", 380 | RowBox[{"(", 381 | RowBox[{"see", " ", "\"\\"", " ", "above"}], ")"}], " ", "to", 382 | " ", "the", " ", "4"}], "-", 383 | RowBox[{ 384 | "th", " ", "power", " ", "is", " ", "in", " ", "the", " ", 385 | "denominator"}]}], " ", "*)"}], "\[IndentingNewLine]", 386 | RowBox[{"Simplify", "[", 387 | RowBox[{ 388 | RowBox[{"duxdmx", "*", "duydmy"}], "-", 389 | RowBox[{"duxdmy", "*", "duydmx"}]}], "]"}]}]], "Input", 390 | CellChangeTimes->{{3.8582497006042023`*^9, 3.8582497266646924`*^9}, { 391 | 3.8646209654133697`*^9, 3.8646210039945765`*^9}, {3.864621697336233*^9, 392 | 3.864621835095113*^9}, {3.8646219969373693`*^9, 3.864622025426999*^9}}], 393 | 394 | Cell[BoxData[ 395 | FractionBox[ 396 | RowBox[{ 397 | RowBox[{ 398 | RowBox[{"-", "b"}], " ", "c"}], "+", 399 | RowBox[{"a", " ", "d"}]}], 400 | SuperscriptBox[ 401 | RowBox[{"(", 402 | RowBox[{"1", "+", 403 | RowBox[{ 404 | RowBox[{"(", 405 | RowBox[{ 406 | RowBox[{"-", "1"}], "+", 407 | SuperscriptBox["a", "2"], "+", 408 | SuperscriptBox["c", "2"]}], ")"}], " ", 409 | SuperscriptBox["mx", "2"]}], "+", 410 | RowBox[{"2", " ", 411 | RowBox[{"(", 412 | RowBox[{ 413 | RowBox[{"a", " ", "b"}], "+", 414 | RowBox[{"c", " ", "d"}]}], ")"}], " ", "mx", " ", "my"}], "+", 415 | RowBox[{ 416 | RowBox[{"(", 417 | RowBox[{ 418 | RowBox[{"-", "1"}], "+", 419 | SuperscriptBox["b", "2"], "+", 420 | SuperscriptBox["d", "2"]}], ")"}], " ", 421 | SuperscriptBox["my", "2"]}]}], ")"}], "2"]]], "Output", 422 | CellChangeTimes->{3.8582497319079924`*^9, 3.858249770228184*^9, 423 | 3.858249810854508*^9, 3.8646210419677486`*^9, 3.8646218592414937`*^9, 424 | 3.8646220302042723`*^9, 3.864622335991762*^9}] 425 | }, Open ]], 426 | 427 | Cell[BoxData[""], "Input", 428 | CellChangeTimes->{{3.864622130656018*^9, 3.8646221331511602`*^9}}] 429 | }, 430 | WindowSize->{759, 849}, 431 | WindowMargins->{{Automatic, 503}, {58, Automatic}}, 432 | FrontEndVersion->"10.4 for Microsoft Windows (64-bit) (February 25, 2016)", 433 | StyleDefinitions->"Default.nb" 434 | ] 435 | (* End of Notebook Content *) 436 | 437 | (* Internal cache information *) 438 | (*CellTagsOutline 439 | CellTagsIndex->{} 440 | *) 441 | (*CellTagsIndex 442 | CellTagsIndex->{} 443 | *) 444 | (*NotebookFileOutline 445 | Notebook[{ 446 | Cell[558, 20, 3969, 106, 292, "Input"], 447 | Cell[CellGroupData[{ 448 | Cell[4552, 130, 796, 19, 52, "Input"], 449 | Cell[5351, 151, 1273, 38, 57, "Output"] 450 | }, Open ]], 451 | Cell[CellGroupData[{ 452 | Cell[6661, 194, 457, 11, 31, "Input"], 453 | Cell[7121, 207, 1295, 39, 57, "Output"] 454 | }, Open ]], 455 | Cell[CellGroupData[{ 456 | Cell[8453, 251, 458, 11, 31, "Input"], 457 | Cell[8914, 264, 1295, 39, 57, "Output"] 458 | }, Open ]], 459 | Cell[CellGroupData[{ 460 | Cell[10246, 308, 453, 11, 31, "Input"], 461 | Cell[10702, 321, 1272, 38, 57, "Output"] 462 | }, Open ]], 463 | Cell[CellGroupData[{ 464 | Cell[12011, 364, 1177, 27, 112, "Input"], 465 | Cell[13191, 393, 989, 30, 53, "Output"] 466 | }, Open ]], 467 | Cell[14195, 426, 94, 1, 31, "Input"] 468 | } 469 | ] 470 | *) 471 | 472 | -------------------------------------------------------------------------------- /math_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // A numerical validation for the paper "Microsurface Transformations" 4 | // by Asen Atanasov, Vladimir Koylazov, Rossen Dimov and Alexander Wilkie, EGSR'22 5 | 6 | const float PI=3.14159265358979323846f; 7 | inline float rnd(float a=0.0f, float b=1.0f) { return a+(b-a)*rand()/float(RAND_MAX); } 8 | 9 | // Follow math utilities copied from V-Ray SDK. 10 | 11 | inline float sqr(float x) { 12 | return x*x; 13 | } 14 | 15 | inline float clamp(float x, float a, float b) { 16 | return std::max(std::min(x, b), a); 17 | } 18 | 19 | inline float radicalInverse2(unsigned n) { 20 | n=(n<<16) | (n>>16); 21 | n=((n&0x00ff00ff)<<8) | ((n&0xff00ff00)>>8); 22 | n=((n&0x0f0f0f0f)<<4) | ((n&0xf0f0f0f0)>>4); 23 | n=((n&0x33333333)<<2) | ((n&0xcccccccc)>>2); 24 | n=((n&0x55555555)<<1) | ((n&0xaaaaaaaa)>>1); 25 | return (float)(double(n)/double(0x100000000)); 26 | } 27 | 28 | class Vector { 29 | public: 30 | /// The components of the vector 31 | union { 32 | struct { float x, y, z; }; 33 | float f[3]; 34 | }; 35 | 36 | /// Default constructor - does nothing 37 | Vector(void) {}; 38 | 39 | /// Initializes the vector with the given components 40 | Vector(float ix, float iy, float iz) { x=float(ix); y=float(iy); z=float(iz); } 41 | 42 | 43 | /// Initializes all components of the vector to the given value 44 | Vector(float f) { x=y=z=float(f); } 45 | 46 | /// Zeroes all components of the vector 47 | void makeZero(void) { x=y=z=0.0f; } 48 | 49 | /// Sets the components of the vector 50 | void set(float ix, float iy, float iz) { x=ix; y=iy; z=iz; } 51 | 52 | void set(int i, float v) { f[i]=v; } 53 | 54 | /// Adds the components of the given vector 55 | void operator +=(const Vector &a) { x+=a.x; y+=a.y; z+=a.z; } 56 | 57 | /// Subtracts the components of the given vector 58 | void operator -=(const Vector &a) { x-=a.x; y-=a.y; z-=a.z; } 59 | 60 | /// Multiplies all components by the given number 61 | void operator *=(float mult) { x*=float(mult); y*=float(mult); z*=float(mult); } 62 | 63 | /// Divides all components by the given number 64 | void operator /=(float f) { x/=float(f); y/=float(f); z/=float(f); } 65 | 66 | /// Reverses the sign of all components 67 | Vector operator-(void) const { return(Vector(-x, -y, -z)); } 68 | 69 | /// Returns the i-th component (0 for x, 1 for y, 2 for z) 70 | float& operator [](const int index) { return f[index]; } 71 | 72 | /// Returns the i-th component (0 for x, 1 for y, 2 for z) as a const 73 | const float& operator [](const int index) const { return f[index]; } 74 | 75 | /// Returns the length of the vector 76 | float length(void) const { return sqrtf(x*x+y*y+z*z); } 77 | 78 | /// Returns the squared length of the vector 79 | float lengthSqr(void) const { return x*x+y*y+z*z; } 80 | 81 | /// Makes this vector a unit vector; does not make a check for division by zero. 82 | /// @note This method uses multiplication by reciprocal length instead of division; if proper division is required, use getUnitVector(). 83 | void makeNormalized(void) { 84 | float len1=1.0f/length(); 85 | x*=len1; 86 | y*=len1; 87 | z*=len1; 88 | } 89 | }; 90 | 91 | inline Vector operator *(const Vector &a, float f) { 92 | Vector res(a); 93 | res*=f; 94 | return res; 95 | } 96 | 97 | inline Vector operator +(const Vector &a, const Vector &b) { 98 | Vector res(a); 99 | res+=b; 100 | return res; 101 | } 102 | 103 | inline Vector operator -(const Vector &a, const Vector &b) { 104 | Vector res(a); 105 | res-=b; 106 | return res; 107 | } 108 | 109 | /// \relates Vector 110 | /// Returns a unit vector along the direction of the given one 111 | inline Vector normalize(const Vector &a) { return a*(1.0f/a.length()); } 112 | 113 | /// Dot product of two vectors; intermediate calculations are double-precision. 114 | inline double operator *(const Vector &a, const Vector &b) { 115 | return (double)a.x*(double)b.x+(double)a.y*(double)b.y+(double)a.z*(double)b.z; 116 | } 117 | 118 | inline float dotf(const Vector &a, const Vector &b) { 119 | return a.x*b.x+a.y*b.y+a.z*b.z; 120 | } 121 | 122 | /// \relates Vector 123 | /// Returns the mixed product of the three vectors ((a^b)*c) 124 | inline double mixed(const Vector &a, const Vector &b, const Vector &c) { 125 | return 126 | ((double)a.y*(double)b.z-(double)b.y*(double)a.z)*(double)c.x+ 127 | ((double)a.z*(double)b.x-(double)b.z*(double)a.x)*(double)c.y+ 128 | ((double)a.x*(double)b.y-(double)b.x*(double)a.y)*(double)c.z; 129 | } 130 | 131 | // Used by the Matrix inverse functions to compute the cross-product of two 132 | // Vector's divided by a double-precision number. All calculations are double-precision. 133 | inline Vector crossd(const Vector &a, const Vector &b, double D) { 134 | return Vector( 135 | ((double)a.y*(double)b.z-(double)b.y*(double)a.z)/D, 136 | ((double)a.z*(double)b.x-(double)b.z*(double)a.x)/D, 137 | ((double)a.x*(double)b.y-(double)b.x*(double)a.y)/D 138 | ); 139 | } 140 | 141 | class Matrix { 142 | public: 143 | Vector f[3]; ///< The three COLUMNS of the matrix. 144 | 145 | /// Return the i-th column of the matrix (i=0,1,2). 146 | Vector& operator [](const int index) { return f[index]; } 147 | 148 | /// Return the i-th column of the matrix as a const object (i=0,1,2). 149 | const Vector& operator [](const int index) const { return f[index]; } 150 | 151 | /// Constructor - does not perform any initialization. 152 | Matrix(void) {} 153 | 154 | /// Constructor to either identity or zero matrix. 155 | /// @param i If this is 0, the matrix is initialized to the zero matrix; if this is 1, the matrix is 156 | /// initialized to the identity matrix. 157 | Matrix(int i) { 158 | if(i==1) makeIdentity(); 159 | if(i==0) makeZero(); 160 | } 161 | 162 | /// Constructor from the three columns of the matrix. 163 | /// @param a The first column. 164 | /// @param b The second column. 165 | /// @param c The third column. 166 | Matrix(const Vector &a, const Vector &b, const Vector &c) { f[0]=a; f[1]=b; f[2]=c; } 167 | 168 | /// Set the columns of the matrix. 169 | /// @param a The first column. 170 | /// @param b The second column. 171 | /// @param c The third column. 172 | void set(const Vector &a, const Vector &b, const Vector &c) { f[0]=a; f[1]=b; f[2]=c; } 173 | 174 | /// Set the i-th column of the matrix. 175 | /// @param i The index of the column to set (i=0,1,2). 176 | /// @param a The value for the column. 177 | void setCol(int i, const Vector &a) { f[i]=a; } 178 | 179 | /// Set the i-th row of the matrix. 180 | /// @param i The index of the row to set (i=0,1,2). 181 | /// @param a The value of the row. 182 | void setRow(int i, const Vector &a) { f[0][i]=a.x; f[1][i]=a.y; f[2][i]=a.z; } 183 | 184 | /// Make the matrix the zero matrix (all elements are zeroes). 185 | void makeZero(void) { memset(f, 0, sizeof(Vector)*3); } 186 | 187 | /// Make the matrix the identity matrix. 188 | void makeIdentity(void) { 189 | f[0].set(1.0f, 0.0f, 0.0f); 190 | f[1].set(0.0f, 1.0f, 0.0f); 191 | f[2].set(0.0f, 0.0f, 1.0f); 192 | } 193 | 194 | /// Make the matrix to be the transpose of its currrent value. 195 | void makeTranspose(void) { 196 | float t; 197 | t=f[0][1]; f[0][1]=f[1][0]; f[1][0]=t; 198 | t=f[0][2]; f[0][2]=f[2][0]; f[2][0]=t; 199 | t=f[1][2]; f[1][2]=f[2][1]; f[2][1]=t; 200 | } 201 | 202 | /// Make the matrix to be the inverse of its current value. Does not check if the 203 | /// matrix has a zero determinant. 204 | void makeInverse(void) { 205 | Matrix r; 206 | double D=mixed(f[0], f[1], f[2]); 207 | r.setRow(0, crossd(f[1], f[2], D)); 208 | r.setRow(1, crossd(f[2], f[0], D)); 209 | r.setRow(2, crossd(f[0], f[1], D)); 210 | *this=r; 211 | } 212 | 213 | /// Multiply all elements in the matrix by the given number. 214 | void operator *=(float x) { for(int i=0; i<3; i++) f[i]*=x; } 215 | 216 | /// Divide all elements in the matrix by the given number. Does not perform a check for divide by zero. 217 | void operator /=(float x) { for(int i=0; i<3; i++) f[i]/=x; } 218 | }; 219 | 220 | /// Vector-matric product which is the same as matrix-vector product with the transposed matrix. 221 | inline Vector operator *(const Vector &a, const Matrix &m) { return Vector(a*m.f[0], a*m.f[1], a*m.f[2]); } 222 | 223 | /// Matrix-vector product. 224 | inline Vector operator *(const Matrix &m, const Vector &a) { return m.f[0]*a.x+m.f[1]*a.y+m.f[2]*a.z; } 225 | 226 | /// Uniformly map a point from the plane to the unit (hemi)sphere using polar mapping. 227 | /// @param[in] u The first coordinate in the plane. If 0<=u<=1, the result is in the 228 | /// positive hemisphere (z>=0). If -1<=u<0, the result is in the negative hemisphere 229 | /// (z<0). Otherwise the result is undefined. 230 | /// @param[in] v The second coordinate in the plane in the range [0,1]. 231 | /// @return A point on the unit (hemi)sphere. 232 | inline Vector getSphereDir(float u, float v) { 233 | float thetaSin=u; 234 | float thetaCos=sqrtf(1.0f-thetaSin*thetaSin); 235 | float phi=2.0f*PI*v; 236 | return Vector(cosf(phi)*thetaCos, sinf(phi)*thetaCos, thetaSin); 237 | } -------------------------------------------------------------------------------- /microsurface_transformations.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // A numerical validation for the paper "Microsurface Transformations" 4 | // by Asen Atanasov, Vladimir Koylazov, Rossen Dimov and Alexander Wilkie, EGSR'22 5 | 6 | // The basic technique to transform microsurfaces is implemented in the base class Microsurface. 7 | // A list of implementations for specific distributions are provided afterwards: 8 | // 1. GTR - "Physically Based Shading at Disney" by Burley (2012) and "Deriving the Smith shadowing function G_1 for \gamma \in (0,4]" by Dimov (2015) 9 | // 2. GGX - Anisotropic GGX, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs" by Heitz (2014) 10 | // 3. Beckmann - Anisotropic Beckmann, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs" by Heitz (2014) 11 | // 4. Phong - "Microfacet Models for Refraction through Rough Surfaces" by Walter et al. (2007) 12 | // 5. Sheen - "Production Friendly Microfacet Sheen BRDF", Estevez and Kulla (2017) 13 | // 6. STD - "STD: Student's t-Distribution of Slopes for Microfacet Based BSDFs" by Ribardiere et al. (2017) 14 | // 7. Discrete - discrete GGX with classical Smith shadowing, "Discrete Stochastic Microfacet Models", Jakob et al. (2014) 15 | 16 | // Author: Asen Atanasov 17 | // The code for GTR D and G1 is copied from V-Ray SDK 18 | 19 | #include "math_utils.h" 20 | 21 | class Microsurface { 22 | Matrix m, im; 23 | float det; 24 | virtual float getD(Vector h) const = 0; 25 | virtual float getG1(Vector dir) const = 0; 26 | 27 | public: 28 | 29 | virtual const char* getName() const = 0; 30 | 31 | // Inilialize a matrix of the form given in Equation (9) 32 | void initTransform(Matrix m) { 33 | this->m=m; 34 | this->im=m; 35 | im.makeInverse(); 36 | det=fabs(mixed(m[0], m[1], m[2])); 37 | } 38 | 39 | // The transformed distribution, Equation (22) 40 | float getMicrofacetDistribution(Vector h) const { 41 | Vector hTransformed=h*m; // multiply with the transpose of m 42 | const float normalization=1.0f/hTransformed.lengthSqr(); 43 | hTransformed*=sqrtf(normalization); 44 | return det*sqr(normalization)*getD(hTransformed); 45 | } 46 | 47 | // The transformed shadowing function, Equation (12) 48 | float getSmithG1(Vector dir) const { 49 | const Vector dirTransformed=normalize(im*dir); 50 | return getG1(dirTransformed); 51 | } 52 | 53 | // Normalization constraint, Equation (3) 54 | float integrateDistribution(int numSamples) const { 55 | double sum=0.0; 56 | for(int i=0; i 82 | static float evalEquidistantNaturalSpline(float knots[KNOTS], float t) { 83 | float f[KNOTS-2]; 84 | float m[KNOTS]; 85 | m[0] = 0.0f; 86 | m[KNOTS-1] = 0.0f; 87 | 88 | // Initialize moments m and result vector f. 89 | for(int i=0; i=0; i--) { 101 | m[i+1] = (f[i]-m[i+2])/m[i+1]; 102 | } 103 | 104 | const int i=clamp(int(floorf(t)), 0, KNOTS-2); 105 | 106 | // Calculate polynomial coefficients. 107 | const float a = m[i+1]-m[i]; 108 | const float b = 3.0f*m[i]; 109 | const float c = knots[i+1]-knots[i]-2.0f*m[i]-m[i+1]; 110 | const float d = knots[i]; 111 | 112 | const float x=t-float(i); 113 | const float res=((a*x+b)*x+c)*x+d; 114 | return res; 115 | } 116 | 117 | class GTR: public Microsurface { 118 | float alpha; 119 | float gamma; 120 | 121 | float getD(Vector h) const { 122 | float res=0.0f; 123 | const float cosTheta=h.z; 124 | if(cosTheta<=1e-3f) 125 | return res; 126 | 127 | const float cosTheta2=sqr(cosTheta); 128 | const float tanTheta2=(1.0f/cosTheta2)-1.0f; 129 | const float roughness2=sqr(alpha); 130 | 131 | if(fabsf(gamma-1.0f)<1e-6f) { 132 | const float div=PI*logf(roughness2)*cosTheta2*(roughness2+tanTheta2); 133 | res=(div<(roughness2-1.0f)*1e-6f)? (roughness2-1.0f)/div : 0.0f; 134 | } 135 | else { 136 | const float divisor=PI*(1.0f-powf(roughness2, 1.0f-gamma))*powf(cosTheta2*(roughness2+tanTheta2), gamma); 137 | const float dividend=(gamma-1.0f)*(roughness2-1.0f); 138 | res=(fabsf(divisor)>fabsf(dividend)*1e-6f)? dividend/divisor : 0.0f; 139 | } 140 | 141 | return res; 142 | } 143 | 144 | float getG1Gamma1(float alpha, float cotTheta) const { 145 | const float cotTheta2=sqr(cotTheta); 146 | const float alpha2=sqr(alpha); 147 | const float a=sqrtf(cotTheta2+alpha2); 148 | const float b=sqrtf(cotTheta2+1.0f); 149 | return cotTheta*logf(alpha2)/(a-b+cotTheta*logf(alpha2*(cotTheta+b)/(cotTheta+a))); 150 | } 151 | 152 | float getG1Gamma2(float alpha, float cotTheta) const { 153 | return 2.f/(1.f+sqrtf(1.f+sqr(alpha/cotTheta))); 154 | } 155 | 156 | float getG1Gamma3(float alpha, float cotTheta) const { 157 | const float cotTheta2=sqr(cotTheta); 158 | const float alpha2=sqr(alpha); 159 | const float a=sqrtf(cotTheta2+alpha2); 160 | const float b=alpha2+1.0f; 161 | return 4.0f*cotTheta*a*b/(2.0f*cotTheta*b*(cotTheta+a)+alpha2*(3.0f*alpha2+1.0f)); 162 | } 163 | 164 | float getG1Gamma4(float alpha, float cotTheta) const { 165 | const float cotTheta2=sqr(cotTheta); 166 | const float alpha2=sqr(alpha); 167 | const float alpha4=sqr(alpha2); 168 | const float a=8.f*(alpha4+alpha2+1.f); 169 | const float b=sqrtf(cotTheta2+alpha2); 170 | const float b3=b*(cotTheta2+alpha2); 171 | return 2.f*cotTheta*a*b3/(a*cotTheta*(b3+cotTheta*cotTheta2)+3.f*alpha2*(4.f*cotTheta2*(2.f*alpha4+alpha2+1.f)+alpha2*(5.f*alpha4+2.f*alpha2+1.f))); 172 | } 173 | 174 | float getG1(Vector dir) const { 175 | const float cosTheta = dir.z; 176 | if(cosTheta<=1e-3f) return 0.0f; 177 | if(cosTheta>=1.0f-1e-6f) return 1.0f; 178 | const float cotTheta = cosTheta/sqrtf(1.f-sqr(cosTheta)); 179 | 180 | float res=0.0f; 181 | // when gamma is any of the integer values 0, 1, 2, 3, 4 apply analytical solution 182 | if(gamma<=0.01f) 183 | res=getG1Gamma2(1.0f, cotTheta); 184 | else if(fabsf(gamma-1.0f)<=1e-2f) 185 | res=getG1Gamma1(alpha, cotTheta); 186 | else if(fabsf(gamma-2.0f)<=1e-2f) 187 | res=getG1Gamma2(alpha, cotTheta); 188 | else if(fabsf(gamma-3.0f)<=1e-2f) 189 | res=getG1Gamma3(alpha, cotTheta); 190 | else if(gamma>=4.0f-1e-2f) 191 | res=getG1Gamma4(alpha, cotTheta); 192 | else { 193 | float knots[5]; 194 | knots[0]=getG1Gamma2(1.0f, cotTheta); 195 | knots[1]=getG1Gamma1(alpha, cotTheta); 196 | knots[2]=getG1Gamma2(alpha, cotTheta); 197 | knots[3]=getG1Gamma3(alpha, cotTheta); 198 | knots[4]=getG1Gamma4(alpha, cotTheta); 199 | res=evalEquidistantNaturalSpline<5>(knots, gamma); 200 | } 201 | return clamp(res, 0.0f, 1.0f); 202 | } 203 | 204 | public: 205 | 206 | GTR(float alpha=0.1f, float gamma=2.0f, Matrix m=Matrix(1.0f)): alpha(alpha), gamma(gamma) { 207 | initTransform(m); 208 | } 209 | 210 | const char* getName() const { 211 | return "GTR "; 212 | } 213 | }; 214 | 215 | inline float getAlpha(Vector dir, float alphaX, float alphaY) { 216 | const float cosTheta2 = sqr(dir.z); 217 | const float invSinTheta2 = 1.0f/(1.0f-cosTheta2); 218 | 219 | if(alphaX==alphaY || invSinTheta2<=0) { 220 | return alphaX; 221 | } 222 | 223 | const float cosPhi2 = sqr(dir.x)*invSinTheta2; 224 | const float sinPhi2 = sqr(dir.y)*invSinTheta2; 225 | 226 | return sqrt(cosPhi2*sqr(alphaX) + sinPhi2*sqr(alphaY)); 227 | } 228 | 229 | class GGX: public Microsurface { 230 | float alphaX; 231 | float alphaY; 232 | 233 | float getD(Vector h) const { 234 | if(h.z<=1e-3f) return 0.0f; 235 | const float denominator=PI*alphaX*alphaY*sqr(sqr(h.x/alphaX)+sqr(h.y/alphaY)+sqr(h.z)); 236 | return 1.0f/denominator; 237 | } 238 | 239 | float getG1(Vector dir) const { 240 | const float cosTheta = dir.z; 241 | if(cosTheta<=1e-3f) return 0.0f; 242 | if(cosTheta>=1.0f-1e-6f) return 1.0f; 243 | const float alpha=getAlpha(dir, alphaX, alphaY); 244 | const float tanTheta2=(1.0f/sqr(dir.z))-1.0f; 245 | const float denominator=1.0f+sqrtf(1.0f+tanTheta2*sqr(alpha)); 246 | return 2.0f/denominator; 247 | } 248 | 249 | public: 250 | 251 | GGX(float alphaX, float alphaY, Matrix m=Matrix(1.0f)): alphaX(alphaX), alphaY(alphaY) { 252 | initTransform(m); 253 | } 254 | 255 | const char* getName() const { 256 | return "GGX "; 257 | } 258 | }; 259 | 260 | class Beckmann: public Microsurface { 261 | float alphaX; 262 | float alphaY; 263 | 264 | float getD(Vector h) const { 265 | const float cosTheta=h.z; 266 | if(cosTheta<=1e-3f) return 0.0f; 267 | const float cosTheta2=sqr(cosTheta); 268 | const float sinTheta2=1.0f-cosTheta2; 269 | float numerator=1.0f; 270 | if(sinTheta2>1e-6f) { 271 | const float tanTheta2=sinTheta2/cosTheta2; 272 | const float cosPhi2=sqr(h.x)/sinTheta2; 273 | const float sinPhi2=sqr(h.y)/sinTheta2; 274 | numerator=expf(-tanTheta2*(cosPhi2/sqr(alphaX)+sinPhi2/sqr(alphaY))); 275 | } 276 | const float denominator=PI*alphaX*alphaY*sqr(cosTheta2); 277 | return numerator/denominator; 278 | } 279 | 280 | float getG1(Vector dir) const { 281 | const float cosTheta = dir.z; 282 | if(cosTheta<=1e-3f) return 0.0f; 283 | if(cosTheta>=1.0f-1e-6f) return 1.0f; 284 | const float alpha=getAlpha(dir, alphaX, alphaY); 285 | const float tanTheta=sqrtf((1.0f/sqr(cosTheta))-1.0f); 286 | const float a=1.0f/(alpha*tanTheta); 287 | if(a<1.6f) { 288 | return (3.535f*a+2.181f*sqr(a))/(1.0f+2.276f*a+2.577f*sqr(a)); 289 | } 290 | return 1.0f; 291 | } 292 | 293 | public: 294 | 295 | Beckmann(float alphaX, float alphaY, Matrix m=Matrix(1.0f)): alphaX(alphaX), alphaY(alphaY) { 296 | initTransform(m); 297 | } 298 | 299 | const char* getName() const { 300 | return "Beckmann"; 301 | } 302 | }; 303 | 304 | class Phong: public Microsurface { 305 | float alpha; 306 | 307 | float getD(Vector h) const { 308 | const float cosTheta=h.z; 309 | if(cosTheta<=1e-3f) return 0.0f; 310 | return (alpha+2.0f)*powf(cosTheta, alpha)/(2.0f*PI); 311 | } 312 | 313 | float getG1(Vector dir) const { 314 | const float cosTheta = dir.z; 315 | if(cosTheta<=1e-3f) return 0.0f; 316 | if(cosTheta>=1.0f-1e-6f) return 1.0f; 317 | const float tanTheta=sqrtf((1.0f/sqr(cosTheta))-1.0f); 318 | const float a=sqrtf(0.5f*alpha+1.0f)/tanTheta; 319 | if(a<1.6f) { 320 | return (3.535f*a+2.181f*sqr(a))/(1.0f+2.276f*a+2.577f*sqr(a)); 321 | } 322 | return 1.0f; 323 | } 324 | 325 | public: 326 | 327 | Phong(float alpha, Matrix m=Matrix(1.0f)): alpha(alpha) { 328 | initTransform(m); 329 | } 330 | 331 | const char* getName() const { 332 | return "Phong "; 333 | } 334 | }; 335 | 336 | class Sheen: public Microsurface { 337 | float alpha; 338 | float a, b, c, d, e; 339 | 340 | float getD(Vector h) const { 341 | const float cosTheta=h.z; 342 | if(cosTheta<=1e-3f) return 0.0f; 343 | const float sinTheta=sqrtf(1.0f-sqr(cosTheta)); 344 | const float power=alpha>1e-6f?1.0f/alpha:1e18f; 345 | return (2.0f+power)*powf(sinTheta, power)/(2.0f*PI); 346 | } 347 | 348 | void initG1() { 349 | const float t=sqr(1.0f-alpha); 350 | a=t*25.3245f+(1-t)*21.5473f; 351 | b=t*3.32435f+(1-t)*3.82987f; 352 | c=t*0.16801f+(1-t)*0.19823f; 353 | d=t*-1.27393f+(1-t)*-1.97760f; 354 | e=t*-4.85967f+(1-t)*-4.32054f; 355 | } 356 | 357 | float getL(float x) const { 358 | return a/(1.0f+b*powf(x, c))+d*x+e; 359 | } 360 | 361 | float getG1(Vector dir) const { 362 | const float cosTheta = dir.z; 363 | if(cosTheta<=1e-3f) return 0.0f; 364 | if(cosTheta>=1.0f-1e-6f) return 1.0f; 365 | if(dir.z<0.5f) { 366 | const float lambda=expf(getL(cosTheta)); 367 | return 1.0f/(1.0f+lambda); 368 | } 369 | const float lambda=expf(2.0f*getL(0.5)-getL(1.0f-cosTheta)); 370 | return 1.0f/(1.0f+lambda); 371 | } 372 | 373 | public: 374 | 375 | Sheen(float alpha, Matrix m=Matrix(1.0f)): alpha(alpha) { 376 | initTransform(m); 377 | this->alpha=clamp(alpha, 1e-6f, 1.0f); 378 | initG1(); 379 | } 380 | 381 | const char* getName() const { 382 | return "Sheen "; 383 | } 384 | }; 385 | 386 | class STD: public Microsurface { 387 | float alphaX; 388 | float alphaY; 389 | float gamma; 390 | 391 | float getD(Vector h) const { 392 | const float cosTheta=h.z; 393 | if(cosTheta<=1e-3f) return 0.0f; 394 | const float cosTheta2=sqr(cosTheta); 395 | const float sinTheta2=1.0f-cosTheta2; 396 | float denominator=PI*alphaX*alphaY; 397 | if(sinTheta2>1e-6f) { 398 | const float tanTheta2=sinTheta2/cosTheta2; 399 | const float cosPhi2=sqr(h.x)/sinTheta2; 400 | const float sinPhi2=sqr(h.y)/sinTheta2; 401 | denominator*=sqr(cosTheta2)*powf(1.0f+tanTheta2*(cosPhi2/sqr(alphaX)+sinPhi2/sqr(alphaY))/(gamma-1.0f), gamma); 402 | } 403 | return 1.0f/denominator; 404 | } 405 | 406 | float getS1(float alpha, float mu) const { 407 | return alpha*powf(gamma-1.0f+sqr(mu/alpha), 1.5f-gamma)/mu; 408 | } 409 | 410 | float getF21(float z) const { 411 | return z*(1.066f+z*(2.655f+4.892f*z))/(1.038f+z*(2.969f+z*(4.305f+4.418f*z))); 412 | } 413 | 414 | float getF22() const { 415 | return (-14.402f+gamma*(-27.145f+gamma*(20.574f-2.745f*gamma)))/(-30.612f+gamma*(86.567f+gamma*(-84.341f+29.938f*gamma))); 416 | } 417 | 418 | float getF23() const { 419 | return (-129.404f+gamma*(324.987f+gamma*(-299.305f+93.268f*gamma)))/(-92.609f+gamma*(256.006f+gamma*(-245.663f+86.064f*gamma))); 420 | } 421 | 422 | float getF24(float z) const { 423 | return (6.537f+z*(6.074f+z*(-0.623f+5.223f*z)))/(6.538f+z*(6.103f+z*(-3.218f+6.347f*z))); 424 | } 425 | 426 | float getS2(float alpha, float mu) const { 427 | const float z=mu/alpha; 428 | return getF21(z)*(getF22()+getF23()*getF24(z)); 429 | } 430 | 431 | float getLambda(float alpha, float mu) const { 432 | const float invSqrtPi=0.56418958354775628694807945156077f; 433 | return invSqrtPi*tgammaf(gamma-0.5f)*(powf(gamma-1.0f, gamma)*getS1(alpha, mu)/(2.0f*gamma-3.0f)+sqrtf(gamma-1.0f)*getS2(alpha, mu))/tgammaf(gamma)-0.5f; 434 | } 435 | 436 | float getG1(Vector dir) const { 437 | const float cosTheta = dir.z; 438 | if(cosTheta<=1e-3f) return 0.0f; 439 | if(cosTheta>=1.0f-1e-6f) return 1.0f; 440 | const float alpha=getAlpha(dir, alphaX, alphaY); 441 | const float cosTheta2=sqr(cosTheta); 442 | const float cotTheta=sqrtf(cosTheta2/(1.0f-cosTheta2)); 443 | const float lambda=getLambda(alpha, cotTheta); 444 | return 1.0f/(1.0f+lambda); 445 | } 446 | 447 | public: 448 | 449 | STD(float alphaX, float alphaY, float gamma, Matrix m=Matrix(1.0f)): alphaX(alphaX), alphaY(alphaY), gamma(gamma) { 450 | initTransform(m); 451 | } 452 | 453 | const char* getName() const { 454 | return "STD "; 455 | } 456 | }; 457 | 458 | class Discrete: public Microsurface { 459 | float alpha; 460 | float gamma; 461 | float cosGamma; 462 | int count; 463 | Vector *normals; 464 | 465 | float getD(Vector h) const { 466 | if (h.z<=1e-3f) return 0.0f; 467 | int inside=0; 468 | for (int i=0; icosGamma) { 470 | inside++; 471 | } 472 | } 473 | const float coneSolidAngle=2.0f*PI*(1.0f-cosGamma); 474 | return float(inside)/(float(count)*coneSolidAngle*h.z); 475 | } 476 | 477 | float getG1(Vector dir) const { 478 | const float cosTheta = dir.z; 479 | if(cosTheta<=1e-3f) return 0.0f; 480 | if(cosTheta>=1.0f-1e-6f) return 1.0f; 481 | const float tanTheta2=(1.0f/sqr(cosTheta))-1.0f; 482 | const float denominator=1.0f+sqrtf(1.0f+tanTheta2*sqr(alpha)); 483 | return 2.0f/denominator; 484 | } 485 | 486 | Vector sampleGGXNormal(float alpha, float u, float v) { 487 | float cosTheta2 = (1.0f-u)/(1.0f+(sqr(alpha)-1.0f)*u); 488 | float cosTheta = sqrtf(std::max(cosTheta2, 0.0f)); 489 | float sinTheta = sqrtf(std::max(1.0f - cosTheta2, 0.0f)); 490 | 491 | float phi = 2.f*PI*v; 492 | return Vector(cosf(phi)*sinTheta, sinf(phi)*sinTheta, cosTheta); 493 | } 494 | 495 | public: 496 | 497 | Discrete(float alpha, float gamma, int count, Matrix m=Matrix(1.0f)): alpha(alpha), gamma(gamma), count(count) { 498 | initTransform(m); 499 | 500 | cosGamma=cosf(PI*gamma/180.0f); 501 | 502 | normals=new Vector[count]; 503 | for (int i=0; i 13 | #include 14 | #include 15 | #include "microsurface_transformations.h" 16 | 17 | using namespace std; 18 | 19 | int main() { 20 | 21 | const int numTests=10; 22 | const int numSamples=1000000; 23 | 24 | for(int i=1; i<=numTests; i++) { 25 | cout << "**********************************************************************************************" << endl; 26 | cout << "Test " << i << ":" << endl; 27 | 28 | const float alphaX=rnd(); 29 | const float alphaY=rnd(); 30 | const float gamma=rnd(0.0f, 4.0f); 31 | 32 | cout << "Random roughness and tail parameters:" << endl; 33 | cout << "AlphaX: " << alphaX << endl; 34 | cout << "AlphaY: " << alphaY << endl; 35 | cout << "Gamma: " << gamma << endl; 36 | cout << endl; 37 | 38 | const Vector outgoing=getSphereDir(rnd(), rnd()); 39 | cout << "Random shadowing direction = (" << outgoing.x << ", " << outgoing.y << ", " << outgoing.z << ")" << endl << endl; 40 | 41 | Matrix m; 42 | m.setCol(0, Vector(rnd(-10.0f, 10.0f), rnd(-10.0f, 10.0f), 0.0f)); 43 | m.setCol(1, Vector(rnd(-10.0f, 10.0f), rnd(-10.0f, 10.0f), 0.0f)); 44 | m.setCol(2, Vector(0.0f, 0.0f, 1.0f)); 45 | 46 | cout << "Generate matrix M with random entries in (-10, 10):" << endl; 47 | cout << "M = (" << m[0][0] << ", " << m[0][1] << ") (" << m[1][0] << ", " << m[1][1] << ")" << endl << endl; 48 | 49 | GTR gtr(alphaX, gamma); 50 | GGX ggx(alphaX, alphaY); 51 | Beckmann beckmann(alphaX, alphaY); 52 | Phong phong(alphaX); 53 | Sheen sheen(alphaX); 54 | STD std(alphaX, alphaY, gamma+1.5f); 55 | Discrete discrete(alphaX, gamma, 100); 56 | 57 | int numSurfaces=0; 58 | Microsurface *microsurfaces[8]; 59 | microsurfaces[numSurfaces++]=>r; 60 | microsurfaces[numSurfaces++]=&ggx; 61 | microsurfaces[numSurfaces++]=&beckmann; 62 | microsurfaces[numSurfaces++]=&phong; 63 | microsurfaces[numSurfaces++]=&sheen; 64 | microsurfaces[numSurfaces++]=&std; 65 | microsurfaces[numSurfaces++]=&discrete; 66 | 67 | cout << "Test normalization and shadowing constraints for the original and transformed by M surfaces:" << endl; 68 | for(int j=0; jgetName(); 71 | const float norm=surface->integrateDistribution(numSamples); 72 | const float shadow=surface->shadowingConstraint(outgoing, numSamples); 73 | surface->initTransform(m); 74 | const float normM=surface->integrateDistribution(numSamples); 75 | const float shadowM=surface->shadowingConstraint(outgoing, numSamples); 76 | 77 | cout << name << " Normalization constraint:\toriginal=" << norm << ",\ttransformed=" << normM << endl; 78 | cout << name << " Shadowing constraint:\toriginal=" << shadow << ",\ttransformed=" << shadowM << endl; 79 | } 80 | } 81 | 82 | return 0; 83 | } --------------------------------------------------------------------------------