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

View File

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

View File

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

View File

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

View File

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

View File

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