35. OpenGL Tiling Engine (Version 2.0)
Introduction
In this tutorial, we’ll create a simple 2D tiling engine in OpenGL. Tiling is a common approach in game development, used to construct game worlds by arranging a grid of tiles with different textures. While OpenGL doesn’t provide built-in support for tiled maps, we can achieve this by rendering a grid of textured quads and binding textures dynamically based on a predefined map.
We’ll use a 10×10 map to demonstrate this technique, assigning two textures (grass and dirt) to represent different tile types.
Defining the Map
The map is represented as a 2D integer array where each number corresponds to a specific texture:
int cMap[10][10] = { {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // Row 1 {1, 0, 1, 0, 0, 0, 0, 0, 0, 1}, // Row 2 {1, 0, 1, 0, 0, 0, 0, 0, 0, 1}, // Row 3 {1, 0, 1, 0, 0, 0, 0, 0, 0, 1}, // Row 4 {1, 0, 1, 0, 0, 0, 0, 0, 0, 1}, // Row 5 {1, 0, 1, 1, 1, 1, 1, 1, 1, 1}, // Row 6 {1, 0, 0, 0, 0, 0, 0, 0, 0, 1}, // Row 7 {1, 0, 0, 0, 0, 0, 0, 0, 0, 1}, // Row 8 {1, 0, 0, 0, 0, 0, 0, 0, 0, 1}, // Row 9 {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // Row 10 };
Explanation:
– Each number represents a tile type, where `1` corresponds to dirt and `0` corresponds to grass.
– You can easily modify this array to create custom maps, adding more tile types as needed.
Loading Textures
To represent different tile types, we use two textures: one for grass and one for dirt. These are loaded into OpenGL using a texture loading function:
GLuint texture, texture2; // Texture handles texture = LoadTexture("texture.raw", 256, 256); // Load grass texture texture2 = LoadTexture("texture2.raw", 256, 256); // Load dirt texture
Explanation:
– The textures are stored as `GLuint` handles.
– Replace `”texture.raw”` and `”texture2.raw”` with your actual texture file paths.
Texture Loader Function
The `LoadTexture` function reads a `.raw` file and uploads the texture data to OpenGL:
GLuint LoadTexture(const char *filename, int width, int height) { GLuint texture; unsigned char *data; FILE *file; file = fopen(filename, "rb"); if (file == NULL) return 0; // Handle missing files // Allocate memory and read texture data data = (unsigned char *)malloc(width * height * 3); fread(data, width * height * 3, 1, file); fclose(file); // Generate and bind the texture glGenTextures(1, &texture); glBindTexture(GL_TEXTURE_2D, texture); // Set texture parameters glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Upload texture data to OpenGL glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); free(data); // Free allocated memory return texture; }
Explanation:
– `fopen` reads the raw texture file. Ensure the file exists in the specified path.
– `glTexParameterf` sets texture parameters like filtering and wrapping modes for smooth rendering.
Rendering the Tiles
To render the map, we loop through each row and column in the map array and draw a textured quad for each tile:
void drawTiles(void) { for (int i = 0; i < 10; i++) { // Loop through rows for (int j = 0; j < 10; j++) { // Loop through columns // Bind the appropriate texture if (cMap[i][j] == 0) { glBindTexture(GL_TEXTURE_2D, texture); // Grass texture } else { glBindTexture(GL_TEXTURE_2D, texture2); // Dirt texture } // Draw the quad glPushMatrix(); glTranslatef(j, -i, 0); // Position each tile glBegin(GL_QUADS); glTexCoord2d(0.0, 0.0); glVertex3f(0.0, 0.0, 0.0); // Bottom-left glTexCoord2d(1.0, 0.0); glVertex3f(1.0, 0.0, 0.0); // Bottom-right glTexCoord2d(1.0, 1.0); glVertex3f(1.0, 1.0, 0.0); // Top-right glTexCoord2d(0.0, 1.0); glVertex3f(0.0, 1.0, 0.0); // Top-left glEnd(); glPopMatrix(); } } }
Explanation:
- `glTranslatef(j, -i, 0)` positions the quad based on its row and column in the map array. Negative `i` flips the Y-axis for correct orientation.
- The texture coordinates (`glTexCoord2d`) map the texture to the quad’s corners.
Tutorial Code
Here’s the full program:
#include <GL/gl.h> #include <GL/glut.h> #include <cstdlib> #include <cstdio> GLuint texture, texture2; // Texture handles int cMap[10][10] = { ... }; // Map definition GLuint LoadTexture(const char *filename, int width, int height); // Function prototype void drawTiles(void) { ... } void display(void) { glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glEnable(GL_TEXTURE_2D); glTranslatef(-5, 4, -20); // Center the map in view drawTiles(); // Render all tiles glutSwapBuffers(); } void reshape(int w, int h) { glViewport(0, 0, (GLsizei)w, (GLsizei)h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60, (GLfloat)w / (GLfloat)h, 1.0, 100.0); glMatrixMode(GL_MODELVIEW); } int main(int argc, char **argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_DEPTH | GLUT_RGBA); glutInitWindowSize(500, 500); glutCreateWindow("OpenGL Tiling Engine"); texture = LoadTexture("texture.raw", 256, 256); texture2 = LoadTexture("texture2.raw", 256, 256); glutDisplayFunc(display); glutIdleFunc(display); glutReshapeFunc(reshape); glutMainLoop(); return 0; }
If you have any questions, feel free to email me at swiftless@gmail.com. Happy coding!
For anyone having the following problems, here’s how I solved them:
1) Not compiling
Add #include //add and move to top of include list for Visual Studio 2010.
#include “glut.h” //add glut header & search for it locally if you need too.
2) Getting white block/square
Check directory of files. Make sure they’re in the project folder.
(the same location as the TileProject.vcxproj file itself).
I hope this helps.
And great job on the tutorials Swiftless. I really enjoy them.
hi, i tried loading a standard jpg file but it’s rendered incorrectly, doesnt even resemble the image itself just random colours.
what’s the default supported image format?
Hi John,
There is no default image format as such. If you use a library to handle image loading, you can use anything. I am using a RAW file format which is just RGB values with no other information.
Cheers,
Swiftless
After looking for a solution for this poor performance I found out that if you check the currently bound textures and if it is already bound then just skipping the binding it actually gives a huge speed boost.
Yes, I mean don’t draw them.
In the loop, before you draw, you can do the check as to whether or not you need to draw.
Cheers,
Swiftless
when you mean cull them do you mean not draw them because doesn’t the whole thing just go through a loop
Hey Jjack,
GL_CULL culls back-facing polygons, so it won’t take care of removing objects out side of the field of view.
If you have a set field of view, then you could do a hack and work out the distance between tiles and the centre of the screen, and cull them if they are too far away. However the correct way is to do a bounding box collision check against the view frustrum.
Vertex Buffer Objects can be set once and then drawn whenever you want them, the advantage being that they are stored on the graphics card and not system memory, so they are so much faster than regular rendering. You can alternatively set the VBO to be update-able if you need to make changes to it at any stage.
Cheers,
Swiftless
Will the simple glEnable(GL_CULL); work or will you have to do some fancy algorithms and with vertex buffer objects do you have to update them each frame or can you just set them once and then they will not get overwritten?
Thanks
Thanks for the tutorial I have been looking all over the internet for one and this seems the most recent. Well I would like to ask why the FPS is so low I am getting about <10 frames per second when scrolling.
Hi jjack,
The number of frames per second if you use this straight out, is going to be fairly poor for a large number of tiles. I am not doing any culling to test if a tile is visible or not, which means every tile is going to be drawn, even if it isn’t in the window.
Another reason it would be so slow, is this is using OpenGL’s Immediate Mode, to speed it up even more so, you could store the tiles in Vertex Buffer Objects.
Cheers,
Swiftless
i played the original SimCity in the 90’s and until now i still play the latest version of SimCity*`:
I have just fixed it up, it appears that
tags are causing the tutorials to disappear in some instances.
Let me know if you find any more.
Cheers,
Swiftless
Hey,
can you re-upload this tutorial, please?
Thank’s a lot!
Hi Meee,
If all the tiles are white, then this sounds like it simply isn’t loading your textures.
Make sure your textures are in the correct directory and that they are loading correctly.
Cheers,
Swiftless
Why am I just getting a white block?