3
\$\begingroup\$

The gist of the situation is as follows. I have hundreds of cubes that need to be rendered. The cubes share the exact same mesh, but they will be rendered at different positions. The position of the cubes is mostly static but can change at any point.

The cube object looks as follows:

class Cube
{
    private:
        static Geometry *mesh;
        static Texture *texture;

        static string Default_Mesh;
        static string Default_Texture;

    public:
        static void make_template(string wavefront_file, string t);
        static void cleanup();

        vec3 position;

        Cube(vec3 p);
        Cube();
        ~Cube();

        void render_cube();
};

My goal is to try to render all cubes in a single call to drawElementArrays() by passing all vertices to the GPU, all Translation matrices to the GPU and then specify that the first 36 vertices use the first translation matrix, the next 36 vertices use the next translation matrix...

But also, even though they share the same geometry, they all have different textures, so I also need to specify the sampler 2D object that each vertiex uses.

Thanks in advance.

\$\endgroup\$
  • \$\begingroup\$ Minecraft doesn't draw cubes. Why do you want to? \$\endgroup\$ – Nicol Bolas Oct 22 '17 at 5:16
  • 2
    \$\begingroup\$ Because I don't care what minecraft does and I am trying to code things myself as a way to self teach myself, and making horrible design desicions, seeing why they don't work, then correcting them is part of learning? \$\endgroup\$ – Makogan Oct 22 '17 at 5:22
  • 1
    \$\begingroup\$ Looked into instancing? \$\endgroup\$ – Sidar Apr 17 '18 at 2:33
1
\$\begingroup\$

glDrawArraysInstanced() is appropriate here.

In your case, I assume all cubes have the same size and orientation? That means you only have to feed the drawcall a position per instance, instead of a full transformation matrix per instance.

A different texture per instance doesn't work though, what you would need to do is use a texture atlas, and supply UV offsets for each instance that points to the correct sub-image in the texture.

You would build a VBO with shared vertex data, which comprises:

36 vertex positions, 36 surface normals (if you do lighting), 36 UV coordinates. This can be interleaved if so desired:

X0,Y0,Z1,NX0,NY0,NZ0,U0,V0, X1,Y1,Z1,NX1,NY1,NZ1,U1,V1, ...

This defines the geometry of your cube.

Then, you append it with per-instance data. Let's say you are drawing 1024 cubes, then in your case this would be:

1024 cube positions, 1024 UV offsets.

Note that this data can be interleaved if required:

X0,Y0,Z0,UOFF0,VOFF0, X1,Y1,Z1,UOFF1,VOFF1, ....

Now all of these, both the shared stuff, and per-instance stuff, are plain old vertex attributes like a normal VBO would have, and how your shader will see them.

You need to tell OpenGL which of these vertex attributes are specific to instances, and you do this with glVertexAttribDivisor().

This is easily accomplished by setting the divisor to 1 for per-instance vertex attributes. So call this for your cubepos and UV offset attributes.

Note that in your shader you would typically still have a ModelViewProjection uniform that you apply. Instead of applying it to the vanilla vertex position, you apply it to a position that has been offset with the cube's position.

Also note: in your shader, you would transform your UV coordinate before sampling your texture, based on the per-instance UV offset value. So that you map the correct image from your atlas to each cube.

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

Seems like you want to either make use of:

  1. 3D texturing. That way you load the textures once and the same models can have different textures depending on the "depth" of the texture.

  2. Or a simpler way to is to have multiple textures saved as a new larger texture split into different sections. So for example, you have 4 512*512 textures, then you put them into a 1024*1024 the top left is for object 1, top right for object 2, etc. There'll probably be unused space if you do this, but it's a lot easier to understand and you can just change it in future if you feel the need. Then you just include that in your shader to take in a new uniform that translates the texture co-ordinate

\$\endgroup\$
  • \$\begingroup\$ Neither of these 2 options necessarily reduce drawcalls though. While they help prevent some overhead on state changes, for every individual object you get a drawcall. It's more meaningful to count something like shader swaps for example because overhead is bigger there. This is why sorting your draw calls on shaders/blendmodes/textures really helps. It minimizes overhead, Texture/shader swaps are even more punishing on something like sprite batchers which have dynamic VBOs, the whole drawcall relies on no pipeline changes so it can upload a large batch to the gpu for one drawcall every frame. \$\endgroup\$ – Sidar Apr 17 '18 at 2:30
0
\$\begingroup\$

If you have access to geometry shaders, then you can duplicate the primitives right on the GPU, and use an array of translation matrices to change their position, and assign a texture. ( but there is a limit )

Otherwise, you can define 2 vertex buffers vec3 position vec3 uvs

where uv.xy maps the image coordinates to the vertex position, and uv.z picks a layer of TEXTURE_2D_ARRAY.

Furthermore, the uv.xy portion of each cube likely won't change, so you can map it once, or use glVertexID (e.g. glVertexID % 8) to determine the uv.xy portion and map a 1D buffer that stores the texture.

You can map uniforms with names like "translation[0]", then define it as a uniform in the shader as uniform translation[size];

\$\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.