现代OpenGL入门教程

在互联网上,关于OpenGL教程大多已经过时,许多教程仍然引用了NeHe的旧教程,自从Jeff Molofee将他的网站维护权交给GameDev之后,这些教程就没有更新过。自那时以来,已经发布了Visual Studio2013和2015,以及Windows 7、8和10。因此,似乎是时候至少尝试创建一些与这些变化保持一致的基础教程了。

目标很简单——在OpenGL窗口中生成一个标准的旋转位图立方体。自Windows 98以来的所有Windows版本都原生支持OpenGL,不需要添加或做任何事情。Windows 7、8和10都默认运行OpenGL版本3.3.0,但一些显卡制造商可能会将其提升到4.5版本。然而,可以假设任何当前的Windows机器上至少有OpenGL版本3.3。

尝试使用NeHe教程的人遇到的第一个问题是GLAUX库已经被弃用,不再存在于Visual Studio中,也不作为Windows的DLL存在。这样做的原因是这些函数可以很容易地被标准的Win32 API调用所替代,在本系列文章中,将使用这个过程。看到很多评论都在说去下载GLAUX的头文件和编译的DLL,但会避免这样做,因为它没有未来。

将尝试使用“保持简单”(KISS)的方法,这种方法的一部分是编程代码没有对象或框架。这并不是因为不相信这些东西有优点,而是为了让代码尽可能简单,以适应最广泛的受众。

代码使用了标准的接口,因此它可以在Ansi、Unicode和Wide字符模式下编译。考虑过不这样做,以符合KISS原则,但觉得不这样做会限制编译模式和选项,特别是对于非英语国家。因为所有涉及到的只是将"char"类型替换为"TCHAR",并且在文本字符串周围使用一些_T语句,觉得这不会从理解上减去太多。

使用代码

首先,让看看将要使用的OpenGL窗口系统的伪代码,将像这样使用它:

1.) 初始化OpenGL(只调用一次) 2.) 缩放OpenGL视口(初始调用) repeat 3.) 绘制场景 4.) 将场景传输到屏幕 until 窗口关闭

注意:如果窗口大小发生变化,也会调用缩放过程。这种方法的原因是它将使OpenGL部分灵活,包括迁移到MFC、WPF等框架。

使用的另一个概念是将所有OpenGL数据放入一个简单的结构中,该结构作为属性附加到窗口本身。在本教程中,人们可能会想知道为什么要这样做,因为使用普通的全局变量会很容易。在第二课中,当有多个OpenGL窗口,然后在随后的课程中有更高级的多个视口时,原因将变得明显。

这是第一课的OpenGLData。在以后的课程中,渲染上下文将始终存在,但所有其他数据将根据需要而变化。在第一课中,将加载一个位图作为纹理(在结构中的gltexture),将把这个纹理放在一个立方体的侧面上,并旋转它(xrot、yrot用于旋转)。

typedef struct OpenGLData { HGLRC Rc; // 渲染上下文 ** 总是需要 GLuint glTexture; // 要绘制的纹理 GLfloat xrot; // X旋转 GLfloat yrot; // Y旋转 } GLDATABASE;

这个基本数据结构通过使用SetProp API调用来附加到窗口句柄,并通过使用GetProp调用来检索。

static const TCHAR* DATABASE_PROPERTY = _T("OurDataStructure");

这是创建并附加数据结构到窗口句柄的代码:

GLDATABASE* db = (GLDATABASE*) malloc(sizeof(GLDATABASE)); // 分配结构 db->Rc = InitGL(Wnd); // 初始化OpenGL持有渲染上下文 db->glTexture = 0; // 纹理归零 db->xrot = 0.0f; // 归零X旋转 db->yrot = 0.0f; // 归零Y旋转 SetProp(Wnd, DATABASE_PROPERTY, (HANDLE) db); // 数据结构到窗口属性

任何时候,想要访问数据,只需做一个简单的检索调用:

GLDATABASE* db = (GLDATABASE*) GetProp(Wnd, DATABASE_PROPERTY); // 获取数据结构

所有这些都说了,在这一课中有一个标准的窗口应用程序骨架。当应用程序窗口被创建时,WM_CREATE将用于初始化OpenGL系统。返回的渲染上下文将保存在数据结构中。ReSizeGLScene直接在它之后被调用,以将OpenGL系统的初始大小设置为创建的窗口大小。

随着正常窗口的运行,只需要处理窗口的WM_PAINT消息,代码如下:

case WM_PAINT: { // WM_PAINT消息 PAINTSTRUCT Ps; GLDATABASE* db = (GLDATABASE*) GetProp(Wnd, DATABASE_PROPERTY); // 获取数据库 BeginPaint(Wnd, &Ps); // 开始绘制 DrawGLScene(db, Ps.hdc); // 绘制OpenGL场景 SwapBuffers(Ps.hdc); // 交换缓冲区 EndPaint(Wnd, &Ps); // 结束绘制 return 0; }

它从正常的BeginPaint调用开始,然后调用OpenGL将其场景绘制到其渲染上下文中。在返回时,将渲染上下文绘制到PaintStructure设备上下文中,这将把OpenGL场景绘制到窗口上。然后清理并退出。

这里的诀窍是渲染过程不是持续运行的,它只在提供WM_PAINT消息时发生,所以如果想动画化东西,需要在变化后提供WM_PAINT消息。当激活计时器时,它会改变旋转,然后使窗口无效,这正是这样做的,创建了一个WM_PAINT消息,使窗口重绘。

这种方法对于构建3D对象编辑器等东西很好,但对于需要快速连续渲染场景的快速移动游戏来说并不好。在后续课程中,将处理这个过程,并使用线程。

现在,运行程序,并使用文件菜单选择加载一个位图,会看到类似的东西:

现在启动一个计时器,立方体应该开始旋转,这就是本课的内容。所以,完成了第一个OpenGL教程,OpenGL在标准应用程序窗口中运行。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485