Merhaba değerli okurlarımız! Bu ay sizlere bilgisayar görselleri alanında, gözlerimizle algıladığımız gerçekliğe oldukça yaklaşan -görseller üreten- bir teknolojiden bahsedeceğim: Işın izleme. Küresel literatürde “ray tracing” olarak geçen bu teknoloji kullanılarak ortaya çıkarılan bazı görseller, fotoğraflardan ayırt edilemeyecek kalitede. Günümüzün oyunlarında ve animasyon teknolojilerinde kullanılan rasterizasyona alternatif olan ışın izlemenin gerçekçiliğinin sırrını, neden bu başarısına rağmen ticari ortamlarda göremediğimizi ve geleceğini bu yazıda sizlerle paylaşmaya çalışacağım.

Algoritma

Günümüz standardı rasterizasyonun temel prensibi, sahnedeki objelerin ekrana olan izdüşümlerini, matematiksel formüller kullanarak bulmak. Işın izlemede ise derdimiz, adı üstünde, ışın. Sahnedeki gözün (veya kameranın) önüne bir ekran koyup, gözden, ekranın bir pikselinden geçecek şekilde, sahneye bir ışın fırlatıyoruz. Sonrasında bu ışının çarptığı yerin (tabii eğer bir yere çarptıysa) ışık alıp almadığını buluyoruz. Eğer ışık alıyorsa bu noktadaki rengi hesaplayıp piksele yazıyoruz. Bu işlemi bütün pikseller için yaptığımız zaman, elimizde bir resim oluyor. Koda dökersek:

def aydinlik(nokta):
    foreach(isik in sahne)
        Isin golgeIsini
        foreach(obje in sahne)
            if(golgeIsini objeyle kesismiyorsa)
                return true
    return false

def isinFirlat(isin):
    Piksel piksel
    foreach(obje in sahne)
        if(isin objeyle kesisiyorsa)
            piksel = kesismeNoktasi.ambient
            if(aydinlik(kesismeNoktasi))
                piksel += kesismeNoktasi.diffuse
                piksel += kesismeNoktasi.specular
                if(obje yanistansa)
                    piksel += isinFirlat(yansiyanIsin)
                if(obje saydamsa)
                    piksel += isinFirlat(kirilanIsin)
    return piksel

def resimOlustur(ekran):
    foreach(piksel in ekran)
        Isin isin(gozKoordinati, pikselKoordinati)
        piksel = isinFirlat(isin)
    return ekran

Burada değinmediğimiz önemli bir şey var: kesismeNoktasi.renk deyiverince tabii ki istediğimiz gerçekçiliği yakalayamıyoruz. O kadar basit değil maalesef. Neyse ki bir noktanın, aldığı ışığın açısıyla değişen rengini hesaplayan güzide bir formül çıkarmış 1973 yılında Vietnamlı Bui Thong Phong amcamız. "Phong shading" olarak bilinen bu formül şöyle oluyor:

I_(toplam) = I_(ambient) + I_(diffuse) + I_(specular)

I_(ambient): Ortamdan kaynaklanan ışık. Objenin her noktası için aynı. Bu yüzden obje ışık alsa da almasa da bu değer elimizde oluyor. Basit bir KYM(kırmızı-yeşil-mavi) veri yapısı, istediğimiz şey oluyor burada.

I_(ambient) = I_a * K_a

Bir KYM değeri olan I_a ortam ışığının gücünü, 0 ile 1 arasında değisen K_a ise objenin bu ışığı ne kadar yansıttığını temsil etmekte.

I_(diffuse): Objenin yansıttığı ışık. Objeler kendi renklerinde ışık yansıttıkları için biz onları o renklerde görürüz. Bu bileşen, objenin malzemesine, rengine ve ışığın elimizdeki noktaya geliş açısına bağlı.

I_(diffuse) = I_i * K_d * cos(A)

KYM I_i => ışığın yoğunluğu
katsayı K_d => bu ışığın ne kadarını yansıttığı
A => bu noktadan ışığa fırlatılan ışın (gölgesini) ile yüzey normali arasındaki açı

Tabii ki bu hesabı bütün ışıklar için teker teker yapmamız gerekiyor. Gözden kaçırmamak gerekir: diffuse bileşeni bizim objeye baktığımız açıdan bağımsız. Bakış açımıza bağlı olan bileşen, specular bileşeni.

I_(specular): Objenin parlaklığından kaynaklanan bileşen.

I_(specular) = I_i * K_s * (cos(B))^n

KYM I_i => ışığın yoğunluğu
katsayı K_s => bu ışığın ne kadarını yansıttığı
B => gölge ışınının yansıma ışınıyla asıl ışınımız arasındaki açı
n => parlaklık katsayısı

Işın izlemenin asıl büyüsü saydam ve yansıtan cisimlerle uğraşmayı çok kolay bir hale getirmesi. Burada yapmak gereken tek işlem, o noktadaki yansıyan ve/veya kırılan ışını hesaplamak ve elimizdeki fonksiyonun aynısını kullanarak, bu ışınların sahnedeki yolculukları boyunca çarptıkları yerleri hiçbir fazladan kod yükü altına girmeden bulmak. Rasterizasyonda tam bir karın ağrısı olan bu işlem, ışın izlemenin gözün doğasına yakınlığından dolayı daha kolay anlaşılır ve özyinelemeli uygulaması sayesinde, sıkıntı kaynağı olmaktan epey uzak.

Verimlilik

Peki madem bu kadar cici görüntüler elde edilebiliyor, niye bu algoritmayı etrafta görmüyoruz? Çünkü yavaş. Tek bir kare elde etmek için bile, sahneye bağlı olarak, dakikalar veya saatler gerekiyor. Benim yukarıda örneğini verdiğim kod parçası sadece algoritmayı anlatmak için, çok uygulanabilirliği olan bir şey değil.

Şöyle örnek verelim. Diyelim ki 1 milyon üçgenden oluşan, sıradan bir modelimiz var ve bunu 1024x768'lik bir ekrana çizdirmek istiyoruz. Sadece üçgen-ışın kesişmelerini hesaplarsak; 1024*768*1000000 = 786 432 000 000 (yazıyla yedi yüz seksen altı milyar dört yüz otuz iki milyon) kesişim hesabı yapmamız gerekiyor. Gölge ışınları, yansımalar ve kırılmalar dahil olmamak üzere. Güzel bir yol değil.

Işın izleme algoritmasını hızlandırmanın en tatlı yöntemlerinden biri, kodu paralelleştirmek. Bir pikselin herhangi bir hesabı diğer pikseli etkilemediği için, bütün pikselleri sırayla yapmak yerine paralel işlemlerde yapmak epey verimlilik sağlıyor.

Başka bir yöntem de sınırlayıcı hacimler (bounding volumes) kullanmak. Eğer ışınımız, sahnede modelle alakasız bir yere gidiyorsa, modelin bütün üçgenleriyle "Belki tutar" diye kesiştirme hesabı yapmak, boşuna vakit kaybı; kesişmeyeceğini biliyoruz çünkü. Bunun çözümü, modeli tamamen içine alacak, basit bir hacim oluşturmak, küre veya küp gibi. Öyle ki, ışınımız eğer bu hacimle kesişmiyorsa, modeldeki herhangi bir üçgenle kesişmeyeceği de kesin olsun. Bu sebepler milyonlarca üçgen yerine basit bir hacimle kesişim denemesi yapmak, tabii ki istediğimiz bir şey olur.

Üzerinde gerçekten epey iyileştirme yapılmasına rağmen bu algoritma, piyasada karşımıza pek çıkmamakta. Şöyle bir örnek vereyim: 1995'teki donanımlar kullanılarak, Oyuncak Hikayesi (Toy Story) adlı animasyon filminin ortalama bir karesini oluşturmak, 2 saat sürüyordu. Yıllar sonra 2006'da gösterime giren, Pixar'ın "Arabalar" animasyonunun ortalama bir karesini oluşturmak ise sizce ne kadar süre aldı? Yarım saat? 1 saat? 2 saat? Cevap yaklaşık 15 saat.

Gerçek Zamanda?

Evet mümkün. Teoride de pratikte de. Günümüz animasyon ve oyunları gerçek zamanda çok rahat çalışabiliyorlar, çünkü ekran kartı donanımı (GPU), rasterizasyon için özelleştirilmiş durumda. Işın izleme ise işini ana işlemcide icra ediyor. Sorun şu ki, bilgisayarlarımızdaki işlemcilerimiz henüz istediğimiz kalitede sahneleri kabul edilebilir hızlarda oluşturamazlar... Henüz. İşlemci teknolojisi, özellikle paralelleştirmeyi kolaylaştırdığı için çok çekirdekli işlemciler hayatımıza girdikçe, bu algoritmanın uygulamalarını daha çok göreceğiz. Hatta son yıllarda, Intel bünyesinde bulunan "Advanced Graphics Lab" 'de bilim adamı olarak çalışan Daniel Pohl isimli arkadaş, Quake 3, Quake 4, Quake Wars ve Wolfenstein oyunlarındaki içeriği, ışın izleme algoritmasıyla makul hızlarda ekrana yansıtmayı başarmış.

Mayıs 2012'deki GTC konferansındaki nVidia'nın tatlı gösterisi, bu konudaki umutlarımızı epey yeşertiyor. Kaynaklardaki linkten erişebileceğiniz videoda izleyeceğiniz üzere, adamlar Kepler adlı berimsel mimariyi kullanarak gerçek zamanda ışın izlemeyi yapmışlar, üzerine bir de sıvı fiziği eklemişler. İşlemci ile GPU'nun birlikte kullanımı konusunda çığır açan bu teknoloji sayesinde yakın gelecekte, gerçekle ayırt edilemeyecek görsellerle karşılaşacağız.

Işın izleme, uzun süre boyunca rasterizasyona alternatif olarak kalmış durumdaydı. Fakat son yıllardaki gelişmeler, artık yavaş yavaş iki yöntemin birbirinin rakibi olarak algılanmasına yol açacak gibi. Günün birinde oynadığınız oyunun, pencereden dışarı baktığınız zaman gördüğünüz gerçeklikle ekranınıza yansıyacak olması, hiç de hayal değil. Gelecek ay görüşmek üzere, kalın sağlıcakla!

Daha Fazla Bilgi İçin