您的位置:首页 > 运维架构

学习opengl官方指南 01 opengl介绍

2017-04-25 10:34 435 查看
申明:翻译的不好,勿喷,轻喷。

本章的目标

1. 可以知道opengl一些常见的专有名词

2. 识别不同级别的渲染复杂度

3. 理解opengl的命令语法

4. 知道opengl管段渲染的系列操作

5. 理解在opengl程序中怎么生产动画

这章介绍opengl,它分为以下几个重要的小节

1. 什么是opengl

解释什么是opengl,它能做什么,它不能做什么,以及它怎么做

2. 一个简单的opengl程序

写一个简单的opengl程序,简单地介绍它,这个小节也介绍计算机图形的基本名词

3. opengl命令语法

解释一些opengl命令常见的习惯以及形式(命令是函数的意思)

4. opengl的状态机模型

介绍opengl的状态变量,已经用于查询,使能状态的命令

5. opengl的渲染管道

展示一系列的图形图像的处理操作

6. opengl相关的库

介绍和opengl相关操作的函数,包括为了简化这本书写的一些具体的辅助函数

7. 动画

介绍在屏幕上移动的一些基础知识

1. 什么是opengl

opengl是图像硬件的软件接口,这个接口包好大约150个不同的命令,可以让你指定物体,以及在三维程序中产生交互效果。

opengl是流类型的,硬件独立的接口,可以在很多不同的硬件平台上实现。为了达到这个目标,opengl不包括一些窗口相关或者获取用户输入的命令,你必须使用指定的窗口穿件工具创建操作相应的窗口。同样,opengl不提供描述三维物体模型的高层次命令。这些命令可以让你很快地创建诸如摩托车,身体,飞机,分子模型等复杂的物体,但是opengl并不提供。使用opengl,你可以通过很小的图像基础点,线,几何体创建相应的复杂图形。

可以在opengl上建立提供这些功能的复杂的库,opengl工具库(glu)提供许多模型的特性,比如三维表面,NURBS曲线。glu是每个opengl实现的标准部分。同时,这里还有一个更高层次的,面向对象的工具集Open Invetor,它建立在opengl之上,可以在很多的opengl实现中获取。(更多的信息查看opengl小节——opengl相关函数库关于open Inventor部分。)

现在你知道了opengl不能做什么,这里是它能做的。快速浏览下面括号中的展示点,它们介绍了opengl的典型使用。它们展示这本书的封面图形,使用opengl更复杂的渲染技术,下面这些知识点介绍生成这些图片的常见知识点

(1)展示一个全部使用线性模型生成的场景,就是说所有的问题都是由线条构成的。每条线条都是基础的几何多边形的边线,比如桌子的表明是由三角多边形构成的,像是一个馅饼。

注意相对于线性模型来说,如果物体是实心的话会挡住后面的物体,比如你可以看见窗户外面的山,尽管这部分被房间的枪挡住了。地球仪看起来是实心的,因为它由成百上千个带颜色的小方块组成,你可以看到所有小方块的边缘都是线性,即使是组成地球仪的背面,地球仪的组成方式给以一个大概的印象,一些负责的物体如何由简单的低阶的物体构成。



(2)展示一个相同的线性模型,不过含有深度变化,线条越远显示越昏暗,和现实生活中近大远小一样的道理,opengl中使用大气效应到达深度曲线的模型。
更多的图形显示查看
http://www.glprogramming.com/red/appendixi.html#plate1
(3)展示有线模型中使用反锯齿的效果,反锯齿的技术减少锯齿边缘,通常是应为边缘无法平滑过渡造成的,因为像素通常是一个一个的小方块,这种小方块在边缘部分最常见。

(4)展示一个平面着色,无灯光的场景,在场景的物体是实心的。他们显得有点平只有一种颜色用于渲染每个图形,因此它们过度的不平缓,同时没有灯光效果。

(5)展示一个有光的,平滑阴影的版本。现在这个场景看起来更加的真实,三维物体由于光源而产生相应的明暗变化,就像是物体的平稳过度。

(6)在之前的例子中增加阴影和纹理,阴影病没有在opengl中具体定义(没有阴影相关的命令),但是你可以使用底14章描述的技术创建。纹理可以让将二维的图像迁移到三维中,在这个场景中,桌子的表面是最明显的纹理。地板上的木头,桌子表面都是纹理,也包括桌子上面的玩具。

(7)展示运动模糊的物体,动物在场景中运动,需要模糊跟踪它的运动路径。

(8)从另一个视角展示这本书的封面的画法,这个场景表明图像只是三维图形的截取。

(9)使用前景fog,呈现空气中有有烟状的效果图,只是在(2)的基础上增加一个更戏剧化的影响。

(10)显示深层渲染效果,就像是相机无法使真实场景中的物体全部正确对
13564
焦,相机只能定位有一个焦点正确定位在场景的物体上。所有离那个点远的近的物体都会变得模糊。

上面的10个例子给你opengl能做哪些事情一些印象,接下来的篇章简要介绍opengl渲染一张图片的基本操作。(详细的信息查看章节--opengl管道渲染。)

从基本的图形中创建物体,随之而创建物体的数学描述。(opengl的基础图形是点,线,多边形,图像,位图。)

在三维空间中防止几何图形,从一个有利的位置点上看整个组成的场景。

计算所有物体的颜色,颜色可以来自于程序直接赋值,取决于具体的光源条件,通过物体的纹理获取,或者是结合三者。

将物体的数学描述和相关联的颜色信息转换为屏幕上的像素,这个过程称为光栅化。

在这些过程中,opengl可能有其它的操作,比如消除被其它物体遮挡的部分。另外,在光栅化之后,显示到屏幕之前,你可以操作像素数据。

在一些实现中,比如(X Window系统),尽管显示的图像不在本地的电脑中,opengl仍然可以工作,这种情况会出现在许多电脑通过网络连接的情况。在这种情况下,接收指令进行相应操作的叫做服务器,发送指令的叫做客户端。从客户端到服务端opengl的指令传输是一样的,所以opengl程序可以通过网络运行,即使是客户端服务端是不同的机器。如果只有一台电脑,没有通过网络运行,那么这台电脑既是客户端又是服务端。

2. 一个简单的opengl程序

因为你可以使用opengl做很多的事情,所以一个opengl程序会很复杂。但是,一个有用的基本架构是简单的:它的任务就是初始化确定的状态和控制opengl如何渲染以及指定那些物体会被渲染。

在你看是看opengl程序之前,先熟悉几个专有名词。渲染(Rendering),你之前已经看过,是一个电脑从模型中创建图像的过程,这些模型或者对象是由基础几何图形(点,线,几何)创建的,通过向量指定它们。

最后的渲染图像显示在屏幕上,一个像素是显示器投影到屏幕上的最小单位。像素的信息,比如颜色的组成,是存放在位内存中的,位内存是是存起屏幕上每个像素的基本信息。位可能含有某个特殊像素的颜色,位像素同时也组成帧缓存,它存在所有需要显示在屏幕上的图形信息。

现在来看看一个简单opengl程序是什么样子的,例子1-1在一个很色北京中渲染一个白色的长方形,如下图所示

白色长方形在黑色背景中

#include <whateverYouNeed.h>

main() {
InitializeAWindowPlease();

glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
glBegin(GL_POLYGON);
glVertex3f(0.25, 0.25, 0.0);
glVertex3f(0.75, 0.25, 0.0);
glVertex3f(0.75, 0.75, 0.0);
glVertex3f(0.25, 0.75, 0.0);
glEnd();
glFlush();

UpdateTheWindowAndCheckForEvents();
}


main方法的第一句话是在屏幕中初始化一个窗口,InitializeAWindowPlease函数是一个窗口具体函数的占位符,它通常不是opengl自己的调用。下面的两行是openggl的命令清楚窗口变成黑色:glClearColor()让窗口的颜色变回想要的颜色。glClear函数清除窗口,这个窗口就被设置成之前的颜色。通过调用glClearColor窗口可以设置成为想要的颜色,同样glColor3f()命令也用于绘画物体时使用那种颜色,在这个场景中,颜色是白色。后面所有的物体都会是这个颜色,知道调用其它的函数设置颜色。

下一个opengl命令glOrtho指定opengl最终图像生成的坐标系,同时将图像映射到屏幕上。下一个调用,从glBegin()开始,glEnd()结束定义了需要生成的物体。在这个例子中,一个多边形有四个向量,多边形的角被glVertex3f命令定义。同事你也可以猜到它们参数的意思,分别指xyz坐标系,这个多边形的z坐标值为0。

最后,flFlush()确保绘制函数确实都已经执行,而不是储存在缓存中等待其它的opengl操作。UpdateTheWindowAndCheckForEvents()是一个占位符,需要系统的window相关函数使它生效。

3.opengl命令语法

就像你前面那个简单的程序中看到的,opengl命令是由gl前缀,每个单词的首字母大写组成opengl的命令,比如glClearColor()。同样,opengl的常量是用GL_开头,所有的字符都是大写,使用下划线分开每个单词,比如GL_COLOR_BUFFER_BIT。

你可能也注意到一些特殊的字母附加在一些命令之后,比如(glColor3f()中的3f, glVertex3f()中的3f),只用到Color部分就已经足够去定义一个命令去设置当前的颜色。但是,不仅仅只有一个函数这样定义,因此你可以使用不同类型的参数。特别指出的是3表示使用三个参数,另一个Color命令可以使用4个参数。f后缀表示参数属于浮点类型。拥有不同的格式,可以允许opengl接受不同的用户数据。

有些opengl命令可以接受多达8种不同类型的参数,下面的表格表示iso c中的基本类型在opengl中的展示。有可能你的opengl实现没有遵循这个规则,比如C++实现,Ada都没有这样做。

因此,下面两个命令是相等的。

glVertex2i(1, 3);

glVertex2f(1.0, 3.0);

除了第一个向量坐标是32位的整数,第二个是一个单精度浮点类型的数。

注意:opengl实现在选择c的数据类型表示opengl的数据类型时会有偏差。如果你十周使用opengl的数据类型,那么在不同的实现中移植你的程序就不会出现数据类型不匹配的情况。

一些opengl命令还有最后一个字母v,表示这个命令的参数是一个指向向量或者数组的指针,而不是一系列的参数。许多命令有向量和非向量类型,但是另外一些命令只接受独立的参数,另外一些命令需要向量表示一些参数。下面这句话给你展示你既可以使用向量类型也可能使用非向量累些那个设置当前的颜色。

glColor3f(1.0, 0.0, 0.0);

GLfloat color_array[] = {1.0, 0.0, 0.0};

glColor3f(color_array);

最后,opengl定义了GLvoid类型,这是最常用的opengl命令接受指向数组的指针类型。

下面的指南中,除了真是的例子,其它的opengl命令只用它们的基本名字表示,一个*表示这个命令不只一种类型,比如glColor*()表示设置当前颜色的所有变种。如果需要某一种类型,可以在后面加上相应的后缀,比如glVertex*v()表示所有的向量类型。

4. opengl的状态机模型

opengl是一个状态机,你把它放置在各种状态或者模型中保留原来的效果直到你去改变它。正如你所看到的,当前的颜色就是一个状态变量。你可以设置当前的颜色为白色,红色,或者任何其它颜色,然后每个被绘制的物体都是用那种颜色,直到你去改变该颜色。当前颜色只是opengl中需要状态变量中的一个。其它的状态变量还有,当前的视角,工程转移,线和多边形模式,多边形绘制模式,颜色选择,光的位置和特性,材料特质。许多这些状态变量都可以使用glEnable或者glDisable打开或者停止这些效果。

每个状态变量或者模式有一个默认值,在任何是时候你都可以向系统获取到这些当前值。你可以使用下面六个之一:glGetBooleanv(), glGetDoublev(), glGetFloatv(), glGetIntegerv(), glGetPointerv(), glEnabled().这些变量的选择取决于你需要获取的变量类型。一些变量还有更具体的类型,比如glGetLight*(), glGetError(),或者glGetPolygonStipple()。另外,你可以保存一系列值,使用glPushAttrib()
或者glPushClientAttrib()函数,临时修改它们。然后使用glPopAttrib()或者glPopClientAttrib()函数恢复。对于临时变量,你应该使用这些变量值,而不是使用问询值,因为这样做更有效率些。

查看附录B有完整的状态变量值,对于每个变量,附录中有glGet*()命令返回相应的变量值,返回这个变量属于那个类型,以及它的默认值。

5.opengl渲染管道

许多opengl的实现有相似的系列操作,一些的opengl渲染管道状态处理。这个过程,如1.2所示,虽然不是opengl的严格定义,但是提供了一个可靠的指南,指出opengl会做哪些事情。



如果你是三维图形的新人,下面的描述就像在一个烧水的房子中喝水。你现在可以跳过它们,但是当你阅读每个章节之后可以再来回顾它们。

下面的这章图表展示一个线性关系,opengl获取处理数据的过程。几何数据(顶点,线,多边形)经过一系列的处理,包括估值,每个定点操作;像素图形(像素,图像,位图)使用另外一个处理过程,在写入到帧缓存之前两种类型最后都经过相同的步骤(光栅化,和碎片化操作)。

现在你可以看到opengl渲染管道每个阶段更多的细节。

显示列表

所有的数据,不管他是描述几何图形的还是图像的,都可以保存在当前的显示列表中供后面使用。(将数据保存在显示列表中,然后马上处理数据,也可以叫做立即模式)。当一个显示列表开始执行的时候,数据从显示列表传送出去就像是数据直接从应用程序传输一样。(查看第7章或者显示列表的更多信息)。

估算

所有的基础图形最终都是通过顶点描述,曲线和表面是被其控制的顶点值和多项式函数控制。估算提供一个表示物体表面的方法。这个方法叫做多项式测绘,从控制顶点中它可以生产正常的表面,纹理坐标,颜色,以及空间变量值。(查看第12章学习更多的估算)

顶点处理

对于顶点值,下一个顶点处理过程,是将顶点处理成初步的图形。一些顶点数据(比如,空间坐标)被转换成4*4的浮点矩阵。空间坐标从一个三维的坐标中转换到屏幕上。(查看第三章获取更多的转置矩阵的的消息)

如果可以使用先进的特性,这个阶段的任务更多。如果纹理被使用,纹理左边会在这里生成和转移。如果光线被使用,光线计算也会在这里转换成相应的坐标,正常的表面,光源位置,或者其它的光信息产生颜色值。

图元装配

剪贴,图元装配中主要的部分,消除超过图形的几何部分。点装配只是简单的传递或者拒绝顶点。线和多边形装配可以增加其它的顶点,取决于线和多边形如何装配。

翻译也是乱翻译,干脆就不翻译其中的步骤了。

像素操作

纹理装配

光栅化

碎片化

6. opengl相关的库

opengl提供了强大的但是比较原始的渲染命令,所有的更高级的绘制方法都是通过这些命令实现的。同事,opengl不得不用底层系统提供的窗口。一些列的库可以简化你的编程任务,包括以下这些库:

GLU库包含一些底层的处理函数,包括位特殊的面向对象建立矩阵,多边形的光栅化,渲染物体表面,这个库作为opengl图形实现的一部分。glu的相关介绍在opengl参考手册中。最重要的一些glu方法在这个教程中介绍,它们关系到一些后续章节的讨论,比如整个11章都与glu有关,第12章也用到一些glu函数。

glut库是一个系统窗口独立库,是由mark写的,作用是隐藏不同窗口系统的复杂性,在他写的书中有详细的介绍。glut函数使用前缀glut,如何获取相关的源码可以去使用ftp获取。

Open Inventor是一个三维软件开发包,提供对象和方法创建三维的交互图形应用程序。

头文件

对于所有的opengl程序,需要包括gl.h头文件,对于使用glu库的文件来说,需要包括glu.h头文件,所有几乎opengl的每个文件都是以

#include <GL/gl.h>

#include <GL/glu.h>

如果你使用的是glut库管理你的窗口任务,你还需要包括

#include <GL/glut.h>

glut介绍

正如你知道的,opengl包括渲染命令,它的设计是独立于任何窗口系统或者是操作系统的。因此,它不包含打开窗口,从鼠标或者键盘读取事件的命令。不幸的是,如果没有打开窗口,它无法写一个完成的几何程序。还有一些更吸引人的程序需要有用户的输入以及操作系统的服务才能完成。在许多情况下,完成一个好的程序,这本书中使用glut来打开串口,检测输入和其它一些事情。如果你的系统中有opengl的实现,以及安装了glut,那么这些程序可以直接在你的电脑上运行,而不用去改变它。

另外,因为opengl绘制命令只是包括形成那些简单的几何图形(点,线,多边形),glut提供了一些函数创建更复杂的三维物体,比如圆柱,圆环,茶壶等。这样这样程序的输出会更好看(注意的是,glu也可以像glut创建一些三维的物体,比如圆柱,圆环)。

glut可能不适用更多功能的opengl程序,但是你可以发现它是学习opengl的一个好的开始,这个小节的剩余部分介绍glut一些常见的函数,以便于你可以阅读这本书的剩余章节,(查看附录Dhuoqu glut更多的函数知识)。

窗口管理

五个函数用于初始化窗口

glutInit(int *argc, char **argv)初始化glut,和处理命令行的参数,对于(X 窗口来说,比如-display -geometry参数可以使用),glut应该最早调用。

glutInitDisplayMode(unsigned int mode)指定是使用RGBA或者是颜色下标值模式,你也可以指定是使用单内存,或者是双内存窗口.(如果你是使用颜色下标值模式,你可以使用glutSetColor()加载相应的颜色进入颜色图谱中。最后,你可以使用这个函数使用相应的深度,颜色模式,叠加模式,比如glutInitDisplayMode(GLUT_DOUBLE|GLUT_RGB|GLUT_DEPTH)。

glutInitWindowPosition(int x, int y)相对于窗口的左上角指定窗口的位置。

glutInitWindowSize(int width, int size)指定窗口的大小,相对于像素来说的。

glutCreateWindow(char *string)使用opengl的内容创建一个窗口,它会返回一个新窗口的唯一确定值。值得注意的是,直到glutMainLoop()被调用之后,窗口才会显示在桌面上。

显示回调函数

glutDisplayFunc(void(*func)(void))是第一个也是最重要的事件回调函数,任何时候glut决定重新显示窗口的内容的时候,glutDisplayFunc()中注册的函数都会被回调。因此,你应该把所有你需要重新回调的函数放在显示回调函数中。

如果你的函数改变窗口内容,你应该调用glutPostRedisplay(void)函数,它会让glutMainLoop()在下一次回调中被调用。

运行示例

最后一件事情就是你需要调用glutMainLoop(void)函数,所有之前被创建的窗口,现在都会显示出来,所有对窗口的渲染都会生效。事件处理函数,会注册的回调函数都会被触发,一旦程序进入循环中,就不会再退出。

一个使用glut的简单opengl函数:hello.c
粘贴函数

#include <GL/gl.h>
#include <GL/glut.h>

void display(void)
{
/* clear all pixels */
glClear (GL_COLOR_BUFFER_BIT);

/* draw white polygon (rectangle) with corners at
* (0.25, 0.25, 0.0) and (0.75, 0.75, 0.0)
*/
glColor3f (1.0, 0.5, 1.0);
glBegin(GL_POLYGON);
glVertex3f (0.25, 0.25, 0.0);
glVertex3f (0.75, 0.25, 0.0);
glVertex3f (0.75, 0.75, 0.0);
glVertex3f (0.25, 0.75, 0.0);
glEnd();

/* don't wait!
* start processing buffered OpenGL routines
*/
glFlush ();
}

void init (void)
{
/* select clearing (background) color */
glClearColor (0.0, 0.0, 0.0, 0.0);

/* initialize viewing values */
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
}

/*
* Declare initial window size, position, and display mode
* (single buffer and RGBA). Open window with "hello"
* in its title bar. Call initialization routines.
* Register callback function to display graphics.
* Enter main loop and process events.
*/
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize (250, 250);
glutInitWindowPosition (100, 100);
glutCreateWindow ("hello");
init ();
glutDisplayFunc(display);
glutMainLoop();
return 0; /* ISO C requires main to return int. */
}

显示一个粉色的正方形



处理输入事件

你可以使用一些注册回调函数处理一些特定事件的发生

glutReshapeFunc(void(*func)(int w, int h))当窗口的大小改变时这是注册的函数会被调用。

glutKeyboardFunc(void (*func)(unsigned char key, int x, int y))和函数

glutMouseFunc(void (*func)(int button, int state, int x, int y)) 允许你当鼠标或者键盘事件发生时相应的函数会被调用。

glutMotionFunc(void (*func)(int x, int y))当鼠标按钮被按下且在移动时会调用相关的注册函数。

绘制三维物体

glut包含几个绘制三维物体的函数

cone

icosahedron

teapot

cube

octahedron

tetrahedron

dodecahedron

sphere

torus

你可以绘制上面这些物体使用线性帧或者是正常的表面。比如,绘制一个球体或者一个方体可以调用下面这些函数:

void glutWireCube(GLdouble size);

void glutSolidCube(GLdouble size);

void glutWireSphere(GLdouble radius, GLint slices, GLint stacks);

void glutSolidSphere(GLdouble radius, GLint slices, GLint stacks);

所有的这些模型都会以世界坐标系的起点开始绘制。

7. 动画

最激动人心的一件事情就是你可以绘制可以运动的物体,不管你是一个工程师,想看到你设计的各个面,或者是一个模拟的飞机模型,很明确的是电脑绘图中动画很重要。

在一个电影场景中,移动是通过在屏幕中一秒钟放24张连续的动画做的。尽管你是看的24张图片,但是在你的脑海中是一系列连续的动画,有些是用48帧表示,最关键的如何绘制每一帧,你会在每一帧中这样做

open_window();

for (i = 0; i < 1000000; i++) {

    clear_the_window();

    draw_frame(i);

    wait_until_a_24th_of_a_second_is_over();

}

大多数的opengl实现提供双缓冲的方式来完成两个颜色缓冲,一个在显示,另一个正在被绘制。当这个绘制的帧完成之后,这两个缓冲进行交换,因此刚刚在显示的缓冲现在正在被绘制。

一个修改后的处理过程如下所示:

open_window_in_double_buffer_mode();

for (i = 0; i < 1000000; i++) {

    clear_the_window();

    draw_frame(i);

    swap_the_buffers();

}

更新等待

对于一些opengl实现,除了一些简单的显示和绘制缓冲之外,swap_the_buffers()函数直到之前的函数全部绘制完成才会去切换。

移动 = 重绘+交换

你可以通过使用glut库中的
void glutSwapBuffers(void)函数来交换缓冲。

最后绘制一个不断旋转的正方形。

#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>

static GLfloat spin = 0.0;

void init(void)
{
glClearColor (0.0, 0.0, 0.0, 0.0);
glShadeModel (GL_FLAT);
}

void display(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glPushMatrix();
glRotatef(spin, 0.0, 0.0, 1.0);
glColor3f(1.0, 1.0, 1.0);
glRectf(-25.0, -25.0, 25.0, 25.0);
glPopMatrix();
glutSwapBuffers();
}

void spinDisplay(void)
{
spin = spin + 2.0;
if (spin > 360.0)
spin = spin - 360.0;
glutPostRedisplay();
}

void reshape(int w, int h)
{
glViewport (0, 0, (GLsizei) w, (GLsizei) h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-50.0, 50.0, -50.0, 50.0, -1.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}

void mouse(int button, int state, int x, int y)
{
switch (button) {
case GLUT_LEFT_BUTTON:
if (state == GLUT_DOWN)
glutIdleFunc(spinDisplay);
break;
case GLUT_MIDDLE_BUTTON:
if (state == GLUT_DOWN)
glutIdleFunc(NULL);
break;
default:
break;
}
}

/*
* Request double buffer display mode.
* Register mouse input callback functions
*/
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
glutInitWindowSize (250, 250);
glutInitWindowPosition (100, 100);
glutCreateWindow (argv[0]);
init ();
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMouseFunc(mouse);
glutMainLoop();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: