c++ 判断硬件是否支持opengl_【译】OpenGL 教程:二维图形绘制

为了更好的排版,请到以下博客查看此教程

OpenGl 环境搭建与介绍​iamazing.cn

1. 搭建 OpenGL 环境

取决于你的编程平台,有以下教程:

  • 基于 C++(也可以看译者自己写的教程)
  • 基于 Java:JOGL 或者 LWJGL
  • 安卓平台

1.1 例子 1:设置 OpenGL 与 GLUT(GL01Hello.cpp)

确保你能够运行以下程序:

/*
 * GL01Hello.cpp: Test OpenGL/GLUT C/C++ Setup
 * Tested under Eclipse CDT with MinGW/Cygwin and CodeBlocks with MinGW
 * To compile with -lfreeglut -lglu32 -lopengl32
 */
#include <windows.h>  // for MS Windows
#include <GL/glut.h>  // GLUT, include glu.h and gl.h

/* Handler for window-repaint event. Call back when the window first appears and
   whenever the window needs to be re-painted. */
void display() {
   glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Set background color to black and opaque
   glClear(GL_COLOR_BUFFER_BIT);         // Clear the color buffer (background)

   // Draw a Red 1x1 Square centered at origin
   glBegin(GL_QUADS);              // Each set of 4 vertices form a quad
      glColor3f(1.0f, 0.0f, 0.0f); // Red
      glVertex2f(-0.5f, -0.5f);    // x, y
      glVertex2f( 0.5f, -0.5f);
      glVertex2f( 0.5f,  0.5f);
      glVertex2f(-0.5f,  0.5f);
   glEnd();

   glFlush();  // Render now
}

/* Main function: GLUT runs as a console application starting at main()  */
int main(int argc, char** argv) {
   glutInit(&argc, argv);                 // Initialize GLUT
   glutCreateWindow("OpenGL Setup Test"); // Create a window with the given title
   glutInitWindowSize(320, 320);   // Set the window's initial width & height
   glutInitWindowPosition(50, 50); // Position the window's initial top-left corner
   glutDisplayFunc(display); // Register display callback handler for window re-paint
   glutMainLoop();           // Enter the event-processing loop
   return 0;
}

#include <windows.h>

注意头文件 “windows.h” 仅在 Windows 平台需要。

#include <GL/glut.h>

我们还需要包含 GLUT 头文件,其已经包含了 “glu.h” 和 “gl.h”。

该程序的剩余部分会在相应的教程中解释。

2. 介绍

OpenGL(开放图形库,Open Graphics Library)是一个跨平台的,具备硬件加速的,语言无关的用于构建 3D(包含2D)图形的工业标准 API。现代计算机大多具备专门的带有独立内存的图像处理单元(GPU)用以加速图形渲染。OpenGL 就是这些图像处理硬件的软件接口。

在我们的 OpenGL 程序中使用了以下三组软件库:

  1. OpenGL 核心库(GL):包含数以百计的函数,以 “gl”开头(例如:glColorglVertexglTranslateglRotate)。OpenGL 核心库通过一组几何图元(例如点,线,多边形)来进行建模。
  2. OpenGL 实用程序库(GLU):基于 OpenGL 核心构建,提供一些重要的实用程序(例如:设置摄像机以及投影),以 “glu”开头(例如:gluLookAtgluPerspective)。
  3. OpenGL 实用工具包(GLUT):OpenGL 被设计为独立于操作系统。因此我们需要 GLUT 来与操作系统进行交互(例如:创建窗口,处理键盘和鼠标输入),其提供的函数以 “glut” 开头(例如:glutCreatewindowglutMouseFunc)。GLUT 是平台无关的,其基于平台相关的 OpenGL 扩展构建,例如对于 X Window 是 GLX,对于 Windows 系统是 WGL,对于 Mac OS 则是 AGL,CGL 或者 Cocoa。

3. 顶点,图元以及颜色

3.1 例子 2:顶点,图元以及颜色(GL02Primitive.cpp)

尝试编译运行以下 OpenGL / C++ 程序:

/*
 * GL02Primitive.cpp: Vertex, Primitive and Color
 * Draw Simple 2D colored Shapes: quad, triangle and polygon.
 */
#include <windows.h>  // for MS Windows
#include <GL/glut.h>  // GLUT, include glu.h and gl.h

/* Initialize OpenGL Graphics */
void initGL() {
   // Set "clearing" or background color
   glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Black and opaque
}

/* 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 with current clearing color

   // Define shapes enclosed within a pair of glBegin and glEnd
   glBegin(GL_QUADS);              // Each set of 4 vertices form a quad
      glColor3f(1.0f, 0.0f, 0.0f); // Red
      glVertex2f(-0.8f, 0.1f);     // Define vertices in counter-clockwise (CCW) order
      glVertex2f(-0.2f, 0.1f);     //  so that the normal (front-face) is facing you
      glVertex2f(-0.2f, 0.7f);
      glVertex2f(-0.8f, 0.7f);

      glColor3f(0.0f, 1.0f, 0.0f); // Green
      glVertex2f(-0.7f, -0.6f);
      glVertex2f(-0.1f, -0.6f);
      glVertex2f(-0.1f,  0.0f);
      glVertex2f(-0.7f,  0.0f);

      glColor3f(0.2f, 0.2f, 0.2f); // Dark Gray
      glVertex2f(-0.9f, -0.7f);
      glColor3f(1.0f, 1.0f, 1.0f); // White
      glVertex2f(-0.5f, -0.7f);
      glColor3f(0.2f, 0.2f, 0.2f); // Dark Gray
      glVertex2f(-0.5f, -0.3f);
      glColor3f(1.0f, 1.0f, 1.0f); // White
      glVertex2f(-0.9f, -0.3f);
   glEnd();

   glBegin(GL_TRIANGLES);          // Each set of 3 vertices form a triangle
      glColor3f(0.0f, 0.0f, 1.0f); // Blue
      glVertex2f(0.1f, -0.6f);
      glVertex2f(0.7f, -0.6f);
      glVertex2f(0.4f, -0.1f);

      glColor3f(1.0f, 0.0f, 0.0f); // Red
      glVertex2f(0.3f, -0.4f);
      glColor3f(0.0f, 1.0f, 0.0f); // Green
      glVertex2f(0.9f, -0.4f);
      glColor3f(0.0f, 0.0f, 1.0f); // Blue
      glVertex2f(0.6f, -0.9f);
   glEnd();

   glBegin(GL_POLYGON);            // These vertices form a closed polygon
      glColor3f(1.0f, 1.0f, 0.0f); // Yellow
      glVertex2f(0.4f, 0.2f);
      glVertex2f(0.6f, 0.2f);
      glVertex2f(0.7f, 0.4f);
      glVertex2f(0.6f, 0.6f);
      glVertex2f(0.4f, 0.6f);
      glVertex2f(0.3f, 0.4f);
   glEnd();

   glFlush();  // Render now
}

/* Main function: GLUT runs as a console application starting at main()  */
int main(int argc, char** argv) {
   glutInit(&argc, argv);          // Initialize GLUT
   glutCreateWindow("Vertex, Primitive & Color");  // Create window with the given title
   glutInitWindowSize(320, 320);   // Set the window's initial width & height
   glutInitWindowPosition(50, 50); // Position the window's initial top-left corner
   glutDisplayFunc(display);       // Register callback handler for window re-paint event
   initGL();                       // Our own OpenGL initialization
   glutMainLoop();                 // Enter the event-processing loop
   return 0;
}

预期得到如下输出:

14adbdce9111d7127bcad749948092c9.png

坐标关系图:

f0a011640a2a1d08ed63ef39770a9bf6.png

我将会在接下来的部分详细解释该程序。

3.2 OpenGL 与状态机

OpenGL 像一个状态机一样运作,并且其维护了一组状态变量(例如:前景色,背景色)。在一个状态机中,一旦设置了状态变量的值,该值将持续存在,直到给出新的值。

举个例子,我们在 initGL() 函数中将 clearing 颜色设置为黑色。我们将在 display() 函数中反复地使用此项设置,注意 display() 函数在窗口重新绘制时被调用。

// In initGL(), set the "clearing" or background color
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);  // black and opaque

// In display(), clear the color buffer (i.e., set background) with the current "clearing" color
glClear(GL_COLOR_BUFFER_BIT);

另一个例子:如果我们使用 glColor 函数设置当前前景色为红色,之后红色将被用于后续所有顶点地着色,除非我们再次调用 glColor 函数改变前景色。

总之,在一个状态机中,任何值都将保持不变除非你显式地改变它。

3.3 OpenGL 命名约定

OpenGL 函数命名约定

  • 以小写的 gl(对于 OpenGL 核心函数库),glu(对于 OpenGL 实用程序库)或者 glut(对于 OpenGL 实用工具包)开头。
  • 紧跟该函数的功能,采用驼峰式命名法,例如 glColor 用于指定要绘制的颜色,glVertex 用于指定顶点的位置。
  • 紧跟参数描述,例如 glColor3f 函数要求三个浮点数作为参数,glVectex2i 函数要求两个整数作为参数。(为什么不直接使用函数重载?因为 C 语言不支持函数重载)。
  • 最后面是一个 v 代表参数需要是一个数组。

OpenGL 数据类型命名约定

typedef unsigned int    GLenum;
typedef unsigned char   GLboolean;
typedef unsigned int    GLbitfield;
typedef void            GLvoid;
typedef signed char     GLbyte;         /* 1-byte signed */
typedef short           GLshort;        /* 2-byte signed */
typedef int             GLint;          /* 4-byte signed */
typedef unsigned char   GLubyte;        /* 1-byte unsigned */
typedef unsigned short  GLushort;       /* 2-byte unsigned */
typedef unsigned int    GLuint;         /* 4-byte unsigned */
typedef int             GLsizei;        /* 4-byte signed */
typedef float           GLfloat;        /* single precision float */
typedef float           GLclampf;       /* single precision float in [0,1] */
typedef double          GLdouble;       /* double precision float */
typedef double          GLclampd;       /* double precision float in [0,1] */

OpenGL 常量命名约定

  • 以 GL,GLU 或者 GLUT 开头.
  • 下划线进行分割。
  • 全大写。

例如:GL_COLOR_BUFFER_BIT。

3.4 initGL()

initGL() 函数用于初始化那些只需要设置一次的任务,例如设置 clearing 颜色。initGL() 函数仅在 main() 函数中被调用一次。

3.5 display()

函数 display() 是一个事件处理回调函数。当一个事件发生时(例如按键被按下,鼠标点击,窗口绘制),相应的事件处理回调函数被调用。

当窗口第一次出现以及之后每次窗口重绘时调用 display() 函数。

注意该函数就是用户创建的一个普通的函数,名字可以任意,将此函数作为参数传递给 glutDisplayFunc(functionName) ,即所谓的向 OpenGL 注册绘制函数,这样 OpenGL 才知道当窗口绘制的时候应该调用哪一个函数。

3.6 设置 GLUT

GLUT 提供了一些较高层次封装的实用函数以简化 OpenGL 编程,尤其是当与操作系统交互时(例如创建窗口,处理键盘和鼠标输入)。在上述程序中用到了以下 GLUT 函数:

  • void glutInit(int *argc, char **argv):初始化 GLUT,需要在任何其它 GL/GLUT 函数前被调用,其参数与 main 函数一样。
  • int glutCreateWindow(char *title):创建一个窗口并设置窗口标题。
  • void glutInitWindowSize(int width, int height):指定窗口的宽度与高度,单位为像素。
  • void glutInitWindowPosition(int x, int y):指定窗口的位置,圆心为屏幕的左上角,x 轴正方向为向右,y 轴正方向则是向下。
  • void glutDisplayFunc(void (*func)(void)):注册处理窗口绘制事件的函数,传入的参数即函数名。
  • void glutMainLoop():进入事件处理循环,OpenGL 图形系统等待事件发生并调用相应的处理函数处理事件。

3.7 颜色

我们使用 glColor设置前景色,使用 glClearColor 函数设置背景色,即所谓的 clearing 颜色。

void glColor3f(GLfloat red, GLfloat green, GLfloat blue)
void glColor3fv(GLfloat *colorRGB)
void glColor4f(GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha)
void glColor4fv(GLfloat *colorRGBA)

void glClearColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha)
   // GLclampf in the range of 0.0f to 1.0f

注意:

  • 颜色通常是浮点数且范围在 0.0f 与 1.0f 之间。
  • 颜色可以使用 RGB 或者 RGBA 模式指定。A (alpha)代表透明度,值为 1 时完全不透明,值为 0 时完全透明。

在上述例子中,我们通过 initGL() 中的 glClearColor 设置背景色。

// In initGL(), set the "clearing" or background color
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);  // Black and opague

在 display() 函数中,我们通过 glColor3f() 函数设置后续的顶点的颜色。

// In display(), set the foreground color of the pixel
glColor3f(1.0f, 0.0f, 0.0f);  // Red

3.8 几何图元

在 OpenGL 中,物体是由诸如三角形,四边形,线段,点之类的几何图元构成的,而图元又由一个或者多个点构成。OpenGL 支持以下图元:

cc770d5cca29f81b96af19f4b1b3aa85.png

几何图元可以通过 glVertex 函数指定其顶点,并由一对 glBegin 和 glEnd 包裹来定义。

void glBegin(GLenum shape)
   void glVertex[234][sifd] (type x, type y, type z, ...)
   void glVertex[234][sifd]v (type *coords)
void glEnd()

glBegin 指定几何体的类型,例如 GL_POINTS,GL_LINES,GL_QUADS,GL_TRIANGLES 以及 GL_POLYGON。对于以 S 结尾的类型,你可以在每一组 glBegin / glEnd 定义多个相同类型的几何体。例如对于 GL_TRIANGLES,每三个顶点定义一个三角形。

顶点通常以单精度浮点数指定。这是因为整型不合适三角运算,而单精度浮点数的精度足够用于中间运算并最后将图形以像素为单位渲染到屏幕上,并且通常没有必要使用双精度浮点数。

在上述例子中:

glBegin(GL_QUADS);
   .... 4 quads with 12x glVertex() ....
glEnd();

我们使用了 12 个 glVertex() 函数定义了 3 个具有颜色的四边形。

glColor3f(1.0f, 0.0f, 0.0f);
glVertex2f(-0.8f, 0.1f);
glVertex2f(-0.2f, 0.1f);
glVertex2f(-0.2f, 0.7f);
glVertex2f(-0.8f, 0.7f);

我们将颜色设置为红色(R=1,G=0,B=0)。所有后续定义的顶点都会是红色。请注意在 OpenGL 中,颜色与许多其它的属性应用于顶点而非图元。图元的颜色由其顶点的颜色插值得来。

第二个四边形定义为为绿色。

对于第三个多边形,其顶点的颜色各不相同。四边形平面的颜色由其顶点的颜色插值而来,如程序的输出所示,结果是白色到深灰色的阴影。

glColor3f(0.2f, 0.2f, 0.2f);  // Dark Gray
glVertex2f(-0.9f, -0.7f);
glColor3f(1.0f, 1.0f, 1.0f);  // White
glVertex2f(-0.5f, -0.7f);
glColor3f(0.2f, 0.2f, 0.2f);  // Dark Gray
glVertex2f(-0.5f, -0.3f);
glColor3f(1.0f, 1.0f, 1.0f);  // White
glVertex2f(-0.9f, -0.3f);

3.9 二维坐标系以及默认视图

下面的图展示了 OpenGL 的二维坐标系统,其与圆心在左下角的直角坐标系相同。

4d53c7a83d50904ac7eb4d91cf88c52d.png

默认的 OpenGL 2D 裁剪区域(即相机捕获的区域)是 x 和 y 分别在-1.0 到 1.0 范围内的正交视图,即以原点为中心的 2x2 正方形。该裁剪区域被映射到屏幕上的视口(viewport)。视口以像素为单位。

研究以上示例,以使自己确信你所创建的 2D 图形在屏幕上被正确定位。

4. 裁剪区域与视口

尝试拉伸窗口使其变大或变小,注意到我们所绘制的形状变形了。我们可以通过 reshape() 回调函数手动处理窗口拉伸事件。

31f7e62cc62dc5f678a80992e4513572.png

裁剪区域:即能看到的区域,以 OpenGL 坐标系进行衡量。

函数 gluOrtho2D 可被用于设置裁剪区域为 2D 正交视图。在裁剪区域之外的物体会被裁剪掉以至于无法被看到。

void gluOrtho2D(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top)
   // The default clipping area is (-1.0, 1.0, -1.0, 1.0) in OpenGL coordinates, 
   // i.e., 2x2 square centered at the origin.

要想设置裁剪区域,我们首先需要设置要操作的矩阵,这里即投影矩阵 GL_PROJECTION,将其重置为单位矩阵,最后设置为正交视图以及裁剪区域的上下左右的参数。

// Set to 2D orthographic projection with the specified clipping area
glMatrixMode(GL_PROJECTION);      // Select the Projection matrix for operation
glLoadIdentity();                 // Reset Projection matrix
gluOrtho2D(-1.0, 1.0, -1.0, 1.0); // Set clipping area's left, right, bottom, top

视口:即屏幕上的可视区域,以屏幕坐标系进行衡量,单位为像素。

裁剪区域被映射到视口,我们可以使用函数 glViewport 配置视口。

void glViewport(GLint xTopLeft, GLint yTopLeft, GLsizei width, GLsizei height)

假设裁剪区域的参数 left, right, bottom, top 分别为 -1.0,1.0,-1.0,1.0(在 OpenGL 坐标系下),视口的参数 xTopLeft, xTopRight, width, height 分别为 0, 0, 640, 480(在屏幕坐标系下,单位为像素),则裁剪区域的左下角 (-1.0, -1.0) 被映射为 视口的 (0, 0),右上角 (1.0, 1.0) 被映射为 (639, 479)。很明显如果裁剪区域的长宽比与视口的长宽比不一致,物体就会变形。

4.3 例子 3:裁剪区域与视口(GL03Viewport.cpp)

/*
 * GL03Viewport.cpp: Clipping-area and Viewport
 * Implementing reshape to ensure same aspect ratio between the
 * clipping-area and the viewport.
 */
#include <windows.h>  // for MS Windows
#include <GL/glut.h>  // GLUT, include glu.h and gl.h

/* Initialize OpenGL Graphics */
void initGL() {
   // Set "clearing" or background color
   glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Black and opaque
}

void display() {
   glClear(GL_COLOR_BUFFER_BIT);   // Clear the color buffer with current clearing color

   // Define shapes enclosed within a pair of glBegin and glEnd
   glBegin(GL_QUADS);              // Each set of 4 vertices form a quad
      glColor3f(1.0f, 0.0f, 0.0f); // Red
      glVertex2f(-0.8f, 0.1f);     // Define vertices in counter-clockwise (CCW) order
      glVertex2f(-0.2f, 0.1f);     //  so that the normal (front-face) is facing you
      glVertex2f(-0.2f, 0.7f);
      glVertex2f(-0.8f, 0.7f);

      glColor3f(0.0f, 1.0f, 0.0f); // Green
      glVertex2f(-0.7f, -0.6f);
      glVertex2f(-0.1f, -0.6f);
      glVertex2f(-0.1f,  0.0f);
      glVertex2f(-0.7f,  0.0f);

      glColor3f(0.2f, 0.2f, 0.2f); // Dark Gray
      glVertex2f(-0.9f, -0.7f);
      glColor3f(1.0f, 1.0f, 1.0f); // White
      glVertex2f(-0.5f, -0.7f);
      glColor3f(0.2f, 0.2f, 0.2f); // Dark Gray
      glVertex2f(-0.5f, -0.3f);
      glColor3f(1.0f, 1.0f, 1.0f); // White
      glVertex2f(-0.9f, -0.3f);
   glEnd();

   glBegin(GL_TRIANGLES);          // Each set of 3 vertices form a triangle
      glColor3f(0.0f, 0.0f, 1.0f); // Blue
      glVertex2f(0.1f, -0.6f);
      glVertex2f(0.7f, -0.6f);
      glVertex2f(0.4f, -0.1f);

      glColor3f(1.0f, 0.0f, 0.0f); // Red
      glVertex2f(0.3f, -0.4f);
      glColor3f(0.0f, 1.0f, 0.0f); // Green
      glVertex2f(0.9f, -0.4f);
      glColor3f(0.0f, 0.0f, 1.0f); // Blue
      glVertex2f(0.6f, -0.9f);
   glEnd();

   glBegin(GL_POLYGON);            // These vertices form a closed polygon
      glColor3f(1.0f, 1.0f, 0.0f); // Yellow
      glVertex2f(0.4f, 0.2f);
      glVertex2f(0.6f, 0.2f);
      glVertex2f(0.7f, 0.4f);
      glVertex2f(0.6f, 0.6f);
      glVertex2f(0.4f, 0.6f);
      glVertex2f(0.3f, 0.4f);
   glEnd();

   glFlush();  // Render now
}

/* 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();             // Reset the projection matrix
   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
   glutInitWindowSize(640, 480);   // Set the window's initial width & height - non-square
   glutInitWindowPosition(50, 50); // Position the window's initial top-left corner
   glutCreateWindow("Viewport Transform");  // 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
   initGL();                       // Our own OpenGL initialization
   glutMainLoop();                 // Enter the infinite event-processing loop
   return 0;
}

当窗口第一次出现以及任何时候当窗口被重新调整大小时,reshape() 函数被调用,用以确保裁剪区域与视口的长宽比的一致性。图形子系统会将以像素为单位的窗口的宽度与高度作为参数传递给 reshape() 函数。

GLfloat aspect = (GLfloat)width / (GLfloat)height;

glViewport(0, 0, width, height);

我们通过设置视口以使其覆盖调整过大小后的窗口。例如,如果我们想设置视口仅覆盖窗口的四分之一(右下角),可以通过调用函数:glViewport(0, 0, width/2, height/2)

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (width >= height) {
   gluOrtho2D(-1.0 * aspect, 1.0 * aspect, -1.0, 1.0);
} else {
   gluOrtho2D(-1.0, 1.0, -1.0 / aspect, 1.0 / aspect);
}

我们在此处设置裁剪区域的长宽比以使其匹配视口。具体的设置步骤,我们首先需要选择在映射矩阵上进行操作:glMatrixMode(GL_PROJECTION)。OpenGL 具有两个矩阵,一个负责处理摄像机投影的映射矩阵,一个用于将物体从其本地坐标系转换到世界坐标系的模型视图矩阵。我们通过glLoadIdentity()来重置映射矩阵。

最终,我们调用gluOrtho2D()设置裁剪区域以使其长宽比与视口匹配。如下图所示,较短的一边的范围为 -1 到 1.

a60c46b83cf21cc5b9207d171dd99b81.png

我们需要通过主函数中的 glutReshapeFunc()注册reshape()回调函数。

int main(int argc, char** argv) {
   glutInitWindowSize(640, 480);
   ......
   glutReshapeFunc(reshape);
}

在上述主函数中,我们指定了初始窗口尺寸为 640x480,其并不是方形的。尝试拉伸窗口并观察变化。

注意reshape()函数在窗口第一次出现时至少运行一次,之后当窗口被重新调整大小时都会被调用。另外,initGL() 函数只运行一次,而display()函数每当窗口被重绘时都会被调用。

5. 平移和旋转

在上述例子中,我们通过指定顶点来摆放形状,而这些顶点都参照同一个坐标原点,即在所谓的世界坐标系下进行定义。我花了很长时间才弄清楚这些顶点的绝对坐标。

这无疑不是很方便,我们可以通过参照图形自身的中心来定义其顶点,即在所谓的模型坐标系下进行定义。之后我们可以使用平移和旋转来在世界坐标系下将图形摆放到目标位置。

5.1 例子 4:平移与旋转(GL04ModelTransform.cpp)

/*
 * GL04ModelTransform.cpp: Model Transform - Translation and Rotation
 * Transform primitives from their model spaces to world space.
 */
#include <windows.h>  // for MS Windows
#include <GL/glut.h>  // GLUT, include glu.h and gl.h

/* Initialize OpenGL Graphics */
void initGL() {
   // Set "clearing" or background color
   glClearColor(0.0f, 0.0f, 0.0f, 1.0f); // Black and opaque
}

/* 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

   glTranslatef(-0.5f, 0.4f, 0.0f); // Translate left and up
   glBegin(GL_QUADS);               // Each set of 4 vertices form a quad
      glColor3f(1.0f, 0.0f, 0.0f);  // Red
      glVertex2f(-0.3f, -0.3f);     // Define vertices in counter-clockwise (CCW) order
      glVertex2f( 0.3f, -0.3f);     //  so that the normal (front-face) is facing you
      glVertex2f( 0.3f,  0.3f);
      glVertex2f(-0.3f,  0.3f);
   glEnd();

   glTranslatef(0.1f, -0.7f, 0.0f); // Translate right and down
   glBegin(GL_QUADS);               // Each set of 4 vertices form a quad
      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();

   glTranslatef(-0.3f, -0.2f, 0.0f); // Translate left and down
   glBegin(GL_QUADS);                // Each set of 4 vertices form a quad
      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();

   glTranslatef(1.1f, 0.2f, 0.0f); // Translate right and up
   glBegin(GL_TRIANGLES);          // Each set of 3 vertices form a triangle
      glColor3f(0.0f, 0.0f, 1.0f); // Blue
      glVertex2f(-0.3f, -0.2f);
      glVertex2f( 0.3f, -0.2f);
      glVertex2f( 0.0f,  0.3f);
   glEnd();

   glTranslatef(0.2f, -0.3f, 0.0f);     // Translate right and down
   glRotatef(180.0f, 0.0f, 0.0f, 1.0f); // Rotate 180 degree
      glBegin(GL_TRIANGLES);               // Each set of 3 vertices form a triangle
      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();

   glRotatef(-180.0f, 0.0f, 0.0f, 1.0f); // Undo previous rotate
   glTranslatef(-0.1f, 1.0f, 0.0f);      // Translate right and down
   glBegin(GL_POLYGON);                  // The vertices form one closed 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();

   glFlush();   // Render now
}

/* 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
   glutInitWindowSize(640, 480);   // Set the window's initial width & height - non-square
   glutInitWindowPosition(50, 50); // Position the window's initial top-left corner
   glutCreateWindow("Model Transform");  // 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
   initGL();                       // Our own OpenGL initialization
   glutMainLoop();                 // Enter the infinite event-processing loop
   return 0;
}
glMatrixMode(GL_MODELVIEW); // To operate on model-view matrix
glLoadIdentity();           // Reset

平移与旋转是所谓的模型变换的一部分,其将模型从模型坐标系下转换到世界坐标系。要想进行模型变换,我们需要首先将当前矩阵模式设置为模型视图矩阵(GL_MODELVIEW)并将其重置为单位矩阵。

OpenGL 作为状态机运行。也就是说,一旦状态被设置,该状态持续存在除非其被改变。结合此处的例子,一旦坐标被平移或旋转了,所有后续的操作都会基于这些被平移或旋转后的坐标。

通过 glTranslate 函数来进行平移:

void gltranslatef (GLfloat x, GLfloat y, GLfloat z)
   // where (x, y, z) is the translational vector

注意glTranslatef 函数必须要放置在 glBegin/glEnd 的外面,而glColor 函数则可以放置在glBegin/glEnd里面。

通过glRotatef函数来进行旋转:

void glRotatef (GLfloat angle, GLfloat x, GLfloat y, GLfloat z)
   // where angle specifies the rotation in degree, (x, y, z) forms the axis of rotation

注意旋转的角度的单位为度,而非弧度。

在上述例子中,我们在 x-y 平面对物体进行了平移操作,并围绕 z 轴旋转物体。

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 函数来交换前后缓冲区。

要想使用双重缓冲,你需要在代码中做以下两处改变:

  1. 首先在 main() 中包含以下代码,注意需要在创建窗口之前:

glutInitDisplayMode(GLUT_DOUBLE); // Set double buffered mode

  1. 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 组成一个圆。

a1e9218ed380bf96849efd20df2a5cec.png
/*
 * 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:详细的代码解释

7. 使用 GLUT 处理键盘输入

我们可以分别为常规按键和特殊按键分别组成回调函数。

  • glutKeyboardFunc 函数:注册处理键盘输入事件的回调函数

c++ void glutKeyboardFunc (void (*func)(unsigned char key, int x, int y) // key is the char pressed, e.g., 'a' or 27 for ESC // (x, y) is the mouse location in Windows' coordinates

  • glutSpecialFunc 函数:注册处理特殊按键(例如箭头按键和功能键)事件的回调函数

c++ void glutSpecialFunc (void (*func)(int specialKey, int x, int y) // specialKey: GLUT_KEY_* (* for LEFT, RIGHT, UP, DOWN, HOME, END, PAGE_UP, PAGE_DOWN, F1,...F12). // (x, y) is the mouse location in Windows' coordinates

7.1 例子 8:在全屏模式和窗口模式之间进行切换(GL08FullScreen.cpp)

对于上述的弹跳小球程序,下述程序实现了使用 F1 键将其从全屏模式以及窗口模式之间进行切换的功能。

/*
 * GL08FullScreen.cpp: Switching between full-screen mode and windowed-mode
 */
#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[] = "Full-Screen & Windowed Mode";  // 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;

bool fullScreenMode = true; // Full-screen or windowed mode?

/* 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
}

/* Callback handler for special-key event */
void specialKeys(int key, int x, int y) {
   switch (key) {
      case GLUT_KEY_F1:    // F1: Toggle between full-screen and windowed mode
         fullScreenMode = !fullScreenMode;         // Toggle state
         if (fullScreenMode) {                     // Full-screen mode
            windowPosX   = glutGet(GLUT_WINDOW_X); // Save parameters for restoring later
            windowPosY   = glutGet(GLUT_WINDOW_Y);
            windowWidth  = glutGet(GLUT_WINDOW_WIDTH);
            windowHeight = glutGet(GLUT_WINDOW_HEIGHT);
            glutFullScreen();                      // Switch into full screen
         } else {                                         // Windowed mode
            glutReshapeWindow(windowWidth, windowHeight); // Switch into windowed mode
            glutPositionWindow(windowPosX, windowPosX);   // Position top-left corner
         }
         break;
   }
}

/* 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
   glutSpecialFunc(specialKeys); // Register callback handler for special-key event
   glutFullScreen();             // Put into full screen
   initGL();                     // Our own OpenGL initialization
   glutMainLoop();               // Enter event-processing loop
   return 0;
}

TODO:详细的代码解释

注意,下面的部分请在我的博客网站上查看,因为本篇文章过长,知乎上无法发布。

OpenGl 环境搭建与介绍​iamazing.cn

7.2 例子 9:按键控制(GL09KeyControl.cpp)

8. 使用 GLUT 处理鼠标输入

8.1 例子 10:鼠标控制(GL10MouseControl.cpp)

8.2 例子 11:一个简单的绘图程序

7f9d520746e6785c2c9386a26fc0dbcd.gif

TODO:使用鼠标移动事件以及 GL_LINE_STRIP。

注意

  • 原文链接:OpenGL Tutorial An Introduction on OpenGL with 2D Graphics
  • OpenGL / 计算机图形学参考与资料
  • 翻译的过程中,我增添与删除了一些内容
  • 如有谬误,还望指正
  • 禁止转载
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值