phaser/src/gameobjects/shader/Shader.js

712 lines
21 KiB
JavaScript
Raw Normal View History

2019-04-25 02:15:51 +00:00
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2019 Photon Storm Ltd.
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
var Class = require('../../utils/Class');
var Components = require('../components');
var GameObject = require('../GameObject');
var ShaderRender = require('./ShaderRender');
2019-04-25 10:30:23 +00:00
var TransformMatrix = require('../components/TransformMatrix');
2019-04-25 02:15:51 +00:00
/**
* @classdesc
* A Shader Game Object.
*
* @class Shader
* @extends Phaser.GameObjects.GameObject
* @memberof Phaser.GameObjects
* @constructor
* @webglOnly
* @since 3.17.0
*
* @extends Phaser.GameObjects.Components.ComputedSize
2019-04-25 02:15:51 +00:00
* @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
2019-04-25 02:15:51 +00:00
* @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.
2019-04-25 14:07:46 +00:00
* @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} [fragSource] - The source code of the fragment shader.
* @param {string} [vertSource] - The source code of the vertex shader.
2019-04-25 02:15:51 +00:00
*/
var Shader = new Class({
Extends: GameObject,
Mixins: [
Components.ComputedSize,
2019-04-25 02:15:51 +00:00
Components.Depth,
Components.GetBounds,
Components.Mask,
Components.Origin,
Components.ScrollFactor,
2019-04-25 02:15:51 +00:00
Components.Transform,
Components.Visible,
ShaderRender
],
initialize:
function Shader (scene, x, y, width, height, fragSource, vertSource, uniforms)
2019-04-25 02:15:51 +00:00
{
2019-04-25 14:07:46 +00:00
if (x === undefined) { x = 0; }
if (y === undefined) { y = 0; }
if (width === undefined) { width = 128; }
if (height === undefined) { height = 128; }
if (fragSource === undefined)
{
fragSource = [
'precision mediump float;',
'uniform vec2 resolution;',
'varying vec2 fragCoord;',
'void main () {',
' vec2 uv = fragCoord / resolution.xy;',
' gl_FragColor = vec4(uv.xyx, 1.0);',
'}'
].join('\n');
}
if (vertSource === undefined)
{
vertSource = [
'precision mediump float;',
'uniform mat4 uProjectionMatrix;',
'uniform mat4 uViewMatrix;',
'attribute vec2 inPosition;',
'varying vec2 fragCoord;',
'void main () {',
'gl_Position = uProjectionMatrix * uViewMatrix * vec4(inPosition, 1.0, 1.0);',
'fragCoord = inPosition;',
'}'
].join('\n');
}
2019-04-25 02:15:51 +00:00
GameObject.call(this, scene, 'Shader');
2019-04-30 10:08:34 +00:00
/**
* This Game Object cannot have a blend mode, so skip all checks.
*
* @name Phaser.GameObjects.Shader#blendMode
* @type {integer}
* @private
* @since 3.17.0
*/
this.blendMode = -1;
2019-04-30 10:08:34 +00:00
/**
* The source code, as a string, of the vertex shader being used.
*
* @name Phaser.GameObjects.Shader#vertSource
* @type {string}
* @since 3.17.0
*/
2019-04-25 14:07:46 +00:00
this.vertSource = vertSource;
2019-04-30 10:08:34 +00:00
/**
* The source code, as a string, of the fragment shader being used.
*
* @name Phaser.GameObjects.Shader#fragSource
* @type {string}
* @since 3.17.0
*/
2019-04-25 14:07:46 +00:00
this.fragSource = fragSource;
var renderer = scene.sys.renderer;
2019-04-30 10:08:34 +00:00
/**
* 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;
2019-04-25 02:15:51 +00:00
2019-04-25 10:30:23 +00:00
/**
2019-04-30 10:08:34 +00:00
* The WebGL context belonging to the renderer.
2019-04-25 10:30:23 +00:00
*
2019-04-30 10:08:34 +00:00
* @name Phaser.GameObjects.Shader#gl
2019-04-25 10:30:23 +00:00
* @type {WebGLRenderingContext}
* @since 3.17.0
*/
2019-04-30 10:08:34 +00:00
this.gl = renderer.gl;
2019-04-25 10:30:23 +00:00
/**
2019-04-30 10:08:34 +00:00
* Raw byte buffer of vertices this Shader uses.
2019-04-25 10:30:23 +00:00
*
2019-04-30 10:08:34 +00:00
* @name Phaser.GameObjects.Shader#vertexData
2019-04-25 10:30:23 +00:00
* @type {ArrayBuffer}
2019-04-30 10:08:34 +00:00
* @since 3.17.0
2019-04-25 10:30:23 +00:00
*/
this.vertexData = new ArrayBuffer(6 * (Float32Array.BYTES_PER_ELEMENT * 2));
2019-04-25 10:30:23 +00:00
/**
2019-04-30 10:08:34 +00:00
* The WebGL vertex buffer object this shader uses.
2019-04-25 10:30:23 +00:00
*
2019-04-30 10:08:34 +00:00
* @name Phaser.GameObjects.Shader#vertexBuffer
2019-04-25 10:30:23 +00:00
* @type {WebGLBuffer}
2019-04-30 10:08:34 +00:00
* @since 3.17.0
2019-04-25 10:30:23 +00:00
*/
this.vertexBuffer = renderer.createVertexBuffer(this.vertexData.byteLength, this.gl.STREAM_DRAW);
2019-04-25 10:30:23 +00:00
/**
2019-04-30 10:08:34 +00:00
* The WebGL shader program this shader uses.
2019-04-25 10:30:23 +00:00
*
2019-04-30 10:08:34 +00:00
* @name Phaser.GameObjects.Shader#program
2019-04-25 10:30:23 +00:00
* @type {WebGLProgram}
2019-04-30 10:08:34 +00:00
* @since 3.17.0
2019-04-25 10:30:23 +00:00
*/
this.program = null;
2019-04-25 10:30:23 +00:00
/**
2019-04-30 10:08:34 +00:00
* Uint8 view to the vertex raw buffer. Used for uploading vertex buffer resources to the GPU.
2019-04-25 10:30:23 +00:00
*
2019-04-30 10:08:34 +00:00
* @name Phaser.GameObjects.Shader#bytes
2019-04-25 10:30:23 +00:00
* @type {Uint8Array}
2019-04-30 10:08:34 +00:00
* @since 3.17.0
2019-04-25 10:30:23 +00:00
*/
this.bytes = new Uint8Array(this.vertexData);
/**
2019-04-30 10:08:34 +00:00
* Float32 view of the array buffer containing the shaders vertices.
2019-04-25 10:30:23 +00:00
*
2019-04-30 10:08:34 +00:00
* @name Phaser.GameObjects.Shader#vertexViewF32
2019-04-25 10:30:23 +00:00
* @type {Float32Array}
* @since 3.17.0
*/
this.vertexViewF32 = new Float32Array(this.vertexData);
/**
* A temporary Transform Matrix, re-used internally during batching.
*
2019-04-30 10:08:34 +00:00
* @name Phaser.GameObjects.Shader#_tempMatrix1
2019-04-25 10:30:23 +00:00
* @private
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.17.0
*/
this._tempMatrix1 = new TransformMatrix();
/**
* A temporary Transform Matrix, re-used internally during batching.
*
2019-04-30 10:08:34 +00:00
* @name Phaser.GameObjects.Shader#_tempMatrix2
2019-04-25 10:30:23 +00:00
* @private
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.17.0
*/
this._tempMatrix2 = new TransformMatrix();
/**
* A temporary Transform Matrix, re-used internally during batching.
*
2019-04-30 10:08:34 +00:00
* @name Phaser.GameObjects.Shader#_tempMatrix3
2019-04-25 10:30:23 +00:00
* @private
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.17.0
*/
this._tempMatrix3 = new TransformMatrix();
/**
2019-04-30 10:08:34 +00:00
* The view matrix the shader uses during rendering.
*
2019-04-30 10:08:34 +00:00
* @name Phaser.GameObjects.Shader#viewMatrix
* @type {Float32Array}
* @readonly
2019-04-30 10:08:34 +00:00
* @since 3.17.0
*/
this.viewMatrix = new Float32Array([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]);
/**
2019-04-30 10:08:34 +00:00
* The projection matrix the shader uses during rendering.
*
2019-04-30 10:08:34 +00:00
* @name Phaser.GameObjects.Shader#projectionMatrix
* @type {Float32Array}
* @readonly
2019-04-30 10:08:34 +00:00
* @since 3.17.0
*/
this.projectionMatrix = new Float32Array([ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ]);
var d = new Date();
/**
2019-04-30 10:08:34 +00:00
* 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}
* @readonly
* @since 3.17.0
*/
this.uniforms = {
resolution: { type: '2f', value: { x: x, y: y }},
time: { type: '1f', value: 0 },
mouse: { type: '2f', value: { x: 0.0, y: 0.0 } },
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 } }
};
// Copy over / replace any passed in the constructor
if (uniforms)
{
for (var key in uniforms)
{
this.uniforms[key] = uniforms[key];
}
}
2019-04-30 10:08:34 +00:00
/**
* 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
*/
2019-04-25 23:56:53 +00:00
this.pointer = null;
2019-04-30 10:08:34 +00:00
/**
* The cached width of the renderer.
*
* @name Phaser.GameObjects.Shader#_rendererWidth
* @type {number}
* @private
* @since 3.17.0
*/
this._rendererWidth = renderer.width;
2019-04-30 10:08:34 +00:00
/**
* The cached height of the renderer.
*
* @name Phaser.GameObjects.Shader#_rendererHeight
* @type {number}
* @private
* @since 3.17.0
*/
this._rendererHeight = renderer.height;
2019-04-25 10:30:23 +00:00
var gl = this.gl;
2019-04-30 10:08:34 +00:00
/**
* Internal gl function mapping for uniform look-up.
* https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/uniform
*
* @name Phaser.GameObjects.Shader#_glFuncMap
* @type {any}
* @private
* @since 3.17.0
*/
this._glFuncMap = {
mat2: { func: gl.uniformMatrix2fv, length: 1, matrix: true },
mat3: { func: gl.uniformMatrix3fv, length: 1, matrix: true },
mat4: { func: gl.uniformMatrix4fv, length: 1, matrix: true },
'1f': { func: gl.uniform1f, length: 1 },
'1fv': { func: gl.uniform1fv, length: 1 },
'1i': { func: gl.uniform1i, length: 1 },
'1iv': { func: gl.uniform1iv, length: 1 },
'2f': { func: gl.uniform2f, length: 2 },
'2fv': { func: gl.uniform2fv, length: 1 },
'2i': { func: gl.uniform2i, length: 2 },
'2iv': { func: gl.uniform2iv, length: 1 },
'3f': { func: gl.uniform3f, length: 3 },
'3fv': { func: gl.uniform3fv, length: 1 },
'3i': { func: gl.uniform3i, length: 3 },
'3iv': { func: gl.uniform3iv, length: 1 },
'4f': { func: gl.uniform4f, length: 4 },
'4fv': { func: gl.uniform4fv, length: 1 },
'4i': { func: gl.uniform4i, length: 4 },
'4iv': { func: gl.uniform4iv, length: 1 }
};
2019-04-30 10:08:34 +00:00
this.setPosition(x, y);
this.setSize(width, height);
this.setOrigin(0.5, 0.5);
2019-04-25 14:07:46 +00:00
this.setShader(fragSource, vertSource);
this.projOrtho(0, renderer.width, renderer.height, 0);
2019-04-25 10:30:23 +00:00
},
2019-04-30 10:08:34 +00:00
/**
* 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} fragSource - The fragment shader source code.
* @param {string} [vertSource] - The vertex shader source code. If not given it will use the default vertex shader.
*
* @return {this} This Shader instance.
*/
2019-04-25 14:07:46 +00:00
setShader: function (fragSource, vertSource)
2019-04-25 10:30:23 +00:00
{
2019-04-25 14:07:46 +00:00
if (vertSource === undefined) { vertSource = this.vertSource; }
var gl = this.gl;
var renderer = this.renderer;
2019-04-25 10:30:23 +00:00
if (this.program)
{
gl.deleteProgram(this.program);
}
var program = renderer.createProgram(vertSource, fragSource);
renderer.setMatrix4(program, 'uViewMatrix', false, this.viewMatrix);
renderer.setMatrix4(program, 'uProjectionMatrix', false, this.projectionMatrix);
this.program = program;
2019-04-25 14:07:46 +00:00
this.fragSource = fragSource;
this.vertSource = vertSource;
2019-04-25 16:13:31 +00:00
this.initUniforms();
2019-04-25 14:07:46 +00:00
return this;
2019-04-25 10:30:23 +00:00
},
2019-04-30 10:08:34 +00:00
/**
* 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.
*/
2019-04-25 23:56:53 +00:00
setPointer: function (pointer)
{
this.pointer = pointer;
return this;
},
2019-04-25 10:30:23 +00:00
/**
2019-04-30 10:08:34 +00:00
* 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.
*
2019-04-30 10:08:34 +00:00
* @method Phaser.GameObjects.Shader#projOrtho
* @since 3.17.0
2019-04-25 10:30:23 +00:00
*
* @param {number} left - The left value.
* @param {number} right - The right value.
* @param {number} bottom - The bottom value.
* @param {number} top - The top value.
2019-04-25 10:30:23 +00:00
*/
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;
this.renderer.setMatrix4(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
2019-04-30 10:08:34 +00:00
/**
* 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._glFuncMap;
var program = this.program;
for (var key in this.uniforms)
{
var uniform = this.uniforms[key];
var type = uniform.type;
var data = map[type];
if (type === 'sampler2D')
{
// this.initSampler2D(uniform);
}
else
{
uniform.glMatrix = data.matrix;
uniform.glValueLength = data.length;
uniform.glFunc = data.func;
2019-04-25 16:13:31 +00:00
uniform.uniformLocation = gl.getUniformLocation(program, key);
}
}
},
2019-04-30 10:08:34 +00:00
/**
* 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;
2019-04-30 10:08:34 +00:00
var uniforms;
var uniform;
var length;
var glFunc;
var location;
var value;
// var textureCount = 1;
2019-04-30 10:08:34 +00:00
for (var key in uniforms)
{
2019-04-30 10:08:34 +00:00
uniform = uniforms[key];
glFunc = uniform.glFunc;
length = uniform.glValueLength;
location = uniform.uniformLocation;
value = uniform.value;
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')
{
if (uniform._init)
{
// gl.activeTexture(gl['TEXTURE' + this.textureCount]);
// gl.bindTexture(gl.TEXTURE_2D, value.baseTexture._glTextures[gl.id]);
// gl.uniform1i(location, textureCount);
// textureCount++;
}
else
{
// this.initSampler2D(uniform);
}
}
}
},
2019-04-30 10:08:34 +00:00
/**
* 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 and finally draws it
* to a single quad.
*
* @method Phaser.GameObjects.Shader#load
* @private
* @since 3.17.0
*/
load: function (matrix2D)
2019-04-25 10:30:23 +00:00
{
// ITRS
var program = this.program;
var x = -this._displayOriginX;
2019-04-25 14:08:32 +00:00
var y = -this._displayOriginY;
2019-04-30 10:08:34 +00:00
var width = this.width;
var height = this.height;
var vm = this.viewMatrix;
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;
this.renderer.setMatrix4(program, 'uViewMatrix', false, this.viewMatrix);
// Bind
2019-04-25 10:30:23 +00:00
var gl = this.gl;
var vertexBuffer = this.vertexBuffer;
var renderer = this.renderer;
var vertexSize = Float32Array.BYTES_PER_ELEMENT * 2;
2019-04-25 10:30:23 +00:00
renderer.setProgram(program);
renderer.setVertexBuffer(vertexBuffer);
var location = gl.getAttribLocation(program, 'inPosition');
if (location !== -1)
2019-04-25 10:30:23 +00:00
{
gl.enableVertexAttribArray(location);
2019-04-25 10:30:23 +00:00
gl.vertexAttribPointer(location, 2, gl.FLOAT, false, vertexSize, 0);
2019-04-25 10:30:23 +00:00
}
2019-04-30 10:08:34 +00:00
// Update common uniforms
var uniforms = this.uniforms;
var res = uniforms.resolution;
res.value.x = width;
res.value.y = height;
uniforms.time.value = renderer.game.loop.getDuration();
2019-04-30 10:08:34 +00:00
var pointer = this.pointer;
2019-04-25 10:30:23 +00:00
2019-04-30 10:08:34 +00:00
if (pointer)
2019-04-25 23:56:53 +00:00
{
2019-04-30 10:08:34 +00:00
var mouse = uniforms.mouse;
var px = pointer.x / width;
var py = 1 - pointer.y / height;
2019-04-25 23:56:53 +00:00
2019-04-30 10:08:34 +00:00
mouse.value.x = px.toFixed(2);
mouse.value.y = py.toFixed(2);
2019-04-25 23:56:53 +00:00
}
this.syncUniforms();
2019-04-25 10:30:23 +00:00
// Draw
var vf = this.vertexViewF32;
vf[3] = height;
vf[4] = width;
vf[5] = height;
vf[8] = width;
vf[9] = height;
vf[10] = width;
// Flush
2019-04-25 10:30:23 +00:00
var vertexCount = 6;
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.bytes.subarray(0, vertexCount * vertexSize));
gl.drawArrays(gl.TRIANGLES, 0, vertexCount);
2019-04-25 10:30:23 +00:00
},
2019-04-29 16:14:05 +00:00
/**
* 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 ()
{
},
/**
2019-04-30 10:08:34 +00:00
* A NOOP method so you can pass a Shader to a Container.
2019-04-29 16:14:05 +00:00
* Calling this method will do nothing. It is intentionally empty.
*
* @method Phaser.GameObjects.Shader#setBlendMode
* @private
* @since 3.17.0
*/
setBlendMode: function ()
{
},
2019-04-25 10:30:23 +00:00
/**
* Removes all object references in this WebGL Pipeline and removes its program from the WebGL context.
*
2019-04-30 10:08:34 +00:00
* @method Phaser.GameObjects.Shader#destroy
* @since 3.17.0
2019-04-25 10:30:23 +00:00
*/
destroy: function ()
{
var gl = this.gl;
gl.deleteProgram(this.program);
gl.deleteBuffer(this.vertexBuffer);
2019-04-25 02:15:51 +00:00
}
});
module.exports = Shader;