Question

I am facing a problem that many developers as have probably found a solution. I have a small project with a floor designed with small cubes (100X100). If I exceed this limit, my game suffered major slowdowns and Lagues!

Here's how I draw my floor:

//Function to draw my ground ( 100 X 100 X 1)
    public void DrawGround(GameTime gameTime)
    {
        // Copy any parent transforms.
        Matrix[] transforms = new Matrix[this.model.Bones.Count];
        this.model.CopyAbsoluteBoneTransformsTo(transforms);

        //loop 1 cube high
        for (int a = 0; a < 1; a++)
        {
            //loop 100 along cube
            for (int b = 0; b < 100; b++)
            {
                //loop 100 cubic wide
                for (int c = 0; c < 100; c++)
                {
                    // Draw the model. A model can have multiple meshes, so loop.
                    foreach (ModelMesh mesh in this.model.Meshes)
                    {
                        // This is where the mesh orientation is set, as well 
                        // as our camera and projection.
                        foreach (BasicEffect effect in mesh.Effects)
                        {
                            effect.EnableDefaultLighting();
                            effect.World = transforms[mesh.ParentBone.Index] * Matrix.CreateTranslation(this.position);
                            effect.View = this.view;
                            effect.Projection = this.projection;


                        }

                        // Draw the mesh, using the effects set above.
                        mesh.Draw();
                    }
                }
            }
        }
    }

I think it is better to use the [VertexBuffer] "memory of the graphics card," but I have not found a tutorial for that I want to do ...

Can you just give me an example to use the [VertexBuffer] in my function "DrawGround"? enter image description here Thank you very much!

Was it helpful?

Solution

tl;dr;

Use Hardware Instancing for drawing the cubes. Optionally use View Frustum Culling in order to avoid drawing invisible instances. Get started with vertex and index buffers by reading Riemer's Tutorial.


Instancing

The idea is, that the vertex and index buffers are bound to the device once and then drawn multiple times in a single draw call. An instance is a small package of data, that only contains the information that is unique for the instance of the model. This instance data is written to an additional vertexbuffer and bound to the device along with the buffers containing the actual vertices and indices.

How can you take advantage of it?

In your case you would have the following setup:

  • an IndexBuffer containing the cube mesh indices
  • a VertexBuffer containing the cube mesh vertices
  • a second VertexBuffer containing the transformation of all the cubes you want to draw
  • a special input layout that takes the instance data into account

As your cubes only differ in their position you don't even have to send the full transformation matrices to the shader. Just send the instance position and add it to the vertex position. You can also send other data like a color for each instance.

How do you implement it?

A quick google search gave me tons of results for xna instancing, so that should get you started. Here two random results that seem promising:


Frustum Culling

At any time only a fraction of the meshes in the scene may actually be visible. An object may be occluded by another object or it lies completely outside the player's field of view. The process of removing those invisible meshes before drawing them is called culling. View Frustum Culling adresses the second case:

All volumes that do not intersect with the volume of the camera are invisible.

What is the volume of the camera? An orthographic projection has a bounding volume in the form of a box. With perspective projection this box is tapered, a pyramid, that is cropped by the near and far plane; hence view frustum.

How can you take advantage of it?

Use the frustum to identify the instances invisible in the current frame. Only draw those cubes that intersect with the view frustum of your camera. Your rendering load may be reduced by up to 100%, when the player is looking into the sky. ;-)

You could combine it with a spatial hierarchy (quadtree or octree) to reduce the amount of box-frustum intersection calculations.

How do you implement it?

Xna provides a BoundingBox Structure as well as a BoundingFrustum Class. All you have to do is use them.


A tiny little drawback

Combining View Frustum Culling and Hardware Instancing can be tricky. Culling instances means you will also have to remove them from the instance buffer => which means recreating the whole thing. Maybe consider calculating cube visibility and updating the instance buffer only every few frames or when the camera is moved rapidly. How and in which extend you finally implement these techniques is up to you and depends on your requirements.


I just realized that you wanted to know how vertex buffers work:

Vertex Buffers

Before a triangle can be drawn to the screen, the graphics device must be prepared. This includes binding shaders, samplers, textures and of course binding your geometry data. When a draw call is made the graphics device runs the shaders and writes the output to the render target.

Currently you are using XNA's built-in abstractions, which simplify the process (and take all the control from you). Binding the shaders, constants, input layout, etc. is currently all hidden in your Effect class and the Mesh class takes care of the geometry side of things.

What does 'mesh.Draw()' do?

When creating the mesh two buffers are created, a VertexBuffer and a IndexBuffer. Creating this buffers is expensive, so it is a good thing that this is done only once. When you call the draw method, the buffers are bound to the device and than the draw call is made. The effect states have to be set first, as you correctly commented in your code.

But you can also create your own vertex and index buffers:

// Instantiate the VertexBuffer
var vertexBuffer = new VertexBuffer(
    device, 
    VertexPositionColorNormal.VertexDeclaration, 
    vertices.Length, 
    BufferUsage.WriteOnly
);

// Write data to the buffer
vertexBuffer.SetData(vertices);

// Instantiate the IndexBuffer
var indexBuffer = new IndexBuffer(
    device, 
    typeof(int), 
    indices.Length,      
    BufferUsage.WriteOnly
);

// Write data to the buffer
indexBuffer.SetData(indices);

... and bind them manually to the device:

device.Indices = myIndexBuffer;
device.SetVertexBuffer(myVertexBuffer);

This code was taken directly from Riemer's XNA Tutorials:

Check out the site, it really helped me a lot when I first started graphics programming.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top