Cet article est la suite d’une série d’articles sur WebGL2. Le premier a commencé par les bases et le précédent portait sur les textures.
Dans le dernier article, nous avons vu comment fonctionnent les textures et comment les appliquer. Nous les avons créées à partir d’images téléchargées. Dans cet article, au lieu d’utiliser une image, nous allons créer les données directement en JavaScript.
Créer des données pour une texture en JavaScript est assez simple selon le format de texture. WebGL2 supporte énormément de formats de textures. WebGL2 supporte tous les formats non dimensionnés de WebGL1
| Format | Type | Canaux | Octets par 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 |
Ils sont appelés non dimensionnés car la façon dont ils sont réellement représentés en interne n’est pas définie dans WebGL1. Elle l’est dans WebGL2. En plus de ces formats non dimensionnés, il existe une foule de formats dimensionnés, notamment
| Format dimensionné |
Format de base |
bits R |
bits G |
bits B |
bits A |
bits partagés |
Rendu couleur |
Filtrage texture |
| 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 | ● |
Et également ces formats de profondeur et de stencil
| Format dimensionné |
Format de base |
bits de profondeur |
bits de stencil |
| 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 |
Légende :
8 signifie 8 bits qui seront normalisés de 0 à 1s comme s8 signifie un nombre signé de 8 bits qui sera normalisé de -1 à 1f comme f16 signifie un nombre à virgule flottantei comme i8 signifie un nombre entierui comme ui8 signifie un entier non signéNous n’utiliserons pas ces informations ici, mais j’ai mis en évidence
les formats de textures à demi-précision et flottants pour montrer que contrairement à WebGL1, ils sont toujours disponibles dans WebGL2,
mais ils ne sont pas marqués comme rendables en couleur et/ou filtrables par texture par défaut.
Ne pas être rendable en couleur signifie qu’on ne peut pas faire de rendu vers eux. Le rendu vers une texture est
couvert dans une autre leçon. Ne pas être filtrable par texture signifie qu’ils
doivent être utilisés avec gl.NEAREST uniquement. Ces deux fonctionnalités sont disponibles comme extensions optionnelles dans WebGL2.
Pour chacun des formats, vous spécifiez à la fois le format interne (le format que le GPU utilisera en interne) et le format et le type des données que vous fournissez à WebGL. Voici un tableau indiquant quel format et type vous devez fournir pour un format interne donné
| Format interne |
Format | Type | Octets source par 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 |
Créons une texture R8 de 3x2 pixels. Comme c’est une texture R8,
il n’y a qu’une seule valeur par pixel dans le canal rouge.
Nous allons partir de l’exemple du dernier article. D’abord, nous allons modifier les coordonnées de texture pour utiliser toute la texture sur chaque face du cube.
// Remplir le buffer avec les coordonnées de texture du cube.
function setTexcoords(gl) {
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([
// face avant
0, 0,
0, 1,
1, 0,
1, 0,
0, 1,
1, 1,
...
Puis, nous allons modifier le code qui crée une texture
// Créer une texture.
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
-// Remplir la texture avec un pixel bleu 1x1.
-gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
- new Uint8Array([0, 0, 255, 255]));
// remplir la texture avec 3x2 pixels
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);
// définir le filtrage pour ne pas avoir besoin de mips et qu'il ne soit pas filtré
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);
-// Charger une image de façon asynchrone
-...
Et voilà le résultat
Oups ! Pourquoi ça ne fonctionne pas ?!?!?
En vérifiant la console JavaScript, nous voyons une erreur comme celle-ci
WebGL: INVALID_OPERATION: texImage2D: ArrayBufferView not big enough for request
Il s’avère qu’il y a un paramètre quelque peu obscur dans WebGL hérité du moment où OpenGL a été créé. Les ordinateurs vont parfois plus vite quand les données ont une certaine taille. Par exemple, il peut être plus rapide de copier 2, 4 ou 8 octets à la fois plutôt qu’un seul. WebGL utilise par défaut 4 octets à la fois, donc il s’attend à ce que chaque ligne de données soit un multiple de 4 octets (sauf pour la dernière ligne).
Nos données ci-dessus font seulement 3 octets par ligne, 6 octets au total, mais WebGL va essayer de lire 4 octets pour la première ligne et 3 octets pour la 2ème ligne pour un total de 7 octets, c’est pourquoi il se plaint.
Nous pouvons indiquer à WebGL de traiter 1 octet à la fois comme ceci
const alignment = 1;
gl.pixelStorei(gl.UNPACK_ALIGNMENT, alignment);
Les valeurs d’alignement valides sont 1, 2, 4 et 8.
Je soupçonne que dans WebGL, vous ne pourrez pas mesurer de différence
de vitesse entre des données alignées et non alignées. Je souhaiterais que la valeur par défaut
soit 1 plutôt que 4 pour que ce problème ne surprenne pas les nouveaux utilisateurs, mais, afin de
rester compatible avec OpenGL, la valeur par défaut devait rester la même.
Ainsi, si une application portée fournit des lignes avec du rembourrage (padding), elle fonctionnera sans modification.
En même temps, dans une nouvelle application, vous pouvez simplement toujours le définir à 1 et
en avoir fini.
Avec ce paramètre, les choses devraient fonctionner
Avec ceci couvert, passons au rendu vers une texture.