Content extract
OpenGL grafikus rendszer alkalmazása Visual C++ rendszerben Az NT 3.51-es verziótól kezdve az OpenGL részévé vált a Windows operációs rendszernek Az új és nagy teljesítményű grafikus kártyák használatával a házi PC-ken is elérhetővé váltak a hatékony grafikus rendszerek. Az OpenGL-t azoknak ajánlják akik érdeklődnek a minőségi két és három dimenziós grafika után. A rendszer majdnem minden platformon létezik, és a legtöbb modern programozási nyelven használható. Ebben a leírásban a C++ kapcsolatát fogom röviden bemutatni, Windows95 alatt. Feltételezem, hogy az olvasó alapszinten jártas a C++ nyelvben, és használta már a Visual C++ fejlesztő környezetet. A GL interfészt eredetileg a Silicon Graphics készítette, de később másokat is érdekelt ez az új grafikai standard. (például a Microsoft is megvásárolta néhány évvel ezelőtt.) Fő erőssége, hogy létrehozható vele 3 dimenziós grafika anélkül, hogy a grafika
matematikai alapjait ismernénk. A rendszer támogat: grafikus primitíveket, 2 és 3 dimenziós transzformációt, megvilágítási lehetőségeket, árnyékolást, Z-buffer eljárást, takart élek felületek eltávolítását, és nem utolsó sorban hálózati kommunikációs lehetőségeket, stb. Van még egy nagyon fontos előnye más rendszerekkel ellentétben, mégpedig az, hogy nem kell hozzá licens, azaz az applikáció fejlesztők szabadon használhatják. Az alap OpenGL program Az első példaprogram bemutat egy egyszerű Windows programot amelynek csak annyi a feladata, hogy a OpenGL használatához szükséges minimum követelményeket beállítsa, azaz nyisson egy ablakot, amelybe majd lehet rajzolni. (A második példában jönnek a rajzolási lehetőségek leírásai.) A GDI -nek (lásd: grafika windows alatt, ennek magyarázatára itt nem térek ki) szüksége van Device Context (DC) kontextusra, az OpenGL -nek egy Rendering Contex (RC) kontextusra. Az RC egy adott
szálban jön létre (thread), ekkor minden egyes OpenGL hívás erre, a szálban létrehozott RC-re fog hivatkozni. Nézzük milyen alapvető lépésekre van szükség a Rendering Context létrehozásához. 1. Az ablak aktuális pixel formátumát kell beállítani 2. Létre kell hozni magát az RC-t 3. A létrehozott RC-t aktualizálni kell A következő lépések segítségével lehet létrehozni a Visual C++ fejlesztőrendszerben a projectet. (A vc++ lapvető kezelését már tudottnak feltételezem, a leírás további részeiben is, hiszen ez a segédlet az OpenGL rendszerről szól alapvetően.) Létre kell hozni egy új Project Workspace-t az AppWizard segítségével. Legyen a neve GLPelda1 A megjelenő dialógus ablak kitöltése legyen a következő: 1. Single Document Interface 2. Database support: none 3. OLE support: None 4. Docking Toolbar: OFF, Initial Status Bar: OFF, Printing an Print Preview: ON, Context-Sensitive Help: OFF, 3D Controls: ON 5. Generate
source File Comments: Yes. 6. Use the MFC library as a shared DLL 7. Ha kész akkor nyomd meg a Finish gombot Szinte mindegy mit állítunk be, a fontos az, hogy single document interface legyen beállítva. Az új project ezzel létrejött. A következő lépés az lesz, hozzá kell adni a projecthez a linkelendő könyvtárakat Ezt a Build-Setting menüben lehet megtenni, ha a Link fület választjuk. Aztán a Generál kategóriát (ez a default), ez gépeljük be az Object/Library Modules mezőbe a következőket: opengl32.lib glu32lib glauxlib Most nyissuk meg az stdafx.h-t és egészítsük ki a következő sorokkal: #include <glgl.h> #include <glglu.h> Ezzel megtörtén e két feltétlenül szükséges incude file beépítése a projectbe. Az ablak stílusát be kell állítani WS CLIPCHILDREN , WS CLIPSIBLINGS típusúra, amihez az OnPreCreate függvényt a következőképpen kell módosítani: BOOL CPelda1View::PreCreateWindow(CREATESTRUCT& cs) {
cs.style |= (WS CLIPCHILDREN | WS CLIPSIBLINGS); return CView::PreCreateWindow(cs); } A következő lépés az ablak pixel formátumának definiálása. A pixel formátum megadja, hogyan lesz az ablak a memóriában tárolva. Pl színmélység, stb Létre kell hozni egy protected tagfüggvényt a CGLSample1View osztályban, melynek neve BOOL SetWindowPixelFormat(HDC hDC) legyen. Ez a függvény a következő sorokat tartalmazza: BOOL CPelda1View::SetWindowPixelFormat(HDC hDC) { PIXELFORMATDESCRIPTOR pixelDesc; pixelDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR); pixelDesc.nVersion = 1; pixelDesc.dwFlags = PFD DRAW TO WINDOW | PFD DRAW TO BITMAP | PFD SUPPORT OPENGL | PFD SUPPORT GDI | PFD STEREO DONTCARE; pixelDesc.iPixelType = PFD TYPE RGBA; pixelDesc.cColorBits = 32; pixelDesc.cRedBits = 8; pixelDesc.cRedShift = 16; pixelDesc.cGreenBits = 8; pixelDesc.cGreenShift = 8; pixelDesc.cBlueBits = 8; pixelDesc.cBlueShift = 0; pixelDesc.cAlphaBits = 0; pixelDesc.cAlphaShift = 0;
pixelDesc.cAccumBits = 64; pixelDesc.cAccumRedBits = 16; pixelDesc.cAccumGreenBits = 16; pixelDesc.cAccumBlueBits = 16; pixelDesc.cAccumAlphaBits = 0; pixelDesc.cDepthBits = 32; pixelDesc.cStencilBits = 8; pixelDesc.cAuxBuffers = 0; pixelDesc.iLayerType = PFD MAIN PLANE; pixelDesc.bReserved = 0; pixelDesc.dwLayerMask = 0; pixelDesc.dwVisibleMask = 0; pixelDesc.dwDamageMask = 0; m GLPixelIndex = ChoosePixelFormat( hDC, &pixelDesc); if (m GLPixelIndex==0) { m GLPixelIndex = 1; if(DescribePixelFormat(hDC,m GLPixelIndex,sizeof(PIXELFORMATDESCRIPTOR), &pixelDesc)==0) return FALSE; } if (SetPixelFormat( hDC, m GLPixelIndex, &pixelDesc)==FALSE) return FALSE; return TRUE; } Nem kell ettől a terjedelmes résztől megijedni, a magyarázatát mindjárt meg fogom adni. Szükséges egy új váltózó is. (A függvények és változók deklarálását megkönnyíti a ClassWizard használata, vagy az egérrel a bal oldali osztály hierarchián állva, a megfelelő osztály nevét
tartalmazó téglalapon jobb egérgombbal a legördülő menü tartalmazza az ‘add member variable / function’ pontot. A többi már magától érthető lesz ) int m GLPixelIndex; // protected Végül le kell kezelni a WM CREATE üzenetet, ehhez az OnCreate függvényt is létre kell hozni. int CPelda1View::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; HWND hWnd = GetSafeHwnd(); HDC hDC = ::GetDC(hWnd); if (SetWindowPixelFormat(hDC)==FALSE) return 0; return 0; } Most pedig le lehet fordítani a programot, miután kijavítottuk az esetleges hibákat. A fordítás után megjelenik egy egyszerű ablak, amely képes lesz a következőkben az OpenGL grafikus ablakként működni. Térjünk vissza az iménti terjedelmes rész magyarázatához: PIXELFORMATDESCRIPTOR tartalmazza a pixel formátumára vonatkozó adatokat, a fontosabbakat mindjárt látni is fogjuk, aki kíváncsi a teljes leírásra az a VC++ online helpben megtalálja. •
dwFlags definiálja az interfészeket és eszközöket amivel a pixel formátum kompatíbilis: PFD DRAW TO WINDOW: Rajzolás az ablakba lesz. PFD DRAW TO BITMAP: Rajzolás egy bittérképbe lesz, ami ebben az esetben a memóriába rajzolást jelenti. PFD SUPPORT GDI: Engedélyezi a GDI függvényhívásokat PFD SUPPORT OPENGL: Engedélyezi az OpenGL hívásokat PFD GENERIC FORMAT: Meghatározza, hogy a pixel formátumot a GDI, vagy a grafikus kártya eszköz vezérlője határozza meg. PFD NEED PALETTE: Azt mondja meg kell e szín paletta. PFD NEED SYSTEM PALETTE: Szükséges e rendszer paletta. (a bemutatott példák mindegyike 16 illetve 24, 32 bit-es színmélységben működik. Ez manapság már nem nagy követelmény, mert a grafikus kártyák szinte mindegyike 800*600-as felbontásban tud 16 bit-es színmélységet. Ehhez egyébként 1MB grafikus memória elegendő. PFD DOUBLEBUFFER: Bekapcsolja a dupla bufferelés lehetőségét. Ez a lehetőség animációk
készítésénél jó, mivel szinte láthatatlanná teszi a képernyő frissítés zavaró hatását. Fontos dolog, hogy a GDI nem támogatja a dupla bufferelést, ezért ha ezt is beállítjuk, akkor a PFD SUPPORT GDI kapcsolót nem szabad használni. PFD STEREO: Sztereó, térhatású képek jeleníthetőek meg e kapcsoló használatával. • iPixelType a színek megjelenítési metódusát állítja be, ami lehet RGB vagy indexelt. Az RGB a piros, zöld, kék komponensek értékét külön állíthatóvá teszi. ( e mód használatát javasolja a szakirodalom ) Az indexelt (PDF TYPE COLORINDEX) módszer hasonló a palettához, amelyben előre definiált színekkel dolgozik, és a színekre sorszámmal lehet hivatkozni. • cColorBits megadja hány biten legyenek a színek ábrázolva, itt lehet megadni pl. 16, 24, 32 bitet (pl a 24 bit 256*256256 féle szín kikeverhetőségét biztosítja.) Indexelt módban pedig a palettán elhelyezhető színek számát adja meg, azaz a
palettaméretet. • cRedBits, cGreenBits, cBlueBits, CAlphaBits megadja, hány bitesek legyenek az egyek színkomponensek. • cRedShift, cGreenShift, cBlueShift, CAlphaShift megadja az egyes komponensek eltolási értékét, azaz pl. a piros szín hanyadik bittől kezdődik. Érdemes ezt a struktúrát egyszer jól beállítani a rendszerünkhöz, aztán pedig minden egyes programíráskor újra felhasználni. m hGLPixelIndex = ChoosePixelFormat(hDC, &pixelDesc); Ez a függvény két paraméteres, visszatérő értéke egy index ami a pixel formátumra vonatkozik. Nulla esetén valami hiba történt. Nem lehet végtelen sok pixel formátum, a rendszer előre definiálja ezek lehetséges számát Abban az esetben, ha már a lista betelt, akkor a függvény a legközelebbi hasonló már meglevő pixel formátum indexét adja vissza. A pixel formátumot csak egyszer lehet beállítani! Most hogy a pixel formátum be lett állítva, létre kell hoznunk a rendering kontextust,
amihez a CreateViewGLContext függvénnyel kell kiegészíteni a programunkat. BOOL CPelda1View::CreateViewGLContext(HDC hDC) { m hGLContext = wglCreateContext(hDC); if (m hGLContext == NULL) return FALSE; if (wglMakeCurrent(hDC, m hGLContext)==FALSE) return FALSE; return TRUE; } A következő változó hozzáadása is szükséges: HGLRC m hGLContext; // protected Ki kell egészíteni az OnCreate függvény is a következő képpen: int CPelda1View::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CView::OnCreate(lpCreateStruct) == -1) return -1; HWND hWnd = GetSafeHwnd(); HDC hDC = ::GetDC(hWnd); if (SetWindowPixelFormat(hDC)==FALSE) return 0; if (CreateViewGLContext(hDC)==FALSE) return 0; return 0; } Le kell kezelni a WM DESTROY üzenetet is, ehhez létre kell hozni az OnDestroy függvényt. void CPelda1View::OnDestroy() { if(wglGetCurrentContext()!=NULL) wglMakeCurrent(NULL, NULL) ; if (m hGLContext!=NULL) { wglDeleteContext(m hGLContext); m hGLContext = NULL; }
CView::OnDestroy(); } Az OnDestroy függvény abban az esetben lesz végrehajtva, ha az ablak megszűnik, ekkor megszüntetjük az RC-t. És végül a CGLSample1View osztály konstruktorát a következőképpen módosítsuk: CPelda1View::CPelda1View() { m hGLContext = NULL; m GLPixelIndex = 0; } Ez nem más mint a két általunk deklarált változók kezdőértékeinek beállítása. Fordítsuk le még egyszer a programot, miután az esetleges hibákat kijavítottuk. Indítás után a program nem sokban különbözik látszólag egy hagyományos MFC programtól, de mostmár lehetőségünk lesz rajzolni bele az GL segítségével. Meg kell említeni, hogy ebben a programban az elején létrehoztuk az RC-t azután használtuk az ablak bezárásáig. Ez ellentétben áll a GDI-ben megszokott módszerrel, amikor is a DC-t akkor kell létrehozni, amikor rajzolás szükséges, ha már nem kell akkor megszűnik rögtön. Ez a módszer lehetséges lenne a RC létrehozásánál is, de
tudni kell, hogy az RC létrehozása nagyon processzorigényes feladat. Ha fontos a teljesítmény, akkor mindenképpen egyszer szabad csak létrehozni az RC-t a program elején, és majd a program bezárásakor megszüntetni. A CreateViewGLContext függvény hozza létre és aktualizálja a render contextust (RC). Ez átadja a RC-nek a window HDC -jét, e hívás előtt a pixel formátumot már be kell állítani. Az ezt követő wglMakeCurrent aktualizálja a létrehozott RC-t. Ha esetleg a wglMakeCurrent hívás előtt más RC aktív akkor ez a függvényhívás egyszerűen átkapcsol az új RC-re. Ha a következő paraméterezést használjuk, akkor nem lesz beállítva semmi aktív kontextusként: wglMakeCurrent(NULL,NULL). Mivel az Ondestroy() felszabadítja az ablak kontextusát, ezért célszerű ekkor a render kontextust is kikapcsolni, de kikapcsolás előtt de - aktivizálni kell. Ezután a wglDeleteContext() fogja megszüntetni az RC-t Megjegyzem, a wglDeleteContext()
hívást végre lehet hajtani anélkül, hogy wglMakeCurrent(NULL,NULL) hívást is használnánk, de ekkor többszálú programok esetén komoly hibákat okozhat! Egyszerű kétdimenziós grafika Ebben a részben a kétdimenziós grafika néhány alaplehetőségével fogunk megismerkedni, például hogyan lehet nézőpontot létrehozni, hogyan lehet a különböző mátrix módokat beállítani, hogyan lehet 2-D rajzokat rajzolni. Kiinduláshoz kellene az előző példaprogram, amit most tovább fogunk bővíteni, felvértezve most már valódi rajzoló képességekkel. A ClassWizardot használva bővítsük az OnSize függvény hozzáadásával a programunkat. Ez a függvény akkor fog automatikusan meghívódni, amikor az ablak mérete változik, és még akkor, mikor az ablak először jelenik meg a képen. Megkapja cx, cy változókban az ablak méretét, ez azért fontos, mivel az OpenGL-nek tudnia kell, mekkora is a rajzolható terület. void CPelda1View::OnSize(UINT nType, int
cx, int cy) { CView::OnSize(nType, cx, cy); GLdouble aspect; GLsizei w,h; w = cx; h = cy; aspect = (GLdouble)w/(GLdouble)h; glViewport(0, 0, w, h); glMatrixMode(GL PROJECTION); glLoadIdentity(); gluOrtho2D(0.0, 1000*aspect, 0.0, 1000); glMatrixMode(GL MODELVIEW); glLoadIdentity(); } Ezek után pedig újra a ClassWizard segítségével hozzunk létre az OnPaint függvényt, amely a WM PAINT üzenet kezelését végzi. void CPelda1View::OnPaint() { CPaintDC dc(this); // device context for painting glLoadIdentity(); glClear(GL COLOR BUFFER BIT); //POLIGON LETREHOZASA glBegin(GL POLYGON); glColor4d(1.0,00,00,10); glVertex2d(50.0,200); glColor4d(0.0,00,10,10); glVertex2d(80.0,500); glColor4d(1.0,10,00,10); glVertex2d(60.0,600); glColor4d(1.0,00,10,10); glVertex2d(50.0,800); glColor4d(0.0,05,10,10); glVertex2d(40.0,600); glColor4d(5.0,05,00,10); glVertex2d(20.0,500); glEnd(); //INNEN LESZ A KIRAJZOLAS glFlush(); } Fordítsuk le és indítsuk el a programot. Ha minden jól megy akkor
látszani fog egy csillagszerű alakzat, amely szép színekben pompázik, nagyon jól reagál az ablak változásaira, ha átméretezzük az ablakot az alakzat mérete is arányosan változik. A viewport az ablak azon része ahová rajzolhat az OpenGL Nézzük a következő hívást: glViewport(0,0,w,h); Az első két paraméter az viewport bal alsó koordinátáját adja meg, a mésik kettő pedig a méretét vízszintes, és függőleges irányban. Ebben az esetben a viewport az ablak 0,0 pontjában kezdődik, és a mérete megegyezik az ablak méretével, a CView::OnSize(nType, cx, cy); hívás visszaadja az ablak aktuális méretét cx, cy változókban. Ki lehet próbálni, mit eredményez, ha a viewportot más értékekkel hívjuk meg, például: ha azt akarjuk, hogy a w/4 h/4 pontban kezdődjön és w/2 h/2 széles, illetve magas legyen a viewport, akkor a paraméterezés a következő lesz: glViewport(w/4,h/4,w/2,h/2); Abban az esetben, ha a viewport nagyobb mint az ablak
mérete, akkor a Windows rendszer automatikusan vág, a kilógó részekkel nem kell foglalkozni. Ezek után nézzük sorra az alkalmazott függvényhívásokat: A első hívás a glMatrixMode(GL PROJECTION). Az OpenGL rendszer három belső mátrixot tart fenn a különböző transzformációkhoz, ezek név szerint a Projection , a ModelView, és Texture. A projekciós mátrix tartalmazhat különböző leképzési módokat, párhuzamos leképzés, stb. A ModelView mátrix a modeltérben elvégezhető transzformációkért felelős, pl.: egy objektum elforgatása, eltolása stb A textúra mátrix felel a textúrák un mappolásáért, ami a textúra adott felületre ráhelyezését jelenti, adott textúra koordináták segítségével. A glMatrixMode nem csinál mást mint beállítja az aktuális mátrixot, amelyen a elkövetkező mátrixoperációkat el kell végezni. Ha nem elég világos ez a rész ajánlom az Ábrázoló Geometria tanszék által vitt Számítógépi Grafika és
Geometria tárgyat, amely mélyebben taglalja az ilyen mátrixokkal végezhető műveleteket a számítógépes grafikában. A glLoadIdentity hívás inicializálja a projekciós mátrixot, a glOrtho2d beállítja a projekciós mátrixot olyanra, amilyen a kétdimenziós leképzésekkor szükséges. Az átadott paraméterek meghatározzák azt a teret, amelyben a leképzés végrehajtódik. Ha ezzel a mátrixszal végzünk transzformációt, akkor az hatással lesz a teljes kirajzolandó objektumokra, azok méretére és elhelyezkedésére egyaránt. Például ha meghívunk egy glRotated(30,0,0,1) hívást a glBegin() előtt az OnPaint() - ban akkor azt fogjuk tapasztalni, hogy az alakzatunk elfordult 30 fokkal az origó körül. Az OnPaint() függvény rajzolja a csillagszerű alakzatot. Az első lépésben töröljük a ModelView mátrixot, ez nem is igazán szükséges, hiszen nem végeztünk transzformációt, ha végeztünk volna akkor szükséges lenne. Ezután a color
buffert kell törölni (ami ebben az esetben a képernyőre mutat, rajzolhatnánk bitmap-ba is, ha a render kontextust olyan típusúra állítjuk be.) A következő hívás a glBegin(GL POLYGON), amely már a rajzolást készíti elő, létrehoz egy GL POLYGON belső objektumot a render kontextuson belül, amelyet az őt követő parancsok fognak definiálni addig, amig egy glEnd() hívás nem következik. A példában a glVertex3d és glColor4d hívások definiálják az alakzat geometriáját és színét Most pedig szeretnék néhány mondatot az OpenGL rendszer névkonvencióinak szentelni. Minden OpenGL parancs gl előtaggal kezdődik, a glu előtaggal kezdődő neveknél az adott parancs a GL Utilities -hez tartozik. Ezek valójában egyszerű OpenGL parancsokat tartalmaznak, de az egyszerűség, a gyorsaság, és a kód rövidsége miatt kaptak külön nevet. (ilyen volt a példánkban az ortografikus mátrix beállítása.) A legtöbb gl parancsnak sok változata van, ami a
sokféle paraméterátadási lehetőségnek köszönhető. Például a glVertex2d parancs definiál egy rajzelemet (vertex) amely 2 paraméteres, és double típusú. Az Online dokumentáció tartalmazza az összes lehetséges variációt, itt megmutatok néhányat: glVertex2d, glVertex2f, glVertex3i, glVertex3f, glVertex2sv, glVertex3dv. Például az utolsó hívás egy olyan vertexet definiál, ami 3 koordinátát tartalmaz, és vektorban várja a paramétereit, azaz egy double coord[3]; változó esetén - glVertex3dv[coord] -hívással meg lehet adni, - ez a megoldás akkor előnyös, ha fontos a sebesség. Az példabeli alakzat definiálására a következő technikát használjuk. Először meghívjuk a glColor4d(10,00,00,10), ez állítja be az aktuális rajzolási színt pirosra, méghozzá azzal a módszerrel, hogy az első három komponens az R G B intenzitását állítja, jelen esetben az R intenzitását 1-re azaz maximumra állítottuk be, a másik kettő nulla
intenzitású. Megadhatunk egynél nagyobb számokat is paraméternek, a GL minden esetben normálni fogja, a három értéket, azaz egységvektort készít belőle. Ekkor az (1,1,1) ugyanolyan intenzív fehér szín lesz mint az (100,100,100) Ezután jön a már sokat említett glVertex hívás amely paramétereivel megad egy síkbeli pontot, mit az alakzat egyik csúcspontját. Ezután újabb színállítás következik, majd újabb pontok, és végül az egészet egy glFlush() hívás zárja le. A glFlush hívás hajtja végre a kirajzolást. Mivel minden ponthoz külön szint rendeltünk, így az OpenGL automatikusan interpolálja a színeket a pontok között. A következő példaprogram fogja megmutatni, hogyan lehet a mátrixot manipulálni, az alap transzformációkat, és a mátrixok verem műveleteit. glBegin(Glenum mode) paraméterek: GL POINTS, GL LINE STRIP, GL LINE LOOP, GL TRIANGLES, GL TRIANGLE STRIP, GL QUADS, GL QUAD STRIP .stb A következő függvényeket lehet a
glBegin és glEnd blokkban meghívni:glVertex, glColor, glIndex, glNormal, glTexCoord, glEvalCoord, glEvalPoint, glMaterial . Transzformációk és mátrix vermek A mostani rész egy kicsik komolyabb példát mutat be, amelyből el lehet sajátítani a mátrix műveleteket, a megjelenítési listák kezelését (listák), a mátrix vermeket, és a dupla bufferelést. Kiindulási alap lesz az előző példaprogram, amelyet kisebb-nagyobb módosításokkal tovább lehet fejleszteni, mégpedig olyanná, hogy három darab téglalapot lehessen mozgatni, az egér segítségével, mintha egy robotkart szimbolizálnának. Mintha a téglalap egyes pontjain tudna a másik téglalap elmozdulni. Olyan programozási módszert választottam amiben egy kis kivétellel továbbra is a View osztályt fogjuk módosítani. Először is módosítani kellene az OnPaint függvényt, az előző példaprogramból maradt ‘szemetet’ el kell távolítani. A kirajzolás most is e függvény segítségével
lesz, de más módon void CPelda1View::OnPaint() { CPaintDC dc(this); // device context for painting Show(); SwapBuffers(dc.m pshdc); } Látható, hogy a függvény tartalmaz egy Show() hivatkozást, ami majd az OpenGL rajzoló parancsait fogja tartalmazni. Erről majd később Ezen kívül létre kellene hozni néhány változót, amit majd a CPelda1View osztály konstruktorában lesznek inicializálva. double m transX; double m transY; double m angle1; double m angle2; double m angle3; Ezek fogják tartalmazni a transzformációval és az elforgatásokkal kapcsolatos értékeket. Inicializálás: CPelda1View::CPelda1View() { // TODO: add construction code here m angle1=15.0; m angle2=15.0; m angle3=15.0; m transX=100.0; m transY=100.0; m hGLContext = NULL; m GLPixelIndex = 0; } m transY - y eltolási érték a kar eltolása a világ koordináta rendszerben m transX - x eltolási érték a kar eltolása a világ koordináta rendszerben m angle1 - elforgatási szög a világ
koordináta rendszerhez képest m angle2 - elforgatási szög a kar első darabjához képest m angle3 - elforgatási szög a kar második darabjához képest Mi display listákat fogunk használni a kar részeinek kirajzolásához. A lista nem más mint OpenGL parancsok egymás után, amire névvel lehet hivatkozni a későbbiekben. A display listák általában már előre feldolgozottak, és ezért előnyük a gyorsabb újrafelhasználásban jelentős. A hátrányuk pedig az, hogy a későbbiekben nem lehet őket módosítani, tehát statikusak Nem lehet belőlük a bennük tárolt információt vissza olvasni. A létrehozott listát a glCallList függvényhívással lehet aktivizálni, amelyben meg kell adni a lista nevét, azaz egy integer számot. A listát legkésőbb a CPelda1Document osztály OnNewDocument tagfüggvényében lehet létrehozni, a következő módon: BOOL CPelda1Doc::OnNewDocument() { if (!CDocument::OnNewDocument()) return FALSE; // TODO: add reinitialization
code here // (SDI documents will reuse this document) double vect[]={-20.,10,-20,-20,150,-20,150,10}; glNewList(1,GL COMPILE); glBegin(GL POLYGON); glVertex2dv(vect); glVertex2dv(vect+2); glVertex2dv(vect+4); glVertex2dv(vect+6); glEnd(); glEndList(); return TRUE; } Akarattal a glVertex2dv függvényhívást használtam, amivel így például előre kiszámolt pontokat is könnyen lehet ábrázolni. Az első hívás a glNewList, aminek a nevében is benne van a feladata, ami nem más mint egy új lista létrehozása Az első paraméter a neve, a második pedig lehet GL COMPILE, vagy GL COMPILE AND EXECUTE. Az utóbbi esetben a lista létrehozás közben ki is rajzolódik, az előbbi esetben, amit a példában is használunk, a megjelenítés majd később lesz esedékes. Bonyolult számítások esetén érdemes listákat használni, mert akkor a rendszer megjelenítéskor csak a konkrét számértékeket kapja meg, így a például használni kívánt animáció lényegesen
gyorsabb lehet. Nem szabad elfelejteni a glEndList hívás lezárásként, mivel elmulasztásánál nem fog semmi megjelenni, mivel a rendszer minden kirajzolást a listához fűz. Ebben az esetben nem használok a listában színeket, és transzformációt sem, de minden további nélkül lehet ezeket is használni. Bővebb felvilágosítást az Online Help ad az érdeklődőknek Most már jöhet egy kis rajzolás: void CPelda1View::Show(void) { glClear(GL COLOR BUFFER BIT); glColor4d(0.0,00,10,10); glCallList(1); glFlush(); } Ha lefordítás után lefuttatjuk a programot akkor a képernyő bal alsó részén egy kék téglalap fog megjelenni. Adjuk hozzá még a következő két sort a függvényhez: glTanslated(m transX,m transY,0.); glRotated(m angle1,0.,0,1); Ez a két parancs a ModelView mátrixra van hatással, mégpedig először elforgatja az m angle1 szöggel, aztán pedig eltolja m transX, és m transY értékkel. Ha lefordítottuk a programot, akkor próbáljuk ki a
következőt: programfutás közben az ablakot egy kissé méretezzük át, vagy probaljuk kiváltani valahogy a WM PAINT üzenetet, ekkor azt tapasztaljuk az méretváltozás mellett tovább tolódik illetve fordul a kis téglalap. Mi ennek az oka? Az hogy a mátrix minden kirajzolásnál újra és újra meg lett szorozva, és az eredeti állapot elveszett. Ahhoz hogy ez ne történjen meg használni kell a mátrix vermet, amelybe a mátrixokat el lehet tárolni, illetve elő lehet venni újból. Nézzük meg a következő módosítást void CPelda1View::Show(void) { glClear(GL COLOR BUFFER BIT); glPushMatrix(); glTanslated(m transX,m transY,0.); glRotated(m angle1,0.,0,1); glColor4d(0.0,00,10,10); glCallList(1); glPopMatrix(); glFlush(); } A glPushmatrix() készít egy másolatot az aktuális mátrixról, és a verem tetejére helyezi. Amikor a glPopMatrix() kerül meghívásra akkor az leemeli a verem tetejéről az előzőleg ráhelyezett mátrixot. Tehát amíg a
glPushMatrix() megőrzi az inicializált mátrixot, azután a glPopMatrix visszaállítja az eredetit. Ennek a technikának van egy másik, jelentősebb előnye, ennek segítségével egymásra lehet építeni geometriailag az objektumokat, mint például a robotkart is. Itt még nem szabad megállni, írjuk tovább a programot: void CPelda1View::Show() { glClear(GL COLOR BUFFER BIT); glPushMatrix();////////////////////////////////////// glTranslated( m transX, m transY, 0); glRotated( m angle1, 0, 0, 1); glPushMatrix();////////////////////////////////// glTranslated( 140, 0, 0); glRotated( m angle2, 0, 0, 1); glPushMatrix();////////////////////////// glTranslated( 140, 0, 0); glRotated( ++m angle3, 0, 0, 1); glColor4d(0.0, 00, 06, 10); glCallList(1); glPopMatrix();/////////////////////////// glColor4d(0.0, 00, 08, 10); glCallList(1); glPopMatrix();/////////////////////////////////// glColor4d(0.0, 00, 10, 10); glCallList(1); glPopMatrix();///////////////////////////////////////
glFlush(); } Indítás után látható három darab egymásra épülő téglalap. Először egy eltolást és egy elforgatást hajt végre a függvény, aztán lementi a mátrixot, ekkor a rendszer úgy viselkedik, mintha a koordinátarendszer el lenne tolva, és forgatva a megadott módon, ezután jön egy eltolás x irányban 140 egységgel. Ekkor a már előzőleg elforgatott x irányba lesz az eltolás értelmezve, és ez még a következő utasítás hatására el lesz forgatva angle2 szöggel is. A következő PushMatrix hívás újra letárolja ezt az eltranszformált mátrixot, és folytatja a transzformációt. Ezután színváltás következik, és a display lista meghívása, ezzel ki is lett rajzolva világos kék színnel a harmadik téglalap, a PopMatrix hívás után a rendszer visszaállítja a mátrix eredeti állapotát. És az előző transzformált helyzetben ki is lehet rajzolni a következő téglalapot Aztán ez a lépéssor egyszer még végrehajtódik. Ezzel
meg is jelenik a három téglalap Ezután kellene valamiféle animációt gyártani hozzá, amit úgy fogunk megtenni, hogy egér segítségével lehet majd forgatni a téglalapokat, és a harmadik téglalap minden fázisban 1 fokkal el fog fordulni a kettes számúhoz képest. Ennek a megoldásához szükség lesz néhány új változó deklarálásához CPoint m jobblepont; //inicializálás (0,0) CPoint m ballepont; //inicializálás (0,0) BOOL m balegergomb, //inicializálás FALSE BOOL m jobbegergomb; //inicializálás FALSE Mivel az egér kezelésével szeretnénk az animációt vezérelni, ezért szükség lesz négy darab függvényre amelyek az egér gomblenyomásait figyelik. Az események a következők WM LBUTTONDOWN, WM LBUTTONUP, WM RBUTTONDOWN, WM RBUTTONUP lesznek. void CPelda1View::OnLButtonUp(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default m balegergomb=FALSE; CView::OnLButtonUp(nFlags, point); } void
CPelda1View::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default m balegergomb=TRUE; m ballepont= point; CView::OnLButtonDown(nFlags, point); } void CPelda1View::OnRButtonUp(UINT nFlags, CPoint point) { m jobbegergomb=FALSE; CView::OnRButtonUp(nFlags, point); } void CPelda1View::OnRButtonDown(UINT nFlags, CPoint point) { m jobbegergomb = TRUE; m jobblepont = point; CView::OnRButtonDown(nFlags, point); } Ezen kívül még szükség lesz egy függvényre amely a WM MOUSEMOVE esemény fogja lekezelni. void CPelda1View::OnMouseMove(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default if (m jobbegergomb) { CSize rotate = m jobblepont - point; m jobblepont = point; m angle1 += rotate.cx/3; m angle2 += rotate.cy/3; InvalidateRect(NULL, FALSE); } if (m balegergomb) { CSize translate = m ballepont - point; m ballepont = point; m transX -= translate.cx/3; m transY += translate.cy/3;
InvalidateRect(NULL, FALSE); } CView::OnMouseMove(nFlags, point); } Most pedig fordítsuk le a programot. A bal egérgomb lenyomása és az egér mozgatásával a robotkar mozgatható a képernyőn, a jobb egérgombbal és az egér jobbra - balra mozgatásával a kar egyik része fog forogni, fel és le mozgatásnál a másik része fog elfordulni, ezenkívül a harmadik karrész, mindig egy fokot fordul el a második karrészhez képest. Most megfigyelhető hogy a grafika elég darabos, a képernyő frissítése zavaróan hat, mivel villog minden egyes fázisváltásnál. Senki ne aggódjon ezt könnyen ki lehet küszöbölni a már említett dupla bufferelés technikájával. A dupla bufferelés egy nagyon egyszerű koncepciója a nagyteljesítményű grafikus rendszereknek. Ahelyett, hogy egy buffert képeznénk a képernyőre, kettőt használunk. (a számításokat az OpenGL rendszer először egy bufferbe végzi, amit a windows rendszer ablakban megjelenít). Az egyik buffer
mindig látszik, ez a front buffer, a másik buffer pedig, amely a back buffer nevet viseli takart, ne látható. Mi mindig a következő fázist a back bufferben számítjuk ki, és ha kész akkor a két buffer szerepét felcseréljük, ezzel elérve a gyors váltást, és az úgymond háttérben való számítást. Sajnos a GDI nem támogatja a dupla bufferelés technikáját, emiatt a GDI és az OpenGL parancsokat nem lehet együtt használni, tehát ha a dupla bufferelés be van kapcsolva, akkor a GDI-t ki kell kapcsolni. Első dolog az lesz, hogy ezentúl az ablak újrarajzolását előidéző InvalidateRect(NULL) függvény helyett az InvalidateRect(NULL,FALSE) függvényt használjuk, elkerülve vele a zavaró villogást. A dupla bufferelés bekapcsolásához meg kell változtani a CPelda1View osztályban a SetWindowPixelFormat függvényben a pixelDesc.dwFlags definícióját a következőképpen pixelDesc.dwFlags = PFD DRAW TO WINDOW | PFD SUPPORT OPENGL | PFD DOUBLEBUFFER |
PFD STEREO DONTCARE; A következő feladat az lesz, meg kell határozni mikor rajzolunk a back bufferbe, és miker lesz felcserélve a két buffer. Ezt a következőképpen lehet megtenni glDrawBuffer(GL BACK); Abban az esetben amikor a egy új képet rajzolunk ki akkor mindig meg kell cserélni a két buffert, amit a View osztály OnPaint függvényében: SwapBuffers(dc.m pshdc); Lefordítás és futtatás után rögtön látszik, hogy az animáció egyáltalán nem villog. Bár a program egy kicsit lassabb lett. Az utolsó példaprogram a három dimenziós grafika használatát fogja bemutatni, amelynek keretében két gúlát fogunk kirajzolni. A háromdimenziós grafika A most bemutatásra kerülő példaprogram a három dimenziós grafika alapjait fogja bemutatni az OpenGL rendszerben. Megtudjuk belőle, hogyan lehet perspektivikus leképzést beállítani, hogyan lehet definiálni három dimenziós objektumokat a modell térben. Ez a rész feltételez némi
előképzettséget a grafika terén, akinek nem lesz valami érthető az próbáljon utánanézni más forrásokból. Kiindulásként most is az előző programot választjuk, amely véső változatában már dupla bufferelt volt. A program megírásához most is olyan módszer használtam amelyben csak a CPelda1View osztályt kell bővíteni. A rész végén egy két függvényhívás erejéig megismerkedünk a GL Utilities lehetőségeivel is. A program egyébként két csúcsával egymásba fordított gúlát rajzol ki, és az egyik tetején egy hengerrel, amelynek belső fala más színű mint a külső. Most pedig nézzük a program fejlesztésének lépéseit: Az első dolog az lesz, hogy az OnSize -ban át kell írni az gluOrtho2D függvényt, ami egyébként három dimenzióban is működik, mégpedig úgy mintha a közeli vágósík -1 a távoli pedig 1-re lenne beállítva. Tehát minden két dimenziós függvényhívás úgy működik, mintha 3 dimenzióban a z koordináta
mindig nulla lenne. Tehát jöhet a OnSize megoldása: void CPelda1View::OnSize(UINT nType, int cx, int cy) { CView::OnSize(nType, cx, cy); GLdouble aspect; GLsizei w,h; w = cx; h = cy; aspect = (GLdouble)w/(GLdouble)h; glViewport(0, 0, w, h); glClearColor(0.4f,04f,04f,10f); glMatrixMode(GL PROJECTION); glLoadIdentity(); gluPerspective(45,aspect,1,10.0); glMatrixMode(GL MODELVIEW); glLoadIdentity(); glEnable(GL LIGHT0); glEnable(GL LIGHTING); glEnable(GL DEPTH TEST); glDrawBuffer(GL BACK); } A gluPerspective(45,aspect,1,10.0); került az előző leképzés helyére Ennek paraméterei a következők Az első paraméter a nézési szöget állítja be, ami jelen esetben 45 fok., az ezt követő aspect érték nem más mint egy arányszám, mint látható az ablak szélessége osztva az ablak magasságával. Ezzel állítjuk ,hogy ne legyen torz a kép A következő paraméterek a közeli és a távoli vágási síkot állítják be, 1-re illetve 10-re, azok az elemek amelyek ebből a
tartománybál kiesnek azok vágásra kerülnek, és nem fognak megjelenni az ablakban. A glEnable(GL LIGHT0); bekapcsolja az egyes számú fényforrást, a rendszer 8 darab különálló fényforrással tud számolni, fontos tudni, hogy több fényforrás esetén a számítási idő négyzetesen arányos fényforrások számával, tehát jól meg kell gondolni hány fényforrást akar a programozó használni a megjelenítés során. A glEnable(GL LIGHTING); bekapcsolja a ‘világítást’, ez által lesznek a görbe felületek élethűek. A glEnable(GL DEPTH TEST); függvényhívás a már említett Z-Buffer eljárást kapcsolja be, ami lehetővé teszi a takart felületek eltávolítását. Nézzük meg ezután hogyan alakul a Show függvény: void CPelda1View::Show() { glLightModelf(GL LIGHT MODEL TWO SIDE,TRUE); GLfloat LightAmbient[] = { 0.1f, 01f, 01f, 01f}; GLfloat LightDiffuse[] = { 0.7f, 07f, 07f, 07f}; GLfloat LightSpecular[] = { 0.0f, 00f, 00f, 01f}; GLfloat
LightPosition[] = { 10.0f, 100f, 100f, 00f}; GLfloat Red[] = { 1.0f, 00f, 00f, 10f}; GLfloat Green[] = { 0.0f, 10f, 00f, 10f}; GLfloat Blue[] = { 0.0f, 00f, 10f, 10f}; GLfloat Yellow[] = { 1.0f, 10f, 00f, 00f}; GLfloat vBlue[] = { 0.0f, 00f, 60f, 10f}; glClear(GL COLOR BUFFER BIT | GL DEPTH BUFFER BIT); glLightfv(GL LIGHT0, GL AMBIENT, LightAmbient); glLightfv(GL LIGHT0, GL DIFFUSE, LightDiffuse); glLightfv(GL LIGHT0, GL SPECULAR, LightSpecular); glLightfv(GL LIGHT0, GL POSITION, LightPosition); glPushMatrix(); glTranslated(0.0, 00, -80); glRotated(m xRotate, 1.0, 00, 00); glRotated(m yRotate, 0.0, 10, 00); glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Red); glBegin(GL POLYGON); glNormal3d( G2, 0.0, G2); glVertex3d( 1.0, 10, -10); glVertex3d( 0.0, 00, 00); glVertex3d( 1.0, -10, -10); glEnd(); glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Green); glBegin(GL POLYGON); glNormal3d( 0.0, G2, G2); glVertex3d( 1.0, 10, -10); glVertex3d( -1.0, 10, -10); glVertex3d( 0.0, 00, 00); glEnd();
glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Blue); glBegin(GL POLYGON); glNormal3d( -G2, 0.0, G2); glVertex3d( -1.0, -10, -10); glVertex3d( 0.0, 00, 00); glVertex3d( -1.0, 10, -10); glEnd(); glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Yellow); glBegin(GL POLYGON); glNormal3d( 0.0, -G2, G2); glVertex3d( -1.0, -10, -10); glVertex3d( 1.0, -10, -10); glVertex3d( 0.0, 00, 00); glEnd(); glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Red); glBegin(GL POLYGON); glNormal3d( 0.0, 00, -10); glVertex3d( -1.0, -10, -10); glVertex3d( -1.0, 10, -10); glVertex3d( 1.0, 10, -10); glVertex3d( 1.0, -10, -10); glEnd(); glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Red); glBegin(GL POLYGON); glNormal3d( G2, 0.0, -G2); glVertex 3d( 0.0, 00, 00); glVertex3d( 1.0, 10, 10); glVertex3d( 1.0, -10, 10); glEnd(); glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Green); glBegin(GL POLYGON); glNormal3d( 0.0, G2, -G2); glVertex3d( 0.0, 00, 00); glVertex3d( -1.0, 10, 10); glVertex3d( 1.0, 10, 10); glEnd();
glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Blue); glBegin(GL POLYGON); glNormal3d( -G2, 0.0, -G2); glVertex3d( 0.0, 00, 00); glVertex3d( -1.0, -10, 10); glVertex3d( -1.0, 10, 10); glEnd(); glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Yellow); glBegin(GL POLYGON); glNormal3d( 0.0, -G2, -G2); glVertex3d( 0.0, 00, 00); glVertex3d( 1.0, -10, 10); glVertex3d( -1.0, -10, 10); glEnd(); glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Red); glBegin(GL POLYGON); glNormal3d( 0.0, 00, 10); glVertex3d( -1.0, -10, 10); glVertex3d( 1.0, -10, 10); glVertex3d( 1.0, 10, 10); glVertex3d( -1.0, 10, 10); glEnd(); glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Blue); glMaterialfv(GL BACK, GL AMBIENT AND DIFFUSE, vBlue); glTranslated(0.0,00,1); GLUquadricObj *cyl1; cyl1=gluNewQuadric(); double R=0.5,H=15; gluCylinder(cyl1,R,R,H,20,1); glPopMatrix(); } Ahhoz hogy ez rész kifogástalanul működjön (le lehessen fordítani) az m xRotate és m yRotate változókat azért deklarálni kell a szokásos
módon, természetesen double típusura. Ezzel még nincs minden kész, mert el kell távolítani a jobb egérgombot kezelő függvényeket, a bal egérgomb kezelője marad, aztán a OnMouseMove függvényt is át kell írni, a következőképpen: void CPelda1View::OnMouseMove(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default if (m balegergomb) { CSize rotate = m ballepont - point; m ballepont = point; m yRotate -= rotate.cx/2; m xRotate -= rotate.cy/2; InvalidateRect(NULL, FALSE); } CView::OnMouseMove(nFlags, point); } Jól látszik ,hogyha a bal egérgomb le van nyomva, akkor az aktuális és az előző koordinátából lehet kiszámítani a két forgási szöget. GLfloat Red[] = { 1.0f, 00f, 00f, 10f}; GLfloat Green[] = { 0.0f, 10f, 00f, 10f}; GLfloat Blue[] = { 0.0f, 00f, 10f, 10f}; GLfloat Yellow[] = { 1.0f, 10f, 00f, 10f}; GLfloat vBlue[] = { 0.0f, 00f, 60f, 10f}; Ezen színeket fogja használni a grafika, ezek a színek most egy-egy
felület színét fogják megadni. Ezeket a színeket most nem a glColor függvény, hanem egy új, a glMaterial fogja paraméterként felhasználni. glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Green); glBegin(); • glEnd(); glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Blue); glBegin(); • glEnd(); A felhasználási példából jól látható, hogy hasonlóan kell használni, mint más színező függvényt, tehát ameddig új anyagot nem adunk meg, addig az előző lesz érvényben. Az anyagot meghatározó függvény első paramétere azt adja meg, melyik oldal színe lesz definiálva jelen esetben GL FRONT az elejét mutatja. Az, hogy melyik oldal az eleje, a felületből kifelé mutató normális vektor iránya dönti el. Ha egy felület definiálásakor kiszámítottuk a normálvektort, akkor már csak arra kell vigyázni, hogy jobb sodrással adjuk meg a vertexeket, mert egyébként nem érjük el a kívánt hatást. A második paraméter a GL AMBIENT AND DIFFUSE
hatására majd visszatérek. Az utolsó paraméter nem más mint a szín vektor. glLightModelf(GL LIGHT MODEL TWO SIDE,TRUE); E függvényhívás feladata nem más mint beállítani a két oldalas világítást, ami a felületek mindkét oldalának jó minőségű számítását teszi lehetővé. Most szeretnék visszatérni a glMatrialfv -re mivel az első paraméterét GL BACK -re állítva a hátsó oldalak színe állítható. glMaterialfv(GL BACK, GL AMBIENT AND DIFFUSE, vBlue); A példában világoskékre állítottam a henger belső színét. glClear(GL COLOR BUFFER BIT | GL DEPTH BUFFER BIT); A már megszokott glClear függvényhívás itt ki van egészítve GL DEPTH BUFFER BIT kapcsolóval. A már említett Z-buffert algoritmusnak szüksége van az inicializálásra, törlésre, minden újra kirajzolásnál szükséges törölni a Zbuffert. Ha még vissza tudunk emlékezni az első leckére, akkor ott említettem, hogy a PIXELFORMATDESCRIPTOR cDepthBits paramétere tárolja a
buffer méretét. Jól meg kell választani ezt az értéket mivel, ha nem optimális, akkor nagy cpu idő, és felesleges tárkapacitás használat veszélye áll fenn. A z buffer tartalmaz minden egyes pixelhez egy értékét. Ez az érték azt mutatja meg, milyen közel helyezkedik el a pixel a nézőponttól. Mindenkor, amikor az OpenGL egy pixelt rajzol, akkor megnézi, hogy az új szín közelebb van e a nézőponthoz mint a régi szín, ha igen akkor a pixelnek beállítja az új színt. Ha nem akkor megmarad a régi szín Ezzel a módszerrel hatékonyan el lehet érni a takart felületrészek eltakarását. Most beszéljünk egy kicsit a fényforrások beállításairól. glLightfv(GL LIGHT0, GL AMBIENT, LightAmbient); glLightfv(GL LIGHT0, GL DIFFUSE, LightDiffuse); glLightfv(GL LIGHT0, GL SPECULAR, LightSpecular); glLightfv(GL LIGHT0, GL POSITION, LightPosition); Ez a példa négy jellemzőét állítja be a nullás számú fényforrásnak. Az ambient érték egy olyan
jellegű fény, aminek nem lehet definiálni a helyét, hanem hasonló a felhős éghez, amikor nem lehet megállapítani a nap helyét, de mindenki látja, hogy világos van. Persze ez a fénytípus önmagában eléggé tompa színeket eredményez A megadott utolsó paraméter minden esetben a megadott típusú megvilágítás színe. A diffuse adja magát a napfényt a tiszta égbolton, ha már a példáknál maradok, általában a színét az 1,1,1 fehér színnel szokták megadni. A specular a visszaverődő fénykomponenst adja meg, amit azt hiszem nem kell magyarázni A GL POSITION a fényforrás helyét adja meg a modelltérben, vigyázni kell, nehogy közel kerüljön a megvilágítandó felülethez, mert akkor a fényforrás ugyan nem fog látszani, de a felület fehér színű lesz. A fényforrás nem süt a szemünkbe soha, ellentétben a nappal, ha mégis ezt szeretnénk akkor mindenképpen szükséges egy gömböt definiálni aminek olyan anyagjellemzőt kell adni, hogy
fény kibocsátó. Egyéb részletesebb leírásokban ez is megtalálható, most nem térek ki rá glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Blue); glMaterialfv(GL BACK, GL AMBIENT AND DIFFUSE, vBlue); glTranslated(0.0,00,1); GLUquadricObj *cyl1; cyl1=gluNewQuadric(); double R=0.5,H=15; gluCylinder(cyl1,R,R,H,20,1); A bonyolítás kedvéért vettem a példába ezt a részt, amely a már említett GL Utilities lehetőségekből mutat be egyet. Ez a rész egy hengert rajzol ki a gluCylinder(cyl1,R,R,H,20,1); segítségével, ahol a két R paraméter azért megegyező mert ugyanezzel a függvénnyel kúpot is lehet rajzolni, amikor is a második R nulla lesz. Az ezt követő paraméterek a kirajzolási finomságot állítják, minél nagyobbak ezek az értékek annál finomabb rajzolatú lesz a henger, és annál lassabb lesz az animáció. A glMaterialfv(GL FRONT, GL AMBIENT AND DIFFUSE, Blue); -ben az ambient, és diffuse értékek együttes beállításával lehet elérni a
legéletszerűbb képminőséget