Webgl 使用纹理时报警告:GL_INVALID_OPERATION: The texture is a non-power-of-two texture. 导致贴图失败,显示黑色

为什么自定义的纹理没有显示出来?这是因为 WebGL 对纹理有一种严格的限制,在两个维度上都不是 2 的幂。2 的幂是1,2,4,8,16,32,64,128,256,512,1024,2048,等等。假如 3D 物体的面长宽是 256 x 256。256 是 2 的幂,而自定义的纹理图的长宽为 320 x 240。这两个都不是2的幂,因此尝试显示纹理时会显示失败。在着色器中,当调用 texture2D 并且引用的纹理设置不正确时,WebGL 将使用黑色的颜色(0,0,0,1)代替纹理。

解决方案:

要解决这个问题,我们需要设置 wrap modeCLAMP_TO_EDGE,并且设置 filteringLINEAR 或者 NEAREST,这样就关闭了 mip mapping。代码如下:

    import fTextureImg from '../../assets/f-texture.jpg'
	// ... 中间其他代码 ...
	
    // Create a texture.
    const texture = gl.createTexture()
    gl.bindTexture(gl.TEXTURE_2D, texture)
    // Fill the texture with a 1x1 blue pixel.
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
      new Uint8Array([0, 0, 255, 255]))
	  
    AsynLoadImage(fTextureImg, (image) => {
      // Now that the image has loaded make copy it to the texture.
      gl.bindTexture(gl.TEXTURE_2D, texture)
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image)
	  /***       下面是解决纹理渲染失败的关键代码         ***/
      // Check if the image is a power of 2 in both dimensions.
      if (isPowerOf2(image.width) && isPowerOf2(image.height)) {
        // Yes, it's a power of 2. Generate mips.
        gl.generateMipmap(gl.TEXTURE_2D)
      } else {
        // No, it's not a power of 2. Turn off mips and set wrapping to clamp to edge
        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)
      }

      drawScene()
    })
	
	// 下面是用到的自定义的函数
    function isPowerOf2 (value) {
      return (value & (value - 1)) === 0
    }
    function AsynLoadImage (url, cb) {
      const img = new Image()
      img.src = url
      if (img.complete) {
        cb(img)
        return
      }
      img.onload = function () {
        img.onload = null
        cb(img)
        return
      };
      img.onerror = function () {
        img.onerror = null
        cb(img)
      }
    }

附录:
WebGL APIWebGLRenderingContext.texParameter[fi]() 方法用于设置纹理参数.
void gl.texParameterf(GLenum target, GLenum pname, GLfloat param);
void gl.texParameteri(GLenum target, GLenum pname, GLint param);

target 是 GLenum 指定绑定点(目标)。可能的值:
gl.TEXTURE_2D : 二维纹理.
gl.TEXTURE_CUBE_MAP : 立方体纹理.
当使用 WebGL 2 context 时,还可以使用以下值
gl.TEXTURE_3D : 三维贴图.
gl.TEXTURE_2D_ARRAY : 二维数组贴图.

pname 参数是 Glenum 指定要设置的纹理参数. param 参数是 GLfloatGLint 已指定的 pname 参数的值。

pname 描述 参数
Available in WebGL 1
gl.TEXTURE_MAG_FILTER 纹理放大滤波器 gl.LINEAR (默认值), gl.NEAREST.
gl.TEXTURE_MIN_FILTER 纹理缩小滤波器 gl.LINEAR, gl.NEAREST, gl.NEAREST_MIPMAP_NEAREST, gl.LINEAR_MIPMAP_NEAREST, gl.NEAREST_MIPMAP_LINEAR (默认值), gl.LINEAR_MIPMAP_LINEAR.
gl.TEXTURE_WRAP_S 纹理坐标水平填充 s gl.REPEAT (默认值),gl.CLAMP_TO_EDGE, gl.MIRRORED_REPEAT.
gl.TEXTURE_WRAP_T 纹理坐标垂直填充 t gl.REPEAT (默认值),gl.CLAMP_TO_EDGE, gl.MIRRORED_REPEAT.
Additionally available when using the EXT_texture_filter_anisotropic extension
ext.TEXTURE_MAX_ANISOTROPY_EXT 纹理最大向异性  GLfloat 值.
Additionally available when using a WebGL 2 context
gl.TEXTURE_BASE_LEVEL 纹理映射等级 任何整型值.
gl.TEXTURE_COMPARE_FUNC 纹理对比函数 gl.LEQUAL (默认值), gl.GEQUAL, gl.LESS, gl.GREATER, gl.EQUAL, gl.NOTEQUAL, gl.ALWAYS, gl.NEVER.
gl.TEXTURE_COMPARE_MODE 纹理对比模式 gl.NONE (默认值), gl.COMPARE_REF_TO_TEXTURE.
gl.TEXTURE_MAX_LEVEL 最大纹理映射数组等级 任何整型值.
gl.TEXTURE_MAX_LOD 纹理最大细节层次值 任何整型值.
gl.TEXTURE_MIN_LOD 纹理最小细节层次值 任何浮点型值.
gl.TEXTURE_WRAP_R 纹理坐标r包装功能 gl.REPEAT (默认值), gl.CLAMP_TO_EDGE, gl.MIRRORED_REPEAT.

参考引用:
https://webglfundamentals.org/webgl/lessons/webgl-3d-textures.html
https://developer.mozilla.org/zh-CN/docs/Web/API/WebGLRenderingContext/texParameter