1 /*{# Copyright (c) 2012 Turbulenz Limited #}*/ 2 /* 3 * @title: Texture effects 4 * @description: 5 * This sample shows how to use advanced texture effects with the Draw2D API. 6 * You can select, customize and combine the following effects to be applied to the rendered texture: 7 * Distortion, Color Matrix, Bloom and Gaussian blur. 8 */ 9 /*{{ javascript("jslib/observer.js") }}*/ 10 /*{{ javascript("jslib/requesthandler.js") }}*/ 11 /*{{ javascript("jslib/utilities.js") }}*/ 12 /*{{ javascript("jslib/services/turbulenzservices.js") }}*/ 13 /*{{ javascript("jslib/services/turbulenzbridge.js") }}*/ 14 /*{{ javascript("jslib/services/gamesession.js") }}*/ 15 /*{{ javascript("jslib/services/mappingtable.js") }}*/ 16 /*{{ javascript("jslib/shadermanager.js") }}*/ 17 /*{{ javascript("jslib/draw2d.js") }}*/ 18 /*{{ javascript("jslib/textureeffects.js") }}*/ 19 /*{{ javascript("scripts/htmlcontrols.js") }}*/ 20 /*global TurbulenzEngine: true */ 21 /*global TurbulenzServices: false */ 22 /*global RequestHandler: false */ 23 /*global Draw2D: false */ 24 /*global Draw2DSprite: false */ 25 /*global TextureEffects: false */ 26 /*global HTMLControls: false */ 27 TurbulenzEngine.onload = function onloadFn() { 28 //========================================================================== 29 // HTML Controls 30 //========================================================================== 31 var htmlControls; 32 33 var distort = false; 34 var strength = 10; 35 var rotation = 0; 36 var scale = 1; 37 38 var colormatrix = false; 39 var saturation = 1; 40 var hue = 0; 41 var brightness = 0; 42 var contrast = 1; 43 var additiveRGB = [0, 0, 0]; 44 var grayscale = false; 45 var sepia = false; 46 var negative = false; 47 48 var bloom = false; 49 var bloomRadius = 20; 50 var bloomThreshold = 0.65; 51 var bloomIntensity = 1.2; 52 var bloomSaturation = 1.2; 53 var originalIntensity = 1.0; 54 var originalSaturation = 1.0; 55 var thresholdCutoff = 3; 56 57 var gausblur = false; 58 var gausBlurRadius = 10; 59 60 //========================================================================== 61 // Turbulenz Initialization 62 //========================================================================== 63 var graphicsDevice = TurbulenzEngine.createGraphicsDevice({}); 64 var mathDevice = TurbulenzEngine.createMathDevice({}); 65 var requestHandler = RequestHandler.create({}); 66 67 var loadedResources = 0; 68 69 // Textures to load: 70 var distortionTextureName = "textures/texfxdisplace.png"; 71 var backgroundTextureName = "textures/texfxbg.png"; 72 73 var textureNames = [distortionTextureName, backgroundTextureName]; 74 75 // List to store Texture objects. 76 var textures = {}; 77 var numResources = textureNames.length; 78 79 function mappingTableReceived(mappingTable) { 80 function textureParams(src) { 81 return { 82 src: mappingTable.getURL(src), 83 mipmaps: true, 84 onload: function (texture) { 85 if (texture) { 86 textures[src] = texture; 87 loadedResources += 1; 88 } 89 } 90 }; 91 } 92 93 var i; 94 for (i = 0; i < textureNames.length; i += 1) { 95 graphicsDevice.createTexture(textureParams(textureNames[i])); 96 } 97 } 98 99 var gameSession; 100 function sessionCreated() { 101 TurbulenzServices.createMappingTable(requestHandler, gameSession, mappingTableReceived); 102 } 103 104 gameSession = TurbulenzServices.createGameSession(requestHandler, sessionCreated); 105 106 //========================================================================== 107 // Draw2D and TextureEffects initialization 108 //========================================================================== 109 var clearColor = mathDevice.v4Build(0, 0, 0, 1); 110 111 var draw2D = Draw2D.create({ 112 graphicsDevice: graphicsDevice 113 }); 114 115 var effects = TextureEffects.create({ 116 graphicsDevice: graphicsDevice, 117 mathDevice: mathDevice 118 }); 119 120 // Create render targets used for effects rendering. 121 var renderTargetFullScreen1 = draw2D.createRenderTarget({}); 122 var renderTargetFullScreen2 = draw2D.createRenderTarget({}); 123 var renderTargetFullScreen3 = draw2D.createRenderTarget({}); 124 var renderTarget256A = draw2D.createRenderTarget({ width: 256, height: 256 }); 125 var renderTarget256B = draw2D.createRenderTarget({ width: 256, height: 256 }); 126 127 // Distortion effect. 128 // ------------------ 129 var distortEffectParam = { 130 transform: [1, 0, 0, 1, 0, 0], 131 source: null, 132 destination: null, 133 strength: 0, 134 distortion: null 135 }; 136 137 function applyDistort(src, dest) { 138 var param = distortEffectParam; 139 param.source = src; 140 param.destination = dest; 141 142 param.strength = strength; 143 var xform = param.transform; 144 145 // We invert rotation and scale as this transformation 146 // occurs in texture lookup to distortion texture. 147 // so to scale by 200% we have to scale texture lookups by 50%. 148 var cos = Math.cos(rotation) / scale; 149 var sin = -Math.sin(rotation) / scale; 150 151 // Determine additional scaling to fit distortion texture to 152 // section of back buffer / render target in use. 153 var w = src.width / graphicsDevice.width; 154 var h = src.height / graphicsDevice.height; 155 156 xform[0] = cos * w; 157 xform[1] = sin * w; 158 xform[2] = -sin * h; 159 xform[3] = cos * h; 160 xform[4] = 0.5 * (1 - cos + sin); 161 xform[5] = 0.5 * (1 - cos - sin); 162 163 effects.applyDistort(param); 164 } 165 166 // ColorMatrix effect. 167 // ------------------- 168 var tmpMatrix = mathDevice.m43BuildIdentity(); 169 var colorMatrixEffectParam = { 170 colorMatrix: mathDevice.m43BuildIdentity(), 171 source: null, 172 destination: null 173 }; 174 function applyColorMatrix(src, dest) { 175 var param = colorMatrixEffectParam; 176 177 var xform = param.colorMatrix; 178 var tmp = tmpMatrix; 179 param.source = src; 180 param.destination = dest; 181 182 mathDevice.m43BuildIdentity(xform); 183 if (saturation !== 1) { 184 effects.saturationMatrix(saturation, tmp); 185 mathDevice.m43Mul(xform, tmp, xform); 186 } 187 if ((hue % (Math.PI * 2)) !== 0) { 188 effects.hueMatrix(hue, tmp); 189 mathDevice.m43Mul(xform, tmp, xform); 190 } 191 if (contrast !== 1) { 192 effects.contrastMatrix(contrast, tmp); 193 mathDevice.m43Mul(xform, tmp, xform); 194 } 195 if (brightness !== 0) { 196 effects.brightnessMatrix(brightness, tmp); 197 mathDevice.m43Mul(xform, tmp, xform); 198 } 199 if (additiveRGB[0] !== 0 || additiveRGB[1] !== 0 || additiveRGB[2] !== 0) { 200 effects.additiveMatrix(additiveRGB, tmp); 201 mathDevice.m43Mul(xform, tmp, xform); 202 } 203 if (grayscale) { 204 effects.grayScaleMatrix(tmp); 205 mathDevice.m43Mul(xform, tmp, xform); 206 } 207 if (negative) { 208 effects.negativeMatrix(tmp); 209 mathDevice.m43Mul(xform, tmp, xform); 210 } 211 if (sepia) { 212 effects.sepiaMatrix(tmp); 213 mathDevice.m43Mul(xform, tmp, xform); 214 } 215 216 effects.applyColorMatrix(param); 217 } 218 219 // Bloom effect. 220 // ------------- 221 var bloomEffectParam = { 222 source: null, 223 destination: null, 224 blurRadius: 0, 225 bloomThreshold: 0, 226 bloomIntensity: 0, 227 bloomSaturation: 0, 228 originalIntensity: 0, 229 originalSaturation: 0, 230 thresholdCutoff: 0, 231 blurTarget1: null, 232 blurTarget2: null 233 }; 234 function applyBloom(src, dest) { 235 var param = bloomEffectParam; 236 param.source = src; 237 param.destination = dest; 238 param.blurRadius = bloomRadius; 239 param.bloomThreshold = bloomThreshold; 240 param.bloomIntensity = bloomIntensity; 241 param.bloomSaturation = bloomSaturation; 242 param.originalIntensity = originalIntensity; 243 param.originalSaturation = originalSaturation; 244 param.thresholdCutoff = thresholdCutoff; 245 246 // Strictly speaking as these are non-dynamic render targets, 247 // we can be sure the render target is not going to change 248 // instead of accessing it each time. 249 param.blurTarget1 = draw2D.getRenderTarget(renderTarget256A); 250 param.blurTarget2 = draw2D.getRenderTarget(renderTarget256B); 251 252 effects.applyBloom(param); 253 } 254 255 // GaussianBlur effect. 256 // -------------------- 257 var gausBlurEffectParam = { 258 source: null, 259 destination: null, 260 blurRadius: 0, 261 blurTarget: null 262 }; 263 function applyGausBlur(src, dest) { 264 var param = gausBlurEffectParam; 265 param.source = src; 266 param.destination = dest; 267 param.blurRadius = gausBlurRadius; 268 269 param.blurTarget = draw2D.getRenderTarget(renderTargetFullScreen3); 270 271 effects.applyGaussianBlur(param); 272 } 273 274 // List of active effect applicators. 275 var effElement = document.getElementById("effectInfo"); 276 var activeEffects = []; 277 278 function invalidateEffects() { 279 var names = []; 280 var i; 281 for (i = 0; i < activeEffects.length; i += 1) { 282 names[i] = activeEffects[i].name; 283 } 284 effElement.innerHTML = names.toString(); 285 } 286 287 //========================================================================== 288 // Main loop. 289 //========================================================================== 290 var fpsElement = document.getElementById("fpscounter"); 291 var lastFPS = ""; 292 var nextUpdate = 0; 293 function displayPerformance() { 294 var currentTime = TurbulenzEngine.time; 295 if (currentTime > nextUpdate) { 296 nextUpdate = (currentTime + 0.1); 297 298 var fpsText = (graphicsDevice.fps).toFixed(2); 299 if (lastFPS !== fpsText) { 300 lastFPS = fpsText; 301 fpsElement.innerHTML = fpsText + " fps"; 302 } 303 } 304 } 305 306 var backgroundSprite = null; 307 var distortionSprite = null; 308 function mainLoop() { 309 if (!graphicsDevice.beginFrame()) 310 return; 311 312 if (activeEffects.length === 0) { 313 draw2D.setBackBuffer(); 314 } else { 315 draw2D.setRenderTarget(renderTargetFullScreen1); 316 } 317 318 draw2D.clear(clearColor); 319 draw2D.begin(); 320 321 backgroundSprite.setWidth(graphicsDevice.width); 322 backgroundSprite.setHeight(graphicsDevice.height); 323 draw2D.drawSprite(backgroundSprite); 324 325 draw2D.end(); 326 327 // Apply effects! 328 var src = renderTargetFullScreen1; 329 var dest = renderTargetFullScreen2; 330 var i; 331 for (i = 0; i < activeEffects.length; i += 1) { 332 activeEffects[i].applyEffect(draw2D.getRenderTargetTexture(src), draw2D.getRenderTarget(dest)); 333 334 var tmp = src; 335 src = dest; 336 dest = tmp; 337 } 338 339 if (activeEffects.length !== 0) { 340 draw2D.setBackBuffer(); 341 draw2D.copyRenderTarget(src); 342 } 343 344 if (distort) { 345 draw2D.begin(); 346 draw2D.drawSprite(distortionSprite); 347 draw2D.end(); 348 } 349 350 graphicsDevice.endFrame(); 351 352 if (fpsElement) { 353 displayPerformance(); 354 } 355 } 356 357 var intervalID; 358 function loadingLoop() { 359 if (loadedResources === numResources) { 360 // Set distortion texture, create background sprite. 361 distortEffectParam.distortion = textures[distortionTextureName]; 362 363 backgroundSprite = Draw2DSprite.create({ 364 texture: textures[backgroundTextureName], 365 origin: [0, 0] 366 }); 367 368 distortionSprite = Draw2DSprite.create({ 369 texture: distortEffectParam.distortion, 370 origin: [0, 0], 371 width: 128, 372 height: 128 373 }); 374 375 TurbulenzEngine.clearInterval(intervalID); 376 intervalID = TurbulenzEngine.setInterval(mainLoop, 1000 / 60); 377 } 378 } 379 380 intervalID = TurbulenzEngine.setInterval(loadingLoop, 100); 381 382 //========================================================================== 383 function loadHtmlControls() { 384 var distortEffect = { 385 applyEffect: applyDistort, 386 name: "distort" 387 }; 388 var colorMatrixEffect = { 389 applyEffect: applyColorMatrix, 390 name: "colorMatrix" 391 }; 392 var gausBlurEffect = { 393 applyEffect: applyGausBlur, 394 name: "gaussianBlur" 395 }; 396 var bloomEffect = { 397 applyEffect: applyBloom, 398 name: "bloom" 399 }; 400 401 htmlControls = HTMLControls.create(); 402 htmlControls.addCheckboxControl({ 403 id: "distortBox", 404 value: "distort", 405 isSelected: distort, 406 fn: function () { 407 distort = !distort; 408 if (distort) { 409 activeEffects.push(distortEffect); 410 } else { 411 activeEffects.splice(activeEffects.indexOf(distortEffect), 1); 412 } 413 invalidateEffects(); 414 return distort; 415 } 416 }); 417 htmlControls.addSliderControl({ 418 id: "strengthSlider", 419 value: strength, 420 max: 100, 421 min: -100, 422 step: 1, 423 fn: function () { 424 strength = this.value; 425 htmlControls.updateSlider("strengthSlider", strength); 426 } 427 }); 428 htmlControls.addSliderControl({ 429 id: "rotationSlider", 430 value: Math.round(rotation * 180 / Math.PI), 431 max: 360, 432 min: 0, 433 step: 1, 434 fn: function () { 435 rotation = this.value * Math.PI / 180; 436 htmlControls.updateSlider("rotationSlider", Math.round(rotation * 180 / Math.PI)); 437 } 438 }); 439 htmlControls.addSliderControl({ 440 id: "scaleSlider", 441 value: (scale * 100), 442 max: 300, 443 min: -300, 444 step: 1, 445 fn: function () { 446 scale = this.value / 100; 447 htmlControls.updateSlider("scaleSlider", scale * 100); 448 } 449 }); 450 451 htmlControls.addCheckboxControl({ 452 id: "colorMatrixBox", 453 value: "colormatrix", 454 isSelected: colormatrix, 455 fn: function () { 456 colormatrix = !colormatrix; 457 if (colormatrix) { 458 activeEffects.push(colorMatrixEffect); 459 } else { 460 activeEffects.splice(activeEffects.indexOf(colorMatrixEffect), 1); 461 } 462 invalidateEffects(); 463 return colormatrix; 464 } 465 }); 466 htmlControls.addSliderControl({ 467 id: "saturationSlider", 468 value: (saturation * 100), 469 max: 200, 470 min: -200, 471 step: 1, 472 fn: function () { 473 saturation = this.value / 100; 474 htmlControls.updateSlider("saturationSlider", saturation * 100); 475 } 476 }); 477 htmlControls.addSliderControl({ 478 id: "hueSlider", 479 value: Math.round(hue * 180 / Math.PI), 480 max: 360, 481 min: 0, 482 step: 1, 483 fn: function () { 484 hue = this.value * Math.PI / 180; 485 htmlControls.updateSlider("hueSlider", Math.round(hue * 180 / Math.PI)); 486 } 487 }); 488 htmlControls.addSliderControl({ 489 id: "brightnessSlider", 490 value: brightness, 491 max: 1, 492 min: -1, 493 step: 0.01, 494 fn: function () { 495 brightness = this.value; 496 htmlControls.updateSlider("brightnessSlider", brightness); 497 } 498 }); 499 htmlControls.addSliderControl({ 500 id: "additiveRedSlider", 501 value: additiveRGB[0], 502 max: 1, 503 min: -1, 504 step: 0.01, 505 fn: function () { 506 additiveRGB[0] = this.value; 507 htmlControls.updateSlider("additiveRedSlider", additiveRGB[0]); 508 } 509 }); 510 htmlControls.addSliderControl({ 511 id: "additiveGreenSlider", 512 value: additiveRGB[1], 513 max: 1, 514 min: -1, 515 step: 0.01, 516 fn: function () { 517 additiveRGB[1] = this.value; 518 htmlControls.updateSlider("additiveGreenSlider", additiveRGB[1]); 519 } 520 }); 521 htmlControls.addSliderControl({ 522 id: "additiveBlueSlider", 523 value: additiveRGB[1], 524 max: 1, 525 min: -1, 526 step: 0.01, 527 fn: function () { 528 additiveRGB[2] = this.value; 529 htmlControls.updateSlider("additiveBlueSlider", additiveRGB[2]); 530 } 531 }); 532 htmlControls.addSliderControl({ 533 id: "contrastSlider", 534 value: (contrast * 100), 535 max: 200, 536 min: 0, 537 step: 1, 538 fn: function () { 539 contrast = this.value / 100; 540 htmlControls.updateSlider("contrastSlider", contrast * 100); 541 } 542 }); 543 htmlControls.addCheckboxControl({ 544 id: "grayscaleBox", 545 value: "grayscale", 546 isSelected: grayscale, 547 fn: function () { 548 grayscale = !grayscale; 549 return grayscale; 550 } 551 }); 552 htmlControls.addCheckboxControl({ 553 id: "negativeBox", 554 value: "negative", 555 isSelected: negative, 556 fn: function () { 557 negative = !negative; 558 return negative; 559 } 560 }); 561 htmlControls.addCheckboxControl({ 562 id: "sepiaBox", 563 value: "sepia", 564 isSelected: sepia, 565 fn: function () { 566 sepia = !sepia; 567 return sepia; 568 } 569 }); 570 571 htmlControls.addCheckboxControl({ 572 id: "bloomBox", 573 value: "bloom", 574 isSelected: bloom, 575 fn: function () { 576 bloom = !bloom; 577 if (bloom) { 578 activeEffects.push(bloomEffect); 579 } else { 580 activeEffects.splice(activeEffects.indexOf(bloomEffect), 1); 581 } 582 invalidateEffects(); 583 return bloom; 584 } 585 }); 586 htmlControls.addSliderControl({ 587 id: "bloomRadiusSlider", 588 value: bloomRadius, 589 max: 50, 590 min: 0, 591 step: 0.5, 592 fn: function () { 593 bloomRadius = this.value; 594 htmlControls.updateSlider("bloomRadiusSlider", bloomRadius); 595 } 596 }); 597 htmlControls.addSliderControl({ 598 id: "bloomThresholdSlider", 599 value: bloomThreshold, 600 max: 1, 601 min: 0, 602 step: 0.01, 603 fn: function () { 604 bloomThreshold = this.value; 605 htmlControls.updateSlider("bloomThresholdSlider", bloomThreshold); 606 } 607 }); 608 htmlControls.addSliderControl({ 609 id: "bloomThresholdCutoffSlider", 610 value: thresholdCutoff, 611 max: 6, 612 min: -3, 613 step: 0.25, 614 fn: function () { 615 thresholdCutoff = this.value; 616 htmlControls.updateSlider("bloomThresholdCutoffSlider", thresholdCutoff); 617 } 618 }); 619 htmlControls.addSliderControl({ 620 id: "bloomIntensitySlider", 621 value: bloomIntensity, 622 max: 2, 623 min: 0, 624 step: 0.02, 625 fn: function () { 626 bloomIntensity = this.value; 627 htmlControls.updateSlider("bloomIntensitySlider", bloomIntensity); 628 } 629 }); 630 htmlControls.addSliderControl({ 631 id: "bloomSaturationSlider", 632 value: bloomSaturation, 633 max: 2, 634 min: -2, 635 step: 0.04, 636 fn: function () { 637 bloomSaturation = this.value; 638 htmlControls.updateSlider("bloomSaturationSlider", bloomSaturation); 639 } 640 }); 641 htmlControls.addSliderControl({ 642 id: "originalIntensitySlider", 643 value: originalIntensity, 644 max: 2, 645 min: 0, 646 step: 0.02, 647 fn: function () { 648 originalIntensity = this.value; 649 htmlControls.updateSlider("originalIntensitySlider", originalIntensity); 650 } 651 }); 652 htmlControls.addSliderControl({ 653 id: "originalSaturationSlider", 654 value: originalSaturation, 655 max: 2, 656 min: -2, 657 step: 0.04, 658 fn: function () { 659 originalSaturation = this.value; 660 htmlControls.updateSlider("originalSaturationSlider", originalSaturation); 661 } 662 }); 663 664 htmlControls.addCheckboxControl({ 665 id: "gausBlurBox", 666 value: "gausblur", 667 isSelected: gausblur, 668 fn: function () { 669 gausblur = !gausblur; 670 if (gausblur) { 671 activeEffects.push(gausBlurEffect); 672 } else { 673 activeEffects.splice(activeEffects.indexOf(gausBlurEffect), 1); 674 } 675 invalidateEffects(); 676 return gausblur; 677 } 678 }); 679 htmlControls.addSliderControl({ 680 id: "gausBlurRadiusSlider", 681 value: gausBlurRadius, 682 max: 50, 683 min: 0, 684 step: 0.5, 685 fn: function () { 686 gausBlurRadius = this.value; 687 htmlControls.updateSlider("gausBlurRadiusSlider", gausBlurRadius); 688 } 689 }); 690 691 htmlControls.register(); 692 } 693 694 loadHtmlControls(); 695 696 // Create a scene destroy callback to run when the window is closed 697 TurbulenzEngine.onunload = function destroyScene() { 698 if (intervalID) { 699 TurbulenzEngine.clearInterval(intervalID); 700 } 701 702 if (gameSession) { 703 gameSession.destroy(); 704 gameSession = null; 705 } 706 }; 707 };