diff --git a/scripts/bundle-shaders.js b/scripts/bundle-shaders.js index cc5742968..3c8040f87 100644 --- a/scripts/bundle-shaders.js +++ b/scripts/bundle-shaders.js @@ -69,7 +69,7 @@ files.forEach(function (file, c) { { inc = inc.concat('Frag'); } - else + else if (file.substr(-4) === 'vert') { inc = inc.concat('Vert'); } diff --git a/src/gameobjects/components/Lighting.js b/src/gameobjects/components/Lighting.js index 0db9bc8aa..9b2179c54 100644 --- a/src/gameobjects/components/Lighting.js +++ b/src/gameobjects/components/Lighting.js @@ -16,10 +16,9 @@ var Lighting = { /** - * Is this GameObject using lighting? + * Should this GameObject use lighting? * - * This flag is read-only and cannot be changed directly. - * Use the `setLighting` method to toggle lighting effects on or off. + * This flag is used to set up WebGL shaders for rendering. * * @name Phaser.GameObjects.Components.Lighting#lighting * @type {boolean} @@ -30,50 +29,75 @@ var Lighting = { */ lighting: false, + /** + * Should this GameObject use self-shadowing? + * Self-shadowing is only enabled if `lighting` is enabled. + * + * The game config option `render.selfShadow` is used if this is not a boolean. + * + * This flag is used to set up WebGL shaders for rendering. + * + * @name Phaser.GameObjects.Components.Lighting#selfShadow + * @type {{ enabled: boolean, penumbra: number, diffuseFlatThreshold: number }} + * @webglOnly + * @since 3.90.0 + */ + selfShadow: { + enabled: null, + penumbra: 0.5, + diffuseFlatThreshold: 1 / 3 + }, + /** * Sets whether this GameObject should use lighting. * - * This will assign the relevant RenderNodes to the GameObject. - * - * This method will override any custom RenderNode in the `Submitter` role, - * either replacing it with the RenderNode in the `SubmitterLight` role, - * or removing it if `enable` is `false`. - * The `SubmitterLight` role is read from `customRenderNodes` first, - * then from `defaultRenderNodes`. - * * @method Phaser.GameObjects.Components.Lighting#setLighting * @webglOnly * @since 3.90.0 * @param {boolean} enable - `true` to use lighting, or `false` to disable it. - * @return {this} This Game Object instance. + * @return {this} This GameObject instance. */ setLighting: function (enable) { - if (!this.defaultRenderNodes) - { - // Cannot enable lighting without the render nodes component. - return this; - } + this.lighting = enable; - if (enable) - { - var submitterLight = - this.customRenderNodes.SubmitterLight || - this.defaultRenderNodes.SubmitterLight; + return this; + }, - if (!submitterLight) + /** + * Sets whether this GameObject should use self-shadowing. + * Self-shadowing is only enabled if `lighting` is also enabled. + * + * @method Phaser.GameObjects.Components.Lighting#setSelfShadow + * @webglOnly + * @since 3.90.0 + * @param {?boolean} [enabled] - `true` to use self-shadowing, `false` to disable it, `null` to use the game default from `config.render.selfShadow`, or `undefined` to keep the setting. + * @param {number} [penumbra] - The penumbra value for the shadow. Lower is sharper but more jagged. Default is 0.5. + * @param {number} [diffuseFlatThreshold] - The texture brightness threshold at which the diffuse lighting will be considered flat. Range is 0-1. Default is 1/3. + * @return {this} This GameObject instance. + */ + setSelfShadow: function (enabled, penumbra, diffuseFlatThreshold) + { + if (enabled !== undefined) + { + if (enabled === null) { - // Cannot enable lighting without the SubmitterLight role. - return this; + this.selfShadow.enabled = this.scene.game.config.selfShadow; + } + else + { + this.selfShadow.enabled = enabled; } - - this.lighting = true; - this.setRenderNodeRole('Submitter', submitterLight); } - else + + if (penumbra !== undefined) { - this.lighting = false; - this.setRenderNodeRole('Submitter', null); + this.selfShadow.penumbra = penumbra; + } + + if (diffuseFlatThreshold !== undefined) + { + this.selfShadow.diffuseFlatThreshold = diffuseFlatThreshold; } return this; diff --git a/src/gameobjects/graphics/GraphicsWebGLRenderer.js b/src/gameobjects/graphics/GraphicsWebGLRenderer.js index b4059003e..98f6c42cb 100644 --- a/src/gameobjects/graphics/GraphicsWebGLRenderer.js +++ b/src/gameobjects/graphics/GraphicsWebGLRenderer.js @@ -71,6 +71,7 @@ var GraphicsWebGLRenderer = function (renderer, src, drawingContext, parentMatri var customRenderNodes = src.customRenderNodes; var defaultRenderNodes = src.defaultRenderNodes; var submitterNode = customRenderNodes.Submitter || defaultRenderNodes.Submitter; + var lighting = src.lighting; var currentContext = drawingContext; @@ -146,7 +147,8 @@ var GraphicsWebGLRenderer = function (renderer, src, drawingContext, parentMatri fillTint.TL, fillTint.TR, fillTint.BL, - pathDetailThreshold + pathDetailThreshold, + lighting ); } break; @@ -169,7 +171,8 @@ var GraphicsWebGLRenderer = function (renderer, src, drawingContext, parentMatri strokeTint.TR, strokeTint.BL, strokeTint.BR, - pathDetailThreshold + pathDetailThreshold, + lighting ); } break; @@ -300,7 +303,8 @@ var GraphicsWebGLRenderer = function (renderer, src, drawingContext, parentMatri fillTint.TL, fillTint.TR, fillTint.BL, - fillTint.BR + fillTint.BR, + lighting ); break; @@ -322,7 +326,8 @@ var GraphicsWebGLRenderer = function (renderer, src, drawingContext, parentMatri commands[++cmdIndex], fillTint.TL, fillTint.TR, - fillTint.BL + fillTint.BL, + lighting ); break; @@ -358,7 +363,8 @@ var GraphicsWebGLRenderer = function (renderer, src, drawingContext, parentMatri strokeTint.TL, strokeTint.TR, strokeTint.BL, - strokeTint.BR + strokeTint.BR, + lighting ); break; } diff --git a/src/gameobjects/nineslice/NineSliceWebGLRenderer.js b/src/gameobjects/nineslice/NineSliceWebGLRenderer.js index 58346c475..183457fb1 100644 --- a/src/gameobjects/nineslice/NineSliceWebGLRenderer.js +++ b/src/gameobjects/nineslice/NineSliceWebGLRenderer.js @@ -7,6 +7,8 @@ var GetCalcMatrix = require('../GetCalcMatrix'); var Utils = require('../../renderer/webgl/Utils'); +var fixedRenderOptions = { multiTexturing: true }; + /** * Renders this Game Object with the WebGL Renderer to the given Camera. * The object will not render if any of its renderFlags are set or it is being actively filtered out by the Camera. @@ -75,7 +77,10 @@ var NineSliceWebGLRenderer = function (renderer, src, drawingContext, parentMatr tintEffect, // Tint colors in order TL, BL, TR, BR: - color, color, color, color + color, color, color, color, + + // Render options: + fixedRenderOptions ); } }; diff --git a/src/renderer/webgl/ProgramManager.js b/src/renderer/webgl/ProgramManager.js new file mode 100644 index 000000000..a3e47f90a --- /dev/null +++ b/src/renderer/webgl/ProgramManager.js @@ -0,0 +1,352 @@ +/** + * @author Benjamin D. Richards + * @copyright 2013-2024 Phaser Studio Inc. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +var Class = require('../../utils/Class'); +var DeepCopy = require('../../utils/object/DeepCopy'); + +/** + * @classdesc + * The ProgramManager is a utility class used to manage + * instantiated shader programs and a suite of associated data, + * such as a VAO. It maintains a shared pool of uniforms, + * so if a different shader program is used, the uniforms + * can be applied to the new program. + * + * @class ProgramManager + * @memberof Phaser.Renderer.WebGL + * @constructor + * @since 3.90.0 + * @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The current WebGLRenderer instance. + * @param {Phaser.Renderer.WebGL.Wrappers.WebGLBufferWrapper} [indexBuffer] - The index buffer to use in the program, if any. + * @param {Phaser.Renderer.WebGL.Wrappers.WebGLAttributeBufferLayout[]} attributeBufferLayouts - The attribute buffer layouts to use in the program. + */ +var ProgramManager = new Class({ + initialize: function ProgramManager (renderer, indexBuffer, attributeBufferLayouts) + { + /** + * The current WebGLRenderer instance. + * + * @name Phaser.Renderer.WebGL.ProgramManager#renderer + * @type {Phaser.Renderer.WebGL.WebGLRenderer} + * @since 3.90.0 + */ + this.renderer = renderer; + + /** + * The index buffer to use in the program, if any. + * This is used to create a VAO. + * + * @name Phaser.Renderer.WebGL.ProgramManager#indexBuffer + * @type {?Phaser.Renderer.WebGL.Wrappers.WebGLBufferWrapper} + * @since 3.90.0 + */ + this.indexBuffer = indexBuffer; + + /** + * The attribute buffer layouts to use in the program. + * These are used to create a VAO. + * + * @name Phaser.Renderer.WebGL.ProgramManager#attributeBufferLayouts + * @type {Phaser.Renderer.WebGL.Wrappers.WebGLAttributeBufferLayout[]} + * @since 3.90.0 + */ + this.attributeBufferLayouts = attributeBufferLayouts; + + /** + * The key of the currently active shader program. + * + * @name Phaser.Renderer.WebGL.ProgramManager#currentProgramKey + * @type {?string} + * @since 3.90.0 + */ + this.currentProgramKey = null; + + /** + * The configuration object currently being assembled. + * + * @name Phaser.Renderer.WebGL.ProgramManager#currentConfig + * @type {object} + * @since 3.90.0 + */ + this.currentConfig = { + base: { + vertexShader: '', + fragmentShader: '' + }, + additions: [], + features: [] + }; + + /** + * A map of shader programs and associated data suite, + * identified by a unique key. + * + * Each key corresponds to an object of the following shape: + * + * - `program` (WebGLProgramWrapper) - The compiled shader program. + * - `vao` (WebGLVAOWrapper) - The VAO associated with the program. + * - `config` (object) - The configuration object used to create the program. + * + * @name Phaser.Renderer.WebGL.ProgramManager#programs + * @type {object} + * @since 3.90.0 + */ + this.programs = {}; + + /** + * A map of uniform values, identified by the shader uniform names. + * This allows uniforms to be kept between shader programs. + * + * @name Phaser.Renderer.WebGL.ProgramManager#uniforms + * @type {object} + * @since 3.90.0 + */ + this.uniforms = {}; + }, + + /** + * Returns a program suite based on the current configuration. + * If the program does not exist, it is created. + * + * The suite contains the following properties: + * + * - `program` (WebGLProgramWrapper) - The compiled shader program. + * - `vao` (WebGLVAOWrapper) - The VAO associated with the program. + * - `config` (object) - The configuration object used to create the program. + * + * @method Phaser.Renderer.WebGL.ProgramManager#getCurrentProgramSuite + * @since 3.90.0 + */ + getCurrentProgramSuite: function () + { + var config = this.currentConfig; + var renderer = this.renderer; + var factory = renderer.shaderProgramFactory; + + var key = factory.getKey(config.base, config.additions, config.features); + + if (!this.programs[key]) + { + var program = factory.createShaderProgram(key, config.base, config.additions, config.features); + this.programs[key] = { + program: program, + vao: renderer.createVAO( + program, + this.indexBuffer, + this.attributeBufferLayouts + ), + config: DeepCopy(config) + }; + } + + return this.programs[key]; + }, + + /** + * Resets the current configuration object. + * + * @method Phaser.Renderer.WebGL.ProgramManager#resetCurrentConfig + * @since 3.90.0 + */ + resetCurrentConfig: function () + { + this.currentConfig.base.vertexShader = ''; + this.currentConfig.base.fragmentShader = ''; + this.currentConfig.additions.length = 0; + this.currentConfig.features.length = 0; + }, + + /** + * Set the value of a uniform, + * available for all shader programs in this manager. + * + * @method Phaser.Renderer.WebGL.ProgramManager#setUniform + * @since 3.90.0 + * @param {string} name - The name of the uniform. + * @param {any} value - The value of the uniform. + */ + setUniform: function (name, value) + { + this.uniforms[name] = value; + }, + + /** + * Delete a uniform value. While unused uniforms are not harmful, + * they do take time to process and can be a source of confusion. + * + * @method Phaser.Renderer.WebGL.ProgramManager#removeUniform + * @since 3.90.0 + * @param {string} name - The name of the uniform. + */ + removeUniform: function (name) + { + delete this.uniforms[name]; + }, + + /** + * Remove all uniforms. + * + * @method Phaser.Renderer.WebGL.ProgramManager#clearUniforms + * @since 3.90.0 + */ + clearUniforms: function () + { + this.uniforms.length = 0; + }, + + /** + * Set the stored uniforms on a shader program. + * + * @method Phaser.Renderer.WebGL.ProgramManager#applyUniforms + * @since 3.90.0 + */ + applyUniforms: function (program) + { + var uniforms = this.uniforms; + + for (var name in uniforms) + { + program.setUniform(name, uniforms[name]); + } + }, + + /** + * Set the base shader for the current configuration. + * + * @method Phaser.Renderer.WebGL.ProgramManager#setBaseShader + * @since 3.90.0 + * @param {string} name - The name of the shader program. + * @param {string} vertexShader - The vertex shader source code. + * @param {string} fragmentShader - The fragment shader source code. + */ + setBaseShader: function (name, vertexShader, fragmentShader) + { + var base = this.currentConfig.base; + base.name = name; + base.vertexShader = vertexShader; + base.fragmentShader = fragmentShader; + }, + + /** + * Add a shader addition to the current configuration. + * + * @method Phaser.Renderer.WebGL.ProgramManager#addAddition + * @since 3.90.0 + * @param {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} addition - The shader addition to add. + * @param {number} [index] - The index at which to insert the addition. If not specified, it will be added at the end. + */ + addAddition: function (addition, index) + { + if (index === undefined) + { + this.currentConfig.additions.push(addition); + } + else + { + this.currentConfig.additions.splice(index, 0, addition); + } + }, + + /** + * Returns the addition with the given name. + * + * @method Phaser.Renderer.WebGL.ProgramManager#getAddition + * @since 3.90.0 + * @param {string} name - The name to find. + * @returns {?Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The addition, or `null` if it was not found. + */ + getAddition: function (name) + { + var additions = this.currentConfig.additions; + for (var i = 0; i < additions.length; i++) + { + var addition = additions[i]; + if (addition.name === name) + { + return addition; + } + } + return null; + }, + + /** + * Returns a list of shader additions in the current config + * that have a specific tag. + * + * @method Phaser.Renderer.WebGL.ProgramManager#getAdditionsByTag + * @since 3.90.0 + * @param {string} tag - The tag to filter by. + */ + getAdditionsByTag: function (tag) + { + return this.currentConfig.additions.filter(function (addition) + { + if (!addition.tags) + { + return false; + } + return addition.tags.includes(tag); + }); + }, + + /** + * Remove a shader addition from the current configuration. + * + * @method Phaser.Renderer.WebGL.ProgramManager#removeAddition + * @since 3.90.0 + * @param {string} name - The name of the shader addition to remove. + */ + removeAddition: function (name) + { + this.currentConfig.additions = this.currentConfig.additions.filter(function (addition) + { + return addition.name !== name; + }); + }, + + /** + * Add a feature to the current configuration. + * + * @method Phaser.Renderer.WebGL.ProgramManager#addFeature + * @since 3.90.0 + * @param {string} feature - The feature to add. + */ + addFeature: function (feature) + { + if (this.currentConfig.features.indexOf(feature) === -1) + { + this.currentConfig.features.push(feature); + } + }, + + /** + * Remove a feature from the current configuration. + * + * @method Phaser.Renderer.WebGL.ProgramManager#removeFeature + * @since 3.90.0 + * @param {string} feature - The feature to remove. + */ + removeFeature: function (feature) + { + this.currentConfig.features = this.currentConfig.features.filter(function (f) + { + return f !== feature; + }); + }, + + /** + * Clear all features from the current configuration. + * + * @method Phaser.Renderer.WebGL.ProgramManager#clearFeatures + * @since 3.90.0 + */ + clearFeatures: function () + { + this.currentConfig.features.length = 0; + } +}); + +module.exports = ProgramManager; diff --git a/src/renderer/webgl/ShaderProgramFactory.js b/src/renderer/webgl/ShaderProgramFactory.js new file mode 100644 index 000000000..f768b31b2 --- /dev/null +++ b/src/renderer/webgl/ShaderProgramFactory.js @@ -0,0 +1,217 @@ +/** + * @author Benjamin D. Richards + * @copyright 2013-2024 Phaser Studio Inc. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +var Class = require('../../utils/Class'); + +/** + * @typedef {object} BaseShaderConfig + * @property {string} name - The name of the shader program, used as a key. + * @property {string} vertexShader - The vertex shader source code. + * @property {string} fragmentShader - The fragment shader source code. + */ + +/** + * @classdesc + * The ShaderProgramFactory is a utility class used to generate + * {@link Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper} objects. + * It facilitates generating variants of a shader program based on + * configuration settings. + * + * @class ShaderProgramFactory + * @memberof Phaser.Renderer.WebGL + * @constructor + * @since 3.90.0 + * @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The WebGLRenderer that owns this ShaderProgramFactory. + */ +var ShaderProgramFactory = new Class({ + initialize: function ShaderProgramFactory (renderer) + { + /** + * The WebGLRenderer that owns this ShaderProgramFactory. + * + * @name Phaser.Renderer.WebGL.ShaderProgramFactory#renderer + * @type {Phaser.Renderer.WebGL.WebGLRenderer} + * @since 3.90.0 + */ + this.renderer = renderer; + + /** + * A map of shader programs, identified by a unique key. + * + * The key of each shader program is made up of the following components: + * + * - The key of the base shader program. + * - The key of each shader addition, in addition order. + * - The key of each enabled shader feature, sorted alphabetically. + * + * @name Phaser.Renderer.WebGL.ShaderProgramFactory#programs + * @type {object} + * @since 3.90.0 + */ + this.programs = {}; + }, + + /** + * Checks if a shader program exists based on the given configuration settings. + * + * @method Phaser.Renderer.WebGL.ShaderProgramFactory#has + * @since 3.90.0 + * @param {string} key - The unique key of the shader program. + */ + has: function (key) + { + return this.programs[key] !== undefined; + }, + + /** + * Returns a shader program based on the given configuration settings. + * + * @method Phaser.Renderer.WebGL.ShaderProgramFactory#getShaderProgram + * @since 3.90.0 + * @param {BaseShaderConfig} base - The base shader configuration. + * @param {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig[]} [additions] - An array of shader addition configurations. + * @param {string[]} [features] - An array of enabled shader feature keys. + */ + getShaderProgram: function (base, additions, features) + { + var key = this.getKey(base, additions, features); + + var program = this.programs.key; + + if (!program) + { + program = this.createShaderProgram(key, base, additions, features); + } + + return program; + }, + + /** + * Returns a unique key for a shader program based on the given configuration settings. + * + * The key is made up of the following components: + * + * - The key of the base shader program. + * - The key of each shader addition, in addition order. + * - The key of each enabled shader feature, sorted alphabetically. + * + * @method Phaser.Renderer.WebGL.ShaderProgramFactory#getKey + * @since 3.90.0 + * @param {BaseShaderConfig} base - The base shader configuration. + * @param {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig[]} [additions] - An array of shader addition configurations. + * @param {string[]} [features] - An array of enabled shader feature keys. + */ + getKey: function (base, additions, features) + { + var key = base.name; + + if (additions && additions.length > 0) + { + key += '_'; + for (var i = 0; i < additions.length; i++) + { + var addition = additions[i]; + if (!addition.disable) + { + key += '_' + addition.name; + } + } + } + + if (features && features.length > 0) + { + key += '__'; + key += features.sort().join('_'); + } + + return key; + }, + + /** + * Creates a shader program based on the given configuration settings. + * + * @method Phaser.Renderer.WebGL.ShaderProgramFactory#createShaderProgram + * @since 3.90.0 + * @param {string} name - The unique key of the shader program. + * @param {BaseShaderConfig} base - The base shader configuration. + * @param {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig[]} [additions] - An array of shader addition configurations. + * @param {string[]} [features] - An array of enabled shader feature keys. + */ + createShaderProgram: function (name, base, additions, features) + { + var vertexSource = base.vertexShader; + var fragmentSource = base.fragmentShader; + + if (additions) + { + var key, value; + var templates = {}; + + for (var i = 0; i < additions.length; i++) + { + var addition = additions[i]; + + if (addition.disable) + { + continue; + } + + for (key in addition.additions) + { + value = addition.additions[key]; + + if (!templates[key]) + { + templates[key] = ''; + } + + templates[key] += value + '\n'; + } + } + + for (key in templates) + { + var template = '#pragma phaserTemplate(' + key + ')\n'; + value = templates[key]; + + vertexSource = vertexSource.replace(template, value); + fragmentSource = fragmentSource.replace(template, value); + } + } + + if (features) + { + var featureDefines = ''; + var reInvalid = /[^a-zA-Z0-9]/g; + + for (i = 0; i < features.length; i++) + { + var feature = features[i].toUpperCase().replace(reInvalid, '_'); + featureDefines += '#define FEATURE_' + feature + '\n'; + } + + vertexSource = vertexSource.replace('#pragma phaserTemplate(features)', featureDefines); + fragmentSource = fragmentSource.replace('#pragma phaserTemplate(features)', featureDefines); + } + + // Name the program after the key. + vertexSource = vertexSource.replace('#pragma phaserTemplate(shaderName)', '#define SHADER_NAME ' + name + '__VERTEX'); + fragmentSource = fragmentSource.replace('#pragma phaserTemplate(shaderName)', '#define SHADER_NAME ' + name + '__FRAGMENT'); + + // Remove any remaining template directives. + var rePragma = /\s*#pragma phaserTemplate\(.*/g; + vertexSource = vertexSource.replace(rePragma, ''); + fragmentSource = fragmentSource.replace(rePragma, ''); + + var program = this.renderer.createProgram(vertexSource, fragmentSource); + + this.programs[name] = program; + + return program; + } +}); + +module.exports = ShaderProgramFactory; diff --git a/src/renderer/webgl/Utils.js b/src/renderer/webgl/Utils.js index e6fc2d924..5e0574341 100644 --- a/src/renderer/webgl/Utils.js +++ b/src/renderer/webgl/Utils.js @@ -128,125 +128,6 @@ module.exports = { } }, - /** - * Checks the given Fragment Shader Source for `%count%` and `%forloop%` declarations and - * replaces those with GLSL code for setting `texture = texture2D(uMainSampler[i], outTexCoord)`. - * - * @function Phaser.Renderer.WebGL.Utils.parseFragmentShaderMaxTextures - * @since 3.50.0 - * - * @param {string} fragmentShaderSource - The Fragment Shader source code to operate on. - * @param {number} maxTextures - The number of maxTextures value. - * - * @return {string} The modified Fragment Shader source. - */ - parseFragmentShaderMaxTextures: function (fragmentShaderSource, maxTextures) - { - if (!fragmentShaderSource) - { - return ''; - } - - // Allow renaming the texCoord variable. - var texCoordName = 'outTexCoord'; - var texCoordRegex = /\s*%texCoordName=(.*)%\n/; - var texCoordMatch = fragmentShaderSource.match(texCoordRegex); - - if (texCoordMatch) - { - texCoordName = texCoordMatch[1]; - fragmentShaderSource = fragmentShaderSource.replace(texCoordRegex, ''); - } - - // Add the texture lookup code for each texture unit - var src = ''; - - for (var i = 0; i < maxTextures; i++) - { - if (i > 0) - { - src += '\n\telse '; - } - - if (i < maxTextures - 1) - { - src += 'if (outTexId < ' + i + '.5)'; - } - - src += '\n\t{'; - src += '\n\t\ttexture = texture2D(uMainSampler[' + i + '], ' + texCoordName + ');'; - src += '\n\t}'; - } - - // Add texture count. - fragmentShaderSource = fragmentShaderSource.replace(/%count%/gi, maxTextures.toString()); - - return fragmentShaderSource.replace(/%forloop%/gi, src); - }, - - /** - * Takes the given shader source and parses it, looking for blocks of code - * wrapped in `%% [feature|without] X %%` and `%% end X %%` directives, - * stripping the directives, and stripping or keeping the block - * based on whether X is in the feature set. - * - * @function Phaser.Renderer.WebGL.Utils.parseShaderFeatures - * @since 3.90.0 - * @param {string} shaderSource - The shader source to parse. - * @param {string[]} features - An array of features to check for. - * @return {string} The modified shader source. - */ - parseShaderFeatures: function (shaderSource, features) - { - var reCommand = new RegExp('%% (feature|without|end) (.+) %%'); - var lines = shaderSource.split('\n'); - var outShaderSource = ''; - - for (var i = 0; i < lines.length; i++) - { - var line = lines[i]; - - var commandExec = reCommand.exec(line); - - if (!commandExec) - { - outShaderSource += line + '\n'; - continue; - } - - var command = commandExec[1]; - var feature = commandExec[2]; - - if ( - (command === 'feature' && features.indexOf(feature) === -1) || - (command === 'without' && features.indexOf(feature) > -1) - ) - { - // Skip lines until this command block ends. - while (i < lines.length) - { - i++; - line = lines[i]; - - commandExec = reCommand.exec(line); - if (!commandExec) - { - continue; - } - command = commandExec[1]; - var nextFeature = commandExec[2]; - - if (command === 'end' && nextFeature === feature) - { - break; - } - } - } - } - - return outShaderSource; - }, - /** * Takes the Glow FX Shader source and parses out the __SIZE__ and __DIST__ * consts with the configuration values. @@ -277,6 +158,146 @@ module.exports = { shader = shader.replace(/__DIST__/gi, distance.toFixed(0) + '.0'); return shader; - } + }, + /** + * Update lighting uniforms for a given shader program manager. + * This is a standard procedure for most lighting shaders. + * + * @function Phaser.Renderer.WebGL.Utils.updateLightingUniforms + * @since 3.90.0 + * @webglOnly + * + * @param {boolean} enable - Whether to enable lighting. + * @param {Phaser.Renderer.WebGL.WebGLRenderer} renderer - The WebGLRenderer instance. + * @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The DrawingContext instance. + * @param {Phaser.Renderer.WebGL.ShaderProgramManager} programManager - The ShaderProgramManager instance. + * @param {Phaser.Math.Vector2} vec - A Vector2 instance. + * @param {boolean} [selfShadow] - Whether to enable self-shadowing. + * @param {number} [selfShadowPenumbra] - The penumbra value for self-shadowing. + * @param {number} [selfShadowThreshold] - The threshold value for self-shadowing. + */ + updateLightingUniforms: function ( + enable, + renderer, + drawingContext, + programManager, + vec, + selfShadow, + selfShadowPenumbra, + selfShadowThreshold + ) + { + var camera = drawingContext.camera; + var scene = camera.scene; + var lightManager = scene.sys.lights; + var lights = lightManager.getLights(camera); + var lightsCount = lights.length; + var ambientColor = lightManager.ambientColor; + var height = renderer.height; + + if (enable) + { + programManager.setUniform( + 'uNormSampler', + 1 + ); + + programManager.setUniform( + 'uCamera', + [ + camera.x, + camera.y, + camera.rotation, + camera.zoom + ] + ); + programManager.setUniform( + 'uAmbientLightColor', + [ + ambientColor.r, + ambientColor.g, + ambientColor.b + ] + ); + programManager.setUniform( + 'uLightCount', + lightsCount + ); + + for (var i = 0; i < lightsCount; i++) + { + var light = lights[i].light; + var color = light.color; + + var lightName = 'uLights[' + i + '].'; + + camera.matrix.transformPoint( + light.x - (camera.scrollX * light.scrollFactorX * camera.zoom), + light.y - (camera.scrollY * light.scrollFactorY * camera.zoom), + vec + ); + + programManager.setUniform( + lightName + 'position', + [ + vec.x, + height - (vec.y), + light.z * camera.zoom + ] + ); + programManager.setUniform( + lightName + 'color', + [ + color.r, + color.g, + color.b + ] + ); + programManager.setUniform( + lightName + 'intensity', + light.intensity + ); + programManager.setUniform( + lightName + 'radius', + light.radius + ); + } + + if (selfShadow) + { + // Self-shadowing uniforms. + programManager.setUniform( + 'uDiffuseFlatThreshold', + selfShadowThreshold * 3 + ); + + programManager.setUniform( + 'uPenumbra', + selfShadowPenumbra + ); + } + } + else + { + // Clear lighting uniforms. + programManager.removeUniform('uNormSampler'); + programManager.removeUniform('uCamera'); + programManager.removeUniform('uAmbientLightColor'); + programManager.removeUniform('uLightCount'); + + programManager.removeUniform('uPenumbra'); + programManager.removeUniform('uDiffuseFlatThreshold'); + + for (i = 0; i < lightsCount; i++) + { + lightName = 'uLights[' + i + '].'; + + programManager.removeUniform(lightName + 'position'); + programManager.removeUniform(lightName + 'color'); + programManager.removeUniform(lightName + 'intensity'); + programManager.removeUniform(lightName + 'radius'); + } + } + } }; diff --git a/src/renderer/webgl/WebGLRenderer.js b/src/renderer/webgl/WebGLRenderer.js index cfc6dfa4f..73e448406 100644 --- a/src/renderer/webgl/WebGLRenderer.js +++ b/src/renderer/webgl/WebGLRenderer.js @@ -35,6 +35,7 @@ var WebGLUniformLocationWrapper = require('./wrappers/WebGLUniformLocationWrappe var WebGLBlendParametersFactory = require('./parameters/WebGLBlendParametersFactory'); var WebGLGlobalParametersFactory = require('./parameters/WebGLGlobalParametersFactory'); var RenderNodeManager = require('./renderNodes/RenderNodeManager'); +var ShaderProgramFactory = require('./ShaderProgramFactory'); var DEBUG = false; @@ -170,6 +171,15 @@ var WebGLRenderer = new Class({ */ this.cameraRenderNode = null; + /** + * The shader program factory for managing variant shaders. + * + * @name Phaser.Renderer.WebGL.WebGLRenderer#shaderProgramFactory + * @type {Phaser.Renderer.WebGL.ShaderProgramFactory} + * @since 3.90.0 + */ + this.shaderProgramFactory = new ShaderProgramFactory(this); + /** * The width of the canvas being rendered to. * This is populated in the onResize event handler. diff --git a/src/renderer/webgl/renderNodes/BatchHandler.js b/src/renderer/webgl/renderNodes/BatchHandler.js index bdcbc1ba3..28b47e581 100644 --- a/src/renderer/webgl/renderNodes/BatchHandler.js +++ b/src/renderer/webgl/renderNodes/BatchHandler.js @@ -5,7 +5,7 @@ */ var Class = require('../../../utils/Class'); -var Utils = require('../Utils'); +var ProgramManager = require('../ProgramManager'); var WebGLVertexBufferLayoutWrapper = require('../wrappers/WebGLVertexBufferLayoutWrapper'); var RenderNode = require('./RenderNode'); @@ -108,14 +108,14 @@ var BatchHandler = new Class({ /** * The maximum number of textures per batch entry. - * This is usually the maximum number of texture units available, + * This is usually set to the maximum number of texture units available, * but it might be smaller for some uses. * * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandler#maxTexturesPerBatch * @type {number} * @since 3.90.0 */ - this.maxTexturesPerBatch = manager.maxParallelTextureUnits; + this.maxTexturesPerBatch = 1; // Listen for changes to the number of draw calls per batch. this.manager.on( @@ -124,37 +124,6 @@ var BatchHandler = new Class({ this ); - /** - * The raw fragment shader source code. - * - * This is used to generate the final fragment shader source code, - * which is then compiled into a shader program. - * The final source code may apply some templates to the raw source - * via `Utils.parseFragmentShaderMaxTextures`, - * generally to set the number of textures per batch, - * which may change at runtime. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandler#rawShaderSourceFS - * @type {string} - * @since 3.90.0 - */ - this.rawShaderSourceFS = config.fragmentSource; - - /** - * The WebGL program used to render the Game Object. - * - * @name Phaser.Renderer.WebGL.RenderNodes.Single#program - * @type {Phaser.Renderer.WebGL.Wrappers.WebGLProgramWrapper} - * @since 3.90.0 - */ - this.program = renderer.createProgram( - config.vertexSource, - Utils.parseFragmentShaderMaxTextures( - this.rawShaderSourceFS, - this.maxTexturesPerBatch - ) - ); - // Ensure that there is no VAO bound, because the following index buffer // will modify any currently bound VAO. renderer.glWrapper.updateVAO({ vao: null }); @@ -192,15 +161,40 @@ var BatchHandler = new Class({ ); /** - * The Vertex Array Object used to render the batch. + * The program manager used to create and manage shader programs. + * This contains shader variants. * - * @name Phaser.Renderer.WebGL.RenderNodes.Single#vao - * @type {Phaser.Renderer.WebGL.Wrappers.WebGLVAOWrapper} + * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandler#programManager + * @type {Phaser.Renderer.WebGL.ProgramManager} * @since 3.90.0 */ - this.vao = renderer.createVAO(this.program, this.indexBuffer, [ - this.vertexBufferLayout - ]); + this.programManager = new ProgramManager( + renderer, + this.indexBuffer, + [ this.vertexBufferLayout ] + ); + + // Fill in program configuration from config. + this.programManager.setBaseShader( + config.shaderName, + config.vertexSource, + config.fragmentSource + ); + if (config.shaderAdditions) + { + for (var i = 0; i < config.shaderAdditions.length; i++) + { + var addition = config.shaderAdditions[i]; + this.programManager.addAddition(addition); + } + } + if (config.shaderFeatures) + { + for (i = 0; i < config.shaderFeatures.length; i++) + { + this.programManager.addFeature(config.shaderFeatures[i]); + } + } /** * The number of bytes per instance, used to determine how much of the vertex buffer to upload. @@ -258,6 +252,9 @@ var BatchHandler = new Class({ */ this.instanceCount = 0; + // Ensure that shader program has the correct number of textures. + this.updateTextureCount(manager.maxParallelTextureUnits); + // Set the dimension-related uniforms and listen for resize events. this.resize(renderer.width, renderer.height); renderer.on(Phaser.Renderer.Events.RESIZE, this.resize, this); @@ -284,8 +281,13 @@ var BatchHandler = new Class({ newConfig.name = config.name || defaultConfig.name; newConfig.verticesPerInstance = config.verticesPerInstance || defaultConfig.verticesPerInstance; newConfig.indicesPerInstance = config.indicesPerInstance || defaultConfig.indicesPerInstance; + + newConfig.shaderName = config.shaderName || defaultConfig.shaderName; newConfig.vertexSource = config.vertexSource || defaultConfig.vertexSource; newConfig.fragmentSource = config.fragmentSource || defaultConfig.fragmentSource; + newConfig.shaderAdditions = config.shaderAdditions || defaultConfig.shaderAdditions; + newConfig.shaderFeatures = config.shaderFeatures || defaultConfig.shaderFeatures; + newConfig.indexBufferDynamic = config.indexBufferDynamic || defaultConfig.indexBufferDynamic; // These may be left undefined to auto-calculate instance count. diff --git a/src/renderer/webgl/renderNodes/BatchHandlerPointLight.js b/src/renderer/webgl/renderNodes/BatchHandlerPointLight.js index 8d8437597..3d0d640fc 100644 --- a/src/renderer/webgl/renderNodes/BatchHandlerPointLight.js +++ b/src/renderer/webgl/renderNodes/BatchHandlerPointLight.js @@ -54,6 +54,7 @@ var BatchHandlerPointLight = new Class({ name: 'BatchHandlerPointLight', verticesPerInstance: 4, indicesPerInstance: 6, + shaderName: 'POINTLIGHT', vertexSource: ShaderSourceVS, fragmentSource: ShaderSourceFS, vertexBufferLayout: { @@ -118,29 +119,35 @@ var BatchHandlerPointLight = new Class({ }, /** - * Called at the beginning of the `run` method. + * Update the uniforms for the current shader program. * - * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerPointLight#onRunBegin + * This method is called automatically when the batch is run. + * + * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerPointLight#setupUniforms * @since 3.90.0 * @param {Phaser.Types.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context. */ - onRunBegin: function (drawingContext) + setupUniforms: function (drawingContext) { - this.program.setUniform( + var programManager = this.programManager; + var width = drawingContext.width; + var height = drawingContext.height; + + programManager.setUniform( 'uCameraZoom', drawingContext.camera.zoom ); - this.program.setUniform( + programManager.setUniform( 'uResolution', - [ drawingContext.width, drawingContext.height ] + [ width, height ] ); drawingContext.renderer.setProjectionMatrix( - drawingContext.width, - drawingContext.height + width, + height ); - this.program.setUniform( + programManager.setUniform( 'uProjectionMatrix', drawingContext.renderer.projectionMatrix.val ); @@ -164,6 +171,14 @@ var BatchHandlerPointLight = new Class({ this.onRunBegin(drawingContext); + var programManager = this.programManager; + var programSuite = programManager.getCurrentProgramSuite(); + var program = programSuite.program; + var vao = programSuite.vao; + + this.setupUniforms(drawingContext); + programManager.applyUniforms(program); + // Update vertex buffers. // Because we are probably using a generic vertex buffer // which is larger than the current batch, we need to update @@ -173,8 +188,8 @@ var BatchHandlerPointLight = new Class({ this.manager.renderer.drawElements( drawingContext, this._emptyTextures, - this.program, - this.vao, + program, + vao, instanceCount * this.indicesPerInstance, 0 ); diff --git a/src/renderer/webgl/renderNodes/BatchHandlerQuad.js b/src/renderer/webgl/renderNodes/BatchHandlerQuad.js index 163a428c2..add31c6d2 100644 --- a/src/renderer/webgl/renderNodes/BatchHandlerQuad.js +++ b/src/renderer/webgl/renderNodes/BatchHandlerQuad.js @@ -4,10 +4,15 @@ * @license {@link https://opensource.org/licenses/MIT|MIT License} */ +var Vector2 = require('../../../math/Vector2'); var Class = require('../../../utils/Class'); +var Utils = require('../Utils'); var ShaderSourceFS = require('../shaders/Multi-frag'); var ShaderSourceVS = require('../shaders/Multi-vert'); -var Utils = require('../Utils'); +var MakeApplyLighting = require('../shaders/configs/MakeApplyLighting'); +var MakeApplyTint = require('../shaders/configs/MakeApplyTint'); +var MakeGetTexture = require('../shaders/configs/MakeGetTexture'); +var MakeRotationDatum = require('../shaders/configs/MakeRotationDatum'); var BatchHandler = require('./BatchHandler'); /** @@ -27,11 +32,40 @@ var BatchHandlerQuad = new Class({ initialize: function BatchHandlerQuad (manager, config) { + /** + * The current render options to which the batch is built. + * These help define the shader. + * + * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuad#renderOptions + * @type {object} + * @since 3.90.0 + */ + this.renderOptions = { + multiTexturing: false, + lighting: false, + selfShadow: false, + selfShadowPenumbra: 0, + selfShadowThreshold: 0 + }; + BatchHandler.call(this, manager, config, this.defaultConfig); // Main sampler will never change after initialization, // because it addresses texture units, not textures. - this.program.setUniform('uMainSampler[0]', this.manager.renderer.textureUnitIndices); + this.programManager.setUniform( + 'uMainSampler[0]', + this.manager.renderer.textureUnitIndices + ); + + /** + * A persistent calculation vector used when processing the lights. + * + * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuad#_lightVector + * @type {Phaser.Math.Vector2} + * @private + * @since 3.90.0 + */ + this._lightVector = new Vector2(); }, /** @@ -46,8 +80,15 @@ var BatchHandlerQuad = new Class({ name: 'BatchHandlerQuad', verticesPerInstance: 4, indicesPerInstance: 6, + shaderName: 'STANDARD', vertexSource: ShaderSourceVS, fragmentSource: ShaderSourceFS, + shaderAdditions: [ + MakeGetTexture(1), + MakeApplyTint(), + MakeRotationDatum(true), + MakeApplyLighting(true) + ], vertexBufferLayout: { usage: 'DYNAMIC_DRAW', layout: [ @@ -60,7 +101,7 @@ var BatchHandlerQuad = new Class({ size: 2 }, { - name: 'inTexId' + name: 'inTexDatum' }, { name: 'inTintEffect' @@ -138,7 +179,7 @@ var BatchHandlerQuad = new Class({ } var newCount = Math.max(1, Math.min(count, renderer.maxTextures)); - if (newCount === this.texturesPerBatch) + if (newCount === this.maxTexturesPerBatch) { return; } @@ -155,35 +196,46 @@ var BatchHandlerQuad = new Class({ this.maxTexturesPerBatch = newCount; - // Recreate the shader program with the new texture count. - this.program.fragmentSource = Utils.parseFragmentShaderMaxTextures( - this.rawShaderSourceFS, - this.maxTexturesPerBatch - ); - this.program.createResource(); + // Update program manager to use the new texture count. + if (this.renderOptions.multiTexturing) + { + var programManager = this.programManager; + var textureAdditions = programManager.getAdditionsByTag('TEXTURE'); + while (textureAdditions.length > 0) + { + var textureAddition = textureAdditions.pop(); + programManager.removeAddition(textureAddition.name); + } + programManager.addAddition( + MakeGetTexture(this.maxTexturesPerBatch), + 0 + ); + } - this.program.setUniform( - 'uMainSampler[0]', - renderer.textureUnitIndices - ); this.resize(renderer.width, renderer.height); }, /** - * Called at the beginning of the `run` method. + * Update the uniforms for the current shader program. * - * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuad#onRunBegin + * This method is called automatically when the batch is run. + * + * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuad#setupUniforms * @since 3.90.0 * @param {Phaser.Types.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context. */ - onRunBegin: function (drawingContext) + setupUniforms: function (drawingContext) { - this.program.setUniform( + var programManager = this.programManager; + var renderOptions = this.renderOptions; + + // Standard uniforms. + programManager.setUniform( 'uRoundPixels', drawingContext.camera.roundPixels ); - this.program.setUniform( + programManager.setUniform( 'uResolution', [ drawingContext.width, drawingContext.height ] ); @@ -192,10 +244,123 @@ var BatchHandlerQuad = new Class({ drawingContext.width, drawingContext.height ); - this.program.setUniform( + programManager.setUniform( 'uProjectionMatrix', drawingContext.renderer.projectionMatrix.val ); + + // Lighting uniforms. + Utils.updateLightingUniforms( + renderOptions.lighting, + this.manager.renderer, + drawingContext, + programManager, + this._lightVector, + renderOptions.selfShadow, + renderOptions.selfShadowThreshold, + renderOptions.selfShadowPenumbra + ); + }, + + /** + * Update the render options for the current shader program. + * If the options have changed, the batch is run to apply the changes. + * + * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuad#updateRenderOptions + * @since 3.90.0 + * @param {Phaser.Types.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context. + * @param {object} renderOptions - The new render options. + */ + updateRenderOptions: function (drawingContext, renderOptions) + { + var programManager = this.programManager; + var oldRenderOptions = this.renderOptions; + var newRenderOptions = { + multiTexturing: false, + lighting: false, + selfShadow: false, + selfShadowPenumbra: 0, + selfShadowThreshold: 0 + }; + + // Parse shader-relevant render options. + if (renderOptions) + { + // Multitexturing is disabled if other textures are in use. + newRenderOptions.multiTexturing = !!renderOptions.multiTexturing && !renderOptions.lighting; + + newRenderOptions.lighting = !!renderOptions.lighting; + + if (renderOptions.lighting && renderOptions.lighting.selfShadow && renderOptions.lighting.selfShadow.enabled) + { + newRenderOptions.selfShadow = true; + newRenderOptions.selfShadowPenumbra = renderOptions.lighting.selfShadow.penumbra; + newRenderOptions.selfShadowThreshold = renderOptions.lighting.selfShadow.diffuseFlatThreshold; + } + } + + // Check for changes. + var updateTexturing = newRenderOptions.multiTexturing !== oldRenderOptions.multiTexturing; + var updateLighting = newRenderOptions.lighting !== oldRenderOptions.lighting; + var updateSelfShadow = newRenderOptions.selfShadow !== oldRenderOptions.selfShadow; + var updateSelfShadowPenumbra = newRenderOptions.selfShadowPenumbra !== oldRenderOptions.selfShadowPenumbra; + var updateSelfShadowThreshold = newRenderOptions.selfShadowThreshold !== oldRenderOptions.selfShadowThreshold; + + // Run the batch if the shader has changed. + if (updateTexturing || updateLighting || updateSelfShadow || updateSelfShadowPenumbra || updateSelfShadowThreshold) + { + this.run(drawingContext); + } + + // Cache new render options. + this.renderOptions = newRenderOptions; + + // Update shader program configuration. + if (updateTexturing) + { + var texturingAddition = programManager.getAdditionsByTag('TEXTURE')[0]; + if (texturingAddition) + { + programManager.removeAddition(texturingAddition.name); + + } + var texCount = newRenderOptions.multiTexturing ? this.maxTexturesPerBatch : 1; + programManager.addAddition( + MakeGetTexture(texCount), + 0 + ); + } + + if (updateLighting) + { + var lightingAddition = programManager.getAddition('LIGHTING'); + if (lightingAddition) + { + lightingAddition.disable = !newRenderOptions.lighting; + if (newRenderOptions.lighting) + { + lightingAddition.additions.fragmentDefine = '#define LIGHT_COUNT ' + this.manager.renderer.config.maxLights; + } + } + + var rotationAddition = programManager.getAddition('RotDatum'); + if (rotationAddition) + { + rotationAddition.disable = !newRenderOptions.lighting; + } + } + + if (updateSelfShadow) + { + if (newRenderOptions.selfShadow) + { + programManager.addFeature('SELFSHADOW'); + } + else + { + programManager.removeFeature('SELFSHADOW'); + } + } }, /** @@ -213,11 +378,16 @@ var BatchHandlerQuad = new Class({ if (this.instanceCount === 0) { return; } this.onRunBegin(drawingContext); + var programManager = this.programManager; + var programSuite = programManager.getCurrentProgramSuite(); + var program = programSuite.program; + var vao = programSuite.vao; + + this.setupUniforms(drawingContext); + programManager.applyUniforms(program); var bytesPerIndexPerInstance = this.bytesPerIndexPerInstance; var indicesPerInstance = this.indicesPerInstance; - var program = this.program; - var vao = this.vao; var renderer = this.manager.renderer; var vertexBuffer = this.vertexBufferLayout.buffer; @@ -284,39 +454,40 @@ var BatchHandlerQuad = new Class({ * @param {number} tintBL - The bottom-left tint color. * @param {number} tintTR - The top-right tint color. * @param {number} tintBR - The bottom-right tint color. + * @param {object} [renderOptions] - Optional render features. + * @param {boolean} [renderOptions.multiTexturing] - Whether to use multi-texturing. + * @param {object} [renderOptions.lighting] - How to treat lighting. If this object is defined, lighting will be activated, and multi-texturing disabled. + * @param {Phaser.Renderer.WebGL.WebGLTextureWrapper} renderOptions.lighting.normalGLTexture - The normal map texture to render. + * @param {number} renderOptions.lighting.normalMapRotation - The rotation of the normal map texture. + * @param {object} [renderOptions.lighting.selfShadow] - Self-shadowing options. + * @param {boolean} renderOptions.lighting.selfShadow.enabled - Whether to use self-shadowing. + * @param {number} renderOptions.lighting.selfShadow.penumbra - Self-shadowing penumbra strength. + * @param {number} renderOptions.lighting.selfShadow.diffuseFlatThreshold - Self-shadowing texture brightness equivalent to a flat surface. */ - batch: function (currentContext, glTexture, x0, y0, x1, y1, x2, y2, x3, y3, texX, texY, texWidth, texHeight, tintFill, tintTL, tintBL, tintTR, tintBR) + batch: function ( + currentContext, + glTexture, + x0, y0, + x1, y1, + x2, y2, + x3, y3, + texX, texY, + texWidth, texHeight, + tintFill, + tintTL, tintBL, tintTR, tintBR, + renderOptions + ) { if (this.instanceCount === 0) { this.manager.setCurrentBatchNode(this, currentContext); } - // Texture + // Check render options and run the batch if they differ. + this.updateRenderOptions(currentContext, renderOptions); - // Check if the texture is already in the batch. - // This could be a very expensive operation if we're not careful. - // If we just use `batchTextures.indexOf`, a linear search, - // we can use up to 20% of a frame budget. - // Instead, we cache the texture unit index on the texture itself, - // so we can immediately tell whether it's in the batch. - // We reset this value when we flush the batch. - - var textureIndex = glTexture.batchUnit; - if (textureIndex === -1) - { - var currentBatchEntry = this.currentBatchEntry; - if (currentBatchEntry.count === this.maxTexturesPerBatch) - { - // Commit the current batch entry and start a new one. - this.pushCurrentBatchEntry(); - currentBatchEntry = this.currentBatchEntry; - } - textureIndex = currentBatchEntry.unit; - glTexture.batchUnit = textureIndex; - currentBatchEntry.texture[textureIndex] = glTexture; - currentBatchEntry.unit++; - } + // Process textures and get relevant data. + var textureDatum = this.batchTextures(glTexture, renderOptions); // Update the vertex buffer. var vertexOffset32 = this.instanceCount * this.floatsPerInstance; @@ -329,7 +500,7 @@ var BatchHandlerQuad = new Class({ vertexViewF32[vertexOffset32++] = y1; vertexViewF32[vertexOffset32++] = texX; vertexViewF32[vertexOffset32++] = texY + texHeight; - vertexViewF32[vertexOffset32++] = textureIndex; + vertexViewF32[vertexOffset32++] = textureDatum; vertexViewF32[vertexOffset32++] = tintFill; vertexViewU32[vertexOffset32++] = tintBL; @@ -338,7 +509,7 @@ var BatchHandlerQuad = new Class({ vertexViewF32[vertexOffset32++] = y0; vertexViewF32[vertexOffset32++] = texX; vertexViewF32[vertexOffset32++] = texY; - vertexViewF32[vertexOffset32++] = textureIndex; + vertexViewF32[vertexOffset32++] = textureDatum; vertexViewF32[vertexOffset32++] = tintFill; vertexViewU32[vertexOffset32++] = tintTL; @@ -347,7 +518,7 @@ var BatchHandlerQuad = new Class({ vertexViewF32[vertexOffset32++] = y3; vertexViewF32[vertexOffset32++] = texX + texWidth; vertexViewF32[vertexOffset32++] = texY + texHeight; - vertexViewF32[vertexOffset32++] = textureIndex; + vertexViewF32[vertexOffset32++] = textureDatum; vertexViewF32[vertexOffset32++] = tintFill; vertexViewU32[vertexOffset32++] = tintBR; @@ -356,7 +527,7 @@ var BatchHandlerQuad = new Class({ vertexViewF32[vertexOffset32++] = y2; vertexViewF32[vertexOffset32++] = texX + texWidth; vertexViewF32[vertexOffset32++] = texY; - vertexViewF32[vertexOffset32++] = textureIndex; + vertexViewF32[vertexOffset32++] = textureDatum; vertexViewF32[vertexOffset32++] = tintFill; vertexViewU32[vertexOffset32++] = tintTR; @@ -374,6 +545,94 @@ var BatchHandlerQuad = new Class({ } }, + /** + * Process textures for batching. + * This method is called automatically by the `batch` method. + * It returns a piece of data used for various texture tasks, + * depending on the render options. + * + * The texture datum may be used for texture ID or normal map rotation. + * + * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuad#batchTextures + * @since 3.90.0 + * @param {Phaser.Renderer.WebGL.WebGLTextureWrapper} glTexture - The texture to render. + * @param {object} renderOptions - The current render options. + * @return {number} The texture datum. + */ + batchTextures: function (glTexture, renderOptions) + { + var newRenderOptions = this.renderOptions; + + // Texture data, used for either texture ID or normal map rotation. + var textureDatum = 0; + + var currentBatchEntry = this.currentBatchEntry; + if (newRenderOptions.multiTexturing) + { + // Multi Texture + + // Check if the texture is already in the batch. + // This could be a very expensive operation if we're not careful. + // If we just use `batchTextures.indexOf`, a linear search, + // we can use up to 20% of a frame budget. + // Instead, we cache the texture unit index on the texture itself, + // so we can immediately tell whether it's in the batch. + // We reset this value when we flush the batch. + + textureDatum = glTexture.batchUnit; + if (textureDatum === -1) + { + if (currentBatchEntry.texture.length === this.maxTexturesPerBatch) + { + // Commit the current batch entry and start a new one. + this.pushCurrentBatchEntry(); + currentBatchEntry = this.currentBatchEntry; + } + textureDatum = currentBatchEntry.unit; + glTexture.batchUnit = textureDatum; + currentBatchEntry.texture[textureDatum] = glTexture; + currentBatchEntry.unit++; + } + } + else if (newRenderOptions.lighting) + { + textureDatum = renderOptions.lighting.normalMapRotation; + + var normalGLTexture = renderOptions.lighting.normalGLTexture; + if ( + currentBatchEntry.texture[0] !== glTexture || + currentBatchEntry.texture[1] !== normalGLTexture + ) + { + this.pushCurrentBatchEntry(); + + // // Complete the entire batch if the texture changes. + // this.run(currentContext); + + // Current batch entry has been redefined. + currentBatchEntry = this.currentBatchEntry; + glTexture.batchUnit = 0; + normalGLTexture.batchUnit = 1; + currentBatchEntry.texture[0] = glTexture; + currentBatchEntry.texture[1] = normalGLTexture; + currentBatchEntry.unit = 2; + } + } + else if (currentBatchEntry.texture[0] !== glTexture) + { + // Single texture. + this.pushCurrentBatchEntry(); + + // Current batch entry has been redefined. + currentBatchEntry = this.currentBatchEntry; + glTexture.batchUnit = 0; + currentBatchEntry.texture[0] = glTexture; + currentBatchEntry.unit = 1; + } + + return textureDatum; + }, + /** * Push the current batch entry to the batch entry list, * and create a new batch entry for future use. @@ -383,6 +642,11 @@ var BatchHandlerQuad = new Class({ */ pushCurrentBatchEntry: function () { + if (this.currentBatchEntry.count < 1) + { + return; + } + this.batchEntries.push(this.currentBatchEntry); // Clear unit assignment on textures. diff --git a/src/renderer/webgl/renderNodes/BatchHandlerQuadLight.js b/src/renderer/webgl/renderNodes/BatchHandlerQuadLight.js deleted file mode 100644 index 78c166b41..000000000 --- a/src/renderer/webgl/renderNodes/BatchHandlerQuadLight.js +++ /dev/null @@ -1,390 +0,0 @@ -/** - * @author Benjamin D. Richards - * @copyright 2013-2024 Phaser Studio Inc. - * @license {@link https://opensource.org/licenses/MIT|MIT License} - */ - -var Vector2 = require('../../../math/Vector2'); -var Class = require('../../../utils/Class'); -var LightShaderSourceFS = require('../shaders/Light-frag'); -var ShaderSourceVS = require('../shaders/Multi-vert'); -var BatchHandlerQuad = require('./BatchHandlerQuad'); - -/** - * @classdesc - * This RenderNode draws Standard Batch Render Quads with a Light Shader - * in batches. - * - * The fragment shader used by this RenderNode will be compiled - * with a maximum light count defined by the renderer configuration. - * The string `%LIGHT_COUNT%` in the fragment shader source will be - * replaced with this value. - * - * @class BatchHandlerQuadLight - * @memberof Phaser.Renderer.WebGL.RenderNodes - * @constructor - * @since 3.90.0 - * @extends Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuad - * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager} manager - The manager that owns this RenderNode. - * @param {Phaser.Types.Renderer.WebGL.RenderNodes.BatchHandlerConfig} config - The configuration object for this RenderNode. - */ -var BatchHandlerQuadLight = new Class({ - Extends: BatchHandlerQuad, - - initialize: function BatchHandlerQuadLight (manager, config) - { - BatchHandlerQuad.call(this, manager, config); - - this.program.setUniform('uMainSampler', 0); - this.program.setUniform('uNormSampler', 1); - - /** - * Inverse rotation matrix for normal map rotations. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuadLight#inverseRotationMatrix - * @type {Float32Array} - * @private - * @since 3.90.0 - */ - this.inverseRotationMatrix = new Float32Array([ - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - ]); - - /** - * A persistent calculation vector used when processing the lights. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuadLight#_lightVector - * @type {Phaser.Math.Vector2} - * @private - * @since 3.90.0 - */ - this._lightVector = new Vector2(); - - /** - * The rotation of the normal map texture. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuadLight#_normalMapRotation - * @type {number} - * @private - * @since 3.90.0 - */ - this._normalMapRotation = 0; - }, - - /** - * The default configuration settings for BatchHandlerQuadLight. - * - * These are very similar to standard settings, - * but because the textures are always set in units 0 and 1, - * there's no need to have an attribute for the texture unit. - * While the vertex shader still reads `inTexId`, it is not used, - * and the default value of 0 is fine. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuadLight#defaultConfig - * @type {Phaser.Types.Renderer.WebGL.RenderNodes.BatchHandlerConfig} - * @since 3.90.0 - * @readonly - */ - defaultConfig: { - name: 'BatchHandlerQuadLight', - verticesPerInstance: 4, - indicesPerInstance: 6, - vertexSource: ShaderSourceVS, - fragmentSource: LightShaderSourceFS, - vertexBufferLayout: { - usage: 'DYNAMIC_DRAW', - layout: [ - { - name: 'inPosition', - size: 2 - }, - { - name: 'inTexCoord', - size: 2 - }, - { - name: 'inTintEffect' - }, - { - name: 'inTint', - size: 4, - type: 'UNSIGNED_BYTE', - normalized: true - } - ] - } - }, - - _copyAndCompleteConfig: function (manager, config, defaultConfig) - { - var newConfig = BatchHandlerQuad.prototype._copyAndCompleteConfig.call(this, manager, config, defaultConfig); - - newConfig.fragmentSource = newConfig.fragmentSource.replace( - '%LIGHT_COUNT%', - manager.renderer.config.maxLights - ); - - return newConfig; - }, - - /** - * Called at the start of the `run` method. - * - * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuadLight#onRunBegin - * @since 3.90.0 - * @param {Phaser.Types.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context. - */ - onRunBegin: function (drawingContext) - { - var camera = drawingContext.camera; - var cameraMatrix = camera.matrix; - var program = this.program; - var scene = camera.scene; - var lightManager = scene.sys.lights; - var lights = lightManager.getLights(camera); - var lightsCount = lights.length; - var ambientColor = lightManager.ambientColor; - var vec = this._lightVector; - var height = this.manager.renderer.height; - - BatchHandlerQuad.prototype.onRunBegin.call(this, drawingContext); - - program.setUniform( - 'uCamera', - [ - camera.x, - camera.y, - camera.rotation, - camera.zoom - ] - ); - program.setUniform( - 'uAmbientLightColor', - [ - ambientColor.r, - ambientColor.g, - ambientColor.b - ] - ); - program.setUniform( - 'uLightCount', - lightsCount - ); - program.setUniform( - 'uInverseRotationMatrix', - this.inverseRotationMatrix - ); - - for (var i = 0; i < lightsCount; i++) - { - var light = lights[i].light; - var color = light.color; - - var lightName = 'uLights[' + i + '].'; - - cameraMatrix.transformPoint( - light.x - (camera.scrollX * light.scrollFactorX * camera.zoom), - light.y - (camera.scrollY * light.scrollFactorY * camera.zoom), - vec - ); - - program.setUniform( - lightName + 'position', - [ - vec.x, - height - (vec.y), - light.z * camera.zoom - ] - ); - program.setUniform( - lightName + 'color', - [ - color.r, - color.g, - color.b - ] - ); - program.setUniform( - lightName + 'intensity', - light.intensity - ); - program.setUniform( - lightName + 'radius', - light.radius - ); - } - }, - - /** - * Add a new quad to the batch. - * - * For compatibility with TRIANGLE_STRIP rendering, - * the vertices are added in the order: - * - * - Top-left - * - Bottom-left - * - Top-right - * - Bottom-right - * - * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuadLight#batch - * @since 3.90.0 - * @param {Phaser.Types.Renderer.WebGL.DrawingContext} currentContext - The current drawing context. - * @param {Phaser.Renderer.WebGL.WebGLTextureWrapper} glTexture - The texture to render. - * @param {Phaser.Renderer.WebGL.WebGLTextureWrapper} normalGLTexture - The normal map texture to render. - * @param {number} normalMapRotation - The rotation of the normal map texture. - * @param {number} x0 - The x coordinate of the top-left corner. - * @param {number} y0 - The y coordinate of the top-left corner. - * @param {number} x1 - The x coordinate of the bottom-left corner. - * @param {number} y1 - The y coordinate of the bottom-left corner. - * @param {number} x2 - The x coordinate of the top-right corner. - * @param {number} y2 - The y coordinate of the top-right corner. - * @param {number} x3 - The x coordinate of the bottom-right corner. - * @param {number} y3 - The y coordinate of the bottom-right corner. - * @param {number} texX - The left u coordinate (0-1). - * @param {number} texY - The top v coordinate (0-1). - * @param {number} texWidth - The width of the texture (0-1). - * @param {number} texHeight - The height of the texture (0-1). - * @param {number} tintFill - Whether to tint the fill color. - * @param {number} tintTL - The top-left tint color. - * @param {number} tintBL - The bottom-left tint color. - * @param {number} tintTR - The top-right tint color. - * @param {number} tintBR - The bottom-right tint color. - */ - batch: function ( - currentContext, - glTexture, - normalGLTexture, - normalMapRotation, - x0, y0, - x1, y1, - x2, y2, - x3, y3, - texX, texY, - texWidth, texHeight, - tintFill, - tintTL, tintBL, tintTR, tintBR - ) - { - if (this.instanceCount === 0) - { - this.manager.setCurrentBatchNode(this, currentContext); - } - - // Texture - - var currentBatchEntry = this.currentBatchEntry; - if ( - currentBatchEntry.texture[0] !== glTexture || - currentBatchEntry.texture[1] !== normalGLTexture - ) - { - // Complete the entire batch if the texture changes. - this.run(currentContext); - } - - // Current batch entry has been redefined. - currentBatchEntry = this.currentBatchEntry; - glTexture.batchUnit = 0; - normalGLTexture.batchUnit = 1; - currentBatchEntry.texture[0] = glTexture; - currentBatchEntry.texture[1] = normalGLTexture; - currentBatchEntry.unit = 2; - - // Normal map rotation - normalMapRotation = -normalMapRotation - currentContext.camera.rotation; - if (this._normalMapRotation !== normalMapRotation) - { - // Complete the entire batch if the normal map rotation changes. - this.run(currentContext); - - this._normalMapRotation = normalMapRotation; - var inverseRotationMatrix = this.inverseRotationMatrix; - - if (normalMapRotation) - { - var c = Math.cos(normalMapRotation); - var s = Math.sin(normalMapRotation); - - inverseRotationMatrix[1] = s; - inverseRotationMatrix[3] = -s; - inverseRotationMatrix[0] = inverseRotationMatrix[4] = c; - } - else - { - inverseRotationMatrix[0] = inverseRotationMatrix[4] = 1; - inverseRotationMatrix[1] = inverseRotationMatrix[3] = 0; - } - - // This matrix will definitely be used by the next render. - this.program.setUniform('uInverseRotationMatrix', inverseRotationMatrix); - } - - // Update the vertex buffer. - var vertexOffset32 = this.instanceCount * this.floatsPerInstance; - var vertexBuffer = this.vertexBufferLayout.buffer; - var vertexViewF32 = vertexBuffer.viewF32; - var vertexViewU32 = vertexBuffer.viewU32; - - // Bottom-left - vertexViewF32[vertexOffset32++] = x1; - vertexViewF32[vertexOffset32++] = y1; - vertexViewF32[vertexOffset32++] = texX; - vertexViewF32[vertexOffset32++] = texY + texHeight; - vertexViewF32[vertexOffset32++] = tintFill; - vertexViewU32[vertexOffset32++] = tintBL; - - // Top-left - vertexViewF32[vertexOffset32++] = x0; - vertexViewF32[vertexOffset32++] = y0; - vertexViewF32[vertexOffset32++] = texX; - vertexViewF32[vertexOffset32++] = texY; - vertexViewF32[vertexOffset32++] = tintFill; - vertexViewU32[vertexOffset32++] = tintTL; - - // Bottom-right - vertexViewF32[vertexOffset32++] = x3; - vertexViewF32[vertexOffset32++] = y3; - vertexViewF32[vertexOffset32++] = texX + texWidth; - vertexViewF32[vertexOffset32++] = texY + texHeight; - vertexViewF32[vertexOffset32++] = tintFill; - vertexViewU32[vertexOffset32++] = tintBR; - - // Top-right - vertexViewF32[vertexOffset32++] = x2; - vertexViewF32[vertexOffset32++] = y2; - vertexViewF32[vertexOffset32++] = texX + texWidth; - vertexViewF32[vertexOffset32++] = texY; - vertexViewF32[vertexOffset32++] = tintFill; - vertexViewU32[vertexOffset32++] = tintTR; - - // Increment the instance count. - this.instanceCount++; - this.currentBatchEntry.count++; - - // Check whether the batch should be rendered immediately. - // This guarantees that none of the arrays are full above. - if (this.instanceCount === this.instancesPerBatch) - { - this.run(currentContext); - - // Now the batch is empty. - } - }, - - /** - * Called by the render node manager when the advised texture unit count changes. - * In `BatchHandlerQuadLight`, this does nothing, because it only ever uses two texture units. - * - * As this extends `BatchHandlerQuad`, it would otherwise rebuild the shader - * program. - * - * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuadLight#updateTextureCount - * @since 3.90.0 - * @param {number} count - The new advised texture unit count. - */ - updateTextureCount: function (count) {} -}); - -module.exports = BatchHandlerQuadLight; diff --git a/src/renderer/webgl/renderNodes/BatchHandlerQuadLightShadow.js b/src/renderer/webgl/renderNodes/BatchHandlerQuadLightShadow.js deleted file mode 100644 index 26089dc16..000000000 --- a/src/renderer/webgl/renderNodes/BatchHandlerQuadLightShadow.js +++ /dev/null @@ -1,140 +0,0 @@ -/** - * @author Benjamin D. Richards - * @copyright 2013-2024 Phaser Studio Inc. - * @license {@link https://opensource.org/licenses/MIT|MIT License} - */ - -var Class = require('../../../utils/Class'); -var LightShaderSourceFS = require('../shaders/LightShadow-frag'); -var ShaderSourceVS = require('../shaders/Multi-vert'); -var BatchHandlerQuadLight = require('./BatchHandlerQuadLight'); - -/** - * @classdesc - * The BatchHandlerQuadLightShadow is a special type of BatchHandlerQuadLight - * that supports self-shadowing based on the diffuse map. - * - * The shader uses the diffuse map to determine the concavity of the surface. - * Darker areas are assumed to be more concave, thus they can only receive light - * from a smaller range of angles. Light outside that range is cut off, - * creating a shadow. - * - * Because most game art wasn't created with these characteristics in mind, - * you may need to adjust the `diffuseFlatThreshold` and `penumbra` values - * to get the desired effect. - * - * To use this RenderNode in your game, you must set the option - * `render.selfShadow` to `true` in your game configuration. - * It will affect all textured objects with lighting enabled - * (technically, all objects that use the `BatchHandlerQuadLight` RenderNode). - * - * Alternatively, you can create a custom RenderNode that uses this handler. - * - * @class BatchHandlerQuadLightShadow - * @extends Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuadLight - * @memberof Phaser.Renderer.WebGL.RenderNodes - * @constructor - * @since 3.90.0 - * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager} manager - The manager that owns this RenderNode. - * @param {Phaser.Types.Renderer.WebGL.RenderNodes.BatchHandlerConfig} [config] - The configuration object for this handler. - */ -var BatchHandlerQuadLightShadow = new Class({ - Extends: BatchHandlerQuadLight, - - initialize: function BatchHandlerQuadLightShadow (manager, config) - { - BatchHandlerQuadLight.call(this, manager, config); - - /** - * The threshold at which the diffuse lighting will be considered flat. - * This is used to derive self-shadowing from the diffuse map. - * - * This is a brightness value in the range 0-1. - * Because art is usually not pure white, the default is 1/3, - * a darker value, which is more likely to be considered flat. - * You should adjust this value based on the art in your game. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuadLightShadow#diffuseFlatThreshold - * @type {number} - * @default 1 - * @since 3.90.0 - */ - this.diffuseFlatThreshold = 1 / 3; - - /** - * The penumbra value for the shadow. - * This smooths the edge of self-shadowing. - * A lower value will create a sharper but more jagged shadow. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuadLightShadow#penumbra - * @type {number} - * @default 0.5 - * @since 3.90.0 - */ - this.penumbra = 0.5; - }, - - /** - * The default configuration settings for BatchHandlerQuadLightShadow. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuadLightShadow#defaultConfig - * @type {Phaser.Types.Renderer.WebGL.RenderNodes.BatchHandlerConfig} - * @since 3.90.0 - * @readonly - */ - defaultConfig: { - name: 'BatchHandlerQuadLightShadow', - verticesPerInstance: 4, - indicesPerInstance: 6, - vertexSource: ShaderSourceVS, - fragmentSource: LightShaderSourceFS, - vertexBufferLayout: { - usage: 'DYNAMIC_DRAW', - layout: [ - { - name: 'inPosition', - size: 2 - }, - { - name: 'inTexCoord', - size: 2 - }, - { - name: 'inTintEffect' - }, - { - name: 'inTint', - size: 4, - type: 'UNSIGNED_BYTE', - normalized: true - } - ] - } - }, - - /** - * Called at the start of the run loop. - * - * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuadLightShadow#onRunBegin - * @since 3.90.0 - * @param {Phaser.Renderer.WebGL.WebGLPipeline} drawingContext - The drawing context. - */ - onRunBegin: function (drawingContext) - { - BatchHandlerQuadLight.prototype.onRunBegin.call(this, drawingContext); - - var program = this.program; - - program.setUniform( - 'uDiffuseFlatThreshold', - this.diffuseFlatThreshold * 3 - ); - - program.setUniform( - 'uPenumbra', - this.penumbra - ); - } -}); - -module.exports = BatchHandlerQuadLightShadow; diff --git a/src/renderer/webgl/renderNodes/BatchHandlerStrip.js b/src/renderer/webgl/renderNodes/BatchHandlerStrip.js index 3d01d5b02..08d825db6 100644 --- a/src/renderer/webgl/renderNodes/BatchHandlerStrip.js +++ b/src/renderer/webgl/renderNodes/BatchHandlerStrip.js @@ -7,6 +7,8 @@ var Class = require('../../../utils/Class'); var ShaderSourceFS = require('../shaders/Multi-frag'); var ShaderSourceVS = require('../shaders/Multi-vert'); +var MakeApplyTint = require('../shaders/configs/MakeApplyTint'); +var MakeGetTexture = require('../shaders/configs/MakeGetTexture'); var Utils = require('../Utils'); var BatchHandlerQuad = require('./BatchHandlerQuad'); @@ -34,6 +36,9 @@ var BatchHandlerStrip = new Class({ initialize: function BatchHandlerStrip (manager, config) { BatchHandlerQuad.call(this, manager, config); + + // We do not expect to use extra textures. + this.renderOptions.multiTexturing = true; }, /** @@ -48,8 +53,13 @@ var BatchHandlerStrip = new Class({ name: 'BatchHandlerStrip', verticesPerInstance: 2, indicesPerInstance: 2, + shaderName: 'STRIP', vertexSource: ShaderSourceVS, fragmentSource: ShaderSourceFS, + shaderAdditions: [ + MakeGetTexture(1), + MakeApplyTint() + ], vertexBufferLayout: { usage: 'DYNAMIC_DRAW', layout: [ @@ -62,7 +72,7 @@ var BatchHandlerStrip = new Class({ size: 2 }, { - name: 'inTexId' + name: 'inTexDatum' }, { name: 'inTintEffect' @@ -144,31 +154,8 @@ var BatchHandlerStrip = new Class({ // Now the batch is empty. } - // Texture - - // Check if the texture is already in the batch. - // This could be a very expensive operation if we're not careful. - // If we just use `batchTextures.indexOf`, a linear search, - // we can use up to 20% of a frame budget. - // Instead, we cache the texture unit index on the texture itself, - // so we can immediately tell whether it's in the batch. - // We reset this value when we flush the batch. - - var textureIndex = glTexture.batchUnit; - if (textureIndex === -1) - { - var currentBatchEntry = this.currentBatchEntry; - if (currentBatchEntry.count === this.maxTexturesPerBatch) - { - // Commit the current batch entry and start a new one. - this.pushCurrentBatchEntry(); - currentBatchEntry = this.currentBatchEntry; - } - textureIndex = currentBatchEntry.unit; - glTexture.batchUnit = textureIndex; - currentBatchEntry.texture[textureIndex] = glTexture; - currentBatchEntry.unit++; - } + // Process textures and get relevant data. + var textureDatum = this.batchTextures(glTexture); // Update the vertex buffer. var vertexOffset32 = this.instanceCount * this.floatsPerInstance; @@ -230,7 +217,7 @@ var BatchHandlerStrip = new Class({ vertexViewF32[vertexOffset32++] = ty; vertexViewF32[vertexOffset32++] = uv[i]; vertexViewF32[vertexOffset32++] = uv[i + 1]; - vertexViewF32[vertexOffset32++] = textureIndex; + vertexViewF32[vertexOffset32++] = textureDatum; vertexViewF32[vertexOffset32++] = tintFill; vertexViewU32[vertexOffset32++] = getTint( colors[i / 2], diff --git a/src/renderer/webgl/renderNodes/BatchHandlerTileSprite.js b/src/renderer/webgl/renderNodes/BatchHandlerTileSprite.js index a976cc240..c3d14e273 100644 --- a/src/renderer/webgl/renderNodes/BatchHandlerTileSprite.js +++ b/src/renderer/webgl/renderNodes/BatchHandlerTileSprite.js @@ -5,8 +5,13 @@ */ var Class = require('../../../utils/Class'); -var ShaderSourceFS = require('../shaders/MultiTileSprite-frag'); -var ShaderSourceVS = require('../shaders/MultiTileSprite-vert'); +var ShaderSourceFS = require('../shaders/Multi-frag'); +var ShaderSourceVS = require('../shaders/Multi-vert'); +var MakeApplyLighting = require('../shaders/configs/MakeApplyLighting'); +var MakeApplyTint = require('../shaders/configs/MakeApplyTint'); +var MakeGetTexture = require('../shaders/configs/MakeGetTexture'); +var MakeRotationDatum = require('../shaders/configs/MakeRotationDatum'); +var MakeTileSpriteWrap = require('../shaders/configs/MakeTileSpriteWrap'); var BatchHandlerQuad = require('./BatchHandlerQuad'); /** @@ -33,8 +38,16 @@ var BatchHandlerTileSprite = new Class({ name: 'BatchHandlerTileSprite', verticesPerInstance: 4, indicesPerInstance: 6, + shaderName: 'TILESPRITE', vertexSource: ShaderSourceVS, fragmentSource: ShaderSourceFS, + shaderAdditions: [ + MakeGetTexture(1), + MakeTileSpriteWrap(), + MakeApplyTint(), + MakeRotationDatum(true), + MakeApplyLighting(true) + ], vertexBufferLayout: { usage: 'DYNAMIC_DRAW', layout: [ @@ -51,7 +64,7 @@ var BatchHandlerTileSprite = new Class({ size: 4 }, { - name: 'inTexId' + name: 'inTexDatum' }, { name: 'inTintEffect' @@ -98,6 +111,15 @@ var BatchHandlerTileSprite = new Class({ * @param {number} tintBL - The tint color for the bottom-left corner. * @param {number} tintTR - The tint color for the top-right corner. * @param {number} tintBR - The tint color for the bottom-right corner. + * @param {object} [renderOptions] - Optional render features. + * @param {boolean} [renderOptions.multiTexturing] - Whether to use multi-texturing. + * @param {object} [renderOptions.lighting] - How to treat lighting. If this object is defined, lighting will be activated, and multi-texturing disabled. + * @param {Phaser.Renderer.WebGL.WebGLTextureWrapper} renderOptions.lighting.normalGLTexture - The normal map texture to render. + * @param {number} renderOptions.lighting.normalMapRotation - The rotation of the normal map texture. + * @param {object} [renderOptions.lighting.selfShadow] - Self-shadowing options. + * @param {boolean} renderOptions.lighting.selfShadow.enabled - Whether to use self-shadowing. + * @param {number} renderOptions.lighting.selfShadow.penumbra - Self-shadowing penumbra strength. + * @param {number} renderOptions.lighting.selfShadow.diffuseFlatThreshold - Self-shadowing texture brightness equivalent to a flat surface. */ batch: function ( currentContext, @@ -106,7 +128,8 @@ var BatchHandlerTileSprite = new Class({ texX, texY, texWidth, texHeight, u0, v0, u1, v1, u2, v2, u3, v3, tintFill, - tintTL, tintBL, tintTR, tintBR + tintTL, tintBL, tintTR, tintBR, + renderOptions ) { if (this.instanceCount === 0) @@ -114,31 +137,11 @@ var BatchHandlerTileSprite = new Class({ this.manager.setCurrentBatchNode(this, currentContext); } - // Texture + // Check render options and run the batch if they differ. + this.updateRenderOptions(currentContext, renderOptions); - // Check if the texture is already in the batch. - // This could be a very expensive operation if we're not careful. - // If we just use `batchTextures.indexOf`, a linear search, - // we can use up to 20% of a frame budget. - // Instead, we cache the texture unit index on the texture itself, - // so we can immediately tell whether it's in the batch. - // We reset this value when we flush the batch. - - var textureIndex = glTexture.batchUnit; - if (textureIndex === -1) - { - var currentBatchEntry = this.currentBatchEntry; - if (currentBatchEntry.count === this.maxTexturesPerBatch) - { - // Commit the current batch entry and start a new one. - this.pushCurrentBatchEntry(); - currentBatchEntry = this.currentBatchEntry; - } - textureIndex = currentBatchEntry.unit; - glTexture.batchUnit = textureIndex; - currentBatchEntry.texture[textureIndex] = glTexture; - currentBatchEntry.unit++; - } + // Process textures and get relevant data. + var textureDatum = this.batchTextures(glTexture, renderOptions); // Update the vertex buffer. var vertexOffset32 = this.instanceCount * this.floatsPerInstance; @@ -155,7 +158,7 @@ var BatchHandlerTileSprite = new Class({ vertexViewF32[vertexOffset32++] = texY; vertexViewF32[vertexOffset32++] = texWidth; vertexViewF32[vertexOffset32++] = texHeight; - vertexViewF32[vertexOffset32++] = textureIndex; + vertexViewF32[vertexOffset32++] = textureDatum; vertexViewF32[vertexOffset32++] = tintFill; vertexViewU32[vertexOffset32++] = tintBL; @@ -168,7 +171,7 @@ var BatchHandlerTileSprite = new Class({ vertexViewF32[vertexOffset32++] = texY; vertexViewF32[vertexOffset32++] = texWidth; vertexViewF32[vertexOffset32++] = texHeight; - vertexViewF32[vertexOffset32++] = textureIndex; + vertexViewF32[vertexOffset32++] = textureDatum; vertexViewF32[vertexOffset32++] = tintFill; vertexViewU32[vertexOffset32++] = tintTL; @@ -181,7 +184,7 @@ var BatchHandlerTileSprite = new Class({ vertexViewF32[vertexOffset32++] = texY; vertexViewF32[vertexOffset32++] = texWidth; vertexViewF32[vertexOffset32++] = texHeight; - vertexViewF32[vertexOffset32++] = textureIndex; + vertexViewF32[vertexOffset32++] = textureDatum; vertexViewF32[vertexOffset32++] = tintFill; vertexViewU32[vertexOffset32++] = tintBR; @@ -194,7 +197,7 @@ var BatchHandlerTileSprite = new Class({ vertexViewF32[vertexOffset32++] = texY; vertexViewF32[vertexOffset32++] = texWidth; vertexViewF32[vertexOffset32++] = texHeight; - vertexViewF32[vertexOffset32++] = textureIndex; + vertexViewF32[vertexOffset32++] = textureDatum; vertexViewF32[vertexOffset32++] = tintFill; vertexViewU32[vertexOffset32++] = tintTR; diff --git a/src/renderer/webgl/renderNodes/BatchHandlerTileSpriteLight.js b/src/renderer/webgl/renderNodes/BatchHandlerTileSpriteLight.js deleted file mode 100644 index 3e61dbf5d..000000000 --- a/src/renderer/webgl/renderNodes/BatchHandlerTileSpriteLight.js +++ /dev/null @@ -1,322 +0,0 @@ -/** - * @author Benjamin D. Richards - * @copyright 2013-2024 Phaser Studio Inc. - * @license {@link https://opensource.org/licenses/MIT|MIT License} - */ - -var Vector2 = require('../../../math/Vector2'); -var Class = require('../../../utils/Class'); -var LightShaderSourceFS = require('../shaders/TileSpriteLight-frag'); -var ShaderSourceVS = require('../shaders/MultiTileSprite-vert'); -var BatchHandlerQuadLight = require('./BatchHandlerQuadLight'); - -/** - * @classdesc - * This RenderNode draws Standard Batch Render Quads with a Light Shader - * in batches. - * - * The fragment shader used by this RenderNode will be compiled - * with a maximum light count defined by the renderer configuration. - * The string `%LIGHT_COUNT%` in the fragment shader source will be - * replaced with this value. - * - * @class BatchHandlerTileSpriteLight - * @memberof Phaser.Renderer.WebGL.RenderNodes - * @constructor - * @since 3.90.0 - * @extends Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuadLight - * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager} manager - The manager that owns this RenderNode. - * @param {Phaser.Types.Renderer.WebGL.RenderNodes.BatchHandlerConfig} config - The configuration object for this RenderNode. - */ -var BatchHandlerTileSpriteLight = new Class({ - Extends: BatchHandlerQuadLight, - - initialize: function BatchHandlerTileSpriteLight (manager, config) - { - BatchHandlerQuadLight.call(this, manager, config); - - this.program.setUniform('uMainSampler', 0); - this.program.setUniform('uNormSampler', 1); - - /** - * Inverse rotation matrix for normal map rotations. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTileSpriteLight#inverseRotationMatrix - * @type {Float32Array} - * @private - * @since 3.90.0 - */ - this.inverseRotationMatrix = new Float32Array([ - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - ]); - - /** - * A persistent calculation vector used when processing the lights. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTileSpriteLight#_lightVector - * @type {Phaser.Math.Vector2} - * @private - * @since 3.90.0 - */ - this._lightVector = new Vector2(); - - /** - * The rotation of the normal map texture. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTileSpriteLight#_normalMapRotation - * @type {number} - * @private - * @since 3.90.0 - */ - this._normalMapRotation = 0; - }, - - /** - * The default configuration settings for BatchHandlerTileSpriteLight. - * - * These are very similar to standard settings, - * but because the textures are always set in units 0 and 1, - * there's no need to have an attribute for the texture unit. - * While the vertex shader still reads `inTexId`, it is not used, - * and the default value of 0 is fine. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTileSpriteLight#defaultConfig - * @type {Phaser.Types.Renderer.WebGL.RenderNodes.BatchHandlerConfig} - * @since 3.90.0 - * @readonly - */ - defaultConfig: { - name: 'BatchHandlerTileSpriteLight', - verticesPerInstance: 4, - indicesPerInstance: 6, - vertexSource: ShaderSourceVS, - fragmentSource: LightShaderSourceFS, - vertexBufferLayout: { - usage: 'DYNAMIC_DRAW', - layout: [ - { - name: 'inPosition', - size: 2 - }, - { - name: 'inTexCoord', - size: 2 - }, - { - name: 'inFrame', - size: 4 - }, - { - name: 'inTintEffect' - }, - { - name: 'inTint', - size: 4, - type: 'UNSIGNED_BYTE', - normalized: true - } - ] - } - }, - - /** - * Add a new quad to the batch. - * - * For compatibility with TRIANGLE_STRIP rendering, - * the vertices are added in the order: - * - * - Top-left - * - Bottom-left - * - Top-right - * - Bottom-right - * - * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTileSpriteLight#batch - * @since 3.90.0 - * @param {Phaser.Types.Renderer.WebGL.DrawingContext} currentContext - The current drawing context. - * @param {Phaser.Renderer.WebGL.WebGLTextureWrapper} glTexture - The texture to render. - * @param {Phaser.Renderer.WebGL.WebGLTextureWrapper} normalGLTexture - The normal map texture to render. - * @param {number} normalMapRotation - The rotation of the normal map texture. - * @param {number} x0 - The x coordinate of the top-left corner. - * @param {number} y0 - The y coordinate of the top-left corner. - * @param {number} x1 - The x coordinate of the bottom-left corner. - * @param {number} y1 - The y coordinate of the bottom-left corner. - * @param {number} x2 - The x coordinate of the top-right corner. - * @param {number} y2 - The y coordinate of the top-right corner. - * @param {number} x3 - The x coordinate of the bottom-right corner. - * @param {number} y3 - The y coordinate of the bottom-right corner. - * @param {number} texX - The left u coordinate (0-1). - * @param {number} texY - The top v coordinate (0-1). - * @param {number} texWidth - The width of the texture (0-1). - * @param {number} texHeight - The height of the texture (0-1). - * @param {number} u0 - The u coordinate of the distorted top-left corner. - * @param {number} v0 - The v coordinate of the distorted top-left corner. - * @param {number} u1 - The u coordinate of the distorted bottom-left corner. - * @param {number} v1 - The v coordinate of the distorted bottom-left corner. - * @param {number} u2 - The u coordinate of the distorted top-right corner. - * @param {number} v2 - The v coordinate of the distorted top-right corner. - * @param {number} u3 - The u coordinate of the distorted bottom-right corner. - * @param {number} v3 - The v coordinate of the distorted bottom-right corner. - * @param {number} tintFill - Whether to tint the fill color. - * @param {number} tintTL - The top-left tint color. - * @param {number} tintBL - The bottom-left tint color. - * @param {number} tintTR - The top-right tint color. - * @param {number} tintBR - The bottom-right tint color. - */ - batch: function ( - currentContext, - glTexture, - normalGLTexture, - normalMapRotation, - x0, y0, - x1, y1, - x2, y2, - x3, y3, - texX, texY, - texWidth, texHeight, - u0, v0, - u1, v1, - u2, v2, - u3, v3, - tintFill, - tintTL, tintBL, tintTR, tintBR - ) - { - if (this.instanceCount === 0) - { - this.manager.setCurrentBatchNode(this, currentContext); - } - - // Texture - - var currentBatchEntry = this.currentBatchEntry; - if ( - currentBatchEntry.texture[0] !== glTexture || - currentBatchEntry.texture[1] !== normalGLTexture - ) - { - // Complete the entire batch if the texture changes. - this.run(currentContext); - } - - // Current batch entry has been redefined. - currentBatchEntry = this.currentBatchEntry; - glTexture.batchUnit = 0; - normalGLTexture.batchUnit = 1; - currentBatchEntry.texture[0] = glTexture; - currentBatchEntry.texture[1] = normalGLTexture; - currentBatchEntry.unit = 2; - - // Normal map rotation - normalMapRotation = -normalMapRotation - currentContext.camera.rotation; - if (this._normalMapRotation !== normalMapRotation) - { - // Complete the entire batch if the normal map rotation changes. - this.run(currentContext); - - this._normalMapRotation = normalMapRotation; - var inverseRotationMatrix = this.inverseRotationMatrix; - - if (normalMapRotation) - { - var c = Math.cos(normalMapRotation); - var s = Math.sin(normalMapRotation); - - inverseRotationMatrix[1] = s; - inverseRotationMatrix[3] = -s; - inverseRotationMatrix[0] = inverseRotationMatrix[4] = c; - } - else - { - inverseRotationMatrix[0] = inverseRotationMatrix[4] = 1; - inverseRotationMatrix[1] = inverseRotationMatrix[3] = 0; - } - - // This matrix will definitely be used by the next render. - this.program.setUniform('uInverseRotationMatrix', inverseRotationMatrix); - } - - // Update the vertex buffer. - var vertexOffset32 = this.instanceCount * this.floatsPerInstance; - var vertexBuffer = this.vertexBufferLayout.buffer; - var vertexViewF32 = vertexBuffer.viewF32; - var vertexViewU32 = vertexBuffer.viewU32; - - // Bottom-left - vertexViewF32[vertexOffset32++] = x1; - vertexViewF32[vertexOffset32++] = y1; - vertexViewF32[vertexOffset32++] = u1; - vertexViewF32[vertexOffset32++] = v1; - vertexViewF32[vertexOffset32++] = texX; - vertexViewF32[vertexOffset32++] = texY; - vertexViewF32[vertexOffset32++] = texWidth; - vertexViewF32[vertexOffset32++] = texHeight; - vertexViewF32[vertexOffset32++] = tintFill; - vertexViewU32[vertexOffset32++] = tintBL; - - // Top-left - vertexViewF32[vertexOffset32++] = x0; - vertexViewF32[vertexOffset32++] = y0; - vertexViewF32[vertexOffset32++] = u0; - vertexViewF32[vertexOffset32++] = v0; - vertexViewF32[vertexOffset32++] = texX; - vertexViewF32[vertexOffset32++] = texY; - vertexViewF32[vertexOffset32++] = texWidth; - vertexViewF32[vertexOffset32++] = texHeight; - vertexViewF32[vertexOffset32++] = tintFill; - vertexViewU32[vertexOffset32++] = tintTL; - - // Bottom-right - vertexViewF32[vertexOffset32++] = x3; - vertexViewF32[vertexOffset32++] = y3; - vertexViewF32[vertexOffset32++] = u3; - vertexViewF32[vertexOffset32++] = v3; - vertexViewF32[vertexOffset32++] = texX; - vertexViewF32[vertexOffset32++] = texY; - vertexViewF32[vertexOffset32++] = texWidth; - vertexViewF32[vertexOffset32++] = texHeight; - vertexViewF32[vertexOffset32++] = tintFill; - vertexViewU32[vertexOffset32++] = tintBR; - - // Top-right - vertexViewF32[vertexOffset32++] = x2; - vertexViewF32[vertexOffset32++] = y2; - vertexViewF32[vertexOffset32++] = u2; - vertexViewF32[vertexOffset32++] = v2; - vertexViewF32[vertexOffset32++] = texX; - vertexViewF32[vertexOffset32++] = texY; - vertexViewF32[vertexOffset32++] = texWidth; - vertexViewF32[vertexOffset32++] = texHeight; - vertexViewF32[vertexOffset32++] = tintFill; - vertexViewU32[vertexOffset32++] = tintTR; - - // Increment the instance count. - this.instanceCount++; - this.currentBatchEntry.count++; - - // Check whether the batch should be rendered immediately. - // This guarantees that none of the arrays are full above. - if (this.instanceCount === this.instancesPerBatch) - { - this.run(currentContext); - - // Now the batch is empty. - } - }, - - /** - * Called by the render node manager when the advised texture unit count changes. - * In `BatchHandlerTileSpriteLight`, this does nothing, because it only ever uses two texture units. - * - * As this extends `BatchHandlerQuad`, it would otherwise rebuild the shader - * program. - * - * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTileSpriteLight#updateTextureCount - * @since 3.90.0 - * @param {number} count - The new advised texture unit count. - */ - updateTextureCount: function (count) {} -}); - -module.exports = BatchHandlerTileSpriteLight; diff --git a/src/renderer/webgl/renderNodes/BatchHandlerTileSpriteLightShadow.js b/src/renderer/webgl/renderNodes/BatchHandlerTileSpriteLightShadow.js deleted file mode 100644 index feac3abb0..000000000 --- a/src/renderer/webgl/renderNodes/BatchHandlerTileSpriteLightShadow.js +++ /dev/null @@ -1,129 +0,0 @@ -/** - * @author Benjamin D. Richards - * @copyright 2013-2024 Phaser Studio Inc. - * @license {@link https://opensource.org/licenses/MIT|MIT License} - */ - -var Class = require('../../../utils/Class'); -var LightShaderSourceFS = require('../shaders/TileSpriteLightShadow-frag'); -var ShaderSourceVS = require('../shaders/MultiTileSprite-vert'); -var BatchHandlerTileSpriteLight = require('./BatchHandlerTileSpriteLight'); - -/** - * @classdesc - * The BatchHandlerTileSpriteLightShadow works like - * @see Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTileSpriteLight - * to render TileSprite GameObjects with self-shadowing lighting. - * - * @class BatchHandlerTileSpriteLightShadow - * @extends Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuadLight - * @memberof Phaser.Renderer.WebGL.RenderNodes - * @constructor - * @since 3.90.0 - * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager} manager - The manager that owns this RenderNode. - * @param {Phaser.Types.Renderer.WebGL.RenderNodes.BatchHandlerConfig} [config] - The configuration object for this handler. - */ -var BatchHandlerTileSpriteLightShadow = new Class({ - Extends: BatchHandlerTileSpriteLight, - - initialize: function BatchHandlerTileSpriteLightShadow (manager, config) - { - BatchHandlerTileSpriteLight.call(this, manager, config); - - /** - * The threshold at which the diffuse lighting will be considered flat. - * This is used to derive self-shadowing from the diffuse map. - * - * This is a brightness value in the range 0-1. - * Because art is usually not pure white, the default is 1/3, - * a darker value, which is more likely to be considered flat. - * You should adjust this value based on the art in your game. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTileSpriteLightShadow#diffuseFlatThreshold - * @type {number} - * @default 1 - * @since 3.90.0 - */ - this.diffuseFlatThreshold = 1 / 3; - - /** - * The penumbra value for the shadow. - * This smooths the edge of self-shadowing. - * A lower value will create a sharper but more jagged shadow. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTileSpriteLightShadow#penumbra - * @type {number} - * @default 0.5 - * @since 3.90.0 - */ - this.penumbra = 0.5; - }, - - /** - * The default configuration settings for BatchHandlerTileSpriteLightShadow. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTileSpriteLightShadow#defaultConfig - * @type {Phaser.Types.Renderer.WebGL.RenderNodes.BatchHandlerConfig} - * @since 3.90.0 - * @readonly - */ - defaultConfig: { - name: 'BatchHandlerTileSpriteLightShadow', - verticesPerInstance: 4, - indicesPerInstance: 6, - vertexSource: ShaderSourceVS, - fragmentSource: LightShaderSourceFS, - vertexBufferLayout: { - usage: 'DYNAMIC_DRAW', - layout: [ - { - name: 'inPosition', - size: 2 - }, - { - name: 'inTexCoord', - size: 2 - }, - { - name: 'inFrame', - size: 4 - }, - { - name: 'inTintEffect' - }, - { - name: 'inTint', - size: 4, - type: 'UNSIGNED_BYTE', - normalized: true - } - ] - } - }, - - /** - * Called at the start of the run loop. - * - * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTileSpriteLightShadow#onRunBegin - * @since 3.90.0 - * @param {Phaser.Renderer.WebGL.WebGLPipeline} drawingContext - The drawing context. - */ - onRunBegin: function (drawingContext) - { - BatchHandlerTileSpriteLight.prototype.onRunBegin.call(this, drawingContext); - - var program = this.program; - - program.setUniform( - 'uDiffuseFlatThreshold', - this.diffuseFlatThreshold * 3 - ); - - program.setUniform( - 'uPenumbra', - this.penumbra - ); - } -}); - -module.exports = BatchHandlerTileSpriteLightShadow; diff --git a/src/renderer/webgl/renderNodes/BatchHandlerTriFlat.js b/src/renderer/webgl/renderNodes/BatchHandlerTriFlat.js index 7b7ce2a1d..133e56cfe 100644 --- a/src/renderer/webgl/renderNodes/BatchHandlerTriFlat.js +++ b/src/renderer/webgl/renderNodes/BatchHandlerTriFlat.js @@ -4,9 +4,12 @@ * @license {@link https://opensource.org/licenses/MIT|MIT License} */ +var Vector2 = require('../../../math/Vector2'); var Class = require('../../../utils/Class'); +var MakeApplyLighting = require('../shaders/configs/MakeApplyLighting'); var ShaderSourceFS = require('../shaders/Flat-frag'); var ShaderSourceVS = require('../shaders/Flat-vert'); +var Utils = require('../Utils'); var BatchHandler = require('./BatchHandler'); /** @@ -48,14 +51,41 @@ var BatchHandlerTriFlat = new Class({ * @since 3.90.0 */ this.vertexCount = 0; + + /** + * A persistent calculation vector used when processing the lights. + * + * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTriFlatLight#_lightVector + * @type {Phaser.Math.Vector2} + * @private + * @since 3.90.0 + */ + this._lightVector = new Vector2(); + + /** + * The current render options to which the batch is built. + * These help define the shader. + * + * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTriFlat#renderOptions + * @type {object} + * @since 3.90.0 + */ + this.renderOptions = { + lighting: false + }; }, defaultConfig: { name: 'BatchHandlerTriFlat', verticesPerInstance: 3, indicesPerInstance: 3, + shaderName: 'FLAT', vertexSource: ShaderSourceVS, fragmentSource: ShaderSourceFS, + shaderAdditions: [ + MakeApplyLighting(true) + ], + shaderFeatures: [ 'FLAT_LIGHTING' ], indexBufferDynamic: true, vertexBufferLayout: { usage: 'DYNAMIC_DRAW', @@ -90,22 +120,79 @@ var BatchHandlerTriFlat = new Class({ }, /** - * Called at the beginning of the `run` method. + * Update the uniforms for the current shader program. * - * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTriFlat#onRunBegin + * This method is called automatically when the batch is run. + * + * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTriFlat#setupUniforms * @since 3.90.0 * @param {Phaser.Types.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context. */ - onRunBegin: function (drawingContext) + setupUniforms: function (drawingContext) { + var programManager = this.programManager; + drawingContext.renderer.setProjectionMatrix( drawingContext.width, drawingContext.height ); - this.program.setUniform( + programManager.setUniform( 'uProjectionMatrix', drawingContext.renderer.projectionMatrix.val ); + + // Lighting uniforms. + Utils.updateLightingUniforms( + this.renderOptions.lighting, + this.manager.renderer, + drawingContext, + programManager, + this._lightVector + ); + + if (this.renderOptions.lighting) + { + programManager.setUniform( + 'uResolution', + [ drawingContext.width, drawingContext.height ] + ); + } + }, + + /** + * Update the render options for the current shader program. + * If the options have changed, the batch is run to apply the changes. + * + * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTriFlat#updateRenderOptions + * @since 3.90.0 + * @param {Phaser.Types.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context. + * @param {boolean} lighting - Should this batch use lighting? + */ + updateRenderOptions: function (drawingContext, lighting) + { + var programManager = this.programManager; + var renderOptions = this.renderOptions; + var updateLighting = this.renderOptions.lighting !== lighting; + + if (updateLighting) + { + this.run(drawingContext); + } + + renderOptions.lighting = lighting; + + if (updateLighting) + { + var lightingAddition = programManager.getAddition('LIGHTING'); + if (lightingAddition) + { + lightingAddition.disable = !lighting; + if (lighting) + { + lightingAddition.additions.fragmentDefine = '#define LIGHT_COUNT ' + this.manager.renderer.config.maxLights; + } + } + } }, /** @@ -124,10 +211,16 @@ var BatchHandlerTriFlat = new Class({ this.onRunBegin(drawingContext); + var programManager = this.programManager; + var programSuite = programManager.getCurrentProgramSuite(); + var program = programSuite.program; + var vao = programSuite.vao; + + this.setupUniforms(drawingContext); + programManager.applyUniforms(program); + var indicesPerInstance = this.indicesPerInstance; var instanceCount = this.instanceCount; - var program = this.program; - var vao = this.vao; var renderer = this.manager.renderer; var vertexBuffer = this.vertexBufferLayout.buffer; var stride = this.vertexBufferLayout.layout.stride; @@ -173,14 +266,18 @@ var BatchHandlerTriFlat = new Class({ * @param {number[]} indexes - The index data. Each triangle is defined by three indices into the vertices array, so the length of this should be a multiple of 3. * @param {number[]} vertices - The vertices data. Each vertex is defined by an x-coordinate and a y-coordinate. * @param {number[]} colors - The color data. Each vertex has a color as a Uint32 value. + * @param {boolean} [lighting=false] - Should this batch use lighting? */ - batch: function (currentContext, indexes, vertices, colors) + batch: function (currentContext, indexes, vertices, colors, lighting) { if (this.instanceCount === 0) { this.manager.setCurrentBatchNode(this, currentContext); } + // Check render options and run the batch if they differ. + this.updateRenderOptions(currentContext, lighting); + var passID = 0; var instanceCompletion = 0; var instancesPerBatch = this.instancesPerBatch; @@ -189,7 +286,7 @@ var BatchHandlerTriFlat = new Class({ var stride = this.vertexBufferLayout.layout.stride; var verticesPerInstance = this.verticesPerInstance; - var indexBuffer = this.vao.indexBuffer; + var indexBuffer = this.indexBuffer; var indexView16 = indexBuffer.viewU16; var indexOffset16 = this.instanceCount * this.indicesPerInstance; diff --git a/src/renderer/webgl/renderNodes/BatchHandlerTriFlatLight.js b/src/renderer/webgl/renderNodes/BatchHandlerTriFlatLight.js deleted file mode 100644 index 48d91db4e..000000000 --- a/src/renderer/webgl/renderNodes/BatchHandlerTriFlatLight.js +++ /dev/null @@ -1,215 +0,0 @@ -/** - * @author Benjamin D. Richards - * @copyright 2013-2024 Phaser Studio Inc. - * @license {@link https://opensource.org/licenses/MIT|MIT License} - */ - -var Vector2 = require('../../../math/Vector2'); -var Class = require('../../../utils/Class'); -var LightShaderSourceFS = require('../shaders/FlatLight-frag'); -var ShaderSourceVS = require('../shaders/Flat-vert'); -var BatchHandlerTriFlat = require('./BatchHandlerTriFlat'); - -/** - * @classdesc - * This RenderNode draws vertex tinted triangles with a Light Shader - * in batches. - * - * The fragment shader used by this RenderNode will be compiled - * with a maximum light count defined by the renderer configuration. - * The string `%LIGHT_COUNT%` in the fragment shader source will be - * replaced with this value. - * - * @class BatchHandlerTriFlatLight - * @memberof Phaser.Renderer.WebGL.RenderNodes - * @constructor - * @since 3.90.0 - * @extends Phaser.Renderer.WebGL.RenderNodes.BatchHandlerQuad - * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager} manager - The manager that owns this RenderNode. - * @param {Phaser.Types.Renderer.WebGL.RenderNodes.BatchHandlerConfig} config - The configuration object for this RenderNode. - */ -var BatchHandlerTriFlatLight = new Class({ - Extends: BatchHandlerTriFlat, - - initialize: function BatchHandlerTriFlatLight (manager, config) - { - BatchHandlerTriFlat.call(this, manager, config); - - /** - * Inverse rotation matrix for normal map rotations. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTriFlatLight#inverseRotationMatrix - * @type {Float32Array} - * @private - * @since 3.90.0 - */ - this.inverseRotationMatrix = new Float32Array([ - 1, 0, 0, - 0, 1, 0, - 0, 0, 1 - ]); - - /** - * A persistent calculation vector used when processing the lights. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTriFlatLight#_lightVector - * @type {Phaser.Math.Vector2} - * @private - * @since 3.90.0 - */ - this._lightVector = new Vector2(); - - /** - * The rotation of the normal map texture. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTriFlatLight#_normalMapRotation - * @type {number} - * @private - * @since 3.90.0 - */ - this._normalMapRotation = 0; - }, - - /** - * The default configuration settings for BatchHandlerTriFlatLight. - * - * @name Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTriFlatLight#defaultConfig - * @type {Phaser.Types.Renderer.WebGL.RenderNodes.BatchHandlerConfig} - * @since 3.90.0 - * @readonly - */ - defaultConfig: { - name: 'BatchHandlerTriFlatLight', - verticesPerInstance: 3, - indicesPerInstance: 3, - vertexSource: ShaderSourceVS, - fragmentSource: LightShaderSourceFS, - vertexBufferLayout: { - usage: 'DYNAMIC_DRAW', - layout: [ - { - name: 'inPosition', - size: 2 - }, - { - name: 'inTint', - size: 4, - type: 'UNSIGNED_BYTE', - normalized: true - } - ] - } - }, - - _copyAndCompleteConfig: function (manager, config, defaultConfig) - { - var newConfig = BatchHandlerTriFlat.prototype._copyAndCompleteConfig.call(this, manager, config, defaultConfig); - - newConfig.fragmentSource = newConfig.fragmentSource.replace( - '%LIGHT_COUNT%', - manager.renderer.config.maxLights - ); - - return newConfig; - }, - - /** - * Set new dimensions for the renderer. This is called automatically when the renderer is resized. - * - * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTriFlatLight#resize - * @since 3.90.0 - * @param {number} width - The new width of the renderer. - * @param {number} height - The new height of the renderer. - */ - resize: function (width, height) - { - BatchHandlerTriFlat.prototype.resize.call(this, width, height); - - this.program.setUniform('uResolution', [ width, height ]); - }, - - /** - * Called at the start of the `run` method. - * - * @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTriFlatLight#onRunBegin - * @since 3.90.0 - * @param {Phaser.Types.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context. - */ - onRunBegin: function (drawingContext) - { - var camera = drawingContext.camera; - var cameraMatrix = camera.matrix; - var program = this.program; - var scene = camera.scene; - var lightManager = scene.sys.lights; - var lights = lightManager.getLights(camera); - var lightsCount = lights.length; - var ambientColor = lightManager.ambientColor; - var vec = this._lightVector; - var height = this.manager.renderer.height; - - BatchHandlerTriFlat.prototype.onRunBegin.call(this, drawingContext); - - program.setUniform( - 'uCamera', - [ - camera.x, - camera.y, - camera.rotation, - camera.zoom - ] - ); - program.setUniform( - 'uAmbientLightColor', - [ - ambientColor.r, - ambientColor.g, - ambientColor.b - ] - ); - program.setUniform( - 'uLightCount', - lightsCount - ); - - for (var i = 0; i < lightsCount; i++) - { - var light = lights[i].light; - var color = light.color; - - var lightName = 'uLights[' + i + '].'; - - cameraMatrix.transformPoint( - light.x - camera.scrollX * light.scrollFactorX * camera.zoom, - light.y - camera.scrollY * light.scrollFactorY * camera.zoom, - vec - ); - - program.setUniform( - lightName + 'position', - [ - vec.x, - height - vec.y - ] - ); - program.setUniform( - lightName + 'color', - [ - color.r, - color.g, - color.b - ] - ); - program.setUniform( - lightName + 'intensity', - light.intensity - ); - program.setUniform( - lightName + 'radius', - light.radius - ); - } - } -}); - -module.exports = BatchHandlerTriFlatLight; diff --git a/src/renderer/webgl/renderNodes/FillPath.js b/src/renderer/webgl/renderNodes/FillPath.js index be09688fc..b3130fcd3 100644 --- a/src/renderer/webgl/renderNodes/FillPath.js +++ b/src/renderer/webgl/renderNodes/FillPath.js @@ -46,8 +46,9 @@ var FillPath = new Class({ * @param {number} tintTR - The top-right tint color. * @param {number} tintBL - The bottom-left tint color. * @param {number} detail - The level of detail to use when filling the path. Points which are only this far apart in screen space are combined. It is ignored if the entire path is equal to or shorter than this distance. + * @param {boolean} lighting - Whether to apply lighting effects to the path. */ - run: function (drawingContext, currentMatrix, submitterNode, path, tintTL, tintTR, tintBL, detail) + run: function (drawingContext, currentMatrix, submitterNode, path, tintTL, tintTR, tintBL, detail, lighting) { this.onRunBegin(drawingContext); @@ -101,7 +102,7 @@ var FillPath = new Class({ colors[colorsIndex++] = tintTL; } - submitterNode.batch(drawingContext, polygonIndexArray, polygonCache, colors); + submitterNode.batch(drawingContext, polygonIndexArray, polygonCache, colors, lighting); } else { @@ -150,7 +151,7 @@ var FillPath = new Class({ indexedTriangles[indexedTrianglesIndex++] = index + 2; } - submitterNode.batch(drawingContext, indexedTriangles, vertices, colors); + submitterNode.batch(drawingContext, indexedTriangles, vertices, colors, lighting); } this.onRunEnd(drawingContext); diff --git a/src/renderer/webgl/renderNodes/FillRect.js b/src/renderer/webgl/renderNodes/FillRect.js index 3088e68d5..6b31eecc3 100644 --- a/src/renderer/webgl/renderNodes/FillRect.js +++ b/src/renderer/webgl/renderNodes/FillRect.js @@ -80,8 +80,9 @@ var FillRect = new Class({ * @param {number} tintTR - The top-right tint color. * @param {number} tintBL - The bottom-left tint color. * @param {number} tintBR - The bottom-right tint color. + * @param {boolean} lighting - Whether to apply lighting effects to the rectangle. */ - run: function (drawingContext, currentMatrix, submitterNode, x, y, width, height, tintTL, tintTR, tintBL, tintBR) + run: function (drawingContext, currentMatrix, submitterNode, x, y, width, height, tintTL, tintTR, tintBL, tintBR, lighting) { this.onRunBegin(drawingContext); @@ -103,7 +104,8 @@ var FillRect = new Class({ quad, [ tintTL, tintBL, tintBR, tintTR - ] + ], + lighting ); this.onRunEnd(drawingContext); diff --git a/src/renderer/webgl/renderNodes/FillTri.js b/src/renderer/webgl/renderNodes/FillTri.js index 7d9e08b7e..9170fce6d 100644 --- a/src/renderer/webgl/renderNodes/FillTri.js +++ b/src/renderer/webgl/renderNodes/FillTri.js @@ -57,8 +57,9 @@ var FillTri = new Class({ * @param {number} tintA - The tint color of the first vertex. * @param {number} tintB - The tint color of the second vertex. * @param {number} tintC - The tint color of the third vertex. + * @param {boolean} lighting - Whether to apply lighting effects to the triangle. */ - run: function (drawingContext, currentMatrix, submitterNode, xA, yA, xB, yB, xC, yC, tintA, tintB, tintC) + run: function (drawingContext, currentMatrix, submitterNode, xA, yA, xB, yB, xC, yC, tintA, tintB, tintC, lighting) { this.onRunBegin(drawingContext); @@ -79,7 +80,8 @@ var FillTri = new Class({ tintA, tintB, tintC - ] + ], + lighting ); } else @@ -99,7 +101,8 @@ var FillTri = new Class({ tintA, tintB, tintC - ] + ], + lighting ); } diff --git a/src/renderer/webgl/renderNodes/RenderNodeManager.js b/src/renderer/webgl/renderNodes/RenderNodeManager.js index 4b3d82a78..7551a31f6 100644 --- a/src/renderer/webgl/renderNodes/RenderNodeManager.js +++ b/src/renderer/webgl/renderNodes/RenderNodeManager.js @@ -21,14 +21,9 @@ var DefaultTileSpriteNodes = require('./defaults/DefaultTileSpriteNodes'); var BatchHandlerPointLight = require('./BatchHandlerPointLight'); var BatchHandlerQuad = require('./BatchHandlerQuad'); -var BatchHandlerQuadLight = require('./BatchHandlerQuadLight'); -var BatchHandlerQuadLightShadow = require('./BatchHandlerQuadLightShadow'); var BatchHandlerStrip = require('./BatchHandlerStrip'); var BatchHandlerTileSprite = require('./BatchHandlerTileSprite'); -var BatchHandlerTileSpriteLight = require('./BatchHandlerTileSpriteLight'); -var BatchHandlerTileSpriteLightShadow = require('./BatchHandlerTileSpriteLightShadow'); var BatchHandlerTriFlat = require('./BatchHandlerTriFlat'); -var BatchHandlerTriFlatLight = require('./BatchHandlerTriFlatLight'); var Camera = require('./Camera'); var DrawLine = require('./DrawLine'); var FillCamera = require('./FillCamera'); @@ -39,9 +34,7 @@ var ListCompositor = require('./ListCompositor'); var RebindContext = require('./RebindContext'); var StrokePath = require('./StrokePath'); var SubmitterQuad = require('./submitter/SubmitterQuad'); -var SubmitterQuadLight = require('./submitter/SubmitterQuadLight'); var SubmitterTileSprite = require('./submitter/SubmitterTileSprite'); -var SubmitterTileSpriteLight = require('./submitter/SubmitterTileSpriteLight'); var TexturerImage = require('./texturer/TexturerImage'); var TexturerTileSprite = require('./texturer/TexturerTileSprite'); var TransformerImage = require('./transformer/TransformerImage'); @@ -156,14 +149,9 @@ var RenderNodeManager = new Class({ this._nodeConstructors = { BatchHandlerPointLight: BatchHandlerPointLight, BatchHandlerQuad: BatchHandlerQuad, - BatchHandlerQuadLight: BatchHandlerQuadLight, - BatchHandlerQuadLightShadow: BatchHandlerQuadLightShadow, BatchHandlerStrip: BatchHandlerStrip, BatchHandlerTileSprite: BatchHandlerTileSprite, - BatchHandlerTileSpriteLight: BatchHandlerTileSpriteLight, - BatchHandlerTileSpriteLightShadow: BatchHandlerTileSpriteLightShadow, BatchHandlerTriFlat: BatchHandlerTriFlat, - BatchHandlerTriFlatLight: BatchHandlerTriFlatLight, Camera: Camera, DrawLine: DrawLine, FillCamera: FillCamera, @@ -174,9 +162,7 @@ var RenderNodeManager = new Class({ RebindContext: RebindContext, StrokePath: StrokePath, SubmitterQuad: SubmitterQuad, - SubmitterQuadLight: SubmitterQuadLight, SubmitterTileSprite: SubmitterTileSprite, - SubmitterTileSpriteLight: SubmitterTileSpriteLight, TexturerImage: TexturerImage, TexturerTileSprite: TexturerTileSprite, TransformerImage: TransformerImage, @@ -185,12 +171,6 @@ var RenderNodeManager = new Class({ YieldContext: YieldContext }; - if (game.config.selfShadow) - { - this._nodeConstructors.BatchHandlerQuadLight = BatchHandlerQuadLightShadow; - this._nodeConstructors.BatchHandlerTileSpriteLight = BatchHandlerTileSpriteLightShadow; - } - /** * The RenderNode which is currently being filled. * This is stored so that it can be completed when another type of diff --git a/src/renderer/webgl/renderNodes/StrokePath.js b/src/renderer/webgl/renderNodes/StrokePath.js index f485e842d..a6582db00 100644 --- a/src/renderer/webgl/renderNodes/StrokePath.js +++ b/src/renderer/webgl/renderNodes/StrokePath.js @@ -52,8 +52,9 @@ var StrokePath = new Class({ * @param {number} tintBL - The bottom-left tint color. * @param {number} tintBR - The bottom-right tint color. * @param {number} detail - The level of detail to use when rendering the stroke. Points which are only this far apart in screen space are combined. It is ignored if the entire path is equal to or shorter than this distance. + * @param {boolean} lighting - Whether to apply lighting effects to the stroke. */ - run: function (drawingContext, submitterNode, path, lineWidth, open, currentMatrix, tintTL, tintTR, tintBL, tintBR, detail) + run: function (drawingContext, submitterNode, path, lineWidth, open, currentMatrix, tintTL, tintTR, tintBL, tintBR, detail, lighting) { this.onRunBegin(drawingContext); @@ -188,7 +189,7 @@ var StrokePath = new Class({ } } - submitterNode.batch(drawingContext, indices, vertices, colors); + submitterNode.batch(drawingContext, indices, vertices, colors, lighting); this.onRunEnd(drawingContext); } diff --git a/src/renderer/webgl/renderNodes/config/typedefs/BatchHandlerConfig.js b/src/renderer/webgl/renderNodes/config/typedefs/BatchHandlerConfig.js index d9c7e420e..9022def81 100644 --- a/src/renderer/webgl/renderNodes/config/typedefs/BatchHandlerConfig.js +++ b/src/renderer/webgl/renderNodes/config/typedefs/BatchHandlerConfig.js @@ -14,8 +14,11 @@ * @property {number} [indicesPerInstance=6] - The number of indices per instance. This is used to populate and advance the element buffer. Default quads use 6 indices in the TRIANGLE_STRIP pattern [0, 0, 1, 2, 3, 3] to connect independent quads with degenerate topology. The minimum number is 3. * @property {number} [maxTexturesPerBatch] - The maximum number of textures per batch entry. This defaults to the maximum number of textures supported by the renderer. It is used to compile the shader program. At runtime, the manager may suggest a different number, which is interpreted by the node's `updateTextureCount` method. * @property {boolean} [indexBufferDynamic=false] - Whether the index buffer should be created as a dynamic buffer. This is useful for handlers that need to change the index data frequently. + * @property {string} [shaderName] - The base name to use for the shader program. * @property {string} [vertexSource] - The vertex shader source code. If not provided, a default quad shader will be used. - * @property {string} [fragmentSource] - The fragment shader source code. If not provided, a default quad shader will be used. The fragment shader will be compiled + * @property {string} [fragmentSource] - The fragment shader source code. If not provided, a default quad shader will be used. + * @property {{name: string, additions: object[], tags: string[]}[]} [shaderAdditions] - An array of shader additions to apply to the shader program. Each addition is an object with a `name` property and an `additions` property. The `additions` property is an object with keys that correspond to template strings in the shader source code. The values are strings that will be added to the templates. The `tags` property is an optional array of strings that describe the shader addition. + * @property {string[]} [shaderFeatures] - An array of shader features to enable in the shader program. * @property {Partial} [vertexBufferLayout] - The vertex buffer layout for the batch handler. If not provided, a default quad layout will be used. The `count` property will be determined by the `instancesPerBatch` and `verticesPerInstance` properties. The `location` and `bytes` properties of each attribute will be determined automatically during initialization. * @property {string[]} [vertexBufferLayoutRemove] - An array of attribute names to remove from the vertex buffer layout. This is useful for removing attributes that are not used by the shader program. * @property {Partial[]} [vertexBufferLayoutAdd] - An array of additional attribute layouts to add to the vertex buffer layout. This is useful for adding attributes to the default shader program. diff --git a/src/renderer/webgl/renderNodes/defaults/DefaultBitmapTextNodes.js b/src/renderer/webgl/renderNodes/defaults/DefaultBitmapTextNodes.js index ee4929115..1dd273d77 100644 --- a/src/renderer/webgl/renderNodes/defaults/DefaultBitmapTextNodes.js +++ b/src/renderer/webgl/renderNodes/defaults/DefaultBitmapTextNodes.js @@ -8,9 +8,7 @@ var Map = require('../../../../structs/Map'); var DefaultBitmapTextNodes = new Map([ [ 'Submitter', 'SubmitterQuad' ], - [ 'SubmitterLight', 'SubmitterQuadLight' ], - [ 'BatchHandler', 'BatchHandlerQuad' ], - [ 'BatchHandlerLight', 'BatchHandlerQuadLight' ] + [ 'BatchHandler', 'BatchHandlerQuad' ] ]); module.exports = DefaultBitmapTextNodes; diff --git a/src/renderer/webgl/renderNodes/defaults/DefaultBlitterNodes.js b/src/renderer/webgl/renderNodes/defaults/DefaultBlitterNodes.js index 9758b657b..1b18eece9 100644 --- a/src/renderer/webgl/renderNodes/defaults/DefaultBlitterNodes.js +++ b/src/renderer/webgl/renderNodes/defaults/DefaultBlitterNodes.js @@ -8,9 +8,7 @@ var Map = require('../../../../structs/Map'); var DefaultBlitterNodes = new Map([ [ 'Submitter', 'SubmitterQuad' ], - [ 'SubmitterLight', 'SubmitterQuadLight' ], - [ 'BatchHandler', 'BatchHandlerQuad' ], - [ 'BatchHandlerLight', 'BatchHandlerQuadLight' ] + [ 'BatchHandler', 'BatchHandlerQuad' ] ]); module.exports = DefaultBlitterNodes; diff --git a/src/renderer/webgl/renderNodes/defaults/DefaultGraphicsNodes.js b/src/renderer/webgl/renderNodes/defaults/DefaultGraphicsNodes.js index 6ef9bed0a..93c7b7d59 100644 --- a/src/renderer/webgl/renderNodes/defaults/DefaultGraphicsNodes.js +++ b/src/renderer/webgl/renderNodes/defaults/DefaultGraphicsNodes.js @@ -8,7 +8,6 @@ var Map = require('../../../../structs/Map'); var DefaultGraphicsNodes = new Map([ [ 'Submitter', 'BatchHandlerTriFlat' ], - [ 'SubmitterLight', 'BatchHandlerTriFlatLight' ], [ 'FillPath', 'FillPath' ], [ 'FillRect', 'FillRect' ], [ 'FillTri', 'FillTri' ], diff --git a/src/renderer/webgl/renderNodes/defaults/DefaultImageNodes.js b/src/renderer/webgl/renderNodes/defaults/DefaultImageNodes.js index 6d29f8935..346af7be2 100644 --- a/src/renderer/webgl/renderNodes/defaults/DefaultImageNodes.js +++ b/src/renderer/webgl/renderNodes/defaults/DefaultImageNodes.js @@ -8,9 +8,7 @@ var Map = require('../../../../structs/Map'); var DefaultImageNodes = new Map([ [ 'Submitter', 'SubmitterQuad' ], - [ 'SubmitterLight', 'SubmitterQuadLight' ], [ 'BatchHandler', 'BatchHandlerQuad' ], - [ 'BatchHandlerLight', 'BatchHandlerQuadLight' ], [ 'Transformer', 'TransformerImage' ], [ 'Texturer', 'TexturerImage' ] ]); diff --git a/src/renderer/webgl/renderNodes/defaults/DefaultParticleEmitterNodes.js b/src/renderer/webgl/renderNodes/defaults/DefaultParticleEmitterNodes.js index 4e12003f3..30a965143 100644 --- a/src/renderer/webgl/renderNodes/defaults/DefaultParticleEmitterNodes.js +++ b/src/renderer/webgl/renderNodes/defaults/DefaultParticleEmitterNodes.js @@ -8,9 +8,7 @@ var Map = require('../../../../structs/Map'); var DefaultParticleEmitterNodes = new Map([ [ 'Submitter', 'SubmitterQuad' ], - [ 'SubmitterLight', 'SubmitterQuadLight' ], - [ 'BatchHandler', 'BatchHandlerQuad' ], - [ 'BatchHandlerLight', 'BatchHandlerQuadLight' ] + [ 'BatchHandler', 'BatchHandlerQuad' ] ]); module.exports = DefaultParticleEmitterNodes; diff --git a/src/renderer/webgl/renderNodes/defaults/DefaultTileSpriteNodes.js b/src/renderer/webgl/renderNodes/defaults/DefaultTileSpriteNodes.js index 21293566f..2f8926400 100644 --- a/src/renderer/webgl/renderNodes/defaults/DefaultTileSpriteNodes.js +++ b/src/renderer/webgl/renderNodes/defaults/DefaultTileSpriteNodes.js @@ -8,9 +8,7 @@ var Map = require('../../../../structs/Map'); var DefaultTileSpriteNodes = new Map([ [ 'Submitter', 'SubmitterTileSprite' ], - [ 'SubmitterLight', 'SubmitterTileSpriteLight' ], [ 'BatchHandler', 'BatchHandlerTileSprite' ], - [ 'BatchHandlerLight', 'BatchHandlerTileSpriteLight' ], [ 'Transformer', 'TransformerTileSprite' ], [ 'Texturer', 'TexturerTileSprite' ] ]); diff --git a/src/renderer/webgl/renderNodes/defaults/DefaultTilemapLayerNodes.js b/src/renderer/webgl/renderNodes/defaults/DefaultTilemapLayerNodes.js index efcee67cf..f9b883392 100644 --- a/src/renderer/webgl/renderNodes/defaults/DefaultTilemapLayerNodes.js +++ b/src/renderer/webgl/renderNodes/defaults/DefaultTilemapLayerNodes.js @@ -8,9 +8,7 @@ var Map = require('../../../../structs/Map'); var DefaultTilemapLayerNodes = new Map([ [ 'Submitter', 'SubmitterQuad' ], - [ 'SubmitterLight', 'SubmitterQuadLight' ], [ 'BatchHandler', 'BatchHandlerQuad' ], - [ 'BatchHandlerLight', 'BatchHandlerQuadLight' ], [ 'Transformer', 'TransformerTile' ] ]); diff --git a/src/renderer/webgl/renderNodes/index.js b/src/renderer/webgl/renderNodes/index.js index bcb239599..1541307c0 100644 --- a/src/renderer/webgl/renderNodes/index.js +++ b/src/renderer/webgl/renderNodes/index.js @@ -12,13 +12,9 @@ var RenderNodes = { BatchHandler: require('./BatchHandler'), BatchHandlerPointLight: require('./BatchHandlerPointLight'), BatchHandlerQuad: require('./BatchHandlerQuad'), - BatchHandlerQuadLight: require('./BatchHandlerQuadLight'), - BatchHandlerQuadLightShadow: require('./BatchHandlerQuadLightShadow'), BatchHandlerStrip: require('./BatchHandlerStrip'), BatchHandlerTileSprite: require('./BatchHandlerTileSprite'), - BatchHandlerTileSpriteLight: require('./BatchHandlerTileSpriteLight'), BatchHandlerTriFlat: require('./BatchHandlerTriFlat'), - BatchHandlerTriFlatLight: require('./BatchHandlerTriFlatLight'), Camera: require('./Camera'), DrawLine: require('./DrawLine'), FillCamera: require('./FillCamera'), @@ -30,9 +26,7 @@ var RenderNodes = { RenderNode: require('./RenderNode'), StrokePath: require('./StrokePath'), SubmitterQuad: require('./submitter/SubmitterQuad'), - SubmitterQuadLight: require('./submitter/SubmitterQuadLight'), SubmitterTileSprite: require('./submitter/SubmitterTileSprite'), - SubmitterTileSpriteLight: require('./submitter/SubmitterTileSpriteLight'), TexturerImage: require('./texturer/TexturerImage'), TexturerTileSprite: require('./texturer/TexturerTileSprite'), TransformerImage: require('./transformer/TransformerImage'), diff --git a/src/renderer/webgl/renderNodes/submitter/SubmitterQuad.js b/src/renderer/webgl/renderNodes/submitter/SubmitterQuad.js index 1cee74605..64a1a7b12 100644 --- a/src/renderer/webgl/renderNodes/submitter/SubmitterQuad.js +++ b/src/renderer/webgl/renderNodes/submitter/SubmitterQuad.js @@ -51,6 +51,37 @@ var SubmitterQuad = new Class({ * @since 3.90.0 */ this.batchHandler = config.batchHandler; + + /** + * Persistent object reused to pass render options to the batch handler. + * + * @name Phaser.Renderer.WebGL.RenderNodes.SubmitterQuad#_renderOptions + * @type {object} + * @since 3.90.0 + * @private + */ + this._renderOptions = { + multiTexturing: true, + lighting: null + }; + + /** + * Persistent object reused to pass lighting options to the batch handler. + * + * @name Phaser.Renderer.WebGL.RenderNodes.SubmitterQuad#_lightingOptions + * @type {object} + * @since 3.90.0 + * @private + */ + this._lightingOptions = { + normalGLTexture: null, + normalMapRotation: 0, + selfShadow: { + enabled: false, + penumbra: 0, + diffuseFlatThreshold: 0 + } + }; }, /** @@ -77,6 +108,8 @@ var SubmitterQuad = new Class({ * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNode|Omit} texturerNode - The texturer node used to texture the GameObject. You may pass a texturer node or an object containing equivalent data without a `run` method. * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNode|{ quad: Float32Array }} transformerNode - The transformer node used to transform the GameObject. You may pass a transformer node or an object with a `quad` property. * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNode|Omit} [tinterNode] - The tinter node used to tint the GameObject. You may pass a tinter node or an object containing equivalent data without a `run` method. If omitted, Image-style tinting will be used. + * @param {Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper} [normalMap] - The normal map texture to use for lighting. If omitted, the normal map texture of the GameObject will be used, or the default normal map texture of the renderer. + * @param {number} [normalMapRotation] - The rotation of the normal map texture. If omitted, the rotation of the GameObject will be used. */ run: function ( drawingContext, @@ -85,7 +118,9 @@ var SubmitterQuad = new Class({ element, texturerNode, transformerNode, - tinterNode + tinterNode, + normalMap, + normalMapRotation ) { this.onRunBegin(drawingContext); @@ -129,6 +164,8 @@ var SubmitterQuad = new Class({ var u1 = uvSource.u1; var v1 = uvSource.v1; + this.setRenderOptions(gameObject, normalMap, normalMapRotation); + ( gameObject.customRenderNodes[this.batchHandler] || gameObject.defaultRenderNodes[this.batchHandler] @@ -151,10 +188,82 @@ var SubmitterQuad = new Class({ tintFill, // Tint colors in order TL, BL, TR, BR: - tintTopLeft, tintBottomLeft, tintTopRight, tintBottomRight + tintTopLeft, tintBottomLeft, tintTopRight, tintBottomRight, + + // Extra render options: + this._renderOptions ); this.onRunEnd(drawingContext); + }, + + setRenderOptions: function (gameObject, normalMap, normalMapRotation) + { + if (gameObject.lighting) + { + // Get normal map. + if (!normalMap) + { + if (gameObject.displayTexture) + { + normalMap = gameObject.displayTexture.dataSource[gameObject.displayFrame.sourceIndex]; + } + else if (gameObject.texture) + { + normalMap = gameObject.texture.dataSource[gameObject.frame.sourceIndex]; + } + else if (gameObject.tileset) + { + if (Array.isArray(gameObject.tileset)) + { + normalMap = gameObject.tileset[0].image.dataSource[0]; + } + else + { + normalMap = gameObject.tileset.image.dataSource[0]; + } + } + } + if (!normalMap) + { + normalMap = this.manager.renderer.normalTexture; + } + else + { + normalMap = normalMap.glTexture; + } + + // Get normal map rotation. + if (isNaN(normalMapRotation)) + { + normalMapRotation = gameObject.rotation; + if (gameObject.parentContainer) + { + var matrix = gameObject.getWorldTransformMatrix(this._tempMatrix, this._tempMatrix2); + normalMapRotation = matrix.rotationNormalized; + } + } + + // Get self-shadow. + var selfShadow = gameObject.selfShadow; + var selfShadowEnabled = selfShadow.enabled; + if (selfShadowEnabled === null) + { + selfShadowEnabled = gameObject.scene.game.config.selfShadow; + } + + this._lightingOptions.normalGLTexture = normalMap; + this._lightingOptions.normalMapRotation = normalMapRotation; + this._lightingOptions.selfShadow.enabled = selfShadowEnabled; + this._lightingOptions.selfShadow.penumbra = selfShadow.penumbra; + this._lightingOptions.selfShadow.diffuseFlatThreshold = selfShadow.diffuseFlatThreshold; + + this._renderOptions.lighting = this._lightingOptions; + } + else + { + this._renderOptions.lighting = null; + } } }); diff --git a/src/renderer/webgl/renderNodes/submitter/SubmitterQuadLight.js b/src/renderer/webgl/renderNodes/submitter/SubmitterQuadLight.js deleted file mode 100644 index 4ea856b31..000000000 --- a/src/renderer/webgl/renderNodes/submitter/SubmitterQuadLight.js +++ /dev/null @@ -1,199 +0,0 @@ -/** - * @author Benjamin D. Richards - * @copyright 2013-2024 Phaser Studio Inc. - * @license {@link https://opensource.org/licenses/MIT|MIT License} - */ - -var Class = require('../../../../utils/Class'); -var Merge = require('../../../../utils/object/Merge'); -var Utils = require('../../Utils.js'); -var SubmitterQuad = require('./SubmitterQuad'); - -var getTint = Utils.getTintAppendFloatAlpha; - -/** - * @classdesc - * The SubmitterQuadLight RenderNode submits data for rendering - * a single Image-like GameObject with lighting information. - * It uses a BatchHandler to render the image as part of a batch. - * - * @class SubmitterQuadLight - * @memberof Phaser.Renderer.WebGL.RenderNodes - * @constructor - * @since 3.90.0 - * @extends Phaser.Renderer.WebGL.RenderNodes.SubmitterQuad - * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager} manager - The manager that owns this RenderNode. - * @param {object} [config] - The configuration object for this RenderNode. - * @param {string} [config.name='SubmitterQuadLight'] - The name of this RenderNode. - * @param {string} [config.role='Submitter'] - The expected role of this RenderNode. - * @param {string} [config.batchHandler='BatchHandlerLight'] - The key of the default batch handler node to use for this RenderNode. This should correspond to a node which extends `BatchHandlerQuadLight`. It will be derived from the game object whenever the node runs. - */ -var SubmitterQuadLight = new Class({ - Extends: SubmitterQuad, - - initialize: function SubmitterQuadLight (manager, config) - { - config = Merge(config || {}, this.defaultConfig); - - SubmitterQuad.call(this, manager, config); - }, - - defaultConfig: { - name: 'SubmitterQuadLight', - role: 'Submitter', - batchHandler: 'BatchHandlerLight' - }, - - /** - * Submit data for rendering. - * - * @method Phaser.Renderer.WebGL.RenderNodes.SubmitterQuadLight#run - * @since 3.90.0 - * @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context. - * @param {Phaser.GameObjects.GameObject} gameObject - The GameObject being rendered. - * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - The parent matrix of the GameObject. - * @param {number} [elementIndex] - The index of the element within the game object. This is used for objects that consist of multiple quads. - * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNode|Omit} texturerNode - The texturer node used to texture the GameObject. You may pass a texturer node or an object containing equivalent data without a `run` method. - * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNode|{ quad: Float32Array }} transformerNode - The transformer node used to transform the GameObject. You may pass a transformer node or an object with a `quad` property. - * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNode|Omit} [tinterNode] - The tinter node used to tint the GameObject. You may pass a tinter node or an object containing equivalent data without a `run` method. If omitted, Image-style tinting will be used. - * @param {Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper} [normalMap] - The normal map texture to use for lighting. If omitted, the normal map texture of the GameObject will be used, or the default normal map texture of the renderer. - * @param {number} [normalMapRotation] - The rotation of the normal map texture. If omitted, the rotation of the GameObject will be used. - */ - run: function ( - drawingContext, - gameObject, - parentMatrix, - elementIndex, - texturerNode, - transformerNode, - tinterNode, - normalMap, - normalMapRotation - ) - { - var lightManager = drawingContext.camera.scene.sys.lights; - if (!lightManager || !lightManager.active) - { - // Skip rendering if the light manager is not active. - return; - } - - this.onRunBegin(drawingContext); - - var cameraAlpha = drawingContext.camera.alpha; - var tintFill, tintTopLeft, tintBottomLeft, tintTopRight, tintBottomRight; - - if (texturerNode.run) - { - texturerNode.run(drawingContext, gameObject, elementIndex); - } - if (transformerNode.run) - { - transformerNode.run(drawingContext, gameObject, parentMatrix, elementIndex, texturerNode); - } - if (tinterNode) - { - if (tinterNode.run) - { - tinterNode.run(drawingContext, gameObject, elementIndex); - } - tintFill = tinterNode.tintFill; - tintTopLeft = tinterNode.tintTopLeft; - tintBottomLeft = tinterNode.tintBottomLeft; - tintTopRight = tinterNode.tintTopRight; - tintBottomRight = tinterNode.tintBottomRight; - } - else - { - tintFill = gameObject.tintFill; - tintTopLeft = getTint(gameObject.tintTopLeft, cameraAlpha * gameObject._alphaTL); - tintBottomLeft = getTint(gameObject.tintBottomLeft, cameraAlpha * gameObject._alphaBL); - tintTopRight = getTint(gameObject.tintTopRight, cameraAlpha * gameObject._alphaTR); - tintBottomRight = getTint(gameObject.tintBottomRight, cameraAlpha * gameObject._alphaBR); - } - - var quad = transformerNode.quad; - var uvSource = texturerNode.uvSource; - var u0 = uvSource.u0; - var v0 = uvSource.v0; - var u1 = uvSource.u1; - var v1 = uvSource.v1; - - // Get normal map. - if (!normalMap) - { - if (gameObject.displayTexture) - { - normalMap = gameObject.displayTexture.dataSource[gameObject.displayFrame.sourceIndex]; - } - else if (gameObject.texture) - { - normalMap = gameObject.texture.dataSource[gameObject.frame.sourceIndex]; - } - else if (gameObject.tileset) - { - if (Array.isArray(gameObject.tileset)) - { - normalMap = gameObject.tileset[0].image.dataSource[0]; - } - else - { - normalMap = gameObject.tileset.image.dataSource[0]; - } - } - } - if (!normalMap) - { - normalMap = this.manager.renderer.normalTexture; - } - else - { - normalMap = normalMap.glTexture; - } - - // Get normal map rotation. - if (isNaN(normalMapRotation)) - { - normalMapRotation = gameObject.rotation; - if (gameObject.parentContainer) - { - var matrix = gameObject.getWorldTransformMatrix(this._tempMatrix, this._tempMatrix2); - normalMapRotation = matrix.rotationNormalized; - } - } - - // Batch the quad. - ( - gameObject.customRenderNodes[this.batchHandler] || - gameObject.defaultRenderNodes[this.batchHandler] - ).batch( - drawingContext, - - // Use `frame.source.glTexture` instead of `frame.glTexture` - // to avoid unnecessary getter function calls. - texturerNode.frame.source.glTexture, - normalMap, - - // Normal map rotation - normalMapRotation, - - // Transformed quad in order TL, BL, TR, BR: - quad[0], quad[1], - quad[2], quad[3], - quad[6], quad[7], - quad[4], quad[5], - - // Texture coordinates in X, Y, Width, Height: - u0, v0, u1 - u0, v1 - v0, - - tintFill, - - // Tint colors in order TL, BL, TR, BR: - tintTopLeft, tintBottomLeft, tintTopRight, tintBottomRight - ); - - this.onRunEnd(drawingContext); - } -}); - -module.exports = SubmitterQuadLight; diff --git a/src/renderer/webgl/renderNodes/submitter/SubmitterTileSprite.js b/src/renderer/webgl/renderNodes/submitter/SubmitterTileSprite.js index a84144c83..71d263b7d 100644 --- a/src/renderer/webgl/renderNodes/submitter/SubmitterTileSprite.js +++ b/src/renderer/webgl/renderNodes/submitter/SubmitterTileSprite.js @@ -65,6 +65,8 @@ var SubmitterTileSprite = new Class({ * @param {Phaser.Renderer.WebGL.RenderNodes.TexturerTileSprite|Omit} texturerNode - The texturer node used to texture the GameObject. You may pass a texturer node or an object containing equivalent data without a `run` method. * @param {Phaser.Renderer.WebGL.RenderNodes.TransformerTileSprite|{ quad: Float32Array }} transformerNode - The transformer node used to transform the GameObject. You may pass a transformer node or an object with a `quad` property. * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNode|Omit} [tinterNode] - The tinter node used to tint the GameObject. You may pass a tinter node or an object containing equivalent data without a `run` method. If omitted, Image-style tinting will be used. + * @param {Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper} [normalMap] - The normal map texture to use for lighting. If omitted, the normal map texture of the GameObject will be used, or the default normal map texture of the renderer. + * @param {number} [normalMapRotation] - The rotation of the normal map texture. If omitted, the rotation of the GameObject will be used. */ run: function ( drawingContext, @@ -73,7 +75,9 @@ var SubmitterTileSprite = new Class({ element, texturerNode, transformerNode, - tinterNode + tinterNode, + normalMap, + normalMapRotation ) { this.onRunBegin(drawingContext); @@ -119,6 +123,11 @@ var SubmitterTileSprite = new Class({ var v1 = uvSource.v1; var uvQuad = texturerNode.uvMatrix.quad; + this.setRenderOptions(gameObject, normalMap, normalMapRotation); + + // Normal map rotation must include the texture rotation. + this._lightingOptions.normalMapRotation += gameObject.tileRotation; + ( gameObject.customRenderNodes[this.batchHandler] || gameObject.defaultRenderNodes[this.batchHandler] @@ -147,7 +156,10 @@ var SubmitterTileSprite = new Class({ tintFill, // Tint colors in order TL, BL, TR, BR: - tintTopLeft, tintBottomLeft, tintTopRight, tintBottomRight + tintTopLeft, tintBottomLeft, tintTopRight, tintBottomRight, + + // Extra render options: + this._renderOptions ); this.onRunEnd(drawingContext); diff --git a/src/renderer/webgl/renderNodes/submitter/SubmitterTileSpriteLight.js b/src/renderer/webgl/renderNodes/submitter/SubmitterTileSpriteLight.js deleted file mode 100644 index 4b64ef88d..000000000 --- a/src/renderer/webgl/renderNodes/submitter/SubmitterTileSpriteLight.js +++ /dev/null @@ -1,206 +0,0 @@ -/** - * @author Benjamin D. Richards - * @copyright 2013-2024 Phaser Studio Inc. - * @license {@link https://opensource.org/licenses/MIT|MIT License} - */ - -var Class = require('../../../../utils/Class'); -var Merge = require('../../../../utils/object/Merge'); -var Utils = require('../../Utils.js'); -var SubmitterQuad = require('./SubmitterQuad'); - -var getTint = Utils.getTintAppendFloatAlpha; - -/** - * @classdesc - * The SubmitterTileSpriteLight RenderNode submits data for rendering - * a single TileSprite GameObject with lighting information. - * - * @class SubmitterTileSpriteLight - * @memberof Phaser.Renderer.WebGL.RenderNodes - * @constructor - * @since 3.90.0 - * @extends Phaser.Renderer.WebGL.RenderNodes.SubmitterQuad - * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNodeManager} manager - The manager that owns this RenderNode. - * @param {object} [config] - The configuration object for this RenderNode. - * @param {string} [config.name='SubmitterTileSpriteLight'] - The name of this RenderNode. - * @param {string} [config.role='Submitter'] - The expected role of this RenderNode. - * @param {string} [config.batchHandler='BatchHandlerLight'] - The key of the default batch handler node to use for this RenderNode. This should correspond to a node which extends `BatchHandlerQuadLight`. It will be derived from the game object whenever the node runs. - */ -var SubmitterTileSpriteLight = new Class({ - Extends: SubmitterQuad, - - initialize: function SubmitterTileSpriteLight (manager, config) - { - config = Merge(config || {}, this.defaultConfig); - - SubmitterQuad.call(this, manager, config); - }, - - defaultConfig: { - name: 'SubmitterTileSpriteLight', - role: 'Submitter', - batchHandler: 'BatchHandlerLight' - }, - - /** - * Submit data for rendering. - * - * @method Phaser.Renderer.WebGL.RenderNodes.SubmitterTileSpriteLight#run - * @since 3.90.0 - * @param {Phaser.Renderer.WebGL.DrawingContext} drawingContext - The current drawing context. - * @param {Phaser.GameObjects.GameObject} gameObject - The GameObject being rendered. - * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - The parent matrix of the GameObject. - * @param {number} [elementIndex] - The index of the element within the game object. This is used for objects that consist of multiple quads. - * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNode|Omit} texturerNode - The texturer node used to texture the GameObject. You may pass a texturer node or an object containing equivalent data without a `run` method. - * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNode|{ quad: Float32Array }} transformerNode - The transformer node used to transform the GameObject. You may pass a transformer node or an object with a `quad` property. - * @param {Phaser.Renderer.WebGL.RenderNodes.RenderNode|Omit} [tinterNode] - The tinter node used to tint the GameObject. You may pass a tinter node or an object containing equivalent data without a `run` method. If omitted, Image-style tinting will be used. - * @param {Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper} [normalMap] - The normal map texture to use for lighting. If omitted, the normal map texture of the GameObject will be used, or the default normal map texture of the renderer. - * @param {number} [normalMapRotation] - The rotation of the normal map texture. If omitted, the rotation of the GameObject will be used. - */ - run: function ( - drawingContext, - gameObject, - parentMatrix, - elementIndex, - texturerNode, - transformerNode, - tinterNode, - normalMap, - normalMapRotation - ) - { - var lightManager = drawingContext.camera.scene.sys.lights; - if (!lightManager || !lightManager.active) - { - // Skip rendering if the light manager is not active. - return; - } - - this.onRunBegin(drawingContext); - - var cameraAlpha = drawingContext.camera.alpha; - var tintFill, tintTopLeft, tintBottomLeft, tintTopRight, tintBottomRight; - - if (texturerNode.run) - { - texturerNode.run(drawingContext, gameObject, elementIndex); - } - if (transformerNode.run) - { - transformerNode.run(drawingContext, gameObject, parentMatrix, elementIndex, texturerNode); - } - if (tinterNode) - { - if (tinterNode.run) - { - tinterNode.run(drawingContext, gameObject, elementIndex); - } - tintFill = tinterNode.tintFill; - tintTopLeft = tinterNode.tintTopLeft; - tintBottomLeft = tinterNode.tintBottomLeft; - tintTopRight = tinterNode.tintTopRight; - tintBottomRight = tinterNode.tintBottomRight; - } - else - { - tintFill = gameObject.tintFill; - tintTopLeft = getTint(gameObject.tintTopLeft, cameraAlpha * gameObject._alphaTL); - tintBottomLeft = getTint(gameObject.tintBottomLeft, cameraAlpha * gameObject._alphaBL); - tintTopRight = getTint(gameObject.tintTopRight, cameraAlpha * gameObject._alphaTR); - tintBottomRight = getTint(gameObject.tintBottomRight, cameraAlpha * gameObject._alphaBR); - } - - var frame = texturerNode.frame; - var quad = transformerNode.quad; - var uvSource = frame; - var u0 = uvSource.u0; - var v0 = uvSource.v0; - var u1 = uvSource.u1; - var v1 = uvSource.v1; - var uvQuad = texturerNode.uvMatrix.quad; - - // Get normal map. - if (!normalMap) - { - if (gameObject.displayTexture) - { - normalMap = gameObject.displayTexture.dataSource[gameObject.displayFrame.sourceIndex]; - } - else if (gameObject.texture) - { - normalMap = gameObject.texture.dataSource[gameObject.frame.sourceIndex]; - } - else if (gameObject.tileset) - { - if (Array.isArray(gameObject.tileset)) - { - normalMap = gameObject.tileset[0].image.dataSource[0]; - } - else - { - normalMap = gameObject.tileset.image.dataSource[0]; - } - } - } - if (!normalMap) - { - normalMap = this.manager.renderer.normalTexture; - } - else - { - normalMap = normalMap.glTexture; - } - - // Get normal map rotation. - if (isNaN(normalMapRotation)) - { - normalMapRotation = gameObject.rotation; - if (gameObject.parentContainer) - { - var matrix = gameObject.getWorldTransformMatrix(this._tempMatrix, this._tempMatrix2); - normalMapRotation = matrix.rotationNormalized; - } - } - - // Batch the quad. - ( - gameObject.customRenderNodes[this.batchHandler] || - gameObject.defaultRenderNodes[this.batchHandler] - ).batch( - drawingContext, - - // Use `frame.source.glTexture` instead of `frame.glTexture` - // to avoid unnecessary getter function calls. - frame.source.glTexture, - normalMap, - - // Normal map rotation - normalMapRotation, - - // Transformed quad in order TL, BL, TR, BR: - quad[0], quad[1], - quad[2], quad[3], - quad[6], quad[7], - quad[4], quad[5], - - // Texture coordinates in X, Y, Width, Height: - u0, v0, u1 - u0, v1 - v0, - - // Dynamic UV coordinates in order TL, BL, TR, BR: - uvQuad[0], uvQuad[1], - uvQuad[2], uvQuad[3], - uvQuad[6], uvQuad[7], - uvQuad[4], uvQuad[5], - - tintFill, - - // Tint colors in order TL, BL, TR, BR: - tintTopLeft, tintBottomLeft, tintTopRight, tintBottomRight - ); - - this.onRunEnd(drawingContext); - } -}); - -module.exports = SubmitterTileSpriteLight; diff --git a/src/renderer/webgl/shaders/LightShadow-frag.js b/src/renderer/webgl/shaders/ApplyLighting-glsl.js similarity index 60% rename from src/renderer/webgl/shaders/LightShadow-frag.js rename to src/renderer/webgl/shaders/ApplyLighting-glsl.js index 4b8ad5e76..ae331f479 100644 --- a/src/renderer/webgl/shaders/LightShadow-frag.js +++ b/src/renderer/webgl/shaders/ApplyLighting-glsl.js @@ -1,6 +1,4 @@ module.exports = [ - '#define SHADER_NAME PHASER_LIGHT_FS', - 'precision mediump float;', 'struct Light', '{', ' vec3 position;', @@ -8,39 +6,31 @@ module.exports = [ ' float intensity;', ' float radius;', '};', - 'const int kMaxLights = %LIGHT_COUNT%;', + 'const int kMaxLights = LIGHT_COUNT;', 'uniform vec4 uCamera; /* x, y, rotation, zoom */', - 'uniform vec2 uResolution;', - 'uniform sampler2D uMainSampler;', 'uniform sampler2D uNormSampler;', 'uniform vec3 uAmbientLightColor;', 'uniform Light uLights[kMaxLights];', - 'uniform mat3 uInverseRotationMatrix;', 'uniform int uLightCount;', + '#ifdef FEATURE_SELFSHADOW', 'uniform float uDiffuseFlatThreshold;', 'uniform float uPenumbra;', - 'varying vec2 outTexCoord;', - 'varying float outTintEffect;', - 'varying vec4 outTint;', - 'void main ()', + '#endif', + 'vec4 applyLighting (vec4 fragColor)', '{', - ' vec3 finalColor = vec3(0.0, 0.0, 0.0);', - ' vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a);', - ' vec4 texture = texture2D(uMainSampler, outTexCoord);', - ' vec4 color = texture * texel;', - ' if (outTintEffect == 1.0)', - ' {', - ' color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a);', - ' }', - ' else if (outTintEffect == 2.0)', - ' {', - ' color = texel;', - ' }', - ' vec3 normalMap = texture2D(uNormSampler, outTexCoord).rgb;', - ' vec3 normal = normalize(uInverseRotationMatrix * vec3(normalMap * 2.0 - 1.0));', + ' vec3 finalColor = vec3(0.0);', + ' #ifdef FEATURE_FLAT_LIGHTING', + ' vec3 normal = vec3(0.0, 0.0, 1.0);', + ' #else', + ' vec2 texCoord = outTexCoord;', + ' vec3 normalMap = texture2D(uNormSampler, texCoord).rgb;', + ' vec3 normal = normalize(outInverseRotationMatrix * vec3(normalMap * 2.0 - 1.0));', + ' #endif', ' vec2 res = vec2(min(uResolution.x, uResolution.y)) * uCamera.w;', - ' vec3 unpremultipliedColor = color.rgb / color.a;', + ' #ifdef FEATURE_SELFSHADOW', + ' vec3 unpremultipliedColor = fragColor.rgb / fragColor.a;', ' float occlusionThreshold = 1.0 - ((unpremultipliedColor.r + unpremultipliedColor.g + unpremultipliedColor.b) / uDiffuseFlatThreshold);', + ' #endif', ' for (int index = 0; index < kMaxLights; ++index)', ' {', ' if (index < uLightCount)', @@ -52,12 +42,17 @@ module.exports = [ ' float diffuseFactor = max(dot(normal, lightNormal), 0.0);', ' float radius = (light.radius / res.x * uCamera.w) * uCamera.w;', ' float attenuation = clamp(1.0 - distToSurf * distToSurf / (radius * radius), 0.0, 1.0);', + ' #ifdef FEATURE_SELFSHADOW', ' float occluded = smoothstep(0.0, 1.0, (diffuseFactor - occlusionThreshold) / uPenumbra);', ' vec3 diffuse = light.color * diffuseFactor * occluded;', + ' #else', + ' vec3 diffuse = light.color * diffuseFactor;', + ' #endif', ' finalColor += (attenuation * diffuse) * light.intensity;', ' }', ' }', ' vec4 colorOutput = vec4(uAmbientLightColor + finalColor, 1.0);', - ' gl_FragColor = color * vec4(colorOutput.rgb * colorOutput.a, colorOutput.a);', + ' fragColor *= vec4(colorOutput.rgb * colorOutput.a, colorOutput.a);', + ' return fragColor;', '}', ].join('\n'); diff --git a/src/renderer/webgl/shaders/ApplyTint-glsl.js b/src/renderer/webgl/shaders/ApplyTint-glsl.js new file mode 100644 index 000000000..35e3ac34e --- /dev/null +++ b/src/renderer/webgl/shaders/ApplyTint-glsl.js @@ -0,0 +1,16 @@ +module.exports = [ + 'vec4 applyTint(vec4 texture)', + '{', + ' vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a);', + ' vec4 color = texture * texel;', + ' if (outTintEffect == 1.0)', + ' {', + ' color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a);', + ' }', + ' else if (outTintEffect == 2.0)', + ' {', + ' color = texel;', + ' }', + ' return color;', + '}', +].join('\n'); diff --git a/src/renderer/webgl/shaders/BatchQuad-frag.js b/src/renderer/webgl/shaders/BatchQuad-frag.js deleted file mode 100644 index e53349615..000000000 --- a/src/renderer/webgl/shaders/BatchQuad-frag.js +++ /dev/null @@ -1,29 +0,0 @@ -module.exports = [ - '#define SHADER_NAME PHASER_BATCH_QUAD_FS', - '#ifdef GL_FRAGMENT_PRECISION_HIGH', - 'precision highp float;', - '#else', - 'precision mediump float;', - '#endif', - 'uniform sampler2D uMainSampler[%count%];', - 'varying vec2 outTexCoord;', - 'varying float outTexId;', - 'varying float outTintEffect;', - 'varying vec4 outTint;', - 'void main ()', - '{', - ' vec4 texture;', - ' %forloop%', - ' vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a);', - ' vec4 color = texture * texel;', - ' if (outTintEffect == 1.0)', - ' {', - ' color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a);', - ' }', - ' else if (outTintEffect == 2.0)', - ' {', - ' color = texel;', - ' }', - ' gl_FragColor = color;', - '}', -].join('\n'); diff --git a/src/renderer/webgl/shaders/BatchQuad-vert.js b/src/renderer/webgl/shaders/BatchQuad-vert.js deleted file mode 100644 index 81d7da409..000000000 --- a/src/renderer/webgl/shaders/BatchQuad-vert.js +++ /dev/null @@ -1,60 +0,0 @@ -module.exports = [ - '#define SHADER_NAME PHASER_BATCH_QUAD_VS', - 'precision mediump float;', - 'uniform mat4 uProjectionMatrix;', - 'uniform int uRoundPixels;', - 'uniform vec2 uResolution;', - 'attribute vec2 inTexIdAndTintEffect;', - 'attribute vec4 inTextureBox;', - 'attribute vec4 inTintTL;', - 'attribute vec4 inTintBL;', - 'attribute vec4 inTintTR;', - 'attribute vec4 inTintBR;', - 'attribute vec4 inObjectMatrixABCD;', - 'attribute vec2 inObjectMatrixXY;', - 'attribute vec4 inWorldMatrixABCD;', - 'attribute vec2 inWorldMatrixXY;', - 'attribute vec4 inViewMatrixABCD;', - 'attribute vec2 inViewMatrixXY;', - 'attribute vec3 inPositionAndIndex;', - 'varying vec2 outTexCoord;', - 'varying float outTexId;', - 'varying float outTintEffect;', - 'varying vec4 outTint;', - 'mat4 assembleMatrix4 (vec4 abcd, vec2 xy)', - '{', - ' return mat4(abcd.xy, 0, 0, abcd.zw, 0, 0, 0, 0, 1, 0, xy.xy, 0, 1);', - '}', - 'void main ()', - '{', - ' vec2 position = inPositionAndIndex.xy;', - ' float index = inPositionAndIndex.z;', - ' mat4 objectMatrix = assembleMatrix4(inObjectMatrixABCD, inObjectMatrixXY);', - ' mat4 worldMatrix = assembleMatrix4(inWorldMatrixABCD, inWorldMatrixXY);', - ' mat4 viewMatrix = assembleMatrix4(inViewMatrixABCD, inViewMatrixXY);', - ' gl_Position = uProjectionMatrix * viewMatrix * worldMatrix * objectMatrix * vec4(position, 1.0, 1.0);', - ' if (uRoundPixels == 1)', - ' {', - ' gl_Position.xy = floor(((gl_Position.xy + 1.0) * 0.5 * uResolution) + 0.5) / uResolution * 2.0 - 1.0;', - ' }', - ' outTexCoord = position * inTextureBox.pq + inTextureBox.st;', - ' outTexId = inTexIdAndTintEffect.x;', - ' outTintEffect = inTexIdAndTintEffect.y;', - ' if (index == 0.0)', - ' {', - ' outTint = inTintTL;', - ' }', - ' else if (index == 1.0)', - ' {', - ' outTint = inTintBL;', - ' }', - ' else if (index == 2.0)', - ' {', - ' outTint = inTintTR;', - ' }', - ' else', - ' {', - ' outTint = inTintBR;', - ' }', - '}', -].join('\n'); diff --git a/src/renderer/webgl/shaders/Flat-frag.js b/src/renderer/webgl/shaders/Flat-frag.js index 508b3fab6..fc73ed515 100644 --- a/src/renderer/webgl/shaders/Flat-frag.js +++ b/src/renderer/webgl/shaders/Flat-frag.js @@ -1,13 +1,20 @@ module.exports = [ - '#define SHADER_NAME PHASER_FLAT_FS', + '#pragma phaserTemplate(shaderName)', + '#pragma phaserTemplate(features)', '#ifdef GL_FRAGMENT_PRECISION_HIGH', 'precision highp float;', '#else', 'precision mediump float;', '#endif', + '#pragma phaserTemplate(fragmentDefine)', + 'uniform vec2 uResolution;', 'varying vec4 outTint;', + '#pragma phaserTemplate(outVariables)', + '#pragma phaserTemplate(fragmentHeader)', 'void main ()', '{', - ' gl_FragColor = outTint;', + ' vec4 fragColor = outTint;', + ' #pragma phaserTemplate(fragmentProcess)', + ' gl_FragColor = fragColor;', '}', ].join('\n'); diff --git a/src/renderer/webgl/shaders/Flat-vert.js b/src/renderer/webgl/shaders/Flat-vert.js index b0b2d5119..01ebdf7ec 100644 --- a/src/renderer/webgl/shaders/Flat-vert.js +++ b/src/renderer/webgl/shaders/Flat-vert.js @@ -1,13 +1,19 @@ module.exports = [ - '#define SHADER_NAME PHASER_FLAT_VS', + '#pragma phaserTemplate(shaderName)', + '#pragma phaserTemplate(features)', 'precision mediump float;', + '#pragma phaserTemplate(vertexDefine)', 'uniform mat4 uProjectionMatrix;', + 'uniform vec2 uResolution;', 'attribute vec2 inPosition;', 'attribute vec4 inTint;', 'varying vec4 outTint;', + '#pragma phaserTemplate(outVariables)', + '#pragma phaserTemplate(vertexHeader)', 'void main ()', '{', ' gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0);', ' outTint = vec4(inTint.bgr * inTint.a, inTint.a);', + ' #pragma phaserTemplate(vertexProcess)', '}', ].join('\n'); diff --git a/src/renderer/webgl/shaders/FlatLight-frag.js b/src/renderer/webgl/shaders/FlatLight-frag.js deleted file mode 100644 index f20045be5..000000000 --- a/src/renderer/webgl/shaders/FlatLight-frag.js +++ /dev/null @@ -1,42 +0,0 @@ -module.exports = [ - '#define SHADER_NAME PHASER_LIGHT_FS', - 'precision mediump float;', - 'struct Light', - '{', - ' vec2 position;', - ' vec3 color;', - ' float intensity;', - ' float radius;', - '};', - 'const int kMaxLights = %LIGHT_COUNT%;', - 'const vec3 normal = vec3(0.0, 0.0, 1.0);', - 'uniform vec4 uCamera; /* x, y, rotation, zoom */', - 'uniform vec2 uResolution;', - 'uniform vec3 uAmbientLightColor;', - 'uniform Light uLights[kMaxLights];', - 'uniform int uLightCount;', - 'varying vec4 outTint;', - 'void main ()', - '{', - ' vec3 finalColor = vec3(0.0, 0.0, 0.0);', - ' vec4 color = outTint;', - ' vec2 res = vec2(min(uResolution.x, uResolution.y)) * uCamera.w;', - ' for (int index = 0; index < kMaxLights; ++index)', - ' {', - ' if (index < uLightCount)', - ' {', - ' Light light = uLights[index];', - ' vec3 lightDir = vec3((light.position.xy / res) - (gl_FragCoord.xy / res), 0.1);', - ' vec3 lightNormal = normalize(lightDir);', - ' float distToSurf = length(lightDir) * uCamera.w;', - ' float diffuseFactor = max(dot(normal, lightNormal), 0.0);', - ' float radius = (light.radius / res.x * uCamera.w) * uCamera.w;', - ' float attenuation = clamp(1.0 - distToSurf * distToSurf / (radius * radius), 0.0, 1.0);', - ' vec3 diffuse = light.color * diffuseFactor;', - ' finalColor += (attenuation * diffuse) * light.intensity;', - ' }', - ' }', - ' vec4 colorOutput = vec4(uAmbientLightColor + finalColor, 1.0);', - ' gl_FragColor = color * vec4(colorOutput.rgb * colorOutput.a, colorOutput.a);', - '}', -].join('\n'); diff --git a/src/renderer/webgl/shaders/GetTextureMulti-glsl.js b/src/renderer/webgl/shaders/GetTextureMulti-glsl.js new file mode 100644 index 000000000..4890d3ed2 --- /dev/null +++ b/src/renderer/webgl/shaders/GetTextureMulti-glsl.js @@ -0,0 +1,13 @@ +module.exports = [ + 'uniform sampler2D uMainSampler[TEXTURE_COUNT];', + 'vec4 getTexture ()', + '{', + ' float texId = outTexDatum;', + ' #pragma phaserTemplate(texIdProcess)', + ' vec2 texCoord = outTexCoord;', + ' #pragma phaserTemplate(texCoordProcess)', + ' vec4 texture = vec4(0.0);', + ' #pragma phaserTemplate(texSamplerProcess)', + ' return texture;', + '}', +].join('\n'); diff --git a/src/renderer/webgl/shaders/GetTextureSingle-glsl.js b/src/renderer/webgl/shaders/GetTextureSingle-glsl.js new file mode 100644 index 000000000..27c86b647 --- /dev/null +++ b/src/renderer/webgl/shaders/GetTextureSingle-glsl.js @@ -0,0 +1,9 @@ +module.exports = [ + 'uniform sampler2D uMainSampler;', + 'vec4 getTexture ()', + '{', + ' vec2 texCoord = outTexCoord;', + ' #pragma phaserTemplate(texCoordProcess)', + ' return texture2D(uMainSampler, texCoord);', + '}', +].join('\n'); diff --git a/src/renderer/webgl/shaders/Multi-frag.js b/src/renderer/webgl/shaders/Multi-frag.js index 957ba74c5..ecf934254 100644 --- a/src/renderer/webgl/shaders/Multi-frag.js +++ b/src/renderer/webgl/shaders/Multi-frag.js @@ -1,29 +1,22 @@ module.exports = [ - '#define SHADER_NAME PHASER_MULTI_FS', + '#pragma phaserTemplate(shaderName)', + '#pragma phaserTemplate(features)', '#ifdef GL_FRAGMENT_PRECISION_HIGH', 'precision highp float;', '#else', 'precision mediump float;', '#endif', - 'uniform sampler2D uMainSampler[%count%];', + '#pragma phaserTemplate(fragmentDefine)', + 'uniform vec2 uResolution;', 'varying vec2 outTexCoord;', - 'varying float outTexId;', + 'varying float outTexDatum;', 'varying float outTintEffect;', 'varying vec4 outTint;', + '#pragma phaserTemplate(outVariables)', + '#pragma phaserTemplate(fragmentHeader)', 'void main ()', '{', - ' vec4 texture;', - ' %forloop%', - ' vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a);', - ' vec4 color = texture * texel;', - ' if (outTintEffect == 1.0)', - ' {', - ' color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a);', - ' }', - ' else if (outTintEffect == 2.0)', - ' {', - ' color = texel;', - ' }', - ' gl_FragColor = color;', + ' #pragma phaserTemplate(fragmentProcess)', + ' gl_FragColor = fragColor;', '}', ].join('\n'); diff --git a/src/renderer/webgl/shaders/Multi-vert.js b/src/renderer/webgl/shaders/Multi-vert.js index b93c996f0..8e7687ab1 100644 --- a/src/renderer/webgl/shaders/Multi-vert.js +++ b/src/renderer/webgl/shaders/Multi-vert.js @@ -1,18 +1,26 @@ module.exports = [ - '#define SHADER_NAME PHASER_MULTI_VS', + '#pragma phaserTemplate(shaderName)', + '#pragma phaserTemplate(features)', + '#ifdef GL_FRAGMENT_PRECISION_HIGH', + 'precision highp float;', + '#else', 'precision mediump float;', + '#endif', + '#pragma phaserTemplate(vertexDefine)', 'uniform mat4 uProjectionMatrix;', 'uniform int uRoundPixels;', 'uniform vec2 uResolution;', 'attribute vec2 inPosition;', 'attribute vec2 inTexCoord;', - 'attribute float inTexId;', + 'attribute float inTexDatum;', 'attribute float inTintEffect;', 'attribute vec4 inTint;', 'varying vec2 outTexCoord;', - 'varying float outTexId;', + 'varying float outTexDatum;', 'varying float outTintEffect;', 'varying vec4 outTint;', + '#pragma phaserTemplate(outVariables)', + '#pragma phaserTemplate(vertexHeader)', 'void main ()', '{', ' gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0);', @@ -21,8 +29,9 @@ module.exports = [ ' gl_Position.xy = floor(((gl_Position.xy + 1.0) * 0.5 * uResolution) + 0.5) / uResolution * 2.0 - 1.0;', ' }', ' outTexCoord = inTexCoord;', - ' outTexId = inTexId;', + ' outTexDatum = inTexDatum;', ' outTint = inTint;', ' outTintEffect = inTintEffect;', + ' #pragma phaserTemplate(vertexProcess)', '}', ].join('\n'); diff --git a/src/renderer/webgl/shaders/MultiTileSprite-frag.js b/src/renderer/webgl/shaders/MultiTileSprite-frag.js deleted file mode 100644 index 6b9c7562b..000000000 --- a/src/renderer/webgl/shaders/MultiTileSprite-frag.js +++ /dev/null @@ -1,32 +0,0 @@ -module.exports = [ - '#define SHADER_NAME PHASER_MULTI_TILE_SPRITE_FS', - '#ifdef GL_FRAGMENT_PRECISION_HIGH', - 'precision highp float;', - '#else', - 'precision mediump float;', - '#endif', - 'uniform sampler2D uMainSampler[%count%];', - 'varying vec2 outTexCoord;', - 'varying vec4 outFrame;', - 'varying float outTexId;', - 'varying float outTintEffect;', - 'varying vec4 outTint;', - 'void main ()', - '{', - ' vec4 texture;', - ' vec2 texCoord = mod(outTexCoord, 1.0) * outFrame.zw + outFrame.xy;', - ' %forloop%', - ' %texCoordName=texCoord%', - ' vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a);', - ' vec4 color = texture * texel;', - ' if (outTintEffect == 1.0)', - ' {', - ' color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a);', - ' }', - ' else if (outTintEffect == 2.0)', - ' {', - ' color = texel;', - ' }', - ' gl_FragColor = color;', - '}', -].join('\n'); diff --git a/src/renderer/webgl/shaders/MultiTileSprite-vert.js b/src/renderer/webgl/shaders/MultiTileSprite-vert.js deleted file mode 100644 index ea3f9b96a..000000000 --- a/src/renderer/webgl/shaders/MultiTileSprite-vert.js +++ /dev/null @@ -1,31 +0,0 @@ -module.exports = [ - '#define SHADER_NAME PHASER_MULTI_TILE_SPRITE_VS', - 'precision mediump float;', - 'uniform mat4 uProjectionMatrix;', - 'uniform int uRoundPixels;', - 'uniform vec2 uResolution;', - 'attribute vec2 inPosition;', - 'attribute vec2 inTexCoord;', - 'attribute vec4 inFrame;', - 'attribute float inTexId;', - 'attribute float inTintEffect;', - 'attribute vec4 inTint;', - 'varying vec2 outTexCoord;', - 'varying vec4 outFrame;', - 'varying float outTexId;', - 'varying float outTintEffect;', - 'varying vec4 outTint;', - 'void main ()', - '{', - ' gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0);', - ' if (uRoundPixels == 1)', - ' {', - ' gl_Position.xy = floor(((gl_Position.xy + 1.0) * 0.5 * uResolution) + 0.5) / uResolution * 2.0 - 1.0;', - ' }', - ' outTexCoord = inTexCoord;', - ' outTexId = inTexId;', - ' outTint = inTint;', - ' outTintEffect = inTintEffect;', - ' outFrame = inFrame;', - '}', -].join('\n'); diff --git a/src/renderer/webgl/shaders/PointLight-frag.js b/src/renderer/webgl/shaders/PointLight-frag.js index 38996eef2..bba4d26e1 100644 --- a/src/renderer/webgl/shaders/PointLight-frag.js +++ b/src/renderer/webgl/shaders/PointLight-frag.js @@ -1,12 +1,16 @@ module.exports = [ - '#define SHADER_NAME PHASER_POINTLIGHT_FS', + '#pragma phaserTemplate(shaderName)', + '#pragma phaserTemplate(features)', 'precision mediump float;', + '#pragma phaserTemplate(fragmentDefine)', 'uniform vec2 uResolution;', 'uniform float uCameraZoom;', 'varying vec4 lightPosition;', 'varying vec4 lightColor;', 'varying float lightRadius;', 'varying float lightAttenuation;', + '#pragma phaserTemplate(outVariables)', + '#pragma phaserTemplate(fragmentHeader)', 'void main ()', '{', ' vec2 center = (lightPosition.xy + 1.0) * (uResolution.xy * 0.5);', @@ -14,6 +18,7 @@ module.exports = [ ' float radius = 1.0 - distToSurf / (lightRadius * uCameraZoom);', ' float intensity = smoothstep(0.0, 1.0, radius * lightAttenuation);', ' vec4 color = vec4(intensity, intensity, intensity, 0.0) * lightColor;', + ' #pragma phaserTemplate(fragmentProcess)', ' gl_FragColor = vec4(color.rgb * lightColor.a, color.a);', '}', ].join('\n'); diff --git a/src/renderer/webgl/shaders/PointLight-vert.js b/src/renderer/webgl/shaders/PointLight-vert.js index 185860dd9..3f224f69d 100644 --- a/src/renderer/webgl/shaders/PointLight-vert.js +++ b/src/renderer/webgl/shaders/PointLight-vert.js @@ -1,6 +1,8 @@ module.exports = [ - '#define SHADER_NAME PHASER_POINTLIGHT_VS', + '#pragma phaserTemplate(shaderName)', + '#pragma phaserTemplate(features)', 'precision mediump float;', + '#pragma phaserTemplate(vertexDefine)', 'uniform mat4 uProjectionMatrix;', 'attribute vec2 inPosition;', 'attribute vec2 inLightPosition;', @@ -11,6 +13,8 @@ module.exports = [ 'varying vec4 lightColor;', 'varying float lightRadius;', 'varying float lightAttenuation;', + '#pragma phaserTemplate(outVariables)', + '#pragma phaserTemplate(vertexHeader)', 'void main ()', '{', ' lightColor = inLightColor;', @@ -18,5 +22,6 @@ module.exports = [ ' lightAttenuation = inLightAttenuation;', ' lightPosition = uProjectionMatrix * vec4(inLightPosition, 1.0, 1.0);', ' gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0);', + ' #pragma phaserTemplate(vertexProcess)', '}', ].join('\n'); diff --git a/src/renderer/webgl/shaders/TileSpriteLight-frag.js b/src/renderer/webgl/shaders/TileSpriteLight-frag.js deleted file mode 100644 index 510cef674..000000000 --- a/src/renderer/webgl/shaders/TileSpriteLight-frag.js +++ /dev/null @@ -1,60 +0,0 @@ -module.exports = [ - '#define SHADER_NAME PHASER_TILE_SPRITE_LIGHT_FS', - 'precision mediump float;', - 'struct Light', - '{', - ' vec3 position;', - ' vec3 color;', - ' float intensity;', - ' float radius;', - '};', - 'const int kMaxLights = %LIGHT_COUNT%;', - 'uniform vec4 uCamera; /* x, y, rotation, zoom */', - 'uniform vec2 uResolution;', - 'uniform sampler2D uMainSampler;', - 'uniform sampler2D uNormSampler;', - 'uniform vec3 uAmbientLightColor;', - 'uniform Light uLights[kMaxLights];', - 'uniform mat3 uInverseRotationMatrix;', - 'uniform int uLightCount;', - 'varying vec2 outTexCoord;', - 'varying vec4 outFrame;', - 'varying float outTintEffect;', - 'varying vec4 outTint;', - 'void main ()', - '{', - ' vec3 finalColor = vec3(0.0, 0.0, 0.0);', - ' vec2 texCoord = mod(outTexCoord, 1.0) * outFrame.zw + outFrame.xy;', - ' vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a);', - ' vec4 texture = texture2D(uMainSampler, texCoord);', - ' vec4 color = texture * texel;', - ' if (outTintEffect == 1.0)', - ' {', - ' color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a);', - ' }', - ' else if (outTintEffect == 2.0)', - ' {', - ' color = texel;', - ' }', - ' vec3 normalMap = texture2D(uNormSampler, texCoord).rgb;', - ' vec3 normal = normalize(uInverseRotationMatrix * vec3(normalMap * 2.0 - 1.0));', - ' vec2 res = vec2(min(uResolution.x, uResolution.y)) * uCamera.w;', - ' for (int index = 0; index < kMaxLights; ++index)', - ' {', - ' if (index < uLightCount)', - ' {', - ' Light light = uLights[index];', - ' vec3 lightDir = vec3((light.position.xy / res) - (gl_FragCoord.xy / res), light.position.z / res.x);', - ' vec3 lightNormal = normalize(lightDir);', - ' float distToSurf = length(lightDir) * uCamera.w;', - ' float diffuseFactor = max(dot(normal, lightNormal), 0.0);', - ' float radius = (light.radius / res.x * uCamera.w) * uCamera.w;', - ' float attenuation = clamp(1.0 - distToSurf * distToSurf / (radius * radius), 0.0, 1.0);', - ' vec3 diffuse = light.color * diffuseFactor;', - ' finalColor += (attenuation * diffuse) * light.intensity;', - ' }', - ' }', - ' vec4 colorOutput = vec4(uAmbientLightColor + finalColor, 1.0);', - ' gl_FragColor = color * vec4(colorOutput.rgb * colorOutput.a, colorOutput.a);', - '}', -].join('\n'); diff --git a/src/renderer/webgl/shaders/TileSpriteLightShadow-frag.js b/src/renderer/webgl/shaders/TileSpriteLightShadow-frag.js deleted file mode 100644 index fa3cffb0c..000000000 --- a/src/renderer/webgl/shaders/TileSpriteLightShadow-frag.js +++ /dev/null @@ -1,65 +0,0 @@ -module.exports = [ - '#define SHADER_NAME PHASER_TILE_SPRITE_LIGHT_FS', - 'precision mediump float;', - 'struct Light', - '{', - ' vec3 position;', - ' vec3 color;', - ' float intensity;', - ' float radius;', - '};', - 'const int kMaxLights = %LIGHT_COUNT%;', - 'uniform vec4 uCamera; /* x, y, rotation, zoom */', - 'uniform vec2 uResolution;', - 'uniform sampler2D uMainSampler;', - 'uniform sampler2D uNormSampler;', - 'uniform vec3 uAmbientLightColor;', - 'uniform Light uLights[kMaxLights];', - 'uniform mat3 uInverseRotationMatrix;', - 'uniform int uLightCount;', - 'uniform float uDiffuseFlatThreshold;', - 'uniform float uPenumbra;', - 'varying vec2 outTexCoord;', - 'varying vec4 outFrame;', - 'varying float outTintEffect;', - 'varying vec4 outTint;', - 'void main ()', - '{', - ' vec3 finalColor = vec3(0.0, 0.0, 0.0);', - ' vec2 texCoord = mod(outTexCoord, 1.0) * outFrame.zw + outFrame.xy;', - ' vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a);', - ' vec4 texture = texture2D(uMainSampler, texCoord);', - ' vec4 color = texture * texel;', - ' if (outTintEffect == 1.0)', - ' {', - ' color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a);', - ' }', - ' else if (outTintEffect == 2.0)', - ' {', - ' color = texel;', - ' }', - ' vec3 normalMap = texture2D(uNormSampler, texCoord).rgb;', - ' vec3 normal = normalize(uInverseRotationMatrix * vec3(normalMap * 2.0 - 1.0));', - ' vec2 res = vec2(min(uResolution.x, uResolution.y)) * uCamera.w;', - ' vec3 unpremultipliedColor = color.rgb / color.a;', - ' float occlusionThreshold = 1.0 - ((unpremultipliedColor.r + unpremultipliedColor.g + unpremultipliedColor.b) / uDiffuseFlatThreshold);', - ' for (int index = 0; index < kMaxLights; ++index)', - ' {', - ' if (index < uLightCount)', - ' {', - ' Light light = uLights[index];', - ' vec3 lightDir = vec3((light.position.xy / res) - (gl_FragCoord.xy / res), light.position.z / res.x);', - ' vec3 lightNormal = normalize(lightDir);', - ' float distToSurf = length(lightDir) * uCamera.w;', - ' float diffuseFactor = max(dot(normal, lightNormal), 0.0);', - ' float radius = (light.radius / res.x * uCamera.w) * uCamera.w;', - ' float attenuation = clamp(1.0 - distToSurf * distToSurf / (radius * radius), 0.0, 1.0);', - ' float occluded = smoothstep(0.0, 1.0, (diffuseFactor - occlusionThreshold) / uPenumbra);', - ' vec3 diffuse = light.color * diffuseFactor * occluded;', - ' finalColor += (attenuation * diffuse) * light.intensity;', - ' }', - ' }', - ' vec4 colorOutput = vec4(uAmbientLightColor + finalColor, 1.0);', - ' gl_FragColor = color * vec4(colorOutput.rgb * colorOutput.a, colorOutput.a);', - '}', -].join('\n'); diff --git a/src/renderer/webgl/shaders/configs/MakeApplyLighting.js b/src/renderer/webgl/shaders/configs/MakeApplyLighting.js new file mode 100644 index 000000000..9104c9e94 --- /dev/null +++ b/src/renderer/webgl/shaders/configs/MakeApplyLighting.js @@ -0,0 +1,50 @@ +/** + * @author Benjamin D. Richards + * @copyright 2013-2024 Phaser Studio Inc. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +var ApplyLighting = require('../ApplyLighting-glsl'); + +var inverseRotation = [ + '', + '#ifndef FEATURE_FLAT_LIGHTING', + 'float inverseRotation = -rotation - uCamera.z;', + 'float irSine = sin(inverseRotation);', + 'float irCosine = cos(inverseRotation);', + 'outInverseRotationMatrix = mat3(', + ' irCosine, irSine, 0.0,', + ' -irSine, irCosine, 0.0,', + ' 0.0, 0.0, 1.0', + ');', + '#endif' +].join('\n '); + +/** + * Return a ShaderAdditionConfig for applying lighting to a texture. + * + * The `rotation` variable must be available in the vertex renderer. + * + * @function Phaser.Renderer.WebGL.Shaders.MakeApplyLighting + * @since 3.90.0 + * @param {boolean} [disable=false] - Whether to disable the shader addition on creation. + * @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration. + */ +var MakeApplyLighting = function (disable) +{ + return { + name: 'LIGHTING', + additions: { + vertexHeader: 'uniform vec4 uCamera;', + vertexProcess: inverseRotation, + outVariables: 'varying mat3 outInverseRotationMatrix;', + fragmentDefine: '#define LIGHT_COUNT 1', + fragmentHeader: ApplyLighting, + fragmentProcess: 'fragColor = applyLighting(fragColor);' + }, + tags: ['LIGHTING'], + disable: !!disable + }; +}; + +module.exports = MakeApplyLighting; diff --git a/src/renderer/webgl/shaders/configs/MakeApplyTint.js b/src/renderer/webgl/shaders/configs/MakeApplyTint.js new file mode 100644 index 000000000..204c830c9 --- /dev/null +++ b/src/renderer/webgl/shaders/configs/MakeApplyTint.js @@ -0,0 +1,30 @@ +/** + * @author Benjamin D. Richards + * @copyright 2013-2024 Phaser Studio Inc. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +var ApplyTint = require('../ApplyTint-glsl'); + +/** + * Return a ShaderAdditionConfig for applying a tint to a texture. + * + * @function Phaser.Renderer.WebGL.Shaders.MakeApplyTint + * @since 3.90.0 + * @param {boolean} [disable=false] - Whether to disable the shader addition on creation. + * @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration. + */ +var MakeApplyTint = function (disable) +{ + return { + name: 'TINT', + additions: { + fragmentHeader: ApplyTint, + fragmentProcess: 'fragColor = applyTint(fragColor);' + }, + tags: ['TINT'], + disable: !!disable + }; +}; + +module.exports = MakeApplyTint; diff --git a/src/renderer/webgl/shaders/configs/MakeGetTexture.js b/src/renderer/webgl/shaders/configs/MakeGetTexture.js new file mode 100644 index 000000000..84e147c21 --- /dev/null +++ b/src/renderer/webgl/shaders/configs/MakeGetTexture.js @@ -0,0 +1,74 @@ +/** + * @author Benjamin D. Richards + * @copyright 2013-2024 Phaser Studio Inc. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +var GetTextureSingle = require('../GetTextureSingle-glsl'); +var GetTextureMulti = require('../GetTextureMulti-glsl'); + +/** + * Return a ShaderAdditionConfig for getting a texture from a sampler2D. + * This maker can return a single texture shader addition + * or a multi-texture shader addition, depending on the maxTextures parameter. + * The addition will change its name based on the number of textures supported, + * so it is tagged with 'TEXTURE' for quick identification at runtime. + * + * @function Phaser.Renderer.WebGL.Shaders.MakeGetTexture + * @since 3.90.0 + * @param {number} maxTextures - The maximum number of textures to support. + * @param {boolean} [disable=false] - Whether to disable the shader addition on creation. + * @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration. + */ +var MakeGetTexture = function (maxTextures, disable) +{ + var fragmentProcess = 'vec4 fragColor = getTexture();'; + + if (maxTextures === 1) + { + return { + name: '1TEXTURE', + additions: { + fragmentHeader: GetTextureSingle, + fragmentProcess: fragmentProcess + }, + tags: [ 'TEXTURE' ], + disable: !!disable + }; + } + + var src = ''; + + for (var i = 0; i < maxTextures; i++) + { + if (i > 0) + { + src += '\n\telse '; + } + + if (i < maxTextures - 1) + { + src += 'if (texId < ' + i + '.5)'; + } + + src += '\n\t{'; + src += '\n\t\ttexture = texture2D(uMainSampler[' + i + '], texCoord);'; + src += '\n\t}'; + } + + return { + name: maxTextures + 'TEXTURES', + additions: { + fragmentDefine: '#define TEXTURE_COUNT ' + maxTextures, + fragmentHeader: GetTextureMulti.replace( + '#pragma phaserTemplate(texSamplerProcess)', + src + ), + fragmentProcess: fragmentProcess + }, + tags: [ 'TEXTURE' ], + disable: !!disable + }; +}; + +module.exports = MakeGetTexture; diff --git a/src/renderer/webgl/shaders/configs/MakeRotationDatum.js b/src/renderer/webgl/shaders/configs/MakeRotationDatum.js new file mode 100644 index 000000000..6cc92af1e --- /dev/null +++ b/src/renderer/webgl/shaders/configs/MakeRotationDatum.js @@ -0,0 +1,28 @@ +/** + * @author Benjamin D. Richards + * @copyright 2013-2024 Phaser Studio Inc. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Return a ShaderAdditionConfig for deriving rotation from `inTexDatum`. + * This is useful for shaders that need to know their orientation. + * + * @function Phaser.Renderer.WebGL.Shaders.MakeRotationDatum + * @since 3.90.0 + * @param {boolean} [disable=false] - Whether to disable the shader addition on creation. + * @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration. + */ +var MakeRotationDatum = function (disable) +{ + return { + name: 'RotDatum', + additions: { + vertexProcess: 'float rotation = inTexDatum;' + }, + tags: ['LIGHTING'], + disable: !!disable + }; +}; + +module.exports = MakeRotationDatum; diff --git a/src/renderer/webgl/shaders/configs/MakeTileSpriteWrap.js b/src/renderer/webgl/shaders/configs/MakeTileSpriteWrap.js new file mode 100644 index 000000000..eaf490e41 --- /dev/null +++ b/src/renderer/webgl/shaders/configs/MakeTileSpriteWrap.js @@ -0,0 +1,31 @@ +/** + * @author Benjamin D. Richards + * @copyright 2013-2024 Phaser Studio Inc. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * Returns a ShaderAdditionConfig for wrapping a TileSprite texture. + * This makes the texture repeat within the bounds of the TileSprite frame - + * it's what makes a TileSprite work. + * + * @function Phaser.Renderer.WebGL.Shaders.MakeTileSpriteWrap + * @since 3.90.0 + * @param {boolean} [disable=false] - Whether to disable the shader addition on creation. + * @returns {Phaser.Types.Renderer.WebGL.ShaderAdditionConfig} The shader addition configuration. + */ +var MakeTileSpriteWrap = function (disable) +{ + return { + name: 'TileSpriteWrap', + additions: { + vertexHeader: 'attribute vec4 inFrame;', + outVariables: 'varying vec4 outFrame;', + vertexProcess: 'outFrame = inFrame;', + texCoordProcess: '// Wrap texture coordinate into the UV space of the texture frame.\ntexCoord = mod(texCoord, 1.0) * outFrame.zw + outFrame.xy;' + }, + disable: !!disable + }; +}; + +module.exports = MakeTileSpriteWrap; diff --git a/src/renderer/webgl/shaders/index.js b/src/renderer/webgl/shaders/index.js index 0e321f5dc..3d0656360 100644 --- a/src/renderer/webgl/shaders/index.js +++ b/src/renderer/webgl/shaders/index.js @@ -11,8 +11,8 @@ module.exports = { AddBlendFrag: require('./AddBlend-frag.js'), - BatchQuadFrag: require('./BatchQuad-frag.js'), - BatchQuadVert: require('./BatchQuad-vert.js'), + ApplyLighting: require('./ApplyLighting-glsl.js'), + ApplyTint: require('./ApplyTint-glsl.js'), BitmapMaskFrag: require('./BitmapMask-frag.js'), BitmapMaskVert: require('./BitmapMask-vert.js'), ColorMatrixFrag: require('./ColorMatrix-frag.js'), @@ -34,9 +34,9 @@ module.exports = { FXWipeFrag: require('./FXWipe-frag.js'), FlatFrag: require('./Flat-frag.js'), FlatVert: require('./Flat-vert.js'), - FlatLightFrag: require('./FlatLight-frag.js'), + GetTextureMulti: require('./GetTextureMulti-glsl.js'), + GetTextureSingle: require('./GetTextureSingle-glsl.js'), LightFrag: require('./Light-frag.js'), - LightShadowFrag: require('./LightShadow-frag.js'), LinearBlendFrag: require('./LinearBlend-frag.js'), MeshFrag: require('./Mesh-frag.js'), MeshVert: require('./Mesh-vert.js'), @@ -44,15 +44,13 @@ module.exports = { MobileVert: require('./Mobile-vert.js'), MultiFrag: require('./Multi-frag.js'), MultiVert: require('./Multi-vert.js'), - MultiTileSpriteFrag: require('./MultiTileSprite-frag.js'), - MultiTileSpriteVert: require('./MultiTileSprite-vert.js'), PointLightFrag: require('./PointLight-frag.js'), PointLightVert: require('./PointLight-vert.js'), PostFXFrag: require('./PostFX-frag.js'), QuadVert: require('./Quad-vert.js'), SingleFrag: require('./Single-frag.js'), SingleVert: require('./Single-vert.js'), - TileSpriteLightFrag: require('./TileSpriteLight-frag.js'), - TileSpriteLightShadowFrag: require('./TileSpriteLightShadow-frag.js') + SpriteLayerFrag: require('./SpriteLayer-frag.js'), + SpriteLayerVert: require('./SpriteLayer-vert.js') }; diff --git a/src/renderer/webgl/shaders/src/LightShadow.frag b/src/renderer/webgl/shaders/src/ApplyLighting.glsl similarity index 58% rename from src/renderer/webgl/shaders/src/LightShadow.frag rename to src/renderer/webgl/shaders/src/ApplyLighting.glsl index 637a893a7..1fc2e9d24 100644 --- a/src/renderer/webgl/shaders/src/LightShadow.frag +++ b/src/renderer/webgl/shaders/src/ApplyLighting.glsl @@ -1,7 +1,3 @@ -#define SHADER_NAME PHASER_LIGHT_FS - -precision mediump float; - struct Light { vec3 position; @@ -10,49 +6,35 @@ struct Light float radius; }; -const int kMaxLights = %LIGHT_COUNT%; +// #define LIGHT_COUNT N +const int kMaxLights = LIGHT_COUNT; uniform vec4 uCamera; /* x, y, rotation, zoom */ -uniform vec2 uResolution; -uniform sampler2D uMainSampler; uniform sampler2D uNormSampler; uniform vec3 uAmbientLightColor; uniform Light uLights[kMaxLights]; -uniform mat3 uInverseRotationMatrix; uniform int uLightCount; +#ifdef FEATURE_SELFSHADOW uniform float uDiffuseFlatThreshold; uniform float uPenumbra; +#endif -varying vec2 outTexCoord; -varying float outTintEffect; -varying vec4 outTint; - -void main () +vec4 applyLighting (vec4 fragColor) { - vec3 finalColor = vec3(0.0, 0.0, 0.0); - - vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a); - vec4 texture = texture2D(uMainSampler, outTexCoord); - // Multiply texture tint - vec4 color = texture * texel; - - if (outTintEffect == 1.0) - { - // Solid color + texture alpha - color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a); - } - else if (outTintEffect == 2.0) - { - // Solid color, no texture - color = texel; - } - - vec3 normalMap = texture2D(uNormSampler, outTexCoord).rgb; - vec3 normal = normalize(uInverseRotationMatrix * vec3(normalMap * 2.0 - 1.0)); + vec3 finalColor = vec3(0.0); + #ifdef FEATURE_FLAT_LIGHTING + vec3 normal = vec3(0.0, 0.0, 1.0); + #else + vec2 texCoord = outTexCoord; + vec3 normalMap = texture2D(uNormSampler, texCoord).rgb; + vec3 normal = normalize(outInverseRotationMatrix * vec3(normalMap * 2.0 - 1.0)); + #endif vec2 res = vec2(min(uResolution.x, uResolution.y)) * uCamera.w; - vec3 unpremultipliedColor = color.rgb / color.a; + #ifdef FEATURE_SELFSHADOW + vec3 unpremultipliedColor = fragColor.rgb / fragColor.a; float occlusionThreshold = 1.0 - ((unpremultipliedColor.r + unpremultipliedColor.g + unpremultipliedColor.b) / uDiffuseFlatThreshold); + #endif for (int index = 0; index < kMaxLights; ++index) { @@ -65,13 +47,19 @@ void main () float diffuseFactor = max(dot(normal, lightNormal), 0.0); float radius = (light.radius / res.x * uCamera.w) * uCamera.w; float attenuation = clamp(1.0 - distToSurf * distToSurf / (radius * radius), 0.0, 1.0); + #ifdef FEATURE_SELFSHADOW float occluded = smoothstep(0.0, 1.0, (diffuseFactor - occlusionThreshold) / uPenumbra); vec3 diffuse = light.color * diffuseFactor * occluded; + #else + vec3 diffuse = light.color * diffuseFactor; + #endif finalColor += (attenuation * diffuse) * light.intensity; } } vec4 colorOutput = vec4(uAmbientLightColor + finalColor, 1.0); - gl_FragColor = color * vec4(colorOutput.rgb * colorOutput.a, colorOutput.a); + fragColor *= vec4(colorOutput.rgb * colorOutput.a, colorOutput.a); + + return fragColor; } diff --git a/src/renderer/webgl/shaders/src/BatchQuad.frag b/src/renderer/webgl/shaders/src/ApplyTint.glsl similarity index 52% rename from src/renderer/webgl/shaders/src/BatchQuad.frag rename to src/renderer/webgl/shaders/src/ApplyTint.glsl index 5913ea03b..570192286 100644 --- a/src/renderer/webgl/shaders/src/BatchQuad.frag +++ b/src/renderer/webgl/shaders/src/ApplyTint.glsl @@ -1,24 +1,5 @@ -#define SHADER_NAME PHASER_BATCH_QUAD_FS - -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif - -uniform sampler2D uMainSampler[%count%]; - -varying vec2 outTexCoord; -varying float outTexId; -varying float outTintEffect; -varying vec4 outTint; - -void main () +vec4 applyTint(vec4 texture) { - vec4 texture; - - %forloop% - vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a); // Multiply texture tint @@ -35,5 +16,5 @@ void main () color = texel; } - gl_FragColor = color; + return color; } diff --git a/src/renderer/webgl/shaders/src/BatchQuad.vert b/src/renderer/webgl/shaders/src/BatchQuad.vert deleted file mode 100644 index 8e172d437..000000000 --- a/src/renderer/webgl/shaders/src/BatchQuad.vert +++ /dev/null @@ -1,70 +0,0 @@ -#define SHADER_NAME PHASER_BATCH_QUAD_VS - -precision mediump float; - -uniform mat4 uProjectionMatrix; -uniform int uRoundPixels; -uniform vec2 uResolution; - -attribute vec2 inTexIdAndTintEffect; -attribute vec4 inTextureBox; -attribute vec4 inTintTL; -attribute vec4 inTintBL; -attribute vec4 inTintTR; -attribute vec4 inTintBR; -attribute vec4 inObjectMatrixABCD; -attribute vec2 inObjectMatrixXY; -attribute vec4 inWorldMatrixABCD; -attribute vec2 inWorldMatrixXY; -attribute vec4 inViewMatrixABCD; -attribute vec2 inViewMatrixXY; -attribute vec3 inPositionAndIndex; - -varying vec2 outTexCoord; -varying float outTexId; -varying float outTintEffect; -varying vec4 outTint; - -mat4 assembleMatrix4 (vec4 abcd, vec2 xy) -{ - return mat4(abcd.xy, 0, 0, abcd.zw, 0, 0, 0, 0, 1, 0, xy.xy, 0, 1); -} - -void main () -{ - vec2 position = inPositionAndIndex.xy; - float index = inPositionAndIndex.z; - - mat4 objectMatrix = assembleMatrix4(inObjectMatrixABCD, inObjectMatrixXY); - mat4 worldMatrix = assembleMatrix4(inWorldMatrixABCD, inWorldMatrixXY); - mat4 viewMatrix = assembleMatrix4(inViewMatrixABCD, inViewMatrixXY); - - gl_Position = uProjectionMatrix * viewMatrix * worldMatrix * objectMatrix * vec4(position, 1.0, 1.0); - - if (uRoundPixels == 1) - { - gl_Position.xy = floor(((gl_Position.xy + 1.0) * 0.5 * uResolution) + 0.5) / uResolution * 2.0 - 1.0; - } - - outTexCoord = position * inTextureBox.pq + inTextureBox.st; - outTexId = inTexIdAndTintEffect.x; - outTintEffect = inTexIdAndTintEffect.y; - - // Which corner are we? - if (index == 0.0) - { - outTint = inTintTL; - } - else if (index == 1.0) - { - outTint = inTintBL; - } - else if (index == 2.0) - { - outTint = inTintTR; - } - else - { - outTint = inTintBR; - } -} diff --git a/src/renderer/webgl/shaders/src/Flat.frag b/src/renderer/webgl/shaders/src/Flat.frag index eba107030..107db0eea 100644 --- a/src/renderer/webgl/shaders/src/Flat.frag +++ b/src/renderer/webgl/shaders/src/Flat.frag @@ -1,4 +1,6 @@ -#define SHADER_NAME PHASER_FLAT_FS +#pragma phaserTemplate(shaderName) + +#pragma phaserTemplate(features) #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; @@ -6,9 +8,21 @@ precision highp float; precision mediump float; #endif +#pragma phaserTemplate(fragmentDefine) + +uniform vec2 uResolution; + varying vec4 outTint; +#pragma phaserTemplate(outVariables) + +#pragma phaserTemplate(fragmentHeader) + void main () { - gl_FragColor = outTint; + vec4 fragColor = outTint; + + #pragma phaserTemplate(fragmentProcess) + + gl_FragColor = fragColor; } diff --git a/src/renderer/webgl/shaders/src/Flat.vert b/src/renderer/webgl/shaders/src/Flat.vert index ef8650c6a..38ff6bb0f 100644 --- a/src/renderer/webgl/shaders/src/Flat.vert +++ b/src/renderer/webgl/shaders/src/Flat.vert @@ -1,17 +1,28 @@ -#define SHADER_NAME PHASER_FLAT_VS +#pragma phaserTemplate(shaderName) + +#pragma phaserTemplate(features) precision mediump float; +#pragma phaserTemplate(vertexDefine) + uniform mat4 uProjectionMatrix; +uniform vec2 uResolution; attribute vec2 inPosition; attribute vec4 inTint; varying vec4 outTint; +#pragma phaserTemplate(outVariables) + +#pragma phaserTemplate(vertexHeader) + void main () { gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0); outTint = vec4(inTint.bgr * inTint.a, inTint.a); + + #pragma phaserTemplate(vertexProcess) } diff --git a/src/renderer/webgl/shaders/src/FlatLight.frag b/src/renderer/webgl/shaders/src/FlatLight.frag deleted file mode 100644 index ad20532b3..000000000 --- a/src/renderer/webgl/shaders/src/FlatLight.frag +++ /dev/null @@ -1,53 +0,0 @@ -#define SHADER_NAME PHASER_LIGHT_FS - -precision mediump float; - -struct Light -{ - vec2 position; - vec3 color; - float intensity; - float radius; -}; - -const int kMaxLights = %LIGHT_COUNT%; - -// Constant normal for flat lighting -const vec3 normal = vec3(0.0, 0.0, 1.0); - -uniform vec4 uCamera; /* x, y, rotation, zoom */ -uniform vec2 uResolution; -uniform vec3 uAmbientLightColor; -uniform Light uLights[kMaxLights]; -uniform int uLightCount; - -varying vec4 outTint; - -void main () -{ - vec3 finalColor = vec3(0.0, 0.0, 0.0); - - vec4 color = outTint; - - vec2 res = vec2(min(uResolution.x, uResolution.y)) * uCamera.w; - - for (int index = 0; index < kMaxLights; ++index) - { - if (index < uLightCount) - { - Light light = uLights[index]; - vec3 lightDir = vec3((light.position.xy / res) - (gl_FragCoord.xy / res), 0.1); - vec3 lightNormal = normalize(lightDir); - float distToSurf = length(lightDir) * uCamera.w; - float diffuseFactor = max(dot(normal, lightNormal), 0.0); - float radius = (light.radius / res.x * uCamera.w) * uCamera.w; - float attenuation = clamp(1.0 - distToSurf * distToSurf / (radius * radius), 0.0, 1.0); - vec3 diffuse = light.color * diffuseFactor; - finalColor += (attenuation * diffuse) * light.intensity; - } - } - - vec4 colorOutput = vec4(uAmbientLightColor + finalColor, 1.0); - - gl_FragColor = color * vec4(colorOutput.rgb * colorOutput.a, colorOutput.a); -} diff --git a/src/renderer/webgl/shaders/src/GetTextureMulti.glsl b/src/renderer/webgl/shaders/src/GetTextureMulti.glsl new file mode 100644 index 000000000..38a39327b --- /dev/null +++ b/src/renderer/webgl/shaders/src/GetTextureMulti.glsl @@ -0,0 +1,15 @@ +uniform sampler2D uMainSampler[TEXTURE_COUNT]; + +vec4 getTexture () +{ + float texId = outTexDatum; + #pragma phaserTemplate(texIdProcess) + vec2 texCoord = outTexCoord; + #pragma phaserTemplate(texCoordProcess) + + vec4 texture = vec4(0.0); + + #pragma phaserTemplate(texSamplerProcess) + + return texture; +} diff --git a/src/renderer/webgl/shaders/src/GetTextureSingle.glsl b/src/renderer/webgl/shaders/src/GetTextureSingle.glsl new file mode 100644 index 000000000..0cd73249e --- /dev/null +++ b/src/renderer/webgl/shaders/src/GetTextureSingle.glsl @@ -0,0 +1,9 @@ +uniform sampler2D uMainSampler; + +vec4 getTexture () +{ + vec2 texCoord = outTexCoord; + #pragma phaserTemplate(texCoordProcess) + + return texture2D(uMainSampler, texCoord); +} diff --git a/src/renderer/webgl/shaders/src/Multi.frag b/src/renderer/webgl/shaders/src/Multi.frag index 0eaea9363..0d9c03bb3 100644 --- a/src/renderer/webgl/shaders/src/Multi.frag +++ b/src/renderer/webgl/shaders/src/Multi.frag @@ -1,4 +1,6 @@ -#define SHADER_NAME PHASER_MULTI_FS +#pragma phaserTemplate(shaderName) + +#pragma phaserTemplate(features) #ifdef GL_FRAGMENT_PRECISION_HIGH precision highp float; @@ -6,34 +8,23 @@ precision highp float; precision mediump float; #endif -uniform sampler2D uMainSampler[%count%]; +#pragma phaserTemplate(fragmentDefine) + +uniform vec2 uResolution; varying vec2 outTexCoord; -varying float outTexId; +varying float outTexDatum; varying float outTintEffect; varying vec4 outTint; +#pragma phaserTemplate(outVariables) + +#pragma phaserTemplate(fragmentHeader) + void main () { - vec4 texture; + #pragma phaserTemplate(fragmentProcess) - %forloop% - - vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a); - - // Multiply texture tint - vec4 color = texture * texel; - - if (outTintEffect == 1.0) - { - // Solid color + texture alpha - color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a); - } - else if (outTintEffect == 2.0) - { - // Solid color, no texture - color = texel; - } - - gl_FragColor = color; + // The fragment process must insert `fragColor`, e.g. `vec4 fragColor = getTexture();`. + gl_FragColor = fragColor; } diff --git a/src/renderer/webgl/shaders/src/Multi.vert b/src/renderer/webgl/shaders/src/Multi.vert index 2038d757b..786b79e5f 100644 --- a/src/renderer/webgl/shaders/src/Multi.vert +++ b/src/renderer/webgl/shaders/src/Multi.vert @@ -1,6 +1,14 @@ -#define SHADER_NAME PHASER_MULTI_VS +#pragma phaserTemplate(shaderName) +#pragma phaserTemplate(features) + +#ifdef GL_FRAGMENT_PRECISION_HIGH +precision highp float; +#else precision mediump float; +#endif + +#pragma phaserTemplate(vertexDefine) uniform mat4 uProjectionMatrix; uniform int uRoundPixels; @@ -8,15 +16,19 @@ uniform vec2 uResolution; attribute vec2 inPosition; attribute vec2 inTexCoord; -attribute float inTexId; +attribute float inTexDatum; attribute float inTintEffect; attribute vec4 inTint; varying vec2 outTexCoord; -varying float outTexId; +varying float outTexDatum; varying float outTintEffect; varying vec4 outTint; +#pragma phaserTemplate(outVariables) + +#pragma phaserTemplate(vertexHeader) + void main () { gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0); @@ -27,7 +39,9 @@ void main () } outTexCoord = inTexCoord; - outTexId = inTexId; + outTexDatum = inTexDatum; outTint = inTint; outTintEffect = inTintEffect; + + #pragma phaserTemplate(vertexProcess) } diff --git a/src/renderer/webgl/shaders/src/MultiTileSprite.frag b/src/renderer/webgl/shaders/src/MultiTileSprite.frag deleted file mode 100644 index a101e316d..000000000 --- a/src/renderer/webgl/shaders/src/MultiTileSprite.frag +++ /dev/null @@ -1,45 +0,0 @@ -#define SHADER_NAME PHASER_MULTI_TILE_SPRITE_FS - -#ifdef GL_FRAGMENT_PRECISION_HIGH -precision highp float; -#else -precision mediump float; -#endif - -uniform sampler2D uMainSampler[%count%]; - -varying vec2 outTexCoord; -varying vec4 outFrame; -varying float outTexId; -varying float outTintEffect; -varying vec4 outTint; - -void main () -{ - vec4 texture; - - // Wrap texture coordinate into the UV space of the texture frame. - vec2 texCoord = mod(outTexCoord, 1.0) * outFrame.zw + outFrame.xy; - - %forloop% - - %texCoordName=texCoord% - - vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a); - - // Multiply texture tint - vec4 color = texture * texel; - - if (outTintEffect == 1.0) - { - // Solid color + texture alpha - color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a); - } - else if (outTintEffect == 2.0) - { - // Solid color, no texture - color = texel; - } - - gl_FragColor = color; -} diff --git a/src/renderer/webgl/shaders/src/MultiTileSprite.vert b/src/renderer/webgl/shaders/src/MultiTileSprite.vert deleted file mode 100644 index ed09d0da2..000000000 --- a/src/renderer/webgl/shaders/src/MultiTileSprite.vert +++ /dev/null @@ -1,36 +0,0 @@ -#define SHADER_NAME PHASER_MULTI_TILE_SPRITE_VS - -precision mediump float; - -uniform mat4 uProjectionMatrix; -uniform int uRoundPixels; -uniform vec2 uResolution; - -attribute vec2 inPosition; -attribute vec2 inTexCoord; -attribute vec4 inFrame; -attribute float inTexId; -attribute float inTintEffect; -attribute vec4 inTint; - -varying vec2 outTexCoord; -varying vec4 outFrame; -varying float outTexId; -varying float outTintEffect; -varying vec4 outTint; - -void main () -{ - gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0); - - if (uRoundPixels == 1) - { - gl_Position.xy = floor(((gl_Position.xy + 1.0) * 0.5 * uResolution) + 0.5) / uResolution * 2.0 - 1.0; - } - - outTexCoord = inTexCoord; - outTexId = inTexId; - outTint = inTint; - outTintEffect = inTintEffect; - outFrame = inFrame; -} diff --git a/src/renderer/webgl/shaders/src/PointLight.frag b/src/renderer/webgl/shaders/src/PointLight.frag index 39bd3f82f..3d31d417a 100644 --- a/src/renderer/webgl/shaders/src/PointLight.frag +++ b/src/renderer/webgl/shaders/src/PointLight.frag @@ -1,7 +1,11 @@ -#define SHADER_NAME PHASER_POINTLIGHT_FS +#pragma phaserTemplate(shaderName) + +#pragma phaserTemplate(features) precision mediump float; +#pragma phaserTemplate(fragmentDefine) + uniform vec2 uResolution; uniform float uCameraZoom; @@ -10,6 +14,10 @@ varying vec4 lightColor; varying float lightRadius; varying float lightAttenuation; +#pragma phaserTemplate(outVariables) + +#pragma phaserTemplate(fragmentHeader) + void main () { vec2 center = (lightPosition.xy + 1.0) * (uResolution.xy * 0.5); @@ -22,5 +30,7 @@ void main () vec4 color = vec4(intensity, intensity, intensity, 0.0) * lightColor; + #pragma phaserTemplate(fragmentProcess) + gl_FragColor = vec4(color.rgb * lightColor.a, color.a); } diff --git a/src/renderer/webgl/shaders/src/PointLight.vert b/src/renderer/webgl/shaders/src/PointLight.vert index d75818cbc..046219b88 100644 --- a/src/renderer/webgl/shaders/src/PointLight.vert +++ b/src/renderer/webgl/shaders/src/PointLight.vert @@ -1,7 +1,11 @@ -#define SHADER_NAME PHASER_POINTLIGHT_VS +#pragma phaserTemplate(shaderName) + +#pragma phaserTemplate(features) precision mediump float; +#pragma phaserTemplate(vertexDefine) + uniform mat4 uProjectionMatrix; attribute vec2 inPosition; @@ -15,6 +19,10 @@ varying vec4 lightColor; varying float lightRadius; varying float lightAttenuation; +#pragma phaserTemplate(outVariables) + +#pragma phaserTemplate(vertexHeader) + void main () { lightColor = inLightColor; @@ -23,4 +31,6 @@ void main () lightPosition = uProjectionMatrix * vec4(inLightPosition, 1.0, 1.0); gl_Position = uProjectionMatrix * vec4(inPosition, 1.0, 1.0); + + #pragma phaserTemplate(vertexProcess) } diff --git a/src/renderer/webgl/shaders/src/TileSpriteLight.frag b/src/renderer/webgl/shaders/src/TileSpriteLight.frag deleted file mode 100644 index c75653c5f..000000000 --- a/src/renderer/webgl/shaders/src/TileSpriteLight.frag +++ /dev/null @@ -1,75 +0,0 @@ -#define SHADER_NAME PHASER_TILE_SPRITE_LIGHT_FS - -precision mediump float; - -struct Light -{ - vec3 position; - vec3 color; - float intensity; - float radius; -}; - -const int kMaxLights = %LIGHT_COUNT%; - -uniform vec4 uCamera; /* x, y, rotation, zoom */ -uniform vec2 uResolution; -uniform sampler2D uMainSampler; -uniform sampler2D uNormSampler; -uniform vec3 uAmbientLightColor; -uniform Light uLights[kMaxLights]; -uniform mat3 uInverseRotationMatrix; -uniform int uLightCount; - -varying vec2 outTexCoord; -varying vec4 outFrame; -varying float outTintEffect; -varying vec4 outTint; - -void main () -{ - vec3 finalColor = vec3(0.0, 0.0, 0.0); - - // Wrap texture coordinate into the UV space of the texture frame. - vec2 texCoord = mod(outTexCoord, 1.0) * outFrame.zw + outFrame.xy; - - vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a); - vec4 texture = texture2D(uMainSampler, texCoord); - // Multiply texture tint - vec4 color = texture * texel; - - if (outTintEffect == 1.0) - { - // Solid color + texture alpha - color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a); - } - else if (outTintEffect == 2.0) - { - // Solid color, no texture - color = texel; - } - - vec3 normalMap = texture2D(uNormSampler, texCoord).rgb; - vec3 normal = normalize(uInverseRotationMatrix * vec3(normalMap * 2.0 - 1.0)); - vec2 res = vec2(min(uResolution.x, uResolution.y)) * uCamera.w; - - for (int index = 0; index < kMaxLights; ++index) - { - if (index < uLightCount) - { - Light light = uLights[index]; - vec3 lightDir = vec3((light.position.xy / res) - (gl_FragCoord.xy / res), light.position.z / res.x); - vec3 lightNormal = normalize(lightDir); - float distToSurf = length(lightDir) * uCamera.w; - float diffuseFactor = max(dot(normal, lightNormal), 0.0); - float radius = (light.radius / res.x * uCamera.w) * uCamera.w; - float attenuation = clamp(1.0 - distToSurf * distToSurf / (radius * radius), 0.0, 1.0); - vec3 diffuse = light.color * diffuseFactor; - finalColor += (attenuation * diffuse) * light.intensity; - } - } - - vec4 colorOutput = vec4(uAmbientLightColor + finalColor, 1.0); - - gl_FragColor = color * vec4(colorOutput.rgb * colorOutput.a, colorOutput.a); -} diff --git a/src/renderer/webgl/shaders/src/TileSpriteLightShadow.frag b/src/renderer/webgl/shaders/src/TileSpriteLightShadow.frag deleted file mode 100644 index 00fc32ee8..000000000 --- a/src/renderer/webgl/shaders/src/TileSpriteLightShadow.frag +++ /dev/null @@ -1,81 +0,0 @@ -#define SHADER_NAME PHASER_TILE_SPRITE_LIGHT_FS - -precision mediump float; - -struct Light -{ - vec3 position; - vec3 color; - float intensity; - float radius; -}; - -const int kMaxLights = %LIGHT_COUNT%; - -uniform vec4 uCamera; /* x, y, rotation, zoom */ -uniform vec2 uResolution; -uniform sampler2D uMainSampler; -uniform sampler2D uNormSampler; -uniform vec3 uAmbientLightColor; -uniform Light uLights[kMaxLights]; -uniform mat3 uInverseRotationMatrix; -uniform int uLightCount; -uniform float uDiffuseFlatThreshold; -uniform float uPenumbra; - -varying vec2 outTexCoord; -varying vec4 outFrame; -varying float outTintEffect; -varying vec4 outTint; - -void main () -{ - vec3 finalColor = vec3(0.0, 0.0, 0.0); - - // Wrap texture coordinate into the UV space of the texture frame. - vec2 texCoord = mod(outTexCoord, 1.0) * outFrame.zw + outFrame.xy; - - vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a); - vec4 texture = texture2D(uMainSampler, texCoord); - // Multiply texture tint - vec4 color = texture * texel; - - if (outTintEffect == 1.0) - { - // Solid color + texture alpha - color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a); - } - else if (outTintEffect == 2.0) - { - // Solid color, no texture - color = texel; - } - - vec3 normalMap = texture2D(uNormSampler, texCoord).rgb; - vec3 normal = normalize(uInverseRotationMatrix * vec3(normalMap * 2.0 - 1.0)); - vec2 res = vec2(min(uResolution.x, uResolution.y)) * uCamera.w; - - vec3 unpremultipliedColor = color.rgb / color.a; - float occlusionThreshold = 1.0 - ((unpremultipliedColor.r + unpremultipliedColor.g + unpremultipliedColor.b) / uDiffuseFlatThreshold); - - for (int index = 0; index < kMaxLights; ++index) - { - if (index < uLightCount) - { - Light light = uLights[index]; - vec3 lightDir = vec3((light.position.xy / res) - (gl_FragCoord.xy / res), light.position.z / res.x); - vec3 lightNormal = normalize(lightDir); - float distToSurf = length(lightDir) * uCamera.w; - float diffuseFactor = max(dot(normal, lightNormal), 0.0); - float radius = (light.radius / res.x * uCamera.w) * uCamera.w; - float attenuation = clamp(1.0 - distToSurf * distToSurf / (radius * radius), 0.0, 1.0); - float occluded = smoothstep(0.0, 1.0, (diffuseFactor - occlusionThreshold) / uPenumbra); - vec3 diffuse = light.color * diffuseFactor * occluded; - finalColor += (attenuation * diffuse) * light.intensity; - } - } - - vec4 colorOutput = vec4(uAmbientLightColor + finalColor, 1.0); - - gl_FragColor = color * vec4(colorOutput.rgb * colorOutput.a, colorOutput.a); -} diff --git a/src/renderer/webgl/typedefs/ShaderAdditionConfig.js b/src/renderer/webgl/typedefs/ShaderAdditionConfig.js new file mode 100644 index 000000000..365056456 --- /dev/null +++ b/src/renderer/webgl/typedefs/ShaderAdditionConfig.js @@ -0,0 +1,25 @@ +/** + * @author Benjamin D. Richards + * @copyright 2013-2024 Phaser Studio Inc. + * @license {@link https://opensource.org/licenses/MIT|MIT License} + */ + +/** + * A ShaderAdditionConfig defines an addition to be made to a shader program. + * It consists of a name, a set of shader additions, and optional tags. + * + * The name is used as a key to identify the shader addition. + * It is used as part of a unique identifier for a shader program. + * + * The shader additions are key-value pairs of strings, + * where the key is the template to which the value is added. + * This is found in the base shader program source code as + * `#pragma phaserTemplate(key)`. + * + * @typedef {object} Phaser.Types.Renderer.WebGL.ShaderAdditionConfig + * @since 3.90.0 + * @property {string} name - The name of the shader addition, used as a key. + * @property {object} additions - The shader additions to apply. Each addition is a key-value pair of strings, where the key is the template to which the value is added. Keys are applied to both vertex and fragment shader code, if the template exists in both. + * @property {string[]} [tags] - Optional tags used to describe the shader addition. + * @property {boolean} [disable] - Whether to ignore this addition while compiling a shader. + */ diff --git a/src/renderer/webgl/wrappers/WebGLProgramWrapper.js b/src/renderer/webgl/wrappers/WebGLProgramWrapper.js index 2db8cc8b4..317aee476 100644 --- a/src/renderer/webgl/wrappers/WebGLProgramWrapper.js +++ b/src/renderer/webgl/wrappers/WebGLProgramWrapper.js @@ -233,13 +233,16 @@ var WebGLProgramWrapper = new Class({ { if (!gl.getShaderParameter(vs, gl.COMPILE_STATUS)) { + console.log(this.vertexSource); throw new Error('Vertex ' + failed + gl.getShaderInfoLog(vs)); } if (!gl.getShaderParameter(fs, gl.COMPILE_STATUS)) { + console.log(this.fragmentSource); throw new Error('Fragment ' + failed + gl.getShaderInfoLog(fs)); } + console.log(this.vertexSource, this.fragmentSource); throw new Error('Link Shader failed:' + gl.getProgramInfoLog(program)); }