<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>내가 기억하려고 만든</title>
    <link>https://white-mouse.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 29 Jun 2026 11:50:08 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>럭키 </managingEditor>
    <item>
      <title>가상함수테이블과 가상함수테이블포인터 정리</title>
      <link>https://white-mouse.tistory.com/155</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;가상함수테이블과&amp;nbsp;가상함수테이블포인터 &lt;br /&gt;&lt;span style=&quot;color: #555555; text-align: center;&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;http://tcpschool.com/c/c_memory_structure&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo1K8i/btqA2a817YE/11TZZuY69K56etZc05CBGK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bo1K8i/btqA2a817YE/11TZZuY69K56etZc05CBGK/img.png&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;314&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6dwPf/btsLDwrnWDf/FGmyUrNfjfJry85QXmCj1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6dwPf/btsLDwrnWDf/FGmyUrNfjfJry85QXmCj1k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6dwPf/btsLDwrnWDf/FGmyUrNfjfJry85QXmCj1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6dwPf%2FbtsLDwrnWDf%2FFGmyUrNfjfJry85QXmCj1k%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;314&quot; height=&quot;512&quot; data-origin-width=&quot;314&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;&lt;span style=&quot;color: #555555; text-align: center;&quot; data-alt=&quot;출처:&amp;amp;amp;nbsp;http://tcpschool.com/c/c_memory_structure&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo1K8i/btqA2a817YE/11TZZuY69K56etZc05CBGK/img.png&quot; data-url=&quot;https://blog.kakaocdn.net/dn/bo1K8i/btqA2a817YE/11TZZuY69K56etZc05CBGK/img.png&quot;&gt;&lt;/span&gt;출처:&amp;nbsp;&lt;a href=&quot;http://tcpschool.com/c/c_memory_structure&quot;&gt;http://tcpschool.com/c/c_memory_structure&lt;/a&gt; &lt;br /&gt;-가상함수 테이블은 코드영역에 선언되며 테이블안에는 virtual 함수를 가리키는 주소가 들어있다.( &lt;b&gt;멤버 함수는 모두 code 영역에 저장되며 멤버 함수는 객체마다 다르게 동작하는 것이 아니기 때문에 객체마다 함수가 할당되면 비효율적일 것이다. 따라서 해당 클래스의 모든 객체는 code 영역에 있는 멤버 함수를 공유하며 사용한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-참고로 &lt;b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;비정적 멤버 변수는 객체의 생성과 동시에 생성된다. 객체 내의 지역 변수와&amp;nbsp; 동일하므로 스택에 저장된다.&amp;nbsp; (물론 동적으로 할당되는 메모리는 heap 영역에 저장됨)&lt;/b&gt; 그리고&amp;nbsp; &lt;b&gt;만약 클래스 내에 정적 멤버 변수를 선언했다면 이 변수는 객체 생성 시 할당되는 것이 아니라 프로그램 시작 시 데이터 영역에 생성된다. 따라서 정적 멤버 변수는 객체의 크기에 영향을 미치지 않는다.&lt;span&gt; &lt;/span&gt;&lt;/b&gt;)&lt;br /&gt;-가상함수&amp;nbsp;테이블은&amp;nbsp;컴파일타임에&amp;nbsp;채워져서&amp;nbsp;런타임에&amp;nbsp;사용된다. &lt;br /&gt;-가상함수테이블포인터를&amp;nbsp;모든&amp;nbsp;객체가&amp;nbsp;각각&amp;nbsp;갖고있고&amp;nbsp; &lt;br /&gt;&amp;nbsp;가리키는&amp;nbsp;클래스의&amp;nbsp;가상함수테이블은&amp;nbsp;한&amp;nbsp;클래스끼리&amp;nbsp;공유한다. &lt;br /&gt;(가상&amp;nbsp;함수&amp;nbsp;테이블은&amp;nbsp;컴파일&amp;nbsp;시&amp;nbsp;각&amp;nbsp;클래스당&amp;nbsp;1개만&amp;nbsp;생성되고&amp;nbsp;같은&amp;nbsp;클래스&amp;nbsp;객체들은&amp;nbsp;모두&amp;nbsp;같은&amp;nbsp;가상&amp;nbsp;함수&amp;nbsp;테이블을&amp;nbsp;공유한다. &lt;br /&gt;-&amp;nbsp;가상&amp;nbsp;함수를&amp;nbsp;선언해도&amp;nbsp;override&amp;nbsp;되지&amp;nbsp;않으면,&amp;nbsp;가상&amp;nbsp;함수&amp;nbsp;테이블에&amp;nbsp;추가되지&amp;nbsp;않는다. &lt;br /&gt;&lt;br /&gt;원리 &lt;br /&gt;Animal*&amp;nbsp;a&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Dog(); &lt;br /&gt;이럴시에&amp;nbsp;a는&amp;nbsp;Animal*타입이지만&amp;nbsp;Animal클래스가&amp;nbsp;가상함수를&amp;nbsp;포함하고&amp;nbsp;있으며 &lt;br /&gt;Dog클래스로&amp;nbsp;동적할당을&amp;nbsp;받았기에&amp;nbsp;생성될때&amp;nbsp;생성자에서&amp;nbsp;a객체의&amp;nbsp;가상함수테이블포인터가 &lt;br /&gt;Dog클래스의 가상함수테이블을 가리키게 된다. (Dog클래스가 컴파일 타임에 가상함수테이블을 작성한다)&lt;br /&gt;&lt;br /&gt;일반적으론&amp;nbsp;가상함수가&amp;nbsp;일반함수에&amp;nbsp;비해&amp;nbsp;성능이&amp;nbsp;많이&amp;nbsp;뒤쳐지지는&amp;nbsp;않으나&amp;nbsp;아래&amp;nbsp;상황에선&amp;nbsp;문제가&amp;nbsp;될&amp;nbsp;수&amp;nbsp;있다. &lt;br /&gt;-&amp;nbsp;가상함수에선&amp;nbsp;inline을&amp;nbsp;쓸&amp;nbsp;수&amp;nbsp;없다.&amp;nbsp;매크로&amp;nbsp;함수와&amp;nbsp;비슷하게&amp;nbsp;lnline&amp;nbsp;함수는&amp;nbsp;컴파일&amp;nbsp;과정에서&amp;nbsp;코드를&amp;nbsp;대체하겠다라는&amp;nbsp;의미이다.&amp;nbsp; &lt;br /&gt;그런데&amp;nbsp;virtual&amp;nbsp;함수는&amp;nbsp;컴파일&amp;nbsp;과정에서는&amp;nbsp;어떤값이&amp;nbsp;쓰일지&amp;nbsp;알&amp;nbsp;수&amp;nbsp;없다. &lt;br /&gt;{ &lt;br /&gt;&amp;nbsp;&amp;nbsp;Animal*a&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Dog()가&amp;nbsp;될지&amp;nbsp;Animal*a&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Cat()이&amp;nbsp;되어서&amp;nbsp;a.Walk()가&amp;nbsp;Dog의&amp;nbsp;Walk일지&amp;nbsp;Cat의&amp;nbsp;Walk일지&amp;nbsp;모른다.&amp;nbsp; &lt;br /&gt;}&amp;nbsp; &lt;br /&gt;동적으로&amp;nbsp;객체가&amp;nbsp;변할&amp;nbsp;수&amp;nbsp;있기&amp;nbsp;때문에&amp;nbsp;그&amp;nbsp;때마다&amp;nbsp;객체의&amp;nbsp;vptr이&amp;nbsp;가리키는&amp;nbsp;vtbl이&amp;nbsp;다를&amp;nbsp;수&amp;nbsp;있다. &lt;br /&gt;즉,&amp;nbsp;컴파일&amp;nbsp;단계에서는&amp;nbsp;가상&amp;nbsp;함수에&amp;nbsp;대해서&amp;nbsp;inline을&amp;nbsp;할&amp;nbsp;수&amp;nbsp;없다. &lt;br /&gt;&lt;br /&gt;-&amp;nbsp;다중&amp;nbsp;상속을&amp;nbsp;하게&amp;nbsp;되면,&amp;nbsp;다중&amp;nbsp;상속을&amp;nbsp;받은&amp;nbsp;클래스의&amp;nbsp;객체&amp;nbsp;안에&amp;nbsp;여러개의&amp;nbsp;vptr이&amp;nbsp;존재하게&amp;nbsp;된다.&amp;nbsp;따라서&amp;nbsp;vptr을&amp;nbsp;선택하는데에도&amp;nbsp;오버헤드가&amp;nbsp;발생하는데에도&amp;nbsp;비용이&amp;nbsp;증가하게&amp;nbsp;된다. &lt;br /&gt;-&amp;nbsp; &lt;br /&gt;&lt;br /&gt;*RTTI&amp;nbsp;(RunTime&amp;nbsp;Type&amp;nbsp;Identification,&amp;nbsp;런타임&amp;nbsp;타입&amp;nbsp;식별) &lt;br /&gt;실행중에&amp;nbsp;객체와&amp;nbsp;클래스&amp;nbsp;정보를&amp;nbsp;알아내기&amp;nbsp;위해&amp;nbsp;사용된다.&amp;nbsp;쓸만한&amp;nbsp;기능이지만&amp;nbsp;이를&amp;nbsp;사용하기&amp;nbsp;위해서는&amp;nbsp;역시&amp;nbsp;객체에&amp;nbsp;어딘가에는&amp;nbsp;해당&amp;nbsp;내용이&amp;nbsp;저장되어&amp;nbsp;있어야한다.&amp;nbsp;모든&amp;nbsp;객체마다&amp;nbsp;갖고&amp;nbsp;있을&amp;nbsp;필요는&amp;nbsp;없고,&amp;nbsp;vptr을&amp;nbsp;이용해서&amp;nbsp;찾는&amp;nbsp;vtbl의&amp;nbsp;첫번째&amp;nbsp;요소에&amp;nbsp;이런&amp;nbsp;type_info를&amp;nbsp;참조할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;주소를&amp;nbsp;저장하고&amp;nbsp;있게&amp;nbsp;된다.&amp;nbsp;이는&amp;nbsp;typeid&amp;nbsp;연산자를&amp;nbsp;통해서&amp;nbsp;해당&amp;nbsp;값에&amp;nbsp;접근할&amp;nbsp;수&amp;nbsp;있다.&amp;nbsp;따라서&amp;nbsp;typeid를&amp;nbsp;사용하게&amp;nbsp;되면,&amp;nbsp;type_info의&amp;nbsp;객체&amp;nbsp;메모리에&amp;nbsp;추가로&amp;nbsp;vtbl의&amp;nbsp;저장공간이&amp;nbsp;추가로&amp;nbsp;소모된다. &lt;br /&gt;&lt;br /&gt;하지만&amp;nbsp;RTTI를&amp;nbsp;통해서&amp;nbsp;type_info를&amp;nbsp;뽑아&amp;nbsp;내기&amp;nbsp;위해서는&amp;nbsp;한&amp;nbsp;가지&amp;nbsp;조건이&amp;nbsp;있다.&amp;nbsp;바로&amp;nbsp;클래스가&amp;nbsp;하나&amp;nbsp;이상의&amp;nbsp;가상&amp;nbsp;함수를&amp;nbsp;갖어야한다는&amp;nbsp;것이다.&amp;nbsp;위&amp;nbsp;설명을&amp;nbsp;들으면&amp;nbsp;당연한&amp;nbsp;것이다.&amp;nbsp;typeid는&amp;nbsp;vptr을&amp;nbsp;타고&amp;nbsp;vtbl의&amp;nbsp;첫번째&amp;nbsp;요소에서&amp;nbsp;값을&amp;nbsp;찾는다고&amp;nbsp;했다.&amp;nbsp;그런데&amp;nbsp;vtbl이&amp;nbsp;있으려면&amp;nbsp;class에&amp;nbsp;하나&amp;nbsp;이상의&amp;nbsp;가상함수가&amp;nbsp;있어야&amp;nbsp;만들어지는&amp;nbsp;것이다.&amp;nbsp;vptr도&amp;nbsp;마찬가지이다. &lt;br /&gt;=&amp;gt;Dynamic_Cast와도 관련&lt;/p&gt;</description>
      <category>[C++]</category>
      <author>럭키 </author>
      <guid isPermaLink="true">https://white-mouse.tistory.com/155</guid>
      <comments>https://white-mouse.tistory.com/155#entry155comment</comments>
      <pubDate>Thu, 2 Jan 2025 11:18:21 +0900</pubDate>
    </item>
    <item>
      <title>[C++ 20] Coroutine</title>
      <link>https://white-mouse.tistory.com/154</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Coroutine을 구글에 검색했을 때 유니티의 코루틴이 제일 많이 나온다. 이는 C#의 코루틴을 유니티에서 적극 채용해 랩핑해서 사용하는 형태이다. 유니티에서 코루틴을 사용해 쿨타임 계산 등이 편리했던 기억이 있다. 이런 코루틴의 기능의 C++20 에도 도입 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Coroutine을 한 마디로 요약하자면 어떤 함수를 호출했는데 어디까지 호출되었는지 저장하고 일시정지했다가 이어서 호출할 수 있는 기능이다.&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;사용될 수 있는 예를 한번 들어보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엄청 복잡하고 어려운 기능을 하는 함수가 있다고 가정해보자. 스타크래프트에서 군집화된 유닛들이 A*같은걸 이용해 길을 찾는 알고리즘 같은것.! 연산이 무거운 함수는 함수가 호출되고 다음으로 넘어가기 때문에 이런 함수는 호출 자체가 큰 부담이 될 것이다. 그리고 게임은 무한 루프 속에서&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;한 프레임마다 한번씩 함수를 호출하게 될것이고 이는 엄청 부담이 된다. 60FPS라 가정하면 1초에 60번씩 저 함수를 호출해야하는데 길찾기 로직을 1/60초 만에 끝내야하는것도 아니고 오래걸리게 되면 틱이 다 밀리게 되는 일이 발생할 수 있다. 즉, 복잡한 연산은 모두 한 번에 끝낼 필요가 없고 나눠서 처리해도 된다는 것이다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&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;위와 같이 1000번까지 돌아야하는 반복문이라면 한 번 호출될 때마다 100씩 0-100, 100-200,..총 10번 돌아서 해결하면 효율적으로 처리할 수 있다. 그런데 이 함수의 상태를 Coroutine이 등장하기 전에는 어디에 저장했냐하면 거의 전역으로 static변수를 만들어 임시저장할 수 있다. 그러나 임시저장할 변수가 굉장히 많이지면 이도 복잡해지므로 Coroutine이 필요해진다. 결국 하나의 복잡한 함수를 조금씩 쪼개서 호출할 수 있도록 현재 상태를 저장하고 중지시켰다가 나중에 호출하고 싶을때 재개하는게 코루틴이다.&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;서버에서는 어떻게 이 코루틴을 이용할 수 있을지 가정해보자.&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;일반적인 MMORPG에서 몬스터를 잡게되면 경험치도 오르고 아이템도 인벤토리에 들어가고 한다. 이를 순서대로&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;2. DB저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 아이템 생성 + 인벤토리 추가 (인게임 메모리)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 순서대로 이루어져야하는데 DB접근 처리가 상대적으로 오래걸리고 만약 아이템 테이블이 많으면 더 오래걸린다. 그런데 MMORPG에서 짧은 순간에 많은 일이 이루어지는데 2가 끝날때까지 기다리는건 무리이다. 그래서 만약 2fmf 별도의 스레드에 던져두고 아이템을 메모리에 생성하고 인벤토리에 추가하고 인게임 로직을 따로 돌리면 되지 않을까 싶지만 이와같이 DB와 관련된 부분과 인게임 로직을 분리하게 되면 위험할 수 있다. 만약 DB에 아이템 추가가 실패한경우 메모리에는 들고있고 렉이 생기다보면 아이템 복사가 일어날 수도 있어서 꼭 1,2,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;이런 모순을 지금까지 해결했던 방법은 이벤트 방식이었다. 잡큐를 만들어서 DB에 아이템 생성일을 넣은 다음 완료되면 콜백으로 3을 람다로 이어서 실행되는 방식이었다. 필연적으로 사용됐으나 이는 복잡하기에 코루틴으로 이를 어떻게 처리할지 고려해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1,2,3을 합친 KillMonster 일련을 Coroutine으로 만들어서 호출한 다음에 그동안 KillMonster외의 다른 부분을 처리하고, 이어서 DB가 성공적으로 처리됐으면 인게임 부분을 처리하도록 할 수 있다.&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;우선 규칙을 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Coroutine을 사용하려면 아래 중 하나를 코루틴으로 만들 함수에서 사용해야한다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 함수가 코루틴이 되려면... &lt;br /&gt;&amp;nbsp; &amp;nbsp;co_return &lt;br /&gt;&amp;nbsp; &amp;nbsp;co_yield &lt;br /&gt;&amp;nbsp; &amp;nbsp;co_await&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;b&gt;2. 코루틴 함수는 코루틴 객체를 반환형으로 해야한다. 코루틴 객체는 struct나 class로 정의할 수 있으며 promise 객체를 내부에 갖고 있어야한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 promise 객체는 다음과 같은 인터페이스를 제공해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-&amp;nbsp;기본&amp;nbsp;생성자&amp;nbsp;:&amp;nbsp;promise&amp;nbsp;객체는&amp;nbsp;기본&amp;nbsp;생성자로&amp;nbsp;만들어질&amp;nbsp;수&amp;nbsp;있어야&amp;nbsp;함 &lt;br /&gt;&amp;nbsp;-&amp;nbsp;get_return_object&amp;nbsp;:&amp;nbsp;코루틴&amp;nbsp;객체를&amp;nbsp;반환&amp;nbsp;(resumable&amp;nbsp;object) &lt;br /&gt;&amp;nbsp;-&amp;nbsp;return_value(val)&amp;nbsp;:&amp;nbsp;co_return&amp;nbsp;val에&amp;nbsp;의해&amp;nbsp;호출됨&amp;nbsp;(코루틴이&amp;nbsp;영구적으로&amp;nbsp;끝나지&amp;nbsp;않으면&amp;nbsp;없어도&amp;nbsp;됨) &lt;br /&gt;&amp;nbsp;-&amp;nbsp;return_void()&amp;nbsp;:&amp;nbsp;co_return에&amp;nbsp;의해&amp;nbsp;호출됨&amp;nbsp;(코루틴이&amp;nbsp;영구적으로&amp;nbsp;끝나지&amp;nbsp;않으면&amp;nbsp;없어도&amp;nbsp;됨) &lt;br /&gt;&amp;nbsp;-&amp;nbsp;yield_value(val)&amp;nbsp;:&amp;nbsp;co_yield에&amp;nbsp;의해&amp;nbsp;호출됨 &lt;br /&gt;&amp;nbsp;-&amp;nbsp;initial_suspend()&amp;nbsp;:&amp;nbsp;코루틴이&amp;nbsp;실행&amp;nbsp;전에&amp;nbsp;중단/연기될&amp;nbsp;수&amp;nbsp;있는지 &lt;br /&gt;&amp;nbsp;-&amp;nbsp;final_suspend()&amp;nbsp;:&amp;nbsp;코루틴이&amp;nbsp;종료&amp;nbsp;전에&amp;nbsp;중단/연기될&amp;nbsp;수&amp;nbsp;있는지 &lt;br /&gt;&amp;nbsp;-&amp;nbsp;unhandled_exception()&amp;nbsp;:&amp;nbsp;예외&amp;nbsp;처리시&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;b&gt;3. 코루틴의 3요소&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;3가지&amp;nbsp;요소로&amp;nbsp;구성 &lt;br /&gt;&amp;nbsp;-&amp;nbsp;promise&amp;nbsp;객체 &lt;br /&gt;&amp;nbsp;-&amp;nbsp;코루틴&amp;nbsp;핸들&amp;nbsp;(밖에서&amp;nbsp;코루틴을&amp;nbsp;resume&amp;nbsp;/&amp;nbsp;destroy&amp;nbsp;할&amp;nbsp;때&amp;nbsp;사용.&amp;nbsp;일종의&amp;nbsp;리모컨) &lt;br /&gt;&amp;nbsp;-&amp;nbsp;코루틴&amp;nbsp;프레임&amp;nbsp;(promise&amp;nbsp;객체,&amp;nbsp;코루틴이&amp;nbsp;인자&amp;nbsp;등을&amp;nbsp;포함하는&amp;nbsp;heap&amp;nbsp;할당&amp;nbsp;객체) &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 2와 3을 미루어 봤을 때 C++ 20에서는 코루틴을 사용할 수 있는 일종의 Framework를 제공하는 것이며 이 프레임워크를 규칙에 맞게 활용하는 것이다.&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;4. 코루틴 실행시 일어나는 일&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;padding: 15px 20px; background-color: #f8f8f8; border-radius: 5px 5px; border: 1px solid #f1f1f1; line-height: 1.8;&quot;&gt;
&lt;div style=&quot;background-image: repeating-linear-gradient(#cfe2c8, #cfe2c8 1px, #f8f8f8 1px, #f8f8f8  1.75em);&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;코루틴 실행시 일어나는 일&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;1.&amp;nbsp;코루틴이&amp;nbsp;최초&amp;nbsp;실행&amp;nbsp;되면&amp;nbsp;new를&amp;nbsp;이용해&amp;nbsp;힙&amp;nbsp;메모리&amp;nbsp;영역에&amp;nbsp;coroutine&amp;nbsp;state를&amp;nbsp;생성합니다. &lt;/s&gt;&lt;br /&gt;&lt;s&gt;2.&amp;nbsp;코루틴&amp;nbsp;함수의&amp;nbsp;모든&amp;nbsp;인자들을&amp;nbsp;coroutine&amp;nbsp;state에&amp;nbsp;복사합니다.&amp;nbsp;이때&amp;nbsp;모든&amp;nbsp;인자들은&amp;nbsp;move&amp;nbsp;되거나&amp;nbsp;복사&amp;nbsp;됩니다.&amp;nbsp; &lt;/s&gt;&lt;br /&gt;&lt;s&gt;단,&amp;nbsp;레퍼런스들은&amp;nbsp;그대로&amp;nbsp;레퍼런스로&amp;nbsp;남아&amp;nbsp;있습니다. &lt;/s&gt;&lt;br /&gt;&lt;s&gt;(*만일&amp;nbsp;코루틴이&amp;nbsp;재개(resume)&amp;nbsp;될&amp;nbsp;때,&amp;nbsp;레퍼런스&amp;nbsp;변수들의&amp;nbsp;생명&amp;nbsp;주기가&amp;nbsp;이미&amp;nbsp;종료&amp;nbsp;되었다면&amp;nbsp;뎅글링&amp;nbsp;레퍼런스를&amp;nbsp;참조&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있으므로&amp;nbsp;코루틴&amp;nbsp;함수의&amp;nbsp;인자로&amp;nbsp;레퍼런스&amp;nbsp;타입을&amp;nbsp;사용&amp;nbsp;할&amp;nbsp;때는&amp;nbsp;주의가&amp;nbsp;필요&amp;nbsp;합니다.)&lt;/s&gt; &lt;br /&gt;3.promise&amp;nbsp;객체의&amp;nbsp;생성자를&amp;nbsp;호출&amp;nbsp;합니다. &lt;br /&gt;(*만일&amp;nbsp;promise&amp;nbsp;타입의&amp;nbsp;생성자가&amp;nbsp;모든&amp;nbsp;코루틴&amp;nbsp;함수의&amp;nbsp;인자를&amp;nbsp;가지고&amp;nbsp;있다면&amp;nbsp;해당&amp;nbsp;생성자가&amp;nbsp;호출&amp;nbsp;됩니다.&amp;nbsp;그렇지&amp;nbsp;않다면&amp;nbsp;기본&amp;nbsp;생성자가&amp;nbsp;호출&amp;nbsp;됩니다.) &lt;br /&gt;4.&amp;nbsp;코루틴&amp;nbsp;반환&amp;nbsp;객체를&amp;nbsp;생성하여&amp;nbsp;반환하는&amp;nbsp;get_return_object()&amp;nbsp;함수를&amp;nbsp;호출&amp;nbsp;합니다.&amp;nbsp;이&amp;nbsp;값은&amp;nbsp;로컬&amp;nbsp;변수에&amp;nbsp;저장되었다가&amp;nbsp;최초&amp;nbsp;코루틴&amp;nbsp;중단(suspend)&amp;nbsp;시&amp;nbsp;코루틴&amp;nbsp;호출자에게&amp;nbsp;리턴&amp;nbsp;됩니다. &lt;br /&gt;5.&amp;nbsp;promise객체의&amp;nbsp;initial_suspend()&amp;nbsp;를&amp;nbsp;호출하고&amp;nbsp;그&amp;nbsp;결과를&amp;nbsp;외부&amp;nbsp;코루틴&amp;nbsp;함수의&amp;nbsp;co_await/&amp;nbsp;co_return/&amp;nbsp;co_yield&amp;nbsp;오퍼레이터에게&amp;nbsp;전달&amp;nbsp;합니다.&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;일반적으로&amp;nbsp;initial_suspend()&amp;nbsp;함수는&amp;nbsp;게으른&amp;nbsp;시작(lazily-start)을&amp;nbsp;위해&amp;nbsp;suspend_always를&amp;nbsp;리턴하거나,&amp;nbsp;즉시&amp;nbsp;시작(eagerly-start)을&amp;nbsp;위해&amp;nbsp;suspend_never를&amp;nbsp;리턴합니다. &lt;br /&gt;6.&amp;nbsp;외부&amp;nbsp;코루틴&amp;nbsp;함수에서&amp;nbsp;co_await연산자를&amp;nbsp;썼으면&amp;nbsp;initial_suspend()로&amp;nbsp;코루틴&amp;nbsp;함수&amp;nbsp;밖으로&amp;nbsp;빠져나간&amp;nbsp;후&amp;nbsp; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;코루틴이&amp;nbsp;다시&amp;nbsp;재게(resume)&amp;nbsp;되면&amp;nbsp;코루틴은&amp;nbsp;그제서야&amp;nbsp;본문(사용자가&amp;nbsp;정의한코루틴&amp;nbsp;함수의&amp;nbsp;내용들)을&amp;nbsp;실행&amp;nbsp;합니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 일들이 코루틴 함수에서 co_await, co_return, co_yield중 하나를 만났을 때 즉, 함수가 코루틴일 때 일어나는 일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중 1,2번은 코루틴이 실행되는 원리이나 자동으로 진행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 코루틴 함수에서 반환형이 코루틴 객체이며 이 객체는 promise타입 객체를 들고 있는 것이다. 또&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;5. 예시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5-1. co_return 사용하기.&lt;/p&gt;
&lt;pre id=&quot;code_1734188874202&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;coroutine&amp;gt;
#include &amp;lt;list&amp;gt;
#include &amp;lt;vector&amp;gt;

// 코루틴 객체
template&amp;lt;typename T&amp;gt;
class Future
{
public:
	Future(shared_ptr&amp;lt;T&amp;gt; value) : _value(value) { }
	T get() { return *_value; }

private:
	shared_ptr&amp;lt;T&amp;gt; _value;

public:
	struct promise_type
	{
		Future&amp;lt;T&amp;gt; get_return_object() { return Future&amp;lt;T&amp;gt;(_ptr); }
		void return_value(T value) { *_ptr = value; }
		std::suspend_never initial_suspend() { return {}; }
		std::suspend_never final_suspend() noexcept { return {}; }
		void unhandled_exception() { }

		// 데이터
		shared_ptr&amp;lt;T&amp;gt; _ptr = make_shared&amp;lt;T&amp;gt;();
	};
};

Future&amp;lt;int&amp;gt; CreateFuture()
{
	co_return 2021;
}


int main()
{
	auto future = CreateFuture();
	// TODO : 다른걸 하다...
	cout &amp;lt;&amp;lt; future.get() &amp;lt;&amp;lt; endl;
	
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main부터 살펴보면 우선 코루틴 함수인 CreateFuture를 실행하려고 보니 co_return이 있었다. 이를 만나면 어떻게 되는지 아래 의사코드로 살펴보자. 실제 실행되는 코드가 아닌 동작 방식인것.!&lt;/p&gt;
&lt;div style=&quot;border: 3px dashed #C6C6C6; padding: 0.6em;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⭐ co_yield, co_await, co_return을 함수 안에서 사용하면, 그 함수는 코루틴이 됨. ⭐ &lt;br /&gt;&lt;br /&gt;&amp;nbsp; Promise prom; (get_return_object)&lt;br /&gt;&amp;nbsp; co_await prom.initial_suspend(); &lt;br /&gt;&amp;nbsp; try &lt;br /&gt;&amp;nbsp; { &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;// co_return, co_yield, co_await를 포함하는 코루틴 함수의 코드 &lt;br /&gt;&amp;nbsp; } &lt;br /&gt;&amp;nbsp; catch (...) &lt;br /&gt;&amp;nbsp; { &lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; prom.unhandled_exception(); &lt;br /&gt;&amp;nbsp; } &lt;br /&gt;&amp;nbsp; co_await prom.final_suspend();&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선&amp;nbsp; co_return을 CreateFuture에서 만났으므로 코루틴 객체 Future 안의 promise객체 생성자가 만들어지고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;코루틴 반환 객체를 생성하여 반환하는 get_return_object() 함수를 호출한다. Future클래스의 promise_type에서 정의했던대로 값을 하나 받아서(여기선 co_return 2021이므로 2021)&amp;nbsp; promise객체의 get_return_object안에서 코루틴 객체 Future의 생성자에 넘겨주고 만들어 이 객체를 반환한다. 즉, promise객체랑 Future객체랑 관련있는것을 get_return_object에서 처리한다. 실질적으로 promise객체가 들고있는 포인터를 Future에도 넘겨줘 같이 가리킨다.그리고 이와같이위의 4에서 3,4가 실행되면 다시 promise_type에 정의되었던 initial_suspend가 호출된다.&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;background-color: #f8f8f8; color: #333333; text-align: start;&quot;&gt;initial_suspend는 initial_final과 마찬가지로 반환형이 awiatable인 &lt;span style=&quot;background-color: #f8f8f8; color: #333333; text-align: start;&quot;&gt;suspend_always&lt;/span&gt; 혹은 &lt;span style=&quot;background-color: #f8f8f8; color: #333333; text-align: start;&quot;&gt;suspend_never&lt;/span&gt; 다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;inital_suspend를 만나면 우선 코루틴 함수 밖으로 호출자로 나가는데 이 다음에 이 함수를 정지할지 아니면 무시하고 진행할지 각각 정하는 것이다. 여기선 suspend_never로 그냥 무시하고 정지하지 않을것이라 했다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;awaitalbe의 플로우&lt;/p&gt;
&lt;pre id=&quot;code_1734191184833&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;		if awaitable.await_ready() returns false;
			suspend coroutine
			awaitable.await_suspend(handle) returns:
				void:
					awaitable.await_suspend(handle);
					coroutine keeps suspended
					return to caller
				bool:
					bool result = awaitable.await_suspend(handle);
					if (result)
						coroutine keeps suspended
						return to caller
					else
						return awaitable.await_resume()
				another coroutine handle:
					anotherCoroutineHandle = awaitable.await_suspend(handle);
					anotherCoroutineHandle.resume();
					return to caller;
		return awaitable.await_resume();&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;await_ready가 false이면 아직 준비가 안됐으니 코루틴을 suspend한다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 그밖의 promise_type을 정의한 함수들을 보면 이 코루틴함수 CreateFuture에서 co_return을 사용했으므로&amp;nbsp; &lt;b&gt;2&lt;/b&gt; 에서 봤듯이 멤버를 반환하는 return_value를 만들어줬다. (그러나 아마도 따로 CreateFuture객체를 핸들로써 사용해 재개하지 않으므로 굳이 사용하지는 않을것 같다.?)&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 inital_suspend에서 다시 CreateFuture로, 다시 main함수로 돌아오면 코루틴객체 CreateFuture타입의 future의 멤버 get()을 하면 2021이 출력된다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;5-2. co_yield사용해서 코루틴 재개해보기.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Future 코루틴 객체에서는 그냥 값을 리턴하는 역할밖엔 안했지만&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;return_yeild를 살펴볼 Generator 코루틴 객체는 정말 값을 저장했다가 재개하는걸 할 것이다.그리고 그러려면 코루틴 핸들을 밖에서 들고 있다가 이를 이용해 제어해야 한다.(함수 밖에서 이를 이용해 끝낼지, 재개할지) 코루틴 객체를 만들 때 핸들을 인자로 받아서 코루틴 객체에서도 이를 들고 있는다.&lt;/p&gt;
&lt;pre id=&quot;code_1734191354866&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#pragma once
#include &amp;lt;coroutine&amp;gt;
#include &amp;lt;iostream&amp;gt;
using namespace std;

template&amp;lt;typename T&amp;gt;
class Generator
{
public:
	struct promise_type;
	using handle_type = coroutine_handle&amp;lt;promise_type&amp;gt;;

	Generator(handle_type handle) : _handle(handle)
	{
	}

	~Generator()
	{
		if (_handle)
			_handle.destroy();
	}

	T get() { return _handle.promise()._value; }

	bool next()
	{
		_handle.resume(); // 중요!
		return !_handle.done();
	}

private:
	handle_type _handle;

public:
	struct promise_type
	{
		Generator&amp;lt;T&amp;gt; get_return_object() { return Generator(handle_type::from_promise(*this)); }
		std::suspend_always initial_suspend() { return {}; }
		std::suspend_always final_suspend() noexcept { return {}; }
		std::suspend_always yield_value(const T value) { _value = value; return {}; }
		std::suspend_always return_void() { return {}; }
		void unhandled_exception() { }

		T _value;
	};
};


int main()
{
	auto numbers = GenNumbers(0, 1);

	for (int i = 0; i &amp;lt; 20; i++)
	{
		numbers.next();

		cout &amp;lt;&amp;lt; &quot; &quot; &amp;lt;&amp;lt; numbers.get();
	}

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 이 코루틴핸들을 이용해 코루틴객체가 될 class(혹은 struct)안에서 코루틴프레임(핸들?) destory혹은 resume(재개)해 줄 수 있다. 그리고 이 코루틴 핸들로 done()을 하면 이 함수가 끝났는지 여부도 확인할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 마찬가지로 이 핸들을 이용해 값을 꺼내오기 위해 get()을 만들어줬다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번엔 suspend_always를 이용해보았다. 위의 Future에서는 절대 멈추지 말고 넘어가라는 suspend_never를 사용했는데 이번엔 무조건 멈추고 빠져나가라고 inital_suspend, final_suspend에서 awaitable에 반환하고 있다.&lt;/p&gt;</description>
      <category>[C++]</category>
      <author>럭키 </author>
      <guid isPermaLink="true">https://white-mouse.tistory.com/154</guid>
      <comments>https://white-mouse.tistory.com/154#entry154comment</comments>
      <pubDate>Wed, 11 Dec 2024 09:38:04 +0900</pubDate>
    </item>
    <item>
      <title>[C++ 20] Range</title>
      <link>https://white-mouse.tistory.com/153</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;C++20에 등장한 Range는 C#의&amp;nbsp; LINQ와 유사하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 아래와 같이 int형 벡터를 만들어 이 중 짝수원소들을 추출해 2를 곱한 새로운 벡터를 만든다고 가정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1733745502137&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int main()
{
	// C# LINQ 문법이랑 비슷하다?

	vector&amp;lt;int&amp;gt; v1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

	vector&amp;lt;int&amp;gt; v2;
	// 짝수를 추출해서
	for (int n : v1)
		if (n % 2 == 0)
			v2.push_back(n);
	// 2를 곱해준다
	for (int&amp;amp; n : v2)
		n = n * 2;

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 벡터, 맵을 순회하면서 직접 연산할 수도 있지만 이는 이제 대체할 수 있는 오래된 스타일이 되어버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::for_each(); &lt;br /&gt;std::find_if(); &lt;br /&gt;std::any_of();&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 쓰기가 권장되기도 하지만 오늘 배울 range와 views를 쓰면 아래와 같이 쉽게 바꿀 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1733745714016&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;map&amp;gt;
#include &amp;lt;ranges&amp;gt;
using namespace std;

int main()
{
	vector&amp;lt;int&amp;gt; v1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

    auto results = v1 | std::views::filter([](int n) { return n % 2 == 0; })
					| std::views::transform([](int n) { return n * 2; });

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 살펴보면 v1으로부터 filter를 사용해 추출해내는 이 하나가 일종의 파이프라인이다. 또 하나의 | 만 쓰는게 아니라 왼쪽에서부터 추가할 수 있다. 즉, filter로 짝수인 원소를 추출해 이를 대상으로 곱하기 2연산을 해준다. 그리고 연산할 함수는 람다로 넣어줘도 가능함을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 이를 쓰기 위해서는 ranges 라이브러리를 include해줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::views를 이용해 filter와 transform을 사용했는데 views는 ranges랑 무엇인지 알아보자.&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;b&gt;Range : Range란 순회할 수 있는 아이템 그룹 (ex. STL Container) &lt;/b&gt;&lt;br /&gt;&lt;b&gt;View : Range에 대해서 적용할 수 있는 연산&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면 위와 같다. views에는 filter와 transform외에도 다양한 함수를 가지고 있는데 아래와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;padding: 15px 20px; background-color: #f8f8f8; border-radius: 5px 5px; border: 1px solid #f1f1f1; line-height: 1.8;&quot;&gt;
&lt;div style=&quot;background-image: repeating-linear-gradient(#cfe2c8, #cfe2c8 1px, #f8f8f8 1px, #f8f8f8  1.75em);&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;views, ranges의 함수들&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::views::all &lt;br /&gt;std::ranges::filter_view / std::views::filter (조건 만족하는거 추출) &lt;br /&gt;std::ranges::transform_view / std::views::transform (각 요소를 변환) &lt;br /&gt;std::ranges::take_view / std::views::take (n개 요소를 추출) &lt;br /&gt;std::ranges::take_while_view / std::views::take_while (조건 만족할 때까지 요소 추출) &lt;br /&gt;std::ranges::drop_view / std::views::drop (n개 요소를 스킵) &lt;br /&gt;std::ranges::drop_while_view / std::views::drop_while (조건 만족할 때까지 요소 스킵) &lt;br /&gt;std::ranges::join_view / std::views::join (view 를 병합) &lt;br /&gt;std::ranges::split_view / std::views::split (split) &lt;br /&gt;std::ranges::reverse_view / std::views::reverse (역순서로 순회) &lt;br /&gt;std::ranges::elements_view / std::views::elements (튜플의 n번째 요소를 대상으로 view 생성)&amp;nbsp;&amp;nbsp;&lt;br /&gt;// 맵, 해쉬맵, unorderd_map 등의 key, value가 있는곳에서 사용가능한&lt;br /&gt;std::ranges::keys_view / std::views::keys (pair-like value의 첫번째 요소를 대상으로 view 생성) &lt;br /&gt;std::ranges::values_view / std::views::values (pair-like value의 두번째 요소를 대상으로 view 생성)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 좀 더 응용해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;① takes&lt;/p&gt;
&lt;pre id=&quot;code_1733746277365&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;	auto results2 = v1 | std::views::filter([](int n) { return n % 2 == 0; })
					| std::views::transform([](int n) { return n * 2; })
					| std::views::take(3);

	for (auto n : results2)
		cout &amp;lt;&amp;lt; n &amp;lt;&amp;lt; &quot; &quot;;   // 3개만 추출해 4,8,12가 나온다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;② sort&lt;/p&gt;
&lt;pre id=&quot;code_1733746806353&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;map&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;ranges&amp;gt;

int main()
{
    vector&amp;lt;int&amp;gt; v1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    
    //std::sort(v1.begin(), v1.end());
	std::ranges::sort(v1);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;algorithm에 있는 sort를 범위를 지정해줘야하는데 ranges에 있는 sort는 범위를 지정해주지 않아도 되어 조금 더 깔끔하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 rages::sort도 두번째 인자는 Predicate(조건)를 받아 less가 기본이며 greater를 넣을 수도 있다. 세번째 인자로는 Projection을 받아서 어떤 원소를 대상으로 정렬을 할지 지정할 수 있다. 이를 좀더 응용해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1733747030018&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;vector&amp;gt;
#include &amp;lt;map&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;ranges&amp;gt;

int main()
{
    struct Knight
	{
		std::string		name;
		int				id;
	};

	vector&amp;lt;Knight&amp;gt; knights =
	{
		{ &quot;Rookiss&quot;, 1},
		{ &quot;Faker&quot;, 2},
		{ &quot;Dopa&quot;, 3},
		{ &quot;Deft&quot;, 4},
	};

	std::ranges::sort(knights, {}, &amp;amp;Knight::name); // ascending by name, Deft, Dofa, Faker,Rookis
	std::ranges::sort(knights, std::ranges::greater(), &amp;amp;Knight::name); // descending by name	
	std::ranges::sort(knights, {}, &amp;amp;Knight::id); // ascending by id	
	std::ranges::sort(knights, std::ranges::greater(), &amp;amp;Knight::id); // ascending by id
    
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위처럼 sort에 두, 세번째 인자를 채워줌으로서 Knight 객체 배열 knights를 name원소 기준, id기준 내림차순/오름차순 정렬할 수 있다.&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;③ map을 대상으로 사용하기&lt;/p&gt;
&lt;pre id=&quot;code_1733749467287&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
#include &amp;lt;map&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;ranges&amp;gt;
using namespace std;

int main()
    map&amp;lt;string, int&amp;gt; m =
	{
		{ &quot;Rookiss&quot;, 1},
		{ &quot;Faker&quot;, 2},
		{ &quot;Dopa&quot;, 3},
		{ &quot;Deft&quot;, 4},
	};

	for (const auto&amp;amp; name : std::views::keys(m) | std::views::reverse)
		cout &amp;lt;&amp;lt; name &amp;lt;&amp;lt; endl;

   return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;m의 키들 역순으로 순회하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Deft, Dopa, Faker, Rookis&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; 0~100 사이의 숫자중 소수인 5개의 숫자를 추출하라?&lt;/p&gt;
&lt;pre id=&quot;code_1733749685309&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int main()
 
    // 소수판별함수
    auto isPrime = [](int num)
	{
		if (num &amp;lt;= 1)
			return false;

		for (int n = 2; n*n &amp;lt;= num; n++)
			if (num % n == 0)
				return false;

		return true;
	};

    std::vector&amp;lt;int&amp;gt; v3;
	// std::views::iota(a, b) : a부터 시작해서 1씩 증가 b개를 만들어줌
	for (int n : std::views::iota(0, 100) | std::views::filter(isPrime) | std::views::take(5))
	{
		v3.push_back(n);
	}

    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 std::views::iota(a, b) : a부터 시작해서 1씩 증가 b개를 만들어 주는 함수를 이용해 0부터 100까지 수를 만든 다음에,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소수를 판별하는 isPrime을 람다함수로 만들어서(에라스토테네스이군) filter로 넣어주고 이 중 5개를 선별하는 take로 마무리한다. 이 값을 순회하며 n으로 받아서 v3에 넣는다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⑤ 커스텀 뷰 (std::ranges::view_interface)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 std에 있는 view가 아닌 새로운 방식으로 view를 만들어서 사용하고자 할 때 어떻게 쓰면 되는지 살펴보자.&lt;/p&gt;
&lt;pre id=&quot;code_1733749484773&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;
#include &amp;lt;list&amp;gt;
#include &amp;lt;vector&amp;gt;
#include &amp;lt;map&amp;gt;
#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;ranges&amp;gt;
#include &amp;lt;concepts&amp;gt;

template&amp;lt;std::ranges::input_range Range&amp;gt;
requires std::ranges::view&amp;lt;Range&amp;gt;
class ContainerView : public std::ranges::view_interface&amp;lt;ContainerView&amp;lt;Range&amp;gt;&amp;gt;
{
public:
	ContainerView() = default;

	constexpr ContainerView(Range r) : _range(std::move(r)), _begin(std::begin(r)), _end(std::end(r))
	{

	}

	constexpr auto begin() const { return _begin; }
	constexpr auto end() const { return _end; }

private:
	Range _range;
	std::ranges::iterator_t&amp;lt;Range&amp;gt; _begin;
	std::ranges::iterator_t&amp;lt;Range&amp;gt; _end;
};

template&amp;lt;typename Range&amp;gt;
ContainerView(Range&amp;amp;&amp;amp; range) -&amp;gt; ContainerView&amp;lt;std::ranges::views::all_t&amp;lt;Range&amp;gt;&amp;gt;;

int main()
{

	// 커스텀 뷰 (std::ranges::view_interface)
	std::vector&amp;lt;int&amp;gt; myVec{1,2,3,4,5};
	auto myView = ContainerView(myVec);

	for (auto n : myView)
	{
		cout &amp;lt;&amp;lt; n &amp;lt;&amp;lt; endl;
	}
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std::ranges::view_interface를 상속받아서 ContainerView클래스를 만들었다. 이때 Range타입을 모두 받기 위해 템플릿을 사용하는데 Ranges를 받도록 concept으로 조건을 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 iterator를 사용해 순회할 수 있도록 begin, end를 지원해줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 public으로 ContainerView의 생성자를 만들어주는데 선처리 영역에서 _range, _begin, _end를 채워주도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 public으로 begin()과 end()에서 이를 가져올 수 있도록 한다.&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;이를 응용해서 보면 위와 같이 myVec이란 이름으로 일반 벡터를 만들었다가 그대로 방금 만든 ContainerView커스텀 뷰에 적용해서 쓸 수 있다. 즉, 벡터를 view로 만드는 헬퍼클래스같은 역할을 ContainerView가 하고 있다.&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://learn.microsoft.com/ko-kr/cpp/standard-library/ranges?view=msvc-170&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://learn.microsoft.com/ko-kr/cpp/standard-library/ranges?view=msvc-170&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1733751105882&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;&amp;lt;ranges&amp;gt;&quot; data-og-description=&quot;STL(표준 템플릿 라이브러리) 범위에 대한 개요를 확인합니다.&quot; data-og-host=&quot;learn.microsoft.com&quot; data-og-source-url=&quot;https://learn.microsoft.com/ko-kr/cpp/standard-library/ranges?view=msvc-170&quot; data-og-url=&quot;https://learn.microsoft.com/ko-kr/cpp/standard-library/ranges?view=msvc-170&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/VeG2F/hyXKmXRJcb/AFRghDCeX0BNt3ZDJnjO3K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://learn.microsoft.com/ko-kr/cpp/standard-library/ranges?view=msvc-170&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://learn.microsoft.com/ko-kr/cpp/standard-library/ranges?view=msvc-170&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/VeG2F/hyXKmXRJcb/AFRghDCeX0BNt3ZDJnjO3K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&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;&amp;lt;ranges&amp;gt;&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;STL(표준 템플릿 라이브러리) 범위에 대한 개요를 확인합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;learn.microsoft.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;이렇게 살펴봤지만 ranges와 view를 이용해 for를 간단하게 쓰는 방법으로만 보는게 좋을 듯하다.&lt;/p&gt;</description>
      <category>[C++]</category>
      <author>럭키 </author>
      <guid isPermaLink="true">https://white-mouse.tistory.com/153</guid>
      <comments>https://white-mouse.tistory.com/153#entry153comment</comments>
      <pubDate>Mon, 9 Dec 2024 21:54:21 +0900</pubDate>
    </item>
    <item>
      <title>[C++ 20] Module</title>
      <link>https://white-mouse.tistory.com/152</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;C++ 20의 module의 도입으로 빌드 시간이 줄어들었다.&lt;/b&gt; &lt;br /&gt;C++의&amp;nbsp;빌드&amp;nbsp;과정 &lt;br /&gt;1.&amp;nbsp;전처리&amp;nbsp;:&amp;nbsp;#include,&amp;nbsp;#define등 &lt;br /&gt;2.&amp;nbsp;컴파일&amp;nbsp;:&amp;nbsp;obj파일&amp;nbsp;생성 &lt;br /&gt;3.&amp;nbsp;링크&amp;nbsp;:&amp;nbsp;obj파일과&amp;nbsp;심볼,&amp;nbsp;등을&amp;nbsp;묶어서&amp;nbsp;실행파일을&amp;nbsp;만듦 &lt;br /&gt;&lt;br /&gt;-&amp;gt;&amp;nbsp;여기서&amp;nbsp;이런&amp;nbsp;기존&amp;nbsp;빌드&amp;nbsp;과정의&amp;nbsp;문제점 &lt;br /&gt;1.&amp;nbsp;너무&amp;nbsp;느린&amp;nbsp;빌드&amp;nbsp;속도(반복된&amp;nbsp;substitution) &lt;br /&gt;같은&amp;nbsp;라이&amp;nbsp;파일도&amp;nbsp;클래스마다&amp;nbsp;들고&amp;nbsp;있으면&amp;nbsp;각각&amp;nbsp;오브젝트에&amp;nbsp;딸려들어가서&amp;nbsp;무거워진다. &lt;br /&gt;(중복으로 인클루드 되는 문제) &lt;br /&gt;2.&amp;nbsp;매크로&amp;nbsp;(#define)의&amp;nbsp;사용을&amp;nbsp;지양하자..? &lt;br /&gt;예)&amp;nbsp;#define&amp;nbsp;NUM&amp;nbsp;1&amp;nbsp;이렇게&amp;nbsp;한&amp;nbsp;파일에서&amp;nbsp;정의하고&amp;nbsp;다른곳에서&amp;nbsp;#define&amp;nbsp;NUM&amp;nbsp;2&amp;nbsp;하면&amp;nbsp;다른데서 &lt;br /&gt;두&amp;nbsp;파일&amp;nbsp;다&amp;nbsp;인클루드하고&amp;nbsp;있을&amp;nbsp;때&amp;nbsp;NUM을&amp;nbsp;뭐로&amp;nbsp;쓸지&amp;nbsp;모름 &lt;br /&gt;3.&amp;nbsp;심볼의&amp;nbsp;중복&amp;nbsp;정의 &lt;br /&gt;보통&amp;nbsp;헤더에&amp;nbsp;함수를&amp;nbsp;선언하고&amp;nbsp;cpp에서&amp;nbsp;정의하지만 &lt;br /&gt;Test.h에&amp;nbsp;함수의&amp;nbsp;선언과&amp;nbsp;정의를&amp;nbsp;같이하고 &lt;br /&gt;메인함수가&amp;nbsp;있는&amp;nbsp;곳에&amp;nbsp;Test.h를&amp;nbsp;추가한다음에&amp;nbsp;빌드하려하면&amp;nbsp;'여러번&amp;nbsp;정의된&amp;nbsp;기호가&amp;nbsp;있습니다.'&amp;nbsp;오류가&amp;nbsp;난다. &lt;br /&gt;그런데&amp;nbsp;메인함수고&amp;nbsp;있는&amp;nbsp;파일에서&amp;nbsp;Test.h를&amp;nbsp;빼면&amp;nbsp;이&amp;nbsp;문제가&amp;nbsp;발생하지&amp;nbsp;않는다. &lt;br /&gt;이&amp;nbsp;문제는&amp;nbsp;이&amp;nbsp;문제는&amp;nbsp;Test.h에&amp;nbsp;선언된&amp;nbsp;함수가&amp;nbsp;Test.obj파일에&amp;nbsp;포함되어&amp;nbsp;있는데&amp;nbsp;그다음에&amp;nbsp;별도로&amp;nbsp;메인함수가&amp;nbsp;있는&amp;nbsp;cpp파일을&amp;nbsp;별도로&amp;nbsp;obj파일에&amp;nbsp; &lt;br /&gt;만들려니&amp;nbsp;두&amp;nbsp;개의&amp;nbsp;object파일에&amp;nbsp;동일한&amp;nbsp;이름으로&amp;nbsp;함수가&amp;nbsp;두&amp;nbsp;번&amp;nbsp;생기게&amp;nbsp;되어서&amp;nbsp;링크에러가&amp;nbsp;발생한다. &lt;br /&gt;&amp;nbsp; &lt;br /&gt;=&amp;gt;&amp;nbsp;이런&amp;nbsp;복잡한&amp;nbsp;문제를&amp;nbsp;모듈을&amp;nbsp;이용하면&amp;nbsp;한번에&amp;nbsp;다&amp;nbsp;처리&amp;nbsp;가능하다. &lt;br /&gt;&lt;br /&gt;Module의&amp;nbsp;장점. &lt;br /&gt;1.&amp;nbsp;딱&amp;nbsp;한번만&amp;nbsp;import된다. &lt;br /&gt;2.&amp;nbsp;import&amp;nbsp;순서에&amp;nbsp;영향을&amp;nbsp;받지&amp;nbsp;않는다.&amp;nbsp; &lt;br /&gt;#define&amp;nbsp;NUM&amp;nbsp;1&amp;nbsp;을&amp;nbsp;한&amp;nbsp;파일과&amp;nbsp;#define&amp;nbsp;NUM&amp;nbsp;2&amp;nbsp;를&amp;nbsp;한&amp;nbsp;파일을&amp;nbsp;include&amp;nbsp;하는&amp;nbsp;순서에&amp;nbsp;따라&amp;nbsp;NUM이&amp;nbsp;다른&amp;nbsp;문제가&amp;nbsp;있엇다. &lt;br /&gt;3.&amp;nbsp;심볼&amp;nbsp;중복&amp;nbsp;정의문제&amp;nbsp;해결 &lt;br /&gt;4.&amp;nbsp;모듈의&amp;nbsp;이름짓기가&amp;nbsp;가능해짐 &lt;br /&gt;5.&amp;nbsp;(h/cpp)인터페이스와&amp;nbsp;구현부&amp;nbsp;분리해&amp;nbsp;관리할&amp;nbsp;필요가&amp;nbsp;없어짐. &lt;br /&gt;&lt;br /&gt;솔루션탐색기에서&amp;nbsp;프로젝트&amp;nbsp;우클릭으로&amp;nbsp;새항목을&amp;nbsp;추가하는데&amp;nbsp; &lt;br /&gt;C++&amp;nbsp;모듈&amp;nbsp;인터페이스&amp;nbsp;단위(.ixx)를&amp;nbsp;선택한다. &lt;br /&gt;(참고로&amp;nbsp;윈도우즈에서는&amp;nbsp;ixx지만&amp;nbsp;리눅스나&amp;nbsp;다른&amp;nbsp;환경에선&amp;nbsp;확장자가&amp;nbsp;다를&amp;nbsp;수&amp;nbsp;있다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;984&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/diSSah/btsKHcvjeeR/9F7dIeHgDJi0N6kx17Azik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/diSSah/btsKHcvjeeR/9F7dIeHgDJi0N6kx17Azik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/diSSah/btsKHcvjeeR/9F7dIeHgDJi0N6kx17Azik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdiSSah%2FbtsKHcvjeeR%2F9F7dIeHgDJi0N6kx17Azik%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;1420&quot; height=&quot;984&quot; data-origin-width=&quot;1420&quot; data-origin-height=&quot;984&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp; &lt;br /&gt;&lt;br /&gt;&lt;b&gt;모듈을&amp;nbsp;사용하는&amp;nbsp;여러가지&amp;nbsp;방법.&lt;/b&gt; &lt;br /&gt;1)&amp;nbsp;함수앞에다가&amp;nbsp;export를&amp;nbsp;쓰고&amp;nbsp;.ixx&amp;nbsp;파일&amp;nbsp;상단에&amp;nbsp;이&amp;nbsp;파일을&amp;nbsp;뭐라고&amp;nbsp;할지 &lt;br /&gt;export&amp;nbsp;module&amp;nbsp;[파일&amp;nbsp;이름]&amp;nbsp; &lt;br /&gt;이런식으로&amp;nbsp;쓴&amp;nbsp;다음에&amp;nbsp;갖다&amp;nbsp;쓸&amp;nbsp;곳에서&amp;nbsp; &lt;br /&gt;import&amp;nbsp;[파일&amp;nbsp;이름] &lt;br /&gt;이렇게&amp;nbsp;쓰고&amp;nbsp;파일에서&amp;nbsp;export한&amp;nbsp;함수&amp;nbsp;가져다가&amp;nbsp;쓰기 &lt;br /&gt;예)&lt;/p&gt;
&lt;pre id=&quot;code_1731499134931&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Math.ixx
export module math;
export int Add(int a, int b)
{
 return a+b;
}

// [프로젝트이름].cpp
#include &amp;lt;iostream&amp;gt;
import math;
int main()
{
cout&amp;lt;&amp;lt;Add(1,2);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;2).ixx파일&amp;nbsp;내에서&amp;nbsp;export해주고싶은&amp;nbsp;것들만&amp;nbsp;묶어서&amp;nbsp;하기.&lt;/p&gt;
&lt;pre id=&quot;code_1731499144786&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Math_Time.ixx
export module math;

void Internal()
{
}

export
{
 void TestExport()
 {
 }
}

// [프로젝트이름].cpp
#include &amp;lt;iostream&amp;gt;
import math;
int main()
{
  TestExport();
  // Internal(); // 에러
}&lt;/code&gt;&lt;/pre&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;3) namespace를 지정해서 쓰기 &amp;lt;- 권장&lt;/p&gt;
&lt;pre id=&quot;code_1731499310437&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Math.ixx
export module math;

export namespace TestNamespace
{
 void TestExport2()
 {
 }
}

// [프로젝트이름].cpp
import math;

int main()
{
 TestNamespace::TestExport2();
 return 0;
}&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모듈 사용시 참고사항&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;export를 붙이지 않은 함수, 네임스페이스 등은 모듈과 같은 페이지 안에 있어도 내보내지지 않아 모듈을 import해도 사용할수 없다.&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.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈 안에서 외부 라이브러리를 사용할 경우 ixx 파일 내에 다음과 같이 작성하면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1731499653093&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Math.ixx

module; // global module fragment
// 각종 외부 헤더 추가
#include &amp;lt;vector&amp;gt;
#include &amp;lt;iostream&amp;gt;
// ...


// module의 시작
export module math;

// module에도 module을 추가할 수 있다.
import math2;

export int Add(int a, int b)
{
// ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;외부 라이브러리를 사용하거나 module을 추가해서 module내에서 사용할때 위같은 순서를 지키는게 일반적이다.&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;3.&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;Math.ixx외에도 Math의 기능을 수행하는 다른 ixx파일도 만든다고 가정하면 이름을 Math_partial1.ixx파일을 만들었다 가정했을 때&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-1.&lt;/p&gt;
&lt;pre id=&quot;code_1731500222323&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Math_Partial.ixx
export module math.partial;

export void MathPartialFunc()
{

}

// [프로젝트이름].cpp
import math.partial;

int main()
{
  MathPartialFunc();
  return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이렇게 하면 결국 모든 module을 import해야하는건 마찬가지이므로&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;3-2.&lt;/p&gt;</description>
      <category>[C++]</category>
      <author>럭키 </author>
      <guid isPermaLink="true">https://white-mouse.tistory.com/152</guid>
      <comments>https://white-mouse.tistory.com/152#entry152comment</comments>
      <pubDate>Wed, 13 Nov 2024 21:11:53 +0900</pubDate>
    </item>
    <item>
      <title>Modern C++ #9 전달 참조(forwarding reference)</title>
      <link>https://white-mouse.tistory.com/151</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;amp;&amp;amp; 얘는 이전 시간에 오른값참조를 받기 위해서만 쓰이는 것처럼 보였다.&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;우선 지난시간의 오른값 참조와 이동연산자인 std::move의 복습이다.&lt;/p&gt;
&lt;pre id=&quot;code_1732100217845&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;iostream&amp;gt;
using namespace std;

// 오른값 참조로 인자를 받으면 전달한 객체를 없애도 된다는 힌트를 준다.
// 값 복사가 일어나야하는 상황에서도 복사대신 이동을 시켜 깊은복사 대신 이동으로
// 최적화의 여지가 생긴다
class Knight
{
public:
  Knight() {cout&amp;lt;&amp;lt;&quot;기본 생성자&quot;&amp;lt;&amp;lt;endl;}
  Knight(const Knight&amp;amp; k) {cout&amp;lt;&amp;lt;&quot;복사 생성자&quot;&amp;lt;&amp;lt;endl;}
  Knight(Knight&amp;amp;&amp;amp; k) {cout&amp;lt;&amp;lt;&quot;이동 생성자&quot;&amp;lt;&amp;lt;endl;}
}


void Test_RValueRef(Knight&amp;amp;&amp;amp; k)
{

}

int main()
{
    Knight k1;
    Test_RValueRef(std::move(k1)); 
    // 오른값만 받으므로 move로 왼값인 k1을 오른값으로 바꿔치기 해야한다.
    // move는 오른값 참조로 캐스팅 역할이 전부다.
    
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 전달참조는 템플릿이나 auto같이 형식 연역이라고 해서 타입&amp;nbsp; 추론(deduce, type guessing)이 일어 날 때&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1732104342610&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;algorithm&amp;gt;

class Knight
{
public:
  int _hp = 10;
public:
  Knight() {cout&amp;lt;&amp;lt;&quot;기본 생성자&quot;&amp;lt;&amp;lt;endl;}
  Knight(const Knight&amp;amp; k) {cout&amp;lt;&amp;lt;&quot;복사 생성자&quot;&amp;lt;&amp;lt;endl;}
  Knight(Knight&amp;amp;&amp;amp; k) {cout&amp;lt;&amp;lt;&quot;이동 생성자&quot;&amp;lt;&amp;lt;endl;}
}

void Test_RValueRef(Knight&amp;amp;&amp;amp; k)
{

}

template&amp;lt;typename T&amp;gt;
void Test_ForwardingRef(T&amp;amp;&amp;amp; param)
{

}

int main()
{
    Knight k1;
    Test_RValueRef(std::move(k1)); 
    // 오른값만 받으므로 move로 왼값인 k1을 오른값으로 바꿔치기 해야한다.
    // move는 오른값 참조로 캐스팅 역할이 전부다.
    
    
    // 타입 추론이 일어나서 Knight의 오른값을 전달하나 싶다.
    Test_ForwardingRef(std::move(k1)); // void Test_ForwardingRef&amp;lt;Knight&amp;gt;(Knight &amp;amp;&amp;amp;param)
    // 그냥 왼값만 받아도 통과 된다.?
    // 심지어 왼값참조.?
    Test_ForwardingRef(k1); // void Test_ForwardingRef&amp;lt;Knight &amp;amp;&amp;gt;(Knight &amp;amp;param) 
    
    //auto를 사용할때 *나 const를 쓸 수 있었던 것처럼 &amp;amp;&amp;amp;도 붙힐 수 있다.
    auto&amp;amp;&amp;amp; k2 = k1;
    // k1을 오른값으로 바꿔서 넘겨주었다.
    auto&amp;amp;&amp;amp; k3 = std::move(k1);
    
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k2는 타입이 오른값 참조가 아닐까 싶은데 왼값이 k1을 그대로 받는거 보면 오른값 참조가 아니라는걸 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마우스오버 해보면 auto&amp;amp;&amp;amp; k2는 Knight &amp;amp;k2로 왼값참조로 바뀌어 있는걸로 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 바로 아래에서 k1을 std::move로 오른값으로 바꿔서 k3에 마우스오버해보면 Knight &amp;amp;&amp;amp;k3로 되어있다.&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;종합해보면 &amp;amp;&amp;amp;는 auto나 템플릿이랑 같이 쓰일 때 오른값참조타입으로도 쓰이고 왼값참조로도 쓰이는 전달참조가 생긴다. &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;이런 공통적 현상을 '형식 연역(type deduction)'이라고 하며 템플릿에서도 일어나는 것으로 아직은 정확히 정해지진 않았지만 넣어주는 값에 따라서 바뀌는 조커같은 역할이라 볼 수 있다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;전달참조의 특징을 보면&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;- 왼값을 넣어주면 왼값참조로 작동이 되고 오른값을 넣어주면 오른값 참조로 동작한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;- TestForwardingRef의 매개변수 T&amp;amp;&amp;amp; param에 const만 붙여도 왼값을 받아주지 않게된다. const T&amp;amp;&amp;amp; param ( auto도 마찬가지)&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;letter-spacing: 0px;&quot;&gt;✨&lt;b&gt;&amp;amp;&amp;amp;가 등장한다고 해서 무조건 오른값참조를 받는게 아니며 auto나 템플릿이 같이 쓰일때 때에따라 왼값도 받는 전달참조가 된다&lt;/b&gt;.&amp;nbsp;&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;letter-spacing: 0px;&quot;&gt;그런데 왜 이런 전달참조를 만들게 되었을까? &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;template&amp;lt;typename T&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;void Test_ForwardingRef(T&amp;amp;&amp;amp; param) // 전달참조&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;void Test_ForwardingRef(const T&amp;amp;&amp;amp; param) // 오른값 참조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;void Test_ForwardingRef(const T&amp;amp; param) // 왼값 참조&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;오른값을 받는 타입외에도 왼값을 참조로 받는 버전도 또 오버로딩으로 만들어서 쓰면 될일을.!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그런데 템플릿을 쓸 때는 꼭 하나씩만 쓰라는 법은 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;template&amp;lt;typename T, typename T2&amp;gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;void Test_ForwardingRef(T&amp;amp;&amp;amp; param, T2&amp;amp;&amp;amp; param2)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 T를 왼값으로 넘길때/오른값으로 넘길때 * T2를 왼값으로 넘길때/오른값으로 넘길때 4개의 함수를 오버로딩해야한다. 그리고 이런 경우는 타입을 여러개 받고싶을 때 그 수가 더 커지게 된다..&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 템플릿이나 오토를 쓸 때는 &amp;amp;&amp;amp;가 왼값도 참조로 받을 수 있고 오른값도 참조로 받을 수 있게 만든것이다.&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;span style=&quot;letter-spacing: 0px;&quot;&gt;그리고 전달참조로 받는다고 해도 왼값으로 받았냐, 오른값으로 받았냐에 따라 그 사용법이 달라져야한다.&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732193856678&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ...
void Test_Copy(Knight k)
{

}

template&amp;lt;typename T&amp;gt;
void Test_ForwardingRef(T&amp;amp;&amp;amp; param) // 전달참조
{
  // ...
  Test_Copy(param); // param이 왼값이었을 때 통과
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Test_ForwardingRef의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; param이 왼값(Test_ForwardingRef&amp;lt;Knight&amp;amp;&amp;gt;(Knight&amp;amp; param))이라면 Test_Copy에 바로 전달 할 시 매개변수가 Knight k 이므로 전달받은 인자는 복사되어 k 가 Knight의 복사생성자 Knight(const Knight&amp;amp;)가 호출되며 만들어진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 Test_ForwardingRef의 param이 오른값이었다면(Test_ForwardingRef&amp;lt;Knight&amp;gt;(Knight &amp;amp;&amp;amp;param)) 바로 Test_Copy에 전달 못하고 std::move(param)을 이용해 이동론을 이용하는 방법으로 전달해야한다. 그럼 이때 Test_Copy의 k는 이동생성자 Knight(Knight&amp;amp;&amp;amp;) 가 호출된다. 그리고 일반적으로 복사를 하는것보다 이동을 하는게 최적화의 여지가 많으며 빠르게 동작할 확률이 높다. - 복사생성자는 깊은 복사 방식으로 만드는게 일반적인 반면 이동생성자는 어차피 전달받은 대상이 더이상 쓰일 일이 없다는게 명확하니 이동방식으로 만들 수 있으니깐.&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;그래서 위 Test_ForwardingRef에서 보면 param이 왼값일 수도 있고 오른값일 수도 있어서 잘 못 사용하면 원본이 유지되어야하는 const Knight&amp;amp; 타입에 들어가야 할 값이 std::move로 소유권을 이전시키면 원본이 훼손될 수 있게된다.&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;pre id=&quot;code_1732194864142&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ...
Knight k1;

Knight&amp;amp; k4 = k1; // k4가 k1을 참조로 받았다.(왼값참조)
Knight&amp;amp;&amp;amp; k5 = std::move(k1); // 오른값 참조로 받아주는 k5&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k4는 왼값참고이고 k5는 오른값참조이다 위에서 오른값참조를 인자로 받았던 Test_RValueRef에 k4를 넣어주면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;// Test_RValueRef(k4); // error&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 오류가 난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 k5도 그냥 넣어주면 오류가 난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;// Test_RValueRef(k5); // error&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른값은 처음에 말했듯이 임시값인데 이를 소유권을 이동시키지 않고 감히 넘기려고 하다니..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k5는 오른값이라고 해서 오른값 참조에 넘길 수 없는것이다!!&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;그리고 오른값 참조는 오른값만 참조할 수 있는 참조타입이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼값을 오른값으로 쓰기 위해서 std::move를 사용하거나 static_cast&amp;lt;Knight&amp;amp;&amp;amp;&amp;gt;()를 사용해 오른값으로 캐스팅해서 써야했다.&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; 위에서 k5가 오른값참조타입은 맞지만 k5가 오른값이냐는 또 별도의 문제이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1732195842849&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ...
cout&amp;lt;&amp;lt;k5._hp&amp;lt;&amp;lt;endl;
k5._hp = 100;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 오른값참조로 받은 k5를 이용해 계속해서 사용할 수도 있다. 즉, k5는 타입은 오른값참조가 맞기는 하지만 k5자체는 오른값은 아니라 왼값이라는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Tes_RValueRef(std::move(k5)); 이렇게 이동시켜줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, std::move로 뱉어주는 임시타입 자체는 재사용할 수 없고 Knight&amp;amp;&amp;amp; 타입은 이를 받아서 사용하는 것이다. 자세히 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Knight&amp;amp;&amp;amp; k5 = std::move(k1);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k5는 왼쪽에 있고 std::move는 왼쪽에 있지 않다..!(?)&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;background-color: #dddddd;&quot;&gt;오른값 참조타입 자체가 원본 자체를 참조하고 있으면서 그 원본자체가 훼손되어도 상관없다는 의미이며, 오른값참조타입 자체는 오른값은 아니라는 것&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 auto 나 템플릿처럼 형식 연역이 일어나는 경우엔 원본의 타입이 무엇인지에 상관없이 바뀌기도 하는데 &amp;amp;&amp;amp;타입에 인자를&lt;span style=&quot;background-color: #c1bef9;&quot;&gt; std::forward&amp;lt;T&amp;gt;()&lt;/span&gt;를 사용하면 const에 따라 오버로딩이 필요 없이 제대로 연역할 수 있게 한다. 이는 std::forward는 오른값으로 캐스팅하는 std::move와는 달리 인자로 받는 값이 왼값, 오른값, xvalue이냐에 따라 달라지기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.cppreference.com/w/cpp/utility/forward&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://en.cppreference.com/w/cpp/utility/forward&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1742736236194&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;std::forward - cppreference.com&quot; data-og-description=&quot;(1) (since C++11) (until C++14) (since C++14) (2) (since C++11) (until C++14) (since C++14) 1) Forwards lvalues as either lvalues or as rvalues, depending on T. When t is a forwarding reference (a function argument that is declared as an rvalue reference t&quot; data-og-host=&quot;en.cppreference.com&quot; data-og-source-url=&quot;https://en.cppreference.com/w/cpp/utility/forward&quot; data-og-url=&quot;https://en.cppreference.com/w/cpp/utility/forward&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://en.cppreference.com/w/cpp/utility/forward&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://en.cppreference.com/w/cpp/utility/forward&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&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;std::forward - cppreference.com&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;(1) (since C++11) (until C++14) (since C++14) (2) (since C++11) (until C++14) (since C++14) 1) Forwards lvalues as either lvalues or as rvalues, depending on T. When t is a forwarding reference (a function argument that is declared as an rvalue reference t&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;en.cppreference.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;&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;pre id=&quot;code_1732196848624&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ...
void Test_Copy(Knight k)
{

}

template&amp;lt;typename T&amp;gt;
void Test_ForwardingRef(T&amp;amp;&amp;amp; param) // 전달참조
{
  // ...
  Test_Copy(param); // param이 왼값이었을 때 통과
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;void Test_ForwardingRef(T&amp;amp;&amp;amp; param) 에서 T&amp;amp;&amp;amp; 가 전달참조로 param이 왼값참조로 받으면 당연히 param은 왼값이지만 &lt;b&gt;&amp;nbsp;전달참조가 오른값으로 받아도 param이 왼값이므로 Test_Copy(param)은 통과&lt;/b&gt;하게 된다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[C++]</category>
      <author>럭키 </author>
      <guid isPermaLink="true">https://white-mouse.tistory.com/151</guid>
      <comments>https://white-mouse.tistory.com/151#entry151comment</comments>
      <pubDate>Tue, 22 Oct 2024 14:49:42 +0900</pubDate>
    </item>
    <item>
      <title>Modern C++ #8 오른값 참조(rvalue reference)와 std::move</title>
      <link>https://white-mouse.tistory.com/150</link>
      <description>&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;rvalue reference&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오른값 참조는 실제로 많이 사용하진 않지만 C++11 에서 가장 큰 변화라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rvalue의 사용으로 이전 C++과는 엄청난 속도차이를 불러일으켰다.&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;왼값 (lvalue) vs. 오른값(rvalue)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lvalue : 단일 식을 넘어서 계속 지속되는 개체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rvalue : lvalue가 아닌 나머지(임시값, 열거형, 람다, i++)&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;int a = 3;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;a = 4;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cout&amp;lt;&amp;lt;a&amp;lt;&amp;lt;endl;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;test(a);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 처음에 사용한 '왼쪽'에 있던 a는 계속 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 3 = a; 이렇게 사용할 수 없다.('식이 사용할 수 있는 왼값이여야합니다.' 라고 오류가 난다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 a++ = 5; 이렇게도 사용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;3이나 a++같은 임시적으로만 사용할 수 있는 값을 통상적으로 오른값&lt;/span&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;그런데 기본적인 상수가 아니라&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hp가 100인 Knight클래스를 정의한다음 k1이라는 객체를 만들었다고 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1729576097930&quot; class=&quot;bash&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;class Knight
{
public:
    int _hp = 100;
public:
    void PrintInfo() { cout&amp;lt;&amp;lt;_hp&amp;lt;&amp;lt;endl; }
    void PrintInfoConst() const { cout&amp;lt;&amp;lt;_hp&amp;lt;&amp;lt;endl; }
}

void TestKnight_Copy(Knight knight) {}
void TestKnight_LValueRef(Knight&amp;amp; knight) {}
void TestKnight_ConstLValueRef(const Knight&amp;amp; knight) 
{
    //knight.PrintInfo();
    knight.PrintInfoConst(); // const로 받은 인자의 const함수만 사용할수 있다. 
}

int main()
{
    Knight k1;
    
    TestKnight_Copy(k1); // 만약 Knight 클래스의 크기가 크면 복사하는 방식이므로 좋지 않다.
    
    TesetKnight_LValueRef(k1); // k1의 주소값을 넘겨줘 불필요한 복사도 없고 원본을 넘길수 있다.
    
    TesetKnight_LValueRef(Knight()); 
    // 임시객체를 넘기려고 하면 const가 붙지않은 값은 lvalue여야한다고 오류가 뜬다.
    // 즉, 단일식을 넘어서 계속 지속되는 개체가 아니라 오류가 나는데
    
    TestKnight_ConstLValueRef(Knight());
    // 이렇게 인자를 const로 받으면 임시객체를 넘기는게 된다.
    // const가 붙지 않으면 함수 내에서 인자로 넘어온 변수의 값을 바꿀 수 있는데
    // 사실상 임시개체가 들어오면 사라지므로 의미 없는 계산이다.
    // const는 읽기용으로 사용할 것이라 예측하기 때문에 넘어간다.

    return 0;
}&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp; &amp;nbsp; 이전까지는 위와 같았는데 C++11 이후엔 오른값을 넘기는 방법이 생겼다.&lt;/p&gt;
&lt;pre id=&quot;code_1729576097932&quot; class=&quot;cpp&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;c++&quot;&gt;&lt;code&gt;void TestKnight_RValueRef(Knight&amp;amp;&amp;amp; knight) {}
//&amp;amp;&amp;amp;를 두 개 붙여서 오른값을 참조로 받을 수 있다.

int main()
{
  Knight k1;
  
  //TestKnight_RValueRef(k1); // lvalue는 받을 수 없어 오류가 난다.
  TestKnight_RValueRef(Knight()); 이렇게 다시 오른값을 보내면 잘 실행된다.
  
  return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디스어셈블리를 보면 TestKnight_Copy 는 _hp값만 복사되어 k1이 아닌 다른 객체가 전달되고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;LValueRef, ConstLValueRef, ValueRef 모두&amp;nbsp; 비슷하게 원본 (Knight()을 넘겨주는곳은 스택에 잠깐 객체를 만든다음) 그 주소를 꺼내서 함수에 넘겨주는 방식이다.&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;pre id=&quot;code_1729576097932&quot; class=&quot;c++ arduino&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;c++&quot;&gt;&lt;code&gt;     TestKnight_RValueRef(static_cast&amp;lt;Knight&amp;amp;&amp;amp;&amp;gt;(k1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;k1은 실질적으로 존재하지만 RValue로 캐스팅해서 보내면 동작하므로&lt;br /&gt;TestKnight_RValueRef 안에서 k1의 원본 값들을 수정할수 있다.&lt;br /&gt;-&amp;gt; 그럼 lvalue를 인자로 받는 함수를 쓰면 되지 굳이 이걸 쓰는 이유는..?&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;background-color: #dddddd;&quot;&gt;어셈블리단에서는 LValue를 받는것과 RValue를 받은 것이 큰 의미가 차이가 없지만 C++입장에서 보면 크게 다르다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&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;Knight&amp;amp; knight 는 원본을 넘겨주니 수정이 가능하고 마음대로 다룰 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;const Knight&amp;amp; knight 는 원본을 넘겨주나 수정해선 안되고 읽는 것은 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;Knight&amp;amp;&amp;amp; knight 는 원본 객체를 넘겨주니 자유롭게 읽고 쓰기가 가능하고 &lt;b&gt;심지어 인자로 넘긴 후 원본은 더 이상 활용하지 않을것이니 마음대로 다룰 수 있다. 즉, 이동대상이라는 의미&lt;/b&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;b&gt;원본을 유지하지 않아도 되면 생기는 장점&lt;/b&gt;을 생각해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Knight클래스의 크기가 엄청 큰 클래스라고 가정해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1729576097933&quot; class=&quot;arduino&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;c++&quot;&gt;&lt;code&gt;class Pet
{
};

class Knight
{
public:
    Pet* _pet = nullptr;
    int  _hp = 100;
    
public:
    Knight() {cout&amp;lt;&amp;lt;&quot;Knight()&quot;&amp;lt;&amp;lt;endl;}
    // 복사 생성자.
    Knight(const Knight&amp;amp; knight)
    {
        cout&amp;lt;&amp;lt;&quot;const Knight&amp;amp;&quot;&amp;lt;&amp;lt;endl;
    }
    
    // 이동 생성자. (오른값을 받는다.받은 값을 보내고 나서 더 이상 활용하지 않는다.)
    Knight(Knight&amp;amp;&amp;amp; knight)
    {
        cout&amp;lt;&amp;lt;&quot;Knight&amp;amp;&amp;amp; knight)&quot;&amp;lt;&amp;lt;endl;
    }
    
    ~Knight()
    {
        if(_pet)
            delete _pet;
    }
    
public:
    // 복사 대입 연산자
    void operator=(const Knight&amp;amp; knight)
    {
        cout&amp;lt;&amp;lt;&quot;operator=(const Knight&amp;amp;)&quot;&amp;lt;&amp;lt;endl;
        _hp = knight._hp;
        //_pet = knight._pet; // 얕은 복사의 문제점. knight의 _pet을 나도 공유하게 된다.
        if(knight._pet != null)
            _pet = new Pet(*knight._pet); // 깊은 복사. 그런데 새로 만들어야하므로 비싸다.
    }
    
    // 이동 대입 연산자 - 받은 값을 더이상 사용하지 않으므로 훼손해도 된다.
    void operator=(Knight&amp;amp;&amp;amp; knight)
    {
        _hp = knight._hp;
        _pet = knight._pet; // 얕은 복사를 사용해도 어차피 knight가 없어질 것이므로 바로 _pet을 사용해도 된다..!
        knight._pet = nullptr;
    }
};


int main()
{
    /// ....
    
    Knight k2;
    k2._pet = new Pet();
    k2._hp = 1000;
    
    Knight k3;
    k3 = static_cast&amp;lt;Knight&amp;amp;&amp;amp;&amp;gt;(k2); // k2가 오른값이므로 이동대입연산자가 호출된다.
    // k2는 더이상 사용하지 않는다는 정보를 같이 주는 것이다.
    // k2의 값을 k3가 뺏어오니깐 k2의 값들을 '이동'시키는 것이다.
    // 참고로 static_cast로 오른값으로 타입변환을 하기도 하지만 일반적으로
    k3 = std::move(k2); // 이렇게 사용한다. move가 오른값 참조로 캐스팅한것과 마찬가지로 내부에서 동작한다.
    
    return 0;
    
}&lt;/code&gt;&lt;/pre&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;background-color: #99cefa;&quot;&gt;복사생성자나 복사대입 연산자에서 깊은 복사를 사용하면 새로운 객체를 만들어야하므로 더 느리고 무거울 수 있는데&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&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;std::move()를 사용해 오른값 참조로 캐스팅을 주로 하는데 처음에 만들 때 rvalue_cast라는 이름으로 쓸까도 후보에 있었다곤 한다.&amp;nbsp; 코어에서는 임시값을 사용했다가 쓰는 일이 빈번해서 C++11 이후에는 성능차이가 크게 생겼다.&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;Knight* k; 를 많이 쓰다보면 누가 k를 관리하는지 확인하기 쉽지 않다보니 딱 하나만 사용한다고 하면 unique_ptr을 사용할 수 있다. 프로젝트에 딱 하나만 존재해야하는 포인터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;std:: unique_ptr&amp;lt;Knight&amp;gt; uptr = std::make_unique&amp;lt;Knight&amp;gt;(); 를 만든 다음에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;//std:: unique_ptr&amp;lt;Knight&amp;gt; uptr2 = uptr; // 하면 삭제된 함수라고 오류가 난다. unique_ptr 클래스를 설계할 때 복사부분을 다 제거한 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이 시점에서 uptr을 더 이상 사용하지 않을 심산으로 uptr2에게 uptr을 관리해달라고 넘기고 싶어지면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;unique_ptr의 소유권을 넘긴다는 뜻으로 오른값으로 캐스팅하는 std::move를 사용해&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&gt;std:: unique_ptr&amp;lt;Knight&amp;gt; uptr2&amp;nbsp;= std::move(uptr);&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #99cefa;&quot;&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;b&gt;&lt;span&gt;&lt;span&gt;즉, 오른값 참조는 원본값을 더 이상 사용할 필요가 없다거나 unique_ptr처럼 권리를 넘기고 싶을 때 사용하면 된다. 이로써 복사하는 값이 들지 않고 포인터를&amp;nbsp; 이동으로 넘겨줌으로써 속도에 엄청난 이점을 가져왔다.&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[C++]</category>
      <author>럭키 </author>
      <guid isPermaLink="true">https://white-mouse.tistory.com/150</guid>
      <comments>https://white-mouse.tistory.com/150#entry150comment</comments>
      <pubDate>Tue, 22 Oct 2024 14:48:22 +0900</pubDate>
    </item>
    <item>
      <title>모델 #3 모델 띄우기</title>
      <link>https://white-mouse.tistory.com/149</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난시간까진 Assimp라이브러리를 이용해서 fbx파일을 읽고 필요한 정보만 머티리얼정보는 .xml파일로, 그외의 메쉬정보(정점, 인덱스, 이름, 계층구조)는 .mesh정보로 저장하는 작업을 AssimpTool프로젝트를 만들어 작업했다.&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;이제 이렇게 만든 파일을 다시 AssimpTool프로젝트에서 읽어다가 쓰는 작업을 해보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 Engine프로젝트에서 우리가 직접만든 파일을 쓰기전에는 텍스쳐를 로드하고 GeometryHelper로 만든 메쉬를 ResourceBase를 상속받은 Mesh에서 인덱스정보와 버텍스 정보를 갖고있게하고, 리소스 매니저에서 이를 가져와 오브젝트에 메쉬를 설정하도록 해서 쓰고 있었다.&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;// 18.NormalMappingDemo.cpp&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;Object &lt;br /&gt;_obj&amp;nbsp;=&amp;nbsp;make_shared&amp;lt;GameObject&amp;gt;(); &lt;br /&gt;_obj-&amp;gt;GetOrAddTransform(); &lt;br /&gt;_obj-&amp;gt;AddComponent(make_shared&amp;lt;MeshRenderer&amp;gt;()); &lt;br /&gt;{ &lt;br /&gt;auto&amp;nbsp;mesh&amp;nbsp;=&amp;nbsp;RESOURCES-&amp;gt;Get&amp;lt;Mesh&amp;gt;(L&quot;Sphere&quot;); &lt;br /&gt;_obj-&amp;gt;GetMeshRenderer()-&amp;gt;SetMesh(mesh); &lt;br /&gt;} &lt;br /&gt;{ &lt;br /&gt;auto&amp;nbsp;material&amp;nbsp;=&amp;nbsp;RESOURCES-&amp;gt;Get&amp;lt;Material&amp;gt;(L&quot;Leather&quot;); &lt;br /&gt;_obj-&amp;gt;GetMeshRenderer()-&amp;gt;SetMaterial(material); &lt;br /&gt;}&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 오브젝트가 들고있는 컴포넌트중 하나인 MeshRenderer에서 Update시(Material정보도 이때 가져와) 그려지곤 했다.&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;&amp;nbsp;하지만 우리는 이 Material, Mesh정보를 쓰지 않고 파일로부터 읽어들인 계층정보도 있는 메쉬와 머티리얼 정보를 사용할 것이다. 그래서 새로운 Model, ModelMesh,Component상속받은 ModelRenderer클래스들을 만들어 Assimp라이브러리로 파싱해 원하는 구조로 저장했던 fbx파일을 읽어들여 사용하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Engine프로젝트의 Resource필터에 Model필터를 추가하고 Model,ModelMesh클래스를 추가했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;커스텀포맷으로 저장한 파일들을 읽어다 쓸 수 있도록 Engie프로젝트에 추가했다. (실제로 호출할것은 AssimpTool프로젝트인데) Mesh클래스가 이미 ResourceBase를 상속받아 만들어져 있는데 AssimpTool로 만든 메쉬는 이와는 조금 다르지만 수정하기 어려우므로 아예 Model클래스를 Model필터를 만들어서 만들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;우선 ResourceBase를 상속받아 만들었던 &lt;b&gt;Mesh 클래스를 대체할 ModelMesh클래스&lt;/b&gt;이다. ResourceBase를 상속받진않지만 똑같이 지오메트리정보, 버텍스버퍼, 인덱스 버퍼를 들고있고 이 메쉬에 연결될 머티리얼 정보와 이 메쉬의 번호(boneIndex), 그리고 계층구조 정보(bone)를 들고있게 했다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1726802450667&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ModelMesh.h
#pragma once

struct ModelBone
{
	wstring name;
	int32 index;
	int32 parentIndex;
	shared_ptr&amp;lt;ModelBone&amp;gt; parent; // Cache

	Matrix transform;
	vector&amp;lt;shared_ptr&amp;lt;ModelBone&amp;gt;&amp;gt; children; // Cache
};

struct ModelMesh
{
	void CreateBuffers();

	wstring name;

	// Mesh
	shared_ptr&amp;lt;Geometry&amp;lt;ModelVertexType&amp;gt;&amp;gt; geometry = make_shared&amp;lt;Geometry&amp;lt;ModelVertexType&amp;gt;&amp;gt;();
	shared_ptr&amp;lt;VertexBuffer&amp;gt; vertexBuffer;
	shared_ptr&amp;lt;IndexBuffer&amp;gt; indexBuffer;

	// Material
	wstring materialName = L&quot;&quot;;
	shared_ptr&amp;lt;Material&amp;gt; material; // Cache

	// Bones
	int32 boneIndex;
	shared_ptr&amp;lt;ModelBone&amp;gt; bone; // Cache;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;그냥 쓰고 asMesh만 조금 수정해서 써보자. &lt;/span&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;asBone을 ModelBone클래스로 바꾼다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;ModelMesh클래스에 material 로 캐싱해놓고 없으면 리소스 매니저에서 가져다가 캐싱해서 쓴다.&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: #24292f; text-align: start;&quot;&gt;&lt;b&gt;Model클래스&lt;/b&gt; 이다. Model은 오브젝트가 MeshRenderer대신 ModelRenderer컴포넌트를 들고 그릴때 필요한 정보이다. 뼈대(계층구조)와 메쉬들, 머티리얼들의 정보를 갖고 있을 것이다. Model객체로 파일들을 로드해서 메모리에 올린다음에 오브젝트에 세팅해준다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1726801333467&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Model.h
#pragma once

struct ModelBone;
struct ModelMesh;

class Model : public enable_shared_from_this&amp;lt;Model&amp;gt;
{
public:
	Model();
	~Model();

public:
	void ReadMaterial(wstring filename);
	void ReadModel(wstring filename);

	uint32 GetMaterialCount() { return static_cast&amp;lt;uint32&amp;gt;(_materials.size()); }
	vector&amp;lt;shared_ptr&amp;lt;Material&amp;gt;&amp;gt;&amp;amp; GetMaterials() { return _materials; }
	shared_ptr&amp;lt;Material&amp;gt; GetMaterialByIndex(uint32 index) { return _materials[index]; }
	shared_ptr&amp;lt;Material&amp;gt; GetMaterialByName(const wstring&amp;amp; name);

	uint32 GetMeshCount() { return static_cast&amp;lt;uint32&amp;gt;(_meshes.size()); }
	vector&amp;lt;shared_ptr&amp;lt;ModelMesh&amp;gt;&amp;gt;&amp;amp; GetMeshes() { return _meshes; }
	shared_ptr&amp;lt;ModelMesh&amp;gt; GetMeshByIndex(uint32 index) { return _meshes[index]; }
	shared_ptr&amp;lt;ModelMesh&amp;gt; GetMeshByName(const wstring&amp;amp; name);

	uint32 GetBoneCount() { return static_cast&amp;lt;uint32&amp;gt;(_bones.size()); }
	vector&amp;lt;shared_ptr&amp;lt;ModelBone&amp;gt;&amp;gt;&amp;amp; GetBones() { return _bones; }
	shared_ptr&amp;lt;ModelBone&amp;gt; GetBoneByIndex(uint32 index) { return (index &amp;lt; 0 || index &amp;gt;= _bones.size() ? nullptr : _bones[index]); }
	shared_ptr&amp;lt;ModelBone&amp;gt; GetBoneByName(const wstring&amp;amp; name);

private:
	void BindCacheInfo();

private:
	wstring _modelPath = L&quot;../Resources/Models/&quot;;
	wstring _texturePath = L&quot;../Resources/Textures/&quot;;

private:
	shared_ptr&amp;lt;ModelBone&amp;gt; _root;
	vector&amp;lt;shared_ptr&amp;lt;Material&amp;gt;&amp;gt; _materials;
	vector&amp;lt;shared_ptr&amp;lt;ModelBone&amp;gt;&amp;gt; _bones;
	vector&amp;lt;shared_ptr&amp;lt;ModelMesh&amp;gt;&amp;gt; _meshes;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1726801423162&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Model.cpp
// ...
void Model::ReadMaterial(wstring filename)
{
	wstring fullPath = _texturePath + filename + L&quot;.xml&quot;;
	auto parentPath = filesystem::path(fullPath).parent_path();

	tinyxml2::XMLDocument* document = new tinyxml2::XMLDocument();
	tinyxml2::XMLError error = document-&amp;gt;LoadFile(Utils::ToString(fullPath).c_str());
	assert(error == tinyxml2::XML_SUCCESS);

	tinyxml2::XMLElement* root = document-&amp;gt;FirstChildElement();
	tinyxml2::XMLElement* materialNode = root-&amp;gt;FirstChildElement();

	while (materialNode)
	{
		shared_ptr&amp;lt;Material&amp;gt; material = make_shared&amp;lt;Material&amp;gt;();

		tinyxml2::XMLElement* node = nullptr;

		node = materialNode-&amp;gt;FirstChildElement();
		material-&amp;gt;SetName(Utils::ToWString(node-&amp;gt;GetText()));

		// Diffuse Texture
		node = node-&amp;gt;NextSiblingElement();
		if (node-&amp;gt;GetText())
		{
			wstring textureStr = Utils::ToWString(node-&amp;gt;GetText());
			if (textureStr.length() &amp;gt; 0)
			{
				auto texture = RESOURCES-&amp;gt;GetOrAddTexture(textureStr, (parentPath / textureStr).wstring());
				material-&amp;gt;SetDiffuseMap(texture);
			}
		}

		// Specular Texture
		node = node-&amp;gt;NextSiblingElement();
		if (node-&amp;gt;GetText())
		{
			wstring texture = Utils::ToWString(node-&amp;gt;GetText());
			if (texture.length() &amp;gt; 0)
			{
				wstring textureStr = Utils::ToWString(node-&amp;gt;GetText());
				if (textureStr.length() &amp;gt; 0)
				{
					auto texture = RESOURCES-&amp;gt;GetOrAddTexture(textureStr, (parentPath / textureStr).wstring());
					material-&amp;gt;SetSpecularMap(texture);
				}
			}
		}

		// Normal Texture
		node = node-&amp;gt;NextSiblingElement();
		if (node-&amp;gt;GetText())
		{
			wstring textureStr = Utils::ToWString(node-&amp;gt;GetText());
			if (textureStr.length() &amp;gt; 0)
			{
				auto texture = RESOURCES-&amp;gt;GetOrAddTexture(textureStr, (parentPath / textureStr).wstring());
				material-&amp;gt;SetNormalMap(texture);
			}
		}

		// Ambient
		{
			node = node-&amp;gt;NextSiblingElement();

			Color color;
			color.x = node-&amp;gt;FloatAttribute(&quot;R&quot;);
			color.y = node-&amp;gt;FloatAttribute(&quot;G&quot;);
			color.z = node-&amp;gt;FloatAttribute(&quot;B&quot;);
			color.w = node-&amp;gt;FloatAttribute(&quot;A&quot;);
			material-&amp;gt;GetMaterialDesc().ambient = color;
		}

		// Diffuse
		{
			node = node-&amp;gt;NextSiblingElement();

			Color color;
			color.x = node-&amp;gt;FloatAttribute(&quot;R&quot;);
			color.y = node-&amp;gt;FloatAttribute(&quot;G&quot;);
			color.z = node-&amp;gt;FloatAttribute(&quot;B&quot;);
			color.w = node-&amp;gt;FloatAttribute(&quot;A&quot;);
			material-&amp;gt;GetMaterialDesc().diffuse = color;
		}

		// Specular
		{
			node = node-&amp;gt;NextSiblingElement();

			Color color;
			color.x = node-&amp;gt;FloatAttribute(&quot;R&quot;);
			color.y = node-&amp;gt;FloatAttribute(&quot;G&quot;);
			color.z = node-&amp;gt;FloatAttribute(&quot;B&quot;);
			color.w = node-&amp;gt;FloatAttribute(&quot;A&quot;);
			material-&amp;gt;GetMaterialDesc().specular = color;
		}

		// Emissive
		{
			node = node-&amp;gt;NextSiblingElement();

			Color color;
			color.x = node-&amp;gt;FloatAttribute(&quot;R&quot;);
			color.y = node-&amp;gt;FloatAttribute(&quot;G&quot;);
			color.z = node-&amp;gt;FloatAttribute(&quot;B&quot;);
			color.w = node-&amp;gt;FloatAttribute(&quot;A&quot;);
			material-&amp;gt;GetMaterialDesc().emissive = color;
		}

		_materials.push_back(material);

		// Next Material
		materialNode = materialNode-&amp;gt;NextSiblingElement();
	}

	BindCacheInfo();
}

void Model::ReadModel(wstring filename)
{
	wstring fullPath = _modelPath + filename + L&quot;.mesh&quot;;

	shared_ptr&amp;lt;FileUtils&amp;gt; file = make_shared&amp;lt;FileUtils&amp;gt;();
	file-&amp;gt;Open(fullPath, FileMode::Read);

	// Bones
	{
		const uint32 count = file-&amp;gt;Read&amp;lt;uint32&amp;gt;();

		for (uint32 i = 0; i &amp;lt; count; i++)
		{
			shared_ptr&amp;lt;ModelBone&amp;gt; bone = make_shared&amp;lt;ModelBone&amp;gt;();
			bone-&amp;gt;index = file-&amp;gt;Read&amp;lt;int32&amp;gt;();
			bone-&amp;gt;name = Utils::ToWString(file-&amp;gt;Read&amp;lt;string&amp;gt;());
			bone-&amp;gt;parentIndex = file-&amp;gt;Read&amp;lt;int32&amp;gt;();
			bone-&amp;gt;transform = file-&amp;gt;Read&amp;lt;Matrix&amp;gt;();

			_bones.push_back(bone);
		}
	}

	// Mesh
	{
		const uint32 count = file-&amp;gt;Read&amp;lt;uint32&amp;gt;();

		for (uint32 i = 0; i &amp;lt; count; i++)
		{
			shared_ptr&amp;lt;ModelMesh&amp;gt; mesh = make_shared&amp;lt;ModelMesh&amp;gt;();

			mesh-&amp;gt;name = Utils::ToWString(file-&amp;gt;Read&amp;lt;string&amp;gt;());
			mesh-&amp;gt;boneIndex = file-&amp;gt;Read&amp;lt;int32&amp;gt;();

			// Material
			mesh-&amp;gt;materialName = Utils::ToWString(file-&amp;gt;Read&amp;lt;string&amp;gt;());

			//VertexData
			{
				const uint32 count = file-&amp;gt;Read&amp;lt;uint32&amp;gt;();
				vector&amp;lt;ModelVertexType&amp;gt; vertices;
				vertices.resize(count);

				void* data = vertices.data();
				file-&amp;gt;Read(&amp;amp;data, sizeof(ModelVertexType) * count);
				mesh-&amp;gt;geometry-&amp;gt;AddVertices(vertices);
			}

			//IndexData
			{
				const uint32 count = file-&amp;gt;Read&amp;lt;uint32&amp;gt;();

				vector&amp;lt;uint32&amp;gt; indices;
				indices.resize(count);

				void* data = indices.data();
				file-&amp;gt;Read(&amp;amp;data, sizeof(uint32) * count);
				mesh-&amp;gt;geometry-&amp;gt;AddIndices(indices);
			}

			mesh-&amp;gt;CreateBuffers();

			_meshes.push_back(mesh);
		}
	}

	BindCacheInfo();
}


void Model::BindCacheInfo()
{
	// Mesh에 Material 캐싱
	for (const auto&amp;amp; mesh : _meshes)
	{
		// 이미 찾았으면 스킵
		if (mesh-&amp;gt;material != nullptr)
			continue;

		mesh-&amp;gt;material = GetMaterialByName(mesh-&amp;gt;materialName);
	}

	// Mesh에 Bone 캐싱
	for (const auto&amp;amp; mesh : _meshes)
	{
		// 이미 찾았으면 스킵
		if (mesh-&amp;gt;bone != nullptr)
			continue;

		mesh-&amp;gt;bone = GetBoneByIndex(mesh-&amp;gt;boneIndex);
	}

	// Bone 계층 정보 채우기
	if (_root == nullptr &amp;amp;&amp;amp; _bones.size() &amp;gt; 0)
	{
		_root = _bones[0];

		for (const auto&amp;amp; bone : _bones)
		{
			if (bone-&amp;gt;parentIndex &amp;gt;= 0)
			{
				bone-&amp;gt;parent = _bones[bone-&amp;gt;parentIndex];
				bone-&amp;gt;parent-&amp;gt;children.push_back(bone);
			}
			else
			{
				bone-&amp;gt;parent = nullptr;
			}
		}
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Model에는 들고있는 모델메쉬정보 벡터, 모델메쉬정보들의 상속관계를 나타낼 본즈 벡터,그리고 같이 쓸 머티리얼 벡터를 갖고있게 했다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;우선 ReadMateral에서 읽어드린 .xml파일에서 asMaterial 대신 ResourceBase를 상속받은 Material을 쓰고자 한다. tinyxml2를 사용해 파싱해서 Material객체마다 텍스쳐맵이나 조명값들을 설정하고 _materials에 추가했다.&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;다만 ReadModel에서는 계층구조에 관한정보를 먼저 모두 읽어드린 다음에 메쉬 정보를 본격적으로 읽어드린다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;메쉬렌더러 컴포넌트 대신&lt;b&gt; ModelRenderer&lt;/b&gt;를 사용해 그리도록 Component를 상속해서 만들었다. 이 컴포넌트를 GameObject가 들고있는것이다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1726811516452&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// ModelRenderer.h
#pragma once
#include &quot;Component.h&quot;

class Model;
class Shader;
class Material;

class ModelRenderer : public Component
{
	using Super = Component;

public:
	ModelRenderer(shared_ptr&amp;lt;Shader&amp;gt; shader);
	virtual ~ModelRenderer();

	virtual void Update() override;

	void SetModel(shared_ptr&amp;lt;Model&amp;gt; model);
	void SetPass(uint8 pass) { _pass = pass; }

private:
	shared_ptr&amp;lt;Shader&amp;gt;	_shader;
	uint8				_pass = 0;
	shared_ptr&amp;lt;Model&amp;gt;	_model;
};

// ModelRenderer.cpp
void ModelRenderer::Update()
{
	if (_model == nullptr)
		return;

	// Bones
	BoneDesc boneDesc;

	const uint32 boneCount = _model-&amp;gt;GetBoneCount();
	for (uint32 i = 0; i &amp;lt; boneCount; i++)
	{
		shared_ptr&amp;lt;ModelBone&amp;gt; bone = _model-&amp;gt;GetBoneByIndex(i);
		boneDesc.transforms[i] = bone-&amp;gt;transform;
	}
	RENDER-&amp;gt;PushBoneData(boneDesc);

	// Transform
	auto world = GetTransform()-&amp;gt;GetWorldMatrix();
	RENDER-&amp;gt;PushTransformData(TransformDesc{ world });

	const auto&amp;amp; meshes = _model-&amp;gt;GetMeshes();
	for (auto&amp;amp; mesh : meshes)
	{
		if (mesh-&amp;gt;material)
			mesh-&amp;gt;material-&amp;gt;Update();

		// BoneIndex
		_shader-&amp;gt;GetScalar(&quot;BoneIndex&quot;)-&amp;gt;SetInt(mesh-&amp;gt;boneIndex);

		uint32 stride = mesh-&amp;gt;vertexBuffer-&amp;gt;GetStride();
		uint32 offset = mesh-&amp;gt;vertexBuffer-&amp;gt;GetOffset();

		DC-&amp;gt;IASetVertexBuffers(0, 1, mesh-&amp;gt;vertexBuffer-&amp;gt;GetComPtr().GetAddressOf(), &amp;amp;stride, &amp;amp;offset);
		DC-&amp;gt;IASetIndexBuffer(mesh-&amp;gt;indexBuffer-&amp;gt;GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0);

		_shader-&amp;gt;DrawIndexed(0, _pass, mesh-&amp;gt;indexBuffer-&amp;gt;GetCount(), 0, 0);
	}
}

void ModelRenderer::SetModel(shared_ptr&amp;lt;Model&amp;gt; model)
{
	_model = model;

	const auto&amp;amp; materials = _model-&amp;gt;GetMaterials();
	for (auto&amp;amp; material : materials)
	{
		material-&amp;gt;SetShader(_shader);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;ModelRenderer의 Update에서 모델이 있는지 확인해서 그린다. GameObject에 이 ModelRenderer컴포넌트를 추가한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;MeshRenderer와의 차이점은 메쉬렌더러는 하나의 메쉬, 하나의 머티리얼을 그렸다면 ModelRenderer에서는 여러개 가지고 있는 메쉬를 순회하면서 메쉬에 맞는 머티리얼을 가져다가 그린다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AssimpTool프로젝트에&lt;b&gt; StaticMeshDemo클래스&lt;/b&gt;를 추가했다. IExecute를 상속받았고, Client프로젝트에서 했던 것처럼 AssimpTool프로젝트의 진입점이 있는 곳에 GameDesc타입의 앱에 StaticMeshDemo를 연결해줬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 StaticMeshDemo는 아래와 같이 작성하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1727055132754&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// StaticDemo.h
#include &quot;IExecute.h&quot;

class StaticMeshDemo : public IExecute
{
public:
	void Init() override;
	void Update() override;
	void Render() override;

	void CreateTower();
	void CreateTank();

private:
	shared_ptr&amp;lt;Shader&amp;gt; _shader;
	shared_ptr&amp;lt;GameObject&amp;gt; _obj;
	shared_ptr&amp;lt;GameObject&amp;gt; _camera;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 IExecute를 상속받아서 Init, Update, Render를 만들고 연결할 쉐이더와 배치할 카메라, 그리고 게임오브젝트는 다를 샘플데모와 똑같다. 다만 CreateTower, CreateTank를 연결하고싶은 게임오브젝트에 따라 Init에서 호출하게 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CreateTank의 소스코드는 아래와 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1727055301756&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// StaticMeshDemo.cpp

void StaticMeshDemo::CreateTank()
{
	// CustomData -&amp;gt; Memory
	shared_ptr&amp;lt;class Model&amp;gt; m1 = make_shared&amp;lt;Model&amp;gt;();
	m1-&amp;gt;ReadModel(L&quot;Tank/Tank&quot;);
	m1-&amp;gt;ReadMaterial(L&quot;Tank/Tank&quot;);

	_obj = make_shared&amp;lt;GameObject&amp;gt;();
	_obj-&amp;gt;GetOrAddTransform()-&amp;gt;SetPosition(Vec3(0, 0, 50));
	_obj-&amp;gt;GetOrAddTransform()-&amp;gt;SetScale(Vec3(1.f));

	_obj-&amp;gt;AddComponent(make_shared&amp;lt;ModelRenderer&amp;gt;(_shader));
	{
		_obj-&amp;gt;GetModelRenderer()-&amp;gt;SetModel(m1);
		_obj-&amp;gt;GetModelRenderer()-&amp;gt;SetPass(1);
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Tower의 &lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;메시는 하나로 돼있었는데 Tank는 계층구조를 가지고 있어 얘를 로드해서 테스트해본다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;만약에 계층구조 없이 바로 로드하면&lt;/span&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/q7aK1/btsJHlz2nFT/GSsHjrtFEVVJiF5V0CkOT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q7aK1/btsJHlz2nFT/GSsHjrtFEVVJiF5V0CkOT0/img.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q7aK1/btsJHlz2nFT/GSsHjrtFEVVJiF5V0CkOT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq7aK1%2FbtsJHlz2nFT%2FGSsHjrtFEVVJiF5V0CkOT0%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;1920&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SGq8s/btsJHPgjZ9o/jkmMpnotqcwNbSvvdtZom0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SGq8s/btsJHPgjZ9o/jkmMpnotqcwNbSvvdtZom0/img.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SGq8s/btsJHPgjZ9o/jkmMpnotqcwNbSvvdtZom0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSGq8s%2FbtsJHPgjZ9o%2FjkmMpnotqcwNbSvvdtZom0%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;1920&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 탱크의 모습은 볼 수 없고 하나로 뭉쳐져서 나온다. 오른쪽 사진처럼 유니티에서 Tank.fbx를 열어보면 메쉬끼리 계층구조를 가지고 있는데 &lt;span style=&quot;background-color: #f8f9fa; color: #24292f; letter-spacing: 0px;&quot;&gt;bones의 정보를 활용하지 않고 모든 메쉬를 중앙에 배치해서 생긴 문제이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;15.ModeDemol.fx새로운 쉐이더 파일에(Client프로젝트\Shaders에 넣어주었다.) 최대 50개 (MAX_MODEL_TRANSFORMS 개) 뼈대 정보를 받아주도록 만들어주고 지금 렌더링하고 있는 뼈대의 인덱스 정보를 BoneBuffer라는 이름으로 상수버퍼를 추가했다. 그리고 이 값들을 ModelRenderer에서 채워서 넘겨준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3141&quot; data-origin-height=&quot;1263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LJ6EW/btsJHCg0Mwq/f7kxBOQQ1xck5A9wHuokyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LJ6EW/btsJHCg0Mwq/f7kxBOQQ1xck5A9wHuokyK/img.png&quot; data-alt=&quot;15.ModelDemo.fx / ModelRenderer.cpp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LJ6EW/btsJHCg0Mwq/f7kxBOQQ1xck5A9wHuokyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLJ6EW%2FbtsJHCg0Mwq%2Ff7kxBOQQ1xck5A9wHuokyK%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;3141&quot; height=&quot;1263&quot; data-origin-width=&quot;3141&quot; data-origin-height=&quot;1263&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;15.ModelDemo.fx / ModelRenderer.cpp&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ModelRenderer의 Update에서&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 RenderManager의 PushBoneData를 호출하고 쉐이더에 직접 본인덱스를 전달한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3119&quot; data-origin-height=&quot;1261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0U4kv/btsJHj3jM1Q/iK8sUtSdU6GkDlK8yXHzbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0U4kv/btsJHj3jM1Q/iK8sUtSdU6GkDlK8yXHzbK/img.png&quot; data-alt=&quot;RendererManager.h / RendererManager.cpp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0U4kv/btsJHj3jM1Q/iK8sUtSdU6GkDlK8yXHzbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0U4kv%2FbtsJHj3jM1Q%2FiK8sUtSdU6GkDlK8yXHzbK%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;3119&quot; height=&quot;1261&quot; data-origin-width=&quot;3119&quot; data-origin-height=&quot;1261&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;RendererManager.h / RendererManager.cpp&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RenderManager에도 같은 정보를 추가한다. ModelRenderer에서 호출할 PushBoneData도 만들어줬다. 상수버퍼 BoneBuffer를 채워준다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 StaticMeshDemodml Update에서 ModelRenderer의 Update가 호출되면 본정보, 트랜스폼정보 등이 쉐이더에 넘어가고 각 본 메쉬들을 돌아가면서 그리게 된다.&lt;/p&gt;
&lt;div style=&quot;border: 3px dashed #C6C6C6; padding: 0.6em;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;void&amp;nbsp;ModelRenderer::Update() &lt;br /&gt;{ &lt;br /&gt;if&amp;nbsp;(_model&amp;nbsp;==&amp;nbsp;nullptr) &lt;br /&gt;return; &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;Bones &lt;br /&gt;BoneDesc&amp;nbsp;boneDesc; &lt;br /&gt;&lt;br /&gt;const&amp;nbsp;uint32&amp;nbsp;boneCount&amp;nbsp;=&amp;nbsp;_model-&amp;gt;GetBoneCount(); &lt;br /&gt;for&amp;nbsp;(uint32&amp;nbsp;i&amp;nbsp;=&amp;nbsp;0;&amp;nbsp;i&amp;nbsp;&amp;lt;&amp;nbsp;boneCount;&amp;nbsp;i++) &lt;br /&gt;{ &lt;br /&gt;shared_ptr&amp;lt;ModelBone&amp;gt;&amp;nbsp;bone&amp;nbsp;=&amp;nbsp;_model-&amp;gt;GetBoneByIndex(i); &lt;br /&gt;boneDesc.transforms[i]&amp;nbsp;=&amp;nbsp;bone-&amp;gt;transform; &lt;br /&gt;} &lt;br /&gt;RENDER-&amp;gt;PushBoneData(boneDesc); &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;Transform &lt;br /&gt;auto&amp;nbsp;world&amp;nbsp;=&amp;nbsp;GetTransform()-&amp;gt;GetWorldMatrix(); &lt;br /&gt;RENDER-&amp;gt;PushTransformData(TransformDesc{&amp;nbsp;world&amp;nbsp;}); &lt;br /&gt;&lt;br /&gt;const&amp;nbsp;auto&amp;amp;&amp;nbsp;meshes&amp;nbsp;=&amp;nbsp;_model-&amp;gt;GetMeshes(); &lt;br /&gt;for&amp;nbsp;(auto&amp;amp;&amp;nbsp;mesh&amp;nbsp;:&amp;nbsp;meshes) &lt;br /&gt;{ &lt;br /&gt;if&amp;nbsp;(mesh-&amp;gt;material) &lt;br /&gt;mesh-&amp;gt;material-&amp;gt;Update(); &lt;br /&gt;&lt;br /&gt;//&amp;nbsp;BoneIndex &lt;br /&gt;_shader-&amp;gt;GetScalar(&quot;BoneIndex&quot;)-&amp;gt;SetInt(mesh-&amp;gt;boneIndex); &lt;br /&gt;&lt;br /&gt;uint32&amp;nbsp;stride&amp;nbsp;=&amp;nbsp;mesh-&amp;gt;vertexBuffer-&amp;gt;GetStride(); &lt;br /&gt;uint32&amp;nbsp;offset&amp;nbsp;=&amp;nbsp;mesh-&amp;gt;vertexBuffer-&amp;gt;GetOffset(); &lt;br /&gt;&lt;br /&gt;DC-&amp;gt;IASetVertexBuffers(0,&amp;nbsp;1,&amp;nbsp;mesh-&amp;gt;vertexBuffer-&amp;gt;GetComPtr().GetAddressOf(),&amp;nbsp;&amp;amp;stride,&amp;nbsp;&amp;amp;offset); &lt;br /&gt;DC-&amp;gt;IASetIndexBuffer(mesh-&amp;gt;indexBuffer-&amp;gt;GetComPtr().Get(),&amp;nbsp;DXGI_FORMAT_R32_UINT,&amp;nbsp;0); &lt;br /&gt;&lt;br /&gt;_shader-&amp;gt;DrawIndexed(0,&amp;nbsp;_pass,&amp;nbsp;mesh-&amp;gt;indexBuffer-&amp;gt;GetCount(),&amp;nbsp;0,&amp;nbsp;0); &lt;br /&gt;} &lt;br /&gt;}&lt;/p&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 본들에 들어 있는 트랜스폼 좌표는 각 메쉬들의 직속 부모의 상대좌표이므로 이를 오브젝트 자체의(루트) 기준의 로컬 좌표로 바꿔주기위해 쉐이더에서 또 수정이 필요하다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;15.ModelDemo.fx를 수정한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ntGLv/btsJHO9xacN/ZsloomQFXO5v9oTK6wp180/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ntGLv/btsJHO9xacN/ZsloomQFXO5v9oTK6wp180/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ntGLv/btsJHO9xacN/ZsloomQFXO5v9oTK6wp180/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FntGLv%2FbtsJHO9xacN%2FZsloomQFXO5v9oTK6wp180%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;1920&quot; height=&quot;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 본들에 들어 있는 트랜스폼 좌표는 각 메쉬들의 직속 부모의 상대좌표이므로 이를 오브젝트 자체의(루트) 기준의 로컬 좌표로 바꿔주기위해 쉐이더에서 또 수정이 필요하다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;15.ModelDemo.fx를 수정한다&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;BoneTransforms행렬 배열에는 각 부품이 로컬로 넘어가는 행렬이 들어 있는것이다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>[DirectX11]</category>
      <author>럭키 </author>
      <guid isPermaLink="true">https://white-mouse.tistory.com/149</guid>
      <comments>https://white-mouse.tistory.com/149#entry149comment</comments>
      <pubDate>Thu, 19 Sep 2024 17:33:49 +0900</pubDate>
    </item>
    <item>
      <title>모델 #2 Assimp라이브러리 이용해 Material, Bone, Mesh로딩하기.</title>
      <link>https://white-mouse.tistory.com/148</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우선 본격적으로 Assimp라이브러리를 사용하기에 앞서 Assimp라이브러리로 읽어들인 fbx파일을 어떤 구조를 가지고 있을지 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Bfs6q/btsJEj2U28F/KcCcubc2HhZmwfmupUYAAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Bfs6q/btsJEj2U28F/KcCcubc2HhZmwfmupUYAAk/img.png&quot; data-alt=&quot;Assimp라이브러리로 읽어들인 fbx의 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Bfs6q/btsJEj2U28F/KcCcubc2HhZmwfmupUYAAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBfs6q%2FbtsJEj2U28F%2FKcCcubc2HhZmwfmupUYAAk%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;649&quot; height=&quot;400&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Assimp라이브러리로 읽어들인 fbx의 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Assimp에서 관리하는 최상위 객체인 Scene이 하나 우선 있다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Scene:이 안에 RootNode와 Mesh,Material 배열이 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; color: #24292f; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: unset;&quot;&gt;RootNode : 메쉬의 계층구조를 위한 정보. 자신의 메쉬정보 Meshes[]와 직접 자식들의 포인터들 같은 정보가 들어있다. 만약 계층구조가 아니라면 RootNode가 아닌 Material, Mesh하나씩만 Scene에 들고 있으면 된다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: unset;&quot;&gt;ChildNode: RootNode의 직접자식들의 포인터가 가리키는 것 중 하나로 또 자신의 메쉬들 정보와/직접자식들의 포인터들 같은 정보가 들어있다. (*그리고 들고있는 메쉬가 꼭 메쉬가 아니라 라이팅 정보일수도 있고 여러가지 다양하다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: unset;&quot;&gt;Material[]
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: unset;&quot;&gt;Material : 텍스쳐가 주요이고 외에도 라이팅 관련된(Ambient, Diffuse, Specular, Emissive)정보들이 있을 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: unset;&quot;&gt;GetTexture&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: unset;&quot;&gt;Mesh[]
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li style=&quot;list-style-type: unset;&quot;&gt;Vertices[], Normals[], TextureCoords[](UV좌표),Faces[]&lt;/li&gt;
&lt;li style=&quot;list-style-type: unset;&quot;&gt;MaterialIndex(메쉬에서 Scene의 mMaterials[]의 MaterialIndex번째의 정보를 가져와 그릴지)&lt;/li&gt;
&lt;li style=&quot;list-style-type: unset;&quot;&gt;Face
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: unset;&quot;&gt;Indices&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 계층구조를 오브젝트가 가지고있는것은 애니메이션 외에도 자식메쉬들이 상대적인 위치와 크기, 회전값을 가지고 재사용될수도 있기 때문이다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;위 구조를 보고 어떻게 사용할지, 파싱할지 생각해보면 우선 Scene에서 Material은 좀 자식이랑 관련이 없이 따로 독립적으로 하나씩 들고있으면 될 듯하고 메쉬들은 Material처럼 하나씩 들고있을 수도 있지만 계층구조를 fbx파일 구조에서 가져오는게 나을 듯하다. Scene노드의 mMeshes[]에 넣는게 아닌 mRootNode에 태우는 것.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;무료 3D 모델을 구하기 위해서&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://free3d.com/&quot;&gt;https://free3d.com/&lt;/a&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;&amp;nbsp;요사이트를 이용해보려고 한다. EpicGames에서 제공하는 것인가 보다..? 여러 형태로 다운받을 수 있는데 fbx로 다운받으면 텍스쳐,obj다 포함되어있다. dragon, house, tower등을 다운받아서 로드해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;전시간에 Assimp라이브러리를 추가하면서 만들었던 새로운 프로젝트 AssimpTool의 Converter클래스를 열어서 시작한다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Assimp에서 제공하는 Importer가 있고 위 구조에서처럼 가져 올 수 있는 Scene이 aiScene이라는 타입으로 들고있을 수 있도록 추가했다. ai로 시작하는 것은 Assimp에서 import해올 것이다(이미 정해진 타입). 반면 Assimp에서 읽어들인것을 우리만의 사용자 지정포맷으로 바꾸고자 하는데 그때 쓸 타입을 as로 시작하도록&amp;nbsp;&lt;b&gt; AsTypes.h&lt;/b&gt;에 아래와 같이 추가하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1726711649138&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// AsTypes.h
#pragma once

using VertexType = VertexTextureNormalTangentBlendData;

struct asBone
{
	string name;
	int32 index = -1;
	int32 parent = -1;
	Matrix transform;
};

struct asMesh
{
	string name;
	aiMesh* mesh;
	vector&amp;lt;VertexType&amp;gt; vertices;
	vector&amp;lt;uint32&amp;gt; indices;

	int32 boneIndex;
	string materialName;
};

struct asMaterial
{
	string name;
	Color ambient;
	Color diffuse;
	Color specular;
	Color emissive;
	string diffuseFile;
	string specularFile;
	string normalFile;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 맨 위에 VertexType을 VertexTextureNormalTangentBlendData으로 만들었는데 이전시간까지 Engine프로젝트의&amp;nbsp; VertexTextureNormalTangentData구조체를 정의해서 position, uv, normal, tangent값을 갖도록 만들어서 썼다면 이후의 애니메이션과 스키닝, 블렌드를 공부할 때 blend도 추가해서 사용할텐데 그때 번거롭게 바꾸지 않기 위해 미리 Engine\VertexData.h에 추가했다.&lt;/p&gt;
&lt;pre id=&quot;code_1726711779127&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// VertexData.h
// ...
struct VertexTextureNormalTangentBlendData
{
	Vec3 position = { 0, 0, 0 };
	Vec2 uv = { 0, 0 };
	Vec3 normal = { 0, 0, 0 };
	Vec3 tangent = { 0, 0, 0 };
	Vec4 blendIndices = { 0, 0, 0, 0 };
	Vec4 blendWeights = { 0, 0, 0, 0 };
};

using ModelVertexType = VertexTextureNormalTangentBlendData;&lt;/code&gt;&lt;/pre&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;Converter클래스&lt;/b&gt;를 보면 아래와 같이 생겼다.&lt;/p&gt;
&lt;pre id=&quot;code_1726711866851&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#pragma once
#include &quot;AsTypes.h&quot;

class Converter
{
public:
	Converter();
	~Converter();

public:
	void ReadAssetFile(wstring file);
	void ExportModelData(wstring savePath);
	void ExportMaterialData(wstring savePath);

private:
	void ReadModelData(aiNode* node, int32 index, int32 parent);
	void ReadMeshData(aiNode* node, int32 bone);
	void WriteModelFile(wstring finalPath);

private:
	void ReadMaterialData();
	void WriteMaterialData(wstring finalPath);
	string WriteTexture(string saveFolder, string file);

private:
	wstring _assetPath = L&quot;../Resources/Assets/&quot;;
	wstring _modelPath = L&quot;../Resources/Models/&quot;;
	wstring _texturePath = L&quot;../Resources/Textures/&quot;;

private:
	shared_ptr&amp;lt;Assimp::Importer&amp;gt; _importer;
	const aiScene* _scene;

private:
	vector&amp;lt;shared_ptr&amp;lt;asBone&amp;gt;&amp;gt; _bones;
	vector&amp;lt;shared_ptr&amp;lt;asMesh&amp;gt;&amp;gt; _meshes;
	vector&amp;lt;shared_ptr&amp;lt;asMaterial&amp;gt;&amp;gt; _materials;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 Converter클래스는 IExecute를 상속받아 만들어진 AssimpTool클래스에서 객체로 만들어져 사용될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서를 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AssimpTool프로젝트를&amp;nbsp;시작프로젝트로&amp;nbsp;실행-&amp;gt; &lt;br /&gt;Main의&amp;nbsp;WinMain에서&amp;nbsp;AssimpTool&amp;nbsp;앱(IExecute상속)실행 &lt;br /&gt;&lt;b&gt;AssimpTool::Init&lt;/b&gt;에서 Converter객체를 생성해 아래와 같이 호출된다.&lt;/p&gt;
&lt;pre id=&quot;code_1726712124706&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &quot;pch.h&quot;
#include &quot;AssimpTool.h&quot;
#include &quot;Converter.h&quot;

void AssimpTool::Init()
{
	shared_ptr&amp;lt;Converter&amp;gt; converter = make_shared&amp;lt;Converter&amp;gt;();

	// FBX -&amp;gt; Memory
	converter-&amp;gt;ReadAssetFile(L&quot;Tower/Tower.fbx&quot;);		// 1

	// Memory -&amp;gt; CustomData (File)
	converter-&amp;gt;ExportMaterialData(L&quot;Tower/Tower&quot;);		// 2
	converter-&amp;gt;ExportModelData(L&quot;Tower/Tower&quot;);	        // 3

	// CustomData (File) -&amp;gt; Memory
    // 이부분은 후에 다른부분에서 진행예정.
}

 // ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AssimpTool에서 Converter툴의 객체를 호출해서 사용하는 것을 보면&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;border: 3px dashed #C6C6C6; padding: 0.6em;&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&lt;br /&gt;&amp;nbsp;ReadAssetFile에서&amp;nbsp;Resources/Assets에&amp;nbsp;있는&amp;nbsp;fbx파일&amp;nbsp;경로&amp;nbsp;전달&lt;br /&gt;&amp;nbsp;Converter의&amp;nbsp;생성자에서&amp;nbsp;만든&amp;nbsp;Assimp::Importer객체로&amp;nbsp;메모리로&amp;nbsp;읽어드림&lt;br /&gt;2.&amp;nbsp;&lt;br /&gt;&amp;nbsp;2-1.&amp;nbsp;ExportMaterialData-&amp;gt;ReadMaterialData()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;scene에서&amp;nbsp;aiMaterial로&amp;nbsp;가져온다음&amp;nbsp;AsTypes에&amp;nbsp;정의했던&amp;nbsp;asMaterial타입&amp;nbsp;정보들만&amp;nbsp;가져와&amp;nbsp;_materials에&amp;nbsp;추가한다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(이&amp;nbsp;때&amp;nbsp;push_back으로&amp;nbsp;들어가서&amp;nbsp;자동으로&amp;nbsp;인덱스&amp;nbsp;생성된다)&lt;br /&gt;&amp;nbsp;2-2.&amp;nbsp;ExportMaterialData-&amp;gt;WriteMaterialData()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;2-1에서&amp;nbsp;변환한&amp;nbsp;타입을&amp;nbsp;Resources/Texures에&amp;nbsp;.xml파일로&amp;nbsp;저장한다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_materials을&amp;nbsp;순회하면서&amp;nbsp;이름,&amp;nbsp;DiffuseMap이&amp;nbsp;있으면&amp;nbsp;텍스쳐로&amp;nbsp;저장하고&amp;nbsp;이름,(SpecularMap,&amp;nbsp;NormalMap도&amp;nbsp;마찬가지)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;머티리얼의&amp;nbsp;Ambient값,&amp;nbsp;Diffuse값,&amp;nbsp;Specular값,&amp;nbsp;Emissive값을&amp;nbsp;xml다&amp;nbsp;저장한다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;3.&amp;nbsp;메쉬정보가&amp;nbsp;뼈대&amp;nbsp;관련된&amp;nbsp;vertex,&amp;nbsp;Index정보&lt;br /&gt;&amp;nbsp;3-1.&amp;nbsp;ExportModelData-&amp;gt;ReadModelData(현재&amp;nbsp;노드,&amp;nbsp;현재&amp;nbsp;인덱스,&amp;nbsp;부모&amp;nbsp;인덱스)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-asBone타입&amp;nbsp;객체&amp;nbsp;동적으로&amp;nbsp;만들어서&amp;nbsp;인자로&amp;nbsp;받은&amp;nbsp;값을&amp;nbsp;넣고(+상대좌표&amp;nbsp;정보,부모좌표계로&amp;nbsp;변환행렬&amp;nbsp;함께&amp;nbsp;추가)&amp;nbsp;_bones에&amp;nbsp;추가한다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-이&amp;nbsp;계층에&amp;nbsp;메쉬&amp;nbsp;정보가&amp;nbsp;있으면(조명이나&amp;nbsp;다른것만&amp;nbsp;있으면&amp;nbsp;넘어갈수도&amp;nbsp;있다.)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;이&amp;nbsp;계층에&amp;nbsp;있어&amp;nbsp;읽은&amp;nbsp;aiMesh정보를&amp;nbsp;asMesh정보로&amp;nbsp;하나씩&amp;nbsp;변환하여&amp;nbsp;_meshes에&amp;nbsp;추가한다.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(이때&amp;nbsp;VertexType,&amp;nbsp;UV,&amp;nbsp;Normal값을&amp;nbsp;필요에&amp;nbsp;맞게&amp;nbsp;변환한다.)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;-ExportModelData에서&amp;nbsp;ReadModelData를&amp;nbsp;호출할때&amp;nbsp;최초엔&amp;nbsp;루트노드를&amp;nbsp;전달하고&amp;nbsp;&lt;br /&gt;그&amp;nbsp;하위&amp;nbsp;자식들을&amp;nbsp;또&amp;nbsp;ReadModelData함수&amp;nbsp;내에서&amp;nbsp;재귀함수로&amp;nbsp;호출된다.&amp;nbsp;(ReadModelData는&amp;nbsp;인자로&amp;nbsp;노드와&amp;nbsp;인덱스,&amp;nbsp;부모가&amp;nbsp;있게&amp;nbsp;된다)&lt;br /&gt;&amp;nbsp;&amp;nbsp;3-2.&amp;nbsp;ExportMaterialData-&amp;gt;WriteModelData()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;3-1에서&amp;nbsp;변환한&amp;nbsp;타입을&amp;nbsp;Resources/Models에&amp;nbsp;.mesh확장자로&amp;nbsp;바이너리&amp;nbsp;파일로&amp;nbsp;저장&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;/div&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;Converter클래스에 있는 aiScene클래스는 Assimp라이브러리에 정의되어있는 타입으로 맨 위의 그림처럼&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;int mNumMeshes;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;aiNode** mRootNode;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;int mNumMaterials;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;aiMaterial** mMaterials;등을 가지고있다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;얘를 이용해 경로를 입력해주면 경로에 있는 애셋파일들을 읽어올것이므로 Converter클래스에&amp;nbsp; void ReadAssetFile(wstring file);을 만들어줬다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;1. fbx 파일을 읽어들이는 ReadAssetFile함수&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1726712469158&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &quot;pch.h&quot;
#include &quot;Converter.h&quot;
#include &amp;lt;filesystem&amp;gt;
#include &quot;Utils.h&quot;
#include &quot;tinyxml2.h&quot;
#include &quot;FileUtils.h&quot;

Converter::Converter()
{
	_importer = make_shared&amp;lt;Assimp::Importer&amp;gt;();

}

Converter::~Converter()
{

}

void Converter::ReadAssetFile(wstring file)
{
	wstring fileStr = _assetPath + file;

	auto p = std::filesystem::path(fileStr);
	assert(std::filesystem::exists(p));

	_scene = _importer-&amp;gt;ReadFile(
		Utils::ToString(fileStr),
		aiProcess_ConvertToLeftHanded |
		aiProcess_Triangulate |
		aiProcess_GenUVCoords |
		aiProcess_GenNormals |
		aiProcess_CalcTangentSpace
	);

	assert(_scene != nullptr);
}
// ...&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;C++17부터 파일 관련된 부분이 표준에 들어가 FILE*이런걸 안쓴다..!!&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;#include &amp;lt;filesystem&amp;gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ReadAssetFile에서&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;path클래스에 파일의 경로를 전달해 만든 객체로 관리한다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;wstring&amp;lt;-&amp;gt;string을 변환하는 함수를 Utils에 넣어뒀었다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 이제 ReadFile로 fbx파일을 읽어올 때 옵션을 줄 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;aiProcess_ConvertToLeftHanded:&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;aiProcess_Triangulate: 삼각형 단위로 읽어온다.?&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;aiProcess_GenUVCoords:UV좌표도 읽어와야하고 없으면 연산으로도 얻어와야한다. GenNormals,CalcTangentSpace도 마찬가지다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 옵션을 주어 연산까지 해서 정보를 추출할 수 있는것은 큰 장점이나 매번 로드할때마다 연산을 다시 하는것은 속도가 너무 느리다. 그래서 우리만의 포맷으로 저장했다가 읽어서 사용할 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 ReadAssetFile로 읽는 부분에 중단점을 걸고 현재 구조는 어떤지 본다음에 구조를 짜보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RPFFE/btsJGlFi09L/KROeFz0yH60p4c4Uf7dCik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RPFFE/btsJGlFi09L/KROeFz0yH60p4c4Uf7dCik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RPFFE/btsJGlFi09L/KROeFz0yH60p4c4Uf7dCik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRPFFE%2FbtsJGlFi09L%2FKROeFz0yH60p4c4Uf7dCik%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;1920&quot; height=&quot;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;유니티에서 아무 fbx파일을 열어서 보면 (여기선 로드할건 아니지만 house.fbx파일을 열었다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;1051&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yncCK/btsJE1gKpiG/QYlT5PCp1CDJOlicKXurSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yncCK/btsJE1gKpiG/QYlT5PCp1CDJOlicKXurSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yncCK/btsJE1gKpiG/QYlT5PCp1CDJOlicKXurSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyncCK%2FbtsJE1gKpiG%2FQYlT5PCp1CDJOlicKXurSK%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;412&quot; height=&quot;379&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;1051&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;유니티에서 같은 fbx파일을 열어서 보면 정점개수 661와 , 인덱스 갯수1433가 있다는 정보, 집의 메쉬도 있다. 즉, 메쉬가 계층구조를 이루고 있는데 모든 정점개수를 한곳에 넣은 다음에 몇번부터 몇번까지는 루트의 정보, 그 다음부터는 그 다음 뼈대, 또 그 다음부터는 그 다음 후손의 정점(인덱스)정보를 담고있는데 이를 분리하지 않고 한번에 쓰고 있는 것이다. 비슷하게 이를 활용해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;339&quot; data-origin-height=&quot;789&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R76Ch/btsJGajZvHz/Mvr4rYd8r4yaSsjy5Sxgzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R76Ch/btsJGajZvHz/Mvr4rYd8r4yaSsjy5Sxgzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R76Ch/btsJGajZvHz/Mvr4rYd8r4yaSsjy5Sxgzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR76Ch%2FbtsJGajZvHz%2FMvr4rYd8r4yaSsjy5Sxgzk%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;207&quot; height=&quot;789&quot; data-origin-width=&quot;339&quot; data-origin-height=&quot;789&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;Assimp와 관련된 타입을 우리만의 사용자 지정 포맷으로 만들기 위해 AssimpTool프로젝트의 Utils에 AsTypes파일을 추가햇었다. 여기에 asBone, asMesh, asMaterial타입을 정의하고 Converter클래스에 vector로 그냥 들고 있게한 이유이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjdZ4h/btsJF6aFnN5/OuOAXmatY0JVLCriXqUN1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjdZ4h/btsJF6aFnN5/OuOAXmatY0JVLCriXqUN1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjdZ4h/btsJF6aFnN5/OuOAXmatY0JVLCriXqUN1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjdZ4h%2FbtsJF6aFnN5%2FOuOAXmatY0JVLCriXqUN1K%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;417&quot; height=&quot;118&quot; data-origin-width=&quot;497&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;asMesh에는 이름과 Assimp에서 로드한 진짜 메쉬 aiMesh와 정점정보, 인덱스 정보를 넣어둔다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 asMesh에 연관된 머티리얼을 맵핑해서 써야하는데 이를 위해 boneIndex와 materialName을 추가했다. 여기서 boneIndex는 계층구조에서 어떤 노드와 붙어있는지를 인덱스로 나타낼 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 Assimp &lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;라이브러리로 fbx파일을 읽어 메모리에 올렸으면 하나하나 분리하는 함수를 만든다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;2. MateraiData 분리하여 .xml파일로 저장하기 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;ReadMaterialData, ReadModelData&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1726723746469&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void Converter::ExportMaterialData(wstring savePath)
{
	wstring finalPath = _texturePath + savePath + L&quot;.xml&quot;;
	ReadMaterialData();
	WriteMaterialData(finalPath);
}


void Converter::ReadMaterialData()
{
	for (uint32 i = 0; i &amp;lt; _scene-&amp;gt;mNumMaterials; i++)
	{
		aiMaterial* srcMaterial =  _scene-&amp;gt;mMaterials[i];

		shared_ptr&amp;lt;asMaterial&amp;gt; material = make_shared&amp;lt;asMaterial&amp;gt;();
		material-&amp;gt;name = srcMaterial-&amp;gt;GetName().C_Str();

		aiColor3D color;
		// Ambient
		srcMaterial-&amp;gt;Get(AI_MATKEY_COLOR_AMBIENT, color);
		material-&amp;gt;ambient = Color(color.r, color.g, color.b, 1.f);

		// Diffuse
		srcMaterial-&amp;gt;Get(AI_MATKEY_COLOR_DIFFUSE, color);
		material-&amp;gt;diffuse = Color(color.r, color.g, color.b, 1.f);

		// Specular
		srcMaterial-&amp;gt;Get(AI_MATKEY_COLOR_SPECULAR, color);
		material-&amp;gt;specular = Color(color.r, color.g, color.b, 1.f);
		srcMaterial-&amp;gt;Get(AI_MATKEY_SHININESS, material-&amp;gt;specular.w);

		// Emissive
		srcMaterial-&amp;gt;Get(AI_MATKEY_COLOR_EMISSIVE, color);
		material-&amp;gt;emissive = Color(color.r, color.g, color.b, 1.0f);

		aiString file;

		// Diffuse Texture
		srcMaterial-&amp;gt;GetTexture(aiTextureType_DIFFUSE, 0, &amp;amp;file);
		material-&amp;gt;diffuseFile = file.C_Str();

		// Specular Texture
		srcMaterial-&amp;gt;GetTexture(aiTextureType_SPECULAR, 0, &amp;amp;file);
		material-&amp;gt;specularFile = file.C_Str();

		// Normal Texture
		srcMaterial-&amp;gt;GetTexture(aiTextureType_NORMALS, 0, &amp;amp;file);
		material-&amp;gt;normalFile = file.C_Str();

		_materials.push_back(material);
	}
}


void Converter::WriteMaterialData(wstring finalPath)
{
	auto path = filesystem::path(finalPath);

	// 폴더가 없으면 만든다.
	filesystem::create_directory(path.parent_path());

	string folder = path.parent_path().string();

	shared_ptr&amp;lt;tinyxml2::XMLDocument&amp;gt; document = make_shared&amp;lt;tinyxml2::XMLDocument&amp;gt;();

	tinyxml2::XMLDeclaration* decl = document-&amp;gt;NewDeclaration();
	document-&amp;gt;LinkEndChild(decl);

	tinyxml2::XMLElement* root = document-&amp;gt;NewElement(&quot;Materials&quot;);
	document-&amp;gt;LinkEndChild(root);
	 
	for (shared_ptr&amp;lt;asMaterial&amp;gt; material : _materials)
	{
		tinyxml2::XMLElement* node = document-&amp;gt;NewElement(&quot;Material&quot;);
		root-&amp;gt;LinkEndChild(node);

		tinyxml2::XMLElement* element = nullptr;

		element = document-&amp;gt;NewElement(&quot;Name&quot;);
		element-&amp;gt;SetText(material-&amp;gt;name.c_str());
		node-&amp;gt;LinkEndChild(element);

		element = document-&amp;gt;NewElement(&quot;DiffuseFile&quot;);
		element-&amp;gt;SetText(WriteTexture(folder, material-&amp;gt;diffuseFile).c_str());
		node-&amp;gt;LinkEndChild(element);

		element = document-&amp;gt;NewElement(&quot;SpecularFile&quot;);
		element-&amp;gt;SetText(WriteTexture(folder, material-&amp;gt;specularFile).c_str());
		node-&amp;gt;LinkEndChild(element);

		element = document-&amp;gt;NewElement(&quot;NormalFile&quot;);
		element-&amp;gt;SetText(WriteTexture(folder, material-&amp;gt;normalFile).c_str());
		node-&amp;gt;LinkEndChild(element);

		element = document-&amp;gt;NewElement(&quot;Ambient&quot;);
		element-&amp;gt;SetAttribute(&quot;R&quot;, material-&amp;gt;ambient.x);
		element-&amp;gt;SetAttribute(&quot;G&quot;, material-&amp;gt;ambient.y);
		element-&amp;gt;SetAttribute(&quot;B&quot;, material-&amp;gt;ambient.z);
		element-&amp;gt;SetAttribute(&quot;A&quot;, material-&amp;gt;ambient.w);
		node-&amp;gt;LinkEndChild(element);

		element = document-&amp;gt;NewElement(&quot;Diffuse&quot;);
		element-&amp;gt;SetAttribute(&quot;R&quot;, material-&amp;gt;diffuse.x);
		element-&amp;gt;SetAttribute(&quot;G&quot;, material-&amp;gt;diffuse.y);
		element-&amp;gt;SetAttribute(&quot;B&quot;, material-&amp;gt;diffuse.z);
		element-&amp;gt;SetAttribute(&quot;A&quot;, material-&amp;gt;diffuse.w);
		node-&amp;gt;LinkEndChild(element);

		element = document-&amp;gt;NewElement(&quot;Specular&quot;);
		element-&amp;gt;SetAttribute(&quot;R&quot;, material-&amp;gt;specular.x);
		element-&amp;gt;SetAttribute(&quot;G&quot;, material-&amp;gt;specular.y);
		element-&amp;gt;SetAttribute(&quot;B&quot;, material-&amp;gt;specular.z);
		element-&amp;gt;SetAttribute(&quot;A&quot;, material-&amp;gt;specular.w);
		node-&amp;gt;LinkEndChild(element);

		element = document-&amp;gt;NewElement(&quot;Emissive&quot;);
		element-&amp;gt;SetAttribute(&quot;R&quot;, material-&amp;gt;emissive.x);
		element-&amp;gt;SetAttribute(&quot;G&quot;, material-&amp;gt;emissive.y);
		element-&amp;gt;SetAttribute(&quot;B&quot;, material-&amp;gt;emissive.z);
		element-&amp;gt;SetAttribute(&quot;A&quot;, material-&amp;gt;emissive.w);
		node-&amp;gt;LinkEndChild(element);
	}

	document-&amp;gt;SaveFile(Utils::ToString(finalPath).c_str());
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;머티리얼 정보는 쉐이더에 넘겨줄 정보로 ambient, diffuse, specular, emissive값, 디퓨즈 텍스쳐, 스페큘러 텍스쳐, 노멀텍스쳐 등이 들어있었다. 이를 갖고 있는 scene에서 각각 가져와 텍스쳐는 복사하고 조명 정보는 xml타입으로 tinyxml2를 이용해 저장한다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 메쉬정보(머티리얼X) 분리하여 .mesh파일로 저장하기&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1726724350606&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Converter.cpp
// ...
void Converter::ExportModelData(wstring savePath)
{
	wstring finalPath = _modelPath + savePath + L&quot;.mesh&quot;;
	ReadModelData(_scene-&amp;gt;mRootNode, -1, -1);
	WriteModelFile(finalPath);
}

void Converter::ReadModelData(aiNode* node, int32 index, int32 parent)
{
	shared_ptr&amp;lt;asBone&amp;gt; bone = make_shared&amp;lt;asBone&amp;gt;();
	bone-&amp;gt;index = index;
	bone-&amp;gt;parent = parent;
	bone-&amp;gt;name = node-&amp;gt;mName.C_Str();

	// Relative Transform
	Matrix transform(node-&amp;gt;mTransformation[0]);
	bone-&amp;gt;transform = transform.Transpose();

	// 2) Root (Local)
	Matrix matParent = Matrix::Identity;
	if (parent &amp;gt;= 0)
		matParent = _bones[parent]-&amp;gt;transform;

	// Local (Root) Transform
	bone-&amp;gt;transform = bone-&amp;gt;transform * matParent;

	_bones.push_back(bone);

	// Mesh
	ReadMeshData(node, index);

	// 재귀 함수
	for (uint32 i = 0; i &amp;lt; node-&amp;gt;mNumChildren; i++)
		ReadModelData(node-&amp;gt;mChildren[i], _bones.size(), index);
}

void Converter::ReadMeshData(aiNode* node, int32 bone)
{
	if (node-&amp;gt;mNumMeshes &amp;lt; 1)
		return;

	shared_ptr&amp;lt;asMesh&amp;gt; mesh = make_shared&amp;lt;asMesh&amp;gt;();
	mesh-&amp;gt;name = node-&amp;gt;mName.C_Str();
	mesh-&amp;gt;boneIndex = bone;

	for (uint32 i = 0; i &amp;lt; node-&amp;gt;mNumMeshes; i++)
	{
		uint32 index = node-&amp;gt;mMeshes[i];
		const aiMesh* srcMesh = _scene-&amp;gt;mMeshes[index];

		// Material Name
		const aiMaterial* material = _scene-&amp;gt;mMaterials[srcMesh-&amp;gt;mMaterialIndex];
		mesh-&amp;gt;materialName = material-&amp;gt;GetName().C_Str();

		const uint32 startVertex = mesh-&amp;gt;vertices.size();

		for (uint32 v = 0; v &amp;lt; srcMesh-&amp;gt;mNumVertices; v++)
		{
			// Vertex
			VertexType vertex;
			::memcpy(&amp;amp;vertex.position, &amp;amp;srcMesh-&amp;gt;mVertices[v], sizeof(Vec3));

			// UV
			if (srcMesh-&amp;gt;HasTextureCoords(0))
				::memcpy(&amp;amp;vertex.uv, &amp;amp;srcMesh-&amp;gt;mTextureCoords[0][v], sizeof(Vec2));

			// Normal
			if (srcMesh-&amp;gt;HasNormals())
				::memcpy(&amp;amp;vertex.normal, &amp;amp;srcMesh-&amp;gt;mNormals[v], sizeof(Vec3));

			mesh-&amp;gt;vertices.push_back(vertex);
		}

		// Index
		for (uint32 f = 0; f &amp;lt; srcMesh-&amp;gt;mNumFaces; f++)
		{
			aiFace&amp;amp; face = srcMesh-&amp;gt;mFaces[f];

			for (uint32 k = 0; k &amp;lt; face.mNumIndices; k++)
				mesh-&amp;gt;indices.push_back(face.mIndices[k] + startVertex);
		}
	}

	_meshes.push_back(mesh);
}


void Converter::WriteModelFile(wstring finalPath)
{
	auto path = filesystem::path(finalPath);

	// 폴더가 없으면 만든다.
	filesystem::create_directory(path.parent_path());

	shared_ptr&amp;lt;FileUtils&amp;gt; file = make_shared&amp;lt;FileUtils&amp;gt;();
	file-&amp;gt;Open(finalPath, FileMode::Write);

	// Bone Data
	file-&amp;gt;Write&amp;lt;uint32&amp;gt;(_bones.size());
	for (shared_ptr&amp;lt;asBone&amp;gt;&amp;amp; bone : _bones)
	{
		file-&amp;gt;Write&amp;lt;int32&amp;gt;(bone-&amp;gt;index);
		file-&amp;gt;Write&amp;lt;string&amp;gt;(bone-&amp;gt;name);
		file-&amp;gt;Write&amp;lt;int32&amp;gt;(bone-&amp;gt;parent);
		file-&amp;gt;Write&amp;lt;Matrix&amp;gt;(bone-&amp;gt;transform);
	}

	// Mesh Data
	file-&amp;gt;Write&amp;lt;uint32&amp;gt;(_meshes.size());
	for (shared_ptr&amp;lt;asMesh&amp;gt;&amp;amp; meshData : _meshes)
	{
		file-&amp;gt;Write&amp;lt;string&amp;gt;(meshData-&amp;gt;name);
		file-&amp;gt;Write&amp;lt;int32&amp;gt;(meshData-&amp;gt;boneIndex);
		file-&amp;gt;Write&amp;lt;string&amp;gt;(meshData-&amp;gt;materialName);

		// Vertex Data
		file-&amp;gt;Write&amp;lt;uint32&amp;gt;(meshData-&amp;gt;vertices.size());
		file-&amp;gt;Write(&amp;amp;meshData-&amp;gt;vertices[0], sizeof(VertexType) * meshData-&amp;gt;vertices.size());

		// Index Data
		file-&amp;gt;Write&amp;lt;uint32&amp;gt;(meshData-&amp;gt;indices.size());
		file-&amp;gt;Write(&amp;amp;meshData-&amp;gt;indices[0], sizeof(uint32) * meshData-&amp;gt;indices.size());
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;요만 보면 &lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;ExportModelData에서 ReadModelData를 호출할때 최초엔 루트노드를 전달하고 그 하위 자식들을 또 ReadModelData함수 내에서 재귀함수로 호출된다. 그래서 ReadModelData는 인자로 노드와 인덱스, 부모가 있게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다음시간엔 AssmipTool을 사용해 만든 메쉬파일(*.mesh)과 머티리얼파일(*.xml)정보를 읽어들여서 모델(오브젝트)를 띄우는 작업을 할 예정이다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[DirectX11]</category>
      <author>럭키 </author>
      <guid isPermaLink="true">https://white-mouse.tistory.com/148</guid>
      <comments>https://white-mouse.tistory.com/148#entry148comment</comments>
      <pubDate>Thu, 19 Sep 2024 10:43:18 +0900</pubDate>
    </item>
    <item>
      <title>모델 #1 Assimp 라이브러리</title>
      <link>https://white-mouse.tistory.com/147</link>
      <description>&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Assimp를 사용해 fbx파일 로드하기&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지는 기본도형 큐브같은걸 코드로 만들어서 갖다 썼지만 유니티짱같은건 만드는게 말이 안된다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 맥스나 마야로 만든 파일을 가져다가 쓸 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 맥스나 마야에서 추출하면 fbx타입으로 나오고 이를 로드할수 있도록 도와주는게 Assimp라이브러리다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;유니티에서 fbx파일을 열어서 보면 하나의 파일이 여러 정보를 포함하고있는데 대부분 메쉬이고 경우에 따라서 머티리얼이나 조명도 포함하고있다.&lt;/span&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/LSaCq/btsJEHWJebe/BLZZKGF1YYqoXMriOClvd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LSaCq/btsJEHWJebe/BLZZKGF1YYqoXMriOClvd0/img.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LSaCq/btsJEHWJebe/BLZZKGF1YYqoXMriOClvd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLSaCq%2FbtsJEHWJebe%2FBLZZKGF1YYqoXMriOClvd0%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;1920&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cluro5/btsJEpvaHG9/ympekkmDcF3yiWMZRBSrxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cluro5/btsJEpvaHG9/ympekkmDcF3yiWMZRBSrxk/img.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cluro5/btsJEpvaHG9/ympekkmDcF3yiWMZRBSrxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcluro5%2FbtsJEpvaHG9%2FympekkmDcF3yiWMZRBSrxk%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;1920&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;드래곤 모델 같은경우는 라이트와 카메라도 들고있다.그런데 왜 이렇게 많은 정보를 포함하고 있을까? 지오메트리 정보(정점정보)만 갖고있으면 되는게 아닐까 싶지만 꼭 모델이 게임에만 쓰이라는 법은 없다. 애니메이션, 영화용으로 쓰일 수도 있기 때문에 복잡하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;그래서 fbx로 만들어진 모델 로드하고 필요한 정보만 골라서 써야하는데 경우에 따라서 한번만 배치하고 끝나는 물체가 있고 애니메이션에 결합해서 써야하는 물체가 있다. 언리얼에선 이를 스태틱메쉬와 스켈레탈 메쉬로 구분해서 쓰기도 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;만약에 드래곤이 고개를 좌우로 젓는 애니메이션을 추가&lt;/span&gt;한다고 하면 2D라면 이미지 여러장을 붙여서 애니메이션을 만들겠지만 3D에선 고개를 돌린 메쉬를 또 추출해서 프레임마다 메쉬를 바꾼다는것은 솔직히 에바무리다. 메쉬를 InputAssembler단계에 꽂아서 GPU에 줘야하는데 정점 수가 바뀌고 연산량도 엄청나게 된다. 아마도 ConstantBuffer나 다른 무언가를 사용할 방법이 있을 듯하다. 애니메이션은 다음에 다룰것이나 미리 생각해보면 유니티에서도 그랬고 언리얼에서도 그렇고 오브젝트는 계층구조를 이루고 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;척추가 있고 팔, 다리, 몸, 머리 그리고 각각도 세분화되어 계층 구조를 만들어 준 다음에 &lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;어느 부분만 움직이게끔 수학적으로 계산해서 움직이는게 3D 모델의 게임에서 동작 원리&lt;/span&gt;이다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 유니티짱의 Hierachy에서 볼 수 있듯이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;하나의 fbx파일임에도 위와 같은 계층구조로 들어가 있는 경우가 많기 때문&lt;/span&gt;에 파싱할때부터 이 계층구조를 생각해서 작업해야한다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 이 &lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;계층구조는 트리를 이용하는게 직관적&lt;/span&gt;이다. 보통 트리는 읽어드릴때 순서가 보장이 안되므로 재&lt;span style=&quot;background-color: #c0d1e7;&quot;&gt;귀적으로 루트를 기준으로 실행하는 함수를 만들어 자식에도 적용&lt;/span&gt;시키는 방법으로 만들 예정이다. 미리 이 계층구조를 fbx파일로부터 읽어서 원하는 포맷으로 저장할 것을 생각하면, 한&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt; fbx파일에는 계층구조를 이용해 각각 다른 자식 메쉬들이 Tranform정보같은 것을 들고 있다. 그리고 각각의 Transform정보는 자신의 부모를 기준으로 하는 자신의 좌표이다. 이 각각의 좌표를 로컬의 좌표라고 하면 로컬스페이스와 혼동이 있을 수 있으니 '상대좌표'..? 라고 루키스는 부르기로한다. (월드 기준이 아니라 직접 상관을 기준으로 하는 상대적인 좌표다는 의미에서)이들의 정보를 들고 있을 것인데 &lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;이 상대좌표를 Transform의 정보에서 SRT행렬을 얻어서 곱하면 world가 아닌 직속상관(부모)로 넘어가게 된다.&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 이 상대좌표를 가진 계층구조안의 어떤 메쉬가 월드좌표를 알고자 한다면 부모의 SRT를 곱하고 또 계속해서 그 부모의 SRT를 곱하고 월드까지 올라가면 된다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 쉐이더 코드에서 이 상대좌표 정보를 처음에 fbx로 읽어 가지고 쉐이더 코드에서 연산할 때는 월드에서의 좌표를 결국 필요로 하는데, 매번 상대좌표를 월드좌표까지 계속 SRT행렬 곱연산을 할게 아니라 처음부터 이 상대좌표를 우선 오브젝트의 root라고 할수 있는 로컬좌표계의 좌표로, fbx파일을 처음부터 읽어드릴때 변환해주고 시작해야한다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;=&amp;gt; 계층구조를 가진 오브젝트의 메쉬에서 각각은 직속 부모를 기준으로한 상대좌표를 가지고있는데 이를 오브젝트의 로컬좌표계 기준으로 바꿔서 들고 있게 한 다음에 쉐이더에 넘어가면 월드, 뷰, 프로젝션 좌표계로 변하게 되는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;그리고 로컬좌표계의 좌표로 변환된 상대좌표를 쉐이더에 넘어가면 일반 오브젝트와 똑같이 World, View 변환 행렬에 곱해져 Projection행렬까지 WVP를 파이프라인에서 타게 될 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;또 반면 애니메이션을 틀 때는 계층구조 안에 있는 메쉬의 상대좌표가 얼마만큼 바뀌느냐로 나타난다.(이 아니라 상대좌표 기준으로 로컬 좌표가 얼마나 움직이는지를 나타내나?)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;이들을 실제로 저장할때는 로컬 좌표계로 변화하는 행렬로 할 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;그래도 이 계층구조에 관한 것은 트랜스폼을 다룰때도 봤었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;fbx파일을 로드하는 라이브러리는 여러가지가 있는데 Assimp라이브러리는 fbx외에도 obj파일도 로드할 수 있는 좀더 범용적인 라이브러리다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;assimp로 로드한 파일을 바로 쓰기도 하지만 보통 직접 짠 구조에 맞게 포맷을 변경한 다음 메모리에 들고 있게 하기도 한다.(fbx에서 로드한 모든 정보-조명 등-가 필요한게 아니라서) 그 포맷으로 최초로 assimp로 로드한 파일을 바꿔서 저장한다음 다시 로드할때 사용자 지정 포맷으로 하면 속도에서 더 우수하기도 하다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/assimp/assimp&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/assimp/assimp&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Assimp라이브러리는 위 깃헙에서 가져오 CMake로 윈도우용으로 빌드한다.( &lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;리눅스에서는 바로 사용할 수 있지만 윈도우에서는 힘들기 때문에 Cmake로 빌드해서 만드는 방법&lt;/span&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/RSyp3/btsJDs0IAP7/AKgxK627WO6qjUxJvIScxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RSyp3/btsJDs0IAP7/AKgxK627WO6qjUxJvIScxk/img.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RSyp3/btsJDs0IAP7/AKgxK627WO6qjUxJvIScxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRSyp3%2FbtsJDs0IAP7%2FAKgxK627WO6qjUxJvIScxk%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;1920&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAd29R/btsJDE0Ry0j/D0F4AE5DJ9ivZ5ycDaDPlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAd29R/btsJDE0Ry0j/D0F4AE5DJ9ivZ5ycDaDPlK/img.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAd29R/btsJDE0Ry0j/D0F4AE5DJ9ivZ5ycDaDPlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAd29R%2FbtsJDE0Ry0j%2FD0F4AE5DJ9ivZ5ycDaDPlK%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;1920&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;Libraries의 Lib,Include에 CMake로 빌드해서 나온 Assimp 라이브러리와 헤더파일들을 각각 추가해줬다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;이들을 사용할 Engine 프로젝트에 라이브러리 추가하는 방법&lt;/span&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/caeFIp/btsJEHh6oYq/MJcP9IiJvYd8XrakmNR5R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caeFIp/btsJEHh6oYq/MJcP9IiJvYd8XrakmNR5R1/img.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caeFIp/btsJEHh6oYq/MJcP9IiJvYd8XrakmNR5R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaeFIp%2FbtsJEHh6oYq%2FMJcP9IiJvYd8XrakmNR5R1%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;1920&quot; height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpHmzD/btsJDVuxWkM/MJDCtwrft7F3A3vmy7zmsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpHmzD/btsJDVuxWkM/MJDCtwrft7F3A3vmy7zmsk/img.png&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpHmzD/btsJDVuxWkM/MJDCtwrft7F3A3vmy7zmsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpHmzD%2FbtsJDVuxWkM%2FMJDCtwrft7F3A3vmy7zmsk%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;1920&quot; height=&quot;1080&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;&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;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;Assimp를 이용해 fbx파일을 로드해 원하는 포맷으로 저장해줄 툴을 만들것인데 Engine,Client프로젝트와도 무관하기때문에 솔루션에 새로운 프로젝트를 추가해준다. 우선 Windows데스크톱 마법사로 AssimpTool이름으로 미리컴파일된 헤더를 포함하여 빈프로젝트로 데스크톱 애플리케이션(.exe)가 출력되도록 만들었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AssimpTool프로젝트의 출력디렉토리도 Client프로젝트와 동일하기 '$(SolutionDir)Binaries\'로 바꿔주고 중간디렉토리도 동일하게 '(SolutionDir)Intermediate\(Configuration)\'으로 수정해주었다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 Client프로젝트의 C/C++&amp;gt;추가포함디렉터리도 복붙했다.(여기에 Include)&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 링커&amp;gt;입력&amp;gt;추가라이브러리디렉터리도 Client프로젝트걸 복붙했다.(여기에 Lib)&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 AssimpTool에 Client프로젝트의 Main.h,cpp, CameraScript.h,cpp파일을 넣었다. Main에서 WinMain함수가 있어서 진입점을 바꾼것이다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;AssimpTool의 pch.h에도 Engine라이브러리를 사용할 수 있도록&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;#pragma comment(lib, &quot;Engine/Engine.lib&quot;)&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;#include &quot;Engine/EnginePch.h&quot;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;를 추가하였다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;실제로 fbx파일을 로드해서 원하는 포맷으로 저장을 위할 파일들을 위해 Utils 필터를 만들고 Converter클래스도 추가했다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 Converter클래스에는 Assimp라이브러리의 importer객체를 생성해 실제로 fbx 파일을 읽어오도록 쓸 것이다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또 Client프로젝트와 마찬가지로 Main에서 IExecute를 상속받은 앱이 실행단위로 실행되도록 AssimpTool클래스를 &lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;IExecute를&lt;span&gt; 상속받게 만들었다.(IExecute는 Engine프로젝트에 있어서 쓸 수 있다.)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;&lt;span&gt;마지막으로 fbx를 최초로 Assimp로 로드한 그대로 사용하지 않고 직접 정의한 구조로 따로 저장했다가 읽어서 사용할 것인데 이때 정의할 구조를 AsTypes파일을 파서 정의하고자 한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;아래와 같은 필터 구조를 가진다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;367&quot; data-origin-height=&quot;593&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eC3YdX/btsJDIPKZY7/8nvGSEKeB331jni7ahhNDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eC3YdX/btsJDIPKZY7/8nvGSEKeB331jni7ahhNDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eC3YdX/btsJDIPKZY7/8nvGSEKeB331jni7ahhNDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeC3YdX%2FbtsJDIPKZY7%2F8nvGSEKeB331jni7ahhNDK%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;367&quot; height=&quot;593&quot; data-origin-width=&quot;367&quot; data-origin-height=&quot;593&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;그 다음 이제는 우리가 모델들 fbx파일들을 많이 로드할건데 이들을 Resources디렉터리에 넣고있을 것이다. 이때 파일구조를 어떻게 할지 고민인데 모델하나하나마다 디렉토리를 생성해서 가지고 있을 수도 있고, Assets디렉토리를 Resources하위에 만들어서 가지고 있을 수도 있다. 우선 여기선 후자의 방법을 선택하Assets디렉토리에 모델 하나하나씩 넣고 Assimp로 이들을 읽어들여 원하는 포맷으로 변경한 파일을 Resources에 Models디렉토리에 넣어둘 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ebI5hu/btsJEOIhKLA/4k1kk6QNnoSwFP1qsXw1Dk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ebI5hu/btsJEOIhKLA/4k1kk6QNnoSwFP1qsXw1Dk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ebI5hu/btsJEOIhKLA/4k1kk6QNnoSwFP1qsXw1Dk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FebI5hu%2FbtsJEOIhKLA%2F4k1kk6QNnoSwFP1qsXw1Dk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;352&quot; height=&quot;214&quot; data-origin-width=&quot;352&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Textures에는 이전까지 테스트하는데 쓰던 디퓨즈맵, 노멀맵 등이 들어있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음시간엔 이 AssimpTool을 사용해서 fbx파일을 로드하고 직접 정의한 포맷으로 파일을 변환해 저장한다음, 다시 읽고 모델을 띄우는것까지 진행해보자.&lt;/p&gt;</description>
      <category>[DirectX11]</category>
      <author>럭키 </author>
      <guid isPermaLink="true">https://white-mouse.tistory.com/147</guid>
      <comments>https://white-mouse.tistory.com/147#entry147comment</comments>
      <pubDate>Thu, 19 Sep 2024 10:08:13 +0900</pubDate>
    </item>
    <item>
      <title>Normal Mapping</title>
      <link>https://white-mouse.tistory.com/146</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난번 시간의 Material에 쉐이더에 넘겨줄 값들을 한데 모으는 작업을 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 시간엔 Material에 질감을 주는 방법을 더 연구해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를들어 같은 메쉬에 같은 디퓨즈맵 텍스쳐를 입히면 같은 오브젝트처럼 보일것이다. 그러나 어떤한곳에는 울툴불퉁한 느낌을 주고싶다. 그럼 어떻게 해야될까? 한 면에서도 빛을 반사시키는 각도를 다르게 보이게 하면 마치 굴절이 있는것처럼 보인다. 그럼 이를 구현하는 방법은 무엇이 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법 1) 메쉬의 정점수를 늘리는 것이다. 그럼 정말로 표면이 울퉁불퉁한 것이고 빛 계산도 정점에 따라 다르게 될 것이다. 그러나 정점수가 늘어난다는 것은 파이프라인에 들어가는 정점의 수가 늘어난단다는것으로 렌더링의 부하가 같이 커지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법2) 그럼 음영효과를 줄 수 있으면서 삼각형의(정점)의 갯수를 늘리지 않는 방법은 무엇일까?&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 style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;빛의 굴절이 생기려면 실제로 한 면에서도 노멀 값이 달라야 하고 이를 모아서 새로운 텍스쳐로 관리를 해야한다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;디퓨즈맵, 색을 뽑아주는 텍스쳐와 별도로말이다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기에서 텍스쳐에서 어떤 정보를 뽑아내느냐에 따라 쓰임이 다양하다는 걸 알 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd9Wul/btsJEjO8iRz/MDSFHvKewvJTcA8iJIEjj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd9Wul/btsJEjO8iRz/MDSFHvKewvJTcA8iJIEjj1/img.png&quot; data-alt=&quot;탄젠트 스페이스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd9Wul/btsJEjO8iRz/MDSFHvKewvJTcA8iJIEjj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd9Wul%2FbtsJEjO8iRz%2FMDSFHvKewvJTcA8iJIEjj1%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;1920&quot; height=&quot;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;탄젠트 스페이스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;그럼 기준을 무엇으로 삼아서 노멀벡터를 구해야하냐가 관건인데 '탄젠트 스페이스'의 개념이 나타난다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;지금까지 좌표계 변환이 로컬-월드-뷰 스페이스로 넘어갔는데 사실은 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;노멀벡터와 관련된 제 3의 공간이 있는것이고 얘를 탄젠트 스페이스&lt;/span&gt;라고 한다.&lt;/span&gt; &lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;탄젠트는 접하는것에 의미가 있는데 구의 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;표면에 접하는 평면&lt;/span&gt;이 있다고 가정해보자.(좌 하단) 그&lt;span style=&quot;background-color: #c1bef9;&quot;&gt; 표면에 3가지 축을 가진 벡터가 있는데 탄젠트, 바이노멀, 노멀이다. 각각 x,y,z축&lt;/span&gt;을 의미한다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;참고로 로컬은 구의 원점(중심)을 기준으로 x,y,z가 있었는데 탄젠트스페이스는 정점마다 다 다른값을 가지고있을수 있어 정점의 개수와 비례한다?&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결론은 로컬스페이스가 있기전에부터 이미 눈에는 보이지 않았지만 모든 공간에 모든 방향에 대해 탄젠트 스페이스가 존재했다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;T,B,&lt;/span&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;N중에서 N은 노멀벡터는 표면을 기준으로 구할 수있고 B,T도 구하는 식이 있으나 생략하고 그냥 접하는 평면으로부터 구할수 있고 나중에 툴로만든 fbx,메쉬를 로드해도 N,B,T를 거기에서 추출할 수 있다. 또 N,B,T 중 두 개만 주어지더라도 외적을 통해 나머지 하나를 구할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OfA5O/btsJD8fTcdG/DdK262uIQZ3L2HdABgtvFk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OfA5O/btsJD8fTcdG/DdK262uIQZ3L2HdABgtvFk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OfA5O/btsJD8fTcdG/DdK262uIQZ3L2HdABgtvFk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOfA5O%2FbtsJD8fTcdG%2FDdK262uIQZ3L2HdABgtvFk%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;242&quot; height=&quot;242&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;&lt;br /&gt;우선 테스트할 노멀맵 텍스쳐를 살펴보자. 솔루션 하위의 Resources\Textures에 Leather_Normal.jpg로 저장해두었다. 이 텍스쳐에서 어떻게 노멀맵의 정보를 가져올 수 있을까? 어떻게 노멀맵을 구성할지 생각해보면 우선&lt;span style=&quot;color: #24292f; text-align: start;&quot;&gt;&amp;nbsp;&lt;span style=&quot;background-color: #c1bef9;&quot;&gt;한 픽셀마다 rgb값이 있다. 얘를 이용해 x,y,z값이 각각 -1 ~ 1의 값&lt;/span&gt;으로 사용할 것이다. 중요한 것은 이 좌표값들은 탄젠트스페이스를 기준으로 나타나는 값이다. &lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;그런데 왜 샘플에 있는 노멀맵은 푸른색을 띄고있을까? 그것은 대부분의 노멀맵이 푸른색인데 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;r,g,b값이 t,b,n을 나타내&lt;/span&gt;고있고 파랑색값이 normal을 나타내는데 노멀벡터값이 제일 높은 경우가 많기 때문이다. (탄젠트스페이스 기준으로 대각선 기울기보다 높이가 더 높은 경우이겠지)&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또 rgba로 탄젠트스페이스의 x,y,z를 나타낸다는 것 외에도 유의할 점은 어떤 좌표계에서 만나서 쉐이더코드를 연산하느냐이다. 지금까지는 픽셀 쉐이더 작업을 할 때 월드에서 만나서 월드에서 연산을 하는 식으로 했다. 그러나 뷰스페이스에서 만나서 연산을 하는것도 방법이다.?-&amp;gt; 아마 픽셀쉐이더에 월드변환 좌표를 넘겨줘서 그 기준으로 연산했다는것인가?&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지는 로컬-&amp;gt;월드-&amp;gt;카메라-&amp;gt;뷰 스페이스 변환이 있었는데 여기에 탄젠트 스페이스가 추가된것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chXLwo/btsJE3EYAch/uC5DzkxW3OGrmr74fuODp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chXLwo/btsJE3EYAch/uC5DzkxW3OGrmr74fuODp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chXLwo/btsJE3EYAch/uC5DzkxW3OGrmr74fuODp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchXLwo%2FbtsJE3EYAch%2FuC5DzkxW3OGrmr74fuODp0%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;1920&quot; height=&quot;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 탄젠트스페이스는 좌표계 변환 단계 중 어디에서 먼저 연산되냐면 로컬스페이스의 전이다. 탄젠트 스페이스의 좌표들이 로컬 스페이스의 좌표계로 넘어가야한다. 탄젠트스페이스에서의 좌표를 로컬스페이스계로 바꿨다가 월드스페이스계로 또 두 번 연산해 바꿀 수도 있지만 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;한번에 탄젠트좌표계에서 월드좌표계로 바꿀&lt;/span&gt; 수도 있다. 그 &lt;span style=&quot;background-color: #c1bef9;&quot;&gt;행렬을 구해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock floatLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UbYTG/btsJDzSEjs9/Uy3LdhTbGtkD09u7d0WUaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UbYTG/btsJDzSEjs9/Uy3LdhTbGtkD09u7d0WUaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UbYTG/btsJDzSEjs9/Uy3LdhTbGtkD09u7d0WUaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUbYTG%2FbtsJDzSEjs9%2FUy3LdhTbGtkD09u7d0WUaK%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;552&quot; height=&quot;311&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만능 좌표계 변환 행렬을 보면 A좌표계에서 B좌표계를 변환할 때&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;u,v벡터를 가진 A좌표계에선 한 점이 (x,y,z)좌표를 갖고 있었는데&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;U,V벡터를 가진 B좌표계에선 같은 값이 (X,Y,Z)좌표를 갖고 있는 것으로 바뀌는 것이다. 이때 x,y,z를 X,Y,Z로 바꿔주는 행렬 M은 사진과 같은 원소들을 가지게 되는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, A좌표계에서 원점을 가져서&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1,0,0&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;0,1,0&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;0,0,1 이 각각 A 좌표계에서의 u,v,w좌표였는데 B 좌표계서는 다른 벡터값들을 가지게 되고 그게&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ux,uv,uz&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;vx,vy,vz&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;wx,wy,wz값을 가진다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각각 right, up, look벡터를 나타낸다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 A좌표계에서 B좌표계로 회전뿐만 아니라 이동도 일어났다면 네번째 행에 B좌표계 기준 A의 좌표 x,y,z값이 들어간다. =&amp;gt; 이동을 포함할거면 A좌표계에서의 한 점의 좌표를 나타낼 때, x,y,z외에 마지막 성분으로 1을 넣고 아니면 0을 넣으면 합 Qx+Qy+Qz의 값이 안나오게 되어 회전만 적용된다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 만능 좌표계 변환 행렬에서 유도해보면 탄젠트 스페이스에서의 한 좌표를 알았을 때 이를 월드스페이스에서의 좌표로 바꾸는 행렬을 어떻게 만드는지 알 수 있다. 다시말하면 탄젠트스페이서의 T,B,N이 월드스페이스에서 어떤 값을 가질지를 예상해보는 것이다. 우선 지금까지는 노멀값(N)만 기본 메쉬에서 쓰고있었는데 탄젠트 값(T)도 넣어서 사용한다. 이 둘을 외적해서 바이노멀값(B)을 구할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/brqjvR/btsJESQ1YFl/wkE849tWlfHMopPQppxPA1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/brqjvR/btsJESQ1YFl/wkE849tWlfHMopPQppxPA1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/brqjvR/btsJESQ1YFl/wkE849tWlfHMopPQppxPA1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbrqjvR%2FbtsJESQ1YFl%2FwkE849tWlfHMopPQppxPA1%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;1920&quot; height=&quot;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;미리 기본으로 넣어준 노멀값, 탄젠트 값은 로컬스페이스를 기준으로 준 값이다. 이를 로컬에서 월드좌표계 변환 행렬에 넣으면 월드에서의 T,B,N 좌표가 나오게 된다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그러면 좌표계 변환행렬의 첫행, 둘째행, 셋째행에 T,B,N의 성분을 넣으면 탄젠트스페이스에서 월드스페이스로의 변환 행렬이 되는 것이다..?&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;다시 정리를 해보면 노멀맵에 넣어주는 r,g,b값은 탄젠트 스페이스 기준 -1~1 사이의 t,b,n값이 0~255로 치환되어 들어가 있는 값이고 이 값이 다시 쉐이더에서 -1~1사이로 변환되고 또 로컬X월드좌표계로 변환해주는 좌표계 변환 행렬을 곱해주면 노멀값이 월드좌표에서 어떤 값을 가지는지가 나온다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;정점을 늘리지 않으면서도 픽셀마다 다른 노멀값을 가지고 있는것 같은 효과를 주는 것이 바로 노멀맵핑의 핵심이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; color: #24292f; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li style=&quot;list-style-type: unset;&quot;&gt;노멀맵핑이 필요한 이유 : 점점추가해 삼각형을 늘리지 않고 음영효과를 내기 위해 노멀 맵핑을 사용한다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: unset;&quot;&gt;노멀맵 텍스쳐에 들어가있는 값의 의미: 텍스쳐의 r,g,b가 탄젠트 스페이스(표면에 접하는 평면)에서의 t,b,n값을 의미한다.&lt;/li&gt;
&lt;li style=&quot;list-style-type: unset;&quot;&gt;노멀맵에서 읽은 값을 로컬좌표계로, 또 다시 월드좌표계로 한번에 변환되는 변환행렬: t,b,n이 월드좌표계 기준으로 구할 수 있으면 그 값이 변환행렬의 up, right, look벡터가 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;그럼 다음 단계에서 픽셀쉐이더 단계에서 노멀 값을 이용해 빛연산이 들어가면 더 입체감이 생기게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이를 한번 구현해보자.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;제일 앞서 VertexData.h에 VertexTextureNormalData구조체를 만들어서 position, uv, normal값이 들어있었다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 normal값을 직접 계산식이나 변환행렬을 이용해 구할 수도 있지만 사실상 대부분(fbx파일등을 읽어서 사용할 때) 정점에서 노말과 탄젠트 값도 이미 포함돼서 쉐이더에 들어간다. 그래서 Tangent값도 들어간 정점 구조체 하나를 더 VertexTextureNormalData타입으로 만들었다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f6f8fa; color: #24292f; text-align: start;&quot;&gt;&lt;code&gt;struct VertexTextureNormalTangentData
{
	Vec3 position = { 0, 0, 0 };
	Vec2 uv = { 0, 0 };
	Vec3 normal = { 0, 0, 0 };
	Vec3 tangent = { 0, 0, 0 };
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;구조체에 Binormal값도 넣을 수 있지만 이미 normal값과 tangent값만 알면 외적을 통해 구할 수 있으므로 굳이 넣지 않아도 된다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 구조체를 사용하도록 모두 수정해야한다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ResourceManager에서 메쉬를 만드는데 메쉬가 들어있는 geometry정보도, 메쉬를 만들때 사용하는 GeometryHelper도 VertexTextureNormalData를 VertexTextureNormalTangentData를 사용하도록 바꿔준다. 아래는 큐브만 일부를 넣었다.&lt;/p&gt;
&lt;pre id=&quot;code_1726635945055&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// GeometryHelper.cpp
// ...
void GeometryHelper::CreateCube(shared_ptr&amp;lt;Geometry&amp;lt;VertexTextureNormalTangentData&amp;gt;&amp;gt; geometry)
{
	float w2 = 0.5f;
	float h2 = 0.5f;
	float d2 = 0.5f;

	vector&amp;lt;VertexTextureNormalTangentData&amp;gt; vtx(24);

	// 앞면
	vtx[0] = VertexTextureNormalTangentData(Vec3(-w2, -h2, -d2), Vec2(0.0f, 1.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
	vtx[1] = VertexTextureNormalTangentData(Vec3(-w2, +h2, -d2), Vec2(0.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
	vtx[2] = VertexTextureNormalTangentData(Vec3(+w2, +h2, -d2), Vec2(1.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
	vtx[3] = VertexTextureNormalTangentData(Vec3(+w2, -h2, -d2), Vec2(1.0f, 1.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
	// 뒷면
	vtx[4] = VertexTextureNormalTangentData(Vec3(-w2, -h2, +d2), Vec2(1.0f, 1.0f), Vec3(0.0f, 0.0f, 1.0f), Vec3(-1.0f, 0.0f, 0.0f));
	vtx[5] = VertexTextureNormalTangentData(Vec3(+w2, -h2, +d2), Vec2(0.0f, 1.0f), Vec3(0.0f, 0.0f, 1.0f), Vec3(-1.0f, 0.0f, 0.0f));
	vtx[6] = VertexTextureNormalTangentData(Vec3(+w2, +h2, +d2), Vec2(0.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f), Vec3(-1.0f, 0.0f, 0.0f));
	vtx[7] = VertexTextureNormalTangentData(Vec3(-w2, +h2, +d2), Vec2(1.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f), Vec3(-1.0f, 0.0f, 0.0f));
	// 윗면
	vtx[8] = VertexTextureNormalTangentData(Vec3(-w2, +h2, -d2), Vec2(0.0f, 1.0f), Vec3(0.0f, 1.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f));
	vtx[9] = VertexTextureNormalTangentData(Vec3(-w2, +h2, +d2), Vec2(0.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f));
	vtx[10] = VertexTextureNormalTangentData(Vec3(+w2, +h2, +d2), Vec2(1.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f));
	vtx[11] = VertexTextureNormalTangentData(Vec3(+w2, +h2, -d2), Vec2(1.0f, 1.0f), Vec3(0.0f, 1.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f));
	// 아랫면
	vtx[12] = VertexTextureNormalTangentData(Vec3(-w2, -h2, -d2), Vec2(1.0f, 1.0f), Vec3(0.0f, -1.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f));
	vtx[13] = VertexTextureNormalTangentData(Vec3(+w2, -h2, -d2), Vec2(0.0f, 1.0f), Vec3(0.0f, -1.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f));
	vtx[14] = VertexTextureNormalTangentData(Vec3(+w2, -h2, +d2), Vec2(0.0f, 0.0f), Vec3(0.0f, -1.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f));
	vtx[15] = VertexTextureNormalTangentData(Vec3(-w2, -h2, +d2), Vec2(1.0f, 0.0f), Vec3(0.0f, -1.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f));
	// 왼쪽면
	vtx[16] = VertexTextureNormalTangentData(Vec3(-w2, -h2, +d2), Vec2(0.0f, 1.0f), Vec3(-1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f));
	vtx[17] = VertexTextureNormalTangentData(Vec3(-w2, +h2, +d2), Vec2(0.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f));
	vtx[18] = VertexTextureNormalTangentData(Vec3(-w2, +h2, -d2), Vec2(1.0f, 0.0f), Vec3(-1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f));
	vtx[19] = VertexTextureNormalTangentData(Vec3(-w2, -h2, -d2), Vec2(1.0f, 1.0f), Vec3(-1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f));
	// 오른쪽면
	vtx[20] = VertexTextureNormalTangentData(Vec3(+w2, -h2, -d2), Vec2(0.0f, 1.0f), Vec3(1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f));
	vtx[21] = VertexTextureNormalTangentData(Vec3(+w2, +h2, -d2), Vec2(0.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f));
	vtx[22] = VertexTextureNormalTangentData(Vec3(+w2, +h2, +d2), Vec2(1.0f, 0.0f), Vec3(1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f));
	vtx[23] = VertexTextureNormalTangentData(Vec3(+w2, -h2, +d2), Vec2(1.0f, 1.0f), Vec3(1.0f, 0.0f, 0.0f), Vec3(0.0f, 0.0f, 1.0f));

	geometry-&amp;gt;SetVertices(vtx);

	vector&amp;lt;uint32&amp;gt; idx(36);

	// 앞면
	idx[0] = 0; idx[1] = 1; idx[2] = 2;
	idx[3] = 0; idx[4] = 2; idx[5] = 3;
	// 뒷면
	idx[6] = 4; idx[7] = 5; idx[8] = 6;
	idx[9] = 4; idx[10] = 6; idx[11] = 7;
	// 윗면
	idx[12] = 8; idx[13] = 9; idx[14] = 10;
	idx[15] = 8; idx[16] = 10; idx[17] = 11;
	// 아랫면
	idx[18] = 12; idx[19] = 13; idx[20] = 14;
	idx[21] = 12; idx[22] = 14; idx[23] = 15;
	// 왼쪽면
	idx[24] = 16; idx[25] = 17; idx[26] = 18;
	idx[27] = 16; idx[28] = 18; idx[29] = 19;
	// 오른쪽면
	idx[30] = 20; idx[31] = 21; idx[32] = 22;
	idx[33] = 20; idx[34] = 22; idx[35] = 23;

	geometry-&amp;gt;SetIndices(idx);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;GeometryHelper에 VertexTextureNormalTanentData로 바꿔줄 때 tangent값은 계산한 값이나 기본값으로 일단 넣어줬다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또 이 정점 구조체 형식에 맞게 쉐이더도 수정해줘야한다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Client프로젝트의 00.Global.fx에도 새로운 구조체&lt;/p&gt;
&lt;pre class=&quot;scss&quot; style=&quot;background-color: #f6f8fa; color: #24292f; text-align: start;&quot;&gt;&lt;code&gt;struct VertexTextureNormalTangent
{
	float4 position : POSITION;
	float2 uv : TEXCOORD;
	float3 normal : NORMAL;
	float3 tangent : TANGENT;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;를 똑같이 추가해준다. 그래야 GeometryHelper로 만든 구조체가 쉐이더에도 똑같은 형태로 만들어진다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또 MeshOutput도 아래와 같은 형식으로 바꿔주었다.&lt;/p&gt;
&lt;pre class=&quot;scss&quot; style=&quot;background-color: #f6f8fa; color: #24292f; text-align: start;&quot;&gt;&lt;code&gt;struct MeshOutput
{
	float4 position : SV_POSITION;
	float3 worldPosition : POSITION1;
	float2 uv : TEXCOORD;
	float3 normal : NORMAL;
	float3 tangent : TANGENT;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;우선 노멀맵핑을 위한 탄젠트값 추가는 다 준비되었다. 이제 이를 활용해보자.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;13.Lighting.fx를 복붙해서 14.NormalMappin.fx를 Client프로젝트의 Shaders필터에 넣었고 또 17.MaterialDemo.h,cpp파일을 복붙해서 18.NormalMappingDemo 파일들을 만들었다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 쓸 쉐이더 파일도 방금 만든 파일로 대체해줬다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;14.NormalMapping.fx 쉐이더에서 수정할 것은 VS의 input 타입과 VS에서 세팅해줄 MeshOutput에 tangent 타입을 넣어주는 것이다. output.tangent값은 로컬좌표계에서의 탄젠트 좌표정보를 월드좌표계변환 행렬에 곱해준 값을 넣어준다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;716&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSiIyh/btsJElGasN0/SLddFl4B8TeIHmdUNB3Bpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSiIyh/btsJElGasN0/SLddFl4B8TeIHmdUNB3Bpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSiIyh/btsJElGasN0/SLddFl4B8TeIHmdUNB3Bpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSiIyh%2FbtsJElGasN0%2FSLddFl4B8TeIHmdUNB3Bpk%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;1512&quot; height=&quot;716&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;716&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 픽셀쉐이더에는 월드좌표기준의 노멀과 탄젠트값이 MeshOutput에 들어와서 input으로 넘어온다. 이 값들을 가지고 픽셀쉐이더에 노멀 맵핑과 관련된 함수를 사용하도록 추가해준다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;공용으로 사용할것이므로 00.Light.fx에 만들어서 가져다 쓰자. ComputeNormalMapping이라는 이름으로 input.normal, input.tangent, input.uv 세가지를 넣으면 normal값을 이용해 새로운 노멀값을 구해 다시 전해줄 것이기 때문에 inout키워드를 넣어주었다. (C++에서 &amp;amp;,*같은 역할)&lt;/p&gt;
&lt;pre id=&quot;code_1726636563120&quot; class=&quot;c++ arduino&quot; data-ke-language=&quot;c++&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 00.Light.fx
// ...
void ComputeNormalMapping(inout float3 normal, float3 tangent, float2 uv)
{
	// [0,255] 범위에서 [0,1]로 변환
	float4 map = NormalMap.Sample(LinearSampler, uv);
	if (any(map.rgb) == false)
		return;

	float3 N = normalize(normal); // z
	float3 T = normalize(tangent); // x
	float3 B = normalize(cross(N, T)); // y
	float3x3 TBN = float3x3(T, B, N); // TS -&amp;gt; WS

	// [0,1] 범위에서 [-1,1] 범위로 변환
	float3 tangentSpaceNormal = (map.rgb * 2.0f - 1.0f);
	float3 worldNormal = mul(tangentSpaceNormal, TBN);

	normal = worldNormal;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 제일 먼저 하는일은 미리 읽어들였던 NormalM ap에서 uv좌표에 있는 픽셀을 리니어 샘플링하는 것 이다. 그리고 그 값이 r,g,b값을 체크해서 값이 아무 것도 없으면 사용할 정보가 없다는 뜻이므로 바로 리턴하게 해줬다.(any는 rgb값중 하나라도 0이 아닌 값이 있으면 true를 반환하고 아니면 false를 반환하 는 함수이다.) &lt;br /&gt;그 다음으로 할 일은 혹시 몰라 normal, tangent값 을 정규화하여 이 둘을 이용해 Binormal값을 외적하여 구하고&lt;span style=&quot;background-color: #c1bef9;&quot;&gt; 첫행을 T,둘째행을 B, 셋째행을 N으로 하 는 3*3행렬을 만든다. 이 변환행렬이 탄젠트스페이스에서 월드스페이스로 변환해주는 변환행렬&lt;/span&gt;이 된다. 위의 NormalMap에서 샘플링을 통해 0~255값을 가지던 픽셀값이 0~1사이의 값으로 변환됐다면 다시 이를 -1~1사이의 값으로 변환해야한다. 그냥 단순히 *2를 한다음에 1을 해주면 된다. 이렇게 구한 tangentSpaceNormal값은 노멀맵에서 추출한 값이 -1~1 사이의 값을 갖도록 된 것이다. 그리고 이 값은 tang ent space에서의 값이므로 world좌표로 바꿔야하 는데 이를 TBN행렬에 곱하면 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f8f9fa; color: #24292f; text-align: start;&quot;&gt;=&amp;gt;이렇게 만든 ComputNormalMapping함수를 픽셀쉐이더에서 호출하고 나온 새로운 노멀값은 처음에 정점에 넣었던 input.normal값이 아니라 텍스쳐에 넣어준 픽셀 단위로 세팅해준 좌표를 uv맵을 통해서 가져온 값으로 바꿔줘 훨씬 정밀한 값을 갖게된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;18.NormalMappingDemo에서도 Init에서 Leather텍스쳐를 읽어와서 디퓨즈맵으로 설정해주고 노멀맵으로 쓸 텍스쳐도 읽어와서 SetNormalMap으로 넘겨줬다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;마지막으로 라이트의 방향을 오른쪽에서 안쪽으로 들어가도록 light.desc값을 1,0,1로 바꿔줬다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결국이 노멀맵을 적용했을 때 훨씬 더 생생하게 물체를 표현할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #24292f; text-align: start;&quot; 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/bbqaTq/btsJDqPfzGw/J9CcvWF1GxSYiS6s1fwEE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbqaTq/btsJDqPfzGw/J9CcvWF1GxSYiS6s1fwEE0/img.png&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;617&quot; data-is-animation=&quot;false&quot; style=&quot;width: 47.3734%; margin-right: 10px;&quot; data-widthpercent=&quot;47.93&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbqaTq/btsJDqPfzGw/J9CcvWF1GxSYiS6s1fwEE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbqaTq%2FbtsJDqPfzGw%2FJ9CcvWF1GxSYiS6s1fwEE0%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;789&quot; height=&quot;617&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D420w/btsJE2MRqVq/WfgpJQaGRZnl6kEWAEkCvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D420w/btsJE2MRqVq/WfgpJQaGRZnl6kEWAEkCvK/img.png&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;573&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;52.07&quot; style=&quot;width: 51.4638%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D420w/btsJE2MRqVq/WfgpJQaGRZnl6kEWAEkCvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD420w%2FbtsJE2MRqVq%2FWfgpJQaGRZnl6kEWAEkCvK%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;573&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;좌) 노멀맵 적용X, 우)노멀맵 적용 O&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>[DirectX11]</category>
      <author>럭키 </author>
      <guid isPermaLink="true">https://white-mouse.tistory.com/146</guid>
      <comments>https://white-mouse.tistory.com/146#entry146comment</comments>
      <pubDate>Wed, 18 Sep 2024 14:23:26 +0900</pubDate>
    </item>
  </channel>
</rss>