Initial
This commit is contained in:
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