holodepth

Holodepth • ByteOmi Köprü • Yaşam Döngüsü

Render Loop

Render loop, 3D uygulamanın kalp atışıdır: her karede önce dünya güncellenir, sonra ekrana çizilir. Bu ritim doğru kurulmazsa stutter, gereksiz CPU yükü ve tutarsız hareketler kaçınılmaz olur. Bu sayfada loop sezgisini, update→render ayrımını, rAF zamanlamasını ve delta time ile gerçek‑zaman tutarlılığını kuruyoruz.

Kaynak metin: byteomi.com • İlgili köprüler: GPU vs CPU • Sonraki köprü: requestAnimationFrame (ekran ritmini takip etmek)

Canlı demo: “Heartbeat & Delta Runner”

İki koşucu aynı pisti koşuyor. Soldaki “frame-based” koşucu gerçek zamandan kopuk bir şekilde “her tick sabit adım” modelini kullanır; FPS düşünce gerçek sürede yavaşlar ve bazen geriye bile kayabilir. Sağdaki “delta-based” koşucu ise \( \Delta t \) ile hızını gerçek saniyeye bağlar; FPS düşse bile aynı gerçek sürede bitiş çizgisini geçmeye yakın kalır.

Hero demo

Update → Render ritmi, rAF vs setInterval sezgisi ve \( \Delta t \) ile FPS bağımsızlığı tek sahnede.

Metronom (1 Hz): T — Son tur (gerçek süre): Frame s • Delta s Ortalama (son 3 tur): Frame s • Delta s
FPS: Frame: ms Δt: s Update: ms • Render: ms

İpucu: Sabotage açıldığında ana iş parçacığında yapay bir “ağır iş” oluşur ve FPS düşer. Soldaki “yanlış model” gerçek sürede geriye düşer; sağdaki doğru model ise clamp ile ani sıçramayı kontrol eder.

Loop nedir? Film şeridi sezgisi

3D sahneler birer optik illüzyondur: durağan kareleri çok hızlı ardı ardına gösterdiğinde beyin bunu “hareket” olarak algılar. 60 FPS hedefi, saniyede yaklaşık 60 kez yeni bir kare üretebilmek demektir. Bu yüzden 3D uygulama “yüklenip bekleyen” bir sayfa değildir; kendi kendini tekrar eden canlı bir sistemdir.

Bu ritmin kuralı kare bütçesidir: 60 FPS hedefinde bir kare için yaklaşık \(16.7ms\) zamanın vardır. Bu pencere içinde mantık (update), çizim (render) ve tarayıcının kendi işleri aynı bütçeyi paylaşır. Tek bir karede uzun süren bir iş oluşursa, kullanıcı bunu “takılma” (stutter) olarak hisseder.

İdeal akış: update → render

Her tick’te iki iş vardır. Update aşamasında “bu karede ne değişti?” sorusunu cevaplarsın (konumlar, input, animasyon zamanı, mantık). Render aşamasında ise güncel state’e göre sahneyi ekrana basarsın. Sezgi: update “dünyayı düzeltir”, render “dünyayı gösterir”.

İyi bir loop’ta update tahmin edilebilir ve kısa tutulur. Aynı kare bütçesinde 5ms→25ms zıplamak, kullanıcıya stutter olarak döner. Büyük işler ya parçalara bölünür ya da kritik yolun dışına alınır.

Zamanlama: neden setInterval yetmez?

setInterval ekranın tazeleme ritminden bağımsız çalışır: ekran hazır değilken çizimi zorlamak gereksiz CPU/GPU baskısı üretir. Ayrıca pratikte “drift” oluşur; aralıklar kayar, kareler düzensiz gelir ve akıcılık bozulur.

Çözüm: requestAnimationFrame (rAF). Tarayıcıya “ekran yeni kare çizmeye hazır olduğunda beni çağır” dersin; loop VSync ile senkronize olur ve arka planda otomatik yavaşlar/durur.

Delta time: FPS bağımsızlığı

Döngünün en büyük düşmanı değişken FPS’dir. Bu yüzden iki kare arasındaki süreyi \( \Delta t \) ölçer ve hareketi buna göre ölçeklersin:
position += velocity * dt. Böylece FPS kaç olursa olsun, obje gerçek saniyede benzer mesafeyi kateder.

Pratik not: \( \Delta t \) çok büyürse (sekme geri geldi, frame kaçırıldı) hareket “zıplayabilir”. Bu yüzden bazı sistemler \( \Delta t \)’yi üstten sınırlar (clamp) veya fixed-step simülasyon uygular.