1 /** 2 * Copyright: Copyright Auburn Sounds 2015 and later. 3 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 4 * Authors: Guillaume Piolat 5 */ 6 module dplug.gui.compositor; 7 8 import std.math; 9 10 import gfm.math.vector; 11 import gfm.math.box; 12 13 import dplug.core.alignedbuffer; 14 import dplug.core.nogc; 15 import dplug.core.math; 16 17 import dplug.window.window; 18 19 import dplug.graphics.mipmap; 20 import dplug.graphics.drawex; 21 import dplug.graphics.image; 22 import dplug.graphics.view; 23 24 // Only deals with rendering tiles. 25 // If you don't like dplug default compositing, just make another Compositor 26 // and assign the 'compositor' field in GUIGraphics. 27 // However for now mipmaps and are not negotiable. 28 interface ICompositor 29 { 30 void compositeTile(ImageRef!RGBA wfb, WindowPixelFormat pf, box2i area, 31 Mipmap!RGBA diffuseMap, 32 Mipmap!RGBA materialMap, 33 Mipmap!L16 depthMap, 34 Mipmap!RGBA skybox) nothrow @nogc; 35 } 36 37 /// "Physically Based"-style rendering 38 class PBRCompositor : ICompositor 39 { 40 nothrow @nogc: 41 // light 1 used for key lighting and shadows 42 // always coming from top-right 43 vec3f light1Color; 44 45 // light 2 used for diffuse lighting 46 vec3f light2Dir; 47 vec3f light2Color; 48 49 // light 3 used for specular highlights 50 vec3f light3Dir; 51 vec3f light3Color; 52 53 float ambientLight; 54 float skyboxAmount; 55 56 this() 57 { 58 float globalLightFactor = 1.3f; 59 // defaults 60 light1Color = vec3f(0.25f, 0.25f, 0.25f) * globalLightFactor; 61 62 light2Dir = vec3f(0.0f, 1.0f, 0.1f).normalized; 63 light2Color = vec3f(0.37f, 0.37f, 0.37f) * globalLightFactor; 64 65 light3Dir = vec3f(0.0f, 1.0f, 0.1f).normalized; 66 light3Color = vec3f(0.2f, 0.2f, 0.2f) * globalLightFactor; 67 68 ambientLight = 0.0625f * globalLightFactor; 69 skyboxAmount = 0.4f * globalLightFactor; 70 71 for (int roughByte = 0; roughByte < 256; ++roughByte) 72 { 73 _exponentTable[roughByte] = 0.8f * exp( (1-roughByte / 255.0f) * 5.5f); 74 } 75 76 // Set standard tables 77 enum tableAlignment = 256; // this is necessary for asm optimization of look-up 78 _tableArea.reallocBuffer(256 * 3, tableAlignment); 79 _redTransferTable = _tableArea.ptr; 80 _greenTransferTable = _tableArea.ptr + 256; 81 _blueTransferTable = _tableArea.ptr + 512; 82 83 foreach(i; 0..256) 84 { 85 _redTransferTable[i] = cast(ubyte)i; 86 _greenTransferTable[i] = cast(ubyte)i; 87 _blueTransferTable[i] = cast(ubyte)i; 88 } 89 } 90 91 ~this() 92 { 93 enum tableAlignment = 256; 94 _tableArea.reallocBuffer(0, tableAlignment); 95 } 96 97 /// Calling this setup color correction table, with the well 98 /// known lift-gamma-gain formula. 99 void setLiftGammaGainContrast(float lift = 0.0f, float gamma = 1.0f, float gain = 1.0f, float contrast = 0.0f) 100 { 101 setLiftGammaGainContrastRGB(lift, gamma, gain, contrast, 102 lift, gamma, gain, contrast, 103 lift, gamma, gain, contrast); 104 } 105 106 /// Calling this setup color correction table, with the well 107 /// known lift-gamma-gain formula, per channel. 108 void setLiftGammaGainRGB(float rLift = 0.0f, float rGamma = 1.0f, float rGain = 1.0f, 109 float gLift = 0.0f, float gGamma = 1.0f, float gGain = 1.0f, 110 float bLift = 0.0f, float bGamma = 1.0f, float bGain = 1.0f) 111 { 112 setLiftGammaGainContrastRGB(rLift, rGamma, rGain, 0.0f, 113 gLift, gGamma, gGain, 0.0f, 114 bLift, bGamma, bGain, 0.0f); 115 } 116 117 /// Calling this setup color correction table, with the well 118 /// known lift-gamma-gain formula + contrast addition, per channel. 119 void setLiftGammaGainContrastRGB( 120 float rLift = 0.0f, float rGamma = 1.0f, float rGain = 1.0f, float rContrast = 0.0f, 121 float gLift = 0.0f, float gGamma = 1.0f, float gGain = 1.0f, float gContrast = 0.0f, 122 float bLift = 0.0f, float bGamma = 1.0f, float bGain = 1.0f, float bContrast = 0.0f) 123 { 124 static float safePow(float a, float b) nothrow @nogc 125 { 126 if (a < 0) 127 a = 0; 128 if (a > 1) 129 a = 1; 130 return a ^^ b; 131 } 132 133 for (int b = 0; b < 256; ++b) 134 { 135 float inp = b / 255.0f; 136 float outR = rGain*(inp + rLift*(1-inp)); 137 float outG = gGain*(inp + gLift*(1-inp)); 138 float outB = bGain*(inp + bLift*(1-inp)); 139 140 outR = safePow(outR, 1.0f / rGamma ); 141 outG = safePow(outG, 1.0f / gGamma ); 142 outB = safePow(outB, 1.0f / bGamma ); 143 144 if (outR < 0) 145 outR = 0; 146 if (outG < 0) 147 outG = 0; 148 if (outB < 0) 149 outB = 0; 150 if (outR > 1) 151 outR = 1; 152 if (outG > 1) 153 outG = 1; 154 if (outB > 1) 155 outB = 1; 156 157 outR = lerp!float(outR, smoothStep!float(0, 1, outR), rContrast); 158 outG = lerp!float(outG, smoothStep!float(0, 1, outG), gContrast); 159 outB = lerp!float(outB, smoothStep!float(0, 1, outB), bContrast); 160 161 _redTransferTable[b] = cast(ubyte)(0.5f + outR * 255.0f); 162 _greenTransferTable[b] = cast(ubyte)(0.5f + outG * 255.0f); 163 _blueTransferTable[b] = cast(ubyte)(0.5f + outB * 255.0f); 164 } 165 } 166 167 /// Don't like this rendering? Feel free to override this method. 168 override void compositeTile(ImageRef!RGBA wfb, WindowPixelFormat pf, box2i area, 169 Mipmap!RGBA diffuseMap, 170 Mipmap!RGBA materialMap, 171 Mipmap!L16 depthMap, 172 Mipmap!RGBA skybox) 173 { 174 ushort[3][3] depthPatch = void; 175 L16*[3] depth_scan = void; 176 177 int w = diffuseMap.levels[0].w; 178 int h = diffuseMap.levels[0].h; 179 float invW = 1.0f / w; 180 float invH = 1.0f / h; 181 static immutable float div255 = 1 / 255.0f; 182 183 for (int j = area.min.y; j < area.max.y; ++j) 184 { 185 RGBA* wfb_scan = wfb.scanline(j).ptr; 186 187 // clamp to existing lines 188 { 189 OwnedImage!L16 depthLevel0 = depthMap.levels[0]; 190 for (int line = 0; line < 3; ++line) 191 { 192 int lineIndex = j - 1 + line; 193 if (lineIndex < 0) 194 lineIndex = 0; 195 if (lineIndex > h - 1) 196 lineIndex = h - 1; 197 depth_scan[line] = depthLevel0.scanline(lineIndex).ptr; 198 } 199 } 200 201 RGBA* materialScan = materialMap.levels[0].scanline(j).ptr; 202 RGBA* diffuseScan = diffuseMap.levels[0].scanline(j).ptr; 203 204 for (int i = area.min.x; i < area.max.x; ++i) 205 { 206 RGBA materialHere = materialScan[i]; 207 208 // Bypass PBR if Physical == 0 209 if (materialHere.a == 0) 210 { 211 wfb_scan[i] = diffuseScan[i]; 212 } 213 else 214 { 215 // clamp to existing columns 216 217 { 218 // Get depth for a 3x3 patch 219 // Turns out DMD like hand-unrolling:( 220 for (int k = 0; k < 3; ++k) 221 { 222 int colIndex = i - 1 + k; 223 if (colIndex < 0) 224 colIndex = 0; 225 if (colIndex > w - 1) 226 colIndex = w - 1; 227 228 depthPatch[0][k] = depth_scan.ptr[0][colIndex].l; 229 depthPatch[1][k] = depth_scan.ptr[1][colIndex].l; 230 depthPatch[2][k] = depth_scan.ptr[2][colIndex].l; 231 } 232 } 233 234 // compute normal 235 float sx = depthPatch[0][0] 236 + depthPatch[1][0] * 2 237 + depthPatch[2][0] 238 - ( depthPatch[0][2] 239 + depthPatch[1][2] * 2 240 + depthPatch[2][2]); 241 242 float sy = depthPatch[2][0] + depthPatch[2][1] * 2 + depthPatch[2][2] 243 - ( depthPatch[0][0] + depthPatch[0][1] * 2 + depthPatch[0][2]); 244 245 // this factor basically tweak normals to make the UI flatter or not 246 // if you change normal filtering, retune this 247 enum float sz = 260.0f * 257.0f / 1.8f; 248 249 vec3f normal = vec3f(sx, sy, sz); 250 normal.fastNormalize(); // this makes very, very little difference in output vs normalize 251 252 RGBA ibaseColor = diffuseScan[i]; 253 vec3f baseColor = vec3f(ibaseColor.r, ibaseColor.g, ibaseColor.b) * div255; 254 255 vec3f toEye = vec3f(0.5f - i * invW, j * invH - 0.5f, 1.0f); 256 toEye.fastNormalize(); // this makes very, very little difference in output vs normalize 257 258 vec3f color = vec3f(0.0f); 259 260 float roughness = materialHere.r * div255; 261 float metalness = materialHere.g * div255; 262 float specular = materialHere.b * div255; 263 264 // Add ambient component 265 { 266 float px = i + 0.5f; 267 float py = j + 0.5f; 268 269 float avgDepthHere = 270 ( depthMap.linearSample(1, px, py) 271 + depthMap.linearSample(2, px, py) 272 + depthMap.linearSample(3, px, py) 273 + depthMap.linearSample(4, px, py) ) * 0.25f; 274 275 float diff = depthPatch[1][1] - avgDepthHere; 276 float cavity = void; 277 if (diff >= 0) 278 cavity = 1; 279 else if (diff < -23040) 280 cavity = 0; 281 else 282 { 283 static immutable float divider23040 = 1.0f / 23040; 284 cavity = (diff + 23040) * divider23040; 285 } 286 if (cavity > 0) 287 color += baseColor * (cavity * ambientLight); 288 } 289 290 // cast shadows, ie. enlight what isn't in shadows 291 { 292 enum float fallOff = 0.78f; 293 294 int samples = 11; 295 296 static immutable float[11] weights = 297 [ 298 1.0f, 299 fallOff, 300 fallOff ^^ 2, 301 fallOff ^^ 3, 302 fallOff ^^ 4, 303 fallOff ^^ 5, 304 fallOff ^^ 6, 305 fallOff ^^ 7, 306 fallOff ^^ 8, 307 fallOff ^^ 9, 308 fallOff ^^ 10 309 ]; 310 311 enum float totalWeights = (1.0f - (fallOff ^^ 11)) / (1.0f - fallOff) - 1; 312 enum float invTotalWeights = 1 / totalWeights; 313 314 float lightPassed = 0.0f; 315 316 OwnedImage!L16 depthLevel0 = depthMap.levels[0]; 317 318 int depthHere = depthPatch[1][1]; 319 for (int sample = 1; sample < samples; ++sample) 320 { 321 int x = i + sample; 322 if (x >= w) 323 x = w - 1; 324 int y = j - sample; 325 if (y < 0) 326 y = 0; 327 int z = depthHere + sample; 328 int diff = z - depthLevel0.scanline(y)[x].l; // FUTURE: use pointer offsets here instead of opIndex 329 330 float contrib = void; 331 if (diff >= 0) 332 contrib = 1; 333 else if (diff < -15360) 334 { 335 contrib = 0; 336 continue; 337 } 338 else 339 { 340 static immutable float divider15360 = 1.0f / 15360; 341 contrib = (diff + 15360) * divider15360; 342 } 343 344 lightPassed += contrib * weights[sample]; 345 } 346 color += baseColor * light1Color * (lightPassed * invTotalWeights); 347 } 348 349 // secundary light 350 { 351 float diffuseFactor = 0.5f + 0.5f * dot(normal, light2Dir);// + roughness; 352 353 diffuseFactor = /*cavity * */ linmap!float(diffuseFactor, 0.24f - roughness * 0.5f, 1, 0, 1.0f); 354 355 if (diffuseFactor > 0) 356 color += baseColor * light2Color * diffuseFactor; 357 } 358 359 // specular reflection 360 if (specular != 0) 361 { 362 vec3f lightReflect = reflect(-light3Dir, normal); 363 float specularFactor = dot(toEye, lightReflect); 364 if (specularFactor > 1e-3f) 365 { 366 float exponent = _exponentTable[materialHere.r]; 367 specularFactor = specularFactor ^^ exponent; 368 float roughFactor = 10 * (1.0f - roughness) * (1 - metalness * 0.5f); 369 specularFactor = specularFactor * roughFactor; 370 if (specularFactor != 0) 371 color += baseColor * light3Color * (specularFactor * specular); 372 } 373 } 374 375 // skybox reflection (use the same shininess as specular) 376 if (metalness != 0) 377 { 378 vec3f pureReflection = reflect(toEye, normal); 379 380 float skyx = 0.5f + ((0.5f + pureReflection.x *0.5f) * (skybox.width - 1)); 381 float skyy = 0.5f + ((0.5f + pureReflection.y *0.5f) * (skybox.height - 1)); 382 383 // 2nd order derivatives 384 float depthDX = depthPatch[2][0] + depthPatch[2][1] + depthPatch[2][2] 385 + depthPatch[0][0] + depthPatch[0][1] + depthPatch[0][2] 386 - 2 * (depthPatch[1][0] + depthPatch[1][1] + depthPatch[1][2]); 387 388 float depthDY = depthPatch[0][2] + depthPatch[1][2] + depthPatch[2][2] 389 + depthPatch[0][0] + depthPatch[1][0] + depthPatch[2][0] 390 - 2 * (depthPatch[0][1] + depthPatch[1][1] + depthPatch[2][1]); 391 392 depthDX *= (1 / 256.0f); 393 depthDY *= (1 / 256.0f); 394 395 float depthDerivSqr = depthDX * depthDX + depthDY * depthDY; 396 float indexDeriv = depthDerivSqr * skybox.width * skybox.height; 397 398 // cooking here 399 // log2 scaling + threshold 400 float mipLevel = 0.5f * fastlog2(1.0f + indexDeriv * 0.00001f) + 6 * roughness; 401 402 vec3f skyColor = skybox.linearMipmapSample(mipLevel, skyx, skyy).rgb * (div255 * metalness * skyboxAmount); 403 color += skyColor * baseColor; 404 } 405 406 // Add light emitted by neighbours 407 { 408 float ic = i + 0.5f; 409 float jc = j + 0.5f; 410 411 // Get alpha-premultiplied, avoids to have to do alpha-aware mipmapping 412 vec4f colorLevel1 = diffuseMap.linearSample(1, ic, jc); 413 vec4f colorLevel2 = diffuseMap.linearSample(2, ic, jc); 414 vec4f colorLevel3 = diffuseMap.linearSample(3, ic, jc); 415 vec4f colorLevel4 = diffuseMap.linearSample(4, ic, jc); 416 vec4f colorLevel5 = diffuseMap.linearSample(5, ic, jc); 417 418 vec4f emitted = colorLevel1 * 0.00117647f; 419 emitted += colorLevel2 * 0.00176471f; 420 emitted += colorLevel3 * 0.00147059f; 421 emitted += colorLevel4 * 0.00088235f; 422 emitted += colorLevel5 * 0.00058823f; 423 color += emitted.rgb; 424 } 425 426 // Partial blending of PBR with diffuse 427 if (materialHere.a != 255) 428 { 429 float physical = materialHere.a * div255; 430 color += (baseColor - color) * (1 - physical); 431 } 432 433 // Show normals 434 // color = normal;//vec3f(0.5f) + normal * 0.5f; 435 436 // Show depth 437 { 438 // float depthColor = depthPatch[1][1] / 65535.0f; 439 // color = vec3f(depthColor); 440 } 441 442 // Show diffuse 443 //color = baseColor; 444 445 // color = toEye; 446 //color = vec3f(cavity); 447 assert(color.x >= 0); 448 assert(color.y >= 0); 449 assert(color.z >= 0); 450 451 if (color.x > 1) 452 color.x = 1; 453 if (color.y > 1) 454 color.y = 1; 455 if (color.z > 1) 456 color.z = 1; 457 458 int r = cast(int)(color.x * 255.99f); 459 int g = cast(int)(color.y * 255.99f); 460 int b = cast(int)(color.z * 255.99f); 461 462 // write composited color 463 wfb_scan[i] = RGBA(cast(ubyte)r, cast(ubyte)g, cast(ubyte)b, 255); 464 } 465 } 466 } 467 468 // Look-up table for color-correction and ordering of output 469 { 470 ubyte* red = _redTransferTable; 471 ubyte* green = _greenTransferTable; 472 ubyte* blue = _blueTransferTable; 473 474 final switch (pf) with (WindowPixelFormat) 475 { 476 case ARGB8: 477 applyColorCorrectionARGB8(wfb, area, red, green, blue); 478 break; 479 480 case BGRA8: 481 applyColorCorrectionBGRA8(wfb, area, red, green, blue); 482 break; 483 484 case RGBA8: 485 applyColorCorrectionRGBA8(wfb, area, red, green, blue); 486 break; 487 } 488 } 489 } 490 491 private: 492 // Assign those to use lookup tables. 493 ubyte[] _tableArea = null; 494 ubyte* _redTransferTable = null; 495 ubyte* _greenTransferTable = null; 496 ubyte* _blueTransferTable = null; 497 498 float[256] _exponentTable; 499 } 500 501 private: 502 503 // cause smoothStep wasn't needed 504 float ctLinearStep(float a, float b)(float t) pure nothrow @nogc 505 { 506 if (t <= a) 507 return 0.0f; 508 else if (t >= b) 509 return 1.0f; 510 else 511 { 512 static immutable divider = 1.0f / (b - a); 513 return (t - a) * divider; 514 } 515 } 516 517 private: 518 519 // cause smoothStep wasn't needed 520 float linearStep(float a, float b, float t) pure nothrow @nogc 521 { 522 if (t <= a) 523 return 0.0f; 524 else if (t >= b) 525 return 1.0f; 526 else 527 { 528 float divider = 1.0f / (b - a); 529 return (t - a) * divider; 530 } 531 } 532 533 // log2 approximation by Laurent de Soras 534 // http://www.flipcode.com/archives/Fast_log_Function.shtml 535 float fastlog2(float val) pure nothrow @nogc 536 { 537 union fi_t 538 { 539 int i; 540 float f; 541 } 542 543 fi_t fi; 544 fi.f = val; 545 int x = fi.i; 546 int log_2 = ((x >> 23) & 255) - 128; 547 x = x & ~(255 << 23); 548 x += 127 << 23; 549 fi.i = x; 550 return fi.f + log_2; 551 } 552 553 // Apply color correction and convert RGBA8 to ARGB8 554 void applyColorCorrectionARGB8(ImageRef!RGBA wfb, box2i area, ubyte* red, ubyte* green, ubyte* blue) nothrow @nogc 555 { 556 for (int j = area.min.y; j < area.max.y; ++j) 557 { 558 RGBA* wfb_scan = wfb.scanline(j).ptr; 559 for (int i = area.min.x; i < area.max.x; ++i) 560 { 561 immutable RGBA color = wfb_scan[i]; 562 wfb_scan[i] = RGBA(255, red[color.r], green[color.g], blue[color.b]); 563 } 564 } 565 } 566 567 // Apply color correction and convert RGBA8 to BGRA8 568 void applyColorCorrectionBGRA8(ImageRef!RGBA wfb, box2i area, ubyte* red, ubyte* green, ubyte* blue) nothrow @nogc 569 { 570 int width = area.width(); 571 for (int j = area.min.y; j < area.max.y; ++j) 572 { 573 RGBA* wfb_scan = wfb.scanline(j).ptr; 574 for (int i = area.min.x; i < area.max.x; ++i) 575 { 576 immutable RGBA color = wfb_scan[i]; 577 wfb_scan[i] = RGBA(blue[color.b], green[color.g], red[color.r], 255); 578 } 579 } 580 } 581 582 // Apply color correction and do nothing about color order 583 void applyColorCorrectionRGBA8(ImageRef!RGBA wfb, box2i area, ubyte* red, ubyte* green, ubyte* blue) nothrow @nogc 584 { 585 for (int j = area.min.y; j < area.max.y; ++j) 586 { 587 RGBA* wfb_scan = wfb.scanline(j).ptr; 588 for (int i = area.min.x; i < area.max.x; ++i) 589 { 590 immutable RGBA color = wfb_scan[i]; 591 wfb_scan[i] = RGBA(red[color.r], green[color.g], blue[color.b], 255); 592 } 593 } 594 }