Bien le bonsoir !
Alors pour vous tenir informés des derniers avancements:
Dernièrement, le moteur a bien avancé, subi des corrections de bugs et de changement d'API, et un contributeur (Gawaboumga) travaille sur l'intégration de tests unitaires et quelques corrections.
La classe Resource a été séparée en RefCounted (reprenant tout le côté listener et comptage de référence) et Resource (qui se résume pour l'instant à un attribut "filepath").
Dans les changements introduits, nous avons:
| NzMaterial* material = new NzMaterial;
material->SetPersistent(false);
|
qui a été remplacé par:
| NzMaterialRef material = NzMaterial::New();
|
Le moteur n'oblige donc plus à passer par la gestion manuelle de mémoire (bien que ça soit toujours possible, pour les éventuels masochistes), et fait en sorte de passer par des références pour les ressources tout le temps, faisant du code du C++ moderne, exception-safe (vive le RAII), avec le moins de fuites mémoires possible.
Les Libraries et Managers ont également fait leur entrée.
Une Library est une collection de noms associés à des références sur des objets, permettant de les retrouver facilement dans le code.
On peut imaginer ceci:
| NzTextureRef texture = NzTexture::New();
texture->LoadFromFile("resources/overlay.png");
NzTextureLibrary::Register("Overlay", texture);
// Bien après, dans un autre fichier lointain, très lointain.
NzMaterialRef material = NzMaterial::New();
material->SetDiffuseMap("Overlay");
|
Ce procédé était déjà utilisé pour les Shaders et UberShaders, il a été étendu à toutes les classes (ou presque, les buffers par exemple n'en sont pas) étant comptées par référence.
Quant aux managers, attendus depuis la création du moteur, ils ont un rôle simple: Empêcher de recharger la même ressource un millier de fois.
| // Charge spaceship.obj la première fois, renvoie une référence vers la ressource déjà chargée les autres fois
NzMeshRef spaceship = NzMesh::Get("spaceship.obj");
|
Cette amélioration, simple mais essentielle, permet de booster le chargement de certaines ressources, de diminuer la consommation mémoire, voire même d'accélérer le rendu (réutilisation des mêmes ressources), mais soyons clairs: c'est une faute de ma part de ne pas l'avoir intégré plus tôt.
Et ça fonctionne aussi avec les matériaux:
| // Si la TextureLibrary n'a pas d'entrée de ce nom, va faire appel au TextureManager
material->SetDiffuseMap("overlay.png");
|
Le manager permet aussi une purge, pour libérer toutes les ressources qu'il est le seul à posséder (utile en cas de changement de niveau dans un jeu par exemple, on purge les managers après avoir chargé le nouveau niveau pour libérer de la mémoire).
Bref au final, les ajouts de ces derniers jours sont assez simples, voire basiques pour certains, mais ils sont surtout essentiels pour l'utilisateur.
Dans les prochains jours, je vais continuer la correction de bugs et l'amélioration de l'API, je vais également attaquer un peu le tutoriel (et pourquoi pas le générateur de doc), je cherche toujours quelqu'un pour l'implémentation Linux aussi.
Dans un futur un peu plus éloigné, il faudra aussi que je permette aux sprites/textes d'avoir des normales et des tangentes (pour supporter pleinement l'éclairage).
Oh j'oubliais, l'interface des scènes dont je parlais dans un précédent post est implémenté, et l'exemple FirstScene a été modifié pour en tenir compte, mais vous savez quoi ? Elle ne me satisfait pas pleinement.
Donc je reprends:
| NzScene scene;
NzModel* spaceship = scene.CreateNode<NzModel>();
// scene est responsable de la durée de vie du node, et le node est automatiquement parenté à la scène.
|
Comment gérer le parenting de nodes à d'autres nodes ? Un argument à la méthode CreateNode ? Comment gérer la durée de vie de ces node-là ? (Dont on peut supposer qu'ils devraient disparaître en même temps que leur parent). Comment gérer le parenting à autre chose que des SceneNode (la caméra par exemple, en tant qu'élément ne faisant pas partie du rendu, n'est qu'un simple Node) dans ces cas-là ?
Bref, je ne vais pas y aller par quatre chemins, je pense enlever la gestion des scènes du moteur, c'est de trop haut niveau pour le "socle" qu'est Nazara, je pense qu'elles ont plus leur place dans le NDK, la bibliothèque qui va se lier à tous les modules du moteur et proposer ce qu'il faut pour un moteur de jeu.
Le rendu directement via les modules de plus bas-niveau ne disparaîtrait évidemment pas, mais serait un peu plus chiante à gérer (gestion manuelle des objets, de la visibilité, des techniques et files de rendu), mais je vois ça comme un mal pour un bien (et je suis toujours en pleine réflexion), ça a plus de sens tant au niveau de l'interface que techniquement.
J'aimerai également discuter avec vous d'une idée que j'ai eu et qui m'intéresse beaucoup malgré sa complexité d'implémentation:
Pour l'instant, la scène contient des Node et des SceneNode, dans une hiérarchie claire, les SceneNode sont les objets qui vont affecter le rendu visuellement (lumières, sprites, modèles, textes), possèdent une bounding box et subissent du culling, ce qui n'est pas affichable (simple Node, Camera, etc.) est alors un Node.
Lors du rendu, la scène va parcourir l'arbre de descendance des nodes, détecter les scene node et ajouter ceux qui sont visibles à la RenderQueue.
Dans mon idée, j'aimerai virer les Nodes, ne garder que les SceneNode (la caméra serait alors un SceneNode), rien qu'eux.
Quid des lumières, modèles, etc ? Ils seraient des "effets" attachables à un SceneNode.
Pourquoi complexifier l'interface ? Pour un avantage très simple: un modèle pourrait être attaché à plusieurs SceneNode.
Imaginez une barrière (comme celle de la démo House), vous pourriez n'avoir qu'un seul modèle attaché à une quarantaine de SceneNode, ou encore une grosse armée de soldats marchant au pas: un seul ou quelques SkeletalModel rattachés à plusieurs nodes.
On a ici une diminution de la consommation mémoire (légère mais présente), et surtout une architecture qui encourage le batching: il serait plus facile de reprendre le même objet plutôt que de le dupliquer (comme actuellement).
1
2
3
4
5
6
7
8
9
10
11
12 | // On charge un soldat et on lui applique l'animation de course
NzSkeletalModel soldier;
soldier.LoadFromFile("soldier.fbx");
soldier.SetSequence("run");
for (unsigned int i = 0; i < 100; ++i)
{
NzSceneNode* node = armyNode->CreateChildren();
node->SetPosition((i%10)*100.f, 0.f, (i/10) * 100.f); // Positionnement
node->Attach(soldier); // On attache le soldat au SceneNode
}
|
Résultat ? Le squelette n'est calculé qu'une seule fois, la bounding box (locale) également, le skinning n'est appliqué qu'une fois, et tout est rendu en une fois (très probablement avec de l'instancing).
(Pour être clair: Le moteur est déjà capable de ces optimisations, mais l'API est un peu un frein).
D'un point de vue technique, le node devrait alors stocker une matrice/bounding volume par effet attaché, rien d'insurmontable, ça se complique un peu pour les sprites/textes qui devraient alors stocker leurs sommets (globaux) dans le node aussi, mais encore une fois rien d'insurmontable.
Donc voilà, n'hésitez pas à commenter, pour l'instant rien n'est fait, donc tout peut changer