original in en Miguel A Sepulveda
en to nl Guus Snijders
Om ontwerp redenen was de OpenGL specificatie geïsoleerd van afhankelijkheden van venster systemen. De resulterende interface is een overdraagbare, gestroomlijnde en efficiënte 2D en 3D rendering library. Het is aan het venster (window) systeem om de vensters te openen en te tekenen. De OpenGL library communiceerd met het onderliggende systeem door additionele hulp libraries. Zo beschrijft de GLX hulp library bijvoorbeeld de interactie tussen OpenGL en het X window System.
De OpenGL Utility Toolkit (GLUT) is een programmeer-interface met ANSI C en FORTRAN bindingen voor het schrijven OpenGL programma's die onafhankelijk zijn van het onderliggende window systeem. Het is geschreven door Mark J. Kilgard en vult een groot gat dat was opengelaten door de OpenGL specificatie. Dankzij de GLUT ontwikkelaars kunnen we een algemene window system interface gebruiken, onafhankelijk van het doelsysteem. OpenGL applicaties die GLUT gebruiken kunnen eenvoudig worden geport tussen platforms zonder veel veranderingen aan de broncode. GLUT vereenvoudigd zeker de productie van OpenGL code en vult de OpenGL library aan.
GLUT is relatief klein een eenvoudig te leren. Het is goed ontworpen en de auteur heeft er reeds goede documentatie voor geschreven. Daardoor lijkt het starten van een serie artikelen hier in LinuxFocus een beetje redundant. We moedigen iedere serieuze ontwikkelaar aan om Mark's documentatie te lezen. Het doel van het schrijven van deze reguliere GLUT column is om de GLUT library en het gebruik ervan te introduceren en stap voor stap met voorbeelden een begeleiding te vormen voor de OpenGL series van dit magazine. We hopen dat dit een nuttige bijdrage zal zijn en meer programmeurs te motiveren op de Open-GL-Linux trein te springen. Zorg er in ieder geval voor dat je een kopie van Mark's documentatie krijgt als een goede referentie.
De GLUT API is een status machine, net als OpenGL. Dit betekend dat GLUT een aantal status variabelen heeft die 'leven' tijdens het uitvoeren van de applicatie. De initiële staten van de GLUT machine zijn redelijk gekozen om aan te sluiten bij de meeste applicaties. Het programma kan de waarden van de status (state) variabelen aanpassen wanneer dat nodig is. Als een GLUT functie wordt aangeroepen, wordt zijn actie aangepast aan de hand van de waarden van de status variabelen. GLUT functies zijn simpel, ze nemen enkele parameters. Er worden geen pointers geretourneerd en de enige pointers die aan GLUT functies worden meegegeven zijn pointers naar karakter strings en onduidelijke font handles.
GLUT functies kunnen in verschillende sub-APIs worden geclassificeerd naar functionaliteit:
Ieder OpenGL programma dat gebruik maakt van GLUT moet
beginnen met het initialiseren van de GLUT status machine.
De glut initialisatie functies beginnen met glutInit-.
De hoofd initialisatie functie is glutInit:
Gebruik:
glutInit(int **argcp, char
**argv);
argcp is een pointer naar
de onveranderde argc programma variabele van main. Bij terugkeer
wordt de waarde waarnaar argcp verwijst, geüpdate omdat
glutInit commando regel opties relevant voor de GLUT library
oppikt, bijvoorbeeld: onder de X Windows System omgeving,
worden opties die relevant zijn voor X venster geaccosieerd met
het GLUT venster.
argv is de ongewijzigde argv variabele
van main.
glutInit zorgt voor het initialiseren van de GLUT
status variabelen en onderhandeld de sessie met het windows
systeem. Er zijn enkele routines die voor glutInit kunnen
voorkomen; alleen routines die beginnen met glutInit-.
Deze routines kunnen gebruikt worden om de default
venster-initialisatie status te zetten. Bijvoorbeeld:
Gebruik:
glutInitWindowPosition(int x,
int **y);
glutInitWindowSize(int breedte,
int **hoogte);
x,y = schermpositie van het venster in
pixels (linker bovenhoek)
breedte,hoogte van het venster in pixels.
Er is nog een andere initialisatie routine alom aanwezig in
iedere OpenGL applicatie, glutInitDisplayMode():
Gebruik:
glutInitDisplayMode(unsigned int mode);
mode is
is de Display (weergave) mode, een bitsgewijze OR of GLUT display
mode bit masker. De mogelijke bitmasker waarden zijn:
GLUT_RGBA | Selecteer een RGBA mode venster. Dit is de standaard als geen GLUT_RGBA noch GLUT_INDEX is opgegeven. |
GLUT_RGB | zelfde als GLUT_RGBA. |
GLUT_INDEX | Selecteer kleur index venster mode. Dit overschrijft GLUT_RGBA. |
GLUT_SINGLE | Selecteer een enkel gebufferd venster. Dit is de standaard. |
GLUT_DOUBLE | Selecteer een dubbel gebuffered venster. Dit overschrijft GLUT_SINGLE. |
GLUT_ACCUM | Selecteer een venster met een accumulatie (verzamel) buffer. |
GLUT_ALPHA | Selecteer een venster met eenn alpha component in de kleuren buffer(s). |
GLUT_DEPTH | Selecteer een venster met een depth (diepte) buffer. |
GLUT_STENCIL | Selecteer een venster met een stencil buffer. |
GLUT_MULTISAMPLE | Selecteer een venster met multismapling ondersteuning. |
GLUT_STEREO | Selecteer een stereo venster. |
GLUT_LUMINANCE | Selecteer een stereo venster met een "luminance" kleur model. |
#include <GL/glut.h>
void main(int argcp, char **argv){
/* Geef venster grootte en locatie */
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Selecteer Display mode type:
Single buffer & RGBA color */
glutInitDisplayMode(GLUT_RGBA | GLUT_SINGLE);
/* Initialiseer GLUT status */
glutInit(&argcp, argv);
.....meer code
};
Ten tweede een voorbeeld van een animatie programma:
#include <GL/glut.h>
void main(int argcp, char **argv){
/* Geef venster grootte en locatie */
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Selecteer Display mode type:
Dubbele buffer & RGBA kleur */
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
/* Initialiseer GLUT status */
glutInit(&argcp, argv);
.....meer code
};
We zullen terugkomen op deze twee voorbeelden terwijl we verdergaan met het leren over GLUT. Het grootste verschil is in het tweede geval het venster wordt geïnitialiseerd in een dubbele buffer modus, ideaal voor animaties omdat het flikkerende effecten elimineerd tijdens het veranderen van frames in de animatie serie.
Zoals eerder genoemd is GLUT een status machine. Nu zullen
we zien dat het ook ontworpen is als een gebeurtenis-gestuurde
machine. Dit betekend dat er een "timer" of continue lus is
die gestart wordt na de juiste initialisaties en die, een voor
een, alle gebeurtenissen afhandeld die gedeclareerd zijn aan GLUT
tijdens de initialisatie. Gebeurtenissen zijn: een muisklik, het
sluiten van een venster, vergroten/verkleinen van een venster,
verplaatsing van de cursor, indrukken van toetsen op het
toetsenbord, en vooral bijzonder de "idle" gebeurtenis, oftewel
als er niets gebeurd! Elk van de mogelijke gebeurtenissen moet
geregistreerd worden in een van de GLUT status variabelen voor
de "timer" of gebeurtenis-verwerkings lus van GLUT om periodiek
te controleren of die gebeurtenis is gestart door de gebruiker.
Zo zouden we bijvoorbeeld een "klik met een muisknop" als een
gebeurtenis kunnen registeren waar GLUT op dient te letten.
Gebeurtenissen worden geregistreed via
callback registration routines. Deze hebben de syntax
glut[eenGebeurtenis]Func, in het geval van de muisklik
zou het glutMouseFunc zijn. Een callback registratie
verteld de GLUT engine welke gebruiker-gedefinieerde functie
aangeroepen moet worden als de bijbehorende gebeurtenis wordt
"getriggerd". Dus, als ik mijn eigen routine MyMouse
schrijf, welke specificeerd wat er moet gebeuren als de linker
muisknop wordt ingedrukt (of de rechter, enz.), dan kan ik mijn
callback functie registreren na de glutInit() in
main() met het statement "glutMouseFunc(MyMouse);".
Welke callback functies en gebeurtenissen zijn toegestaan in GLUT zullen we later bekijken. Het belangrijkste nu is dat nadat alle belangrijke gebeurtenissen in onze applicatie zijn geregistreerd, we de gebeurtenis-afhandelings routine van GLUT moeten starten, dit is glutMainLoop(). De functie komt niet meer terug, ons programma gaat in feite een oneindige lus in. Het zal callbacks die eerder zijn geregistreerd aanroepen wanneer nodig. Iedere main() voor een OpenGL applicatie dient te eindigen in een glutMainLoop() statement. In het geval van ons animatie voorbeeld wordt dit dus:
#include <GL/glut.h>
void main(int argcp, char **argv){
/* Initialiseer GLUT status */
glutInit(&argcp, argv);
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Open een venster */
glutCreateWindow("Mijn OpenGL Applicatie");
/* Selecteer Display mode type:
Double buffer & RGBA color */
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
/* Registreer Callback Functies */
.....
/* Start Gebeurtenis Afhandeling Engine */
glutMainLoop();
};
Merk op dat ik wat extra code heb toegevoegd die we nog niet eerder hebben genoemd. Het is een van GLUT's venster beheer routines, glutCreateWindow(char **name). Dit vind ik nu zo mooi aan de OpenGL & GLUT ontwerp filosofy, het is vrij duidelijk wat de routine doet, door alleen naar de naam te kijken! Het zorgt ook voor het doorgeven van de opdracht aan het onderliggende venster systeem om een venster te openen voor onze OpenGL applicatie. Het venster krijgt de naam "name" mee als een karakter string. In de X Window omgeving wordt deze naam op de linkerbovenhoek van het venster geschreven. Het venster beheer deel van GLUT heeft vele andere functies die we uiteindelijk zullen bekijken. Voor het moment is deze voldoende. Ik heb ook de initialisatie routines gesorteerd om te laten zien dat deze ook na glutInit() kunnen worden geplaatst.
Terug naar gebeurtenissen... Ik wil nu twee callback registratie functies introduceren die erg fundamenteel zijn in ieder animatie programma. De glutDisplayFunc welke de display functie voor het huidige venster zet en glutIdleFunc, welke de idle callback zet. Beide registratie routines verwachten een functie van het type void *(void). Stel dat we twee additionele callback functies voor ons animatie voorbeeld willen schrijven, void MyDisplay(void), welke zorgt voor het aanroepen van de OpenGL instructies die in feite onze scene op het venster schrijven en void MyIdle(void), een functie om aangeroepen te worden als er geen invoer van de gebruiker is, dat is, iedere keer als de gebeurtenis afhandelings machine van GLUT de oneindige lus rondt (glutMainLoop()) en geen nieuwe gebeurtenis start, verwerkt het MyIdle. Waarom zou ik een Idle callback functie willen registeren in een animatie programma? Omdat als we elk van de afbeeldingen (frames) willen weergeven tijdens de animatie, onafhankelijk van gebruikers-invoer, er een functie moet zijn (de idle callback functie) die telkens wordt aangeroepen tijdens het uitvoeren van het OpenGL programma en de frames veranderd, voordat deze worden getekend door Mydisplay().
Tenslotte is hier een eenvoudig voorbeeld voor een animatie
programma:
#include <GL/glut.h>
void MyIdle(void){
/* Wat code om de variabelen aan te passen voor het
volgende frame */
....
};
void MyDisplay(void){
/* Wat OpenGL code die een frame tekend */
....
/* Na het tekenen van het frame wisselen we de buffers
*/
glutSwapBuffers();
};
void main(int argcp, char **argv){
/* Initialiseer GLUT status */
glutInit(&argcp, argv);
glutInitWindowSize(640, 480);
glutInitWindowPosition(0, 0);
/* Open een venster */
glutCreateWindow("Mijn OpenGL Applicatie");
/* Selecteer Display mode type:
Double buffer & RGBA color */
glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
/* Registreer Callback Functies */
glutDisplayFunc(MyDisplay)
glutIdleFunc(MyIdle)
/* Start Gebeurtenis Afhandelings Engine */
glutMainLoop();
};
Merk op dat ik aan het eind van MyDisplay ik een nieuwe GLUT routine heb toegevoegd, glutSwapBuffers(). Deze is erg bruikbaar in animaties. We gebruiken een venster in DUBBELE buffer modus, een weergegeven en een verborgen. De tekenende OpenGL instructies renderen in dit geval altijd in de verborgen buffer. Het aanroepen van glutSwapBuffers verwisseld de buffers en geeft in het venster wat er was getekend. Deze techniek komt veel voor in computer animaties omdat het voorkomt dat het menselijk oog ziet hoe het frame lijn voor lijn wordt opgebouwd.
Er is reeds genoeg materiaal om te beginnen met het schrijven van OpenGL applicaties. Het enige dat mist zijn de OpenGL instructies in MyDisplay die zorgen voor eigenlijke tekenen...maar dat is een ander verhaal ;-).
In het volgende artikel over GLUT programmeren verkennen we
de functionaliteit die ons beschikbaar wordt gesteld in het
Window Management (venster beheer) deel van GLUT en hoe we
meerdere scenes kunnen openen in hetzelfde venster. We zullen
ook leren over het gebruik van menus, inclusief de voor- en
nadelen ervan voor hun overdraagbaarheid (portability).