<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>옆히</title>
    <link>https://ndhphysics.tistory.com/</link>
    <description>ndhphysics@gmail.com</description>
    <language>ko</language>
    <pubDate>Tue, 16 Jun 2026 21:11:43 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>옆집히드라</managingEditor>
    <image>
      <title>옆히</title>
      <url>https://tistory1.daumcdn.net/tistory/6863523/attach/7b74513c9f9f4746817bb8afc1940134</url>
      <link>https://ndhphysics.tistory.com</link>
    </image>
    <item>
      <title>TIL260616 - Ch3 - 2 - 카툰 렌더링</title>
      <link>https://ndhphysics.tistory.com/98</link>
      <description>&lt;h1&gt;1. 핵심 개념&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하프 램버트 &amp;amp; 램버트&lt;/li&gt;
&lt;li&gt;MPC&lt;/li&gt;
&lt;li&gt;블린-퐁 스페큘러&lt;/li&gt;
&lt;li style=&quot;list-style-type: none;&quot;&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 상세 내용&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;관련 문서에 참고한 이득우의 카룬 렌더링 강의 내용을 정리함&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.1. 카툰 렌더링&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1.1. 카툰 렌더링의 기본 원리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;렌더링, 메터리얼, Diffuse, Specular, BRDF 기본 개념(램버트 디퓨즈, 램버트 BRDF가 원론적)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반사량에 따라 정해지는 표면에 들어오는 빛의 양 &amp;gt; Illuminance&lt;br /&gt;디퓨즈 계산 식은 $cos \theta$ (세타는 점 p에서 노말 벡터 N과 빛의 방향 라이트 벡터 L 사이의 각)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 벡터의 크기가 1이라면 $\vec{a} \cdot \vec{b} = a_x b_x + a_y b_y = \Vert{}\vec{a}\Vert{} \Vert{}\vec{b}\Vert{} \cos\theta$ 에 의해서 컴퓨터에 유리한 덧셈, 곱셈 연산만으로 사잇각을 구할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;램버트 연산은 직접광 연산을 위한 것으로 간접광 연산을 위해서는 또 다른 모델이 필요하다. 과거에는 이 간접광 연산이 매우 무거웠기 때문에 &lt;i&gt;하프 램버트&lt;/i&gt; 같은 경량 모델을 사용했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;하프 램버트&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616134315.jpg&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vU4bj/dJMcacDxo7c/lTacik3yuYhYTvI8aZVIZK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vU4bj/dJMcacDxo7c/lTacik3yuYhYTvI8aZVIZK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vU4bj/dJMcacDxo7c/lTacik3yuYhYTvI8aZVIZK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvU4bj%2FdJMcacDxo7c%2FlTacik3yuYhYTvI8aZVIZK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;206&quot; data-filename=&quot;Pasted image 20260616134315.jpg&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Half Lambert lighting&lt;/b&gt; is a technique designed to prevent the rear of an object losing its shape and looking too flat.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Half Lambert is a completely non-physical technique and gives a purely percieved visual enhancement and is an example of a forgiving lighting model.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 라이트 값 계산
float difLight = dot(s.Normal, lightDir);
// 라이트 값을 하프 램버트 값으로
float hLambert = difLight * 0.5 + 0.5;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하프 램버트 = cos에(normalized 된 두 벡터의 dot)에 0.5를 곱하고 0.5를 더해서 모든 영역에 그림자가 지게 함&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;밴드(Band) 셰이딩&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하프 램버트에서 ceil 함수를 사용하면 그림자 영역을 띠로 나누어 그리는 밴드 셰이딩을 할 수 있다. 이는 카툰 렌더링의 기초가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616142342.png&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qFANU/dJMcaci8JjU/nG0KAv630CwONUAWHKXeYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qFANU/dJMcaci8JjU/nG0KAv630CwONUAWHKXeYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qFANU/dJMcaci8JjU/nG0KAv630CwONUAWHKXeYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqFANU%2FdJMcaci8JjU%2FnG0KAv630CwONUAWHKXeYk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;535&quot; height=&quot;257&quot; data-filename=&quot;Pasted image 20260616142342.png&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올림 함수(치역 0&lt;/p&gt;
&lt;p&gt;&lt;del&gt;1)에 나눌 영역의 수인 N을 곱한 뒤에 floor(또는 ceil, round) 함수를 취한 뒤에 나온 값을 다시 N으로 나누면(치역 0&lt;/del&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1 맞춤) 밴딩 효과를 구현할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1.2. 카툰 명암을 조절하고 색을 입히는 방법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가산 모델(RGB)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;색에 흑백 그레이디언트를 곱하면 밝은 곳에서 점차 어두워지는 음영 표현이 가능(색 * 무채색(어둡기))&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616142648.png&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k5o9m/dJMcadvxJsW/gQKIIPbyFkJlqBiylrhUi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k5o9m/dJMcadvxJsW/gQKIIPbyFkJlqBiylrhUi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k5o9m/dJMcadvxJsW/gQKIIPbyFkJlqBiylrhUi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk5o9m%2FdJMcadvxJsW%2FgQKIIPbyFkJlqBiylrhUi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;796&quot; height=&quot;576&quot; data-filename=&quot;Pasted image 20260616142648.png&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그레이디언트에 색을 곱하는 것만으로 모델에 음영 표현이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616143030.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boetYd/dJMcadvxJsY/dAAIkzd4xkk2If4q7qwPh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boetYd/dJMcadvxJsY/dAAIkzd4xkk2If4q7qwPh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boetYd/dJMcadvxJsY/dAAIkzd4xkk2If4q7qwPh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboetYd%2FdJMcadvxJsY%2FdAAIkzd4xkk2If4q7qwPh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;809&quot; height=&quot;564&quot; data-filename=&quot;Pasted image 20260616143030.png&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;564&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;lerp&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 선형 보간 $lerp(A, B, \alpha) = A + \alpha (B - A)$ 를 사용해서 어두운 영역과 밝은 영역의 컬러를 각각 A, B로 두어서 보간할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616144807.png&quot; data-origin-width=&quot;2022&quot; data-origin-height=&quot;753&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VCFCv/dJMb997O74K/TTLzfmOn60RlQF8PvqNjyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VCFCv/dJMb997O74K/TTLzfmOn60RlQF8PvqNjyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VCFCv/dJMb997O74K/TTLzfmOn60RlQF8PvqNjyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVCFCv%2FdJMb997O74K%2FTTLzfmOn60RlQF8PvqNjyK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2022&quot; height=&quot;753&quot; data-filename=&quot;Pasted image 20260616144807.png&quot; data-origin-width=&quot;2022&quot; data-origin-height=&quot;753&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.1.1에서 언급한 밴드 쉐이딩을 사용하여 암부와 명부에 컬러를 지정해서 lerp로 보간한 메테리얼의 모습이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;step&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616162244.png&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bS1F4B/dJMb997O745/KqPkGB5EBKbSf1pRMpIud0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bS1F4B/dJMb997O745/KqPkGB5EBKbSf1pRMpIud0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bS1F4B/dJMb997O745/KqPkGB5EBKbSf1pRMpIud0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbS1F4B%2FdJMb997O745%2FKqPkGB5EBKbSf1pRMpIud0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1167&quot; height=&quot;348&quot; data-filename=&quot;Pasted image 20260616162244.png&quot; data-origin-width=&quot;1167&quot; data-origin-height=&quot;348&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;step(y, x) y를 기준으로 반올림(round) 한다. 위와 같이 구성하면 매초 0-&amp;gt;1, 1-&amp;gt;0으로 왕복하는 모습을 볼 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Smooth step&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ndhphysics.tistory.com/79&quot;&gt;TIL260518 - VFX&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dna/cNIu23/dJMcacDbMZ5/AAAAAAAAAAAAAAAAAAAAABdA5eGndKCzjapjvyy9TVP5GQM0s-VZXqOS-2iIGj1y/img.png?credential=yqXZFxpELC7KVnFOS48ylbz2pIh7yKj8&amp;amp;expires=1782831599&amp;amp;allow_ip=&amp;amp;allow_referer=&amp;amp;signature=ngP6%2BbP5IsqJdNG3BfrwKGPKNis%3D&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SmoothStep graph&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력값 $x$를 $min$과 $max$ 사이의 $0$ ~ $1$ 범위로 정규화($t$)한 뒤, 부드러운 곡선을 만드는 3차 다항식에 대입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[1단계: 클램핑 및 정규화]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규화는 Min-Max 정규화 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$t = max(0, {min}(1, \frac{x - min}{max - min}))$$&lt;br /&gt;[2단계: 3차 에르미트 보간(Hermite Interpolation)]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$${SmoothStep}(x) = 3t^2 - 2t^3$$&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;b&gt;상황&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;max&amp;minus;min (거리)&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;보간 구간&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;시각적 효과&lt;/b&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;max 증가 &amp;uarr;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;멀어짐&lt;/td&gt;
&lt;td&gt;&lt;b&gt;넓어짐&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;더 부드럽고 완만한 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;max 감소 &amp;darr;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;가까워짐&lt;/td&gt;
&lt;td&gt;&lt;b&gt;좁아짐&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;더 급격하고 뚜렷한 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;min 증가 &amp;uarr;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;가까워짐&lt;/td&gt;
&lt;td&gt;&lt;b&gt;좁아짐&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;더 늦게 시작해서 급하게 변함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;min 감소 &amp;darr;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;멀어짐&lt;/td&gt;
&lt;td&gt;&lt;b&gt;넓어짐&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;더 일찍 시작해서 천천히 변함&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616170916.png&quot; data-origin-width=&quot;769&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHDEKR/dJMb997O75z/bv2vRG9IJgsoqz8rDVIock/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHDEKR/dJMb997O75z/bv2vRG9IJgsoqz8rDVIock/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHDEKR/dJMb997O75z/bv2vRG9IJgsoqz8rDVIock/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHDEKR%2FdJMb997O75z%2Fbv2vRG9IJgsoqz8rDVIock%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;769&quot; height=&quot;534&quot; data-filename=&quot;Pasted image 20260616170916.png&quot; data-origin-width=&quot;769&quot; data-origin-height=&quot;534&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;step보다 보간 영역이 더 넓고 부드러운 것을 알 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ramp 텍스처 셰이딩&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616171134.png&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;565&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJcJHy/dJMcafUx2rI/DBkp38MMZdN3bbKDzHPHK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJcJHy/dJMcafUx2rI/DBkp38MMZdN3bbKDzHPHK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJcJHy/dJMcafUx2rI/DBkp38MMZdN3bbKDzHPHK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJcJHy%2FdJMcafUx2rI%2FDBkp38MMZdN3bbKDzHPHK1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;759&quot; height=&quot;565&quot; data-filename=&quot;Pasted image 20260616171134.png&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;565&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ramp는 경사로를 의미하나 그래픽스에서는 한 색에서 다른 색으로 서서히 이동하는 디자인 기법을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616171439.png&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;461&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yCOHC/dJMcafUx2rS/DkwjUebkI2cXd5QVzLRG9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yCOHC/dJMcafUx2rS/DkwjUebkI2cXd5QVzLRG9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yCOHC/dJMcafUx2rS/DkwjUebkI2cXd5QVzLRG9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyCOHC%2FdJMcafUx2rS%2FDkwjUebkI2cXd5QVzLRG9K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;838&quot; height=&quot;461&quot; data-filename=&quot;Pasted image 20260616171439.png&quot; data-origin-width=&quot;838&quot; data-origin-height=&quot;461&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 배운 공식들과 같이 수치적으로 계산할 수 있지만 램프 텍스처를 준비해서 각 영역(0~1)에 램프 텍스처를 매핑하는 식으로 그림자를 표현할 수도 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;(번외) MPC와 메테리얼&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616155922.png&quot; data-origin-width=&quot;927&quot; data-origin-height=&quot;433&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cBIr53/dJMcadPRRgD/uc4RuKrCe1MdrLm1f24Zy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cBIr53/dJMcadPRRgD/uc4RuKrCe1MdrLm1f24Zy1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cBIr53/dJMcadPRRgD/uc4RuKrCe1MdrLm1f24Zy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcBIr53%2FdJMcadPRRgD%2Fuc4RuKrCe1MdrLm1f24Zy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;927&quot; height=&quot;433&quot; data-filename=&quot;Pasted image 20260616155922.png&quot; data-origin-width=&quot;927&quot; data-origin-height=&quot;433&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MPC(Material Parameter Collection)은 모든 메테리얼에서 참조 가능한 글로벌 변수 구조체이다. 만약 MPC가 없다면 메테리얼마다 Dynamic Material instance를 생성해서 일일히 값을 변경해줘야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 다음과 같은 경우에서 MPC를 사용하는 것이 유리하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;글로벌 날씨 시스템: 맵 전체에 비가 와서 오브젝트가 젖음&lt;/li&gt;
&lt;li&gt;바람 시스템: 맵 전체의 나무, 풀, 깃발이 흔들리는 방향과 강도를 한 번에 제어할 때&lt;/li&gt;
&lt;li&gt;플레이어 상호작용: 플레이어의 현재 위치를 실시간으로 MPC에 업데이트해서, 플레이어가 지나갈 때마 주변 풀들이 알아서 구부러지는 효과 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616160039.png&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqoeMR/dJMcabq3XqS/SmUueXlQ1JiCAeb796L3K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqoeMR/dJMcabq3XqS/SmUueXlQ1JiCAeb796L3K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqoeMR/dJMcabq3XqS/SmUueXlQ1JiCAeb796L3K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqoeMR%2FdJMcabq3XqS%2FSmUueXlQ1JiCAeb796L3K0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;571&quot; height=&quot;382&quot; data-filename=&quot;Pasted image 20260616160039.png&quot; data-origin-width=&quot;571&quot; data-origin-height=&quot;382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 등록한 MPC는 Directional light 블루프린트에서 수치를 변경한다. 변경된 수치는 앞서 사용한 메테리얼 그래프에서 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616160324.png&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lqgEn/dJMcaicG1Vx/Wk6nz3aXrtj1CXq7IlH0RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lqgEn/dJMcaicG1Vx/Wk6nz3aXrtj1CXq7IlH0RK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lqgEn/dJMcaicG1Vx/Wk6nz3aXrtj1CXq7IlH0RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlqgEn%2FdJMcaicG1Vx%2FWk6nz3aXrtj1CXq7IlH0RK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;829&quot; height=&quot;534&quot; data-filename=&quot;Pasted image 20260616160324.png&quot; data-origin-width=&quot;829&quot; data-origin-height=&quot;534&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 이미지는 밴드 쉐이딩이 적용되고 있는 예시이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1.3. 빛의 반사 영역은 어떻게 만들어지는가?&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616171702.png&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;439&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H3Bdm/dJMcaf70fAO/35XmKBLGKKwbPsgkvxB8fK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H3Bdm/dJMcaf70fAO/35XmKBLGKKwbPsgkvxB8fK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H3Bdm/dJMcaf70fAO/35XmKBLGKKwbPsgkvxB8fK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH3Bdm%2FdJMcaf70fAO%2F35XmKBLGKKwbPsgkvxB8fK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;999&quot; height=&quot;439&quot; data-filename=&quot;Pasted image 20260616171702.png&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;439&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스페큘러 로브의 BRDF(램버트 BRDF, 퐁 BRDF)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퐁 BRDF에서 반사 벡터 R을 구하는 식($R = 2 ( N \cdot L) N - L$) 유도는 &lt;a href=&quot;https://ndhphysics.tistory.com/4&quot;&gt;How to calculate the reflection vector&lt;/a&gt;에서 스크랩한 글 참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616172332.png&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;528&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHCMj0/dJMcahShJ2K/wCf1zq7jwLhaZkNR3XXa0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHCMj0/dJMcahShJ2K/wCf1zq7jwLhaZkNR3XXa0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHCMj0/dJMcahShJ2K/wCf1zq7jwLhaZkNR3XXa0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHCMj0%2FdJMcahShJ2K%2FwCf1zq7jwLhaZkNR3XXa0K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;877&quot; height=&quot;528&quot; data-filename=&quot;Pasted image 20260616172332.png&quot; data-origin-width=&quot;877&quot; data-origin-height=&quot;528&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반사 벡터 R과 카메라 벡터 V와의 사잇각으로 스페큘러를 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616172720.png&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;471&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjGM1R/dJMcagZ8U0X/AfcQ4WcMgjkwkGcLlVAAOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjGM1R/dJMcagZ8U0X/AfcQ4WcMgjkwkGcLlVAAOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjGM1R/dJMcagZ8U0X/AfcQ4WcMgjkwkGcLlVAAOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjGM1R%2FdJMcagZ8U0X%2FAfcQ4WcMgjkwkGcLlVAAOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;890&quot; height=&quot;471&quot; data-filename=&quot;Pasted image 20260616172720.png&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;471&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내적으로 만든 그레이디언트를 쨍하게(집중되게) 변형할 때는 거듭 제곱 연산(pow)을 사용하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616172827.png&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GQ5HB/dJMcaaeDhIM/fwYrVEwD0821QTXeHvTBW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GQ5HB/dJMcaaeDhIM/fwYrVEwD0821QTXeHvTBW1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GQ5HB/dJMcaaeDhIM/fwYrVEwD0821QTXeHvTBW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGQ5HB%2FdJMcaaeDhIM%2FfwYrVEwD0821QTXeHvTBW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;883&quot; height=&quot;521&quot; data-filename=&quot;Pasted image 20260616172827.png&quot; data-origin-width=&quot;883&quot; data-origin-height=&quot;521&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;20번부터 스페큘러와 비슷하게 상이 맺히는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표면이 매우 거친 경우 스페큘러 로브의 크기가 90도를 넘어갈 수 있는데(2.1.3. 상단 그림의 3번째 예시) 퐁 스페큘러 모델에서는 이런 90도가 넘어가는 영역을 모두 검정(0)으로 처리하는 문제가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616173132.png&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;501&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCS1p3/dJMcacQZj33/PgjWDaMuKUWuyAKhKajag0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCS1p3/dJMcacQZj33/PgjWDaMuKUWuyAKhKajag0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCS1p3/dJMcacQZj33/PgjWDaMuKUWuyAKhKajag0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCS1p3%2FdJMcacQZj33%2FPgjWDaMuKUWuyAKhKajag0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;767&quot; height=&quot;501&quot; data-filename=&quot;Pasted image 20260616173132.png&quot; data-origin-width=&quot;767&quot; data-origin-height=&quot;501&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퐁 모델이 가진 문제를 보완하기 위해 나온 것이 블린-퐁 스페큘러 모델이다. 블린-퐁 스페큘러 모델은 노말 벡터를 사용하여 명암을 계산하기 때문에 하프 벡터 H와 N 벡터 사이가 90도를 넘을 일이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하프 벡터 H는 빛 벡터 L과 카메라 벡터 V를 더해줘서 normalize 하면 구할 수 있다. 이렇게 구한 하프 벡터 H와 노말 벡터 N의 사잇각을 사용해서 명암을 표현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 퐁, 블린-퐁이 아닌 GGX 모델을 사용한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616181641.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;560&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3rv2h/dJMcabq3Xsb/qyh2rqEkUTWK4Vw6BSgAi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3rv2h/dJMcabq3Xsb/qyh2rqEkUTWK4Vw6BSgAi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3rv2h/dJMcabq3Xsb/qyh2rqEkUTWK4Vw6BSgAi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3rv2h%2FdJMcabq3Xsb%2Fqyh2rqEkUTWK4Vw6BSgAi0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;896&quot; height=&quot;560&quot; data-filename=&quot;Pasted image 20260616181641.png&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;560&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위부터 하프 램버트, 램버트, 하프 램버트와 Smooth step을 사용한 diffuse, lerp를 사용한 ColorDiffuse를 구현한 예제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616181747.png&quot; data-origin-width=&quot;835&quot; data-origin-height=&quot;565&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TypIK/dJMcabq3Xsd/sxdrFiOSFW9dSQPgjKy7u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TypIK/dJMcabq3Xsd/sxdrFiOSFW9dSQPgjKy7u0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TypIK/dJMcabq3Xsd/sxdrFiOSFW9dSQPgjKy7u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTypIK%2FdJMcabq3Xsd%2FsxdrFiOSFW9dSQPgjKy7u0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;835&quot; height=&quot;565&quot; data-filename=&quot;Pasted image 20260616181747.png&quot; data-origin-width=&quot;835&quot; data-origin-height=&quot;565&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블린-퐁 구현 예제&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616182108.png&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;519&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MARNd/dJMcaaMqVTr/aTKnQThrsj4BLoWMJ8jBqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MARNd/dJMcaaMqVTr/aTKnQThrsj4BLoWMJ8jBqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MARNd/dJMcaaMqVTr/aTKnQThrsj4BLoWMJ8jBqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMARNd%2FdJMcaaMqVTr%2FaTKnQThrsj4BLoWMJ8jBqk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;907&quot; height=&quot;519&quot; data-filename=&quot;Pasted image 20260616182108.png&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;519&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 구한 diffuse, specular를 lerp로 보간하여 최종적으로 완성된 셰이더 예제&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;두 RGB 색의 혼합&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260616182230.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpA2wi/dJMcab5EqGe/NLJRCHSZuL701BkzKtS2Pk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpA2wi/dJMcab5EqGe/NLJRCHSZuL701BkzKtS2Pk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpA2wi/dJMcab5EqGe/NLJRCHSZuL701BkzKtS2Pk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpA2wi%2FdJMcab5EqGe%2FNLJRCHSZuL701BkzKtS2Pk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1010&quot; height=&quot;536&quot; data-filename=&quot;Pasted image 20260616182230.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;색을 곱할 때 multiply로 표현한다면 색이 점점 어두워지는 문제가 있다. 이때 multiply로 구한 색과 add로 구한 두 색을 lerp로 보간하면 원래의 색을 살리면서 어두워지지 않게 색을 혼합해서 사용할 수 있다.&lt;/p&gt;
&lt;h1&gt;3. 질문 및 해결 (Q&amp;amp;A)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GGX 모델은?&lt;/li&gt;
&lt;li&gt;Hermit 보간은?&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;4. 관련 문서 (Links)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=6qCYaGne8H8&amp;amp;list=PLMcHQUYJZc73kt997o4aKCPa1bMrNm3PU&amp;amp;index=7&quot;&gt;1강 - 카툰 렌더링의 기본 원리&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.valvesoftware.com/wiki/Half_Lambert&quot;&gt;Half Lambert - Valve Developer Community&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://lee-seokhyun.gitbook.io/game-programming/client/easy-mathematics/gdc2012/3-cubic-hermite-splines&quot;&gt;3차 허밋 스플라인(Cubic Hermite Splines) | Game Programming&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=jvPPXbo87ds&quot;&gt;The Continuity of Splines&lt;/a&gt; 39:40&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프/TIL</category>
      <author>옆집히드라</author>
      <guid isPermaLink="true">https://ndhphysics.tistory.com/98</guid>
      <comments>https://ndhphysics.tistory.com/98#entry98comment</comments>
      <pubDate>Tue, 16 Jun 2026 18:32:21 +0900</pubDate>
    </item>
    <item>
      <title>TIL260615 - Ch3 - 1 - 표정, 메테리얼 슬롯, 양면 렌더</title>
      <link>https://ndhphysics.tistory.com/97</link>
      <description>&lt;h1&gt;1. 핵심 개념&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;표정 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 상세 내용&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;챕터3 팀프로젝트를 기획하면서 전체적인 아트워크를 아이돌과 아이돌을 쫓는 육수(?) 팬덤으로 카툰풍에 에셋을 준비해야 할 필요가 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 캐릭터 에셋을 준비하기 위해 자료를 찾는 중 personal 기준으로 무료로 제공하는 에셋을 찾았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.fab.com/listings/5506e030-5fe4-406d-adaa-0f2201ef3b0a&quot;&gt;CiciToon Character Shader Pak V3.5 | Fab&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781541806235&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;CiciToon Character Shader Pak V3.5&quot; data-og-description=&quot;You can find my other project in:MrCici | PatreonYou can find toon shader tutorial in:MrCici - YouTubeStill struggling with complex cartoon rendering nodes? This out-of-the-box cartoon shading package designed for indie developers comes with complete chara&quot; data-og-host=&quot;www.fab.com&quot; data-og-source-url=&quot;https://www.fab.com/listings/5506e030-5fe4-406d-adaa-0f2201ef3b0a&quot; data-og-url=&quot;https://www.fab.com/listings/5506e030-5fe4-406d-adaa-0f2201ef3b0a&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cWdiw9/dJMb8TCiCWP/afMAQv322IkEJp7c3daHe1/img.png?width=2220&amp;amp;height=1353&amp;amp;face=1730_253_1962_506,https://scrap.kakaocdn.net/dn/BLeaE/dJMb81G6gym/ryoKBA05OSmx5O2lZuypH1/img.png?width=2220&amp;amp;height=1353&amp;amp;face=1730_253_1962_506,https://scrap.kakaocdn.net/dn/c2cWq2/dJMb9hC9UQk/F372gGMOWdjXmbPQTarKrk/img.jpg?width=1920&amp;amp;height=1170&amp;amp;face=1521_231_1704_414&quot;&gt;&lt;a href=&quot;https://www.fab.com/listings/5506e030-5fe4-406d-adaa-0f2201ef3b0a&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.fab.com/listings/5506e030-5fe4-406d-adaa-0f2201ef3b0a&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cWdiw9/dJMb8TCiCWP/afMAQv322IkEJp7c3daHe1/img.png?width=2220&amp;amp;height=1353&amp;amp;face=1730_253_1962_506,https://scrap.kakaocdn.net/dn/BLeaE/dJMb81G6gym/ryoKBA05OSmx5O2lZuypH1/img.png?width=2220&amp;amp;height=1353&amp;amp;face=1730_253_1962_506,https://scrap.kakaocdn.net/dn/c2cWq2/dJMb9hC9UQk/F372gGMOWdjXmbPQTarKrk/img.jpg?width=1920&amp;amp;height=1170&amp;amp;face=1521_231_1704_414');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;CiciToon Character Shader Pak V3.5&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;You can find my other project in:MrCici | PatreonYou can find toon shader tutorial in:MrCici - YouTubeStill struggling with complex cartoon rendering nodes? This out-of-the-box cartoon shading package designed for indie developers comes with complete chara&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.fab.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 에셋은 툰 쉐이더 + 캐주얼한 캐릭터 모델을 제공하지만 옷이 교복 밖에 없어서 아이돌 색이 적다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://booth.pm/en/items/3083936&quot;&gt;☆無料☆ももか専用衣装No.87 - るるくショップ - BOOTH&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1781541833696&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;product&quot; data-og-title=&quot;☆無料☆ももか専用衣装No.87 - るるくショップ - BOOTH&quot; data-og-description=&quot;※アバターは付属しません こちらの衣装データは ショップ「T.GardeN」ちゅばき様が制作した子 「ももか」　https://booth.pm/ja/items/2309079 のオリジナル3Dモデル専用の衣装になります Unityで着せ&quot; data-og-host=&quot;booth.pm&quot; data-og-source-url=&quot;https://booth.pm/en/items/3083936&quot; data-og-url=&quot;https://booth.pm/en/items/3083936&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dZZ40u/dJMb9frN8II/wslRpCorMGFoeL5upL25tk/img.jpg?width=620&amp;amp;height=388&amp;amp;face=0_0_620_388,https://scrap.kakaocdn.net/dn/5LC48/dJMb9cBQKcO/C5VhqOScvrFJeaL6WKLoT1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/FPwTv/dJMb9hC9UQv/2PGEtas84rmGiok5WumRq0/img.jpg?width=1024&amp;amp;height=640&amp;amp;face=0_0_1024_640&quot;&gt;&lt;a href=&quot;https://booth.pm/en/items/3083936&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://booth.pm/en/items/3083936&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dZZ40u/dJMb9frN8II/wslRpCorMGFoeL5upL25tk/img.jpg?width=620&amp;amp;height=388&amp;amp;face=0_0_620_388,https://scrap.kakaocdn.net/dn/5LC48/dJMb9cBQKcO/C5VhqOScvrFJeaL6WKLoT1/img.jpg?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/FPwTv/dJMb9hC9UQv/2PGEtas84rmGiok5WumRq0/img.jpg?width=1024&amp;amp;height=640&amp;amp;face=0_0_1024_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;☆無料☆ももか専用衣装No.87 - るるくショップ - BOOTH&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;※アバターは付属しません こちらの衣装データは ショップ「T.GardeN」ちゅばき様が制作した子 「ももか」　https://booth.pm/ja/items/2309079 のオリジナル3Dモデル専用の衣装になります Unityで着せ&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;booth.pm&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 BOOTH에서 위와 같은 아이돌 복 FBX를 구해서 직접 블렌더로 합치기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1. 옷 변경 작업&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-06-11 151912.png&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/unbdH/dJMcadbeJMZ/CFJdqscHCLDYh35rGIPK90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/unbdH/dJMcadbeJMZ/CFJdqscHCLDYh35rGIPK90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/unbdH/dJMcadbeJMZ/CFJdqscHCLDYh35rGIPK90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FunbdH%2FdJMcadbeJMZ%2FCFJdqscHCLDYh35rGIPK90%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2560&quot; height=&quot;1392&quot; data-filename=&quot;Screenshot 2026-06-11 151912.png&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에셋을 직접 블렌더로 Asset Action -&amp;gt; export 하니 블렌더에서 위와 같이 inverted 된 아웃라인 메시랑 Shape key(언리얼에서는 Morph target)에 쓰이는 마스크나 안경 같이 안쓰는 메시가 통짜메시로 임포트 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이중에서 안쓰는 메시는 제거하고 분리할 메시는 분리하면서 메시를 정리하고 안쓰는 본을 제거했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-06-11 162031.png&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSH21U/dJMcaayVnou/A8EB7NklOnjuNuxq9dqQSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSH21U/dJMcaayVnou/A8EB7NklOnjuNuxq9dqQSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSH21U/dJMcaayVnou/A8EB7NklOnjuNuxq9dqQSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSH21U%2FdJMcaayVnou%2FA8EB7NklOnjuNuxq9dqQSk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2560&quot; height=&quot;1392&quot; data-filename=&quot;Screenshot 2026-06-11 162031.png&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 캐릭터 메시를 조정하고 옷을 직접 붙였다.(옷 아래에 body는 벗을 때를 고려해서 일부로 남겨두었다.(추가할지는...))&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-06-12 134333.png&quot; data-origin-width=&quot;2561&quot; data-origin-height=&quot;1393&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/44sMN/dJMcajoYkWh/0U27zKTT2SvK5fI12Lks21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/44sMN/dJMcajoYkWh/0U27zKTT2SvK5fI12Lks21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/44sMN/dJMcajoYkWh/0U27zKTT2SvK5fI12Lks21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F44sMN%2FdJMcajoYkWh%2F0U27zKTT2SvK5fI12Lks21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2561&quot; height=&quot;1393&quot; data-filename=&quot;Screenshot 2026-06-12 134333.png&quot; data-origin-width=&quot;2561&quot; data-origin-height=&quot;1393&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 옷에 리본과 치마를 kawaii physics를 이용해서 팔랑임을 구현하도록 직접 본을 조정하고 웨이트 페인팅을 다시 해두었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-06-15 203042.png&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;979&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lhsAn/dJMcaiKprJh/O3XeFmae07WkKDS04lq5y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lhsAn/dJMcaiKprJh/O3XeFmae07WkKDS04lq5y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lhsAn/dJMcaiKprJh/O3XeFmae07WkKDS04lq5y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlhsAn%2FdJMcaiKprJh%2FO3XeFmae07WkKDS04lq5y1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;403&quot; height=&quot;453&quot; data-filename=&quot;Screenshot 2026-06-15 203042.png&quot; data-origin-width=&quot;871&quot; data-origin-height=&quot;979&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 옷 메시는 부스에서 단일 메시면으로 제작이 되어서 실제 엔진에서 백페이스 컬링이 일어나서 위에 치마 아래 부분 이미지가 렌더링이 안됐다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csE7Qy/dJMcaiwVZdU/o5fqGtC4t9uJhXnY0DjAWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csE7Qy/dJMcaiwVZdU/o5fqGtC4t9uJhXnY0DjAWK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;185&quot; data-filename=&quot;Screenshot 2026-06-12 183239.png&quot; style=&quot;width: 71.2165%; margin-right: 10px;&quot; data-widthpercent=&quot;72.05&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csE7Qy/dJMcaiwVZdU/o5fqGtC4t9uJhXnY0DjAWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsE7Qy%2FdJMcaiwVZdU%2Fo5fqGtC4t9uJhXnY0DjAWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;477&quot; height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6Zxs1/dJMb99Ug9no/dSrYXMIpVntBoFJOgP5rk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6Zxs1/dJMb99Ug9no/dSrYXMIpVntBoFJOgP5rk0/img.png&quot; data-is-animation=&quot;false&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot; data-filename=&quot;SKM_Hayakawa_Main.png&quot; width=&quot;240&quot; height=&quot;240&quot; style=&quot;width: 27.6207%;&quot; data-widthpercent=&quot;27.95&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6Zxs1/dJMb99Ug9no/dSrYXMIpVntBoFJOgP5rk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6Zxs1%2FdJMb99Ug9no%2FdSrYXMIpVntBoFJOgP5rk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 팬티를 그리는 텍스처 작업을 하면서 동시에 치마면을 양면으로 하게 메시를 조정했다.(사타구니 영역이 모호해서 텍스처링할 때 UV layout을 팬티가 입혀질 메시영역을 선택해서 클립스튜디오에서 그렸다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분을 조정하면서 메테리얼에 양면 렌더링 옵션을 키는 것이 성능상 메시를 추가하는 것보다 더 부하가 있지 않을까 싶어서 막연하게 작업했다. 그래서 이 부분을 제미나이 사고 모델에 질의해서 더 찾아봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;머티리얼 옵션 사용 (Two-Sided Material)&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;단일 페이스 메시를 그대로 두고, 언리얼 머티리얼 에디터에서 &lt;b data-index-in-node=&quot;34&quot; data-path-to-node=&quot;11&quot;&gt;Two Sided&lt;/b&gt;를 체크하는 방식&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;장점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,1,0,0&quot;&gt;메모리 및 성능:&lt;/b&gt; 폴리곤 수가 증가하지 않아 렌더링 부하가 적다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,1,1,0&quot;&gt;애니메이션 작업 용이:&lt;/b&gt; 리깅(Rigging) 시 웨이트 페인팅(Weight Painting)을 한 겹만 칠하면 되므로 작업이 매우 빠르다. 물리 시뮬레이션 시 옷이 겹쳐서 깨지는 현상도 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;단점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;12,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,1,0,0&quot;&gt;물리적 두께감 부재:&lt;/b&gt; 옆에서 단면을 보았을 때 옷감이 종이장처럼 얇아 보임&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,1,1,0&quot;&gt;라이팅/노멀 계산의 한계:&lt;/b&gt; 언리얼의 Default Lit 셰이딩 모델에서 Two-Sided를 켜면, 뒷면의 노멀(법선)이 앞면의 반대 방향으로 강제 복사됩니다. 환경광(GI)이나 그림자가 뒷면에 맺힐 때 다소 평면적이거나 어색하게 렌더링될 수 있다. (이를 보완하기 위해 Two-Sided Foliage나 Cloth 셰이딩 모델이 필요)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;메시를 양면으로 모델링 (Double-Sided Geometry)&lt;/b&gt;&lt;/p&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;블렌더 등에서 Solidify 모디파이어나 Extrude를 사용해 치마에 실제 두께(안감)를 만들어주는 방식&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,0&quot;&gt;장점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,0,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,1,0,0&quot;&gt;시각적 디테일과 현실감:&lt;/b&gt; 실제 옷감처럼 두께와 단면(Rim)이 생겨 렌더링 퀄리티가 상승. 특히 천의 끝단 처리가 자연스럽스럽다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,1,1,0&quot;&gt;완벽한 라이팅 통제:&lt;/b&gt; 앞면과 뒷면이 독립된 폴리곤이므로, 언리얼의 라이팅과 그림자 계산이 물리적으로 완벽하게 들어맞는다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,0,1,2,0&quot;&gt;자유로운 텍스처링:&lt;/b&gt; 안감과 겉감의 UV를 완전히 분리하여 전혀 다른 재질이나 패턴을 입히기 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,0&quot;&gt;단점:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15,1,1&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,1,0,0&quot;&gt;최적화 부담:&lt;/b&gt; 버텍스 수가 2배로 뛰어 드로우 콜 처리에 부담&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;15,1,1,1,0&quot;&gt;웨이트 페인팅 지옥:&lt;/b&gt; 치마가 움직일 때 안감 버텍스와 겉감 버텍스가 정확히 동일한 가중치로 움직이지 않으면 안감이 겉감을 뚫고 나온다(Clipping). 언리얼의 Chaos Cloth 물리 시뮬레이션을 적용하기 까다로워짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 내 방식이 더 무겁고 퀄리티를 치중한 방식이였다.(의도와 정반대) 물론 드로우 콜을 수행할 cpu 성능이 떨어지는 환경이나 대량의 군중을 표시해야 해서 드로우 콜 증가가 더 치명적인 경우에는 전자의 방식이 더 무거울 수도 있지만 현재 나는 치마를 물리 시뮬레이션 해야하는 상황이여서 좋지 못한 선택을 한 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 프로젝트가 진행됨에 따라 추가적인 변경을 고려해봐야겠다.(실제로 프로파일링 해보면서)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2. 표정 구현&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-06-15 145049.png&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGnsq7/dJMcah5T85o/KrIgIyc8sLZeRMBWa8vNvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGnsq7/dJMcah5T85o/KrIgIyc8sLZeRMBWa8vNvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGnsq7/dJMcah5T85o/KrIgIyc8sLZeRMBWa8vNvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGnsq7%2FdJMcah5T85o%2FKrIgIyc8sLZeRMBWa8vNvk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2560&quot; height=&quot;1392&quot; data-filename=&quot;Screenshot 2026-06-15 145049.png&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1392&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한편, 캐릭터 모델을 다시 편집하는 과정에서 표정을 어떻게 구현하는지 알아냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;캐릭터 얼굴에 표정을 띄울 메시를 만든다.(얼굴에 띄워질 공간을 ctrl + d로 복제해서 살짝 위로 옮김)&lt;/li&gt;
&lt;li&gt;만든 메시를 머리 안쪽에 작게 해서 숨긴다.&lt;/li&gt;
&lt;li&gt;표정용 텍스처를 준비해서 미리 만들어둔 표정 메시에 UV 맵을 만든다.&lt;/li&gt;
&lt;li&gt;이렇게 만든 메시는 쉐이프키를 이용해서 1.0 지점에 얼굴에 완전히 띄워지도록 설정한다.&lt;/li&gt;
&lt;li&gt;설정된 쉐이프 키는 언리얼의 Morph target을 조정해서 사용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;667&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8REE2/dJMcag6VMSf/YVH2k7osMQylVu1ANkHKY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8REE2/dJMcag6VMSf/YVH2k7osMQylVu1ANkHKY1/img.png&quot; data-alt=&quot;적용된 모습&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8REE2/dJMcag6VMSf/YVH2k7osMQylVu1ANkHKY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8REE2%2FdJMcag6VMSf%2FYVH2k7osMQylVu1ANkHKY1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;711&quot; height=&quot;384&quot; data-origin-width=&quot;1236&quot; data-origin-height=&quot;667&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;적용된 모습&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.3. 메테리얼 슬롯&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-06-12 180735.png&quot; data-origin-width=&quot;2571&quot; data-origin-height=&quot;1452&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8R1l1/dJMcadoMq0l/BtRRBfgM3SHp0xqwK2R7m0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8R1l1/dJMcadoMq0l/BtRRBfgM3SHp0xqwK2R7m0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8R1l1/dJMcadoMq0l/BtRRBfgM3SHp0xqwK2R7m0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8R1l1%2FdJMcadoMq0l%2FBtRRBfgM3SHp0xqwK2R7m0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2571&quot; height=&quot;1452&quot; data-filename=&quot;Screenshot 2026-06-12 180735.png&quot; data-origin-width=&quot;2571&quot; data-origin-height=&quot;1452&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 과정을 거쳐서 새로 만든 몸통 부분은 실제 원본 캐릭터 모델과 비교해서(highlight 옵션을 켜서 확인) 올바른 곳에 적당한 메테리얼을 다시 할당해줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최초에는 원본 모델과 동일한 순서로 메테리얼을 임포트했는데 블렌더에서 변경이 일어난 메테리얼 목록은 언리얼로 임포트할 때 메테리얼이 저장된 배열 형태의 인덱스가 재조정돼서 원래 위치와 다른 곳에 메테리얼이 할당되거나 새로 생겨났다. 따라서 메테리얼을 직접 어느 메시에 사용되는지 일일이 비교해가면서 넣었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제미나이에게 질의한 결과 위와 같은 문제가 발생했을 때 다음과 같이 해결할 수 있다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;언리얼 엔진의 FBX Import Options에서 머티리얼 매칭 방식을 인덱스가 아닌 &lt;b data-index-in-node=&quot;79&quot; data-path-to-node=&quot;3,2,0&quot;&gt;이름(Name) 기반&lt;/b&gt;으로 변경&lt;/li&gt;
&lt;li&gt;블렌더 측에서 머티리얼 슬롯의 순서를 원본과 동일하게 맞춘다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 특정 메시 영역만 블렌더에서 select 해서 메테리얼 슬롯을 추가해서 다르게 렌더링 할 수 있는 것을 알았다. 하지만 이 방식은 다음과 같은 사항을 고려해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;드로우 콜 증가&lt;/b&gt;&lt;br /&gt;그래픽스 API(DirectX, Vulkan 등) 관점에서, 하나의 메시에 적용된 머티리얼이 &lt;span data-index-in-node=&quot;56&quot; data-math=&quot;N&quot;&gt;N&lt;/span&gt;개라면, 렌더링 엔진은 이 메시를 &lt;span data-index-in-node=&quot;76&quot; data-math=&quot;N&quot;&gt;N&lt;/span&gt;개의 별도 서브 메시(Sub-mesh)로 분리하여 처리한다 따라서 머티리얼 슬롯이 1개 추가될 때마다 드로우 콜이 1회 추가된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;버텍스 분할 (Vertex Splitting / Duplication)&lt;/b&gt;&lt;br /&gt;래픽 카드 파이프라인에서 하나의 버텍스(Vertex)는 단 하나의 머티리얼 ID(Draw call 그룹)에만 속할 수 있기 때문에 서로 다른 두 머티리얼이 맞닿는 경계면(Seam)에 위치한 버텍스들은 게임 엔진 내부적으로 물리적인 위치가 같더라도 강제로 복제(Split)됩니다. 따라서 블렌더에서 확인한 버텍스 개수보다 언리얼 엔진 내부에서 처리하는 실제 버텍스 개수가 증가할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 제공받은 무료 에셋 기준의 멀티 메테리얼 방식은 드로우 콜과 버텍스가 증가하는 대신에&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;각 부위별로 텍스처 해상도를 독립적으로 높게 가져갈&lt;span&gt; 수 있다. 추가로 완전히 다른 셰이딩 모델을 적용하기 유리하다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;만약 이 방식을 &lt;/span&gt;&lt;/span&gt;최적화 한다면 텍스처 마스킹 / ID 맵 방식으로 모델을 단일 메테리얼 슬롯 1개로 합친 다음에 RGB 마스크 텍스처나 Color ID 맵을 사용해서 영역을 관리하는 방법을 사용할 수 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-06-12 181320.png&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;955&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HjTC5/dJMcafG0zPh/4Li1OIaz96HNaQKaymOCEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HjTC5/dJMcafG0zPh/4Li1OIaz96HNaQKaymOCEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HjTC5/dJMcafG0zPh/4Li1OIaz96HNaQKaymOCEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHjTC5%2FdJMcafG0zPh%2F4Li1OIaz96HNaQKaymOCEk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;796&quot; height=&quot;955&quot; data-filename=&quot;Screenshot 2026-06-12 181320.png&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;955&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 성공적으로 아이돌 옷을 입힌 캐릭터를 언리얼로 포팅했다.&lt;/p&gt;
&lt;h1&gt;3. 질문 및 해결 (Q&amp;amp;A)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 캐릭터에 아이돌 옷은 입힌 것은 좋은데 아웃라인 적용이 하나도 안되어 있다. 실제 카툰 느낌을 살리려면 아웃라인 느낌과 툰 쉐이딩이 필수인데 cicitoon의 세팅을 따라서 이 부분을 어떻게 적용해야 할지 고민해야 할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 카툰 렌더링을 기반 캐릭터 작업을 새로운 용어들이 등장하는데 이를 알아야 할 필요가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NPR 렌더링&lt;/li&gt;
&lt;li&gt;SDF의 개념과 SDF를 사용한 캐릭터 얼굴 음영 구현&lt;/li&gt;
&lt;li&gt;SSS, OMR 텍스처의 개념과 사용법(packing 방법)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;4. 관련 문서 (Links)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[[이전 관련 노트]]&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;www.google.com&quot;&gt;참고한 외부 링크&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;iframe src=&quot;chrome-extension://nnodgmifnfgkolmakhcfkkbbjjcobhbl/opencvHandler.html&quot; id=&quot;opencvFrame&quot;&gt;&lt;/iframe&gt;&lt;iframe src=&quot;chrome-extension://nnodgmifnfgkolmakhcfkkbbjjcobhbl/ocr.html&quot; id=&quot;ocrFrame&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;</description>
      <category>내일배움캠프/TIL</category>
      <author>옆집히드라</author>
      <guid isPermaLink="true">https://ndhphysics.tistory.com/97</guid>
      <comments>https://ndhphysics.tistory.com/97#entry97comment</comments>
      <pubDate>Mon, 15 Jun 2026 21:02:07 +0900</pubDate>
    </item>
    <item>
      <title>TIL260612 - Unreal</title>
      <link>https://ndhphysics.tistory.com/96</link>
      <description>&lt;h1&gt;1. 핵심 개념&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;오늘 한 일 리깅하기&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 상세 내용&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. 질문 및 해결 (Q&amp;amp;A)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TODO: 치마 본에 물리 넣기(kawaii physisc)&lt;/li&gt;
&lt;li&gt;옷에 대하여 아웃라인 쉐이더 적용&lt;/li&gt;
&lt;li&gt;애니메이션 리타게터로 라이라 프로젝트와 동기화&lt;/li&gt;
&lt;li&gt;IMC, IA 설계 후 애니메이션 정상 동작하는지 실험&lt;/li&gt;
&lt;li&gt;사용된 카툰렌더링, 텍스처 매핑 분석&lt;/li&gt;
&lt;li&gt;안쓰는 쉐이프키 정리&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;4. 관련 문서 (Links)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[[이전 관련 노트]]&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;www.google.com&quot;&gt;참고한 외부 링크&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프/TIL</category>
      <author>옆집히드라</author>
      <guid isPermaLink="true">https://ndhphysics.tistory.com/96</guid>
      <comments>https://ndhphysics.tistory.com/96#entry96comment</comments>
      <pubDate>Fri, 12 Jun 2026 23:46:42 +0900</pubDate>
    </item>
    <item>
      <title>TIL260611 - Unreal - GAS4</title>
      <link>https://ndhphysics.tistory.com/95</link>
      <description>&lt;h1&gt;1. 핵심 개념&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GE링 ASC에 태그 부여하는 방식 고민&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 상세 내용&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;3. 질문 및 해결 (Q&amp;amp;A)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Q1&lt;/h2&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;언리얼 GAS에서 ASC에 태그를 부여할 때
SetAndApplyTargetTagChanges
ApplyGameplayEffectSpecToTarget는 각각 무슨 차이가 있어?

후자는
// GameplayEffectAggregator.cpp
FActiveGameplayEffectHandle FActiveGameplayEffectsContainer::ApplyGameplayEffectSpec(
    const FGameplayEffectSpec&amp;amp; Spec)
{
    // ① 태그 조건 검사
    // Owner(Target)가 Blocked 태그 갖고 있으면 차단
    if (Owner-&amp;gt;AreAbilityTagsBlocked(Spec.Def-&amp;gt;InheritableGrantedTagsContainer.CombinedTags))
        return FActiveGameplayEffectHandle();

    // ② Required 태그 검사
    // Application Required Tags 없으면 차단
    if (!Spec.Def-&amp;gt;ApplicationTagRequirements.RequirementsMet(
        Owner-&amp;gt;GetOwnedGameplayTags()))
        return FActiveGameplayEffectHandle();

    // ③ FActiveGameplayEffect 생성 (런타임 인스턴스)
    FActiveGameplayEffect NewEffect(
        GenerateNewActiveEffectHandle(), Spec, GetWorldTime());

    // ④ 실제 컨테이너에 등록
    GameplayEffects_Internal.Add(NewEffect);

    // ⑤ Modifier 적용 (어트리뷰트 수치 변경)
    ApplyModifiersToAttribute(Spec);

    // ⑥ 태그 부여
    Owner-&amp;gt;AddLooseGameplayTags(
        Spec.Def-&amp;gt;InheritableGrantedTagsContainer.CombinedTags);

    // ⑦ Duration 타이머 등록
    if (Spec.Def-&amp;gt;DurationPolicy == EGameplayEffectDurationType::HasDuration)
    {
        FTimerDelegate Delegate;
        Delegate.BindUObject(this, 
            &amp;amp;FActiveGameplayEffectsContainer::OnActiveGameplayEffectExpired,
            NewEffect.Handle);
        Owner-&amp;gt;GetWorld()-&amp;gt;GetTimerManager().SetTimer(
            NewEffect.DurationHandle, Delegate, Spec.Duration, false);
    }

    // ⑧ 복제 (멀티플레이)
    Owner-&amp;gt;MarkArrayDirty();

    return NewEffect.Handle;
}
와 같이 위에 명시적인 과정을 거치고 나서 AddLooseGameplayTags를 붙여서 ASC에 태그가 들어오는데

전자는 
FInheritedTagContainer TagContainer;
TagContainer.Added.AddTag(FGameplayTag::RequestGameplayTag(FName(&quot;State.Stun&quot;)));
TagsComp-&amp;gt;SetAndApplyTargetTagChanges(TagContainer);
와 같이 직접 태그 컨테이너를 넣어서 조정이 가능하거든? 공식적인 루트는 후자인 ApplyGameplayEffectSpecToTarget를 사용하는 것 같은데
SetAndApplyTargetTagChanges로 태그를 넣는 방식도 존재해서 둘이 무슨 차이인지 알려줘

전자 방식인 SetAndApplyTargetTagChanges는 내부 소스코드를 봐도

#endif // WITH_EDITOR

void UTargetTagsGameplayEffectComponent::SetAndApplyTargetTagChanges(const FInheritedTagContainer&amp;amp; TagContainerMods)
{
    InheritableGrantedTagsContainer = TagContainerMods;

    // Try to find the parent and update the inherited tags
    const UTargetTagsGameplayEffectComponent* Parent = FindParentComponent(*this);
    InheritableGrantedTagsContainer.UpdateInheritedTagProperties(Parent ? &amp;amp;Parent-&amp;gt;InheritableGrantedTagsContainer : nullptr);

    // Apply to the owning Gameplay Effect Component
    ApplyTargetTagChanges();
}

void UTargetTagsGameplayEffectComponent::ApplyTargetTagChanges() const
{
    UGameplayEffect* Owner = GetOwner();
    InheritableGrantedTagsContainer.ApplyTo(Owner-&amp;gt;CachedGrantedTags);
}

#undef LOCTEXT_NAMESPACE

어디서 어떻게 태그가 ASC로 들어가는지 알기 힘들어.

최종적으로 다음의 2개의 질문을 답해
1. 왜 ASC에 태그를 붙이는 방식이 2가지인가? 무엇을 사용해야하고, 전자 방식을 사용한다면 무슨 이점을 얻을 수 있는가?
2. 전자 방식을 사용한다면 어느 코드에서 구체적으로 ASC에 태그를 넣는가? 이렇게 넣은 태그는 영구적인가? 아니면 후자의 방식 같이 GE 적용이 끝나면 사라지는가?&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;A1&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 구분&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 두 함수가 작동하는 &lt;b&gt;레이어 자체가 다르다&lt;/b&gt;.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&amp;nbsp;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;SetAndApplyTargetTagChanges&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;ApplyGameplayEffectSpecToTarget&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;레이어&lt;/td&gt;
&lt;td&gt;&lt;b&gt;GE 에셋 정의 수정&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;GE 런타임 인스턴스 적용&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대상&lt;/td&gt;
&lt;td&gt;&lt;code&gt;UGameplayEffect&lt;/code&gt; CDO/에셋&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ASC&lt;/code&gt;의 &lt;code&gt;FActiveGameplayEffectsContainer&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시점&lt;/td&gt;
&lt;td&gt;에셋 로드 후, 적용 전&lt;/td&gt;
&lt;td&gt;실제 전투/게임플레이 중&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;영속성&lt;/td&gt;
&lt;td&gt;에셋에 반영 &amp;rarr; 이후 모든 적용에 영향&lt;/td&gt;
&lt;td&gt;Duration/Instant/Infinite 정책에 따라 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q1. 왜 2가지인가? 무엇을 써야 하는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;code&gt;SetAndApplyTargetTagChanges&lt;/code&gt;는 GE 에셋의 &quot;부여할 태그 목록&quot;을 런타임에 변경하는 함수다.&lt;/b&gt;&lt;br /&gt;ASC에 태그를 직접 넣는 게 아니라, GE가 &quot;나중에 적용될 때 어떤 태그를 줄지&quot;를 결정하는 GE 에셋 자체를 수정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스 코드를 다시 보면:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cpp&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;void UTargetTagsGameplayEffectComponent::ApplyTargetTagChanges() const {
    UGameplayEffect* Owner = GetOwner();
    InheritableGrantedTagsContainer.ApplyTo(Owner-&amp;gt;CachedGrantedTags); // &amp;larr; GE 에셋의 CachedGrantedTags 갱신
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;CachedGrantedTags&lt;/code&gt;는 &lt;b&gt;GE 에셋이 들고 있는 &quot;줄 태그 목록&quot; 캐시&lt;/b&gt;다.&lt;br /&gt;이 시점에선 ASC에 아무것도 들어가지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이 방식을 쓰는 이유와 이점:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터 드리븐 GE 커스터마이징&lt;/b&gt;: 같은 GE 블루프린트를 재사용하면서 부여 태그만 C++/BP로 런타임 교체&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GE 파생(Inheritance) 시스템 활용&lt;/b&gt;: &lt;code&gt;UpdateInheritedTagProperties&lt;/code&gt;로 부모 GE Component의 태그를 상속받아 오버라이드 가능 &amp;mdash; 에셋 중복 없이 태그 변형&lt;/li&gt;
&lt;li&gt;&lt;b&gt;에디터 워크플로우&lt;/b&gt;: &lt;code&gt;WITH_EDITOR&lt;/code&gt; 분기가 있는 이유가 이것 &amp;mdash; 에디터에서 GE를 편집할 때 태그 미리보기/검증용으로도 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결론: 일반적인 게임플레이 태그 부여는 &lt;code&gt;ApplyGameplayEffectSpecToTarget&lt;/code&gt;가 공식 루트.&lt;/b&gt;&lt;br /&gt;&lt;code&gt;SetAndApplyTargetTagChanges&lt;/code&gt;는 &quot;이 GE가 앞으로 줄 태그를 프로그래밍적으로 바꾸고 싶을 때&quot; &amp;mdash; 즉 &lt;b&gt;GE 에셋을 동적으로 구성하는 도구&lt;/b&gt;다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Q2. 전자 방식에서 ASC에 실제로 태그가 들어가는 시점과 영속성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;SetAndApplyTargetTagChanges&lt;/code&gt; &amp;rarr; &lt;code&gt;ApplyTargetTagChanges&lt;/code&gt; &amp;rarr; &lt;code&gt;Owner-&amp;gt;CachedGrantedTags&lt;/code&gt; 갱신까지는 &lt;b&gt;GE 에셋 레이어&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 ASC 진입은 이후 &lt;b&gt;GE가 적용될 때&lt;/b&gt; 일반 경로를 그대로 탄다:&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;SetAndApplyTargetTagChanges()
    └─ Owner-&amp;gt;CachedGrantedTags 갱신 (GE 에셋)
           &amp;darr; (이후 별도로)
ASC-&amp;gt;ApplyGameplayEffectSpecToSelf(Spec)
    └─ FActiveGameplayEffectsContainer::ApplyGameplayEffectSpec()
           └─ Owner-&amp;gt;AddLooseGameplayTags(
                  Spec.Def-&amp;gt;CachedGrantedTags  &amp;larr; 여기서 수정된 값이 사용됨
              )&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 &lt;b&gt;&lt;code&gt;CachedGrantedTags&lt;/code&gt;를 바꿔놓고, GE를 ASC에 Apply하는 순간&lt;/b&gt; &lt;code&gt;AddLooseGameplayTags&lt;/code&gt;를 통해 ASC에 들어간다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;영속성은 GE의 Duration Policy가 결정한다&lt;/b&gt; &amp;mdash; &lt;code&gt;SetAndApplyTargetTagChanges&lt;/code&gt;가 아니라.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Duration Policy&lt;/th&gt;
&lt;th&gt;태그 수명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Instant&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;태그 부여 자체가 의미없음 (즉시 제거)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HasDuration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Duration 만료 시 &lt;code&gt;RemoveLooseGameplayTags&lt;/code&gt; 호출로 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Infinite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GE가 명시적으로 제거될 때까지 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;code&gt;SetAndApplyTargetTagChanges&lt;/code&gt;로 넣은 태그가 영구적인지 여부는 &lt;b&gt;그 GE의 Duration Policy에 달려 있다&lt;/b&gt;. 전자 방식이라서 특별히 영구적이거나 임시적인 게 아니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요약&lt;/h3&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;SetAndApplyTargetTagChanges
= &quot;이 GE가 줄 태그 목록을 런타임에 바꾼다&quot; (GE 에셋 조작)

ApplyGameplayEffectSpecToTarget  
= &quot;이 GE를 지금 대상 ASC에 적용한다&quot; (런타임 인스턴스 생성)

ASC에 태그가 실제로 들어가는 건 항상 후자의 경로.
전자는 후자가 참조할 CachedGrantedTags를 미리 세팅하는 것.&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GAS는 수치/상태 변화 + 행동 중에 캔슬/금지/쿨타임 등 필요하면 검토&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;4. 관련 문서 (Links)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[(연구) SDF 텍스처는 직접 그려서 쓸 수 있을까? - 3DCG 채널](&lt;a href=&quot;https://arca.live/b/3d3d/141290850&quot;&gt;https://arca.live/b/3d3d/141290850&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프/TIL</category>
      <author>옆집히드라</author>
      <guid isPermaLink="true">https://ndhphysics.tistory.com/95</guid>
      <comments>https://ndhphysics.tistory.com/95#entry95comment</comments>
      <pubDate>Thu, 11 Jun 2026 21:04:27 +0900</pubDate>
    </item>
    <item>
      <title>TIL260610 - Unreal - GAS3</title>
      <link>https://ndhphysics.tistory.com/94</link>
      <description>&lt;h1&gt;1. 핵심 개념&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Gameplay Cue&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 상세 내용&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.1. Gameplay Cue&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260610141442.png&quot; data-origin-width=&quot;373&quot; data-origin-height=&quot;366&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/YwMLE/dJMcaar579f/TbE0k8onyHWjZdCLHIwQdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/YwMLE/dJMcaar579f/TbE0k8onyHWjZdCLHIwQdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/YwMLE/dJMcaar579f/TbE0k8onyHWjZdCLHIwQdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FYwMLE%2FdJMcaar579f%2FTbE0k8onyHWjZdCLHIwQdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;373&quot; height=&quot;366&quot; data-filename=&quot;Pasted image 20260610141442.png&quot; data-origin-width=&quot;373&quot; data-origin-height=&quot;366&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gameplay Cue는 게임플레이 로직에 영향을 주지 않는 부가 효과(FX) 전용 기능이다. 하나의 게임플레이 태그가 하나의 게임플레이 큐에 대응되며 독립된 GameplayCueManager가 태그를 기반으로 FX 효과를 재생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GameplayCue는 게임플레이 이펙트에서 태그를 지정해서 자동 발동할 수 있으며, 태그를 사용하여 원하는 타이밍에 직접 호출도 가능하다. 또한 Gameplay Cue에 사용되는 태그는 특정 폴더 내에 블루프린트 이름과 자동으로 매칭하는 기능을 지원한다.(GC_DAMAGE_EFFECT 블루 프린트를 GameplayCue.Damage.Effect 태그에 대응하는 식)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260609174107.png&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo5sM2/dJMcaar579m/RKgX2Mh5sshTciF08aijeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo5sM2/dJMcaar579m/RKgX2Mh5sshTciF08aijeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo5sM2/dJMcaar579m/RKgX2Mh5sshTciF08aijeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo5sM2%2FdJMcaar579m%2FRKgX2Mh5sshTciF08aijeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;442&quot; height=&quot;405&quot; data-filename=&quot;Pasted image 20260609174107.png&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GameplayCue의 종류&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Static: 일순간(Burst) -&amp;gt; Instant 타입의 GE와 연계&lt;/li&gt;
&lt;li&gt;Actor: 살아있는 동안 지속 -&amp;gt; Duration 타입의 GE와 연계&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Event Functions&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GameplayCue Static: 발동 시 OnExecute 함수를 실행함&lt;/li&gt;
&lt;li&gt;GameplayCue Actor: 반복되는 주기(Period)마다 Executed 이벤트를 발생시킴&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;GAS에서의 데이터&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;수치 데이터&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;AttributeSet&lt;/code&gt;과 &lt;code&gt;FGameplayAttributeData&lt;/code&gt;를 통해 관리한다. GAS의 코어 시스템(클라이언트 예측, 네트워크 롤백, 수치 중첩 및 계수 곱연산 파이프라인 등)은 성능 최적화와 연산의 통일성을 위해 내부적으로 오직 &lt;code&gt;float&lt;/code&gt;으로만 구현되어 있다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 1. 선언은 무조건 float 기반으로 진행
UPROPERTY(BlueprintReadOnly, Category = &quot;Ammo&quot;)
FGameplayAttributeData CurrentAmmo;
ATTRIBUTE_ACCESSORS(UMyAttributeSet, CurrentAmmo)

// 2. 로직에서 꺼내 쓸 때 형변환 (C++)
int32 DisplayAmmo = FMath::RoundToInt(GetCurrentAmmo());&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;논리 데이터&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Gameplay Tag&lt;/code&gt;로 관리한다.&lt;/p&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;// ASC에서 특정 상태(Tag)를 가지고 있는지 검사하여 bool처럼 활용
if (TargetASC-&amp;gt;HasMatchingGameplayTag(FGameplayTag::RequestGameplayTag(FName(&quot;Status.Debuff.Stun&quot;))))
{
    // 기절 상태일 때의 처리 로직
    return; 
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그외 데이터&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Gameplay Effect Context&lt;/code&gt;에 상속해 Pre(Post)GameplayEffectExecute에서 사용한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// 1. 커스텀 Context 구조체 선언
USTRUCT()
struct FMyCustomEffectContext : public FGameplayEffectContext
{
    GENERATED_BODY()

    // 원하는 데이터를 자유롭게 선언
    UPROPERTY()
    FVector HitImpactLocation;

    UPROPERTY()
    FString UsedWeaponName;
};

// 2. 타겟의 AttributeSet(PostGameplayEffectExecute)에서 데이터 추출
void UMyAttributeSet::PostGameplayEffectExecute(const FGameplayEffectModCallbackData&amp;amp; Data)
{
    Super::PostGameplayEffectExecute(Data);

    // 전달받은 봉투(Context)를 열어서 커스텀 타입으로 캐스팅
    if (const FMyCustomEffectContext* CustomContext = static_cast&amp;lt;const FMyCustomEffectContext*&amp;gt;(Data.EffectSpec.GetContext().Get()))
    {
        FVector ImpactPoint = CustomContext-&amp;gt;HitImpactLocation;
        UE_LOG(LogTemp, Log, TEXT(&quot;타격당한 위치: %s&quot;), *ImpactPoint.ToString());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;info, handle, spec&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Info (정보 / 신분증)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대표 구조체:&lt;/b&gt; &lt;code&gt;FGameplayAbilityActorInfo&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비유:&lt;/b&gt; 스킬을 사용하는 주체의 &lt;b&gt;&quot;종합 신분증명서&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[역할과 특징]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킬(GA)이 실행될 때, *&quot;내 주인이 누구지? 내 애니메이션은 어디서 틀어야 하지? 내 컨트롤러는 뭐지?&quot;*를 알기 위해 매번 &lt;code&gt;Cast&amp;lt;ACharacter&amp;gt;&lt;/code&gt;를 하는 것은 성능상 매우 비효율적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 GAS는 &lt;code&gt;InitAbilityActorInfo()&lt;/code&gt;가 호출되는 순간, 이 스킬을 쓸 액터의 필수 컴포넌트(포인터)들을 미리 싹 다 캐싱(저장)해 둡니다. 이 캐싱된 데이터 묶음이 바로 &lt;code&gt;ActorInfo&lt;/code&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[주요 포함 데이터]&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;OwnerActor&lt;/code&gt;: ASC를 소유한 논리적 액터 (예: PlayerState)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AvatarActor&lt;/code&gt;: 월드에 존재하는 물리적 액터 (예: Character)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PlayerController&lt;/code&gt;, &lt;code&gt;SkeletalMeshComponent&lt;/code&gt;, &lt;code&gt;MovementComponent&lt;/code&gt; 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[실제 사용 예시]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킬(GA) 내부에서 주인의 캐릭터 무브먼트에 접근하고 싶을 때 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;// GA 내부에서 매번 Cast 할 필요 없이 Info에서 바로 꺼내 씁니다.
UCharacterMovementComponent* MoveComp = GetCurrentActorInfo()-&amp;gt;MovementComponent.Get();&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Handle (핸들 / 영수증 번호)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대표 구조체:&lt;/b&gt; &lt;code&gt;FGameplayAbilitySpecHandle&lt;/code&gt;, &lt;code&gt;FActiveGameplayEffectHandle&lt;/code&gt;, &lt;code&gt;FGameplayAbilityTargetDataHandle&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비유:&lt;/b&gt; 포인터 대신 사용하는 &lt;b&gt;&quot;안전한 식별표(티켓 번호)&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[역할과 특징]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티플레이 게임에서는 스킬이나 이펙트가 언제 파괴되거나 지연(Lag)될지 모릅니다. 만약 C++ 원시 포인터(&lt;code&gt;*&lt;/code&gt;)를 주고받다가 대상이 삭제되면 게임이 크래시(Crash)됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 막기 위해 GAS는 객체 자체를 넘기지 않고, 고유한 ID 번호가 적힌 가벼운 구조체인 &lt;code&gt;Handle&lt;/code&gt;을 발급합니다. 시스템에 *&quot;이 핸들 번호 가진 이펙트 좀 지워줘&quot;*라고 요청하면, 시스템이 알아서 안전하게 찾아 지워줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[주요 사용처]&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;FGameplayAbilitySpecHandle&lt;/code&gt;:&lt;/b&gt; 장착된 스킬을 특정해서 발동(&lt;code&gt;TryActivateAbility&lt;/code&gt;)하거나 해제할 때 사용.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;FActiveGameplayEffectHandle&lt;/code&gt;:&lt;/b&gt; 타겟에게 걸어둔 독(Poison) 데미지를 5초 뒤에 추적해서 강제로 지울 때 사용.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[실제 사용 예시]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++&lt;/p&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;// 1. 적에게 이펙트를 적용하고 '영수증 번호(Handle)'를 받음
FActiveGameplayEffectHandle PoisonHandle = TargetASC-&amp;gt;ApplyGameplayEffectSpecToSelf(*Spec);

// 2. 나중에 해독제를 먹으면 그 영수증 번호로 이펙트를 지움
TargetASC-&amp;gt;RemoveActiveGameplayEffect(PoisonHandle);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Spec (명세서 / 주문서)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대표 구조체:&lt;/b&gt; &lt;code&gt;FGameplayAbilitySpec&lt;/code&gt;, &lt;code&gt;FGameplayEffectSpec&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비유:&lt;/b&gt; 원본 클래스(설계도)를 바탕으로 구체적인 수치를 적어 넣은 &lt;b&gt;&quot;실행 주문서&quot;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[역할과 특징]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼의 클래스(Class)나 CDO는 원본 데이터이므로 게임 중에 함부로 수정할 수 없습니다. 따라서 원본 클래스를 바탕으로 &lt;b&gt;&quot;현재 레벨&quot;, &quot;시전자 정보&quot;, &quot;동적 태그&quot; 등을 조합한 인스턴스화된 데이터&lt;/b&gt;를 만들어야 하는데, 이것이 &lt;code&gt;Spec&lt;/code&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[주요 사용처]&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;FGameplayAbilitySpec&lt;/code&gt;:&lt;/b&gt; 캐릭터에게 스킬을 부여(&lt;code&gt;GiveAbility&lt;/code&gt;)할 때, 단순히 '파이어볼 클래스'만 주는 게 아니라 *&quot;파이어볼 클래스, 스킬 레벨 3, 입력 키 Q&quot;*를 묶어서 Spec으로 만들어 줍니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;code&gt;FGameplayEffectSpec&lt;/code&gt;:&lt;/b&gt; 적에게 데미지를 줄 때 적용됩니다. 단순히 '데미지 GE'를 넣는 게 아니라, *&quot;데미지 GE, 시전자의 현재 레벨, 시전자의 공격력 계수&quot;*를 모두 담은 명세서(&lt;code&gt;Spec&lt;/code&gt;)를 만들어서 타겟에게 날립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[실제 사용 예시]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;// 1. GE 클래스를 기반으로 빈 명세서(Spec)를 만듭니다.
FGameplayEffectSpecHandle SpecHandle = MakeOutgoingGameplayEffectSpec(DamageEffectClass, Level);

// 2. 명세서에 방금 타겟팅한 정보를 추가로 적어 넣습니다.
SpecHandle.Data.Get()-&amp;gt;AddDynamicAssetTags(MyTags);

// 3. 완성된 명세서를 타겟의 ASC에 제출(Apply)합니다.
TargetASC-&amp;gt;ApplyGameplayEffectSpecToSelf(*SpecHandle.Data.Get());&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  3줄 요약 테이블&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;b&gt;용어&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;풀네임 예시&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;비유&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;핵심 역할&lt;/b&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Info&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FGameplayAbilityActorInfo&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;신분증&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&quot;내가 누구고 어떤 컴포넌트를 가졌는가?&quot; (캐싱된 포인터 모음)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Handle&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FActiveGameplayEffectHandle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;티켓 / 번호표&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&quot;포인터 대신 쓰는 안전한 ID 번호&quot; (크래시 방지용 참조 객체)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Spec&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FGameplayEffectSpec&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;주문서 / 명세서&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&quot;클래스 + 레벨 + 동적 변수&quot;가 결합된 실질적인 실행 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;3. 질문 및 해결 (Q&amp;amp;A)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ASC의 레플리케이션 정책은? 언리얼에서의 레플리케이션의 개념은 구체적으로 무엇?&lt;/li&gt;
&lt;li&gt;ASC에서 AttributeSet은 어디에?&lt;/li&gt;
&lt;li&gt;GA 없이 GE만으로 AttributeSet 변경 가능?&lt;/li&gt;
&lt;li&gt;AbilityTask는 비동기 구현용?&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;4. 관련 문서 (Links)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[[이전 관련 노트]]&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;www.google.com&quot;&gt;참고한 외부 링크&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프/TIL</category>
      <author>옆집히드라</author>
      <guid isPermaLink="true">https://ndhphysics.tistory.com/94</guid>
      <comments>https://ndhphysics.tistory.com/94#entry94comment</comments>
      <pubDate>Wed, 10 Jun 2026 23:28:07 +0900</pubDate>
    </item>
    <item>
      <title>TIL260609 - Unreal - GAS2</title>
      <link>https://ndhphysics.tistory.com/93</link>
      <description>&lt;h1&gt;1. 핵심 개념&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GameFeature 플러그인&lt;/li&gt;
&lt;li&gt;AttributeSet&lt;/li&gt;
&lt;li&gt;GameplayEffect&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 상세 내용&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.1. AttributeSet&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AttrubuteSet은 다수의 &lt;code&gt;FGameplayAttributeData&lt;/code&gt;를 묶어 관리하는 언리얼 오브젝트로 5.6버전 기준 cpp로만 생성이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 스탯에 대해서 max값을 GameplayAbilityData로 추가 설정하는 것을 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보유한 스탯의 BaseValue가 변하면 AttributeSet의 이벤트 함수가 호출된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;FGameplayAttributeData&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;FGameplayAttributeData&lt;/code&gt;는 단일 &lt;code&gt;float&lt;/code&gt; 값이 아니라 내부적으로 BaseValue(기본값)와 &lt;b&gt;CurrentValue(현재값)&lt;/b&gt; 두 가지 상태를 유지하며, Gameplay Effect(GE)의 지속 시간(Duration Policy)에 따라 다르게 반응한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Instant GE (즉발형):&lt;/b&gt; 데미지나 영구적인 스탯 상승처럼 즉시 적용되는 효과. &lt;b&gt;BaseValue&lt;/b&gt; 자체를 영구적으로 변경한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Duration / Infinite GE (지속/무한형):&lt;/b&gt; 10초간 공격력 증가 버프와 같은 효과. BaseValue는 원래 상태로 보존하고 &lt;b&gt;CurrentValue&lt;/b&gt;만 일시적으로 변경한다. 버프가 종료되면 CurrentValue는 자동으로 BaseValue로 롤백된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;매크로&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AttributeSet을 만들 때는 &lt;code&gt;UAttributeSet&lt;/code&gt;을 상속해서 만든다. 아래 테이블과 같이 매크로를 사용하여 게터, 세터, 초기화 함수를 설정할 수 있다.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;매크로(파라미터)&lt;/th&gt;
&lt;th&gt;생성된 함수의 시그니처&lt;/th&gt;
&lt;th&gt;행동/사용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UMyAttributeSet, Health)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static FGameplayAttribute GetHealth()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;스태틱 함수이며, 엔진의 리플렉션 시스템으로부터 &lt;code&gt;FGameplayAttribute&lt;/code&gt; 구조체를 반환합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GAMEPLAYATTRIBUTE_VALUE_GETTER(Health)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;float GetHealth() const&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;게임플레이 어트리뷰트 'Health'의 현재 값을 반환합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GAMEPLAYATTRIBUTE_VALUE_SETTER(Health)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;void SetHealth(float NewVal)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;게임플레이 어트리뷰트 'Health'의 값을 &lt;code&gt;NewVal&lt;/code&gt; 로 설정합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GAMEPLAYATTRIBUTE_VALUE_INITTER(Health)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;void InitHealth(float NewVal)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;게임플레이 어트리뷰트 'Health'의 값을 &lt;code&gt;NewVal&lt;/code&gt; 로 초기화합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;        UCLASS()
    class MYPROJECT_API UMyAttributeSet : public UAttributeSet
    {
        GENERATED_BODY()

        protected:
        /** 샘플 어트리뷰트 'Health'*/
        UPROPERTY(EditAnywhere, BlueprintReadOnly)
        FGameplayAttributeData Health;

        //~ ... 여기에 다른 게임플레이 어트리뷰트...

        public:
        //~ 어트리뷰트 'Health'의 헬퍼 함수
        GAMEPLAYATTRIBUTE_PROPERTY_GETTER(UMyAttributeSet, Health);
        GAMEPLAYATTRIBUTE_VALUE_GETTER(Health);
        GAMEPLAYATTRIBUTE_VALUE_SETTER(Health);
        GAMEPLAYATTRIBUTE_VALUE_INITTER(Health);

        //~ ... 여기에 다른 게임플레이 어트리뷰트의 헬퍼 함수...
    };&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Attribute의 초기화는 'AttributeMetaData'라는 게임플레이 어빌리티 시스템 행 타입을 사용하는 &lt;a href=&quot;https://dev.epicgames.com/documentation/unreal-engine/data-driven-gameplay-elements-in-unreal-engine#datatables&quot;&gt;데이터 테이블&lt;/a&gt;로 초기화할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Meta Attribute (메타 어트리뷰트)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메타 어트리뷰트는 체력(Health)이나 마나(Mana)처럼 지속적으로 유지되는 스탯이 아니라, 데미지나 치유량 등 중간 연산을 위해 일회성으로 사용되는 임시 스탯(상태)을 의미한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;설계 목적:&lt;/b&gt; 공격(GE)이 타겟의 체력을 직접 깎아버리면 무적 상태나 방어력 계산 등을 중간에 개입시키기 어렵다. 따라서 GE는 타겟의 체력이 아닌 &lt;b&gt;'Damage'라는 임시 스탯&lt;/b&gt;을 증가시키고, AttributeSet에서 이를 확인하여 최종 체력을 깎는 완충 지대 역할을 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;특징:&lt;/b&gt; 서버와 클라이언트 간에 동기화(Replication)할 필요가 없으며, 연산이 끝나면 즉시 0으로 초기화해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UI와 연동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AttributeSet에는 PreAttributeChange와 PostAttributeChange가 존재한다. clmap와 같은 스탯의 사전 설정은 pre에서, BaseValue가 변경된 후의 값 처리는 Post에서 처리한다.(OldValue, NewValue 모두 알려줌)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260609004937.png&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be8CLg/dJMcaar5aWP/JSjYVlVBPXSfRiZbiRZNX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be8CLg/dJMcaar5aWP/JSjYVlVBPXSfRiZbiRZNX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be8CLg/dJMcaar5aWP/JSjYVlVBPXSfRiZbiRZNX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe8CLg%2FdJMcaar5aWP%2FJSjYVlVBPXSfRiZbiRZNX1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1581&quot; height=&quot;543&quot; data-filename=&quot;Pasted image 20260609004937.png&quot; data-origin-width=&quot;1581&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ASC는 InitializeComponent 함수에서 액터에 설정된 모든 AttributeSet 객체를 자동으로 찾아서 등록한다. 이 작업이 끝나면 PostInitializeComponents 함수가 실행되는데, 이 때 확정된 AttributeSet 정보를 UI와 연동하는 것을 권장한다.(위 시퀀스 참고)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.2. GameplayEffect&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GameplayEffect는 블루프린트 에디터에서 설정하는 것을 권장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260609142658.png&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;471&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CDWQS/dJMcabYPSv7/lIpfTcfk3DsAEoWhEO9l8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CDWQS/dJMcabYPSv7/lIpfTcfk3DsAEoWhEO9l8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CDWQS/dJMcabYPSv7/lIpfTcfk3DsAEoWhEO9l8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCDWQS%2FdJMcabYPSv7%2FlIpfTcfk3DsAEoWhEO9l8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;643&quot; height=&quot;471&quot; data-filename=&quot;Pasted image 20260609142658.png&quot; data-origin-width=&quot;643&quot; data-origin-height=&quot;471&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 게임플레이 이펙트는 독립적으로 동작하기 위해 다양한 정보를 받아 실행된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GameplayEffectSpec: 다양한 정보를 담은 데이터이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GameplayEffect Class: 이펙트를 발동한 &lt;b&gt;GA&lt;/b&gt;와 이를 수행한 ASC 관련 액터 정보(ActorInfo)&lt;/li&gt;
&lt;li&gt;GameplayEffect Context: 발동할 게임플레이 이펙트 클래스 정보&lt;/li&gt;
&lt;li&gt;GameplayEffect Context: 게임플레이 이펙트가 발동했던 상황정보&lt;/li&gt;
&lt;li&gt;Level &amp;amp; Stack Count: 레벨 및 스택 카운트&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GameplayEffectSpec을 기반으로 자신에게 발동 혹은 다른 액터에게 발동 가능(레벨업으로 자신의 능력치 상승, 공격으로 상대방의 Health Attribute 차감 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.3. AttributeSet과 GameplayEffect 연동&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260609013558.png&quot; data-origin-width=&quot;683&quot; data-origin-height=&quot;739&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceH7dQ/dJMcabLk1YQ/Kdlj2tJeKvQQTmKKKncDX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceH7dQ/dJMcabLk1YQ/Kdlj2tJeKvQQTmKKKncDX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceH7dQ/dJMcabLk1YQ/Kdlj2tJeKvQQTmKKKncDX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceH7dQ%2FdJMcabLk1YQ%2FKdlj2tJeKvQQTmKKKncDX0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;683&quot; height=&quot;739&quot; data-filename=&quot;Pasted image 20260609013558.png&quot; data-origin-width=&quot;683&quot; data-origin-height=&quot;739&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Attribute Set에서는 Gameplay Effect 적용에 관련된 가상 함수(virtual function)를 제공한다.&lt;br /&gt;각각 BaseValue 변경에 대해서만 반응&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PreGameplayEffectExecute: Attribute가 변경되기 전, 현재 진행 중인 Gameplay Effect를 진행할 것인확인하는 함수&lt;/li&gt;
&lt;li&gt;PostGameplayEffectExecute: Gameplay Effect가 확정되기 직전에 BaseValue를 수정할 수 있는 최종함수&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;3. 질문 및 해결 (Q&amp;amp;A)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내일 Gameplay Cue 파트 추가로 본 다음 전체적인 흐름도 다이어그램으로 정리하자&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;4. 관련 문서 (Links)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=-DLeHXrGPrM&quot;&gt;UE5의 모듈형 게임 피처: 언리얼 방식의 플러그 앤 플레이&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/unreal-engine/gameplay-attributes-and-attribute-sets-for-the-gameplay-ability-system-in-unreal-engine&quot;&gt;Gameplay Attributes and Attribute Sets for the Gameplay Ability System in Unreal Engine | Unreal Engine 5.7 Documentation | Epic Developer Community&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프/TIL</category>
      <author>옆집히드라</author>
      <guid isPermaLink="true">https://ndhphysics.tistory.com/93</guid>
      <comments>https://ndhphysics.tistory.com/93#entry93comment</comments>
      <pubDate>Tue, 9 Jun 2026 21:03:15 +0900</pubDate>
    </item>
    <item>
      <title>TIL260608 - Unreal</title>
      <link>https://ndhphysics.tistory.com/92</link>
      <description>&lt;h1 data-heading=&quot;1. 핵심 개념&quot;&gt;1. 핵심 개념&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GameMode &amp;amp; States&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 data-heading=&quot;2. 상세 내용&quot;&gt;2. 상세 내용&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260608202157.png&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;435&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/30KCR/dJMcaip4jfn/mYTBuOSqy73VC6qb53Bu0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/30KCR/dJMcaip4jfn/mYTBuOSqy73VC6qb53Bu0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/30KCR/dJMcaip4jfn/mYTBuOSqy73VC6qb53Bu0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F30KCR%2FdJMcaip4jfn%2FmYTBuOSqy73VC6qb53Bu0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;728&quot; height=&quot;435&quot; data-filename=&quot;Pasted image 20260608202157.png&quot; data-origin-width=&quot;728&quot; data-origin-height=&quot;435&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-heading=&quot;2.1. GameMode &amp;amp; States&quot; data-ke-size=&quot;size26&quot;&gt;2.1. GameMode &amp;amp; States&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 플레이에 관한 정보는 GameMode와 GameStates에 의해 관리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임을 구성하는 규칙들은 게임 모드를 구성한다. 이때 게임모드는 다음과 같은 룰을 포함한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플레이어(또는 spectators)의 목록&lt;/li&gt;
&lt;li&gt;게임을 어떻게 들어오는지, 스폰 지역과 스폰 행동&lt;/li&gt;
&lt;li&gt;게임이 정지되었는지, 정지 되었다면 어떻게 처리되는지&lt;/li&gt;
&lt;li&gt;레벨 간 전환&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;룰과 관련된 이벤트는 다른 모든 플레이어들에 의해 추적되거나(tracked) 공유될 수 있어야 한다. 이러한 정보는 GameState에 의해 저장되고 동기화 된다. 이때 GameState는 다음과 같은 정보(information)를 포함한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;러닝 타임&lt;/li&gt;
&lt;li&gt;각 플레이어가 언제 접속(join) 했는지, 그리고 각 플레이어의 현재 상태&lt;/li&gt;
&lt;li&gt;현재 Game Mode의 베이스 클래스&lt;/li&gt;
&lt;li&gt;게임이 시작되었는지, 아닌지&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-heading=&quot;Game Modes&quot; data-ke-size=&quot;size23&quot;&gt;Game Modes&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AGameModeBase: 모든 게임 모드의 베이스 클래스로 부적인 구현이 없어서 가벼움(simplicity &amp;amp; efficiency)&lt;/li&gt;
&lt;li&gt;AGameMode: AGameModeBase의 파생 클래스로 match state 개념이 추가되어서 멀티플레이 슈터 장르 같은 게임에 적합하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. AGameModeBase (기본 게임 모드)&lt;/b&gt; 모든 게임 모드의 최상위 기본 클래스이며, 게임의 전반적인 뼈대와 플레이어 접속 관리를 담당합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;서버 전용:&lt;/b&gt; 이 클래스는 오직 서버에만 존재하며 클라이언트에는 복제(Replication)되지 않습니다. 클라이언트는 현재 어떤 게임 모드 클래스가 쓰이는지만 알 수 있고, 내부 변수에는 접근할 수 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주요 함수:&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InitGame: 다른 스크립트나 컴포넌트가 초기화되기 전 가장 먼저 호출되어 게임 모드의 파라미터 등을 세팅합니다.&lt;/li&gt;
&lt;li&gt;PreLogin / PostLogin: 플레이어의 서버 접속 시도를 승인/거절(PreLogin)하고, 접속이 완료된 후(PostLogin) 컨트롤러에 안전하게 복제 함수를 호출할 수 있게 합니다.&lt;/li&gt;
&lt;li&gt;HandleStartingNewPlayer / RestartPlayer / SpawnDefaultPawnAtTransform: 플레이어의 폰(Pawn)을 생성하고 알맞은 위치(PlayerStart 등)에 스폰시킵니다.&lt;/li&gt;
&lt;li&gt;Logout: 플레이어가 게임을 떠나거나 파괴될 때 호출됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. AGameMode (멀티플레이어 게임 모드)&lt;/b&gt; AGameModeBase의 하위 클래스로, 멀티플레이어 매치의 흐름을 제어하기 위한 &lt;b&gt;상태기(State Machine)&lt;/b&gt; 기능이 추가되어 있습니다. *&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;주요 매치 상태 (Match States):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EnteringMap: 맵 로딩 상태. 아직 틱(Tick)이 돌지 않습니다.&lt;/li&gt;
&lt;li&gt;WaitingToStart: 틱은 돌고 있지만 아직 플레이어들이 스폰되지 않은 대기 상태입니다.&lt;/li&gt;
&lt;li&gt;InProgress: 본격적인 게임 진행 상태입니다. 모든 액터의 BeginPlay가 호출됩니다.&lt;/li&gt;
&lt;li&gt;WaitingPostMatch: 게임이 끝난 후 다른 맵으로 이동하기 전의 대기 상태입니다. 신규 유저 접속이 차단됩니다.&lt;/li&gt;
&lt;li&gt;LeavingMap / Aborted: 맵 전환 중이거나 치명적인 오류로 경기가 중단된 상태입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 블루프린트 게임 모드 (Game Mode Blueprints)&lt;/b&gt; C++ 코드를 건드리지 않고도 에디터 내에서 변수를 수정하거나 맵마다 쉽게 적용하기 위해 게임 모드를 블루프린트로 상속받아 사용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기본 설정 클래스:&lt;/b&gt; 사용할 Default Pawn, HUD, PlayerController, Game State, Player State 등을 여기서 지정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 게임 모드 적용 우선순위 (Setting the Game Mode)&lt;/b&gt; 특정 맵에 게임 모드가 적용되는 순서입니다. (1번이 가장 우선순위가 높습니다)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;URL 인자:&lt;/b&gt; 게임 실행 시 명령줄에 ?game=MyGameMode 파라미터를 강제로 전달할 때.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;맵 접두사 (Map Prefixes):&lt;/b&gt; DefaultEngine.ini에 규칙을 정해둘 때 (예: 맵 이름이 DM-으로 시작하면 데스매치 모드 자동 적용).&lt;/li&gt;
&lt;li&gt;&lt;b&gt;월드 세팅 (World Settings):&lt;/b&gt; 에디터 내부의 개별 맵 세팅 창에서 덮어쓰기(Override)할 때.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;프로젝트 세팅:&lt;/b&gt; 기본 세팅(GlobalDefaultGameMode)에 설정된 값.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 게임 스테이트 (Game State)&lt;/b&gt; 서버에서 관리되는 게임의 전반적인 상황을 모든 클라이언트에게 동기화(Replication)하는 역할을 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;관리 목적:&lt;/b&gt; '팀 전체 점수', '접속된 플레이어 목록', '완료된 퀘스트' 등 특정 개인이 아닌 &lt;b&gt;모두가 공유해야 하는 정보&lt;/b&gt;를 담는 데 사용합니다. (개인의 킬/데스 점수는 PlayerState에서 관리하는 것이 맞습니다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;주요 기능 (AGameStateBase):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GetServerWorldTimeSeconds(): 서버와 클라이언트 간에 오차가 없는 동기화된 서버 시간을 반환합니다.&lt;/li&gt;
&lt;li&gt;PlayerArray: 현재 게임에 접속한 모든 플레이어의 상태(APlayerState)를 담고 있는 배열입니다.&lt;/li&gt;
&lt;li&gt;HasBegunPlay(): 게임이 본격적으로 시작되어 BeginPlay가 호출되었는지 여부를 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;플레이어 스테이트(Player State)&lt;br /&gt;A PlayerState is created for every player on a server (or in a standalone game). PlayerStates are replicated to all clients, and contain network game relevant information about the player, such as playername, score, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 data-heading=&quot;3. 질문 및 해결 (Q&amp;amp;A)&quot;&gt;3. 질문 및 해결 (Q&amp;amp;A)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;언리얼 인풋 라우팅 되는 구조 좀 봐야겠다.(&lt;a href=&quot;https://forums.unrealengine.com/t/playercontroller-best-practices/53630/7&quot; data-tooltip-position=&quot;top&quot;&gt;PlayerController best practices. - Programming &amp;amp; Scripting / Blueprint - Epic Developer Community Forums&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;AGameSession, Steam, Epic Online Services(EOS) 등 온라인 서브시스템(OSS)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 data-heading=&quot;4. 관련 문서 (Links)&quot;&gt;4. 관련 문서 (Links)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.epicgames.com/documentation/unreal-engine/game-mode-and-game-state?application_version=4.27&quot; data-tooltip-position=&quot;top&quot;&gt;Game Mode and Game State | Unreal Engine 4.27 Documentation | Epic Developer Community&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프/TIL</category>
      <author>옆집히드라</author>
      <guid isPermaLink="true">https://ndhphysics.tistory.com/92</guid>
      <comments>https://ndhphysics.tistory.com/92#entry92comment</comments>
      <pubDate>Mon, 8 Jun 2026 20:52:54 +0900</pubDate>
    </item>
    <item>
      <title>TIL260605 - Unreal - GAS1</title>
      <link>https://ndhphysics.tistory.com/91</link>
      <description>&lt;h1&gt;1. 핵심 개념&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2026-06-05 191941.png&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;615&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dLDJ3F/dJMcacXE5KT/fkx7jtpG4HLyZwlXOoVq1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dLDJ3F/dJMcacXE5KT/fkx7jtpG4HLyZwlXOoVq1k/img.png&quot; data-alt=&quot;TMI) 젭에서 희성님 생축함&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dLDJ3F/dJMcacXE5KT/fkx7jtpG4HLyZwlXOoVq1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdLDJ3F%2FdJMcacXE5KT%2Ffkx7jtpG4HLyZwlXOoVq1k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;235&quot; data-filename=&quot;Screenshot 2026-06-05 191941.png&quot; data-origin-width=&quot;786&quot; data-origin-height=&quot;615&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;TMI) 젭에서 희성님 생축함&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UIManager 변경안&lt;/li&gt;
&lt;li&gt;GAS&lt;/li&gt;
&lt;li&gt;Named Slot: UMG에서 위젯은 자식을 가질 수 없는데 Named Slot을 사용하면 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 상세 내용&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.1. UIManager 변경안(메모)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 만들어둔 MVP 패턴 기반 UIManager-UIBase-UIRoot 구조를 Lyra 프로젝트의 구현을 반영하고 MVVM 패턴을 적용하는 식으로 리팩토링 하려고 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MVVM 패턴 적용: 기존 리플렉션 기반 Presenter 바인딩 구조를 엎고 MVVM 패턴을 적용한다.&lt;/li&gt;
&lt;li&gt;UI Extension System(동적 UI 슬롯 주입) 반영&lt;/li&gt;
&lt;li&gt;Soft Reference 기반의 지연 로딩&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config = Game&lt;/code&gt;을 활용한 데이터 주도 설계 (Data-Driven):&lt;/li&gt;
&lt;li&gt;Abstract 키워드로 블루프린트 강제&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.2. Input 흐름&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UEnhancedInputLocalPlayerSubsystem에 Input Map Context 등록&lt;/li&gt;
&lt;li&gt;Input Action 정의&lt;/li&gt;
&lt;li&gt;정의된 Input Action을 IMC에 등록&lt;/li&gt;
&lt;li&gt;조작 받을 폰에 IA에 맞는 논리 구현 후 SetupPlayerInputComponent에서 IMC와 바인딩&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2.3. Game Ability System(GAS)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;![[Pasted image 20260605151430.png]]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GAS는 어빌리티의 소유 및 활성화 기능을 제공하고 어빌리티/액터 간 상호작용을 지원하는 프레임워크이다. 기본 구조는 cpp로 잡고 자주 변겨오디는 설정이나 로직은 블루프린트를 사용하는 것을 추천한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GAS의 핵심 구성 요소&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260605151628.png&quot; data-origin-width=&quot;1337&quot; data-origin-height=&quot;586&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzc1oE/dJMb99Uagry/WwKOkIjOZFmG5X8r6kAlHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzc1oE/dJMb99Uagry/WwKOkIjOZFmG5X8r6kAlHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzc1oE/dJMb99Uagry/WwKOkIjOZFmG5X8r6kAlHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzc1oE%2FdJMb99Uagry%2FWwKOkIjOZFmG5X8r6kAlHK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1337&quot; height=&quot;586&quot; data-filename=&quot;Pasted image 20260605151628.png&quot; data-origin-width=&quot;1337&quot; data-origin-height=&quot;586&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ASC: GAS를 구동하는 핵심 컴포넌트로 액터에 해당 컴포넌트를 부착하면 GAS를 사용할 수 있다. GAS의 다른 요소를 쌓아올릴 수 있는 기반 요소; ASC를 부착한 액터끼리 GAS 사용 가능&lt;/li&gt;
&lt;li&gt;Gameplay Tag: 계층적인 태깅; 하나의 어빌리티에 여러 개의 게임플레이 태그를 할당한다면 Gameplay Tag Container를 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;GA: 모든 행동은 GA로 만들어질 수 있다. 주로 플레이어의 직접적인 입력을 통해 발동되도록 설계&lt;/li&gt;
&lt;li&gt;Attrubute Set: 영구적 또는 일시적 스탯을 표시하는데 사용&lt;/li&gt;
&lt;li&gt;GE: 단순한 데미지 계산을 넘어 복잡한 효과를 계산 구현 가능&lt;/li&gt;
&lt;li&gt;GC(Gameplay Cue): 치장(Cosmetic) 효과 처리를 위한 시스템&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GAS의 게임 플레이 루프&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260605152840.png&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VTVLN/dJMcaayOkbn/l0oZb50IvmhH299fkaTmv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VTVLN/dJMcaayOkbn/l0oZb50IvmhH299fkaTmv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VTVLN/dJMcaayOkbn/l0oZb50IvmhH299fkaTmv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVTVLN%2FdJMcaayOkbn%2Fl0oZb50IvmhH299fkaTmv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;743&quot; height=&quot;512&quot; data-filename=&quot;Pasted image 20260605152840.png&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ASC는 모든 상호작용의 허브로 GAS의 각 컴포넌트들은 서로를 직접 참조하지 않고 ASC를 통해 통신한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GAS의 게임플레이 루프는 Gameplay Tag를 규칙으로 진행한다. 스킬이 시작할 때(GA), 데미지가 들어갈 때(GE) 등 모든 단계에서 태그가 검사되어서 통과 여부를 결정한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Gameplay Ability (행동):&lt;/b&gt; 애니메이션을 재생하고, 타겟을 찾고, 데미지 데이터를 조립하는 '행동'의 주체&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Gameplay Effect (변화 지시):&lt;/b&gt; GA가 만들어낸 '데이터 명세서'입니다. 타겟의 스탯을 어떻게 바꿀지(예: 빙결 시 체력 -100)를 담고 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Attribute Set (결과 적용):&lt;/b&gt; GE의 지시를 받아 실제 캐릭터의 스탯 수치(메모리 값)가 변동되는 종착지&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Feedback (반응):&lt;/b&gt; 스탯이 변동되거나 타격이 적중했을 때, Gameplay Cue(얼음 깨지는 파티클/사운드)나 UI 업데이트 델리게이트를 통해 플레이어에게 시각적/청각적 피드백을 반환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 GAS에서의 GA는 아래와 같이 부여된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20260605153316.png&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;325&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zkC7R/dJMcaayOkbo/9cG1h6uvSWDqT0hkztVLgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zkC7R/dJMcaayOkbo/9cG1h6uvSWDqT0hkztVLgk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zkC7R/dJMcaayOkbo/9cG1h6uvSWDqT0hkztVLgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzkC7R%2FdJMcaayOkbo%2F9cG1h6uvSWDqT0hkztVLgk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1030&quot; height=&quot;325&quot; data-filename=&quot;Pasted image 20260605153316.png&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;325&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Give (부여 = 스킬 장착):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상단의 &lt;code&gt;Gameplay Ability Class&lt;/code&gt;는 에디터에서 만든 블루프린트(설계도, UClass)입니다.&lt;/li&gt;
&lt;li&gt;캐릭터가 태어날 때 이 클래스를 ASC에게 줍니다(&lt;code&gt;GiveAbility&lt;/code&gt;). 그러면 ASC는 스킬을 즉시 실행하는 게 아니라, &lt;code&gt;SpecHandle&lt;/code&gt; (빨간 원, FGameplayAbilitySpec)이라는 '티켓'을 발급하여 자신의 목록에 저장합니다. (RPG 게임에서 단축키 슬롯에 스킬을 올려둔 상태와 같습니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Activate (실행 = 스킬 사용):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플레이어가 단축키를 누르면 ASC는 해당 &lt;code&gt;SpecHandle&lt;/code&gt; 티켓을 사용해 스킬을 가동시킵니다(&lt;code&gt;TryActivateAbility&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;이때 비로소 우측의 &lt;code&gt;Gameplay Ability Instance&lt;/code&gt; (실제 메모리에 올라가 실행되는 스킬 객체)가 만들어지거나 활성화됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AbilityActorInfo (문맥 정보의 전달):&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왼쪽 ASC 안에 있던 하얀 박스(&lt;code&gt;AbilityActorInfo&lt;/code&gt;)가 우측 인스턴스 안으로 화살표 없이 동일하게 존재하는 것이 보이실 겁니다.&lt;/li&gt;
&lt;li&gt;이 구조체 안에는 &quot;로직의 주인(Owner)은 누구고, 실제 월드에서 움직이는 육신(Avatar)은 누구인가?&quot; 같은 핵심 포인터들이 들어있습니다. ASC가 스킬을 실행할 때 이 문맥 정보를 인스턴스에게 쥐여주기 때문에, 스킬 코드 내부에서 언제든 시전자의 위치나 상태를 쉽게 꺼내 볼 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;3. 질문 및 해결 (Q&amp;amp;A)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Component 리얼 타임에서 생성 절차&lt;/li&gt;
&lt;li&gt;ActorLifeCycle&lt;/li&gt;
&lt;li&gt;GameMode - GameState&lt;/li&gt;
&lt;li&gt;GameMode - GameModeBase&lt;/li&gt;
&lt;li&gt;GameplayMessageSubststem&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;4. 관련 문서 (Links)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://velog.io/@hy_gdev/GameplayMessageSystem&quot;&gt;GameplayMessageSystem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=R2pbT0h3YZU&quot;&gt;게임플레이 어빌리티 시스템(GAS)으로 RPG 시스템 만들기 1~4편&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프/TIL</category>
      <author>옆집히드라</author>
      <guid isPermaLink="true">https://ndhphysics.tistory.com/91</guid>
      <comments>https://ndhphysics.tistory.com/91#entry91comment</comments>
      <pubDate>Fri, 5 Jun 2026 21:00:07 +0900</pubDate>
    </item>
    <item>
      <title>TIL260604 - Unreal</title>
      <link>https://ndhphysics.tistory.com/90</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ima123123ge.png&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccSpJD/dJMcagTiD70/fiLblkwxhy9Y7NMoGNkJj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccSpJD/dJMcagTiD70/fiLblkwxhy9Y7NMoGNkJj1/img.png&quot; data-alt=&quot;아이돌 데뷔&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccSpJD/dJMcagTiD70/fiLblkwxhy9Y7NMoGNkJj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccSpJD%2FdJMcagTiD70%2FfiLblkwxhy9Y7NMoGNkJj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;464&quot; height=&quot;211&quot; data-filename=&quot;ima123123ge.png&quot; data-origin-width=&quot;464&quot; data-origin-height=&quot;211&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;아이돌 데뷔&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;1. 핵심 개념&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TMap&amp;lt;Key, TArray&amp;lt;Value&amp;gt;&amp;gt; 쓰는 법&lt;/li&gt;
&lt;li&gt;CDO&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 상세 내용&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.1. TMap&amp;lt;Key, TArray&amp;lt;Value&amp;gt;&amp;gt; 쓰는 법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;언리얼 엔진의 리플렉션 시스템(&lt;code&gt;UPROPERTY&lt;/code&gt; 매크로)은 중첩된 컨테이너(Nested Containers)를 지원하지 않는다.&lt;/b&gt; 내용물의 포인터들이 가비지 컬렉터(GC)에 의해 메모리에서 날아가는 것을 방지하려면 &lt;code&gt;UPROPERTY()&lt;/code&gt; 선언이 필수적인데, 이 규칙이 충돌하여 UHT(Unreal Header Tool) 컴파일 에러가 발생함&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2.2. CDO(Class Default Object)&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;cdo1.png&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;280&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bH9dq2/dJMcaayNiCa/v77K7704itEKRjvxUJJhhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bH9dq2/dJMcaayNiCa/v77K7704itEKRjvxUJJhhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bH9dq2/dJMcaayNiCa/v77K7704itEKRjvxUJJhhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbH9dq2%2FdJMcaayNiCa%2Fv77K7704itEKRjvxUJJhhk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;740&quot; height=&quot;280&quot; data-filename=&quot;cdo1.png&quot; data-origin-width=&quot;740&quot; data-origin-height=&quot;280&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;cdo2 (2).png&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OfUCk/dJMcaijghm4/XtmXlNbkPa8pepqExtDgjK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OfUCk/dJMcaijghm4/XtmXlNbkPa8pepqExtDgjK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OfUCk/dJMcaijghm4/XtmXlNbkPa8pepqExtDgjK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOfUCk%2FdJMcaijghm4%2FXtmXlNbkPa8pepqExtDgjK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;940&quot; height=&quot;157&quot; data-filename=&quot;cdo2 (2).png&quot; data-origin-width=&quot;940&quot; data-origin-height=&quot;157&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CDO의 성능상 이점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼 엔진에서&lt;code&gt;UCLASS()&lt;/code&gt; 매크로가 붙은 모든 클래스는 메모리 상에 CDO를 생성하고 유지한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액터를 스폰할 때마다 생성자를 호출하고 컴포넌트를 새로 조립한다면 성능 저하가 발생하지만, 언리얼에서는 메모리에 이미 생성자를 호출해서 완성된 CDO를 복사하는 방식으로 액터를 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 언리얼 에디터에서는 맵, 블루프린트를 저장할 때 모든 데이터를 저장하지 않고 CDO에서 변경된 값만을 저장하기 때문에 프로젝트 용량과 메모리 사용량을 아낄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;GetDefault&amp;lt;AMonster&amp;gt;()&lt;/code&gt;를 사용해서 객체 생성 없이 기본 값 확인이 가능&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CDO의 생명 주기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;엔진 초기화 단계:&lt;/b&gt; 언리얼 에디터를 실행하기 위해 로딩 창이 뜰 때, 엔진은 모든 C++ 코드를 훑어보며 클래스들을 수집합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;생성자 일괄 호출:&lt;/b&gt; 수집된 모든 클래스의 C++ 생성자를 &lt;b&gt;단 한 번씩만&lt;/b&gt; 일괄적으로 쫙 호출합니다. 이때 생성된 결과물이 CDO로 메모리에 고정됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;블루프린트 적용:&lt;/b&gt; C++ CDO를 기반으로 블루프린트 CDO가 파생되어 만들어집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;게임 플레이 (BeginPlay):&lt;/b&gt; 우리가 Play 버튼을 누르면, 이 CDO들을 복사하여 실제 월드에 객체들이 던져지고 그때서야 &lt;code&gt;BeginPlay()&lt;/code&gt;가 호출됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;주의점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언리얼에서 CDO는 에디터가 로딩될 때 실행되기 때문에 생성자 시점에서는 게임 월드 자체가 존재하지 않는다. 따라서 생성자 안에서 특정 액터를 찾는다거나, 물리 엔진 조작, 다른 객체 스폰과 같이 월드를 사용하는 작업을 하는 경우 무조건 크래시가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 언리얼에서 동적인 런타임 로직은 생성자가 아닌 &lt;code&gt;BeginPlay()&lt;/code&gt;에 넣는게 바람직하다.&lt;/p&gt;
&lt;h1&gt;3. 질문 및 해결 (Q&amp;amp;A)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h1&gt;4. 관련 문서 (Links)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미지 출처: &lt;a href=&quot;https://openmynotepad.tistory.com/113&quot;&gt;나만의 연습장 :: Unreal Engine Class Default Object (CDO)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프/TIL</category>
      <author>옆집히드라</author>
      <guid isPermaLink="true">https://ndhphysics.tistory.com/90</guid>
      <comments>https://ndhphysics.tistory.com/90#entry90comment</comments>
      <pubDate>Thu, 4 Jun 2026 21:53:28 +0900</pubDate>
    </item>
    <item>
      <title>TIL260602 - 개인</title>
      <link>https://ndhphysics.tistory.com/89</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;프붕쿤_코드카다나_푸세요.png&quot; data-origin-width=&quot;1507&quot; data-origin-height=&quot;863&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RjiB3/dJMcaiQ3Nqz/YBIcclhFwmKavh66ZxK2l1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RjiB3/dJMcaiQ3Nqz/YBIcclhFwmKavh66ZxK2l1/img.png&quot; data-alt=&quot;딴짓금지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RjiB3/dJMcaiQ3Nqz/YBIcclhFwmKavh66ZxK2l1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRjiB3%2FdJMcaiQ3Nqz%2FYBIcclhFwmKavh66ZxK2l1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1507&quot; height=&quot;863&quot; data-filename=&quot;프붕쿤_코드카다나_푸세요.png&quot; data-origin-width=&quot;1507&quot; data-origin-height=&quot;863&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;딴짓금지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;1. 핵심 개념&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이력서를 못쓰는 나에 대한 성찰&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. 상세 내용&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 일기장처럼 진행하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 내일배움캠프에서 커리어데이 파트가 진행되는 날이었다. 이력서를 작성하는 세션이였는데 이력서에 넣을 학력이며, 자격증, 경력, 대외활동이 그 무엇하나 없었다. 말 그대로 텅 빈 이력서였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;불행 중 다행인 것은, 게임 업계에서는 포트폴리오가 특출나면 어필할 여지가 있다는 것이다. 하지만 포트폴리오를 보이기에는 나의 개발 이력이 너무 짧았다. 24년도에 개발 공부를 해서 25년에 그나마 유니티로 친구 졸업프로젝트 8달 같이 진행한 것이 나의 개발 포폴에 전부다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 스스로 그래도 아트워크도 약간이나마 가능하고, 기획을 명세서부터 위키로 총 정리한 경험이 있으니 제너럴리스트? 라는 점에서 차별점이 있을까 여겼지만 클라이언트 개발자로써의 내 역량을 보여주기에는 전혀 장르가 달랐다.(자기소개서 정도는 채울 수 있을지도...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비록 원하던 기획은 도달하지 못했지만, 졸업 프로젝트로 작성한 포트폴리오라도 작성하고 싶었지만 전혀 쓰기에 정돈된 상태가 아니였다. 프로젝트를 했던 경험만으로는 이력서에 넣지 못하고, 이것을 회고하고 완전히 보여주기 좋은 상태로 정돈(유튜브 시연까지 찍어서)해서 외관으로 보여줘야 비로서 이력서에 넣을 수 있는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이력서에 작성할 내력 하나 없는게 절망적이였지만 그래도 오늘 교훈은 얻었다. 내가 작업한 프로젝트는 반드시 문서화를 생활화하고 회고를 거쳐서 완벽하게 남에게 어필할 수 있는 상태로 만드는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이번에 드러난 나의 문제점&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프로젝트를 하면 회고를 하지 않는다. 보여줄 요소가 없다.&lt;/li&gt;
&lt;li&gt;아직도 아트에 매몰된 것 같다. 인디겜 만들기에는 훌륭한 요소지만 회사 취업하기에는 별로 도움이 되 않는다.&lt;/li&gt;
&lt;li&gt;나를 보여주는 일에 전혀 능숙하지 못하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;앞으로 프로젝트를 하면은&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;개발 도중에 겪은 트러블 슈팅은 기록해놓는다.&lt;/li&gt;
&lt;li&gt;유튜브에 시연 영상을 올린다.&lt;/li&gt;
&lt;li&gt;프로젝트를 회고하면서 내가 맡은 구현부에 대한 기술 내역서를 작성한다.(남에게 보여주기 좋은 형태 정돈할 것)&lt;/li&gt;
&lt;li&gt;기술 내역서에 내 구현력을 어필할만한 어려운 파트를 도전하고 맡는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 공부하는데 정신 팔리지 말고 기록도 생활화하면서 취업을 염두해야 할 것 같다. 추가로 내년에 부캠 끝나서 상대적으로 널럴해지면 자격증 도전 좀 해야겠다. 채울게 하나도 없어서 너무 부끄럽다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 의미에서 내 TODO 리스트를 추가해야겠다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; #TODO ProjecYec에서 얻은 경험을 회고한다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; #TODO 내일배움캠프 챕터1에서 만든 피빨이 서바이벌을 회고한다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; #TODO 이세계해방 프로젝트에서 겪은 경험을 회고한다.&lt;/li&gt;
&lt;li&gt;&lt;input disabled=&quot;disabled&quot; type=&quot;checkbox&quot; /&gt; #TODO 텍스트 알피지에서 겪은 경험을 회고한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;3. 질문 및 해결 (Q&amp;amp;A)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;아래 같이 기술 내역서를 작성했더라&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서머리(뭐잘한다, 팀 플젝 협업, 구현역량), 학력사항, 어쩌구&amp;hellip;),&lt;/li&gt;
&lt;li&gt;프로젝트(소개, 개발 기간, 기술, 담당 역할,&lt;/li&gt;
&lt;li&gt;기타(링크, 브로셔, 영상 링크, 메타휴먼?)),&lt;/li&gt;
&lt;li&gt;기술내역서(설계 배경, 핵심 구조 및 설계, 구현 중 해결한 기술적 과제)&lt;/li&gt;
&lt;li&gt;문제 해결 사례(트러블 슈팅)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;배울 점&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자신이 구현한 기능은 모두 블로그에 정리하고 시연 영상까지 잊지 않고 찍는다.&lt;/li&gt;
&lt;li&gt;면접을 하지 않더라도 꾸준히 이력서를 작성하면서 갱신했다.&lt;/li&gt;
&lt;li&gt;AI에 토큰처럼 자신의 이력을 쌓어서 도움이 되는 결과를 얻어냈다.(채팅 내역이 너무 길때 로드가 느려면 확장 플러그인으로 갱신하는 것이 있더라)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언오더맵에 스트링 키값 쓸 때 비교 연산 줄이기&lt;/p&gt;
&lt;h1&gt;4. 관련 문서 (Links)&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;[[이전 관련 노트]]&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;www.google.com&quot;&gt;참고한 외부 링크&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>내일배움캠프/TIL</category>
      <author>옆집히드라</author>
      <guid isPermaLink="true">https://ndhphysics.tistory.com/89</guid>
      <comments>https://ndhphysics.tistory.com/89#entry89comment</comments>
      <pubDate>Tue, 2 Jun 2026 21:03:25 +0900</pubDate>
    </item>
  </channel>
</rss>