Initial
This commit is contained in:
330
resources/app/client/pixi/webgl/shaders/base-shader-mixin.js
Normal file
330
resources/app/client/pixi/webgl/shaders/base-shader-mixin.js
Normal 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;
|
||||
};
|
||||
102
resources/app/client/pixi/webgl/shaders/base-shader.js
Normal file
102
resources/app/client/pixi/webgl/shaders/base-shader.js
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
364
resources/app/client/pixi/webgl/shaders/filters/blur.js
Normal file
364
resources/app/client/pixi/webgl/shaders/filters/blur.js
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
122
resources/app/client/pixi/webgl/shaders/filters/environment.js
Normal file
122
resources/app/client/pixi/webgl/shaders/filters/environment.js
Normal 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
|
||||
};
|
||||
}
|
||||
148
resources/app/client/pixi/webgl/shaders/filters/glow-overlay.js
Normal file
148
resources/app/client/pixi/webgl/shaders/filters/glow-overlay.js
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
583
resources/app/client/pixi/webgl/shaders/filters/transition.js
Normal file
583
resources/app/client/pixi/webgl/shaders/filters/transition.js
Normal 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);
|
||||
}
|
||||
}
|
||||
206
resources/app/client/pixi/webgl/shaders/filters/visibility.js
Normal file
206
resources/app/client/pixi/webgl/shaders/filters/visibility.js
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
11
resources/app/client/pixi/webgl/shaders/filters/void.js
Normal file
11
resources/app/client/pixi/webgl/shaders/filters/void.js
Normal 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);
|
||||
}`;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
538
resources/app/client/pixi/webgl/shaders/grid/grid.js
Normal file
538
resources/app/client/pixi/webgl/shaders/grid/grid.js
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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});
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
`;
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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});
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
`;
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
`;
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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});
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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}
|
||||
}`;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
65
resources/app/client/pixi/webgl/shaders/region/base.js
Normal file
65
resources/app/client/pixi/webgl/shaders/region/base.js
Normal 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;
|
||||
}
|
||||
}
|
||||
84
resources/app/client/pixi/webgl/shaders/region/highlight.js
Normal file
84
resources/app/client/pixi/webgl/shaders/region/highlight.js
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
369
resources/app/client/pixi/webgl/shaders/samplers/base-sampler.js
Normal file
369
resources/app/client/pixi/webgl/shaders/samplers/base-sampler.js
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
421
resources/app/client/pixi/webgl/shaders/samplers/depth.js
Normal file
421
resources/app/client/pixi/webgl/shaders/samplers/depth.js
Normal 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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
@@ -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;
|
||||
}`;
|
||||
}
|
||||
395
resources/app/client/pixi/webgl/shaders/samplers/occlusion.js
Normal file
395
resources/app/client/pixi/webgl/shaders/samplers/occlusion.js
Normal 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;
|
||||
}
|
||||
}
|
||||
46
resources/app/client/pixi/webgl/shaders/samplers/primary.js
Normal file
46
resources/app/client/pixi/webgl/shaders/samplers/primary.js
Normal 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) {}
|
||||
}
|
||||
|
||||
339
resources/app/client/pixi/webgl/shaders/samplers/token-ring.js
Normal file
339
resources/app/client/pixi/webgl/shaders/samplers/token-ring.js
Normal 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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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;`
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
107
resources/app/client/pixi/webgl/shaders/vision/effects/wave.js
Normal file
107
resources/app/client/pixi/webgl/shaders/vision/effects/wave.js
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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]
|
||||
};
|
||||
}
|
||||
208
resources/app/client/pixi/webgl/shaders/weather/base-weather.js
Normal file
208
resources/app/client/pixi/webgl/shaders/weather/base-weather.js
Normal 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;
|
||||
}
|
||||
}
|
||||
58
resources/app/client/pixi/webgl/shaders/weather/effect.js
Normal file
58
resources/app/client/pixi/webgl/shaders/weather/effect.js
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
129
resources/app/client/pixi/webgl/shaders/weather/fog.js
Normal file
129
resources/app/client/pixi/webgl/shaders/weather/fog.js
Normal 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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
48
resources/app/client/pixi/webgl/shaders/weather/rain.js
Normal file
48
resources/app/client/pixi/webgl/shaders/weather/rain.js
Normal 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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
55
resources/app/client/pixi/webgl/shaders/weather/snow.js
Normal file
55
resources/app/client/pixi/webgl/shaders/weather/snow.js
Normal 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;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user