在 Windows 上手动设置 OpenGL
最后包含完整的示例代码
OpenGL 的 Windows 组件
WGL
WGL(可以发音为 wiggle )代表“Windows-GL”,如“Windows 和 OpenGL 之间的接口” - 来自 Windows API 的一组函数,用于与 OpenGL 通信。WGL 函数具有 wgl 前缀,其标记具有 WGL_ 前缀。
Microsoft 系统支持的默认 OpenGL 版本为 1.1。这是一个非常旧的版本(最近的版本是 4.5)。获取最新版本的方法是更新图形驱动程序,但你的图形卡必须支持这些新版本。
可以在此处找到 WGL 功能的完整列表。
图形设备接口(GDI)
GDI(今天更新为 GDI +)是一个 2D 绘图界面,允许你在 Windows 中绘制窗口。你需要 GDI 来初始化 OpenGL 并允许它与它交互(但实际上不会使用 GDI 本身)。
在 GDI 中,每个窗口都有一个设备上下文(DC) ,用于在调用函数时标识绘图目标(将其作为参数传递)。但是,OpenGL 使用自己的渲染上下文(RC) 。因此,DC 将用于创建 RC。
基本设置
创建一个窗口
因此,对于 OpenGL 中的操作,我们需要 RC,要获得 RC,我们需要 DC,要获得 DC,我们需要一个窗口。使用 Windows API 创建窗口需要几个步骤。这是一个基本例程,因此有关更详细的说明,你应该参考其他文档,因为这不是关于使用 Windows API。
这是 Windows 设置,因此必须包含 Windows.h
,程序的入口点必须是 WinMain
过程及其参数。该程序还需要链接到 opengl32.dll
和 gdi32.dll
(无论你使用的是 64 位还是 32 位系统)。
首先,我们需要使用 WNDCLASS
结构来描述我们的窗口。它包含有关我们要创建的窗口的信息:
/* REGISTER WINDOW */
WNDCLASS window_class;
// Clear all structure fields to zero first
ZeroMemory(&window_class, sizeof(window_class));
// Define fields we need (others will be zero)
window_class.style = CS_OWNDC;
window_class.lpfnWndProc = window_procedure; // To be introduced later
window_class.hInstance = instance_handle;
window_class.lpszClassName = TEXT("OPENGL_WINDOW");
// Give our class to Windows
RegisterClass(&window_class);
/* *************** */
有关每个字段含义的精确解释(以及完整的字段列表),请参阅 MSDN 文档。
然后,我们可以使用 CreateWindowEx
创建一个窗口。创建窗口后,我们可以获得其 DC:
/* CREATE WINDOW */
HWND window_handle = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,
TEXT("OPENGL_WINDOW"),
TEXT("OpenGL window"),
WS_OVERLAPPEDWINDOW,
0, 0,
800, 600,
NULL,
NULL,
instance_handle,
NULL);
HDC dc = GetDC(window_handle);
ShowWindow(window_handle, SW_SHOW);
/* ************* */
最后,我们需要创建一个从 OS 接收窗口事件的消息循环:
/* EVENT PUMP */
MSG msg;
while (true) {
if (PeekMessage(&msg, window_handle, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// draw(); <- there goes your drawing
SwapBuffers(dc); // To be mentioned later
}
/* ********** */
像素格式
OpenGL 需要了解有关窗口的一些信息,例如颜色位,缓冲方法等。为此,我们使用像素格式。但是,我们只能向操作系统建议我们需要什么样的像素格式,操作系统将提供最相似的支持,我们无法直接控制它。这就是为什么它只被称为描述符。
/* PIXEL FORMAT */
PIXELFORMATDESCRIPTOR descriptor;
// Clear all structure fields to zero first
ZeroMemory(&descriptor, sizeof(descriptor));
// Describe our pixel format
descriptor.nSize = sizeof(descriptor);
descriptor.nVersion = 1;
descriptor.dwFlags = PFD_DRAW_TO_WINDOW | PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL | PFD_GENERIC_ACCELERATED | PFD_DOUBLEBUFFER | PFD_SWAP_LAYER_BUFFERS;
descriptor.iPixelType = PFD_TYPE_RGBA;
descriptor.cColorBits = 32;
descriptor.cRedBits = 8;
descriptor.cGreenBits = 8;
descriptor.cBlueBits = 8;
descriptor.cAlphaBits = 8;
descriptor.cDepthBits = 32;
descriptor.cStencilBits = 8;
// Ask for a similar supported format and set it
int pixel_format = ChoosePixelFormat(dc, &descriptor);
SetPixelFormat(dc, pixel_format, &descriptor);
/* *********************** */
我们在 dwFlags
字段中启用了双缓冲,因此我们必须调用 SwapBuffers
以便在绘制后查看内容。
渲染上下文
之后,我们可以简单地创建渲染上下文:
/* RENDERING CONTEXT */
HGLRC rc = wglCreateContext(dc);
wglMakeCurrent(dc, rc);
/* ***************** */
请注意,一次只能有一个线程使用 RC。如果你希望稍后从另一个线程使用它,你必须在那里调用 wglMakeCurrent
再次激活它(这将在它当前处于活动状态的线程上停用它,依此类推)。
获得 OpenGL 功能
通过使用函数指针获得 OpenGL 函数。一般程序是:
- 以某种方式获取函数指针类型(本质上是函数原型)
- 声明我们想要使用的每个函数(使用其函数指针类型)
- 获得实际功能
例如,考虑 glBegin:
// We need to somehow find something that contains something like this,
// as we can't know all the OpenGL function prototypes
typedef void (APIENTRY *PFNGLBEGINPROC)(GLenum);
// After that, we need to declare the function in order to use it
PFNGLBEGINPROC glBegin;
// And finally, we need to somehow make it an actual function
(PFN
表示指向函数的指针,然后是 OpenGL 函数的名称,最后是 PROC
- 这是通常的 OpenGL 函数指针类型名称。)
这是在 Windows 上完成的。如前所述,微软只发布 OpenGL 1.1。首先,可以通过包含 GL/gl.h
找到该版本的函数指针类型。在那之后,我们声明我们打算使用的所有函数,如上所示(在头文件中执行它并将它们声明为 extern
将允许我们在加载它们之后使用它们,只需包含它)。最后,通过打开 DLL 来加载 OpenGL 1.1 函数:
HMODULE gl_module = LoadLibrary(TEXT("opengl32.dll"));
/* Load all the functions here */
glBegin = (PFNGLBEGINPROC)GetProcAddress("glBegin");
// ...
/* *************************** */
FreeLibrary(gl_module);
但是,我们可能想要比 OpenGL 1.1 更多一点。但 Windows 并没有为我们提供函数原型或导出函数。原型需要从 OpenGL 注册表中获取。我们感兴趣的文件有三个:GL/glext.h
,GL/glcorearb.h
和 GL/wglext.h
。
为了完成 Windows 提供的 GL/gl.h
,我们需要 GL/glext.h
。它包含(如注册表所述)“OpenGL 1.2 及以上兼容性配置文件和扩展接口”(稍后将详细介绍配置文件和扩展,我们将看到使用这两个文件实际上并不是一个好主意 )。
实际的功能需要通过 wglGetProcAddress
获得(不需要为这个人打开 DLL,它们不在那里,只需使用该功能)。有了它,我们可以从 OpenGL 1.2 及更高版本(但不是 1.1)获取所有功能。请注意,为了使其正常运行,必须创建 OpenGL 渲染上下文并使其成为当前。所以,例如,glClear
:
// Include the header from the OpenGL registry for function pointer types
// Declare the functions, just like before
PFNGLCLEARPROC glClear;
// ...
// Get the function
glClear = (PFNGLCLEARPROC)wglGetProcAddress("glClear");
我们实际上可以构建一个使用 wglGetProcAddress
和 GetProcAddress
的包装器 get_proc
程序:
// Get function pointer
void* get_proc(const char *proc_name)
{
void *proc = (void*)wglGetProcAddress(proc_name);
if (!proc) proc = (void*)GetProcAddress(gl_module, proc_name); // gl_module must be somewhere in reach
return proc;
}
所以要结束,我们将创建一个充满函数指针声明的头文件,如下所示:
extern PFNGLCLEARCOLORPROC glClearColor;
extern PFNGLCLEARDEPTHPROC glClearDepth;
extern PFNGLCLEARPROC glClear;
extern PFNGLCLEARBUFFERIVPROC glClearBufferiv;
extern PFNGLCLEARBUFFERFVPROC glClearBufferfv;
// And so on...
然后我们可以创建一个像 load_gl_functions
这样的程序,我们只调用一次,其工作原理如下:
glClearColor = (PFNGLCLEARCOLORPROC)get_proc("glClearColor");
glClearDepth = (PFNGLCLEARDEPTHPROC)get_proc("glClearDepth");
glClear = (PFNGLCLEARPROC)get_proc("glClear");
glClearBufferiv = (PFNGLCLEARBUFFERIVPROC)get_proc("glClearBufferiv");
glClearBufferfv = (PFNGLCLEARBUFFERFVPROC)get_proc("glClearBufferfv");
你们都准备好了! 只需包含带有函数指针和 GL 的标题。
更好的设置
OpenGL 配置文件
OpenGL 已经开发了 20 多年,开发人员始终严格遵守向后兼容性(BC) 。因此,添加新功能非常困难。因此,在 2008 年,它被分为两个概况。核心和兼容性。核心配置文件打破了 BC,有利于性能改进和一些新功能。它甚至完全删除了一些遗留功能。兼容性配置文件维护 BC 的所有版本低至 1.0,并且其中没有一些新功能。它仅用于旧的遗留系统,所有新应用程序都应使用核心配置文件。
因此,我们的基本设置存在问题 - 它只提供向后兼容 OpenGL 1.0 的上下文。像素格式也是有限的。有一种更好的方法,使用扩展。
OpenGL 扩展
对 OpenGL 的原始功能的任何添加都称为扩展。通常,它们可以使某些事情变得合法,扩展参数值范围,扩展 GLSL,甚至添加全新的功能。
有三个主要的扩展组:供应商,EXT 和 ARB。供应商扩展来自特定供应商,它们具有供应商特定标记,如 AMD 或 NV。EXT 扩展由多个供应商共同完成。一段时间后,它们可能会成为 ARB 扩展,这些都是官方支持的 ARB 和 ARB 批准的扩展。
要获取所有扩展的函数指针类型和函数原型,并且如前所述,来自 OpenGL 1.2 和更高版本的所有函数指针类型,必须从 OpenGL 注册表下载头文件。正如所讨论的,对于新的应用程序,最好使用核心配置文件,因此最好包括 GL/glcorearb.h
而不是 GL/gl.h
和 GL/glext.h
(如果你使用 GL/glcorearb.h
则不包括 GL/gl.h
)。
在 GL/wglext.h
中还有 WGL 的扩展。例如,获取所有支持的扩展名列表的功能实际上是扩展本身,wglGetExtensionsStringARB
(它返回一个大字符串,其中包含所有支持的扩展名的空格分隔列表)。
获取扩展也是通过 wglGetProcAddress
处理的,所以我们可以像以前一样使用我们的包装器。
高级像素格式和上下文创建
WGL_ARB_pixel_format
扩展允许我们创建高级像素格式。与以前不同,我们不使用结构。相反,我们传递了想要的属性列表。
int pixel_format_arb;
UINT pixel_formats_found;
int pixel_attributes[] = {
WGL_SUPPORT_OPENGL_ARB, 1,
WGL_DRAW_TO_WINDOW_ARB, 1,
WGL_DRAW_TO_BITMAP_ARB, 1,
WGL_DOUBLE_BUFFER_ARB, 1,
WGL_SWAP_LAYER_BUFFERS_ARB, 1,
WGL_COLOR_BITS_ARB, 32,
WGL_RED_BITS_ARB, 8,
WGL_GREEN_BITS_ARB, 8,
WGL_BLUE_BITS_ARB, 8,
WGL_ALPHA_BITS_ARB, 8,
WGL_DEPTH_BITS_ARB, 32,
WGL_STENCIL_BITS_ARB, 8,
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
0
};
BOOL result = wglChoosePixelFormatARB(dc, pixel_attributes, NULL, 1, &pixel_format_arb, &pixel_formats_found);
同样,WGL_ARB_create_context
扩展允许我们进行高级上下文创建:
GLint context_attributes[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 3,
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
0
};
HGLRC new_rc = wglCreateContextAttribsARB(dc, 0, context_attributes);
有关参数和功能的精确说明,请参阅 OpenGL 规范。
我们为什么不从他们开始呢?好吧,那是因为扩展允许我们这样做,并且为了获得扩展,我们需要 wglGetProcAddress
,但这只适用于有效的有效上下文。所以实质上,在我们能够创建我们想要的上下文之前,我们需要已经有一些上下文活动,并且它通常被称为虚拟上下文。
但是,Windows 不允许多次设置窗口的像素格式。因此,需要销毁窗口并重新创建窗口以应用新的东西:
wglMakeCurrent(dc, NULL);
wglDeleteContext(rc);
ReleaseDC(window_handle, dc);
DestroyWindow(window_handle);
// Recreate the window...
完整示例代码:
/* We want the core profile, so we include GL/glcorearb.h. When including that, then
GL/gl.h should not be included.
If using compatibility profile, the GL/gl.h and GL/glext.h need to be included.
GL/wglext.h gives WGL extensions.
Note that Windows.h needs to be included before them. */
#include <cstdio>
#include <Windows.h>
#include <GL/glcorearb.h>
#include <GL/wglext.h>
LRESULT CALLBACK window_procedure(HWND, UINT, WPARAM, LPARAM);
void* get_proc(const char*);
/* gl_module is for opening the DLL, and the quit flag is here to prevent
quitting when recreating the window (see the window_procedure function) */
HMODULE gl_module;
bool quit = false;
/* OpenGL function declarations. In practice, we would put these in a
separate header file and add "extern" in front, so that we can use them
anywhere after loading them only once. */
PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB;
PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB;
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB;
PFNGLGETSTRINGPROC glGetString;
int WINAPI WinMain(HINSTANCE instance_handle, HINSTANCE prev_instance_handle, PSTR cmd_line, int cmd_show) {
/* REGISTER WINDOW */
WNDCLASS window_class;
// Clear all structure fields to zero first
ZeroMemory(&window_class, sizeof(window_class));
// Define fields we need (others will be zero)
window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
window_class.lpfnWndProc = window_procedure;
window_class.hInstance = instance_handle;
window_class.lpszClassName = TEXT("OPENGL_WINDOW");
// Give our class to Windows
RegisterClass(&window_class);
/* *************** */
/* CREATE WINDOW */
HWND window_handle = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,
TEXT("OPENGL_WINDOW"),
TEXT("OpenGL window"),
WS_OVERLAPPEDWINDOW,
0, 0,
800, 600,
NULL,
NULL,
instance_handle,
NULL);
HDC dc = GetDC(window_handle);
ShowWindow(window_handle, SW_SHOW);
/* ************* */
/* PIXEL FORMAT */
PIXELFORMATDESCRIPTOR descriptor;
// Clear all structure fields to zero first
ZeroMemory(&descriptor, sizeof(descriptor));
// Describe our pixel format
descriptor.nSize = sizeof(descriptor);
descriptor.nVersion = 1;
descriptor.dwFlags = PFD_DRAW_TO_WINDOW | PFD_DRAW_TO_BITMAP | PFD_SUPPORT_OPENGL | PFD_GENERIC_ACCELERATED | PFD_DOUBLEBUFFER | PFD_SWAP_LAYER_BUFFERS;
descriptor.iPixelType = PFD_TYPE_RGBA;
descriptor.cColorBits = 32;
descriptor.cRedBits = 8;
descriptor.cGreenBits = 8;
descriptor.cBlueBits = 8;
descriptor.cAlphaBits = 8;
descriptor.cDepthBits = 32;
descriptor.cStencilBits = 8;
// Ask for a similar supported format and set it
int pixel_format = ChoosePixelFormat(dc, &descriptor);
SetPixelFormat(dc, pixel_format, &descriptor);
/* *********************** */
/* RENDERING CONTEXT */
HGLRC rc = wglCreateContext(dc);
wglMakeCurrent(dc, rc);
/* ***************** */
/* LOAD FUNCTIONS (should probably be put in a separate procedure) */
gl_module = LoadLibrary(TEXT("opengl32.dll"));
wglGetExtensionsStringARB = (PFNWGLGETEXTENSIONSSTRINGARBPROC)get_proc("wglGetExtensionsStringARB");
wglChoosePixelFormatARB = (PFNWGLCHOOSEPIXELFORMATARBPROC)get_proc("wglChoosePixelFormatARB");
wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)get_proc("wglCreateContextAttribsARB");
glGetString = (PFNGLGETSTRINGPROC)get_proc("glGetString");
FreeLibrary(gl_module);
/* ************** */
/* PRINT VERSION */
const GLubyte *version = glGetString(GL_VERSION);
printf("%s\n", version);
fflush(stdout);
/* ******* */
/* NEW PIXEL FORMAT*/
int pixel_format_arb;
UINT pixel_formats_found;
int pixel_attributes[] = {
WGL_SUPPORT_OPENGL_ARB, 1,
WGL_DRAW_TO_WINDOW_ARB, 1,
WGL_DRAW_TO_BITMAP_ARB, 1,
WGL_DOUBLE_BUFFER_ARB, 1,
WGL_SWAP_LAYER_BUFFERS_ARB, 1,
WGL_COLOR_BITS_ARB, 32,
WGL_RED_BITS_ARB, 8,
WGL_GREEN_BITS_ARB, 8,
WGL_BLUE_BITS_ARB, 8,
WGL_ALPHA_BITS_ARB, 8,
WGL_DEPTH_BITS_ARB, 32,
WGL_STENCIL_BITS_ARB, 8,
WGL_ACCELERATION_ARB, WGL_FULL_ACCELERATION_ARB,
WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB,
0
};
BOOL result = wglChoosePixelFormatARB(dc, pixel_attributes, NULL, 1, &pixel_format_arb, &pixel_formats_found);
if (!result) {
printf("Could not find pixel format\n");
fflush(stdout);
return 0;
}
/* **************** */
/* RECREATE WINDOW */
wglMakeCurrent(dc, NULL);
wglDeleteContext(rc);
ReleaseDC(window_handle, dc);
DestroyWindow(window_handle);
window_handle = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,
TEXT("OPENGL_WINDOW"),
TEXT("OpenGL window"),
WS_OVERLAPPEDWINDOW,
0, 0,
800, 600,
NULL,
NULL,
instance_handle,
NULL);
dc = GetDC(window_handle);
ShowWindow(window_handle, SW_SHOW);
/* *************** */
/* NEW CONTEXT */
GLint context_attributes[] = {
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 3,
WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,
0
};
rc = wglCreateContextAttribsARB(dc, 0, context_attributes);
wglMakeCurrent(dc, rc);
/* *********** */
/* EVENT PUMP */
MSG msg;
while (true) {
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT)
break;
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// draw(); <- there goes your drawing
SwapBuffers(dc);
}
/* ********** */
return 0;
}
// Procedure that processes window events
LRESULT CALLBACK window_procedure(HWND window_handle, UINT message, WPARAM param_w, LPARAM param_l)
{
/* When destroying the dummy window, WM_DESTROY message is going to be sent,
but we don't want to quit the application then, and that is controlled by
the quit flag. */
switch(message) {
case WM_DESTROY:
if (!quit) quit = true;
else PostQuitMessage(0);
return 0;
}
return DefWindowProc(window_handle, message, param_w, param_l);
}
/* A procedure for getting OpenGL functions and OpenGL or WGL extensions.
When looking for OpenGL 1.2 and above, or extensions, it uses wglGetProcAddress,
otherwise it falls back to GetProcAddress. */
void* get_proc(const char *proc_name)
{
void *proc = (void*)wglGetProcAddress(proc_name);
if (!proc) proc = (void*)GetProcAddress(gl_module, proc_name);
return proc;
}
使用带有 MinGW / Cygwin 的 g++ GLExample.cpp -lopengl32 -lgdi32
或带有 MSVC 编译器的 cl GLExample.cpp opengl32.lib gdi32.lib user32.lib
编译。但请确保 OpenGL 注册表中的标头位于包含路径中。如果没有,请使用 g++
的 -I
标志或 cl
的/I
,以告诉编译器它们在哪里。