You are on page 1of 28

Seçim ( Picking, Selection )

OpenGL'de seçim biri direkt biride endirekt olarak iki şekilde ele alınabilir. İlk metot
OpenGL'nin kendisinin bu iş için doğrudan atfettiği apilerle yapılırken ikinci metotta yine
OpenGL'deki bazı apilerle ama direkt bu iş için atfetmediği apilerle yapılır(renk kodlama diye
adlandırabileceğimiz bu metotda, nesnelere renk atayarak sahneleme yapmadan arka
tampondan piksellerin okunması).

1.METOT: Direkt Metot

Bu dersi 3 bölümde inceleyeceğiz...


1.Bölüm: İsim Yığını
2.Bölüm: Seçim Modu
3.Bölüm: Vuruş Kayıtların İşlenmesi

1.Bölüm: İsim Yığını(Name Stack)

Giriş:
OpenGL , 3D sahnede nesnelerin seçimi için bir mekanizma sağlamıştır. Bu ders, farenin
altındaki veya OpenGL penceresinin bir dörtgensel bölgesinde bulunan nesnenin nasıl
algılanıp tespit edileceğini gösterecek. Adımlar, farenin tıklandığı yerdeki nesneleri tespit
etmek içindir:

1. Farenin pencere koordinatları elde edilir


2. Seçim(Selection) moduna girilir
3. Fare imleci etrafında sadece küçük bir pencere alanında sahneleme için Görüş hacmi tekrar
tanımlanır
4. Bütün primitifler veya sadece seçim işlemiyle ilgili olanlar sahnelenir
5. Seçim modundan çıkılıp, ekranda küçük parçasında sahnelenen nesneler tespit edilir

Nesneleri tespit etmek için, sahnenizdeki bütün konuyla ilgili nesneleri isimlendirmelisiniz.
OpenGL , primitiflere veya primitif setlerine (Nesneler) isimler vermenize imkan verir. Seçim
modunda olunduğunda, OpenGL tarafından aslında hiçbir nesnenin framebuffer'da
sahnelenmediği özel bir sahneleme modu sağlanır. Nesnelerin isimleri (artı derinlik bilgisi)
bir dizide toplanır. İsimsiz nesneler için, sadece derinlik bilgisi toplanır.

OpenGL terminolojisini kullanarak, Her ne zaman bir primitif seçim modunda sahnelenirse
bir vuruş(hit) meydana gelir . Vuruş kayıtları(Hit records), seçim tamponunda(selection
buffer) depolanır . Seçim modundan çıkılması üzerine, OpenGL, vuruş kayıtlarının seti ile
seçim tamponunu döndürür. OpenGL, her vuruş için derinlik bilgisini sağladığından dolayı,
uygulama, hangi nesnenin kullanıcıya daha yakın olduğunu o zaman kolayca bulabilir.

İsim Yığını:
Başlıktanda anlaşılacağı üzere, nesnelere verdiğiniz isimler bir yığında(stack) depolanır .
Aslında nesnelere bir metin string'indeki gibi isimler vermezsiniz. Onun yerine, nesneleri
numaralarsın. Bununla birlikte, OpenGL isim terimini kullanıldığından dolayı, bu derste aynı
şekilde numara yerine isim terimi kullanılacak.

Bir nesne sahnelendiği zaman, eğer o yeni görüş hacmini keserse bir vuruş kaydı yaratılır .
Vuruş kaydı yığındaki mevcut isimleri artı nesne için minimum ve maksimum derinliği içerir
. Vuruş kaydı, isim yığını boş bile olsa yaratıldığına dikkat çünkü derinlik bilgisi var. Eğer
başka nesneler, uygulama, seçim modundan ayrılmadan önce veya isim yığını değiştirilmeden
önce sahnelenirse, O zaman vuruş kaydında depolanan derinlik değerleri, buna bağlı olarak
değiştirilir .

Vuruş kaydı, sadece, uygulama, seçim modundan ayrıldığı zaman veya isim yığınının mevcut
içeriği değiştirildiğinde seçim tamponunda depolanır. Bu yüzden seçim modu için sahneleme
fonksiyonu , primitiflerin sahnelenmesine ek olarak isim yığınının içeriğinden de sorumludur.

OpenGL, isim yığınını yönetmek için devamdaki fonksiyonları sağlamıştır:

________________________________________
void glInitNames(void);
________________________________________

Bu fonksiyon boş bir isim yığını yaratır. Sen, isimleri yığına koymadan(push) önce yığını
ilklemek için bu fonksiyonu çağırman gerekir.
________________________________________
void glPushName(GLuint name);
________________________________________

Yığının en üstüne ismi ekler. Yığının maksimum boyutu uyarlamaya bağlıdır, OpenGL el
kitabında , uygulamaların büyük çoğunluğu için gereği kadar olacak en az 64 ismi içerdiği
yazılmakta. Tam sayıyı öğrenmek için GL_NAME_STACK_DEPTH parametresiyle
glGetIntegerv fonksiyonunu kullanabilirsin. Kapasitesi ötesinde yığına değerlere push etmek,
bir taşma hatası olan GL_STACK_OVERFLOW 'e sebebiyet verir .
________________________________________
void glPopName();
________________________________________

Yığının en üstündeki ismi çıkarır. Boş bir yığından bir değeri almak(pop etmek), bir aşağı
taşma hatası olan
GL_STACK_UNDERFLOW 'e sebebiyet verir.
________________________________________
void glLoadName(GLunit name);
________________________________________

Bu fonksiyon, yığının en üstünü isimle değiştirir. Şu çağrılarla aynıdır:


________________________________________

glPopName();
glPushName(name);

________________________________________

glLoadName fonksiyonu, temelde yukarıdaki kod parçası için bir kestirme yoldur. Boş bir
yığına bir ismi yüklemek, GL_INVALID_OPERATION hatasına sebebiyet verir .

Not: Yukarıdaki fonksiyonçağrıları, seçim modunda olunmadığı zaman görmezlikten gelinir.


Bu, tüm isim yığını
fonksiyonlarının bir tek(il) sahneleme fonksiyonu içinde iş görebileceği anlamındadır. Normal
gösteri modunda iken fonksiyonlargörmezlikten gelinir ve seçim modunda iken vuruş
kayıtları toplanır.

Not: glBegin glEnd arasında bu fonksiyonları koyamazsın, Seçim modu için yeni bir
sahneleme fonksiyonunu gerektirecek can sıkıcı bir durum ortaya çıkar. Örneğin eğer glBegin
glEnd arasında noktalar setine sahipsen ve Onları ayırt edebileceğin bir şekilde onları
adlandırmayı istiyorsan, O zaman, her isim için bir glBegin glEnd bloğu yaratmalısın .

Seçim modu için sahneleme:


Şimdi bir sahneleme fonksiyonu örneği sunulacak.
________________________________________

#define BODY 1
#define HEAD 2

...
void renderInSelectionMode() {
1 glInitNames();

2 glPushName(BODY);
3 drawBody();
4 glPopName();

5 glPushName(HEAD);
6 drawHead();
7 drawEyes();
8 glPopName();

9 drawGround();
}
________________________________________

Okey, satır satır gidelim:

1. glInitNames(); - Bu fonksiyon boş bir yığın yaratır. Bu, yığına yükleme(load),


koyma(push) veya alma(pop) gibi yığında üzerinde işlemlerden yapmadan önce gereklidir.

2. glPushName(BODY); - Bir isim yığına konur(push). Yığın şimdi tek bir isim içeriyor .

3. drawBody(); - Bir şeyler çizmek için OpenGL primitiflerini çağıran bir fonksiyon . Eğer
burada çağrılan primitiflerin herhangi biri görüş hacmini keserse bir vuruş kaydı yaratılır.
Vuruş kaydının içeriği, isim yığınında mevcut isim olacak(BODY), artı görüş hacmini kesen
o primitifler için minimum ve maksimum derinlik değerleri.

4. glPopName(); - Yığının en üstündeki ismi çıkarır . Yığında tek bir item olduğundan dolayı,
o şimdi boş olacak. İsim yığını değiştirildi bu yüzden Eğer vuruş kaydı 2'de yaratılsaydı, o,
seçim tamponunda saklanacaktı.

5. glPushName(HEAD); - Bir isim yığına konur(push). Yığın şimdi, yeniden tek bir isim
içeriyor . Yığın değiştirildi , Ama vuruş kaydı yok bu yüzden Hiçbir şey seçim tamponuna
gitmez.

6. drawHead(); - OpenGL primitiflerini sahneleyen diğer fonksiyon . Eğer primitiflerin


herhangi biri görüş hacmini keserse vuruş kaydı bir daha yaratılır .

7. drawEyes(); - OpenGL primitiflerini sahneleyen yine başka bir fonksiyon. Eğer burada
primitiflerin herhangi biri görüş hacmini keser ve 6'dan bir vuruş kaydı varsa, vuruş kaydı
buna bağlı olarak güncellenir. Vuruş kaydındaki mevcut isimler tutulur , Ama eğer
drawEyes()'deki primitiflerin herhangi biri daha minimumluk veya daha maksimumluk
derinliğe sahipse, O zaman bu yeni değerler vuruş kaydında depolanır . Eğer drawEyes()'deki
primitifler görüş hacmini kesiyor ama drawHead()'den hiçbir vuruş kaydı yoksa O zaman
yenisi yaratılır .

8. glPopName(); - Yığının en üstündeki ismiçıkarır. Yığın, tek bir isme sahip olduğundan
dolayı yığın şimdi boştur. Yeniden yığın değiştirildi bu yüzden Eğer bir vuruş kaydı
yaratılsaydı, o, seçim tamponunda depolanacaktı.

9. drawGround(); - Eğer burada çağrılan primitiflerin herhangi biri görüş hacmini keserse bir
vuruş kaydı yaratılır. Yığın boştur, bu yüzden Hiçbir isim vuruş kaydında depolanmayacak ,
sadece derinlik bilgisi. Eğer isim yığınında bu noktadan sonra hiçbir değişiklik meydana
gelmezse , O zaman yaratılan vuruş kaydı sadece, uygulama, seçim modundan ayrıldığı
zaman seçim tamponunda depolanacak . Bu, derste sonra anlatılacak.

Not: 4. ve 5. satırlar yerine glLoadName(HEAD) kullanılabilir;

En başta sallama bir ismi (kullanılmamış bir değer) push edip, Sonra İsimleri push ve pop
etmek yerine sadece glLoadName'i kullanabileceğine dikat. Yine de bir isme sahip olmayan
nesneleri es geçme ismin sallama isim mi diye kontrol etmekden daha hızlıdır . Diğer taraftan
, push - pop etmeye kıyasla glLoadName()'i çağırmak daha hızlıdır.

Bir nesne için çoklu isimler kullanmak:


Bir nesne, sadece tek bir isme sahip olacak diye bir kural yoktur. Bir nesneye bir çok isim
verebilirsin. Mesela, bir ızgara içinde birkaç kardan adam yerleştirilmiş olduğunu varsay .
Onları 1,2,3,... şeklinde isimlendirmek yerine Onların yerleştirildiği satır ve sütunlarla her
birini isimlendirebilirdin. Bu durumda her kardan adam kendi konumunu tanımlayan iki isme
sahip olacak: ızgaranın satır ve sütünu.

Devamdaki iki fonksiyon iki farklı yaklaşımı gösteriyor . ilki her kardan adam için tek bir
isim kullanıyor.
________________________________________

for(int i = -3; i < 3; i++)


for(int j = -3; j < 3; j++) {
glPushMatrix();
glPushName(i*6+j);
glTranslatef(i*10.0,0,j * 10.0);
glCallList(snowManDL);
glPopName();
glPopMatrix();
}
________________________________________

Devamdaki fonksiyon her kardan adama iki isim verir. Bu ikinci fonksiyonda eğer bir vuruş
meydana gelirse vuruş kaydı iki isme sahip olacak.
________________________________________

for(int i = -3; i < 3; i++) {


glPushName(i);
for(int j = -3; j < 3; j++) {
glPushMatrix();
glPushName(j);
glTranslatef(i*10.0,0,j * 10.0);
glCallList(snowManDL);
glPopName();
glPopMatrix();
}
glPopName();
}
________________________________________

Hiyerarşik isimlendirme:
Bu, çoklu isimlendirmede doğal bir aşamadır. Bir nesnenin özel bir yerinin tıkladığını bulmak
için bu ilginç olabilir.Örneğin sadece hangi kardan adamın tıkladığında bilmek yetmez aynı
zamanda kafasının mı yoksa vücudunun mu tıklandığınıda bilmek isteyebilirsin. Devamdaki
fonksiyon bu bilgiyi sağlar .
________________________________________

for(int i = -3; i < 3; i++) {


glPushName(i);
for(int j = -3; j < 3; j++) {
glPushMatrix();
glPushName(j);
glTranslatef(i*10.0,0,j * 10.0);
glPushName(HEAD);
glCallList(snowManHeadDL);
glLoadName(BODY);
glCallList(snowManBodyDL);
glPopName();
glPopName();
glPopMatrix();
}
glPopName();
}
________________________________________

Bu durumda, bir kardan adamın vücut veya kafasından herhangisine tıkladığında üç isme
sahip olacaksın: satır, sütun ve BODY veya HEAD değeri.

Seçim modunda sahneleme hakkında bir not:


Daha önce bahsedildiği gibi, Seçim modunda olunmadığı zaman(mesela framebuffer'da
sahneleme yapılırken) tüm yığın fonksiyon çağrıları görmezlikten gelinir. Bu, sizin, her iki
mod için bir tek(il) sahneleme fonksiyonuna sahip olmanızı ifade eder. Bu, ciddi bir israfa
neden olabilir. Bir uygulama, az miktarda seçilebilir nesnelere sahip olmalıdır. Her iki mod
için aynı fonksiyonun kullanılması , Seçim modunda olunduğu zaman birçok seçilebilir
olmayan nesnenin lüzumsuz sahnelenmesini gerektirecek. Sadece sahneleme süresi
gereğinden uzun sürmeyecek aynı zamanda vuruş kayıtlarını işlemek için gerekli sürede
uzayacak.

Bu , bu durum için seçim moduna özel bir sahneleme fonksiyonu yazmayı dikkate almayı
ifade eder. Tabi bunu yaparken çok dikkatli olmalısın. Örneğin uygulamanızda birkaç odaya
sahip olduğunuzu ve her odada çeşitli seçilebilir nesneler olduğunu varsayalım. Nesneler
diğer odalarda ve görünmüyor, duvarlarla kullanıcının nesneleri seçmesini engellemelisiniz.
Eğer aynı sahneleme fonksiyonunu kullanırsanız O zaman duvarlar bir vuruşa sebebiyet
verecek , Ve dolayısıyla duvar arkasında olan nesneleri es geçmek için derinlik bilgisini
kullanabilirsin. Bununla birlikte eğer sadece seçilebilir nesnelerli bir fonksiyon inşa etmeye
karar verirsen O zaman, bir nesnenin mevcut odada mı veya duvarın ardında mı olduğunu
söylemek için bir yola sahip olamazsın.

Bu yüzden, seçim modu için özel sahneleme fonksiyonu inşa ettiğinde, Sen, sadece seçilebilir
nesneleri değil aynı zamanda mesela duvarlar gibi blokaja sebebiyet verebilen bütün nesneleri
dahil etmelisin. Yüksek etkileşimli gerçek zamanlı uygulamalar için iyi bir çıkış yoludur bu.
Duvarlar yerine, kutu, masada olabilir.

2.Bölüm: Seçim Modu

Önceki bölümde OpenGL'de isimlendirme konusu sunuldu. Bu bölümde, seçim amacına


dönük seçim moduna girmenin nasıl olduğu gösterilecek. İlk adım, vuruş kayıtlarının
depolanacağı yeri OpenGL'e söylemektir. Bu, devamdaki fonksiyonla yapılır:
________________________________________
void glSelectBuffer(GLsizei size, GLuint *buffer);

Parametreler:
buffer : İşaretsiz tam sayı tipinde bir dizi. Bu dizi, OpenGL'nin, vuruş kayıtlarını depolayacağı
yerdir.
size : Dizinin boyutu.
________________________________________

Seçim moduna girmeden önce bu fonksiyonu çağırman gereklidir. Sonra, devamdaki çağrıyla
seçim moduna girersin:
________________________________________
void glRenderMode(GLenum mode);
Parametreler:
mode - Sahneleme moduna girmek için GL_SELECT kullanılır normal sahnelemeye dönmek
için GL_RENDER. Bu sonraki değer, varsayılan değerdir.
________________________________________

Şimdi geldik zor kısma. Uygulama, görüş hacmini tekrar tanımlamalı ki Sadece farenin
tıklandığı yer etrafında küçük bir alan sağlansın. Bunu yapmak için GL_PROJECTION'e
matris modunu set etmek gereklidir. Sonra, uygulama, normal sahneleme modu
düzenlemelerini saklamak üzere güncel matrisi push( yığına koymalı) etmelidir. Sonra matris
ilklenir. Devamdaki kod parçasında bunların nasıl yapıldığı gösterilmektedir:
________________________________________

glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();

________________________________________

Tamam böylece şimdi temiz bir projeksiyon matrisine sahipsin. Şimdi gereken tek şey, görüş
hacmini tanımlamaktır ki sahneleme sadece fare imlecinin etrafında küçük bir alanda yapılsın.
Bu, devamdaki fonksiyonu kullanarak yapılabilir:
________________________________________
void gluPickMatrix(GLdouble x, GLdouble y, GLdouble witdth, GLdouble height, GLint
viewport[4]);

Parametreler:
x,y : Bu iki değer, fare imlecinin yerini temsil eder. Onlar, seçim alanının merkezini tanımlar.
Bu değerler, pencere koordinatlarında belirtilir. Tabi OpenGL’de pencere koordinatlarının
viewport’un sol alt köşesi orjinli iken Windows işletim sisteminin sol üst köşe orjinli
olduğuna dikkat.
width, height : Seçim bölgesinin boyutu, Fazla küçük , küçük nesneleri seçiminde zorlanmanı,
Fazla büyükde, çok fazla vuruşla uğraşmak zorunda kalabilirsin.
viewport : Geçerli viewport
________________________________________

Yukarıdaki fonksiyonu çağırmadan önce, güncel viewportu elde etmek zorundasın. Bunu,
GL_VIEWPORT parametresiyle(glGetIntegerv fonksiyonunda) OpenGL durum
sorgulamasıyla yapabilirsin. Sonra, gluPickMatrix()'i çağırıp normal sahneleme için yaptığın
gibi projeksiyonunu(perspektif veya ortogonal ) set edersin. Sonunda modelview matris
moduna dönülür, Ve sahnelemeye başlamak için isim yığını ilklenir. Devamdaki kodda, bir
perspektif projeksiyonunu kullanmada yapılacak fonksiyon çağrıları sırası gösteriliyor.
________________________________________

glGetIntegerv(GL_VIEWPORT,viewport);
gluPickMatrix(cursorX,viewport[3]-cursorY,5,5,viewport);
gluPerspective(45,ratio,0.1,1000);
glMatrixMode(GL_MODELVIEW);
glInitNames();
________________________________________

gluPickMatrix'in ikinci parametresine dikkat. Daha önce bahsettik, OpenGL pencere


koordinatları windows işletim sisteminkinden farklıdır diye. İkinci parametre, iki sistem
arasında dönüşümü sağlar, yani, orjin sol üst köşeden, sol alt köşeye çevrilir.
Burda seçim bölgesi 5x5 bir penceredir. Kendi uygulamanız için uygun bir değere karar
verebilirsiniz. Doğru nesneleri seçmede zorlanıyorsanız uygun değeri bulmak için bir kaç
deneme yapın.
Devamdaki fonksiyon, seçim moduna girme ve seçimi başlatma için gerekli bütün işlemleri
yapmaktadır, Fonksiyonun aldığı cursorX ve cursorY parametreleri , farenin tıklandığı yerin
Windows işletim sistemi pencere koordinatlarıdır.
________________________________________

#define BUFSIZE 512


GLuint selectBuf[BUFSIZE]

...

void startPicking(int cursorX, int cursorY) {

GLint viewport[4];

glSelectBuffer(BUFSIZE,selectBuf);
glRenderMode(GL_SELECT);

glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();

glGetIntegerv(GL_VIEWPORT,viewport);
gluPickMatrix(cursorX,viewport[3]-cursorY,5,5,viewport);
gluPerspective(45,ratio,0.1,1000);
glMatrixMode(GL_MODELVIEW);
glInitNames();
}
________________________________________

Uygulamanıza bu fonksiyonu, projeksiyonunuzu dikkate alan uygun değişikliklerle, kopyala


yapıştır yapabilirsiniz. Bunu yaparsan o zaman sahneleme moduna girip herhangi bir grafiksel
primitifi sahnelemeden hemen önce bu fonksiyonu çağırmalısınız.

3.Bölüm: Vuruş Kayıtlarının İşlenmesi

Vuruş kayıtlarını işleme tabi tutmak için uygulamanız ilkin normal sahneleme moduna
dönmelidir. Bu, prametresi GL_RENDER olan glRenderMode() çağrısıyla yapılır. Bu
fonksiyon, seçim modunda sahneleme esnasında yaratılan vuruş kayıtlarının
sayısını döndürür. Bu adımdan sonra uygulamanız seçim tamponunu işleme tabi tutabilir.
GL_RENDER 'li glRender çağrısından önce vuruş kayıtlarının seçim tamponuna
kaydedildiğinin hiçbir garantisi yoktur buna dikkat.

Ayrıca, projeksiyon matrisini orjinal eski haline getirmek gereklidir . Bu matris, glPushMatrix
ile seçim moduna
girildiğinde kaydedildiğinden dolayı, Gerekli olan tek şey matrisi pop etmektir(stacktan
almak). Devamdaki kod parçasında gerekli adımlar gösteriliyor:

____________________________________
void stopPicking() {

int hits;

// projeksiyon matrisini orjinal eski haline getir


glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glFlush();

// Normal sahneleme moduna dön


hits = glRenderMode(GL_RENDER);

// Eğer varsa vuruşları işle


if (hits != 0)
processHits(hits,selectBuf);
}
____________________________________

Son değineceğimiz, seçim tamponu yapısı ile ilgilidir. Seçim tamponu, vuruş kayıtlarını
meydana geldiği sırayla depolar , yani primitiflerin çizim sırasıyla. Z-culling hala vuruş
kayıtları ürettiği için primitiflerin çizilmeyeceğine dikkat.

Vuruş kayıtları, isimlerin numarasını içerdiğinden dolayı, değişken boyutlu kayıtlardır.

Vuruş kaydının ilk alanı, içerilen isimlerin sayısıdır .

İkinci ve üçüncü alanlar, vuruşun minimum ve maksimum derinliğini temsil eder. Sadece
görüş hacmini kesen primitiflerin noktalarının hesaba alındığına dikkat. OpenGL kırpılan
çokgenler için noktaları tekrar inşa eder. Bundan dolayı temelde, görüş hacmini kesen
primitiflerin sahnelenen parçasının minimum ve maksimum derinliklerini elde edersin, bütün
primitifin minimum ve maksimum derinlikliğini değil. Derinlik, Z buffer'dan alınır ([0,1]
ermininde uzandığı yer), 2^32 -1 hesaplamasını yapıp en yakın tam sayıya yuvarlanır. z
buffer'ın doğrusal olmayan yapısı nedeniyle Elde edeceğiniz derinlik, bakış açısına uzaklığına
doğrusal orantılı değildir buna dikkat .

Sonraki alanlar vuruş için isimlerin dizisi kaydedilir. Bu isimler, vuruş kaydedildiği zaman
isim yığınının içerikleridir. isimlerin numarası sıfır olabildiğinden dolayı bu dizi boş dizi
olabilir buna dikkat.

Şimdi sunulan, 3 vuruş kaydı ile bir seçim tamponunun örneğidir:


Vuruş Kaydı İçeriği Tanımlama

0 ilk vuruşta hiç isim yok


4.2822e+009 İlk vuruş için minimum derinlik
4.28436e+009 İlk vuruş için maksimum derinlik

1 ikinci vuruştaki isimlerin miktarı


4.2732e+009 İkinci vuruş için minimum derinlik
4.27334e+009 İkinci vuruş için maksimum derinlik
6 İkinci vuruş için isim

2 üçüncü vuruşta isimlerin miktarı


4.27138e+009 Üçüncü vuruş için minimum derinlik
4.27155e+009 Üçüncü vuruş için maksimum derinlik
2 Üçüncü vuruş için ilk isim
5 Üçüncü vuruş için ikinci isim

Viewport'a en yakın nesneyi bulmak için derinlik bilgisini kullanırsınız. Örneğin kullanıcının
tıkladığını en küçük minimum derinliğe sahip nesneyi seçmek isteyebilirsiniz. Yukarıdaki
örnekte ilgili vuruş üçüncü vuruştur. Devamdaki fonksiyon, Red Book'dan alınmıştır, ekrana
en yakın nesnenin ismi yazdırılmakta.

_______________________________________

void processHits2 (GLint hits, GLuint buffer[])


{
unsigned int i, j;
GLuint names, *ptr, minZ,*ptrNames, numberOfNames;

printf ("hits = %d\n", hits);


ptr = (GLuint *) buffer;
minZ = 0xffffffff;
for (i = 0; i < hits; i++) {
names = *ptr;
ptr++;
if (*ptr < minZ) {
numberOfNames = names;
minZ = *ptr;
ptrNames = ptr+2;
}

ptr += names+2;
}
printf ("The closest hit names are ");
ptr = ptrNames;
for (j = 0; j < numberOfNames; j++,ptr++) {
printf ("%d ", *ptr);
}
printf ("\n");
}

Şimdiye kadar için kodlar:


//main.cpp dosyasına koyun

#include <math.h>
#include <gl\glut.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "render.h"

static camera cam;

#define SelBufferSize 512

static int mainWindow;


static int border = 6,h=200,w=350;

#define RENDER 1
#define SELECT 2
#define BUFSIZE 1024
GLuint selectBuf[BUFSIZE];
GLint hits;
int mode = RENDER;
int cursorX,cursorY;

void changeSize(int w1, int h1) {

float ratio;

h = h1;
w = w1;

if(h == 0)
h = 1;

ratio = 1.0f * w / h;
glMatrixMode(GL_PROJECTION);
glLoadIdentity();

glViewport(0, 0, w, h);

gluPerspective(45,ratio,0.1,1000);

glMatrixMode(GL_MODELVIEW);
}
void startPicking() {

GLint viewport[4];
float ratio;

glSelectBuffer(BUFSIZE,selectBuf);

glGetIntegerv(GL_VIEWPORT,viewport);

glRenderMode(GL_SELECT);

glInitNames();

glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();

gluPickMatrix(cursorX,viewport[3]-cursorY,5,5,viewport);
ratio = (viewport[2]+0.0) / viewport[3];
gluPerspective(45,ratio,0.1,1000);
glMatrixMode(GL_MODELVIEW);
}

void processHits2 (GLint hits, GLuint buffer[], int sw)


{
GLint i, j, numberOfNames;
GLuint names, *ptr, minZ,*ptrNames;

ptr = (GLuint *) buffer;


minZ = 0xffffffff;
for (i = 0; i < hits; i++) {
names = *ptr;
ptr++;
if (*ptr < minZ) {
numberOfNames = names;
minZ = *ptr;
ptrNames = ptr+2;
}

ptr += names+2;
}
if (numberOfNames > 0) {
printf ("tıkladığınız kardan adam ");
ptr = ptrNames;
for (j = 0; j < numberOfNames; j++,ptr++) {
printf ("%d ", *ptr);
}
}
else
printf("bir kardan adam tıklamadınız!");
printf ("\n");

void stopPicking() {

glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glFlush();
hits = glRenderMode(GL_RENDER);
if (hits != 0){
processHits2(hits,selectBuf,0);
}
mode = RENDER;
}

void renderScene() {

glutSetWindow(mainWindow);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

if (mode == SELECT) {
startPicking();
}
glLoadIdentity();

gluLookAt(cam.pos[0],
cam.pos[1],
cam.pos[2],

cam.lookAt[0],
cam.lookAt[1],
cam.lookAt[2],

cam.lookUp[0],
cam.lookUp[1],
cam.lookUp[2]);

draw();

if (mode == SELECT)
stopPicking();
else
glutSwapBuffers();
}

void processNormalKeys(unsigned char key, int x, int y) {

if (key == 27) {
quit();
exit(0);
}
else
processKeyboard(key, x, y);
}

void mouseStuff(int button, int state, int x, int y) {

if (button != GLUT_LEFT_BUTTON || state != GLUT_DOWN)


return;

cursorX = x;
cursorY = y;
mode = SELECT;
}

int main(int argc, char **argv)


{

glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(w,h);
mainWindow = glutCreateWindow("Direkt metotla seçim");

glutKeyboardFunc(processNormalKeys);
glutReshapeFunc(changeSize);
glutDisplayFunc(renderScene);
glutMouseFunc(mouseStuff);
glutIdleFunc(renderScene);

initScene(argc,argv);
init(&cam);

glutMainLoop();

return(0);
}

//render.cpp dosyasına koyun


#include <math.h>
#include <gl\glut.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <stdio.h>
#include <stdlib.h>

#include "render.h"

static GLint snowman_display_list;


void quit() {}

void processKeyboard(unsigned char key, int x, int y) {


printf("tuş: %d\n",key);
}

void picked(GLuint name, int sw) {


printf("ismim = %d in %d\n",name,sw);
}

void init(camera *cam) {

cam->pos[0] = 1.5;
cam->pos[1] = 3.75;
cam->pos[2] = 3;

cam->lookAt[0] = 1.5;
cam->lookAt[1] = 1.75;
cam->lookAt[2] = 0;

cam->lookUp[0] = 0;
cam->lookUp[1] = 1;
cam->lookUp[2] = 0;

void drawSnowMan() {

glColor3f(1.0f, 1.0f, 1.0f);

glTranslatef(0.0f ,0.75f, 0.0f);


glutSolidSphere(0.75f,20,20);

glTranslatef(0.0f, 1.0f, 0.0f);


glutSolidSphere(0.25f,20,20);

glPushMatrix();
glColor3f(0.0f,0.0f,0.0f);
glTranslatef(0.05f, 0.10f, 0.18f);
glutSolidSphere(0.05f,10,10);
glTranslatef(-0.1f, 0.0f, 0.0f);
glutSolidSphere(0.05f,10,10);
glPopMatrix();

glColor3f(1.0f, 0.5f , 0.5f);


glRotatef(0.0f,1.0f, 0.0f, 0.0f);
glutSolidCone(0.08f,0.5f,10,2);
}
GLuint createDL() {
GLuint snowManDL;

snowManDL = glGenLists(1);

glNewList(snowManDL,GL_COMPILE);
drawSnowMan();
glEndList();

return(snowManDL);
}

void initScene(int argc, char **argv) {

glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);

snowman_display_list = createDL();
}

void draw() {

glColor3f(0.9f, 0.9f, 0.9f);


glBegin(GL_QUADS);
glVertex3f(-100.0f, 0.0f, -100.0f);
glVertex3f(-100.0f, 0.0f, 100.0f);
glVertex3f( 100.0f, 0.0f, 100.0f);
glVertex3f( 100.0f, 0.0f, -100.0f);
glEnd();

for(int i = 0; i < 2; i++)


for(int j = 0; j < 2; j++) {
glPushMatrix();
glPushName(i*2+j);
glTranslatef(i*3.0,0,-j * 3.0);
glCallList(snowman_display_list);
glPopName();
glPopMatrix();
}

//render.h dosyasına koyun

typedef struct cameras {


float pos[3];
float lookAt[3];
float lookUp[3];
} camera;
void initScene(int argc, char **argv);
void init(camera *cam);
void draw();
void picked(GLuint name,int sw);
void processKeyboard(unsigned char key, int x, int y);
void quit();

2.METOT: Endirekt Metot (Renk kodlama )

Red Book, seçim konusuna ilişkin renk kodlama bazlı ilginç bir yaklaşım sunmuştur. Bu
yaklaşım, önceki bölümde anlatılana kıyasla kullanımı çok daha basit.
Renk kodlamada, perspektif değişikliği gerekmiyor ve bu yüzden teoride daha basittir.
Sadece, konuyla ilgili nesnelerin (seçilebilir ) her biri için farklı bir renk tayin edildiği bir
sahneleme fonksiyonu tanımlanır. Kullanıcı, sahne üzerinde fareyi tıkladığı zaman arka
tamponda sahne sahnelenir, arka tampondan seçilen piksel okunur ve rengi kontrol edilir.
Tamponlar değiştirilmediği için renk kodlamanın yapıldığı sahneleme asla görülmez.
Örneğin, 4 kardan adamlı devamdaki sahneyi düşünün. Gördükleriniz, normal sahneleme
fonksiyonunun ürünüdür.

Sonraki figür, renk kodlama sahneleme fonksiyonu tarafından arka tamponda üretilmiş
sonuçtur. Gördüğünüz üzere her kardan adam farklı bir renge sahip.

Fare tıklandığı zaman gerekli adımlar:


1. renk kodlama sahneleme fonksiyonu çağrılır
2. arka tampondan farenin tıklandığı yerdeki piksel okunur
3. tıklanan şeyi bulmak için renk bilgisi işleme tabi tutulur
4. tamponlar DEĞİŞTİRİLMEMELİ, aksi halde Kullanıcı, renk kodlanmış sahneyi
görecek(yukardaki ikinci resimdeki). Aşağıdaki kodlar zemin ve kardan adamların display list
ile çizimine ilişkin kodlar (detay için display list dersine bakın).

void draw() {

// zemini çiz
glColor3f(0.9f, 0.9f, 0.9f);
glBegin(GL_QUADS);
glVertex3f(-100.0f, 0.0f, -100.0f);
glVertex3f(-100.0f, 0.0f, 100.0f);
glVertex3f( 100.0f, 0.0f, 100.0f);
glVertex3f( 100.0f, 0.0f, -100.0f);
glEnd();

// 4 kardan adam çiz

glColor3f(1.0f, 1.0f, 1.0f);

for(int i = 0; i < 2; i++)


for(int j = 0; j < 2; j++) {
glPushMatrix();
glTranslatef(i*3.0,0,-j * 3.0);
glColor3f(1.0f, 1.0f, 1.0f);
glCallList(snowman_display_list);
glPopMatrix();
}

Takip eden fonksiyon renk kodlama sahneleme fonksiyonu. görebileceğiniz üzere, zemin,
seçilebilir bir nesne olmadığıdan burada tekrar çizimine gerek yok. Tabi kardan adamlar için
kodlar uygun şekilde değiştirildi

void drawPickingMode() {

// 4 kardan adam çiz

glDisable(GL_DITHER);
for(int i = 0; i < 2; i++)
for(int j = 0; j < 2; j++) {
glPushMatrix();
// her kardan adam için farklı renk

switch (i*2+j) {
case 0: glColor3ub(255,0,0);break;
case 1: glColor3ub(0,255,0);break;
case 2: glColor3ub(0,0,255);break;
case 3: glColor3ub(250,0,250);break;
}

glTranslatef(i*3.0,0,-j * 3.0);
glCallList(snowman_display_list);
glPopMatrix();
}
glEnable(GL_DITHER);
}

Farenin tıklandığı pixelin rengini kontrol etme, arka tampondan aynı pixeli okumayı kapsar.
Bu, devamdaki fonksiyonu kullanarak başarılabilir:

void glReadPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format,


GLenum type, GLvoid *pixels);

Parametreler:
x,y : sol alt köşe
width, length: okunacak alanın boyutu
format: okunacak veri tipi. Burada, GL_RGB.
type: piksel öğelerinin veri tipi. Burada , GL_UNSIGNED_BYTE kullanacağız.
pixels: piksellerin depolanacağı dizi. Bu, fonksiyonun sonucu.

format ve type ikilisi için muhtemel diğer seçenekleri görmek için, Red veya Blue book’a
bak.
bu yaklaşımı kullanımının getirdiği Viewport bilgisini okumak üzere y koordinatını ters
çevirmek gerekli. Viewport bilgisini aldıktan sonra, aşağıdaki kodda gösterildiği gibi
glReadPixels çağrılır. x ve y parametreleri kursör konumunu temsil eder. sadece tek pixel
okunduğundan dolayı genişlik ve yükseklik ikilisi 1 set edilir. sonra, pixel değerleri kontrol
edilir.

void processPick ()
{
GLint viewport[4];
GLubyte pixel[3];

glGetIntegerv(GL_VIEWPORT,viewport);

glReadPixels(cursorX,viewport[3]-cursorY,1,1,
GL_RGB,GL_UNSIGNED_BYTE,(void *)pixel);

printf("%d %d %d\n",pixel[0],pixel[1],pixel[2]);
if (pixel[0] == 255)
printf ("1.sıradaki 1. kardan adamı tıkladınız");
else if (pixel[1] == 255)
printf ("2.sıradaki 1. kardan adamı tıkladınız");
else if (pixel[2] == 255)
printf ("1.sıradaki 2. kardan adamı tıkladınız");
else if (pixel[0] == 250)
printf ("2.sıradaki 2. kardan adamı tıkladınız");
else
printf("Bir kardan adam tıklamadınız!");
printf ("\n");

Bu yaklaşımı problemsiz kullanmak için, birkaç şey hakkında daha bilgi vereyim. İlki, float
yerine unsigned byte olarak renkleri set et. RGB modda, her bileşen için sadece 256 değer
mümkündür, bu yüzde float olarak renk set edersen, OpenGL, mümkün en yakın rengi
seçecek. İkincisi, monitörünüzün modu , True Color(gerçek renk) olarak set olduğuna emin
olun. Aksi halde ekranda sunulan renk, icap edene yakını seçilir ve dolayısıyla arka
tampondan okuduğumuz pikseller set ettiğimiz değerlerle eşleşmez. Ve son olarak, renk
kodlaması sahneleme fonksiyonunda, aydınlatma ,dokulama, dithering(renk bileşenleri veya
indeksler , renk tamponuna yazılmadan önce mixlenme-harmanla işlemine sokulur) pasif
edildiğine emin olmalısın, çünkü bu işlemler orijinal rengi değiştirebilir.
Eğer renk set etmek için floatlarla çalışmayı istiyor veya monitörünüzün TrueColor'a
ayarlanık olduğuna garanti veremiyorsanıx o zaman belirtilen renklerin sahnelendiği zaman
nasıl görüneceğini kontrol etmek için bir test yapabilirsin.
Bu yaklaşım şunu gerektirir: uygulamanın başlatmadan önce istenen renk bloklarıyla
sahneleme yapılıp gerçekte sahnelenen renkleri glReadPixels ile okunup Bir dizide bu renkler
depolanıp ve sahneleme zamanı belirttiğin renkler yerine onlar kullanılır.
Gerçi renk seçiminde dikkatli olunmalı. Eğer her bileşen için değerler yeterince farklı set
edilmezse yine sorun yaşayabilirsin
. Örneğin monitör gerçek renk yerine yüksek renk (high color) olarak set edilikse unsigned
byte ile 250 ‘e kırmızı bileşeni ayarladığında, aslında 255'lik kırmızı bileşenini elde edersin.
32 veya 24 yerine bir rengi belirtmek için sadece 16 bit kullanıldığı zaman her bileşen için
daha az mümkün değer olur. 16 bitlik bileşen için bitlerin mümkün kombinasyonu hakkında
detay için red bookta pixel packing values(paketli piksel değerler) kısmına bak.

Şimdiye kadar için kodlar:

//main.cpp dosyasına koy

#include <math.h>
#include <gl\glut.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "render.h"

static camera cam;

#define SelBufferSize 512

static int mainWindow;


static int border = 6,h=200,w=350;

#define RENDER 1
#define SELECT 2
#define BUFSIZE 1024
GLuint selectBuf[BUFSIZE];
GLint hits;
int mode = RENDER;
int cursorX,cursorY;

void changeSize(int w1, int h1) {

float ratio;

h = h1;
w = w1;

if(h == 0)
h = 1;

ratio = 1.0f * w / h;

glMatrixMode(GL_PROJECTION);
glLoadIdentity();

glViewport(0, 0, w, h);

gluPerspective(45,ratio,0.1,1000);
glMatrixMode(GL_MODELVIEW);
}

void processPick ()
{
GLint viewport[4];
GLubyte pixel[3];

glGetIntegerv(GL_VIEWPORT,viewport);

glReadPixels(cursorX,viewport[3]-
cursorY,1,1,GL_RGB,GL_UNSIGNED_BYTE,(void *)pixel);

printf("%d %d %d\n",pixel[0],pixel[1],pixel[2]);
if (pixel[0] == 255)
printf ("1.sıradaki 1.kardan adamı tıkladınız");
else if (pixel[1] == 255)
printf ("2.sıradaki 1.kardan adamı tıkladınız ");
else if (pixel[2] == 255)
printf ("1.sıradaki 2.kardan adamı tıkladınız ");
else if (pixel[0] == 250)
printf ("2.sıradaki 2.kardan adamı tıkladınız ");
else
printf("Bir kardan adam tıklamadınız!");
printf ("\n");

void renderScene() {

glutSetWindow(mainWindow);

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

glLoadIdentity();

gluLookAt(cam.pos[0],
cam.pos[1],
cam.pos[2],

cam.lookAt[0],
cam.lookAt[1],
cam.lookAt[2],

cam.lookUp[0],
cam.lookUp[1],
cam.lookUp[2]);

if (mode == SELECT)
drawPickingMode();
else
drawPickingMode();

if (mode == SELECT) {
processPick();
mode = RENDER;
}
else
glutSwapBuffers();
}

void processNormalKeys(unsigned char key, int x, int y) {

if (key == 27) {
quit();
exit(0);
}
else
processKeyboard(key, x, y);
}

void mouseStuff(int button, int state, int x, int y) {

if (button != GLUT_LEFT_BUTTON || state != GLUT_DOWN)


return;

cursorX = x;
cursorY = y;
mode = SELECT;
}

int main(int argc, char **argv)


{

glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(w,h);
mainWindow = glutCreateWindow("Renk kodlamayla seçim");

glutKeyboardFunc(processNormalKeys);
glutReshapeFunc(changeSize);
glutDisplayFunc(renderScene);
glutMouseFunc(mouseStuff);
glutIdleFunc(renderScene);

initScene(argc,argv);
init(&cam);

glutMainLoop();

return(0);
}

//render.cpp dosyasına koyun

#include <math.h>
#include <gl\glut.h>
#include <gl\gl.h>
#include <gl\glu.h>
#include <stdio.h>
#include <stdlib.h>

#include "render.h"

static GLint snowman_display_list;

void quit() {}
void processKeyboard(unsigned char key, int x, int y) {
printf("tuş: %d\n",key);
}

void picked(GLuint name, int sw) {


printf("ismim = %d in %d\n",name,sw);
}

void init(camera *cam) {

cam->pos[0] = 1.5;
cam->pos[1] = 3.75;
cam->pos[2] = 3;

cam->lookAt[0] = 1.5;
cam->lookAt[1] = 1.75;
cam->lookAt[2] = 0;

cam->lookUp[0] = 0;
cam->lookUp[1] = 1;
cam->lookUp[2] = 0;

void drawSnowMan() {

glTranslatef(0.0f ,0.75f, 0.0f);


glutSolidSphere(0.75f,20,20);

glTranslatef(0.0f, 1.0f, 0.0f);


glutSolidSphere(0.25f,20,20);

glPushMatrix();
glColor3f(0.0f,0.0f,0.0f);
glTranslatef(0.05f, 0.10f, 0.18f);
glutSolidSphere(0.05f,10,10);
glTranslatef(-0.1f, 0.0f, 0.0f);
glutSolidSphere(0.05f,10,10);
glPopMatrix();

glColor3f(1.0f, 0.5f , 0.5f);


glRotatef(0.0f,1.0f, 0.0f, 0.0f);
glutSolidCone(0.08f,0.5f,10,2);
}

GLuint createDL() {
GLuint snowManDL;

snowManDL = glGenLists(1);

glNewList(snowManDL,GL_COMPILE);
drawSnowMan();
glEndList();

return(snowManDL);
}

void initScene(int argc, char **argv) {

glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
snowman_display_list = createDL();
}

void draw() {

glColor3f(0.9f, 0.9f, 0.9f);


glBegin(GL_QUADS);
glVertex3f(-100.0f, 0.0f, -100.0f);
glVertex3f(-100.0f, 0.0f, 100.0f);
glVertex3f( 100.0f, 0.0f, 100.0f);
glVertex3f( 100.0f, 0.0f, -100.0f);
glEnd();

glColor3f(1.0f, 1.0f, 1.0f);

for(int i = 0; i < 2; i++)


for(int j = 0; j < 2; j++) {
glPushMatrix();
glPushName(i*2+j);
glTranslatef(i*3.0,0,-j * 3.0);
glColor3f(1.0f, 1.0f, 1.0f);
glCallList(snowman_display_list);
glPopName();
glPopMatrix();
}
}

void drawPickingMode() {

glDisable(GL_DITHER);
for(int i = 0; i < 2; i++)
for(int j = 0; j < 2; j++) {
glPushMatrix();

switch (i*2+j) {
case 0: glColor3ub(255,0,0);break;
case 1: glColor3ub(0,255,0);break;
case 2: glColor3ub(0,0,255);break;
case 3: glColor3ub(130,0,130);break;
}

glTranslatef(i*3.0,0,-j * 3.0);
glCallList(snowman_display_list);
glPopMatrix();
}
glEnable(GL_DITHER);
}

//render.h dosyasına koyun

typedef struct cameras {


float pos[3];
float lookAt[3];
float lookUp[3];
} camera;

void initScene(int argc, char **argv);


void init(camera *cam);
void draw();
void drawPickingMode();
void picked(GLuint name,int sw);
void processKeyboard(unsigned char key, int x, int y);
void quit();

You might also like