本文主要是介绍实验四、三维屏保制作,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
http://www.jsj.tzc.edu.cn/teacher/crq/content/courses/opengl/%CA%B5%D1%E94.htm
4.1 三维屏保版本一
屏幕保护程序是一种特殊的应用程序,如果用户打开了 Windows 的屏幕保护功能,而且在一特定的时间段内没有任何输入,系统就会自动启动屏幕保护程序。屏幕保护程序有两个作用:
1 .防止屏幕长时间显示静态图像而降低荧光介质的寿命。
2 .防止用户离开计算机时屏幕上的信息被不相干的人看到。
相应的,屏幕保护程序至少应该实现以下两个功能:
1 .动画功能:显示动态的图象。
2 .密码校验功能:退出程序时屏蔽一切系统热键( Ctrl+Alt+Del , Alt+Tab , Ctrl+Esc 等,避免用户使用热键关闭程序或切换到其它程序而跳过密码校验),并要求输入授权密码。
有了前面的基础,本节的工作就少多了,本实验使用实验三创建的烟花效果作为基础,效果都与实验三一样,只是现在要做到是屏保程序,与普通应用程序稍微有点区别。
屏保程序的主要特点是:
( 1 )它的后缀是 .scr ,而不是 exe
( 2 )它需要放在系统目录下,并有系统程序调用
( 3 )它和我们之前的程序一样,也是 Windows 窗口程序。
事实上,当我们在桌面属性中设置好屏幕保护程序后,系统监视鼠标、键盘的动静,如果在规定的时间内系统没有动静,就会启动屏保程序,这时候系统将给屏保程序传递 /s 、 /S 、 -s 、或 -S 参数。而当我们的鼠标、键盘有动静时,需要由我们的应用程序进行监视。因此我们的代码就比较简单了,步骤如下:
1 、修改所生成的程序后缀: 在工程的 Settings (设置) =>link (链接)选项下,修改 Output File Name 为:三维烟花效果 .scr ,此时你若运行程序,会发现其实和应用程序没什么区别。
2 、改为屏保程序: 在 WinMain 函数中的起始处写下下面的代码:
BOOL Status=FALSE;
int i=0;
while(lpCmdLine[i])
{
if((lpCmdLine[i]=='/' || lpCmdLine[i]=='-') && (lpCmdLine[i+1]=='s'|| lpCmdLine[i+1]=='S'))
Status=TRUE;
i++;
}
if(!Status)
return TRUE;
上面的代码会判断命令行参数中是否存在 -s 、 -S 、 /s 或 /S 等,如果是那么就设置 Status 为 True ,只有该变量为 True 时,程序才能运行。这正好是屏幕保护程序的特点。
此时你若点击 VC 中的运行按钮,是看不到效果的,因为我们没有传递上述命令行参数,程序很快就退出了。
3 、修改程序: 事实上,现在已经是屏幕保护程序了,但它还存在较多的问题,首先我们去掉分辨率提示消息窗口,让程序初始就进入全屏,不需要用户作出响应,因此请删掉 下面的代码:
if (MessageBox(NULL,"Would You Like To Run In Fullscreen Mode?", "Start FullScreen?",MB_YESNO|MB_ICONQUESTION)==IDNO)
{
fullscreen=FALSE;
}
如果觉得分辨率不够,将窗口创建语句修改为:
CreateGLWindow(" 烟花效果 ",1024,768,16,fullscreen)
4 、监视鼠标和键盘: 我们只需要子 WndProc 函数中修改对鼠标和键盘的响应即可,先删除掉原来的鼠标和键盘响应,再添加下面的代码:
case WM_MOUSEMOVE:
{
static int fst;
if(fst<50)
fst++;
else
PostQuitMessage (0);
return 0; // Jump Back
}
case WM_KEYDOWN: // Is A Key Being Held Down?
case WM_DESTROY:
{
PostQuitMessage (0);
return 0; // Jump Back
}
这样,当你按下按键或者移动鼠标时,屏幕保护程序就会关闭。
现在你可以将所有用到的文件,包括程序、图片、声音已将相关的库都拷贝到系统的 system32 目录下,就可以在桌面上进行设置了。
5 、添加设置窗口: 一般屏幕保护程序都提供了参数设置窗口,根据我们目前的程序,我们的烟花数是可以设置的,下面我们就来建立这样的参数设置窗口。
首先我们选择文件中的新建菜单项,弹出下列对话框,并选择 Resource Script ,新建资源脚本。再选择 Insert (插入),选择资源,弹出下一个对话框,选择 Dialog (对话框)。
然后设计对话框如下,其中对话框的 ID 为: IDD_SETTINGS_DLG ,文本框的 ID 为 IDC_EDIT_NUM 。确定和取消按钮的 ID 默认值为 IDOK 和 IDCANCEL 。
然后我们修改 WndProc 中对键盘的响应代码,我们准备在屏幕保护程序运行时按下空格键时弹出上面的参数设置窗口。代码如下:
case WM_KEYDOWN: // Is A Key Being Held Down?
{
if (wParam==VK_SPACE)
{
DialogBox(hInstance,MAKEINTRESOURCE(IDD_SETTINGS_DLG),hWnd,SettingDlgProc);
}
else
PostQuitMessage(0);
return 0;
}
case WM_DESTROY:
{
PostQuitMessage (0);
return 0; // Jump Back
}
上面的 DialogBox 用于弹出一个对话框,其中 IDD_SETTINGS_DLG 为对话框 ID ,它定义在 resource.h 中,因此需要我们包含该头文件, SettingDlgProc 是对话框的消息处理函数,需要我们自己定义,代码如下:
BOOL CALLBACK SettingDlgProc(HWND hDlg, UINT message, WPARAM wParam,LPARAM lParam)
{
switch(message)
{
case WM_INITDIALOG:
SetDlgItemInt(hDlg,IDC_EDIT_NUM,MAX_FIRES,TRUE);
return TRUE;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case IDOK:
BOOL state;
MAX_FIRES = GetDlgItemInt(hDlg,IDC_EDIT_NUM,&state,TRUE);
DrawGLScene();
case IDCANCEL:
EndDialog(hDlg,0);
return TRUE;
}
break;
}
return FALSE;
}
上述代码存在错误,因为我们原来的 MAX_FIRES 被定义为一个宏,因此没法改变,你需要将它声明为一个 UINT 变量。
当前版本的屏保没有提供密码校验功能,下面我们使用第二个版本即使用 VC 提供的屏幕保护库改善这个问题。
4.2 三维屏保版本二
1 、 Windows 屏幕保护库介绍: Windows 屏幕保护库为我们提供了以下函数:
( 1 ) WinMain ( ) 函数:完成程序初始化和消息调度。看到这里,可能有些读者已经想到了:我们不能在 MFC 中使用 Windows 屏幕保护库。为什么呢?原因很简单: MFC 的底层封装了 WinMain( ) 函数,如果在 MFC 中使用 Windows 屏幕保护库,一个程序中就会有两个 WinMain( ) 函数,显然说不过去。因此,我们只有使用 Win32 SDK 了。当然我们也不用自己写 WinMain 了,因为系统已经有一个了。
( 2 ) DefScreenSaverProc ( ) 函数:处理键盘、鼠标等消息,实现屏蔽热键及密码校验等功能。
( 3 )一些函数、变量和常量的定义。
大家可以在 Visual C++ 安装目录的 Include 子目录中找到 SCRNSAVE.H 文件,通过它,我们可以看到它们具体的定义。
当系统启动一个屏幕保护程序时, WinMain( ) 函数首先判断系统传递的命令行参数。如果参数指示设置屏幕保护程序,那么 WinMain( ) 函数将会用下列函数来创建设置对话框并将用户的输入保存到注册表中:
BOOL WINAPI ScreenSaverConfigureDialog (HWND hDlg,
UINT message,
WPARAM wParam,
LPARAM lParam
);
因此,我们如果要对屏幕保护进行设置,可以在这个函数中添加相关的代码。
还会用下列函数来注册设置对话框中的特殊窗口类或自定义控件类:
BOOL WINAPI RegisterDialogClasses (HANDLE hInst)
如果没有特殊窗口类和自定义控件类, RegisterDialogClasses( ) 函数只需简单的返回 TRUE 值。如果参数指示运行屏幕保护程序, WinMain( ) 函数会调用一些初始化代码来注册窗口类并建立一个全屏窗口。
而发送给屏幕保护程序的消息则由下列函数来处理:
LRESULT WINAPI ScreenSaverProc ( HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam
);
我们可以在 ScreenSaverProc( ) 函数中处理下面几个消息(当然也可以处理其它消息):
WM_CREATE :从注册表中获取屏幕保护程序的设置信息,创建一个计时器以及处理其它的初始化工作;
WM_TIMER :处理绘制工作
WM_DESTORY :销毁计时器以及处理其它销毁工作
ScreenSaverProc( ) 函数未处理的消息则传递给 DefScreenSaverProc( ) 处理。
从这里我们可以看出,我们所要做的只是实现 ScreenSaverProc , ScreenSaverConfigureDialog 和 RegisterDialogClasses 三个函数,而麻烦的密码校验和密码更改已经由 DefScreenSaverProc 函数实现了!剩下的就是编译并与 Windows 屏幕保护程序库连接了。
必须注意的是, Windows 屏幕保护程序库对资源 ID 有一些特殊的要求。首先,它要求有一个 ID 为 IDS_DESCRIPTION (定义为 #define IDS_DESCRIPTION 1 ,见 Visual C++ 目录的 Include/SCRNSAVE.H 文件),长度小于 25 的字符串资源,这个字符串将显示在桌面属性对话框屏幕保护程序列表框中。另一个要求是屏幕保护程序的设置对话框的 ID 必须为 DLG_SCRNSAVECONFIGURE (定义为 #define DLG_SCRNSAVECONFIGURE 2003 )。
Ok ,我们还是边做边理解上述知识吧,下面在版本一定基础上做一些修改。
2 、修改代码: 首先注释掉 WinMain 函数、 WinProc 函数以及 SettingDlgProc 函数,因为我们不再需要它们了。当然其中的部分语句我们还是有必要先保留着,所以先注释掉,而不是直接删除。然后我们加入头文件
#include <scrnsave.h>
并将相对应的库 scrnsave.lib 加入到工程的 Link 选项中。
然后我们添加下面的代码框架,这里我们还没有进行任何处理
LRESULT CALLBACK ScreenSaverProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
break;
case WM_TIMER:
break;
case WM_DESTROY:
break;
}
return DefScreenSaverProc(hWnd, message, wParam, lParam);
}
BOOL WINAPI ScreenSaverConfigureDialog (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch ( message )
{
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
return TRUE;
}
return FALSE;
}
BOOL WINAPI RegisterDialogClasses (HANDLE hInst)
{
return TRUE;
}
然后我们修改 CreateGLWindow 函数的内容,由于我们的屏保程序,窗口是已经创建好了的,因此我们没有必要新创建一个窗口,但是系统创建的窗口是没有设置 OpenGL 环境的,因此直接使用无法进行 OpenGL 绘图,因此,我们需要保留 CreateGLWindow 中的 OpenGL 设置部分。而且我们需要传递一个窗口句柄,而不是重新创建一个,下面为代码:
BOOL CreateGLWindow(HWND hWnd, int bits)// 这里少了很多没有用的参数
{
GLuint PixelFormat;
hInstance = GetModuleHandle(NULL);
static PIXELFORMATDESCRIPTOR pfd=
{
sizeof(PIXELFORMATDESCRIPTOR),
1,
PFD_DRAW_TO_WINDOW |
PFD_SUPPORT_OPENGL |
PFD_DOUBLEBUFFER,
PFD_TYPE_RGBA,
bits,
0, 0, 0, 0, 0, 0,
0,
0,
0,
0, 0, 0, 0,
16,
0,
0,
PFD_MAIN_PLANE,
0,
0, 0, 0
};
if (!(hDC=GetDC(hWnd)))
{
KillGLWindow();
MessageBox(NULL,"Can't Create A GL Device Context.", "ERROR", MB_OK | MB_ICONEXCLAMATION );
return FALSE;
}
if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))
{
KillGLWindow();
MessageBox(NULL,"Can't Find A Suitable PixelFormat.", "ERROR",MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
if(!SetPixelFormat(hDC,PixelFormat,&pfd))
{
KillGLWindow();
MessageBox(NULL,"Can't Set The PixelFormat.", "ERROR",MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
if (!(hRC=wglCreateContext(hDC)))
{
KillGLWindow();
MessageBox(NULL,"Can't Create A GL Rendering Context.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
if(!wglMakeCurrent(hDC,hRC))
{
KillGLWindow();
MessageBox(NULL,"Can't Activate The GL Rendering Context.", "ERROR", MB_OK | MB_ICONEXCLAMATION);
return FALSE;
}
RECT WindowRect;
GetClientRect (hWnd, &WindowRect);
int width = WindowRect.right - WindowRect.left;
int height = WindowRect.bottom - WindowRect.top;
ReSizeGLScene(width, height);
if (!InitGL()) // Initialize Our Newly Created GL Window
{
KillGLWindow(); // Reset The Display
MessageBox(NULL,"Initialization Failed.","ERROR",MB_OK|MB_ICONEXCLAMATION);
return FALSE; // Return FALSE
}
return TRUE; // Success
}
大家仔细比较一下原来的程序,我们发现这里少了窗口类的注册和窗口的创建,同时我们需要重新获取 width 和 height ,因为我们还没有初始化这两个值,它们影响到我们的画图区域。
同样的,我们也不需要在 KillGLWindow 中清除 hWnd 窗口句柄和反注册窗口类。同时我们也删除了相关的全屏代码,因为我们的屏保一般都在全屏下工作,代码如下:
GLvoid KillGLWindow(GLvoid)
{
UnloadTextures();
DeleteAll(&Particles);
CloseSound(0);
CloseSound(1);
if (hRC)
{
if (!wglMakeCurrent(NULL,NULL))
{
MessageBox(NULL,"Release Of DC And RC Failed.","SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
}
if (!wglDeleteContext(hRC))
{
MessageBox(NULL,"Release Rendering Context Failed.","SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
}
hRC=NULL;
}
if (hDC && !ReleaseDC(hWnd,hDC))
{
MessageBox(NULL,"Release Device Context Failed.","SHUTDOWN ERROR", MB_OK | MB_ICONINFORMATION);
hDC=NULL;
}
}
3 、处理 ScreenSaverProc : 在该函数中,我们响应各个事件,首先在 WM_CREATE 下增加代码,用于创建 OpenGL 环境,为绘图做准备,同时我们创建一个计时器,只有创建了计时器,才会有 WM_TIMER 消息,也才会有我们的绘图。然后我们在 WM_DESTROY 下摧毁计时器以及窗口的 OpenGL 绘图资源。因此就有了我们下面的代码:
LRESULT CALLBACK ScreenSaverProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
CreateGLWindow(hWnd,16);
uTimer = SetTimer(hWnd, 1, 10, NULL);
break;
case WM_TIMER:
DrawGLScene();
SwapBuffers(hDC);
break;
case WM_DESTROY:
{
if (uTimer)
KillTimer(hWnd, uTimer);
KillGLWindow();
break;
}
}
return DefScreenSaverProc(hWnd, message, wParam, lParam);
}
其中的 uTimer 被定义为 UINT 全局变量。此时运行屏保程序,已经良好。但若放到 system32 下,并在桌面属性进行预览时,会提示下列错误的对话框:
原来是我们的纹理都没有装进来,可能的问题在于 glaux 库中的 auxDIBImageLoad 调用不成功( LoadGLTextures 函数中调用的)。为了解决这个问题,我不准备用此函数,而是改用了另外的方法替换掉这个系统函数,因此这部分的代码需要你重新修改(如果你有更简单的方法,麻烦告诉我一声)。首先我自定义了 Image 数据结构:
struct Image {
unsigned long sizeX;
unsigned long sizeY;
char *data;
};
下面的 ImageLoadFromFile 函数是新增加的,用于读取 BMP 文件,如果你愿意仔细阅读该代码,你会明白 BMP 文件中存储的各个数据到底是什么,下面是代码:
int ImageLoadFromFile(LPCTSTR lpszName, Image *image) {
HANDLE hBmp = LoadImage(hInstance, lpszName, IMAGE_BITMAP,
0, 0, LR_LOADFROMFILE);
if(!hBmp)
return 0;
// Get the bitmap dimensions.
BITMAP bmp;
if(!GetObject((HBITMAP)hBmp, sizeof(bmp), &bmp))
return 0;
// First try to get how much memory we need.
BITMAPINFO bi = {0};
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bi.bmiHeader.biWidth = bmp.bmWidth;
bi.bmiHeader.biHeight = bmp.bmHeight;
bi.bmiHeader.biPlanes = bmp.bmPlanes;
bi.bmiHeader.biBitCount = 24;
if(!GetDIBits(hDC, (HBITMAP)hBmp, 0, 1, NULL, &bi, DIB_PAL_COLORS))
return 0;
// Allocate memory for the required number of bytes.
image->sizeX = bi.bmiHeader.biWidth;
image->sizeY = bi.bmiHeader.biHeight;
image->data = (char*)malloc(bi.bmiHeader.biSizeImage);
// Get the bits in 24 bit format.
if(!GetDIBits(hDC, (HBITMAP)hBmp, 0, bi.bmiHeader.biHeight, (LPVOID)image->data, &bi, DIB_PAL_COLORS))
return 0;
for (unsigned long i=0;i<bi.bmiHeader.biSizeImage;i+=3) { //bgr -> rgb
char temp = image->data[i];
image->data[i] = image->data[i+2];
image->data[i+2] = temp;
}
return 1;
}
要注意,上面的 image 是没有 new 出来的,也就是说还没有分配空间,因此你需要在定义之后,使用 malloc 函数分配一块空间,比如这样子:
Image *tmpTexture=(Image *)malloc(sizeof(Image));
ImageLoadFromFile("Particle.bmp",tmpTexture);
每个图像都需要按上面的步骤进行。而且程序中所有的 AUX_RGBImageRec 都被换成了 Image (事实上,我们在模仿 glaux 中的函数)。
4 、处理 ScreenSaverConfigureDialog :这个函数可以用于处理我们的屏保设置对话框,我们在 WM_INITDIALOG 下加入下列语句,用于初始化对话框中的值,即烟花粒子数:
SetDlgItemInt(hDlg,IDC_EDIT_NUM,MAX_FIRES,TRUE);
SetFocus(GetDlgItem(hDlg,IDC_EDIT_NUM));
在 WM_COMMAND 下处理我们的按钮命令,将修改后的值保存。最终的代码如下:
BOOL WINAPI ScreenSaverConfigureDialog (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch ( message )
{
case WM_INITDIALOG:
SetDlgItemInt(hDlg,IDC_EDIT_NUM,MAX_FIRES,TRUE);
SetFocus(GetDlgItem(hDlg,IDC_EDIT_NUM));
return TRUE;
case WM_COMMAND:
if ( LOWORD( wParam ) == IDOK )
{
BOOL state;
MAX_FIRES = GetDlgItemInt(hDlg,IDC_EDIT_NUM,&state,TRUE);
EndDialog( hDlg, LOWORD( wParam ) );
DrawGLScene();
return TRUE;
}
else if( LOWORD( wParam ) == IDCANCEL )
{
EndDialog( hDlg, LOWORD( wParam ) );
return TRUE;
}
}
return FALSE;
}
但此时,你进入桌面属性设置屏保时,并不会弹出我们的设置窗口,这是因为屏保设置对话框的 ID 必须为 2003 ,我们打开 resource.h ,将 IDD_SETTINGS_DLG 修改为 2003 ,即:
#define IDD_SETTINGS_DLG 2003
此时就可以弹出设置对话框了,当然要真正保存这些值并使之生效还涉及到注册表之类的操作,我们这里就不再讨论了。有兴趣的同学可以自己改进。
最终的程序作品演示提供下载
这篇关于实验四、三维屏保制作的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!