Move brightness from the shine compute shader to the fragment shader and clean it up

This commit is contained in:
Rezmason
2022-09-15 21:10:33 -07:00
parent b0613f9bc3
commit 3b456baef9
5 changed files with 89 additions and 81 deletions

View File

@@ -1,18 +1,15 @@
TODO:
Test all the versions!
Reformulate the basis
https://buf.com/films/the-matrix-resurrections
Rain pass frag's output should match its debug view output
Move brightness and contrast logic to the later passes
The channels get individually blurred
Then the other passes use the red, green and blue channels for separate things
red: cursor
green: long tail
blue: short tail
Tune the colors
New config properties
The post-bloom passes should apply brightness, contrast, and handle effects
r: cursor, g: long tail, b: short tail, a: maybe effects data goes here? we'll see
But, the texture format beyond the rain pass is 8-bit. Something to watch out for.
Create new, simpler default colorizer
Config properties: Cursor color, long tail color, short tail color
Figure out what to do with the thunder and ripple
Tune ALL the versions!
Find a way to support the old stuff?
Migrate to WebGPU
@@ -27,7 +24,6 @@ Audio system
Randomize pitch a little?
Playdate version
Separate glyph cycling from brightness
Audio system
Falling sound
Launch sound

View File

@@ -150,11 +150,11 @@ const versions = {
font: "coptic",
bloomStrength: 1,
highPassThreshold: 0,
cycleSpeed: 0.05,
cycleSpeed: 0.005,
baseBrightness: -1.3,
baseContrast: 2,
brightnessDecay: 0.05,
fallSpeed: 0.04,
fallSpeed: 0.02,
isPolar: true,
rippleTypeName: "circle",
rippleSpeed: 0.1,
@@ -176,7 +176,7 @@ const versions = {
baseContrast: 1.17,
highPassThreshold: 0,
numColumns: 70,
cycleSpeed: 0.05,
cycleSpeed: 0.03,
bloomStrength: 0.7,
fallSpeed: 0.3,
paletteEntries: [
@@ -218,7 +218,7 @@ const versions = {
font: "resurrections",
numColumns: 20,
fallSpeed: 0.35,
cycleSpeed: 0.3,
cycleSpeed: 0.04,
glyphEdgeCrop: 0.1,
ditherMagnitude: 0,
paletteEntries: [
@@ -240,7 +240,7 @@ const versions = {
["3d"]: {
volumetric: true,
fallSpeed: 0.5,
cycleSpeed: 0.35,
cycleSpeed: 0.03,
baseBrightness: -0.9,
baseContrast: 1.5,
raindropLength: 0.3,

View File

@@ -59,7 +59,7 @@ export default ({ regl, config, lkg }) => {
const rainPassShine = loadText("shaders/glsl/rainPass.shine.frag.glsl");
const shineUniforms = {
...commonUniforms,
...extractEntries(config, ["baseBrightness", "baseContrast", "brightnessDecay", "fallSpeed", "raindropLength", "loops"]),
...extractEntries(config, ["brightnessDecay", "fallSpeed", "raindropLength", "loops"]),
};
const shine = regl({
frag: regl.prop("frag"),
@@ -126,6 +126,8 @@ export default ({ regl, config, lkg }) => {
"forwardSpeed",
"glyphVerticalSpacing",
// fragment
"baseBrightness",
"baseContrast",
"brightnessThreshold",
"brightnessOverride",
"cursorBrightness",

View File

@@ -8,6 +8,7 @@ uniform sampler2D shineState, symbolState, effectState;
uniform float numColumns, numRows;
uniform sampler2D glyphTex;
uniform float glyphHeightToWidth, glyphSequenceLength, glyphEdgeCrop;
uniform float baseContrast, baseBrightness;
uniform float brightnessOverride, brightnessThreshold, cursorBrightness;
uniform vec2 glyphTextureGridSize;
uniform vec2 slantVec;
@@ -29,84 +30,99 @@ float modI(float a, float b) {
return floor(m + 0.5);
}
vec2 getSymbolUV(float symbol) {
float symbolX = modI(symbol, glyphTextureGridSize.x);
float symbolY = (symbol - symbolX) / glyphTextureGridSize.x;
symbolY = glyphTextureGridSize.y - symbolY - 1.;
return vec2(symbolX, symbolY);
}
vec2 getUV(vec2 uv) {
void main() {
vec2 uv = vUV;
// In normal mode, derives the current glyph and UV from vUV
if (!volumetric) {
if (isPolar) {
// Curved space that makes letters appear to radiate from up above
uv -= 0.5;
uv *= 0.5;
uv.y -= 0.5;
float radius = length(uv);
float angle = atan(uv.y, uv.x) / (2. * PI) + 0.5;
uv = vec2(fract(angle * 4. - 0.5), 1.5 * (1. - sqrt(radius)));
} else {
// Applies the slant and scales space so the viewport is fully covered
uv = vec2(
(uv.x - 0.5) * slantVec.x + (uv.y - 0.5) * slantVec.y,
(uv.y - 0.5) * slantVec.x - (uv.x - 0.5) * slantVec.y
) * slantScale + 0.5;
}
uv.y /= glyphHeightToWidth;
if (volumetric) {
return uv;
}
// Unpack the values from the data textures
vec4 shine = volumetric ? vShine : texture2D(shineState, uv);
vec4 symbol = volumetric ? vSymbol : texture2D(symbolState, uv);
vec4 effect = volumetric ? vEffect : texture2D(effectState, uv);
vec2 symbolUV = getSymbolUV(symbol.r);
if (isPolar) {
// Curved space that makes letters appear to radiate from up above
uv -= 0.5;
uv *= 0.5;
uv.y -= 0.5;
float radius = length(uv);
float angle = atan(uv.y, uv.x) / (2. * PI) + 0.5;
uv = vec2(fract(angle * 4. - 0.5), 1.5 * (1. - sqrt(radius)));
} else {
// Applies the slant and scales space so the viewport is fully covered
uv = vec2(
(uv.x - 0.5) * slantVec.x + (uv.y - 0.5) * slantVec.y,
(uv.y - 0.5) * slantVec.x - (uv.x - 0.5) * slantVec.y
) * slantScale + 0.5;
}
float brightness = shine.r;
uv.y /= glyphHeightToWidth;
return uv;
}
float getBrightness(float brightness, float cursor, float multipliedEffects, float addedEffects) {
brightness = (1.0 - brightness) * baseContrast + baseBrightness;
// Modes that don't fade glyphs set their actual brightness here
if (brightnessOverride > 0. && brightness > brightnessThreshold) {
brightness = brightnessOverride;
}
brightness *= effect.r; // multiplied effects
brightness += effect.g; // added effects
brightness = max(shine.b * cursorBrightness, brightness);
brightness *= multipliedEffects;
brightness += addedEffects;
brightness = max(cursor * cursorBrightness, brightness);
// In volumetric mode, distant glyphs are dimmer
if (volumetric && !showDebugView) {
brightness = brightness * min(1., vDepth);
}
return brightness;
}
vec2 getSymbolUV(float index) {
float symbolX = modI(index, glyphTextureGridSize.x);
float symbolY = (index - symbolX) / glyphTextureGridSize.x;
symbolY = glyphTextureGridSize.y - symbolY - 1.;
return vec2(symbolX, symbolY);
}
float getSymbol(vec2 uv, float index) {
// resolve UV to cropped position of glyph in MSDF texture
vec2 glyphUV = fract(uv * vec2(numColumns, numRows));
glyphUV -= 0.5;
glyphUV *= clamp(1. - glyphEdgeCrop, 0., 1.);
glyphUV += 0.5;
vec2 msdfUV = (glyphUV + symbolUV) / glyphTextureGridSize;
uv = fract(uv * vec2(numColumns, numRows));
uv -= 0.5;
uv *= clamp(1. - glyphEdgeCrop, 0., 1.);
uv += 0.5;
uv = (uv + getSymbolUV(index)) / glyphTextureGridSize;
// MSDF: calculate brightness of fragment based on distance to shape
vec3 dist = texture2D(glyphTex, msdfUV).rgb;
vec3 dist = texture2D(glyphTex, uv).rgb;
float sigDist = median3(dist) - 0.5;
float alpha = clamp(sigDist/fwidth(sigDist) + 0.5, 0., 1.);
return clamp(sigDist/fwidth(sigDist) + 0.5, 0., 1.);
}
void main() {
vec2 uv = getUV(vUV);
// Unpack the values from the data textures
vec4 shineData = volumetric ? vShine : texture2D( shineState, uv);
vec4 symbolData = volumetric ? vSymbol : texture2D(symbolState, uv);
vec4 effectData = volumetric ? vEffect : texture2D(effectState, uv);
float brightness = getBrightness(shineData.r, shineData.g, effectData.r, effectData.g);
float symbol = getSymbol(uv, symbolData.r);
if (showDebugView) {
gl_FragColor = vec4(
vec3(
shine.b,
shineData.g,
vec2(
1.0 - (shine.g * 3.0),
1.0 - (shine.g * 10.0)
) * (1.0 - shine.b)
) * alpha,
1.0 - (shineData.r * 3.0),
1.0 - (shineData.r * 8.0)
) * (1.0 - shineData.g)
) * symbol,
1.
);
} else {
gl_FragColor = vec4(brightness * alpha, 0., 0., 1.);
gl_FragColor = vec4(brightness * symbol, 0., 0., 1.);
}
}

View File

@@ -42,11 +42,10 @@ float wobble(float x) {
return x + 0.3 * sin(SQRT_2 * x) + 0.2 * sin(SQRT_5 * x);
}
// 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.
float getRainTime(float simTime, vec2 glyphPos) {
// It's also why those bright areas are truncated into raindrops.
float getBrightness(float simTime, vec2 glyphPos) {
float columnTimeOffset = randomFloat(vec2(glyphPos.x, 0.)) * 1000.;
float columnSpeedOffset = randomFloat(vec2(glyphPos.x + 0.1, 0.)) * 0.5 + 0.5;
if (loops) {
@@ -57,20 +56,15 @@ float getRainTime(float simTime, vec2 glyphPos) {
if (!loops) {
rainTime = wobble(rainTime);
}
return rainTime;
}
float getBrightness(float rainTime) {
return (1. - fract(rainTime)) * baseContrast + baseBrightness;
return fract(rainTime);
}
// Main function
vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenPos, vec4 previous) {
float rainTime = getRainTime(simTime, glyphPos);
float rainTimeBelow = getRainTime(simTime, glyphPos + vec2(0., -1.));
float cursor = fract(rainTime) < fract(rainTimeBelow) ? 1.0 : 0.0;
float brightness = getBrightness(rainTime);
float brightness = getBrightness(simTime, glyphPos);
float brightnessBelow = getBrightness(simTime, glyphPos + vec2(0., -1.));
float cursor = brightness < brightnessBelow ? 1.0 : 0.0;
// Blend the glyph's brightness with its previous brightness, so it winks on and off organically
if (!isFirstFrame) {
@@ -78,7 +72,7 @@ vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenP
brightness = mix(previousBrightness, brightness, brightnessDecay);
}
vec4 result = vec4(brightness, fract(rainTime), cursor, 0.0);
vec4 result = vec4(brightness, cursor, 0.0, 0.0);
return result;
}