OpenGL ve C başlığı altında yürüteceğimiz yazı dizimizde, OpenGL teknolojisinin
ne olduğunu, bu teknoloji ile neler yapılabileceğini ve OpenGL kullanarak C
dilinde nasıl kod yazılacağını ele alacağız. Grafik programlamaya temel teşkil
edebilecek bir proje oluşturarak, pratik bilgimizi de pekiştireceğiz. Örnekleri
iyi anlayabilmek için bir miktar Windows API Programlama bilgisine de ihtiyacımız
olacak. Bu yazı dizisinde API programlama, sadece projemizin gerektirdiği kadar
ele alınacaktır. (OpenGL ile daha ayrıntılı bilgi için www.opengl.org
ve www.nehe.gamedev.net sitelerinden yararlanabilirsiniz.)
OpenGL (Open Graphics
Library), iki veya üç boyutlu grafik programlamada kullanılan bir teknoloji
olarak tanımlanabilir. OpenGL kütüphaneleri, birtakım grafik işlemler yapan
API (Application Programming Language) fonksiyonları içerirler. API, işletim
sistemlerinin, altlık olarak veya yardımcı olarak kullanacağımız programların
gerçekleştirdiği bir işlemi, programcının kullanmasını sağlayan yöntemler olarak
tanımlanabilir. Kullandığımız programlama dilinde, gerekli olduğu yerlerde OpenGL’in
API fonksiyonlarını çağırarak grafiksel işlemler yapılmasını sağlarız. Aslında
OpenGL bir kütüphanedir. Bu kütüphanedeki fonksiyonlar sayesinde biz, grafik
donanımını yönetme olanağı elde ederiz. OpenGL kütüphanesi, grafiksel programlamayı
standartlaştırmayı ve işletim sistemleri arasında taşınabilir kılmayı amaçlar.
OpenGL’i platformdan tamamen bağımsız kılmak için OpenGL Araç Kiti (GLUT) geliştirilmiştir.
Programımız içerisinde bir form oluşturmak, kullanıcıdan veri alabilmek için
gerekli bileşenleri oluşturmak için kullandığımız işletim sistemine bağlıyızdır.
GLUT bu bağımlılığı ortadan kaldırmak için geliştirilmiştir. GLUT ile OpenGL
programlarımızda formlar oluşturabilir, kullanıcıdan veri alabiliriz.
OpenGL teknolojisini
hangi programlama dillerinde kullanabiliriz ?
|
* Ada |
* C |
* C++ |
|
*
C# |
*
Fortran |
*
Python |
|
*
Perl |
*Java
... |
|
Yüksek seviyeli
dillerde, OpenGL kütüphanesinin kullanımını daha basit hale getiren sınıflar
oluşturulmuştur. C dili için böyle bir şey söz konusu değildir. Bu nedenle projemizde
yapmak istediğimiz her şeyi kendimiz kodlayacağız. OpenGL bir kütüphane olduğuna
göre, programımızı çalıştırabilmek için öncelikle işletim sistemimizde çalışma
zamanı kütüphanelerinin (run-time libraries) bulunması gereklidir. OpenGL yaygın
olarak kullanıldığı için, OpenGL çalışma zamanı kütüphaneleri bazı işletim sistemlerinin
kurulumu sırasında sistemimize otomatik olarak eklenir. Bu işletim sistemleri
arasında, Linux, Unix, Mac OS, Windows 95 / 98 / NT / 2000 sayılabilir. Normal
şartlar altında bu işletim sistemlerinde kütüphane hatalarının çıkmaması gereklidir.
Programımızı yazarken kullandığımız araçta da, bu kütüphaneleri kullanacağımızı
derleyiciye anlatabilmek için birkaç ufak işlem yapmamız gereklidir. Projemizi
geliştirmemiz sırasında bu ayrıntılara değinilecektir. OpenGL projesi geliştirirken
izleyebileceğimiz birkaç yol vardır. GLUT araç kitini kullanarak pencere oluşturma,
kullanıcıdan bilgi alma gibi işlemleri bu araç ile yapabiliriz. Dolayısıyla
bu işlemler için kendimizin kod yazmasına gerek kalmayacaktır ve kod azalacaktır.
Sadece istediğimiz işlemleri yapan kütüphane fonksiyonlarını çağırmamız gerekecektir.
İzleyebileceğimiz diğer bir yol ise, kısa ve kolay bir şekilde OpenGL uygulaması
geliştirmemizi sağlamak için geliştirilen uygulama sihirbazlarını kullanmaktır.
Örneğin Jeff Burns tarafından geliştirilen OpenGL sihirbazı bu amaçla kullanılabilir.
(Bu sihirbazı internetten bulabilir ve bilgisayarınıza kurabilirsiniz.) Başka
bir yol ise bu araçların hiçbirisini kullanmamak ve gerekli tüm kodları, OpenGL
kitaplığını da kullanarak kendimiz yazmaktır. İşin detaylı bir şekilde öğrenilmesi
için tercih edebileceğimiz en iyi yol üçüncü yoldur.
İşe OpenGL kitaplığındaki
sık kullanılan fonksiyonları inceleyerek başlayabiliriz.
    * TEMEL OPENGL FONKSİYONLARI :
void
glBegin(enum kip); |
Bir
çizime başlandığını belirtir. Parametresi çizilen geometrik şekli tanımlar.
Bu parametre yerine geçilmesi gereken sembolik sabitler şunlardır :
   - GL_POINTS => Nokta çizileceğini
belirtir.
   - GL_LINES => Verilen
noktaların birleştirilerek bir çizgi çizileceğini belirtir.
   - GL_POLYGON => Verilen
noktaların birleştirilerek doğrular oluşturulacağını ve oluşan şeklin alansal
bir şekil olacağını, içinin renklendirileceğini belirtir.
   - GL_QUADS => Verilen
dört noktadan içi renkelndirilmiş bir dörtgen oluşturulacağını belirtir.
   - GL_TRIANGLES => Verilen
üç noktadan içi renklendirilmiş üçgen oluşturulacağını belirtir.
   - GL_TRIANGLES_STRIP =>
Verilen noktaların üçer üçer sırasıyla birleştirerek üçgenler oluşturulacağını
belirtir.
   - GL_QUAD_STRIP => Verilen
noktaların dörder dörder sırasıyla birleştirilerek dörtgenler oluşturulacağını
belirtir.
   - GL_TRIANGLE_FAN => Verilen
noktaların ilk nokta ikişer ikişer alınıp her adımda ilk noktayı üçüncü
nokta kabul ederek birleştirileceğini ve yelpazemsi bir şekil oluşturulacağını
belirtir. |
void
glEnd(glVoid); |
glBegin()
fonksiyonu ile başlatılmış çizim işleminin bittiğini belirtir. Çizdirilen
şekil ekrana bastırılmak üzere saklanır. Saklanmış bu şeklin çizdirilmesi
için başka bir fonksiyon kullanılır. |
void
glFlush(glVoid); |
Tampon
bellekteki tüm şekillerin ekrana çizdirilmesini sağlar. |
void
glClear(GLbitfield maske); |
Tamponların
içeriğini belli sembolik sabitleri dikkate alarak temizler. Parametre olarak
da bu sembolik sabitleri veya bunların "veya" bağlaçları ile olan
kombinasyonlarını alır. Bu sembolik sabitler şunlardır :
   - GL_COLOR_BUFFER_BIT
   - GL_DEPTH_BUFFER_BIT
   - GL_ACCUM_BUFFER_BIT
   - GL_STENCIL_BUFFER_BIT |
void
glClearColor(GLclampf kirmizi,GLclampf yesil,GLclampf mavi,GLclampf donukluk); |
glClear()
fonksiyonu ile temizlenen ekran renk tamponunun ne renk alacağını belirler.
Her parametre için 0 ya da 1 değeri geçilir. |
void
glColor3s(short kirmizi,short yesil,short mavi); |
Çizilecek
şeklin rengini belirler. Ön tanımlı değerler 0’dır. |
void
glLineWidth(float genislik); |
Çizgi
kalınlığını belirler. Ön tanımlı değer 1.0’dır. |
void
glLineStipple(int carpan,short oruntu); |
Çizginin
kesikli ya da düz görünmesini sağlar. Örüntüdeki bit 0 ise bu bite karşılık
gelen benek ekrana basılmaz, 1 ise basılır. Böylece çizgi kesikli görünür.
|
void
glEnable();
void glDisable(); |
Çizim
performansında artış sağlamak için kesiklilik, ışıklandırma, dokuma gibi
özellikler glDisable() fonksiyonu ile kaldırılır. Böylece çizimin hızlanması
sağlanır. glEnable() fonksiyonu ile özellikler tekrar kullanılabilir hale
getirilir.
Örnek :
    glEnable(GL_LINE_STIPPLE); //Kesikli çizgi çizilebilmesini
sağlar
    glEnable(GL_SMOOTH); //Renk geçişlerini yumuşatır. |
void
glRecti(int x1,int y1,int x2,int y2); |
Dörtgen
çizer. Bu fonksiyon ile aynı işi yapan glRects, glRectf, glRectd fonksiyonları
da vardır. Bu fonksiyonların sadece parametre değişkenlerinin türleri farklıdır.
Parametre değişkenlerinin türleri sırasıyla short, float ve double türlerindendir.
|
void
glVertex2f(float x,float y); |
Bir
geometrik şekle ait kontrol noktasının koordinat değerlerini belirtir. Geometrik
şeklin ne olacağı glBegin fonksiyonunun parametresi ile belirlenir. Bu fonksiyon
iki boyutlu çizim yapılacağı zaman kullanılır. Koordinatın sadece x ve y
değerleri verilir. Bu fonksiyon ile aynı işi yapan glVertex2s, glVertex2i,
glVertex2d fonksiyonları sırasıyla short, integer, double türden parametre
değişkenleri alırlar. |
void
glVertex3f(float x,float y,float z); |
Bir
geometrik şekle ait kontrol noktasının koordinat değerlerini belirtir. Geometrik
şeklin ne olacağı glBegin fonksiyonunun parametresi ile belirlenir. Bu fonksiyon
üç boyutlu çizim yapılacağı zaman kullanılır. Koordinatın x ve y değerlerine
ilaveten z değeri de verilir. Bu fonksiyon ile aynı işi yapan glVertex3s,
glVertex3i, glVertex3d fonksiyonları sırasıyla short, integer, double türden
parametre değişkenleri alırlar. |
   
* OPENGL DÖNÜŞÜM FONKSİYONLARI :
void
glRotate(double aci,double x,double y,double z); |
Şekil
"aci" parametresi ile belirtilen derece değeri kadar döndürülür. x, y ve z parametrelerine 0 değeri verilirse o eksen etrafında dönme olmaz, 1 ise o eksen etrafında dönme olur, -1 ise o eksen etrafında ters yöne dönme olur. |
void
glTranslated(double x,double y,double z); |
Koordinat
sistemini, o anki koordinatlara x, y, z değerlerini ekleyerek öteler. |
void
glLoadIdentity(); |
Yapılmış
tüm dönüşümleri geri alır. |
void
glScaled(double x,double y,double z); |
Ölçekleme
yapar. Parametre olarak verilen değerler 1’den küçük ise şekil küçültülür,
büyükse şekil büyütülür. Bu fonksiyonla aynı işi yapan glScalef fonksiyonu
float türden parametre alır. |
* Grafik
Donanımı ve Görüntü Yönetimi Üzerine Temel Bilgiler :
Ekrandaki görüntünün
yenilenmesi, CRT (Cathode Ray Tube) tarafından yapılır. CRT, bilgisayar grafik
donanımının bir parçasıdır. Ekran için yatay ve düşey tarama zamanlama sinyalleri
oluştur, eş zamanlı olarak da görüntü belleği adres sayacını bir artırır. Görüntüleme
sistemi, bu adres değerlerini alarak buralardaki kodları okur ve çözümleyerek
monitöre sinyaller şeklinde gönderir. Görüntü yönetimi tamamen CRT denetleyicisinde
olduğu için biz programımızdan buna müdahele edemeyiz. Programımızdan OpenGL’in
çizim için kullandığı bellek bölgesine yeni şekiller eklemek yoluyla değişiklikler
gönderdiğimizde, bu değişiklikler anında monitöre yansıtılır. Bu durum, çizimde
performansı düşürücü sonuçlara sebep olur. Devamlı ekrandaki görüntünün yenilenmesi
ekranda yanıp sönme gibi sonuçlara da yol açabilir. Bu problemi çözmek için
izleyebileceğimiz birkaç teknik vardır. Bu tekniklerde temel mantık, ekrandaki
görüntünün sürekli yenilenmesi değil, değişikliklerin hepsinin tamamlandıktan
sonra bir defada yenilenmesidir. Bu tekniği kullandığımız zaman, çizim işlemlerini
başka bir bellek bölgesinde yapıp sonra ekran bölgesine aktarırırz. Buna "double
buffering) denilmektedir. ekranın yenilenmiş görüntüsü arka planda hazırlanırken,
kullanıcının gördüğü ekranda hiçbir değişiklik yapılmaz. Tüm değişiklikler tamamlandıktan
sonra, programımızdan glSwapBuffers() fonksiyonunu çağırırız ve arka planda
hazırlanan değişiklikler kullanıcıya yansıtılır. Ekranın yenilenmesi sırasındaki
titremeler, yanıp sönme vb problemler bu şekilde önlenebilir.
Şimdi uygulamamıza
geçelim. İlk olarak tüm kodları kendimiz yazacağız, yukarıda bahsettiğimiz GLUT
araç kitini veya sihirbazı kullanmayacağız. Bu araçların nasıl kullanılacağına
dair bilgileri de ileride ele alırız. OpenGL kütüphanesini kullanacağımız için
öncelikle derleyiciye bu kütüphanelere .lib dosyalarını göstermeliyiz. Bu dosyalar
derleme işlemi yapan programlarda, proje ayarları bölümünde belirtilir. Microsoft
Visual C++ 6.0 derleyicisinde bu işlem şöyle yapılır : Proje açıldıktan sonra
"Project" menüsünden "Settings" seçeneği seçilerek ayarlar
penceresine ulaşılır. Bu pencerede "Link" sekmesinde "Object/library
modules" kısmına ilgili dosyaların isimleri yazılır. MinGW’de de aynı yol
izlenir. Microsoft Visual C++ 7.0 (.NET)’da ise "Project" menüsünden
"Properties" seçilerek özellikler penceresi açılır. Bu pencerede soldaki
menüden "Linker" seçilir ve "Input" sekmesine gelinir. Burada
"Additional Dependencies" kısmına ilhili dosyaların isimleri yazılır.
    
|
|
Yukarıda
solda görülen resim, Microsoft Visual C++ 6.0’da, yukarıda sağda görülen
resim Microsoft Visual C++ 7.0’da (.NET’te) ve yandaki resim de MinGW’de
kütüphane dosyalarının belirtilmesini göstermektedir. Kaynak kod dosyamızda
"include" etmemiz gereken başlık dosyasının adı ise gl\glu.h’dır.
Projeyi oluştururken, proje seçeneği olarak "Win32 Application"
seçilmelidir. Projeye .c uzantılı bir kaynak kod dosyası ekledikten sonra
kullanacağımız kütüphane dosyalarını proje ayarları menülerinde yukarda
bahsedildiği gibi belirtiriz. Artık kodlamaya geçebiliriz.
Programımız
WinMain fonksiyonu ile başlayacak. Bu fonksiyon içerisinde pencere oluşturan
"CreateWindow" ya da "CreateWindowEx" fonksiyonu yerine
kendi yazacağımız bir pencere fonksiyonunu çağıracağız. Bizim yazacağımız
bu fonksiyon da CraeteWindow ya da CraeteWindowEx fonksiyonunu çağıracak,
ancak biz ilaveten başka işlemler de yapacağız ve sonuçta oluşan pencere
bir openGL penceresi olacak. Önce WinMain fonksiyonumuzu yazalım. OpenGL
penceresini oluşturan fonksiyonu, WinMain içerisinde çağıracağımız için
bu fonksiyonun prototip bildirimine bir bakalım. Tanımlamasını WinMain’i
oluşturduktan sonra yapalım.
BOOL
CreateGLWindow(char * title, int width, int height, int bits) |
Fonksiyonun
geri dönüş değeri türü olarak yazılan BOOL, kaynak kod dosyasının başında
#define önişlemci komutu ile int olarak tanımlanmıştır. Fonksiyonun ilk
parametresi oluşturulacak pencerenin başlığı, ikinci ve üçüncü parametreleri
pencerenin genişliği ve yüksekliği, son parametresi ise bir seçeneğini
belirtmektedir. Geri dönüş değeri başarı durumunu belirtmektedir.
|
WinMain içerisinden
çağırdığımız diğer fonksiyonlar da, oluşturduğumuz OpenGL penceresini yok eden
KillGLWindow isimli fonksiyon ve boş bir pencere çizen DrawGLSceen isimli fonksiyondur.
CreateGLWindow fonksiyonundan çağrılan fonksiyonlar da, penceremiz için başlangıç
işlemlerini yapan InitializeGL fonksiyonu ve penceremizin boyutlarını ayarlayan
ResizeGLScene fonksiyonudur. Bu fonksiyonların prototip bildirimleri ise şu şekildedir
:
GLvoid KillGLWindow(GLvoid);
BOOL DrawGLScene(GLvoid); int InitializeGL(GLvoid);
GLvoid ReSizeGLScene(GLsizei width, GLsizei height); |
GLvoid, OpenGL
kütüphanesinde tanımlanmış bir sembolik sabittir. C dilinde kullandığımız void
anahtar sözcüğü ile eşdeğer anlama sahiptir. Yani KillGLWindow isimli fonksiyonumuz
parametre değişkeni almamaktadır ve geri dönüş değeri de yoktur. Yaptığı iş
ise, oluşturulan pencereyi yok etmektir. DrawGLScene isimli fonksiyon ise çizim
işlemlerinin yapılacağı fonksiyondur. Bu uygulamada herhangi bir çizim yapmayacağımız
için aslında bu fonksiyon sadece ekranı temizleyecektir. Yazı dizimizin ileriki
bölümlerinde bu fonksiyon içerisinde çizim işlemleri de yapacağız. Kaynak kod
dosyamızın başında tüm program boyunca kullanacağımız birtakım global deişkenler
tanımladık. Bu değişkenlere de bir göz atalım :
HGLRC hRC
= NULL;
HDC hDC = NULL;
HWND hWnd = NULL;
HINSTANCE hInstance; |
OpenGL programları
bir Rendering Context’e (RC) bağlıdırlar. RC, OpenGL programından yapılan çağrıları
Device Context’e (DC) iletir. Bir pencereye çizim yapabilmek için bir DC oluşturmamız
gereklidir. DC, penceremiz ile GDI (Graphics Device Interface) arasında bağlantı
kurar. RC ve DC nesnelerini global alanda hRC ve hDC isimleri ile tanımladık.
HWND türünden tanımladığımız hWnd isimli nesne ise, oluşturacağımız pencereye
Windows sistemi tarafından atanacak handle değerini tutacaktır. HISNTANCE türünden
tanımladığımız hInstance ise, uygulamanın belleğe yüklenme adresini tutacaktır.
Bu nesnelerin hepsini yazdığımız fonksiyonlarda kulanacağımız için global alanda
tanımladık.
Şimdi WinMain fonksiyonumuza
geçelim :
int WINAPI
WinMain(HINSTANCE hInstance, //.exe dosya formatının belleğe yüklenme adresi
                          
HINSTANCE hPrevInstance, //Önceki kopya
                          
LPSTR lpCmdLine, //Komut satırı argümanları
                          
int nCmdShow) //Ana pencerenin görüntülenme özelliğini belirten parametre
{
    WPARAM wParam;
    MSG msg;
    BOOL end = FALSE;
   
if (!CreateGLWindow("C ve OpenGL : OpenGL Penceresi Oluşturmak",640,480,16))
       return -1;
   
while(!end)
    {
       if (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
{
          if (msg.message == WM_QUIT)
             end = TRUE;
          else {
             TranslateMessage(&msg);
             DispatchMessage(&msg);
          }
       }
       else
{
          DrawGLScene();
          SwapBuffers(hDC);
       }
    }
    KillGLWindow();
    return (msg.wParam);
}
|
Windows GUI programlama’dan
ve API programlamadan hatırlanacağı üzere WINAPI sembolik sabiti, fonksiyonun
çağrılma biçimini belirler. "_stdcall" olarak tanımlanmıştır. Fonksiyonun
başında tanımlanan wParam değişkeni ve msg yapısı, mesajlarla ilgili birtakım
bilgileri tutmaktadır. "done" isimli değişken ise TRUE veya FALSE
değerlerini alabilir ve programın akışını kontrol eder. Bu değişken 0 değerine
sahip olduğunda while döngüsünden çıkılarak program da sonlandırılacaktır. WinMain’in
başında değişkenler tanımlandıktan sonra, CreateGLWindow fonksiyonu çağrılarak
pencerenin oluşturulması sağlanıyor. Eğer pencere oluşturma işlemi başarısızlıkla
sonuçlanırsa sıfırdan farklı bir değere geri dönülerek fonksiyonun başarısız
olduğu bilgisi gönderiliyor. Eğer pencere başarılı bir şekilde oluşturulmuşsa
while döngüsüne giriliyor. Döngü içerisinde, İşlenmesi gereken mesaj olup olmadığı
PeekMessage fonksiyonu ile sorgulanıyor. Eğer işlenmesi gereken bir mesaj varsa
PeekMessage fonksiyonu içerisinde bu mesaj ile ilgili bilgiler fonksiyona parametre
olarak geçilen msg nesnesine yazılır. Blok içerisinde de msg nesnesinin içeriği
kontrol edilerek işlenmesi gereken mesajlara ilişkin işlemler yapılıyor. Eğer
VM_QUIT mesajı gönderilmişse "end" değişkeni TRUE yapılıyor ve while
döngüsünden çıkılarak program sonlandırılıyor. VM_QUIT mesajı programın sonlandırılması
istendiğinde gönderilir. Eğer çıkma mesajı gelmemişse gelen mesaj TranslateMessage
ve DispatchMessage fonksiyonları ile sistemin işleyebileceği bir hale getirilir.
Herhangi bir mesajın alınmaması durumunda ise DrawGLScene fonksiyonu çağrılarak
çizim işlemi yapılıyor ve SwapBuffers fonksiyonu çağrılarak arka planda depolanan
çizim değişikliklerinin ekrana yansıtılması sağlanıyor. Şimdi başlangıç fonksiyonumuzda
çağırdığımız diğer fonksiyonların tanımlamalarına geçebiliriz. İlk olarak pencereyi
oluşturan CreateGLScene fonksiyonunu ele alalım :
BOOL CreateGLWindow(char
* title, int width, int height, int bits)
{
    GLuint PixelFormat;
    WNDCLASS wc;
    DWORD dwExStyle;
    DWORD dwStyle;
    static PIXELFORMATDESCRIPTOR
pfd;
    RECT WindowRect;
    WindowRect.left = (long)0;
    WindowRect.right = (long)width;
    WindowRect.top = (long)0;
    WindowRect.bottom = (long)height;
    hInstance
= GetModuleHandle(NULL);
    wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
    wc.lpfnWndProc = (WNDPROC) WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
    wc.hCursor = LoadCursor(NULL,IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName = NULL;
    wc.lpszClassName = "OpenGL";
    if (!RegisterClass(&wc))
{
       MessageBox(NULL, "Pencere sınıfı tanıtımı
işleminde başarısız olundu.", "HATA", MB_OK|MB_ICONEXCLAMATION);
       return FALSE;
    }
    dwExStyle
= WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
    dwStyle = WS_OVERLAPPEDWINDOW;
    AdjustWindowRectEx(&WindowRect,
dwStyle, FALSE, dwExStyle);
   
if (!(hWnd = CreateWindowEx(dwExStyle,
                    
"COpenGL",
                    
title,
                    
WS_CLIPSIBLINGS |
                    
WS_CLIPCHILDREN | //OpenGL penceresinin doğru bir şekilde gösterilmesini
sağlar.
                    
dwStyle,
                    
0, 0,
                    
WindowRect.right - WindowRect.left,
                    
WindowRect.bottom - WindowRect.top,
                    
NULL,
                    
NULL, //no menu
                    
hInstance,
                    
NULL)))
    {
       KillGLWindow();
       MessageBox(NULL, "Pencere oluştururken
hata oluştu", "HATA", MB_OK|MB_ICONEXCLAMATION);
       return 0;
   }
    pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = bits;
    pfd.cRedBits = 0;
    pfd.cRedShift = 0;
    pfd.cGreenBits = 0;
    pfd.cGreenShift = 0;
    pfd.cBlueBits = 0;
    pfd.cBlueShift = 0;
    pfd.cAlphaBits = 0;
    pfd.cAlphaShift = 0;
    pfd.cAccumBits = 0;
    pfd.cAccumRedBits = 0;
    pfd.cAccumGreenBits = 0;
    pfd.cAccumBlueBits = 0;
    pfd.cAccumAlphaBits = 0;
    pfd.cDepthBits = 16;
    pfd.cStencilBits = 0;
    pfd.cAuxBuffers = 0;
    pfd.iLayerType = PFD_MAIN_PLANE;
    pfd.bReserved = 0;
    pfd.dwLayerMask = 0;
    pfd.dwVisibleMask = 0;
    pfd.dwDamageMask = 0;
    if (!(hDC
= GetDC(hWnd)))
    {
       KillGLWindow();
       MessageBox(NULL, "OpenGL Device Context oluşturulamadı","HATA",
MB_OK|MB_ICONEXCLAMATION);
       return FALSE;
    }
    if (!(PixelFormat=ChoosePixelFormat(hDC,&pfd)))
{
       KillGLWindow();
       MessageBox(NULL,"Uygun pixel formatı bulunamadı","HATA",
MB_OK|MB_ICONEXCLAMATION);
       return FALSE;
    }
    if (!SetPixelFormat(hDC,
PixelFormat, &pfd)) {
       KillGLWindow();
       MessageBox(NULL,"Pixel formatı set edilemedi","HATA",
MB_OK|MB_ICONEXCLAMATION);
       return FALSE;
    }
    if (!(hRC
= wglCreateContext(hDC))) {
       KillGLWindow();
       MessageBox(NULL,"OpenGL rendering context alınamadı","HATA",
MB_OK|MB_ICONEXCLAMATION);
       return FALSE;
    }
    if (!wglMakeCurrent(hDC,hRC))
{
       KillGLWindow();
       MessageBox(NULL,"Rendering context
aktif hale getirilemedi","HATA", MB_OK|MB_ICONEXCLAMATION);
       return FALSE;
    }
    ShowWindow(hWnd,SW_SHOW);
    SetForegroundWindow(hWnd);
    SetFocus(hWnd);
    ReSizeGLScene(width,height);
   
if (!InitializeGL()) {
       KillGLWindow();
       MessageBox(NULL,"Initilaize başarısız
oldu","HATA",MB_OK|MB_ICONEXCLAMATION);
       return FALSE;
    }
    return TRUE;
}
|
OpenGL penceresini
oluşturan CreateGLWindow fonksiyonumuzu adım adım inceleyelim. Oluşturacağımız
pencere ilişkin özellik bilgilerini tutacağımız WNDCLASS türünden wc isimli
bir yapı nesnesi tanımlıyoruz. Penceremize ilşkin bilgileri bu yapı nesnesinin
veri elemanlarına atıyoruz. Pencereyi oluşturmadan önce RegisterClass fonksiyonu
ile sisteme tanıtıyoruz. PixelFormat nesnesi ile işletim sistemine kullanmak
istediğimiz piksel formatını belirteceğiz. Eğer böyle bir format bulunursa sistem
bize o formata ilişkin bir değeri döndürecek. dwExStyle ve dwStyle nesneleri
de pencereye ilişkin stil bilgilerini tutacak. RECT yapısı türünden tanımladığımız
WindowRect yapı nesnesini ise pencerenin genişlik ve yükseklik değerlerini belirtirken
kullanacağız. WS_EX_WINDOWEDGE, pencereye biraz daha üç boyutlu bir görünüm
kazandırır. WS_OVERLAPPEDWINDOW ise başlık çubuğu, minimize/maximize tuşları
ve pencere menüsü olan bir pencereyi ifade eder. AdjustWindowRectEx fonksiyonu
ile pencerenin stil ve özellik bilgileri ayarlanır. Eğer pencere oluşturulamamışsa
!hWnd koşul bloğuna girilir, KillGLWindow fonksiyonu çağrılarak pencere yok
edilir ve program sonlandırılır. PIXELFORMATDESCRIPTOR yapısı türünden tanımladığımız
pfd isimli yapı nesnesi kullanmak istediğimiz piksel formatına ilişkin bilgileri
tutacak. OpenGL uygulaması yapıtığımız için kullanacağımız formatın OpenGL’i
desteklemesi gereklidir. Ayrıca yukarıda bahsettiğimiz "double buffering"
tekniğini kullanacağımız için formatın bu tekniği de desteklemesi gereklidir.
pfd yapı nesnesinin veri elemanlarına istediğimiz teknikleri destekleyen formata
ilişkin bilgileri atadık. GetDC fonksiyonu ile, DeviceContext’e erişmeye çalışıyoruz.
Eğer bu işlem başarılı olamazsa program sonlandırılmalıdır. Başarılı olursa
buradan gelen değeri hDC isimli global nesnemize atıyoruz. DC başarıyla alındıktan
sonra kullanmak istediğimiz piksel formatını sisteme belirtiyoruz. Eğer sistemimiz
kullanmak istediğimiz piksel formatını desteklemiyorsa yine program sonlandırılmalıdır.
Eğer kullanmak istediğimiz piksel formatı sistemimiz tarafından destekleniyorsa,
bu formatı kullanmak istediğimizi SetPikselFormat fonksiyonu ile belirtiyoruz.
Bu işlemin başarısız olması durumunda yine program sonlandırılıyor. wglCreateContext
fonksiyonu ile RC’yi oluşturmaya çalışıyoruz. Bu işlem başarılı olursa geri
dönen değer hRC isimli nesneye atanıyor. Başarısız olursa program sonlandırılıyor.
Tüm bu işlemler başarıyla gerçekleşmişse RC ve DC’yi kullanmak için önce bunları
wglMakeCurrent fonksiyonu ile aktif hale getirmeye çalışıyoruz. Başarısızlık
durumunda yine program sonlandırılıyor. Bu işlemlerin herhangi birisinin başarısız
olması durumunda programı sonlandırırken KillGLWindow isimli fonksiyonu çağırıyoruz.
Eğer tüm bu adımlar geçilmişse, artık elimizde bir OpenGL penceresi vardır.
Şimdi ShowWindow fonksiyonunu çağırarak bu pencereyi gösterebiliriz. SetForegroundWindow
fonksiyonu pencerenin yüksek öncelikle gösterilmesini sağlar. SetFocus fonksiyonu
ile, oluşturulan pencereye odaklanma sağlanır. ReSizeGLScene fonksiyonu ile
penceremizi istediğimiz genişlik ve yüksekliğe sahip hale getiriyoruz. InıtGL
fonksiyonu, OpenGL penceremiz oluştuğu zaman yapmak istediklerimizi belirttiğimiz
bir fonksiyondur. Örneğin eğer biz OpenGL penceremizde çizeceğimiz şekillere
bir doku oluşturmak istiyorsak (şekil yüzeylerini fotoğraflarla kaplama) bu
işlem için kullanacağımız resimleri yüklememiz gereklidir. Bu işlemi de InitializeGL
fonksiyonunda yapıyoruz. InitializeGL fonksiyonu OpenGL penceremiz için bir
başlangıç fonksiyonu olarak düşünülebilir. Penceremizin genel özelliklerini,
resim, doku döşeme gibi işlemler, görüntüye belli bir derinlik değeri verme
gibi ayarlamalar bu fonksiyonda yapılarak başarısı kontrol edilir. Eğer bu işlemlerden
birisinde başarısız olunmuşsa program sonlandırılır. Başlangıç için yapmak istediğimiz
herhangi bir özel işlem yoksa bu fonksiyonu yazmayabiliriz.
Şimdi WinMain fonksiyonu
içerisinden çağırdığımız fonksiyonların tanımlamalarına geçelim :
GLvoid KillGLWindow(GLvoid)
{
    if (hRC) {
       if (!wglMakeCurrent(NULL,NULL))
          MessageBox(NULL,"DC ve RC kapatılması sırasında hata oluştu","KAPATMA
HATASI",MB_OK | MB_ICONINFORMATION);
       if (!wglDeleteContext(hRC))
          MessageBox(NULL,"Rendering Context kapatılamadı","KAPATMA
HATASI",MB_OK | MB_ICONINFORMATION);
       hRC = NULL;
    }
    if (hDC
&& !ReleaseDC(hWnd,hDC)) {
       MessageBox(NULL,"Device Context kapatılamadı","KAPATMA
HATASI",MB_OK | MB_ICONINFORMATION);
       hDC = NULL;
    }
    if (hWnd
&& !DestroyWindow(hWnd)) {
       MessageBox(NULL,"hWnd kapatılamadı","KAPATMA HATASI",MB_OK
| MB_ICONINFORMATION);
       hWnd = NULL;
    }
    if (!UnregisterClass("OpenGL",hInstance))
{
       MessageBox(NULL,"Class Unregister işlemi yapılamadı","KAPATMA
HATASI",MB_OK | MB_ICONINFORMATION);
       hInstance = NULL;
    }
}
|
KillGLWindow isimli
fonksiyon, oluşturulan pencereyi yok eden ve program sonlandırılmadan önce yapılması
gereken birtakım ayarlamaları yapan fonksiyonumuzdur. Fonksiyon hiçbir parametre
değişkeni almıyor ve geri dönüş değeri de yok. WinMain fonksiyonunda oluşturulan
RC ve DC nesneleri program sonlandırılmadan önce yok edilmelidir. KillGLWindow
fonksiyonunun ilk iki adımında bu işlemler yapılıyor. Tabii bu işlemleri yapmadan
önce bu nesnelerin başarıyla oluştuurlup oluşturulmadığını if ifadeleri ile
kontrol ediyoruz. wglMakeCurrent fonksiyonu ile RC ve DC nesnelerinin serbest
bırakılıpğ bırakılamayacağı kontrol ediliyor. Eğer serbest bırakılabiliyorsa,
wglDeleteContext fonksiyonu ile RC nesnesi siliniyor ve bu nesneyi gösteren
hRC göstericisine NULL değeri atanıyor. İkinci adımda DC nesnesinin serbest
bırakılıp bırakılamayacağı sorgulanıyor. Daha sonra pencereyi yok eden DestroyWindow
fonksiyonu çağrılıyor ve sisteme RegisterClass fonksiyonu ile tanıttığımız pencere
sınıfını unregisterClass fonksiyonu ile yok ediyoruz. Tüm bu işlemlerden sonra
programımız artık başarıyla sonlanabilir.
BOOL DrawGLScene(GLvoid)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    return TRUE;
} |
DrawGLScene fonksiyonunda,
çizim işlemlerini yapan kodlar yer alır. Bu uygulamada herhangi bir çizim yapmıyoruz,
sadece siyah ve boş bir OpenGL penceresi oluşturuyoruz. O nedenle bu fonksiyonda
ekranı temizleyen glClear fonksiyonunu ve yapılan dönüşümleri geri alan glLoadIdentity
fonksiyonunu çağırarak fonksiyondan TRUE değeri ile geri dönüyoruz.
int InitializeGL(GLvoid)
{
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
    return TRUE;
}
|
InitializeGL fonksiyonunda,
penceremiz için başlangıç ayarlarını yapacağız. (Örneğin çizeceğimiz şekillere
doku döşemek isteseydik kullanacağımız resimleri bu fonksiyonda yükleyebilir
ve başarısını kontrol edebilirdik.) glClearColor fonksiyonu ile arka planı siyah
renk yapıyoruz. glHint fonksiyonu ile pencerenin perspektifi ile ilgili bir
takım düzeltmeler yapılıyor. Bu sayede görünümün kalitesi artıyor. Ancak bu
işlem bir miktar performans düşüşüne sebep olabilir. Bu uygulama için bu performans
düşmesi önemli boyutlarda değildir. InitializeGL fonksiyonunda bu ayarlamalardan
başka birtakım derinlik ayarlamaları da yapılabilir. Bu uygulama için gerekli
olmadığından bu konuyu ileriki bölümlere bırakıyoruz.
GLvoid ReSizeGLScene(GLsizei
width, GLsizei height)
{
    if (height == 0)
       height = 1;
    gluPerspective(45.0f, (GLfloat)width / (GLfloat)height, 0.1f, 100.0f);
} |
ReSizeGLScene fonksiyonu
ile pencerenin boyutları verilen değerlere göre ayarlanır. width parametre değişkeni
pencerenin yeni genişlik değerini, height parametre değişkeni ise pencerenin
yeni yükseklik değerini belirtir. Fonksiyonu başında height parametre değişkeninin
değerinin sıfır olup olmadığı kontrol ediliyor, çünkü fonksiyonun ilerleyen
adımlarında height değeri bölen olarak kullanılacak. gluPerspective fonksiyonu
ile yeniden boyutlandırılan OpenGL penceresine bakış açısı ayarlanıyor. Pencerenin
genişlik ve yükseklik değerlerine bağlı olarak 45 derecelik bir açıdan bakış
sağlanıyor. Fonksiyonun son iki parametresi ile, pencereye ne kadar derinlikte
çizim yapabileceğimizi belirten başlangıç ve bitiş noktası veriliyor. Pencereye
olan bakışın değiştirilmesi işleminin daha ayrıntılı noktaları da vardır. Bu
ayarlamaların hepsi daha iyi bir görünüm sağlamak içindir.
Son olarak uygulamamıza,
gelen sistem mesajlarının işlenmesi işlemini yapan WndProc fonksiyonunu da ekliyoruz
:
LRESULT CALLBACK
WndProc (HWND hWnd,
                                        UINT uMsg,
                                        WPARAM wParam,
                                        LPARAM lParam)
{
    switch (uMsg)
{
       case WM_CLOSE :
{
          PostQuitMessage(0);
          return 0;
       }     }
    return DefWindowProc(hWnd,uMsg,wParam,lParam); }
|
WndProc fonksiyonunda,
uMsg parametre değişkeninin değerini kontrol eden bir switch-case ifadesi oluşturduk.
Bu parametre değişkeni gelen mesajlara ilişkin bir takım sembolik sabit değerlerine
sahip olabilir. WndProc içerisinde işlemek istediğimiz mesajları bu şekilde
yakalayabiliriz. Örneğin bir tuşa basıldığında ekran görünümüne ilişkin bir
ayarlamayı değiştirmek istiyorsak, bu tuşa basılma mesajını bu fonksiyon içerisinde
yakalayıp işleyebiliriz. Bu uygulamada biz sadece programdan çıkış mesajı verilip
verilmediğini takip ediyoruz. Eğer programdan çıkış mesajı verilmişse uMsg nesnesi
"WM_CLOSE" değerine sahip olacaktır. Bu durumda PostQuitMessage fonksiyonu
ile çıkış mesajı gönderiyoruz. Bu fonksiyonda başka herhangi bir mesajı işlemek
istemediğimiz için diğer mesajlar için DefWindowProc fonksiyonunu çağırıyoruz
ve bu mesajların işlenmesini sisteme bırakıyoruz.
Tüm bu işlemlerden
sonra uygulamamızı çalıştırdığımızda karşımıza siyah bir OpenGL penceresi çıkacaktır.
Penceremiz artık
çizim işlemleri için bizi bekliyor. Bu işlemi başka bir yazımıza bırakıyorum
ve mutlu günler diliyorum. Makaleye ait uygulamayı
buradan indirebilirsiniz. (Örnek uygulama Microsoft Visual C++ 6.0’da hazırlanmıştır.)
* KAYNAKLAR
:
  www.nehe.gamedev.net
Makale:
C ve OpenGL : Bir OpenGL Penceresi Oluşturmak C ve Sistem Programlama Çiğdem Çavdaroğlu
|