/** * WebGL版本的液化管理器 - 暂时不兼容WebGL的版本 后续看情况支持 * 使用GPU加速进行高性能液化变形处理,支持持续按压效果 */ export class LiquifyWebGLManager { constructor(options = {}) { this.config = { meshResolution: options.meshResolution || 64, // WebGL网格精度 maxStrength: options.maxStrength || 100, useFloatTextures: options.useFloatTextures !== false, // 默认启用浮点纹理 }; this.params = { size: 50, pressure: 0.5, distortion: 0, power: 0.5, }; this.modes = { PUSH: "push", CLOCKWISE: "clockwise", COUNTERCLOCKWISE: "counterclockwise", PINCH: "pinch", EXPAND: "expand", CRYSTAL: "crystal", EDGE: "edge", RECONSTRUCT: "reconstruct", }; this.currentMode = this.modes.PUSH; this.initialized = false; // WebGL相关 this.canvas = null; this.gl = null; this.program = null; this.meshBuffer = null; this.originalTexture = null; this.currentTexture = null; this.framebuffer = null; // 持续按压相关状态 this.pressStartTime = 0; this.pressDuration = 0; this.accumulatedRotation = 0; this.accumulatedScale = 0; this.isHolding = false; this.continuousTimer = null; this.continuousApplyInterval = 50; // 50ms间隔 // 初始点坐标 this.initialMouseX = 0; this.initialMouseY = 0; this.isDragging = false; } /** * 检查WebGL支持 */ static isSupported() { try { const canvas = document.createElement("canvas"); const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); return !!gl; } catch (e) { return false; } } /** * 初始化WebGL液化管理器 */ initialize(imageSource) { try { // 创建WebGL画布 this.canvas = document.createElement("canvas"); this.gl = this.canvas.getContext("webgl") || this.canvas.getContext("experimental-webgl"); if (!this.gl) { throw new Error("WebGL不可用"); } // 处理图像源 if (imageSource instanceof HTMLImageElement) { this.canvas.width = imageSource.width; this.canvas.height = imageSource.height; this._initWebGL(imageSource); } else { throw new Error("WebGL版本目前只支持HTMLImageElement"); } this.initialized = true; return true; } catch (error) { console.error("WebGL液化管理器初始化失败:", error); return false; } } /** * 初始化WebGL环境 */ _initWebGL(image) { const gl = this.gl; // 创建着色器程序 this._createShaderProgram(); // 创建纹理 this.originalTexture = this._createTexture(image); this.currentTexture = this._createTexture(image); // 创建帧缓冲 this.framebuffer = gl.createFramebuffer(); // 创建网格缓冲 this._createMeshBuffer(); console.log("WebGL液化环境初始化完成"); } /** * 创建着色器程序 */ _createShaderProgram() { const gl = this.gl; // 顶点着色器 const vertexShaderSource = ` attribute vec2 a_position; attribute vec2 a_texCoord; varying vec2 v_texCoord; void main() { gl_Position = vec4(a_position, 0.0, 1.0); v_texCoord = a_texCoord; } `; // 片段着色器 - 支持多种液化模式 const fragmentShaderSource = ` precision mediump float; uniform sampler2D u_texture; uniform vec2 u_center; uniform float u_radius; uniform float u_strength; uniform int u_mode; uniform float u_time; uniform float u_distortion; uniform vec2 u_dragVector; varying vec2 v_texCoord; void main() { vec2 coord = v_texCoord; vec2 center = u_center; float dist = distance(coord, center); if (dist < u_radius && dist > 0.0) { float factor = 1.0 - (dist / u_radius); factor = factor * factor * factor * u_strength; if (u_mode == 0) { // PUSH vec2 direction = normalize(u_dragVector); coord += direction * factor * 0.1; } else if (u_mode == 1 || u_mode == 2) { // CLOCKWISE/COUNTERCLOCKWISE float angle = atan(coord.y - center.y, coord.x - center.x); float rotationSpeed = u_mode == 1 ? 0.1 : -0.1; angle += rotationSpeed * factor * (1.0 + u_time * 0.001); coord = center + dist * vec2(cos(angle), sin(angle)); } else if (u_mode == 3) { // PINCH vec2 direction = center - coord; coord += direction * factor * (0.5 + u_time * 0.0005); } else if (u_mode == 4) { // EXPAND vec2 direction = coord - center; coord += direction * factor * (0.5 + u_time * 0.0005); } else if (u_mode == 5) { // CRYSTAL float crystalAngle = atan(coord.y - center.y, coord.x - center.x); float wave1 = sin(crystalAngle * 8.0 + u_time * 0.005) * 0.6; float wave2 = cos(crystalAngle * 12.0 + u_time * 0.003) * 0.4; crystalAngle += (wave1 + wave2) * u_distortion * factor; coord = center + dist * vec2(cos(crystalAngle), sin(crystalAngle)); } else if (u_mode == 6) { // EDGE float edgeAngle = atan(coord.y - center.y, coord.x - center.x); float edgeWave = sin(dist * 3.14159 * 4.0 + u_time * 0.004) * cos(edgeAngle * 6.0 + u_time * 0.002); float perpAngle = edgeAngle + 3.14159 * 0.5; coord += vec2(cos(perpAngle), sin(perpAngle)) * edgeWave * factor * u_distortion * 0.5; } } gl_FragColor = texture2D(u_texture, coord); } `; this.program = this._createProgram(vertexShaderSource, fragmentShaderSource); } /** * 创建着色器 */ _createShader(type, source) { 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("着色器编译失败: " + gl.getShaderInfoLog(shader)); } return shader; } /** * 创建着色器程序 */ _createProgram(vertexSource, fragmentSource) { const gl = this.gl; const vertexShader = this._createShader(gl.VERTEX_SHADER, vertexSource); const fragmentShader = this._createShader(gl.FRAGMENT_SHADER, fragmentSource); const program = gl.createProgram(); gl.attachShader(program, vertexShader); gl.attachShader(program, fragmentShader); gl.linkProgram(program); if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { throw new Error("程序链接失败: " + gl.getProgramInfoLog(program)); } return program; } /** * 创建纹理 */ _createTexture(image) { const gl = this.gl; const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); return texture; } /** * 创建网格缓冲 */ _createMeshBuffer() { const gl = this.gl; const vertices = new Float32Array([-1, -1, 0, 0, 1, -1, 1, 0, -1, 1, 0, 1, 1, 1, 1, 1]); this.meshBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this.meshBuffer); gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); } /** * 设置液化模式 */ setMode(mode) { if (Object.values(this.modes).includes(mode)) { this.currentMode = mode; return true; } return false; } /** * 设置参数 */ setParam(param, value) { if (param in this.params) { this.params[param] = value; return true; } return false; } /** * 获取参数 */ getParams() { return { ...this.params }; } /** * 开始液化操作 */ startDeformation(x, y) { this.initialMouseX = x; this.initialMouseY = y; this.isDragging = true; // 初始化持续按压状态 this.pressStartTime = Date.now(); this.pressDuration = 0; this.accumulatedRotation = 0; this.accumulatedScale = 0; this.isHolding = true; // 启动持续效果定时器 this.startContinuousEffect(); console.log(`WebGL液化开始,初始点: (${x}, ${y})`); } /** * 结束液化操作 */ endDeformation() { this.isDragging = false; this.isHolding = false; this.pressStartTime = 0; this.pressDuration = 0; this.accumulatedRotation = 0; this.accumulatedScale = 0; // 停止持续效果定时器 this.stopContinuousEffect(); console.log("WebGL液化结束"); } /** * 启动持续效果 */ startContinuousEffect() { this.stopContinuousEffect(); this.continuousTimer = setInterval(() => { if (this.isHolding && this.initialized) { this.pressDuration = Date.now() - this.pressStartTime; this.applyContinuousDeformation(); } }, this.continuousApplyInterval); } /** * 停止持续效果 */ stopContinuousEffect() { if (this.continuousTimer) { clearInterval(this.continuousTimer); this.continuousTimer = null; } } /** * 应用持续变形效果 */ applyContinuousDeformation() { if (!this.isHolding || !this.initialized) return; return this.applyDeformation(this.initialMouseX, this.initialMouseY); } /** * 应用液化变形 */ applyDeformation(x, y) { if (!this.initialized || !this.gl) return null; const gl = this.gl; const { size, pressure, power, distortion } = this.params; // 设置视口 gl.viewport(0, 0, this.canvas.width, this.canvas.height); // 使用着色器程序 gl.useProgram(this.program); // 绑定属性 const positionLoc = gl.getAttribLocation(this.program, "a_position"); const texCoordLoc = gl.getAttribLocation(this.program, "a_texCoord"); gl.bindBuffer(gl.ARRAY_BUFFER, this.meshBuffer); gl.enableVertexAttribArray(positionLoc); gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 16, 0); gl.enableVertexAttribArray(texCoordLoc); gl.vertexAttribPointer(texCoordLoc, 2, gl.FLOAT, false, 16, 8); // 设置uniform变量 const centerLoc = gl.getUniformLocation(this.program, "u_center"); const radiusLoc = gl.getUniformLocation(this.program, "u_radius"); const strengthLoc = gl.getUniformLocation(this.program, "u_strength"); const modeLoc = gl.getUniformLocation(this.program, "u_mode"); const timeLoc = gl.getUniformLocation(this.program, "u_time"); const distortionLoc = gl.getUniformLocation(this.program, "u_distortion"); const dragVectorLoc = gl.getUniformLocation(this.program, "u_dragVector"); // 计算参数 const centerX = x / this.canvas.width; const centerY = 1.0 - y / this.canvas.height; // WebGL坐标系Y轴翻转 const radius = (size / Math.max(this.canvas.width, this.canvas.height)) * 2; // 计算强度,考虑持续按压效果 let strength = pressure * power; if (this.isHolding) { const timeFactor = Math.min(this.pressDuration / 1000, 3.0); if (this.currentMode === this.modes.PUSH) { strength *= 0.2 + timeFactor * 0.3; } else { strength *= 0.5 + timeFactor * 0.5; } } // 计算拖拽向量(用于推拉模式) const dragX = this.isDragging ? (x - this.initialMouseX) / this.canvas.width : 0; const dragY = this.isDragging ? -(y - this.initialMouseY) / this.canvas.height : 0; // 获取模式索引 const modeIndex = Object.values(this.modes).indexOf(this.currentMode); // 设置uniform值 gl.uniform2f(centerLoc, centerX, centerY); gl.uniform1f(radiusLoc, radius); gl.uniform1f(strengthLoc, strength); gl.uniform1i(modeLoc, modeIndex); gl.uniform1f(timeLoc, this.pressDuration); gl.uniform1f(distortionLoc, distortion); gl.uniform2f(dragVectorLoc, dragX, dragY); // 绑定纹理 gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, this.currentTexture); gl.uniform1i(gl.getUniformLocation(this.program, "u_texture"), 0); // 渲染到帧缓冲 gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.currentTexture, 0 ); // 绘制 gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); // 读取结果并转换为ImageData const pixels = new Uint8Array(this.canvas.width * this.canvas.height * 4); gl.readPixels(0, 0, this.canvas.width, this.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); // 翻转Y轴以匹配ImageData格式 const flippedPixels = new Uint8ClampedArray(pixels.length); for (let y = 0; y < this.canvas.height; y++) { for (let x = 0; x < this.canvas.width; x++) { const srcIndex = ((this.canvas.height - 1 - y) * this.canvas.width + x) * 4; const dstIndex = (y * this.canvas.width + x) * 4; flippedPixels[dstIndex] = pixels[srcIndex]; flippedPixels[dstIndex + 1] = pixels[srcIndex + 1]; flippedPixels[dstIndex + 2] = pixels[srcIndex + 2]; flippedPixels[dstIndex + 3] = pixels[srcIndex + 3]; } } return new ImageData(flippedPixels, this.canvas.width, this.canvas.height); } /** * 重置到原始状态 */ reset() { if (!this.initialized) return null; const gl = this.gl; // 将原始纹理复制到当前纹理 gl.bindTexture(gl.TEXTURE_2D, this.currentTexture); gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, this.canvas.width, this.canvas.height, 0); // 读取原始纹理数据 gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer); gl.framebufferTexture2D( gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.originalTexture, 0 ); const pixels = new Uint8Array(this.canvas.width * this.canvas.height * 4); gl.readPixels(0, 0, this.canvas.width, this.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels); return new ImageData(new Uint8ClampedArray(pixels), this.canvas.width, this.canvas.height); } /** * 重置参数为默认值 */ resetParams() { this.params = { size: 50, pressure: 0.5, distortion: 0, power: 0.5, }; } /** * 释放资源 */ dispose() { if (this.gl) { if (this.program) this.gl.deleteProgram(this.program); if (this.meshBuffer) this.gl.deleteBuffer(this.meshBuffer); if (this.originalTexture) this.gl.deleteTexture(this.originalTexture); if (this.currentTexture) this.gl.deleteTexture(this.currentTexture); if (this.framebuffer) this.gl.deleteFramebuffer(this.framebuffer); } this.stopContinuousEffect(); this.initialized = false; this.canvas = null; this.gl = null; console.log("WebGL液化管理器资源已释放"); } }