-1
\$\begingroup\$

After googling for hours I've come to a few conclusions, I need to either rewrite my whole cube class, or figure out how to use hardware instancing. I can draw up to 2500 cubes with little lag, but after that my fps drops. Is there a way I can use my class for hardware instancing? Or would I be better off rewriting my class for optimization?

    public class Cube
{
public GraphicsDevice device;
public VertexBuffer cubeVertexBuffer;

public Cube(GraphicsDevice graphicsDevice)
{
    device = graphicsDevice;

    var vertices = new List<VertexPositionTexture>();

    BuildFace(vertices, new Vector3(0, 0, 0), new Vector3(0, 1, 1));
    BuildFace(vertices, new Vector3(0, 0, 1), new Vector3(1, 1, 1));
    BuildFace(vertices, new Vector3(1, 0, 1), new Vector3(1, 1, 0));
    BuildFace(vertices, new Vector3(1, 0, 0), new Vector3(0, 1, 0));

    BuildFaceHorizontal(vertices, new Vector3(0, 1, 0), new Vector3(1, 1, 1));
    BuildFaceHorizontal(vertices, new Vector3(0, 0, 1), new Vector3(1, 0, 0));

    cubeVertexBuffer = new VertexBuffer(device, VertexPositionTexture.VertexDeclaration, vertices.Count, BufferUsage.WriteOnly);

    cubeVertexBuffer.SetData<VertexPositionTexture>(vertices.ToArray());


}

private void BuildFace(List<VertexPositionTexture> vertices, Vector3 p1, Vector3 p2)
{
    vertices.Add(BuildVertex(p1.X, p1.Y, p1.Z, 1, 0));
    vertices.Add(BuildVertex(p1.X, p2.Y, p1.Z, 1, 1));
    vertices.Add(BuildVertex(p2.X, p2.Y, p2.Z, 0, 1));
    vertices.Add(BuildVertex(p2.X, p2.Y, p2.Z, 0, 1));
    vertices.Add(BuildVertex(p2.X, p1.Y, p2.Z, 0, 0));
    vertices.Add(BuildVertex(p1.X, p1.Y, p1.Z, 1, 0));
}

private void BuildFaceHorizontal(List<VertexPositionTexture> vertices, Vector3 p1, Vector3 p2)
{
    vertices.Add(BuildVertex(p1.X, p1.Y, p1.Z, 0, 1));
    vertices.Add(BuildVertex(p2.X, p1.Y, p1.Z, 1, 1));
    vertices.Add(BuildVertex(p2.X, p2.Y, p2.Z, 1, 0));
    vertices.Add(BuildVertex(p1.X, p1.Y, p1.Z, 0, 1));
    vertices.Add(BuildVertex(p2.X, p2.Y, p2.Z, 1, 0));
    vertices.Add(BuildVertex(p1.X, p1.Y, p2.Z, 0, 0));
}

private VertexPositionTexture BuildVertex(float x, float y, float z, float u, float v)
{
    return new VertexPositionTexture(new Vector3(x, y, z), new Vector2(u, v));
}

public void Draw(BasicEffect effect)
{

    foreach (EffectPass pass in effect.CurrentTechnique.Passes)
    {
        pass.Apply();
        device.SetVertexBuffer(cubeVertexBuffer);
        device.DrawPrimitives(PrimitiveType.TriangleList, 0, cubeVertexBuffer.VertexCount / 3);
    }
}
}

The following class is a list that draws the cubes.

    public class DrawableList<T> : DrawableGameComponent
{
    private BasicEffect effect;
    private ThirdPersonCam camera;
    private class Entity
    {
        public Vector3 Position { get; set; }
        public Matrix Orientation { get; set; }
        public Texture2D Texture { get; set; }
    }

    private Cube cube;
    private List<Entity> entities = new List<Entity>();

    public DrawableList(Game game, ThirdPersonCam camera, BasicEffect effect)
        : base(game)
    {
        this.effect = effect;
        cube = new Cube(game.GraphicsDevice);
        this.camera = camera;
    }

    public void Add(Vector3 position, Matrix orientation, Texture2D texture)
    {

        entities.Add(new Entity()
        {
            Position = position,
            Orientation = orientation,
            Texture = texture
        });
    }

    public override void Draw(GameTime gameTime)
    {
        foreach (var item in entities)
        {

            effect.VertexColorEnabled = false;
            effect.TextureEnabled = true;
            effect.Texture = item.Texture;

            Matrix center = Matrix.CreateTranslation(new Vector3(-0.5f, -0.5f, -0.5f));
            Matrix scale = Matrix.CreateScale(1f);
            Matrix translate = Matrix.CreateTranslation(item.Position);

            effect.World = center * scale * translate;
            effect.View = camera.view;
            effect.Projection = camera.proj;

            effect.FogEnabled = true;
            effect.FogColor = Color.CornflowerBlue.ToVector3();
            effect.FogStart = 1.0f;
            effect.FogEnd = 50.0f;

            cube.Draw(effect);
        }

        base.Draw(gameTime);
    }
}
}

There are probably many reasons that my fps is so slow, but I can't seem to figure out how to fix it. I've looked at techcraft as well, but what I have is too specific to what I want the outcome to be to just rewrite everything from scratch

\$\endgroup\$
1
  • \$\begingroup\$ Might be a micro-optimization, but you probably want to batch together cubes with like textures. I've never tried to draw that many objects, but I know that switching the texture (and I think just the general "pass.Apply" function) is relatively expensive when called numerous times. \$\endgroup\$ – Heckman Jun 24 '13 at 15:50
3
\$\begingroup\$

Are you trying to create a voxel engine (with grid-aligned cubes)?

If so: Try doing face occlusion. When you build a cube, check if it has a neighbor cube. If, say, the block to the X- position is solid, then you don't need to build the X- face.

if(!blocks[x - 1][y][z].solid) { /* build XNegative face */ }
if(!blocks[x + 1][y][z].solid) { /* build XPositive face */ }
// etc...

enter image description here

If you aren't trying to create a voxel engine, and instead want to just have a lot of objects that are cubes, then yes, you will want to look into hardware instancing. You can do so with XNA's GraphicsDevice.DrawInstancedPrimitives() method.

\$\endgroup\$
12
  • \$\begingroup\$ Ah this makes sense. I thought this was automatically done by XNA already through frustum culling or whatever its called. How would I implement this into my cube class tho? My cubes are added to my cubeDrawable list through a for method in my main class \$\endgroup\$ – Christian Frantz Jun 24 '13 at 2:55
  • \$\begingroup\$ The only automatic culling that XNA does for you is backface culling. (Frustum culling also requires you to manually check if an object is inside the camera's view frustum) To achieve face occlusion culling, you'll need to set up some method to access cubes. The easiest way to achieve this is to have an array of cubes; what many voxel engines like Techcraft or Minecraft do is they split this array into chunks to allow dynamically loading/unloading chunks. \$\endgroup\$ – untitled Jun 24 '13 at 2:59
  • \$\begingroup\$ I planned to have different drawable lists storing different areas of the map. That should be easy enough \$\endgroup\$ – Christian Frantz Jun 24 '13 at 3:07
  • \$\begingroup\$ Is it possible that my vertex and indices buffer is being drawn more than once? It should only be drawn every time a cube is interacted with. \$\endgroup\$ – Christian Frantz Jun 24 '13 at 3:33
  • 1
    \$\begingroup\$ Relying on late-stage culling will never save you as much processing as never sending the face down the pipeline in the first place. \$\endgroup\$ – bobobobo Jun 24 '13 at 4:00
1
\$\begingroup\$

The blog 0FPS has an excellent article on optimizing using greedy meshes: Meshing in a Minecraft Game

The article reviews some of the basic ideas like culling presented by previous answers, and then describes a fairly simple system to greatly reduce the meshes needed for a large number of cubes. While the middle of the article is math-heavy, there are concrete examples near the end including a JavaScript demo. An additional part 2 of the article describes other possible optimizations.

\$\endgroup\$
0
\$\begingroup\$

You should only draw the cubes that need to be drawn. I can't help you much on this since my experience lies with 2D mostly.

And you should never use you cubeclass to populate the world. If so your cube class gets iterated each gameloop probably multiple times (in update and drawing). If you just make a struct like this:

struct Cube
{
public int textureID;
public int health;
//or even
public byte health;
}

and then make a 3 dimensional array from that it will iterate through that many times faster. Within this iteration you decide what to draw and using the cubes[x,y,z] to where you are drawing it. Using bytes won't speed up the iteration but when saving millions of cubes to disk will save disk space.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.