Der Schmale – David Lenaerts’s blog

Flash Platform Experiments

Building efficient content in Away3D 4.0: Sharing Materials & Geometries

Tags: , , , , , , , ,

Time to dig deeper into Away3D 4.0 and see how you can structure and manage your content to get the best performance! If you haven’t read the previous posts, it wouldn’t hurt to do so now, since this one will be all about Materials (on the Mesh side of things) and Geometries.

In any sort of project, if you have data collections that use a lot of memory, it makes sense not to duplicate them if you want to share the same data across several clients (clients simply meaning “users of the data”). Instead, you make all clients refer to the same data object, or model. Similarly, you probably wouldn’t want to perform the same expensive operations on this data for every client. The shared model can take care of that only once for all clients. In the Away3D scene graph structure, the Geometry is essentially your model, and a Mesh refers to it. Many Meshes can make use of the same Geometry. Similarly, Materials can be shared across Meshes as well. This all sounds very logical, yet for some reason I can’t fathom, this is a principle that’s often violated against. Even though Away3D internally tries to minimize memory use and tries to share things as best as it can (it caches Program3D instances, Texture objects, etc., across material instances), I can’t stress this enough: reuse Material and Geometry instances whenever possible. Not only does it limit CPU and GPU memory usage, it’s also essential for performance. I’ll explain why.

Reusing Materials

Every material uses a “shader program”, which is represented in Actionscript by a Program3D object. It’s a combination of a vertex shader and a fragment shader. I won’t go into the subject of shader programs in detail, but if you’re interested, check wikipedia for starters. A large task of the shader program is to define the colour of every pixel that’s being drawn. In other words, it controls how your rendered object will be coloured, textured, lit, shaded, … If it’s on your screen, it passed through a shader program. As a result, a different material means it needs a different Program3D. When drawing objects, the gpu needs to be told to use a different program every time for every material. Unfortunately, this switch is typically very expensive and something you’d want to prevent as much as possible. To this end, Away3D sorts all the objects that need to be rendered (the so called “renderables”) primarily according to their materials so all objects with the same material will be rendered consecutively. If you’re using different material instances for things that look exactly the same, it means more programs need to be switched needlessly.

Sharing materials allows more renderables (here SubGeometry objects) to be rendered with less switches

Sharing materials allows more renderables (here SubGeometry objects) to be rendered with less switches

Okay, I’ll admit, that isn’t entirely true. For those interested, I’ll explain what really happens. Whenever a material is created or changed so that it needs to create a new shader program, the vertex and fragment code is regenerated. A Program3D is requested from a central cache, but if a Program3D object already exists for that exact code combination, the existing object is returned. Different materials using the same code also use the same programs, which means there won’t be any program switching. However, it’s easy enough to accidentally have small differences between materials so that the code – and in turn the program instance – changes. And the following is still true regardless:

Every time a material changes, certain data needs to be uploaded to the GPU: Textures, lighting properties, … No caching of Program3D objects is going to prevent that. If you use a new material instance for every object, these uploads may occur for every object. Share a material, and it only happens once for all the renderables using it.

Note: if you really need several material instances, but you’re using the same BitmapData objects as textures or normal maps, at least try to share those across materials unless you have a very good reason not to. RAM’s not for wasting! Furthermore, there’s a limit to the amount of Texture objects (a buffer object representing the image data on the GPU) that can be created, and for every BitmapData, a Texture is created. You wouldn’t want to go over the limit!

Reusing Geometries

Similarly, Meshes that use the same data should all refer to a single Geometry instance. A very common example is a game in which there are several occurences of the same monster type spread throughout the game map. You can have one “beer demon” Geometry, and place many beer demons in your level by making the different Mesh objects refer to that single Geometry instance. Not only does this use much less memory, it also causes less instances of VertexBuffer3D to be created (these are buffers representing the vertex data on the GPU side of things). There’s a limit on how many can exist at any given time and if you exceed it, an error will be thrown and your game will crash. Sharing Geometry objects can also result in better performance in some cases, because if the same Geometry was used for the previously rendered object, it may not have to reupload its data to the GPU!

Material/Geometry Tandems

In many cases, especially in games, you’ll have a collection of game object “types”, such as different monsters, a barrel, weapon/health/power-up pick-ups. Each of these objects will probably have their own specific Geometry and Material objects. Makes sense, since you typically wouldn’t want to use a barrel geometry for a monster, or a shotgun texture on a health boost pick-up, would you? This one-on-one mapping is a pretty fortunate situation, because it means that the rendering for these objects can be batched very efficiently. Only when different objects will be rendered, does any material or geometry data need to be uploaded, with the exception of scene-graph-specific data such as transformation matrices.

Rendering objects with one-on-one Material/Geometry combinations

Rendering objects with one-on-one Material/Geometry combinations

Making sure that the same Geometry and Material instances are used across all instances for the same game object type is easy. The Mesh class contains a clone() method that creates a new Mesh instance that refers to the same Geometry and Material. This way, you can build a library of reference Meshes that are not actually added to the scene. Instead, you populate your scene with these clone objects. You can dispose of them whenever they’re picked up (health pack) or killed (monster), while the reference mesh and its material and geometry can be disposed when everything is unloaded. For this reference library, you can use Away3D’s AssetLibrary, which will take care of all your loading and management needs :)

For example

Here’s a comparison:

Demo NOT reusing anything (warning, can be very slow!)

Demo reusing both Geometry and Materials

Source for both

I’ll admit, a large cost in the first demo is the creation of the objects rather than their use. This creation is a constant cost, however, and you can see the performance drop drastically over a short time. So… Enough incentive? :-)

 

Update

In the “dev” branch (and when we go Beta it will also be in the master branch), BitmapMaterial was removed in favour of TextureMaterial, accepting a texture rather than a BitmapData. It goes without saying that you should share these texture objects whenever possible rather than creating new textures with the same content.

All sources for this post have been updated to use said dev branch: https://github.com/away3d/away3d-core-fp11/tree/dev

Leave a comment (15 comments)

The Away3D 4.0 Scene Graph Architecture 2: Meshes and Geometries

Tags: , , , , , , ,

It’s time to elaborate on the previous blog post’s topic and investigate one of the most important scene graph nodes: the Mesh. It’ll probably be the object you’ll be working with most, and it allows a form of flexibility that can really provide a benefit to your memory usage and performance. That is, of course, if you know how. I’ll talk more about performance and memory usage next time, for now it’s just important to understand how Mesh works.

Meshes and Geometries

Meshes and Geometry objects are closely related. Geometries provide the – you guessed it – geometry of a 3D object: the vertices, triangle indices, UV coordinates, vertex normals, and what have you. Mesh objects are there to give a Geometry a place in the scene graph (and as such, a position in 3D space), as well as link it to a material. Why bother with the distinction, you may wonder. Why not just place the geometry data inside the Mesh class and be done with it? In many cases, you’d want to use the same geometry in several places. Different monsters of the same type throughout a level, for instance. The Geometry object allows you to save memory and limit the amount of data that’s uploaded to the GPU.

Both Meshes and Geometry mirror each other in that they both contain “sub”-objects (SubMesh and SubGeometry, respectively). Each SubMesh object links to a corresponding SubGeometry object in the Mesh’s Geometry object. Each SubGeometry provides the vertex and triangle data for each part that will make up the entire object. Confused? Check the graph below. The reason for this subdivision is mainly so multiple materials can be used within a single Mesh. Because of how the GPU issues draw commands, materials cannot be assigned to individual triangles in the model. Instead, several SubGeometries are used, and each can be rendered with a different material. This is achieved by assigning a material to the corresponding SubMesh. SubMesh materials are optional; if not provided, the main Mesh object’s material is used for rendering. In other words, the SubMesh material property overrides the Mesh material property for the corresponding SubGeometry.

Time for a practical example. A car Mesh can refer to a Geometry object containing a SubGeometry for the car body and one for the windows. This way, a metallic BitmapMaterial can be used for the car body, and a transparent ColorMaterial can be used for the windows, by assigning the materials to the SubMesh objects.

SceneGraph

The scene graph inheritance structure (black arrows) and the Mesh/Geometry architecture (white arrows)

The Scene Graph in Action

If you’re still a bit confused even after looking at the magnificent chart above (yes, that is the same one as in the previous post – sue me), a demo will definitely help. And what way to better illustrate a hierarchical scene graph than one illustrating a blatant lack of inspiration as well: a mini solar system! Not everything in the source is as pragmatic as it could be, but as it’s done for purely illustrative purposes, I’ll forgive myself just this once. Play around with the source, and things should start making more sense.

Demo

Source

And that’s it for now! Any questions? shoot!

Update

All sources for this post have been updated to use the dev branch of Away3D 4.0: https://github.com/away3d/away3d-core-fp11/tree/dev

Leave a comment (7 comments)

The Away3D 4.0 Scene Graph Architecture 1: Node Types

Tags: , , , , , ,

I’m planning some future blog posts dealing with tips to get better performance out of your Away3D 4.0 (FP11) projects. When working with the GPU, it’s easy to get unexpected slowdowns due to inefficient content. There are a lot of common mistakes and misconceptions that I see popping up time and time again, and I reckon it’s a good idea to try and rid the world of them. Before that, it’s important to understand one of the most central aspects of the engine: the scene graph. It forms the basis of many of the performance tips that will follow, so despite the seemingly trivial nature of the subject, it is important to explain things in proper detail.

The scene graph is essentially the 3D equivalent of the Flash display list. If you’ve worked with Away3D before, or any other 3D engine for that matter, it’s nothing new; every version of Away3D has had a scene graph, even if it was never been called that way, and it’s simply the Scene3D object and all its children: ObjectContainer3D, Mesh, Sprite3D, … It’s the building blocks you use to construct your 3D scene in a hierarchical way that feels natural. There are some differences with older versions of the engine, especially how Mesh objects are constructed, so let’s step through the different objects.

Scene graph objects

The scene graph is made up of objects (nodes) that form a tree data structure. Nodes can be added as a child to other nodes, which makes their transformation (position, rotation and scale) relative to that of their parents’.

  • Scene3D: Essentially, this is the root node of a scene graph. It wraps a root ObjectContainer3D and links it to the spatial partitioning system (more on that in a future post). Whenever a View3D is created, an empty Scene3D is created by default if one is not passed along. Since it’s a root node by design, a Scene3D object cannot be added as a child to other containers.
  • ObjectContainer3D: Comparable to Flash’s DisplayObjectContainer, it is the most basic scene graph node. It can be added as a child to other ObjectContainer3Ds (or subclasses), and in turn it can be a parent of other ObjectContainer3D (or subclass) objects. Despite the name, child nodes aren’t necessarily “contained” by the container in the most literal sense. It merely implies that the child objects’ transformation is relative to that of the parent, much like Flash Sprite objects that are a child of a DisplayObjectContainer. Move or rotate the parent, and the child will move or rotate along. For instance, a rider object can be a child of a horse object so that when the horse moves, the rider will always be in the correct place without having to explicitly update its position. It does not mean – assuming your game is within the bounds of the morally acceptable – that the rider is inside the horse. This may seem like a distinction too trivial to even point out, but it’s a misconception that has managed to confuse a good few people in the past. The results are often wastefully constructed scene graphs and superfluous transformation updates, simply because of this misinterpretation. Every scene graph object that you manipulate to generate your scene (that excludes Scene3D itself), extends ObjectContainer3D.
  • Entity: While it’s an abstract class and you should never use it directly, it’s important enough to know of its existence, especially if you’re looking into creating your own scene graph objects. It forms the base for any object that has a “physical presence”. Entities are collected in the render loop, and are dealt with differently depending on the concrete subclass.
  • Mesh: The most typical renderable 3D object, consisting out of a Geometry that defines vertices, triangles, and all that jazz. It also provides the material with which the object is rendered. Mesh objects will be the central subject of the next blog post, so I’ll keep it brief here.
  • Sprite3D: A so-called “billboard”, basically a 2-triangle plane that always faces the camera. Remember the monsters in old shooters like Doom or Duke Nukem 3D? Sprites, baby!
  • SkyBox: A special case scene graph object, used for faking the sky or environment in a scene. As it’s always supposed to be “as far away as possible”, it can’t be moved and will always center itself around the camera and size itself so it’ll fit within the camera frustum. There’s not supposed to be more than one SkyBox in the scene.
  • PointLight/DirectionalLight: These are light objects that can be used for material shading and casting shadows (DirectionalLight only). Traditionally, lights weren’t part of the scene graph in Away3D, but now they can be added in order to make lights dependent on other objects’ position (fe: the light on a miner’s hat). However, because directional lights are “infinitely far away” to approximate very distance light sources such as the sun, their position is in fact irrelevant. It’s recommended to add them to the scene directly. However, the position in combination with lookAt could be used to create sun cycles, but yeah let’s not go there now ;)
  • Camera3D: Cameras are used to render the scene to a view, and don’t necessarily need to be added to the scene graph (by default, they aren’t). However, it can be convenient if you want to make your camera’s position and orientation depend on another object (a rigid chase camera for instance), which is also a change from older versions of the engine.
SceneGraph

The scene graph inheritance structure (black arrows) and the Mesh/Geometry architecture (white arrows)

See, not that different from what you probably already know! However, the way Meshes are constructed, and how they relate to Geometry objects has changed considerably, and can be an important aspect when creating efficient content. And that’s… for the next blog post!

Leave a comment (3 comments)

© 2009 Der Schmale – David Lenaerts’s blog. All Rights Reserved.

This blog is powered by Wordpress and Magatheme by Bryan Helmig.