mirror of
https://github.com/Rezmason/matrix.git
synced 2026-04-21 15:29:30 -07:00
Replaced MatrixGeometry and MatrixMaterial with MatrixRenderer. Got rid of a,b,c variables.
This commit is contained in:
21
TODO.txt
21
TODO.txt
@@ -1,8 +1,15 @@
|
|||||||
TODO:
|
TODO:
|
||||||
|
|
||||||
Migrate rain logic to shaders that operate on double buffer RTTs
|
|
||||||
|
|
||||||
Reach out to Ashley's partner about producing sounds
|
Reach out to Ashley's partner about producing sounds
|
||||||
|
Raindrop sound
|
||||||
|
https://youtu.be/KoQOKq1C3O4?t=30
|
||||||
|
https://youtu.be/h1vLZeVAp5o?t=28
|
||||||
|
Flashing row sound
|
||||||
|
https://youtu.be/z_KmNZNT5xw?t=16
|
||||||
|
Square sound
|
||||||
|
https://youtu.be/ngnlBZNuVb8?t=204
|
||||||
|
https://youtu.be/721sG2D_9-U?t=67
|
||||||
|
And some kind of ambient sound that they play over
|
||||||
|
|
||||||
Much later:
|
Much later:
|
||||||
Optimizations
|
Optimizations
|
||||||
@@ -11,14 +18,8 @@ Much later:
|
|||||||
Dissolve threejs project into webgl project
|
Dissolve threejs project into webgl project
|
||||||
Maybe webgl2 project?
|
Maybe webgl2 project?
|
||||||
Deluxe
|
Deluxe
|
||||||
Raindrop sound
|
Flashing row effect
|
||||||
https://youtu.be/KoQOKq1C3O4?t=30
|
Square event
|
||||||
https://youtu.be/h1vLZeVAp5o?t=28
|
|
||||||
Flashing row effect?
|
|
||||||
https://youtu.be/z_KmNZNT5xw?t=16
|
|
||||||
Square event?
|
|
||||||
https://youtu.be/ngnlBZNuVb8?t=200
|
|
||||||
https://youtu.be/721sG2D_9-U?t=67
|
|
||||||
More patterns?
|
More patterns?
|
||||||
Symbol duplication is common
|
Symbol duplication is common
|
||||||
|
|
||||||
|
|||||||
@@ -1,240 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<body style="height: 100vh; margin: 0; overflow: hidden; position: fixed; padding: 0; width: 100vw;">
|
|
||||||
<script src="./lib/three.js"></script>
|
|
||||||
<script src="./js/GPUComputationRenderer.js"></script>
|
|
||||||
<script>
|
|
||||||
const camera = new THREE.OrthographicCamera( -0.5, 0.5, 0.5, -0.5, 0.0001, 10000 );
|
|
||||||
const scene = new THREE.Scene();
|
|
||||||
const renderer = new THREE.WebGLRenderer();
|
|
||||||
renderer.setPixelRatio( window.devicePixelRatio );
|
|
||||||
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
||||||
document.body.appendChild( renderer.domElement );
|
|
||||||
|
|
||||||
const numColumns = 80;
|
|
||||||
|
|
||||||
// Creates the gpu computation class and sets it up
|
|
||||||
const gpuCompute = new GPUComputationRenderer( numColumns, numColumns, renderer );
|
|
||||||
const glyphValue = gpuCompute.createTexture();
|
|
||||||
// This is how one might initialize data
|
|
||||||
|
|
||||||
const pixels = glyphValue.image.data;
|
|
||||||
for (let i = 0; i < numColumns * numColumns; i++) {
|
|
||||||
pixels[i * 4 + 0] = 0;
|
|
||||||
pixels[i * 4 + 1] = Math.random();
|
|
||||||
pixels[i * 4 + 2] = 0;
|
|
||||||
pixels[i * 4 + 3] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const glyphVariable = gpuCompute.addVariable(
|
|
||||||
"glyph",
|
|
||||||
`
|
|
||||||
precision highp float;
|
|
||||||
#define PI 3.14159265359
|
|
||||||
#define SQRT_2 1.4142135623730951
|
|
||||||
#define SQRT_5 2.23606797749979
|
|
||||||
uniform float now;
|
|
||||||
uniform float delta;
|
|
||||||
uniform float animationSpeed;
|
|
||||||
uniform float fallSpeed;
|
|
||||||
uniform float cycleSpeed;
|
|
||||||
uniform float a;
|
|
||||||
uniform float b;
|
|
||||||
uniform float c;
|
|
||||||
uniform float brightnessChangeBias;
|
|
||||||
uniform float glyphSequenceLength;
|
|
||||||
uniform float numGlyphColumns;
|
|
||||||
|
|
||||||
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() {
|
|
||||||
|
|
||||||
vec2 cellSize = 1.0 / resolution.xy;
|
|
||||||
vec2 uv = (gl_FragCoord.xy) * cellSize;
|
|
||||||
|
|
||||||
float columnTimeOffset = rand(vec2(gl_FragCoord.x, 0.0));
|
|
||||||
float columnSpeedOffset = rand(vec2(gl_FragCoord.x + 0.1, 0.0));
|
|
||||||
|
|
||||||
vec4 data = texture2D( glyph, uv );
|
|
||||||
|
|
||||||
float brightness = data.r;
|
|
||||||
float cycle = data.g;
|
|
||||||
|
|
||||||
float simTime = now * 0.0005 * animationSpeed * fallSpeed;
|
|
||||||
float columnTime = (columnTimeOffset * 1000.0 + simTime) * (0.5 + columnSpeedOffset * 0.5) + (sin(simTime * 2.0 * columnSpeedOffset) * 0.2);
|
|
||||||
float glyphTime = gl_FragCoord.y * 0.01 + columnTime;
|
|
||||||
|
|
||||||
float value = 1.0 - fract((glyphTime + 0.3 * sin(SQRT_2 * glyphTime) + 0.2 * sin(SQRT_5 * glyphTime)));
|
|
||||||
|
|
||||||
float newBrightness = clamp(a + b * log(c * (value - 0.5)), 0.0, 1.0);
|
|
||||||
|
|
||||||
brightness = mix(brightness, newBrightness, brightnessChangeBias);
|
|
||||||
|
|
||||||
|
|
||||||
float glyphCycleSpeed = delta * cycleSpeed * 0.2 * pow(1.0 - brightness, 4.0);
|
|
||||||
cycle = fract(cycle + glyphCycleSpeed);
|
|
||||||
float symbol = floor(glyphSequenceLength * cycle);
|
|
||||||
float symbolX = mod(symbol, numGlyphColumns);
|
|
||||||
float symbolY = ((numGlyphColumns - 1.0) - (symbol - symbolX) / numGlyphColumns);
|
|
||||||
|
|
||||||
gl_FragColor = vec4(1.0);
|
|
||||||
gl_FragColor.r = brightness;
|
|
||||||
gl_FragColor.g = cycle;
|
|
||||||
gl_FragColor.b = symbolX / numGlyphColumns;
|
|
||||||
gl_FragColor.a = symbolY / numGlyphColumns;
|
|
||||||
}
|
|
||||||
`
|
|
||||||
,
|
|
||||||
glyphValue
|
|
||||||
);
|
|
||||||
gpuCompute.setVariableDependencies( glyphVariable, [ glyphVariable ] );
|
|
||||||
const animationSpeed = 1;
|
|
||||||
const brightnessChangeBias = animationSpeed == 0 ? 1 : Math.min(1, Math.abs(animationSpeed));
|
|
||||||
const glyphSequenceLength = 57;
|
|
||||||
Object.assign(glyphVariable.material.uniforms, {
|
|
||||||
now: { type: "f", value: 0 },
|
|
||||||
delta: { type: "f", value: 0.01 },
|
|
||||||
animationSpeed: { type: "f", value: animationSpeed },
|
|
||||||
fallSpeed: { type: "f", value: 1 },
|
|
||||||
cycleSpeed: {type: "f", value: 1 },
|
|
||||||
glyphSequenceLength: { type: "f", value: glyphSequenceLength },
|
|
||||||
numGlyphColumns: {type: "f", value: 8},
|
|
||||||
|
|
||||||
a: { type: "f", value: 1.125 },
|
|
||||||
b: { type: "f", value: 1.125 },
|
|
||||||
c: { type: "f", value: 1.25 },
|
|
||||||
brightnessChangeBias: { type: "f", value: brightnessChangeBias },
|
|
||||||
});
|
|
||||||
|
|
||||||
const error = gpuCompute.init();
|
|
||||||
if ( error !== null ) {
|
|
||||||
console.error( error );
|
|
||||||
}
|
|
||||||
|
|
||||||
const sharpness = 0.5;
|
|
||||||
const glyphRTT = gpuCompute.getCurrentRenderTarget( glyphVariable ).texture;
|
|
||||||
|
|
||||||
const plane = new THREE.Mesh(
|
|
||||||
new THREE.PlaneBufferGeometry(),
|
|
||||||
new THREE.RawShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
glyphs: { type: "t", value: glyphRTT },
|
|
||||||
msdf: { type: "t", value: new THREE.TextureLoader().load( './matrixcode_msdf.png' ) },
|
|
||||||
numColumns: {type: "f", value: numColumns},
|
|
||||||
sharpness: { type: "f", value: sharpness },
|
|
||||||
numGlyphColumns: {type: "f", value: 8},
|
|
||||||
},
|
|
||||||
vertexShader: `
|
|
||||||
attribute vec2 uv;
|
|
||||||
attribute vec3 position;
|
|
||||||
uniform mat4 projectionMatrix;
|
|
||||||
uniform mat4 modelViewMatrix;
|
|
||||||
varying vec2 vUV;
|
|
||||||
void main() {
|
|
||||||
vUV = uv;
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader: `
|
|
||||||
#ifdef GL_OES_standard_derivatives
|
|
||||||
#extension GL_OES_standard_derivatives: enable
|
|
||||||
#endif
|
|
||||||
precision lowp float;
|
|
||||||
#define BIG_ENOUGH 0.001
|
|
||||||
#define MODIFIED_ALPHATEST (0.02 * isBigEnough / BIG_ENOUGH)
|
|
||||||
uniform float sharpness;
|
|
||||||
uniform sampler2D msdf;
|
|
||||||
uniform sampler2D glyphs;
|
|
||||||
uniform float numColumns;
|
|
||||||
uniform float numGlyphColumns;
|
|
||||||
varying vec2 vUV;
|
|
||||||
|
|
||||||
float median(float r, float g, float b) {
|
|
||||||
return max(min(r, g), min(max(r, g), b));
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
|
|
||||||
vec4 glyph = texture2D(glyphs, vUV);
|
|
||||||
float brightness = glyph.r;
|
|
||||||
vec2 symbolUV = glyph.ba;
|
|
||||||
vec4 sample = texture2D(msdf, fract(vUV * numColumns) / numGlyphColumns + symbolUV);
|
|
||||||
|
|
||||||
// MSDF
|
|
||||||
float sigDist = median(sample.r, sample.g, sample.b) - 0.5;
|
|
||||||
float alpha = clamp(sigDist/fwidth(sigDist) + 0.5, 0.0, 1.0);
|
|
||||||
float dscale = 0.353505 / sharpness;
|
|
||||||
vec2 duv = dscale * (dFdx(vUV) + dFdy(vUV));
|
|
||||||
float isBigEnough = max(abs(duv.x), abs(duv.y));
|
|
||||||
if (isBigEnough > BIG_ENOUGH) {
|
|
||||||
float ratio = BIG_ENOUGH / isBigEnough;
|
|
||||||
alpha = ratio * alpha + (1.0 - ratio) * (sigDist + 0.5);
|
|
||||||
}
|
|
||||||
if (isBigEnough <= BIG_ENOUGH && alpha < 0.5) { discard; return; }
|
|
||||||
if (alpha < 0.5 * MODIFIED_ALPHATEST) { discard; return; }
|
|
||||||
|
|
||||||
gl_FragColor = vec4(vec3(brightness * alpha), 1);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
})
|
|
||||||
/*
|
|
||||||
new THREE.MeshBasicMaterial({ map: glyphRTT })
|
|
||||||
*/
|
|
||||||
);
|
|
||||||
plane.geometry.computeVertexNormals();
|
|
||||||
scene.add( plane );
|
|
||||||
|
|
||||||
const start = Date.now();
|
|
||||||
let last = 0;
|
|
||||||
|
|
||||||
const animate = () => {
|
|
||||||
requestAnimationFrame( animate );
|
|
||||||
|
|
||||||
const now = Date.now() - start;
|
|
||||||
|
|
||||||
if (now - last > 50) {
|
|
||||||
last = now;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const delta = ((now - last > 1000) ? 0 : now - last) / 1000 * animationSpeed;
|
|
||||||
last = now;
|
|
||||||
|
|
||||||
glyphVariable.material.uniforms.now.value = now;
|
|
||||||
glyphVariable.material.uniforms.delta.value = delta;
|
|
||||||
|
|
||||||
gpuCompute.compute(); // Do the gpu computation
|
|
||||||
renderer.render( scene, camera );
|
|
||||||
}
|
|
||||||
|
|
||||||
const windowResize = () => {
|
|
||||||
const [width, height] = [window.innerWidth, window.innerHeight];
|
|
||||||
const ratio = height / width;
|
|
||||||
const frac = 0.5;
|
|
||||||
if (ratio < 1) {
|
|
||||||
camera.left = -frac;
|
|
||||||
camera.right = frac;
|
|
||||||
camera.bottom = (camera.left - camera.right) * ratio + frac;
|
|
||||||
camera.top = frac;
|
|
||||||
} else {
|
|
||||||
camera.bottom = -frac;
|
|
||||||
camera.top = frac;
|
|
||||||
camera.left = camera.bottom / ratio;
|
|
||||||
camera.right = camera.top / ratio;
|
|
||||||
}
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
renderer.setSize(width, height);
|
|
||||||
// bloomPass.setSize( window.innerWidth, window.innerHeight );
|
|
||||||
}
|
|
||||||
window.addEventListener("resize", windowResize, false);
|
|
||||||
window.addEventListener("orientationchange", windowResize, false);
|
|
||||||
windowResize();
|
|
||||||
|
|
||||||
animate();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
48
index.html
48
index.html
@@ -20,8 +20,7 @@
|
|||||||
<script src="./js/ImageOverlayPass.js"></script>
|
<script src="./js/ImageOverlayPass.js"></script>
|
||||||
<script src="./js/GPUComputationRenderer.js"></script>
|
<script src="./js/GPUComputationRenderer.js"></script>
|
||||||
|
|
||||||
<script src="./js/MatrixMaterial.js"></script>
|
<script src="./js/MatrixRenderer.js"></script>
|
||||||
<script src="./js/MatrixGeometry.js"></script>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
@@ -33,22 +32,14 @@
|
|||||||
const fallSpeed = parseFloat(getParam("fallSpeed", 1));
|
const fallSpeed = parseFloat(getParam("fallSpeed", 1));
|
||||||
const cycleSpeed = parseFloat(getParam("cycleSpeed", 1));
|
const cycleSpeed = parseFloat(getParam("cycleSpeed", 1));
|
||||||
const numColumns = parseInt(getParam("width", 80));
|
const numColumns = parseInt(getParam("width", 80));
|
||||||
const numRows = numColumns;
|
|
||||||
const glyphSequenceLength = 57;
|
const glyphSequenceLength = 57;
|
||||||
|
const numGlyphColumns = 8;
|
||||||
const a = parseFloat(getParam("a", 1.125));
|
|
||||||
const b = parseFloat(getParam("b", 1.125));
|
|
||||||
const c = parseFloat(getParam("c", 1.25));
|
|
||||||
|
|
||||||
const effect = getParam("effect", "plain");
|
const effect = getParam("effect", "plain");
|
||||||
|
|
||||||
document.ontouchmove = (e) => e.preventDefault();
|
document.ontouchmove = (e) => e.preventDefault();
|
||||||
const element = document.createElement("matrixcode");
|
const element = document.createElement("matrixcode");
|
||||||
document.body.appendChild(element);
|
document.body.appendChild(element);
|
||||||
const camera = new THREE.OrthographicCamera( -0.5, 0.5, 0.5, -0.5, 0.0001, 10000 );
|
|
||||||
const scene = new THREE.Scene();
|
|
||||||
scene.background = new THREE.Color(0, 0, 0);
|
|
||||||
scene.add(camera);
|
|
||||||
const renderer = new THREE.WebGLRenderer({ stencil: false, depth: false, precision: "lowp" });
|
const renderer = new THREE.WebGLRenderer({ stencil: false, depth: false, precision: "lowp" });
|
||||||
renderer.sortObjects = true;
|
renderer.sortObjects = true;
|
||||||
renderer.setPixelRatio(window.devicePixelRatio);
|
renderer.setPixelRatio(window.devicePixelRatio);
|
||||||
@@ -57,18 +48,17 @@
|
|||||||
const composer = new THREE.EffectComposer( renderer );
|
const composer = new THREE.EffectComposer( renderer );
|
||||||
|
|
||||||
const texture = new THREE.TextureLoader().load( './matrixcode_msdf.png' );
|
const texture = new THREE.TextureLoader().load( './matrixcode_msdf.png' );
|
||||||
const material = makeMatrixMaterial(texture, sharpness);
|
|
||||||
const {geometry, update} = makeMatrixGeometry({
|
const matrixRenderer = makeMatrixRenderer(renderer, texture, {
|
||||||
numRows, numColumns,
|
sharpness,
|
||||||
|
numColumns,
|
||||||
animationSpeed, fallSpeed, cycleSpeed,
|
animationSpeed, fallSpeed, cycleSpeed,
|
||||||
a, b, c,
|
|
||||||
glyphSequenceLength,
|
glyphSequenceLength,
|
||||||
|
numGlyphColumns
|
||||||
});
|
});
|
||||||
|
|
||||||
const mesh = new THREE.Mesh( geometry, material );
|
matrixRenderer.pass.renderToScreen = false;
|
||||||
scene.add(mesh);
|
composer.addPass( matrixRenderer.pass );
|
||||||
|
|
||||||
composer.addPass( new THREE.RenderPass( scene, camera ) );
|
|
||||||
|
|
||||||
const bloomPass = new THREE.UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 2, 0.5, 0.3 );
|
const bloomPass = new THREE.UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 2, 0.5, 0.3 );
|
||||||
composer.addPass( bloomPass );
|
composer.addPass( bloomPass );
|
||||||
@@ -110,26 +100,12 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
composer.passes.filter(pass => !pass.enabled).renderToScreen = false;
|
composer.passes[composer.passes.length - 1].renderToScreen = true;
|
||||||
composer.passes.filter(pass => pass.enabled).pop().renderToScreen = true;
|
|
||||||
|
|
||||||
const windowResize = () => {
|
const windowResize = () => {
|
||||||
const [width, height] = [window.innerWidth, window.innerHeight];
|
const [width, height] = [window.innerWidth, window.innerHeight];
|
||||||
const ratio = height / width;
|
|
||||||
const frac = 0.5;
|
|
||||||
if (ratio < 1) {
|
|
||||||
camera.left = -frac;
|
|
||||||
camera.right = frac;
|
|
||||||
camera.bottom = (camera.left - camera.right) * ratio + frac;
|
|
||||||
camera.top = frac;
|
|
||||||
} else {
|
|
||||||
camera.bottom = -frac;
|
|
||||||
camera.top = frac;
|
|
||||||
camera.left = camera.bottom / ratio;
|
|
||||||
camera.right = camera.top / ratio;
|
|
||||||
}
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
renderer.setSize(width, height);
|
renderer.setSize(width, height);
|
||||||
|
matrixRenderer.resize(width, height);
|
||||||
bloomPass.setSize( window.innerWidth, window.innerHeight );
|
bloomPass.setSize( window.innerWidth, window.innerHeight );
|
||||||
}
|
}
|
||||||
window.addEventListener("resize", windowResize, false);
|
window.addEventListener("resize", windowResize, false);
|
||||||
@@ -138,7 +114,7 @@
|
|||||||
|
|
||||||
const render = () => {
|
const render = () => {
|
||||||
requestAnimationFrame(render);
|
requestAnimationFrame(render);
|
||||||
update(Date.now());
|
matrixRenderer.render();
|
||||||
composer.render();
|
composer.render();
|
||||||
}
|
}
|
||||||
render();
|
render();
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
const makeMatrixGeometry = ({
|
|
||||||
numRows, numColumns,
|
|
||||||
animationSpeed, fallSpeed, cycleSpeed,
|
|
||||||
a, b, c,
|
|
||||||
glyphSequenceLength,
|
|
||||||
}) => {
|
|
||||||
|
|
||||||
const fTexU = 1 / 8;
|
|
||||||
const fTexV = 1 / 8;
|
|
||||||
const verticesPerGlyph = 4;
|
|
||||||
|
|
||||||
const glyphPositionTemplate = [[0, 1, 0, 1], [1, 1, 0, 1], [0, 0, 0, 1], [1, 0, 0, 1],];
|
|
||||||
const glyphBrightnessTemplate = [0, 0, 0, 0,];
|
|
||||||
const glyphUVTemplate = [[0, fTexV], [fTexU, fTexV], [0, 0], [fTexU, 0],];
|
|
||||||
const glyphIndexTemplate = [0, 2, 1, 2, 3, 1,];
|
|
||||||
|
|
||||||
const glyphPositionMarch = 4;
|
|
||||||
const glyphBrightnessMarch = 1;
|
|
||||||
const glyphUVMarch = 2;
|
|
||||||
const glyphIndexMarch = 4;
|
|
||||||
|
|
||||||
const glyphPositionArray = [];
|
|
||||||
const glyphBrightnessArray = [];
|
|
||||||
const glyphUVArray = [];
|
|
||||||
const glyphIndexArray = [];
|
|
||||||
|
|
||||||
const fRow = 1 / numRows;
|
|
||||||
const fColumn = 1 / numColumns;
|
|
||||||
|
|
||||||
for (let column = 0; column < numColumns; column++) {
|
|
||||||
for (let row = 0; row < numRows; row++) {
|
|
||||||
const index = row + column * numRows;
|
|
||||||
glyphPositionArray.push(...[].concat(...glyphPositionTemplate.map(([x, y, z, w]) => [
|
|
||||||
(x + column) / numColumns - 0.5,
|
|
||||||
(y + row) / numRows - 0.5,
|
|
||||||
z,
|
|
||||||
w,
|
|
||||||
])));
|
|
||||||
glyphBrightnessArray.push(...glyphBrightnessTemplate);
|
|
||||||
glyphUVArray.push(...[].concat(...glyphUVTemplate));
|
|
||||||
glyphIndexArray.push(...[].concat(glyphIndexTemplate.map(i => i + index * glyphIndexMarch)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const geometry = new THREE.BufferGeometry();
|
|
||||||
const glyphPositionFloat32Array = new Float32Array(glyphPositionArray);
|
|
||||||
const glyphBrightnessFloat32Array = new Float32Array(glyphBrightnessArray);
|
|
||||||
const glyphUVFloat32Array = new Float32Array(glyphUVArray);
|
|
||||||
|
|
||||||
const columns = Array(numColumns).fill().map((_, columnIndex) => {
|
|
||||||
const column = {
|
|
||||||
timeOffset: Math.random(),
|
|
||||||
speedOffset: Math.random(),
|
|
||||||
glyphs: Array(numRows).fill().map((_, index) => ({
|
|
||||||
cycle: Math.random(),
|
|
||||||
symbol: 0,
|
|
||||||
brightness: 0,
|
|
||||||
})),
|
|
||||||
brightnessArray: glyphBrightnessFloat32Array.subarray(numRows * (columnIndex) * glyphBrightnessMarch * verticesPerGlyph, numRows * (columnIndex + 1) * glyphBrightnessMarch * verticesPerGlyph),
|
|
||||||
uvArray: glyphUVFloat32Array.subarray(numRows * (columnIndex) * glyphUVMarch * verticesPerGlyph, numRows * (columnIndex + 1) * glyphUVMarch * verticesPerGlyph),
|
|
||||||
};
|
|
||||||
return column;
|
|
||||||
});
|
|
||||||
|
|
||||||
geometry.addAttribute('position', new THREE.BufferAttribute(glyphPositionFloat32Array, glyphPositionMarch));
|
|
||||||
geometry.addAttribute('brightness', new THREE.BufferAttribute(glyphBrightnessFloat32Array, glyphBrightnessMarch));
|
|
||||||
geometry.addAttribute('uv', new THREE.BufferAttribute(glyphUVFloat32Array, glyphUVMarch));
|
|
||||||
geometry.setIndex(glyphIndexArray);
|
|
||||||
|
|
||||||
const flattenedUVTemplate = [].concat(...glyphUVTemplate);
|
|
||||||
const uvScrap = [];
|
|
||||||
let last = NaN;
|
|
||||||
|
|
||||||
const SQRT_2 = Math.sqrt(2);
|
|
||||||
const SQRT_5 = Math.sqrt(5);
|
|
||||||
|
|
||||||
const minimumPostProcessingFrameTime = 1;
|
|
||||||
|
|
||||||
const brightnessChangeBias = animationSpeed == 0 ? 1 : Math.min(1, Math.abs(animationSpeed));
|
|
||||||
|
|
||||||
const fract = x => x < 0 ? (1 - (-x % 1)) : (x % 1);
|
|
||||||
|
|
||||||
const update = now => {
|
|
||||||
if (now - last > 50) {
|
|
||||||
last = now;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const delta = ((isNaN(last) || now - last > 1000) ? 0 : now - last) / 1000 * animationSpeed;
|
|
||||||
last = now;
|
|
||||||
|
|
||||||
const simTime = now * animationSpeed * fallSpeed * 0.0005;
|
|
||||||
|
|
||||||
for (const column of columns) {
|
|
||||||
|
|
||||||
const columnTime = (column.timeOffset * 1000 + simTime) * (0.5 + column.speedOffset * 0.5) + (Math.sin(simTime * 2 * column.speedOffset) * 0.2);
|
|
||||||
|
|
||||||
for (let rowIndex = 0; rowIndex < column.glyphs.length; rowIndex++) {
|
|
||||||
const glyph = column.glyphs[rowIndex];
|
|
||||||
const glyphTime = rowIndex * 0.01 + columnTime;
|
|
||||||
const value = 1 - fract((glyphTime + 0.3 * Math.sin(SQRT_2 * glyphTime) + 0.2 * Math.sin(SQRT_5 * glyphTime)));
|
|
||||||
|
|
||||||
const computedBrightness = a + b * Math.log(c * (value - 0.5));
|
|
||||||
const newBrightness = isNaN(computedBrightness) ? 0 : Math.min(1, Math.max(0, computedBrightness));
|
|
||||||
glyph.brightness = glyph.brightness * (1 - brightnessChangeBias) + newBrightness * brightnessChangeBias;
|
|
||||||
|
|
||||||
column.brightnessArray.set(glyphBrightnessTemplate.map(() => glyph.brightness), rowIndex * verticesPerGlyph * glyphBrightnessMarch);
|
|
||||||
|
|
||||||
const glyphCycleSpeed = delta * cycleSpeed * 0.2 * Math.pow(1 - glyph.brightness, 4);
|
|
||||||
glyph.cycle = fract(glyph.cycle + glyphCycleSpeed);
|
|
||||||
const symbol = Math.floor(glyphSequenceLength * glyph.cycle);
|
|
||||||
if (glyph.symbol != symbol) {
|
|
||||||
glyph.symbol = symbol;
|
|
||||||
const symbolX = (symbol % 8) / 8;
|
|
||||||
const symbolY = (7 - (symbol - symbolX * 8) / 8) / 8;
|
|
||||||
for (let i = 0; i < 4; i++) {
|
|
||||||
uvScrap[i * 2 + 0] = flattenedUVTemplate[i * 2 + 0] + symbolX;
|
|
||||||
uvScrap[i * 2 + 1] = flattenedUVTemplate[i * 2 + 1] + symbolY;
|
|
||||||
}
|
|
||||||
column.uvArray.set(uvScrap, rowIndex * verticesPerGlyph * glyphUVMarch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
geometry.attributes.uv.needsUpdate = true;
|
|
||||||
geometry.attributes.brightness.needsUpdate = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {geometry, update};
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
const makeMatrixMaterial = (texture, sharpness) => new THREE.RawShaderMaterial({
|
|
||||||
uniforms: {
|
|
||||||
map: { "type": "t", value: texture },
|
|
||||||
sharpness: { "type": "f", value: sharpness },
|
|
||||||
},
|
|
||||||
vertexShader:`
|
|
||||||
attribute vec2 uv;
|
|
||||||
attribute vec3 position;
|
|
||||||
attribute float brightness;
|
|
||||||
uniform mat4 projectionMatrix;
|
|
||||||
uniform mat4 modelViewMatrix;
|
|
||||||
varying vec2 vUV;
|
|
||||||
varying float vBrightness;
|
|
||||||
void main(void) {
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
||||||
vUV = uv;
|
|
||||||
vBrightness = brightness;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
fragmentShader:`
|
|
||||||
#ifdef GL_OES_standard_derivatives
|
|
||||||
#extension GL_OES_standard_derivatives: enable
|
|
||||||
#endif
|
|
||||||
precision lowp float;
|
|
||||||
#define BIG_ENOUGH 0.001
|
|
||||||
#define MODIFIED_ALPHATEST (0.02 * isBigEnough / BIG_ENOUGH)
|
|
||||||
uniform sampler2D map;
|
|
||||||
uniform float sharpness;
|
|
||||||
varying vec2 vUV;
|
|
||||||
varying float vBrightness;
|
|
||||||
float median(float r, float g, float b) {
|
|
||||||
return max(min(r, g), min(max(r, g), b));
|
|
||||||
}
|
|
||||||
void main() {
|
|
||||||
vec3 sample = texture2D(map, vUV).rgb;
|
|
||||||
float sigDist = median(sample.r, sample.g, sample.b) - 0.5;
|
|
||||||
float alpha = clamp(sigDist/fwidth(sigDist) + 0.5, 0.0, 1.0);
|
|
||||||
float dscale = 0.353505 / sharpness;
|
|
||||||
vec2 duv = dscale * (dFdx(vUV) + dFdy(vUV));
|
|
||||||
float isBigEnough = max(abs(duv.x), abs(duv.y));
|
|
||||||
if (isBigEnough > BIG_ENOUGH) {
|
|
||||||
float ratio = BIG_ENOUGH / isBigEnough;
|
|
||||||
alpha = ratio * alpha + (1.0 - ratio) * (sigDist + 0.5);
|
|
||||||
}
|
|
||||||
if (isBigEnough <= BIG_ENOUGH && alpha < 0.5) { discard; return; }
|
|
||||||
if (alpha < 0.5 * MODIFIED_ALPHATEST) { discard; return; }
|
|
||||||
gl_FragColor = vec4(vec3(vBrightness * alpha), 1);
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
217
js/MatrixRenderer.js
Normal file
217
js/MatrixRenderer.js
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
const makeMatrixRenderer = (renderer, texture, {
|
||||||
|
sharpness,
|
||||||
|
numColumns,
|
||||||
|
animationSpeed, fallSpeed, cycleSpeed,
|
||||||
|
glyphSequenceLength,
|
||||||
|
numGlyphColumns
|
||||||
|
}) => {
|
||||||
|
const matrixRenderer = {};
|
||||||
|
const camera = new THREE.OrthographicCamera( -0.5, 0.5, 0.5, -0.5, 0.0001, 10000 );
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
const gpuCompute = new GPUComputationRenderer( numColumns, numColumns, renderer );
|
||||||
|
const glyphValue = gpuCompute.createTexture();
|
||||||
|
const pixels = glyphValue.image.data;
|
||||||
|
for (let i = 0; i < numColumns * numColumns; i++) {
|
||||||
|
pixels[i * 4 + 0] = 0;
|
||||||
|
pixels[i * 4 + 1] = Math.random();
|
||||||
|
pixels[i * 4 + 2] = 0;
|
||||||
|
pixels[i * 4 + 3] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const glyphVariable = gpuCompute.addVariable(
|
||||||
|
"glyph",
|
||||||
|
`
|
||||||
|
precision highp float;
|
||||||
|
#define PI 3.14159265359
|
||||||
|
#define SQRT_2 1.4142135623730951
|
||||||
|
#define SQRT_5 2.23606797749979
|
||||||
|
uniform float now;
|
||||||
|
uniform float delta;
|
||||||
|
uniform float animationSpeed;
|
||||||
|
uniform float fallSpeed;
|
||||||
|
uniform float cycleSpeed;
|
||||||
|
uniform float brightnessChangeBias;
|
||||||
|
uniform float glyphSequenceLength;
|
||||||
|
uniform float numGlyphColumns;
|
||||||
|
|
||||||
|
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() {
|
||||||
|
|
||||||
|
vec2 cellSize = 1.0 / resolution.xy;
|
||||||
|
vec2 uv = (gl_FragCoord.xy) * cellSize;
|
||||||
|
|
||||||
|
float columnTimeOffset = rand(vec2(gl_FragCoord.x, 0.0));
|
||||||
|
float columnSpeedOffset = rand(vec2(gl_FragCoord.x + 0.1, 0.0));
|
||||||
|
|
||||||
|
vec4 data = texture2D( glyph, uv );
|
||||||
|
|
||||||
|
float brightness = data.r;
|
||||||
|
float cycle = data.g;
|
||||||
|
|
||||||
|
float simTime = now * 0.0005 * animationSpeed * fallSpeed;
|
||||||
|
float columnTime = (columnTimeOffset * 1000.0 + simTime) * (0.5 + columnSpeedOffset * 0.5) + (sin(simTime * 2.0 * columnSpeedOffset) * 0.2);
|
||||||
|
float glyphTime = gl_FragCoord.y * 0.01 + columnTime;
|
||||||
|
|
||||||
|
float value = 1.0 - fract((glyphTime + 0.3 * sin(SQRT_2 * glyphTime) + 0.2 * sin(SQRT_5 * glyphTime)));
|
||||||
|
|
||||||
|
// float newBrightness = clamp(b * 3.0 * log(c * value), 0.0, 1.0);
|
||||||
|
float newBrightness = 3.0 * log(value * 1.25);
|
||||||
|
|
||||||
|
brightness = mix(brightness, newBrightness, brightnessChangeBias);
|
||||||
|
|
||||||
|
|
||||||
|
float glyphCycleSpeed = delta * cycleSpeed * 0.2 * pow(1.0 - brightness, 4.0);
|
||||||
|
cycle = fract(cycle + glyphCycleSpeed);
|
||||||
|
float symbol = floor(glyphSequenceLength * cycle);
|
||||||
|
float symbolX = mod(symbol, numGlyphColumns);
|
||||||
|
float symbolY = ((numGlyphColumns - 1.0) - (symbol - symbolX) / numGlyphColumns);
|
||||||
|
|
||||||
|
gl_FragColor = vec4(1.0);
|
||||||
|
gl_FragColor.r = brightness;
|
||||||
|
gl_FragColor.g = cycle;
|
||||||
|
gl_FragColor.b = symbolX / numGlyphColumns;
|
||||||
|
gl_FragColor.a = symbolY / numGlyphColumns;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
,
|
||||||
|
glyphValue
|
||||||
|
);
|
||||||
|
gpuCompute.setVariableDependencies( glyphVariable, [ glyphVariable ] );
|
||||||
|
|
||||||
|
const brightnessChangeBias = animationSpeed == 0 ? 1 : Math.min(1, Math.abs(animationSpeed));
|
||||||
|
Object.assign(glyphVariable.material.uniforms, {
|
||||||
|
now: { type: "f", value: 0 },
|
||||||
|
delta: { type: "f", value: 0.01 },
|
||||||
|
animationSpeed: { type: "f", value: animationSpeed },
|
||||||
|
fallSpeed: { type: "f", value: fallSpeed },
|
||||||
|
cycleSpeed: {type: "f", value: cycleSpeed },
|
||||||
|
glyphSequenceLength: { type: "f", value: glyphSequenceLength },
|
||||||
|
numGlyphColumns: {type: "f", value: numGlyphColumns },
|
||||||
|
brightnessChangeBias: { type: "f", value: brightnessChangeBias },
|
||||||
|
});
|
||||||
|
|
||||||
|
const error = gpuCompute.init();
|
||||||
|
if ( error !== null ) {
|
||||||
|
console.error( error );
|
||||||
|
}
|
||||||
|
|
||||||
|
const glyphRTT = gpuCompute.getCurrentRenderTarget( glyphVariable ).texture;
|
||||||
|
|
||||||
|
const mesh = new THREE.Mesh(
|
||||||
|
new THREE.PlaneBufferGeometry(),
|
||||||
|
new THREE.RawShaderMaterial({
|
||||||
|
uniforms: {
|
||||||
|
glyphs: { type: "t", value: glyphRTT },
|
||||||
|
msdf: { type: "t", value: texture },
|
||||||
|
numColumns: {type: "f", value: numColumns},
|
||||||
|
sharpness: { type: "f", value: sharpness },
|
||||||
|
numGlyphColumns: {type: "f", value: numGlyphColumns},
|
||||||
|
},
|
||||||
|
vertexShader: `
|
||||||
|
attribute vec2 uv;
|
||||||
|
attribute vec3 position;
|
||||||
|
uniform mat4 projectionMatrix;
|
||||||
|
uniform mat4 modelViewMatrix;
|
||||||
|
varying vec2 vUV;
|
||||||
|
void main() {
|
||||||
|
vUV = uv;
|
||||||
|
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
fragmentShader: `
|
||||||
|
#ifdef GL_OES_standard_derivatives
|
||||||
|
#extension GL_OES_standard_derivatives: enable
|
||||||
|
#endif
|
||||||
|
precision lowp float;
|
||||||
|
#define BIG_ENOUGH 0.001
|
||||||
|
#define MODIFIED_ALPHATEST (0.02 * isBigEnough / BIG_ENOUGH)
|
||||||
|
uniform float sharpness;
|
||||||
|
uniform sampler2D msdf;
|
||||||
|
uniform sampler2D glyphs;
|
||||||
|
uniform float numColumns;
|
||||||
|
uniform float numGlyphColumns;
|
||||||
|
varying vec2 vUV;
|
||||||
|
|
||||||
|
float median(float r, float g, float b) {
|
||||||
|
return max(min(r, g), min(max(r, g), b));
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
// Unpack the values from the glyph texture
|
||||||
|
vec4 glyph = texture2D(glyphs, vUV);
|
||||||
|
float brightness = glyph.r;
|
||||||
|
vec2 symbolUV = glyph.ba;
|
||||||
|
vec4 sample = texture2D(msdf, fract(vUV * numColumns) / numGlyphColumns + symbolUV);
|
||||||
|
|
||||||
|
// The rest is straight up MSDF
|
||||||
|
float sigDist = median(sample.r, sample.g, sample.b) - 0.5;
|
||||||
|
float alpha = clamp(sigDist/fwidth(sigDist) + 0.5, 0.0, 1.0);
|
||||||
|
float dscale = 0.353505 / sharpness;
|
||||||
|
vec2 duv = dscale * (dFdx(vUV) + dFdy(vUV));
|
||||||
|
float isBigEnough = max(abs(duv.x), abs(duv.y));
|
||||||
|
if (isBigEnough > BIG_ENOUGH) {
|
||||||
|
float ratio = BIG_ENOUGH / isBigEnough;
|
||||||
|
alpha = ratio * alpha + (1.0 - ratio) * (sigDist + 0.5);
|
||||||
|
}
|
||||||
|
if (isBigEnough <= BIG_ENOUGH && alpha < 0.5) { discard; return; }
|
||||||
|
if (alpha < 0.5 * MODIFIED_ALPHATEST) { discard; return; }
|
||||||
|
|
||||||
|
gl_FragColor = vec4(vec3(brightness * alpha), 1);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
})
|
||||||
|
);
|
||||||
|
scene.add( mesh );
|
||||||
|
|
||||||
|
let start = NaN;
|
||||||
|
let last = NaN;
|
||||||
|
|
||||||
|
matrixRenderer.pass = new THREE.RenderPass( scene, camera );
|
||||||
|
|
||||||
|
matrixRenderer.render = () => {
|
||||||
|
if (isNaN(start)) {
|
||||||
|
start = Date.now();
|
||||||
|
last = 0;
|
||||||
|
}
|
||||||
|
const now = Date.now() - start;
|
||||||
|
|
||||||
|
if (now - last > 50) {
|
||||||
|
last = now;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const delta = ((now - last > 1000) ? 0 : now - last) / 1000 * animationSpeed;
|
||||||
|
last = now;
|
||||||
|
|
||||||
|
glyphVariable.material.uniforms.now.value = now;
|
||||||
|
glyphVariable.material.uniforms.delta.value = delta;
|
||||||
|
|
||||||
|
gpuCompute.compute(); // Do the gpu computation
|
||||||
|
renderer.render( scene, camera );
|
||||||
|
};
|
||||||
|
|
||||||
|
matrixRenderer.resize = (width, height) => {
|
||||||
|
const ratio = height / width;
|
||||||
|
const frac = 0.5;
|
||||||
|
if (ratio < 1) {
|
||||||
|
camera.left = -frac;
|
||||||
|
camera.right = frac;
|
||||||
|
camera.bottom = (camera.left - camera.right) * ratio + frac;
|
||||||
|
camera.top = frac;
|
||||||
|
} else {
|
||||||
|
camera.bottom = -frac;
|
||||||
|
camera.top = frac;
|
||||||
|
camera.left = camera.bottom / ratio;
|
||||||
|
camera.right = camera.top / ratio;
|
||||||
|
}
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
};
|
||||||
|
|
||||||
|
return matrixRenderer;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user