एक हफ्ते में C में बना 3D मॉडलर
(danielchasehooper.com)- Wheel Reinvention Jam के “मौजूदा software को नए नजरिए से फिर से देखना” वाले challenge से ShapeUp बना, और यह browser में चलने वाले demo तथा
.objexport तक की सुविधा वाला 3D modeler बनकर तैयार हुआ - एक हफ्ते के अंदर 3D tool बना पाने की मुख्य वजह ray marched SDF थी; इससे triangle-based renderer की तुलना में रंग, soft shadows और ambient occlusion वाले scene जल्दी implement किए जा सके
- implementation को C single file के इर्द-गिर्द सरल रखा गया, और model में अधिकतम 100 Shapes को static array में रखकर memory management का बोझ घटाया गया
- raylib ने OpenGL window जल्दी खोलने में मदद की, लेकिन
int-centric API, parameter validation की कमी, GLFW dependency और raygui की सीमाओं के कारण सीधे OpenGL का उपयोग करना या features फिर से बनाना पड़ा - अंतिम output C की 2024 lines और GLSL की 250 lines, कुल लगभग 2300 lines का था, जो file open/save, कई platforms पर execution और
.objexport support करता है
ShapeUp के 3D modeler में बदलने की प्रक्रिया
- Wheel Reinvention Jam मौजूदा software systems को नए नजरिए से फिर से देखने वाला एक हफ्ते का programming event था
- शुरुआती लक्ष्य धीमे TypeScript compiler से नाराजगी से निकला था:
tscसे तेज TypeScript subset बनानाesbuildयाBunके TypeScript parser को शुरुआती बिंदु बनाया जाए तो यह संभव लगता था- लेकिन successful demo बस “एक terminal command दूसरी command से जल्दी खत्म हुई” तक सीमित रहता, जो visually आकर्षक नहीं था; इसलिए अंततः दिशा 3D की ओर मोड़ दी गई
- ShapeUp को mouse से shapes edit करने वाले 3D modeler के रूप में बनाया गया
- पहले SDF shader लिखने का अनुभव था, लेकिन code को सीधे बदलकर modeling करना स्वाभाविक नहीं लगता था
- लक्ष्य था SDF-based shape editing को mouse से संभव बनाना
SDF ने एक हफ्ते के project को कैसे संभव बनाया
- ShapeUp का rendering base ray marched signed distance fields(SDFs) है
- SDF scene में colors, soft shadows और ambient occlusion शामिल होने पर भी इसे triangle-based renderer की तुलना में तेजी से implement किया जा सका
- Inigo Quilez द्वारा SDF से एक ही session में Pixar-style character बनाने का उदाहरण technical direction के लिए reference point बना
- ShapeUp code editing के बजाय shapes को सीधे manipulate करने के तरीके से SDF modeling संभालता है
C implementation और data structure
- ShapeUp C में लिखा गया और OpenGL window बनाने के लिए raylib का उपयोग किया गया
- C चुनने की वजहें थीं तेज compilation, complex behavior को न छिपाने वाला syntax, familiarity, और native व WebAssembly दोनों के लिए compile होने की संभावना
- model
Shapestructs के collection से बना है- हर Shape में position, size, angle, corner radius, blob amount, color, mirroring axis और subtract flag होता है
- Shape list को dynamic allocation के बजाय static array से manage किया गया
MAX_SHAPE_COUNT100 है- state को
Shape shapes[MAX_SHAPE_COUNT],shape_count,selected_shapeसे manage किया गया - इस approach से allocation failure और leaks की संभावना खत्म हो गई
- 100 Shapes की limit actual use में बड़ी समस्या नहीं बनी
- renderer optimization के लिए समय कम था, इसलिए 100 तक पहुंचने से पहले ही framerate पहले गिर जाता था
- समय होता तो model को छोटे brick units में बांटकर हर brick के अंदर ray marching करने की कोशिश की जाती
memory use का तरीका
- ShapeUp dynamic memory allocation का उपयोग सिर्फ 3 जगहों पर करता है
- save: पूरे document को रखने के लिए buffer allocation
.OBJexport: सभी vertices रखने के लिए buffer allocation- GLSL shader generation: shader source के लिए buffer allocation
- हर case में function के अंत में सिर्फ एक बार
freeकिया जाता है - हर Shape को अलग से
mallocकरके pointers को dynamic array में store किया जा सकता था, लेकिन इस project में ऐसी structure की जरूरत नहीं थी - C का फायदा यह था कि memory layout पर सीधा control मिलता है
- अगर dynamic arrays या hashmaps की जरूरत होती, तो
stb_ds.hजैसे tools इस्तेमाल किए जा सकते थे
UI implementation का तरीका
- UI को immediate mode user interface(IMGUI) तरीके से implement किया गया
- IMGUI की खूबियां हैं कि debugging आसान होती है, और CSS, constraints या SwiftUI के बजाय वास्तविक programming language से elements की position तय की जा सकती है
- focused element या mouse action को
Controlenum से track किया गया- position, size, angle, color, move, rotate, scale, camera rotate, blob amount जैसी manipulation states को enum values से represent किया गया
focused_controlऔरmouse_actioncurrent UI state रखते हैं
raylib और raygui में जहां रुकावट आई
- raylib OpenGL window जल्दी खोलने के लिए useful था, लेकिन समय के साथ यह development speed कम करने वाला factor बन गया
- raylib API में सबसे असुविधाजनक हिस्सा type information की कमी था
- जहां enum type expected था, वहां भी
intइस्तेमाल होता है, इसलिए compiler type checking नहीं मिलती - function signature से ही parameters का मतलब साफ नहीं दिखता
- उदाहरण के लिए
IsGestureDetected(unsigned int gesture)मेंgestureregistered gesture ID जैसा लगता है, लेकिन असल में यहGestureenum है - documentation header files पर केंद्रित है, इसलिए कौन-सा
intवास्तव में enum है, यह check करने के लिए implementation देखना पड़ता था
- जहां enum type expected था, वहां भी
- default parameter validation न करने वाली design ने भी problem बढ़ाई
LoadFileData(const char *fileName, int * dataSize)में अगरdataSizeNULLहो तो segfault होता है- header यह नहीं बताता कि
dataSizeoutput parameter है या null नहीं होना चाहिए - validation की कमी से simple issues track करना मुश्किल हुआ, और कुछ मामलों में चुपचाप गलत behavior हो सकता था
- dependency handling में भी expectations से फर्क था
- GLFW के issues को raylib द्वारा workaround करने या patch submit न करने की समस्या थी
- end users को window creation की internal implementation से ज्यादा raylib features का सही काम करना महत्वपूर्ण लगता है
- raygui UI library project में उपयोग के लिए काफी constrained थी
- floating-point numbers दिखा नहीं पाती थी, इसलिए खुद float text field बनाना पड़ा
- overlapping या clipped elements के mouse event routing को handle नहीं कर पाती
- UI में common rounded corners support नहीं करती
- अच्छे दिखने वाले styling करना मुश्किल था
- bugs ने भी development flow में बाधा डाली
- raygui tool bug की वजह से overly stylized default font बदला नहीं जा सका
DrawCircle(...)जैसे drawing functions triangles के बीच vertices share नहीं करते, इसलिए current matrix में scale या rotation होने पर floating-point error से pixel gaps बन जाते थे
- मिले issues को कुछ समय तक report किया गया, लेकिन ज्यादातर “wont fix” के रूप में बंद हो गए, और उसके बाद report करना बंद कर दिया गया
- workaround था सीधे OpenGL functions इस्तेमाल करना या जरूरी features को शुरुआत से implement करना
- आगे raylib के बजाय sokol इस्तेमाल करने की योजना है
6 दिनों में पूरा करने वाली चार चीजें
- ShapeUp को 6 दिनों के अंदर मोटे तौर पर चार हिस्से पूरे करने थे
- user interface: 3D gizmo, keyboard shortcuts, sidebar, game controller
- GLSL shader generator और ray marching renderer
- GPU-based mouse selection
- export के लिए marching cubes
- मुश्किल हर feature से ज्यादा priority बनाए रखने में थी
- कठिन या ज्यादा समय लेने वाली problems को design बदलकर avoid किया गया, या 90% मामलों में काम करने वाले simple solution से handle किया गया
- कुछ features के solutions एक दिन टालकर रखने के दौरान सूझ गए
- काम करने का तरीका यह था कि हमेशा functioning 3D modeler बनाए रखा जाए, और समय जितना allow करे उतना gradually improve किया जाए
- इसकी तुलना ऐसे तरीके से की गई जिसमें सिर्फ पूरा होने के समय ही pyramid न बने, बल्कि किसी भी stage पर रुकने पर भी एक complete छोटा pyramid मिले
अंतिम परिणाम
- हफ्ते के अंत तक ShapeUp meaningful 3D models बना सकता था और उन्हें
.objfiles के रूप में export कर सकता था - यह कई platforms पर चलता है, और file open व save भी support करता है
- code size C की 2024 lines और GLSL की 250 lines है
- लगभग 2300 lines में कुछ हद तक useful 3D modeler बनाया जा सका, यह notable result है
- project खुद relatively simple है, लेकिन क्या बनाना है यह चुनने की समझ, उसे बनाने का ज्ञान, और एक हफ्ते में खत्म करने का discipline अहम रहे
1 टिप्पणियां
Hacker News की राय
Raylib की सीमाओं पर लेखक से मैं पूरी तरह सहमत हूँ। अभी Raylib से शुरू किया हुआ tower defense style game बना रहा हूँ, और वही सीमाएँ व कई दूसरी समस्याएँ झेल रहा हूँ
उदाहरण के लिए, हर platform पर full screen switching लगातार एक जैसा काम नहीं करता, screen modes enumerate नहीं कर सकते, runtime में rendering features toggle करना मुश्किल है, compiled shaders save करने की दिक्कतें हैं, वगैरह
फिर भी Ray ने इस library पर जो काम किया है, उसके लिए आभारी हूँ और आगे भी support करने का इरादा है। Raylib तेज़ी से prototype बनाने के लिए शानदार है, लेकिन भारी constraints स्वीकार न करें तो उससे आगे जाना आसान नहीं
निश्चित रूप से बहुत कुछ सीखा है, लेकिन अब Raylib से जुड़ा सारा code SDL जैसी किसी चीज़ में बदलने के लिए development बहुत आगे बढ़ चुका है
Things Unexpectedly Named After People: https://notes.rolandcrosby.com/posts/unexpectedly-eponymous/
आजकल की strategy बस borderless window mode इस्तेमाल करने और असली full screen के न होने का दिखावा करने की है
अभी सबसे बड़ी समस्या font handling और text rendering है। लगता है TTF fonts की जगह पहले से baked bitmap fonts पर जाना पड़ेगा, लेकिन बाद में localization करते समय यह काफी दर्दनाक होगा
Love2D से आने के बाद जिन दो features की सबसे ज्यादा कमी खली, वे हैं कई रंगों वाला text आसानी से render करना, और textures को आसानी से काटकर repeat या tile करना। Raylib में color markup के आधार पर text को खुद टुकड़ों में बाँटना, width offset लगाना, फिर line wrapping तक ध्यान में रखकर हर टुकड़े के लिए draw function call करना पड़ता है
screen पर बहुत text draw करने पर FPS भी काफी गिरता लगता है, शायद text के draw call batching टूट रही हो। पहले tile texture draw करने का function था, लेकिन किसी वजह से हटा दिया गया
Wasm और browser 3D/2D graphics में जो बात हमेशा खटकती रही है, वह है scroll जैसी छोटी समस्याओं का बार-बार दिखना। यहाँ का “Background scrolling & parallax” example देखें: https://www.raylib.com/examples.html
कई devices पर test किया, और अगर मेरी आँखें ही गड़बड़ नहीं हैं, तो यह साफ तौर पर smooth scrolling नहीं है। 2024 में 2D smooth scrolling अभी भी solved problem नहीं है, यह बात अजीब लगती है
“Shapes को statically allocated array में रखा जाता है। allocation failure नहीं, leak नहीं, फालतू overhead नहीं। प्यारा है। 100 shapes की limit असल में कोई constraint नहीं थी। renderer optimization के लिए लगभग कोई समय नहीं था, इसलिए 100 तक पहुँचने से पहले ही frame rate गिर गया होता।”
हाल में देखे गए premature optimization से बचने के सबसे अच्छे examples में से एक
सच में दिलचस्प लेख है, और memory handling के तरीके और raylib में आई समस्याओं जैसे कई decisions के बारे में बताना अच्छा लगा। संयोग से मैं Crafting Interpreters के भाग 2 में जाते हुए C फिर से revise कर रहा हूँ, इसलिए C किन चीज़ों में अच्छा है यह फिर याद आना अच्छा लगा
video में real-time demo वाकई अच्छा है। app बनाना तो अलग बात है, अगर मैं कोशिश करता तो शायद वह video भी एक हफ्ते में नहीं बना पाता
बहुत पहले desk phone के लिए operating system पर काम किया था। RAM सिर्फ 64K थी, इसलिए dynamic memory management बिल्कुल नहीं था, और static variables बहुत इस्तेमाल किए ताकि compiler compile time पर सब कुछ layout कर दे
यह बात आसानी से भूल जाते हैं कि कई applications में dynamic memory management की जरूरत शायद बिल्कुल न हो। fixed-size buffers के कुछ sets allocate कर दें और उन buffers के भर जाने पर exception cases को साफ-सुथरे तरीके से handle कर लें, तो कई बार इतना काफी होता है
उस context में C वास्तव में काफी ज्यादा safe है। memory leaks नहीं, चिंता सिर्फ buffer overflow की है। अगर सभी variables statically allocated हैं, तो
sizeofको सावधानी से इस्तेमाल करके manage किया जा सकता हैइसका मतलब यह नहीं कि आज Rust और Go अच्छे विकल्प नहीं हैं, लेकिन सादा पुराना C भी अभी तक अच्छा काम करता है और उसे nightmare जितना complex होने की जरूरत नहीं
topic से थोड़ा हटकर, ऐसा WebAssembly interface पहली बार देखकर अच्छा लगा जिसमें text धुंधला नहीं दिखता। सच में पहली बार
programs और कुछ operating systems, जैसे Windows तक बात बढ़ाएँ, तो पिछले कुछ वर्षों में text rasterization का एक तरीका common trend और default setting बन गया है, जिससे overall problem पैदा हुई है
दुर्भाग्य से users अक्सर sharp text पाने के लिए antialiasing बंद नहीं कर सकते, और जहाँ कभी-कभार option होता भी है, वहाँ menus जैसे interfaces पर फिर भी antialiasing लागू रहती है
ऐसे projects मुझे बहुत पसंद हैं। अभी भी C की low-level nature अच्छी लगती है। आजकल Rust और Elixir/Erlang बहुत इस्तेमाल करता हूँ, लेकिन C की simplicity और explicitness अक्सर याद आती है
इसलिए Zig भी काफी इस्तेमाल करता हूँ; यह C की philosophy को काफी हद तक बनाए रखते हुए भी बहुत अच्छे improvements करता है
C के बारे में उनके आकलन से मैं सच में सहमत हूँ। खासकर यह बात कि “syntax जटिल behavior को छिपाता नहीं है। यह इतना सरल है कि बार-बार खोजकर देखने की ज़रूरत नहीं पड़ती”, और उससे आगे, C के बारे में कुछ खोजना पड़े तब भी यह बहुत आसान और उपयोगी होता है
सरल और पुरानी भाषा के अपने फायदे होते हैं
हर Shape को
mallocसे अलग-अलग allocate करके उन pointers को dynamic array में store करें, तो निश्चित रूप से आप अपने लिए चीज़ें और कठिन बना सकते हैं। कहा गया है कि C# जैसी language इस्तेमाल करने पर ऐसी allocation structure मजबूरन अपनानी पड़ती है, लेकिन मुझे हैरानी है कि लेखक ने C में जैसा किया, वैसा structures का fixed array C# में इस्तेमाल करने से आखिर क्या रोकता हैकाश कोई इस project को आगे बढ़ाता रहे। बस कुछ महीनों तक और polish कर दिया जाए तो कुछ खास use cases में यह Blender या FreeCAD का गंभीर alternative बन सकता है, और learning curve भी काफी आसान दिखता है
हालांकि मूल रूप से यह SDF पर काम करता है, इसलिए triangles, vertices वगैरह इस्तेमाल करने वाले पारंपरिक mesh से modeling experience भी अलग है और store किया जाने वाला data भी अलग है
SDF को mesh में बदलना marching cubes जैसी technique से संभव है, लेकिन ऐसे data को बाद में Blender जैसी apps में वैसे भी clean up करना पड़ने की संभावना ज्यादा है
अगर renderer भी SDF-based हो तो SDF शानदार है। लेकिन ज्यादातर renderer ऐसे नहीं होते
अगर आपको यह पहले से पता था तो माफ़ कीजिए