在 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
,以告訴編譯器它們在哪裡。