Bir haftada C ile yapılan 3D modelleyici
(danielchasehooper.com)- Wheel Reinvention Jam’in “mevcut yazılımlara yeni bir gözle yeniden bakma” göreviyle ShapeUp ortaya çıktı ve tarayıcıda çalışan demo ile
.objdışa aktarma destekli bir 3D modelleyiciye dönüştü - Bir hafta içinde 3D araç yapılabilmesinin anahtarı ray marched SDF oldu; üçgen tabanlı bir rendererdan daha hızlı şekilde renk, yumuşak gölge ve ambient occlusion içeren sahneler uygulanabildi
- Uygulama tek C dosyası merkezli olacak şekilde basit tutuldu ve model en fazla 100 Shape’i statik dizide tutarak bellek yönetimi yükünü azalttı
- raylib, OpenGL penceresini hızlıca açmada yardımcı oldu ancak
intağırlıklı API’si, parametre doğrulaması eksikliği, GLFW bağımlılığı ve raygui sınırlamaları nedeniyle bazı yerlerde doğrudan OpenGL kullanmak ya da özellikleri baştan yazmak gerekti - Nihai çıktı 2024 satır C ve 250 satır GLSL, toplamda yaklaşık 2300 satırlık bir projeydi; dosya açma-kaydetme, çoklu platform çalıştırma ve
.objdışa aktarmayı destekliyordu
ShapeUp’ın 3D modelleyiciye dönüşme süreci
- Wheel Reinvention Jam, mevcut yazılım sistemlerine yeni bir bakışla tekrar yaklaşmayı amaçlayan bir haftalık bir programlama etkinliğiydi
- İlk hedef, yavaş TypeScript derleyicilerinden duyulan rahatsızlıktan yola çıkarak
tsc’den daha hızlı çalışan bir TypeScript alt kümesi yapmaktıesbuildya daBunun TypeScript parser’ı başlangıç noktası olarak kullanılabilir görünüyordu- Ancak başarılı demo en fazla “bir terminal komutu diğerinden daha hızlı bitiyor” seviyesinde kalıyordu ve görsel olarak çekici değildi; bu yüzden yön 3D’ye çevrildi
- ShapeUp, şekilleri fareyle düzenlemeye odaklanan bir 3D modelleyici olarak geliştirildi
- Daha önce SDF shader yazma deneyimi vardı ama modeli doğrudan kod değiştirerek oluşturma yaklaşımı doğal gelmiyordu
- Amaç, SDF tabanlı şekil düzenlemeyi fareyle yapılabilir hale getirmekti
SDF’nin bir haftalık projeyi mümkün kılma nedeni
- ShapeUp’ın render altyapısı ray marched signed distance fields (SDFs) üzerine kurulu
- SDF sahneleri, renk, yumuşak gölge ve ambient occlusion içerse bile üçgen tabanlı bir rendererdan daha hızlı uygulanabildi
- Inigo Quilez’in SDF ile oturup tek seferde Pixar tarzı bir karakter oluşturduğu örnek, teknik yön için önemli bir referans oldu
- ShapeUp, kod düzenlemek yerine şekilleri doğrudan manipüle ederek SDF modellemeyi ele alıyor
C uygulaması ve veri yapısı
- ShapeUp C ile yazıldı ve OpenGL penceresi oluşturmak için raylib kullanıldı
- C’nin seçilme nedenleri hızlı derleme, karmaşık davranışları gizlemeyen sözdizimi, aşinalık ve hem native hem de WebAssembly’ye derlenebilmesiydi
- Model,
Shapestruct’larının bir koleksiyonundan oluşuyor- Her Shape; konum, boyut, açı, köşe yarıçapı, blob seviyesi, renk, aynalama ekseni ve subtract bilgisi taşıyor
- Shape listesi dinamik tahsis yerine statik diziyle yönetildi
MAX_SHAPE_COUNTdeğeri 100- Durum,
Shape shapes[MAX_SHAPE_COUNT],shape_countveselected_shapeile tutuluyor - Bu yaklaşım, tahsis başarısızlığı ve sızıntı ihtimalini ortadan kaldırıyor
- 100 Shape sınırı pratik kullanımda büyük bir sorun olmadı
- Renderer optimizasyonuna zaman kalmadığı için 100 Shape’e ulaşmadan önce kare hızı düşüyordu
- Zaman olsaydı model küçük tuğla benzeri bölümlere ayrılacak ve ray marching her bölüm içinde yapılacaktı
Bellek kullanım yöntemi
- ShapeUp dinamik bellek tahsisini yalnızca 3 yerde kullanıyor
- Kaydetme: tüm belgeyi tutacak bir buffer tahsisi
.OBJdışa aktarma: tüm vertex’leri tutacak bir buffer tahsisi- GLSL shader üretimi: shader source için buffer tahsisi
- Her durumda fonksiyonun sonunda yalnızca bir kez
freeçağrılıyor - Her Shape’i ayrı ayrı
mallocile ayırıp pointer’ları dinamik bir dizide tutmak da mümkündü ama bu proje için böyle bir yapıya gerek yoktu - C, bellek yerleşimini doğrudan kontrol edebilme açısından avantaj sağladı
- Dinamik dizi ya da hash map gerekseydi
stb_ds.hgibi araçlar kullanılabilirdi
UI’nin uygulanma biçimi
- UI, immediate mode user interface (IMGUI) yaklaşımıyla geliştirildi
- IMGUI; debug etmenin kolay olması ve CSS, constraints ya da SwiftUI yerine öğe konumlarını gerçek programlama diliyle belirlemeye izin vermesi açısından avantajlıydı
- Odaktaki öğe ve fare davranışları
Controlenum’u ile takip edildi- Konum, boyut, açı, renk, taşıma, döndürme, ölçekleme, kamera döndürme ve blob seviyesi gibi etkileşim durumları enum değerleriyle ifade edildi
focused_controlvemouse_action, mevcut UI durumunu tutuyor
raylib ve raygui ile tıkanılan noktalar
- raylib, OpenGL penceresini hızlı açmakta yararlıydı ama zaman geçtikçe geliştirme hızını düşüren bir unsura dönüştü
- raylib API’sinde özellikle rahatsız edici olan nokta tip bilgisinin yetersizliğiydi
- enum beklenen yerlerde bile
intkullanıldığı için derleyicinin tip kontrolünden yararlanılamıyordu - Yalnızca fonksiyon imzasından parametrelerin anlamı net anlaşılmıyordu
- Örneğin
IsGestureDetected(unsigned int gesture)içindekigesture, kayıtlı bir gesture ID’si gibi görünse de gerçekteGestureenum’uydu - Dokümantasyon header dosyaları merkezli olduğundan hangi
intdeğerinin aslında enum olduğunu anlamak için implementasyona bakmak gerekiyordu
- enum beklenen yerlerde bile
- Temel parametre doğrulamasının yapılmaması da sorunu büyüttü
LoadFileData(const char *fileName, int * dataSize)içindedataSizeNULLolursa segfault oluşuyor- Header,
dataSizeparametresinin output parametresi olduğunu ya da null olamayacağını belirtmiyor - Doğrulama eksikliği, basit sorunların izini sürmeyi zorlaştırdı ve bazı durumlarda sessizce hatalı davranışlara yol açabildi
- Bağımlılık yönetiminde de beklentiyle gerçeklik farklıydı
- GLFW tarafındaki sorunlarda raylib’nin workaround üretmemesi ya da patch göndermemesi problem oldu
- Son kullanıcı için pencerenin içeride nasıl oluşturulduğundan çok raylib özelliğinin düzgün çalışıp çalışmadığı önemliydi
- raygui UI kütüphanesi projede kullanılmak için fazla kısıtlı kaldı
- Kayan noktalı sayı gösteremediği için float metin alanlarını elle yapmak gerekti
- Üst üste binen ya da kırpılmış öğelerde fare olayı yönlendirmesini işleyemiyordu
- UI’de yaygın olan yuvarlatılmış köşeleri desteklemiyordu
- Güzel görünecek şekilde stillendirmek zordu
- Hatalar da geliştirme akışını bozdu
- raygui araç hatası yüzünden aşırı stillendirilmiş varsayılan font değiştirilemedi
DrawCircle(...)gibi çizim fonksiyonları üçgenler arasında vertex paylaşmadığı için mevcut matriste ölçekleme ya da döndürme varsa kayan nokta hataları nedeniyle pikseller arasında boşluklar oluşuyordu
- Bulunan sorunlar bir süre raporlandı ama çoğu “wont fix” olarak kapatıldı; sonrasında raporlamaya son verildi
- Çözüm olarak ya doğrudan OpenGL fonksiyonları kullanıldı ya da gereken özellikler sıfırdan yazıldı
- İleride raylib yerine sokol kullanılması planlanıyor
6 günde tamamlanması gereken dört şey
- ShapeUp’ın 6 gün içinde büyük ölçüde dört bölümü tamamlaması gerekiyordu
- Kullanıcı arayüzü: 3D gizmo’lar, klavye kısayolları, kenar çubuğu, oyun kumandası desteği
- GLSL shader üreticisi ve ray marching renderer
- GPU tabanlı fareyle seçim
- Dışa aktarma için marching cubes
- Zorluk, her bir özelliğin kendisinden çok öncelikleri korumakta yatıyordu
- Zor ya da çok zaman alan problemler ya tasarım değiştirilerek aşıldı ya da vakaların %90’ında çalışan basit çözümlerle geçildi
- Bazı özelliklerde çözüm, bir gün bekletildikten sonra akla geldi
- Çalışma yöntemi, her zaman çalışan bir 3D modelleyiciyi koruyup zamanı oldukça aşamalı iyileştirmeler yapmaktı
- Bu yaklaşım, sadece en sonunda piramit haline gelen bir yapı kurmak yerine, hangi aşamada durulursa durulsun tam bir küçük piramit elde etmeye benzetiliyor
Nihai sonuç
- Haftanın sonunda ShapeUp, anlamlı 3D modeller oluşturabiliyor ve bunları
.objdosyası olarak dışa aktarabiliyordu - Birden fazla platformda çalışıyor, dosya açma ve kaydetmeyi de destekliyordu
- Kod boyutu 2024 satır C ve 250 satır GLSL idi
- Yaklaşık 2300 satırla belli ölçüde kullanışlı bir 3D modelleyici yapılabilmiş olması dikkat çekici bir sonuçtu
- Projenin kendisi görece basit olsa da ne yapılacağını seçme sezgisi, bunu yapabilecek bilgi birikimi ve bir hafta içinde bitirme disiplini belirleyici oldu
1 yorum
Hacker News yorumları
Raylib’in sınırlamaları konusunda yazarla tamamen aynı fikirdeyim. Şu anda Raylib ile başlamış bir tower defense tarzı oyun yapıyorum ve aynı sınırlamalarla birlikte başka pek çok sorun yaşıyorum.
Örneğin platformlar arasında tam ekran geçişi tutarlı çalışmıyor, ekran modları listelenemiyor, çalışma sırasında render özelliklerini açıp kapatmak zor oluyor ya da derlenmiş shader’ları kaydetme gibi sorunlar var.
Yine de Ray’in bu kütüphaneye verdiği emek için minnettarım ve desteklemeye devam etmeyi düşünüyorum. Raylib hızlı prototip üretmek için harika, ama ciddi kısıtları kabul etmeden onun ötesine geçmek kolay değil.
Kesinlikle çok şey öğrendim, ama bu saatten sonra Raylib ile ilgili tüm kodu SDL gibi bir şeyle baştan değiştirmek için geliştirme fazla ilerledi
Things Unexpectedly Named After People: https://notes.rolandcrosby.com/posts/unexpectedly-eponymous/
Bugünlerde strateji sadece kenarlıksız pencere modunu kullanmak ve gerçek tam ekran yokmuş gibi davranmak
Şu anda en büyük sorun font işleme ve metin render etme. TTF fontlar yerine önceden hazırlanmış bitmap fontlara geçmem gerekecek gibi görünüyor, ama bu da ileride yerelleştirme yaparken epey acı verici olabilir.
Love2D’den geçtikten sonra en çok eksikliğini hissettiğim iki özellik, çok renkli metni kolayca render etmek ve texture’ları kolayca kesip tekrar ettirmek ya da tile etmek. Raylib’de renk işaretleme temelli olarak metni bizzat parçalara ayırıp, genişlik ofsetleri uygulayıp, satır sonlarını da hesaba katarak her parça için çizim fonksiyonunu çağırmak gerekiyor.
Ekrana çok fazla metin çizince FPS’in de ciddi biçimde düştüğünü düşünüyorum; belki de metin çizim çağrılarının batching’i bozuluyordur. Eskiden tile texture çizme fonksiyonu vardı ama nedense kaldırıldı
Wasm ve tarayıcı 3D/2D grafiklerinde beni hep rahatsız eden şey, scroll gibi küçük sorunların sık sık görünmesi. Buradaki “Background scrolling & parallax” örneğine bakın: https://www.raylib.com/examples.html
Birden fazla cihazda test ettim ve gözlerim bozuk değilse bunun kesinlikle akıcı kaydırma olmadığını söyleyebilirim. 2024 yılında 2D akıcı kaydırmanın hâlâ çözülmemiş bir sorun olması akıl alır gibi değil
“Shape’leri statik olarak ayrılmış bir dizide tutuyorum. Ne allocation failure var ne memory leak ne de gereksiz karmaşa. Bayıldım. 100 shape sınırı pratikte bir kısıt değildi. Renderer’ı optimize etmeye neredeyse hiç zaman ayırmadığım için 100’e ulaşmadan önce zaten frame rate düşecekti.”
Son zamanlarda gördüğüm erken optimizasyondan kaçınma konusundaki en iyi örnek
Gerçekten ilgi çekici bir yazı; bellek yönetimi biçimi ve raylib’de karşılaştığı sorunlar gibi çeşitli kararları anlatması hoşuma gitti. Tam da Crafting Interpreters’ın 2. bölümüne girip C’yi yeniden gözden geçiriyorken, C’nin iyi olduğu şeyleri yeniden hatırlatması güzel oldu
Videodaki canlı demo gerçekten çok iyi. Uygulamayı yapmak bir yana, ben deneseydim o videoyu bile bir hafta içinde hazırlayamazdım gibi geliyor
Uzun zaman önce masaüstü telefonlar için bir işletim sistemi üzerinde çalışmıştım. Yalnızca 64K RAM vardı, bu yüzden hiç dinamik bellek yönetimi yoktu; derleyicinin her şeyi derleme zamanında yerleştirmesi için bolca statik değişken kullanıyorduk.
Pek çok uygulamanın aslında hiç dinamik bellek yönetimine ihtiyaç duymayabileceğini unutmak kolay. Birkaç sabit boyutlu buffer ayırıp, o buffer dolduğunda oluşacak istisnai durumu temizce ele almak çoğu durumda yeterli olabilir.
Bu bağlamda C aslında çok daha güvenli. Memory leak yok; dert edilmesi gereken tek şey buffer overflow. Tüm değişkenler statik olarak ayrılmışsa bunu
sizeofkullanımına dikkat ederek yönetmek mümkün.Bu, günümüzde Rust ve Go’nun harika seçenekler olmadığı anlamına gelmiyor; ama mütevazı eski C hâlâ gayet işe yarıyor ve kâbus gibi karmaşık olmak zorunda değil
Konudan biraz sapıyor ama, metni bulanık görünmeyen ilk WebAssembly arayüzünü gördüğüme sevindim. Gerçekten ilk kez görüyorum.
Bunu programlar ve bazı işletim sistemleri, örneğin Windows ölçeğine kadar genişletirsek, son birkaç yılda metin rasterizasyonu yönteminin ortak moda ve varsayılan ayar hâline gelmesiyle genel bir sorun oluştu.
Ne yazık ki kullanıcı çoğu zaman net metin elde etmek için anti-aliasing’i kapatamıyor; nadiren böyle bir seçenek olsa bile menüler gibi arayüzlerde anti-aliasing yine uygulanıyor
Bu tür projeleri gerçekten seviyorum. C’nin düşük seviye doğası hâlâ hoşuma gidiyor. Şu sıralar daha çok Rust ve Elixir/Erlang kullanıyorum ama C’nin sadeliğini ve açıklığını sık sık özlüyorum.
Bu yüzden Zig de epey kullanıyorum; C’nin felsefesini büyük ölçüde korurken onu oldukça iyi geliştiren bir dil
C hakkındaki değerlendirmesine gerçekten katılıyorum. Özellikle “karmaşık davranışları sözdiziminin arkasına gizlemiyor. Sürekli dönüp bakmayı gerektirmeyecek kadar basit” kısmı çok doğru ve ayrıca C hakkında bir şeyi araştırmanız gerektiğinde de bu genelde çok kolay ve faydalı oluyor
Basit ve eski bir dil olmanın kendine özgü avantajları var
Her
Shapeiçin ayrı ayrımallocçağırıp bu işaretçileri dinamik bir dizide tutarsanız, işi kesinlikle kendi kendinize daha zor hale getirebilirsiniz. C# gibi bir dilin bu tür bir tahsis yapısını zorunlu kıldığı yönünde bir ifade var ama yazarın C'de yaptığı gibi sabit bir struct dizisi kullanmayı C#'ta neyin engellediğini merak ediyorumKeşke biri bu projeyi sürdürse. Birkaç ay daha cilalanırsa belirli kullanım alanlarında Blender veya FreeCAD'e ciddi bir alternatif olabilir ve öğrenme eğrisi de çok daha yumuşak görünüyor
Ancak temelde SDF ile çalıştığı için, üçgenler ve köşeler kullanan geleneksel mesh'lerden hem modelleme deneyimi hem de saklanan veri açısından farklı
SDF'yi mesh'e dönüştürmek marching cubes gibi yöntemlerle mümkün ama ortaya çıkan veriyi sonrasında Blender benzeri uygulamalarda büyük olasılıkla yine temizlemek gerekir
Eğer render motoru da SDF tabanlıysa, SDF harikadır. Ama çoğu öyle değil
Bunu zaten biliyorsanız kusura bakmayın