Этот пост является продолжением серии постов о WebGL2. Первый начался с основ и предыдущий был о текстурах.
В последнем посте мы рассмотрели, как работают текстуры и как их применять. Мы создавали их из изображений, которые загружали. В этой статье вместо использования изображения мы создадим данные в JavaScript напрямую.
Создание данных для текстуры в JavaScript в основном простое в зависимости от формата текстуры. WebGL2 поддерживает тонну форматов текстур. WebGL2 поддерживает все неразмерные форматы из WebGL1
Format | Type | Channels | Bytes per pixel |
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. В дополнение к этим неразмерным форматам есть куча размерных форматов, включая
Sized Format |
Base Format |
R bits |
G bits |
B bits |
A bits |
Shared bits |
Color renderable |
Texture filterable |
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 | ● |
И эти форматы глубины и трафарета также
Sized Format |
Base Format |
Depth bits |
Stencil bits |
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 |
Легенда:
8
означает 8 бит, которые будут нормализованы от 0 до 1s
как s8
означает знаковое 8-битное число, которое будет нормализовано от -1 до 1f
как f16
означает число с плавающей точкой.i
как i8
означает целое число.ui
как ui8
означает беззнаковое целое число.Мы не будем использовать эту информацию здесь, но я выделил
половинные и float форматы текстур, чтобы показать, в отличие от WebGL1, они всегда доступны в WebGL2,
но они не отмечены как color renderable и/или texture filterable по умолчанию.
Не быть color renderable означает, что они не могут быть отрендерены. Рендеринг в текстуру покрыт
в другом уроке. Не texture filterable означает, что они
должны использоваться только с gl.NEAREST
. Обе эти функции доступны как опциональные
расширения в WebGL2.
Для каждого из форматов вы указываете как internal format (формат, который GPU будет использовать внутренне), так и format и type данных, которые вы предоставляете WebGL. Вот таблица, показывающая, какой format и type вы должны предоставить данные для данного internal format
Internal Format |
Format | Type | Source Bytes Per Pixel |
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 |
Давайте создадим 3x2 пиксельную R8
текстуру. Поскольку это R8
текстура,
есть только 1 значение на пиксель в красном канале.
Мы возьмем образец из последней статьи. Сначала мы изменим координаты текстуры, чтобы использовать всю текстуру на каждой грани куба.
// Заполняем буфер координатами текстуры куба.
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);
Допустимые значения alignment: 1, 2, 4 и 8.
Я подозреваю, что в WebGL вы не сможете измерить разницу
в скорости между выровненными данными и невыровненными данными. Я хотел бы, чтобы по умолчанию
было 1 вместо 4, чтобы эта проблема не кусала новых пользователей, но, чтобы
остаться совместимым с OpenGL, по умолчанию нужно было остаться тем же.
Таким образом, если портированное приложение предоставляет дополненные строки, оно будет работать без изменений.
В то же время, в новом приложении вы можете просто всегда устанавливать это в 1
и
затем покончить с этим.
С этим установленным вещи должны работать
И с этим покрытым давайте перейдем к рендерингу в текстуру.