Ce projet est une implémentation d'un moteur graphique avec rendu de type deffered, dans une pipeline HDR avec un modèle d'irradiance à microfacettes.
Codé en Rust, il utilise OpenGL 3.3 core et devrait normalement pouvoir compiler sans dépendance supplémentaire (OpenGL est évidemment requis, ainsi qu'une toolchain Rust). Il a été testé sous Windows (toolchain GNU) et Linux. Rien n'est spécifique à la toolchain GNU Windows de Rust, la toolchain MSVC doit marcher aussi.
Le projet est structuré selon plusieurs dossiers à la racine :
assetssont les données externes et ne contiennent pas de contenu original - ils sont là pour pouvoir être chargés par les binaries.bincontient les projets (cratesen jargon Rust) qui produisent des exécutables, qui servent à montrer plusieurs aspects du projet. En particulier, les binairessandboxetload_gltfsont notables, car permettent de montrer le pipeline de rendu en son complet. Ils sont resp. un pseudo-éditeur de scène et un visualiseur de fichiers GLTFlibsont les différentes bibliothèques qui amènent différents niveaux d'abstractions, ainsi que les fonctionnalités principales du projet. En particulier,violetteest la bibliothèque d'abstraction OpenGL, fournissant des objets RAII et une API safe et "bindless" autour d'OpenGL.resest le répertoire des ressources internes du projet, notamment les sources des shaders en GLSL. Une couche de modularité a été ajoutée par le biais d'un pré-processeur personnalisé (situé danslib/glsl-preprocessor) pour permettre les#include. Ceci a été fait surtout pour réutiliser des définitions d'uniform blocks à travers plusieurs shaders.
Le rendu d'objets se fait via une architecture deffered qui sous-traite l'éclairage en se servant des données
suivantes :
- Position
- Albedo
- Normales + "Coverage" (texture
GL_RGBA32Favec l'alpha mis à 1 lors de l'écriture, pour servir de masque lors de la passe defferée) - Roughness/Metal (packée dans une texture
GL_RG32F) - Emission
Chaque mesh possède une information de position, normale et UV. Chaque matériau possède à la fois une texture
et un facteur indépendant qui écrit dans ces textures. Ceci est fait par les
shaders mesh.vert.glsl
et mesh.frag.glsl, la définition d'un Vertex est faite dans
material.rs et celle de l'uniform View dans
camera.rs. À noter que le derive(VertexAttributes) est une macro personnalisée définie
dans violette-derive.
Le calcul de l'éclairage se fait en itérant côté CPU sur les lumières, en avançant un curseur sur l'UBO des
lumières qui ont été auparavant envoyées au GPU (dans sandbox, une détection des changements permet de mettre
à jour ce buffer automatiquement lors d'un changement d'un composant touchant à une lumière). L'éclairage par
l'environnement est une étape à part qui utilise des textures d'irradiance diffuse et spéculaire qui sont calculées
au chargement de la carte d'environnement (dans sandbox et load_gltf, il suffit de glisser une texture sur la
fenêtre pour la changer et refaire ces calculs).
L'éclairage par environnement se fait dans equirectangular.glsl, avec la
génération des cartes d'irradiance diffuse et
spéculaires.
Une étape de post-processing est enfin appliquée afin de combiner le bloom, de lens flares et de dessiner la scène à l'écran, appliquer un opérateur de tonemapping (ACES Fitted), dont le shader est défini dans postprocess.glsl.
Une étape de génération de bloom est ajoutée pour donner une impression d'éblouissement, et parce que c'est la seule vraie raison de faire un pipeline HDR. Elle se base sur un pull push décrite dans cet article, et est implémentée côté CPU dans blur.rs, et côté GPU avec downsample.glsl et upsample.glsl.
flowchart LR
subgraph uniforms [Uniforms]
lights[(Lights UBO)];
view[(View uniforms)];
end
subgraph geom [Geometry]
gPosition[Position];
gNormal[Normal];
gUV[UV];
gBoneIndex[Bone index];
gBoneWeights[Bone weight];
gBoneIndex --> gBoneWeights --> gPosition & gNormal;
end
subgraph material [Material]
mColor[[Color]];
mColorFac[Color multiplier];
mNormal[[Normal]];
mNormalFac[Normal multiplier];
mRoughMetal[[Roughness/Metal]];
mRoughMetalFac[Roughness/Metal multiplier];
mEmit[[Emission]];
mEmitFac[Emission factor];
mColorFac --> mColor;
mNormalFac --> mNormal;
mRoughMetalFac --> mRoughMetal;
mEmitFac --> mEmit;
end
subgraph gb [Geometry Pass]
Position[[Position]];
Albedo[[Albedo]];
Normal[[Normal]];
RG[[Roughness/Metal]];
Emission[[Emission]];
end
subgraph env [Environment Pass]
lEnvMap[[Environment map]];
lIrradiance[[Pre-computed diffuse irradiance]];
lSpecIBL[[Pre-computed specular irradiance]];
lEnvMap --> lIrradiance & lSpecIBL;
end
subgraph light [Light pass]
lOutput[[Shading output]];
end
subgraph bloom [Bloom pass]
bMip[[Mipchain 1..N]];
bMip --> bMip;
end
subgraph pp [Post-process]
backbuffer[[Backbuffer]];
end
view & gPosition & gNormal --> Position & Normal;
gUV --> mColor & mNormal & mRoughMetal & mEmit --> Albedo & Normal & RG & Emission;
lights & Albedo & Normal & RG & Emission --> lOutput;
lIrradiance & lSpecIBL
--> lOutput
--> bMip
--> backbuffer;
Il y a une branche dans le dépot Git concernant une implémentation d'un SSAO. Le principal problème non-résolu à temps concerne le fait que l'occlusion semble se "renverser" selon la position de la caméra.
Une erreur quelque part fait que les index vers les matrices de transformation ne sont pas bien passées
(le format reçu par OpenGL est incorrect pour l'index alors que l'attribut est bien mis en GL_UNSIGNED_INT). Le
résultat est que les indices doivent être codés dans le shader pour avoir une déformation.
Par manque de temps, la transparence n'a pas été considérée.