Home Index Search Links About Us
[LinuxFocus Image]
[Navegation Bar]
  Duyumlar   Belgelikler   Kuruluşlar  Öğütler  

OpenGL Programlama: Basit Çokgen Görüntüleştirimi

Yazar: Miguel Angel Sepúlveda

Çeviri: Esma Meral


Giriş

Noktaların Çizimi

Doğruların ve Çokgenlerin Çizimi

Alıştırmalar

Gelecek Yazı

Giriş

Bu yazı, 2 ve 3 boyutlu grafikler için bir endüstri ölçünü (standardı) olan OpenGL hakkındaki makale dizisinin ilkidir. (Bakınız: OpenGL Nedir?). Okuyucunun, C geliştirme çalışmatabanına alışık ve GLUT kitaplığı hakkında bazı bilgilere sahip olduğunu kabul ediyoruz (Aksi durumda bu dergideki "GLUT programlama" konusundaki yazıların izlenmesi gerekir). Linux altında çalışırken, OpenGL konusunda özgür kullanıma açık eşsiz uygulamaları bulunan Mesa-kitaplığını kullanmanızı öneririz.Bu anda Mesa için donanım desteği bile bulunmaktadır. (Bakınız: 3Dfx grafik kartı).

Yeni OpenGl güdümlerinin sunumu, en az bizim de yapmaya çabalayacağımız kadar, onun işlevselliğini kullanmaya çalışan bir örnek ile süregidecektir. Dizimizin sonunda bütünüyle OpenGL ile yazılmış bir oyun öykünümünü (simülasyonu), kaynak düzgüsüyle (koduyla) birlikte vereceğiz.

Başlamadan önce, bir bilim adamı olduğum için, OpenGL deneyimimin büyük bir çoğunluğunun, OpenGL'in gerçel kuvantum ve kökleşik (klasik) sistemlerin öykünümlerini (simulasyonlarını) yazmak için bir araç olduğunu söylemek isterim. Bu nedenle örneklerim biraz bu yöndedir. ;-). Okuyucunun bu örnekleri erişilebilir ya da en azından eğlenceli bulacağını umuyorum. Eğer farklı örnekler görmek istiyorsanız, beni duyumlu kılmanız (haberdar etmeniz) yeterlidir.

OpenGL, sıklıkla 3 boyutlu grafiklerle, özel etkilerle, karmaşık örneklerin ışıkla gerçek modellemesi gibi konularla ilgilidir. Bunun yanında 2 boyutlu grafikleri görüntüleştirmeye (render) yarayan bir makinedir. 3 boyutlu görüngelerin (perspektiflerin) karmaşıklığı, model görüntüleştirme, ışık, görüntüçeker (kamera) konumu ve bu gibi konuları öğrenmeye başlamadan önce 2 boyutta yapmayı öğrenebileceğiniz pek çok şey olması bakımından bu konu önemlidir. Mühendislikle ve bilimsel konularla ilgili çok sayıda uygulama 2 boyutta görüntüleştirilebilir (rendered). Dolayısıyla, ilk olarak bazı basit 2 boyut canlandırımlarının (animasyonlarının) nasıl yapıldığını öğrenmek yerinde olacaktır.

Noktaların Çizimi

OpenGL yalnızca birkaç geometrik temelögeye sahiptir: noktalar, doğrular ve çokgenler. Bunların her biri kendilerine ait olan köşeler ile tanımlanır. Bir köşe 2 ya da 3 kayarnoktalı sayı (floating point number) ile özyapılandırılır (karakterize edilir), köşenin kartezyen koordinatları, 2 boyutta (x,y) ve 3 boyutta (x,y,z)'dir. Kartezyen koordinatlar çok yaygın olmakla beraber, bilgisayar grafiklerinde ayrıca her noktanın (x,y,z,w) 4 kayarnoktalı sayı ile gösterildiği benzeşik (homojen) koordinat sistemi de vardır. 3 boyutun temel özelliklerini inceledikten sonra bunlara geri döneceğiz.

OpenGL'de tüm geometrik nesneler bir sıralı köşeler kümesi olarak tanımlandığından, bir köşenin belirtimi için bir yordam demeti bulunmaktadır.; Kullanımı şöyledir:

void glVertex{234}{sifd}[v](TYPE coords); 

Haydi, bu kullanıma alışkanlık edinelim.{} imleri arasındaki sözcük yordamın adını göstermektedir.; Yordamlar short, long, float ya da double türde 2, 3 ya da 4 parametre alabilirler. Seçimebağlı olarak bu parametreler vektor formda verilebilir, biz v-tip yordamları kullanacağız. İşte bazı örnekler:

void glVertex2s(1, 3);
void glVertex2i(23L, 43L);
void glVertex3f(1.0F, 1.0F, 5.87220972F);

float vector[3];
void glVertex3fv(vector);

Basitleştirmek için bu yordamların tümü glVertex* olarak tanımlanırlar.

OpenGL, herhangi bir köşe dizisini içeriğine göre yorumlar. Bu içeriğin belirtimi glBegin(GLenum mode) ve glEnd(), yordam ikilisiyle yapılmaktadır. Bu iki yordam arasında çalıştırılan herhangi bir glVertex* deyimi kip (mod) değerine göre yorumlanır. Örneğin:

glBegin(GL_POINTS);
glVertex2f(0.0, 0.0);
glVertex2f(1.0, 0.0);
glVertex2f(0.0, 1.0);
glVertex2f(1.0, 1.0);
glVertex2f(0.5, 0.5);
glEnd();

2 boyutta 5 nokta çizer. GL_POINTS, OpenGL başlık dosyasında <GL/gl.h>. tanımlanmış etiketlerden birisidir. Daha pek çok uygun kip (mod) vardır ancak, bunlardan, gerektiğinde, sözedilecektir. 

Her nokta, OpenGL'in renk arabelleği (buffer) ile ilişkili durum değişkenlerinde o anda saklanan renklerle çizilir. Geçerli rengi değiştirmek için glColor* yordam demetini kullanın. Renkleri seçmek ve uygulamak hakkında söylenecek çok şey vardır (Diğer bir yazı yalnızca bu konuda olacaktır). 0.0'dan 1.0'a kadar 3 tane kayarnoktalı değer kullanarak RGB (Kırmızı - Yeşil - Mavi) renkleri tanımlayabiliriz.
glColor3f(1.0, 1.0, 1.0); /* Beyaz */ 
glColor3f(1.0, 0.0, 0.0); /* Kırmızı*/ 
glColor3f(1.0, 1.0, 0.0); /* Magenta*/ 
etc... 

Yöredençekim:Makefile, example1.c, example2.c

Buraya kadar anlatılanlar ilk iki örnek düzgümüzü (kodumuzu) yazmak için yeterli malzemeyi oluşturmuş bulunmaktadır. İlk örnek, kargaşasal (kaotik) dönüşümde (ölçünlü dönüşüm, standart dönüşüm) birçok yörünge çizen basit bir OpenGL programıdır. Okuyucunun dönüşümler ve özellikle ölçünlü (standart) dönüşümler konusunda bilgili olmaması sorun değildir. Basitçe söylemek gerekirse, bir dönüşümün bir noktayı alıp iyi tanımlı bir bağıntı aracılığıyla yeni bir nokta üretir:

yn+1 = yn + K sin(xn)
xn+1 = xn + yn+1

Ölçünlü (standart) dönüşüm durumunda yüklü bir parçacığın, parçacık hızlandırıcının simitinde hızlandırıcının düzlemini keserek yaptığı devinimde bıraktığı izi betimleyen bir model sözkonusu olur. Bunun ve diğer dönüşümlerin incelenmesi, çemberselhızlandırıcı içine kapatılmış bir yüklü parçacığın deviniminin kararlılığının anlaşılması açısında fizikte çok önemlidir. Ölçünlü (standart) dönüşüm, parametresinin bazı değerleri iğin K'nın açıkça kargaşasal (kaotik) ve tutuklu devinimin (trapped motion) bir karışımını göstermesinden dolayı çok donuktur??????. Sonuç olarak, fizik ile gerçekten ilgilenilmese de bir çok güzel grafik düzgü (kod) geliştirmek isteyenler ölçünlü (standart) dönüşümlere yeterince dikkat etmelidirler. Doku, ateş parlaması, ağaçlar, karaparçaları gibi şeyleri üretmekte kullanılan pek çok algoritma fractal dönüşümlere dayandırılmaktadır. 

../../common/January1998/../../common/January1998/example1.c'nin kaynak düzgüsü (kodu):

    
#include <GL/glut.h>
#include <math.h>  

const  double pi2 = 6.28318530718;

void NonlinearMap(double *x, double *y){
    static double K = 1.04295;
    *y += K * sin(*x);
    *x += *y;
    *x = fmod(*x, pi2);
    if (*x < 0.0) *x += pi2;
};

void winInit(){
    /* Set system of coordinates */
    gluOrtho2D(0.0, pi2, 0.0, pi2);
};

void display(void){
    const int    NumberSteps = 1000;
    const int    NumberOrbits = 100;
    const double Delta_x = pi2/(NumberOrbits-1);
    int step, orbit;

    glColor3f(0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(1.0, 1.0, 1.0);

    for (orbit = 0; orbit < NumberOrbits; orbit++){
      double x, y;
      y = 3.1415;
      x = Delta_x * orbit;

      glBegin(GL_POINTS);
      for (step = 0; step < NumberSteps; step++){
        NonlinearMap(&x, &y);
        glVertex2f(x, y);
      };
    
      glEnd();
    };

    for (orbit = 0; orbit < NumberOrbits; orbit++){
      double x, y;
      x = 3.1415;
        y = Delta_x * orbit;

        glBegin(GL_POINTS);
        for (step = 0; step < NumberSteps; step++){
          NonlinearMap(&x, &y);
          glVertex2f(x, y);
        };
        glEnd();

     };
};

int main(int argc, char **argv) {
  glutInit(&argc, argv);  
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);  
  glutInitWindowPosition(5,5);  
  glutInitWindowSize(300,300);  
  glutCreateWindow("Standard Map");  
  
  winInit();
  glutDisplayFunc(display);  
  glutMainLoop();  
  
  return 0;  
}  
  
Glut* yordamlarını anlamak için, GLUT Programlama yazısını okuyabilirsiniz, Yukarıdaki düzgünün (kodun) çoğunluğu oradan gelmektedir. Grafik penceresi tek arabellekte (buffer) ve RGB kipte (modda) açılır. Bunun ardından display() adlı geriçağırma fonksiyonu grafiği çizer: İlk olarak arkataban için kara renk seçilir; glClear(GL_COLOR_BUFFER_BIT) renk arabelleğini geçerli renk karaya yenidenkurar, daha sonra glColor ile beyaz renk seçildiktan sonra, NonlinearMap() fonksiyonu birçok kez çalıştırılır ve noktalar glVertex* fonksiyonu ile GL_POINTS kipte (mod) çizdirilir. Yani gerçekten de kolay!

Pencere başlatım yordamında yani winInit() 'da OpenGL elaygıt takımından gelen yalnızca tek bir deyim bulunduğuna dikkat çekmek gerekir:gluOrtho2D(). Bu yordam 2 boyutlu dik sistemin koordinatlarını gösterir. Fonksiyona geçen parametreler "minimum x, maksimum x, minimum y, maksimum y" dir.
ŞekIi çizimin ilk anından başlayarak görebilme şansınız olması için tek bir ekran modu ve çok sayıda nokta seçtim. Bu büyük ve zaman gereksinimi yüksek olan görüntüler durumundaki tek kip için alışılan bir tavırdır, nesneler OpenGL yordamlarıyla yaşamageçirildikçe ekranınızda görünüurler. 

../../common/January1998/example1'i çalıştırdıktan sonra şu şekli göreceksiniz:

 

Şimdi ikinci programa geçelim, ../../common/January1998/../../common/January1998/example2.c:

    
#include <GL/glut.h> 
#include <math.h>

const  double  pi2 = 6.28318530718; 
const  double  K_max = 3.5;
const  double  K_min = 0.1;
static double  Delta_K = 0.01;
static double  K = 0.1;          


void NonlinearMap(double *x, double *y){
    /* Standard Map */
    *y += K * sin(*x);
    *x += *y;

    /* Angle x is module 2Pi */
    *x = fmod(*x, pi2);
    if (*x < 0.0) *x += pi2;
};


/* Callback function: 
   What to do in absence of use input */
void  idle(void){
    /* Increase the stochastic parameter */
    K += Delta_K;
    if(K > K_max) K = K_min;

    /* Redraw the display */
    glutPostRedisplay();
};


/* Initialization for the graphics window */
void winInit(void){
    gluOrtho2D(0.0, pi2, 0.0, pi2);
};

/* Callback function:
    What to do when the display needs redrawing */
void display(void){
    const int    NumberSteps = 1000;
    const int    NumberOrbits = 50;
    const double Delta_x = pi2/(NumberOrbits-1);
    int step, orbit;

    glColor3f(0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glColor3f(1.0, 1.0, 1.0);
                
    for (orbit = 0; orbit < NumberOrbits; orbit++){
        double x, y;
        y = 3.1415;
        x = Delta_x * orbit;

        glBegin(GL_POINTS);
        for (step = 0; step < NumberSteps; step++){
          NonlinearMap(&x, &y);
          glVertex2f(x, y);
        };
        glEnd();
     };

     for (orbit = 0; orbit < NumberOrbits; orbit++){
        double x, y;
        x = 3.1415;
        y = Delta_x * orbit;

        glBegin(GL_POINTS);
        for (step = 0; step < NumberSteps; step++){
          NonlinearMap(&x, &y);
          glVertex2f(x, y);
        };
        glEnd();
     };

     glutSwapBuffers();
};


int main(int argc, char **argv)  {  
  /* GLUT Initializations */
  glutInit(&argc, argv);  
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA);  
  glutInitWindowPosition(5,5);  
  glutInitWindowSize(300,300);
  
  /* Open Window */
  glutCreateWindow("Order to Chaos");  
  
  /* Window initializations */
  winInit();

  /* Register callback functions */
  glutDisplayFunc(display);  
  glutIdleFunc(idle);

  /* Launch event processing */
  glutMainLoop();  
  
  return 0;  
}  
  
    
    
Bu program ../../common/January1998/../../common/January1998/example1.c'ye benzemektedir. Temel fark ekranın ikili arabellek kipte (modda) açılması ve K parametresinin program boyunca farklı değerler alan bir değişken olmasıdır, idle() adında GLUT olay işsleyicisine glutIdleFunc() tarafından yazmaçlanmış yeni geriçağırma fonksiyonu vardır. Bu fonksiyonun özel bir anlamı vardır; o, kullanıcı verigirdisi olmaması durumunda, olay içleyicisi tarafından sık sık koşulur. idle() geriçağırma fonksiyonu canlandırım (animasyon) programlaması için ülküseldir (ideal). Example 2'de grafik parametresinin küçük miktarlarda değişmesi amacıyla kullanılmıştır. idle() fonksiyonunun sonunda önceki ekranın ilk değerlerini koruyarak, ekranı yeniden çizen glutPostResDisplay() adlı kullanışlı bir GLUT güdümü (komutu) daha vardır. Genelde bu yöntem display() fonksiyonunu yeniden çağırmaktan daha etkilidir.

Dikkate değer diğer bir farklılık ise display() fonksiyonunun sonunda glutSwapBuffers() fonksiyonunun kullanılmasıdır. Pencere ikili arabellek kipinde açılmıştır, dolayısıyla tüm görüntüleştirme yönergeleri gizliarabelleğe uygulanır; bu durumda kullanıcı şeklin çizimini görememektedir. Şeklin çizimi tamamen bittikten sonra glutSwapBuffers() ile gizli ve görünür arabelleklerin görevleri değiştirilir Bu teknik olmaksızın animasyon düzgün bir şekilde çalışmaz.  

Canlandırım (animasyon) süresince görüntülenen bazı şekiller:

 

ÖNEMLİ: display() geriçağırma fonksiyonu , idle() fonksiyonundan önce en az bir kere yaşamageçirilir. Canlandırımlarınızı (animasyonlarınızı) yazarken ve display() ile idle() fonksiyonlarına hangi değerlerin geçtiğine karar verirken bunu aklınızdan çıkarmayın.

Doğruların ve Çokgenlerin Çizimi

Yöredençekim (download):
example3.c

Önceden sözedildiği üuzere glBegin(GLenum mode) çeşitli kipler (modlar) köşe dizilerini alabilir. Ardarda belirtimi yapılan v0, v1,v2,v3, v4,..., vn-1 uygun biçimde yorumlanabilir. Olası kip (mod) değerleri ve gerçekleştirilen eylemler aşağıda belirtilmiştir:

  • GL_POINTS    n köşenin her birisine bir nokta çizer.. 
  • GL_LINES    Bağlantısız doğru dizisi çizer. Doğru parçaları v0 ve v1, v2 ve v3,...vb arasında çizdirilirler. n tek ise n-1 işleme alınmaz.. 
  • GL_POLYGON    v0, v1,..,v n-1 noktalarını köşe alarak bir çokgen çizer.  n en az 3 olmalıdır, aksi halde hiçbir şey çizdirilmez. Aynı zamanda (donanımın algorithma kısıtlamalarından dolayı) bir çokgen kendini kesmemeli ve dişbükey olmalıdır. 
  • GL_TRIANGLES    Önce v0, v1 ve v2; ardından v3, v4 ve v5 vb. noktalarını kullanarak bir üçgen dizisi oluşturur. Eğer n 3'ün tam katı değilse geri kalan noktalar gözardı edilir. 
  • GL_LINE_STRIP    Önce v0'dan v1'e, ardından v1'den v2'ye ... doğrular çizer. Son olarak vn-2'den vn-1'e bir doğru çizerek n-1 tane doğru kümesi oluşturur. Doğruları tanımlayan noktalar üzerinde herhangi bir kısıt yoktur, doğrular keyfi olarak birbirlerini kesebilirler. 
  • GL_LINE_LOOP    vn-1'den v0'a doğru çizerek döngüyü kapamasi dışında GL_LINE_STRIP ile aynıdır. 
  • GL_QUADS    Önce v0, v1, v2, v3 noktalarından, sonra v4, v5, v6, v7 noktalarından ... geçen dörtgenler çizer. 
  • GL_QUAD_STRIP    Önce v0, v1, v3, v2 noktalarından, daha sonra v2, v3, v5, v4 noktalarından geçen dörtgenler çizer. 
  • GL_TRIANGLE_STRIP    Köşe koordinatları v0, v1, v2; v2, v1, v3; v2, v3, v4,.. olan üçgen dizisi çizer. Sıralama üçgenlerin doğru yönlenimli olma güvencesini sağlayacak biçimde olup kuşak bir yüzey parçası oluşturmak için kullanılabilir. 
  • GL_TRIANGLE_FAN   Üçgenlerin önce v0, v1, v2 ardından v0,v2, v3, v0, v3, v4,... noktalarından çizdirilmesi dışında GL_TRIANGLE_STRIP ile benzer çalışır. Tüm üçgenlerin v0 köşesi ortaktır. 

Üçüncü örneğimizde GL_LINES ve GL_POLYGON'nun kullanımını gösteren başka bir canlandırım (animasyon) vardır. Programı derleyin, kaynak düzgüsüne (koduna) bakın ve nasıl çalıştığını inceleyin. Example2.c'ye oldukça benzerdir, burada çok basit bir sarkacın devinimi (hareketi) çizdirilmiştir. Canlandırım ülküsel (ideal) bir sarkacın devinimini öykünmektedir (simüle etmektedir). İşte canlandırımdan bir anlıkgörüntü (snapshot):

 

Önceden olduğu gibi yine idle() adlı, amacı saati çalışır tutmak olan yani time değişkenini güncelleyen, bir geriçağırma fonksiyonu bulunmaktadır. display() , iki nesne çizer; sarkacın ipi ve ağırlığı (sırasıyla beyaz ve kırmızı renklerde). Sarkaç koordinatlarınin devinimi xcenter ve ycenter için verilen bağıntılardan kapalı olarak elde edilebilmektedir:: 

    

void display(void){
  static double radius = 0.05;
  const double delta_theta = pi2/20;
  double xcenter , ycenter;  
  double x, y;
  double theta = 0.0;

  double current_angle = cos(omega * time);

  glColor3f(0.0, 0.0, 0.0);
  glClear(GL_COLOR_BUFFER_BIT);
  glColor3f(1.0, 1.0, 1.0);

  /* Draw pendulum cord */  
  glColor3f(1.0, 1.0, 1.0);
  glBegin(GL_LINES);
  glVertex2f(0.0, 0.0);
  xcenter = -cord_length * sin(current_angle);
  ycenter = -cord_length * cos(current_angle);
  glVertex2f(xcenter, ycenter);
  glEnd();

  /* Draw pendulum dish */
  glColor3f(1.0, 0.0, 0.0);
  glBegin(GL_POLYGON);
  while (theta <= pi2) {
    x = xcenter + radius * sin(theta);
    y = ycenter + radius * cos(theta);
    glVertex2f(x, y);
    theta += delta_theta;
  };
  glEnd();
  
  glutSwapBuffers();
};

    
    

Alıştırmalar

Öğrendiklerinizi uygulamak için bazı öneriler:

  • In ../../common/January1998/../../common/January1998/example1.c 'deki diğer grafikleri deneyin. Kitaplıktan, Kargaşa (Chaos) ve Fraktallar (Fractals) hakkında herhangi bir kitap alın, içerisinde pek çok örnek bulacağınıza eminim. Çizdirmeden önce, parametreleri, sistem koordinatlarını değiştirin ve ardışık grafiklere bunları uygulayın. Bunu eğlenceli bulacaksınız. 
  • In ../../common/January1998/../../common/January1998/example2.c 'de noktaların her birisine renkler ekleyebilirsiniz. Örneğin, çok ilginç bir renk düzgülemesi (kodlama) olarak, her noktaya yörüngenin yerel kararlılığını betimleyecek ayrı bir renk verebilirsiniz.  (Physics Review Letters Vol 63, (1989) 1226), öyle ki, devinimyolu kargaşasal bir bölgeye girdiğinde renk kırmızılaşır. Örneğin kararlı adasal yörelerde renk mavileştirilebilir. Eğer düzgünüz (kodunuz) bu etkiye sahip olursa, örnekteki fractal'ın şekli şaşırtıcı olacaktır. Türevsel denklemler dersini almamış olanlar için biraz ileri olmakla beraber bilgisayarlı grafikte dönüşümlerin ve fractalların üstünlüklerinden yararlanmak için bu konuda bilgi edinmeye değeceğini vurgulamak gerekmektedir. 
  • In Örnek3.c 'deki tekeri (diski) çizdirmekte kullanılan doğruların türünü değiştirmeyi deneyin. GL_LINES, GL_TRIANGLES.. gibi fonksiyonları kullanın. Sonuçlarını gözlemleyin. Tekerin (diskin) çizimini eniyilemeye (optimize etmeye) çalışın, her çerçeve içinde aynı tekeri (diski) çizmek için birçok kez sinus ve cosinus hesaplamanıza gerek yoktur. Onları bir dizi içerisine saklayabilirsiniz. Çokgen çizimini kullanarak sarkacın ucuna kutular, elmaslar ya da ne isterseniz onu eklemeye çalışın. Her çerçeve içine iki sarkaç yazın, onları bağımsız devindirin (hareket ettirin) ya da birbirleriyle çarpıştırın. 

Gelecek Yazı

Şu anda söyleyeceklerim bu kadar. Çokgenler hakkında daha tartışılacak çok şey var. Bir sonraki sayıda (Mart 1998) çokgenleri araştırmaya devam edeceğiz ve zaten kullanmaya alışık olduğunuz bazı güdümlerin ayrıntılarına inip modellemesini yapacağız.. 


Daha Çok Bilgi İçin:
© 1998 Miguel Angel Sepúlveda
Bu sanalyörenin bakımı Miguel A Sepulveda tarafından yapılmaktadır.