Ana içeriğe geç

Bölüm 3: Işınlar, Basit Kamera ve Arka Plan

Bütün ışın izleyicilerin temelinde tek bir soru yatar: Gözden çıkan bir ışık ışını uzayda hangi rengi görür?

Bu soruyu yanıtlamak için: 1. Bir ışını matematiksel olarak tanımlamamız, 2. Kameramızdan sahnedeki her bir piksele doğru ışınlar fırlatmamız, 3. Ve o ışının yöneldiği rengi hesaplamamız gerekir.


Işın (ray) Nedir?

Matematikte bir ışın, belirli bir başlangıç noktasından çıkan ve belirli bir doğrultuda sonsuza uzanan bir doğrudur. Parametrik denklemi şöyledir:

\[\mathbf{P}(t) = \mathbf{A} + t \cdot \mathbf{b}\]

Burada: - \(\mathbf{A}\): Işının başlangıç noktası (Origin), - \(\mathbf{b}\): Işının doğrultu vektörü (Direction), - \(t\): Bir reel sayı parametresidir.

\(t > 0\) olduğu sürece \(\mathbf{P}(t)\), ışın çizgisi boyunca ileriye doğru hareket eden bir noktayı temsil eder:

ray.h
#ifndef RAY_H
#define RAY_H

#include "vec3.h"

class ray {
  public:
    ray() {}

    ray(const point3& origin, const vec3& direction)
      : orig(origin), dir(direction) {}

    const point3& origin() const  { return orig; }
    const vec3& direction() const { return dir; }

    // P(t) = Origin + t * Direction
    point3 at(double t) const {
        return orig + t*dir;
    }

  private:
    point3 orig;
    vec3 dir;
};

#endif

Kamera Geometrisi ve Görüş Penceresi (Viewport)

Kameramızı dünya koordinatlarında \((0, 0, 0)\) merkezine yerleştirelim.

Grafik geleneğinde standart olarak: - \(+X\): Sağa bakar, - \(+Y\): Yukarı bakar, - \(-Z\): Ekranın içine (karşıya) bakar.

Kamera Geometrisi

Kameranın önünde, \(Z = -1\) mesafesinde sanal bir Görüş Penceresi (Viewport) tanımlarız. Ekrandaki her bir piksel, bu pencere üzerinde küçük bir alana karşılık gelir:

Piksel Izgarası ve Işınlar

Penceremizin sol üst köşesinden başlayarak her bir pikselin dünya koordinatlarını hesaplar ve kameramızdan o noktaya doğru bir ray fırlatırız!


Gökyüzü Gradyanı: Doğrusal İnterpolasyon (lerp)

Eğer fırlattığımız ışın sahnede hiçbir nesneye çarpmazsa ne görecek? Arka planı, yani gökyüzünü!

Mavi-beyaz harika bir gökyüzü gradyanı oluşturmak için Doğrusal İnterpolasyon (Linear Interpolation / lerp) kullanırız:

\[\text{renk} = (1 - a) \cdot \text{Beyaz} + a \cdot \text{Mavi}\]

Lerp İnterpolasyonu

Burada \(a\), ışın yönünün normalize edilmiş \(y\) bileşenine göre \([0, 1]\) aralığına ölçeklenir:

ray_color Fonksiyonu
1
2
3
4
5
color ray_color(const ray& r) {
    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);
}

Tam Çalışan Kamera Programı

Artık pikselleri doğrudan boyamak yerine, kameramızdan ışınlar göndererek sahneyi render ediyoruz:

main.cc
#include "vec3.h"
#include "color.h"
#include "ray.h"

#include <iostream>

color ray_color(const ray& r) {
    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);
}

int main() {

    // Resim En/Boy Oranı ve Boyutları
    auto aspect_ratio = 16.0 / 9.0;
    int image_width = 400;

    // Yüksekliği hesapla (en az 1 piksel olmalı)
    int image_height = int(image_width / aspect_ratio);
    image_height = (image_height < 1) ? 1 : image_height;

    // Kamera Parametreleri
    auto focal_length = 1.0;
    auto viewport_height = 2.0;
    auto viewport_width = viewport_height * (double(image_width)/image_height);
    auto camera_center = point3(0, 0, 0);

    // Viewport kenar vektörleri
    auto viewport_u = vec3(viewport_width, 0, 0);
    auto viewport_v = vec3(0, -viewport_height, 0);

    // Pikseller arası mesafe vektörleri (delta)
    auto pixel_delta_u = viewport_u / image_width;
    auto pixel_delta_v = viewport_v / image_height;

    // Sol üst pikselin konumunu belirle
    auto viewport_upper_left = camera_center
                             - vec3(0, 0, focal_length) - viewport_u/2 - viewport_v/2;
    auto pixel00_loc = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v);

    // Render Döngüsü
    std::cout << "P3\n" << image_width << " " << image_height << "\n255\n";

    for (int j = 0; j < image_height; j++) {
        std::clog << "\rKalan satır: " << (image_height - j) << ' ' << std::flush;
        for (int i = 0; i < image_width; i++) {
            auto pixel_center = pixel00_loc + (i * pixel_delta_u) + (j * pixel_delta_v);
            auto ray_direction = pixel_center - camera_center;
            ray r(camera_center, ray_direction);

            color pixel_color = ray_color(r);
            write_color(std::cout, pixel_color);
        }
    }

    std::clog << "\rTamamlandı!                 \n";
}

Bu kodu derleyip çalıştırdığınızda:

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

Elde edeceğimiz görsel, gerçek bir kameranın sanal gökyüzüne bakışıdır:

Mavi Beyaz Gökyüzü

Tebrikler! Işın izleyicimizin sanal kamerası artık çalışıyor. Şimdi bir sonraki bölümde uzayımıza ilk 3 boyutlu nesnemizi (bir küreyi) yerleştireceğiz!