ÜÇÜNCÜ BOYUT VE DEVAMI
Bu bölümde asıl ilgi alanımız olan üç boyutlu uzayı göreceğiz. Aslında 1 ya da 2 boyutlu uzaylardan teorik olarak bir farkı yok bu 3D uzayın, ama gözümüze görüntüler sanki 3 boyuttaymış gibi göründüğü için yaptığımız demolarda da bu boyutu kullanıyoruz. Sebep ise 3 boyutlu bir cismin monitör üzerinde izdüşümünün, gözümüzde oluşan görüntüsüyle aynı olması.
Şimdi çok kısa bir hatırlatma yapalım. 2D bir referans uzayında, başka yeni bir 2D uzayın basisi şöyle olsun:
[ox, ox] pozisyon vektörü (orijin),
[xx, xy] yön vektörü (i),
[yx, yy] yön vektörü (j).
Bu yeni uzayımızdaki bir pozisyon vektörünün ([px, py]'), yani bir konumun, referans uzayında şöyle ifade edildiğini söylemiştik:
[px, py]' = [ox, oy] + px[xx, xy] + py[yx, yy]
Bir yön vektörü ([vx, vy]') ise, referans uzayına geçtiğinde şu değerlere sahip olacaktı.
[vx, vy]' = vx[xx, xy] + vy[yx, yy]
Yani yön vektörleri uzaydan uzaya geçerken orijinlerin nerede olduğu ile ilgilenmiyordu. Sadece yön çevrimleri yapılıyordu. Bi de burada dikkatlı okuyucunun (eheh) fark edeceği üzere her vektörde vektörün cinsini belirttik. Halbuki pozisyon ve yön vektörlerini ayırmak için bi notasyon kullansak ne süper olacak. İleride onu da yapacağız, az sabredin.
Üçüncü Boyut
Eğer uzayımız 1D ya da 2D değil de 3D olsaydı durum yine değişmeycekti. Eğer tutorialı buraya kadar takip ettiyseniz ben söylemeden aşağıdakileri çıkarabilirsiniz.
Her 3D kartezyen uzayımızın basisi kendi içinde şöyle ifade edilecek:
[0, 0, 0] pozisyon vektörü (o),
[1, 0, 0] yön vektörü (i),
[0, 1, 0] yön vektörü (j),
[0, 0, 1] yön vektörü (k),
Aynı uzay içindeki tüm pozisyon vektörleri de önceki yöntemlerle ifade edilecek. Örneğin [5, -2, 8] pozisyon vektörü dediğimde bu şu anlama gelecek:
[5, -2, 8] = [0, 0, 0] + 5[1, 0, 0] - 2[0, 1, 0] + 8[0, 0, 1]
[5, -2, 8] = orijin + 5i - 2j + 8k
Bunun yanı sıra bir [5, 2, 0] yön vektörü mesela şu açılıma sahip:
[5, 2, 0] = 5[1, 0, 0] + 2[0, 1, 0] + 0[0, 0, 1]
[5, 2, 0] = 5i + 2j + 0k
"Neden pozisyon vektörlerinde orijini dahil ettik? Zaten sonuca etki etmiyor." demeyin artık. Çünkü daha önce de belirttiğim gibi, bu vektörü başka bi uzayda gösterirken orijin, o uzaydaki koordinatlara göre olacak. O zaman da sonuca etki edecek. Yani orijinin üzerine vektör ekleme, tüm pozisyon vektörlerinin tanımında var. Bu hadise de sadece matematiksel bütünlük sağlamak için değil. İleride o orijin ekleme olayı belki de hayatımızı kurtaracak.
Ha bu arada; ikinci boyutta kullandığımız x ve y eksenlerine, artık z'yi de ekledik. Söylemeyi unuttum ama o kadar önemli değil.
Üçüncü Boyutta Çevirimler
Her zamanki gibi yine bir referans uzayım var ve tüm sonuçlarımı bu uzayın vektörleri cinsinden vereceğim. Bir de hep 'yeni' dediğim (niyeyse) başka bir uzay var. Bu yeni uzayın basisinin referans uzayımdaki karşılığını biliyorum. Amacım, yeni uzayımda koordinatları verilmiş bir pozisyon yahut yön vektörünü referans uzayımın koordinatlarına çevirmek.
Pozisyon vektörü çevirimi için:
[px, py, pz]' = [ox, oy, oz] + px[xx, xy, xz] + py[yx, yy, yz] + pz[zx, zy, zz]
Yön vektörü çevrimi için:
[vx, vy, vz]' = vx[xx, xy, xz] + vy[yx, yy, yz] + vz[zx, zy, zz]
Örneğin yeni uzayımın basisinin referans uzayımdaki hali şöyle olsun:
[4, 2, 0] orijin
[3, 1, 0] i
[0, 3, 2] j
[1, 1, 6] k
Yeni uzayımda bir adet [3, 2, 2]' pozisyon vektörü olsun. Bunun referanstaki karşılığı şöyle olacaktır:
[3, 2, 2]' = [4, 2, 0] + 3[3, 1, 0] + 2[0, 3, 2] + 2[1, 1, 6]
[3, 2, 2]' = [4, 2, 0] + [9, 3, 0] + [0, 6, 4] + [2, 2, 12]
[3, 2, 2]' = [4 + 9 + 0 + 2, 2 + 3 + 6 + 2, 0 + 0 + 4 + 12]
[3, 2, 2]' = [15, 13, 16]
Yeni uzayımda bir adet [1.5, 1, 0.5]' yön vektörünün karşılığı ise şöyledir:
[1.5, 1, 0.5]' = 1.5[3, 1, 0] + 1[0, 3, 2] + 0.5[1, 1, 6]
[1.5, 1, 0.5]' = [4.5, 1.5, 0] + [0, 3, 2] + [0.5, 0.5, 3]
[1.5, 1, 0.5]' = [4.5 + 0 + 0.5, 1.5 + 3 + 0.5, 0 + 2 + 3]
[1.5, 1, 0.5]' = [5, 5, 5]
Hiç bi yeni olayımız yok. Aynı yöntemleri kullanarak yaptık hepsini.
Ters Çevrimler
Ters çevirimde amacımız referans uzayındaki bir vektörün, basisi referans uzayında belli bir uzayda nasıl gösterildiğini bulmak. Ama hatırlarsanız geçen bölümde iki boyutta bile bunu yapmak için ne taklalar atmıştık; ki yine de referans uzayının, yeni uzaydaki basisini bulamadık. O yüzden bu kısmı şimdilik atlıyorum. Lakin unutmayın ki kendileri gayet de önemli bir konu, çünkü kamera yaparken bunları kullanacağız.
3D Uzayda Birtakım Başka Olaylar
3D uzayımızın diğer 1D ve 2D uzayda bulunmayan bir özelliği mevcut. Ona geçmeden önce biraz vektörlerle uğraşalım.
Bir vektörle bir sayıyı çarpmayı, yani vektörün bir katını bulmayı anlatmadım ama her yerde kullandım. Zaten çok acayip bi şey değil. Bir vektörün a katı şöyle olacak:
a[x, y, z] = [ax, ay, az]
Aynı zamanda iki vektörün toplamı (ya da farkı) da bu yazılarda anlatılmadı. O da şöyle olacaktı.
[x1, y1, z1] + [x2, y2, z2] = [x1 + x2, y1 + y2, z1 + z2]
Bu iki işlemi çevrimlerde sürekli kullandık ama tanımlarını yapmamıştık. Zaten çok da önemli değil, direk nasıl yapılacakları belli.
Ama vektörlerle nasıl yapılacağı direk belli olmayan bir işlem var, o da çarpma. Şimdi ilk akla gelen çarpma yöntemi şöyle:
[x1, y1, z1] * [x2, y2, z2] = [x1 * x2, y1 * y2, z1 * z2]
Gayet güzel bir teknik lakin bu işlemde sonuçta elde ettiğimiz vektör bize genelde hiç bi şey sağlamıyor. Onun yerine sonucu pratikte işe yarayan iki çarpım tekniği var. Bunları sırayla görelim.
Skalar Çarpım
'Skalar çarpım' şöyle gösteriliyor:
[x1, y1, z1] . [x2, y2, z2] = x1 * x2 + y1 * y2 + z1 * z2
Yani karşılıklı elemanları çarpıp, bu çarpımların toplamını alıyoruz. Bu işleme, nokta ile gösterildiği için aynı zamanda 'dot product' da der gavurlar. Skalar çarpımda iki vektörü çarparak vektör olmayan (skalar) bir değer buluyoruz.
Şimdi bu çarpımın bize sağladıklarını öğrenelim.
Eğer bir vektörü kendisi ile skalar çarparsak şöyle olur:
[x, y, z] . [x, y, z] = x² + y² + z²
Bir vektörün uzunluğu ise 'Pisagor Teoremi' ile şöyle tanımlıdır:
[x, y, z] vektörünün uzunluğu r ise,
r = karekök (x² + y² + z²)
Demek ki;
r² = [x, y, z] . [x, y, z]
Yani bir vektörün kendisi ile skalar çarpımı, uzunluğunun karesini verir.
Skalar çarpım sadece 3D için değil tüm kartezyen uzaylar için tanımlı. Örneğin çeşitli uzaylarda şöyle:
1D uzay:
[x1] . [x2] = x1 * x2
[x] . [x] = r² = x²
2D uzay:
[x1, y1] . [x2, y2] = x1 * x2 + y1 * y2
[x, y] . [x, y] = r² = x² + y²
5D uzay:
[ax1, ax2, ax3, ax4, ax5] . [bx1, bx2, bx3, bx4, bx5]
= ax1 * bx1 + ax2 * bx2 + ax3 * bx3 + ax4 * bx4 + ax5 * bx5
[x1, x2, x3, x4, x5] . [x1, x2, x3, x4, x5]
= r² = x1² + x2² + x3² + x4² + x5²
Skalar çarpımın bir güzel özelliği de şöyle:
[x1, y1] vektörünün uzunluğu r1 yani karekök (x1² + y1²) olsun.
[x2, y2] vektörünün uzunluğu ise r2 olsun. Bu durumda şu olur:
[x1, y1] . [x2, y2] = x1 * x2 + y2 * y2 = r1 * r2 * cos(theta)
Theta diye seslendiğimiz şey bu iki vektörün yönleri arasındaki açıdır. Yani a ve b adlı iki vektörün arasındaki açıyı istiyorsak şu denklemi kullanırız.
theta = acos(a.b / (ra * rb))
ra = karekök(a.a) ve rb = karekök(b.B) olduğu içün:
theta = acos(a.B) / karekök(a.a * b.B)
Bu açıyı her boyutta bulabiliriz, hatta kolaylık açısından 1D uzayda bakalım.
[x1] . [x2] = x1 * x2 = r1 * r2 * cos(theta)
r1 = x1 ve r2 = x2 olduğu için
x1 * x2 = x1 * x2 * cos(theta)
cos(theta) = 1
Yani 1D uzaydaki herhangi iki vektör arasındaki açı ya 0 ya da 180 derece olacak.
Skalar çarpımla başka neler yapabiliriz? Bunlar bilinmesi gerekli şeyler değil isteyen atlayabilir.
Eğer bir vektörü kendi uzunluğuna bölersek, 1 uzunluğunda ve aynı yöne bakan bir vektör elde ederiz:
a / ra = a / karekök(a.a)
Bir vektörün başka bir vektör üzerindeki izdüşümünün uzunluğu ise kendi uzunluğu * cos(theta)'dır. Yani a vektörünü b vektörü üzerine yansıtırsak ortaya çıkan vektörün uzunluğu şudur:
ra * cos(theta) = karekök(a.a) * cos(theta)
ra * cos(theta) = (ra * rb * cos(theta)) / rb
ra * cos(theta) = (a.B) / rb
Şimdi, bu izdüşüm vektörü b ile aynı yönde olacak (kağıda çizip görün). Yani b yönündeki birim vektörü izdüşüm vektörünün uzunluğuyla çarparsak, izdüşüm vektörünü buluruz.
b yönündeki birim vektör b / rb idi.
izd-ab = (b / rb) * (a.b / rb)
izd-ab = (b * a.B) / rb²
izd-ab = (b * a.B) / b.b
Aynı mantıkla b vektörünün a üzerindeki izdüşümü şöyle olur:
izd-ba = (a * a.B) / a.a
Buradaki bölüm işleminin üst kısmı bir vektör çarpı bir skalar, yani bir vektör. Bir skalara bölersem bunu, sonuç yine vektördür. Bu izdüşüm vektörünü kartezyen kamera olayında kullanacağız. Ama orada da optional olacak. Aynı zamanda bu izdüşüm hadisesi çok rahatça anlaşılabilecek üzere gölgelendirmede kullanılıyor. Ama o konuya bu tutorialda yer veremeyeceğim. (Ben de bilmiyom lan daha!)
Skalar çarpım hazmedildiyse bir sonrakine geçelim...
Vektör Çarpım
Şimdi skalar çarpımı bi şekilde işimize yaradığı için özel bi şekilde tanımladık. Şimdi iki vektör arasında öyle bi çarpma işlemi tanımlayalım ki, sonuç hem vektör olsun hem de işimize yarasın.
3 boyutta bildiğimiz gibi 3 farklı yöne gidebiliriz. Ve tüm yönlere gidebilmek için en az 3 vektöre ihtiyaç var. O zaman vektör çarpımımız iki vektörü çarptığı zaman, sonuç bu iki vektöre de dik olsun. Bu sayede iki vektörün gidemediği yerlere gidebilecek bir vektör elde edelim. Yani vektör çarpım sonucundaki yönü belirledik.
Bu vektörün bir de uzunluğu olacak. Skalar çarpımda vektör uzunluğu bana iki vektör arasındaki açının kosinüsünü veriyordu. Vektör çarpım sonucu da, bana aynı açının sinüsünü versin de bi işe yarasın.
|a × b| = ra * rb * sin(theta)
Yani diyor ki, a ve b nin vektör çarpımının uzunluğu, bu vektörlerin uzunlukları ile, aralarındaki açının sinüsünün çarpımıdır. Vektör çarpımını '×' ile gösteriyoruz ve bazen 'cross product' diyoruz.
Şimdi yönü hakkında konuşalım. İki vektöre de dik olan yönü verecek. Eğer böyle bir yön tekse 3D uzaydayız (yani 3 tane dik yön vardır) demektir. Bu yüzden vektör çarpım sadece ve sadece 3D uzayda tanımlıdır. Bu da 3D uzayın yazının başında bahsettiğim özelliği idi.
Sonucun yönünü nasıl bulacağız. Şimdi sağ elinizi tabanca pozisyonuna getirin. Baş parmak havada, işaret parmağı ve orta parmak ileriyi gösteriyor. Şimdi orta parmağınızı sola doğru (avuç içine) hareket ettirin. Baş parmağınız hala bu iki parmağa dik kalacaktır. 'İşaret parmağı × orta parmak = baş parmak' olur. Buna 'sağ el kuralı' adını vermişler. Eğer ilk vektörünüz işaret, ikincisi orta parmak yönündeyse, vektör çarpımınız baş parmak yönünde olacaktır. Eğer ters yönde yaparsanız işlemi, ters yönde bir vektör çıkar. Yani:
a × b = -(b × a)
Ne işe yarayacak bu derseniz, mesela işaret ve orta parmağınız, bir yüzeyin bir noktasındaki teğet yüzeyi temsil ediyor olsun, ya da basitçe işaret ve orta parmağınızla bir üçgen oluşturdunuz. Burada baş parmağınız bu üçgene dik olacaktır. Eğer başparmağınızı kendi uzunluğuna bölerseniz, sonuç bu yüzeyin ya da üçgenin normalidir. Bu normal denen birim vektör o yüzeyin o kısmının ne kadar aydınlatılması gerektiği hakkında şahane bilgiler verir.
Vektör çarpım hakkında yeterli teorik bilgimiz olduğuna göre tanımına gelelim. İki vektörün vektör çarpımı şöyle gösterilir:
[x1, y1, z1] × [x2, y2, z2]
= [y1 * z2 - z1 * y2, z1 * x2 - x1 * z2, x1 * y2 - y1 * x2]
Eğer bu formülü ilk kez görüyorsanız muhtemelen hakkında iyi şeyler düşünmeyeceksiniz. Formülün nasıl çıkarıldığı gibi abuk bi konuya da girmiyorum. Ezberlemenize gerek yok, gerektikçe burdan geçirebilirsiniz. Ama ezberlemek isterseniz de kolay bi yol mevcut (ki ben onu kullanıyorum). Eğer biliyosanız Buraya Atlayın diyen yere atlayın.
Çarpım şöyle olsun:
a × b = c
Kural 1: c'nin x değeri, a ve b'nin y ve z değerlerine bağlı.
Kural 2: c'nin y değeri, a ve b'nin z ve x değerlerine bağlı.
Kural 3: c'nin z değeri, a ve b'nin x ve y değerlerine bağlı.
Bu üç kuralın, c'nin a ve b'ye dik olmasını sağladığını söyleyebiliriz. c nin her elemanı, a ve b'de diğer elemanlara bağlı olacak.
Kural 4:
...ax, ay, az, ax...
...bx, by, bz, bx...
Yani diyor ki (demiyor bendiyorum) çarpımlar tabloda çapraz yapılacak. Yani:
a x b =
[
ay * bz - az * by, // yz ve zy çapraz
az * bx - ax * bz, // zx ve xz çapraz
ax * by - ay * bx // xy ve yx çapraz
]
Kural 5: Çarpımlar arasında eksiler olacak.
Kural 6: Çapraz çarpımlarda, bir önce gelen eleman başlıcak.
Mesela ilkinde y, z'den önce o başladı. İkincide z, x'ten önce (kural 4'e bak) ve üçüncüde de x, y den önce.
Kural 7: a[hede] * b[hodo] - a[hodo] * b[hede] şeklinde bi çaprazlama olacak.
Ehehe fazla kural oldu, ama cidden çok kolay. Şunları hatırlayın:
x elemanı için yazıcam. Bi sonraki y, Demek ki ay * bz - az * by
y elemanı için yazıcam. Bi sonraki z, Demek ki az * bx - ax * bz
z elemanı için yazıcam. Bi sonraki x, Demek ki ax * by - ay * bx
Buraya Atlayın
Vektör çarpım bilmek çevrimlerinizi değiştirmeyecek ama olaya hakimiyetinizi artıracak. Işıklandırma yapacaksanız da zaten kullanmak zorundasınız. OpenGL filan normal bulma özelliği sunuyor, ama performans için kendi normallerinizi kendiniz bulmak isteyeceksiniz (her framede baştan hesaplamamak için mesela).
3D hakkında söylenecek başka bi şey yok. Vektör çarpım hariç tüm kartezyen uzayların özelliklerini taşıyor. Öyleyse artık devamına geçebiliriz...
Daha Fazlası
İnat edip buraya kadar okuduysanız 4, 5 ve daha fazla boyutlar sizin gözünüzü korkutuyor olmamalı. Tüm çevrim formülleri aynı. Hatta matrislere geçince ters çevrimlerin de aynı olduğunu göreceğiz. 3 boyutu monitöre aktarırken perspektif kullanmıyorsak, x ve y değerlerini aynen alıyoruz ve z değerini atıyoruz. 4 ya da 5 boyutlu olsa cisim, yine aynısını yapacağız. Ama sonuçta ortaya çıkan şey çok rahat idrak edilir olmayacak. Aynı zamanda 3D haricinde istediğimiz kadar perspektif tanımlarız ama gözümüzdekinden olmadığı için yine monitörde o ilüzyonu alamayacağız.
Yazının geri kalanı matrislerle ilgili olacak. Onlarda da 3D'den öteye gitmeyeceğiz, ama bilin ki aynı olaylar bi çok alanda çok boyutlarda yapılabiliyor. Bunların geometrik anlamları bile var. Misal vektör alanları yahut tensorler gibi ucubik isimli konular söz konusu olunca çevrimlerin 6 boyutlu matrislerle filan yapılması gerekebiliyor.
O zaman, "Pozisyon, uzay, vektör gibi geometrik kavramları yaladım yuttum, teorik altyapım da az çok oturdu." diyen herkesi bir sonraki yazı olan 'Neden Matrisler?'e davet edelim.