您的当前位置:首页正文

第四节:Windows下的OpenGL OpenGL Win32 Wiggle

来源:帮我找美食网
第四节:Windows下的OpenGL: OpenGL+Win32 = Wiggle

这一节中你将学到:

窗口中不使用辅助库的OpenGL任务 你将用到以下函数 创建和使用rendering contexts 选择一种像素格式 回应窗口消息 使用双缓存 wglCreateContext, wglDeleteContext,wglMakeCurrent ChoosePixelFormat, SetPixelFormat WM_PAINT,WM_CREATE, WM_DESTROY,WM_SIZE SwapBuffers OpenGL就是单纯的图形API(编程接口),用户的交互和屏幕窗口处理都交给了操作系统去完成。为了和操作系统很好的配合,每一种操作系统都有自己对OpenGL的扩展,包括一些窗口管理和用户交互的一些函数。有必要提供一些设置缓冲模式、颜色深度和其它一些绘图特征。

对于微软的Windows操作系统,这种紧密的关系包括在六个新的wiggle函数添加到OpenGL中(称为wiggle是因为它们的前缀为wgl而不是gl),和五个新的Win32他可有才函数加入到Windows NT和95的GDI中。我们将在这一节中讲述这个gluing函数,并不用辅助库来创建我们的OpenGL框架。

在第三节中我们把辅助库作为学习OpenGL的C编程的基础知识。你已知道怎样画一些二维和三维的物体和指定坐标系统和透视,此时并不需考虑Windows编程的细节。现在该到我们看看OpenGL是怎样在Windows环境下工作的。除非你对单窗口、无菜单、无打印功能、无对话框,只有几个与用户交互的功能,否则你就要学怎样在你的Win32程序中使用OpenGL。

在本节的开始部分,我们将建立一个功能齐全的Windows应用程序,它可以利用操作系统的所有功能。你将会看到支持OpenGL程序的Windows操作系统的窗口有什么特点。你将会学到表现良好的OpenGL窗口怎样处理和处理哪些消息。本节介绍这些概念是循序渐进的,像我们用C建立的一个OpenGL初始模型将在我们将来的例子中实用。

到此为止,你不需要任何关于3D图形的预备知识,只需要一些C语言的基本知识。从这点而言,尽管如此,我们还是要假设你有入门级的Windows编程知识。如果对Windows很陌生,或你对Windows的程序框架很吃力、或对Windows的过程、消息很不熟悉,等等。我强烈建议你看一下附录B中推荐的书之后再看下文的内容。

在Windows中绘制窗口

用辅助库创建的只有一个窗口,而你自己Windows应用程序通常不止一个窗口。实际上,对话方框,菜单通常是一个Windows程序的基本组成部分。一个有用的程序几乎不可能只有一个窗口。因此OpenGL是怎样知道执行程序时要在哪里绘画呢?在我回答这个问题之前,让我们来回顾一下不使用OpenGL,Windows是怎样画图的。

GDI装置内容

不使用OpenGL在Windows中绘图就要用到Windows的GDI(图形装置接口)。每一个窗口都有一个装置内容来接收图形输出,每一个GDI函数都把装置内容作为函数参数来表明哪个窗口将会被绘图所影响。你可以有多个装置内容,但是每个窗口只能有一个。

在赠送的CD中有这样一个例程WINRECT绘制了一个蓝色背景和中间有一个红色方块的普通的窗口。程序的输出如图4-1所示。你会觉得眼熟,因为它跟我们第三节中第二个程序friendly.c输出的图形很像。与前面的程序不同的是,WINRECT是一个WINAPI程序。WINRECT的代码跟普通的Windows 编程代码很像,从WinMain函数开始并发出消息,然

后WndProc函数来为主窗口处理消息。

图4-1 Windows版的friendly.c

你要是精通Windows编程的话就应包括创建和显示一个窗口的细节。所以我只列出了绘制背景和方块的代码。

首先,我们必须创建一个蓝色和红色的画刷来填充和绘画。画刷的句柄声明为全局变量://Handles to GDI brushes we will use for drawing

HBRUSH hBlueBrush,hRedBrush;

然后在WinMain()主函数中用RGB宏来设置画刷为红色和蓝色:

//Create a blue and red brush for drawing and filling operations. hBlueBrush = CreateSolidBrush(RGB(0,0,255)); hRedBrush = CreateSolidBrush(RGB(255,0,0));

当指定了窗口的样式以后,就可给窗口的背景指定为用蓝色画刷填充了。窗口的尺寸位置与原来用auxInitPosition设置的一样在窗口被创建时指定: hWnd = CreateWindow(

szClassName,szAppName,

WS_OVERLAPPEDWINDOW,

CW_USEDEFAULT,CW_USEDEFAULT,

250,250,NULL,NULL,hInstance,0);

最后,在窗口处理函数WndProc中WM_PAINT消息内部加入下面的代码: case WM_PAINT: //Start painting

BeginPaint(hWnd,&ps); hdc = GetDC(hWnd); //Select and use red brush

hOldBrush = SelectObject(hdc,hRedBrush);

//Draw a rectangle filled with the currently selected brush Rectangle(hdc,100,100,150,150); //Deselect the brush

SelectObject(hdc,hOldBrush); //End painting

EndPaint(hWnd,&ps);

ReleaseDC(hWnd,hdc);

break;

当你在给OpenGL下这样一个结论之前,就是OpenGL也是用同样的方式来工作的,请记住:GDI是Windows专用的。其它的操作系统没有装置内容、窗口句柄。然而OpenGL是为各种操作系统和平台而设计的。只有在Windows操作系统中要添加一个装置内容的参数到OpenGL函数中去渲染你的OpenGL代码。

OpenGL渲染内容

为了使OpenGL的核心函数有很好的移植性,各种操作系统在执行OpenGL的命令之前都要提供一个当前渲染窗口。在Windows中,OpenGL包括在渲染内容中。就像装置内容存储GDI的关于绘图模式和命令的内容一样,渲染内容存储OpenGL的设置和命令。 你的程序中可以有不止一个的渲染内容,例如两个窗口使用不同的绘图模式、透视方法等。为了使OpenGL知道它的命令在哪个窗口中运行,在每个进程的任何时刻只有一个渲染内容被作为当前渲染内容。当一个渲染内容作为当前渲染内容时,它通常也和一个装置内容和一个特定的窗口一起被处理。现在OpenGL知道去渲染哪个窗口。图4-2表示了这个观念:

图4-2 OpenGL命令是怎样找到它的窗口的

使用Wiggle函数

渲染内容严格来说不是OpenGL的观念,只是Windows为了支持OpenGL而添加到Windows 中的API。实际上,新的Wiggle函数只是为支持OpenGL。渲染内容中最常用的三个函数是:

HGLRC wglCreateContext(HDC hDC);

BOOL wglDeleteContext(HGLRC hrc);

BOOL wglMakeCurrent(HDC hDC,HGLRC hrc);

创建和选择一个渲染内容

注意第一个新的数据类型HGLRC,它表示一个指向渲染内容的句柄。函数wglCreateContent()以GDI装置内容为参数,返回一个OpenGL渲染内容的句柄。像一个GDI的装置内容一样,渲染内容在你使用完毕后也要删除。函数wglDeleteContext()就是完成这个任务的,它的参数就是你要删除的渲染内容的句柄。

当从一个装置内容创建了一个渲染内容后,就可以在那个装置内容上绘图了。当函数wglMakeCurrent()把一个渲染内容置为当前时,是否在开始就指定渲染内容就是不很严格了。然而,创建渲染内容时的装置内容和使用渲染内容时装置内容的参数是一样,这些参数包括:颜色深度、缓存的定义、等等。这些都包含在像素格式(Pixel format)中。

把一个为装置内容设定为当前渲染内容不同于创建一个渲染内容,它们都有必须有相同的像素格式。你可通过把其它的渲染内容置为当前或调用wglMakeCurrent()函数,把渲染内容置为NULL来放弃正在使用的渲染内容。

使用OpenGL绘画

若你没编过什么GDI的程序,跟踪装置内容或渲染内容将是令人头疼的事情,但当你看到它是怎样来处理之后,你就会觉得很简单的。在老版本的16位的Windows编程中,为了处理更快,当你使用完装置内容后你就要释放装置内容。因为Windows一次只能记住五个装置内容。在新的32位的Windows操作系统中,没有了这些内部资源的限制,但并不是让我们大意,它是指一个窗口要创建一个私有的装置内容只有几条实施方案(window style WS_OWNDC),因此当我们处理完以后就要结束装置内容。

将来我们的会有动画,我们应避免重复调用GetDC()来使用渲染内容置为当前,因为代价在贵。另一个省时的办法就是在一个渲染内容创建之后就把它置为当前,并使它始终处于当前。如果只有一个OpenGL的窗口,将比重复调wglMakeCurrent更省时。

处理创建和删除渲染内容只需要在两个消息处加上相应的代码:WM_CREATE和WM_DESTROY。通常,在WM_CREATE消息中创建渲染内容,在WM_DESTROY消息中删除它。一个从使用OpenGL绘图的窗口处理过程中展示了怎样创建和删除渲染内容:

LRESTULT CALLBACK WndProc(HWND hWnd,….. {

static HGLRC hRC; static HDC hDC; switch(msg) { }

}

处理窗口的绘制的仍由WM_PAINT消息来完成。现在它其中可以把的OpenGL绘图命令分配到BeginPaint/EndPaint之间。使用OpenGL你只需将窗口窗客户区用效让它一直都有

case WM_CREATE:

hDeviceContext = GetDC(hWnd);

……

hRenderContext = wglCreateContext(hDC);

wglMakeCurrent(hDC,hRC); break;

wglMakeCurrent(hDC,NULL); wglDeleteContext(hRC); break;

case WM_DESTROY:

一个WM_PAINT消息,这里有一个处理WM_PAINT消息的框架: case WM_PAINT: { //OpenGL drawing code or you Render function called here. RenderScene();

ValidateRect(hWnd,NULL); }break;

为OpenGL准备Window

在M_PAINT消息中添加原来的代码和渲染函数已可以作出一个窗口程序,但是不要急,在创建渲染内容之前还有两个重要的准备工作。 窗口样式

为了在窗口中能用OpenGL绘图,窗口的样式必须设置为:WS_CLIPCHILDREN 和S_CLIPSIBLINGS,且不能包含CS_PARENTDC样式。这是因为窗口只有在装置内容设置为渲染内容之后才合适绘图,或窗口中用的是一种像素格式。因为WS_CLIPCHILDREN WS_CLIPSIBLINGS样式使用绘图函数去更新所有的子窗口。CS_PARENTDC样式使一个窗口继承它的父窗口的装置内容,因此是禁止的,因为渲染内容只能处理一个装置内容和窗口。如果这两个样式不指定的话,你将不能设置窗口的像素格式。像素格式是我们准备Windows OpenGL程序的最后一个细节。

像素格式

在窗口中使用OpenGL绘图还需要你选择一种像素格式。跟渲染内容一样,像素格式也不是OpenGL 的一部分,它是在Win32API中对用OpenGL的扩展。像素格式就是为设置装置内容的OpenGL属性,就像颜色和缓存深度,或窗口是否为双缓存。你在用装置内容创建渲染内容之前一定要设置像素格式。这是你将用到的两个函数:

int ChoosePixelFormat(HDC hDC,PIXELFORMATDESCRIPTOR *ppfd);

BOOL SetPixelFormat(HDC hDC,int iPixelFormat,PIXELFORMATDESCRIPTOR *ppfd); 设置像素格式分三步,首先你可以根据你想要的窗口特点设置格式中的变量;然后再把这个结构交给ChoosePixelFormat函数来处理。函数返回一个装置内容可用的像素格式的整型索引。索引再交给SetPixelFormat函数处理。处理过程如下: PIXELFORMATDESCRIPTOR pixelFormat; int nFormatIndex; HDC hDC;

// initialize pixelFormat structure …. ….

nFormatIndex = ChoosePixelFormat(hDC, &pixelFormat);

SetPixelFormat(hDC, nPixelFormat, &pixelFormat); ChoosePixelFormat()函数根据你的在PIXELFORMATDESRCIPTOT中的设置去匹配一个支持的像素格式。返回值是像素格式的标签。例如:你可要求一个像素格式有16百万色,但是硬件只支持256色。在这种情况下,返回值将会返回一个可能的相近的值。这个索引传递给SetPixelFormat()函数。

你将在下面找到PIXELFORMATDESCRIPTOR结构的细节。列表4-1为例子GLRECT中的设置PIXELFORMATDESCRIPTOR结构和设置像素格式的函数。

清单4-1 设置装置内容像素格式的高水平函数 /Select the pixel format for a given device context void SetDCPixelFormat(HDC hDC) { int nPixelFormat; static PIXELFORMATDESCRIPTOR pfd = {

sizeof(PIXELFORMATDESCRIPTOR), 1,

PFD_DRAW_TO_WINDOW| PFD_SUPPTOR_OPENGL | PFD_TYPE_RGBA, 24,

0,0,0,0,0,0, 0,0,

0,0,0,0,0, 32, 0,0,

PFD_MANI_PLANE, 0,0,0};

nPixelFormat = ChoosePixelFormat(hDC,&pfd); SetPixelFormat(hDC,nPixelFormat,&pfd);

}

正如这个例子中所示的,并不所有的结构变量都使用了。表4-1列出了清单4-1中设置的结构体变量。其它的可以设置为零。

表4-1 设置像素格式时所需设置的PIXELFORMATDESCRIPTOR结构体变量 结构成员 nSize nVersion dwFlags 描述 结构体的大小。设置为sizeof(PIXELFORMATDESCRIPTOR) 数据结构的版本。设置为1 指定像素格式属性的标签,设置为(PFD_DRAW_TO_WINDOW| PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER) 这表示装置内容不是一个位图内容,将会使用OpenGL,窗口为双缓存的。 像素数据类型,通常设置OpenGL使用RGBA模式或索引颜色模式。设置为 PFD_TYPE_RGBA为RGBA模式。 iPixelType 颜色的位数。这里设置为24位的,如果硬件不支持24位的,硬件将自动选择 nColorBits 它所支持的最大的颜色位数。 cDepthBits 缓存深度(Z轴)。设置的最大精度为32,通常16已经足够了。 图层类型。Windows中的OpenGL只有PFD_MAIN_PLANE。 iLayerType

回到反弹方块

现在我们已经有了足够的信息去创建一个不使用辅助库的OpenGL程序。程序代码如清单4-2所示,它的效果和第三节中BOUNCE2是一样的。从代码的长度你可看出辅助库做了很大的工作。

第三节中例子的函数RenderScene,ChangeSize,IdleFunction没有改动,因此在此处就省去了代码。上述函数和清单4-1一起组成了例程GLRECT。图4-3所示为相同的反弹方块,清单4-2所示为创建窗口的主函数和处理消息的函数。

图4-3 Windows 版的反弹方块

清单4-2:不用辅助库的动画方块(程序的全部代码WIN32 Application) //windows version bounce square.

#include #include #include

#define IDT_TIMER 101

HGLRC hRC; HDC hDC;

GLfloat x1 = 100.0f; GLfloat y1 = 100.0f; GLsizei rsize = 50; GLfloat windowWidth; GLfloat windowHeight; GLfloat xstep = 1.0f; GLfloat ystep = 1.0f;

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM); void SetDCPixelFormat(HDC hDC); void CALLBACK RenderScene(void);

void CALLBACK IdleFunction(void);

void CALLBACK ChangeSize(GLsizei width,GLsizei height);

//Entry point of all windows programs.

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow)

{

//Register Window style wc.style = CS_HREDRAW|CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0;

wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = NULL;

wc.hCursor = LoadCursor(NULL,IDC_ARROW); wc.lpszClassName= szClassName;

wc.hbrBackground= NULL; //No need for background brush for OpenGL window wc.lpszMenuName = NULL; if(!RegisterClass(&wc)) { }

MessageBox(NULL,\"Register class failed!\return FALSE;

MSG msg; HWND hWnd; WNDCLASS wc;

CHAR szAppName [] = \"Windows Version bouncing-eryar\"; CHAR szClassName[] = \"myOpenGLClass\";

hWnd = CreateWindow(

szClassName, szAppName,

WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,CW_USEDEFAULT, 250,250, NULL,NULL, hInstance, NULL);

if(!hWnd) { MessageBox(NULL,\"Create Window failed!\ }

ShowWindow(hWnd,nCmdShow); UpdateWindow(hWnd);

while(GetMessage(&msg,NULL,0,0)) { TranslateMessage(&msg);

return FALSE;

}

}

DispatchMessage(&msg);

return msg.wParam;

LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) { }

//Select the pixel format for a given device context void SetDCPixelFormat(HDC hDC) {

int nPixelFormat; static PIXELFORMATDESCRIPTOR pfd = { sizeof(PIXELFORMATDESCRIPTOR), switch(message) {

case WM_CREATE: //Window creation,setup for OpenGL hDC = GetDC(hWnd); //Store the device context

SetDCPixelFormat(hDC);//Select the pixel format

hRC = wglCreateContext(hDC); //Create the rendering context

wglMakeCurrent(hDC,hRC); //and make it current

SetTimer(hWnd,IDT_TIMER,1,NULL);//Create a timer that fires every millisecond

break;

case WM_SIZE: ChangeSize(LOWORD(lParam),HIWORD(lParam));

break;

RenderScene(); SwapBuffers(hDC);

ValidateRect(hWnd,NULL); break; case WM_PAINT:

case WM_TIMER: IdleFunction();

InvalidateRect(hWnd,NULL,FALSE); break;

case WM_DESTROY:

KillTimer(hWnd,IDT_TIMER); wglMakeCurrent(hDC,NULL); PostQuitMessage(0); break;

}

return DefWindowProc(hWnd,message,wParam,lParam);

}

1,

PFD_DRAW_TO_WINDOW| PFD_SUPPORT_OPENGL| PFD_DOUBLEBUFFER, PFD_TYPE_RGBA, 24, 0,0,0,0,0,0, 0,0,

0,0,0,0,0, 32,

0,0,

PFD_MAIN_PLANE,

0,0,0,0 };

//Choose a pixel format that best matches the described in pfd nPixelFormat = ChoosePixelFormat(hDC,&pfd); //Set the pixel format for the device context SetPixelFormat(hDC,nPixelFormat,&pfd);

void CALLBACK ChangeSize(GLsizei width,GLsizei height) { if(height == 0) }

void CALLBACK RenderScene(void) {

glClearColor(0.0f,0.0f,1.0f,1.0f);

glClear(GL_COLOR_BUFFER_BIT); glColor3f(1.0f,0.0f,0.0f); height = 1;

glViewport(0,0,width,height); glLoadIdentity(); if(width<=height) { windowWidth = 250; }

windowHeight= (GLfloat)250*height/width;

else { windowWidth = (GLfloat)250*width/height; }

windowHeight= 250;

glOrtho(0.0f,windowWidth,0.0f,windowHeight,-1.0f,1.0f);

glRectf(x1,y1,x1+rsize,y1+rsize);

glFlush(); }

void CALLBACK IdleFunction(void) {

if(x1>windowWidth-rsize||x1<0) xstep = -xstep;

if(y1>windowHeight-rsize||y1<0)

ystep = -ystep;

if(x1>windowWidth-rsize) x1 = windowWidth - rsize -1; if(y1>windowHeight-rsize) y1 = windowHeight - rsize-1; x1 += xstep;

y1 += ystep; }

如果你跟着我们讨论的内容,Windows版的反弹方块的程序将很好理解。让我们来看一下程序中值得关注的几点。 窗口的缩放

在第三节中我们基于辅助库的例子,当窗口尺寸改时辅助库调用changeSize函数。在我们新的例子中,我们当窗口尺寸改变时,就会发出WM_SIZE消息,我们自己调用ChangeSize函数。传递参数lParam的LOWORD和HIWORD。 lParam的LOWORD中包含了新的窗口宽度,HIWORD中包含了新的高度。

//Window is resized case WM_SIZE:

ChangeSize(LOWORD(lParam),HIWORD(lParam)); break;

TickTock,the Idle Clock

通过辅助库我们可以优雅地处理我们的IdleFunction函。这个函数当程序空闲时就被调用。我们也可模拟这个功能通过设置一个计时器Timer。代码如下:

//Create a timer that fires every millisecond SetTimer(hWnd,IDT_TIMER,1,NULL);

当窗口创建时就设置了一个计时器。Windows每毫秒就给OpenGL窗口发送一个WM_TIMER消息。通常,Windows可以发送这个消息当程序的消息队列中没有其它的消息。(可以参考Windows API Bible, James L.Conger , Published by Waite Group Press )当WndProc收到WM_TIMER消息时,就会执行下面的代码:

case WM_TIMER:

IdleFunction();

InvalidateRect(hWnd,NULL,FALSE);

break;

函数IdleFunction和BOUNCE2中的是相同的,除了这里没有调用RenderScene()。窗口被重画,当调用InvalidateRect,这将使Windows发送一个WM_PAINT消息。

灯光、摄影、ACTION!

现在一切都已就绪,该到行动的时候了。渲染OpenGL代码放到WM_PAINT处理。在这里调用RenderScene(同样,我们也从BOUNCE2中借用一下):

case WM_PAINT: RenderScene();

SwapBuffers(hDC); ValidateRect(hWnd,NULL);

break;

这里,我们发现了一个Windows GDI的函数:SwapBuffers。这个函数和辅助库中的函数auxSwapBuffers的功能是一样的。唯一的参数是装置内容。注意这里的装置内容必须设置为PFD_DOUBLEBUFFER,否则这个函数将会失效。

就是这些了!现在你得到了一个程序框架,你可以在其中处理你的OpenGL绘画。它将很灵活的维护一个窗口的各种属性,包括移动、调整窗口尺寸、等等。当然将来你也可以用它来创建一个功能齐全的程序,包括窗口、菜单、等等。

总结

在这一节中,你应该掌握了OpenGL 的Windows程序框架。根据GDI介绍了渲染内容。也讲了在创建使用渲染内容之前选择和设置像素格式。 索引 略

因篇幅问题不能全部显示,请点此查看更多更全内容

Top