1 /*{# Copyright (c) 2010-2012 Turbulenz Limited #}*/ 2 /* 3 * @title: Post effects 4 * @description: 5 * This sample shows how to use render targets to apply post processing effects to a dynamically rendered image. 6 * You can select between simple copy, inverted colors or light bloom post effects. 7 */ 8 /*{{ javascript("jslib/aabbtree.js") }}*/ 9 /*{{ javascript("jslib/camera.js") }}*/ 10 /*{{ javascript("jslib/geometry.js") }}*/ 11 /*{{ javascript("jslib/material.js") }}*/ 12 /*{{ javascript("jslib/light.js") }}*/ 13 /*{{ javascript("jslib/scenenode.js") }}*/ 14 /*{{ javascript("jslib/scene.js") }}*/ 15 /*{{ javascript("jslib/vmath.js") }}*/ 16 /*{{ javascript("jslib/effectmanager.js") }}*/ 17 /*{{ javascript("jslib/shadermanager.js") }}*/ 18 /*{{ javascript("jslib/texturemanager.js") }}*/ 19 /*{{ javascript("jslib/renderingcommon.js") }}*/ 20 /*{{ javascript("jslib/defaultrendering.js") }}*/ 21 /*{{ javascript("jslib/observer.js") }}*/ 22 /*{{ javascript("jslib/resourceloader.js") }}*/ 23 /*{{ javascript("jslib/utilities.js") }}*/ 24 /*{{ javascript("jslib/requesthandler.js") }}*/ 25 /*{{ javascript("jslib/vertexbuffermanager.js") }}*/ 26 /*{{ javascript("jslib/indexbuffermanager.js") }}*/ 27 /*{{ javascript("jslib/services/turbulenzservices.js") }}*/ 28 /*{{ javascript("jslib/services/turbulenzbridge.js") }}*/ 29 /*{{ javascript("jslib/services/gamesession.js") }}*/ 30 /*{{ javascript("jslib/services/mappingtable.js") }}*/ 31 /*{{ javascript("scripts/sceneloader.js") }}*/ 32 /*{{ javascript("scripts/motion.js") }}*/ 33 /*{{ javascript("scripts/htmlcontrols.js") }}*/ 34 /*global TurbulenzEngine: true */ 35 /*global TurbulenzServices: false */ 36 /*global Motion: false */ 37 /*global RequestHandler: false */ 38 /*global TextureManager: false */ 39 /*global ShaderManager: false */ 40 /*global EffectManager: false */ 41 /*global Scene: false */ 42 /*global SceneLoader: false */ 43 /*global Camera: false */ 44 /*global HTMLControls: false */ 45 /*global DefaultRendering: false */ 46 TurbulenzEngine.onload = function onloadFn() { 47 var errorCallback = function errorCallback(msg) { 48 window.alert(msg); 49 }; 50 51 var graphicsDeviceParameters = {}; 52 var graphicsDevice = TurbulenzEngine.createGraphicsDevice(graphicsDeviceParameters); 53 54 if (!graphicsDevice.shadingLanguageVersion) { 55 errorCallback("No shading language support detected.\nPlease check your graphics drivers are up to date."); 56 graphicsDevice = null; 57 return; 58 } 59 60 // Clear the background color of the engine window 61 var clearColor = [0.5, 0.5, 0.5, 1.0]; 62 if (graphicsDevice.beginFrame()) { 63 graphicsDevice.clear(clearColor); 64 graphicsDevice.endFrame(); 65 } 66 67 var mathDeviceParameters = {}; 68 var mathDevice = TurbulenzEngine.createMathDevice(mathDeviceParameters); 69 70 var requestHandlerParameters = {}; 71 var requestHandler = RequestHandler.create(requestHandlerParameters); 72 73 var textureManager = TextureManager.create(graphicsDevice, requestHandler, null, errorCallback); 74 var shaderManager = ShaderManager.create(graphicsDevice, requestHandler, null, errorCallback); 75 var effectManager = EffectManager.create(); 76 77 var scene = Scene.create(mathDevice); 78 var sceneLoader = SceneLoader.create(); 79 80 // Setup world space 81 var worldUp = mathDevice.v3Build(0.0, 1.0, 0.0); 82 83 // Setup a camera to view a close-up object 84 var camera = Camera.create(mathDevice); 85 var cameraDistanceFactor = 1.0; 86 camera.nearPlane = 0.05; 87 88 var defaultHeight = 1024; 89 var defaultWidth = 1024; 90 91 var postFX = { 92 copy: 0, 93 invert: 1, 94 bloom: 2 95 }; 96 97 // The current effect to be processed 98 var effectID = postFX.copy; 99 100 // The effects available 101 var effects = {}; 102 103 // Renderer for the scene. 104 var renderer; 105 106 var shaderLoaded = function shaderLoadedFn(shaderText) { 107 if (shaderText) { 108 var shaderParameters = JSON.parse(shaderText); 109 var shader = graphicsDevice.createShader(shaderParameters); 110 if (shader) { 111 var technique = shader.getTechnique("luminance"); 112 if (technique) { 113 effects.luminance = { 114 technique: technique, 115 techniqueParameters: graphicsDevice.createTechniqueParameters({ 116 luminance: 0.51, 117 bgColor: mathDevice.v4BuildZero(), 118 inputTexture0: null 119 }) 120 }; 121 } 122 123 technique = shader.getTechnique("blurvert"); 124 if (technique) { 125 effects.blurvert = { 126 technique: technique, 127 techniqueParameters: graphicsDevice.createTechniqueParameters({ 128 vertDim: defaultHeight / 2, 129 inputTexture0: null 130 }) 131 }; 132 } 133 134 technique = shader.getTechnique("blurhorz"); 135 if (technique) { 136 effects.blurhorz = { 137 technique: technique, 138 techniqueParameters: graphicsDevice.createTechniqueParameters({ 139 horzDim: defaultWidth / 2, 140 inputTexture0: null 141 }) 142 }; 143 } 144 145 technique = shader.getTechnique("composite"); 146 if (technique) { 147 effects.composite = { 148 technique: technique, 149 techniqueParameters: graphicsDevice.createTechniqueParameters({ 150 alpha: 0.8, 151 inputTexture0: null, 152 inputTexture1: null 153 }) 154 }; 155 } 156 157 technique = shader.getTechnique("copy"); 158 if (technique) { 159 effects.copy = { 160 technique: technique, 161 techniqueParameters: graphicsDevice.createTechniqueParameters({ 162 inputTexture0: null 163 }) 164 }; 165 } 166 167 technique = shader.getTechnique("invert"); 168 if (technique) { 169 effects.invert = { 170 technique: technique, 171 techniqueParameters: graphicsDevice.createTechniqueParameters({ 172 inputTexture0: null 173 }) 174 }; 175 } 176 } 177 } 178 }; 179 180 // Render Target 181 var renderBuffer = null; 182 var renderTarget = null; 183 var renderTexture = null; 184 185 var renderTextureParameters = { 186 name: "rendertexture", 187 width: defaultWidth, 188 height: defaultHeight, 189 depth: 1, 190 format: "R8G8B8A8", 191 cubemap: false, 192 mipmaps: false, 193 renderable: true 194 }; 195 196 var renderBufferParams = { 197 width: defaultWidth, 198 height: defaultHeight, 199 format: "D24S8" 200 }; 201 202 var renderTargetParams = { 203 colorTexture0: null, 204 depthBuffer: null 205 }; 206 207 // PostFX Target 208 var postFXTarget = null; 209 var postFXTexture = null; 210 211 var postFXTextureParameters = { 212 name: "postfxtexture", 213 width: defaultWidth / 2, 214 height: defaultHeight / 2, 215 depth: 1, 216 format: "R8G8B8A8", 217 cubemap: false, 218 mipmaps: false, 219 renderable: true 220 }; 221 222 var postFXTargetParams = { 223 colorTexture0: null 224 }; 225 226 // PostFX2 Target 227 var postFX2Target = null; 228 var postFX2Texture = null; 229 230 var postFX2TextureParameters = { 231 name: "postfx2texture", 232 width: defaultWidth / 2, 233 height: defaultHeight / 2, 234 depth: 1, 235 format: "R8G8B8A8", 236 cubemap: false, 237 mipmaps: false, 238 renderable: true 239 }; 240 241 var postFX2TargetParams = { 242 colorTexture0: null 243 }; 244 245 /*jshint white: false*/ 246 // Create a quad 247 var quadVertexBufferParams = { 248 numVertices: 4, 249 attributes: ['FLOAT2', 'FLOAT2'], 250 dynamic: false, 251 data: [ 252 -1.0, 253 1.0, 254 0.0, 255 1.0, 256 1.0, 257 1.0, 258 1.0, 259 1.0, 260 -1.0, 261 -1.0, 262 0.0, 263 0.0, 264 1.0, 265 -1.0, 266 1.0, 267 0.0 268 ] 269 }; 270 271 /*jshint white: true*/ 272 var quadSemantics = graphicsDevice.createSemantics(['POSITION', 'TEXCOORD0']); 273 var quadPrimitive = graphicsDevice.PRIMITIVE_TRIANGLE_STRIP; 274 var quadVertexBuffer = graphicsDevice.createVertexBuffer(quadVertexBufferParams); 275 276 var destroyBuffers = function destroyBuffersFn() { 277 if (renderTarget) { 278 renderTarget.destroy(); 279 renderTarget = null; 280 } 281 282 if (renderBuffer) { 283 renderBuffer.destroy(); 284 renderBuffer = null; 285 } 286 287 if (renderTexture) { 288 renderTexture.destroy(); 289 renderTexture = null; 290 } 291 292 if (postFXTarget) { 293 postFXTarget.destroy(); 294 postFXTarget = null; 295 } 296 297 if (postFXTexture) { 298 postFXTexture.destroy(); 299 postFXTexture = null; 300 } 301 302 if (postFX2Target) { 303 postFX2Target.destroy(); 304 postFX2Target = null; 305 } 306 307 if (postFX2Texture) { 308 postFX2Texture.destroy(); 309 postFX2Texture = null; 310 } 311 }; 312 313 var createBuffers = function createBuffersFn() { 314 // Create renderTarget textures & buffers 315 renderTexture = graphicsDevice.createTexture(renderTextureParameters); 316 if (!renderTexture) { 317 errorCallback("Render Texture not created."); 318 } 319 320 renderBuffer = graphicsDevice.createRenderBuffer(renderBufferParams); 321 if (!renderBuffer) { 322 errorCallback("Render Buffer not created."); 323 } 324 325 if (renderTexture && renderBuffer) { 326 renderTargetParams.colorTexture0 = renderTexture; 327 renderTargetParams.depthBuffer = renderBuffer; 328 329 renderTarget = graphicsDevice.createRenderTarget(renderTargetParams); 330 } 331 332 // Create postFX textures & buffers 333 postFXTexture = graphicsDevice.createTexture(postFXTextureParameters); 334 if (!postFXTexture) { 335 errorCallback("PostFX Texture not created."); 336 } 337 338 if (postFXTexture) { 339 postFXTargetParams.colorTexture0 = postFXTexture; 340 341 postFXTarget = graphicsDevice.createRenderTarget(postFXTargetParams); 342 } 343 344 // Create postFX2 textures & buffers 345 postFX2Texture = graphicsDevice.createTexture(postFX2TextureParameters); 346 if (!postFX2Texture) { 347 errorCallback("PostFX2 Texture not created."); 348 } 349 350 if (postFX2Texture) { 351 postFX2TargetParams.colorTexture0 = postFX2Texture; 352 353 postFX2Target = graphicsDevice.createRenderTarget(postFX2TargetParams); 354 } 355 }; 356 357 // Process effect finds the requested, sets the technique and input texture, then runs on the specified render target 358 var processEffect = function processEffectFn(params) { 359 var outputTarget = params.outputTarget; 360 var inputTexture0 = params.inputTexture0; 361 362 var effect = effects[params.effect]; 363 if (effect) { 364 var techniqueParameters = effect.techniqueParameters; 365 366 graphicsDevice.setTechnique(effect.technique); 367 368 techniqueParameters.inputTexture0 = inputTexture0; 369 graphicsDevice.setTechniqueParameters(techniqueParameters); 370 371 if (graphicsDevice.beginRenderTarget(outputTarget)) { 372 // Apply the effect to the output render target 373 graphicsDevice.setStream(quadVertexBuffer, quadSemantics); 374 graphicsDevice.draw(quadPrimitive, 4); 375 376 graphicsDevice.endRenderTarget(); 377 } 378 } 379 }; 380 381 var setupCopy = function setupCopyFn(inputTexture) { 382 // The copy is applied directly from the default render target to backbuffer 383 var copy = effects.copy; 384 if (copy) { 385 var copyTechniqueParameters = copy.techniqueParameters; 386 copyTechniqueParameters.inputTexture0 = inputTexture; 387 388 // The copy technique & copyTechniqueParameters are set ready for the final pass 389 graphicsDevice.setTechnique(copy.technique); 390 graphicsDevice.setTechniqueParameters(copyTechniqueParameters); 391 } 392 }; 393 394 var setupInvertEffect = function setupInvertEffectFn(inputTexture) { 395 // The invert is also applied directly from the default render target to backbuffer 396 var invert = effects.invert; 397 if (invert) { 398 var invertTechniqueParameters = invert.techniqueParameters; 399 invertTechniqueParameters.inputTexture0 = inputTexture; 400 401 // The invert technique & techniqueParameters are set ready for the final pass 402 graphicsDevice.setTechnique(invert.technique); 403 graphicsDevice.setTechniqueParameters(invertTechniqueParameters); 404 } 405 }; 406 407 var renderBloomEffect = function renderBloomEffectFn(inputTexture) { 408 // To demonstrate the use of multiple render targets, this bloom effect uses additional render targets 409 // Pass 1: Luminance of inputTexture, downsampled to the smaller postFXTarget 410 processEffect({ 411 effect: "luminance", 412 outputTarget: postFXTarget, 413 inputTexture0: inputTexture 414 }); 415 416 // Pass 2: blur the texture in the vertical direction to postFX2Target 417 processEffect({ 418 effect: "blurvert", 419 outputTarget: postFX2Target, 420 inputTexture0: postFXTexture 421 }); 422 423 // Pass 3: blur the texture in the horizontal direction back to postFXTarget 424 processEffect({ 425 effect: "blurhorz", 426 outputTarget: postFXTarget, 427 inputTexture0: postFX2Texture 428 }); 429 430 // The composite is applied to the postFX texture and the input texture to the backbuffer 431 var composite = effects.composite; 432 if (composite) { 433 var compositeTechniqueParameters = composite.techniqueParameters; 434 compositeTechniqueParameters.inputTexture0 = inputTexture; 435 compositeTechniqueParameters.inputTexture1 = postFXTexture; 436 437 // The composite technique & techniqueParameters, ready for the final pass 438 graphicsDevice.setTechnique(composite.technique); 439 graphicsDevice.setTechniqueParameters(compositeTechniqueParameters); 440 } 441 }; 442 443 // The name of the node we want to rotate to demonstrate the postFX effect 444 var nodeName = "LOD3sp"; 445 var objectNode = null; 446 447 // Initialise all render targets, textures and buffers 448 createBuffers(); 449 450 var rotation = Motion.create(mathDevice, "scene"); 451 rotation.setConstantRotation(0.01); 452 453 // Initialize the previous frame time 454 var previousFrameTime = TurbulenzEngine.time; 455 456 var renderFrame = function renderFrameFn() { 457 var currentTime = TurbulenzEngine.time; 458 var deltaTime = (currentTime - previousFrameTime); 459 if (deltaTime > 0.1) { 460 deltaTime = 0.1; 461 } 462 var gdWidth = graphicsDevice.width; 463 var gdHeight = graphicsDevice.height; 464 465 var aspectRatio = (gdWidth / gdHeight); 466 if (aspectRatio !== camera.aspectRatio) { 467 camera.aspectRatio = aspectRatio; 468 camera.updateProjectionMatrix(); 469 } 470 camera.updateViewProjectionMatrix(); 471 472 if (objectNode) { 473 // Update the scene with rotation 474 rotation.update(deltaTime); 475 objectNode.setLocalTransform(rotation.matrix); 476 } 477 478 scene.update(); 479 480 renderer.update(graphicsDevice, camera, scene, currentTime); 481 482 if (graphicsDevice.beginFrame()) { 483 if (renderTarget) { 484 if (graphicsDevice.beginRenderTarget(renderTarget)) { 485 if (renderer.updateBuffers(graphicsDevice, gdWidth, gdHeight)) { 486 renderer.draw(graphicsDevice, clearColor); 487 } 488 489 graphicsDevice.endRenderTarget(); 490 } 491 } 492 493 switch (effectID) { 494 case postFX.copy: 495 // Copy only needs to set the copy technique and texture to copy from 496 // In this case, the renderTexture 497 setupCopy(renderTexture); 498 break; 499 case postFX.invert: 500 // Invert only needs to set the invert technique and texture to apply the invert to 501 // In this case, the renderTexture 502 setupInvertEffect(renderTexture); 503 break; 504 case postFX.bloom: 505 // This effect requires an additional render target to perform the postFX technique 506 // Once it has applied the technique to the render target, it will set the final technique to apply to the backbuffer 507 renderBloomEffect(renderTexture); 508 break; 509 default: 510 } 511 512 // The final step is to apply the final postFX technique to the backbuffer 513 graphicsDevice.setStream(quadVertexBuffer, quadSemantics); 514 graphicsDevice.draw(quadPrimitive, 4); 515 516 graphicsDevice.endFrame(); 517 } 518 }; 519 520 // Controls 521 var htmlControls = HTMLControls.create(); 522 523 htmlControls.addRadioControl({ 524 id: "radio01", 525 groupName: "postEffect", 526 radioIndex: 0, 527 value: "copy", 528 fn: function () { 529 effectID = postFX.copy; 530 }, 531 isDefault: true 532 }); 533 htmlControls.addRadioControl({ 534 id: "radio02", 535 groupName: "postEffect", 536 radioIndex: 1, 537 value: "invert", 538 fn: function () { 539 effectID = postFX.invert; 540 }, 541 isDefault: false 542 }); 543 htmlControls.addRadioControl({ 544 id: "radio03", 545 groupName: "postEffect", 546 radioIndex: 2, 547 value: "bloom", 548 fn: function () { 549 effectID = postFX.bloom; 550 }, 551 isDefault: false 552 }); 553 htmlControls.register(); 554 555 var intervalID; 556 var loadingLoop = function loadingLoopFn() { 557 if (sceneLoader.complete()) { 558 TurbulenzEngine.clearInterval(intervalID); 559 560 objectNode = scene.findNode(nodeName); 561 562 var sceneExtents = scene.getExtents(); 563 var sceneMinExtent = mathDevice.v3Build(sceneExtents[0], sceneExtents[1], sceneExtents[2]); 564 var sceneMaxExtent = mathDevice.v3Build(sceneExtents[3], sceneExtents[4], sceneExtents[5]); 565 var center = mathDevice.v3ScalarMul(mathDevice.v3Add(sceneMaxExtent, sceneMinExtent), 0.5); 566 var extent = mathDevice.v3Sub(center, sceneMinExtent); 567 568 camera.lookAt(center, worldUp, mathDevice.v3Build(center[0] + extent[0] * 2 * cameraDistanceFactor, center[1] + extent[1] * cameraDistanceFactor, center[2] + extent[2] * 2 * cameraDistanceFactor)); 569 camera.updateViewMatrix(); 570 571 renderer.updateShader(shaderManager); 572 573 intervalID = TurbulenzEngine.setInterval(renderFrame, 1000 / 60); 574 } 575 }; 576 intervalID = TurbulenzEngine.setInterval(loadingLoop, 1000 / 10); 577 578 var loadAssets = function loadAssetsFn(mappingTable) { 579 // Renderer for the scene (requires shader assets). 580 renderer = DefaultRendering.create(graphicsDevice, mathDevice, shaderManager, effectManager); 581 582 renderer.setGlobalLightPosition(mathDevice.v3Build(0.5, 100.0, 0.5)); 583 renderer.setAmbientColor(mathDevice.v3Build(0.3, 0.3, 0.4)); 584 renderer.setDefaultTexture(textureManager.get("default")); 585 586 // Create duck 587 sceneLoader.load({ 588 scene: scene, 589 assetPath: "models/duck.dae", 590 graphicsDevice: graphicsDevice, 591 mathDevice: mathDevice, 592 textureManager: textureManager, 593 effectManager: effectManager, 594 shaderManager: shaderManager, 595 requestHandler: requestHandler, 596 append: false, 597 dynamic: true 598 }); 599 600 requestHandler.request({ 601 src: mappingTable.getURL("shaders/postfx.cgfx"), 602 onload: shaderLoaded 603 }); 604 }; 605 606 var mappingTableReceived = function mappingTableReceivedFn(mappingTable) { 607 textureManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix); 608 shaderManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix); 609 sceneLoader.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix); 610 611 loadAssets(mappingTable); 612 }; 613 614 var gameSessionCreated = function gameSessionCreatedFn(gameSession) { 615 TurbulenzServices.createMappingTable(requestHandler, gameSession, mappingTableReceived); 616 }; 617 var gameSession = TurbulenzServices.createGameSession(requestHandler, gameSessionCreated); 618 619 // Create a scene destroy callback to run when the window is closed 620 TurbulenzEngine.onunload = function destroyScene() { 621 TurbulenzEngine.clearInterval(intervalID); 622 clearColor = null; 623 624 if (gameSession) { 625 gameSession.destroy(); 626 gameSession = null; 627 } 628 629 rotation = null; 630 if (scene) { 631 scene.destroy(); 632 scene = null; 633 } 634 requestHandler = null; 635 636 if (renderer) { 637 renderer.destroy(); 638 renderer = null; 639 } 640 641 camera = null; 642 643 effects = null; 644 645 renderTextureParameters = null; 646 renderBufferParams = null; 647 renderTargetParams = null; 648 649 postFXTextureParameters = null; 650 postFXTargetParams = null; 651 652 postFX2TextureParameters = null; 653 postFX2TargetParams = null; 654 655 quadVertexBufferParams = null; 656 quadSemantics = null; 657 quadPrimitive = null; 658 quadVertexBuffer = null; 659 660 destroyBuffers(); 661 662 if (textureManager) { 663 textureManager.destroy(); 664 textureManager = null; 665 } 666 667 if (shaderManager) { 668 shaderManager.destroy(); 669 shaderManager = null; 670 } 671 672 effectManager = null; 673 674 TurbulenzEngine.flush(); 675 graphicsDevice = null; 676 mathDevice = null; 677 }; 678 };