Anatomie d'une frame — Le cycle de vie complet de suckless-ogl
Posted on Sun 29 March 2026 in Développement
Anatomie d'une frame : le cycle de vie complet de suckless-ogl
De main() aux photons sur l'écran — une plongée complète dans un moteur PBR OpenGL moderne écrit en C.
Introduction
suckless-ogl est un moteur de rendu PBR (Physically-Based Rendering) minimaliste et performant, écrit en C11 avec OpenGL 4.4 Core Profile. Il affiche une grille de 100 sphères aux matériaux variés (métaux, diélectriques, peintures, organiques…) éclairées par Image-Based Lighting (IBL), avec un pipeline de post-processing complet : bloom, depth of field, motion blur, FXAA, tone mapping, color grading…
Cet article retrace le cycle de vie complet de l'application : depuis le premier octet alloué dans main() jusqu'au moment où le GPU présente la première frame complètement éclairée à l'écran. On va traverser chaque couche — mémoire CPU, ressources GPU, la poignée de main X11/GLFW, la création du contexte OpenGL, la compilation des shaders, le chargement asynchrone de textures, et l'architecture de rendu multi-pass qui produit chaque frame.
Ce qu'on va couvrir
Chapitre 1 — Le point d'entrée
Tout commence dans main() (src/main.c) :
int main(int argc, char* argv[])
{
tracy_manager_init_global(); // 1. Bootstrap du profiler
CliAction action = cli_handle_args(argc, argv); // 2. Parsing CLI
if (action == CLI_ACTION_EXIT_SUCCESS) return EXIT_SUCCESS;
if (action == CLI_ACTION_EXIT_FAILURE) return EXIT_FAILURE;
// 3. Allocation SIMD-aligned de la structure App
App* app = (App*)platform_aligned_alloc(sizeof(App), SIMD_ALIGNMENT);
*app = (App){0};
// 4. Initialisation complète
if (!app_init(app, WINDOW_WIDTH, WINDOW_HEIGHT, "Icosphere Phong"))
{ app_cleanup(app); platform_aligned_free(app); return EXIT_FAILURE; }
// 5. Boucle principale
app_run(app);
// 6. Nettoyage
app_cleanup(app);
platform_aligned_free(app);
return EXIT_SUCCESS;
}
Le design est volontairement simple — toute la complexité est encapsulée dans app_init() → app_run() → app_cleanup().
Décisions de design
| Décision | Pourquoi ? |
|---|---|
| Allocation alignée SIMD | La structure App contient des mat4/vec3 (via cglm) qui bénéficient de l'alignement 16 octets pour la vectorisation SSE/NEON |
Zero-init {0} |
État déterministe — chaque pointeur commence à NULL, chaque flag à 0 |
| Tracy en premier | Le profiler doit être initialisé avant tout autre sous-système pour capturer la timeline complète |
Un seul App struct |
Tout l'état applicatif vit dans une allocation contiguë — cache-friendly, facile à passer |
graph TD
A("🚀 main()") --> B("app_init()")
B --> B1("Fenêtre + Contexte OpenGL")
B --> B2("Caméra & Entrées")
B --> B3("Scène — Ressources GPU")
B --> B4("Thread de chargement async")
B --> B5("Pipeline Post-Processing")
B --> B6("Systèmes de profiling")
B1 & B2 & B3 & B4 & B5 & B6 --> C("app_run() — Boucle principale")
C --> C1("Poll Events")
C1 --> C2("Physique caméra")
C2 --> C3("renderer_draw_frame()")
C3 --> C4("SwapBuffers")
C4 -->|"frame suivante"| C1
C --> D("app_cleanup()")
D --> E("🏁 Fin")
classDef entryExit fill:#fce4ec,stroke:#c2185b,stroke-width:2.5px,color:#2d2d2d
classDef keyFunc fill:#fff59d,stroke:#f9a825,stroke-width:2px,color:#2d2d2d
classDef subsystem fill:#ffffff,stroke:#aaaaaa,stroke-width:1.5px,color:#444444
classDef loopNode fill:#e3f2fd,stroke:#42a5f5,stroke-width:1.5px,color:#2d2d2d
class A,E entryExit
class B,C,D keyFunc
class B1,B2,B3,B4,B5,B6 subsystem
class C1,C2,C3,C4 loopNode
Chapitre 2 — Ouvrir une fenêtre (GLFW + X11 + OpenGL)
Le premier vrai travail se fait dans window_create() (src/window.c).
2.1 — Initialisation GLFW et Window Hints
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4); // OpenGL 4.4
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GL_TRUE); // Messages de debug
glfwWindowHint(GLFW_SAMPLES, DEFAULT_SAMPLES); // MSAA = 1 (désactivé)
En coulisses, GLFW effectue un handshake X11 complet :
sequenceDiagram
participant App as Application
participant GLFW as GLFW
participant X11 as Serveur X11
participant Mesa as Mesa/Driver GPU
participant GPU as GPU
App->>GLFW: glfwInit()
GLFW->>X11: XOpenDisplay()
X11-->>GLFW: Display* (connexion)
App->>GLFW: glfwCreateWindow(1920, 1080)
GLFW->>X11: XCreateWindow() + GLX setup
X11->>Mesa: glXCreateContextAttribsARB(4.4 Core, Debug)
Mesa->>GPU: Allocation command buffer + état contexte
Mesa-->>X11: GLXContext
X11-->>GLFW: Fenêtre + Contexte prêts
App->>GLFW: glfwMakeContextCurrent()
GLFW->>Mesa: glXMakeCurrent()
Mesa->>GPU: Bind du contexte au thread appelant
2.2 — GLAD : chargement des pointeurs de fonctions OpenGL
gladLoadGLLoader((GLADloadproc)glfwGetProcAddress);
OpenGL n'est pas une bibliothèque au sens classique — c'est une spécification. Les adresses réelles des fonctions vivent dans le driver GPU (Mesa, NVIDIA, AMD). GLAD interroge chaque adresse à l'exécution via glXGetProcAddress et remplit une table de pointeurs de fonctions. Après cet appel, glCreateShader, glDispatchCompute, etc. deviennent utilisables.
2.3 — Contexte debug OpenGL
setup_opengl_debug();
Cela active GL_DEBUG_OUTPUT_SYNCHRONOUS et enregistre un callback qui intercepte chaque erreur, warning et hint de performance GL. Une table de hachage déduplique les messages (log uniquement à la première occurrence).
2.4 — Capture d'entrées et VSync
glfwSwapInterval(0); // VSync OFF — FPS illimité
glfwSetInputMode(app->window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); // Mode FPS
Le curseur est capturé en mode relatif — les mouvements souris produisent des deltas pour le contrôle orbital de la caméra.
Chapitre 3 — Initialisation côté CPU
Avant de toucher au GPU, plusieurs systèmes CPU sont initialisés.
3.1 — La caméra orbitale
camera_init(&app->camera, 20.0F, -90.0F, 0.0F);
La caméra démarre à :
- Distance : 20 unités de l'origine
- Yaw : −90° (regarde le long de −Z)
- Pitch : 0° (niveau de l'horizon)
- FOV : 60° vertical
- Z-clip : [0.1, 1000.0]
Elle utilise un modèle physique à pas fixe (60 Hz) avec lissage exponentiel pour la rotation :
graph LR
subgraph "Pipeline de mise à jour caméra"
A("Delta souris") -->|"Filtre EMA"| B("yaw_target / pitch_target")
B -->|"Lerp α=0.1"| C("yaw / pitch (lissés)")
C --> D("camera_update_vectors()")
D --> E("vecteurs front, right, up")
E --> F("Matrice de vue (lookAt)")
end
subgraph "Physique (60Hz fixe)"
G("Touches ZQSD") --> H("Vélocité cible")
H -->|"accélération × dt"| I("Vélocité courante")
I -->|"friction"| J("Position += vel × dt")
J --> K("Head bobbing (sin)")
end
classDef keyFunc fill:#fff59d,stroke:#f9a825,stroke-width:2px,color:#2d2d2d
classDef subsystem fill:#ffffff,stroke:#aaaaaa,stroke-width:1.5px,color:#444444
classDef loopNode fill:#e3f2fd,stroke:#42a5f5,stroke-width:1.5px,color:#2d2d2d
class D,F keyFunc
class A,B,C,E subsystem
class G,H,I,J,K loopNode
3.2 — Thread de chargement asynchrone
app->async_loader = async_loader_create(&app->tracy_mgr);
Un thread POSIX dédié est créé pour les I/O en arrière-plan. Il dort sur une variable de condition (pthread_cond_wait) jusqu'à ce qu'un travail soit soumis. Cela empêche les lectures disque de bloquer la boucle de rendu.
stateDiagram-v2
[*] --> IDLE
IDLE --> PENDING: async_loader_request()
PENDING --> LOADING: Le worker se réveille
LOADING --> WAITING_FOR_PBO: I/O terminée, besoin d'un buffer GPU
WAITING_FOR_PBO --> CONVERTING: Le thread principal fournit le PBO
CONVERTING --> READY: Conversion SIMD Float→Half terminée
READY --> IDLE: Le thread principal consomme le résultat
Chapitre 4 — Initialisation de la scène (le GPU se réveille)
scene_init() (src/scene.c) est l'endroit où le GPU reçoit son premier vrai travail.
4.1 — État initial de la scène
scene->subdivisions = 3; // Icosphère niveau 3
scene->wireframe = 0; // Remplissage solide
scene->show_envmap = 1; // Skybox visible
scene->billboard_mode = 1; // Sphères transparentes (billboard)
scene->sorting_mode = SORTING_MODE_GPU_BITONIC; // Tri GPU
scene->gi_mode = GI_MODE_OFF; // Pas de GI
scene->specular_aa_enabled = 1; // AA basé sur la courbure
4.2 — Textures factices et BRDF LUT
Deux textures sentinelles sont créées immédiatement — elles servent de fallback tant qu'une texture IBL n'est pas prête :
scene->dummy_black_tex = render_utils_create_color_texture(0.0, 0.0, 0.0, 0.0); // 1×1 RGBA
scene->dummy_white_tex = render_utils_create_color_texture(1.0, 1.0, 1.0, 1.0); // 1×1 RGBA
Puis la BRDF LUT (Look-Up Table) est générée une seule fois via compute shader :
scene->brdf_lut_tex = build_brdf_lut_map(512);
| Propriété | Valeur |
|---|---|
| Taille | 512 × 512 |
| Format | GL_RG16F (2 canaux, 16-bit float chacun) |
| Contenu | BRDF split-sum pré-intégrée (Fresnel-Schlick–GGX) |
| Shader | shaders/IBL/spbrdf.glsl (compute) |
| Work groups | 16 × 16 (512/32 par axe) |
Cette texture mappe (NdotV, roughness) → (F0_scale, F0_bias) et est utilisée chaque frame par le fragment shader PBR pour éviter l'intégration BRDF coûteuse en temps réel.
4.3 — Deux modes de rendu : Billboard Ray-Tracing vs. Mesh Icosphère
Le moteur supporte deux stratégies de rendu de sphères. Le mode par défaut est le billboard ray-tracing.
Mode par défaut : Billboard + Ray-Tracing par pixel (billboard_mode = 1)
Chaque sphère est rendue comme un simple quad aligné à l'écran (4 vertices, 2 triangles). Le fragment shader effectue une intersection rayon-sphère analytique par pixel, produisant des sphères mathématiquement parfaites.
Avantages :
- Silhouettes parfaites au pixel près (jamais de facettage polygonal)
- Profondeur correcte par pixel (
gl_FragDepthécrit depuis le point d'intersection) - Normales analytiquement lisses (normalisé
hitPos − center) - Anti-aliasing des bords via atténuation douce du discriminant
- Vraie transparence alpha (aspect verre, avec tri back-to-front)
graph LR
subgraph "Billboard Ray-Tracing (Défaut)"
A("Quad 4-vertex
(par instance)") -->|"Vertex Shader :
projection sur bounds sphère"| B("Quad écran")
B -->|"Fragment Shader :
intersection rayon-sphère"| C("Sphère parfaite
normale + profondeur par pixel")
end
subgraph "Mesh Icosphère (Fallback)"
D("Mesh 642-vertex
(icosaèdre subdivisé)") -->|"Rastérisé en
triangles"| E("Approximation polygonale
(facettée à basse subdiv)")
end
classDef highlight fill:#fff59d,stroke:#f9a825,stroke-width:2px,color:#2d2d2d
classDef fallback fill:#f5f5f5,stroke:#bdbdbd,stroke-width:1.5px,color:#666666
class A,B,C highlight
class D,E fallback
💡 Pourquoi le billboard ray-tracing ? Avec 100 sphères, l'approche billboard utilise 100 × 4 = 400 vertices au total, versus 100 × 642 = 64 200 vertices pour des icosphères niveau 3. Plus important encore, les sphères sont mathématiquement parfaites à tout niveau de zoom — aucun artefact de tessellation.
Fallback : Mesh icosphère instancié (billboard_mode = 0)
Le chemin icosphère génère un icosaèdre subdivisé récursivement :
graph LR
A("Niveau 0
12 vertices
20 triangles") -->|"Subdiviser"| B("Niveau 1
42 vertices
80 triangles")
B -->|"Subdiviser"| C("Niveau 2
162 vertices
320 triangles")
C -->|"Subdiviser"| D("Niveau 3
642 vertices
1 280 triangles")
D -->|"..."| E("Niveau 6
~40k vertices")
classDef keyFunc fill:#fff59d,stroke:#f9a825,stroke-width:2px,color:#2d2d2d
classDef subsystem fill:#ffffff,stroke:#aaaaaa,stroke-width:1.5px,color:#444444
class D keyFunc
class A,B,C,E subsystem
4.4 — Bibliothèque de matériaux
scene->material_lib = material_load_presets("assets/materials/pbr_materials.json");
Le fichier JSON définit 101 matériaux PBR organisés par catégorie :
| Catégorie | Exemples | Métallicité | Rugosité |
|---|---|---|---|
| Métaux purs | Or, Argent, Cuivre, Chrome | 1.0 | 0.05–0.2 |
| Métaux vieillis | Fer rouillé, Cuivre oxydé | 0.7–0.95 | 0.4–0.8 |
| Diélectriques brillants | Plastiques colorés | 0.0 | 0.05–0.15 |
| Matériaux mats | Tissu, Argile, Sable | 0.0 | 0.65–0.95 |
| Pierres | Granit, Marbre, Obsidienne | 0.0 | 0.35–0.85 |
| Organiques | Chêne, Cuir, Os | 0.0 | 0.35–0.75 |
| Peintures | Carrosserie, Nacré, Satin | 0.3–0.7 | 0.1–0.5 |
| Techniques | Caoutchouc, Carbone, Céramique | 0.0–0.1 | 0.05–0.85 |
Chaque matériau fournit : albedo (RGB), metallic (0–1), roughness (0–1).
4.5 — La grille d'instances
const int cols = 10; // DEFAULT_COLS
const float spacing = 2.5F; // DEFAULT_SPACING
Une grille 10×10 de 100 sphères est disposée dans le plan XY, centrée à l'origine :
Dimensions de la grille :
Largeur = (10 - 1) × 2.5 = 22.5 unités
Hauteur = (10 - 1) × 2.5 = 22.5 unités
Z = 0 (toutes les sphères dans le même plan)
Chaque instance stocke 88 octets :
typedef struct SphereInstance {
mat4 model; // 64 octets — matrice de transformation 4×4
vec3 albedo; // 12 octets — couleur RGB
float metallic; // 4 octets
float roughness; // 4 octets
float ao; // 4 octets — toujours 1.0
} SphereInstance; // Total : 88 octets par instance
4.6 — Layout du VAO (mode billboard)
En mode billboard, le VAO lie un quad de 4 vertices et les données matériaux par instance :
┌────────────────────────────────────────────────────────────────┐
│ Billboard VAO (Mode de rendu par défaut) │
├────────────┬────────────┬─────────────────────────────────────┤
│ Location │ Source │ Description │
├────────────┼────────────┼─────────────────────────────────────┤
│ 0 │ Quad VBO │ vec3 position (±0.5 quad verts) │
│ 1 │ Quad VBO │ vec3 normale (stub, inutilisé) │
│ 2–5 │ Inst VBO │ mat4 modèle (par instance) │
│ 6 │ Inst VBO │ vec3 albedo (par instance) │
│ 7 │ Inst VBO │ vec3 pbr (M,R,AO) (par instance) │
└────────────┴────────────┴─────────────────────────────────────┘
Location 0–1 : glVertexAttribDivisor = 0 (avance par vertex, 4 verts)
Location 2–7 : glVertexAttribDivisor = 1 (avance par instance)
Appel de draw : glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, 100) — 100 quads, face culling désactivé.
4.7 — Compilation des shaders
Tous les shaders sont compilés pendant scene_init(). Le loader (src/shader.c) supporte un système d'inclusion @header personnalisé :
// Dans pbr_ibl_instanced.frag :
@header "pbr_functions.glsl"
@header "sh_probe.glsl"
Ce système inline récursivement les fichiers (profondeur max : 16), avec déduplication type include-guard.
graph TD
INIT("scene_init() — Compilation des shaders") --> REND
INIT --> COMP
INIT --> POST
subgraph REND ["🎨 Programmes de rendu"]
direction TB
PBR("PBR Instancié — pbr_ibl_instanced.vert/.frag")
BB("PBR Billboard — pbr_ibl_billboard.vert/.frag")
SKY("Skybox — background.vert/.frag")
UI("UI Overlay — ui.vert/.frag")
end
subgraph COMP ["⚡ Compute Shaders"]
direction TB
SPMAP("Prefiltre Spéculaire — IBL/spmap.glsl")
IRMAP("Conv. Irradiance — IBL/irmap.glsl")
BRDF("BRDF LUT — IBL/spbrdf.glsl")
LUM("Réduction Luminance — IBL/luminance_reduce")
end
subgraph POST ["✨ Post-Process"]
direction TB
PP("Composite Final — postprocess.vert/.frag")
BL("Bloom — down/up/prefilter")
end
classDef keyFunc fill:#fff59d,stroke:#f9a825,stroke-width:2px,color:#2d2d2d
classDef render fill:#e3f2fd,stroke:#42a5f5,stroke-width:1.5px,color:#2d2d2d
classDef compute fill:#f3e5f5,stroke:#ab47bc,stroke-width:1.5px,color:#2d2d2d
classDef postfx fill:#fce4ec,stroke:#c2185b,stroke-width:1.5px,color:#2d2d2d
class INIT keyFunc
class PBR,BB,SKY,UI render
class SPMAP,IRMAP,BRDF,LUM compute
class PP,BL postfx
Chapitre 5 — Setup du pipeline de post-processing
postprocess_init(&app->postprocess, &app->gpu_profiler, 1920, 1080);
5.1 — Le FBO de scène (Multi-Render Target)
Le framebuffer offscreen principal utilise le MRT (Multiple Render Targets) :
| Attachment | Format | Taille | Rôle |
|---|---|---|---|
GL_COLOR_ATTACHMENT0 |
GL_RGBA16F |
1920×1080 | Couleur HDR de la scène (alpha = luma pour FXAA) |
GL_COLOR_ATTACHMENT1 |
GL_RG16F |
1920×1080 | Vélocité par pixel pour le motion blur |
GL_DEPTH_STENCIL_ATTACHMENT |
GL_DEPTH32F_STENCIL8 |
1920×1080 | Buffer de profondeur + masque stencil |
| Stencil view | GL_R8UI |
1920×1080 | Stencil en lecture seule comme texture |
graph TD
FBO("FBO de Scène — Multi-Render Target") --> C0
FBO --> C1
FBO --> DS
DS --> SV
C0("🟦 Color 0 — GL_RGBA16F
Couleur HDR scène")
C1("🟩 Color 1 — GL_RG16F
Vecteurs vélocité")
DS("🟫 Depth/Stencil — GL_DEPTH32F_STENCIL8")
SV("🟪 Vue Stencil — GL_R8UI (TextureView)")
classDef keyFunc fill:#fff59d,stroke:#f9a825,stroke-width:2px,color:#2d2d2d
classDef color0 fill:#e3f2fd,stroke:#42a5f5,stroke-width:1.5px,color:#2d2d2d
classDef color1 fill:#e8f5e9,stroke:#66bb6a,stroke-width:1.5px,color:#2d2d2d
classDef depth fill:#fff3e0,stroke:#ff9800,stroke-width:1.5px,color:#2d2d2d
classDef stencil fill:#f3e5f5,stroke:#ab47bc,stroke-width:1.5px,color:#2d2d2d
class FBO keyFunc
class C0 color0
class C1 color1
class DS depth
class SV stencil
5.2 — Ressources des sous-effets
Chaque effet de post-processing initialise ses propres ressources :
| Effet | Ressources GPU |
|---|---|
| Bloom | FBOs mip-chain (6 niveaux), textures prefilter/downsample/upsample |
| DoF | Texture de flou, texture CoC (Circle of Confusion) |
| Auto-Exposure | Texture de downsample luminance, 2× PBOs (readback), 2× GLSync fences |
| Motion Blur | Texture tile-max velocity (compute), texture neighbor-max (compute) |
| 3D LUT | GL_TEXTURE_3D 32³ chargé depuis des fichiers .cube |
5.3 — Effets actifs par défaut
postprocess_enable(&app->postprocess, POSTFX_FXAA); // Seulement FXAA
Au démarrage, seul FXAA est actif. Les autres effets sont activés/désactivés en temps réel via raccourcis clavier.
Chapitre 6 — Le premier chargement HDR
env_manager_load(&app->env_mgr, app->async_loader, "env.hdr");
Cela déclenche le pipeline de chargement asynchrone d'environnement — l'opération multi-frame la plus complexe du moteur.
6.1 — Séquence de chargement async
sequenceDiagram
participant Main as Thread Principal (Rendu)
participant Worker as Thread Worker Async
participant GPU as GPU
Main->>Worker: async_loader_request("env.hdr")
Note over Worker: État: PENDING → LOADING
Worker->>Worker: stbi_loadf() — décode HDR en float RGBA
Note over Worker: ~50ms pour 2K HDR sur NVMe
Worker-->>Main: État: WAITING_FOR_PBO
Main->>GPU: glGenBuffers() → PBO
Main->>GPU: glMapBuffer(PBO, WRITE)
Main-->>Worker: async_loader_provide_pbo(pbo_ptr)
Note over Worker: État: CONVERTING
Worker->>Worker: Conversion SIMD float32 → float16
Note over Worker: ~2ms pour 2048×1024
Worker-->>Main: État: READY
Main->>GPU: glUnmapBuffer(PBO)
Main->>GPU: glTexSubImage2D(depuis PBO)
Note over GPU: Transfert DMA : PBO → VRAM
Main->>GPU: glGenerateMipmap()
6.2 — Machine à états de transition
Pendant le premier chargement, l'écran reste noir (pas de crossfade depuis une scène précédente) :
stateDiagram-v2
[*] --> WAIT_IBL: "Premier chargement"
WAIT_IBL --> WAIT_IBL: "IBL en cours..."
WAIT_IBL --> FADE_IN: "IBL terminé"
FADE_IN --> IDLE: "Alpha atteint 0"
WAIT_IBL : transition_alpha = 1.0 (noir opaque complet) — l'écran est noir pendant les premières frames.
FADE_IN : l'alpha diminue de 1.0 → 0.0 sur 250ms.
Chapitre 7 — Génération IBL (progressive, multi-frame)
Une fois la texture HDR uploadée, le coordinateur IBL (src/ibl_coordinator.c) prend le relais. Il calcule trois maps sur plusieurs frames pour éviter les stalls GPU.
7.1 — Les trois maps IBL
graph TB
HDR("Map d'environnement HDR
2048×1024 equirectangulaire
GL_RGBA16F") --> SPEC
HDR --> IRR
HDR --> LUM
SPEC("Map Prefiltre Spéculaire
1024×1024 × 5 niveaux mip
Compute: spmap.glsl")
IRR("Map d'Irradiance
64×64
Compute: irmap.glsl")
LUM("Réduction Luminance
1×1 moyenne
Compute: luminance_reduce")
SPEC -->|"Réflexion par pixel
roughness → niveau mip"| PBR("Shader PBR")
IRR -->|"Intégrale diffuse
hémisphérique"| PBR
LUM -->|"Seuil auto-exposure"| PP("Post-Process")
classDef keyFunc fill:#fff59d,stroke:#f9a825,stroke-width:2px,color:#2d2d2d
classDef compute fill:#f3e5f5,stroke:#ab47bc,stroke-width:1.5px,color:#2d2d2d
classDef target fill:#e3f2fd,stroke:#42a5f5,stroke-width:1.5px,color:#2d2d2d
class HDR keyFunc
class SPEC,IRR,LUM compute
class PBR,PP target
| Map | Résolution | Format | Niveaux Mip | Compute Shader |
|---|---|---|---|---|
| Prefiltre Spéculaire | 1024×1024 | GL_RGBA16F |
5 | IBL/spmap.glsl |
| Irradiance | 64×64 | GL_RGBA16F |
1 | IBL/irmap.glsl |
| Luminance | 1×1 | GL_R32F |
1 | IBL/luminance_reduce_pass1/2.glsl |
7.2 — Stratégie de découpage progressif
Pour éviter les pics de frame time, chaque niveau mip est subdivisé en slices traitées sur des frames consécutives :
| Étape IBL | GPU Hardware | GPU Software (llvmpipe) |
|---|---|---|
| Specular Mip 0 (1024²) | 24 slices (42 lignes chacune) | 1 slice (complet) |
| Specular Mip 1 (512²) | 8 slices | 1 slice |
| Specular Mips 2–4 | Groupées (1 dispatch) | 1 slice |
| Irradiance (64²) | 12 slices | 1 slice |
| Luminance | 2 dispatches (pass 1 + 2) | 2 dispatches |
gantt
title Timeline de génération IBL progressive
dateFormat x
axisFormat Frame %s
section Luminance
Luminance Pass 1 :lum1, 0, 1000
Luminance Wait (fence) :lum2, 1000, 2000
Luminance Readback :lum3, 2000, 3000
section Specular Mip 0
Slice 1/24 :s1, 3000, 4000
Slice 2/24 :s2, 4000, 5000
Slice ... :s3, 5000, 6000
Slice 24/24 :s4, 6000, 7000
section Specular Mip 1
Slices 1-8 :m1, 7000, 9000
section Specular Mips 2-4
Dispatch groupé :m3, 9000, 10000
section Irradiance
Slices 1-12 :i1, 10000, 12000
section Terminé
IBL Complet → Fade In :ibl_done, 12000, 13000
7.3 — Machine à états IBL
enum IBLState {
IBL_STATE_IDLE, // Pas de travail
IBL_STATE_LUMINANCE, // Pass 1 : réduction luminance
IBL_STATE_LUMINANCE_WAIT, // Attente fence readback
IBL_STATE_SPECULAR_INIT, // Allocation texture spéculaire
IBL_STATE_SPECULAR_MIPS, // Génération progressive des mips
IBL_STATE_IRRADIANCE, // Convolution irradiance progressive
IBL_STATE_DONE // Toutes les maps prêtes
};
Chapitre 8 — La boucle principale
app_run() (src/app.c) est le battement de cœur — une boucle de jeu non capée classique avec physique à pas fixe.
graph TD
A("① glfwPollEvents() — Clavier, souris, resize")
A --> B("② Temps & FPS — delta_time, frame_count")
B --> C("③ Physique Caméra — Pas fixe 60Hz, lerp rotation")
C --> D("④ Mise à jour géométrie — si subdiv changé")
D --> E("⑤ app_update() — Traitement état entrées")
E --> F("⑥ renderer_draw_frame() — LE GROS MORCEAU")
F --> G("⑦ Tracy screenshots — profiling")
G --> H("⑧ glfwSwapBuffers() — Présentation à l'écran")
H -->|"frame suivante"| A
classDef keyFunc fill:#fff59d,stroke:#f9a825,stroke-width:2px,color:#2d2d2d
classDef loopNode fill:#e3f2fd,stroke:#42a5f5,stroke-width:1.5px,color:#2d2d2d
classDef subsystem fill:#ffffff,stroke:#aaaaaa,stroke-width:1.5px,color:#444444
class F keyFunc
class A,B,C,D,E,G loopNode
class H subsystem
8.1 — Resize différé
Les événements de redimensionnement sont différés — le callback GLFW enregistre seulement les nouvelles dimensions. La recréation réelle des FBOs se fait au début de la frame suivante, hors du contexte limité du callback.
8.2 — Intégration caméra à pas fixe
app->camera.physics_accumulator += (float)app->delta_time;
while (app->camera.physics_accumulator >= app->camera.fixed_timestep) {
camera_fixed_update(&app->camera); // Vélocité, friction, bobbing
app->camera.physics_accumulator -= app->camera.fixed_timestep;
}
// Rotation lissée (interpolation exponentielle)
float alpha = app->camera.rotation_smoothing; // ~0.1
app->camera.yaw += (app->camera.yaw_target - app->camera.yaw) * alpha;
app->camera.pitch += (app->camera.pitch_target - app->camera.pitch) * alpha;
camera_update_vectors(&app->camera);
Cela garantit une physique déterministe indépendamment du framerate, tandis que la rotation reste fluide via interpolation par frame.
Chapitre 9 — Le rendu d'une frame
renderer_draw_frame() (src/renderer.c) orchestre le pipeline de rendu complet.
9.1 — Architecture haut niveau
graph TD
A("GPU Profiler Begin") --> B("postprocess_begin() — Bind Scene FBO, Clear")
B --> C("camera_get_view_matrix()")
C --> D("glm_perspective() — FOV=60°, near=0.1, far=1000")
D --> E("ViewProj = Proj × View")
E --> G1("🌅 Pass 1 : Skybox — depth désactivée")
G1 --> G2("🔢 Pass 2 : Tri des sphères — GPU Bitonic")
G2 --> G3("🔮 Pass 3 : Sphères PBR — draw instancié billboard")
G3 --> H("✨ postprocess_end() — Pipeline 7 étapes")
H --> I("🖥️ UI Overlay + Transition Env")
classDef keyFunc fill:#fff59d,stroke:#f9a825,stroke-width:2px,color:#2d2d2d
classDef loopNode fill:#e3f2fd,stroke:#42a5f5,stroke-width:1.5px,color:#2d2d2d
classDef setup fill:#ffffff,stroke:#aaaaaa,stroke-width:1.5px,color:#444444
classDef postfx fill:#fce4ec,stroke:#c2185b,stroke-width:1.5px,color:#2d2d2d
class G1,G2,G3 keyFunc
class A,B,C,D,E setup
class H,I postfx
9.2 — Pass 1 : Skybox
La skybox est dessinée en premier, avec le test de profondeur désactivé. Elle utilise une astuce de quad plein écran :
// background.vert — reconstruction du rayon en espace monde
gl_Position = vec4(in_position.xy, 1.0, 1.0); // Depth = 1.0 (plan lointain)
vec4 pos = m_inv_view_proj * vec4(in_position.xy, 1.0, 1.0);
RayDir = pos.xyz / pos.w; // Rayon espace monde reconstruit
// background.frag — échantillonnage equirectangulaire de la HDR
vec2 uv = SampleEquirectangular(normalize(RayDir));
vec3 envColor = textureLod(environmentMap, uv, blur_lod).rgb;
envColor = clamp(envColor, vec3(0.0), vec3(200.0)); // Protection NaN + anti-fireflies
FragColor = vec4(envColor, luma); // Alpha = luma pour FXAA
VelocityOut = vec2(0.0); // Pas de mouvement pour la skybox
9.3 — Pass 2 : Tri des sphères (Bitonic Sort GPU)
Pour le rendu transparent par billboard, les sphères doivent être dessinées back-to-front :
| Mode | Où | Algorithme | Complexité |
|---|---|---|---|
CPU_QSORT |
CPU | qsort() (stdlib) |
O(n·log n) moy |
CPU_RADIX |
CPU | Tri radix | O(n·k) |
GPU_BITONIC ★ |
GPU | Tri bitonique (compute) | O(n·log²n) |
9.4 — Pass 3 : Sphères PBR — Billboard Ray-Tracing
C'est le cœur du rendu. Un seul appel de draw rend les 100 sphères :
| Métrique | Valeur |
|---|---|
| Vertices par sphère | 4 (quad billboard) |
| Triangles par sphère | 2 (triangle strip) |
| Instances | 100 (grille 10×10) |
| Total vertices | 400 |
| Draw calls | 1 |
| Précision sphère | Mathématiquement parfaite (ray-tracée) |
9.5 — Le fragment shader billboard (intersection rayon-sphère)
Le fragment shader (pbr_ibl_billboard.frag) est là où la magie opère. Au lieu de shader un mesh rastérisé, il intersecte analytiquement un rayon avec une sphère parfaite :
// Intersection rayon-sphère analytique
vec3 oc = rayOrigin - center;
float b = dot(oc, rayDir);
float c = dot(oc, oc) - radius * radius;
float discriminant = b * b - c; // >0 = touché, <0 = raté
if (discriminant < 0.0) discard;
float t = -b - sqrt(discriminant); // intersection la plus proche
vec3 hitPos = rayOrigin + t * rayDir;
vec3 N = normalize(hitPos - center); // normale analytique parfaite
graph TD
R("🔦 Construction rayon — origin=camPos, dir=normalize(WorldPos-camPos)")
R --> INT("📐 Intersection Rayon-Sphère — discriminant = b² - c")
INT --> HIT{"Touché ?"}
HIT -->|"Non — disc < 0"| DISCARD("❌ discard — pixel hors sphère")
HIT -->|"Oui"| HITPOS("✅ hitPos = origin + t × dir")
HITPOS --> NORMAL("N = normalize(hitPos - center) — normale parfaite")
HITPOS --> DEPTH("gl_FragDepth = project(hitPos) — Z-buffer correct")
NORMAL --> PBR("V = -rayDir")
PBR --> FRESNEL("Fresnel-Schlick")
PBR --> GGX("Smith-GGX Geometry")
PBR --> NDF("GGX NDF Distribution")
FRESNEL & GGX & NDF --> SPEC("IBL Spéculaire — prefilterMap × brdfLUT")
PBR --> DIFF("IBL Diffuse — irradiance(N) × albedo")
SPEC & DIFF --> FINAL("couleur = Diffuse + Spéculaire")
FINAL --> AA("Anti-Aliasing bords — smoothstep sur discriminant")
AA --> ALPHA("FragColor = vec4(color, edgeFactor) — alpha prémultiplié")
classDef keyFunc fill:#fff59d,stroke:#f9a825,stroke-width:2px,color:#2d2d2d
classDef compute fill:#e3f2fd,stroke:#42a5f5,stroke-width:1.5px,color:#2d2d2d
classDef entryExit fill:#fce4ec,stroke:#c2185b,stroke-width:2px,color:#2d2d2d
classDef subsystem fill:#ffffff,stroke:#aaaaaa,stroke-width:1.5px,color:#444444
class R,INT,HIT keyFunc
class HITPOS,NORMAL,DEPTH,PBR compute
class FRESNEL,GGX,NDF,SPEC,DIFF subsystem
class FINAL,AA,ALPHA,DISCARD entryExit
Anti-aliasing analytique des bords
float pixelSizeWorld = (2.0 * clipW) / (proj[1][1] * screenHeight);
float edgeFactor = smoothstep(0.0, 1.0, discriminant / (2.0 * radius * pixelSizeWorld));
FragColor = vec4(color * edgeFactor, edgeFactor); // alpha prémultiplié
Projection billboard
Chapitre 10 — Pipeline de post-processing
Après le rendu de la scène 3D dans le FBO MRT, postprocess_end() applique jusqu'à 8 effets dans un pipeline soigneusement ordonné.
10.1 — Le pipeline en 7 étapes
graph TD
A("Memory Barrier — flush écriture MRT")
A --> B("① Bloom — Downsample → Seuil → Upsample")
B --> C("② Profondeur de Champ — CoC → Flou bokeh")
C --> D("③ Auto-Exposition — Réduction luminance → PBO readback")
D --> E("④ Motion Blur — Tile-max velocity → Neighbor-max")
E --> F("⑤ Bind 9 Textures + Upload UBO")
F --> H("Draw fullscreen quad")
H --> J("Vignette")
J --> K("Grain film")
K --> L("Balance des blancs")
L --> M("Color Grading — Sat, Contraste, Gamma, Gain")
M --> N("Tonemapping — courbe filmique")
N --> O("Grading 3D LUT")
O --> P("FXAA")
P --> Q("Dithering — anti-banding")
Q --> R("Brouillard atmosphérique")
classDef keyFunc fill:#fff59d,stroke:#f9a825,stroke-width:2px,color:#2d2d2d
classDef compute fill:#f3e5f5,stroke:#ab47bc,stroke-width:1.5px,color:#2d2d2d
classDef shader fill:#e3f2fd,stroke:#42a5f5,stroke-width:1.5px,color:#2d2d2d
classDef subsystem fill:#ffffff,stroke:#aaaaaa,stroke-width:1.5px,color:#444444
class A,F,H subsystem
class B,C,D,E compute
class J,K,L,M,N,O,P,Q,R shader
10.2 — Galerie des effets de post-processing
Voici le rendu vue de face avec différents effets activés individuellement — chaque image montre l'effet isolé appliqué à la même scène :
Sans post-processing (brut)
FXAA (anti-aliasing rapide)
Bloom
Depth of Field (Profondeur de champ)
Auto-Exposure
Motion Blur
Profil cinématique Sony A7S III
10.3 — Optimisation shader par compilation conditionnelle
Le fragment shader post-process utilise des #define au compile-time pour éliminer les branches :
#ifdef OPT_ENABLE_BLOOM
color += bloomTexture * bloomIntensity;
#endif
#ifdef OPT_ENABLE_FXAA
color = fxaa(color, uv, texelSize);
#endif
Un cache LRU de 32 entrées stocke les variantes compilées pour différentes combinaisons de flags d'effets. Changer d'effet déclenche une recompilation paresseuse uniquement à la première occurrence d'une nouvelle combinaison.
10.4 — Courbes de tonemapping
10.5 — Adaptation d'exposition
Chapitre 11 — La première frame visible
Voyons ce qui apparaît réellement à l'écran pendant les premières secondes :
Timeline de démarrage
| Frames | Ce qui se passe | À l'écran |
|---|---|---|
| 1–2 | Le loader async lit env.hdr depuis le disque |
Écran noir (transition_alpha = 1.0) |
| 3–4 | Transfert PBO → GPU (DMA) + génération mipmaps | Écran noir |
| 5–15 | Calcul IBL progressif (luminance, spéculaire, irradiance) | Écran noir (mais les sphères sont rendues dans le FBO) |
| ~16 | IBL terminé → TRANSITION_FADE_IN |
Le fade-in commence |
| ~20+ | Transition terminée — état stable | Scène PBR complètement éclairée |
Frame en état stable
| Étape | Détail | Temps |
|---|---|---|
| 1. Poll Events | glfwPollEvents() |
~0.1ms CPU |
| 2. Mise à jour caméra | Physique 60Hz + lerp rotation | ~0.01ms CPU |
| 3a. Skybox | Quad plein écran, sampling equirect. | ~0.2ms GPU |
| 3b. Tri bitonique | Compute shader, 100 sphères | ~0.1ms GPU |
| 3c. Sphères billboard | 100 quads ray-tracées, 1 draw call | ~0.5ms GPU |
| 4a. Bloom | Downsample → Upsample (si activé) | ~0.3ms GPU |
| 4b. DoF | CoC → Flou bokeh (si activé) | ~0.2ms GPU |
| 4c. Auto-Exposure | Réduction luminance | ~0.1ms GPU |
| 4d. Motion Blur | Tile-max velocity (si activé) | ~0.2ms GPU |
| 4e. Composite final | 9 textures, UBO, fullscreen quad | ~0.3ms GPU |
| 5. UI Overlay | Texte + profiler + transition | ~0.1ms GPU |
| 6. SwapBuffers | Présentation à l'écran | (attente) |
| Temps de frame typique | 1–3ms GPU |
Chapitre 12 — Budget mémoire GPU
Voici une estimation de la consommation VRAM en état stable :
Textures
| Ressource | Résolution | Format | Taille |
|---|---|---|---|
| HDR Environnement | 2048×1024 | GL_RGBA16F |
~16 Mo (avec mips) |
| Prefiltre Spéculaire | 1024² × 5 mips | GL_RGBA16F |
~10.5 Mo |
| Irradiance | 64×64 | GL_RGBA16F |
~32 Ko |
| BRDF LUT | 512×512 | GL_RG16F |
~1 Mo |
| Couleur Scène (FBO) | 1920×1080 | GL_RGBA16F |
~16 Mo |
| Vélocité (FBO) | 1920×1080 | GL_RG16F |
~8 Mo |
| Depth/Stencil (FBO) | 1920×1080 | GL_DEPTH32F_STENCIL8 |
~10 Mo |
| Chaîne Bloom (6 mips) | Divers | GL_RGBA16F |
~21 Mo |
| DoF flou | 1920×1080 | GL_RGBA16F |
~16 Mo |
| Auto-Exposure | 64×64 → 1×1 | GL_R32F |
~16 Ko |
| SH Probes (7 tex) | 21×21×3 | GL_RGBA16F |
~74 Ko |
Buffers
| Ressource | Quantité | Taille unitaire | Total |
|---|---|---|---|
| Billboard quad VBO | 4 verts | 12 o (vec3) | 48 o |
| Instance VBO | 100 instances | ~88 o | ~8.6 Ko |
| Sort SSBO | 100 entrées | 8 o | ~800 o |
| Fullscreen quad VBO | 6 verts | 20 o | 120 o |
| UBO (post-process) | 1 | ~256 o | 256 o |
Total estimé
| Catégorie | Approximatif |
|---|---|
| Textures | ~99 Mo |
| Buffers | ~40 Ko |
| Shaders (compilés) | ~2 Mo |
| Total | ~101 Mo VRAM |
💡 Coût dominant : La map HDR d'environnement + chaîne bloom + FBOs de scène dominent l'utilisation VRAM. La géométrie elle-même (100 quads billboard × 4 vertices en mode par défaut) est négligeable — le vrai calcul de sphère se passe dans le fragment shader via ray-tracing.
Galerie : vues multi-angles
Le moteur supporte des captures automatisées depuis différents angles de caméra, utilisées pour les tests de régression visuelle :
![]() Face |
![]() Gauche |
![]() Droite |
![]() Dessus |
![]() Dessous |
![]() Sony A7S III |
Pipeline de données global
graph TD
POLL("① CPU — glfwPollEvents()") --> TIME("② CPU — Calcul Δt")
TIME --> CAM("③ CPU — Physique caméra 60Hz")
CAM --> SORT("④ CPU → GPU — Tri des sphères")
SORT --> FBO("⑤ GPU — Bind Scene FBO, Clear")
FBO --> SKY("🌅 Pass Skybox — Sampling equirectangulaire")
SKY --> SPHERES("🔮 Pass Billboard — 1 draw call, 100 instances, ray-tracing")
SPHERES --> BLOOM("✨ Bloom + DoF + Auto-Exposure + Motion Blur")
BLOOM --> COMP("🎬 Composite Final — 9 textures, UBO, fullscreen quad")
COMP --> UI("🖥️ UI Overlay + Profiler + Transition")
UI --> SWAP("⑩ glfwSwapBuffers()")
classDef cpu fill:#e3f2fd,stroke:#42a5f5,stroke-width:1.5px,color:#2d2d2d
classDef gpu fill:#fff59d,stroke:#f9a825,stroke-width:2px,color:#2d2d2d
classDef postfx fill:#f3e5f5,stroke:#ab47bc,stroke-width:1.5px,color:#2d2d2d
classDef subsystem fill:#ffffff,stroke:#aaaaaa,stroke-width:1.5px,color:#444444
class POLL,TIME,CAM,SORT cpu
class FBO,SKY,SPHERES gpu
class BLOOM,COMP postfx
class UI,SWAP subsystem
Glossaire
Référence rapide des termes techniques utilisés dans l'article, avec liens vers la documentation officielle.
Langages, API & Standards
| Terme | Description | Lien |
|---|---|---|
| C11 | Version 2011 du standard du langage C, utilisé pour tout le moteur | cppreference — C11 |
| OpenGL 4.4 | API graphique bas-niveau pour communiquer avec le GPU | OpenGL 4.4 Spec (Khronos) |
| Core Profile | Mode OpenGL qui retire les fonctions dépréciées (pipeline fixe) | OpenGL Wiki — Core Profile |
| GLSL | OpenGL Shading Language — langage des programmes GPU (shaders) | GLSL Spec (Khronos) |
| GLFW | Bibliothèque C pour créer des fenêtres et gérer les entrées clavier/souris | glfw.org |
| Window Hints | Paramètres GLFW configurés avant la création de la fenêtre (version OpenGL, profil, MSAA…) | GLFW — Window Guide |
| GLAD | Générateur de loader OpenGL — résout les adresses des fonctions GL au runtime | GLAD Generator |
| GLX | Extension X11 qui fait le pont entre le Window System et OpenGL sous Linux | GLX Spec (Khronos) |
| X11 | Système de fenêtrage historique de Linux (serveur d'affichage) | X.Org |
| Mesa | Implémentation open-source des API graphiques (OpenGL, Vulkan) sous Linux | mesa3d.org |
Rendu 3D — Concepts fondamentaux
| Terme | Description | Lien |
|---|---|---|
| PBR | Physically-Based Rendering — modèle d'éclairage qui simule la physique réelle de la lumière | learnopengl.com — PBR Theory |
| IBL | Image-Based Lighting — éclairage extrait d'une image panoramique HDR de l'environnement | learnopengl.com — IBL |
| HDR | High Dynamic Range — valeurs de couleur supérieures à 1.0 (lumière réaliste) | learnopengl.com — HDR |
| LDR | Low Dynamic Range — valeurs de couleur 0–255, ce que l'écran affiche réellement | learnopengl.com — HDR |
| Shader | Programme exécuté directement sur le GPU (vertex, fragment, compute) | OpenGL Wiki — Shader |
| Vertex Shader | Shader qui traite chaque sommet de la géométrie (position, projection) | OpenGL Wiki — Vertex Shader |
| Fragment Shader | Shader qui calcule la couleur de chaque pixel à l'écran | OpenGL Wiki — Fragment Shader |
| Compute Shader | Shader généraliste pour du calcul GPU hors pipeline de rendu | OpenGL Wiki — Compute Shader |
| Skybox | Image panoramique affichée en fond de scène comme ciel/environnement | learnopengl.com — Cubemaps |
| Rasterisation | Processus de conversion des triangles 3D en pixels 2D à l'écran | OpenGL Wiki — Rasterization |
| Draw Call | Un appel CPU→GPU qui demande le rendu d'un jeu de géométrie | OpenGL Wiki — Rendering Pipeline |
| Instanced Rendering | Technique pour dessiner N copies d'un objet en un seul draw call | OpenGL Wiki — Instancing |
| Mipmap | Versions pré-réduites d'une texture (½, ¼, ⅛…) pour un filtrage plus propre au loin | OpenGL Wiki — Texture#Mip_maps |
Objets GPU OpenGL
| Terme | Description | Lien |
|---|---|---|
| FBO | Framebuffer Object — surface de rendu offscreen (on dessine dedans au lieu de l'écran) | OpenGL Wiki — Framebuffer Object |
| MRT | Multiple Render Targets — écrire dans plusieurs textures en une seule passe de rendu | OpenGL Wiki — MRT |
| VAO | Vertex Array Object — décrit le format des données géométriques envoyées au GPU | OpenGL Wiki — VAO |
| VBO | Vertex Buffer Object — buffer GPU contenant les positions, normales, etc. des sommets | OpenGL Wiki — VBO |
| SSBO | Shader Storage Buffer Object — buffer GPU en lecture/écriture depuis les shaders | OpenGL Wiki — SSBO |
| UBO | Uniform Buffer Object — bloc de données partagé entre CPU et shaders | OpenGL Wiki — UBO |
| PBO | Pixel Buffer Object — buffer pour les transferts asynchrones de pixels CPU↔GPU | OpenGL Wiki — PBO |
| Texture View | Vue alternative sur les données d'une texture existante (format ou couches différents) | OpenGL Wiki — Texture View |
Ray-Tracing & Géométrie
| Terme | Description | Lien |
|---|---|---|
| Ray-Tracing | Technique qui trace des rayons lumineux pour calculer l'intersection avec des objets | Scratchapixel — Ray-Sphere |
| Billboard | Quad (rectangle) toujours face à la caméra, utilisé ici comme surface de ray-tracing | OpenGL Wiki — Billboard |
| AABB | Axis-Aligned Bounding Box — boîte englobante alignée aux axes, pour le culling rapide | Wikipedia — AABB |
| Icosphère | Sphère construite en subdivisant un icosaèdre (20 faces) — plus uniforme qu'une UV sphere | Wikipedia — Icosphère |
| Discriminant | Valeur mathématique (b²−c) déterminant si un rayon touche une sphère | Scratchapixel — Ray-Sphere |
| Normale | Vecteur perpendiculaire à la surface en un point — détermine l'orientation de la surface | learnopengl.com — Basic Lighting |
| Tessellation | Subdivision de la géométrie en triangles plus fins pour plus de détail | OpenGL Wiki — Tessellation |
| Mesh | Ensemble de triangles formant un objet 3D | Wikipedia — Polygon mesh |
| Quad | Rectangle composé de 2 triangles — la primitive 2D de base | learnopengl.com — Hello Triangle |
| Anti-aliasing | Technique de lissage des bords en escalier (aliasing) pour un rendu visuellement plus propre | Wikipedia — Anti-aliasing |
PBR & Éclairage
| Terme | Description | Lien |
|---|---|---|
| BRDF | Bidirectional Reflectance Distribution Function — fonction décrivant comment la lumière rebondit sur une surface | learnopengl.com — PBR Theory |
| BRDF LUT | Texture pré-calculée qui encode l'intégrale BRDF pour toutes les combinaisons (angle, rugosité) | learnopengl.com — Specular IBL |
| Fresnel-Schlick | Approximation de l'effet Fresnel : les surfaces reflètent plus en angle rasant | learnopengl.com — PBR Theory |
| GGX / Smith-GGX | Modèle de micro-facettes pour la géométrie et distribution des normales (rugosité) | learnopengl.com — PBR Theory |
| NDF | Normal Distribution Function — distribution statistique de l'orientation des micro-facettes | learnopengl.com — PBR Theory |
| Albedo | Couleur de base d'un matériau (sans éclairage) | learnopengl.com — PBR Theory |
| Metallic | Paramètre PBR : 0 = diélectrique (plastique, bois), 1 = métal (or, chrome) | learnopengl.com — PBR Theory |
| Roughness | Paramètre PBR : 0 = miroir parfait, 1 = complètement mat | learnopengl.com — PBR Theory |
| AO | Ambient Occlusion — assombrit les creux et recoins (occlusion de la lumière ambiante) | learnopengl.com — SSAO |
| Diélectrique | Matériau non-métallique (plastique, verre, bois) — reflète peu à angle direct | learnopengl.com — PBR Theory |
| Irradiance Map | Texture encodant la lumière ambiante diffuse intégrée sur l'hémisphère pour chaque direction | learnopengl.com — Diffuse Irradiance |
| Prefiltre Spéculaire | Texture mip-mappée encodant les réflexions floues par niveau de rugosité | learnopengl.com — Specular IBL |
| Equirectangulaire | Projection 2D d'une sphère (comme une carte du monde) — le format des images HDR .hdr |
Wikipedia — Equirectangular |
| SH Probes | Spherical Harmonics — représentation compacte d'un champ lumineux basse fréquence | Wikipedia — SH Lighting |
Post-Processing
| Terme | Description | Lien |
|---|---|---|
| Bloom | Effet de halo lumineux autour des zones très brillantes (diffusion lumineuse de l'objectif) | learnopengl.com — Bloom |
| Prefilter (Bloom) | Passe initiale du bloom qui extrait les pixels au-dessus d'un seuil de luminosité | learnopengl.com — Bloom |
| Downsample | Réduction progressive de la résolution d'une texture (par 2 à chaque niveau) | learnopengl.com — Bloom |
| Upsample | Agrandissement progressif d'une texture basse résolution vers la résolution originale | learnopengl.com — Bloom |
| Depth of Field (DoF) | Profondeur de champ — flou des objets hors de la distance de mise au point | Wikipedia — Depth of field |
| CoC | Circle of Confusion — diamètre du disque de flou d'un point hors focus | Wikipedia — CoC |
| Bokeh | Forme esthétique du flou d'arrière-plan (disques, hexagones…) | Wikipedia — Bokeh |
| Motion Blur | Flou de mouvement par pixel simulant l'obturateur d'une caméra | GPU Gems — Motion Blur |
| Tile-Max Velocity | Texture intermédiaire qui stocke la vélocité maximale par tuile (ex: 20×20 pixels), pour optimiser le motion blur | McGuire — Motion Blur |
| Neighbor-Max | Texture qui propage la vélocité max aux tuiles voisines, couvrant le flou de mouvement inter-tuiles | McGuire — Motion Blur |
| .cube | Format de fichier texte définissant une LUT 3D de correspondance couleur, standard dans le cinéma et les DCC | Adobe — .cube LUT Spec |
| FXAA | Fast Approximate Anti-Aliasing — anti-crénelage rapide en post-process sur l'image finale | NVIDIA — FXAA |
| MSAA | Multisample Anti-Aliasing — anti-crénelage géométrique (coûteux, évité ici) | OpenGL Wiki — Multisampling |
| Tonemapping | Conversion des couleurs HDR (illimitées) en LDR affichable (0–255) | learnopengl.com — HDR |
| Color Grading | Ajustement créatif des couleurs (saturation, contraste, gamma, teinte) | Wikipedia — Color grading |
| 3D LUT | Table 3D de correspondance couleur → couleur pour un "look" cinématique (fichier .cube) |
Wikipedia — 3D LUT |
| Vignette | Assombrissement progressif des bords de l'image (effet objectif) | Wikipedia — Vignetting |
| Grain film | Bruit photographique simulé ajouté à l'image pour un rendu analogique/cinématique | Wikipedia — Film grain |
| Balance des blancs | Correction de la température de couleur pour que les blancs apparaissent neutres sous différents éclairages | Wikipedia — White balance |
| Brouillard atmosphérique | Effet de profondeur qui estompe et teinte les objets lointains, simulant la diffusion de la lumière dans l'air | Wikipedia — Atmospheric scattering |
| Dithering | Ajout de bruit imperceptible pour casser les artefacts de banding dans les dégradés | Wikipedia — Dither |
| Auto-Exposition | Adaptation automatique de la luminosité de la scène (simule l'iris de l'œil) | learnopengl.com — HDR |
| Luminance | Mesure de l'intensité lumineuse perçue d'une image — utilisée pour l'auto-exposition | Wikipedia — Luminance |
| Luma | Approximation rapide de la luminance perçue (0.299R + 0.587G + 0.114B) — utilisée par FXAA pour détecter les bords | Wikipedia — Luma |
| VSync | Vertical Sync — synchronise le rendu avec le rafraîchissement de l'écran (évite le tearing) | Wikipedia — VSync |
Architecture & Performance
| Terme | Description | Lien |
|---|---|---|
| SIMD | Single Instruction, Multiple Data — calcul vectoriel (1 instruction traite 4+ valeurs) | Wikipedia — SIMD |
| SSE | Extensions SIMD d'Intel/AMD pour le x86 (registres 128-bit) | Intel — SSE Intrinsics |
| NEON | Extensions SIMD d'ARM (smartphones, Apple Silicon, Raspberry Pi) | ARM — NEON |
| VRAM | Mémoire dédiée du GPU — c'est là que vivent textures et buffers | Wikipedia — VRAM |
| DMA | Direct Memory Access — transfert de données sans impliquer le CPU | Wikipedia — DMA |
| Cache-friendly | Organisation mémoire qui minimise les cache-miss CPU (données contiguës) | Wikipedia — Cache |
| LRU Cache | Least Recently Used — cache qui éjecte l'élément le moins récemment utilisé | Wikipedia — LRU |
| Fence (GLSync) | Objet de synchronisation GPU — permet d'attendre qu'un travail GPU soit terminé | OpenGL Wiki — Sync Object |
| Memory Barrier | Instruction GPU garantissant que les écritures précédentes sont visibles avant les lectures suivantes | OpenGL Wiki — Memory Barrier |
| Work Group | Groupe de threads GPU exécutés ensemble dans un compute shader (ex: 16×16 = 256 threads) | OpenGL Wiki — Compute Shader |
| Dispatch | Appel CPU qui lance un compute shader sur le GPU | OpenGL Wiki — Compute Shader |
| GPU Stall | Blocage du pipeline GPU quand il attend une ressource ou une synchronisation — provoque des chutes de framerate | NVIDIA — GPU Performance |
| llvmpipe | Driver OpenGL logiciel de Mesa — émule le GPU entièrement sur CPU via LLVM JIT, utilisé en CI ou sans carte graphique | Mesa — llvmpipe |
Mathématiques & Caméra
| Terme | Description | Lien |
|---|---|---|
| mat4 / vec3 | Matrice 4×4 et vecteur 3D — types fondamentaux de la 3D | cglm docs |
| FOV | Field of View — angle de vision de la caméra (60° ici) | Wikipedia — FOV |
| Matrice de vue | Transforme les coordonnées monde en coordonnées caméra (lookAt) |
learnopengl.com — Camera |
| Matrice de projection | Transforme la 3D en 2D avec perspective (objets lointains = petits) | learnopengl.com — Coordinate Systems |
| Yaw / Pitch | Yaw = rotation gauche-droite, Pitch = rotation haut-bas de la caméra | learnopengl.com — Camera |
| Caméra orbitale | Contrôle caméra qui tourne autour d'un point d'intérêt via yaw/pitch depuis les mouvements souris | learnopengl.com — Camera |
| Lerp | Linear Interpolation — transition progressive entre deux valeurs : a + t × (b − a) |
Wikipedia — Lerp |
| EMA | Exponential Moving Average — moyenne glissante qui donne plus de poids aux valeurs récentes | Wikipedia — EMA |
| Smoothstep | Fonction d'interpolation en S (transition douce entre 0 et 1) | Khronos — smoothstep |
| Z-buffer / Depth Buffer | Texture qui stocke la profondeur de chaque pixel pour gérer l'occlusion | learnopengl.com — Depth Testing |
| Z-clip (Near/Far) | Plans de découpage proche et lointain de la caméra — définissent la plage de profondeur visible | learnopengl.com — Coordinate Systems |
| Stencil Buffer | Masque par pixel permettant de restreindre le rendu à certaines zones | learnopengl.com — Stencil |
Algorithmes de tri
| Terme | Description | Lien |
|---|---|---|
| Bitonic Sort | Tri parallèle adapté au GPU — compare et échange par paires | Wikipedia — Bitonic sort |
| Radix Sort | Tri par chiffres successifs — O(n·k), efficace sur CPU pour des clés entières | Wikipedia — Radix sort |
| Back-to-front | Ordre de rendu du plus loin au plus proche, nécessaire pour la transparence correcte | Wikipedia — Painter's algorithm |
Multithreading
| Terme | Description | Lien |
|---|---|---|
| POSIX Threads | API standard de threads sur Unix/Linux (pthread_create, pthread_cond_wait) |
man — pthreads |
| Chargement asynchrone | Exécuter les I/O disque sur un thread séparé pour ne pas bloquer le rendu | Wikipedia — Async I/O |
| Variable de condition | Mécanisme de synchronisation : un thread dort jusqu'à ce qu'un autre le réveille | man — pthread_cond_wait |
Divers
| Terme | Description | Lien |
|---|---|---|
| Tracy | Profiler temps réel pour jeux et applis graphiques (mesure CPU + GPU par frame) | Tracy Profiler (GitHub) |
| cglm | Bibliothèque C de maths 3D optimisée SIMD (matrices, vecteurs, quaternions) | cglm (GitHub) |
| stb_image | Bibliothèque C mono-fichier pour charger des images (PNG, JPEG, HDR…) | stb (GitHub) |
| Pas fixe (Fixed Timestep) | Mise à jour de la physique à intervalle constant (ex: 60 Hz) indépendamment du framerate | Fix Your Timestep! (Fiedler) |
| Game Loop | Boucle principale d'un jeu : lire les entrées → mettre à jour → dessiner → recommencer | Game Programming Patterns — Game Loop |
| Frame Time | Durée totale de rendu d'une image — 16.6ms à 60 FPS, tout dépassement provoque une saccade | Wikipedia — Frame rate |
| Fireflies | Pixels aberrants ultra-lumineux causés par des valeurs HDR extrêmes (artefact) | Physically Based — Fireflies |
| Alpha prémultiplié | Convention où RGB est déjà multiplié par alpha — permet un blending correct | Wikipedia — Premultiplied alpha |
| Face Culling | Optimisation GPU qui élimine les triangles dont la face arrière est visible — désactivé ici pour les billboards | OpenGL Wiki — Face Culling |
| Include Guard | Mécanisme de déduplication qui empêche un fichier d'être inclus plusieurs fois | Wikipedia — Include guard |
| Zero-init | Initialisation à zéro d'une structure C via {0} — garantit un état déterministe au démarrage |
cppreference — Zero initialization |
| JSON | JavaScript Object Notation — format de fichier texte léger pour stocker des données structurées (clé/valeur) | json.org |
Conclusion
suckless-ogl démontre qu'un moteur PBR complet peut être construit avec un code C11 lisible, un pipeline de rendu clair et des performances GPU mesurées en millisecondes. Les choix de design — billboard ray-tracing au lieu de mesh, chargement HDR asynchrone, IBL progressive, post-processing modulaire — montrent comment résoudre des problèmes graphiques réels avec élégance.
Le code source complet est disponible sur GitHub, et la documentation technique détaillée sur yoyonel.github.io/suckless-ogl.
Dans les prochains articles, on explorera les projets Vulkan et NVRHI qui poussent ces concepts encore plus loin.





