Содержание

WebGL2Fundamentals.org

Fix, Fork, Contribute

WebGL2 3D - Data Textures

Этот пост является продолжением серии постов о WebGL2. Первый начался с основ и предыдущий был о текстурах.

В последнем посте мы рассмотрели, как работают текстуры и как их применять. Мы создавали их из изображений, которые загружали. В этой статье вместо использования изображения мы создадим данные в JavaScript напрямую.

Создание данных для текстуры в JavaScript в основном простое в зависимости от формата текстуры. WebGL2 поддерживает тонну форматов текстур. WebGL2 поддерживает все неразмерные форматы из WebGL1

FormatTypeChannelsBytes per pixel
RGBAUNSIGNED_BYTE44
RGBUNSIGNED_BYTE33
RGBAUNSIGNED_SHORT_4_4_4_442
RGBAUNSIGNED_SHORT_5_5_5_142
RGBUNSIGNED_SHORT_5_6_532
LUMINANCE_ALPHAUNSIGNED_BYTE22
LUMINANCEUNSIGNED_BYTE11
ALPHAUNSIGNED_BYTE11

Они называются неразмерными, потому что то, как они фактически представлены внутренне, не определено в 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 ui10ui2
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 ui16ui16
RGBA32I RGBA i32 i32 i32 i32
RGBA32UI RGBA ui32 ui32 ui32ui32

И эти форматы глубины и трафарета также

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 до 1
  • число, предшествующее s как s8 означает знаковое 8-битное число, которое будет нормализовано от -1 до 1
  • число, предшествующее f как 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

  <tr><td>RGBA                                            </td><td>RGBA            </td><td>UNSIGNED_BYTE                  </td><td>4  </td></tr>
  <tr><td>RGBA                                            </td><td>RGBA            </td><td>UNSIGNED_SHORT_4_4_4_4         </td><td>2  </td></tr>
  <tr><td>RGBA                                            </td><td>RGBA            </td><td>UNSIGNED_SHORT_5_5_5_1         </td><td>2  </td></tr>
  <tr><td>RGB                                             </td><td>RGB             </td><td>UNSIGNED_BYTE                  </td><td>3  </td></tr>
  <tr><td>RGB                                             </td><td>RGB             </td><td>UNSIGNED_SHORT_5_6_5           </td><td>2  </td></tr>
  <tr><td>LUMINANCE_ALPHA                                 </td><td>LUMINANCE_ALPHA </td><td>UNSIGNED_BYTE                  </td><td>2  </td></tr>
  <tr><td>LUMINANCE                                       </td><td>LUMINANCE       </td><td>UNSIGNED_BYTE                  </td><td>1  </td></tr>
  <tr><td>ALPHA                                           </td><td>ALPHA           </td><td>UNSIGNED_BYTE                  </td><td>1  </td></tr>

</tbody>
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 и затем покончить с этим.

С этим установленным вещи должны работать

И с этим покрытым давайте перейдем к рендерингу в текстуру.

Пиксель vs Тексель

Иногда пиксели в текстуре называются текселями. Пиксель - это сокращение от Picture Element. Тексель - это сокращение от Texture Element.

Я уверен, что получу нагоняй от какого-нибудь гуру графики, но насколько я могу судить, "тексель" - это пример жаргона. Лично я обычно использую "пиксель" при обращении к элементам текстуры, не думая об этом. 😇

Есть предложения или замечания? Создайте issue на GitHub.
comments powered by Disqus