[Из песочницы] Анимированные текстуры в OpenGL
Проблема заключается в том, что преградой становится шина передачи данных между CPU и GPU. И на каждый фрейм загружать новую текстуру в видеопамять, еще и в большем разрешении становится проблематичным.
Первый способ это использовать 2 промежуточных пиксельных буфера: в один буфер рендерится, а из другого считывается, при чем для этих целей используется DMA что значительно ускоряет процесс.
Второй способ гораздо проще: использовать Spritesheet. Такой способ применялся еще в первых консольных приставках, его я и опишу.
Идея заключается в том что бы на одной текстуре разместить изображения нескольких фреймов, и для того что бы выбрать какой фрейм необходимо передать на GPU только текстурные координаты. Для генерации Sprite Sheet можно воспользоваться этим сайтом, или скачать в интернете. Я скачал вот такую фот картинку:
Приступим к реализации. Для этого необходимо подключить несколько сторонних библиотек
- glew — собственно говоря сам OpenGL
- glfw — библиотека для создания окна и контекста OpenGL
- FreeImage — библиотека для открытия и декодирования изображений
Загружаем и рендерим квадрат с текстурой, есть много туториалов как это делать, например этот. Для удобства создадим структуру в которой будет храниться информация о количестве рядов и столбцов в Sprite Sheet, а так же текущий фрейм.
struct SpriteAnimator {
unsigned int currentFreme;
unsigned int rows;
unsigned int columns;
};
Для использования анимации необходимо рассчитывать и менять текстурные координаты на каждый фрейм. Для это используются функции initSpriteAnimation — для инициализации, и spriteAnimationNextFrame — для пересчета текстурных координат на следующий фрейм:
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);
}
И собственно говоря сама цикл рендеринга:
while (!glfwWindowShouldClose(window))
{
render(shader);
Sleep(1000 / 25);
spriteAnimationNextFrame(animator, texCoord);
glfwSwapBuffers(window);
glfwPollEvents();
}
Анимация имеет frame rate 25 кадров в секунду, поэтому используется задержка в 1/25 сек. Не лучшее решение, но зато самое простое.
В результате получилась вот такая вот анимация:
#include
#include
#include
#include
#include
#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;
}
Спасибо за внимание!
Комментарии (1)
29 сентября 2016 в 17:14
0↑
↓
>Такой способ применялся еще в первых консольных приставкахНесколько раньше. Спрайтовая анимация существует столько же, сколько вся компьютерная графика. Она намного старше, чем OpenGL.
Слово «спрайт» было придумано в 1970-е годы одним сотрудником компании Texas Instruments: их новая микросхема TMS9918 могла аппаратно отображать небольшие картинки поверх неподвижного фона
За статью спасибо. Отличное справочное пособие по быстрому началу «плоского» OpenGL-приложения.