原文地址:http://www.videotutorialsrock.com/opengl_tutorial/basic_shapes/home.php
视频下载:http://www.videotutorialsrock.com/opengl_tutorial/basic_shapes/video.flv
文本课件:http://www.videotutorialsrock.com/opengl_tutorial/basic_shapes/text.php
源码下载:http://www.videotutorialsrock.com/opengl_tutorial/basic_shapes/basicshapes.zip
译文:
运行程序
来看我们的第一个OpenGL程序。下载basic shapes源码,编译并运行,应该会出现下面的图形,按ESC键退出。
程序说明
程序是如何运行的?基本的思路是告诉OpenGL我们想要画的图形的所有顶点的3维坐标。OpenGL使用标准的坐标系,x正半轴指向右边,y正半轴指向上边。但是3维还需要一个坐标轴:z轴。z正半轴指向屏幕外面。如下图。
OpenGL又是如何使用3维坐标的呢?这是通过模拟我们的眼睛来工作的。看下图:
OpenGL在描绘任何东西之前都会将3D坐标转换为屏幕上某个象素的坐标。见上图,OpenGL将场景中每个点画一条直线到你的眼睛中,并取这条线和屏幕的交点作为这个象素的坐标。如果OpenGL想要描绘一个三角形,它会将这三个顶点转换为象素的坐标并使用这些坐标画一个2D的三角形在屏幕上。
用户的“眼睛”总是处在原点并且向z轴的负方向看。显然,OpenGL不会画出在眼睛后面的任何东西。
那么屏幕离眼睛有多远呢?实际上这并不重要,不管屏幕有多远,一个给定的3维点总是会映射到相同的象素坐标上,不同的只是你眼睛能观看的角度变了。
源码分析
关于象素坐标就这些,作为程序员,我们还想看下代码是怎样的。现在来看main.cpp文件。
首先注意到有一段许可说明,这个代码包括本课程所有代码都市免费的,并可以用于商业用途。
其次有大量的注释,因为这是第一课。其他的课程不会这么详细的解释,但是仍然会有必要的解释。
现在看看这个文件,看我们能不能理解它在干嘛。
#include <iostream>
#include <stdlib.h> //Needed for "exit" function
//Include OpenGL header files, so that we can use OpenGL
#ifdef __APPLE__
#include <OpenGL/OpenGL.h>
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif
首先包含标准C++头文件。如果使用MAC系统则包含glut/glut.h和OpenGL/OpenGL.h文件,否则就包含gl/glut.h文件。
using namespace std;
将这一行放在main.cppa的顶部。这样做可以省去输入很多的std::的麻烦,比如可以用cout代替std::cout。
//Called when a key is pressed
void handleKeypress(unsigned char key, //The key that was pressed
int x, int y) { //The current mouse coordinates
switch (key) {
case 27: //Escape key
exit(0); //Exit the program
}
}
这个函数处理用户按下的键盘操作。目前仅仅有在用户按下ESC键时退出的功能(调用exit函数)。这个函数也传递鼠标当前的x和y坐标,但我们并没有用到。
//Initializes 3D rendering
void initRendering() {
//Makes 3D drawing work when something is in front of something else
glEnable(GL_DEPTH_TEST);
}
initRendering函数初始化我们的绘制(render)参数。目前并没有做什么,在初始化绘制时多半需要调用glEnable(GL_DEPTH_TEST),这个函数确保在某个物体之后的那个物体也同样画在那个物体后面(被覆盖),这也正是我们想要的。
注意glEnable同每个OpenGL函数一样是以gl开头的。
//Called when the window is resized
void handleResize(int w, int h) {
//Tell OpenGL how to convert from coordinates to pixel values
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION); //Switch to setting the camera perspective
//Set the camera perspective
glLoadIdentity(); //Reset the camera
gluPerspective(45.0, //The camera angle
(double)w / (double)h, //The width-to-height ratio
1.0, //The near z clipping coordinate
200.0); //The far z clipping coordinate
}
handleResize函数在窗口大小变化时调用。w和h是新窗口的宽和高。在我们的工程中handleResize的内容并没有多少变化,不必太多考虑这个函数。
有几点需要注意。我们将45.0传给gluPrespective的时候,我们在告诉OpenGL用户的眼睛能看见的角度。1.0表示不画出z轴大于-1的物体,即这个物体紧贴着你的眼睛而不是充满整个屏幕。200.0表示OpenGL不画出z轴小于-200的任何物体。目前可以不用管这个。
为什么gluPerspective是以glu开始而不是gl呢?这是因为这个函数是GLU(GL Utility)函数。除了以gl和glu,有些函数是以glut(GL Utility Toolkit)开始的。目前我们不关心这之间的区别。
//Draws the 3D scene
void drawScene() {
//Clear information from last draw
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
drawScene函数是真正的3D绘图函数。首先调用glClear清理上传画图的信息。在几乎所有的OpenGL程序中都需要这样做。
glMatrixMode(GL_MODELVIEW); //Switch to the drawing perspective
glLoadIdentity(); //Reset the drawing perspective
目前不介绍这个,在下一课讲介绍这些函数进行一些进行转换。
glBegin(GL_QUADS); //Begin quadrilateral coordinates
//Trapezoid
glVertex3f(-0.7f, -1.5f, -5.0f);
glVertex3f(0.7f, -1.5f, -5.0f);
glVertex3f(0.4f, -0.5f, -5.0f);
glVertex3f(-0.4f, -0.5f, -5.0f);
glEnd(); //End quadrilateral coordinates
这里是程序的实质部分。这段代码画一个梯形。调用glBegin(GL_QUADS)表明我们想开始画一个四边形。然后我们使用glVertex3f指定四边形顶点的3维坐标。调用glVertex3f时,我们指明3个float型的坐标。当我们画出四边形之后调用glEnd()。注意每个glBegin必须要同glEnd匹配。
每个顶点坐标之后的“f”强迫编译器将数字看为float型。技术上来说并不必须这样做,但是我仍然会使用。
glBegin(GL_TRIANGLES); //Begin triangle coordinates
//Pentagon
glVertex3f(0.5f, 0.5f, -5.0f);
glVertex3f(1.5f, 0.5f, -5.0f);
glVertex3f(0.5f, 1.0f, -5.0f);
glVertex3f(0.5f, 1.0f, -5.0f);
glVertex3f(1.5f, 0.5f, -5.0f);
glVertex3f(1.5f, 1.0f, -5.0f);
glVertex3f(0.5f, 1.0f, -5.0f);
glVertex3f(1.5f, 1.0f, -5.0f);
glVertex3f(1.0f, 1.5f, -5.0f);
现在来画五边形,我们将五边形分解为两个三角形,这在OpenGL中很常用。开始调用glBegin(GL_TRIANGLES)表示我们想要画三角形。然后指明三角形的顶点坐标。
OpenGL自动将三个顶点的坐标当做一组,代表一个三角形的三个顶点。
//Triangle
glVertex3f(-0.5f, 0.5f, -5.0f);
glVertex3f(-1.0f, 1.5f, -5.0f);
glVertex3f(-1.5f, 0.5f, -5.0f);
最后画三角形。由于并没有调用glEnd()告诉OpenGL我们画完了,说明我们继续画三角形。
glEnd(); //End triangle coordinates
现在三角形画完了,因此调用glEnd()。
也可以通过四个glBegin(GL_TRIANGLES)和四个glEnd()来画上面四个三角形,但是这样做是低效的,应该避免。
除了可以传递GL_TRIANGLES和GL_QUADS还可以传其他形状,只是三角形和四边形是绘画最常用的。
glutSwapBuffers(); //Send the 3D scene to the screen
}
这一行让OpenGL将创建移到屏幕上。当我们完成一个场景的绘制时应该调用它。
int main(int argc, char** argv) {
//Initialize GLUT
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(400, 400); //Set the window size
//Create the window
glutCreateWindow("Basic Shapes - videotutorialsrock.com");
initRendering(); //Initialize rendering
这是程序的主函数。一开始初始化glut,这也是大多数程序需要做的,不需要太注意。在glutInitWindowSize函数中设置窗口大小为400×400。调用glutCreateWindow时指定窗口的标题。然后调用initRendering来初始化OpenGL的绘制。
//Set handler functions for drawing, keypresses, and window resizes
glutDisplayFunc(drawScene);
glutKeyboardFunc(handleKeypress);
glutReshapeFunc(handleResize);
现在指定glut调用我们写的处理键盘,绘画和重新定义窗口大小的函数。注意除了在drawScene中明确写出或者调用的那些物体之外,不允许画任何其他物体。
glutMainLoop(); //Start the main loop. glutMainLoop doesn't return.
return 0; //This line is never reached
}
接着调用glutMainLoop告诉glut进入事情处理。这就是我们告诉glut捕捉键盘和鼠标输入,当调用drawScene函数的时候绘画场景等等。
glutMainLoop象一个死循环,不返回值。glut处理程序剩下的所有事情。调用这个函数之后返回一个0值使得编译器在编译的时候不报错,但是程序不会执行到哪一行。
这就是我们第一个OpenGL程序是如何工作的。可以尝试做一个小练习以熟悉上面讲的内容。
练习:
- 让五边形离镜头(用户的眼睛)更远些。
- 想象一个最喜欢的四边形,并代替三角形。
- 使用glBegin(GL_TRIANGLES)来绘画梯形。