<?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>Mon, 02 Mar 2026 16:02:21 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[[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>
        <item>
            <title><![CDATA[C++ 썸네일]]></title>
            <link>https://velog.io/@jm450_/C-%EC%8D%B8%EB%84%A4%EC%9D%BC</link>
            <guid>https://velog.io/@jm450_/C-%EC%8D%B8%EB%84%A4%EC%9D%BC</guid>
            <pubDate>Tue, 12 Dec 2023 05:49:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jm450_/post/a20a7b9c-a5e0-4841-a72d-2e555f204a0b/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Aura 썸네일]]></title>
            <link>https://velog.io/@jm450_/Aura-%EC%8D%B8%EB%84%A4%EC%9D%BC</link>
            <guid>https://velog.io/@jm450_/Aura-%EC%8D%B8%EB%84%A4%EC%9D%BC</guid>
            <pubDate>Tue, 12 Dec 2023 05:47:35 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jm450_/post/7f510e8b-3ea1-4729-a516-49bd5b1ad16d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Roguelike 썸네일]]></title>
            <link>https://velog.io/@jm450_/Roguelike-%EC%8D%B8%EB%84%A4%EC%9D%BC</link>
            <guid>https://velog.io/@jm450_/Roguelike-%EC%8D%B8%EB%84%A4%EC%9D%BC</guid>
            <pubDate>Tue, 12 Dec 2023 05:46:30 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jm450_/post/ec5de3ba-5a03-499b-b38e-8fe5554f2c0d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Assassin 썸네일]]></title>
            <link>https://velog.io/@jm450_/Assassin-%EC%8D%B8%EB%84%A4%EC%9D%BC</link>
            <guid>https://velog.io/@jm450_/Assassin-%EC%8D%B8%EB%84%A4%EC%9D%BC</guid>
            <pubDate>Tue, 12 Dec 2023 05:45:05 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jm450_/post/eacd4575-b49a-4aeb-bff4-d341a0e27e5f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Operation System 썸네일]]></title>
            <link>https://velog.io/@jm450_/Operation-System-%EC%8D%B8%EB%84%A4%EC%9D%BC</link>
            <guid>https://velog.io/@jm450_/Operation-System-%EC%8D%B8%EB%84%A4%EC%9D%BC</guid>
            <pubDate>Tue, 12 Dec 2023 05:41:34 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jm450_/post/6eeb7e60-7ea2-4944-8229-78bfc6bca4b9/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Game Design Pattern 썸네일]]></title>
            <link>https://velog.io/@jm450_/Game-Design-Pattern-%EC%8D%B8%EB%84%A4%EC%9D%BC</link>
            <guid>https://velog.io/@jm450_/Game-Design-Pattern-%EC%8D%B8%EB%84%A4%EC%9D%BC</guid>
            <pubDate>Tue, 12 Dec 2023 05:39:47 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jm450_/post/e65e4ae6-327e-44ab-ac2b-8690a60473e3/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Unity Engine 썸네일]]></title>
            <link>https://velog.io/@jm450_/Unity-Engine-%EC%8D%B8%EB%84%A4%EC%9D%BC</link>
            <guid>https://velog.io/@jm450_/Unity-Engine-%EC%8D%B8%EB%84%A4%EC%9D%BC</guid>
            <pubDate>Tue, 12 Dec 2023 05:35:49 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jm450_/post/72b78789-2801-433e-a5cb-fc9cf6ec4390/image.png" alt=""></p>
]]></description>
        </item>
    </channel>
</rss>