此文上接 WebGL2 系列文章,第一篇是基础概念, 上一篇是纹理。
上节中讲到了纹理的工作原理以及如何使用,我们用下载的图像创建纹理, 在这篇文章中我们将直接用 JavaScript 创建数据。
根据纹理格式,用 JavaScript 为纹理创建数据是比较直接的。 WebGL2 支持大量纹理格式。 WebGL2 支持所有在 WebGL1 中 未定义大小 的格式
格式 | 数据类型 | 通道数 | 单像素字节数 |
RGBA | UNSIGNED_BYTE | 4 | 4 |
RGB | UNSIGNED_BYTE | 3 | 3 |
RGBA | UNSIGNED_SHORT_4_4_4_4 | 4 | 2 |
RGBA | UNSIGNED_SHORT_5_5_5_1 | 4 | 2 |
RGB | UNSIGNED_SHORT_5_6_5 | 3 | 2 |
LUMINANCE_ALPHA | UNSIGNED_BYTE | 2 | 2 |
LUMINANCE | UNSIGNED_BYTE | 1 | 1 |
ALPHA | UNSIGNED_BYTE | 1 | 1 |
它们被称为 未定义大小 因为它们在内部的实际表示方式在 WebGL1 中是未定义的。 它们在 WebGL2 中被定义。 除了那些未定义大小的格式之外,还有许多定义了大小的格式,包括
大小 格式 |
基础 格式 |
R 位 |
G 位 |
B 位 |
A 位 |
共享 位 |
可渲染 颜色 |
纹理 可过滤 |
R8 | RED | 8 | ● | ● | ||||
R8_SNORM | RED | s8 | ● | |||||
RG8 | RG | 8 | 8 | ● | ● | |||
RG8_SNORM | RG | s8 | s8 | ● | ||||
RGB8 | RGB | 8 | 8 | 8 | ● | ● | ||
RGB8_SNORM | RGB | s8 | s8 | s8 | ● | |||
RGB565 | RGB | 5 | 6 | 5 | ● | ● | ||
RGBA4 | RGBA | 4 | 4 | 4 | 4 | ● | ● | |
RGB5_A1 | RGBA | 5 | 5 | 5 | 1 | ● | ● | |
RGBA8 | RGBA | 8 | 8 | 8 | 8 | ● | ● | |
RGBA8_SNORM | RGBA | s8 | s8 | s8 | s8 | ● | ||
RGB10_A2 | RGBA | 10 | 10 | 10 | 2 | ● | ● | |
RGB10_A2UI | RGBA | ui10 | ui10 | ui10 | ui2 | ● | ||
SRGB8 | RGB | 8 | 8 | 8 | ● | |||
SRGB8_ALPHA8 | RGBA | 8 | 8 | 8 | 8 | ● | ● | |
R16F | RED | f16 | ● | |||||
RG16F | RG | f16 | f16 | ● | ||||
RGB16F | RGB | f16 | f16 | f16 | ● | |||
RGBA16F | RGBA | f16 | f16 | f16 | f16 | ● | ||
R32F | RED | f32 | ||||||
RG32F | RG | f32 | f32 | |||||
RGB32F | RGB | f32 | f32 | f32 | ||||
RGBA32F | RGBA | f32 | f32 | f32 | f32 | |||
R11F_G11F_B10F | RGB | f11 | f11 | f10 | ● | |||
RGB9_E5 | RGB | 9 | 9 | 9 | 5 | ● | ||
R8I | RED | i8 | ● | |||||
R8UI | RED | ui8 | ● | |||||
R16I | RED | i16 | ● | |||||
R16UI | RED | ui16 | ● | |||||
R32I | RED | i32 | ● | |||||
R32UI | RED | ui32 | ● | |||||
RG8I | RG | i8 | i8 | ● | ||||
RG8UI | RG | ui8 | ui8 | ● | ||||
RG16I | RG | i16 | i16 | ● | ||||
RG16UI | RG | ui16 | ui16 | ● | ||||
RG32I | RG | i32 | i32 | ● | ||||
RG32UI | RG | ui32 | ui32 | ● | ||||
RGB8I | RGB | i8 | i8 | i8 | ||||
RGB8UI | RGB | ui8 | ui8 | ui8 | ||||
RGB16I | RGB | i16 | i16 | i16 | ||||
RGB16UI | RGB | ui16 | ui16 | ui16 | ||||
RGB32I | RGB | i32 | i32 | i32 | ||||
RGB32UI | RGB | ui32 | ui32 | ui32 | ||||
RGBA8I | RGBA | i8 | i8 | i8 | i8 | ● | ||
RGBA8UI | RGBA | ui8 | ui8 | ui8 | ui8 | ● | ||
RGBA16I | RGBA | i16 | i16 | i16 | i16 | ● | ||
RGBA16UI | RGBA | ui16 | ui16 | ui16 | ui16 | ● | ||
RGBA32I | RGBA | i32 | i32 | i32 | i32 | ● | ||
RGBA32UI | RGBA | ui32 | ui32 | ui32 | ui32 | ● |
还有这些深度和模板格式 And these depth and stencil formats as well
大小 格式 |
基本 格式 |
深度 位 |
模板 位 |
DEPTH_COMPONENT16 | DEPTH_COMPONENT | 16 | |
DEPTH_COMPONENT24 | DEPTH_COMPONENT | 24 | |
DEPTH_COMPONENT32F | DEPTH_COMPONENT | f32 | |
DEPTH24_STENCIL8 | DEPTH_STENCIL | 24 | ui8 |
DEPTH32F_STENCIL8 | DEPTH_STENCIL | f32 | ui8 |
Legend:
8
表示 8 位将归一化为 0 到 1s
的数字比如 s8
表示一个有符号的 8 位数字,将归一化为 -1 到 1f
的数字比如 f16
表示浮点数。i
的数字比如 i8
表示整数。ui
的数字比如 ui8
表示无符号整数。我们不会在此处使用此信息, 但我 突出显示
了 half 和 float 纹理格式以显示与 WebGL1 的不同,它们始终在 WebGL2 中可用,
但默认情况下它们未标记为可渲染颜色和/或可过滤纹理。
不可渲染意味着它们不能被渲染。 渲染到纹理将在另一课中介绍。不可过滤纹理意味着它们必须仅用于 gl.NEAREST
。这两个特性都可以作为 WebGL2 中的可选扩展。
对于每种格式,你都指定 内部格式 (GPU 将在内部使用的格式)以及您提供给 WebGL 的数据的 格式 和 类型 。这是你提供给内部格式的数据的 格式 和 类型
内部 格式 |
格式 | 类型 | 每像素 源字节数 |
RGBA8 RGB5_A1 RGBA4 SRGB8_ALPHA8 | RGBA | UNSIGNED_BYTE | 4 |
RGBA8_SNORM | RGBA | BYTE | 4 |
RGBA4 | RGBA | UNSIGNED_SHORT_4_4_4_4 | 2 |
RGB5_A1 | RGBA | UNSIGNED_SHORT_5_5_5_1 | 2 |
RGB10_A2 RGB5_A1 | RGBA | UNSIGNED_INT_2_10_10_10_REV | 4 |
RGBA16F | RGBA | HALF_FLOAT | 8 |
RGBA32F RGBA16F | RGBA | FLOAT | 16 |
RGBA8UI | RGBA_INTEGER | UNSIGNED_BYTE | 4 |
RGBA8I | RGBA_INTEGER | BYTE | 4 |
RGBA16UI | RGBA_INTEGER | UNSIGNED_SHORT | 8 |
RGBA16I | RGBA_INTEGER | SHORT | 8 |
RGBA32UI | RGBA_INTEGER | UNSIGNED_INT | 16 |
RGBA32I | RGBA_INTEGER | INT | 16 |
RGB10_A2UI | RGBA_INTEGER | UNSIGNED_INT_2_10_10_10_REV | 4 |
RGB8 RGB565 SRGB8 | RGB | UNSIGNED_BYTE | 3 |
RGB8_SNORM | RGB | BYTE | 3 |
RGB565 | RGB | UNSIGNED_SHORT_5_6_5 | 2 |
R11F_G11F_B10F | RGB | UNSIGNED_INT_10F_11F_11F_REV | 4 |
RGB9_E5 | RGB | UNSIGNED_INT_5_9_9_9_REV | 4 |
RGB16F R11F_G11F_B10F RGB9_E5 | RGB | HALF_FLOAT | 6 |
RGB32F RGB16F R11F_G11F_B10F RGB9_E5 | RGB | FLOAT | 12 |
RGB8UI | RGB_INTEGER | UNSIGNED_BYTE | 3 |
RGB8I | RGB_INTEGER | BYTE | 3 |
RGB16UI | RGB_INTEGER | UNSIGNED_SHORT | 6 |
RGB16I | RGB_INTEGER | SHORT | 6 |
RGB32UI | RGB_INTEGER | UNSIGNED_INT | 12 |
RGB32I | RGB_INTEGER | INT | 12 |
RG8 | RG | UNSIGNED_BYTE | 2 |
RG8_SNORM | RG | BYTE | 2 |
RG16F | RG | HALF_FLOAT | 4 |
RG32F RG16F | RG | FLOAT | 8 |
RG8UI | RG_INTEGER | UNSIGNED_BYTE | 2 |
RG8I | RG_INTEGER | BYTE | 2 |
RG16UI | RG_INTEGER | UNSIGNED_SHORT | 4 |
RG16I | RG_INTEGER | SHORT | 4 |
RG32UI | RG_INTEGER | UNSIGNED_INT | 8 |
RG32I | RG_INTEGER | INT | 8 |
R8 | RED | UNSIGNED_BYTE | 1 |
R8_SNORM | RED | BYTE | 1 |
R16F | RED | HALF_FLOAT | 2 |
R32F R16F | RED | FLOAT | 4 |
R8UI | RED_INTEGER | UNSIGNED_BYTE | 1 |
R8I | RED_INTEGER | BYTE | 1 |
R16UI | RED_INTEGER | UNSIGNED_SHORT | 2 |
R16I | RED_INTEGER | SHORT | 2 |
R32UI | RED_INTEGER | UNSIGNED_INT | 4 |
R32I | RED_INTEGER | INT | 4 |
DEPTH_COMPONENT16 | DEPTH_COMPONENT | UNSIGNED_SHORT | 2 |
DEPTH_COMPONENT24 DEPTH_COMPONENT16 | DEPTH_COMPONENT | UNSIGNED_INT | 4 |
DEPTH_COMPONENT32F | DEPTH_COMPONENT | FLOAT | 4 |
DEPTH24_STENCIL8 | DEPTH_STENCIL | UNSIGNED_INT_24_8 | 4 |
DEPTH32F_STENCIL8 | DEPTH_STENCIL | FLOAT_32_UNSIGNED_INT_24_8_REV | 8 |
RGBA | RGBA | UNSIGNED_BYTE | 4 |
RGBA | RGBA | UNSIGNED_SHORT_4_4_4_4 | 2 |
RGBA | RGBA | UNSIGNED_SHORT_5_5_5_1 | 2 |
RGB | RGB | UNSIGNED_BYTE | 3 |
RGB | RGB | UNSIGNED_SHORT_5_6_5 | 2 |
LUMINANCE_ALPHA | LUMINANCE_ALPHA | UNSIGNED_BYTE | 2 |
LUMINANCE | LUMINANCE | UNSIGNED_BYTE | 1 |
ALPHA | ALPHA | UNSIGNED_BYTE | 1 |
让我们创建一个 3×2 像素的 R8
纹理, 因为是 R8
纹理,
所以在红色通道里每个像素只有一个值
我们继续使用上篇文章中的例子,首先修改纹理坐标,每个面使用整个纹理
// 填充立方体纹理坐标的缓冲
function setTexcoords(gl) {
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
// 正面
0, 0,
0, 1,
1, 0,
1, 0,
0, 1,
1, 1,
...
然后修改代码创建一个纹理
// 创建一个纹理
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
-// 用 1x1 的蓝色像素填充纹理
-gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
- new Uint8Array([0, 0, 255, 255]));
// 用 3x2 的像素填充纹理
const level = 0;
const internalFormat = gl.R8;
const width = 3;
const height = 2;
const border = 0;
const format = gl.RED;
const type = gl.UNSIGNED_BYTE;
const data = new Uint8Array([
128, 64, 128,
0, 192, 0,
]);
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border,
format, type, data);
// 设置筛选器,我们不需要使用贴图所以就不用筛选器了
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
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);
-// 异步加载图像
-...
这是结果
哦!为什么不管用?!?!!
查看 JavaScript 控制台看到这样的错误信息
WebGL: INVALID_OPERATION: texImage2D: ArrayBufferView not big enough for request
结果是 WebGL 中有一种首次创建 OpenGL 后的模糊设定, 计算机有时在数据为某些特定大小时速度会快一些, 例如一次拷贝 2,4 或 8 个字节比一次拷贝 1 个字节要快, WebGL 默认使用 4 字节长度,所以它期望每一行数据是多个 4 字节数据(最后一行除外)。
我们之前的数据每行只有 3 个字节,总共为 6 字节, 但是 WebGL 试图在第一行获取 4 个字节,第二行获取 3 个字节, 总共 7 个字节,所以会出现这样的报错。
我们可以告诉 WebGL 一次处理 1 个字节
const alignment = 1;
gl.pixelStorei(gl.UNPACK_ALIGNMENT, alignment);
有效参数为 1,2,4 和 8.
我觉得你可能无法计算出对齐数据和非对齐数据的速度区别, 所以希望默认值是 1 而不是 4, 这样这个问题就不会困扰新手, 但是为了适配 OpenGL,所以要保留相同的默认设置,这样移植应用就不用改变行数, 然后可以为新的应用在需要的地方设置属性为 1。
有了这个设置后就能正常运行了
有着这些基础就可以讲渲染到材质了。