There are many WebGL pits in small programs, one of which is that HDR will occasionally show all black, all half black, half bright and half black on the page in and out of multiple times.

Bug replicating screen (no lighting, just lighting scene with HDR)

I drew the corresponding envMap texture and found the problem.

All black Half black normal

By reading the source code of PMREMGenerator, understand its generation mode

function _applyPMREM( cubeUVRenderTarget ) {

	var autoClear = _renderer.autoClear;
	_renderer.autoClear = false;

	for ( var i = 1; i < TOTAL_LODS; i ++ ) {

		var sigma = Math.sqrt(
			_sigmas[ i ] * _sigmas[ i ] -
		_sigmas[ i - 1 ] * _sigmas[ i - 1]);var poleAxis =
		_axisDirections[ ( i - 1 ) % _axisDirections.length ];
		_blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis );

	}

	_renderer.autoClear = autoClear;

}

/** * This is a two-pass Gaussian blur for a cubemap. Normally this is done * vertically and horizontally, but this breaks down on a cube. Here we apply * the blur latitudinally (around the poles), and then longitudinally (towards * the poles) to approximate the orthogonally-separable blur. It is least * accurate at the poles, but still does a decent job. */
function _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) {

	_halfBlur(
		cubeUVRenderTarget,
		_pingPongRenderTarget,
		lodIn,
		lodOut,
		sigma,
		'latitudinal',
		poleAxis );

	_halfBlur(
		_pingPongRenderTarget,
		cubeUVRenderTarget,
		lodOut,
		lodOut,
		sigma,
		'longitudinal',
		poleAxis );

}

// The _halfBlur code is not pasted, the _blur gaze is roughly described
Copy the code

In fact, it is a pingpong image processing method, can refer to WebGLFundamental, a simple understanding is

The initial image first write FBOPing FBOPing texture input - > gaussian blur latitudinal, double - > FBOPong FBOPong texture input - > gaussian blur longitudinal, Writes to the same location - > FBOPing FBOPing texture input - > gaussian blur latitudinal, double - > FBOPong FBOPong texture input - > gaussian blur longitudinal, Writes to the same location - > FBOPing FBOPing texture input - > gaussian blur latitudinal, double - > FBOPong FBOPong texture input - > gaussian blur longitudinal, Writes to the same location - > FBOPing FBOPing texture input - > gaussian blur latitudinal, double - > FBOPong FBOPong texture input - > gaussian blur longitudinal, Write to the same location -> FBOPing... Use to get the final FBOPingCopy the code

So guess it’s a hardware problem? But you need to rule out the _halfBlur/Shader problem, so you need to write a simple PingPong like processing to see if it works

const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera();
const geometry = new THREE.PlaneGeometry(0.7.0.7.1.1);
const geometry1 = new THREE.PlaneGeometry(0.5.0.5.1.1);
const plane = new THREE.Mesh(
  geometry,
  new THREE.MeshBasicMaterial({
    color: new THREE.Color(0x123456),
    side: THREE.DoubleSide,
  }),
);
const planeTmp = new THREE.Mesh(
  geometry,
  new THREE.MeshBasicMaterial({ side: THREE.DoubleSide }),
);
const srcTarget = new THREE.WebGLRenderTarget(
  this.canvas.width,
  this.canvas.height,
);
const destTarget = new THREE.WebGLRenderTarget(
  this.canvas.width,
  this.canvas.height,
);
plane.position.z = -0.1;
const autoClear = this.renderer.autoClear;
this.renderer.autoClear = false;
scene.add(plane);

const viewport = (target, x, y, w, h) = > {
  target.viewport.set(x, y, w, h);
  target.scissor.set(x, y, w, h);
};
const wh = this.canvas.width / 2;
const hh = this.canvas.height / 2; [[0.0],
  [wh, 0],
  [0, hh],
  [wh, hh],
].forEach(([x, y]) = > {
  viewport(srcTarget, x, y, wh, hh);
  this.renderer.setRenderTarget(srcTarget);
  this.renderer.render(scene, camera); / / cell phone use PerspectiveCamera, painting not to come out, in addition to line 123, can, strange
  scene.remove(plane);
  scene.add(planeTmp);

  // Write the result to dest
  planeTmp.material.map = srcTarget.texture;
  viewport(destTarget, x, y, wh, hh);
  this.renderer.setRenderTarget(destTarget);
  this.renderer.render(scene, this.camera);
});

// show result
this.renderer.autoClear = autoClear;
this.renderer.setRenderTarget(null);
const planeSrc = new THREE.Mesh(
  geometry1,
  new THREE.MeshBasicMaterial({
    map: srcTarget.texture,
    side: THREE.DoubleSide,
  }),
);
const planeDest = new THREE.Mesh(
  geometry1,
  new THREE.MeshBasicMaterial({
    map: destTarget.texture,
    side: THREE.DoubleSide,
  }),
);
planeSrc.position.z = 0.2;
planeDest.position.z = -0.2;
this.scene.add(planeSrc, planeDest);
Copy the code

Unfortunately, the same problem did arise.

So guess it’s a hardware problem? However, we still need to eliminate the Three problem, so we need to write a pure WebGL demo to see if we can reproduce it. (WebGL code is tedious, shader is not easy to write, mainly look at the render function)

Page({
  data: {},
  onReady() {this.onClick()},

  onClick() {
    wx.createSelectorQuery().select('#gl').node().exec((res) = > {
      if (res[0]) {
        this.test(res[0].node)
      }
    })
  },

  async test(canvas) {
    const gl = canvas.getContext('webgl');
    const maxVertexShaderTextureUnits = gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
    const maxFragmentShaderTextureUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
    const { windowHeight, windowWidth, pixelRatio } = wx.getSystemInfoSync()
    canvas.height = windowHeight * pixelRatio;
    canvas.width = windowWidth * pixelRatio;
    const fb0 = gl.createFramebuffer();
    const fb1 = gl.createFramebuffer();
    const tex0 = gl.createTexture();
    const tex1 = gl.createTexture();

    // prettier-ignore
    const VERTXES = [
      0.0.1.0.1.0, -1.0,
      -1.0, -1.0,];// prettier-ignore
    const TEXTURE_VERTXES = [
      -1.0.1.0,
      -1.0.0.0.1.0.1.0.1.0.0.0,
      -1.0.0.0.1.0.1.0,]// prettier-ignore
    const COPY_TEXTURE_VERTXES = [
      -1.0.1.0,
      -1.0, -1.0.1.0.1.0.1.0, -1.0,
      -1.0, -1.0.1.0.1.0,]const [triangleProgram, triangleVS, triangleFS] = createProgram(
      glsl` #pragma vscode_glsllint_stage : vert attribute vec2 a_position; varying vec4 v_color; void main() { gl_Position = vec4(a_position.x, a_position.y, 0, 1); V_color = gl_Position * 0.5 + 0.5; } `,
      glsl` #pragma vscode_glsllint_stage : frag precision mediump float; varying vec4 v_color; void main() { gl_FragColor = v_color; } `,);const [copyTextureProgram, copyTexVS, copyTexFS] = createProgram(
      glsl` #pragma vscode_glsllint_stage : vert attribute vec2 a_position; varying vec2 v_texcoord; void main() { gl_Position = vec4(a_position.x, a_position.y, 0, 1); V_texcoord = vec2((a_position.x + 1.0) *.5, (a_position.y + 1.0) * 0.5); } `,
      glsl` #pragma vscode_glsllint_stage : frag precision mediump float; varying vec2 v_texcoord; uniform sampler2D u_texture; void main() { gl_FragColor = texture2D(u_texture, v_texcoord); } `,);const [textureProgram, texVS, texFS] = createProgram(
      glsl` #pragma vscode_glsllint_stage : vert attribute vec2 a_position; uniform bool u_up; varying vec2 v_texcoord; void main() { if (u_up) { gl_Position = vec4(a_position.x, a_position.y, 0, 1); } else {gl_Position = vec4(a_position.x, a_position. y-1.0, 0, 1); } v_texcoord = vec2((a_position.x + 1.0) * 0.5, a_position.y); } `,
      glsl` #pragma vscode_glsllint_stage : frag precision mediump float; varying vec2 v_texcoord; uniform sampler2D u_texture; void main() { gl_FragColor = texture2D(u_texture, v_texcoord); } `,);const buffer = gl.createBuffer();

    initTexture(tex0);
    initTexture(tex1);

    bindFrameBufferToTexture(fb0, tex0);
    bindFrameBufferToTexture(fb1, tex1);

    gl.bindFramebuffer(gl.FRAMEBUFFER, fb0);
    drawTriangle();

    for (let index = 0; index < 2; index++) {
      renader()
    }

    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    gl.clearColor(1.1.0.1)
    gl.clear(gl.COLOR_BUFFER_BIT);
    drawTexture(tex0, 1);
    drawTexture(tex1, 0);
    dispose()

    // Utility functions
    function renader() {
      gl.bindFramebuffer(gl.FRAMEBUFFER, fb1);
      copyTexture(tex0);

      gl.bindFramebuffer(gl.FRAMEBUFFER, fb0);
      drawTexture(tex0, 1); // The magic little program WebGL
      drawTexture(tex1, 0);
    }

    function copyTexture(tex) {
      gl.viewport(0.0, gl.canvas.width, gl.canvas.height);
      gl.useProgram(copyTextureProgram);

      gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
      gl.bufferData(
        gl.ARRAY_BUFFER,
        new Float32Array(COPY_TEXTURE_VERTXES),
        gl.STATIC_DRAW,
      );

      const aPosition = gl.getAttribLocation(copyTextureProgram, 'a_position');
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
      gl.enableVertexAttribArray(aPosition);
      gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false.0.0);

      const uTexture = gl.getUniformLocation(copyTextureProgram, 'u_texture');
      gl.bindTexture(gl.TEXTURE_2D, tex);
      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.LINEAR);
      gl.uniform1i(uTexture, 0);

      gl.drawArrays(gl.TRIANGLES, 0.6);
    }

    function drawTexture(tex, up = 1) {
      gl.viewport(0.0, gl.canvas.width, gl.canvas.height);
      gl.useProgram(textureProgram);

      gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
      gl.bufferData(
        gl.ARRAY_BUFFER,
        new Float32Array(TEXTURE_VERTXES),
        gl.STATIC_DRAW,
      );

      const aPosition = gl.getAttribLocation(textureProgram, 'a_position');
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
      gl.enableVertexAttribArray(aPosition);
      gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false.0.0);

      const uTexture = gl.getUniformLocation(textureProgram, 'u_texture');
      gl.bindTexture(gl.TEXTURE_2D, tex);
      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.LINEAR);
      gl.uniform1i(uTexture, 0);

      const uUp = gl.getUniformLocation(textureProgram, 'u_up');
      gl.uniform1i(uUp, up);

      gl.drawArrays(gl.TRIANGLES, 0.6);
    }

    function drawTriangle() {
      gl.useProgram(triangleProgram);
      checkError('after useProgram');

      // Write vertices
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(VERTXES), gl.STATIC_DRAW);
      checkError('after bufferData');

      // Write aPosition to buffer
      const aPosition = gl.getAttribLocation(triangleProgram, 'a_position');
      gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
      gl.enableVertexAttribArray(aPosition);
      gl.vertexAttribPointer(aPosition, 2, gl.FLOAT, false.0.0);
      checkError('after vertexAttribPointer');

      / / to draw
      gl.viewport(0.0, gl.canvas.width, gl.canvas.height);
      checkError('after viewport');
      gl.drawArrays(gl.TRIANGLES, 0.3); }}// Some utility functions are hidden
})
Copy the code

In fact, the main look at the render function, the error code found more magical Bug, small program WebGL actually allowed to produce self sales?

bindFrameBufferToTexture(fb0, tex0);
bindFrameBufferToTexture(fb1, tex1);

function renader() {
  gl.bindFramebuffer(gl.FRAMEBUFFER, fb1);
  copyTexture(tex0);

  gl.bindFramebuffer(gl.FRAMEBUFFER, fb0);
  drawTexture(tex0, 1); 
  // The WebGL applet allows reading tex0 bound to FB0 and writing to tex0 bound to FB0.
  drawTexture(tex1, 0);
}
Copy the code
Small program PC

And switching pages back and forth will occasionally cause WebGL strikes and nothing will be drawn… At the same time, there is no error. Suddenly it wasn’t a hardware problem.

The curve saves the country, the Bug still needs to be solved

Since it is locating the problem of PMREMGenerator process, it seems that the fastest solution is to avoid the three that may have bugs, the micro channel small program WebGL that may have bugs, the mobile phone OpenGL that may have bugs, and the hand that may have bugs.

So the solution is to simply use the correctly generated texture.

// const hdr = await rgbeLoader.loadAsync('your.hdr');
// const envMap = pmremGenerator.fromEquirectangular(hdr).texture;

const envMap = await textureLoader.loadAsync('Above envMap saved after the image. PNG')

envMap.magFilter = THREE.NearestFilter
envMap.minFilter = THREE.NearestFilter
envMap.generateMipmaps = false
envMap.type = THREE.UnsignedByteType
envMap.format = THREE.RGBEFormat
envMap.encoding = THREE.RGBEEncoding
envMap.mapping = THREE.CubeUVReflectionMapping
envMap.name = 'PMREM.cubeUv';
envMap.needsUpdate = true;

scene.environment = envMap
Copy the code

If compression is not set, the brightness reduction should be better. Let’s say it’s in a bin file, but it’s a lot bigger. However, HDR 1024*512 will be larger than the 768*768 output

The end of the

We didn’t find the root cause, but we fixed the bug and compressed the HDR file? Only in the scene of three, of course.

The little program WebGL is amazing