When making an optimized 3DO, it's important to know what you are optimizing for. If your 3DO is very detailed, but there will only be a few instances (or even just one), then the optimization should be to reduce the 3DO's memory footprint. If your 3DO is very simple, but there will be many of them (such as with the caution light 3DOs), you should sacrifice memory footprint to minimize draw calls. Each mesh is one draw call.
Here's how a draw call works.
The game will first modify the GPU state such that the mesh appears correctly. This means setting the state registers to their correct values, setting the texture registers to point to the correct textures, copying material data to the GPU, and setting the correct vertex and index buffers. Finally, the game sends the draw command to the GPU and the object is drawn to the back buffer.
In NR2003, each mesh may have multiple index buffers. These are in the form of one or more triangle strip arrays (one buffer per array) and an optional triangle list array. If there are no triangle strip arrays, then there will be a single triangle list array. The difference between the two types of arrays aren't important here (though triangle strips are generally faster to draw). What is important is the fact that each of these index arrays results in its own draw call. Practically speaking, this means that (assuming NR2003 is properly optimized) for every mesh, the game has to set the state and texture registers, copy material data, and set the correct vertex buffer, then set each index buffer individually before sending the draw command for that index buffer. Make3do can be told to optimize the mesh using tri-stripping, which may or may not provide a performance benefit on modern systems (I haven't tested that, nor do I know of any tests). For simplicity however, we will consider each mesh to be just a single draw call for this post.
Anyways, a lot of the considerations for mesh optimization will actually come down to state switches. For something like the default caution lights, you will notice that there are 4, nearly identical copies of the mesh (one for each state that the caution light can be in). The only difference is in which of the lamps are lit at any given time. The state switch within the 3DO effectively gates the mesh rendering to just a single mesh at a time. So we can consider each instance of the caution light 3DO to be a single draw call. It makes sense to make the caution light 3DO in this manner, since the mesh is relatively simple and there are going to be a lot of caution lights around the track. The file is larger than it could be, with the 4 meshes making the memory footprint almost 4 times larger than what it could be, but each caution light only needs one draw call. If we were to optimize it to minimize memory footprint and file size, we would need 2 draw calls. One draw call for the part not dependent on the state of the light, and the second draw call for the part that does depend on the state of the light.
Interestingly enough, truck_tron.3do also appears to be made in this manner, even though there are only going to be a few of those at any track at most. The default Talladega track has just one. Though this can still be somewhat justified by the simplicity of the mesh, which itself is justified by the fact that it's never going to be viewed up close. But in this case, because there are so few of these at any given track, it would be perfectly reasonable to have everything except the animated part outside of the animation state switch. That would be two draw calls at most. Note that for this 3do, there's a weekend and population state switch that will hide the 3do under certain conditions, resulting in zero draw calls for that object. There's also a state switch that turns off the animations. So in this case, we could, in theory, have only one draw call for when the object isn't being animated, but two draw calls when it is. And of course, zero draw calls when the weekend or population state switches have turned it off. The population state switch is controlled by the user's graphics settings, under "Trackside Objects".
There is another point I want to touch on. And that's having multiple, identical copies of the mesh with the only real difference being the texture that's applied to them. This is a limitation of using make3do, as PSG scripts must make a copy of the mesh in order to apply a different texture to it. The 3DO file itself has no such limitations. This means that objects like truck_tron.3do has 8 copies of the mesh that has the animation. And the grandstands also have two copies of their meshes, one with crowds in them, and one without. But there is nothing in the 3DO file itself that requires us to have one copy of each mesh per texture that we want to apply to it. Remember, each mesh is one vertex buffer plus one or more index buffers. So making a 3DO file that has only one copy of a mesh where only the textures are swapped-out would be a tremendous memory footprint optimization with no downsides. All we need are the proper tools.
While much of what I said here uses track 3DOs as examples, some of this also applies equally to car 3DOs. Especially the point about not needing to make a copy of the mesh for the sole purpose of retexturing it. The main issue that one has to consider is memory footprint vs the number of draw calls against the number of instances one expects to be in the scene. Simple 3DOs (such as boxes textured to look more complex) should be optimized for the fewest draw calls. Detailed 3DOs should have a single copy of their most detailed meshes to reduce memory footprint, while the simpler meshes within detailed 3DOs should be optimized to reduce the number of draw calls. By far the most detailed meshes should be the vehicles that will be driving around the track, or objects that can be viewed up close during normal gameplay, but will only have a small handful of instances ever. NR2003 can only use a maximum of 4 GB of RAM. That might sound like a lot at first, but it will quickly be taken up by the many 3DOs and their textures that need to be loaded, and the track geometry and its textures. All of which must remain in memory and have room left over for the game to store stuff like scoring and replay information.
Edit: It should also be noted that if your model uses multiple textures, you will need to split your mesh up into multiple meshes. One mesh per texture. Where possible, you should combine multiple textures into a texture atlas to reduce the number of meshes that are generated. Make3do will make the necessary splits automatically. A couple other things to keep in mind is that faces that triangles that share a single point that have a common normal and texture coordinate can share the same vertex. But if the texture coords or normals of two triangles that share a single point are different, then you will need one vertex per triangle that does not share both a texture coord and normal with another triangle. Make3do will not always correctly generate these extra vertices, which will result in the model being incorrectly shaded in-game. So you will have to make the necessary splits in the modeling program in order for it to be shaded correctly in game.
Here's how a draw call works.
The game will first modify the GPU state such that the mesh appears correctly. This means setting the state registers to their correct values, setting the texture registers to point to the correct textures, copying material data to the GPU, and setting the correct vertex and index buffers. Finally, the game sends the draw command to the GPU and the object is drawn to the back buffer.
In NR2003, each mesh may have multiple index buffers. These are in the form of one or more triangle strip arrays (one buffer per array) and an optional triangle list array. If there are no triangle strip arrays, then there will be a single triangle list array. The difference between the two types of arrays aren't important here (though triangle strips are generally faster to draw). What is important is the fact that each of these index arrays results in its own draw call. Practically speaking, this means that (assuming NR2003 is properly optimized) for every mesh, the game has to set the state and texture registers, copy material data, and set the correct vertex buffer, then set each index buffer individually before sending the draw command for that index buffer. Make3do can be told to optimize the mesh using tri-stripping, which may or may not provide a performance benefit on modern systems (I haven't tested that, nor do I know of any tests). For simplicity however, we will consider each mesh to be just a single draw call for this post.
Anyways, a lot of the considerations for mesh optimization will actually come down to state switches. For something like the default caution lights, you will notice that there are 4, nearly identical copies of the mesh (one for each state that the caution light can be in). The only difference is in which of the lamps are lit at any given time. The state switch within the 3DO effectively gates the mesh rendering to just a single mesh at a time. So we can consider each instance of the caution light 3DO to be a single draw call. It makes sense to make the caution light 3DO in this manner, since the mesh is relatively simple and there are going to be a lot of caution lights around the track. The file is larger than it could be, with the 4 meshes making the memory footprint almost 4 times larger than what it could be, but each caution light only needs one draw call. If we were to optimize it to minimize memory footprint and file size, we would need 2 draw calls. One draw call for the part not dependent on the state of the light, and the second draw call for the part that does depend on the state of the light.
Interestingly enough, truck_tron.3do also appears to be made in this manner, even though there are only going to be a few of those at any track at most. The default Talladega track has just one. Though this can still be somewhat justified by the simplicity of the mesh, which itself is justified by the fact that it's never going to be viewed up close. But in this case, because there are so few of these at any given track, it would be perfectly reasonable to have everything except the animated part outside of the animation state switch. That would be two draw calls at most. Note that for this 3do, there's a weekend and population state switch that will hide the 3do under certain conditions, resulting in zero draw calls for that object. There's also a state switch that turns off the animations. So in this case, we could, in theory, have only one draw call for when the object isn't being animated, but two draw calls when it is. And of course, zero draw calls when the weekend or population state switches have turned it off. The population state switch is controlled by the user's graphics settings, under "Trackside Objects".
There is another point I want to touch on. And that's having multiple, identical copies of the mesh with the only real difference being the texture that's applied to them. This is a limitation of using make3do, as PSG scripts must make a copy of the mesh in order to apply a different texture to it. The 3DO file itself has no such limitations. This means that objects like truck_tron.3do has 8 copies of the mesh that has the animation. And the grandstands also have two copies of their meshes, one with crowds in them, and one without. But there is nothing in the 3DO file itself that requires us to have one copy of each mesh per texture that we want to apply to it. Remember, each mesh is one vertex buffer plus one or more index buffers. So making a 3DO file that has only one copy of a mesh where only the textures are swapped-out would be a tremendous memory footprint optimization with no downsides. All we need are the proper tools.
While much of what I said here uses track 3DOs as examples, some of this also applies equally to car 3DOs. Especially the point about not needing to make a copy of the mesh for the sole purpose of retexturing it. The main issue that one has to consider is memory footprint vs the number of draw calls against the number of instances one expects to be in the scene. Simple 3DOs (such as boxes textured to look more complex) should be optimized for the fewest draw calls. Detailed 3DOs should have a single copy of their most detailed meshes to reduce memory footprint, while the simpler meshes within detailed 3DOs should be optimized to reduce the number of draw calls. By far the most detailed meshes should be the vehicles that will be driving around the track, or objects that can be viewed up close during normal gameplay, but will only have a small handful of instances ever. NR2003 can only use a maximum of 4 GB of RAM. That might sound like a lot at first, but it will quickly be taken up by the many 3DOs and their textures that need to be loaded, and the track geometry and its textures. All of which must remain in memory and have room left over for the game to store stuff like scoring and replay information.
Edit: It should also be noted that if your model uses multiple textures, you will need to split your mesh up into multiple meshes. One mesh per texture. Where possible, you should combine multiple textures into a texture atlas to reduce the number of meshes that are generated. Make3do will make the necessary splits automatically. A couple other things to keep in mind is that faces that triangles that share a single point that have a common normal and texture coordinate can share the same vertex. But if the texture coords or normals of two triangles that share a single point are different, then you will need one vertex per triangle that does not share both a texture coord and normal with another triangle. Make3do will not always correctly generate these extra vertices, which will result in the model being incorrectly shaded in-game. So you will have to make the necessary splits in the modeling program in order for it to be shaded correctly in game.
Last edited:



