postfx.js
  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 };