OpenGl 动画
OpenGL 二维图形学教程系列
此系列翻译自:OpenGL Tutorial An Introduction on OpenGL with 2D Graphics
- OpenGl 环境搭建与介绍
- OpenGl 顶点,图元以及颜色
- OpenGL 裁剪区域与视口
- OpenGl 平移和旋转
- OpenGl 动画
- OpenGL 使用 GLUT 处理键盘输入
- OpenGL 使用 GLUT 处理鼠标输入
6. 动画
6.1 空闲函数
要实现动画(例如旋转图形),你可以在 GLUT 中通过 glutIdleFunc
函数来注册一个空闲回调函数 idle()
来处理空闲事件。图形系统将在没有其他事件发生时调用该空闲函数。
void glutIdleFunc(void (*func)(void))
在 idle()
函数中,你可以调用glutPostRedisplay
函数让窗口重新渲染,该函数又会调用 display()
函数。
void idle() {
glutPostRedisplay(); // Post a re-paint request to activate display()
}
注意上述代码与下面直接将 display()
注册为空闲函数的代码等价。
// main
glutIdleFunc(display);
6.2 双重缓冲
双重缓冲使用两个显示缓冲区来使动画更加流畅。要显示的下一张屏幕的内容在后缓存中存储,而当前屏幕上显示的内容则在前缓冲中存储。一旦准备过程完成,你就可以使用 glutSwapBuffer
函数来交换前后缓冲区。
要想使用双重缓冲,你需要在代码中做以下两处改变:
首先在
main()
中包含以下代码,注意需要在创建窗口之前:glutInitDisplayMode(GLUT_DOUBLE); // Set double buffered mode
在
display()
函数中,将glFlush()
替换为glutSwapBuffers()
,其作用为交换前后缓冲区。
在动画中,我们需要使用双重缓冲。对于静态的显示,单缓冲区足够了。(大多数图形硬件一直使用双重缓冲)。
6.3 例子 5:使用空闲函数的动画(GL05IdleFunc.cpp)
下述程序使用空闲函数以及双重缓冲旋转我们在之前的例子中创建的图形。
/*
* GL05IdleFunc.cpp: Translation and Rotation
* Transform primitives from their model spaces to world space (Model Transform).
*/
#include <windows.h> // for MS Windows
#include <GL/glut.h> // GLUT, include glu.h and gl.h
// Global variable
GLfloat angle = 0.0f; // Current rotational angle of the shapes
/* Initialize OpenGL Graphics */
void initGL() {
// Set "clearing" or background color
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Black and opaque
}
/* Called back when there is no other event to be handled */
void idle() {
glutPostRedisplay(); // Post a re-paint request to activate display()
}
/* Handler for window-repaint event. Call back when the window first appears and
whenever the window needs to be re-painted. */
void display() {
glClear(GL_COLOR_BUFFER_BIT); // Clear the color buffer
glMatrixMode(GL_MODELVIEW); // To operate on Model-View matrix
glLoadIdentity(); // Reset the model-view matrix
glPushMatrix(); // Save model-view matrix setting
glTranslatef(-0.5f, 0.4f, 0.0f); // Translate
glRotatef(angle, 0.0f, 0.0f, 1.0f); // rotate by angle in degrees
glBegin(GL_QUADS); // Each set of 4 vertices form a quad
glColor3f(1.0f, 0.0f, 0.0f); // Red
glVertex2f(-0.3f, -0.3f);
glVertex2f( 0.3f, -0.3f);
glVertex2f( 0.3f, 0.3f);
glVertex2f(-0.3f, 0.3f);
glEnd();
glPopMatrix(); // Restore the model-view matrix
glPushMatrix(); // Save model-view matrix setting
glTranslatef(-0.4f, -0.3f, 0.0f); // Translate
glRotatef(angle, 0.0f, 0.0f, 1.0f); // rotate by angle in degrees
glBegin(GL_QUADS);
glColor3f(0.0f, 1.0f, 0.0f); // Green
glVertex2f(-0.3f, -0.3f);
glVertex2f( 0.3f, -0.3f);
glVertex2f( 0.3f, 0.3f);
glVertex2f(-0.3f, 0.3f);
glEnd();
glPopMatrix(); // Restore the model-view matrix
glPushMatrix(); // Save model-view matrix setting
glTranslatef(-0.7f, -0.5f, 0.0f); // Translate
glRotatef(angle, 0.0f, 0.0f, 1.0f); // rotate by angle in degrees
glBegin(GL_QUADS);
glColor3f(0.2f, 0.2f, 0.2f); // Dark Gray
glVertex2f(-0.2f, -0.2f);
glColor3f(1.0f, 1.0f, 1.0f); // White
glVertex2f( 0.2f, -0.2f);
glColor3f(0.2f, 0.2f, 0.2f); // Dark Gray
glVertex2f( 0.2f, 0.2f);
glColor3f(1.0f, 1.0f, 1.0f); // White
glVertex2f(-0.2f, 0.2f);
glEnd();
glPopMatrix(); // Restore the model-view matrix
glPushMatrix(); // Save model-view matrix setting
glTranslatef(0.4f, -0.3f, 0.0f); // Translate
glRotatef(angle, 0.0f, 0.0f, 1.0f); // rotate by angle in degrees
glBegin(GL_TRIANGLES);
glColor3f(0.0f, 0.0f, 1.0f); // Blue
glVertex2f(-0.3f, -0.2f);
glVertex2f( 0.3f, -0.2f);
glVertex2f( 0.0f, 0.3f);
glEnd();
glPopMatrix(); // Restore the model-view matrix
glPushMatrix(); // Save model-view matrix setting
glTranslatef(0.6f, -0.6f, 0.0f); // Translate
glRotatef(180.0f + angle, 0.0f, 0.0f, 1.0f); // Rotate 180+angle degree
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f); // Red
glVertex2f(-0.3f, -0.2f);
glColor3f(0.0f, 1.0f, 0.0f); // Green
glVertex2f( 0.3f, -0.2f);
glColor3f(0.0f, 0.0f, 1.0f); // Blue
glVertex2f( 0.0f, 0.3f);
glEnd();
glPopMatrix(); // Restore the model-view matrix
glPushMatrix(); // Save model-view matrix setting
glTranslatef(0.5f, 0.4f, 0.0f); // Translate
glRotatef(angle, 0.0f, 0.0f, 1.0f); // rotate by angle in degrees
glBegin(GL_POLYGON);
glColor3f(1.0f, 1.0f, 0.0f); // Yellow
glVertex2f(-0.1f, -0.2f);
glVertex2f( 0.1f, -0.2f);
glVertex2f( 0.2f, 0.0f);
glVertex2f( 0.1f, 0.2f);
glVertex2f(-0.1f, 0.2f);
glVertex2f(-0.2f, 0.0f);
glEnd();
glPopMatrix(); // Restore the model-view matrix
glutSwapBuffers(); // Double buffered - swap the front and back buffers
// Change the rotational angle after each display()
angle += 0.2f;
}
/* Handler for window re-size event. Called back when the window first appears and
whenever the window is re-sized with its new width and height */
void reshape(GLsizei width, GLsizei height) { // GLsizei for non-negative integer
// Compute aspect ratio of the new window
if (height == 0) height = 1; // To prevent divide by 0
GLfloat aspect = (GLfloat)width / (GLfloat)height;
// Set the viewport to cover the new window
glViewport(0, 0, width, height);
// Set the aspect ratio of the clipping area to match the viewport
glMatrixMode(GL_PROJECTION); // To operate on the Projection matrix
glLoadIdentity();
if (width >= height) {
// aspect >= 1, set the height from -1 to 1, with larger width
gluOrtho2D(-1.0 * aspect, 1.0 * aspect, -1.0, 1.0);
} else {
// aspect < 1, set the width to -1 to 1, with larger height
gluOrtho2D(-1.0, 1.0, -1.0 / aspect, 1.0 / aspect);
}
}
/* Main function: GLUT runs as a console application starting at main() */
int main(int argc, char** argv) {
glutInit(&argc, argv); // Initialize GLUT
glutInitDisplayMode(GLUT_DOUBLE); // Enable double buffered mode
glutInitWindowSize(640, 480); // Set the window's initial width & height - non-square
glutInitWindowPosition(50, 50); // Position the window's initial top-left corner
glutCreateWindow("Animation via Idle Function"); // Create window with the given title
glutDisplayFunc(display); // Register callback handler for window re-paint event
glutReshapeFunc(reshape); // Register callback handler for window re-size event
glutIdleFunc(idle); // Register callback handler if no other event
initGL(); // Our own OpenGL initialization
glutMainLoop(); // Enter the infinite event-processing loop
return 0;
}
在上述例子中,我们使用 glPushMatrix
保存当前状态,执行变换,并通过 glPopMatrix
来重置为之前保存的状态,而非累积之前的所有平移操作并撤销旋转操作。(不过在上述例子中,我们也可以使用 glLoadIdentity
来重置矩阵)
GLfloat angle = 0.0f; // Current rotational angle of the shapes
我们定义了一个名为 angle 的全局变量记录所有形状的旋转角度。我们在之后将使用 glRotatef
函数旋转所有的图形这个角度。
angle += 0.2f;
在每次刷新的最后,我们更新这个旋转角度。
glutSwapBuffers(); // Swap front- and back framebuffer
glutInitDisplayMode(GLUT_DOUBLE); // In main(), enable double buffered mode
我们启用双重缓冲并使用 glutSwapBuffer()
来交换前后缓冲区而非使用glFlush()
刷新帧缓冲区以立即显示,这样做可以使动画更加流程。
void idle() {
glutPostRedisplay(); // Post a re-paint request to activate display()
}
glutIdleFunc(idle); // In main() - Register callback handler if no other event
我们定义了一个 idle()
函数,其请求重新绘制屏幕并调用display()
函数。我们在 main()
中通过 glutIdleFunc()
注册了 idle()
函数。
6.4 双重缓冲以及刷新率
当启用双重缓冲时,glutSwapBuffers
与屏幕的刷新间隔(VSync)进行同步。也就是说,这些缓冲将在显示器显示新的一帧时被交换。作为其结果,idle() 函数在最好情况下与以屏幕的刷新率一致的频率刷新动画。它可能以屏幕刷新率的一半,三分之一,四分之一等等的速率运行,因为它必须等待 VSync。
6.5 计时器函数
使用 idle()
函数,我们无法控制刷新间隔。我们可以通过 GLUT 中的 glutTimerFunc
函数注册一个 Timer()
函数。该 Timer()
函数将以指定的固定的时间间隔被调用。
void glutTimerFunc(unsigned int millis, void (*func)(int value), value)
// where millis is the delay in milliseconds, value will be passed to the timer function.
6.6 例子 6:基于计时器函数的动画(GL06TimerFunc.cpp)
下述程序将每 30 毫秒按逆时针旋转我们之前创建的图形两度。
/*
* GL06TimerFunc.cpp: Translation and Rotation
* Transform primitives from their model spaces to world space (Model Transform).
*/
#include <windows.h> // for MS Windows
#include <GL/glut.h> // GLUT, include glu.h and gl.h
// global variable
GLfloat angle = 0.0f; // rotational angle of the shapes
int refreshMills = 30; // refresh interval in milliseconds
/* Initialize OpenGL Graphics */
void initGL() {
// Set "clearing" or background color
glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Black and opaque
}
/* Called back when timer expired */
void Timer(int value) {
glutPostRedisplay(); // Post re-paint request to activate display()
glutTimerFunc(refreshMills, Timer, 0); // next Timer call milliseconds later
}
/* Handler for window-repaint event. Call back when the window first appears and
whenever the window needs to be re-painted. */
void display() {
glClear(GL_COLOR_BUFFER_BIT); // Clear the color buffer
glMatrixMode(GL_MODELVIEW); // To operate on Model-View matrix
glLoadIdentity(); // Reset the model-view matrix
glPushMatrix(); // Save model-view matrix setting
glTranslatef(-0.5f, 0.4f, 0.0f); // Translate
glRotatef(angle, 0.0f, 0.0f, 1.0f); // rotate by angle in degrees
glBegin(GL_QUADS); // Each set of 4 vertices form a quad
glColor3f(1.0f, 0.0f, 0.0f); // Red
glVertex2f(-0.3f, -0.3f);
glVertex2f( 0.3f, -0.3f);
glVertex2f( 0.3f, 0.3f);
glVertex2f(-0.3f, 0.3f);
glEnd();
glPopMatrix(); // Restore the model-view matrix
glPushMatrix(); // Save model-view matrix setting
glTranslatef(-0.4f, -0.3f, 0.0f); // Translate
glRotatef(angle, 0.0f, 0.0f, 1.0f); // rotate by angle in degrees
glBegin(GL_QUADS);
glColor3f(0.0f, 1.0f, 0.0f); // Green
glVertex2f(-0.3f, -0.3f);
glVertex2f( 0.3f, -0.3f);
glVertex2f( 0.3f, 0.3f);
glVertex2f(-0.3f, 0.3f);
glEnd();
glPopMatrix(); // Restore the model-view matrix
glPushMatrix(); // Save model-view matrix setting
glTranslatef(-0.7f, -0.5f, 0.0f); // Translate
glRotatef(angle, 0.0f, 0.0f, 1.0f); // rotate by angle in degrees
glBegin(GL_QUADS);
glColor3f(0.2f, 0.2f, 0.2f); // Dark Gray
glVertex2f(-0.2f, -0.2f);
glColor3f(1.0f, 1.0f, 1.0f); // White
glVertex2f( 0.2f, -0.2f);
glColor3f(0.2f, 0.2f, 0.2f); // Dark Gray
glVertex2f( 0.2f, 0.2f);
glColor3f(1.0f, 1.0f, 1.0f); // White
glVertex2f(-0.2f, 0.2f);
glEnd();
glPopMatrix(); // Restore the model-view matrix
glPushMatrix(); // Save model-view matrix setting
glTranslatef(0.4f, -0.3f, 0.0f); // Translate
glRotatef(angle, 0.0f, 0.0f, 1.0f); // rotate by angle in degrees
glBegin(GL_TRIANGLES);
glColor3f(0.0f, 0.0f, 1.0f); // Blue
glVertex2f(-0.3f, -0.2f);
glVertex2f( 0.3f, -0.2f);
glVertex2f( 0.0f, 0.3f);
glEnd();
glPopMatrix(); // Restore the model-view matrix
glPushMatrix(); // Save model-view matrix setting
glTranslatef(0.6f, -0.6f, 0.0f); // Translate
glRotatef(180.0f + angle, 0.0f, 0.0f, 1.0f); // Rotate 180+angle degree
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 0.0f, 0.0f); // Red
glVertex2f(-0.3f, -0.2f);
glColor3f(0.0f, 1.0f, 0.0f); // Green
glVertex2f( 0.3f, -0.2f);
glColor3f(0.0f, 0.0f, 1.0f); // Blue
glVertex2f( 0.0f, 0.3f);
glEnd();
glPopMatrix(); // Restore the model-view matrix
glPushMatrix(); // Save model-view matrix setting
glTranslatef(0.5f, 0.4f, 0.0f); // Translate
glRotatef(angle, 0.0f, 0.0f, 1.0f); // rotate by angle in degrees
glBegin(GL_POLYGON);
glColor3f(1.0f, 1.0f, 0.0f); // Yellow
glVertex2f(-0.1f, -0.2f);
glVertex2f( 0.1f, -0.2f);
glVertex2f( 0.2f, 0.0f);
glVertex2f( 0.1f, 0.2f);
glVertex2f(-0.1f, 0.2f);
glVertex2f(-0.2f, 0.0f);
glEnd();
glPopMatrix(); // Restore the model-view matrix
glutSwapBuffers(); // Double buffered - swap the front and back buffers
// Change the rotational angle after each display()
angle += 2.0f;
}
/* Handler for window re-size event. Called back when the window first appears and
whenever the window is re-sized with its new width and height */
void reshape(GLsizei width, GLsizei height) { // GLsizei for non-negative integer
// Compute aspect ratio of the new window
if (height == 0) height = 1; // To prevent divide by 0
GLfloat aspect = (GLfloat)width / (GLfloat)height;
// Set the viewport to cover the new window
glViewport(0, 0, width, height);
// Set the aspect ratio of the clipping area to match the viewport
glMatrixMode(GL_PROJECTION); // To operate on the Projection matrix
glLoadIdentity();
if (width >= height) {
// aspect >= 1, set the height from -1 to 1, with larger width
gluOrtho2D(-1.0 * aspect, 1.0 * aspect, -1.0, 1.0);
} else {
// aspect < 1, set the width to -1 to 1, with larger height
gluOrtho2D(-1.0, 1.0, -1.0 / aspect, 1.0 / aspect);
}
}
/* Main function: GLUT runs as a console application starting at main() */
int main(int argc, char** argv) {
glutInit(&argc, argv); // Initialize GLUT
glutInitDisplayMode(GLUT_DOUBLE); // Enable double buffered mode
glutInitWindowSize(640, 480); // Set the window's initial width & height - non-square
glutInitWindowPosition(50, 50); // Position the window's initial top-left corner
glutCreateWindow("Animation via Idle Function"); // Create window with the given title
glutDisplayFunc(display); // Register callback handler for window re-paint event
glutReshapeFunc(reshape); // Register callback handler for window re-size event
glutTimerFunc(0, Timer, 0); // First timer call immediately
initGL(); // Our own OpenGL initialization
glutMainLoop(); // Enter the infinite event-processing loop
return 0;
}
void Timer(int value) {
glutPostRedisplay(); // Post re-paint request to activate display()
glutTimerFunc(refreshMills, Timer, 0); // next Timer call milliseconds later
}
我们将 idle()
函数替换为 timer()
函数,其在计时器超时时发起重绘请求以调用 display()
函数。
glutTimerFunc(0, Timer, 0); // First timer call immediately
在 main()
函数中,我们注册 timer()
函数并将其立刻激活(通过将初始的计时间隔设置为 0)。
6.7 更多的 GLUT 函数
glutInitDisplayMode
:请求以指定模式进行显示,例如颜色模式(GLUT_RGB
, GLUT_RGBA
, GLUT_INDEX
),单/双缓冲(GLUT_SINGLE
, GLUT_DOUBLE
),启用深度(GLUT_DEPTH
),通过位操作符OR ‘|’ 将这些模式结合起来。
void glutInitDisplayMode(unsigned int displayMode)
例如:
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
// Use RGBA color, enable double buffering and enable depth buffer
6.8 例子 7:弹跳小球(GL07BouncingBall.cpp)
这个例子展示了一个小球在窗口中弹跳。注意在 OpenGL 中圆并非一个基本几何形状。该例子使用 TRIANGLE_FAN
组成一个圆。
/*
* GL07BouncingBall.cpp: A ball bouncing inside the window
*/
#include <windows.h> // for MS Windows
#include <GL/glut.h> // GLUT, includes glu.h and gl.h
#include <Math.h> // Needed for sin, cos
#define PI 3.14159265f
// Global variables
char title[] = "Bouncing Ball (2D)"; // Windowed mode's title
int windowWidth = 640; // Windowed mode's width
int windowHeight = 480; // Windowed mode's height
int windowPosX = 50; // Windowed mode's top-left corner x
int windowPosY = 50; // Windowed mode's top-left corner y
GLfloat ballRadius = 0.5f; // Radius of the bouncing ball
GLfloat ballX = 0.0f; // Ball's center (x, y) position
GLfloat ballY = 0.0f;
GLfloat ballXMax, ballXMin, ballYMax, ballYMin; // Ball's center (x, y) bounds
GLfloat xSpeed = 0.02f; // Ball's speed in x and y directions
GLfloat ySpeed = 0.007f;
int refreshMillis = 30; // Refresh period in milliseconds
// Projection clipping area
GLdouble clipAreaXLeft, clipAreaXRight, clipAreaYBottom, clipAreaYTop;
/* Initialize OpenGL Graphics */
void initGL() {
glClearColor(0.0, 0.0, 0.0, 1.0); // Set background (clear) color to black
}
/* Callback handler for window re-paint event */
void display() {
glClear(GL_COLOR_BUFFER_BIT); // Clear the color buffer
glMatrixMode(GL_MODELVIEW); // To operate on the model-view matrix
glLoadIdentity(); // Reset model-view matrix
glTranslatef(ballX, ballY, 0.0f); // Translate to (xPos, yPos)
// Use triangular segments to form a circle
glBegin(GL_TRIANGLE_FAN);
glColor3f(0.0f, 0.0f, 1.0f); // Blue
glVertex2f(0.0f, 0.0f); // Center of circle
int numSegments = 100;
GLfloat angle;
for (int i = 0; i <= numSegments; i++) { // Last vertex same as first vertex
angle = i * 2.0f * PI / numSegments; // 360 deg for all segments
glVertex2f(cos(angle) * ballRadius, sin(angle) * ballRadius);
}
glEnd();
glutSwapBuffers(); // Swap front and back buffers (of double buffered mode)
// Animation Control - compute the location for the next refresh
ballX += xSpeed;
ballY += ySpeed;
// Check if the ball exceeds the edges
if (ballX > ballXMax) {
ballX = ballXMax;
xSpeed = -xSpeed;
} else if (ballX < ballXMin) {
ballX = ballXMin;
xSpeed = -xSpeed;
}
if (ballY > ballYMax) {
ballY = ballYMax;
ySpeed = -ySpeed;
} else if (ballY < ballYMin) {
ballY = ballYMin;
ySpeed = -ySpeed;
}
}
/* Call back when the windows is re-sized */
void reshape(GLsizei width, GLsizei height) {
// Compute aspect ratio of the new window
if (height == 0) height = 1; // To prevent divide by 0
GLfloat aspect = (GLfloat)width / (GLfloat)height;
// Set the viewport to cover the new window
glViewport(0, 0, width, height);
// Set the aspect ratio of the clipping area to match the viewport
glMatrixMode(GL_PROJECTION); // To operate on the Projection matrix
glLoadIdentity(); // Reset the projection matrix
if (width >= height) {
clipAreaXLeft = -1.0 * aspect;
clipAreaXRight = 1.0 * aspect;
clipAreaYBottom = -1.0;
clipAreaYTop = 1.0;
} else {
clipAreaXLeft = -1.0;
clipAreaXRight = 1.0;
clipAreaYBottom = -1.0 / aspect;
clipAreaYTop = 1.0 / aspect;
}
gluOrtho2D(clipAreaXLeft, clipAreaXRight, clipAreaYBottom, clipAreaYTop);
ballXMin = clipAreaXLeft + ballRadius;
ballXMax = clipAreaXRight - ballRadius;
ballYMin = clipAreaYBottom + ballRadius;
ballYMax = clipAreaYTop - ballRadius;
}
/* Called back when the timer expired */
void Timer(int value) {
glutPostRedisplay(); // Post a paint request to activate display()
glutTimerFunc(refreshMillis, Timer, 0); // subsequent timer call at milliseconds
}
/* Main function: GLUT runs as a console application starting at main() */
int main(int argc, char** argv) {
glutInit(&argc, argv); // Initialize GLUT
glutInitDisplayMode(GLUT_DOUBLE); // Enable double buffered mode
glutInitWindowSize(windowWidth, windowHeight); // Initial window width and height
glutInitWindowPosition(windowPosX, windowPosY); // Initial window top-left corner (x, y)
glutCreateWindow(title); // Create window with given title
glutDisplayFunc(display); // Register callback handler for window re-paint
glutReshapeFunc(reshape); // Register callback handler for window re-shape
glutTimerFunc(0, Timer, 0); // First timer call immediately
initGL(); // Our own OpenGL initialization
glutMainLoop(); // Enter event-processing loop
return 0;
}
TODO:详细的代码解释
Links: OpenGl-动画