LearnOpenGL

Translation in progress of learnopengl.com.
git clone https://git.mtkn.jp/LearnOpenGL
Log | Files | Refs

Scene-Graph.html (17528B)


      1     <div id="content">
      2     <h1 id="content-title">Scene Graph</h1>
      3 <h1 id="content-url" style='display:none;'>Guest-Articles/2021/Scene/Scene-Graph</h1>
      4 <p>
      5         In this article, we will talk about the <def>scene graph</def>.
      6 		A scene graph is not a class or an object, it's more like a pattern that allows you to create inheritance.
      7 		This pattern is used a lot in game engines.
      8 		For example, it is used in animation to manage bones.
      9 		If I move my arm, my hand needs to move too.
     10 		To obtain this result, I need a hierarchy with parents and children.
     11 		Thanks to it, my hand will be a child of my arm that is also a child of my body.
     12 		This hierarchy has the consequence of representing objects in global and local space.
     13 		Global space is based on position/rotation and scale based on the world and local space is based on its parent.
     14 		So, if I only move my hand, its local and global position will change but not the local and global position of my arm.
     15 		However, if I move my arm, its local and global position will change but also the global position of my hand.
     16 		Scene graphs can take a lot of forms: register or not the parent, list or contiguous buffer to store the children, flags...
     17 		Firstly, we will see a quick example of it.
     18 		In a second time, we will see simple optimizations to improve it.
     19 		So, talk less, code more ! ;D
     20 	</p>
     21 
     22 	<div class="box">
     23       <img src="/img/guest/2021/scene_graph/graph.png" width="400" style="display: inline" alt="Graph example"/>
     24       <img src="/img/guest/2021/scene_graph/sceneGraph.png" width="400" style="display: inline" alt="Scene graph example in editor"/>
     25 	</div>
     26 
     27 	<p>
     28 		First of all, let’s create a transform.
     29 		Transform will contain local and global space information about our entities
     30 	</p>
     31 
     32 	<pre><code>
     33 struct Transform
     34 {
     35 	/*SPACE INFORMATION*/
     36 	//Local space information
     37 	glm::vec3 pos = { 0.0f, 0.0f, 0.0f };
     38 	glm::vec3 eulerRot = { 0.0f, 0.0f, 0.0f };
     39 	glm::vec3 scale = { 1.0f, 1.0f, 1.0f };
     40 
     41     //Global space information concatenate in matrix
     42 	glm::mat4 modelMatrix = glm::mat4(1.0f);
     43 };
     44 	</code></pre>
     45 
     46 	<note>
     47 		In this example, we use the Euler angle system.
     48 		The Euler system is easy to use and understand but it contains a lot of undesirable effects: <a href="https://en.wikipedia.org/wiki/Gimbal_lock" target="_blank">Gimbal lock</a>, shortest angle between to angle thanks to lerp is wrong, cost and size...
     49         To avoid it, we need to use <strong>quaternion</strong> but it needs to be hidden because it's hard to represent a rotation thanks to it.
     50 		To have a better understanding of this tutorial we will use the Euler angle but I invite you to do some research on quaternion. 
     51 	</note>
     52 
     53 	<p>
     54 		Now we can represent local and global space information about an object.
     55 		We will make create a class called <funct>Entity</funct>.
     56 		This class will simply wrap the space information with a model for the visual demonstration.
     57 	</p>
     58 
     59 	<pre><code>
     60 class Entity : public Model
     61 {
     62 public:
     63 [...]
     64 
     65 Transform transform;
     66 
     67 	// constructor, expects a filepath to a 3D model.
     68 	Entity(string const& path, bool gamma = false) : Model(path, gamma)
     69 	{}
     70 };
     71 	</code></pre>
     72 	<p>
     73 		The scene graph is still missing. So, let's add it into the Entity class.
     74 	</p>
     75 	<pre><code>
     76 /*SCENE GRAPH*/
     77 std::list<std::unique_ptr<Entity>> children;
     78 	Entity* parent = nullptr;
     79 	</code></pre>
     80 	<p>
     81 		As we said, scene graphs can take a lot of forms: use std::vector instead of std::list, don't register its parent to simplify the size of the class...
     82 		In our example we used std::list. 
     83 		We don't want our entity address to change at all.
     84 		My parents’ address always needs to be valid.
     85 		Then, I used std::unique_ptr because it's the cleanest approach to avoid a leak.
     86 		Memory leak appears happens if you allocate memory thanks to new or malloc and forget to call delete or free when datas disappears.
     87 		Information is still in memory but you cannot have access to it.
     88 		If you want more information about it, many tutorials talk about it more clearly.
     89 	</p>
     90 
     91 	<p>
     92 		<dl>
     93 			<dt>
     94 				We need now to create a function to add a child to our entity.
     95 				This function will be easy:
     96 			</dt>
     97 			<dd>
     98 				1: Add entity
     99 			</dd>
    100 			<dd>
    101 				2: Register parent of this new entity as the current entity
    102 			</dd>
    103 		  </dl> 
    104 	</p>
    105 
    106 
    107 	<pre><code>
    108 template<typename... TArgs>
    109 void addChild(const TArgs&... args)
    110 {
    111 	children.emplace_back(std::make_unique<Entity>(args...));
    112 	children.back()->parent = this;
    113 }
    114 	</code></pre>
    115 
    116 	<note>
    117         A variadic template allows us to create an entity with any constructor that you want to use without overloading the addChild function.
    118 		I used it in this example to awake your curiosity and show you a simple use of it.
    119 	</note>
    120 
    121 	<p>
    122 	Ok, perfect! It's almost done, courage!
    123 	Now we have scene graphs and space information but something is still missing.
    124 	When we want to send space information to our shader, we need a model matrix.
    125 	However, we don't see how to compute it thanks to our parents and our local information.
    126 	First, we need a function to create a local model matrix that represents an object in space based on its parent.
    127 	This function will be added to the transform class. 
    128 	</p>
    129 
    130 	<pre><code>
    131 glm::mat4 getLocalModelMatrix()
    132 {
    133 	const glm::mat4 transformX = <function id='57'>glm::rotate</function>(glm::mat4(1.0f),
    134 						 <function id='63'>glm::radians</function>(eulerRot.x),
    135 						 glm::vec3(1.0f, 0.0f, 0.0f));
    136 	const glm::mat4 transformY = <function id='57'>glm::rotate</function>(glm::mat4(1.0f),
    137 						 <function id='63'>glm::radians</function>(eulerRot.y),
    138 						 glm::vec3(0.0f, 1.0f, 0.0f));
    139 	const glm::mat4 transformZ = <function id='57'>glm::rotate</function>(glm::mat4(1.0f),
    140 						 <function id='63'>glm::radians</function>(eulerRot.z),
    141 						 glm::vec3(0.0f, 0.0f, 1.0f));
    142 
    143 	// Y * X * Z
    144 	const glm::mat4 roationMatrix = transformY * transformX * transformZ;
    145 
    146 	// translation * rotation * scale (also know as TRS matrix)
    147 	return <function id='55'>glm::translate</function>(glm::mat4(1.0f), pos) *
    148 				roationMatrix *
    149 				<function id='56'>glm::scale</function>(glm::mat4(1.0f), scale);
    150 }
    151 	</code></pre>
    152 
    153 	<p>
    154 		To combine multiple model matrices, we need to multiply them.
    155 		So if I multiply the local model matrix of my hand with the model matrix of the world with arm and arm with hand, I will obtain the global matrix of my hand!
    156 		So, let's implement a function in entity class that will do it for us: 
    157 	</p>
    158 
    159 	<pre><code>
    160 void updateSelfAndChild()
    161 {
    162 	if (parent)
    163 		modelMatrix = parent->modelMatrix * getLocalModelMatrix();
    164 	else
    165 		modelMatrix = getLocalModelMatrix();
    166 
    167 	for (auto&& child : children)
    168 	{
    169 		child->updateSelfAndChild();
    170 	}
    171 }
    172 	</code></pre>
    173 
    174 	<p>
    175 		Now, if I update the world, all of my entities contained will also be updated and our global model matrix will be computed.
    176 		Finally, we just need to add some code in the main function to add multiple moons with the same local distance and scale and code in the main loop to move the first entity.
    177 	</p>
    178 
    179 	<pre><code>
    180 /*BEFOR THE MAIN LOOP*/
    181 
    182 // load entities
    183 // -----------
    184 const char* pathStr = "resources/objects/planet/planet.obj";
    185 Entity ourEntity(FileSystem::getPath(pathStr));
    186 ourEntity.transform.pos.x = 10;
    187 const float scale = 0.75;
    188 ourEntity.transform.scale = { scale, scale, scale };
    189 
    190 {
    191 	Entity* lastEntity = &ourEntity;
    192 
    193 	for (unsigned int i = 0; i &lt; 10; ++i)
    194 	{
    195 		lastEntity->addChild(FileSystem::getPath(pathStr));
    196 		lastEntity = lastEntity->children.back().get();
    197 
    198 		//Set tranform values
    199 		lastEntity->transform.pos.x = 10;
    200 		lastEntity->transform.scale = { scale, scale, scale };
    201 	}
    202 }
    203 ourEntity.updateSelfAndChild();
    204 
    205 /*IN THE MAIN LOOP*/
    206 
    207 // draw our scene graph
    208 Entity* lastEntity = &ourEntity;
    209 while (lastEntity->children.size())
    210 {
    211 	ourShader.setMat4("model", lastEntity->transform.modelMatrix);
    212 	lastEntity->Draw(ourShader);
    213 	lastEntity = lastEntity->children.back().get();
    214 }
    215 
    216 ourEntity.transform.eulerRot.y += 20 * deltaTime;
    217 ourEntity.updateSelfAndChild();
    218 	</code></pre>
    219 	
    220 	<p>
    221 		We can see this result thanks to this code.
    222 	</p>
    223 
    224       <img src="/img/guest/2021/scene_graph/result.png" alt="Graph example"/>
    225 
    226 	<h2>Optimization</h2>
    227 
    228 	<p>
    229         Being pragmatic is essential to implement a new feature but now let's see how to optimize it.
    230         Firstly, in the main loop, we always update the model matrix even if it doesn't move.
    231 		In programming, a pattern called a dirty flag can help us.
    232         A dirty flag is a simple boolean called "isDirty" that allows us to know if an entity was moved during the previous frame.
    233         So, if the user scales, rotates or translates it, we need to set this flag on.
    234 		Don't forget, the model matrix is based on the parent model matrix.
    235 		So if I change it, I also need to compute all its children's matrix.
    236         This flag will be set off in the update function when the model matrix was recomputed.
    237 	</p>
    238 
    239 	<warning>
    240         This pattern is very powerful but can be integrated easily only if you encapsulate your class correctly.
    241 		I haven’t done it to give you an example of rigid code without the possibility to evolve and change easily.
    242 		This code requires the programmer to code its functionality perfectly and is difficult to change, improve or optimize.
    243 		But in production, time is a precious resource and you sometimes need to implement the feature without optimizing.
    244 		One day, if your feature becomes the performance bottleneck, you need to change it easily.
    245 		Encapsulation, private/protected, getter/setter is C++ advantage that allows you to do it.
    246 		Encapsulation has its downsides too.
    247 		It can increase the size of your class and reduce its visibility.
    248 		It can also be laborious if you create a getter/setter for all your members.
    249         An illarouse <a href="https://www.youtube.com/watch?v=-AQfQFcXac8" target="_blank">video</a> show the c++ limit but keep in mind this example and make do the balance in function of your situation.
    250 	</warning>
    251 
    252 	<p>
    253 		Let's remake do again transform function with encapsulation and dirty flag 
    254 	</p>
    255 
    256 	<pre><code>
    257 class Transform
    258 {
    259 protected:
    260 	//Local space information
    261 	glm::vec3 m_pos = { 0.0f, 0.0f, 0.0f };
    262 	glm::vec3 m_eulerRot = { 0.0f, 0.0f, 0.0f }; //In degrees
    263 	glm::vec3 m_scale = { 1.0f, 1.0f, 1.0f };
    264 
    265 	//Global space information concatenate in matrix
    266 	glm::mat4 m_modelMatrix = glm::mat4(1.0f);
    267 
    268 	//Dirty flag
    269 	bool m_isDirty = true;
    270 
    271 protected:
    272 	glm::mat4 getLocalModelMatrix()
    273 	{
    274 		const glm::mat4 transformX = <function id='57'>glm::rotate</function>(glm::mat4(1.0f),
    275 					<function id='63'>glm::radians</function>(m_eulerRot.x),
    276 					glm::vec3(1.0f, 0.0f, 0.0f));
    277 		const glm::mat4 transformY = <function id='57'>glm::rotate</function>(glm::mat4(1.0f),
    278 					<function id='63'>glm::radians</function>(m_eulerRot.y),
    279 					glm::vec3(0.0f, 1.0f, 0.0f));
    280 		const glm::mat4 transformZ = <function id='57'>glm::rotate</function>(glm::mat4(1.0f),
    281 					<function id='63'>glm::radians</function>(m_eulerRot.z),
    282 					glm::vec3(0.0f, 0.0f, 1.0f));
    283 
    284 		// Y * X * Z
    285 		const glm::mat4 roationMatrix = transformY * transformX * transformZ;
    286 
    287 		// translation * rotation * scale (also know as TRS matrix)
    288 		return <function id='55'>glm::translate</function>(glm::mat4(1.0f), m_pos) *
    289 					roationMatrix *
    290 					<function id='56'>glm::scale</function>(glm::mat4(1.0f), m_scale);
    291 	}
    292 public:
    293 
    294 	void computeModelMatrix()
    295 	{
    296 		m_modelMatrix = getLocalModelMatrix();
    297 	}
    298 
    299 	void computeModelMatrix(const glm::mat4& parentGlobalModelMatrix)
    300 	{
    301 		m_modelMatrix = parentGlobalModelMatrix * getLocalModelMatrix();
    302 	}
    303 
    304 	void setLocalPosition(const glm::vec3& newPosition)
    305 	{
    306 		m_pos = newPosition;
    307 		m_isDirty = true;
    308 	}
    309 
    310 	[...]
    311 
    312 	const glm::vec3& getLocalPosition()
    313 	{
    314 		return m_pos;
    315 	}
    316 
    317 	[...]
    318 
    319 	const glm::mat4& getModelMatrix()
    320 	{
    321 		return m_modelMatrix;
    322 	}
    323 
    324 	bool isDirty()
    325 	{
    326 		return m_isDirty;
    327 	}
    328 };
    329 
    330 
    331 class Entity : public Model
    332 {
    333 public:
    334 	//Scene graph
    335 	std::list<std::unique_ptr<Entity>> children;
    336 	Entity* parent = nullptr;
    337 
    338 	//Space information
    339 	Transform transform;
    340 
    341 	// constructor, expects a filepath to a 3D model.
    342 	Entity(string const& path, bool gamma = false) : Model(path, gamma)
    343 	{}
    344 
    345 	//Add child. Argument input is argument of any constructor that you create.
    346 	//By default you can use the default constructor and don't put argument input.
    347 	template<typename... TArgs>
    348 	void addChild(const TArgs&... args)
    349 	{
    350 		children.emplace_back(std::make_unique<Entity>(args...));
    351 		children.back()->parent = this;
    352 	}
    353 
    354 	//Update transform if it was changed
    355 	void updateSelfAndChild()
    356 	{
    357 		if (!transform.isDirty())
    358 			return;
    359 
    360 		forceUpdateSelfAndChild();
    361 	}
    362 
    363 	//Force update of transform even if local space don't change
    364 	void forceUpdateSelfAndChild()
    365 	{
    366 		if (parent)
    367 			transform.computeModelMatrix(parent->transform.getModelMatrix());
    368 		else
    369 			transform.computeModelMatrix();
    370 
    371 		for (auto&& child : children)
    372 		{
    373 			child->forceUpdateSelfAndChild();
    374 		}
    375 	}
    376 };
    377 	</code></pre>
    378 	
    379 	<p>
    380 	Perfect! Another solution to improve performance can be memorizing the local Model matrix.
    381 	Thanks to it, we avoid recomputing all child local model matrices if the parent is moved.
    382 	This solution improves performance but transforms become heavier.
    383 	This size is really important for your hardware.
    384 	We will see why in the limitation subchapter.
    385 	You can find the code <a href="https://learnopengl.com/code_viewer_gh.php?code=src/8.guest/2021/1.scene/1.scene_graph/scene_graph.cpp" target="_blank">here</a>.
    386 	</p>
    387 
    388 	<h2>Limitation</h2>
    389 
    390 	<p>
    391 		Do you ever hear about <def>data oriented design</def> ? No ? Let's talk brevely about it !
    392 	</p>
    393 	<p>
    394 		This design is based on how your hardware works.
    395 		In your computer, data is aligned into your memory.
    396 		When you use data like a variable, this data is sent to the cache.
    397 		The cache is a very fast memory but without a lot of space.
    398 		This memory is localized into your CPU.
    399 		For example, if you want to make a cake, you need to go to a supermarket (hard disk) to buy ingredients.
    400 		A supermarket is very big but is also very far from your home.
    401 		When you buy your ingredients, you store them in your fridge (RAM).
    402 		Your fridge contains ingredients that you need to live but I hope for you that you don't live only with cake ^^
    403 		It is also small but near to your kitchen worktop.
    404 		And finally, your kitchen worktop (the cache) is small and can't contain all your ingredients but is very near to your preparation.
    405 		It's the same for the PC!
    406 		When you need to process data, your system will copy your data but also all datas after in cache (depending on your cache line size).
    407 		So, if all your datas are not contiguous in your memory, you will be as slow as if you need to take eggs one by one in your fridge to make your cake.
    408 		std::list is a noncontiguous array.
    409 		std::map will probably make the job better but inheritance cannot be aligned into your memory because it's a tree.
    410 		So, if you want to make RTS for example, with an independent unit, you probably don't need or don't want a scene graph to manage your entities.
    411 	</p>
    412 
    413 	<note>
    414 		std::map is tree and is aligned in memory but scene graph will jump into this memory and will be slower than don't use inheritance.
    415 	</note>
    416 
    417 	<p>
    418 		You know now what a Scene graph is and how to use it with its limitations.
    419 		In the next chapter, we will talk about frustum culling with a scene graph.
    420 	</p>
    421 
    422 	<h2>Additional resources</h2>
    423 
    424 	<ul>
    425 		<li><a href="https://walterkuppens.com/post/wtf-is-a-scene-graph/" target="_blank">
    426 			walterkuppens article about scene graph</a>: An article by Walter Kuppens with another approach of the scene graph.</li>
    427 
    428 		<li><a href="https://webglfundamentals.org/webgl/lessons/webgl-scene-graph.html" target="_blank">
    429 			webglfundamentals article and demonstration about scene graph</a>: An article with animated webGL code and animated demonstration</li>			
    430     </ul>
    431 	
    432       <author>
    433 			<strong>Article by: </strong>Six Jonathan<br>
    434 			<strong>Contact: </strong><a href="Six-Jonathan@orange.fr" target="_blank">e-mail</a><br>
    435 			<strong>Date: </strong> 09/2021<br>
    436 			<div>
    437 				<a href="https://github.com/Renardjojo">
    438 					<svg height="32" aria-hidden="true" viewBox="0 0 16 16" version="1.1" width="32" data-view-component="true" class="octicon octicon-mark-github v-align-middle">
    439 						<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"></path>
    440 					</svg>
    441 				</a>
    442 				<a href="https://www.linkedin.com/in/jonathan-six-4553611a9/">
    443 					<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 34 34" class="global-nav__logo">
    444 						<path d="M34,2.5v29A2.5,2.5,0,0,1,31.5,34H2.5A2.5,2.5,0,0,1,0,31.5V2.5A2.5,2.5,0,0,1,2.5,0h29A2.5,2.5,0,0,1,34,2.5ZM10,13H5V29h5Zm.45-5.5A2.88,2.88,0,0,0,7.59,4.6H7.5a2.9,2.9,0,0,0,0,5.8h0a2.88,2.88,0,0,0,2.95-2.81ZM29,19.28c0-4.81-3.06-6.68-6.1-6.68a5.7,5.7,0,0,0-5.06,2.58H17.7V13H13V29h5V20.49a3.32,3.32,0,0,1,3-3.58h.19c1.59,0,2.77,1,2.77,3.52V29h5Z" fill="currentColor"></path>
    445 					</svg>
    446 				</a>
    447 			</div>
    448       </author>
    449 
    450   
    451        
    452 
    453     </div>
    454