mirror of
https://github.com/Rezmason/matrix.git
synced 2026-04-15 21:09:29 -07:00
Added some documentation, cleaned up some code, fleshed out the remaining work to make the project a little easier for newcomers to approach
This commit is contained in:
21
TODO.txt
21
TODO.txt
@@ -1,5 +1,21 @@
|
||||
TODO:
|
||||
|
||||
|
||||
Improve forkability
|
||||
Document every variable in config.js
|
||||
Document every variable, method, and section of the main function in compute.frag
|
||||
Maybe rewrite it? Make the time based stuff easier to read?
|
||||
Document resurrectionPass
|
||||
Label it a WIP
|
||||
List intended characteristics
|
||||
Comment makePalette
|
||||
|
||||
Write a document (and include images) that explains the underlying principle of the rain pass
|
||||
|
||||
Create interactive controls?
|
||||
Is the pipeline stuff just too bizarre?
|
||||
|
||||
|
||||
Resurrection
|
||||
Modified glyph order?
|
||||
|
||||
@@ -14,6 +30,11 @@ Resurrection
|
||||
|
||||
New glyphs?
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Experiment with varying the colors in the palette pass
|
||||
Maybe a separate palette for the non-bloom
|
||||
Maybe dim and widen the bloom
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { loadText, makePassFBO, makePyramid, resizePyramid, makePass } from "./utils.js";
|
||||
|
||||
// The bloom pass is basically an added high-pass blur.
|
||||
// The blur approximation is the sum of a pyramid of downscaled textures.
|
||||
|
||||
const pyramidHeight = 5;
|
||||
const levelStrengths = Array(pyramidHeight)
|
||||
@@ -9,8 +10,10 @@ const levelStrengths = Array(pyramidHeight)
|
||||
.reverse();
|
||||
|
||||
export default (regl, config, inputs) => {
|
||||
const enabled = config.bloomSize > 0 && config.bloomStrength > 0;
|
||||
const { bloomStrength, bloomSize, highPassThreshold } = config;
|
||||
const enabled = bloomSize > 0 && bloomStrength > 0;
|
||||
|
||||
// If there's no bloom to apply, return a no-op pass with an empty bloom texture
|
||||
if (!enabled) {
|
||||
return makePass({
|
||||
primary: inputs.primary,
|
||||
@@ -18,16 +21,14 @@ export default (regl, config, inputs) => {
|
||||
});
|
||||
}
|
||||
|
||||
const { bloomStrength, highPassThreshold } = config;
|
||||
|
||||
// Build three pyramids of FBOs, one for each step in the process
|
||||
const highPassPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
|
||||
const hBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
|
||||
const vBlurPyramid = makePyramid(regl, pyramidHeight, config.useHalfFloat);
|
||||
const output = makePassFBO(regl, config.useHalfFloat);
|
||||
|
||||
const highPassFrag = loadText("shaders/highPass.frag");
|
||||
|
||||
// The high pass restricts the blur to bright things in our input texture.
|
||||
const highPassFrag = loadText("shaders/highPass.frag");
|
||||
const highPass = regl({
|
||||
frag: regl.prop("frag"),
|
||||
uniforms: {
|
||||
@@ -39,7 +40,8 @@ export default (regl, config, inputs) => {
|
||||
|
||||
// A 2D gaussian blur is just a 1D blur done horizontally, then done vertically.
|
||||
// The FBO pyramid's levels represent separate levels of detail;
|
||||
// by blurring them all, this 3x1 blur approximates a more complex gaussian.
|
||||
// by blurring them all, this basic blur approximates a more complex gaussian:
|
||||
// https://software.intel.com/en-us/articles/compute-shader-hdr-and-bloom
|
||||
|
||||
const blurFrag = loadText("shaders/blur.frag");
|
||||
const blur = regl({
|
||||
@@ -53,8 +55,8 @@ export default (regl, config, inputs) => {
|
||||
framebuffer: regl.prop("fbo"),
|
||||
});
|
||||
|
||||
// The pyramid of textures gets flattened onto the source texture.
|
||||
const flattenPyramid = regl({
|
||||
// The pyramid of textures gets flattened (summed) into a final blurry "bloom" texture
|
||||
const sumPyramid = regl({
|
||||
frag: `
|
||||
precision mediump float;
|
||||
varying vec2 vUV;
|
||||
@@ -88,15 +90,15 @@ export default (regl, config, inputs) => {
|
||||
blur({ fbo: vBlurFBO, frag: blurFrag.text(), tex: hBlurFBO, direction: [0, 1] });
|
||||
}
|
||||
|
||||
flattenPyramid();
|
||||
sumPyramid();
|
||||
},
|
||||
(w, h) => {
|
||||
// The blur pyramids can be lower resolution than the screen.
|
||||
resizePyramid(highPassPyramid, w, h, config.bloomSize);
|
||||
resizePyramid(hBlurPyramid, w, h, config.bloomSize);
|
||||
resizePyramid(vBlurPyramid, w, h, config.bloomSize);
|
||||
resizePyramid(highPassPyramid, w, h, bloomSize);
|
||||
resizePyramid(hBlurPyramid, w, h, bloomSize);
|
||||
resizePyramid(vBlurPyramid, w, h, bloomSize);
|
||||
output.resize(w, h);
|
||||
},
|
||||
[highPassFrag.laoded, blurFrag.loaded]
|
||||
[highPassFrag.loaded, blurFrag.loaded]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { loadImage, loadText, makePassFBO, makePass } from "./utils.js";
|
||||
|
||||
// Multiplies the rendered rain and bloom by a loaded in image
|
||||
|
||||
const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/0/0a/Flammarion_Colored.jpg";
|
||||
|
||||
export default (regl, config, inputs) => {
|
||||
|
||||
21
js/main.js
21
js/main.js
@@ -1,5 +1,7 @@
|
||||
import { makeFullScreenQuad, makePipeline } from "./utils.js";
|
||||
import makeConfig from "./config.js";
|
||||
|
||||
import makeConfig from "./config.js"; // The settings of the effect, specified in the URL query params
|
||||
|
||||
import makeRain from "./rainPass.js";
|
||||
import makeBloomPass from "./bloomPass.js";
|
||||
import makePalettePass from "./palettePass.js";
|
||||
@@ -34,12 +36,10 @@ const effects = {
|
||||
};
|
||||
|
||||
const config = makeConfig(window.location.search);
|
||||
const resolution = config.resolution;
|
||||
const effect = config.effect in effects ? config.effect : "plain";
|
||||
|
||||
const resize = () => {
|
||||
canvas.width = Math.ceil(canvas.clientWidth * resolution);
|
||||
canvas.height = Math.ceil(canvas.clientHeight * resolution);
|
||||
canvas.width = Math.ceil(canvas.clientWidth * config.resolution);
|
||||
canvas.height = Math.ceil(canvas.clientHeight * config.resolution);
|
||||
};
|
||||
window.onresize = resize;
|
||||
resize();
|
||||
@@ -49,12 +49,11 @@ const dimensions = { width: 1, height: 1 };
|
||||
document.body.onload = async () => {
|
||||
// All this takes place in a full screen quad.
|
||||
const fullScreenQuad = makeFullScreenQuad(regl);
|
||||
|
||||
const bloomPass = effect === "none" ? null : makeBloomPass;
|
||||
const pipeline = makePipeline([makeRain, bloomPass, effects[effect]], (p) => p.outputs, regl, config);
|
||||
const uniforms = { tex: pipeline[pipeline.length - 1].outputs.primary };
|
||||
const drawToScreen = regl({ uniforms });
|
||||
await Promise.all(pipeline.map(({ ready }) => ready));
|
||||
const effectName = config.effect in effects ? config.effect : "plain";
|
||||
const pipeline = makePipeline([makeRain, makeBloomPass, effects[effectName]], (p) => p.outputs, regl, config);
|
||||
const screenUniforms = { tex: pipeline[pipeline.length - 1].outputs.primary };
|
||||
const drawToScreen = regl({ uniforms: screenUniforms });
|
||||
await Promise.all(pipeline.map((step) => step.ready));
|
||||
const tick = regl.frame(({ viewportWidth, viewportHeight }) => {
|
||||
// tick.cancel();
|
||||
if (dimensions.width !== viewportWidth || dimensions.height !== viewportHeight) {
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { loadText, make1DTexture, makePassFBO, makePass } from "./utils.js";
|
||||
|
||||
// Maps the brightness of the rendered rain and bloom to colors
|
||||
// in a 1D gradient palette texture generated from the passed-in color sequence
|
||||
|
||||
// This shader introduces noise into the renders, to avoid banding
|
||||
|
||||
const colorToRGB = ([hue, saturation, lightness]) => {
|
||||
const a = saturation * Math.min(lightness, 1 - lightness);
|
||||
const f = (n) => {
|
||||
|
||||
105
js/rainPass.js
105
js/rainPass.js
@@ -20,64 +20,32 @@ const brVert = [1, 1];
|
||||
const quadVertices = [tlVert, trVert, brVert, tlVert, brVert, blVert];
|
||||
|
||||
export default (regl, config) => {
|
||||
// The volumetric mode multiplies the number of columns
|
||||
// to reach the desired density, and then overlaps them
|
||||
const volumetric = config.volumetric;
|
||||
const density = volumetric && config.effect !== "none" ? config.density : 1;
|
||||
const [numRows, numColumns] = [config.numColumns, config.numColumns * density];
|
||||
|
||||
// The volumetric mode requires us to create a grid of quads,
|
||||
// rather than a single quad for our geometry
|
||||
const [numQuadRows, numQuadColumns] = volumetric ? [numRows, numColumns] : [1, 1];
|
||||
const numQuads = numQuadRows * numQuadColumns;
|
||||
const quadSize = [1 / numQuadColumns, 1 / numQuadRows];
|
||||
|
||||
// Various effect-related values
|
||||
const rippleType = config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1;
|
||||
const cycleStyle = config.cycleStyleName in cycleStyles ? cycleStyles[config.cycleStyleName] : 0;
|
||||
const slantVec = [Math.cos(config.slant), Math.sin(config.slant)];
|
||||
const slantScale = 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1);
|
||||
const showComputationTexture = config.effect === "none";
|
||||
|
||||
const uniforms = {
|
||||
...extractEntries(config, [
|
||||
// general
|
||||
"glyphHeightToWidth",
|
||||
"glyphTextureColumns",
|
||||
// compute
|
||||
"animationSpeed",
|
||||
"brightnessMinimum",
|
||||
"brightnessMix",
|
||||
"brightnessMultiplier",
|
||||
"brightnessOffset",
|
||||
"cursorEffectThreshold",
|
||||
"cycleSpeed",
|
||||
"fallSpeed",
|
||||
"glyphSequenceLength",
|
||||
"hasSun",
|
||||
"hasThunder",
|
||||
"raindropLength",
|
||||
"rippleScale",
|
||||
"rippleSpeed",
|
||||
"rippleThickness",
|
||||
"resurrectingCodeRatio",
|
||||
// render vertex
|
||||
"forwardSpeed",
|
||||
// render fragment
|
||||
"glyphEdgeCrop",
|
||||
"isPolar",
|
||||
]),
|
||||
density,
|
||||
numRows,
|
||||
const commonUniforms = {
|
||||
...extractEntries(config, ["animationSpeed", "glyphHeightToWidth", "glyphSequenceLength", "glyphTextureColumns", "resurrectingCodeRatio"]),
|
||||
numColumns,
|
||||
numQuadRows,
|
||||
numQuadColumns,
|
||||
quadSize,
|
||||
volumetric,
|
||||
|
||||
rippleType,
|
||||
cycleStyle,
|
||||
slantVec,
|
||||
slantScale,
|
||||
numRows,
|
||||
showComputationTexture,
|
||||
};
|
||||
|
||||
const msdf = loadImage(regl, config.glyphTexURL);
|
||||
|
||||
// These two framebuffers are used to compute the raining code.
|
||||
// they take turns being the source and destination of the "compute" shader.
|
||||
// The half float data type is crucial! It lets us store almost any real number,
|
||||
@@ -91,14 +59,31 @@ export default (regl, config) => {
|
||||
wrapT: "clamp",
|
||||
type: "half float",
|
||||
});
|
||||
|
||||
const output = makePassFBO(regl, config.useHalfFloat);
|
||||
|
||||
const updateFrag = loadText("shaders/compute.frag");
|
||||
const update = regl({
|
||||
const computeFrag = loadText("shaders/compute.frag");
|
||||
const computeUniforms = {
|
||||
...commonUniforms,
|
||||
...extractEntries(config, [
|
||||
"brightnessMinimum",
|
||||
"brightnessMix",
|
||||
"brightnessMultiplier",
|
||||
"brightnessOffset",
|
||||
"cursorEffectThreshold",
|
||||
"cycleSpeed",
|
||||
"fallSpeed",
|
||||
"hasSun",
|
||||
"hasThunder",
|
||||
"raindropLength",
|
||||
"rippleScale",
|
||||
"rippleSpeed",
|
||||
"rippleThickness",
|
||||
]),
|
||||
cycleStyle,
|
||||
rippleType,
|
||||
};
|
||||
const compute = regl({
|
||||
frag: regl.prop("frag"),
|
||||
uniforms: {
|
||||
...uniforms,
|
||||
...computeUniforms,
|
||||
lastState: doubleBuffer.back,
|
||||
},
|
||||
|
||||
@@ -114,8 +99,27 @@ export default (regl, config) => {
|
||||
);
|
||||
|
||||
// We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen
|
||||
const msdf = loadImage(regl, config.glyphTexURL);
|
||||
const renderVert = loadText("shaders/rain.vert");
|
||||
const renderFrag = loadText("shaders/rain.frag");
|
||||
const output = makePassFBO(regl, config.useHalfFloat);
|
||||
const renderUniforms = {
|
||||
...commonUniforms,
|
||||
...extractEntries(config, [
|
||||
// vertex
|
||||
"forwardSpeed",
|
||||
// fragment
|
||||
"glyphEdgeCrop",
|
||||
"isPolar",
|
||||
]),
|
||||
density,
|
||||
numQuadColumns,
|
||||
numQuadRows,
|
||||
quadSize,
|
||||
slantScale,
|
||||
slantVec,
|
||||
volumetric,
|
||||
};
|
||||
const render = regl({
|
||||
blend: {
|
||||
enable: true,
|
||||
@@ -128,7 +132,7 @@ export default (regl, config) => {
|
||||
frag: regl.prop("frag"),
|
||||
|
||||
uniforms: {
|
||||
...uniforms,
|
||||
...renderUniforms,
|
||||
|
||||
lastState: doubleBuffer.front,
|
||||
glyphTex: msdf.texture,
|
||||
@@ -147,6 +151,7 @@ export default (regl, config) => {
|
||||
framebuffer: output,
|
||||
});
|
||||
|
||||
// Camera and transform math for the volumetric mode
|
||||
const screenSize = [1, 1];
|
||||
const { mat4, vec3 } = glMatrix;
|
||||
const camera = mat4.create();
|
||||
@@ -161,7 +166,7 @@ export default (regl, config) => {
|
||||
primary: output,
|
||||
},
|
||||
() => {
|
||||
update({ frag: updateFrag.text() });
|
||||
compute({ frag: computeFrag.text() });
|
||||
regl.clear({
|
||||
depth: 1,
|
||||
color: [0, 0, 0, 1],
|
||||
@@ -175,6 +180,6 @@ export default (regl, config) => {
|
||||
glMatrix.mat4.perspective(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
|
||||
[screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
|
||||
},
|
||||
[msdf.loaded, updateFrag.loaded, renderVert.loaded, renderFrag.loaded]
|
||||
[msdf.loaded, computeFrag.loaded, renderVert.loaded, renderFrag.loaded]
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { loadText, make1DTexture, makePassFBO, makePass } from "./utils.js";
|
||||
|
||||
// Multiplies the rendered rain and bloom by a 1D gradient texture
|
||||
// generated from the passed-in color sequence
|
||||
|
||||
// This shader introduces noise into the renders, to avoid banding
|
||||
|
||||
const transPrideStripeColors = [
|
||||
[0.3, 1.0, 1.0],
|
||||
[0.3, 1.0, 1.0],
|
||||
@@ -27,6 +32,8 @@ export default (regl, config, inputs) => {
|
||||
const output = makePassFBO(regl, config.useHalfFloat);
|
||||
|
||||
const { backgroundColor } = config;
|
||||
|
||||
// Expand and convert stripe colors into 1D texture data
|
||||
const stripeColors =
|
||||
"stripeColors" in config ? config.stripeColors.split(",").map(parseFloat) : config.effect === "pride" ? prideStripeColors : transPrideStripeColors;
|
||||
const numStripeColors = Math.floor(stripeColors.length / 3);
|
||||
|
||||
@@ -6,6 +6,9 @@ varying vec2 vUV;
|
||||
|
||||
void main() {
|
||||
vec3 bgColor = texture2D(backgroundTex, vUV).rgb;
|
||||
|
||||
// Combine the texture and bloom, then blow it out to reveal more of the image
|
||||
float brightness = pow(min(1., texture2D(tex, vUV).r * 2.) + texture2D(bloomTex, vUV).r, 1.5);
|
||||
|
||||
gl_FragColor = vec4(bgColor * brightness, 1.0);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,13 @@ highp float rand( const in vec2 uv, const in float t ) {
|
||||
|
||||
void main() {
|
||||
vec4 brightnessRGB = texture2D( tex, vUV ) + texture2D( bloomTex, vUV );
|
||||
|
||||
// Combine the texture and bloom
|
||||
float brightness = brightnessRGB.r + brightnessRGB.g + brightnessRGB.b;
|
||||
float at = brightness - rand( gl_FragCoord.xy, time ) * ditherMagnitude;
|
||||
gl_FragColor = texture2D( palette, vec2(at, 0.0)) + vec4(backgroundColor, 0.0);
|
||||
|
||||
// Dither: subtract a random value from the brightness
|
||||
brightness = brightness - rand( gl_FragCoord.xy, time ) * ditherMagnitude;
|
||||
|
||||
// Map the brightness to a position in the palette texture
|
||||
gl_FragColor = texture2D( palette, vec2(brightness, 0.0)) + vec4(backgroundColor, 0.0);
|
||||
}
|
||||
|
||||
@@ -33,9 +33,10 @@ void main() {
|
||||
|
||||
vec2 uv = vUV;
|
||||
|
||||
// In normal mode, derives the current glyph and UV from vUV
|
||||
if (!volumetric) {
|
||||
if (isPolar) {
|
||||
// Curves the UV space to make letters appear to radiate from up above
|
||||
// Curved space that makes letters appear to radiate from up above
|
||||
uv -= 0.5;
|
||||
uv *= 0.5;
|
||||
uv.y -= 0.5;
|
||||
@@ -43,35 +44,29 @@ void main() {
|
||||
float angle = atan(uv.y, uv.x) / (2. * PI) + 0.5;
|
||||
uv = vec2(angle * 4. - 0.5, 1.5 - pow(radius, 0.5) * 1.5);
|
||||
} else {
|
||||
// Applies the slant, scaling the UV space
|
||||
// to guarantee the viewport is still covered
|
||||
// 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
|
||||
(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;
|
||||
}
|
||||
|
||||
// Unpack the values from the data texture
|
||||
vec4 glyph = volumetric ? vGlyph : texture2D(lastState, uv);
|
||||
|
||||
if (showComputationTexture) {
|
||||
gl_FragColor = glyph;
|
||||
return;
|
||||
}
|
||||
|
||||
// Unpack the values from the font texture
|
||||
float brightness = glyph.r;
|
||||
float symbolIndex = getSymbolIndex(glyph.g);
|
||||
float quadDepth = glyph.b;
|
||||
float effect = glyph.a;
|
||||
|
||||
brightness = max(effect, brightness);
|
||||
// In volumetric mode, distant glyphs are dimmer
|
||||
if (volumetric) {
|
||||
brightness = min(1.0, brightness * quadDepth * 1.25);
|
||||
brightness = brightness * min(1.0, quadDepth);
|
||||
}
|
||||
|
||||
// resolve UV to MSDF texture coord
|
||||
// resolve UV to position of glyph in MSDF texture
|
||||
vec2 symbolUV = vec2(mod(symbolIndex, glyphTextureColumns), floor(symbolIndex / glyphTextureColumns));
|
||||
vec2 glyphUV = fract(uv * vec2(numColumns, numRows));
|
||||
glyphUV -= 0.5;
|
||||
@@ -79,10 +74,15 @@ void main() {
|
||||
glyphUV += 0.5;
|
||||
vec2 msdfUV = (glyphUV + symbolUV) / glyphTextureColumns;
|
||||
|
||||
// MSDF
|
||||
// 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);
|
||||
|
||||
gl_FragColor = vec4(vChannel * brightness * alpha, 1.0);
|
||||
if (showComputationTexture) {
|
||||
gl_FragColor = vec4(glyph.rgb * alpha, 1.0);
|
||||
} else {
|
||||
gl_FragColor = vec4(vChannel * brightness * alpha, 1.0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ void main() {
|
||||
vUV = (aPosition + aCorner) * quadSize;
|
||||
vGlyph = texture2D(lastState, aPosition * quadSize);
|
||||
|
||||
// Calculate the world space position
|
||||
float quadDepth = 0.0;
|
||||
if (volumetric && !showComputationTexture) {
|
||||
quadDepth = fract(vGlyph.b + time * animationSpeed * forwardSpeed);
|
||||
@@ -34,16 +35,17 @@ void main() {
|
||||
vec2 position = (aPosition + aCorner * vec2(density, 1.)) * quadSize;
|
||||
vec4 pos = vec4((position - 0.5) * 2.0, quadDepth, 1.0);
|
||||
|
||||
// "Resurrected" columns are in the green channel,
|
||||
// and are vertically flipped (along with their glyphs)
|
||||
vChannel = vec3(1.0, 0.0, 0.0);
|
||||
if (volumetric && rand(vec2(aPosition.x, 0)) < resurrectingCodeRatio) {
|
||||
pos.y = -pos.y;
|
||||
vChannel = vec3(0.0, 1.0, 0.0);
|
||||
}
|
||||
|
||||
// Convert the world space position to screen space
|
||||
if (volumetric) {
|
||||
if (rand(vec2(aPosition.x, 0)) < resurrectingCodeRatio) {
|
||||
pos.y = -pos.y;
|
||||
vChannel = vec3(0.0, 1.0, 0.0);
|
||||
}
|
||||
|
||||
pos.x /= glyphHeightToWidth;
|
||||
|
||||
pos = camera * transform * pos;
|
||||
} else {
|
||||
pos.xy *= screenSize;
|
||||
|
||||
@@ -17,7 +17,11 @@ highp float rand( const in vec2 uv, const in float t ) {
|
||||
|
||||
void main() {
|
||||
vec3 color = texture2D(stripes, vUV).rgb;
|
||||
// Combine the texture and bloom
|
||||
float brightness = min(1., texture2D(tex, vUV).r * 2.) + texture2D(bloomTex, vUV).r;
|
||||
float at = brightness - rand( gl_FragCoord.xy, time ) * ditherMagnitude;
|
||||
gl_FragColor = vec4(color * at + backgroundColor, 1.0);
|
||||
|
||||
// Dither: subtract a random value from the brightness
|
||||
brightness = brightness - rand( gl_FragCoord.xy, time ) * ditherMagnitude;
|
||||
|
||||
gl_FragColor = vec4(color * brightness + backgroundColor, 1.0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user