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

基于纹理坐标导数计算Mip Level的实现问题排查

OpenGL/WebGL 纹理Mip Level计算异常问题

我尝试基于纹理坐标导数,以与采样器相同的方式计算纹理Mip Level。参考Stack Overflow、Reddit、Khronos社区等多个资料,得到的计算公式如下:

vec2  dx_vtc        = dFdx(texture_coordinate);
vec2  dy_vtc        = dFdy(texture_coordinate);
float delta_max_sqr = max(dot(dx_vtc, dx_vtc), dot(dy_vtc, dy_vtc));

mip_level = 0.5 * log2(delta_max_sqr);

基于此我编写了测试程序,包含两个Shader:

  • 第一个Shader使用纹理坐标采样含7级Mipmap的纹理(各级为纯色),采用NEAREST_MIPMAP_NEAREST过滤,纹理坐标范围变化时Mip Level切换正常;
  • 第二个Shader通过上述公式计算Mip Level,再从对应颜色表中选取颜色(颜色与纹理Mip级一致),但始终显示红色(对应Mip Level 0)。

第二个Shader代码如下:

#version 300 es
precision highp float;

in vec2 v_texCoord;

out vec4 outColor;

const vec4 colors[8] = vec4[8](
  vec4(  1,   0,   0, 1), // 0: red
  vec4(  1,   1,   0, 1), // 1: yellow
  vec4(  0,   1,   0, 1), // 2: green
  vec4(  0,   1,   1, 1), // 3: cyan
  vec4(  0,   0,   1, 1), // 4: blue
  vec4(  1,   0,   1, 1), // 5: magenta
  vec4(0.5, 0.5, 0.5, 1), // 6: gray
  vec4(  1,   1,   1, 1));// 7: white

void main() {
  vec2 dx = dFdx(v_texCoord);
  vec2 dy = dFdy(v_texCoord);
  float deltaMaxSq = max(dot(dx, dx), dot(dy, dy));
  float mipLevel = 0.5 * log2(deltaMaxSq);
  
  outColor = colors[int(mipLevel)];
}

我已完成多项调试:确认纹理坐标正确、颜色表索引有效、尝试可视化Mip Level均无效。请问问题出在哪里?


完整测试代码

CSS部分

html, body {
  margin: 0;
  font-family: monospace;
  height: 100%;
}
canvas {
  display: block;
  width: 100%;
  height: 100%;
}

HTML与JS部分

<canvas id="c"></canvas>

<script type="module">
import * as twgl from 'https://twgljs.org/dist/5.x/twgl-full.module.js';

const vs = `#version 300 es
uniform mat4 u_worldViewProjection;
uniform mat3 u_texMat;

out vec2 v_texCoord;

const vec2 position[6] = vec2[6](
  vec2(0, 0),
  vec2(1, 0),
  vec2(0, 1),
  vec2(0, 1),
  vec2(1, 0),
  vec2(1, 1));

void main() {
  vec2 p = position[gl_VertexID];
  v_texCoord = (u_texMat * vec3(p, 1)).xy;
  gl_Position = u_worldViewProjection * vec4(p, 0, 1);
}
`;
const fsTex = `#version 300 es
precision highp float;

in vec2 v_texCoord;

uniform sampler2D u_tex;

out vec4 outColor;

void main() {
  outColor = texture(u_tex, v_texCoord);
}
`;
const fsMipLevel = `#version 300 es
precision highp float;

in vec2 v_texCoord;

out vec4 outColor;

const vec4 colors[8] = vec4[8](
  vec4(  1,   0,   0, 1), // 0: red
  vec4(  1,   1,   0, 1), // 1: yellow
  vec4(  0,   1,   0, 1), // 2: green
  vec4(  0,   1,   1, 1), // 3: cyan
  vec4(  0,   0,   1, 1), // 4: blue
  vec4(  1,   0,   1, 1), // 5: magenta
  vec4(0.5, 0.5, 0.5, 1), // 6: gray
  vec4(  1,   1,   1, 1));// 7: white

void main() {
  vec2 dx = dFdx(v_texCoord);
  vec2 dy = dFdy(v_texCoord);
  float deltaMaxSq = max(dot(dx, dx), dot(dy, dy));
  float mipLevel = 0.5 * log2(deltaMaxSq);
  
  // mipLevel = mod(gl_FragCoord.x / 16.0, 8.0);  // comment in to test we can use the colors

  outColor = colors[int(mipLevel)];

  // outColor = vec4(mipLevel / 7.0, 0, 0, 1);  // comment in to visualize another way
  // outColor = vec4(fract(v_texCoord), 0, 1);  // comment in to visualize texcoord
}
`;

const colors = [
  '#F00',
  '#FF0',
  '#0F0',
  '#0FF',
  '#00F',
  '#F0F',
  '#888',
  '#FFF',
];


function createMips(colors) {
  const ctx = document.createElement('canvas').getContext('2d');
  const numMips = colors.length;
  return colors.map((color, i) => {
    const size = 2 ** (numMips - i - 1);
    ctx.canvas.width = size;
    ctx.canvas.height = size;
    ctx.fillStyle = color;
    ctx.fillRect(0, 0, size, size);
    return ctx.getImageData(0, 0, size, size);
  });
}

function main() {
  const m4 = twgl.m4;
  const gl = document.getElementById("c").getContext("webgl2");
  if (!gl) {
    alert("Sorry, this example requires WebGL 2.0");  // eslint-disable-line
    return;
  }

  const texProgramInfo = twgl.createProgramInfo(gl, [vs, fsTex]);
  const mipProgramInfo = twgl.createProgramInfo(gl, [vs, fsMipLevel]);

  const texImage = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_2D, texImage);
  const data = createMips(colors);
  data.forEach(({width, height, data}, level) => {
    gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
  });
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_NEAREST);

  const lerp = (a, b, t) => a + (b - a) * t;

  function render(time) {
    time *= 0.001;
    twgl.resizeCanvasToDisplaySize(gl.canvas);
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
    gl.clearColor(0.3, 0.3, 0.3, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    const uniforms = {};

    const s = lerp(1, 128, Math.sin(time) * 0.5 + 0.5);
    uniforms.u_texMat = [
      s, 0, 0,
      0, s, 0,
      0, 0, 1,
    ];
    uniforms.u_worldViewProjection = m4.translation([-1.01, -0.5, 0]);

    gl.useProgram(texProgramInfo.program);
    twgl.setUniforms(texProgramInfo, uniforms);
    gl.drawArrays(gl.TRIANGLES, 0, 6);

    uniforms.u_worldViewProjection = m4.translation([0.01, -0.5, 0]);

    gl.useProgram(mipProgramInfo.program);
    twgl.setUniforms(mipProgramInfo, uniforms);
    gl.drawArrays(gl.TRIANGLES, 0, 6);

    requestAnimationFrame(render);
  }
  requestAnimationFrame(render);
}

main();
</script>

内容的提问来源于stack exchange,提问作者user128511

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 15:13:09