Animated Textures in OpenGL

The problem is that the data bus between the CPU and GPU becomes an obstacle. And for each frame to load a new texture into video memory, even in higher resolution it becomes problematic.
The first way is to use 2 intermediate pixel buffers : it is rendered into one buffer and read from the other, using DMA for this purpose, which greatly speeds up the process.
The second way is much simpler: use Spritesheet . This method was used in the first console consoles, and I will describe it.
The idea is to place images of several frames on one texture, and in order to choose which frame it is necessary to transfer only texture coordinates to the GPU. To generate Sprite Sheet, you can use this site , or download on the Internet. I downloaded the following picture:

Let's get started. To do this, you need to connect several third-party libraries
- glew - actually OpenGL itself
- glfw - library for creating OpenGL window and context
- FreeImage - a library for opening and decoding images
We load and render a square with a texture, there are many tutorials on how to do this, for example this one . For convenience, create a structure in which information about the number of rows and columns in the Sprite Sheet, as well as the current frame, will be stored.
structSpriteAnimator {unsignedint currentFreme;
unsignedint rows;
unsignedint columns;
};
To use animation, you need to calculate and change the texture coordinates for each frame. For this, the initSpriteAnimation functions are used to initialize, and spriteAnimationNextFrame is used to recalculate texture coordinates to the following frame:
SpriteAnimator initSpriteAnimation( introws, intcolumns, float * texCoord) {
SpriteAnimator animator;
animator.currentFreme = 0;
animator.rows = rows;
animator.columns = columns;
spriteAnimationUpdate(animator, texCoord);
return animator;
}
void spriteAnimationNextFrame(SpriteAnimator & animator, float * texCoord) {
const int maxFrame = animator.columns * animator.rows - 1;
if (maxFrame == animator.currentFreme) {
animator.currentFreme = 0;
}
else {
animator.currentFreme++;
}
spriteAnimationUpdate(animator, texCoord);
}
void spriteAnimationUpdate(SpriteAnimator & animator, float * texCoord) {
const int X = 0;
const int Y = 1;
const int V0 = 0;
const int V1 = 2;
const int V2 = 4;
const int V3 = 6;
const float frameWidth = 1.f / animator.columns;
const float frameHeight = 1.f / animator.rows;
const introw = animator.rows - animator.currentFreme / animator.columns;
const int col = animator.currentFreme % animator.columns;
texCoord[V0 + X] = frameWidth * col;
texCoord[V0 + Y] = frameHeight * row;
texCoord[V1 + X] = frameWidth * (col + 1);
texCoord[V1 + Y] = frameHeight * row;
texCoord[V2 + X] = frameWidth * (col + 1);
texCoord[V2 + Y] = frameHeight * (row + 1);
texCoord[V3 + X] = frameWidth * col;
texCoord[V3 + Y] = frameHeight * (row + 1);
}
And actually the rendering cycle itself:
while (!glfwWindowShouldClose(window))
{
render(shader);
Sleep(1000 / 25);
spriteAnimationNextFrame(animator, texCoord);
glfwSwapBuffers(window);
glfwPollEvents();
}
The animation has a frame rate of 25 frames per second, so a delay of 1/25 sec is used. Not the best solution, but the simplest.
The result is such an animation:

The following is a complete example:
#include<chrono>#include<thread>#include<GL\glew.h>#include<GLFW\glfw3.h>#include<FreeImage.h>#define Sleep(ms) std::this_thread::sleep_for(std::chrono::milliseconds(ms));float position[] = {-1.f, -1.f, 0,
1.f, -1.f, 0,
1.f, 1.f, 0,
-1.f, 1.f, 0};
float texCoord[] = { 0.f, 0.f,
1.f, 0.f,
1.f, 1.f,
0.f, 1.f };
GLuint indexes[] = { 0, 1,2,
2,3,0};
constchar vertexShader[] = "attribute vec4 a_position;""attribute vec2 a_texCoord;""out vec2 v_texCoord;""void main(void) {"" v_texCoord = a_texCoord;"" gl_Position = a_position;""}";
constchar fragmentShader[] = "uniform sampler2D text;""in vec2 v_texCoord;""void main (void) { "" gl_FragColor = texture(text, v_texCoord);""}";
structShader {
GLuint program;
GLuint position;
GLuint texCoord;
GLuint tex;
};
structSpriteAnimator {unsignedint currentFreme;
unsignedint rows;
unsignedint columns;
};
GLuint loadTexture(constchar * path){
int w, h;
GLuint tex;
FIBITMAP *dib(0);
FREE_IMAGE_FORMAT fif = FreeImage_GetFileType(path, 0);
if (fif == FIF_UNKNOWN)
fif = FreeImage_GetFIFFromFilename(path);
if (fif == FIF_UNKNOWN)
return-1;
if (FreeImage_FIFSupportsReading(fif)) {
dib = FreeImage_Load(fif, path);
if (!dib) return-1;
}
w = FreeImage_GetWidth(dib);
h = FreeImage_GetHeight(dib);
constchar * data = (constchar *)FreeImage_GetBits(dib);
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_BGR, GL_UNSIGNED_BYTE, data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
return tex;
}
Shader createShader(){
Shader shader;
GLint statusF, statusV;
GLuint vertexShaderId = glCreateShader(GL_VERTEX_SHADER);
GLuint fragmentShaderId = glCreateShader(GL_FRAGMENT_SHADER);
char * srcPrt;
srcPrt = (char *)vertexShader;
glShaderSource(vertexShaderId, 1, (const GLchar **)&srcPrt, NULL);
srcPrt = (char *)fragmentShader;
glShaderSource(fragmentShaderId , 1, (const GLchar **)&srcPrt, NULL);
glCompileShader(vertexShaderId);
glCompileShader(fragmentShaderId);
glGetShaderiv(vertexShaderId, GL_COMPILE_STATUS, &statusV);
glGetShaderiv(fragmentShaderId, GL_COMPILE_STATUS, &statusF);
if (statusV == GL_FALSE){ /*сообщить об ошибке компиляции вершинного шейдера*/ }
if (statusF == GL_FALSE) { /*сообщить об ошибке компиляции фрагментного шейдера*/ }
shader.program = glCreateProgram();
glAttachShader(shader.program, vertexShaderId);
glAttachShader(shader.program, fragmentShaderId);
glLinkProgram(shader.program);
glUseProgram(shader.program);
shader.position = glGetAttribLocation(shader.program, "a_position");
shader.texCoord = glGetAttribLocation(shader.program, "a_texCoord");
glEnableVertexAttribArray(shader.position);
glEnableVertexAttribArray(shader.texCoord);
shader.tex = loadTexture("FireSpriteSheet.jpg");
return shader;
}
voidrender(Shader & shader){
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glClearColor(0., 0., 0., 1.);
glUseProgram(shader.program);
glBindTexture(GL_TEXTURE_2D, shader.tex);
glVertexAttribPointer(shader.position, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), position);
glVertexAttribPointer(shader.texCoord, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), texCoord);
glDrawElements(GL_TRIANGLES, sizeof(indexes) / sizeof(indexes[0]), GL_UNSIGNED_INT, indexes);
glBindTexture(GL_TEXTURE_2D, 0);
glUseProgram(0);
return;
}
voidspriteAnimationUpdate(SpriteAnimator & animator, float * texCoord);
SpriteAnimator initSpriteAnimation( int rows, int columns, float * texCoord){
SpriteAnimator animator;
animator.currentFreme = 0;
animator.rows = rows;
animator.columns = columns;
spriteAnimationUpdate(animator, texCoord);
return animator;
}
voidspriteAnimationNextFrame(SpriteAnimator & animator, float * texCoord){
constint maxFrame = animator.columns * animator.rows - 1;
if (maxFrame == animator.currentFreme) {
animator.currentFreme = 0;
}
else {
animator.currentFreme++;
}
spriteAnimationUpdate(animator, texCoord);
}
voidspriteAnimationUpdate(SpriteAnimator & animator, float * texCoord){
constint X = 0;
constint Y = 1;
constint V0 = 0;
constint V1 = 2;
constint V2 = 4;
constint V3 = 6;
constfloat frameWidth = 1.f / animator.columns;
constfloat frameHeight = 1.f / animator.rows;
constint row = animator.rows - animator.currentFreme / animator.columns;
constint col = animator.currentFreme % animator.columns;
texCoord[V0 + X] = frameWidth * col;
texCoord[V0 + Y] = frameHeight * row;
texCoord[V1 + X] = frameWidth * (col + 1);
texCoord[V1 + Y] = frameHeight * row;
texCoord[V2 + X] = frameWidth * (col + 1);
texCoord[V2 + Y] = frameHeight * (row + 1);
texCoord[V3 + X] = frameWidth * col;
texCoord[V3 + Y] = frameHeight * (row + 1);
}
intmain(){
GLFWwindow* window;
if (!glfwInit())
return-1;
window = glfwCreateWindow(640, 480, "Simple animated texture with OpenGL", NULL, NULL);
if (!window)
{
glfwTerminate();
return-1;
}
glfwMakeContextCurrent(window);
glewInit();
Shader shader = createShader();
SpriteAnimator animator = initSpriteAnimation(6, 6, texCoord);
while (!glfwWindowShouldClose(window))
{
render(shader);
Sleep(1000 / 25);
//Sleep(1000);
spriteAnimationNextFrame(animator, texCoord);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
return0;
}
Thanks for attention!