From f01d185db3dc14aa3102ea330d02d135dad392b1 Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Tue, 15 Oct 2024 15:55:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=80=E4=BA=9B=E7=9D=80=E8=89=B2?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/libs/events.js | 1 + public/project/floors/MT16.js | 4 + src/core/fx/shader.ts | 545 ------------------------------ src/core/render/camera.ts | 7 +- src/core/render/index.ts | 2 +- src/core/render/shader.ts | 94 ++++-- src/core/render/transform.ts | 15 +- src/data/logger.json | 3 +- src/game/game.ts | 2 + src/plugin/chase/chase.ts | 21 ++ src/plugin/chase/chase1.ts | 38 ++- src/plugin/fx/pointShader.ts | 620 ++++++++++++++++++++++++++++++++++ 12 files changed, 764 insertions(+), 588 deletions(-) delete mode 100644 src/core/fx/shader.ts create mode 100644 src/plugin/fx/pointShader.ts diff --git a/public/libs/events.js b/public/libs/events.js index 213b83a..0bbc104 100644 --- a/public/libs/events.js +++ b/public/libs/events.js @@ -318,6 +318,7 @@ events.prototype.restart = function () { core.hideStatusBar(); core.showStartAnimate(); core.playBgm(main.startBgm); + Mota.require('var', 'hook').emit('restart'); }; ////// 询问是否需要重新开始 ////// diff --git a/public/project/floors/MT16.js b/public/project/floors/MT16.js index 625d448..c4b333b 100644 --- a/public/project/floors/MT16.js +++ b/public/project/floors/MT16.js @@ -130,6 +130,7 @@ main.floors.MT16= ] }, "追逐战后录像会进行自动修复,不用担心录像问题", + "如果逃脱失败,或者想重新开始追逐战,直接读取自动存档即可,会跳过前奏", { "type": "hideStatusBar", "toolbox": true @@ -400,6 +401,9 @@ main.floors.MT16= { "type": "function", "function": "function(){\nMota.Plugin.require('chase_r').start(false);\n}" + }, + { + "type": "autoSave" } ], "2,23": [ diff --git a/src/core/fx/shader.ts b/src/core/fx/shader.ts deleted file mode 100644 index 31dce52..0000000 --- a/src/core/fx/shader.ts +++ /dev/null @@ -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; - -interface ProgramInfo { - program: WebGLProgram; - attrib: Record; - uniform: Record; -} - -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 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; - set(value: UniformBinderValue): void; - get(): UniformBinderValue; -} - -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 { - 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 { - 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; - const value = ( - num === 1 ? 0 : Array(num).fill(0) - ) as UniformBinderValue; - - 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); - } - }; -} diff --git a/src/core/render/camera.ts b/src/core/render/camera.ts index ee0e8b8..d3e8026 100644 --- a/src/core/render/camera.ts +++ b/src/core/render/camera.ts @@ -600,13 +600,14 @@ export class CameraAnimation extends EventEmitter { if (!end) return; const t = end.start + end.time; if (t > endTime) endTime = t; + const cam = this.camera; if (ope.type === 'translate') { - this.camera.applyTranslateAnimation(ope, exe.animation, t + 50); + cam.applyTranslateAnimation(ope, exe.animation, t + 100); } else if (ope.type === 'rotate') { - this.camera.applyRotateAnimation(ope, exe.animation, t + 50); + cam.applyRotateAnimation(ope, exe.animation, t + 100); } else { - this.camera.applyScaleAnimation(ope, exe.animation, t + 50); + cam.applyScaleAnimation(ope, exe.animation, t + 100); } }); this.endTime = endTime + this.startTime; diff --git a/src/core/render/index.ts b/src/core/render/index.ts index b7f40cf..4f5a574 100644 --- a/src/core/render/index.ts +++ b/src/core/render/index.ts @@ -14,7 +14,7 @@ import { Container } from './container'; let main: MotaRenderer; -Mota.require('var', 'loading').once('loaded', () => { +Mota.require('var', 'loading').once('coreInit', () => { const render = new MotaRenderer(); main = render; render.mount(); diff --git a/src/core/render/shader.ts b/src/core/render/shader.ts index 15dde9c..1f2e49a 100644 --- a/src/core/render/shader.ts +++ b/src/core/render/shader.ts @@ -1,4 +1,3 @@ -import { isNil } from 'lodash-es'; import { logger } from '../common/logger'; import { MotaOffscreenCanvas2D } from '../fx/canvas2d'; import { isWebGL2Supported } from '../fx/webgl'; @@ -7,9 +6,7 @@ import { ERenderItemEvent, RenderItem, RenderItemPosition } from './item'; import { Transform } from './transform'; const SHADER_VERTEX_PREFIX_300 = /* glsl */ `#version 300 es -#ifdef GL_ES - precision highp float; -#endif +precision highp float; in vec4 a_position; in vec2 a_texCoord; @@ -17,9 +14,7 @@ in vec2 a_texCoord; out highp vec2 v_texCoord; `; const SHADER_VERTEX_PREFIX_100 = /* glsl */ ` -#ifdef GL_ES - precision highp float; -#endif +precision highp float; attribute vec4 a_position; attribute vec2 a_texCoord; @@ -28,18 +23,14 @@ varying highp vec2 v_texCoord; `; const SHADER_FRAGMENT_PREFIX_300 = /* glsl */ `#version 300 es -#ifdef GL_ES - precision highp float; -#endif +precision highp float; in highp vec2 v_texCoord; uniform sampler2D u_sampler; `; const SHADER_FRAGMENT_PREFIX_100 = /* glsl */ ` -#ifdef GL_ES - precision highp float; -#endif +precision highp float; varying highp vec2 v_texCoord; @@ -288,6 +279,12 @@ export class Shader extends Container { gl.depthFunc(gl.LEQUAL); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + const pre = this.preDraw(); + if (!pre) { + this.postDraw(); + return; + } + // 设置顶点信息 this.setPositionAttrib(); this.setTextureAttrib(); @@ -310,8 +307,24 @@ export class Shader extends Container { // 绘制 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 着色器程序 @@ -542,13 +555,9 @@ interface ShaderUniformMatrix { interface ShaderUniformBlock { location: GLuint; buffer: WebGLBuffer; - set(srcData: AllowSharedBufferSource | null, usage: GLenum): void; - set( - srcData: ArrayBufferView, - usage: GLenum, - srcOffset: number, - length?: GLuint - ): void; + size: number; + set(srcData: AllowSharedBufferSource | null): void; + set(srcData: ArrayBufferView, srcOffset: number, length?: GLuint): void; } type UniformDefineFn = { @@ -713,7 +722,7 @@ const attribDefine: AttribDefineFn = { } }; -class ShaderProgram { +export class ShaderProgram { /** 顶点着色器 */ private vertex: string = DEFAULT_VS; /** 片元着色器 */ @@ -923,10 +932,20 @@ class ShaderProgram { /** * 定义一个 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 的操作对象,可用于设置其值 */ - defineUniformBlock(block: string): ShaderUniformBlock | null { + defineUniformBlock( + block: string, + size: number, + usage: number, + binding: number + ): ShaderUniformBlock | null { if (this.version === this.element.VERSION_ES_100) { logger.warn(24); return null; @@ -935,18 +954,33 @@ class ShaderProgram { const gl = this.element.gl; if (!program || !gl) return null; const location = gl.getUniformBlockIndex(program, block); - if (!location) return null; + if (location === -1) return null; const buffer = gl.createBuffer(); 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 = { location, buffer, - set: (srcData, usage, o?: number, l?: number) => { + size, + set: (data, o?: number, l?: number) => { gl.bindBuffer(gl.UNIFORM_BUFFER, buffer); - // @ts-ignore - gl.bufferData(gl.UNIFORM_BUFFER, srcData, usage, o, l); - gl.uniformBlockBinding(program, location, 0); - gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, buffer); + if (o !== void 0) { + // @ts-ignore + gl.bufferSubData(gl.UNIFORM_BUFFER, 0, data, o, l); + } else { + // @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); @@ -977,7 +1011,7 @@ class ShaderProgram { } private compile() { - this.shaderDirty = true; + this.shaderDirty = false; this.clearProgram(); const shader = this.element; diff --git a/src/core/render/transform.ts b/src/core/render/transform.ts index 48bb7e3..436435a 100644 --- a/src/core/render/transform.ts +++ b/src/core/render/transform.ts @@ -203,12 +203,15 @@ export class Transform { static readonly identity = new Transform(); } -function multiplyVec3(mat: ReadonlyMat3, vec: ReadonlyVec3) { - return vec3.fromValues( - mat[0] * vec[0] + mat[3] * vec[1] + mat[6] * vec[2], - mat[1] * vec[0] + mat[4] * vec[1] + mat[7] * vec[2], - mat[2] * vec[0] + mat[5] * vec[1] + mat[8] * vec[2] - ); +function multiplyVec3(mat: ReadonlyMat3, vec: ReadonlyVec3): vec3 { + const v0 = vec[0]; + const v1 = vec[1]; + const v2 = 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 { diff --git a/src/data/logger.json b/src/data/logger.json index f44ae9a..f8522e1 100644 --- a/src/data/logger.json +++ b/src/data/logger.json @@ -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.", "23": "Render item with id of '$1' has already exists.", "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." } } \ No newline at end of file diff --git a/src/game/game.ts b/src/game/game.ts index d270231..607fcc9 100644 --- a/src/game/game.ts +++ b/src/game/game.ts @@ -91,6 +91,8 @@ export interface GameEvent { ]; /** Emitted in game/enemy/damage.ts */ enemyExtract: [col: EnemyCollection]; + /** Emitted in lib/events.js restart */ + restart: []; } export const hook = new EventEmitter(); diff --git a/src/plugin/chase/chase.ts b/src/plugin/chase/chase.ts index e126302..35b8d18 100644 --- a/src/plugin/chase/chase.ts +++ b/src/plugin/chase/chase.ts @@ -2,6 +2,7 @@ import { MotaOffscreenCanvas2D } from '@/core/fx/canvas2d'; import { CameraAnimation } from '@/core/render/camera'; import { LayerGroup } from '@/core/render/preset/layer'; import { MotaRenderer } from '@/core/render/render'; +import { Shader } from '@/core/render/shader'; import { Sprite } from '@/core/render/sprite'; import { disableViewport, enableViewport } from '@/core/render/utils'; import type { HeroMover, MoveStep } from '@/game/state/move'; @@ -55,6 +56,8 @@ interface ChaseEvent { } export class Chase extends EventEmitter { + static shader: Shader; + /** 本次追逐战的数据 */ private readonly data: ChaseData; @@ -315,6 +318,11 @@ export class Chase extends EventEmitter { floorTime.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'); } @@ -327,6 +335,19 @@ export class Chase extends EventEmitter { this.layer.removeTicker(this.delegation); this.pathSprite?.destroy(); 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.removeAllListeners(); } } + +Mota.require('var', 'loading').once('coreInit', () => { + const shader = new Shader(); + Chase.shader = shader; + shader.size(480, 480); + shader.setHD(true); +}); diff --git a/src/plugin/chase/chase1.ts b/src/plugin/chase/chase1.ts index bbacac4..fd82739 100644 --- a/src/plugin/chase/chase1.ts +++ b/src/plugin/chase/chase1.ts @@ -6,6 +6,8 @@ import { LayerGroup } from '@/core/render/preset/layer'; import { MotaRenderer } from '@/core/render/render'; import { Sprite } from '@/core/render/sprite'; import { bgm } from '@/core/audio/bgm'; +import { Shader, ShaderProgram, UniformType } from '@/core/render/shader'; +import { PointEffect, PointEffectType } from '../fx/pointShader'; const path: Partial> = { MT16: [ @@ -99,6 +101,11 @@ const path: Partial> = { }; 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 animation15 = new CameraAnimation(camera); const animation14 = new CameraAnimation(camera); + effect.setTransform(layer.camera); const data: ChaseData = { path, @@ -205,22 +213,28 @@ export function initChase(): IChaseController { Mota.Plugin.require('chase_g').chaseInit1(); + chase.on('end', () => { + effect.end(); + camera.destroy(); + }); + const controller: IChaseController = { chase, start(fromSave) { core.setFlag('onChase', true); core.setFlag('chaseId', 1); - core.autosave(); chase.start(); camera.enable(); wolfMove(chase); + effect.use(); + effect.start(); if (fromSave) { initFromSave(chase); } + testEffect(chase); }, end(success) { chase.end(success); - camera.destroy(); }, initAudio(fromSave) { if (fromSave) initFromSave(chase); @@ -230,6 +244,26 @@ export function initChase(): IChaseController { 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) { playAudio(0, chase); } diff --git a/src/plugin/fx/pointShader.ts b/src/plugin/fx/pointShader.ts new file mode 100644 index 0000000..2b40dba --- /dev/null +++ b/src/plugin/fx/pointShader.ts @@ -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(); +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 = 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 = new Set(); + /** 每个特效的开始时间 */ + private startTime: Map = 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('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('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(); + 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; +} +`; +}