2P by GN⁺ | ★ favorite | 댓글 1개
  • Wheel Reinvention Jam의 “기존 소프트웨어를 새 시각으로 다시 보기”라는 과제에서 ShapeUp이 탄생했고, 브라우저 실행 데모와 .obj 내보내기까지 갖춘 3D 모델러로 완성됨
  • 일주일 안에 3D 도구를 만들 수 있었던 핵심은 ray marched SDF였으며, 삼각형 기반 렌더러보다 색상·부드러운 그림자·ambient occlusion이 있는 장면을 빠르게 구현할 수 있었음
  • 구현은 C 단일 파일 중심으로 단순하게 유지됐고, 모델은 최대 100개 Shape를 정적 배열에 담아 메모리 관리 부담을 줄임
  • raylib는 OpenGL 창을 빨리 띄우는 데 도움을 줬지만, int 중심 API, 매개변수 검증 부재, GLFW 의존성, raygui 한계 때문에 직접 OpenGL을 쓰거나 기능을 새로 만들어야 했음
  • 최종 결과물은 C 2024줄과 GLSL 250줄, 총 약 2300줄 규모로 파일 열기·저장, 여러 플랫폼 실행, .obj 내보내기를 지원함

ShapeUp이 3D 모델러로 바뀐 과정

  • Wheel Reinvention Jam은 기존 소프트웨어 시스템을 새 시각으로 다시 살펴보는 일주일짜리 프로그래밍 이벤트였음
  • 처음 목표는 느린 TypeScript 컴파일러에 대한 불만에서 출발해 tsc보다 빠른 TypeScript 부분집합을 만드는 것이었음
    • esbuildBun의 TypeScript 파서를 시작점으로 삼으면 가능해 보였음
    • 하지만 성공 데모가 “한 터미널 명령이 다른 명령보다 빨리 끝남”에 그쳐 시각적으로 매력적이지 않았고, 결국 3D로 방향을 틀었음
  • ShapeUp은 마우스로 도형을 편집하는 3D 모델러로 만들어짐
    • 기존 SDF 셰이더 작성 경험은 있었지만, 코드를 직접 수정해 모델링하는 방식은 자연스럽지 않았음
    • 목표는 SDF 기반 형태 편집을 마우스로 할 수 있게 만드는 것이었음

SDF가 일주일 프로젝트를 가능하게 한 이유

  • ShapeUp의 렌더링 기반은 ray marched signed distance fields(SDFs)
  • SDF 장면은 색상, 부드러운 그림자, ambient occlusion을 포함해도 삼각형 기반 렌더러보다 빠르게 구현할 수 있었음
  • Inigo Quilez가 SDF로 한 자리에서 Pixar풍 캐릭터를 만드는 사례가 기술적 방향의 참고점이 됨
  • ShapeUp은 코드 편집 대신 도형을 직접 조작하는 방식으로 SDF 모델링을 다룸

C 구현과 데이터 구조

  • ShapeUp은 C로 작성됐고, OpenGL 창 생성을 위해 raylib를 사용함
  • C를 고른 이유는 빠른 컴파일, 복잡한 동작을 숨기지 않는 문법, 익숙함, 네이티브와 WebAssembly 양쪽 컴파일 가능성 때문임
  • 모델은 Shape 구조체들의 집합으로 구성됨
    • 각 Shape는 위치, 크기, 각도, 모서리 반경, blob 정도, 색상, 미러링 축, subtract 여부를 가짐
  • Shape 목록은 동적 할당 대신 정적 배열로 관리됨
    • MAX_SHAPE_COUNT는 100
    • Shape shapes[MAX_SHAPE_COUNT], shape_count, selected_shape로 상태를 관리함
    • 이 방식은 할당 실패와 누수 가능성을 없앰
  • 100개 Shape 제한은 실제 사용에서 큰 문제가 되지 않았음
    • 렌더러 최적화 시간이 부족해 100개에 도달하기 전에 프레임레이트가 먼저 떨어졌음
    • 시간이 있었다면 모델을 작은 벽돌 단위로 나누고 각 벽돌 안에서 ray marching을 수행하려 했음

메모리 사용 방식

  • ShapeUp은 동적 메모리 할당을 3곳에서만 사용함
    • 저장: 전체 문서를 담을 수 있는 버퍼 할당
    • .OBJ 내보내기: 모든 정점을 담을 수 있는 버퍼 할당
    • GLSL 셰이더 생성: 셰이더 소스용 버퍼 할당
  • 각 경우 함수 끝에서 한 번만 free
  • Shape를 각각 malloc하고 포인터를 동적 배열에 저장할 수도 있지만, 이 프로젝트에는 그런 구조가 필요하지 않았음
  • C는 메모리 배치를 직접 제어할 수 있다는 점에서 장점이 있었음
  • 동적 배열이나 해시맵이 필요했다면 stb_ds.h 같은 도구를 쓸 수 있었음

UI 구현 방식

  • UI는 immediate mode user interface(IMGUI) 방식으로 구현됨
  • IMGUI는 디버깅이 쉽고, CSS·constraints·SwiftUI 대신 실제 프로그래밍 언어로 요소 위치를 정할 수 있다는 점이 장점임
  • 포커스된 요소나 마우스 동작은 Control enum으로 추적함
    • 위치, 크기, 각도, 색상, 이동, 회전, 스케일, 카메라 회전, blob 정도 같은 조작 상태를 enum 값으로 표현함
    • focused_controlmouse_action이 현재 UI 상태를 담음

raylib와 raygui에서 막힌 지점

  • raylib는 OpenGL 창을 빠르게 띄우는 데는 유용했지만, 시간이 지날수록 개발 속도를 늦추는 요소가 됨
  • raylib API에서 특히 불편했던 부분은 타입 정보 부족이었음
    • enum 타입이 기대되는 곳에도 int를 사용해 컴파일러 타입 검사를 받을 수 없음
    • 함수 시그니처만으로 매개변수 의미가 잘 드러나지 않음
    • 예를 들어 IsGestureDetected(unsigned int gesture)gesture는 등록된 제스처 ID처럼 보이지만 실제로는 Gesture enum임
    • 문서가 헤더 파일 중심이라 어떤 int가 실제로 enum인지 확인하려면 구현을 봐야 했음
  • 기본 매개변수 검증을 하지 않는 설계도 문제를 키움
    • LoadFileData(const char *fileName, int * dataSize)dataSizeNULL이면 segfault가 남
    • 헤더는 dataSize가 출력 매개변수인지, null이면 안 되는지 표시하지 않음
    • 검증 부재 때문에 단순한 문제 추적이 어려워졌고, 경우에 따라 조용히 이상 동작을 할 수 있었음
  • 의존성 처리에서도 기대와 차이가 있었음
    • GLFW의 이슈를 raylib가 우회하거나 패치를 제출하지 않는 문제가 있었음
    • 최종 사용자는 창 생성 방식의 내부 구현보다 raylib 기능이 제대로 동작하는지를 더 중요하게 봄
  • raygui UI 라이브러리는 프로젝트에 쓰기엔 제약이 많았음
    • 부동소수점 숫자를 표시하지 못해 직접 float 텍스트 필드를 만들어야 했음
    • 겹치거나 잘린 요소의 마우스 이벤트 라우팅을 처리하지 못함
    • UI에서 흔한 둥근 모서리를 지원하지 못함
    • 보기 좋게 스타일링하기 어려움
  • 버그도 개발 흐름을 방해함
    • raygui 도구 버그 때문에 과하게 스타일화된 기본 폰트를 바꾸지 못했음
    • DrawCircle(...) 같은 그리기 함수는 삼각형 사이에서 정점을 공유하지 않아, 현재 행렬에 스케일이나 회전이 있으면 부동소수점 오차로 픽셀 틈이 생겼음
  • 발견한 이슈를 한동안 보고했지만 대부분 “wont fix”로 닫혔고, 이후에는 보고를 중단함
  • 우회 방법은 직접 OpenGL 함수를 쓰거나 필요한 기능을 처음부터 구현하는 것이었음
  • 앞으로는 raylib 대신 sokol을 사용할 계획임

6일 안에 끝내야 했던 네 가지

  • ShapeUp은 6일 안에 크게 네 부분을 완성해야 했음
    • 사용자 인터페이스: 3D 기즈모, 키보드 단축키, 사이드바, 게임 컨트롤러
    • GLSL 셰이더 생성기와 ray marching 렌더러
    • GPU 기반 마우스 선택
    • 내보내기용 marching cubes
  • 어려움은 각 기능 자체보다 우선순위 유지에 있었음
  • 까다롭거나 시간이 오래 걸리는 문제는 설계를 바꿔 피하거나, 90% 경우에 동작하는 단순한 해결책으로 처리함
  • 어떤 기능은 하루 미뤄두는 동안 해결책이 떠오르기도 했음
  • 작업 방식은 항상 동작하는 3D 모델러를 유지하고, 시간이 허락하는 만큼 점진적으로 개선하는 쪽이었음
    • 완성 시점에만 피라미드가 되는 방식이 아니라, 어느 단계에서 멈춰도 완전한 작은 피라미드가 되도록 만드는 방식에 비유됨

최종 결과

  • 일주일이 끝날 때 ShapeUp은 의미 있는 3D 모델을 만들고 .obj 파일로 내보낼 수 있었음
  • 여러 플랫폼에서 실행되고, 파일 열기와 저장도 지원함
  • 코드 규모는 C 2024줄GLSL 250줄
  • 약 2300줄로 어느 정도 유용한 3D 모델러를 만들 수 있었다는 점이 눈에 띄는 결과임
  • 프로젝트 자체는 비교적 단순하지만, 무엇을 만들지 고르는 감각, 만들 수 있는 지식, 일주일 안에 끝내는 규율이 중요하게 작용함

댓글과 토론

Hacker News 의견들
  • Raylib의 한계에 대해 글쓴이와 완전히 같은 생각임. 지금 Raylib로 시작한 타워 디펜스 스타일 게임을 만들고 있는데, 같은 한계들과 그 외 문제들을 많이 겪는 중임
    예를 들면 플랫폼마다 전체 화면 전환이 일관되게 동작하지 않거나, 화면 모드를 열거할 수 없거나, 실행 중 렌더링 기능을 토글하기 어렵거나, 컴파일된 셰이더를 저장하는 문제 등이 있음
    그래도 Ray가 이 라이브러리에 들인 작업은 고맙고 계속 후원할 생각임. Raylib는 빠르게 프로토타입을 만들기에는 훌륭하지만, 심한 제약을 감수하지 않는 한 그 이상으로는 쉽지 않음
    확실히 배운 점은 있지만, 이제 와서 Raylib 관련 코드를 전부 SDL 같은 것으로 갈아엎기에는 개발이 너무 많이 진행됐음

    • Raylib가 광선 추적이 아니라 만든 사람 이름인 Ray에서 왔다는 디테일이 재미있음
      Things Unexpectedly Named After People: https://notes.rolandcrosby.com/posts/unexpectedly-eponymous/
    • Raylib는 시작하기 쉽지만 프로젝트가 조금 복잡해지면 되레 물기 시작함. 반면 SDL은 초기 설정에 시간이 더 들지만 프로젝트가 커질수록 매우 잘 확장됨. 코드 품질도 예외적으로 좋음
    • Raylib에는 앞으로도 고쳐지지 않을 문제가 많지만, 전체 화면 문제를 Raylib 탓으로만 보기는 어려움. Windows의 전체 화면은 수십 년째 사실상 못 쓸 정도로 망가져 있었고, 다른 플랫폼도 비슷할 가능성이 큼
      요즘 전략은 그냥 테두리 없는 창 모드를 쓰고 진짜 전체 화면은 없는 척하는 것임
    • 비슷한 느낌임. 2개월쯤 전에 프로젝트를 시작하며 Raylib를 골랐고, 기본적인 것은 정말 쉽게 굴러가지만 쓸수록 사소하고 랜덤한 불편을 계속 만나게 됨. 이제는 Raylib 사용을 되돌리기엔 프로젝트에 너무 많이 투자했음
      지금 가장 큰 문제는 글꼴 처리와 텍스트 렌더링임. TTF 글꼴 대신 미리 구운 비트맵 글꼴로 바꿔야 할 것 같은데, 나중에 지역화할 때 꽤 괴로울 듯함
      Love2D에서 넘어온 뒤 가장 아쉬운 두 기능은 여러 색상 텍스트를 쉽게 렌더링하는 것과, 텍스처를 쉽게 잘라서 반복하거나 타일링하는 것임. Raylib에서는 색상 마크업 기준으로 텍스트를 직접 조각내고, 너비 오프셋을 적용한 뒤, 줄바꿈까지 고려해 각 조각마다 그리기 함수를 호출해야 함
      화면에 텍스트를 많이 그리면 FPS도 크게 떨어지는 것 같은데, 텍스트의 그리기 호출 배칭이 깨진 것일 수도 있음. 예전에는 타일 텍스처 그리기 함수가 있었는데 어떤 이유인지 제거됐음
    • 이걸 보고 raylib를 살펴보고 싶어졌음. WebAssembly로 실행되는 귀여운 예제들이 있음: https://www.raylib.com/examples.html
      Wasm과 브라우저 3D/2D 그래픽에서 늘 거슬렸던 점은 스크롤 같은 작은 문제가 자주 보인다는 것임. 여기의 “Background scrolling & parallax” 예제를 보면 됨: https://www.raylib.com/examples.html
      여러 기기에서 테스트했는데, 내 눈이 이상한 게 아니라면 분명 부드러운 스크롤이 아님. 2024년에 2D 부드러운 스크롤이 아직 해결된 문제가 아니라는 게 말이 되나 싶음
  • “Shape들을 정적으로 할당된 배열에 보관한다. 할당 실패도 없고 누수도 없고 군더더기도 없다. 사랑스럽다. Shape 100개 제한은 실제로는 제약이 아니었다. 렌더러 최적화 시간이 거의 없었기 때문에 100개에 도달하기도 전에 프레임률이 떨어졌을 것이다.”
    최근 본 것 중 성급한 최적화 회피의 가장 좋은 예시임

    • 오히려 반대에 가깝다고 봄. 성급한 추상화와 일반화를 피한 것임
    • 직접 만드는 사람들과, 어떻게 만들지를 두고 앉아서 다투기만 하는 사람들의 차이를 보여주는 최고의 예시임
  • 정말 흥미로운 글이고, 메모리 처리 방식과 raylib에서 마주친 문제 같은 여러 결정을 이야기해줘서 좋았음. 마침 Crafting Interpreters 2부에 들어가면서 C를 다시 복습하는 중이라, C가 잘하는 것을 다시 떠올리게 되어 좋음

  • 영상 속 실시간 데모가 정말 좋음. 앱을 만드는 건 둘째 치고, 내가 시도했다면 그 영상조차 일주일 안에 못 만들었을 것 같음

    • 영상 만드는 데 앱보다 더 오래 걸렸음. 유튜버들이 어떻게 그렇게 꾸준히 하는지 모르겠음
  • 오래전에 탁상 전화기용 운영체제를 작업한 적이 있음. RAM이 64K뿐이라 동적 메모리 관리가 전혀 없었고, 정적 변수를 많이 써서 컴파일러가 컴파일 시점에 전부 배치하게 했음
    많은 애플리케이션에는 동적 메모리 관리가 아예 필요 없을 수도 있다는 점을 쉽게 잊게 됨. 고정 크기 버퍼 몇 개를 할당해두고, 그 버퍼가 찼을 때의 예외 상황만 깔끔히 처리해도 충분한 경우가 많음
    그런 맥락에서는 C가 실제로 훨씬 안전함. 메모리 누수가 없고, 걱정할 것은 버퍼 오버플로뿐임. 모든 변수가 정적으로 할당되어 있다면 sizeof를 조심해서 쓰는 방식으로 관리할 수 있음
    요즘 Rust와 Go가 훌륭한 선택지가 아니라는 뜻은 아니지만, 소박한 옛 C도 여전히 잘 동작하며 악몽처럼 복잡할 필요는 없음

  • 주제에서 조금 벗어나지만, 텍스트가 흐릿해 보이지 않는 WebAssembly 인터페이스를 처음 봐서 반가움. 정말 처음임
    프로그램과 일부 운영체제, 예를 들면 Windows까지 넓혀보면, 지난 몇 년 동안 텍스트 래스터화 방식이 공통 유행이자 기본 설정이 되면서 전반적인 문제가 생겼음
    안타깝게도 사용자는 선명한 텍스트를 얻기 위해 안티앨리어싱을 끌 수 없는 경우가 많고, 드물게 옵션이 있어도 메뉴 같은 인터페이스에는 여전히 안티앨리어싱이 적용됨

    • 그 선명한 텍스트는 그다지 인상적이지 않음. 서체에 곡선이 없고 스무딩도 없음. 어떤 해상도에서도 선명하고 블록처럼 각진 텍스트로 보일 것임
    • WebAssembly와 래스터화 사이에 어떤 연결이 있는지 궁금함. 정말로 흥미로워 보임
  • 이런 프로젝트가 정말 좋음. 아직도 C의 저수준성이 좋음. 지금은 Rust와 Elixir/Erlang을 많이 쓰지만, C의 단순함과 명시성이 종종 그리움
    그래서 Zig도 많이 쓰는데, C의 철학을 많이 유지하면서도 아주 괜찮게 개선한 언어임

    • Erlang도 꽤 단순한 언어임
  • C에 대한 그의 판단에 정말 동의함. “문법이 복잡한 동작을 숨기지 않는다. 계속 찾아보지 않아도 될 만큼 단순하다”는 부분이 특히 그렇고, 더 나아가 C에 대해 뭔가 찾아봐야 할 때도 매우 쉽고 유익함
    단순하고 오래된 언어에는 그만한 장점이 있음

  • 각 Shape를 malloc으로 따로 할당하고 그 포인터들을 동적 배열에 저장하면 확실히 스스로 더 어렵게 만들 수 있음. C# 같은 언어를 쓰면 그런 할당 구조를 강제한다는 식의 말이 있는데, 글쓴이가 C에서 한 것처럼 구조체 고정 배열을 C#에서 쓰지 못하게 막는 것이 무엇인지 궁금함

    • 아무것도 막지 않음. C#에서도 이런 식으로 struct 배열을 쓰는 건 드문 일이 아님
  • 누군가 이 프로젝트를 계속 이어갔으면 함. 몇 달만 더 다듬으면 특정 용도에서는 Blender나 FreeCAD의 진지한 대안이 될 수 있고, 학습 곡선도 훨씬 완만해 보임

    • 더 정교한 버전이면서 여전히 무료인 MagicaCSG도 확인해보면 좋음: https://ephtracy.github.io/index.html?page=magicacsg#ss-caro...
    • 수정: 아하, 이 프로그램은 이미 마칭 큐브로 메시 내보내기를 지원함. 사이트의 YouTube 영상을 보면 됨. 그걸 몰랐음
      다만 근본적으로 SDF로 동작하기 때문에, 삼각형·정점 등을 쓰는 전통적인 메시와는 모델링 경험도 다르고 저장하는 데이터도 다름
      SDF를 메시로 바꾸는 것은 마칭 큐브 같은 방식으로 가능하지만, 그런 데이터는 이후 Blender류 앱에서 어차피 정리해야 할 가능성이 큼
      렌더러도 SDF 기반이라면 SDF는 훌륭함. 대부분은 그렇지 않지만
      이미 알고 있었다면 미안함
    • SDF를 좋아한다면 Womp가 시작하기 꽤 좋음. Tinkercad도 입문용 CAD로 꽤 괜찮음
    • Dune3D와 Salome-Platform도 확인해보면 좋음
    • Blender 대안은 될 수 있어도, 안타깝게도 CAD 작업 대안은 아닐 듯함