Textures
Une texture est un tableau de données indexé par des coordonnées 2D (ou 3D). En rendu, c'est l'image projetée sur un mesh — mais aussi des données de normales, de rugosité (roughness), de profondeur (depth), d'émission (emissive), de couleur de base (albedo/diffuse), de métallicité (metallic), d’occlusion ambiante (AO), voire des informations spécifiques comme la transparence (alpha💡) ou des masques personnalisés, …
On reviendra dessus pour créer des animations sur le logiciel Blender dans les prochains chapitres, pour faire des rendus AAA, …
Dans un pipeline graphique moderne en C++ & MSL, une texture n’est pas seulement une image : c’est une ressource GPU typée, souvent stockée en mémoire optimisée (VRAM), échantillonnée dans les shaders via des coordonnées UV interpolées. Le fragment shader utilise ces données pour reconstruire les propriétés physiques d’un matériau selon un modèle de rendu (souvent PBR — Physically Based Rendering), combinant lumière, normales perturbées (normal mapping), microfacettes (roughness/metallic) et contributions lumineuses directes et indirectes.
Pour dessiner une texture, le code a besoin de 3 choses :
- Une image accessible au GPU.
- toujours à projeter sur un
mesh - Passe 2
- Données montrant à Metal comment placer l'image sur chaque triangle.
- c'est ce que l'on a fait dans les débuts.
- Opérations pour appliquer l'image aux pixels rendus.
La classe MTL::TextureDescriptor est l’objet qui décrit la texture. (2)
Le moteur de rendu crée une texture à partir de l'objet (protocole) MTL::Device en utilisant ce descripteur
Cela crée l'objet et alloue de la mémoire à l'image. Le moteur de rendu doit toujours remplir la mémoire avec des données d'image.
Généralement, une application remplira la mémoire de la texture avec les données d'un fichier image.
Metal ne fournit pas d'API pour charger les données d'image à partir de fichiers, de sorte que les applications doivent utiliser un code personnalisé ou une API qui gère des images telles que MetalKit ou Image I/O (Obj-C).
J’ai choisis l’API stb-image, qui gère aussi l’export des textures avec assimp dans le logiciel Blender).
Un exemple de texture produite sur Blender et important dans un projet 3D MSL & C++ comprenant une AO map, une diffuse map, une roughness map et une normal map. La position du soleil est envoyée au shader depuis Metal, ce genre d'import est important à maîtriser lorsqu'il s'agira de rendre des animations complexes, on en peut pas tout hardcoder.
Il sera toujours plus optimisé de générer la texture directement sur Metal, mais, étant donné la complexité et le détail des jeux vidéos, certains caractères sont réalisés sur un logiciel de 3D externe, même dans les plus grosses boites de jeux vidéos, par exemple.
Créer une texture
MTL::TextureDescriptor* td = MTL::TextureDescriptor::alloc()->init();
td->setTextureType(MTL::TextureType2D);
td->setWidth(1024);
td->setHeight(1024);
td->setMipmapLevelCount(1);
// Format selon l'usage :
td->setPixelFormat(MTL::PixelFormatRGBA8Unorm_sRGB); // albedo (couleur perceptuelle)
// td->setPixelFormat(MTL::PixelFormatRGBA8Unorm); // données linéaires (normal, roughness)
// td->setPixelFormat(MTL::PixelFormatR32Float); // depth, single channel float
td->setUsage(MTL::TextureUsageShaderRead);
td->setStorageMode(MTL::StorageModeShared);
MTL::Texture* texture = m_p_device->newTexture(td);
td->release();
Uploader des pixels
MTL::Region region = MTL::Region::Make2D(0, 0, width, height);
texture->replaceRegion(
region,
0, // mipmap level
pixels, // pointeur vers tes données RGBA
width * 4 // bytesPerRow : 4 bytes par pixel (RGBA8)
);
sRGB vs Linéaire — critique pour le rendu correct
C'est une des erreurs les plus fréquentes : utiliser le mauvais espace colorimétrique.
| Texture | Format | Pourquoi |
|---|---|---|
| Albedo (couleur) | RGBA8Unorm_sRGB |
Metal applique la correction gamma automatiquement |
| Normal map | RGBA8Unorm |
Données vectorielles — linéaire impératif |
| Roughness / Metallic | R8Unorm |
Canal unique, données linéaires |
| Depth | Depth32Float |
Float 32 bits — précision maximale |
Sampler — comment le GPU lit la texture
Le sampler définit comment interpoler entre les texels quand on échantillonne à une coordonnée non entière.
MTL::SamplerDescriptor* sd = MTL::SamplerDescriptor::alloc()->init();
sd->setMinFilter(MTL::SamplerMinMagFilterLinear);
sd->setMagFilter(MTL::SamplerMinMagFilterLinear);
sd->setMipFilter(MTL::SamplerMipFilterLinear); // trilinear
sd->setSAddressMode(MTL::SamplerAddressModeRepeat);
sd->setTAddressMode(MTL::SamplerAddressModeRepeat);
sd->setMaxAnisotropy(8); // filtrage anisotropique (max.16)
m_p_sampler = m_p_device->newSamplerState(sd);
sd->release();
Lier texture + sampler au fragment shader
// CPU :
enc->setFragmentTexture(m_p_texture, 0);
enc->setFragmentSamplerState(m_p_sampler, 0);
// MSL :
fragment float4 fragment_main(
VertexOut in [[stage_in]],
texture2d<float> albedo [[texture(0)]],
sampler smp [[sampler(0)]])
{
float4 color = albedo.sample(smp, in.uv);
return color;
}
Mipmaps
Les mipmaps sont des versions pré-calculées de la texture à des résolutions décroissantes (512, 256, 128…). Le GPU sélectionne automatiquement le bon niveau selon la distance — évite le aliasing.
// Générer les mipmaps via un command buffer :
MTL::CommandBuffer* cmd = m_p_queue->commandBuffer();
MTL::BlitCommandEncoder* blit = cmd->blitCommandEncoder();
blit->generateMipmaps(m_p_texture);
blit->endEncoding();
cmd->commit();
cmd->waitUntilCompleted();
Chaque type de texture alimente une étape précise du calcul :
l’albedo définit la couleur diffuse sans éclairage,
la normal map modifie la normale de surface pour simuler du relief sans géométrie supplémentaire,
la roughness contrôle la diffusion des reflets,
la metallic détermine le comportement conducteur ou diélectrique,
la depth (ou height) peut servir au parallax mapping ou aux tests de profondeur,
l’emissive ajoute une contribution lumineuse indépendante des sources de lumière.
Côté C++, ces textures sont chargées, organisées (souvent via des atlases ou des bindless resources), puis liées au pipeline graphique. Côté MSL, elles sont déclarées comme texture2d