Ana içeriğe geç

Bölüm 4: Sahneye Küre Ekleme

Bir ışın izleyici (ray tracer) yazarken sahneye ekleyeceğimiz ilk nesne geleneksel olarak küredir (sphere). Bunun sebebi analitik geometride bir ışının küreyle kesişip kesişmediğini hesaplamanın hem çok kolay olması, hem de temiz bir cebirsel çözüme sahip olmasıdır.


Işın-Küre Kesişimi Matematiği

Analitik geometride, merkezi \((C_x, C_y, C_z)\) noktasında ve yarıçapı \(r\) olan bir kürenin yüzeyindeki herhangi bir \((x, y, z)\) noktası şu denklemi sağlar:

\[(x - C_x)^2 + (y - C_y)^2 + (z - C_z)^2 = r^2\]

Eğer bir nokta kürenin içindeyse, bu ifadenin değeri \(r^2\)'den küçüktür; eğer dışındaysa, \(r^2\)'den büyüktür; tam yüzeyindeyse \(r^2\)'ye eşittir.

Bu ifadeyi vektör cebiriyle çok daha zarif bir şekilde yazabiliriz. Küre üzerindeki herhangi bir noktayı \(\mathbf{P} = (x, y, z)\) ve kürenin merkezini \(\mathbf{C} = (C_x, C_y, C_z)\) vektörleri olarak tanımlarsak, \(\mathbf{C}\) noktasından \(\mathbf{P}\) noktasına giden vektör \((\mathbf{P} - \mathbf{C})\) olur.

Bir vektörün kendisiyle nokta çarpımı (dot product), boyunun karesine eşittir:

\[(\mathbf{P} - \mathbf{C}) \cdot (\mathbf{P} - \mathbf{C}) = (x - C_x)^2 + (y - C_y)^2 + (z - C_z)^2\]

Dolayısıyla küre denklemimiz tek satırda şuna dönüşür:

\[(\mathbf{P} - \mathbf{C}) \cdot (\mathbf{P} - \mathbf{C}) = r^2\]

Bu denklemi şöyle okuyabiliriz: "Merkeze (\(\mathbf{C}\)) olan uzaklığının karesi \(r^2\) olan tüm \(\mathbf{P}\) noktaları kürenin yüzeyindedir."

Işın Küre Kesişimi


Işın Denklemini Küre Denklemine Yerleştirmek

Bizim bir ışınımız var: Başlangıç noktası \(\mathbf{Q}\) (origin), doğrultusu \(\mathbf{d}\) (direction) olan ve parametresi \(t\) olan doğru:

\[\mathbf{P}(t) = \mathbf{Q} + t \cdot \mathbf{d}\]

Sorumuz şudur: "Işın üzerinde öyle bir \(t\) parametresi var mıdır ki, o noktadaki \(\mathbf{P}(t)\), aynı zamanda kürenin denklemini de sağlasın?"

Bunu bulmak için \(\mathbf{P}(t)\) ifadesini küre denklemindeki \(\mathbf{P}\) yerine koyarız:

\[((\mathbf{Q} + t \mathbf{d}) - \mathbf{C}) \cdot ((\mathbf{Q} + t \mathbf{d}) - \mathbf{C}) = r^2\]

Burada vektör çıkarma işleminin yerini değiştirip terimleri gruplayalım:

\[(t \mathbf{d} + (\mathbf{Q} - \mathbf{C})) \cdot (t \mathbf{d} + (\mathbf{Q} - \mathbf{C})) = r^2\]

Nokta çarpımının dağılma özelliğini kullanarak parantezleri açalım:

\[t^2 (\mathbf{d} \cdot \mathbf{d}) + 2t (\mathbf{d} \cdot (\mathbf{Q} - \mathbf{C})) + ((\mathbf{Q} - \mathbf{C}) \cdot (\mathbf{Q} - \mathbf{C})) = r^2\]

\(r^2\) terimini sol tarafa alıp sıfıra eşitleyelim:

\[t^2 (\mathbf{d} \cdot \mathbf{d}) + 2t (\mathbf{d} \cdot (\mathbf{Q} - \mathbf{C})) + ((\mathbf{Q} - \mathbf{C}) \cdot (\mathbf{Q} - \mathbf{C}) - r^2) = 0\]

Dikkatle bakın: Bu denklemde \(t\) bilinmeyen bir skaler sayıdır; \(\mathbf{d}\), \(\mathbf{Q}\), \(\mathbf{C}\) ve \(r\) ise bizim bildiğimiz sabit değerlerdir!

Bu tam olarak lise matematiğinden bildiğimiz ikinci dereceden (kuadratik) denklemdir:

\[a t^2 + b t + c = 0\]

Burada katsayılar şunlardır: - \(a = \mathbf{d} \cdot \mathbf{d}\) - \(b = 2 \mathbf{d} \cdot (\mathbf{Q} - \mathbf{C})\) - \(c = (\mathbf{Q} - \mathbf{C}) \cdot (\mathbf{Q} - \mathbf{C}) - r^2\)


Diskriminant (\(\Delta\)) ve Köklerin Anlamı

Kuadratik bir denklemde çözümlerin sayısı Diskriminant ile belirlenir:

\[\Delta = b^2 - 4ac\]
  • \(\Delta < 0\) (Negatif): Gerçel kök yoktur. Işın küreyi tamamen ıskalar, boşluktan geçer.
  • \(\Delta = 0\) (Sıfır): Tek bir gerçel kök vardır. Işın kürenin yüzeyine tam olarak teğet geçer.
  • \(\Delta > 0\) (Pozitif): İki gerçel kök (\(t_1\) ve \(t_2\)) vardır. Işın kürenin ön yüzeyinden içeri girer (\(t_1\)) ve arka yüzeyinden dışarı çıkar (\(t_2\)).

Çözüm kökleri genel kuadratik formülle bulunur:

\[t = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}\]

Biz kameraya en yakın olan çarpışma noktasını görmek istediğimiz için genellikle küçük olan pozitif kökü (\(t = \frac{-b - \sqrt{\Delta}}{2a}\)) seçeriz.


İlk Kesişim Testi Kodu

Şimdilik sadece ışının küreye çarpıp çarpmadığını test eden basit bir fonksiyon yazalım:

hit_sphere Fonksiyonu
1
2
3
4
5
6
7
8
bool hit_sphere(const point3& center, double radius, const ray& r) {
    vec3 oc = center - r.origin();
    auto a = dot(r.direction(), r.direction());
    auto b = -2.0 * dot(r.direction(), oc);
    auto c = dot(oc, oc) - radius*radius;
    auto discriminant = b*b - 4*a*c;
    return (discriminant >= 0);
}

Not: Kodda oc vektörünü (center - r.origin()) yani \((\mathbf{C} - \mathbf{Q})\) olarak aldık. Bu durumda formüldeki \((\mathbf{Q} - \mathbf{C}) = -(\mathbf{C} - \mathbf{Q})\) olduğu için \(b\) katsayısının başına eksi işareti gelir: -2.0 * dot(r.direction(), oc).


ray_color Fonksiyonunun Güncellenmesi

Eğer ışınımız \(Z = -1\) noktasında duran \(0.5\) yarıçaplı küreye çarpıyorsa kırmızı bir renk döndürelim; çarpmıyorsa önceki bölümdeki gökyüzü gradyanını gösterelim:

main.cc
1
2
3
4
5
6
7
8
color ray_color(const ray& r) {
    if (hit_sphere(point3(0,0,-1), 0.5, r))
        return color(1, 0, 0); // Kırmızı

    vec3 unit_direction = unit_vector(r.direction());
    auto a = 0.5*(unit_direction.y() + 1.0);
    return (1.0-a)*color(1.0, 1.0, 1.0) + a*color(0.5, 0.7, 1.0);
}

İlk 3B Kırmızı Küre Renderımız

Programımızı derleyip çalıştırdığımızda:

g++ -O3 main.cc -o inOneWeekend
./inOneWeekend > image.ppm

Karşımıza çıkan görüntü:

İlk Kırmızı Küre

Tebrikler! Düz bir çizim değil, 3 boyutlu uzayda ışınların fiziksel olarak çarptığı gerçek bir küreyi render ettiniz!

Şu anda bu küre düz kırmızı bir disk gibi görünüyor; çünkü kürenin üzerindeki eğrilik ve derinlik hakkında hiçbir görsel ipucumuz yok. Bir sonraki bölümde yüzey normallerini hesaplayarak küremize gerçek bir 3B hacim kazandıracağız!