mirror of
https://github.com/Rezmason/matrix.git
synced 2026-04-18 06:09:30 -07:00
Split the rain pass's compute shader in two, with one governing brightness and the other governing glyph cycling. This allows glyphs to randomly cycle properly, and leaves room to store new properties.
This commit is contained in:
@@ -4,10 +4,11 @@
|
||||
#endif
|
||||
precision lowp float;
|
||||
|
||||
uniform sampler2D state;
|
||||
uniform sampler2D shineState, symbolState;
|
||||
uniform float numColumns, numRows;
|
||||
uniform sampler2D glyphTex;
|
||||
uniform float glyphHeightToWidth, glyphSequenceLength, glyphEdgeCrop;
|
||||
uniform float brightnessOverride, brightnessThreshold, cursorBrightness;
|
||||
uniform vec2 glyphTextureGridSize;
|
||||
uniform vec2 slantVec;
|
||||
uniform float slantScale;
|
||||
@@ -17,7 +18,7 @@ uniform bool volumetric;
|
||||
|
||||
varying vec2 vUV;
|
||||
varying vec3 vChannel;
|
||||
varying vec4 vGlyph;
|
||||
varying vec4 vShine, vSymbol;
|
||||
varying float vDepth;
|
||||
|
||||
float median3(vec3 i) {
|
||||
@@ -29,11 +30,10 @@ float modI(float a, float b) {
|
||||
return floor(m + 0.5);
|
||||
}
|
||||
|
||||
vec2 getSymbolUV(float glyphCycle) {
|
||||
float symbol = floor((glyphSequenceLength) * glyphCycle);
|
||||
vec2 getSymbolUV(float symbol) {
|
||||
float symbolX = modI(symbol, glyphTextureGridSize.x);
|
||||
float symbolY = (symbol - symbolX) / glyphTextureGridSize.x;
|
||||
symbolY = glyphTextureGridSize.y - symbolY - 1.0;
|
||||
symbolY = glyphTextureGridSize.y - symbolY - 1.;
|
||||
return vec2(symbolX, symbolY);
|
||||
}
|
||||
|
||||
@@ -61,38 +61,45 @@ void main() {
|
||||
uv.y /= glyphHeightToWidth;
|
||||
}
|
||||
|
||||
// Unpack the values from the data texture
|
||||
vec4 glyph = volumetric ? vGlyph : texture2D(state, uv);
|
||||
float brightness = glyph.r;
|
||||
vec2 symbolUV = getSymbolUV(glyph.g);
|
||||
float effect = glyph.a;
|
||||
// Unpack the values from the data textures
|
||||
vec4 shine = volumetric ? vShine : texture2D(shineState, uv);
|
||||
vec4 symbol = volumetric ? vSymbol : texture2D(symbolState, uv);
|
||||
vec2 symbolUV = getSymbolUV(symbol.r);
|
||||
|
||||
brightness = max(effect, brightness);
|
||||
float brightness = shine.r;
|
||||
|
||||
// Modes that don't fade glyphs set their actual brightness here
|
||||
if (brightnessOverride > 0. && brightness > brightnessThreshold) {
|
||||
brightness = brightnessOverride;
|
||||
}
|
||||
|
||||
brightness = max(shine.b * cursorBrightness, brightness);
|
||||
brightness = max(shine.a, brightness);
|
||||
// In volumetric mode, distant glyphs are dimmer
|
||||
if (volumetric) {
|
||||
brightness = brightness * min(1.0, vDepth);
|
||||
brightness = brightness * min(1., vDepth);
|
||||
}
|
||||
|
||||
// resolve UV to cropped position of glyph in MSDF texture
|
||||
vec2 glyphUV = fract(uv * vec2(numColumns, numRows));
|
||||
glyphUV -= 0.5;
|
||||
glyphUV *= clamp(1.0 - glyphEdgeCrop, 0.0, 1.0);
|
||||
glyphUV *= clamp(1. - glyphEdgeCrop, 0., 1.);
|
||||
glyphUV += 0.5;
|
||||
vec2 msdfUV = (glyphUV + symbolUV) / glyphTextureGridSize;
|
||||
|
||||
// MSDF: calculate brightness of fragment based on distance to shape
|
||||
vec3 dist = texture2D(glyphTex, msdfUV).rgb;
|
||||
float sigDist = median3(dist) - 0.5;
|
||||
float alpha = clamp(sigDist/fwidth(sigDist) + 0.5, 0.0, 1.0);
|
||||
float alpha = clamp(sigDist/fwidth(sigDist) + 0.5, 0., 1.);
|
||||
|
||||
if (showComputationTexture) {
|
||||
vec4 debugColor = vec4(glyph.r - alpha, glyph.g * alpha, glyph.a - alpha, 1.0);
|
||||
vec4 debugColor = vec4(shine.r - alpha, shine.g * alpha, shine.a - alpha, 1.);
|
||||
if (volumetric) {
|
||||
debugColor.g = debugColor.g * 0.9 + 0.1;
|
||||
}
|
||||
gl_FragColor = debugColor;
|
||||
} else {
|
||||
gl_FragColor = vec4(vChannel * brightness * alpha, 1.0);
|
||||
gl_FragColor = vec4(vChannel * brightness * alpha, 1.);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
precision highp float;
|
||||
|
||||
// This shader is the star of the show. For each glyph, it determines its:
|
||||
// This shader is the star of the show.
|
||||
// It writes falling rain to four channels of a data texture:
|
||||
// R: brightness
|
||||
// G: progress through the glyph sequence
|
||||
// B: unused!
|
||||
// A: additional brightness for effects
|
||||
// G: unused
|
||||
// B: whether the cell is a "cursor"
|
||||
// A: some other effect, such as a ripple
|
||||
|
||||
// Listen.
|
||||
// I understand if this shader looks confusing. Please don't be discouraged!
|
||||
@@ -15,19 +16,17 @@ precision highp float;
|
||||
#define SQRT_2 1.4142135623730951
|
||||
#define SQRT_5 2.23606797749979
|
||||
|
||||
uniform sampler2D previousState;
|
||||
uniform sampler2D previousShineState;
|
||||
uniform float numColumns, numRows;
|
||||
uniform float time, tick, cycleFrameSkip;
|
||||
uniform float animationSpeed, fallSpeed, cycleSpeed;
|
||||
uniform float time, tick;
|
||||
uniform float animationSpeed, fallSpeed;
|
||||
|
||||
uniform bool hasSun, hasThunder, loops;
|
||||
uniform bool showComputationTexture;
|
||||
uniform float brightnessOverride, brightnessThreshold, brightnessDecay;
|
||||
uniform float brightnessDecay;
|
||||
uniform float baseContrast, baseBrightness;
|
||||
uniform float raindropLength, glyphHeightToWidth, glyphSequenceLength;
|
||||
uniform int cycleStyle, rippleType;
|
||||
uniform float raindropLength, glyphHeightToWidth;
|
||||
uniform int rippleType;
|
||||
uniform float rippleScale, rippleSpeed, rippleThickness;
|
||||
uniform float cursorEffectThreshold;
|
||||
|
||||
// Helper functions for generating randomness, borrowed from elsewhere
|
||||
|
||||
@@ -67,16 +66,6 @@ float getBrightness(float rainTime) {
|
||||
return value * baseContrast + baseBrightness;
|
||||
}
|
||||
|
||||
float getCycleSpeed(float rainTime, float brightness) {
|
||||
float localCycleSpeed = 0.;
|
||||
if (cycleStyle == 0 && brightness > 0.) {
|
||||
localCycleSpeed = pow(1. - brightness, 4.);
|
||||
} else if (cycleStyle == 1) {
|
||||
localCycleSpeed = fract(rainTime);
|
||||
}
|
||||
return animationSpeed * cycleSpeed * localCycleSpeed;
|
||||
}
|
||||
|
||||
// Additional effects
|
||||
|
||||
float applySunShowerBrightness(float brightness, vec2 screenPos) {
|
||||
@@ -131,77 +120,44 @@ float applyRippleEffect(float effect, float simTime, vec2 screenPos) {
|
||||
return effect;
|
||||
}
|
||||
|
||||
float applyCursorEffect(float effect, float brightness) {
|
||||
if (brightness >= cursorEffectThreshold) {
|
||||
effect = 1.;
|
||||
}
|
||||
return effect;
|
||||
}
|
||||
|
||||
// Main function
|
||||
|
||||
vec4 computeResult(bool isFirstFrame, vec4 previousResult, vec2 glyphPos, vec2 screenPos) {
|
||||
vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenPos, vec4 previous, vec4 previousBelow) {
|
||||
|
||||
// Determine the glyph's local time.
|
||||
float simTime = time * animationSpeed;
|
||||
float rainTime = getRainTime(simTime, glyphPos);
|
||||
|
||||
// Rain time is the backbone of this effect.
|
||||
|
||||
// Determine the glyph's brightness.
|
||||
float previousBrightness = previousResult.r;
|
||||
float brightness = getBrightness(rainTime);
|
||||
if (hasSun) {
|
||||
brightness = applySunShowerBrightness(brightness, screenPos);
|
||||
}
|
||||
if (hasThunder) {
|
||||
brightness = applyThunderBrightness(brightness, simTime, screenPos);
|
||||
}
|
||||
|
||||
// Determine the glyph's cycle— the percent this glyph has progressed through the glyph sequence
|
||||
float previousCycle = previousResult.g;
|
||||
bool resetGlyph = isFirstFrame;
|
||||
if (loops) {
|
||||
resetGlyph = resetGlyph || previousBrightness <= 0.;
|
||||
}
|
||||
if (resetGlyph) {
|
||||
previousCycle = showComputationTexture ? 0. : randomFloat(screenPos);
|
||||
}
|
||||
float localCycleSpeed = getCycleSpeed(rainTime, brightness);
|
||||
float cycle = previousCycle;
|
||||
if (mod(tick, cycleFrameSkip) == 0.) {
|
||||
cycle = fract(previousCycle + 0.005 * localCycleSpeed * cycleFrameSkip);
|
||||
}
|
||||
if (hasSun) brightness = applySunShowerBrightness(brightness, screenPos);
|
||||
if (hasThunder) brightness = applyThunderBrightness(brightness, simTime, screenPos);
|
||||
|
||||
// Determine the glyph's effect— the amount the glyph lights up for other reasons
|
||||
float effect = 0.;
|
||||
effect = applyRippleEffect(effect, simTime, screenPos); // Round or square ripples across the grid
|
||||
effect = applyCursorEffect(effect, brightness); // The bright glyphs at the "bottom" of raindrops
|
||||
|
||||
// Modes that don't fade glyphs set their actual brightness here
|
||||
if (brightnessOverride > 0. && brightness > brightnessThreshold) {
|
||||
brightness = brightnessOverride;
|
||||
}
|
||||
float previousBrightnessBelow = previousBelow.r;
|
||||
float cursor = brightness > previousBrightnessBelow ? 1.0 : 0.0;
|
||||
|
||||
// Blend the glyph's brightness with its previous brightness, so it winks on and off organically
|
||||
if (!isFirstFrame) {
|
||||
float previousBrightness = previous.r;
|
||||
brightness = mix(previousBrightness, brightness, brightnessDecay);
|
||||
}
|
||||
|
||||
vec4 result = vec4(brightness, cycle, 0.0, effect);
|
||||
|
||||
// Better use of the alpha channel, for demonstrating how the glyph cycle works
|
||||
if (showComputationTexture) {
|
||||
result.a = min(1., localCycleSpeed);
|
||||
}
|
||||
|
||||
vec4 result = vec4(brightness, 0., cursor, effect);
|
||||
return result;
|
||||
}
|
||||
|
||||
void main() {
|
||||
float simTime = time * animationSpeed;
|
||||
bool isFirstFrame = tick <= 1.;
|
||||
vec2 glyphPos = gl_FragCoord.xy;
|
||||
vec2 screenPos = glyphPos / vec2(numColumns, numRows);
|
||||
vec4 previousResult = texture2D( previousState, screenPos );
|
||||
gl_FragColor = computeResult(isFirstFrame, previousResult, glyphPos, screenPos);
|
||||
vec4 previous = texture2D( previousShineState, screenPos );
|
||||
vec4 previousBelow = texture2D( previousShineState, screenPos + vec2(0., -1. / numRows));
|
||||
gl_FragColor = computeResult(simTime, isFirstFrame, glyphPos, screenPos, previous, previousBelow);
|
||||
}
|
||||
80
shaders/glsl/rainPass.symbol.frag.glsl
Normal file
80
shaders/glsl/rainPass.symbol.frag.glsl
Normal file
@@ -0,0 +1,80 @@
|
||||
precision highp float;
|
||||
|
||||
// This shader governs the glyphs appearing in the rain.
|
||||
// It writes each glyph's state to four channels of a data texture:
|
||||
// R: symbol
|
||||
// G: age
|
||||
// B: unused
|
||||
// A: unused
|
||||
|
||||
#define PI 3.14159265359
|
||||
|
||||
uniform sampler2D previousSymbolState, shineState;
|
||||
uniform float numColumns, numRows;
|
||||
uniform float time, tick, cycleFrameSkip;
|
||||
uniform float animationSpeed, cycleSpeed;
|
||||
uniform bool loops, showComputationTexture;
|
||||
uniform float glyphSequenceLength;
|
||||
uniform int cycleStyle;
|
||||
|
||||
// Helper functions for generating randomness, borrowed from elsewhere
|
||||
|
||||
highp float randomFloat( const in vec2 uv ) {
|
||||
const highp float a = 12.9898, b = 78.233, c = 43758.5453;
|
||||
highp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );
|
||||
return fract(sin(sn) * c);
|
||||
}
|
||||
|
||||
// Core functions
|
||||
|
||||
float getCycleSpeed(float brightness) {
|
||||
float localCycleSpeed = 1.;
|
||||
if (cycleStyle == 0 && brightness > 0.) {
|
||||
localCycleSpeed = pow(1. - brightness, 4.);
|
||||
}
|
||||
return animationSpeed * cycleSpeed * localCycleSpeed;
|
||||
}
|
||||
|
||||
// Main function
|
||||
|
||||
vec4 computeResult(float simTime, bool isFirstFrame, vec2 glyphPos, vec2 screenPos, vec4 previous, vec4 shine) {
|
||||
|
||||
float brightness = shine.r;
|
||||
|
||||
float previousSymbol = previous.r;
|
||||
float previousAge = previous.g;
|
||||
bool resetGlyph = isFirstFrame;
|
||||
if (loops) {
|
||||
resetGlyph = resetGlyph || brightness <= 0.;
|
||||
}
|
||||
if (resetGlyph) {
|
||||
previousAge = randomFloat(screenPos + vec2(0.5));
|
||||
previousSymbol = floor(glyphSequenceLength * randomFloat(screenPos));
|
||||
}
|
||||
float cycleSpeed = getCycleSpeed(brightness);
|
||||
float age = previousAge;
|
||||
float symbol = previousSymbol;
|
||||
if (mod(tick, cycleFrameSkip) == 0.) {
|
||||
age += cycleSpeed * cycleFrameSkip;
|
||||
float advance = floor(age);
|
||||
age = fract(age);
|
||||
if (cycleStyle == 0) {
|
||||
symbol = mod(symbol + advance, glyphSequenceLength);
|
||||
} else if (cycleStyle == 1 && advance > 0.) {
|
||||
symbol = floor(glyphSequenceLength * randomFloat(screenPos + vec2(simTime)));
|
||||
}
|
||||
}
|
||||
|
||||
vec4 result = vec4(symbol, age, 0., 0.);
|
||||
return result;
|
||||
}
|
||||
|
||||
void main() {
|
||||
float simTime = time * animationSpeed;
|
||||
bool isFirstFrame = tick <= 1.;
|
||||
vec2 glyphPos = gl_FragCoord.xy;
|
||||
vec2 screenPos = glyphPos / vec2(numColumns, numRows);
|
||||
vec4 previous = texture2D( previousSymbolState, screenPos );
|
||||
vec4 shine = texture2D( shineState, screenPos );
|
||||
gl_FragColor = computeResult(simTime, isFirstFrame, glyphPos, screenPos, previous, shine);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#define PI 3.14159265359
|
||||
precision lowp float;
|
||||
attribute vec2 aPosition, aCorner;
|
||||
uniform sampler2D state;
|
||||
uniform sampler2D shineState, symbolState;
|
||||
uniform float density;
|
||||
uniform vec2 quadSize;
|
||||
uniform float glyphHeightToWidth, glyphVerticalSpacing;
|
||||
@@ -11,7 +11,7 @@ uniform float time, animationSpeed, forwardSpeed;
|
||||
uniform bool volumetric;
|
||||
varying vec2 vUV;
|
||||
varying vec3 vChannel;
|
||||
varying vec4 vGlyph;
|
||||
varying vec4 vShine, vSymbol;
|
||||
varying float vDepth;
|
||||
|
||||
highp float rand( const in vec2 uv ) {
|
||||
@@ -23,7 +23,8 @@ highp float rand( const in vec2 uv ) {
|
||||
void main() {
|
||||
|
||||
vUV = (aPosition + aCorner) * quadSize;
|
||||
vGlyph = texture2D(state, aPosition * quadSize);
|
||||
vShine = texture2D(shineState, aPosition * quadSize);
|
||||
vSymbol = texture2D(symbolState, aPosition * quadSize);
|
||||
|
||||
// Calculate the world space position
|
||||
float quadDepth = 0.0;
|
||||
|
||||
Reference in New Issue
Block a user