下载无exe源代码
下载带exe源代码
Code Project论坛上的一些用户问题让我意识到:互联网上的大多数OpenGL教程已经过时了。许多还在参考旧的NeHe教程,自从Jeff Molofee转向GameDev之后,这个教程就再也没有更新了。
从那时到现在,已经发布了Visual Studio 2013和2015,还有Windows 7, 8 和Windows10。 所以,是时候创建一些符合新变化的基础教程了。
我们的目标很简单 - 在OpenGL窗口中生成标准立方体旋转位图。
从Windows 98开始,所有的Windows版本都自带OpenGL,所以你无需另外安装。Windows 7,8和10自带OpenGL 3.3.0,但是有些显卡制造商可能在他们的驱动中集成高版本的OpenGL,例如OpenGL 4.5。所以你可以认为在当前的Windows机器中,至少会带有OpenGL 3.3。
试图使用NeHe教程的人遇到的第一个问题可能是GLAUX库已经废弃,Visual Studio中不再包含,Windows中也没有相应的DLL。废弃的原因是其功能可以使用标准Win32 API调用替换。在本系列文章中,我们会使用Win32 API调用。我看了所有的关于下载GLAUX头文件和编译成DLL的评论,我们将会避免这种没有前途的做法。
我尝试使用保持简单直白的原则( Keep It Simple Stupid (KISS) ),这部分代码不是对象(Objects)的或者框架化(Frameworks)的。不是因为我不相信这些东西,而是我们尽量让代码简单,并让它有最广泛的用户。
代码使用标准的 <TChar.h> 接口,因此它能被 Ansi,Unicode 和宽字符模式编译。我们讨论过不保持 KISS 原则,但这样就会限制编译模式,特别是非英语国家。因为这涉及到“char”类型与“TCHAR”的交换,一个很小的_T语句在字符串附近,我觉得它对于理解来说是有目共睹的。
首先,让我们在OpenGL窗口系统中编写伪代码,我们通常是如下编写:
初始化OpenGL(调用一次)
测量OpenGL视窗(首次调用)
重复
绘制场景
把场景迁移到屏幕上
直到视窗关闭
注意事项:如果视窗大小改变,那么测量的进程也会被调用
这个方式的原因是因为它会使得包括OpenGL部分灵活迁移到像MFC、WPF等框架上。另一个我所持的观点是把OpenGL数据输入到一个简单的结构上作为一个属性附加到视窗本身上。在这个教程中,人们想知道为什么只是简单的使用正常的全局变量,原因显而易见,在第二节课我们会使用多个OpenGL视窗和更多先进的多视窗。
这是我们第一次课程要使用到的OpenGLData数据结构。这里描述的代码片段在以后的课程中也会用到,而其他一些数据会根据需要而增减。在第一次课程中,我们会加载一张bitmap格式的图片作为纹理(结构中的glTexture),我会把纹理绘制在立方体的表面并旋转立方体(旋转使用了结构中的 xrot, yrot)。
typedef struct OpenGLData { HGLRC Rc; // 绘制的句柄 **(总是需要) GLuint glTexture; // 要绘制的纹理 GLfloat xrot; // X旋转 GLfloat yrot; // Y旋转 } GLDATABASE;
代码片段如上。
那些基础的数据结构通过SetProp API函数被附加到了窗口的句柄上,被定义为了一个string类型的静态变量。当我们需要获取这个数据结构(OpenGLData)时,可以随时通过调用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系统。返回的Render内容会被保留在我们的数据结构中。ReSizeGLScene会直接在调用Render之后,用它来设置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把它的场景绘制给render内容,在返回了之后,我们交换render的内容给PaintStructure设备,它可以给我们的窗口绘制OpenGL场景。接着,我们清理并退出。
诀窍是渲染过程不是持续运行的,它仅仅发生在 WM_PAINT 消息提供时,因此你希望这些东西动起来,你就需要在更改之后提供 WM_PAINT 消息。当我们启动定时器,更改旋转率,然后窗口还是无效,这时需要创建一个 WM_PAINT 消息让窗口重绘。
这个过程是很棒的东西,像构建一个 3D 对象编辑器,除了在快速移动的游戏中不是很好,在这种场景中需要快速不断渲染场景。在后面的课程中,我们将会处理这一过程并使用线程。
现在,运行程序并点击File菜单,选择load a bitmap并继续,你将会看到如下所示:
启动一个定时器,你的立方体应该开始旋转了,OpenGL在一个标准应用窗口中运行,这正是你想看到的。我们第一个OpenGL教程就这样完成了。
在我们接下来的教程中,我们将会在一个MDI应用中展开更多内容。