Rendering Blocky Terrain Tiles
1. Each tile is a cube in 3-space.
2. Theoretically infinite expansion.
3. Each Tile having an independent texture. This is the tricky one for me.
4. Each texture covers all sides of a block. Eventually, I may need full skins like MC uses, but I'm not concerned with that at the moment.
I've got #2 covered with noise functions. #1 is trivial in isolation, but I encounter problems coupling it with 3.
My original idea/implementation was texture atlasing. However, atlasing a uniform terrain like this requires a lot of vertex duplication (36 vertices for every cube, because no vertices can be shared between anything beyond the quad level for atlasing to work), and batch limits mean I need an enormous number of draw calls for even a small sample. The other alternative I thought of was to create a new VBO for every texture, and make the textures separate. The problem with this is that it requires at least as many draw calls as there are tile textures, even on small geometry samples. This doesn't scale terribly well.
How do people actually do it? It doesn't seem like a terribly complicated problem, and everyone else seems to manage just fine.
Both of your naive solution will work very well.
I've started using your second solution: 1dynamic vb per texture.
- Then i've subdivided my terrain in chunk.
- Then i've added atlasing.
- Then i've added what you call "skinnin" at a later stage.
My best advice, do it then optimize! Dont optimize what you dont need to.
The only true thing about software design is: "The best solution is always the simplest solution".
In other words, the most naive solution is ALWAYS the best one.
When that first naive solution dont fit your needs/problems anymore; move forward.
If you're just using unit cubes anyway, you can limit yourself to per-face information and with a texture atlas get away with a few bytes per visible face. After all it just takes a few bits to encode the face, one byte to index the texture atlas and 3 bytes for the x,y,z position. So even if you blow this up to 4x32bit to make your hardware happy, you can save a lot of memory in return for a little work in the geometry shader.
My version basically passes two ivec4. One with position and texture info, one with lighting info. x and z are stored in position.x, y is stored in position.y (z and w are basically wasted). each coordinate in the light info has the sun and block light info for one face vertex.
So you have uniform info that never changes (face vertices for a unit cube), some that changes per frame (daylight), some that changes per chunk (chunk coordinate offset), and the actual geometry for each visible face.
Major downside if you aim at a MC clone: MC doesn't exclusively use unit cubes. Though there are enough of them to consider rendering those and the "special" blocks separately, especially if you just want to experiment and see how far you can push it.
- Then i've added atlasing.
What's the point of atlasing if you tie each VBO to a unique texture?
My best advice, do it then optimize! Dont optimize what you dont need to.
The only true thing about software design is: "The best solution is always the simplest solution".
In other words, the most naive solution is ALWAYS the best one.
When that first naive solution dont fit your needs/problems anymore; move forward.
The problem is that the first solution isn't good enough now. I haven't yet coded the second one, but it's a non-trivial task if I'm not even sure whether it will be sufficient.
If you're just using unit cubes anyway, you can limit yourself to per-face information and with a texture atlas get away with a few bytes per visible face. After all it just takes a few bits to encode the face, one byte to index the texture atlas and 3 bytes for the x,y,z position. So even if you blow this up to 4x32bit to make your hardware happy, you can save a lot of memory in return for a little work in the geometry shader.
My version basically passes two ivec4. One with position and texture info, one with lighting info. x and z are stored in position.x, y is stored in position.y (z and w are basically wasted). each coordinate in the light info has the sun and block light info for one face vertex.
So you have uniform info that never changes (face vertices for a unit cube), some that changes per frame (daylight), some that changes per chunk (chunk coordinate offset), and the actual geometry for each visible face.
Major downside if you aim at a MC clone: MC doesn't exclusively use unit cubes. Though there are enough of them to consider rendering those and the "special" blocks separately, especially if you just want to experiment and see how far you can push it.
This sounds interesting, but I really have no clue what you're describing. I'm not sure if my tiles are all going to be unit or not, but I do know that whatever dimensions they are, they will be uniform, so I won't have half-tiles or anything like that. I'm not really making a MC clone, it's just the easiest way to describe the kind of terrain I'm trying to make. My terrain won't even have overhangs or be destructible.
Vector4 faceCoords[6][4] =
{
{ Vector4(0,1,0,0), Vector4(0,0,0,0), Vector4(0,1,1,0), Vector4(0,0,1,0), } //Left
{ Vector4(1,1,1,0), Vector4(1,0,1,0), Vector4(1,1,0,0), Vector4(1,0,0,0), } //Right
{ Vector4(0,1,1,0), Vector4(0,0,1,0), Vector4(1,1,1,0), Vector4(1,0,1,0), } //Front
{ Vector4(1,1,0,0), Vector4(1,0,0,0), Vector4(0,1,0,0), Vector4(0,0,0,0), } //Back
{ Vector4(0,1,0,0), Vector4(0,1,1,0), Vector4(1,1,0,0), Vector4(1,1,1,0), } //Top
{ Vector4(1,0,0,0), Vector4(1,0,1,0), Vector4(0,0,0,0), Vector4(0,0,1,0), } //Bottom
};
void EmitFaceVertex(vec4 pos, vec3 uvt)
{
gl_Position = mvp * pos;
texCoord = uvt;
EmitVertex();
}
void main()
{
ivec4 pos = position[0];
int textureID = pos.w;
int face = pos.z;
pos = ivec4(chunkPos, 0) + ivec4(pos.x >> 4, pos.y, pos.x & 0xF, 1);
EmitFaceVertex( pos + faceCoords[face][0], vec3(0, 0, textureID) );
EmitFaceVertex( pos + faceCoords[face][1], vec3(0, 1, textureID) );
EmitFaceVertex( pos + faceCoords[face][2], vec3(1, 0, textureID) );
EmitFaceVertex( pos + faceCoords[face][3], vec3(1, 1, textureID) );
EndPrimitive();
}
for (i = 0; i < 6; ++i)
{
if (pos.z & (1<<i))
{
EmitFaceVertex( pos + faceCoords[i][0], vec3(0, 0, textureID) );
EmitFaceVertex( pos + faceCoords[i][1], vec3(0, 1, textureID) );
EmitFaceVertex( pos + faceCoords[i][2], vec3(1, 0, textureID) );
EmitFaceVertex( pos + faceCoords[i][3], vec3(1, 1, textureID) );
EndPrimitive();
}
}