返回

小程序WebGL之HDR纹理绘制后黑屏问题排查

前端

众所周知,小程序WebGL坑比较多,其中一个算是HDR,会在多次进出页面偶现全黑、全半黑、一半亮一半黑的情况。这让人很头疼,排查起来也比较费时。

最近遇到了一个类似的问题,尝试把对应的envMap的纹理绘制出来后发现问题了:envMap纹理绘制到canvas上,结果很不幸,确实出现了和HDR同样的问题。所以猜测是硬件问题?

但是还需要排除Three的问题,所以需要编写纯WebGL的demo看是否也存在这个问题。

排查过程

编写一个简单的纯WebGL的demo,绘制一个HDR的球体。

const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');

// 顶点着色器
const vsSource = `
  attribute vec3 aPosition;
  attribute vec2 aTexCoord;

  varying vec2 vTexCoord;

  void main() {
    gl_Position = vec4(aPosition, 1.0);
    vTexCoord = aTexCoord;
  }
`;

// 片段着色器
const fsSource = `
  precision mediump float;

  varying vec2 vTexCoord;

  uniform sampler2D uTexture;

  void main() {
    gl_FragColor = texture2D(uTexture, vTexCoord);
  }
`;

// 编译着色器
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vsSource);
gl.compileShader(vertexShader);

const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fsSource);
gl.compileShader(fragmentShader);

// 创建程序对象
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);

// 获取属性位置
const positionLocation = gl.getAttribLocation(program, 'aPosition');
const texCoordLocation = gl.getAttribLocation(program, 'aTexCoord');

// 获取uniform位置
const textureLocation = gl.getUniformLocation(program, 'uTexture');

// 创建缓冲区对象
const positionBuffer = gl.createBuffer();
const texCoordBuffer = gl.createBuffer();

// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

// 设置缓冲区数据
const positions = [
  -1.0, -1.0, 0.0,
  1.0, -1.0, 0.0,
  1.0, 1.0, 0.0,
  -1.0, 1.0, 0.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

// 绑定缓冲区对象
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);

// 设置缓冲区数据
const texCoords = [
  0.0, 0.0,
  1.0, 0.0,
  1.0, 1.0,
  0.0, 1.0,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texCoords), gl.STATIC_DRAW);

// 创建纹理对象
const texture = gl.createTexture();

// 绑定纹理对象
gl.bindTexture(gl.TEXTURE_2D, texture);

// 设置纹理参数
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);

// 设置纹理图像
const image = new Image();
image.onload = function() {
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);
  gl.generateMipmap(gl.TEXTURE_2D);
  draw();
};
image.src = 'envmap.jpg';

function draw() {
  // 使用程序对象
  gl.useProgram(program);

  // 绑定缓冲区对象
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  // 启用属性
  gl.enableVertexAttribArray(positionLocation);

  // 设置属性指针
  gl.vertexAttribPointer(positionLocation, 3, gl.FLOAT, false, 0, 0);

  // 绑定缓冲区对象
  gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);

  // 启用属性
  gl.enableVertexAttribArray(texCoordLocation);

  // 设置属性指针
  gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);

  // 绑定纹理对象
  gl.bindTexture(gl.TEXTURE_2D, texture);

  // 设置uniform
  gl.uniform1i(textureLocation, 0);

  // 清除画布
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.clear(gl.COLOR_BUFFER_BIT);

  // 绘制
  gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
}

运行这个demo,发现果然也会出现同样的问题。

解决方案

经过一番排查,最终发现问题出在HDR纹理的mipmap上。禁用mipmap后,问题消失。

因此,解决方案就是禁用HDR纹理的mipmap。

总结

通过这个案例,我们了解到小程序WebGL中HDR纹理绘制后出现黑屏问题可能是由于mipmap造成的。禁用mipmap可以解决这个问题。