背景
之前尝试过利用多个纹理单元,再基于传入给 shader 的 vertexBuffer 信息决定选 1 号纹理单元还是 2 号纹理单元。
虽然理论上,这个方式确实行得通,但是一次 drawcall 绘制多个纹理,本来目的是为了提高绘制性能,而实际上却无法提高性能,甚至还有反作用。
因为有说法是 shader 分支会降低 GPU 性能,详见:https://blog.csdn.net/qq_31788759/article/details/107248224
那么,是否还有其他批量绘制不同纹理的方式呢?
WebGL 2 sampler2DArray
WebGL 2 带来了一个新的数据结构:sampler2DArray。这个东西,还是占用一个纹理单元,但传入的不是一个纹理,而是一个纹理数组。
关键 shader 代码:
#version 300 es
precision mediump float;
precision mediump sampler2DArray;
// our texture
uniform sampler2DArray u_image;
// the texCoords passed in from the vertex shader.
in vec3 v_texCoord;
// we need to declare an output for the fragment shader
out vec4 outColor;
void main() {
outColor = texture(u_image, v_texCoord); // 这里从一般的 vec2 变成了 vec3,第三个元素是纹理 index
}
</script>
相对于普通的 2d 纹理渲染,最关键的是 JS 如何传递纹理到 GPU。这里关键是类型 gl.TEXTURE_2D 变成 gl.TEXTURE_2D_ARRAY,texImage2D 变成 texImage3D。
有两种方式传递纹理:
- 可以把多个纹理合并到一个大图,一次性推送;
- 也可以先预申请空间,再用 texSubImage3D 逐个推送。
关键 JS 代码:
var canvas = document.createElement('canvas');
canvas.width = imgs[0].width;
canvas.height = imgs[0].height * imgs.length;
var ctx = canvas.getContext('2d');
for (let i = 0; i < imgs.length; i++) {
ctx.drawImage(imgs[i], 0, imgs[0].height * i);
}
var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
var pixels = new Uint8Array(imageData.data.buffer);
gl.texImage3D(
gl.TEXTURE_2D_ARRAY,
0, // level 暂时不知道什么地方会用到
gl.RGBA, // internalFormat
imgs[0].width, // width
imgs[0].height, // height
imgs.length, // depth 多少个纹理
0, // border
gl.RGBA, // format
gl.UNSIGNED_BYTE, // type
pixels
);
// 利用 texImage3D 设定空间,再用 texSubImage3D 设置图片。texImage3D 创建空间时,宽高设定很重要,后续的尺寸不能超过这个尺寸。如果后续尺寸比这个小,会导致渲染时图片偏小,也很好理解,因为 subImage 传递的只是左上角一小部分的图片,其他面积为空白。
gl.texImage3D(
gl.TEXTURE_2D_ARRAY,
0, // level 暂时不知道什么地方会用到
gl.RGBA, // internalFormat
imgs[0].width, // width
imgs[0].height, // height
imgs.length, // depth 多少个纹理
0, // border
gl.RGBA, // format
gl.UNSIGNED_BYTE, // type
null
);
/**
* 用 texStorage3D 搭配 texSubImage3D 也可以。但这里需要时 RGBA8 不是 RGBA。
* 因为选项里边没有 RGBA https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/texStorage3D
* 另外,这里不是指输入图片的格式,是指把图片转为什么格式存储。
* texImage3D 的 gl.RGBA 默认情况下跟 gl.RGBA8 一致
*/
// gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, imgs[0].width, imgs[0].height,3);
gl.texSubImage3D(
gl.TEXTURE_2D_ARRAY,
0, // level 暂时不知道什么地方会用到
0, // x offset
0, // y offset
0, // z offset 不能超过前边预留的 depth
imgs[0].width, // width
imgs[0].height, // height
1, // depth 多少个纹理
gl.RGBA, // format
gl.UNSIGNED_BYTE, // type
imgs[0]
);
gl.texSubImage3D(
gl.TEXTURE_2D_ARRAY,
0, // level 暂时不知道什么地方会用到
0,
0,
1,
imgs[0].width, // width
imgs[0].height, // height
1, // depth 多少个纹理
gl.RGBA, // format
gl.UNSIGNED_BYTE, // type
imgs[1]
);
gl.texSubImage3D(
gl.TEXTURE_2D_ARRAY,
0, // level 暂时不知道什么地方会用到
0,
0,
2,
imgs[0].width, // width
imgs[0].height, // height
1, // depth 多少个纹理
gl.RGBA, // format
gl.UNSIGNED_BYTE, // type
imgs[2]
);
详细代码请见:https://github.com/kenkozheng/HTML5_research/tree/master/WebGL/sampler2DArray
参考资料
https://www.nxrte.com/jishu/19240.html
https://juejin.cn/post/6844903846678888461
https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/texImage3D
https://github.com/WebGLSamples/WebGL2Samples/blob/master/samples/texture_2d_array.html