Table des matières

WebGL2Fundamentals.org

Fix, Fork, Contribute

WebGL2 3D - Textures de données

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

FormatTypeCanauxOctets par 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

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 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

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 :

  • un seul nombre comme 8 signifie 8 bits qui seront normalisés de 0 à 1
  • un nombre précédé d’un s comme s8 signifie un nombre signé de 8 bits qui sera normalisé de -1 à 1
  • un nombre précédé d’un f comme f16 signifie un nombre à virgule flottante
  • un nombre précédé d’un i comme i8 signifie un nombre entier
  • un nombre précédé de ui 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é

  <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>
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.

Pixel vs Texel

Parfois, les pixels d'une texture sont appelés texels. Pixel est l'abréviation de Picture Element (élément d'image). Texel est l'abréviation de Texture Element (élément de texture).

Je suis sûr que je recevrai des critiques de certains gourous de la programmation graphique, mais pour autant que je puisse en juger, "texel" est un exemple de jargon. Personnellement, j'utilise généralement "pixel" pour désigner les éléments d'une texture sans y penser. 😇

Problème ou bug ? Créez un ticket sur github.
Utilisez <pre><code>le code ici</code></pre> pour les blocs de code
comments powered by Disqus