/** * @author Richard Davey * @copyright 2016 Photon Storm Ltd. * @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License} */ /** * Frame Buffer Object with drawing quad + shader * * @class Phaser.Renderer.Canvas * @constructor * @param {Phaser.Game} game - Game reference to the currently running game. */ Phaser.Renderer.WebGL.QuadFBO = function (renderer, x, y, width, height) { this.renderer = renderer; this.gl = null; this._x = x; this._y = y; this._width = width; this._height = height; this.textureIndex = 0; this.clipX = function (x) { return (renderer.clipUnitX * x) - 1; }; this.clipY = function (y) { return 1 - (renderer.clipUnitY * y); }; this.vertexBuffer; this.indexBuffer; this.textureBuffer; this.vertices; this.texture; this.renderBuffer; this.frameBuffer; this.program; this.aVertexPosition; this.aTextureCoord; }; Phaser.Renderer.WebGL.QuadFBO.prototype.constructor = Phaser.Renderer.WebGL.QuadFBO; Phaser.Renderer.WebGL.QuadFBO.prototype = { init: function () { // console.log('QuadFBO.init'); this.gl = this.renderer.gl; var gl = this.gl; // An FBO quad is made up of 2 triangles (A and B in the image below) // // 0 = Bottom Left // 1 = Bottom Right // 2 = Top Left // 3 = Top Right // // 2----3 // |\ B| // | \ | // | \ | // | A \| // | \ // 0----1 this.indexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([ 0, 1, 2, 2, 1, 3 ]), gl.STATIC_DRAW); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null); this.vertices = new Float32Array(8); this.updateVerts(); this.vertexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW); this.textureBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.textureBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ 0, 0, 1, 0, 0, 1, 1, 1 ]), gl.STATIC_DRAW); // Create a texture for our color buffer // this.texture = this.renderer.createEmptyTexture(this.width, this.height); console.log('QuadFBO created texture bound to TEXTURE0'); var texture = gl.createTexture(); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); // We'll read from this texture, but it won't have mipmaps, so turn them off: gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.width, this.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); this.texture = texture; console.log(this.texture); // this.renderer.textureArray[0] = this.texture; // The FBO's depth buffer this.renderBuffer = gl.createRenderbuffer(); gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderBuffer); gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.width, this.height); this.frameBuffer = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer); gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texture, 0); // May need to optionally be: gl.DEPTH_STENCIL_ATTACHMENT gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, this.renderBuffer); var fbStatus = gl.checkFramebufferStatus(gl.FRAMEBUFFER); if (fbStatus !== gl.FRAMEBUFFER_COMPLETE) { window.console.error('FrameBuffer Error: ', this.renderer._fbErrors[fbStatus]); } this.createShader(); // Reset back to defaults gl.bindRenderbuffer(gl.RENDERBUFFER, null); gl.bindFramebuffer(gl.FRAMEBUFFER, null); }, // This whole function ought to be split out into the Shader Manager // so they can easily change the shader being used for an FBO. // This class will have to expose those shader attribs though. createShader: function () { // Create the quad shader var gl = this.gl; var vertexSrc = [ 'attribute vec2 aVertexPosition;', 'attribute vec2 aTextureCoord;', 'varying vec2 vTextureCoord;', 'void main(void) {', ' vTextureCoord = aTextureCoord;', ' gl_Position = vec4(aVertexPosition, 0.0, 1.0);', '}' ]; var fragmentSrc = [ 'precision mediump float;', 'uniform sampler2D uSampler;', 'varying vec2 vTextureCoord;', 'void main(void) {', ' gl_FragColor = texture2D(uSampler, vTextureCoord);', '}' ]; var twirlFragmentSrc = [ 'precision mediump float;', 'uniform sampler2D uSampler;', 'varying vec2 vTextureCoord;', 'const float radius = 0.5;', 'const float angle = 5.0;', 'const vec2 offset = vec2(0.5, 0.5);', 'void main(void) {', ' vec2 coord = vTextureCoord - offset;', ' float distance = length(coord);', ' if (distance < radius) {', ' float ratio = (radius - distance) / radius;', ' float angleMod = ratio * ratio * angle;', ' float s = sin(angleMod);', ' float c = cos(angleMod);', ' coord = vec2(coord.x * c - coord.y * s, coord.x * s + coord.y * c);', ' }', ' gl_FragColor = texture2D(uSampler, coord + offset);', '}' ]; // This compiles, attaches and links the shader this.program = this.renderer.compileProgram(vertexSrc, fragmentSrc); this.program2 = this.renderer.compileProgram(vertexSrc, twirlFragmentSrc); this.aVertexPosition = gl.getAttribLocation(this.program, 'aVertexPosition'); this.aTextureCoord = gl.getAttribLocation(this.program, 'aTextureCoord'); }, setPosition: function (x, y) { if (x === undefined) { x = 0; } if (y === undefined) { y = 0; } if (x !== this._x || y !== this._y) { this._x = x; this._y = y; this.updateVerts(); } }, setSize: function (width, height) { if (width === undefined) { width = this.renderer.width; } if (height === undefined) { height = this.renderer.height; } if (width !== this._width || height !== this._height) { this._width = width; this._height = height; this.updateVerts(); } }, updateVerts: function () { var x = this._x; var y = this._y; var width = this._width; var height = this._height; // Bottom Left this.vertices[0] = this.clipX(x); this.vertices[1] = this.clipY(y + height); // Bottom Right this.vertices[2] = this.clipX(x + width); this.vertices[3] = this.clipY(y + height); // Top Left this.vertices[4] = this.clipX(x); this.vertices[5] = this.clipY(y); // Top Right this.vertices[6] = this.clipX(x + width); this.vertices[7] = this.clipY(y); }, activate: function () { var gl = this.gl; // console.log('QuadFBO.activate'); gl.bindFramebuffer(gl.FRAMEBUFFER, this.frameBuffer); // gl.bindRenderbuffer(gl.RENDERBUFFER, this.renderBuffer); this.renderer.textureArray[0] = this.texture; // gl.viewport(0, 0, this.width, this.height); // gl.clear(gl.COLOR_BUFFER_BIT); }, bindShader: function () { var program = this.program2; // console.log('QuadFBO bindShader'); var gl = this.gl; // gl.useProgram(program); gl.uniform1i(gl.getUniformLocation(program, 'uSampler'), 0); gl.enableVertexAttribArray(this.aTextureCoord); gl.enableVertexAttribArray(this.aVertexPosition); gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer); gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices); gl.vertexAttribPointer(this.aVertexPosition, 2, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, this.textureBuffer); gl.vertexAttribPointer(this.aTextureCoord, 2, gl.FLOAT, false, 0, 0); }, // destinationBuffer MUST be set, even if just to 'null' render: function (destinationBuffer) { // console.log('QuadFBO.render'); var gl = this.gl; // Return to the default framebuffer gl.bindFramebuffer(gl.FRAMEBUFFER, destinationBuffer); // Bind the texture we rendered to, for reading gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this.texture); // The shader that will read from the fbo texture if (this.renderer.shaderManager.setShader(this.program2)) { this.bindShader(); } gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0); this.renderer.drawCount++; // Unbind the fbo texture - forget this and Chrome goes haywire! gl.bindTexture(gl.TEXTURE_2D, null); }, destroy: function () { // TODO! } }; Object.defineProperties(Phaser.Renderer.WebGL.QuadFBO.prototype, { x: { enumerable: true, get: function () { return this._x; }, set: function (value) { if (value !== this._x) { this._x = value; this.updateVerts(); } } }, y: { enumerable: true, get: function () { return this._y; }, set: function (value) { if (value !== this._y) { this._y = value; this.updateVerts(); } } }, width: { enumerable: true, get: function () { return this._width; }, set: function (value) { if (value !== this._width) { this._width = value; this.updateVerts(); } } }, height: { enumerable: true, get: function () { return this._height; }, set: function (value) { if (value !== this._height) { this._height = value; this.updateVerts(); } } } });