This commit is contained in:
2025-01-04 00:34:03 +01:00
parent 41829408dc
commit 0ca14bbc19
18111 changed files with 1871397 additions and 0 deletions

View File

@@ -0,0 +1,369 @@
/**
* The base sampler shader exposes a simple sprite shader and all the framework to handle:
* - Batched shaders and plugin subscription
* - Configure method (for special processing done once or punctually)
* - Update method (pre-binding, normally done each frame)
* All other sampler shaders (batched or not) should extend BaseSamplerShader
*/
class BaseSamplerShader extends AbstractBaseShader {
/**
* The named batch sampler plugin that is used by this shader, or null if no batching is used.
* @type {string|null}
*/
static classPluginName = "batch";
/**
* Is this shader pausable or not?
* @type {boolean}
*/
static pausable = true;
/**
* The plugin name associated for this instance, if any.
* Returns "batch" if the shader is disabled.
* @type {string|null}
*/
get pluginName() {
return this.#pluginName;
}
#pluginName = this.constructor.classPluginName;
/**
* Activate or deactivate this sampler. If set to false, the batch rendering is redirected to "batch".
* Otherwise, the batch rendering is directed toward the instance pluginName (might be null)
* @type {boolean}
*/
get enabled() {
return this.#enabled;
}
set enabled(enabled) {
this.#pluginName = enabled ? this.constructor.classPluginName : "batch";
this.#enabled = enabled;
}
#enabled = true;
/**
* Pause or Unpause this sampler. If set to true, the shader is disabled. Otherwise, it is enabled.
* Contrary to enabled, a shader might decide to refuse a pause, to continue to render animations per example.
* @see {enabled}
* @type {boolean}
*/
get paused() {
return !this.#enabled;
}
set paused(paused) {
if ( !this.constructor.pausable ) return;
this.enabled = !paused;
}
/**
* Contrast adjustment
* @type {string}
*/
static CONTRAST = `
// Computing contrasted color
if ( contrast != 0.0 ) {
changedColor = (changedColor - 0.5) * (contrast + 1.0) + 0.5;
}`;
/**
* Saturation adjustment
* @type {string}
*/
static SATURATION = `
// Computing saturated color
if ( saturation != 0.0 ) {
vec3 grey = vec3(perceivedBrightness(changedColor));
changedColor = mix(grey, changedColor, 1.0 + saturation);
}`;
/**
* Exposure adjustment.
* @type {string}
*/
static EXPOSURE = `
if ( exposure != 0.0 ) {
changedColor *= (1.0 + exposure);
}`;
/**
* The adjustments made into fragment shaders.
* @type {string}
*/
static get ADJUSTMENTS() {
return `vec3 changedColor = baseColor.rgb;
${this.CONTRAST}
${this.SATURATION}
${this.EXPOSURE}
baseColor.rgb = changedColor;`;
}
/** @override */
static vertexShader = `
precision ${PIXI.settings.PRECISION_VERTEX} float;
attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
varying vec2 vUvs;
void main() {
gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
vUvs = aTextureCoord;
}
`;
/** @override */
static fragmentShader = `
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
uniform sampler2D sampler;
uniform vec4 tintAlpha;
varying vec2 vUvs;
void main() {
gl_FragColor = texture2D(sampler, vUvs) * tintAlpha;
}
`;
/**
* The batch vertex shader source.
* @type {string}
*/
static batchVertexShader = `
#version 300 es
precision ${PIXI.settings.PRECISION_VERTEX} float;
in vec2 aVertexPosition;
in vec2 aTextureCoord;
in vec4 aColor;
in float aTextureId;
uniform mat3 projectionMatrix;
uniform mat3 translationMatrix;
uniform vec4 tint;
out vec2 vTextureCoord;
flat out vec4 vColor;
flat out float vTextureId;
void main(void){
gl_Position = vec4((projectionMatrix * translationMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
vTextureCoord = aTextureCoord;
vTextureId = aTextureId;
vColor = aColor * tint;
}
`;
/**
* The batch fragment shader source.
* @type {string}
*/
static batchFragmentShader = `
#version 300 es
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
in vec2 vTextureCoord;
flat in vec4 vColor;
flat in float vTextureId;
uniform sampler2D uSamplers[%count%];
out vec4 fragColor;
#define texture2D texture
void main(void){
vec4 color;
%forloop%
fragColor = color * vColor;
}
`;
/** @inheritdoc */
static defaultUniforms = {
sampler: 0,
tintAlpha: [1, 1, 1, 1]
};
/**
* Batch geometry associated with this sampler.
* @type {typeof PIXI.BatchGeometry|{id: string, size: number, normalized: boolean, type: PIXI.TYPES}[]}
*/
static batchGeometry = PIXI.BatchGeometry;
/**
* The size of a vertice with all its packed attributes.
* @type {number}
*/
static batchVertexSize = 6;
/**
* Pack interleaved geometry custom function.
* @type {Function|undefined}
* @protected
*/
static _packInterleavedGeometry;
/**
* A prerender function happening just before the batch renderer is flushed.
* @type {(batchRenderer: BatchRenderer) => void | undefined}
* @protected
*/
static _preRenderBatch;
/**
* A function that returns default uniforms associated with the batched version of this sampler.
* @type {object}
*/
static batchDefaultUniforms = {};
/**
* The number of reserved texture units for this shader that cannot be used by the batch renderer.
* @type {number}
*/
static reservedTextureUnits = 0;
/**
* Initialize the batch geometry with custom properties.
*/
static initializeBatchGeometry() {}
/**
* The batch renderer to use.
* @type {typeof BatchRenderer}
*/
static batchRendererClass = BatchRenderer;
/**
* The batch generator to use.
* @type {typeof BatchShaderGenerator}
*/
static batchShaderGeneratorClass = BatchShaderGenerator;
/* ---------------------------------------- */
/**
* Create a batch plugin for this sampler class.
* @returns {typeof BatchPlugin} The batch plugin class linked to this sampler class.
*/
static createPlugin() {
const shaderClass = this;
const geometryClass = Array.isArray(shaderClass.batchGeometry)
? class BatchGeometry extends PIXI.Geometry {
constructor(_static=false) {
super();
this._buffer = new PIXI.Buffer(null, _static, false);
this._indexBuffer = new PIXI.Buffer(null, _static, true);
for ( const {id, size, normalized, type} of shaderClass.batchGeometry ) {
this.addAttribute(id, this._buffer, size, normalized, type);
}
this.addIndex(this._indexBuffer);
}
} : shaderClass.batchGeometry;
return class BatchPlugin extends shaderClass.batchRendererClass {
/** @override */
static get shaderGeneratorClass() {
return shaderClass.batchShaderGeneratorClass;
}
/* ---------------------------------------- */
/** @override */
static get defaultVertexSrc() {
return shaderClass.batchVertexShader;
}
/* ---------------------------------------- */
/** @override */
static get defaultFragmentTemplate() {
return shaderClass.batchFragmentShader;
}
/* ---------------------------------------- */
/** @override */
static get defaultUniforms() {
return shaderClass.batchDefaultUniforms;
}
/* ---------------------------------------- */
/**
* The batch plugin constructor.
* @param {PIXI.Renderer} renderer The renderer
*/
constructor(renderer) {
super(renderer);
this.geometryClass = geometryClass;
this.vertexSize = shaderClass.batchVertexSize;
this.reservedTextureUnits = shaderClass.reservedTextureUnits;
this._packInterleavedGeometry = shaderClass._packInterleavedGeometry;
this._preRenderBatch = shaderClass._preRenderBatch;
}
/* ---------------------------------------- */
/** @inheritdoc */
setShaderGenerator(options) {
if ( !canvas.performance ) return;
super.setShaderGenerator(options);
}
/* ---------------------------------------- */
/** @inheritdoc */
contextChange() {
this.shaderGenerator = null;
super.contextChange();
}
};
}
/* ---------------------------------------- */
/**
* Register the plugin for this sampler.
* @param {object} [options] The options
* @param {object} [options.force=false] Override the plugin of the same name that is already registered?
*/
static registerPlugin({force=false}={}) {
const pluginName = this.classPluginName;
// Checking the pluginName
if ( !(pluginName && (typeof pluginName === "string") && (pluginName.length > 0)) ) {
const msg = `Impossible to create a PIXI plugin for ${this.name}. `
+ `The plugin name is invalid: [pluginName=${pluginName}]. `
+ "The plugin name must be a string with at least 1 character.";
throw new Error(msg);
}
// Checking for existing plugins
if ( !force && BatchRenderer.hasPlugin(pluginName) ) {
const msg = `Impossible to create a PIXI plugin for ${this.name}. `
+ `The plugin name is already associated to a plugin in PIXI.Renderer: [pluginName=${pluginName}].`;
throw new Error(msg);
}
// Initialize custom properties for the batch geometry
this.initializeBatchGeometry();
// Create our custom batch renderer for this geometry
const plugin = this.createPlugin();
// Register this plugin with its batch renderer
PIXI.extensions.add({
name: pluginName,
type: PIXI.ExtensionType.RendererPlugin,
ref: plugin
});
}
/* ---------------------------------------- */
/** @override */
_preRender(mesh, renderer) {
const uniforms = this.uniforms;
uniforms.sampler = mesh.texture;
uniforms.tintAlpha = mesh._cachedTint;
}
}

View File

@@ -0,0 +1,42 @@
/**
* Compute baseline illumination according to darkness level encoded texture.
*/
class BaselineIlluminationSamplerShader extends BaseSamplerShader {
/** @override */
static classPluginName = null;
/** @inheritdoc */
static fragmentShader = `
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
uniform sampler2D sampler;
uniform vec4 tintAlpha;
uniform vec3 ambientDarkness;
uniform vec3 ambientDaylight;
varying vec2 vUvs;
void main() {
float illuminationRed = texture2D(sampler, vUvs).r;
vec3 finalColor = mix(ambientDaylight, ambientDarkness, illuminationRed);
gl_FragColor = vec4(finalColor, 1.0) * tintAlpha;
}`;
/** @inheritdoc */
static defaultUniforms = {
tintAlpha: [1, 1, 1, 1],
ambientDarkness: [0, 0, 0],
ambientDaylight: [1, 1, 1],
sampler: null
};
/* -------------------------------------------- */
/** @inheritDoc */
_preRender(mesh, renderer) {
super._preRender(mesh, renderer);
const c = canvas.colors;
const u = this.uniforms;
c.ambientDarkness.applyRGB(u.ambientDarkness);
c.ambientDaylight.applyRGB(u.ambientDaylight);
}
}

View File

@@ -0,0 +1,239 @@
/**
* A color adjustment shader.
*/
class ColorAdjustmentsSamplerShader extends BaseSamplerShader {
/** @override */
static classPluginName = null;
/* -------------------------------------------- */
/** @override */
static vertexShader = `
precision ${PIXI.settings.PRECISION_VERTEX} float;
attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
uniform vec2 screenDimensions;
varying vec2 vUvs;
varying vec2 vScreenCoord;
void main() {
gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
vUvs = aTextureCoord;
vScreenCoord = aVertexPosition / screenDimensions;
}`;
/* -------------------------------------------- */
/** @override */
static fragmentShader = `
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
uniform sampler2D sampler;
uniform vec4 tintAlpha;
uniform vec3 tint;
uniform float exposure;
uniform float contrast;
uniform float saturation;
uniform float brightness;
uniform sampler2D darknessLevelTexture;
uniform bool linkedToDarknessLevel;
varying vec2 vUvs;
varying vec2 vScreenCoord;
${this.CONSTANTS}
${this.PERCEIVED_BRIGHTNESS}
void main() {
vec4 baseColor = texture2D(sampler, vUvs);
if ( baseColor.a > 0.0 ) {
// Unmultiply rgb with alpha channel
baseColor.rgb /= baseColor.a;
// Copy original color before update
vec3 originalColor = baseColor.rgb;
${this.ADJUSTMENTS}
if ( linkedToDarknessLevel ) {
float darknessLevel = texture2D(darknessLevelTexture, vScreenCoord).r;
baseColor.rgb = mix(originalColor, baseColor.rgb, darknessLevel);
}
// Multiply rgb with tint and alpha channel
baseColor.rgb *= (tint * baseColor.a);
}
// Output with tint and alpha
gl_FragColor = baseColor * tintAlpha;
}`;
/* -------------------------------------------- */
/** @inheritdoc */
static defaultUniforms = {
tintAlpha: [1, 1, 1, 1],
tint: [1, 1, 1],
contrast: 0,
saturation: 0,
exposure: 0,
sampler: null,
linkedToDarknessLevel: false,
darknessLevelTexture: null,
screenDimensions: [1, 1]
};
/* -------------------------------------------- */
get linkedToDarknessLevel() {
return this.uniforms.linkedToDarknessLevel;
}
set linkedToDarknessLevel(link) {
this.uniforms.linkedToDarknessLevel = link;
}
/* -------------------------------------------- */
get contrast() {
return this.uniforms.contrast;
}
set contrast(contrast) {
this.uniforms.contrast = contrast;
}
/* -------------------------------------------- */
get exposure() {
return this.uniforms.exposure;
}
set exposure(exposure) {
this.uniforms.exposure = exposure;
}
/* -------------------------------------------- */
get saturation() {
return this.uniforms.saturation;
}
set saturation(saturation) {
this.uniforms.saturation = saturation;
}
}
/* -------------------------------------------- */
/**
* A light amplification shader.
*/
class AmplificationSamplerShader extends ColorAdjustmentsSamplerShader {
/** @override */
static classPluginName = null;
/* -------------------------------------------- */
/** @override */
static vertexShader = `
precision ${PIXI.settings.PRECISION_VERTEX} float;
attribute vec2 aVertexPosition;
attribute vec2 aTextureCoord;
uniform mat3 projectionMatrix;
uniform vec2 screenDimensions;
varying vec2 vUvs;
varying vec2 vScreenCoord;
void main() {
gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);
vUvs = aTextureCoord;
vScreenCoord = aVertexPosition / screenDimensions;
}
`;
/* -------------------------------------------- */
/** @override */
static fragmentShader = `
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
uniform sampler2D sampler;
uniform vec4 tintAlpha;
uniform vec3 tint;
uniform float exposure;
uniform float contrast;
uniform float saturation;
uniform float brightness;
uniform sampler2D darknessLevelTexture;
uniform bool linkedToDarknessLevel;
uniform bool enable;
varying vec2 vUvs;
varying vec2 vScreenCoord;
${this.CONSTANTS}
${this.PERCEIVED_BRIGHTNESS}
void main() {
vec4 baseColor = texture2D(sampler, vUvs);
if ( enable && baseColor.a > 0.0 ) {
// Unmultiply rgb with alpha channel
baseColor.rgb /= baseColor.a;
float lum = perceivedBrightness(baseColor.rgb);
vec3 vision = vec3(smoothstep(0.0, 1.0, lum * 1.5)) * tint;
float darknessLevel = texture2D(darknessLevelTexture, vScreenCoord).r;
baseColor.rgb = vision + (vision * (lum + brightness) * 0.1) + (baseColor.rgb * (1.0 - darknessLevel) * 0.125);
${this.ADJUSTMENTS}
// Multiply rgb with alpha channel
baseColor.rgb *= baseColor.a;
}
// Output with tint and alpha
gl_FragColor = baseColor * tintAlpha;
}`;
/* -------------------------------------------- */
/** @inheritdoc */
static defaultUniforms = {
tintAlpha: [1, 1, 1, 1],
tint: [0.38, 0.8, 0.38],
brightness: 0,
darknessLevelTexture: null,
screenDimensions: [1, 1],
enable: true
};
/* -------------------------------------------- */
/**
* Brightness controls the luminosity.
* @type {number}
*/
get brightness() {
return this.uniforms.brightness;
}
set brightness(brightness) {
this.uniforms.brightness = brightness;
}
/* -------------------------------------------- */
/**
* Tint color applied to Light Amplification.
* @type {number[]} Light Amplification tint (default: [0.48, 1.0, 0.48]).
*/
get colorTint() {
return this.uniforms.colorTint;
}
set colorTint(color) {
this.uniforms.colorTint = color;
}
}

View File

@@ -0,0 +1,421 @@
/**
* The batch data that is needed by {@link DepthSamplerShader} to render an element with batching.
* @typedef {object} DepthBatchData
* @property {PIXI.Texture} _texture The texture
* @property {Float32Array} vertexData The vertices
* @property {Uint16Array|Uint32Array|number[]} indices The indices
* @property {Float32Array} uvs The texture UVs
* @property {number} elevation The elevation
* @property {number} textureAlphaThreshold The texture alpha threshold
* @property {number} fadeOcclusion The amount of FADE occlusion
* @property {number} radialOcclusion The amount of RADIAL occlusion
* @property {number} visionOcclusion The amount of VISION occlusion
*/
/**
* The depth sampler shader.
*/
class DepthSamplerShader extends BaseSamplerShader {
/* -------------------------------------------- */
/* Batched version Rendering */
/* -------------------------------------------- */
/** @override */
static classPluginName = "batchDepth";
/* ---------------------------------------- */
/** @override */
static batchGeometry = [
{id: "aVertexPosition", size: 2, normalized: false, type: PIXI.TYPES.FLOAT},
{id: "aTextureCoord", size: 2, normalized: false, type: PIXI.TYPES.FLOAT},
{id: "aTextureId", size: 1, normalized: false, type: PIXI.TYPES.UNSIGNED_BYTE},
{id: "aTextureAlphaThreshold", size: 1, normalized: true, type: PIXI.TYPES.UNSIGNED_BYTE},
{id: "aDepthElevation", size: 1, normalized: true, type: PIXI.TYPES.UNSIGNED_BYTE},
{id: "aRestrictionState", size: 1, normalized: false, type: PIXI.TYPES.UNSIGNED_BYTE},
{id: "aOcclusionData", size: 4, normalized: true, type: PIXI.TYPES.UNSIGNED_BYTE}
];
/* ---------------------------------------- */
/** @override */
static batchVertexSize = 6;
/* -------------------------------------------- */
/** @override */
static reservedTextureUnits = 1; // We need a texture unit for the occlusion texture
/* -------------------------------------------- */
/** @override */
static defaultUniforms = {
screenDimensions: [1, 1],
sampler: null,
occlusionTexture: null,
textureAlphaThreshold: 0,
depthElevation: 0,
occlusionElevation: 0,
fadeOcclusion: 0,
radialOcclusion: 0,
visionOcclusion: 0,
restrictsLight: false,
restrictsWeather: false
};
/* -------------------------------------------- */
/** @override */
static batchDefaultUniforms(maxTex) {
return {
screenDimensions: [1, 1],
occlusionTexture: maxTex
};
}
/* -------------------------------------------- */
/** @override */
static _preRenderBatch(batchRenderer) {
const uniforms = batchRenderer._shader.uniforms;
uniforms.screenDimensions = canvas.screenDimensions;
batchRenderer.renderer.texture.bind(canvas.masks.occlusion.renderTexture, uniforms.occlusionTexture);
}
/* ---------------------------------------- */
/** @override */
static _packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) {
const {float32View, uint8View} = attributeBuffer;
// Write indices into buffer
const packedVertices = aIndex / this.vertexSize;
const indices = element.indices;
for ( let i = 0; i < indices.length; i++ ) {
indexBuffer[iIndex++] = packedVertices + indices[i];
}
// Prepare attributes
const vertexData = element.vertexData;
const uvs = element.uvs;
const textureId = element._texture.baseTexture._batchLocation;
const restrictionState = element.restrictionState;
const textureAlphaThreshold = (element.textureAlphaThreshold * 255) | 0;
const depthElevation = (canvas.masks.depth.mapElevation(element.elevation) * 255) | 0;
const occlusionElevation = (canvas.masks.occlusion.mapElevation(element.elevation) * 255) | 0;
const fadeOcclusion = (element.fadeOcclusion * 255) | 0;
const radialOcclusion = (element.radialOcclusion * 255) | 0;
const visionOcclusion = (element.visionOcclusion * 255) | 0;
// Write attributes into buffer
const vertexSize = this.vertexSize;
for ( let i = 0, j = 0; i < vertexData.length; i += 2, j += vertexSize ) {
let k = aIndex + j;
float32View[k++] = vertexData[i];
float32View[k++] = vertexData[i + 1];
float32View[k++] = uvs[i];
float32View[k++] = uvs[i + 1];
k <<= 2;
uint8View[k++] = textureId;
uint8View[k++] = textureAlphaThreshold;
uint8View[k++] = depthElevation;
uint8View[k++] = restrictionState;
uint8View[k++] = occlusionElevation;
uint8View[k++] = fadeOcclusion;
uint8View[k++] = radialOcclusion;
uint8View[k++] = visionOcclusion;
}
}
/* ---------------------------------------- */
/** @override */
static get batchVertexShader() {
return `
#version 300 es
${this.GLSL1_COMPATIBILITY_VERTEX}
precision ${PIXI.settings.PRECISION_VERTEX} float;
in vec2 aVertexPosition;
in vec2 aTextureCoord;
uniform vec2 screenDimensions;
${this._batchVertexShader}
in float aTextureId;
in float aTextureAlphaThreshold;
in float aDepthElevation;
in vec4 aOcclusionData;
in float aRestrictionState;
uniform mat3 projectionMatrix;
uniform mat3 translationMatrix;
out vec2 vTextureCoord;
out vec2 vOcclusionCoord;
flat out float vTextureId;
flat out float vTextureAlphaThreshold;
flat out float vDepthElevation;
flat out float vOcclusionElevation;
flat out float vFadeOcclusion;
flat out float vRadialOcclusion;
flat out float vVisionOcclusion;
flat out uint vRestrictionState;
void main() {
vec2 vertexPosition;
vec2 textureCoord;
_main(vertexPosition, textureCoord);
vec3 tPos = translationMatrix * vec3(vertexPosition, 1.0);
gl_Position = vec4((projectionMatrix * tPos).xy, 0.0, 1.0);
vTextureCoord = textureCoord;
vOcclusionCoord = tPos.xy / screenDimensions;
vTextureId = aTextureId;
vTextureAlphaThreshold = aTextureAlphaThreshold;
vDepthElevation = aDepthElevation;
vOcclusionElevation = aOcclusionData.x;
vFadeOcclusion = aOcclusionData.y;
vRadialOcclusion = aOcclusionData.z;
vVisionOcclusion = aOcclusionData.w;
vRestrictionState = uint(aRestrictionState);
}
`;
}
/* -------------------------------------------- */
/**
* The batch vertex shader source. Subclasses can override it.
* @type {string}
* @protected
*/
static _batchVertexShader = `
void _main(out vec2 vertexPosition, out vec2 textureCoord) {
vertexPosition = aVertexPosition;
textureCoord = aTextureCoord;
}
`;
/* ---------------------------------------- */
/** @override */
static get batchFragmentShader() {
return `
#version 300 es
${this.GLSL1_COMPATIBILITY_FRAGMENT}
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
in vec2 vTextureCoord;
flat in float vTextureId;
uniform sampler2D uSamplers[%count%];
${DepthSamplerShader.#OPTIONS_CONSTANTS}
${this._batchFragmentShader}
in vec2 vOcclusionCoord;
flat in float vTextureAlphaThreshold;
flat in float vDepthElevation;
flat in float vOcclusionElevation;
flat in float vFadeOcclusion;
flat in float vRadialOcclusion;
flat in float vVisionOcclusion;
flat in uint vRestrictionState;
uniform sampler2D occlusionTexture;
out vec3 fragColor;
void main() {
float textureAlpha = _main();
float textureAlphaThreshold = vTextureAlphaThreshold;
float depthElevation = vDepthElevation;
float occlusionElevation = vOcclusionElevation;
float fadeOcclusion = vFadeOcclusion;
float radialOcclusion = vRadialOcclusion;
float visionOcclusion = vVisionOcclusion;
bool restrictsLight = ((vRestrictionState & RESTRICTS_LIGHT) == RESTRICTS_LIGHT);
bool restrictsWeather = ((vRestrictionState & RESTRICTS_WEATHER) == RESTRICTS_WEATHER);
${DepthSamplerShader.#FRAGMENT_MAIN}
}
`;
}
/* -------------------------------------------- */
/**
* The batch fragment shader source. Subclasses can override it.
* @type {string}
* @protected
*/
static _batchFragmentShader = `
float _main() {
vec4 color;
%forloop%
return color.a;
}
`;
/* -------------------------------------------- */
/* Non-Batched version Rendering */
/* -------------------------------------------- */
/** @override */
static get vertexShader() {
return `
#version 300 es
${this.GLSL1_COMPATIBILITY_VERTEX}
precision ${PIXI.settings.PRECISION_VERTEX} float;
in vec2 aVertexPosition;
in vec2 aTextureCoord;
uniform vec2 screenDimensions;
${this._vertexShader}
uniform mat3 projectionMatrix;
out vec2 vUvs;
out vec2 vOcclusionCoord;
void main() {
vec2 vertexPosition;
vec2 textureCoord;
_main(vertexPosition, textureCoord);
gl_Position = vec4((projectionMatrix * vec3(vertexPosition, 1.0)).xy, 0.0, 1.0);
vUvs = textureCoord;
vOcclusionCoord = vertexPosition / screenDimensions;
}
`;
}
/* -------------------------------------------- */
/**
* The vertex shader source. Subclasses can override it.
* @type {string}
* @protected
*/
static _vertexShader = `
void _main(out vec2 vertexPosition, out vec2 textureCoord) {
vertexPosition = aVertexPosition;
textureCoord = aTextureCoord;
}
`;
/* -------------------------------------------- */
/** @override */
static get fragmentShader() {
return `
#version 300 es
${this.GLSL1_COMPATIBILITY_FRAGMENT}
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
in vec2 vUvs;
uniform sampler2D sampler;
${DepthSamplerShader.#OPTIONS_CONSTANTS}
${this._fragmentShader}
in vec2 vOcclusionCoord;
uniform sampler2D occlusionTexture;
uniform float textureAlphaThreshold;
uniform float depthElevation;
uniform float occlusionElevation;
uniform float fadeOcclusion;
uniform float radialOcclusion;
uniform float visionOcclusion;
uniform bool restrictsLight;
uniform bool restrictsWeather;
out vec3 fragColor;
void main() {
float textureAlpha = _main();
${DepthSamplerShader.#FRAGMENT_MAIN}
}
`;
}
/* -------------------------------------------- */
/**
* The fragment shader source. Subclasses can override it.
* @type {string}
* @protected
*/
static _fragmentShader = `
float _main() {
return texture(sampler, vUvs).a;
}
`;
/* -------------------------------------------- */
/** @inheritdoc */
_preRender(mesh, renderer) {
super._preRender(mesh, renderer);
const uniforms = this.uniforms;
uniforms.screenDimensions = canvas.screenDimensions;
uniforms.textureAlphaThreshold = mesh.textureAlphaThreshold;
const occlusionMask = canvas.masks.occlusion;
uniforms.occlusionTexture = occlusionMask.renderTexture;
uniforms.occlusionElevation = occlusionMask.mapElevation(mesh.elevation);
uniforms.depthElevation = canvas.masks.depth.mapElevation(mesh.elevation);
const occlusionState = mesh._occlusionState;
uniforms.fadeOcclusion = occlusionState.fade;
uniforms.radialOcclusion = occlusionState.radial;
uniforms.visionOcclusion = occlusionState.vision;
uniforms.restrictsLight = mesh.restrictsLight;
uniforms.restrictsWeather = mesh.restrictsWeather;
}
/* -------------------------------------------- */
/**
* The restriction options bit mask constants.
* @type {string}
*/
static #OPTIONS_CONSTANTS = foundry.utils.BitMask.generateShaderBitMaskConstants([
"RESTRICTS_LIGHT",
"RESTRICTS_WEATHER"
]);
/* -------------------------------------------- */
/**
* The fragment source.
* @type {string}
*/
static #FRAGMENT_MAIN = `
float inverseDepthElevation = 1.0 - depthElevation;
fragColor = vec3(inverseDepthElevation, depthElevation, inverseDepthElevation);
fragColor *= step(textureAlphaThreshold, textureAlpha);
vec3 weight = 1.0 - step(occlusionElevation, texture(occlusionTexture, vOcclusionCoord).rgb);
float occlusion = step(0.5, max(max(weight.r * fadeOcclusion, weight.g * radialOcclusion), weight.b * visionOcclusion));
fragColor.r *= occlusion;
fragColor.g *= 1.0 - occlusion;
fragColor.b *= occlusion;
if ( !restrictsLight ) {
fragColor.r = 0.0;
fragColor.g = 0.0;
}
if ( !restrictsWeather ) {
fragColor.b = 0.0;
}
`;
}

View File

@@ -0,0 +1,19 @@
/**
* A simple shader which purpose is to make the original texture red channel the alpha channel,
* and still keeping channel informations. Used in cunjunction with the AlphaBlurFilterPass and Fog of War.
*/
class FogSamplerShader extends BaseSamplerShader {
/** @override */
static classPluginName = null;
/** @override */
static fragmentShader = `
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
uniform sampler2D sampler;
uniform vec4 tintAlpha;
varying vec2 vUvs;
void main() {
vec4 color = texture2D(sampler, vUvs);
gl_FragColor = vec4(1.0, color.gb, 1.0) * step(0.15, color.r) * tintAlpha;
}`;
}

View File

@@ -0,0 +1,395 @@
/**
* The batch data that is needed by {@link OccludableSamplerShader} to render an element with batching.
* @typedef {object} OccludableBatchData
* @property {PIXI.Texture} _texture The texture
* @property {Float32Array} vertexData The vertices
* @property {Uint16Array|Uint32Array|number[]} indices The indices
* @property {Float32Array} uvs The texture UVs
* @property {number} worldAlpha The world alpha
* @property {number} _tintRGB The tint
* @property {number} blendMode The blend mode
* @property {number} elevation The elevation
* @property {number} unoccludedAlpha The unoccluded alpha
* @property {number} occludedAlpha The unoccluded alpha
* @property {number} fadeOcclusion The amount of FADE occlusion
* @property {number} radialOcclusion The amount of RADIAL occlusion
* @property {number} visionOcclusion The amount of VISION occlusion
*/
/**
* The occlusion sampler shader.
*/
class OccludableSamplerShader extends BaseSamplerShader {
/**
* The fragment shader code that applies occlusion.
* @type {string}
*/
static #OCCLUSION = `
vec3 occluded = 1.0 - step(occlusionElevation, texture(occlusionTexture, vScreenCoord).rgb);
float occlusion = max(occluded.r * fadeOcclusion, max(occluded.g * radialOcclusion, occluded.b * visionOcclusion));
fragColor *= mix(unoccludedAlpha, occludedAlpha, occlusion);
`;
/* -------------------------------------------- */
/* Batched version Rendering */
/* -------------------------------------------- */
/** @override */
static classPluginName = "batchOcclusion";
/* ---------------------------------------- */
/** @override */
static batchGeometry = [
{id: "aVertexPosition", size: 2, normalized: false, type: PIXI.TYPES.FLOAT},
{id: "aTextureCoord", size: 2, normalized: false, type: PIXI.TYPES.FLOAT},
{id: "aColor", size: 4, normalized: true, type: PIXI.TYPES.UNSIGNED_BYTE},
{id: "aTextureId", size: 1, normalized: false, type: PIXI.TYPES.UNSIGNED_SHORT},
{id: "aOcclusionAlphas", size: 2, normalized: true, type: PIXI.TYPES.UNSIGNED_BYTE},
{id: "aOcclusionData", size: 4, normalized: true, type: PIXI.TYPES.UNSIGNED_BYTE}
];
/* -------------------------------------------- */
/** @override */
static batchVertexSize = 7;
/* -------------------------------------------- */
/** @override */
static reservedTextureUnits = 1; // We need a texture unit for the occlusion texture
/* -------------------------------------------- */
/** @override */
static defaultUniforms = {
screenDimensions: [1, 1],
sampler: null,
tintAlpha: [1, 1, 1, 1],
occlusionTexture: null,
unoccludedAlpha: 1,
occludedAlpha: 0,
occlusionElevation: 0,
fadeOcclusion: 0,
radialOcclusion: 0,
visionOcclusion: 0
};
/* -------------------------------------------- */
/** @override */
static batchDefaultUniforms(maxTex) {
return {
screenDimensions: [1, 1],
occlusionTexture: maxTex
};
}
/* -------------------------------------------- */
/** @override */
static _preRenderBatch(batchRenderer) {
const occlusionMask = canvas.masks.occlusion;
const uniforms = batchRenderer._shader.uniforms;
uniforms.screenDimensions = canvas.screenDimensions;
batchRenderer.renderer.texture.bind(occlusionMask.renderTexture, uniforms.occlusionTexture);
}
/* ---------------------------------------- */
/** @override */
static _packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) {
const {float32View, uint8View, uint16View, uint32View} = attributeBuffer;
// Write indices into buffer
const packedVertices = aIndex / this.vertexSize;
const indices = element.indices;
for ( let i = 0; i < indices.length; i++ ) {
indexBuffer[iIndex++] = packedVertices + indices[i];
}
// Prepare attributes
const vertexData = element.vertexData;
const uvs = element.uvs;
const baseTexture = element._texture.baseTexture;
const alpha = Math.min(element.worldAlpha, 1.0);
const argb = PIXI.Color.shared.setValue(element._tintRGB).toPremultiplied(alpha, baseTexture.alphaMode > 0);
const textureId = baseTexture._batchLocation;
const unoccludedAlpha = (element.unoccludedAlpha * 255) | 0;
const occludedAlpha = (element.occludedAlpha * 255) | 0;
const occlusionElevation = (canvas.masks.occlusion.mapElevation(element.elevation) * 255) | 0;
const fadeOcclusion = (element.fadeOcclusion * 255) | 0;
const radialOcclusion = (element.radialOcclusion * 255) | 0;
const visionOcclusion = (element.visionOcclusion * 255) | 0;
// Write attributes into buffer
const vertexSize = this.vertexSize;
for ( let i = 0, j = 0; i < vertexData.length; i += 2, j += vertexSize ) {
let k = aIndex + j;
float32View[k++] = vertexData[i];
float32View[k++] = vertexData[i + 1];
float32View[k++] = uvs[i];
float32View[k++] = uvs[i + 1];
uint32View[k++] = argb;
k <<= 1;
uint16View[k++] = textureId;
k <<= 1;
uint8View[k++] = unoccludedAlpha;
uint8View[k++] = occludedAlpha;
uint8View[k++] = occlusionElevation;
uint8View[k++] = fadeOcclusion;
uint8View[k++] = radialOcclusion;
uint8View[k++] = visionOcclusion;
}
}
/* -------------------------------------------- */
/** @override */
static get batchVertexShader() {
return `
#version 300 es
${this.GLSL1_COMPATIBILITY_VERTEX}
precision ${PIXI.settings.PRECISION_VERTEX} float;
in vec2 aVertexPosition;
in vec2 aTextureCoord;
in vec4 aColor;
uniform mat3 translationMatrix;
uniform vec4 tint;
uniform vec2 screenDimensions;
${this._batchVertexShader}
in float aTextureId;
in vec2 aOcclusionAlphas;
in vec4 aOcclusionData;
uniform mat3 projectionMatrix;
out vec2 vTextureCoord;
out vec2 vScreenCoord;
flat out vec4 vColor;
flat out float vTextureId;
flat out float vUnoccludedAlpha;
flat out float vOccludedAlpha;
flat out float vOcclusionElevation;
flat out float vFadeOcclusion;
flat out float vRadialOcclusion;
flat out float vVisionOcclusion;
void main() {
vec2 vertexPosition;
vec2 textureCoord;
vec4 color;
_main(vertexPosition, textureCoord, color);
gl_Position = vec4((projectionMatrix * vec3(vertexPosition, 1.0)).xy, 0.0, 1.0);
vTextureCoord = textureCoord;
vScreenCoord = vertexPosition / screenDimensions;
vColor = color;
vTextureId = aTextureId;
vUnoccludedAlpha = aOcclusionAlphas.x;
vOccludedAlpha = aOcclusionAlphas.y;
vOcclusionElevation = aOcclusionData.x;
vFadeOcclusion = aOcclusionData.y;
vRadialOcclusion = aOcclusionData.z;
vVisionOcclusion = aOcclusionData.w;
}
`;
}
/* -------------------------------------------- */
/**
* The batch vertex shader source. Subclasses can override it.
* @type {string}
* @protected
*/
static _batchVertexShader = `
void _main(out vec2 vertexPosition, out vec2 textureCoord, out vec4 color) {
vertexPosition = (translationMatrix * vec3(aVertexPosition, 1.0)).xy;
textureCoord = aTextureCoord;
color = aColor * tint;
}
`;
/* ---------------------------------------- */
/** @override */
static get batchFragmentShader() {
return `
#version 300 es
${this.GLSL1_COMPATIBILITY_FRAGMENT}
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
in vec2 vTextureCoord;
in vec2 vScreenCoord;
flat in vec4 vColor;
flat in float vTextureId;
uniform sampler2D uSamplers[%count%];
${this._batchFragmentShader}
flat in float vUnoccludedAlpha;
flat in float vOccludedAlpha;
flat in float vOcclusionElevation;
flat in float vFadeOcclusion;
flat in float vRadialOcclusion;
flat in float vVisionOcclusion;
uniform sampler2D occlusionTexture;
out vec4 fragColor;
void main() {
fragColor = _main();
float unoccludedAlpha = vUnoccludedAlpha;
float occludedAlpha = vOccludedAlpha;
float occlusionElevation = vOcclusionElevation;
float fadeOcclusion = vFadeOcclusion;
float radialOcclusion = vRadialOcclusion;
float visionOcclusion = vVisionOcclusion;
${OccludableSamplerShader.#OCCLUSION}
}
`;
}
/* -------------------------------------------- */
/**
* The batch fragment shader source. Subclasses can override it.
* @type {string}
* @protected
*/
static _batchFragmentShader = `
vec4 _main() {
vec4 color;
%forloop%
return color * vColor;
}
`;
/* -------------------------------------------- */
/* Non-Batched version Rendering */
/* -------------------------------------------- */
/** @override */
static get vertexShader() {
return `
#version 300 es
${this.GLSL1_COMPATIBILITY_VERTEX}
precision ${PIXI.settings.PRECISION_VERTEX} float;
in vec2 aVertexPosition;
in vec2 aTextureCoord;
uniform vec2 screenDimensions;
${this._vertexShader}
uniform mat3 projectionMatrix;
out vec2 vUvs;
out vec2 vScreenCoord;
void main() {
vec2 vertexPosition;
vec2 textureCoord;
_main(vertexPosition, textureCoord);
gl_Position = vec4((projectionMatrix * vec3(vertexPosition, 1.0)).xy, 0.0, 1.0);
vUvs = textureCoord;
vScreenCoord = vertexPosition / screenDimensions;
}
`;
}
/* -------------------------------------------- */
/**
* The vertex shader source. Subclasses can override it.
* @type {string}
* @protected
*/
static _vertexShader = `
void _main(out vec2 vertexPosition, out vec2 textureCoord) {
vertexPosition = aVertexPosition;
textureCoord = aTextureCoord;
}
`;
/* -------------------------------------------- */
/** @override */
static get fragmentShader() {
return `
#version 300 es
${this.GLSL1_COMPATIBILITY_FRAGMENT}
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
in vec2 vUvs;
in vec2 vScreenCoord;
uniform sampler2D sampler;
uniform vec4 tintAlpha;
${this._fragmentShader}
uniform sampler2D occlusionTexture;
uniform float unoccludedAlpha;
uniform float occludedAlpha;
uniform float occlusionElevation;
uniform float fadeOcclusion;
uniform float radialOcclusion;
uniform float visionOcclusion;
out vec4 fragColor;
void main() {
fragColor = _main();
${OccludableSamplerShader.#OCCLUSION}
}
`;
}
/* -------------------------------------------- */
/**
* The fragment shader source. Subclasses can override it.
* @type {string}
* @protected
*/
static _fragmentShader = `
vec4 _main() {
return texture(sampler, vUvs) * tintAlpha;
}
`;
/* -------------------------------------------- */
/** @inheritdoc */
_preRender(mesh, renderer) {
super._preRender(mesh, renderer);
const uniforms = this.uniforms;
uniforms.screenDimensions = canvas.screenDimensions;
const occlusionMask = canvas.masks.occlusion;
uniforms.occlusionTexture = occlusionMask.renderTexture;
uniforms.occlusionElevation = occlusionMask.mapElevation(mesh.elevation);
uniforms.unoccludedAlpha = mesh.unoccludedAlpha;
uniforms.occludedAlpha = mesh.occludedAlpha;
const occlusionState = mesh._occlusionState;
uniforms.fadeOcclusion = occlusionState.fade;
uniforms.radialOcclusion = occlusionState.radial;
uniforms.visionOcclusion = occlusionState.vision;
}
}

View File

@@ -0,0 +1,46 @@
/**
* The base shader class of {@link PrimarySpriteMesh}.
*/
class PrimaryBaseSamplerShader extends OccludableSamplerShader {
/**
* The depth shader class associated with this shader.
* @type {typeof DepthSamplerShader}
*/
static depthShaderClass = DepthSamplerShader;
/* -------------------------------------------- */
/**
* The depth shader associated with this shader.
* The depth shader is lazily constructed.
* @type {DepthSamplerShader}
*/
get depthShader() {
return this.#depthShader ??= this.#createDepthShader();
}
#depthShader;
/* -------------------------------------------- */
/**
* Create the depth shader and configure it.
* @returns {DepthSamplerShader}
*/
#createDepthShader() {
const depthShader = this.constructor.depthShaderClass.create();
this._configureDepthShader(depthShader);
return depthShader;
}
/* -------------------------------------------- */
/**
* One-time configuration that is called when the depth shader is created.
* @param {DepthSamplerShader} depthShader The depth shader
* @protected
*/
_configureDepthShader(depthShader) {}
}

View File

@@ -0,0 +1,339 @@
/**
* The shader definition which powers the TokenRing.
*/
class TokenRingSamplerShader extends PrimaryBaseSamplerShader {
/** @override */
static classPluginName = "tokenRingBatch";
/* -------------------------------------------- */
/** @override */
static pausable = false;
/* -------------------------------------------- */
/** @inheritdoc */
static batchGeometry = [
...(super.batchGeometry ?? []),
{id: "aRingTextureCoord", size: 2, normalized: false, type: PIXI.TYPES.FLOAT},
{id: "aBackgroundTextureCoord", size: 2, normalized: false, type: PIXI.TYPES.FLOAT},
{id: "aRingColor", size: 4, normalized: true, type: PIXI.TYPES.UNSIGNED_BYTE},
{id: "aBackgroundColor", size: 4, normalized: true, type: PIXI.TYPES.UNSIGNED_BYTE},
{id: "aStates", size: 1, normalized: false, type: PIXI.TYPES.FLOAT},
{id: "aScaleCorrection", size: 2, normalized: false, type: PIXI.TYPES.FLOAT},
{id: "aRingColorBand", size: 2, normalized: false, type: PIXI.TYPES.FLOAT},
{id: "aTextureScaleCorrection", size: 1, normalized: false, type: PIXI.TYPES.FLOAT}
];
/* -------------------------------------------- */
/** @inheritdoc */
static batchVertexSize = super.batchVertexSize + 12;
/* -------------------------------------------- */
/** @inheritdoc */
static reservedTextureUnits = super.reservedTextureUnits + 1;
/* -------------------------------------------- */
/**
* A null UVs array used for nulled texture position.
* @type {Float32Array}
*/
static nullUvs = new Float32Array([0, 0, 0, 0, 0, 0, 0, 0]);
/* -------------------------------------------- */
/** @inheritdoc */
static batchDefaultUniforms(maxTex) {
return {
...super.batchDefaultUniforms(maxTex),
tokenRingTexture: maxTex + super.reservedTextureUnits,
time: 0
};
}
/* -------------------------------------------- */
/** @override */
static _preRenderBatch(batchRenderer) {
super._preRenderBatch(batchRenderer);
batchRenderer.renderer.texture.bind(CONFIG.Token.ring.ringClass.baseTexture,
batchRenderer.uniforms.tokenRingTexture);
batchRenderer.uniforms.time = canvas.app.ticker.lastTime / 1000;
batchRenderer.uniforms.debugColorBands = CONFIG.Token.ring.debugColorBands;
}
/* ---------------------------------------- */
/** @inheritdoc */
static _packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex) {
super._packInterleavedGeometry(element, attributeBuffer, indexBuffer, aIndex, iIndex);
const {float32View, uint32View} = attributeBuffer;
// Prepare token ring attributes
const vertexData = element.vertexData;
const trConfig = CONFIG.Token.ringClass;
const object = element.object.object || {};
const ringColor = PIXI.Color.shared.setValue(object.ring?.ringColorLittleEndian ?? 0xFFFFFF).toNumber();
const bkgColor = PIXI.Color.shared.setValue(object.ring?.bkgColorLittleEndian ?? 0xFFFFFF).toNumber();
const ringUvsFloat = object.ring?.ringUVs ?? trConfig.tokenRingSamplerShader.nullUvs;
const bkgUvsFloat = object.ring?.bkgUVs ?? trConfig.tokenRingSamplerShader.nullUvs;
const states = (object.ring?.effects ?? 0) + 0.5;
const scaleCorrectionX = (object.ring?.scaleCorrection ?? 1) * (object.ring?.scaleAdjustmentX ?? 1);
const scaleCorrectionY = (object.ring?.scaleCorrection ?? 1) * (object.ring?.scaleAdjustmentY ?? 1);
const colorBandRadiusStart = object.ring?.colorBand.startRadius ?? 0;
const colorBandRadiusEnd = object.ring?.colorBand.endRadius ?? 0;
const textureScaleAdjustment = object.ring?.textureScaleAdjustment ?? 1;
// Write attributes into buffer
const vertexSize = this.vertexSize;
const attributeOffset = PrimaryBaseSamplerShader.batchVertexSize;
for ( let i = 0, j = attributeOffset; i < vertexData.length; i += 2, j += vertexSize ) {
let k = aIndex + j;
float32View[k++] = ringUvsFloat[i];
float32View[k++] = ringUvsFloat[i + 1];
float32View[k++] = bkgUvsFloat[i];
float32View[k++] = bkgUvsFloat[i + 1];
uint32View[k++] = ringColor;
uint32View[k++] = bkgColor;
float32View[k++] = states;
float32View[k++] = scaleCorrectionX;
float32View[k++] = scaleCorrectionY;
float32View[k++] = colorBandRadiusStart;
float32View[k++] = colorBandRadiusEnd;
float32View[k++] = textureScaleAdjustment;
}
}
/* ---------------------------------------- */
/* GLSL Shader Code */
/* ---------------------------------------- */
/**
* The fragment shader header.
* @type {string}
*/
static #FRAG_HEADER = `
const uint STATE_RING_PULSE = 0x02U;
const uint STATE_RING_GRADIENT = 0x04U;
const uint STATE_BKG_WAVE = 0x08U;
const uint STATE_INVISIBLE = 0x10U;
/* -------------------------------------------- */
bool hasState(in uint state) {
return (vStates & state) == state;
}
/* -------------------------------------------- */
vec2 rotation(in vec2 uv, in float a) {
uv -= 0.5;
float s = sin(a);
float c = cos(a);
return uv * mat2(c, -s, s, c) + 0.5;
}
/* -------------------------------------------- */
float normalizedCos(in float val) {
return (cos(val) + 1.0) * 0.5;
}
/* -------------------------------------------- */
float wave(in float dist) {
float sinWave = 0.5 * (sin(-time * 4.0 + dist * 100.0) + 1.0);
return mix(1.0, 0.55 * sinWave + 0.8, clamp(1.0 - dist, 0.0, 1.0));
}
/* -------------------------------------------- */
vec4 colorizeTokenRing(in vec4 tokenRing, in float dist) {
if ( tokenRing.a > 0.0 ) tokenRing.rgb /= tokenRing.a;
vec3 rcol = hasState(STATE_RING_PULSE)
? mix(tokenRing.rrr, tokenRing.rrr * 0.35, (cos(time * 2.0) + 1.0) * 0.5)
: tokenRing.rrr;
vec3 ccol = vRingColor * rcol;
vec3 gcol = hasState(STATE_RING_GRADIENT)
? mix(ccol, vBackgroundColor * tokenRing.r, smoothstep(0.0, 1.0, dot(rotation(vTextureCoord, time), vec2(0.5))))
: ccol;
vec3 col = mix(tokenRing.rgb, gcol, step(vRingColorBand.x, dist) - step(vRingColorBand.y, dist));
return vec4(col, 1.0) * tokenRing.a;
}
/* -------------------------------------------- */
vec4 colorizeTokenBackground(in vec4 tokenBackground, in float dist) {
if (tokenBackground.a > 0.0) tokenBackground.rgb /= tokenBackground.a;
float wave = hasState(STATE_BKG_WAVE) ? (0.5 + wave(dist) * 1.5) : 1.0;
vec3 bgColor = tokenBackground.rgb;
vec3 tintColor = vBackgroundColor.rgb;
vec3 resultColor;
// Overlay blend mode
if ( tintColor == vec3(1.0, 1.0, 1.0) ) {
// If tint color is pure white, keep the original background color
resultColor = bgColor;
} else {
// Overlay blend mode
for ( int i = 0; i < 3; i++ ) {
if ( bgColor[i] < 0.5 ) resultColor[i] = 2.0 * bgColor[i] * tintColor[i];
else resultColor[i] = 1.0 - 2.0 * (1.0 - bgColor[i]) * (1.0 - tintColor[i]);
}
}
return vec4(resultColor, 1.0) * tokenBackground.a * wave;
}
/* -------------------------------------------- */
vec4 processTokenColor(in vec4 finalColor) {
if ( !hasState(STATE_INVISIBLE) ) return finalColor;
// Computing halo
float lum = perceivedBrightness(finalColor.rgb);
vec3 haloColor = vec3(lum) * vec3(0.5, 1.0, 1.0);
// Construct final image
return vec4(haloColor, 1.0) * finalColor.a
* (0.55 + normalizedCos(time * 2.0) * 0.25);
}
/* -------------------------------------------- */
vec4 blend(vec4 src, vec4 dst) {
return src + (dst * (1.0 - src.a));
}
/* -------------------------------------------- */
float getTokenTextureClip() {
return step(3.5,
step(0.0, vTextureCoord.x) +
step(0.0, vTextureCoord.y) +
step(vTextureCoord.x, 1.0) +
step(vTextureCoord.y, 1.0));
}
`;
/* ---------------------------------------- */
/**
* Fragment shader body.
* @type {string}
*/
static #FRAG_MAIN = `
vec4 color;
vec4 result;
%forloop%
if ( vStates == 0U ) result = color * vColor;
else {
// Compute distances
vec2 scaledDistVec = (vOrigTextureCoord - 0.5) * 2.0 * vScaleCorrection;
// Euclidean distance computation
float dist = length(scaledDistVec);
// Rectangular distance computation
vec2 absScaledDistVec = abs(scaledDistVec);
float rectangularDist = max(absScaledDistVec.x, absScaledDistVec.y);
// Clip token texture color (necessary when a mesh is padded on x and/or y axis)
color *= getTokenTextureClip();
// Blend token texture, token ring and token background
result = blend(
processTokenColor(color * (vColor / vColor.a)),
blend(
colorizeTokenRing(texture(tokenRingTexture, vRingTextureCoord), dist),
colorizeTokenBackground(texture(tokenRingTexture, vBackgroundTextureCoord), dist)
) * step(rectangularDist, 1.0)
) * vColor.a;
}
`;
/* ---------------------------------------- */
/**
* Fragment shader body for debug code.
* @type {string}
*/
static #FRAG_MAIN_DEBUG = `
if ( debugColorBands ) {
vec2 scaledDistVec = (vTextureCoord - 0.5) * 2.0 * vScaleCorrection;
float dist = length(scaledDistVec);
result.rgb += vec3(0.0, 0.5, 0.0) * (step(vRingColorBand.x, dist) - step(vRingColorBand.y, dist));
}
`;
/* ---------------------------------------- */
/** @override */
static _batchVertexShader = `
in vec2 aRingTextureCoord;
in vec2 aBackgroundTextureCoord;
in vec2 aScaleCorrection;
in vec2 aRingColorBand;
in vec4 aRingColor;
in vec4 aBackgroundColor;
in float aTextureScaleCorrection;
in float aStates;
out vec2 vRingTextureCoord;
out vec2 vBackgroundTextureCoord;
out vec2 vOrigTextureCoord;
flat out vec2 vRingColorBand;
flat out vec3 vRingColor;
flat out vec3 vBackgroundColor;
flat out vec2 vScaleCorrection;
flat out uint vStates;
void _main(out vec2 vertexPosition, out vec2 textureCoord, out vec4 color) {
vRingTextureCoord = aRingTextureCoord;
vBackgroundTextureCoord = aBackgroundTextureCoord;
vRingColor = aRingColor.rgb;
vBackgroundColor = aBackgroundColor.rgb;
vStates = uint(aStates);
vScaleCorrection = aScaleCorrection;
vRingColorBand = aRingColorBand;
vOrigTextureCoord = aTextureCoord;
vertexPosition = (translationMatrix * vec3(aVertexPosition, 1.0)).xy;
textureCoord = (aTextureCoord - 0.5) * aTextureScaleCorrection + 0.5;
color = aColor * tint;
}
`;
/* -------------------------------------------- */
/** @override */
static _batchFragmentShader = `
in vec2 vRingTextureCoord;
in vec2 vBackgroundTextureCoord;
in vec2 vOrigTextureCoord;
flat in vec3 vRingColor;
flat in vec3 vBackgroundColor;
flat in vec2 vScaleCorrection;
flat in vec2 vRingColorBand;
flat in uint vStates;
uniform sampler2D tokenRingTexture;
uniform float time;
uniform bool debugColorBands;
${this.CONSTANTS}
${this.PERCEIVED_BRIGHTNESS}
${TokenRingSamplerShader.#FRAG_HEADER}
vec4 _main() {
${TokenRingSamplerShader.#FRAG_MAIN}
${TokenRingSamplerShader.#FRAG_MAIN_DEBUG}
return result;
}
`;
}