UNPKG

@visactor/vrender-animate

Version:

This module provides a graph-based animation system for VRender.

232 lines (222 loc) 24.1 kB
import { HybridEffectBase } from "./base/CustomEffectBase"; import { ImageProcessUtils, ShaderLibrary } from "./base/ImageProcessUtils"; export class Dissolve extends HybridEffectBase { constructor(from, to, duration, easing, params) { var _a, _b, _c, _d; super(from, to, duration, easing, params), this.noiseData = null; const rawNoiseScale = null === (_a = null == params ? void 0 : params.options) || void 0 === _a ? void 0 : _a.noiseScale, clampedNoiseScale = void 0 !== rawNoiseScale ? Math.max(0, Math.floor(rawNoiseScale)) : 8; this.dissolveConfig = { dissolveType: (null === (_b = null == params ? void 0 : params.options) || void 0 === _b ? void 0 : _b.dissolveType) || "outward", useWebGL: void 0 === (null === (_c = null == params ? void 0 : params.options) || void 0 === _c ? void 0 : _c.useWebGL) || params.options.useWebGL, noiseScale: clampedNoiseScale, fadeEdge: void 0 === (null === (_d = null == params ? void 0 : params.options) || void 0 === _d ? void 0 : _d.fadeEdge) || params.options.fadeEdge }; } getShaderSources() { return { vertex: ShaderLibrary.STANDARD_VERTEX_SHADER, fragment: `\n precision mediump float;\n uniform sampler2D u_texture;\n uniform sampler2D u_noiseTexture;\n uniform float u_time;\n uniform int u_dissolveType;\n uniform vec2 u_resolution;\n uniform float u_noiseScale;\n uniform bool u_fadeEdge;\n varying vec2 v_texCoord;\n\n ${ShaderLibrary.SHADER_FUNCTIONS}\n\n // 向外溶解函数\n float outwardDissolve(vec2 uv, float time, float pixelSize, vec2 resolution) {\n vec2 center = vec2(0.5, 0.5);\n float distFromCenter = length(uv - center);\n float maxDist = length(vec2(0.5, 0.5));\n\n // 归一化距离 (0为中心,1为边缘)\n float normalizedDist = distFromCenter / maxDist;\n\n // 向外溶解:从边缘开始溶解,time控制溶解进度\n // 增加安全边距,确保动画结束时完全溶解\n float edgeThreshold = 1.2 - time * 1.5;\n\n // 当pixelSize > 0时添加颗粒效果\n if (pixelSize > 0.0) {\n // 添加基于像素大小的噪声,让边缘呈现颗粒状\n vec2 pixelCoord = uv * resolution; // 转换为像素坐标\n float noiseValue = pixelNoise(pixelCoord, pixelSize);\n float noiseInfluence = (noiseValue - 0.5) * 0.4; // 增强噪声影响\n edgeThreshold += noiseInfluence;\n return normalizedDist > edgeThreshold ? 0.0 : 1.0;\n } else {\n // 平滑溶解:根据fadeEdge决定是否使用渐变\n if (u_fadeEdge) {\n // 柔和边缘:返回渐变值\n float fadeWidth = 0.15; // 渐变宽度\n return 1.0 - smoothstep(edgeThreshold - fadeWidth, edgeThreshold, normalizedDist);\n } else {\n // 硬边缘:返回0或1\n return normalizedDist > edgeThreshold ? 0.0 : 1.0;\n }\n }\n }\n\n // 向内溶解函数\n float inwardDissolve(vec2 uv, float time, float pixelSize, vec2 resolution) {\n vec2 center = vec2(0.5, 0.5);\n float distFromCenter = length(uv - center);\n float maxDist = length(vec2(0.5, 0.5));\n\n float normalizedDist = distFromCenter / maxDist;\n\n // 向内溶解:从中心开始溶解,time控制溶解进度\n // 增加系数,确保动画结束时完全溶解\n float centerThreshold = time * 1.4;\n\n // 当pixelSize > 0时添加颗粒效果\n if (pixelSize > 0.0) {\n vec2 pixelCoord = uv * resolution;\n float noiseValue = pixelNoise(pixelCoord, pixelSize);\n float noiseInfluence = (noiseValue - 0.5) * 0.4;\n centerThreshold += noiseInfluence;\n return normalizedDist < centerThreshold ? 0.0 : 1.0;\n } else {\n // 平滑溶解:根据fadeEdge决定是否使用渐变\n if (u_fadeEdge) {\n // 柔和边缘:返回渐变值\n float fadeWidth = 0.15; // 渐变宽度\n return smoothstep(centerThreshold, centerThreshold + fadeWidth, normalizedDist);\n } else {\n // 硬边缘:返回0或1\n return normalizedDist < centerThreshold ? 0.0 : 1.0;\n }\n }\n }\n\n // 径向溶解函数\n float radialDissolve(vec2 uv, float time, float pixelSize, vec2 resolution) {\n vec2 center = vec2(0.5, 0.5);\n float angle = atan(uv.y - center.y, uv.x - center.x);\n float normalizedAngle = (angle + 3.14159) / (2.0 * 3.14159);\n\n // 径向溶解:按角度顺序溶解,time控制溶解进度\n // 增加系数,确保动画结束时完全溶解\n float angleThreshold = time * 1.2;\n\n // 当pixelSize > 0时添加颗粒效果\n if (pixelSize > 0.0) {\n vec2 pixelCoord = uv * resolution;\n float noiseValue = pixelNoise(pixelCoord, pixelSize);\n float noiseInfluence = (noiseValue - 0.5) * 0.3;\n angleThreshold += noiseInfluence;\n return normalizedAngle < angleThreshold ? 0.0 : 1.0;\n } else {\n // 平滑溶解:根据fadeEdge决定是否使用渐变\n if (u_fadeEdge) {\n // 柔和边缘:返回渐变值\n float fadeWidth = 0.08; // 渐变宽度\n return smoothstep(angleThreshold, angleThreshold + fadeWidth, normalizedAngle);\n } else {\n // 硬边缘:返回0或1\n return normalizedAngle < angleThreshold ? 0.0 : 1.0;\n }\n }\n }\n\n // 从左到右溶解函数\n float leftToRightDissolve(vec2 uv, float time, float pixelSize, vec2 resolution) {\n // 左到右溶解:从x=0开始向x=1溶解\n float dissolvePosition = time * 1.2; // 增加系数确保完全溶解\n\n // 当pixelSize > 0时添加颗粒效果\n if (pixelSize > 0.0) {\n vec2 pixelCoord = uv * resolution;\n float noiseValue = pixelNoise(pixelCoord, pixelSize);\n float noiseInfluence = (noiseValue - 0.5) * 0.3;\n dissolvePosition += noiseInfluence;\n return uv.x < dissolvePosition ? 0.0 : 1.0;\n } else {\n // 平滑溶解:根据fadeEdge决定是否使用渐变\n if (u_fadeEdge) {\n // 柔和边缘:返回渐变值\n float fadeWidth = 0.08; // 渐变宽度\n return smoothstep(dissolvePosition, dissolvePosition + fadeWidth, uv.x);\n } else {\n // 硬边缘:返回0或1\n return uv.x < dissolvePosition ? 0.0 : 1.0;\n }\n }\n }\n\n // 从右到左溶解函数\n float rightToLeftDissolve(vec2 uv, float time, float pixelSize, vec2 resolution) {\n // 右到左溶解:从x=1开始向x=0溶解\n float dissolvePosition = 1.0 - time * 1.2; // 增加系数确保完全溶解\n\n // 当pixelSize > 0时添加颗粒效果\n if (pixelSize > 0.0) {\n vec2 pixelCoord = uv * resolution;\n float noiseValue = pixelNoise(pixelCoord, pixelSize);\n float noiseInfluence = (noiseValue - 0.5) * 0.3;\n dissolvePosition += noiseInfluence;\n return uv.x > dissolvePosition ? 0.0 : 1.0;\n } else {\n // 平滑溶解:根据fadeEdge决定是否使用渐变\n if (u_fadeEdge) {\n // 柔和边缘:返回渐变值\n float fadeWidth = 0.08; // 渐变宽度\n return smoothstep(dissolvePosition - fadeWidth, dissolvePosition, uv.x);\n } else {\n // 硬边缘:返回0或1\n return uv.x > dissolvePosition ? 0.0 : 1.0;\n }\n }\n }\n\n // 从上到下溶解函数\n float topToBottomDissolve(vec2 uv, float time, float pixelSize, vec2 resolution) {\n // 上到下溶解:从y=0开始向y=1溶解\n float dissolvePosition = time * 1.2; // 增加系数确保完全溶解\n\n // 当pixelSize > 0时添加颗粒效果\n if (pixelSize > 0.0) {\n vec2 pixelCoord = uv * resolution;\n float noiseValue = pixelNoise(pixelCoord, pixelSize);\n float noiseInfluence = (noiseValue - 0.5) * 0.3;\n dissolvePosition += noiseInfluence;\n return uv.y < dissolvePosition ? 0.0 : 1.0;\n } else {\n // 平滑溶解:根据fadeEdge决定是否使用渐变\n if (u_fadeEdge) {\n // 柔和边缘:返回渐变值\n float fadeWidth = 0.08; // 渐变宽度\n return smoothstep(dissolvePosition, dissolvePosition + fadeWidth, uv.y);\n } else {\n // 硬边缘:返回0或1\n return uv.y < dissolvePosition ? 0.0 : 1.0;\n }\n }\n }\n\n // 从下到上溶解函数\n float bottomToTopDissolve(vec2 uv, float time, float pixelSize, vec2 resolution) {\n // 下到上溶解:从y=1开始向y=0溶解\n float dissolvePosition = 1.0 - time * 1.2; // 增加系数确保完全溶解\n\n // 当pixelSize > 0时添加颗粒效果\n if (pixelSize > 0.0) {\n vec2 pixelCoord = uv * resolution;\n float noiseValue = pixelNoise(pixelCoord, pixelSize);\n float noiseInfluence = (noiseValue - 0.5) * 0.3;\n dissolvePosition += noiseInfluence;\n return uv.y > dissolvePosition ? 0.0 : 1.0;\n } else {\n // 平滑溶解:根据fadeEdge决定是否使用渐变\n if (u_fadeEdge) {\n // 柔和边缘:返回渐变值\n float fadeWidth = 0.08; // 渐变宽度\n return smoothstep(dissolvePosition - fadeWidth, dissolvePosition, uv.y);\n } else {\n // 硬边缘:返回0或1\n return uv.y > dissolvePosition ? 0.0 : 1.0;\n }\n }\n }\n\n void main() {\n vec2 uv = v_texCoord;\n vec4 texColor = texture2D(u_texture, uv);\n\n float alpha = 1.0;\n\n // 根据溶解类型选择对应的溶解函数\n if (u_dissolveType == 0) {\n alpha = outwardDissolve(uv, u_time, u_noiseScale, u_resolution);\n } else if (u_dissolveType == 1) {\n alpha = inwardDissolve(uv, u_time, u_noiseScale, u_resolution);\n } else if (u_dissolveType == 2) {\n alpha = radialDissolve(uv, u_time, u_noiseScale, u_resolution);\n } else if (u_dissolveType == 3) {\n alpha = leftToRightDissolve(uv, u_time, u_noiseScale, u_resolution);\n } else if (u_dissolveType == 4) {\n alpha = rightToLeftDissolve(uv, u_time, u_noiseScale, u_resolution);\n } else if (u_dissolveType == 5) {\n alpha = topToBottomDissolve(uv, u_time, u_noiseScale, u_resolution);\n } else if (u_dissolveType == 6) {\n alpha = bottomToTopDissolve(uv, u_time, u_noiseScale, u_resolution);\n }\n\n gl_FragColor = vec4(texColor.rgb, texColor.a * alpha);\n }\n ` }; } applyWebGLEffect(canvas) { if (!this.gl || !this.program || !this.webglCanvas) return canvas; this.setupWebGLState(canvas); const texture = this.createTextureFromCanvas(canvas); if (!texture) return canvas; this.noiseData || (this.noiseData = ImageProcessUtils.generateNoiseTexture(256, 256)); const noiseTexture = this.gl.createTexture(); this.gl.activeTexture(this.gl.TEXTURE1), this.gl.bindTexture(this.gl.TEXTURE_2D, noiseTexture), this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.LUMINANCE, 256, 256, 0, this.gl.LUMINANCE, this.gl.UNSIGNED_BYTE, this.noiseData), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.REPEAT), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.REPEAT), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR), this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR); const vertexBuffer = this.createFullScreenQuad(); return vertexBuffer ? (this.gl.useProgram(this.program), this.setupVertexAttributes(), this.setUniforms(), this.gl.enable(this.gl.BLEND), this.gl.blendFunc(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA), this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4), this.gl.deleteTexture(texture), this.gl.deleteTexture(noiseTexture), this.gl.deleteBuffer(vertexBuffer), this.webglCanvas) : canvas; } setUniforms() { if (!this.gl || !this.program || !this.webglCanvas) return; const textureLocation = this.gl.getUniformLocation(this.program, "u_texture"), noiseTextureLocation = this.gl.getUniformLocation(this.program, "u_noiseTexture"), timeLocation = this.gl.getUniformLocation(this.program, "u_time"), dissolveTypeLocation = this.gl.getUniformLocation(this.program, "u_dissolveType"), resolutionLocation = this.gl.getUniformLocation(this.program, "u_resolution"), noiseScaleLocation = this.gl.getUniformLocation(this.program, "u_noiseScale"), fadeEdgeLocation = this.gl.getUniformLocation(this.program, "u_fadeEdge"); this.gl.uniform1i(textureLocation, 0), this.gl.uniform1i(noiseTextureLocation, 1), this.gl.uniform1f(timeLocation, this.currentAnimationRatio), this.gl.uniform2f(resolutionLocation, this.webglCanvas.width, this.webglCanvas.height), this.gl.uniform1f(noiseScaleLocation, this.dissolveConfig.noiseScale), this.gl.uniform1i(fadeEdgeLocation, this.dissolveConfig.fadeEdge ? 1 : 0); this.gl.uniform1i(dissolveTypeLocation, { outward: 0, inward: 1, radial: 2, leftToRight: 3, rightToLeft: 4, topToBottom: 5, bottomToTop: 6 }[this.dissolveConfig.dissolveType] || 0); } applyCanvas2DEffect(canvas) { const outputCanvas = this.createOutputCanvas(canvas); if (!outputCanvas) return canvas; const {canvas: outputCanvasElement, ctx: ctx} = outputCanvas, imageData = ctx.getImageData(0, 0, canvas.width, canvas.height), progress = this.currentAnimationRatio; let dissolvedImageData; switch (this.dissolveConfig.dissolveType) { case "outward": dissolvedImageData = this.applyOutwardDissolve(imageData, progress); break; case "inward": dissolvedImageData = this.applyInwardDissolve(imageData, progress); break; case "radial": dissolvedImageData = this.applyRadialDissolve(imageData, progress); break; case "leftToRight": dissolvedImageData = this.applyLeftToRightDissolve(imageData, progress); break; case "rightToLeft": dissolvedImageData = this.applyRightToLeftDissolve(imageData, progress); break; case "topToBottom": dissolvedImageData = this.applyTopToBottomDissolve(imageData, progress); break; case "bottomToTop": dissolvedImageData = this.applyBottomToTopDissolve(imageData, progress); break; default: dissolvedImageData = imageData; } return ctx.putImageData(dissolvedImageData, 0, 0), outputCanvasElement; } applyOutwardDissolve(imageData, progress) { const {data: data, width: width, height: height} = imageData, result = new Uint8ClampedArray(data.length); result.set(data); const centerX = width / 2, centerY = height / 2, maxDist = Math.sqrt(centerX * centerX + centerY * centerY), pixelSize = this.dissolveConfig.noiseScale; for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { const dx = x - centerX, dy = y - centerY, normalizedDist = Math.sqrt(dx * dx + dy * dy) / maxDist; let dissolveThreshold = 1.2 - 1.4 * progress, alpha = 1; if (pixelSize > 0) { dissolveThreshold += .4 * (ImageProcessUtils.pixelNoise(x, y, pixelSize) - .5), alpha = normalizedDist > dissolveThreshold ? 0 : 1; } else if (this.dissolveConfig.fadeEdge) { const fadeStart = dissolveThreshold - .15; alpha = normalizedDist < fadeStart ? 1 : normalizedDist > dissolveThreshold ? 0 : 1 - (normalizedDist - fadeStart) / (dissolveThreshold - fadeStart); } else alpha = normalizedDist > dissolveThreshold ? 0 : 1; const index = 4 * (y * width + x); result[index + 3] = Math.floor(result[index + 3] * alpha); } return new ImageData(result, width, height); } applyInwardDissolve(imageData, progress) { const {data: data, width: width, height: height} = imageData, result = new Uint8ClampedArray(data.length); result.set(data); const centerX = width / 2, centerY = height / 2, maxDist = Math.sqrt(centerX * centerX + centerY * centerY), pixelSize = this.dissolveConfig.noiseScale; for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { const dx = x - centerX, dy = y - centerY, normalizedDist = Math.sqrt(dx * dx + dy * dy) / maxDist; let dissolveThreshold = 1.4 * progress, alpha = 1; if (pixelSize > 0) { dissolveThreshold += .4 * (ImageProcessUtils.pixelNoise(x, y, pixelSize) - .5), alpha = normalizedDist < dissolveThreshold ? 0 : 1; } else if (this.dissolveConfig.fadeEdge) { const fadeEnd = dissolveThreshold + .15; alpha = normalizedDist < dissolveThreshold ? 0 : normalizedDist > fadeEnd ? 1 : (normalizedDist - dissolveThreshold) / (fadeEnd - dissolveThreshold); } else alpha = normalizedDist < dissolveThreshold ? 0 : 1; const index = 4 * (y * width + x); result[index + 3] = Math.floor(result[index + 3] * alpha); } return new ImageData(result, width, height); } applyRadialDissolve(imageData, progress) { const {data: data, width: width, height: height} = imageData, result = new Uint8ClampedArray(data.length); result.set(data); const centerX = width / 2, centerY = height / 2, pixelSize = this.dissolveConfig.noiseScale; for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { const dx = x - centerX, dy = y - centerY, normalizedAngle = (Math.atan2(dy, dx) + Math.PI) / (2 * Math.PI); let dissolveThreshold = 1.2 * progress, alpha = 1; if (pixelSize > 0) { dissolveThreshold += .3 * (ImageProcessUtils.pixelNoise(x, y, pixelSize) - .5), alpha = normalizedAngle < dissolveThreshold ? 0 : 1; } else if (this.dissolveConfig.fadeEdge) { const fadeEnd = dissolveThreshold + .08; alpha = normalizedAngle < dissolveThreshold ? 0 : normalizedAngle > fadeEnd ? 1 : (normalizedAngle - dissolveThreshold) / (fadeEnd - dissolveThreshold); } else alpha = normalizedAngle < dissolveThreshold ? 0 : 1; const index = 4 * (y * width + x); result[index + 3] = Math.floor(result[index + 3] * alpha); } return new ImageData(result, width, height); } applyLeftToRightDissolve(imageData, progress) { const {data: data, width: width, height: height} = imageData, result = new Uint8ClampedArray(data.length); result.set(data); const pixelSize = this.dissolveConfig.noiseScale; for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { const normalizedX = x / width; let dissolveThreshold = 1.2 * progress, alpha = 1; if (pixelSize > 0) { dissolveThreshold += .3 * (ImageProcessUtils.pixelNoise(x, y, pixelSize) - .5), alpha = normalizedX < dissolveThreshold ? 0 : 1; } else if (this.dissolveConfig.fadeEdge) { const fadeEnd = dissolveThreshold + .08; alpha = normalizedX < dissolveThreshold ? 0 : normalizedX > fadeEnd ? 1 : (normalizedX - dissolveThreshold) / (fadeEnd - dissolveThreshold); } else alpha = normalizedX < dissolveThreshold ? 0 : 1; const index = 4 * (y * width + x); result[index + 3] = Math.floor(result[index + 3] * alpha); } return new ImageData(result, width, height); } applyRightToLeftDissolve(imageData, progress) { const {data: data, width: width, height: height} = imageData, result = new Uint8ClampedArray(data.length); result.set(data); const pixelSize = this.dissolveConfig.noiseScale; for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { const normalizedX = x / width; let dissolveThreshold = 1 - 1.2 * progress, alpha = 1; if (pixelSize > 0) { dissolveThreshold += .3 * (ImageProcessUtils.pixelNoise(x, y, pixelSize) - .5), alpha = normalizedX > dissolveThreshold ? 0 : 1; } else if (this.dissolveConfig.fadeEdge) { const fadeStart = dissolveThreshold - .08; alpha = normalizedX < fadeStart ? 1 : normalizedX > dissolveThreshold ? 0 : 1 - (normalizedX - fadeStart) / (dissolveThreshold - fadeStart); } else alpha = normalizedX > dissolveThreshold ? 0 : 1; const index = 4 * (y * width + x); result[index + 3] = Math.floor(result[index + 3] * alpha); } return new ImageData(result, width, height); } applyTopToBottomDissolve(imageData, progress) { const {data: data, width: width, height: height} = imageData, result = new Uint8ClampedArray(data.length); result.set(data); const pixelSize = this.dissolveConfig.noiseScale; for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { const normalizedY = y / height; let dissolveThreshold = 1.2 * progress, alpha = 1; if (pixelSize > 0) { dissolveThreshold += .3 * (ImageProcessUtils.pixelNoise(x, y, pixelSize) - .5), alpha = normalizedY < dissolveThreshold ? 0 : 1; } else if (this.dissolveConfig.fadeEdge) { const fadeEnd = dissolveThreshold + .08; alpha = normalizedY < dissolveThreshold ? 0 : normalizedY > fadeEnd ? 1 : (normalizedY - dissolveThreshold) / (fadeEnd - dissolveThreshold); } else alpha = normalizedY < dissolveThreshold ? 0 : 1; const index = 4 * (y * width + x); result[index + 3] = Math.floor(result[index + 3] * alpha); } return new ImageData(result, width, height); } applyBottomToTopDissolve(imageData, progress) { const {data: data, width: width, height: height} = imageData, result = new Uint8ClampedArray(data.length); result.set(data); const pixelSize = this.dissolveConfig.noiseScale; for (let y = 0; y < height; y++) for (let x = 0; x < width; x++) { const normalizedY = y / height; let dissolveThreshold = 1 - 1.2 * progress, alpha = 1; if (pixelSize > 0) { dissolveThreshold += .3 * (ImageProcessUtils.pixelNoise(x, y, pixelSize) - .5), alpha = normalizedY > dissolveThreshold ? 0 : 1; } else if (this.dissolveConfig.fadeEdge) { const fadeStart = dissolveThreshold - .08; alpha = normalizedY < fadeStart ? 1 : normalizedY > dissolveThreshold ? 0 : 1 - (normalizedY - fadeStart) / (dissolveThreshold - fadeStart); } else alpha = normalizedY > dissolveThreshold ? 0 : 1; const index = 4 * (y * width + x); result[index + 3] = Math.floor(result[index + 3] * alpha); } return new ImageData(result, width, height); } } //# sourceMappingURL=dissolve.js.map