Testing new batched lights

This commit is contained in:
Richard Davey 2020-10-13 18:17:30 +01:00
parent f74ab77431
commit b1a5ce7f55
7 changed files with 603 additions and 10 deletions

View file

@ -7,6 +7,7 @@
var Class = require('../../utils/Class');
var Light = require('./Light');
var Utils = require('../../renderer/webgl/Utils');
var PointLight = require('./PointLight');
/**
* @callback LightForEach
@ -96,6 +97,11 @@ var LightsManager = new Class({
this.maxLights = -1;
},
addPointLight: function (x, y, color, radius, intensity)
{
return this.systems.displayList.add(new PointLight(this.scene, x, y, color, radius, intensity));
},
/**
* Enable the Lights Manager.
*

View file

@ -0,0 +1,237 @@
/**
* @author Richard Davey <rich@photonstorm.com>
* @copyright 2020 Photon Storm Ltd.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Class = require('../../utils/Class');
var Components = require('../components');
var GameObject = require('../GameObject');
var IntegerToRGB = require('../../display/color/IntegerToRGB');
var RGB = require('../../display/RGB');
/**
* @classdesc
* An Image Game Object.
*
* An Image is a light-weight Game Object useful for the display of static images in your game,
* such as logos, backgrounds, scenery or other non-animated elements. Images can have input
* events and physics bodies, or be tweened, tinted or scrolled. The main difference between an
* Image and a Sprite is that you cannot animate an Image as they do not have the Animation component.
*
* @class PointLight
* @extends Phaser.GameObjects.GameObject
* @memberof Phaser.GameObjects
* @constructor
* @since 3.50.0
*
* @extends Phaser.GameObjects.Components.AlphaSingle
* @extends Phaser.GameObjects.Components.BlendMode
* @extends Phaser.GameObjects.Components.Depth
* @extends Phaser.GameObjects.Components.GetBounds
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.Pipeline
* @extends Phaser.GameObjects.Components.ScrollFactor
* @extends Phaser.GameObjects.Components.Transform
* @extends Phaser.GameObjects.Components.Visible
*
* @param {Phaser.Scene} scene - The Scene to which this Game Object belongs. A Game Object can only belong to one Scene at a time.
* @param {number} x - The horizontal position of this Game Object in the world.
* @param {number} y - The vertical position of this Game Object in the world.
*/
var PointLight = new Class({
Extends: GameObject,
Mixins: [
Components.AlphaSingle,
Components.BlendMode,
Components.Depth,
Components.GetBounds,
Components.Mask,
Components.Pipeline,
Components.ScrollFactor,
Components.Transform,
Components.Visible
],
initialize:
function PointLight (scene, x, y, color, radius, intensity)
{
if (color === undefined) { color = 0xffffff; }
if (radius === undefined) { radius = 128; }
if (intensity === undefined) { intensity = 1; }
GameObject.call(this, scene, 'PointLight');
this.initPipeline('Light2D');
this.setPosition(x, y);
var rgb = IntegerToRGB(color);
this.color = new RGB(
rgb.r / 255,
rgb.g / 255,
rgb.b / 255
);
this.intensity = intensity;
// read only:
this.width = radius * 2;
this.height = radius * 2;
// private
this._radius = radius;
},
radius: {
get: function ()
{
return this._radius;
},
set: function (value)
{
this._radius = value;
this.width = value * 2;
this.height = value * 2;
}
},
/**
* Internal value to allow Containers to be used for input and physics.
* Do not change this value. It has no effect other than to break things.
*
* @name Phaser.GameObjects.Container#originX
* @type {number}
* @readonly
* @since 3.4.0
*/
originX: {
get: function ()
{
return 0.5;
}
},
/**
* Internal value to allow Containers to be used for input and physics.
* Do not change this value. It has no effect other than to break things.
*
* @name Phaser.GameObjects.Container#originY
* @type {number}
* @readonly
* @since 3.4.0
*/
originY: {
get: function ()
{
return 0.5;
}
},
/**
* Internal value to allow Containers to be used for input and physics.
* Do not change this value. It has no effect other than to break things.
*
* @name Phaser.GameObjects.Container#displayOriginX
* @type {number}
* @readonly
* @since 3.4.0
*/
displayOriginX: {
get: function ()
{
return this._radius;
}
},
/**
* Internal value to allow Containers to be used for input and physics.
* Do not change this value. It has no effect other than to break things.
*
* @name Phaser.GameObjects.Container#displayOriginY
* @type {number}
* @readonly
* @since 3.4.0
*/
displayOriginY: {
get: function ()
{
return this._radius;
}
},
renderWebGL: function (renderer, src, camera, parentTransformMatrix)
{
var pipeline = renderer.pipelines.set(this.pipeline);
var camMatrix = pipeline._tempMatrix1;
var lightMatrix = pipeline._tempMatrix2;
var calcMatrix = pipeline._tempMatrix3;
var width = src.width;
var height = src.height;
var x = -src.radius;
var y = -src.radius;
var xw = x + width;
var yh = y + height;
lightMatrix.applyITRS(src.x, src.y, src.rotation, src.scaleX, src.scaleY);
camMatrix.copyFrom(camera.matrix);
if (parentTransformMatrix)
{
// Multiply the camera by the parent matrix
camMatrix.multiplyWithOffset(parentTransformMatrix, -camera.scrollX * src.scrollFactorX, -camera.scrollY * src.scrollFactorY);
// Undo the camera scroll
lightMatrix.e = src.x;
lightMatrix.f = src.y;
}
else
{
lightMatrix.e -= camera.scrollX * src.scrollFactorX;
lightMatrix.f -= camera.scrollY * src.scrollFactorY;
}
// Multiply by the Sprite matrix, store result in calcMatrix
camMatrix.multiply(lightMatrix, calcMatrix);
var lightX = calcMatrix.getX(0, 0);
var lightY = calcMatrix.getY(0, 0);
var tx0 = calcMatrix.getX(x, y);
var ty0 = calcMatrix.getY(x, y);
var tx1 = calcMatrix.getX(x, yh);
var ty1 = calcMatrix.getY(x, yh);
var tx2 = calcMatrix.getX(xw, yh);
var ty2 = calcMatrix.getY(xw, yh);
var tx3 = calcMatrix.getX(xw, y);
var ty3 = calcMatrix.getY(xw, y);
this.pipeline.batchLight(src, camera, tx0, ty0, tx1, ty1, tx2, ty2, tx3, ty3, lightX, lightY);
}
});
module.exports = PointLight;

View file

@ -7,8 +7,11 @@
var Class = require('../../../utils/Class');
var GetFastValue = require('../../../utils/object/GetFastValue');
var ShaderSourceFS = require('../shaders/Light-frag.js');
var MultiPipeline = require('./MultiPipeline');
var ModelViewProjection = require('./components/ModelViewProjection');
var PointLightShaderSourceFS = require('../shaders/PointLight-frag.js');
var PointLightShaderSourceVS = require('../shaders/PointLight-vert.js');
var ProjectOrtho = require('../mvp/ProjectOrtho');
var TransformMatrix = require('../../../gameobjects/components/TransformMatrix');
var WebGLPipeline = require('../WebGLPipeline');
var LIGHT_COUNT = 10;
@ -63,19 +66,111 @@ var LIGHT_COUNT = 10;
*/
var LightPipeline = new Class({
Extends: MultiPipeline,
Extends: WebGLPipeline,
Mixins: [
ModelViewProjection
],
initialize:
function LightPipeline (config)
{
var gl = config.game.renderer.gl;
LIGHT_COUNT = config.game.renderer.config.maxLights;
var fragmentShaderSource = GetFastValue(config, 'fragShader', ShaderSourceFS);
// var fragmentShaderSource = GetFastValue(config, 'fragShader', ShaderSourceFS);
// config.fragShader = fragmentShaderSource.replace('%LIGHT_COUNT%', LIGHT_COUNT.toString());
config.fragShader = fragmentShaderSource.replace('%LIGHT_COUNT%', LIGHT_COUNT.toString());
config.fragShader = GetFastValue(config, 'fragShader', PointLightShaderSourceFS);
config.vertShader = GetFastValue(config, 'vertShader', PointLightShaderSourceVS);
config.vertexSize = GetFastValue(config, 'vertexSize', 36);
config.attributes = GetFastValue(config, 'attributes', [
{
name: 'inPosition',
size: 2,
type: gl.FLOAT,
normalized: false,
offset: 0,
enabled: false,
location: -1
},
{
name: 'inLightPosition',
size: 2,
type: gl.FLOAT,
normalized: false,
offset: 8,
enabled: false,
location: -1
},
{
name: 'inLightRadius',
size: 1,
type: gl.FLOAT,
normalized: false,
offset: 16,
enabled: false,
location: -1
},
{
name: 'inLightColor',
size: 4,
type: gl.FLOAT,
normalized: false,
offset: 20,
enabled: false,
location: -1
}
]);
MultiPipeline.call(this, config);
config.uniforms = GetFastValue(config, 'uniforms', [
'uProjectionMatrix',
'uViewMatrix',
'uModelMatrix'
]);
WebGLPipeline.call(this, config);
/**
* Float32 view of the array buffer containing the pipeline's vertices.
*
* @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#vertexViewF32
* @type {Float32Array}
* @since 3.0.0
*/
this.vertexViewF32 = new Float32Array(this.vertexData);
/**
* A temporary Transform Matrix, re-used internally during batching.
*
* @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#_tempMatrix1
* @private
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.11.0
*/
this._tempMatrix1 = new TransformMatrix();
/**
* A temporary Transform Matrix, re-used internally during batching.
*
* @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#_tempMatrix2
* @private
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.11.0
*/
this._tempMatrix2 = new TransformMatrix();
/**
* A temporary Transform Matrix, re-used internally during batching.
*
* @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#_tempMatrix3
* @private
* @type {Phaser.GameObjects.Components.TransformMatrix}
* @since 3.11.0
*/
this._tempMatrix3 = new TransformMatrix();
/**
* Inverse rotation matrix for normal map rotations.
@ -111,6 +206,8 @@ var LightPipeline = new Class({
this.lightCount = 0;
this.forceZero = true;
this.mvpInit();
},
/**
@ -141,6 +238,139 @@ var LightPipeline = new Class({
return this;
},
batchLight: function (light, camera, x0, y0, x1, y1, x2, y2, x3, y3, lightX, lightY)
{
var color = light.color;
var intensity = light.intensity;
var radius = light.radius;
var r = color.r * intensity;
var g = color.g * intensity;
var b = color.b * intensity;
var a = camera.alpha * light.alpha;
if (this.shouldFlush(6))
{
this.flush();
}
var vertexViewF32 = this.vertexViewF32;
var vertexOffset = (this.vertexCount * this.vertexComponentCount) - 1;
vertexViewF32[++vertexOffset] = x0;
vertexViewF32[++vertexOffset] = y0;
vertexViewF32[++vertexOffset] = lightX;
vertexViewF32[++vertexOffset] = lightY;
vertexViewF32[++vertexOffset] = radius;
vertexViewF32[++vertexOffset] = r;
vertexViewF32[++vertexOffset] = g;
vertexViewF32[++vertexOffset] = b;
vertexViewF32[++vertexOffset] = a;
vertexViewF32[++vertexOffset] = x1;
vertexViewF32[++vertexOffset] = y1;
vertexViewF32[++vertexOffset] = lightX;
vertexViewF32[++vertexOffset] = lightY;
vertexViewF32[++vertexOffset] = radius;
vertexViewF32[++vertexOffset] = r;
vertexViewF32[++vertexOffset] = g;
vertexViewF32[++vertexOffset] = b;
vertexViewF32[++vertexOffset] = a;
vertexViewF32[++vertexOffset] = x2;
vertexViewF32[++vertexOffset] = y2;
vertexViewF32[++vertexOffset] = lightX;
vertexViewF32[++vertexOffset] = lightY;
vertexViewF32[++vertexOffset] = radius;
vertexViewF32[++vertexOffset] = r;
vertexViewF32[++vertexOffset] = g;
vertexViewF32[++vertexOffset] = b;
vertexViewF32[++vertexOffset] = a;
vertexViewF32[++vertexOffset] = x0;
vertexViewF32[++vertexOffset] = y0;
vertexViewF32[++vertexOffset] = lightX;
vertexViewF32[++vertexOffset] = lightY;
vertexViewF32[++vertexOffset] = radius;
vertexViewF32[++vertexOffset] = r;
vertexViewF32[++vertexOffset] = g;
vertexViewF32[++vertexOffset] = b;
vertexViewF32[++vertexOffset] = a;
vertexViewF32[++vertexOffset] = x2;
vertexViewF32[++vertexOffset] = y2;
vertexViewF32[++vertexOffset] = lightX;
vertexViewF32[++vertexOffset] = lightY;
vertexViewF32[++vertexOffset] = radius;
vertexViewF32[++vertexOffset] = r;
vertexViewF32[++vertexOffset] = g;
vertexViewF32[++vertexOffset] = b;
vertexViewF32[++vertexOffset] = a;
vertexViewF32[++vertexOffset] = x3;
vertexViewF32[++vertexOffset] = y3;
vertexViewF32[++vertexOffset] = lightX;
vertexViewF32[++vertexOffset] = lightY;
vertexViewF32[++vertexOffset] = radius;
vertexViewF32[++vertexOffset] = r;
vertexViewF32[++vertexOffset] = g;
vertexViewF32[++vertexOffset] = b;
vertexViewF32[++vertexOffset] = a;
this.vertexCount += 6;
},
/**
* Called every time a Game Object needs to use this pipeline.
*
* @method Phaser.Renderer.WebGL.Pipelines.MultiPipeline#onBind
* @since 3.0.0
*
* @param {Phaser.GameObjects.GameObject} [gameObject] - The Game Object that invoked this pipeline, if any.
*
* @return {this} This WebGLPipeline instance.
*/
onBind: function ()
{
this.mvpUpdate();
return this;
},
/**
* Uploads the vertex data and emits a draw call for the current batch of vertices.
*
* @method Phaser.Renderer.WebGL.Pipelines.MultiPipeline#flush
* @since 3.0.0
*
* @return {this} This WebGLPipeline instance.
*/
flush: function ()
{
var gl = this.gl;
var vertexCount = this.vertexCount;
var vertexSize = this.vertexSize;
if (vertexCount > 0)
{
if (vertexCount === this.vertexCapacity)
{
gl.bufferData(gl.ARRAY_BUFFER, this.vertexData, gl.DYNAMIC_DRAW);
}
else
{
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.bytes.subarray(0, vertexCount * vertexSize));
}
gl.drawArrays(this.topology, 0, vertexCount);
this.vertexCount = 0;
}
return this;
},
/**
* Called every time the pipeline is bound by the renderer.
* Sets the shader program, vertex buffer and other resources.
@ -152,7 +382,6 @@ var LightPipeline = new Class({
* @param {boolean} [reset=false] - Should the pipeline be fully re-bound after a renderer pipeline clear?
*
* @return {this} This WebGLPipeline instance.
*/
bind: function (reset)
{
if (reset === undefined) { reset = false; }
@ -168,6 +397,27 @@ var LightPipeline = new Class({
return this;
},
*/
/**
* Resizes this pipeline and updates the projection.
*
* @method Phaser.Renderer.WebGL.Pipelines.MultiPipeline#resize
* @since 3.0.0
*
* @param {number} width - The new width.
* @param {number} height - The new height.
*
* @return {this} This WebGLPipeline instance.
*/
resize: function (width, height)
{
WebGLPipeline.prototype.resize.call(this, width, height);
ProjectOrtho(this, 0, this.width, this.height, 0, -1000, 1000);
return this;
},
/**
* This function sets all the needed resources for each camera pass.
@ -179,7 +429,6 @@ var LightPipeline = new Class({
* @param {Phaser.Cameras.Scene2D.Camera} camera - The Scene Camera being rendered with.
*
* @return {this} This WebGLPipeline instance.
*/
onRender: function (scene, camera)
{
this.active = false;
@ -251,6 +500,7 @@ var LightPipeline = new Class({
return this;
},
*/
/**
* Rotates the normal map vectors inversely by the given angle.
@ -302,7 +552,6 @@ var LightPipeline = new Class({
*
* @param {WebGLTexture} [texture] - WebGLTexture that will be assigned to the current batch. If not given uses blankTexture.
* @param {Phaser.GameObjects.GameObject} [gameObject] - The Game Object being rendered or added to the batch.
*/
setTexture2D: function (texture, gameObject)
{
var renderer = this.renderer;
@ -327,6 +576,7 @@ var LightPipeline = new Class({
return 0;
},
*/
/**
* Custom pipelines can use this method in order to perform any required pre-batch tasks
@ -339,7 +589,6 @@ var LightPipeline = new Class({
* @param {Phaser.Textures.Frame} [frame] - Optional frame to use. Can override that of the Game Object.
*
* @return {number} The texture unit the Game Object has been assigned.
*/
setGameObject: function (gameObject, frame)
{
if (frame === undefined) { frame = gameObject.frame; }
@ -362,6 +611,7 @@ var LightPipeline = new Class({
return 0;
},
*/
/**
* Returns the normal map WebGLTexture from the given Game Object.

View file

@ -0,0 +1,25 @@
module.exports = [
'#define SHADER_NAME PHASER_POINTLIGHT_FS',
'',
'precision mediump float;',
'',
'varying vec2 lightPosition;',
'varying vec4 lightColor;',
'varying float lightRadius;',
'',
'void main()',
'{',
' vec2 res = vec2(800.0, 600.0);',
' vec2 center = vec2(lightPosition.x, res.y - lightPosition.y);',
'',
' float distance = length(center.xy - gl_FragCoord.xy);',
'',
' // float intensity = 1.0 - min(distance, lightRadius) / lightRadius;',
' float intensity = clamp(1.0 - distance * distance / (lightRadius * lightRadius), 0.0, 1.0);',
'',
' vec4 color = vec4(intensity, intensity, intensity, 0.0) * vec4(lightColor.r, lightColor.g, lightColor.b, 1.0);',
'',
' gl_FragColor = vec4(color.rgb * lightColor.a, color.a);',
'}',
''
].join('\n');

View file

@ -0,0 +1,28 @@
module.exports = [
'#define SHADER_NAME PHASER_POINTLIGHT_VS',
'',
'precision mediump float;',
'',
'uniform mat4 uProjectionMatrix;',
'uniform mat4 uViewMatrix;',
'uniform mat4 uModelMatrix;',
'',
'attribute vec2 inPosition;',
'attribute vec2 inLightPosition;',
'attribute vec4 inLightColor;',
'attribute float inLightRadius;',
'',
'varying vec2 lightPosition;',
'varying vec4 lightColor;',
'varying float lightRadius;',
'',
'void main ()',
'{',
' lightRadius = inLightRadius;',
' lightColor = inLightColor;',
' lightPosition = inLightPosition;',
'',
' gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(inPosition, 1.0, 1.0);',
'}',
''
].join('\n');

View file

@ -0,0 +1,22 @@
#define SHADER_NAME PHASER_POINTLIGHT_FS
precision mediump float;
varying vec2 lightPosition;
varying vec4 lightColor;
varying float lightRadius;
void main()
{
vec2 res = vec2(800.0, 600.0);
vec2 center = vec2(lightPosition.x, res.y - lightPosition.y);
float distance = length(center.xy - gl_FragCoord.xy);
// float intensity = 1.0 - min(distance, lightRadius) / lightRadius;
float intensity = clamp(1.0 - distance * distance / (lightRadius * lightRadius), 0.0, 1.0);
vec4 color = vec4(intensity, intensity, intensity, 0.0) * vec4(lightColor.r, lightColor.g, lightColor.b, 1.0);
gl_FragColor = vec4(color.rgb * lightColor.a, color.a);
}

View file

@ -0,0 +1,25 @@
#define SHADER_NAME PHASER_POINTLIGHT_VS
precision mediump float;
uniform mat4 uProjectionMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uModelMatrix;
attribute vec2 inPosition;
attribute vec2 inLightPosition;
attribute vec4 inLightColor;
attribute float inLightRadius;
varying vec2 lightPosition;
varying vec4 lightColor;
varying float lightRadius;
void main ()
{
lightRadius = inLightRadius;
lightColor = inLightColor;
lightPosition = inLightPosition;
gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(inPosition, 1.0, 1.0);
}