OpenGL okno v dialogu

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.

Zdroj dialogu

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>

Zdrojové kódy

Dialog