mirror of
https://github.com/Rezmason/matrix.git
synced 2026-04-14 12:29:30 -07:00
Separated color passes into separate modules.
Moved main JS into its own module. Main module now builds passes into a pipeline, based on the value of config.effect. The passes no longer make stubs when they're not meant to be active. Asset loading has been moved into the passes, which resolve their ready promise when they've finished loading.
This commit is contained in:
2
TODO.txt
2
TODO.txt
@@ -1,6 +1,8 @@
|
||||
TODO:
|
||||
|
||||
Clean up config.js
|
||||
Too many responsibilities
|
||||
Pass-specific properties should be made into uniforms in the passes
|
||||
|
||||
Reach out to someone about producing sounds
|
||||
Raindrop sound
|
||||
|
||||
100
index.html
100
index.html
@@ -20,90 +20,22 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script src="lib/regl.min.js"></script>
|
||||
<script type="module">
|
||||
/*
|
||||
<!--
|
||||
This is an implementation of the green code seen in The Matrix film and video game franchise.
|
||||
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
|
||||
updating two alternating FBOs
|
||||
3. Rendering crisp "vector" graphics, with a multiple-channel
|
||||
signed distance field (or 'MSDF')
|
||||
4. Creating a blur/bloom effect from a texture pyramid
|
||||
5. Color mapping with noise, to hide banding
|
||||
|
||||
This is an implementation of the green code seen in The Matrix film and video game franchise.
|
||||
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
|
||||
updating two alternating FBOs
|
||||
3. Rendering crisp "vector" graphics, with a multiple-channel
|
||||
signed distance field (or 'MSDF')
|
||||
4. Creating a blur/bloom effect from a texture pyramid
|
||||
5. Color mapping with noise, to hide banding
|
||||
|
||||
For more information, please visit: https://github.com/Rezmason/matrix
|
||||
|
||||
*/
|
||||
|
||||
import {
|
||||
loadImages,
|
||||
makeFullScreenQuad,
|
||||
make1DTexture
|
||||
} from "./js/utils.js";
|
||||
import makeConfig from "./js/config.js";
|
||||
import makeMatrixRenderer from "./js/renderer.js";
|
||||
import makeBloomPass from "./js/bloomPass.js";
|
||||
import makeColorPass from "./js/colorPass.js";
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
document.body.appendChild(canvas);
|
||||
document.addEventListener("touchmove", e => e.preventDefault(), {
|
||||
passive: false
|
||||
});
|
||||
|
||||
const regl = createREGL({
|
||||
canvas,
|
||||
extensions: ["OES_texture_half_float", "OES_texture_half_float_linear"],
|
||||
// These extensions are also needed, but Safari misreports that they are missing
|
||||
optionalExtensions: [
|
||||
"EXT_color_buffer_half_float",
|
||||
"WEBGL_color_buffer_float",
|
||||
"OES_standard_derivatives"
|
||||
]
|
||||
});
|
||||
|
||||
const [config, uniforms] = makeConfig(window.location.search, data =>
|
||||
make1DTexture(regl, data)
|
||||
);
|
||||
|
||||
const resize = () => {
|
||||
canvas.width = canvas.clientWidth;
|
||||
canvas.height = canvas.clientHeight;
|
||||
};
|
||||
window.onresize = resize;
|
||||
resize();
|
||||
|
||||
document.body.onload = async () => {
|
||||
const images = await loadImages(regl, {
|
||||
msdfTex: config.glyphTexURL,
|
||||
bgTex: config.effect === "image" ? config.bgURL : null
|
||||
});
|
||||
|
||||
// All this takes place in a full screen quad.
|
||||
const fullScreenQuad = makeFullScreenQuad(regl, uniforms);
|
||||
const renderer = makeMatrixRenderer(regl, config, images);
|
||||
const bloomPass = makeBloomPass(regl, config, renderer.output);
|
||||
const colorPass = makeColorPass(regl, config, images, bloomPass.output);
|
||||
const drawToScreen = regl({
|
||||
uniforms: {
|
||||
tex: colorPass.output
|
||||
}
|
||||
});
|
||||
|
||||
const passes = [renderer, bloomPass, colorPass];
|
||||
|
||||
const loop = regl.frame(({ viewportWidth, viewportHeight }) => {
|
||||
passes.forEach(pass => pass.resize(viewportWidth, viewportHeight));
|
||||
fullScreenQuad(() => {
|
||||
passes.forEach(pass => pass.render());
|
||||
drawToScreen();
|
||||
});
|
||||
});
|
||||
};
|
||||
</script>
|
||||
For more information, please visit: https://github.com/Rezmason/matrix
|
||||
-->
|
||||
<!-- <script src="lib/regl.min.js"></script> -->
|
||||
<script src="lib/regl.js"></script>
|
||||
<script type="module" src="js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10,11 +10,7 @@ const levelStrengths = Array(pyramidHeight)
|
||||
)
|
||||
.reverse();
|
||||
|
||||
export default (regl, config, input) => {
|
||||
if (!config.performBloom) {
|
||||
return makePass(input, null, null);
|
||||
}
|
||||
|
||||
export default (regl, { bloomSize }, input) => {
|
||||
const highPassPyramid = makePyramid(regl, pyramidHeight);
|
||||
const hBlurPyramid = makePyramid(regl, pyramidHeight);
|
||||
const vBlurPyramid = makePyramid(regl, pyramidHeight);
|
||||
@@ -102,7 +98,7 @@ export default (regl, config, input) => {
|
||||
framebuffer: output
|
||||
});
|
||||
|
||||
const scale = config.bloomSize;
|
||||
const scale = bloomSize;
|
||||
|
||||
return makePass(
|
||||
output,
|
||||
|
||||
112
js/colorPass.js
112
js/colorPass.js
@@ -1,112 +0,0 @@
|
||||
import { makePassFBO, makePass } from "./utils.js";
|
||||
|
||||
const colorizeByPalette = (regl, uniforms, framebuffer) =>
|
||||
// The rendered texture's values are mapped to colors in a palette texture.
|
||||
// A little noise is introduced, to hide the banding that appears
|
||||
// in subtle gradients. The noise is also time-driven, so its grain
|
||||
// won't persist across subsequent frames. This is a safe trick
|
||||
// in screen space.
|
||||
regl({
|
||||
frag: `
|
||||
precision mediump float;
|
||||
#define PI 3.14159265359
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform sampler2D palette;
|
||||
uniform float ditherMagnitude;
|
||||
uniform float time;
|
||||
varying vec2 vUV;
|
||||
|
||||
highp float rand( const in vec2 uv, const in float t ) {
|
||||
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 + t);
|
||||
}
|
||||
|
||||
void main() {
|
||||
float at = texture2D( tex, vUV ).r - rand( gl_FragCoord.xy, time ) * ditherMagnitude;
|
||||
gl_FragColor = texture2D( palette, vec2(at, 0.0));
|
||||
}
|
||||
`,
|
||||
|
||||
uniforms: {
|
||||
...uniforms,
|
||||
ditherMagnitude: 0.05
|
||||
},
|
||||
framebuffer
|
||||
});
|
||||
|
||||
const colorizeByStripes = (regl, uniforms, framebuffer) =>
|
||||
regl({
|
||||
frag: `
|
||||
precision mediump float;
|
||||
#define PI 3.14159265359
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform sampler2D stripes;
|
||||
uniform float ditherMagnitude;
|
||||
varying vec2 vUV;
|
||||
|
||||
highp float rand( 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);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 color = texture2D(stripes, vUV).rgb - rand( gl_FragCoord.xy ) * ditherMagnitude;
|
||||
float brightness = texture2D(tex, vUV).r;
|
||||
gl_FragColor = vec4(color * brightness, 1.0);
|
||||
}
|
||||
`,
|
||||
|
||||
uniforms: {
|
||||
...uniforms,
|
||||
ditherMagnitude: 0.1
|
||||
},
|
||||
framebuffer
|
||||
});
|
||||
|
||||
const colorizeByImage = (regl, uniforms, framebuffer) =>
|
||||
regl({
|
||||
frag: `
|
||||
precision mediump float;
|
||||
uniform sampler2D tex;
|
||||
uniform sampler2D bgTex;
|
||||
varying vec2 vUV;
|
||||
|
||||
void main() {
|
||||
vec3 bgColor = texture2D(bgTex, vUV).rgb;
|
||||
float brightness = pow(texture2D(tex, vUV).r, 1.5);
|
||||
gl_FragColor = vec4(bgColor * brightness, 1.0);
|
||||
}
|
||||
`,
|
||||
uniforms,
|
||||
framebuffer
|
||||
});
|
||||
|
||||
const colorizersByEffect = {
|
||||
plain: colorizeByPalette,
|
||||
customStripes: colorizeByStripes,
|
||||
stripes: colorizeByStripes,
|
||||
image: colorizeByImage
|
||||
};
|
||||
|
||||
export default (regl, config, { bgTex }, input) => {
|
||||
if (config.effect === "none") {
|
||||
return makePass(input, null, null);
|
||||
}
|
||||
|
||||
if (bgTex == null) {
|
||||
bgTex = 0;
|
||||
}
|
||||
|
||||
const output = makePassFBO(regl);
|
||||
|
||||
return makePass(
|
||||
output,
|
||||
(config.effect in colorizersByEffect
|
||||
? colorizersByEffect[config.effect]
|
||||
: colorizeByPalette)(regl, { bgTex, tex: input }, output)
|
||||
);
|
||||
};
|
||||
52
js/config.js
52
js/config.js
@@ -171,6 +171,8 @@ const versions = {
|
||||
versions.throwback = versions.operator;
|
||||
versions["1999"] = versions.classic;
|
||||
|
||||
// Start here
|
||||
|
||||
export default (searchString, make1DTexture) => {
|
||||
const urlParams = new URLSearchParams(searchString);
|
||||
const getParam = (keyOrKeys, defaultValue) => {
|
||||
@@ -185,8 +187,7 @@ export default (searchString, make1DTexture) => {
|
||||
};
|
||||
|
||||
const versionName = getParam("version", "classic");
|
||||
const version =
|
||||
versions[versionName] == null ? versions.classic : versions[versionName];
|
||||
const version = versions[versionName] == null ? versions.classic : versions[versionName];
|
||||
|
||||
const config = { ...version };
|
||||
|
||||
@@ -194,43 +195,19 @@ export default (searchString, make1DTexture) => {
|
||||
config.fallSpeed *= parseFloat(getParam("fallSpeed", 1));
|
||||
config.cycleSpeed *= parseFloat(getParam("cycleSpeed", 1));
|
||||
config.numColumns = parseInt(getParam("width", config.numColumns));
|
||||
config.raindropLength = parseFloat(
|
||||
getParam(["raindropLength", "dropLength"], config.raindropLength)
|
||||
);
|
||||
config.raindropLength = parseFloat(getParam(["raindropLength", "dropLength"], config.raindropLength));
|
||||
config.glyphSequenceLength = config.glyphSequenceLength;
|
||||
config.slant =
|
||||
(parseFloat(getParam(["slant", "angle"], config.slant)) * Math.PI) / 180;
|
||||
config.slant = (parseFloat(getParam(["slant", "angle"], config.slant)) * Math.PI) / 180;
|
||||
config.slantVec = [Math.cos(config.slant), Math.sin(config.slant)];
|
||||
config.slantScale =
|
||||
1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1);
|
||||
config.slantScale = 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1);
|
||||
config.glyphEdgeCrop = parseFloat(getParam("encroach", config.glyphEdgeCrop));
|
||||
config.glyphHeightToWidth = parseFloat(
|
||||
getParam("stretch", config.glyphHeightToWidth)
|
||||
);
|
||||
config.cursorEffectThreshold = getParam(
|
||||
"cursorEffectThreshold",
|
||||
config.cursorEffectThreshold
|
||||
);
|
||||
config.bloomSize = Math.max(
|
||||
0.01,
|
||||
Math.min(1, parseFloat(getParam("bloomSize", 0.5)))
|
||||
);
|
||||
config.glyphHeightToWidth = parseFloat(getParam("stretch", config.glyphHeightToWidth));
|
||||
config.cursorEffectThreshold = getParam("cursorEffectThreshold", config.cursorEffectThreshold);
|
||||
config.bloomSize = Math.max(0.01, Math.min(1, parseFloat(getParam("bloomSize", 0.5))));
|
||||
config.effect = getParam("effect", "plain");
|
||||
config.bgURL = getParam(
|
||||
"url",
|
||||
"https://upload.wikimedia.org/wikipedia/commons/0/0a/Flammarion_Colored.jpg"
|
||||
);
|
||||
config.customStripes = getParam(
|
||||
"colors",
|
||||
"0.4,0.15,0.1,0.4,0.15,0.1,0.8,0.8,0.6,0.8,0.8,0.6,1.0,0.7,0.8,1.0,0.7,0.8,"
|
||||
)
|
||||
.split(",")
|
||||
.map(parseFloat);
|
||||
config.bgURL = getParam("url", "https://upload.wikimedia.org/wikipedia/commons/0/0a/Flammarion_Colored.jpg");
|
||||
config.customStripes = getParam("colors", "0.4,0.15,0.1,0.4,0.15,0.1,0.8,0.8,0.6,0.8,0.8,0.6,1.0,0.7,0.8,1.0,0.7,0.8,").split(",").map(parseFloat);
|
||||
config.showComputationTexture = config.effect === "none";
|
||||
config.performBloom =
|
||||
config.effect !== "none" &&
|
||||
config.bloomSize > 0 &&
|
||||
config.bloomStrength > 0;
|
||||
|
||||
switch (config.cycleStyleName) {
|
||||
case "cycleFasterWhenDimmed":
|
||||
@@ -291,7 +268,7 @@ export default (searchString, make1DTexture) => {
|
||||
|
||||
if (config.effect === "pride") {
|
||||
config.effect = "stripes";
|
||||
config.customStripes = [
|
||||
config.stripeColors = [
|
||||
[1, 0, 0],
|
||||
[1, 0.5, 0],
|
||||
[1, 1, 0],
|
||||
@@ -302,8 +279,9 @@ export default (searchString, make1DTexture) => {
|
||||
}
|
||||
|
||||
if (config.effect === "customStripes" || config.effect === "stripes") {
|
||||
const numFlagColors = Math.floor(config.customStripes.length / 3);
|
||||
stripeColors = config.customStripes.slice(0, numFlagColors * 3);
|
||||
config.effect = "stripes";
|
||||
const numStripeColors = Math.floor(config.stripeColors.length / 3);
|
||||
stripeColors = config.stripeColors.slice(0, numStripeColors * 3);
|
||||
}
|
||||
|
||||
config.stripes = make1DTexture(stripeColors.map(f => Math.floor(f * 0xff)));
|
||||
|
||||
27
js/imagePass.js
Normal file
27
js/imagePass.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import { loadImage, makePassFBO, makePass } from "./utils.js";
|
||||
|
||||
export default (regl, { bgURL }, input) => {
|
||||
const output = makePassFBO(regl);
|
||||
const bgLoader = loadImage(regl, bgURL);
|
||||
return makePass(
|
||||
output,
|
||||
regl({
|
||||
frag: `
|
||||
precision mediump float;
|
||||
uniform sampler2D tex;
|
||||
uniform sampler2D bgTex;
|
||||
varying vec2 vUV;
|
||||
|
||||
void main() {
|
||||
vec3 bgColor = texture2D(bgTex, vUV).rgb;
|
||||
float brightness = pow(texture2D(tex, vUV).r, 1.5);
|
||||
gl_FragColor = vec4(bgColor * brightness, 1.0);
|
||||
}
|
||||
`,
|
||||
uniforms: { bgTex: bgLoader.texture, tex: input },
|
||||
framebuffer: output
|
||||
}),
|
||||
null,
|
||||
bgLoader.ready
|
||||
);
|
||||
};
|
||||
71
js/main.js
Normal file
71
js/main.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import { makeFullScreenQuad, make1DTexture, makePipeline } from "./utils.js";
|
||||
import makeConfig from "./config.js";
|
||||
import makeMatrixRenderer from "./renderer.js";
|
||||
import makeBloomPass from "./bloomPass.js";
|
||||
import makePalettePass from "./palettePass.js";
|
||||
import makeStripePass from "./stripePass.js";
|
||||
import makeImagePass from "./imagePass.js";
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
document.body.appendChild(canvas);
|
||||
document.addEventListener("touchmove", e => e.preventDefault(), {
|
||||
passive: false
|
||||
});
|
||||
|
||||
const regl = createREGL({
|
||||
canvas,
|
||||
extensions: ["OES_texture_half_float", "OES_texture_half_float_linear"],
|
||||
// These extensions are also needed, but Safari misreports that they are missing
|
||||
optionalExtensions: [
|
||||
"EXT_color_buffer_half_float",
|
||||
"WEBGL_color_buffer_float",
|
||||
"OES_standard_derivatives"
|
||||
]
|
||||
});
|
||||
|
||||
const effects = {
|
||||
none: null,
|
||||
plain: makePalettePass,
|
||||
stripes: makeStripePass,
|
||||
image: makeImagePass
|
||||
};
|
||||
|
||||
const [config, uniforms] = makeConfig(window.location.search, data =>
|
||||
make1DTexture(regl, data)
|
||||
);
|
||||
const effect = config.effect in effects ? config.effect : "plain";
|
||||
|
||||
const resize = () => {
|
||||
canvas.width = canvas.clientWidth;
|
||||
canvas.height = canvas.clientHeight;
|
||||
};
|
||||
window.onresize = resize;
|
||||
resize();
|
||||
|
||||
document.body.onload = async () => {
|
||||
// All this takes place in a full screen quad.
|
||||
const fullScreenQuad = makeFullScreenQuad(regl, uniforms);
|
||||
const pipeline = makePipeline(
|
||||
[
|
||||
makeMatrixRenderer,
|
||||
effect === "none" ? null : makeBloomPass,
|
||||
effects[effect]
|
||||
],
|
||||
p => p.output,
|
||||
regl,
|
||||
config
|
||||
);
|
||||
const drawToScreen = regl({
|
||||
uniforms: {
|
||||
tex: pipeline[pipeline.length - 1].output
|
||||
}
|
||||
});
|
||||
await Promise.all(pipeline.map(({ ready }) => ready));
|
||||
regl.frame(({ viewportWidth, viewportHeight }) => {
|
||||
pipeline.forEach(({ resize }) => resize(viewportWidth, viewportHeight));
|
||||
fullScreenQuad(() => {
|
||||
pipeline.forEach(({ render }) => render());
|
||||
drawToScreen();
|
||||
});
|
||||
});
|
||||
};
|
||||
43
js/palettePass.js
Normal file
43
js/palettePass.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { makePassFBO, makePass } from "./utils.js";
|
||||
|
||||
// The rendered texture's values are mapped to colors in a palette texture.
|
||||
// A little noise is introduced, to hide the banding that appears
|
||||
// in subtle gradients. The noise is also time-driven, so its grain
|
||||
// won't persist across subsequent frames. This is a safe trick
|
||||
// in screen space.
|
||||
|
||||
export default (regl, {}, input) => {
|
||||
const output = makePassFBO(regl);
|
||||
return makePass(
|
||||
output,
|
||||
regl({
|
||||
frag: `
|
||||
precision mediump float;
|
||||
#define PI 3.14159265359
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform sampler2D palette;
|
||||
uniform float ditherMagnitude;
|
||||
uniform float time;
|
||||
varying vec2 vUV;
|
||||
|
||||
highp float rand( const in vec2 uv, const in float t ) {
|
||||
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 + t);
|
||||
}
|
||||
|
||||
void main() {
|
||||
float at = texture2D( tex, vUV ).r - rand( gl_FragCoord.xy, time ) * ditherMagnitude;
|
||||
gl_FragColor = texture2D( palette, vec2(at, 0.0));
|
||||
}
|
||||
`,
|
||||
|
||||
uniforms: {
|
||||
tex: input,
|
||||
ditherMagnitude: 0.05
|
||||
},
|
||||
framebuffer: output
|
||||
})
|
||||
);
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import { makePassFBO, makeDoubleBuffer, makePass } from "./utils.js";
|
||||
import { loadImage, makePassFBO, makeDoubleBuffer, makePass } from "./utils.js";
|
||||
|
||||
export default (regl, config, { msdfTex }) => {
|
||||
export default (regl, config) => {
|
||||
// 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,
|
||||
@@ -213,6 +213,8 @@ export default (regl, config, { msdfTex }) => {
|
||||
framebuffer: doubleBuffer.front
|
||||
});
|
||||
|
||||
const msdfLoader = loadImage(regl, config.glyphTexURL);
|
||||
|
||||
// We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen
|
||||
const render = regl({
|
||||
vert: `
|
||||
@@ -235,7 +237,7 @@ export default (regl, config, { msdfTex }) => {
|
||||
#endif
|
||||
precision lowp float;
|
||||
|
||||
uniform sampler2D msdfTex;
|
||||
uniform sampler2D glyphTex;
|
||||
uniform sampler2D lastState;
|
||||
uniform float numColumns;
|
||||
uniform float glyphTextureColumns;
|
||||
@@ -298,7 +300,7 @@ export default (regl, config, { msdfTex }) => {
|
||||
vec2 msdfUV = (glyphUV + symbolUV) / glyphTextureColumns;
|
||||
|
||||
// MSDF
|
||||
vec3 dist = texture2D(msdfTex, msdfUV).rgb;
|
||||
vec3 dist = texture2D(glyphTex, msdfUV).rgb;
|
||||
float sigDist = median3(dist) - 0.5;
|
||||
float alpha = clamp(sigDist/fwidth(sigDist) + 0.5, 0.0, 1.0);
|
||||
|
||||
@@ -307,7 +309,7 @@ export default (regl, config, { msdfTex }) => {
|
||||
`,
|
||||
|
||||
uniforms: {
|
||||
msdfTex,
|
||||
glyphTex: msdfLoader.texture,
|
||||
height: regl.context("viewportWidth"),
|
||||
width: regl.context("viewportHeight"),
|
||||
lastState: doubleBuffer.front
|
||||
@@ -316,8 +318,13 @@ export default (regl, config, { msdfTex }) => {
|
||||
framebuffer: output
|
||||
});
|
||||
|
||||
return makePass(output, resources => {
|
||||
update();
|
||||
render(resources);
|
||||
});
|
||||
return makePass(
|
||||
output,
|
||||
resources => {
|
||||
update();
|
||||
render(resources);
|
||||
},
|
||||
null,
|
||||
msdfLoader.ready
|
||||
);
|
||||
};
|
||||
|
||||
37
js/stripePass.js
Normal file
37
js/stripePass.js
Normal file
@@ -0,0 +1,37 @@
|
||||
import { makePassFBO, makePass } from "./utils.js";
|
||||
|
||||
export default (regl, {}, input) => {
|
||||
const output = makePassFBO(regl);
|
||||
return makePass(
|
||||
output,
|
||||
regl({
|
||||
frag: `
|
||||
precision mediump float;
|
||||
#define PI 3.14159265359
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform sampler2D stripes;
|
||||
uniform float ditherMagnitude;
|
||||
varying vec2 vUV;
|
||||
|
||||
highp float rand( 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);
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec3 color = texture2D(stripes, vUV).rgb - rand( gl_FragCoord.xy ) * ditherMagnitude;
|
||||
float brightness = texture2D(tex, vUV).r;
|
||||
gl_FragColor = vec4(color * brightness, 1.0);
|
||||
}
|
||||
`,
|
||||
|
||||
uniforms: {
|
||||
tex: input,
|
||||
ditherMagnitude: 0.1
|
||||
},
|
||||
framebuffer: output
|
||||
})
|
||||
);
|
||||
};
|
||||
58
js/utils.js
58
js/utils.js
@@ -43,11 +43,11 @@ const resizePyramid = (pyramid, vw, vh, scale) =>
|
||||
const loadImages = async (regl, manifest) => {
|
||||
const keys = Object.keys(manifest);
|
||||
const urls = Object.values(manifest);
|
||||
const images = await Promise.all(urls.map(url => loadImage(regl, url)));
|
||||
const images = await Promise.all(urls.map(url => loadImageOld(regl, url)));
|
||||
return Object.fromEntries(images.map((image, index) => [keys[index], image]));
|
||||
};
|
||||
|
||||
const loadImage = async (regl, url) => {
|
||||
const loadImageOld = async (regl, url) => {
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -63,6 +63,34 @@ const loadImage = async (regl, url) => {
|
||||
});
|
||||
};
|
||||
|
||||
const loadImage = (regl, url) => {
|
||||
let texture = regl.texture([[0]]);
|
||||
let loaded = false;
|
||||
return {
|
||||
texture: () => {
|
||||
if (!loaded) {
|
||||
console.warn(`texture still loading: ${url}`);
|
||||
}
|
||||
return texture;
|
||||
},
|
||||
ready: (async () => {
|
||||
if (url != null) {
|
||||
const data = new Image();
|
||||
data.crossOrigin = "anonymous";
|
||||
data.src = url;
|
||||
await data.decode();
|
||||
loaded = true;
|
||||
texture = regl.texture({
|
||||
data,
|
||||
mag: "linear",
|
||||
min: "linear",
|
||||
flipY: true
|
||||
});
|
||||
}
|
||||
})()
|
||||
};
|
||||
};
|
||||
|
||||
const makeFullScreenQuad = (regl, uniforms = {}, context = {}) =>
|
||||
regl({
|
||||
vert: `
|
||||
@@ -109,24 +137,35 @@ const make1DTexture = (regl, data) =>
|
||||
min: "linear"
|
||||
});
|
||||
|
||||
const makePass = (output, render, resize) => {
|
||||
const makePass = (output, render, resize, ready) => {
|
||||
if (render == null) {
|
||||
render = () => {};
|
||||
}
|
||||
if (resize === undefined) {
|
||||
// "default" resize function is on the FBO
|
||||
if (resize == null) {
|
||||
resize = (w, h) => output.resize(w, h);
|
||||
}
|
||||
if (resize == null) {
|
||||
resize = () => {};
|
||||
if (ready == null) {
|
||||
ready = Promise.resolve();
|
||||
}
|
||||
return {
|
||||
output,
|
||||
render,
|
||||
resize
|
||||
resize,
|
||||
ready
|
||||
};
|
||||
};
|
||||
|
||||
const makePipeline = (steps, getInput, ...params) =>
|
||||
steps
|
||||
.filter(f => f != null)
|
||||
.reduce(
|
||||
(pipeline, f, i) => [
|
||||
...pipeline,
|
||||
f(...params, i == 0 ? null : getInput(pipeline[i - 1]))
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
export {
|
||||
makePassTexture,
|
||||
makePassFBO,
|
||||
@@ -137,5 +176,6 @@ export {
|
||||
loadImages,
|
||||
makeFullScreenQuad,
|
||||
make1DTexture,
|
||||
makePass
|
||||
makePass,
|
||||
makePipeline
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user