mirror of
https://github.com/unanmed/HumanBreak.git
synced 2025-04-30 03:03:24 +08:00
feat: 一些着色器
This commit is contained in:
parent
b3727080b2
commit
f01d185db3
@ -318,6 +318,7 @@ events.prototype.restart = function () {
|
|||||||
core.hideStatusBar();
|
core.hideStatusBar();
|
||||||
core.showStartAnimate();
|
core.showStartAnimate();
|
||||||
core.playBgm(main.startBgm);
|
core.playBgm(main.startBgm);
|
||||||
|
Mota.require('var', 'hook').emit('restart');
|
||||||
};
|
};
|
||||||
|
|
||||||
////// 询问是否需要重新开始 //////
|
////// 询问是否需要重新开始 //////
|
||||||
|
@ -130,6 +130,7 @@ main.floors.MT16=
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"追逐战后录像会进行自动修复,不用担心录像问题",
|
"追逐战后录像会进行自动修复,不用担心录像问题",
|
||||||
|
"如果逃脱失败,或者想重新开始追逐战,直接读取自动存档即可,会跳过前奏",
|
||||||
{
|
{
|
||||||
"type": "hideStatusBar",
|
"type": "hideStatusBar",
|
||||||
"toolbox": true
|
"toolbox": true
|
||||||
@ -400,6 +401,9 @@ main.floors.MT16=
|
|||||||
{
|
{
|
||||||
"type": "function",
|
"type": "function",
|
||||||
"function": "function(){\nMota.Plugin.require('chase_r').start(false);\n}"
|
"function": "function(){\nMota.Plugin.require('chase_r').start(false);\n}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "autoSave"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"2,23": [
|
"2,23": [
|
||||||
|
@ -1,545 +0,0 @@
|
|||||||
import { Animation, Ticker, hyper } from 'mutate-animate';
|
|
||||||
import { EventEmitter } from '../common/eventEmitter';
|
|
||||||
import { ensureArray } from '@/plugin/utils';
|
|
||||||
|
|
||||||
interface ShaderEvent {}
|
|
||||||
|
|
||||||
const isWebGLSupported = (() => {
|
|
||||||
return !!document.createElement('canvas').getContext('webgl');
|
|
||||||
})();
|
|
||||||
|
|
||||||
type ShaderColorArray = [number, number, number, number];
|
|
||||||
type ShaderEffectImage = Exclude<TexImageSource, VideoFrame | ImageData>;
|
|
||||||
|
|
||||||
interface ProgramInfo {
|
|
||||||
program: WebGLProgram;
|
|
||||||
attrib: Record<string, number>;
|
|
||||||
uniform: Record<string, WebGLUniformLocation>;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ShaderEffectBuffer {
|
|
||||||
position: WebGLBuffer;
|
|
||||||
texture: WebGLBuffer;
|
|
||||||
indices: WebGLBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ShaderEffectShader {
|
|
||||||
vertex: WebGLShader;
|
|
||||||
fragment: WebGLShader;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MixedImage {
|
|
||||||
canvas: HTMLCanvasElement;
|
|
||||||
update(): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
type UniformBinderNum = 1 | 2 | 3 | 4;
|
|
||||||
type UniformBinderType = 'f' | 'i';
|
|
||||||
type UniformFunc<
|
|
||||||
N extends UniformBinderNum,
|
|
||||||
T extends UniformBinderType,
|
|
||||||
V extends 'v' | ''
|
|
||||||
> = `uniform${N}${T}${V}`;
|
|
||||||
|
|
||||||
type UniformBinderValue<N extends UniformBinderNum> = N extends 1
|
|
||||||
? number
|
|
||||||
: N extends 2
|
|
||||||
? [number, number]
|
|
||||||
: N extends 3
|
|
||||||
? [number, number, number]
|
|
||||||
: [number, number, number, number];
|
|
||||||
|
|
||||||
interface UniformBinder<
|
|
||||||
N extends UniformBinderNum,
|
|
||||||
T extends UniformBinderType,
|
|
||||||
V extends 'v' | ''
|
|
||||||
> {
|
|
||||||
value: UniformBinderValue<N>;
|
|
||||||
set(value: UniformBinderValue<N>): void;
|
|
||||||
get(): UniformBinderValue<N>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const builtinVs = `
|
|
||||||
#ifdef GL_ES
|
|
||||||
precision highp float;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
attribute vec4 aVertexPosition;
|
|
||||||
attribute vec2 aTextureCoord;
|
|
||||||
|
|
||||||
varying highp vec2 vTextureCoord;
|
|
||||||
`;
|
|
||||||
const builtinFs = `
|
|
||||||
#ifdef GL_ES
|
|
||||||
precision highp float;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
varying highp vec2 vTextureCoord;
|
|
||||||
|
|
||||||
uniform sampler2D uSampler;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export class ShaderEffect extends EventEmitter<ShaderEvent> {
|
|
||||||
canvas: HTMLCanvasElement;
|
|
||||||
gl: WebGLRenderingContext;
|
|
||||||
program: WebGLProgram | null = null;
|
|
||||||
texture: WebGLTexture | null = null;
|
|
||||||
programInfo: ProgramInfo | null = null;
|
|
||||||
buffer: ShaderEffectBuffer | null = null;
|
|
||||||
shader: ShaderEffectShader | null = null;
|
|
||||||
textureCanvas: MixedImage | null = null;
|
|
||||||
|
|
||||||
private baseImages: ShaderEffectImage[] = [];
|
|
||||||
private background: ShaderColorArray = [0, 0, 0, 0];
|
|
||||||
|
|
||||||
private _vsSource: string = '';
|
|
||||||
private _fsSource: string = '';
|
|
||||||
|
|
||||||
get vsSource() {
|
|
||||||
return builtinVs + this._vsSource;
|
|
||||||
}
|
|
||||||
get fsSource() {
|
|
||||||
return builtinFs + this._fsSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(background: ShaderColorArray) {
|
|
||||||
super();
|
|
||||||
this.canvas = document.createElement('canvas');
|
|
||||||
if (!isWebGLSupported) {
|
|
||||||
throw new Error(
|
|
||||||
`Cannot initialize ShaderEffect, since your device does not support webgl.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
this.gl = this.canvas.getContext('webgl')!;
|
|
||||||
this.gl.clearColor(...background);
|
|
||||||
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
|
|
||||||
this.background = background;
|
|
||||||
const s = core.domStyle.scale * devicePixelRatio;
|
|
||||||
this.canvas.width = s * core._PX_;
|
|
||||||
this.canvas.height = s * core._PY_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置特效作用于的图片,不会修改原图片,而是会在 ShaderEffect.canvas 画布元素中展现
|
|
||||||
* @param img 特效作用于的图片
|
|
||||||
*/
|
|
||||||
baseImage(...img: ShaderEffectImage[]) {
|
|
||||||
this.baseImages = img;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 强制重新渲染特效
|
|
||||||
* @param compile 是否重新编译着色器脚本,并重新创建纹理
|
|
||||||
*/
|
|
||||||
update(compile: boolean = false) {
|
|
||||||
if (compile) this.compile();
|
|
||||||
this.textureCanvas?.update();
|
|
||||||
this.drawScene();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 仅重新编译着色器,不进行纹理创建和特效渲染
|
|
||||||
*/
|
|
||||||
compile() {
|
|
||||||
const gl = this.gl;
|
|
||||||
gl.deleteProgram(this.program);
|
|
||||||
gl.deleteTexture(this.texture);
|
|
||||||
gl.deleteBuffer(this.buffer?.position ?? null);
|
|
||||||
gl.deleteBuffer(this.buffer?.texture ?? null);
|
|
||||||
gl.deleteShader(this.shader?.vertex ?? null);
|
|
||||||
gl.deleteShader(this.shader?.fragment ?? null);
|
|
||||||
|
|
||||||
this.program = this.createProgram();
|
|
||||||
this.programInfo = this.getProgramInfo();
|
|
||||||
this.buffer = this.initBuffers();
|
|
||||||
this.texture = this.createTexture();
|
|
||||||
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
|
|
||||||
gl.useProgram(this.program);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置顶点着色器,使用 glsl 编写,插件提供了一些新的 api
|
|
||||||
* 着色器中必须包含 main 函数,同时为 gl_Position 赋值
|
|
||||||
* @param shader 顶点着色器代码
|
|
||||||
*/
|
|
||||||
vs(shader: string) {
|
|
||||||
this._vsSource = shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置片元着色器,使用 glsl 编写,插件提供了一些新的 api
|
|
||||||
* 着色器中必须包含 main 函数,同时为 gl_FragColor 赋值
|
|
||||||
* @param shader 片元着色器代码
|
|
||||||
*/
|
|
||||||
fs(shader: string) {
|
|
||||||
this._fsSource = shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绘制特效
|
|
||||||
*/
|
|
||||||
drawScene() {
|
|
||||||
// 清空画布
|
|
||||||
const gl = this.gl;
|
|
||||||
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
||||||
|
|
||||||
gl.clearColor(...this.background);
|
|
||||||
gl.clearDepth(1);
|
|
||||||
gl.enable(gl.DEPTH_TEST);
|
|
||||||
gl.depthFunc(gl.LEQUAL);
|
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
|
||||||
|
|
||||||
// 设置顶点信息
|
|
||||||
this.setPositionAttrib();
|
|
||||||
this.setTextureAttrib();
|
|
||||||
|
|
||||||
// 准备绘制
|
|
||||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffer!.indices);
|
|
||||||
gl.useProgram(this.program);
|
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.texture);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
||||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
||||||
gl.texImage2D(
|
|
||||||
gl.TEXTURE_2D,
|
|
||||||
0,
|
|
||||||
gl.RGBA,
|
|
||||||
gl.RGBA,
|
|
||||||
gl.UNSIGNED_BYTE,
|
|
||||||
this.textureCanvas!.canvas
|
|
||||||
);
|
|
||||||
gl.uniform1i(this.programInfo!.uniform.uSampler, 0);
|
|
||||||
|
|
||||||
// 绘制
|
|
||||||
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建一个全局变量绑定器,用于操作全局变量
|
|
||||||
* @param uniform 全局变量的变量名
|
|
||||||
* @param num 变量的元素数量,float和int视为1,vec2 vec3 vec4分别视为 2 3 4
|
|
||||||
* @param type 数据类型,可以填'f',表示浮点型,或者填'i',表示整型
|
|
||||||
* @param vector 是否为向量,可以填'v',表示是向量,或者填'',表示不是向量
|
|
||||||
* @returns 一个uniform绑定器,用于操作全局变量uniform
|
|
||||||
*/
|
|
||||||
createUniformBinder<
|
|
||||||
N extends UniformBinderNum,
|
|
||||||
T extends UniformBinderType,
|
|
||||||
V extends 'v' | ''
|
|
||||||
>(uniform: string, num: N, type: T, vector: V): UniformBinder<N, T, V> {
|
|
||||||
if (!this.program) {
|
|
||||||
throw new Error(
|
|
||||||
`Uniform binder should be use when the program initialized.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const suffix = `${num}${type}${vector ? 'v' : ''}`;
|
|
||||||
const func = `uniform${suffix}` as UniformFunc<N, T, V>;
|
|
||||||
const value = (
|
|
||||||
num === 1 ? 0 : Array(num).fill(0)
|
|
||||||
) as UniformBinderValue<N>;
|
|
||||||
|
|
||||||
const loc = this.gl.getUniformLocation(this.program, uniform);
|
|
||||||
const gl = this.gl;
|
|
||||||
|
|
||||||
return {
|
|
||||||
value,
|
|
||||||
set(value) {
|
|
||||||
this.value = value;
|
|
||||||
let v;
|
|
||||||
if (vector === 'v') {
|
|
||||||
let _v = ensureArray(value);
|
|
||||||
if (type === 'f') {
|
|
||||||
v = new Float32Array(_v);
|
|
||||||
} else {
|
|
||||||
v = new Int32Array(_v);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
v = ensureArray(value);
|
|
||||||
}
|
|
||||||
// 对uniform赋值
|
|
||||||
if (vector === 'v') {
|
|
||||||
// @ts-ignore
|
|
||||||
gl[func](loc, v);
|
|
||||||
} else {
|
|
||||||
// @ts-ignore
|
|
||||||
gl[func](loc, ...v);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
get() {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private createProgram() {
|
|
||||||
const gl = this.gl;
|
|
||||||
const vs = this.loadShader(gl.VERTEX_SHADER, this.vsSource);
|
|
||||||
const fs = this.loadShader(gl.FRAGMENT_SHADER, this.fsSource);
|
|
||||||
|
|
||||||
this.shader = {
|
|
||||||
vertex: vs,
|
|
||||||
fragment: fs
|
|
||||||
};
|
|
||||||
|
|
||||||
const program = gl.createProgram()!;
|
|
||||||
gl.attachShader(program, vs);
|
|
||||||
gl.attachShader(program, fs);
|
|
||||||
gl.linkProgram(program);
|
|
||||||
|
|
||||||
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
||||||
throw new Error(
|
|
||||||
`Cannot initialize shader program. Error info: ${gl.getProgramInfoLog(
|
|
||||||
program
|
|
||||||
)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return program;
|
|
||||||
}
|
|
||||||
|
|
||||||
private loadShader(type: number, source: string) {
|
|
||||||
const gl = this.gl;
|
|
||||||
const shader = gl.createShader(type)!;
|
|
||||||
gl.shaderSource(shader, source);
|
|
||||||
gl.compileShader(shader);
|
|
||||||
|
|
||||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
||||||
throw new Error(
|
|
||||||
`Cannot compile ${
|
|
||||||
type === gl.VERTEX_SHADER ? 'vertex' : 'fragment'
|
|
||||||
} shader. Error info: ${gl.getShaderInfoLog(shader)}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
private createTexture() {
|
|
||||||
const gl = this.gl;
|
|
||||||
|
|
||||||
const img = mixImage(this.baseImages);
|
|
||||||
this.textureCanvas = img;
|
|
||||||
const texture = gl.createTexture();
|
|
||||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
||||||
gl.texImage2D(
|
|
||||||
gl.TEXTURE_2D,
|
|
||||||
0,
|
|
||||||
gl.RGBA,
|
|
||||||
gl.RGBA,
|
|
||||||
gl.UNSIGNED_BYTE,
|
|
||||||
img.canvas
|
|
||||||
);
|
|
||||||
gl.generateMipmap(gl.TEXTURE_2D);
|
|
||||||
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
private initBuffers(): ShaderEffectBuffer {
|
|
||||||
const positions = new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]);
|
|
||||||
const posBuffer = this.initBuffer(positions);
|
|
||||||
const textureCoord = new Float32Array([1, 1, 0, 1, 1, 0, 0, 0]);
|
|
||||||
const textureBuffer = this.initBuffer(textureCoord);
|
|
||||||
|
|
||||||
return (this.buffer = {
|
|
||||||
position: posBuffer,
|
|
||||||
texture: textureBuffer,
|
|
||||||
indices: this.initIndexBuffer()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private initBuffer(pos: Float32Array) {
|
|
||||||
const gl = this.gl;
|
|
||||||
const posBuffer = gl.createBuffer()!;
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
|
|
||||||
gl.bufferData(gl.ARRAY_BUFFER, pos, gl.STATIC_DRAW);
|
|
||||||
|
|
||||||
return posBuffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private initIndexBuffer() {
|
|
||||||
const gl = this.gl;
|
|
||||||
const buffer = gl.createBuffer()!;
|
|
||||||
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
|
|
||||||
const indices = new Uint16Array([0, 1, 2, 2, 3, 1]);
|
|
||||||
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getProgramInfo(): ProgramInfo | null {
|
|
||||||
if (!this.program) return null;
|
|
||||||
const gl = this.gl;
|
|
||||||
const pro = this.program;
|
|
||||||
return (this.programInfo = {
|
|
||||||
program: pro,
|
|
||||||
attrib: {
|
|
||||||
vertexPosition: gl.getAttribLocation(pro, 'aVertexPosition'),
|
|
||||||
textureCoord: gl.getAttribLocation(pro, 'aTextureCoord')
|
|
||||||
},
|
|
||||||
uniform: {
|
|
||||||
uSampler: gl.getUniformLocation(pro, 'uSampler')!
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private setTextureAttrib() {
|
|
||||||
const gl = this.gl;
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer!.texture);
|
|
||||||
gl.vertexAttribPointer(
|
|
||||||
this.programInfo!.attrib.textureCoord,
|
|
||||||
2,
|
|
||||||
gl.FLOAT,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
gl.enableVertexAttribArray(this.programInfo!.attrib.textureCoord);
|
|
||||||
}
|
|
||||||
|
|
||||||
private setPositionAttrib() {
|
|
||||||
const gl = this.gl;
|
|
||||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffer!.position);
|
|
||||||
gl.vertexAttribPointer(
|
|
||||||
this.programInfo!.attrib.vertexPosition,
|
|
||||||
2,
|
|
||||||
gl.FLOAT,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
);
|
|
||||||
gl.enableVertexAttribArray(this.programInfo!.attrib.vertexPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultVs: string = `
|
|
||||||
void main() {
|
|
||||||
vTextureCoord = aTextureCoord;
|
|
||||||
gl_Position = aVertexPosition;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
static defaultFs: string = `
|
|
||||||
void main() {
|
|
||||||
gl_FragColor = texture2D(uSampler, vTextureCoord);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GameCanvasReplacer {
|
|
||||||
recover(): void;
|
|
||||||
append(): void;
|
|
||||||
remove(): void;
|
|
||||||
update(compile?: boolean): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function floorPower2(value: number) {
|
|
||||||
return Math.pow(2, Math.ceil(Math.log(value) / Math.LN2));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 规范化 webgl 纹理图片,规范成2的幂的形式
|
|
||||||
* @param img 要被规范化的图片
|
|
||||||
*/
|
|
||||||
function normalizeTexture(img: ShaderEffectImage) {
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = floorPower2(img.width);
|
|
||||||
canvas.height = floorPower2(img.height);
|
|
||||||
const ctx = canvas.getContext('2d')!;
|
|
||||||
ctx.imageSmoothingEnabled = false;
|
|
||||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
||||||
return canvas;
|
|
||||||
}
|
|
||||||
|
|
||||||
function mixImage(imgs: ShaderEffectImage[]): MixedImage {
|
|
||||||
// todo: 直接使用webgl纹理进行图片混合
|
|
||||||
if (imgs.length === 0) {
|
|
||||||
throw new Error(`Cannot mix images whose count is 0.`);
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
imgs.some(v => v.width !== imgs[0].width || v.height !== imgs[0].height)
|
|
||||||
) {
|
|
||||||
throw new Error(`Cannot mix images with different size.`);
|
|
||||||
}
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
canvas.width = floorPower2(imgs[0].width);
|
|
||||||
canvas.height = floorPower2(imgs[0].height);
|
|
||||||
const ctx = canvas.getContext('2d')!;
|
|
||||||
imgs.forEach(v => {
|
|
||||||
const img = normalizeTexture(v);
|
|
||||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
canvas,
|
|
||||||
update() {
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
imgs.forEach(v => {
|
|
||||||
const img = normalizeTexture(v);
|
|
||||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 为一个着色器特效创建每帧更新的 ticker,部分条件下性能表现可能较差
|
|
||||||
* @param effect 要每帧更新的着色器特效
|
|
||||||
*/
|
|
||||||
export function setTickerFor(effect: ShaderEffect) {
|
|
||||||
const ticker = new Ticker();
|
|
||||||
ticker.add(() => {
|
|
||||||
effect.update();
|
|
||||||
});
|
|
||||||
|
|
||||||
return ticker;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 用着色器特效画布替换样板画布
|
|
||||||
* @param effect 着色器特效实例
|
|
||||||
* @param canvas 要替换的画布列表
|
|
||||||
* @returns 特效控制器,用于控制特效的显示
|
|
||||||
*/
|
|
||||||
export function replaceGameCanvas(
|
|
||||||
effect: ShaderEffect,
|
|
||||||
canvas: string[]
|
|
||||||
): GameCanvasReplacer {
|
|
||||||
let zIndex = 0;
|
|
||||||
canvas.forEach(v => {
|
|
||||||
const canvas = core.canvas[v].canvas;
|
|
||||||
const style = getComputedStyle(canvas);
|
|
||||||
const z = parseInt(style.zIndex);
|
|
||||||
if (z > zIndex) zIndex = z;
|
|
||||||
canvas.style.display = 'none';
|
|
||||||
});
|
|
||||||
const gl = effect.canvas;
|
|
||||||
gl.style.left = '0';
|
|
||||||
gl.style.top = '0';
|
|
||||||
gl.style.position = 'absolute';
|
|
||||||
gl.style.zIndex = zIndex.toString();
|
|
||||||
gl.style.display = 'block';
|
|
||||||
gl.style.width = `${core._PX_ * core.domStyle.scale}px`;
|
|
||||||
gl.style.height = `${core._PY_ * core.domStyle.scale}px`;
|
|
||||||
core.dom.gameDraw.appendChild(gl);
|
|
||||||
|
|
||||||
return {
|
|
||||||
recover() {
|
|
||||||
canvas.forEach(v => {
|
|
||||||
const canvas = core.canvas[v].canvas;
|
|
||||||
canvas.style.display = 'block';
|
|
||||||
});
|
|
||||||
gl.style.display = 'none';
|
|
||||||
},
|
|
||||||
append() {
|
|
||||||
canvas.forEach(v => {
|
|
||||||
const canvas = core.canvas[v].canvas;
|
|
||||||
canvas.style.display = 'none';
|
|
||||||
});
|
|
||||||
gl.style.display = 'block';
|
|
||||||
},
|
|
||||||
remove() {
|
|
||||||
this.recover();
|
|
||||||
gl.remove();
|
|
||||||
},
|
|
||||||
update(compile?: boolean) {
|
|
||||||
effect.update(compile);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -600,13 +600,14 @@ export class CameraAnimation extends EventEmitter<CameraAnimationEvent> {
|
|||||||
if (!end) return;
|
if (!end) return;
|
||||||
const t = end.start + end.time;
|
const t = end.start + end.time;
|
||||||
if (t > endTime) endTime = t;
|
if (t > endTime) endTime = t;
|
||||||
|
const cam = this.camera;
|
||||||
|
|
||||||
if (ope.type === 'translate') {
|
if (ope.type === 'translate') {
|
||||||
this.camera.applyTranslateAnimation(ope, exe.animation, t + 50);
|
cam.applyTranslateAnimation(ope, exe.animation, t + 100);
|
||||||
} else if (ope.type === 'rotate') {
|
} else if (ope.type === 'rotate') {
|
||||||
this.camera.applyRotateAnimation(ope, exe.animation, t + 50);
|
cam.applyRotateAnimation(ope, exe.animation, t + 100);
|
||||||
} else {
|
} else {
|
||||||
this.camera.applyScaleAnimation(ope, exe.animation, t + 50);
|
cam.applyScaleAnimation(ope, exe.animation, t + 100);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.endTime = endTime + this.startTime;
|
this.endTime = endTime + this.startTime;
|
||||||
|
@ -14,7 +14,7 @@ import { Container } from './container';
|
|||||||
|
|
||||||
let main: MotaRenderer;
|
let main: MotaRenderer;
|
||||||
|
|
||||||
Mota.require('var', 'loading').once('loaded', () => {
|
Mota.require('var', 'loading').once('coreInit', () => {
|
||||||
const render = new MotaRenderer();
|
const render = new MotaRenderer();
|
||||||
main = render;
|
main = render;
|
||||||
render.mount();
|
render.mount();
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { isNil } from 'lodash-es';
|
|
||||||
import { logger } from '../common/logger';
|
import { logger } from '../common/logger';
|
||||||
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
import { MotaOffscreenCanvas2D } from '../fx/canvas2d';
|
||||||
import { isWebGL2Supported } from '../fx/webgl';
|
import { isWebGL2Supported } from '../fx/webgl';
|
||||||
@ -7,9 +6,7 @@ import { ERenderItemEvent, RenderItem, RenderItemPosition } from './item';
|
|||||||
import { Transform } from './transform';
|
import { Transform } from './transform';
|
||||||
|
|
||||||
const SHADER_VERTEX_PREFIX_300 = /* glsl */ `#version 300 es
|
const SHADER_VERTEX_PREFIX_300 = /* glsl */ `#version 300 es
|
||||||
#ifdef GL_ES
|
precision highp float;
|
||||||
precision highp float;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
in vec4 a_position;
|
in vec4 a_position;
|
||||||
in vec2 a_texCoord;
|
in vec2 a_texCoord;
|
||||||
@ -17,9 +14,7 @@ in vec2 a_texCoord;
|
|||||||
out highp vec2 v_texCoord;
|
out highp vec2 v_texCoord;
|
||||||
`;
|
`;
|
||||||
const SHADER_VERTEX_PREFIX_100 = /* glsl */ `
|
const SHADER_VERTEX_PREFIX_100 = /* glsl */ `
|
||||||
#ifdef GL_ES
|
precision highp float;
|
||||||
precision highp float;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
attribute vec4 a_position;
|
attribute vec4 a_position;
|
||||||
attribute vec2 a_texCoord;
|
attribute vec2 a_texCoord;
|
||||||
@ -28,18 +23,14 @@ varying highp vec2 v_texCoord;
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const SHADER_FRAGMENT_PREFIX_300 = /* glsl */ `#version 300 es
|
const SHADER_FRAGMENT_PREFIX_300 = /* glsl */ `#version 300 es
|
||||||
#ifdef GL_ES
|
precision highp float;
|
||||||
precision highp float;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
in highp vec2 v_texCoord;
|
in highp vec2 v_texCoord;
|
||||||
|
|
||||||
uniform sampler2D u_sampler;
|
uniform sampler2D u_sampler;
|
||||||
`;
|
`;
|
||||||
const SHADER_FRAGMENT_PREFIX_100 = /* glsl */ `
|
const SHADER_FRAGMENT_PREFIX_100 = /* glsl */ `
|
||||||
#ifdef GL_ES
|
precision highp float;
|
||||||
precision highp float;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
varying highp vec2 v_texCoord;
|
varying highp vec2 v_texCoord;
|
||||||
|
|
||||||
@ -288,6 +279,12 @@ export class Shader extends Container<EShaderEvent> {
|
|||||||
gl.depthFunc(gl.LEQUAL);
|
gl.depthFunc(gl.LEQUAL);
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
const pre = this.preDraw();
|
||||||
|
if (!pre) {
|
||||||
|
this.postDraw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 设置顶点信息
|
// 设置顶点信息
|
||||||
this.setPositionAttrib();
|
this.setPositionAttrib();
|
||||||
this.setTextureAttrib();
|
this.setTextureAttrib();
|
||||||
@ -310,8 +307,24 @@ export class Shader extends Container<EShaderEvent> {
|
|||||||
|
|
||||||
// 绘制
|
// 绘制
|
||||||
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
|
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
|
||||||
|
|
||||||
|
this.postDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在本着色器内部渲染之前执行的渲染,如果返回false,则表示不进行内部渲染,但依然会执行 {@link postDraw}
|
||||||
|
* 继承本类,并复写此方法即可实现前置渲染功能
|
||||||
|
*/
|
||||||
|
protected preDraw(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在本着色器内部渲染之后执行的渲染,即使preDraw返回false,本函数也会执行
|
||||||
|
* 继承本类,并复写此方法即可实现后置渲染功能
|
||||||
|
*/
|
||||||
|
protected postDraw() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 切换着色器程序
|
* 切换着色器程序
|
||||||
* @param program 着色器程序
|
* @param program 着色器程序
|
||||||
@ -542,13 +555,9 @@ interface ShaderUniformMatrix {
|
|||||||
interface ShaderUniformBlock {
|
interface ShaderUniformBlock {
|
||||||
location: GLuint;
|
location: GLuint;
|
||||||
buffer: WebGLBuffer;
|
buffer: WebGLBuffer;
|
||||||
set(srcData: AllowSharedBufferSource | null, usage: GLenum): void;
|
size: number;
|
||||||
set(
|
set(srcData: AllowSharedBufferSource | null): void;
|
||||||
srcData: ArrayBufferView,
|
set(srcData: ArrayBufferView, srcOffset: number, length?: GLuint): void;
|
||||||
usage: GLenum,
|
|
||||||
srcOffset: number,
|
|
||||||
length?: GLuint
|
|
||||||
): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UniformDefineFn = {
|
type UniformDefineFn = {
|
||||||
@ -713,7 +722,7 @@ const attribDefine: AttribDefineFn = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class ShaderProgram {
|
export class ShaderProgram {
|
||||||
/** 顶点着色器 */
|
/** 顶点着色器 */
|
||||||
private vertex: string = DEFAULT_VS;
|
private vertex: string = DEFAULT_VS;
|
||||||
/** 片元着色器 */
|
/** 片元着色器 */
|
||||||
@ -923,10 +932,20 @@ class ShaderProgram {
|
|||||||
/**
|
/**
|
||||||
* 定义一个 uniform block,例如 UBO,并存入本着色器程序的 uniform block 映射
|
* 定义一个 uniform block,例如 UBO,并存入本着色器程序的 uniform block 映射
|
||||||
* 用于一次性向着色器传输大量数据
|
* 用于一次性向着色器传输大量数据
|
||||||
* @param uniform uniform block 名称
|
* @param block uniform block 名称
|
||||||
|
* @param size 数据量,即数据长度,例如一个vec4就是4个长度
|
||||||
|
* @param usage 缓冲区用途,例如 gl.STATIC_DRAW 是指会频繁读取但不会频繁写入
|
||||||
|
* 参考 https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/bufferData
|
||||||
|
* 的 `usage` 参数
|
||||||
|
* @param binding uniform block 的索引,例如这是你设置的第一个uniform block,就可以填0,第二个填1,以此类推
|
||||||
* @returns uniform block 的操作对象,可用于设置其值
|
* @returns uniform block 的操作对象,可用于设置其值
|
||||||
*/
|
*/
|
||||||
defineUniformBlock(block: string): ShaderUniformBlock | null {
|
defineUniformBlock(
|
||||||
|
block: string,
|
||||||
|
size: number,
|
||||||
|
usage: number,
|
||||||
|
binding: number
|
||||||
|
): ShaderUniformBlock | null {
|
||||||
if (this.version === this.element.VERSION_ES_100) {
|
if (this.version === this.element.VERSION_ES_100) {
|
||||||
logger.warn(24);
|
logger.warn(24);
|
||||||
return null;
|
return null;
|
||||||
@ -935,18 +954,33 @@ class ShaderProgram {
|
|||||||
const gl = this.element.gl;
|
const gl = this.element.gl;
|
||||||
if (!program || !gl) return null;
|
if (!program || !gl) return null;
|
||||||
const location = gl.getUniformBlockIndex(program, block);
|
const location = gl.getUniformBlockIndex(program, block);
|
||||||
if (!location) return null;
|
if (location === -1) return null;
|
||||||
const buffer = gl.createBuffer();
|
const buffer = gl.createBuffer();
|
||||||
if (!buffer) return null;
|
if (!buffer) return null;
|
||||||
|
const data = new Float32Array(size);
|
||||||
|
data.fill(0);
|
||||||
|
gl.bindBuffer(gl.UNIFORM_BUFFER, buffer);
|
||||||
|
gl.bufferData(gl.UNIFORM_BUFFER, data, usage);
|
||||||
|
gl.uniformBlockBinding(program, location, binding);
|
||||||
|
gl.bindBufferBase(gl.UNIFORM_BUFFER, binding, buffer);
|
||||||
const obj: ShaderUniformBlock = {
|
const obj: ShaderUniformBlock = {
|
||||||
location,
|
location,
|
||||||
buffer,
|
buffer,
|
||||||
set: (srcData, usage, o?: number, l?: number) => {
|
size,
|
||||||
|
set: (data, o?: number, l?: number) => {
|
||||||
gl.bindBuffer(gl.UNIFORM_BUFFER, buffer);
|
gl.bindBuffer(gl.UNIFORM_BUFFER, buffer);
|
||||||
|
if (o !== void 0) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
gl.bufferData(gl.UNIFORM_BUFFER, srcData, usage, o, l);
|
gl.bufferSubData(gl.UNIFORM_BUFFER, 0, data, o, l);
|
||||||
gl.uniformBlockBinding(program, location, 0);
|
} else {
|
||||||
gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, buffer);
|
// @ts-ignore
|
||||||
|
gl.bufferSubData(gl.UNIFORM_BUFFER, 0, data);
|
||||||
|
}
|
||||||
|
// gl.uniformBlockBinding(program, location, binding);
|
||||||
|
gl.bindBufferBase(gl.UNIFORM_BUFFER, binding, buffer);
|
||||||
|
// const array = new Float32Array(160);
|
||||||
|
// gl.getBufferSubData(gl.UNIFORM_BUFFER, 0, array);
|
||||||
|
// console.log(array[0]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
this.block.set(block, obj);
|
this.block.set(block, obj);
|
||||||
@ -977,7 +1011,7 @@ class ShaderProgram {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private compile() {
|
private compile() {
|
||||||
this.shaderDirty = true;
|
this.shaderDirty = false;
|
||||||
this.clearProgram();
|
this.clearProgram();
|
||||||
|
|
||||||
const shader = this.element;
|
const shader = this.element;
|
||||||
|
@ -203,12 +203,15 @@ export class Transform {
|
|||||||
static readonly identity = new Transform();
|
static readonly identity = new Transform();
|
||||||
}
|
}
|
||||||
|
|
||||||
function multiplyVec3(mat: ReadonlyMat3, vec: ReadonlyVec3) {
|
function multiplyVec3(mat: ReadonlyMat3, vec: ReadonlyVec3): vec3 {
|
||||||
return vec3.fromValues(
|
const v0 = vec[0];
|
||||||
mat[0] * vec[0] + mat[3] * vec[1] + mat[6] * vec[2],
|
const v1 = vec[1];
|
||||||
mat[1] * vec[0] + mat[4] * vec[1] + mat[7] * vec[2],
|
const v2 = vec[2];
|
||||||
mat[2] * vec[0] + mat[5] * vec[1] + mat[8] * vec[2]
|
return [
|
||||||
);
|
mat[0] * v0 + mat[3] * v1 + mat[6] * v2,
|
||||||
|
mat[1] * v0 + mat[4] * v1 + mat[7] * v2,
|
||||||
|
mat[2] * v0 + mat[5] * v1 + mat[8] * v2
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTranslation(mat: ReadonlyMat3): vec2 {
|
function getTranslation(mat: ReadonlyMat3): vec2 {
|
||||||
|
@ -48,6 +48,7 @@
|
|||||||
"22": "There is already an active camera for delivered render item. Consider using 'Camera.for' or diable the active camera to avoid some exceptions.",
|
"22": "There is already an active camera for delivered render item. Consider using 'Camera.for' or diable the active camera to avoid some exceptions.",
|
||||||
"23": "Render item with id of '$1' has already exists.",
|
"23": "Render item with id of '$1' has already exists.",
|
||||||
"24": "Uniform block can only be used in glsl version es 300.",
|
"24": "Uniform block can only be used in glsl version es 300.",
|
||||||
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency."
|
"1001": "Item-detail extension needs 'floor-binder' and 'floor-damage' extension as dependency.",
|
||||||
|
"1101": "Cannot add new effect to point effect instance, for there's no more reserve space for it. Please increase the max count of the instance."
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -91,6 +91,8 @@ export interface GameEvent {
|
|||||||
];
|
];
|
||||||
/** Emitted in game/enemy/damage.ts */
|
/** Emitted in game/enemy/damage.ts */
|
||||||
enemyExtract: [col: EnemyCollection];
|
enemyExtract: [col: EnemyCollection];
|
||||||
|
/** Emitted in lib/events.js restart */
|
||||||
|
restart: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const hook = new EventEmitter<GameEvent>();
|
export const hook = new EventEmitter<GameEvent>();
|
||||||
|
@ -2,6 +2,7 @@ import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d';
|
|||||||
import { CameraAnimation } from '@/core/render/camera';
|
import { CameraAnimation } from '@/core/render/camera';
|
||||||
import { LayerGroup } from '@/core/render/preset/layer';
|
import { LayerGroup } from '@/core/render/preset/layer';
|
||||||
import { MotaRenderer } from '@/core/render/render';
|
import { MotaRenderer } from '@/core/render/render';
|
||||||
|
import { Shader } from '@/core/render/shader';
|
||||||
import { Sprite } from '@/core/render/sprite';
|
import { Sprite } from '@/core/render/sprite';
|
||||||
import { disableViewport, enableViewport } from '@/core/render/utils';
|
import { disableViewport, enableViewport } from '@/core/render/utils';
|
||||||
import type { HeroMover, MoveStep } from '@/game/state/move';
|
import type { HeroMover, MoveStep } from '@/game/state/move';
|
||||||
@ -55,6 +56,8 @@ interface ChaseEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Chase extends EventEmitter<ChaseEvent> {
|
export class Chase extends EventEmitter<ChaseEvent> {
|
||||||
|
static shader: Shader;
|
||||||
|
|
||||||
/** 本次追逐战的数据 */
|
/** 本次追逐战的数据 */
|
||||||
private readonly data: ChaseData;
|
private readonly data: ChaseData;
|
||||||
|
|
||||||
@ -315,6 +318,11 @@ export class Chase extends EventEmitter<ChaseEvent> {
|
|||||||
floorTime.sort((a, b) => a.time - b.time);
|
floorTime.sort((a, b) => a.time - b.time);
|
||||||
}
|
}
|
||||||
this.onTimeListener.sort((a, b) => a.time - b.time);
|
this.onTimeListener.sort((a, b) => a.time - b.time);
|
||||||
|
const render = MotaRenderer.get('render-main')!;
|
||||||
|
const mapDraw = render.getElementById('map-draw')!;
|
||||||
|
render.appendChild(Chase.shader);
|
||||||
|
mapDraw.remove();
|
||||||
|
mapDraw.append(Chase.shader);
|
||||||
this.emit('start');
|
this.emit('start');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,6 +335,19 @@ export class Chase extends EventEmitter<ChaseEvent> {
|
|||||||
this.layer.removeTicker(this.delegation);
|
this.layer.removeTicker(this.delegation);
|
||||||
this.pathSprite?.destroy();
|
this.pathSprite?.destroy();
|
||||||
this.heroMove.off('stepEnd', this.onStepEnd);
|
this.heroMove.off('stepEnd', this.onStepEnd);
|
||||||
|
const render = MotaRenderer.get('render-main')!;
|
||||||
|
const mapDraw = render.getElementById('map-draw')!;
|
||||||
|
mapDraw.remove();
|
||||||
|
Chase.shader.remove();
|
||||||
|
mapDraw.append(render);
|
||||||
this.emit('end', success);
|
this.emit('end', success);
|
||||||
|
this.removeAllListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mota.require('var', 'loading').once('coreInit', () => {
|
||||||
|
const shader = new Shader();
|
||||||
|
Chase.shader = shader;
|
||||||
|
shader.size(480, 480);
|
||||||
|
shader.setHD(true);
|
||||||
|
});
|
||||||
|
@ -6,6 +6,8 @@ import { LayerGroup } from '@/core/render/preset/layer';
|
|||||||
import { MotaRenderer } from '@/core/render/render';
|
import { MotaRenderer } from '@/core/render/render';
|
||||||
import { Sprite } from '@/core/render/sprite';
|
import { Sprite } from '@/core/render/sprite';
|
||||||
import { bgm } from '@/core/audio/bgm';
|
import { bgm } from '@/core/audio/bgm';
|
||||||
|
import { Shader, ShaderProgram, UniformType } from '@/core/render/shader';
|
||||||
|
import { PointEffect, PointEffectType } from '../fx/pointShader';
|
||||||
|
|
||||||
const path: Partial<Record<FloorIds, LocArr[]>> = {
|
const path: Partial<Record<FloorIds, LocArr[]>> = {
|
||||||
MT16: [
|
MT16: [
|
||||||
@ -99,6 +101,11 @@ const path: Partial<Record<FloorIds, LocArr[]>> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let back: Sprite | undefined;
|
let back: Sprite | undefined;
|
||||||
|
const effect = new PointEffect();
|
||||||
|
|
||||||
|
Mota.require('var', 'loading').once('loaded', () => {
|
||||||
|
effect.create(Chase.shader, 10);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化并开始这个追逐战
|
* 初始化并开始这个追逐战
|
||||||
@ -116,6 +123,7 @@ export function initChase(): IChaseController {
|
|||||||
const animation16 = new CameraAnimation(camera);
|
const animation16 = new CameraAnimation(camera);
|
||||||
const animation15 = new CameraAnimation(camera);
|
const animation15 = new CameraAnimation(camera);
|
||||||
const animation14 = new CameraAnimation(camera);
|
const animation14 = new CameraAnimation(camera);
|
||||||
|
effect.setTransform(layer.camera);
|
||||||
|
|
||||||
const data: ChaseData = {
|
const data: ChaseData = {
|
||||||
path,
|
path,
|
||||||
@ -205,22 +213,28 @@ export function initChase(): IChaseController {
|
|||||||
|
|
||||||
Mota.Plugin.require('chase_g').chaseInit1();
|
Mota.Plugin.require('chase_g').chaseInit1();
|
||||||
|
|
||||||
|
chase.on('end', () => {
|
||||||
|
effect.end();
|
||||||
|
camera.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
const controller: IChaseController = {
|
const controller: IChaseController = {
|
||||||
chase,
|
chase,
|
||||||
start(fromSave) {
|
start(fromSave) {
|
||||||
core.setFlag('onChase', true);
|
core.setFlag('onChase', true);
|
||||||
core.setFlag('chaseId', 1);
|
core.setFlag('chaseId', 1);
|
||||||
core.autosave();
|
|
||||||
chase.start();
|
chase.start();
|
||||||
camera.enable();
|
camera.enable();
|
||||||
wolfMove(chase);
|
wolfMove(chase);
|
||||||
|
effect.use();
|
||||||
|
effect.start();
|
||||||
if (fromSave) {
|
if (fromSave) {
|
||||||
initFromSave(chase);
|
initFromSave(chase);
|
||||||
}
|
}
|
||||||
|
testEffect(chase);
|
||||||
},
|
},
|
||||||
end(success) {
|
end(success) {
|
||||||
chase.end(success);
|
chase.end(success);
|
||||||
camera.destroy();
|
|
||||||
},
|
},
|
||||||
initAudio(fromSave) {
|
initAudio(fromSave) {
|
||||||
if (fromSave) initFromSave(chase);
|
if (fromSave) initFromSave(chase);
|
||||||
@ -230,6 +244,26 @@ export function initChase(): IChaseController {
|
|||||||
return controller;
|
return controller;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testEffect(chase: Chase) {
|
||||||
|
// effect.addEffect(
|
||||||
|
// PointEffectType.CircleContrast,
|
||||||
|
// Date.now(),
|
||||||
|
// 100000,
|
||||||
|
// [7 * 32 + 16, 17 * 32 + 16, 200, 150],
|
||||||
|
// [1, 0, 0, 0]
|
||||||
|
// );
|
||||||
|
effect.addEffect(
|
||||||
|
PointEffectType.CircleWarpTangetial,
|
||||||
|
Date.now(),
|
||||||
|
100000,
|
||||||
|
[7 * 32 + 16, 17 * 32 + 16, 150, 120],
|
||||||
|
[Math.PI / 2, 0, 0, 0]
|
||||||
|
);
|
||||||
|
chase.on('frame', () => {
|
||||||
|
effect.requestUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function initAudio(chase: Chase) {
|
function initAudio(chase: Chase) {
|
||||||
playAudio(0, chase);
|
playAudio(0, chase);
|
||||||
}
|
}
|
||||||
|
620
src/plugin/fx/pointShader.ts
Normal file
620
src/plugin/fx/pointShader.ts
Normal file
@ -0,0 +1,620 @@
|
|||||||
|
import { logger } from '@/core/common/logger';
|
||||||
|
import { Shader, ShaderProgram, UniformType } from '@/core/render/shader';
|
||||||
|
import { Transform } from '@/core/render/transform';
|
||||||
|
|
||||||
|
export const enum PointEffectType {
|
||||||
|
/**
|
||||||
|
* 无特效,在此之后的所有特效将会被截断,因此不要在空特效之后添加特效,也不要手动添加空特效
|
||||||
|
*/
|
||||||
|
None,
|
||||||
|
/**
|
||||||
|
* 反色特效,可与任意特效叠加\
|
||||||
|
* 参数分别为:\
|
||||||
|
* `data1: x, y, radius, decay` | 横坐标,纵坐标,半径,衰减开始半径\
|
||||||
|
* `data2: ratio, _, _, _` | 反色强度,空,空,空
|
||||||
|
*/
|
||||||
|
CircleInvert,
|
||||||
|
/**
|
||||||
|
* 灰度特效,可与任意特效叠加\
|
||||||
|
* 参数分别为:\
|
||||||
|
* `data1: x, y, radius, decay` | 横坐标,纵坐标,半径,衰减开始半径\
|
||||||
|
* `data2: ratio, _, _, _` | 黑白强度,空,空,空
|
||||||
|
*/
|
||||||
|
CircleGray,
|
||||||
|
/**
|
||||||
|
* 对比度特效,可与任意特效叠加\
|
||||||
|
* 参数分别为:\
|
||||||
|
* `data1: x, y, radius, decay` | 横坐标,纵坐标,半径,衰减开始半径\
|
||||||
|
* `data2: ratio, _, _, _` | 对比度强度(0表示不变,-1表示灰色,1表示2倍对比度),空,空,空
|
||||||
|
*/
|
||||||
|
CircleContrast,
|
||||||
|
/**
|
||||||
|
* 饱和度特效,可与任意特效叠加\
|
||||||
|
* 参数分别为:\
|
||||||
|
* `data1: x, y, radius, decay` | 横坐标,纵坐标,半径,衰减开始半径\
|
||||||
|
* `data2: ratio, _, _, _` | 对比度强度(0表示不变,-1表示灰色,1表示2倍饱和度),空,空,空
|
||||||
|
*/
|
||||||
|
CircleSaturate,
|
||||||
|
/**
|
||||||
|
* 饱和度特效,可与任意特效叠加\
|
||||||
|
* 参数分别为:\
|
||||||
|
* `data1: x, y, radius, decay` | 横坐标,纵坐标,半径,衰减开始半径\
|
||||||
|
* `data2: angle, _, _, _` | 旋转角度(0表示旋转0度,1表示旋转360度),空,空,空
|
||||||
|
*/
|
||||||
|
CircleHue,
|
||||||
|
/**
|
||||||
|
* 圆形扭曲特效,注意此特效会导致在此之前的所有非扭曲类特效失效,在添加时,系统会自动排序以保证特效正常显示\
|
||||||
|
* 参数分别为:\
|
||||||
|
* `data1: x, y, maxRaius, waveRadius` | 中心横坐标,中心纵坐标,波纹最大传播距离,波纹环的半径\
|
||||||
|
* `data2: amplitude, attenuation, linear, tangential` \
|
||||||
|
*   * `amplitude`: 震动幅度(1表示幅度为1个屏幕,受摄像机缩放影响)\
|
||||||
|
*   * `attenuation`: 衰减比例,即衰减速度\
|
||||||
|
*   * `linear`: 开始线性衰减时间(为确保波纹最终归于平静,在衰减时,会首先平方反比衰减,
|
||||||
|
* 然后到此值时转为线性衰减。此参数范围0-1,0.5表示从一半时间处开始线性衰减)\
|
||||||
|
*   * `tangential`: 切向扭曲比例(1表示与振动幅度一致)\
|
||||||
|
* `data3: startPhase, endPhase, _, _` | 起始位置相位(靠近波纹中心的位置),终止位置相位(远离波纹中心位置),空,空,
|
||||||
|
* 其中相位一个周期为2 * PI,填 Math.PI * 2 则表示相位从一个周期处开始
|
||||||
|
*/
|
||||||
|
CircleWarp,
|
||||||
|
/**
|
||||||
|
* 圆形切向扭曲特效\
|
||||||
|
* 参数分别为:\
|
||||||
|
* `data1: x, y, minRadius, maxRadius` | 中心横坐标,中心纵坐标,扭曲环内圈半径,扭曲环外圈半径
|
||||||
|
* (1表示扭曲了相位角的程度,例如Math.PI的相位,幅度为1,表示旋转整整一圈)\
|
||||||
|
* `data2: startPhase, endPhase, _, _` 起始位置相位(靠近波纹中心的位置),终止位置相位(远离波纹中心的位置),空,空
|
||||||
|
*/
|
||||||
|
CircleWarpTangetial
|
||||||
|
}
|
||||||
|
|
||||||
|
type EffectData = [x0: number, x1: number, x2: number, x3: number];
|
||||||
|
|
||||||
|
const warpEffect = new Set<PointEffectType>();
|
||||||
|
warpEffect.add(PointEffectType.CircleWarp);
|
||||||
|
|
||||||
|
export class PointEffect {
|
||||||
|
/** 着色器程序 */
|
||||||
|
private program?: ShaderProgram;
|
||||||
|
/** 着色器渲染元素 */
|
||||||
|
private shader?: Shader;
|
||||||
|
|
||||||
|
/** 是否开始计时器 */
|
||||||
|
private started: boolean = false;
|
||||||
|
/** 计时器委托id */
|
||||||
|
private delegation: number = -1;
|
||||||
|
/** 特效id计数器,用于修改对应特效的数据 */
|
||||||
|
private effectId: number = 0;
|
||||||
|
/** 特效id到特效索引的映射 */
|
||||||
|
private idIndexMap: Map<number, number> = new Map();
|
||||||
|
/** 变换矩阵 */
|
||||||
|
private transform?: Transform;
|
||||||
|
|
||||||
|
/** 特效最大数量 */
|
||||||
|
private effectCount: number = 0;
|
||||||
|
/** 特效数据 */
|
||||||
|
private dataList: Float32Array = new Float32Array();
|
||||||
|
/** 经过矩阵变换操作后的特效数据 */
|
||||||
|
private transformed: Float32Array = new Float32Array();
|
||||||
|
/** 当前的数据指针,也就是下一次添加特效应该添加至哪 */
|
||||||
|
private dataPointer: number = 0;
|
||||||
|
/** 是否需要更新数据 */
|
||||||
|
private needUpdateData: boolean = false;
|
||||||
|
/** 需要在之后添加的特效 */
|
||||||
|
private toAdd: Set<number[]> = new Set();
|
||||||
|
/** 每个特效的开始时间 */
|
||||||
|
private startTime: Map<number, number> = new Map();
|
||||||
|
/**
|
||||||
|
* 为着色器创建程序
|
||||||
|
* @param shader 着色器渲染元素
|
||||||
|
* @param itemCount 最大效果数量
|
||||||
|
*/
|
||||||
|
create(shader: Shader, itemCount: number) {
|
||||||
|
if (this.program || this.shader || this.started) return;
|
||||||
|
const program = shader.createProgram();
|
||||||
|
program.setVersion(shader.VERSION_ES_300);
|
||||||
|
program.fs(generateFragment(itemCount));
|
||||||
|
program.requestCompile();
|
||||||
|
|
||||||
|
// 声明变量
|
||||||
|
program.defineUniform('u_count', shader.UNIFORM_1i);
|
||||||
|
program.defineUniform('u_screen', shader.UNIFORM_2f);
|
||||||
|
program.defineUniformBlock(
|
||||||
|
'EffectInfo',
|
||||||
|
itemCount * 16,
|
||||||
|
shader.gl.DYNAMIC_DRAW,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
|
this.program = program;
|
||||||
|
this.shader = shader;
|
||||||
|
this.effectCount = itemCount;
|
||||||
|
this.dataList = new Float32Array(itemCount * 16);
|
||||||
|
this.transformed = new Float32Array(itemCount * 16);
|
||||||
|
|
||||||
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在下一帧更新特效数据
|
||||||
|
*/
|
||||||
|
requestUpdate() {
|
||||||
|
this.needUpdateData = true;
|
||||||
|
if (this.shader) this.shader.update(this.shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置本特效实例的变换矩阵,变换矩阵可以在设置特效位置时自动进行变换,配合摄像机视角
|
||||||
|
* @param tranform 变换矩阵
|
||||||
|
*/
|
||||||
|
setTransform(tranform: Transform) {
|
||||||
|
this.transform = tranform;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加一个特效,如果特效还未开始,那么会在其开始时添加特效,注意特效数据必须填四位,不足者补0
|
||||||
|
* @param type 特效类型
|
||||||
|
* @param startTime 特效开始时间
|
||||||
|
* @param time 特效持续时间
|
||||||
|
* @param data1 第一组自定义数据,可选
|
||||||
|
* @param data2 第二组自定义数据,可选
|
||||||
|
* @param data3 第三组自定义数据,可选
|
||||||
|
* @returns 这个特效的唯一id,可用于设置特效
|
||||||
|
*/
|
||||||
|
addEffect(
|
||||||
|
type: PointEffectType,
|
||||||
|
startTime: number,
|
||||||
|
time: number,
|
||||||
|
data1: EffectData = [0, 0, 0, 0],
|
||||||
|
data2: EffectData = [0, 0, 0, 0],
|
||||||
|
data3: EffectData = [0, 0, 0, 0]
|
||||||
|
) {
|
||||||
|
if (type === PointEffectType.None) return -1;
|
||||||
|
const now = Date.now();
|
||||||
|
// 如果已经结束,那么什么都不干
|
||||||
|
if (now > time + startTime) return -1;
|
||||||
|
// 填充 0 是因为 std140 布局中,每个数据都会占据 16 的倍数个字节
|
||||||
|
// 不过第二项填充一个整数,是为了能够对每个特效进行标识,从而能够对某个特效进行修改
|
||||||
|
const id = this.effectId++;
|
||||||
|
// 第三项为特效的进度
|
||||||
|
const data = [type, id, 0, time, ...data1, ...data2, ...data3];
|
||||||
|
this.startTime.set(id, now);
|
||||||
|
|
||||||
|
if (now > startTime) {
|
||||||
|
this.addEffectToList(data);
|
||||||
|
} else {
|
||||||
|
// 如果还没开始,那么添加至预备队列
|
||||||
|
this.toAdd.add(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 立刻删除一个特效
|
||||||
|
* @param id 要删除的特效的id
|
||||||
|
*/
|
||||||
|
deleteEffect(id: number) {
|
||||||
|
this.removeEffect(this.findIndexById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置一个特效的数据,注意特效数据必须填四位,不足者补0
|
||||||
|
* @param id 特效id
|
||||||
|
* @param data1 第一组自定义数据,可选
|
||||||
|
* @param data2 第二组自定义数据,可选
|
||||||
|
* @param data3 第三组自定义数据,可选
|
||||||
|
*/
|
||||||
|
setEffect(
|
||||||
|
id: number,
|
||||||
|
data1?: EffectData,
|
||||||
|
data2?: EffectData,
|
||||||
|
data3?: EffectData
|
||||||
|
) {
|
||||||
|
const index = this.findIndexById(id);
|
||||||
|
const list = this.dataList;
|
||||||
|
if (data1) {
|
||||||
|
list.set(data1, index * 16 + 4);
|
||||||
|
}
|
||||||
|
if (data2) {
|
||||||
|
list.set(data2, index * 16 + 8);
|
||||||
|
}
|
||||||
|
if (data3) {
|
||||||
|
list.set(data3, index * 16 + 12);
|
||||||
|
}
|
||||||
|
this.needUpdateData = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private findIndexById(id: number) {
|
||||||
|
const map = this.idIndexMap.get(id);
|
||||||
|
if (map !== void 0) return map;
|
||||||
|
let index = -1;
|
||||||
|
const list = this.dataList;
|
||||||
|
for (let i = 0; i < this.effectCount; i++) {
|
||||||
|
const realIndex = i * 16 + 1;
|
||||||
|
if (list[realIndex] === id) {
|
||||||
|
index = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (index !== -1) {
|
||||||
|
this.idIndexMap.set(id, index);
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addEffectToList(data: number[]) {
|
||||||
|
if (this.dataPointer >= this.effectCount) {
|
||||||
|
logger.warn(1101);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const type = data[0];
|
||||||
|
const list = this.dataList;
|
||||||
|
const id = data[1];
|
||||||
|
if (warpEffect.has(type)) {
|
||||||
|
list.copyWithin(16, 0, 16);
|
||||||
|
list.set(data, 0);
|
||||||
|
this.dataPointer++;
|
||||||
|
this.idIndexMap.clear();
|
||||||
|
} else {
|
||||||
|
list.set(data, this.dataPointer * 16);
|
||||||
|
this.idIndexMap.set(id, this.dataPointer);
|
||||||
|
this.dataPointer++;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.needUpdateData = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private removeEffect(index: number) {
|
||||||
|
if (index >= this.effectCount) return;
|
||||||
|
if (this.dataPointer === 0) return;
|
||||||
|
const list = this.dataList;
|
||||||
|
const id = list[index * 16 + 1];
|
||||||
|
this.startTime.delete(id);
|
||||||
|
list.copyWithin(index * 16, index * 16 + 16);
|
||||||
|
list.fill(0, -16, -1);
|
||||||
|
this.dataPointer--;
|
||||||
|
this.needUpdateData = true;
|
||||||
|
this.idIndexMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用这个特效作为着色器程序
|
||||||
|
*/
|
||||||
|
use() {
|
||||||
|
if (!this.shader || !this.program) return;
|
||||||
|
this.shader.useProgram(this.program);
|
||||||
|
const uCount =
|
||||||
|
this.program.getUniform<UniformType.Uniform1f>('u_count');
|
||||||
|
uCount?.set(this.effectCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始计时器
|
||||||
|
*/
|
||||||
|
start() {
|
||||||
|
if (!this.shader || !this.program) return;
|
||||||
|
this.started = true;
|
||||||
|
const block = this.program.getUniformBlock('EffectInfo')!;
|
||||||
|
const screen =
|
||||||
|
this.program.getUniform<UniformType.Uniform2f>('u_screen');
|
||||||
|
const { width, height } = this.shader;
|
||||||
|
const scale = core.domStyle.scale * devicePixelRatio;
|
||||||
|
screen?.set(width * scale, height * scale);
|
||||||
|
|
||||||
|
// 不知道性能怎么样,只能试试看了
|
||||||
|
this.delegation = this.shader.delegateTicker(() => {
|
||||||
|
if (!this.started) {
|
||||||
|
this.shader?.removeTicker(this.delegation);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const now = Date.now();
|
||||||
|
const toDelete = new Set<number[]>();
|
||||||
|
this.toAdd.forEach(v => {
|
||||||
|
const id = v[1];
|
||||||
|
const startTime = this.startTime.get(id);
|
||||||
|
if (!startTime) return;
|
||||||
|
const time = v[3];
|
||||||
|
if (now > startTime + time) {
|
||||||
|
toDelete.add(v);
|
||||||
|
} else if (now >= startTime) {
|
||||||
|
this.addEffectToList(v);
|
||||||
|
toDelete.add(v);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
toDelete.forEach(v => this.toAdd.delete(v));
|
||||||
|
|
||||||
|
const toRemove: number[] = [];
|
||||||
|
const list = this.dataList;
|
||||||
|
|
||||||
|
// 倒序以保证删除时不会影响到其他删除
|
||||||
|
for (let i = this.effectCount - 1; i >= 0; i--) {
|
||||||
|
const type = list[i * 16];
|
||||||
|
if (type === PointEffectType.None) continue;
|
||||||
|
const id = list[i * 16 + 1];
|
||||||
|
const start = this.startTime.get(id);
|
||||||
|
if (!start) continue;
|
||||||
|
const time = list[i * 16 + 3];
|
||||||
|
const progress = (now - start) / time;
|
||||||
|
if (progress > 1) toRemove.push(i);
|
||||||
|
list[i * 16 + 2] = progress;
|
||||||
|
}
|
||||||
|
toRemove.forEach(v => {
|
||||||
|
this.removeEffect(v);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.needUpdateData) {
|
||||||
|
const list = this.dataList;
|
||||||
|
const transformed = this.transformed;
|
||||||
|
transformed.set(list);
|
||||||
|
this.transformData();
|
||||||
|
block.set(transformed);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结束计时器
|
||||||
|
*/
|
||||||
|
end() {
|
||||||
|
if (!this.started || !this.shader || !this.program) return;
|
||||||
|
this.shader.removeTicker(this.delegation);
|
||||||
|
this.started = false;
|
||||||
|
this.dataList.fill(0);
|
||||||
|
this.dataPointer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
this.end();
|
||||||
|
if (this.shader && this.program) {
|
||||||
|
this.shader.deleteProgram(this.program);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private transformData() {
|
||||||
|
const transform = this.transform;
|
||||||
|
if (!transform) return;
|
||||||
|
const count = this.effectCount;
|
||||||
|
const list = this.dataList;
|
||||||
|
const transformed = this.transformed;
|
||||||
|
let scale = transform.scaleX * core.domStyle.scale;
|
||||||
|
if (this.shader?.highResolution) scale *= devicePixelRatio;
|
||||||
|
const scaleTransform = new Transform();
|
||||||
|
scaleTransform.scale(scale, scale);
|
||||||
|
const scaled = scaleTransform.multiply(transform);
|
||||||
|
const fixedHeight = core._PY_ * scale;
|
||||||
|
|
||||||
|
const transformXY = (index: number) => {
|
||||||
|
const x = list[index + 4];
|
||||||
|
const y = list[index + 5];
|
||||||
|
const [tx, ty] = Transform.transformed(scaled, x, y);
|
||||||
|
transformed[index + 4] = tx;
|
||||||
|
transformed[index + 5] = fixedHeight - ty;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
const index = i * 16;
|
||||||
|
const type = list[index];
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case PointEffectType.CircleGray:
|
||||||
|
case PointEffectType.CircleInvert:
|
||||||
|
case PointEffectType.CircleContrast:
|
||||||
|
case PointEffectType.CircleSaturate:
|
||||||
|
case PointEffectType.CircleHue:
|
||||||
|
case PointEffectType.CircleWarpTangetial: {
|
||||||
|
transformXY(index);
|
||||||
|
transformed[index + 6] *= scale;
|
||||||
|
transformed[index + 7] *= scale;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PointEffectType.CircleWarp: {
|
||||||
|
transformXY(index);
|
||||||
|
transformed[index + 6] *= scale;
|
||||||
|
transformed[index + 7] *= scale;
|
||||||
|
transformed[index + 8] *= scale;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PointEffectType.None: {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateFragment(itemCount: number) {
|
||||||
|
return /* glsl */ `
|
||||||
|
uniform int u_count;
|
||||||
|
uniform vec2 u_screen; // 画布长宽
|
||||||
|
|
||||||
|
out vec4 outColor;
|
||||||
|
|
||||||
|
struct Effect {
|
||||||
|
vec2 type; // 效果类型
|
||||||
|
vec2 time; // 开始时间,持续时长
|
||||||
|
vec4 info1; // 第一组信息,表示自定义参数
|
||||||
|
vec4 info2; // 第二组信息,表示自定义参数
|
||||||
|
vec4 info3; // 第三组信息,表示自定义参数
|
||||||
|
};
|
||||||
|
|
||||||
|
layout (std140) uniform EffectInfo {
|
||||||
|
Effect effects[${itemCount}];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function: RGB to HSL conversion
|
||||||
|
vec3 rgb2hsl(vec3 color) {
|
||||||
|
float maxC = max(max(color.r, color.g), color.b);
|
||||||
|
float minC = min(min(color.r, color.g), color.b);
|
||||||
|
float delta = maxC - minC;
|
||||||
|
|
||||||
|
float h = 0.0;
|
||||||
|
float s = 0.0;
|
||||||
|
float l = (maxC + minC) * 0.5;
|
||||||
|
|
||||||
|
if (delta != 0.0) {
|
||||||
|
s = (l < 0.5) ? delta / (maxC + minC) : delta / (2.0 - maxC - minC);
|
||||||
|
|
||||||
|
if (maxC == color.r) {
|
||||||
|
h = (color.g - color.b) / delta + (color.g < color.b ? 6.0 : 0.0);
|
||||||
|
} else if (maxC == color.g) {
|
||||||
|
h = (color.b - color.r) / delta + 2.0;
|
||||||
|
} else {
|
||||||
|
h = (color.r - color.g) / delta + 4.0;
|
||||||
|
}
|
||||||
|
h /= 6.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec3(h, s, l);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function: Hue to RGB conversion
|
||||||
|
float hue2rgb(float p, float q, float t) {
|
||||||
|
if (t < 0.0) t += 1.0;
|
||||||
|
if (t > 1.0) t -= 1.0;
|
||||||
|
if (t < 1.0 / 6.0) return p + (q - p) * 6.0 * t;
|
||||||
|
if (t < 1.0 / 2.0) return q;
|
||||||
|
if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6.0;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function: HSL to RGB conversion
|
||||||
|
vec3 hsl2rgb(vec3 hsl) {
|
||||||
|
float h = hsl.x;
|
||||||
|
float s = hsl.y;
|
||||||
|
float l = hsl.z;
|
||||||
|
|
||||||
|
float r, g, b;
|
||||||
|
|
||||||
|
if (s == 0.0) {
|
||||||
|
r = g = b = l; // Achromatic (gray)
|
||||||
|
} else {
|
||||||
|
float q = (l < 0.5) ? (l * (1.0 + s)) : (l + s - l * s);
|
||||||
|
float p = 2.0 * l - q;
|
||||||
|
|
||||||
|
r = hue2rgb(p, q, h + 1.0 / 3.0);
|
||||||
|
g = hue2rgb(p, q, h);
|
||||||
|
b = hue2rgb(p, q, h - 1.0 / 3.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return vec3(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
// x: 横坐标 y: 纵坐标 z: 半径 w: 衰减开始半径
|
||||||
|
// 计算圆形效果的衰减程度
|
||||||
|
float calCircleDecay(vec4 data) {
|
||||||
|
float dis = distance(data.xy, gl_FragCoord.xy);
|
||||||
|
if (dis <= data.w) return 1.0;
|
||||||
|
if (dis >= data.z) return 0.0;
|
||||||
|
if (data.z <= data.w) return 1.0;
|
||||||
|
return 1.0 - (dis - data.w) / (data.z - data.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
// data1: x, y, maxRadius, waveRadius
|
||||||
|
// data2: amplitude, attenuation, linear, _
|
||||||
|
// 计算波纹扭曲的衰减程度,从 x = 1 的平方反比函数开始,衰减至 x = 1 + attenuation,然后线性衰减
|
||||||
|
float calWarpDecay(float progress, vec4 data1, vec4 data2) {
|
||||||
|
if (progress >= data2.z) {
|
||||||
|
float end = 1.0 / pow(1.0 + data2.y, 2.0);
|
||||||
|
return end - end * (progress - data2.z) / (1.0 - data2.z);
|
||||||
|
} else {
|
||||||
|
return 1.0 / pow(1.0 + (progress / data2.z) * data2.y, 2.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 coord = v_texCoord;
|
||||||
|
vec4 color = texture(u_sampler, v_texCoord);
|
||||||
|
for (int i = 0; i < u_count; i++) {
|
||||||
|
Effect effect = effects[i];
|
||||||
|
int effectType = int(effect.type.x);
|
||||||
|
vec2 timeInfo = effect.time;
|
||||||
|
vec4 data1 = effect.info1;
|
||||||
|
vec4 data2 = effect.info2;
|
||||||
|
vec4 data3 = effect.info3;
|
||||||
|
if (effectType == ${PointEffectType.None}) break;
|
||||||
|
float progress = timeInfo.x;
|
||||||
|
// 我草了这句continue,调试的时候直接硬控我俩小时
|
||||||
|
// if (now < 0.0 || now > end) continue;
|
||||||
|
|
||||||
|
// 下面开始实施对应的着色器特效
|
||||||
|
|
||||||
|
// 反色,data1: x y radius decay;data2: ratio _ _ _
|
||||||
|
if (effectType == ${PointEffectType.CircleInvert}) {
|
||||||
|
float ratio = data2.x * calCircleDecay(data1);
|
||||||
|
if (ratio > 0.0) {
|
||||||
|
vec3 delta = (1.0 - 2.0 * color.rgb) * ratio;
|
||||||
|
color.rgb = clamp(color.rgb + delta, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 灰度,data1: x y radius decay;data2: ratio _ _ _
|
||||||
|
else if (effectType == ${PointEffectType.CircleGray}) {
|
||||||
|
float ratio = data2.x * calCircleDecay(data1);
|
||||||
|
if (ratio > 0.0) {
|
||||||
|
float gray = dot(color.rgb, vec3(0.2126, 0.7125, 0.0722));
|
||||||
|
vec3 grayed = color.rgb - gray;
|
||||||
|
color = vec4(color.rgb - grayed * ratio, 1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 对比度,data1: x y radius decay;data2: ratio _ _ _
|
||||||
|
else if (effectType == ${PointEffectType.CircleContrast}) {
|
||||||
|
float ratio = data2.x * calCircleDecay(data1) + 1.0;
|
||||||
|
if (ratio > 0.0) {
|
||||||
|
color.rgb = mix(vec3(0.5), color.rgb, ratio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 饱和度,data1: x y radius decay;data2: ratio _ _ _
|
||||||
|
else if (effectType == ${PointEffectType.CircleSaturate}) {
|
||||||
|
float ratio = data2.x * calCircleDecay(data1) + 1.0;
|
||||||
|
if (ratio > 0.0) {
|
||||||
|
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
||||||
|
color.rgb = mix(vec3(gray), color.rgb, ratio);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 色相旋转,data1: x y radius decay;data2: angle _ _ _
|
||||||
|
else if (effectType == ${PointEffectType.CircleHue}) {
|
||||||
|
float ratio = data2.x * calCircleDecay(data1);
|
||||||
|
if (ratio > 0.0) {
|
||||||
|
vec3 hsl = rgb2hsl(color.rgb);
|
||||||
|
hsl.x = mod(hsl.x + data2.x * ratio, 1.0);
|
||||||
|
color.rgb = hsl2rgb(hsl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 扭曲,data1: x, y, maxRadius, waveRadius; data2: amplitude, attenuation, linear, _
|
||||||
|
// data3: startPhase, endPhase, _, _
|
||||||
|
else if (effectType == ${PointEffectType.CircleWarp}) {
|
||||||
|
float dis = distance(data1.xy, gl_FragCoord.xy);
|
||||||
|
// 当前半径
|
||||||
|
float radius = progress * data1.z;
|
||||||
|
if (dis > radius + data1.w || dis < radius - data1.w) continue;
|
||||||
|
float ratio = data2.x * calWarpDecay(progress, data1, data2);
|
||||||
|
float halfRadius = data1.w / 2.0;
|
||||||
|
if (ratio > 0.0 && abs(dis - radius) <= halfRadius) {
|
||||||
|
// 计算当前相位
|
||||||
|
float x = ((dis - radius) / data1.w + 0.5) * (data3.y - data3.x) + data3.x;
|
||||||
|
float wave = sin(x) * ratio;
|
||||||
|
float xRatio = (gl_FragCoord.x - data1.x) / dis;
|
||||||
|
float yRatio = (gl_FragCoord.y - data1.y) / dis;
|
||||||
|
coord.x += wave * xRatio + wave * yRatio * data2.w;
|
||||||
|
coord.y += wave * yRatio + wave * xRatio * data2.w;
|
||||||
|
color = texture(u_sampler, coord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 切向扭曲,data1: x, y, minRadius, maxRadius; data2: startPhase, endPhase, _, _
|
||||||
|
else if (effectType == ${PointEffectType.CircleWarpTangetial}) {
|
||||||
|
float dis = distance(data1.xy, gl_FragCoord.xy);
|
||||||
|
float ratio = (dis - data1.z) / (data1.w - data1.z);
|
||||||
|
if (ratio <= 1.0 && ratio > 0.0) {
|
||||||
|
float phase = ratio * (data2.y - data2.x) + data2.x;
|
||||||
|
float waveSin = sin(phase);
|
||||||
|
float waveCos = cos(phase);
|
||||||
|
// 相对坐标
|
||||||
|
vec2 relCoord = gl_FragCoord.xy - data1.xy;
|
||||||
|
coord.x = (data1.x + relCoord.x * waveCos - relCoord.y * waveSin) / u_screen.x;
|
||||||
|
coord.y = 1.0 - (data1.y + relCoord.x * waveSin + relCoord.y * waveCos) / u_screen.y;
|
||||||
|
color = texture(u_sampler, coord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outColor = color;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user