mirror of
https://github.com/Rezmason/matrix.git
synced 2026-04-16 05:19: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:
|
||||
|
||||
Migrate rain logic to shaders that operate on double buffer RTTs
|
||||
|
||||
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:
|
||||
Optimizations
|
||||
@@ -11,14 +18,8 @@ Much later:
|
||||
Dissolve threejs project into webgl project
|
||||
Maybe webgl2 project?
|
||||
Deluxe
|
||||
Raindrop sound
|
||||
https://youtu.be/KoQOKq1C3O4?t=30
|
||||
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
|
||||
Flashing row effect
|
||||
Square event
|
||||
More patterns?
|
||||
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/GPUComputationRenderer.js"></script>
|
||||
|
||||
<script src="./js/MatrixMaterial.js"></script>
|
||||
<script src="./js/MatrixGeometry.js"></script>
|
||||
<script src="./js/MatrixRenderer.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -33,22 +32,14 @@
|
||||
const fallSpeed = parseFloat(getParam("fallSpeed", 1));
|
||||
const cycleSpeed = parseFloat(getParam("cycleSpeed", 1));
|
||||
const numColumns = parseInt(getParam("width", 80));
|
||||
const numRows = numColumns;
|
||||
const glyphSequenceLength = 57;
|
||||
|
||||
const a = parseFloat(getParam("a", 1.125));
|
||||
const b = parseFloat(getParam("b", 1.125));
|
||||
const c = parseFloat(getParam("c", 1.25));
|
||||
const numGlyphColumns = 8;
|
||||
|
||||
const effect = getParam("effect", "plain");
|
||||
|
||||
document.ontouchmove = (e) => e.preventDefault();
|
||||
const element = document.createElement("matrixcode");
|
||||
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" });
|
||||
renderer.sortObjects = true;
|
||||
renderer.setPixelRatio(window.devicePixelRatio);
|
||||
@@ -57,18 +48,17 @@
|
||||
const composer = new THREE.EffectComposer( renderer );
|
||||
|
||||
const texture = new THREE.TextureLoader().load( './matrixcode_msdf.png' );
|
||||
const material = makeMatrixMaterial(texture, sharpness);
|
||||
const {geometry, update} = makeMatrixGeometry({
|
||||
numRows, numColumns,
|
||||
|
||||
const matrixRenderer = makeMatrixRenderer(renderer, texture, {
|
||||
sharpness,
|
||||
numColumns,
|
||||
animationSpeed, fallSpeed, cycleSpeed,
|
||||
a, b, c,
|
||||
glyphSequenceLength,
|
||||
numGlyphColumns
|
||||
});
|
||||
|
||||
const mesh = new THREE.Mesh( geometry, material );
|
||||
scene.add(mesh);
|
||||
|
||||
composer.addPass( new THREE.RenderPass( scene, camera ) );
|
||||
matrixRenderer.pass.renderToScreen = false;
|
||||
composer.addPass( matrixRenderer.pass );
|
||||
|
||||
const bloomPass = new THREE.UnrealBloomPass( new THREE.Vector2( window.innerWidth, window.innerHeight ), 2, 0.5, 0.3 );
|
||||
composer.addPass( bloomPass );
|
||||
@@ -110,26 +100,12 @@
|
||||
break;
|
||||
}
|
||||
|
||||
composer.passes.filter(pass => !pass.enabled).renderToScreen = false;
|
||||
composer.passes.filter(pass => pass.enabled).pop().renderToScreen = true;
|
||||
composer.passes[composer.passes.length - 1].renderToScreen = true;
|
||||
|
||||
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);
|
||||
matrixRenderer.resize(width, height);
|
||||
bloomPass.setSize( window.innerWidth, window.innerHeight );
|
||||
}
|
||||
window.addEventListener("resize", windowResize, false);
|
||||
@@ -138,7 +114,7 @@
|
||||
|
||||
const render = () => {
|
||||
requestAnimationFrame(render);
|
||||
update(Date.now());
|
||||
matrixRenderer.render();
|
||||
composer.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