mirror of
https://github.com/Rezmason/matrix.git
synced 2026-04-17 22:09:28 -07:00
Migrated changes to WebGPU
This commit is contained in:
20
TODO.txt
20
TODO.txt
@@ -1,6 +1,6 @@
|
|||||||
TODO:
|
TODO:
|
||||||
|
|
||||||
Migrate recoded changes to WebGPU
|
Isolate classic from defaults
|
||||||
|
|
||||||
Update the README
|
Update the README
|
||||||
|
|
||||||
@@ -67,18 +67,18 @@ Write an explanation of the rain pass (and include images)
|
|||||||
Fullscreen quad and spacial mapping
|
Fullscreen quad and spacial mapping
|
||||||
MSDFs
|
MSDFs
|
||||||
|
|
||||||
|
Idea: Build a UI
|
||||||
|
Replace versions with presets
|
||||||
|
Simple changes update the values
|
||||||
|
Complex changes replace the pipeline
|
||||||
|
Make it a form, so it's accessible
|
||||||
|
Then, make it look cool like the UI from the old site
|
||||||
|
|
||||||
|
Maybe pay someone to make Mac/Windows screensavers
|
||||||
|
|
||||||
Zion Control's matrix variant
|
Zion Control's matrix variant
|
||||||
From Reloaded
|
From Reloaded
|
||||||
|
|
||||||
Idea: Build a UI
|
|
||||||
Changes some uniforms
|
|
||||||
Effect selection would swap out the desired effect pass, somehow
|
|
||||||
The color palette stuff would be hard
|
|
||||||
|
|
||||||
Deja vu effect: flashing rows
|
|
||||||
Make them flash all the time
|
|
||||||
Then use a thunder-like pattern to show and hide the flash
|
|
||||||
|
|
||||||
gpu-buffer, working title
|
gpu-buffer, working title
|
||||||
Support type aliasing (type Q = array<i32, 5>)
|
Support type aliasing (type Q = array<i32, 5>)
|
||||||
Support shorthand (vec4f)
|
Support shorthand (vec4f)
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
This project demonstrates five concepts:
|
This project demonstrates five concepts:
|
||||||
1. Drawing to floating point frame buffer objects, or 'FBO's,
|
1. Drawing to floating point frame buffer objects, or 'FBO's,
|
||||||
for performing computation and post-processing
|
for performing computation and post-processing
|
||||||
2. GPU-side computation, with a fragment shader
|
2. GPU-side computation, with fragment shaders
|
||||||
updating two alternating FBOs
|
updating two alternating FBOs
|
||||||
3. Rendering crisp "vector" graphics, with a multiple-channel
|
3. Rendering crisp "vector" graphics, with a multiple-channel
|
||||||
signed distance field (or 'MSDF')
|
signed distance field (or 'MSDF')
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ export default ({ config, device, timeBuffer }) => {
|
|||||||
bloomStrength: config.bloomStrength,
|
bloomStrength: config.bloomStrength,
|
||||||
ditherMagnitude: config.ditherMagnitude,
|
ditherMagnitude: config.ditherMagnitude,
|
||||||
backgroundColor: config.backgroundColor,
|
backgroundColor: config.backgroundColor,
|
||||||
|
cursorColor: config.cursorColor,
|
||||||
});
|
});
|
||||||
|
|
||||||
const paletteUniforms = paletteShaderUniforms.Palette;
|
const paletteUniforms = paletteShaderUniforms.Palette;
|
||||||
|
|||||||
@@ -6,11 +6,6 @@ const rippleTypes = {
|
|||||||
circle: 1,
|
circle: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const cycleStyles = {
|
|
||||||
cycleFasterWhenDimmed: 0,
|
|
||||||
cycleRandomly: 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
const numVerticesPerQuad = 2 * 3;
|
const numVerticesPerQuad = 2 * 3;
|
||||||
|
|
||||||
const makeConfigBuffer = (device, configUniforms, config, density, gridSize) => {
|
const makeConfigBuffer = (device, configUniforms, config, density, gridSize) => {
|
||||||
@@ -19,7 +14,6 @@ const makeConfigBuffer = (device, configUniforms, config, density, gridSize) =>
|
|||||||
gridSize,
|
gridSize,
|
||||||
density,
|
density,
|
||||||
showDebugView: config.effect === "none",
|
showDebugView: config.effect === "none",
|
||||||
cycleStyle: config.cycleStyleName in cycleStyles ? cycleStyles[config.cycleStyleName] : 0,
|
|
||||||
rippleType: config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1,
|
rippleType: config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1,
|
||||||
slantScale: 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1),
|
slantScale: 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1),
|
||||||
slantVec: [Math.cos(config.slant), Math.sin(config.slant)],
|
slantVec: [Math.cos(config.slant), Math.sin(config.slant)],
|
||||||
|
|||||||
@@ -78,6 +78,7 @@ export default ({ config, device, timeBuffer }) => {
|
|||||||
bloomStrength: config.bloomStrength,
|
bloomStrength: config.bloomStrength,
|
||||||
ditherMagnitude: config.ditherMagnitude,
|
ditherMagnitude: config.ditherMagnitude,
|
||||||
backgroundColor: config.backgroundColor,
|
backgroundColor: config.backgroundColor,
|
||||||
|
cursorColor: config.cursorColor,
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|||||||
@@ -23,18 +23,15 @@ vec4 getBrightness(vec2 uv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 brightnessRGB = getBrightness(vUV);
|
vec4 brightness = getBrightness(vUV);
|
||||||
|
|
||||||
// Combine the texture and bloom
|
|
||||||
vec2 brightness = brightnessRGB.rg;
|
|
||||||
|
|
||||||
// Dither: subtract a random value from the brightness
|
// Dither: subtract a random value from the brightness
|
||||||
brightness = brightness - rand( gl_FragCoord.xy, time ) * ditherMagnitude;
|
brightness -= rand( gl_FragCoord.xy, time ) * ditherMagnitude;
|
||||||
|
|
||||||
// Map the brightness to a position in the palette texture
|
// Map the brightness to a position in the palette texture
|
||||||
gl_FragColor = vec4(
|
gl_FragColor = vec4(
|
||||||
texture2D( palette, vec2(brightness.r, 0.0)).rgb
|
texture2D( palette, vec2(brightness.r, 0.0)).rgb
|
||||||
+ min(cursorColor * brightness.g, 1.0)
|
+ min(cursorColor * brightness.g, vec3(1.0))
|
||||||
+ backgroundColor,
|
+ backgroundColor,
|
||||||
1.0
|
1.0
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ float getThunder(float simTime, vec2 screenPos) {
|
|||||||
return 0.;
|
return 0.;
|
||||||
}
|
}
|
||||||
|
|
||||||
simTime *= 0.5;
|
float thunderTime = simTime * 0.5;
|
||||||
float thunder = 1. - fract(wobble(simTime));
|
float thunder = 1. - fract(wobble(thunderTime));
|
||||||
if (loops) {
|
if (loops) {
|
||||||
thunder = 1. - fract(simTime + 0.3);
|
thunder = 1. - fract(thunderTime + 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
thunder = log(thunder * 1.5) * 4.;
|
thunder = log(thunder * 1.5) * 4.;
|
||||||
@@ -82,7 +82,7 @@ float getRipple(float simTime, vec2 screenPos) {
|
|||||||
|
|
||||||
vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenPos, vec4 previous) {
|
vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenPos, vec4 previous) {
|
||||||
|
|
||||||
float multipliedEffects = 1.0 + getThunder(simTime, screenPos);
|
float multipliedEffects = 1. + getThunder(simTime, screenPos);
|
||||||
float addedEffects = getRipple(simTime, screenPos); // Round or square ripples across the grid
|
float addedEffects = getRipple(simTime, screenPos); // Round or square ripples across the grid
|
||||||
|
|
||||||
vec4 result = vec4(multipliedEffects, addedEffects, 0., 0.);
|
vec4 result = vec4(multipliedEffects, addedEffects, 0., 0.);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ uniform bool volumetric;
|
|||||||
uniform bool isolateCursor;
|
uniform bool isolateCursor;
|
||||||
|
|
||||||
varying vec2 vUV;
|
varying vec2 vUV;
|
||||||
varying vec4 vShine, vSymbol, vEffect;
|
varying vec4 vRaindrop, vSymbol, vEffect;
|
||||||
varying float vDepth;
|
varying float vDepth;
|
||||||
|
|
||||||
float median3(vec3 i) {
|
float median3(vec3 i) {
|
||||||
@@ -62,7 +62,7 @@ vec2 getBrightness(float brightness, float cursor, float multipliedEffects, floa
|
|||||||
if (!isolateCursor) {
|
if (!isolateCursor) {
|
||||||
cursor = 0.;
|
cursor = 0.;
|
||||||
}
|
}
|
||||||
brightness = (1.0 - brightness) * baseContrast + baseBrightness;
|
brightness = (1. - brightness) * baseContrast + baseBrightness;
|
||||||
|
|
||||||
// Modes that don't fade glyphs set their actual brightness here
|
// Modes that don't fade glyphs set their actual brightness here
|
||||||
if (brightnessOverride > 0. && brightness > brightnessThreshold && cursor == 0.) {
|
if (brightnessOverride > 0. && brightness > brightnessThreshold && cursor == 0.) {
|
||||||
@@ -98,7 +98,7 @@ float getSymbol(vec2 uv, float index) {
|
|||||||
// MSDF: calculate brightness of fragment based on distance to shape
|
// MSDF: calculate brightness of fragment based on distance to shape
|
||||||
vec3 dist = texture2D(glyphTex, uv).rgb;
|
vec3 dist = texture2D(glyphTex, uv).rgb;
|
||||||
float sigDist = median3(dist) - 0.5;
|
float sigDist = median3(dist) - 0.5;
|
||||||
return clamp(sigDist/fwidth(sigDist) + 0.5, 0., 1.);
|
return clamp(sigDist / fwidth(sigDist) + 0.5, 0., 1.);
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@@ -106,9 +106,9 @@ void main() {
|
|||||||
vec2 uv = getUV(vUV);
|
vec2 uv = getUV(vUV);
|
||||||
|
|
||||||
// Unpack the values from the data textures
|
// Unpack the values from the data textures
|
||||||
vec4 raindropData = volumetric ? vShine : texture2D( raindropState, uv);
|
vec4 raindropData = volumetric ? vRaindrop : texture2D(raindropState, uv);
|
||||||
vec4 symbolData = volumetric ? vSymbol : texture2D(symbolState, uv);
|
vec4 symbolData = volumetric ? vSymbol : texture2D( symbolState, uv);
|
||||||
vec4 effectData = volumetric ? vEffect : texture2D(effectState, uv);
|
vec4 effectData = volumetric ? vEffect : texture2D( effectState, uv);
|
||||||
|
|
||||||
vec2 brightness = getBrightness(raindropData.r, raindropData.g, effectData.r, effectData.g);
|
vec2 brightness = getBrightness(raindropData.r, raindropData.g, effectData.r, effectData.g);
|
||||||
float symbol = getSymbol(uv, symbolData.r);
|
float symbol = getSymbol(uv, symbolData.r);
|
||||||
@@ -118,9 +118,9 @@ void main() {
|
|||||||
vec3(
|
vec3(
|
||||||
raindropData.g,
|
raindropData.g,
|
||||||
vec2(
|
vec2(
|
||||||
1.0 - (raindropData.r * 3.0),
|
1. - (raindropData.r * 3.),
|
||||||
1.0 - (raindropData.r * 8.0)
|
1. - (raindropData.r * 8.)
|
||||||
) * (1.0 - raindropData.g)
|
) * (1. - raindropData.g)
|
||||||
) * symbol,
|
) * symbol,
|
||||||
1.
|
1.
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ precision highp float;
|
|||||||
|
|
||||||
// This shader is the star of the show.
|
// This shader is the star of the show.
|
||||||
// It writes falling rain to the channels of a data texture:
|
// It writes falling rain to the channels of a data texture:
|
||||||
// R: brightness
|
// R: raindrop brightness
|
||||||
// G: unused
|
// G: whether the cell is a "cursor"
|
||||||
// B: whether the cell is a "cursor"
|
// B: unused
|
||||||
// A: unused
|
// A: unused
|
||||||
|
|
||||||
// Listen.
|
// Listen.
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ uniform vec2 screenSize;
|
|||||||
uniform float time, animationSpeed, forwardSpeed;
|
uniform float time, animationSpeed, forwardSpeed;
|
||||||
uniform bool volumetric;
|
uniform bool volumetric;
|
||||||
varying vec2 vUV;
|
varying vec2 vUV;
|
||||||
varying vec4 vShine, vSymbol, vEffect;
|
varying vec4 vRaindrop, vSymbol, vEffect;
|
||||||
varying float vDepth;
|
varying float vDepth;
|
||||||
|
|
||||||
highp float rand( const in vec2 uv ) {
|
highp float rand( const in vec2 uv ) {
|
||||||
@@ -22,9 +22,9 @@ highp float rand( const in vec2 uv ) {
|
|||||||
void main() {
|
void main() {
|
||||||
|
|
||||||
vUV = (aPosition + aCorner) * quadSize;
|
vUV = (aPosition + aCorner) * quadSize;
|
||||||
vShine = texture2D(raindropState, aPosition * quadSize);
|
vRaindrop = texture2D(raindropState, aPosition * quadSize);
|
||||||
vSymbol = texture2D(symbolState, aPosition * quadSize);
|
vSymbol = texture2D( symbolState, aPosition * quadSize);
|
||||||
vEffect = texture2D(effectState, aPosition * quadSize);
|
vEffect = texture2D( effectState, aPosition * quadSize);
|
||||||
|
|
||||||
// Calculate the world space position
|
// Calculate the world space position
|
||||||
float quadDepth = 0.0;
|
float quadDepth = 0.0;
|
||||||
|
|||||||
@@ -24,11 +24,11 @@ vec4 getBrightness(vec2 uv) {
|
|||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 color = texture2D(stripes, vUV).rgb;
|
vec3 color = texture2D(stripes, vUV).rgb;
|
||||||
// Combine the texture and bloom
|
|
||||||
vec4 brightness = getBrightness(vUV);
|
vec4 brightness = getBrightness(vUV);
|
||||||
|
|
||||||
// Dither: subtract a random value from the brightness
|
// Dither: subtract a random value from the brightness
|
||||||
brightness = brightness - rand( gl_FragCoord.xy, time ) * ditherMagnitude;
|
brightness -= rand( gl_FragCoord.xy, time ) * ditherMagnitude;
|
||||||
|
|
||||||
gl_FragColor = vec4(
|
gl_FragColor = vec4(
|
||||||
color * brightness.r
|
color * brightness.r
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ fn getBrightness(uv : vec2<f32>) -> vec4<f32> {
|
|||||||
var bgColor = textureSampleLevel( backgroundTex, linearSampler, uv, 0.0 ).rgb;
|
var bgColor = textureSampleLevel( backgroundTex, linearSampler, uv, 0.0 ).rgb;
|
||||||
|
|
||||||
// Combine the texture and bloom, then blow it out to reveal more of the image
|
// Combine the texture and bloom, then blow it out to reveal more of the image
|
||||||
var brightness = getBrightness(uv).r;
|
var brightness = getBrightness(uv);
|
||||||
brightness = pow(brightness, 1.5);
|
|
||||||
|
|
||||||
textureStore(outputTex, coord, vec4<f32>(bgColor * brightness, 1.0));
|
textureStore(outputTex, coord, vec4<f32>(bgColor * (brightness.r + brightness.g * 2.0), 1.0));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ struct Config {
|
|||||||
bloomStrength : f32,
|
bloomStrength : f32,
|
||||||
ditherMagnitude : f32,
|
ditherMagnitude : f32,
|
||||||
backgroundColor : vec3<f32>,
|
backgroundColor : vec3<f32>,
|
||||||
|
cursorColor : vec3<f32>
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Palette {
|
struct Palette {
|
||||||
@@ -54,17 +55,19 @@ fn getBrightness(uv : vec2<f32>) -> vec4<f32> {
|
|||||||
|
|
||||||
var uv = vec2<f32>(coord) / vec2<f32>(screenSize);
|
var uv = vec2<f32>(coord) / vec2<f32>(screenSize);
|
||||||
|
|
||||||
var brightnessRGB = getBrightness(uv);
|
var brightness = getBrightness(uv);
|
||||||
|
|
||||||
// Combine the texture and bloom
|
|
||||||
var brightness = brightnessRGB.r + brightnessRGB.g + brightnessRGB.b;
|
|
||||||
|
|
||||||
// Dither: subtract a random value from the brightness
|
// Dither: subtract a random value from the brightness
|
||||||
brightness -= randomFloat( uv + vec2<f32>(time.seconds) ) * config.ditherMagnitude;
|
brightness -= randomFloat( uv + vec2<f32>(time.seconds) ) * config.ditherMagnitude;
|
||||||
|
|
||||||
var paletteIndex = clamp(i32(brightness * 512.0), 0, 511);
|
|
||||||
|
|
||||||
// Map the brightness to a position in the palette texture
|
// Map the brightness to a position in the palette texture
|
||||||
textureStore(outputTex, coord, vec4<f32>(palette.colors[paletteIndex] + config.backgroundColor, 1.0));
|
var paletteIndex = clamp(i32(brightness.r * 512.0), 0, 511);
|
||||||
|
|
||||||
|
textureStore(outputTex, coord, vec4<f32>(
|
||||||
|
palette.colors[paletteIndex]
|
||||||
|
+ min(config.cursorColor * brightness.g, vec3<f32>(1.0))
|
||||||
|
+ config.backgroundColor,
|
||||||
|
1.0
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,23 +14,21 @@ struct Config {
|
|||||||
brightnessThreshold : f32,
|
brightnessThreshold : f32,
|
||||||
brightnessOverride : f32,
|
brightnessOverride : f32,
|
||||||
brightnessDecay : f32,
|
brightnessDecay : f32,
|
||||||
baseBrightness : f32,
|
|
||||||
baseContrast : f32,
|
|
||||||
cursorBrightness : f32,
|
cursorBrightness : f32,
|
||||||
cycleSpeed : f32,
|
cycleSpeed : f32,
|
||||||
cycleFrameSkip : i32,
|
cycleFrameSkip : i32,
|
||||||
fallSpeed : f32,
|
fallSpeed : f32,
|
||||||
hasSun : i32,
|
|
||||||
hasThunder : i32,
|
hasThunder : i32,
|
||||||
raindropLength : f32,
|
raindropLength : f32,
|
||||||
rippleScale : f32,
|
rippleScale : f32,
|
||||||
rippleSpeed : f32,
|
rippleSpeed : f32,
|
||||||
rippleThickness : f32,
|
rippleThickness : f32,
|
||||||
cycleStyle : i32,
|
|
||||||
rippleType : i32,
|
rippleType : i32,
|
||||||
|
|
||||||
// render-specific properties
|
// render-specific properties
|
||||||
forwardSpeed : f32,
|
forwardSpeed : f32,
|
||||||
|
baseBrightness : f32,
|
||||||
|
baseContrast : f32,
|
||||||
glyphVerticalSpacing : f32,
|
glyphVerticalSpacing : f32,
|
||||||
glyphEdgeCrop : f32,
|
glyphEdgeCrop : f32,
|
||||||
isPolar : i32,
|
isPolar : i32,
|
||||||
@@ -38,6 +36,7 @@ struct Config {
|
|||||||
slantScale : f32,
|
slantScale : f32,
|
||||||
slantVec : vec2<f32>,
|
slantVec : vec2<f32>,
|
||||||
volumetric : i32,
|
volumetric : i32,
|
||||||
|
isolateCursor : i32,
|
||||||
loops : i32,
|
loops : i32,
|
||||||
highPassThreshold : f32,
|
highPassThreshold : f32,
|
||||||
};
|
};
|
||||||
@@ -58,6 +57,7 @@ struct Scene {
|
|||||||
struct Cell {
|
struct Cell {
|
||||||
raindrop : vec4<f32>,
|
raindrop : vec4<f32>,
|
||||||
symbol : vec4<f32>,
|
symbol : vec4<f32>,
|
||||||
|
effect : vec4<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
// The array of cells that the compute shader updates, and the fragment shader draws.
|
// The array of cells that the compute shader updates, and the fragment shader draws.
|
||||||
@@ -128,9 +128,10 @@ fn wobble(x : f32) -> f32 {
|
|||||||
|
|
||||||
// Compute shader core functions
|
// Compute shader core functions
|
||||||
|
|
||||||
// Rain time is the shader's key underlying concept.
|
// This is the code rain's key underlying concept.
|
||||||
// It's why glyphs that share a column are lit simultaneously, and are brighter toward the bottom.
|
// It's why glyphs that share a column are lit simultaneously, and are brighter toward the bottom.
|
||||||
fn getRainTime(simTime : f32, glyphPos : vec2<f32>) -> f32 {
|
// It's also why those bright areas are truncated into raindrops.
|
||||||
|
fn getRainBrightness(simTime : f32, glyphPos : vec2<f32>) -> f32 {
|
||||||
var columnTimeOffset = randomFloat(vec2<f32>(glyphPos.x, 0.0)) * 1000.0;
|
var columnTimeOffset = randomFloat(vec2<f32>(glyphPos.x, 0.0)) * 1000.0;
|
||||||
var columnSpeedOffset = randomFloat(vec2<f32>(glyphPos.x + 0.1, 0.0)) * 0.5 + 0.5;
|
var columnSpeedOffset = randomFloat(vec2<f32>(glyphPos.x + 0.1, 0.0)) * 0.5 + 0.5;
|
||||||
if (bool(config.loops)) {
|
if (bool(config.loops)) {
|
||||||
@@ -141,31 +142,16 @@ fn getRainTime(simTime : f32, glyphPos : vec2<f32>) -> f32 {
|
|||||||
if (!bool(config.loops)) {
|
if (!bool(config.loops)) {
|
||||||
rainTime = wobble(rainTime);
|
rainTime = wobble(rainTime);
|
||||||
}
|
}
|
||||||
return rainTime;
|
return fract(rainTime);
|
||||||
}
|
|
||||||
|
|
||||||
fn getBrightness(rainTime : f32) -> f32 {
|
|
||||||
return (1.0 - fract(rainTime)) * config.baseContrast + config.baseBrightness;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getCycleSpeed(brightness : f32) -> f32 {
|
|
||||||
var localCycleSpeed = 1.0;
|
|
||||||
if (config.cycleStyle == 0 && brightness > 0.0) {
|
|
||||||
localCycleSpeed = pow(1.0 - brightness, 4.0);
|
|
||||||
}
|
|
||||||
return config.animationSpeed * config.cycleSpeed * localCycleSpeed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute shader additional effects
|
// Compute shader additional effects
|
||||||
|
|
||||||
fn applySunShowerBrightness(brightness : f32, screenPos : vec2<f32>) -> f32 {
|
fn getThunder(simTime : f32, screenPos : vec2<f32>) -> f32 {
|
||||||
if (brightness >= -4.0) {
|
if (!bool(config.hasThunder)) {
|
||||||
return pow(fract(brightness * 0.5), 3.0) * screenPos.y * 1.5;
|
return 0.0;
|
||||||
}
|
}
|
||||||
return brightness;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn applyThunderBrightness(brightness : f32, simTime : f32, screenPos : vec2<f32>) -> f32 {
|
|
||||||
var thunderTime = simTime * 0.5;
|
var thunderTime = simTime * 0.5;
|
||||||
var thunder = 1.0 - fract(wobble(thunderTime));
|
var thunder = 1.0 - fract(wobble(thunderTime));
|
||||||
if (bool(config.loops)) {
|
if (bool(config.loops)) {
|
||||||
@@ -173,14 +159,13 @@ fn applyThunderBrightness(brightness : f32, simTime : f32, screenPos : vec2<f32>
|
|||||||
}
|
}
|
||||||
|
|
||||||
thunder = log(thunder * 1.5) * 4.0;
|
thunder = log(thunder * 1.5) * 4.0;
|
||||||
thunder = clamp(thunder, 0.0, 1.0);
|
thunder = clamp(thunder, 0.0, 1.0) * 10.0 * pow(screenPos.y, 2.0);
|
||||||
thunder *= pow(screenPos.y, 2.0) * 3.0;
|
return thunder;
|
||||||
return brightness + thunder;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn applyRippleEffect(effect : f32, simTime : f32, screenPos : vec2<f32>) -> f32 {
|
fn getRipple(simTime : f32, screenPos : vec2<f32>) -> f32 {
|
||||||
if (config.rippleType == -1) {
|
if (config.rippleType == -1) {
|
||||||
return effect;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rippleTime = (simTime * 0.5 + sin(simTime) * 0.2) * config.rippleSpeed + 1.0; // TODO: clarify
|
var rippleTime = (simTime * 0.5 + sin(simTime) * 0.2) * config.rippleSpeed + 1.0; // TODO: clarify
|
||||||
@@ -204,37 +189,19 @@ fn applyRippleEffect(effect : f32, simTime : f32, screenPos : vec2<f32>) -> f32
|
|||||||
var rippleValue = fract(rippleTime) * config.rippleScale - rippleDistance;
|
var rippleValue = fract(rippleTime) * config.rippleScale - rippleDistance;
|
||||||
|
|
||||||
if (rippleValue > 0.0 && rippleValue < config.rippleThickness) {
|
if (rippleValue > 0.0 && rippleValue < config.rippleThickness) {
|
||||||
return effect + 0.75;
|
return 0.75;
|
||||||
}
|
}
|
||||||
|
|
||||||
return effect;
|
return 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute shader main functions
|
// Compute shader main functions
|
||||||
|
|
||||||
fn computeShine (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, screenPos : vec2<f32>, previous : vec4<f32>) -> vec4<f32> {
|
fn computeRaindrop (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, screenPos : vec2<f32>, previous : vec4<f32>) -> vec4<f32> {
|
||||||
|
|
||||||
// Determine the glyph's local time.
|
|
||||||
var rainTime = getRainTime(simTime, glyphPos);
|
|
||||||
var rainTimeBelow = getRainTime(simTime, glyphPos + vec2<f32>(0., -1.));
|
|
||||||
var cursor = select(0.0, 1.0, fract(rainTime) < fract(rainTimeBelow));
|
|
||||||
|
|
||||||
// Rain time is the backbone of this effect.
|
|
||||||
|
|
||||||
// Determine the glyph's brightness.
|
|
||||||
var brightness = getBrightness(rainTime);
|
|
||||||
|
|
||||||
if (bool(config.hasSun)) {
|
|
||||||
brightness = applySunShowerBrightness(brightness, screenPos);
|
|
||||||
}
|
|
||||||
if (bool(config.hasThunder)) {
|
|
||||||
brightness = applyThunderBrightness(brightness, simTime, screenPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the glyph's effect— the amount the glyph lights up for other reasons
|
|
||||||
var effect = 0.0;
|
|
||||||
effect = applyRippleEffect(effect, simTime, screenPos); // Round or square ripples across the grid
|
|
||||||
|
|
||||||
|
var brightness = getRainBrightness(simTime, glyphPos);
|
||||||
|
var brightnessBelow = getRainBrightness(simTime, glyphPos + vec2(0., -1.));
|
||||||
|
var cursor = select(0.0, 1.0, brightness < brightnessBelow);
|
||||||
|
|
||||||
// Blend the glyph's brightness with its previous brightness, so it winks on and off organically
|
// Blend the glyph's brightness with its previous brightness, so it winks on and off organically
|
||||||
if (!isFirstFrame) {
|
if (!isFirstFrame) {
|
||||||
@@ -242,35 +209,31 @@ fn computeShine (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, scree
|
|||||||
brightness = mix(previousBrightness, brightness, config.brightnessDecay);
|
brightness = mix(previousBrightness, brightness, config.brightnessDecay);
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = vec4<f32>(brightness, fract(rainTime), cursor, effect);
|
var result = vec4<f32>(brightness, cursor, 0.0, 0.0);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn computeSymbol (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, screenPos : vec2<f32>, previous : vec4<f32>, raindrop : vec4<f32>) -> vec4<f32> {
|
fn computeSymbol (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, screenPos : vec2<f32>, previous : vec4<f32>, raindrop : vec4<f32>) -> vec4<f32> {
|
||||||
|
|
||||||
var brightness = raindrop.r;
|
|
||||||
|
|
||||||
var previousSymbol = previous.r;
|
var previousSymbol = previous.r;
|
||||||
var previousAge = previous.g;
|
var previousAge = previous.g;
|
||||||
var resetGlyph = isFirstFrame;
|
var resetGlyph = isFirstFrame;
|
||||||
if (bool(config.loops)) {
|
if (bool(config.loops)) {
|
||||||
resetGlyph = resetGlyph || brightness < 0.0;
|
resetGlyph = resetGlyph || raindrop.r < 0.0;
|
||||||
}
|
}
|
||||||
if (resetGlyph) {
|
if (resetGlyph) {
|
||||||
previousAge = randomFloat(screenPos + 0.5);
|
previousAge = randomFloat(screenPos + 0.5);
|
||||||
previousSymbol = floor(config.glyphSequenceLength * randomFloat(screenPos));
|
previousSymbol = floor(config.glyphSequenceLength * randomFloat(screenPos));
|
||||||
}
|
}
|
||||||
var cycleSpeed = getCycleSpeed(brightness);
|
var cycleSpeed = config.animationSpeed * config.cycleSpeed;
|
||||||
var age = previousAge;
|
var age = previousAge;
|
||||||
var symbol = previousSymbol;
|
var symbol = previousSymbol;
|
||||||
if (time.frames % config.cycleFrameSkip == 0) {
|
if (time.frames % config.cycleFrameSkip == 0) {
|
||||||
age += cycleSpeed * f32(config.cycleFrameSkip);
|
age += cycleSpeed * f32(config.cycleFrameSkip);
|
||||||
var advance = floor(age);
|
var advance = floor(age);
|
||||||
age = fract(age);
|
if (age > 1.0) {
|
||||||
if (config.cycleStyle == 0) {
|
|
||||||
symbol = (symbol + advance) % config.glyphSequenceLength;
|
|
||||||
} else if (config.cycleStyle == 1 && advance > 0.) {
|
|
||||||
symbol = floor(config.glyphSequenceLength * randomFloat(screenPos + simTime));
|
symbol = floor(config.glyphSequenceLength * randomFloat(screenPos + simTime));
|
||||||
|
age = fract(age);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,6 +241,15 @@ fn computeSymbol (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, scre
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn computeEffect (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, screenPos : vec2<f32>, previous : vec4<f32>, raindrop : vec4<f32>) -> vec4<f32> {
|
||||||
|
|
||||||
|
var multipliedEffects = 1.0 + getThunder(simTime, screenPos);
|
||||||
|
var addedEffects = getRipple(simTime, screenPos); // Round or square ripples across the grid
|
||||||
|
|
||||||
|
var result = vec4<f32>(multipliedEffects, addedEffects, 0.0, 0.0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
@compute @workgroup_size(32, 1, 1) fn computeMain(input : ComputeInput) {
|
@compute @workgroup_size(32, 1, 1) fn computeMain(input : ComputeInput) {
|
||||||
|
|
||||||
// Resolve the invocation ID to a cell coordinate
|
// Resolve the invocation ID to a cell coordinate
|
||||||
@@ -298,8 +270,9 @@ fn computeSymbol (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, scre
|
|||||||
var screenPos = glyphPos / config.gridSize;
|
var screenPos = glyphPos / config.gridSize;
|
||||||
|
|
||||||
var cell = cells_RW.cells[i];
|
var cell = cells_RW.cells[i];
|
||||||
cell.raindrop = computeShine(simTime, isFirstFrame, glyphPos, screenPos, cell.raindrop);
|
cell.raindrop = computeRaindrop(simTime, isFirstFrame, glyphPos, screenPos, cell.raindrop);
|
||||||
cell.symbol = computeSymbol(simTime, isFirstFrame, glyphPos, screenPos, cell.symbol, cell.raindrop);
|
cell.symbol = computeSymbol(simTime, isFirstFrame, glyphPos, screenPos, cell.symbol, cell.raindrop);
|
||||||
|
cell.effect = computeEffect(simTime, isFirstFrame, glyphPos, screenPos, cell.effect, cell.raindrop);
|
||||||
cells_RW.cells[i] = cell;
|
cells_RW.cells[i] = cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,89 +340,117 @@ fn median3(i : vec3<f32>) -> f32 {
|
|||||||
return max(min(i.r, i.g), min(max(i.r, i.g), i.b));
|
return max(min(i.r, i.g), min(max(i.r, i.g), i.b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn getUV(inputUV : vec2<f32>) -> vec2<f32> {
|
||||||
|
|
||||||
|
var uv = inputUV;
|
||||||
|
|
||||||
|
if (bool(config.volumetric)) {
|
||||||
|
return uv;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bool(config.isPolar)) {
|
||||||
|
// Curved space to make the letters appear to radiate from up above
|
||||||
|
uv -= 0.5;
|
||||||
|
uv *= 0.5;
|
||||||
|
uv.y -= 0.5;
|
||||||
|
var radius = length(uv);
|
||||||
|
var angle = atan2(uv.y, uv.x) / (2.0 * PI) + 0.5;
|
||||||
|
uv = vec2<f32>(fract(angle * 4.0 - 0.5), 1.5 * (1.0 - sqrt(radius)));
|
||||||
|
} else {
|
||||||
|
// Apply the slant and a scale to space so the viewport is still fully covered by the geometry
|
||||||
|
uv = vec2<f32>(
|
||||||
|
(uv.x - 0.5) * config.slantVec.x + (uv.y - 0.5) * config.slantVec.y,
|
||||||
|
(uv.y - 0.5) * config.slantVec.x - (uv.x - 0.5) * config.slantVec.y
|
||||||
|
) * config.slantScale + 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
uv.y /= config.glyphHeightToWidth;
|
||||||
|
|
||||||
|
return uv;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getBrightness(inputBrightness : f32, cursor : f32, quadDepth : f32, multipliedEffects : f32, addedEffects : f32) -> vec2<f32> {
|
||||||
|
|
||||||
|
var isCursor = bool(cursor);
|
||||||
|
|
||||||
|
if (!bool(config.isolateCursor)) {
|
||||||
|
isCursor = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var brightness = (1.0 - inputBrightness) * config.baseContrast + config.baseBrightness;
|
||||||
|
|
||||||
|
// Modes that don't fade glyphs set their actual brightness here
|
||||||
|
if (config.brightnessOverride > 0. && brightness > config.brightnessThreshold && !isCursor) {
|
||||||
|
brightness = config.brightnessOverride;
|
||||||
|
}
|
||||||
|
|
||||||
|
brightness *= multipliedEffects;
|
||||||
|
brightness += addedEffects;
|
||||||
|
|
||||||
|
// In volumetric mode, distant glyphs are dimmer
|
||||||
|
if (bool(config.volumetric) && !bool(config.showDebugView)) {
|
||||||
|
brightness = brightness * min(1.0, quadDepth);
|
||||||
|
}
|
||||||
|
|
||||||
|
return select(vec2<f32>(1.0, 0.0), vec2<f32>(0.0, 1.0), isCursor) * brightness;
|
||||||
|
}
|
||||||
|
|
||||||
fn getSymbolUV(symbol : i32) -> vec2<f32> {
|
fn getSymbolUV(symbol : i32) -> vec2<f32> {
|
||||||
var symbolX = symbol % config.glyphTextureGridSize.x;
|
var symbolX = symbol % config.glyphTextureGridSize.x;
|
||||||
var symbolY = symbol / config.glyphTextureGridSize.x;
|
var symbolY = symbol / config.glyphTextureGridSize.x;
|
||||||
return vec2<f32>(f32(symbolX), f32(symbolY));
|
return vec2<f32>(f32(symbolX), f32(symbolY));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn getSymbol(cellUV : vec2<f32>, index : i32) -> f32 {
|
||||||
|
// resolve UV to cropped position of glyph in MSDF texture
|
||||||
|
var uv = fract(cellUV * config.gridSize);
|
||||||
|
uv.y = 1.0 - uv.y; // WebGL -> WebGPU y-flip
|
||||||
|
uv -= 0.5;
|
||||||
|
uv *= clamp(1.0 - config.glyphEdgeCrop, 0.0, 1.0);
|
||||||
|
uv += 0.5;
|
||||||
|
uv = (uv + getSymbolUV(index)) / vec2<f32>(config.glyphTextureGridSize);
|
||||||
|
|
||||||
|
// MSDF: calculate brightness of fragment based on distance to shape
|
||||||
|
var dist = textureSample(msdfTexture, linearSampler, uv).rgb;
|
||||||
|
var sigDist = median3(dist) - 0.5;
|
||||||
|
return clamp(sigDist / fwidth(sigDist) + 0.5, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
// Fragment shader
|
// Fragment shader
|
||||||
|
|
||||||
@fragment fn fragMain(input : VertOutput) -> FragOutput {
|
@fragment fn fragMain(input : VertOutput) -> FragOutput {
|
||||||
|
|
||||||
var volumetric = bool(config.volumetric);
|
var uv = getUV(input.uv);
|
||||||
var uv = input.uv;
|
|
||||||
|
|
||||||
// For normal mode, derive the fragment's glyph and msdf UV from its screen space position
|
|
||||||
if (!volumetric) {
|
|
||||||
if (bool(config.isPolar)) {
|
|
||||||
// Curve space to make the letters appear to radiate from up above
|
|
||||||
uv -= 0.5;
|
|
||||||
uv *= 0.5;
|
|
||||||
uv.y -= 0.5;
|
|
||||||
var radius = length(uv);
|
|
||||||
var angle = atan2(uv.y, uv.x) / (2.0 * PI) + 0.5;
|
|
||||||
uv = vec2<f32>(fract(angle * 4.0 - 0.5), 1.5 * (1.0 - sqrt(radius)));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Apply the slant and a scale to space so the viewport is still fully covered by the geometry
|
|
||||||
uv = vec2<f32>(
|
|
||||||
(uv.x - 0.5) * config.slantVec.x + (uv.y - 0.5) * config.slantVec.y,
|
|
||||||
(uv.y - 0.5) * config.slantVec.x - (uv.x - 0.5) * config.slantVec.y
|
|
||||||
) * config.slantScale + 0.5;
|
|
||||||
}
|
|
||||||
uv.y /= config.glyphHeightToWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve cell
|
// Retrieve cell
|
||||||
var gridCoord : vec2<i32> = vec2<i32>(uv * config.gridSize);
|
var gridCoord : vec2<i32> = vec2<i32>(uv * config.gridSize);
|
||||||
var gridIndex = gridCoord.y * i32(config.gridSize.x) + gridCoord.x;
|
var gridIndex = gridCoord.y * i32(config.gridSize.x) + gridCoord.x;
|
||||||
var cell = cells_RO.cells[gridIndex];
|
var cell = cells_RO.cells[gridIndex];
|
||||||
var symbolUV = getSymbolUV(i32(cell.symbol.r));
|
|
||||||
|
|
||||||
var brightness = cell.raindrop.r;
|
var brightness = getBrightness(
|
||||||
|
cell.raindrop.r,
|
||||||
// Modes that don't fade glyphs set their actual brightness here
|
cell.raindrop.g,
|
||||||
if (config.brightnessOverride > 0.0 && brightness > config.brightnessThreshold) {
|
input.quadDepth,
|
||||||
brightness = config.brightnessOverride;
|
cell.effect.r,
|
||||||
}
|
cell.effect.g
|
||||||
|
);
|
||||||
brightness = max(cell.raindrop.b * config.cursorBrightness, brightness);
|
var symbol = getSymbol(uv, i32(cell.symbol.r));
|
||||||
brightness = max(cell.raindrop.a, brightness);
|
|
||||||
|
|
||||||
// In volumetric mode, distant glyphs are dimmer
|
|
||||||
if (volumetric) {
|
|
||||||
brightness *= min(1.0, input.quadDepth);
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolve UV to cropped position of glyph in MSDF texture
|
|
||||||
var glyphUV = fract(uv * config.gridSize);
|
|
||||||
glyphUV.y = 1.0 - glyphUV.y; // WebGL -> WebGPU y-flip
|
|
||||||
glyphUV -= 0.5;
|
|
||||||
glyphUV *= clamp(1.0 - config.glyphEdgeCrop, 0.0, 1.0);
|
|
||||||
glyphUV += 0.5;
|
|
||||||
var msdfUV = (glyphUV + symbolUV) / vec2<f32>(config.glyphTextureGridSize);
|
|
||||||
|
|
||||||
// MSDF : calculate brightness of fragment based on distance to shape
|
|
||||||
var dist = textureSample(msdfTexture, linearSampler, msdfUV).rgb;
|
|
||||||
var sigDist = median3(dist) - 0.5;
|
|
||||||
var alpha = clamp(sigDist / fwidth(sigDist) + 0.5, 0.0, 1.0);
|
|
||||||
|
|
||||||
var output : FragOutput;
|
var output : FragOutput;
|
||||||
|
|
||||||
if (bool(config.showDebugView)) {
|
if (bool(config.showDebugView)) {
|
||||||
output.color = vec4<f32>(
|
output.color = vec4<f32>(
|
||||||
vec3<f32>(
|
vec3<f32>(
|
||||||
cell.raindrop.b,
|
cell.raindrop.g,
|
||||||
vec2<f32>(
|
vec2<f32>(
|
||||||
1.0 - (cell.raindrop.g * 3.0),
|
1.0 - (cell.raindrop.r * 3.0),
|
||||||
1.0 - (cell.raindrop.g * 10.0)
|
1.0 - (cell.raindrop.r * 8.0)
|
||||||
) * (1.0 - cell.raindrop.b)
|
) * (1.0 - cell.raindrop.g)
|
||||||
) * alpha,
|
) * symbol,
|
||||||
1.
|
1.0
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
output.color = vec4<f32>(brightness * alpha, 0., 0., 1.0);
|
output.color = vec4(brightness * symbol, 0.0, 0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
var highPassColor = output.color;
|
var highPassColor = output.color;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ struct Config {
|
|||||||
bloomStrength : f32,
|
bloomStrength : f32,
|
||||||
ditherMagnitude : f32,
|
ditherMagnitude : f32,
|
||||||
backgroundColor : vec3<f32>,
|
backgroundColor : vec3<f32>,
|
||||||
|
cursorColor : vec3<f32>
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Time {
|
struct Time {
|
||||||
@@ -52,10 +53,15 @@ fn getBrightness(uv : vec2<f32>) -> vec4<f32> {
|
|||||||
|
|
||||||
var color = textureSampleLevel( stripeTexture, linearSampler, uv, 0.0 ).rgb;
|
var color = textureSampleLevel( stripeTexture, linearSampler, uv, 0.0 ).rgb;
|
||||||
|
|
||||||
var brightness = getBrightness(uv).r;
|
var brightness = getBrightness(uv);
|
||||||
|
|
||||||
// Dither: subtract a random value from the brightness
|
// Dither: subtract a random value from the brightness
|
||||||
brightness -= randomFloat( uv + vec2<f32>(time.seconds) ) * config.ditherMagnitude;
|
brightness -= randomFloat( uv + vec2<f32>(time.seconds) ) * config.ditherMagnitude;
|
||||||
|
|
||||||
textureStore(outputTex, coord, vec4<f32>(color * brightness + config.backgroundColor, 1.0));
|
textureStore(outputTex, coord, vec4<f32>(
|
||||||
|
color * brightness.r
|
||||||
|
+ min(config.cursorColor * brightness.g, vec3<f32>(1.0))
|
||||||
|
+ config.backgroundColor,
|
||||||
|
1.0
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user