<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>LazySonic Blog</title>
    <link>https://develop-4-art.tistory.com/</link>
    <description>정리하는 습관을 기르기 위해 시작한 기술 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Fri, 3 Jul 2026 14:26:00 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>LazySonic</managingEditor>
    <image>
      <title>LazySonic Blog</title>
      <url>https://tistory1.daumcdn.net/tistory/2845950/attach/cb9753e2eb5748b98135ab48172a001b</url>
      <link>https://develop-4-art.tistory.com</link>
    </image>
    <item>
      <title>[Graphics] 복셀 마칭큐브와 Mesh Terrain - 4</title>
      <link>https://develop-4-art.tistory.com/173</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://develop-4-art.tistory.com/170&quot;&gt;[Graphics] 복셀 마칭큐브와 Mesh Terrain - 1&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://develop-4-art.tistory.com/171&quot;&gt;[Graphics] 복셀 마칭큐브와 Mesh Terrain - 2&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://develop-4-art.tistory.com/172&quot;&gt;[Graphics] 복셀 마칭큐브와 Mesh Terrain - 3&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Ch4. &lt;/span&gt;케이스 스터디 &amp;mdash;&lt;span&gt; UE 5.8 &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshTerrainMode&lt;/span&gt;&lt;span&gt; / &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshPartition&lt;/span&gt;에서 마칭큐브가 호출되는 자리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 본 추상적 절차 &amp;mdash;&lt;span&gt; 8&lt;/span&gt;비트 인덱스 &amp;rarr; 두 장의 테이블 &amp;rarr; 보간 &amp;rarr; 모호성 처리 &amp;mdash; 가 실제 게임 엔진의 한 줄 코드로 응결되는 자리를 보면 알고리즘 이해가 마무리된다&lt;span&gt;. UE 5.8&lt;/span&gt;에 새로 들어온 두 플러그인&lt;span&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshTerrainMode&lt;/span&gt;와 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshPartition&lt;/span&gt;이 그 자리다&lt;span&gt;. &lt;/span&gt;이 챕터는 그 둘이 무엇이고&lt;span&gt;, &quot;&lt;/span&gt;두 절벽 붙이기&lt;span&gt;&quot; &lt;/span&gt;인터랙션이 어떻게 복셀화&lt;span&gt; + &lt;/span&gt;마칭큐브&lt;span&gt; + &lt;/span&gt;협대역&lt;span&gt; SDF&lt;/span&gt;로 풀리는지를 따라간다&lt;span&gt;. &lt;/span&gt;검증 소스는 원본 블로그 글&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;a href=&quot;https://develop-4-art.tistory.com/169&quot;&gt;https://develop-4-art.tistory.com/169&lt;/a&gt;&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;과&lt;span&gt; UE 5.8 &lt;/span&gt;릴리스 노트&lt;span&gt;, Epic &lt;/span&gt;공식 문서다&lt;span&gt;(&lt;/span&gt;로컬&lt;span&gt; UE &lt;/span&gt;소스가&lt;span&gt; 5.6&lt;/span&gt;이므로 클래스 수준의 직접 확인은 외부 문서에 의존한다&lt;span&gt;).&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;두 플러그인의 역할 분담&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;UE 5.8&lt;/span&gt;의 메시 기반 지형 시스템은 두 개의 분리된 플러그인이 짝을 이루는 구조다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;`MeshTerrainMode`&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;&amp;mdash; 에디터 모드 플러그인&lt;span&gt;. &lt;/span&gt;지형 생성&amp;middot;편집&amp;middot;스컬프팅 같은 작업의&lt;span&gt; UI/&lt;/span&gt;도구 레이어를 담당한다&lt;span&gt;. 6&lt;/span&gt;개의 서브모드&lt;span&gt;(Create / Edit / Sculpt &lt;/span&gt;등 &amp;mdash; 디테일은&lt;span&gt; [&lt;/span&gt;지난 글&lt;span&gt;](&lt;a href=&quot;https://develop-4-art.tistory.com/169)&quot;&gt;https://develop-4-art.tistory.com/169)&lt;/a&gt; &lt;/span&gt;참조&lt;span&gt;)&lt;/span&gt;가 이 안에 들어있고&lt;span&gt;, &lt;/span&gt;각 서브모드가 내부적으로 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshPartition&lt;/span&gt;의 메시 데이터를 갱신한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;`MeshPartition`&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;&amp;mdash; 런타임&lt;span&gt; + &lt;/span&gt;에디터 양쪽에서 살아 있는 데이터 레이어&lt;span&gt;. &lt;/span&gt;월드를 청크로 자르고&lt;span&gt;, &lt;/span&gt;각 청크의 메시&lt;span&gt;(=&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;FDynamicMesh3&lt;/span&gt;&lt;span&gt; &lt;/span&gt;삼각형 집합&lt;span&gt;)&lt;/span&gt;와&lt;span&gt; (&lt;/span&gt;필요할 때&lt;span&gt;) &lt;/span&gt;협대역&lt;span&gt; SDF&lt;/span&gt;를 들고 있다&lt;span&gt;. &lt;/span&gt;런타임에는 그냥 정적 메시처럼 동작하고&lt;span&gt;, &lt;/span&gt;에디터에서만 편집 가능한 상태가 된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의&lt;span&gt; Landscape &lt;/span&gt;시스템이 하이트맵 텍스처를&lt;span&gt; GPU&lt;/span&gt;에서 매 프레임 셰이더로 변위시키는 구조였다면&lt;span&gt;, &lt;/span&gt;새 메시 테레인은 &lt;b&gt;데이터가 처음부터 진짜 삼각형&lt;/b&gt;이다&lt;span&gt;. &lt;/span&gt;하이트맵은 일회성 임포트 포맷으로만 쓰이고&lt;span&gt;, &lt;/span&gt;임포트가 끝나면 결과는 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;FDynamicMesh3&lt;/span&gt;&lt;span&gt; &lt;/span&gt;메시로 변환돼 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshPartition&lt;/span&gt;에 박힌다&lt;span&gt;. &lt;/span&gt;그 이후 모든 편집 &amp;mdash; 끌어올리기&lt;span&gt;, &lt;/span&gt;깎기&lt;span&gt;, &lt;/span&gt;절벽 두 개 합치기 &amp;mdash; 은 모두 메시 위에서 직접 일어난다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;&quot;&lt;/span&gt;두 절벽 붙이기&lt;span&gt;&quot; &lt;/span&gt;&amp;mdash;&lt;span&gt; MC&lt;/span&gt;가 호출되는 정확한 자리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글의 본 주제와 직결되는 자리가 바로 여기다&lt;span&gt;. &lt;/span&gt;사용자가 에디터에서 절벽 두 개를 가까이 가져가서&lt;span&gt; &quot;&lt;/span&gt;합쳐라&lt;span&gt;&quot;&lt;/span&gt;라고 명령하는 순간&lt;span&gt;, &lt;/span&gt;다음 단계가 차례로 일어난다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mexnr/dJMcacccBXC/IjRL7c1U9a3V7uITI5YlG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mexnr/dJMcacccBXC/IjRL7c1U9a3V7uITI5YlG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mexnr/dJMcacccBXC/IjRL7c1U9a3V7uITI5YlG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmexnr%2FdJMcacccBXC%2FIjRL7c1U9a3V7uITI5YlG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;938&quot; height=&quot;313&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;313&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[IMAGE 15 &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;mdash;&lt;span&gt; UE5.8 &lt;/span&gt;메시 테레인&lt;span&gt; &quot;&lt;/span&gt;두 절벽 합치기&lt;span&gt;&quot; &lt;/span&gt;파이프라인&lt;span&gt; (SVG): &lt;/span&gt;박스 다이어그램 &amp;mdash;&lt;span&gt; Input(&lt;/span&gt;두&lt;span&gt; FDynamicMesh3) &lt;/span&gt;&amp;rarr; ①&lt;span&gt; Voxelization (&lt;/span&gt;좁은 영역만&lt;span&gt;) &lt;/span&gt;&amp;rarr; ②&lt;span&gt; Narrow-band SDF &lt;/span&gt;&amp;rarr; ③&lt;span&gt; **Marching Cubes** &lt;/span&gt;&amp;rarr; ④ 결과&lt;span&gt; FDynamicMesh3 &lt;/span&gt;&amp;rarr;&lt;span&gt; MeshPartition &lt;/span&gt;갱신&lt;span&gt;. &lt;/span&gt;화살표&lt;span&gt; + &lt;/span&gt;단계 라벨&lt;span&gt;.]&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;1.&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;선택 영역 추출&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;두 절벽 메시 중 합쳐질 부근&lt;span&gt;(&lt;/span&gt;예&lt;span&gt;: &lt;/span&gt;한 청크 또는 사용자가 지정한 박스&lt;span&gt;)&lt;/span&gt;만 잘라낸다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;2.&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;복셀화&lt;span&gt;(voxelization).&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;그 좁은 영역만&lt;span&gt; N&lt;/span&gt;&amp;sup3; 격자&lt;span&gt;(&lt;/span&gt;보통 해상도가 청크 단위 한정으로 작게 잡힘&lt;span&gt;)&lt;/span&gt;로 샘플링한다&lt;span&gt;. &lt;/span&gt;두 메시의 표면에서 부호 있는 거리를 평가해서 각 격자 점에&lt;span&gt; SDF &lt;/span&gt;값을 채운다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;3.&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;협대역&lt;span&gt; SDF(narrow-band SDF) &lt;/span&gt;구축&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;표면 근처의 좁은 띠 안쪽만 정확한&lt;span&gt; SDF &lt;/span&gt;값을 들고 있고&lt;span&gt;, &lt;/span&gt;그 바깥은&lt;span&gt; &quot;&lt;/span&gt;충분히 멀다&lt;span&gt;&quot;&lt;/span&gt;로 잘라낸다&lt;span&gt;. &lt;/span&gt;메모리&amp;middot;계산 양쪽에서 보수적이고&lt;span&gt;, &lt;/span&gt;우리 목적엔 표면 근처 값만 있으면 충분하다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;4.&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;마칭큐브&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;그&lt;span&gt; SDF &lt;/span&gt;격자를 입력으로 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;iso = 0&lt;/span&gt;&lt;span&gt; &lt;/span&gt;등치면을 추출한다&lt;span&gt;. Ch3&lt;/span&gt;에서 본 그 절차 &amp;mdash; 큐브 인덱스 &amp;rarr; 엣지 테이블 &amp;rarr; 보간 &amp;rarr; 삼각형 테이블 &amp;rarr; 정점 등록 &amp;mdash; 이 격자 한 칸씩 행진하며 도장 찍힌다&lt;span&gt;. &lt;/span&gt;출력은 새 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;FDynamicMesh3&lt;/span&gt;&lt;span&gt; &lt;/span&gt;삼각형들이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;5.&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;`MeshPartition` &lt;/span&gt;갱신&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;결과 메시가 두 원본 메시의 자리를 교체하고&lt;span&gt;, BVH/&lt;/span&gt;충돌 메시가 재구축된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;&lt;span&gt;MC &lt;/span&gt;호출이 통째로 에디터 안에서 일어난다&lt;/b&gt;는 점이다&lt;span&gt;. &lt;/span&gt;한 번 베이크된 결과는 그 이후 런타임에서는 그냥 정적 메시로 보인다&lt;span&gt;. &lt;/span&gt;사용자가 다시 편집 모드에 들어가서 같은 영역을 또 만지지 않는 한 마칭큐브가 다시 돌 일은 없다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;왜 에디터 전용인가&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;런타임 동적 편집이 안 되는 이유는 마칭큐브 자체의 비용보다도 &lt;b&gt;후속 비용&lt;/b&gt;에 있다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;새 메시를 만든 다음 충돌 메시&lt;span&gt;(BVH/PhysX/Chaos &lt;/span&gt;등&lt;span&gt;)&lt;/span&gt;를 재구축해야 한다&lt;span&gt;. &lt;/span&gt;청크 단위라도&lt;span&gt; ms &lt;/span&gt;단위로 떨어진다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;LOD &lt;/span&gt;메시&lt;span&gt;(&lt;/span&gt;원본 외에 단순화된 메시 여러 단계&lt;span&gt;)&lt;/span&gt;를 재계산해야 한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;GPU &lt;/span&gt;리소스&lt;span&gt;(&lt;/span&gt;정점 버퍼&lt;span&gt;, &lt;/span&gt;인덱스 버퍼&lt;span&gt;)&lt;/span&gt;를 업로드해야 한다 &amp;mdash; 메인 스레드를 잡으면 프레임 드랍&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;정적 라이팅이 미리 베이크돼 있던 경우 그 라이팅이 무효화된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 누적 비용 때문에&lt;span&gt; UE 5.8&lt;/span&gt;은 첫 출시 단계에서 &lt;b&gt;&lt;span&gt;MC &lt;/span&gt;베이크를 에디터 전용&lt;/b&gt;으로 못 박았다&lt;span&gt;. &lt;/span&gt;알고리즘 자체는 청크 단위&lt;span&gt; GPU &lt;/span&gt;컴퓨트로 런타임에서도 충분히 돌릴 수 있다 &amp;mdash; 사실&lt;span&gt; Voxel Tools, Voxel Farm &lt;/span&gt;같은 서드파티는 이미 그렇게 한다&lt;span&gt;. UE 5.8&lt;/span&gt;의 결정은&lt;span&gt; &quot;&lt;/span&gt;런타임이 안 된다&lt;span&gt;&quot;&lt;/span&gt;가 아니라&lt;span&gt; &quot;&lt;/span&gt;런타임 동적 편집의 후속 비용을 첫 출시에서 책임지지 않는다&lt;span&gt;&quot;&lt;/span&gt;에 가깝다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;협대역&lt;span&gt; SDF&lt;/span&gt;가 등장한 이유 &amp;mdash;&lt;span&gt; Ch3&lt;/span&gt;의 모호성 케이스와 연결&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Ch3&lt;/span&gt;에서 모호성 케이스&lt;span&gt;(6/10/12/13)&lt;/span&gt;를 다룰 때&lt;span&gt; &quot;&lt;/span&gt;위상이 약간 거짓말을 해도 시각적으로 큰 문제가 안 되는 자리에서는 그냥&lt;span&gt; 15&lt;/span&gt;케이스 표를 쓰고 모호성을 무시한다&lt;span&gt;&quot;&lt;/span&gt;고 했는데&lt;span&gt;, &lt;/span&gt;사실 모호성을 거의 발생시키지 않는 입력을 미리 만드는 길도 있다&lt;span&gt;. &lt;/span&gt;그 길이 바로 &lt;b&gt;협대역&lt;span&gt; SDF&lt;/span&gt;로 입력을 정규화&lt;/b&gt;하는 것이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;협대역&lt;span&gt; SDF&lt;/span&gt;는 표면 근처에서 부드럽고 단조롭게 변하는 부호 있는 거리 함수다&lt;span&gt;. &lt;/span&gt;이런 입력에서는 한 큐브 안에서 안&lt;span&gt;/&lt;/span&gt;밖 비트가 대각선으로 갈리는 패턴 &amp;mdash; 즉&lt;span&gt; 6/10/12/13 &lt;/span&gt;같은 모호성 케이스 &amp;mdash; 자체가 거의 발생하지 않는다&lt;span&gt;. SDF&lt;/span&gt;의 단조성이 큐브 안의 위상을 미리 정리해주는 셈이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FvhSh/dJMcajh4HGL/V4NNpcYkBDaJD507U4FuE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FvhSh/dJMcajh4HGL/V4NNpcYkBDaJD507U4FuE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FvhSh/dJMcajh4HGL/V4NNpcYkBDaJD507U4FuE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFvhSh%2FdJMcajh4HGL%2FV4NNpcYkBDaJD507U4FuE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;938&quot; height=&quot;438&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[IMAGE 16 &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;mdash; 두 절벽 메시가 마칭큐브로 매끄럽게 융합되는 컷&lt;span&gt; (nanobanana &lt;/span&gt;일러스트&lt;span&gt;): &lt;/span&gt;두 거친 절벽 사이의 좁은 협곡이 부드러운 곡면으로 이어지는 비유 컷&lt;span&gt;. &lt;/span&gt;정확 데이터 아님&lt;span&gt;.]&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉&lt;span&gt; UE 5.8&lt;/span&gt;의 파이프라인은 마칭큐브를 &lt;b&gt;모호성이 거의 없는 입력에서만 호출&lt;/b&gt;하도록 설계되어 있다&lt;span&gt;. &lt;/span&gt;입력을 협대역&lt;span&gt; SDF&lt;/span&gt;로 정규화하는 한 단계를 앞에 두는 것으로&lt;span&gt;, MC &lt;/span&gt;자체는 단순한&lt;span&gt; 15&lt;/span&gt;케이스 표만으로도 충분히 안전하게 동작한다&lt;span&gt;. &lt;/span&gt;알고리즘과 입력의 책임을 깔끔하게 분리한 디자인이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;좁은 영역만 베이크하는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔진 입장에서는 두 절벽 청크 전체를 다시 베이크할 수도 있지만&lt;span&gt;, &lt;/span&gt;그러면 사용자가 만진 적도 없는 자리까지 메시가 재구성되고 정점&lt;span&gt; ID&lt;/span&gt;가 바뀌어버린다&lt;span&gt;. &lt;/span&gt;머티리얼 페인팅&lt;span&gt;, &lt;/span&gt;식생 인스턴스 같은&lt;span&gt; attachment&lt;/span&gt;가 끊긴다&lt;span&gt;. &lt;/span&gt;그래서 메시 테레인은 &lt;b&gt;편집이 영향을 미치는 좁은 박스만&lt;/b&gt; 잘라서 베이크하고&lt;span&gt;, &lt;/span&gt;결과 메시를 원본 메시의 해당 영역에만&lt;span&gt; patch-in &lt;/span&gt;한다&lt;span&gt;. &lt;/span&gt;협대역&lt;span&gt; SDF&lt;/span&gt;가&lt;span&gt; &quot;&lt;/span&gt;협대역&lt;span&gt;&quot;&lt;/span&gt;인 이유도 같다 &amp;mdash; 표면 근처 좁은 띠만 정확히 들고 있으면 충분하기 때문에 메모리도&lt;span&gt;, &lt;/span&gt;베이크 시간도 박스 부피에 비례한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 좁은 박스 베이크는 사실&lt;span&gt; GPU &lt;/span&gt;컴퓨트로 옮기기 매우 좋은 워크로드다&lt;span&gt;. &lt;/span&gt;박스가 작으니 한 컴퓨트 디스패치로 충분히 들어가고&lt;span&gt;, &lt;/span&gt;격자 한 칸당 한 스레드 &amp;rarr;&lt;span&gt; atomic&lt;/span&gt;이 거의 필요 없는 두 패스 구조 &amp;rarr; 결과는 작은 정점 버퍼 한 묶음&lt;span&gt;. UE 5.8 &lt;/span&gt;첫 출시에서는&lt;span&gt; CPU &lt;/span&gt;베이크로 깔린 듯하지만&lt;span&gt;, &lt;/span&gt;후속 릴리스에서&lt;span&gt; GPU &lt;/span&gt;베이크로 내려갈 가능성이 가장 높은 자리다 &amp;mdash; 알고리즘 자체가 그렇게 설계되어 있고&lt;span&gt;, MC &lt;/span&gt;가족&lt;span&gt; 30&lt;/span&gt;년 노하우가 이미&lt;span&gt; GPU &lt;/span&gt;구현을 충분히 다듬어 두었다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;실전 &amp;mdash; 한 번 직접 만져보면 보이는 것&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘 글이 가장 빨리 흡수되는 자리는 결국 자기 손으로 한 번 만져본 다음이다&lt;span&gt;. UE 5.8&lt;/span&gt;을 설치할 수 있다면 다음 짧은 시나리오로 마칭큐브가 호출되는 순간을 직접 확인할 수 있다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;1.&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;새 레벨에&lt;span&gt; Mesh Terrain &lt;/span&gt;추가&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;에디터 모드 패널에서 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshTerrainMode&lt;/span&gt;를 켠 다음&lt;span&gt; Create &lt;/span&gt;서브모드로 작은 패치 두 개를 만든다&lt;span&gt;. &lt;/span&gt;절벽처럼 가파른 경사가 좋다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;2.&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;두 패치를 가까이&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;두 절벽이 서로 마주 보도록 위치를 조정한다&lt;span&gt;. &lt;/span&gt;이 시점까지는 마칭큐브가 호출되지 않는다 &amp;mdash; 그냥 두 개의 독립 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;FDynamicMesh3&lt;/span&gt;다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;3.&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;Edit &lt;/span&gt;또는&lt;span&gt; Sculpt &lt;/span&gt;서브모드로 합치기&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;두 절벽 사이에 브러시로 채우거나&lt;span&gt; &quot;merge&quot; &lt;/span&gt;작업을 트리거한다&lt;span&gt;. &lt;/span&gt;이 순간 에디터가&lt;span&gt; &quot;Baking&lt;/span&gt;&amp;hellip;&lt;span&gt;&quot; &lt;/span&gt;표시를 잠깐 띄울 텐데&lt;span&gt;, &lt;/span&gt;그 짧은 시간 안에 복셀화&lt;span&gt; + &lt;/span&gt;협대역&lt;span&gt; SDF + &lt;/span&gt;마칭큐브&lt;span&gt; + &lt;/span&gt;메시 재구성이 차례로 일어난다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;4.&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;결과 메시 확인&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;와이어프레임 모드로 보면 합쳐진 영역의 삼각형 토폴로지가 원본 절벽 두 개와 완전히 다른 &amp;mdash; 마칭큐브 특유의 격자&lt;span&gt;-aligned &lt;/span&gt;삼각형 &amp;mdash; 으로 바뀐 게 보인다&lt;span&gt;. &lt;/span&gt;격자 해상도&lt;span&gt;(&lt;/span&gt;에디터 설정에서 조절 가능&lt;span&gt;)&lt;/span&gt;를 올리면 더 부드러운 곡면이 나오고&lt;span&gt;, &lt;/span&gt;내리면 격자 흔적이 더 도드라진다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;5.&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;언두&lt;span&gt;/&lt;/span&gt;리두&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;합치기 결과는 한 번의 베이크 결과이므로&lt;span&gt; undo&lt;/span&gt;로 되돌릴 수 있다&lt;span&gt;. &lt;/span&gt;마칭큐브 호출은 비결정적이지 않으니&lt;span&gt;(&lt;/span&gt;같은 입력 &amp;rarr; 같은 출력&lt;span&gt;) &lt;/span&gt;같은 자리를 여러 번 만져도 결과가 떨리지 않는다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 한 시나리오를 한 번 돌려보고 와이어프레임을 한 번 확인하고 나면&lt;span&gt;, Ch2-3&lt;/span&gt;의 모든 추상 절차가&lt;span&gt; &quot;&lt;/span&gt;내가 방금 본 그 격자 위의 그 삼각형들&lt;span&gt;&quot;&lt;/span&gt;로 머릿속에 박힌다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;정리 &amp;mdash; 이 글이 끝나는 자리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 마칭큐브의 핵심 &amp;mdash;&lt;span&gt; 8&lt;/span&gt;비트 인덱스&lt;span&gt;, &lt;/span&gt;두 장의 테이블&lt;span&gt;, &lt;/span&gt;보간&lt;span&gt;, &lt;/span&gt;모호성&lt;span&gt;, &lt;/span&gt;비교&lt;span&gt;, &lt;/span&gt;성능 &amp;mdash; 을 한 번에 풀고&lt;span&gt;, &lt;/span&gt;그게&lt;span&gt; UE 5.8 &lt;/span&gt;메시 테레인의 한 자리에 어떻게 박혀 있는지까지 따라왔다&lt;span&gt;. &lt;/span&gt;처음 읽었을 때 한 줄로 지나친&lt;span&gt; &quot;&lt;/span&gt;복셀화&lt;span&gt; + &lt;/span&gt;마칭큐브&lt;span&gt; + &lt;/span&gt;협대역&lt;span&gt; SDF&quot; &lt;/span&gt;표현이&lt;span&gt;, &lt;/span&gt;이제는 왜 그 세 단계의 순서로 묶여 있고&lt;span&gt;, &lt;/span&gt;왜 에디터 전용인지가 보일 것이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;남은 자리는 두 군데다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;런타임 동적 편집&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;후속 비용을 엔진이 흡수하기 시작하면 마칭큐브가 런타임으로 내려올 수 있다&lt;span&gt;. UE 5.x&lt;/span&gt;의 향후 릴리스를 기다리거나&lt;span&gt;, Voxel Tools / Voxel Farm &lt;/span&gt;류의 서드파티로 우회하면 된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;날카로운 모서리&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; MC&lt;/span&gt;는 항상 부드러운 메시를 뽑으니&lt;span&gt;, &lt;/span&gt;날카로운 모서리가 필요한 자리는&lt;span&gt; Dual Contouring &lt;/span&gt;또는 그 변종&lt;span&gt;(Manifold DC, Cubical MC)&lt;/span&gt;이 더 적합하다&lt;span&gt;. &lt;/span&gt;마찬가지로 서드파티 영역이거나&lt;span&gt;, &lt;/span&gt;직접 구현해야 하는 자리다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마칭큐브는&lt;span&gt; 1987&lt;/span&gt;년 논문 한 편으로 시작해서&lt;span&gt; 30&lt;/span&gt;년 넘게 표준 자리를 지킨&lt;span&gt;, &lt;/span&gt;흔치 않은 사례의 알고리즘이다&lt;span&gt;. &lt;/span&gt;단순한 룩업 두 번이 정말로 메시를 토해낸다는 사실 &amp;mdash; 그 단순함이 모든 후속 변종이 결국 돌아오는 기준점을 만들었다&lt;span&gt;. &lt;/span&gt;다음에 어디서든 등치면 추출이라는 단어를 만나면&lt;span&gt;, &lt;/span&gt;그 자리는 거의 항상 마칭큐브거나 그 가족이다&lt;span&gt;. &lt;/span&gt;그리고 이제 그 자리를 다시 만났을 때&lt;span&gt;, &lt;/span&gt;한 큐브 안에서 무엇이 일어나는지가 한 번에 떠오를 것이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;</description>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/173</guid>
      <comments>https://develop-4-art.tistory.com/173#entry173comment</comments>
      <pubDate>Fri, 29 May 2026 01:37:43 +0900</pubDate>
    </item>
    <item>
      <title>[Graphics] 복셀 마칭큐브와 Mesh Terrain - 3</title>
      <link>https://develop-4-art.tistory.com/172</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://develop-4-art.tistory.com/170&quot;&gt;[Graphics] 복셀 마칭큐브와 Mesh Terrain - 1&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://develop-4-art.tistory.com/171&quot;&gt;[Graphics] 복셀 마칭큐브와 Mesh Terrain - 2&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://develop-4-art.tistory.com/173&quot;&gt;[Graphics] 복셀 마칭큐브와 Mesh Terrain - 4&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Ch3. &lt;/span&gt;딥다이브 &amp;mdash; 인덱싱 &amp;rarr; 테이블 &amp;rarr; 보간 &amp;rarr; 모호성 &amp;rarr; 비교 &amp;rarr; 성능&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 챕터는 한 큐브 안에서 마칭큐브가 실제로 무슨 일을 하는지를 끝까지 푼다&lt;span&gt;. 8&lt;/span&gt;개 안&lt;span&gt;/&lt;/span&gt;밖 비트가 어떻게&lt;span&gt; 8&lt;/span&gt;비트 정수 하나로 압축되고&lt;span&gt;, &lt;/span&gt;그 정수가 어떻게 엣지 테이블&amp;middot;삼각형 테이블이라는 두 장의 미리 계산된 룩업표를 한 번씩 조회해서 삼각형을 토해내는지&lt;span&gt;, &lt;/span&gt;보간이 어떻게 정확한 위치를 잡는지&lt;span&gt;, &lt;/span&gt;그리고&lt;span&gt; 1987&lt;/span&gt;년부터 따라다닌 모호성&lt;span&gt;(ambiguity) &lt;/span&gt;케이스가 왜 위상 결정을 흔드는지까지가 한 묶음이다&lt;span&gt;. &lt;/span&gt;마지막에는&lt;span&gt; Dual Contouring / Surface Nets &lt;/span&gt;같은 사촌 알고리즘들과의 짧은 비교&lt;span&gt;, &lt;/span&gt;그리고 성능&amp;middot;메모리 실전 고려사항으로 마무리한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;8&lt;/span&gt;비트 인덱스 &amp;mdash; 한 칸을 한 정수로 압축&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Ch2&lt;/span&gt;에서 정한 인덱싱 컨벤션을 그대로 들고 온다&lt;span&gt;. V_i&lt;/span&gt;가 안이면&lt;span&gt; i&lt;/span&gt;번 비트가&lt;span&gt; 1&lt;/span&gt;이라는 규칙으로&lt;span&gt;, &lt;/span&gt;한 큐브의 안&lt;span&gt;/&lt;/span&gt;밖 패턴은&lt;span&gt; 0&lt;/span&gt;&amp;hellip;&lt;span&gt;255 &lt;/span&gt;사이의 정수 하나로 압축된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;cubeIndex = 0&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;for i in 0..7:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;if scalar[V_i] &amp;lt; iso:&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;cubeIndex |= (1 &amp;lt;&amp;lt; i)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 부등호 방향은 컨벤션이다&lt;span&gt;. &lt;/span&gt;이 글에서는 &lt;b&gt;&lt;span&gt;&quot;&lt;/span&gt;안&lt;span&gt; = &lt;/span&gt;값이 등치값보다 작다&lt;span&gt;&quot;&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;를 일관되게 쓴다&lt;span&gt;(Bourke &lt;/span&gt;코드와 동일&lt;span&gt;, OpenVDB&lt;/span&gt;와 동일&lt;span&gt;). &lt;/span&gt;부호 있는 거리 장&lt;span&gt;(SDF)&lt;/span&gt;을 쓰면&lt;span&gt; &quot;&lt;/span&gt;안&lt;span&gt; = &lt;/span&gt;음수&lt;span&gt;&quot;&lt;/span&gt;가 되니 같은 의미가 된다&lt;span&gt;. &lt;/span&gt;부호를 통째로 뒤집은 컨벤션을 쓰는 구현체도 있는데&lt;span&gt;(&lt;/span&gt;특히 의료 영상의 일부 코드&lt;span&gt;), &lt;/span&gt;그 경우 결과 메시의 노멀이 뒤집힌 채로 나온다&lt;span&gt;. &lt;/span&gt;정점이 같은 자리에 찍히므로 메시 자체는 옳고&lt;span&gt;, &lt;/span&gt;보통 노멀만 부호 뒤집어 처리하면 끝이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;256&lt;/span&gt;개 케이스가 나오지만&lt;span&gt;, &lt;/span&gt;회전&lt;span&gt;(&lt;/span&gt;큐브의 회전 대칭군은&lt;span&gt; 24&lt;/span&gt;개&lt;span&gt;)&lt;/span&gt;과 반사 대칭으로 묶으면 &lt;b&gt;&lt;span&gt;15&lt;/span&gt;개 고유 케이스&lt;/b&gt;로 줄어든다&lt;span&gt;. 1987&lt;/span&gt;년&lt;span&gt; Lorensen &amp;amp; Cline&lt;/span&gt;의&lt;span&gt; SIGGRAPH &lt;/span&gt;원논문이 처음 분류한 것도 정확히 이&lt;span&gt; 15&lt;/span&gt;개다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJJcYz/dJMcaaS0xRl/Gj8YrpPmKxp5aIm4HUkgQK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJJcYz/dJMcaaS0xRl/Gj8YrpPmKxp5aIm4HUkgQK/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJJcYz/dJMcaaS0xRl/Gj8YrpPmKxp5aIm4HUkgQK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bJJcYz/dJMcaaS0xRl/Gj8YrpPmKxp5aIm4HUkgQK/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;560&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;700&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[IMAGE 08 &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;mdash;&lt;span&gt; 15 unique cases (Python): 4&lt;/span&gt;&amp;times;&lt;span&gt;4 grid (15 + blank), &lt;/span&gt;각&lt;span&gt; cell&lt;/span&gt;은&lt;span&gt; isometric &lt;/span&gt;큐브&lt;span&gt; + &lt;/span&gt;안쪽 정점 검정 동그라미&lt;span&gt; + &lt;/span&gt;등치 삼각형 빨강&lt;span&gt;. Case 0(&lt;/span&gt;전부 밖&lt;span&gt;) ~ Case 14 &lt;/span&gt;라벨&lt;span&gt;. Paul Bourke&lt;/span&gt;의&lt;span&gt; polygonising &lt;/span&gt;테이블을 독립 렌더링&lt;span&gt; (Lorensen Fig 4 &lt;/span&gt;트레이스 아님&lt;span&gt;).]&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&lt;span&gt; 15 &lt;/span&gt;케이스를 회전&amp;middot;반사로 펼치면 다시&lt;span&gt; 256&lt;/span&gt;개 케이스가 모두 나오는데&lt;span&gt;, &lt;/span&gt;&lt;b&gt;실제 구현은 그&lt;span&gt; 256&lt;/span&gt;개를 미리 펼쳐서 두 장의 표에 박아 놓는다&lt;/b&gt;&lt;span&gt;. &lt;/span&gt;런타임에 회전 행렬을 곱할 필요가 없으니 룩업 한 번이 진짜로 룩업 한 번이다&lt;span&gt;. &lt;/span&gt;그 두 장의 표가 다음 절에서 만날 엣지 테이블과 삼각형 테이블이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;두 장의 룩업 표 &amp;mdash; 엣지 테이블&lt;span&gt; &amp;amp; &lt;/span&gt;삼각형 테이블&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;엣지 테이블 &amp;mdash;&lt;span&gt; &quot;&lt;/span&gt;어느 엣지가 교차되었나&lt;span&gt;&quot;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엣지 테이블은 길이&lt;span&gt; 256&lt;/span&gt;의 배열이고&lt;span&gt;, &lt;/span&gt;각 원소는&lt;span&gt; 12&lt;/span&gt;비트 마스크다&lt;span&gt;. cubeIndex&lt;/span&gt;로 한 번 인덱싱하면 그 큐브에서 등치면이 가로지르는&lt;span&gt; 12&lt;/span&gt;개 엣지 중 어느 것이 활성인지가 한 비트씩 켜져 있다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;// &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;발췌&lt;span&gt;: paulbourke.net/geometry/polygonise&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;int edgeTable[256] = {&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;0x0,&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;/span&gt;0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;// ... (&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;총&lt;span&gt; 256&lt;/span&gt;개&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;};&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;int edgeMask = edgeTable[cubeIndex];&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;if (edgeMask == 0) return;&lt;span&gt;&amp;nbsp; &lt;/span&gt;// &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;전부 안이거나 전부 밖&lt;span&gt;, &lt;/span&gt;삼각형 없음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;edgeMask &amp;amp; (1 &amp;lt;&amp;lt; e) [0001 0000 1001]&lt;/span&gt;가 켜져 있으면&lt;span&gt; E_e &lt;/span&gt;엣지에 등치면이 교차한다는 뜻이다&lt;span&gt;. &lt;/span&gt;그 자리에 보간 교점을 찍어둔다&lt;span&gt;(&lt;/span&gt;자세한 보간 공식은 다음 절&lt;span&gt;).&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &lt;span style=&quot;color: #0a0a0a; text-align: start;&quot;&gt;edgeMask &amp;amp; (1 &amp;lt;&amp;lt; e) [0001 0000 1001]&lt;/span&gt; 의 시각적의미 : &lt;br /&gt;V0(물 속)에서 출발해서 물 밖의 꼭짓점으로 향하는 엣지는 딱 3개뿐입니다. V1으로 가는 &lt;b data-index-in-node=&quot;60&quot; data-path-to-node=&quot;9,3,0&quot;&gt;E0&lt;/b&gt;, V3로 가는 &lt;b data-index-in-node=&quot;71&quot; data-path-to-node=&quot;9,3,0&quot;&gt;E3&lt;/b&gt;, V4로 올라가는 &lt;b data-index-in-node=&quot;84&quot; data-path-to-node=&quot;9,3,0&quot;&gt;E8&lt;/b&gt;입니다. &lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;삼각형 테이블 &amp;mdash;&lt;span&gt; &quot;&lt;/span&gt;어느 엣지들을 잇는가&lt;span&gt;&quot;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삼각형 테이블도 길이&lt;span&gt; 256&lt;/span&gt;이지만&lt;span&gt;, &lt;/span&gt;각 원소는 길이&lt;span&gt; 16&lt;/span&gt;의 정수 배열이다&lt;span&gt;. &lt;b&gt;3&lt;/b&gt;&lt;/span&gt;&lt;b&gt;개씩 묶이는 엣지 인덱스 트리플&lt;/b&gt;의 나열이고&lt;span&gt;, &lt;/span&gt;끝은 &amp;minus;&lt;span&gt;1&lt;/span&gt;로 종료된다&lt;span&gt;. &lt;/span&gt;한 큐브에서 최대&lt;span&gt; 5&lt;/span&gt;개의 삼각형이 나올 수 있으므로&lt;span&gt; 15&lt;/span&gt;개의 엣지 인덱스&lt;span&gt; + &lt;/span&gt;종료 &amp;minus;&lt;span&gt;1&lt;/span&gt;로&lt;span&gt; 16&lt;/span&gt;칸이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;( 엣지 테이블을 통해 교차하는 엣지들을 찾아 그 위에 점(교점)을 찍었다면, 삼각형 테이블은 &quot;그 점들을 어떤 순서로 이어서 삼각형(폴리곤)을 만들 것인가?&quot;를 알려줍니다. )&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;// &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;예&lt;span&gt;: cubeIndex == 0xe (V1,V2,V3&lt;/span&gt;가 안&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;// triTable[0xe] = { 9, 8, 3,&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;/span&gt;9, 3, 2,&lt;span&gt;&amp;nbsp; &lt;/span&gt;9, 2, 11, -1, ... }&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;// &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&amp;rarr; 삼각형&lt;span&gt; 3&lt;/span&gt;개&lt;span&gt;: (E9, E8, E3), (E9, E3, E2), (E9, E2, E11)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;for (int i = 0; triTable[cubeIndex][i] != -1; i += 3) {&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;addTriangle(vertOnEdge[triTable[cubeIndex][i + 0]],&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;vertOnEdge[triTable[cubeIndex][i + 1]],&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;vertOnEdge[triTable[cubeIndex][i + 2]]);&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;}&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;vertOnEdge[e]&lt;/span&gt;는 엣지 테이블 단계에서 미리 계산해 둔&lt;span&gt;, E_e &lt;/span&gt;위의 보간 교점이다&lt;span&gt;. &lt;/span&gt;이 두 단계를 합치면 한 큐브에서&lt;span&gt; 0~5&lt;/span&gt;개의 삼각형이 나온다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12&quot;&gt;[시각적 예시: 이어서 삼각형 만들기]&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;상황:&lt;/b&gt; 위의 예시와 동일하게 cubeIndex가 1인 상황입니다. 교차점이 생긴 엣지는 E0, E3, E8입니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;테이블 조회:&lt;/b&gt; triTable[1]을 조회하면 결과 배열은 [0, 8, 3, -1]로 나옵니다. (-1은 배열의 끝을 의미합니다.)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,2,0&quot;&gt;시각적 의미:&lt;/b&gt; * 첫 번째 교점: E0 엣지 위의 점
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;13,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 번째 교점: E8 엣지 위의 점&lt;/li&gt;
&lt;li&gt;세 번째 교점: E3 엣지 위의 점&lt;/li&gt;
&lt;li&gt;이 세 점을 선으로 쫙 이어붙이면 V0 꼭짓점 하나만 싹둑 잘라내는 형태의 &lt;b data-index-in-node=&quot;42&quot; data-path-to-node=&quot;13,2,1,2,0&quot;&gt;작은 삼각형 1개&lt;/b&gt;가 허공에 그려집니다. 이것이 렌더링될 메시의 한 조각입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ephg93/dJMcagTdX4h/mcnia7q4yICineF4JmuEik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ephg93/dJMcagTdX4h/mcnia7q4yICineF4JmuEik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ephg93/dJMcagTdX4h/mcnia7q4yICineF4JmuEik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fephg93%2FdJMcagTdX4h%2Fmcnia7q4yICineF4JmuEik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;938&quot; height=&quot;391&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;391&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[IMAGE 09 &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;mdash; 테이블 룩업 흐름&lt;span&gt; (SVG): cubeIndex(8-bit) &lt;/span&gt;&amp;rarr;&lt;span&gt; edgeTable &lt;/span&gt;&amp;rarr; 활성 엣지 마스크&lt;span&gt;(12-bit) &lt;/span&gt;&amp;rarr;&lt;span&gt; vertOnEdge &lt;/span&gt;보간 &amp;rarr;&lt;span&gt; triTable &lt;/span&gt;&amp;rarr; 삼각형 트리플&lt;span&gt;. &lt;/span&gt;화살표&lt;span&gt; + &lt;/span&gt;작은 큐브 픽토그램&lt;span&gt;.]&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;룩업 한 번이 끝나면 그 큐브의 메시 기여분은 결정이다&lt;span&gt;. &lt;/span&gt;격자 전체에 같은 절차를 마칭하면 전역 메시가 완성된다&lt;span&gt;. &lt;/span&gt;데이터 의존성이 한 큐브 안으로 닫혀 있다는 점이&lt;span&gt; GPU &lt;/span&gt;병렬화의 자유도를 그대로 보장한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;엣지 보간 &amp;mdash; 정확한 교점 찍기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엣지 테이블이&lt;span&gt; &quot;&lt;/span&gt;어느 엣지가 활성&lt;span&gt;&quot;&lt;/span&gt;인지만 알려준다면&lt;span&gt;, &lt;/span&gt;그 엣지 위 &lt;b&gt;어디&lt;/b&gt;에 정점을 찍을지는 보간이 결정한다&lt;span&gt;. &lt;/span&gt;등치값보다 작은 끝과 큰 끝 사이를 선형 보간한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;v0&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;iso&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;v1&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;●&lt;span&gt;----------|-------------&lt;/span&gt;●&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;p0&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;p&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;p1&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;t = (iso - v0) / (v1 - v0)&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;// &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&amp;isin;&lt;span&gt; [0, 1]&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;p = p0 + t * (p1 - p0)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;v0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;v1&lt;/span&gt;은 엣지 양끝의 스칼라 값&lt;span&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;p0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;p1&lt;/span&gt;은 양끝의 좌표&lt;span&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;iso&lt;/span&gt;는 등치값&lt;span&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;t&lt;/span&gt;는 보간 파라미터다&lt;span&gt;. &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;iso == v0&lt;/span&gt;이면 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;t == 0&lt;/span&gt;이라 정점은 정확히 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;p0&lt;/span&gt;에 떨어지고&lt;span&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;iso == v1&lt;/span&gt;이면 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;t == 1&lt;/span&gt;이라 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;p1&lt;/span&gt;에 떨어진다&lt;span&gt;. &lt;/span&gt;&lt;b&gt;수치적으로&lt;span&gt; v0 == v1&lt;/span&gt;&lt;/b&gt;인 자리는&lt;span&gt; 0&lt;/span&gt;으로 나누기가 되니&lt;span&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;t = 0.5&lt;/span&gt;로 폴백하거나 작은 엡실론 가드를 두는 게 보통이다&lt;span&gt;(&lt;/span&gt;그 자리는 등치값이 끝과 정확히 같다는 의미라 어느 위치를 골라도 거의 동일하다&lt;span&gt;).&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Atkzn/dJMcagTdX4u/9Fn6vUIpRxV6as9kw2mtQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Atkzn/dJMcagTdX4u/9Fn6vUIpRxV6as9kw2mtQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Atkzn/dJMcagTdX4u/9Fn6vUIpRxV6as9kw2mtQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAtkzn%2FdJMcagTdX4u%2F9Fn6vUIpRxV6as9kw2mtQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;875&quot; height=&quot;500&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[IMAGE 10 &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;mdash; 엣지 보간&lt;span&gt; (SVG): &lt;/span&gt;한 엣지 수평선&lt;span&gt;, &lt;/span&gt;왼쪽 끝&lt;span&gt; v0=-0.4, &lt;/span&gt;오른쪽 끝&lt;span&gt; v1=+0.8, iso=0 &lt;/span&gt;표시&lt;span&gt;. &lt;/span&gt;보간&lt;span&gt; t = 0.4/1.2 = 0.333 &lt;/span&gt;&amp;rarr;&lt;span&gt; p = p0 + 0.333*(p1-p0) &lt;/span&gt;위치에 빨강 점&lt;span&gt;. &lt;/span&gt;수식 박스 인접 배치&lt;span&gt;.]&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선형 보간을 쓰는 것은 한 큐브 내부에서 스칼라 장이 &lt;b&gt;선형으로 변한다고 가정&lt;/b&gt;하기 때문이다&lt;span&gt;. &lt;/span&gt;실제 장은 일반적으로 비선형이지만&lt;span&gt;, &lt;/span&gt;한 칸이 충분히 작으면 그 안에서의 선형 근사가 만들어내는 오차는 무시할 만한 수준이고&lt;span&gt;, &lt;/span&gt;등치면 자체의 위상은 정확히 보존된다&lt;span&gt;. &lt;/span&gt;더 정확한 위치가 필요하면 &lt;b&gt;격자 해상도를 올리는 것이 보간 차수를 올리는 것보다 거의 항상 더 효과적&lt;/b&gt;이다 &amp;mdash; 이 점은 마칭큐브 가족의 거의 모든 후속 논문이 공유하는 결론이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;모호성 케이스 &amp;mdash; 면 모호성&lt;span&gt;, &lt;/span&gt;내부 모호성&lt;span&gt;, dual surface&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Lorensen &amp;amp; Cline&lt;/span&gt;의 원논문은&lt;span&gt; 15 &lt;/span&gt;케이스로 깔끔하게 끝나지만&lt;span&gt;, &lt;/span&gt;그 분류 안에는 &lt;b&gt;위상적으로 잘못된 메시를 만들 수 있는 케이스&lt;/b&gt;가 숨어 있다&lt;span&gt;. D&amp;uuml;rst&lt;/span&gt;가&lt;span&gt; 1988&lt;/span&gt;년에 처음 지적했고&lt;span&gt;, &lt;/span&gt;그 이후&lt;span&gt; 30&lt;/span&gt;년 동안 수많은 변종 논문이 이 자리를 두드렸다&lt;span&gt;. &lt;/span&gt;핵심 케이스는 네 자리 &amp;mdash; &lt;b&gt;&lt;span&gt;6, 10, 12, 13&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;&amp;mdash; 이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Case 6 &lt;/span&gt;&amp;mdash; 면&lt;span&gt;(face) &lt;/span&gt;모호성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 큐브의 어떤 한 면&lt;span&gt;(face)&lt;/span&gt;에서&lt;span&gt;, &lt;/span&gt;그 면의&lt;span&gt; 4 &lt;/span&gt;꼭짓점이 대각선으로 안&lt;span&gt;-&lt;/span&gt;밖이 갈리는 패턴이 나타나면 면 모호성이 발생한다&lt;span&gt;. &lt;/span&gt;그 면을 가로지르는 등치선을 두 가지 합법적인 방식으로 그릴 수 있고&lt;span&gt;, &lt;/span&gt;이웃 큐브의 같은 면에서 어느 쪽을 골랐는지와 어긋나면 두 큐브 사이의 메시에 구멍이 뚫린다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;469&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dwjCPR/dJMcagTdX4w/35I2CVjbMB9hgIhWMlsQd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dwjCPR/dJMcagTdX4w/35I2CVjbMB9hgIhWMlsQd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dwjCPR/dJMcagTdX4w/35I2CVjbMB9hgIhWMlsQd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdwjCPR%2FdJMcagTdX4w%2F35I2CVjbMB9hgIhWMlsQd1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;875&quot; height=&quot;469&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;469&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[IMAGE 11a &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;mdash; 면 모호성&lt;span&gt; (Case 6) (SVG): &lt;/span&gt;같은&lt;span&gt; 8&lt;/span&gt;버텍스 패턴&lt;span&gt;(&lt;/span&gt;예&lt;span&gt;: V1+V2 &lt;/span&gt;안&lt;span&gt;)&lt;/span&gt;에서 두 가지 합법 삼각화&lt;span&gt; A&lt;/span&gt;&amp;middot;&lt;span&gt;B&lt;/span&gt;를 좌&amp;middot;우 나란히&lt;span&gt;. &lt;/span&gt;면 가운데 점선으로&lt;span&gt; &quot;&lt;/span&gt;두 합법 곡선&lt;span&gt;&quot; &lt;/span&gt;강조&lt;span&gt;.]&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Case 6&lt;/span&gt;은 마칭 스퀘어의&lt;span&gt; Case D(saddle)&lt;/span&gt;가&lt;span&gt; 3D&lt;/span&gt;로 올라온 직접 후예다&lt;span&gt;. &lt;/span&gt;해결법은 그 면의 중앙에 보간된 스칼라 값을 따로 평가해서 위상을 결정하는 &lt;b&gt;&lt;span&gt;asymptotic decider[등가면 테스트]&lt;/span&gt;&lt;/b&gt;&lt;span&gt;(Nielson &amp;amp; Hamann, 1991)&lt;/span&gt;다&lt;span&gt;. &lt;/span&gt;같은 결정 함수를 양쪽 큐브가 똑같이 적용하므로 자동으로 일관된 선택이 나온다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Case 13 &lt;/span&gt;&amp;mdash; 내부&lt;span&gt;(internal) &lt;/span&gt;모호성과&lt;span&gt; dual surface&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Case 13&lt;/span&gt;은 더 까다롭다&lt;span&gt;. &lt;/span&gt;큐브의&lt;span&gt; 8 &lt;/span&gt;꼭짓점이 대각선 쌍으로 안&lt;span&gt;-&lt;/span&gt;밖이 갈리는 패턴인데&lt;span&gt;, &lt;/span&gt;이 경우 큐브 &lt;b&gt;내부에서&lt;/b&gt; 등치면을 두 가지 위상으로 그릴 수 있다&lt;span&gt;. &lt;/span&gt;한쪽은 두 갈래 표면이 큐브를 관통하며 갈라지는 모양이고&lt;span&gt;, &lt;/span&gt;다른 쪽은 두 표면이 큐브 내부에서 좁은 터널로 이어지는 모양이다&lt;span&gt;. &lt;/span&gt;같은 안&lt;span&gt;/&lt;/span&gt;밖 비트가 &lt;b&gt;연결된 메시&lt;span&gt; vs &lt;/span&gt;분리된 메시&lt;/b&gt;라는 완전히 다른 위상을 만든다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;469&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B0fj4/dJMb99T4KwB/mT3V4N9PNd5aQKHkWJYIa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B0fj4/dJMb99T4KwB/mT3V4N9PNd5aQKHkWJYIa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B0fj4/dJMb99T4KwB/mT3V4N9PNd5aQKHkWJYIa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB0fj4%2FdJMb99T4KwB%2FmT3V4N9PNd5aQKHkWJYIa1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;875&quot; height=&quot;469&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;469&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[IMAGE 11b &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;mdash; 내부 모호성&lt;span&gt; (Case 13) (SVG): &lt;/span&gt;동일&lt;span&gt; 8&lt;/span&gt;버텍스 패턴&lt;span&gt;(&lt;/span&gt;대각 쌍&lt;span&gt;)&lt;/span&gt;에서 좌측은 두 표면 분리&lt;span&gt;, &lt;/span&gt;우측은 좁은 터널로 연결&lt;span&gt;. &quot;dual surface&quot; &lt;/span&gt;라벨&lt;span&gt;.]&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&lt;span&gt; dual surface &lt;/span&gt;문제를 가장 정직하게 다루는 길은 큐브 &lt;b&gt;내부 중심&lt;/b&gt;의 보간된 스칼라 값까지 동원해서 위상을 결정하는 것이고&lt;span&gt;, Asymptotic Decider&lt;/span&gt;의 확장 버전 또는&lt;span&gt; Nielson&lt;/span&gt;의&lt;span&gt; &quot;MC33&quot;&lt;/span&gt;이라 불리는&lt;span&gt; 33&lt;/span&gt;케이스 확장 분류가 표준 해법이다&lt;span&gt;(&lt;/span&gt;원래&lt;span&gt; 15 &lt;/span&gt;케이스 안에 모호성 케이스들을 추가 분기로 풀어&lt;span&gt; 33&lt;/span&gt;개로 다시 묶는다&lt;span&gt;). Case 10, 12&lt;/span&gt;는&lt;span&gt; 6 / 13&lt;/span&gt;의 사촌으로&lt;span&gt;, &lt;/span&gt;같은 가족 안에서 처리된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Asymptotic Decider &lt;/span&gt;공식&lt;span&gt; (&lt;/span&gt;스니펫&lt;span&gt;)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면 모호성 케이스 한 자리에서 실제로 어떻게 결정하는지만 한 번 보고 가자&lt;span&gt;. &lt;/span&gt;한 면의&lt;span&gt; 4 &lt;/span&gt;꼭짓점 값을 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;a, b, c, d&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;좌하&amp;middot;우하&amp;middot;우상&amp;middot;좌상 순&lt;span&gt;)&lt;/span&gt;라 하면&lt;span&gt;, &lt;/span&gt;그 면을 가로지르는 두 등치선의 점근&lt;span&gt;(asymptote) &lt;/span&gt;교점에서 평가한 스칼라 값은&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;f_center = (a * c - b * d) / (a + c - b - d)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이고&lt;span&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;f_center&lt;/span&gt;의 부호가 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;iso&lt;/span&gt;보다 크냐 작냐로 두 가지 합법 연결 중 하나를 고른다&lt;span&gt;. &lt;/span&gt;이웃 큐브의 같은 면도 같은&lt;span&gt; 4 &lt;/span&gt;꼭짓점을 공유하므로 같은 값을 얻고&lt;span&gt;, &lt;/span&gt;&lt;b&gt;결과적으로 두 큐브가 자동으로 같은 연결을 선택&lt;/b&gt;한다 &amp;mdash; 추가 통신 없이 일관성이 보장되는 점이 이 방법의 우아한 점이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Case 13 &lt;/span&gt;같은 내부 모호성도 같은 아이디어를 큐브 중심으로 확장한 결정 함수로 풀린다&lt;span&gt;. &lt;/span&gt;디테일은&lt;span&gt; Nielson &amp;amp; Hamann (1991) &lt;/span&gt;원논문과&lt;span&gt; MC33 &lt;/span&gt;분류표&lt;span&gt;(Chernyaev 1995, Lewiner et al. 2003)&lt;/span&gt;가 표준 참고문헌이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;수치 안정성과 퇴화 케이스&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;v0 == v1&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;앞서 언급한&lt;span&gt; 0 &lt;/span&gt;나누기&lt;span&gt;), &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;v_i == iso&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;꼭짓점이 정확히 등치값 위에 있음&lt;span&gt;), &lt;/span&gt;그리고 한 큐브에서 여러 정점이 같은 좌표로 떨어져 &lt;b&gt;퇴화 삼각형&lt;/b&gt;&lt;span&gt;(degenerate triangle, &lt;/span&gt;넓이&lt;span&gt; 0)&lt;/span&gt;이 생기는 경우 &amp;mdash; 이 세 가지는 어떤 마칭큐브 구현체든 한 번씩은 부딪힌다&lt;span&gt;. &lt;/span&gt;표준 처리는 다음과 같다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;v0 == v1&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&amp;rarr; &lt;span style=&quot;color: #0a0a0a;&quot;&gt;t = 0.5&lt;/span&gt;로 폴백&lt;span&gt;(&lt;/span&gt;또는 작은 엡실론 가드&lt;span&gt;).&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;v_i == iso&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&amp;rarr; 안&lt;span&gt;/&lt;/span&gt;밖 컨벤션에 따라 한쪽으로 일관되게 분류&lt;span&gt;(&lt;/span&gt;보통&lt;span&gt; &quot;&lt;/span&gt;안&lt;span&gt;&quot;&lt;/span&gt;으로&lt;span&gt;). &lt;/span&gt;모든 큐브가 같은 규칙을 쓰면 인접 큐브 사이의 불일치가 없다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;퇴화 삼각형 &amp;rarr; 메시 등록 단계에서 넓이 검사로 필터링&lt;span&gt;. &lt;/span&gt;또는&lt;span&gt; BVH/&lt;/span&gt;충돌 메시 구축 시 자동으로 무시되도록 후처리&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 세 가지는 정확한 결정이 핵심이 아니라 &lt;b&gt;모든 큐브가 같은 규칙을 따른다는 일관성&lt;/b&gt;이 핵심이다&lt;span&gt;. &lt;/span&gt;한 큐브에서 안으로 분류한 정점이 옆 큐브에서 밖으로 분류되면 그 자리에 메시 구멍이 뚫린다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실용적으로는 게임용 절차적 지형처럼 위상이 약간 거짓말을 해도 시각적으로 큰 문제가 안 되는 자리에서는 &lt;b&gt;그냥&lt;span&gt; 15&lt;/span&gt;케이스 표를 쓰고 모호성을 무시&lt;/b&gt;한다&lt;span&gt;. &lt;/span&gt;의료 영상&lt;span&gt; / CAD&lt;/span&gt;처럼 정확한 위상이 중요하면&lt;span&gt; MC33 &lt;/span&gt;또는 그 후속 분류를 쓴다&lt;span&gt;. UE 5.8 &lt;/span&gt;메시 테레인은 게임 쪽이라 단순한 분류면 충분한데&lt;span&gt;, &quot;&lt;/span&gt;두 절벽을 합치는&lt;span&gt;&quot; &lt;/span&gt;자리는 사용자가 미리&lt;span&gt; SDF&lt;/span&gt;로 부드럽게 깔아두므로 모호성 자체가 잘 발생하지 않는다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;이웃 알고리즘 &amp;mdash;&lt;span&gt; Dual Contouring, Surface Nets&lt;/span&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마칭큐브가&lt;span&gt; 30&lt;/span&gt;년 넘게 표준 자리를 지킨 만큼&lt;span&gt;, &quot;&lt;/span&gt;마칭큐브의 약점을 노린&lt;span&gt;&quot; &lt;/span&gt;사촌 알고리즘들도 여럿이다&lt;span&gt;. &lt;/span&gt;두 가지만 짧게 비교한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; width=&quot;600&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;150&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;항목&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;150&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Marching Cubes&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;150&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Surface Nets (Gibson 1998)&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;150&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Dual Contouring (Ju et al. 2002)&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;정점 위치&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;엣지 위 보간 교점&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;큐브 중심&lt;span&gt; (&lt;/span&gt;격자 노드의 듀얼&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;큐브 내부&lt;span&gt;, &lt;/span&gt;헤시안 최적화&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;입력&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;스칼라 값&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;스칼라 값&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;스칼라 값&lt;span&gt; + &lt;/span&gt;&lt;b&gt;엣지 노멀&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;모호성 케이스&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;&lt;span&gt;6/10/12/13 (&lt;/span&gt;해결법 별도&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;거의 없음&lt;span&gt; (&lt;/span&gt;정점이 큐브당&lt;span&gt; 1&lt;/span&gt;개&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;거의 없음&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;날카로운 모서리&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;&lt;b&gt;표현 불가&lt;/b&gt;&lt;span&gt; (&lt;/span&gt;항상 부드러움&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;표현 약함&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;&lt;b&gt;표현 가능&lt;/b&gt;&lt;span&gt; (&lt;/span&gt;날카로운 특징 보존&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;구현 복잡도&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;낮음&lt;span&gt; (&lt;/span&gt;테이블 룩업&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;중간&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;높음&lt;span&gt; (QEF &lt;/span&gt;최적화&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;메시 토폴로지&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;매니폴드 보장 어려움&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;항상 매니폴드&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;150&quot;&gt;&lt;span&gt;매니폴드 보장 어려움&lt;span&gt; (Manifold DC &lt;/span&gt;변종 필요&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pFJH5/dJMcagFIIB9/1cckSQ0VMXrATDBqhnPTAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pFJH5/dJMcagFIIB9/1cckSQ0VMXrATDBqhnPTAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pFJH5/dJMcagFIIB9/1cckSQ0VMXrATDBqhnPTAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpFJH5%2FdJMcagFIIB9%2F1cckSQ0VMXrATDBqhnPTAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;938&quot; height=&quot;336&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[IMAGE 12 &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;mdash;&lt;span&gt; MC vs Surface Nets vs Dual Contouring (Python): &lt;/span&gt;같은&lt;span&gt; sharp-corner SDF &lt;/span&gt;입력에 대해 세 알고리즘의 출력 메시 와이어프레임&lt;span&gt; 3&lt;/span&gt;개 나란히&lt;span&gt;. MC&lt;/span&gt;는 모서리 둥글&lt;span&gt;, Surface Nets&lt;/span&gt;는 약간 더 부드러움&lt;span&gt;, DC&lt;/span&gt;는 모서리 보존&lt;span&gt;.]&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;UE 5.8 &lt;/span&gt;메시 테레인이&lt;span&gt; MC&lt;/span&gt;를 쓰는 이유는 명확하다&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &quot;&lt;/span&gt;두 절벽을 합치기&lt;span&gt;&quot;&lt;/span&gt;는 모서리 보존이 핵심이 아니라 &lt;b&gt;위상 변화가 부드럽게 일어나는 게&lt;/b&gt; 중요하다&lt;span&gt;. &lt;/span&gt;사용자가 만지작거리는 동안 메시가 매끄럽게 변형되는 게 보장돼야 하는 에디터 인터랙션이므로&lt;span&gt;, MC&lt;/span&gt;의 항상&lt;span&gt;-&lt;/span&gt;부드러운 출력이 오히려 장점이다&lt;span&gt;. &lt;/span&gt;날카로운 모서리는 따로 모델링하면 되고&lt;span&gt;, &lt;/span&gt;절벽의 자연스러운 곡면은&lt;span&gt; MC&lt;/span&gt;가 그대로 뽑아준다&lt;span&gt;. &lt;/span&gt;구현 복잡도가 낮아서 에디터 전용 베이크 코드로 깔기에도 부담이 적다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;성능 &amp;mdash; 메모리&lt;span&gt;, &lt;/span&gt;병렬화&lt;span&gt;, &lt;/span&gt;청크 베이크&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마칭큐브의 성능 특성은 한 문장으로 요약된다&lt;span&gt;: &lt;/span&gt;&lt;b&gt;창피할 만큼 병렬화가 잘 되지만&lt;span&gt;, &lt;/span&gt;메모리 대역폭이 보틀넥&lt;/b&gt;이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;연산 비용&lt;span&gt;:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;한 큐브당 룩업&lt;span&gt; 2&lt;/span&gt;번&lt;span&gt;(&lt;/span&gt;엣지 테이블&lt;span&gt; + &lt;/span&gt;삼각형 테이블&lt;span&gt;) + &lt;/span&gt;보간 최대&lt;span&gt; 12&lt;/span&gt;번&lt;span&gt; + &lt;/span&gt;삼각형 등록 최대&lt;span&gt; 5&lt;/span&gt;개&lt;span&gt;. &lt;/span&gt;분기는 거의 없고&lt;span&gt;, &lt;/span&gt;데이터 의존성이 한 큐브 안에 닫혀 있다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;메모리 비용&lt;span&gt;:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; N&lt;/span&gt;&amp;sup3; 격자라면 스칼라 장&lt;span&gt; N&lt;/span&gt;&amp;sup3;개를 들고 있어야 한다&lt;span&gt;. 8&lt;/span&gt;버텍스 샘플링은 인접 큐브&lt;span&gt; 7&lt;/span&gt;개와&lt;span&gt; 6&lt;/span&gt;면을 공유하므로&lt;span&gt;, &lt;/span&gt;캐시 친화적인&lt;span&gt; z-order &lt;/span&gt;또는 청크 단위 슬라이스 접근이 거의 필수다&lt;span&gt;. &lt;/span&gt;출력 메시도 케이스에 따라 한 큐브당&lt;span&gt; 0~15&lt;/span&gt;개의 정점&lt;span&gt;(&lt;/span&gt;중복 제거 전&lt;span&gt;)&lt;/span&gt;이 나오므로 메모리 예약은 보수적으로 잡는다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;GPU &lt;/span&gt;매핑&lt;span&gt;:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;한 큐브&lt;span&gt; = &lt;/span&gt;한 스레드 그룹의 한 스레드&lt;span&gt;. &lt;/span&gt;텍스처 또는&lt;span&gt; SSBO&lt;/span&gt;에 스칼라 장을 깔고&lt;span&gt;, append-buffer&lt;/span&gt;로 정점을 모은다&lt;span&gt;. &lt;/span&gt;정점 중복 제거&lt;span&gt;(&lt;/span&gt;엣지 인덱스 &amp;rarr; 정점 인덱스 해시&lt;span&gt;)&lt;/span&gt;는 두 번째 패스로 처리하는 게 보통이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;청크 베이크&lt;span&gt;:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; UE 5.8 &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshPartition&lt;/span&gt;처럼 큰 월드를 청크로 자른 다음 청크 단위로 베이크하면 메모리 부하가 분산되고&lt;span&gt;, &lt;/span&gt;청크 경계에서만 정점 공유를 별도 처리하면 된다&lt;span&gt;. &lt;/span&gt;청크 내부는 완전 독립 처리&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcHbNg/dJMcacpLtB2/Pxgtd6ZwkZgBjksrsqEE5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcHbNg/dJMcacpLtB2/Pxgtd6ZwkZgBjksrsqEE5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcHbNg/dJMcacpLtB2/Pxgtd6ZwkZgBjksrsqEE5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcHbNg%2FdJMcacpLtB2%2FPxgtd6ZwkZgBjksrsqEE5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;938&quot; height=&quot;516&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[IMAGE 13 &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;mdash; 성능 컷&lt;span&gt; (nanobanana &lt;/span&gt;일러스트&lt;span&gt;): &lt;/span&gt;거대한 복셀 그리드 위에 청크 단위로 마칭이 동시에 진행되는 추상 컷&lt;span&gt;. &lt;/span&gt;정량 데이터 아님&lt;span&gt;, &lt;/span&gt;분위기&lt;span&gt;.]&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실전 수치는 케이스에 따라 천차만별이지만&lt;span&gt;, 64&lt;/span&gt;&amp;sup3; 격자의 메시화는 데스크톱&lt;span&gt; CPU 1&lt;/span&gt;코어에서 수 밀리초 단위&lt;span&gt;, GPU&lt;/span&gt;에서는 마이크로초 단위로 떨어진다&lt;span&gt;. &lt;/span&gt;일반적인 게임 엔진에서 &lt;b&gt;에디터 베이크 단계&lt;/b&gt;의 비용은 거의 무시할 만하다&lt;span&gt;. &lt;/span&gt;런타임 동적 편집이 부담스러운 이유는 메시 자체보다도 &lt;b&gt;충돌 메시 재구성&lt;span&gt; + GPU &lt;/span&gt;리소스 업로드&lt;span&gt; + LOD &lt;/span&gt;재계산&lt;/b&gt;같은 후속 비용이 누적되기 때문이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정점 중복 제거 &amp;mdash; 엣지 인덱스 해시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 큐브 한 큐브가 독립적으로 처리되면&lt;span&gt;, &lt;/span&gt;두 인접 큐브가 공유하는 같은 엣지 위에 같은 보간 교점이 &lt;b&gt;두 번&lt;/b&gt; 등록된다&lt;span&gt;. &lt;/span&gt;좌표가 동일하다는 게 보장돼 있으니 좌표 비교로 중복을 제거할 수도 있지만&lt;span&gt;, &lt;/span&gt;부동소수점 비교는 위험하다&lt;span&gt;. &lt;/span&gt;표준적인 트릭은 &lt;b&gt;엣지 한 개를 전역적으로 식별하는 정수 키&lt;/b&gt;를 만들어서 그 키 &amp;rarr; 정점 인덱스 해시를 들고 다니는 것이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;// &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;엣지의 두 끝 격자 인덱스&lt;span&gt; (a, b) &lt;/span&gt;&amp;mdash; 항상&lt;span&gt; a &amp;lt; b&lt;/span&gt;로 정렬&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;edgeKey = pack3(min(a, b)) * SHIFT + pack3(max(a, b))&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;if edgeKey in cache: reuse cache[edgeKey]&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #f4f4f4;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;else: cache[edgeKey] = registerVertex(p)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;GPU &lt;/span&gt;구현은 보통 두 패스로 푼다 &amp;mdash; 첫 패스에서 각 엣지 위에 정점을 후보로 등록&lt;span&gt;(activeEdge &lt;/span&gt;마스크&lt;span&gt; + prefix sum), &lt;/span&gt;두 번째 패스에서 그 정점 인덱스를 참조해 삼각형 인덱스 버퍼를 만든다&lt;span&gt;. &lt;/span&gt;한 패스 안에서 모든 걸 끝내려면&lt;span&gt; atomic &lt;/span&gt;연산이 필요한데&lt;span&gt;, atomic&lt;/span&gt;이 비싸므로 두 패스 분리가 보통 더 빠르다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;청크 경계 처리 &amp;mdash;&lt;span&gt; UE5.8 MeshPartition&lt;/span&gt;의 자리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 단위 베이크가 가장 까다로워지는 자리는 &lt;b&gt;청크 경계&lt;/b&gt;다&lt;span&gt;. &lt;/span&gt;두 청크가 공유하는 엣지 위 정점이 양쪽에서 같은 좌표여야 하고&lt;span&gt;, &lt;/span&gt;그 정점에 연결되는 삼각형들이 두 청크에서 모순 없이 만나야 한다&lt;span&gt;. &lt;/span&gt;표준 해법은 청크 그리드를 한 칸 겹쳐서&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;overlap = 1&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;베이크하고&lt;span&gt;, &lt;/span&gt;두 번째 패스에서 경계 정점을&lt;span&gt; stitching&lt;/span&gt;하는 것이다&lt;span&gt;. UE 5.8 &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshPartition&lt;/span&gt;이 청크를 자르는 방식도 결국 이 패턴 위에 서 있다 &amp;mdash; 한 청크 안은 완전 독립이고&lt;span&gt;, &lt;/span&gt;청크 사이의 일관성은 별도 패스가 책임진다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 분리 덕분에 청크 베이크는 &lt;b&gt;독립적이고 병렬화 가능한&lt;span&gt; N&lt;/span&gt;개의 작은 베이크&lt;span&gt; + &lt;/span&gt;결정론적인&lt;span&gt; stitching &lt;/span&gt;한 번&lt;/b&gt;으로 분해된다&lt;span&gt;. &lt;/span&gt;큰 월드도 청크 단위로 점진 베이크가 가능하고&lt;span&gt;, &lt;/span&gt;사용자가 한 영역을 편집한 결과가 다른 영역으로 새지 않는다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;이 챕터 요약&lt;/h1&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; width=&quot;600&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단계&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;무엇&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;자료구조&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;1. &lt;/span&gt;분류&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;8&lt;/span&gt;버텍스 안&lt;span&gt;/&lt;/span&gt;밖 &amp;rarr;&lt;span&gt; 8&lt;/span&gt;비트 정수&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;cubeIndex &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&amp;isin;&lt;span&gt; [0, 256)&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;2. &lt;/span&gt;엣지 마스크&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;어느 엣지가 활성인가&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;edgeTable[256] &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&amp;rarr;&lt;span&gt; 12-bit mask&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;3. &lt;/span&gt;보간&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;활성 엣지 위 교점 위치&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;t = (iso-v0)/(v1-v0)&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;4. &lt;/span&gt;삼각형 트리플&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;활성 엣지 인덱스로 삼각형 구성&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;triTable[256][16]&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;5. &lt;/span&gt;모호성 해결&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;면&lt;span&gt;/&lt;/span&gt;내부&lt;span&gt; dual surface &lt;/span&gt;결정&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;asymptotic decider, MC33&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;6. &lt;/span&gt;누적&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;격자 전체에서 메시 합성&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;per-cube &lt;/span&gt;독립&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마칭큐브가&lt;span&gt; 30&lt;/span&gt;년 넘게 표준 자리를 지킨 이유가 이 표 안에 모두 들어 있다&lt;span&gt;. 6&lt;/span&gt;단계 중&lt;span&gt; 5&lt;/span&gt;단계가 룩업과 산술이고&lt;span&gt;, &lt;/span&gt;한 단계&lt;span&gt;(&lt;/span&gt;모호성 해결&lt;span&gt;)&lt;/span&gt;만 분기를 요구하는데 그마저도 입력을&lt;span&gt; SDF&lt;/span&gt;로 정규화하면 사실상 우회된다&lt;span&gt;. &lt;/span&gt;알고리즘 자체의 단순함이 후속&lt;span&gt; 30&lt;/span&gt;년의 모든 변종이&lt;span&gt; &quot;&lt;/span&gt;그래도 결국 돌아오는&lt;span&gt;&quot; &lt;/span&gt;기준점을 만들었다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 마칭큐브가 한 큐브 안에서 무슨 일을 하는지를 끝까지 따라왔다&lt;span&gt;. 8&lt;/span&gt;비트 인덱스 한 번 만들고&lt;span&gt;, &lt;/span&gt;두 장의 표를 한 번씩 조회하고&lt;span&gt;, &lt;/span&gt;최대&lt;span&gt; 12&lt;/span&gt;개 엣지에서 선형 보간을 한 번씩 돌리면 한 큐브의 메시 기여분이 결정된다&lt;span&gt;. &lt;/span&gt;격자 전체에 이 절차가 독립적으로 일어나니&lt;span&gt; GPU&lt;/span&gt;로 옮기든 청크로 베이크하든 자유다&lt;span&gt;. &lt;/span&gt;다음 챕터에서는 이 알고리즘이&lt;span&gt; UE 5.8 &lt;/span&gt;메시 테레인의&lt;span&gt; &quot;&lt;/span&gt;두 절벽 붙이기&lt;span&gt;&quot; &lt;/span&gt;흐름에서 정확히 어느 자리에 박혀 있는지를 본다 &amp;mdash; 추상 절차가 엔진 코드로 응결되는 한 자리를 확인하는 것이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</description>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/172</guid>
      <comments>https://develop-4-art.tistory.com/172#entry172comment</comments>
      <pubDate>Fri, 29 May 2026 01:35:50 +0900</pubDate>
    </item>
    <item>
      <title>[Graphics] 복셀 마칭큐브와 Mesh Terrain - 2</title>
      <link>https://develop-4-art.tistory.com/171</link>
      <description>&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://develop-4-art.tistory.com/170&quot;&gt;[Graphics] 복셀 마칭큐브와 Mesh Terrain - 1&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://develop-4-art.tistory.com/172&quot;&gt;[Graphics] 복셀 마칭큐브와 Mesh Terrain - 3&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://develop-4-art.tistory.com/173&quot;&gt;[Graphics] 복셀 마칭큐브와 Mesh Terrain - 4&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Ch2. &lt;/span&gt;입문 &amp;mdash;&lt;span&gt; 2D &lt;/span&gt;마칭 스퀘어로 직관 잡고&lt;span&gt; 3D&lt;/span&gt;로 점프&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마칭큐브는&lt;span&gt; 3D &lt;/span&gt;알고리즘인데&lt;span&gt;, &lt;/span&gt;처음 만나면 큐브&lt;span&gt; 8&lt;/span&gt;버텍스가 동시에 안&lt;span&gt;/&lt;/span&gt;밖이 갈리는 그림이 머릿속에 잘 그려지지 않는다&lt;span&gt;. &lt;/span&gt;한 차원 내려가서&lt;span&gt; 2D&lt;/span&gt;부터 보면 같은 아이디어가 손바닥만한 격자에 펼쳐지고&lt;span&gt;, &lt;/span&gt;거기서&lt;span&gt; 3D&lt;/span&gt;로 점프하는 게 훨씬 자연스럽다&lt;span&gt;. &lt;/span&gt;이 챕터는&lt;span&gt; 2D&lt;/span&gt;의 &lt;b&gt;마칭 스퀘어&lt;/b&gt;&lt;span&gt;(marching squares)&lt;/span&gt;로 직관을 잡은 다음&lt;span&gt;, &lt;/span&gt;그 직관을 그대로 한 차원 끌어올려&lt;span&gt; 3D &lt;/span&gt;마칭큐브로 넘어가는 게 목표다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;2D &lt;/span&gt;마칭 스퀘어 &amp;mdash;&lt;span&gt; 4 &lt;/span&gt;꼭짓점&lt;span&gt;, 16 &lt;/span&gt;케이스&lt;span&gt;, 4 &lt;/span&gt;고유 케이스&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스칼라 장 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;f(x, y)&lt;/span&gt;가 평면에 정의돼 있다고 하자&lt;span&gt;. &lt;/span&gt;우리가 원하는 건 그 장의 &lt;b&gt;등치선&lt;/b&gt;&lt;span&gt;(isoline), &lt;/span&gt;즉 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;f = iso&lt;/span&gt;가 되는 곡선이다&lt;span&gt;. &lt;/span&gt;격자&lt;span&gt;(grid) &lt;/span&gt;한 칸을 떼어내면 그 칸은&lt;span&gt; 4&lt;/span&gt;개의 꼭짓점을 가진 사각형이다&lt;span&gt;. &lt;/span&gt;각 꼭짓점에서 스칼라 값을 한 번씩 샘플링해 두면&lt;span&gt;, &lt;/span&gt;그 값이 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;iso&lt;/span&gt;보다 작으냐 크냐로 &lt;b&gt;안&lt;span&gt;(below)&lt;/span&gt;&lt;/b&gt;&lt;span&gt; / &lt;/span&gt;&lt;b&gt;밖&lt;span&gt;(above)&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;두 상태 중 하나가 된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭짓점&lt;span&gt; 4&lt;/span&gt;개 &amp;times; 상태&lt;span&gt; 2&lt;/span&gt;개니까 한 칸이 가질 수 있는 안&lt;span&gt;/&lt;/span&gt;밖 패턴은 정확히 &lt;b&gt;&lt;span&gt;2&lt;/span&gt;⁴&lt;span&gt; = 16&lt;/span&gt;가지&lt;/b&gt;다&lt;span&gt;. 0000(&lt;/span&gt;전부 밖&lt;span&gt;)&lt;/span&gt;부터&lt;span&gt; 1111(&lt;/span&gt;전부 안&lt;span&gt;)&lt;/span&gt;까지&lt;span&gt;. &lt;/span&gt;각 패턴마다 등치선이 그 사각형을 어떻게 가로지르는지가 정해진다&lt;span&gt;. &lt;/span&gt;가장 흔한 패턴 한두 개만 그림으로 보면&lt;span&gt; &quot;&lt;/span&gt;아하&lt;span&gt;&quot; &lt;/span&gt;하는 순간이 온다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yejX2/dJMcaaepFXU/SIum1VdjSGYbv2bVk9Emc0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yejX2/dJMcaaepFXU/SIum1VdjSGYbv2bVk9Emc0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yejX2/dJMcaaepFXU/SIum1VdjSGYbv2bVk9Emc0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/yejX2/dJMcaaepFXU/SIum1VdjSGYbv2bVk9Emc0/img.gif&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;560&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;700&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[IMAGE 04 &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;mdash;&lt;span&gt; Marching Squares 16 cases (Python/matplotlib): 4&lt;/span&gt;&amp;times;&lt;span&gt;4 grid, &lt;/span&gt;각 셀에&lt;span&gt; 4&lt;/span&gt;꼭짓점 채움 패턴&lt;span&gt; + &lt;/span&gt;그 안을 가로지르는 등치선&lt;span&gt;. &lt;/span&gt;회전&lt;span&gt;/&lt;/span&gt;반사로 묶이는&lt;span&gt; 4 &lt;/span&gt;고유 케이스는 색상 그룹으로 표시&lt;span&gt;. &lt;/span&gt;좌표 정확&lt;span&gt;.]&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회전&lt;span&gt;(90&lt;/span&gt;&amp;deg;&amp;middot;&lt;span&gt;180&lt;/span&gt;&amp;deg;&amp;middot;&lt;span&gt;270&lt;/span&gt;&amp;deg;&lt;span&gt;)&lt;/span&gt;과 반사 대칭으로 묶으면&lt;span&gt; 16&lt;/span&gt;개 케이스는 결국 &lt;b&gt;&lt;span&gt;4&lt;/span&gt;개의 고유 케이스&lt;/b&gt;로 줄어든다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;Case A &lt;/span&gt;&amp;mdash; 전부 안&lt;span&gt; / &lt;/span&gt;전부 밖&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;등치선 없음&lt;span&gt;. (0000, 1111)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;Case B &lt;/span&gt;&amp;mdash; 한 꼭짓점만 다른 상태&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;그 꼭짓점을 둘러싼 두 엣지를 가로지르는 짧은 직선 한 조각&lt;span&gt;. (8&lt;/span&gt;개 패턴이 회전&amp;middot;반사로 묶임&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;Case C &lt;/span&gt;&amp;mdash; 인접한 두 꼭짓점이 같은 상태&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;사각형을 가로지르는 직선 한 조각&lt;span&gt;. (4&lt;/span&gt;개 패턴&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;Case D &lt;/span&gt;&amp;mdash; 대각선 두 꼭짓점만 같은 상태&lt;span&gt; (saddle).&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;두 조각이 나오는데&lt;span&gt;, &lt;/span&gt;두 가지 연결 방식이 모두 합법이다&lt;span&gt;. &lt;/span&gt;이게&lt;span&gt; 2D&lt;/span&gt;판 &lt;b&gt;모호성&lt;span&gt;(ambiguity)&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;케이스다&lt;span&gt;. (2&lt;/span&gt;개 패턴&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Case D&lt;/span&gt;의 두 가지 합법 연결 중 어느 쪽을 고를지는 보조 정보 &amp;mdash; 예를 들어 사각형 중앙의 보간된 스칼라 값 &amp;mdash; 로 결정한다&lt;span&gt;. &lt;/span&gt;이게&lt;span&gt; 3D&lt;/span&gt;에서&lt;span&gt; Case 6 / 13 &lt;/span&gt;같은 모호성 케이스로 그대로 확대된다&lt;span&gt;. 2D&lt;/span&gt;에서 잠깐 머리를 갸웃하게 만드는 정도지만&lt;span&gt;, 3D&lt;/span&gt;에서는&lt;span&gt; &quot;&lt;/span&gt;터널&lt;span&gt;(tunnel)&lt;/span&gt;이 뚫리느냐 막히느냐&lt;span&gt;&quot;&lt;/span&gt;가 갈리는 위상 결정으로 격상된다&lt;span&gt;. Ch3&lt;/span&gt;에서 다시 만난다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&quot;Marching&quot;&lt;/span&gt;이라는 이름의 유래&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 알고리즘 가족이 굳이 마칭&lt;span&gt;(marching, &lt;/span&gt;행진&lt;span&gt;)&lt;/span&gt;이라는 군대 용어를 달고 다니는 이유는&lt;span&gt;, &lt;/span&gt;격자의 한 칸 한 칸을 &lt;b&gt;마치 검열관이 행진하듯&lt;/b&gt; 순회하면서 같은 룩업 규칙을 반복 적용하기 때문이다&lt;span&gt;. &lt;/span&gt;한 칸의 결정이 옆 칸에 영향을 주지 않고&lt;span&gt;, &lt;/span&gt;모든 칸이 똑같은 짧은 절차를 거친다&lt;span&gt;. &lt;/span&gt;컴파일러로 치면 한 줄짜리 함수를 격자 전체에 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;map()&lt;/span&gt;&lt;span&gt; &lt;/span&gt;하는 셈이고&lt;span&gt;, GPU&lt;/span&gt;로 옮기면 한 칸당 한 스레드를 띄우면 끝이다&lt;span&gt;. &lt;/span&gt;알고리즘 이름이 곧 그 구조를 광고하는 드문 사례다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대안 이름인&lt;span&gt; cuberille(&lt;/span&gt;라틴어로&lt;span&gt; &quot;&lt;/span&gt;큐브 격자&lt;span&gt;&quot;), isosurfacing, contouring &lt;/span&gt;같은 표현들도 같은 가족을 가리키지만&lt;span&gt;, &lt;/span&gt;&lt;b&gt;마칭&lt;/b&gt; 계열만이&lt;span&gt; &quot;&lt;/span&gt;한 칸씩 도장 찍듯 진행한다&lt;span&gt;&quot;&lt;/span&gt;는 구현 패턴까지 이름에 박아 넣었다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;점프 &amp;mdash;&lt;span&gt; 4 &lt;/span&gt;꼭짓점이&lt;span&gt; 8 &lt;/span&gt;꼭짓점이 되는 순간&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 잡았으면&lt;span&gt; 3D &lt;/span&gt;마칭큐브는 &lt;b&gt;같은 알고리즘을 한 차원 끌어올린 것&lt;/b&gt;이다&lt;span&gt;. &lt;/span&gt;정확히 세 가지가 바뀐다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;1.&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;격자 한 칸의 꼭짓점이&lt;span&gt; 4&lt;/span&gt;개 &amp;rarr;&lt;span&gt; 8&lt;/span&gt;개로 늘어난다&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;사각형이 큐브가 되니까&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;2.&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;케이스 수가&lt;span&gt; 2&lt;/span&gt;⁴&lt;span&gt; = 16 &lt;/span&gt;&amp;rarr;&lt;span&gt; 2&lt;/span&gt;⁸&lt;span&gt; = 256&lt;/span&gt;으로 폭증한다&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;모든 안&lt;span&gt;/&lt;/span&gt;밖 조합&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;3.&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;출력이 선분&lt;span&gt;(line segment)&lt;/span&gt;이 아니라 삼각형&lt;span&gt;(triangle)&lt;/span&gt;이다&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;등치선이 등치면이 됐으니까&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzCz7N/dJMcafz039B/fqiew5IhwWcwVmbctOKspK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzCz7N/dJMcafz039B/fqiew5IhwWcwVmbctOKspK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzCz7N/dJMcafz039B/fqiew5IhwWcwVmbctOKspK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzCz7N%2FdJMcafz039B%2Ffqiew5IhwWcwVmbctOKspK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;813&quot; height=&quot;594&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[IMAGE 05 &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;mdash;&lt;span&gt; 2D Marching Squares &lt;/span&gt;&amp;rarr;&lt;span&gt; 3D Marching Cubes (SVG): &lt;/span&gt;좌측에&lt;span&gt; 4&lt;/span&gt;꼭짓점 한 케이스&lt;span&gt;, &lt;/span&gt;우측에&lt;span&gt; 8&lt;/span&gt;꼭짓점 큐브의 대응되는 케이스&lt;span&gt;. &lt;/span&gt;화살표로 차원 점프&lt;span&gt;. 4&lt;/span&gt;&amp;rarr;&lt;span&gt;8, 16&lt;/span&gt;&amp;rarr;&lt;span&gt;256, &lt;/span&gt;선분&amp;rarr;삼각형 라벨링&lt;span&gt;.]&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;256&lt;/span&gt;은 큰 숫자처럼 들리지만&lt;span&gt;, &lt;/span&gt;회전&lt;span&gt;(24&lt;/span&gt;개&lt;span&gt;)&lt;/span&gt;과 반사 대칭을 모두 적용해 묶으면 결국 &lt;b&gt;&lt;span&gt;15&lt;/span&gt;개의 고유 케이스&lt;/b&gt;로 줄어든다&lt;span&gt;. &lt;/span&gt;이&lt;span&gt; 15 &lt;/span&gt;케이스를 한 장에 그려둔 그림이 마칭큐브 글의 트레이드마크 같은 다이어그램이고&lt;span&gt;, Ch3&lt;/span&gt;에서 자세히 본다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 한 가지 안심해도 되는 사실&lt;span&gt;. &lt;b&gt;256&lt;/b&gt;&lt;/span&gt;&lt;b&gt;개 케이스 표를 직접 손으로 채울 일은 거의 없다&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 1987&lt;/span&gt;년&lt;span&gt; Lorensen &amp;amp; Cline&lt;/span&gt;의 원논문이&lt;span&gt; 15 &lt;/span&gt;케이스를 그려서 발표한 이후&lt;span&gt;, Paul Bourke&lt;/span&gt;의&lt;span&gt; polygonising &lt;/span&gt;코드&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;paulbourke.net/geometry/polygonise&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;가&lt;span&gt; public-domain&lt;/span&gt;으로&lt;span&gt; 256&lt;/span&gt;개 표를 모두 펼쳐 풀어 깔아 두었고&lt;span&gt;, &lt;/span&gt;그 표가 사실상 모든 마칭큐브 구현의 출발점이 됐다&lt;span&gt;. OpenVDB, FastNoise, Voxel Tools, Unreal Engine &lt;/span&gt;내부&lt;span&gt; GeometryProcessing &lt;/span&gt;모듈까지 &amp;mdash; 모두 같은 인덱싱 컨벤션과 같은&lt;span&gt; (&lt;/span&gt;또는 동등한&lt;span&gt;) &lt;/span&gt;룩업 테이블을 공유한다&lt;span&gt;. &lt;/span&gt;즉 새 프로젝트에서 마칭큐브를 들고 와야 할 때 진짜로 직접 짤 부분은 &lt;b&gt;스칼라 장을 어떻게 채울지&lt;/b&gt;&lt;span&gt;(&lt;/span&gt;노이즈&lt;span&gt; vs SDF vs CT &lt;/span&gt;데이터&lt;span&gt;) &lt;/span&gt;정도이고&lt;span&gt;, &lt;/span&gt;케이스 분류와 룩업은 거의 모두 잘 다듬어진 레퍼런스에서 옮겨오면 된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;큐브 인덱싱 컨벤션 &amp;mdash; 끝까지 들고 갈 약속&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;3D&lt;/span&gt;에서&lt;span&gt; 8&lt;/span&gt;개 꼭짓점에 번호를 매기는 방식은 구현체마다 살짝 다르지만&lt;span&gt;, &lt;/span&gt;이 글에서는&lt;span&gt; Paul Bourke&lt;/span&gt;의&lt;span&gt; polygonising &lt;/span&gt;코드와 대부분의 오픈소스 구현&lt;span&gt;(&lt;/span&gt;특히&lt;span&gt; OpenVDB, FastNoise)&lt;/span&gt;이 채택한 표준을 따른다&lt;span&gt;. &lt;/span&gt;&lt;b&gt;이 표는 이 글 끝까지 그대로 쓰니까 한 번에 못 박고 가자&lt;span&gt;.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; width=&quot;600&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;좌표 &lt;/span&gt;&lt;/b&gt;&lt;b&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;(x, y, z)&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위치 설명&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V0&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;(0, 0, 0)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;바닥 &amp;mdash; 앞&lt;span&gt;-&lt;/span&gt;왼쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V1&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;(1, 0, 0)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;바닥 &amp;mdash; 앞&lt;span&gt;-&lt;/span&gt;오른쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V2&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;(1, 1, 0)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;바닥 &amp;mdash; 뒤&lt;span&gt;-&lt;/span&gt;오른쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V3&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;(0, 1, 0)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;바닥 &amp;mdash; 뒤&lt;span&gt;-&lt;/span&gt;왼쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V4&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;(0, 0, 1)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;위 &amp;mdash; 앞&lt;span&gt;-&lt;/span&gt;왼쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V5&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;(1, 0, 1)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;위 &amp;mdash; 앞&lt;span&gt;-&lt;/span&gt;오른쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V6&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;(1, 1, 1)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;위 &amp;mdash; 뒤&lt;span&gt;-&lt;/span&gt;오른쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V7&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;(0, 1, 1)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;위 &amp;mdash; 뒤&lt;span&gt;-&lt;/span&gt;왼쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엣지는 그 사이를 잇는다&lt;span&gt;. 12&lt;/span&gt;개의 엣지에도 번호가 있다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; width=&quot;600&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;인덱스&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;잇는 꼭짓점&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위치&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;E0&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V0 &lt;/span&gt;&amp;mdash;&lt;span&gt; V1&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;바닥 앞&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;E1&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V1 &lt;/span&gt;&amp;mdash;&lt;span&gt; V2&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;바닥 오른쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;E2&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V2 &lt;/span&gt;&amp;mdash;&lt;span&gt; V3&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;바닥 뒤&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;E3&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V3 &lt;/span&gt;&amp;mdash;&lt;span&gt; V0&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;바닥 왼쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;E4&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V4 &lt;/span&gt;&amp;mdash;&lt;span&gt; V5&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;위 앞&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;E5&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V5 &lt;/span&gt;&amp;mdash;&lt;span&gt; V6&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;위 오른쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;E6&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V6 &lt;/span&gt;&amp;mdash;&lt;span&gt; V7&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;위 뒤&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;E7&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V7 &lt;/span&gt;&amp;mdash;&lt;span&gt; V4&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;위 왼쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;E8&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V0 &lt;/span&gt;&amp;mdash;&lt;span&gt; V4&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;세로 &amp;mdash; 앞&lt;span&gt;-&lt;/span&gt;왼쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;E9&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V1 &lt;/span&gt;&amp;mdash;&lt;span&gt; V5&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;세로 &amp;mdash; 앞&lt;span&gt;-&lt;/span&gt;오른쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;E10&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V2 &lt;/span&gt;&amp;mdash;&lt;span&gt; V6&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;세로 &amp;mdash; 뒤&lt;span&gt;-&lt;/span&gt;오른쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;E11&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;V3 &lt;/span&gt;&amp;mdash;&lt;span&gt; V7&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;200&quot;&gt;&lt;span&gt;세로 &amp;mdash; 뒤&lt;span&gt;-&lt;/span&gt;왼쪽&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;594&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbAkkB/dJMcajoL677/BpUbOnXKouRP6Pk6kqbKok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbAkkB/dJMcajoL677/BpUbOnXKouRP6Pk6kqbKok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbAkkB/dJMcajoL677/BpUbOnXKouRP6Pk6kqbKok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbAkkB%2FdJMcajoL677%2FBpUbOnXKouRP6Pk6kqbKok%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;813&quot; height=&quot;594&quot; data-origin-width=&quot;813&quot; data-origin-height=&quot;594&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[IMAGE 06 &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;mdash; 큐브&lt;span&gt; 8&lt;/span&gt;버텍스&lt;span&gt; / 12&lt;/span&gt;엣지 인덱싱&lt;span&gt; (SVG): isometric &lt;/span&gt;큐브&lt;span&gt;, V0-V7 &lt;/span&gt;라벨&lt;span&gt; + E0-E11 &lt;/span&gt;엣지 라벨&lt;span&gt;. &lt;/span&gt;바닥&lt;span&gt;(z=0) &lt;/span&gt;옅은 색&lt;span&gt;, &lt;/span&gt;위&lt;span&gt;(z=1) &lt;/span&gt;진한 색으로 구분&lt;span&gt;. &lt;/span&gt;좌표축 표시&lt;span&gt;.]&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 번호 매김이 정해지면 한 칸의 안&lt;span&gt;/&lt;/span&gt;밖 패턴은&lt;span&gt; 8&lt;/span&gt;비트 정수 하나로 압축된다&lt;span&gt;. &lt;b&gt;V_i&lt;/b&gt;&lt;/span&gt;&lt;b&gt;가 안이면&lt;span&gt; i&lt;/span&gt;번 비트가&lt;span&gt; 1&lt;/span&gt;&lt;/b&gt;이라는 단순한 규칙이다&lt;span&gt;. &lt;/span&gt;즉 안&lt;span&gt;/&lt;/span&gt;밖 패턴 &amp;rarr;&lt;span&gt; 0&lt;/span&gt;&amp;hellip;&lt;span&gt;255 &lt;/span&gt;사이 정수&lt;span&gt; = &lt;/span&gt;큐브 인덱스&lt;span&gt;. &lt;/span&gt;그 인덱스가 곧&lt;span&gt; 256 &lt;/span&gt;케이스 중 어느 케이스인지를 가리키는 키가 된다&lt;span&gt;. Ch3&lt;/span&gt;의 테이블 룩업이 바로 이 인덱스 한 방으로 결정된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;복셀 &amp;mdash; 마칭이 실제로 행진하는 단위&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&quot;&lt;/span&gt;복셀&lt;span&gt;&quot;&lt;/span&gt;이라는 단어를 여러 의미로 쓰는데&lt;span&gt;, &lt;/span&gt;마칭큐브 맥락에서는 &lt;b&gt;&lt;span&gt;3D &lt;/span&gt;격자의 한 칸&lt;/b&gt;&lt;span&gt;, &lt;/span&gt;정확히는 그 칸의 &lt;b&gt;&lt;span&gt;8&lt;/span&gt;개 꼭짓점에서 스칼라 값을 샘플링했다는 사실&lt;/b&gt;을 가리킨다&lt;span&gt;. &lt;/span&gt;그래서&lt;span&gt; NxNxN &lt;/span&gt;그리드에는&lt;span&gt; (N-1)&lt;/span&gt;&amp;sup3;개의 복셀 칸이 있고&lt;span&gt;, &lt;/span&gt;각 칸마다 위에서 본&lt;span&gt; 8&lt;/span&gt;버텍스 인덱싱 &amp;rarr;&lt;span&gt; 256 &lt;/span&gt;케이스 룩업 &amp;rarr; 삼각형 추출이 &lt;b&gt;독립적으로&lt;/b&gt; 일어난다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccuYee/dJMcaipXl5c/UrFB8gTVtehcn4KBgFkhbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccuYee/dJMcaipXl5c/UrFB8gTVtehcn4KBgFkhbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccuYee/dJMcaipXl5c/UrFB8gTVtehcn4KBgFkhbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccuYee%2FdJMcaipXl5c%2FUrFB8gTVtehcn4KBgFkhbK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;875&quot; height=&quot;578&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[IMAGE 07 &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;mdash;&lt;span&gt; Voxel grid &lt;/span&gt;분위기&lt;span&gt; (nanobanana &lt;/span&gt;일러스트&lt;span&gt;): &lt;/span&gt;반투명한&lt;span&gt; 3D &lt;/span&gt;그리드 위에 등치면이 살짝 떠오르는 컷&lt;span&gt;. &lt;/span&gt;칸마다 마칭이 진행되는 느낌&lt;span&gt;. &lt;/span&gt;정확 좌표보다는 시각적 분위기&lt;span&gt;.]&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서&lt;span&gt; &quot;&lt;/span&gt;독립적&lt;span&gt;&quot;&lt;/span&gt;이라는 단어가 중요하다&lt;span&gt;. &lt;/span&gt;한 큐브의 결과가 옆 큐브에 직접 의존하지 않으므로 알고리즘은 &lt;b&gt;창피할 만큼 병렬화가 잘 된다&lt;/b&gt;&lt;span&gt;(embarrassingly parallel). GPU &lt;/span&gt;컴퓨트 셰이더로 옮기기 쉬운 이유&lt;span&gt;, UE 5.8 &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshPartition&lt;/span&gt;이 청크 단위로 베이크할 수 있는 이유가 모두 여기에서 온다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 인접 큐브가 같은 엣지를 공유하므로&lt;span&gt;, &lt;/span&gt;그 엣지 위에 찍히는 보간 교점도 두 큐브에서 같은 좌표여야 한다&lt;span&gt;. &lt;/span&gt;보간 공식이 양쪽에서 동일한 두 끝값으로 계산되니 자연히 그렇게 되지만&lt;span&gt;, &lt;/span&gt;정점 중복&lt;span&gt;(&lt;/span&gt;같은 엣지 교점을 두 번 등록&lt;span&gt;)&lt;/span&gt;을 피하려면 엣지 인덱스 &amp;rarr; 정점 인덱스 해시를 들고 다니는 게 보통이다&lt;span&gt;. &lt;/span&gt;구현 디테일은&lt;span&gt; Ch3&lt;/span&gt;의 성능 절&lt;span&gt;(&lt;/span&gt;節&lt;span&gt;)&lt;/span&gt;에서 다시 만난다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스칼라 장이 어디서 오는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;8&lt;/span&gt;버텍스 샘플링이라는 표현을 가볍게 썼지만&lt;span&gt;, &lt;/span&gt;그 값이 &lt;b&gt;어디서 오는가&lt;/b&gt;는 사용 케이스마다 다르다&lt;span&gt;. &lt;/span&gt;의료 영상에서는&lt;span&gt; CT &lt;/span&gt;보클&lt;span&gt;(voxel)&lt;/span&gt;의 헝&lt;span&gt;(Hounsfield) &lt;/span&gt;단위가 그대로 들어오고&lt;span&gt;, &lt;/span&gt;게임의 절차적 지형에서는&lt;span&gt; 3D Perlin / Simplex &lt;/span&gt;노이즈가 위치를 받아 실수 하나를 뱉는다&lt;span&gt;. UE 5.8 &lt;/span&gt;메시 테레인에서는 &lt;b&gt;하이트맵을 한 번 임포트한 다음&lt;/b&gt; 그 결과를 협대역&lt;span&gt; SDF&lt;/span&gt;로 들고 있다가&lt;span&gt;, &lt;/span&gt;두 메시를 합칠 때 그&lt;span&gt; SDF &lt;/span&gt;값을&lt;span&gt; 8&lt;/span&gt;버텍스에서 평가하는 식이다&lt;span&gt;. &lt;/span&gt;즉 마칭큐브 입력은 함수든 보클 그리드든&lt;span&gt; SDF &lt;/span&gt;텍스처든 상관없이&lt;span&gt;, &lt;b&gt;&quot;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;한 점에 실수 하나를 돌려주는 무언가&lt;span&gt;&quot;&lt;/span&gt;&lt;/b&gt;면 충분하다&lt;span&gt;. &lt;/span&gt;이 추상화가 알고리즘이&lt;span&gt; 30&lt;/span&gt;년 넘게 살아남은 이유 중 하나다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;노멀은 어떻게 구하나&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삼각형이 나와도 셰이딩에 쓸 노멀이 없으면 메시가 완성되지 않는다&lt;span&gt;. &lt;/span&gt;마칭큐브에서 노멀은 보통 &lt;b&gt;스칼라 장의 그래디언트&lt;/b&gt;&lt;span&gt;(&lt;/span&gt;&amp;nabla;&lt;span&gt;f)&lt;/span&gt;를 정규화해서 쓴다&lt;span&gt;. &lt;/span&gt;정점 좌표가 정해진 다음 그 좌표에서 &amp;part;&lt;span&gt;f/&lt;/span&gt;&amp;part;&lt;span&gt;x, &lt;/span&gt;&amp;part;&lt;span&gt;f/&lt;/span&gt;&amp;part;&lt;span&gt;y, &lt;/span&gt;&amp;part;&lt;span&gt;f/&lt;/span&gt;&amp;part;&lt;span&gt;z&lt;/span&gt;를 중앙 차분&lt;span&gt;(central difference)&lt;/span&gt;으로 추정하고&lt;span&gt;, &lt;/span&gt;부호는 등치값 방향에 맞춰 뒤집는다&lt;span&gt;. &lt;/span&gt;별도의 후처리&lt;span&gt;(per-face normal &lt;/span&gt;&amp;rarr;&lt;span&gt; vertex normal averaging) &lt;/span&gt;없이 &lt;b&gt;샘플링 단계에서 바로 부드러운 노멀&lt;/b&gt;이 떨어지는 게 마칭큐브 가족의 깔끔한 점이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;이 챕터 요약&lt;/h1&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; width=&quot;600&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;차원&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한 칸의 꼭짓점&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;케이스 수&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;회전&amp;middot;반사 후 고유 케이스&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #e8eef7;&quot; width=&quot;120&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;출력&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;2D (Marching Squares)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;4&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;⁴&lt;span&gt; = 16&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;4&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;120&quot;&gt;&lt;span&gt;선분&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;3D (Marching Cubes)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;8&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;2&lt;/span&gt;⁸&lt;span&gt; = 256&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;120&quot;&gt;&lt;span&gt;&lt;span&gt;15&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td width=&quot;120&quot;&gt;&lt;span&gt;삼각형&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 챕터에서는 그&lt;span&gt; 15 &lt;/span&gt;케이스가 어떻게 한 장에 펼쳐지는지&lt;span&gt;, &lt;/span&gt;엣지 테이블과 삼각형 테이블이 어떻게 한 번의 룩업으로 메시를 토해내는지&lt;span&gt;, &lt;/span&gt;그리고&lt;span&gt; 1987&lt;/span&gt;년부터 따라다닌 모호성 케이스의 정체를 차례로 푼다&lt;span&gt;. &lt;/span&gt;차원이 늘어났을 뿐&lt;span&gt; &quot;&lt;/span&gt;한 칸을&lt;span&gt; 8&lt;/span&gt;비트로 압축해 룩업 한 번에 끝낸다&lt;span&gt;&quot;&lt;/span&gt;는 핵심 골격은&lt;span&gt; 2D &lt;/span&gt;마칭 스퀘어 때와 정확히 같다는 점만 머릿속에 들고 넘어가면 된다&lt;span&gt;. &lt;/span&gt;핵심은 비트 패턴 하나가 곧 인덱스고&lt;span&gt;, &lt;/span&gt;인덱스 하나가 곧 케이스고&lt;span&gt;, &lt;/span&gt;케이스 하나가 곧 미리 계산된 삼각형 묶음을 가리킨다는 사실 &amp;mdash; 룩업 한 번이 메시 한 조각이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;</description>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/171</guid>
      <comments>https://develop-4-art.tistory.com/171#entry171comment</comments>
      <pubDate>Fri, 29 May 2026 01:33:44 +0900</pubDate>
    </item>
    <item>
      <title>[Graphics] 복셀 마칭큐브와 Mesh Terrain - 1</title>
      <link>https://develop-4-art.tistory.com/170</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://develop-4-art.tistory.com/171&quot;&gt;[Graphics] 복셀 마칭큐브와 Mesh Terrain - 2&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://develop-4-art.tistory.com/172&quot;&gt;[Graphics] 복셀 마칭큐브와 Mesh Terrain - 3&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://develop-4-art.tistory.com/173&quot;&gt;[Graphics] 복셀 마칭큐브와 Mesh Terrain - 4&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bybpWd/dJMcageBVkZ/HtbhhKzZRyBSj8Xszw80Ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bybpWd/dJMcageBVkZ/HtbhhKzZRyBSj8Xszw80Ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bybpWd/dJMcageBVkZ/HtbhhKzZRyBSj8Xszw80Ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbybpWd%2FdJMcageBVkZ%2FHtbhhKzZRyBSj8Xszw80Ck%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;938&quot; height=&quot;422&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #f8f4e6;&quot;&gt;
&lt;p style=&quot;background-color: #f8f4e6;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #000000;&quot;&gt;마칭큐브는&lt;span&gt; 3&lt;/span&gt;차원 스칼라 장&lt;span&gt;(scalar field)&lt;/span&gt;에서 등치면&lt;span&gt;(isosurface)&lt;/span&gt;을 삼각형 메시로 뽑아내는 알고리즘이다&lt;span&gt;. 1987&lt;/span&gt;년&lt;span&gt; Lorensen&lt;/span&gt;과&lt;span&gt; Cline&lt;/span&gt;이&lt;span&gt; SIGGRAPH&lt;/span&gt;에서 발표한 이후 의료 영상의&lt;span&gt; CT &lt;/span&gt;표면 재구성부터 게임의 절차적 지형&lt;span&gt;, &lt;/span&gt;유체 시뮬레이션의 마칭 결과 렌더까지&lt;span&gt;, &quot;&lt;/span&gt;복셀 그리드를 진짜 메시로 바꿔야 하는 모든 자리&lt;span&gt;&quot;&lt;/span&gt;의 표준이 되었다&lt;span&gt;. UE 5.8&lt;/span&gt;에 새로 들어온 &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshTerrainMode&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; / &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshPartition&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;플러그인도 절벽 두 개를 붙이는 그 순간에 내부적으로 복셀화 &amp;rarr; &lt;b&gt;마칭큐브&lt;/b&gt; &amp;rarr; 협대역&lt;span&gt; SDF &lt;/span&gt;파이프라인을 돌린다&lt;span&gt;. &lt;/span&gt;이 글은 그 핵심을 한 번 끝까지 풀어본다&lt;span&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h1&gt;왜 이 글이 따로 필요한가&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://develop-4-art.tistory.com/169&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;지난 글&lt;/a&gt;에서&lt;span&gt; UE 5.8&lt;/span&gt;의 메시 기반 지형 시스템 두 종&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshTerrainMode&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshPartition&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;을 훑었다&lt;span&gt;. &lt;/span&gt;그때&lt;span&gt; &quot;&lt;/span&gt;두 절벽을 합치는 순간엔 복셀화&lt;span&gt; + &lt;/span&gt;마칭큐브&lt;span&gt; + &lt;/span&gt;협대역&lt;span&gt; SDF&lt;/span&gt;가 에디터에서 돌아간다&lt;span&gt;&quot;&lt;/span&gt;는 한 줄로 넘어갔는데&lt;span&gt;, &lt;/span&gt;그 한 줄의 무게가 사실 글 전체보다 무겁다&lt;span&gt;. &lt;/span&gt;마칭큐브를 모르면 그 파이프라인이 왜 그렇게 생겼는지&lt;span&gt;, &lt;/span&gt;왜 런타임이 아니라 에디터 전용인지&lt;span&gt;, &lt;/span&gt;왜 협대역&lt;span&gt; SDF&lt;/span&gt;로 잘라내는지가 전부 블랙박스로 남는다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이번엔 순서를 뒤집는다&lt;span&gt;. &lt;/span&gt;&lt;b&gt;마칭큐브가 주연&lt;/b&gt;이고&lt;span&gt;, UE 5.8 &lt;/span&gt;메시 테레인은 그 알고리즘이 실제 엔진 코드에 어떻게 박혀 들어가는지를 보여주는 사례 한 편이 된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;이 글이 답하는 것&lt;span&gt; / &lt;/span&gt;답하지 않는 것&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;답하는 것&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;등치면이 뭐고&lt;span&gt;, &lt;/span&gt;왜&lt;span&gt; &quot;&lt;/span&gt;큐브 단위로 행진&lt;span&gt;&quot;&lt;/span&gt;하면 그게 뽑히는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;큐브&lt;span&gt; 8&lt;/span&gt;버텍스를 안&lt;span&gt;/&lt;/span&gt;밖으로 분류해서 만든&lt;span&gt; 8&lt;/span&gt;비트 인덱스가 어떻게&lt;span&gt; 256&lt;/span&gt;개 경우&lt;span&gt;, &lt;/span&gt;그리고 회전&amp;middot;반사 대칭으로&lt;span&gt; 15&lt;/span&gt;개 고유 케이스로 줄어드는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;엣지 테이블과 삼각형 테이블이 무엇을 들고 있고&lt;span&gt;, &lt;/span&gt;한 번의 룩업으로 어떻게 삼각형들이 튀어나오는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;선형 보간으로 엣지 위 교점을 어디에 찍는지&lt;span&gt; (&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;t = (iso - v0) / (v1 - v0)&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;1987&lt;/span&gt;년부터 따라다닌 모호성 케이스&lt;span&gt; (6, 10, 12, 13) &lt;/span&gt;&amp;mdash; 면 모호성과 내부 모호성&lt;span&gt;, &lt;/span&gt;그리고&lt;span&gt; dual surface &lt;/span&gt;문제가 왜 생기는지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Dual Contouring / Surface Nets &lt;/span&gt;같은 사촌 알고리즘들과의 짧은 비교&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;성능&lt;span&gt; / &lt;/span&gt;메모리 측면의 실전 고려사항&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;위 모든 게&lt;span&gt; UE 5.8 &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;MeshTerrainMode&lt;/span&gt;의&lt;span&gt; &quot;&lt;/span&gt;두 절벽 붙이기&lt;span&gt;&quot; &lt;/span&gt;흐름에 어떻게 매핑되는지&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;438&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qb2lx/dJMcaarWX8G/Q44zuNysvwI0CJKlZOfGAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qb2lx/dJMcaarWX8G/Q44zuNysvwI0CJKlZOfGAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qb2lx/dJMcaarWX8G/Q44zuNysvwI0CJKlZOfGAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fqb2lx%2FdJMcaarWX8G%2FQ44zuNysvwI0CJKlZOfGAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;938&quot; height=&quot;438&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;[IMAGE 02 &lt;/span&gt;&lt;/i&gt;&lt;i&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;mdash;&lt;span&gt; Ch1 metaphor: &quot;two cliffs welded into one continuous surface by an unseen algorithmic chisel&quot;]&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;답하지 않는 것&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;등치면 이론의 미분기하학적 정초&lt;span&gt; (&lt;/span&gt;풀리 핸들 같은 토폴로지 깊이&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;GPU &lt;/span&gt;컴퓨트 셰이더 구현 코드&lt;span&gt; (&lt;/span&gt;의사코드까지만&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;Manifold Dual Contouring, Cubical Marching Squares &lt;/span&gt;같은 고급 변종의 전체 구현&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;UE &lt;/span&gt;플러그인의&lt;span&gt; 6&lt;/span&gt;개 서브모드&lt;span&gt;(Create / Edit / Sculpt &lt;/span&gt;등&lt;span&gt;) &lt;/span&gt;디테일 &amp;mdash; 그건 지난 글로 충분히 깔아 두었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;런타임 동적 편집 기능에 대한 추측&lt;span&gt; (&lt;/span&gt;현재 에디터 전용이라는 사실만 인용한다&lt;span&gt;)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;span&gt;4&lt;/span&gt;단계 로드맵&lt;/h1&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; width=&quot;600&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;background-color: #e8eef7; width: 50%;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;챕터&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;background-color: #e8eef7; width: 50%;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;무엇&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;Ch1&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot; width=&quot;200&quot;&gt;&lt;span&gt;지금 읽고 있는 개요&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;Ch2&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;b&gt;입문 &amp;mdash;&lt;span&gt; 2D &lt;/span&gt;마칭 스퀘어로 직관 잡고&lt;span&gt; 3D&lt;/span&gt;로 점프&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;Ch3&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;b&gt;딥다이브 &amp;mdash; 인덱싱 &amp;rarr; 테이블 &amp;rarr; 보간 &amp;rarr; 모호성 &amp;rarr; 비교 &amp;rarr; 성능&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;span&gt;Ch4&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot; width=&quot;200&quot;&gt;&lt;span&gt;&lt;b&gt;사례 &amp;mdash;&lt;span&gt; UE 5.8 `MeshTerrainMode`&lt;/span&gt;에서 마칭큐브가 실제로 호출되는 지점&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서대로 읽으면 매끄럽지만&lt;span&gt;, &lt;/span&gt;각 챕터는 자기 토픽 문장 한 줄로 시작해서 독립적으로 떠먹어도 큰 무리가 없게 잘랐다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;전제 독자&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 프로그래머 또는 테크니컬 아티스트&lt;span&gt;(TA)&lt;/span&gt;를 가정한다&lt;span&gt;. &lt;/span&gt;다음은 글 안에서 따로 풀지 않고 그냥 쓴다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;스칼라 장&lt;span&gt;(scalar field):&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;공간상의 점마다 실수 한 개가 정의된 함수 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;f(x, y, z) &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&amp;rarr; ℝ&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;등치면&lt;span&gt;(isosurface):&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;그 값이 특정 임계값과 같아지는 지점들의 집합 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;{ p : f(p) = iso }&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;SDF(Signed Distance Field):&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;가장 가까운 표면까지의 부호 있는 거리&lt;span&gt;. &lt;/span&gt;안쪽이 음수&lt;span&gt;, &lt;/span&gt;바깥이 양수&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;복셀&lt;span&gt;(voxel):&lt;/span&gt;&lt;/b&gt;&lt;span&gt; 3D &lt;/span&gt;그리드 셀&lt;span&gt;. &lt;/span&gt;여기서는 한 셀의&lt;span&gt; 8&lt;/span&gt;개 꼭짓점에서 스칼라 값을 샘플링했다는 의미&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;메시 토폴로지 기본&lt;span&gt;:&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;버텍스&lt;span&gt; / &lt;/span&gt;엣지&lt;span&gt; / &lt;/span&gt;삼각형&lt;span&gt; / &lt;/span&gt;노멀&lt;span&gt;, &lt;/span&gt;매니폴드&lt;span&gt; / &lt;/span&gt;비매니폴드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 정도는&lt;span&gt; UE&lt;/span&gt;의 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;FDynamicMesh3&lt;/span&gt;&lt;span&gt; API&lt;/span&gt;나&lt;span&gt; Houdini&lt;/span&gt;의&lt;span&gt; VDB / VolumeSDF &lt;/span&gt;노드를 만져본 사람이라면 이미 머릿속에 들어있는 어휘다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;이 글의 그림들 &amp;mdash; 책임 분리&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적으로 들어가기 전에 한 가지만 분명히 해 두자&lt;span&gt;. &lt;/span&gt;마칭큐브 같은 알고리즘 글은 그림의 정확성이 글의 정확성을 절반쯤 결정한다&lt;span&gt;. &lt;/span&gt;큐브&lt;span&gt; 8&lt;/span&gt;버텍스 어디가&lt;span&gt; V0&lt;/span&gt;인지 한 칸 어긋나면 인덱싱 규칙 전체가 무너지고&lt;span&gt;, 256 &lt;/span&gt;케이스 중&lt;span&gt; 6&lt;/span&gt;번이&lt;span&gt; 13&lt;/span&gt;번처럼 그려지면 모호성 절&lt;span&gt;(&lt;/span&gt;節&lt;span&gt;)&lt;/span&gt;이 통째로 거짓말이 된다&lt;span&gt;. &lt;/span&gt;그래서 이 글의 그림은 두 갈래로 명확히 갈라 그렸다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;정확 도식&lt;span&gt; (SVG / Python).&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;큐브 인덱싱&lt;span&gt;, 15 &lt;/span&gt;케이스 그리드&lt;span&gt;, &lt;/span&gt;엣지 테이블 룩업 흐름&lt;span&gt;, &lt;/span&gt;보간 벡터&lt;span&gt;, ambiguity &lt;/span&gt;분기 &amp;mdash; 좌표가 의미를 갖는 모든 도식은 손으로 짠&lt;span&gt; SVG &lt;/span&gt;또는&lt;span&gt; matplotlib &lt;/span&gt;스크립트로 만들었다&lt;span&gt;. &lt;/span&gt;코드가 공개되어 있으니&lt;span&gt;(&lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;output/scripts/&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;재현 가능하고&lt;span&gt;, &lt;/span&gt;라벨 한 칸 옮기는 수정도 안전하다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;일러스트&lt;span&gt; (AI &lt;/span&gt;이미지 &amp;mdash;&lt;span&gt; nanobanana).&lt;/span&gt;&lt;/b&gt;&lt;span&gt; &lt;/span&gt;표지&lt;span&gt;, &lt;/span&gt;비유 컷&lt;span&gt;, &lt;/span&gt;분위기 컷처럼&lt;span&gt; &quot;&lt;/span&gt;느낌&lt;span&gt;&quot;&lt;/span&gt;만 전달하면 되는 자리는&lt;span&gt; nanobanana(Gemini &lt;/span&gt;기반 이미지 생성&lt;span&gt;)&lt;/span&gt;로 뽑았다&lt;span&gt;. &lt;/span&gt;정확성은 글이 책임지고&lt;span&gt;, &lt;/span&gt;그림은 호흡 정도만 맞춘다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 분리를 미리 깔아두는 이유는&lt;span&gt;, &lt;/span&gt;본문에서&lt;span&gt; &quot;&lt;/span&gt;이건 도식이라 좌표를 믿어도 된다&lt;span&gt; / &lt;/span&gt;이건 일러스트라 시각적 분위기만 읽어라&lt;span&gt;&quot; &lt;/span&gt;같은 메타 설명을 매번 붙이지 않기 위해서다&lt;span&gt;. &lt;/span&gt;캡션의 톤만 보고도 어느 쪽인지 짐작하면 된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h1&gt;표기 약속&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;큐브&lt;span&gt; 8&lt;/span&gt;버텍스는 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;V0&lt;/span&gt;&amp;ndash;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;V7&lt;/span&gt;로 라벨링한다&lt;span&gt;. &lt;/span&gt;인덱싱 규칙&lt;span&gt;(&lt;/span&gt;어느 모서리가&lt;span&gt; V0&lt;/span&gt;인지&lt;span&gt;)&lt;/span&gt;은&lt;span&gt; Ch2&lt;/span&gt;의&lt;span&gt; &quot;&lt;/span&gt;큐브 인덱싱 컨벤션&lt;span&gt;&quot; &lt;/span&gt;표에서 한 번에 못 박아 두고&lt;span&gt;, &lt;/span&gt;이후 챕터 전체에서 동일하게 쓴다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;12 &lt;/span&gt;엣지는 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;E0&lt;/span&gt;&amp;ndash;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;E11&lt;/span&gt;이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;등치값은 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;iso&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;한 엣지 위 두 끝값은 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;v0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;v1&lt;/span&gt;&lt;span&gt; (&lt;/span&gt;소문자&lt;span&gt; v&lt;/span&gt;는 스칼라 값&lt;span&gt;, &lt;/span&gt;대문자&lt;span&gt; V&lt;/span&gt;는 정점 위치&lt;span&gt;).&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;bull;&lt;span&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;보간 파라미터는 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;t &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;&amp;isin;&lt;span&gt; [0, 1]&lt;/span&gt;&lt;/span&gt;&lt;span&gt;. &lt;/span&gt;교점 위치는 &lt;span style=&quot;color: #0a0a0a;&quot;&gt;p = p0 + t * (p1 - p0)&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style=&quot;color: #0a0a0a;&quot;&gt;t = (iso - v0) / (v1 - v0)&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자&lt;span&gt;, &lt;/span&gt;시동 걸었다&lt;span&gt;. &lt;/span&gt;다음 챕터에서&lt;span&gt; 2D &lt;/span&gt;격자 위에 등치선을 그어 보면서&lt;span&gt; 3D&lt;/span&gt;로의 점프를 준비한다&lt;span&gt;. &lt;/span&gt;그 점프가 끝나고 나면 마칭큐브의 핵심 골격이 이미 손바닥에 들어와 있을 것이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;</description>
      <category>개발</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/170</guid>
      <comments>https://develop-4-art.tistory.com/170#entry170comment</comments>
      <pubDate>Fri, 29 May 2026 01:32:06 +0900</pubDate>
    </item>
    <item>
      <title>[UE5/Mesh Terrain] Mesh Terrain 오버뷰</title>
      <link>https://develop-4-art.tistory.com/169</link>
      <description>&lt;h1&gt;&lt;b&gt;0. 핵심 요약&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;UE 5.8-Preview&lt;/span&gt;는 두 개의 신규&lt;span&gt; Experimental &lt;/span&gt;플러그인을 통해&lt;span&gt; &quot;Mesh Terrain&quot; &lt;/span&gt;시스템을 도입했다&lt;span&gt;: &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에디터 모드를 제공하는&lt;span&gt; `MeshTerrainMode`&lt;/span&gt;와 런타임 데이터&lt;span&gt;/&lt;/span&gt;스트리밍을 담당하는&lt;span&gt; `MeshPartition`. &lt;/span&gt;두 플러그인 모두&lt;span&gt; 5.7.4&lt;/span&gt;에는 존재하지 않는다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 결론: &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Mesh Terrain&lt;/span&gt;은&lt;span&gt; Landscape&lt;/span&gt;처럼 영구적인&lt;span&gt; 2D &lt;/span&gt;하이트맵을 저장하지 않는다&lt;span&gt;. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하이트맵은 일회성 임포트 포맷일 뿐이며&lt;span&gt;, &lt;/span&gt;임포트 직후&lt;span&gt; `FDynamicMesh3` &lt;/span&gt;삼각형 메시로 변환된다&lt;span&gt;. &lt;/span&gt;이 덕분에 오버행&lt;span&gt;/&lt;/span&gt;동굴&lt;span&gt;/&lt;/span&gt;분기 토폴로지가 가능하다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 절벽 메시를 하나로 합치는 작업은 가능하다&lt;span&gt; &amp;mdash; &lt;/span&gt;단&lt;span&gt;,&amp;nbsp;&lt;/span&gt;&lt;b&gt;에디터 타임 한정&lt;/b&gt;으로 복셀화&lt;span&gt;(Voxelization) &lt;/span&gt;기반&lt;span&gt; CSG &lt;/span&gt;도구&lt;span&gt; `FVoxelBooleanMeshesOp` / `FVoxelMergeMeshesOp`&lt;/span&gt;를 통해 수행된다&lt;span&gt;. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 마칭 큐브&lt;span&gt;(MarchingCubes)&lt;/span&gt;와 협대역&lt;span&gt;(Narrowband) SDF&lt;/span&gt;가 사용된다&lt;span&gt;. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패키지된 런타임에서는 복셀&lt;span&gt;/SDF &lt;/span&gt;저장소가 없으며&lt;span&gt;, &lt;/span&gt;베이크된&lt;span&gt; `FMeshData` &lt;/span&gt;삼각형 메시만 유통된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;&lt;b&gt;1.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;span&gt;플러그인 구성&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;1.1 &lt;/span&gt;두 개의 신규 플러그인&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;플러그인&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;모듈 수&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;런타임&lt;span&gt;/&lt;/span&gt;에디터&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;MeshTerrainMode&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Mesh Partition 편집을 위한 인터랙티브 툴 모음&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;1 (Editor)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;에디터 전용&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;MeshPartition&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;런타임 데이터&lt;span&gt; + &lt;/span&gt;스트리밍&lt;span&gt; + &lt;/span&gt;에디터 워크플로&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;5 (Runtime 2 + Editor 3)&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;혼합&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;MeshTerrainMode &lt;/span&gt;매니페스트 요약&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 아직 Experimental이며, 기본적으로 활성화되지 않습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;FriendlyName: &quot;Mesh Terrain Mode&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Category: &quot;Mesh Partition&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Description: &quot;Mesh Partition&lt;/span&gt;을 생성&lt;span&gt;/&lt;/span&gt;편집하기 위한 인터랙티브 툴 모음&lt;span&gt;&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;DocsURL: &lt;a href=&quot;https://dev.epicgames.com/community/learning/knowledge-base/nK7J/unreal-engine-introduction-to-mesh-terrain#meshterrainmode&quot;&gt;https://dev.epicgames.com/community/learning/knowledge-base/nK7J/unreal-engine-introduction-to-mesh-terrain#meshterrainmode&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Plugins &lt;/span&gt;의존성&lt;span&gt;: MeshModelingToolset, MeshModelingToolsetExp, MeshLODToolset, ToolPresets, StylusInput, ModelingToolsEditorMode, MeshPartition&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;EnabledByDefault: false / IsExperimentalVersion: true&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;MeshPartition &lt;/span&gt;모듈&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MeshPartition (Runtime, Default)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MeshPartitionCompute (Runtime, PostConfigInit)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MeshPartitionEditor (Editor)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MeshPartitionModelingToolset (Editor)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;MeshPartitionEditorUI (Editor)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt;2. 데이터 표현 &amp;mdash; Landscape vs Mesh Terrain&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;2.1 Landscape &lt;/span&gt;기준선&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Landscape&lt;/span&gt;는 영구&lt;span&gt; 2D &lt;/span&gt;하이트맵 기반 시스템이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;핵심 구조&lt;span&gt;: `FHeightmapData`&lt;/span&gt;가&lt;span&gt; `UTexture2D` &lt;/span&gt;래핑&lt;span&gt; (LandscapeComponent.h:321)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;저장 포맷&lt;span&gt;: uint16 raw &lt;/span&gt;배열&lt;span&gt; + LOD&lt;/span&gt;별&lt;span&gt; mip &amp;mdash; `TMap&amp;lt;int32, TArray&amp;lt;uint16&amp;gt;&amp;gt; HeightMipData` (LandscapeComponent.h:227)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;그리드&lt;span&gt;: `(SubsectionSizeQuads + 1) * ComponentNumSubsections` &lt;/span&gt;정점&lt;span&gt; (&lt;/span&gt;예&lt;span&gt;: 4&amp;times;63+1 = 253 &lt;/span&gt;정점&lt;span&gt;/&lt;/span&gt;컴포넌트&lt;span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;LOD: &lt;/span&gt;컴포넌트별 결정론적&lt;span&gt; mip + GPU&lt;/span&gt;에서 정점 모핑&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;토폴로지&lt;span&gt;: **2D &lt;/span&gt;정규 그리드만&lt;span&gt;**. X-Y &lt;/span&gt;평면&lt;span&gt; + Z &lt;/span&gt;높이&lt;span&gt;. &lt;/span&gt;오버행&lt;span&gt;/&lt;/span&gt;동굴 불가능&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;스트리밍&lt;span&gt;: `ALandscapeStreamingProxy`&lt;/span&gt;가 셀별 액터 분할&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;편집&lt;span&gt;: &lt;/span&gt;브러시 페인팅으로 높이만 수정&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;결정론&lt;span&gt;: &lt;/span&gt;높음&lt;span&gt; (&lt;/span&gt;고정 그리드&lt;span&gt;, &lt;/span&gt;재현 가능&lt;span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;2.2 Mesh Terrain (MeshPartition) &lt;/span&gt;표현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Mesh Terrain&lt;/span&gt;은 완전&lt;span&gt; 3D &lt;/span&gt;삼각형 메시 기반이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;핵심 구조&lt;span&gt;: `FMeshData` &amp;mdash; &lt;/span&gt;명시적 정점&lt;span&gt;/&lt;/span&gt;삼각형 배열&lt;span&gt; (MeshPartitionMeshData.h:17)&lt;br /&gt;&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`FDynamicMesh3` &lt;/span&gt;및&lt;span&gt; `FMeshDescription`&lt;/span&gt;과 양방향 변환&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`FTriMeshCollisionData`&lt;/span&gt;로 변환되어 복합 충돌 생성&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;토폴로지&lt;span&gt;:&amp;nbsp;&lt;/span&gt;&lt;b&gt;완전&lt;/b&gt;&lt;span&gt;&lt;b&gt; 3D&lt;/b&gt;.&amp;nbsp;&lt;/span&gt;오버행&lt;span&gt;/&lt;/span&gt;동굴&lt;span&gt;/&lt;/span&gt;분기 가능&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;스트리밍&lt;span&gt;: &lt;b&gt;`ACompiledSection`&lt;/b&gt; &lt;/span&gt;액터가&lt;span&gt; &lt;b&gt;World Partition &lt;/b&gt;&lt;/span&gt;&lt;b&gt;그리드(`GridCellCoord`)&lt;/b&gt;에 등록&lt;/li&gt;
&lt;li&gt;&lt;b&gt; &lt;/b&gt;&lt;span&gt;&lt;b&gt;`bSplitSectionsToMatchWorldPartitionRuntimeGrid`&lt;/b&gt; &lt;/span&gt;플래그로 자동 그리드 정렬&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;결정론&lt;span&gt;: &lt;/span&gt;낮음&lt;span&gt; &lt;b&gt;(&lt;/b&gt;&lt;/span&gt;&lt;b&gt;메시 편집 순서 의존)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1779928644164&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TArray&amp;lt;FVector3d&amp;gt; Vertices;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 3D 위치

TArray&amp;lt;FIndex3i&amp;gt; Triangles;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 삼각형 인덱스

TArray&amp;lt;FVector3f&amp;gt; Normals;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // 법선

TArray&amp;lt;FUVChannel&amp;gt; ChannelUVs;&amp;nbsp;&amp;nbsp;&amp;nbsp; // UV 채널

TArray&amp;lt;FWeightLayer&amp;gt; WeightLayers; // 가중치 레이어&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;2.3 &lt;/span&gt;하이트맵의 역할&lt;span&gt; &amp;mdash; &lt;/span&gt;일회성 임포트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Mesh Terrain&lt;/span&gt;에서 하이트맵은&lt;span&gt; &quot;&lt;/span&gt;시작점&lt;span&gt;&quot; &lt;/span&gt;임포트 포맷일 뿐&lt;span&gt;, &lt;/span&gt;런타임 데이터 모델이 아니다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`FHeightmapImporter`&lt;/span&gt;가&lt;span&gt; PNG/RAW &lt;/span&gt;하이트맵을&lt;span&gt; `TArray64&amp;lt;uint16&amp;gt;`&lt;/span&gt;로 로드&lt;span&gt; (MeshPartitionHeightmapImporter.h:22)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;바이리니어 보간으로&lt;span&gt; UV &amp;rarr; &lt;/span&gt;정점&lt;span&gt; XY + Z(&lt;/span&gt;높이&lt;span&gt;) &lt;/span&gt;매핑&lt;span&gt; (MeshPartitionHeightmapImporter.cpp:418)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;쿼드 분할&lt;span&gt; &amp;rarr; &lt;/span&gt;삼각형 그리드 생성&lt;span&gt; (MeshPartitionHeightmapImporter.cpp:432)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;섹션별 메시를 병렬 스레드로 빌드 후&lt;span&gt; `AMeshPartition` &lt;/span&gt;액터에 머지&lt;span&gt; (MeshPartitionHeightmapImporter.cpp:235)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;b&gt;임포트 직후 하이트맵 텍스처 폐기&lt;/b&gt;&lt;span&gt;.&amp;nbsp;&lt;/span&gt;이후 모든 편집은 정점 단위로 진행&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;에디터 메인 진입점&lt;span&gt;: `BeginHeightmapImport` &lt;/span&gt;액션&lt;span&gt; (MeshTerrainMode.cpp:1023)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;2.4 &lt;/span&gt;차이 매트릭스&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;Landscape&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;Mesh Terrain&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;높이 저장&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;영구&lt;span&gt; `UTexture2D` + `uint16` mip &lt;/span&gt;배열&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;임포트 시점만&lt;span&gt;; &lt;/span&gt;이후 삼각형 메시&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;메시 형태&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;암묵적&lt;span&gt; 2D &lt;/span&gt;그리드&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;명시적&lt;span&gt; `FDynamicMesh3`&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;오버행&lt;span&gt;/&lt;/span&gt;동굴&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;불가능&lt;span&gt; (2D &lt;/span&gt;함수 제약&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;가능&lt;span&gt; (&lt;/span&gt;완전&lt;span&gt; 3D)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;분기 토폴로지&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;불가능&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;가능&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;LOD&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;컴포넌트별 결정론&lt;span&gt; mip&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Nanite fallback 의존&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;스트리밍&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;셀별 액터&lt;span&gt; (&lt;/span&gt;효율적&lt;span&gt; 2D)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;섹션별&lt;span&gt; (&lt;/span&gt;그리드 인식 약함&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;편집&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;브러시 페인팅&lt;span&gt; (&lt;/span&gt;높이만&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;모디파이어 스택&lt;span&gt; (&lt;/span&gt;비파괴&lt;span&gt; + &lt;/span&gt;지오메트리 변형&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;결정론성&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;높음&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;낮음&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt;3. MeshTerrainMode 에디터 구조&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;3.1 &lt;/span&gt;메인 클래스&lt;/h2&gt;
&lt;pre id=&quot;code_1779928683608&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MeshTerrainMode.h:38
class UMeshTerrainMode : public UBaseLegacyWidgetEdMode, 
public ILegacyEdModeSelectInterface&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;`UBaseLegacyWidgetEdMode` &lt;/span&gt;&lt;/b&gt;기반 에디터 모드&lt;span&gt;. ModelingToolset &lt;/span&gt;인터랙티브 툴 인프라&lt;span&gt;(`UInteractiveToolBuilder`, `UInteractiveTool`)&lt;/span&gt;를 사용한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;3.2 &lt;/span&gt;서브모드&lt;span&gt; 6&lt;/span&gt;종&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;서브모드&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;목적&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;주요 툴&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;연산 모델&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Create&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;절차적 메시 생성&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;CreateMegaMeshRectangle, HeightmapImport, DrawSpline, Pattern&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;메시 신규 생성 또는 하이트맵 임포트&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Edit&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;메시 토폴로지&lt;span&gt;/&lt;/span&gt;변환&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Convert/Expand/Split/Stitch/Merge MegaMesh, ResectionMesh, EditPivot, BakeTransform, AttributeEditor&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;구조적 편집&lt;span&gt; (&lt;/span&gt;변위 없음&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Sculpt&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;정점&lt;span&gt; + &lt;/span&gt;하이트맵 스컬핑&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;[Sculpt] Offset/Move/Smooth/Pinch/Flatten + [Height] HeightSculpt/HeightSmooth/HeightFlatten/ZeroHeight/SlopeErode&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;정점 푸시&lt;span&gt;-&lt;/span&gt;풀&lt;span&gt;(3D) + Z&lt;/span&gt;잠금 브러시&lt;span&gt;(&lt;/span&gt;혼합&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Shapes&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;CSG 부울 모디파이어&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Add Box/Sphere/Cylinder/Capsule/Cone/Torus/Rectangle/Disc&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;모디파이어로 적용되는&lt;span&gt; CSG&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Paint&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;어트리뷰트 페인팅&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;MeshAttributePaint, MeshVertexPaint, AttributeEditor&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;정점 색상&lt;span&gt;/&lt;/span&gt;어트리뷰트&lt;span&gt; (&lt;/span&gt;비기하&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Modifiers&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;절차적 변형 스택&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;MeshProject, TexturePatch, Spline, ProjectMeshLayers, Lattice, Noise, Patch / Boolean / Remesh / SplineRemesh&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;변위&lt;span&gt; + &lt;/span&gt;리메시&lt;span&gt; + CSG&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;3.3 Sculpt &lt;/span&gt;서브모드의 듀얼 팔레트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Sculpt&lt;/span&gt;는 두 가지 모드를 함께 제공한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[Sculpt &lt;/span&gt;팔레트&lt;span&gt;] &lt;/span&gt;정점을 노멀 방향으로 푸시&lt;span&gt;/&lt;/span&gt;풀&lt;span&gt; &amp;mdash; **&lt;/span&gt;완전&lt;span&gt; 3D** &lt;/span&gt;변형&lt;span&gt;, &lt;/span&gt;오버행 생성 가능&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;[Height &lt;/span&gt;팔레트&lt;span&gt;] Z&lt;/span&gt;축에만 작용하는 변위&lt;span&gt; &amp;mdash; Landscape &lt;/span&gt;스타일의 하이트필드 편집&lt;span&gt;, &lt;/span&gt;하이트맵 임포트 후에 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 분리는&lt;span&gt; &quot;&lt;/span&gt;기본 하이트맵 기반 지형&lt;span&gt;&quot;&lt;/span&gt;과&lt;span&gt; &quot;3D &lt;/span&gt;스컬프트 영역&lt;span&gt;&quot;&lt;/span&gt;을 동시에 한 자산 안에서 다루기 위함이다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;3.4 &lt;/span&gt;모디파이어 스택&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;모디파이어&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;범주&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;효과&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;토폴로지 변경&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;URemeshModifier&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;리토폴로지&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Uniform/Adaptive 링&lt;span&gt;, Red-Green &lt;/span&gt;테셀레이션&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;O&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;UBooleanModifier&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;CSG&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;Union / Subtract / Trim&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;O (구멍&lt;span&gt;/&lt;/span&gt;오버행&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;UMeshProjectModifier&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;변위&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;참조 메시에&lt;span&gt; Z &lt;/span&gt;투영&lt;span&gt; (Raise/Lower/Set)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;X&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;USplineModifier&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;변위&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;스플라인 경로 따라 변형&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;X&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;UPatchModifier&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;로컬 패치&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;국지 메시 부분 교체&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;O (국소&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;UTexturePatchModifier&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;변위&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;텍스처 기반 변위&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;X&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;UNoiseModifier&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;변위&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;절차적 정점 오프셋&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;X&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;ULatticeModifier&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;변위&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;FFD(자유 형식 변형&lt;span&gt;) &lt;/span&gt;격자&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;X&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 모델&lt;span&gt;: `FTransformerPipeline`&lt;/span&gt;이 모디파이어 체인을 비동기 태스크로 실행&lt;span&gt;. &lt;/span&gt;결과는&lt;span&gt; `FMeshData`&lt;/span&gt;에 베이크&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt;4. 두 절벽 합치기 &amp;mdash; 복셀/SDF 알고리즘&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;4.1 &lt;/span&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 개의 분리된 절벽 메시를 토폴로지 일관성 있게&lt;span&gt; (&lt;/span&gt;오버행 포함&lt;span&gt;) &lt;/span&gt;하나로 합치는 작업은&lt;span&gt; **&lt;/span&gt;에디터에서 가능&lt;span&gt;**&lt;/span&gt;하다&lt;span&gt;. &lt;/span&gt;내부적으로 복셀화&lt;span&gt; + &lt;/span&gt;마칭큐브&lt;span&gt; + &lt;/span&gt;협대역&lt;span&gt; SDF&lt;/span&gt;가 사용된다&lt;span&gt;. **&lt;/span&gt;런타임에서는 불가능&lt;span&gt;** &amp;mdash; &lt;/span&gt;결과는 베이크된 삼각형 메시로만 유통된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;4.2 &lt;/span&gt;사용 가능 도구&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;툴&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;클래스&lt;span&gt;/Op&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;위치&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Voxel Boolean&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;FVoxelBooleanMeshesOp&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;VoxelBooleanMeshesOp.h:21&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;복셀 그리드&lt;span&gt; union/diff/intersect&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Voxel Merge&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;FVoxelMergeMeshesOp&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;VoxelMergeMeshesOp.h:21&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;복셀 기반 메시 통합&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Iso-Surface Offset&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;IsoSurfaceD&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;MergeMeshesTool.cpp:74&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;암시적 표면 오프셋 제어&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Narrowband SDF&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;TSweepingMeshSDF&amp;lt;&amp;gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;MeshSimpleShapeApproximation.cpp:551&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;NarrowBand_SpatialFloodFill 모드&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;Marching Cubes&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;MarchingCubesGridScale 설정&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;MeshSimpleShapeApproximation.cpp:398&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;SDF &amp;rarr; 메시 추출&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;ProxyLOD CSG&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;IVoxelBasedCSG::CreateCSGTool()&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;VoxelMergeMeshesOp.cpp:48&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;ProxyLOD 통합&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;4.3 MeshTerrainMode&lt;/span&gt;에서 등록된 액션&lt;/h2&gt;
&lt;pre id=&quot;code_1779928726317&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// MeshTerrainMode.cpp:918
ToolManager-&amp;gt;RegisterToolType(&quot;BeginVoxelMergeTool&quot;, ...);

// MeshTerrainMode.cpp:921
ToolManager-&amp;gt;RegisterToolType(&quot;BeginVoxelBooleanTool&quot;, ...);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;MeshTerrainModeManagerActions.cpp:184-185 &lt;/span&gt;에서&lt;span&gt; &quot;Voxel Boolean&quot; &lt;/span&gt;및&lt;span&gt; &quot;Voxel Merge&quot; &lt;/span&gt;명령어가&lt;span&gt; UI&lt;/span&gt;에 노출된다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;4.4 &lt;/span&gt;알고리즘 동작 흐름&lt;span&gt; (&lt;/span&gt;두 절벽&lt;span&gt; &amp;rarr; &lt;/span&gt;단일 메시&lt;span&gt;)&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt; &lt;/span&gt;입력&lt;span&gt;: `FDynamicMesh3` &amp;times; 2 (&lt;/span&gt;절벽&lt;span&gt; A, &lt;/span&gt;절벽&lt;span&gt; B)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt; &lt;/span&gt;복셀화&lt;span&gt;: &lt;/span&gt;각 메시를 협대역&lt;span&gt; SDF&lt;/span&gt;로 변환&lt;span&gt; (`TSweepingMeshSDF&amp;lt;&amp;gt;` + NarrowBand_SpatialFloodFill)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt; CSG &lt;/span&gt;연산&lt;span&gt;: &lt;/span&gt;복셀 도메인에서&lt;span&gt; union / subtract / intersect / merge&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt; &lt;/span&gt;마칭 큐브&lt;span&gt;: &lt;/span&gt;결과&lt;span&gt; SDF&lt;/span&gt;의&lt;span&gt; iso-surface (`IsoSurfaceD` &lt;/span&gt;오프셋&lt;span&gt;) &lt;/span&gt;추출&lt;span&gt; &amp;rarr; &lt;/span&gt;새&lt;span&gt; `FDynamicMesh3`&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt; &lt;/span&gt;옵션&lt;span&gt;: &lt;/span&gt;리메시 모디파이어로 정점 분포 정리&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt; &lt;/span&gt;결과 베이크&lt;span&gt;: `FMeshData`&lt;/span&gt;로 압축되어&lt;span&gt; `ACompiledSection` &lt;/span&gt;액터에 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;4.5 &lt;/span&gt;런타임 제약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`ModelingOperatorsEditorOnly` &lt;/span&gt;모듈은&lt;span&gt; **&lt;/span&gt;에디터 전용&lt;span&gt;** &amp;mdash; &lt;/span&gt;패키지에 포함되지 않음&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`FMeshBoolean` (GeometryCore &lt;/span&gt;런타임 모듈&lt;span&gt;)&lt;/span&gt;은 존재하지만&lt;span&gt; `MeshPartitionEditor/Modifiers/MeshPartitionBooleanModifier.cpp:6`&lt;/span&gt;에서만 사용&lt;span&gt; &amp;mdash; &lt;/span&gt;에디터 모디파이어로 한정&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`MeshPartition.Build.cs` (&lt;/span&gt;런타임&lt;span&gt;)&lt;/span&gt;에는&lt;span&gt; Boolean &lt;/span&gt;모디파이어 모듈 없음&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;런타임 데이터는 베이크된&lt;span&gt; `FMeshData` &lt;/span&gt;삼각형 메시뿐&lt;span&gt;. SDF/&lt;/span&gt;복셀 그리드는 저장되지 않음&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;결론&lt;span&gt;: &quot;&lt;/span&gt;두 절벽 합치기&lt;span&gt;&quot;&lt;/span&gt;는&lt;span&gt; **&lt;/span&gt;에디터 워크플로 한정&lt;span&gt;**. &lt;/span&gt;게임 플레이 중 동적 합성은 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt;5. 채널 / 충돌 / 렌더링&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;5.1 &lt;/span&gt;채널 시스템&lt;span&gt; (&lt;/span&gt;메타데이터&lt;span&gt;)&lt;/span&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`FChannelMap` / `FChannelName` &amp;mdash; &lt;/span&gt;명명된 어트리뷰트&lt;span&gt; (&lt;/span&gt;가중치 레이어 등&lt;span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;커스텀 프리미티브 데이터&lt;span&gt;(Custom Primitive Data)&lt;/span&gt;로 머티리얼 룩업&lt;span&gt; &amp;mdash; 5&lt;/span&gt;비트 슬롯 패킹&lt;span&gt;, &lt;/span&gt;최대&lt;span&gt; 24&lt;/span&gt;채널&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;채널 텍스처는&lt;span&gt; UV &lt;/span&gt;공간에 래스터화&lt;span&gt; (&lt;/span&gt;월드 공간 복셀 아님&lt;span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Virtual Texture fallback &lt;/span&gt;컴포넌트&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;타일 기반 머티리얼 캐시&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;5.2 &lt;/span&gt;충돌&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`UMeshPartitionCollisionComponent`&lt;/span&gt;가&lt;span&gt; `FTriMeshCollisionData` &lt;/span&gt;보유&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`IInterface_CollisionDataProvider` &lt;/span&gt;구현&lt;span&gt; &amp;mdash; &lt;/span&gt;복합 충돌&lt;span&gt;(Complex Collision)&lt;/span&gt;을 삼각형 메시에서 직접 추출&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;`bCanEverAffectNavigation` &lt;/span&gt;플래그로&lt;span&gt; NavMesh &lt;/span&gt;영향 제어&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;채널별 물리 머티리얼 매핑&lt;span&gt; (&lt;/span&gt;가중치 폴백&lt;span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;5.3 &lt;/span&gt;컴포넌트 계층&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;클래스&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;&lt;b&gt;파일&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;AMeshPartition&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;루트 액터&lt;span&gt;, &lt;/span&gt;정의&lt;span&gt; + &lt;/span&gt;컴포넌트 보유&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;MeshPartition.h:24&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;UMeshPartitionDefinition&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;공유 설정 에셋&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;MeshPartitionDefinition.h:149&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;UMeshPartitionComponent&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;PrimitiveComponent 기반&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;(Runtime)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;UMeshPartitionStaticMeshComponent&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;StaticMeshComponent 래퍼&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;(Runtime)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;UMeshPartitionCollisionComponent&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;충돌 전용&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;MeshPartitionCollisionComponent.h:35&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span&gt;ACompiledSection&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;런타임 결과 액터&lt;span&gt; (World Partition)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span&gt;MeshPartitionCompiledSection.h:163&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt;6. 제약사항 및 결론&lt;/b&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;6.1 &lt;/span&gt;사용 시 제약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Experimental &lt;/span&gt;단계&lt;span&gt; &amp;mdash; API &lt;/span&gt;변경 가능성 큼&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;기본 비활성화&lt;span&gt; (`EnabledByDefault: false`)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;StylusInput &lt;/span&gt;의존&lt;span&gt; &amp;mdash; &lt;/span&gt;스타일러스 디바이스 지원 권장&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;World Partition &lt;/span&gt;활성 환경 필요&lt;span&gt; &amp;mdash; `bSplitSectionsToMatchWorldPartitionRuntimeGrid`&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;자체&lt;span&gt; LOD &lt;/span&gt;없음&lt;span&gt; &amp;mdash; Nanite &lt;/span&gt;의존&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;런타임 동적 토폴로지 변경 불가&lt;span&gt; &amp;mdash; &lt;/span&gt;모든&lt;span&gt; CSG/&lt;/span&gt;복셀은 에디터에서 베이크&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;6.2 Mesh Terrain&lt;/span&gt;이 적합한 시나리오&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;오버행&lt;span&gt;/&lt;/span&gt;동굴&lt;span&gt;/&lt;/span&gt;아치 등 비&lt;span&gt;-&lt;/span&gt;함수형 지형이 필요한 환경&lt;span&gt; (&lt;/span&gt;절벽&lt;span&gt;, &lt;/span&gt;협곡&lt;span&gt;, &lt;/span&gt;동굴 입구&lt;span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;아티스트가 스컬프팅 워크플로로 직접 모델링하는 핸드크래프트 지형&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;CSG&lt;/span&gt;로 절차적 절벽 결합&lt;span&gt;/&lt;/span&gt;구멍 뚫기가 필요한 레벨&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Nanite &lt;/span&gt;활용 가능한&lt;span&gt; PC/&lt;/span&gt;콘솔 타깃&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;6.3 Landscape&lt;/span&gt;를 유지해야 하는 시나리오&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;거대한&lt;span&gt; 2D &lt;/span&gt;함수형 지형&lt;span&gt; (km &lt;/span&gt;단위 평원&lt;span&gt;, &lt;/span&gt;언덕&lt;span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;결정론적&lt;span&gt; LOD &lt;/span&gt;모핑이 중요한 게임&lt;span&gt; (&lt;/span&gt;모바일&lt;span&gt;/&lt;/span&gt;멀티플레이&lt;span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Layer &lt;/span&gt;기반 텍스처 블렌딩 워크플로&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;스플랫맵&lt;span&gt; + &lt;/span&gt;풀&lt;span&gt;/&lt;/span&gt;디칼 시스템에 강하게 의존하는 프로젝트&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;6.4 &lt;/span&gt;두 시스템 병행 권장&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;UE 5.8 &lt;/span&gt;단계에서는&lt;span&gt; &quot;&lt;/span&gt;기본 지형&lt;span&gt; = Landscape, &lt;/span&gt;특수 오버행 영역&lt;span&gt; = Mesh Terrain&quot; &lt;/span&gt;하이브리드 접근이 현실적이다&lt;span&gt;. Mesh Terrain&lt;/span&gt;은 절벽&lt;span&gt;/&lt;/span&gt;동굴&lt;span&gt;/&lt;/span&gt;아치 등 함수형으로 표현할 수 없는 국지 지형에 한정 사용하고&lt;span&gt;, &lt;/span&gt;평원&lt;span&gt;/&lt;/span&gt;언덕은&lt;span&gt; Landscape&lt;/span&gt;의 효율적&lt;span&gt; 2D &lt;/span&gt;스트리밍과 결정론적&lt;span&gt; LOD&lt;/span&gt;를 활용한다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;&lt;b&gt;부록 A. 핵심 파일 경로&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Plugins/Experimental/MeshTerrainMode/MeshTerrainMode.uplugin&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Plugins/Experimental/MeshTerrainMode/Source/MeshTerrainMode/Private/MeshTerrainMode.h&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Plugins/Experimental/MeshTerrainMode/Source/MeshTerrainMode/Private/Submodes/*&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Plugins/Experimental/MeshPartition/MeshPartition.uplugin&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Plugins/Experimental/MeshPartition/Source/MeshPartition/Public/MeshPartitionMeshData.h&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Plugins/Experimental/MeshPartition/Source/MeshPartitionEditor/Public/MeshPartitionHeightmapImporter.h&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Plugins/Runtime/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Public/CompositionOps/VoxelBooleanMeshesOp.h&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Plugins/Runtime/MeshModelingToolset/Source/ModelingOperatorsEditorOnly/Public/CompositionOps/VoxelMergeMeshesOp.h&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Plugins/Runtime/GeometryProcessing/Source/DynamicMesh/Private/ShapeApproximation/MeshSimpleShapeApproximation.cpp&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Source/Runtime/Landscape/Classes/LandscapeComponent.h&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;부록 B. 공식 문서&lt;/b&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Epic Dev Community: &quot;Introduction to Mesh Terrain&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;URL: &lt;a href=&quot;https://dev.epicgames.com/community/learning/knowledge-base/nK7J/unreal-engine-introduction-to-mesh-terrain#meshterrainmode&quot;&gt;https://dev.epicgames.com/community/learning/knowledge-base/nK7J/unreal-engine-introduction-to-mesh-terrain#meshterrainmode&lt;/a&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&lt;b&gt;부록 C. 향후 조사 권장&lt;/b&gt;&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Nanite &lt;/span&gt;적용 시&lt;span&gt; LOD &lt;/span&gt;비용 측정&lt;span&gt; &amp;mdash; Landscape mip &lt;/span&gt;시스템과 비교 벤치마크&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;Voxel Boolean&lt;/span&gt;의 입력 메시 해상도별 베이크 시간&lt;span&gt;/&lt;/span&gt;메모리 프로파일&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;World Partition &lt;/span&gt;셀 경계에서의 메시 이음새&lt;span&gt;(seam) &lt;/span&gt;처리 방식 검증&lt;/li&gt;
&lt;li&gt;&lt;span&gt; &lt;/span&gt;멀티 유저 에디터에서의 모디파이어 스택 동시 편집 충돌 정책&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/169</guid>
      <comments>https://develop-4-art.tistory.com/169#entry169comment</comments>
      <pubDate>Thu, 28 May 2026 09:39:32 +0900</pubDate>
    </item>
    <item>
      <title>[Graphics] 복셀 렌더링 - 3 &amp;lt;Spatial Hash Map&amp;gt;</title>
      <link>https://develop-4-art.tistory.com/168</link>
      <description>&lt;h1&gt;Spatial Hash Map (공간 해시 맵)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://develop-4-art.tistory.com/167&quot;&gt;[Graphics] 복셀 렌더링 - 2 &amp;lt;언리얼 SVT&amp;gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1775795557216&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Graphics] 복셀 렌더링 - 2 &amp;lt;언리얼 SVT&amp;gt;&quot; data-og-description=&quot;기존 포스팅 내용 요약[Graphics] 복셀 렌더링 - 1 본격적인 이야기에 앞서, 지난 포스트에서 다루었던 '복셀(Voxel) 데이터를 메모리에 욱여넣는 방법'을 짧게 되짚어 보겠습니다. 거대한 3D 공간에서&quot; data-og-host=&quot;develop-4-art.tistory.com&quot; data-og-source-url=&quot;https://develop-4-art.tistory.com/167&quot; data-og-url=&quot;https://develop-4-art.tistory.com/167&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/I2qLu/dJMb9hC2kun/6g6VPISzOEOKQMYBdOq5Wk/img.jpg?width=230&amp;amp;height=203&amp;amp;face=0_0_230_203,https://scrap.kakaocdn.net/dn/V15st/dJMb9frGuOJ/KVC1DvJ1sVhJ3KpuDaFZJ0/img.jpg?width=230&amp;amp;height=203&amp;amp;face=0_0_230_203,https://scrap.kakaocdn.net/dn/hUMNb/dJMb8XR6HZl/ik8pmWlYkKfvHJwajBBJf1/img.png?width=1024&amp;amp;height=559&amp;amp;face=0_0_1024_559&quot;&gt;&lt;a href=&quot;https://develop-4-art.tistory.com/167&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://develop-4-art.tistory.com/167&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/I2qLu/dJMb9hC2kun/6g6VPISzOEOKQMYBdOq5Wk/img.jpg?width=230&amp;amp;height=203&amp;amp;face=0_0_230_203,https://scrap.kakaocdn.net/dn/V15st/dJMb9frGuOJ/KVC1DvJ1sVhJ3KpuDaFZJ0/img.jpg?width=230&amp;amp;height=203&amp;amp;face=0_0_230_203,https://scrap.kakaocdn.net/dn/hUMNb/dJMb8XR6HZl/ik8pmWlYkKfvHJwajBBJf1/img.png?width=1024&amp;amp;height=559&amp;amp;face=0_0_1024_559');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Graphics] 복셀 렌더링 - 2 &amp;lt;언리얼 SVT&amp;gt;&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;기존 포스팅 내용 요약[Graphics] 복셀 렌더링 - 1 본격적인 이야기에 앞서, 지난 포스트에서 다루었던 '복셀(Voxel) 데이터를 메모리에 욱여넣는 방법'을 짧게 되짚어 보겠습니다. 거대한 3D 공간에서&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;develop-4-art.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 포스트였던 언리얼 SVT에 이어 이제 트리구조에서 벗어난 해시맵에 대한 이야기를 다뤄보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1.트리(Tree)의 한계와 Spatial Hash Map의 등장&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SVO, NanoVDB, SVT는 모두 '위에서 아래로 길을 찾아가는' 구조입니다. 지도를 보고 목적지를 찾아가는 것과 같죠.&lt;br /&gt;하지만 데이터가 매 프레임 파괴되고 생성되는 동적인 환경(Dynamic Environment)에서는 지도가 수시로 바뀝니다. 지도를 고쳐 그리는 시간(Re-build)이 렌더링 시간보다 더 걸리는 배보다 배꼽이 더 큰 상황이 벌어집니다.&lt;br /&gt;그래서 그래픽스 프로그래머들은 길 찾기를 포기하고, &lt;b&gt;&quot;좌표를 부르면 즉시 데이터가 튀어나오는 방법&quot;&lt;/b&gt;을 선택했습니다. 그것이 바로 공간 해시 맵입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;O(1)의 마법: 해시 함수(Hash Function)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;작동 원리: 3D 공간의 특정 좌표 $(x, y, z)$를 복잡한 수학 공식(해시 함수)에 집어넣습니다. 이 함수는 해당 좌표를 1차원 메모리 배열의 특정 인덱스(예: 705번 방)로 즉시 변환해 줍니다.&lt;/li&gt;
&lt;li&gt;압도적 장점: 트리의 깊이(Depth)를 타고 내려갈 필요가 없습니다. 무조건 단 한 번의 연산으로 끝납니다. 즉, 수학적으로 가장 완벽한 $O(1)$의 읽기/쓰기 속도를 자랑합니다.&lt;/li&gt;
&lt;li&gt;무한한 공간: 전체 월드의 크기를 미리 정해둘 필요가 없습니다. 우주 끝에 있는 좌표라도 해시 함수에 넣기만 하면 배열의 어느 한 칸으로 매핑되기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;가장 널리 쓰이는 해시 함수 형태(Teschner et al., 2003, 테슈너의 충돌관련 논문집)는 다음과 같습니다.&lt;/b&gt;&lt;/p&gt;
&lt;div data-path-to-node=&quot;13&quot;&gt;
&lt;div data-math=&quot;h(C_x, C_y, C_z) = ((C_x \cdot p_1) \oplus (C_y \cdot p_2) \oplus (C_z \cdot p_3)) \pmod{N}&quot;&gt;$$h(C_x, C_y, C_z) = ((C_x \cdot p_1) \oplus (C_y \cdot p_2) \oplus (C_z \cdot p_3)) \pmod{N}$$&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-index-in-node=&quot;0&quot; data-math=&quot;p_1, p_2, p_3&quot;&gt;$p_1, p_2, p_3$&lt;/span&gt;: 매우 큰 소수 (서로 다른 임의의 소수)&lt;/li&gt;
&lt;li&gt;&lt;span data-index-in-node=&quot;0&quot; data-math=&quot;\oplus&quot;&gt;$\oplus$&lt;/span&gt;: 비트 XOR 연산자&lt;/li&gt;
&lt;li&gt;&lt;span data-index-in-node=&quot;0&quot; data-math=&quot;N&quot;&gt;$N$&lt;/span&gt;: 1차원 배열(해시 테이블)의 전체 크기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15&quot;&gt;추천하는 소수 값 (&lt;span data-index-in-node=&quot;11&quot; data-math=&quot;p_1, p_2, p_3&quot;&gt;$p_1, p_2, p_3$&lt;/span&gt;)&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;보통 충돌(Collision)을 최소화하기 위해 실험적으로 검증된 다음과 같은 큰 소수들을 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span data-index-in-node=&quot;0&quot; data-math=&quot;p_1 = 73856093&quot;&gt;$p_1 = 73856093$&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-index-in-node=&quot;0&quot; data-math=&quot;p_2 = 19349663&quot;&gt;$p_2 = 19349663$&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span data-index-in-node=&quot;0&quot; data-math=&quot;p_3 = 83492791&quot;&gt;$p_3 = 83492791$&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;17&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;왜 이런 공식을 사용할까요?&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;19&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,0,0&quot;&gt;소수 곱셈:&lt;/b&gt; 좌표값에 불규칙성을 부여하여, 공간상에 규칙적으로 배치된 물체들이 해시 테이블의 같은 인덱스로 몰리는 현상(Clustering)을 방지합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,1,0&quot;&gt;XOR 연산 (&lt;span data-index-in-node=&quot;8&quot; data-math=&quot;\oplus&quot;&gt;$\oplus$&lt;/span&gt;):&lt;/b&gt; 덧셈 대신 XOR을 사용하면 각 좌표의 비트 정보가 섞이면서 해시값이 더 고르게 분포됩니다. 비트 연산이므로 연산 속도도 매우 빠릅니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;19,2,0&quot;&gt;모듈러 연산 (&lt;span data-index-in-node=&quot;8&quot; data-math=&quot;\pmod{N}&quot;&gt;$\pmod{N}$&lt;/span&gt;):&lt;/b&gt; 계산된 거대한 해시값을 우리가 실제로 메모리에 할당한 1차원 배열의 크기(&lt;span data-index-in-node=&quot;60&quot; data-math=&quot;N&quot;&gt;$N$&lt;/span&gt;) 안으로 구겨 넣는 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 해시 맵의 치명적인 단점 (Trade-off)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세상에 공짜는 없습니다. $O(1)$의 쾌감을 얻은 대신 아주 치명적인 두 가지를 포기해야 했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해시 충돌 (Hash Collision)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공간은 무한하지만 우리가 가진 1차원 배열(메모리)의 크기는 유한합니다. 완전히 다른 두 3D 좌표를 해시 함수에 넣었는데, 하필 똑같이 '705번 방'이라는 결과가 나올 수 있습니다. 이를 해결하기 위해 연결 리스트(Linked List)로 방을 늘리거나 다른 빈방을 찾아 헤매는 추가 연산이 필요해져 성능이 널뛰기할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;공간 지역성의 붕괴 (Loss of Spatial Locality)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPU는 메모리를 한 번 읽을 때, 요청한 데이터 주변의 데이터를 뭉텅이로 캐시(Cache)에 올려서 속도를 냅니다. SVO나 NanoVDB는 이웃 복셀들이 메모리 상에서도 이웃해 있었습니다.&lt;br /&gt;하지만 해시 맵을 통과하면? 현실의 3D 공간에서는 바로 옆에 딱 붙어있는 이웃 복셀이라도, 메모리 배열 상에서는 1번 방과 100만 번 방처럼 극과 극으로 흩어지게 됩니다. 캐시 미스(Cache Miss)가 미친 듯이 발생하며 렌더링 성능이 바닥을 칩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 활용&amp;nbsp;사례 1: 마인크래프트의 &quot;청크(Chunk) 기반 하이브리드 해시 맵&quot;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=7OHrE2krQU8&amp;amp;list=PLVsTSlfj0qsWEJ-5eMtXsYp03Y9yF1dEn&amp;amp;index=2&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://www.youtube.com/watch?v=7OHrE2krQU8&amp;amp;list=PLVsTSlfj0qsWEJ-5eMtXsYp03Y9yF1dEn&amp;amp;index=2&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=7OHrE2krQU8&amp;amp;list=PLVsTSlfj0qsWEJ-5eMtXsYp03Y9yF1dEn&amp;amp;index=2&quot; data-video-thumbnail=&quot;https://blog.kakaocdn.net/dna/mlJ79/dJMb8SXyE7t/AAAAAAAAAAAAAAAAAAAAAAkFz54T54FVebdwabQPDqzu02ouhbwCVvAGqxwTqF8b/img.jpg?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1777561199&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=IwPtH%2F%2FNAcd8l2IzaHezHL0AhDU%3D&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;Make Minecraft In Unity 3D Tutorial - 02 - The First Chunk&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/7OHrE2krQU8&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;그렇다면 마인크래프트는 이 무시무시한 캐시 미스를 뚫고 어떻게 그 수많은 블록을 실시간으로 렌더링할까요? 정답은 &quot;낱개의 복셀을 해싱하지 않고, 덩어리(Chunk)를 해싱한다&quot;는 천재적인 타협안에 있습니다.&lt;br /&gt;마인크래프트는 무한한 월드를 보통 $16 \times 384 \times 16$ 크기의 거대한 기둥인 &lt;b&gt;청크(Chunk)&lt;/b&gt; 단위로 썰어버립니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;거시적(Macro) 관리 - 해시 맵:&lt;/b&gt; 플레이어가 이동할 때 (청크의 X 좌표, 청크의 Z 좌표)를 해시 키(Key)로 삼아, 수만 개의 청크 중 내가 원하는 청크를 $O(1)$의 속도로 즉시 찾고 버립니다. (월드 스트리밍에 최적화)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;미시적(Micro) 관리 - 1차원 연속 배열:&lt;/b&gt; 청크를 열어보면 그 안에는 해시 맵이 아니라, $16 \times 16 \times 16$ 단위로 쪼개진 순수한 연속 배열(Dense Array)이 들어있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과적으로&lt;/b&gt; 청크 덩어리를 찾을 때는 해시 맵으로 빛의 속도로 찾고, 렌더링을 위해 블록을 긁어올 때는 1차원 배열 덕분에 주변 블록들이 한 번에 CPU 캐시에 쏙 들어옵니다. (공간 지역성 완벽 방어)&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이제 청크를 이용한 메모리 구조 내 공간 지역성을 방어하는 방법은 익혔는데 그렇다면 렌더링 차원에서는 어떨까요?&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그리디 메싱(Greedy Meshing)이란 무엇인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://youtu.be/qnGoGq7DWMc?t=471&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;그리디 메싱 알고리즘 설명&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=qnGoGq7DWMc&quot; data-video-thumbnail=&quot;https://blog.kakaocdn.net/dna/pTxny/dJMb81GYame/AAAAAAAAAAAAAAAAAAAAANY_HXlw_ChLJmwADhNPhcmbRXp9KM9l4jg3RgqzFcyC/img.jpg?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1777561199&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=0wum76pLEHBC4IsB%2F30ktXC2HW8%3D&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;Blazingly Fast Greedy Mesher - Voxel Engine Optimizations&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/qnGoGq7DWMc&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;마인크래프트 같은 네모네모 복셀 게임에서 가장 멍청한 렌더링 방식은 &quot;모든 블록의 6개 면을 다 그리는 것&quot;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;겉에서 보이지 않는 땅속 블록의 면까지 그리면 GPU가 터집니다. (이걸 막는 게 Face Culling)&lt;/li&gt;
&lt;li&gt;Face Culling을 해서 겉에 보이는 면만 그린다고 쳐도, 평평한 벽돌 담장이 $10 \times 10$ 크기라면 $100$개의 작은 네모(200개의 트라이앵글)를 그려야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그리디 메싱&lt;/b&gt;은 여기서 한 발 더 나아간 &lt;b&gt;폴리곤 압축 알고리즘&lt;/b&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;핵심 원리:&lt;/b&gt; &quot;어차피 똑같은 재질(예: 돌 블록)의 면이 평평하게 이어져 있다면, 자잘한 네모 100개로 나누지 말고 &lt;b&gt;거대한 네모 1개로 합쳐서(Merge) 그리자!&lt;/b&gt;&quot;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과:&lt;/b&gt; $10 \times 10$ 담장을 그릴 때 200개였던 트라이앵글이, 단 &lt;b&gt;2개의 트라이앵글&lt;/b&gt;로 압축됩니다. 렌더링 부하가 기하급수적으로 줄어듭니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;청크(Chunk)와 그리디 메싱의 완벽한 시너지&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이 그리디 메싱 알고리즘은 왜 반드시&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;'청크'&lt;/b&gt;라는 단위 위에서 돌아가야만 할까요?&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;① 무한한 세계를 묶어주는 &quot;작업의 한계선&quot;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;549&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOcIFH/dJMb990ip6J/VfkTPCzvsS3fP72FBVKMLK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOcIFH/dJMb990ip6J/VfkTPCzvsS3fP72FBVKMLK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOcIFH/dJMb990ip6J/VfkTPCzvsS3fP72FBVKMLK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOcIFH%2FdJMb990ip6J%2FVfkTPCzvsS3fP72FBVKMLK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;206&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;549&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리디 메싱은 주변 블록들을 탐색하며 &quot;너 나랑 같은 블록이야? 그럼 합치자!&quot;를 반복하는 알고리즘입니다. 만약 청크라는 구역(Boundary)이 없다면, 평평한 바다가 끝없이 펼쳐져 있을 때 알고리즘은 멈추지 못하고 무한히 탐색을 계속하다가 메모리 한계로 뻗어버릴 것입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;청크의 역할:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;그리디 메싱에게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;딱 이 $16 \times 384 \times 16$ 공간 안에서만 합치기 작업을 수행해!&quot;&lt;/b&gt;라는 물리적인 제한 구역(Scope)을 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;② 비동기 멀티스레딩 (Asynchronous Multithreading)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마인크래프트에서 플레이어가 맵을 돌아다니면 새로운 지형이 계속 생성됩니다. 이때 메쉬를 굽는(Bake) 과정은 CPU 연산을 꽤 많이 먹습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;청크의 역할:&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;맵이 청크 단위로 썰려 있기 때문에, &quot;A 청크는 1번 스레드에서 그리디 메싱해!&quot;, &quot;B 청크는 2번 스레드에서 메싱해!&quot; 하고 병렬 처리(Multi-threading)를 던져주기가 완벽하게 좋습니다. 메인 스레드(프레임)를 멈추지 않고 백그라운드에서 메쉬를 조립할 수 있는 비결입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 style=&quot;color: #000000;&quot; data-ke-size=&quot;size20&quot;&gt;③ 부분 업데이트의 효율성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크리퍼가 터져서 블록 하나가 부서졌다고 상상해 봅시다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SVO처럼 전체 월드가 묶여있다면 월드 전체의 그리디 메싱을 다시 계산해야 할 수도 있습니다.&lt;/li&gt;
&lt;li&gt;하지만 청크가 있다면?&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;해당 블록이 속한 단 1개의 청크 메쉬만 파기하고, 그 청크만 그리디 메싱을 다시 돌려서 GPU에 새로 올려주면&quot;&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;끝입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;주의사항 : 두 개념이 만났을 때 발생하는 유일한 타협점 (Trade-off)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvShQ3/dJMcaibQ9H5/WPdKE0EO1fRyFlrFC5AJD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvShQ3/dJMcaibQ9H5/WPdKE0EO1fRyFlrFC5AJD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvShQ3/dJMcaibQ9H5/WPdKE0EO1fRyFlrFC5AJD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvShQ3%2FdJMcaibQ9H5%2FWPdKE0EO1fRyFlrFC5AJD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;480&quot; height=&quot;317&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 기반 그리디 메싱에는 아주 미세한 단점이 하나 있습니다. 바로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;'청크 경계선(Chunk Boundary)'&lt;/b&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A 청크와 B 청크가 맞닿아 있는 평평한 초원이 있다고 칩시다.&lt;/li&gt;
&lt;li&gt;시각적으로는 완벽하게 이어져 보이지만, 알고리즘은 A 청크와 B 청크를 따로 계산합니다.&lt;/li&gt;
&lt;li&gt;따라서 A 청크 끝에서 메쉬 합치기가 한 번 끊어지고, B 청크 시작에서 새로운 메쉬가 생깁니다. 즉, 청크와 청크가 만나는 경계선에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;폴리곤이 완벽하게 하나로 합쳐지지 않고 쪼개지는 현상&lt;/b&gt;이 발생합니다.&lt;/li&gt;
&lt;li&gt;하지만 이 정도의 폴리곤 낭비는 청크 시스템이 주는 막대한 성능적 이점에 비하면 아주 사소한 비용(Trade-off)으로 치부됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://youtu.be/qnGoGq7DWMc?t=471&quot;&gt;&lt;span&gt;청크슬라이싱&lt;/span&gt;&lt;/a&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://www.youtube.com/watch?v=7OHrE2krQU8&amp;amp;list=PLVsTSlfj0qsWEJ-5eMtXsYp03Y9yF1dEn&amp;amp;index=2&quot;&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=qnGoGq7DWMc&quot; data-video-thumbnail=&quot;https://blog.kakaocdn.net/dna/bOVHLz/dJMb9kmdE5A/AAAAAAAAAAAAAAAAAAAAAMpg_7QATv5qgc13ZK9oWqqTuRNFBlqUnHzBwVoHV31_/img.jpg?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1777561199&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=Qr3kk9puBfXB5Vtyhm3KGA2WmHo%3D&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;Blazingly Fast Greedy Mesher - Voxel Engine Optimizations&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/qnGoGq7DWMc&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리디 메싱 알고리즘의 이해&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h3 data-path-to-node=&quot;5&quot; data-ke-size=&quot;size23&quot;&gt;1. 그리디 메싱의 시각적 알고리즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;먼저 그리디 메싱이 청크(Chunk)의 단면을 어떻게 묶어내는지 시각적으로 이해해 봅시다.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1541&quot; data-origin-height=&quot;1180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sALis/dJMb9962b26/TtqHruXbJzrWLkzqiDwmCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sALis/dJMb9962b26/TtqHruXbJzrWLkzqiDwmCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sALis/dJMb9962b26/TtqHruXbJzrWLkzqiDwmCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsALis%2FdJMb9962b26%2FTtqHruXbJzrWLkzqiDwmCk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1541&quot; height=&quot;1180&quot; data-origin-width=&quot;1541&quot; data-origin-height=&quot;1180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot; data-path-to-node=&quot;7&quot;&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;7,0,0&quot; data-index-in-node=&quot;0&quot;&gt;탐색 시작&lt;/b&gt;: 좌표 0부터 시작하여 블록의 타입(예: 잔디)을 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;7,1,0&quot; data-index-in-node=&quot;0&quot;&gt;수직 확장 (Y축)&lt;/b&gt;: 바로 위 블록이 같은 '잔디' 타입이라면 쿼드(Quad)의 높이를 키웁니다. 공기(Air) 블록이나 다른 타입의 블록을 만날 때까지 수직으로 계속 확장합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;7,2,0&quot; data-index-in-node=&quot;0&quot;&gt;수평 확장 (X축)&lt;/b&gt;: 수직 확장이 끝난 묶음을 통째로 옆 열(Row)과 비교합니다. 옆 열의 블록들도 방금 묶은 블록들과 타입이 완벽히 일치한다면 가로로 확장합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;7,3,0&quot; data-index-in-node=&quot;0&quot;&gt;데이터 초기화&lt;/b&gt;: 이미 쿼드로 묶인 블록들은 다음 탐색에서 중복 처리되지 않도록 메모리에서 지워줍니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 직관적인 로직의 가장 큰 문제는 &lt;b&gt;&quot;루프(Loop)를 너무 많이 돈다&quot;&lt;/b&gt;는 것입니다. 매번 배열의 인덱스를 찾아가 데이터를 비교하는 과정은 CPU를 병목에 빠지게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;2. 패러다임의 전환: 32비트 정수(Integer)와 1차원 배열&lt;/h3&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;속도를 높이기 위해 3차원 배열에 모든 블록 ID를 쑤셔 넣는 방식을 버려야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;블록 타입별 평면 분리&lt;/b&gt;: 잔디 평면, 흙 평면 등 블록 타입별로 데이터를 완전히 분리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;바이너리(Binary) 변환&lt;/b&gt;: 이제 특정 위치에 잔디가 있으면 True(1), 없으면 False(0)로 표현할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,2,0&quot;&gt;비트 패킹(Bit Packing)&lt;/b&gt;: Boolean 값 32개는 &lt;b&gt;단 하나의 32비트 정수(32-bit Integer)&lt;/b&gt;에 들어갑니다!&lt;/li&gt;
&lt;/ul&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;545&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tbebW/dJMcabwZEVZ/NMkwhB5bnfqkAPxWMk8Oi1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tbebW/dJMcabwZEVZ/NMkwhB5bnfqkAPxWMk8Oi1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tbebW/dJMcabwZEVZ/NMkwhB5bnfqkAPxWMk8Oi1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtbebW%2FdJMcabwZEVZ%2FNMkwhB5bnfqkAPxWMk8Oi1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;360&quot; height=&quot;399&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;545&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-path-to-node=&quot;12&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 2차원 평면 데이터가 순수한 1차원 배열로 평탄화되며, 각 요소(정수 하나)가 32개의 복셀(한 줄)의 정보를 온전히 품게 됩니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;3. 비트 연산(Bitwise Manipulations)의 마법&lt;/h3&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;데이터가 32비트 정수로 변환되었으므로, 복잡한 반복문 대신 단일 CPU 명령어(Instruction)로 그리디 메싱을 처리할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;높이 구하기&lt;/b&gt;: CPU가 지원하는 Trailing Zero Bits(끝에서부터 연속된 0의 개수)를 세는 하드웨어 연산을 통해 첫 번째 블록의 Y 위치를 단숨에 찾아냅니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;수평 비교&lt;/b&gt;: 마스크(Mask)를 생성하여 옆 열의 32비트 정수와 AND 연산 등으로 비교하면, 수직으로 쌓인 블록들이 모두 일치하는지를 루프 없이 단 한 번의 연산으로 판별할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;이 비트 연산 기반의 탐색은 눈 깜짝할 사이에 끝나버릴 정도로 압도적으로 빠릅니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;4. 가장 무거운 짐 덜어내기: 페이스 컬링(Face Culling) 최적화&lt;/h3&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;그리디 메싱의 근본적인 지연 원인은 겉으로 드러난 면(Face)을 찾기 위해 이웃한 복셀을 끝없이 샘플링하는 과정이었습니다 (초기 버전에서는 청크 하나당 110만 번 샘플링 발생).&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해, 같은 축(Axis) 선상에 있는 블록들의 충돌 여부를 &lt;b&gt;비트 시프트(Bit Shift)&lt;/b&gt;로 계산합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;32개의 복셀 데이터를 담은 정수(Column)를 복사합니다.&lt;/li&gt;
&lt;li&gt;이 복사본을 왼쪽이나 오른쪽으로 &lt;b data-index-in-node=&quot;18&quot; data-path-to-node=&quot;20,1,0&quot;&gt;1비트 시프트(Shift)&lt;/b&gt; 합니다 (이웃 블록과의 위치를 맞추는 과정).&lt;/li&gt;
&lt;li&gt;시프트 된 비트들을 &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;20,2,0&quot;&gt;반전(Flip)&lt;/b&gt; 시킵니다 (빈 공간을 1로, 채워진 공간을 0으로).&lt;/li&gt;
&lt;li&gt;마지막으로 원본 정수와 조작된 정수를 &lt;b data-index-in-node=&quot;21&quot; data-path-to-node=&quot;20,3,0&quot;&gt;AND 연산&lt;/b&gt; 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;이 간단한 비트 조작만 거치면, &quot;오직 공기와 맞닿아 있는 겉면(Exposed Faces)&quot;의 위치만이 1(True)로 남게 됩니다. 무거운 루프 검사 없이 단 4줄의 연산으로 컬링이 끝나는 것입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1 = 블록 있음 (Solid)&lt;/li&gt;
&lt;li&gt;0 = 블록 없음 (Air)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size20&quot;&gt;Step 1. 원본 데이터 (Original)&lt;/h4&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjChcv3r96TAxUAAAAAHQAAAAAQ6gI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Plaintext&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;scheme&quot;&gt;&lt;code&gt;(위) [0] [1] [1] [0] [1] [0] (아래)
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;우리가 찾아야 할 정답은 어디일까요? 블록(1)의 바로 위(오른쪽)에 공기(0)가 있는 곳입니다. 즉, 맨 오른쪽의 [1]과 왼쪽 덩어리의 끝인 [1]이 겉면이 됩니다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;20&quot; data-ke-size=&quot;size20&quot;&gt;Step 2. 시프트 연산 (Shift)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;21&quot; data-ke-size=&quot;size16&quot;&gt;데이터를 통째로 오른쪽(아래)으로 한 칸 밀어버립니다. (블록들의 위치를 한 칸 이동시켜서 바로 옆 칸과 비교하기 위한 준비 단계입니다.)&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjChcv3r96TAxUAAAAAHQAAAAAQ6wI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Plaintext&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;원본:   [0] [1] [1] [0] [1] [0]
시프트: [0] [0] [1] [1] [0] [1]  &amp;lt;-- 모든 값이 오른쪽으로 한 칸 밀림
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node=&quot;23&quot; data-ke-size=&quot;size20&quot;&gt;Step 3. 비트 반전 (Flip)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;방금 밀어버린 시프트 데이터의 0과 1을 완전히 뒤집어 버립니다. (~ 연산)&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjChcv3r96TAxUAAAAAHQAAAAAQ7AI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Plaintext&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;시프트: [0] [0] [1] [1] [0] [1]
반전:   [1] [1] [0] [0] [1] [0]
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;26&quot; data-ke-size=&quot;size16&quot;&gt;이 반전된 데이터의 의미는 무엇일까요? 바로 &quot;원래 블록이 있던 자리의 바로 윗칸(오른쪽 칸)이 공기(Air)인가?&quot;를 나타내는 지도(Mask)가 된 것입니다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;27&quot; data-ke-size=&quot;size20&quot;&gt;Step 4. AND 연산 (최종 결합)&lt;/h4&gt;
&lt;p data-path-to-node=&quot;28&quot; data-ke-size=&quot;size16&quot;&gt;이제 [Step 1의 원본]과 [Step 3의 반전 데이터]를 AND 연산(둘 다 1일 때만 1)으로 겹쳐봅니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjChcv3r96TAxUAAAAAHQAAAAAQ7QI&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;Plaintext&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;원본:     [0]  [1]  [1]  [0]  [1]  [0]  (나는 블록인가?)
반전:   &amp;amp; [1]  [1]  [0]  [0]  [1]  [0]  (내 윗칸은 공기인가?)
--------------------------------------
결과:     [0]  [1]  [0]  [0]  [1]  [0]  (나는 윗칸이 공기인 블록이다!)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;청크 슬라이싱의 이해&lt;/p&gt;
&lt;div id=&quot;code_1775782734002&quot; data-ke-type=&quot;html&quot; data-source=&quot;&amp;lt;p class=&amp;quot;codepen&amp;quot; data-height=&amp;quot;300&amp;quot; data-pen-title=&amp;quot;Chunk Slicing&amp;quot; data-default-tab=&amp;quot;html,result&amp;quot; data-slug-hash=&amp;quot;myrzObr&amp;quot; data-user=&amp;quot;SungmMin-Jeon&amp;quot; style=&amp;quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&amp;quot;&amp;gt;
  &amp;lt;span&amp;gt;See the Pen &amp;lt;a href=&amp;quot;https://codepen.io/SungmMin-Jeon/pen/myrzObr&amp;quot;&amp;gt;
  Chunk Slicing&amp;lt;/a&amp;gt; by SungmMin Jeon (&amp;lt;a href=&amp;quot;https://codepen.io/SungmMin-Jeon&amp;quot;&amp;gt;@SungmMin-Jeon&amp;lt;/a&amp;gt;)
  on &amp;lt;a href=&amp;quot;https://codepen.io&amp;quot;&amp;gt;CodePen&amp;lt;/a&amp;gt;.&amp;lt;/span&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;script async src=&amp;quot;https://public.codepenassets.com/embed/index.js&amp;quot;&amp;gt;&amp;lt;/script&amp;gt;&quot;&gt;
&lt;p class=&quot;codepen&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot; data-height=&quot;300&quot; data-pen-title=&quot;Chunk Slicing&quot; data-default-tab=&quot;html,result&quot; data-slug-hash=&quot;myrzObr&quot; data-user=&quot;SungmMin-Jeon&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/SungmMin-Jeon/pen/myrzObr&quot;&gt; Chunk Slicing&lt;/a&gt; by SungmMin Jeon (&lt;a href=&quot;https://codepen.io/SungmMin-Jeon&quot;&gt;@SungmMin-Jeon&lt;/a&gt;) on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;script src=&quot;https://public.codepenassets.com/embed/index.js&quot;&gt;&lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #fafafa; color: #333333;&quot; data-text-less=&quot;닫기&quot; data-text-more=&quot;더보기&quot; data-ke-type=&quot;moreLess&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-path-to-node=&quot;5&quot; data-ke-size=&quot;size23&quot;&gt;청크 슬라이싱의 4단계 원리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span data-math=&quot;32 \times 32 \times 32&quot; data-index-in-node=&quot;0&quot;&gt;$32 \times 32 \times 32$&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;크기의 큐브 모양 청크가 있다고 상상해 봅시다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;탄탄의 알고리즘은 이 거대한 3D 덩어리를 한 번에 메싱하려고 덤벼들지 않습니다. 철저하게 &quot;분할 정복(Divide and Conquer)'을 시도합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-path-to-node=&quot;7&quot; data-index-in-node=&quot;0&quot;&gt;Step 1. 6개의 방향 분리 (Separating Directions)&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;모든 복셀 큐브는 6개의 면(위, 아래, 왼쪽, 오른쪽, 앞, 뒤)을 가집니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;알고리즘은 이 6개의 방향을 완전히 독립적인 작업으로 분리합니다. 예를 들어, &quot;지금부터는 '위를 바라보는 면(Up Faces)'만 만들 거야!&quot;라고 타겟을 하나만 정하는 것입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-path-to-node=&quot;8&quot; data-index-in-node=&quot;0&quot;&gt;Step 2. 축을 따라 얇게 썰기 (Slicing the Axis)&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;'위를 바라보는 면'을 찾으려면 Y축(위아래 축)을 탐색해야 합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;알고리즘은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-math=&quot;32 \times 32 \times 32&quot; data-index-in-node=&quot;84&quot;&gt;$32 \times 32 \times 32$&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;청크를 Y축을 따라 32장의 아주 얇은 도마 위 슬라이스(2D 평면)로 썰어버립니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;마치 식빵을 32장으로 얇게 써는 것과 똑같습니다. 이제 한 장의 슬라이스는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-math=&quot;32 \times 32&quot; data-index-in-node=&quot;202&quot;&gt;$32 \times 32$&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;크기의 완벽한 2D 평면이 되었습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-path-to-node=&quot;9&quot; data-index-in-node=&quot;0&quot;&gt;Step 3. 2D 평면에서의 비트 마법 (Apply 2D Greedy Meshing)&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;이제 준비가 끝났습니다! 방금 썰어낸 얇은 2D 슬라이스 한 장을 도마 위에 올립니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;이 평면 위에서 앞서 우리가 배운&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;9&quot; data-index-in-node=&quot;117&quot;&gt;'비트 연산(Trailing Zeros)을 이용한 O(1) 쿼드 압축'&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;마법을 시전합니다. 3D를 신경 쓸 필요 없이 오직 이 2D 평면 안에서만 사각형을 넓게 묶어냅니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-path-to-node=&quot;10&quot; data-index-in-node=&quot;0&quot;&gt;Step 4. 반복 및 결합 (Iterate and Combine)&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;도마 위의 슬라이스 메싱이 끝나면, 다음 슬라이스를 올려서 똑같이 작업합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot; data-path-to-node=&quot;11&quot;&gt;
&lt;li&gt;Y축 32장 슬라이스 완료 (위를 바라보는 면 끝!)&lt;/li&gt;
&lt;li&gt;방향을 바꿔 X축(오른쪽 면) 32장 슬라이스 반복&lt;/li&gt;
&lt;li&gt;Z축(앞쪽 면) 32장 슬라이스 반복...&lt;/li&gt;
&lt;li data-path-to-node=&quot;11,2,0&quot;&gt;이렇게 6개 방향&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-math=&quot;\times&quot; data-index-in-node=&quot;34&quot;&gt;$\times$&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;32장 =&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;11,2,0&quot; data-index-in-node=&quot;47&quot;&gt;총 192장의 2D 슬라이스&lt;/b&gt;를 각각 처리한 뒤, 만들어진 폴리곤(Mesh) 조각들을 하나로 합치면 완벽한 3D 청크 메쉬가 탄생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-path-to-node=&quot;12&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-path-to-node=&quot;13&quot; data-ke-size=&quot;size23&quot;&gt;왜 이렇게까지 썰어내는 걸까? (슬라이싱의 위력)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&quot;그냥 3D 상태에서 뭉치면 안 되나? 왜 굳이 192장으로 썰어내는 고생을 하지?&quot;라는 의문이 드실 수 있습니다. 여기에는 하드웨어 아키텍처를 고려한 엄청난 이점이 숨어 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot; data-path-to-node=&quot;15&quot;&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;15,0,0&quot; data-index-in-node=&quot;0&quot;&gt;복잡도 붕괴 (3D&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span data-math=&quot;\rightarrow&quot; data-index-in-node=&quot;11&quot;&gt;$\rightarrow$&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;2D)&lt;/b&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;15,0,0&quot;&gt;3D 공간에서 부피를 가진 큐브들을 병합하는 알고리즘은 극도로 복잡하고 버그가 생기기 쉽습니다. 하지만 슬라이싱을 통해 문제를 2D 평면의 직사각형 묶기로 격하시키면, 알고리즘이 무서울 정도로 단순해지고&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;15,0,0&quot; data-index-in-node=&quot;141&quot;&gt;비트 연산&lt;/b&gt;이라는 궁극의 무기를 쓸 수 있게 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;15,1,0&quot; data-index-in-node=&quot;0&quot;&gt;캐시 친화성 (Cache Friendly)&lt;/b&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;15,1,0&quot;&gt;메모리는 기본적으로 1차원 선형 구조입니다. 3D 공간을 이리저리 점프하며 탐색하면 메모리 캐시 미스(Cache Miss)가 터집니다. 하지만 하나의 축을 고정하고 2D 슬라이스 단위로 평면을 훑으면, 메모리를 아주 예쁘게 순차적으로 읽어 들일 수 있어 CPU가 최대의 속도를 낼 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-path-to-node=&quot;15,2,0&quot; data-index-in-node=&quot;0&quot;&gt;독립적인 병렬 처리&lt;/b&gt;&lt;/li&gt;
&lt;li data-path-to-node=&quot;15,2,0&quot;&gt;각각의 슬라이스는 다른 슬라이스의 결과에 영향을 받지 않습니다. 즉, 1번 슬라이스와 2번 슬라이스를 서로 다른 스레드(Thread)에 던져서 동시에 메싱하게 만드는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b data-path-to-node=&quot;15,2,0&quot; data-index-in-node=&quot;104&quot;&gt;멀티스레딩 최적화&lt;/b&gt;가 매우 용이해집니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;a href=&quot;https://techartnomad.tistory.com/594&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://techartnomad.tistory.com/594&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;[Greedy Meshing(그리디 메싱) 이론, 처리 구조, 활용 이유 및 Voxel Chunk(복셀 청크)&lt;br /&gt;2001년 쯤인가.. 삼성종합기술원에서 연구원으로 있을 때 복셀렌더링용 플러그인 개발에 참여 해 보고는 너무나 오랫만이라 트렌드 따라가기 위해 메모 해 놓는 중이다.&amp;ndash; Unity Engine 6 기반 최신 V&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복셀 렌더링 자료 조사&lt;br /&gt;- 복셀라이징&lt;br /&gt;&lt;a href=&quot;https://indievisuallab.github.io/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://indievisuallab.github.io/&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;[IndieVisualLab&lt;br /&gt;Indie Visual Lab Indie Visual LabはリアルタイムCGプログラミングについての技術資料を作成・公開したり、その技術に基づくコンテンツ開発を行っているサークルです。&lt;br /&gt;indievisuallab.github.io](&lt;a href=&quot;https://indievisuallab.github.io/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://indievisuallab.github.io/&lt;/span&gt;&lt;/a&gt;)&lt;br /&gt;&lt;a href=&quot;https://github.com/IndieVisualLab/UnityGraphicsProgrammingSeries/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/IndieVisualLab/UnityGraphicsProgrammingSeries/&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://github.com/IndieVisualLab/UnityGraphicsProgramming2/tree/master/Assets/RealTimeGPUBasedVoxelizer&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/IndieVisualLab/UnityGraphicsProgramming2/tree/master/Assets/RealTimeGPUBasedVoxelizer&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;[UnityGraphicsProgramming2/Assets/RealTimeGPUBasedVoxelizer at master &amp;middot; IndieVisualLab/UnityGraphicsProgramming2&lt;br /&gt;書籍「UnityGraphicsProgramming vol.2」のサンプルコードリポジトリ. Contribute to IndieVisualLab/UnityGraphicsProgramming2 development by creating an account on GitHub.&lt;br /&gt;github.com](&lt;a href=&quot;https://github.com/IndieVisualLab/UnityGraphicsProgramming2/tree/master/Assets/RealTimeGPUBasedVoxelizer&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/IndieVisualLab/UnityGraphicsProgramming2/tree/master/Assets/RealTimeGPUBasedVoxelizer&lt;/span&gt;&lt;/a&gt;)&lt;br /&gt;- 마칭큐브&lt;br /&gt;&lt;a href=&quot;https://pichachu.tistory.com/33&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://pichachu.tistory.com/33&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://xoft.tistory.com/47&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://xoft.tistory.com/47&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발</category>
      <category>Graphics</category>
      <category>Unreal</category>
      <category>Voxel</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/168</guid>
      <comments>https://develop-4-art.tistory.com/168#entry168comment</comments>
      <pubDate>Thu, 19 Mar 2026 13:23:21 +0900</pubDate>
    </item>
    <item>
      <title>[Graphics] 복셀 렌더링 - 2 &amp;lt;언리얼 SVT&amp;gt;</title>
      <link>https://develop-4-art.tistory.com/167</link>
      <description>&lt;h1&gt;기존 포스팅 내용 요약&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://develop-4-art.tistory.com/166&quot;&gt;[Graphics] 복셀 렌더링 - 1 &amp;lt;복셀과 자료구조&amp;gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;본격적인 이야기에 앞서, 지난 포스트에서 다루었던 &lt;strong&gt;&amp;#39;복셀(Voxel) 데이터를 메모리에 욱여넣는 방법&amp;#39;&lt;/strong&gt;을 짧게 되짚어 보겠습니다. 거대한 3D 공간에서 텅 빈 공간(Empty Space)을 쳐내기 위해 우리는 다음과 같은 트리 기반의 자료구조들을 살펴봤습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SVO (Sparse Voxel Octree):&lt;/strong&gt; 공간을 8등분하며 파고드는 직관적인 구조. 동적 업데이트와 LOD에 유리하지만, 노드를 찾기 위해 메모리 주소를 계속 추적해야 하는 &lt;strong&gt;&amp;#39;포인터 체이싱(Pointer Chasing)&amp;#39;&lt;/strong&gt; 때문에 GPU가 제 속도를 내지 못하는 치명적인 단점이 있었습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SVDAG (Sparse Voxel Directed Acyclic Graph):&lt;/strong&gt; SVO에서 더 나아가, 똑같이 생긴 덩어리(중복 데이터)들마저 하나로 병합해 버리는 극한의 메모리 압축 기술입니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NanoVDB:&lt;/strong&gt; GPU가 가장 싫어하는 &amp;#39;포인터&amp;#39;를 아예 없애버린 혁명적인 구조. 트리의 논리적 형태는 유지하되, 물리적으로는 &lt;strong&gt;거대한 1차원 배열(Flat Array)&lt;/strong&gt;에 데이터를 빽빽하게 세워두고 &amp;#39;비트 연산&amp;#39;과 &amp;#39;점프(Offset)&amp;#39;만으로 데이터를 빛의 속도로 읽어냅니다. (구름, 연기 등 정적인 데이터 읽기에 적합)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;그렇다면 NanoVDB가 게임 엔진의 최종 정답일까?&lt;/h2&gt;
&lt;p&gt;지난 포스트의 마지막에서 우리는 한 가지 의문을 던졌습니다. &lt;em&gt;&amp;quot;블록이 실시간으로 박살 나는 마인크래프트는 SVO를 쓸까? 언리얼 엔진은 NanoVDB를 그대로 쓸까?&amp;quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;정답은 &lt;strong&gt;&amp;quot;아니오&amp;quot;&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;NanoVDB는 완벽에 가까운 구조지만, &lt;strong&gt;&amp;#39;게임 엔진(실시간 렌더링)&amp;#39;&lt;/strong&gt;이라는 가혹한 환경에서는 역부족이었습니다.&lt;/p&gt;
&lt;p&gt;이에 이번 포스트에서는 다음 두 가지 새로운 패러다임을 이야기하고자 합니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;언리얼 엔진의 SVT (Sparse Volume Texture):&lt;/strong&gt; NanoVDB는 데이터를 읽어올 때 부드러운 혼합(보간)을 위해 셰이더 연산(ALU)을 너무 많이 씁니다. 언리얼 엔진은 GPU에 공짜로 탑재된 &lt;strong&gt;&amp;#39;텍스처 하드웨어 필터링&amp;#39;&lt;/strong&gt;을 써먹기 위해, 1차원 배열을 억지로 3D 텍스처 형태로 비틀어버렸습니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;마인크래프트의 Spatial Hash Map (공간 해시 맵):&lt;/strong&gt; 곡괭이질 한 번에 지형이 무너지는 환경에서는 트리를 타고 내려갈 시간조차 아깝습니다. 트리를 아예 부숴버리고, 좌표를 부르면 즉시 데이터가 튀어나오는 &lt;strong&gt;$O(1)$의 마법&lt;/strong&gt;, 공간 해싱을 선택했습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h1&gt;언리얼 엔진의 SVT (Sparse Volume Texture)&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/en-us/unreal-engine/sparse-volume-textures-in-unreal-engine&quot;&gt;언리얼 SVT 공식문서&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;지난 포스트에서 우리는 NanoVDB가 포인터를 없애고 1차원 배열(Linear Buffer)을 통해 극강의 탐색 속도를 이끌어낸 과정을 보았습니다. 그렇다면 언리얼 엔진 5의 나이아가라(Niagara) 유체 시뮬레이션이나 Heterogeneous Volumes는 NanoVDB를 그대로 사용할까요? 정답은 아래와 같습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;p&gt;&amp;quot;데이터를 들고 올 때는 VDB를 쓰지만, GPU로 렌더링할 때는 SVT(Sparse Volume Texture) 사용&amp;quot;&lt;/p&gt;
&lt;/span&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;완벽해 보이는 NanoVDB를 굳이 다른 형태로 변환하는 이유, 그것은 바로 &lt;strong&gt;GPU 하드웨어의 구조적 특징&lt;/strong&gt; 때문입니다.&lt;/p&gt;
&lt;h2&gt;1. NanoVDB의 뼈아픈 약점: 소프트웨어 보간과 ALU 병목&lt;/h2&gt;
&lt;p&gt;구름이나 연기 같은 볼류메트릭 데이터를 렌더링할 때, 마인크래프트처럼 각지게 보이지 않으려면 복셀과 복셀 사이의 중간 밀도값을 아주 부드럽게 섞어주어야 합니다. 이를 &lt;strong&gt;삼선형 보간(Trilinear Interpolation)&lt;/strong&gt;이라고 합니다.&lt;/p&gt;
&lt;p&gt;* 쌍선형 보간은 2차원 평면 상에서 한 점의 위치를 보간을 통해 구하기 위해 사용하는 방법으로&lt;/p&gt;
&lt;p&gt;흔히 우리가 사용하는 1차원 보간인 Lerp를 X축으로 두번, Y축으로 한 번 (반대로 Y축 2번 X축 1번이어도 됩니다.) 수행하여 구할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FShVh/dJMcahDFcva/Bbj5ksN3mL0umqJ5vERvl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FShVh/dJMcahDFcva/Bbj5ksN3mL0umqJ5vERvl0/img.png&quot; data-alt=&quot;위키백과 쌍선형 보간 참조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FShVh/dJMcahDFcva/Bbj5ksN3mL0umqJ5vERvl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFShVh%2FdJMcahDFcva%2FBbj5ksN3mL0umqJ5vERvl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;250&quot; height=&quot;237&quot; data-origin-width=&quot;250&quot; data-origin-height=&quot;237&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;위키백과 쌍선형 보간 참조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;* 삼선형 보간은 이런 보간 방식을 3차원상에서 수행하는 것을 의미합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;230&quot; data-origin-height=&quot;203&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbWrK8/dJMcaibxOV4/dE9Rb1xo4HMPT7NSkHo9Uk/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbWrK8/dJMcaibxOV4/dE9Rb1xo4HMPT7NSkHo9Uk/tfile.svg&quot; data-alt=&quot;위키백과 삼선형보간 참조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbWrK8/dJMcaibxOV4/dE9Rb1xo4HMPT7NSkHo9Uk/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbWrK8%2FdJMcaibxOV4%2FdE9Rb1xo4HMPT7NSkHo9Uk%2Ftfile.svg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;230&quot; height=&quot;203&quot; data-origin-width=&quot;230&quot; data-origin-height=&quot;203&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;위키백과 삼선형보간 참조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NanoVDB의 딜레마:&lt;/strong&gt; NanoVDB는 근본적으로 1차원 &lt;strong&gt;버퍼&lt;/strong&gt;입니다. 따라서 셰이더에서 임의의 위치에 있는 부드러운 값을 얻으려면, 주변 8개의 복셀 데이터를 버퍼에서 직접 찾아 읽어온 뒤, 셰이더 코드 안에서 복잡한 수학 공식으로 직접 섞어주어야 합니다.&lt;/li&gt;
&lt;li&gt;화면의 수백만 픽셀마다 레이(Ray)를 쏘며 매 스텝 이 무거운 연산을 반복한다면? GPU의 연산 유닛(ALU)이 비명을 지르며 프레임이 곤두박질치게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. SVT의 핵심: 가상 메모리(Virtual Memory)의 3D화&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;584&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK0PHb/dJMcahwHB3Q/4S2E7CqJ1DQmtBcF539Hl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK0PHb/dJMcahwHB3Q/4S2E7CqJ1DQmtBcF539Hl0/img.png&quot; data-alt=&quot;Page Table과 Physical 3D Tiles&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK0PHb/dJMcahwHB3Q/4S2E7CqJ1DQmtBcF539Hl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK0PHb%2FdJMcahwHB3Q%2F4S2E7CqJ1DQmtBcF539Hl0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;933&quot; height=&quot;584&quot; data-origin-width=&quot;933&quot; data-origin-height=&quot;584&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Page Table과 Physical 3D Tiles&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;그래픽스 프로그래머들은 생각했습니다.&lt;/p&gt;
&lt;p&gt;&amp;quot;GPU 안에는 텍스처를 기가 막히게 섞어주는 전용 하드웨어인 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%ED%85%8D%EC%8A%A4%EC%B2%98_%EB%A7%A4%ED%95%91_%EC%9C%A0%EB%8B%9B%5D_%EA%B0%80&quot;&gt;TMU(Texture Mapping Unit)&lt;/a&gt;가 놀고 있잖아? 데이터를 무식하게 버퍼에 넣지 말고, 3D 텍스처에 구겨 넣으면 TMU가 공짜로 보간을 해주지 않을까?&amp;quot;_&lt;/p&gt;
&lt;p&gt;이 발상에서 탄생한 것이 SVT입니다. 언리얼 엔진은 5.3 버전부터 SVT 개념을 도입했으며(&lt;a href=&quot;https://www.youtube.com/watch?v=QtJzitZweWM&quot;&gt;https://www.youtube.com/watch?v=QtJzitZweWM&lt;/a&gt;), SVT는 컴퓨터 운영체제의 &lt;a href=&quot;https://ground90.tistory.com/119&quot;&gt;&amp;#39;가상 메모리 페이징&amp;#39; 기법&lt;/a&gt;을 3D 그래픽스에 그대로 이식한 구조로, 두 개의 거대한 3D 텍스처로 이루어집니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Page Table Texture (가상 주소록):&lt;/strong&gt;&lt;br&gt;전체 월드 공간을 덮고 있는 저해상도 3D 텍스처입니다.&lt;ul&gt;
&lt;li&gt;실제 밀도 데이터가 아니라, &lt;strong&gt;&amp;quot;진짜 데이터가 있는 물리적 주소(인덱스)&amp;quot;&lt;/strong&gt;만 적혀 있습니다.&lt;/li&gt;
&lt;li&gt;텅 빈 공기(Empty Space)라면 이곳에 0이 적혀 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Physical Texture Atlas (물리 데이터 창고):&lt;/strong&gt;&lt;br&gt;빈 공간은 싹 쳐내고, 실제 데이터가 존재하는 $8 \times 8 \times 8$ 크기의 &amp;#39;페이지(Page)&amp;#39;들만 테트리스처럼 빽빽하게 모아둔 거대한 고해상도 3D 텍스처입니다. (기존 VDB의 Leaf Data에 해당)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cDApMj/dJMcaa5mwxU/Uc4x0Kf1gjpbbHlDXfoo31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cDApMj/dJMcaa5mwxU/Uc4x0Kf1gjpbbHlDXfoo31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cDApMj/dJMcaa5mwxU/Uc4x0Kf1gjpbbHlDXfoo31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcDApMj%2FdJMcaa5mwxU%2FUc4x0Kf1gjpbbHlDXfoo31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이 이원화된 구조 덕분에 &lt;strong&gt;빈 공간의 메모리 낭비는 완벽하게 제거(NanoVDB의 장점)&lt;/strong&gt;하면서도, 최종 데이터는 &lt;strong&gt;3D 텍스처 형태에 보관(TMU 활용 가능)&lt;/strong&gt; 할 수 있게 되었습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;특징&lt;/th&gt;
&lt;th&gt;OS 가상 메모리 페이징&lt;/th&gt;
&lt;th&gt;언리얼 SVT&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;가상공간&lt;/td&gt;
&lt;td&gt;Virtual Memory (연속된 거대한 주소 공간)&lt;/td&gt;
&lt;td&gt;렌더링할 구름의 전체 3D Bounding Box&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;매핑 테이블&lt;/td&gt;
&lt;td&gt;Page Table (가상 $\rightarrow$ 물리 주소록)&lt;/td&gt;
&lt;td&gt;Page Table Texture (저해상도 3D 주소록 텍스처)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;물리 공간&lt;/td&gt;
&lt;td&gt;Physical RAM (파편화된 실제 메모리 조각, Frames)&lt;/td&gt;
&lt;td&gt;Physical Texture Atlas (데이터가 꽉 찬 타일들의 모음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;블록 단위&lt;/td&gt;
&lt;td&gt;보통 4KB 단위의 Page/Frame&lt;/td&gt;
&lt;td&gt;보통 $8 \times 8 \times 8$ 단위의 Voxel Brick (타일)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빈 공간 처리&lt;/td&gt;
&lt;td&gt;페이지 테이블에 &amp;#39;할당 안 됨(Null)&amp;#39; 표시&lt;/td&gt;
&lt;td&gt;Page Table Texture에 0 기록 (Empty Space Skipping)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 스트리밍&lt;/td&gt;
&lt;td&gt;디스크(SSD)에서 RAM으로 Swap In&lt;/td&gt;
&lt;td&gt;디스크/CPU에서 VRAM 물리 텍스처로 Tile Streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;3. 실무에서 SVT 제작 및 임포트하는 방법&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://monggus.tistory.com/716&quot;&gt;언리얼 VDB 파이프라인 기초&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;4. 실무 구현의 최대 난제: 경계면 아티팩트와 패딩(Padding)&lt;/h2&gt;
&lt;p&gt;이론은 완벽하지만, SVT를 실제 엔진에 구현할 때 그래픽스 엔지니어들의 뒤통수를 치는 엄청난 문제가 하나 발생합니다. 바로 &lt;strong&gt;경계면 필터링(Boundary Filtering) 문제&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;p&gt;Physical Texture 안에는 서로 다른 공간에서 온 $8 \times 8 \times 8$ 페이지들이 순서 없이 다닥다닥 붙어있습니다. 만약 하드웨어 텍스처 유닛(TMU)이 &amp;#39;페이지의 끝자락(경계면)&amp;#39;에 있는 복셀을 삼선형 보간하려고 하면 어떤 일이 벌어질까요?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;전혀 상관없는 옆 페이지의 데이터까지 스며들어와 같이 섞여버립니다(Bleeding).&lt;/strong&gt; 결과적으로 렌더링 된 구름 표면에 끔찍한 격자무늬(Artifact)가 생겨버리죠. (텍스쳐 아틀라스에서도 쉽게 겪는 이슈입니다.)&lt;/p&gt;
&lt;h3&gt;언리얼 엔진의 해결책: 1-Voxel Padding&lt;/h3&gt;
&lt;p&gt;이 문제를 해결하기 위해 언리얼 엔진의 SVT는 물리 텍스처에 데이터를 올릴 때 $8 \times 8 \times 8$ 원본 크기 그대로 저장하지 않습니다.&lt;/p&gt;
&lt;p&gt;사방으로 1복셀씩 &lt;strong&gt;이웃 페이지의 데이터를 복사해 와서 덧붙인(Padding) $10 \times 10 \times 10$ 크기&lt;/strong&gt;로 뻥튀기하여 저장합니다.&lt;/p&gt;
&lt;p&gt;이렇게 여분의 보호막(Padding)을 둘러치면, TMU가 경계면에서 보간 연산을 하더라도 항상 올바른 이웃 복셀 데이터와 섞이게 되어 아티팩트가 완벽하게 사라진다고 설명합니다. (이로 인해 메모리를 약간 더 쓰게 되는 Trade-off를 감수하는 것입니다.)&lt;/p&gt;
&lt;p&gt;하지만, 이는 전통적인 아틀라스 관리방법에서도 사용했던 솔루션이고 우리는 이에 상응하는 대가가 무엇인지 알고 있습니다.&lt;/p&gt;
&lt;h3&gt;1. 밉맵 블리딩 (Mipmap Bleeding): 하드웨어 밉맵의 사용 불가&lt;/h3&gt;
&lt;p&gt;2D 아틀라스에서 가장 아티팩트가 심하게 터지는 순간은, 카메라가 멀어져서 &lt;strong&gt;밉맵(Mipmap)&lt;/strong&gt;이 작동할 때입니다. 해상도가 절반씩 줄어들다 보면 결국 패딩 공간마저 뭉개지면서 옆 텍스처의 색상이 스며들어옵니다(Bleeding).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SVT의 한계:&lt;/strong&gt;&lt;br&gt;이 때문에 SVT의 Physical Texture는 GPU 하드웨어가 자동으로 만들어주는 밉맵 생성 기능(GenerateMips)을 &lt;strong&gt;절대 사용할 수 없습니다.&lt;/strong&gt; 만약 3D 아틀라스를 그대로 축소해 버리면, 전혀 상관없는 5번 구역 연기와 100번 구역 구름이 섞여버리는 대참사가 일어납니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SVT의 해결책 (비용 증가):&lt;/strong&gt;&lt;br&gt;언리얼 엔진은 이 블리딩을 막기 위해 &lt;strong&gt;각 밉맵 레벨(LOD)마다 별도의 SVT(Page Table + Physical Texture 세트)를 아예 새로 만듭니다.&lt;/strong&gt; 즉, Mip 0용 아틀라스, Mip 1용 아틀라스, Mip 2용 아틀라스를 CPU나 Compute Shader에서 각각 따로 계산해서 메모리에 올려야 합니다. 유지보수와 메모리 관리 비용이 기하급수적으로 증가합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 메모리 뻥튀기 (The Curse of Volume): 3D 패딩의 끔찍한 나비효과&lt;/h3&gt;
&lt;p&gt;2D 텍스처에서 1픽셀 패딩을 두르는 것은 전체 용량에 큰 타격을 주지 않습니다. 하지만 3D 공간인 복셀에서는 &lt;strong&gt;&amp;#39;부피(Volume)&amp;#39;&lt;/strong&gt;의 저주가 시작됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;$8 \times 8 \times 8$ 크기의 원본 데이터 블록은 &lt;strong&gt;512개&lt;/strong&gt;의 복셀을 가집니다.&lt;/li&gt;
&lt;li&gt;여기에 사방으로 1복셀씩만 패딩을 둘러도 $10 \times 10 \times 10$이 되어 &lt;strong&gt;1000개&lt;/strong&gt;의 복셀이 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결과:&lt;/strong&gt; 고작 1복셀 두께의 경계면 아티팩트를 막겠다고, &lt;strong&gt;메모리를 무려 95%나 더 쓰게 되는 미친 낭비&lt;/strong&gt;가 발생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;SVT의 해결책:&lt;/strong&gt; 이 메모리 폭발을 줄이기 위해, 실제 상용 엔진에서는 블록(Page)의 기본 크기를 훨씬 크게 잡습니다. 예를 들어 $32 \times 32 \times 32$ (32,768 복셀)로 잡고 패딩을 둘러 $34 \times 34 \times 34$ (39,304 복셀)로 만들면, 오버헤드 비율을 약 20% 수준으로 억누를 수 있습니다. (대신 블록이 너무 커지면 빈 공간을 쳐내는 효율이 떨어지는 또 다른 딜레마에 빠집니다.)&lt;/p&gt;
&lt;h3&gt;3. 부동소수점 정밀도 한계 (Floating-Point Precision Seams)&lt;/h3&gt;
&lt;p&gt;패딩을 완벽하게 둘렀다고 해도 시각적 아티팩트가 생길 수 있습니다. 셰이더에서 레이마칭을 할 때, 3D 월드 좌표를 물리 텍스처(Physical Texture) 내부의 아주 미세한 UVW 좌표로 변환하는 수학적 계산을 거칩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;문제점:&lt;/strong&gt; GPU의 float 부동소수점 연산은 소수점 아래로 내려갈수록 미세한 오차가 발생합니다. 이 오차 때문에 텍스처 샘플러가 우리가 의도한 &amp;#39;안전한 1복셀 패딩 구역&amp;#39;을 아주 미세하게(0.0001 픽셀만큼) 벗어나서 샘플링하게 되면, 구름이나 연기 표면에 아주 얇고 날카로운 &lt;strong&gt;경계선 틈새(Seam)&lt;/strong&gt;가 깜빡거리며 보이게 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해결책:&lt;/strong&gt; 셰이더 프로그래머들은 이 아티팩트를 없애기 위해 UVW 좌표가 절대 패딩 바깥으로 넘어가지 않도록, 수식 끝에 미세한 오프셋을 더하고 빼거나 clamp() 함수를 걸어주는 등 피눈물 나는 예외 처리 코드를 추가해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5. 셰이더(HLSL) 레벨의 SVT 샘플링 (수도 코드)&lt;/h2&gt;
&lt;p&gt;실제 언리얼 엔진 내부의 셰이더 코드가 레이마칭을 수행할 때, SVT를 어떻게 읽어오는지 그 논리적 흐름을 단순화한 수도 코드(Pseudo Code)입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// SVT 텍스처 자원들
Texture3D&amp;lt;uint&amp;gt; PageTableTex;         // 주소록 (가상 공간)
Texture3D&amp;lt;float&amp;gt; PhysicalTex;         // 실제 데이터 (물리 공간 아틀라스)
SamplerState TrilinearSampler;        // 하드웨어 보간 샘플러

float SampleSVT(float3 WorldPos)
{
    // 1. 월드 좌표를 Page Table의 3D 가상 UVW 좌표 [0~1]로 변환
    float3 VirtualUVW = WorldToVirtualUVW(WorldPos);

    // 2. 주소록(Page Table) 읽기: 현재 좌표가 속한 페이지의 물리적 주소를 가져옴
    // (Point 샘플링: 섞이지 않은 정확한 정수 인덱스만 획득)
    uint PhysicalPageIndex = PageTableTex.Load(PointSampler, int(floor(VirtualUVW)), 0);

    // 3. 빈 공간(Empty Space) 스킵 로직
    // 주소가 0이면 텅 빈 공기! 무거운 연산 즉시 종료. (NanoVDB의 Bitmask 역할)
    if (PhysicalPageIndex == 0)
    {
        return 0.0f; 
    }

    // 4. 가상 좌표 안에서 나의 디테일한 위치(Local UVW) 및 패딩(Padding) 보정 계산
    float3 PhysicalUVW = ConvertToPhysicalUVW(PhysicalPageIndex, VirtualUVW);

    // 5. 하드웨어 삼선형 보간의 마법!
    // GPU의 TMU가 알아서 8개 복셀을 부드럽게 섞어 반환함. (ALU 연산 거의 제로)
    return PhysicalTex.SampleLevel(TrilinearSampler, PhysicalUVW, 0);
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;텍스처를 두 번 읽는 것(Indirection)은 GPU 입장에서 부담일 수 있습니다.&lt;br&gt;하지만 코드의 3번 단계를 보세요. PhysicalPageIndex == 0일 경우 두 번째 텍스처 읽기(PhysicalTex 샘플링)를 아예 생략해 버립니다. 구름이 없는 90%의 허공을 렌더링할 때 엄청난 연산 스킵이 발생하므로 전체적인 성능은 압도적으로 유리해집니다.&lt;/p&gt;
&lt;p&gt;아래는 이제 실제 언리얼 엔진 내 구현 사용부입니다.&lt;br&gt;(SparseVolumeTextureCommon.ush)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// PageTable에서부터 Voxel의 UVW 좌표값을 가져오는 과정입니다.
float3 SparseVolumeTextureGetVoxelCoord(Texture3D&amp;lt;uint&amp;gt; PageTable, FSparseVolumeTextureUniforms Uniforms, float3 PageTableCoord, int MipLevel)
{
    // Load from page table texture
    // 아래 보면 PageTable (가상 테이블)로부터 주소값을 가져올 때는 MipLevel을 사용하는 것을 알 수 있습니다.
    const uint PackedPhysicalTileCoord = PageTable.Load(int4(floor(PageTableCoord), MipLevel)).x;
    const int3 PhysicalTileCoord = int3(PackedPhysicalTileCoord &amp;amp; 0xFFu, (PackedPhysicalTileCoord &amp;gt;&amp;gt; 8u) &amp;amp; 0xFFu, (PackedPhysicalTileCoord &amp;gt;&amp;gt; 16u) &amp;amp; 0xFFu);
    const uint TileMipLevel = PackedPhysicalTileCoord &amp;gt;&amp;gt; 24u;

    // Scale TileCoord by 1 / exp2(ActualMipLevel - DesiredMipLevel) to get correct UVs when sampling from a higher mip as fallback
    const float TileRcpMipLevelFactor = rcp(float(1u &amp;lt;&amp;lt; (TileMipLevel - uint(MipLevel))));
    const float3 FracTileCoord = frac(PageTableCoord * TileRcpMipLevelFactor);

    const float3 VoxelCoord = float3(PhysicalTileCoord) * float(SPARSE_VOLUME_TILE_RES_PADDED) + (FracTileCoord * float(SPARSE_VOLUME_TILE_RES) + float(SPARSE_VOLUME_TILE_BORDER));
    return VoxelCoord;
}

float3 SparseVolumeTextureSamplePageTable(Texture3D&amp;lt;uint&amp;gt; PageTable, FSparseVolumeTextureUniforms Uniforms, float3 UVW, uint AddressU, uint AddressV, uint AddressW, int MipLevel = 0)
{
    // Apply address mode to UVW and clamp mip level to resident levels
    UVW = SparseVolumeTextureApplyAddressMode(UVW, AddressU, AddressV, AddressW);
    MipLevel = clamp(MipLevel, 0, Uniforms.HighestMipLevel);

    const float RcpMipLevelFactor = rcp(float(1u &amp;lt;&amp;lt; (uint)MipLevel));
    const float3 VolumePageCoord = UVW * Uniforms.VolumePageResolution;
    const float3 MipPageTableOffset = floor(Uniforms.PageTableOffset * RcpMipLevelFactor);
    const float3 PageTableCoord = VolumePageCoord * RcpMipLevelFactor - MipPageTableOffset;

    const float3 VoxelCoord = SparseVolumeTextureGetVoxelCoord(PageTable, Uniforms, PageTableCoord, MipLevel);
    const float3 VoxelUVW = VoxelCoord * Uniforms.TileDataTexelSize;
    return VoxelUVW;
}

// 위에서 얻은 UVW를 아래 PhysicalTileData 샘플링 함수에 사용함으로써 실제 저장된 값을 가져올 수 있습니다.
float4 SparseVolumeTextureSamplePhysicalTileData(Texture3D PhysicalTileDataA, Texture3D PhysicalTileDataB, SamplerState TileDataSampler, float3 VoxelUVW, int PhysicalTileDataIndex)
{
    // SampleLevel을 할 때 보면 Mip Bleeding을 해소하기 위해 MipLevel을 0.0f로 고정하고 있는 것을 알 수 있습니다.
    switch (PhysicalTileDataIndex)
    {
    case 0: return PhysicalTileDataA.SampleLevel(TileDataSampler, VoxelUVW, 0.0f);
    case 1: return PhysicalTileDataB.SampleLevel(TileDataSampler, VoxelUVW, 0.0f);
    default: return 0.0f;
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;경계면 패딩(Padding)&lt;br&gt;이전에 &amp;quot;패딩 때문에 메모리 낭비가 있을 것이다.&amp;quot;라고 했습니다. 이에 엔진에서는 아래와 같이 로직을 명시하고 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// UE 코드 내부 (SparseVolumeTextureCommon.ush Line 8) 
#define SPARSE_VOLUME_TILE_RES 16 // 순수 데이터 타일 크기 (예: 16)
#define SPARSE_VOLUME_TILE_RES_PADDED (SPARSE_VOLUME_TILE_RES + 2 * SPARSE_VOLUME_TILE_BORDER) // 패딩이 포함된 타일 크기
#define SPARSE_VOLUME_TILE_BORDER 1 // 경계면 패딩 두께 (예: 1)&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;수학적 계산: 물리적 타일 시작 위치(Padded 기준) + (타일 내 비율 * 순수 해상도) + 테두리 두께(Border)&lt;br&gt;이 수식이 바로, 하드웨어 삼선형 보간(Trilinear Interpolation) 시 블리딩(Bleeding) 아티팩트를 막기 위해 1복셀씩 안쪽으로 밀어 넣어서 샘플링하는 정확한 오프셋 계산입니다. 우리가 논의했던 이론이 코드 한 줄로 완벽히 구현되어 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Page Table의 비트 패킹 (데이터 압축)&lt;br&gt;수도 코드에서는 단순히 uint PhysicalPageIndex 하나만 가져온다고 쳤지만, 언리얼은 32비트 정수 하나(PackedPhysicalTileCoord)에 4가지 정보를 욱여넣었습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// 32비트를 8비트씩 쪼개서 X, Y, Z, MipLevel을 추출함 int3 PhysicalTileCoord = int3(PackedPhysicalTileCoord &amp;amp; 0xFFu, (PackedPhysicalTileCoord &amp;gt;&amp;gt; 8u) &amp;amp; 0xFFu, (PackedPhysicalTileCoord &amp;gt;&amp;gt; 16u) &amp;amp; 0xFFu); uint TileMipLevel = PackedPhysicalTileCoord &amp;gt;&amp;gt; 24u;&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;물리적 텍스처(Physical Texture)가 1차원 배열이 아니라 3D 텍스처이기 때문에, 1D 인덱스가 아닌 3D 타일 좌표 $(X, Y, Z)$가 필요합니다. 이를 8비트씩 잘라서 쓰고, 남은 상위 8비트는 해당 타일의 밉맵(MipLevel) 정보를 담는 데 사용하여 메모리 대역폭을 극한으로 아꼈습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;분기문(if) 없는 Empty Space 처리 (Branchless Optimization)&lt;br&gt;수도 코드에서는 빈 공간을 만나면 렌더링을 멈추기 위해 if (Index == 0) return 0; 이라는 분기문을 넣었습니다. 하지만 언리얼 코드에는 if문이 보이지 않습니다. 왜 그럴까요?&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;언리얼의 꼼수 (Tile 0 예약)&lt;/h4&gt;
&lt;p&gt;GPU는 if문을 만날 때 성능이 저하될 수 있습니다(Warp Divergence). 언리얼은 이를 막기 위해, Physical Texture의 &lt;strong&gt;가장 첫 번째 타일(0, 0, 0 위치)을 &amp;#39;모든 값이 0(투명)으로 꽉 찬 블랙 타일&amp;#39;&lt;/strong&gt;로 만들어 둡니다.&lt;/p&gt;
&lt;p&gt;Page Table에서 빈 공간을 만나 PackedPhysicalTileCoord가 0이 나오면, if문으로 끊지 않고 그냥 0번 타일(투명한 블랙 타일)의 좌표를 계산해서 샘플링하게 둡니다. 결과적으로 투명한 값이 반환되므로 시각적 결과는 같으면서 GPU 파이프라인이 멈추지 않게(Branchless) 최적화한 것입니다.&lt;/p&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;하드웨어 삼선형 보간 (Trilinear Filter)&lt;br&gt;이 부분이 우리가 NanoVDB 대신 SVT를 쓰는 궁극적인 이유였죠.복잡한 수학 공식 하나 없이, SampleLevel 함수와 TileDataSampler (여기에 Trilinear 필터 세팅이 들어있음)를 호출하여 GPU 텍스처 유닛(TMU)에 보간을 완전히 떠넘겼습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;PhysicalTileDataA.SampleLevel(TileDataSampler, VoxelUVW, 0.0f);&lt;/code&gt;&lt;/pre&gt;&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;밉맵 폴백 (Mipmap Fallback) 로직&lt;br&gt;언리얼 코드의 아주 특별한 부분입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;const float TileRcpMipLevelFactor = rcp(float(1u &amp;lt;&amp;lt; (TileMipLevel - uint(MipLevel))));&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;이 폴백 함수를 디테일하게 살펴보면 아래와 같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// TileMipLevel(가진 것) = 2, MipLevel(원하는 것) = 0 이라고 가정해 봅시다.

// 1단계: 해상도 차이 계산 (비트 시프트)
(TileMipLevel - uint(MipLevel)) // 2 - 0 = 2 (2단계 차이남)

// 2단계: 크기 비율 계산 (2의 거듭제곱)
1u &amp;lt;&amp;lt; 2 // 이것은 2^2 와 같습니다. 즉, 값이 &amp;#39;4&amp;#39;가 됩니다.
// (Mip 2 타일은 1D 축을 기준으로 Mip 0 타일보다 4배 더 넓은 공간을 포괄합니다)

// 3단계: 역수(Reciprocal) 취하기
rcp(4.0f) // 1.0f / 4.0f = 0.25f&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;언리얼 월드는 방대해서, 특정 구역의 고해상도(Mip 0) 데이터가 메모리에 아직 덜 로딩(Streaming)되었을 수 있습니다. 이때 코드가 터지지 않고, &lt;strong&gt;&amp;quot;원하는 밉맵이 없으면, 현재 메모리에 올라와 있는 더 낮은 해상도(TileMipLevel)의 데이터를 스케일링해서 일단 보여줘!&amp;quot;&lt;/strong&gt;라는 강력한 예외 처리 로직이 들어있습니다.&lt;/p&gt;
&lt;p&gt;원래 이 포스트를 통해 Spatial Hash Map에 대한 이야기까지 나눈 뒤 렌더링 쪽으로 넘어가고자 했으나, 생각보다 발표 시간이 촉박하여 다음 포스팅에서 이어 설명한 뒤 렌더링으로 이어갑니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>Graphics</category>
      <category>svt</category>
      <category>Unreal</category>
      <category>Voxel</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/167</guid>
      <comments>https://develop-4-art.tistory.com/167#entry167comment</comments>
      <pubDate>Thu, 5 Mar 2026 01:34:50 +0900</pubDate>
    </item>
    <item>
      <title>[Graphics] 복셀 렌더링 - 1 &amp;lt;복셀과 자료구조&amp;gt;</title>
      <link>https://develop-4-art.tistory.com/166</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;연관 포스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://develop-4-art.tistory.com/167&quot;&gt;[Graphics] 복셀 렌더링 - 2 &amp;lt;언리얼 SVT와 Spatial Hash Map&amp;gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다각형(Polygon)의 껍데기를 넘어 속이 꽉 찬 세계로&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 우리가 지금까지 보았던 '징후'들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 그래픽스 스터디하면서 알게 모르게 계속 마주쳤던 녀석이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Ray Marching &amp;amp; SDF:&lt;/b&gt; 그냥 3D 모델을 띄우는 게 아니라, 수학적인 거리 함수로 공간을 탐색하며 그림자를 만들었죠.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjaOP9/dJMcagEwApu/MqPs02OYkS41DpesgfVaKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjaOP9/dJMcagEwApu/MqPs02OYkS41DpesgfVaKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjaOP9/dJMcagEwApu/MqPs02OYkS41DpesgfVaKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjaOP9%2FdJMcagEwApu%2FMqPs02OYkS41DpesgfVaKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://iquilezles.org/articles/distfunctions/&quot;&gt;https://iquilezles.org/articles/distfunctions/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.shadertoy.com/view/XslGRr&quot;&gt;https://www.shadertoy.com/view/XslGRr&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Sphere Tracing:&lt;/b&gt; 부드러운 그림자(Soft Shadow)를 만들기 위해 공간을 '구(Sphere)' 단위로 듬성듬성, 하지만 효율적으로 건너뛰었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOM5F6/dJMcab4eyvv/qBtreuciwUjlLhMj2IxkU1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOM5F6/dJMcab4eyvv/qBtreuciwUjlLhMj2IxkU1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOM5F6/dJMcab4eyvv/qBtreuciwUjlLhMj2IxkU1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOM5F6%2FdJMcab4eyvv%2FqBtreuciwUjlLhMj2IxkU1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;424&quot; height=&quot;119&quot; data-origin-width=&quot;424&quot; data-origin-height=&quot;119&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;UE5 Lumen:&lt;/b&gt; 루멘이 빛을 계산할 때 &quot;Mesh Distance Field&quot;를 쓴다는 거 보셨던 기억이 있으실 겁니다. 메시를 있는 그대로 쓰는 게 아니라, &lt;b&gt;'공간에 대한 정보'로 변환&lt;/b&gt;해서 처리했다는 뜻입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://gamedevlab.tistory.com/entry/%EC%96%B8%EB%A6%AC%EC%96%BC-%EB%A0%88%EC%9D%B4%ED%8A%B8%EB%A0%88%EC%9D%B4%EC%8B%B1%EA%B3%BC-%EB%A3%A8%EB%A9%98&quot;&gt;언리얼 레이트레이싱과 루멘&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1771645783872&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;언리얼 레이트레이싱과 루멘&quot; data-og-description=&quot;이번 세션은 설명을 한번 쭉 읽고, 이어서 나오는 시그라프 문서를 그대로 읽으면 된다. 1회차와 2회차 게임을 하듯이 구성했다. 오픈월드 RT전통적인 레이트레이싱은 닫힌 방 안에서 디테일한 &quot; data-og-host=&quot;gamedevlab.tistory.com&quot; data-og-source-url=&quot;https://gamedevlab.tistory.com/entry/%EC%96%B8%EB%A6%AC%EC%96%BC-%EB%A0%88%EC%9D%B4%ED%8A%B8%EB%A0%88%EC%9D%B4%EC%8B%B1%EA%B3%BC-%EB%A3%A8%EB%A9%98&quot; data-og-url=&quot;https://gamedevlab.tistory.com/entry/%EC%96%B8%EB%A6%AC%EC%96%BC-%EB%A0%88%EC%9D%B4%ED%8A%B8%EB%A0%88%EC%9D%B4%EC%8B%B1%EA%B3%BC-%EB%A3%A8%EB%A9%98&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/uR5OA/dJMb82eJJUK/TOGk8kXtrVXvE0td5ARo5K/img.jpg?width=800&amp;amp;height=450&amp;amp;face=468_153_636_336,https://scrap.kakaocdn.net/dn/cFMU3z/dJMb8UHLRCx/L4Or5tYkKIf0aJwZChfAkk/img.jpg?width=800&amp;amp;height=450&amp;amp;face=468_153_636_336,https://scrap.kakaocdn.net/dn/plref/dJMb8SXuuqw/foQLGRzlNTDqTKxdHEbnk0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=718_218_1060_592&quot;&gt;&lt;a href=&quot;https://gamedevlab.tistory.com/entry/%EC%96%B8%EB%A6%AC%EC%96%BC-%EB%A0%88%EC%9D%B4%ED%8A%B8%EB%A0%88%EC%9D%B4%EC%8B%B1%EA%B3%BC-%EB%A3%A8%EB%A9%98&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://gamedevlab.tistory.com/entry/%EC%96%B8%EB%A6%AC%EC%96%BC-%EB%A0%88%EC%9D%B4%ED%8A%B8%EB%A0%88%EC%9D%B4%EC%8B%B1%EA%B3%BC-%EB%A3%A8%EB%A9%98&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/uR5OA/dJMb82eJJUK/TOGk8kXtrVXvE0td5ARo5K/img.jpg?width=800&amp;amp;height=450&amp;amp;face=468_153_636_336,https://scrap.kakaocdn.net/dn/cFMU3z/dJMb8UHLRCx/L4Or5tYkKIf0aJwZChfAkk/img.jpg?width=800&amp;amp;height=450&amp;amp;face=468_153_636_336,https://scrap.kakaocdn.net/dn/plref/dJMb8SXuuqw/foQLGRzlNTDqTKxdHEbnk0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=718_218_1060_592');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;언리얼 레이트레이싱과 루멘&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이번 세션은 설명을 한번 쭉 읽고, 이어서 나오는 시그라프 문서를 그대로 읽으면 된다. 1회차와 2회차 게임을 하듯이 구성했다. 오픈월드 RT전통적인 레이트레이싱은 닫힌 방 안에서 디테일한&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;gamedevlab.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이 모든 건 &lt;b&gt;&quot;3D 공간을 어떻게 효율적으로 표현하고 계산할 것인가?&quot;&lt;/b&gt; 라는 거대한 질문에 대한 답들이었습니다. 그리고 오늘 우리는 그 질문의 가장 &lt;b&gt;직관적이고 강력한 대답&lt;/b&gt; &lt;b&gt;복셀(Voxel)&lt;/b&gt;에 대해 본격적으로 파헤쳐 보려 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 복셀(Voxel), 도대체 왜 다시 뜨는가?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1027&quot; data-origin-height=&quot;539&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n8rvO/dJMcaibgtwQ/eqE5irzGyf0tgB8luDB3TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n8rvO/dJMcaibgtwQ/eqE5irzGyf0tgB8luDB3TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n8rvO/dJMcaibgtwQ/eqE5irzGyf0tgB8luDB3TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn8rvO%2FdJMcaibgtwQ%2FeqE5irzGyf0tgB8luDB3TK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1027&quot; height=&quot;539&quot; data-origin-width=&quot;1027&quot; data-origin-height=&quot;539&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;픽셀 (Pixel):&lt;/b&gt; Picture (사진/그림) + Element (요소)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2D 평면 이미지를 구성하는 가장 작은 점입니다. 우리는 모니터라는 평면 도화지 위에 수많은 픽셀을 찍어서 그림을 봅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복셀 (Voxel):&lt;/b&gt; Volume (부피/공간) + Element (요소) (또는 Volumetric Pixel)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3D 공간을 구성하는 가장 작은 단위입니다. 평면에 찍히는 '점'이 아니라, 공간을 차지하는 &lt;b&gt;'정육면체(큐브)'&lt;/b&gt; 형태를 띱니다.&lt;/li&gt;
&lt;li&gt;속이 꽉 차 있습니다. 부수면 부서진 단면이 바로 보이고, 쌓으면 지형이 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에는 &quot;복셀? 그거 마인크래프트 같은 네모네모 그래픽 아니야?&quot;라고 했지만, 이제는 다릅니다. 하드웨어가 발전하고 &lt;b&gt;Unreal Engine 5.7&lt;/b&gt;에서 &lt;b&gt;Voxel Foliage&lt;/b&gt; 같은 기능이 정식으로 들어올 만큼, 복셀은 '리얼리스틱 그래픽'의 핵심 기술로 자리 잡고 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 복셀과 메모리 폭발&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복셀에는 치명적인 단점이 있습니다. &lt;b&gt;데이터가 너무 큽니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1024 X 1024 해상도의 2D 이미지는 1MB 정도지만, 1024 X 1024 X 1024 복셀은 수 GB가 넘어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 오늘 스터디 핵심은 &lt;b&gt;&quot;어떻게 이 큰 데이터를 압축하고, 빠르게 꺼내 쓸 것인가?&quot;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 오늘 이 이야기의 핵심이 되는 내용을 요약해놓은 것 입니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;자료구조의 진화 (The Container):&lt;/b&gt;&lt;br /&gt;3D 배열에서 시작해, 필요한 곳만 저장하는 &lt;b&gt;SVO (Sparse Voxel Octree)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중복을 제거해 극도로 압축하는 &lt;b&gt;SVDAG&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;영화(VFX)와 게임 양쪽에서 표준이 된 &lt;b&gt;NanoVDB&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;그리고 O(1) 접근 속도를 노리는 &lt;b&gt;Spatial Hash Map&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;렌더링과 알고리즘 (The Technique):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터가 비어있는지 아닌지 빠르게 확인하는 &lt;b&gt;Ray Marching&lt;/b&gt; 전략.&lt;/li&gt;
&lt;li&gt;각진 복셀을 부드러운 곡선으로 깎아내는 &lt;b&gt;Marching Cubes&lt;/b&gt;와 &lt;b&gt;Dual Contouring&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;LOD가 서로 다를 때 지형이 찢어지는 걸 막는 &lt;b&gt;Transvoxel&lt;/b&gt; 개념.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;실전 응용 (The Reality):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 모든 이론이 집대성된 최신 기술, &lt;b&gt;Unreal Engine 5.7의 Voxel Foliage&lt;/b&gt;가 도대체 어떤 원리로 돌아가는지.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;자료구조의 진화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 복셀 렌더링의 핵심인 &lt;b&gt;'방대한 3D 데이터를 메모리에 어떻게 효율적으로 담을 것인가'&lt;/b&gt;를 해결하기 위한 자료구조의 진화 과정을 살펴보겠습니다. 무식한 3D 배열(Dense Grid)에서 시작해, 현대 게임 엔진과 VFX에서 사용하는 최적화된 구조까지의 흐름입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복셀 데이터의 가장 큰 특징은 &lt;b&gt;희소성(Sparsity)&lt;/b&gt;입니다. 거대한 3D 월드에서 실제로 물체가 존재하는 공간(표면이나 내부)보다 텅 빈 공간(Empty Space)이 차지하는 비율이 압도적으로 높습니다. 따라서 '빈 공간은 저장하지 않고, 존재하는 데이터만 빠르게 찾는 희소성 탐색과 극대화'가 모든 자료구조의 핵심 목표입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. SVO (Sparse Voxel Octree)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 고전적이면서도 직관적인 희소 데이터 구조입니다. 3D 공간을 8개의 정육면체(Octants)로 분할하고, 데이터가 존재하는 공간만 계속해서 파고들어 가는 옥트리 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://eisenwave.github.io/voxel-compression-docs/svo/svo.html&quot;&gt;(Voxel Compression&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;735&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blTss1/dJMcafMn3w1/JLw2FKT9BI17atd1UL3rx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blTss1/dJMcafMn3w1/JLw2FKT9BI17atd1UL3rx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blTss1/dJMcafMn3w1/JLw2FKT9BI17atd1UL3rx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblTss1%2FdJMcafMn3w1%2FJLw2FKT9BI17atd1UL3rx0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;735&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;735&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;동작 원리:&lt;/b&gt; 루트(Root) 노드가 전체 공간을 포괄합니다. 공간을 8등분 했을 때 빈 공간인 자식 노드는 생성하지 않고 메모리 할당을 멈춥니다. 데이터가 있는 자식 노드만 다시 8등분하며 원하는 해상도에 도달할 때까지 분할 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징 및 장점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;계층적 LOD (Level of Detail):&lt;/b&gt; 트리의 얕은 깊이(Depth) 노드를 읽으면 자연스럽게 낮은 해상도의 복셀 덩어리가 되므로, 거리에 따른 LOD 구현이 매우 쉽습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Empty Space Skipping:&lt;/b&gt; 레이마칭(Ray Marching) 시 큰 빈 공간 노드를 만나면 내부를 검사할 필요 없이 통째로 건너뛸 수 있어 탐색 효율이 높습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;깊이에 따른 수학적 탐사속도&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수학적 시간 복잡도 : $O(logN) 또는 O(D)$&lt;br /&gt;특정 위치의 복셀을 찾거나 레이(Ray)를 쏠 때, 전체 공간 $N$을 뒤질 필요 없이 루트 노드에서부터 해당 위치가 포함된 자식 노드만 따라 내려가면 됩니다.&lt;br /&gt;즉, 최대 깊이 $D$만큼만 검사하면 돼, 수학적으로는 매우 훌륭하고 빠릅니다.&lt;/li&gt;
&lt;li&gt;GPU에서 실제 속도 : &quot;포인터 체이싱(Pointer Chasing)의 지옥&quot;&lt;br /&gt;GPU는 수천 개의 코어가 메모리에 있는 데이터를 한 번에 뭉텅이로 가져올 때(Coalesced Memory Access) 최고의 성능을 냅니다.&lt;br /&gt;그런데 SVO를 탐색하려면 다음 과정을 거쳐야 합니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;부모 노드 메모리 읽기&lt;/li&gt;
&lt;li&gt;어느 자식 노드로 갈지 계산 후, 자식 노드의 메모리 포인터 확인&lt;/li&gt;
&lt;li&gt;자식 노드 메모리 읽기 (캐시 미스 발생 확률 높음)&lt;/li&gt;
&lt;li&gt;다시 그 자식의 주소 확인...&lt;/li&gt;
&lt;/ol&gt;
이렇게 앞의 결과를 알아야만 다음 데이터를 읽을 수 있는 종속적인 메모리 접근이 깊이 $D$만큼 반복됩니다.&lt;br /&gt;깊이가 10이라면, 최악의 경우 GPU가 연산은 안 하고 메모리를 가져오는 데만 10번을 쉬어야 합니다.&lt;br /&gt;수학적으로는 빠르지만, 물리적인 VRAM 접근 지연 때문에 프레임 드랍의 주범이 됩니다.&lt;br /&gt;(이후 설명할 NanoVDB가 트리를 얕고 넓게 만든 이유가 바로 이것입니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;한계점:&lt;/b&gt; 트리가 깊어질수록 자식 노드를 찾기 위한 &lt;b&gt;포인터 체이싱(Pointer Chasing)&lt;/b&gt;이 심해져 GPU의 메모리 캐시 미스(Cache Miss)를 유발합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 데이터보다 노드 간의 관계를 정의하는 포인터 메모리 오버헤드가 큽니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;깊이 제한:&lt;/b&gt; 이론상으로는 무한히 파고들 수 있지만, 실시간 렌더링에서는 메모리와 GPU 구조의 한계 때문에 보통 &lt;b&gt;최대 깊이를 10~12 레벨&lt;/b&gt; 정도로 제한합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SVO는 한 번 깊어질 때마다 해상도가 2배씩 늘어납니다.&lt;/li&gt;
&lt;li&gt;해상도와 깊이의 관계:깊이 $D$일 때, 3D 그리드의 해상도는 $2^D \times 2^D \times 2^D$ 가 됩니다.&lt;/li&gt;
&lt;li&gt;깊이 8 = 256 x 256 x 256&lt;/li&gt;
&lt;li&gt;깊이 10 = 1024 x 1024 x 1024 (약 10억 개의 복셀 공간)&lt;/li&gt;
&lt;li&gt;깊이 12 = 4096 x 4096 x 4096&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 수 킬로미터 단위의 거대한 오픈 월드에서 1cm 크기의 정밀한 복셀을 표현하려고 SVO의 깊이를 계속 늘린다면 어떻게 될까요? 빈 공간을 아무리 잘라낸다 하더라도, 노드 자체를 가리키는 포인터 데이터만으로도 VRAM이 터져버립니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;해결 전략 (Voxel Clipmap):그래서 넓은 월드에서는 단일 SVO 깊이를 무식하게 늘리지 않습니다. 카메라를 중심으로 가까운 곳은 깊이가 깊은(고해상도) SVO를, 먼 곳은 얕은(저해상도) SVO를 여럿 겹쳐서 사용하는 카스케이드(Cascade) 방식을 사용합니다.&lt;br /&gt;섀도우 맵에서 쓰는 CSM(Cascaded Shadow Maps)과 같은 원리입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/better-programming/nvidia-vxgi-unity-voxel-based-rendering-demo-using-direct3d-12-graphics-backend-25c3a32b0f77&quot;&gt;Voxel Clipmap Nvidia VXGI 정보&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:720/format:webp/1*intLDftdDTEnvP7MTOSoYw.png&quot; alt=&quot;VoxelClipmap Example&quot; /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;SVO의 주요 응용 사례 (게임 및 그래픽스)&lt;/b&gt;&lt;br /&gt;이러한 한계에도 불구하고 SVO가 가진 '공간의 계층적 분할'이라는 강력한 장점 때문에 여러 혁신적인 기술에 응용되었습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Voxel Cone Tracing (실시간 글로벌 일루미네이션):&lt;br /&gt;루멘(Lumen)이 등장하기 전, 에픽게임즈와 NVIDIA가 시도했던 차세대 GI 기술(VXGI)의 핵심입니다. 씬(Scene) 전체의 빛과 색상 정보를 SVO에 굽습니다. 그리고 화면의 픽셀에서 빛을 추적할 때 얇은 선(Ray)이 아니라 점점 퍼지는 원뿔(Cone) 형태를 쏩니다. 원뿔이 커지면서 SVO의 더 얕은 깊이(큰 노드, 즉 거칠게 블러링된 빛 정보)를 자연스럽게 읽어와 부드러운 반사광과 간접광을 만들어냅니다.&lt;br /&gt;해당 기술은 복잡하기에 더 간단한 예시를 위해 위 기술의 근간이 됐던 SSAO와 VXAO 기법을 비교해보겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:720/format:webp/1*CIaV-2S71zb5JFw0MZMuCA.png&quot; alt=&quot;SSAO&quot; /&gt;&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 SSAO의 방식이 위와 같이 Screen Pass에서 각 픽셀 위치에서 반구 형태의 Depth Test를 수행했다면,&lt;br /&gt;VXAO는 복셀 안에 지형이나 물체가 얼마나 빽빽하게 들어있는지, 즉 '불투명도(Opacity)' 지도만 만들어 둡니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UNRcb/dJMcadnrmXB/flupo7Kg4wbCH2PMtdl0UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UNRcb/dJMcadnrmXB/flupo7Kg4wbCH2PMtdl0UK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UNRcb/dJMcadnrmXB/flupo7Kg4wbCH2PMtdl0UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUNRcb%2FdJMcadnrmXB%2Fflupo7Kg4wbCH2PMtdl0UK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이렇게 미리 구워놓는 덕분에 화면 밖의 데이터도 3D 복셀로 메모리에 올라가 있기 때문에, 카메라 뒤에 있는 거대한 바위가 드리우는 간접 그림자까지도 화면에 정확하게 계산해 낼 수 있는 겁니다. &lt;b&gt;그리고 화면을 그릴 때 표면에서 원뿔(Cone) 모양의 레이더를 쏩니다.&lt;/b&gt; 원뿔이 넓게 퍼져나가면서 '아, 저 멀리에 불투명도 0.8짜리 거대한 복셀 덩어리(상위 밉맵)가 있네? 그럼 이쪽 방향으론 빛이 덜 들어오겠다'라고 판단하는 거죠.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://miro.medium.com/v2/resize:fit:640/format:webp/1*XACrRn_ZD1yalXD9nOfhUw.png&quot; alt=&quot;VXAO&quot; /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SVO의 게임 산업에서의 사용사례&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대규모 파괴 가능 지형 (Destructible Terrain):&lt;br /&gt;수많은 개체가 맵을 파고드는 생존 게임이나 샌드박스 게임에서 지형을 관리할 때 쓰입니다. 플레이어가 땅을 파내면 해당 구역의 SVO 노드만 쪼개서 업데이트하면 되므로, 폴리곤 메시에 불리언(Boolean) 연산을 하는 것보다 훨씬 빠르고 안정적으로 지형 변화를 처리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;충돌 감지 (Collision Detection) 및 공간 쿼리:&lt;/b&gt;&lt;br /&gt;물리 엔진에서 수많은 오브젝트가 동시에 얽혀있을 때, SVO 구조를 활용하면 아주 빠르게 충돌 가능성이 있는 그룹을 솎아낼 수 있습니다. &quot;이 거대한 노드 안에는 빈 공간뿐이다&quot;라는 것을 한 번의 확인으로 통과할 수 있기 때문입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자동화된 3D LOD (Level of Detail):&lt;/b&gt;&lt;br /&gt;SVO의 레벨 12가 원본 메시라면, 레벨 10, 레벨 8로 올라갈수록 자연스럽게 형태가 단순화된 디테일이 나옵니다. 원경에 있는 오브젝트나 지형을 그릴 때 트리의 상위 노드만 렌더링하는 방식으로 자동 LOD 시스템을 구축할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. SVDAG (Sparse Voxel Directed Acyclic Graph)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SVO가 빈 공간(Empty Space)을 쳐냈다면, SVDAG는 &lt;b&gt;중복되는 공간(Duplicate Space)&lt;/b&gt;마저 하나로 합쳐버리는 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.themoonlight.io/ko/review/hybrid-voxel-formats-for-efficient-ray-tracing&quot;&gt;[논문 리뷰] Hybrid Voxel Formats for Efficient Ray Tracing&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MxzGi/dJMcaaK3tUy/IgaJLOL1b4VtgXFsrCA8x1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MxzGi/dJMcaaK3tUy/IgaJLOL1b4VtgXFsrCA8x1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MxzGi/dJMcaaK3tUy/IgaJLOL1b4VtgXFsrCA8x1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMxzGi%2FdJMcaaK3tUy%2FIgaJLOL1b4VtgXFsrCA8x1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름의 유례를 이미지와 함께 이해해보면 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Sparse Voxel (희소 복셀):&lt;br /&gt;기본 베이스는 SVO와 같습니다. 3D 공간에서 데이터가 없는 빈 곳은 저장하지 않습니다. '희소한' 데이터만 다룹니다.&lt;/li&gt;
&lt;li&gt;Directed Graph (방향성 그래프):&lt;br /&gt;그림 왼쪽의 SVO(Tree)를 보세요. 부모에서 자식으로 화살표가 한 방향으로만 흐릅니다. 그리고 한 자식은 오직 한 부모만 가집니다. 이게 '트리'입니다.&lt;/li&gt;
&lt;li&gt;그림 오른쪽의 SVDAG(Graph)를 보세요. 'Shared Nodes' 부분을 보시면, 하나의 자식 노드를 여러 부모가 동시에 가리키고 있습니다. 부모-자식 관계가 1:N이 아니라 N:M으로 얽히기 시작했습니다. 더 이상 단순한 트리가 아니라, 화살표 방향이 있는 '그래프' 구조가 된 것입니다.&lt;/li&gt;
&lt;li&gt;Acyclic (비순환):그래프 구조이지만, 화살표를 따라가다 보면 절대 자기 자신으로 되돌아오지 않습니다. 만약 자식이 부모를 가리키거나, 형제끼리 서로 가리켜서 뺑뺑 돌 수 있다면(Cycle), 컴퓨터가 데이터를 찾다가 무한 루프에 빠져버립니다.&lt;/li&gt;
&lt;li&gt;SVDAG는 SVO에서 파생되었기 때문에, 항상 '위에서 아래로', '큰 공간에서 작은 공간으로'만 화살표가 흐릅니다. 그래서 '비순환(Acyclic)' 구조가 보장됩니다.&lt;/li&gt;
&lt;li&gt;이게 가장 중요합니다. &quot;Cyclic(순환하는)&quot;의 반대말입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SVDAG 빌드 알고리즘&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dWYTS3/dJMcahDozUO/QaqvZyTyblTYbZtXQeysQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dWYTS3/dJMcahDozUO/QaqvZyTyblTYbZtXQeysQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dWYTS3/dJMcahDozUO/QaqvZyTyblTYbZtXQeysQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdWYTS3%2FdJMcahDozUO%2FQaqvZyTyblTYbZtXQeysQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 1: 최하단 리프 노드 병합 (Leaf Nodes &amp;amp; Hashing)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;대상:&lt;/b&gt; 트리의 가장 깊은 곳에 위치한 실제 데이터 블록 (예: $2 \times 2 \times 2$ 복셀 노드)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작동 원리:&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각 노드가 가진 내부 데이터(색상, 밀도 등)를 기반으로 해시값(Hash Value)을 생성합니다.&lt;/li&gt;
&lt;li&gt;생성된 해시값을 &lt;b&gt;해시 사전(Hash Dictionary)&lt;/b&gt;에 조회합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;중복 발생:&lt;/b&gt; 사전에 동일한 패턴이 이미 존재한다면, 새로운 메모리를 할당하지 않고 기존에 등록된 &lt;b&gt;고유 노드(Unique Leaf Node)&lt;/b&gt;의 주소(포인터)만 반환받아 공유합니다.&lt;/li&gt;
&lt;li&gt;이 과정을 통해 수많은 리프 노드가 단 몇 개의 고유한 데이터 블록으로 압축됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 2: 내부 노드 병합 (Internal Nodes &amp;amp; Pointer Hashing)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;대상:&lt;/b&gt; 리프 노드를 제외한 상위 부모 노드들&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작동 원리:&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;한 단계 위로 올라가면, 부모 노드의 정체성은 데이터가 아니라 '&lt;b&gt;자신이 거느린 8개 자식 노드의 포인터 조합'&lt;/b&gt;으로 결정됩니다.&lt;/li&gt;
&lt;li&gt;이 8개의 자식 포인터 배열을 통째로 해싱(Pointer Hashing)하여 다시 사전에 조회합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;중복 발생:&lt;/b&gt; &quot;내 자식들 구성과 완전히 똑같은 구성을 가진 다른 부모 노드&quot;가 사전에 있다면, 현재 노드는 삭제되고 기존의 &lt;b&gt;고유 내부 노드(Unique Internal Node)&lt;/b&gt;를 가리키게 됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 3: 최종 DAG 구조 완성 (Final Root &amp;amp; DAG Structure)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;대상:&lt;/b&gt; 루트(Root) 노드&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작동 원리:&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Step 2의 과정을 트리의 최상단인 루트 노드에 도달할 때까지 재귀적(Recursive)으로 반복합니다.&lt;/li&gt;
&lt;li&gt;결과적으로 메모리 상의 모든 중복 데이터와 중복 경로가 제거된, 극도로 압축된 &lt;b&gt;방향성 비순환 그래프(DAG)&lt;/b&gt; 형태가 완성됩니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Nano VDB (Nano Volumetric Dynamic B+ Tree)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NanoVDB의 태생을 이해하려면 먼저 영화(VFX) 업계의 표준인 &lt;b&gt;OpenVDB&lt;/b&gt;를 알아야 합니다. 구름, 폭발, 연기 같은 복잡한 볼륨 데이터를 저장하기 위해 만들어진 OpenVDB는 매우 훌륭한 트리 구조를 가졌지만, GPU 기반의 실시간 렌더링에 올리기에는 치명적인 단점이 있었습니다. 바로 잦은 '포인터 체이싱(Pointer Chasing)'과 메모리 파편화입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NVIDIA는 이를 실시간 렌더링에 맞게 개조하여 &lt;b&gt;NanoVDB&lt;/b&gt;를 탄생시켰습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;NanoVDB와 SVO 구조 비교 도식화&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bguTH9/dJMcaaEi7v5/fA9ZiBmOCz1ZkRRBajP8Nk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bguTH9/dJMcaaEi7v5/fA9ZiBmOCz1ZkRRBajP8Nk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bguTH9/dJMcaaEi7v5/fA9ZiBmOCz1ZkRRBajP8Nk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbguTH9%2FdJMcaaEi7v5%2FfA9ZiBmOCz1ZkRRBajP8Nk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조가 GPU에서 압도적인 성능을 내는 핵심 이유는 크게 두 가지입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 얕고 넓은 트리 (Shallow and Wide Tree)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SVO가 2 x 2 x 2씩 잘게 쪼개며 깊이 파고드는(Deep) 방식이었다면, NanoVDB는 가지를 아주 넓게 뻗는(Wide) 방식을 취합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;고정된 4단계 깊이:&lt;/b&gt; NanoVDB는 보통 Root -&amp;gt; Internal Node 1 -&amp;gt; Internal Node 2 -&amp;gt; Leaf Node라는 딱 4단계의 깊이만 가집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;거대한 브랜칭 팩터(Branching Factor):&lt;/b&gt; 한 번 내려갈 때 8개로 쪼개는 게 아니라, 아주 큰 덩어리로 쪼갭니다. 예를 들어 리프 노드 하나가 $8 \times 8 \times 8 = 512$개의 복셀을 한 번에 품고 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;효과:&lt;/b&gt; 트리를 타고 내려가는 횟수 자체가 3~4번으로 고정되므로 탐색 시간이 매우 짧고 일정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3D 좌표값을 인덱싱하는 방법&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvyh8y/dJMb99L7QW4/Dg8fW6WcdouZoakt3V9Adk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvyh8y/dJMb99L7QW4/Dg8fW6WcdouZoakt3V9Adk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvyh8y/dJMb99L7QW4/Dg8fW6WcdouZoakt3V9Adk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcvyh8y%2FdJMb99L7QW4%2FDg8fW6WcdouZoakt3V9Adk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;(X,Y,Z) 총 96비트짜리 데이터가 있다고 가정하자. 여기서 각 축마다 20비트씩 잘라내서 도합 60비트를 Root Node Hash Table의 Key로 삼아, Internal Node2의 1D Array를 구한다.&lt;/li&gt;
&lt;li&gt;각 축마다 남아있는 비트는 12비트다. 여기서 우리는 Internal 2에서 한 축당 비트 5개 ($2^5$ 개)씩 사용하여 한 축에 32개짜리 복셀 공간을 표현할 수 있다.&lt;br /&gt;(이를 통해 해당 공간 값이 (12, 16, 24) 와 같은 식으로 인덱싱 될 수 있는 것 이다.)&lt;/li&gt;
&lt;li&gt;이제 시프트하고 남은 각 축당 비트는 7개다. 이를 4비트와 3비트로 쪼개어 두개의 추가 레이어를 만든 것 이다.&lt;/li&gt;
&lt;li&gt;두 레이어에서도 같은 연산을 반복하고 마지막에 남게되는 Leaf Index를 이용하여 데이터를 읽기/쓰기 하는 구조인 것이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 선형 메모리 직렬화 (Linear Memory Serialization)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 NanoVDB 최적화의 꽃입니다. GPU는 메모리 이곳저곳을 점프하며 읽는 것을 극도로 싫어하고, &lt;b&gt;한 번에 연속된 메모리를 뭉텅이로 긁어오는 것(Coalesced Memory Access)&lt;/b&gt;을 가장 좋아합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;포인터 대신 오프셋(Offset):&lt;/b&gt; NanoVDB는 트리 구조임에도 불구하고, 실제 메모리 상에서는 거대한 1D 배열(Linear Buffer) 딱 하나로 존재합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;배치 방식:&lt;/b&gt; 메모리의 맨 앞에는 Root 노드가, 그 뒤에는 모든 Internal 노드들이 빽빽하게, 그리고 마지막에는 모든 Leaf 노드(실제 복셀 데이터들)가 한 덩어리로 줄을 서 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;효과:&lt;/b&gt; 자식 노드를 찾을 때 주소(Pointer)를 묻는 게 아니라, &quot;내 위치에서 몇 바이트(Byte) 뒤로 가라(Offset)&quot;는 단순 덧셈 연산으로 데이터를 찾습니다. 캐시 미스(Cache Miss)가 획기적으로 줄어듭니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 비트마스크(Bitmask)를 이용한 초고속 빈 공간 건너뛰기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NanoVDB의 노드 안에는 해당 공간에 데이터가 꽉 차 있는지, 텅 비어있는지(Empty Space)를 나타내는 &lt;b&gt;64비트 정수형 비트마스크(Bitmask)&lt;/b&gt;가 들어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이마칭을 하다가 빈 공간을 만나면, 무거운 부동소수점 연산을 할 필요 없이 단순히 비트 연산(Bitwise Operation) 몇 번만으로 &quot;이 노드는 비었으니 통과!&quot; 하고 거대한 공간을 한 번에 스킵해 버립니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. SVO 대비 NanoVDB의 메모리 효율성&lt;/h4&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size18&quot;&gt;① '포인터 메타데이터' 오버헤드 제거&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,0,0&quot;&gt;SVO의 경우:&lt;/b&gt; 자식 노드가 8개라면, 부모 노드는 8개의 메모리 주소(64-bit 포인터)를 들고 있어야 합니다. 트리가 깊어질수록 실제 복셀 데이터(밀도값)보다 &lt;b data-index-in-node=&quot;92&quot; data-path-to-node=&quot;6,0,0&quot;&gt;'길 안내를 위한 포인터 메타데이터'의 용량이 배보다 배꼽이 더 커지는 현상&lt;/b&gt;이 발생합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6,1,0&quot;&gt;NanoVDB의 경우:&lt;/b&gt; 1차원 배열(Flat Array)에 데이터를 빽빽하게 줄 세우고, 비트 연산으로 오프셋을 계산합니다. 각 노드가 무거운 포인터 배열을 들고 있을 필요가 없으므로, 메타데이터가 차지하는 용량이 기하급수적으로 줄어듭니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size18&quot;&gt;② 64비트 마스크(Bitmask)를 통한 극한의 압축&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;NanoVDB의 리프 노드(Leaf Node)나 내부 노드 앞단에는 항상 &lt;b&gt;64비트 정수형 마스크&lt;/b&gt;가 존재합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 &lt;span data-index-in-node=&quot;3&quot; data-math=&quot;4 \times 4 \times 4 = 64&quot;&gt;$4 \times 4 \times 4 = 64$&lt;/span&gt;개의 서브 복셀 공간이 있다면, 이를 64개의 float(256바이트)로 0인지 1인지 저장하는 것이 아닙니다.&lt;/li&gt;
&lt;li&gt;단 하나의 64비트 정수(8바이트)를 사용하여, 각 비트의 0과 1로 빈 공간 여부를 기록합니다. 데이터가 있는 곳만 실제 배열에 값을 할당하므로, 메모리 낭비가 0에 수렴하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size18&quot;&gt;③ 캐시 친화적(Cache-Friendly) 연속 메모리&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;메모리가 파편화되어 있지 않고 한 덩어리의 배열로 연속되어 있기 때문에, GPU가 VRAM에서 데이터를 가져올 때(Memory Fetch) 한 번의 요청으로 대량의 연관 데이터를 캐시에 올릴 수 있습니다. 이는 실질적인 대역폭 효율을 극대화합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;5. OpenVDB와 NanoVDB의 차이&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OpenVDB는 후디니나 블랜더와 같은 곳에서 볼류메트릭한 정적 데이터를 만들 때 사용됩니다. (생성된 확장자는 .vdb입니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로는 배열 기반의 B+ 트리구조인 NanoVDB와는 다르게 포인터 기반으로 작성된 B+트리구조입니다. 다만, NanoVDB와 유사하게 4개의 레이어 계층 (Root, Internal Nodes2, Internal Nodes1, Leaf Data)을 가지고 있어 위에서 서술한 비트 쉬프트를 통한 포인터 인덱스 참조와 같은 로직은 동일 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 구조적 차이 때문에 기존에 문제였던 Pointer Chasing과 메모리 파편화가 그대로 발생한다는 문제점이 있지만, 포인터 기반인 덕에 복셀 데이터의 수정과 삽입이 Nano VDB 보다 훨씬 유리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;때문에 DCC툴인 후디니와 같은 곳에서 OpenVDB를 통해 데이터를 구축하고 리얼타임 렌더링이 필요한 게임 엔진에서는 NanoVDB 기반으로 변환하여 처리합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxWsa1/dJMcacB7LbJ/kULcCfW6kDbyoCXuYk0kh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxWsa1/dJMcacB7LbJ/kULcCfW6kDbyoCXuYk0kh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxWsa1/dJMcacB7LbJ/kULcCfW6kDbyoCXuYk0kh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbxWsa1%2FdJMcacB7LbJ%2FkULcCfW6kDbyoCXuYk0kh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋습니다. 이제 트리를 기반으로 하는 두 메모리 관리 기법의 핵심 원리를 이해했으니 기법별로 사용하는 시점과 차이를 정리해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;NanoVDB와 SVO 사용시점&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-path-to-node=&quot;15&quot; data-ke-size=&quot;size23&quot;&gt;요약 비교 및 정리&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 89px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 22.7519%; height: 21px; text-align: center;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 43.9147%; height: 21px; text-align: center;&quot;&gt;SVO (Sparse Voxel Octree)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 21px; text-align: center;&quot;&gt;NanoVDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 22.7519%; height: 17px;&quot;&gt;메모리 구조&lt;/td&gt;
&lt;td style=&quot;width: 43.9147%; height: 17px;&quot;&gt;노드&amp;nbsp;간&amp;nbsp;포인터&amp;nbsp;기반의&amp;nbsp;트리&amp;nbsp;(파편화됨)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;1D&amp;nbsp;선형&amp;nbsp;배열&amp;nbsp;기반의&amp;nbsp;얕은&amp;nbsp;트리&amp;nbsp;(연속됨)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 22.7519%; height: 17px;&quot;&gt;최대 장점&lt;/td&gt;
&lt;td style=&quot;width: 43.9147%; height: 17px;&quot;&gt;빠른&amp;nbsp;실시간&amp;nbsp;데이터&amp;nbsp;수정(파괴/생성),&amp;nbsp;계층적&amp;nbsp;LOD&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;GPU&amp;nbsp;캐시&amp;nbsp;친화적,&amp;nbsp;압도적인&amp;nbsp;레이마칭&amp;nbsp;탐색&amp;nbsp;속도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 22.7519%; height: 17px;&quot;&gt;최대 단점&lt;/td&gt;
&lt;td style=&quot;width: 43.9147%; height: 17px;&quot;&gt;포인터&amp;nbsp;체이싱으로&amp;nbsp;인한&amp;nbsp;지연,&amp;nbsp;메타데이터&amp;nbsp;메모리&amp;nbsp;낭비&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;동적&amp;nbsp;업데이트(구조&amp;nbsp;변경)&amp;nbsp;비용이&amp;nbsp;매우&amp;nbsp;큼&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 22.7519%; height: 17px;&quot;&gt;주요 사용처&lt;/td&gt;
&lt;td style=&quot;width: 43.9147%; height: 17px;&quot;&gt;파괴&amp;nbsp;가능한&amp;nbsp;동적&amp;nbsp;지형,&amp;nbsp;Voxel&amp;nbsp;Cone&amp;nbsp;Tracing&amp;nbsp;(GI)&lt;/td&gt;
&lt;td style=&quot;width: 33.3333%; height: 17px;&quot;&gt;실시간&amp;nbsp;구름/연기&amp;nbsp;볼륨&amp;nbsp;렌더링,&amp;nbsp;고해상도&amp;nbsp;SDF&amp;nbsp;처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-path-to-node=&quot;15&quot; data-ke-size=&quot;size23&quot;&gt;NanoVDB를 사용해야 하는 경우&lt;/h3&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;NanoVDB는 &lt;b data-index-in-node=&quot;9&quot; data-path-to-node=&quot;16&quot;&gt;&quot;한 번 구워진 데이터를 GPU에서 빛의 속도로 읽어야 할 때&quot;&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,0,0&quot;&gt;볼류메트릭 이펙트 (구름, 연기, 폭발):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;밀도가 제각각인 이질적 볼륨(Heterogeneous Volumes)을 레이마칭(Raymarching)으로 렌더링할 때 압도적인 성능을 냅니다.&lt;/li&gt;
&lt;li&gt;언리얼 엔진의 SVT 백엔드나, 영화 VFX 렌더링에서 표준으로 쓰이는 이유입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,1,0&quot;&gt;고해상도 SDF (Signed Distance Field) 저장:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;매우 복잡한 충돌체나 고품질 루멘표면 캐시를 위해 무거운 3D 거리장 데이터를 저장하고 빠르게 쿼리해야 할 때 유리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;17,2,0&quot;&gt;정적인(Static) 데이터 중심:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1차원 배열로 꽉 짜여 있기 때문에, 복셀 하나를 부수거나 구조를 바꾸려면 배열 전체의 크기를 다시 잡고 메모리를 재할당(Re-build)해야 합니다. 즉, &lt;b data-index-in-node=&quot;88&quot; data-path-to-node=&quot;17,2,1,0,0&quot;&gt;실시간 지형 파괴 같은 동적인 변화에는 매우 쥐약&lt;/b&gt;입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size23&quot;&gt;SVO (Sparse Voxel Octree)를 사용해야 하는 경우&lt;/h3&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;SVO는 &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;19&quot;&gt;&quot;실시간으로 부수고 만들거나, 거대한 계층적 LOD가 필요할 때&quot;&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,0,0&quot;&gt;실시간 파괴 가능 지형 (마인크래프트 류, 샌드박스 게임):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;20,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플레이어가 곡괭이로 땅을 파서 복셀 데이터가 실시간으로 바뀔 때, SVO는 해당 노드의 '포인터'만 똑 끊어버리거나 자식 포인터를 새로 할당하면 그만입니다. 전체 메모리를 다시 짤 필요가 없으므로 &lt;b data-index-in-node=&quot;110&quot; data-path-to-node=&quot;20,0,1,0,0&quot;&gt;동적 업데이트 속도가 매우 빠릅니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,1,0&quot;&gt;자동화된 거대 스케일 LOD 렌더링:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;20,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트리의 얕은 노드(큰 박스) 자체가 자연스러운 저해상도 LOD 역할을 합니다. 먼 거리의 지형을 그릴 때는 굳이 리프 노드까지 내려가지 않고 상위 노드에서 렌더링을 멈출 수 있어, 원경 처리에 강력합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,2,0&quot;&gt;Voxel Cone Tracing (실시간 GI):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;20,2,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빛이 퍼져나가는 원뿔(Cone)의 크기에 맞춰 트리의 상위/하위 노드를 샘플링하여 부드러운 간접광을 만들 때, SVO의 완벽한 계층구조가 필수적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;그렇다면 마인크래프트는 어떤 구조인가?&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 배운 내용을 토대로라면 마인크래프트는 실시간으로 복셀 데이터가 수정되어야 하니 SVO를 기반으로 제작되어야 할 것 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만, 실제로는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Sparse Hash Map&lt;/b&gt;과 해당 자료구조의 단점을 보완한 '&lt;b&gt;Chunk 기반의 하이브리드 해시 맵'&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;을 기반으로 작성됐습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;때문에, Sparse Hash Map과 언리얼에서 사용하고 있는 SVT에 대한 내용도 추가적으로 설명해야 하는데 해당 포스트가 생각보다 길어져 다음 챕터에서 마저 이야기를 진행하도록 하겠습니다.&lt;/p&gt;</description>
      <category>개발</category>
      <category>Graphics</category>
      <category>NANO</category>
      <category>NanoVDB</category>
      <category>SVO</category>
      <category>VDB</category>
      <category>Voxel</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/166</guid>
      <comments>https://develop-4-art.tistory.com/166#entry166comment</comments>
      <pubDate>Fri, 20 Feb 2026 21:03:15 +0900</pubDate>
    </item>
    <item>
      <title>[UE5/Plugin] TurboSequence 심층 분석</title>
      <link>https://develop-4-art.tistory.com/164</link>
      <description>&lt;h2 style=&quot;text-align: start;&quot; data-line=&quot;0&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Github&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/LukasFratzl/TurboSequence&quot;&gt;LukasFratzl/TurboSequence: Skeletal Based GPU Crowds for UE5  &lt;/a&gt;&lt;/p&gt;
&lt;h2 style=&quot;text-align: start;&quot; data-line=&quot;2&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: start;&quot; data-line=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;TurboSequence는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Niagara Mesh Particles를 사용하되, Skeletal Mesh가 아닌 Static Mesh를 렌더링&lt;/b&gt;합니다. 핵심 트릭은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;애니메이션 데이터를 텍스처에 베이크하고, GPU Compute Shader + Vertex Shader에서 실시간 스키닝을 수행&lt;/b&gt;하는 것입니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: start;&quot; data-line=&quot;6&quot; data-ke-size=&quot;size23&quot;&gt;왜 Niagara SkeletalMesh Instance가 아닌가?&lt;/h3&gt;
&lt;p style=&quot;text-align: start;&quot; data-line=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;Niagara의&lt;span&gt;&amp;nbsp;&lt;/span&gt;SkeletalMesh&lt;span&gt;&amp;nbsp;&lt;/span&gt;렌더러는 내부적으로 각 인스턴스마다 개별 draw call을 발생시킵니다. TurboSequence는 이를 우회하여:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; text-align: start;&quot; data-line=&quot;9&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;9&quot;&gt;&lt;b&gt;Static Mesh를 Niagara Mesh Particles로 렌더링&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(단일 instanced draw call)&lt;/li&gt;
&lt;li data-line=&quot;10&quot;&gt;&lt;b&gt;애니메이션을 GPU에서 계산&lt;/b&gt;하여 CPU 오버헤드 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-line=&quot;12&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;text-align: start;&quot; data-line=&quot;14&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;파이프라인 전체 구조&lt;/b&gt;&lt;/h2&gt;
&lt;h3 style=&quot;text-align: start;&quot; data-line=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;1단계: 오프라인 변환 (Content Pipeline)&lt;/h3&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;// TurboSequence_ControlPanelLibrary_Lf.cpp - ConvertSkeletalMeshToTurboSequence_BlueprintThreadSafe
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-line=&quot;22&quot; data-ke-size=&quot;size20&quot;&gt;1.1 Static Mesh 생성&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 원본 Skeletal Mesh의 각 LOD를 Static Mesh로 변환
for (int32 i = 0; i &amp;lt; SkeletalMesh-&amp;gt;GetLODNum(); ++i)
{
    UStaticMesh* StaticMesh = NewObject&amp;lt;UStaticMesh&amp;gt;();
    // Skeletal Mesh의 정점 데이터를 Static Mesh로 복사
    // 본 웨이트는 제거되고 Bind Pose 정점만 저장
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-line=&quot;33&quot; data-ke-size=&quot;size20&quot;&gt;1.2 애니메이션 라이브러리 텍스처 생성&lt;/h4&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;// TurboSequence_Helper_Lf.cpp - CreateAnimationLibraryTexture2D
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: start;&quot; data-line=&quot;38&quot; data-ke-size=&quot;size16&quot;&gt;핵심은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;모든 애니메이션 프레임의 본 Transform을 텍스처에 베이크&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;텍스처 구조:
- Width: NumBones * 3 (각 본당 3픽셀 = Rotation Quat + Translation + Scale)
- Height: TotalAnimationFrames (모든 애니메이션의 모든 프레임)
- Format: RGBA16F (고정밀도)

예시:
- 본 50개, 애니메이션 10개 (각 100프레임) = 150x1000 텍스처
- Pixel[0-2, FrameY] = Bone0의 Rotation(RGBA), Translation(RGB), Scale(RGB)
- Pixel[3-5, FrameY] = Bone1의 데이터...
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: start;&quot; data-line=&quot;52&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;텍스처 픽셀 인코딩&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;tp&quot;&gt;&lt;code&gt;// 각 본의 Transform을 3개 픽셀에 저장
FLinearColor RotationPixel = FLinearColor(Quat.X, Quat.Y, Quat.Z, Quat.W);
FLinearColor TranslationPixel = FLinearColor(Pos.X, Pos.Y, Pos.Z, 0);
FLinearColor ScalePixel = FLinearColor(Scale.X, Scale.Y, Scale.Z, 0);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-line=&quot;60&quot; data-ke-size=&quot;size20&quot;&gt;1.3 본 웨이트 텍스처 생성&lt;/h4&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;// 각 정점의 본 인덱스와 웨이트를 텍스처에 저장
// Width: NumVertices
// Height: 1
// RGBA: (BoneIndex0, Weight0, BoneIndex1, Weight1)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-line=&quot;68&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;text-align: start;&quot; data-line=&quot;70&quot; data-ke-size=&quot;size23&quot;&gt;2단계: 런타임 CPU (Game Thread)&lt;/h3&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;// TurboSequence_Manager_Lf.cpp
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-line=&quot;76&quot; data-ke-size=&quot;size20&quot;&gt;2.1 인스턴스 스폰&lt;/h4&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;FTurboSequence_MinimalMeshData_Lf AddSkinnedMeshInstance_GameThread(...)
{
    // 1. MeshAsset에서 Static Mesh + 텍스처 참조 가져오기
    // 2. Niagara System에 인스턴스 추가 준비
    // 3. GPU에 전달할 메타데이터 구성
    FTurboSequence_MinimalMeshData_Lf Data;
    Data.AnimationStartFrame = 0;
    Data.AnimationEndFrame = 100;
    Data.CustomData = FVector4f(...); // Niagara User Parameter로 전달
    return Data;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-line=&quot;91&quot; data-ke-size=&quot;size20&quot;&gt;2.2 애니메이션 업데이트 (Concurrent)&lt;/h4&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void UpdateMeshAnimation_Concurrent(...)
{
    // CPU에서는 메타데이터만 업데이트 (어떤 애니메이션, 어떤 프레임)
    Instance.AnimationStartFrame = NewAnimStartFrame;
    Instance.AnimationTime += DeltaTime;
    // 실제 본 계산은 GPU에서 수행
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-line=&quot;102&quot; data-ke-size=&quot;size20&quot;&gt;2.3 Solve (Game Thread)&lt;/h4&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;void SolveMeshes_GameThread(float DeltaTime, ...)
{
    // 1. CPU에서 업데이트된 모든 인스턴스 데이터를 모음
    // 2. Niagara System에 인스턴스 데이터 전달
    // 3. GPU Compute Shader 디스패치 스케줄링
    
    for (UpdateGroup : UpdateGroups)
    {
        NiagaraComponent-&amp;gt;SetCustomData(InstanceData); // GPU로 전송
        DispatchComputeShaders(UpdateGroup);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-line=&quot;118&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;text-align: start;&quot; data-line=&quot;120&quot; data-ke-size=&quot;size23&quot;&gt;3단계: 런타임 GPU - Compute Shader&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;// Shaders/Private/BoneSettings_CS_Lf.usf
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-line=&quot;126&quot; data-ke-size=&quot;size20&quot;&gt;3.1 본 데이터 준비 Compute Shader&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;[numthreads(64, 1, 1)]
void BoneSettings_CS_Lf(uint3 ThreadId : SV_DispatchThreadID)
{
    uint InstanceID = ThreadId.x;
    
    // 1. 인스턴스 메타데이터 로드 (Niagara User Parameters)
    float AnimTime = InstanceAnimTimes[InstanceID];
    int StartFrame = InstanceStartFrames[InstanceID];
    int EndFrame = InstanceEndFrames[InstanceID];
    
    // 2. 현재 프레임 계산
    float FrameFloat = StartFrame + (AnimTime * FPS);
    int Frame0 = floor(FrameFloat);
    int Frame1 = ceil(FrameFloat);
    float BlendAlpha = frac(FrameFloat);
    
    // 3. 애니메이션 라이브러리 텍스처에서 본 Transform 샘플링
    for (int BoneIdx = 0; BoneIdx &amp;lt; NumBones; BoneIdx++)
    {
        // 텍스처 UV 계산
        float U0 = (BoneIdx * 3.0f) / TextureWidth;
        float V0 = Frame0 / TextureHeight;
        float V1 = Frame1 / TextureHeight;
        
        // 본 Transform 로드 (3픽셀 = Rotation, Translation, Scale)
        float4 Rot0 = AnimLibraryTexture.SampleLevel(Sampler, float2(U0, V0), 0);
        float4 Trans0 = AnimLibraryTexture.SampleLevel(Sampler, float2(U0 + 1.0/Width, V0), 0);
        float4 Scale0 = AnimLibraryTexture.SampleLevel(Sampler, float2(U0 + 2.0/Width, V0), 0);
        
        float4 Rot1 = AnimLibraryTexture.SampleLevel(Sampler, float2(U0, V1), 0);
        float4 Trans1 = AnimLibraryTexture.SampleLevel(Sampler, float2(U0 + 1.0/Width, V1), 0);
        float4 Scale1 = AnimLibraryTexture.SampleLevel(Sampler, float2(U0 + 2.0/Width, V1), 0);
        
        // 4. 프레임 보간
        float4 FinalRot = QuatSlerp(Rot0, Rot1, BlendAlpha);
        float3 FinalTrans = lerp(Trans0.xyz, Trans1.xyz, BlendAlpha);
        float3 FinalScale = lerp(Scale0.xyz, Scale1.xyz, BlendAlpha);
        
        // 5. 결과를 Per-Instance 본 버퍼에 저장 (다음 단계에서 사용)
        OutBoneTransforms[InstanceID * NumBones + BoneIdx] = 
            ComposeTransform(FinalRot, FinalTrans, FinalScale);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-line=&quot;173&quot; data-ke-size=&quot;size20&quot;&gt;3.2 메시 단위 Compute Shader&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// Shaders/Private/MeshUnit_CS_Lf.usf
[numthreads(64, 1, 1)]
void MeshUnit_CS_Lf(uint3 ThreadId : SV_DispatchThreadID)
{
    uint InstanceID = ThreadId.x;
    
    // 추가 메시별 로직 (LOD 선택, 가시성 등)
    // 실제 정점 스키닝은 Vertex Shader에서 수행
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-line=&quot;186&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;text-align: start;&quot; data-line=&quot;188&quot; data-ke-size=&quot;size23&quot;&gt;4단계: 런타임 GPU - Vertex Shader (Material)&lt;/h3&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;// Shaders/Private/GPU_VertexSkinning_VS_Lf.ush
// Material Function: MF_TurboSequence_PositionOffset_Lf에서 호출
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-line=&quot;195&quot; data-ke-size=&quot;size20&quot;&gt;정점 스키닝 수행&lt;/h4&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;float3 TurboSequence_VertexSkinning(
    float3 LocalPosition,
    uint VertexID,
    uint InstanceID)
{
    // 1. 본 웨이트 텍스처에서 해당 정점의 영향받는 본 정보 로드
    float4 BoneWeightData = BoneWeightTexture.Load(int3(VertexID, 0, 0));
    int BoneIndex0 = int(BoneWeightData.x);
    float Weight0 = BoneWeightData.y;
    int BoneIndex1 = int(BoneWeightData.z);
    float Weight1 = BoneWeightData.w;
    
    // 2. Compute Shader에서 계산한 본 Transform 로드
    float4x4 BoneMatrix0 = BoneTransforms[InstanceID * NumBones + BoneIndex0];
    float4x4 BoneMatrix1 = BoneTransforms[InstanceID * NumBones + BoneIndex1];
    
    // 3. 정점에 본 Transform 적용 (스키닝)
    float3 SkinnedPos0 = mul(float4(LocalPosition, 1.0), BoneMatrix0).xyz;
    float3 SkinnedPos1 = mul(float4(LocalPosition, 1.0), BoneMatrix1).xyz;
    
    // 4. 웨이트 블렌딩
    float3 FinalPosition = SkinnedPos0 * Weight0 + SkinnedPos1 * Weight1;
    
    // 5. World Position Offset으로 출력
    return FinalPosition - LocalPosition; // Offset 반환
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style=&quot;text-align: start;&quot; data-line=&quot;225&quot; data-ke-size=&quot;size20&quot;&gt;Material Graph 연결&lt;/h4&gt;
&lt;pre class=&quot;mathematica&quot;&gt;&lt;code&gt;Material:
  World Position Offset Pin &amp;lt;- MF_TurboSequence_PositionOffset_Lf 출력
  ├─ VertexID (Vertex Interpolator)
  ├─ InstanceID (Niagara Mesh Particles 자동 제공)
  └─ Bone Transform Buffer (Compute Shader 출력)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-line=&quot;234&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 style=&quot;text-align: start;&quot; data-line=&quot;236&quot; data-ke-size=&quot;size23&quot;&gt;5단계: Niagara Instanced Rendering&lt;/h3&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;// Niagara System이 최종 렌더링 수행
NiagaraRenderer-&amp;gt;DrawInstances(
    StaticMesh,                    // 변환된 Static Mesh
    Material,                      // 커스텀 스키닝 Material
    InstanceCount,                 // 10k-50k 인스턴스
    PerInstanceData                // Compute Shader 결과
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: start;&quot; data-line=&quot;248&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심&lt;/b&gt;: Niagara는 하나의&lt;span&gt;&amp;nbsp;&lt;/span&gt;DrawIndexedInstanced&lt;span&gt;&amp;nbsp;&lt;/span&gt;호출로 모든 인스턴스를 렌더링. GPU는 각 인스턴스마다 Vertex Shader를 실행하며, 그 안에서 본 스키닝이 발생.&lt;/p&gt;
&lt;hr data-line=&quot;250&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;text-align: start;&quot; data-line=&quot;252&quot; data-ke-size=&quot;size26&quot;&gt;GPU 메모리 레이아웃&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;GPU Memory:
├─ Animation Library Texture (VRAM)
│  └─ 150x1000x8bytes = ~1.2MB (본 50개, 프레임 1000개 기준)
├─ Bone Weight Texture (VRAM)
│  └─ NumVertices x 16bytes (정점당 4개 float)
├─ Bone Transform Buffer (Compute Shader 출력)
│  └─ NumInstances x NumBones x 64bytes (4x4 행렬)
│     예: 10k instances x 50 bones = ~30MB
└─ Static Mesh Vertex/Index Buffers (VRAM)
   └─ 표준 Static Mesh 데이터
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-line=&quot;267&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;text-align: start;&quot; data-line=&quot;269&quot; data-ke-size=&quot;size26&quot;&gt;성능 최적화 원리&lt;/h2&gt;
&lt;h3 style=&quot;text-align: start;&quot; data-line=&quot;271&quot; data-ke-size=&quot;size23&quot;&gt;왜 빠른가?&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal; text-align: start;&quot; data-line=&quot;273&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-line=&quot;273&quot;&gt;&lt;b&gt;Draw Call 극소화&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;274&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;274&quot;&gt;기존: 10k 인스턴스 = 10k draw calls&lt;/li&gt;
&lt;li data-line=&quot;275&quot;&gt;TurboSequence: 10k 인스턴스 = 1 draw call (아키타입당)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;277&quot;&gt;&lt;b&gt;CPU 부하 이동&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;278&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;278&quot;&gt;기존: CPU에서 본 행렬 계산 &amp;rarr; GPU로 전송&lt;/li&gt;
&lt;li data-line=&quot;279&quot;&gt;TurboSequence: CPU는 메타데이터만 업데이트, GPU가 모든 계산 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;281&quot;&gt;&lt;b&gt;메모리 대역폭 최적화&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;282&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;282&quot;&gt;애니메이션 데이터를 텍스처 압축으로 전송&lt;/li&gt;
&lt;li data-line=&quot;283&quot;&gt;본당 3픽셀(48바이트) vs 기존 본 행렬(64바이트)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 style=&quot;text-align: start;&quot; data-line=&quot;285&quot; data-ke-size=&quot;size23&quot;&gt;트레이드오프&lt;/h3&gt;
&lt;p style=&quot;text-align: start;&quot; data-line=&quot;287&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;장점&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; text-align: start;&quot; data-line=&quot;288&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;288&quot;&gt;수만 개 인스턴스를 단일 draw call로 렌더링&lt;/li&gt;
&lt;li data-line=&quot;289&quot;&gt;CPU 오버헤드 거의 없음&lt;/li&gt;
&lt;li data-line=&quot;290&quot;&gt;애니메이션 블렌딩도 GPU에서 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: start;&quot; data-line=&quot;292&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;단점&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; text-align: start;&quot; data-line=&quot;293&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;293&quot;&gt;Static Mesh 변환 필요 (오프라인 작업)&lt;/li&gt;
&lt;li data-line=&quot;294&quot;&gt;텍스처 메모리 사용 (애니메이션마다 텍스처 생성)&lt;/li&gt;
&lt;li data-line=&quot;295&quot;&gt;복잡한 애니메이션 블렌딩 제한 (CPU 로직 필요 시 병목)&lt;/li&gt;
&lt;li data-line=&quot;296&quot;&gt;본 개수 제한 (30-75개 권장, 텍스처 크기 이슈)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-line=&quot;298&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;text-align: start;&quot; data-line=&quot;300&quot; data-ke-size=&quot;size26&quot;&gt;코드 흐름 요약&lt;/h2&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;1. [Editor] Skeletal Mesh &amp;rarr; Static Mesh + Animation Textures
2. [CPU GameThread] Spawn Instances &amp;rarr; Update Animation Meta
3. [CPU GameThread] SolveMeshes &amp;rarr; Dispatch Compute Shaders
4. [GPU Compute] BoneSettings_CS &amp;rarr; Calculate Bone Transforms per Instance
5. [GPU Compute] MeshUnit_CS &amp;rarr; LOD/Visibility processing
6. [GPU Vertex] Material Vertex Shader &amp;rarr; Skinning per Vertex
7. [GPU Raster] Niagara Instanced Draw Call &amp;rarr; Final Render
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-line=&quot;312&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 style=&quot;text-align: start;&quot; data-line=&quot;314&quot; data-ke-size=&quot;size26&quot;&gt;실제 구현 확인 파일&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc; text-align: start;&quot; data-line=&quot;316&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;316&quot;&gt;&lt;b&gt;Compute Shader&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;Shaders/Private/BoneSettings_CS_Lf.usf,&lt;span&gt;&amp;nbsp;&lt;/span&gt;MeshUnit_CS_Lf.usf&lt;/li&gt;
&lt;li data-line=&quot;317&quot;&gt;&lt;b&gt;Vertex Shader&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;Shaders/Private/GPU_VertexSkinning_VS_Lf.ush&lt;/li&gt;
&lt;li data-line=&quot;318&quot;&gt;&lt;b&gt;Material Functions&lt;/b&gt;: MF_TurboSequence_PositionOffset_Lf.uasset&lt;/li&gt;
&lt;li data-line=&quot;319&quot;&gt;&lt;b&gt;Texture 생성&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;Source/TurboSequence_Lf/Private/TurboSequence_Helper_Lf.cpp::CreateAnimationLibraryTexture2D&lt;/li&gt;
&lt;li data-line=&quot;320&quot;&gt;&lt;b&gt;Compute Dispatch&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;Source/TurboSequence_Lf/Private/TurboSequence_Manager_Lf.cpp::SolveMeshes_GameThread&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: start;&quot; data-line=&quot;322&quot; data-ke-size=&quot;size16&quot;&gt;이 구조로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Niagara의 Static Mesh Instancing + GPU Compute Skinning&lt;/b&gt;을 결합하여 대규모 크라우드 렌더링을 달성합니다.&lt;/p&gt;</description>
      <category>개발/UE5</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/164</guid>
      <comments>https://develop-4-art.tistory.com/164#entry164comment</comments>
      <pubDate>Fri, 16 Jan 2026 17:15:05 +0900</pubDate>
    </item>
    <item>
      <title>[UE5/Interchange] FBX 임포트 LOD 장애 Guide</title>
      <link>https://develop-4-art.tistory.com/163</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;(Interchange FBX &amp;rarr; LOD Description 오류 / Legacy Import &amp;rarr; UV NaN 경고)&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Unreal Engine 5.6.1에서 FBX를 임포트할 때 기본적으로 &lt;b&gt;Interchange Pipeline&lt;/b&gt;이 사용된다.&lt;br /&gt;하지만 일부 FBX 파일(특히 2020.x 포맷 기반의 LOD 포함 에셋)에서 다음과 같은 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- LOD Description 손상&lt;br /&gt;- LOD 그룹 혹은 LODMetadata가 비정상적으로 읽힘&lt;br /&gt;- Interchange FBX Import 실패 또는 메시 빌드 중단&lt;br /&gt;- Legacy FBX Import 사용 시 임포트는 되지만 &lt;b&gt;UV 데이터에 NaN이 포함되는 경고 발생&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본 문서는 위 문제를 해결하거나 예방하기 위한 &lt;b&gt;DCC 단계의 처리 방법&lt;/b&gt;&lt;br /&gt;그리고 Unreal 임포트 파이프라인 설정 관련 절차를 안내한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 문제 상황 요약&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 Interchange FBX Import 사용 시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UE5.6.1에서 FBX 2020.3 파일을 Import하면 다음 오류가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;br /&gt;LOD Description is corrupted or missing.&lt;br /&gt;Failed to import mesh using Interchange pipeline.&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인 후보:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Interchange가 &lt;b&gt;FBX 2020.x 포맷의 LODMetadata parsing&lt;/b&gt;을 완전히 지원하지 않음&lt;br /&gt;- FBX 내 일부 LOD Node의 구조가 구버전 FBX SDK에서 생성된 형태와 충돌&lt;br /&gt;- LOD 그룹의 이름 규칙이 Interchange 기준과 맞지 않는 경우&lt;br /&gt;- FBX 변환 과정에서 잘못된 Custom Attribute/Metadata가 추가된 경우&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 Legacy FBX Import 사용 시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`Interchange.FeatureFlags.Import.FBX=false` 옵션을 통해 &lt;b&gt;구(legacy) FBX Importer&lt;/b&gt;를 강제로 사용하면&lt;br /&gt;임포트 자체는 통과하지만, Cooking 중 아래 경고가 출력된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;br /&gt;Mesh ~ has NaNs in its vertex instance UVs!&lt;br /&gt;Offending uvs are set to zero.&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;* FBX 파일 내부의 &lt;b&gt;일부 버텍스 UV 값이 NaN(Not a Number)&lt;/b&gt; 으로 포함되어 있음&lt;br /&gt;* Interchange는 이 값을 읽지 못해 실패하지만 Legacy는 읽고 &lt;b&gt;0으로 치환하여 임포트 성공&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 근본적인 문제는 &lt;b&gt;FBX 파일 내부 데이터가 깨져 있음&lt;/b&gt;에 가깝다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 해결 전략 개요&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;조치&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;DCC 툴에서 메시의 UV, LOD, History 를 점검하여 데이터 손상 여부 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;FBX Export 시 버전 및 옵션을 정상화 (문제 있는 Metadata 제거 포함)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;UE에서 Interchange 사용 여부 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Legacy로 임포트한 경우 NaN 경고가 뜨면 DCC 단계에서 메시를 정리 후 재수출&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. DCC(Blender / 3ds Max / Maya)에서 확인해야 하는 사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.1 UV 데이터 검증&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Blender&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Edit Mode &amp;rarr; UV Editor 열기&lt;/li&gt;
&lt;li&gt;`Select &amp;rarr; Select All by Trait &amp;rarr; Non-Uniform` 또는 `Select &amp;rarr; Select Linked`&lt;/li&gt;
&lt;li&gt;아래 항목을 점검:&lt;/li&gt;
&lt;li&gt;- UV가 존재하지 않는 Faces&lt;br /&gt;- 면적이 0이거나 뒤틀린 UV&lt;br /&gt;- UV 좌표가 매우 큰 값(Float Overflow 근처)&lt;br /&gt;- Add-on 또는 스크립트로 잘못된 UV 자동 생성의 흔적&lt;/li&gt;
&lt;li&gt;`UV &amp;rarr; Pack Islands` 또는 새 Unwrap으로 재정리할 수 있음&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3ds Max&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;UVW Unwrap Modifier 적용&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;UV View 창에서 다음 확인:&lt;/li&gt;
&lt;li&gt;오른쪽 아래에서 &amp;ldquo;Invalid Faces&amp;rdquo; 자동 선택&lt;br /&gt;UV가 존재하지 않는 Face (Empty Channel)&lt;br /&gt;Custom Attribute에 의해 UV가 자동 변형된 흔적&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Maya&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;UV Editor &amp;rarr; &lt;b&gt;UV &amp;gt; Cleanup&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;옵션:&lt;/li&gt;
&lt;li&gt;&quot;Check for Invalid Components&quot;&lt;br /&gt;&quot;Non-Manifold Geometry&quot;&lt;br /&gt;&quot;Overlapping UVs&quot; (선택)&lt;/li&gt;
&lt;li&gt;필요하면 History 삭제 후 새 UV 생성&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.2 메시/LOD 구조 점검&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FBX 2020.x 기반에서 LOD 그룹이 꼬이는 주요 원인은 다음이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp; LOD 이름 규칙이 Unreal 표준과 불일치&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 예: `mesh_LOD0`, `mesh_LOD1`, `meshLOD2` 같은 혼재된 네이밍&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; LOD Mesh 또는 Group Node 안에 &lt;b&gt;빈 Transform 노드&lt;/b&gt;가 들어가 있는 경우&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; LOD 사이즈가 0 Triangles인 경우&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; LOD 일부가 Hidden/Disabled 상태로 Export된 경우&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; Export 이전에 삭제된 히스토리가 노드 메타데이터로 남아 있는 경우&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4.3 History / Custom Data 삭제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Maya:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Edit &amp;rarr; Delete by Type &amp;rarr; History&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3ds Max:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;-&amp;nbsp;&amp;nbsp; `Utilities &amp;rarr; Reset XForm &amp;rarr; Collapse`&lt;br /&gt;-&amp;nbsp;&amp;nbsp; `Editable Poly &amp;rarr; Clean up`&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Blender:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;-&amp;nbsp;&amp;nbsp; `Object &amp;rarr; Apply &amp;rarr; All Transforms`&lt;br /&gt;-&amp;nbsp;&amp;nbsp; `Mesh &amp;rarr; Clean Up &amp;rarr; Delete Loose / Degenerate Dissolve`&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 정리 과정에서 NaN UV 또는 잘못된 LOD Metadata가 사라지는 경우가 많다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. FBX Export 설정 가이드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FBX 버전 선택&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; UE5.6.x와의 호환성이 가장 높은 버전: &lt;b&gt;FBX 2018 / 2019&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 2020.x는 Interchange 지원 범위가 미완성인 부분이 있어 문제가 발생할 수 있음&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;권장 Export 설정&lt;/h3&gt;
&lt;table style=&quot;height: 201px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr style=&quot;height: 24px;&quot;&gt;
&lt;th style=&quot;height: 24px;&quot;&gt;설정&lt;/th&gt;
&lt;th style=&quot;height: 24px;&quot;&gt;값&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;FBX Version&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;&lt;b&gt;2018 / 2019&lt;/b&gt; 권장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Smoothing Groups&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;ON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;Tangents &amp;amp; Binormals&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;OFF (UE에서 자동 생성 권장)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Preserve Instances&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;OFF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;Embed Media&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;OFF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;Bake Animation&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;OFF (필요 시 최소화)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;Triangulate&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;ON (특수 메시일 경우)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;Add Leaf Bones&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;OFF (특히 Blender)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;Units&lt;/td&gt;
&lt;td style=&quot;height: 21px;&quot;&gt;cm 또는 m &amp;rarr; UE 스케일 일관성 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. Unreal Engine Import 절차&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.1 Interchange 기본 사용 (권장)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 없는 FBX라면 아래 옵션을 그대로 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; `Interchange.FeatureFlags.Import.FBX=true` (기본값)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 향후 UE 메이저 버전에서 지원 지속&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; LOD 및 메타데이터 처리 개선&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.2 Interchange 실패 시 Legacy Import 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 콘솔 변수로 Legacy FBX Import 강제 가능:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Interchange.FeatureFlags.Import.FBX=false&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 임포트는 성공할 수 있지만, 메시 빌드 중 UV NaN 경고가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경고 예시:&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Mesh ~ has NaNs in it's vertex instance uvs! Offending uvs are set to zero.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6.3 NaN UV 발생 시 조치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; NaN 값은 Unreal이 자동으로 (0,0) 으로 치환하지만 &lt;b&gt;정확한 UV가 필요한 경우(머티리얼, 라이트맵)&lt;/b&gt;&amp;nbsp;반드시 DCC에서 수정해야 한다.&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 아래 단계 수행:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;LOD0 기준으로 메시/UV를 처음부터 다시 정리&lt;/li&gt;
&lt;li&gt;LOD1 이상도 동일하게 UV 정합성 유지&lt;/li&gt;
&lt;li&gt;FBX 2018/2019 포맷으로 재수출&lt;/li&gt;
&lt;li&gt;Interchange Import로 재시도&lt;/li&gt;
&lt;li&gt;여전히 실패하면 Legacy Import 후 메시/LOD 재빌드&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 문제 원인 정리&lt;/h2&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;문제&lt;/th&gt;
&lt;th&gt;원인&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Interchange Import&lt;/td&gt;
&lt;td&gt;LOD Description 오류&lt;/td&gt;
&lt;td&gt;FBX 2020.x LODMetadata 불일치 / 노드구조 손상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legacy Import&lt;/td&gt;
&lt;td&gt;UV NaN 발생&lt;/td&gt;
&lt;td&gt;FBX 내부 UV Float 값이 NaN으로 포함되어 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DCC&lt;/td&gt;
&lt;td&gt;UV 및 메시 구조 손상&lt;/td&gt;
&lt;td&gt;잘못된 Modifier/History/Export 스크립트 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; UE5.6.x의 Interchange는 FBX 2020.x 포맷에서 일부 LOD 구조를 완벽히 해석하지 못해 문제가 발생할 수 있다.&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; Legacy Import로 우회할 수 있지만, UV NaN 문제처럼 &lt;b&gt;FBX 자체 데이터 손상&lt;/b&gt;이 드러날 수 있다.&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 가장 확실한 해결책은 &lt;b&gt;DCC 단계에서 메시 및 LOD 구조를 정리하고 Stable FBX Format(2018/2019)&lt;/b&gt; 으로 재수출하는 것이다.&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 가능하다면 Interchange Import를 사용하는 것이 장기적으로 안전하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 추가 참고 자료&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; Unreal Documentation &amp;ndash; Interchange Framework&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; FBX SDK 2019 vs 2020 Metadata 차이&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; Blender FBX Export Known Issues (Add Leaf Bones / Broken UV Data)&lt;br /&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;-&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; Autodesk FBX 2020 Change Notes&lt;/p&gt;</description>
      <category>개발/UE5</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/163</guid>
      <comments>https://develop-4-art.tistory.com/163#entry163comment</comments>
      <pubDate>Mon, 8 Dec 2025 09:26:33 +0900</pubDate>
    </item>
    <item>
      <title>[UE5/Networking] IRIS 네트워크 모델 분석</title>
      <link>https://develop-4-art.tistory.com/161</link>
      <description>&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #172b4d; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기존 네트워크 기법과의 차이&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개념&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Push 모델 기반&lt;/li&gt;
&lt;li&gt;Replication Graph 의 경우 필터링으로 대체 됨&lt;/li&gt;
&lt;li&gt;브릿지와 리플리케이션 시스템으로 구성. 목표는 기존 Polling 기법 기반으로 수 많은 virtual call을 탐색 및 호출하는 구조를 개선하기 위함.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RPC
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이제 무조건 RPC는 RPC 수행 대상자의 Replicated State Data가 적용된 이후에 수행된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;활성화 방법&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;PushDown 모델 활성화 필요
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 아이리스에 대해서만 활성화 하고 싶다면, '&lt;span style=&quot;color: #003366;&quot;&gt;Net.IsPushModelEnabled' 활성화하면 됨&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;pgsql&quot; data-bidi-marker=&quot;true&quot;&gt;&lt;code&gt;// Enable iris if it is not already on by default
if (!bUseIris)
{
    // If we enable Iris for a single target we also need to set the TargetBuildEnvironment to unique, as other projects in the solution might want it compiled out
    BuildEnvironment = TargetBuildEnvironment.Unique;
    bUseIris = true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;구조&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;ReplicationState
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가장 기초적인 네트워크 Primitive 데이터이며, 사실상 리플리케이트 될 데이터를 담는다.&lt;/li&gt;
&lt;li&gt;해당 데이터를 설명하기 위해 ReplicationStateDescriptor를 하나씩 들고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ReplicationStateDescriptor
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 레이아웃, 조건, 필터링, 우선순위 설정, 직렬화 방법에 대한 데이터를 들고 있다.&lt;/li&gt;
&lt;li&gt;해당 State Descriptor를 커스터마이징하여 사용하는 예시는 TestNetworkPlugin에서 볼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ReplicationFrament
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게임플레이 코드와 리플리케이션 시스템 사이에서 스테이트를 주고 받는 작업을 담당한다. (말로는 이해 불가, 내부 코드 분석은 아래와 같다.)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ReplicationFragment.h&amp;nbsp;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;FReplicationStateOwnerCollector&lt;/li&gt;
&lt;li&gt;FReplicationStateApplyContext
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Descriptor pointer&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;주석
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나 이상의 ReplicationState를 오너와 바인딩한다. 이는 NetObject를 구성할 때 핵심이 된다.&lt;/li&gt;
&lt;li&gt;게임쪽에서 이 데이터를 추출하거나 지정할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ReplicationProtocol
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FReplicationProtocol은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;정적인 구조 정의&lt;/b&gt;이고,&lt;br /&gt;FReplicationInstanceProtocol은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;실제 객체 인스턴스와 연결된 동적인 상태&lt;/b&gt;를 관리합니다.&lt;hr data-ke-style=&quot;style1&quot; /&gt;  구조 관계 요약&lt;br /&gt;
&lt;div&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;FReplicationProtocol&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;어떤 데이터가 복제될지 정의 (정적)&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;FReplicationStateDescriptor&lt;span&gt;&amp;nbsp;&lt;/span&gt;배열 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;FReplicationInstanceProtocol&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;실제 객체 인스턴스의 복제 상태 관리 (동적)&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;FReplicationFragment*&lt;span&gt;&amp;nbsp;&lt;/span&gt;배열 포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;FReplicationFragment&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;실제 복제 상태 추적 및 직렬화&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;FReplicatedState&lt;span&gt;&amp;nbsp;&lt;/span&gt;포함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;FReplicationBridge&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;객체 등록/해제 및 Protocol &amp;harr; Instance 연결&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;FReplicationInstanceProtocol&lt;span&gt;&amp;nbsp;&lt;/span&gt;생성 및 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;NetHandle&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;&quot; data-bidi-marker=&quot;true&quot;&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;아이리스 시스템 초기화 전체흐름&lt;/b&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;FReplicationSystemUtil::BeginReplicationForActorsInWorldForNetDriver&lt;/li&gt;
&lt;li&gt;AActor::BeginReplication
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Call::FReplicationSystemUtil::BeginReplication&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Called From
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FReplicationSystemUtil::BeginReplicationForActorsInWorldForNetDriver&lt;/li&gt;
&lt;li&gt;FReplicationSystemUtil::FlushNetDormancy&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;FReplicationSystemUtil::BeginReplication
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ForEachReplicationSystem{Call::EngineReplicationBridge::StartReplicatingActor}&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;FReplicationSystemUtil::AddDependentActor
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ForEachReplicationSystem{Call::EngineReplicationBridge::StartReplicatingActor)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;EngineReplicationBridge::StartReplicatingActor&lt;/li&gt;
&lt;li&gt;ObjectReplicationBridge::StartReplicatingRootObject&lt;/li&gt;
&lt;li&gt;ObjectReplicationBridge::StartReplicatingNetObject
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;pre class=&quot;ada&quot; data-bidi-marker=&quot;true&quot;&gt;&lt;code&gt;개별 인스턴스 프로토콜 생성 : FReplicationInstanceProtocolPtr InstanceProtocol = FReplicationProtocolManager::CreateInstanceProtocol&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;인스턴스 프로토콜 구분자 계산 : FReplicationProtocolIdentifier ProtocolIdentifier&amp;nbsp; = FReplicationProtocolManager::CalculateProtocolIdentifier&lt;/li&gt;
&lt;li&gt;프로토콜 구분자와 아키텍쳐 타입을 이용해 프로토콜 가져옴 못 찾으면 아래에서 아에 생성함 : FReplicationProtocol* ReplicationProtocol = ProtocolManager&amp;rarr;GetReplicationProtocol(ProtocolIdentifier, ArchetypeOrCDOUsedAsKey)&lt;/li&gt;
&lt;li&gt;넷 핸들러 구축 및 인스턴스 연결
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FNetHandle NetHandle = FNetHandleManager::GetOrCreateNetHandle(Instance&amp;larr;리플리케이트할 대상을 기반으로 넷핸들러 생성)&lt;/li&gt;
&lt;li&gt;FNetRefHandle RefHandle = UReplicationBridge::InternalCreateNetObject(AllocatedRefHandle, NetHandle, ReplicationProtocol)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;AllocatedRefHandle과 프로토콜을 이용해 넷 오브젝트 생성 맟 핸들 반환 : UReplicationBridge::InternalCreateNetObject&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;더티 트래킹을 위해 인스턴스와 인스턴스 프로토콜을 바인딩 : UReplicationBridge::InternalAttachInstanceToNetRefHandle&lt;/li&gt;
&lt;li&gt;Push Model 인 경우 인스턴스에 PushId 지정 : UObjectReplicationBridge::SetNetPushIdOnInstance&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre class=&quot;&quot; data-bidi-marker=&quot;true&quot;&gt;&lt;code&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;더티 트래킹의 전체 흐름&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;BeginReplication()&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;StartReplicatingActor()&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;FReplicationBridge가&lt;span&gt;&amp;nbsp;&lt;/span&gt;FReplicationInstanceProtocol을 생성.&lt;/li&gt;
&lt;li&gt;이때&lt;span&gt;&amp;nbsp;&lt;/span&gt;FReplicationFragment들이 바인딩되고, 각 Fragment는 자신의&lt;span&gt;&amp;nbsp;&lt;/span&gt;FReplicatedState를 관리.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;데이터 변경 감지 (Dirty Detection)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;이 함수는 각 프레임마다 호출되어&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;상태의 변경 여부를 감지&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;내부적으로는&lt;span&gt;&amp;nbsp;&lt;/span&gt;FReplicatedState의 메모리를 비교하거나, 커스텀 로직을 통해 변경 여부를 판단합니다.&lt;/li&gt;
&lt;/ul&gt;
  변경 감지 방식
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;메모리 비교&lt;/b&gt;: 이전 상태와 현재 상태를 비교하여 변경된 바이트를 추적.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;커스텀 로직&lt;/b&gt;:&lt;span&gt;&amp;nbsp;&lt;/span&gt;PollReplicationStateChanges()를 오버라이드하여 특정 조건에서만 더티로 간주.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;더티 마킹 (Dirty Marking)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;변경된 상태는 이 버퍼에 기록됩니다.&lt;/li&gt;
&lt;li&gt;각&lt;span&gt;&amp;nbsp;&lt;/span&gt;FReplicatedState는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;더티 비트 마스크&lt;/b&gt;를 통해 어떤 필드가 변경되었는지 추적합니다.&lt;/li&gt;
&lt;/ul&gt;
 &lt;span&gt;&amp;nbsp;&lt;/span&gt;FReplicationInstanceProtocol::MarkStateDirty()
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Fragment가 변경된 상태를 감지하면 이 함수를 호출하여 해당 상태를 더티로 마킹.&lt;/li&gt;
&lt;li&gt;이후 전송 대상에 포함됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;4.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;직렬화 및 전송&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;더티로 마킹된 상태만 직렬화하여 네트워크 패킷에 포함.&lt;/li&gt;
&lt;li&gt;FNetSerializer를 통해 필드 단위 직렬화가 수행됨.&lt;/li&gt;
&lt;/ul&gt;
 &lt;span&gt;&amp;nbsp;&lt;/span&gt;FReplicationProtocol::ReplicationStateDescriptors
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;어떤 필드를 어떻게 직렬화할지 정의된 메타데이터를 기반으로 처리.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;5.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;클라이언트에서 수신 및 적용&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;수신된 패킷을 역직렬화하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;FReplicatedState에 적용.&lt;/li&gt;
&lt;li&gt;FReplicationFragment::ApplyReplicatedState()를 통해 실제 객체에 반영.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;핵심 구조체 변화 요약&lt;hr data-ke-style=&quot;style1&quot; /&gt;고급 팁
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;IRIS는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;푸쉬 모델&lt;/b&gt;이기 때문에, 더티 상태를 감지하지 못하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;복제가 발생하지 않습니다&lt;/b&gt;.&lt;/li&gt;
&lt;li&gt;PollReplicationStateChanges()를 커스터마이징하면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;조건부 복제&lt;/b&gt;나&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;최적화된 전송&lt;/b&gt;이 가능합니다.&lt;/li&gt;
&lt;li&gt;FReplicationFragment는 상태를 직접 관리하므로,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;커스텀 Fragment를 구현&lt;/b&gt;하면 더티 트래킹 로직을 세밀하게 제어할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;FReplicatedState&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;변경된 필드의 값이 업데이트됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;FReplicationStateChangeBuffer&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;변경된 필드의 인덱스/비트가 기록됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;FReplicationInstanceProtocol&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;해당 Fragment의 상태를 더티로 마킹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;FNetSendHandler&lt;/td&gt;
&lt;td style=&quot;text-align: left;&quot;&gt;더티 상태만 직렬화하여 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;li id=&quot;id-[기능분석]IRIS네트워크모델- FNetReceiveHandler::DeserializeAndApply()&quot; style=&quot;color: #172b4d;&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;FNetReceiveHandler::DeserializeAndApply()&lt;/li&gt;
&lt;li id=&quot;id-[기능분석]IRIS네트워크모델- FNetSendHandler::SerializeAndSend()&quot; style=&quot;color: #172b4d;&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;FNetSendHandler::SerializeAndSend()&lt;/li&gt;
&lt;li id=&quot;id-[기능분석]IRIS네트워크모델- FReplicationStateChangeBuffer&quot; style=&quot;color: #172b4d;&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;FReplicationStateChangeBuffer&lt;/li&gt;
&lt;li id=&quot;id-[기능분석]IRIS네트워크모델- FReplicationFragment::PollReplicationStateChanges()&quot; style=&quot;color: #172b4d;&quot;&gt; &lt;span&gt;&amp;nbsp;&lt;/span&gt;FReplicationFragment::PollReplicationStateChanges()&lt;/li&gt;
&lt;li id=&quot;id-[기능분석]IRIS네트워크모델-1.객체가등록되고초기화됨&quot; style=&quot;color: #172b4d;&quot;&gt;1.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;객체가 등록되고 초기화됨&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발/UE5</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/161</guid>
      <comments>https://develop-4-art.tistory.com/161#entry161comment</comments>
      <pubDate>Fri, 5 Sep 2025 16:56:40 +0900</pubDate>
    </item>
    <item>
      <title>[Rendering] First Person Rendering</title>
      <link>https://develop-4-art.tistory.com/160</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;참고 발표&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=11sLIyw0pWQ&quot;&gt;First-Person Rendering in Unreal Engine 5.6 | Unreal Fest Orlando 2025&lt;/a&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=11sLIyw0pWQ&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/A3TRC/hyZGg3d6lf/Q18lXydroVpKHBkIGVpVc1/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720,https://scrap.kakaocdn.net/dn/basgsF/hyZDY3qJyN/OEUkmtLYvNrfctVQzgOhA0/img.jpg?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot; data-video-width=&quot;860&quot; data-video-height=&quot;484&quot; data-video-origin-width=&quot;860&quot; data-video-origin-height=&quot;484&quot; data-ke-mobilestyle=&quot;widthContent&quot; data-video-title=&quot;First-Person Rendering in Unreal Engine 5.6 | Unreal Fest Orlando 2025&quot; data-original-url=&quot;&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/11sLIyw0pWQ&quot; width=&quot;860&quot; height=&quot;484&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption style=&quot;display: none;&quot;&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;언리얼 엔진에서 1인칭 솔루션을 제공하는 이유&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;도전과제&lt;/b&gt;&lt;/h3&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 씬 클리핑&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1281&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XsuyX/btsQ3hxsTzl/nr40xqoxV0Jb0X9w0yQBK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XsuyX/btsQ3hxsTzl/nr40xqoxV0Jb0X9w0yQBK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XsuyX/btsQ3hxsTzl/nr40xqoxV0Jb0X9w0yQBK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXsuyX%2FbtsQ3hxsTzl%2Fnr40xqoxV0Jb0X9w0yQBK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1281&quot; height=&quot;720&quot; data-origin-width=&quot;1281&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Field of View&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1841&quot; data-origin-height=&quot;994&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/svHcA/btsQ4COop6B/dxUR1Ebfq8F81lli4PZDs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/svHcA/btsQ4COop6B/dxUR1Ebfq8F81lli4PZDs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/svHcA/btsQ4COop6B/dxUR1Ebfq8F81lli4PZDs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsvHcA%2FbtsQ4COop6B%2FdxUR1Ebfq8F81lli4PZDs0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1841&quot; height=&quot;994&quot; data-origin-width=&quot;1841&quot; data-origin-height=&quot;994&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Fake Legs&lt;/b&gt;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;719&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bU7i2o/btsQ480oKCA/ScebljCUJa3ykbIrkoOhp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bU7i2o/btsQ480oKCA/ScebljCUJa3ykbIrkoOhp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bU7i2o/btsQ480oKCA/ScebljCUJa3ykbIrkoOhp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbU7i2o%2FbtsQ480oKCA%2FScebljCUJa3ykbIrkoOhp1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1284&quot; height=&quot;719&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;719&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. Lighting, Shadow&lt;/b&gt;&lt;/h4&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;1028&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CB45T/btsQ3FdPhak/v049vhLwCx8k2Ls1MnK7Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CB45T/btsQ3FdPhak/v049vhLwCx8k2Ls1MnK7Qk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CB45T/btsQ3FdPhak/v049vhLwCx8k2Ls1MnK7Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCB45T%2FbtsQ3FdPhak%2Fv049vhLwCx8k2Ls1MnK7Qk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1828&quot; height=&quot;1028&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;1028&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;b&gt;Reflections&lt;/b&gt;&lt;/h4&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;954&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4ckVs/btsQ5i24Taq/tMKj9DLxkrhUvAK1aeOVS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4ckVs/btsQ5i24Taq/tMKj9DLxkrhUvAK1aeOVS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4ckVs/btsQ5i24Taq/tMKj9DLxkrhUvAK1aeOVS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4ckVs%2FbtsQ5i24Taq%2FtMKj9DLxkrhUvAK1aeOVS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1830&quot; height=&quot;954&quot; data-origin-width=&quot;1830&quot; data-origin-height=&quot;954&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;도전과제 요약정리&lt;/b&gt;&lt;br /&gt;&lt;img style=&quot;text-align: center; caret-color: transparent; letter-spacing: 0px;&quot; src=&quot;https://blog.kakaocdn.net/dna/lT5zW/btsQ5zjdLvc/AAAAAAAAAAAAAAAAAAAAAEx9JSOOr4TXkHl2cjBPe6fH-mOGVivcSV7AUOlcvC93/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1761922799&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=EL%2FUrcAiurMA9F7dXVT4lYfECQQ%3D&quot; data-origin-width=&quot;1060&quot; data-origin-height=&quot;949&quot; data-is-animation=&quot;false&quot; /&gt;&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;기존 해결방법&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;에셋 기반으로 해결 하던 방식&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;b&gt;Headless Mesh&lt;/b&gt; &lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;span&gt;&lt;b&gt;&lt;b&gt;Floating Arms&lt;/b&gt; &lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;br /&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;721&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9saT4/btsQ4Na824A/X4LrkVhsPRFkz1b13zpuT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9saT4/btsQ4Na824A/X4LrkVhsPRFkz1b13zpuT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9saT4/btsQ4Na824A/X4LrkVhsPRFkz1b13zpuT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9saT4%2FbtsQ4Na824A%2FX4LrkVhsPRFkz1b13zpuT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;721&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;721&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;713&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5JPnF/btsQ6fExoqM/BvyXXOfLipheKG2dAXPTFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5JPnF/btsQ6fExoqM/BvyXXOfLipheKG2dAXPTFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5JPnF/btsQ6fExoqM/BvyXXOfLipheKG2dAXPTFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5JPnF%2FbtsQ6fExoqM%2FBvyXXOfLipheKG2dAXPTFK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1282&quot; height=&quot;713&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;713&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;br /&gt;렌더링 기법을 통해 해결 하던 방식&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Compositing&lt;br /&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1283&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yP7aL/btsQ3IVZPYP/JC2KKQ1vTTUv9yRAd9GWM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yP7aL/btsQ3IVZPYP/JC2KKQ1vTTUv9yRAd9GWM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yP7aL/btsQ3IVZPYP/JC2KKQ1vTTUv9yRAd9GWM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyP7aL%2FbtsQ3IVZPYP%2FJC2KKQ1vTTUv9yRAd9GWM1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1283&quot; height=&quot;720&quot; data-origin-width=&quot;1283&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1인칭과 3인칭 각각 따로 그린 뒤 씬을 합치는 방법을 고안해봤으나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 방법에서는 빛과 그림자를 결합하는 과정에서 해결 불가능한 문제가 생기며, 비용 또한 높다는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Seperate Render Pass&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;713&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk43qD/btsQ3daRxHp/ajXX4CsyN7t0kty76SyCeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk43qD/btsQ3daRxHp/ajXX4CsyN7t0kty76SyCeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk43qD/btsQ3daRxHp/ajXX4CsyN7t0kty76SyCeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk43qD%2FbtsQ3daRxHp%2FajXX4CsyN7t0kty76SyCeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1284&quot; height=&quot;713&quot; data-origin-width=&quot;1284&quot; data-origin-height=&quot;713&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1인칭 메시를 먼저 그린 뒤 뎁스 스텐실과 같은 장치를 통해 3인칭 뷰 정보가 이를 덮어 씌우지 못 하도록 그리는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 꽤 괜찮지만, 1인칭 메시가 Depth 정보를 모두 덮어 씌우기 때문에 Depth 기반의 Post Process를 사용하지 못 하는 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 이 문제는 반투명 객체나 파티클과의 결합에서도 문제를 야기하기 때문에 일반적인 게임에선 사용하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Custom Projection Matrix&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;717&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K4XFG/btsQ3IBGPkB/6gaLFNh4z3JujKLEjELLn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K4XFG/btsQ3IBGPkB/6gaLFNh4z3JujKLEjELLn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K4XFG/btsQ3IBGPkB/6gaLFNh4z3JujKLEjELLn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK4XFG%2FbtsQ3IBGPkB%2F6gaLFNh4z3JujKLEjELLn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;717&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;717&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1인칭 FOV 데이터를 분리하여 1인칭에 맞는 메트릭스를 별도로 구성, 이를 기반으로 1인칭 메시들을 그려 빛과 그림자가 드리우게 하고 3인칭 이후에 글려 클리핑을 방지하도록 하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, Fake Legs과 Legs의 지면 클리핑 문제는 해결해야 하는 문제이며 별도의 매트릭스를 관리해야한다는 점에서 다소 복잡성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇지만, 언리얼엔진은 이 방법이 그렇게 어려운건 아니라 생각해 이 아이디어를 바탕으로 1인칭 FOV 데이터를 분리, 별도의 메트릭스를 구성하여 렌더링하도록 하는 방법을 채택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;언리얼에서 제공하는 솔루션&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Cliping&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style8&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;b&gt; Full Scale Geometry &lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 50%; text-align: center;&quot;&gt;&lt;b&gt; Scaled Geometry &lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;1004&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n7iU0/btsQ6P6IOnu/K9DjDb1xc3VPWPrIV8edDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n7iU0/btsQ6P6IOnu/K9DjDb1xc3VPWPrIV8edDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n7iU0/btsQ6P6IOnu/K9DjDb1xc3VPWPrIV8edDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn7iU0%2FbtsQ6P6IOnu%2FK9DjDb1xc3VPWPrIV8edDK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1828&quot; height=&quot;1004&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;1004&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;span&gt;&lt;br /&gt;&lt;/span&gt;전체 사이즈 지오메트리로 라이팅을 연산하는 경우 굉장히 정확하게 그려짐으로, 대부분의 상황에 제대로 보인다.&lt;br /&gt;&lt;br /&gt;다만, 클리핑 되는 상황이 발생하는 경우 실제로 클리핑 되어야 하는 안쪽의 경우 어둡기 때문에 사진과 같이 그림자 지게 보인다.&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;858&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d891Ec/btsQ3ZpI1uU/mDvFlrZ0MbmKYECkT1VrZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d891Ec/btsQ3ZpI1uU/mDvFlrZ0MbmKYECkT1VrZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d891Ec/btsQ3ZpI1uU/mDvFlrZ0MbmKYECkT1VrZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd891Ec%2FbtsQ3ZpI1uU%2FmDvFlrZ0MbmKYECkT1VrZ1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1796&quot; height=&quot;858&quot; data-origin-width=&quot;1796&quot; data-origin-height=&quot;858&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;br /&gt;다소 부정확한 라이팅이 일반적으로 사용되어 이따금씩 플리커링 처럼 그림자가 생기는 이슈가 있을 수 있다.&lt;br /&gt;&lt;br /&gt;다만, 클리핑되는 지역에서도 정상적인 느낌으로 그림자를 줄 수 있다는 장점이 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 두 상황을 적절히 타개하기 위해 언리얼 엔진에서는 Scaling and Morphing을 적용하기로 했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1288&quot; data-origin-height=&quot;722&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gxt1H/btsQ5jukNZp/N29NijZrYTOYuwZNQi43jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gxt1H/btsQ5jukNZp/N29NijZrYTOYuwZNQi43jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gxt1H/btsQ5jukNZp/N29NijZrYTOYuwZNQi43jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGxt1H%2FbtsQ5jukNZp%2FN29NijZrYTOYuwZNQi43jk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1288&quot; height=&quot;722&quot; data-origin-width=&quot;1288&quot; data-origin-height=&quot;722&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;수식 설명에 필요한 기본 수학 지식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(추가 작성 예정)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;행렬변환&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;역행렬&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로젝션 행렬&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;솔루션에 사용되는 수식 설명&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FOV Correction&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1716&quot; data-origin-height=&quot;844&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qfZNX/btsQ5hwxllB/o1UqXTXW0Vcq5awwSrM7wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qfZNX/btsQ5hwxllB/o1UqXTXW0Vcq5awwSrM7wk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qfZNX/btsQ5hwxllB/o1UqXTXW0Vcq5awwSrM7wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqfZNX%2FbtsQ5hwxllB%2Fo1UqXTXW0Vcq5awwSrM7wk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1716&quot; height=&quot;844&quot; data-origin-width=&quot;1716&quot; data-origin-height=&quot;844&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;M * V * P_fp = M * 'T' * V * P = M * V * P_fp * P^-1 * V^-1 * V * P&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 여기서 변환행렬 'T'를 구하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;유도 과정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;M*V*P_fp = M*T*V*P =&amp;gt; M 역행렬을 양쪽에 곱함 =&amp;gt; V*P_fp = T*V*P&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;양쪽에 V역행렬을 곱함=&amp;gt;P_fp = T*P =&amp;gt; P 역행렬을 곱함 =&amp;gt; T = P_fp * P^-1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; M*V*P_fp = M * (P_fp*P^-1) * V * P&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 그럼 여기서 왜 V^-1*V를 중간에 넣어주는가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 T 연산 자체가 카메라 공간(V)에서 이뤄져야 하는 연산이기 때문이다. 따라서 M * V 로 카메라 공간으로 이동 후 T 연산 한 결과물은 카메라 공간상의 결과물이 됩니다. 이를 다시 월드로 돌리기 위해 V^-1을 뒤에 곱해줍니다.&lt;br /&gt;이 결과물이 M*V*P_fp*P^-1*V^-1*V*P가 됩니다. 여기서 V^-1*V가 항등행렬이라 당황스러울 수 있지만 흐름을 따라가면 말한대로 공간상의 정의를 설명하기 위한 중요 요소가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고로 T = V*P_fp*P*V^-1이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;With Scaling&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S = ScaleMatrix(s,s,s)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;T = V*P_fp*P^-1*S*V^-1&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Simplify&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;P_fp*P^-1=(f,f,1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 정리가 가능한 이유는&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;124&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCjX1h/btsQkU14WBf/7RH8K43PwkMR3deAyc5Psk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCjX1h/btsQkU14WBf/7RH8K43PwkMR3deAyc5Psk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCjX1h/btsQkU14WBf/7RH8K43PwkMR3deAyc5Psk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCjX1h%2FbtsQkU14WBf%2F7RH8K43PwkMR3deAyc5Psk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;320&quot; height=&quot;124&quot; data-origin-width=&quot;320&quot; data-origin-height=&quot;124&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝션 행렬의 경우 위와 같은 행렬을 갖고 (Theta는 FOV) 이는 FOv에 따라 x,y 축으로 확대 축소가 이뤄진다는 뜻 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 P와 P_fp의 차이는 FOV의 값 차이 뿐이다. 이에 따라 P_fp에 P 역행렬을 곱하면 기저&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;f = tan(FOV/2) / tan(FOV_fp/2)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Final Transform&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;T = V*ScaleMatrix(f*s, f*s, f*s)*V^-1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정에 의한 Final Transform을 구성하는 로직은 SceneView.h, FViewMatrices::Init 내 FirstPersonTransform 연산부에서 확인 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구성된 FirstPersonTransform은&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FViewInfo::TUniquePtr&amp;lt;FViewUniformShaderParameters&amp;gt; CachedViewUniformShaderParameters&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 URV 구조체에 아래와 같이 등록되며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#define VIEW_UNIFORM_BUFFER_MEMBER_TABLE&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VIEW_UNIFORM_BUFFER_MEMBER_PER_VIEW(FMatrix44f, FirstPersonTransform)&lt;br /&gt;VIEW_UNIFORM_BUFFER_MEMBER_PER_VIEW(FMatrix44f, PrevFirstPersonTransform)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;URV로 등록된 FisrtPersonTransform을 각종 쉐이더의 VertexPass 에서 'MaterialTemplate.ush' 에 있는 'ApplyMaterialFirstPersonTransform' 함수를 통해 WorldPositionWithWPO로 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 로직은 'BasePassVertexShader.usf' 에서 확인 가능하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Fake Legs&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 이야기했던 내용에 따르면 1인칭 메시가 그려지는 공간을 왜곡함으로써 클리핑과 조명 렌더링에 대한 영역을 해결해 나갔다는 것을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 팔이 아니라 1인칭 다른 바디 파츠에 대해선 어떻게 처리해야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같다면, 다리와 몸통 같은 경우도 공간이 왜곡되어 바닥과 일치하지 않는 장애가 생기진 않을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영상속 답변은 '생긴다'. 이에, 솔루션으로 제공한 기능이 '&lt;b&gt;World Space Interpolation'&lt;/b&gt; 마테리얼 노드다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 노드는 'MaterialTemplate.ush' 내에서 다음과 같이 3인칭 공간과 1인칭 공간 사이를 보간하여&amp;nbsp; WPO를 구성하는데 사용된다.&lt;/p&gt;
&lt;pre id=&quot;code_1758857309535&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void ApplyMaterialFirstPersonTransform(FMaterialVertexParameters Parameters, inout float3 WorldPositionWithWPO)
{
#if SUPPORT_FIRST_PERSON_RENDERING
	BRANCH
	if (IsFirstPerson(Parameters))
	{
		const float LerpAlpha = GetMaterialFirstPersonInterpolationAlpha(Parameters);
		const float3 FirstPersonPosition = TransformToFirstPerson(WorldPositionWithWPO, LerpAlpha);
		WorldPositionWithWPO = FirstPersonPosition;
	}
#endif
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 노드를 이용해 실질적인 Fake Legs를 구현한 샘플은 FPS Sample에서 다음과 같이 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1137&quot; data-origin-height=&quot;651&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qEuhT/btsQQmR7kZf/V7JMqOBhec21hGhwivD1TK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qEuhT/btsQQmR7kZf/V7JMqOBhec21hGhwivD1TK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qEuhT/btsQQmR7kZf/V7JMqOBhec21hGhwivD1TK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqEuhT%2FbtsQQmR7kZf%2FV7JMqOBhec21hGhwivD1TK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1137&quot; height=&quot;651&quot; data-origin-width=&quot;1137&quot; data-origin-height=&quot;651&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의미는 스피어 마스크에 시각적 장애가 없는 임의의 상수 Scale을 넣어 절대 월드 위치와 1인칭 공간상의 절대 위치 차를 보간 알파값으로 등록함으로써 시각 아티펙트를 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Casting Scene Shadows&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오로지 그림자만 캐스팅하고 프리미티브는 숨긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1인칭 그림자는 별도 쉐도우 맵을 그리고 씬과 융화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련한 로직은 'SceneRendering.h'와 'ShadowSetup.cpp', 'ShadowSceneRenderer.cpp' 를 참고하면 도움이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ShadowSetup에서 FSortedShadowMap 내 VirtualShadowMapShadows를 1인칭 여부 확인 후 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ShadowSceneRenderer에서 1인칭 그림자인지 확인 후&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;HWRT Reflections&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실무 세팅 및 적용&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[업무에 쫓겨 이후 작성 예정]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발/UE5</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/160</guid>
      <comments>https://develop-4-art.tistory.com/160#entry160comment</comments>
      <pubDate>Wed, 3 Sep 2025 14:59:34 +0900</pubDate>
    </item>
    <item>
      <title>[UE5/Plugin] 언리얼 Mass Entity Framework와 Unity DOTS 비교, 그리고 활용법</title>
      <link>https://develop-4-art.tistory.com/159</link>
      <description>&lt;h2 data-end=&quot;227&quot; data-start=&quot;201&quot; data-ke-size=&quot;size26&quot;&gt;1. Unity ECS와 DOTS 개요&lt;/h2&gt;
&lt;h3 data-end=&quot;263&quot; data-start=&quot;229&quot; data-ke-size=&quot;size23&quot;&gt;ECS(Entity Component System)&lt;/h3&gt;
&lt;p data-end=&quot;330&quot; data-start=&quot;264&quot; data-ke-size=&quot;size16&quot;&gt;Unity가 도입한 ECS는 &amp;ldquo;데이터 지향적 설계&amp;rdquo;를 통해 기존 객체 지향(OOP)의 한계를 극복하려는 접근입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;563&quot; data-start=&quot;332&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;375&quot; data-start=&quot;332&quot;&gt;&lt;b&gt;Entity&lt;/b&gt;: 단순히 ID 값. 그 자체로는 아무 기능도 없음.&lt;/li&gt;
&lt;li data-end=&quot;452&quot; data-start=&quot;376&quot;&gt;&lt;b&gt;Component&lt;/b&gt;: Entity에 부착되는 순수 데이터. 예: Position, Velocity, Health.&lt;/li&gt;
&lt;li data-end=&quot;563&quot; data-start=&quot;453&quot;&gt;&lt;b&gt;System&lt;/b&gt;: 특정 컴포넌트 조합을 가진 엔티티를 찾아 로직을 실행. 예: MovementSystem은 Position과 Velocity를 가진 엔티티에 대해 위치를 갱신.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;588&quot; data-start=&quot;565&quot; data-ke-size=&quot;size23&quot;&gt;Hot / Cold 데이터 분리&lt;/h3&gt;
&lt;p data-end=&quot;677&quot; data-start=&quot;589&quot; data-ke-size=&quot;size16&quot;&gt;CPU 성능은 캐시 히트율에 크게 좌우됩니다. ECS는 &lt;b&gt;자주 갱신되는 Hot 데이터와 가끔 접근되는 Cold 데이터를 분리&lt;/b&gt;해 캐시 효율을 높입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;781&quot; data-start=&quot;679&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;734&quot; data-start=&quot;679&quot;&gt;Hot: Position, Rotation, Velocity &amp;rarr; 매 프레임 사용.&lt;/li&gt;
&lt;li data-end=&quot;781&quot; data-start=&quot;735&quot;&gt;Cold: WeaponType, AIConfig &amp;rarr; 조건부로만 접근.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;864&quot; data-start=&quot;783&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 하면 불필요한 데이터 로딩 없이 필요한 데이터만 연속적으로 메모리에 올릴 수 있어, &lt;b&gt;SIMD 및 멀티스레딩 효율&lt;/b&gt;이 극대화됩니다.&lt;/p&gt;
&lt;h3 data-end=&quot;903&quot; data-start=&quot;866&quot; data-ke-size=&quot;size23&quot;&gt;DOTS (Data-Oriented Tech Stack)&lt;/h3&gt;
&lt;p data-end=&quot;974&quot; data-start=&quot;904&quot; data-ke-size=&quot;size16&quot;&gt;Unity의 DOTS는 ECS에 &lt;b&gt;Jobs System&lt;/b&gt;과 &lt;b&gt;Burst Compiler&lt;/b&gt;를 결합한 기술 스택입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1069&quot; data-start=&quot;975&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;994&quot; data-start=&quot;975&quot;&gt;Jobs: 멀티스레딩 지원.&lt;/li&gt;
&lt;li data-end=&quot;1040&quot; data-start=&quot;995&quot;&gt;Burst: CPU 아키텍처에 맞춰 SIMD 최적화된 네이티브 코드 생성.&lt;/li&gt;
&lt;li data-end=&quot;1069&quot; data-start=&quot;1041&quot;&gt;ECS: 데이터 지향적 구조로 확장성 확보.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-end=&quot;1144&quot; data-start=&quot;1071&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1144&quot; data-start=&quot;1073&quot; data-ke-size=&quot;size16&quot;&gt;정리하자면, DOTS는 대규모 데이터를 CPU에서 효율적으로 돌리기 위한 &lt;b&gt;Unity만의 병렬화&amp;middot;최적화 풀 스택&lt;/b&gt;입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1195&quot; data-start=&quot;1151&quot; data-ke-size=&quot;size26&quot;&gt;2. Unreal Mass Entity Framework: 개념과 명칭&lt;/h2&gt;
&lt;p data-end=&quot;1341&quot; data-start=&quot;1197&quot; data-ke-size=&quot;size16&quot;&gt;언리얼 엔진도 ECS와 같은 데이터 지향적 모델을 필요로 했습니다. 하지만 이미 &lt;b&gt;Actor/Component 체계&lt;/b&gt;가 뿌리 깊게 자리잡고 있었기 때문에 혼란을 피하기 위해 &lt;b&gt;Mass Entity Framework&lt;/b&gt;라는 독자적인 이름을 사용합니다.&lt;/p&gt;
&lt;h3 data-end=&quot;1354&quot; data-start=&quot;1343&quot; data-ke-size=&quot;size23&quot;&gt;핵심 개념&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1624&quot; data-start=&quot;1355&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1409&quot; data-start=&quot;1355&quot;&gt;&lt;b&gt;Fragment&lt;/b&gt;: Unity ECS의 Component와 유사. 순수 데이터 단위.&lt;/li&gt;
&lt;li data-end=&quot;1452&quot; data-start=&quot;1410&quot;&gt;&lt;b&gt;Tag&lt;/b&gt;: 특정 속성을 나타내는 경량 마커. 조건 분기에 사용.&lt;/li&gt;
&lt;li data-end=&quot;1497&quot; data-start=&quot;1453&quot;&gt;&lt;b&gt;Processor&lt;/b&gt;: Fragment 조합에 로직을 실행하는 단위.&lt;/li&gt;
&lt;li data-end=&quot;1624&quot; data-start=&quot;1498&quot;&gt;&lt;b&gt;Trait&lt;/b&gt;: Fragment, Tag, Processor를 패키징한 개념.
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1624&quot; data-start=&quot;1550&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1624&quot; data-start=&quot;1550&quot;&gt;예: CharacterTrait을 붙이면 이동, 렌더링, 입력 등 관련 Fragment와 Processor가 자동 추가됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1697&quot; data-start=&quot;1626&quot; data-ke-size=&quot;size16&quot;&gt;이러한 구조 덕분에, Mass는 &lt;b&gt;프로그래머뿐 아니라 디자이너도 Trait 기반으로 쉽게 엔티티를 구성&lt;/b&gt;할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-end=&quot;1726&quot; data-start=&quot;1704&quot; data-ke-size=&quot;size26&quot;&gt;3. Movement 구현 예시&lt;/h2&gt;
&lt;p data-end=&quot;1763&quot; data-start=&quot;1728&quot; data-ke-size=&quot;size16&quot;&gt;실제 Movement를 처리하는 과정을 예로 들어보겠습니다.&lt;/p&gt;
&lt;h3 data-end=&quot;1780&quot; data-start=&quot;1765&quot; data-ke-size=&quot;size23&quot;&gt;Fragment 정의&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1756818298676&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct FTransformFragment : public FMassFragment
{
    FTransform Transform;
};

struct FVelocityFragment : public FMassFragment
{
    FVector Velocity;
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-end=&quot;1965&quot; data-start=&quot;1949&quot; data-ke-size=&quot;size23&quot;&gt;Processor 구현&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1756818311555&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UCLASS()
class UMassMovementProcessor : public UMassProcessor
{
    GENERATED_BODY()

public:
    UMassMovementProcessor()
    {
        ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Movement;
    }

protected:
    virtual void Execute(FMassEntityManager&amp;amp; EntityManager, FMassExecutionContext&amp;amp; Context) override
    {
        auto TransformList = Context.GetMutableFragmentView&amp;lt;FTransformFragment&amp;gt;();
        auto VelocityList  = Context.GetFragmentView&amp;lt;FVelocityFragment&amp;gt;();

        for (int32 i = 0; i &amp;lt; Context.GetNumEntities(); i++)
        {
            TransformList[i].Transform.AddToTranslation(
                VelocityList[i].Velocity * Context.GetDeltaTimeSeconds()
            );
        }
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot; data-start=&quot;2707&quot; data-end=&quot;2717&quot;&gt;SIMD 적용 예시&lt;/h3&gt;
&lt;pre id=&quot;code_1756818955726&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;immintrin.h&amp;gt; // AVX Intrinsics

UCLASS()
class UMassSIMDGravityProcessor : public UMassProcessor
{
    GENERATED_BODY()

public:
    UMassSIMDGravityProcessor()
    {
        ExecutionOrder.ExecuteInGroup = UE::Mass::ProcessorGroupNames::Movement;
    }

protected:
    virtual void Execute(FMassEntityManager&amp;amp; EntityManager, FMassExecutionContext&amp;amp; Context) override
    {
        auto Velocities = Context.GetMutableFragmentView&amp;lt;FVelocityFragment&amp;gt;();
        const int32 NumEntities = Context.GetNumEntities();

        const float DeltaTime = Context.GetDeltaTimeSeconds();
        __m128 Gravity = _mm_set_ps(0.f, 0.f, -980.f * DeltaTime, 0.f); // [X, Y, Z, Padding]

        for (int32 i = 0; i &amp;lt; NumEntities; i += 4) // 4개 단위로 SIMD 처리
        {
            // Velocities[i] ~ Velocities[i+3]를 로드
            __m128 vx = _mm_loadu_ps(&amp;amp;Velocities[i + 0].Velocity.X);
            __m128 vy = _mm_loadu_ps(&amp;amp;Velocities[i + 1].Velocity.X);
            __m128 vz = _mm_loadu_ps(&amp;amp;Velocities[i + 2].Velocity.X);
            __m128 vw = _mm_loadu_ps(&amp;amp;Velocities[i + 3].Velocity.X);

            // Z 축에만 중력 적용 (X, Y는 그대로)
            vx = _mm_add_ps(vx, Gravity);
            vy = _mm_add_ps(vy, Gravity);
            vz = _mm_add_ps(vz, Gravity);
            vw = _mm_add_ps(vw, Gravity);

            // 다시 저장
            _mm_storeu_ps(&amp;amp;Velocities[i + 0].Velocity.X, vx);
            _mm_storeu_ps(&amp;amp;Velocities[i + 1].Velocity.X, vy);
            _mm_storeu_ps(&amp;amp;Velocities[i + 2].Velocity.X, vz);
            _mm_storeu_ps(&amp;amp;Velocities[i + 3].Velocity.X, vw);
        }
    }
};&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-end=&quot;2717&quot; data-start=&quot;2707&quot; data-ke-size=&quot;size23&quot;&gt;ISPC 적용 예시 (더 간단한 SIMD 활용법)&lt;/h3&gt;
&lt;pre id=&quot;code_1756818980385&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Gravity.ispc
export void ApplyGravity(uniform float3* velocities, uniform int N, uniform float dt)
{
    foreach (i = 0 ... N)
    {
        velocities[i].z += -980.0f * dt; // SIMD 병렬화 자동 적용
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-end=&quot;2717&quot; data-start=&quot;2707&quot; data-ke-size=&quot;size18&quot;&gt;C++ Processor에서는 ISPC 함수를 호출하기만 하면 됩니다:&lt;/p&gt;
&lt;pre id=&quot;code_1756819025858&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extern void ApplyGravity_ispc(FVector* Velocities, int N, float dt);

virtual void Execute(FMassEntityManager&amp;amp; EntityManager, FMassExecutionContext&amp;amp; Context) override
{
    auto Velocities = Context.GetMutableFragmentView&amp;lt;FVelocityFragment&amp;gt;();
    ApplyGravity_ispc(&amp;amp;Velocities[0].Velocity, Context.GetNumEntities(), Context.GetDeltaTimeSeconds());
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-end=&quot;2717&quot; data-start=&quot;2707&quot; data-ke-size=&quot;size23&quot;&gt;실무 팁&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2891&quot; data-start=&quot;2718&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2781&quot; data-start=&quot;2718&quot;&gt;GetMutableFragmentView를 사용하면 성능 오버헤드 없이 대규모 연속 데이터 접근 가능.&lt;/li&gt;
&lt;li data-end=&quot;2835&quot; data-start=&quot;2782&quot;&gt;Processor는 &lt;b&gt;Chunk 단위 병렬 실행&lt;/b&gt;되므로, SIMD 최적화 효과가 큼.&lt;/li&gt;
&lt;li data-end=&quot;2891&quot; data-start=&quot;2836&quot;&gt;필요시 ParallelForEachEntityChunk를 이용해 코어 활용 극대화 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;2896&quot; data-start=&quot;2893&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2925&quot; data-start=&quot;2898&quot; data-ke-size=&quot;size26&quot;&gt;4. Fragment 접근 및 수정 방식&lt;/h2&gt;
&lt;h3 data-end=&quot;2942&quot; data-start=&quot;2927&quot; data-ke-size=&quot;size23&quot;&gt;SharedPtr&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3066&quot; data-start=&quot;2943&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2970&quot; data-start=&quot;2943&quot;&gt;&lt;b&gt;장점&lt;/b&gt;: 데이터 소유권 관리에 유리.&lt;/li&gt;
&lt;li data-end=&quot;3013&quot; data-start=&quot;2971&quot;&gt;&lt;b&gt;단점&lt;/b&gt;: 참조 카운트 연산 비용 때문에 루프 내부에서는 비효율.&lt;/li&gt;
&lt;li data-end=&quot;3066&quot; data-start=&quot;3014&quot;&gt;&lt;b&gt;사용 예시&lt;/b&gt;: 외부 모듈과 데이터 공유, 긴 생명주기를 가진 대형 오브젝트 관리.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;3100&quot; data-start=&quot;3068&quot; data-ke-size=&quot;size23&quot;&gt;ArrayView / ConstArrayView&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3199&quot; data-start=&quot;3101&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3130&quot; data-start=&quot;3101&quot;&gt;&lt;b&gt;장점&lt;/b&gt;: 오버헤드가 거의 없는 뷰 타입.&lt;/li&gt;
&lt;li data-end=&quot;3150&quot; data-start=&quot;3131&quot;&gt;&lt;b&gt;단점&lt;/b&gt;: 소유권 없음.&lt;/li&gt;
&lt;li data-end=&quot;3199&quot; data-start=&quot;3151&quot;&gt;&lt;b&gt;사용 예시&lt;/b&gt;: Processor 내부에서 대규모 시뮬레이션 처리 시 권장.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3257&quot; data-start=&quot;3201&quot; data-ke-size=&quot;size16&quot;&gt;결론: &lt;b&gt;성능 중심 로직 = ArrayView, 메모리 관리 필요 = SharedPtr&lt;/b&gt;&lt;/p&gt;
&lt;hr data-end=&quot;3262&quot; data-start=&quot;3259&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;3298&quot; data-start=&quot;3264&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;3298&quot; data-start=&quot;3264&quot; data-ke-size=&quot;size26&quot;&gt;5. Mass Entity Framework 플러그인&lt;/h2&gt;
&lt;p data-end=&quot;3337&quot; data-start=&quot;3300&quot; data-ke-size=&quot;size16&quot;&gt;Mass는 다양한 플러그인과 결합해 기능을 확장할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3631&quot; data-start=&quot;3339&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3386&quot; data-start=&quot;3339&quot;&gt;&lt;b&gt;MassGameplay&lt;/b&gt;: 캐릭터, NPC, 일반적인 게임 플레이 지원.&lt;/li&gt;
&lt;li data-end=&quot;3424&quot; data-start=&quot;3387&quot;&gt;&lt;b&gt;MassCrowd&lt;/b&gt;: 수천 명 이상의 군중 시뮬레이션.&lt;/li&gt;
&lt;li data-end=&quot;3466&quot; data-start=&quot;3425&quot;&gt;&lt;b&gt;MassTraffic&lt;/b&gt;: 차량, 교통 네트워크, 신호등 제어.&lt;/li&gt;
&lt;li data-end=&quot;3499&quot; data-start=&quot;3467&quot;&gt;&lt;b&gt;MassLOD&lt;/b&gt;: 엔티티 단위 LOD 최적화.&lt;/li&gt;
&lt;li data-end=&quot;3537&quot; data-start=&quot;3500&quot;&gt;&lt;b&gt;MassReplication&lt;/b&gt;: 네트워크 복제 최적화.&lt;/li&gt;
&lt;li data-end=&quot;3587&quot; data-start=&quot;3538&quot;&gt;&lt;b&gt;MassSmartObjects&lt;/b&gt;: NPC와 상호작용 가능한 오브젝트 시스템.&lt;/li&gt;
&lt;li data-end=&quot;3631&quot; data-start=&quot;3588&quot;&gt;&lt;b&gt;MassDebug/Visualizer&lt;/b&gt;: 시각화 및 디버깅 도구.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3690&quot; data-start=&quot;3633&quot; data-ke-size=&quot;size16&quot;&gt;이 플러그인들을 조합하면 단순한 시뮬레이션을 넘어 &lt;b&gt;도시 전체 생태계&lt;/b&gt;까지 구성할 수 있습니다.&lt;/p&gt;
&lt;hr data-end=&quot;3695&quot; data-start=&quot;3692&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;3720&quot; data-start=&quot;3697&quot; data-ke-size=&quot;size26&quot;&gt;6. Mass가 적합한 게임 장르&lt;/h2&gt;
&lt;p data-end=&quot;3758&quot; data-start=&quot;3722&quot; data-ke-size=&quot;size16&quot;&gt;Mass는 특히 &lt;b&gt;대규모 단순 객체 처리&lt;/b&gt;에서 강력합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3914&quot; data-start=&quot;3760&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3796&quot; data-start=&quot;3760&quot;&gt;&lt;b&gt;RTS/전략 게임&lt;/b&gt;: 수백~수천 개 유닛 동시 제어.&lt;/li&gt;
&lt;li data-end=&quot;3837&quot; data-start=&quot;3797&quot;&gt;&lt;b&gt;군중/생태계 시뮬레이션&lt;/b&gt;: 도시 인구, 동물 무리, 생태계.&lt;/li&gt;
&lt;li data-end=&quot;3874&quot; data-start=&quot;3838&quot;&gt;&lt;b&gt;트래픽/레이싱 게임&lt;/b&gt;: 도로 네트워크, NPC 차량.&lt;/li&gt;
&lt;li data-end=&quot;3914&quot; data-start=&quot;3875&quot;&gt;&lt;b&gt;샌드박스/서바이벌&lt;/b&gt;: 월드 내 자원, 몬스터, 동물 개체.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3963&quot; data-start=&quot;3916&quot; data-ke-size=&quot;size16&quot;&gt;실무에서는 &lt;b&gt;Actor + Mass 하이브리드 구조&lt;/b&gt;를 쓰는 경우가 많습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4029&quot; data-start=&quot;3964&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3992&quot; data-start=&quot;3964&quot;&gt;Mass: 단순하고 대량의 NPC/오브젝트.&lt;/li&gt;
&lt;li data-end=&quot;4029&quot; data-start=&quot;3993&quot;&gt;Actor: 복잡한 개체(보스 몬스터, 플레이어 캐릭터).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;4034&quot; data-start=&quot;4031&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;4059&quot; data-start=&quot;4036&quot; data-ke-size=&quot;size26&quot;&gt;7. Mass 내부 구조 이해하기&lt;/h2&gt;
&lt;p data-end=&quot;4106&quot; data-start=&quot;4061&quot; data-ke-size=&quot;size16&quot;&gt;Mass의 성능 핵심은 &lt;b&gt;Archetype-Chunk 메모리 구조&lt;/b&gt;입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4318&quot; data-start=&quot;4108&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4156&quot; data-start=&quot;4108&quot;&gt;&lt;b&gt;Archetype&lt;/b&gt;: 특정 Fragment 조합을 가진 Entity 그룹.&lt;/li&gt;
&lt;li data-end=&quot;4202&quot; data-start=&quot;4157&quot;&gt;&lt;b&gt;Chunk&lt;/b&gt;: 동일 Archetype 엔티티들을 묶어 저장하는 단위.&lt;/li&gt;
&lt;li data-end=&quot;4265&quot; data-start=&quot;4203&quot;&gt;Chunk는 &lt;b&gt;Struct-of-Arrays(SoA)&lt;/b&gt; 형식으로 데이터를 저장해 캐시 효율이 뛰어남.&lt;/li&gt;
&lt;li data-end=&quot;4318&quot; data-start=&quot;4266&quot;&gt;Processor는 Query를 통해 대상 Chunk를 찾아내고, 이를 한 번에 처리.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4390&quot; data-start=&quot;4320&quot; data-ke-size=&quot;size16&quot;&gt;즉, CPU는 연속 메모리에 정렬된 데이터만 읽으므로 &lt;b&gt;캐시 미스가 최소화&lt;/b&gt;되고, &lt;b&gt;SIMD 활용률&lt;/b&gt;이 높아집니다.&lt;/p&gt;
&lt;hr data-end=&quot;4395&quot; data-start=&quot;4392&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;4405&quot; data-start=&quot;4397&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-end=&quot;4473&quot; data-start=&quot;4407&quot; data-ke-size=&quot;size16&quot;&gt;Unity DOTS와 Unreal Mass는 모두 데이터 지향적 패러다임을 기반으로 하지만, 구현 철학이 다릅니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4584&quot; data-start=&quot;4474&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4522&quot; data-start=&quot;4474&quot;&gt;Unity: ECS를 &amp;ldquo;게임 개발의 기본 모델&amp;rdquo;로 삼고 독립적 기술 스택 구축.&lt;/li&gt;
&lt;li data-end=&quot;4584&quot; data-start=&quot;4523&quot;&gt;Unreal: 기존 Actor/Component 시스템과 공존 가능한 별도 프레임워크로 Mass 개발.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;4607&quot; data-start=&quot;4586&quot; data-ke-size=&quot;size16&quot;&gt;Mass의 강점은 다음과 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;4688&quot; data-start=&quot;4608&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;4631&quot; data-start=&quot;4608&quot;&gt;Trait 기반으로 손쉬운 구성.&lt;/li&gt;
&lt;li data-end=&quot;4665&quot; data-start=&quot;4632&quot;&gt;대규모 시뮬레이션에 적합한 고성능 Chunk 구조.&lt;/li&gt;
&lt;li data-end=&quot;4688&quot; data-start=&quot;4666&quot;&gt;플러그인 생태계를 통한 확장성.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;4784&quot; data-start=&quot;4690&quot; data-ke-size=&quot;size16&quot;&gt;앞으로 대규모 NPC, 군중, 트래픽, 생태계 시뮬레이션을 구현하고 싶다면, Mass Entity Framework는 반드시 고려해볼 가치가 있는 강력한 도구입니다.&lt;/p&gt;
&lt;p data-end=&quot;4784&quot; data-start=&quot;4690&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot; data-start=&quot;4397&quot; data-end=&quot;4405&quot;&gt;참고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/Megafunk/MassSample/blob/main/README.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/Megafunk/MassSample/blob/main/README.md&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1756819554540&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;MassSample/README.md at main &amp;middot; Megafunk/MassSample&quot; data-og-description=&quot;My understanding of Unreal Engine 5's experimental ECS plugin with a small sample project. - Megafunk/MassSample&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/Megafunk/MassSample/blob/main/README.md&quot; data-og-url=&quot;https://github.com/Megafunk/MassSample/blob/main/README.md&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cXaHeE/hyZGW3Ttrj/gL2qQRPy6GtRmDk2XOXbp0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/01GV4/hyZG2wgRQu/OFKom46HPKl7BlrXQdG8JK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/Megafunk/MassSample/blob/main/README.md&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/Megafunk/MassSample/blob/main/README.md&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cXaHeE/hyZGW3Ttrj/gL2qQRPy6GtRmDk2XOXbp0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600,https://scrap.kakaocdn.net/dn/01GV4/hyZG2wgRQu/OFKom46HPKl7BlrXQdG8JK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;MassSample/README.md at main &amp;middot; Megafunk/MassSample&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;My understanding of Unreal Engine 5's experimental ECS plugin with a small sample project. - Megafunk/MassSample&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://contents.premium.naver.com/unrealstudy/unreal/contents/250524153826875lb&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://contents.premium.naver.com/unrealstudy/unreal/contents/250524153826875lb&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발/UE5</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/159</guid>
      <comments>https://develop-4-art.tistory.com/159#entry159comment</comments>
      <pubDate>Tue, 2 Sep 2025 22:06:59 +0900</pubDate>
    </item>
    <item>
      <title>DX12 기반 복셀엔진 개발 마일스톤</title>
      <link>https://develop-4-art.tistory.com/158</link>
      <description>&lt;h1&gt;DirectX 12 기반 복셀 렌더링 엔진 &amp;ndash; 전체 마일스톤 계획&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;M0 &amp;ndash; 최소 실행 엔진 (Minimal Engine Bring-Up)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 실행 가능한 최소 엔진 뼈대 구축&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Win32 창 생성 및 이벤트 루프&lt;/li&gt;
&lt;li&gt;Core / Engine 모듈 초기화&lt;/li&gt;
&lt;li&gt;DX12 Agility SDK + DXC 설정&lt;/li&gt;
&lt;li&gt;D3D12 RHI 초기화 (Device, Queue, SwapChain, RTV, Fence)&lt;/li&gt;
&lt;li&gt;화면 단색 Clear &amp;amp; Present&lt;/li&gt;
&lt;li&gt;Debug/Release 빌드 환경 안정화&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;M1 &amp;ndash; 타일 단일 시뮬레이션 &amp;amp; 디버그&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 1개 타일에서 토사(Cellular Automata) 및 유체(Heightfield) 기본 시뮬레이션 구현&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RHI UAV/SRV 리소스 핸들링&lt;/li&gt;
&lt;li&gt;Compute Shader로 토사 CA 1스텝 처리 (ping-pong 버퍼)&lt;/li&gt;
&lt;li&gt;얕은수 기반 유체 시뮬레이션 초기 버전&lt;/li&gt;
&lt;li&gt;Slice 뷰 디버그 렌더링&lt;/li&gt;
&lt;li&gt;시뮬 파라미터(중력, 스텝 수 등) 조절 UI&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;M2 &amp;ndash; 렌더링 통합 &amp;amp; 메싱&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 시뮬레이션 데이터를 3D 메시로 변환해 렌더링&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RenderGraph 최소 버전 도입 (패스 의존성/리소스 관리)&lt;/li&gt;
&lt;li&gt;Marching Cubes로 토사/지면 메싱&lt;/li&gt;
&lt;li&gt;단색 머티리얼 기본 셰이딩&lt;/li&gt;
&lt;li&gt;프러스텀 컬링 적용&lt;/li&gt;
&lt;li&gt;와이어프레임 / 노멀 디버그 뷰&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;M3 &amp;ndash; 멀티 타일 &amp;amp; 경계 교환&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 여러 타일이 연결된 월드 시뮬레이션&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;타일 경계(ghost cell) 버퍼 설계 및 교환&lt;/li&gt;
&lt;li&gt;멀티 타일 동시 시뮬 안정화&lt;/li&gt;
&lt;li&gt;타일 LOD 및 스트리밍 초석&lt;/li&gt;
&lt;li&gt;시뮬레이션 성능 최적화 (타일 활성/비활성 관리)&lt;/li&gt;
&lt;li&gt;프로파일러 도입 (CPU/GPU 타이밍)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;M4 &amp;ndash; 토사 &amp;harr; 유체 상호작용 &amp;amp; 고도화&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 토사와 유체의 상호작용 및 시각적 개선&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;침식/퇴적 로직&lt;/li&gt;
&lt;li&gt;유체 입자 표현(PBD/MPM) 또는 고급 solver 적용&lt;/li&gt;
&lt;li&gt;물 표면 메싱 및 반사/굴절 렌더링&lt;/li&gt;
&lt;li&gt;Async Compute 병렬화&lt;/li&gt;
&lt;li&gt;고급 조명(간단한 GI 또는 SSAO) 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;M5 &amp;ndash; 에디터/툴 확장&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 개발 생산성 향상을 위한 디버그/에디터 기능&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ImGui 기반 월드/타일 편집&lt;/li&gt;
&lt;li&gt;시뮬레이션 파라미터 실시간 조정&lt;/li&gt;
&lt;li&gt;RenderGraph 시각화&lt;/li&gt;
&lt;li&gt;셰이더 핫리로드&lt;/li&gt;
&lt;li&gt;캡처/리플레이 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;M6 &amp;ndash; 안정화 및 배포&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt; 엔진 안정성 확보 및 초기 배포 준비&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스/메모리 누수 점검&lt;/li&gt;
&lt;li&gt;모듈별 코드 정리 및 문서화&lt;/li&gt;
&lt;li&gt;빌드 스크립트 자동화&lt;/li&gt;
&lt;li&gt;샘플 콘텐츠 제작&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발/VxEngine</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/158</guid>
      <comments>https://develop-4-art.tistory.com/158#entry158comment</comments>
      <pubDate>Sun, 10 Aug 2025 18:55:33 +0900</pubDate>
    </item>
    <item>
      <title>250810 - DirectX 12 기반 엔진 뼈대 만들기 &amp;ndash; M0 단계 기록</title>
      <link>https://develop-4-art.tistory.com/157</link>
      <description>&lt;h2 data-sourcepos=&quot;3:1-3:22&quot; data-ke-size=&quot;size26&quot;&gt;1. 오늘의 목표&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;5:1-11:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;5:1-5:93&quot;&gt;언리얼 엔진 구조를 참고하여 DirectX 12 기반의 복셀 렌더링 엔진 준비&lt;/li&gt;
&lt;li data-sourcepos=&quot;6:1-11:0&quot;&gt;&lt;b&gt;M0 마일스톤&lt;/b&gt;: &amp;ldquo;주행 가능한 최소 엔진&amp;rdquo; 완성
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;7:3-11:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;7:3-7:20&quot;&gt;Win32 창 생성&lt;/li&gt;
&lt;li data-sourcepos=&quot;8:3-8:41&quot;&gt;Core / Engine 모듈 초기화 루프&lt;/li&gt;
&lt;li data-sourcepos=&quot;9:3-9:33&quot;&gt;DX12 Agility SDK + DXC 설정&lt;/li&gt;
&lt;li data-sourcepos=&quot;10:3-11:0&quot;&gt;RHI(D3D12) 초기화 &amp;rarr; 화면 클리어 &amp;amp; Present&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-sourcepos=&quot;12:1-12:3&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;14:1-14:35&quot; data-ke-size=&quot;size26&quot;&gt;2. 작업 순서 &amp;amp; 진행 내용&lt;/h2&gt;
&lt;h3 data-sourcepos=&quot;16:1-16:34&quot; data-ke-size=&quot;size23&quot;&gt;(1) 프로젝트 구조 준비&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;18:1-21:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;18:1-18:35&quot;&gt;VS2022 기반 프로젝트 생성&lt;/li&gt;
&lt;li data-sourcepos=&quot;19:1-19:52&quot;&gt;모듈 설계: Core, Engine, RHI, D3D12RHI&lt;/li&gt;
&lt;li data-sourcepos=&quot;20:1-21:0&quot;&gt;AppMain.cpp에서 WinMain 엔트리 작성, ESC 종료 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;pre id=&quot;code-77&quot; class=&quot;reasonml&quot; data-sourcepos=&quot;22:1-28:3&quot; data-canonical-lang=&quot;cpp&quot;&gt;&lt;code&gt;LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    if (msg == WM_DESTROY) { PostQuitMessage(0); return 0; }
    if (msg == WM_KEYDOWN &amp;amp;&amp;amp; wParam == VK_ESCAPE) DestroyWindow(hWnd);
    return DefWindowProc(hWnd, msg, wParam, lParam);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h3 data-sourcepos=&quot;30:1-30:32&quot; data-ke-size=&quot;size23&quot;&gt;(2) Agility SDK &amp;amp; DXC 설정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;32:1-36:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;32:1-34:46&quot;&gt;NuGet 패키지 설치
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;33:3-34:46&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;33:3-33:42&quot;&gt;Microsoft.Direct3D.D3D12 (Agility SDK)&lt;/li&gt;
&lt;li data-sourcepos=&quot;34:3-34:46&quot;&gt;Microsoft.Direct3D.DXC (HLSL 컴파일러)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-sourcepos=&quot;35:1-36:0&quot;&gt;Agility SDK 로딩 설정 (AppMainAgilityExports.cpp)&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;pre id=&quot;code-78&quot; class=&quot;cpp&quot; data-sourcepos=&quot;37:1-42:3&quot; data-canonical-lang=&quot;cpp&quot;&gt;&lt;code&gt;extern &quot;C&quot; {
__declspec(dllexport) extern const unsigned int D3D12SDKVersion = 616;
__declspec(dllexport) extern const char*       D3D12SDKPath    = u8&quot;.\\&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;44:1-45:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;44:1-45:0&quot;&gt;Post-Build 이벤트로 DLL 복사 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-sourcepos=&quot;46:1-46:35&quot; data-ke-size=&quot;size23&quot;&gt;(3) 빌드 에러 해결 과정&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;48:1-53:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;48:1-48:116&quot;&gt;&lt;b&gt;#include 경로 문제&lt;/b&gt; &amp;rarr; Additional Include Directories에 $(ProjectDir)Core; $(ProjectDir)Engine 추가&lt;/li&gt;
&lt;li data-sourcepos=&quot;49:1-49:91&quot;&gt;&lt;b&gt;WinMain 링크 오류&lt;/b&gt; &amp;rarr; 서브시스템을 Windows (/SUBSYSTEM:WINDOWS)로 변경&lt;/li&gt;
&lt;li data-sourcepos=&quot;50:1-50:76&quot;&gt;&lt;b&gt;SAFE_HR 매크로 오류&lt;/b&gt; &amp;rarr; SAFE_HR_V(void 전용) 매크로 추가&lt;/li&gt;
&lt;li data-sourcepos=&quot;51:1-51:82&quot;&gt;CD3DX12_ 타입 인식 안됨* &amp;rarr; 최신 d3dx12.h 추가 및 인클루드&lt;/li&gt;
&lt;li data-sourcepos=&quot;52:1-53:0&quot;&gt;&lt;b&gt;D3D12/DXGI unresolved externals&lt;/b&gt; &amp;rarr; #pragma comment(lib, &quot;d3d12.lib&quot;) 등 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-sourcepos=&quot;54:1-54:29&quot; data-ke-size=&quot;size23&quot;&gt;(4) RHI &amp;amp; D3D12 초기화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;56:1-59:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;56:1-56:36&quot;&gt;RHI 인터페이스 설계: IRHI&lt;/li&gt;
&lt;li data-sourcepos=&quot;57:1-57:63&quot;&gt;FD3D12RHI 구현: Device, Queue, SwapChain, RTV Heap, Fence&lt;/li&gt;
&lt;li data-sourcepos=&quot;58:1-59:0&quot;&gt;BeginFrame &amp;rarr; RenderTarget Clear &amp;rarr; EndFrame &amp;rarr; Present&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;pre id=&quot;code-79&quot; class=&quot;angelscript&quot; data-sourcepos=&quot;60:1-64:3&quot; data-canonical-lang=&quot;cpp&quot;&gt;&lt;code&gt;float clear[4] = {0.07f, 0.07f, 0.12f, 1.0f};
mCmd-&amp;gt;OMSetRenderTargets(1, &amp;amp;rtv, FALSE, nullptr);
mCmd-&amp;gt;ClearRenderTargetView(rtv, clear, 0, nullptr);&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;hr data-sourcepos=&quot;66:1-66:3&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-sourcepos=&quot;68:1-68:37&quot; data-ke-size=&quot;size23&quot;&gt;(5) 링크 라이브러리 역할&lt;/h3&gt;
&lt;p&gt;라이브러리 역할 실행 시 로드되는 DLL 예시 심볼&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;70:1-74:83&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr data-sourcepos=&quot;72:1-72:91&quot;&gt;
&lt;td data-sourcepos=&quot;72:2-72:14&quot;&gt;d3d12.lib&lt;/td&gt;
&lt;td data-sourcepos=&quot;72:16-72:40&quot;&gt;D3D12 API 함수 연결&lt;/td&gt;
&lt;td data-sourcepos=&quot;72:42-72:68&quot;&gt;d3d12.dll / d3d12core.dll&lt;/td&gt;
&lt;td data-sourcepos=&quot;72:70-72:90&quot;&gt;D3D12CreateDevice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;73:1-73:73&quot;&gt;
&lt;td data-sourcepos=&quot;73:2-73:13&quot;&gt;dxgi.lib&lt;/td&gt;
&lt;td data-sourcepos=&quot;73:15-73:38&quot;&gt;DXGI API 함수 연결&lt;/td&gt;
&lt;td data-sourcepos=&quot;73:40-73:49&quot;&gt;dxgi.dll&lt;/td&gt;
&lt;td data-sourcepos=&quot;73:51-73:72&quot;&gt;CreateDXGIFactory2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;74:1-74:83&quot;&gt;
&lt;td data-sourcepos=&quot;74:2-74:15&quot;&gt;dxguid.lib&lt;/td&gt;
&lt;td data-sourcepos=&quot;74:17-74:50&quot;&gt;DirectX GUID/CLSID 상수 포함&lt;/td&gt;
&lt;td data-sourcepos=&quot;74:52-74:61&quot;&gt;(없음)&lt;/td&gt;
&lt;td data-sourcepos=&quot;74:63-74:82&quot;&gt;IID_ID3D12Device&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-sourcepos=&quot;76:1-76:3&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;78:1-78:45&quot; data-ke-size=&quot;size26&quot;&gt;3. 오늘 해결한 주요 질문 &amp;amp; 답변&lt;/h2&gt;
&lt;p&gt;질문 핵심 답변&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-sourcepos=&quot;80:1-86:82&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr data-sourcepos=&quot;82:1-82:82&quot;&gt;
&lt;td data-sourcepos=&quot;82:2-82:42&quot;&gt;WinMain인데 main 못 찾는 LNK2019&lt;/td&gt;
&lt;td data-sourcepos=&quot;82:44-82:81&quot;&gt;서브시스템을 Windows로 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;83:1-83:65&quot;&gt;
&lt;td data-sourcepos=&quot;83:2-83:24&quot;&gt;include 경로 에러&lt;/td&gt;
&lt;td data-sourcepos=&quot;83:26-83:64&quot;&gt;Additional Include Directories 설정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;84:1-84:68&quot;&gt;
&lt;td data-sourcepos=&quot;84:2-84:27&quot;&gt;SAFE_HR 매크로 에러&lt;/td&gt;
&lt;td data-sourcepos=&quot;84:29-84:67&quot;&gt;void 함수 전용 SAFE_HR_V 추가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;85:1-85:64&quot;&gt;
&lt;td data-sourcepos=&quot;85:2-85:28&quot;&gt;CD3DX12_* 인식 불가&lt;/td&gt;
&lt;td data-sourcepos=&quot;85:30-85:63&quot;&gt;d3dx12.h 추가 &amp;amp; 인클루드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-sourcepos=&quot;86:1-86:82&quot;&gt;
&lt;td data-sourcepos=&quot;86:2-86:34&quot;&gt;D3D12/DXGI unresolved externals&lt;/td&gt;
&lt;td data-sourcepos=&quot;86:36-86:81&quot;&gt;d3d12.lib, dxgi.lib, dxguid.lib 링크&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-sourcepos=&quot;88:1-88:3&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;90:1-90:12&quot; data-ke-size=&quot;size26&quot;&gt;4. 결과&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;92:1-95:0&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;92:1-92:36&quot;&gt;Debug/Release 모두 빌드 성공&lt;/li&gt;
&lt;li data-sourcepos=&quot;93:1-93:60&quot;&gt;창 생성 후 배경색 클리어 &amp;amp; Present 정상 동작&lt;/li&gt;
&lt;li data-sourcepos=&quot;94:1-95:0&quot;&gt;M0 단계 완료 &amp;rarr; 다음은 &lt;b&gt;WM_SIZE 처리 + 프레임 타이머 + ImGui 디버그 UI&lt;/b&gt; 예정&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-sourcepos=&quot;96:1-96:3&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-sourcepos=&quot;98:1-98:16&quot; data-ke-size=&quot;size26&quot;&gt;5. 배운 점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-sourcepos=&quot;100:1-103:77&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-sourcepos=&quot;100:1-100:80&quot;&gt;Agility SDK와 DXC 설정은 &lt;b&gt;NuGet + EXE 심볼 export&lt;/b&gt;로 간단히 가능&lt;/li&gt;
&lt;li data-sourcepos=&quot;101:1-101:81&quot;&gt;Win32 서브시스템/링크 설정은 main/WinMain 호출 차이의 핵심&lt;/li&gt;
&lt;li data-sourcepos=&quot;102:1-102:107&quot;&gt;DirectX 라이브러리 3종(d3d12.lib, dxgi.lib, dxguid.lib)의 역할과 DLL 로딩 순서 이해&lt;/li&gt;
&lt;li data-sourcepos=&quot;103:1-103:77&quot;&gt;d3dx12.h 헬퍼는 필수 수준 (리소스 배리어, 핸들 계산 등)&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발/VxEngine</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/157</guid>
      <comments>https://develop-4-art.tistory.com/157#entry157comment</comments>
      <pubDate>Sun, 10 Aug 2025 18:53:14 +0900</pubDate>
    </item>
    <item>
      <title>[C/C++] 조건문 컴파일러 최적화</title>
      <link>https://develop-4-art.tistory.com/154</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;if문이 컴파일러가 switch로 대체할 수 있을 만큼 간단하고 내부에서 간단한 함수 콜을 한다면, 컴파일 타임에 Jump Table을 만들어 이를 기반으로 함수를 콜해주도록 최적화된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면, 아래와 같은 if else 문과 switch문이 있다고 가정하자.&lt;/p&gt;
&lt;pre id=&quot;code_1747909990831&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void dispatch(int x) {
    if (x == 0) f0();
    else if (x == 1) f1();
    else if (x == 2) f2();
    else if (x == 3) f3();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1747910007304&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// switch로 변환
switch (x) {
    case 0: f0(); break;
    case 1: f1(); break;
    case 2: f2(); break;
    case 3: f3(); break;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 컴파일러가 어샘블리로 다음과 같이 전환한다.&lt;/p&gt;
&lt;pre id=&quot;code_1747910043016&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;jmp [jump_table + eax * 4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구축된 점프 테이블과 인자를 갖고, 단순하게 함수로 점프 콜 해버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 주의해야 할 점은 인자로 enum이 들어오는 경우 switch에서 default를 사용할 수 있는데 이 때는 다소 주의를 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는, default 아래 작성되는 로직은 jump_table에 넣지 못 하기 때문인데 이런 문제로 컴파일러의 최적화 과정을 믿을 수 없다면 다음과 같은 방법을 사용하면 좋다.&lt;/p&gt;
&lt;pre id=&quot;code_1747910162460&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void (*handlers[3])() = { handleCase0, handleCase1, handleCase2 };
handlers[x](); // 조건 없이 바로 호출&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/154</guid>
      <comments>https://develop-4-art.tistory.com/154#entry154comment</comments>
      <pubDate>Thu, 22 May 2025 19:36:17 +0900</pubDate>
    </item>
    <item>
      <title>[UE5/GAS] GamePhase Ability</title>
      <link>https://develop-4-art.tistory.com/153</link>
      <description>&lt;h2&gt;GamePhaseSubsystem&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;StartPhase : GameState에서 관리하는 ASC에 어빌리티 부여&lt;/li&gt;
&lt;li&gt;OnBeginPhase : 이미 부여되어있던 GamePhaseAbility를 찾아, &amp;#39;CancelAbilitiesByFunc&amp;#39; 호출을 통해 취소&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;GamePhaseAbility&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;GamePhaseSubsystem에 의해 부여되는 어빌리티&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/153</guid>
      <comments>https://develop-4-art.tistory.com/153#entry153comment</comments>
      <pubDate>Mon, 19 May 2025 14:01:37 +0900</pubDate>
    </item>
    <item>
      <title>[OS] CPU 스케쥴링 (CPU Scheduling)</title>
      <link>https://develop-4-art.tistory.com/152</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K2IEE/btsNJwqMIgg/RjIp2oyXNcwzMMEF2XLMr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K2IEE/btsNJwqMIgg/RjIp2oyXNcwzMMEF2XLMr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K2IEE/btsNJwqMIgg/RjIp2oyXNcwzMMEF2XLMr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK2IEE%2FbtsNJwqMIgg%2FRjIp2oyXNcwzMMEF2XLMr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;352&quot; height=&quot;528&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3️⃣ CPU 스케줄링 (CPU Scheduling)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 왜 스케줄링이 필요한가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 시스템은 &lt;b&gt;멀티태스킹&lt;/b&gt; 환경에서 여러 프로세스가 동시에 실행되는 것처럼 보이지만, 실제로는 &lt;b&gt;한정된 CPU&lt;/b&gt;가 프로세스를 &lt;b&gt;순차적으로 실행&lt;/b&gt;합니다. 따라서 운영체제는 &lt;b&gt;어떤 프로세스가 언제 CPU를 차지할지 결정&lt;/b&gt;해야 합니다. 이게 바로 &lt;b&gt;스케줄링&lt;/b&gt;이에요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 스케줄링 시점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Non-preemptive (비선점):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 실행 중인 프로세스가 CPU를 자발적으로 내놓을 때만 스위칭.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Preemptive (선점형):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OS가 강제로 CPU를 회수하여 다른 프로세스에 할당 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 현대 OS는 &lt;b&gt;선점형 스케줄링&lt;/b&gt;을 사용합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 주요 스케줄링 알고리즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘 설명 특징&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FCFS (First-Come First-Served)&lt;/td&gt;
&lt;td&gt;먼저 도착한 프로세스부터 순차 실행&lt;/td&gt;
&lt;td&gt;구현 간단 / 긴 대기시간 발생 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SJF (Shortest Job First)&lt;/td&gt;
&lt;td&gt;실행 시간이 가장 짧은 프로세스를 우선 실행&lt;/td&gt;
&lt;td&gt;평균 대기시간 최소 / 실행 시간 예측 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Priority Scheduling&lt;/td&gt;
&lt;td&gt;우선순위 높은 프로세스부터 실행&lt;/td&gt;
&lt;td&gt;기아(Starvation) 문제 발생 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Round Robin (RR)&lt;/td&gt;
&lt;td&gt;고정된 시간 할당(Quantum) 후 선점하여 순환&lt;/td&gt;
&lt;td&gt;응답 시간 개선 / Context Switch 오버헤드 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multilevel Queue&lt;/td&gt;
&lt;td&gt;여러 큐로 분류(예: 상호작용/배치) 후 각 큐 별 알고리즘 적용&lt;/td&gt;
&lt;td&gt;복잡한 환경 최적화 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multilevel Feedback Queue&lt;/td&gt;
&lt;td&gt;우선순위 큐 + 동적 이동 (실행 패턴에 따라 큐 위치가 바뀜)&lt;/td&gt;
&lt;td&gt;매우 유연 / 구현 복잡&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 평가 지표&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;CPU Utilization (CPU 이용률):&lt;/b&gt; 얼마나 CPU가 바쁘게 일하는지.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Throughput (처리량):&lt;/b&gt; 단위 시간당 완료된 프로세스 수.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Turnaround Time (반환 시간):&lt;/b&gt; 프로세스 제출 &amp;rarr; 완료까지 걸린 시간.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Waiting Time (대기 시간):&lt;/b&gt; 프로세스가 Ready 상태로 기다린 총 시간.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Response Time (응답 시간):&lt;/b&gt; 프로세스 제출 &amp;rarr; 첫 응답까지의 시간.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이 지표들은 &lt;b&gt;서로 Trade-off 관계&lt;/b&gt;가 있으므로 OS의 목적에 따라 우선순위가 달라집니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  스케줄링 평가 지표 간의 &lt;b&gt;연관 공식&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 3가지가 서로 밀접하게 연결되어 있습니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;b&gt;Turnaround Time (반환 시간)&lt;/b&gt;&lt;br /&gt;2️⃣ &lt;b&gt;Waiting Time (대기 시간)&lt;/b&gt;&lt;br /&gt;3️⃣ &lt;b&gt;Burst Time (실행 시간)&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 공식 ①&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Turnaround Time (TAT)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Turnaround&amp;nbsp;Time=Completion&amp;nbsp;Time&amp;minus;Arrival&amp;nbsp;Time\text{Turnaround Time} = \text{Completion Time} - \text{Arrival Time}&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 프로세스가 들어온 시점부터 끝나는 시점까지 전체 소요 시간입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 공식 ②&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Waiting Time (WT)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Waiting&amp;nbsp;Time=Turnaround&amp;nbsp;Time&amp;minus;Burst&amp;nbsp;Time\text{Waiting Time} = \text{Turnaround Time} - \text{Burst Time}&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Burst Time (BT):&lt;/b&gt; CPU에서 실제로 실행된 시간.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;따라서:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;WT=(CT&amp;minus;AT)&amp;minus;BTWT = (CT - AT) - BT&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이 공식은 &lt;b&gt;대기 시간 = 전체 걸린 시간 - 실제 실행 시간&lt;/b&gt;이라는 의미예요.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 공식 ③&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Response Time (RT)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Response&amp;nbsp;Time=First&amp;nbsp;CPU&amp;nbsp;Start&amp;nbsp;Time&amp;minus;Arrival&amp;nbsp;Time\text{Response Time} = \text{First CPU Start Time} - \text{Arrival Time}&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Round Robin 등에서는 Response Time이 중요하며, 첫 응답 시점을 측정합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 정리된 공식 관계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지표 공식&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Turnaround Time (TAT)&lt;/td&gt;
&lt;td&gt;Completion Time (CT) - Arrival Time (AT)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Waiting Time (WT)&lt;/td&gt;
&lt;td&gt;Turnaround Time (TAT) - Burst Time (BT)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response Time (RT)&lt;/td&gt;
&lt;td&gt;First Execution Start Time - Arrival Time (AT)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예제  &lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로세스:&lt;/b&gt; P1 (AT=0, BT=5)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CT = 10&lt;/li&gt;
&lt;li&gt;AT = 0&lt;/li&gt;
&lt;li&gt;BT = 5&lt;/li&gt;
&lt;li&gt;TAT = 10 - 0 = 10&lt;/li&gt;
&lt;li&gt;WT = 10 - 5 = 5&lt;/li&gt;
&lt;li&gt;(만약 첫 실행이 3초에 시작했다면) RT = 3 - 0 = 3&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평가지표들은 서로 &lt;b&gt;긴밀하게 연결&lt;/b&gt;되어 있어, 한 지표만 알면 나머지를 유추할 수 있습니다. 다만, 스케줄링 정책이나 &lt;b&gt;프로세스 간 선점 여부&lt;/b&gt;에 따라 값이 달라질 뿐, &lt;b&gt;기본 공식은 일정&lt;/b&gt;합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 심화: 다중 프로세서 스케줄링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티코어 환경에서는 스케줄링이 더 복잡해집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;대칭형 (SMP):&lt;/b&gt; 모든 CPU가 OS의 스케줄링 알고리즘을 공유.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비대칭형 (AMP):&lt;/b&gt; 한 CPU가 스케줄링만 담당.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Load Balancing&lt;/b&gt;: 각 CPU에 작업을 고르게 분배해야 병목을 방지할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  다중 프로세서 스케줄링의 Load Balancing&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;목표:&lt;/b&gt;&lt;br /&gt;CPU가 여러 개 있을 때 특정 프로세서만 과부하 걸리는 걸 방지하고, &lt;b&gt;작업을 고르게 분산&lt;/b&gt;하여 전체 시스템 효율을 높이는 것.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ Load Balancing 방식&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ &lt;b&gt;Central Queue 방식&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;설명:&lt;/b&gt; 모든 CPU가 &lt;b&gt;하나의 공통 큐&lt;/b&gt;에서 작업을 가져감.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; 자연스럽게 부하가 균형을 이룸.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점:&lt;/b&gt; 큐 자체가 병목이 될 수 있음 (Lock 경쟁).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예:&lt;/b&gt; 단순한 멀티코어 OS, 예제 수준의 스케줄러.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ &lt;b&gt;Local Queue + Work Stealing 방식&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;설명:&lt;/b&gt; 각 CPU는 &lt;b&gt;자신만의 로컬 큐&lt;/b&gt;를 가지고 작업을 관리.&lt;br /&gt;부하가 한쪽에 몰리면 &lt;b&gt;작업이 없는 CPU가 다른 CPU에서 작업을 &quot;훔쳐옴&quot;.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; 병렬성이 뛰어나고 큐 병목이 줄어듦.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점:&lt;/b&gt; Stealing 시 약간의 오버헤드 발생.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Linux CFS (Completely Fair Scheduler)에서 NUMA-aware 로드 밸런싱 시.&lt;/li&gt;
&lt;li&gt;멀티스레딩 라이브러리(TBB, Go 런타임 등).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ &lt;b&gt;Periodic Balancing (주기적 균형 재조정)&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;설명:&lt;/b&gt; 스케줄러가 일정 시간마다 CPU별 로드 상태를 점검하고 불균형 시 프로세스를 재배치.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;장점:&lt;/b&gt; 단순하지만 안정적.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단점:&lt;/b&gt; 너무 잦으면 오버헤드 발생.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4️⃣ &lt;b&gt;Push vs Pull 전략&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Push:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;과부하가 걸린 CPU&lt;/b&gt;가 스스로 일부 작업을 다른 CPU로 &lt;b&gt;밀어냄.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pull:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;빈 CPU가 적극적으로 다른 CPU에서 작업을 가져옴.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현대 OS들은 보통 &lt;b&gt;Push + Pull 혼합 전략&lt;/b&gt;을 씁니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 심화 예시: Linux CFS&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;다중 러너블 큐 (Per-CPU Runqueue)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Idle CPU는 Steal 우선 / 부하 심하면 Push도 수행&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;NUMA 환경에선 메모리 locality도 고려&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 간단 비교표&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방식 특징&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Central Queue&lt;/td&gt;
&lt;td&gt;단순 / 병목 발생 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local Queue + Stealing&lt;/td&gt;
&lt;td&gt;고성능 / 구현 복잡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Periodic Balancing&lt;/td&gt;
&lt;td&gt;간단 / 자주 하면 오버헤드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push / Pull&lt;/td&gt;
&lt;td&gt;균형 감시 / 유연한 분산&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &lt;b&gt;정리:&lt;/b&gt; 현대의 다중 프로세서 스케줄러는 &lt;b&gt;Local Queue + Work Stealing + Periodic Rebalance&lt;/b&gt; 같은 복합적인 방식을 채택해 높은 병렬성과 효율을 추구합니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 심화: 실시간 스케줄링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실시간 시스템은 &lt;b&gt;데드라인&lt;/b&gt;이 핵심입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Hard Real-Time:&lt;/b&gt; 데드라인 미스 절대 금지 (항공, 의료).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Soft Real-Time:&lt;/b&gt; 데드라인 미스 가능하지만 성능 저하 (멀티미디어).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;알고리즘:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Rate Monotonic (RM)&lt;/li&gt;
&lt;li&gt;Earliest Deadline First (EDF)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  간단한 Round Robin 예시 (C++)&lt;/h3&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;queue&amp;gt;

struct Process {
    int id;
    int burst;
};

int main() {
    std::queue&amp;lt;Process&amp;gt; q;
    q.push({1, 5});
    q.push({2, 10});
    q.push({3, 7});

    int quantum = 3;

    while (!q.empty()) {
        Process p = q.front(); q.pop();
        if (p.burst &amp;gt; quantum) {
            std::cout &amp;lt;&amp;lt; &quot;Process &quot; &amp;lt;&amp;lt; p.id &amp;lt;&amp;lt; &quot; runs for &quot; &amp;lt;&amp;lt; quantum &amp;lt;&amp;lt; &quot;\n&quot;;
            p.burst -= quantum;
            q.push(p);
        } else {
            std::cout &amp;lt;&amp;lt; &quot;Process &quot; &amp;lt;&amp;lt; p.id &amp;lt;&amp;lt; &quot; finishes\n&quot;;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 스케줄링의 핵심 요약&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;선점형&lt;/b&gt; 스케줄링은 컨텍스트 스위치를 동반하며 &lt;b&gt;응답성&lt;/b&gt;이 좋음.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;알고리즘 선택&lt;/b&gt;은 시스템의 특성(대화형? 배치형? 실시간?)에 따라 달라짐.&lt;/li&gt;
&lt;li&gt;현대 OS들은 대개 &lt;b&gt;Multilevel Feedback Queue&lt;/b&gt; 같은 복합 스케줄러를 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>다시 정리하는 CS 이론/OS</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/152</guid>
      <comments>https://develop-4-art.tistory.com/152#entry152comment</comments>
      <pubDate>Sat, 3 May 2025 01:21:30 +0900</pubDate>
    </item>
    <item>
      <title>[OS] 프로세스 관리</title>
      <link>https://develop-4-art.tistory.com/151</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K2IEE/btsNJwqMIgg/RjIp2oyXNcwzMMEF2XLMr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K2IEE/btsNJwqMIgg/RjIp2oyXNcwzMMEF2XLMr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K2IEE/btsNJwqMIgg/RjIp2oyXNcwzMMEF2XLMr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK2IEE%2FbtsNJwqMIgg%2FRjIp2oyXNcwzMMEF2XLMr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;352&quot; height=&quot;528&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2️⃣ 프로세스 관리 (Process Management)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 프로세스란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**프로세스(Process)**는 실행 중인 프로그램의 인스턴스입니다. 단순한 프로그램 코드가 &lt;b&gt;실제 메모리에서 실행&lt;/b&gt;되면서 **자원(CPU, 메모리, 파일 등)**을 할당받은 존재예요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구성 요소:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그램 코드 (텍스트 섹션)&lt;/li&gt;
&lt;li&gt;프로세스 스택 (함수 호출, 지역변수)&lt;/li&gt;
&lt;li&gt;데이터 섹션 (글로벌 변수)&lt;/li&gt;
&lt;li&gt;힙 (동적 할당)&lt;/li&gt;
&lt;li&gt;프로세스 상태 정보 (레지스터, 프로그램 카운터 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 프로세스 상태 (Process States)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;New:&lt;/b&gt; 생성 중&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Ready:&lt;/b&gt; 실행 준비 상태&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Running:&lt;/b&gt; CPU에서 실행 중&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Waiting (Blocked):&lt;/b&gt; I/O 등 대기 중&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Terminated:&lt;/b&gt; 실행 종료&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 프로세스 제어 블록 (PCB)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제가 각 프로세스를 관리하기 위해 사용하는 &lt;b&gt;데이터 구조&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;포함 내용:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스 ID (PID)&lt;/li&gt;
&lt;li&gt;프로세스 상태&lt;/li&gt;
&lt;li&gt;프로그램 카운터&lt;/li&gt;
&lt;li&gt;CPU 레지스터&lt;/li&gt;
&lt;li&gt;메모리 관리 정보&lt;/li&gt;
&lt;li&gt;계정 정보 (UID 등)&lt;/li&gt;
&lt;li&gt;I/O 상태 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  컨텍스트 스위칭 시 이 PCB 정보가 저장/복원됩니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 프로세스 계층 및 관계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;부모/자식 프로세스:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;fork() 시스템 콜로 부모 &amp;rarr; 자식 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로세스 트리:&lt;/b&gt; 계층적 구조&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 멀티프로세싱과 멀티스레딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 멀티프로세싱 멀티스레딩&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;정의&lt;/td&gt;
&lt;td&gt;여러 프로세스가 동시에 실행&lt;/td&gt;
&lt;td&gt;하나의 프로세스 내에서 여러 쓰레드 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메모리 공간&lt;/td&gt;
&lt;td&gt;각자 독립적&lt;/td&gt;
&lt;td&gt;메모리(힙, 데이터)는 공유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;td&gt;여러 브라우저 인스턴스&lt;/td&gt;
&lt;td&gt;웹 페이지 렌더링 + 다운로드 동시 진행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 프로세스 생성 &amp;amp; 종료 (UNIX 기준)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;생성:&lt;/b&gt; fork(), exec()&lt;/li&gt;
&lt;li&gt;&lt;b&gt;종료:&lt;/b&gt; exit()&lt;/li&gt;
&lt;li&gt;&lt;b&gt;좀비 프로세스:&lt;/b&gt; 부모가 자식의 종료 상태를 수거하지 않으면 생김 (wait() 필요)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 쓰레드 개념 (간략)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드는 &lt;b&gt;프로세스 내의 경량 실행 단위&lt;/b&gt;로, 프로세스와 달리 &lt;b&gt;같은 주소 공간을 공유&lt;/b&gt;합니다. 프로세스와 다르게 오버헤드가 작아 빠른 병렬 처리가 가능합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;POSIX Thread (pthreads)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;C++11 이후:&lt;/b&gt; std::thread&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  C++ 간단 예시&lt;/h3&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;thread&amp;gt;

void task() {
    std::cout &amp;lt;&amp;lt; &quot;Hello from thread!\n&quot;;
}

int main() {
    std::thread t(task);
    t.join();  // join 호출 안 하면 메인 종료 시 크래시
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 심화 개념&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;컨텍스트 스위칭 비용:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스 간: 무겁다 (메모리 맵까지 바꿔야 함)&lt;/li&gt;
&lt;li&gt;쓰레드 간: 상대적으로 가볍다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로세스 간 통신 (IPC):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파이프, 소켓, 공유 메모리, 메시지 큐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;스핀락을 사용할 때, 컨텍스트 스위칭 비용은 원칙적으로 발생하지 않습니다.&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;조건이 있습니다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 왜 컨텍스트 스위칭 비용이 발생하지 않나?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일반적인 std::mutex나 OS 커널 수준의 뮤텍스는 락을 획득하지 못하면 **커널에 제어권을 넘기고 대기 상태(블록 상태)**로 전환되며 &lt;b&gt;컨텍스트 스위치&lt;/b&gt;가 발생합니다.&lt;/li&gt;
&lt;li&gt;반면 **스핀락(SpinLock)**은:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;락을 잡을 때까지 계속 루프를 돌면서(active waiting)&lt;/b&gt; CPU를 점유합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다른 쓰레드로 전환되지 않고 현재 쓰레드가 계속 CPU를 차지&lt;/b&gt;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스핀락을 사용하는 동안에는 &lt;b&gt;쓰레드가 블록되지 않으므로 컨텍스트 스위칭이 발생하지 않습니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;이 때문에 &lt;b&gt;락 대기 시간이 매우 짧은 상황&lt;/b&gt;에서는 스핀락이 mutex보다 빠를 수 있어요.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  그런데 주의할 점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스핀락은 컨텍스트 스위칭은 안 하지만, &lt;b&gt;CPU 자원을 낭비&lt;/b&gt;합니다. CPU 코어 하나가 그 락을 기다리며 &lt;b&gt;계속 바쁘게 루프를 돌기 때문&lt;/b&gt;입니다.&lt;/li&gt;
&lt;li&gt;운영체제의 스케줄러가 &quot;이건 너무 오래 스핀만 돈다&quot;라고 판단하면 &lt;b&gt;강제로 다른 스레드로 전환시킬 수 있습니다.&lt;/b&gt; 이건 운영체제 스케줄링 정책에 따라 다릅니다만, &lt;b&gt;긴 스핀은 결국 컨텍스트 스위치를 유발할 가능성이 있습니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;✅ 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문 답변&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;스핀락 사용 시 컨텍스트 스위칭이 발생하는가?&lt;/td&gt;
&lt;td&gt;&lt;b&gt;아니요, 기본적으로 발생하지 않습니다.&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;언제든 컨텍스트 스위치가 발생할 수 있는가?&lt;/td&gt;
&lt;td&gt;OS 스케줄러가 개입하거나 타임 슬라이스가 끝나면 발생할 수 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;락 점유 시간이 길면 어떤 문제가 생기는가?&lt;/td&gt;
&lt;td&gt;스핀락이 비효율적이고, 결국 컨텍스트 스위칭이 발생할 수도 있습니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, &lt;b&gt;짧은 임계구역&lt;/b&gt;에서는 스핀락이 유리하지만, &lt;b&gt;긴 대기 시간&lt;/b&gt;이 예상되면 일반 mutex 같은 커널 락을 쓰는 게 안전합니다.&lt;/p&gt;</description>
      <category>다시 정리하는 CS 이론/OS</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/151</guid>
      <comments>https://develop-4-art.tistory.com/151#entry151comment</comments>
      <pubDate>Sat, 3 May 2025 01:06:27 +0900</pubDate>
    </item>
    <item>
      <title>[OS] 운영체제 개요</title>
      <link>https://develop-4-art.tistory.com/150</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K2IEE/btsNJwqMIgg/RjIp2oyXNcwzMMEF2XLMr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K2IEE/btsNJwqMIgg/RjIp2oyXNcwzMMEF2XLMr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K2IEE/btsNJwqMIgg/RjIp2oyXNcwzMMEF2XLMr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK2IEE%2FbtsNJwqMIgg%2FRjIp2oyXNcwzMMEF2XLMr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;352&quot; height=&quot;528&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ 운영체제 개요 (Introduction to Operating Systems)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 운영체제란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제(OS)는 &lt;b&gt;컴퓨터 하드웨어와 사용자 프로그램 사이에 위치하는 시스템 소프트웨어&lt;/b&gt;입니다. **자원 관리(Resource Management)**와 &lt;b&gt;인터페이스 제공&lt;/b&gt;이 주된 역할이에요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 주요 기능&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;프로세스/스레드 관리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU 스케줄링, 동기화, 교착상태 방지 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리 관리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 할당, 가상 메모리, 캐싱 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;저장장치 관리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 시스템, 디스크 스케줄링 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;입출력(I/O) 관리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장치 드라이버, 버퍼링 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보호 및 보안&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 권한, 데이터 보호&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 인터페이스 제공&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CLI (Command Line), GUI (Graphical User Interface)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 운영체제의 역할&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;자원 관리자 (Resource Manager):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU, 메모리, 디스크, I/O 장치 같은 &lt;b&gt;하드웨어 자원의 효율적 관리&lt;/b&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로그램 실행 환경 제공:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 프로그램들이 하드웨어를 직접 만지지 않도록 보호하며 실행되도록 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;추상화 (Abstraction):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하드웨어 자원의 복잡성을 감춤 (예: 파일 시스템이 디스크의 실제 구조를 숨김).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ OS의 분류&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Batch 시스템:&lt;/b&gt; 작업을 모아서 일괄 처리 (초창기 컴퓨터)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Time Sharing:&lt;/b&gt; 여러 사용자가 동시에 사용하는 시스템 (멀티태스킹)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Real-Time OS:&lt;/b&gt; 즉각 반응이 필요한 시스템 (항공, 의료)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;멀티프로세싱 OS:&lt;/b&gt; 여러 CPU를 지원 (대형 서버)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분산 OS:&lt;/b&gt; 네트워크를 통한 자원 공유 (클러스터, 클라우드)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 커널 구조&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Monolithic Kernel:&lt;/b&gt; 커널 내 모든 기능을 하나의 큰 블록으로 구현 (Linux)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Microkernel:&lt;/b&gt; 최소한의 핵심만 커널에 두고 나머지는 사용자 모드에서 실행 (Minix)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Hybrid:&lt;/b&gt; 두 가지의 장점을 섞음 (Windows NT)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 사용자/커널 모드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;커널 모드:&lt;/b&gt; 하드웨어 직접 접근 가능 (운영체제 코드)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 모드:&lt;/b&gt; 제한된 권한 (일반 앱)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  OS는 시스템 콜(system call)을 통해 사용자 모드 &amp;harr; 커널 모드 전환을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>다시 정리하는 CS 이론/OS</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/150</guid>
      <comments>https://develop-4-art.tistory.com/150#entry150comment</comments>
      <pubDate>Sat, 3 May 2025 01:05:46 +0900</pubDate>
    </item>
    <item>
      <title>[OS] 운영체제 커리큘럼</title>
      <link>https://develop-4-art.tistory.com/149</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K2IEE/btsNJwqMIgg/RjIp2oyXNcwzMMEF2XLMr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K2IEE/btsNJwqMIgg/RjIp2oyXNcwzMMEF2XLMr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K2IEE/btsNJwqMIgg/RjIp2oyXNcwzMMEF2XLMr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK2IEE%2FbtsNJwqMIgg%2FRjIp2oyXNcwzMMEF2XLMr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;352&quot; height=&quot;528&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;운영체제(OS)&lt;/b&gt; 강의에서 다루는 커리큘럼은 대체로 &lt;b&gt;컴퓨터 구조, 병렬성, 메모리 관리, 자원 관리&lt;/b&gt;를 단계적으로 배우게 구성됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 &lt;b&gt;일반적인 CS 학부 과정&lt;/b&gt;을 기준으로 커리큘럼을 설명할게요:&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1️⃣ 운영체제 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심 개념:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영체제의 정의와 역할&lt;/li&gt;
&lt;li&gt;사용자 &amp;harr; 하드웨어 사이의 인터페이스&lt;/li&gt;
&lt;li&gt;OS 구조 (커널, 쉘, 사용자 모드/커널 모드)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배경:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Batch &amp;rarr; Time Sharing &amp;rarr; Multiprocessing &amp;rarr; Modern OS 발전사&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2️⃣ 프로세스 관리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;프로세스와 쓰레드 개념&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;프로세스 상태 (Ready, Running, Blocked)&lt;/li&gt;
&lt;li&gt;프로세스 제어 블록 (PCB)&lt;/li&gt;
&lt;li&gt;컨텍스트 스위칭&lt;/li&gt;
&lt;li&gt;쓰레드의 등장과 멀티스레딩&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;심화:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티프로세싱과 멀티스레딩 비교&lt;/li&gt;
&lt;li&gt;Thread Pool 패턴&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3️⃣ CPU 스케줄링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스케줄링 알고리즘:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FCFS (First-Come, First-Served)&lt;/li&gt;
&lt;li&gt;SJF (Shortest Job First)&lt;/li&gt;
&lt;li&gt;Priority Scheduling&lt;/li&gt;
&lt;li&gt;Round Robin&lt;/li&gt;
&lt;li&gt;Multilevel Queue&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;평가 지표:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;응답 시간, 대기 시간, 처리량, CPU 이용률&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;심화:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다중 프로세서 스케줄링&lt;/li&gt;
&lt;li&gt;실시간 스케줄링 (RTOS)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4️⃣ 동기화 문제&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;임계구역 문제(Critical Section Problem)&lt;/li&gt;
&lt;li&gt;경쟁 상태(Race Condition)&lt;/li&gt;
&lt;li&gt;동기화 도구:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mutex, Semaphore, Monitor, SpinLock&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;고전적 문제:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생산자-소비자 문제&lt;/li&gt;
&lt;li&gt;철학자들의 식사 문제&lt;/li&gt;
&lt;li&gt;독자-작가 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;심화:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데커, 피터슨 알고리즘&lt;/li&gt;
&lt;li&gt;Test-and-Set, Compare-and-Swap&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5️⃣ 교착 상태(Deadlock)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필요 조건 (Mutual Exclusion 등)&lt;/li&gt;
&lt;li&gt;자원 할당 그래프&lt;/li&gt;
&lt;li&gt;Deadlock 예방, 회피 (Banker's Algorithm), 탐지, 회복&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6️⃣ 메모리 관리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주소 공간 (논리, 물리 주소)&lt;/li&gt;
&lt;li&gt;MMU (Memory Management Unit)&lt;/li&gt;
&lt;li&gt;연속 할당:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;First Fit, Best Fit, Worst Fit&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;분산 및 외부 단편화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;가상 메모리:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;페이징, 세그먼트&lt;/li&gt;
&lt;li&gt;페이지 교체 알고리즘:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FIFO, LRU, Optimal&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TLB (Translation Lookaside Buffer)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7️⃣ 스토리지 관리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디스크 구조 (플래터, 실린더, 섹터)&lt;/li&gt;
&lt;li&gt;디스크 스케줄링:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;FCFS, SSTF, SCAN, C-SCAN&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;파일 시스템:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디렉토리 구조&lt;/li&gt;
&lt;li&gt;파일 할당 방식 (연속, 연결, 인덱스)&lt;/li&gt;
&lt;li&gt;FAT, i-node&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8️⃣ 입출력 시스템&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;I/O 장치 개요&lt;/li&gt;
&lt;li&gt;동기 vs 비동기 I/O&lt;/li&gt;
&lt;li&gt;인터럽트, DMA (Direct Memory Access)&lt;/li&gt;
&lt;li&gt;버퍼링, 캐싱&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9️⃣ 보안 &amp;amp; 보호&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보안과 보호 차이&lt;/li&gt;
&lt;li&gt;접근 제어 매트릭스&lt;/li&gt;
&lt;li&gt;유저 인증 및 권한 관리&lt;/li&gt;
&lt;li&gt;멀웨어 방지 개념&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;  심화/특수 주제 (선택적으로 다룸)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티코어/멀티CPU 환경&lt;/li&gt;
&lt;li&gt;분산 시스템 (RPC, 분산 파일 시스템)&lt;/li&gt;
&lt;li&gt;클라우드 운영체제 개념&lt;/li&gt;
&lt;li&gt;모바일 OS (안드로이드/iOS 구조)&lt;/li&gt;
&lt;li&gt;최신: 컨테이너화(Linux cgroup, namespace)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  보통 참고하는 교재&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Operating System Concepts (Silberschatz)&lt;/b&gt; &amp;mdash; 소위 공룡책&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Modern Operating Systems (Tanenbaum)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Operating Systems: Three Easy Pieces (OSTEP)&lt;/b&gt; &amp;mdash; 무료 PDF로도 유명&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>다시 정리하는 CS 이론/OS</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/149</guid>
      <comments>https://develop-4-art.tistory.com/149#entry149comment</comments>
      <pubDate>Sat, 3 May 2025 01:04:48 +0900</pubDate>
    </item>
    <item>
      <title>[컴구론] 캐시 히트와 미스 탐구</title>
      <link>https://develop-4-art.tistory.com/148</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yt0cF/btsNKAr4x57/p5cCkHhoJWp7UQkTMFo9gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yt0cF/btsNKAr4x57/p5cCkHhoJWp7UQkTMFo9gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yt0cF/btsNKAr4x57/p5cCkHhoJWp7UQkTMFo9gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyt0cF%2FbtsNKAr4x57%2Fp5cCkHhoJWp7UQkTMFo9gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;389&quot; height=&quot;389&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;캐시 히트(Cache Hit) 와 미스(Cache Miss) ― &amp;ldquo;명령어&amp;middot;데이터가 어디에 있느냐가 속도를 좌우한다&amp;rdquo;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;캐시가 하는 일, 10초 요약&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;CPU &amp;harr; DRAM&lt;/b&gt; 속도 차가 수 10 배 이상 &amp;rarr; 중간에 &lt;b&gt;SRAM 캐시(L1/L2/L3)&lt;/b&gt; 를 끼워서 &amp;lsquo;자주 쓰는 데이터&amp;rsquo;를 &lt;b&gt;가까운 곳&lt;/b&gt;에 복사해 둔다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;히트&lt;/b&gt;: CPU가 요구한 주소가 &lt;b&gt;이미 캐시에 있음&lt;/b&gt; &amp;rarr; 수 ns 내 응답.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;미스&lt;/b&gt;: 캐시에 없음 &amp;rarr; DRAM(또는 하위 캐시)까지 내려가 수 10 ns~㎲ 지연 후 캐시에 &lt;b&gt;라인(line)&lt;/b&gt; 단위로 불러와 재시도.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 사이클 관점으로 보면 &lt;b&gt;Memory 단계&lt;/b&gt;에서 AGU&amp;rarr;MAR&amp;rarr;(Cache) 시점에 히트/미스가 갈린다.&lt;br /&gt;미스가 나면 CU가 파이프라인을 &lt;b&gt;스톨&lt;/b&gt;하거나 OoO 코어는 다른 &amp;mu;op로 지연을 &amp;lsquo;가린다&amp;rsquo;.&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;45&quot; data-end=&quot;78&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-start=&quot;45&quot; data-end=&quot;78&quot; data-ke-size=&quot;size23&quot;&gt;지역성(locality) = &amp;ldquo;다시 쓸 확률&amp;rdquo;&lt;/h3&gt;
&lt;div style=&quot;color: #333333; text-align: start;&quot;&gt;
&lt;div&gt;종류 뜻 코드 패턴 캐시 영향
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;시간 지역성(Temporal)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;방금 접근한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;주소를 곧 또&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;쓴다&lt;/td&gt;
&lt;td&gt;누적 변수 sum += a[i]&amp;middot;루프 카운터&lt;/td&gt;
&lt;td&gt;동일 라인이 L1에 남아&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;히트율&amp;uarr;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;공간 지역성(Spatial)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;근처 주소&lt;/b&gt;도 곧 사용한다&lt;/td&gt;
&lt;td&gt;① 선형 배열 순회 i=0‥N-1② 구조체 내부 연속 멤버&lt;/td&gt;
&lt;td&gt;캐시는 64 B 라인 단위로 로드 &amp;rarr; 옆 데이터가 &amp;ldquo;덤&amp;rdquo;으로 따라옴&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;blockquote style=&quot;color: #666666; text-align: start;&quot; data-start=&quot;372&quot; data-end=&quot;418&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p style=&quot;color: #666666;&quot; data-start=&quot;374&quot; data-end=&quot;418&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;규칙&lt;/b&gt;&amp;emsp;&amp;ldquo;가까이&amp;middot;많이&amp;middot;다시&amp;rdquo; 액세스하는 코드는 대부분 캐시 친화적이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;히트 vs 미스 &amp;mdash; 작동 흐름 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단계 히트 (L1 Hit) 미스 (L1 Miss &amp;rarr; L2)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;① 태그 비교&lt;/td&gt;
&lt;td&gt;태그 일치&lt;/td&gt;
&lt;td&gt;불일치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;② 데이터 반환&lt;/td&gt;
&lt;td&gt;4 ns 내 레지스터로 포워딩&lt;/td&gt;
&lt;td&gt;L2 Cache 요청(8&amp;ndash;12 ns)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;③ 캐시 갱신&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;td&gt;하위 캐시에서 64 B 라인 전체 상위 캐시에 복사&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;④ 파이프라인 영향&lt;/td&gt;
&lt;td&gt;&lt;b&gt;노 버블&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Load &amp;mu;op &amp;rarr; Stalled, 다른 &amp;mu;op 스케줄 시도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⑤ 전력 소모&lt;/td&gt;
&lt;td&gt;수 pJ&lt;/td&gt;
&lt;td&gt;L2/L3 버스 + DRAM 액세스 &amp;rarr; nJ 단위 &amp;uarr;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;미스의 4가지 종류 (3C + Coherence)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름 발생 원인 예시 / 해결책&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Compulsory&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;처음 보는 주소 (Warm-up)&lt;/td&gt;
&lt;td&gt;프리페치, 큰 라인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Capacity&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;전체 작업 집합 &amp;gt; 캐시 용량&lt;/td&gt;
&lt;td&gt;더 큰 캐시, SW 타일링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Conflict&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;같은 인덱스에 다른 태그 충돌(Set 2-way&amp;darr;)&lt;/td&gt;
&lt;td&gt;연관도&amp;uarr;(4/8-way), 주소 재배치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Coherence&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;멀티코어가 같은 라인 수정&lt;/td&gt;
&lt;td&gt;MESI 프로토콜, NUCA 디자인&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능 지표: AMAT 공식을 기억하자&lt;/h3&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;AMAT = Hit Time + Miss Rate &amp;times; Miss Penalty
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Hit Time&lt;/b&gt;: L1 히트 시 지연 (e.g., 4 ns)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Miss Rate&lt;/b&gt;: L1 요청 중 미스 비율 (e.g., 5 %)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Miss Penalty&lt;/b&gt;: 미스&amp;rarr;DRAM 왕복 지연 (e.g., 50 ns)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예) (4 ns) + 0.05 &amp;times; 50 ns = 6.5 ns &amp;rArr; 히트율을 95&amp;rarr;97 %로만 올려도 &lt;b&gt;실효 지연&lt;/b&gt;이 5.5 ns로 떨어진다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;캐시 친화적 코드 감각&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;패턴 히트&amp;uarr; 이유 간단 예&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;연속 스캔(Sequential)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;공간 지역성&lt;/td&gt;
&lt;td&gt;for(i=0;i&amp;lt;N;i++) sum+=A[i];&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;블록 타일링&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;작동 집합(Block) &amp;lt; 캐시 용량&lt;/td&gt;
&lt;td&gt;2 D 행렬 곱에서 for(i, j, k in tile)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;구조체 패킹&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;더 많은 객체가 한 라인&lt;/td&gt;
&lt;td&gt;struct 멤버 정렬 &amp;middot; 압축&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Hot/Cold 분리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;자주 쓰는 필드만 캐시 사용&lt;/td&gt;
&lt;td&gt;게임 컴포넌트 데이터 레이아웃&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파이프라인과의 상호 작용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;In-Order 코어(임베디드)&lt;/b&gt;: 미스 = 파이프라인 전면 Stall &amp;rarr; CPI 급등&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Out-of-Order 코어(데스크톱/서버)&lt;/b&gt;: 독립 &amp;mu;op 먼저 실행해 숨어 있는 ILP 로 미스 레이턴시 가리기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;멀티스레드(SMT)&lt;/b&gt;: 다른 하드웨어 스레드로 슬랏을 채워 &amp;lsquo;빈 시간&amp;rsquo; 활용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프리페처&lt;/b&gt;: CU 앞단에서 선형&amp;middot;스트라이드 패턴을 예측 &amp;rarr; 미스 전에 라인 미리 로드&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기억해 두면 좋은 숫자 (서버-급, 2025년 기준)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계층 용량 레이턴시(Hit) BW&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;L1 I/D&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;64 KB 각&lt;/td&gt;
&lt;td&gt;4 cycles (&amp;asymp; 1 ns)&lt;/td&gt;
&lt;td&gt;1.5 TB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;L2 (Private)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;1 MB&lt;/td&gt;
&lt;td&gt;12 cycles&lt;/td&gt;
&lt;td&gt;0.7 TB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;L3 (Shared)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;32 MB&lt;/td&gt;
&lt;td&gt;40 cycles&lt;/td&gt;
&lt;td&gt;0.3 TB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;DDR5 DRAM&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;수 GB&lt;/td&gt;
&lt;td&gt;150 cycles (&amp;asymp; 55 ns)&lt;/td&gt;
&lt;td&gt;100 GB/s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 정리&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;캐시 히트&lt;/b&gt;는 &amp;ldquo;데이터가 이미 가까이 있다&amp;rdquo; &amp;rarr; 클럭 몇 번 안에 끝.&lt;br /&gt;&lt;b&gt;캐시 미스&lt;/b&gt;는 &amp;ldquo;데이터를 밑에서 끌어올려야 한다&amp;rdquo; &amp;rarr; 파이프라인이 수 10 ~ 수 100 클럭 지연, 전력도 &amp;uarr;.&lt;br /&gt;설계자는 &lt;b&gt;정책(연관도&amp;middot;교체&amp;middot;프리페치)과 용량&lt;/b&gt;, 개발자는 &lt;b&gt;지역성&amp;middot;데이터 레이아웃&lt;/b&gt; 최적화로 &lt;b&gt;Miss Rate &amp;times; Miss Penalty&lt;/b&gt;를 줄여 성능과 전력을 모두 잡는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;히트 &amp;harr; 미스-흐름 (복습)&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;L1 Hit        : 4 cycles
L1 Miss &amp;rarr; L2  : 12 cycles
L2 Miss &amp;rarr; L3  : 40 cycles
L3 Miss &amp;rarr; DRAM: 150 cycles ⚠️
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AMAT = HitTime + MissRate &amp;times; MissPenalty&amp;emsp;⟶&amp;emsp;지역성을 살리면 MissRate가 뚝&amp;darr;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Hot/Cold 분리(Hoisting) &amp;mdash; &lt;b&gt;게임 개발 예시&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;문제&lt;/h4&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;struct Monster {
    Vec3 position;
    float health;
    bool isAlive;
    // &amp;darr; 드물게 쓰는 데이터
    std::string name;
    std::array&amp;lt;Item, 16&amp;gt; inventory;
    AIState brain;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Update Loop&lt;/b&gt;(60 FPS)에서는 position, health, isAlive만 매 프레임 접근&lt;/li&gt;
&lt;li&gt;name&amp;middot;inventory&amp;middot;brain 은 &lt;b&gt;이벤트 발생&lt;/b&gt; 시에만 사용&lt;/li&gt;
&lt;li&gt;한 구조체에 섞여 있으므로 &lt;b&gt;64 B 라인에 불필요한 Cold 데이터&lt;/b&gt;까지 끌어올려 L1 압박 &amp;rarr; 미스 증가&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;해결 &amp;ndash; Hot/Cold 분할&lt;/h4&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// Hot &amp;mdash; 매 프레임 필드만
struct MonsterCore {
    Vec3  position;
    float health;
    bool  isAlive;
    uint32_t coldIndex;   // Cold 배열 인덱스
};

// Cold &amp;mdash; 희소 액세스 필드
struct MonsterCold {
    std::string name;
    std::array&amp;lt;Item, 16&amp;gt; inventory;
    AIState brain;
};

// 게임 월드 컨테이너
std::vector&amp;lt;MonsterCore&amp;gt; monsters;        // Tightly packed  
std::vector&amp;lt;MonsterCold&amp;gt; monstersCold;    // Sparse ❄️

void UpdateMonsters(float dt)
{
    for (auto&amp;amp; m : monsters)
    {
        if (!m.isAlive) continue;
        m.position += ComputeVelocity(m) * dt;
        // 건강 체크 등 Hot 데이터만 사용 &amp;rarr; L1/L2 Hit !
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Hot 배열&lt;/b&gt;은 연속 배열 &amp;rarr; &lt;b&gt;공간 지역성&lt;/b&gt; 덕분에 한 번 미스 후 파이프라인이 대부분 히트&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Cold 배열&lt;/b&gt;은 이벤트룩 등에서만 액세스 &amp;rarr; 캐시 오염 최소&lt;/li&gt;
&lt;li&gt;큰 규모(수만 엔티티)에서 &lt;b&gt;프레임당 L1 miss 수가 두 자릿수 % 감소&lt;/b&gt;하는 것이 흔하다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요약 체크리스트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 친화 코드 작성 법 왜 그런가?&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;연속 순회&lt;/b&gt;(배열 &amp;gt; 연결리스트)&lt;/td&gt;
&lt;td&gt;공간 지역성, HW 프리페처가 다발 히트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;타일링&lt;/b&gt;(행렬 2 D)&lt;/td&gt;
&lt;td&gt;작동 집합을 L1/L2 용량 안에 고정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Hot/Cold 분리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;불필요한 라인 로드&amp;middot;교체 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;구조체 패킹 / SoA&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;구조 개수 &amp;uarr; = 같은 L1 라인에 더 많은 요소&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 한 줄&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시간&amp;middot;공간 지역성을 살리고 Hot/Cold 데이터를 떼어놓으면&lt;/b&gt; L1/L2 히트율이 올라가고 DRAM 미스 패널티가 줄어, 게임 루프&amp;middot;물리 시뮬레이션 등 프레임 결정 로직에서 눈에 띄는 성능&amp;middot;전력 이득을 얻는다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>다시 정리하는 CS 이론/컴퓨터구조론</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/148</guid>
      <comments>https://develop-4-art.tistory.com/148#entry148comment</comments>
      <pubDate>Thu, 1 May 2025 17:22:31 +0900</pubDate>
    </item>
    <item>
      <title>[컴구론] 명령어 사이클과 인터럽트</title>
      <link>https://develop-4-art.tistory.com/147</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yt0cF/btsNKAr4x57/p5cCkHhoJWp7UQkTMFo9gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yt0cF/btsNKAr4x57/p5cCkHhoJWp7UQkTMFo9gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yt0cF/btsNKAr4x57/p5cCkHhoJWp7UQkTMFo9gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyt0cF%2FbtsNKAr4x57%2Fp5cCkHhoJWp7UQkTMFo9gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;389&quot; height=&quot;389&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CPU Instruction Cycle&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단계 핵심 전용 레지스터 세부 흐름 (&amp;rarr; 는 데이터 이동) 메모&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;1️⃣ Fetch&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;PC&lt;/b&gt;(Program Counter) &amp;rarr; &lt;b&gt;MAR&lt;/b&gt; &amp;rarr; Memory &amp;rarr; &lt;b&gt;MDR&lt;/b&gt; &amp;rarr; &lt;b&gt;IR&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;① PC 값을 &lt;b&gt;MAR&lt;/b&gt;에 복사 (주소 버스)② I-Cache/TLB 경유하여 명령어 읽기③ 읽어온 32/64 bit 코드를 &lt;b&gt;MDR&lt;/b&gt;(Memory Data Register) 에 임시 저장④ MDR 내용을 &lt;b&gt;IR&lt;/b&gt;(Instruction Register) 로 이동⑤ PC &amp;larr; PC + len (또는 분기&amp;middot;예외로 덮어쓰기)&lt;/td&gt;
&lt;td&gt;&amp;ldquo;주소 지시 = MAR, 데이터 수신 = MDR, 보관 = IR&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;2️⃣ Decode (ID)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;IR, 레지스터 파일&lt;/td&gt;
&lt;td&gt;&amp;bull; CU가 IR &amp;rarr; opcode&amp;middot;오퍼랜드 분해&amp;bull; 레지스터 파일에서 &lt;b&gt;src&lt;/b&gt; 값 읽기&amp;bull; 의존성&amp;middot;Hazard 검사, 스케줄 큐 등록&lt;/td&gt;
&lt;td&gt;멀티-디코더, &amp;micro;-op Cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;3️⃣ Execute (EX)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;ALU/FPU, &lt;b&gt;AGU&lt;/b&gt;(Address Gen Unit)&lt;/td&gt;
&lt;td&gt;&amp;bull; ALU/FPU/SIMD로 산술&amp;middot;논리&amp;middot;분기 평가&amp;bull; Load/Store &amp;rarr; &lt;b&gt;AGU&lt;/b&gt;가 주소 계산: Base Reg + Offset&lt;/td&gt;
&lt;td&gt;분기 결과 miss 시 파이프라인 flush&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;4️⃣ Memory (MEM)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;AGU 주소 &amp;rarr; MAR &amp;rarr; Memory &amp;harr; MDR&lt;/td&gt;
&lt;td&gt;&amp;bull; AGU 주소를 다시 &lt;b&gt;MAR&lt;/b&gt;에 로드&amp;bull; D-Cache Hit &amp;rarr; MDR 로 데이터 수신&amp;bull; Store 일 경우 MDR &amp;larr; 레지스터 데이터 &amp;rarr; 메모리 기록 (쓰기 버퍼)&lt;/td&gt;
&lt;td&gt;TLB&amp;middot;캐시&amp;middot;ECC 동작&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;5️⃣ Write-Back (WB)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;MDR / ALU 결과&lt;/td&gt;
&lt;td&gt;&amp;bull; 결과 &amp;rarr; &lt;b&gt;레지스터 파일&lt;/b&gt;(RD 포트)&amp;bull; ALU 플래그 &amp;rarr; 플래그 레지스터&amp;bull; OoO 코어는 &lt;b&gt;ROB Commit&lt;/b&gt; 단계까지 완료&lt;/td&gt;
&lt;td&gt;포워딩&amp;thinsp;&amp;middot;&amp;thinsp;리네이밍으로 의존성 가리기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그림 없이 흐름만 눈에 넣는 팁&lt;/h3&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;PC &amp;rarr; MAR &amp;rarr; Mem &amp;rarr; MDR &amp;rarr; IR ─┐   (Fetch)
                           │
          RegSrc &amp;rarr; ALU ----┤   (Execute)
                           &amp;darr;
            ALU / MDR &amp;rarr; RegDst (Write-Back)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;MAR(Memory Address Register)&lt;/b&gt; : &amp;ldquo;이번 &lt;b&gt;버스 접근 주소&lt;/b&gt;&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MDR(Memory Data Register)&lt;/b&gt; : &amp;ldquo;메모리에서 &lt;b&gt;막 주고받은 데이터&lt;/b&gt;&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;IR(Instruction Register)&lt;/b&gt; : &amp;ldquo;방금 가져와 &lt;b&gt;해석 중인 명령어&lt;/b&gt;&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단계 간 버블(지연)을 줄이는 주요 기법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hazard 원인 대표 해결책&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Data&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;lw r1&amp;hellip; 바로 뒤 add r2,r1,r3&lt;/td&gt;
&lt;td&gt;&lt;b&gt;포워딩&lt;/b&gt;, 스톨 1클럭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Control&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;분기 목표 예상 실패&lt;/td&gt;
&lt;td&gt;&lt;b&gt;분기 예측기&lt;/b&gt;, Return Stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Structural&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;IF&amp;middot;MEM 단계가 같은 캐시 병행 요청&lt;/td&gt;
&lt;td&gt;&lt;b&gt;I/D Cache 분리&lt;/b&gt;, 다중 포트&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;한 줄로 총정리&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PC&amp;rarr;MAR&amp;rarr;Mem&amp;rarr;MDR&amp;rarr;IR&lt;/b&gt; 로 명령어를 집어오고, Decode&amp;middot;Execute 과정을 거쳐 &lt;b&gt;AGU&amp;rarr;MAR&amp;rarr;Mem&amp;rarr;MDR&lt;/b&gt; 로 데이터까지 주고받은 뒤, 결과를 레지스터에 Write-Back 한다. 이 전 경로를 파이프라인으로 겹쳐 돌리면서 슈퍼스칼라&amp;middot;OoO&amp;middot;캐시&amp;middot;분기예측으로 버블을 최소화하는 것이 현대 CPU 설계의 핵심이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;인터럽트 + ISR(Interrupt Service Routine) ― &amp;ldquo;CPU가 비상 호출을 처리하는 &lt;b&gt;끝-까지&lt;/b&gt; 흐름&amp;rdquo;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1️⃣ 인터럽트 한-줄 정의 &amp;mdash; 왜 필요한가?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인터럽트&lt;/b&gt; = &amp;ldquo;지금 실행 중이던 명령 흐름을 잠깐 멈추고, 더 급한 이벤트를 처리하라&amp;rdquo;는 &lt;b&gt;하드웨어/소프트웨어 신호&lt;/b&gt;&lt;br /&gt;예: 키보드 키 입력, SSD DMA 완료, 1 ms 타이머 Tick, 0으로 나누기 오류, 시스템 호출(int 0x80)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2️⃣ ISR 이란? (Interrupt Service Routine)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문 답&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;무엇?&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;인터럽트가 발생했을 때 &lt;b&gt;CPU가 자동으로 점프&lt;/b&gt;해 들어가는 &lt;b&gt;작은 함수&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;어디에?&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;ISA별 &lt;b&gt;인터럽트 벡터 테이블&lt;/b&gt;(IVT)에 등록된 시작 주소(벡터)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;무엇을 하냐?&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;① 장치 플래그/데이터 읽기・초기화② 커널/OS에 이벤트 전달(큐에 push)③ 필요 시 후속 작업 예약(DMA 재개, Task wake-up)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;제약&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;bull; &lt;b&gt;짧고 빠르게&lt;/b&gt;: 파이프라인 스톨을 최소화&amp;bull; &lt;b&gt;재진입 안전&lt;/b&gt;: 공유 데이터 보호(락, 원자 연산)&amp;bull; &lt;b&gt;스택 사용 최소&lt;/b&gt;: 컨텍스트 저장 분량 줄이기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;복귀 방법&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;마지막에 iret/er et 처럼 &lt;b&gt;특수 Return 명령&lt;/b&gt;으로 상태 복구 후 원래 PC로 점프&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3️⃣ 명령어 사이클 + 인터럽트 + ISR = 11단계 &amp;ldquo;풀 경로&amp;rdquo;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 단계 핵심 레지스터 이동(&amp;rarr;) 일어나는 일&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1-5&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Fetch-Decode-Execute-Memory-Write Back&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;(PC&amp;rarr;MAR&amp;rarr;MDR&amp;rarr;IR &amp;hellip;)&lt;/td&gt;
&lt;td&gt;정상 파이프라인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Interrupt Check&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;ndash;&lt;/td&gt;
&lt;td&gt;CU가 IRQ 라인 감지&amp;middot;우선순위 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Context Save&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;GPR/PC/FLAGS &amp;rarr; MDR &amp;rarr; Stack&lt;/td&gt;
&lt;td&gt;자동 PUSH (SP 감소)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Vector Fetch&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;IVT[IRQ#] &amp;rarr; MAR &amp;rarr; MDR &amp;rarr; PC&lt;/td&gt;
&lt;td&gt;&lt;b&gt;ISR 시작 주소&lt;/b&gt; 로드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;b&gt;ISR Execute&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;레지스터 &amp;harr; ALU/메모리&lt;/td&gt;
&lt;td&gt;장치 서비스, 플래그 클리어, OS 통지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Context Restore&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Stack &amp;rarr; MDR &amp;rarr; GPR/FLAGS/PC&lt;/td&gt;
&lt;td&gt;POP, 상태 복구&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Return (IRET)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;MDR &amp;rarr; PC&lt;/td&gt;
&lt;td&gt;파이프라인 재시동, 원래 코드 복귀&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의:&lt;/b&gt; OoO 코어에서는 단계 7 전에 ROB가 &amp;ldquo;Precise State&amp;rdquo;가 될 때까지 대기하여 &lt;b&gt;정확한 인터럽트(Precise Interrupt)&lt;/b&gt; 를 보장합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4️⃣ ARMv8-A 예시 ISR 스켈레톤&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;// Vector table에 등록된 타이머 IRQ 핸들러
.align 7
ISR_Timer:
    // --- Context Save (필요 최소)
    stp     x0, x1, [sp, #-16]!

    // --- 실질 서비스
    mrs     x0, cntpct_el0        // 현재 타이머 카운터 읽기
    add     x0, x0, #100000       // 다음 인터벌 설정
    msr     cntp_cval_el0, x0
    mov     w1, #0                // 인터럽트 클리어
    str     w1, [xIC]             // 장치 레지스터

    // --- Context Restore
    ldp     x0, x1, [sp], #16
    eret                        // PC&amp;middot;PSR 복귀
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특징: 통상 &lt;b&gt;수십~수백 사이클&lt;/b&gt; 안에 끝내도록 작성, ISR 내부에서 긴 루프&amp;middot;동적 할당 지양.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5️⃣ 성능&amp;middot;안정성을 지키는 실무 팁&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항목 권장 사항&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ISR 실행 시간&lt;/td&gt;
&lt;td&gt;➜ 짧게. 무거운 작업은 &lt;b&gt;작업 큐/스레드&lt;/b&gt;에 넘기기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;중첩 인터럽트&lt;/td&gt;
&lt;td&gt;➜ 우선순위 컨트롤러(APIC, GIC)로 관리, 필요 시 마스크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;공유 데이터&lt;/td&gt;
&lt;td&gt;➜ &lt;b&gt;락&amp;middot;원자 연산&lt;/b&gt; 또는 &lt;b&gt;임계구역 금지&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;빈번한 IRQ&lt;/td&gt;
&lt;td&gt;➜ &lt;b&gt;배치(Batching)&lt;/b&gt;, &lt;b&gt;폴링 전환&lt;/b&gt; 고려 (예: NAPI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;디버깅&lt;/td&gt;
&lt;td&gt;➜ perf record -e irq:* - 인터럽트 빈도 분석&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한 문장으로 총정리&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ISR&lt;/b&gt;은 인터럽트 발생 시 CPU가 뛰어드는 &amp;ldquo;미니 함수&amp;rdquo;로, &lt;b&gt;컨텍스트 자동 저장 &amp;rarr; 벡터 점프 &amp;rarr; 빠른 장치 서비스 &amp;rarr; IRET 복귀&lt;/b&gt; 과정 속에 존재한다. 이를 명령어 파이프라인 뒤에 삽입한 &lt;b&gt;인터럽트 사이클&lt;/b&gt; 덕분에 CPU는 외부 이벤트에 실시간으로 응답하면서도, 원래 실행 흐름과 데이터 일관성을 안전하게 유지할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>다시 정리하는 CS 이론/컴퓨터구조론</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/147</guid>
      <comments>https://develop-4-art.tistory.com/147#entry147comment</comments>
      <pubDate>Thu, 1 May 2025 17:14:09 +0900</pubDate>
    </item>
    <item>
      <title>[컴구론] ALU, 제어장치, 레지스터</title>
      <link>https://develop-4-art.tistory.com/146</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yt0cF/btsNKAr4x57/p5cCkHhoJWp7UQkTMFo9gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yt0cF/btsNKAr4x57/p5cCkHhoJWp7UQkTMFo9gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yt0cF/btsNKAr4x57/p5cCkHhoJWp7UQkTMFo9gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyt0cF%2FbtsNKAr4x57%2Fp5cCkHhoJWp7UQkTMFo9gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;389&quot; height=&quot;389&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ALU ‧ 제어장치(CU) ‧ 레지스터 ― &amp;ldquo;CPU 속 3대 핵심 블록&amp;rdquo; 심화 노트&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;회로 세부&amp;middot;최신 마이크로아키텍처 관점&lt;/b&gt;을 덧붙여 정리했습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. ALU (Arithmetic Logic Unit)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인트 상세 설명&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;역할&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;정수 덧셈&amp;middot;뺄셈&amp;middot;증가/감소, 논리 AND/OR/XOR/NOT, 시프트&amp;middot;회전, 비교, 비트 카운트 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;하드웨어 핵심&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;bull; 가산기: &lt;b&gt;Carry-Lookahead Adder&lt;/b&gt;(CLA)로 리플-캐리 지연을 O(log n)로 단축 (&lt;a href=&quot;https://pages.hmc.edu/harris/class/e85/old/fall19/lect8.pdf?utm_source=chatgpt.com&quot;&gt;[PDF] Lecture 8:&lt;/a&gt;, [Carry Look-Ahead Adder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;입력&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;ⓐ 두(또는 그 이상) 오퍼랜드 &amp;rarr; 레지스터 파일에서 읽음 ⓑ &lt;b&gt;연산 선택 코드&lt;/b&gt; &amp;rarr; 제어장치가 ALU Control 라인으로 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;출력&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;ⓐ 결과 &amp;rarr; 레지스터 파일로 Write-back ⓑ &lt;b&gt;플래그 비트&lt;/b&gt; &amp;rarr; 플래그 레지스터로 전달 (분기&amp;middot;예외 결정)&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;모던 확장&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&amp;bull; &lt;b&gt;SIMD/Vector ALU&lt;/b&gt; (AVX-512, SVE2) : 128-2048 bit 병렬 &amp;bull; &lt;b&gt;FMA ( a &amp;times; b + c )&lt;/b&gt; 유닛: FP*&amp;middot;AI 가속 핵심 &amp;bull; &lt;b&gt;암호/비트 조작 확장&lt;/b&gt; : RISC-V B&amp;middot;AES-NI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 제어장치 (CU, Control Unit)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구분 Hard-wired CU Micro-programmed CU&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;원리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;상태 머신 + PLA로 &lt;b&gt;고정&lt;/b&gt; 로직&lt;/td&gt;
&lt;td&gt;명령어 &amp;rarr; &lt;b&gt;마이크로코드 ROM&lt;/b&gt; &amp;rarr; 제어신호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;속도&amp;uarr;, 전력&amp;darr;&lt;/td&gt;
&lt;td&gt;명령어 세트 수정&amp;middot;확장 용이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;단점&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;ISA 변경 시 재설계 필요&lt;/td&gt;
&lt;td&gt;ROM 지연, 패치 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;현대 사용처&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;RISC 코어&amp;middot;DSP&lt;/td&gt;
&lt;td&gt;x86 디코더, 복잡 ISA 백엔드 (&lt;a href=&quot;https://www.geeksforgeeks.org/difference-between-hardwired-and-micro-programmed-control-unit-set-2/?utm_source=chatgpt.com&quot;&gt;Difference between Hardwired and Micro-programmed Control Unit&lt;/a&gt;, &lt;a href=&quot;https://www.geeksforgeeks.org/computer-organization-hardwired-vs-micro-programmed-control-unit/?utm_source=chatgpt.com&quot;&gt;Hardwired v/s Micro-programmed Control Unit - GeeksforGeeks&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입&amp;middot;출력 신호&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;입력&lt;/b&gt;: IR(명령어), 플래그, 클럭, 외부 인터럽트&amp;middot;DMA 요청&lt;/li&gt;
&lt;li&gt;&lt;b&gt;출력&lt;/b&gt;: ALU Op, 레지스터 읽기/쓰기, 버스 RD/WR, 분기 PC mux 선택 등&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파이프라인 제어&lt;/b&gt;: Hazard 스톨, 분기 Flush, Reorder Buffer Commit( OoO )&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 레지스터(Registers) &amp;ndash; &amp;ldquo;CPU 한가운데 캐시&amp;rdquo;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범주 대표 비고&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;프로그램 제어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;PC/RIP, IR, &lt;b&gt;CSR&lt;/b&gt; (status &amp;amp; control)&lt;/td&gt;
&lt;td&gt;흐름&amp;middot;권한&amp;middot;예외 관리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;데이터&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;GPR&lt;/b&gt; (x86 EAX&amp;hellip; / ARM X0-X30)&lt;/td&gt;
&lt;td&gt;산술&amp;middot;주소&amp;middot;임시 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;메모리 연동&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;MAR, MDR/MBR&lt;/td&gt;
&lt;td&gt;버스 인터페이스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;스택&amp;middot;프레임&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;SP, BP/FP&lt;/td&gt;
&lt;td&gt;함수 호출 규약&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;플래그&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;EFLAGS/APS-R/NZCV&lt;/td&gt;
&lt;td&gt;ALU 상태 비트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;특수&amp;middot;대형&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;SIMD(XMM/YMM/Z), &lt;b&gt;Vector&lt;/b&gt;(V0..), &lt;b&gt;System&lt;/b&gt;(TTBR, TPIDR)&lt;/td&gt;
&lt;td&gt;128-2048 bit, MMU&amp;middot;스레드 ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;마이크로-아키텍처&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;물리 레지스터&lt;/b&gt; + Rename Map&lt;/td&gt;
&lt;td&gt;OoO 코어당 150-300개&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 현대 코어는 &lt;b&gt;다중 포트 레지스터 파일&lt;/b&gt;(읽기 6-10, 쓰기 4-6)과 &lt;b&gt;포워딩 네트워크&lt;/b&gt;를 갖춰 1 사이클 내 데이터 의존을 해결한다. (&lt;a href=&quot;https://azeria-labs.com/arm-data-types-and-registers-part-2/?utm_source=chatgpt.com&quot;&gt;ARM Data Types and Registers (Part 2) - Azeria Labs&lt;/a&gt;, &lt;a href=&quot;https://www.geeksforgeeks.org/general-purpose-registers/?utm_source=chatgpt.com&quot;&gt;General Purpose Registers | GeeksforGeeks&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 세 블록이 협업하는 한 사이클 예 (RISC-V ADD x5,x1,x2)&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Fetch&lt;/b&gt; &amp;mdash; IR ⇠ I-Cache, PC + 4&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Decode / CU&lt;/b&gt; &amp;mdash; opcode=ADD &amp;rarr; ALU_OP=ADD, src= x1/x2, dst=x5&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Register Read&lt;/b&gt; &amp;mdash; x1, x2 값 ALU 입력으로 포워딩&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ALU Execute&lt;/b&gt; &amp;mdash; 합산, 플래그 생성(ZF 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Write-back&lt;/b&gt; &amp;mdash; 결과 &amp;rarr; x5, CU가 분기 여부 판단(플래그 활용)&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이프라인 상태에 따라 위 단계들이 &lt;b&gt;컨베이어&lt;/b&gt;처럼 엇갈려 흐르며, CU가 Hazard를 감시하고 ALU는 매 싸이클 다른 연산을 수행할 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 학습 팁 &amp;amp; 실습 아이디어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무엇을 해보나 얻는 인사이트&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;가산기 RTL&lt;/b&gt;(Verilog) 시뮬레이션 + CLA 변환&lt;/td&gt;
&lt;td&gt;리플-캐리 &amp;harr; CLA 지연&amp;middot;게이트 수 비교&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gdb / lldb 로 레지스터 덤프&lt;/td&gt;
&lt;td&gt;함수 콜 전후 PC, SP, FP 변화 추적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Intel&amp;reg; VTune / perf&lt;/b&gt; 로 &amp;micro;op &amp;amp; stall 분석&lt;/td&gt;
&lt;td&gt;ALU 포트 사용률&amp;middot;레지스터 재네임 관찰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;RISC-V 툴체인&lt;/b&gt;으로 objdump&lt;/td&gt;
&lt;td&gt;레지스터-중심 3-Address 명령 형식 체험&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한 줄 정리&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ALU&lt;/b&gt;는 &amp;ldquo;계산+플래그&amp;rdquo;, &lt;b&gt;CU&lt;/b&gt;는 &amp;ldquo;시퀀서+신호&amp;rdquo;, &lt;b&gt;레지스터&lt;/b&gt;는 &amp;ldquo;초고속 기억&amp;rdquo;이다.&lt;br /&gt;세 블록이 파이프라인&amp;middot;OoO&amp;middot;SIMD 같은 현대 기법과 엮이면서, 10⁹ 회/초 이상의 명령 처리가 가능해진다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>다시 정리하는 CS 이론/컴퓨터구조론</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/146</guid>
      <comments>https://develop-4-art.tistory.com/146#entry146comment</comments>
      <pubDate>Thu, 1 May 2025 17:05:04 +0900</pubDate>
    </item>
    <item>
      <title>[컴구론] 명령어의 구조</title>
      <link>https://develop-4-art.tistory.com/145</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yt0cF/btsNKAr4x57/p5cCkHhoJWp7UQkTMFo9gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yt0cF/btsNKAr4x57/p5cCkHhoJWp7UQkTMFo9gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yt0cF/btsNKAr4x57/p5cCkHhoJWp7UQkTMFo9gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyt0cF%2FbtsNKAr4x57%2Fp5cCkHhoJWp7UQkTMFo9gk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;389&quot; height=&quot;389&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;0. 큰 그림 잡기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**명령어(Instruction)**는 CPU가 이해-실행할 수 있도록 &lt;b&gt;비트 수준으로 인코딩&lt;/b&gt;된 최소 단위입니다.&lt;br /&gt;가장 일반적인 틀은&lt;/p&gt;
&lt;pre class=&quot;coq&quot;&gt;&lt;code&gt;┌──────────┬──────────┬────────────┐
│  Opcode  │ Operand  │  &amp;hellip; Fields  │
└──────────┴──────────┴────────────┘
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Opcode (Operation Code)&lt;/b&gt; &amp;ndash; &amp;ldquo;무슨 연산을 하라&amp;rdquo;는 코드&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Operand Field(s)&lt;/b&gt; &amp;ndash; &amp;ldquo;어떤 데이터/주소에 대해 하라&amp;rdquo;는 정보&lt;/li&gt;
&lt;li&gt;(선택) &lt;b&gt;Address-mode bits, Immediate, Condition, Shift, Predication&lt;/b&gt; 등 추가 메타데이터 (&lt;a href=&quot;https://www.tutorialspoint.com/what-are-instruction-formats?utm_source=chatgpt.com&quot;&gt;Instruction Formats in Computer Architecture - Tutorialspoint&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 주소 개수(0-&amp;middot;1-&amp;middot;2-&amp;middot;3 Address)로 본 명령어 형식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;주소 개수(0-&amp;middot;1-&amp;middot;2-&amp;middot;3 Address)란?&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**명령어 인코딩에 &amp;ldquo;주소(오퍼랜드)를 몇 개까지 명시적으로 넣느냐&amp;rdquo;**를 말한다.&lt;br /&gt;스택 꼭대기(TOS)&amp;middot;누산기(ACC)처럼 &lt;b&gt;암시적(implicit) 피연산자&lt;/b&gt;는 개수에 포함되지 않는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 같은 식을 네 가지 형식으로 번역해 보기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표 식 Z &amp;larr; A + B&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형식 기계 사고방식 어셈블리 예 메모 접근 수&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;0-Address&lt;/b&gt;(스택)&lt;/td&gt;
&lt;td&gt;&amp;ldquo;스택에 올리고 계산&amp;rdquo;&lt;/td&gt;
&lt;td&gt;PUSH APUSH BADD ; TOS = A+BPOP Z&lt;/td&gt;
&lt;td&gt;3 load + 1 store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;1-Address&lt;/b&gt;(누산기)&lt;/td&gt;
&lt;td&gt;&amp;ldquo;ACC + M[addr]&amp;rdquo;&lt;/td&gt;
&lt;td&gt;LOAD A ; ACC=AADD BSTORE Z&lt;/td&gt;
&lt;td&gt;2 load + 1 store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;2-Address&lt;/b&gt;(레지스터/메모리 혼용)&lt;/td&gt;
&lt;td&gt;결과가 첫 피연산자에 덮어씀&lt;/td&gt;
&lt;td&gt;MOV R1,AADD R1,B ; R1=R1+BMOV Z,R1&lt;/td&gt;
&lt;td&gt;1 load + 1 store&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;3-Address&lt;/b&gt;(RISC)&lt;/td&gt;
&lt;td&gt;&amp;ldquo;src1 + src2 &amp;rarr; dst&amp;rdquo;&lt;/td&gt;
&lt;td&gt;LD R1,ALD R2,BADD R3,R1,R2ST Z,R3&lt;/td&gt;
&lt;td&gt;2 load + 1 store&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0-Address는 스택 깊이만 관리하면 코드가 가장 짧고, 3-Address는 명령 하나가 &amp;lsquo;입력 2 + 출력 1&amp;rsquo;을 모두 지정해 &lt;b&gt;병렬성 &amp;amp; 가독성&lt;/b&gt;이 최고다. (&lt;a href=&quot;https://www.geeksforgeeks.org/computer-organization-instruction-formats-zero-one-two-three-address-instruction/?utm_source=chatgpt.com&quot;&gt;Instruction Formats (Zero, One, Two and Three Address Instruction)&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 형식별 하드웨어&amp;middot;성능 특징&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형식 하드웨어 조직 장점 단점&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;0-Addr&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;스택 레지스터 파일&lt;/b&gt; (TOS 캐시)&lt;/td&gt;
&lt;td&gt;코드 밀도&amp;uarr;, 디코더 단순&lt;/td&gt;
&lt;td&gt;스택 톱니 구동 &amp;rarr; 랜덤 접근 어려움, 반복 루프 오버헤드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;1-Addr&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;ACC + 일반 메모리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;인코딩 짧음, HW 간단&lt;/td&gt;
&lt;td&gt;ACC 레이턴시가 곧 병목, 결과 덮어쓰기 많음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;2-Addr&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;범용 레지스터 파일&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;명령 수 적음, 코드 밀도 괜찮음&lt;/td&gt;
&lt;td&gt;피연산자 1개 파괴 &amp;rarr; 추가 MOV 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;3-Addr&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;레지스터 파일 + Load/Store 전용&lt;/td&gt;
&lt;td&gt;레지스터 재사용 융통성, 파이프라인 쉽다&lt;/td&gt;
&lt;td&gt;인코딩 비트 폭&amp;uarr; &amp;rarr; 코드 크기&amp;uarr;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 실존 ISA 예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;형식 대표 ISA / CPU 샘플 명령&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0-Addr&lt;/td&gt;
&lt;td&gt;&lt;b&gt;JVM 바이트코드&lt;/b&gt;, Forth, 일부 DSP&lt;/td&gt;
&lt;td&gt;iadd, fsub, dup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1-Addr&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Intel 8080/8051&lt;/b&gt;, PDP-8&lt;/td&gt;
&lt;td&gt;ADD 0x40(ACC&amp;larr;ACC+M[0x40]) (&lt;a href=&quot;https://www.lkouniv.ac.in/site/writereaddata/siteContent/202003271505203215anshu_singh_instructional_formats.pdf?utm_source=chatgpt.com&quot;&gt;[PDF] Instruction Formats (Zero, One, Two and Three Address Instruction)&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2-Addr&lt;/td&gt;
&lt;td&gt;&lt;b&gt;x86 (ALU)&lt;/b&gt;, VAX&lt;/td&gt;
&lt;td&gt;ADD [EAX], EBX (dst도 피연산자)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3-Addr&lt;/td&gt;
&lt;td&gt;&lt;b&gt;MIPS, RISC-V, ARM A-profile&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;ADD x3,x1,x2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. 고급 주의 사항&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;혼합 사용&lt;/b&gt; &amp;ndash; x86은 메모리-to-레지스터 2-Address 모델이지만, 스택(0-Addr)인 FPU 명령도 갖는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;VLIW&amp;middot;GPU&lt;/b&gt; &amp;ndash; 3-Address 기반이면서 &amp;ldquo;슬롯&amp;rdquo;에 4-8개의 명령을 묶어 전송(VLIW)하거나 4-Address(FMA)처럼 연산+상수+오프셋까지 묶기도 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴파일러 관점&lt;/b&gt; &amp;ndash; SSA 중간 표현은 3-Address에 대응, 스택머신 바이트코드(JVM/WebAssembly)는 0-Address 패러다임을 따른다. JIT 단계에서 레지스터 배분이 필요.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;핵심 한 줄&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주소 개수 = 명령이 노출 하는 피연산자 슬롯 수&lt;/b&gt;. 0-Addr(스택) &amp;harr; 3-Addr(RISC) 스펙트럼 어디에 두느냐가 &lt;b&gt;코드 밀도&amp;middot;디코더 복잡도&amp;middot;병렬성&lt;/b&gt;을 결정한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 주소 지정(Addressing) 모음 &amp;mdash; &amp;ldquo;오퍼랜드를 어디서 찾나?&amp;rdquo;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분류 비트 구성&amp;middot;동작 장점 단점&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;즉시(Immediate)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;데이터가 명령어 안&lt;/td&gt;
&lt;td&gt;가장 빠름&lt;/td&gt;
&lt;td&gt;데이터 크기 제한&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;직접(Absolute)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;필드=실제 주소&lt;/td&gt;
&lt;td&gt;해석 단순&lt;/td&gt;
&lt;td&gt;32-64 bit 주소 길이 부담&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;간접(Indirect)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;필드&amp;rarr;메모리&amp;rarr;실주소&lt;/td&gt;
&lt;td&gt;긴 주소 가능&lt;/td&gt;
&lt;td&gt;메모리 2회 접근&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;레지스터 직접&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;필드=레지스터 번호&lt;/td&gt;
&lt;td&gt;메모리 접근 X&lt;/td&gt;
&lt;td&gt;레지스터 한정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;레지스터 간접&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;레지스터 값=주소&lt;/td&gt;
&lt;td&gt;짧은 코드+유연&lt;/td&gt;
&lt;td&gt;1회 메모리만큼 지연&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;변위(베이스+오프셋)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Base Reg + Disp&lt;/td&gt;
&lt;td&gt;코드 이식성, PIC&lt;/td&gt;
&lt;td&gt;오프셋 비트 제한&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;PC-상대&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;PC + Disp (분기)&lt;/td&gt;
&lt;td&gt;코드 위치 독립&lt;/td&gt;
&lt;td&gt;범위 제한&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;묵시/스택&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;필드 없음, TOS 고정&lt;/td&gt;
&lt;td&gt;코드 가장 짧음&lt;/td&gt;
&lt;td&gt;HW가 스택 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 고정 vs 가변 길이&amp;middot;RISC vs CISC&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ISA 길이 디코더 난이도 대표&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;고정 32 bit&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;한 번에 4 B 읽으면 완전&lt;/td&gt;
&lt;td&gt;파이프라인 설계 단순&lt;/td&gt;
&lt;td&gt;MIPS, RISC-V, ARMv8-A32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;가변 1-15 B&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Prefix&amp;middot;Opcode&amp;middot;ModRM&amp;middot;SIB&amp;hellip;&lt;/td&gt;
&lt;td&gt;x86 1-cycle 디코더 어려움 &amp;rarr; &amp;mu;-op Cache&lt;/td&gt;
&lt;td&gt;x86-64, 68000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;VLIW 64-256 bit&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;다수 연산을 한 &amp;ldquo;슬롯&amp;rdquo;에&lt;/td&gt;
&lt;td&gt;HW 덜 복잡, SW스케줄러&amp;uarr;&lt;/td&gt;
&lt;td&gt;Itanium, DSP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Compressed 16 bit&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;저전력&amp;middot;임베디드 코드 밀도&amp;uarr;&lt;/td&gt;
&lt;td&gt;디코더 확장 필요&lt;/td&gt;
&lt;td&gt;RISC-V C, ARM Thumb&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*가변 길이 ISA(x86)*는 코드 밀도는 좋지만 디코더 전력과 분기 타겟 정렬이 복잡합니다. 반대로 고정 길이 RISC는 단순화된 디코더 덕분에 파이프라인을 깊게 하고 슈퍼스칼라 폭을 넓히기 쉽습니다. (&lt;a href=&quot;https://www.tutorialspoint.com/what-are-instruction-formats?utm_source=chatgpt.com&quot;&gt;Instruction Formats in Computer Architecture - Tutorialspoint&lt;/a&gt;)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 실전 인코딩 예시&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-1. MIPS R-Type 32 bit&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;31        26 25    21 20    16 15  11 10 6 5   0
┌─────────┬────────┬────────┬──────┬────┬─────┐
│ opcode  │   rs   │   rt   │  rd  │shmt│funct│
└─────────┴────────┴────────┴──────┴────┴─────┘
ADD $t0,$t1,$t2  &amp;rarr;  000000 01001 01010 01000 00000 100000
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;opcode=0 &amp;rArr; &amp;ldquo;R-type&amp;rdquo;, funct=0x20 &amp;rArr; ADD&lt;/li&gt;
&lt;li&gt;파이프라인은 &lt;b&gt;opcode 만 읽어도 길이&amp;middot;필드 위치 고정&lt;/b&gt;이라 1-cycle Decode가 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4-2. x86-64 가변 길이&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;66      0F      6F      44    24  10
└─prefix┘└opcode┘└ModRM┘└SIB┘└disp┘
MOVDQA XMM0, [RSP+16]
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Prefix(0x66)&lt;/b&gt;: SIMD size 바꿈&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Opcode(0F 6F)&lt;/b&gt; + &lt;b&gt;ModRM(44)&lt;/b&gt; + &lt;b&gt;SIB(24)&lt;/b&gt; + &lt;b&gt;Disp(10)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;길이가 명령마다 달라 프런트엔드가 prefix 탐색 &amp;rarr; 길이 계산 &amp;rarr; 큐 과정을 거침. 최신 코어는 &lt;b&gt;&amp;mu;-op Cache&lt;/b&gt;로 복잡성을 완화. (&lt;a href=&quot;https://www.geeksforgeeks.org/computer-organization-instruction-formats-zero-one-two-three-address-instruction/?utm_source=chatgpt.com&quot;&gt;Instruction Formats (Zero, One, Two and Three Address Instruction)&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 형식이 CPU 성능에 미치는 영향&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하드웨어 관점 형식 선택이 주는 영향&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;디코더 폭&amp;middot;전력&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;고정 길이 = 단순 하드와이어 / 가변 = 복잡한 테이블-루프&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;파이프라인 깊이&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;고정 길이일수록 FrontEnd 병목 완화 &amp;rarr; 높은 GHz 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;캐시 효율&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;코드 밀도&amp;uarr;(가변) &amp;harr; I-Cache miss&amp;darr; vs Fetch-Align 복잡&amp;uarr;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;분기 타깃 계산&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;고정=타깃 정렬 쉬움, 가변=BTB에 길이 예측 로직 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;JIT&amp;middot;트랜스코더&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;중간 IR 변환 시 필드 위치가 명확할수록 최적화 용이&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 최신 트렌드 &amp;amp; 학습 포인트&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;RISC-V&lt;/b&gt;: 기본 32 bit + 16 bit C-extension &amp;rarr; IoT부터 서버까지 스케일.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ARMv9 SVE2/SME&lt;/b&gt;: 길이 불특정(128-2048 bit) 벡터 레지스터 &amp;rarr; &amp;ldquo;Scalable ISA&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Code-Size 압축&lt;/b&gt;: Apple M-시리즈는 하드웨어에서 x86-&amp;gt;&amp;mu;-op 트랜스코드로 캐시 압축.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Macro-op Fusion&lt;/b&gt;: x86의 CMP+Jcc &amp;rarr; 1 &amp;mu;-op으로 합쳐 디코더 대역 절감.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Security Tagging&lt;/b&gt;: ARM MTE, CHERI처럼 &lt;b&gt;캡슐 태그&lt;/b&gt;를 명령 포맷에 추가해 메모리 안전성 확보.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리 한 줄&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;명령어 포맷&lt;/b&gt;은 &amp;ldquo;어떤 비트가 무엇을 뜻하느냐&amp;rdquo;를 정의해 &lt;b&gt;디코더 복잡도&amp;middot;코드 밀도&amp;middot;파이프라인 효율&lt;/b&gt;을 좌우하며, RISC-류의 고정 길이와 x86-류의 가변 길이는 각각 &lt;b&gt;하드웨어 단순화 vs 코드 압축&lt;/b&gt;이라는 트레이드오프 상에서 진화하고 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>다시 정리하는 CS 이론/컴퓨터구조론</category>
      <author>LazySonic</author>
      <guid isPermaLink="true">https://develop-4-art.tistory.com/145</guid>
      <comments>https://develop-4-art.tistory.com/145#entry145comment</comments>
      <pubDate>Thu, 1 May 2025 16:57:36 +0900</pubDate>
    </item>
  </channel>
</rss>