WebGL片段着色器中鼠标位置使用异常问题排查
WebGL鼠标位置交互纹理无效果问题排查与修复
核心问题点
- 错误的绘制调用:鼠标移动事件中执行
gl.drawArrays(gl.POINTS, 0, 1)仅绘制单个点,而非覆盖整个画布的矩形网格,导致视觉上无变化。 - 鼠标坐标计算偏差:Y轴计算中额外减去了
10,导致坐标错位;且未完全对齐WebGL的坐标系规则。 - 冗余的Uniform位置获取:每次鼠标移动都调用
getUniformLocation,虽功能正常但属于不必要的性能损耗。 - 未重新渲染完整场景:原代码仅在纹理加载时渲染一次,鼠标移动时未重新绘制所有纹理tile,新的鼠标uniform值无法作用到整个画面。
修复方案
1. 提前获取Uniform位置
在初始化阶段一次性获取u_mouseCoord的位置,避免重复调用:
// 全局/初始化阶段执行 const mouseCoordUniformLocation = gl.getUniformLocation(program, 'u_mouseCoord'); // 设置初始鼠标位置 gl.uniform2f(mouseCoordUniformLocation, 0, 0);
2. 修正鼠标坐标计算
移除多余的-10,确保Y轴与WebGL坐标系(原点在左下角)一致:
canvas.addEventListener('mousemove', (e) => { const br = canvas.getBoundingClientRect(); // 计算画布内鼠标坐标,翻转Y轴适配WebGL const x = e.clientX - br.left; const y = canvas.height - (e.clientY - br.top); gl.uniform2f(mouseCoordUniformLocation, x, y); // 更新uniform后重新渲染整个场景 renderAll(); });
3. 封装全量渲染函数
将所有tile的渲染逻辑封装为renderAll,确保鼠标移动时重新绘制完整画面:
// 存储已加载完成的tile数据 const loadedTiles = []; // 修改原render函数,将tile信息存入loadedTiles function render(tileImage, i, tile) { // ... 保留原有的纹理、缓冲区初始化代码 ... loadedTiles.push({ tileImage, tile }); } // 全量渲染所有tile的函数 function renderAll() { // 清除画布 gl.clear(gl.COLOR_BUFFER_BIT); loadedTiles.forEach(({ tileImage, tile }) => { const positionLocation = gl.getAttribLocation(program, 'a_position'); const texcoordLocation = gl.getAttribLocation(program, 'a_texCoord'); // 重新设置位置缓冲区 const positionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); setRectangle(gl, tile.position.x, tile.position.y, tileImage.width, tileImage.height); // 重新设置纹理坐标缓冲区 const texcoordBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer); gl.bufferData( gl.ARRAY_BUFFER, new Float32Array([ 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0, ]), gl.STATIC_DRAW ); // 绑定并设置纹理参数 const texture = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, texture); 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.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, tileImage); // 设置全局uniforms const resolutionLocation = gl.getUniformLocation(program, 'u_resolution'); const textureSizeLocation = gl.getUniformLocation(program, 'u_textureSize'); gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height); gl.uniform2f(textureSizeLocation, 256, 256); // 启用属性并绘制 gl.enableVertexAttribArray(positionLocation); gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(texcoordLocation); gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer); gl.vertexAttribPointer(texcoordLocation, 2, gl.FLOAT, false, 0, 0); gl.drawArrays(gl.TRIANGLES, 0, 6); }); }
4. 优化片段着色器
将硬编码的分辨率改为从uniform获取,避免与实际画布尺寸不匹配:
// frag.glsl precision mediump float; uniform sampler2D u_image; uniform vec2 u_textureSize; uniform vec2 u_mouseCoord; uniform vec2 u_resolution; // 添加此uniform varying vec2 v_texCoord; void main() { vec4 c = texture2D(u_image, v_texCoord); vec4 white = vec4(1.0, 1.0 ,1.0, 1.0); // 直接使用像素坐标比较,无需额外除法 if (gl_FragCoord.x < u_mouseCoord.x) { gl_FragColor = white; } else { gl_FragColor = c; } }
关键说明
- 直接从JS向片段着色器传递uniform变量是WebGL动态传递参数的标准方式,完全可行。
- 必须确保uniform更新后重新渲染整个场景,否则新参数无法反映到画面上。
- WebGL坐标系原点在左下角,浏览器鼠标事件原点在左上角,因此需要翻转Y轴坐标适配。
内容的提问来源于stack exchange,提问作者Seth Lutske
相关产品推荐
相关产品推荐

