OpenGL a VB lekce 01

Předmluva

Již nějaký ten pátek programuji ve VB6, ale o 3D grafiku jsem se začal zajímat až cca před jedním rokem (koupě nového PC :) ). Zhruba před měsícem jsem náhodou narazil na stránky o OpenGL v C a to mne přivedlo na myšlenku zkusit to ve VB. Jde to pomalu a ztuha, ale jde to. Bohužel pokud jde o Visual Basic tak na internetu moc informací o OpenGL není a v češtině v podstatě žádné. Pokud ovšem alespoň trochu rozumíte C++ (já trošku ano) tak jste na tom lépe, protože tady už se nějaké informace dají sehnat. Můj hlavní zdroj informací byl
http://nehe.ceskehry.cz/tut_obsah.php což je český překlad http://nehe.gamedev.net/ . Těm chlapcům patří můj velký dík za tu spoustu perfektně podaných informací. Pravda jedná se o C++, ale je to tak dobře vysvětlené že pokud čtenář pochopí podstatu věci dokáže získané informace využít i ve svém jazyce. Takže mi to nedalo a začal jsem to zkoušet ve VB. Zatím se teprve učím (a ještě dlouho budu) osvojit si nově získané vědomosti takže o nějaké totálním návodu jak na OGL ve VB nemůže být řeč, přesto bych se s vámi rád podělil o to co již trochu chápu. Pokud se se mnou rozhodnete vydat na tuto dloooouhou a krkolomnou cestu připravte se na to, že potrvá desítky hodin než budete schopni vytvořit neco jako "Morhuhn. Když vám bude někdo tvrdit , že VB nelze na tvorbu ve 3D použít tak nemá pravdu (pouze nás to bude stát trochu námahy navíc).

První věc kterou musíte udělat pokud chcete programovat v OpenGL je stáhnout si hnihovnu vbopengl.tlb z
http://home.pacific.net.hk/~edx/tlb.htm (bude přiložena také u "zdojáků" této lekce) a integrovat ji do systému dle návodu na stránkách. Nebo ji stačí jen přidat k projektu a vložit na ni odkaz (menu Project-->References). Tato knihovna obsahuje definice všech funkcí, typů, matic atd., jinak bychom je museli pracně definovat v každém novém projektu. Je to v podstatě takové "malé" API.

Pokud budou nějaké připomínky, dotazy můžete reagovat ve fóru nebo přímo na sid26@seznam.cz

Konfigurace PC na kterém to programuji a testuji:
CPU Intel
VGA GeForce
OS WinXP Pro
IDE VB6+SP6

Základní informace

Než napíšeme náš první OpenGL program, je nejdříve dobré vědět s kým máme tu čest. OpenGL (Open Graphics Library) je nízkoůrovňová knihovna pro práci s trojrozměrnou grafikou. Knihovna vznikla v roce 1992 u společnosti Silicon Graphics Inc (SGI). Jejím hlavním rysem je nezávislost na cílové platformě a použitém programovacím jazyku. Díky tomu se stala široce uznávaným a podporovaným standartem v oblasti aplikací CAD/CAM, virtuální reality a tvorby her. Najdeme ji např. v programech Catia, SolidWorks, Maya atd... U her třeba v Unreal, Half-Life, Sin a jiné. OpenGL je otevřený standard na jehož dalším vývoji se podílí hned několik společností působící v oblasti vývoje grafických zařízení. Každá nová verze musí projít náročnýmy testy několika firem než je uvedena na trh. Současná verze je 2.0. Více na http://www.opengl.org/

Oproti tomu Direct3D (což je konkurenční standard vyvíjený pouze firmou Microsoft) je možno použít jen ve Windows. V podstatě se jedná o balík komponent DirectDraw, DirectSound, Direct3D,..... atd. známé jako DirectX. Tato knihovna je navržena zejména na tvorbu her. Zda prochází nějakými testy je těžké říct, ale vzhledem k fregvenci nových verzí ..... :)

Nechme DirectX tam kde je a vraťme se k OpenGL.

Typický program začíná vytvořením okna, do kterého bude směřovat výstup. Nejprve se alokuje GL kontext (rendrovací kontext), což je jakýsi soubor stavových proměnných, který je unikátní pro každý grafický výstup. Jakmile se rendrovací kontext přiřadí k oknu, mohou se volat vlastní funkce OpenGL. (mimo rendrovacího kontextu se vytvoří i framebuffer, což je soubor zásobníků, ve kterých se uchovávají informace o scéně).

OpenGL je v podstatě balík cca 200 funkcí, procedur, proměnných, matic atd. K proměnným není přístup přímo, ale prostřednictvím k tomu určených funkcí. Je dobré také vědět, že takto nastavené proměnné si uchovávají svoji hodnotu dokud ji programátor opět nezmění, což mu značne ulehčuje práci. Vzhledem k přenositelnosti na jiné platformy na úrovni kódu, obsahuje OpenGL speciálně definované datové typy. Jejich pojmenování se řídí konvencí, že první dvě písmena jsou GL a potom následuje jméno typu.

OpenGL typVisual Basic typpříponamin. počet bitů
GlenumLongui32-bit
GlbooleanByteub8-bit
GlbyteByteb8-bit
GlshortIntegers16-bit
GlintLongi32-bit
GlsizeiLongi32-bit
GlubyteByteub8-bit
GlushortIntegerus16-bit
GluintLongui32-bit
GlfloatSinglef32-bit
GlclampfSinglef32-bit
GldoubleDoubled64-bit
GlclampdDoubled64-bit
Glvoidprázdný ukazatel  

Podobnou konvencí se řídí také pojmenování funkcí. Předpona určuje ze které knihovny funkce pocházejí "gl", následuje název funkce a nakonec její přípona. Funkce mohou mít stenou předponu a název ale rozdílnou příponu. Ta se skládá z číslice (tou se oznamuje kolik parametrů budeme předávat) a písmena (to určute typ dat jenž budeme předávat). Na základě techto informací potom knihovna zjisti která funkce je požadována.

Příklad: (upravené prototipy funkcí)

glVertex3f ( x as GLfloat, y as GLfloat, z as GLfloat)
funkce očekává tři hodnoty typu GLfloat -> 3f
glVertex2sv ( v(2) as GLshort)
funkce očekává dvě hodnoty zadané v poli typu GLshort -> 2sv
glVertex4d ( x as GLdouble, y as GLdouble, z as GLdouble, w as GLdouble)
funkce očekává čtyři hodnoty typu GLdouble -> 4d

OpenGL - vytvoření okna

Malé upozornění nepřenášejte zdrojáky z netu do vašeho projektu. Za prvé sem nebudu psát všechno, ale jen to co bude potřeba podrobněji vysvětlit a za druhé se může stát že se nějaké písmenko ztratí a nebude to fungovat. Na konci každé (skoro každé :) ) lekce vám dám zdrojáky. Nezapomeňte že jsou určeny pouze ke studiu, takže kód je záměrně psán co nejjednodušeji, tedy žádné optimalizace nečekejte. Tak úvod máme za sebou a pozvolna se do toho pustíme. Ostatní budu vysvětlovat za pochodu.

Nejdříve si založte nový projekt typu Standart.EXE, dále si pro usnadnění přidáme 1 standardní modul *.bas a 1 modul třídy *.cls. Zde jsou přesná nastavení jaká budu používat.

Formulář:
Name = frmMain
BorderStyle = 0
ScaleMode = 3 - Pixel (toto je důležité)

Standardní modul:
Name = mdlMain

Modul třídy:
Name = clsEngine

Teď si projekt uložte, přidejte k němu vbopengl.tlb a nastavte na něj "Rreferences". Dále také nastavte ve vlastnostech projektu že budeme startovat z funkce Main uložené ve standardním modulu.

Do formuláře budu psát jen to opravdu nejnutnější, do třídy Engine jen kód který se málo mění a často opakuje a nakonec standardní modul bude naše základna. Tak se do toho dáme. Začneme v našem modulu třídy.

Engine:

Public hrc As Long ' trvalý Rendering Context
Public active As Boolean ' ponese informaci o tom, zda je okno aktivní
Private Window As mWindow ' pomocna struktura naseho okna
Private Type mWindow
Width As Integer ' nova vyska
Height As Integer ' nova sirka
Bits As Integer ' nova bitova hloubka (8, 16, 24 nebo 32 bitu)
VertRefresh As Long ' nova obnovovaci frekvence monitoru
oldWidth As Integer ' soucana vyska
oldHeight As Integer ' soucana sirka
oldBits As Integer ' soucana bitova hloubka (8, 16, 24 nebo 32 bitu)
oldVertRefresh As Long ' soucasna obnovovaci frekvence monitoru
FullScreen As Boolean ' udrzuje informaci o tom zda jsme v okne nebo ve fullscreenu
End Type

Typ mWindow nám ulehčuje přehled o nastavení obrazovky pokud přecházíme z okna do fullscreenu a zpět.
Funkce pro vytvoení okna.

Public Function CreateWindow(ByRef frm As Form, Optional ByVal Width As Integer = 640, _
Optional ByVal Height As Integer = 480, Optional ByVal Bits As Integer = 16, _
Optional ByVal FullScreen As Boolean = False) As Boolean
Dim PixelFormat As GLuint, pfd As PIXELFORMATDESCRIPTOR
On Error GoTo Chyba

Abychom mohli pracovat s OpenGL musíme přemostit vykreslování na rozhraní OpenGL tím, že nastavíme tzv. PixelFormatDescriptor, který slouží jako komunikátor mezi výstupními rutinami windows a výstupními rutinami knihovny OpenGL. Poukazuje na to, jaké rozdíly jsou v zobrazování barev atd

With Window
.Width = Width
.Height = Height
.Bits = Bits
.FullScreen = FullScreen
.VertRefresh = -1
End With

If FullScreen Then
Call DisplaySetting
frm.WindowState = vbMaximized
End If

Pokud požadujeme fullscreen je potřeba nejdříve nastavit požadované rozlišení a další parametry viz. fce DisplaySetting. Dále naplníme strukturu PIXELFORMATDESCRIPTOR daty.

pfd.cColorBits = Bits
pfd.cDepthBits = Bits
pfd.dwFlags = PFD_DRAW_TO_WINDOW Or PFD_SUPPORT_OPENGL Or PFD_DOUBLEBUFFER Or PFD_TYPE_RGBA
pfd.iLayerType = PFD_MAIN_PLANE
pfd.iPixelType = PFD_TYPE_RGBA
pfd.nSize = Len(pfd)
pfd.nVersion = 1

Požádáme Windows, aby pro nás našel pixel format, který odpovídá tomu, který chceme.
PixelFormat = ChoosePixelFormat(frm.hDC, pfd)
If PixelFormat = 0 Then Err.Raise 1

Pokud je vrácena ne-nulová hodnota, je vše v pořádku a zkusíme ji tedy použít
If SetPixelFormat(frm.hDC, PixelFormat, pfd) = 0 Then Err.Raise 2

Nyní se pokusíme vutvořit Rendering Context
hrc = wglCreateContext(frm.hDC)
If hrc = 0 Then Err.Raise 3
a připojit ho k našemu oknu
If wglMakeCurrent(frm.hDC, hrc) = 0 Then Err.Raise 4
Vše je v pořádku, zkusíme vytvořit okno
If Not InitGL() Then Err.Raise 5

Nakonec už jen okno zobrazíme a předáme řízení hlavní funkci Main ve standardním module
frm.Show
SetForegroundWindow frm.hWnd
frm.SetFocus

CreateWindow = True
active = True
Exit Function

Chyba:

Err.Clear
KillWindow (frm)
CreateWindow = False

Select Case Err.Number
Case 1
MsgBox "Nepodaoilo se najít PixelFormat.", vbExclamation, "Chyba..."
Case 2
MsgBox "Nepodaoilo se nastavit PixelFormat.", vbExclamation, "Chyba..."
Case 3
MsgBox "Nepodaoilo se vytvooit Rendering Context.", vbExclamation, "Chyba..."
Case 4
MsgBox "Nepodaoilo se aktivovat Rendering Context.", vbExclamation, "Chyba..."
Case 5
MsgBox "Nepodaoilo se vytvooit okno.", vbExclamation, "Chyba..."
End Select

End Function

Další kód již nebudu popisovat. Jeho účel je snadné vyčíst z komentářů v kódu.

Public Sub KillWindow(ByRef frm As Form)
' uvolnuje RC a odstrani okno
If Window.FullScreen Then Call DisplaySetting(True)

If hrc Then
If wglMakeCurrent(0, 0) = 0 Then MsgBox "Uvolneni Rendering Contextu se nepodarilo.", vbInformation
If wglDeleteContext(hrc) = 0 Then MsgBox "Odstraneni Rendering Contextu se nepodarilo.", vbInformation
hrc = 0
End If
Unload frm
End Sub

Public Function InitGL() As Boolean
' inicializace OpenGL okna
glShadeModel smSmooth ' povolí jemné stínování
glClearColor 0#, 0#, 0#, 0# ' cerné pozadí RGB+Alpha
glClearDepth 1# ' nastavení hloubkového bufferu
glEnable glcDepthTest ' povolí hloubkové testování
glDepthFunc GL_LEQUAL ' typ hloubkového testování
glHint htPerspectiveCorrectionHint, hmNicest ' nejlepší perspektivní korekce
InitGL = True
End Function
Public Sub ReSizeGLScene(ByVal Width As GLsizei, ByVal Height As GLsizei)
' tato procedura je volana vzdy kdyz dojde ke zmene velikosti OpenGL okna
If Height = 0 Then Height = 1 ' zabezpeceni proti deleni nulou
glViewport 0, 0, Width, Height ' resetuje aktuální nastavení (jako LPRect)
glMatrixMode mmProjection ' zvolí projekcní matici
glLoadIdentity ' reset matice
gluPerspective 45#, Width / Height, 0.1, 600# ' výpocet perspektivy
glMatrixMode mmModelView ' zvolí matici Modelview
glLoadIdentity ' reset matice

End Sub

Public Function DisplaySetting(Optional Restore As Boolean = False) As Boolean
Dim dm As DEVMODE, dc As Long
' umoznuje menit rozliseni, frekvenci a barevnou hloubku monitoru

Select Case Restore
Case False ' jdeme z okna do fullscreenu
dc = CreateDC("DISPLAY", vbNullString, vbNullString, dm)

' zjisti aktualni nastaveni zobrazeni a ulozi jej do nasi struktury Window
With Window
.oldWidth = GetDeviceCaps(dc, HORZRES)
.oldHeight = GetDeviceCaps(dc, VERTRES)
.oldBits = GetDeviceCaps(dc, BITSPIXEL)
.oldVertRefresh = GetDeviceCaps(dc, VREFRESH)
dc = EnumDisplaySettings(0&, 0&, dm)

' overi zda je nove zobrazeni podporovano pokud ano tak je nastavi
dm.dmFields = DM_BITSPERPEL Or DM_PELSWIDTH Or DM_PELSHEIGHT
dm.dmPelsWidth = .Width
dm.dmPelsHeight = .Height
dm.dmBitsPerPel = .Bits
Call DeleteDC(dc)

' pokusi se aktivovat nove nastavene zobrazeni
If (ChangeDisplaySettings(dm, CDS_FULLSCREEN) <> &H0) Then
MsgBox "Fullscreen neni podporovan", vbCritical
DisplaySetting = False
.FullScreen = False
Else
DisplaySetting = True
.FullScreen = True
End If
End With

Case True ' jdeme z fullscreenu do okna
With Window
Call EnumDisplaySettings(0&, 0&, dm)
dm.dmFields = DM_BITSPERPEL Or DM_PELSWIDTH Or DM_PELSHEIGHT
dm.dmPelsWidth = .oldWidth
dm.dmPelsHeight = .oldHeight
dm.dmBitsPerPel = .oldBits
dm.dmDisplayFrequency = .oldVertRefresh

If (ChangeDisplaySettings(dm, &H0) <> &H0) Then
MsgBox "Pri zmene rozliseni nastala chyba", vbCritical
DisplaySetting = False
.FullScreen = False
Else
DisplaySetting = True
.FullScreen = True
End If
End With

End Select

End Function

Teď se přesuneme do standardního modulu:

Nejdříve si vytvoříme objektovou proměnnou, ta nám velice ulehčí volání v ní napsaných funkcí. Nemusíte si tak přesně pomatovat všechny jejich názvy a navíc máme kód pěkně uzavřen.
Public Engine As New clsEngine

Tady startuje náš program
Sub Main()
Nejdříve musíme vytvořit okno. Pokud chceme pracovat v okne, stačí zadat pouze odkaz na náš formulář.
V případě fullscreenu je potřeba uvest rozlšení, barevnou hloubku a "True" pro potvrzení že chceme pracovat ve fullscreenu. Pokud zadáte všechny hodnoty, ale poslední parametr necháte "False", pokusí se windows roztáhnout okno na zadané rozmery. Tohle si můžete upravit podle sebe. Já jsem to chtěl jenom zkusit :)
Pokud se nepodaří okno vytvořit tak končíme.
If Not (Engine.CreateWindow(frmMain, 640, 480, 16, True)) Then Exit Sub

Zde je naše hlavní smyčka. Odtud voláme funkci pro vykreslení a to tak dlouho dokud uživate nestiskne Escape viz. kód ve frmMain (je uveden níže)
Do
DrawScene
Call SwapBuffers(frmMain.hDC) ' prohození bufferu (Double Buffering)
DoEvents
Loop Until Not Engine.active
Pokud uživatel stiskl Escape musime uvolnit RC a všechny použité zdroje.
Call Engine.KillWindow(frmMain)
A konec
End Sub

Naše hlavní vykreslovací funkce. Vše co se zobrazí na monitoru pochází odsud.

Sub DrawScene()
glClear GL_COLOR_BUFFER_BIT Or GL_DEPTH_BUFFER_BIT ' vymaze obrazovku a hloubkový buffer
glLoadIdentity ' reset matice

glTranslatef 0#, 0#, -6# ' posun do hloubky X, Y, Z

' tady se bude kreslit
glBegin (GL_TRIANGLES) ' zacatek kreslení trojúhelníku
glVertex3f 0#, 1#, 0# ' horní bod
glVertex3f -1#, -1#, 0# ' levý dolní bod
glVertex3f 1#, -1#, 0# ' pravý dolní bod
glEnd ' ukoncení kreslení trojúhelníku

End Sub

Na závěr ještě uvedu kód z formuláře

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)

Select Case KeyCode

Case vbKeyEscape ' ukonceni programu
Engine.active = False
Case vbKeyF1 ' prepina z okna do fullscreenu
If Me.WindowState = 0 Then
Engine.DisplaySetting False
Me.WindowState = 2
Else ' a zpet do okna
Me.WindowState = 0
Engine.DisplaySetting True
Me.Left = (Screen.Width - Me.Width) / 2
Me.Top = (Screen.Height - Me.Height) / 2
End If
End Select

End Sub

Private Sub Form_Resize()
Call Engine.ReSizeGLScene(ScaleWidth, ScaleHeight)
End Sub

A zde je výsledek. Není to nic moc, ale pro začátek dobrý.



ZDROJOVÝ KÓD

© 2005 Jindřich Mach & Jan Ticháček