Bezier
Hanne Moen / Student 2002
Å tegne:>Flaske

Farrisflaske

Hva
flaskeIntro2
Demonstrasjon av bezier, teksturer og gjennomskinnelighet

Jeg har bestemt meg for å modellere bilder/h2o_stor.jpg. Gjennom dette vil jeg fortelle om å manipulere bezierflaters form og litt om å legge texturer på disse. Jeg vil også sette meg inn i det å vise gjennomsiktig materiale. Jeg vil begynne veldig grunnleggende, og legge vekt på det jeg savnet informasjon om og det jeg synes var vanskelig da jeg begynte med openGL. Den skal med andre ord være noe jeg ville hatt nytte av da jeg startet på kurset, og som skal gi brukeren et utgangspunkt i forhold til bezierkurver.

I denne modulen tar jeg først for meg bezierflater generelt, og hvordan jeg lærte det, før jeg til slutt forklarer hvordan jeg har formet de forskjellige komponentene for å få laget flasken.

Demonstrasjonskoden er skrevet i GL4Java.

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.
bezierkurve
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:

flatBezier

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.

bezier_ulempe

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:
etikett_fill

Denne laget jeg med utgangspunkt i den flate, firkantede bezierflaten. Det jeg gjorde her, var først å rotere flaten 45 grader.
flat_eti rot_eti
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.
etikett_linjer

Toppen

Deretter begynner jeg med å lage toppen av flasken:
topp_linjer topp_fill

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:
upper_linjer upper_fill

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.
uppersidectr

Flaskens kropp

Det neste jeg lager er selve kroppen til flasken:
middle_linjer middle_fill

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:
middlesidectr

Nederste del

Nederste del av flasken ser slik ut:
lower_linjer lower_fill

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:
bottom_linjer bottom_fill

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
Referanser
  1. The OpenGL Programming Guide, 6 editionDave Schreiner, Mason Woo,Jackie Neider,Tom Davies2007Addison-Wesley Professional0321481003www.opengl.org/documentation/red_book/14-03-2010
  1. Latest 'NEHE' NewsNEHE, NeonHelium ProductionsOpenGL-tutorials.nehe.gamedev.net/14-03-2009
  • Kode for flaska:
    Flaske.java
    Top.java Upper.java Middle.java
    Lower.java Bottom.java Etikett.java
  • Alle filene, inklusive tekstur: alt.zip.
  • Fredrik Danielsens modul om vann: Vann, for å få flasken gjennomsiktig.
Vedlikehold
Skrevet april 2002, Hanne Moen
Redaksjonelle endringer mai 2002, Børre Stenseth
(Velkommen) Å tegne:>Flaske (Bildebok)