<< >> Title Contents Index

3. Objektien suunnittelu ja piirto


OpenGL sisältää piirtorutiinit tavallisimmille kaksi- ja kolmiulotteisille geometrisille kappaleille. Näitä ovat pisteet, viivat, kaaret, neliöt, ympyrät, pallot, kuutiot, kartiot ja sylinterit. Haluttaessa piirtää jokin edellisistä poikkeava objekti, on se koottava niistä.

3.1. Objektien piirto

Geometristen objektien piirto tapahtuu aina kutsujen glBegin (GLenum mode) ja glEnd ( ) välissä. Kontrollipisteet annetaan aliohjelmakutsulla glVertex{234}{s i f d}v( ). Piirrettävä objekti määritellään glBegin(GLenum mode)-kutsulle välitettävällä parametrilla mode. Sen on oltava jokin seuraavista (katso Kuva 3.1.):


Kuva 3.1. Geometriset mallit.

Aliohjelmakutsujen glBegin(GLenum mode) ja glEnd( ) välissä sallitut OpenGL:n omat aliohjelmakutsut on rajattu tarkasti (katso OpenGL Programming Guide s.38). Kontrollipisteiden määrittelyn lisäksi siellä saa olla vain objektin väriin, materiaaliin ja reunoihin liittyviä aliohjelmakutsuja. Ohjelmointikielen omia funktioita ja laskusuorituksia koodin seassa saa olla vapaasti. Ympyrän kaaren approksimaatio voitaisiin määritellä esimerkiksi näin:

    #define PI 3.14159 // Määritellään PI:n arvo.
    static void CALLBACK Piirra( void )
    {
      glBegin( GL_LINE_LOOP); // Piirrettävä objekti koostuu viivoista,
      // jonka viimeisen viivan loppupiste
      // yhdistetään ensimmäisen viivan
      // alkupisteeseen.

      for( i = 0; i < 100; i++ )
      {
        angle = 2*PI*i/100.0; // Kontrollipisteen paikka radiaaneina.
        glVertex2f( cos(angle), sin(angle) ); // Piirretään seuraava kontrollipiste.
      }
      glEnd( ); // Objekti piirretty.
    }

3.2. Pisteet

Pisteet esitetään kolmiulotteisessa koordinaatistossa aina neljän reaaliluvun (x,y,z,w) avulla. Mikäli pisteen paikan määräämiseksi riittää kaksiulotteinen koordinaatisto, voidaan kaksi viimeistä lukua jättää määrittelemättä ja OpenGL oletuksena antaa z-koordinaatille arvon 0.0 ja w-koordinaatille arvon 1.0. Jos neljäs reaaliluku w määritellään erisuureksi kuin nolla, kolme ensimmäistä koordinaattipistettä jaetaan sillä (x/w, y/w, z/w). Oletuksena w:n arvo on 1.0.

Useimmiten piste esitetään yhden pikselin kokoiseksi, joka on myös oletuksena OpenGL:ssä. Mikäli piste halutaan suuremmaksi kuin pikseli, voidaan sen koko määritellä kutsulla glPointSize(GLfloat size). Esimerkiksi parametrin size arvolla 2.0 pisteen koko on 2x2 pikseliä.

3.3. Viivat

Viivojen piirrossa annetaan aina viivan alku- ja loppupiste, jotka yhdistetään viivaksi. Oletuksena viivojen paksuus on yksi pikseli ja tyyli on normaali jatkuva viiva. Viivan paksuus voidaan muuttaa kutsulla glLineWidth(GLfloat width).

Lisäksi viivatyyppi voidaan valita katkoviivaksi, pisteviivaksi tai näiden yhdistelmäksi. Viivan tyyli määritellään kutsulla glLineStipple (GLint factor, GLshort pattern). Toinen parametri pattern on 16-bittinen sarja nollia ja ykkösiä. Ykkösellä piirretään viivaa ja nollalla piirtoa ei tapahdu. Ensimmäinen parametri factor venyttää yhden bitin vaikutusaluetta. Jos se on ykkönen, jokainen bitti vaikuttaa yhteen pikseliin. Jos factor on kakkonen, jokainen bitti vaikuttaa kahteen pikseliin jne. Sallittu arvo ensimmäiselle parametrille on kokonaisluku välillä 1-255. Katkoviivan piirto on tehtävä vielä mahdolliseksi kutsulla glEnable (GL_LINE_STIPPLE).

Kuvassa 3.2. olevien eri viivatyyppien piirtoon vaadittavat aliohjelmat on esitetty seuraavassa:

    void AlustaArvot( void )
    {
      glClearColor( 1.0, 1.0, 1.0, 0.0 ); // Taustan väri valkoiseksi.
      glColor3f( 0.0, 0.0, 0.0 ); // Piirtoväri mustaksi.
    }

    static void CALLBACK PaivitaKoko( int leveys, int korkeus )
    {
      glViewport( 0, 0, leveys, korkeus ); // Katselualueeksi koko ikkuna.

      glMatrixMode( GL_PROJECTION ); // Käsitellään koordinaatistoa.
      glLoadIdentity( ); // Otetaan peruskoordinaatisto käyttöön.
      glOrtho( -1.0, 1.0, -0.1, 0.1, -1.0, 1.0 ) // Määrätään koordinaatiston rajat.
      glMatrixMode( GL_MODELVIEW ); // Käsitellään objektia.
      glLoadIdentity( ); // Otetaan peruskoordinaatisto käyttöön.

    }

    #define VIIVANPIIRTO( x1,y1, x2,y2 ) glBegin( GL_LINES );\
    glVertex2f( x1, y1 );\
    glVertex2f( x2, y2 );\
    glEnd( );

    static void CALLBACK Piirra( void )
    {
      glClear( GL_COLOR_BUFFER_BIT ); // Suorittaa taustan värityksen.

      VIIVANPIIRTO( -0.8, 0.08, 0.8, 0.08 ) // Tavallisen viivan piirto.
      glEnable(GL_LINE_STIPPLE); // Mahdollistaa eri viivatyypit.
      glLineStipple( 1, 0x00FF ); // Toinen viiva (ylhäältä).
      VIIVANPIIRTO( -0.8, 0.04, 0.8, 0.04 )
      glLineStipple( 2, 0x00FF ); // Kolmas viiva (ylhäältä).
      VIIVANPIIRTO( -0.8, 0.0, 0.8, 0.0 )
      glLineWidth( 3.0 ); // Muutetaan viivan paksuus.
      glLineStipple( 4, 0xAAAA ); // Neljäs viiva (ylhäältä).
      VIIVANPIIRTO( -0.8, -0.04, 0.8, -0.04 )
      glLineWidth( 1.0 ); // Muutetaan viivan paksuus takaisin.
      glLineStipple( 4, 0x1C47 ); // Viides viiva (ylhäältä).
      VIIVANPIIRTO( -0.8, -0.08, 0.8, -0.08 )

      glFlush( ); // Suorittaa piirrot puskurista ikkunaan.
    }


Kuva 3.2. Viivatyyppejä.

3.4. Polygonit (monitahokkaat)

Polygonit koostuvat useammasta viivasta. Viimeisen viivan loppupiste palaa aina ensimmäisen viivan alkupisteeseen. Polygonit voivat käytännössä olla erittäin monimutkaisia. OpenGL onkin joutunut rajoittamaan polygonien määrittelyä aika rajusti:

Syy rajoituksiin polygonien esityksessä löytyy tietenkin nopeuden maksimoinnista. Yksinkertaisen ja konveksin polygonin "täyttö" on huomattavasti nopeampaa. Piirrettäessä rautalankamallia, ei rajoituksista siten tarvitse välittää. Usein halutaan kuitenkin piirtää polygoni, joka ei ole yksinkertainen tai konveksi. Miten ongelma ratkaistaan? Kaikki polygonit voidaan paloitella yksinkertaisiksi konvekseiksi polygoneiksi ja esittää siten alkuperäinen polygoni useamman polygonin avulla. OpenGL Utility Library (Luku 8.) sisältää monia valmiita aliohjelmakutsuja monimutkaisempien polygonien esittämistä varten.

Polygonin pinnan määrittelyyn löytyy useita vaihtoehtoja. Sisä- ja ulkopinnat voidaan värittää eri väreillä sekä pinnoille voidaan liittää bittikartta tai kuva.

Polygonien suunnittelussa on syytä huomioida muutama tärkeä yksityiskohta. Kontrollipisteiden suunta (myötä- tai vastapäivään) tulee olla sama kaikilla saman objektin polygoneilla. Toinen huomioitava asia koskee polygonien jakoa useammaksi polygoniksi. Mikäli alkuperäinen polygoni ei ole kolmikulmio, on syytä varmistua, että uusien polygonien kaikki kontrollipisteet sijaitsevat samalla tasolla.

3.5. Nelikulmiot

Nelikulmiot ovat erittäin yleisiä graafisissa sovelluksissa. Vaikka nelikulmiot on helppo määritellä polygonin avulla, on niille haluttu määritellä oma aliohjelmakutsu glRect{s i f d}v(TYPE x1, TYPE y1, TYPE x2, TYPE y2). Kutsu määrittelee vasemman alakulman ja oikean yläkulman koordinaattipisteet.

3.6. Kaaret ja kolmiulotteiset pinnat

Kaaren piirto voidaan suorittaa kahdella eri tavalla. Mikäli kaarella sijaitsevien pisteiden paikat on helppo laskea, voidaan (ja kannattaa) kaari approksimoida näiden pisteiden avulla. Kaksi peräkkäistä pistettä yksinkertaisesti yhdistetään viivaksi. Tällä tavalla saadaan usein tarpeeksi tarkka approksimaatio kaarelle ja piirto on nopeampi kuin varsinaisen kaaren määrittelyllä piirretty kaari. Kolmiulotteinen pintakin on mahdollista määritellä useamman kolmion yhdistelmänä. Pintoja kuitenkin harvemmin approksimoidaan kolmioiden avulla, koska kolmioiden kontrollipisteiden paikan laskeminen ei ole niin helppoa, eikä niistä muodostuva approksimaatio ole tarpeeksi hyvä.

Monet kaaret ja kolmiulotteiset pinnat on kuitenkin mahdollista määritellä matemaattisesti muutamien kontrollipisteiden avulla. Eriasteiset splinefunktiot, kuten B-splinet, NURBS-pinnat (Non-Uniform Rational B-spline), Bézier-kaaret ja -pinnat (katso Kuva 3.3.) sekä Hermite-splinet ovat kaikki käytettävissä, kun halutaan määritellä kaaria tai pintoja.

Kaarien ja kolmiulotteisten pintojen määrittely voi tuntua välillä aika monimutkaiselta. Samalla aliohjelmalla voidaan valita kaaren tai pinnan kohdealueeksi normaali koordinaatisto, värikartta tai kuva. Yksinkertaistettuna niiden määrittely ja piirto toimii seuraavasti:

Yksiulotteisen kaaren piirtoon tarvittavan "kartan" pisteet määritellään aliohjelmalla glMap1{f d}v(GLenum target, TYPE u1, TYPE u2,GLint stride, GLint order, const TYPE* points). Ensimmäinen parametri kertoo kohdealueen. Kaksi seuraavaa lukua määrittelevät välin, jolla parametrin u arvot ovat. Myöhemmin kun "karttaa" piirretään, annetaan aliohjelmalle arvoja, jotka ovat näiden kahden luvun väliltä. Parametri stride kertoo, kuinka monta lukua määrittelee yhden kontrollipisteen. Kontrollipisteiden määrä voi vaihdella, riippuen piirretäänkö tavalliseen koordinaatistoon, värikarttaan vaiko kuvaan. Parametrin order tulisi aina olla yhtä suuri kuin kontrollipisteiden lukumäärä. Osoitin ensimmäisen kontrollipisteen ensimmäiseen koordinaattiin tuodaan viimeisenä parametrina.

Jotta kaari tulee piirrettyä, on kutsuttava aliohjelmaa glEvalCoord1{f d}{v}(TYPE u) useilla u:n arvoilla, jotka ovat "kartan" määrittelyalueen sisällä. Jos esimerkiksi aliohjelmalle glMap1f(..) vietäisiin u1 = 0.0 ja u2 = 1.0, tulisi aliohjelmaa glEvalCoord1f(...) kutsua peräkkäin esimerkiksi arvoilla 0/r, 1/r, 2/r,..., r/r. Parametri r voidaan valita siten, että haluttu tarkkuus saavutetaan.

Kuvassa 3.3. olevan kaaren piirtoon vaadittavat aliohjelmat on esitetty seuraavissa kolmessa koodilaatikossa.

    void AlustaArvot( void )
    {
      glClearColor( 1.0, 1.0, 1.0, 0.0 ); // Määrää pohjavärin valkoiseksi.
      glColor3f( 0.0, 0.0, 0.0 ); // Piirtoväri mustaksi.
      glMap1f( GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4, &kontrollipisteet[0][0] );
      // Määrittää "kartan".
      glEnable( GL_MAP1_VERTEX_3 ); // Mahdollistaa kaaren piirron.
    }

    static void CALLBACK PaivitaKoko( int leveys, int korkeus )
    {
      glViewport(0, 0, leveys, korkeus);
      glMatrixMode(GL_PROJECTION);
      glLoadIdentity();
      glOrtho( -5.0, 5.0, -5.0, 5.0, -1.0, 1.0 ); // Määrää koordinaatiston.
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity();
    }

    #define PISTEITA 4

    GLfloat kontrollipisteet[PISTEITA][3] = {
    {-4.0,-4.0, 0.0},{-4.0,4.0,0.0},
    {4.0,4.0,0.0}, {4.0,-4.0,0.0}};

    static void CALLBACK Piirra( )
    {
      int i;
      glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

      glBegin( GL_LINE_STRIP);
        for( i=0; i<=40; i++ )
          glEvalCoord1f((GLfloat) i/40 );
      glEnd( );

      glPointSize( 5.0 );
      glBegin( GL_POINTS);
        for( i=0; i < PISTEITA; i++)
          glVertex3fv( &kontrollipisteet[i][0] );
      glEnd( );
      glFlush( );
    }


Kuva 3.3. Bézier-kaari.

Samaan tyyliin määritellään kaksiulotteisen kaaren "kartta". Aliohjelmalle glMap2{f d}(GLenum target, TYPE u1, TYPE u2, GLint ustride, GLint uorder, TYPE v1, TYPE v2, GLint vstride, GLint vorder, TYPE points) tarvitsee kertoa nyt u-suunnassa olevien kontrollipisteiden lisäksi myös v-suunnassa olevat pisteet. Lisäksi on annettava molempien pisteiden lukumäärä stride ja aste order. Piirto suoritetaan taas aliohjelmalla glEvalCoord2{f d}{v}(TYPE u, TYPE v), jolle viedään sekä u että v. Esimerkkikoodi Bézier-pinnan piirrosta on liitteessä 1.

Kolmiulotteiset pinnat määritellään usein NURBS-pintoina. NURBS-pintojen määrittely ei ole sen vaikeampaa kuin edellä esitettävien kaarien määrittelykään. NURBS-pinnat määritellään samalla tavalla kontrollipisteiden, niiden lukumäärän ja asteen mukaan. Lisäksi NURBS-pinnoille kerrotaan, halutaanko käyttää pintamallia vai rautalankamallia.

Kuvan 3.4. NURBS-pintojen piirtoon vaadittavat aliohjelmat on esitetty seuraavissa neljässä koodilaatikossa.

    #define VPISTEET 4
    #define UPISTEET 4

    GLfloat kontrollipisteet[UPISTEET][VPISTEET][3];

    void LaskeKontrolliPisteet( void )
    {
      int u, v;
      for( u = 0; u < UPISTEET; u++ ){
        for( v = 0; v < VPISTEET; v++ ) {
          kontrollipisteet[u][v][0] = 2.0*( u - 1.5 );
          kontrollipisteet[u][v][1] = 2.0*( v - 1.5 );
          if( u == 0 )
            kontrollipisteet[u][v][2] = 0.0;
          else if( u == 1 )
            kontrollipisteet[u][v][2] = 3.0;
          else if( u == 0 )
            kontrollipisteet[u][v][2] = 1.0;
          else
            kontrollipisteet[u][v][2] = 0.0;
        }
      }
    }

    void AlustaArvot( void )
    {
      GLfloat matDiffuse = { 0.7, 0.7, 0.7, 1.0 };
      GLfloat matSpecular = { 0.9, 0.9, 0.9, 1.0 };
      GLfloat matShininess = { 128.0 };

      glClearColor( 1.0, 1.0, 1.0, 0.0 );
      glMaterialfv( GL_FRONT, GL_DIFFUSE, matDiffuse );
      glMaterialfv( GL_FRONT, GL_SPECULAR, matSpecular );
      glMaterialfv( GL_FRONT, GL_SHININESS, matShininess );

      glEnable( GL_LIGHTING );
      glEnable( GL_LIGHT0 );
      glDepthFunc( GL_LEQUAL );
      glEnable( GL_DEPTH_TEST );
      glEnable( GL_AUTO_NORMAL );
      glEnable( GL_NORMALIZE );

      LaskeKontrolliPisteet( );
      theNurb = gluNewNurbsRenderer( );
      gluNurbsProperty( theNurb, GLU_SAMPLING_TOLERANCE, 25.0 );
      gluNurbsProperty( theNurb, GLU_DISPLAY_MODE, GLU_FILL );
      // gluNurbsProperty( theNurb,
      // GLU_DISPLAY_MODE, GLU_OUTLINE_POLYGON

      // Piirretään rautalankamalli.
    }

    static void CALLBACK PaivitaKoko( int leveys, int korkeus )
    {
      glViewport( 0, 0, leveys, korkeus );
      glMatrixMode( GL_PROJECTION );
      glLoadIdentity( );
      gluPerspective ( 45.0, 1.0, 3.0, 8.0 );
      glMatrixMode(GL_MODELVIEW);
      glLoadIdentity( );
      glTranslatef ( 0.0, 0.0, -5.0 );
    }

    static void CALLBACK Piirra( void )
    {
      GLfloat knots[8] = {0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0};
      glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
      glPushMatrix( );
      glRotatef( 45.0, -1.0,0.0,0.0 );
      glScalef ( 0.5, 0.5, 0.5 );
      gluBeginSurface( theNurb );
      gluNurbsSurface( theNurb,
        8, knots,
        8, knots,
        4 * 3,
        3,
        &kontrollipisteet[0][0][0],
        4, 4,
        GL_MAP2_VERTEX_3 );
      gluEndSurface( theNurb );
      glPopMatrix( );
      glFlush( );
    }


Kuva 3.4. NURBS-pinnan piirto.

NURBS-pinnan hyvä puoli on lisäksi se, että niille on helppo määritellä reikiä. Jos alue rajataan vastapäivään, täytetään sisäpinta. Kun taas alue rajataan myötäpäivään, tulee siihen reikä (katso Kuva 3.5.).

Kuvan 3.5. reiällisten NURBS-pintojen piirtoon vaadittavat aliohjelmat ovat aliohjelmaa Piirra(void) lukuunottamatta samanlaiset kuin edellä.

    static void CALLBACK Piirra( void )
    {
      GLfloat knots[8] = {-1.0, -1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0};

      // Ulkoreuna rajataan vastapäivään.
      GLfloat ulkoreuna[5][2] = {{ -1.0, -1.0 }, { 1.0, -1.0 },
      { 1.0, 1.0 }, { -1.0, 1.0 }, { -1.0, -1.0 }};
      // Neliön muotoinen reikä rajataan myötäpäivään.
      GLfloat nelioReika[5][2] = {{ -0.8, -0.8 }, { -0.8, 0.8 },
      { -0.4, 0.8 }, { -0.4, -0.8 }, { -0.8, -0.8 }};
      // Ellipsin muotoinen reikä on tehty kahdesta kaaresta.
      // Alempi kaari rajataan myötäpäivään.
      GLfloat alempiKaari[4][2] = {{ 0.2, 0.0 }, { 0.2, 0.8 },
      { 0.8, 0.8 }, { 0.8, 0.0 }};
      // Ylempi kaari rajataan myötäpäivään.
      GLfloat ylempiKaari[4][2] = {{ 0.8, 0.0 }, { 0.8, -0.8 },
      { 0.2, -0.8 }, { 0.2, 0.0 }};

      glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
      glPushMatrix( );
      glRotatef( 45.0, -1.0,0.0,0.0 );
      glScalef ( 0.5, 0.5, 0.5 );
      gluBeginSurface( theNurb );
      gluNurbsSurface( theNurb,
        8, knots,
        8, knots,
        4 * 3,
        3,
        &kontrollipisteet[0][0][0],
        4, 4,
        GL_MAP2_VERTEX_3 );
      gluBeginTrim( theNurb );
      gluPwlCurve( theNurb, 5, &reuna[0][0], 2,
        GLU_MAP1_TRIM_2 );
        gluEndTrim( theNurb );
        gluBeginTrim( theNurb );
          gluPwlCurve( theNurb, 5, &nelioReika[0][0], 2,
          GLU_MAP1_TRIM_2 );
        gluEndTrim( theNurb );
        gluBeginTrim( theNurb );
          gluNurbsCurve( theNurb, 8, knots, 2,
          &ylempiKaari[0][0], 4,GLU_MAP1_TRIM_2 );
          gluNurbsCurve( theNurb, 8, knots, 2,
          &alempiKaari[0][0], 4, GLU_MAP1_TRIM_2 );
        gluEndTrim( theNurb );
      gluEndSurface( theNurb );
      glPopMatrix( );
      glFlush( );
    }


Kuva 3.5. NURBS-pinnan reikien määrittely.

Kaarien ja pintojen piirtoa varten kannattaa tutustua kirjan OpenGL Programming Guide lukuun 11.


<< >> Title Contents Index