Farrisflaske
Bezierflater
Teori bak bezierflater
Evaluatorer brukes for å tegne kurver eller flater som er bezierbaserte. De gir oss muligheten til spesifisere punkter på en flate ved å bare bruke kontrollpunkter, derfra kan flater rendres slik du vil.
Med kontrollpunkter mener jeg punkter som sier noe om hvordan kurven se ut.
Denne kurven har fire kontrollpunkter. Punktene P0 og P3 er endepunktene til kurven.
Dette er punkter som kurven må gå igjennom,
mens P1 og P2 er punkter som er med på å forme kurven.
Disse punktene bestemmer hvor mye og i hvilken retning kurven vil bue seg.
Kurven vil ikke gå igjennom disse punktene, med mindre den er lineær.
Jo flere kontrollpunkter du har, jo mer kontroll har du over hvordan du kan bue kurven.
Bezierflater tegnes altså ved å spesifisere punkter på en overflate ved å bruke kontrollpunkter. Man kan også tegne flater ved å sette de sammen av mange små polygoner (ofte trekanter), men ved å bruke kontrollpunkter kan man beskrive flater med få parametre. På denne måten kan flaten rendres akkurat sånn man vil, man er ikke avhengig av faste geometriske figurer. Bezierflater er kan beskrives slik:
F(u,v)=[x(u,v)y(u,v)z(u,v)]
Det er altså en vektorfunksjon med to variable, der u og v kan variere. En bezierkurve beskrives med bare én variabel, mens flater beskrives med to. Dette er fordi en bezierflate kan beskrives som bezierkurve * bezierkurve, akkurat som et rektangel kan beskrives som lengde * bredde. Man kan egentlig tenke seg bezierflater som flere bezierkurver som ligger ved siden av hverandre i rommet. For hver u og v kalkulerer F() et punkt på flaten.
Tegne bezierflater
Dette må man gjøre for å kunne tegne bezierflater:
1)Definere evaluatoren med glMap2*
2)Enable dem, ved å sende med de riktige parametrene til glEnable().
3)Kalle evaluatoren, enten med glEvalCoord2() mellom en begin-end blokk, eller ved å spesifisere og så legge på et nett med glMapGrid2() og glEvalMesh2(). (Jeg har gjort på sistnevnte måte på min flaske).
- Jeg ser litt på noen av parametrene til metodene under neste avsnitt i modulen.
Flat:
Den beste måten å lære å lage bezierflater er å eksperimentere med kontrollpunkter, og å følge med på hva endringer i de forskjellige kontrollpunktene fører til. Jeg startet med å tegne ut en bezierflate som er et flatt rutenett:
Denne er tegnet slik:
int UN =4; int VN =4; float M[] = { -10.0f,-10.0f,0.0f, -5.0f,-10.0f,0.0f, 5.0f,-10.0f,0.0f, 10.0f,-10.0f,0.0f , -10.0f,5.0f,0.0f, -5.0f,5.0f,0.0f, 5.0f,5.0f,0.0f, 10.0f,5.0f,0.0f , -10.0f,5.0f,0.0f, -5.0f,5.0f,0.0f, 5.0f,5.0f,0.0f, 10.0f,5.0f,0.0f , -10.0f,10.0f,0.0f, -5.0f,10.0f,0.0f, 5.0f,10.0f,0.0f, 10.0f,10.0f,0.0f }; gl.glMap2f(GL_MAP2_VERTEX_3,0.0f,1.0f,3,UN,0.0f,1.0f,UN*3,VN,M); gl.glEnable(GL_MAP2_VERTEX_3); gl.glMapGrid2f(20, 0.0f, 1.0f, 10, 0.0f, 1.0f); gl.glEvalMesh2(GL_LINE, 0, 20, 0, 10);
Dette kan se skummelt ut, men mesteparten av dette er bare M[], altså kontrollpunktene til bezierflaten.
Forklaring av tegnekode
UN (bortover) og VN(oppover) forteller at denne flaten er bygget opp av 4 kurvede linjer i hver retning. Det ser du på kontrollpunktene i M[] også, der det er fire bolker med fire punkter. Hvis du ville hatt flere punkter den ene eller andre veien, må du i tillegg til å legge inn de kontrollpunktene i arrayen, huske å øke UN eller VN.
M[] er altså arrayen som inneholder alle kontrollpunktene som flaten skal tegnes opp av. Slik mitt koordinatsystem er satt opp, er første verdi x-verdien, andre verdien y-verdien og tredje verdi er z-verdien. X-verdien går horisontalt på skjermen, y-verdien vertikalt, mens z-verdien går "innover i skjermen". Sånn som punktene er satt opp her, har alle punktene bort over på samme rekke lik y-verdi, alle punktene oppover på samme rekke har lik x-verdi. Siden dette er en flate som ikke har noen retning i rommet, er z-verdien her satt til samme verdi, 0, for alle.
gl.glMap2f(GL_MAP2_VERTEX_3,0.0f,1.0f,3,UN,0.0f,1.0f,UN*3,VN,M);
Dette er en definering av evaluatoren. Nedenfor følger forklaring av parameterne:
1. parameter: Tredimensjonelle kontrollpunkter produserer tredimensjonelle vertexer.
2. parameter: Minste verdi av u.
3. parameter: Høyeste verdi av u.
4. parameter: U-avstanden i array.
5. parameter: U's orden.
6. parameter: Minste verdi av v.
7. parameter: Høyeste verdi av v.
8. parameter: V-avstanden i array.
9. parameter: V's orden
10. parameter: Array med kontrollpunktene.
gl.glEnable(GL_MAP2_VERTEX_3);
Enabler evaluatoren.
gl.glMapGrid2f(20, 0.0f, 1.0f, 10, 0.0f, 1.0f); gl.glEvalMesh2(GL_LINE, 0, 20, 0, 10);
Her kaller jeg evaluatoren ved å spesifisere og legge på nettet. Det er verdt å merke seg at når man tegner bezierkurver kan man velge mellom GL_LINE og GL_FILL, der GL_FILL genererer fyllte polygoner, mens GL_LINE tegner rutenettet.
Krummet:
For å gjøre denne flaten krummet, dvs. at den skal bli 3-dimensjonal, kan du endre z-verdiene til punktene i kontrollpunktarrayen M[]. Dersom du drar de to midterste punktene litt mot deg, og lar ytterpunktene på flaten være de samme, vil du få en krummet flate, formet som en halv sylinder.
Rund:
For å få en rund figur, tar man konturen beskrevet med den krummede flaten, og roterer den 180 grader rundt y-aksen. Deretter tegner du opp på nytt:
gl.glRotatef(180.0f,0.0f,1.0f,0.0f); gl.glEvalMesh2(GL_FILL,0,20,0,10);
Du vil da ende opp med to sammensatte bezierflater som tilsammen former en sylinder.
Gjennomsiktig matriale
Siden jeg skulle modellere en flaske, var det naturlig å sette seg inn i hvordan man får et materiale til å se gjennomsiktig ut. Jeg ville, at dersom jeg så flasken min bakfra skulle baksiden av etiketten synes igjennom flaskens materiale.
Når man ser på objekter gjennom farget glass, er den fargen du ser delvis objektets egen farge, delvis fargen til glasset. Fargene blandes altså. For å se effekten av de blandete fargene, må man skru kalle funksjonen som tillater dette:
gl.glEnable(GL_BLEND);
Deretter brukes funksjonen gl.glBlendFunc til å si noe om hvordan kildefaktoren skal beregnes og hvordan målfaktoren skal beregnes.
gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
Etter dette setter man opp lyset slik at det gir ønsket farge. Når man har enablet blending, brukes alfaverdien, dvs. A'en i RGBA (de fire parametrene som står i lyssettingen), til å si noe om hvor gjennomsiktig et objekt skal være. Dersom den settes til 1.0 er den ikke gjennomsiktig i det hele tatt, og hvis den settes til 0.0 vil ikke objektet syntes, fordi det da er helt gjennomsiktig, og slipper gjennom all farge fra bakgrunnen.
public void setMaterialBottle() { float amb[] = {0.1f, 0.73f, 1.0f, 0.5f}; float diff[] = {0.95f, 0.62f, 0.95f, 0.5f}; float spec[] = {1.0f, 1.0f, 1.0f, 0.5f}; float shine = 50f; gl.glMaterialfv(GL_FRONT, GL_AMBIENT, amb); gl.glMaterialfv(GL_FRONT, GL_DIFFUSE, diff); gl.glMaterialfv(GL_FRONT, GL_SPECULAR, spec); gl.glMaterialf(GL_FRONT, GL_SHININESS, shine); gl.glEnable(GL_LIGHT0); gl.glEnable(GL_LIGHTING); }
Denne metoden setter fargen til lys blå, og er ganske gjennomsiktig, da alfaverdien er satt til 0.5.
Dersom du skal ha noe som skal skinne gjennom det som er gjennomsiktig, for eksempel etiketten på en flaske, må du tegne opp det som skal skinne igjennom, altså etiketten, før du tegner opp det som det skal skinne igjennom, altså flasken.
Etter man har vist det man ønsket å ha gjennomsiktig, skur man av denne funksjonaliteten:
gl.glDisable(GL_BLEND);
Tekstur
For å få objektene du har laget med opengl til å se mer virkelighetsnære ut, kan du legge på en tekstur. Nedenfor viser jeg koden for hvordan jeg har lagt tekstur på flasken min. Jeg har ikke gått så nøye inn på hva hver kodelinje betyr her, bare hva du må gjøre for å få frem en tekstur. Dette er fordi det var det å modellere bezierflater som var mitt hovedtema for modulen.
Det første du må gjøre er å lage en array for texturen:
int []texName = new int[1];
Deretter må du, før du tegner det du skal ha tekstur på, enable teksturer:
gl.glEnable(GL_TEXTURE_2D);
I metoden der jeg tegner det objektet som skal ha tekstur, spesifiserer jeg først filnavnet, deretter kaller jeg metoden "textures", som laster bildet og lager teksturen. (Denne metoden er beskrevet litt lenger ned på siden). Deretter spesifiserer jeg hvordan teksturen skal legges på dette objektet.
String filnavn="vann.png"; //kaller teksturmetoden, for å loade bildet textures(filnavn); //Hvordan teksturen skal legges på gl.glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_CLAMP); gl.glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_CLAMP); gl.glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST); gl.glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); //modulate her for at lyset skal få noen effekt når det er tekstur gl.glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); //koordinatene til teksturen float textPoints[]= { 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, }; //gjør klar for å legge ut tekstur gl.glMap2f(GL_MAP2_TEXTURE_COORD_2, 0,1,2,2, 0,1,4,2,textPoints); gl.glEnable(GL_MAP2_TEXTURE_COORD_2);
Og her er metoden textures, som altså laster bildet og lager teksturen.:
public void textures(String filnavn) { //Laster inn bildet PngTextureLoader txtLoader = new PngTextureLoader(gl, glu); txtLoader.readTexture(filnavn); //Hvis det går greit å laste inn bildet så.. if(txtLoader.isOk()) { //Genererer en tekstur gl.glGenTextures(1,texName); gl.glBindTexture(GL_TEXTURE_2D,texName[0]); //Lager teksturen og gir informasjon om bildet. gl.glTexImage2D(GL_TEXTURE_2D, 0, 3, txtLoader.getImageWidth(), txtLoader.getImageHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, txtLoader.getTexture()); }
Tilslutt må du skru av teksturer igjen, før du skal tegne noe nytt som ikke skal ha samme tekstur, eller som ikke skal ha tekstur i det hele tatt:
gl.glDisable(GL_TEXTURE_2D);
Det er verdt å merke seg at du må tegne ut bezierflaten din med GL_FILL, for at teksturen skal synes ordentelig. Hvis ikke du gjør det, blir teksturen tegnet opp på linjene som bezierflaten deles opp i.
Det er også viktig å nevne at for at lyssettingen skal fungere ordentelig, bør du ha argumentet GL_MODULATE tilslutt i metoden gl.glTexEnvf. Dersom du har GL_REPLACE eller noe annet her, vil ikke lyset komme til sin rett. I denne sammenheng er det også viktig å ha beregnet normalene på flaten, eller å la openGL regne det ut for deg, ved å enable GL_NORMALIZE.
Fordeler og ulemper
Fordeler
For det første trenger vi bare å spesifisere kontrollpunktene for flaten når vi vil beskrive en bezierflate.
Et annet viktig poeng med bezierflater, er at flatene alltid går gjennom endepunktene som er spesifisert. Selv om den ikke går gjennom alle punktene, vet i hvertfall den som skal modellere flatene hvor flaten starter og ender, noe som gir en viss kontroll med flaten.
Ulemper
Bezierflater gir ikke nok lokal kontroll over fasongen på flatene. Dersom du endrer noen av punktene, vil dette påvirke hele flaten, ikke bare der du vil endre. Dette er fordi hvert kontrollpunkt er aktivt langs hele flaten, ikke bare akkurat der det ligger på flaten.
For å få den ønskede kurven på bildet over, ville jeg prøvd å flytte P2 og P3 litt opp, for å tvinge kurven nærmere slik jeg vil ha den, men det vil påvirke første halvdel av kurven også, og jeg ville fortsatt ikke fått kurven slik jeg vil ha den.
Flasken
Flasken min er bygget opp av fem bezierflater, i tillegg etiketten er en bezierflate. Figuren er altså seks bezierflater tilsammen.
Nedenfor fulger de ulike delene av flasken min, tegnet opp med linjer for å vise bezierflatenes oppbygning og med texturer ved siden av:
Etiketten
Jeg lager etiketten, og legger den rundt flasken:
Denne laget jeg med utgangspunkt i den flate, firkantede bezierflaten. Det jeg gjorde her, var først å rotere flaten 45 grader.
Deretter dro jeg de ytterste punktene i kantene lengst bakover, de innenfor der igjen litt mindre bakover, mens de midterste punktene fikk stå der de var. Slik fikk jeg formen på etiketten.
Toppen
Deretter begynner jeg med å lage toppen av flasken:
Denne delen er satt sammen av to like halvdeler, dvs. jeg har først tegnet halve ringen, rotert 180 grader rundt y, og så tegnet på nytt. Jeg har her fire punkter i høyden, hvor av tre er like "brede", mens ett er dratt lenger ut. Ved å dra dette ene punktet lenger ut, vil dette påvirke slik at flaten "buler ut".
Flaskehals
Så lager jeg flaskehalsen:
Denne delen av flasken er satt sammen av to halvdeler på samme måte som flasketoppen. Flaskehalsen skulle være helt rett, derfor ligger alle ytterpunktene på rekke over hverandre, det er ingen som "buler ut" slik som i flasketoppen.
Flaskens kropp
Det neste jeg lager er selve kroppen til flasken:
Som alle de andre delene av flasken, utenom bunnen og etiketten, er denne satt sammen av to halvdeler. Denne skulle formes slik at nederste del er smalere enn øverste del. Dette gjorde jeg ved at ytterpunktene får gradvis større absoluttverdi oppoverflasken, med et ekstra stort sprang på bredeste del, før jeg setter de til like brede som flaskehalsen på toppen av delen.
Bildet viser kontrollpunktene:
Nederste del
Nederste del av flasken ser slik ut:
Denne er også satt sammen av to halve rundete bezierflater. Den har fire punkter "oppover", der de to nederste har samme x-verdi, det 3. punktet er det punktet med høyest absoluttverdi, og det øverste er like bredt som nederste del av flaskens kropp.
Flaskebunnen
Det siste jeg lager på flaskekroppen er bunnen:
Den laget jeg ved først å ta utgangspunkt i en flat, firkantet bezierflate. Deretter dro jeg hjørnene innover, slik at kantene ble runde. Etter å ha justert kontrollpunktene, endte jeg opp med en flat, rund bezierflate. Men de fleste flaskebunner går jo litt innover i flasken igjen. Dette løste jeg ved å flytte det midterste kontrollpunktet høyt oppover, slik av rundingen fikk et "søkk" på midten.
Mulige utvidelser
- Glatte sidene på flasken