<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jm450_.log</title>
        <link>https://velog.io/</link>
        <description>그냥 하자</description>
        <lastBuildDate>Sun, 26 Jul 2026 14:33:30 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jm450_.log</title>
            <url>https://velog.velcdn.com/images/jm450_/profile/e324f59c-f510-4efe-a8bc-a31b3c2f4c31/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jm450_.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jm450_" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[UE] SceneVisibility]]></title>
            <link>https://velog.io/@jm450_/UE-SceneVisibility</link>
            <guid>https://velog.io/@jm450_/UE-SceneVisibility</guid>
            <pubDate>Sun, 26 Jul 2026 14:33:30 GMT</pubDate>
            <description><![CDATA[<p>언리얼 렌더링 파이프라인 중 BeginInit에서 가시성을 판단하여 불필요한 Draw를 줄인다.
해당 문서에서는 가시성 판단 과정을 상세히 기술하겠다.</p>
<h1 id="가시성-파이프라인">가시성 파이프라인</h1>
<p>아래 함수를 통해 3단계를 통해 가시성을 판단한다.</p>
<ul>
<li><code>FVisibilityViewPacket::BeginInitVisibility</code><ul>
<li><code>FrustumCull</code><ul>
<li>프러스텀 + 거리</li>
</ul>
</li>
<li><code>OcclusionCull</code><ul>
<li>HZB / 쿼리</li>
</ul>
</li>
<li><code>ComputeRelevence</code><ul>
<li>드로우/패스 분류</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>가시성 판단에는 아래 4가지 기법을 사용한다.</p>
<ul>
<li><p>비트맵 기반</p>
<ul>
<li>각 뷰가 씬의 프리미티브 수만큼 비트를 가진 Map을 만들고 단계별로 비트를 끔</li>
<li><code>View.PrimitiveVisibilityMap</code>
<img src="https://velog.velcdn.com/images/jm450_/post/b46f6085-a8cd-43da-b737-6685e6144117/image.png" alt=""></li>
</ul>
</li>
<li><p>워드 단위 병렬 분할</p>
<ul>
<li>프리미티브를 32개(1DWORD) 단위로 묶어 태스크에 배분<ul>
<li><a href="https://kim-dj.tistory.com/13">https://kim-dj.tistory.com/13</a></li>
</ul>
</li>
<li><code>WorkerThread</code> x <code>NumFrustumCullTaskPerThread</code>기준으로 태스크 수 결정
<img src="https://velog.velcdn.com/images/jm450_/post/187839cd-8006-4ae3-89d9-8dd68389120d/image.png" alt=""></li>
</ul>
</li>
<li><p>CommandPipe 단계로 연결</p>
<ul>
<li>프러스텀 컬이 끝난 32개 플록을 곧바로 오클루전 큐로 흘려보냄</li>
<li>따라서, 프러스텀 중 오클루전 이미 시작
<img src="https://velog.velcdn.com/images/jm450_/post/325b1e7b-2668-4c36-a8e8-4ed1d39cb9d4/image.png" alt=""></li>
</ul>
</li>
<li><p>AlwaysVisible 최적화</p>
<ul>
<li><code>PrimitivesAlwaysVisibleOffset</code> 이후의 프리미티브는 컬링 테스트 제외
<img src="https://velog.velcdn.com/images/jm450_/post/ac8f3b41-0a81-4c93-9ce3-b85d507bc36b/image.png" alt=""></li>
</ul>
</li>
</ul>
<h2 id="frustumcull">FrustumCull</h2>
<p>View별로 WORD 블록을 돌면서 비트별로 판정한다.
여러 테스트를 진행하며, 비용 낮은 -&gt; 높은 테스트 순서로 진행한다.</p>
<h3 id="1-hideshow-필터">1. Hide/Show 필터</h3>
<ul>
<li>SceneVisibility.cpp:625</li>
<li>View 내부 데이터, 플래그로 판단
<img src="https://velog.velcdn.com/images/jm450_/post/bd0193f6-205d-46a8-b808-ad997d831b46/image.png" alt=""></li>
</ul>
<h3 id="2-프러스텀-교차-판정">2. 프러스텀 교차 판정</h3>
<blockquote>
<p>프리미티브가 View Frusutm 내부에 있는지 확인
<img src="https://velog.velcdn.com/images/jm450_/post/0bd58453-1da9-44bc-9c3e-2818ec71b871/image.png" alt=""></p>
</blockquote>
<ul>
<li>SceneVisibility.cpp:599</li>
<li>프러스텀 내부에 존재하는지 확인한다
<img src="https://velog.velcdn.com/images/jm450_/post/c05d568a-e900-4cc4-80f9-03203b019739/image.png" alt=""></li>
</ul>
<h4 id="custum-culling">Custum Culling</h4>
<ul>
<li>커스텀 Visibility 훅<h4 id="sphere-test-옵션">Sphere test (옵션)</h4>
</li>
<li>Radius와 비교해 빠르게 안보이는 Primitive 마킹<ul>
<li>평면과 Primitive의 Radius를 비교<ul>
<li><a href="https://mathworld.wolfram.com/Point-PlaneDistance.html">https://mathworld.wolfram.com/Point-PlaneDistance.html</a></li>
</ul>
</li>
<li>SIMD를 통해 4개 평면 한번에 계산<ul>
<li><a href="https://rhksgml78.tistory.com/304">https://rhksgml78.tistory.com/304</a>
<img src="https://velog.velcdn.com/images/jm450_/post/4b831637-903a-4abd-9613-318f9f103f83/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="box-vs-8-plane">Box vs 8-plane</h4>
<ul>
<li><strong>IntersectBox8Plane</strong><ul>
<li>평면이 정확히 8개일 때 실행</li>
<li>루프와 경계 브랜치를 제거한 버전</li>
<li>두 SIMD 그룹(4평면씩)으로 실행</li>
<li>옥트리를 통해 내부 판정을 가속할 수 있음
<img src="https://velog.velcdn.com/images/jm450_/post/767d71d1-5edf-4fd3-a8b1-4716dd5c562c/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="3-거리-컬링">3. 거리 컬링</h3>
<ul>
<li>프러스텀 컬링 통과 시에만 테스트
<img src="https://velog.velcdn.com/images/jm450_/post/032525d4-1797-40a1-ad46-43b928b63a9f/image.png" alt=""></li>
</ul>
<h2 id="occlusioncull">OcclusionCull</h2>
<blockquote>
<p>프리미티브가 다른 물체에 가려지지 않았는지 확인
프러스텀+거리 통과시에만 테스트한다.</p>
</blockquote>
<p>이전 프레인 쿼리 결과를 이번 프레임 테스트에 사용하는 것이 특징이다. (temporal)</p>
<ul>
<li>테스트 결과를 기다리지 않음으로 Wait 시간 제거</li>
</ul>
<p><strong>가림 판정</strong></p>
<ul>
<li><p>판정 방법은 총 3개가 있음</p>
<ul>
<li><p>OcclusionFeedback </p>
<ul>
<li>GPU가 지난 프레임에 이 프리미티브의 픽셀을 실제로 그렸는지를 기록한 피드백 버퍼로 판정</li>
</ul>
</li>
<li><p>HZBOcclusion</p>
<ul>
<li><p>이전 프레임 Mipmap Depth Depth로 만든 HZB에 BBox를 한번에 테스트</p>
<ul>
<li><p><a href="https://ebs12373.tistory.com/123">https://ebs12373.tistory.com/123</a>
<img src="https://velog.velcdn.com/images/jm450_/post/8cb4b5fe-dce8-4986-ac9e-86c32189307f/image.png" alt=""></p>
</li>
<li><p>바운드 박스를 스크린공간으로 투영 -&gt; 박스의 가장 가까운 깊이 계산</p>
</li>
<li><p>밉레벨 선택</p>
<ul>
<li>사각형이 약 2×2 텍셀만 덮는 밉 레벨을 고름<ul>
<li>텍셀 4개만 읽으면 됨
<img src="https://velog.velcdn.com/images/jm450_/post/5e155e0f-ffd6-4251-b776-9ed711a12f7e/image.png" alt=""></li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
<li><p>GPU: HZB에 가림 테스트 해서 결과 텍스처에 write
<img src="https://velog.velcdn.com/images/jm450_/post/84132d39-28c1-4dea-ae07-1e3187cc81db/image.png" alt=""></p>
</li>
<li><p>CPU: readback을 잠궈 ResultsBuffer 포인터에 연결
<img src="https://velog.velcdn.com/images/jm450_/post/cf8b3630-3f12-49b1-9381-003422774937/image.png" alt=""></p>
</li>
</ul>
</li>
<li><p>occlusion query</p>
<ul>
<li>픽셀 샘플을 통해 통과 픽셀이 0개라면 가려짐으로 판정</li>
<li>매번 모든 Primitive를 쿼리할 순 없으니 화면점유율로 재쿼리 확률을 계산</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jm450_/post/7c668298-526c-4e3c-b767-e1772809d9cf/image.png" alt=""></p>
<p><strong>이전 프레임 정보 저장</strong></p>
<ul>
<li><p>이전 프레임 HZB 저장
<img src="https://velog.velcdn.com/images/jm450_/post/70aeb647-36a7-4da0-bca0-9d2a87d9683b/image.png" alt=""></p>
</li>
<li><p>오클루전 쿼리 정보 저장
<img src="https://velog.velcdn.com/images/jm450_/post/cebd4206-a155-4648-85b3-c6e80bf3cc6f/image.png" alt=""></p>
</li>
</ul>
<p><strong>쿼리요청</strong></p>
<ul>
<li>오클루전 쿼리는 GPU가 담당하기 때문에 Renderer에서 쿼리 요청을 담당<ul>
<li><code>InstanceOcclusionQueryMask</code>에 결과 저장 (다음 프레임 사용)
<img src="https://velog.velcdn.com/images/jm450_/post/54f34f99-6795-4e43-a3cc-76aa91337716/image.png" alt=""></li>
</ul>
</li>
<li>모든 쿼리를 할 수 없으니 <code>LastPixelsPercentage</code>, <code>LastProvenVisibleTime</code>등을 통해 쿼리 여부를 결정
<img src="https://velog.velcdn.com/images/jm450_/post/753e45ee-9a3d-4013-b15a-391d979ae35c/image.png" alt=""></li>
</ul>
<p><strong>쿼리 결과사용</strong></p>
<ul>
<li>PrimitiveOcclusionHistory에 이전 테스트 요청 시 사용했던 Index를 저장해뒀다가 결과 조회시 사용
<img src="https://velog.velcdn.com/images/jm450_/post/a806db82-bce8-441c-ab05-62b679e0506b/image.png" alt=""></li>
</ul>
<h2 id="relevance">Relevance</h2>
<p>컬링을 통과한 보여줄 프리미티브를 어떤 렌더 패스로 어떻게 그릴지 분류하는 단계이다.
<code>PrimitiveSceneProxy</code>가 머티리얼+설정값을 보고 <code>FPrimitiveViewRelevance</code>에 값을 채운다.</p>
<p><strong>Draw Type</strong></p>
<ul>
<li>NotDrawRelevant<ul>
<li><code>!bDrawRelevance</code>가 설정되어 DrawCommand 제외</li>
</ul>
</li>
<li>Static+Draw/Shadow<ul>
<li>LOD 결정 후 StaticMeshVibilityMap에 마킹<ul>
<li>캐시된 DrawCommand 사용</li>
</ul>
</li>
</ul>
</li>
<li>Dynamic<ul>
<li>AddDynamicPrimitive로 GDME 대상 등록</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jm450_/post/13ca6a83-3f29-4c9c-915a-f26bf3a0a4c4/image.png" alt=""></p>
<h1 id="참고문헌">참고문헌</h1>
<ul>
<li><a href="https://dev.epicgames.com/documentation/unreal-engine/visibility-and-occlusion-culling-in-unreal-engine?lang=ko">https://dev.epicgames.com/documentation/unreal-engine/visibility-and-occlusion-culling-in-unreal-engine?lang=ko</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UE5] DeferredRenderer]]></title>
            <link>https://velog.io/@jm450_/UE5-DefferedRenderer</link>
            <guid>https://velog.io/@jm450_/UE5-DefferedRenderer</guid>
            <pubDate>Sun, 19 Jul 2026 15:27:51 GMT</pubDate>
            <description><![CDATA[<h1 id="디퍼드-렌더링">디퍼드 렌더링</h1>
<h2 id="문제-상황">문제 상황</h2>
<p>포워드 렌더 시 각 메시를 래스터화 한다.
이때, 픽셀에 영향을 미치는 라이트를 순회하게 되면서 <code>픽셀 수 × 픽셀당 라이트 수</code>에 비례하는 비용이 발생한다.
따라서, 최종적으로 보이지 않는 픽셀까지 라이팅을 수행하는 불필요한 연산이 발생한다.</p>
<h2 id="해결-방안">해결 방안</h2>
<p>디퍼드에서는 해당 계산을 분리한다.</p>
<h3 id="1-base-pass">1. Base Pass</h3>
<ul>
<li>메시를 한 번 래스터화하면서 라이팅은 수행하지 않는다.</li>
<li>대신 머티리얼 속성(베이스 컬러, 노멀, 메탈릭, 스페큘러, 러프니스, 셰이딩 모델 등)을 화면 크기의 여러 렌더 타깃(G-Buffer)에 저장한다.</li>
</ul>
<h3 id="2-lights">2. Lights</h3>
<ul>
<li>G-Buffer를 읽어 화면 공간(Screen Space)에서 라이팅을 수행한다.</li>
<li>각 화면 픽셀의 G-Buffer 정보를 이용해 모든 라이트의 영향을 계산하고 결과를 누적하여 최종 색상을 생성한다.</li>
<li>이 과정은 이미 화면에 보이는 픽셀만 대상으로 수행되므로, 기하(Geometry)의 복잡도와 독립적으로 라이팅을 처리할 수 있다.</li>
</ul>
<h2 id="최종-문제-해결">최종 문제 해결</h2>
<p>라이팅 비용은 화면에 보이는 픽셀 수와 라이트 수에 주로 영향을 받으며, 메시를 다시 렌더링하지 않기 때문에 기하 복잡도와 분리된다.</p>
<h1 id="deferredshadingscenerendererrender-pipeline">DeferredShadingSceneRenderer::Render Pipeline</h1>
<h2 id="1-initviews">1. InitViews</h2>
<ul>
<li>프레임 시작에서 뷰 별로 가시성을 결정</li>
<li>프러스텀, 오클루전 등을 통해 가시여부 판단</li>
<li>동적 셰도우, 라이트가시성, 메시 드로우 커멘드 준비 뷰 유니폼 버퍼 구성</li>
</ul>
<h2 id="2-prepass">2. PrePass</h2>
<ul>
<li>불투명 지오메트리의 깊이만 먼저 그려 깊이 버퍼를 채움</li>
<li>이후 BasePass에서 가려질 픽셀의 PS를 GPU의 early-Z 테스트에서 사전 컬링<ul>
<li>BasePass의 오버드로우를 줄임
<img src="https://velog.velcdn.com/images/jm450_/post/18d5271d-061b-426e-8a57-d7ca22986faa/image.png" alt=""></li>
</ul>
</li>
</ul>
<h2 id="3-basepass">3. BasePass</h2>
<ul>
<li>불투면/마스트 메시를 래스터화하면서 <strong>라이팅하지 않고</strong> 머티리얼 속성을 MRT(Multiple RenderTargets)로 GBuffer에 출력
<img src="https://velog.velcdn.com/images/jm450_/post/2267e559-7635-490d-a98f-b23a7b714927/image.png" alt=""></li>
</ul>
<h2 id="4-shadowdepths">4. ShadowDepths</h2>
<ul>
<li>라이팅 전 그림자 정보를 준비하는 단계</li>
<li>각 그림자 캐스팅 라이트에 대해 라이트 시점의 깊이를 그려 셰도우 깊이맵을 만듦 (Virtual Shadow Map)</li>
<li>라이팅 단계에서 라이트 별로 이 깊이 맵을 샘플링해 가시성을 구함
<img src="https://velog.velcdn.com/images/jm450_/post/86ae76ac-9d5a-4ab9-9c03-acce5bceedb0/image.png" alt=""></li>
</ul>
<h2 id="5-light">5. Light</h2>
<ul>
<li>GBuffer를 읽어 화면 공간에서 라이트를 SceneColor에 가산 누적</li>
<li>픽셀당 한번의 GBuffer 디코드 + 라이트별 누적 계산이 필요
<img src="https://velog.velcdn.com/images/jm450_/post/50fc9395-7f03-4a20-a58e-57eb594b5903/image.png" alt=""></li>
</ul>
<h2 id="6-translucency">6. Translucency</h2>
<ul>
<li>GBuffer에 반투명을 인코딩할 수 없으니 (여러 표면이 한 픽셀에 겹침) Forward 방식으로 그림</li>
<li>라이팅 된 후 SceneColor 에 블렌딩 하는 방식</li>
</ul>
<h2 id="7-postprocess">7. PostProcess</h2>
<ul>
<li>라이팅 된 HDR Scene Color에 적용되는 패스</li>
<li>MotionBlur, DOF, Bloom, ToneMapping 등</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jm450_/post/908d04c1-d7ce-4b5b-8f2f-d206dc7d55ce/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UE5] Volume&Brush]]></title>
            <link>https://velog.io/@jm450_/UE5-VolumeBrush</link>
            <guid>https://velog.io/@jm450_/UE5-VolumeBrush</guid>
            <pubDate>Sun, 12 Jul 2026 14:42:24 GMT</pubDate>
            <description><![CDATA[<p>언리얼에서 제공하는 AVolume이 기하를 표현하는 방법을 살펴보자</p>
<h1 id="1-avolume">1. AVolume</h1>
<p>볼륨은 3D 영역을 구획하는 액터로 실제 기하 정보는 부모 클래스인 ABrush에 존재한다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/2b51f5ea-6ec1-4898-88f5-4094337f8b4c/image.png" alt=""></p>
<h1 id="2-ubrushcomponent">2. UBrushComponent</h1>
<p>ABrush가 실제로 사용하는 PrimitiveComponent로 렌더링/물리(충돌)을 담당한다.</p>
<p>Model을 하드레퍼런싱 하고 있는데, Model 정보가 변경 되면 내부 정보를 싱크 맞추기 위해 업데이트한다.</p>
<h2 id="21-ubodysetup">2.1. UBodySetup</h2>
<p>Physics가 사용하는 Collision정보이다.
<code>UBrushComponent::BuildSimpleBrushCollision()</code> 를 통해 Model 정보를 빌드한다.
<img src="https://velog.velcdn.com/images/jm450_/post/989fe2d7-8b67-458c-904b-6a3e8a71ab61/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/bc16a844-d371-40b2-b18b-ab5435801a42/image.png" alt=""></p>
<h2 id="22-fkaggregategeom">2.2. FKAggregateGeom</h2>
<p>단순 Collision Shape 집합으로 Box, Sphere, Capsule, Convex로 구분해 분류한다.
해당 정보를 통해 Chaos 물리를 사용한다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/432917c2-9469-4f68-b46d-5f766e4c527f/image.png" alt="">
리프까지 내려간 뒤 AddConvex를 하는것을 볼 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/7590bf9a-996c-47ff-9030-67d4902b63cf/image.png" alt=""></p>
<h1 id="3-umodel">3. UModel</h1>
<p>Brush의 실제 BSP 지오메트리 저장소로 모든 Polygon정보와 BSPTree를 구성한다.</p>
<h2 id="31-upolys">3.1. UPolys</h2>
<p>원본 Polygon 정보를 그대로 보관한다. 
이는 지오메트리 편집 시 사용된다. (따라서 에디터 전용이다.)</p>
<p>해당 정보를 FBspNode로 변환하여 런타임에 사용된다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/1a4e9823-a880-4890-b76f-2b5fce4b4129/image.png" alt="">
<img src="https://velog.velcdn.com/images/jm450_/post/f44bd18f-e656-4180-a02c-271fdbb79573/image.png" alt=""></p>
<h2 id="32-fbspnode">3.2. FBspNode</h2>
<p>BSP Tree의 노드로 UPolys를 BspBuild하여 Node로 저장한다.</p>
<h2 id="33-bspbuild과정">3.3. BspBuild과정</h2>
<p><img src="https://velog.velcdn.com/images/jm450_/post/be820dfc-bcd7-469d-82a2-7ebf0fc853c9/image.png" alt="">
<img src="https://velog.velcdn.com/images/jm450_/post/498b6951-efac-4ee8-bad6-25f324412696/image.png" alt=""></p>
<h1 id="4-bspbinary-space-partitioning">4. BSP(Binary Space Partitioning)</h1>
<p>공간 전체를 평면 하나로 두 쪽으로 자르는 일을 재귀적으로 반복해서, 공간을 이진 트리로 표현하는 기법</p>
<h2 id="41-재귀-분할--이진-트리">4.1. 재귀 분할 = 이진 트리</h2>
<p>평면 하나는 공간을 정확히 두 반공간(front/back)으로 나눈다. 
각 반공간을 또 자르면 트리가 되고, 내부 노드는 분할 평면, 리프는 더 이상 자르지 않은 공간 조각이 된다. 
평면으로만 잘랐으므로 리프 영역은 항상 볼록(convex)하다.
<img src="https://velog.velcdn.com/images/jm450_/post/c3082dba-d187-49da-a0eb-ae85fc15f16c/image.png" alt=""></p>
<h2 id="42-점-위치-판정">4.2. 점 위치 판정</h2>
<p>&quot;점 P가 어느 조각에 있나?&quot;는 루트부터 평면 부호 검사(앞? 뒤?)만 반복하면 리프에 떨어진다. 
전체 폴리곤을 뒤질 필요 없이 트리 깊이만큼의 내적 연산으로 끝난다.
볼륨 내부 판정(EncompassesPoint)류 쿼리의 원형이다.
<img src="https://velog.velcdn.com/images/jm450_/post/666f50b2-3f8c-4a19-b865-0eef8b370817/image.png" alt=""></p>
<h2 id="43-csgconstructive-solid-geometry">4.3. CSG(Constructive Solid Geometry)</h2>
<p>브러시를 월드에서 빼는(Subtract) 연산을 생각하자. 
브러시 폴리곤들을 월드 BSP에 흘려보내면 각 조각이 &quot;월드 솔리드 내부에 있음 / 빈 공간에 있음&quot;으로 분류된다. 
Subtract에서는 솔리드 내부에 들어간 브러시 폴리 조각이 뒤집혀 새 표면이 되고, 반대로 브러시 내부에 들어간 월드 폴리 조각은 제거된다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/13a6d1b5-a523-48ea-8fb0-764a88394318/image.png" alt=""></p>
<h2 id="44-볼록-분해">4.4 볼록 분해</h2>
<p>리프가 항상 볼록하다는 성질의 실용적 결과: 오목한 임의 형상도 BSP를 만들고 솔리드 리프만 모으면 겹침 없는 볼록 조각 목록이 된다. 
물리 엔진(Chaos)은 볼록체를 좋아하므로, 이것이 브러시/볼륨 충돌 캐시(FKConvexElem)를 만드는 방법이다.
<img src="https://velog.velcdn.com/images/jm450_/post/c8e595d7-e049-4037-9632-9520b6f34143/image.png" alt=""></p>
<h2 id="45-스플리터-선택">4.5. 스플리터 선택</h2>
<p>언리얼식 BSP는 분할 평면을 따로 두지 않고 입력 폴리곤 자신을 스플리터로 쓴다(auto-partitioning). 
그래서 어떤 폴리곤을 먼저 고르느냐가 트리 품질을 좌우한다.
스플리터 평면에 걸치는 폴리곤은 둘로 쪼개져(SP_Split) 개수가 늘고, 분할을 아끼면 트리가 한쪽으로 길어진다. 
UE의 FindBestSplit이 이 둘을 Balance 가중치로 점수화해 고른다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/d5e3f63f-b1b9-452e-9926-441c013220b3/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UE_WorldPartition] Runtime Streaming]]></title>
            <link>https://velog.io/@jm450_/UEWorldPartition-Runtime-Streaming</link>
            <guid>https://velog.io/@jm450_/UEWorldPartition-Runtime-Streaming</guid>
            <pubDate>Sat, 11 Jul 2026 10:33:41 GMT</pubDate>
            <description><![CDATA[<p>런타임에 cell이 어떻게 로드/언로드/활성화/비활성화 하는지 살펴보자.</p>
<p>용어정리</p>
<table>
<thead>
<tr>
<th>축</th>
<th>타입</th>
<th>역할</th>
</tr>
</thead>
<tbody><tr>
<td><strong>스트리밍 소스</strong></td>
<td><code>FWorldPartitionStreamingSource</code></td>
<td>&quot;어디를, 어떤 상태로(Loaded/Activated) 로드할지&quot;를 담은 요청 단위</td>
</tr>
<tr>
<td><strong>스트리밍 정책</strong></td>
<td><code>UWorldPartitionStreamingPolicy</code></td>
<td>매 프레임 소스를 모아 셀 상태 머신을 돌리는 두뇌</td>
</tr>
<tr>
<td><strong>셀</strong></td>
<td><code>UWorldPartitionRuntimeCell</code></td>
<td>상태 전이(<code>Load</code>/<code>Unload</code>/<code>Activate</code>/<code>Deactivate</code>)를 ULevelStreaming에 위임하는 추상 객체</td>
</tr>
</tbody></table>
<hr>
<h1 id="1-streaming-source">1. Streaming Source</h1>
<p>스트리밍의 중심이 되는 소스로, <code>APlayerController</code>가 <code>IWorldPartitionStreamingSourceProvider</code>를 상속받아 사용된다.</p>
<p>만약 커스텀 소스를 만들고 싶다면 <code>UWorldPartitionStreamingSourceComponent</code>를 붙이면 된다.</p>
<h2 id="11-소스-제공자-인터페이스">1.1. 소스 제공자 인터페이스</h2>
<p>소스는 &quot;제공자(provider)&quot;가 매 프레임 내놓는다. 인터페이스는 단수/복수 두 진입점을 가진다.</p>
<pre><code class="language-cpp">struct IWorldPartitionStreamingSourceProvider
{
    virtual bool GetStreamingSource(FWorldPartitionStreamingSource&amp; OutSource) const;
    virtual bool GetStreamingSources(TArray&lt;FWorldPartitionStreamingSource&gt;&amp; OutSources) const; // 기본: 단수 호출해 배열에 담음
    virtual const UObject* GetStreamingSourceOwner() const;
};</code></pre>
<p>기본 <code>GetStreamingSources</code>는 단수 <code>GetStreamingSource</code>를 호출해 배열 한 칸에 담는 구현이다. 즉 대부분의 제공자는 단수만 구현하면 된다.</p>
<h2 id="12-fworldpartitionstreamingsource">1.2. FWorldPartitionStreamingSource</h2>
<p>소스 하나는 <strong>위치/회전/반경/우선순위/타깃 상태</strong>를 가진 단일 스트리밍 요청이다.</p>
<table>
<thead>
<tr>
<th>필드</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>Name</code></td>
<td>소스 고유 이름</td>
</tr>
<tr>
<td><code>Location</code> / <code>Rotation</code></td>
<td>소스 위치/회전</td>
</tr>
<tr>
<td><code>TargetState</code></td>
<td><code>Loaded</code> 또는 <code>Activated</code> — 이 소스가 원하는 셀 상태</td>
</tr>
<tr>
<td><code>Priority</code></td>
<td>소스 우선순위 (<code>Highest=0</code> … <code>Lowest=255</code>, 작을수록 우선)</td>
</tr>
<tr>
<td><code>bBlockOnSlowLoading</code></td>
<td>느린 스트리밍 시 블록 대상 소스인지</td>
</tr>
<tr>
<td><code>Velocity</code></td>
<td>속도(예측 로딩 정렬 기여)</td>
</tr>
<tr>
<td><code>TargetBehavior</code> / <code>TargetGrids</code></td>
<td>대상 그리드 포함/제외</td>
</tr>
<tr>
<td><code>Shapes</code></td>
<td>소스 내부 shape 목록(비어 있으면 구를 자동 생성)</td>
</tr>
</tbody></table>
<p>여기서 가장 중요한 건 <strong><code>TargetState</code></strong>다. enum은 딱 두 값뿐이다.</p>
<pre><code class="language-cpp">enum class EStreamingSourceTargetState : uint8 { Loaded, Activated };</code></pre>
<ul>
<li><code>Loaded</code> — 셀을 메모리에 올려두되 <strong>월드에 추가하지 않음</strong>(액터가 틱/렌더되지 않음). 예: 근처지만 아직 안 보이는 영역을 미리 준비.</li>
<li><code>Activated</code> — 로드 + <strong>월드에 추가(가시)</strong>. 실제로 보고 상호작용하는 영역.</li>
</ul>
<h2 id="13-shape">1.3. shape</h2>
<p>소스의 영향 범위는 <code>FSphericalSector</code>(구면 부채꼴)로 표현된다. 중심/반경/축/각도를 가지며, <strong>각도가 360이면 완전한 구</strong>다.</p>
<pre><code class="language-cpp">struct FSphericalSector { /* Center, Radius, Axis, Angle ... */ };</code></pre>
<p>shape를 실제로 만드는 함수는 <code>FStreamingSourceShapeHelper::ForEachShape</code>다.</p>
<ul>
<li>소스에 <code>Shapes</code>가 <strong>비어 있으면</strong>, <code>기본 반경 + ExtraRadius</code>로 <strong>단일 구</strong> shape를 만든다.</li>
<li><code>Shapes</code>가 <strong>있으면</strong>, 각 shape마다 반경(<code>bUseGridLoadingRange</code>면 그리드 로딩 반경 × <code>LoadingRangeScale</code>, 아니면 <code>Radius</code>), 각도(<code>bIsSector</code>면 <code>SectorAngle</code>, 아니면 360), 축을 계산해 <code>FSphericalSector</code>를 만든다.</li>
<li><code>bForce2D</code>(2D 투영)면 Yaw만 남기고 Z를 0으로 만든다.</li>
</ul>
<p>또한 소스는 <strong>특정 그리드만</strong> 노릴 수 있다. 
<code>IsSourceAffectingGrid</code>가 <code>Include</code>/<code>Exclude</code> 규칙으로 그리드를 걸러낸 뒤에야 shape를 만든다
앞 편에서 본 SpatialHash의 <code>GetCells</code>가 바로 이 그리드명 버전 <code>ForEachShape</code>를 호출한다(<strong>소스와 셀 생성부가 맞물리는 경계</strong>)</p>
<h2 id="14-소스-수집">1.4. 소스 수집</h2>
<p>정책은 소스를 직접 만들지 않고 <strong>서브시스템에서 받는다.</strong></p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/00e227a3-f696-478c-b21d-f3570198fa39/image.png" alt=""></p>
<p><code>CanStream</code>이 거짓이면 소스를 비우고, 최적화가 켜져 있고 소스 해시가 직전과 같으면 배열 전체를 다시 받지 않고 <strong>Velocity만 갱신</strong>한다(예측 로딩용).</p>
<hr>
<h1 id="2-uworldpartitionstreamingpolicy">2. UWorldPartitionStreamingPolicy</h1>
<p>셀의 상태를 제어하는 역할.</p>
<blockquote>
<p><strong>셀의 상태</strong></p>
<ul>
<li>로드 (Load)</li>
<li>언로드 (Unload)</li>
<li>활성화 (Activate)</li>
<li>비활성화 (Deactivate)</li>
</ul>
</blockquote>
<p>정책 자신은 상태 머신의 제어자일 뿐, 실제 레벨 조작은 셀 ULevelStreaming으로 내려간다.</p>
<h2 id="21-셀의-외부-상태는-3단계다">2.1. 셀의 외부 상태는 3단계다</h2>
<p>로드/언로드/활성/비활성은 &quot;행위(전이)&quot;이고, 셀이 실제로 놓이는 &quot;상태&quot;는 3단계 enum이다.</p>
<pre><code class="language-cpp">enum class EWorldPartitionRuntimeCellState : uint8 { Unloaded, Loaded, Activated };</code></pre>
<p>이 순서 <code>Unloaded &lt; Loaded &lt; Activated</code>는 코드가 의존하는 <strong>불변식</strong>이며 <code>static_assert</code>로 강제된다(<code>...RuntimeCell.h:229</code>). 뒤에서 볼 완료 판정의 <code>CellState &lt; QueryState</code> 비교가 이 순서를 전제로 한다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/000e440d-b468-4c70-9ea4-03c4d6256c75/image.png" alt=""></p>
<ul>
<li><strong>Load</strong>: 셀을 로드하되 월드에 추가하지 않음 → <code>Loaded</code></li>
<li><strong>Activate</strong>: 로드 + 월드에 추가(가시) → <code>Activated</code></li>
<li><strong>Deactivate</strong>: 월드에서 제거하되 로드는 유지 → <code>Activated</code> → <code>Loaded</code> (강등)</li>
<li><strong>Unload</strong>: 완전 언로드 → <code>Unloaded</code></li>
</ul>
<h2 id="22-levelstreaming을-통한-상태">2.2. LevelStreaming을 통한 상태</h2>
<p>중요한 포인트. 셀의 현재 상태는 정책이 직접 저장하는 게 아니라, 셀이 감싼 <code>ULevelStreaming</code>의 실제 상태(<code>ELevelStreamingState</code>)에서 <strong>유도</strong>된다.</p>
<p><code>UWorldPartitionRuntimeLevelStreamingCell::GetCurrentState</code>:</p>
<ul>
<li><code>LoadedVisible</code> → <code>Activated</code></li>
<li><code>LoadedNotVisible</code> 이상 → <code>Loaded</code></li>
<li>LevelStreaming이 없으면 → <code>bIsAlwaysLoaded</code>면 <code>Activated</code>, 아니면 <code>Unloaded</code></li>
</ul>
<hr>
<h1 id="3-uworldpartitionruntimecell">3. UWorldPartitionRuntimeCell</h1>
<h2 id="31-로드언로드">3.1. 로드/언로드</h2>
<p>셀 본체는 추상 클래스이고, 전이 함수는 전부 순수 가상이다. 구현은 <code>UWorldPartitionRuntimeLevelStreamingCell</code>이 맡는다.</p>
<table>
<thead>
<tr>
<th>메서드</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>Load() const</code></td>
<td>셀을 로드하되 월드에 추가하지 않음</td>
</tr>
<tr>
<td><code>Unload() const</code></td>
<td>셀 언로드</td>
</tr>
<tr>
<td><code>CanUnload() const</code></td>
<td>언로드 가능 여부</td>
</tr>
<tr>
<td><code>Activate() const</code></td>
<td>로드 + 월드에 추가(가시)</td>
</tr>
<tr>
<td><code>Deactivate() const</code></td>
<td>월드에서 제거, 로드는 유지</td>
</tr>
<tr>
<td><code>GetCurrentState() const</code></td>
<td>현재 셀 상태</td>
</tr>
</tbody></table>
<p>셀은 이 밖에 <code>bIsAlwaysLoaded</code>(항상 로드 → 언로드 안 함), <code>bIsSpatiallyLoaded</code>, <code>DataLayers</code>, <code>bIsHLOD</code>, <code>bBlockOnSlowLoading</code>, <code>RuntimeCellData</code>(거리/우선순위 캐싱) 같은 멤버를 가진다.</p>
<h2 id="32-ulevelstreaming">3.2. ULevelStreaming</h2>
<p>전이는 전부 <code>ULevelStreaming</code>에 위임된다</p>
<p><code>UWorldPartitionRuntimeLevelStreamingCell</code>은 네 전이를 모두 <code>UWorldPartitionLevelStreamingDynamic</code>에 위임한다.</p>
<table>
<thead>
<tr>
<th>셀 메서드</th>
<th>위임 대상</th>
</tr>
</thead>
<tbody><tr>
<td><code>Load()</code></td>
<td><code>LevelStreaming-&gt;Load()</code></td>
</tr>
<tr>
<td><code>Activate()</code></td>
<td><code>LevelStreaming-&gt;Activate()</code> (LevelStreaming 없고 커스텀 HLOD면 <code>OnCellShown()</code>)</td>
</tr>
<tr>
<td><code>Deactivate()</code></td>
<td><code>LevelStreaming-&gt;Deactivate()</code></td>
</tr>
<tr>
<td><code>Unload()</code></td>
<td><code>LevelStreaming-&gt;Unload()</code></td>
</tr>
<tr>
<td><code>CanUnload()</code></td>
<td>항상 <code>true</code></td>
</tr>
</tbody></table>
<p>그리고 ULevelStreamingDynamic의 전이는 결국 <strong>세 플래그 세팅</strong>으로 끝난다.</p>
<table>
<thead>
<tr>
<th>전이</th>
<th>ShouldBeLoaded</th>
<th>ShouldBeVisible</th>
<th>비고</th>
</tr>
</thead>
<tbody><tr>
<td><code>Load()</code></td>
<td><code>true</code></td>
<td><code>false</code></td>
<td>월드 스트리밍 레벨에 추가</td>
</tr>
<tr>
<td><code>Activate()</code></td>
<td><code>true</code></td>
<td><code>true</code></td>
<td>월드에 추가(가시)</td>
</tr>
<tr>
<td><code>Deactivate()</code></td>
<td>(유지)</td>
<td><code>false</code></td>
<td>가시성만 내림</td>
</tr>
<tr>
<td><code>Unload()</code></td>
<td><code>false</code></td>
<td><code>false</code></td>
<td>언로드/제거 요청</td>
</tr>
</tbody></table>
<p>즉 &quot;셀을 활성화한다&quot;는 결국 <code>bShouldBeLoaded = bShouldBeVisible = true</code>로 만드는 것이고, 실제 레벨 추가/제거는 언리얼의 기존 레벨 스트리밍(<code>UWorld::UpdateLevelStreaming</code> → <code>AddToWorld</code>/<code>RemoveFromWorld</code>)이 처리한다. World Partition은 그 위에 <strong>&quot;어떤 셀을 언제 그 상태로 만들지&quot;</strong>를 얹은 층이다.</p>
<hr>
<h1 id="4-updatestreamingstate">4. UpdateStreamingState</h1>
<p>서브시스템이 매 프레임 해당 함수를 호출한다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/6683915c-2aa2-4c31-911e-bad250823495/image.png" alt=""></p>
<p><code>RuntimeHash-&gt;ForEachStreamingCellsSources</code>가 소스가 커버하는 셀을 훑고, 콜백이 소스의 <code>TargetState</code>에 따라 <code>FrameLoadCells</code> / <code>FrameActivateCells</code>로 분류한다</p>
<p>같은 셀이 로드/활성 양쪽에 걸리면 <code>FrameLoadCells = FrameLoadCells.Difference(FrameActivateCells)</code>로 <strong>활성 쪽만 남긴다</strong></p>
<p>현재 상태와 이번 프레임 희망을 비교해 네 집합을 만든다.</p>
<table>
<thead>
<tr>
<th>집합</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>ToActivateCells</code></td>
<td>새로 활성화할 셀</td>
</tr>
<tr>
<td><code>ToLoadCells</code></td>
<td>새로 로드할(또는 활성→로드 강등) 셀</td>
</tr>
<tr>
<td><code>ToDeactivateCells</code></td>
<td>활성→비활성 강등 (주로 서버)</td>
</tr>
<tr>
<td><code>ToUnloadCells</code></td>
<td>현재 로드/활성인데 이번 프레임 후보에 <strong>없는</strong> 셀 → 언로드 대상</td>
</tr>
</tbody></table>
<p>예산(<code>MaxCellsToLoad</code>)이 한정돼 있으므로 <code>SortCompare</code>로 순서를 정한다. </p>
<ul>
<li>소스 우선순위(낮을수록 먼저)</li>
<li>계층 레벨(높을수록 먼저) </li>
<li>셀 priority</li>
<li>공간 정렬 우선순위(거리·각도)</li>
</ul>
<hr>
<h1 id="5-실제로-전이를-호출하는-건-누구인가">5. 실제로 전이를 호출하는 건 누구인가</h1>
<p>정책은 &quot;목록을 만들 뿐&quot;, 전이 함수를 직접 부르는 건 <strong>정책의 <code>SetCellState*</code>와 서브시스템</strong>이다.</p>
<table>
<thead>
<tr>
<th>함수</th>
<th>하는 일</th>
</tr>
</thead>
<tbody><tr>
<td><code>SetCellStateToActivated</code></td>
<td>셀이 <code>LoadedCells</code>에 있으면 빼고(승격), 아니면 예산(<code>--MaxCellsToLoad</code>) 소모 → <code>ActivatedCells.Add</code> + <code>InCell-&gt;Activate()</code></td>
</tr>
<tr>
<td><code>SetCellStateToLoaded</code></td>
<td>활성 상태면 <code>Deactivate()</code> 후 로드로 강등, 아니면 예산 내 <code>Load()</code> → <code>LoadedCells.Add</code></td>
</tr>
<tr>
<td><code>SetCellsStateToUnloaded</code></td>
<td><code>CanUnload()</code>가 참인 셀을 <code>Unload()</code>하고 두 집합에서 제거</td>
</tr>
</tbody></table>
<p>호출 주체를 정리하면:</p>
<ul>
<li><strong>활성/로드</strong>: 서브시스템 <code>UpdateStreamingStateInternal</code>이 <code>GetCellsToUpdate</code>로 모은 셀을 정렬·예산화해 <code>SetCellStateToActivated</code>/<code>SetCellStateToLoaded</code> 호출</li>
<li><strong>언로드/서버 강등</strong>: 정책의 <code>PostUpdateStreamingStateInternal_GameThread</code>가 직접 <code>SetCellsStateToUnloaded</code> 처리</li>
</ul>
<p>즉 <strong>로드/활성은 예산 제한을 받고(프레임당 N개), 언로드는 곧바로</strong> 처리된다.</p>
<hr>
<h1 id="6-activate-과정">6. Activate 과정</h1>
<p><code>Activate()</code>를 불렀다고 해서 그 프레임에 바로 화면에 뜨는 게 아니다. 
레벨이 실제로 월드에 추가되어 보이게 되면 <code>ULevelStreaming</code>이 <code>OnLevelShown</code>/<code>OnLevelHidden</code> 델리게이트를 쏘고, 이게 셀의 <code>OnCellShown</code>/<code>OnCellHidden</code>을 거쳐 정책까지 전파된다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/68c197f7-67c4-49a6-9eac-ddffa3a2320b/image.png" alt=""></p>
<p>그래서 정책은 활성 셀을 <code>FActivatedCells</code> 안에서 <strong><code>Cells</code>(활성 요청됨)</strong>와 <strong><code>PendingAddToWorldCells</code>(요청됐지만 아직 안 보임)</strong>로 나눠 추적한다 
<strong>스트리밍이 끝났는지</strong> 판정할 때 이 구분이 중요하다.</p>
<hr>
<h1 id="7-완료-판정과-블로킹-로딩">7. 완료 판정과 블로킹 로딩</h1>
<h2 id="71-isstreamingcompleted">7.1. IsStreamingCompleted</h2>
<p><code>IsStreamingCompleted(소스)</code>는 비공간 셀(데이터 레이어)과 각 소스 기반 공간 질의를 차례로 검사한다. 셀의 현재 상태가 질의 상태와 다르면(스킵 불가 조건에서) <strong>미완료</strong>로 본다.</p>
<h2 id="72-블로킹-로딩">7.2. 블로킹 로딩</h2>
<p>스트리밍이 너무 느려서 플레이어가 아직 안 로드된 영역으로 넘어가버릴 위험이 있으면, 프레임을 멈추고서라도 로드를 끝내야 한다.</p>
<ul>
<li>성능이 Critical 이상이고 블록 대상이면 <code>UpdateStreamingPerformance</code>가 <code>World-&gt;bRequestedBlockOnAsyncLoading = true</code>로 만들어 <strong>다음 프레임 블로킹 로딩</strong>을 요청</li>
<li>반대로 성능 저하 중에는 <code>CanAddCellToWorld</code>가 <strong>비블로킹 셀의 월드 추가를 막아</strong> 급한 셀부터 처리</li>
<li>어떤 소스/셀이 블로킹 대상인지는 소스의 <code>bBlockOnSlowLoading</code>과 셀의 <code>bBlockOnSlowLoading</code> 플래그로 정함</li>
</ul>
<hr>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UE_WorldPartition] Streaming Generation]]></title>
            <link>https://velog.io/@jm450_/UEWorldPartition-Streaming-Generation</link>
            <guid>https://velog.io/@jm450_/UEWorldPartition-Streaming-Generation</guid>
            <pubDate>Sat, 11 Jul 2026 09:43:39 GMT</pubDate>
            <description><![CDATA[<p>1편에서 다룬 ActorDesc를 어떻게 Cell에 배치하는지 알아보겠다.
1편: <a href="https://velog.io/@jm450_/UEWorldPartition-ActorDesc">https://velog.io/@jm450_/UEWorldPartition-ActorDesc</a></p>
<p>쿠킹 시 or 에디터에서 실행 시 해당로직이 실행된다.
해당 문서에서는 PIE에서 실행될 때를 다룬다.</p>
<h1 id="1-generatestreaming-호출">1. GenerateStreaming 호출</h1>
<p>PIE가 호출되면서 GenerateStreaming 함수가 호출된다.
<img src="https://velog.velcdn.com/images/jm450_/post/f1b01d3a-137e-4573-ad67-a36eb027ab01/image.png" alt=""></p>
<p>컨테이너 컬렉션을 구성한 뒤 <code>GenerateContainerStreaming()</code>으로 넘긴다. 
<img src="https://velog.velcdn.com/images/jm450_/post/9b267306-affa-4793-8bd9-2d6d79419dcf/image.png" alt=""></p>
<p>이후 두 가지 큰 작업이 순서대로 일어난다</p>
<ul>
<li>Preparation Phase(클러스터/ActorSet 준비)</li>
<li>RuntimeHash 스트리밍 생성(셀 배치).
<img src="https://velog.velcdn.com/images/jm450_/post/134d644d-53d8-4970-9983-c9f7e6282038/image.png" alt=""></li>
</ul>
<h1 id="2-preparation-phase">2. Preparation Phase</h1>
<p>해당 단계에서는 ActorDesc를 클러스터링 하는 과정을 거친다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/c38ec750-f780-4d7e-962c-7b8bbb5a277b/image.png" alt=""></p>
<h3 id="21-클러스터링-조건">2.1. 클러스터링 조건</h3>
<p>ActorDesc로 넣어둔 References Actor들을 클러스터링 한다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/3b7f94ca-ac8e-4054-9094-93e15c851a41/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/e12860a0-d2e8-486d-b307-5e35f4857b02/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/ba3f9950-50cf-42d5-b871-1215b15f8558/image.png" alt=""></p>
<h3 id="22-클러스터링-알고리즘">2.2. 클러스터링 알고리즘</h3>
<p>참조를 양방향 간선으로 만든 무방향 그래프 구성하고 명시적 스택 기반 반복 DFS로 연결 요소 수집한다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/8ae9b50d-fcd8-4882-9ff9-67a16c7f74bc/image.png" alt=""></p>
<p>지금까지 내용을 정리하자면 아래 그림과 같다.
<img src="https://velog.velcdn.com/images/jm450_/post/bd0c5080-57fb-49a7-af66-0ae18be8a5ea/image.png" alt=""></p>
<h3 id="23-셀-배치">2.3. 셀 배치</h3>
<p>이후 ActorSetInstance 를 통해 ActorSet 단위로 Bound등을 검사해서 그리드 셀에 배치한다.</p>
<p>셀에 배치하는 규칙은 런타임 해시 규칙에 따라 다르다.
<code>virtual bool GenerateStreaming</code> 를 따라가면 셀배치 로직을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/jm450_/post/1929bd06-c7cd-4c80-a163-b3772cdcf295/image.png" alt=""></p>
<p><code>UWorldPartitionRuntimeSpatialHash</code> 클래스를 보면 CellSize크기로 나눠진 cell에 배치가능한지 불가능하다면 Level을 올리며 배치가능한 Cell을 찾고 있다 Level이 1오를 수 록 CellSize는 2배가 된다
<img src="https://velog.velcdn.com/images/jm450_/post/1691fb2e-72bb-4c63-896a-1d32718cfbdf/image.png" alt="">
<img src="https://velog.velcdn.com/images/jm450_/post/069c7cd8-5f33-4b5c-8dfa-072e454f54fb/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UE_WorldPartition] ActorDesc]]></title>
            <link>https://velog.io/@jm450_/UEWorldPartition-ActorDesc</link>
            <guid>https://velog.io/@jm450_/UEWorldPartition-ActorDesc</guid>
            <pubDate>Sat, 11 Jul 2026 08:54:53 GMT</pubDate>
            <description><![CDATA[<h1 id="1-worldpartition의-액터-관리">1. WorldPartition의 액터 관리</h1>
<p>언리얼에서 월드파티션을 사용하지 않는 Level이라면 일반적으로 Level 패키지에 Actor를 포함한다.
하지만 월드파티션을 사용하는 Level은 월드 크기가 일반적인 케이스보다 N배 크고 포함되는 Actor도 많다.</p>
<p>이로 인해 Level에 포함해서 에디터에서 한번에 로드한다면 메모리 크래시가 발생하고 Level단위로 저장되기 때문에 분산 작업이 불가한 문제가 있다.</p>
<p>따라서, 액터 인스턴스의 데이터를 외부 파일에 저장하는 방법을 사용한다. (One File Per Actor, OFPA) </p>
<blockquote>
<p>참고 문서: <a href="https://dev.epicgames.com/documentation/unreal-engine/one-file-per-actor-in-unreal-engine">https://dev.epicgames.com/documentation/unreal-engine/one-file-per-actor-in-unreal-engine</a></p>
</blockquote>
<p>해당 인스턴스를 로드하지 않고 정보를 가져오기 위한 메타 데이터를 WorldPartitionActorDesc로 구현한다.
이는 에디터 전용이다.</p>
<h1 id="2-worldpartitionactordesc">2. WorldPartitionActorDesc</h1>
<p>아래 정보들을 통해 액터의 로드/언로드를 제어한다.
또한 액터를 직접 로드하지 않더라도 메타데이터를 통해 연산하기도 한다.</p>
<p>액터 저장 시 업데이트 되고 FAssetData에 저장된다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/e69155d9-90bf-4125-ae90-b10342d4556e/image.png" alt=""></p>
<h3 id="21-fworldpartitionactordescinstance">2.1. FWorldPartitionActorDescInstance</h3>
<p>UE5.4부터 Transient한 데이터를 분리해서 다루기 위해 FWorldPartitionActorDescInstance 클래스가 추가되었다.
실제로 데이터 Get과 같은 인터페이스 함수는 해당 클래스에서 처리한다.</p>
<blockquote>
<p>엔진 업뎃했더니 함수가 다 없어져서 고생했었다...</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jm450_/post/f6d20e13-0351-4fdc-9961-c161b89e904c/image.png" alt=""></p>
<h1 id="3-uactordesccontainer">3. UActorDescContainer</h1>
<p>ActorDesc의 Add, Remove, Update 등에 대한 액션을 처리하고 관리하는 컨테이너 클래스  </p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/065764bb-52af-4c0b-8a04-50426c478747/image.png" alt=""></p>
<p>Transient 하게 존재한다.
<img src="https://velog.velcdn.com/images/jm450_/post/2829e30e-2fe3-4d09-ba08-1ac8935ee850/image.png" alt=""></p>
<h3 id="31-uactordesccontainerinstance">3.1. UActorDescContainerInstance</h3>
<p>Container가 Transient한데 Instance 클래스가 존재한다.</p>
<p>해당 클래스는 추상화 되는 구간이다.
ActorDescContinerInstance 베이스 클래스가 존재한다.
<img src="https://velog.velcdn.com/images/jm450_/post/9e86df57-5ab0-4e7c-8bf2-9ce72549bd91/image.png" alt=""></p>
<p>이를 상속받은 LevelInstanceContainerActorInstance가 존재한다.
<img src="https://velog.velcdn.com/images/jm450_/post/5f827454-a96a-42ae-a778-3003c3e5909b/image.png" alt=""></p>
<p>따라서 어떤 Level(레벨 시스템)에 소속한 ActorDesc인지에 따라 액션을 다르게 처리하기 위한 클래스임을 알 수 있다.
또한 레벨 인스턴스처럼 Package는 1개이지만 N곳에 인스턴싱된 액터를 사용할 때 Instance를 N개 만들어서 제어할 수 있다.</p>
<h1 id="4-uactorcontainersubsystem">4. UActorContainerSubsystem</h1>
<p>UActorDescContainer를 관리하며, InvalidMapAssets을 통해 레벨이 존재하지 않지만 남은 고아 Actor 정보를 가지고 있다.
<img src="https://velog.velcdn.com/images/jm450_/post/8dbe6142-01e9-4f26-a273-9f8650f5cdc6/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Resonant] Wwise]]></title>
            <link>https://velog.io/@jm450_/Resonant-Wwise</link>
            <guid>https://velog.io/@jm450_/Resonant-Wwise</guid>
            <pubDate>Sun, 17 May 2026 14:01:46 GMT</pubDate>
            <description><![CDATA[<h2 id="wwise">Wwise?</h2>
<p>게임 오디오 엔진 미들웨어로, 게임에서 사운드 재생을 제어하기 위한 도구다.
이를 사용하므로서 아래와 같은 이점이 있다.</p>
<ul>
<li>복잡한 사운드 로직을 게임로직과 분리 가능</li>
<li>사운드 디자이너가 직업 사운드 로직 작업 가능<ul>
<li>역할 분리</li>
</ul>
</li>
</ul>
<h2 id="wwise-적용하기">Wwise 적용하기</h2>
<h3 id="1-wwise-설치">1. Wwise 설치</h3>
<p>먼저 Wwise를 설치하여야한다.</p>
<h4 id="11-audiokinetic-launcher-설치">1.1. Audiokinetic Launcher 설치</h4>
<p>공홈에서 런처를 다운받는다
<a href="https://www.audiokinetic.com/ko/download/">https://www.audiokinetic.com/ko/download/</a></p>
<h4 id="12-wwise를-설치">1.2. Wwise를 설치</h4>
<p>Wwise 탭에서 설치한다.
<img src="https://velog.velcdn.com/images/jm450_/post/2b8ffabf-56d2-48cc-b787-4d8abc4b2448/image.png" alt=""></p>
<h4 id="13-unreal에-플러그인-추가">1.3. Unreal에 플러그인 추가</h4>
<p>공식 가이드 문서가 있다.
<a href="https://www.audiokinetic.com/ko/public-library/2025.1.7_9143/?id=index.html&amp;source=UE4&amp;utm_source=chatgpt.com">https://www.audiokinetic.com/ko/public-library/2025.1.7_9143/?id=index.html&amp;source=UE4&amp;utm_source=chatgpt.com</a></p>
<p>이게 더 직관적이다..
<a href="https://www.audiokinetic.com/ko/community/blog/how-to-play-sounds-from-wwise-in-unreal-engine/#project-3">https://www.audiokinetic.com/ko/community/blog/how-to-play-sounds-from-wwise-in-unreal-engine/#project-3</a></p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/673b11d2-cc60-48fb-9ab3-30dd710d618b/image.png" alt=""></p>
<h4 id="14-테스트하기">1.4 테스트하기</h4>
<p>Wwise 에디터에서 wav 파일을 임포트하고 event화하였다.
그리고 사운드 뱅크에 추가하였다.
<img src="https://velog.velcdn.com/images/jm450_/post/a751e8d6-7df0-48a7-a67d-7d3603f7b553/image.png" alt=""></p>
<p>언리얼 에디터에서 사운드 뱅크를 동기화하면 에셋이 만들어진다.
<img src="https://velog.velcdn.com/images/jm450_/post/f19a6321-c8f8-42c7-9608-85a2d74e8d5c/image.png" alt=""></p>
<p>볼륨 진입하면 Ak Event 실행하게 만들었다.
<img src="https://velog.velcdn.com/images/jm450_/post/01907e9b-935b-409d-a7c8-df96333bd813/image.png" alt=""></p>
<h2 id="마치며">마치며</h2>
<ul>
<li>Wwise Unreal Integration을 적용</li>
<li>Wwise Authoring에서 생성한 Audio Event와 SoundBank를
Unreal Runtime에서 호출할 수 있는 기본 오디오 파이프라인을 구축</li>
</ul>
<p><strong>작업 흐름</strong></p>
<pre><code>Wwise Authoring
    ↓
Audio Import
    ↓
Event 생성
    ↓
SoundBank Generate
    ↓
Unreal Wwise Integration
    ↓
AkAudioEvent 참조
    ↓
C++ PostEvent 호출
    ↓
Runtime Sound Playback</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[DX11] Animation 실행하기]]></title>
            <link>https://velog.io/@jm450_/DX11-Animation-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jm450_/DX11-Animation-%EC%8B%A4%ED%96%89%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 02 Mar 2026 16:02:21 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>드디어 애니메이션을 실행시킨다. 언리얼의 애니메이션 기능을 하나씩 붙여보려고 한다. 해당 문서에서는 AnimSequence 실행과 RootMotion 적용을 다룬다.</p>
<h1 id="구현-내용">구현 내용</h1>
<h2 id="0-skinned-mesh">0. Skinned Mesh</h2>
<p>0번으로 스킨 메시가 렌더링 되어야한다. 이미 이전에 렌더링되도록 만든것 아닌가라는 의문이 든다면 스킨 메시에 대해서 공부해보자.</p>
<p>스켈레탈 메시는 아래 사진 우측과 같이 본이 트리 구조로 이루어져있다. 본은 부모본에 Attach된 상태라고 인지하면 쉽다. 부모 본에 상대적인 위치를 가지고 있어 부모 본의 Transform에 변형이 가해진다면 자식 본들도 상태 Transform이 변경된다.</p>
<p>또한 이러한 본에 의해 메시 버텍스들이 움직이게 된다.
메시 버텍스 하나 당 N개의 본의 영향을 받을 수 있고 가중치가 설정되어 있다. 이거 때문에 스킨메시의 버텍스 포지션 구하는 방법이다른 것이다!
<img src="https://velog.velcdn.com/images/jm450_/post/07f17803-0a74-45da-a823-3737096ba17a/image.png" alt=""></p>
<p>스켈레탈 컴포넌트와 프록시의 로직에서 BoneMatrix를 추가하고 VS를 다른 쉐이더를 사용하도록 처리했다.
아래는 본인덱스(4개 사용)에 따라 매트릭스를 계산해 버텍스 포지션 설정하는 것이다.</p>
<pre><code>// 매트릭스 계산
float4x4 MakeSkinMatrix(uint4 idx, float4 w)
{
    return gBones[idx.x] * w.x +
           gBones[idx.y] * w.y +
           gBones[idx.z] * w.z +
           gBones[idx.w] * w.w;
}

// VS
VSOut VSMain(VSIn vin)
{
    VSOut o;

    float4x4 skin = MakeSkinMatrix(vin.boneIdx, vin.weight);

    float3 skPos = mul(float4(vin.pos, 1), skin).xyz;
    float3 skN   = mul(vin.n, (float3x3)skin);
    float3 skT   = mul(vin.tangent.xyz, (float3x3)skin);

    float4 posW  = mul(float4(skPos,1), gWorld);
    o.pos        = mul(float4(skPos,1), gWorldViewProj);
    o.posWS      = posW.xyz;

    float3x3 world3    = (float3x3)gWorld;
    float3x3 normalMat = transpose(Inverse3x3(world3));

    float3 N = normalize(mul(skN, normalMat));
    float3 T = normalize(mul(skT, world3));
    T = normalize(T - N * dot(N,T));

    float sign = vin.tangent.w;
    o.normalWS  = N;
    o.tangentWS = float4(T, sign);
    o.bitanWS   = normalize(cross(N,T)) * sign;

    o.uv = vin.uv;
    return o;
}</code></pre><h2 id="1-animsequence">1. AnimSequence</h2>
<p>일단 FBX 파일 중 애니메이션이 들어있는 파일을 AnimSequenceAsset으로 임포트한 뒤 사용하였다.</p>
<h3 id="1-animsequenceasset">1) AnimSequenceAsset</h3>
<ul>
<li>프레임, 본 마다 Transform 정보를 저장</li>
<li>정적 데이터 역할을 하기 때문에 런타임에 데이터 정보가 변하지 않기를 기대</li>
</ul>
<h3 id="2-animinstance">2) AnimInstance</h3>
<ul>
<li>애니메이션 관련 런타임 로직을 담당할 객체</li>
<li>AS를 실행할 때 AnimSequenceAsset을 바라보며 정적데이터를 통해 런타임 Bone 데이터를 Modify하는 역할을 수행<ul>
<li>AnimInstance에 LocalPose 변수를 둬서 해당 변수를 변경하는 방식</li>
</ul>
</li>
</ul>
<pre><code>    // 본별 Transform 보간 -&gt; 행렬로 변환하여 LocalPose에 저장
    for (uint32_t b = 0; b &lt; m_BoneCount; ++b)
    {
        const Transform&amp; k0 = sec.Keys[(size_t)f0 * m_BoneCount + b];
        const Transform&amp; k1 = sec.Keys[(size_t)f1 * m_BoneCount + b];

        Transform blended = Transform::Lerp(k0, k1, alpha);

        DirectX::XMFLOAT4X4 M;
        DirectX::XMStoreFloat4x4(&amp;M, XMMatrixTranspose(blended.ToMatrix()));
        m_LocalPose[b] = M;
    }</code></pre><ul>
<li><p>AnimInstance에서 계산한 LocalPose값을 SkeletalMesh에서 적용</p>
<pre><code>void SkeletalMeshComponent::BuildFinalPalette_FromLocalPose(const std::vector&lt;DirectX::XMFLOAT4X4&gt;&amp; localPose)
{
  //.. 생략

  // 로컬 Transform을 월드 Transform으로 변경
  BuildGlobalPose_FromLocalPose(localPose);

  // FinalPalette
  for (uint32_t i = 0; i &lt; n; ++i)
  {
      const auto&amp; off = skel.Bones[i].Offset;
      m_FinalPalette[i] = MulM(MulM(skel.GlobalInverse, m_GlobalPose[i]), off);
      XMStoreFloat4x4(&amp;m_FinalPalette[i], XMLoadFloat4x4(&amp;m_FinalPalette[i]));
  }
</code></pre></li>
</ul>
<p>}</p>
<pre><code>
끼얏호우 잘동작한다 !!
&gt;사실 이렇게 바로 잘 동작하지 않았고 Matrix 때문에 꼬여서 디버깅을 많이했다. FBX는 Col-Major를 사용해서 matrix를 transpose해서 가져오고 있다. 이때, 어디까지 Col-Major를 사용하고 어디까지 Row-Major를 사용할지에 대해 정확하게 정하지 않은 상태에서 작업을 하다보니 한쪽에서 매트릭스가 꼬이는 사태가 발생했고 이를 디버깅하기 매우 어려웠다...(사실 코드 보고 알았다)

&gt;다음부터 작업할 땐 꼭 해당 정책을 정해야 겠다고 다짐했다 또한 Transpose하는 부분에선 꼭 이유를 적어둬야겠다. 내 코드니깐 알아보지 다른사람 작업이었으면 못찾았을 것 같다.

![](https://velog.velcdn.com/images/jm450_/post/f100852d-5067-403c-9f96-c320c51030c8/image.gif)

### 3) RootMotion 구현
성질이 급해서 movementComponent도 없으면서 RootMotion을 구현했다... 일단 물리는 생각하지 않고 RootComponent의 Transform 변경해주는 기능만 만들어뒀다.

캐릭터의 머티리얼이 날아간 이유는... 믹사모 캐릭터에는 루트본이 없다.... 그래서 언리얼에서 루트본 추가하고 FBX로 Export하니깐 머티리얼이 다 날아가있다... 일단 이 이슈는 나중에 보기로하고 작업을 진행했다.

- 루트 모션을 실행하기 위해선 RootBone의 Transform을 본에서 제거해줘야한다. (RootComp에서 소모하기 위함)
  ```cpp
  void AnimInstance::ApplyRootLockToLocalPose(const AnimSection&amp; sec)
{
    //...생략

    switch (sec.RootLockMode)
    {
    case ERootLockMode::RefPose:
        if (root &lt; m_Skeleton-&gt;RefLocalPose.size())
            m_LocalPose[root] = m_Skeleton-&gt;RefLocalPose[root];
        break;

    case ERootLockMode::AnimFirstFrame:
        if (!m_bHasFirstRootInSection)
        {
            m_FirstRootInSection = SampleBoneTransform(sec, root, 0.f);
            m_bHasFirstRootInSection = true;
        }
        XMStoreFloat4x4(&amp;m_LocalPose[root], m_FirstRootInSection.ToMatrix());
        break;

    case ERootLockMode::Zero:
    default:
        XMStoreFloat4x4(&amp;m_LocalPose[root], XMMatrixIdentity());
        break;
    }
}</code></pre><ul>
<li><p>그리고 루트의 델타 Transform을 추출해 MovementComponent에서 Root로 적용시킨다</p>
<pre><code class="language-cpp">void CharacterMovementComponent::ConsumeAndApplyRootMotion(float dt)
{
  //... 생략

  const Transform deltaMeshSpace = anim-&gt;ConsumeExtractedRootMotion();
  const Transform deltaRootSpace = ConvertMeshSpaceDeltaToRootSpace(deltaMeshSpace, sk);
  const Transform deltaScaled = ScaleRootMotionTranslationByRootScale(deltaRootSpace, root);
  MoveWithCollision(deltaScaled);
}</code></pre>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jm450_/post/d5f6a329-f5cb-42e9-b665-04d117d74610/image.gif" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DX11] 렌더러 만들기]]></title>
            <link>https://velog.io/@jm450_/DX11-%EB%A0%8C%EB%8D%94%EB%9F%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@jm450_/DX11-%EB%A0%8C%EB%8D%94%EB%9F%AC-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Mon, 02 Mar 2026 14:59:44 GMT</pubDate>
            <description><![CDATA[<h1 id="개요">개요</h1>
<p>사실 렌더러는 퐁쉐이딩만 하도록 대충 만들어두려고 했는데 보이는게 안이쁘니깐 애니메이션 작업도 흥미가 안생기더라... 그래서 PBR을 구현해보기로 했다. 인터넷에 자료가 많아서 많이 따라갔다.</p>
<h1 id="개념">개념</h1>
<h2 id="pbr-physically-based-rendering">PBR (Physically Based Rendering)</h2>
<h3 id="1-pbr-개요">1. PBR 개요</h3>
<p>PBR은 물리 기반(혹은 물리 근사)으로 빛-재질 상호작용을 모델링해, 환경/조명 변화에 대해 일관된 결과를 얻는 렌더링 방식이다.
실시간 PBR에서 핵심 구성은 보통 아래 3가지로 설명한다.</p>
<ul>
<li>Microfacet Model: 표면을 “아주 작은 거울 조각”들의 집합으로 보고 반사를 모델링</li>
<li>Energy Conservation: 반사 + 확산으로 나가는 에너지가 들어오는 에너지를 초과하지 않도록 제한</li>
<li>BRDF: 입사/출사 방향에 따른 반사 비율을 정의하는 함수(특히 Cook-Torrance)</li>
</ul>
<h3 id="2-microfacet-model-미세면-모델">2. Microfacet Model (미세면 모델)</h3>
<p><strong>개념</strong>
표면은 완전히 매끈하지 않고, 미세한 면(거울 조각)들이 다양한 방향을 가진다고 가정한다.</p>
<ul>
<li>Normal Map: 픽셀마다 microfacet의 “평균 법선 방향”을 바꾼다.</li>
<li>Roughness: microfacet 법선 분포의 폭(퍼짐)을 바꾼다.<ul>
<li>roughness ↓ : 하이라이트가 작고 날카로움</li>
<li>roughness ↑ : 하이라이트가 넓고 흐림</li>
</ul>
</li>
</ul>
<p><strong>픽셀 법선(N) 구성 흐름</strong>
실무적으로는 “기본 법선(지오메트리)” 위에 “노멀맵(탄젠트 공간)”을 적용해 최종 월드 법선을 만든다.</p>
<p>1) 버텍스 노멀 보간으로 픽셀 노멀 얻기</p>
<ul>
<li>VS에서 월드 공간 normal을 넘기면 PS에서 보간된 픽셀 normal을 얻을 수 있음.</li>
</ul>
<p>2) 노멀맵을 샘플링해 TBN으로 월드 공간 변환</p>
<ul>
<li>노멀맵은 보통 Tangent Space 기준</li>
<li>샘플한 노멀(nTS)을 TBN으로 변환해 월드 노멀로 만든다.</li>
<li>이때 TBN의 N은 1)에서 얻은 보간 노멀을 사용<pre><code class="language-hlsl">float3 ApplyNormalMap(float2 uv, float3 N, float3 T, float3 B)
{
  float3 nTS = DecodeNormalTS(gTexNormal.Sample(CommonSampler, uv).xyz);
  float3x3 TBN = float3x3(normalize(T), normalize(B), normalize(N));
  return normalize(mul(nTS, TBN)); // tangent-&gt;world
}</code></pre>
</li>
</ul>
<p>3) D(NDF): Roughness로 분포 폭 제어 (GGX)
Cook-Torrance 스페큘러의 첫 요소는 D (Normal Distribution Function).
여기서 roughness가 하이라이트의 모양/크기에 가장 크게 영향을 준다.</p>
<ul>
<li><p>사용 기법: DistributionGGX</p>
</li>
<li><p>NdotH: N과 H(half vector)의 내적</p>
</li>
<li><p>roughness ↑ → 분포가 넓어짐 → 하이라이트가 퍼짐</p>
<pre><code class="language-hlsl">// D: GGX(Trowbridge-Reitz) Normal Distribution Function
// - microfacet들의 법선이 하프벡터 H 방향으로 얼마나 모여있는지(분포)를 근사
// - roughness ↓  -&gt; 분포가 좁아짐(하이라이트 작고 날카로움)
// - roughness ↑  -&gt; 분포가 넓어짐(하이라이트 넓고 흐림)
float DistributionGGX(float NdotH, float roughness)
{
  // UE4 관례: roughness를 제곱해서 &#39;a&#39;로 사용 (반응을 더 자연스럽게 만듦)
  // a가 작을수록 매우 매끈한 표면에 해당
  float a  = roughness * roughness;
  float a2 = a * a;

  // GGX 분모: ((N·H)^2 (a^2 - 1) + 1)^2
  // NdotH가 1(=H가 N과 거의 같은 방향)일 때 D가 크게 나와 하이라이트가 생김
  float denom = (NdotH * NdotH) * (a2 - 1.0f) + 1.0f;

  // 정규화까지 포함한 GGX NDF
  return a2 / max(PI * denom * denom, 1e-6);
}</code></pre>
<h3 id="3-energy-conservation-에너지-보존">3. Energy Conservation (에너지 보존)</h3>
</li>
<li><p><em>개념*</em>
표면에서 나가는 빛(반사 + 확산)은 들어오는 빛을 넘을 수 없다.
Cook-Torrance는 보통 <strong>Specular 비율(kS)</strong>과 <strong>Diffuse 비율(kD)</strong>로 나눠 이를 만족시킨다.</p>
</li>
<li><p><code>kS</code>: 반사(specular)로 나가는 비율</p>
</li>
<li><p><code>kD</code>: 확산(diffuse)로 나가는 비율</p>
</li>
<li><p>보통 트레이드오프: kD = 1 - kS</p>
</li>
<li><p>Metallic workflow에서는 금속일수록 diffuse가 사라짐:</p>
<ul>
<li>metallic = 1 → diffuse 거의 0</li>
<li>metallic = 0 → dielectric → diffuse 유지</li>
</ul>
</li>
</ul>
<p><strong>Fresnel: kS(반사 비율)를 각도로 결정</strong>
Fresnel은 시선/입사 각도에 따라 반사가 증가하는 현상.
실시간에서는 보통 Schlick 근사를 사용한다.</p>
<ul>
<li>F0: 정면(수직)에서의 기본 반사율<ul>
<li>비금속: 대략 0.04 (4%)</li>
<li>금속: 반사 자체가 색을 띠므로 baseColor가 F0 역할을 함</li>
</ul>
</li>
<li>metalness 워크플로우의 F0:<ul>
<li><code>F0 = lerp(0.04, baseColor, metallic)</code></li>
</ul>
</li>
<li>Schlick:<ul>
<li><code>F = F0 + (1 - F0) * (1 - cosTheta)^5</code></li>
<li>cosTheta는 보통 dot(V, H) 계열 사용<pre><code class="language-HLSL">float NdotL = saturate(dot(N, L));
float NdotV = saturate(dot(N, V));
float NdotH = saturate(dot(N, H));
float VdotH = saturate(dot(V, H));
</code></pre>
</li>
</ul>
</li>
</ul>
<p>float3 F0 = lerp(float3(0.04, 0.04, 0.04), baseColor, metallic);</p>
<p>float3 F = FresnelSchlick(VdotH, F0);</p>
<p>float3 kS = F;
float3 kD = (1.0f - kS) * (1.0f - metallic);</p>
<pre><code>### 4. BRDF (Bidirectional Reflectance Distribution Function)
**개념**
BRDF는 **입사 방향(wi)와 출사 방향(wo)**에 따라 반사되는 빛의 비율을 반환하는 함수다.
Cook-Torrance BRDF는 보통 다음처럼 구성한다.
- Diffuse(확산): Lambert
  - `diffuse = baseColor / PI`
  - 에너지 보존을 위해 `kD`를 곱해 사용
- Specular(반사): Cook-Torrance
  - `pec = (D * G * F) / (4 * NdotV * NdotL)`
  - D: microfacet 분포 (GGX)
  - G: microfacet 가림(Geometry)
  - F: Fresnel (Schlick)

**Geometry(G): microfacet 가림(Shadowing/Masking)**
거칠수록 미세 요철이 서로를 더 가려서 스페큘러 기여가 줄어든다.
- View 방향에서 가림: Masking
- Light 방향에서 가림: Shadowing
- Smith 형태로 결합:
  - `G(N,V,L) = G1(N,V) * G1(N,L)`
</code></pre><p>float GeometrySchlickGGX(float NdotV, float roughness) // NdotL이 들어올 수 있음
{
    float r = roughness + 1.0f;
    float k = (r * r) / 8.0f; // direct lighting용
    return NdotV / max(NdotV * (1.0f - k) + k, 1e-6);
}</p>
<pre><code></code></pre><p>// G1: Schlick-GGX (direct lighting용 k 사용)
float GeometrySchlickGGX(float NdotV, float roughness)
{
    float r = roughness + 1.0f;
    float k = (r * r) / 8.0f; // direct lighting용
    return NdotV / max(NdotV * (1.0f - k) + k, 1e-6);
}</p>
<p>// Smith: view 방향 가림 * light 방향 가림
float GeometrySmith(float NdotV, float NdotL, float roughness)
{
    float ggxV = GeometrySchlickGGX(NdotV, roughness);
    float ggxL = GeometrySchlickGGX(NdotL, roughness);
    return ggxV * ggxL;
}</p>
<p>// Cook-Torrance specular BRDF 조립은 EvaluatePBR_Direct 내부에서:
// spec = (D<em>G</em>F) / (4<em>NdotV</em>NdotL)
// diffuse = baseColor/PI (에너지 보존 위해 kD 곱)
// 최종: (diffuse + spec) * radiance * NdotL</p>
<pre><code>
# 작업 결과

스켈레탈 메시

![](https://velog.velcdn.com/images/jm450_/post/09d9259d-3e8c-47de-b6d3-faab17a987d7/image.png)

스태틱 메시
![](https://velog.velcdn.com/images/jm450_/post/46cf53a0-4926-4f94-8fc7-a949bba76aae/image.png)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[UE5] MotionWarping 고찰]]></title>
            <link>https://velog.io/@jm450_/UE5-MotionWarping-%EA%B3%A0%EC%B0%B0</link>
            <guid>https://velog.io/@jm450_/UE5-MotionWarping-%EA%B3%A0%EC%B0%B0</guid>
            <pubDate>Mon, 02 Mar 2026 13:15:28 GMT</pubDate>
            <description><![CDATA[<h1 id="1-개요">1. 개요</h1>
<p>요즘 DX11로 애니메이션 기능들을 하나씩 만들어보고있다. 그 중 최근에 RootMotion을 구현했는데, 취준 때 루트모션과 비슷한 기능인 모션워핑을 사용한 경험이 생각났다. 모션 워핑에서는 어떻게 루트 모션을 사용했고, 달성한 결과는 무엇인지 알아보자.</p>
<p>[UE5] Assassin14 - Motion Warping을 사용한 암살1: <a href="https://velog.io/@jm450_/UE5-Assassin13-Motion-Warping%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-Assassination">https://velog.io/@jm450_/UE5-Assassin13-Motion-Warping%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-Assassination</a></p>
<p>[UE5] Assassin14 - Motion Warping을 사용한 암살2: <a href="https://velog.io/@jm450_/UE5-Assassin13-Motion-Warping%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%95%94%EC%82%B42">https://velog.io/@jm450_/UE5-Assassin13-Motion-Warping%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%9C-%EC%95%94%EC%82%B42</a></p>
<h1 id="2-모션-워핑">2. 모션 워핑</h1>
<p>Motion Warping은 캐릭터의 루트 모션이 <strong>워프 타깃</strong>과 일치하도록 애니메이션의 특정 구간을 동적으로 정렬해주는 기능이다.</p>
<h2 id="1-사용자-인터페이스">1) 사용자 인터페이스</h2>
<h3 id="애님-몽타주">애님 몽타주</h3>
<p>언제부터 언제까지 워핑할지를 AnimNotifyState(모션 워핑) 로 워핑 구간을 만든다.</p>
<ul>
<li>애님에서 RootTransform의 변화값이 있는 구간으로 설정해 자연스러운 이동을 설정 가능</li>
<li>Notify 프로퍼티<ul>
<li>Root Motion Modifier 타입<ul>
<li>Scale: 스케일을 “균등하게” 바꿔서 맞추는 워프<ul>
<li>Skew Warp: 워핑 구간 끝에 레벨상의 위치/회전이 타깃과 딱 맞도록 루트모션을 워프</li>
</ul>
</li>
</ul>
</li>
<li>Warp Target Name: 블루프린트에서 연결할 “타깃 이름”(이름이 반드시 일치해야 함)</li>
<li>Warp Translation / Warp Rotation: 이동/회전 워핑을 각각 켤지</li>
<li>Ignore ZAxis: 이동 워핑에서 Z를 무시할지</li>
<li>Rotation Type<ul>
<li>Default: 타깃의 회전과 일치</li>
<li>Facing: 타깃을 “바라보도록” 회전</li>
</ul>
</li>
<li>Warp Rotation Time Multiplier: 회전이 얼마나 빨리 목표 회전에 도달할지(예: 2초 구간에서 0.5면 1초만에 회전 완료)
<img src="https://velog.velcdn.com/images/jm450_/post/42d8024b-5cde-4b1f-8058-61331661338b/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="모션-워핑-컴포넌트">모션 워핑 컴포넌트</h3>
<p>“워프 타깃(목표 위치/회전)이 어디인지”를 이름으로 등록(Add/Update Warp Target) 한다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/c767c98d-fac1-4766-b69a-fce82a9a9cfb/image.png" alt=""></p>
<p>AnimNotifyState를 동적으로 추가할 수도 있다.</p>
<ul>
<li>하지만 WarpTarget도 동적으로 정하는데 구간도 동적으로 넣어줘야할까? </li>
<li>에셋 관리하는 입장에선 불안할 것 같다. </li>
<li>조건에 따라 넣어줘야 된다면 Notify에 Condition을 추가하는 방식으로 확장하는게 더 좋을 것 같다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jm450_/post/8fadf19b-ec10-4875-bfdf-9e5bb505743c/image.png" alt=""></p>
<h3 id="주의할-점">주의할 점</h3>
<p>해당 기능은 RootMotion을 기반으로 동작하기 때문에 RootMotion을 활성화해줘야한다.
<img src="https://velog.velcdn.com/images/jm450_/post/f11ce5a6-8661-4eb9-9239-75ceb2c5816c/image.png" alt=""></p>
<h2 id="2-내부-로직-보기">2) 내부 로직 보기</h2>
<p><code>AnimNotifyState_MotionWarping</code> 생성자를 보면 <code>RootMotionModifier_SkewWarp</code>를 사용하는 것을 알 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/0a7fca25-4bef-48b0-b436-ed88216a6174/image.png" alt=""></p>
<p>Modifier는 MotionWarpingComponent가 관리한다
<img src="https://velog.velcdn.com/images/jm450_/post/e88f5b1f-30f2-43e8-811d-9565d1f0dab7/image.png" alt=""></p>
<p>MotionWarpingComponent는 CharacterMovmentComponent, Character 등과 연결하기 위한 Adapter를 가지고 있고 해당 인스턴스의 이벤트를 받아 RootMotion을 Modify한다.</p>
<ul>
<li>Adapter를 쓰는 이유는 이동을 담당하는 Component가 여러개 인데, 여러개의 Componenet의 기능을 공통 인터페이스로 사용하기 위함이다</li>
<li>궁금한 사람은 MovementComponent, MoverComponent를 보길 바람<ul>
<li>Mover가 5.x에 나왔던걸로 기억하는데 두 기능 유지보수 하느라 에픽애들 좀 고생하는 것 같다. 예전엔 이슈도 많았는데 이젠 꼼꼼히 챙기는듯... (사실 안써봐서 잘모름)
<img src="https://velog.velcdn.com/images/jm450_/post/cf36c438-9a43-4ed8-9e28-081c2367fc9e/image.png" alt=""></li>
</ul>
</li>
</ul>
<p>ProcessRootMotionPreConvertToWorld 쪽 로직을 타면서 Modify된다.
<img src="https://velog.velcdn.com/images/jm450_/post/fa0bc8c7-b8d2-4b24-9d21-a9d60c0eac99/image.png" alt=""></p>
<p>이런식으로 MovementComp(또는 Mover)에서 RootMotion 데이터를 저장할 때 딜리게이트를 Broadcast하여 다른 시스템에서 PreProcess할 기회를 준다.</p>
<p>모션 워핑에서는 Pre 시점에 연결해서 PreProcess하고 있음
<img src="https://velog.velcdn.com/images/jm450_/post/4a4b0f82-22f9-4cca-b657-576deaff5613/image.png" alt=""></p>
<p>이후에는 RootMotion이 적용되는 것과 같다.</p>
<ul>
<li><p>RootMotionParams 데이터로 설정되고</p>
<ul>
<li>최종 RootMotion의 AnimTransform에 Accumulate 된다.</li>
<li>Translation은 Velocity로 적용</li>
<li>Rotation은 추가해서 MovementUpdate로 적용</li>
</ul>
</li>
<li><p>궁금한 사람은 <code>void UCharacterMovementComponent::PerformMovement(float DeltaSeconds)</code> 참고</p>
</li>
</ul>
<h2 id="3-의견">3) 의견</h2>
<p>기능적으로는 RootMotion에서 추가되는데, 코드적으로는 많이 분리해서 만들어둔 플러그인 기능이다.
RootMotion의 장점은 AnimSequence에서 잡은 키값을 그대로 Character에 적용함으로서 캐릭터 움직임을 현실적으로 할 수 있다는 장점이 있다. </p>
<p>반면에 모션 워핑의 경우 TargetTransform으로 이동시키는 기능이다보니 Transform계산을 코드 내부에서 하고 있고 이 부분에서 연출적으로 어색한 부분이 생길 수 밖에 없는 구조이다.
따라서 해당 기능을 사용한다면 걸음 같은 RootMotion Transform의 정확도가 중요한 애님보다 특정위치까지 점프해서 도달하는 등 역동적인 애님에 효과적인 기능이 될 수 있다. 전투 시스템에서 많이 사용할 수 있을 것으로 기대된다.</p>
<p>데이터 유지보수 관점에서 보면 모션 워핑 Notify를 사용함에도 RootMotion을 활성화 하지 않았다면 Warning Message를 출력하고 모션 워핑 이름도 휴먼에러가 날 수 있기 때문에 규칙화해서 넣어두면 ComboBox로 선택할 수 있는 구조로 만드는게 좋을 것 같다.
Settings같은 곳에 넣어두고 매핑된 이름만 사용하는 구조다. Settings에서 지울 때는 이미 이름을 사용하고 있는곳을 찾아서 제거할지 다이얼로그 띄워줘 데이터를 클린하게 핸들링 할 수 있어야된다. </p>
<p>결론적으론 제한적으로 사용할 수 밖에 없는 기능이고(자연스러운 연출적 관점) 그대로 사용하기엔 데이터 유지보수에서 어려운 부분이 있기 때문에 사용 전 규칙을 정하는 과정이 필요해보인다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[DX11] FBX Import]]></title>
            <link>https://velog.io/@jm450_/DX11-FBX-Import</link>
            <guid>https://velog.io/@jm450_/DX11-FBX-Import</guid>
            <pubDate>Sun, 25 Jan 2026 08:56:08 GMT</pubDate>
            <description><![CDATA[<h1 id="목표">목표</h1>
<p>StaticMesh를 렌더할 수 있도록 엔진을 만들었다.
해당 엔진에 FBX 파일을 임포트하여, StaticMesh로 변환한 뒤 렌더하는 기능을 구현한다.</p>
<h1 id="아이디어">아이디어</h1>
<ul>
<li>IMGUI를 통해 모델 파일경로 입력</li>
<li>Assimp을 통해 FBX 파일 읽기</li>
<li>읽어온 ai 데이터를 엔진 데이터로 변환<ul>
<li>MeshAsset</li>
<li>MaterialInstance</li>
</ul>
</li>
</ul>
<blockquote>
<p>서브 메시 개념은 생각하지 못해 이후에 도입했습니다.</p>
</blockquote>
<h1 id="구현-내용">구현 내용</h1>
<h2 id="1-assimp-임포트">1. Assimp 임포트</h2>
<h3 id="11-임포트-플래그">1.1. 임포트 플래그</h3>
<p>Assimp에서 제공하는 플래그를 통해 내가 만든 엔진 구조에 맞게 읽음</p>
<ul>
<li>aiProcess_Triangulate<ul>
<li>모든 면을 삼각형으로 변환</li>
</ul>
</li>
<li>aiProcess_JoinIdenticalVertices<ul>
<li>동일 버텍스 병합</li>
</ul>
</li>
<li>aiProcess_ImproveCacheLocality<ul>
<li>GPU 캐시 효율 향상을 위한 인덱스 재정렬</li>
</ul>
</li>
<li>aiProcess_SortByPType<ul>
<li>Primitive 타입별 정리</li>
</ul>
</li>
<li>aiProcess_FlipUVs<ul>
<li>UV 반전</li>
</ul>
</li>
<li>aiProcess_ConvertToLeftHanded<ul>
<li>좌수계 변환</li>
</ul>
</li>
</ul>
<h3 id="12-assimp-데이터-구조">1.2. Assimp 데이터 구조</h3>
<p><strong>aiScene</strong>
<code>Assimp::Importer::ReadFile()</code>의 결과는 <code>aiScene*</code>이며, 이 객체가 임포트된 모델의 루트 컨테이너다.</p>
<p>주요 멤버</p>
<ul>
<li><code>scene-&gt;mRootNode</code> : 노드 트리의 루트</li>
<li><code>scene-&gt;mMeshes[]</code> : 메시 배열(실제 geometry 데이터)</li>
<li><code>scene-&gt;mMaterials[]</code> : 머티리얼 배열</li>
<li><code>scene-&gt;mNumTextures / mTextures[]</code> : embedded texture(내장 텍스처) 배열</li>
</ul>
<blockquote>
<p>이걸 봤을 때 Mesh와 Material이 배열인걸 보고 뭔가 잘못됐다 생각함. 내 엔진은 StaticMeshComp 1--1 MeshData 1--1 Material 1대1구조임</p>
</blockquote>
<p>** aiNode **
Assimp 구조에서 <strong>노드(aiNode)는 트랜스폼을 가진다</strong>. 
반면, 메시 데이터는 <code>scene-&gt;mMeshes[idx]</code>에 있다.</p>
<ul>
<li>노드는 <code>node-&gt;mMeshes[]</code>로 <strong>참조하는 mesh index</strong>를 보유</li>
<li>실제 버텍스/인덱스는 <code>scene-&gt;mMeshes[meshIndex]</code>에 존재</li>
<li>따라서 FBX 같은 포맷은 보통 <strong>node 트리를 순회하면서 누적 트랜스폼을 적용</strong>해야 제대로 보인다.</li>
</ul>
<p>최종 코드에서는 DFS로 노드를 순회하며 누적 트랜스폼을 만든다:</p>
<ul>
<li><code>global = parent * node-&gt;mTransformation</code></li>
<li>각 node가 가진 mesh index에 대해 <code>scene-&gt;mMeshes[meshIndex]</code>를 처리</li>
</ul>
<h2 id="2-mesh-빌드">2. Mesh 빌드</h2>
<h3 id="21-버텍스-생성">2.1. 버텍스 생성</h3>
<ul>
<li>Position: <code>p&#39; = global * p</code></li>
<li>Normal: <code>inverse-transpose(upper3x3(global))</code> 적용 후 normalize</li>
</ul>
<p>이를 통해 노드 트랜스폼(특히 스케일/회전)이 포함된 올바른 월드 공간 지오메트리를 얻는다.</p>
<h3 id="22-머지-시-고려할-점">2.2. 머지 시 고려할 점</h3>
<p>기존 엔진 구조로 임포트 하기 위해 하나의 aiMesh만 사용해 임포트 하였더니 특정 부위가 정상적으로 구성되지 않은 것을 확인했다.</p>
<p><code>aiNode</code>가 N개 이고, 다른 meshIndex를 사용하는 경우였다. 이를 고려해 <code>ChildNode</code>를 순회하며 버텍스를 생성해줘야한다.</p>
<blockquote>
<p>ChildNode의 버텍스 정보는 캐싱해둬야 한다. 자세한 내용은 Material에서 다룬다.  </p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jm450_/post/e11508f7-1098-47b0-8707-5628c63bb985/image.png" alt=""></p>
<h2 id="3-material-빌드">3. Material 빌드</h2>
<h3 id="31-texture-로드">3.1. Texture 로드</h3>
<p>텍스쳐 경로에 따라 처리 로직을 나눈다</p>
<p><strong>텍스쳐가 FBX에 포함되어 있는 경우</strong></p>
<ul>
<li><code>if (const aiTexture* at = scene-&gt;GetEmbeddedTexture(t8.c_str()))</code>를 통해 가져오기 시도</li>
<li><code>CreateSRVFromEmbeddedRawBGRA8</code>를 통해 SRV 변환</li>
</ul>
<p><strong>scene-&gt;mTextures를 레퍼런스 하는 경우</strong></p>
<ul>
<li>texture path string이 <code>&quot;*index&quot;</code> 형태인지 검사</li>
<li><code>scene-&gt;mTextures</code>접근하여 <code>CreateSRVFromEmbeddedRawBGRA8</code>를 통해 SRV 변환</li>
</ul>
<p><strong>외부 경로인지 확인</strong></p>
<ul>
<li>경로에 파일이 있는지 확인하여 로드</li>
<li><code>DirectX::CreateWICTextureFromFile</code></li>
</ul>
<h3 id="32-로드시-고려할-점">3.2. 로드시 고려할 점</h3>
<p>위에서 봤듯이 <code>scene-&gt;mMaterials[]</code>은 배열이다.
각각의 메시가 참조하는 머티리얼이 다르다. 
따라서 엔진에서도 메시를 작은 단위 (굳이 노드가 아니여도 됨)로 나누고 작은 단위의 메시 마다 머티리얼 참조를 할 수 있는 구조를 만들어야한다.</p>
<p>나는 하나의 머티리얼만 사용하는 줄 알고 0번을 레퍼런스 하도록 구현했었는데 아래와 같이 0번 머티리얼을 사용하지 않는 메시의 머티리얼이 비정상적인 것을 볼 수 있다
<img src="https://velog.velcdn.com/images/jm450_/post/307ad5fa-ecb6-44da-af2e-4ce2795fe39f/image.png" alt=""></p>
<p>이를 해결하기 위해 아래와 같이 대응했다</p>
<ul>
<li>메시를 Section으로 나눌 수 있도록 데이터 구조 변경</li>
<li>Section 마다 MaterialIndex를 가지고 있도록 변경</li>
<li>MaterialInstance를 Array로 가지고 있도록 변경
<img src="https://velog.velcdn.com/images/jm450_/post/0d8d0d1d-c8fd-42ae-9c2e-a6157cde6889/image.png" alt=""></li>
<li>Section마다 나눠 DrawCall
<img src="https://velog.velcdn.com/images/jm450_/post/d75df017-a236-491e-8b51-862b4ee38009/image.png" alt=""></li>
</ul>
<h1 id="결과">결과</h1>
<h2 id="1-메시-normalvector-임포트-잘-되었는지-확인">1. 메시 NormalVector 임포트 잘 되었는지 확인</h2>
<p><img src="https://velog.velcdn.com/images/jm450_/post/84941a43-ccb9-455a-a177-70528fd2282b/image.png" alt=""></p>
<h2 id="2-메시-데이터-및-머티리얼-임포트-확인">2. 메시 데이터 및 머티리얼 임포트 확인</h2>
<ul>
<li>오른쪽<ul>
<li>최종 결과</li>
</ul>
</li>
<li>왼쪽<ul>
<li>제공된 FBX 파일</li>
</ul>
</li>
<li>중간<ul>
<li>임포트 실패 과정...</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jm450_/post/ac8d2b0e-53a1-4640-a386-a4ef36ff41f2/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] BSP (Binary Space Partitioning)]]></title>
            <link>https://velog.io/@jm450_/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-BSP-Binary-Space-Partitioning</link>
            <guid>https://velog.io/@jm450_/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-BSP-Binary-Space-Partitioning</guid>
            <pubDate>Tue, 28 Oct 2025 08:17:20 GMT</pubDate>
            <description><![CDATA[<h1 id="bsp-binary-space-partitioning">BSP (Binary Space Partitioning)</h1>
<p>재귀적으로 유클리드 공간을 초평면상의 <strong>볼록 집합</strong>으로 분할하는 알고리즘</p>
<ul>
<li>하나의 공간을 두 개씩 나누어 가는 과정을 재귀적으로 실행</li>
<li>맵 렌더링, 충돌 감지 등에 최적화를 위해 사용</li>
</ul>
<h1 id="공간-분할-실행-흐름">공간 분할 실행 흐름</h1>
<ol>
<li>하나의 분할 평면을 선택</li>
<li>하나의 분할 평면을 두 개의 하위 공간으로 분할</li>
<li>각 하위 공간에 대해 1~2 과정을 재귀적으로 반복</li>
<li>원하는 기준 (최종 목적)에 도달할 때 까지 해당 과정을 수행<blockquote>
<p>해당 과정을 거쳐 만들어진 트리를 <strong>BSP 트리</strong> 라고 부르며, <strong>노드는 분할 평면 정보</strong>를 <strong>리프노드는 최종적으로 분할이 완료된 공간 정보</strong>를 담게된다.</p>
</blockquote>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/jm450_/post/a02194c6-7743-4181-914f-34b7ca66b558/image.png" alt=""></p>
<h1 id="재귀-종료-조건">재귀 종료 조건</h1>
<p>BSP는 다양한 용도로 사용되고, 용도에 따라 언제까지 재귀를 실행할지 결정해야 한다.</p>
<h3 id="1-렌더링-최적화">1. 렌더링 최적화</h3>
<p><strong>용도</strong>
시야에 보이는 부분만 효율적으로 렌더링하기 위해 공간 분할하여 가시성을 판단</p>
<p><strong>종료 조건</strong>
각 리프 노드가 시야 계산이 필요없는 수준, 렌더링 퀄리티에 영향을 미치지 않는 수준이 된다면, 더 이상 분할할 필요가 없음</p>
<h3 id="2-충돌-검출">2. 충돌 검출</h3>
<p><strong>용도</strong>
물리 엔진에서 객체 충돌을 빠르게 판단하기 위해 공간 분할</p>
<p><strong>종료 조건</strong>
분할된 리프 공간 내부에 포함된 오브젝트 수가 일정 수준 이하가 되면 더 이상 분할하지 않음</p>
<h3 id="3-레이-트레이싱">3. 레이 트레이싱</h3>
<p><strong>용도</strong>
광선을 쏠 때 충돌 가능성이 있는 폴리곤 오브젝트간 빠르게 찾기 위해 BSP 트리를 사용</p>
<p><strong>종료 조건</strong>
광선 검출에 필요한 정확도를 만족할 만큼의 분할이 이루어지면 어 이상 분할하지 않음</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UE5] UObject]]></title>
            <link>https://velog.io/@jm450_/UE5-UObject</link>
            <guid>https://velog.io/@jm450_/UE5-UObject</guid>
            <pubDate>Wed, 19 Feb 2025 15:10:37 GMT</pubDate>
            <description><![CDATA[<h1 id="uobject">UObject</h1>
<hr>
<p>언리얼은 게임 객체를 처리하기 위한 강력한 시스템을 가지고 있다.
언리얼의 객체에 대한 기본 클래스는 <code>UObject</code>이다.
<code>UCLASS</code> 매크로는 <code>UObject</code>처리 시스템이 이를 인식할 수 있도록 <code>UObject</code>에서 파생된 클래스에 태그를 지정하는 데 사용할 수 있다.</p>
<h1 id="uclass">UCLASS</h1>
<hr>
<p>UClass 매크로는 언리얼 기반 타입을 설명하는 <code>UCLASS</code>에 대한 레퍼런스를 <code>UObject</code>에 제공합니다.
각 UClass는 클래스 디폴트 오브젝트(CDO, Class Default Object)라는 하나의 오브젝트를 유지합니다.
CDO는 기본적으로 클래스 생성자에 의해 생성되고 이후에는 수정되지 않는 디폴트 템플릿 오브젝트 입니다.</p>
<p>UCLASS와 CDO는 보토 읽기 전용으로 간주되어야 하지만, 주어진 오브젝트 인스턴스에 대해 얻을 수 있습니다.
오브젝트 인스턴스에 대한 UCLASS는 <code>GetClass</code>함수를 사용하여 언제든지 엑세스할 수 있습니다.</p>
<p>UCLASS에는 클래스를 정의하는 프로퍼티와 함수 세트가 포함되어 있습니다.
이는 표준 C++ 코드에 사용할 수 잇는 일반 C++함수 및 변수이지만, 오브젝트 시스템 내에서 행동하는 방식을 제어하는 언리얼 엔진 전용 메타데이터가 태그로 지정되어 있습니다. </p>
<blockquote>
<p>UObject 클래스에는 UFUNCTION 또는 UPROPERTY 지정자로 리플렉션이 표시되지 않은 네이티브 전용 프로퍼티가 포함될 수 있습니다. 그러나 지정자 매크로로 표시된 함수와 프로퍼티만 해당 UCLASS 내에 나열됩니다.</p>
</blockquote>
<h1 id="프로퍼티-및-함수-타입">프로퍼티 및 함수 타입</h1>
<p>UObject는 모든 타입의 함수 또는 멤버 변수(프로퍼티라고도 함)를 가질 수 있습니다. 그러나 언리얼 엔진이 이러한 변수나 함수를 인식하고 조작하려면 특수 매크로로 표시하고 특정 타입 표준을 준수해야 합니다.</p>
<h1 id="uobject-생성">UObject 생성</h1>
<p>UObject는 생성자 실행인자를 지원하지 않습니다. 모든 C++ UObject는 엔진 시작 시 초기화되며, 엔진은 디폴트 생성자를 호출합니다. 디폴트 생성자가 없으면 UObject가 컴파일되지 않습니다.
UObject 생성자는 가벼워야 하고 디폴트값과 서브오브젝트를 구성하는 데에만 사용되어야 하며, 생성 시 다른 함수 기능을 호출해서는 안 됩니다.
UObject는 런타임에 NewObject를 사용하거나 생성자의 경우 CreateDefaultSubobject를 사용하여 생성해야 합니다.</p>
<blockquote>
</blockquote>
<p>UObject는 절대로 new 연산자를 사용하면 안 됩니다. 모든 UObject는 언리얼 엔진으로 관리되는 메모리이며 가비지 컬렉션됩니다. new 또는 delete를 사용하여 메모리를 수동으로 관리하면 메모리가 손상될 수 있습니다.</p>
<h1 id="언리얼-헤더-툴">언리얼 헤더 툴</h1>
<hr>
<p>UObject 파생 타입이 제공하는 기능을 활용하려면 해당 타입에 대한 헤더 파일에 전처리 단계를 실행하여 필요한 정보를 대조해야 합니다. 
이 전처리 단계는 UnrealHeaderTool, 줄여서 UHT에서 수행합니다. UObject 파생 타입에는 준수해야 하는 특정 구조가 있습니다.</p>
<h1 id="헤더-파일-포맷">헤더 파일 포맷</h1>
<p>소스(.cpp) 파일의 UObject 구현은 다른 C++ 클래스와 같지만, 헤더(.h) 파일의 정의는 언리얼 엔진에서 제대로 작동하려면 특정 기본 구조를 준수해야 합니다. 에디터의 New C++ Class 명령을 사용하는 것은 올바른 포맷의 헤더 파일을 구성하는 가장 쉬운 방법입니다. UObject 파생 클래스의 이름이 UMyObject이고 이 클래스가 생성된 프로젝트가 MyProject라고 할 때 UObject 파생 클래스의 기본 헤더 파일은 다음과 같습니다.</p>
<pre><code>    #pragma once

    #include &#39;Object.h&#39;
    #include &#39;MyObject.generated.h&#39;

    UCLASS()
    class MYPROJECT_API UMyObject : public UObject
    {
        GENERATED_BODY()

    };</code></pre><p>언리얼 관련 부분은 다음과 같습니다.</p>
<pre><code>#include &quot;MyObject.generated.h&quot;</code></pre><p>이 줄은 파일의 마지막 #include 지시문이 될 것입니다. 이 헤더 파일이 다른 클래스에 대해 알아야 하는 경우 해당 클래스를 파일의 어느 곳에서나 포워드 선언하거나 MyObject.generated.h 위에 포함할 수 있습니다.</p>
<pre><code>UCLASS()</code></pre><p>UCLASS 매크로는 언리얼 엔진에 UMyObject 가 표시되도록 합니다. 매크로는 클래스에 대해 어떤 기능을 켜거나 끌지 결정하는 다양한 클래스 지정자를 지원합니다.</p>
<pre><code>class MYPROJECT_API UMyObject : public UObject</code></pre><p>MyProject가 UMyObject 클래스를 다른 모듈에 노출시키기를 원한다면 MYPROJECT_API 를 지정해야 합니다. 이는 게임 프로젝트에 포함될 모듈이나 플러그인에 가장 유용하며 여러 프로젝트에 걸쳐 이식 가능하고 자체 포함된 함수 기능을 제공하기 위해 의도적으로 클래스를 노출합니다.</p>
<pre><code>GENERATED_BODY()</code></pre><p>GENERATED_BODY 매크로는 실행인자를 사용하지 않지만 엔진에 필요한 인프라를 지원하기 위해 클래스를 구성합니다. 모든 UCLASS 및 USTRUCT 에 필요합니다.</p>
<h1 id="오브젝트-업데이트하기">오브젝트 업데이트하기</h1>
<p>티킹(Ticking)은 언리얼 엔진에서 오브젝트가 업데이트되는 방식을 말합니다. 모든 액터에는 프레임마다 티킹할 수 있는 기능이 있어 필요한 업데이트 계산이나 액션을 수행할 수 있습니다.</p>
<p>액터 및 액터 컴포넌트(ActorComponents)에는 등록 시 자동으로 호출되는 틱(Tick) 함수가 있지만, UObjects 에는 기본 업데이트 기능이 없습니다. 프로젝트에 필요한 경우 상속 클래스 지정자를 사용해 FTickableGameObject 클래스에서 상속하여 추가할 수 있습니다. 그런 다음 엔진이 각 프레임을 호출하는 Tick() 함수를 구현할 수 있습니다.</p>
<h1 id="오브젝트-파괴하기">오브젝트 파괴하기</h1>
<p>오브젝트가 더 이상 레퍼런스되지 않을 때 가비지 컬렉션 시스템이 자동으로 오브젝트를 파괴합니다. 즉, UPROPERTY 포인터, 엔진 컨테이너, TStrongObjectPtr 또는 클래스 인스턴스가 강한 참조를 가져서는 안 된다는 뜻입니다.</p>
<blockquote>
<p>위크 포인터는 오브젝트가 가비지 컬렉션되는지 여부에 영향을 미치지 않습니다.</p>
</blockquote>
<p>가비지 컬렉터를 실행하면, 참조되지 않은 오브젝트를 발견하면 메모리에서 제거합니다. 또한 MarkPendingKill() 함수를 오브젝트에서 바로 호출할 수 있습니다. 이 함수는 오브젝트에 대한 모든 포인터를 NULL 로 설정하고 글로벌 검색에서 오브젝트를 제거합니다. 오브젝트는 다음 가비지 컬렉션 패스에서 완전히 삭제됩니다.</p>
<blockquote>
<p>스마트 포인터는 UObject와 함께 사용하기 위한 것이 아닙니다.</p>
</blockquote>
<p><code>Object-&gt;MarkPendingKill()</code> 은 <code>Obj-&gt;MarkAsGarbage()</code> 로 대체되었습니다. 이 새로운 함수는 이제 오래된 오브젝트를 추적하는 용도로만 사용됩니다. <code>gc.PendingKillEnabled=true</code> 인 경우 <code>PendingKill</code> 로 표시된 오브젝트는 자동으로 <code>null</code>이 되고 가비지 컬렉터에 의해 삭제됩니다.</p>
<p>강한 참조가 있으면 UObject가 유지됩니다. 이러한 레퍼런스가 UObject를 활성 상태로 유지하는 것을 원하지 않는 경우 해당 레퍼런스가 위크 포인터를 사용하도록 변환하거나 (퍼포먼스가 중요한 경우) 프로그래머가 수동으로 삭제하는 노멀 포인터여야 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UE5/Graphics] Signed Distance Field]]></title>
            <link>https://velog.io/@jm450_/UE5-Signed-Distance-Field</link>
            <guid>https://velog.io/@jm450_/UE5-Signed-Distance-Field</guid>
            <pubDate>Sat, 04 Jan 2025 16:49:09 GMT</pubDate>
            <description><![CDATA[<h1 id="sdf란">SDF란?</h1>
<hr>
<p>특정 점에서 객체까지의 거리를 0~1로 나타낸 것이다.
아래 사진에서 원의 표면에 있는 점의 값은 1, 우측상단 점의 값은 0이다.
<img src="https://velog.velcdn.com/images/jm450_/post/c133e8f5-8209-44cb-a1e8-cb6dc1b080a9/image.png" alt=""></p>
<p>표면에서 멀어질 수록 0에 가까워지고 가까울 수록 밝게, 멀 수록 어둡게 표현한 것이다.
멀다(어둡다, 0) - 가깝다(밝다, 1)
<img src="https://velog.velcdn.com/images/jm450_/post/9c10ea06-b86b-4cae-9e3c-5ec359d47733/image.png" alt=""></p>
<p>이제 객체 내부에서도 거리 값을 설정해봅시다.
객체의 표면이 아닌 <strong>중심</strong>을 기준으로 거리값을 설정한다.
<img src="https://velog.velcdn.com/images/jm450_/post/2354cde3-13ab-4bac-bcac-9b963603d47b/image.png" alt=""></p>
<h1 id="distance-field-사용처">Distance Field 사용처</h1>
<hr>
<h2 id="렌더링-범위-설정">렌더링 범위 설정</h2>
<p>SDF를 통해 셰이더에서 렌더링 범위를 설정할 수 있습니다.</p>
<p>원을 아래 값을 통해 렌더링 해봅시다.
최소 거리 값: 0.499
최대 거리 값: 0.5
<img src="https://velog.velcdn.com/images/jm450_/post/ed316b06-f284-4748-abf3-ee6666142dc0/image.png" alt=""></p>
<h2 id="glow-표현">Glow 표현</h2>
<p>거리 값에 따라 표현이 달라지는 것이 활용하여 글로우한 표현을 할 수 있습니다.
최소, 최대 거리 값 차이가 커질 수록 더 흐릿한 이미지를 얻을 수 있으니 이를 활용할 수 있습니다.</p>
<p>예시를 봅시다.</p>
<p>TextureSample로 가장 흐릿한 이미지를 사용합니다.
흐릿한 이미지의 흐릿한 부분만 필요하니, 필요없는 부분은 빼줍니다.
최소/최대 차이 값으로 흐릿한 이미지의 거리값을 나눠줍니다.
나눠진 값을 사용하여 Glow 표현을 완성합니다. (파란 부분)
<img src="https://velog.velcdn.com/images/jm450_/post/51c9f4d6-472b-4462-9e85-dc56052af537/image.png" alt=""></p>
<p>머티리얼에서 값을 설정할 수도 있고,
<img src="https://velog.velcdn.com/images/jm450_/post/e180c79e-8b47-4c20-b441-2cf69fe6b006/image.png" alt=""></p>
<p>UMG에서 사용한다면 애니메이션을 통해 값을 변경하여 동적인 표현도 가능합니다.
<img src="https://velog.velcdn.com/images/jm450_/post/3a2a684d-bc51-4d65-af1d-934a2016776c/image.png" alt=""></p>
<h1 id="signed-distance-field-사용처">Signed Distance Field 사용처</h1>
<hr>
<h2 id="외곽선-그리기">외곽선 그리기</h2>
<p>절대값을 취한 뒤 외곡선 두께를 빼서 뒤집으면 양파 껍질처럼 외곽선을 그릴 수 있습니다.
<img src="https://velog.velcdn.com/images/jm450_/post/8e7f743e-5d01-4c57-a36f-2e2a9ceb3206/image.png" alt=""></p>
<h2 id="패턴-및-다양한-효과-제작">패턴 및 다양한 효과 제작</h2>
<p>sine을 통해 패턴을 그릴 수도 있고
<img src="https://velog.velcdn.com/images/jm450_/post/db2bae01-bd4c-4bb1-8feb-c0d3916b0526/image.png" alt=""></p>
<p>컬러 채널을 사용하는 SDF를 만들고, N개의 아이콘에서 사용할 채널 값을 다르게 하여 하나의 SDF로 뽑으면 변형된 이미지를 렌더링할 수 있습니다.
UMG에서 애니메이션으로 프로퍼티 값을 조정하면 멋진 위젯 애니메이션이 됩니다.
<img src="https://velog.velcdn.com/images/jm450_/post/9d334f55-8115-4d25-a2fb-fa4a93168a37/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jm450_/post/a1eac059-fbcf-4e76-aed4-4a35777bbedc/image.png" alt=""></p>
<h1 id="signed-distance-field-value-range">Signed Distance Field Value Range</h1>
<hr>
<p>값 범위는 일반적으로 0~1를 사용한다.</p>
<ul>
<li><code>0 &lt;= Value &lt; 0.5</code>: 객체 내부</li>
<li><code>Value == 0.5</code>: 객체 표면</li>
<li><code>0.5 &lt; Value &lt;= 1</code>: 객체 외부</li>
</ul>
<p>직관성을 위해 -1 ~ 1로 재 맵핑하여 사용할 수 있다.
변환법: <code>(Value-0.5) * 2</code></p>
<ul>
<li><code>-1 &lt;= Value &lt; 0</code>: 객체 내부</li>
<li><code>Value == 0</code>: 객체 표면</li>
<li><code>1 &lt; Value &lt;= 1</code>: 객체 외부</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jm450_/post/72773e98-d5a1-48c9-8bea-1258afb91533/image.png" alt=""></p>
<p>음/양 분리 외에 <strong>채널을 분리</strong>하여 사용하는 경우도 있다고 한다.</p>
<h1 id="참고-자료">참고 자료</h1>
<p><a href="https://joyrok.com/What-Are-SDFs-Anyway">https://joyrok.com/What-Are-SDFs-Anyway</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UE5] Editor Utility Widget]]></title>
            <link>https://velog.io/@jm450_/UE5-Editor-Utility-Widget</link>
            <guid>https://velog.io/@jm450_/UE5-Editor-Utility-Widget</guid>
            <pubDate>Sun, 26 May 2024 12:23:16 GMT</pubDate>
            <description><![CDATA[<h1 id="1-editor-utility-widget">1. Editor Utility Widget</h1>
<ul>
<li>UMG 기반으로 에디터 UI를 추가 가능
<code>UEditorUtilityWidget : public UUserWidget</code></li>
<li>커스텀 에디터 탭을 만들 수 있음</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UE5] TSoftObjectPtr]]></title>
            <link>https://velog.io/@jm450_/UE5-TSoftObjectPtr</link>
            <guid>https://velog.io/@jm450_/UE5-TSoftObjectPtr</guid>
            <pubDate>Sat, 13 Apr 2024 13:40:56 GMT</pubDate>
            <description><![CDATA[<h1 id="1-tsoftobjectptr">1. TSoftObjectPtr</h1>
<hr>
<ul>
<li><code>FSoftObjectPath</code>: 에셋의 전체이름이 저장된 스트링 구조체</li>
<li><code>TSoftObjectPtr</code>: <code>FSoftObjectPath</code>를 감싸는 <code>TWeakObjectPtr</code></li>
</ul>
<h1 id="2-asset-registry-object-library">2. Asset Registry&amp; Object Library</h1>
<hr>
<ul>
<li><code>AssetRegistry</code>: 에셋의 메타데이터를 저장해 에셋에 대한 검색 및 질의를 가능하게 해주는 시스템<ul>
<li>에디터에서 컨텐츠 브라우저에 정보를 표시하기 위해 사용</li>
<li>게임플레이 코드에서 현재 로드되지 않은 게임플레이 에셋에 대한 메타데이터 질의를 하는데 사용 가능</li>
<li>검색 가능하게 만들기 위해 <code>AssetRegistrySearchable</code> 프로퍼티 태그를 추가해야 한다</li>
</ul>
</li>
<li><code>ObjectLibrary</code>: 로드된 오브젝트 리스트와 로드 되지 않은 오브젝트는 FAssetData리스트로 가지고 있는 오브젝트<ul>
<li><code>ObjectLibrary</code>에 특정 경로로 로드하면 해당 경로에 있는 모든 에셋이 로드 됨</li>
</ul>
</li>
</ul>
<h1 id="3-streamablemanager와-비동기-로드">3. StreamableManager와 비동기 로드</h1>
<ul>
<li><code>FStreamableMananger</code>: 비동기 로딩을 수행</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UE5] Slate]]></title>
            <link>https://velog.io/@jm450_/UE5-Slate</link>
            <guid>https://velog.io/@jm450_/UE5-Slate</guid>
            <pubDate>Sat, 23 Mar 2024 06:54:11 GMT</pubDate>
            <description><![CDATA[<h1 id="1-slate란">1. Slate란</h1>
<hr>
<p>언리얼 엔진에서 제공되는 커스텀 가능한 UI 프레임워크다. slate는 서술형 문법을 사용하여 프로그래머가 인디렉션 레이어를 추가하지 않고 쉽게 UI를 제작 가능한 기능을 제공한다. 또한 Composition을 통해 반복적으로 사용되는 요소들을 빠르게 배치할 수 있도록 구성되어 있다.</p>
<h1 id="2-widget과-slate">2. Widget과 Slate</h1>
<hr>
<p>언리얼에서 UI를 제작할 때 UMG를 사용해 제작한다. 그렇다면 Slate와 Widget은 뭐가 다른 것일까? 사실 Widget은 Slate의 껍데기(Container)일 뿐이다. 실질적으로 화면에 보여주기 위한 기능들은 Slate에 있고 Widget은 Slate를 제어하기 위한 데이터를 가지고 있을 뿐이다. 우리는 <code>SetText()</code>같은 함수들로 Widget의 데이터를 제어하고 있을 뿐이다.</p>
<h1 id="3-왜-역할을-분리했을까">3. 왜 역할을 분리했을까?</h1>
<hr>
<p>결국 Slate가 실질적으로 View의 역할을 하고있는데 Widget으로 왜 Warpping하고 있는 것일까? 이건 MVC 구조를 이해하고 있다면 쉽게 이해할 수 있다. </p>
<p>Slate는 보여줄 데이터를 직접 가지고 있지 않고 필요할 때 마다 딜리게이트를 브로드캐스트해 값을 가져온다. (TreeView에서 Getchildren을 사용하면 이해가 바로 된다.) 따라서 Slate는 화면에 보여주는 역할(View), 데이터를 전달하는 역할(Controller)을 수행하고 Widget은 데이터를 가지고 있는 역할(Model)을 수행한다.</p>
<p>그렇기 때문에 Slate는 피드백 루프 방식이 아닌 폴링을 통해 화면에 그려준다.</p>
<p><strong>세줄 요약</strong></p>
<ul>
<li>언리얼에서 UI는 Slate로 컴포지션화 하여 빠르게 만들 수 있다.</li>
<li>Widget은 Slate를 가지고 있어 실질적으로 화면에 보여지는 것은 Slate다.</li>
<li>Slate는 데이터를 가지고 있지 않고 필요할 때 마다 딜리게이트로 Widget의 데이터를 가져온다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jm450_/post/045d8184-5dba-4a06-b354-9365e3d2a14d/image.png" alt=""></p>
<p>Scrolling한 위젯들을 가상화시키는게 언리얼이 추구하는 방향성 같은데 관련해서 ListView(Tile, Tree도 있음)이 위젯을 풀링하는 방식도 궁금하다. 이건 대충 이해하긴 했는데 다음에 포스팅하면서 정리하겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[UE5] 이벤트와 딜리게이트]]></title>
            <link>https://velog.io/@jm450_/UE5-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EC%99%80-%EB%94%9C%EB%A6%AC%EA%B2%8C%EC%9D%B4%ED%8A%B8</link>
            <guid>https://velog.io/@jm450_/UE5-%EC%9D%B4%EB%B2%A4%ED%8A%B8%EC%99%80-%EB%94%9C%EB%A6%AC%EA%B2%8C%EC%9D%B4%ED%8A%B8</guid>
            <pubDate>Sun, 28 Jan 2024 06:35:29 GMT</pubDate>
            <description><![CDATA[<h2 id="1-딜리게이트delegate">1. 딜리게이트(Delegate)</h2>
<ul>
<li>C++오브젝트 사으이 멤버 함수를 안전한 방식으로 호출 가능</li>
<li>오브젝트의 멤버 함수에 동적으로 바인딩 가능</li>
<li>값 복사 시 동적으로 Heap에 메모리를 할당해야 되기 때문에 참조 전달을 지향</li>
</ul>
<table>
<thead>
<tr>
<th align="left"><center>딜리게이트 종류</center></th>
<th align="left"><center>설명</center></th>
</tr>
</thead>
<tbody><tr>
<td align="left"><center>싱글캐스트 딜리게이트</center></td>
<td align="left">1:1 통신으로 한 객체에 한 함수만 호출 가능</td>
</tr>
<tr>
<td align="left"><center>멀티캐스트 딜리게이트</center></td>
<td align="left">1:n 통신으로 한 함수를 호출하면 해당 함수를 가지고 있는 바인딩된 모든 객체를 호출</td>
</tr>
<tr>
<td align="left"><center>다이나믹 딜리게이트</center></td>
<td align="left">런타임에 동적 바인딩이 가능한 딜리게이트</td>
</tr>
</tbody></table>
<h2 id="2-이벤트event">2. 이벤트(Event)</h2>
<ul>
<li><code>MultiCastDelegate</code>와 일반적으로 비슷하지만 <code>Event</code>를 선언한 클래스만이 <code>Broadcast</code>, <code>IsBound</code>, <code>Clear</code>함수를 호출 가능<ul>
<li>함수를 접근을 제한하여 접근권을 확보할 때 사용 가능</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[디자인 패턴] 프록시(Proxy) 패턴]]></title>
            <link>https://velog.io/@jm450_/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%ED%94%84%EB%A1%9D%EC%8B%9CProxy-%ED%8C%A8%ED%84%B4</link>
            <guid>https://velog.io/@jm450_/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-%ED%94%84%EB%A1%9D%EC%8B%9CProxy-%ED%8C%A8%ED%84%B4</guid>
            <pubDate>Sun, 28 Jan 2024 05:51:44 GMT</pubDate>
            <description><![CDATA[<h2 id="1-proxy-pattern">1. Proxy Pattern</h2>
<ul>
<li>대상 원본 객체를 대리하여 대신 처리하게 함으로써 로직의 흐름을 제어하는 행동 패턴</li>
<li><code>client</code> -&gt; <code>Proxy</code> -&gt; <code>Subject</code> 순서로 접근
<img src="https://velog.velcdn.com/images/jm450_/post/36ad5440-f53c-4dab-8fad-0955ffc54d0b/image.png" alt=""></li>
</ul>
<h2 id="2-proxy-pattern을-사용하는-이유">2. Proxy Pattern을 사용하는 이유</h2>
<ul>
<li>Subject가 민감한 정보를 가지고 있거나 인스턴스화 하기 무겁거나 추가 기능을 가미하고 싶은데, 원본 객체를 수정할 수 없는 상황일 때를 극복하기 위해</li>
</ul>
<h2 id="3-proxy-pattern-구조">3. Proxy Pattern 구조</h2>
<ul>
<li>proxy와 subject는 동일한 인터페이스를 가지고 있음<ul>
<li>다른 인터페이스와 완전히 호환되도록 바꿀 수 있음</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Kit AR Project 썸네일]]></title>
            <link>https://velog.io/@jm450_/Kit-AR-Project-%EC%8D%B8%EB%84%A4%EC%9D%BC</link>
            <guid>https://velog.io/@jm450_/Kit-AR-Project-%EC%8D%B8%EB%84%A4%EC%9D%BC</guid>
            <pubDate>Tue, 12 Dec 2023 05:57:20 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jm450_/post/ff59a392-e80e-4d77-96af-ab16da3d197f/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>