WebGL设置gl.NEAREST/LINEAR过滤后纹理仍模糊问题排查
WebGL纹理持续模糊问题根因与修复
核心问题定位
纹理模糊是多个代码bug共同导致的,最核心的原因是纹理过滤器参数设置逻辑存在分支遗漏:
- 你所有修改
TEXTURE_MIN_FILTER、TEXTURE_MAG_FILTER的代码全部写在「非2次幂纹理」的else分支里,只要加载的图片宽高是2的整数次幂,这两行参数设置代码根本不会执行,WebGL会使用默认过滤参数:- 默认
MAG_FILTER为gl.LINEAR(线性插值,本身自带平滑模糊效果) - 默认
MIN_FILTER为gl.NEAREST_MIPMAP_LINEAR(三线性mipmap过滤,会混合多层mipmap采样,进一步加剧模糊)
你以为自己切换了NEAREST/LINEAR参数,实际上这些修改只对非2次幂纹理生效,2次幂纹理始终用默认的线性过滤参数,自然怎么改都没用。
- 默认
其他会导致模糊/显示异常的附带问题:
- 顶点计数逻辑错误:
GL.add方法中用obj.vertex.length / 2计算顶点数,但position属性是vec3类型,每个顶点占3个浮点数,正确计算方式应该是除以3。同时你固定用36个索引绘制立方体,顶点计数错误会导致drawElements读取索引数组越界,拿到垃圾坐标/UV数据,造成纹理拉伸、错乱,视觉上和模糊效果类似。 - 未设置纹理Y轴翻转:浏览器加载的图片原点在左上角,WebGL纹理采样原点在左下角,不设置翻转参数会导致纹理坐标错位,加剧显示异常。
- 错误设置深度纹理专用参数:非2次幂分支里设置的
gl.TEXTURE_COMPARE_FUNC只用于阴影对比的深度纹理,普通颜色纹理不需要设置,可能引发不可预期的采样行为。 - 未校验Canvas实际像素尺寸:如果只通过CSS设置Canvas宽高、没有设置Canvas标签的
width/height属性为实际物理像素值,整个WebGL画布都会被拉伸模糊,和纹理参数无关。 - 2次幂纹理分支只生成了mipmap,没有显式设置包裹模式、过滤参数,完全依赖WebGL默认值,行为不可控。
修复方案
1. 修正Texture类的参数设置逻辑
把所有纹理参数设置逻辑移到分支外,不管是不是2次幂纹理都显式设置参数,同时添加Y轴翻转配置,删掉无用的深度纹理参数:
class Texture { #texture; get texture() { return this.#texture; } constructor(location, cb) { const gl = main.gl; 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); // 上传1x1蓝色占位纹理 gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0,0,255,255])); const image = new Image(); image.onload = () => { gl.bindTexture(gl.TEXTURE_2D, texture); // 翻转图片Y轴匹配WebGL纹理坐标 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image); const isPowerOf2 = (image.width & (image.width - 1)) == 0 && (image.height & (image.height - 1)) == 0; if (isPowerOf2) { gl.generateMipmap(gl.TEXTURE_2D); // 2次幂纹理要锐利效果用NEAREST_MIPMAP_NEAREST,要平滑效果用LINEAR_MIPMAP_LINEAR gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); } else { // 非2次幂纹理必须用CLAMP_TO_EDGE,且不能用mipmap过滤 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); } // 所有纹理统一设置MAG过滤器,不要放在分支里 gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); this.#texture = texture; cb(); }; image.src = location.toString(); } }
2. 修正GL类的顶点计数与绘制逻辑
- 修复顶点数计算错误,立方体固定用36个索引绘制,直接传值即可,不需要动态计算
- 修复变量名冲突问题,把UV数组和attrib location的变量名分开,避免覆盖
class GL { static #verticies = 0; static render() { const gl = main.gl; this.#verticies = 0; gl.clearColor(0.0, 0.0, 0.0, 1.0); gl.clearDepth(1.0); // 确保viewport尺寸和canvas实际像素尺寸一致 gl.viewport(0, 0, main.canvas.width, main.canvas.height); gl.enable(gl.DEPTH_TEST); gl.enable(gl.CULL_FACE); gl.depthFunc(gl.LEQUAL); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); } static draw(type = main.gl.TRIANGLES) { const gl = main.gl; gl.useProgram(main.program); // 立方体固定36个索引,直接传值避免计数错误 gl.drawElements(type, 36, gl.UNSIGNED_SHORT, 0); } static add(obj) { const gl = main.gl; const cps = obj.vertex; // UV数组改名叫uvs,避免和后面的attrib location变量冲突 const uvs = [ 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 ]; const cel = [ 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23 ] const bps = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, bps); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cps), gl.STATIC_DRAW); const buv = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buv); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(uvs), gl.STATIC_DRAW); const bel = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bel); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cel), gl.STATIC_DRAW); const matrix = main.camera; const vertex = matrix.Matrix; const md = gl.getUniformLocation(main.program, `model`); const posLoc = gl.getAttribLocation(main.program, `position`); const uvLoc = gl.getAttribLocation(main.program, `texcoord`); gl.uniformMatrix4fv(md, false, new Float32Array(vertex)); gl.enableVertexAttribArray(posLoc); gl.bindBuffer(gl.ARRAY_BUFFER, bps); gl.vertexAttribPointer(posLoc, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(uvLoc); gl.bindBuffer(gl.ARRAY_BUFFER, buv); gl.vertexAttribPointer(uvLoc, 2, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bel); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, obj.texture); gl.uniform1i(gl.getUniformLocation(main.program, `uSampler`), 0); } }
3. 校验Canvas像素尺寸
确保初始化WebGL上下文时,Canvas标签的width、height属性和显示的物理像素尺寸一致,不要只设置CSS宽高:
const canvas = document.getElementById('glcanvas'); // 按设备像素比设置实际像素尺寸,避免高分屏模糊 canvas.width = window.innerWidth * window.devicePixelRatio; canvas.height = window.innerHeight * window.devicePixelRatio; // CSS设置为显示尺寸 canvas.style.width = window.innerWidth + 'px'; canvas.style.height = window.innerHeight + 'px'; const gl = canvas.getContext('webgl');
修改完成后,纹理过滤器参数会正确生效,需要锐利像素效果就用gl.NEAREST,需要平滑效果就用gl.LINEAR,不会再出现参数修改不生效导致的持续模糊问题。
内容的提问来源于stack exchange,提问作者Bomber87t
相关产品推荐
相关产品推荐

