Migrated changes to WebGPU

This commit is contained in:
Rezmason
2022-09-16 21:08:00 -07:00
parent ab280a95d3
commit 9ab9924294
15 changed files with 181 additions and 179 deletions

View File

@@ -1,6 +1,6 @@
TODO:
Migrate recoded changes to WebGPU
Isolate classic from defaults
Update the README
@@ -67,18 +67,18 @@ Write an explanation of the rain pass (and include images)
Fullscreen quad and spacial mapping
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
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
Support type aliasing (type Q = array<i32, 5>)
Support shorthand (vec4f)

View File

@@ -23,7 +23,7 @@
This project demonstrates five concepts:
1. Drawing to floating point frame buffer objects, or 'FBO's,
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
3. Rendering crisp "vector" graphics, with a multiple-channel
signed distance field (or 'MSDF')

View File

@@ -107,6 +107,7 @@ export default ({ config, device, timeBuffer }) => {
bloomStrength: config.bloomStrength,
ditherMagnitude: config.ditherMagnitude,
backgroundColor: config.backgroundColor,
cursorColor: config.cursorColor,
});
const paletteUniforms = paletteShaderUniforms.Palette;

View File

@@ -6,11 +6,6 @@ const rippleTypes = {
circle: 1,
};
const cycleStyles = {
cycleFasterWhenDimmed: 0,
cycleRandomly: 1,
};
const numVerticesPerQuad = 2 * 3;
const makeConfigBuffer = (device, configUniforms, config, density, gridSize) => {
@@ -19,7 +14,6 @@ const makeConfigBuffer = (device, configUniforms, config, density, gridSize) =>
gridSize,
density,
showDebugView: config.effect === "none",
cycleStyle: config.cycleStyleName in cycleStyles ? cycleStyles[config.cycleStyleName] : 0,
rippleType: config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1,
slantScale: 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1),
slantVec: [Math.cos(config.slant), Math.sin(config.slant)],

View File

@@ -78,6 +78,7 @@ export default ({ config, device, timeBuffer }) => {
bloomStrength: config.bloomStrength,
ditherMagnitude: config.ditherMagnitude,
backgroundColor: config.backgroundColor,
cursorColor: config.cursorColor,
});
})();

View File

@@ -23,18 +23,15 @@ vec4 getBrightness(vec2 uv) {
}
void main() {
vec4 brightnessRGB = getBrightness(vUV);
// Combine the texture and bloom
vec2 brightness = brightnessRGB.rg;
vec4 brightness = getBrightness(vUV);
// 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
gl_FragColor = vec4(
texture2D( palette, vec2(brightness.r, 0.0)).rgb
+ min(cursorColor * brightness.g, 1.0)
+ min(cursorColor * brightness.g, vec3(1.0))
+ backgroundColor,
1.0
);

View File

@@ -35,10 +35,10 @@ float getThunder(float simTime, vec2 screenPos) {
return 0.;
}
simTime *= 0.5;
float thunder = 1. - fract(wobble(simTime));
float thunderTime = simTime * 0.5;
float thunder = 1. - fract(wobble(thunderTime));
if (loops) {
thunder = 1. - fract(simTime + 0.3);
thunder = 1. - fract(thunderTime + 0.3);
}
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) {
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
vec4 result = vec4(multipliedEffects, addedEffects, 0., 0.);

View File

@@ -19,7 +19,7 @@ uniform bool volumetric;
uniform bool isolateCursor;
varying vec2 vUV;
varying vec4 vShine, vSymbol, vEffect;
varying vec4 vRaindrop, vSymbol, vEffect;
varying float vDepth;
float median3(vec3 i) {
@@ -62,7 +62,7 @@ vec2 getBrightness(float brightness, float cursor, float multipliedEffects, floa
if (!isolateCursor) {
cursor = 0.;
}
brightness = (1.0 - brightness) * baseContrast + baseBrightness;
brightness = (1. - brightness) * baseContrast + baseBrightness;
// Modes that don't fade glyphs set their actual brightness here
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
vec3 dist = texture2D(glyphTex, uv).rgb;
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() {
@@ -106,9 +106,9 @@ void main() {
vec2 uv = getUV(vUV);
// Unpack the values from the data textures
vec4 raindropData = volumetric ? vShine : texture2D( raindropState, uv);
vec4 symbolData = volumetric ? vSymbol : texture2D(symbolState, uv);
vec4 effectData = volumetric ? vEffect : texture2D(effectState, uv);
vec4 raindropData = volumetric ? vRaindrop : texture2D(raindropState, uv);
vec4 symbolData = volumetric ? vSymbol : texture2D( symbolState, uv);
vec4 effectData = volumetric ? vEffect : texture2D( effectState, uv);
vec2 brightness = getBrightness(raindropData.r, raindropData.g, effectData.r, effectData.g);
float symbol = getSymbol(uv, symbolData.r);
@@ -118,9 +118,9 @@ void main() {
vec3(
raindropData.g,
vec2(
1.0 - (raindropData.r * 3.0),
1.0 - (raindropData.r * 8.0)
) * (1.0 - raindropData.g)
1. - (raindropData.r * 3.),
1. - (raindropData.r * 8.)
) * (1. - raindropData.g)
) * symbol,
1.
);

View File

@@ -2,9 +2,9 @@ precision highp float;
// This shader is the star of the show.
// It writes falling rain to the channels of a data texture:
// R: brightness
// G: unused
// B: whether the cell is a "cursor"
// R: raindrop brightness
// G: whether the cell is a "cursor"
// B: unused
// A: unused
// Listen.

View File

@@ -10,7 +10,7 @@ uniform vec2 screenSize;
uniform float time, animationSpeed, forwardSpeed;
uniform bool volumetric;
varying vec2 vUV;
varying vec4 vShine, vSymbol, vEffect;
varying vec4 vRaindrop, vSymbol, vEffect;
varying float vDepth;
highp float rand( const in vec2 uv ) {
@@ -22,9 +22,9 @@ highp float rand( const in vec2 uv ) {
void main() {
vUV = (aPosition + aCorner) * quadSize;
vShine = texture2D(raindropState, aPosition * quadSize);
vSymbol = texture2D(symbolState, aPosition * quadSize);
vEffect = texture2D(effectState, aPosition * quadSize);
vRaindrop = texture2D(raindropState, aPosition * quadSize);
vSymbol = texture2D( symbolState, aPosition * quadSize);
vEffect = texture2D( effectState, aPosition * quadSize);
// Calculate the world space position
float quadDepth = 0.0;

View File

@@ -24,11 +24,11 @@ vec4 getBrightness(vec2 uv) {
void main() {
vec3 color = texture2D(stripes, vUV).rgb;
// Combine the texture and bloom
vec4 brightness = getBrightness(vUV);
// 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(
color * brightness.r

View File

@@ -34,8 +34,7 @@ fn getBrightness(uv : vec2<f32>) -> vec4<f32> {
var bgColor = textureSampleLevel( backgroundTex, linearSampler, uv, 0.0 ).rgb;
// Combine the texture and bloom, then blow it out to reveal more of the image
var brightness = getBrightness(uv).r;
brightness = pow(brightness, 1.5);
var brightness = getBrightness(uv);
textureStore(outputTex, coord, vec4<f32>(bgColor * brightness, 1.0));
textureStore(outputTex, coord, vec4<f32>(bgColor * (brightness.r + brightness.g * 2.0), 1.0));
}

View File

@@ -2,6 +2,7 @@ struct Config {
bloomStrength : f32,
ditherMagnitude : f32,
backgroundColor : vec3<f32>,
cursorColor : vec3<f32>
};
struct Palette {
@@ -54,17 +55,19 @@ fn getBrightness(uv : vec2<f32>) -> vec4<f32> {
var uv = vec2<f32>(coord) / vec2<f32>(screenSize);
var brightnessRGB = getBrightness(uv);
// Combine the texture and bloom
var brightness = brightnessRGB.r + brightnessRGB.g + brightnessRGB.b;
var brightness = getBrightness(uv);
// Dither: subtract a random value from the brightness
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
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
));
}

View File

@@ -14,23 +14,21 @@ struct Config {
brightnessThreshold : f32,
brightnessOverride : f32,
brightnessDecay : f32,
baseBrightness : f32,
baseContrast : f32,
cursorBrightness : f32,
cycleSpeed : f32,
cycleFrameSkip : i32,
fallSpeed : f32,
hasSun : i32,
hasThunder : i32,
raindropLength : f32,
rippleScale : f32,
rippleSpeed : f32,
rippleThickness : f32,
cycleStyle : i32,
rippleType : i32,
// render-specific properties
forwardSpeed : f32,
baseBrightness : f32,
baseContrast : f32,
glyphVerticalSpacing : f32,
glyphEdgeCrop : f32,
isPolar : i32,
@@ -38,6 +36,7 @@ struct Config {
slantScale : f32,
slantVec : vec2<f32>,
volumetric : i32,
isolateCursor : i32,
loops : i32,
highPassThreshold : f32,
};
@@ -58,6 +57,7 @@ struct Scene {
struct Cell {
raindrop : vec4<f32>,
symbol : vec4<f32>,
effect : vec4<f32>,
};
// 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
// 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.
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 columnSpeedOffset = randomFloat(vec2<f32>(glyphPos.x + 0.1, 0.0)) * 0.5 + 0.5;
if (bool(config.loops)) {
@@ -141,31 +142,16 @@ fn getRainTime(simTime : f32, glyphPos : vec2<f32>) -> f32 {
if (!bool(config.loops)) {
rainTime = wobble(rainTime);
}
return 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;
return fract(rainTime);
}
// Compute shader additional effects
fn applySunShowerBrightness(brightness : f32, screenPos : vec2<f32>) -> f32 {
if (brightness >= -4.0) {
return pow(fract(brightness * 0.5), 3.0) * screenPos.y * 1.5;
fn getThunder(simTime : f32, screenPos : vec2<f32>) -> f32 {
if (!bool(config.hasThunder)) {
return 0.0;
}
return brightness;
}
fn applyThunderBrightness(brightness : f32, simTime : f32, screenPos : vec2<f32>) -> f32 {
var thunderTime = simTime * 0.5;
var thunder = 1.0 - fract(wobble(thunderTime));
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 = clamp(thunder, 0.0, 1.0);
thunder *= pow(screenPos.y, 2.0) * 3.0;
return brightness + thunder;
thunder = clamp(thunder, 0.0, 1.0) * 10.0 * pow(screenPos.y, 2.0);
return thunder;
}
fn applyRippleEffect(effect : f32, simTime : f32, screenPos : vec2<f32>) -> f32 {
fn getRipple(simTime : f32, screenPos : vec2<f32>) -> f32 {
if (config.rippleType == -1) {
return effect;
return 0.0;
}
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;
if (rippleValue > 0.0 && rippleValue < config.rippleThickness) {
return effect + 0.75;
return 0.75;
}
return effect;
return 0.0;
}
// Compute shader main functions
fn computeShine (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
fn computeRaindrop (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, screenPos : vec2<f32>, previous : vec4<f32>) -> vec4<f32> {
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
if (!isFirstFrame) {
@@ -242,35 +209,31 @@ fn computeShine (simTime : f32, isFirstFrame : bool, glyphPos : vec2<f32>, scree
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;
}
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 previousAge = previous.g;
var resetGlyph = isFirstFrame;
if (bool(config.loops)) {
resetGlyph = resetGlyph || brightness < 0.0;
resetGlyph = resetGlyph || raindrop.r < 0.0;
}
if (resetGlyph) {
previousAge = randomFloat(screenPos + 0.5);
previousSymbol = floor(config.glyphSequenceLength * randomFloat(screenPos));
}
var cycleSpeed = getCycleSpeed(brightness);
var cycleSpeed = config.animationSpeed * config.cycleSpeed;
var age = previousAge;
var symbol = previousSymbol;
if (time.frames % config.cycleFrameSkip == 0) {
age += cycleSpeed * f32(config.cycleFrameSkip);
var advance = floor(age);
age = fract(age);
if (config.cycleStyle == 0) {
symbol = (symbol + advance) % config.glyphSequenceLength;
} else if (config.cycleStyle == 1 && advance > 0.) {
if (age > 1.0) {
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;
}
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) {
// 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 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.effect = computeEffect(simTime, isFirstFrame, glyphPos, screenPos, cell.effect, cell.raindrop);
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));
}
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> {
var symbolX = symbol % config.glyphTextureGridSize.x;
var symbolY = symbol / config.glyphTextureGridSize.x;
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 fn fragMain(input : VertOutput) -> FragOutput {
var volumetric = bool(config.volumetric);
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;
}
var uv = getUV(input.uv);
// Retrieve cell
var gridCoord : vec2<i32> = vec2<i32>(uv * config.gridSize);
var gridIndex = gridCoord.y * i32(config.gridSize.x) + gridCoord.x;
var cell = cells_RO.cells[gridIndex];
var symbolUV = getSymbolUV(i32(cell.symbol.r));
var brightness = cell.raindrop.r;
// Modes that don't fade glyphs set their actual brightness here
if (config.brightnessOverride > 0.0 && brightness > config.brightnessThreshold) {
brightness = config.brightnessOverride;
}
brightness = max(cell.raindrop.b * config.cursorBrightness, brightness);
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 brightness = getBrightness(
cell.raindrop.r,
cell.raindrop.g,
input.quadDepth,
cell.effect.r,
cell.effect.g
);
var symbol = getSymbol(uv, i32(cell.symbol.r));
var output : FragOutput;
if (bool(config.showDebugView)) {
output.color = vec4<f32>(
vec3<f32>(
cell.raindrop.b,
cell.raindrop.g,
vec2<f32>(
1.0 - (cell.raindrop.g * 3.0),
1.0 - (cell.raindrop.g * 10.0)
) * (1.0 - cell.raindrop.b)
) * alpha,
1.
1.0 - (cell.raindrop.r * 3.0),
1.0 - (cell.raindrop.r * 8.0)
) * (1.0 - cell.raindrop.g)
) * symbol,
1.0
);
} else {
output.color = vec4<f32>(brightness * alpha, 0., 0., 1.0);
output.color = vec4(brightness * symbol, 0.0, 0.0);
}
var highPassColor = output.color;

View File

@@ -2,6 +2,7 @@ struct Config {
bloomStrength : f32,
ditherMagnitude : f32,
backgroundColor : vec3<f32>,
cursorColor : vec3<f32>
};
struct Time {
@@ -52,10 +53,15 @@ fn getBrightness(uv : vec2<f32>) -> vec4<f32> {
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
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
));
}