Move fullscreen wash to WebGPU
This commit is contained in:
1 parent
ebd6fedd89
commit
b696da1854
5 files changed
+579
-134
No files matched your search
@@ -0,0 +1,116 @@
|
||||
import { fromOkLab, toOkLab } from "../palette"
|
||||
import type { BackgroundFrame, BackgroundRenderer } from "./types"
|
||||
|
||||
function gradientHash(x: number, y: number): [number, number] {
|
||||
const hash = (a: number, b: number, xScale: number, yScale: number) => {
|
||||
const value = Math.sin(a * xScale + b * yScale) * 43758.5453
|
||||
return (value - Math.floor(value)) * 2 - 1
|
||||
}
|
||||
return [hash(x, y, 127.1, 311.7), hash(x, y, 269.5, 183.3)]
|
||||
}
|
||||
|
||||
function gradientNoise(x: number, y: number) {
|
||||
const cellX = Math.floor(x)
|
||||
const cellY = Math.floor(y)
|
||||
const offsetX = x - cellX
|
||||
const offsetY = y - cellY
|
||||
const easeX = offsetX * offsetX * (3 - 2 * offsetX)
|
||||
const easeY = offsetY * offsetY * (3 - 2 * offsetY)
|
||||
const dot = (cx: number, cy: number, ox: number, oy: number) => {
|
||||
const [gx, gy] = gradientHash(cx, cy)
|
||||
return gx * ox + gy * oy
|
||||
}
|
||||
const lower =
|
||||
dot(cellX, cellY, offsetX, offsetY) * (1 - easeX) +
|
||||
dot(cellX + 1, cellY, offsetX - 1, offsetY) * easeX
|
||||
const upper =
|
||||
dot(cellX, cellY + 1, offsetX, offsetY - 1) * (1 - easeX) +
|
||||
dot(cellX + 1, cellY + 1, offsetX - 1, offsetY - 1) * easeX
|
||||
return 0.5 + 0.5 * (lower * (1 - easeY) + upper * easeY)
|
||||
}
|
||||
|
||||
function smoothstep(edge0: number, edge1: number, value: number) {
|
||||
const amount = Math.max(0, Math.min(1, (value - edge0) / (edge1 - edge0)))
|
||||
return amount * amount * (3 - 2 * amount)
|
||||
}
|
||||
|
||||
function ditherNoise(x: number, y: number, channel: number) {
|
||||
const value = Math.sin(
|
||||
(x + channel * 17) * 127.1 + (y + channel * 59) * 311.7,
|
||||
) * 43758.5453
|
||||
return value - Math.floor(value) - 0.5
|
||||
}
|
||||
|
||||
function flowPoint(
|
||||
x: number,
|
||||
y: number,
|
||||
frame: BackgroundFrame,
|
||||
): [number, number] {
|
||||
const { random, angleJitter, flowParams, time } = frame
|
||||
const degree = gradientNoise(
|
||||
time * 0.1 + random[0] * 0.07,
|
||||
x * y + random[1] * 0.07,
|
||||
)
|
||||
const angle = (((degree - 0.5) * 720 + 180) * Math.PI) / 180 + angleJitter
|
||||
const sine = Math.sin(angle)
|
||||
const cosine = Math.cos(angle)
|
||||
let flowX = x * cosine - y * sine
|
||||
let flowY = x * sine + y * cosine
|
||||
const speed = time * flowParams[2]
|
||||
flowX += Math.sin(flowY * flowParams[0] + speed) / flowParams[1]
|
||||
flowY +=
|
||||
Math.sin(flowX * flowParams[0] * 1.5 + speed) / (flowParams[1] * 0.5)
|
||||
const tiltSine = Math.sin(flowParams[3])
|
||||
const tiltCosine = Math.cos(flowParams[3])
|
||||
return [
|
||||
flowX * tiltCosine - flowY * tiltSine,
|
||||
flowX * tiltSine + flowY * tiltCosine,
|
||||
]
|
||||
}
|
||||
|
||||
export function createCpuRenderer(
|
||||
canvas: HTMLCanvasElement,
|
||||
): BackgroundRenderer | null {
|
||||
const context = canvas.getContext("2d")
|
||||
if (!context) return null
|
||||
|
||||
return {
|
||||
render(frame) {
|
||||
if (canvas.width !== frame.width || canvas.height !== frame.height) {
|
||||
canvas.width = frame.width
|
||||
canvas.height = frame.height
|
||||
}
|
||||
const image = context.createImageData(frame.width, frame.height)
|
||||
const dominant = toOkLab(frame.palette.dominant)
|
||||
const accent = toOkLab(frame.palette.accent)
|
||||
const maxPaletteLightness = Math.max(dominant[0], accent[0])
|
||||
for (let y = 0; y < frame.height; y++) {
|
||||
for (let x = 0; x < frame.width; x++) {
|
||||
const px = x / (frame.width - 1)
|
||||
const py = y / (frame.height - 1)
|
||||
const point = flowPoint(px - 0.5, py - 0.5, frame)
|
||||
const blend = smoothstep(-0.34, 0.16, point[0])
|
||||
const transition = 1 - Math.abs(blend * 2 - 1)
|
||||
const lab: [number, number, number] = [
|
||||
dominant[0] * (1 - blend) + accent[0] * blend,
|
||||
dominant[1] * (1 - blend) + accent[1] * blend,
|
||||
dominant[2] * (1 - blend) + accent[2] * blend,
|
||||
]
|
||||
lab[0] = Math.min(maxPaletteLightness, lab[0] + frame.beat * 0.01)
|
||||
lab[1] *= 1 + frame.beat * 0.02
|
||||
lab[2] *= 1 + frame.beat * 0.02
|
||||
const color = fromOkLab(lab)
|
||||
const offset = (y * frame.width + x) * 4
|
||||
image.data[offset] = color[0] + ditherNoise(x, y, 0) * transition * 3
|
||||
image.data[offset + 1] =
|
||||
color[1] + ditherNoise(x, y, 1) * transition * 3
|
||||
image.data[offset + 2] =
|
||||
color[2] + ditherNoise(x, y, 2) * transition * 3
|
||||
image.data[offset + 3] = 255
|
||||
}
|
||||
}
|
||||
context.putImageData(image, 0, 0)
|
||||
},
|
||||
dispose() {},
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user