You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 02:45:54