Saf JavaScript ve Matematik ile 3D Render Motoru
Günümüzde web projelerinde 3 boyutlu grafikler denildiğinde akla ilk olarak Three.js, Babylon.js veya doğrudan WebGL API gelir. Ancak bu kütüphanelerin arkasında çalışan temel mimari, aslında lise matematiğinden bildiğimiz trigonometri, vektör işlemleri ve perspektif projeksiyon formüllerine dayanır.
Bu yazıda, sıfırdan hiçbir 3D kütüphanesi veya hazır motor kullanmadan, saf JavaScript ve 2D HTML5 Canvas API ile 3 boyutlu bir nesneyi ekrana nasıl yansıttığımızı, döndürdüğümüzü ve animasyon döngüsüne bağladığımızı adım adım inceleyeceğiz.
1. 3D Uzay vs 2D Ekran Pikselleri
Bilgisayar ekranlarımız 2 boyutlu ( ve pikselleri) düzlemlerdir. Bizim amacımız, 3 boyutlu sanal uzayda temsil ettiğimiz koordinatlarına sahip noktaları (), ekranımızdaki piksel karşılıklarına dönüştürmektir.
3D uzaydaki bir noktanın bileşenleri:
- Ekseni: Yatay eksen (Sağ / Sol)
- Ekseni: Dikey eksen (Yukarı / Aşağı)
- Ekseni: Derinlik ekseni (Ekrana yakınlaşma / Uzaklaşma)
2. Perspektif Projeksiyon Matematiği
Gözümüz veya fotoğraf makinesi lensleri nesnelerden uzaklaştıkça nesneleri daha küçük görür. Matematiksel olarak derinlik arttıkça () koordinat değerlerini bu değerine bölerek küçültürüz.
Buna Perspektif Projeksiyon (Perspective Projection) denir.
Formül:
JavaScript fonksiyon karşılığı:
javascript/** * 3D noktayı 2D düzleme projeksiyonlar (Perspective projection). */ function project3DTo2D({ x, y, z }) { // Sıfıra bölünme hatasını (Division by zero) önlemek için z = 0 kontrolü const viewDistance = z === 0 ? 0.1 : z; return { x: x / viewDistance, y: y / viewDistance, }; }
3. Trigonometrik Matris Dönüşümleri (Rotation)
Nesnemizin kendi etrafında dönmesi için noktaların düzlemindeki açılarını trigonometrik Sinüs () ve Kosinüs () fonksiyonları ile güncelleriz.
Y ekseni etrafındaki dönme dönüşüm matrisi formülü:
JavaScript kod karşılığı:
javascript/** * Noktayı Y ekseni etrafında theta açısı kadar döndürür (XZ düzlemi). */ function rotateY({ x, y, z }, angle) { const cos = Math.cos(angle); const sin = Math.sin(angle); return { x: x * cos - z * sin, y: y, z: x * sin + z * cos, }; }
4. Normalize Koordinatları Piksel Düzlemine Çevirme
Projeksiyon sonucunda elde ettiğimiz aralığındaki matematiksel koordinatları, Canvas ekranındaki piksel boyutlarına () ölçekleriz:
javascript/** * Normalleştirilmiş koordinatları (-1 ile +1) ekran piksellerine çevirir. */ function convertToScreenCoords(p) { return { x: (p.x + 1) / 2 * canvas.width, y: (1 - (p.y + 1) / 2) * canvas.height, }; }
5. Tam Motor Algoritması ve Canvas Çizim Döngüsü
Şimdi tüm bu matematiksel adımları (Döndürme Derinlik Öteleme Projeksiyon Ekran Dönüşümü) birleştirerek 60 FPS akıcılıkta 3D döner küp oluşturan ana kod bloğuna bakalım:
javascriptconst BACKGROUND_COLOR = "#101010"; const FOREGROUND_COLOR = "#7050ff"; const CANVAS_SIZE = 800; // Canvas Kurulumu const canvas = document.getElementById("cube"); canvas.width = CANVAS_SIZE; canvas.height = CANVAS_SIZE; const ctx = canvas.getContext("2d"); /** * Ekranı her karede temizler. */ function clearCanvas() { ctx.fillStyle = BACKGROUND_COLOR; ctx.fillRect(0, 0, canvas.width, canvas.height); } /** * İki 2D nokta arasında çizgi çizer. */ function drawLine(p1, p2) { ctx.lineWidth = 3; ctx.strokeStyle = FOREGROUND_COLOR; ctx.beginPath(); ctx.moveTo(p1.x, p1.y); ctx.lineTo(p2.x, p2.y); ctx.stroke(); } /** * Derinlik (Z) ötelemesi yapar. */ function translateZ({ x, y, z }, distance) { return { x, y, z: z + distance }; } // Durum Değişkenleri let cameraZOffset = 1; let rotationAngle = 0; let lastTime = 0; /** * Ana Animasyon Döngüsü (RequestAnimationFrame) */ function animate(currentTime) { // Kare hızından bağımsız akıcı animasyon için Delta Time hesabı const deltaTime = (currentTime - lastTime) / 1000; lastTime = currentTime; if (!isNaN(deltaTime)) { rotationAngle += Math.PI * deltaTime; // Saniyede 180 derece dönme } clearCanvas(); // Küpün Kenarlarını (Edges) ve Noktalarını (Vertices) İşle for (const edge of CUBE_EDGES) { for (let i = 0; i < edge.length; ++i) { const startVertex = CUBE_VERTICES[edge[i]]; const endVertex = CUBE_VERTICES[edge[(i + 1) % edge.length]]; // Matematiksel Boru Hattı (Pipeline) const processPoint = (v) => { const rotated = rotateY(v, rotationAngle); const translated = translateZ(rotated, cameraZOffset); const projected = project3DTo2D(translated); return convertToScreenCoords(projected); }; drawLine(processPoint(startVertex), processPoint(endVertex)); } } requestAnimationFrame(animate); } // Animasyonu Başlat requestAnimationFrame(animate);
6. Projenin GitHub Reposu ve Canlı İnceleme
Bu projenin tam kaynak kodlarına, HTML5 test ortamına ve detaylı dosya yapısına GitHub üzerindeki github.com/lulushuz/MathTo3D-JS açık kaynak projemden erişebilirsiniz.
Grafik kütüphanelerinin arkasındaki matematiksel mantığı öğrenmek, performans odaklı render motorları geliştirmek ve WebGL dünyasına sağlam bir giriş yapmak için harika bir temel projedir.
Keyifli kodlamalar! 🚀
