U₧ dßvno jsem si cht∞l naprogramovat vlastnφ Üet°iΦ obrazovky. M∞l jsem sice t°φdu CScreenSaverWnd pro MFC, ale ta nepodporovala OpenGL. U NeHe tutorißlu 38 jsem naÜel odkaz na Üet°iΦ obrazovky s podporou OpenGL, kter² napsal Brian Hunsucker. Cht∞l bych mu pod∞kovat, proto₧e na jeho zdrojovΘm k≤du z v∞tÜφ Φßsti stavφ tento Φlßnek.
ZaΦneme parametry spouÜt∞nφ Üet°iΦe obrazovky. KterΘ definujφ, zda se mß spustit Üet°iΦ obrazovky jako takov² (s) nebo pouze jeho konfiguraΦnφ dialog (c). M∞li bychom ho zφskßvat z p°φkazovΘ °ßdky testovßnφm parametr∙ funkce main(), ale defakto se o n∞j nemusφme starat, proto₧e vÜe zajistφ knihovna scrnsave, kterß je souΦßstφ Visual C++. Jenom tak na okraj, k main() se v∙bec nedostaneme, proto₧e je p°edkompilovanß v scrnsave. Stejn∞ tak se nestarßme o zav°enφ Üet°iΦe a podobnΘ v∞ci. Program vÜe d∞lß automaticky.
Jedna malß poznßmka: p°i v²voji aplikace se po spuÜt∞nφ zobrazφ konfiguraΦnφ dialog. Abychom program spustili jako Üet°iΦ, musφme mu p°edat parametr s. V Project/Settings pod nabφdkou Debug se musφ napsat do Program arguments pφsmeno s.
Vygenerujeme klasick² Win32 Aplication projekt a m∙₧eme psßt k≤d. Vlo₧φme hlaviΦkovΘ soubory a p°ilinkujeme pot°ebnΘ knihovny.
#include <windows.h>// HlaviΦkov² soubor pro Windows
#include <scrnsave.h>// HlaviΦkov² soubor pro Üet°iΦ obrazovky
#include <GL/gl.h>// HlaviΦkov² soubor pro OpenGL
#include <GL/glu.h>// HlaviΦkov² soubor pro GLU
#include "res/resource.h"// HlaviΦkov² soubor pro Resource (konfiguraΦnφ dialog, ikona ...)
#pragma comment (lib,"opengl32.lib")// P°ilinkovßnφ OpenGL
#pragma comment (lib,"glu32.lib")// P°ilinkovßnφ GLU
#pragma comment (lib,"scrnsave.lib")// P°ilinkovßnφ knihovny Üet°iΦe obrazovky
Instance aplikace je jedinou globßlnφ prom∞nnou.
HINSTANCE hInstance;// Uklßdß instanci aplikace
V nßsledujφcφ funkci inicializujeme okno tak, aby podporovalo OpenGL. Dß se °φct, ₧e s nejv∞tÜφ pravd∞podobnostφ tuto funkci nebudete muset nikdy zm∞nit.
HGLRC InitOGLWindow(HWND hWnd)// Inicializace okna
{
HDC hDC = GetDC(hWnd);// Kontext za°φzenφ
HGLRC hRC = 0;// Renderovacφ kontext
PIXELFORMATDESCRIPTOR pfd;
int nFormat;
ZeroMemory(&pfd, sizeof(PIXELFORMATDESCRIPTOR));
// Nastavenφ okna
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER;
pfd.cColorBits = 24;
pfd.cDepthBits = 24;
nFormat = ChoosePixelFormat(hDC, &pfd);
DescribePixelFormat(hDC, nFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfd);
SetPixelFormat(hDC, nFormat, &pfd);
hRC = wglCreateContext(hDC);
wglMakeCurrent(hDC, hRC);
ReleaseDC(hWnd, hDC);
return hRC;// Vrßtφ renderovacφ kontext
}
Do inicializace OpenGL p°idßme i nastavenφ perspektivy, kterß se standardn∞ vklßdß do funkce pro zm∞nu velikosti okna. Nic se nestane, proto₧e parametry okna Üet°iΦe obrazovky se nikdy nezm∞nφ. Jak takΘ? Jakmile se pohne myÜφ, program je ukonΦen.
void InitOpenGL(GLsizei width, GLsizei height)// Inicializace OpenGL
{
if (height==0)// Proti d∞lenφ nulou
{
height=1;
}
glViewport(0,0,width,height);// Reset Viewportu
glMatrixMode(GL_PROJECTION);// Zvolφ projekΦnφ matici
glLoadIdentity();// Reset matice
// Perspektiva
gluPerspective(45.0f, (GLfloat)(width)/(GLfloat)(height),1.0f, 20.0f);
glMatrixMode(GL_MODELVIEW);// Zvolφ matici Modelview
glLoadIdentity();
Na tomto mφst∞ si sami nastavte OpenGL, jak uznßte za vhodnΘ. V naÜem p°φpad∞ definujeme stφnovßnφ, perspektivnφ korekce, barvu pozadφ a nastavenφ hloubkovΘho bufferu.
// U₧ivatelskß inicializace
glShadeModel(GL_SMOOTH);// VyhlazenΘ stφnovßnφ
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// Perspektivnφ korekce
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);// ╚ernΘ pozadφ
glClearDepth(1.0f);// Nastavenφ hloubkovΘho bufferu
glDepthFunc(GL_LEQUAL);// Typ hloubkovΘho testovßnφ
glEnable(GL_DEPTH_TEST);// Zapne test hloubky
}
Druh²m mφstem, kterΘ se p°i vytvß°enφ novΘho Üet°iΦe m∞nφ, je vykreslovacφ funkce. Abychom v∞d∞li, ₧e program funguje, vykreslφme troj·helnφk a obdΘlnφk. Nic sv∞tobornΘho...
void DrawGLScene()
{
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Sma₧e obrazovku a hloubkov² buffer
glLoadIdentity();// Reset matice
glTranslatef(-1.5f,0.0f,-6.0f);// Posun doleva a do hloubky
glBegin(GL_TRIANGLES);// ZaΦßtek kreslenφ troj·helnφk∙
glVertex3f( 0.0f, 1.0f, 0.0f);// Hornφ bod
glVertex3f(-1.0f,-1.0f, 0.0f);// Lev² dolnφ bod
glVertex3f( 1.0f,-1.0f, 0.0f);// Prav² dolnφ bod
glEnd();
glTranslatef(3.0f,0.0f,0.0f);// Posun o 3 jednotky doprava
glBegin(GL_QUADS);// ZaΦßtek kreslenφ obdΘlnφk∙
glVertex3f(-1.0f, 1.0f, 0.0f);// Lev² hornφ bod
glVertex3f( 1.0f, 1.0f, 0.0f);// Prav² hornφ bod
glVertex3f( 1.0f,-1.0f, 0.0f);// Prav² dolnφ bod
glVertex3f(-1.0f,-1.0f, 0.0f);// Lev² dolnφ bod
glEnd();// Konec kreslenφ obdΘlnφk∙
glFlush();
}
I Üet°iΦi obrazovky posφlß systΘm zprßvy. Nenφ jich sice mnoho, ale bez n∞kter²ch bychom se urΦit∞ neobeÜli. Deklarujeme prom∞nnΘ kontextu za°φzenφ a renderovacφho kontextu a dßle v∞tvφme funkci podle doÜlΘ zprßvy.
// Procedura okna Üet°iΦe obrazovky
LRESULT WINAPI ScreenSaverProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hDC;// Kontext za°φzenφ
static HGLRC hRC;// Renderovacφ kontext
switch (message)// V∞tvφ program podle doÜlΘ zprßvy
{
Na zprßvu WM_CREATE, kterou posφlß systΘm ihned po vytvo°enφ okna, inicializujeme okno. PotΘ pomocφ funkce GetClientRect() nagrabujeme jeho Üφ°ku a v²Üku, kterou p°edßme do InitOpenGL(). Nakonec spustφme timer zajiÜ¥ujφcφ periodickΘ p°ekreslovßnφ scΘny.
case WM_CREATE:// Vytvo°enφ okna
hRC = InitOGLWindow(hWnd);// Zφskßnφ kontextu za°φzenφ
RECT WindowRect;// Pro zjiÜt∞nφ velikosti okna
int width;// èφ°ka
int height;// V²Üka
GetClientRect(hWnd, &WindowRect);// Zφskß velikost okna
width = WindowRect.right - WindowRect.left;// èφ°ka okna
height = WindowRect.bottom - WindowRect.top;// V²Üka okna
InitOpenGL(width, height);// Inicializace OpenGL
SetTimer(hWnd, 1, 20, NULL);// Zapnutφ timeru
break;
O kousek v²Üe jsme spustili timer. Nynφ definujeme, ₧e po p°φchodu jeho zprßvy se mß zφskat DC. PotΘ p°ekreslφme scΘnu, prohodφme buffery a op∞t uvolnφme DC. K≤d je tak krßtk², ₧e vytvß°et novou funkci je ·pln∞ zbyteΦnΘ.
case WM_TIMER:// Zprßva od ΦasovaΦe
hDC = GetDC(hWnd);// Zφskßnφ kontextu za°φzenφ
DrawGLScene();// Vykreslφ scΘnu
SwapBuffers(hDC);// Prohodφ buffery
ReleaseDC(hWnd, hDC);// Uvolnφ kontext za°φzenφ
break;
Na zprßvu WM_DESTROY provedeme deinicializaci.
case WM_DESTROY:// Zav°enφ okna
KillTimer(hWnd,1); // Vypnutφ timeru
wglMakeCurrent(NULL, NULL);
wglDeleteContext(hRC);// Sma₧e renderovacφ kontext
break;
}
VÜechny ostatnφ zprßvy p°edßme dßle.
return DefScreenSaverProc(hWnd, message, wParam, lParam);// NeoÜet°enΘ zprßvy
}
┌pln∞ na zaΦßtku jsme si °ekli, ₧e program mß dv∞ cesty provßd∞nφ. Klasick² Üet°iΦ u₧ mßme, nynφ se vrhneme na konfiguraΦnφ dialog. Nejprve ho vytvo°φme. Mß dv∞ tlaΦφtka OK a CANCEL (pop°. i dalÜφ). ZaΦneme mapou zprßv a dß se °φct, ₧e i skonΦφme. P°i WM_INITDIALOG m∙₧eme jednotlivΘ prvky inicializovat na hodnoty, kterΘ zφskßme nap°φklad z pomocnΘho souboru nebo z registr∙ Windows. UrΦovaly by chovßnφ Üet°iΦe, ale nßÜ program je velmi jednoduch², tak proΦ ho komplikovat. Namapovßnφm zprßvy WM_COMMAND urΦφme, co se mß ud∞lat po kliknutφ na tlaΦφtka.
// Procedura okna konfiguraΦnφho dialogu
BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)// V∞tvφ program podle doÜlΘ zprßvy
{
case WM_INITDIALOG:// Inicializace dialogu
return TRUE;// V po°ßdku
case WM_COMMAND:// P°φkaz (nap°. kliknutφ na tlaΦφtko)
switch (LOWORD(wParam))// KterΘ tlaΦφtko?
{
case IDOK:// OK
EndDialog(hDlg, TRUE);// Zav°e dialog
return TRUE;// V po°ßdku
case IDCANCEL:// Cancel
EndDialog(hDlg, TRUE);// Zav°e dialog
break;
}
}
return FALSE;// False - u₧ivatel nap°. stiskl Cancel
}
K Φemu je tato funkce, abych °ekl pravdu, nevφm, ale kompilßtor mi bez nφ hlßsφ chyby.
BOOL WINAPI RegisterDialogClasses(HANDLE hInst)// ???
{
return TRUE;
}
No a tφm jsme i skonΦili. Mo₧nß se ptßte: "A opravdu to funguje?". Ano, takΘ jsem nechßpal. Kdy₧ jsem tento k≤d vid∞l poprvΘ, bez main() a jak²chkoli jin²ch nßvaznostφ pochyboval jsem, ₧e sv∙j vlastnφ Üet°iΦ n∞kdy v ₧ivot∞ rozjedu, ale poda°ilo se. ╚lov∞k se nemusφ skoro o nic starat vÜe je p°ipraveno v knihovn∞ scrnsave. Stßhn∞te si zdrojov² k≤d a uvidφte.
Abych nezapomn∞l, v²sledn² .exe soubor je nutnΘ p°ejmenovat na .scr a zkopφrovat do Windows/System. A₧ potom budete moci v nastavenφ obrazovky vym∞nit Üet°iΦ za nov² (samoz°ejm∞ lepÜφ).
napsal: Michal Turek - Woq