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,330 @@
/**
* A mixin which decorates a PIXI.Filter or PIXI.Shader with common properties.
* @category - Mixins
* @param {typeof PIXI.Shader} ShaderClass The parent ShaderClass class being mixed.
* @returns {typeof BaseShaderMixin} A Shader/Filter subclass mixed with BaseShaderMixin features.
* @mixin
*/
const BaseShaderMixin = ShaderClass => {
class BaseShaderMixin extends ShaderClass {
/**
* Useful constant values computed at compile time
* @type {string}
*/
static CONSTANTS = `
const float PI = 3.141592653589793;
const float TWOPI = 6.283185307179586;
const float INVPI = 0.3183098861837907;
const float INVTWOPI = 0.15915494309189535;
const float SQRT2 = 1.4142135623730951;
const float SQRT1_2 = 0.7071067811865476;
const float SQRT3 = 1.7320508075688772;
const float SQRT1_3 = 0.5773502691896257;
const vec3 BT709 = vec3(0.2126, 0.7152, 0.0722);
`;
/* -------------------------------------------- */
/**
* Fast approximate perceived brightness computation
* Using Digital ITU BT.709 : Exact luminance factors
* @type {string}
*/
static PERCEIVED_BRIGHTNESS = `
float perceivedBrightness(in vec3 color) { return sqrt(dot(BT709, color * color)); }
float perceivedBrightness(in vec4 color) { return perceivedBrightness(color.rgb); }
float reversePerceivedBrightness(in vec3 color) { return 1.0 - perceivedBrightness(color); }
float reversePerceivedBrightness(in vec4 color) { return 1.0 - perceivedBrightness(color.rgb); }
`;
/* -------------------------------------------- */
/**
* Convertion functions for sRGB and Linear RGB.
* @type {string}
*/
static COLOR_SPACES = `
float luminance(in vec3 c) { return dot(BT709, c); }
vec3 linear2grey(in vec3 c) { return vec3(luminance(c)); }
vec3 linear2srgb(in vec3 c) {
vec3 a = 12.92 * c;
vec3 b = 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055;
vec3 s = step(vec3(0.0031308), c);
return mix(a, b, s);
}
vec3 srgb2linear(in vec3 c) {
vec3 a = c / 12.92;
vec3 b = pow((c + 0.055) / 1.055, vec3(2.4));
vec3 s = step(vec3(0.04045), c);
return mix(a, b, s);
}
vec3 srgb2linearFast(in vec3 c) { return c * c; }
vec3 linear2srgbFast(in vec3 c) { return sqrt(c); }
vec3 colorClamp(in vec3 c) { return clamp(c, vec3(0.0), vec3(1.0)); }
vec4 colorClamp(in vec4 c) { return clamp(c, vec4(0.0), vec4(1.0)); }
vec3 tintColorLinear(in vec3 color, in vec3 tint, in float intensity) {
float t = luminance(tint);
float c = luminance(color);
return mix(color, mix(
mix(tint, vec3(1.0), (c - t) / (1.0 - t)),
tint * (c / t),
step(c, t)
), intensity);
}
vec3 tintColor(in vec3 color, in vec3 tint, in float intensity) {
return linear2srgbFast(tintColorLinear(srgb2linearFast(color), srgb2linearFast(tint), intensity));
}
`;
/* -------------------------------------------- */
/**
* Fractional Brownian Motion for a given number of octaves
* @param {number} [octaves=4]
* @param {number} [amp=1.0]
* @returns {string}
*/
static FBM(octaves = 4, amp = 1.0) {
return `float fbm(in vec2 uv) {
float total = 0.0, amp = ${amp.toFixed(1)};
for (int i = 0; i < ${octaves}; i++) {
total += noise(uv) * amp;
uv += uv;
amp *= 0.5;
}
return total;
}`;
}
/* -------------------------------------------- */
/**
* High Quality Fractional Brownian Motion
* @param {number} [octaves=3]
* @returns {string}
*/
static FBMHQ(octaves = 3) {
return `float fbm(in vec2 uv, in float smoothness) {
float s = exp2(-smoothness);
float f = 1.0;
float a = 1.0;
float t = 0.0;
for( int i = 0; i < ${octaves}; i++ ) {
t += a * noise(f * uv);
f *= 2.0;
a *= s;
}
return t;
}`;
}
/* -------------------------------------------- */
/**
* Angular constraint working with coordinates on the range [-1, 1]
* => coord: Coordinates
* => angle: Angle in radians
* => smoothness: Smoothness of the pie
* => l: Length of the pie.
* @type {string}
*/
static PIE = `
float pie(in vec2 coord, in float angle, in float smoothness, in float l) {
coord.x = abs(coord.x);
vec2 va = vec2(sin(angle), cos(angle));
float lg = length(coord) - l;
float clg = length(coord - va * clamp(dot(coord, va) , 0.0, l));
return smoothstep(0.0, smoothness, max(lg, clg * sign(va.y * coord.x - va.x * coord.y)));
}`;
/* -------------------------------------------- */
/**
* A conventional pseudo-random number generator with the "golden" numbers, based on uv position
* @type {string}
*/
static PRNG_LEGACY = `
float random(in vec2 uv) {
return fract(cos(dot(uv, vec2(12.9898, 4.1414))) * 43758.5453);
}`;
/* -------------------------------------------- */
/**
* A pseudo-random number generator based on uv position which does not use cos/sin
* This PRNG replaces the old PRNG_LEGACY to workaround some driver bugs
* @type {string}
*/
static PRNG = `
float random(in vec2 uv) {
uv = mod(uv, 1000.0);
return fract( dot(uv, vec2(5.23, 2.89)
* fract((2.41 * uv.x + 2.27 * uv.y)
* 251.19)) * 551.83);
}`;
/* -------------------------------------------- */
/**
* A Vec2 pseudo-random generator, based on uv position
* @type {string}
*/
static PRNG2D = `
vec2 random(in vec2 uv) {
vec2 uvf = fract(uv * vec2(0.1031, 0.1030));
uvf += dot(uvf, uvf.yx + 19.19);
return fract((uvf.x + uvf.y) * uvf);
}`;
/* -------------------------------------------- */
/**
* A Vec3 pseudo-random generator, based on uv position
* @type {string}
*/
static PRNG3D = `
vec3 random(in vec3 uv) {
return vec3(fract(cos(dot(uv, vec3(12.9898, 234.1418, 152.01))) * 43758.5453),
fract(sin(dot(uv, vec3(80.9898, 545.8937, 151515.12))) * 23411.1789),
fract(cos(dot(uv, vec3(01.9898, 1568.5439, 154.78))) * 31256.8817));
}`;
/* -------------------------------------------- */
/**
* A conventional noise generator
* @type {string}
*/
static NOISE = `
float noise(in vec2 uv) {
const vec2 d = vec2(0.0, 1.0);
vec2 b = floor(uv);
vec2 f = smoothstep(vec2(0.), vec2(1.0), fract(uv));
return mix(
mix(random(b), random(b + d.yx), f.x),
mix(random(b + d.xy), random(b + d.yy), f.x),
f.y
);
}`;
/* -------------------------------------------- */
/**
* Convert a Hue-Saturation-Brightness color to RGB - useful to convert polar coordinates to RGB
* @type {string}
*/
static HSB2RGB = `
vec3 hsb2rgb(in vec3 c) {
vec3 rgb = clamp(abs(mod(c.x*6.0+vec3(0.0,4.0,2.0), 6.0)-3.0)-1.0, 0.0, 1.0 );
rgb = rgb*rgb*(3.0-2.0*rgb);
return c.z * mix(vec3(1.0), rgb, c.y);
}`;
/* -------------------------------------------- */
/**
* Declare a wave function in a shader -> wcos (default), wsin or wtan.
* Wave on the [v1,v2] range with amplitude -> a and speed -> speed.
* @param {string} [func="cos"] the math function to use
* @returns {string}
*/
static WAVE(func="cos") {
return `
float w${func}(in float v1, in float v2, in float a, in float speed) {
float w = ${func}( speed + a ) + 1.0;
return (v1 - v2) * (w * 0.5) + v2;
}`;
}
/* -------------------------------------------- */
/**
* Rotation function.
* @type {string}
*/
static ROTATION = `
mat2 rot(in float a) {
float s = sin(a);
float c = cos(a);
return mat2(c, -s, s, c);
}
`;
/* -------------------------------------------- */
/**
* Voronoi noise function. Needs PRNG2D and CONSTANTS.
* @see PRNG2D
* @see CONSTANTS
* @type {string}
*/
static VORONOI = `
vec3 voronoi(in vec2 uv, in float t, in float zd) {
vec3 vor = vec3(0.0, 0.0, zd);
vec2 uvi = floor(uv);
vec2 uvf = fract(uv);
for ( float j = -1.0; j <= 1.0; j++ ) {
for ( float i = -1.0; i <= 1.0; i++ ) {
vec2 uvn = vec2(i, j);
vec2 uvr = 0.5 * sin(TWOPI * random(uvi + uvn) + t) + 0.5;
uvr = 0.5 * sin(TWOPI * uvr + t) + 0.5;
vec2 uvd = uvn + uvr - uvf;
float dist = length(uvd);
if ( dist < vor.z ) {
vor.xy = uvr;
vor.z = dist;
}
}
}
return vor;
}
vec3 voronoi(in vec2 vuv, in float zd) {
return voronoi(vuv, 0.0, zd);
}
vec3 voronoi(in vec3 vuv, in float zd) {
return voronoi(vuv.xy, vuv.z, zd);
}
`;
/* -------------------------------------------- */
/**
* Enables GLSL 1.0 backwards compatibility in GLSL 3.00 ES vertex shaders.
* @type {string}
*/
static GLSL1_COMPATIBILITY_VERTEX = `
#define attribute in
#define varying out
`;
/* -------------------------------------------- */
/**
* Enables GLSL 1.0 backwards compatibility in GLSL 3.00 ES fragment shaders.
* @type {string}
*/
static GLSL1_COMPATIBILITY_FRAGMENT = `
#define varying in
#define texture2D texture
#define textureCube texture
#define texture2DProj textureProj
#define texture2DLodEXT textureLod
#define texture2DProjLodEXT textureProjLod
#define textureCubeLodEXT textureLod
#define texture2DGradEXT textureGrad
#define texture2DProjGradEXT textureProjGrad
#define textureCubeGradEXT textureGrad
#define gl_FragDepthEXT gl_FragDepth
`;
}
return BaseShaderMixin;
};

View File

@@ -0,0 +1,102 @@
/**
* This class defines an interface which all shaders utilize.
* @extends {PIXI.Shader}
* @property {PIXI.Program} program The program to use with this shader.
* @property {object} uniforms The current uniforms of the Shader.
* @mixes BaseShaderMixin
* @abstract
*/
class AbstractBaseShader extends BaseShaderMixin(PIXI.Shader) {
constructor(program, uniforms) {
super(program, foundry.utils.deepClone(uniforms));
/**
* The initial values of the shader uniforms.
* @type {object}
*/
this.initialUniforms = uniforms;
}
/* -------------------------------------------- */
/**
* The raw vertex shader used by this class.
* A subclass of AbstractBaseShader must implement the vertexShader static field.
* @type {string}
*/
static vertexShader = "";
/**
* The raw fragment shader used by this class.
* A subclass of AbstractBaseShader must implement the fragmentShader static field.
* @type {string}
*/
static fragmentShader = "";
/**
* The default uniform values for the shader.
* A subclass of AbstractBaseShader must implement the defaultUniforms static field.
* @type {object}
*/
static defaultUniforms = {};
/* -------------------------------------------- */
/**
* A factory method for creating the shader using its defined default values
* @param {object} initialUniforms
* @returns {AbstractBaseShader}
*/
static create(initialUniforms) {
const program = PIXI.Program.from(this.vertexShader, this.fragmentShader);
const uniforms = foundry.utils.mergeObject(this.defaultUniforms, initialUniforms,
{inplace: false, insertKeys: false});
const shader = new this(program, uniforms);
shader._configure();
return shader;
}
/* -------------------------------------------- */
/**
* Reset the shader uniforms back to their initial values.
*/
reset() {
for (let [k, v] of Object.entries(this.initialUniforms)) {
this.uniforms[k] = foundry.utils.deepClone(v);
}
}
/* ---------------------------------------- */
/**
* A one time initialization performed on creation.
* @protected
*/
_configure() {}
/* ---------------------------------------- */
/**
* Perform operations which are required before binding the Shader to the Renderer.
* @param {PIXI.DisplayObject} mesh The mesh display object linked to this shader.
* @param {PIXI.Renderer} renderer The renderer
* @protected
* @internal
*/
_preRender(mesh, renderer) {}
/* -------------------------------------------- */
/* Deprecations and Compatibility */
/* -------------------------------------------- */
/**
* @deprecated since v12
* @ignore
*/
get _defaults() {
const msg = "AbstractBaseShader#_defaults is deprecated in favor of AbstractBaseShader#initialUniforms.";
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14});
return this.initialUniforms;
}
}

View File

@@ -0,0 +1,35 @@
/**
* An abstract filter which provides a framework for reusable definition
* @extends {PIXI.Filter}
* @mixes BaseShaderMixin
* @abstract
*/
class AbstractBaseFilter extends BaseShaderMixin(PIXI.Filter) {
/**
* The default uniforms used by the filter
* @type {object}
*/
static defaultUniforms = {};
/**
* The fragment shader which renders this filter.
* @type {string}
*/
static fragmentShader = undefined;
/**
* The vertex shader which renders this filter.
* @type {string}
*/
static vertexShader = undefined;
/**
* A factory method for creating the filter using its defined default values.
* @param {object} [initialUniforms] Initial uniform values which override filter defaults
* @returns {AbstractBaseFilter} The constructed AbstractFilter instance.
*/
static create(initialUniforms={}) {
return new this(this.vertexShader, this.fragmentShader, {...this.defaultUniforms, ...initialUniforms});
}
}

View File

@@ -0,0 +1,46 @@
/**
* This class defines an interface for masked custom filters
*/
class AbstractBaseMaskFilter extends AbstractBaseFilter {
/**
* The default vertex shader used by all instances of AbstractBaseMaskFilter
* @type {string}
*/
static vertexShader = `
attribute vec2 aVertexPosition;
uniform mat3 projectionMatrix;
uniform vec2 screenDimensions;
uniform vec4 inputSize;
uniform vec4 outputFrame;
varying vec2 vTextureCoord;
varying vec2 vMaskTextureCoord;
vec4 filterVertexPosition( void ) {
vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy;
return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0., 1.);
}
// getting normalized coord for the tile texture
vec2 filterTextureCoord( void ) {
return aVertexPosition * (outputFrame.zw * inputSize.zw);
}
// getting normalized coord for a screen sized mask render texture
vec2 filterMaskTextureCoord( in vec2 textureCoord ) {
return (textureCoord * inputSize.xy + outputFrame.xy) / screenDimensions;
}
void main() {
vTextureCoord = filterTextureCoord();
vMaskTextureCoord = filterMaskTextureCoord(vTextureCoord);
gl_Position = filterVertexPosition();
}`;
/** @override */
apply(filterManager, input, output, clear, currentState) {
this.uniforms.screenDimensions = canvas.screenDimensions;
filterManager.applyFilter(this, input, output, clear);
}
}

View File

@@ -0,0 +1,364 @@
/**
* Apply a vertical or horizontal gaussian blur going inward by using alpha as the penetrating channel.
* @param {boolean} horizontal If the pass is horizontal (true) or vertical (false).
* @param {number} [strength=8] Strength of the blur (distance of sampling).
* @param {number} [quality=4] Number of passes to generate the blur. More passes = Higher quality = Lower Perf.
* @param {number} [resolution=PIXI.Filter.defaultResolution] Resolution of the filter.
* @param {number} [kernelSize=5] Number of kernels to use. More kernels = Higher quality = Lower Perf.
*/
class AlphaBlurFilterPass extends PIXI.Filter {
constructor(horizontal, strength=8, quality=4, resolution=PIXI.Filter.defaultResolution, kernelSize=5) {
const vertSrc = AlphaBlurFilterPass.vertTemplate(kernelSize, horizontal);
const fragSrc = AlphaBlurFilterPass.fragTemplate(kernelSize);
super(vertSrc, fragSrc);
this.horizontal = horizontal;
this.strength = strength;
this.passes = quality;
this.resolution = resolution;
}
/**
* If the pass is horizontal (true) or vertical (false).
* @type {boolean}
*/
horizontal;
/**
* Strength of the blur (distance of sampling).
* @type {number}
*/
strength;
/**
* The number of passes to generate the blur.
* @type {number}
*/
passes;
/* -------------------------------------------- */
/**
* The quality of the filter is defined by its number of passes.
* @returns {number}
*/
get quality() {
return this.passes;
}
set quality(value) {
this.passes = value;
}
/* -------------------------------------------- */
/**
* The strength of the blur filter in pixels.
* @returns {number}
*/
get blur() {
return this.strength;
}
set blur(value) {
this.padding = 1 + (Math.abs(value) * 2);
this.strength = value;
}
/* -------------------------------------------- */
/**
* The kernels containing the gaussian constants.
* @type {Record<number, number[]>}
*/
static GAUSSIAN_VALUES = {
5: [0.153388, 0.221461, 0.250301],
7: [0.071303, 0.131514, 0.189879, 0.214607],
9: [0.028532, 0.067234, 0.124009, 0.179044, 0.20236],
11: [0.0093, 0.028002, 0.065984, 0.121703, 0.175713, 0.198596],
13: [0.002406, 0.009255, 0.027867, 0.065666, 0.121117, 0.174868, 0.197641],
15: [0.000489, 0.002403, 0.009246, 0.02784, 0.065602, 0.120999, 0.174697, 0.197448]
};
/* -------------------------------------------- */
/**
* The fragment template generator
* @param {number} kernelSize The number of kernels to use.
* @returns {string} The generated fragment shader.
*/
static fragTemplate(kernelSize) {
return `
varying vec2 vBlurTexCoords[${kernelSize}];
varying vec2 vTextureCoords;
uniform sampler2D uSampler;
void main(void) {
vec4 finalColor = vec4(0.0);
${this.generateBlurFragSource(kernelSize)}
finalColor.rgb *= clamp(mix(-1.0, 1.0, finalColor.a), 0.0, 1.0);
gl_FragColor = finalColor;
}
`;
}
/* -------------------------------------------- */
/**
* The vertex template generator
* @param {number} kernelSize The number of kernels to use.
* @param {boolean} horizontal If the vertex should handle horizontal or vertical pass.
* @returns {string} The generated vertex shader.
*/
static vertTemplate(kernelSize, horizontal) {
return `
attribute vec2 aVertexPosition;
uniform mat3 projectionMatrix;
uniform float strength;
varying vec2 vBlurTexCoords[${kernelSize}];
varying vec2 vTextureCoords;
uniform vec4 inputSize;
uniform vec4 outputFrame;
vec4 filterVertexPosition( void ) {
vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy;
return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
}
vec2 filterTextureCoord( void ) {
return aVertexPosition * (outputFrame.zw * inputSize.zw);
}
void main(void) {
gl_Position = filterVertexPosition();
vec2 textureCoord = vTextureCoords = filterTextureCoord();
${this.generateBlurVertSource(kernelSize, horizontal)}
}
`;
}
/* -------------------------------------------- */
/**
* Generating the dynamic part of the blur in the fragment
* @param {number} kernelSize The number of kernels to use.
* @returns {string} The dynamic blur part.
*/
static generateBlurFragSource(kernelSize) {
const kernel = AlphaBlurFilterPass.GAUSSIAN_VALUES[kernelSize];
const halfLength = kernel.length;
let value;
let blurLoop = "";
for ( let i = 0; i < kernelSize; i++ ) {
blurLoop += `finalColor += texture2D(uSampler, vBlurTexCoords[${i.toString()}])`;
value = i >= halfLength ? kernelSize - i - 1 : i;
blurLoop += ` * ${kernel[value].toString()};\n`;
}
return blurLoop;
}
/* -------------------------------------------- */
/**
* Generating the dynamic part of the blur in the vertex
* @param {number} kernelSize The number of kernels to use.
* @param {boolean} horizontal If the vertex should handle horizontal or vertical pass.
* @returns {string} The dynamic blur part.
*/
static generateBlurVertSource(kernelSize, horizontal) {
const halfLength = Math.ceil(kernelSize / 2);
let blurLoop = "";
for ( let i = 0; i < kernelSize; i++ ) {
const khl = i - (halfLength - 1);
blurLoop += horizontal
? `vBlurTexCoords[${i.toString()}] = textureCoord + vec2(${khl}.0 * strength, 0.0);`
: `vBlurTexCoords[${i.toString()}] = textureCoord + vec2(0.0, ${khl}.0 * strength);`;
blurLoop += "\n";
}
return blurLoop;
}
/* -------------------------------------------- */
/** @override */
apply(filterManager, input, output, clearMode) {
// Define strength
const ow = output ? output.width : filterManager.renderer.width;
const oh = output ? output.height : filterManager.renderer.height;
this.uniforms.strength = (this.horizontal ? (1 / ow) * (ow / input.width) : (1 / oh) * (oh / input.height))
* this.strength / this.passes;
// Single pass
if ( this.passes === 1 ) {
return filterManager.applyFilter(this, input, output, clearMode);
}
// Multi-pass
const renderTarget = filterManager.getFilterTexture();
const renderer = filterManager.renderer;
let flip = input;
let flop = renderTarget;
// Initial application
this.state.blend = false;
filterManager.applyFilter(this, flip, flop, PIXI.CLEAR_MODES.CLEAR);
// Additional passes
for ( let i = 1; i < this.passes - 1; i++ ) {
filterManager.bindAndClear(flip, PIXI.CLEAR_MODES.BLIT);
this.uniforms.uSampler = flop;
const temp = flop;
flop = flip;
flip = temp;
renderer.shader.bind(this);
renderer.geometry.draw(5);
}
// Final pass and return filter texture
this.state.blend = true;
filterManager.applyFilter(this, flop, output, clearMode);
filterManager.returnFilterTexture(renderTarget);
}
}
/* -------------------------------------------- */
/**
* Apply a gaussian blur going inward by using alpha as the penetrating channel.
* @param {number} [strength=8] Strength of the blur (distance of sampling).
* @param {number} [quality=4] Number of passes to generate the blur. More passes = Higher quality = Lower Perf.
* @param {number} [resolution=PIXI.Filter.defaultResolution] Resolution of the filter.
* @param {number} [kernelSize=5] Number of kernels to use. More kernels = Higher quality = Lower Perf.
*/
class AlphaBlurFilter extends PIXI.Filter {
constructor(strength=8, quality=4, resolution=PIXI.Filter.defaultResolution, kernelSize=5) {
super();
this.blurXFilter = new AlphaBlurFilterPass(true, strength, quality, resolution, kernelSize);
this.blurYFilter = new AlphaBlurFilterPass(false, strength, quality, resolution, kernelSize);
this.resolution = resolution;
this._repeatEdgePixels = false;
this.quality = quality;
this.blur = strength;
}
/* -------------------------------------------- */
/** @override */
apply(filterManager, input, output, clearMode) {
const xStrength = Math.abs(this.blurXFilter.strength);
const yStrength = Math.abs(this.blurYFilter.strength);
// Blur both directions
if ( xStrength && yStrength ) {
const renderTarget = filterManager.getFilterTexture();
this.blurXFilter.apply(filterManager, input, renderTarget, PIXI.CLEAR_MODES.CLEAR);
this.blurYFilter.apply(filterManager, renderTarget, output, clearMode);
filterManager.returnFilterTexture(renderTarget);
}
// Only vertical
else if ( yStrength ) this.blurYFilter.apply(filterManager, input, output, clearMode);
// Only horizontal
else this.blurXFilter.apply(filterManager, input, output, clearMode);
}
/* -------------------------------------------- */
/**
* Update the filter padding according to the blur strength value (0 if _repeatEdgePixels is active)
*/
updatePadding() {
this.padding = this._repeatEdgePixels ? 0
: Math.max(Math.abs(this.blurXFilter.strength), Math.abs(this.blurYFilter.strength)) * 2;
}
/* -------------------------------------------- */
/**
* The amount of blur is forwarded to the X and Y filters.
* @type {number}
*/
get blur() {
return this.blurXFilter.blur;
}
set blur(value) {
this.blurXFilter.blur = this.blurYFilter.blur = value;
this.updatePadding();
}
/* -------------------------------------------- */
/**
* The quality of blur defines the number of passes used by subsidiary filters.
* @type {number}
*/
get quality() {
return this.blurXFilter.quality;
}
set quality(value) {
this.blurXFilter.quality = this.blurYFilter.quality = value;
}
/* -------------------------------------------- */
/**
* Whether to repeat edge pixels, adding padding to the filter area.
* @type {boolean}
*/
get repeatEdgePixels() {
return this._repeatEdgePixels;
}
set repeatEdgePixels(value) {
this._repeatEdgePixels = value;
this.updatePadding();
}
/* -------------------------------------------- */
/**
* Provided for completeness with PIXI.BlurFilter
* @type {number}
*/
get blurX() {
return this.blurXFilter.blur;
}
set blurX(value) {
this.blurXFilter.blur = value;
this.updatePadding();
}
/* -------------------------------------------- */
/**
* Provided for completeness with PIXI.BlurFilter
* @type {number}
*/
get blurY() {
return this.blurYFilter.blur;
}
set blurY(value) {
this.blurYFilter.blur = value;
this.updatePadding();
}
/* -------------------------------------------- */
/**
* Provided for completeness with PIXI.BlurFilter
* @type {number}
*/
get blendMode() {
return this.blurYFilter.blendMode;
}
set blendMode(value) {
this.blurYFilter.blendMode = value;
}
}

View File

@@ -0,0 +1,207 @@
/**
* This filter handles masking and post-processing for visual effects.
*/
class VisualEffectsMaskingFilter extends AbstractBaseMaskFilter {
/** @override */
static create({postProcessModes, ...initialUniforms}={}) {
const fragmentShader = this.fragmentShader(postProcessModes);
const uniforms = {...this.defaultUniforms, ...initialUniforms};
return new this(this.vertexShader, fragmentShader, uniforms);
}
/**
* Code to determine which post-processing effect is applied in this filter.
* @type {string[]}
*/
#postProcessModes;
/* -------------------------------------------- */
/**
* Masking modes.
* @enum {number}
*/
static FILTER_MODES = Object.freeze({
BACKGROUND: 0,
ILLUMINATION: 1,
COLORATION: 2
});
/* -------------------------------------------- */
/** @override */
static defaultUniforms = {
tint: [1, 1, 1],
screenDimensions: [1, 1],
enableVisionMasking: true,
visionTexture: null,
darknessLevelTexture: null,
exposure: 0,
contrast: 0,
saturation: 0,
mode: 0,
ambientDarkness: [0, 0, 0],
ambientDaylight: [1, 1, 1],
replacementColor: [0, 0, 0]
};
/* -------------------------------------------- */
/**
* Update the filter shader with new post-process modes.
* @param {string[]} [postProcessModes=[]] New modes to apply.
* @param {object} [uniforms={}] Uniforms value to update.
*/
updatePostprocessModes(postProcessModes=[], uniforms={}) {
// Update shader uniforms
for ( let [uniform, value] of Object.entries(uniforms) ) {
if ( uniform in this.uniforms ) this.uniforms[uniform] = value;
}
// Update the shader program if post-processing modes have changed
if ( postProcessModes.equals(this.#postProcessModes) ) return;
this.#postProcessModes = postProcessModes;
this.program = PIXI.Program.from(this.constructor.vertexShader,
this.constructor.fragmentShader(this.#postProcessModes));
}
/* -------------------------------------------- */
/**
* Remove all post-processing modes and reset some key uniforms.
*/
reset() {
this.#postProcessModes = [];
this.program = PIXI.Program.from(this.constructor.vertexShader,
this.constructor.fragmentShader());
const uniforms = ["tint", "exposure", "contrast", "saturation"];
for ( const uniform of uniforms ) {
this.uniforms[uniform] = this.constructor.defaultUniforms[uniform];
}
}
/* -------------------------------------------- */
/** @override */
apply(filterManager, input, output, clear, currentState) {
const c = canvas.colors;
const u = this.uniforms;
if ( u.mode === this.constructor.FILTER_MODES.ILLUMINATION ) {
c.ambientDarkness.applyRGB(u.ambientDarkness);
c.ambientDaylight.applyRGB(u.ambientDaylight);
}
super.apply(filterManager, input, output, clear, currentState);
}
/* -------------------------------------------- */
/**
* Filter post-process techniques.
* @enum {{id: string, glsl: string}}
*/
static POST_PROCESS_TECHNIQUES = {
EXPOSURE: {
id: "EXPOSURE",
glsl: `if ( exposure != 0.0 ) {
finalColor.rgb *= (1.0 + exposure);
}`
},
CONTRAST: {
id: "CONTRAST",
glsl: `if ( contrast != 0.0 ) {
finalColor.rgb = (finalColor.rgb - 0.5) * (contrast + 1.0) + 0.5;
}`
},
SATURATION: {
id: "SATURATION",
glsl: `if ( saturation != 0.0 ) {
float reflection = perceivedBrightness(finalColor.rgb);
finalColor.rgb = mix(vec3(reflection), finalColor.rgb, 1.0 + saturation) * finalColor.a;
}`
}
};
/* -------------------------------------------- */
/**
* Memory allocations and headers for the VisualEffectsMaskingFilter
* @returns {string} The filter header according to the filter mode.
*/
static fragmentHeader = `
varying vec2 vTextureCoord;
varying vec2 vMaskTextureCoord;
uniform float contrast;
uniform float saturation;
uniform float exposure;
uniform vec3 ambientDarkness;
uniform vec3 ambientDaylight;
uniform vec3 replacementColor;
uniform vec3 tint;
uniform sampler2D uSampler;
uniform sampler2D visionTexture;
uniform sampler2D darknessLevelTexture;
uniform bool enableVisionMasking;
uniform int mode;
vec4 baseColor;
vec4 finalColor;
${this.CONSTANTS}
${this.PERCEIVED_BRIGHTNESS}
vec4 getReplacementColor() {
if ( mode == 0 ) return vec4(0.0);
if ( mode == 2 ) return vec4(replacementColor, 1.0);
float darknessLevel = texture2D(darknessLevelTexture, vMaskTextureCoord).r;
return vec4(mix(ambientDaylight, ambientDarkness, darknessLevel), 1.0);
}
`;
/* -------------------------------------------- */
/**
* The fragment core code.
* @type {string}
*/
static fragmentCore = `
// Get the base color from the filter sampler
finalColor = texture2D(uSampler, vTextureCoord);
// Handling vision masking
if ( enableVisionMasking ) {
finalColor = mix( getReplacementColor(),
finalColor,
texture2D(visionTexture, vMaskTextureCoord).r);
}
`;
/* -------------------------------------------- */
/**
* Construct filter post-processing code according to provided value.
* @param {string[]} postProcessModes Post-process modes to construct techniques.
* @returns {string} The constructed shader code for post-process techniques.
*/
static fragmentPostProcess(postProcessModes=[]) {
return postProcessModes.reduce((s, t) => s + this.POST_PROCESS_TECHNIQUES[t].glsl ?? "", "");
}
/* -------------------------------------------- */
/**
* Specify the fragment shader to use according to mode
* @param {string[]} postProcessModes
* @returns {string}
* @override
*/
static fragmentShader(postProcessModes=[]) {
return `
${this.fragmentHeader}
void main() {
${this.fragmentCore}
${this.fragmentPostProcess(postProcessModes)}
if ( enableVisionMasking ) finalColor *= vec4(tint, 1.0);
gl_FragColor = finalColor;
}
`;
}
}

View File

@@ -0,0 +1,122 @@
/**
* A filter used to apply color adjustments and other modifications to the environment.
*/
class PrimaryCanvasGroupAmbienceFilter extends AbstractBaseMaskFilter {
/** @override */
static fragmentShader = `
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
// Base ambience uniforms
uniform vec3 baseTint;
uniform float baseIntensity;
uniform float baseLuminosity;
uniform float baseSaturation;
uniform float baseShadows;
// Darkness ambience uniforms
uniform vec3 darkTint;
uniform float darkIntensity;
uniform float darkLuminosity;
uniform float darkSaturation;
uniform float darkShadows;
// Cycle enabled or disabled
uniform bool cycle;
// Textures
uniform sampler2D darknessLevelTexture;
uniform sampler2D uSampler;
// Varyings
varying vec2 vTextureCoord;
varying vec2 vMaskTextureCoord;
${this.CONSTANTS}
${this.COLOR_SPACES}
// Ambience parameters computed according to darkness level (per pixel)
vec3 tint;
float intensity;
float luminosity;
float saturation;
float shadows;
/* ----------------------------------------------------------------- */
/* Compute ambience parameters according to darkness level texture */
/* ----------------------------------------------------------------- */
void computeAmbienceParameters() {
float dl = texture2D(darknessLevelTexture, vMaskTextureCoord).r;
// Determine the tint based on base and dark ambience parameters
if ( baseIntensity > 0.0 ) tint = (cycle && darkIntensity > 0.0) ? mix(baseTint, darkTint, dl) : baseTint;
else if ( darkIntensity > 0.0 && cycle ) tint = darkTint;
else tint = vec3(1.0);
// Compute the luminosity based on the cycle condition
float luminosityBase = cycle ? mix(baseLuminosity, darkLuminosity, dl) : baseLuminosity;
luminosity = luminosityBase * (luminosityBase >= 0.0 ? 1.2 : 0.8);
// Compute the shadows based on the cycle condition
shadows = (cycle ? mix(baseShadows, darkShadows, dl) : baseShadows) * 0.15;
// Using a non-linear easing with intensity input value: x^2
intensity = cycle ? mix(baseIntensity * baseIntensity, darkIntensity * darkIntensity, dl)
: baseIntensity * baseIntensity;
// Compute the saturation based on the cycle condition
saturation = cycle ? mix(baseSaturation, darkSaturation, dl) : baseSaturation;
}
/* -------------------------------------------- */
void main() {
vec4 baseColor = texture2D(uSampler, vTextureCoord);
if ( baseColor.a > 0.0 ) {
computeAmbienceParameters();
// Unmultiply rgb with alpha channel
baseColor.rgb /= baseColor.a;
// Apply shadows and luminosity on sRGB values
if ( shadows > 0.0 ) {
float l = luminance(srgb2linearFast(baseColor.rgb));
baseColor.rgb *= min(l / shadows, 1.0);
}
if ( luminosity != 0.0 ) baseColor.rgb *= (1.0 + luminosity);
baseColor.rgb = srgb2linear(baseColor.rgb); // convert to linear before saturating and tinting
// Apply saturation and tint on linearized rgb
if ( saturation != 0.0 ) baseColor.rgb = mix(linear2grey(baseColor.rgb), baseColor.rgb, 1.0 + saturation);
if ( intensity > 0.0 ) baseColor.rgb = tintColorLinear(colorClamp(baseColor.rgb), tint, intensity);
else baseColor.rgb = colorClamp(baseColor.rgb);
baseColor.rgb = linear2srgb(baseColor.rgb); // convert back to sRGB
// Multiply rgb with alpha channel
baseColor.rgb *= baseColor.a;
}
// Output the result
gl_FragColor = baseColor;
}
`;
/** @override */
static defaultUniforms = {
uSampler: null,
darknessLevelTexture: null,
cycle: true,
baseTint: [1, 1, 1], // Important: The base tint uniform must be in linear RGB!
baseIntensity: 0,
baseLuminosity: 0,
baseSaturation: 0,
baseShadows: 0,
darkTint: [1, 1, 1], // Important: The dark tint uniform must be in linear RGB!
darkIntensity: 0,
darkLuminosity: 0,
darkSaturation: 0,
darkShadows: 0
};
}

View File

@@ -0,0 +1,148 @@
/**
* A filter which implements an inner or outer glow around the source texture.
* Inspired from https://github.com/pixijs/filters/tree/main/filters/glow
* @license MIT
*/
class GlowOverlayFilter extends AbstractBaseFilter {
/** @override */
padding = 6;
/**
* The inner strength of the glow.
* @type {number}
*/
innerStrength = 3;
/**
* The outer strength of the glow.
* @type {number}
*/
outerStrength = 3;
/**
* Should this filter auto-animate?
* @type {boolean}
*/
animated = true;
/** @inheritdoc */
static defaultUniforms = {
distance: 10,
glowColor: [1, 1, 1, 1],
quality: 0.1,
time: 0,
knockout: true,
alpha: 1
};
/**
* Dynamically create the fragment shader used for filters of this type.
* @param {number} quality
* @param {number} distance
* @returns {string}
*/
static createFragmentShader(quality, distance) {
return `
precision mediump float;
varying vec2 vTextureCoord;
varying vec4 vColor;
uniform sampler2D uSampler;
uniform float innerStrength;
uniform float outerStrength;
uniform float alpha;
uniform vec4 glowColor;
uniform vec4 inputSize;
uniform vec4 inputClamp;
uniform bool knockout;
const float PI = 3.14159265358979323846264;
const float DIST = ${distance.toFixed(0)}.0;
const float ANGLE_STEP_SIZE = min(${(1 / quality / distance).toFixed(7)}, PI * 2.0);
const float ANGLE_STEP_NUM = ceil(PI * 2.0 / ANGLE_STEP_SIZE);
const float MAX_TOTAL_ALPHA = ANGLE_STEP_NUM * DIST * (DIST + 1.0) / 2.0;
float getClip(in vec2 uv) {
return step(3.5,
step(inputClamp.x, uv.x) +
step(inputClamp.y, uv.y) +
step(uv.x, inputClamp.z) +
step(uv.y, inputClamp.w));
}
void main(void) {
vec2 px = inputSize.zw;
float totalAlpha = 0.0;
vec2 direction;
vec2 displaced;
vec4 curColor;
for (float angle = 0.0; angle < PI * 2.0; angle += ANGLE_STEP_SIZE) {
direction = vec2(cos(angle), sin(angle)) * px;
for (float curDistance = 0.0; curDistance < DIST; curDistance++) {
displaced = vTextureCoord + direction * (curDistance + 1.0);
curColor = texture2D(uSampler, displaced) * getClip(displaced);
totalAlpha += (DIST - curDistance) * (smoothstep(0.5, 1.0, curColor.a));
}
}
curColor = texture2D(uSampler, vTextureCoord);
float alphaRatio = (totalAlpha / MAX_TOTAL_ALPHA);
float innerGlowAlpha = (1.0 - alphaRatio) * innerStrength * smoothstep(0.6, 1.0, curColor.a);
float innerGlowStrength = min(1.0, innerGlowAlpha);
vec4 innerColor = mix(curColor, glowColor, innerGlowStrength);
float outerGlowAlpha = alphaRatio * outerStrength * (1.0 - smoothstep(0.35, 1.0, curColor.a));
float outerGlowStrength = min(1.0 - innerColor.a, outerGlowAlpha);
vec4 outerGlowColor = outerGlowStrength * glowColor.rgba;
if ( knockout ) {
float resultAlpha = outerGlowAlpha + innerGlowAlpha;
gl_FragColor = mix(vec4(glowColor.rgb * resultAlpha, resultAlpha), vec4(0.0), curColor.a);
}
else {
vec4 outerGlowColor = outerGlowStrength * glowColor.rgba * alpha;
gl_FragColor = innerColor + outerGlowColor;
}
}`;
}
/** @inheritdoc */
static vertexShader = `
precision mediump float;
attribute vec2 aVertexPosition;
uniform mat3 projectionMatrix;
uniform vec4 inputSize;
uniform vec4 outputFrame;
varying vec2 vTextureCoord;
void main(void) {
vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.0)) + outputFrame.xy;
gl_Position = vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
vTextureCoord = aVertexPosition * (outputFrame.zw * inputSize.zw);
}`;
/** @inheritdoc */
static create(initialUniforms={}) {
const uniforms = {...this.defaultUniforms, ...initialUniforms};
const fragmentShader = this.createFragmentShader(uniforms.quality, uniforms.distance);
return new this(this.vertexShader, fragmentShader, uniforms);
}
/* -------------------------------------------- */
/** @override */
apply(filterManager, input, output, clear) {
let strength = canvas.stage.worldTransform.d;
if ( this.animated && !canvas.photosensitiveMode ) {
const time = canvas.app.ticker.lastTime;
strength *= Math.oscillation(0.5, 2.0, time, 2000);
}
this.uniforms.outerStrength = this.outerStrength * strength;
this.uniforms.innerStrength = this.innerStrength * strength;
filterManager.applyFilter(this, input, output, clear);
}
}

View File

@@ -0,0 +1,36 @@
/**
* Invisibility effect filter for placeables.
*/
class InvisibilityFilter extends AbstractBaseFilter {
/** @override */
static fragmentShader = `
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
uniform vec3 color;
uniform sampler2D uSampler;
varying vec2 vTextureCoord;
${this.CONSTANTS}
${this.PERCEIVED_BRIGHTNESS}
void main() {
vec4 baseColor = texture2D(uSampler, vTextureCoord);
// Unmultiply rgb with alpha channel
if ( baseColor.a > 0.0 ) baseColor.rgb /= baseColor.a;
// Computing halo
float lum = perceivedBrightness(baseColor.rgb);
vec3 haloColor = vec3(lum) * color * 2.0;
// Construct final image
gl_FragColor = vec4(haloColor, 1.0) * 0.5 * baseColor.a;
}
`;
/** @override */
static defaultUniforms = {
uSampler: null,
color: [0.5, 1, 1]
};
}

View File

@@ -0,0 +1,190 @@
/**
* A filter which implements an outline.
* Inspired from https://github.com/pixijs/filters/tree/main/filters/outline
* @license MIT
*/
class OutlineOverlayFilter extends AbstractBaseFilter {
/** @override */
padding = 3;
/** @override */
autoFit = false;
/**
* If the filter is animated or not.
* @type {boolean}
*/
animated = true;
/** @inheritdoc */
static defaultUniforms = {
outlineColor: [1, 1, 1, 1],
thickness: [1, 1],
alphaThreshold: 0.60,
knockout: true,
wave: false
};
/** @override */
static vertexShader = `
attribute vec2 aVertexPosition;
uniform mat3 projectionMatrix;
uniform vec2 screenDimensions;
uniform vec4 inputSize;
uniform vec4 outputFrame;
varying vec2 vTextureCoord;
varying vec2 vFilterCoord;
vec4 filterVertexPosition( void ) {
vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy;
return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0., 1.);
}
// getting normalized coord for the tile texture
vec2 filterTextureCoord( void ) {
return aVertexPosition * (outputFrame.zw * inputSize.zw);
}
// getting normalized coord for a screen sized mask render texture
vec2 filterCoord( in vec2 textureCoord ) {
return textureCoord * inputSize.xy / outputFrame.zw;
}
void main() {
vTextureCoord = filterTextureCoord();
vFilterCoord = filterCoord(vTextureCoord);
gl_Position = filterVertexPosition();
}`;
/**
* Dynamically create the fragment shader used for filters of this type.
* @returns {string}
*/
static createFragmentShader() {
return `
varying vec2 vTextureCoord;
varying vec2 vFilterCoord;
uniform sampler2D uSampler;
uniform vec2 thickness;
uniform vec4 outlineColor;
uniform vec4 filterClamp;
uniform float alphaThreshold;
uniform float time;
uniform bool knockout;
uniform bool wave;
${this.CONSTANTS}
${this.WAVE()}
void main(void) {
float dist = distance(vFilterCoord, vec2(0.5)) * 2.0;
vec4 ownColor = texture2D(uSampler, vTextureCoord);
vec4 wColor = wave ? outlineColor *
wcos(0.0, 1.0, dist * 75.0,
-time * 0.01 + 3.0 * dot(vec4(1.0), ownColor))
* 0.33 * (1.0 - dist) : vec4(0.0);
float texAlpha = smoothstep(alphaThreshold, 1.0, ownColor.a);
vec4 curColor;
float maxAlpha = 0.;
vec2 displaced;
for ( float angle = 0.0; angle <= TWOPI; angle += ${this.#quality.toFixed(7)} ) {
displaced.x = vTextureCoord.x + thickness.x * cos(angle);
displaced.y = vTextureCoord.y + thickness.y * sin(angle);
curColor = texture2D(uSampler, clamp(displaced, filterClamp.xy, filterClamp.zw));
curColor.a = clamp((curColor.a - 0.6) * 2.5, 0.0, 1.0);
maxAlpha = max(maxAlpha, curColor.a);
}
float resultAlpha = max(maxAlpha, texAlpha);
vec3 result = ((knockout ? vec3(0.0) : ownColor.rgb) + outlineColor.rgb * (1.0 - texAlpha)) * resultAlpha;
gl_FragColor = mix(vec4(result, resultAlpha), wColor, texAlpha);
}
`;
}
/* -------------------------------------------- */
/**
* Quality of the outline according to performance mode.
* @returns {number}
*/
static get #quality() {
switch ( canvas.performance.mode ) {
case CONST.CANVAS_PERFORMANCE_MODES.LOW:
return (Math.PI * 2) / 10;
case CONST.CANVAS_PERFORMANCE_MODES.MED:
return (Math.PI * 2) / 20;
default:
return (Math.PI * 2) / 30;
}
}
/* -------------------------------------------- */
/**
* The thickness of the outline.
* @type {number}
*/
get thickness() {
return this.#thickness;
}
set thickness(value) {
this.#thickness = value;
this.padding = value * 1.5;
}
#thickness = 3;
/* -------------------------------------------- */
/** @inheritdoc */
static create(initialUniforms={}) {
const uniforms = {...this.defaultUniforms, ...initialUniforms};
return new this(this.vertexShader, this.createFragmentShader(), uniforms);
}
/* -------------------------------------------- */
/** @override */
apply(filterManager, input, output, clear) {
if ( canvas.photosensitiveMode ) this.uniforms.wave = false;
let time = 0;
let thickness = this.#thickness * canvas.stage.scale.x;
if ( this.animated && !canvas.photosensitiveMode ) {
time = canvas.app.ticker.lastTime;
thickness *= Math.oscillation(0.75, 1.25, time, 1500);
}
this.uniforms.time = time;
this.uniforms.thickness[0] = thickness / input._frame.width;
this.uniforms.thickness[1] = thickness / input._frame.height;
filterManager.applyFilter(this, input, output, clear);
}
/* -------------------------------------------- */
/* Deprecations and Compatibility */
/* -------------------------------------------- */
/**
* @deprecated since v12
* @ignore
*/
get animate() {
const msg = "OutlineOverlayFilter#animate is deprecated in favor of OutlineOverlayFilter#animated.";
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14});
return this.animated;
}
/**
* @deprecated since v12
* @ignore
*/
set animate(v) {
const msg = "OutlineOverlayFilter#animate is deprecated in favor of OutlineOverlayFilter#animated.";
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14});
this.animated = v;
}
}

View File

@@ -0,0 +1,583 @@
/**
* A filter specialized for transition effects between a source object and a target texture.
*/
class TextureTransitionFilter extends AbstractBaseFilter {
/**
* If this filter requires padding (according to type)
* @type {boolean}
*/
#requirePadding = false;
/* -------------------------------------------- */
/**
* Transition types for this shader.
* @enum {string}
*/
static get TYPES() {
return TextureTransitionFilter.#TYPES;
}
static #TYPES = Object.freeze({
FADE: "fade",
SWIRL: "swirl",
WATER_DROP: "waterDrop",
MORPH: "morph",
CROSSHATCH: "crosshatch",
WIND: "wind",
WAVES: "waves",
WHITE_NOISE: "whiteNoise",
HOLOGRAM: "hologram",
HOLE: "hole",
HOLE_SWIRL: "holeSwirl",
GLITCH: "glitch",
DOTS: "dots"
});
/* -------------------------------------------- */
/**
* Maps the type number to its string.
* @type {ReadonlyArray<string>}
*/
static #TYPE_NUMBER_TO_STRING = Object.freeze(Object.values(this.TYPES));
/* -------------------------------------------- */
/**
* Maps the type string to its number.
* @type {Readonly<{[type: string]: number}>}
*/
static #TYPE_STRING_TO_NUMBER = Object.freeze(Object.fromEntries(this.#TYPE_NUMBER_TO_STRING.map((t, i) => [t, i])));
/* -------------------------------------------- */
/**
* Types that requires padding
* @type {ReadonlyArray<string>}
*/
static #PADDED_TYPES = Object.freeze([
this.#TYPES.SWIRL,
this.#TYPES.WATER_DROP,
this.#TYPES.WAVES,
this.#TYPES.HOLOGRAM
]);
/* -------------------------------------------- */
/**
* The transition type (see {@link TextureTransitionFilter.TYPES}).
* @type {string}
* @defaultValue TextureTransitionFilter.TYPES.FADE
*/
get type() {
return TextureTransitionFilter.#TYPE_NUMBER_TO_STRING[this.uniforms.type];
}
set type(type) {
if ( !(type in TextureTransitionFilter.#TYPE_STRING_TO_NUMBER) ) throw new Error("Invalid texture transition type");
this.uniforms.type = TextureTransitionFilter.#TYPE_STRING_TO_NUMBER[type];
this.#requirePadding = TextureTransitionFilter.#PADDED_TYPES.includes(type);
}
/* -------------------------------------------- */
/**
* Sampler target for this filter.
* @param {PIXI.Texture} targetTexture
*/
set targetTexture(targetTexture) {
if ( !targetTexture.uvMatrix ) {
targetTexture.uvMatrix = new PIXI.TextureMatrix(targetTexture, 0.0);
targetTexture.uvMatrix.update();
}
this.uniforms.targetTexture = targetTexture;
this.uniforms.targetUVMatrix = targetTexture.uvMatrix.mapCoord.toArray(true);
}
/* -------------------------------------------- */
/**
* Animate a transition from a subject SpriteMesh/PIXI.Sprite to a given texture.
* @param {PIXI.Sprite|SpriteMesh} subject The source mesh/sprite to apply a transition.
* @param {PIXI.Texture} texture The target texture.
* @param {object} [options]
* @param {string} [options.type=TYPES.FADE] The transition type (default to FADE.)
* @param {string|symbol} [options.name] The name of the {@link CanvasAnimation}.
* @param {number} [options.duration=1000] The animation duration
* @param {Function|string} [options.easing] The easing function of the animation
* @returns {Promise<boolean>} A Promise which resolves to true once the animation has concluded
* or false if the animation was prematurely terminated
*/
static async animate(subject, texture, {type=this.TYPES.FADE, name, duration, easing}={}) {
if ( !((subject instanceof SpriteMesh) || (subject instanceof PIXI.Sprite)) ) {
throw new Error("The subject must be a subclass of SpriteMesh or PIXI.Sprite");
}
if ( !(texture instanceof PIXI.Texture) ) {
throw new Error("The target texture must be a subclass of PIXI.Texture");
}
// Create the filter and activate it on the subject
const filter = this.create();
filter.type = type;
filter.targetTexture = texture;
subject.filters ??= [];
subject.filters.unshift(filter);
// Create the animation
const promise = CanvasAnimation.animate([{attribute: "progress", parent: filter.uniforms, to: 1}],
{name, duration, easing, context: subject});
// Replace the texture if the animation was completed
promise.then(completed => {
if ( completed ) subject.texture = texture;
});
// Remove the transition filter from the target once the animation was completed or terminated
promise.finally(() => {
subject.filters?.findSplice(f => f === filter);
});
return promise;
}
/* -------------------------------------------- */
/** @inheritDoc */
static defaultUniforms = {
tintAlpha: [1, 1, 1, 1],
targetTexture: null,
progress: 0,
rotation: 0,
anchor: {x: 0.5, y: 0.5},
type: 1,
filterMatrix: new PIXI.Matrix(),
filterMatrixInverse: new PIXI.Matrix(),
targetUVMatrix: new PIXI.Matrix()
};
/* -------------------------------------------- */
/** @inheritDoc */
static vertexShader = `
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
attribute vec2 aVertexPosition;
uniform mat3 projectionMatrix;
uniform mat3 filterMatrix;
uniform vec4 inputSize;
uniform vec4 outputFrame;
varying vec2 vTextureCoord;
varying vec2 vFilterCoord;
vec4 filterVertexPosition() {
vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy;
return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
}
vec2 filterTextureCoord() {
return aVertexPosition * (outputFrame.zw * inputSize.zw);
}
void main() {
gl_Position = filterVertexPosition();
vTextureCoord = filterTextureCoord();
vFilterCoord = (filterMatrix * vec3(vTextureCoord, 1.0)).xy;
}
`;
/* -------------------------------------------- */
/** @inheritDoc */
static fragmentShader = `
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
${this.CONSTANTS}
${this.PRNG}
uniform float progress;
uniform float rotation;
uniform vec2 anchor;
uniform int type;
uniform sampler2D uSampler;
uniform sampler2D targetTexture;
uniform vec4 tintAlpha;
uniform mat3 filterMatrixInverse;
uniform mat3 targetUVMatrix;
varying vec2 vTextureCoord;
varying vec2 vFilterCoord;
/* -------------------------------------------- */
/* UV Mapping Functions */
/* -------------------------------------------- */
/* Map filter coord to source texture coord */
vec2 mapFuv2Suv(in vec2 uv) {
return (filterMatrixInverse * vec3(uv, 1.0)).xy;
}
/* Map filter coord to target texture coord */
vec2 mapFuv2Tuv(in vec2 uv) {
return (targetUVMatrix * vec3(uv, 1.0)).xy;
}
/* -------------------------------------------- */
/* Clipping Functions */
/* -------------------------------------------- */
float getClip(in vec2 uv) {
return step(3.5,
step(0.0, uv.x) +
step(0.0, uv.y) +
step(uv.x, 1.0) +
step(uv.y, 1.0));
}
/* -------------------------------------------- */
/* Texture Functions */
/* -------------------------------------------- */
vec4 colorFromSource(in vec2 uv) {
return texture2D(uSampler, uv);
}
vec4 colorFromTarget(in vec2 uv) {
return texture2D(targetTexture, mapFuv2Tuv(uv))
* getClip(uv);
}
/* -------------------------------------------- */
/* Simple transition */
/* -------------------------------------------- */
vec4 transition() {
return mix(
colorFromSource(vTextureCoord),
colorFromTarget(vFilterCoord),
progress
);
}
/* -------------------------------------------- */
/* Morphing */
/* -------------------------------------------- */
vec4 morph() {
vec4 ca = colorFromSource(vTextureCoord);
vec4 cb = colorFromTarget(vFilterCoord);
float a = mix(ca.a, cb.a, progress);
vec2 oa = (((ca.rg + ca.b) * 0.5) * 2.0 - 1.0);
vec2 ob = (((cb.rg + cb.b) * 0.5) * 2.0 - 1.0);
vec2 oc = mix(oa, ob, 0.5) * 0.2;
float w0 = progress;
float w1 = 1.0 - w0;
return mix(colorFromSource(mapFuv2Suv(vFilterCoord + oc * w0)),
colorFromTarget(vFilterCoord - oc * w1),
progress) * smoothstep(0.0, 0.5, a);
}
/* -------------------------------------------- */
/* Water Drop */
/* -------------------------------------------- */
vec4 drop() {
vec2 dir = vFilterCoord - 0.5;
float dist = length(dir);
float da = clamp(1.6 - distance(vec2(0.0), vFilterCoord * 2.0 - 1.0), 0.0, 1.6) / 1.6;
vec2 offset = mix(vec2(0.0),
dir * sin(dist * 35.0 - progress * 35.0),
min(min(progress, 1.0 - progress) * 2.0, da));
return mix(colorFromSource(mapFuv2Suv(vFilterCoord + offset)),
colorFromTarget(vFilterCoord + offset),
progress);
}
/* -------------------------------------------- */
/* Waves effect */
/* -------------------------------------------- */
vec2 offset(in float progress, in float x, in float str) {
float p = smoothstep(0.0, 1.0, min(progress, 1.0 - progress) * 2.0);
float shifty = str * p * cos(30.0 * (progress + x));
return vec2(0.0, shifty);
}
vec4 wavy() {
vec4 ca = colorFromSource(vTextureCoord);
vec4 cb = colorFromTarget(vFilterCoord);
float a = mix(ca.a, cb.a, progress);
vec2 shift = vFilterCoord + offset(progress, vFilterCoord.x, 0.20);
vec4 c0 = colorFromSource(mapFuv2Suv(shift));
vec4 c1 = colorFromTarget(shift);
return mix(c0, c1, progress);
}
/* -------------------------------------------- */
/* White Noise */
/* -------------------------------------------- */
float noise(vec2 co) {
float a = 12.9898;
float b = 78.233;
float c = 43758.5453;
float dt = dot(co.xy * progress, vec2(a, b));
float sn = mod(dt, 3.14);
return fract(sin(sn) * c);
}
vec4 whitenoise() {
const float m = (1.0 / 0.15);
vec4 noise = vec4(vec3(noise(vFilterCoord)), 1.0);
vec4 cr = morph();
float alpha = smoothstep(0.0, 0.75, cr.a);
return mix(cr, noise * alpha, smoothstep(0.0, 0.1, min(progress, 1.0 - progress)));
}
/* -------------------------------------------- */
/* Swirling */
/* -------------------------------------------- */
vec2 sphagetization(inout vec2 uv, in float p) {
const float r = 1.0;
float dist = length(uv);
if ( dist < r ) {
float percent = r - dist;
float a = (p <= 0.5) ? mix(0.0, 1.0, p / 0.5) : mix(1.0, 0.0, (p - 0.5) / 0.5);
float tt = percent * percent * a * 8.0 * PI;
float s = sin(tt);
float c = cos(tt);
uv = vec2(dot(uv, vec2(c, -s)), dot(uv, vec2(s, c)));
}
return uv;
}
vec4 swirl() {
float p = progress;
vec2 uv = vFilterCoord - 0.5;
uv = sphagetization(uv, p);
uv += 0.5;
vec4 c0 = colorFromSource(mapFuv2Suv(uv));
vec4 c1 = colorFromTarget(uv);
return mix(c0, c1, p) * smoothstep(0.0, 0.5, mix(c0.a, c1.a, progress));
}
/* -------------------------------------------- */
/* Cross Hatch */
/* -------------------------------------------- */
vec4 crosshatch() {
float dist = distance(vec2(0.5), vFilterCoord) / 3.0;
float r = progress - min(random(vec2(vFilterCoord.y, 0.0)),
random(vec2(0.0, vFilterCoord.x)));
return mix(colorFromSource(vTextureCoord),
colorFromTarget(vFilterCoord),
mix(0.0,
mix(step(dist, r),
1.0,
smoothstep(0.7, 1.0, progress)),
smoothstep(0.0, 0.3, progress)));
}
/* -------------------------------------------- */
/* Lateral Wind */
/* -------------------------------------------- */
vec4 wind() {
const float s = 0.2;
float r = random(vec2(0, vFilterCoord.y));
float p = smoothstep(0.0, -s, vFilterCoord.x * (1.0 - s) + s * r - (progress * (1.0 + s)));
return mix(
colorFromSource(vTextureCoord),
colorFromTarget(vFilterCoord),
p
);
}
/* -------------------------------------------- */
/* Holographic effect */
/* -------------------------------------------- */
vec2 roffset(in float progress, in float x, in float theta, in float str) {
float shifty = (1.0 - progress) * str * progress * cos(10.0 * (progress + x));
return vec2(0, shifty);
}
vec4 hologram() {
float cosProg = 0.5 * (cos(2.0 * PI * progress) + 1.0);
vec2 os = roffset(progress, vFilterCoord.x, 0.0, 0.24);
vec4 fscol = colorFromSource(mapFuv2Suv(vFilterCoord + os));
vec4 ftcol = colorFromTarget(vFilterCoord + os);
float scintensity = max(max(fscol.r, fscol.g), fscol.b);
float tcintensity = max(max(ftcol.r, ftcol.g), ftcol.b);
vec4 tscol = vec4(0.0, fscol.g * 3.0, 0.0, 1.0) * scintensity;
vec4 ttcol = vec4(ftcol.r * 3.0, 0.0, 0.0, 1.0) * tcintensity;
vec4 iscol = vec4(0.0, fscol.g * 3.0, fscol.b * 3.0, 1.0) * scintensity;
vec4 itcol = vec4(ftcol.r * 3.0, 0.0, ftcol.b * 3.0, 1.0) * tcintensity;
vec4 smix = mix(mix(fscol, tscol, progress), iscol, 1.0 - cosProg);
vec4 tmix = mix(mix(ftcol, ttcol, 1.0 - progress), itcol, 1.0 - cosProg);
return mix(smix, tmix, progress);
}
/* -------------------------------------------- */
/* Hole effect */
/* -------------------------------------------- */
vec4 hole() {
vec2 uv = vFilterCoord;
float s = smoothstep(0.0, 1.0, min(progress, 1.0 - progress) * 2.0);
uv -= 0.5;
uv *= (1.0 + s * 30.0);
uv += 0.5;
float clip = getClip(uv);
vec4 sc = colorFromSource(mapFuv2Suv(uv)) * clip;
vec4 tc = colorFromTarget(uv);
return mix(sc, tc, smoothstep(0.4, 0.6, progress));
}
/* -------------------------------------------- */
/* Hole Swirl effect */
/* -------------------------------------------- */
vec4 holeSwirl() {
vec2 uv = vFilterCoord;
vec4 deepBlack = vec4(vec3(0.25), 1.0);
float mp = min(progress, 1.0 - progress) * 2.0;
float sw = smoothstep(0.0, 1.0, mp);
uv -= 0.5;
uv *= (1.0 + sw * 15.0);
uv = sphagetization(uv, progress);
uv += 0.5;
float clip = getClip(uv);
vec4 sc = colorFromSource(mapFuv2Suv(uv)) * clip;
vec4 tc = colorFromTarget(uv);
float sv = smoothstep(0.0, 0.35, mp);
return mix(mix(sc, sc * deepBlack, sv), mix(tc, tc * deepBlack, sv), smoothstep(0.4, 0.6, progress));
}
/* -------------------------------------------- */
/* Glitch */
/* -------------------------------------------- */
vec4 glitch() {
// Precompute constant values
vec2 inv64 = vec2(1.0 / 64.0);
vec2 uvOffset = floor(vec2(progress) * vec2(1200.0, 3500.0)) * inv64;
vec2 halfVec = vec2(0.5);
// Compute block and normalized UV coordinates
vec2 blk = floor(vFilterCoord / vec2(16.0));
vec2 uvn = blk * inv64 + uvOffset;
// Compute distortion only if progress > 0.0
vec2 dist = progress > 0.0
? (fract(uvn) - halfVec) * 0.3 * (1.0 - progress)
: vec2(0.0);
// Precompute distorted coordinates
vec2 coords[4];
for ( int i = 0; i < 4; ++i ) {
coords[i] = vFilterCoord + dist * (0.4 - 0.1 * float(i));
}
// Fetch colors and mix them
vec4 colorResult;
for ( int i = 0; i < 4; ++i ) {
vec4 colorSrc = colorFromSource(mapFuv2Suv(coords[i]));
vec4 colorTgt = colorFromTarget(coords[i]);
colorResult[i] = mix(colorSrc[i], colorTgt[i], progress);
}
return colorResult;
}
/* -------------------------------------------- */
/* Dots */
/* -------------------------------------------- */
vec4 dots() {
vec2 halfVec = vec2(0.5);
float distToCenter = distance(vFilterCoord, halfVec);
float threshold = pow(progress, 3.0) / distToCenter;
float distToDot = distance(fract(vFilterCoord * 30.0), halfVec);
// Compute the factor to mix colors based on the threshold comparison
float isTargetFactor = step(distToDot, threshold);
vec4 targetColor = colorFromTarget(vFilterCoord);
vec4 sourceColor = colorFromSource(vTextureCoord);
return mix(sourceColor, targetColor, isTargetFactor);
}
/* -------------------------------------------- */
/* Main Program */
/* -------------------------------------------- */
void main() {
vec4 result;
if ( type == 1 ) {
result = swirl();
} else if ( type == 2 ) {
result = drop();
} else if ( type == 3 ) {
result = morph();
} else if ( type == 4 ) {
result = crosshatch();
} else if ( type == 5 ) {
result = wind();
} else if ( type == 6 ) {
result = wavy();
} else if ( type == 7 ) {
result = whitenoise();
} else if ( type == 8 ) {
result = hologram();
} else if ( type == 9 ) {
result = hole();
} else if ( type == 10 ) {
result = holeSwirl();
} else if ( type == 11 ) {
result = glitch();
} else if ( type == 12 ) {
result = dots();
} else {
result = transition();
}
gl_FragColor = result * tintAlpha;
}
`;
/* -------------------------------------------- */
/** @inheritDoc */
apply(filterManager, input, output, clear) {
const filterMatrix = this.uniforms.filterMatrix;
const {sourceFrame, destinationFrame, target} = filterManager.activeState;
if ( this.#requirePadding ) {
this.padding = Math.max(target.width, target.height) * 0.5 * canvas.stage.worldTransform.d;
}
else this.padding = 0;
filterMatrix.set(destinationFrame.width, 0, 0, destinationFrame.height, sourceFrame.x, sourceFrame.y);
const worldTransform = PIXI.Matrix.TEMP_MATRIX;
const localBounds = target.getLocalBounds();
worldTransform.copyFrom(target.transform.worldTransform);
worldTransform.invert();
filterMatrix.prepend(worldTransform);
filterMatrix.translate(-localBounds.x, -localBounds.y);
filterMatrix.scale(1.0 / localBounds.width, 1.0 / localBounds.height);
const filterMatrixInverse = this.uniforms.filterMatrixInverse;
filterMatrixInverse.copyFrom(filterMatrix);
filterMatrixInverse.invert();
filterManager.applyFilter(this, input, output, clear);
}
}

View File

@@ -0,0 +1,206 @@
/**
* Apply visibility coloration according to the baseLine color.
* Uses very lightweight gaussian vertical and horizontal blur filter passes.
*/
class VisibilityFilter extends AbstractBaseMaskFilter {
constructor(...args) {
super(...args);
// Handling inner blur filters configuration
const b = canvas.blur;
if ( b.enabled ) {
const resolution = PIXI.Filter.defaultResolution;
this.#blurXFilter = new b.blurPassClass(true, b.strength, b.passes, resolution, b.kernels);
this.#blurYFilter = new b.blurPassClass(false, b.strength, b.passes, resolution, b.kernels);
}
// Handling fog overlay texture matrix
this.#overlayTex = this.uniforms.overlayTexture;
if ( this.#overlayTex && !this.#overlayTex.uvMatrix ) {
this.#overlayTex.uvMatrix = new PIXI.TextureMatrix(this.#overlayTex.uvMatrix, 0.0);
}
}
/**
* Horizontal inner blur filter
* @type {AlphaBlurFilterPass}
*/
#blurXFilter;
/**
* Vertical inner blur filter
* @type {AlphaBlurFilterPass}
*/
#blurYFilter;
/**
* Optional fog overlay texture
* @type {PIXI.Texture|undefined}
*/
#overlayTex;
/** @override */
static defaultUniforms = {
exploredColor: [1, 1, 1],
unexploredColor: [0, 0, 0],
screenDimensions: [1, 1],
visionTexture: null,
primaryTexture: null,
overlayTexture: null,
overlayMatrix: new PIXI.Matrix(),
hasOverlayTexture: false
};
/** @override */
static create(initialUniforms={}, options={}) {
const uniforms = {...this.defaultUniforms, ...initialUniforms};
return new this(this.vertexShader, this.fragmentShader(options), uniforms);
}
static vertexShader = `
attribute vec2 aVertexPosition;
uniform mat3 projectionMatrix;
uniform mat3 overlayMatrix;
varying vec2 vTextureCoord;
varying vec2 vMaskTextureCoord;
varying vec2 vOverlayCoord;
varying vec2 vOverlayTilingCoord;
uniform vec4 inputSize;
uniform vec4 outputFrame;
uniform vec4 dimensions;
uniform vec2 screenDimensions;
uniform bool hasOverlayTexture;
vec4 filterVertexPosition( void ) {
vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy;
return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0.0, 1.0);
}
vec2 filterTextureCoord( void ) {
return aVertexPosition * (outputFrame.zw * inputSize.zw);
}
vec2 overlayTilingTextureCoord( void ) {
if ( hasOverlayTexture ) return vOverlayCoord * (dimensions.xy / dimensions.zw);
return vOverlayCoord;
}
// getting normalized coord for a screen sized mask render texture
vec2 filterMaskTextureCoord( in vec2 textureCoord ) {
return (textureCoord * inputSize.xy + outputFrame.xy) / screenDimensions;
}
void main(void) {
gl_Position = filterVertexPosition();
vTextureCoord = filterTextureCoord();
vMaskTextureCoord = filterMaskTextureCoord(vTextureCoord);
vOverlayCoord = (overlayMatrix * vec3(vTextureCoord, 1.0)).xy;
vOverlayTilingCoord = overlayTilingTextureCoord();
}`;
/** @override */
static fragmentShader(options) { return `
varying vec2 vTextureCoord;
varying vec2 vMaskTextureCoord;
varying vec2 vOverlayCoord;
varying vec2 vOverlayTilingCoord;
uniform sampler2D uSampler;
uniform sampler2D primaryTexture;
uniform sampler2D overlayTexture;
uniform vec3 unexploredColor;
uniform vec3 backgroundColor;
uniform bool hasOverlayTexture;
${options.persistentVision ? ``
: `uniform sampler2D visionTexture;
uniform vec3 exploredColor;`}
${this.CONSTANTS}
${this.PERCEIVED_BRIGHTNESS}
// To check if we are out of the bound
float getClip(in vec2 uv) {
return step(3.5,
step(0.0, uv.x) +
step(0.0, uv.y) +
step(uv.x, 1.0) +
step(uv.y, 1.0));
}
// Unpremultiply fog texture
vec4 unPremultiply(in vec4 pix) {
if ( !hasOverlayTexture || (pix.a == 0.0) ) return pix;
return vec4(pix.rgb / pix.a, pix.a);
}
void main() {
float r = texture2D(uSampler, vTextureCoord).r; // Revealed red channel from the filter texture
${options.persistentVision ? `` : `float v = texture2D(visionTexture, vMaskTextureCoord).r;`} // Vision red channel from the vision cached container
vec4 baseColor = texture2D(primaryTexture, vMaskTextureCoord);// Primary cached container renderTexture color
vec4 fogColor = hasOverlayTexture
? texture2D(overlayTexture, vOverlayTilingCoord) * getClip(vOverlayCoord)
: baseColor;
fogColor = unPremultiply(fogColor);
// Compute fog exploration colors
${!options.persistentVision
? `float reflec = perceivedBrightness(baseColor.rgb);
vec4 explored = vec4(min((exploredColor * reflec) + (baseColor.rgb * exploredColor), vec3(1.0)), 0.5);`
: ``}
vec4 unexplored = hasOverlayTexture
? mix(vec4(unexploredColor, 1.0), vec4(fogColor.rgb * backgroundColor, 1.0), fogColor.a)
: vec4(unexploredColor, 1.0);
// Mixing components to produce fog of war
${options.persistentVision
? `gl_FragColor = mix(unexplored, vec4(0.0), r);`
: `vec4 fow = mix(unexplored, explored, max(r,v));
gl_FragColor = mix(fow, vec4(0.0), v);`}
// Output the result
gl_FragColor.rgb *= gl_FragColor.a;
}`
}
/**
* Set the blur strength
* @param {number} value blur strength
*/
set blur(value) {
if ( this.#blurXFilter ) this.#blurXFilter.blur = this.#blurYFilter.blur = value;
}
get blur() {
return this.#blurYFilter?.blur;
}
/** @override */
apply(filterManager, input, output, clear) {
this.calculateMatrix(filterManager);
if ( canvas.blur.enabled ) {
// Get temporary filter textures
const firstRenderTarget = filterManager.getFilterTexture();
// Apply inner filters
this.state.blend = false;
this.#blurXFilter.apply(filterManager, input, firstRenderTarget, PIXI.CLEAR_MODES.NONE);
this.#blurYFilter.apply(filterManager, firstRenderTarget, input, PIXI.CLEAR_MODES.NONE);
this.state.blend = true;
// Inform PIXI that temporary filter textures are not more necessary
filterManager.returnFilterTexture(firstRenderTarget);
}
// Apply visibility
super.apply(filterManager, input, output, clear);
}
/**
* Calculate the fog overlay sprite matrix.
* @param {PIXI.FilterSystem} filterManager
*/
calculateMatrix(filterManager) {
if ( !this.uniforms.hasOverlayTexture || !this.#overlayTex ) return;
if ( this.#overlayTex && !this.#overlayTex.uvMatrix ) {
this.#overlayTex.uvMatrix = new PIXI.TextureMatrix(this.#overlayTex.uvMatrix, 0.0);
}
this.#overlayTex.uvMatrix.update();
const mat = filterManager.calculateSpriteMatrix(this.uniforms.overlayMatrix, canvas.visibility.visibilityOverlay);
this.uniforms.overlayMatrix = mat.prepend(this.#overlayTex.uvMatrix.mapCoord);
}
}

View File

@@ -0,0 +1,36 @@
class VisionMaskFilter extends AbstractBaseMaskFilter {
/** @override */
static fragmentShader = `
precision mediump float;
varying vec2 vTextureCoord;
varying vec2 vMaskTextureCoord;
uniform sampler2D uSampler;
uniform sampler2D uMaskSampler;
void main() {
float mask = texture2D(uMaskSampler, vMaskTextureCoord).r;
gl_FragColor = texture2D(uSampler, vTextureCoord) * mask;
}`;
/** @override */
static defaultUniforms = {
uMaskSampler: null
};
/** @override */
static create() {
return super.create({
uMaskSampler: canvas.masks.vision.renderTexture
});
}
/**
* Overridden as an alias for canvas.visibility.visible.
* This property cannot be set.
* @override
*/
get enabled() {
return canvas.visibility.visible;
}
set enabled(value) {}
}

View File

@@ -0,0 +1,11 @@
/**
* A minimalist filter (just used for blending)
*/
class VoidFilter extends AbstractBaseFilter {
static fragmentShader = `
varying vec2 vTextureCoord;
uniform sampler2D uSampler;
void main() {
gl_FragColor = texture2D(uSampler, vTextureCoord);
}`;
}

View File

@@ -0,0 +1,156 @@
/**
* The filter used by the weather layer to mask weather above occluded roofs.
* @see {@link WeatherEffects}
*/
class WeatherOcclusionMaskFilter extends AbstractBaseMaskFilter {
/**
* Elevation of this weather occlusion mask filter.
* @type {number}
*/
elevation = Infinity;
/** @override */
static vertexShader = `
attribute vec2 aVertexPosition;
// Filter globals uniforms
uniform mat3 projectionMatrix;
uniform mat3 terrainUvMatrix;
uniform vec4 inputSize;
uniform vec4 outputFrame;
// Needed to compute mask and terrain normalized coordinates
uniform vec2 screenDimensions;
// Needed for computing scene sized texture coordinates
uniform vec2 sceneAnchor;
uniform vec2 sceneDimensions;
uniform bool useTerrain;
varying vec2 vTextureCoord;
varying vec2 vMaskTextureCoord;
varying vec2 vTerrainTextureCoord;
varying vec2 vSceneCoord;
vec4 filterVertexPosition( void ) {
vec2 position = aVertexPosition * max(outputFrame.zw, vec2(0.)) + outputFrame.xy;
return vec4((projectionMatrix * vec3(position, 1.0)).xy, 0., 1.);
}
// getting normalized coord for the tile texture
vec2 filterTextureCoord( void ) {
return aVertexPosition * (outputFrame.zw * inputSize.zw);
}
// getting normalized coord for a screen sized mask render texture
vec2 filterMaskTextureCoord( void ) {
return (aVertexPosition * outputFrame.zw + outputFrame.xy) / screenDimensions;
}
vec2 filterTerrainSceneCoord( in vec2 textureCoord ) {
return (textureCoord - (sceneAnchor / screenDimensions)) * (screenDimensions / sceneDimensions);
}
// get normalized terrain texture coordinates
vec2 filterTerrainTextureCoord( in vec2 sceneCoord ) {
return (terrainUvMatrix * vec3(vSceneCoord, 1.0)).xy;
}
void main() {
vTextureCoord = filterTextureCoord();
if ( useTerrain ) {
vSceneCoord = filterTerrainSceneCoord(vTextureCoord);
vTerrainTextureCoord = filterTerrainTextureCoord(vSceneCoord);
}
vMaskTextureCoord = filterMaskTextureCoord();
gl_Position = filterVertexPosition();
}`;
/** @override */
static fragmentShader = `
// Occlusion mask uniforms
uniform bool useOcclusion;
uniform sampler2D occlusionTexture;
uniform bool reverseOcclusion;
uniform vec4 occlusionWeights;
// Terrain mask uniforms
uniform bool useTerrain;
uniform sampler2D terrainTexture;
uniform bool reverseTerrain;
uniform vec4 terrainWeights;
// Other uniforms
varying vec2 vTextureCoord;
varying vec2 vMaskTextureCoord;
varying vec2 vTerrainTextureCoord;
varying vec2 vSceneCoord;
uniform sampler2D uSampler;
uniform float depthElevation;
uniform highp mat3 terrainUvMatrix;
// Clip the terrain texture if out of bounds
float getTerrainClip(vec2 uv) {
return step(3.5,
step(0.0, uv.x) +
step(0.0, uv.y) +
step(uv.x, 1.0) +
step(uv.y, 1.0));
}
void main() {
// Base mask value
float mask = 1.0;
// Process the occlusion mask
if ( useOcclusion ) {
float oMask = step(depthElevation, (254.5 / 255.0) - dot(occlusionWeights, texture2D(occlusionTexture, vMaskTextureCoord)));
if ( reverseOcclusion ) oMask = 1.0 - oMask;
mask *= oMask;
}
// Process the terrain mask
if ( useTerrain ) {
float tMask = dot(terrainWeights, texture2D(terrainTexture, vTerrainTextureCoord));
if ( reverseTerrain ) tMask = 1.0 - tMask;
mask *= (tMask * getTerrainClip(vSceneCoord));
}
// Process filtering and apply mask value
gl_FragColor = texture2D(uSampler, vTextureCoord) * mask;
}`;
/** @override */
static defaultUniforms = {
depthElevation: 0,
useOcclusion: true,
occlusionTexture: null,
reverseOcclusion: false,
occlusionWeights: [0, 0, 1, 0],
useTerrain: false,
terrainTexture: null,
reverseTerrain: false,
terrainWeights: [1, 0, 0, 0],
sceneDimensions: [0, 0],
sceneAnchor: [0, 0],
terrainUvMatrix: new PIXI.Matrix()
};
/** @override */
apply(filterManager, input, output, clear, currentState) {
if ( this.uniforms.useTerrain ) {
const wt = canvas.stage.worldTransform;
const z = wt.d;
const sceneDim = canvas.scene.dimensions;
// Computing the scene anchor and scene dimensions for terrain texture coordinates
this.uniforms.sceneAnchor[0] = wt.tx + (sceneDim.sceneX * z);
this.uniforms.sceneAnchor[1] = wt.ty + (sceneDim.sceneY * z);
this.uniforms.sceneDimensions[0] = sceneDim.sceneWidth * z;
this.uniforms.sceneDimensions[1] = sceneDim.sceneHeight * z;
}
this.uniforms.depthElevation = canvas.masks.depth.mapElevation(this.elevation);
return super.apply(filterManager, input, output, clear, currentState);
}
}

View File

@@ -0,0 +1,31 @@
/**
* A mixin wich decorates a shader or filter and construct a fragment shader according to a choosen channel.
* @category - Mixins
* @param {typeof PIXI.Shader|PIXI.Filter} ShaderClass The parent ShaderClass class being mixed.
* @returns {typeof AdaptiveFragmentChannelMixin} A Shader/Filter subclass mixed with AdaptiveFragmentChannelMixin.
* @mixin
*/
const AdaptiveFragmentChannelMixin = ShaderClass => {
class AdaptiveFragmentChannelMixin extends ShaderClass {
/**
* The fragment shader which renders this filter.
* A subclass of AdaptiveFragmentChannelMixin must implement the fragmentShader static field.
* @type {Function}
*/
static adaptiveFragmentShader = null;
/**
* A factory method for creating the filter using its defined default values
* @param {object} [options] Options which affect filter construction
* @param {object} [options.uniforms] Initial uniforms provided to the filter/shader
* @param {string} [options.channel="r"] The color channel to target for masking
* @returns {PIXI.Shader|PIXI.Filter}
*/
static create({channel="r", ...uniforms}={}) {
this.fragmentShader = this.adaptiveFragmentShader(channel);
return super.create(uniforms);
}
}
return AdaptiveFragmentChannelMixin;
};

View File

@@ -0,0 +1,538 @@
/**
* The grid shader used by {@link GridMesh}.
*/
class GridShader extends AbstractBaseShader {
/**
* The grid type uniform.
* @type {string}
*/
static TYPE_UNIFORM = `
const int TYPE_SQUARE = ${CONST.GRID_TYPES.SQUARE};
const int TYPE_HEXODDR = ${CONST.GRID_TYPES.HEXODDR};
const int TYPE_HEXEVENR = ${CONST.GRID_TYPES.HEXEVENR};
const int TYPE_HEXODDQ = ${CONST.GRID_TYPES.HEXODDQ};
const int TYPE_HEXEVENQ = ${CONST.GRID_TYPES.HEXEVENQ};
uniform lowp int type;
#define TYPE_IS_SQUARE (type == TYPE_SQUARE)
#define TYPE_IS_HEXAGONAL ((TYPE_HEXODDR <= type) && (type <= TYPE_HEXEVENQ))
#define TYPE_IS_HEXAGONAL_COLUMNS ((type == TYPE_HEXODDQ) || (type == TYPE_HEXEVENQ))
#define TYPE_IS_HEXAGONAL_ROWS ((type == TYPE_HEXODDR) || (type == TYPE_HEXEVENR))
#define TYPE_IS_HEXAGONAL_EVEN ((type == TYPE_HEXEVENR) || (type == TYPE_HEXEVENQ))
#define TYPE_IS_HEXAGONAL_ODD ((type == TYPE_HEXODDR) || (type == TYPE_HEXODDQ))
`;
/* -------------------------------------------- */
/**
* The grid thickness uniform.
* @type {string}
*/
static THICKNESS_UNIFORM = "uniform float thickness;";
/* -------------------------------------------- */
/**
* The grid color uniform.
* @type {string}
*/
static COLOR_UNIFORM = "uniform vec4 color;";
/* -------------------------------------------- */
/**
* The resolution (pixels per grid space units) uniform.
* @type {string}
*/
static RESOLUTION_UNIFORM = "uniform float resolution;";
/* -------------------------------------------- */
/**
* The antialiased step function.
* The edge and x values is given in grid space units.
* @type {string}
*/
static ANTIALIASED_STEP_FUNCTION = `
#define ANTIALIASED_STEP_TEMPLATE(type) \
type antialiasedStep(type edge, type x) { \
return clamp(((x - edge) * resolution) + 0.5, type(0.0), type(1.0)); \
}
ANTIALIASED_STEP_TEMPLATE(float)
ANTIALIASED_STEP_TEMPLATE(vec2)
ANTIALIASED_STEP_TEMPLATE(vec3)
ANTIALIASED_STEP_TEMPLATE(vec4)
#undef ANTIALIASED_STEP_TEMPLATE
`;
/* -------------------------------------------- */
/**
* The line converage function, which returns the alpha value at a point with the given distance (in grid space units)
* from an antialiased line (or point) with the given thickness (in grid space units).
* @type {string}
*/
static LINE_COVERAGE_FUNCTION = `
float lineCoverage(float distance, float thickness, float alignment) {
float alpha0 = antialiasedStep((0.0 - alignment) * thickness, distance);
float alpha1 = antialiasedStep((1.0 - alignment) * thickness, distance);
return alpha0 - alpha1;
}
float lineCoverage(float distance, float thickness) {
return lineCoverage(distance, thickness, 0.5);
}
`;
/* -------------------------------------------- */
/**
* Hexagonal functions conversion for between grid and cube space.
* @type {string}
*/
static HEXAGONAL_FUNCTIONS = `
vec2 pointToCube(vec2 p) {
float x = p.x;
float y = p.y;
float q;
float r;
float e = TYPE_IS_HEXAGONAL_EVEN ? 1.0 : 0.0;
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
q = ((2.0 * SQRT1_3) * x) - (2.0 / 3.0);
r = (-0.5 * (q + e)) + y;
} else {
r = ((2.0 * SQRT1_3) * y) - (2.0 / 3.0);
q = (-0.5 * (r + e)) + x;
}
return vec2(q, r);
}
vec2 cubeToPoint(vec2 a) {
float q = a[0];
float r = a[1];
float x;
float y;
float e = TYPE_IS_HEXAGONAL_EVEN ? 1.0 : 0.0;
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
x = (SQRT3 / 2.0) * (q + (2.0 / 3.0));
y = (0.5 * (q + e)) + r;
} else {
y = (SQRT3 / 2.0) * (r + (2.0 / 3.0));
x = (0.5 * (r + e)) + q;
}
return vec2(x, y);
}
vec2 offsetToCube(vec2 o) {
float i = o[0];
float j = o[1];
float q;
float r;
float e = TYPE_IS_HEXAGONAL_EVEN ? 1.0 : -1.0;
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
q = j;
r = i - ((j + (e * mod(j, 2.0))) * 0.5);
} else {
q = j - ((i + (e * mod(i, 2.0))) * 0.5);
r = i;
}
return vec2(q, r);
}
ivec2 offsetToCube(ivec2 o) {
int i = o[0];
int j = o[1];
int q;
int r;
int e = TYPE_IS_HEXAGONAL_EVEN ? 1 : -1;
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
q = j;
r = i - ((j + (e * (j & 1))) / 2);
} else {
q = j - ((i + (e * (i & 1))) / 2);
r = i;
}
return ivec2(q, r);
}
vec2 cubeToOffset(vec2 a) {
float q = a[0];
float r = a[1];
float i;
float j;
float e = TYPE_IS_HEXAGONAL_EVEN ? 1.0 : -1.0;
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
j = q;
i = r + ((q + (e * mod(q, 2.0))) * 0.5);
} else {
i = r;
j = q + ((r + (e * mod(r, 2.0))) * 0.5);
}
return vec2(i, j);
}
ivec2 cubeToOffset(ivec2 a) {
int q = a[0];
int r = a[1];
int i;
int j;
int e = TYPE_IS_HEXAGONAL_EVEN ? 1 : -1;
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
j = q;
i = r + ((q + (e * (q & 1))) / 2);
} else {
i = r;
j = q + ((r + (e * (r & 1))) / 2);
}
return ivec2(i, j);
}
vec2 cubeRound(vec2 a) {
float q = a[0];
float r = a[1];
float s = -q - r;
float iq = floor(q + 0.5);
float ir = floor(r + 0.5);
float is = floor(s + 0.5);
float dq = abs(iq - q);
float dr = abs(ir - r);
float ds = abs(is - s);
if ( (dq > dr) && (dq > ds) ) {
iq = -ir - is;
} else if ( dr > ds ) {
ir = -iq - is;
} else {
is = -iq - ir;
}
return vec2(iq, ir);
}
float cubeDistance(vec2 a, vec2 b) {
vec2 c = b - a;
float q = c[0];
float r = c[1];
return (abs(q) + abs(r) + abs(q + r)) * 0.5;
}
int cubeDistance(ivec2 a, ivec2 b) {
ivec2 c = b - a;
int q = c[0];
int r = c[1];
return (abs(q) + abs(r) + abs(q + r)) / 2;
}
`;
/* -------------------------------------------- */
/**
* Get the nearest vertex of a grid space to the given point.
* @type {string}
*/
static NEAREST_VERTEX_FUNCTION = `
vec2 nearestVertex(vec2 p) {
if ( TYPE_IS_SQUARE ) {
return floor(p + 0.5);
}
if ( TYPE_IS_HEXAGONAL ) {
vec2 c = cubeToPoint(cubeRound(pointToCube(p)));
vec2 d = p - c;
float a = atan(d.y, d.x);
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
a = floor((a / (PI / 3.0)) + 0.5) * (PI / 3.0);
} else {
a = (floor(a / (PI / 3.0)) + 0.5) * (PI / 3.0);
}
return c + (vec2(cos(a), sin(a)) * SQRT1_3);
}
}
`;
/* -------------------------------------------- */
/**
* This function returns the distance to the nearest edge of a grid space given a point.
* @type {string}
*/
static EDGE_DISTANCE_FUNCTION = `
float edgeDistance(vec2 p) {
if ( TYPE_IS_SQUARE ) {
vec2 d = abs(p - floor(p + 0.5));
return min(d.x, d.y);
}
if ( TYPE_IS_HEXAGONAL ) {
vec2 a = pointToCube(p);
vec2 b = cubeRound(a);
vec2 c = b - a;
float q = c[0];
float r = c[1];
float s = -q - r;
return (2.0 - (abs(q - r) + abs(r - s) + abs(s - q))) * 0.25;
}
}
`;
/* -------------------------------------------- */
/**
* This function returns an vector (x, y, z), where
* - x is the x-offset along the nearest edge,
* - y is the y-offset (the distance) from the nearest edge, and
* - z is the length of the nearest edge.
* @type {string}
*/
static EDGE_OFFSET_FUNCTION = `
vec3 edgeOffset(vec2 p) {
if ( TYPE_IS_SQUARE ) {
vec2 d = abs(p - floor(p + 0.5));
return vec3(max(d.x, d.y), min(d.x, d.y), 1.0);
}
if ( TYPE_IS_HEXAGONAL ) {
vec2 c = cubeToPoint(cubeRound(pointToCube(p)));
vec2 d = p - c;
float a = atan(d.y, d.x);
if ( TYPE_IS_HEXAGONAL_COLUMNS ) {
a = (floor(a / (PI / 3.0)) + 0.5) * (PI / 3.0);
} else {
a = floor((a / (PI / 3.0)) + 0.5) * (PI / 3.0);
}
vec2 n = vec2(cos(a), sin(a));
return vec3((0.5 * SQRT1_3) + dot(d, vec2(-n.y, n.x)), 0.5 - dot(d, n), SQRT1_3);
}
}
`;
/* -------------------------------------------- */
/**
* A function that draws the grid given a grid point, style, thickness, and color.
* @type {string}
*/
static DRAW_GRID_FUNCTION = `
const int STYLE_LINE_SOLID = 0;
const int STYLE_LINE_DASHED = 1;
const int STYLE_LINE_DOTTED = 2;
const int STYLE_POINT_SQUARE = 3;
const int STYLE_POINT_DIAMOND = 4;
const int STYLE_POINT_ROUND = 5;
vec4 drawGrid(vec2 point, int style, float thickness, vec4 color) {
float alpha;
if ( style == STYLE_POINT_SQUARE ) {
vec2 offset = abs(nearestVertex(point) - point);
float distance = max(offset.x, offset.y);
alpha = lineCoverage(distance, thickness);
}
else if ( style == STYLE_POINT_DIAMOND ) {
vec2 offset = abs(nearestVertex(point) - point);
float distance = (offset.x + offset.y) * SQRT1_2;
alpha = lineCoverage(distance, thickness);
}
else if ( style == STYLE_POINT_ROUND ) {
float distance = distance(point, nearestVertex(point));
alpha = lineCoverage(distance, thickness);
}
else if ( style == STYLE_LINE_SOLID ) {
float distance = edgeDistance(point);
alpha = lineCoverage(distance, thickness);
}
else if ( (style == STYLE_LINE_DASHED) || (style == STYLE_LINE_DOTTED) ) {
vec3 offset = edgeOffset(point);
if ( (style == STYLE_LINE_DASHED) && TYPE_IS_HEXAGONAL ) {
float padding = thickness * ((1.0 - SQRT1_3) * 0.5);
offset.x += padding;
offset.z += (padding * 2.0);
}
float intervals = offset.z * 0.5 / thickness;
if ( intervals < 0.5 ) {
alpha = lineCoverage(offset.y, thickness);
} else {
float interval = thickness * (2.0 * (intervals / floor(intervals + 0.5)));
float dx = offset.x - (floor((offset.x / interval) + 0.5) * interval);
float dy = offset.y;
if ( style == STYLE_LINE_DOTTED ) {
alpha = lineCoverage(length(vec2(dx, dy)), thickness);
} else {
alpha = min(lineCoverage(dx, thickness), lineCoverage(dy, thickness));
}
}
}
return color * alpha;
}
`;
/* -------------------------------------------- */
/** @override */
static vertexShader = `
#version 300 es
${this.GLSL1_COMPATIBILITY_VERTEX}
precision ${PIXI.settings.PRECISION_VERTEX} float;
in vec2 aVertexPosition;
uniform mat3 translationMatrix;
uniform mat3 projectionMatrix;
uniform vec4 meshDimensions;
uniform vec2 canvasDimensions;
uniform vec4 sceneDimensions;
uniform vec2 screenDimensions;
uniform float gridSize;
out vec2 vGridCoord; // normalized grid coordinates
out vec2 vCanvasCoord; // normalized canvas coordinates
out vec2 vSceneCoord; // normalized scene coordinates
out vec2 vScreenCoord; // normalized screen coordinates
void main() {
vec2 pixelCoord = (aVertexPosition * meshDimensions.zw) + meshDimensions.xy;
vGridCoord = pixelCoord / gridSize;
vCanvasCoord = pixelCoord / canvasDimensions;
vSceneCoord = (pixelCoord - sceneDimensions.xy) / sceneDimensions.zw;
vec3 tPos = translationMatrix * vec3(aVertexPosition, 1.0);
vScreenCoord = tPos.xy / screenDimensions;
gl_Position = vec4((projectionMatrix * tPos).xy, 0.0, 1.0);
}
`;
/* -------------------------------------------- */
/** @override */
static get fragmentShader() {
return `
#version 300 es
${this.GLSL1_COMPATIBILITY_FRAGMENT}
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
in vec2 vGridCoord; // normalized grid coordinates
in vec2 vCanvasCoord; // normalized canvas coordinates
in vec2 vSceneCoord; // normalized scene coordinates
in vec2 vScreenCoord; // normalized screen coordinates
${this.CONSTANTS}
${this.TYPE_UNIFORM}
${this.THICKNESS_UNIFORM}
${this.COLOR_UNIFORM}
${this.RESOLUTION_UNIFORM}
${this.ANTIALIASED_STEP_FUNCTION}
${this.LINE_COVERAGE_FUNCTION}
${this.HEXAGONAL_FUNCTIONS}
${this.NEAREST_VERTEX_FUNCTION}
${this.EDGE_DISTANCE_FUNCTION}
${this.EDGE_OFFSET_FUNCTION}
${this._fragmentShader}
uniform float alpha;
out vec4 fragColor;
void main() {
fragColor = _main() * alpha;
}
`;
}
/* ---------------------------------------- */
/**
* The fragment shader source. Subclasses can override it.
* @type {string}
* @protected
*/
static _fragmentShader = `
uniform lowp int style;
${this.DRAW_GRID_FUNCTION}
vec4 _main() {
return drawGrid(vGridCoord, style, thickness, color);
}
`;
/* ---------------------------------------- */
/** @override */
static defaultUniforms = {
canvasDimensions: [1, 1],
meshDimensions: [0, 0, 1, 1],
sceneDimensions: [0, 0, 1, 1],
screenDimensions: [1, 1],
gridSize: 1,
type: 0,
thickness: 0,
resolution: 0,
color: [0, 0, 0, 0],
alpha: 0,
style: 0
};
/* ---------------------------------------- */
/**
* Configure the shader.
* @param {object} options
*/
configure(options) {
if ( "style" in options ) {
this.uniforms.style = options.style ?? 0;
}
}
/* ---------------------------------------- */
#color = new PIXI.Color(0);
/* ---------------------------------------- */
/** @override */
_preRender(mesh, renderer) {
const data = mesh.data;
const size = data.size;
const uniforms = this.uniforms;
const dimensions = canvas.dimensions;
uniforms.meshDimensions[0] = mesh.x;
uniforms.meshDimensions[1] = mesh.y;
uniforms.meshDimensions[2] = data.width; // === mesh.width
uniforms.meshDimensions[3] = data.height; // === mesh.height
uniforms.canvasDimensions[0] = dimensions.width;
uniforms.canvasDimensions[1] = dimensions.height;
uniforms.sceneDimensions = dimensions.sceneRect;
uniforms.screenDimensions = canvas.screenDimensions;
uniforms.gridSize = size;
uniforms.type = data.type;
uniforms.thickness = data.thickness / size;
uniforms.alpha = mesh.worldAlpha;
this.#color.setValue(data.color).toArray(uniforms.color);
// Only uniform scale is supported!
const {resolution} = renderer.renderTexture.current ?? renderer;
let scale = resolution * mesh.worldTransform.a / data.width;
const projection = renderer.projection.transform;
if ( projection ) {
const {a, b} = projection;
scale *= Math.sqrt((a * a) + (b * b));
}
uniforms.resolution = scale * size;
}
}

View File

@@ -0,0 +1,91 @@
/**
* The default coloration shader used by standard rendering and animations.
* A fragment shader which creates a solid light source.
*/
class AdaptiveBackgroundShader extends AdaptiveLightingShader {
/**
* Memory allocations for the Adaptive Background Shader
* @type {string}
*/
static SHADER_HEADER = `
${this.FRAGMENT_UNIFORMS}
${this.VERTEX_FRAGMENT_VARYINGS}
${this.FRAGMENT_FUNCTIONS}
${this.CONSTANTS}
${this.SWITCH_COLOR}
`;
/** @inheritdoc */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
${this.ADJUSTMENTS}
${this.BACKGROUND_TECHNIQUES}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
/** @override */
static defaultUniforms = {
technique: 1,
contrast: 0,
shadows: 0,
saturation: 0,
intensity: 5,
attenuation: 0.5,
exposure: 0,
ratio: 0.5,
color: [1, 1, 1],
colorBackground: [1, 1, 1],
screenDimensions: [1, 1],
time: 0,
useSampler: true,
primaryTexture: null,
depthTexture: null,
darknessLevelTexture: null,
depthElevation: 1,
ambientBrightest: [1, 1, 1],
ambientDarkness: [0, 0, 0],
ambientDaylight: [1, 1, 1],
weights: [0, 0, 0, 0],
dimLevelCorrection: 1,
brightLevelCorrection: 2,
computeIllumination: false,
globalLight: false,
globalLightThresholds: [0, 0]
};
static {
const initial = foundry.data.LightData.cleanData();
this.defaultUniforms.technique = initial.coloration;
this.defaultUniforms.contrast = initial.contrast;
this.defaultUniforms.shadows = initial.shadows;
this.defaultUniforms.saturation = initial.saturation;
this.defaultUniforms.intensity = initial.animation.intensity;
this.defaultUniforms.attenuation = initial.attenuation;
}
/**
* Flag whether the background shader is currently required.
* Check vision modes requirements first, then
* if key uniforms are at their default values, we don't need to render the background container.
* @type {boolean}
*/
get isRequired() {
const vs = canvas.visibility.lightingVisibility;
// Checking if a vision mode is forcing the rendering
if ( vs.background === VisionMode.LIGHTING_VISIBILITY.REQUIRED ) return true;
// Checking if disabled
if ( vs.background === VisionMode.LIGHTING_VISIBILITY.DISABLED ) return false;
// Then checking keys
const keys = ["contrast", "saturation", "shadows", "exposure", "technique"];
return keys.some(k => this.uniforms[k] !== this.constructor.defaultUniforms[k]);
}
}

View File

@@ -0,0 +1,517 @@
/* eslint-disable no-tabs */
/**
* @typedef {Object} ShaderTechnique
* @property {number} id The numeric identifier of the technique
* @property {string} label The localization string that labels the technique
* @property {string|undefined} coloration The coloration shader fragment when the technique is used
* @property {string|undefined} illumination The illumination shader fragment when the technique is used
* @property {string|undefined} background The background shader fragment when the technique is used
*/
/**
* This class defines an interface which all adaptive lighting shaders extend.
*/
class AdaptiveLightingShader extends AbstractBaseShader {
/**
* Has this lighting shader a forced default color?
* @type {boolean}
*/
static forceDefaultColor = false;
/* -------------------------------------------- */
/** Called before rendering. */
update() {
this.uniforms.depthElevation = canvas.masks.depth.mapElevation(this.uniforms.elevation ?? 0);
}
/* -------------------------------------------- */
/**
* Common attributes for vertex shaders.
* @type {string}
*/
static VERTEX_ATTRIBUTES = `
attribute vec2 aVertexPosition;
attribute float aDepthValue;
`;
/**
* Common uniforms for vertex shaders.
* @type {string}
*/
static VERTEX_UNIFORMS = `
uniform mat3 translationMatrix;
uniform mat3 projectionMatrix;
uniform float rotation;
uniform float angle;
uniform float radius;
uniform float depthElevation;
uniform vec2 screenDimensions;
uniform vec2 resolution;
uniform vec3 origin;
uniform vec3 dimensions;
`;
/**
* Common varyings shared by vertex and fragment shaders.
* @type {string}
*/
static VERTEX_FRAGMENT_VARYINGS = `
varying vec2 vUvs;
varying vec2 vSamplerUvs;
varying float vDepth;
`;
/**
* Common functions used by the vertex shaders.
* @type {string}
* @abstract
*/
static VERTEX_FUNCTIONS = "";
/**
* Common uniforms shared by fragment shaders.
* @type {string}
*/
static FRAGMENT_UNIFORMS = `
uniform int technique;
uniform bool useSampler;
uniform bool hasColor;
uniform bool computeIllumination;
uniform bool linkedToDarknessLevel;
uniform bool enableVisionMasking;
uniform bool globalLight;
uniform float attenuation;
uniform float borderDistance;
uniform float contrast;
uniform float shadows;
uniform float exposure;
uniform float saturation;
uniform float intensity;
uniform float brightness;
uniform float luminosity;
uniform float pulse;
uniform float brightnessPulse;
uniform float backgroundAlpha;
uniform float illuminationAlpha;
uniform float colorationAlpha;
uniform float ratio;
uniform float time;
uniform float darknessLevel;
uniform float darknessPenalty;
uniform vec2 globalLightThresholds;
uniform vec3 color;
uniform vec3 colorBackground;
uniform vec3 colorVision;
uniform vec3 colorTint;
uniform vec3 colorEffect;
uniform vec3 colorDim;
uniform vec3 colorBright;
uniform vec3 ambientDaylight;
uniform vec3 ambientDarkness;
uniform vec3 ambientBrightest;
uniform int dimLevelCorrection;
uniform int brightLevelCorrection;
uniform vec4 weights;
uniform sampler2D primaryTexture;
uniform sampler2D framebufferTexture;
uniform sampler2D depthTexture;
uniform sampler2D darknessLevelTexture;
uniform sampler2D visionTexture;
// Shared uniforms with vertex shader
uniform ${PIXI.settings.PRECISION_VERTEX} float rotation;
uniform ${PIXI.settings.PRECISION_VERTEX} float angle;
uniform ${PIXI.settings.PRECISION_VERTEX} float radius;
uniform ${PIXI.settings.PRECISION_VERTEX} float depthElevation;
uniform ${PIXI.settings.PRECISION_VERTEX} vec2 resolution;
uniform ${PIXI.settings.PRECISION_VERTEX} vec2 screenDimensions;
uniform ${PIXI.settings.PRECISION_VERTEX} vec3 origin;
uniform ${PIXI.settings.PRECISION_VERTEX} vec3 dimensions;
uniform ${PIXI.settings.PRECISION_VERTEX} mat3 translationMatrix;
uniform ${PIXI.settings.PRECISION_VERTEX} mat3 projectionMatrix;
`;
/**
* Common functions used by the fragment shaders.
* @type {string}
* @abstract
*/
static FRAGMENT_FUNCTIONS = `
#define DARKNESS -2
#define HALFDARK -1
#define UNLIT 0
#define DIM 1
#define BRIGHT 2
#define BRIGHTEST 3
vec3 computedDimColor;
vec3 computedBrightColor;
vec3 computedBackgroundColor;
float computedDarknessLevel;
vec3 getCorrectedColor(int level) {
if ( (level == HALFDARK) || (level == DIM) ) {
return computedDimColor;
} else if ( (level == BRIGHT) || (level == DARKNESS) ) {
return computedBrightColor;
} else if ( level == BRIGHTEST ) {
return ambientBrightest;
} else if ( level == UNLIT ) {
return computedBackgroundColor;
}
return computedDimColor;
}
`;
/** @inheritdoc */
static CONSTANTS = `
${super.CONSTANTS}
const float INVTHREE = 1.0 / 3.0;
const vec2 PIVOT = vec2(0.5);
const vec4 ALLONES = vec4(1.0);
`;
/** @inheritdoc */
static vertexShader = `
${this.VERTEX_ATTRIBUTES}
${this.VERTEX_UNIFORMS}
${this.VERTEX_FRAGMENT_VARYINGS}
${this.VERTEX_FUNCTIONS}
void main() {
vec3 tPos = translationMatrix * vec3(aVertexPosition, 1.0);
vUvs = aVertexPosition * 0.5 + 0.5;
vDepth = aDepthValue;
vSamplerUvs = tPos.xy / screenDimensions;
gl_Position = vec4((projectionMatrix * tPos).xy, 0.0, 1.0);
}`;
/* -------------------------------------------- */
/* GLSL Helper Functions */
/* -------------------------------------------- */
/**
* Construct adaptive shader according to shader type
* @param {string} shaderType shader type to construct : coloration, illumination, background, etc.
* @returns {string} the constructed shader adaptive block
*/
static getShaderTechniques(shaderType) {
let shader = "";
let index = 0;
for ( let technique of Object.values(this.SHADER_TECHNIQUES) ) {
if ( technique[shaderType] ) {
let cond = `if ( technique == ${technique.id} )`;
if ( index > 0 ) cond = `else ${cond}`;
shader += `${cond} {${technique[shaderType]}\n}\n`;
index++;
}
}
return shader;
}
/* -------------------------------------------- */
/**
* The coloration technique coloration shader fragment
* @type {string}
*/
static get COLORATION_TECHNIQUES() {
return this.getShaderTechniques("coloration");
}
/* -------------------------------------------- */
/**
* The coloration technique illumination shader fragment
* @type {string}
*/
static get ILLUMINATION_TECHNIQUES() {
return this.getShaderTechniques("illumination");
}
/* -------------------------------------------- */
/**
* The coloration technique background shader fragment
* @type {string}
*/
static get BACKGROUND_TECHNIQUES() {
return this.getShaderTechniques("background");
}
/* -------------------------------------------- */
/**
* The adjustments made into fragment shaders
* @type {string}
*/
static get ADJUSTMENTS() {
return `vec3 changedColor = finalColor;\n
${this.CONTRAST}
${this.SATURATION}
${this.EXPOSURE}
${this.SHADOW}
if ( useSampler ) finalColor = changedColor;`;
}
/* -------------------------------------------- */
/**
* 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 = `
// Computing exposed color for background
if ( exposure > 0.0 ) {
float halfExposure = exposure * 0.5;
float attenuationStrength = attenuation * 0.25;
float lowerEdge = 0.98 - attenuationStrength;
float upperEdge = 1.02 + attenuationStrength;
float finalExposure = halfExposure *
(1.0 - smoothstep(ratio * lowerEdge, clamp(ratio * upperEdge, 0.0001, 1.0), dist)) +
halfExposure;
changedColor *= (1.0 + finalExposure);
}
`;
/* -------------------------------------------- */
/**
* Switch between an inner and outer color, by comparing distance from center to ratio
* Apply a strong gradient between the two areas if attenuation uniform is set to true
* @type {string}
*/
static SWITCH_COLOR = `
vec3 switchColor( in vec3 innerColor, in vec3 outerColor, in float dist ) {
float attenuationStrength = attenuation * 0.7;
float lowerEdge = 0.99 - attenuationStrength;
float upperEdge = 1.01 + attenuationStrength;
return mix(innerColor, outerColor, smoothstep(ratio * lowerEdge, clamp(ratio * upperEdge, 0.0001, 1.0), dist));
}`;
/* -------------------------------------------- */
/**
* Shadow adjustment
* @type {string}
*/
static SHADOW = `
// Computing shadows
if ( shadows != 0.0 ) {
float shadowing = mix(1.0, smoothstep(0.50, 0.80, perceivedBrightness(changedColor)), shadows);
// Applying shadow factor
changedColor *= shadowing;
}`;
/* -------------------------------------------- */
/**
* Transition between bright and dim colors, if requested
* @type {string}
*/
static TRANSITION = `
finalColor = switchColor(computedBrightColor, computedDimColor, dist);`;
/**
* Incorporate falloff if a attenuation uniform is requested
* @type {string}
*/
static FALLOFF = `
if ( attenuation != 0.0 ) depth *= smoothstep(1.0, 1.0 - attenuation, dist);
`;
/**
* Compute illumination uniforms
* @type {string}
*/
static COMPUTE_ILLUMINATION = `
float weightDark = weights.x;
float weightHalfdark = weights.y;
float weightDim = weights.z;
float weightBright = weights.w;
if ( computeIllumination ) {
computedDarknessLevel = texture2D(darknessLevelTexture, vSamplerUvs).r;
computedBackgroundColor = mix(ambientDaylight, ambientDarkness, computedDarknessLevel);
computedBrightColor = mix(computedBackgroundColor, ambientBrightest, weightBright);
computedDimColor = mix(computedBackgroundColor, computedBrightColor, weightDim);
// Apply lighting levels
vec3 correctedComputedBrightColor = getCorrectedColor(brightLevelCorrection);
vec3 correctedComputedDimColor = getCorrectedColor(dimLevelCorrection);
computedBrightColor = correctedComputedBrightColor;
computedDimColor = correctedComputedDimColor;
}
else {
computedBackgroundColor = colorBackground;
computedDimColor = colorDim;
computedBrightColor = colorBright;
computedDarknessLevel = darknessLevel;
}
computedDimColor = max(computedDimColor, computedBackgroundColor);
computedBrightColor = max(computedBrightColor, computedBackgroundColor);
if ( globalLight && ((computedDarknessLevel < globalLightThresholds[0]) || (computedDarknessLevel > globalLightThresholds[1])) ) discard;
`;
/**
* Initialize fragment with common properties
* @type {string}
*/
static FRAGMENT_BEGIN = `
${this.COMPUTE_ILLUMINATION}
float dist = distance(vUvs, vec2(0.5)) * 2.0;
vec4 depthColor = texture2D(depthTexture, vSamplerUvs);
float depth = smoothstep(0.0, 1.0, vDepth) * step(depthColor.g, depthElevation) * step(depthElevation, (254.5 / 255.0) - depthColor.r);
vec4 baseColor = useSampler ? texture2D(primaryTexture, vSamplerUvs) : vec4(1.0);
vec3 finalColor = baseColor.rgb;
`;
/**
* Shader final
* @type {string}
*/
static FRAGMENT_END = `
gl_FragColor = vec4(finalColor, 1.0) * depth;
`;
/* -------------------------------------------- */
/* Shader Techniques for lighting */
/* -------------------------------------------- */
/**
* A mapping of available shader techniques
* @type {Record<string, ShaderTechnique>}
*/
static SHADER_TECHNIQUES = {
LEGACY: {
id: 0,
label: "LIGHT.LegacyColoration"
},
LUMINANCE: {
id: 1,
label: "LIGHT.AdaptiveLuminance",
coloration: `
float reflection = perceivedBrightness(baseColor);
finalColor *= reflection;`
},
INTERNAL_HALO: {
id: 2,
label: "LIGHT.InternalHalo",
coloration: `
float reflection = perceivedBrightness(baseColor);
finalColor = switchColor(finalColor, finalColor * reflection, dist);`
},
EXTERNAL_HALO: {
id: 3,
label: "LIGHT.ExternalHalo",
coloration: `
float reflection = perceivedBrightness(baseColor);
finalColor = switchColor(finalColor * reflection, finalColor, dist);`
},
COLOR_BURN: {
id: 4,
label: "LIGHT.ColorBurn",
coloration: `
float reflection = perceivedBrightness(baseColor);
finalColor = (finalColor * (1.0 - sqrt(reflection))) / clamp(baseColor.rgb * 2.0, 0.001, 0.25);`
},
INTERNAL_BURN: {
id: 5,
label: "LIGHT.InternalBurn",
coloration: `
float reflection = perceivedBrightness(baseColor);
finalColor = switchColor((finalColor * (1.0 - sqrt(reflection))) / clamp(baseColor.rgb * 2.0, 0.001, 0.25), finalColor * reflection, dist);`
},
EXTERNAL_BURN: {
id: 6,
label: "LIGHT.ExternalBurn",
coloration: `
float reflection = perceivedBrightness(baseColor);
finalColor = switchColor(finalColor * reflection, (finalColor * (1.0 - sqrt(reflection))) / clamp(baseColor.rgb * 2.0, 0.001, 0.25), dist);`
},
LOW_ABSORPTION: {
id: 7,
label: "LIGHT.LowAbsorption",
coloration: `
float reflection = perceivedBrightness(baseColor);
reflection *= smoothstep(0.35, 0.75, reflection);
finalColor *= reflection;`
},
HIGH_ABSORPTION: {
id: 8,
label: "LIGHT.HighAbsorption",
coloration: `
float reflection = perceivedBrightness(baseColor);
reflection *= smoothstep(0.55, 0.85, reflection);
finalColor *= reflection;`
},
INVERT_ABSORPTION: {
id: 9,
label: "LIGHT.InvertAbsorption",
coloration: `
float r = reversePerceivedBrightness(baseColor);
finalColor *= (r * r * r * r * r);`
},
NATURAL_LIGHT: {
id: 10,
label: "LIGHT.NaturalLight",
coloration: `
float reflection = perceivedBrightness(baseColor);
finalColor *= reflection;`,
background: `
float ambientColorIntensity = perceivedBrightness(computedBackgroundColor);
vec3 mutedColor = mix(finalColor,
finalColor * mix(color, computedBackgroundColor, ambientColorIntensity),
backgroundAlpha);
finalColor = mix( finalColor,
mutedColor,
computedDarknessLevel);`
}
};
/* -------------------------------------------- */
/* Deprecations and Compatibility */
/* -------------------------------------------- */
/**
* @deprecated since v12
* @ignore
*/
getDarknessPenalty(darknessLevel, luminosity) {
const msg = "AdaptiveLightingShader#getDarknessPenalty is deprecated without replacement. " +
"The darkness penalty is no longer applied on light and vision sources.";
foundry.utils.logCompatibilityWarning(msg, {since: 12, until: 14});
return 0;
}
}

View File

@@ -0,0 +1,116 @@
/**
* The default coloration shader used by standard rendering and animations.
* A fragment shader which creates a light source.
*/
class AdaptiveColorationShader extends AdaptiveLightingShader {
/** @override */
static FRAGMENT_END = `
gl_FragColor = vec4(finalColor * depth, 1.0);
`;
/**
* The adjustments made into fragment shaders
* @type {string}
*/
static get ADJUSTMENTS() {
return `
vec3 changedColor = finalColor;\n
${this.SATURATION}
${this.SHADOW}
finalColor = changedColor;\n`;
}
/** @override */
static SHADOW = `
// Computing shadows
if ( shadows != 0.0 ) {
float shadowing = mix(1.0, smoothstep(0.25, 0.35, perceivedBrightness(baseColor.rgb)), shadows);
// Applying shadow factor
changedColor *= shadowing;
}
`;
/**
* Memory allocations for the Adaptive Coloration Shader
* @type {string}
*/
static SHADER_HEADER = `
${this.FRAGMENT_UNIFORMS}
${this.VERTEX_FRAGMENT_VARYINGS}
${this.FRAGMENT_FUNCTIONS}
${this.CONSTANTS}
${this.SWITCH_COLOR}
`;
/** @inheritdoc */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
finalColor = color * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
/** @inheritdoc */
static defaultUniforms = {
technique: 1,
shadows: 0,
contrast: 0,
saturation: 0,
colorationAlpha: 1,
intensity: 5,
attenuation: 0.5,
ratio: 0.5,
color: [1, 1, 1],
time: 0,
hasColor: false,
screenDimensions: [1, 1],
useSampler: false,
primaryTexture: null,
depthTexture: null,
darknessLevelTexture: null,
depthElevation: 1,
ambientBrightest: [1, 1, 1],
ambientDarkness: [0, 0, 0],
ambientDaylight: [1, 1, 1],
weights: [0, 0, 0, 0],
dimLevelCorrection: 1,
brightLevelCorrection: 2,
computeIllumination: false,
globalLight: false,
globalLightThresholds: [0, 0]
};
static {
const initial = foundry.data.LightData.cleanData();
this.defaultUniforms.technique = initial.coloration;
this.defaultUniforms.contrast = initial.contrast;
this.defaultUniforms.shadows = initial.shadows;
this.defaultUniforms.saturation = initial.saturation;
this.defaultUniforms.intensity = initial.animation.intensity;
this.defaultUniforms.attenuation = initial.attenuation;
}
/**
* Flag whether the coloration shader is currently required.
* @type {boolean}
*/
get isRequired() {
const vs = canvas.visibility.lightingVisibility;
// Checking if a vision mode is forcing the rendering
if ( vs.coloration === VisionMode.LIGHTING_VISIBILITY.REQUIRED ) return true;
// Checking if disabled
if ( vs.coloration === VisionMode.LIGHTING_VISIBILITY.DISABLED ) return false;
// Otherwise, we need the coloration if it has color
return this.constructor.forceDefaultColor || this.uniforms.hasColor;
}
}

View File

@@ -0,0 +1,119 @@
/**
* The default coloration shader used by standard rendering and animations.
* A fragment shader which creates a solid light source.
*/
class AdaptiveDarknessShader extends AdaptiveLightingShader {
/** @override */
update() {
super.update();
this.uniforms.darknessLevel = canvas.environment.darknessLevel;
}
/* -------------------------------------------- */
/**
* Flag whether the darkness shader is currently required.
* Check vision modes requirements first, then
* if key uniforms are at their default values, we don't need to render the background container.
* @type {boolean}
*/
get isRequired() {
const vs = canvas.visibility.lightingVisibility;
// Checking if darkness layer is disabled
if ( vs.darkness === VisionMode.LIGHTING_VISIBILITY.DISABLED ) return false;
// Otherwise, returns true in every circumstances
return true;
}
/* -------------------------------------------- */
/* GLSL Statics */
/* -------------------------------------------- */
/** @override */
static defaultUniforms = {
intensity: 5,
color: Color.from("#8651d5").rgb,
screenDimensions: [1, 1],
time: 0,
primaryTexture: null,
depthTexture: null,
visionTexture: null,
darknessLevelTexture: null,
depthElevation: 1,
ambientBrightest: [1, 1, 1],
ambientDarkness: [0, 0, 0],
ambientDaylight: [1, 1, 1],
weights: [0, 0, 0, 0],
dimLevelCorrection: 1,
brightLevelCorrection: 2,
borderDistance: 0,
darknessLevel: 0,
computeIllumination: false,
globalLight: false,
globalLightThresholds: [0, 0],
enableVisionMasking: false
};
static {
const initial = foundry.data.LightData.cleanData();
this.defaultUniforms.intensity = initial.animation.intensity;
}
/* -------------------------------------------- */
/**
* Shader final
* @type {string}
*/
static FRAGMENT_END = `
gl_FragColor = vec4(finalColor, 1.0) * depth;
`;
/* -------------------------------------------- */
/**
* Initialize fragment with common properties
* @type {string}
*/
static FRAGMENT_BEGIN = `
${this.COMPUTE_ILLUMINATION}
float dist = distance(vUvs, vec2(0.5)) * 2.0;
vec4 depthColor = texture2D(depthTexture, vSamplerUvs);
float depth = smoothstep(0.0, 1.0, vDepth) *
step(depthColor.g, depthElevation) *
step(depthElevation, (254.5 / 255.0) - depthColor.r) *
(enableVisionMasking ? 1.0 - step(texture2D(visionTexture, vSamplerUvs).r, 0.0) : 1.0) *
(1.0 - smoothstep(borderDistance, 1.0, dist));
vec4 baseColor = texture2D(primaryTexture, vSamplerUvs);
vec3 finalColor = baseColor.rgb;
`;
/* -------------------------------------------- */
/**
* Memory allocations for the Adaptive Background Shader
* @type {string}
*/
static SHADER_HEADER = `
${this.FRAGMENT_UNIFORMS}
${this.VERTEX_FRAGMENT_VARYINGS}
${this.FRAGMENT_FUNCTIONS}
${this.CONSTANTS}
`;
/* -------------------------------------------- */
/** @inheritdoc */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
finalColor *= (mix(color, color * 0.33, darknessLevel) * colorationAlpha);
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,81 @@
/**
* Bewitching Wave animation illumination shader
*/
class BewitchingWaveIlluminationShader extends AdaptiveIlluminationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PRNG}
${this.NOISE}
${this.FBM(4, 1.0)}
${this.PERCEIVED_BRIGHTNESS}
// Transform UV
vec2 transform(in vec2 uv, in float dist) {
float t = time * 0.25;
mat2 rotmat = mat2(cos(t), -sin(t), sin(t), cos(t));
mat2 scalemat = mat2(2.5, 0.0, 0.0, 2.5);
uv -= vec2(0.5);
uv *= rotmat * scalemat;
uv += vec2(0.5);
return uv;
}
float bwave(in float dist) {
vec2 uv = transform(vUvs, dist);
float motion = fbm(uv + time * 0.25);
float distortion = mix(1.0, motion, clamp(1.0 - dist, 0.0, 1.0));
float sinWave = 0.5 * (sin(-time * 6.0 + dist * 10.0 * intensity * distortion) + 1.0);
return 0.3 * sinWave + 0.8;
}
void main() {
${this.FRAGMENT_BEGIN}
${this.TRANSITION}
finalColor *= bwave(dist);
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}
/* -------------------------------------------- */
/**
* Bewitching Wave animation coloration shader
*/
class BewitchingWaveColorationShader extends AdaptiveColorationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PRNG}
${this.NOISE}
${this.FBM(4, 1.0)}
${this.PERCEIVED_BRIGHTNESS}
// Transform UV
vec2 transform(in vec2 uv, in float dist) {
float t = time * 0.25;
mat2 rotmat = mat2(cos(t), -sin(t), sin(t), cos(t));
mat2 scalemat = mat2(2.5, 0.0, 0.0, 2.5);
uv -= vec2(0.5);
uv *= rotmat * scalemat;
uv += vec2(0.5);
return uv;
}
float bwave(in float dist) {
vec2 uv = transform(vUvs, dist);
float motion = fbm(uv + time * 0.25);
float distortion = mix(1.0, motion, clamp(1.0 - dist, 0.0, 1.0));
float sinWave = 0.5 * (sin(-time * 6.0 + dist * 10.0 * intensity * distortion) + 1.0);
return 0.55 * sinWave + 0.8;
}
void main() {
${this.FRAGMENT_BEGIN}
finalColor = color * bwave(dist) * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,41 @@
/**
* Black Hole animation illumination shader
*/
class BlackHoleDarknessShader extends AdaptiveDarknessShader {
/* -------------------------------------------- */
/* GLSL Statics */
/* -------------------------------------------- */
/** @inheritdoc */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PRNG}
${this.NOISE}
${this.FBMHQ()}
${this.PERCEIVED_BRIGHTNESS}
// create an emanation composed of n beams, n = intensity
vec3 beamsEmanation(in vec2 uv, in float dist, in vec3 pCol) {
float angle = atan(uv.x, uv.y) * INVTWOPI;
// Create the beams
float dad = mix(0.33, 5.0, dist);
float beams = fract(angle + sin(dist * 30.0 * (intensity * 0.2) - time + fbm(uv * 10.0 + time * 0.25, 1.0) * dad));
// Compose the final beams and reverse beams, to get a nice gradient on EACH side of the beams.
beams = max(beams, 1.0 - beams);
// Creating the effect
return smoothstep(0.0, 1.1 + (intensity * 0.1), beams * pCol);
}
void main() {
${this.FRAGMENT_BEGIN}
vec2 uvs = (2.0 * vUvs) - 1.0;
finalColor *= (mix(color, color * 0.66, darknessLevel) * colorationAlpha);
float rd = pow(1.0 - dist, 3.0);
finalColor = beamsEmanation(uvs, rd, finalColor);
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,25 @@
/**
* Chroma animation coloration shader
*/
class ChromaColorationShader extends AdaptiveColorationShader {
/** @override */
static forceDefaultColor = true;
/** @override */
static fragmentShader = `
${this.SHADER_HEADER}
${this.HSB2RGB}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
finalColor = mix( color,
hsb2rgb(vec3(time * 0.25, 1.0, 1.0)),
intensity * 0.1 ) * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,38 @@
/**
* Emanation animation coloration shader
*/
class EmanationColorationShader extends AdaptiveColorationShader {
/** @override */
static forceDefaultColor = true;
/** @override */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
// Create an emanation composed of n beams, n = intensity
vec3 beamsEmanation(in vec2 uv, in float dist) {
float angle = atan(uv.x, uv.y) * INVTWOPI;
// create the beams
float beams = fract( angle * intensity + sin(dist * 10.0 - time));
// compose the final beams with max, to get a nice gradient on EACH side of the beams.
beams = max(beams, 1.0 - beams);
// creating the effect : applying color and color correction. saturate the entire output color.
return smoothstep( 0.0, 1.0, beams * color);
}
void main() {
${this.FRAGMENT_BEGIN}
vec2 uvs = (2.0 * vUvs) - 1.0;
// apply beams emanation, fade and alpha
finalColor = beamsEmanation(uvs, dist) * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,77 @@
/**
* Energy field animation coloration shader
*/
class EnergyFieldColorationShader extends AdaptiveColorationShader {
/** @override */
static forceDefaultColor = true;
/** @override */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PRNG3D}
${this.PERCEIVED_BRIGHTNESS}
// classic 3d voronoi (with some bug fixes)
vec3 voronoi3d(const in vec3 x) {
vec3 p = floor(x);
vec3 f = fract(x);
float id = 0.0;
vec2 res = vec2(100.0);
for (int k = -1; k <= 1; k++) {
for (int j = -1; j <= 1; j++) {
for (int i = -1; i <= 1; i++) {
vec3 b = vec3(float(i), float(j), float(k));
vec3 r = vec3(b) - f + random(p + b);
float d = dot(r, r);
float cond = max(sign(res.x - d), 0.0);
float nCond = 1.0 - cond;
float cond2 = nCond * max(sign(res.y - d), 0.0);
float nCond2 = 1.0 - cond2;
id = (dot(p + b, vec3(1.0, 67.0, 142.0)) * cond) + (id * nCond);
res = vec2(d, res.x) * cond + res * nCond;
res.y = cond2 * d + nCond2 * res.y;
}
}
}
// replaced abs(id) by pow( abs(id + 10.0), 0.01)
// needed to remove artifacts in some specific configuration
return vec3( sqrt(res), pow( abs(id + 10.0), 0.01) );
}
void main() {
${this.FRAGMENT_BEGIN}
vec2 uv = vUvs;
// Hemispherize and scaling the uv
float f = (1.0 - sqrt(1.0 - dist)) / dist;
uv -= vec2(0.5);
uv *= f * 4.0 * intensity;
uv += vec2(0.5);
// time and uv motion variables
float t = time * 0.4;
float uvx = cos(uv.x - t);
float uvy = cos(uv.y + t);
float uvxt = cos(uv.x + sin(t));
float uvyt = sin(uv.y + cos(t));
// creating the voronoi 3D sphere, applying motion
vec3 c = voronoi3d(vec3(uv.x - uvx + uvyt,
mix(uv.x, uv.y, 0.5) + uvxt - uvyt + uvx,
uv.y + uvxt - uvx));
// applying color and contrast, to create sharp black areas.
finalColor = c.x * c.x * c.x * color * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,95 @@
/**
* Fairy light animation coloration shader
*/
class FairyLightColorationShader extends AdaptiveColorationShader {
/** @override */
static forceDefaultColor = true;
/** @override */
static fragmentShader = `
${this.SHADER_HEADER}
${this.HSB2RGB}
${this.PRNG}
${this.NOISE}
${this.FBM(3, 1.0)}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
// Creating distortion with vUvs and fbm
float distortion1 = fbm(vec2(
fbm(vUvs * 3.0 + time * 0.50),
fbm((-vUvs + vec2(1.)) * 5.0 + time * INVTHREE)));
float distortion2 = fbm(vec2(
fbm(-vUvs * 3.0 + time * 0.50),
fbm((-vUvs + vec2(1.)) * 5.0 - time * INVTHREE)));
vec2 uv = vUvs;
// time related var
float t = time * 0.5;
float tcos = 0.5 * (0.5 * (cos(t)+1.0)) + 0.25;
float tsin = 0.5 * (0.5 * (sin(t)+1.0)) + 0.25;
// Creating distortions with cos and sin : create fluidity
uv -= PIVOT;
uv *= tcos * distortion1;
uv *= tsin * distortion2;
uv *= fbm(vec2(time + distortion1, time + distortion2));
uv += PIVOT;
// Creating the rainbow
float intens = intensity * 0.1;
vec2 nuv = vUvs * 2.0 - 1.0;
vec2 puv = vec2(atan(nuv.x, nuv.y) * INVTWOPI + 0.5, length(nuv));
vec3 rainbow = hsb2rgb(vec3(puv.x + puv.y - time * 0.2, 1.0, 1.0));
vec3 mixedColor = mix(color, rainbow, smoothstep(0.0, 1.5 - intens, dist));
finalColor = distortion1 * distortion1 *
distortion2 * distortion2 *
mixedColor * colorationAlpha * (1.0 - dist * dist * dist) *
mix( uv.x + distortion1 * 4.5 * (intensity * 0.4),
uv.y + distortion2 * 4.5 * (intensity * 0.4), tcos);
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}
/* -------------------------------------------- */
/**
* Fairy light animation illumination shader
*/
class FairyLightIlluminationShader extends AdaptiveIlluminationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
${this.PRNG}
${this.NOISE}
${this.FBM(3, 1.0)}
void main() {
${this.FRAGMENT_BEGIN}
// Creating distortion with vUvs and fbm
float distortion1 = fbm(vec2(
fbm(vUvs * 3.0 - time * 0.50),
fbm((-vUvs + vec2(1.)) * 5.0 + time * INVTHREE)));
float distortion2 = fbm(vec2(
fbm(-vUvs * 3.0 - time * 0.50),
fbm((-vUvs + vec2(1.)) * 5.0 - time * INVTHREE)));
// linear interpolation motion
float motionWave = 0.5 * (0.5 * (cos(time * 0.5) + 1.0)) + 0.25;
${this.TRANSITION}
finalColor *= mix(distortion1, distortion2, motionWave);
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,82 @@
/**
* Alternative torch illumination shader
*/
class FlameIlluminationShader extends AdaptiveIlluminationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
${this.TRANSITION}
finalColor *= brightnessPulse;
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
/** @inheritdoc */
static defaultUniforms = ({...super.defaultUniforms, brightnessPulse: 1});
}
/* -------------------------------------------- */
/**
* Alternative torch coloration shader
*/
class FlameColorationShader extends AdaptiveColorationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PRNG}
${this.NOISE}
${this.FBMHQ(3)}
${this.PERCEIVED_BRIGHTNESS}
vec2 scale(in vec2 uv, in float scale) {
mat2 scalemat = mat2(scale, 0.0, 0.0, scale);
uv -= PIVOT;
uv *= scalemat;
uv += PIVOT;
return uv;
}
void main() {
${this.FRAGMENT_BEGIN}
vec2 uv = scale(vUvs, 10.0 * ratio);
float intens = pow(0.1 * intensity, 2.0);
float fratioInner = ratio * (intens * 0.5) -
(0.005 *
fbm( vec2(
uv.x + time * 8.01,
uv.y + time * 10.72), 1.0));
float fratioOuter = ratio - (0.007 *
fbm( vec2(
uv.x + time * 7.04,
uv.y + time * 9.51), 2.0));
float fdist = max(dist - fratioInner * intens, 0.0);
float flameDist = smoothstep(clamp(0.97 - fratioInner, 0.0, 1.0),
clamp(1.03 - fratioInner, 0.0, 1.0),
1.0 - fdist);
float flameDistInner = smoothstep(clamp(0.95 - fratioOuter, 0.0, 1.0),
clamp(1.05 - fratioOuter, 0.0, 1.0),
1.0 - fdist);
vec3 flameColor = color * 8.0;
vec3 flameFlickerColor = color * 1.2;
finalColor = mix(mix(color, flameFlickerColor, flameDistInner),
flameColor,
flameDist) * brightnessPulse * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}
`;
/** @inheritdoc */
static defaultUniforms = ({ ...super.defaultUniforms, brightnessPulse: 1});
}

View File

@@ -0,0 +1,53 @@
/**
* Fog animation coloration shader
*/
class FogColorationShader extends AdaptiveColorationShader {
/** @override */
static forceDefaultColor = true;
/** @override */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PRNG}
${this.NOISE}
${this.FBM(4, 1.0)}
${this.PERCEIVED_BRIGHTNESS}
vec3 fog() {
// constructing the palette
vec3 c1 = color * 0.60;
vec3 c2 = color * 0.95;
vec3 c3 = color * 0.50;
vec3 c4 = color * 0.75;
vec3 c5 = vec3(0.3);
vec3 c6 = color;
// creating the deformation
vec2 uv = vUvs;
vec2 p = uv.xy * 8.0;
// time motion fbm and palette mixing
float q = fbm(p - time * 0.1);
vec2 r = vec2(fbm(p + q - time * 0.5 - p.x - p.y),
fbm(p + q - time * 0.3));
vec3 c = clamp(mix(c1,
c2,
fbm(p + r)) + mix(c3, c4, r.x)
- mix(c5, c6, r.y),
vec3(0.0), vec3(1.0));
// returning the color
return c;
}
void main() {
${this.FRAGMENT_BEGIN}
float intens = intensity * 0.2;
// applying fog
finalColor = fog() * intens * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,87 @@
/**
* A futuristic Force Grid animation.
*/
class ForceGridColorationShader extends AdaptiveColorationShader {
/** @override */
static forceDefaultColor = true;
/** @override */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
const float MAX_INTENSITY = 1.2;
const float MIN_INTENSITY = 0.8;
vec2 hspherize(in vec2 uv, in float dist) {
float f = (1.0 - sqrt(1.0 - dist)) / dist;
uv -= vec2(0.50);
uv *= f * 5.0;
uv += vec2(0.5);
return uv;
}
float wave(in float dist) {
float sinWave = 0.5 * (sin(time * 6.0 + pow(1.0 - dist, 0.10) * 35.0 * intensity) + 1.0);
return ((MAX_INTENSITY - MIN_INTENSITY) * sinWave) + MIN_INTENSITY;
}
float fpert(in float d, in float p) {
return max(0.3 -
mod(p + time + d * 0.3, 3.5),
0.0) * intensity * 2.0;
}
float pert(in vec2 uv, in float dist, in float d, in float w) {
uv -= vec2(0.5);
float f = fpert(d, min( uv.y, uv.x)) +
fpert(d, min(-uv.y, uv.x)) +
fpert(d, min(-uv.y, -uv.x)) +
fpert(d, min( uv.y, -uv.x));
f *= f;
return max(f, 3.0 - f) * w;
}
vec3 forcegrid(vec2 suv, in float dist) {
vec2 uv = suv - vec2(0.2075, 0.2075);
vec2 cid2 = floor(uv);
float cid = (cid2.y + cid2.x);
uv = fract(uv);
float r = 0.3;
float d = 1.0;
float e;
float c;
for( int i = 0; i < 5; i++ ) {
e = uv.x - r;
c = clamp(1.0 - abs(e * 0.75), 0.0, 1.0);
d += pow(c, 200.0) * (1.0 - dist);
if ( e > 0.0 ) {
uv.x = (uv.x - r) / (2.0 - r);
}
uv = uv.yx;
}
float w = wave(dist);
vec3 col = vec3(max(d - 1.0, 0.0)) * 1.8;
col *= pert(suv, dist * intensity * 4.0, d, w);
col += color * 0.30 * w;
return col * color;
}
void main() {
${this.FRAGMENT_BEGIN}
vec2 uvs = vUvs;
uvs -= PIVOT;
uvs *= intensity * 0.2;
uvs += PIVOT;
vec2 suvs = hspherize(uvs, dist);
finalColor = forcegrid(suvs, dist) * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}
`;
}

View File

@@ -0,0 +1,86 @@
/**
* Ghost light animation illumination shader
*/
class GhostLightIlluminationShader extends AdaptiveIlluminationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
${this.PRNG}
${this.NOISE}
${this.FBM(3, 1.0)}
void main() {
${this.FRAGMENT_BEGIN}
// Creating distortion with vUvs and fbm
float distortion1 = fbm(vec2(
fbm(vUvs * 5.0 - time * 0.50),
fbm((-vUvs - vec2(0.01)) * 5.0 + time * INVTHREE)));
float distortion2 = fbm(vec2(
fbm(-vUvs * 5.0 - time * 0.50),
fbm((-vUvs + vec2(0.01)) * 5.0 + time * INVTHREE)));
vec2 uv = vUvs;
// time related var
float t = time * 0.5;
float tcos = 0.5 * (0.5 * (cos(t)+1.0)) + 0.25;
${this.TRANSITION}
finalColor *= mix( distortion1 * 1.5 * (intensity * 0.2),
distortion2 * 1.5 * (intensity * 0.2), tcos);
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}
/* -------------------------------------------- */
/**
* Ghost light animation coloration shader
*/
class GhostLightColorationShader extends AdaptiveColorationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PRNG}
${this.NOISE}
${this.FBM(3, 1.0)}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
// Creating distortion with vUvs and fbm
float distortion1 = fbm(vec2(
fbm(vUvs * 3.0 + time * 0.50),
fbm((-vUvs + vec2(1.)) * 5.0 + time * INVTHREE)));
float distortion2 = fbm(vec2(
fbm(-vUvs * 3.0 + time * 0.50),
fbm((-vUvs + vec2(1.)) * 5.0 - time * INVTHREE)));
vec2 uv = vUvs;
// time related var
float t = time * 0.5;
float tcos = 0.5 * (0.5 * (cos(t)+1.0)) + 0.25;
float tsin = 0.5 * (0.5 * (sin(t)+1.0)) + 0.25;
// Creating distortions with cos and sin : create fluidity
uv -= PIVOT;
uv *= tcos * distortion1;
uv *= tsin * distortion2;
uv *= fbm(vec2(time + distortion1, time + distortion2));
uv += PIVOT;
finalColor = distortion1 * distortion1 *
distortion2 * distortion2 *
color * pow(1.0 - dist, dist)
* colorationAlpha * mix( uv.x + distortion1 * 4.5 * (intensity * 0.2),
uv.y + distortion2 * 4.5 * (intensity * 0.2), tcos);
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,83 @@
/**
* Hexagonal dome animation coloration shader
*/
class HexaDomeColorationShader extends AdaptiveColorationShader {
/** @override */
static forceDefaultColor = true;
/** @override */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
// rotate and scale uv
vec2 transform(in vec2 uv, in float dist) {
float hspherize = (1.0 - sqrt(1.0 - dist)) / dist;
float t = -time * 0.20;
float scale = 10.0 / (11.0 - intensity);
float cost = cos(t);
float sint = sin(t);
mat2 rotmat = mat2(cost, -sint, sint, cost);
mat2 scalemat = mat2(scale, 0.0, 0.0, scale);
uv -= PIVOT;
uv *= rotmat * scalemat * hspherize;
uv += PIVOT;
return uv;
}
// Adapted classic hexa algorithm
float hexDist(in vec2 uv) {
vec2 p = abs(uv);
float c = dot(p, normalize(vec2(1.0, 1.73)));
c = max(c, p.x);
return c;
}
vec4 hexUvs(in vec2 uv) {
const vec2 r = vec2(1.0, 1.73);
const vec2 h = r*0.5;
vec2 a = mod(uv, r) - h;
vec2 b = mod(uv - h, r) - h;
vec2 gv = dot(a, a) < dot(b,b) ? a : b;
float x = atan(gv.x, gv.y);
float y = 0.55 - hexDist(gv);
vec2 id = uv - gv;
return vec4(x, y, id.x, id.y);
}
vec3 hexa(in vec2 uv) {
float t = time;
vec2 uv1 = uv + vec2(0.0, sin(uv.y) * 0.25);
vec2 uv2 = 0.5 * uv1 + 0.5 * uv + vec2(0.55, 0);
float a = 0.2;
float c = 0.5;
float s = -1.0;
uv2 *= mat2(c, -s, s, c);
vec3 col = color;
float hexy = hexUvs(uv2 * 10.0).y;
float hexa = smoothstep( 3.0 * (cos(t)) + 4.5, 12.0, hexy * 20.0) * 3.0;
col *= mix(hexa, 1.0 - hexa, min(hexy, 1.0 - hexy));
col += color * fract(smoothstep(1.0, 2.0, hexy * 20.0)) * 0.65;
return col;
}
void main() {
${this.FRAGMENT_BEGIN}
// Rotate, magnify and hemispherize the uvs
vec2 uv = transform(vUvs, dist);
// Hexaify the uv (hemisphere) and apply fade and alpha
finalColor = hexa(uv) * pow(1.0 - dist, 0.18) * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,57 @@
/**
* Light dome animation coloration shader
*/
class LightDomeColorationShader extends AdaptiveColorationShader {
/** @override */
static forceDefaultColor = true;
/** @override */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PRNG}
${this.NOISE}
${this.FBM(2)}
${this.PERCEIVED_BRIGHTNESS}
// Rotate and scale uv
vec2 transform(in vec2 uv, in float dist) {
float hspherize = (1.0 - sqrt(1.0 - dist)) / dist;
float t = time * 0.02;
mat2 rotmat = mat2(cos(t), -sin(t), sin(t), cos(t));
mat2 scalemat = mat2(8.0 * intensity, 0.0, 0.0, 8.0 * intensity);
uv -= PIVOT;
uv *= rotmat * scalemat * hspherize;
uv += PIVOT;
return uv;
}
vec3 ripples(in vec2 uv) {
// creating the palette
vec3 c1 = color * 0.550;
vec3 c2 = color * 0.020;
vec3 c3 = color * 0.3;
vec3 c4 = color;
vec3 c5 = color * 0.025;
vec3 c6 = color * 0.200;
vec2 p = uv + vec2(5.0);
float q = 2.0 * fbm(p + time * 0.2);
vec2 r = vec2(fbm(p + q + ( time ) - p.x - p.y), fbm(p * 2.0 + ( time )));
return clamp( mix( c1, c2, abs(fbm(p + r)) ) + mix( c3, c4, abs(r.x * r.x * r.x) ) - mix( c5, c6, abs(r.y * r.y)), vec3(0.0), vec3(1.0));
}
void main() {
${this.FRAGMENT_BEGIN}
// to hemispherize, rotate and magnify
vec2 uv = transform(vUvs, dist);
finalColor = ripples(uv) * pow(1.0 - dist, 0.25) * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,101 @@
/**
* Creates a gloomy ring of pure darkness.
*/
class MagicalGloomDarknessShader extends AdaptiveDarknessShader {
/* -------------------------------------------- */
/* GLSL Statics */
/* -------------------------------------------- */
/** @inheritdoc */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
${this.PRNG}
${this.NOISE}
${this.FBMHQ()}
vec3 colorScale(in float t) {
return vec3(1.0 + 0.8 * t) * t;
}
vec2 radialProjection(in vec2 uv, in float s, in float i) {
uv = vec2(0.5) - uv;
float px = 1.0 - fract(atan(uv.y, uv.x) / TWOPI + 0.25) + s;
float py = (length(uv) * (1.0 + i * 2.0) - i) * 2.0;
return vec2(px, py);
}
float interference(in vec2 n) {
float noise1 = noise(n);
float noise2 = noise(n * 2.1) * 0.6;
float noise3 = noise(n * 5.4) * 0.42;
return noise1 + noise2 + noise3;
}
float illuminate(in vec2 uv) {
float t = time;
// Adjust x-coordinate based on time and y-value
float xOffset = uv.y < 0.5
? 23.0 + t * 0.035
: -11.0 + t * 0.03;
uv.x += xOffset;
// Shift y-coordinate to range [0, 0.5]
uv.y = abs(uv.y - 0.5);
// Scale x-coordinate
uv.x *= (10.0 + 80.0 * intensity * 0.2);
// Compute interferences
float q = interference(uv - t * 0.013) * 0.5;
vec2 r = vec2(interference(uv + q * 0.5 + t - uv.x - uv.y), interference(uv + q - t));
// Compute final shade value
float sh = (r.y + r.y) * max(0.0, uv.y) + 0.1;
return sh * sh * sh;
}
vec3 voidHalf(in float intensity) {
float minThreshold = 0.35;
// Alter gradient
intensity = pow(intensity, 0.75);
// Compute the gradient
vec3 color = colorScale(intensity);
// Normalize the color by the sum of m2 and the color values
color /= (1.0 + max(vec3(0), color));
return color;
}
vec3 voidRing(in vec2 uvs) {
vec2 uv = (uvs - 0.5) / (borderDistance * 1.06) + 0.5;
float r = 3.6;
float ff = 1.0 - uv.y;
vec2 uv2 = uv;
uv2.y = 1.0 - uv2.y;
// Calculate color for upper half
vec3 colorUpper = voidHalf(illuminate(radialProjection(uv, 1.0, r))) * ff;
// Calculate color for lower half
vec3 colorLower = voidHalf(illuminate(radialProjection(uv2, 1.9, r))) * (1.0 - ff);
// Return upper and lower half combined
return colorUpper + colorLower;
}
void main() {
${this.FRAGMENT_BEGIN}
float lumBase = perceivedBrightness(finalColor);
lumBase = mix(lumBase, lumBase * 0.33, darknessLevel);
vec3 voidRingColor = voidRing(vUvs);
float lum = pow(perceivedBrightness(voidRingColor), 4.0);
vec3 voidRingFinal = vec3(perceivedBrightness(voidRingColor)) * color;
finalColor = voidRingFinal * lumBase * colorationAlpha;
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,44 @@
/**
* Pulse animation illumination shader
*/
class PulseIlluminationShader extends AdaptiveIlluminationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
float fading = pow(abs(1.0 - dist * dist), 1.01 - ratio);
${this.TRANSITION}
finalColor *= fading;
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}
/* -------------------------------------------- */
/**
* Pulse animation coloration shader
*/
class PulseColorationShader extends AdaptiveColorationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
float pfade(in float dist, in float pulse) {
return 1.0 - smoothstep(pulse * 0.5, 1.0, dist);
}
void main() {
${this.FRAGMENT_BEGIN}
finalColor = color * pfade(dist, pulse) * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
/** @inheritdoc */
static defaultUniforms = ({...super.defaultUniforms, pulse: 0});
}

View File

@@ -0,0 +1,30 @@
/**
* Radial rainbow animation coloration shader
*/
class RadialRainbowColorationShader extends AdaptiveColorationShader {
/** @override */
static forceDefaultColor = true;
/** @override */
static fragmentShader = `
${this.SHADER_HEADER}
${this.HSB2RGB}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
float intens = intensity * 0.1;
vec2 nuv = vUvs * 2.0 - 1.0;
vec2 puv = vec2(atan(nuv.x, nuv.y) * INVTWOPI + 0.5, length(nuv));
vec3 rainbow = hsb2rgb(vec3(puv.y - time * 0.2, 1.0, 1.0));
finalColor = mix(color, rainbow, smoothstep(0.0, 1.5 - intens, dist))
* (1.0 - dist * dist * dist);
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,40 @@
/**
* Revolving animation coloration shader
*/
class RevolvingColorationShader extends AdaptiveColorationShader {
/** @override */
static forceDefaultColor = true;
/** @override */
static fragmentShader = `
${this.SHADER_HEADER}
uniform float gradientFade;
uniform float beamLength;
${this.PERCEIVED_BRIGHTNESS}
${this.PIE}
${this.ROTATION}
void main() {
${this.FRAGMENT_BEGIN}
vec2 ncoord = vUvs * 2.0 - 1.0;
float angularIntensity = mix(PI, PI * 0.5, intensity * 0.1);
ncoord *= rot(angle + time);
float angularCorrection = pie(ncoord, angularIntensity, gradientFade, beamLength);
finalColor = color * colorationAlpha * angularCorrection;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}
`;
/** @inheritdoc */
static defaultUniforms = {
...super.defaultUniforms,
angle: 0,
gradientFade: 0.15,
beamLength: 1
};
}

View File

@@ -0,0 +1,64 @@
/**
* Roling mass illumination shader - intended primarily for darkness
*/
class RoilingDarknessShader extends AdaptiveDarknessShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
${this.PRNG}
${this.NOISE}
${this.FBM(3)}
void main() {
${this.FRAGMENT_BEGIN}
// Creating distortion with vUvs and fbm
float distortion1 = fbm( vec2(
fbm( vUvs * 2.5 + time * 0.5),
fbm( (-vUvs - vec2(0.01)) * 5.0 + time * INVTHREE)));
float distortion2 = fbm( vec2(
fbm( -vUvs * 5.0 + time * 0.5),
fbm( (vUvs + vec2(0.01)) * 2.5 + time * INVTHREE)));
// Timed values
float t = -time * 0.5;
float cost = cos(t);
float sint = sin(t);
// Rotation matrix
mat2 rotmat = mat2(cost, -sint, sint, cost);
vec2 uv = vUvs;
// Applying rotation before distorting
uv -= vec2(0.5);
uv *= rotmat;
uv += vec2(0.5);
// Amplify distortions
vec2 dstpivot = vec2( sin(min(distortion1 * 0.1, distortion2 * 0.1)),
cos(min(distortion1 * 0.1, distortion2 * 0.1)) ) * INVTHREE
- vec2( cos(max(distortion1 * 0.1, distortion2 * 0.1)),
sin(max(distortion1 * 0.1, distortion2 * 0.1)) ) * INVTHREE ;
vec2 apivot = PIVOT - dstpivot;
uv -= apivot;
uv *= 1.13 + 1.33 * (cos(sqrt(max(distortion1, distortion2)) + 1.0) * 0.5);
uv += apivot;
// distorted distance
float ddist = clamp(distance(uv, PIVOT) * 2.0, 0.0, 1.0);
// R'lyeh Ftagnh !
float smooth = smoothstep(borderDistance, borderDistance * 1.2, ddist);
float inSmooth = min(smooth, 1.0 - smooth) * 2.0;
// Creating the spooky membrane around the bright area
vec3 membraneColor = vec3(1.0 - inSmooth);
finalColor *= (mix(color, color * 0.33, darknessLevel) * colorationAlpha);
finalColor = mix(finalColor,
vec3(0.0),
1.0 - smoothstep(0.25, 0.30 + (intensity * 0.2), ddist));
finalColor *= membraneColor;
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,74 @@
/**
* Siren light animation coloration shader
*/
class SirenColorationShader extends AdaptiveColorationShader {
static fragmentShader = `
${this.SHADER_HEADER}
uniform float gradientFade;
uniform float beamLength;
${this.PERCEIVED_BRIGHTNESS}
${this.PIE}
${this.ROTATION}
void main() {
${this.FRAGMENT_BEGIN}
vec2 ncoord = vUvs * 2.0 - 1.0;
float angularIntensity = mix(PI, 0.0, intensity * 0.1);
ncoord *= rot(time * 50.0 + angle);
float angularCorrection = pie(ncoord, angularIntensity, clamp(gradientFade * dist, 0.05, 1.0), beamLength);
finalColor = color * brightnessPulse * colorationAlpha * angularCorrection;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}
`;
/** @inheritdoc */
static defaultUniforms = ({
...super.defaultUniforms,
ratio: 0,
brightnessPulse: 1,
angle: 0,
gradientFade: 0.15,
beamLength: 1
});
}
/* -------------------------------------------- */
/**
* Siren light animation illumination shader
*/
class SirenIlluminationShader extends AdaptiveIlluminationShader {
static fragmentShader = `
${this.SHADER_HEADER}
uniform float gradientFade;
uniform float beamLength;
${this.PERCEIVED_BRIGHTNESS}
${this.PIE}
${this.ROTATION}
void main() {
${this.FRAGMENT_BEGIN}
${this.TRANSITION}
vec2 ncoord = vUvs * 2.0 - 1.0;
float angularIntensity = mix(PI, 0.0, intensity * 0.1);
ncoord *= rot(time * 50.0 + angle);
float angularCorrection = mix(1.0, pie(ncoord, angularIntensity, clamp(gradientFade * dist, 0.05, 1.0), beamLength), 0.5);
finalColor *= angularCorrection;
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
/** @inheritdoc */
static defaultUniforms = ({
...super.defaultUniforms,
angle: 0,
gradientFade: 0.45,
beamLength: 1
});
}

View File

@@ -0,0 +1,91 @@
/**
* A patch of smoke
*/
class SmokePatchColorationShader extends AdaptiveColorationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
${this.PRNG}
${this.NOISE}
${this.FBMHQ(3)}
vec2 transform(in vec2 uv, in float dist) {
float t = time * 0.1;
float cost = cos(t);
float sint = sin(t);
mat2 rotmat = mat2(cost, -sint, sint, cost);
mat2 scalemat = mat2(10.0, uv.x, uv.y, 10.0);
uv -= PIVOT;
uv *= (rotmat * scalemat);
uv += PIVOT;
return uv;
}
float smokefading(in float dist) {
float t = time * 0.4;
vec2 uv = transform(vUvs, dist);
return pow(1.0 - dist,
mix(fbm(uv, 1.0 + intensity * 0.4),
max(fbm(uv + t, 1.0),
fbm(uv - t, 1.0)),
pow(dist, intensity * 0.5)));
}
void main() {
${this.FRAGMENT_BEGIN}
finalColor = color * smokefading(dist) * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}
`;
}
/* -------------------------------------------- */
/**
* A patch of smoke
*/
class SmokePatchIlluminationShader extends AdaptiveIlluminationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
${this.PRNG}
${this.NOISE}
${this.FBMHQ(3)}
vec2 transform(in vec2 uv, in float dist) {
float t = time * 0.1;
float cost = cos(t);
float sint = sin(t);
mat2 rotmat = mat2(cost, -sint, sint, cost);
mat2 scalemat = mat2(10.0, uv.x, uv.y, 10.0);
uv -= PIVOT;
uv *= (rotmat * scalemat);
uv += PIVOT;
return uv;
}
float smokefading(in float dist) {
float t = time * 0.4;
vec2 uv = transform(vUvs, dist);
return pow(1.0 - dist,
mix(fbm(uv, 1.0 + intensity * 0.4),
max(fbm(uv + t, 1.0),
fbm(uv - t, 1.0)),
pow(dist, intensity * 0.5)));
}
void main() {
${this.FRAGMENT_BEGIN}
${this.TRANSITION}
finalColor *= smokefading(dist);
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}
`;
}

View File

@@ -0,0 +1,49 @@
/**
* A disco like star light.
*/
class StarLightColorationShader extends AdaptiveColorationShader {
/** @override */
static forceDefaultColor = true;
/** @override */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
${this.PRNG}
${this.NOISE}
${this.FBM(2, 1.0)}
vec2 transform(in vec2 uv, in float dist) {
float t = time * 0.20;
float cost = cos(t);
float sint = sin(t);
mat2 rotmat = mat2(cost, -sint, sint, cost);
uv *= rotmat;
return uv;
}
float makerays(in vec2 uv, in float t) {
vec2 uvn = normalize(uv * (uv + t)) * (5.0 + intensity);
return max(clamp(0.5 * tan(fbm(uvn - t)), 0.0, 2.25),
clamp(3.0 - tan(fbm(uvn + t * 2.0)), 0.0, 2.25));
}
float starlight(in float dist) {
vec2 uv = (vUvs - 0.5);
uv = transform(uv, dist);
float rays = makerays(uv, time * 0.5);
return pow(1.0 - dist, rays) * pow(1.0 - dist, 0.25);
}
void main() {
${this.FRAGMENT_BEGIN}
finalColor = clamp(color * starlight(dist) * colorationAlpha, 0.0, 1.0);
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}
`;
}

View File

@@ -0,0 +1,87 @@
/**
* Sunburst animation illumination shader
*/
class SunburstIlluminationShader extends AdaptiveIlluminationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
// Smooth back and forth between a and b
float cosTime(in float a, in float b) {
return (a - b) * ((cos(time) + 1.0) * 0.5) + b;
}
// Create the sunburst effect
vec3 sunBurst(in vec3 color, in vec2 uv, in float dist) {
// Pulse calibration
float intensityMod = 1.0 + (intensity * 0.05);
float lpulse = cosTime(1.3 * intensityMod, 0.85 * intensityMod);
// Compute angle
float angle = atan(uv.x, uv.y) * INVTWOPI;
// Creating the beams and the inner light
float beam = fract(angle * 16.0 + time);
float light = lpulse * pow(abs(1.0 - dist), 0.65);
// Max agregation of the central light and the two gradient edges
float sunburst = max(light, max(beam, 1.0 - beam));
// Creating the effect : applying color and color correction. ultra saturate the entire output color.
return color * pow(sunburst, 3.0);
}
void main() {
${this.FRAGMENT_BEGIN}
vec2 uv = (2.0 * vUvs) - 1.0;
finalColor = switchColor(computedBrightColor, computedDimColor, dist);
${this.ADJUSTMENTS}
finalColor = sunBurst(finalColor, uv, dist);
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}
/**
* Sunburst animation coloration shader
*/
class SunburstColorationShader extends AdaptiveColorationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
// Smooth back and forth between a and b
float cosTime(in float a, in float b) {
return (a - b) * ((cos(time) + 1.0) * 0.5) + b;
}
// Create a sun burst effect
vec3 sunBurst(in vec2 uv, in float dist) {
// pulse calibration
float intensityMod = 1.0 + (intensity * 0.05);
float lpulse = cosTime(1.1 * intensityMod, 0.85 * intensityMod);
// compute angle
float angle = atan(uv.x, uv.y) * INVTWOPI;
// creating the beams and the inner light
float beam = fract(angle * 16.0 + time);
float light = lpulse * pow(abs(1.0 - dist), 0.65);
// agregation of the central light and the two gradient edges to create the sunburst
float sunburst = max(light, max(beam, 1.0 - beam));
// creating the effect : applying color and color correction. saturate the entire output color.
return color * pow(sunburst, 3.0);
}
void main() {
${this.FRAGMENT_BEGIN}
vec2 uvs = (2.0 * vUvs) - 1.0;
finalColor = sunBurst(uvs, dist) * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,29 @@
/**
* Swirling rainbow animation coloration shader
*/
class SwirlingRainbowColorationShader extends AdaptiveColorationShader {
/** @override */
static forceDefaultColor = true;
/** @override */
static fragmentShader = `
${this.SHADER_HEADER}
${this.HSB2RGB}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
float intens = intensity * 0.1;
vec2 nuv = vUvs * 2.0 - 1.0;
vec2 puv = vec2(atan(nuv.x, nuv.y) * INVTWOPI + 0.5, length(nuv));
vec3 rainbow = hsb2rgb(vec3(puv.x + puv.y - time * 0.2, 1.0, 1.0));
finalColor = mix(color, rainbow, smoothstep(0.0, 1.5 - intens, dist))
* (1.0 - dist * dist * dist);
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,40 @@
/**
* Allow coloring of illumination
*/
class TorchIlluminationShader extends AdaptiveIlluminationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
${this.TRANSITION}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}
/* -------------------------------------------- */
/**
* Torch animation coloration shader
*/
class TorchColorationShader extends AdaptiveColorationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
finalColor = color * brightnessPulse * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}
`;
/** @inheritdoc */
static defaultUniforms = ({...super.defaultUniforms, ratio: 0, brightnessPulse: 1});
}

View File

@@ -0,0 +1,154 @@
/**
* Vortex animation coloration shader
*/
class VortexColorationShader extends AdaptiveColorationShader {
/** @override */
static forceDefaultColor = true;
/** @override */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PRNG}
${this.NOISE}
${this.FBM(4, 1.0)}
${this.PERCEIVED_BRIGHTNESS}
vec2 vortex(in vec2 uv, in float dist, in float radius, in mat2 rotmat) {
float intens = intensity * 0.2;
vec2 uvs = uv - PIVOT;
uv *= rotmat;
if ( dist < radius ) {
float sigma = (radius - dist) / radius;
float theta = sigma * sigma * TWOPI * intens;
float st = sin(theta);
float ct = cos(theta);
uvs = vec2(dot(uvs, vec2(ct, -st)), dot(uvs, vec2(st, ct)));
}
uvs += PIVOT;
return uvs;
}
vec3 spice(in vec2 iuv, in mat2 rotmat) {
// constructing the palette
vec3 c1 = color * 0.55;
vec3 c2 = color * 0.95;
vec3 c3 = color * 0.45;
vec3 c4 = color * 0.75;
vec3 c5 = vec3(0.20);
vec3 c6 = color * 1.2;
// creating the deformation
vec2 uv = iuv;
uv -= PIVOT;
uv *= rotmat;
vec2 p = uv.xy * 6.0;
uv += PIVOT;
// time motion fbm and palette mixing
float q = fbm(p + time);
vec2 r = vec2(fbm(p + q + time * 0.9 - p.x - p.y),
fbm(p + q + time * 0.6));
vec3 c = mix(c1,
c2,
fbm(p + r)) + mix(c3, c4, r.x)
- mix(c5, c6, r.y);
// returning the color
return c;
}
void main() {
${this.FRAGMENT_BEGIN}
// Timed values
float t = time * 0.5;
float cost = cos(t);
float sint = sin(t);
// Rotation matrix
mat2 vortexRotMat = mat2(cost, -sint, sint, cost);
mat2 spiceRotMat = mat2(cost * 2.0, -sint * 2.0, sint * 2.0, cost * 2.0);
// Creating vortex
vec2 vuv = vortex(vUvs, dist, 1.0, vortexRotMat);
// Applying spice
finalColor = spice(vuv, spiceRotMat) * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}
/* -------------------------------------------- */
/**
* Vortex animation coloration shader
*/
class VortexIlluminationShader extends AdaptiveIlluminationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PRNG}
${this.NOISE}
${this.FBM(4, 1.0)}
${this.PERCEIVED_BRIGHTNESS}
vec2 vortex(in vec2 uv, in float dist, in float radius, in float angle, in mat2 rotmat) {
vec2 uvs = uv - PIVOT;
uv *= rotmat;
if ( dist < radius ) {
float sigma = (radius - dist) / radius;
float theta = sigma * sigma * angle;
float st = sin(theta);
float ct = cos(theta);
uvs = vec2(dot(uvs, vec2(ct, -st)), dot(uvs, vec2(st, ct)));
}
uvs += PIVOT;
return uvs;
}
vec3 spice(in vec2 iuv, in mat2 rotmat) {
// constructing the palette
vec3 c1 = vec3(0.20);
vec3 c2 = vec3(0.80);
vec3 c3 = vec3(0.15);
vec3 c4 = vec3(0.85);
vec3 c5 = c3;
vec3 c6 = vec3(0.9);
// creating the deformation
vec2 uv = iuv;
uv -= PIVOT;
uv *= rotmat;
vec2 p = uv.xy * 6.0;
uv += PIVOT;
// time motion fbm and palette mixing
float q = fbm(p + time);
vec2 r = vec2(fbm(p + q + time * 0.9 - p.x - p.y), fbm(p + q + time * 0.6));
// Mix the final color
return mix(c1, c2, fbm(p + r)) + mix(c3, c4, r.x) - mix(c5, c6, r.y);
}
vec3 convertToDarknessColors(in vec3 col, in float dist) {
float intens = intensity * 0.20;
float lum = (col.r * 2.0 + col.g * 3.0 + col.b) * 0.5 * INVTHREE;
float colorMod = smoothstep(ratio * 0.99, ratio * 1.01, dist);
return mix(computedDimColor, computedBrightColor * colorMod, 1.0 - smoothstep( 0.80, 1.00, lum)) *
smoothstep( 0.25 * intens, 0.85 * intens, lum);
}
void main() {
${this.FRAGMENT_BEGIN}
${this.TRANSITION}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,47 @@
/**
* Wave animation illumination shader
*/
class WaveIlluminationShader extends AdaptiveIlluminationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
float wave(in float dist) {
float sinWave = 0.5 * (sin(-time * 6.0 + dist * 10.0 * intensity) + 1.0);
return 0.3 * sinWave + 0.8;
}
void main() {
${this.FRAGMENT_BEGIN}
${this.TRANSITION}
finalColor *= wave(dist);
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}
/* -------------------------------------------- */
/**
* Wave animation coloration shader
*/
class WaveColorationShader extends AdaptiveColorationShader {
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
float wave(in float dist) {
float sinWave = 0.5 * (sin(-time * 6.0 + dist * 10.0 * intensity) + 1.0);
return 0.55 * sinWave + 0.8;
}
void main() {
${this.FRAGMENT_BEGIN}
finalColor = color * wave(dist) * colorationAlpha;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
}

View File

@@ -0,0 +1,129 @@
/**
* The default coloration shader used by standard rendering and animations.
* A fragment shader which creates a solid light source.
*/
class AdaptiveIlluminationShader extends AdaptiveLightingShader {
/** @inheritdoc */
static FRAGMENT_BEGIN = `
${super.FRAGMENT_BEGIN}
vec3 framebufferColor = min(texture2D(framebufferTexture, vSamplerUvs).rgb, computedBackgroundColor);
`;
/** @override */
static FRAGMENT_END = `
gl_FragColor = vec4(mix(framebufferColor, finalColor, depth), 1.0);
`;
/**
* The adjustments made into fragment shaders
* @type {string}
*/
static get ADJUSTMENTS() {
return `
vec3 changedColor = finalColor;\n
${this.SATURATION}
${this.EXPOSURE}
${this.SHADOW}
finalColor = changedColor;\n`;
}
static EXPOSURE = `
// Computing exposure with illumination
if ( exposure > 0.0 ) {
// Diminishing exposure for illumination by a factor 2 (to reduce the "inflating radius" visual problem)
float quartExposure = exposure * 0.25;
float attenuationStrength = attenuation * 0.25;
float lowerEdge = 0.98 - attenuationStrength;
float upperEdge = 1.02 + attenuationStrength;
float finalExposure = quartExposure *
(1.0 - smoothstep(ratio * lowerEdge, clamp(ratio * upperEdge, 0.0001, 1.0), dist)) +
quartExposure;
changedColor *= (1.0 + finalExposure);
}
else if ( exposure != 0.0 ) changedColor *= (1.0 + exposure);
`;
/**
* Memory allocations for the Adaptive Illumination Shader
* @type {string}
*/
static SHADER_HEADER = `
${this.FRAGMENT_UNIFORMS}
${this.VERTEX_FRAGMENT_VARYINGS}
${this.FRAGMENT_FUNCTIONS}
${this.CONSTANTS}
${this.SWITCH_COLOR}
`;
/** @inheritdoc */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
${this.TRANSITION}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
/** @inheritdoc */
static defaultUniforms = {
technique: 1,
shadows: 0,
saturation: 0,
intensity: 5,
attenuation: 0.5,
contrast: 0,
exposure: 0,
ratio: 0.5,
darknessLevel: 0,
color: [1, 1, 1],
colorBackground: [1, 1, 1],
colorDim: [1, 1, 1],
colorBright: [1, 1, 1],
screenDimensions: [1, 1],
time: 0,
useSampler: false,
primaryTexture: null,
framebufferTexture: null,
depthTexture: null,
darknessLevelTexture: null,
depthElevation: 1,
ambientBrightest: [1, 1, 1],
ambientDarkness: [0, 0, 0],
ambientDaylight: [1, 1, 1],
weights: [0, 0, 0, 0],
dimLevelCorrection: 1,
brightLevelCorrection: 2,
computeIllumination: false,
globalLight: false,
globalLightThresholds: [0, 0]
};
static {
const initial = foundry.data.LightData.cleanData();
this.defaultUniforms.technique = initial.coloration;
this.defaultUniforms.contrast = initial.contrast;
this.defaultUniforms.shadows = initial.shadows;
this.defaultUniforms.saturation = initial.saturation;
this.defaultUniforms.intensity = initial.animation.intensity;
this.defaultUniforms.attenuation = initial.attenuation;
}
/**
* Flag whether the illumination shader is currently required.
* @type {boolean}
*/
get isRequired() {
const vs = canvas.visibility.lightingVisibility;
// Checking if disabled
if ( vs.illumination === VisionMode.LIGHTING_VISIBILITY.DISABLED ) return false;
// For the moment, we return everytimes true if we are here
return true;
}
}

View File

@@ -0,0 +1,130 @@
/**
* Abstract shader used for Adjust Darkness Level region behavior.
* @abstract
* @internal
* @ignore
*/
class AbstractDarknessLevelRegionShader extends RegionShader {
/** @inheritDoc */
static defaultUniforms = {
...super.defaultUniforms,
bottom: 0,
top: 0,
depthTexture: null
};
/* ---------------------------------------- */
/**
* The darkness level adjustment mode.
* @type {number}
*/
mode = foundry.data.regionBehaviors.AdjustDarknessLevelRegionBehaviorType.MODES.OVERRIDE;
/* ---------------------------------------- */
/**
* The darkness level modifier.
* @type {number}
*/
modifier = 0;
/* ---------------------------------------- */
/**
* Current darkness level of this mesh.
* @type {number}
*/
get darknessLevel() {
const M = foundry.data.regionBehaviors.AdjustDarknessLevelRegionBehaviorType.MODES;
switch ( this.mode ) {
case M.OVERRIDE: return this.modifier;
case M.BRIGHTEN: return canvas.environment.darknessLevel * (1 - this.modifier);
case M.DARKEN: return 1 - ((1 - canvas.environment.darknessLevel) * (1 - this.modifier));
default: throw new Error("Invalid mode");
}
}
/* ---------------------------------------- */
/** @inheritDoc */
_preRender(mesh, renderer) {
super._preRender(mesh, renderer);
const u = this.uniforms;
u.bottom = canvas.masks.depth.mapElevation(mesh.region.bottom);
u.top = canvas.masks.depth.mapElevation(mesh.region.top);
if ( !u.depthTexture ) u.depthTexture = canvas.masks.depth.renderTexture;
}
}
/* ---------------------------------------- */
/**
* Render the RegionMesh with darkness level adjustments.
* @internal
* @ignore
*/
class AdjustDarknessLevelRegionShader extends AbstractDarknessLevelRegionShader {
/** @override */
static fragmentShader = `
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
uniform sampler2D depthTexture;
uniform float darknessLevel;
uniform float top;
uniform float bottom;
uniform vec4 tintAlpha;
varying vec2 vScreenCoord;
void main() {
vec2 depthColor = texture2D(depthTexture, vScreenCoord).rg;
float depth = step(depthColor.g, top) * step(bottom, (254.5 / 255.0) - depthColor.r);
gl_FragColor = vec4(darknessLevel, 0.0, 0.0, 1.0) * tintAlpha * depth;
}
`;
/* ---------------------------------------- */
/** @inheritDoc */
static defaultUniforms = {
...super.defaultUniforms,
darknessLevel: 0
};
/* ---------------------------------------- */
/** @inheritDoc */
_preRender(mesh, renderer) {
super._preRender(mesh, renderer);
this.uniforms.darknessLevel = this.darknessLevel;
}
}
/* ---------------------------------------- */
/**
* Render the RegionMesh with darkness level adjustments.
* @internal
* @ignore
*/
class IlluminationDarknessLevelRegionShader extends AbstractDarknessLevelRegionShader {
/** @override */
static fragmentShader = `
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
uniform sampler2D depthTexture;
uniform float top;
uniform float bottom;
uniform vec4 tintAlpha;
varying vec2 vScreenCoord;
void main() {
vec2 depthColor = texture2D(depthTexture, vScreenCoord).rg;
float depth = step(depthColor.g, top) * step(bottom, (254.5 / 255.0) - depthColor.r);
gl_FragColor = vec4(1.0) * tintAlpha * depth;
}
`;
}

View File

@@ -0,0 +1,65 @@
/**
* The shader used by {@link RegionMesh}.
*/
class RegionShader extends AbstractBaseShader {
/** @override */
static vertexShader = `
precision ${PIXI.settings.PRECISION_VERTEX} float;
attribute vec2 aVertexPosition;
uniform mat3 translationMatrix;
uniform mat3 projectionMatrix;
uniform vec2 canvasDimensions;
uniform vec4 sceneDimensions;
uniform vec2 screenDimensions;
varying vec2 vCanvasCoord; // normalized canvas coordinates
varying vec2 vSceneCoord; // normalized scene coordinates
varying vec2 vScreenCoord; // normalized screen coordinates
void main() {
vec2 pixelCoord = aVertexPosition;
vCanvasCoord = pixelCoord / canvasDimensions;
vSceneCoord = (pixelCoord - sceneDimensions.xy) / sceneDimensions.zw;
vec3 tPos = translationMatrix * vec3(aVertexPosition, 1.0);
vScreenCoord = tPos.xy / screenDimensions;
gl_Position = vec4((projectionMatrix * tPos).xy, 0.0, 1.0);
}
`;
/** @override */
static fragmentShader = `
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
uniform vec4 tintAlpha;
void main() {
gl_FragColor = tintAlpha;
}
`;
/* ---------------------------------------- */
/** @override */
static defaultUniforms = {
canvasDimensions: [1, 1],
sceneDimensions: [0, 0, 1, 1],
screenDimensions: [1, 1],
tintAlpha: [1, 1, 1, 1]
};
/* ---------------------------------------- */
/** @override */
_preRender(mesh, renderer) {
const uniforms = this.uniforms;
uniforms.tintAlpha = mesh._cachedTint;
const dimensions = canvas.dimensions;
uniforms.canvasDimensions[0] = dimensions.width;
uniforms.canvasDimensions[1] = dimensions.height;
uniforms.sceneDimensions = dimensions.sceneRect;
uniforms.screenDimensions = canvas.screenDimensions;
}
}

View File

@@ -0,0 +1,84 @@
/**
* Shader for the Region highlight.
* @internal
* @ignore
*/
class HighlightRegionShader extends RegionShader {
/** @override */
static vertexShader = `\
precision ${PIXI.settings.PRECISION_VERTEX} float;
${this.CONSTANTS}
attribute vec2 aVertexPosition;
uniform mat3 translationMatrix;
uniform mat3 projectionMatrix;
uniform vec2 canvasDimensions;
uniform vec4 sceneDimensions;
uniform vec2 screenDimensions;
uniform mediump float hatchThickness;
varying vec2 vCanvasCoord; // normalized canvas coordinates
varying vec2 vSceneCoord; // normalized scene coordinates
varying vec2 vScreenCoord; // normalized screen coordinates
varying float vHatchOffset;
void main() {
vec2 pixelCoord = aVertexPosition;
vCanvasCoord = pixelCoord / canvasDimensions;
vSceneCoord = (pixelCoord - sceneDimensions.xy) / sceneDimensions.zw;
vec3 tPos = translationMatrix * vec3(aVertexPosition, 1.0);
vScreenCoord = tPos.xy / screenDimensions;
gl_Position = vec4((projectionMatrix * tPos).xy, 0.0, 1.0);
vHatchOffset = (pixelCoord.x + pixelCoord.y) / (SQRT2 * 2.0 * hatchThickness);
}
`;
/* ---------------------------------------- */
/** @override */
static fragmentShader = `\
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
varying float vHatchOffset;
uniform vec4 tintAlpha;
uniform float resolution;
uniform bool hatchEnabled;
uniform mediump float hatchThickness;
void main() {
gl_FragColor = tintAlpha;
if ( !hatchEnabled ) return;
float x = abs(vHatchOffset - floor(vHatchOffset + 0.5)) * 2.0;
float s = hatchThickness * resolution;
float y0 = clamp((x + 0.5) * s + 0.5, 0.0, 1.0);
float y1 = clamp((x - 0.5) * s + 0.5, 0.0, 1.0);
gl_FragColor *= mix(0.3333, 1.0, y0 - y1);
}
`;
/* ---------------------------------------- */
/** @inheritDoc */
static defaultUniforms = {
...super.defaultUniforms,
resolution: 1,
hatchEnabled: false,
hatchThickness: 1
};
/** @inheritDoc */
_preRender(mesh, renderer) {
super._preRender(mesh, renderer);
const uniforms = this.uniforms;
uniforms.resolution = (renderer.renderTexture.current ?? renderer).resolution * mesh.worldTransform.a;
const projection = renderer.projection.transform;
if ( projection ) {
const {a, b} = projection;
uniforms.resolution *= Math.sqrt((a * a) + (b * b));
}
}
}

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;
}
`;
}

View File

@@ -0,0 +1,91 @@
/**
* The default background shader used for vision sources
*/
class BackgroundVisionShader extends AdaptiveVisionShader {
/** @inheritdoc */
static FRAGMENT_END = `
finalColor *= colorTint;
if ( linkedToDarknessLevel ) finalColor = mix(baseColor.rgb, finalColor, computedDarknessLevel);
${super.FRAGMENT_END}
`;
/**
* Adjust the intensity according to the difference between the pixel darkness level and the scene darkness level.
* Used to see the difference of intensity when computing the background shader which is completeley overlapping
* The surface texture.
* @type {string}
*/
static ADJUST_INTENSITY = `
float darknessLevelDifference = clamp(computedDarknessLevel - darknessLevel, 0.0, 1.0);
finalColor = mix(finalColor, finalColor * 0.5, darknessLevelDifference);
`;
/** @inheritdoc */
static ADJUSTMENTS = `
${this.ADJUST_INTENSITY}
${super.ADJUSTMENTS}
`;
/**
* Memory allocations for the Adaptive Background Shader
* @type {string}
*/
static SHADER_HEADER = `
${this.FRAGMENT_UNIFORMS}
${this.VERTEX_FRAGMENT_VARYINGS}
${this.FRAGMENT_FUNCTIONS}
${this.CONSTANTS}`;
/** @inheritdoc */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
${this.ADJUSTMENTS}
${this.BACKGROUND_TECHNIQUES}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
/** @inheritdoc */
static defaultUniforms = {
technique: 0,
saturation: 0,
contrast: 0,
attenuation: 0.10,
exposure: 0,
darknessLevel: 0,
colorVision: [1, 1, 1],
colorTint: [1, 1, 1],
colorBackground: [1, 1, 1],
screenDimensions: [1, 1],
time: 0,
useSampler: true,
linkedToDarknessLevel: true,
primaryTexture: null,
depthTexture: null,
darknessLevelTexture: null,
depthElevation: 1,
ambientBrightest: [1, 1, 1],
ambientDarkness: [0, 0, 0],
ambientDaylight: [1, 1, 1],
weights: [0, 0, 0, 0],
dimLevelCorrection: 1,
brightLevelCorrection: 2,
globalLight: false,
globalLightThresholds: [0, 0]
};
/**
* Flag whether the background shader is currently required.
* If key uniforms are at their default values, we don't need to render the background container.
* @type {boolean}
*/
get isRequired() {
const keys = ["contrast", "saturation", "colorTint", "colorVision"];
return keys.some(k => this.uniforms[k] !== this.constructor.defaultUniforms[k]);
}
}

View File

@@ -0,0 +1,66 @@
/**
* This class defines an interface which all adaptive vision shaders extend.
*/
class AdaptiveVisionShader extends AdaptiveLightingShader {
/** @inheritDoc */
static FRAGMENT_FUNCTIONS = `
${super.FRAGMENT_FUNCTIONS}
vec3 computedVisionColor;
`;
/* -------------------------------------------- */
/** @override */
static EXPOSURE = `
// Computing exposed color for background
if ( exposure != 0.0 ) {
changedColor *= (1.0 + exposure);
}`;
/* -------------------------------------------- */
/** @inheritDoc */
static COMPUTE_ILLUMINATION = `
${super.COMPUTE_ILLUMINATION}
if ( computeIllumination ) computedVisionColor = mix(computedDimColor, computedBrightColor, brightness);
else computedVisionColor = colorVision;
`;
/* -------------------------------------------- */
// FIXME: need to redeclare fragment begin here to take into account COMPUTE_ILLUMINATION
// Do not work without this redeclaration.
/** @override */
static FRAGMENT_BEGIN = `
${this.COMPUTE_ILLUMINATION}
float dist = distance(vUvs, vec2(0.5)) * 2.0;
vec4 depthColor = texture2D(depthTexture, vSamplerUvs);
float depth = smoothstep(0.0, 1.0, vDepth) * step(depthColor.g, depthElevation) * step(depthElevation, (254.5 / 255.0) - depthColor.r);
vec4 baseColor = useSampler ? texture2D(primaryTexture, vSamplerUvs) : vec4(1.0);
vec3 finalColor = baseColor.rgb;
`;
/* -------------------------------------------- */
/** @override */
static SHADOW = "";
/* -------------------------------------------- */
/* Shader Techniques for vision */
/* -------------------------------------------- */
/**
* A mapping of available shader techniques
* @type {Record<string, ShaderTechnique>}
*/
static SHADER_TECHNIQUES = {
LEGACY: {
id: 0,
label: "LIGHT.AdaptiveLuminance",
coloration: `
float reflection = perceivedBrightness(baseColor);
finalColor *= reflection;`
}
};
}

View File

@@ -0,0 +1,71 @@
/**
* The default coloration shader used for vision sources.
*/
class ColorationVisionShader extends AdaptiveVisionShader {
/** @override */
static EXPOSURE = "";
/** @override */
static CONTRAST = "";
/**
* Memory allocations for the Adaptive Coloration Shader
* @type {string}
*/
static SHADER_HEADER = `
${this.FRAGMENT_UNIFORMS}
${this.VERTEX_FRAGMENT_VARYINGS}
${this.FRAGMENT_FUNCTIONS}
${this.CONSTANTS}
`;
/** @inheritdoc */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
finalColor = colorEffect;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
/** @inheritdoc */
static defaultUniforms = {
technique: 0,
saturation: 0,
attenuation: 0,
colorEffect: [0, 0, 0],
colorBackground: [0, 0, 0],
colorTint: [1, 1, 1],
time: 0,
screenDimensions: [1, 1],
useSampler: true,
primaryTexture: null,
linkedToDarknessLevel: true,
depthTexture: null,
depthElevation: 1,
ambientBrightest: [1, 1, 1],
ambientDarkness: [0, 0, 0],
ambientDaylight: [1, 1, 1],
weights: [0, 0, 0, 0],
dimLevelCorrection: 1,
brightLevelCorrection: 2,
globalLight: false,
globalLightThresholds: [0, 0]
};
/**
* Flag whether the coloration shader is currently required.
* If key uniforms are at their default values, we don't need to render the coloration container.
* @type {boolean}
*/
get isRequired() {
const keys = ["saturation", "colorEffect"];
return keys.some(k => this.uniforms[k] !== this.constructor.defaultUniforms[k]);
}
}

View File

@@ -0,0 +1,29 @@
/**
* Shader specialized in light amplification
*/
class AmplificationBackgroundVisionShader extends BackgroundVisionShader {
/** @inheritdoc */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
float lum = perceivedBrightness(baseColor.rgb);
vec3 vision = vec3(smoothstep(0.0, 1.0, lum * 1.5)) * colorTint;
finalColor = vision + (vision * (lum + brightness) * 0.1) + (baseColor.rgb * (1.0 - computedDarknessLevel) * 0.125);
${this.ADJUSTMENTS}
${this.BACKGROUND_TECHNIQUES}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
/** @inheritdoc */
static defaultUniforms = ({...super.defaultUniforms, colorTint: [0.38, 0.8, 0.38], brightness: 0.5});
/** @inheritdoc */
get isRequired() {
return true;
}
}

View File

@@ -0,0 +1,107 @@
/**
* Shader specialized in wave like senses (tremorsenses)
*/
class WaveBackgroundVisionShader extends BackgroundVisionShader {
/** @inheritdoc */
static fragmentShader = `
${this.SHADER_HEADER}
${this.WAVE()}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
// Normalize vUvs and compute base time
vec2 uvs = (2.0 * vUvs) - 1.0;
float t = time * -8.0;
// Rotate uvs
float sinX = sin(t * 0.02);
float cosX = cos(t * 0.02);
mat2 rotationMatrix = mat2( cosX, -sinX, sinX, cosX);
vec2 ruv = ((vUvs - 0.5) * rotationMatrix) + 0.5;
// Produce 4 arms smoothed to the edges
float angle = atan(ruv.x * 2.0 - 1.0, ruv.y * 2.0 - 1.0) * INVTWOPI;
float beam = fract(angle * 4.0);
beam = smoothstep(0.3, 1.0, max(beam, 1.0 - beam));
// Construct final color
vec3 grey = vec3(perceivedBrightness(baseColor.rgb));
finalColor = mix(baseColor.rgb, grey * 0.5, sqrt(beam)) * mix(vec3(1.0), colorTint, 0.3);
${this.ADJUSTMENTS}
${this.BACKGROUND_TECHNIQUES}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
/** @inheritdoc */
static defaultUniforms = ({...super.defaultUniforms, colorTint: [0.8, 0.1, 0.8]});
/** @inheritdoc */
get isRequired() {
return true;
}
}
/* -------------------------------------------- */
/**
* The wave vision shader, used to create waves emanations (ex: tremorsense)
*/
class WaveColorationVisionShader extends ColorationVisionShader {
/** @inheritdoc */
static fragmentShader = `
${this.SHADER_HEADER}
${this.WAVE()}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
// Normalize vUvs and compute base time
vec2 uvs = (2.0 * vUvs) - 1.0;
float t = time * -8.0;
// Rotate uvs
float sinX = sin(t * 0.02);
float cosX = cos(t * 0.02);
mat2 rotationMatrix = mat2( cosX, -sinX, sinX, cosX);
vec2 ruv = ((vUvs - 0.5) * rotationMatrix) + 0.5;
// Prepare distance from 4 corners
float dst[4];
dst[0] = distance(vec2(0.0), ruv);
dst[1] = distance(vec2(1.0), ruv);
dst[2] = distance(vec2(1.0,0.0), ruv);
dst[3] = distance(vec2(0.0,1.0), ruv);
// Produce 4 arms smoothed to the edges
float angle = atan(ruv.x * 2.0 - 1.0, ruv.y * 2.0 - 1.0) * INVTWOPI;
float beam = fract(angle * 4.0);
beam = smoothstep(0.3, 1.0, max(beam, 1.0 - beam));
// Computing the 4 corner waves
float multiWaves = 0.0;
for ( int i = 0; i <= 3 ; i++) {
multiWaves += smoothstep(0.6, 1.0, max(multiWaves, wcos(-10.0, 1.30 - dst[i], dst[i] * 120.0, t)));
}
// Computing the central wave
multiWaves += smoothstep(0.6, 1.0, max(multiWaves, wcos(-10.0, 1.35 - dist, dist * 120.0, -t)));
// Construct final color
finalColor = vec3(mix(multiWaves, 0.0, sqrt(beam))) * colorEffect;
${this.COLORATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
/** @inheritdoc */
static defaultUniforms = ({...super.defaultUniforms, colorEffect: [0.8, 0.1, 0.8]});
/** @inheritdoc */
get isRequired() {
return true;
}
}

View File

@@ -0,0 +1,89 @@
/**
* The default illumination shader used for vision sources
*/
class IlluminationVisionShader extends AdaptiveVisionShader {
/** @inheritdoc */
static FRAGMENT_BEGIN = `
${super.FRAGMENT_BEGIN}
vec3 framebufferColor = min(texture2D(framebufferTexture, vSamplerUvs).rgb, computedBackgroundColor);
`;
/** @override */
static FRAGMENT_END = `
gl_FragColor = vec4(mix(framebufferColor, finalColor, depth), 1.0);
`;
/**
* Transition between bright and dim colors, if requested
* @type {string}
*/
static VISION_COLOR = `
finalColor = computedVisionColor;
`;
/**
* The adjustments made into fragment shaders
* @type {string}
*/
static get ADJUSTMENTS() {
return `
vec3 changedColor = finalColor;\n
${this.SATURATION}
finalColor = changedColor;\n`;
}
/**
* Memory allocations for the Adaptive Illumination Shader
* @type {string}
*/
static SHADER_HEADER = `
${this.FRAGMENT_UNIFORMS}
${this.VERTEX_FRAGMENT_VARYINGS}
${this.FRAGMENT_FUNCTIONS}
${this.CONSTANTS}
`;
/** @inheritdoc */
static fragmentShader = `
${this.SHADER_HEADER}
${this.PERCEIVED_BRIGHTNESS}
void main() {
${this.FRAGMENT_BEGIN}
${this.VISION_COLOR}
${this.ILLUMINATION_TECHNIQUES}
${this.ADJUSTMENTS}
${this.FALLOFF}
${this.FRAGMENT_END}
}`;
/** @inheritdoc */
static defaultUniforms = {
technique: foundry.data.LightData.cleanData().initial,
attenuation: 0,
exposure: 0,
saturation: 0,
darknessLevel: 0,
colorVision: [1, 1, 1],
colorTint: [1, 1, 1],
colorBackground: [1, 1, 1],
screenDimensions: [1, 1],
time: 0,
useSampler: false,
linkedToDarknessLevel: true,
primaryTexture: null,
framebufferTexture: null,
depthTexture: null,
darknessLevelTexture: null,
depthElevation: 1,
ambientBrightest: [1, 1, 1],
ambientDarkness: [0, 0, 0],
ambientDaylight: [1, 1, 1],
weights: [0, 0, 0, 0],
dimLevelCorrection: 1,
brightLevelCorrection: 2,
globalLight: false,
globalLightThresholds: [0, 0]
};
}

View File

@@ -0,0 +1,208 @@
/**
* The base shader class for weather shaders.
*/
class AbstractWeatherShader extends AbstractBaseShader {
constructor(...args) {
super(...args);
Object.defineProperties(this, Object.keys(this.constructor.defaultUniforms).reduce((obj, k) => {
obj[k] = {
get() {
return this.uniforms[k];
},
set(value) {
this.uniforms[k] = value;
},
enumerable: false
};
return obj;
}, {}));
}
/**
* Compute the weather masking value.
* @type {string}
*/
static COMPUTE_MASK = `
// Base mask value
float mask = 1.0;
// Process the occlusion mask
if ( useOcclusion ) {
float oMask = step(depthElevation, (254.5 / 255.0) - dot(occlusionWeights, texture2D(occlusionTexture, vUvsOcclusion)));
if ( reverseOcclusion ) oMask = 1.0 - oMask;
mask *= oMask;
}
// Process the terrain mask
if ( useTerrain ) {
float tMask = dot(terrainWeights, texture2D(terrainTexture, vUvsTerrain));
if ( reverseTerrain ) tMask = 1.0 - tMask;
mask *= tMask;
}
`;
/**
* Compute the weather masking value.
* @type {string}
*/
static FRAGMENT_HEADER = `
precision ${PIXI.settings.PRECISION_FRAGMENT} float;
// Occlusion mask uniforms
uniform bool useOcclusion;
uniform sampler2D occlusionTexture;
uniform bool reverseOcclusion;
uniform vec4 occlusionWeights;
// Terrain mask uniforms
uniform bool useTerrain;
uniform sampler2D terrainTexture;
uniform bool reverseTerrain;
uniform vec4 terrainWeights;
// Other uniforms and varyings
uniform vec3 tint;
uniform float time;
uniform float depthElevation;
uniform float alpha;
varying vec2 vUvsOcclusion;
varying vec2 vUvsTerrain;
varying vec2 vStaticUvs;
varying vec2 vUvs;
`;
/**
* Common uniforms for all weather shaders.
* @type {{
* useOcclusion: boolean,
* occlusionTexture: PIXI.Texture|null,
* reverseOcclusion: boolean,
* occlusionWeights: number[],
* useTerrain: boolean,
* terrainTexture: PIXI.Texture|null,
* reverseTerrain: boolean,
* terrainWeights: number[],
* alpha: number,
* tint: number[],
* screenDimensions: [number, number],
* effectDimensions: [number, number],
* depthElevation: number,
* time: number
* }}
*/
static commonUniforms = {
terrainUvMatrix: new PIXI.Matrix(),
useOcclusion: false,
occlusionTexture: null,
reverseOcclusion: false,
occlusionWeights: [0, 0, 1, 0],
useTerrain: false,
terrainTexture: null,
reverseTerrain: false,
terrainWeights: [1, 0, 0, 0],
alpha: 1,
tint: [1, 1, 1],
screenDimensions: [1, 1],
effectDimensions: [1, 1],
depthElevation: 1,
time: 0
};
/**
* Default uniforms for a specific class
* @abstract
*/
static defaultUniforms;
/* -------------------------------------------- */
/** @override */
static create(initialUniforms) {
const program = this.createProgram();
const uniforms = {...this.commonUniforms, ...this.defaultUniforms, ...initialUniforms};
return new this(program, uniforms);
}
/* -------------------------------------------- */
/**
* Create the shader program.
* @returns {PIXI.Program}
*/
static createProgram() {
return PIXI.Program.from(this.vertexShader, this.fragmentShader);
}
/* -------------------------------------------- */
/** @inheritdoc */
static vertexShader = `
precision ${PIXI.settings.PRECISION_VERTEX} float;
attribute vec2 aVertexPosition;
uniform mat3 translationMatrix;
uniform mat3 projectionMatrix;
uniform mat3 terrainUvMatrix;
uniform vec2 screenDimensions;
uniform vec2 effectDimensions;
varying vec2 vUvsOcclusion;
varying vec2 vUvsTerrain;
varying vec2 vUvs;
varying vec2 vStaticUvs;
void main() {
vec3 tPos = translationMatrix * vec3(aVertexPosition, 1.0);
vStaticUvs = aVertexPosition;
vUvs = vStaticUvs * effectDimensions;
vUvsOcclusion = tPos.xy / screenDimensions;
vUvsTerrain = (terrainUvMatrix * vec3(aVertexPosition, 1.0)).xy;
gl_Position = vec4((projectionMatrix * tPos).xy, 0.0, 1.0);
}
`;
/* -------------------------------------------- */
/* Common Management and Parameters */
/* -------------------------------------------- */
/**
* Update the scale of this effect with new values
* @param {number|{x: number, y: number}} scale The desired scale
*/
set scale(scale) {
this.#scale.x = typeof scale === "object" ? scale.x : scale;
this.#scale.y = (typeof scale === "object" ? scale.y : scale) ?? this.#scale.x;
}
set scaleX(x) {
this.#scale.x = x ?? 1;
}
set scaleY(y) {
this.#scale.y = y ?? 1;
}
#scale = {
x: 1,
y: 1
};
/* -------------------------------------------- */
/**
* The speed multiplier applied to animation.
* 0 stops animation.
* @type {number}
*/
speed = 1;
/* -------------------------------------------- */
/** @override */
_preRender(mesh, renderer) {
this.uniforms.alpha = mesh.worldAlpha;
this.uniforms.depthElevation = canvas.masks.depth.mapElevation(canvas.weather.elevation);
this.uniforms.time += (canvas.app.ticker.deltaMS / 1000 * this.speed);
this.uniforms.screenDimensions = canvas.screenDimensions;
this.uniforms.effectDimensions[0] = this.#scale.x * mesh.scale.x / 10000;
this.uniforms.effectDimensions[1] = this.#scale.y * mesh.scale.y / 10000;
}
}

View File

@@ -0,0 +1,58 @@
/**
* An interface for defining shader-based weather effects
* @param {object} config The config object to create the shader effect
*/
class WeatherShaderEffect extends QuadMesh {
constructor(config, shaderClass) {
super(shaderClass);
this.stop();
this._initialize(config);
}
/* -------------------------------------------- */
/**
* Set shader parameters.
* @param {object} [config={}]
*/
configure(config={}) {
for ( const [k, v] of Object.entries(config) ) {
if ( k in this.shader ) this.shader[k] = v;
else if ( k in this.shader.uniforms ) this.shader.uniforms[k] = v;
}
}
/* -------------------------------------------- */
/**
* Begin animation
*/
play() {
this.visible = true;
}
/* -------------------------------------------- */
/**
* Stop animation
*/
stop() {
this.visible = false;
}
/* -------------------------------------------- */
/**
* Initialize the weather effect.
* @param {object} config Config object.
* @protected
*/
_initialize(config) {
this.configure(config);
const sr = canvas.dimensions.sceneRect;
this.position.set(sr.x, sr.y);
this.width = sr.width;
this.height = sr.height;
}
}

View File

@@ -0,0 +1,129 @@
/**
* Fog shader effect.
*/
class FogShader extends AbstractWeatherShader {
/** @inheritdoc */
static defaultUniforms = {
intensity: 1,
rotation: 0,
slope: 0.25
};
/* ---------------------------------------- */
/**
* Configure the number of octaves into the shaders.
* @param {number} mode
* @returns {string}
*/
static OCTAVES(mode) {
return `${mode + 2}`;
}
/* -------------------------------------------- */
/**
* Configure the fog complexity according to mode (performance).
* @param {number} mode
* @returns {string}
*/
static FOG(mode) {
if ( mode === 0 ) {
return `vec2 mv = vec2(fbm(uv * 4.5 + time * 0.115)) * (1.0 + r * 0.25);
mist += fbm(uv * 4.5 + mv - time * 0.0275) * (1.0 + r * 0.25);`;
}
return `for ( int i=0; i<2; i++ ) {
vec2 mv = vec2(fbm(uv * 4.5 + time * 0.115 + vec2(float(i) * 250.0))) * (0.50 + r * 0.25);
mist += fbm(uv * 4.5 + mv - time * 0.0275) * (0.50 + r * 0.25);
}`;
}
/* -------------------------------------------- */
/** @override */
static createProgram() {
const mode = canvas?.performance.mode ?? 2;
return PIXI.Program.from(this.vertexShader, this.fragmentShader(mode));
}
/* -------------------------------------------- */
/** @inheritdoc */
static fragmentShader(mode) {
return `
${this.FRAGMENT_HEADER}
uniform float intensity;
uniform float slope;
uniform float rotation;
${this.CONSTANTS}
${this.PERCEIVED_BRIGHTNESS}
${this.PRNG}
${this.ROTATION}
// ********************************************************* //
float fnoise(in vec2 coords) {
vec2 i = floor(coords);
vec2 f = fract(coords);
float a = random(i);
float b = random(i + vec2(1.0, 0.0));
float c = random(i + vec2(0.0, 1.0));
float d = random(i + vec2(1.0, 1.0));
vec2 cb = f * f * (3.0 - 2.0 * f);
return mix(a, b, cb.x) + (c - a) * cb.y * (1.0 - cb.x) + (d - b) * cb.x * cb.y;
}
// ********************************************************* //
float fbm(in vec2 uv) {
float r = 0.0;
float scale = 1.0;
uv += time * 0.03;
uv *= 2.0;
for (int i = 0; i < ${this.OCTAVES(mode)}; i++) {
r += fnoise(uv + time * 0.03) * scale;
uv *= 3.0;
scale *= 0.3;
}
return r;
}
// ********************************************************* //
vec3 mist(in vec2 uv, in float r) {
float mist = 0.0;
${this.FOG(mode)}
return vec3(0.9, 0.85, 1.0) * mist;
}
// ********************************************************* //
void main() {
${this.COMPUTE_MASK}
vec2 ruv;
if ( rotation != 0.0 ) {
ruv = vUvs - 0.5;
ruv *= rot(rotation);
ruv += 0.5;
}
else {
ruv = vUvs;
}
vec3 col = mist(ruv * 2.0 - 1.0, 0.0) * 1.33;
float pb = perceivedBrightness(col);
pb = smoothstep(slope * 0.5, slope + 0.001, pb);
gl_FragColor = vec4( mix(vec3(0.05, 0.05, 0.08), col * clamp(slope, 1.0, 2.0), pb), 1.0)
* vec4(tint, 1.0) * intensity * mask * alpha;
}
`;
}
}

View File

@@ -0,0 +1,48 @@
/**
* Rain shader effect.
*/
class RainShader extends AbstractWeatherShader {
/** @inheritdoc */
static defaultUniforms = {
opacity: 1,
intensity: 1,
strength: 1,
rotation: 0.5,
resolution: [3200, 80] // The resolution to have nice rain ropes with the voronoi cells
};
/* -------------------------------------------- */
/** @inheritdoc */
static fragmentShader = `
${this.FRAGMENT_HEADER}
${this.CONSTANTS}
${this.PERCEIVED_BRIGHTNESS}
${this.ROTATION}
${this.PRNG2D}
${this.VORONOI}
uniform float intensity;
uniform float opacity;
uniform float strength;
uniform float rotation;
uniform vec2 resolution;
// Compute rain according to uv and dimensions for layering
float computeRain(in vec2 uv, in float t) {
vec2 tuv = uv;
vec2 ruv = ((tuv + 0.5) * rot(rotation)) - 0.5;
ruv.y -= t * 0.8;
vec2 st = ruv * resolution;
vec3 d2 = voronoi(vec3(st - t * 0.5, t * 0.8), 10.0);
float df = perceivedBrightness(d2);
return (1.0 - smoothstep(-df * strength, df * strength + 0.001, 1.0 - smoothstep(0.3, 1.0, d2.z))) * intensity;
}
void main() {
${this.COMPUTE_MASK}
gl_FragColor = vec4(vec3(computeRain(vUvs, time)) * tint, 1.0) * alpha * mask * opacity;
}
`;
}

View File

@@ -0,0 +1,55 @@
/**
* Snow shader effect.
*/
class SnowShader extends AbstractWeatherShader {
/** @inheritdoc */
static defaultUniforms = {
direction: 1.2
};
/* -------------------------------------------- */
/** @inheritdoc */
static fragmentShader = `
${this.FRAGMENT_HEADER}
uniform float direction;
// Contribute to snow PRNG
const mat3 prng = mat3(13.323122, 23.5112, 21.71123, 21.1212,
28.731200, 11.9312, 21.81120, 14.7212, 61.3934);
// Compute snow density according to uv and layer
float computeSnowDensity(in vec2 uv, in float layer) {
vec3 snowbase = vec3(floor(uv), 31.189 + layer);
vec3 m = floor(snowbase) / 10000.0 + fract(snowbase);
vec3 mp = (31415.9 + m) / fract(prng * m);
vec3 r = fract(mp);
vec2 s = abs(fract(uv) - 0.5 + 0.9 * r.xy - 0.45) + 0.01 * abs( 2.0 * fract(10.0 * uv.yx) - 1.0);
float d = 0.6 * (s.x + s.y) + max(s.x, s.y) - 0.01;
float edge = 0.005 + 0.05 * min(0.5 * abs(layer - 5.0 - sin(time * 0.1)), 1.0);
return smoothstep(edge * 2.0, -edge * 2.0, d) * r.x / (0.5 + 0.01 * layer * 1.5);
}
void main() {
${this.COMPUTE_MASK}
// Snow accumulation
float accumulation = 0.0;
// Compute layers
for ( float i=5.0; i<25.0; i++ ) {
// Compute uv layerization
vec2 snowuv = vUvs.xy * (1.0 + i * 1.5);
snowuv += vec2(snowuv.y * 1.2 * (fract(i * 6.258817) - direction), -time / (1.0 + i * 1.5 * 0.03));
// Perform accumulation layer after layer
accumulation += computeSnowDensity(snowuv, i);
}
// Output the accumulated snow pixel
gl_FragColor = vec4(vec3(accumulation) * tint, 1.0) * mask * alpha;
}
`;
}