NeHe的OpenGL教程3(Bang翻译Delphi版)-如何给图形着色
作为第二课的扩展,我将教你如何使用颜色。你将理解两种着色模式,在左图中,三角形用的是光滑着色,四边形用的是平面着色。如下图:
program lesson3a;
{
OpenGL DelphiXE
出处:根据NeHe代码翻译而来(http://nehe.gamedev.net/)
作者:帅宏军 shuaihj@163.com
}
uses
Windows,
Messages,
OpenGL;
// 全局变量
var
h_Rc: HGLRC; // 窗口着色描述表句柄
h_Dc: HDC; // OpenGL渲染描述表句柄
h_Wnd: HWND; // 保存我们的窗口句柄
keys: array [0..255] of BOOL; // 保存键盘按键的数组
Active: bool = true; // 窗口的活动标志,缺省为TRUE
FullScreen:bool = true; // P全屏标志缺省,缺省设定成全屏模式
// 重置OpenGL窗口大小
procedure ReSizeGLScene(Width: GLsizei; Height: GLsizei);
begin
if (Height=0) then // 防止被零除
Height:=1; // 将Height设为1
glViewport(0, 0, Width, Height); // 重置当前的视口(和窗口大小相当)
glMatrixMode(GL_PROJECTION); // 指定"投影矩阵堆栈"是下一个矩阵操作的目标
glLoadIdentity(); // 重置当前指定的矩阵为单位矩阵
gluPerspective(45.0,Width/Height,0.1,100.0); // 设置视口的大小
glMatrixMode(GL_MODELVIEW); // 指定"模型视图矩阵堆栈"是下一个矩阵操作的目标
glLoadIdentity; // 重置当前指定的矩阵为单位矩阵
end;
// 初始化OpenGL所有设置
function InitGL:bool;
begin
glShadeModel(GL_SMOOTH); // 采用光滑着色(绘制指定两点间其他点颜色时使用过渡色)
glClearColor(0.0, 0.0, 0.0, 0.5); // 指定清除颜色缓存时使用的颜色值(黑色,0.5透明)
glClearDepth(1.0); // 指定清除深度缓存时使用的深度值
glEnable(GL_DEPTH_TEST); // 启用深度测试,对深度缓存中符合"深度比较算法"要求的像素进行重绘
glDepthFunc(GL_LEQUAL); // 指定"深度比较算法"
glHint(GL_PERSPECTIVE_CORRECTION_HINT,GL_NICEST); // 告诉系统对透视进行最高质量修正
Result:=true;
end;
// 绘制OpenGL场景(任何您所想在屏幕上显示的东东都将在此段代码中出现)
function DrawGLScene():bool;
begin
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); // 根据glClearColor和glClearDepth指定的值清除颜色和深度缓存
glLoadIdentity(); // 重置当前指定的矩阵为单位矩阵
//你自己的OpengGL绘制代码
//当您调用glLoadIdentity()之后,您实际上将当前点移到了屏幕中心,
//X坐标轴从左至右,Y坐标轴从下至上,Z坐标轴从里至外。
//OpenGL屏幕中心的坐标值是X和Y轴上的0.0f点。
//中心左面的坐标值是负值,右面是正值。移向屏幕顶端是正值,移向屏幕底端是负值。移入屏幕深处是负值,移出屏幕则是正值。
glTranslatef(-1.5,0.0,-6.0); // 左移 1.5 单位,并移入屏幕 6.0
//因为每个顶点有不同的颜色,因此看起来颜色从每个角喷出,并刚好在三角形的中心汇合,
//三种颜色相互混合。这就是平滑着色
glBegin(GL_TRIANGLES); // 绘制三角形(逆时针画出来的三角形才是正面朝着我们的)
glColor3f(1.0,0.0,0.0); // 设置当前色为红色
glVertex3f(-1.0,-1.0, 0.0); // 左下顶点(//glVertex 的第一个参数是X坐标,然后依次是Y坐标和Z坐标)
glColor3f(0.0,1.0,0.0); // 设置当前色为绿色
glVertex3f( 1.0,-1.0, 0.0); // 右下顶点
glColor3f(0.0,0.0,1.0); // 设置当前色为蓝色
glVertex3f( 0.0, 1.0, 0.0); // 上顶点
glEnd(); // 三角形绘制结束
//在屏幕的左半部分画完三角形后,我们要移到右半部分来画正方形。
//这次右移,所以X坐标值为正值。因为前面左移了1.5个单位,
//这次要先向右移回屏幕中心(1.5个单位),再向右移动1.5个单位。总共要向右移3.0个单位。
glTranslatef(3.0,0.0,0.0); // 右移3单位
glColor3f(0.5,0.5,1.0); // 一次性将当前色设置为蓝色
glBegin(GL_QUADS); // 绘制正方形(逆时针画出来的正方形才是正面朝着我们的)
glVertex3f(-1.0,-1.0, 0.0); // 左下
glVertex3f( 1.0,-1.0, 0.0); // 右下
glVertex3f( 1.0, 1.0, 0.0); // 右上
glVertex3f(-1.0, 1.0, 0.0); // 左上
glEnd(); // 正方形绘制结束
Result := true;
end;
//处理所有的窗口消息。当我们注册好窗口类之后,程序跳转到这部分代码处理窗口消息
function WndProc(hWnd: HWND; // 窗口的句柄
message: UINT; // 窗口的消息
wParam: WPARAM; // 附加的消息内容
lParam: LPARAM): // 附加的消息内容
LRESULT; stdcall;
var
scrWidth,scrHeight: integer;
rect: TRect;
begin
if message=WM_SYSCOMMAND then // 监视系统中断命令
begin
case wParam of
SC_SCREENSAVE,SC_MONITORPOWER: // 屏保要运行或显示器要进入节电模式
begin
result:=0; // 禁止命令执行
exit;
end;
end;
end;
case message of
WM_CREATE: // 监视构建窗体消息
begin
//获得屏幕尺寸
scrWidth := GetSystemMetrics(SM_CXSCREEN);
scrHeight := GetSystemMetrics(SM_CYSCREEN);
//获取窗体尺寸
GetWindowRect(hWnd,&rect);
rect.left := (scrWidth-rect.right) DIV 2;
rect.top := (scrHeight-rect.bottom) DIV 2;
//设置窗体位置(屏幕居中)
SetWindowPos(hWnd,HWND_TOP,rect.left,rect.top,rect.right,rect.bottom,SWP_SHOWWINDOW);
result:=0; // 返回消息循环
end;
WM_ACTIVATE: // 监视窗口激活消息
begin
if (Hiword(wParam)=0) then // 检查最小化状态
active:=true // 程序处于激活状态
else
active:=false; // 程序不再激活
Result:=0; // 返回消息循环
end;
WM_CLOSE: // 监视窗口关闭消息
Begin
PostQuitMessage(0); // 发出退出消息
result:=0 // 返回消息循环
end;
WM_KEYDOWN: // 监视键盘有键按下消息
begin
keys[wParam] := TRUE; // 如果是,设为TRUE
result:=0; // 返回消息循环
end;
WM_KEYUP: // 监视键盘有键抬起消息
begin
keys[wParam] := FALSE; // 如果是,设为FALSE
result:=0; // 返回消息循环
end;
WM_SIZE: // 监视窗口尺寸改变消息
begin
ReSizeGLScene(LOWORD(lParam),HIWORD(lParam)); // 重置OpenGL窗口大小
result:=0; // 返回消息循环
end
else
// 其余无关的消息被传递给DefWindowProc,让Windows自行处理
begin
Result := DefWindowProc(hWnd, message, wParam, lParam);
end;
end;
end;
//正常销毁窗口(在程序退出之前调用,依次释放着色描述表,设备描述表和窗口句柄)
procedure KillGLWindow;
begin
if FullScreen then // 处于全屏模式吗?
begin
ChangeDisplaySettings(devmode(nil^),0); // 回到原始桌面
showcursor(true); // 显示鼠标指针
end;
if h_rc<> 0 then // 拥有OpenGL渲染描述表吗?
begin
if (not wglMakeCurrent(h_Dc,0)) then // 释放DC和RC描述表
MessageBox(0,'Release of DC and RC failed.',' 关闭错误',MB_OK or MB_ICONERROR);
if (not wglDeleteContext(h_Rc)) then // 删除RC
begin
MessageBox(0,'释放RC失败.',' 关闭错误',MB_OK or MB_ICONERROR);
h_Rc:=0; // 将RC设为 NULL
end;
end;
if (h_Dc=1) and (releaseDC(h_Wnd,h_Dc)<>0) then // 释放 DC
begin
MessageBox(0,'释放DC失败.',' S关闭错误',MB_OK or MB_ICONERROR);
h_Dc:=0; // 将 DC 设为 NULL
end;
if (h_Wnd<>0) and (not destroywindow(h_Wnd))then // 销毁窗口
begin
MessageBox(0,'释放窗口句柄失败.',' 关闭错误',MB_OK or MB_ICONERROR);
h_Wnd:=0; // 将 hWnd 设为 NULL
end;
if (not UnregisterClass('OpenGL',hInstance)) then // 注销类
begin
MessageBox(0,'不能注销窗口类.','关闭错误',MB_OK or MB_ICONINFORMATION);
end;
end;
//创建我们的OpenGL窗口
function CreateGlWindow(title:Pchar; width,height,bits:integer;FullScreenflag:bool):boolean stdcall;
var
Pixelformat: GLuint; // 当我们要求Windows为我们寻找相匹配的象素格式时,Windows寻找结束后将模式值保存在变量PixelFormat中
wc:TWndclass; // 窗口类结构
dwExStyle:dword; // 扩展窗口风格
dwStyle:dword; // 窗口风格
pfd: pixelformatdescriptor; // 象素格式描述
dmScreenSettings: Devmode; // 设备模式
h_Instance:hinst; // 窗口的实例
WindowRect: TRect; // 取得矩形的左上角和右下角的坐标值
begin
//取得矩形的左上角和右下角的坐标值。
//我们将使用这些值来调整我们的窗口使得其上的绘图区的大小恰好是我们所需的分辨率的值。
//通常如果我们创建一个640x480的窗口,窗口的边框会占掉一些分辨率的值。
WindowRect.Left := 0; // 将Left 设为 0
WindowRect.Top := 0; // 将Right 设为要求的宽度
WindowRect.Right := width; // 将Top 设为 0
WindowRect.Bottom := height; // 将Bottom 设为要求的高度
FullScreen:=FullScreenflag; // 设置全局全屏标志
//我们取得窗口的实例,然后定义窗口类
h_instance:=GetModuleHandle(nil); // 取得我们窗口的实例
with wc do
begin
style := CS_HREDRAW or CS_VREDRAW or CS_OWNDC; // 移动时重画,并为窗口取得DC
lpfnWndProc:=@WndProc; // WndProc消息处理函数回调
cbClsExtra:=0; // 无额外窗口数据
cbWndExtra:=0; // 无额外窗口数据
hInstance:=h_Instance; // 设置窗口实例
hIcon:=LoadIcon(0,IDI_WINLOGO); // 装入缺省图标
hCursor:=LoadCursor(0,IDC_ARROW); // 装入鼠标指针
hbrBackground:=0; // GL不需要背景
lpszMenuName:=nil; // 不需要菜单
lpszClassName:='OpenGl'; // 设定类名字
end;
//注册窗口类
if RegisterClass(wc)=0 then // 注册窗口类
begin
MessageBox(0,'注册窗口失败.','错误',MB_OK or MB_ICONERROR);
Result:=false;
exit;
end;
//尝试全屏模式
if FullScreen then
begin
//分配用于存储视频设置的空间;设定屏幕的宽,高,色彩深度
ZeroMemory( @dmScreenSettings, sizeof(dmScreenSettings) ); // 初始化内存
with dmScreensettings do //设备模式
begin
dmSize := sizeof(dmScreenSettings); // Devmode 结构的大小
dmPelsWidth := width; // 所选屏幕宽度
dmPelsHeight := height; // 所选屏幕高度
dmBitsPerPel := bits; // 每象素所选的色彩深度
dmFields := DM_BITSPERPEL or DM_PELSWIDTH or DM_PELSHEIGHT;
end;
// 尝试设置显示模式并返回结果。注: CDS_FULLSCREEN 移去了状态条。
if (ChangeDisplaySettings(dmScreenSettings, CDS_FULLSCREEN))<>DISP_CHANGE_SUCCESSFUL THEN
Begin
// 若全屏模式失败,提供两个选项:退出或在窗口内运行
if MessageBox(0,'全屏模式在当前显卡上设置失败!\n使用窗口模式?'
,'OpenGL',MB_YESNO or MB_ICONEXCLAMATION)= IDYES then
FullScreen:=false // 选择窗口模式(Fullscreen=FALSE)
else
begin
// 弹出一个对话框,告诉用户程序结束
MessageBox(0,'程序将被关闭.','错误',MB_OK or MB_ICONERROR);
Result:=false; // 退出并返回 FALSE
exit;
end;
end;
end;
//由于全屏模式可能失败,用户可能决定在窗口下运行,
//我们需要在设置屏幕/窗口之前,再次检查fullscreen的值是TRUE或FALSE
if FullScreen then
begin
dwExStyle:=WS_EX_APPWINDOW; // 扩展窗体风格(窗体可见时处于最前面)
dwStyle:=WS_POPUP or WS_CLIPSIBLINGS or WS_CLIPCHILDREN; // 窗体风格(没有边框)
Showcursor(false); // 隐藏鼠标指针
end
else
begin
dwExStyle:=WS_EX_APPWINDOW or WS_EX_WINDOWEDGE; // 扩展窗体风格(增强窗体的3D感观)
dwStyle:=WS_OVERLAPPEDWINDOW or WS_CLIPSIBLINGS or WS_CLIPCHILDREN; // 窗体风格(带标题栏、可变大小的边框、菜单和最大化/最小化按钮的窗体)
end;
AdjustWindowRectEx(WindowRect,dwStyle,false,dwExStyle); // 调整窗口达到真正要求的大小
// 开始创建窗口并检查窗口是否成功创建
H_wnd:=CreateWindowEx(dwExStyle, // 扩展窗体风格
'OpenGl', // 类名字
Title, // 窗口标题
dwStyle, // 窗体风格
0,0, // 窗口位置
WindowRect.Right-WindowRect.Left, // 计算调整好的窗口宽度
WindowRect.Bottom-WindowRect.Top, // 计算调整好的窗口高度
0, // 无父窗口
0, // 无菜单
hinstance, // 窗口实例
nil); // 不向WM_CREATE传递任何东东
if h_Wnd=0 then // 窗口是否正常创建
begin
KillGlWindow(); // 重置显示区
MessageBox(0,'不能创建一个窗口设备描述表.','错误',MB_OK or MB_ICONEXCLAMATION);
Result:=false; // 返回 FALSE
exit;
end;
//描述象素格式
with pfd do
begin
nSize:= SizeOf( PIXELFORMATDESCRIPTOR ); // 象素描述符的大小
nVersion:= 1; // 版本号
dwFlags:= PFD_DRAW_TO_WINDOW // 格式必须支持窗口
or PFD_SUPPORT_OPENGL // 格式必须支持OpenGL
or PFD_DOUBLEBUFFER; // 格式必须支持双缓冲
iPixelType:= PFD_TYPE_RGBA; // 申请 RGBA 格式
cColorBits:= bits; // 选定色彩深度
cRedBits:= 0; // 忽略的色彩位
cRedShift:= 0;
cGreenBits:= 0;
cBlueBits:= 0;
cBlueShift:= 0;
cAlphaBits:= 0; // 无Alpha缓存
cAlphaShift:= 0; // 忽略Shift Bit
cAccumBits:= 0; // 无累加缓存
cAccumRedBits:= 0; // 忽略聚集位
cAccumGreenBits:= 0;
cAccumBlueBits:= 0;
cAccumAlphaBits:= 0;
cDepthBits:= 16; // 16位 Z-缓存 (深度缓存)
cStencilBits:= 0; // 无蒙板缓存
cAuxBuffers:= 0; // 无辅助缓存
iLayerType:= PFD_MAIN_PLANE; // 主绘图层
bReserved:= 0; // Reserved
dwLayerMask:= 0; // 忽略层遮罩
dwVisibleMask:= 0;
dwDamageMask:= 0;
end;
//尝试取得OpenGL设备描述表
h_Dc := GetDC(h_Wnd);
if h_Dc=0 then
begin
KillGLWindow(); // 重置显示区
MessageBox(0,'不能创建一种相匹配的像素格式.','错误',MB_OK or MB_ICONEXCLAMATION);
Result:=false; // 返回 FALSE
exit;
end;
//找到对应与此前我们选定的象素格式的象素格式
PixelFormat := ChoosePixelFormat(h_Dc, @pfd);
if (PixelFormat=0) then
begin
KillGLWindow(); // 重置显示区
MessageBox(0,'不能找到像素格式.','错误',MB_OK or MB_ICONEXCLAMATION);
Result:=false; // 返回 FALSE
exit;
end;
//尝试设置象素格式
if (not SetPixelFormat(h_Dc,PixelFormat,@pfd)) then
begin
KillGLWindow(); // 重置显示区
MessageBox(0,'不能设置像素格式.','错误',MB_OK or MB_ICONEXCLAMATION);
Result:=false; // 返回 FALSE
exit;
end;
//尝试取得着色描述表
h_Rc := wglCreateContext(h_Dc); // 尝试取得着色描述表
if (h_Rc=0) then
begin
KillGLWindow(); // 重置显示区
MessageBox(0,'不能创建OpenGL渲染描述表.','错误',MB_OK or MB_ICONEXCLAMATION);
Result:=false; // 返回 FALSE
exit;
end;
//激活着色描述表
if (not wglMakeCurrent(h_Dc, h_Rc)) then
begin
KillGLWindow(); // 重置显示区
MessageBox(0,'不能激活当前的OpenGL渲然描述表.','错误',MB_OK or MB_ICONEXCLAMATION);
Result:=false; // 返回 FALSE
exit;
end;
//显示创建完成的OpenGL窗口
ShowWindow(h_Wnd,SW_SHOW); // 显示创建完成的OpenGL窗口
SetForegroundWindow(h_Wnd); // 设为前端窗口(给它更高的优先级)
SetFocus(h_Wnd); // 并将焦点移至此窗口
ReSizeGLScene(width,height); // 设置透视 GL 屏幕
//初始化OpenGL(设置光照、纹理、等等任何需要设置的东东)
if (not InitGl()) then // 初始化OpenGL
begin
KillGLWindow(); // 重置显示区
MessageBox(0,'初始化失败.','错误',MB_OK or MB_ICONEXCLAMATION);
Result:=false; // 返回 FALSE
exit;
end;
Result:=true; // 成功
end;
//Windows程序的入口(调用窗口创建例程,处理窗口消息,并监视人机交互)
function WinMain(hInstance: HINST; // 当前窗口实例
hPrevInstance: HINST; // 前一个窗口实例
lpCmdLine: PChar; // 命令行参数
nCmdShow: integer): // 窗口显示状态
integer; stdcall;
var
msg: TMsg; // 用来检查是否有消息等待处理
done: Bool; // 用来检查否完程序完成运行
begin
done:=false; //用来退出循环的Bool 变量
// 选择窗口是否全屏
if MessageBox(0,'你想在全屏模式下运行么?','全屏',
MB_YESNO or MB_ICONQUESTION)=IDNO then
FullScreen:=false
else
FullScreen:=true;
// 创建OpenGL窗口
if not CreateGLWindow('我的OpenGL 框架',640,480,16,FullScreen) then
begin
Result := 0; // 失败退出
exit;
end;
// 下面是消息循环的开始。只要done保持FALSE,循环一直进行
while not done do
begin
if (PeekMessage(msg, 0, 0, 0, PM_REMOVE)) then // 检查是否有消息在等待
begin
if msg.message=WM_QUIT then // 收到退出消息?
done:=true
else // 如果不是退出消息,我们翻译消息,然后发送消息(使得WndProc() 或 Windows能够处理他们)
begin
TranslateMessage(msg); // 翻译消息
DispatchMessage(msg); // 发送消息
end;
end
else
begin
// J如果没有消息,绘制我们的OpenGL场景失败,或者ESC 发出退出信号直接退出程序
if (active and not(DrawGLScene()) or keys[VK_ESCAPE]) then
done:=true
else
SwapBuffers(h_Dc); // 交换缓存 (双缓存)
if keys[VK_F1] then // 允许用户按下F1键在全屏模式和窗口模式间切换
begin
Keys[VK_F1] := false; // 若是,使对应的Key数组中的值为 FALSE
KillGLWindow(); // 销毁当前的窗口
FullScreen := not FullScreen; // 切换 全屏 / 窗口 模式
// 重建 OpenGL 窗口
CreateGLWindow('我的OpenGL 框架',640,480,16,fullscreen);
end;
end;
end;
// 关闭程序
killGLwindow(); // 销毁窗口
result:=msg.wParam; // 退出程序
end;
begin
WinMain(hInstance, HPrevInst, CmdLine, CmdShow); // 程序开始运行
end.