【OpenGL Tutorial】Lesson1 基本图形

试一试

让我们先来看看我们的第一个OpenGL程序。下载“basic shapes”测试程序,编译并运行它(怎样用Visual Studio编译看第一节课程)。程序的运行样例如下图,点击ESC可以退出程序。



程序是怎样运行起来的

程序是怎样运行起来的呢?最基本的思想就是我们告知OpenGL每个图形顶点的3D坐标。OpenGL使用标准的x,y坐标系,x水平指向右方,y竖直指向上方。但在3D中我们还需要一个z坐标系,它指向屏幕。

OpenGL是怎样利用3D坐标的呢?它模仿人类眼睛的工作机理,看下面这张图片。


OpenGL在画图前都将所有的3D点转化为一个像素坐标,正如上面这张图片所描述的,它为了将3D点转化为一个像素坐标,其通过将此3D点与眼睛联系,然后会与屏幕有一个焦点,此点就转化为了屏幕上的像素坐标。所以,当OpenGL要画一个三角形的时候,它将3D坐标转化为像素坐标,然后画一个“2D”的三角形。
用户的“眼睛”总位于原点,看向z轴的负方向。当然,OpenGL不会画出任何在“眼睛”后面的图像的。
屏幕离我们的眼睛到底有多远呢?其实这个真的不重要,无聊屏幕离我们有多远,一个给定的3D点都会定位到固定的像素点上,关键是我们眼睛的角度。

源代码

关于像素坐标的事情都是上面所说的,但是作为程序员,我们更想看看代码,让我们看看main.cpp吧。
你关注的第一件事就是指明此代码的License,此网站上的所有代码都是免费的,你甚至可以将其应用到商业工程上去。
第二件事就是你会发现代码中有许多注释,虽然有些碍眼,但这是第一节课。后面的课程中的代码注释将会减少。
让我们看一下代码,看看我们是否已经弄懂他们了。
#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,那么我们的程序还需要包含“OpenGL/OpenGL.h”和“GLUT/glut.h”两个头文件;否则我们只包含"GL/glut.h"一个头文件

using namespace std;

这一行代码位于main.cpp的顶端。它仅仅是让我们不用在写代码的过程中写太多的std::

//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时,程序将会退出。此函数还会传递鼠标坐标的信息,但我们并没有进行处理。

//Initializes 3D rendering
void initRendering() {
	//Makes 3D drawing work when something is in front of something else
	glEnable(GL_DEPTH_TEST);
}

initRendering函数初始化我们的渲染参数。现在,它做的很少。当我们初始化初始化渲染时,我们总是用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表示用户的“眼睛可以看到的角度”。1.0指明当物体的z坐标大于-1时,物体无法成像到屏幕上;200.0表示到物体的z坐标小于-200时,也无法成像。
那么,为什么gltPerspective是以“glu”开头的而不是以“gl”开头的那?那是因为技术上面他是GLU(GL Utility)函数。另外还有“gl”和“glu”,有些函数将“glut”作为前缀。其实我们无需但系OpenGL,GLU和GLUT的不同。

//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)告诉OpenGL我们要画一个四边形,之后我们调用glVertex3f函数标定了4个3D坐标。当我们调用glVertex3f时,我们将3个浮点型值输入。当我们画完一个四边形的时,我们调用glEnd()。注意glBegin和glEnd必须配对。
在左右顶点坐标数值后的f都是强制编译器将其视为浮点型。技术上我并不觉得这样做有必要,但写代码时还是继续在做。

        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);

现在,我们开始画五角星,我们将五角星分成3个三角形,这样对OpenGL来说更加直观。我们通过调用glBegin(GL_TRIANGLES)来向OpenGL说明我们要开始画三角形了,之后我们填入三角形的坐标。
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()和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),我们指明窗口的大小是400×400的,当我们调用glutCreateWindow函数,我们告知窗口的名称。initRendering函数则是我们上面介绍的初始化渲染的函数。

	//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做它该做的事情。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值