reuse 3d primitives

Started by
27 comments, last by VladR 10 years, 11 months ago

Is there any way to make a primitive and use it over and over again? ex: if I make one cube, can I create 100 and make a 10x10 grid? I've tried using a for loop and updating the x and z coords with each loop thru, but it only moves the one cube thats created in the beginning


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Cube_Chaser
{
    class Cube
    {
        private GraphicsDevice device;
        private Texture2D texture;

        public Vector3 location;

        private Vector3 position;

        private VertexBuffer cubeVertexBuffer;
        private List<VertexPositionTexture> vertices = new List<VertexPositionTexture>();

        public Cube(GraphicsDevice graphicsDevice, Vector3 playerLocation, float minDistance, Texture2D texture)
        {
            device = graphicsDevice;
            this.texture = texture;

            PositionCube(playerLocation, minDistance);

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

            BuildFaceHorizontal(new Vector3(0, 1, 0), new Vector3(1, 1, 1));
            BuildFaceHorizontal(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());

            this.position = position;
        }

        private void BuildFace(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(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 PositionCube(Vector3 playerLocation, float minDistance)
        {
                location = new Vector3(.5f, .5f, .5f);
        }

        public void Draw(Camera camera, BasicEffect effect)
        {
            effect.VertexColorEnabled = false;
            effect.TextureEnabled = true;
            effect.Texture = texture;

            Matrix center = Matrix.CreateTranslation(new Vector3(-0.5f, -0.5f, -0.5f));
            Matrix scale = Matrix.CreateScale(0.05f);
            Matrix translate = Matrix.CreateTranslation(location);

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

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

If you see a post from me, you can safely assume its C# and XNA :)

Advertisement

Sure, you can create one cube and reuse it.
there are a few options:
- create one cube geometry set (VB + IB (optional) ), and render it n times, setting different world transform matrices as constants for each draw call.
- create one cube geometry set and instance it n times with one draw call ( for example, drawIndexedInstanced in d3d11 ). You can then use SV_InstanceID to index into a cb containing an array of transforms, or just have some other simple shader code to offset each vertex based on instance id.

- You can also use GS to do the instancing, but that will almost certainly be alot slower, especially for large amount of instances. ( en example of this though could be have no vertex geometry, call draw with the number of verts set to the number of cubes you want on screen, and then expand each point into a cube in GS )

Sure, you can create one cube and reuse it.
there are a few options:
- create one cube geometry set (VB + IB (optional) ), and render it n times, setting different world transform matrices as constants for each draw call.
- create one cube geometry set and instance it n times with one draw call ( for example, drawIndexedInstanced in d3d11 ). You can then use SV_InstanceID to index into a cb containing an array of transforms, or just have some other simple shader code to offset each vertex based on instance id.

- You can also use GS to do the instancing, but that will almost certainly be alot slower, especially for large amount of instances. ( en example of this though could be have no vertex geometry, call draw with the number of verts set to the number of cubes you want on screen, and then expand each point into a cube in GS )

Second and last options are out seeing this is XNA so no DX11 support, you can still do instancing but you have to look up DX9 instancing instead. See this link for hardware instancing in XNA 4. Depending on how many cubes you are going to draw instancing is your best bet for realizing a decent FPS. DX doesn't like small vertex buffers and slows down dramatically when it is faced with loads of them.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

Sure, you can create one cube and reuse it.
there are a few options:
- create one cube geometry set (VB + IB (optional) ), and render it n times, setting different world transform matrices as constants for each draw call.
- create one cube geometry set and instance it n times with one draw call ( for example, drawIndexedInstanced in d3d11 ). You can then use SV_InstanceID to index into a cb containing an array of transforms, or just have some other simple shader code to offset each vertex based on instance id.

- You can also use GS to do the instancing, but that will almost certainly be alot slower, especially for large amount of instances. ( en example of this though could be have no vertex geometry, call draw with the number of verts set to the number of cubes you want on screen, and then expand each point into a cube in GS )

Second and last options are out seeing this is XNA so no DX11 support, you can still do instancing but you have to look up DX9 instancing instead. See this link for hardware instancing in XNA 4. Depending on how many cubes you are going to draw instancing is your best bet for realizing a decent FPS. DX doesn't like small vertex buffers and slows down dramatically when it is faced with loads of them.

Heh, I guess I should have checked that it was xna.

If you are just going to be rendering cubes (rather than looking for a unified solution for instancing / drawing loads of arbitrary meshes ), I believe it would be faster to just have one large VB with loads of cubes inside, storing the cube index in the W component of the position for example (if SV_VertexID or equivalent is not available), and then controlling how many you render by specifying different primitive count in the draw call. You can then use the cube index to index to resolve you final vertex positions as discussed above.

i'm able to do about 11,000 batches of 8 triangles at 30 fps dx9 fixed function.

if your cubes don't change every frame, create a static (NOT dynamic) vb and ib and fill it with cubes, then draw that. i use this trick for ground meshes drawn one quad at a time depending on texture. then on top of that, i draw 11,0000 plants, rocks, and alpha blended clouds (thats 11,000 visible on the screen, after all culling). and thats all done with just 4 plant meshes, 2 rock meshes, and one billboard quad - drawn over, and over again. the trick is to draw them ordered on texture. that way you set each texture in the scene just once.

note that indexed will (usually/almost always?) be faster than non-indexed.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

I read up on DrawUserIndexedPrimitives and I think its what I want because Im going to be drawing a lot of cubes eventually. But I have no idea how to use it within my cube class. I may just rewrite the class entirely lol. Also, the buffer will only change every time the player interacts with it so hopefully this will improve performance

If you see a post from me, you can safely assume its C# and XNA :)

DrawIndexedPrimitives (which uses a vertex buffer) will be significantly faster than DrawUserIndexedPrimitives (which sends all your vertex data to the GPU every time) if your cubes only change when the player interacts with them.

Also, the buffer will only change every time the player interacts with it so hopefully this will improve performance

sounds like a cube world. you'll want some sort of world map to tell you what cube is what and where. you use that to generate the static vb and ib. once at game start, and once every time the player changes the world. basically, you have a cube vb and ib in memory (your source mesh). then you have a big system memory vb and ib (your system mesh) that will hold lots of cubes. i use my own data structures for these. then you will have a big static directx or openGL vb and ib that you've allocated in default memory pool. you copy the source mesh into the system mesh multiple times, applying transforms as you go. then you lock the directx/openGL buffer and memcpy the whole system vb and IB to the directx/ openGL vb and ib. then draw to your heart's content til its time to regenerate.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

The world will be drawn from a text file:

"1, 1, 1, 1, 1, 1"

"1, 1, 1, 1, 1, 1"

"1, 1, 1, 1, 1, 1"

that would be three rows of six cubes one cube high

new classes


class Cube
{
    private GraphicsDevice device;
    private 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);
        }
    }
}

cube class


public class DrawableList<T> : DrawableGameComponent
{
    private BasicEffect effect;
    private Camera 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, Camera 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 )
    {
        base.Draw (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(0.05f);
            Matrix translate = Matrix.CreateTranslation(item.Position);

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

            cube.Draw (effect);
        }
    }
}

camera = new Camera (graphics.GraphicsDevice);
        effect = new BasicEffect (graphics.GraphicsDevice);
        cubes = new DrawableList<Cube> (this, camera, effect);

        Components.Add (cubes);

        for (int i=0 ; i < 50; i++)
        {
            cubes.Add (new Vector3( i*0.5f, 50.0f, 50.0f), Matrix.Identity, logoTexture);
        }

If you see a post from me, you can safely assume its C# and XNA :)

That works but in those cases instancing is a far better solution as you already know how many cubes you are going to render, and you will only issue one drawcall, which means effect setup is only run once as well. And instead of setting the World param on each shader effect you pass this into a VB, removing an object then becomes axing the entity and not building that matrix into the instance data buffer. You can use multiple textures and an index passed through the instance data if you need to modify the texture on particular objects that are drawn through instancing.

Another minor optimisation in your code would be to calculate the center and scale combined matrix only once and storing that as a static variable of the DrawableList class if you are not intending to change it.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

This topic is closed to new replies.

Advertisement