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