Koordinat Sistemleri
3D web’e geçerken en büyük zihinsel kırılma “piksel” değil, koordinat
alışkanlığıdır.
DOM dünyasında (0,0) sol üsttedir; 3D dünyada merkezde bir
(0,0,0) vardır ve artık Z diye bir derinlik eksenin
olur.
Bu sayfada ekran koordinatlarından dünya koordinatlarına geçişi, right-hand rule,
hiyerarşik dönüşümler ve NDC üzerinden netleştireceğiz.
Kökenlerin Savaşı Ekran Koordinatları vs. Dünya Koordinatları
HTML/CSS dünyasında koordinat sistemi, çoğunlukla ekranın sol üst köşesini başlangıç kabul eder: \((0,0)\) yukarıdadır, X sağa giderken Y aşağı doğru artar.
Bu model “doküman akışı” için idealdir: metin yukarıdan aşağıya akar, layout soldan sağa yerleşir. Birimi de nettir: piksel (veya piksele bağlanan CSS birimleri).
3D Dünyası: Merkez ve Derinlik ile BaşlarWebGL/Three.js tarafında ise “ekranın sol üstü” gibi bir başlangıç hissi yoktur. Sen bir uzay tanımlarsın ve bu uzayın merkezinde \((0,0,0)\) durursun. Üstelik artık sadece \(x\) ve \(y\) değil, bir de Z vardır: derinlik.
Buradaki birimler “birimsiz” gibi görünür; çünkü 1 birim senin kurguna göre 1 metre de olabilir, 1 kilometre de.
Bu iki dünya arasındaki ilk “şaşırtıcı” fark, Y ekseninin yönüdür: 2D ekranda aşağı doğru artan Y, 3D uzayda genellikle yukarı doğru artar. Bu yüzden 2D’de “aşağı indir” refleksi, 3D’de “eksi Y” gibi davranabilir; hata ayıklarken en sık düşülen tuzaklardan biri budur.
İkinci büyük fark, kameranın devreye girmesidir: 2D’de ekran “mutlak” bir referans gibi görünür; 3D’de ise gördüğün her şey,
kameranın bakışına göre anlam kazanır. Yani world space’de doğru yerde duran bir obje, kamerayı oynattığında ekranda tamamen farklı bir noktaya düşer — bu normaldir.
Pratik Ölçek: “1 Birim Ne?” Sorusu3D sahnede “1 birim”i erken tanımlamak, her şeyin stabil görünmesini sağlar. Örneğin 1 birimi “1 metre” gibi düşünürsen; kamera yakın/uzak düzlemleri (near/far), hızlar, ışık menzilleri ve gölge ayarları daha tutarlı olur.
Mini kontrol: Bir şey “kayıp” görünüyorsa çoğu zaman sebep koordinat değil, ölçek ve kamera clip ayarlarıdır: obje çok küçük/çok büyük olabilir ya da near/far dışında kalmış olabilir.
Ekran → Dünya köprüsü: Web’de etkileşim kurarken genelde elindeki veri clientX/clientY gibi ekran koordinatlarıdır. 3D’de bu değerler tek başına “dünya konumu” değildir; çoğu zaman bu noktayı NDC’ye çevirip kameradan bir ışın (ray) atarak sahnede “hangi yönde” olduğunu bulursun. Bu mantık, ileride NDC bölümünde tam yerine oturacak.
İçerik Odağı: 3D’de “ekranın neresinde?” sorusu tek başına yetmez; aynı zamanda “kameraya göre ne kadar uzakta?” sorusu da gelir. Bu yüzden 2D alışkanlığıyla 3D hata ayıklamak, genellikle eksen ve ölçek şaşırmalarına yol açar.
|
Özellik
|
DOM / 2D Screen
|
WebGL / 3D World
|
|---|---|---|
|
Köken
|
\((0,0)\) sol üst
|
\((0,0,0)\) merkez
|
|
Y yönü
|
Aşağı = artı
|
Yukarı = artı (up-axis)
|
|
Birim
|
px tabanlı
|
kurguya bağlı (unit)
|
|
Yeni eksen
|
Yok (2D)
|
Z = derinlik
|
Üçüncü Boyut Z Ekseni ve Derinlik Algısı
3D uzayda yönü doğru kurmanın en pratik yolu sağ el kuralıdır. Three.js bu düzeni kullanır: baş parmak X (sağ), işaret parmağı Y (yukarı), orta parmak Z (ekrana doğru/ekrandan dışarı) gibi düşünülür.
Bu model, “Z artınca yaklaşır mı, uzaklaşır mı?” gibi temel soruları hızlı çözer. Kameranın baktığı yön, near/far düzlemleri ve derinlik testi; hepsi bu yön hissinin üstüne oturur.
Derinlik: Piksel Değil, Hacim2D’de “yakın-uzak” genelde çizim sırasıdır; 3D’de ise derinlik, sahnenin fiziksel bir parametresidir. Bir objenin z değerini değiştirdiğinde sadece ekrandaki yeri değil, kameraya göre mesafesi de değişir.
Bu, düşünce modelini değiştirir: artık “pikselleri boyamak” yerine, vektörlerle ve dönüşüm matrisleriyle bir hacmi yönetmeye başlarsın. Sonrasında bu hacim, projeksiyonla ekrana “indirgenir”.
Kamera burada bir “göz” değil, bir görünürlük hacmi tanımlar: near ve far düzlemleri, hangi derinlik aralığının çizileceğini belirler. Bu aralığın dışında kalan nesneler sahnede var olsa bile, ekrana hiç düşmeyebilir.
Mini debug: “Objem yok oldu” hissinde önce şunu kontrol et: objenin Z mesafesi near/far aralığında mı, yoksa kameranın arkasında mı kaldı? Bu kontrol çoğu zaman koordinat hatasını saniyeler içinde ortaya çıkarır.
Depth Precision: Near/Far Büyüdükçe Hassasiyet DüşerDerinlik tamponu sınırsız hassasiyete sahip değildir. Near/far aralığını gereksiz büyüttüğünde, derinlik değerleri aynı aralığa “sıkışır” ve iki yüzey çok yakınsa GPU hangisinin önde olduğuna kararsız kalabilir. Bunun tipik sonucu z-fighting (yüzeylerin titremesi) olur.
Pratik kural: Near’ı mümkün olduğunca büyük, far’ı da mümkün olduğunca küçük tutmak (sahnenin ihtiyacına göre), hem derinlik stabilitesini hem de sahne algısını iyileştirir.
Uzay Hiyerarşisi Local vs. World (Parent / Child)
Three.js’de bir nesnenin konumu “mutlak” değildir; çoğu zaman bir ebeveynin (parent) uzayında anlam kazanır. Bir karakterin elinin gövdeye göre konumu local space örneğidir: el, kendi başına değil; gövdenin referansına göre “doğru” yerde durur.
Sahnenin tamamına göre konum ise world space olur. Parent’ı hareket ettirdiğinde child, kendi local koordinatlarını korur; fakat dünyadaki yeri değişir. Bu, sahne grafiği (scene graph) mantığının temelidir.
Bu noktada zihnine şu resmi koy: world space bir “mutlak koordinat” gibi görünse de, çoğu nesne için bu mutlaklık toplam dönüşüm sonucudur. Yani child’ın world konumu, parent zincirindeki tüm dönüşümlerin (position/rotation/scale) birikimidir.
Kısa örnek sezgisi: “Karakterin eli” local space’de gövdeye göre sabit kalır; karakter yürüdüğünde elin world space’deki yolu değişir ama local offset değişmez. Bu yüzden rig/animasyon gibi sistemler, local space fikri olmadan tasarlanamaz.
Derin not: Bu hiyerarşi, matris çarpımı ile taşınır: parent’ın dünya matrisi ile child’ın local matrisi çarpılarak world matrisi bulunur. Pratikte bu akış; Core & Script Base sayfasındaki “scene bootstrap” fikrini tamamlar: sahne bir “liste” değil, bir ağaç yapısıdır.
Pratikte Three.js, her objenin local dönüşümünü bir matrise çevirir ve bunu parent zinciriyle çarpar. Bu yüzden bir objenin “gerçek” konumunu merak ettiğinde, local değerleri okumak yetmez; çoğu zaman world matrix üzerinden bakmak gerekir.
Pratik kullanım: Ekranda bir UI/etiket yerleştireceksen veya bir objeyi başka bir objeye “kilitleyeceksen”, genelde world uzayında ölçüp sonra gerekirse tekrar local uzaya dönersin. Yani akış çoğu zaman local → world → local şeklinde gider.
Performans sezgisi de buradan gelir: her karede tüm ağacı gereksiz güncellemek yerine, değişen dalları güncellemek daha doğru olur. Bu bakış, CPU tarafındaki update yükünü inceltir ve GPU’ya daha stabil komut akışı üretir.
Bu sebeple “scene graph” sadece düzen değil; performans stratejisidir: doğru hiyerarşi kurduğunda, bir parent’ı hareket ettirmek child’ların davranışını otomatik taşır ve gereksiz hesapları azaltır. Yanlış hiyerarşi ise her karede aynı dönüşümü tekrar tekrar hesaplatır.
NDC Normalized Device Coordinates
Three.js’de bir objeyi \((5, 2, -10)\) gibi bir dünya koordinatına koysan da, GPU tarafında işlenen nihai uzay çoğu zaman NDC aralığıdır: ekranın solu \(-1\), sağı \(+1\); altı \(-1\), üstü \(+1\) gibi.
Bu dönüşümü yapan şey projection matrixtir: senin dünya birimlerini, ekrana sığan bu dar aralığa “mercek” gibi dönüştürür. Perspektif hissi, FOV ve near/far düzlemleri bu merceğin parametreleridir.
NDC’nin \([-1, +1]\) olması rastgele değildir: GPU, “ekrana sığan” alanı bu normalize aralıkta tanımlar. World → view → clip dönüşümünden sonra gelen perspective divide adımı (kabaca \(x/w, y/w, z/w\)), koordinatları bu normalize aralığa yerleştirir. Böylece ekran boyutu değişse bile, GPU aynı dilde çalışır.
Mini sezgi: “Projection matrix”i, dünya birimlerini ekrana sığdıran bir mercek gibi düşün; ama asıl kritik iş, bu merceğin çıktısını NDC’ye normalize eden bölme adımıdır. Bu yüzden NDC, 3D sahnenin “ekrana çevrilmiş” halidir.
Mouse Picking Neden Zor? Çünkü Dil DeğişiyorEkranda bir yere tıklamak \(clientX/clientY\) gibi piksel koordinatları verir; ama sahne seçiminde (picking) çoğu zaman NDC’ye geçip oradan 3D uzaya bir ışın (ray) atman gerekir. Yani 2D ekran noktasını, kameranın uzayında 3D bir yöne çevirirsin.
Burada yapılan dönüşümün “ince ayarı” önemlidir: NDC’ye çevirirken genelde X doğrusal gider ama Y ekseni ters çevrilir (çünkü ekran koordinatlarında Y aşağı artar). Ayrıca viewport’un (canvas) sayfa içindeki konumu da hesaba katılmalıdır; aksi halde doğru yerde tıklasan bile ray yanlış yöne gider.
Pratik sezgi: “Ben tıkladım, neden obje seçilmedi?” sorusunun cevabı çoğu zaman geometriden önce koordinat dönüşümlerindedir: screen → NDC → camera space → world space. Bu zinciri doğru kurduğunda, Raycaster gibi araçlar anlam kazanır.
Hızlı kontrol listesi: (1) Mouse koordinatını canvas’a göre mi alıyorsun, yoksa tüm pencereye göre mi? (2) Y eksenini NDC’ye çevirirken flip ediyor musun? (3) Canvas ölçeklendiyse (CSS size ≠ gerçek pixel size), oranı doğru kuruyor musun? Bu üç madde doğruysa, picking problemlerinin büyük kısmı kendiliğinden çözülür.
Uzayı canlı modelle denemek için aşağıdaki Sistem Terminali kartından HoloDepth laboratuvarına geçebilirsin.
2D Pikselden 3D Hacme Geçiş
Ekranın sol üst köşesinden kurtul. Sağ el kuralını canlı bir model üzerinde test et ve Local vs. World koordinatlarının bir hiyerarşide nasıl birbirine bağlandığını sayılarla izle.
Piksel düzlemi ile dünya matrisi arasındaki köprüyü hissedince, bu sayfadaki dönüşüm zinciri daha az soyut kalır.