WIN32 API Kullanarak OpenGL Programlamaya Giriş

WIN32 API Kullanarak OpenGL Programlamaya Giriş:


//Bu programda windowsta bir opengl programı yaratmayı ve ekrana basit bir çizim yapmayı
//işleyeceğiz :) Anlatımları mümkün olduğunca yeni başlayanlara yönelik hazırlamaya çalıştım.
//Yine de anlayabilmek için programlamadan orta derecede haberdar olmanız gerek.


#define WIN32_LEAN_AND_MEAN
//Bunu yazarak windows headerlarındaki bize yaramayacak fazlalıkları ayırıyoruz.Böylece
//programımızın *.exe si daha az yer kaplıyor.


#define SCREEN_WIDTH 640
#define SCREEN_HEIGHT 480
#define SCREEN_DEPTH 32
//en boy ve renk derinligi için tanımlamalar

#include <windows.h>
//Windows fonksiyon ve tanımlarının olduğu dosyayı ekliyoruz.

#include <gl\gl.h>
#include <gl\glu.h>
//OpenGL tanımlamalarını ve fonksiyonları kullanmak için bu dosyaları ekliyoruz.

#pragma comment(lib,"opengl32.lib")
#pragma comment(lib,"glu32.lib")
//OpenGL kütüphanelerini ekliyoruz.Bunu Project\Settings Link kısmına gidipte yapabilirdik.

HDC g_hdc;
//device context(programın sonunda açıklama yapmaya çalıştım ;))

HGLRC g_hrc;
//rendering context yine çizim için gerekli

float aci=0.0f;
//ekranda bir üçgeni döndürmek için kullanacağız


LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam);
//bu fonksiyon windows'un programa yolladığı mesajları kontrol etmemizi sağlıyor.
//bu mesajlar;boy değiştirme,kapanma,hareket etme gibi şeyler.
//parametreler (hwnd - penceremizi belirtiyor,iMsg mesajı,wParam ve lParam gelen mesaj hakkında
//ek bilgi içeriyor.)



int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR
lpCmdLine,int nShowCmd)
//dos uygulamalarındaki main() windows programlarında WinMain'e dönüşüyor.Bu fonksiyon geriye
//int döndürüyor.int'ten sonra gelen "WINAPI" bu fonksiyonun programın başlangıç noktası olduğunu belirtiyor.
//Şimdi parametrelere gelelim...
//hInstance     - programın o anı için bilgiler tutuyor.
//hPrevInstance - programın bir önceki anı için bilgiler tutuyor(O_o).Bu tüm win32 programlarında NULL
//                  yani sıfır işimize yaramayacak.
//lpCmdLine     - program çalıştırılırken komut satırında exe nin yanına yazılanlar bu değişkene
//                  gönderiliyor.Örnek : "ilkprog.exe deneme 1" yazarsak lpCmdLine "deneme 1" olur.
//nShowCmd      - program ilk çalıştırıldığında penceremizin nasıl gösterilceğini belirten değişken.

{
    //evet programımız başladı.şimdi yapmamız gereken bir pencere yaratmak.
    //bunu yapmak için WNDCLASSEX adındaki yapıyı kullanacağız.Bu yapının içini doldurup,
    //windows'a register edip bu yapıdaki özelliklere sahip bir pencere yaratabiliriz.

    WNDCLASSEX winclass;
    ///windows class(sınıf)

    HWND hwnd;
    //hwnd - yaratacağımız pencereyi kullanmamızı saglayacak değişken.(pencereyi yarattıktan sonra daha
    //iyi göreceksiniz.)

    MSG msg;
    //windows'un programımıza gönderebileceği tonlarca mesaj var.Bu mesajları bu değişkenle kontrol
    //edeceğiz.


    bool bBitti;
    //ve son olarak programın sonlanıp sonlanmadığını kontrol etmemizi sağlayacak değişken.

    //şimdi winclass'ın içini dolduracağız.Bu biraz uzun gelebilir ve burdaki her şey anlaşılamayabilir.
    //ama burda yazılanlar her programda nerdeyse aynı yani ilerde bunları bir fonksiyona koyup bir daha
    //yüzüne pek bakmadan kullanabilirsiniz.
    //Neyse niye uzattım ki bu kadar işte winclass... :)

    winclass.cbSize=sizeof(WNDCLASSEX);                    //yapının boyutunu belirtiyoruz
    winclass.style=CS_HREDRAW|CS_VREDRAW;                //pencere dikey veya yatayda değişikliğe uğrarsa
                                                        //tekrar çizilmesini istiyoruz
    winclass.lpfnWndProc=WndProc;                        //windows mesajlarını halledicek fonksiyonumuz
    winclass.cbClsExtra=0;                                //bu ikisi hep sıfır
    winclass.cbWndExtra=0;                                //^^^^^^^^^^
    winclass.hInstance=hInstance;                        //WinMain'deki hInstance'ı hatırladınız mı işte burda
                                                        //kullanıyoruz.
    winclass.hIcon=LoadIcon(NULL,IDI_APPLICATION);        //program ikonunu yüklüyoruz.(alt+tab yapınca çıkan)
    winclass.hCursor=LoadCursor(NULL,IDC_ARROW);        //Cursor  olarak ok yüklüyoruz.
    winclass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);    //background'u beyaza boyuyoruz.
    winclass.lpszMenuName=NULL;                            //menümüz yok.
    winclass.lpszClassName="patlican";                    //class ismi.buna istediğiniz ismi verin :)
    winclass.hIconSm=LoadIcon(NULL,IDI_WINLOGO);        //küçük ikon (pencerenin sol üst köşesinde çıkan)

    //evet winclass'ı doldurduk şimdi bunu kayıt etmemiz lazım bunu aşağıdaki gibi yapıyoruz.
    //eğer register ederken bi sorun çıkarsa programı bitiriyoruz.Ama genelde sorun çıkmaz :P

    if(RegisterClassEx(&winclass)==NULL)
        return 0;


    //kayıt işi bitti şimdi penceremizi yaratabiliriz.Bunun için CreateWindowEx(); fonksiyonunu kullanıyoruz.
    //bu fonksiyonunda birçok parametresi var.bu fonksiyon geriye,yarattığı pencereyi tanımlayan bir değer
    //gönderiyor bunu hwnd ye koyuyoruz böylece yaratılan pencereyi kontrol edebiliriz.

    hwnd = CreateWindowEx(NULL,                                //still için extra bilgi yok
                          "patlican",                        //class ismimiz yukardakiyle aynı olmalı
                          "İlk OpenGL Programımız",            //programın tepesinde yazan isim
                          WS_OVERLAPPEDWINDOW,                //pencerenin stili,ufaltma kapama buttonları var cervesi var vs.
                          100,100,                            //pencerenin sol üst köşesinin koordinatları
                          SCREEN_WIDTH,SCREEN_HEIGHT,        //pencerenin genişliği ve boyu
                          NULL,                                //pencerenin parent'ı yok yani ona sahip bir üst pencere yok
                          NULL,                                //pencerenin menüsü yok.
                          hInstance,                        //hInstance'ı burda da yazıyoruz.
                          NULL);                            //extra parametre yok
    //şimdi pencerenin yaratılmasında hata oluşup oluşmadığını kontrol ediyoruz.Eğer hata oluşmuşsa hwnd
    //NULL olur.Bir hata varsa prorgramı bitiriyoruz.

    if(hwnd==NULL)
        return 0;

    //penceremizi yarattıktan sonra artık opengl ile alakalı işlere başlayabiliriz.ilk olarak
    //pixel format oluşturacagız.

    PIXELFORMATDESCRIPTOR pfd;
    //pixel format yapısı

    //şimdi pixel format yapısını dolduracağız

    pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);        //yapının boyutu
    pfd.nVersion = 1;                                //bunun her zaman 1 olması lazım
    pfd.dwFlags = PFD_DRAW_TO_WINDOW                //pencereye cizimi,
                 | PFD_SUPPORT_OPENGL                //opengl'i
                 | PFD_SUPPORT_GDI                    //gdi cizimlerini
                 | PFD_DOUBLEBUFFER;                //ve double bufferı destekleyen bir pixel format
    pfd.dwLayerMask = PFD_MAIN_PLANE;                //bu dikkate alınmıyor.
    pfd.iPixelType = PFD_TYPE_RGBA;                    //renk olarak kırmızı yesil mavi ve
                                                    //alpha(şeffalık için) istiyoruz
    pfd.cColorBits = SCREEN_DEPTH;                    //renk derinligi
    pfd.cDepthBits = 16;                            //bu z bufferın derinligi
    pfd.cAccumBits = 0;                                //bunlar şu an gerekli diil
    pfd.cStencilBits = 0;                            //stencil buffer da istemiyoruz

    //yapıyı doldurduk. :D

    g_hdc=GetDC(hwnd);
    //penceremizin "device context"ini alıyoruz.bu çizim yapmamız için gerekli.

    int nPixelFormat;
    if ((nPixelFormat=ChoosePixelFormat(g_hdc,&pfd))==FALSE )
       //ChoosePixelFormat fonksiyonu yukarda belirtigimiz özelliklere en iyi uyan pixel formatı seçer.
    //ve geri bu pixel formatı belirten bir sayi döndürür.

    {
        //hata olusursa bir mesaj kutusu ile haberdar ediyoruz kullanıcıyı.ve programı bitiriyoruz.
        MessageBox(NULL, "Pixel Format seçme başarısız!", "Hata", MB_OK);
        PostQuitMessage(0);
    }

    if (SetPixelFormat(g_hdc, nPixelFormat, &pfd) == FALSE)
    //seçilen pixel formatı kullanmak için bu fonksiyonu kullanıyoruz.
    {
        //yine bir uyarı mesajı ve programı bitiriyoruz.
        MessageBox(NULL, "SetPixelFormat başarısız!", "Hata", MB_OK);
        PostQuitMessage(0);

    }


    g_hrc=wglCreateContext(g_hdc);
    //son olarak "rendering context"i yaratıyoruz
    wglMakeCurrent(g_hdc,g_hrc);
    //ve g_hdc ye seçiyoruz.böylece artık g_hdc'yi kullanarak çizim yapabiliriz."rendering context"i
    //oluşturmadan önce pixel formatı yaratmalıyız.ayrıca bir kaç tane "rendering context"
    //yaratıp ayrı pencerelere render yapabiliriz.(harita editörlerinde olduğu gibi)
    //Ama bu konumuzun dışında :)


    ///Penceremizi gösterip update ediyoruz.hwnd işte burda ve pencere ile ilgili fonksiyonlarda kullanılıyor.
    ShowWindow(hwnd,nShowCmd);
    UpdateWindow(hwnd);

    bBitti=false;
    //programın bitmediğini belirten değişken

    while(bBitti==false)
    //burda döngümüze başlıyoruz.Programı bitirmek istedigimizde bBitti değişkenini true yapmamız yeterli.
    {
        //bu fonksiyon eğer bir mesaj varsa doğru olur.
        //Ve o mesajı 'PM_REMOVE' olduğu için mesaj kuyruğundan kaldırır.

        if(PeekMessage(&msg, NULL, NULL, NULL, PM_REMOVE))
        {
            //eger gönderilen mesaj çıkma mesajı ise yani kullanıcı pencereyi kapatmak istemişse programı
            //bitiriyoruz.

            if (msg.message == WM_QUIT)
                bBitti = true;
            //virtual key kodlarını character kodlarına çeviriyor gerekli :)
            TranslateMessage(&msg);
            //mesajı windos'a geri yolluyor.
            DispatchMessage(&msg);
        }
        //evet programızın en önemli kısmı burası.Buranın dışında yaptığımız herşey her programımızda neredeyse
        //aynı kalıcak.Burda ekrana bir üçgen cizip döndüreceğiz.En azında OpenGL ile birşey çizmiş olucaz bu
        //kadar koddan sonra :)


        //****rendering
        //önce ekranı ve depth bufferı temizliyoruz.


        glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
        //sonra modelview matrisi birim matrise eşitliyoruz.yani her frame(kare)de herşey ilk haline dönüyor.
        glLoadIdentity();
        //kamera her frame basında 0,0,0 da olduğu için 5 birim geri hareket ediyoruz
        //böylece ne çizdiğimizi göreceğiz.Bunu yapmak yerine çizdigimiz objenin z degerini -5 yapabilirdik
        //ama ben geri hareket etmeyi tercih ettim :)
                    //X      //Y  //Z


        glTranslatef(0.0f,0.0f,-5.0f);
        //glRotatef belli bir açı kadar bir eksen etrafında dönme sağlıyor.
        glRotatef(aci,0.0f,0.0f,1.0f);
        //açıyı artırıyoruz böylece dönme sürekli oluyor
        aci+=0.5f;
        //eğer tam bir tur atmışsak baştan başlıyoruz
        if(aci>=360.0f) aci=0.0f;
        //evet OpenGL de birşey çizmeden önce glBegin(); ile ne çizeceğimizi belirtiyoruz.
        //biz burda GL_TRIANGLES yazdık şimdi opengl yazdığım üç noktayı birleştirip bir üçgen
        //yapacağını biliyor.

        glBegin(GL_TRIANGLES);
            //birinci nokta tam merkezde
            glVertex3f(0.0f,0.0f,0.0f);
            //ikincisi onun sağında
            glVertex3f(1.0f,0.0f,0.0f);
            //üçüncü nokta merkezdekinin üstünde
            glVertex3f(0.0f,1.0f,0.0f);
        //çizim bitti
        glEnd();
        //çizim yaparken noktaların koordinatlarını saat yönünün tersinde yazmak iyi bir alışkanlık.
        //ama mecbur diilsiniz tabi :)


        SwapBuffers(g_hdc);
        //SwapBuffers(); nedir yahu diyenler olabilir.
        //Hani pixel format yaratırken şöyle bişey yazmıştık PFD_DOUBLEBUFFER;
        //şimdi bizim çizim için iki tane alanımız var bunlardan biri ön yüzey digeri arka yüzey
        //kullanıcı sadece ön yüzü görüyor biz ise çizimlerimizi arka yüzeye yapıyoruz.
        //çizimleri bitirince arka yüzü bu komutla öne getiriyoruz ve kullanıcı ne çizdiğimizi görüyor.
        //bunun faydası görüntünün titremesini önlemsi.inanmayan arkadaşlar pixelformattan
        //PFD_DOUBLEBUFFER 'ı kaldırıp farkı görebilirler :]


    }
    //bu noktaya gelindiğinde program bitmek üzere.Ama programı bitirmeden önce yapmamız
    //gereken bir kaç sey daha var.Öncelikle yarattığımız pencereyi aşağıdaki basit fonksiyonla
    //yok ediyoruz hwnd'nin faydaları yine görülüyor.

    DestroyWindow(hwnd);
    //pencereyi yok ettikten sonra sıra winclass'a geliyor.Onu da verdigimiz adı ve hInstance'ı
    //kullanarak unregister ediyoruz

    UnregisterClass("patlican",hInstance);
    //son olarak msg ın wParam kısmını döndürüyoruz ve programımız bitiyor.
    return msg.wParam;
}
//bu bizim mesaj fonksiyonumuz windowstan gelen farklı mesajları burada kontrol ediceğiz.Bu
//programda sadece bir kaç mesaja bakıcaz.

LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM
lParam)
{
    //pencerinin boyutları değiştiğinde kullanacağız.
    int  height,width;

    switch(iMsg)
    {
    //bu mesaj sadece bir kere pencere yaratıldığında gelir.Eğer sadece bir kez yapılmasını istediğiniz
    //şeyler varsa mesela pixel format yaratmak o kodu burayada yazabilirdik.

    case WM_CREATE:

        break;
    //bu mesaj pencerenin boyutları değiştigi zaman gelir.Burada ufak bir OpenGL ayarı yapıyoruz.
    //bunun nedeni pencere boyutları değiştiginde perspektifi tekrar hesaplamak.

    case WM_SIZE:
        width = LOWORD(lParam);
        height = HIWORD(lParam);
        //pencerenin yeni genişliği ve boyu lParam içinde.

        if (height==0)
        //aşagıda genişliği boya böleceğiz bu yüzden boyun sıfır olması bir sayının sıfıra
        //bölünmesi ihtimalini doğuruyor ki buna asla programlarımızda izin vermemeliyiz

        {
            height=1;
            //boyu 1 yapıyoruz böylece hiçbir zaman sıfır olmuyor.
        }

        glViewport(0,0,width,height);
        //viewport'u bütün pencere yapıyoruz istersek daha küçük bir alanda yapabiliriz.
        //bu alan çizim yapacağımız bölgeyi belirtiyor.


        glMatrixMode(GL_PROJECTION);
        // projeksiyon matrisini seciyoruz
        glLoadIdentity();
        // ve birim matrise eşitliyoruz

        gluPerspective(45.0f,(GLfloat)width/(GLfloat)height, 1 ,150.0f);
        //perspective ayarı yapıyoruz.parametreler sırasıyla
        //FOV   - field of view,görüş alanı
        //ratio - width/height oranı objelerin nasıl göründüğümü etkiliyor.
        //near clipping plane - objelerin kameradan ne kadar yakında çizilmeyecegini belirtiyor.
        //                        kameraya 1 birimden yakın objeler çizilmeyecek
        //far clipping plane - objelerin kameradan ne kadar uzakta çizilmeyeceğini belirtiyor.
        //                        kameradan 150 birim ve daha uzaktaki objeler çizilmeyecek.


        glMatrixMode(GL_MODELVIEW);
        //tekrar model matrisine dönüyoruz
        glLoadIdentity();
        //ve birim matrise eşitliyoruz
        break;
        //bu iki mesaj pencere kapatılmaya ya da yok edilmeye çalışıldığında gelir.
        //PostQuitMessage(0); WM_QUIT mesajı gonderir ki bunu da yukardaki döngüde kontrol ediyoruz.
        //(PeekMessage'ın orda)
        //böylece program bitiyor.

        case WM_DESTROY:
        case WM_CLOSE:
            //Program bitmeden önce yarattığımız "rendering context"i seçip siliyoruz.
            wglMakeCurrent(NULL,NULL);
            wglDeleteContext(g_hrc);
            PostQuitMessage(0);
            break;

    }
    //bu fonksiyon butun mesajlar için normal yapılanları yapıyor.
    return (DefWindowProc(hwnd, iMsg, wParam, lParam));
}

////Notlar:
// Bu program Microsoft Visual C++ 6.0 ile yazılmıştır.
// Programın çalışması için opengl kütüphanelerinin bilgisayarınızda bulunması gerekli
// ayrıca yeni bir proje yaratırken win32 application seçmelisiniz.
//
//
// Bu baya uzun bir program gibi gözüküyor ama yorumlar olmadığı zaman o kadar da uzun diil.
// Kısaca şunları yaptık.
// 1.Pencere classı yaratıp doldurduk.Bunu kaydettik sonra bir pencere yaratık.
// 2.Ardından pencerenin "device context"ini alıp bunu pixel format yaratmak için kullandık.
// 3.Daha sonra wglCreateContext ile "rendering context"i  yaratıyoruz ve bunu "device context" e seçiyoruz.
//   Artık çizim yapabiliriz.
// 4.Çizimlerimizi yapıyoruz ve programı bitiriyoruz.
//
// Mesajlar - Windows tarafından programa gönderilen mesajlardır.Siz mouse ile programı kapatmaya çalışınca
// ya da pencerenin boyunu değiştirdeğinizde farklı mesajlar oluşur.Bu mesajlar oluştuğunda programınızın neler
// yapacağına karar verebilirsiniz.Bunu WndProc fonksiyonu ile sağlıyoruz.
//
// HDC - handle to device context.İlk duyduğumda ve gördügümde ben de ne olduğunu tam anlamamıştım.
// hdc çıktı yapmanızı sağlayan bi yapı bizim durumuzda bu çıktı ekrana oluyor ama bu printer ile
// ya da başka çıktı araçlarıyla da olabilir.
//
// HGLRC - "rendering context" HDC ile çizim yapmak için rendering context oluşturmamız gerekiyor.
// rendering context OpenGL ile çizim yapmayı sağlıyor.
//
// Evet ilk OpenGL programımız bitti.Bu programda yazılan herşeyi hemen anlamanız pek mümkün diil.
// Benim tavsiyem programla oynayıp orasını burası değiştirmeniz ve neler olduğunu görmeniz.Böylece
// çok daha kolay öğrenebilirsiniz.
//
//

Barış S. Uşaklı :: www.oyunyapimi.org :: 2003 ::




Bu yazının bulunduğu site: OyunYapimi.org
http://www.oyunyapimi.org

Bu yazı için adres:
http://www.oyunyapimi.org/modules.php?name=Sections&op=viewarticle&artid=23