Aller au menu - Aller au contenu

Icône Rendu des scene nodes

Avatar
Mise à jour : 23/03/2011
Difficulté : Facile Facile Creative Commons BY-SA
1 621 visites depuis 7 jours, dont 7 sur ce chapitre classé 80/786
Préparez vous psychologiquement amis lecteurs. Dans ce chapitre nous allons creuser très profond pour comprendre comment les scene nodes que nous ajoutons innocemment au scene manager depuis le début de ce tutoriel sont rendus à l'écran.

Autant vous dire que la tâche ne va pas être aisée, mais un peu de courage, dans le prochain chapitre nous apprendrons à créer nos propres types de scene node, et nous aurons évidemment besoin de tout ce qu'on va voir ici. :)
Sommaire du chapitre :
Icône du chapitre
Chapitre précédent Sommaire Chapitre suivant

Au début il y avait le code source

Commençons par le commencement en jetant un oeil à la méthode qui permet de rendre tous les nodes associés au scene manager. Je veux parler de irr::scene::ISceneManager::drawAll() bien sûr.

Dont voici le code source :


Code : C++
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
void CSceneManager::drawAll()
{
	if (!Driver)
		return;

	// reset attributes
	Parameters.setAttribute ( "culled", 0 );
	Parameters.setAttribute ( "calls", 0 );
	Parameters.setAttribute ( "drawn_solid", 0 );
	Parameters.setAttribute ( "drawn_transparent", 0 );
	Parameters.setAttribute ( "drawn_transparent_effect", 0 );

	// reset all transforms
	video::IVideoDriver* driver = getVideoDriver();
	if ( driver )
	{
		driver->setTransform ( video::ETS_PROJECTION, core::IdentityMatrix );
		driver->setTransform ( video::ETS_VIEW, core::IdentityMatrix );
		driver->setTransform ( video::ETS_WORLD, core::IdentityMatrix );
		driver->setTransform ( video::ETS_TEXTURE_0, core::IdentityMatrix );
		driver->setTransform ( video::ETS_TEXTURE_1, core::IdentityMatrix );
		driver->setTransform ( video::ETS_TEXTURE_2, core::IdentityMatrix );
		driver->setTransform ( video::ETS_TEXTURE_3, core::IdentityMatrix );
	}

	driver->setAllowZWriteOnTransparent(Parameters.getAttributeAsBool( ALLOW_ZWRITE_ON_TRANSPARENT) );

	// do animations and other stuff.
	OnAnimate(os::Timer::getTime());

	/*!
		First Scene Node for prerendering should be the active camera
		consistent Camera is needed for culling
	*/
	camWorldPos.set(0,0,0);
	if ( ActiveCamera )
	{
		ActiveCamera->OnRegisterSceneNode();
		camWorldPos = ActiveCamera->getAbsolutePosition();
	}

	// let all nodes register themselves
	OnRegisterSceneNode();

	if(LightManager)
		LightManager->OnPreRender(LightList);

	u32 i; // new ISO for scoping problem in some compilers

	//render camera scenes
	{
		CurrentRendertime = ESNRP_CAMERA;
		Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);

		if(LightManager)
			LightManager->OnRenderPassPreRender(CurrentRendertime);

		for (i=0; i<CameraList.size(); ++i)
			CameraList[i]->render();

		CameraList.set_used(0);

		if(LightManager)
			LightManager->OnRenderPassPostRender(CurrentRendertime);
	}

	//render lights scenes
	{
		CurrentRendertime = ESNRP_LIGHT;
		Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);

		if(LightManager)
		{
			LightManager->OnRenderPassPreRender(CurrentRendertime);
		}
		else
		{
			// Sort the lights by distance from the camera
			core::vector3df camWorldPos(0, 0, 0);
			if(ActiveCamera)
				camWorldPos = ActiveCamera->getAbsolutePosition();

			core::array<DistanceNodeEntry> SortedLights;
			SortedLights.set_used(LightList.size());
			for(s32 light = (s32)LightList.size() - 1; light >= 0; --light)
				SortedLights[light].setNodeAndDistanceFromPosition(LightList[light], camWorldPos);

			SortedLights.set_sorted(false);
			SortedLights.sort();

			for(s32 light = (s32)LightList.size() - 1; light >= 0; --light)
				LightList[light] = static_cast<ILightSceneNode*>(SortedLights[light].Node);
		}

		Driver->deleteAllDynamicLights();

		Driver->setAmbientLight(AmbientLight);

		u32 maxLights = LightList.size();

		if(!LightManager)
			maxLights = core::min_ ( Driver->getMaximalDynamicLightAmount(), maxLights);

		for (i=0; i< maxLights; ++i)
			LightList[i]->render();

		if(LightManager)
			LightManager->OnRenderPassPostRender(CurrentRendertime);
	}

	// render skyboxes
	{
		CurrentRendertime = ESNRP_SKY_BOX;
		Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);

		if(LightManager)
		{
			LightManager->OnRenderPassPreRender(CurrentRendertime);
			for (i=0; i<SkyBoxList.size(); ++i)
			{
				ISceneNode* node = SkyBoxList[i];
				LightManager->OnNodePreRender(node);
				node->render();
				LightManager->OnNodePostRender(node);
			}
		}
		else
		{
			for (i=0; i<SkyBoxList.size(); ++i)
				SkyBoxList[i]->render();
		}

		SkyBoxList.set_used(0);

		if(LightManager)
			LightManager->OnRenderPassPostRender(CurrentRendertime);
	}


	// render default objects
	{
		CurrentRendertime = ESNRP_SOLID;
		Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);

		SolidNodeList.sort(); // sort by textures

		if(LightManager)
		{
			LightManager->OnRenderPassPreRender(CurrentRendertime);
			for (i=0; i<SolidNodeList.size(); ++i)
			{
				ISceneNode* node = SolidNodeList[i].Node;
				LightManager->OnNodePreRender(node);
				node->render();
				LightManager->OnNodePostRender(node);
			}
		}
		else
		{
			for (i=0; i<SolidNodeList.size(); ++i)
				SolidNodeList[i].Node->render();
		}

		Parameters.setAttribute ( "drawn_solid", (s32) SolidNodeList.size() );
		SolidNodeList.set_used(0);

		if(LightManager)
			LightManager->OnRenderPassPostRender(CurrentRendertime);
	}

	// render shadows
	{
		CurrentRendertime = ESNRP_SHADOW;
		Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);

		if(LightManager)
		{
			LightManager->OnRenderPassPreRender(CurrentRendertime);
			for (i=0; i<ShadowNodeList.size(); ++i)
			{
				ISceneNode* node = ShadowNodeList[i];
				LightManager->OnNodePreRender(node);
				node->render();
				LightManager->OnNodePostRender(node);
			}
		}
		else
		{
			for (i=0; i<ShadowNodeList.size(); ++i)
				ShadowNodeList[i]->render();
		}

		if (!ShadowNodeList.empty())
			Driver->drawStencilShadow(true,ShadowColor, ShadowColor,
				ShadowColor, ShadowColor);

		ShadowNodeList.set_used(0);

		if(LightManager)
			LightManager->OnRenderPassPostRender(CurrentRendertime);
	}

	// render transparent objects.
	{
		CurrentRendertime = ESNRP_TRANSPARENT;
		Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);

		TransparentNodeList.sort(); // sort by distance from camera
		if(LightManager)
		{
			LightManager->OnRenderPassPreRender(CurrentRendertime);

			for (i=0; i<TransparentNodeList.size(); ++i)
			{
				ISceneNode* node = TransparentNodeList[i].Node;
				LightManager->OnNodePreRender(node);
				node->render();
				LightManager->OnNodePostRender(node);
			}
		}
		else
		{
			for (i=0; i<TransparentNodeList.size(); ++i)
				TransparentNodeList[i].Node->render();
		}

		Parameters.setAttribute ( "drawn_transparent", (s32) TransparentNodeList.size() );
		TransparentNodeList.set_used(0);

		if(LightManager)
			LightManager->OnRenderPassPostRender(CurrentRendertime);
	}

	// render transparent effect objects.
	{
		CurrentRendertime = ESNRP_TRANSPARENT_EFFECT;
		Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);

		TransparentEffectNodeList.sort(); // sort by distance from camera

		if(LightManager)
		{
			LightManager->OnRenderPassPreRender(CurrentRendertime);

			for (i=0; i<TransparentEffectNodeList.size(); ++i)
			{
				ISceneNode* node = TransparentEffectNodeList[i].Node;
				LightManager->OnNodePreRender(node);
				node->render();
				LightManager->OnNodePostRender(node);
			}
		}
		else
		{
			for (i=0; i<TransparentEffectNodeList.size(); ++i)
				TransparentEffectNodeList[i].Node->render();
		}

		Parameters.setAttribute ( "drawn_transparent_effect", (s32) TransparentEffectNodeList.size() );
		TransparentEffectNodeList.set_used(0);
	}

	if(LightManager)
		LightManager->OnPostRender();

	LightList.set_used(0);
	clearDeletionList();

	CurrentRendertime = ESNRP_NONE;
}


Et beh quoi ?! Partez pas ! Ça parait impressionnant comme ça mais en fait... Ouais OK, en fait c'est vraiment compliqué. Mais bon pas de panique, nous avons tout un chapitre pour comprendre cette fonction. Et nous allons d'ailleurs le découper pour qu'il corresponde aux 3 grandes étapes de celle-ci :
  • Préparation
  • Pré-rendu
  • Rendu

Préparation

On entame avec quelque chose de simple. Le premier bloc sert à tester qu'il existe bien un driver.

Code : C++
1
2
if (!Driver)
        return;



Comme l'indique le commentaire, Le bloc suivant a pour but de remettre certains "attributs" à zero. Ces attributs servent en fait à compter un certain nombre de choses à des fins de débogguage. drawn_solid représente par exemple le nombre de scene node "solid" qui vont êtres rendus, comme nous le verrons plus loin dans la fonction.

Code : C++
1
2
3
4
5
6
// reset attributes
Parameters.setAttribute ( "culled", 0 );
Parameters.setAttribute ( "calls", 0 );
Parameters.setAttribute ( "drawn_solid", 0 );
Parameters.setAttribute ( "drawn_transparent", 0 );
Parameters.setAttribute ( "drawn_transparent_effect", 0 );



Ensuite, et comme l'indique le commentaire une fois de plus, on fait un reset général des matrices. Toutes les matrices du driver sont multipliées à la matrice identité pour les remettre à leur état d'origine (voir ici pour les néophytes en mathématique) :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// reset all transforms
video::IVideoDriver* driver = getVideoDriver();
if ( driver )
{
	driver->setTransform ( video::ETS_PROJECTION, core::IdentityMatrix );
	driver->setTransform ( video::ETS_VIEW, core::IdentityMatrix );
	driver->setTransform ( video::ETS_WORLD, core::IdentityMatrix );
	driver->setTransform ( video::ETS_TEXTURE_0, core::IdentityMatrix );
	driver->setTransform ( video::ETS_TEXTURE_1, core::IdentityMatrix );
	driver->setTransform ( video::ETS_TEXTURE_2, core::IdentityMatrix );
	driver->setTransform ( video::ETS_TEXTURE_3, core::IdentityMatrix );
}



La ligne suivante permet de prendre en compte le flag qui détermine si on peut ou pas écrire les objets transparents sur le Z buffer. Par défaut on ne peut pas, mais il est possible de forcer le flag manuellement grâce à une fonction du scene manager.

Code : C++
1
driver->setAllowZWriteOnTransparent(Parameters.getAttributeAsBool( ALLOW_ZWRITE_ON_TRANSPARENT) );

Pré-rendu

On attaque maintenant des choses un peu plus ardues. J'espère que vous avez lu attentivement le chapitre précédent. :)

La fonction qui nous intéresse est OnAnimate. Elle n'est pas spécifique au scene manager mais à la classe irr::scene::ISCeneNode. C'est exactement le même principe que pour le rendu des éléments de la GUI que nous avons vu dans le chapitre précédent. La fonction OnAnimate est redéfinie pour chaque type de scene node, et s'appelle chez tous les nodes enfants. Comme l'indique le commentaire, OnAnimate est principalement utile pour calculer les animations des modèles 3D mais aussi d'autres petites choses.

Code : C++
1
2
// do animations and other stuff.
OnAnimate(os::Timer::getTime());



Le commentaire suivant explique parfaitement les prochaines lignes de code mais voici tout de même la traduction pour nos amis non-anglophones : le premier scene node à être pré-rendu doit être la caméra active étant donné qu'elle est nécessaire au culling. Pour ceux qui ne le saurait pas, le culling consiste en gros à ne pas calculer (ne pas effectuer le rendu de) tout ce qui ne sera pas visible à l'écran, donc tout ce qui n'entre pas dans le champ de la caméra.

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/*!
	First Scene Node for prerendering should be the active camera
	consistent Camera is needed for culling
*/
camWorldPos.set(0,0,0);
if ( ActiveCamera )
{
	ActiveCamera->OnRegisterSceneNode();
	camWorldPos = ActiveCamera->getAbsolutePosition();
}

camWorldPos est un attribut du scene manager qui contient la position de la caméra active. ActiveCamera est bien entendu un attribut du scene manager qui pointe vers la caméra active. Le bloc se contente donc de mettre à jour l'attribut du scene manager contenant la position de la caméra après qu'on ait appellé la fonction OnRegisterSceneNode de celle-ci. Et c'est cela qui nous interesse le plus car cette fonction, tout comme OnAnimate, va s'appeller recursivement dans tous les scene nodes, comme le montrent les 2 lignes suivantes :


Code : C++
1
2
// let all nodes register themselves
OnRegisterSceneNode();

Pour comprendre le but de OnRegisterSceneNode, il faut déjà savoir que le scene manager procède au rendu des scene nodes dans un ordre spécifique. Après la caméra ce sont les scene nodes lumières, puis les skyboxes, etc... Nous verrons la liste complète un peu plus loin. Pour réussir à effectuer le rendu dans l'ordre, le scene manager se sert de plusieurs listes chainées pour stocker les différents types de node. Si vous jetez un oeil vers la fin du fichier CSceneManager.h, vous verrez la déclaration de ces listes chainées :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
core::array<ISceneNode*> CameraList;
core::array<ILightSceneNode*> LightList;
core::array<ISceneNode*> ShadowNodeList;
core::array<ISceneNode*> SkyBoxList;
core::array<DefaultNodeEntry> SolidNodeList;
core::array<TransparentNodeEntry> TransparentNodeList;
core::array<TransparentNodeEntry> TransparentEffectNodeList;

core::array<IMeshLoader*> MeshLoaderList;
core::array<ISceneNode*> DeletionList;
core::array<ISceneNodeFactory*> SceneNodeFactoryList;
core::array<ISceneNodeAnimatorFactory*> SceneNodeAnimatorFactoryList;

Le but de OnRegisterSceneNode est tout simplement de dispatcher chaque scene node ajouté à la scène dans la liste correspondante.


Le morceau de code suivant concerne les personnes qui veulent un contrôle très précis sur l'éclairage de leurs scènes. LightManager est un pointeur de type ILightManager stocké en attribut du scene manager et qui ne pointe sur rien par défaut. A vrai dire cette classe n'existe dans le moteur que sous forme abstraite. Il appartient à l'utilisateur qui veut s'en servir de redéfinir lui même ses fonctions. Ainsi il est possible d'avoir accès à la liste chainée contenant tous les scene nodes en rapport avec la lumière si on a créé une instance d'une classe dérivée de ILightManager et qu'on l'a ajouté au scene manager via la fonction prévue pour (setLightManager, qui n'est pas documentée). Nous aborderons peut être le sujet plus en profondeur dans un chapitre ultérieur, mais pour le moment, retenez juste que par défaut LightManager n'a aucune incidence sur le processus de rendu. ;)

Code : C++
1
2
if(LightManager)
	LightManager->OnPreRender(LightList);

Rendu

La "partie" rendu de la fonction commence tout bêtement par la déclaration d'une variable qui va servir à parcourir des tableaux. Rien de neuf sous le soleil :

Code : C++
1
u32 i; // new ISO for scoping problem in some compilers



Là on attaque du lourd, le "véritable" rendu commence maintenant. Première constatation, ce bloc concerne les cameras :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//render camera scenes
{
    CurrentRendertime = ESNRP_CAMERA;
    Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);

    if(LightManager)
	LightManager->OnRenderPassPreRender(CurrentRendertime);

    for (i=0; i<CameraList.size(); ++i)
	CameraList[i]->render();

    CameraList.set_used(0);

    if(LightManager)
	LightManager->OnRenderPassPostRender(CurrentRendertime);
}



La ligne 3 est intéressante, et va se retrouver au début de chaque "bloc de rendu". CurrentRendertime est un attribut du scene manager de type E_SCENE_NODE_RENDER_PASS. En fouillant un peu dans le fichier ISceneManager.h (à ce stade vous pouvez tout simplement oublier la doc... Image utilisateur ) on peut trouver l'énumération de cette chose. Je vous mets la version longue qui, certes l'est, mais permet de tout comprendre d'un coup :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//! Enumeration for render passes.
/** A parameter passed to the registerNodeForRendering() method of the ISceneManager,
specifying when the node wants to be drawn in relation to the other nodes. */
enum E_SCENE_NODE_RENDER_PASS
{
    //! No pass currently active
    ESNRP_NONE =0,

    //! Camera pass. The active view is set up here. The very first pass.
    ESNRP_CAMERA =1,

    //! In this pass, lights are transformed into camera space and added to the driver
    ESNRP_LIGHT =2,

    //! This is used for sky boxes.
    ESNRP_SKY_BOX =4,

    //! All normal objects can use this for registering themselves.
    /** This value will never be returned by
    ISceneManager::getSceneNodeRenderPass(). The scene manager
    will determine by itself if an object is transparent or solid
    and register the object as SNRT_TRANSPARENT or SNRT_SOLD
    automatically if you call registerNodeForRendering with this
    value (which is default). Note that it will register the node
    only as ONE type. If your scene node has both solid and
    transparent material types register it twice (one time as
    SNRT_SOLID, the other time as SNRT_TRANSPARENT) and in the
    render() method call getSceneNodeRenderPass() to find out the
    current render pass and render only the corresponding parts of
    the node. */
    ESNRP_AUTOMATIC =24,

    //! Solid scene nodes or special scene nodes without materials.
    ESNRP_SOLID =8,

    //! Transparent scene nodes, drawn after solid nodes. They are sorted from back to front and drawn in that order.
    ESNRP_TRANSPARENT =16,

    //! Transparent effect scene nodes, drawn after Transparent nodes. They are sorted from back to front and drawn in that order.
    ESNRP_TRANSPARENT_EFFECT =32,

    //! Drawn after the transparent nodes, the time for drawing shadow volumes
    ESNRP_SHADOW =64
};


Et voilà donc que CurrentRendertime est un nombre indiquant quel est le type de scene node actuellement en train d'être rendu. Son utilité est visible dès la ligne suivante où il sert à effectuer un ET logique. En gros cette ligne 3 teste que le type de node en train d'être rendu correspond avec ce que l'OverrideMaterial doit "surpasser" (souvenez vous). Si c'est le cas alors il fait son boulot, sinon le résultat du ET logique renverra 0 et rien ne se passera.

Ligne 9, la liste chainée des cameras est parcourue et la méthode render de chaque élément appelée. Et enfin ligne 12, la liste chainée est redimensionnée à 0 éléments.


Le prochain bloc est un peu plus complexe (si si c'est possible, vous allez voir). Il concerne la lumière comme l'indique le commentaire et les 9 premières lignes ne devraient pas vous poser de problème. C'est après que ça se corse.

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//render lights scenes
{
    CurrentRendertime = ESNRP_LIGHT;
    Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);

    if(LightManager)
    {
	LightManager->OnRenderPassPreRender(CurrentRendertime);
    }
    else
    {
	// Sort the lights by distance from the camera
	core::vector3df camWorldPos(0, 0, 0);
	if(ActiveCamera)
	     camWorldPos = ActiveCamera->getAbsolutePosition();

	core::array<DistanceNodeEntry> SortedLights;
	SortedLights.set_used(LightList.size());
	for(s32 light = (s32)LightList.size() - 1; light >= 0; --light)
	    SortedLights[light].setNodeAndDistanceFromPosition(LightList[light], camWorldPos);

	SortedLights.set_sorted(false);
	SortedLights.sort();

	for(s32 light = (s32)LightList.size() - 1; light >= 0; --light)
	    LightList[light] = static_cast<ILightSceneNode*>(SortedLights[light].Node);
    }

    Driver->deleteAllDynamicLights();

    Driver->setAmbientLight(AmbientLight);

    u32 maxLights = LightList.size();

    if(!LightManager)
	maxLights = core::min_ ( Driver->getMaximalDynamicLightAmount(), maxLights);

    for (i=0; i< maxLights; ++i)
        LightList[i]->render();

    if(LightManager)
        LightManager->OnRenderPassPostRender(CurrentRendertime);
}


Lignes 13 à 15, un vecteur contenant la position de la camera active est créé et initialisé. Ligne 17, c'est une liste chainée qui est créée, elle va servir à stocker les nodes à rendre en fonction de leur distance avec la caméra. Notez au passage qu'elle est de type DistanceNodeEntry, ce qui lui permet de stocker un pointeur vers un node ILightSceneNode et une distance (un nombre) associé à celui-ci. Elle est d'ailleurs paramétrée ligne 18 pour atteindre la taille de la liste chainée du scene manager contenant tous les scene nodes "lumières".

Lignes 19 et 20, une boucle est faite permettant à la liste "sorted" de dupliquer la liste originale en stockant pour chaque noeud la distance par rapport à la camera. C'est alors un jeu d'enfant en ligne 23 de trier la liste, pour ensuite la re-basculer (l.25-26) en bonne et due forme dans la liste originale gérée par le scene manager. La seule subtilité réside dans le cast qui permet de revenir à un ILightSceneNode classique, sachant que la liste chainée "sorted" était de type DistanceNodeEntry.

La ligne 29 est on ne peut plus explicite, et la ligne 31 est tout à fait logique quand on se souvient que c'est le scene manager et non le driver qui gère la lumière ambiante (comme nous l'avons déjà vu ici). Enfin ligne 33, une variable contenant la taille de la liste chainée est créée, et est utilisée ligne 38 pour passer tous les éléments en revue et ainsi pouvoir appeler leur fonction render. Retenez au passage que cette fois-ci la liste chainée n'a pas été réduite à 0, ce sera pour plus tard. ;)


Le bloc suivant est une promenade de santé en comparaison. A vrai dire il n'y a rien que nous n'ayons déjà vu, aussi allons nous directement passer à celui d'après. :)

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// render skyboxes
{
    CurrentRendertime = ESNRP_SKY_BOX;
    Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);

    if(LightManager)
    {
	LightManager->OnRenderPassPreRender(CurrentRendertime);
	for (i=0; i<SkyBoxList.size(); ++i)
	{
            ISceneNode* node = SkyBoxList[i];
	    LightManager->OnNodePreRender(node);
	    node->render();
	    LightManager->OnNodePostRender(node);
	}
    }
    else
    {
	for (i=0; i<SkyBoxList.size(); ++i)
	    SkyBoxList[i]->render();
    }

    SkyBoxList.set_used(0);

    if(LightManager)
	LightManager->OnRenderPassPostRender(CurrentRendertime);
}



Là encore pas de difficultés majeures mais quelques subtilités néanmoins sur lesquelles nous allons nous attarder. Ligne 6 nous pouvons voir que la liste contenant les nodes "solid" est triée. C'est le même principe qu'auparavant, le type de cette liste est hybride et ne contient pas uniquement des scene nodes.

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// render default objects
{
    CurrentRendertime = ESNRP_SOLID;
    Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);

    SolidNodeList.sort(); // sort by textures

    if(LightManager)
    {
	LightManager->OnRenderPassPreRender(CurrentRendertime);
	for (i=0; i<SolidNodeList.size(); ++i)
	{
	    ISceneNode* node = SolidNodeList[i].Node;
	    LightManager->OnNodePreRender(node);
	    node->render();
	    LightManager->OnNodePostRender(node);
	}
    }
    else
    {
	for (i=0; i<SolidNodeList.size(); ++i)
	    SolidNodeList[i].Node->render();
    }

    Parameters.setAttribute ( "drawn_solid", (s32) SolidNodeList.size() );
    SolidNodeList.set_used(0);

    if(LightManager)
        LightManager->OnRenderPassPostRender(CurrentRendertime);
}


Ensuite c'est du grand classique, on appelle la fonction render de chaque noeud lignes 21 et 22. Petite subtilité ligne 25 où on stocke le nombre de noeuds rendus dans un "attribut" vu plus haut dans ce chapitre. Et enfin on remet la liste chainée à 0 ligne 26.


Seules les lignes 23 à 25 devraient vous poser problème dans le bloc suivant. En fait le nom de la fonction est assez explicite puisque celle-ci va se servir du stencil buffer pour dessiner les ombres du rendu. C'est trop complexe pour être abordé ici (le chapitre est déjà suffisamment long ^^ ), mais qui sait, peut être plus tard...

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// render shadows
{
    CurrentRendertime = ESNRP_SHADOW;
    Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);

    if(LightManager)
    {
	LightManager->OnRenderPassPreRender(CurrentRendertime);
	for (i=0; i<ShadowNodeList.size(); ++i)
	{
	    ISceneNode* node = ShadowNodeList[i];
	    LightManager->OnNodePreRender(node);
	    node->render();
	    LightManager->OnNodePostRender(node);
	}
    }
    else
    {
	for (i=0; i<ShadowNodeList.size(); ++i)
	ShadowNodeList[i]->render();
    }

    if (!ShadowNodeList.empty())
	Driver->drawStencilShadow(true,ShadowColor, ShadowColor,
	ShadowColor, ShadowColor);

    ShadowNodeList.set_used(0);

    if(LightManager)
	LightManager->OnRenderPassPostRender(CurrentRendertime);
}



Rien de très palpitant pour le bloc suivant :

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// render transparent objects.
    {
	CurrentRendertime = ESNRP_TRANSPARENT;
	Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);

	TransparentNodeList.sort(); // sort by distance from camera
	if(LightManager)
	{
	    LightManager->OnRenderPassPreRender(CurrentRendertime);

	    for (i=0; i<TransparentNodeList.size(); ++i)
            {
		ISceneNode* node = TransparentNodeList[i].Node;
		LightManager->OnNodePreRender(node);
		node->render();
		LightManager->OnNodePostRender(node);
	    }
        }
	else
	{
	    for (i=0; i<TransparentNodeList.size(); ++i)
	        TransparentNodeList[i].Node->render();
	}

	Parameters.setAttribute ( "drawn_transparent", (s32) TransparentNodeList.size() );
	TransparentNodeList.set_used(0);

	if(LightManager)
	    LightManager->OnRenderPassPostRender(CurrentRendertime);
}



Idem. Ca en devient presque trop facile. ;)

Code : C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// render transparent effect objects.
{
    CurrentRendertime = ESNRP_TRANSPARENT_EFFECT;
    Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRendertime) != 0);

    TransparentEffectNodeList.sort(); // sort by distance from camera

    if(LightManager)
    {
	LightManager->OnRenderPassPreRender(CurrentRendertime);

	for (i=0; i<TransparentEffectNodeList.size(); ++i)
	{
	    ISceneNode* node = TransparentEffectNodeList[i].Node;
	    LightManager->OnNodePreRender(node);
	    node->render();
	    LightManager->OnNodePostRender(node);
	}
    }
    else
    {
	for (i=0; i<TransparentEffectNodeList.size(); ++i)
	TransparentEffectNodeList[i].Node->render();
    }

    Parameters.setAttribute ( "drawn_transparent_effect", (s32) TransparentEffectNodeList.size() );
    TransparentEffectNodeList.set_used(0);
}



Et nous voici déjà au bout de la fonction. On peut voir ligne 4 la mise à 0 de la chaine des scene nodes "lumières" qui manquait plus haut. Ligne 5, la DeletionList est nettoyée. Il s'agit d'une liste chainée à laquelle on peut ajouter des noeuds qui seront donc supprimés à la fin de chaque rendu. Par défaut elle est vide. Et enfin ligne 7, on remet le CurrentRendertime à sa valeur d'origine, indiquant qu'aucun rendu n'est en cours.

Code : C++
1
2
3
4
5
6
7
if(LightManager)
    LightManager->OnPostRender();

LightList.set_used(0);
clearDeletionList();

CurrentRendertime = ESNRP_NONE;

Et le rendu fut

Je crois qu'on vient de voir un peu trop d'informations pour pouvoir tout assimiler d'un coup. Alors voici un petit tableau récapitulatif des principales étapes de la fonction drawAll :


DétailGénéral
Reset des attributs Préparation
Reset des matrices
OnAnimate Pré-rendu
Pré-rendu caméra
OnRegisterSceneNode
Rendu des caméras Rendu
Rendu des lumière
Rendu des skyboxes
Rendu des nodes par défaut
Rendu des ombres
Rendu des objets transparents
Rendu des effets de transparence
Nettoyage


Voila qui pourra servir de pense bête au cas où (si si je vous assure qu'il est parfois utile de se rappeler de l'ordre des opérations de rendu dans cette fonction ^^ ).
Une fois encore l'intérêt de descendre aussi bas dans le code source n'est peut être pas évident. Au delà du fait que c'est amusant (comment ça pas pour vous ?), c'est très utile dès qu'on veut modifier quelque chose. Histoire de ne pas jouer à la roulette russe quand on dérive une classe.

Et depuis le temps qu'on en parle, c'est au prochain chapitre que nous allons enfin créer nos propres types de scene node. De grandes réjouissances en perspectives. ;)
Chapitre précédent Sommaire Chapitre suivant

Partager

1 commentaire pour "Rendu des scene nodes"
Note moyenne : 3.60 / 4 (122 votes)
Pseudo Commentaire
Hors ligne titz # Posté le 02/11/2009 à 19:48:39
Avatar

Avis : Très bon

Ville : Vernon
Pays : France métropolitaine

Sa fait plaisir de voir que presque trois ans après la création du tutoriel, tu l' entretiens toujours et que même que tu rajoute du contenu :D

'fin, ce tutoriel (en tout) il est sympa. Sa m'as fait plaisir d'utiliser Irrlicht, c'est gratifiant, interessant, toussa²

Merci et bon courage pour la suite(si y en a une :p), ++
 

Voir tous les commentaires