2P by neo 3달전 | favorite | 댓글 1개

3D 모델러를 C언어로 일주일 만에 만들기

  • 작년 가을에 "Wheel Reinvention Jam"이라는 일주일 동안의 프로그래밍 이벤트에 참여함
  • 기존 소프트웨어 시스템을 새로운 시각으로 다시 살펴보는 것이 목적이었음
  • "ShapeUp"이라는 3D 모델러를 만들었고, 이 글을 읽기 전에 ShapeUp의 데모 동영상을 먼저 보는 것이 이해에 도움됨
  • ShapeUp은 브라우저에서 직접 사용해 볼 수 있음

언어 선택: C

  • Typescript 컴파일러가 느린 것에 대한 불만으로 인해 Jam에 참여
  • esbuild나 Bun의 Typescript 파서로 시작한다면 Typescript의 빠른 부분 집합을 구현하는 프로젝트가 가능해 보였음
  • 하지만 터미널 명령어 실행 속도 비교로는 흥미로운 데모가 되지 않을 것 같아 3D 프로젝트로 방향을 전환함
  • Ray marched signed distance fields(SDFs) 기술 덕분에 일주일 만에 처음부터 3D 프로젝트를 만드는 것이 가능해 보였음
  • 동등한 삼각형 기반 렌더러보다 SDF를 사용한 장면이 훨씬 빠르게 구현 가능
  • 이전에 SDF 셰이더를 작성해본 적은 있었지만 매우 기초적인 수준이었고, 코드를 편집하여 모델링하는 것은 자연스럽지 않게 느껴졌음
  • 마우스로 모양을 편집하고 싶었고, 이번 Jam이 그것을 실현할 기회라고 생각했음
  • 프로젝트 이름을 ShapeUp이라고 지음

C언어 사용의 장점

  • C는 매우 단순하고 원시적인 언어여서 내장 데이터 구조의 부족을 해결하고 포인터 버그를 고치는데 많은 시간을 소비할 것이라고 여겨짐
  • 하지만 C의 단순성은 장점이 됨
    • 빠르게 컴파일 됨
    • 문법이 복잡한 연산을 숨기지 않음
    • 간단해서 끊임없이 문법을 찾아볼 필요가 없음
    • native와 web assembly로 쉽게 컴파일 가능
  • C의 결점들은 22년 동안 사용하면서 개발한 습관으로 피할 수 있음
  • ShapeUp은 작은 단일 C 파일로 구성되어 매우 간단함

ShapeUp의 데이터 구조

  • 모델은 Shapes라는 구조체의 배열로 구성됨
  • Shapes는 정적으로 할당된 배열에 저장됨
    • 할당 실패나 메모리 누수의 위험이 없음
    • 100개의 Shape 제한은 실제로는 제한적이지 않았음
    • 렌더러 최적화 시간이 부족하여 100개가 되기 전에 프레임 속도가 떨어졌을 것
    • 시간이 있었다면 모델을 작은 블록으로 나누고 각 블록 내에서 raymarching을 수행했을 것
  • 동적 메모리는 단 3곳에서만 malloc을 호출함
    • 저장 (전체 문서를 담을 수 있을 만큼 큰 버퍼 할당)
    • OBJ 내보내기 (모든 꼭지점을 담을 수 있을 만큼 큰 버퍼 할당)
    • GLSL 셰이더 생성 (셰이더 소스용 버퍼)
  • 모든 경우 함수 끝에 단일 free가 있음
  • C에서 메모리 관리가 간단할 수 있다는 것을 보여주는 예시
  • C#, Javascript, Python 같은 언어는 Shape마다 개별적으로 malloc하고 해당 포인터를 동적 배열에 저장하는 할당 구조를 강제함
  • C는 메모리 레이아웃을 제어할 수 있어서 좋음

사용자 인터페이스

  • immediate mode user interface(IMGUI)로 구현됨
  • IMGUI 방식의 UI를 좋아함
    • 디버깅이 매우 쉬움
    • 요소를 배치하기 위해 실제 프로그래밍 언어 사용 (CSS, constraints, SwiftUI와 달리)
  • 대부분의 IMGUI와 마찬가지로 enum을 사용하여 어떤 요소에 포커스가 있는지 또는 마우스가 어떤 동작을 하고 있는지 추적함
  • 이 프로젝트에는 동적 배열이나 해시맵이 필요하지 않았지만, 필요했다면 stb_ds.h와 같은 것을 사용했을 것

Raylib 라이브러리의 문제점

  • C를 사용하기로 결정한 것은 좋았지만, raylib은 문제가 됨
  • 개발자 경험을 해치는 이상한 설계 선택이 있음
    • enum 타입이 예상되는 곳에 int를 사용하여 컴파일러 타입 검사를 방지하고 함수가 self document 되지 않음
    • 기본 매개변수 유효성 검사를 하지 않음 (설계 선택)
    • 종속성에 대한 책임을 지지 않음 (GLFW 이슈를 해결하거나 패치를 제출하지 않음)
  • raygui UI 라이브러리는 장난감에 불과함
    • 부동 소수점 숫자를 표시할 수 없음
    • 겹치거나 클리핑된 요소에 대한 마우스 이벤트 라우팅을 처리하지 않음
    • 둥근 모서리를 만들 수 없음
    • 보기 좋게 스타일을 지정할 수 없음
  • 버그도 있음
    • 폰트 변경 방지 버그
    • 그리기 함수가 삼각형 사이의 꼭지점을 공유하지 않아 픽셀 간격이 발생
  • 문제를 발견할 때마다 보고했지만 대부분 "won't fix"로 닫혔고 버그 리포트 작성에 시간이 많이 걸려 포기함
  • OpenGL 창을 만들어준 것은 좋았지만 그 편의성에 큰 대가를 치름
  • 다행히 OpenGL 함수를 직접 사용하거나 기능을 처음부터 구현하는 탈출구를 찾을 수 있었음
  • 앞으로는 sokol을 사용할 예정

일주일 동안의 개발 과정

  • ShapeUp은 6일 동안 완료해야 하는 4가지 주요 부분으로 구성됨
    1. 사용자 인터페이스 (3D 도구, 키보드 단축키, 사이드바, 게임 컨트롤러)
    2. GLSL 셰이더 생성기 + Ray marching 렌더러
    3. GPU 기반 마우스 선택
    4. Marching cubes for export
  • 각각은 어렵지 않았지만, 우선순위를 올바르게 정하고 빠져나가지 않는 것이 어려웠음
  • 까다롭거나 시간이 많이 걸리는 문제는 설계를 통해 해결하거나 90% 경우에 작동하는 멍청한 해결책을 사용하는 것이 도움됨
  • 때로는 기능을 하루 정도 미루면 무의식적으로 해결책을 찾을 수 있었음
  • 항상 작동하는 3D 모델러를 가지고 있고 시간이 허락하는 대로 점진적으로 개선하려고 노력함
  • 피라미드를 만드는 것처럼 생각함. 층별로 만들면 마지막까지 피라미드가 완성되지 않지만, 어느 단계에서 멈추더라도 완전한 피라미드가 되도록 만들 수 있음

프로젝트 결과

  • 일주일 후에는 의미 있는 3D 모델을 만들고 .obj 파일로 내보낼 수 있는 3D 프로그램을 가지게 됨
  • 멀티플랫폼에서 실행되고 파일 열기/저장 기능도 있음
  • 프로젝트는 2024줄의 C코드와 250줄의 GLSL로 구성됨
  • 약 2300줄 정도로 어느 정도 쓸모 있는 3D 모델러를 표현할 수 있다는 것이 약간 놀라움
  • Jam 요약과 Handmade Seattle 컨퍼런스에서 ShapeUp 데모를 보여달라는 요청을 받음
  • 사람들은 ShapeUp에 감명을 받은 듯 했지만 큰 성과를 거둔 것 같지는 않음. 비교적 간단한 프로젝트임
  • 내가 한 일에 특별한 것이 있다면, 무엇을 만들지 선택할 수 있는 감각, 그것을 만드는 데 필요한 지식, 그리고 일주일 안에 해내는 규율이었음

GN⁺의 의견

  • C언어의 단순함과 속도의 장점을 잘 보여주는 흥미로운 프로젝트임. 하지만 C의 낮은 추상화 수준 때문에 상용 프로젝트에 그대로 사용하긴 어려워 보임. 현대적인 3D 모델링 도구의 기능을 모두 C로 직접 구현하려면 엄청난 노력이 필요할 것으로 예상됨
  • 일주일 만에 동작하는 프로그램을 완성한 것이 인상적임. 하지만 장기적인 관점에서 코드 유지보수와 기능 확장을 고려했을 때 C++이나 Rust 같은 언어를 선택하는 것이 더 나은 선택일 수 있음
  • SDF를 이용한 렌더링 기법은 빠르고 간단하지만 모델링의 자유도나 퀄리티 면에서는 한계가 있어 보임. 상용 모델링 툴은 SubD나 NURBS 같은 표면 모델링 기술을 주로 사용함. 하지만 게임이나 데모 등 실시간성이 중요한 분야에서는 SDF 렌더링이 여전히 활용 가치가 높아 보임
  • 오픈소스 라이브러리 선택의 어려움을 잘 보여주는 사례. 문서화와 코드 품질, 지원 여부 등을 잘 파악하고 신중하게 선택해야 함. 자체 구현도 좋은 대안이 될 수 있음
  • 동작하는 프로그램을 먼저 만들고 점진적으로 개선해 나가는 방식은 실무에서도 매우 유용함. 핵심 기능부터 완성하고 세부 사항을 개선하는 식으로 우선순위를 잘 조절하는 것이 중요해 보임
Hacker News 의견
  • Raylib의 한계점에 대해 저자와 전적으로 동의함
    • 현재 Raylib으로 시작한 타워 디펜스 스타일 게임 개발 중이지만 비슷한 제한 사항에 직면함
    • 플랫폼 간 전체 화면 전환 불일치, 화면 모드 열거 불가, 런타임 렌더링 기능 전환 불가, 컴파일된 셰이더 저장 불가 등의 이슈가 있음
    • Raylib은 프로토타입 제작에는 좋지만 심각한 제한 사항을 감수하지 않는 한 그 이상은 어려움
    • 개발이 너무 진행되어 이제 Raylib을 SDL 등으로 교체하기에는 늦음
  • 형상을 정적 할당된 배열에 보관하는 것은 할당 실패나 메모리 누수 위험이 없는 사랑스러운 방식임. 실제로 100개 형상 제한은 제약이 되지 않음.
  • 이 프로젝트가 계속 발전하길 바람. 몇 달 후면 학습 곡선이 훨씬 완만한 Blender/FreeCAD의 특정 사용 사례에 대한 진지한 대안이 될 수 있음.
  • 영상의 라이브 데모가 정말 마음에 듦. 앱 제작은 고사하고 그런 영상을 1주일 만에 만들 수 없을 것임.
  • 메모리 처리 등 다양한 결정에 대해 이야기한 흥미로운 글. Crafting Interpreters 2부에 뛰어들면서 C 언어를 다시 공부하는 입장에서 C가 잘하는 것이 무엇인지 상기시켜 줌.
  • 2024줄의 C 코드로 아이러니를 가능케 한 노력에 감사함 :)
  • 잘 아는 도구를 가지고 그냥 멋진 것을 만드는 데에는 정말 강력한 무언가가 있음. 글 잘 읽었음.
  • C에 대한 주장에 정말 동의함. 특히 "문법이 복잡한 연산을 숨기지 않음. 간단해서 계속 찾아볼 필요가 없음"에 더욱 공감. 또한 C에 대해 뭔가 찾아봐야 한다면 매우 쉽고 유익함. 단순하고 오래된 언어의 장점임.
  • 가끔 C가 우리에게 필요한 전부라고 생각함.
  • 인상적인 개발 속도임. 설명 영상도 정말 재미있게 봤음!