Reputation: 505
I'm not really not knowledgeable with directX but I will do my best to explain. To start I have multiple Model Objects (custom object with vertex data). At the moment my code (see below) can render a single model in a scene using a vertexBuffer and indexBuffer for that model. What I would like to do is given an array of models. Render them all in a single scene.This is my current code:
private void Render()
{
device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.White, 1.0f, 0);
device.BeginScene();
float x = (float)Math.Cos(0);
float z = (float)Math.Sin(0);
device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI / 4, this.Width / this.Height, 1f, 50f);
device.Transform.View = Matrix.LookAtLH(new Vector3(x, 6, z), new Vector3(0, 0, 0), new Vector3(0, 1, 0));
device.RenderState.Lighting = true;
device.Lights[0].Type = LightType.Directional;
device.Lights[0].Diffuse = Color.White;
device.Lights[0].Direction = new Vector3(-x, -6, -z);
device.Lights[0].Position = new Vector3(x, 6, z);
device.Lights[0].Enabled = true;
device.Transform.World = model.transform;
device.VertexFormat = CustomVertex.PositionNormalColored.Format;
device.SetStreamSource(0, vertexBuffer, 0);
device.Indices = indexBuffer;
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, model.getVertices().Length, 0, model.getIndices().Length / 3);
device.EndScene();
device.Present();
}
public void RenderModel(Model model)
{
this.model = model;
vertexBuffer = new VertexBuffer(typeof(CustomVertex.PositionNormalColored), model.getVertices().Length,
device, Usage.Dynamic | Usage.WriteOnly, CustomVertex.PositionNormalColored.Format, Pool.Default);
vertexBuffer.SetData(model.getVertices(), 0, LockFlags.None);
indexBuffer = new IndexBuffer(typeof(ushort), model.getIndices().Length, device, Usage.WriteOnly, Pool.Default);
indexBuffer.SetData(model.getIndices(), 0, LockFlags.None);
Render();
}
Upvotes: 2
Views: 5245
Reputation: 3745
tl;dr;
You can simply call RenderModel
in a loop. Though, for better performance you would want to sort your operations by update frequency, always dispose unneeded resources and don't keep multiple copies of the same data.
Well, a naive approach for rendering multiple objects with your setup would look like this:
foreach (var model in models)
{
RenderModel(model);
}
I say naive, because there are quite a lot of things I would fix first.
Real-time 3D applications are all about good performance. In order to achieve it, you have to really be careful about how often you do expensive tasks. For example, at the moment you are creating a new vertex and index buffer everytime the RenderModel
method is called. (And I suppose this would happen each frame?) Also, the effect states (like view/projection matrices and lighting) would be set for every model individually, eventhough they probably stay they same for the whole frame.
Identify expensive operations (setting/creating shaders, constants, buffers; roughly anything that uses the GraphicsDevice) and think about how often they need to change. For optimal performance, don't do it more often than that. Examples:
Vertex buffer for a mesh: If the mesh is not dynamic and the vertices won't change for some time (or not at all), the vertex buffer will always contain the same data. It is totally sufficient to create it when you load the mesh and then reuse it over and over again. (Also applies to index buffers.) (Update frequency: Once per level)
Setting lighting, view and projection matrices: Lighting usually stays the same during one frame. So do the camera matrices. Set them once at the beginning of the frame and you are done. (Update frequency: Once per frame)
Binding textures: Usually you would have different materials in the scene, associated with individual models. Most of the time a material belongs to multiple objects (because you combined their textures in one large texture map). So binding these textures and constants once for all meshes which use it would save the most GPU capacity. (Update frequency: Once per material)
Binding vertex and index buffers: Vertex and index data is usually unique for a mesh. So if you have more than one mesh in your scene, switching buffers on the device as soon you are done with one mesh is inevitable. (Update frequency: Once per mesh)
Setting the world matrix: Your effect states are set and the mesh data is bound to the device. You can finally draw your object. So you set the world matrix and call the draw method of the device. But wait, you want another copy of the mesh somewhere else in the scene? Now is the best time for it. Simply bind the other world matrix and draw again. You just drew two objects with a minimum change of state on the device. Perfect!1 (Update frequency: Once per mesh instance)
1 If you really need a lot of copies of the same mesh in the scene, I suggest taking a look into Hardware instancing, available starting from Direc3D 9.
Usually the Garbage Collector takes care of your old objects and it does a really good job at that. But here you are working with unamanged resources. They are called unmanaged because they are not managed by the CLR and therefore not affected by garbage collection.
Keeping such objects around will probably lead to a memory leak. Your memory will be cluttered with unneeded resources, resulting in a performance loss.2
.NET provides an interface for classes that use unmanaged resources, called the IDisposable interface. It exposes a Disposable
method from which the unmanaged resources should be released. All you have to do is call the method, as soon as you are done using the object.
if (myTexture != null)
myTexture.Dispose();
For example in your case you create a new vertex and index buffer every frame. You should definitely dispose them, before you overwrite them with new buffers and the old ones disappear into nirvana. (Not to mention, that you shouldn't create them so often anyways.) Just take a look at some of the Direct3D classes, and you will see that most of them implement IDisposable.
2From what I understand, it is actually not that bad. Implementing IDisposable correctly usually means that the classes have a Destructor, which calls Dispose
anyways as soon as the object is garbage collected. Still, since you cannot know when garbage collection happens, it is the general recommondation to dispose Disposable objects manually anyways.
At the moment you are probably storing your vertices and indices in arrays. When you create the buffer, you are basically creating a copy of the data. As I mentioned before: The vertex and index buffer can be created once per mesh. So when you do that, write the vertices and indices to the buffers and then keep only those. Your model class would of course change slightly:
public class Model
{
public VertexBuffer Vertices { get; private set; }
public IndexBuffer Indices { get; private set; }
}
If you don't need access to the vertices and indices directly, this would be a better solution.
I know, this is a minor step, but I think it's good to be aware of it.
public void RenderFrame(Model[] models)
{
// Per frame
Bind(View);
Bind(Projection);
BindLighting();
// Per effect
BindEffect();
foreach (var material in GetMaterials(models))
{
// Per material
Bind(material.Color);
Bind(material.DiffuseMap);
foreach (var model in GetModelsByMaterial(material, models))
{
// Per mesh
Bind(model.VertexBuffer);
Bind(model.IndexBuffer);
foreach (var instance in model.Instances)
{
// Per instance
Bind(instance.World);
// Draw the instance
Draw();
}
}
}
}
Disclaimer: I have been working with Direct3D 9 (XNA) only for some months. Most of the things I wrote should apply to both, D3D9 and Direct3D 10/11, which I am by far more familiar with.
Upvotes: 9