Effet de Lensing Shockwave¶
Vue d'ensemble¶
Lorsqu'une particule N-body franchit le rayon de confinement, une onde de choc lensing sur billboard s'étend depuis le point d'impact. L'effet est rendu sous forme d'un quad face-caméra qui échantillonne la scène derrière lui et applique :
- Déplacement UV radial — les pixels sont repoussés vers l'extérieur depuis le centre de l'anneau
- Aberration chromatique — les canaux R, G, B sont échantillonnés à des décalages différents (1.0×, 1.25×, 1.5×)
- Glow HDR additif — le bord de l'anneau émet de la lumière colorée qui alimente le bloom
Architecture¶
graph TD
A[Géométrie de scène] --> B[Scene FBO - RGBA16F]
B --> C{Shockwaves actives ?}
C -->|Non| E[Post-Processing]
C -->|Oui| D[Grab Pass : glCopyImageSubData]
D --> F[Draw Billboard]
F --> G[Fragment Shader : échantillonne grab_tex + distorsion]
G --> B
B --> E
Flux de rendu¶
- La géométrie de scène est rendue dans le FBO HDR (
scene_color_tex, RGBA16F) - Grab pass :
glCopyImageSubDatacopiescene_color_tex→grab_tex(DMA texture-à-texture, allocation paresseuse, même format/taille) - Draw billboard : pour chaque shockwave active, un quad face-caméra est rastérisé
- Fragment shader : échantillonne
grab_texaux UV écran décalés avec aberration chromatique par canal - Le post-processing lit le
scene_color_texmodifié normalement
Fichiers clés¶
| Fichier | Rôle |
|---|---|
include/shockwave.h |
Struct ShockwaveRenderer, constantes, API |
src/shockwave.c |
Grab pass, logique emit/update/draw |
shaders/shockwave.vert |
Vertex shader billboard, sortie vScreenUV |
shaders/shockwave.frag |
Déplacement + aberration chromatique + glow |
src/scene.c |
Site d'appel avec profiler GPU + support wireframe |
Analyse du coût du Grab Pass¶
Le grab pass utilise glCopyImageSubData (GL 4.3) — une copie DMA texture-à-texture qui contourne entièrement le pipeline de lecture framebuffer. Le coût est limité par la bande passante VRAM uniquement :
| Résolution | Format | Taille/frame | Bande passante @ 60 fps | % d'un GPU 200 Go/s |
|---|---|---|---|---|
| 1920×1080 | RGBA16F (8 o/px) | ~16 Mo | ~960 Mo/s | 0.5% |
| 2560×1440 | RGBA16F | ~29 Mo | ~1.7 Go/s | 0.9% |
| 3840×2160 | RGBA16F | ~66 Mo | ~3.9 Go/s | 2.0% |
Propriétés clés :
- Aucun stall CPU, aucune synchronisation de pipeline, aucun binding framebuffer requis
- Coût = 0 quand aucune shockwave n'est active (early return avant la copie)
- Allocation paresseuse :
grab_texn'est créée qu'au premier usage et redimensionnée au changement de résolution - Le handle
scene_color_texest câblé depuisPostProcessviarenderer_draw_frame
Analyse des optimisations du Grab Pass¶
Le grab pass est le coût dominant de l'effet shockwave. Quatre stratégies d'optimisation ont été évaluées avant de choisir glCopyImageSubData :
Approche 1 : Grab Pass demi-résolution¶
Allouer grab_tex à screen_w/2 × screen_h/2 au lieu de la pleine résolution. La distorsion de lensing étant un effet basse fréquence, la différence visuelle est imperceptible.
- Gain : ~75% de réduction de bande passante (÷4 texels copiés)
- Complexité : Faible — changement de taille dans
ensure_grab_texture()+glBlitFramebufferpour le downscale - Risque : Aucun (le fragment shader utilise déjà des UV normalisés
[0,1]) - Statut : Reporté — optimisation future viable
Approche 2 : glTextureBarrier (Éliminer la copie entièrement)¶
OpenGL 4.5 / GL_NV_texture_barrier. Lire scene_color_tex directement comme entrée sampler dans le même framebuffer en cours d'écriture. Un appel glTextureBarrier() flush les caches et rend les écritures précédentes visibles.
- Gain : ~100% de la bande passante de copie éliminée (~16 Mo/frame à 1080p). Coût résiduel = flush pipeline ~1-5μs vs ~0.1-0.3ms pour la copie complète
- Complexité : Faible — supprimer
grab_tex, binderscene_color_texdirectement - Risque : Comportement indéfini si deux quads shockwave se chevauchent sur le même pixel (même texel lu ET écrit). Avec des billboards localisés c'est improbable mais pas impossible
- Statut : Rejeté — fragile, dépendant du driver, risque d'UB avec événements superposés
Approche 3 : glCopyImageSubData (Implémentation actuelle) ✅¶
Copie DMA texture-à-texture directe. Ne transite pas par le pipeline de lecture framebuffer — le moteur de copie GPU gère indépendamment.
- Gain : ~10-30% par rapport à
glCopyTexSubImage2D(contourne le pipeline de lecture framebuffer, aucun binding d'unité texture requis) - Complexité : Triviale — remplacement drop-in, 1 ligne de changement
- Risque : Aucun (nécessite GL 4.3, déjà notre minimum)
- Statut : Implémenté — handle
scene_color_texcâblé depuisPostProcessviarenderer_draw_frame
Approche 4 : Clipping AABB espace écran¶
Calculer la boîte englobante espace écran de tous les quads shockwave actifs, ne copier que cette zone au lieu du framebuffer entier.
- Gain : Variable (0-90%) — dépend de la surface écran couverte par les shockwaves. 2 shockwaves couvrant 10% de l'écran → copie 10% au lieu de 100%
- Complexité : Moyenne — nécessite de projeter tous les coins de billboard en espace écran et calculer min/max
- Risque : Aucun
- Statut : Reporté — bon ROI pour les scènes avec peu de petites shockwaves
Résumé des décisions¶
| Approche | Gain BW | Complexité | Risque | Statut |
|---|---|---|---|---|
| Demi-résolution | ~75% | Faible | Aucun | Reporté |
glTextureBarrier |
~100% | Faible | UB si overlap | Rejeté |
glCopyImageSubData |
~10-30% | Triviale | Aucun | Actif |
| Clipping AABB | 0-90% | Moyenne | Aucun | Reporté |
Design des Shaders¶
Vertex Shader (shockwave.vert)¶
Le billboard est un quad unitaire [-1,1] mis à l'échelle par u_radius et orienté face à la caméra via des vecteurs de base par produit vectoriel. Deux sorties :
vUV— coordonnées locales du quad[-1,1]pour le profil de l'anneauvScreenUV— coordonnées écran après division perspective[0,1]pour échantillonner la scène
Fragment Shader (shockwave.frag)¶
Profil de l'anneau : Gaussienne centrée sur le front d'onde en expansion
ring_radius = 0.3 + 0.6 * progress
ring = exp(-8 * (dist - ring_radius)²)
Enveloppe temporelle : sin(π * progress) → fondu entrant/sortant lisse
Déplacement : factor = 0.06 * intensity * ring * envelope
offset = factor * direction_radiale
Aberration chromatique :
R = sample(grab_tex, screenUV + offset * 1.0)
G = sample(grab_tex, screenUV + offset * 1.25)
B = sample(grab_tex, screenUV + offset * 1.5)
Glow additif : couleur * 3.0 * ring * envelope * intensity * 0.25
Constantes¶
| Constante | Valeur | Description |
|---|---|---|
DISTORT_STRENGTH |
0.06 | Déplacement UV maximum au pic |
RING_SHARPNESS |
8.0 | Largeur de la Gaussienne (plus = anneau plus fin) |
CA_SPREAD_R/G/B |
1.0 / 1.25 / 1.5 | Multiplicateurs de déplacement par canal |
HDR_GLOW_SCALE |
3.0 | Intensité du glow émissif |
GLOW_MIX |
0.25 | Contribution du glow vs distorsion pure |
Filtrage des émissions¶
Chaque franchissement de frontière ne produit pas forcément une shockwave visible. Trois filtres s'appliquent :
- Seuil de vitesse (
SHOCKWAVE_MIN_VELOCITY = 0.05) : seule la composante de vitesse radiale sortante est mesurée (v_out = dot(velocity, radial_hat)). Les franchissements tangentiels ne produisent aucun effet - Suivi du pic : par body, seule la vitesse maximale pendant une frame est enregistrée (évite les doublons du sous-stepping)
- Limite de capacité (
SHOCKWAVE_MAX_ACTIVE = 16) : quand plein, l'événement le plus ancien est évincé
Support du temps inversé¶
La simulation supporte le temps inversé via time_scale < 0. Les shockwaves utilisent fabsf(sim_time - start_time) pour le calcul de l'âge, garantissant :
- L'expansion de l'anneau est toujours vers l'extérieur quelle que soit la direction du temps
- Le nettoyage supprime les événements après
SHOCKWAVE_DURATIONsecondes absolues - Aucune accumulation d'événements périmés en mode inversé
Profilage & Observabilité¶
Le draw des shockwaves est instrumenté à trois niveaux :
| Outil | Mécanisme | Visibilité |
|---|---|---|
| GPU Profiler (F3) | GPU_STAGE_PROFILER("Shockwave VFX") |
Overlay in-app, timer query |
| Tracy | TracyCZoneN + tracy_gpu_zone_begin/end |
Profiler Tracy (build avec TRACY_ENABLE) |
| RenderDoc | gl_debug_push_group("Shockwave_VFX") |
Groupes de capture RenderDoc |
Le stage du profiler inclut le grab pass et les draw calls des billboards, donnant le coût GPU total de l'effet.
Debug Wireframe¶
Quand le mode wireframe est actif (touche W), les billboards shockwave sont rendus en quads filaires via glPolygonMode(GL_FRONT_AND_BACK, GL_LINE). Cela montre :
- La géométrie et l'orientation du quad billboard
- Le rayon d'expansion de l'anneau au fil du temps
- Le nombre de shockwaves actives