📜 ⬆️ ⬇️

Animated textures in opengl

The use of textures in OpenGL is a fairly common topic on various training sites and forums. But the code needed to make an animated texture, everything becomes not so simple. I know of 2 ways to do this and I will describe 1 of them.

The problem is that the data transfer bus between the CPU and the GPU becomes an obstacle. And for each frame to load a new texture into the video memory, it also becomes problematic in higher resolution.

The first way is to use 2 intermediate pixel buffers : in one buffer it is rendered, and read from the other, and DMA is used for this purpose, which significantly speeds up the process.

The second method 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 you need to transfer only texture coordinates to the GPU. To generate Sprite Sheet, you can use this site , or download on the Internet. I downloaded this photo:



We proceed to the implementation. To do this, you need to connect several third-party libraries.


We load and render a square with a texture, there are many tutorials on how to do this, for example this one . For convenience, we will create a structure in which information on the number of rows and columns in the Sprite Sheet will be stored, as well as the current frame.

struct SpriteAnimator { unsigned int currentFreme; unsigned int rows; unsigned int columns; }; 

To use the animation you need to calculate and change the texture coordinates for each frame. For this, the functions are used initSpriteAnimation - for initialization, and spriteAnimationNextFrame - for recalculation of texture coordinates on the following frame:

 SpriteAnimator initSpriteAnimation( int rows, int columns, 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 int row = 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 an animation like this:


Further example entirely:
 #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}; const char vertexShader[] = "attribute vec4 a_position;" "attribute vec2 a_texCoord;" "out vec2 v_texCoord;" "void main(void) {" " v_texCoord = a_texCoord;" " gl_Position = a_position;" "}"; const char fragmentShader[] = "uniform sampler2D text;" "in vec2 v_texCoord;" "void main (void) { " " gl_FragColor = texture(text, v_texCoord);" "}"; struct Shader { GLuint program; GLuint position; GLuint texCoord; GLuint tex; }; struct SpriteAnimator { unsigned int currentFreme; unsigned int rows; unsigned int columns; }; GLuint loadTexture(const char * 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); const char * data = (const char *)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; } void render(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; } void spriteAnimationUpdate(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; } 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 int row = 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); } int main() { 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(); return 0; } 


Thanks for attention!

Source: https://habr.com/ru/post/311346/


All Articles