Ana içeriğe geç

Bölüm 10: Yalıtkanlar ve Cam (Dielectrics)

Su, cam, kristal, elmas gibi şeffaf malzemeler bilgisayar grafiklerinde Yalıtkan (Dielectric) olarak adlandırılır.

Işık bir cam veya su yüzeyine çarptığında iki farklı davranış gösterir: 1. Işığın bir kısmı yüzeyden ayna gibi geri seker (Yansıma / Reflection), 2. Bir kısmı ise maddenin içine girerek hızını ve doğrultusunu değiştirir (Kırılma / Refraction).

Bu bölümde bu optik fiziği sıfırdan modelleyeceğiz.


Kırılma ve Snell Yasası (Refraction & Snell's Law)

Işığın farklı optik yoğunluktaki ortamlardan geçerken nasıl kırıldığı Snell Yasası ile ifade edilir:

\[\eta \cdot \sin \theta = \eta' \cdot \sin \theta'\]

Burada: - \(\theta\): Gelen ışının yüzey normaliyle yaptığı açı, - \(\theta'\): Kırılan ışının normalle yaptığı açı, - \(\eta\) ve \(\eta'\): İki ortamın kırılma indisleridir (Refractive Index).

Yaygın bazı kırılma indisleri: - Hava: \(\approx 1.0\) - Su: \(1.33\) - Cam: \(1.5 - 1.7\) - Elmas: \(2.4\)

Kırılma Geometrisi

Kırılan ışın vektörünü \(\mathbf{R}'\) matematiksel olarak türetmek için normal vektörüne dik (\(\mathbf{R}'_{\perp}\)) ve paralel (\(\mathbf{R}'_{\parallel}\)) iki bileşene ayırırız:

\[\mathbf{R}' = \mathbf{R}'_{\perp} + \mathbf{R}'_{\parallel}\]

Snell yasasını vektör cebirine uyguladığımızda:

\[\mathbf{R}'_{\perp} = \frac{\eta}{\eta'} (\mathbf{R} + \cos \theta \mathbf{n})\]
\[\mathbf{R}'_{\parallel} = -\sqrt{1 - |\mathbf{R}'_{\perp}|^2} \mathbf{n}\]

Burada \(\cos \theta\), gelen birim ışın vektörü ile normalin nokta çarpımıyla bulunur: \(\cos \theta = (-\mathbf{R} \cdot \mathbf{n})\).

vec3.h dosyamıza bu refract fonksiyonunu ekleyelim:

vec3.h (Kırılma Fonksiyonu)
1
2
3
4
5
6
inline vec3 refract(const vec3& uv, const vec3& n, double etai_over_etat) {
    auto cos_theta = std::fmin(dot(-uv, n), 1.0);
    vec3 r_out_perp = etai_over_etat * (uv + cos_theta*n);
    vec3 r_out_parallel = -std::sqrt(std::fabs(1.0 - r_out_perp.length_squared())) * n;
    return r_out_perp + r_out_parallel;
}

Tam İç Yansıma (Total Internal Reflection)

Işın havadan suya girerken problem yoktur. Ancak ışın yoğun bir ortamdan (örneğin camın veya suyun içinden, \(\eta = 1.5\)) seyrek bir ortama (havaya, \(\eta' = 1.0\)) çıkmaya çalışırken çok ilginç bir durum ortaya çıkar!

Snell yasasını yeniden düzenleyelim:

\[\sin \theta' = \frac{\eta}{\eta'} \cdot \sin \theta\]

Eğer ışın camın içinden çok yatık bir açıyla gelirse, \(\frac{1.5}{1.0} \cdot \sin \theta\) ifadesinin değeri \(1.0\)'dan büyük çıkabilir!

Matematikte bir açının sinüsü asla \(1\)'den büyük olamaz. \(\sin \theta' > 1.0\) çıktığında kırılma denkleminin gerçel bir çözümü yoktur; yani fiziksel olarak ışığın dışarı kırılması imkansızdır!

Bu durumda ışığın tamamı camın iç yüzeyinden mükemmel bir ayna gibi geri sekmek zorundadır. Doğada su altındayken su yüzeyine yatık baktığınızda yüzeyin ayna gibi görünmesinin sebebi işte bu Tam İç Yansıma (Total Internal Reflection) olgusudur.


Schlick Yaklaşımı (Fresnel Yansıması)

Gerçek bir pencere camına veya bir vitrine tam karşıdan (dik) baktığınızda cam neredeyse tamamen şeffaftır, neredeyse hiçbir yansıma görmezsiniz.

Fakat cama çok dar, yatık bir açıyla baktığınızda yüzey adeta gümüş bir ayna gibi parlar ve etrafı yansıtır!

Bu açıya bağlı yansıma miktarını kesin olarak hesaplayan denklemler (Augustin-Jean Fresnel'in dalga denklemleri) son derece karmaşıktır. Bilgisayar grafiklerinde bu etkiyi modellemek için Christophe Schlick tarafından geliştirilen efsanevi polinom yaklaşımı kullanılır:

\[R(\theta) = R_0 + (1 - R_0)(1 - \cos \theta)^5\]

Burada dik bakış açısındaki temel yansıma katsayısı şudur:

\[R_0 = \left(\frac{1 - \eta}{1 + \eta}\right)^2\]

dielectric Materyal Sınıfının Tam Kod Tanımı

Tüm bu optik kuralları tek bir dielectric sınıfında birleştirelim:

material.h (Dielectric Sınıfı)
class dielectric : public material {
  public:
    dielectric(double refraction_index) : refraction_index(refraction_index) {}

    bool scatter(const ray& r_in, const hit_record& rec, color& attenuation, ray& scattered)
    const override {
        attenuation = color(1.0, 1.0, 1.0); // Cam ışığı soğurmaz, tüm enerjiyi iletir
        double ri = rec.front_face ? (1.0/refraction_index) : refraction_index;

        vec3 unit_direction = unit_vector(r_in.direction());
        double cos_theta = std::fmin(dot(-unit_direction, rec.normal), 1.0);
        double sin_theta = std::sqrt(1.0 - cos_theta*cos_theta);

        bool cannot_refract = ri * sin_theta > 1.0;
        vec3 direction;

        // Tam iç yansıma varsa veya Schlick olasılığı gerçekleşirse yansı; aksi halde kırıl!
        if (cannot_refract || reflectance(cos_theta, ri) > random_double())
            direction = reflect(unit_direction, rec.normal);
        else
            direction = refract(unit_direction, rec.normal, ri);

        scattered = ray(rec.p, direction);
        return true;
    }

  private:
    double refraction_index;

    static double reflectance(double cosine, double refraction_index) {
        auto r0 = (1 - refraction_index) / (1 + refraction_index);
        r0 = r0*r0;
        return r0 + (1-r0)*std::pow((1 - cosine), 5);
    }
};

İçi Boş Cam Küre (Hava Kabarcığı Numarası)

Bir cam kürenin içinde yüzen bir hava kabarcığı render etmek isterseniz ne yaparsınız?

İnanılmaz derecede zekice bir geometri numarası vardır: Yarıçapı negatif olan ikinci bir iç küre tanımlamak!

İçi Boş Cam Küre Sahnesi
auto material_ground = make_shared<lambertian>(color(0.8, 0.8, 0.0));
auto material_center = make_shared<lambertian>(color(0.1, 0.2, 0.5));
auto material_left   = make_shared<dielectric>(1.50);
auto material_right  = make_shared<metal>(color(0.8, 0.6, 0.2), 0.0);

world.add(make_shared<sphere>(point3( 0.0, -100.5, -1.0), 100.0, material_ground));
world.add(make_shared<sphere>(point3( 0.0,    0.0, -1.2),   0.5, material_center));
// Cam küre ve içindeki hava kabarcığı
world.add(make_shared<sphere>(point3(-1.0,    0.0, -1.0),   0.5, material_left));
world.add(make_shared<sphere>(point3(-1.0,    0.0, -1.0),  -0.4, material_left));
world.add(make_shared<sphere>(point3( 1.0,    0.0, -1.0),   0.5, material_right));

Neden negatif yarıçap? Çünkü kürenin normali \(\frac{\mathbf{P} - \mathbf{C}}{r}\) formülüyle hesaplanır. Yarıçap \(r\) negatif olduğunda normal vektörü dışarı değil, içe doğru yönelir! Bu da ışın izleyiciye camın içinde hava olduğunu bildirir!

Sonuç: Büyüleyici bir cam küre ve içinde ışığı baş aşağı kıran hava kabarcığı:

İçi Boş Cam Küre Renderı