Add lighting and self-shadowing to TileSprite.

This commit is contained in:
Ben Richards 2024-07-12 17:39:08 +12:00
parent cf7e46eff7
commit de036b999f
16 changed files with 1007 additions and 4 deletions

View file

@ -49,6 +49,7 @@ var _FLAG = 8; // 1000
* @extends Phaser.GameObjects.Components.Depth
* @extends Phaser.GameObjects.Components.Flip
* @extends Phaser.GameObjects.Components.GetBounds
* @extends Phaser.GameObjects.Components.Lighting
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.Origin
* @extends Phaser.GameObjects.Components.PostPipeline
@ -79,6 +80,7 @@ var TileSprite = new Class({
Components.Depth,
Components.Flip,
Components.GetBounds,
Components.Lighting,
Components.Mask,
Components.Origin,
Components.PostPipeline,

View file

@ -83,7 +83,7 @@ var BatchHandlerQuadLightShadow = new Class({
* @readonly
*/
defaultConfig: {
name: 'BatchHandlerQuadLight',
name: 'BatchHandlerQuadLightShadow',
verticesPerInstance: 4,
indicesPerInstance: 6,
vertexSource: ShaderSourceVS,

View file

@ -71,6 +71,33 @@ var BatchHandlerTileSprite = new Class({
*
* @method Phaser.Renderer.WebGL.RenderNodes.BatchHandlerTileSprite#batch
* @since 3.90.0
* @param {Phaser.Renderer.WebGL.DrawingContext} currentContext - The current drawing context.
* @param {Phaser.Renderer.WebGL.WebGLTextureWrapper} glTexture - The texture to render.
* @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 tint color for the top-left corner.
* @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.
*/
batch: function (
currentContext,

View file

@ -0,0 +1,322 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @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
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 rot = -normalMapRotation;
var c = Math.cos(rot);
var s = Math.sin(rot);
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;

View file

@ -0,0 +1,129 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @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;

View file

@ -25,6 +25,8 @@ 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');
@ -39,6 +41,7 @@ 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');
@ -154,8 +157,11 @@ var RenderNodeManager = new Class({
BatchHandlerPointLight: BatchHandlerPointLight,
BatchHandlerQuad: BatchHandlerQuad,
BatchHandlerQuadLight: BatchHandlerQuadLight,
BatchHandlerQuadLightShadow: BatchHandlerQuadLightShadow,
BatchHandlerStrip: BatchHandlerStrip,
BatchHandlerTileSprite: BatchHandlerTileSprite,
BatchHandlerTileSpriteLight: BatchHandlerTileSpriteLight,
BatchHandlerTileSpriteLightShadow: BatchHandlerTileSpriteLightShadow,
BatchHandlerTriFlat: BatchHandlerTriFlat,
BatchHandlerTriFlatLight: BatchHandlerTriFlatLight,
Camera: Camera,
@ -170,6 +176,7 @@ var RenderNodeManager = new Class({
SubmitterQuad: SubmitterQuad,
SubmitterQuadLight: SubmitterQuadLight,
SubmitterTileSprite: SubmitterTileSprite,
SubmitterTileSpriteLight: SubmitterTileSpriteLight,
TexturerImage: TexturerImage,
TexturerTileSprite: TexturerTileSprite,
TransformerImage: TransformerImage,
@ -181,6 +188,7 @@ var RenderNodeManager = new Class({
if (game.config.selfShadow)
{
this._nodeConstructors.BatchHandlerQuadLight = BatchHandlerQuadLightShadow;
this._nodeConstructors.BatchHandlerTileSpriteLight = BatchHandlerTileSpriteLightShadow;
}
/**

View file

@ -8,7 +8,9 @@ var Map = require('../../../../structs/Map');
var DefaultTileSpriteNodes = new Map([
[ 'Submitter', 'SubmitterTileSprite' ],
[ 'SubmitterLight', 'SubmitterTileSpriteLight' ],
[ 'BatchHandler', 'BatchHandlerTileSprite' ],
[ 'BatchHandlerLight', 'BatchHandlerTileSpriteLight' ],
[ 'Transformer', 'TransformerTileSprite' ],
[ 'Texturer', 'TexturerTileSprite' ]
]);

View file

@ -16,6 +16,7 @@ var RenderNodes = {
BatchHandlerQuadLightShadow: require('./BatchHandlerQuadLightShadow'),
BatchHandlerStrip: require('./BatchHandlerStrip'),
BatchHandlerTileSprite: require('./BatchHandlerTileSprite'),
BatchHandlerTileSpriteLight: require('./BatchHandlerTileSpriteLight'),
BatchHandlerTriFlat: require('./BatchHandlerTriFlat'),
BatchHandlerTriFlatLight: require('./BatchHandlerTriFlatLight'),
Camera: require('./Camera'),
@ -31,6 +32,7 @@ var RenderNodes = {
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'),

View file

@ -15,8 +15,7 @@ var getTint = Utils.getTintAppendFloatAlpha;
* @classdesc
* The SubmitterQuadLight RenderNode submits data for rendering
* a single Image-like GameObject with lighting information.
*
* It uses a BatchHandlerQuadLight to render the image as part of a batch.
* It uses a BatchHandler to render the image as part of a batch.
*
* @class SubmitterQuadLight
* @memberof Phaser.Renderer.WebGL.RenderNodes

View file

@ -10,6 +10,29 @@ var SubmitterQuad = require('./SubmitterQuad.js');
var getTint = Utils.getTintAppendFloatAlpha;
/**
* @classdesc
* The SubmitterTileSprite RenderNode submits data for rendering a single TileSprite GameObject.
* It uses a BatchHandler to render the TileSprite as part of a batch.
*
* This node receives the drawing context, game object, and parent matrix.
* It also receives the texturer, tinter, and transformer nodes
* from the node that invoked it.
* This allows the behavior to be configured by setting the appropriate nodes
* on the GameObject for individual tweaks, or on the invoking Renderer node
* for global changes.
*
* @class SubmitterTileSprite
* @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='SubmitterTileSprite'] - The name of this RenderNode.
* @param {string} [config.role='Submitter'] - The expected role of this RenderNode.
* @param {string} [config.batchHandler='BatchHandler'] - The key of the default batch handler node to use for this RenderNode. This should correspond to a node which extends `BatchHandlerTileSprite`. It will be derived from the game object whenever the node runs.
*/
var SubmitterTileSprite = new Class({
Extends: SubmitterQuad,

View file

@ -0,0 +1,206 @@
/**
* @author Benjamin D. Richards <benjamindrichards@gmail.com>
* @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<Phaser.Renderer.WebGL.RenderNodes.TexturerImage, 'run'>} 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<Phaser.Renderer.WebGL.RenderNodes.RenderNode, 'run'>} [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;

View file

@ -0,0 +1,60 @@
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');

View file

@ -0,0 +1,65 @@
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');

View file

@ -51,6 +51,8 @@ module.exports = {
PostFXFrag: require('./PostFX-frag.js'),
QuadVert: require('./Quad-vert.js'),
SingleFrag: require('./Single-frag.js'),
SingleVert: require('./Single-vert.js')
SingleVert: require('./Single-vert.js'),
TileSpriteLightFrag: require('./TileSpriteLight-frag.js'),
TileSpriteLightShadowFrag: require('./TileSpriteLightShadow-frag.js')
};

View file

@ -0,0 +1,75 @@
#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);
}

View file

@ -0,0 +1,81 @@
#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);
}