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