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