Zobrazíme dětské OpenGL okno v dialogu a budeme mu předávat hodnoty získané z ovládacích prvků (editboxy a radiobuttony). Periodické překreslování OpenGL okna zajišťuje zpráva WM_TIMER - trojúhelník a čtverec budou rotovat.
Zdrojový kód pro tento článek vychází z programu Projekt - Dialog, který mi poslal Max Zelený <prog.max (zavináč) seznam.cz>. Program jsem upravil, aby více demonstroval možnost ovlivňování scény hodnotami v ovládacích prvcích. Musím ale přiznat, že bez něj by tento článek neměl šanci vzniknout, protože bych neměl dostatečné znalosti, jak vytvořit dětské OpenGL okno. Díky.
Začneme vygenerováním klasické Dialog aplikace používající MFC. Upravíme zdroj dialogu tak, aby vypadal přibližně jako na obrázku. V levé části necháme volné místo pro zobrazení dětského OpenGL okna.
Pomocí ClassWizardu připojíme proměnné a funkce k dialogovým prvkům:
Aby se po stisku klávesy ENTER dialog nezavíral přepíšeme virtuální funkci OnOK(), tak aby nic nedělala.
void CDialogDlg::OnOK()// Aby se po stisku klávesy ENTER dialog nezavřel
{
// TODO: Add extra validation here
// CDialog::OnOK();// Zrušit
}
Do deklarace třídy dialogu přidáme ukazatel na proměnnou třídy COpenGL.
class CDialogDlg : public CDialog// Třída dialogu
{
public:
COpenGL* gl_okno;// Ukazatel na dětské OpenGL okno
CDialogDlg(CWnd* pParent = NULL);// Konstruktor
~CDialogDlg();// Destruktor
// Proměnné a funkce generované ClassWizzardem
}
Na konci konstruktoru ukazatel inicializujeme na NULL.
CDialogDlg::CDialogDlg(CWnd* pParent) : CDialog(CDialogDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CDialogDlg)
c_hloubka = -7.0;
t_hloubka = -7.0;
rotace_ano = 0;
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
gl_okno = NULL;// Aby destruktor věděl, jestli bylo okno vytvořeno
}
Funkce OnInitDialog() se volá hned po vytvoření dialogu, těsně před prvním vykreslením - nejlepší místo pro vytvoření dětského OpenGL okna.
BOOL CDialogDlg::OnInitDialog()// Inicializace
{
CDialog::OnInitDialog();
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE);// Set big icon
SetIcon(m_hIcon, FALSE);// Set small icon
// TODO: Add extra initialization here
Vytvoříme obdélník, který určuje pozici a velikost zamýšleného dětského okna.
CRect rect(7, 7, 300, 300);// Pozice a velikost okna (obdélník)
Alokujeme pro okno dynamickou paměť a inicializujeme jeho členské proměnné. T_hloubka a c_hloubka určují hloubku trojúhelníku a čtverce ve scéně.
gl_okno = new COpenGL;// Dynamický objekt třídy
gl_okno->rc = rect;// Nastavení atributů třídy
gl_okno->t_hloubka = t_hloubka;
gl_okno->c_hloubka = c_hloubka;
Pomocí funkce Create() okno vytvoříme.
// Vytvoření a zobrazení okna
gl_okno->Create(NULL, NULL, WS_CHILD|WS_CLIPSIBLINGS|WS_CLIPCHILDREN|WS_VISIBLE, rect, this, 0);
return TRUE;// Return TRUE unless you set the focus to a control
}
V destruktoru ověřujeme, jestli je ukazatel stále nastaven na NULL. Pokud ne uvolníme jeho paměť.
CDialogDlg::~CDialogDlg()// Destruktor
{
if(gl_okno != NULL)// Bylo okno vytvořeno?
{
delete gl_okno;// Smaže ho
}
}
Poslední funkcí, která v této třídě stojí za vysvětlení je reakce na tlačítko IDC_AKTUALIZOVAT, ale řekneme si o ní více, až budeme vědět, jak vypadá a funguje třída COpenGL.
Pozn.: Správně bychom měli vytvořit ještě jednu třídu - potomka COpenGL a až tu upravovat. Výsledný článek by se však velmi znepřehlednil. Zapouzdřenost dat radši vůbec nezmiňuji...
class COpenGL : public CWnd// Třída OpenGL okna
{
public:
COpenGL();// Konstruktor
virtual ~COpenGL();// Destruktor
Funkci MySetPixelFormat() voláme ve funkci OnCreate(). Má za úkol nastavit okno tak, aby podporovalo OpenGL.
MySetPixelFormat(HDC hdc);// Nastavuje pixel formát
Proměnnou rc jsme inicializovali ve funkci CDialogDlg::OnInitDialog() těsně před zavoláním Create(). Obsahuje velikost okna, která je důležitá pro nastavení perspektivy. Kontext zařízení a rendering kontext už určitě znáte. OpenGL se bez nich neobejde.
CRect rc;// Velikost okna
HDC m_hgldc;// Kontext zařízení
HGLRC m_hglRC;// Rendering kontext
Následující čtyři proměnné slouží pro uložení hloubky objektů ve scéně a úhlu natočení. Přepínač rotace zapíná/vypíná otáčení objektů.
double t_hloubka;// Hloubka trojúhelníku ve scéně
double c_hloubka;// Hloubka čtverce ve scéně
float t_rot;// Úhel rotace trojúhelníku
float c_rot;// Úhel rotace čtverce
bool rotace;// Zapnutá/vypnutá rotace objektů
Deklarace funkcí generovaných ClassWizzardem.
protected:
//{{AFX_MSG(COpenGL)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnPaint();
afx_msg void OnTimer(UINT nIDEvent);
afx_msg void OnDestroy();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
}
V konstruktoru třídy inicializujeme proměnné.
COpenGL::COpenGL()// Konstruktor
{
t_rot = 0.0f;// Nastavení parametrů na výchozí hodnoty
c_rot = 0.0f;
rotace = true;
}
Ve funkci MySetPixelFormat() nastavujeme okno, aby podporovalo OpenGL. Nebudu ji vysvětlovat, protože je tato operace velmi dobře popsaná např. v NeHe tutoriálu 1.
int COpenGL::MySetPixelFormat(HDC hdc)// Nastaví pixel formát
{
PIXELFORMATDESCRIPTOR *ppfd;
int pixelformat;
PIXELFORMATDESCRIPTOR pfd =
{
sizeof(PIXELFORMATDESCRIPTOR),// Velikost struktury
1,// Číslo verze
PFD_DRAW_TO_WINDOW |// Podpora okna
PFD_SUPPORT_OPENGL |// OpenGL
PFD_DOUBLEBUFFER,// Dva buffery
PFD_TYPE_RGBA,// RGBA formát
32,// Barevná hloubka
0, 0, 0, 0, 0, 0,// Bity barev ignorovány
8,// Žádný alpha buffer
0,// Shift Bit ignorován
8,// Žádný Accumulation buffer
0, 0, 0, 0,// Accumulation bity ignorovány
64,// Z-Buffer
8,// Stencil buffer
8,// Auxiliary buffer
PFD_MAIN_PLANE,// Hlavní vykreslovací vrstva
0,// Rezervováno
0, 0, 0// Layer Masks ignorovány
};
ppfd = &pfd;
if ((pixelformat = ChoosePixelFormat(hdc, ppfd)) == 0)// Podařilo se najít Pixel Format?
{
::MessageBox(NULL, "ChoosePixelFormat failed", "Error", MB_OK);
return FALSE;
}
if (SetPixelFormat(hdc, pixelformat, ppfd) == FALSE)// Podařilo se nastavit Pixel Format?
{
::MessageBox(NULL, "SetPixelFormat failed", "Error", MB_OK);
return FALSE;
}
return TRUE;
}
Funkce OnCreate() se volá po vytvoření okna, před prvním vykreslením. Inicializujeme v ní OpenGL okno.
int COpenGL::OnCreate(LPCREATESTRUCT lpCreateStruct)// Inicializace
{
Získáme kontext zařízení a nastavíme Pixel Format, aby okno podporovalo OpenGL. Potom nastavíme rendering kontext.
m_hgldc = ::GetDC(m_hWnd);// Získá kontext zařízení
if(!MySetPixelFormat(m_hgldc))// Podařilo se nastavit Pixel Format?
{
::MessageBox(m_hWnd,"MySetPixelFormat Failed!","Error",MB_OK);
return -1;
}
// Vytvoří a nastaví Rendering kontext
m_hglRC = wglCreateContext(m_hgldc);
wglMakeCurrent(m_hgldc, m_hglRC);
Nastavíme OpenGL perspektivu. Tento kód by se měl volat při každé změně velikosti okna, ale protože se rozměry dialogu nemění, stačí pouze jednou při inicializaci. V objektu rc máme uloženou velikost okna.
// Inicializace OpenGL okna
if(rc.bottom == 0)// Proti dělení nulou
{
rc.bottom = 1;
}
glViewport(0, 0, rc.right, rc.bottom);// Resetuje aktuální nastavení
glMatrixMode(GL_PROJECTION);// Zvolí projekční matici
glLoadIdentity();// Reset matice
gluPerspective(45.0f, (GLfloat)rc.right / (GLfloat)rc.bottom, 1.0f, 100.0f);// Výpočet perspektivy
glMatrixMode(GL_MODELVIEW);// Zvolí matici Modelview
glLoadIdentity();// Reset matice
Inicializujeme OpenGL podle konkrétních požadavků.
// Uživatelská inicializace
glShadeModel(GL_SMOOTH);// Zapne stínování(jemné)
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);// Černé pozadí
glClearDepth(1.0f);// Nastaví Depth Buffer
glEnable(GL_DEPTH_TEST);// Povolí hloubkové testování
glDepthFunc(GL_LEQUAL);// Typ hloubkového testování
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);// Perspektiva
Spustíme timer, který bude zajišťovat periodické překreslování scény.
SetTimer(1, 30, NULL);// Spustí timer pro překreslování
return 0;
}
Ošetříme zprávu časovače. Volání funkce Invalidate() má za následek překreslení okna.
void COpenGL::OnTimer(UINT nIDEvent)// Časovač
{
Invalidate();// Překreslení okna
CWnd::OnTimer(nIDEvent);// Metoda rodičovské třídy
}
Funkce OnDestroy() se volá těsně před zavřením okna. Vypneme časovač a smažeme kontexty.
void COpenGL::OnDestroy()// Deinicializace
{
CWnd::OnDestroy();// Metoda rodičovské třídy
KillTimer(1);// Vypnutí časovače
wglMakeCurrent(NULL, NULL);// Neaktivní rendering kontext
wglDeleteContext(m_hglRC);// Smaže rendering kontext
::ReleaseDC(m_hWnd, m_hgldc);// Ukončí vykreslování
}
OnPaint() zajišťuje renderování OpenGL scény. Vytvoříme něco ve stylu NeHe tutoriálu 4. Trojúhelník rotuje kolem osy y čtverec kolem osy x.
void COpenGL::OnPaint()// Vykreslování
{
CPaintDC dc(this);// Kontext zařízení
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);// Vyčistí buffery
glLoadIdentity();// Reset matice
glTranslated(-1.5, 0.0, t_hloubka);// Posun doleva a do hloubky
glRotatef(t_rot, 0.0f, 1.0f, 0.0f);// Rotace na ose y
glBegin(GL_TRIANGLES);// Začátek kreslení trojúhelníků
glColor3f(1.0f, 0.0f, 0.0f);// Červená barva
glVertex3f(0.0f, 1.0f, 0.0f);// Horní bod
glColor3f(0.0f, 1.0f, 0.0f);// Zelená barva
glVertex3f(-1.0f, -1.0f, 0.0f);// Levý dolní bod
glColor3f(0.0f, 0.0f, 1.0f);// Modrá barva
glVertex3f(1.0f, -1.0f, 0.0f);// Pravý dolní bod
glEnd();
glLoadIdentity();// Reset matice
glTranslated(1.5, 0.0, c_hloubka);// Posun o 3 jednotky doprava
glRotatef(c_rot, 1.0f, 0.0f, 0.0f);// Rotace na ose x
glColor3f(0.5f, 0.5f, 1.0f);// Světle modrá barva
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();
if(rotace)// Je zapnutá rotace?
{
t_rot += 3.0f;// Zvětší úhel
c_rot += 2.0f;
}
Nakonec nesmíme zapomenout prohodit buffery.
SwapBuffers(m_hgldc);// Prohodí buffery
}
Nyní víte, jak se vytváří OpenGL okno a jak pracuje. Nezapomněli jsme na něco? Ještě musíme připojit ovládací prvky dialogu tak, aby mohly ovlivňovat vykreslovanou scénu. Po stisku tlačítka IDC_AKTUALIZOVAT získáme obsah ovládacích prvků a nastavíme položky objektu třídy COpenGL. Opravdu nic složitého.
void CDialogDlg::OnAktualizovat()// Stisk tlačítka
{
if(UpdateData() == 0)// Nagrabuje hodnoty z ovládacích prvků
return;
gl_okno->t_hloubka = t_hloubka;// Nastaví proměnné
gl_okno->c_hloubka = c_hloubka;
if(!gl_okno->rotace && rotace_ano == 0)// Zapnout rotaci
{
gl_okno->rotace = true;
gl_okno->SetTimer(1, 30, NULL);
}
if(gl_okno->rotace && rotace_ano == 1)// Vypnout rotaci
{
gl_okno->rotace = false;
gl_okno->KillTimer(1);
}
Preventivně překreslíme OpenGL scénu. Kdyby nebyla aktivní rotace, timer by byl vypnutý a tudíž by nevolal periodické překreslování. Změny by se neprojevily.
gl_okno->Invalidate();// Překreslení OpenGL okna
}
Pokud programujete pod MFC, nemělo by vám napojení dialogových prvků dělat větší problémy. Já jsem se v začátcích nedostal přes vytváření OpenGL okna, ještě jednou proto děkuji Maxi Zelenému <prog.max (zavináč) seznam.cz>, který mi poslal ukázkový program.
napsal: Michal Turek - Woq <WOQ (zavináč) seznam.cz>