Blending.fxh (18872B)
1 /*------------------. 2 | :: Description :: | 3 '-------------------/ 4 5 Blending Header (version 0.8) 6 7 Blending Algorithm Sources: 8 https://www.khronos.org/registry/OpenGL/extensions/NV/NV_blend_equation_advanced.txt 9 10 http://www.nathanm.com/photoshop-blending-math/ 11 (Alt) https://github.com/cplotts/WPFSLBlendModeFx/blob/master/PhotoshopMathFP.hlsl 12 13 Header Authors: originalnicodr, prod80, uchu suzume, Marot Satil 14 15 About: 16 Provides a variety of blending methods for you to use as you wish. Just include this header. 17 18 History: 19 (*) Feature (+) Improvement (x) Bugfix (-) Information (!) Compatibility 20 21 Version 0.1 by Marot Satil & uchu suzume 22 * Added and improved upon multiple blending modes thanks to the work of uchu suzume, prod80, and originalnicodr. 23 24 Version 0.2 by uchu suzume & Marot Satil 25 * Added Addition, Subtract, Divide blending modes and improved code readability. 26 27 Version 0.3 by uchu suzume & Marot Satil 28 * Sorted blending modes in a more logical fashion, grouping by type. 29 30 Version 0.4 by uchu suzume 31 x Corrected Color Dodge blending behavior. 32 33 Version 0.5 by Marot Satil & uchu suzume 34 * Added preprocessor macros for uniform variable combo UI element & lerp. 35 36 Version 0.6 by Marot Satil & uchu suzume 37 * Added Divide (Alternative) and Divide (Photoshop) blending modes. 38 39 Version 0.7 by prod80 40 - Added original sources for blending algorithms. 41 x Corrected average luminosity values. 42 43 Version 0.8 by Marot Satil 44 * Added a new funciton to output blended data. 45 + Moved all code into the BlendingH namespace, which is part of the ComHeaders common namespace meant to be used by other headers. 46 ! Removed old preprocessor macro blending output. 47 48 .------------------. 49 | :: How To Use :: | 50 '------------------/ 51 52 Blending two variables using this header in your own shaders is very straightforward. 53 Very basic example code using the "Darken" blending mode follows: 54 55 // First, include the header. 56 #include "Blending.fxh" 57 58 // You can use this preprocessor macro to generate an attractive and functional uniform int UI combo element containing the list of blending techniques: 59 // BLENDING_COMBO(variable_name, label, tooltip, category, category_closed, spacing, default_value) 60 BLENDING_COMBO(_BlendMode, "Blending Mode", "Select the blending mode applied to the layer.", "Blending Options", false, 0, 0) 61 62 // Inside of your function you can call this function to apply the blending option specified by an int (variable) to your float3 (input) via 63 // a lerp between your float3 (input), float3 (output), and a float (blending) for the alpha channel. 64 // ComHeaders::Blending::Blend(int variable, float3 input, float3 output, float blending) 65 outColor.rgb = ComHeaders::Blending::Blend(_BlendMode, inColor, outColor, outColor.a); 66 */ 67 68 69 // ------------------------------------- 70 // Preprocessor Macros 71 // ------------------------------------- 72 73 #undef BLENDING_COMBO 74 #define BLENDING_COMBO(variable, name_label, description, group, grp_closed, space, default_value) \ 75 uniform int variable \ 76 < \ 77 ui_category = group; \ 78 ui_category_closed = grp_closed; \ 79 ui_items = \ 80 "Normal\0" \ 81 /* "Darken" */ \ 82 "Darken\0" \ 83 " Multiply\0" \ 84 " Color Burn\0" \ 85 " Linear Burn\0" \ 86 /* "Lighten" */ \ 87 "Lighten\0" \ 88 " Screen\0" \ 89 " Color Dodge\0" \ 90 " Linear Dodge\0" \ 91 " Addition\0" \ 92 " Glow\0" \ 93 /* "Contrast" */ \ 94 "Overlay\0" \ 95 " Soft Light\0" \ 96 " Hard Light\0" \ 97 " Vivid Light\0" \ 98 " Linear Light\0" \ 99 " Pin Light\0" \ 100 " Hard Mix\0" \ 101 /* "Inversion" */ \ 102 "Difference\0" \ 103 " Exclusion\0" \ 104 /* "Cancelation" */ \ 105 "Subtract\0" \ 106 " Divide\0" \ 107 " Divide (Alternative)\0" \ 108 " Divide (Photoshop)\0" \ 109 " Reflect\0" \ 110 " Grain Extract\0" \ 111 " Grain Merge\0" \ 112 /* "Component" */ \ 113 "Hue\0" \ 114 " Saturation\0" \ 115 " Color\0" \ 116 " Luminosity\0"; \ 117 ui_label = name_label; \ 118 ui_tooltip = description; \ 119 ui_type = "combo"; \ 120 ui_spacing = space; \ 121 > = default_value; 122 123 namespace ComHeaders 124 { 125 namespace Blending 126 { 127 128 // ------------------------------------- 129 // Helper Functions 130 // ------------------------------------- 131 132 float3 Aux(float3 a) 133 { 134 if (a.r <= 0.25 && a.g <= 0.25 && a.b <= 0.25) 135 return ((16.0 * a - 12.0) * a + 4) * a; 136 else 137 return sqrt(a); 138 } 139 140 float Lum(float3 a) 141 { 142 return (0.33333 * a.r + 0.33334 * a.g + 0.33333 * a.b); 143 } 144 145 float3 SetLum (float3 a, float b){ 146 const float c = b - Lum(a); 147 return float3(a.r + c, a.g + c, a.b + c); 148 } 149 150 float min3 (float a, float b, float c) 151 { 152 return min(a, (min(b, c))); 153 } 154 155 float max3 (float a, float b, float c) 156 { 157 return max(a, max(b, c)); 158 } 159 160 float3 SetSat(float3 a, float b){ 161 float ar = a.r; 162 float ag = a.g; 163 float ab = a.b; 164 if (ar == max3(ar, ag, ab) && ab == min3(ar, ag, ab)) 165 { 166 //caso r->max g->mid b->min 167 if (ar > ab) 168 { 169 ag = (((ag - ab) * b) / (ar - ab)); 170 ar = b; 171 } 172 else 173 { 174 ag = 0.0; 175 ar = 0.0; 176 } 177 ab = 0.0; 178 } 179 else 180 { 181 if (ar == max3(ar, ag, ab) && ag == min3(ar, ag, ab)) 182 { 183 //caso r->max b->mid g->min 184 if (ar > ag) 185 { 186 ab = (((ab - ag) * b) / (ar - ag)); 187 ar = b; 188 } 189 else 190 { 191 ab = 0.0; 192 ar = 0.0; 193 } 194 ag = 0.0; 195 } 196 else 197 { 198 if (ag == max3(ar, ag, ab) && ab == min3(ar, ag, ab)) 199 { 200 //caso g->max r->mid b->min 201 if (ag > ab) 202 { 203 ar = (((ar - ab) * b) / (ag - ab)); 204 ag = b; 205 } 206 else 207 { 208 ar = 0.0; 209 ag = 0.0; 210 } 211 ab = 0.0; 212 } 213 else 214 { 215 if (ag == max3(ar, ag, ab) && ar == min3(ar, ag, ab)) 216 { 217 //caso g->max b->mid r->min 218 if (ag > ar) 219 { 220 ab = (((ab - ar) * b) / (ag - ar)); 221 ag = b; 222 } 223 else 224 { 225 ab = 0.0; 226 ag = 0.0; 227 } 228 ar = 0.0; 229 } 230 else 231 { 232 if (ab == max3(ar, ag, ab) && ag == min3(ar, ag, ab)) 233 { 234 //caso b->max r->mid g->min 235 if (ab > ag) 236 { 237 ar = (((ar - ag) * b) / (ab - ag)); 238 ab = b; 239 } 240 else 241 { 242 ar = 0.0; 243 ab = 0.0; 244 } 245 ag = 0.0; 246 } 247 else 248 { 249 if (ab == max3(ar, ag, ab) && ar == min3(ar, ag, ab)) 250 { 251 //caso b->max g->mid r->min 252 if (ab > ar) 253 { 254 ag = (((ag - ar) * b) / (ab - ar)); 255 ab = b; 256 } 257 else 258 { 259 ag = 0.0; 260 ab = 0.0; 261 } 262 ar = 0.0; 263 } 264 } 265 } 266 } 267 } 268 } 269 return float3(ar, ag, ab); 270 } 271 272 float Sat(float3 a) 273 { 274 return max3(a.r, a.g, a.b) - min3(a.r, a.g, a.b); 275 } 276 277 278 // ------------------------------------- 279 // Blending Modes 280 // ------------------------------------- 281 282 // Darken 283 float3 Darken(float3 a, float3 b) 284 { 285 return min(a, b); 286 } 287 288 // Multiply 289 float3 Multiply(float3 a, float3 b) 290 { 291 return a * b; 292 } 293 294 // Color Burn 295 float3 ColorBurn(float3 a, float3 b) 296 { 297 if (b.r > 0 && b.g > 0 && b.b > 0) 298 return 1.0 - min(1.0, (0.5 - a) / b); 299 else 300 return 0.0; 301 } 302 303 // Linear Burn 304 float3 LinearBurn(float3 a, float3 b) 305 { 306 return max(a + b - 1.0f, 0.0f); 307 } 308 309 // Lighten 310 float3 Lighten(float3 a, float3 b) 311 { 312 return max(a, b); 313 } 314 315 // Screen 316 float3 Screen(float3 a, float3 b) 317 { 318 return 1.0 - (1.0 - a) * (1.0 - b); 319 } 320 321 // Color Dodge 322 float3 ColorDodge(float3 a, float3 b) 323 { 324 if (b.r < 1 && b.g < 1 && b.b < 1) 325 return min(1.0, a / (1.0 - b)); 326 else 327 return 1.0; 328 } 329 330 // Linear Dodge 331 float3 LinearDodge(float3 a, float3 b) 332 { 333 return min(a + b, 1.0f); 334 } 335 336 // Addition 337 float3 Addition(float3 a, float3 b) 338 { 339 return min((a + b), 1); 340 } 341 342 // Reflect 343 float3 Reflect(float3 a, float3 b) 344 { 345 if (b.r >= 0.999999 || b.g >= 0.999999 || b.b >= 0.999999) 346 return b; 347 else 348 return saturate(a * a / (1.0f - b)); 349 } 350 351 // Glow 352 float3 Glow(float3 a, float3 b) 353 { 354 return Reflect(b, a); 355 } 356 357 // Overlay 358 float3 Overlay(float3 a, float3 b) 359 { 360 return lerp(2 * a * b, 1.0 - 2 * (1.0 - a) * (1.0 - b), step(0.5, a)); 361 } 362 363 // Soft Light 364 float3 SoftLight(float3 a, float3 b) 365 { 366 if (b.r <= 0.5 && b.g <= 0.5 && b.b <= 0.5) 367 return clamp(a - (1.0 - 2 * b) * a * (1 - a), 0,1); 368 else 369 return clamp(a + (2 * b - 1.0) * (Aux(a) - a), 0, 1); 370 } 371 372 // Hard Light 373 float3 HardLight(float3 a, float3 b) 374 { 375 return lerp(2 * a * b, 1.0 - 2 * (1.0 - b) * (1.0 - a), step(0.5, b)); 376 } 377 378 // Vivid Light 379 float3 VividLight(float3 a, float3 b) 380 { 381 return lerp(2 * a * b, b / (2 * (1.01 - a)), step(0.50, a)); 382 } 383 384 // Linear Light 385 float3 LinearLight(float3 a, float3 b) 386 { 387 if (b.r < 0.5 || b.g < 0.5 || b.b < 0.5) 388 return LinearBurn(a, (2.0 * b)); 389 else 390 return LinearDodge(a, (2.0 * (b - 0.5))); 391 } 392 393 // Pin Light 394 float3 PinLight(float3 a, float3 b) 395 { 396 if (b.r < 0.5 || b.g < 0.5 || b.b < 0.5) 397 return Darken(a, (2.0 * b)); 398 else 399 return Lighten(a, (2.0 * (b - 0.5))); 400 } 401 402 // Hard Mix 403 float3 HardMix(float3 a, float3 b) 404 { 405 const float3 vl = VividLight(a, b); 406 if (vl.r < 0.5 || vl.g < 0.5 || vl.b < 0.5) 407 return 0.0; 408 else 409 return 1.0; 410 } 411 412 // Difference 413 float3 Difference(float3 a, float3 b) 414 { 415 return max(a - b, b - a); 416 } 417 418 // Exclusion 419 float3 Exclusion(float3 a, float3 b) 420 { 421 return a + b - 2 * a * b; 422 } 423 424 // Subtract 425 float3 Subtract(float3 a, float3 b) 426 { 427 return max((a - b), 0); 428 } 429 430 // Divide 431 float3 Divide(float3 a, float3 b) 432 { 433 return (saturate(a / (b + 0.01))); 434 } 435 436 // Divide (Alternative) 437 float3 DivideAlt(float3 a, float3 b) 438 { 439 return (saturate(1.0 / (a / b))); 440 } 441 442 // Divide (Photoshop) 443 float3 DividePS(float3 a, float3 b) 444 { 445 return (saturate(a / b)); 446 } 447 448 // Grain Merge 449 float3 GrainMerge(float3 a, float3 b) 450 { 451 return saturate(b + a - 0.5); 452 } 453 454 // Grain Extract 455 float3 GrainExtract(float3 a, float3 b) 456 { 457 return saturate(a - b + 0.5); 458 } 459 460 // Hue 461 float3 Hue(float3 a, float3 b) 462 { 463 return SetLum(SetSat(b, Sat(a)), Lum(a)); 464 } 465 466 // Saturation 467 float3 Saturation(float3 a, float3 b) 468 { 469 return SetLum(SetSat(a, Sat(b)), Lum(a)); 470 } 471 472 // Color 473 float3 ColorB(float3 a, float3 b) 474 { 475 return SetLum(b, Lum(a)); 476 } 477 478 // Luminousity 479 float3 Luminosity(float3 a, float3 b) 480 { 481 return SetLum(a, Lum(b)); 482 } 483 484 485 // ------------------------------------- 486 // Output Functions 487 // ------------------------------------- 488 489 float3 Blend(int mode, float3 input, float3 output, float blending) 490 { 491 switch (mode) 492 { 493 // Normal 494 default: 495 return lerp(input.rgb, output.rgb, blending); 496 // Darken 497 case 1: 498 return lerp(input.rgb, Darken(input.rgb, output.rgb), blending); 499 // Multiply 500 case 2: 501 return lerp(input.rgb, Multiply(input.rgb, output.rgb), blending); 502 // Color Burn 503 case 3: 504 return lerp(input.rgb, ColorBurn(input.rgb, output.rgb), blending); 505 // Linear Burn 506 case 4: 507 return lerp(input.rgb, LinearBurn(input.rgb, output.rgb), blending); 508 // Lighten 509 case 5: 510 return lerp(input.rgb, Lighten(input.rgb, output.rgb), blending); 511 // Screen 512 case 6: 513 return lerp(input.rgb, Screen(input.rgb, output.rgb), blending); 514 // Color Dodge 515 case 7: 516 return lerp(input.rgb, ColorDodge(input.rgb, output.rgb), blending); 517 // Linear Dodge 518 case 8: 519 return lerp(input.rgb, LinearDodge(input.rgb, output.rgb), blending); 520 // Addition 521 case 9: 522 return lerp(input.rgb, Addition(input.rgb, output.rgb), blending); 523 // Glow 524 case 10: 525 return lerp(input.rgb, Glow(input.rgb, output.rgb), blending); 526 // Overlay 527 case 11: 528 return lerp(input.rgb, Overlay(input.rgb, output.rgb), blending); 529 // Soft Light 530 case 12: 531 return lerp(input.rgb, SoftLight(input.rgb, output.rgb), blending); 532 // Hard Light 533 case 13: 534 return lerp(input.rgb, HardLight(input.rgb, output.rgb), blending); 535 // Vivid Light 536 case 14: 537 return lerp(input.rgb, VividLight(input.rgb, output.rgb), blending); 538 // Linear Light 539 case 15: 540 return lerp(input.rgb, LinearLight(input.rgb, output.rgb), blending); 541 // Pin Light 542 case 16: 543 return lerp(input.rgb, PinLight(input.rgb, output.rgb), blending); 544 // Hard Mix 545 case 17: 546 return lerp(input.rgb, HardMix(input.rgb, output.rgb), blending); 547 // Difference 548 case 18: 549 return lerp(input.rgb, Difference(input.rgb, output.rgb), blending); 550 // Exclusion 551 case 19: 552 return lerp(input.rgb, Exclusion(input.rgb, output.rgb), blending); 553 // Subtract 554 case 20: 555 return lerp(input.rgb, Subtract(input.rgb, output.rgb), blending); 556 // Divide 557 case 21: 558 return lerp(input.rgb, Divide(input.rgb, output.rgb), blending); 559 // Divide (Alternative) 560 case 22: 561 return lerp(input.rgb, DivideAlt(input.rgb, output.rgb), blending); 562 // Divide (Photoshop) 563 case 23: 564 return lerp(input.rgb, DividePS(input.rgb, output.rgb), blending); 565 // Reflect 566 case 24: 567 return lerp(input.rgb, Reflect(input.rgb, output.rgb), blending); 568 // Grain Merge 569 case 25: 570 return lerp(input.rgb, GrainMerge(input.rgb, output.rgb), blending); 571 // Grain Extract 572 case 26: 573 return lerp(input.rgb, GrainExtract(input.rgb, output.rgb), blending); 574 // Hue 575 case 27: 576 return lerp(input.rgb, Hue(input.rgb, output.rgb), blending); 577 // Saturation 578 case 28: 579 return lerp(input.rgb, Saturation(input.rgb, output.rgb), blending); 580 // Color 581 case 29: 582 return lerp(input.rgb, ColorB(input.rgb, output.rgb), blending); 583 // Luminosity 584 case 30: 585 return lerp(input.rgb, Luminosity(input.rgb, output.rgb), blending); 586 } 587 } 588 } 589 }