mirror of
https://github.com/Rezmason/matrix.git
synced 2026-04-14 12:29:30 -07:00
Ran the format script
This commit is contained in:
50
js/Matrix.js
50
js/Matrix.js
@@ -107,34 +107,32 @@ import makeConfig from "./utils/config";
|
||||
|
||||
/** @param {MatrixProps} props */
|
||||
export const Matrix = memo((props) => {
|
||||
const { style, className, ...rest } = props;
|
||||
const elProps = { style, className };
|
||||
const matrix = useRef(null);
|
||||
const rainRef = useRef(null);
|
||||
const canvasRef = useRef(null);
|
||||
const { style, className, ...rest } = props;
|
||||
const elProps = { style, className };
|
||||
const matrix = useRef(null);
|
||||
const rainRef = useRef(null);
|
||||
const canvasRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.style.width = "100%";
|
||||
canvas.style.height = "100%";
|
||||
canvasRef.current = canvas;
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.style.width = "100%";
|
||||
canvas.style.height = "100%";
|
||||
canvasRef.current = canvas;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
matrix.current.appendChild(canvasRef.current);
|
||||
const gl = canvasRef.current.getContext("webgl");
|
||||
createRain(canvasRef.current, makeConfig({ ...rest }), gl).then(
|
||||
(handles) => {
|
||||
rainRef.current = handles;
|
||||
}
|
||||
);
|
||||
useEffect(() => {
|
||||
matrix.current.appendChild(canvasRef.current);
|
||||
const gl = canvasRef.current.getContext("webgl");
|
||||
createRain(canvasRef.current, makeConfig({ ...rest }), gl).then((handles) => {
|
||||
rainRef.current = handles;
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (rainRef.current) {
|
||||
destroyRain(rainRef.current);
|
||||
}
|
||||
};
|
||||
}, [props]);
|
||||
return () => {
|
||||
if (rainRef.current) {
|
||||
destroyRain(rainRef.current);
|
||||
}
|
||||
};
|
||||
}, [props]);
|
||||
|
||||
return <div ref={matrix} {...elProps}></div>;
|
||||
return <div ref={matrix} {...elProps}></div>;
|
||||
});
|
||||
|
||||
70
js/index.js
70
js/index.js
@@ -6,43 +6,43 @@ import { Matrix } from "./Matrix";
|
||||
const root = createRoot(document.getElementById("root"));
|
||||
let idx = 1;
|
||||
const versions = [
|
||||
"3d",
|
||||
"trinity",
|
||||
"bugs",
|
||||
"megacity",
|
||||
"nightmare",
|
||||
"paradise",
|
||||
"resurrections",
|
||||
"operator",
|
||||
"holoplay",
|
||||
"throwback",
|
||||
"updated",
|
||||
"1999",
|
||||
"2003",
|
||||
"2021",
|
||||
"3d",
|
||||
"trinity",
|
||||
"bugs",
|
||||
"megacity",
|
||||
"nightmare",
|
||||
"paradise",
|
||||
"resurrections",
|
||||
"operator",
|
||||
"holoplay",
|
||||
"throwback",
|
||||
"updated",
|
||||
"1999",
|
||||
"2003",
|
||||
"2021",
|
||||
];
|
||||
const App = () => {
|
||||
const [version, setVersion] = React.useState(versions[0]);
|
||||
// const [number, setNumber] = React.useState(0);
|
||||
const onButtonClick = () => {
|
||||
setVersion((s) => {
|
||||
const newVersion = versions[idx];
|
||||
idx = (idx + 1) % versions.length;
|
||||
console.log(newVersion);
|
||||
return newVersion;
|
||||
});
|
||||
};
|
||||
// const newNum = () => setNumber((n) => n + 1);
|
||||
console.log("version", version);
|
||||
// console.log("num", number);
|
||||
const [version, setVersion] = React.useState(versions[0]);
|
||||
// const [number, setNumber] = React.useState(0);
|
||||
const onButtonClick = () => {
|
||||
setVersion((s) => {
|
||||
const newVersion = versions[idx];
|
||||
idx = (idx + 1) % versions.length;
|
||||
console.log(newVersion);
|
||||
return newVersion;
|
||||
});
|
||||
};
|
||||
// const newNum = () => setNumber((n) => n + 1);
|
||||
console.log("version", version);
|
||||
// console.log("num", number);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Rain</h1>
|
||||
<button onClick={onButtonClick}>change version</button>
|
||||
{/* <button onClick={newNum}>change number</button> */}
|
||||
<Matrix version={version} density={7.0} />
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<h1>Rain</h1>
|
||||
<button onClick={onButtonClick}>change version</button>
|
||||
{/* <button onClick={newNum}>change number</button> */}
|
||||
<Matrix version={version} density={7.0} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
root.render(<App />);
|
||||
|
||||
@@ -7,7 +7,11 @@ document.addEventListener("touchmove", (e) => e.preventDefault(), {
|
||||
});
|
||||
|
||||
const supportsWebGPU = async () => {
|
||||
return window.GPUQueue != null && navigator.gpu != null && navigator.gpu.getPreferredCanvasFormat != null;
|
||||
return (
|
||||
window.GPUQueue != null &&
|
||||
navigator.gpu != null &&
|
||||
navigator.gpu.getPreferredCanvasFormat != null
|
||||
);
|
||||
};
|
||||
|
||||
const isRunningSwiftShader = () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { makePassFBO, makePass } from "./utils";
|
||||
import highPassFrag from '../../shaders/glsl/bloomPass.highPass.frag.glsl';
|
||||
import blurFrag from '../../shaders/glsl/bloomPass.blur.frag.glsl';
|
||||
import combineFrag from '../../shaders/glsl/bloomPass.combine.frag.glsl';
|
||||
import highPassFrag from "../../shaders/glsl/bloomPass.highPass.frag.glsl";
|
||||
import blurFrag from "../../shaders/glsl/bloomPass.blur.frag.glsl";
|
||||
import combineFrag from "../../shaders/glsl/bloomPass.combine.frag.glsl";
|
||||
|
||||
// The bloom pass is basically an added high-pass blur.
|
||||
// The blur approximation is the sum of a pyramid of downscaled, blurred textures.
|
||||
@@ -16,7 +16,9 @@ const makePyramid = (regl, height, halfFloat) =>
|
||||
.map((_) => makePassFBO(regl, halfFloat));
|
||||
|
||||
const resizePyramid = (pyramid, vw, vh, scale) =>
|
||||
pyramid.forEach((fbo, index) => fbo.resize(Math.floor((vw * scale) / 2 ** index), Math.floor((vh * scale) / 2 ** index)));
|
||||
pyramid.forEach((fbo, index) =>
|
||||
fbo.resize(Math.floor((vw * scale) / 2 ** index), Math.floor((vh * scale) / 2 ** index)),
|
||||
);
|
||||
|
||||
export default ({ regl, config }, inputs) => {
|
||||
const { bloomStrength, bloomSize, highPassThreshold } = config;
|
||||
@@ -94,12 +96,16 @@ export default ({ regl, config }, inputs) => {
|
||||
const highPassFBO = highPassPyramid[i];
|
||||
const hBlurFBO = hBlurPyramid[i];
|
||||
const vBlurFBO = vBlurPyramid[i];
|
||||
highPass({ fbo: highPassFBO, frag: highPassFrag, tex: i === 0 ? inputs.primary : highPassPyramid[i - 1] });
|
||||
highPass({
|
||||
fbo: highPassFBO,
|
||||
frag: highPassFrag,
|
||||
tex: i === 0 ? inputs.primary : highPassPyramid[i - 1],
|
||||
});
|
||||
blur({ fbo: hBlurFBO, frag: blurFrag, tex: highPassFBO, direction: [1, 0] });
|
||||
blur({ fbo: vBlurFBO, frag: blurFrag, tex: hBlurFBO, direction: [0, 1] });
|
||||
}
|
||||
|
||||
combine({ frag: combineFrag });
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,7 +3,8 @@ import imagePassFrag from "../../shaders/glsl/imagePass.frag.glsl";
|
||||
|
||||
// Multiplies the rendered rain and bloom by a loaded in image
|
||||
|
||||
const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flammarion_Colored.jpg/917px-Flammarion_Colored.jpg";
|
||||
const defaultBGURL =
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flammarion_Colored.jpg/917px-Flammarion_Colored.jpg";
|
||||
|
||||
export default ({ regl, config }, inputs) => {
|
||||
const output = makePassFBO(regl, config.useHalfFloat);
|
||||
@@ -28,6 +29,6 @@ export default ({ regl, config }, inputs) => {
|
||||
if (shouldRender) {
|
||||
render({ frag: imagePassFrag });
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,100 +2,95 @@
|
||||
const HoloPlayCore = require("holoplay-core");
|
||||
|
||||
const recordedDevice = {
|
||||
buttons: [0, 0, 0, 0],
|
||||
calibration: {
|
||||
DPI: { value: 324 },
|
||||
center: { value: 0.15018756687641144 },
|
||||
configVersion: "3.0",
|
||||
flipImageX: { value: 0 },
|
||||
flipImageY: { value: 0 },
|
||||
flipSubp: { value: 0 },
|
||||
fringe: { value: 0 },
|
||||
invView: { value: 1 },
|
||||
pitch: { value: 52.58013153076172 },
|
||||
screenH: { value: 2048 },
|
||||
screenW: { value: 1536 },
|
||||
slope: { value: -7.145165920257568 },
|
||||
verticalAngle: { value: 0 },
|
||||
viewCone: { value: 40 },
|
||||
},
|
||||
defaultQuilt: {
|
||||
quiltAspect: 0.75,
|
||||
quiltX: 3840,
|
||||
quiltY: 3840,
|
||||
tileX: 8,
|
||||
tileY: 6,
|
||||
},
|
||||
hardwareVersion: "portrait",
|
||||
hwid: "LKG-P11063",
|
||||
index: 0,
|
||||
joystickIndex: -1,
|
||||
state: "ok",
|
||||
unityIndex: 1,
|
||||
windowCoords: [1440, 900],
|
||||
buttons: [0, 0, 0, 0],
|
||||
calibration: {
|
||||
DPI: { value: 324 },
|
||||
center: { value: 0.15018756687641144 },
|
||||
configVersion: "3.0",
|
||||
flipImageX: { value: 0 },
|
||||
flipImageY: { value: 0 },
|
||||
flipSubp: { value: 0 },
|
||||
fringe: { value: 0 },
|
||||
invView: { value: 1 },
|
||||
pitch: { value: 52.58013153076172 },
|
||||
screenH: { value: 2048 },
|
||||
screenW: { value: 1536 },
|
||||
slope: { value: -7.145165920257568 },
|
||||
verticalAngle: { value: 0 },
|
||||
viewCone: { value: 40 },
|
||||
},
|
||||
defaultQuilt: {
|
||||
quiltAspect: 0.75,
|
||||
quiltX: 3840,
|
||||
quiltY: 3840,
|
||||
tileX: 8,
|
||||
tileY: 6,
|
||||
},
|
||||
hardwareVersion: "portrait",
|
||||
hwid: "LKG-P11063",
|
||||
index: 0,
|
||||
joystickIndex: -1,
|
||||
state: "ok",
|
||||
unityIndex: 1,
|
||||
windowCoords: [1440, 900],
|
||||
};
|
||||
|
||||
const interpretDevice = (device) => {
|
||||
if (device == null) {
|
||||
return { enabled: false, tileX: 1, tileY: 1 };
|
||||
}
|
||||
if (device == null) {
|
||||
return { enabled: false, tileX: 1, tileY: 1 };
|
||||
}
|
||||
|
||||
const fov = 15;
|
||||
const fov = 15;
|
||||
|
||||
const calibration = Object.fromEntries(
|
||||
Object.entries(device.calibration)
|
||||
.map(([key, value]) => [key, value.value])
|
||||
.filter(([key, value]) => value != null)
|
||||
);
|
||||
const calibration = Object.fromEntries(
|
||||
Object.entries(device.calibration)
|
||||
.map(([key, value]) => [key, value.value])
|
||||
.filter(([key, value]) => value != null),
|
||||
);
|
||||
|
||||
const screenInches = calibration.screenW / calibration.DPI;
|
||||
const pitch =
|
||||
calibration.pitch *
|
||||
screenInches *
|
||||
Math.cos(Math.atan(1.0 / calibration.slope));
|
||||
const tilt =
|
||||
(calibration.screenH / (calibration.screenW * calibration.slope)) *
|
||||
-(calibration.flipImageX * 2 - 1);
|
||||
const subp = 1 / (calibration.screenW * 3);
|
||||
const screenInches = calibration.screenW / calibration.DPI;
|
||||
const pitch = calibration.pitch * screenInches * Math.cos(Math.atan(1.0 / calibration.slope));
|
||||
const tilt =
|
||||
(calibration.screenH / (calibration.screenW * calibration.slope)) *
|
||||
-(calibration.flipImageX * 2 - 1);
|
||||
const subp = 1 / (calibration.screenW * 3);
|
||||
|
||||
const defaultQuilt = device.defaultQuilt;
|
||||
const defaultQuilt = device.defaultQuilt;
|
||||
|
||||
const quiltViewPortion = [
|
||||
(Math.floor(defaultQuilt.quiltX / defaultQuilt.tileX) *
|
||||
defaultQuilt.tileX) /
|
||||
defaultQuilt.quiltX,
|
||||
(Math.floor(defaultQuilt.quiltY / defaultQuilt.tileY) *
|
||||
defaultQuilt.tileY) /
|
||||
defaultQuilt.quiltY,
|
||||
];
|
||||
const quiltViewPortion = [
|
||||
(Math.floor(defaultQuilt.quiltX / defaultQuilt.tileX) * defaultQuilt.tileX) /
|
||||
defaultQuilt.quiltX,
|
||||
(Math.floor(defaultQuilt.quiltY / defaultQuilt.tileY) * defaultQuilt.tileY) /
|
||||
defaultQuilt.quiltY,
|
||||
];
|
||||
|
||||
return {
|
||||
...defaultQuilt,
|
||||
...calibration,
|
||||
pitch,
|
||||
tilt,
|
||||
subp,
|
||||
return {
|
||||
...defaultQuilt,
|
||||
...calibration,
|
||||
pitch,
|
||||
tilt,
|
||||
subp,
|
||||
|
||||
quiltViewPortion,
|
||||
fov,
|
||||
enabled: true,
|
||||
};
|
||||
quiltViewPortion,
|
||||
fov,
|
||||
enabled: true,
|
||||
};
|
||||
};
|
||||
|
||||
export default async (useHoloplay = false, useRecordedDevice = false) => {
|
||||
if (!useHoloplay) {
|
||||
return interpretDevice(null);
|
||||
}
|
||||
// const HoloPlayCore = await import("../../lib/holoplaycore.module.js");
|
||||
const device = await new Promise(
|
||||
(resolve, reject) =>
|
||||
new HoloPlayCore.Client(
|
||||
(data) => resolve(data.devices?.[0]),
|
||||
(error) => resolve(null)
|
||||
)
|
||||
);
|
||||
if (device == null && useRecordedDevice) {
|
||||
return interpretDevice(recordedDevice);
|
||||
}
|
||||
return interpretDevice(device);
|
||||
if (!useHoloplay) {
|
||||
return interpretDevice(null);
|
||||
}
|
||||
// const HoloPlayCore = await import("../../lib/holoplaycore.module.js");
|
||||
const device = await new Promise(
|
||||
(resolve, reject) =>
|
||||
new HoloPlayCore.Client(
|
||||
(data) => resolve(data.devices?.[0]),
|
||||
(error) => resolve(null),
|
||||
),
|
||||
);
|
||||
if (device == null && useRecordedDevice) {
|
||||
return interpretDevice(recordedDevice);
|
||||
}
|
||||
return interpretDevice(device);
|
||||
};
|
||||
|
||||
236
js/regl/main.js
236
js/regl/main.js
@@ -7,24 +7,20 @@ import makeStripePass from "./stripePass.js";
|
||||
import makeImagePass from "./imagePass.js";
|
||||
import makeQuiltPass from "./quiltPass.js";
|
||||
import makeMirrorPass from "./mirrorPass.js";
|
||||
import {
|
||||
setupCamera,
|
||||
cameraCanvas,
|
||||
cameraAspectRatio,
|
||||
} from "../utils/camera.js";
|
||||
import { setupCamera, cameraCanvas, cameraAspectRatio } from "../utils/camera.js";
|
||||
import getLKG from "./lkgHelper.js";
|
||||
|
||||
const effects = {
|
||||
none: null,
|
||||
plain: makePalettePass,
|
||||
palette: makePalettePass,
|
||||
customStripes: makeStripePass,
|
||||
stripes: makeStripePass,
|
||||
pride: makeStripePass,
|
||||
transPride: makeStripePass,
|
||||
trans: makeStripePass,
|
||||
image: makeImagePass,
|
||||
mirror: makeMirrorPass,
|
||||
none: null,
|
||||
plain: makePalettePass,
|
||||
palette: makePalettePass,
|
||||
customStripes: makeStripePass,
|
||||
stripes: makeStripePass,
|
||||
pride: makeStripePass,
|
||||
transPride: makeStripePass,
|
||||
trans: makeStripePass,
|
||||
image: makeImagePass,
|
||||
mirror: makeMirrorPass,
|
||||
};
|
||||
|
||||
const dimensions = { width: 1, height: 1 };
|
||||
@@ -41,132 +37,124 @@ const dimensions = { width: 1, height: 1 };
|
||||
// Promise.all([loadJS("lib/regl.min.js"), loadJS("lib/gl-matrix.js")]);
|
||||
|
||||
export const createRain = async (canvas, config, gl) => {
|
||||
const resize = () => {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = Math.ceil(window.innerWidth * dpr * config.resolution);
|
||||
canvas.height = Math.ceil(window.innerHeight * dpr * config.resolution);
|
||||
};
|
||||
const resize = () => {
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
canvas.width = Math.ceil(window.innerWidth * dpr * config.resolution);
|
||||
canvas.height = Math.ceil(window.innerHeight * dpr * config.resolution);
|
||||
};
|
||||
|
||||
window.onresize = resize;
|
||||
if (document.fullscreenEnabled || document.webkitFullscreenEnabled) {
|
||||
window.ondblclick = () => {
|
||||
if (document.fullscreenElement == null) {
|
||||
if (canvas.webkitRequestFullscreen != null) {
|
||||
canvas.webkitRequestFullscreen();
|
||||
} else {
|
||||
canvas.requestFullscreen();
|
||||
}
|
||||
} else {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
};
|
||||
}
|
||||
resize();
|
||||
window.onresize = resize;
|
||||
if (document.fullscreenEnabled || document.webkitFullscreenEnabled) {
|
||||
window.ondblclick = () => {
|
||||
if (document.fullscreenElement == null) {
|
||||
if (canvas.webkitRequestFullscreen != null) {
|
||||
canvas.webkitRequestFullscreen();
|
||||
} else {
|
||||
canvas.requestFullscreen();
|
||||
}
|
||||
} else {
|
||||
document.exitFullscreen();
|
||||
}
|
||||
};
|
||||
}
|
||||
resize();
|
||||
|
||||
if (config.useCamera) {
|
||||
await setupCamera();
|
||||
}
|
||||
if (config.useCamera) {
|
||||
await setupCamera();
|
||||
}
|
||||
|
||||
const extensions = [
|
||||
"OES_texture_half_float",
|
||||
"OES_texture_half_float_linear",
|
||||
];
|
||||
// These extensions are also needed, but Safari misreports that they are missing
|
||||
const optionalExtensions = [
|
||||
"EXT_color_buffer_half_float",
|
||||
"WEBGL_color_buffer_float",
|
||||
"OES_standard_derivatives",
|
||||
];
|
||||
const extensions = ["OES_texture_half_float", "OES_texture_half_float_linear"];
|
||||
// These extensions are also needed, but Safari misreports that they are missing
|
||||
const optionalExtensions = [
|
||||
"EXT_color_buffer_half_float",
|
||||
"WEBGL_color_buffer_float",
|
||||
"OES_standard_derivatives",
|
||||
];
|
||||
|
||||
switch (config.testFix) {
|
||||
case "fwidth_10_1_2022_A":
|
||||
extensions.push("OES_standard_derivatives");
|
||||
break;
|
||||
case "fwidth_10_1_2022_B":
|
||||
optionalExtensions.forEach((ext) => extensions.push(ext));
|
||||
extensions.length = 0;
|
||||
break;
|
||||
}
|
||||
switch (config.testFix) {
|
||||
case "fwidth_10_1_2022_A":
|
||||
extensions.push("OES_standard_derivatives");
|
||||
break;
|
||||
case "fwidth_10_1_2022_B":
|
||||
optionalExtensions.forEach((ext) => extensions.push(ext));
|
||||
extensions.length = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
const regl = createREGL({
|
||||
gl,
|
||||
pixelRatio: 1,
|
||||
extensions,
|
||||
optionalExtensions,
|
||||
});
|
||||
const regl = createREGL({
|
||||
gl,
|
||||
pixelRatio: 1,
|
||||
extensions,
|
||||
optionalExtensions,
|
||||
});
|
||||
|
||||
const cameraTex = regl.texture(cameraCanvas);
|
||||
const lkg = await getLKG(config.useHoloplay, true);
|
||||
const cameraTex = regl.texture(cameraCanvas);
|
||||
const lkg = await getLKG(config.useHoloplay, true);
|
||||
|
||||
// All this takes place in a full screen quad.
|
||||
const fullScreenQuad = makeFullScreenQuad(regl);
|
||||
const effectName = config.effect in effects ? config.effect : "palette";
|
||||
const context = { regl, config, lkg, cameraTex, cameraAspectRatio };
|
||||
const pipeline = makePipeline(context, [
|
||||
makeRain,
|
||||
makeBloomPass,
|
||||
effects[effectName],
|
||||
makeQuiltPass,
|
||||
]);
|
||||
// All this takes place in a full screen quad.
|
||||
const fullScreenQuad = makeFullScreenQuad(regl);
|
||||
const effectName = config.effect in effects ? config.effect : "palette";
|
||||
const context = { regl, config, lkg, cameraTex, cameraAspectRatio };
|
||||
const pipeline = makePipeline(context, [
|
||||
makeRain,
|
||||
makeBloomPass,
|
||||
effects[effectName],
|
||||
makeQuiltPass,
|
||||
]);
|
||||
|
||||
const screenUniforms = { tex: pipeline[pipeline.length - 1].outputs.primary };
|
||||
const drawToScreen = regl({ uniforms: screenUniforms });
|
||||
await Promise.all(pipeline.map((step) => step.ready));
|
||||
pipeline.forEach((step) => step.setSize(canvas.width, canvas.height));
|
||||
dimensions.width = canvas.width;
|
||||
dimensions.height = canvas.height;
|
||||
const screenUniforms = { tex: pipeline[pipeline.length - 1].outputs.primary };
|
||||
const drawToScreen = regl({ uniforms: screenUniforms });
|
||||
await Promise.all(pipeline.map((step) => step.ready));
|
||||
pipeline.forEach((step) => step.setSize(canvas.width, canvas.height));
|
||||
dimensions.width = canvas.width;
|
||||
dimensions.height = canvas.height;
|
||||
|
||||
const targetFrameTimeMilliseconds = 1000 / config.fps;
|
||||
let last = NaN;
|
||||
const targetFrameTimeMilliseconds = 1000 / config.fps;
|
||||
let last = NaN;
|
||||
|
||||
const tick = regl.frame(({ viewportWidth, viewportHeight }) => {
|
||||
if (config.once) {
|
||||
tick.cancel();
|
||||
}
|
||||
const tick = regl.frame(({ viewportWidth, viewportHeight }) => {
|
||||
if (config.once) {
|
||||
tick.cancel();
|
||||
}
|
||||
|
||||
const now = regl.now() * 1000;
|
||||
const now = regl.now() * 1000;
|
||||
|
||||
if (isNaN(last)) {
|
||||
last = now;
|
||||
}
|
||||
if (isNaN(last)) {
|
||||
last = now;
|
||||
}
|
||||
|
||||
const shouldRender =
|
||||
config.fps >= 60 ||
|
||||
now - last >= targetFrameTimeMilliseconds ||
|
||||
config.once == true;
|
||||
const shouldRender =
|
||||
config.fps >= 60 || now - last >= targetFrameTimeMilliseconds || config.once == true;
|
||||
|
||||
if (shouldRender) {
|
||||
while (now - targetFrameTimeMilliseconds > last) {
|
||||
last += targetFrameTimeMilliseconds;
|
||||
}
|
||||
}
|
||||
if (shouldRender) {
|
||||
while (now - targetFrameTimeMilliseconds > last) {
|
||||
last += targetFrameTimeMilliseconds;
|
||||
}
|
||||
}
|
||||
|
||||
if (config.useCamera) {
|
||||
cameraTex(cameraCanvas);
|
||||
}
|
||||
if (
|
||||
dimensions.width !== viewportWidth ||
|
||||
dimensions.height !== viewportHeight
|
||||
) {
|
||||
dimensions.width = viewportWidth;
|
||||
dimensions.height = viewportHeight;
|
||||
for (const step of pipeline) {
|
||||
step.setSize(viewportWidth, viewportHeight);
|
||||
}
|
||||
}
|
||||
fullScreenQuad(() => {
|
||||
for (const step of pipeline) {
|
||||
step.execute(shouldRender);
|
||||
}
|
||||
drawToScreen();
|
||||
});
|
||||
});
|
||||
if (config.useCamera) {
|
||||
cameraTex(cameraCanvas);
|
||||
}
|
||||
if (dimensions.width !== viewportWidth || dimensions.height !== viewportHeight) {
|
||||
dimensions.width = viewportWidth;
|
||||
dimensions.height = viewportHeight;
|
||||
for (const step of pipeline) {
|
||||
step.setSize(viewportWidth, viewportHeight);
|
||||
}
|
||||
}
|
||||
fullScreenQuad(() => {
|
||||
for (const step of pipeline) {
|
||||
step.execute(shouldRender);
|
||||
}
|
||||
drawToScreen();
|
||||
});
|
||||
});
|
||||
|
||||
return { regl, tick, canvas };
|
||||
return { regl, tick, canvas };
|
||||
};
|
||||
|
||||
export const destroyRain = ({ regl, tick, canvas }) => {
|
||||
tick.cancel(); // stop RAF
|
||||
regl.destroy(); // release all GPU resources & event listeners
|
||||
//canvas.remove(); // drop from the DOM
|
||||
tick.cancel(); // stop RAF
|
||||
regl.destroy(); // release all GPU resources & event listeners
|
||||
//canvas.remove(); // drop from the DOM
|
||||
};
|
||||
|
||||
@@ -45,6 +45,6 @@ export default ({ regl, config, cameraTex, cameraAspectRatio }, inputs) => {
|
||||
if (shouldRender) {
|
||||
render({ frag: mirrorPassFrag });
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,47 +8,45 @@ import palettePassFrag from "../../shaders/glsl/palettePass.frag.glsl";
|
||||
// This shader introduces noise into the renders, to avoid banding
|
||||
|
||||
const makePalette = (regl, entries) => {
|
||||
const PALETTE_SIZE = 2048;
|
||||
const paletteColors = Array(PALETTE_SIZE);
|
||||
const PALETTE_SIZE = 2048;
|
||||
const paletteColors = Array(PALETTE_SIZE);
|
||||
|
||||
// Convert HSL gradient into sorted RGB gradient, capping the ends
|
||||
const sortedEntries = entries
|
||||
.slice()
|
||||
.sort((e1, e2) => e1.at - e2.at)
|
||||
.map((entry) => ({
|
||||
rgb: colorToRGB(entry.color),
|
||||
arrayIndex: Math.floor(
|
||||
Math.max(Math.min(1, entry.at), 0) * (PALETTE_SIZE - 1)
|
||||
),
|
||||
}));
|
||||
sortedEntries.unshift({ rgb: sortedEntries[0].rgb, arrayIndex: 0 });
|
||||
sortedEntries.push({
|
||||
rgb: sortedEntries[sortedEntries.length - 1].rgb,
|
||||
arrayIndex: PALETTE_SIZE - 1,
|
||||
});
|
||||
// Convert HSL gradient into sorted RGB gradient, capping the ends
|
||||
const sortedEntries = entries
|
||||
.slice()
|
||||
.sort((e1, e2) => e1.at - e2.at)
|
||||
.map((entry) => ({
|
||||
rgb: colorToRGB(entry.color),
|
||||
arrayIndex: Math.floor(Math.max(Math.min(1, entry.at), 0) * (PALETTE_SIZE - 1)),
|
||||
}));
|
||||
sortedEntries.unshift({ rgb: sortedEntries[0].rgb, arrayIndex: 0 });
|
||||
sortedEntries.push({
|
||||
rgb: sortedEntries[sortedEntries.length - 1].rgb,
|
||||
arrayIndex: PALETTE_SIZE - 1,
|
||||
});
|
||||
|
||||
// Interpolate between the sorted RGB entries to generate
|
||||
// the palette texture data
|
||||
sortedEntries.forEach((entry, index) => {
|
||||
paletteColors[entry.arrayIndex] = entry.rgb.slice();
|
||||
if (index + 1 < sortedEntries.length) {
|
||||
const nextEntry = sortedEntries[index + 1];
|
||||
const diff = nextEntry.arrayIndex - entry.arrayIndex;
|
||||
for (let i = 0; i < diff; i++) {
|
||||
const ratio = i / diff;
|
||||
paletteColors[entry.arrayIndex + i] = [
|
||||
entry.rgb[0] * (1 - ratio) + nextEntry.rgb[0] * ratio,
|
||||
entry.rgb[1] * (1 - ratio) + nextEntry.rgb[1] * ratio,
|
||||
entry.rgb[2] * (1 - ratio) + nextEntry.rgb[2] * ratio,
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
// Interpolate between the sorted RGB entries to generate
|
||||
// the palette texture data
|
||||
sortedEntries.forEach((entry, index) => {
|
||||
paletteColors[entry.arrayIndex] = entry.rgb.slice();
|
||||
if (index + 1 < sortedEntries.length) {
|
||||
const nextEntry = sortedEntries[index + 1];
|
||||
const diff = nextEntry.arrayIndex - entry.arrayIndex;
|
||||
for (let i = 0; i < diff; i++) {
|
||||
const ratio = i / diff;
|
||||
paletteColors[entry.arrayIndex + i] = [
|
||||
entry.rgb[0] * (1 - ratio) + nextEntry.rgb[0] * ratio,
|
||||
entry.rgb[1] * (1 - ratio) + nextEntry.rgb[1] * ratio,
|
||||
entry.rgb[2] * (1 - ratio) + nextEntry.rgb[2] * ratio,
|
||||
];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return make1DTexture(
|
||||
regl,
|
||||
paletteColors.map((rgb) => [...rgb, 1])
|
||||
);
|
||||
return make1DTexture(
|
||||
regl,
|
||||
paletteColors.map((rgb) => [...rgb, 1]),
|
||||
);
|
||||
};
|
||||
|
||||
// The rendered texture's values are mapped to colors in a palette texture.
|
||||
@@ -58,44 +56,44 @@ const makePalette = (regl, entries) => {
|
||||
// in screen space.
|
||||
|
||||
export default ({ regl, config }, inputs) => {
|
||||
const output = makePassFBO(regl, config.useHalfFloat);
|
||||
const paletteTex = makePalette(regl, config.palette);
|
||||
const {
|
||||
backgroundColor,
|
||||
cursorColor,
|
||||
glintColor,
|
||||
cursorIntensity,
|
||||
glintIntensity,
|
||||
ditherMagnitude,
|
||||
} = config;
|
||||
const output = makePassFBO(regl, config.useHalfFloat);
|
||||
const paletteTex = makePalette(regl, config.palette);
|
||||
const {
|
||||
backgroundColor,
|
||||
cursorColor,
|
||||
glintColor,
|
||||
cursorIntensity,
|
||||
glintIntensity,
|
||||
ditherMagnitude,
|
||||
} = config;
|
||||
|
||||
const render = regl({
|
||||
frag: regl.prop("frag"),
|
||||
const render = regl({
|
||||
frag: regl.prop("frag"),
|
||||
|
||||
uniforms: {
|
||||
backgroundColor: colorToRGB(backgroundColor),
|
||||
cursorColor: colorToRGB(cursorColor),
|
||||
glintColor: colorToRGB(glintColor),
|
||||
cursorIntensity,
|
||||
glintIntensity,
|
||||
ditherMagnitude,
|
||||
tex: inputs.primary,
|
||||
bloomTex: inputs.bloom,
|
||||
paletteTex,
|
||||
},
|
||||
framebuffer: output,
|
||||
});
|
||||
uniforms: {
|
||||
backgroundColor: colorToRGB(backgroundColor),
|
||||
cursorColor: colorToRGB(cursorColor),
|
||||
glintColor: colorToRGB(glintColor),
|
||||
cursorIntensity,
|
||||
glintIntensity,
|
||||
ditherMagnitude,
|
||||
tex: inputs.primary,
|
||||
bloomTex: inputs.bloom,
|
||||
paletteTex,
|
||||
},
|
||||
framebuffer: output,
|
||||
});
|
||||
|
||||
return makePass(
|
||||
{
|
||||
primary: output,
|
||||
},
|
||||
palettePassFrag.loaded,
|
||||
(w, h) => output.resize(w, h),
|
||||
(shouldRender) => {
|
||||
if (shouldRender) {
|
||||
render({ frag: palettePassFrag });
|
||||
}
|
||||
}
|
||||
);
|
||||
return makePass(
|
||||
{
|
||||
primary: output,
|
||||
},
|
||||
palettePassFrag.loaded,
|
||||
(w, h) => output.resize(w, h),
|
||||
(shouldRender) => {
|
||||
if (shouldRender) {
|
||||
render({ frag: palettePassFrag });
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -30,6 +30,6 @@ export default ({ regl, config, lkg }, inputs) => {
|
||||
if (shouldRender) {
|
||||
render({ frag: quiltPassFrag });
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
loadImage,
|
||||
makePassFBO,
|
||||
makeDoubleBuffer,
|
||||
makePass,
|
||||
} from "./utils.js";
|
||||
import { loadImage, makePassFBO, makeDoubleBuffer, makePass } from "./utils.js";
|
||||
import { mat4, vec3 } from "gl-matrix";
|
||||
import rainPassIntro from "../../shaders/glsl/rainPass.intro.frag.glsl";
|
||||
import rainPassRaindrop from "../../shaders/glsl/rainPass.raindrop.frag.glsl";
|
||||
@@ -13,13 +8,11 @@ import rainPassVert from "../../shaders/glsl/rainPass.vert.glsl";
|
||||
import rainPassFrag from "../../shaders/glsl/rainPass.frag.glsl";
|
||||
|
||||
const extractEntries = (src, keys) =>
|
||||
Object.fromEntries(
|
||||
Array.from(Object.entries(src)).filter(([key]) => keys.includes(key))
|
||||
);
|
||||
Object.fromEntries(Array.from(Object.entries(src)).filter(([key]) => keys.includes(key)));
|
||||
|
||||
const rippleTypes = {
|
||||
box: 0,
|
||||
circle: 1,
|
||||
box: 0,
|
||||
circle: 1,
|
||||
};
|
||||
|
||||
// These compute buffers are used to compute the properties of cells in the grid.
|
||||
@@ -30,12 +23,12 @@ const rippleTypes = {
|
||||
// These double buffers are smaller than the screen, because their pixels correspond
|
||||
// with cells in the grid, and the cells' glyphs are much larger than a pixel.
|
||||
const makeComputeDoubleBuffer = (regl, height, width) =>
|
||||
makeDoubleBuffer(regl, {
|
||||
width,
|
||||
height,
|
||||
wrapT: "clamp",
|
||||
type: "half float",
|
||||
});
|
||||
makeDoubleBuffer(regl, {
|
||||
width,
|
||||
height,
|
||||
wrapT: "clamp",
|
||||
type: "half float",
|
||||
});
|
||||
|
||||
const numVerticesPerQuad = 2 * 3;
|
||||
const tlVert = [0, 0];
|
||||
@@ -45,352 +38,301 @@ const brVert = [1, 1];
|
||||
const quadVertices = [tlVert, trVert, brVert, tlVert, brVert, blVert];
|
||||
|
||||
export default ({ regl, config, lkg }) => {
|
||||
// The volumetric mode multiplies the number of columns
|
||||
// to reach the desired density, and then overlaps them
|
||||
const volumetric = config.volumetric;
|
||||
const density = volumetric && config.effect !== "none" ? config.density : 1;
|
||||
const [numRows, numColumns] = [
|
||||
config.numColumns,
|
||||
Math.floor(config.numColumns * density),
|
||||
];
|
||||
// The volumetric mode multiplies the number of columns
|
||||
// to reach the desired density, and then overlaps them
|
||||
const volumetric = config.volumetric;
|
||||
const density = volumetric && config.effect !== "none" ? config.density : 1;
|
||||
const [numRows, numColumns] = [config.numColumns, Math.floor(config.numColumns * density)];
|
||||
|
||||
// The volumetric mode requires us to create a grid of quads,
|
||||
// rather than a single quad for our geometry
|
||||
const [numQuadRows, numQuadColumns] = volumetric
|
||||
? [numRows, numColumns]
|
||||
: [1, 1];
|
||||
const numQuads = numQuadRows * numQuadColumns;
|
||||
const quadSize = [1 / numQuadColumns, 1 / numQuadRows];
|
||||
// The volumetric mode requires us to create a grid of quads,
|
||||
// rather than a single quad for our geometry
|
||||
const [numQuadRows, numQuadColumns] = volumetric ? [numRows, numColumns] : [1, 1];
|
||||
const numQuads = numQuadRows * numQuadColumns;
|
||||
const quadSize = [1 / numQuadColumns, 1 / numQuadRows];
|
||||
|
||||
// Various effect-related values
|
||||
const rippleType =
|
||||
config.rippleTypeName in rippleTypes
|
||||
? rippleTypes[config.rippleTypeName]
|
||||
: -1;
|
||||
const slantVec = [Math.cos(config.slant), Math.sin(config.slant)];
|
||||
const slantScale =
|
||||
1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1);
|
||||
const showDebugView = config.effect === "none";
|
||||
// Various effect-related values
|
||||
const rippleType = config.rippleTypeName in rippleTypes ? rippleTypes[config.rippleTypeName] : -1;
|
||||
const slantVec = [Math.cos(config.slant), Math.sin(config.slant)];
|
||||
const slantScale = 1 / (Math.abs(Math.sin(2 * config.slant)) * (Math.sqrt(2) - 1) + 1);
|
||||
const showDebugView = config.effect === "none";
|
||||
|
||||
const commonUniforms = {
|
||||
...extractEntries(config, [
|
||||
"animationSpeed",
|
||||
"glyphHeightToWidth",
|
||||
"glyphSequenceLength",
|
||||
"glyphTextureGridSize",
|
||||
]),
|
||||
numColumns,
|
||||
numRows,
|
||||
showDebugView,
|
||||
};
|
||||
const commonUniforms = {
|
||||
...extractEntries(config, [
|
||||
"animationSpeed",
|
||||
"glyphHeightToWidth",
|
||||
"glyphSequenceLength",
|
||||
"glyphTextureGridSize",
|
||||
]),
|
||||
numColumns,
|
||||
numRows,
|
||||
showDebugView,
|
||||
};
|
||||
|
||||
const introDoubleBuffer = makeComputeDoubleBuffer(regl, 1, numColumns);
|
||||
const introDoubleBuffer = makeComputeDoubleBuffer(regl, 1, numColumns);
|
||||
|
||||
const introUniforms = {
|
||||
...commonUniforms,
|
||||
...extractEntries(config, ["fallSpeed", "skipIntro"]),
|
||||
};
|
||||
const intro = regl({
|
||||
frag: regl.prop("frag"),
|
||||
uniforms: {
|
||||
...introUniforms,
|
||||
previousIntroState: introDoubleBuffer.back,
|
||||
},
|
||||
const introUniforms = {
|
||||
...commonUniforms,
|
||||
...extractEntries(config, ["fallSpeed", "skipIntro"]),
|
||||
};
|
||||
const intro = regl({
|
||||
frag: regl.prop("frag"),
|
||||
uniforms: {
|
||||
...introUniforms,
|
||||
previousIntroState: introDoubleBuffer.back,
|
||||
},
|
||||
|
||||
framebuffer: introDoubleBuffer.front,
|
||||
});
|
||||
framebuffer: introDoubleBuffer.front,
|
||||
});
|
||||
|
||||
const raindropDoubleBuffer = makeComputeDoubleBuffer(
|
||||
regl,
|
||||
numRows,
|
||||
numColumns
|
||||
);
|
||||
|
||||
const raindropUniforms = {
|
||||
...commonUniforms,
|
||||
...extractEntries(config, [
|
||||
"brightnessDecay",
|
||||
"fallSpeed",
|
||||
"raindropLength",
|
||||
"loops",
|
||||
"skipIntro",
|
||||
]),
|
||||
};
|
||||
const raindrop = regl({
|
||||
frag: regl.prop("frag"),
|
||||
uniforms: {
|
||||
...raindropUniforms,
|
||||
introState: introDoubleBuffer.front,
|
||||
previousRaindropState: raindropDoubleBuffer.back,
|
||||
},
|
||||
const raindropDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
|
||||
|
||||
framebuffer: raindropDoubleBuffer.front,
|
||||
});
|
||||
const raindropUniforms = {
|
||||
...commonUniforms,
|
||||
...extractEntries(config, [
|
||||
"brightnessDecay",
|
||||
"fallSpeed",
|
||||
"raindropLength",
|
||||
"loops",
|
||||
"skipIntro",
|
||||
]),
|
||||
};
|
||||
const raindrop = regl({
|
||||
frag: regl.prop("frag"),
|
||||
uniforms: {
|
||||
...raindropUniforms,
|
||||
introState: introDoubleBuffer.front,
|
||||
previousRaindropState: raindropDoubleBuffer.back,
|
||||
},
|
||||
|
||||
const symbolDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
|
||||
framebuffer: raindropDoubleBuffer.front,
|
||||
});
|
||||
|
||||
const symbolUniforms = {
|
||||
...commonUniforms,
|
||||
...extractEntries(config, ["cycleSpeed", "cycleFrameSkip", "loops"]),
|
||||
};
|
||||
const symbol = regl({
|
||||
frag: regl.prop("frag"),
|
||||
uniforms: {
|
||||
...symbolUniforms,
|
||||
raindropState: raindropDoubleBuffer.front,
|
||||
previousSymbolState: symbolDoubleBuffer.back,
|
||||
},
|
||||
const symbolDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
|
||||
|
||||
framebuffer: symbolDoubleBuffer.front,
|
||||
});
|
||||
const symbolUniforms = {
|
||||
...commonUniforms,
|
||||
...extractEntries(config, ["cycleSpeed", "cycleFrameSkip", "loops"]),
|
||||
};
|
||||
const symbol = regl({
|
||||
frag: regl.prop("frag"),
|
||||
uniforms: {
|
||||
...symbolUniforms,
|
||||
raindropState: raindropDoubleBuffer.front,
|
||||
previousSymbolState: symbolDoubleBuffer.back,
|
||||
},
|
||||
|
||||
const effectDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
|
||||
framebuffer: symbolDoubleBuffer.front,
|
||||
});
|
||||
|
||||
const effectUniforms = {
|
||||
...commonUniforms,
|
||||
...extractEntries(config, [
|
||||
"hasThunder",
|
||||
"rippleScale",
|
||||
"rippleSpeed",
|
||||
"rippleThickness",
|
||||
"loops",
|
||||
]),
|
||||
rippleType,
|
||||
};
|
||||
const effect = regl({
|
||||
frag: regl.prop("frag"),
|
||||
uniforms: {
|
||||
...effectUniforms,
|
||||
raindropState: raindropDoubleBuffer.front,
|
||||
previousEffectState: effectDoubleBuffer.back,
|
||||
},
|
||||
const effectDoubleBuffer = makeComputeDoubleBuffer(regl, numRows, numColumns);
|
||||
|
||||
framebuffer: effectDoubleBuffer.front,
|
||||
});
|
||||
const effectUniforms = {
|
||||
...commonUniforms,
|
||||
...extractEntries(config, [
|
||||
"hasThunder",
|
||||
"rippleScale",
|
||||
"rippleSpeed",
|
||||
"rippleThickness",
|
||||
"loops",
|
||||
]),
|
||||
rippleType,
|
||||
};
|
||||
const effect = regl({
|
||||
frag: regl.prop("frag"),
|
||||
uniforms: {
|
||||
...effectUniforms,
|
||||
raindropState: raindropDoubleBuffer.front,
|
||||
previousEffectState: effectDoubleBuffer.back,
|
||||
},
|
||||
|
||||
const quadPositions = Array(numQuadRows)
|
||||
.fill()
|
||||
.map((_, y) =>
|
||||
Array(numQuadColumns)
|
||||
.fill()
|
||||
.map((_, x) => Array(numVerticesPerQuad).fill([x, y]))
|
||||
);
|
||||
framebuffer: effectDoubleBuffer.front,
|
||||
});
|
||||
|
||||
// We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen
|
||||
const glyphMSDF = loadImage(regl, config.glyphMSDFURL);
|
||||
const glintMSDF = loadImage(regl, config.glintMSDFURL);
|
||||
const baseTexture = loadImage(regl, config.baseTextureURL, true);
|
||||
const glintTexture = loadImage(regl, config.glintTextureURL, true);
|
||||
const output = makePassFBO(regl, config.useHalfFloat);
|
||||
const renderUniforms = {
|
||||
...commonUniforms,
|
||||
...extractEntries(config, [
|
||||
// vertex
|
||||
"forwardSpeed",
|
||||
"glyphVerticalSpacing",
|
||||
// fragment
|
||||
"baseBrightness",
|
||||
"baseContrast",
|
||||
"glintBrightness",
|
||||
"glintContrast",
|
||||
"hasBaseTexture",
|
||||
"hasGlintTexture",
|
||||
"brightnessThreshold",
|
||||
"brightnessOverride",
|
||||
"isolateCursor",
|
||||
"isolateGlint",
|
||||
"glyphEdgeCrop",
|
||||
"isPolar",
|
||||
]),
|
||||
density,
|
||||
numQuadColumns,
|
||||
numQuadRows,
|
||||
quadSize,
|
||||
slantScale,
|
||||
slantVec,
|
||||
volumetric,
|
||||
};
|
||||
const render = regl({
|
||||
blend: {
|
||||
enable: true,
|
||||
func: {
|
||||
src: "one",
|
||||
dst: "one",
|
||||
},
|
||||
},
|
||||
vert: regl.prop("vert"),
|
||||
frag: regl.prop("frag"),
|
||||
const quadPositions = Array(numQuadRows)
|
||||
.fill()
|
||||
.map((_, y) =>
|
||||
Array(numQuadColumns)
|
||||
.fill()
|
||||
.map((_, x) => Array(numVerticesPerQuad).fill([x, y])),
|
||||
);
|
||||
|
||||
uniforms: {
|
||||
...renderUniforms,
|
||||
// We render the code into an FBO using MSDFs: https://github.com/Chlumsky/msdfgen
|
||||
const glyphMSDF = loadImage(regl, config.glyphMSDFURL);
|
||||
const glintMSDF = loadImage(regl, config.glintMSDFURL);
|
||||
const baseTexture = loadImage(regl, config.baseTextureURL, true);
|
||||
const glintTexture = loadImage(regl, config.glintTextureURL, true);
|
||||
const output = makePassFBO(regl, config.useHalfFloat);
|
||||
const renderUniforms = {
|
||||
...commonUniforms,
|
||||
...extractEntries(config, [
|
||||
// vertex
|
||||
"forwardSpeed",
|
||||
"glyphVerticalSpacing",
|
||||
// fragment
|
||||
"baseBrightness",
|
||||
"baseContrast",
|
||||
"glintBrightness",
|
||||
"glintContrast",
|
||||
"hasBaseTexture",
|
||||
"hasGlintTexture",
|
||||
"brightnessThreshold",
|
||||
"brightnessOverride",
|
||||
"isolateCursor",
|
||||
"isolateGlint",
|
||||
"glyphEdgeCrop",
|
||||
"isPolar",
|
||||
]),
|
||||
density,
|
||||
numQuadColumns,
|
||||
numQuadRows,
|
||||
quadSize,
|
||||
slantScale,
|
||||
slantVec,
|
||||
volumetric,
|
||||
};
|
||||
const render = regl({
|
||||
blend: {
|
||||
enable: true,
|
||||
func: {
|
||||
src: "one",
|
||||
dst: "one",
|
||||
},
|
||||
},
|
||||
vert: regl.prop("vert"),
|
||||
frag: regl.prop("frag"),
|
||||
|
||||
raindropState: raindropDoubleBuffer.front,
|
||||
symbolState: symbolDoubleBuffer.front,
|
||||
effectState: effectDoubleBuffer.front,
|
||||
glyphMSDF: glyphMSDF.texture,
|
||||
glintMSDF: glintMSDF.texture,
|
||||
baseTexture: baseTexture.texture,
|
||||
glintTexture: glintTexture.texture,
|
||||
glyphTransform: regl.prop('glyphTransform'),
|
||||
uniforms: {
|
||||
...renderUniforms,
|
||||
|
||||
msdfPxRange: 4.0,
|
||||
glyphMSDFSize: () => [glyphMSDF.width(), glyphMSDF.height()],
|
||||
glintMSDFSize: () => [glintMSDF.width(), glintMSDF.height()],
|
||||
raindropState: raindropDoubleBuffer.front,
|
||||
symbolState: symbolDoubleBuffer.front,
|
||||
effectState: effectDoubleBuffer.front,
|
||||
glyphMSDF: glyphMSDF.texture,
|
||||
glintMSDF: glintMSDF.texture,
|
||||
baseTexture: baseTexture.texture,
|
||||
glintTexture: glintTexture.texture,
|
||||
glyphTransform: regl.prop("glyphTransform"),
|
||||
|
||||
camera: regl.prop("camera"),
|
||||
transform: regl.prop("transform"),
|
||||
screenSize: regl.prop("screenSize"),
|
||||
},
|
||||
msdfPxRange: 4.0,
|
||||
glyphMSDFSize: () => [glyphMSDF.width(), glyphMSDF.height()],
|
||||
glintMSDFSize: () => [glintMSDF.width(), glintMSDF.height()],
|
||||
|
||||
viewport: regl.prop("viewport"),
|
||||
camera: regl.prop("camera"),
|
||||
transform: regl.prop("transform"),
|
||||
screenSize: regl.prop("screenSize"),
|
||||
},
|
||||
|
||||
attributes: {
|
||||
aPosition: quadPositions,
|
||||
aCorner: Array(numQuads).fill(quadVertices),
|
||||
},
|
||||
count: numQuads * numVerticesPerQuad,
|
||||
viewport: regl.prop("viewport"),
|
||||
|
||||
framebuffer: output,
|
||||
});
|
||||
attributes: {
|
||||
aPosition: quadPositions,
|
||||
aCorner: Array(numQuads).fill(quadVertices),
|
||||
},
|
||||
count: numQuads * numVerticesPerQuad,
|
||||
|
||||
// Camera and transform math for the volumetric mode
|
||||
const screenSize = [1, 1];
|
||||
//const { mat4, vec3 } = glMatrix;
|
||||
const transform = mat4.create();
|
||||
if (volumetric && config.isometric) {
|
||||
mat4.rotateX(transform, transform, (Math.PI * 1) / 8);
|
||||
mat4.rotateY(transform, transform, (Math.PI * 1) / 4);
|
||||
mat4.translate(transform, transform, vec3.fromValues(0, 0, -1));
|
||||
mat4.scale(transform, transform, vec3.fromValues(1, 1, 2));
|
||||
} else if (lkg.enabled) {
|
||||
mat4.translate(transform, transform, vec3.fromValues(0, 0, -1.1));
|
||||
mat4.scale(transform, transform, vec3.fromValues(1, 1, 1));
|
||||
mat4.scale(transform, transform, vec3.fromValues(0.15, 0.15, 0.15));
|
||||
} else {
|
||||
mat4.translate(transform, transform, vec3.fromValues(0, 0, -1));
|
||||
}
|
||||
const camera = mat4.create();
|
||||
framebuffer: output,
|
||||
});
|
||||
|
||||
const vantagePoints = [];
|
||||
// Camera and transform math for the volumetric mode
|
||||
const screenSize = [1, 1];
|
||||
//const { mat4, vec3 } = glMatrix;
|
||||
const transform = mat4.create();
|
||||
if (volumetric && config.isometric) {
|
||||
mat4.rotateX(transform, transform, (Math.PI * 1) / 8);
|
||||
mat4.rotateY(transform, transform, (Math.PI * 1) / 4);
|
||||
mat4.translate(transform, transform, vec3.fromValues(0, 0, -1));
|
||||
mat4.scale(transform, transform, vec3.fromValues(1, 1, 2));
|
||||
} else if (lkg.enabled) {
|
||||
mat4.translate(transform, transform, vec3.fromValues(0, 0, -1.1));
|
||||
mat4.scale(transform, transform, vec3.fromValues(1, 1, 1));
|
||||
mat4.scale(transform, transform, vec3.fromValues(0.15, 0.15, 0.15));
|
||||
} else {
|
||||
mat4.translate(transform, transform, vec3.fromValues(0, 0, -1));
|
||||
}
|
||||
const camera = mat4.create();
|
||||
|
||||
return makePass(
|
||||
{
|
||||
primary: output,
|
||||
},
|
||||
Promise.all([
|
||||
glyphMSDF.loaded,
|
||||
glintMSDF.loaded,
|
||||
baseTexture.loaded,
|
||||
glintTexture.loaded,
|
||||
]),
|
||||
(w, h) => {
|
||||
output.resize(w, h);
|
||||
const aspectRatio = w / h;
|
||||
const vantagePoints = [];
|
||||
|
||||
const [numTileColumns, numTileRows] = [lkg.tileX, lkg.tileY];
|
||||
const numVantagePoints = numTileRows * numTileColumns;
|
||||
const tileWidth = Math.floor(w / numTileColumns);
|
||||
const tileHeight = Math.floor(h / numTileRows);
|
||||
vantagePoints.length = 0;
|
||||
for (let row = 0; row < numTileRows; row++) {
|
||||
for (let column = 0; column < numTileColumns; column++) {
|
||||
const index = column + row * numTileColumns;
|
||||
const camera = mat4.create();
|
||||
return makePass(
|
||||
{
|
||||
primary: output,
|
||||
},
|
||||
Promise.all([glyphMSDF.loaded, glintMSDF.loaded, baseTexture.loaded, glintTexture.loaded]),
|
||||
(w, h) => {
|
||||
output.resize(w, h);
|
||||
const aspectRatio = w / h;
|
||||
|
||||
if (volumetric && config.isometric) {
|
||||
if (aspectRatio > 1) {
|
||||
mat4.ortho(
|
||||
camera,
|
||||
-1.5 * aspectRatio,
|
||||
1.5 * aspectRatio,
|
||||
-1.5,
|
||||
1.5,
|
||||
-1000,
|
||||
1000
|
||||
);
|
||||
} else {
|
||||
mat4.ortho(
|
||||
camera,
|
||||
-1.5,
|
||||
1.5,
|
||||
-1.5 / aspectRatio,
|
||||
1.5 / aspectRatio,
|
||||
-1000,
|
||||
1000
|
||||
);
|
||||
}
|
||||
} else if (lkg.enabled) {
|
||||
mat4.perspective(
|
||||
camera,
|
||||
(Math.PI / 180) * lkg.fov,
|
||||
lkg.quiltAspect,
|
||||
0.0001,
|
||||
1000
|
||||
);
|
||||
const [numTileColumns, numTileRows] = [lkg.tileX, lkg.tileY];
|
||||
const numVantagePoints = numTileRows * numTileColumns;
|
||||
const tileWidth = Math.floor(w / numTileColumns);
|
||||
const tileHeight = Math.floor(h / numTileRows);
|
||||
vantagePoints.length = 0;
|
||||
for (let row = 0; row < numTileRows; row++) {
|
||||
for (let column = 0; column < numTileColumns; column++) {
|
||||
const index = column + row * numTileColumns;
|
||||
const camera = mat4.create();
|
||||
|
||||
const distanceToTarget = -1; // TODO: Get from somewhere else
|
||||
let vantagePointAngle =
|
||||
(Math.PI / 180) *
|
||||
lkg.viewCone *
|
||||
(index / (numVantagePoints - 1) - 0.5);
|
||||
if (isNaN(vantagePointAngle)) {
|
||||
vantagePointAngle = 0;
|
||||
}
|
||||
const xOffset = distanceToTarget * Math.tan(vantagePointAngle);
|
||||
if (volumetric && config.isometric) {
|
||||
if (aspectRatio > 1) {
|
||||
mat4.ortho(camera, -1.5 * aspectRatio, 1.5 * aspectRatio, -1.5, 1.5, -1000, 1000);
|
||||
} else {
|
||||
mat4.ortho(camera, -1.5, 1.5, -1.5 / aspectRatio, 1.5 / aspectRatio, -1000, 1000);
|
||||
}
|
||||
} else if (lkg.enabled) {
|
||||
mat4.perspective(camera, (Math.PI / 180) * lkg.fov, lkg.quiltAspect, 0.0001, 1000);
|
||||
|
||||
mat4.translate(camera, camera, vec3.fromValues(xOffset, 0, 0));
|
||||
const distanceToTarget = -1; // TODO: Get from somewhere else
|
||||
let vantagePointAngle =
|
||||
(Math.PI / 180) * lkg.viewCone * (index / (numVantagePoints - 1) - 0.5);
|
||||
if (isNaN(vantagePointAngle)) {
|
||||
vantagePointAngle = 0;
|
||||
}
|
||||
const xOffset = distanceToTarget * Math.tan(vantagePointAngle);
|
||||
|
||||
camera[8] =
|
||||
-xOffset /
|
||||
(distanceToTarget *
|
||||
Math.tan((Math.PI / 180) * 0.5 * lkg.fov) *
|
||||
lkg.quiltAspect); // Is this right??
|
||||
} else {
|
||||
mat4.perspective(
|
||||
camera,
|
||||
(Math.PI / 180) * 90,
|
||||
aspectRatio,
|
||||
0.0001,
|
||||
1000
|
||||
);
|
||||
}
|
||||
mat4.translate(camera, camera, vec3.fromValues(xOffset, 0, 0));
|
||||
|
||||
const viewport = {
|
||||
x: column * tileWidth,
|
||||
y: row * tileHeight,
|
||||
width: tileWidth,
|
||||
height: tileHeight,
|
||||
};
|
||||
vantagePoints.push({ camera, viewport });
|
||||
}
|
||||
}
|
||||
[screenSize[0], screenSize[1]] =
|
||||
aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
|
||||
},
|
||||
(shouldRender) => {
|
||||
intro({ frag: rainPassIntro });
|
||||
raindrop({ frag: rainPassRaindrop });
|
||||
symbol({ frag: rainPassSymbol });
|
||||
effect({ frag: rainPassEffect });
|
||||
camera[8] =
|
||||
-xOffset /
|
||||
(distanceToTarget * Math.tan((Math.PI / 180) * 0.5 * lkg.fov) * lkg.quiltAspect); // Is this right??
|
||||
} else {
|
||||
mat4.perspective(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
|
||||
}
|
||||
|
||||
if (shouldRender) {
|
||||
regl.clear({
|
||||
depth: 1,
|
||||
color: [0, 0, 0, 1],
|
||||
framebuffer: output,
|
||||
});
|
||||
|
||||
for (const vantagePoint of vantagePoints) {
|
||||
render({
|
||||
...vantagePoint,
|
||||
transform,
|
||||
screenSize,
|
||||
vert: rainPassVert,
|
||||
frag: rainPassFrag,
|
||||
glyphTransform: [1, 0, 0, 1]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
const viewport = {
|
||||
x: column * tileWidth,
|
||||
y: row * tileHeight,
|
||||
width: tileWidth,
|
||||
height: tileHeight,
|
||||
};
|
||||
vantagePoints.push({ camera, viewport });
|
||||
}
|
||||
}
|
||||
[screenSize[0], screenSize[1]] = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
|
||||
},
|
||||
(shouldRender) => {
|
||||
intro({ frag: rainPassIntro });
|
||||
raindrop({ frag: rainPassRaindrop });
|
||||
symbol({ frag: rainPassSymbol });
|
||||
effect({ frag: rainPassEffect });
|
||||
|
||||
if (shouldRender) {
|
||||
regl.clear({
|
||||
depth: 1,
|
||||
color: [0, 0, 0, 1],
|
||||
framebuffer: output,
|
||||
});
|
||||
|
||||
for (const vantagePoint of vantagePoints) {
|
||||
render({
|
||||
...vantagePoint,
|
||||
transform,
|
||||
screenSize,
|
||||
vert: rainPassVert,
|
||||
frag: rainPassFrag,
|
||||
glyphTransform: [1, 0, 0, 1],
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,77 +8,77 @@ import stripePassFrag from "../../shaders/glsl/stripePass.frag.glsl";
|
||||
// This shader introduces noise into the renders, to avoid banding
|
||||
|
||||
const transPrideStripeColors = [
|
||||
{ space: "rgb", values: [0.36, 0.81, 0.98] },
|
||||
{ space: "rgb", values: [0.96, 0.66, 0.72] },
|
||||
{ space: "rgb", values: [1.0, 1.0, 1.0] },
|
||||
{ space: "rgb", values: [0.96, 0.66, 0.72] },
|
||||
{ space: "rgb", values: [0.36, 0.81, 0.98] },
|
||||
{ space: "rgb", values: [0.36, 0.81, 0.98] },
|
||||
{ space: "rgb", values: [0.96, 0.66, 0.72] },
|
||||
{ space: "rgb", values: [1.0, 1.0, 1.0] },
|
||||
{ space: "rgb", values: [0.96, 0.66, 0.72] },
|
||||
{ space: "rgb", values: [0.36, 0.81, 0.98] },
|
||||
]
|
||||
.map((color) => Array(3).fill(color))
|
||||
.flat();
|
||||
.map((color) => Array(3).fill(color))
|
||||
.flat();
|
||||
|
||||
const prideStripeColors = [
|
||||
{ space: "rgb", values: [0.89, 0.01, 0.01] },
|
||||
{ space: "rgb", values: [1.0, 0.55, 0.0] },
|
||||
{ space: "rgb", values: [1.0, 0.93, 0.0] },
|
||||
{ space: "rgb", values: [0.0, 0.5, 0.15] },
|
||||
{ space: "rgb", values: [0.0, 0.3, 1.0] },
|
||||
{ space: "rgb", values: [0.46, 0.03, 0.53] },
|
||||
{ space: "rgb", values: [0.89, 0.01, 0.01] },
|
||||
{ space: "rgb", values: [1.0, 0.55, 0.0] },
|
||||
{ space: "rgb", values: [1.0, 0.93, 0.0] },
|
||||
{ space: "rgb", values: [0.0, 0.5, 0.15] },
|
||||
{ space: "rgb", values: [0.0, 0.3, 1.0] },
|
||||
{ space: "rgb", values: [0.46, 0.03, 0.53] },
|
||||
]
|
||||
.map((color) => Array(2).fill(color))
|
||||
.flat();
|
||||
.map((color) => Array(2).fill(color))
|
||||
.flat();
|
||||
|
||||
export default ({ regl, config }, inputs) => {
|
||||
const output = makePassFBO(regl, config.useHalfFloat);
|
||||
const output = makePassFBO(regl, config.useHalfFloat);
|
||||
|
||||
const {
|
||||
backgroundColor,
|
||||
cursorColor,
|
||||
glintColor,
|
||||
cursorIntensity,
|
||||
glintIntensity,
|
||||
ditherMagnitude,
|
||||
} = config;
|
||||
const {
|
||||
backgroundColor,
|
||||
cursorColor,
|
||||
glintColor,
|
||||
cursorIntensity,
|
||||
glintIntensity,
|
||||
ditherMagnitude,
|
||||
} = config;
|
||||
|
||||
// Expand and convert stripe colors into 1D texture data
|
||||
const stripeColors =
|
||||
"stripeColors" in config
|
||||
? config.stripeColors
|
||||
: config.effect === "pride"
|
||||
? prideStripeColors
|
||||
: transPrideStripeColors;
|
||||
const stripeTex = make1DTexture(
|
||||
regl,
|
||||
stripeColors.map((color) => [...colorToRGB(color), 1])
|
||||
);
|
||||
// Expand and convert stripe colors into 1D texture data
|
||||
const stripeColors =
|
||||
"stripeColors" in config
|
||||
? config.stripeColors
|
||||
: config.effect === "pride"
|
||||
? prideStripeColors
|
||||
: transPrideStripeColors;
|
||||
const stripeTex = make1DTexture(
|
||||
regl,
|
||||
stripeColors.map((color) => [...colorToRGB(color), 1]),
|
||||
);
|
||||
|
||||
const render = regl({
|
||||
frag: regl.prop("frag"),
|
||||
const render = regl({
|
||||
frag: regl.prop("frag"),
|
||||
|
||||
uniforms: {
|
||||
backgroundColor: colorToRGB(backgroundColor),
|
||||
cursorColor: colorToRGB(cursorColor),
|
||||
glintColor: colorToRGB(glintColor),
|
||||
cursorIntensity,
|
||||
glintIntensity,
|
||||
ditherMagnitude,
|
||||
tex: inputs.primary,
|
||||
bloomTex: inputs.bloom,
|
||||
stripeTex,
|
||||
},
|
||||
framebuffer: output,
|
||||
});
|
||||
uniforms: {
|
||||
backgroundColor: colorToRGB(backgroundColor),
|
||||
cursorColor: colorToRGB(cursorColor),
|
||||
glintColor: colorToRGB(glintColor),
|
||||
cursorIntensity,
|
||||
glintIntensity,
|
||||
ditherMagnitude,
|
||||
tex: inputs.primary,
|
||||
bloomTex: inputs.bloom,
|
||||
stripeTex,
|
||||
},
|
||||
framebuffer: output,
|
||||
});
|
||||
|
||||
return makePass(
|
||||
{
|
||||
primary: output,
|
||||
},
|
||||
null,
|
||||
(w, h) => output.resize(w, h),
|
||||
(shouldRender) => {
|
||||
if (shouldRender) {
|
||||
render({ frag: stripePassFrag });
|
||||
}
|
||||
}
|
||||
);
|
||||
return makePass(
|
||||
{
|
||||
primary: output,
|
||||
},
|
||||
null,
|
||||
(w, h) => output.resize(w, h),
|
||||
(shouldRender) => {
|
||||
if (shouldRender) {
|
||||
render({ frag: stripePassFrag });
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,8 @@ const makePassTexture = (regl, halfFloat) =>
|
||||
mag: "linear",
|
||||
});
|
||||
|
||||
const makePassFBO = (regl, halfFloat) => regl.framebuffer({ color: makePassTexture(regl, halfFloat) });
|
||||
const makePassFBO = (regl, halfFloat) =>
|
||||
regl.framebuffer({ color: makePassTexture(regl, halfFloat) });
|
||||
|
||||
const makeDoubleBuffer = (regl, props) => {
|
||||
const state = Array(2)
|
||||
@@ -17,7 +18,7 @@ const makeDoubleBuffer = (regl, props) => {
|
||||
regl.framebuffer({
|
||||
color: regl.texture(props),
|
||||
depthStencil: false,
|
||||
})
|
||||
}),
|
||||
);
|
||||
return {
|
||||
front: ({ tick }) => state[tick % 2],
|
||||
@@ -149,6 +150,21 @@ const makePass = (outputs, ready, setSize, execute) => ({
|
||||
});
|
||||
|
||||
const makePipeline = (context, steps) =>
|
||||
steps.filter((f) => f != null).reduce((pipeline, f, i) => [...pipeline, f(context, i == 0 ? null : pipeline[i - 1].outputs)], []);
|
||||
steps
|
||||
.filter((f) => f != null)
|
||||
.reduce(
|
||||
(pipeline, f, i) => [...pipeline, f(context, i == 0 ? null : pipeline[i - 1].outputs)],
|
||||
[],
|
||||
);
|
||||
|
||||
export { makePassTexture, makePassFBO, makeDoubleBuffer, loadImage, loadText, makeFullScreenQuad, make1DTexture, makePass, makePipeline };
|
||||
export {
|
||||
makePassTexture,
|
||||
makePassFBO,
|
||||
makeDoubleBuffer,
|
||||
loadImage,
|
||||
loadText,
|
||||
makeFullScreenQuad,
|
||||
make1DTexture,
|
||||
makePass,
|
||||
makePipeline,
|
||||
};
|
||||
|
||||
1056
js/utils/config.js
1056
js/utils/config.js
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,11 @@
|
||||
import { structs } from "../../lib/gpu-buffer.js";
|
||||
import { makeComputeTarget, loadShader, makeUniformBuffer, makeBindGroup, makePass } from "./utils.js";
|
||||
import {
|
||||
makeComputeTarget,
|
||||
loadShader,
|
||||
makeUniformBuffer,
|
||||
makeBindGroup,
|
||||
makePass,
|
||||
} from "./utils.js";
|
||||
|
||||
// const makePyramid = makeComputeTarget;
|
||||
|
||||
@@ -20,8 +26,8 @@ const makePyramid = (device, size, pyramidHeight) =>
|
||||
.map((_, index) =>
|
||||
makeComputeTarget(
|
||||
device,
|
||||
size.map((x) => Math.floor(x * 2 ** -index))
|
||||
)
|
||||
size.map((x) => Math.floor(x * 2 ** -index)),
|
||||
),
|
||||
);
|
||||
|
||||
const destroyPyramid = (pyramid) => pyramid?.forEach((texture) => texture.destroy());
|
||||
@@ -47,7 +53,10 @@ export default ({ config, device }) => {
|
||||
return makePass("No Bloom", null, (size, inputs) => ({ ...inputs, bloom: emptyTexture }));
|
||||
}
|
||||
|
||||
const assets = [loadShader(device, "shaders/wgsl/bloomBlur.wgsl"), loadShader(device, "shaders/wgsl/bloomCombine.wgsl")];
|
||||
const assets = [
|
||||
loadShader(device, "shaders/wgsl/bloomBlur.wgsl"),
|
||||
loadShader(device, "shaders/wgsl/bloomCombine.wgsl"),
|
||||
];
|
||||
|
||||
const linearSampler = device.createSampler({
|
||||
magFilter: "linear",
|
||||
@@ -122,12 +131,27 @@ export default ({ config, device }) => {
|
||||
for (let i = 0; i < pyramidHeight; i++) {
|
||||
const hBlurPyramidView = makePyramidLevelView(hBlurPyramid, i);
|
||||
const vBlurPyramidView = makePyramidLevelView(vBlurPyramid, i);
|
||||
hBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [hBlurBuffer, linearSampler, srcView, hBlurPyramidView]);
|
||||
vBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [vBlurBuffer, linearSampler, hBlurPyramidView, vBlurPyramidView]);
|
||||
hBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [
|
||||
hBlurBuffer,
|
||||
linearSampler,
|
||||
srcView,
|
||||
hBlurPyramidView,
|
||||
]);
|
||||
vBlurBindGroups[i] = makeBindGroup(device, blurPipeline, 0, [
|
||||
vBlurBuffer,
|
||||
linearSampler,
|
||||
hBlurPyramidView,
|
||||
vBlurPyramidView,
|
||||
]);
|
||||
srcView = hBlurPyramidView;
|
||||
}
|
||||
|
||||
combineBindGroup = makeBindGroup(device, combinePipeline, 0, [combineBuffer, linearSampler, ...makePyramidViews(vBlurPyramid), output.createView()]);
|
||||
combineBindGroup = makeBindGroup(device, combinePipeline, 0, [
|
||||
combineBuffer,
|
||||
linearSampler,
|
||||
...makePyramidViews(vBlurPyramid),
|
||||
output.createView(),
|
||||
]);
|
||||
|
||||
return {
|
||||
...inputs,
|
||||
@@ -144,7 +168,11 @@ export default ({ config, device }) => {
|
||||
|
||||
computePass.setPipeline(blurPipeline);
|
||||
for (let i = 0; i < pyramidHeight; i++) {
|
||||
const dispatchSize = [Math.ceil(Math.floor(scaledScreenSize[0] * 2 ** -i) / 32), Math.floor(Math.floor(scaledScreenSize[1] * 2 ** -i)), 1];
|
||||
const dispatchSize = [
|
||||
Math.ceil(Math.floor(scaledScreenSize[0] * 2 ** -i) / 32),
|
||||
Math.floor(Math.floor(scaledScreenSize[1] * 2 ** -i)),
|
||||
1,
|
||||
];
|
||||
computePass.setBindGroup(0, hBlurBindGroups[i]);
|
||||
computePass.dispatchWorkgroups(...dispatchSize);
|
||||
computePass.setBindGroup(0, vBlurBindGroups[i]);
|
||||
|
||||
@@ -45,7 +45,10 @@ export default ({ device, canvasFormat, canvasContext }) => {
|
||||
})();
|
||||
|
||||
const build = (size, inputs) => {
|
||||
renderBindGroup = makeBindGroup(device, renderPipeline, 0, [nearestSampler, inputs.primary.createView()]);
|
||||
renderBindGroup = makeBindGroup(device, renderPipeline, 0, [
|
||||
nearestSampler,
|
||||
inputs.primary.createView(),
|
||||
]);
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import { structs } from "../../lib/gpu-buffer.js";
|
||||
import { makeComputeTarget, makeUniformBuffer, loadTexture, loadShader, makeBindGroup, makePass } from "./utils.js";
|
||||
import {
|
||||
makeComputeTarget,
|
||||
makeUniformBuffer,
|
||||
loadTexture,
|
||||
loadShader,
|
||||
makeBindGroup,
|
||||
makePass,
|
||||
} from "./utils.js";
|
||||
|
||||
// Multiplies the rendered rain and bloom by a loaded in image
|
||||
|
||||
const defaultBGURL = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flammarion_Colored.jpg/917px-Flammarion_Colored.jpg";
|
||||
const defaultBGURL =
|
||||
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/0a/Flammarion_Colored.jpg/917px-Flammarion_Colored.jpg";
|
||||
|
||||
export default ({ config, device }) => {
|
||||
const bgURL = "bgURL" in config ? config.bgURL : defaultBGURL;
|
||||
|
||||
@@ -74,7 +74,10 @@ export default async (canvas, config) => {
|
||||
const cameraTex = device.createTexture({
|
||||
size: cameraSize,
|
||||
format: "rgba8unorm",
|
||||
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
|
||||
usage:
|
||||
GPUTextureUsage.TEXTURE_BINDING |
|
||||
GPUTextureUsage.COPY_DST |
|
||||
GPUTextureUsage.RENDER_ATTACHMENT,
|
||||
});
|
||||
|
||||
const context = {
|
||||
@@ -90,7 +93,12 @@ export default async (canvas, config) => {
|
||||
};
|
||||
|
||||
const effectName = config.effect in effects ? config.effect : "palette";
|
||||
const pipeline = await makePipeline(context, [makeRain, makeBloomPass, effects[effectName], makeEndPass]);
|
||||
const pipeline = await makePipeline(context, [
|
||||
makeRain,
|
||||
makeBloomPass,
|
||||
effects[effectName],
|
||||
makeEndPass,
|
||||
]);
|
||||
|
||||
const targetFrameTimeMilliseconds = 1000 / config.fps;
|
||||
let frames = 0;
|
||||
@@ -107,7 +115,8 @@ export default async (canvas, config) => {
|
||||
last = start;
|
||||
}
|
||||
|
||||
const shouldRender = config.fps >= 60 || now - last >= targetFrameTimeMilliseconds || config.once;
|
||||
const shouldRender =
|
||||
config.fps >= 60 || now - last >= targetFrameTimeMilliseconds || config.once;
|
||||
if (shouldRender) {
|
||||
while (now - targetFrameTimeMilliseconds > last) {
|
||||
last += targetFrameTimeMilliseconds;
|
||||
@@ -125,10 +134,18 @@ export default async (canvas, config) => {
|
||||
}
|
||||
|
||||
if (config.useCamera) {
|
||||
device.queue.copyExternalImageToTexture({ source: cameraCanvas }, { texture: cameraTex }, cameraSize);
|
||||
device.queue.copyExternalImageToTexture(
|
||||
{ source: cameraCanvas },
|
||||
{ texture: cameraTex },
|
||||
cameraSize,
|
||||
);
|
||||
}
|
||||
|
||||
device.queue.writeBuffer(timeBuffer, 0, timeUniforms.toBuffer({ seconds: (now - start) / 1000, frames }));
|
||||
device.queue.writeBuffer(
|
||||
timeBuffer,
|
||||
0,
|
||||
timeUniforms.toBuffer({ seconds: (now - start) / 1000, frames }),
|
||||
);
|
||||
frames++;
|
||||
|
||||
const encoder = device.createCommandEncoder();
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import { structs } from "../../lib/gpu-buffer.js";
|
||||
import { makeComputeTarget, makeUniformBuffer, loadShader, makeBindGroup, makePass } from "./utils.js";
|
||||
import {
|
||||
makeComputeTarget,
|
||||
makeUniformBuffer,
|
||||
loadShader,
|
||||
makeBindGroup,
|
||||
makePass,
|
||||
} from "./utils.js";
|
||||
|
||||
let start;
|
||||
const numTouches = 5;
|
||||
@@ -77,7 +83,11 @@ export default ({ config, device, cameraTex, cameraAspectRatio, timeBuffer }) =>
|
||||
]);
|
||||
|
||||
const screenAspectRatio = size[0] / size[1];
|
||||
device.queue.writeBuffer(sceneBuffer, 0, sceneUniforms.toBuffer({ screenAspectRatio, cameraAspectRatio }));
|
||||
device.queue.writeBuffer(
|
||||
sceneBuffer,
|
||||
0,
|
||||
sceneUniforms.toBuffer({ screenAspectRatio, cameraAspectRatio }),
|
||||
);
|
||||
|
||||
return { primary: output };
|
||||
};
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import colorToRGB from "../colorToRGB.js";
|
||||
import { structs } from "../../lib/gpu-buffer.js";
|
||||
import { loadShader, makeUniformBuffer, makeBindGroup, makeComputeTarget, makePass } from "./utils.js";
|
||||
import {
|
||||
loadShader,
|
||||
makeUniformBuffer,
|
||||
makeBindGroup,
|
||||
makeComputeTarget,
|
||||
makePass,
|
||||
} from "./utils.js";
|
||||
|
||||
// Maps the brightness of the rendered rain and bloom to colors
|
||||
// in a linear gradient buffer generated from the passed-in color sequence
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
import { structs } from "../../lib/gpu-buffer.js";
|
||||
import { makeRenderTarget, loadTexture, loadShader, makeUniformBuffer, makeBindGroup, makePass } from "./utils.js";
|
||||
import {
|
||||
makeRenderTarget,
|
||||
loadTexture,
|
||||
loadShader,
|
||||
makeUniformBuffer,
|
||||
makeBindGroup,
|
||||
makePass,
|
||||
} from "./utils.js";
|
||||
|
||||
const rippleTypes = {
|
||||
box: 0,
|
||||
@@ -46,7 +53,10 @@ export default ({ config, device, timeBuffer }) => {
|
||||
// rather than a single quad for our geometry
|
||||
const numQuads = config.volumetric ? numCells : 1;
|
||||
|
||||
const glyphTransform = mat2.fromScaling(mat2.create(), vec2.fromValues(config.glyphFlip ? -1 : 1, 1));
|
||||
const glyphTransform = mat2.fromScaling(
|
||||
mat2.create(),
|
||||
vec2.fromValues(config.glyphFlip ? -1 : 1, 1),
|
||||
);
|
||||
mat2.rotate(glyphTransform, glyphTransform, (config.glyphRotation * Math.PI) / 180);
|
||||
|
||||
const transform = mat4.create();
|
||||
@@ -98,10 +108,18 @@ export default ({ config, device, timeBuffer }) => {
|
||||
let highPassOutput;
|
||||
|
||||
const loaded = (async () => {
|
||||
const [glyphMSDFTexture, glintMSDFTexture, baseTexture, glintTexture, rainShader] = await Promise.all(assets);
|
||||
const [glyphMSDFTexture, glintMSDFTexture, baseTexture, glintTexture, rainShader] =
|
||||
await Promise.all(assets);
|
||||
|
||||
const rainShaderUniforms = structs.from(rainShader.code);
|
||||
configBuffer = makeConfigBuffer(device, rainShaderUniforms.Config, config, density, gridSize, glyphTransform);
|
||||
configBuffer = makeConfigBuffer(
|
||||
device,
|
||||
rainShaderUniforms.Config,
|
||||
config,
|
||||
density,
|
||||
gridSize,
|
||||
glyphTransform,
|
||||
);
|
||||
|
||||
const introCellsBuffer = device.createBuffer({
|
||||
size: gridSize[0] * rainShaderUniforms.IntroCell.minSize,
|
||||
@@ -168,8 +186,17 @@ export default ({ config, device, timeBuffer }) => {
|
||||
}),
|
||||
]);
|
||||
|
||||
introBindGroup = makeBindGroup(device, introPipeline, 0, [configBuffer, timeBuffer, introCellsBuffer]);
|
||||
computeBindGroup = makeBindGroup(device, computePipeline, 0, [configBuffer, timeBuffer, cellsBuffer, introCellsBuffer]);
|
||||
introBindGroup = makeBindGroup(device, introPipeline, 0, [
|
||||
configBuffer,
|
||||
timeBuffer,
|
||||
introCellsBuffer,
|
||||
]);
|
||||
computeBindGroup = makeBindGroup(device, computePipeline, 0, [
|
||||
configBuffer,
|
||||
timeBuffer,
|
||||
cellsBuffer,
|
||||
introCellsBuffer,
|
||||
]);
|
||||
renderBindGroup = makeBindGroup(device, renderPipeline, 0, [
|
||||
configBuffer,
|
||||
timeBuffer,
|
||||
@@ -196,7 +223,11 @@ export default ({ config, device, timeBuffer }) => {
|
||||
mat4.perspectiveZO(camera, (Math.PI / 180) * 90, aspectRatio, 0.0001, 1000);
|
||||
}
|
||||
const screenSize = aspectRatio > 1 ? [1, aspectRatio] : [1 / aspectRatio, 1];
|
||||
device.queue.writeBuffer(sceneBuffer, 0, sceneUniforms.toBuffer({ screenSize, camera, transform }));
|
||||
device.queue.writeBuffer(
|
||||
sceneBuffer,
|
||||
0,
|
||||
sceneUniforms.toBuffer({ screenSize, camera, transform }),
|
||||
);
|
||||
|
||||
// Update
|
||||
output?.destroy();
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import colorToRGB from "../colorToRGB.js";
|
||||
import { structs } from "../../lib/gpu-buffer.js";
|
||||
import { loadShader, make1DTexture, makeUniformBuffer, makeBindGroup, makeComputeTarget, makePass } from "./utils.js";
|
||||
import {
|
||||
loadShader,
|
||||
make1DTexture,
|
||||
makeUniformBuffer,
|
||||
makeBindGroup,
|
||||
makeComputeTarget,
|
||||
makePass,
|
||||
} from "./utils.js";
|
||||
|
||||
// Multiplies the rendered rain and bloom by a 1D gradient texture
|
||||
// generated from the passed-in color sequence
|
||||
@@ -38,10 +45,15 @@ const numVerticesPerQuad = 2 * 3;
|
||||
|
||||
export default ({ config, device, timeBuffer }) => {
|
||||
// Expand and convert stripe colors into 1D texture data
|
||||
const stripeColors = "stripeColors" in config ? config.stripeColors : config.effect === "pride" ? prideStripeColors : transPrideStripeColors;
|
||||
const stripeColors =
|
||||
"stripeColors" in config
|
||||
? config.stripeColors
|
||||
: config.effect === "pride"
|
||||
? prideStripeColors
|
||||
: transPrideStripeColors;
|
||||
const stripeTex = make1DTexture(
|
||||
device,
|
||||
stripeColors.map((color) => [...colorToRGB(color), 1])
|
||||
stripeColors.map((color) => [...colorToRGB(color), 1]),
|
||||
);
|
||||
|
||||
const linearSampler = device.createSampler({
|
||||
|
||||
@@ -3,7 +3,10 @@ const loadTexture = async (device, url) => {
|
||||
return device.createTexture({
|
||||
size: [1, 1, 1],
|
||||
format: "rgba8unorm",
|
||||
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
|
||||
usage:
|
||||
GPUTextureUsage.TEXTURE_BINDING |
|
||||
GPUTextureUsage.COPY_DST |
|
||||
GPUTextureUsage.RENDER_ATTACHMENT,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -15,7 +18,10 @@ const loadTexture = async (device, url) => {
|
||||
const texture = device.createTexture({
|
||||
size,
|
||||
format: "rgba8unorm",
|
||||
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
|
||||
usage:
|
||||
GPUTextureUsage.TEXTURE_BINDING |
|
||||
GPUTextureUsage.COPY_DST |
|
||||
GPUTextureUsage.RENDER_ATTACHMENT,
|
||||
});
|
||||
|
||||
device.queue.copyExternalImageToTexture({ source, flipY: true }, { texture }, size);
|
||||
@@ -28,7 +34,11 @@ const makeRenderTarget = (device, size, format, mipLevelCount = 1) =>
|
||||
size: [...size, 1],
|
||||
mipLevelCount,
|
||||
format,
|
||||
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
|
||||
usage:
|
||||
GPUTextureUsage.TEXTURE_BINDING |
|
||||
GPUTextureUsage.COPY_SRC |
|
||||
GPUTextureUsage.COPY_DST |
|
||||
GPUTextureUsage.RENDER_ATTACHMENT,
|
||||
});
|
||||
|
||||
const makeComputeTarget = (device, size, mipLevelCount = 1) =>
|
||||
@@ -36,7 +46,11 @@ const makeComputeTarget = (device, size, mipLevelCount = 1) =>
|
||||
size: [...size, 1],
|
||||
mipLevelCount,
|
||||
format: "rgba8unorm",
|
||||
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.STORAGE_BINDING,
|
||||
usage:
|
||||
GPUTextureUsage.TEXTURE_BINDING |
|
||||
GPUTextureUsage.COPY_SRC |
|
||||
GPUTextureUsage.COPY_DST |
|
||||
GPUTextureUsage.STORAGE_BINDING,
|
||||
});
|
||||
|
||||
const loadShader = async (device, url) => {
|
||||
@@ -105,4 +119,14 @@ const makePipeline = async (context, steps) => {
|
||||
};
|
||||
};
|
||||
|
||||
export { makeRenderTarget, makeComputeTarget, make1DTexture, loadTexture, loadShader, makeUniformBuffer, makePass, makePipeline, makeBindGroup };
|
||||
export {
|
||||
makeRenderTarget,
|
||||
makeComputeTarget,
|
||||
make1DTexture,
|
||||
loadTexture,
|
||||
loadShader,
|
||||
makeUniformBuffer,
|
||||
makePass,
|
||||
makePipeline,
|
||||
makeBindGroup,
|
||||
};
|
||||
|
||||
@@ -8,37 +8,37 @@ import terser from "@rollup/plugin-terser";
|
||||
import { string } from "rollup-plugin-string";
|
||||
|
||||
export default {
|
||||
input: "js/Matrix.js",
|
||||
external: ["react", "react-dom"], // keep them out of your bundle
|
||||
plugins: [
|
||||
peerDepsExternal(), // auto-exclude peerDeps
|
||||
nodeResolve(), // so Rollup can find deps in node_modules
|
||||
string({ include: ["**/*.glsl"] }),
|
||||
url({ include: ["**/*.png"], limit: 0 }),
|
||||
babel({
|
||||
exclude: "node_modules/**", // transpile JSX
|
||||
babelHelpers: "bundled",
|
||||
presets: ["@babel/preset-react", "@babel/preset-env"],
|
||||
}),
|
||||
commonjs(), // turn CJS deps into ES
|
||||
terser({
|
||||
sourceMap: false, // <- suppress .map generation
|
||||
format: { comments: false },
|
||||
}),
|
||||
visualizer({
|
||||
filename: "dist/stats.html",
|
||||
gzipSize: true,
|
||||
brotliSize: true,
|
||||
includeAssets: true,
|
||||
}), // bundle-size treemap
|
||||
],
|
||||
output: [
|
||||
{
|
||||
file: "dist/index.cjs.js",
|
||||
format: "cjs",
|
||||
exports: "named",
|
||||
sourcemap: false,
|
||||
},
|
||||
// { file: 'dist/index.esm.js', format: 'es' } // optional ESM build
|
||||
],
|
||||
input: "js/Matrix.js",
|
||||
external: ["react", "react-dom"], // keep them out of your bundle
|
||||
plugins: [
|
||||
peerDepsExternal(), // auto-exclude peerDeps
|
||||
nodeResolve(), // so Rollup can find deps in node_modules
|
||||
string({ include: ["**/*.glsl"] }),
|
||||
url({ include: ["**/*.png"], limit: 0 }),
|
||||
babel({
|
||||
exclude: "node_modules/**", // transpile JSX
|
||||
babelHelpers: "bundled",
|
||||
presets: ["@babel/preset-react", "@babel/preset-env"],
|
||||
}),
|
||||
commonjs(), // turn CJS deps into ES
|
||||
terser({
|
||||
sourceMap: false, // <- suppress .map generation
|
||||
format: { comments: false },
|
||||
}),
|
||||
visualizer({
|
||||
filename: "dist/stats.html",
|
||||
gzipSize: true,
|
||||
brotliSize: true,
|
||||
includeAssets: true,
|
||||
}), // bundle-size treemap
|
||||
],
|
||||
output: [
|
||||
{
|
||||
file: "dist/index.cjs.js",
|
||||
format: "cjs",
|
||||
exports: "named",
|
||||
sourcemap: false,
|
||||
},
|
||||
// { file: 'dist/index.esm.js', format: 'es' } // optional ESM build
|
||||
],
|
||||
};
|
||||
|
||||
@@ -3,52 +3,52 @@ const path = require("path");
|
||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||
|
||||
module.exports = {
|
||||
mode: "development",
|
||||
entry: path.resolve(__dirname, "./js/index.js"),
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: ["babel-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.(png|j?g|svg|gif)?$/,
|
||||
type: "asset/resource",
|
||||
},
|
||||
{
|
||||
test: /\.(glsl|frag|vert)$/i,
|
||||
exclude: /node_modules/,
|
||||
use: ["raw-loader"],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: ["*", ".js", ".jsx"],
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, "./dist"),
|
||||
filename: "[name].bundle.js",
|
||||
clean: true,
|
||||
},
|
||||
devtool: "inline-source-map",
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: path.resolve(__dirname, "public/index.html"),
|
||||
filename: "index.html",
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
],
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
static: path.resolve(__dirname, "./dist"),
|
||||
compress: true,
|
||||
hot: true,
|
||||
open: true,
|
||||
port: 3000,
|
||||
},
|
||||
mode: "development",
|
||||
entry: path.resolve(__dirname, "./js/index.js"),
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
use: ["babel-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
{
|
||||
test: /\.(png|j?g|svg|gif)?$/,
|
||||
type: "asset/resource",
|
||||
},
|
||||
{
|
||||
test: /\.(glsl|frag|vert)$/i,
|
||||
exclude: /node_modules/,
|
||||
use: ["raw-loader"],
|
||||
},
|
||||
],
|
||||
},
|
||||
resolve: {
|
||||
extensions: ["*", ".js", ".jsx"],
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, "./dist"),
|
||||
filename: "[name].bundle.js",
|
||||
clean: true,
|
||||
},
|
||||
devtool: "inline-source-map",
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: path.resolve(__dirname, "public/index.html"),
|
||||
filename: "index.html",
|
||||
}),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
],
|
||||
devServer: {
|
||||
historyApiFallback: true,
|
||||
static: path.resolve(__dirname, "./dist"),
|
||||
compress: true,
|
||||
hot: true,
|
||||
open: true,
|
||||
port: 3000,
|
||||
},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user