mirror of
https://github.com/photonstorm/phaser
synced 2024-12-25 12:33:38 +00:00
1226 lines
40 KiB
JavaScript
1226 lines
40 KiB
JavaScript
/**
|
|
* @author Richard Davey <rich@photonstorm.com>
|
|
* @copyright 2022 Photon Storm Ltd.
|
|
* @license {@link https://opensource.org/licenses/MIT|MIT License}
|
|
*/
|
|
|
|
var Class = require('../../utils/Class');
|
|
var Components = require('../components');
|
|
var GameObject = require('../GameObject');
|
|
var GetFastValue = require('../../utils/object/GetFastValue');
|
|
var Extend = require('../../utils/object/Extend');
|
|
var SetValue = require('../../utils/object/SetValue');
|
|
var ShaderRender = require('./ShaderRender');
|
|
var TransformMatrix = require('../components/TransformMatrix');
|
|
|
|
/**
|
|
* @classdesc
|
|
* A Shader Game Object.
|
|
*
|
|
* This Game Object allows you to easily add a quad with its own shader into the display list, and manipulate it
|
|
* as you would any other Game Object, including scaling, rotating, positioning and adding to Containers. Shaders
|
|
* can be masked with either Bitmap or Geometry masks and can also be used as a Bitmap Mask for a Camera or other
|
|
* Game Object. They can also be made interactive and used for input events.
|
|
*
|
|
* It works by taking a reference to a `Phaser.Display.BaseShader` instance, as found in the Shader Cache. These can
|
|
* be created dynamically at runtime, or loaded in via the GLSL File Loader:
|
|
*
|
|
* ```javascript
|
|
* function preload ()
|
|
* {
|
|
* this.load.glsl('fire', 'shaders/fire.glsl.js');
|
|
* }
|
|
*
|
|
* function create ()
|
|
* {
|
|
* this.add.shader('fire', 400, 300, 512, 512);
|
|
* }
|
|
* ```
|
|
*
|
|
* Please see the Phaser 3 Examples GitHub repo for examples of loading and creating shaders dynamically.
|
|
*
|
|
* Due to the way in which they work, you cannot directly change the alpha or blend mode of a Shader. This should
|
|
* be handled via exposed uniforms in the shader code itself.
|
|
*
|
|
* By default a Shader will be created with a standard set of uniforms. These were added to match those
|
|
* found on sites such as ShaderToy or GLSLSandbox, and provide common functionality a shader may need,
|
|
* such as the timestamp, resolution or pointer position. You can replace them by specifying your own uniforms
|
|
* in the Base Shader.
|
|
*
|
|
* These Shaders work by halting the current pipeline during rendering, creating a viewport matched to the
|
|
* size of this Game Object and then renders a quad using the bound shader. At the end, the pipeline is restored.
|
|
*
|
|
* Because it blocks the pipeline it means it will interrupt any batching that is currently going on, so you should
|
|
* use these Game Objects sparingly. If you need to have a fully batched custom shader, then please look at using
|
|
* a custom pipeline instead. However, for background or special masking effects, they are extremely effective.
|
|
*
|
|
* @class Shader
|
|
* @extends Phaser.GameObjects.GameObject
|
|
* @memberof Phaser.GameObjects
|
|
* @constructor
|
|
* @webglOnly
|
|
* @since 3.17.0
|
|
*
|
|
* @extends Phaser.GameObjects.Components.ComputedSize
|
|
* @extends Phaser.GameObjects.Components.Depth
|
|
* @extends Phaser.GameObjects.Components.GetBounds
|
|
* @extends Phaser.GameObjects.Components.Mask
|
|
* @extends Phaser.GameObjects.Components.Origin
|
|
* @extends Phaser.GameObjects.Components.ScrollFactor
|
|
* @extends Phaser.GameObjects.Components.Transform
|
|
* @extends Phaser.GameObjects.Components.Visible
|
|
*
|
|
* @param {Phaser.Scene} scene - The Scene to which this Game Object belongs. A Game Object can only belong to one Scene at a time.
|
|
* @param {(string|Phaser.Display.BaseShader)} key - The key of the shader to use from the shader cache, or a BaseShader instance.
|
|
* @param {number} [x=0] - The horizontal position of this Game Object in the world.
|
|
* @param {number} [y=0] - The vertical position of this Game Object in the world.
|
|
* @param {number} [width=128] - The width of the Game Object.
|
|
* @param {number} [height=128] - The height of the Game Object.
|
|
* @param {string[]} [textures] - Optional array of texture keys to bind to the iChannel0...3 uniforms. The textures must already exist in the Texture Manager.
|
|
* @param {any} [textureData] - Additional texture data if you want to create shader with none NPOT textures.
|
|
*/
|
|
var Shader = new Class({
|
|
|
|
Extends: GameObject,
|
|
|
|
Mixins: [
|
|
Components.ComputedSize,
|
|
Components.Depth,
|
|
Components.GetBounds,
|
|
Components.Mask,
|
|
Components.Origin,
|
|
Components.ScrollFactor,
|
|
Components.Transform,
|
|
Components.Visible,
|
|
ShaderRender
|
|
],
|
|
|
|
initialize:
|
|
|
|
function Shader (scene, key, x, y, width, height, textures, textureData)
|
|
{
|
|
if (x === undefined) { x = 0; }
|
|
if (y === undefined) { y = 0; }
|
|
if (width === undefined) { width = 128; }
|
|
if (height === undefined) { height = 128; }
|
|
|
|
GameObject.call(this, scene, 'Shader');
|
|
|
|
/**
|
|
* This Game Object cannot have a blend mode, so skip all checks.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#blendMode
|
|
* @type {number}
|
|
* @private
|
|
* @since 3.17.0
|
|
*/
|
|
this.blendMode = -1;
|
|
|
|
/**
|
|
* The underlying shader object being used.
|
|
* Empty by default and set during a call to the `setShader` method.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#shader
|
|
* @type {Phaser.Display.BaseShader}
|
|
* @since 3.17.0
|
|
*/
|
|
this.shader;
|
|
|
|
var renderer = scene.sys.renderer;
|
|
|
|
/**
|
|
* A reference to the current renderer.
|
|
* Shaders only work with the WebGL Renderer.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#renderer
|
|
* @type {(Phaser.Renderer.Canvas.CanvasRenderer|Phaser.Renderer.WebGL.WebGLRenderer)}
|
|
* @since 3.17.0
|
|
*/
|
|
this.renderer = renderer;
|
|
|
|
/**
|
|
* The WebGL context belonging to the renderer.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#gl
|
|
* @type {WebGLRenderingContext}
|
|
* @since 3.17.0
|
|
*/
|
|
this.gl = renderer.gl;
|
|
|
|
/**
|
|
* Raw byte buffer of vertices this Shader uses.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#vertexData
|
|
* @type {ArrayBuffer}
|
|
* @since 3.17.0
|
|
*/
|
|
this.vertexData = new ArrayBuffer(6 * (Float32Array.BYTES_PER_ELEMENT * 2));
|
|
|
|
/**
|
|
* The WebGL vertex buffer object this shader uses.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#vertexBuffer
|
|
* @type {WebGLBuffer}
|
|
* @since 3.17.0
|
|
*/
|
|
this.vertexBuffer = renderer.createVertexBuffer(this.vertexData.byteLength, this.gl.STREAM_DRAW);
|
|
|
|
/**
|
|
* The WebGL shader program this shader uses.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#program
|
|
* @type {WebGLProgram}
|
|
* @since 3.17.0
|
|
*/
|
|
this.program = null;
|
|
|
|
/**
|
|
* Uint8 view to the vertex raw buffer. Used for uploading vertex buffer resources to the GPU.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#bytes
|
|
* @type {Uint8Array}
|
|
* @since 3.17.0
|
|
*/
|
|
this.bytes = new Uint8Array(this.vertexData);
|
|
|
|
/**
|
|
* Float32 view of the array buffer containing the shaders vertices.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#vertexViewF32
|
|
* @type {Float32Array}
|
|
* @since 3.17.0
|
|
*/
|
|
this.vertexViewF32 = new Float32Array(this.vertexData);
|
|
|
|
/**
|
|
* A temporary Transform Matrix, re-used internally during batching.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#_tempMatrix1
|
|
* @private
|
|
* @type {Phaser.GameObjects.Components.TransformMatrix}
|
|
* @since 3.17.0
|
|
*/
|
|
this._tempMatrix1 = new TransformMatrix();
|
|
|
|
/**
|
|
* A temporary Transform Matrix, re-used internally during batching.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#_tempMatrix2
|
|
* @private
|
|
* @type {Phaser.GameObjects.Components.TransformMatrix}
|
|
* @since 3.17.0
|
|
*/
|
|
this._tempMatrix2 = new TransformMatrix();
|
|
|
|
/**
|
|
* A temporary Transform Matrix, re-used internally during batching.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#_tempMatrix3
|
|
* @private
|
|
* @type {Phaser.GameObjects.Components.TransformMatrix}
|
|
* @since 3.17.0
|
|
*/
|
|
this._tempMatrix3 = new TransformMatrix();
|
|
|
|
/**
|
|
* The view matrix the shader uses during rendering.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#viewMatrix
|
|
* @type {Float32Array}
|
|
* @readonly
|
|
* @since 3.17.0
|
|
*/
|
|
this.viewMatrix = new Float32Array([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]);
|
|
|
|
/**
|
|
* The projection matrix the shader uses during rendering.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#projectionMatrix
|
|
* @type {Float32Array}
|
|
* @readonly
|
|
* @since 3.17.0
|
|
*/
|
|
this.projectionMatrix = new Float32Array([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]);
|
|
|
|
/**
|
|
* The default uniform mappings. These can be added to (or replaced) by specifying your own uniforms when
|
|
* creating this shader game object. The uniforms are updated automatically during the render step.
|
|
*
|
|
* The defaults are:
|
|
*
|
|
* `resolution` (2f) - Set to the size of this shader.
|
|
* `time` (1f) - The elapsed game time, in seconds.
|
|
* `mouse` (2f) - If a pointer has been bound (with `setPointer`), this uniform contains its position each frame.
|
|
* `date` (4fv) - A vec4 containing the year, month, day and time in seconds.
|
|
* `sampleRate` (1f) - Sound sample rate. 44100 by default.
|
|
* `iChannel0...3` (sampler2D) - Input channels 0 to 3. `null` by default.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#uniforms
|
|
* @type {any}
|
|
* @since 3.17.0
|
|
*/
|
|
this.uniforms = {};
|
|
|
|
/**
|
|
* The pointer bound to this shader, if any.
|
|
* Set via the chainable `setPointer` method, or by modifying this property directly.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#pointer
|
|
* @type {Phaser.Input.Pointer}
|
|
* @since 3.17.0
|
|
*/
|
|
this.pointer = null;
|
|
|
|
/**
|
|
* The cached width of the renderer.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#_rendererWidth
|
|
* @type {number}
|
|
* @private
|
|
* @since 3.17.0
|
|
*/
|
|
this._rendererWidth = renderer.width;
|
|
|
|
/**
|
|
* The cached height of the renderer.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#_rendererHeight
|
|
* @type {number}
|
|
* @private
|
|
* @since 3.17.0
|
|
*/
|
|
this._rendererHeight = renderer.height;
|
|
|
|
/**
|
|
* Internal texture count tracker.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#_textureCount
|
|
* @type {number}
|
|
* @private
|
|
* @since 3.17.0
|
|
*/
|
|
this._textureCount = 0;
|
|
|
|
/**
|
|
* A reference to the GL Frame Buffer this Shader is drawing to.
|
|
* This property is only set if you have called `Shader.setRenderToTexture`.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#framebuffer
|
|
* @type {?WebGLFramebuffer}
|
|
* @since 3.19.0
|
|
*/
|
|
this.framebuffer = null;
|
|
|
|
/**
|
|
* A reference to the WebGLTexture this Shader is rendering to.
|
|
* This property is only set if you have called `Shader.setRenderToTexture`.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#glTexture
|
|
* @type {?WebGLTexture}
|
|
* @since 3.19.0
|
|
*/
|
|
this.glTexture = null;
|
|
|
|
/**
|
|
* A flag that indicates if this Shader has been set to render to a texture instead of the display list.
|
|
*
|
|
* This property is `true` if you have called `Shader.setRenderToTexture`, otherwise it's `false`.
|
|
*
|
|
* A Shader that is rendering to a texture _does not_ appear on the display list.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#renderToTexture
|
|
* @type {boolean}
|
|
* @readonly
|
|
* @since 3.19.0
|
|
*/
|
|
this.renderToTexture = false;
|
|
|
|
/**
|
|
* A reference to the Phaser.Textures.Texture that has been stored in the Texture Manager for this Shader.
|
|
*
|
|
* This property is only set if you have called `Shader.setRenderToTexture`, otherwise it is `null`.
|
|
*
|
|
* @name Phaser.GameObjects.Shader#texture
|
|
* @type {Phaser.Textures.Texture}
|
|
* @since 3.19.0
|
|
*/
|
|
this.texture = null;
|
|
|
|
this.setPosition(x, y);
|
|
this.setSize(width, height);
|
|
this.setOrigin(0.5, 0.5);
|
|
this.setShader(key, textures, textureData);
|
|
},
|
|
|
|
/**
|
|
* Compares the renderMask with the renderFlags to see if this Game Object will render or not.
|
|
* Also checks the Game Object against the given Cameras exclusion list.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#willRender
|
|
* @since 3.0.0
|
|
*
|
|
* @param {Phaser.Cameras.Scene2D.Camera} camera - The Camera to check against this Game Object.
|
|
*
|
|
* @return {boolean} True if the Game Object should be rendered, otherwise false.
|
|
*/
|
|
willRender: function (camera)
|
|
{
|
|
if (this.renderToTexture)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return !(GameObject.RENDER_MASK !== this.renderFlags || (this.cameraFilter !== 0 && (this.cameraFilter & camera.id)));
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Changes this Shader so instead of rendering to the display list it renders to a
|
|
* WebGL Framebuffer and WebGL Texture instead. This allows you to use the output
|
|
* of this shader as an input for another shader, by mapping a sampler2D uniform
|
|
* to it.
|
|
*
|
|
* After calling this method the `Shader.framebuffer` and `Shader.glTexture` properties
|
|
* are populated.
|
|
*
|
|
* Additionally, you can provide a key to this method. Doing so will create a Phaser Texture
|
|
* from this Shader and save it into the Texture Manager, allowing you to then use it for
|
|
* any texture-based Game Object, such as a Sprite or Image:
|
|
*
|
|
* ```javascript
|
|
* var shader = this.add.shader('myShader', x, y, width, height);
|
|
*
|
|
* shader.setRenderToTexture('doodle');
|
|
*
|
|
* this.add.image(400, 300, 'doodle');
|
|
* ```
|
|
*
|
|
* Note that it stores an active reference to this Shader. That means as this shader updates,
|
|
* so does the texture and any object using it to render with. Also, if you destroy this
|
|
* shader, be sure to clear any objects that may have been using it as a texture too.
|
|
*
|
|
* You can access the Phaser Texture that is created via the `Shader.texture` property.
|
|
*
|
|
* By default it will create a single base texture. You can add frames to the texture
|
|
* by using the `Texture.add` method. After doing this, you can then allow Game Objects
|
|
* to use a specific frame from a Render Texture.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#setRenderToTexture
|
|
* @since 3.19.0
|
|
*
|
|
* @param {string} [key] - The unique key to store the texture as within the global Texture Manager.
|
|
* @param {boolean} [flipY=false] - Does this texture need vertically flipping before rendering? This should usually be set to `true` if being fed from a buffer.
|
|
*
|
|
* @return {this} This Shader instance.
|
|
*/
|
|
setRenderToTexture: function (key, flipY)
|
|
{
|
|
if (flipY === undefined) { flipY = false; }
|
|
|
|
if (!this.renderToTexture)
|
|
{
|
|
var width = this.width;
|
|
var height = this.height;
|
|
var renderer = this.renderer;
|
|
|
|
this.glTexture = renderer.createTextureFromSource(null, width, height, 0);
|
|
|
|
this.glTexture.flipY = flipY;
|
|
|
|
this.framebuffer = renderer.createFramebuffer(width, height, this.glTexture, false);
|
|
|
|
this._rendererWidth = width;
|
|
this._rendererHeight = height;
|
|
|
|
this.renderToTexture = true;
|
|
|
|
this.projOrtho(0, this.width, this.height, 0);
|
|
|
|
if (key)
|
|
{
|
|
this.texture = this.scene.sys.textures.addGLTexture(key, this.glTexture, width, height);
|
|
}
|
|
}
|
|
|
|
// And now render at least once, so our texture isn't blank on the first update
|
|
|
|
if (this.shader)
|
|
{
|
|
renderer.pipelines.clear();
|
|
|
|
this.load();
|
|
this.flush();
|
|
|
|
renderer.pipelines.rebind();
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Sets the fragment and, optionally, the vertex shader source code that this Shader will use.
|
|
* This will immediately delete the active shader program, if set, and then create a new one
|
|
* with the given source. Finally, the shader uniforms are initialized.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#setShader
|
|
* @since 3.17.0
|
|
*
|
|
* @param {(string|Phaser.Display.BaseShader)} key - The key of the shader to use from the shader cache, or a BaseShader instance.
|
|
* @param {string[]} [textures] - Optional array of texture keys to bind to the iChannel0...3 uniforms. The textures must already exist in the Texture Manager.
|
|
* @param {any} [textureData] - Additional texture data.
|
|
*
|
|
* @return {this} This Shader instance.
|
|
*/
|
|
setShader: function (key, textures, textureData)
|
|
{
|
|
if (textures === undefined) { textures = []; }
|
|
|
|
if (typeof key === 'string')
|
|
{
|
|
var cache = this.scene.sys.cache.shader;
|
|
|
|
if (!cache.has(key))
|
|
{
|
|
console.warn('Shader missing: ' + key);
|
|
return this;
|
|
}
|
|
|
|
this.shader = cache.get(key);
|
|
}
|
|
else
|
|
{
|
|
this.shader = key;
|
|
}
|
|
|
|
var gl = this.gl;
|
|
var renderer = this.renderer;
|
|
|
|
if (this.program)
|
|
{
|
|
gl.deleteProgram(this.program);
|
|
}
|
|
|
|
var program = renderer.createProgram(this.shader.vertexSrc, this.shader.fragmentSrc);
|
|
|
|
// The default uniforms available within the vertex shader
|
|
gl.uniformMatrix4fv(gl.getUniformLocation(program, 'uViewMatrix'), false, this.viewMatrix);
|
|
gl.uniformMatrix4fv(gl.getUniformLocation(program, 'uProjectionMatrix'), false, this.projectionMatrix);
|
|
gl.uniform2f(gl.getUniformLocation(program, 'uResolution'), this.width, this.height);
|
|
|
|
this.program = program;
|
|
|
|
var d = new Date();
|
|
|
|
// The default uniforms available within the fragment shader
|
|
var defaultUniforms = {
|
|
resolution: { type: '2f', value: { x: this.width, y: this.height } },
|
|
time: { type: '1f', value: 0 },
|
|
mouse: { type: '2f', value: { x: this.width / 2, y: this.height / 2 } },
|
|
date: { type: '4fv', value: [ d.getFullYear(), d.getMonth(), d.getDate(), d.getHours() * 60 * 60 + d.getMinutes() * 60 + d.getSeconds() ] },
|
|
sampleRate: { type: '1f', value: 44100.0 },
|
|
iChannel0: { type: 'sampler2D', value: null, textureData: { repeat: true } },
|
|
iChannel1: { type: 'sampler2D', value: null, textureData: { repeat: true } },
|
|
iChannel2: { type: 'sampler2D', value: null, textureData: { repeat: true } },
|
|
iChannel3: { type: 'sampler2D', value: null, textureData: { repeat: true } }
|
|
};
|
|
|
|
if (this.shader.uniforms)
|
|
{
|
|
this.uniforms = Extend(true, {}, this.shader.uniforms, defaultUniforms);
|
|
}
|
|
else
|
|
{
|
|
this.uniforms = defaultUniforms;
|
|
}
|
|
|
|
for (var i = 0; i < 4; i++)
|
|
{
|
|
if (textures[i])
|
|
{
|
|
this.setSampler2D('iChannel' + i, textures[i], i, textureData);
|
|
}
|
|
}
|
|
|
|
this.initUniforms();
|
|
|
|
this.projOrtho(0, this._rendererWidth, this._rendererHeight, 0);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Binds a Phaser Pointer object to this Shader.
|
|
*
|
|
* The screen position of the pointer will be set in to the shaders `mouse` uniform
|
|
* automatically every frame. Call this method with no arguments to unbind the pointer.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#setPointer
|
|
* @since 3.17.0
|
|
*
|
|
* @param {Phaser.Input.Pointer} [pointer] - The Pointer to bind to this shader.
|
|
*
|
|
* @return {this} This Shader instance.
|
|
*/
|
|
setPointer: function (pointer)
|
|
{
|
|
this.pointer = pointer;
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Sets this shader to use an orthographic projection matrix.
|
|
* This matrix is stored locally in the `projectionMatrix` property,
|
|
* as well as being bound to the `uProjectionMatrix` uniform.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#projOrtho
|
|
* @since 3.17.0
|
|
*
|
|
* @param {number} left - The left value.
|
|
* @param {number} right - The right value.
|
|
* @param {number} bottom - The bottom value.
|
|
* @param {number} top - The top value.
|
|
*/
|
|
projOrtho: function (left, right, bottom, top)
|
|
{
|
|
var near = -1000;
|
|
var far = 1000;
|
|
|
|
var leftRight = 1 / (left - right);
|
|
var bottomTop = 1 / (bottom - top);
|
|
var nearFar = 1 / (near - far);
|
|
|
|
var pm = this.projectionMatrix;
|
|
|
|
pm[0] = -2 * leftRight;
|
|
pm[5] = -2 * bottomTop;
|
|
pm[10] = 2 * nearFar;
|
|
pm[12] = (left + right) * leftRight;
|
|
pm[13] = (top + bottom) * bottomTop;
|
|
pm[14] = (far + near) * nearFar;
|
|
|
|
var program = this.program;
|
|
|
|
var gl = this.gl;
|
|
var renderer = this.renderer;
|
|
|
|
renderer.setProgram(program);
|
|
|
|
gl.uniformMatrix4fv(gl.getUniformLocation(program, 'uProjectionMatrix'), false, this.projectionMatrix);
|
|
|
|
this._rendererWidth = right;
|
|
this._rendererHeight = bottom;
|
|
},
|
|
|
|
// Uniforms are specified in the GLSL_ES Specification: http://www.khronos.org/registry/webgl/specs/latest/1.0/
|
|
// http://www.khronos.org/registry/gles/specs/2.0/GLSL_ES_Specification_1.0.17.pdf
|
|
|
|
/**
|
|
* Initializes all of the uniforms this shader uses.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#initUniforms
|
|
* @private
|
|
* @since 3.17.0
|
|
*/
|
|
initUniforms: function ()
|
|
{
|
|
var gl = this.gl;
|
|
var map = this.renderer.glFuncMap;
|
|
var program = this.program;
|
|
|
|
this._textureCount = 0;
|
|
|
|
for (var key in this.uniforms)
|
|
{
|
|
var uniform = this.uniforms[key];
|
|
|
|
var type = uniform.type;
|
|
var data = map[type];
|
|
|
|
uniform.uniformLocation = gl.getUniformLocation(program, key);
|
|
|
|
if (type !== 'sampler2D')
|
|
{
|
|
uniform.glMatrix = data.matrix;
|
|
uniform.glValueLength = data.length;
|
|
uniform.glFunc = data.func;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Sets a sampler2D uniform on this shader where the source texture is a WebGLTexture.
|
|
*
|
|
* This allows you to feed the output from one Shader into another:
|
|
*
|
|
* ```javascript
|
|
* let shader1 = this.add.shader(baseShader1, 0, 0, 512, 512).setRenderToTexture();
|
|
* let shader2 = this.add.shader(baseShader2, 0, 0, 512, 512).setRenderToTexture('output');
|
|
*
|
|
* shader1.setSampler2DBuffer('iChannel0', shader2.glTexture, 512, 512);
|
|
* shader2.setSampler2DBuffer('iChannel0', shader1.glTexture, 512, 512);
|
|
* ```
|
|
*
|
|
* In the above code, the result of baseShader1 is fed into Shader2 as the `iChannel0` sampler2D uniform.
|
|
* The result of baseShader2 is then fed back into shader1 again, creating a feedback loop.
|
|
*
|
|
* If you wish to use an image from the Texture Manager as a sampler2D input for this shader,
|
|
* see the `Shader.setSampler2D` method.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#setSampler2DBuffer
|
|
* @since 3.19.0
|
|
*
|
|
* @param {string} uniformKey - The key of the sampler2D uniform to be updated, i.e. `iChannel0`.
|
|
* @param {WebGLTexture} texture - A WebGLTexture reference.
|
|
* @param {number} width - The width of the texture.
|
|
* @param {number} height - The height of the texture.
|
|
* @param {number} [textureIndex=0] - The texture index.
|
|
* @param {any} [textureData] - Additional texture data.
|
|
*
|
|
* @return {this} This Shader instance.
|
|
*/
|
|
setSampler2DBuffer: function (uniformKey, texture, width, height, textureIndex, textureData)
|
|
{
|
|
if (textureIndex === undefined) { textureIndex = 0; }
|
|
if (textureData === undefined) { textureData = {}; }
|
|
|
|
var uniform = this.uniforms[uniformKey];
|
|
|
|
uniform.value = texture;
|
|
|
|
textureData.width = width;
|
|
textureData.height = height;
|
|
|
|
uniform.textureData = textureData;
|
|
|
|
this._textureCount = textureIndex;
|
|
|
|
this.initSampler2D(uniform);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Sets a sampler2D uniform on this shader.
|
|
*
|
|
* The textureKey given is the key from the Texture Manager cache. You cannot use a single frame
|
|
* from a texture, only the full image. Also, lots of shaders expect textures to be power-of-two sized.
|
|
*
|
|
* If you wish to use another Shader as a sampler2D input for this shader, see the `Shader.setSampler2DBuffer` method.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#setSampler2D
|
|
* @since 3.17.0
|
|
*
|
|
* @param {string} uniformKey - The key of the sampler2D uniform to be updated, i.e. `iChannel0`.
|
|
* @param {string} textureKey - The key of the texture, as stored in the Texture Manager. Must already be loaded.
|
|
* @param {number} [textureIndex=0] - The texture index.
|
|
* @param {any} [textureData] - Additional texture data.
|
|
*
|
|
* @return {this} This Shader instance.
|
|
*/
|
|
setSampler2D: function (uniformKey, textureKey, textureIndex, textureData)
|
|
{
|
|
if (textureIndex === undefined) { textureIndex = 0; }
|
|
|
|
var textureManager = this.scene.sys.textures;
|
|
|
|
if (textureManager.exists(textureKey))
|
|
{
|
|
var frame = textureManager.getFrame(textureKey);
|
|
|
|
if (frame.glTexture && frame.glTexture.isRenderTexture)
|
|
{
|
|
return this.setSampler2DBuffer(uniformKey, frame.glTexture, frame.width, frame.height, textureIndex, textureData);
|
|
}
|
|
|
|
var uniform = this.uniforms[uniformKey];
|
|
var source = frame.source;
|
|
|
|
uniform.textureKey = textureKey;
|
|
uniform.source = source.image;
|
|
uniform.value = frame.glTexture;
|
|
|
|
if (source.isGLTexture)
|
|
{
|
|
if (!textureData)
|
|
{
|
|
textureData = {};
|
|
}
|
|
|
|
textureData.width = source.width;
|
|
textureData.height = source.height;
|
|
}
|
|
|
|
if (textureData)
|
|
{
|
|
uniform.textureData = textureData;
|
|
}
|
|
|
|
this._textureCount = textureIndex;
|
|
|
|
this.initSampler2D(uniform);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Sets a property of a uniform already present on this shader.
|
|
*
|
|
* To modify the value of a uniform such as a 1f or 1i use the `value` property directly:
|
|
*
|
|
* ```javascript
|
|
* shader.setUniform('size.value', 16);
|
|
* ```
|
|
*
|
|
* You can use dot notation to access deeper values, for example:
|
|
*
|
|
* ```javascript
|
|
* shader.setUniform('resolution.value.x', 512);
|
|
* ```
|
|
*
|
|
* The change to the uniform will take effect the next time the shader is rendered.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#setUniform
|
|
* @since 3.17.0
|
|
*
|
|
* @param {string} key - The key of the uniform to modify. Use dots for deep properties, i.e. `resolution.value.x`.
|
|
* @param {any} value - The value to set into the uniform.
|
|
*
|
|
* @return {this} This Shader instance.
|
|
*/
|
|
setUniform: function (key, value)
|
|
{
|
|
SetValue(this.uniforms, key, value);
|
|
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Returns the uniform object for the given key, or `null` if the uniform couldn't be found.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#getUniform
|
|
* @since 3.17.0
|
|
*
|
|
* @param {string} key - The key of the uniform to return the value for.
|
|
*
|
|
* @return {any} A reference to the uniform object. This is not a copy, so modifying it will update the original object also.
|
|
*/
|
|
getUniform: function (key)
|
|
{
|
|
return GetFastValue(this.uniforms, key, null);
|
|
},
|
|
|
|
/**
|
|
* A short-cut method that will directly set the texture being used by the `iChannel0` sampler2D uniform.
|
|
*
|
|
* The textureKey given is the key from the Texture Manager cache. You cannot use a single frame
|
|
* from a texture, only the full image. Also, lots of shaders expect textures to be power-of-two sized.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#setChannel0
|
|
* @since 3.17.0
|
|
*
|
|
* @param {string} textureKey - The key of the texture, as stored in the Texture Manager. Must already be loaded.
|
|
* @param {any} [textureData] - Additional texture data.
|
|
*
|
|
* @return {this} This Shader instance.
|
|
*/
|
|
setChannel0: function (textureKey, textureData)
|
|
{
|
|
return this.setSampler2D('iChannel0', textureKey, 0, textureData);
|
|
},
|
|
|
|
/**
|
|
* A short-cut method that will directly set the texture being used by the `iChannel1` sampler2D uniform.
|
|
*
|
|
* The textureKey given is the key from the Texture Manager cache. You cannot use a single frame
|
|
* from a texture, only the full image. Also, lots of shaders expect textures to be power-of-two sized.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#setChannel1
|
|
* @since 3.17.0
|
|
*
|
|
* @param {string} textureKey - The key of the texture, as stored in the Texture Manager. Must already be loaded.
|
|
* @param {any} [textureData] - Additional texture data.
|
|
*
|
|
* @return {this} This Shader instance.
|
|
*/
|
|
setChannel1: function (textureKey, textureData)
|
|
{
|
|
return this.setSampler2D('iChannel1', textureKey, 1, textureData);
|
|
},
|
|
|
|
/**
|
|
* A short-cut method that will directly set the texture being used by the `iChannel2` sampler2D uniform.
|
|
*
|
|
* The textureKey given is the key from the Texture Manager cache. You cannot use a single frame
|
|
* from a texture, only the full image. Also, lots of shaders expect textures to be power-of-two sized.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#setChannel2
|
|
* @since 3.17.0
|
|
*
|
|
* @param {string} textureKey - The key of the texture, as stored in the Texture Manager. Must already be loaded.
|
|
* @param {any} [textureData] - Additional texture data.
|
|
*
|
|
* @return {this} This Shader instance.
|
|
*/
|
|
setChannel2: function (textureKey, textureData)
|
|
{
|
|
return this.setSampler2D('iChannel2', textureKey, 2, textureData);
|
|
},
|
|
|
|
/**
|
|
* A short-cut method that will directly set the texture being used by the `iChannel3` sampler2D uniform.
|
|
*
|
|
* The textureKey given is the key from the Texture Manager cache. You cannot use a single frame
|
|
* from a texture, only the full image. Also, lots of shaders expect textures to be power-of-two sized.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#setChannel3
|
|
* @since 3.17.0
|
|
*
|
|
* @param {string} textureKey - The key of the texture, as stored in the Texture Manager. Must already be loaded.
|
|
* @param {any} [textureData] - Additional texture data.
|
|
*
|
|
* @return {this} This Shader instance.
|
|
*/
|
|
setChannel3: function (textureKey, textureData)
|
|
{
|
|
return this.setSampler2D('iChannel3', textureKey, 3, textureData);
|
|
},
|
|
|
|
/**
|
|
* Internal method that takes a sampler2D uniform and prepares it for use by setting the
|
|
* gl texture parameters.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#initSampler2D
|
|
* @private
|
|
* @since 3.17.0
|
|
*
|
|
* @param {any} uniform - The sampler2D uniform to process.
|
|
*/
|
|
initSampler2D: function (uniform)
|
|
{
|
|
if (!uniform.value)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var gl = this.gl;
|
|
|
|
gl.activeTexture(gl.TEXTURE0 + this._textureCount);
|
|
gl.bindTexture(gl.TEXTURE_2D, uniform.value);
|
|
|
|
// Extended texture data
|
|
|
|
var data = uniform.textureData;
|
|
|
|
if (data && !uniform.value.isRenderTexture)
|
|
{
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texImage2D
|
|
|
|
// mag / minFilter can be: gl.LINEAR, gl.LINEAR_MIPMAP_LINEAR or gl.NEAREST
|
|
// wrapS/T can be: gl.CLAMP_TO_EDGE or gl.REPEAT
|
|
// format can be: gl.LUMINANCE or gl.RGBA
|
|
|
|
var magFilter = gl[GetFastValue(data, 'magFilter', 'linear').toUpperCase()];
|
|
var minFilter = gl[GetFastValue(data, 'minFilter', 'linear').toUpperCase()];
|
|
var wrapS = gl[GetFastValue(data, 'wrapS', 'repeat').toUpperCase()];
|
|
var wrapT = gl[GetFastValue(data, 'wrapT', 'repeat').toUpperCase()];
|
|
var format = gl[GetFastValue(data, 'format', 'rgba').toUpperCase()];
|
|
|
|
if (data.repeat)
|
|
{
|
|
wrapS = gl.REPEAT;
|
|
wrapT = gl.REPEAT;
|
|
}
|
|
|
|
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !!data.flipY);
|
|
|
|
if (data.width)
|
|
{
|
|
var width = GetFastValue(data, 'width', 512);
|
|
var height = GetFastValue(data, 'height', 2);
|
|
var border = GetFastValue(data, 'border', 0);
|
|
|
|
// texImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, ArrayBufferView? pixels)
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, format, width, height, border, format, gl.UNSIGNED_BYTE, null);
|
|
}
|
|
else
|
|
{
|
|
// texImage2D(GLenum target, GLint level, GLenum internalformat, GLenum format, GLenum type, ImageData? pixels)
|
|
gl.texImage2D(gl.TEXTURE_2D, 0, format, gl.RGBA, gl.UNSIGNED_BYTE, uniform.source);
|
|
}
|
|
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, magFilter);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapS);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapT);
|
|
}
|
|
|
|
this.renderer.setProgram(this.program);
|
|
|
|
gl.uniform1i(uniform.uniformLocation, this._textureCount);
|
|
|
|
this._textureCount++;
|
|
},
|
|
|
|
/**
|
|
* Synchronizes all of the uniforms this shader uses.
|
|
* Each uniforms gl function is called in turn.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#syncUniforms
|
|
* @private
|
|
* @since 3.17.0
|
|
*/
|
|
syncUniforms: function ()
|
|
{
|
|
var gl = this.gl;
|
|
|
|
var uniforms = this.uniforms;
|
|
var uniform;
|
|
var length;
|
|
var glFunc;
|
|
var location;
|
|
var value;
|
|
var textureCount = 0;
|
|
|
|
for (var key in uniforms)
|
|
{
|
|
uniform = uniforms[key];
|
|
|
|
glFunc = uniform.glFunc;
|
|
length = uniform.glValueLength;
|
|
location = uniform.uniformLocation;
|
|
value = uniform.value;
|
|
|
|
if (value === null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (length === 1)
|
|
{
|
|
if (uniform.glMatrix)
|
|
{
|
|
glFunc.call(gl, location, uniform.transpose, value);
|
|
}
|
|
else
|
|
{
|
|
glFunc.call(gl, location, value);
|
|
}
|
|
}
|
|
else if (length === 2)
|
|
{
|
|
glFunc.call(gl, location, value.x, value.y);
|
|
}
|
|
else if (length === 3)
|
|
{
|
|
glFunc.call(gl, location, value.x, value.y, value.z);
|
|
}
|
|
else if (length === 4)
|
|
{
|
|
glFunc.call(gl, location, value.x, value.y, value.z, value.w);
|
|
}
|
|
else if (uniform.type === 'sampler2D')
|
|
{
|
|
gl.activeTexture(gl.TEXTURE0 + textureCount);
|
|
|
|
gl.bindTexture(gl.TEXTURE_2D, value);
|
|
|
|
gl.uniform1i(location, textureCount);
|
|
|
|
textureCount++;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Called automatically during render.
|
|
*
|
|
* This method performs matrix ITRS and then stores the resulting value in the `uViewMatrix` uniform.
|
|
* It then sets up the vertex buffer and shader, updates and syncs the uniforms ready
|
|
* for flush to be called.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#load
|
|
* @since 3.17.0
|
|
*
|
|
* @param {Phaser.GameObjects.Components.TransformMatrix} [matrix2D] - The transform matrix to use during rendering.
|
|
*/
|
|
load: function (matrix2D)
|
|
{
|
|
// ITRS
|
|
|
|
var gl = this.gl;
|
|
var width = this.width;
|
|
var height = this.height;
|
|
var renderer = this.renderer;
|
|
var program = this.program;
|
|
var vm = this.viewMatrix;
|
|
|
|
if (!this.renderToTexture)
|
|
{
|
|
var x = -this._displayOriginX;
|
|
var y = -this._displayOriginY;
|
|
|
|
vm[0] = matrix2D[0];
|
|
vm[1] = matrix2D[1];
|
|
vm[4] = matrix2D[2];
|
|
vm[5] = matrix2D[3];
|
|
vm[8] = matrix2D[4];
|
|
vm[9] = matrix2D[5];
|
|
vm[12] = vm[0] * x + vm[4] * y;
|
|
vm[13] = vm[1] * x + vm[5] * y;
|
|
}
|
|
|
|
// Update vertex shader uniforms
|
|
|
|
gl.useProgram(program);
|
|
|
|
gl.uniformMatrix4fv(gl.getUniformLocation(program, 'uViewMatrix'), false, vm);
|
|
gl.uniform2f(gl.getUniformLocation(program, 'uResolution'), this.width, this.height);
|
|
|
|
// Update fragment shader uniforms
|
|
|
|
var uniforms = this.uniforms;
|
|
var res = uniforms.resolution;
|
|
|
|
res.value.x = width;
|
|
res.value.y = height;
|
|
|
|
uniforms.time.value = renderer.game.loop.getDuration();
|
|
|
|
var pointer = this.pointer;
|
|
|
|
if (pointer)
|
|
{
|
|
var mouse = uniforms.mouse;
|
|
|
|
var px = pointer.x / width;
|
|
var py = 1 - pointer.y / height;
|
|
|
|
mouse.value.x = px.toFixed(2);
|
|
mouse.value.y = py.toFixed(2);
|
|
}
|
|
|
|
this.syncUniforms();
|
|
},
|
|
|
|
/**
|
|
* Called automatically during render.
|
|
*
|
|
* Sets the active shader, loads the vertex buffer and then draws.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#flush
|
|
* @since 3.17.0
|
|
*/
|
|
flush: function ()
|
|
{
|
|
// Bind
|
|
|
|
var width = this.width;
|
|
var height = this.height;
|
|
var program = this.program;
|
|
|
|
var gl = this.gl;
|
|
var vertexBuffer = this.vertexBuffer;
|
|
var renderer = this.renderer;
|
|
var vertexSize = Float32Array.BYTES_PER_ELEMENT * 2;
|
|
|
|
if (this.renderToTexture)
|
|
{
|
|
renderer.setFramebuffer(this.framebuffer);
|
|
|
|
gl.clearColor(0, 0, 0, 0);
|
|
|
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
}
|
|
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
|
|
|
|
var location = gl.getAttribLocation(program, 'inPosition');
|
|
|
|
if (location !== -1)
|
|
{
|
|
gl.enableVertexAttribArray(location);
|
|
|
|
gl.vertexAttribPointer(location, 2, gl.FLOAT, false, vertexSize, 0);
|
|
}
|
|
|
|
// Draw
|
|
|
|
var vf = this.vertexViewF32;
|
|
|
|
vf[3] = height;
|
|
vf[4] = width;
|
|
vf[5] = height;
|
|
vf[8] = width;
|
|
vf[9] = height;
|
|
vf[10] = width;
|
|
|
|
// Flush
|
|
|
|
var vertexCount = 6;
|
|
|
|
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.bytes.subarray(0, vertexCount * vertexSize));
|
|
|
|
gl.drawArrays(gl.TRIANGLES, 0, vertexCount);
|
|
|
|
if (this.renderToTexture)
|
|
{
|
|
renderer.setFramebuffer(null, false);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A NOOP method so you can pass a Shader to a Container.
|
|
* Calling this method will do nothing. It is intentionally empty.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#setAlpha
|
|
* @private
|
|
* @since 3.17.0
|
|
*/
|
|
setAlpha: function ()
|
|
{
|
|
},
|
|
|
|
/**
|
|
* A NOOP method so you can pass a Shader to a Container.
|
|
* Calling this method will do nothing. It is intentionally empty.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#setBlendMode
|
|
* @private
|
|
* @since 3.17.0
|
|
*/
|
|
setBlendMode: function ()
|
|
{
|
|
},
|
|
|
|
/**
|
|
* Internal destroy handler, called as part of the destroy process.
|
|
*
|
|
* @method Phaser.GameObjects.Shader#preDestroy
|
|
* @protected
|
|
* @since 3.17.0
|
|
*/
|
|
preDestroy: function ()
|
|
{
|
|
var gl = this.gl;
|
|
|
|
gl.deleteProgram(this.program);
|
|
gl.deleteBuffer(this.vertexBuffer);
|
|
|
|
if (this.renderToTexture)
|
|
{
|
|
this.renderer.deleteFramebuffer(this.framebuffer);
|
|
|
|
this.texture.destroy();
|
|
|
|
this.framebuffer = null;
|
|
this.glTexture = null;
|
|
this.texture = null;
|
|
}
|
|
}
|
|
|
|
});
|
|
|
|
module.exports = Shader;
|