/** * WebGL加速的液化管理器 * 使用WebGL技术进行加速液化变形处理 */ export class LiquifyWebGLManager { /** * 创建WebGL液化管理器 * @param {Object} options 配置选项 */ constructor(options = {}) { this.canvas = null; this.gl = null; this.program = null; this.texture = null; this.mesh = null; this.initialized = false; this.originalImageData = null; this.currentImageData = null; // 变形配置 this.config = { gridSize: options.gridSize || 20, maxStrength: options.maxStrength || 100, textureSize: 0, meshResolution: options.meshResolution || 64, }; // 当前参数 this.params = { size: 80, // 增大默认尺寸 pressure: 0.8, // 增大默认压力 distortion: 0, power: 0.8, // 增大默认动力 }; // 鼠标位置跟踪(用于推拉模式) this.lastMouseX = 0; this.lastMouseY = 0; this.mouseMovementX = 0; this.mouseMovementY = 0; this.isFirstApply = true; // 标记是否是首次应用 // 液化工具模式 this.modes = { PUSH: "push", CLOCKWISE: "clockwise", COUNTERCLOCKWISE: "counterclockwise", PINCH: "pinch", EXPAND: "expand", CRYSTAL: "crystal", EDGE: "edge", RECONSTRUCT: "reconstruct", }; this.currentMode = this.modes.PUSH; // 变形点历史记录 this.deformHistory = []; // WebGL着色器程序 this.vertexShaderSource = ` attribute vec2 a_position; attribute vec2 a_texCoord; uniform mat3 u_matrix; varying vec2 v_texCoord; void main() { gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1); v_texCoord = a_texCoord; } `; this.fragmentShaderSource = ` precision mediump float; uniform sampler2D u_image; uniform vec2 u_textureSize; varying vec2 v_texCoord; void main() { vec2 onePixel = vec2(1.0, 1.0) / u_textureSize; vec4 color = texture2D(u_image, v_texCoord); // 简单的边缘检查,保证边缘渲染正确 if(v_texCoord.x < 0.0 || v_texCoord.x > 1.0 || v_texCoord.y < 0.0 || v_texCoord.y > 1.0) { gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0); } else { gl_FragColor = color; } } `; // 变形网格着色器程序 this.deformVertexShaderSource = ` attribute vec2 a_position; attribute vec2 a_texCoord; attribute vec2 a_deformation; varying vec2 v_texCoord; void main() { vec2 position = a_position + a_deformation; gl_Position = vec4(position * 2.0 - 1.0, 0, 1); v_texCoord = a_texCoord; } `; this.deformFragmentShaderSource = ` precision mediump float; uniform sampler2D u_image; varying vec2 v_texCoord; void main() { vec4 color = texture2D(u_image, v_texCoord); gl_FragColor = color; } `; } /** * 初始化WebGL环境 * @param {HTMLImageElement} image 图像元素 * @returns {Boolean} 是否初始化成功 */ initialize(image) { // 创建WebGL Canvas this.canvas = document.createElement("canvas"); // 设置canvas大小与图像相同 this.canvas.width = image.width; this.canvas.height = image.height; // 尝试获取WebGL上下文 try { this.gl = this.canvas.getContext("webgl") || this.canvas.getContext("experimental-webgl"); } catch (e) { console.error("WebGL初始化失败:", e); return false; } if (!this.gl) { console.error("WebGL不可用"); return false; } // 设置视口 this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); // 编译着色器程序 if (!this._createShaderProgram()) { console.error("着色器程序创建失败"); return false; } // 创建纹理 this.texture = this._createTexture(image); if (!this.texture) { console.error("纹理创建失败"); return false; } // 记录原始图像数据 const tempCanvas = document.createElement("canvas"); tempCanvas.width = image.width; tempCanvas.height = image.height; const tempCtx = tempCanvas.getContext("2d"); tempCtx.drawImage(image, 0, 0); this.originalImageData = tempCtx.getImageData( 0, 0, image.width, image.height ); this.currentImageData = new ImageData( new Uint8ClampedArray(this.originalImageData.data), this.originalImageData.width, this.originalImageData.height ); // 创建变形网格 this._createDeformMesh(); this.config.textureSize = [image.width, image.height]; this.initialized = true; return true; } /** * 创建着色器程序 * @returns {Boolean} 是否创建成功 * @private */ _createShaderProgram() { // 创建标准渲染程序 const vertexShader = this._compileShader( this.vertexShaderSource, this.gl.VERTEX_SHADER ); const fragmentShader = this._compileShader( this.fragmentShaderSource, this.gl.FRAGMENT_SHADER ); if (!vertexShader || !fragmentShader) return false; // 创建程序 this.program = this.gl.createProgram(); this.gl.attachShader(this.program, vertexShader); this.gl.attachShader(this.program, fragmentShader); this.gl.linkProgram(this.program); if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) { console.error( "着色器程序链接失败:", this.gl.getProgramInfoLog(this.program) ); return false; } // 创建变形渲染程序 const deformVertexShader = this._compileShader( this.deformVertexShaderSource, this.gl.VERTEX_SHADER ); const deformFragmentShader = this._compileShader( this.deformFragmentShaderSource, this.gl.FRAGMENT_SHADER ); if (!deformVertexShader || !deformFragmentShader) return false; // 创建变形程序 this.deformProgram = this.gl.createProgram(); this.gl.attachShader(this.deformProgram, deformVertexShader); this.gl.attachShader(this.deformProgram, deformFragmentShader); this.gl.linkProgram(this.deformProgram); if (!this.gl.getProgramParameter(this.deformProgram, this.gl.LINK_STATUS)) { console.error( "变形着色器程序链接失败:", this.gl.getProgramInfoLog(this.deformProgram) ); return false; } return true; } /** * 编译着色器 * @param {String} source 着色器源码 * @param {Number} type 着色器类型 * @returns {WebGLShader} 编译后的着色器 * @private */ _compileShader(source, type) { const shader = this.gl.createShader(type); this.gl.shaderSource(shader, source); this.gl.compileShader(shader); if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) { console.error( "着色器编译失败:", this.gl.getShaderInfoLog(shader), "shader type:", type === this.gl.VERTEX_SHADER ? "VERTEX_SHADER" : "FRAGMENT_SHADER", "source:", source ); this.gl.deleteShader(shader); return null; } return shader; } /** * 创建WebGL纹理 * @param {HTMLImageElement} image 图像元素 * @returns {WebGLTexture} WebGL纹理 * @private */ _createTexture(image) { const texture = this.gl.createTexture(); this.gl.bindTexture(this.gl.TEXTURE_2D, texture); // 设置参数,使我们可以渲染任何尺寸的图像 this.gl.texParameteri( this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE ); this.gl.texParameteri( this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE ); 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 ); // 上传图像到纹理 try { this.gl.texImage2D( this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, image ); } catch (e) { console.error("纹理上传失败:", e); return null; } return texture; } /** * 创建变形网格 * @private */ _createDeformMesh() { const { meshResolution } = this.config; // 创建网格顶点 const vertices = []; const texCoords = []; const indices = []; const deformations = []; // 创建顶点和纹理坐标 for (let y = 0; y <= meshResolution; y++) { for (let x = 0; x <= meshResolution; x++) { const xPos = x / meshResolution; const yPos = y / meshResolution; // 顶点位置 vertices.push(xPos, yPos); // 纹理坐标 texCoords.push(xPos, yPos); // 初始无变形 deformations.push(0, 0); } } // 创建索引(三角形) for (let y = 0; y < meshResolution; y++) { for (let x = 0; x < meshResolution; x++) { const i0 = y * (meshResolution + 1) + x; const i1 = i0 + 1; const i2 = i0 + meshResolution + 1; const i3 = i2 + 1; // 三角形1 indices.push(i0, i2, i1); // 三角形2 indices.push(i1, i2, i3); } } this.mesh = { vertices: new Float32Array(vertices), texCoords: new Float32Array(texCoords), indices: new Uint16Array(indices), deformations: new Float32Array(deformations), resolution: meshResolution, }; // 创建顶点缓冲区 this.vertexBuffer = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer); this.gl.bufferData( this.gl.ARRAY_BUFFER, this.mesh.vertices, this.gl.STATIC_DRAW ); // 创建纹理坐标缓冲区 this.texCoordBuffer = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer); this.gl.bufferData( this.gl.ARRAY_BUFFER, this.mesh.texCoords, this.gl.STATIC_DRAW ); // 创建变形缓冲区 this.deformBuffer = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.deformBuffer); this.gl.bufferData( this.gl.ARRAY_BUFFER, this.mesh.deformations, this.gl.DYNAMIC_DRAW ); // 创建索引缓冲区 this.indexBuffer = this.gl.createBuffer(); this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); this.gl.bufferData( this.gl.ELEMENT_ARRAY_BUFFER, this.mesh.indices, this.gl.STATIC_DRAW ); } /** * 应用液化变形 * @param {Number} x 变形中心X坐标 (图像像素坐标) * @param {Number} y 变形中心Y坐标 (图像像素坐标) */ applyDeformation(x, y) { if (!this.initialized || !this.mesh) return; // 计算鼠标移动方向 if (!this.isFirstApply) { this.mouseMovementX = x - this.lastMouseX; this.mouseMovementY = y - this.lastMouseY; } else { // 首次应用时不计算移动,避免初始变形 this.mouseMovementX = 0; this.mouseMovementY = 0; this.isFirstApply = false; } this.lastMouseX = x; this.lastMouseY = y; // 将图像像素坐标转换为纹理坐标 (0-1范围) // 使用原始图像数据的尺寸进行归一化,而不是WebGL canvas的尺寸 const imageWidth = this.originalImageData ? this.originalImageData.width : this.canvas.width; const imageHeight = this.originalImageData ? this.originalImageData.height : this.canvas.height; const tx = x / imageWidth; const ty = y / imageHeight; console.log( `WebGL变形: 像素坐标(${x}, ${y}) -> 纹理坐标(${tx.toFixed( 3 )}, ${ty.toFixed(3)}), 图像尺寸(${imageWidth}x${imageHeight})` ); // 获取当前参数 const { size, pressure, distortion, power } = this.params; const mode = this.currentMode; // 计算影响半径 (纹理坐标空间) const radius = (size / 100) * 0.2; // 调整半径计算,使效果更自然 const strength = (pressure * power * this.config.maxStrength) / 800; // 进一步降低基础强度 // 保存当前变形点,用于重建功能 this.deformHistory.push({ x: tx, y: ty, radius, strength, mode, distortion, }); // 对网格顶点应用变形 const { resolution } = this.mesh; const deformations = this.mesh.deformations; for (let i = 0; i <= resolution; i++) { for (let j = 0; j <= resolution; j++) { const idx = (i * (resolution + 1) + j) * 2; // 顶点在纹理空间中的位置 const vx = j / resolution; const vy = i / resolution; // 计算到变形中心的距离 const dx = vx - tx; const dy = vy - ty; const distance = Math.sqrt(dx * dx + dy * dy); // 只影响半径内的点 if (distance < radius) { // 计算影响因子 const factor = Math.pow(1 - distance / radius, 2) * strength; // 根据不同模式应用变形 switch (mode) { case this.modes.PUSH: // 推拉模式 - 真正的拖拽效果 // 计算鼠标移动距离(转换为纹理坐标空间) const movementX = this.mouseMovementX / imageWidth; const movementY = this.mouseMovementY / imageHeight; const movementLength = Math.sqrt( movementX * movementX + movementY * movementY ); // 只有在有足够移动距离时才应用效果 if (movementLength > 0.002) { // 提高阈值,确保有明显移动 // 归一化移动方向 const moveX = movementX / movementLength; const moveY = movementY / movementLength; // 计算衰减(距离中心越近,效果越强) const radiusRatio = distance / radius; const falloff = Math.pow(1 - radiusRatio, 2.0); // 使用更强的衰减 // 基于实际移动距离计算强度 const moveStrength = pressure * power * movementLength * 0.5; // 降低移动强度系数 // 计算最终拖拽强度 const dragStrength = moveStrength * falloff * factor; // 向鼠标移动方向拖拽 const dragX = moveX * dragStrength; const dragY = moveY * dragStrength; // 应用变形,但限制最大变形量 const maxDeform = 0.01; // 限制单次最大变形量(纹理坐标空间) deformations[idx] += Math.max( -maxDeform, Math.min(maxDeform, dragX) ); deformations[idx + 1] += Math.max( -maxDeform, Math.min(maxDeform, dragY) ); } break; case this.modes.CLOCKWISE: // 顺时针旋转 const angle = Math.atan2(dy, dx) + factor; const len = distance; deformations[idx] += Math.cos(angle) * len - dx; deformations[idx + 1] += Math.sin(angle) * len - dy; break; case this.modes.COUNTERCLOCKWISE: // 逆时针旋转 const angle2 = Math.atan2(dy, dx) - factor; const len2 = distance; deformations[idx] += Math.cos(angle2) * len2 - dx; deformations[idx + 1] += Math.sin(angle2) * len2 - dy; break; case this.modes.PINCH: // 捏合效果 - 向中心收缩 deformations[idx] -= dx * factor; deformations[idx + 1] -= dy * factor; break; case this.modes.EXPAND: // 展开效果 - 参考捏合算法的反向操作 const expandFactor = factor * 1.5; deformations[idx] += dx * expandFactor; deformations[idx + 1] += dy * expandFactor; break; case this.modes.CRYSTAL: // 水晶效果 - 参考旋转算法创建多重角度变形 const crystalAngle = Math.atan2(dy, dx); const crystalRadius = distance / radius; // 确保有基础效果 const baseDistortion = Math.max(distortion, 0.3); // 创建多重波形 - 类似旋转但加入波形调制 const wave1 = Math.sin(crystalAngle * 8) * 0.6; const wave2 = Math.cos(crystalAngle * 12) * 0.4; const waveAngle = crystalAngle + (wave1 + wave2) * baseDistortion; // 径向扭曲 - 类似旋转的距离调制 const radialMod = 1 + Math.sin(crystalRadius * Math.PI * 2) * 0.3; const modDistance = distance * radialMod; const crystalX = Math.cos(waveAngle) * modDistance; const crystalY = Math.sin(waveAngle) * modDistance; deformations[idx] += (crystalX - (tx + dx)) * factor; deformations[idx + 1] += (crystalY - (ty + dy)) * factor; break; case this.modes.EDGE: // 边缘效果 - 参考旋转算法创建垂直于径向的波纹 const edgeAngle = Math.atan2(dy, dx); const edgeRadius = distance / radius; // 确保有基础效果 const baseEdgeDistortion = Math.max(distortion, 0.5); // 创建边缘波纹 - 垂直于径向方向的调制 const edgeWave = Math.sin(edgeRadius * Math.PI * 4) * Math.cos(edgeAngle * 6); const perpAngle = edgeAngle + Math.PI / 2; // 垂直角度 const edgeFactor = edgeWave * factor * baseEdgeDistortion; const edgeX = Math.cos(perpAngle) * edgeFactor; const edgeY = Math.sin(perpAngle) * edgeFactor; deformations[idx] += edgeX; deformations[idx + 1] += edgeY; break; case this.modes.RECONSTRUCT: // 重建 - 向原始位置恢复 deformations[idx] *= 0.9; deformations[idx + 1] *= 0.9; break; } } } } // 更新变形缓冲区 this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.deformBuffer); this.gl.bufferData( this.gl.ARRAY_BUFFER, deformations, this.gl.DYNAMIC_DRAW ); // 重新渲染 this._render(); // 更新当前图像数据 this.currentImageData = this._getImageData(); return this.currentImageData; } /** * 渲染变形后的图像 * @private */ _render() { if (!this.initialized) return; // 清除画布 this.gl.clearColor(0, 0, 0, 0); this.gl.clear(this.gl.COLOR_BUFFER_BIT); // 使用变形程序 this.gl.useProgram(this.deformProgram); // 设置纹理 this.gl.activeTexture(this.gl.TEXTURE0); this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture); const u_image = this.gl.getUniformLocation(this.deformProgram, "u_image"); this.gl.uniform1i(u_image, 0); // 设置顶点位置属性 const a_position = this.gl.getAttribLocation( this.deformProgram, "a_position" ); this.gl.enableVertexAttribArray(a_position); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer); this.gl.vertexAttribPointer(a_position, 2, this.gl.FLOAT, false, 0, 0); // 设置纹理坐标属性 const a_texCoord = this.gl.getAttribLocation( this.deformProgram, "a_texCoord" ); this.gl.enableVertexAttribArray(a_texCoord); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer); this.gl.vertexAttribPointer(a_texCoord, 2, this.gl.FLOAT, false, 0, 0); // 设置变形属性 const a_deformation = this.gl.getAttribLocation( this.deformProgram, "a_deformation" ); this.gl.enableVertexAttribArray(a_deformation); this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.deformBuffer); this.gl.vertexAttribPointer(a_deformation, 2, this.gl.FLOAT, false, 0, 0); // 绘制三角形 this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer); this.gl.drawElements( this.gl.TRIANGLES, this.mesh.indices.length, this.gl.UNSIGNED_SHORT, 0 ); } /** * 获取当前图像数据 * @returns {ImageData} 当前图像数据 * @private */ _getImageData() { const width = this.canvas.width; const height = this.canvas.height; // 读取WebGL画布像素 const pixels = new Uint8Array(width * height * 4); this.gl.readPixels( 0, 0, width, height, this.gl.RGBA, this.gl.UNSIGNED_BYTE, pixels ); // 直接创建ImageData,不进行翻转 // WebGL和Canvas2D的坐标系不同,但这里我们保持WebGL的原始输出 const imageData = new ImageData( new Uint8ClampedArray(pixels), width, height ); return imageData; } /** * 重置所有变形 * @returns {ImageData} 重置后的图像数据 */ reset() { if (!this.initialized) return null; // 清除变形历史 this.deformHistory = []; // 重置所有变形 const deformations = new Float32Array(this.mesh.deformations.length); this.mesh.deformations = deformations; // 更新变形缓冲区 this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.deformBuffer); this.gl.bufferData( this.gl.ARRAY_BUFFER, deformations, this.gl.DYNAMIC_DRAW ); // 重新渲染 this._render(); // 更新当前图像数据 this.currentImageData = this._getImageData(); return this.currentImageData; } /** * 设置液化模式 * @param {String} mode 液化模式 */ setMode(mode) { if (Object.values(this.modes).includes(mode)) { this.currentMode = mode; return true; } return false; } /** * 设置液化参数 * @param {String} param 参数名 * @param {Number} value 参数值 */ setParam(param, value) { if (param in this.params) { this.params[param] = value; return true; } return false; } /** * 获取当前参数 * @returns {Object} 当前参数 */ getParams() { return { ...this.params }; } /** * 重置参数为默认值 */ resetParams() { this.params = { size: 50, pressure: 0.5, distortion: 0, power: 0.5, }; } /** * 获取原始图像数据 * @returns {ImageData} 原始图像数据 */ getOriginalImageData() { return this.originalImageData; } /** * 获取当前图像数据 * @returns {ImageData} 当前图像数据 */ getCurrentImageData() { return this.currentImageData; } /** * 释放资源 */ dispose() { if (!this.gl) return; // 删除缓冲区 if (this.vertexBuffer) this.gl.deleteBuffer(this.vertexBuffer); if (this.texCoordBuffer) this.gl.deleteBuffer(this.texCoordBuffer); if (this.deformBuffer) this.gl.deleteBuffer(this.deformBuffer); if (this.indexBuffer) this.gl.deleteBuffer(this.indexBuffer); // 删除纹理 if (this.texture) this.gl.deleteTexture(this.texture); // 删除着色器程序 if (this.program) this.gl.deleteProgram(this.program); if (this.deformProgram) this.gl.deleteProgram(this.deformProgram); // 重置属性 this.canvas = null; this.gl = null; this.program = null; this.deformProgram = null; this.texture = null; this.mesh = null; this.initialized = false; this.deformHistory = []; } /** * 检查是否支持WebGL * @returns {Boolean} 是否支持WebGL */ static isSupported() { try { const canvas = document.createElement("canvas"); return !!( window.WebGLRenderingContext && (canvas.getContext("webgl") || canvas.getContext("experimental-webgl")) ); } catch (e) { return false; } } }