mirror of
https://github.com/lqtmcstudio/QZMusic_PC.git
synced 2026-06-21 15:54:25 +08:00
fork(fix): Clone AMLL 并修复 BUG
- 将AMLL Clone到本以地进行修复和优化(emm虽然这很不优雅但是暂时无时间做子模块和Fork) - 修复在当前播放歌词行不可见的视口Seek会出现滚动偏移的问题
This commit is contained in:
154
amll-local/packages/core/src/bg-render/base.ts
Normal file
154
amll-local/packages/core/src/bg-render/base.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import type { Disposable, HasElement } from "../interfaces.ts";
|
||||
|
||||
export abstract class AbstractBaseRenderer implements Disposable, HasElement {
|
||||
/**
|
||||
* 修改背景的流动速度,数字越大越快,默认为 8
|
||||
* @param speed 背景的流动速度,默认为 8
|
||||
*/
|
||||
abstract setFlowSpeed(speed: number): void;
|
||||
/**
|
||||
* 修改背景的渲染比例,默认是 0.5
|
||||
*
|
||||
* 一般情况下这个程度既没有明显瑕疵也不会特别吃性能
|
||||
* @param scale 背景的渲染比例
|
||||
*/
|
||||
abstract setRenderScale(scale: number): void;
|
||||
/**
|
||||
* 是否启用静态模式,即图片在更换后就会保持静止状态并禁用更新,以节省性能
|
||||
* @param enable 是否启用静态模式
|
||||
*/
|
||||
abstract setStaticMode(enable: boolean): void;
|
||||
/**
|
||||
* 修改背景动画帧率,默认是 30 FPS
|
||||
*
|
||||
* 如果设置成 0 则会停止动画
|
||||
* @param fps 目标帧率,默认 30 FPS
|
||||
*/
|
||||
abstract setFPS(fps: number): void;
|
||||
/**
|
||||
* 暂停背景动画,画面即便是更新了图片也不会发生变化
|
||||
*/
|
||||
abstract pause(): void;
|
||||
/**
|
||||
* 恢复播放背景动画
|
||||
*/
|
||||
abstract resume(): void;
|
||||
/**
|
||||
* 设置背景专辑资源,纹理加载并设置完成后会返回
|
||||
* @param albumSource 专辑的资源链接,可以是图片或视频链接,抑或是任意 img/video 元素,如果提供字符串链接且为视频则需要指定第二个参数
|
||||
*/
|
||||
abstract setAlbum(
|
||||
albumSource: string | HTMLImageElement | HTMLVideoElement,
|
||||
isVideo?: boolean,
|
||||
): Promise<void>;
|
||||
/**
|
||||
* 设置低频的音量大小,范围在 80hz-120hz 之间为宜,取值范围在 [0.0-1.0] 之间
|
||||
*
|
||||
* 部分渲染器会根据音量大小调整背景效果(例如根据鼓点跳动)
|
||||
*
|
||||
* 如果无法获取到类似的数据,请传入 1.0 作为默认值,或不做任何处理(默认值即 1.0)
|
||||
* @param volume 低频的音量大小,范围在 50hz-120hz 之间为宜,取值范围在 [0.0-1.0] 之间
|
||||
*/
|
||||
abstract setLowFreqVolume(volume: number): void;
|
||||
/**
|
||||
* 设置背景是否根据“是否有歌词”这个特征调整自身效果,例如有歌词时会变得更加活跃
|
||||
*
|
||||
* 部分渲染器会根据这个特征调整自身效果
|
||||
*
|
||||
* 如果不确定是否需要赋值或无法知晓是否包含歌词,请传入 true 或不做任何处理(默认值为 true)
|
||||
*
|
||||
* @param hasLyric 是否有歌词,如不确定是否需要赋值,请传入 true 或不做任何处理(默认值为 true)
|
||||
*/
|
||||
abstract setHasLyric(hasLyric: boolean): void;
|
||||
abstract dispose(): void;
|
||||
abstract getElement(): HTMLElement;
|
||||
}
|
||||
|
||||
function clamp1(x: number): number {
|
||||
return Math.max(1, x);
|
||||
}
|
||||
|
||||
export abstract class BaseRenderer extends AbstractBaseRenderer {
|
||||
private observer: ResizeObserver;
|
||||
protected flowSpeed = 1;
|
||||
protected currerntRenderScale = 0.75;
|
||||
constructor(protected canvas: HTMLCanvasElement) {
|
||||
super();
|
||||
this.observer = new ResizeObserver(() => {
|
||||
const width = clamp1(
|
||||
canvas.clientWidth * window.devicePixelRatio * this.currerntRenderScale,
|
||||
);
|
||||
const height = clamp1(
|
||||
canvas.clientHeight *
|
||||
window.devicePixelRatio *
|
||||
this.currerntRenderScale,
|
||||
);
|
||||
this.onResize(width, height);
|
||||
});
|
||||
this.observer.observe(canvas);
|
||||
}
|
||||
setRenderScale(scale: number): void {
|
||||
this.currerntRenderScale = scale;
|
||||
this.onResize(
|
||||
this.canvas.clientWidth *
|
||||
window.devicePixelRatio *
|
||||
this.currerntRenderScale,
|
||||
this.canvas.clientHeight *
|
||||
window.devicePixelRatio *
|
||||
this.currerntRenderScale,
|
||||
);
|
||||
}
|
||||
/**
|
||||
* 当画板元素大小发生变化时此函数会被调用
|
||||
* 可以在此处重设和渲染器相关的尺寸设置
|
||||
* 考虑到初始化的时候元素不一定在文档中或出于某些特殊样式状态,尺寸长宽有可能会为 0,请注意进行特判处理
|
||||
* @param width 画板元素实际的物理像素宽度,有可能为 0
|
||||
* @param height 画板元素实际的物理像素高度,有可能为 0
|
||||
*/
|
||||
protected onResize(width: number, height: number): void {
|
||||
this.canvas.width = width;
|
||||
this.canvas.height = height;
|
||||
}
|
||||
/**
|
||||
* 修改背景的流动速度,数字越大越快,默认为 1
|
||||
* @param speed 背景的流动速度,默认为 1
|
||||
*/
|
||||
setFlowSpeed(speed: number): void {
|
||||
this.flowSpeed = speed;
|
||||
}
|
||||
/**
|
||||
* 是否启用静态模式,即图片在更换后就会保持静止状态并禁用更新,以节省性能
|
||||
* @param enable 是否启用静态模式
|
||||
*/
|
||||
abstract override setStaticMode(enable: boolean): void;
|
||||
/**
|
||||
* 修改背景动画帧率,默认是 30 FPS
|
||||
*
|
||||
* 如果设置成 0 则会停止动画
|
||||
* @param fps 目标帧率,默认 30 FPS
|
||||
*/
|
||||
abstract override setFPS(fps: number): void;
|
||||
/**
|
||||
* 暂停背景动画,画面即便是更新了图片也不会发生变化
|
||||
*/
|
||||
abstract override pause(): void;
|
||||
/**
|
||||
* 恢复播放背景动画
|
||||
*/
|
||||
abstract override resume(): void;
|
||||
/**
|
||||
* 设置背景专辑资源,纹理加载并设置完成后会返回
|
||||
* @param albumSource 专辑的资源链接,可以是图片或视频链接,抑或是任意 img/video 元素,如果提供字符串链接且为视频则需要指定第二个参数
|
||||
*/
|
||||
abstract override setAlbum(
|
||||
albumSource: string | HTMLImageElement | HTMLVideoElement,
|
||||
isVideo?: boolean,
|
||||
): Promise<void>;
|
||||
dispose(): void {
|
||||
this.observer.disconnect();
|
||||
this.canvas.remove();
|
||||
}
|
||||
override getElement(): HTMLElement {
|
||||
return this.canvas;
|
||||
}
|
||||
}
|
||||
168
amll-local/packages/core/src/bg-render/img.ts
Normal file
168
amll-local/packages/core/src/bg-render/img.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
export function blurImage(
|
||||
imageData: ImageData,
|
||||
radius: number,
|
||||
quality: number,
|
||||
): void {
|
||||
const pixels = imageData.data;
|
||||
const width = imageData.width;
|
||||
const height = imageData.height;
|
||||
|
||||
let rsum: number;
|
||||
let gsum: number;
|
||||
let bsum: number;
|
||||
let asum: number;
|
||||
let x: number;
|
||||
let y: number;
|
||||
let i: number;
|
||||
let p: number;
|
||||
let p1: number;
|
||||
let p2: number;
|
||||
let yp: number;
|
||||
let yi: number;
|
||||
let yw: number;
|
||||
const wm = width - 1;
|
||||
const hm = height - 1;
|
||||
const rad1x = radius + 1;
|
||||
const divx = radius + rad1x;
|
||||
const rad1y = radius + 1;
|
||||
const divy = radius + rad1y;
|
||||
const div2 = 1 / (divx * divy);
|
||||
|
||||
const r: number[] = [];
|
||||
const g: number[] = [];
|
||||
const b: number[] = [];
|
||||
const a: number[] = [];
|
||||
|
||||
const vmin: number[] = [];
|
||||
const vmax: number[] = [];
|
||||
|
||||
while (quality-- > 0) {
|
||||
yw = yi = 0;
|
||||
|
||||
for (y = 0; y < height; y++) {
|
||||
rsum = pixels[yw] * rad1x;
|
||||
gsum = pixels[yw + 1] * rad1x;
|
||||
bsum = pixels[yw + 2] * rad1x;
|
||||
asum = pixels[yw + 3] * rad1x;
|
||||
|
||||
for (i = 1; i <= radius; i++) {
|
||||
p = yw + ((i > wm ? wm : i) << 2);
|
||||
rsum += pixels[p++];
|
||||
gsum += pixels[p++];
|
||||
bsum += pixels[p++];
|
||||
asum += pixels[p];
|
||||
}
|
||||
|
||||
for (x = 0; x < width; x++) {
|
||||
r[yi] = rsum;
|
||||
g[yi] = gsum;
|
||||
b[yi] = bsum;
|
||||
a[yi] = asum;
|
||||
|
||||
if (y === 0) {
|
||||
vmin[x] = Math.min(x + rad1x, wm) << 2;
|
||||
vmax[x] = Math.max(x - radius, 0) << 2;
|
||||
}
|
||||
|
||||
p1 = yw + vmin[x];
|
||||
p2 = yw + vmax[x];
|
||||
|
||||
rsum += pixels[p1++] - pixels[p2++];
|
||||
gsum += pixels[p1++] - pixels[p2++];
|
||||
bsum += pixels[p1++] - pixels[p2++];
|
||||
asum += pixels[p1] - pixels[p2];
|
||||
|
||||
yi++;
|
||||
}
|
||||
yw += width << 2;
|
||||
}
|
||||
|
||||
for (x = 0; x < width; x++) {
|
||||
yp = x;
|
||||
rsum = r[yp] * rad1y;
|
||||
gsum = g[yp] * rad1y;
|
||||
bsum = b[yp] * rad1y;
|
||||
asum = a[yp] * rad1y;
|
||||
|
||||
for (i = 1; i <= radius; i++) {
|
||||
yp += i > hm ? 0 : width;
|
||||
rsum += r[yp];
|
||||
gsum += g[yp];
|
||||
bsum += b[yp];
|
||||
asum += a[yp];
|
||||
}
|
||||
|
||||
yi = x << 2;
|
||||
for (y = 0; y < height; y++) {
|
||||
pixels[yi] = (rsum * div2 + 0.5) | 0;
|
||||
pixels[yi + 1] = (gsum * div2 + 0.5) | 0;
|
||||
pixels[yi + 2] = (bsum * div2 + 0.5) | 0;
|
||||
pixels[yi + 3] = (asum * div2 + 0.5) | 0;
|
||||
|
||||
if (x === 0) {
|
||||
vmin[y] = Math.min(y + rad1y, hm) * width;
|
||||
vmax[y] = Math.max(y - radius, 0) * width;
|
||||
}
|
||||
|
||||
p1 = x + vmin[y];
|
||||
p2 = x + vmax[y];
|
||||
|
||||
rsum += r[p1] - r[p2];
|
||||
gsum += g[p1] - g[p2];
|
||||
bsum += b[p1] - b[p2];
|
||||
asum += a[p1] - a[p2];
|
||||
|
||||
yi += width << 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function saturateImage(imageData: ImageData, saturation: number): void {
|
||||
const pixels = imageData.data;
|
||||
|
||||
for (let i = 0; i < pixels.length; i += 4) {
|
||||
const r = pixels[i];
|
||||
const g = pixels[i + 1];
|
||||
const b = pixels[i + 2];
|
||||
const a = pixels[i + 3];
|
||||
const gray = r * 0.3 + g * 0.59 + b * 0.11;
|
||||
pixels[i] = gray * (1 - saturation) + r * saturation;
|
||||
pixels[i + 1] = gray * (1 - saturation) + g * saturation;
|
||||
pixels[i + 2] = gray * (1 - saturation) + b * saturation;
|
||||
pixels[i + 3] = a;
|
||||
}
|
||||
}
|
||||
|
||||
export function brightnessImage(
|
||||
imageData: ImageData,
|
||||
brightness: number,
|
||||
): void {
|
||||
const pixels = imageData.data;
|
||||
|
||||
for (let i = 0; i < pixels.length; i += 4) {
|
||||
const r = pixels[i];
|
||||
const g = pixels[i + 1];
|
||||
const b = pixels[i + 2];
|
||||
const a = pixels[i + 3];
|
||||
pixels[i] = r * brightness;
|
||||
pixels[i + 1] = g * brightness;
|
||||
pixels[i + 2] = b * brightness;
|
||||
pixels[i + 3] = a;
|
||||
}
|
||||
}
|
||||
|
||||
export function contrastImage(imageData: ImageData, contrast: number): void {
|
||||
const pixels = imageData.data;
|
||||
|
||||
for (let i = 0; i < pixels.length; i += 4) {
|
||||
const r = pixels[i];
|
||||
const g = pixels[i + 1];
|
||||
const b = pixels[i + 2];
|
||||
const a = pixels[i + 3];
|
||||
pixels[i] = (r - 128) * contrast + 128;
|
||||
pixels[i + 1] = (g - 128) * contrast + 128;
|
||||
pixels[i + 2] = (b - 128) * contrast + 128;
|
||||
pixels[i + 3] = a;
|
||||
}
|
||||
}
|
||||
71
amll-local/packages/core/src/bg-render/index.ts
Normal file
71
amll-local/packages/core/src/bg-render/index.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* @fileoverview
|
||||
* 一个播放歌词的组件
|
||||
* @author SteveXMH
|
||||
*/
|
||||
|
||||
export { AbstractBaseRenderer, BaseRenderer } from "./base.ts";
|
||||
export { MeshGradientRenderer } from "./mesh-renderer/index.ts";
|
||||
export { PixiRenderer } from "./pixi-renderer.ts";
|
||||
import type { AbstractBaseRenderer, BaseRenderer } from "./base.ts";
|
||||
|
||||
export class BackgroundRender<Renderer extends BaseRenderer>
|
||||
implements AbstractBaseRenderer
|
||||
{
|
||||
private element: HTMLCanvasElement;
|
||||
private renderer: Renderer;
|
||||
constructor(renderer: Renderer, canvas: HTMLCanvasElement) {
|
||||
this.renderer = renderer;
|
||||
|
||||
this.element = canvas;
|
||||
canvas.style.pointerEvents = "none";
|
||||
canvas.style.zIndex = "-1";
|
||||
canvas.style.contain = "strict";
|
||||
}
|
||||
|
||||
static new<Renderer extends BaseRenderer>(type: {
|
||||
new (canvas: HTMLCanvasElement): Renderer;
|
||||
}): BackgroundRender<Renderer> {
|
||||
const newCanvas = document.createElement("canvas");
|
||||
return new BackgroundRender(new type(newCanvas), newCanvas);
|
||||
}
|
||||
|
||||
setRenderScale(scale: number): void {
|
||||
this.renderer.setRenderScale(scale);
|
||||
}
|
||||
|
||||
setFlowSpeed(speed: number): void {
|
||||
this.renderer.setFlowSpeed(speed);
|
||||
}
|
||||
setStaticMode(enable: boolean): void {
|
||||
this.renderer.setStaticMode(enable);
|
||||
}
|
||||
setFPS(fps: number): void {
|
||||
this.renderer.setFPS(fps);
|
||||
}
|
||||
pause(): void {
|
||||
this.renderer.pause();
|
||||
}
|
||||
resume(): void {
|
||||
this.renderer.resume();
|
||||
}
|
||||
setLowFreqVolume(volume: number): void {
|
||||
this.renderer.setLowFreqVolume(volume);
|
||||
}
|
||||
setHasLyric(hasLyric: boolean): void {
|
||||
this.renderer.setHasLyric(hasLyric);
|
||||
}
|
||||
setAlbum(
|
||||
albumSource: string | HTMLImageElement | HTMLVideoElement,
|
||||
isVideo?: boolean,
|
||||
): Promise<void> {
|
||||
return this.renderer.setAlbum(albumSource, isVideo);
|
||||
}
|
||||
getElement(): HTMLCanvasElement {
|
||||
return this.element;
|
||||
}
|
||||
dispose(): void {
|
||||
this.renderer.dispose();
|
||||
this.element.remove();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* @fileoverview
|
||||
* 实验性的随机控制点生成函数算法
|
||||
* 目的是取代原先大量的预设控制点代码
|
||||
*/
|
||||
|
||||
import { clamp01 } from "#utils/clamp.ts";
|
||||
import {
|
||||
type ControlPointConf,
|
||||
type ControlPointPreset,
|
||||
p,
|
||||
preset,
|
||||
} from "./cp-presets.ts";
|
||||
|
||||
const randomRange = (min: number, max: number): number =>
|
||||
Math.random() * (max - min) + min;
|
||||
|
||||
function smoothstep(edge0: number, edge1: number, x: number): number {
|
||||
const t = clamp01((x - edge0) / (edge1 - edge0));
|
||||
return t * t * (3 - 2 * t);
|
||||
}
|
||||
|
||||
function smoothifyControlPoints(
|
||||
conf: ControlPointConf[],
|
||||
w: number,
|
||||
h: number,
|
||||
iterations = 2,
|
||||
factor = 0.5,
|
||||
factorIterationModifier = 0.1,
|
||||
): void {
|
||||
let grid: ControlPointConf[][] = [];
|
||||
let f = factor;
|
||||
|
||||
for (let j = 0; j < h; j++) {
|
||||
grid[j] = [];
|
||||
for (let i = 0; i < w; i++) {
|
||||
grid[j][i] = conf[j * w + i];
|
||||
}
|
||||
}
|
||||
|
||||
const kernel = [
|
||||
[1, 2, 1],
|
||||
[2, 4, 2],
|
||||
[1, 2, 1],
|
||||
];
|
||||
const kernelSum = 16;
|
||||
|
||||
for (let iter = 0; iter < iterations; iter++) {
|
||||
const newGrid: ControlPointConf[][] = [];
|
||||
for (let j = 0; j < h; j++) {
|
||||
newGrid[j] = [];
|
||||
for (let i = 0; i < w; i++) {
|
||||
if (i === 0 || i === w - 1 || j === 0 || j === h - 1) {
|
||||
newGrid[j][i] = grid[j][i];
|
||||
continue;
|
||||
}
|
||||
let sumX = 0;
|
||||
let sumY = 0;
|
||||
let sumUR = 0;
|
||||
let sumVR = 0;
|
||||
let sumUP = 0;
|
||||
let sumVP = 0;
|
||||
for (let dj = -1; dj <= 1; dj++) {
|
||||
for (let di = -1; di <= 1; di++) {
|
||||
const weight = kernel[dj + 1][di + 1];
|
||||
const nb = grid[j + dj][i + di];
|
||||
sumX += nb.x * weight;
|
||||
sumY += nb.y * weight;
|
||||
sumUR += nb.ur * weight;
|
||||
sumVR += nb.vr * weight;
|
||||
sumUP += nb.up * weight;
|
||||
sumVP += nb.vp * weight;
|
||||
}
|
||||
}
|
||||
const avgX = sumX / kernelSum;
|
||||
const avgY = sumY / kernelSum;
|
||||
const avgUR = sumUR / kernelSum;
|
||||
const avgVR = sumVR / kernelSum;
|
||||
const avgUP = sumUP / kernelSum;
|
||||
const avgVP = sumVP / kernelSum;
|
||||
|
||||
const cur = grid[j][i];
|
||||
const newX = cur.x * (1 - f) + avgX * f;
|
||||
const newY = cur.y * (1 - f) + avgY * f;
|
||||
const newUR = cur.ur * (1 - f) + avgUR * f;
|
||||
const newVR = cur.vr * (1 - f) + avgVR * f;
|
||||
const newUP = cur.up * (1 - f) + avgUP * f;
|
||||
const newVP = cur.vp * (1 - f) + avgVP * f;
|
||||
newGrid[j][i] = p(i, j, newX, newY, newUR, newVR, newUP, newVP);
|
||||
}
|
||||
}
|
||||
grid = newGrid;
|
||||
f = clamp01(f + factorIterationModifier);
|
||||
}
|
||||
|
||||
for (let j = 0; j < h; j++) {
|
||||
for (let i = 0; i < w; i++) {
|
||||
conf[j * w + i] = grid[j][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function noise(x: number, y: number): number {
|
||||
return fract(Math.sin(x * 12.9898 + y * 78.233) * 43758.5453);
|
||||
}
|
||||
|
||||
function fract(x: number): number {
|
||||
return x - Math.floor(x);
|
||||
}
|
||||
|
||||
function smoothNoise(x: number, y: number): number {
|
||||
const x0 = Math.floor(x);
|
||||
const y0 = Math.floor(y);
|
||||
const x1 = x0 + 1;
|
||||
const y1 = y0 + 1;
|
||||
|
||||
const xf = x - x0;
|
||||
const yf = y - y0;
|
||||
|
||||
const u = xf * xf * (3 - 2 * xf);
|
||||
const v = yf * yf * (3 - 2 * yf);
|
||||
|
||||
const n00 = noise(x0, y0);
|
||||
const n10 = noise(x1, y0);
|
||||
const n01 = noise(x0, y1);
|
||||
const n11 = noise(x1, y1);
|
||||
|
||||
const nx0 = n00 * (1 - u) + n10 * u;
|
||||
const nx1 = n01 * (1 - u) + n11 * u;
|
||||
|
||||
return nx0 * (1 - v) + nx1 * v;
|
||||
}
|
||||
|
||||
function computeNoiseGradient(
|
||||
perlinFn: (x: number, y: number) => number,
|
||||
x: number,
|
||||
y: number,
|
||||
epsilon = 0.001,
|
||||
): [number, number] {
|
||||
const n1 = perlinFn(x + epsilon, y);
|
||||
const n2 = perlinFn(x - epsilon, y);
|
||||
const n3 = perlinFn(x, y + epsilon);
|
||||
const n4 = perlinFn(x, y - epsilon);
|
||||
const dx = (n1 - n2) / (2 * epsilon);
|
||||
const dy = (n3 - n4) / (2 * epsilon);
|
||||
const len = Math.sqrt(dx * dx + dy * dy) || 1;
|
||||
return [dx / len, dy / len];
|
||||
}
|
||||
|
||||
export function generateControlPoints(
|
||||
width: number,
|
||||
height: number,
|
||||
variationFraction: number = randomRange(0.4, 0.6), // = 0.2,
|
||||
normalOffset: number = randomRange(0.3, 0.6), // = 0.3,
|
||||
blendFactor = 0.8,
|
||||
smoothIters: number = Math.floor(randomRange(3, 5)), // = 3,
|
||||
smoothFactor: number = randomRange(0.2, 0.3), // = 0.3,
|
||||
smoothModifier: number = randomRange(-0.1, -0.05), // = -0.05,
|
||||
): ControlPointPreset {
|
||||
const w = width ?? Math.floor(randomRange(3, 6));
|
||||
const h = height ?? Math.floor(randomRange(3, 6));
|
||||
|
||||
const conf: ControlPointConf[] = [];
|
||||
const dx = w === 1 ? 0 : 2 / (w - 1);
|
||||
const dy = h === 1 ? 0 : 2 / (h - 1);
|
||||
|
||||
for (let j = 0; j < h; j++) {
|
||||
for (let i = 0; i < w; i++) {
|
||||
const baseX = (w === 1 ? 0 : i / (w - 1)) * 2 - 1;
|
||||
const baseY = (h === 1 ? 0 : j / (h - 1)) * 2 - 1;
|
||||
|
||||
const isBorder = i === 0 || i === w - 1 || j === 0 || j === h - 1;
|
||||
const pertX = isBorder
|
||||
? 0
|
||||
: randomRange(-variationFraction * dx, variationFraction * dx);
|
||||
const pertY = isBorder
|
||||
? 0
|
||||
: randomRange(-variationFraction * dy, variationFraction * dy);
|
||||
let x = baseX + pertX;
|
||||
let y = baseY + pertY;
|
||||
|
||||
const ur = isBorder ? 0 : randomRange(-60, 60);
|
||||
const vr = isBorder ? 0 : randomRange(-60, 60);
|
||||
const up = isBorder ? 1 : randomRange(0.8, 1.2);
|
||||
const vp = isBorder ? 1 : randomRange(0.8, 1.2);
|
||||
|
||||
if (!isBorder) {
|
||||
const uNorm = (baseX + 1) / 2;
|
||||
const vNorm = (baseY + 1) / 2;
|
||||
|
||||
const [nx, ny] = computeNoiseGradient(smoothNoise, uNorm, vNorm, 0.001);
|
||||
let offsetX = nx * normalOffset;
|
||||
let offsetY = ny * normalOffset;
|
||||
|
||||
const distToBorder = Math.min(uNorm, 1 - uNorm, vNorm, 1 - vNorm); // in [0,0.5]
|
||||
|
||||
const weight = smoothstep(0, 1.0, distToBorder);
|
||||
offsetX *= weight;
|
||||
offsetY *= weight;
|
||||
|
||||
x = x * (1 - blendFactor) + (x + offsetX) * blendFactor;
|
||||
y = y * (1 - blendFactor) + (y + offsetY) * blendFactor;
|
||||
}
|
||||
conf.push(p(i, j, x, y, ur, vr, up, vp));
|
||||
}
|
||||
}
|
||||
|
||||
smoothifyControlPoints(conf, w, h, smoothIters, smoothFactor, smoothModifier);
|
||||
|
||||
return preset(w, h, conf);
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/** @internal */
|
||||
export interface ControlPointConf {
|
||||
cx: number;
|
||||
cy: number;
|
||||
x: number;
|
||||
y: number;
|
||||
ur: number;
|
||||
vr: number;
|
||||
up: number;
|
||||
vp: number;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface ControlPointPreset {
|
||||
width: number;
|
||||
height: number;
|
||||
conf: ControlPointConf[];
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export const p = (
|
||||
cx: number,
|
||||
cy: number,
|
||||
x: number,
|
||||
y: number,
|
||||
ur = 0,
|
||||
vr = 0,
|
||||
up = 1,
|
||||
vp = 1,
|
||||
) => Object.freeze({ cx, cy, x, y, ur, vr, up, vp }) as ControlPointConf;
|
||||
/** @internal */
|
||||
export const preset = (
|
||||
width: number,
|
||||
height: number,
|
||||
conf: ControlPointConf[],
|
||||
) => Object.freeze({ width, height, conf }) as ControlPointPreset;
|
||||
|
||||
export const CONTROL_POINT_PRESETS: ControlPointPreset[] = [
|
||||
// TODO: 竖屏推荐
|
||||
preset(5, 5, [
|
||||
p(0, 0, -1, -1, 0, 0, 1, 1),
|
||||
p(1, 0, -0.5, -1, 0, 0, 1, 1),
|
||||
p(2, 0, 0, -1, 0, 0, 1, 1),
|
||||
p(3, 0, 0.5, -1, 0, 0, 1, 1),
|
||||
p(4, 0, 1, -1, 0, 0, 1, 1),
|
||||
p(0, 1, -1, -0.5, 0, 0, 1, 1),
|
||||
p(1, 1, -0.5, -0.5, 0, 0, 1, 1),
|
||||
p(2, 1, -0.0052029684413368305, -0.6131420587090777, 0, 0, 1, 1),
|
||||
p(3, 1, 0.5884227308309977, -0.3990805107556692, 0, 0, 1, 1),
|
||||
p(4, 1, 1, -0.5, 0, 0, 1, 1),
|
||||
p(0, 2, -1, 0, 0, 0, 1, 1),
|
||||
p(1, 2, -0.4210024670505933, -0.11895058380429502, 0, 0, 1, 1),
|
||||
p(2, 2, -0.1019613423315412, -0.023812118047224606, 0, -47, 0.629, 0.849),
|
||||
p(3, 2, 0.40275125660925437, -0.06345314544600389, 0, 0, 1, 1),
|
||||
p(4, 2, 1, 0, 0, 0, 1, 1),
|
||||
p(0, 3, -1, 0.5, 0, 0, 1, 1),
|
||||
p(1, 3, 0.06801958477287173, 0.5205913248960121, -31, -45, 1, 1),
|
||||
p(2, 3, 0.21446469120128908, 0.29331610114301043, 6, -56, 0.566, 1.321),
|
||||
p(3, 3, 0.5, 0.5, 0, 0, 1, 1),
|
||||
p(4, 3, 1, 0.5, 0, 0, 1, 1),
|
||||
p(0, 4, -1, 1, 0, 0, 1, 1),
|
||||
p(1, 4, -0.31378372841550195, 1, 0, 0, 1, 1),
|
||||
p(2, 4, 0.26153633255328046, 1, 0, 0, 1, 1),
|
||||
p(3, 4, 0.5, 1, 0, 0, 1, 1),
|
||||
p(4, 4, 1, 1, 0, 0, 1, 1),
|
||||
]),
|
||||
// TODO: 横屏推荐
|
||||
preset(4, 4, [
|
||||
p(0, 0, -1, -1, 0, 0, 1, 1),
|
||||
p(1, 0, -0.33333333333333337, -1, 0, 0, 1, 1),
|
||||
p(2, 0, 0.33333333333333326, -1, 0, 0, 1, 1),
|
||||
p(3, 0, 1, -1, 0, 0, 1, 1),
|
||||
p(0, 1, -1, -0.04495399932657351, 0, 0, 1, 1),
|
||||
p(1, 1, -0.24056117520129328, -0.22465999020104, 0, 0, 1, 1),
|
||||
p(2, 1, 0.334758885767489, -0.00531297192779423, 0, 0, 1, 1),
|
||||
p(3, 1, 0.9989920470678106, -0.3382976020775408, 8, 0, 0.566, 1.792),
|
||||
p(0, 2, -1, 0.33333333333333326, 0, 0, 1, 1),
|
||||
p(1, 2, -0.3425497314639411, -0.000027501607956947893, 0, 0, 1, 1),
|
||||
p(2, 2, 0.3321437945812673, 0.1981776353859399, 0, 0, 1, 1),
|
||||
p(3, 2, 1, 0.0766118180296832, 0, 0, 1, 1),
|
||||
p(0, 3, -1, 1, 0, 0, 1, 1),
|
||||
p(1, 3, -0.33333333333333337, 1, 0, 0, 1, 1),
|
||||
p(2, 3, 0.33333333333333326, 1, 0, 0, 1, 1),
|
||||
p(3, 3, 1, 1, 0, 0, 1, 1),
|
||||
]),
|
||||
preset(4, 4, [
|
||||
p(0, 0, -1, -1, 0, 0, 1, 2.075),
|
||||
p(1, 0, -0.33333333333333337, -1, 0, 0, 1, 1),
|
||||
p(2, 0, 0.33333333333333326, -1, 0, 0, 1, 1),
|
||||
p(3, 0, 1, -1, 0, 0, 1, 1),
|
||||
p(0, 1, -1, -0.4545779491139603, 0, 0, 1, 1),
|
||||
p(1, 1, -0.33333333333333337, -0.33333333333333337, 0, 0, 1, 1),
|
||||
p(2, 1, 0.0889403142626457, -0.6025711180694033, -32, 45, 1, 1),
|
||||
p(3, 1, 1, -0.33333333333333337, 0, 0, 1, 1),
|
||||
p(0, 2, -1, -0.07402408608567845, 1, 0, 1, 0.094),
|
||||
p(1, 2, -0.2719422694359541, 0.09775369930903222, 25, -18, 1.321, 0),
|
||||
p(2, 2, 0.19877414408395877, 0.4307383294587789, 48, -40, 0.755, 0.975),
|
||||
p(3, 2, 1, 0.33333333333333326, -37, 0, 1, 1),
|
||||
p(0, 3, -1, 1, 0, 0, 1, 1),
|
||||
p(1, 3, -0.33333333333333337, 1, 0, 0, 1, 1),
|
||||
p(2, 3, 0.5125850864305672, 1, -20, -18, 0, 1.604),
|
||||
p(3, 3, 1, 1, 0, 0, 1, 1),
|
||||
]),
|
||||
preset(5, 5, [
|
||||
p(0, 0, -1, -1, 0, 0, 1, 1),
|
||||
p(1, 0, -0.4501953125, -1, 0, 55, 1, 2.075),
|
||||
p(2, 0, 0.1953125, -1, 0, 0, 1, 1),
|
||||
p(3, 0, 0.4580078125, -1, 0, -25, 1, 1),
|
||||
p(4, 0, 1, -1, 0, 0, 1, 1),
|
||||
p(0, 1, -1, -0.2514475377525607, -16, 0, 2.327, 0.943),
|
||||
p(1, 1, -0.55859375, -0.6609325945787148, 47, 0, 2.358, 0.377),
|
||||
p(2, 1, 0.232421875, -0.5244375756366635, -66, -25, 1.855, 1.164),
|
||||
p(3, 1, 0.685546875, -0.3753706470552125, 0, 0, 1, 1),
|
||||
p(4, 1, 1, -0.6699125300354287, 0, 0, 1, 1),
|
||||
p(0, 2, -1, 0.035910396862284255, 0, 0, 1, 1),
|
||||
p(1, 2, -0.4921875, 0.005378616309457018, 90, 23, 1, 1.981),
|
||||
p(2, 2, 0.021484375, -0.1365043639066228, 0, 42, 1, 1),
|
||||
p(3, 2, 0.4765625, 0.05925822904974043, -30, 0, 1.95, 0.44),
|
||||
p(4, 2, 1, 0.251428847823418, 0, 0, 1, 1),
|
||||
p(0, 3, -1, 0.6968336464764276, -68, 0, 1, 0.786),
|
||||
p(1, 3, -0.6904296875, 0.5890744209958608, -68, 0, 1, 1),
|
||||
p(2, 3, 0.1845703125, 0.3879238667654693, 61, 0, 1, 1),
|
||||
p(3, 3, 0.60546875, 0.4633553246018661, -47, -59, 0.849, 1.73),
|
||||
p(4, 3, 1, 0.6214021886400309, -33, 0, 0.377, 1.604),
|
||||
p(0, 4, -1, 1, 0, 0, 1, 1),
|
||||
p(1, 4, -0.5, 1, 0, -73, 1, 1),
|
||||
p(2, 4, -0.3271484375, 1, 0, -24, 0.314, 2.704),
|
||||
p(3, 4, 0.5, 1, 0, 0, 1, 1),
|
||||
p(4, 4, 1, 1, 0, 0, 1, 1),
|
||||
]),
|
||||
preset(5, 5, [
|
||||
p(0, 0, -1, -1),
|
||||
p(1, 0, -0.6393, -1, 0, 0, 1, 2.3884),
|
||||
p(2, 0, 0, -1),
|
||||
p(3, 0, 0.5, -1),
|
||||
p(4, 0, 1, -1),
|
||||
p(0, 1, -1, -0.2301),
|
||||
p(1, 1, -0.6934, -0.331, 0, -0.7188, 1, 1.063),
|
||||
p(2, 1, -0.0082, -0.6814, -0.2583, 0, 1.0964, 1),
|
||||
p(3, 1, 0.5836, -0.531, 0.7029, 0, 1.5466, 1),
|
||||
p(4, 1, 1, -0.6407),
|
||||
p(0, 2, -1, 0.2973, 0, 0, 1.8352, 1),
|
||||
p(1, 2, -0.4082, 0.0602),
|
||||
p(2, 2, -0.1803, -0.3646, -0.2998, 0, 1.1513, 1),
|
||||
p(3, 2, 0.477, -0.1027, 0.8903, -0.1882, 1.0807, 0.8551),
|
||||
p(4, 2, 1, -0.2973),
|
||||
p(0, 3, -1, 0.7628, 0, 0, 2.3868, 1),
|
||||
p(1, 3, -0.2525, 0.4814, -0.8406, -1.6199, 1.4093, 1.2215),
|
||||
p(2, 3, 0.3607, 0.2814, -1.0713, -0.0529, 1.0025, 0.7611),
|
||||
p(3, 3, 0.4885, 0.623, 0, 0.8184, 1, 1.2876),
|
||||
p(4, 3, 1, 0.5),
|
||||
p(0, 4, -1, 1),
|
||||
p(1, 4, -0.4033, 1),
|
||||
p(2, 4, 0.2672, 1),
|
||||
p(3, 4, 0.5967, 1),
|
||||
p(4, 4, 1, 1),
|
||||
]),
|
||||
preset(5, 5, [
|
||||
p(0, 0, -1, -1),
|
||||
p(1, 0, -0.2197, -1),
|
||||
p(2, 0, 0.0197, -1),
|
||||
p(3, 0, 0.8033, -1),
|
||||
p(4, 0, 1, -1),
|
||||
p(0, 1, -1, -0.5451),
|
||||
p(1, 1, -0.4885, -0.4035, -1.0246, -0.2268, 1.1936, 0.8005),
|
||||
p(2, 1, -0.1213, -0.2867, 0, -0.6981, 1, 0.809),
|
||||
p(3, 1, 0.3246, -0.5628, 0, -1.2188, 1, 1.044),
|
||||
p(4, 1, 1, -0.3292),
|
||||
p(0, 2, -1, 0.1416),
|
||||
p(1, 2, -0.341, -0.0142, 0, -0.4004, 1, 1.1293),
|
||||
p(2, 2, -0.0393, -0.023, 0.2915, -0.373, 1.044, 0.9879),
|
||||
p(3, 2, 0.3148, -0.0673, -0.7853, -0.8962, 1.4709, 1.0247),
|
||||
p(4, 2, 1, 0.1912),
|
||||
p(0, 3, -1, 0.5),
|
||||
p(1, 3, -0.2689, 0.2743, 0.3404, -0.5248, 1.0184, 0.4391),
|
||||
p(2, 3, 0.0721, 0.269, 0.5302, 0.1244, 0.6723, 0.3225),
|
||||
p(3, 3, 0.4148, 0.3894, -0.6977, -0.6783, 0.8094, 0.9247),
|
||||
p(4, 3, 1, 0.446),
|
||||
p(0, 4, -1, 1),
|
||||
p(1, 4, -0.7311, 1),
|
||||
p(2, 4, 0.323, 1),
|
||||
p(3, 4, 0.6393, 1),
|
||||
p(4, 4, 1, 1),
|
||||
]),
|
||||
] as const;
|
||||
1352
amll-local/packages/core/src/bg-render/mesh-renderer/index.ts
Normal file
1352
amll-local/packages/core/src/bg-render/mesh-renderer/index.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,53 @@
|
||||
precision highp float;
|
||||
|
||||
varying vec3 v_color;
|
||||
varying vec2 v_uv;
|
||||
uniform sampler2D u_texture;
|
||||
uniform float u_time;
|
||||
uniform float u_volume;
|
||||
uniform float u_alpha;
|
||||
|
||||
// 预计算常量
|
||||
const float INV_255 = 1.0 / 255.0;
|
||||
const float HALF_INV_255 = 0.5 / 255.0;
|
||||
const float GRADIENT_NOISE_A = 52.9829189;
|
||||
const vec2 GRADIENT_NOISE_B = vec2(0.06711056, 0.00583715);
|
||||
|
||||
/* Gradient noise from Jorge Jimenez's presentation: */
|
||||
/* http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare */
|
||||
float gradientNoise(in vec2 uv) {
|
||||
return fract(GRADIENT_NOISE_A * fract(dot(uv, GRADIENT_NOISE_B)));
|
||||
}
|
||||
|
||||
// 优化的旋转函数,避免重复计算sin/cos
|
||||
vec2 rot(vec2 v, float angle) {
|
||||
float s = sin(angle);
|
||||
float c = cos(angle);
|
||||
return vec2(c * v.x - s * v.y, s * v.x + c * v.y);
|
||||
}
|
||||
|
||||
void main() {
|
||||
// 合并计算以减少指令数
|
||||
float volumeEffect = u_volume * 2.0;
|
||||
float timeVolume = u_time + u_volume;
|
||||
|
||||
float dither = INV_255 * gradientNoise(gl_FragCoord.xy) - HALF_INV_255;
|
||||
vec2 centeredUV = v_uv - vec2(0.2);
|
||||
vec2 rotatedUV = rot(centeredUV, timeVolume * 2.0);
|
||||
vec2 finalUV = rotatedUV * max(0.001, 1.0 - volumeEffect) + vec2(0.5);
|
||||
|
||||
vec4 result = texture2D(u_texture, finalUV);
|
||||
|
||||
float alphaVolumeFactor = u_alpha * max(0.5, 1.0 - u_volume * 0.5);
|
||||
result.rgb *= v_color * alphaVolumeFactor;
|
||||
result.a *= alphaVolumeFactor;
|
||||
|
||||
result.rgb += vec3(dither);
|
||||
|
||||
float dist = distance(v_uv, vec2(0.5));
|
||||
float vignette = smoothstep(0.8, 0.3, dist);
|
||||
float mask = 0.6 + vignette * 0.4;
|
||||
result.rgb *= mask;
|
||||
|
||||
gl_FragColor = result;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
precision highp float;
|
||||
|
||||
attribute vec2 a_pos;
|
||||
attribute vec3 a_color;
|
||||
attribute vec2 a_uv;
|
||||
varying vec3 v_color;
|
||||
varying vec2 v_uv;
|
||||
|
||||
uniform float u_aspect;
|
||||
|
||||
void main() {
|
||||
v_color = a_color;
|
||||
v_uv = a_uv;
|
||||
vec2 pos = a_pos;
|
||||
if (u_aspect > 1.0) {
|
||||
pos.y *= u_aspect;
|
||||
} else {
|
||||
pos.x /= u_aspect;
|
||||
}
|
||||
gl_Position = vec4(pos, 0.0, 1.0);
|
||||
}
|
||||
264
amll-local/packages/core/src/bg-render/pixi-renderer.ts
Normal file
264
amll-local/packages/core/src/bg-render/pixi-renderer.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
import { Application } from "@pixi/app";
|
||||
import { Texture } from "@pixi/core";
|
||||
import { Container } from "@pixi/display";
|
||||
import { BlurFilter } from "@pixi/filter-blur";
|
||||
import { BulgePinchFilter } from "@pixi/filter-bulge-pinch";
|
||||
import { ColorMatrixFilter } from "@pixi/filter-color-matrix";
|
||||
import { Sprite } from "@pixi/sprite";
|
||||
import {
|
||||
loadResourceFromElement,
|
||||
loadResourceFromUrl,
|
||||
} from "../utils/resource";
|
||||
import { BaseRenderer } from "./base";
|
||||
import { clampPositive } from "#utils/clamp.ts";
|
||||
|
||||
class TimedContainer extends Container {
|
||||
public time = 0;
|
||||
}
|
||||
|
||||
export class PixiRenderer extends BaseRenderer {
|
||||
private app: Application;
|
||||
private curContainer?: TimedContainer;
|
||||
private staticMode = false;
|
||||
private lastContainer: Set<TimedContainer> = new Set();
|
||||
private onTick = (delta: number): void => {
|
||||
for (const lastContainer of this.lastContainer) {
|
||||
lastContainer.alpha = clampPositive(lastContainer.alpha - delta / 60);
|
||||
if (lastContainer.alpha <= 0) {
|
||||
this.app.stage.removeChild(lastContainer);
|
||||
this.lastContainer.delete(lastContainer);
|
||||
lastContainer.destroy(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.curContainer) {
|
||||
this.curContainer.alpha = Math.min(
|
||||
1,
|
||||
this.curContainer.alpha + delta / 60,
|
||||
);
|
||||
const [s1, s2, s3, s4] = this.curContainer.children as Sprite[];
|
||||
const maxSize = Math.max(this.app.screen.width, this.app.screen.height);
|
||||
s1.position.set(this.app.screen.width / 2, this.app.screen.height / 2);
|
||||
s2.position.set(
|
||||
this.app.screen.width / 2.5,
|
||||
this.app.screen.height / 2.5,
|
||||
);
|
||||
s3.position.set(this.app.screen.width / 2, this.app.screen.height / 2);
|
||||
s4.position.set(this.app.screen.width / 2, this.app.screen.height / 2);
|
||||
s1.width = maxSize * Math.sqrt(2);
|
||||
s1.height = s1.width;
|
||||
s2.width = maxSize * 0.8;
|
||||
s2.height = s2.width;
|
||||
s3.width = maxSize * 0.5;
|
||||
s3.height = s3.width;
|
||||
s4.width = maxSize * 0.25;
|
||||
s4.height = s4.width;
|
||||
|
||||
this.curContainer.time += delta * this.flowSpeed;
|
||||
|
||||
s1.rotation += (delta / 1000) * this.flowSpeed;
|
||||
s2.rotation -= (delta / 500) * this.flowSpeed;
|
||||
s3.rotation += (delta / 1000) * this.flowSpeed;
|
||||
s4.rotation -= (delta / 750) * this.flowSpeed;
|
||||
|
||||
s3.x =
|
||||
this.app.screen.width / 2 +
|
||||
(this.app.screen.width / 4) *
|
||||
Math.cos((this.curContainer.time / 1000) * 0.75);
|
||||
s3.y =
|
||||
this.app.screen.height / 2 +
|
||||
(this.app.screen.width / 4) *
|
||||
Math.cos((this.curContainer.time / 1000) * 0.75);
|
||||
|
||||
s4.x =
|
||||
this.app.screen.width / 2 +
|
||||
(this.app.screen.width / 4) * 0.1 +
|
||||
Math.cos(this.curContainer.time * 0.006 * 0.75);
|
||||
s4.y =
|
||||
this.app.screen.height / 2 +
|
||||
(this.app.screen.width / 4) * 0.1 +
|
||||
Math.cos(this.curContainer.time * 0.006 * 0.75);
|
||||
|
||||
if (
|
||||
this.curContainer.alpha >= 1 &&
|
||||
this.lastContainer.size === 0 &&
|
||||
this.staticMode
|
||||
) {
|
||||
this.app.ticker.stop();
|
||||
}
|
||||
}
|
||||
};
|
||||
constructor(protected override canvas: HTMLCanvasElement) {
|
||||
super(canvas);
|
||||
this.app = new Application({
|
||||
view: canvas,
|
||||
resizeTo: this.canvas,
|
||||
powerPreference: "low-power",
|
||||
backgroundAlpha: 1,
|
||||
});
|
||||
this.rebuildFilters();
|
||||
this.app.ticker.maxFPS = 30;
|
||||
this.app.ticker.add(this.onTick);
|
||||
this.app.ticker.start();
|
||||
}
|
||||
|
||||
protected override onResize(width: number, height: number): void {
|
||||
super.onResize(width, height);
|
||||
this.app.resize();
|
||||
this.rebuildFilters();
|
||||
}
|
||||
|
||||
override setRenderScale(scale: number): void {
|
||||
super.setRenderScale(scale);
|
||||
this.rebuildFilters();
|
||||
}
|
||||
private rebuildFilters() {
|
||||
const minBorder = Math.min(this.canvas.width, this.canvas.height);
|
||||
const maxBorder = Math.max(this.canvas.width, this.canvas.height);
|
||||
const c0 = new ColorMatrixFilter();
|
||||
c0.saturate(1.2, false);
|
||||
const c1 = new ColorMatrixFilter();
|
||||
c1.brightness(0.6, false);
|
||||
const c2 = new ColorMatrixFilter();
|
||||
c2.contrast(0.3, true);
|
||||
for (const filter of this.app.stage.filters ?? []) {
|
||||
filter.destroy();
|
||||
}
|
||||
this.app.stage.filters = [];
|
||||
this.app.stage.filters.push(new BlurFilter(5, 1));
|
||||
this.app.stage.filters.push(new BlurFilter(10, 1));
|
||||
this.app.stage.filters.push(new BlurFilter(20, 2));
|
||||
this.app.stage.filters.push(new BlurFilter(40, 2));
|
||||
this.app.stage.filters.push(new BlurFilter(80, 2));
|
||||
if (minBorder > 768) this.app.stage.filters.push(new BlurFilter(160, 4));
|
||||
if (minBorder > 768 * 2)
|
||||
this.app.stage.filters.push(new BlurFilter(320, 4));
|
||||
|
||||
this.app.stage.filters.push(c0, c1, c2);
|
||||
this.app.stage.filters.push(new BlurFilter(5, 1));
|
||||
if (Math.random() > 0.5) {
|
||||
this.app.stage.filters.push(
|
||||
new BulgePinchFilter({
|
||||
radius: (maxBorder + minBorder) / 2,
|
||||
strength: 1,
|
||||
center: [0.25, 1],
|
||||
}),
|
||||
);
|
||||
this.app.stage.filters.push(
|
||||
new BulgePinchFilter({
|
||||
radius: (maxBorder + minBorder) / 2,
|
||||
strength: 1,
|
||||
center: [0.75, 0],
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
this.app.stage.filters.push(
|
||||
new BulgePinchFilter({
|
||||
radius: (maxBorder + minBorder) / 2,
|
||||
strength: 1,
|
||||
center: [0.75, 1],
|
||||
}),
|
||||
);
|
||||
this.app.stage.filters.push(
|
||||
new BulgePinchFilter({
|
||||
radius: (maxBorder + minBorder) / 2,
|
||||
strength: 1,
|
||||
center: [0.25, 0],
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
override setStaticMode(enable = false): void {
|
||||
this.staticMode = enable;
|
||||
this.app.ticker.start();
|
||||
}
|
||||
|
||||
override setFPS(fps: number): void {
|
||||
this.app.ticker.maxFPS = fps;
|
||||
}
|
||||
|
||||
override pause(): void {
|
||||
this.app.ticker.stop();
|
||||
this.app.render();
|
||||
}
|
||||
|
||||
override resume(): void {
|
||||
this.app.ticker.start();
|
||||
}
|
||||
|
||||
override setLowFreqVolume(_volume: number): void {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
override setHasLyric(_hasLyric: boolean): void {
|
||||
// NOOP
|
||||
}
|
||||
|
||||
override async setAlbum(
|
||||
albumSource?: string | HTMLImageElement | HTMLVideoElement,
|
||||
isVideo?: boolean,
|
||||
): Promise<void> {
|
||||
if (
|
||||
!albumSource ||
|
||||
(typeof albumSource === "string" && albumSource.trim().length === 0)
|
||||
)
|
||||
return;
|
||||
let res: HTMLImageElement | HTMLVideoElement | null = null;
|
||||
let remainRetryTimes = 5;
|
||||
let tex: Texture | null = null;
|
||||
while (!tex?.baseTexture?.resource?.valid && remainRetryTimes > 0) {
|
||||
try {
|
||||
if (typeof albumSource === "string") {
|
||||
res = await loadResourceFromUrl(albumSource, isVideo);
|
||||
} else {
|
||||
res = await loadResourceFromElement(albumSource);
|
||||
}
|
||||
tex = Texture.from(res, {
|
||||
resourceOptions: {
|
||||
autoLoad: false,
|
||||
},
|
||||
});
|
||||
await tex.baseTexture.resource.load();
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
`failed on loading album image, retrying (${remainRetryTimes})`,
|
||||
albumSource,
|
||||
error,
|
||||
);
|
||||
tex = null;
|
||||
remainRetryTimes--;
|
||||
}
|
||||
}
|
||||
if (!tex) return;
|
||||
const container = new TimedContainer();
|
||||
const s1 = new Sprite(tex);
|
||||
const s2 = new Sprite(tex);
|
||||
const s3 = new Sprite(tex);
|
||||
const s4 = new Sprite(tex);
|
||||
s1.anchor.set(0.5, 0.5);
|
||||
s2.anchor.set(0.5, 0.5);
|
||||
s3.anchor.set(0.5, 0.5);
|
||||
s4.anchor.set(0.5, 0.5);
|
||||
s1.rotation = Math.random() * Math.PI * 2;
|
||||
s2.rotation = Math.random() * Math.PI * 2;
|
||||
s3.rotation = Math.random() * Math.PI * 2;
|
||||
s4.rotation = Math.random() * Math.PI * 2;
|
||||
container.addChild(s1, s2, s3, s4);
|
||||
if (this.curContainer) this.lastContainer.add(this.curContainer);
|
||||
this.curContainer = container;
|
||||
this.app.stage.addChild(container);
|
||||
this.curContainer.alpha = 0;
|
||||
this.app.ticker.start();
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
this.app.ticker.remove(this.onTick);
|
||||
this.app.destroy(true);
|
||||
}
|
||||
|
||||
override getElement(): HTMLElement {
|
||||
return this.canvas;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D src;
|
||||
in vec2 f_v_coord;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
vec2 coord = f_v_coord;
|
||||
fragColor = texture(src, coord);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
in vec2 v_coord;
|
||||
out vec2 f_v_coord;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(v_coord, 0.0f, 1.0f);
|
||||
f_v_coord = (vec2(v_coord.x, v_coord.y) + vec2(1.0f, 1.0f)) / 2.0f;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D src;
|
||||
uniform float lerp;
|
||||
uniform float scale;
|
||||
in vec2 f_v_coord;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
vec2 tex_coord = f_v_coord;
|
||||
if(scale < 1.0f) {
|
||||
tex_coord /= scale;
|
||||
}
|
||||
vec4 srcColor = texture(src, tex_coord);
|
||||
fragColor = mix(vec4(srcColor.xyz, 0.0f), srcColor, lerp);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D src;
|
||||
in vec2 f_v_coord;
|
||||
out vec4 fragColor;
|
||||
|
||||
/* Gradient noise from Jorge Jimenez's presentation: */
|
||||
/* http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare */
|
||||
float gradientNoise(in vec2 uv) {
|
||||
return fract(52.9829189f * fract(dot(uv, vec2(0.06711056f, 0.00583715f))));
|
||||
}
|
||||
|
||||
void main() {
|
||||
vec2 tex_coord = f_v_coord;
|
||||
float dither = (1.0f / 255.0f) * gradientNoise(gl_FragCoord.xy) - (0.5f / 255.0f);
|
||||
vec4 color = texture(src, tex_coord);
|
||||
color += dither;
|
||||
fragColor = color;
|
||||
}
|
||||
42
amll-local/packages/core/src/bg-render/shaders/taa.frag.glsl
Normal file
42
amll-local/packages/core/src/bg-render/shaders/taa.frag.glsl
Normal file
@@ -0,0 +1,42 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
uniform sampler2D src;
|
||||
uniform sampler2D historyFrame0;
|
||||
uniform vec2 texSize;
|
||||
in vec2 f_v_coord;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = texture(src, f_v_coord);
|
||||
return;
|
||||
// get the neighborhood min / max from this frame's render
|
||||
vec3 center = texture(src, f_v_coord).rgb;
|
||||
vec3 minColor = center;
|
||||
vec3 maxColor = center;
|
||||
for (int iy = -1; iy <= 1; ++iy)
|
||||
{
|
||||
for (int ix = -1; ix <= 1; ++ix)
|
||||
{
|
||||
if (ix == 0 && iy == 0)
|
||||
continue;
|
||||
|
||||
|
||||
vec2 offsetUV = ((f_v_coord * texSize + vec2(ix, iy))) / texSize;
|
||||
vec3 color = texture(src, offsetUV).rgb;
|
||||
minColor = min(minColor, color);
|
||||
maxColor = max(maxColor, color);
|
||||
}
|
||||
}
|
||||
|
||||
// get last frame's pixel and clamp it to the neighborhood of this frame
|
||||
vec3 old = texture(historyFrame0, f_v_coord).rgb;
|
||||
old = max(minColor, old);
|
||||
old = min(maxColor, old);
|
||||
|
||||
// interpolate from the clamped old color to the new color.
|
||||
// Reject all history when the mouse moves.
|
||||
float lerpAmount = 0.1f;
|
||||
vec3 pixelColor = mix(old, center, lerpAmount);
|
||||
fragColor = vec4(pixelColor, 1.0f);
|
||||
}
|
||||
Reference in New Issue
Block a user