Replaced MatrixGeometry and MatrixMaterial with MatrixRenderer. Got rid of a,b,c variables.

This commit is contained in:
Rezmason
2018-09-05 15:28:01 -07:00
parent 1c6e351352
commit a287bccd1c
6 changed files with 240 additions and 465 deletions

View File

@@ -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

View File

@@ -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>

View File

@@ -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();

View File

@@ -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};
}

View File

@@ -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
View 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;
};