Ana içeriğe geç
Ahmet Faruk UzunkayaYaratıcı geliştirici
← Projelere dön
GrafikGeliştiriliyorGüncellendi · 28 Eylül 2026

PhotonEngine

Ürün görselleştirme için yazdığım bir C++20 path tracer ve üstüne kurduğum masaüstü stüdyo.

KeyShot'a benzer bir iş akışıyla çalışan C++20 render motoru. İçinde NEE + MIS'li path tracing, Disney Principled malzeme, Owen karıştırmalı Sobol örnekleme, Embree 4 ile ışın kesişimi ve Intel OIDN ile gürültü giderme var. Üstündeki ImGui masaüstü uygulaması render'ı ayrı bir thread'de yürüttüğü için arayüz hiç beklemiyor.

C++20Path TracingEmbree 4OIDNImGui
PhotonEngine ekran görüntüsü
Durum
Geliştiriliyor
Kategori
Grafik
Güncelleme
28 Eylül 2026
Yığın
C++20Path TracingEmbree 4OIDNImGui
PhotonEngine

PhotonEngine, ürün görselleştirme için C++20 ile yazdığım bir path tracer; üstüne de bir masaüstü stüdyo uygulaması kurdum. Kullanımı KeyShot'a benziyor. OBJ, glTF ya da GLB modelini pencereye sürüklüyorsun, model zemine oturuyor, kamera kadrajı ayarlanıyor ve varsayılan stüdyo ışıkları yerini alıyor. Sonra kütüphaneden bir malzeme küresini parçanın üzerine bırakıp bir stüdyo ya da HDRI seçmen yeterli. Viewport birkaç saniye içinde gürültüsüz bir önizleme veriyor, son render'ı da tek tıkla alıyorsun. Render penceresinde 4K'ya kadar çözünürlük hazırları, örnek ya da süre sınırı ve PNG, JPEG, EXR çıktı seçenekleri var; turntable için kare dizisi de alabiliyorsun.

Bu aynı zamanda bir öğrenme projesi. Kodu, bilgisayar grafiği hattını baştan sona öğretecek şekilde Türkçe açıklamalarla yazdım. Arayüzde kütüphane, sahne ağacı ve özellikler paneli var; nesneleri viewport'ta ImGuizmo ile taşıyıp döndürebilirsin. Render ve Kil modlarının yanında Katı, Tel kafes ve Normaller modları da var, bunlar GPU'da anında çiziliyor. Projeni JSON tabanlı bir .photon dosyasına kaydedebiliyorsun. Aynı motoru arayüz olmadan kullanmak için de photon_render adında bir render ve ölçüm aracı var.

Bir ışının izlediği yol

Işık taşınımı integrators/path_tracer.cpp içinde. Her köşede hem ışık örneklemesi (NEE) hem BSDF örneklemesi yapıyorum ve ikisini güç sezgiseliyle MIS üzerinden birleştiriyorum. Ayna ve pürüzsüz cam gibi delta yüzeylerde NEE yapılmıyor. Yol, throughput'un en büyük bileşenine bakan Rus ruletiyle sonlanıyor. HDRI ortam ışığı, luminance × sin θ dağılımından kurulan 2B bir CDF ile önem örneklemesine giriyor. Örnekleyici olarak Owen karıştırmalı Sobol kullanıyorum (Burley 2020); ilerlemeli render hızlı yakınsamasını buna borçlu.

Kesişim testi için iki yol var. Embree 4 bulunursa EmbreeAccel kullanılıyor. Embree yalnızca t değerini ve barisentrik koordinatları döndürüyor; yüzey verisini motorun kendi Triangle::fillHit kodu hesaplıyor. Embree yoksa motorun kendi 12 kovalı SAH BVH'si devreye giriyor. Bu BVH'de yapraklar sırayla düz bir üçgen dizisine yerleşiyor, gezinirken önce yakın çocuğa iniliyor, gölge ışınlarında da erken çıkılıyor. Gürültüyü, albedo ve normal AOV'lerinden de yararlanan Intel Open Image Denoise 2.5 temizliyor. OIDN de isteğe bağlı; bulunmazsa gürültü giderme kapanıyor.

Malzemeler ve stüdyo ışığı

Ana malzeme modeli Disney Principled. Metal ve plastiğin yanında araç boyası için clear coat, fırçalanmış metal için anizotropi, kumaş için sheen, ince yüzey geçirgenliği ve ışık yayma da bu modelden çıkıyor. Cam ise ayrı bir malzeme: pürüzsüz ya da GGX tabanlı buzlu (VNDF örneklemeli) olabiliyor, su, cam, kristal ve elmas için de kırılma indisi hazırları var. Kütüphanede sekiz gruba ayrılmış 37 hazır malzeme duruyor. Küçük resimlerini motorun kendisi arka planda render edip diske önbelleğe alıyor; projedeki en sevdiğim ayrıntılardan biri bu. Dokular taban rengi (sRGB), normal, pürüzlülük ve metaliklik (doğrusal) kanallarına bağlanıyor.

Aydınlatmada Poly Haven'dan CC0 HDRI'lar ve prosedürel gökyüzleri var, ikisini de döndürebilirsin. Arka plan ortamın kendisi, düz bir renk ya da şeffaf olabiliyor. Sahneye gölge ve yansıma taşıyan sonsuz bir zemin kendiliğinden ekleniyor. Işıkları elle kurmak istersen alan (softbox), güneş ve nokta ışıkları var. İstemezsen ışıkları sahnenin boyutuna ve kameraya göre yerleştiren 6 stüdyo preset'inden birini seçmen yeterli.

Arayüz render'ı hiç beklemiyor

Uygulamada iki iş parçacığı var: ImGui arayüzünü yaklaşık 60 fps ile çizen UI thread'i ve viewport'u render eden RenderController. Koyduğum temel kural şu: UI hiçbir zaman bir pass'i beklemeyecek. Bunun için sahneyi iki ayrı biçimde tutuyorum. SceneGraph düzenlenebilir belge; yerel dönüşümleri, paylaşılan değişmez mesh'leri, malzemeleri, görünürlüğü ve uid'leri taşıyan bir düğüm ağacı. UI, proje dosyası ve geri al bununla çalışıyor. Render ve seçim ışınları ise değişmez bir Scene kullanıyor; içinde dünya uzayına bake edilmiş mesh'ler, ışık listesi, ortam, arka plan ve Embree ya da BVH var.

Belge değiştiğinde buildRenderScene() ağacı dünya uzayına bake ediyor; zemini, ışıkları, ortamın CDF'ini ve hızlandırıcıyı kurup yeni bir Scene üretiyor. Bu sahne render thread'ine shared_ptr ile geçiyor. Render thread'i elindeki pass bitene kadar eski sahneyi tutuyor, sıradaki pass yenisiyle başlıyor. Kamera ya da görüntü ayarı değişirse birikim sıfırlanıyor. Viewport'ta sürüklediğin sürece render 1/2 ile 1/8 arası bir çözünürlüğe iniyor ve her kareyi OIDN'in hızlı modu temizliyor. Bıraktığında tam çözünürlüğe dönülüyor; gürültü giderme bu kez 2'nin kuvveti olan örnek sayılarında çalışıyor. Ton eşlemesi yapılmış kare RGBA8 olarak yayınlanıyor, UI da onu glTexSubImage2D ile bir dokuya yükleyip ImGui::Image olarak gösteriyor.

Sahnede yerinde değiştirdiğim tek şey malzeme parametreleri. Kaydırıcıyı sürüklerken her karede sahneyi yeniden derlemek pahalı olurdu. Bu yüzden RenderController::edit() o anki pass'i karo ya da satır düzeyinde iptal ediyor, sahne kilidini alıp yeni değeri yazıyor ve birikimi baştan başlatıyor. İptal için parallelFor2D'ye bir atomic<bool> veriyorum; her karo ve satır başlamadan önce bu bayrağa bakıyor. Son render ise kendi iş parçacığında, malzemeleri kopyalanmış izole bir sahneyle çalışıyor; bu sırada viewport duraklıyor.

Geri al da bu ayrımdan faydalanıyor. UndoStack<DocSnapshot> her düzenlemeden önce belgenin tam bir kopyasını alıyor. Geometri değişmez olduğu için paylaşılıyor; kopyalanan yalnızca malzemeler ve dönüşümler. Kaydırıcıyı ne kadar sürüklersen sürükle, undoPoint bunu tek bir geri alma adımında birleştiriyor.

Renkten piksele

Sahneyi doğrusal Rec.709 uzayında hesaplıyorum, görüntü de ekrana gelene kadar şu hattan geçiyor:

Film (toplam + sayı) → ortalama → (OIDN) → pozlama (2^EV)
  → ton eğrisi (PBR Neutral / AgX / ACES / Hable) → sRGB OETF → dither'lı 8 bit

Ton eğrilerinden Khronos PBR Neutral ürün renkleri için orada; istersen AgX, ACES ya da Hable'ı da seçebilirsin. EXR çıktısı ton eşlemesi yapılmamış doğrusal radyans. Şeffaf arka planda renk premultiplied biriktiği için PNG'ye yazarken alfaya bölüyorum.

Her alt sistem src/ altında ayrı bir statik kütüphane. Derlemeyi CMake ve Ninja ile yapıyorum, şimdilik yalnızca Windows ve MSVC için tanımlı. Arayüz tarafı GLFW, Dear ImGui, ImGuizmo ve OpenGL üzerinde duruyor. Mimarinin ayrıntılarını merak edersen depodaki docs/architecture.md dosyasına göz atabilirsin.