<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>수학 프로그래밍으로 세상 보이기</title>
    <link>https://mathmakeworld.tistory.com/</link>
    <description>수학, 물리, 프로그래밍 관련된 지식을 모아두는 블로그입니다.
확실하고, 쉽고, 실전적인 철학을 지향합니다.</description>
    <language>ko</language>
    <pubDate>Tue, 4 Aug 2026 21:47:48 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>merit nada</managingEditor>
    <image>
      <title>수학 프로그래밍으로 세상 보이기</title>
      <url>https://tistory1.daumcdn.net/tistory/3201426/attach/c99917e94184409a9958d531bb3470ad</url>
      <link>https://mathmakeworld.tistory.com</link>
    </image>
    <item>
      <title>UE5 차세대 머터리얼 Substrate 분석</title>
      <link>https://mathmakeworld.tistory.com/136</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번 시간에는 UE5의 차세대 머티리얼 표현 방식인 &lt;b&gt;Substrate&lt;/b&gt;에 대해 알아보겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;Substrate&lt;/b&gt;는 &lt;b&gt;UE5.2&lt;/b&gt;에서 &lt;b&gt;Experimental&lt;/b&gt; 기능으로 본격적으로 공개되었으며, &lt;b&gt;UE5.5&lt;/b&gt;에서는 &lt;b&gt;Beta&lt;/b&gt; 단계로 전환되었습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이후 &lt;b&gt;UE5.7&lt;/b&gt;에서 &lt;b&gt;Production Ready&lt;/b&gt; 단계에 도달해 새로 생성하는 프로젝트에서 &lt;b&gt;Substrate&lt;/b&gt;가 기본적으로 활성화됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 언리얼에 Substrate를 도입한 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 Substrate를 도입했는지 이해하려면 일단 기존에 Material 시스템이 재질을 어떤 식으로 표현했는지 알아야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 Material에는 &lt;b&gt;Shading Model&lt;/b&gt;이라는 개념이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Shading Model&lt;/b&gt;은 재질이 빛에 반응하는 방식을 정의한 모델로, &lt;b&gt;Default Lit, Unlit, Hair, Subsurface&lt;/b&gt; 등 여러 종류가 미리 정의되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 &lt;b&gt;Shading Model&lt;/b&gt;을 선택하느냐에 따라 사용할 수 있는 Material Input이 달라지고, 엔진 내부에서 수행되는 라이팅 계산 방식도 달라집니다.&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;Shading Model&lt;/b&gt;마다 라이팅 계산 방식이 다른 이유는 실제 세계에서도 재질에 따라 빛이 반사되고, 투과되고, 산란되는 방식이 서로 다르기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Material 시스템은 이러한 재질별 특성을 각각의 &lt;b&gt;Shading Model&lt;/b&gt;과 그에 대응하는 라이팅 계산식으로 표현합니다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 이미지는 &lt;b&gt;Shading Model&lt;/b&gt; 별로 라이팅을 계산하는 방식을 도식화한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Default Lit&lt;/b&gt;의 경우 &lt;b&gt;반사광&lt;/b&gt;을 계산하고, &lt;b&gt;Hair&lt;/b&gt;는 &lt;b&gt;반사광&lt;/b&gt; 뿐만 아니라, &lt;b&gt;투사광&lt;/b&gt;도 계산합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Subsurface&lt;/b&gt;에서는 내부에서 산란하고 다시 밖으로 나오는 &lt;b&gt;Subsurface Scattering&lt;/b&gt;도 계산합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1695&quot; data-origin-height=&quot;307&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQfUzI/dJMcajiCVh8/1Ua6sfB6QFuBlh2pMvDAK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQfUzI/dJMcajiCVh8/1Ua6sfB6QFuBlh2pMvDAK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQfUzI/dJMcajiCVh8/1Ua6sfB6QFuBlh2pMvDAK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQfUzI%2FdJMcajiCVh8%2F1Ua6sfB6QFuBlh2pMvDAK0%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;1695&quot; height=&quot;307&quot; data-origin-width=&quot;1695&quot; data-origin-height=&quot;307&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 이러한 방식만으로 세상에 존재하는 다양한 표면을 표현하기에는 한계가 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;대표적인 한계는 서로 다른 특성을 가진 &lt;b&gt;여러 재질&lt;/b&gt;이 &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;span&gt;Substrate를 설명할 때 자주 등장하는 예시가 바로&lt;b&gt; &quot;자동차의 도장 표면&quot;&lt;/b&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;410&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSYaaN/dJMcadWZ601/pGpEFeqnLKXT37JNZmSVm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSYaaN/dJMcadWZ601/pGpEFeqnLKXT37JNZmSVm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSYaaN/dJMcadWZ601/pGpEFeqnLKXT37JNZmSVm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSYaaN%2FdJMcadWZ601%2FpGpEFeqnLKXT37JNZmSVm1%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;410&quot; height=&quot;136&quot; data-origin-width=&quot;410&quot; data-origin-height=&quot;136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;자동차의 표면은 단순한 금속 하나로 이루어진 것이 아니라, 금속성 도장층 위에 투명한 코팅층이 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기존 Material 시스템에도 &lt;b&gt;Clear Coat&lt;/b&gt;라는 &lt;b&gt;Shading Model&lt;/b&gt;이 있기 때문에 이러한 표면을 어느 정도 표현할 수 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 &lt;b&gt;Clear Coat&lt;/b&gt;는 기본 재질 위에 얇은 투명 코팅층이 존재하는 구조를 표현하기 위해 미리 설계된 &lt;b&gt;Shading Model&lt;/b&gt;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서 이와 조금 다른 구조의 복합 재질을 만들고 싶다면, 기존의 고정된 &lt;b&gt;Shading Model&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;span&gt;또 다른 예로 &lt;b&gt;&quot;흙이 묻은 피부&quot;&lt;/b&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;445&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clrSHV/dJMcad3IrCD/aAMSi8UsARKhyEfXGM1pa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clrSHV/dJMcad3IrCD/aAMSi8UsARKhyEfXGM1pa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clrSHV/dJMcad3IrCD/aAMSi8UsARKhyEfXGM1pa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclrSHV%2FdJMcad3IrCD%2FaAMSi8UsARKhyEfXGM1pa1%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;445&quot; height=&quot;138&quot; data-origin-width=&quot;445&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;흙은 일반적인 불투명 표면의 특성에 가깝고, 피부는 빛이 표면 아래로 들어가 산란되는 Subsurface 특성을 가집니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기존 Material 시스템에서는 이러한 서로 다른 재질의 광학적 특성을 하나의 표면 안에서 자유롭게 조합하고 계층화하기가 어렵습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;물론 메시의 영역을 나눈 뒤 각각 다른 Material을 할당하는 방식으로 표현할 수도 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;하지만 이 경우 재질 표현을 바꾸기 위해 &lt;b&gt;Mesh&lt;/b&gt;나 &lt;b&gt;Material Slot&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;span&gt;Substrate의 핵심은 다양한 재질을 보다 일반화된 방식으로 표현하고 조합할 수 있도록 만든 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예를 들어 흙이 묻은 피부나 녹슨 금속처럼 서로 다른 재질의 특성이 섞여 있는 표면도, 사용자가 각각의 특성을 정의하고 원하는 방식으로 조합하여 표현할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이를 위해 Substrate에서는 &lt;b&gt;Slab&lt;/b&gt;이라는 개념을 사용합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Slab은 하나의 물질을 표현하는 기본 단위이며, &lt;b&gt;Material Graph&lt;/b&gt;에서 여러 개의 &lt;b&gt;Slab&lt;/b&gt;을 다양한 &lt;b&gt;Operator&lt;/b&gt;를 이용해 트리 구조로 조합할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;기본적인 &lt;b&gt;Slab&lt;/b&gt;은 크게 &lt;b&gt;Interface&lt;/b&gt;와 &lt;b&gt;Medium&lt;/b&gt;으로 구성됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;185&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK7nGG/dJMcaa66Ty1/bjUTdZWEjQsXrH0wzSmva0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK7nGG/dJMcaa66Ty1/bjUTdZWEjQsXrH0wzSmva0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK7nGG/dJMcaa66Ty1/bjUTdZWEjQsXrH0wzSmva0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK7nGG%2FdJMcaa66Ty1%2FbjUTdZWEjQsXrH0wzSmva0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;875&quot; height=&quot;185&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;185&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;b&gt;Interface&lt;/b&gt;는 빛이 물질의 표면과 상호작용하는 방식을 표현하고, &lt;b&gt;Medium&lt;/b&gt;은 빛이 물질 내부에서 산란되고, 투과되고, 흡수되는 방식을 표현합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;사용자는 이러한 특성을 조절하고 여러 &lt;b&gt;Slab&lt;/b&gt;을 조합함으로써, 기존처럼 미리 정의된 하나의 &lt;b&gt;Shading Model&lt;/b&gt;을 선택하는 방식에서 벗어나 보다 다양한 재질의 빛 반응을 구성할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. UE5에서 Substrate를 사용하는 법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UE5에서 Substrate를 사용하려면 &lt;b&gt;프로젝트 셋팅 -&amp;gt; 엔진 -&amp;gt; 렌더링&lt;/b&gt; 항목의 &lt;b&gt;서브스트레이트&lt;/b&gt; 부분을 수정해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 &lt;b&gt;&quot;서브스트레이트 머터리얼&quot;&lt;/b&gt;을 활성화 해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;&quot;서브스트레이트 GBuffer 포맷&quot;&lt;/b&gt;을 &lt;b&gt;Adaptive GBuffer&lt;/b&gt;로 설정해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Substrate에는 &lt;b&gt;Adaptive GBuffer&lt;/b&gt;와 &lt;b&gt;Blendable GBuffer&lt;/b&gt;라는 두 가지 GBuffer 포맷이 있는데 &lt;/span&gt;&lt;span&gt;복잡한 Slab 구조를 활용한 고급 재질 표현을 사용하려면 &lt;b&gt;Adaptive GBuffer&lt;/b&gt;를 사용하는 것이 좋습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;화면 캡처 2026-07-11 000418.png&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;277&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NS4fM/dJMcaaF2NMg/hH9kv8JUmaEsiPBh2wYHk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NS4fM/dJMcaaF2NMg/hH9kv8JUmaEsiPBh2wYHk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NS4fM/dJMcaaF2NMg/hH9kv8JUmaEsiPBh2wYHk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNS4fM%2FdJMcaaF2NMg%2FhH9kv8JUmaEsiPBh2wYHk1%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;787&quot; height=&quot;277&quot; data-filename=&quot;화면 캡처 2026-07-11 000418.png&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;277&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 셋팅을 변경하고 나서는 &lt;b&gt;Material Editor&lt;/b&gt;에서 &lt;b&gt;Substrate&lt;/b&gt; 관련 노드를 추가하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Substrate를 활용한 &lt;b&gt;Material&lt;/b&gt; 예시입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;763&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lkkE4/dJMcajbLOpW/We7NEnIIH1grFcf30rj031/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lkkE4/dJMcajbLOpW/We7NEnIIH1grFcf30rj031/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lkkE4/dJMcajbLOpW/We7NEnIIH1grFcf30rj031/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlkkE4%2FdJMcajbLOpW%2FWe7NEnIIH1grFcf30rj031%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;996&quot; height=&quot;763&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;763&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;121&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnQEYw/dJMcagGgAnT/VX9MLOtaeJvmma9mmnE2O1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnQEYw/dJMcagGgAnT/VX9MLOtaeJvmma9mmnE2O1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnQEYw/dJMcagGgAnT/VX9MLOtaeJvmma9mmnE2O1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnQEYw%2FdJMcagGgAnT%2FVX9MLOtaeJvmma9mmnE2O1%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;462&quot; height=&quot;121&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;121&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btgqFH/dJMcaaeWOmz/6huJkbclDkQnU2oEy3nJ1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btgqFH/dJMcaaeWOmz/6huJkbclDkQnU2oEy3nJ1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btgqFH/dJMcaaeWOmz/6huJkbclDkQnU2oEy3nJ1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtgqFH%2FdJMcaaeWOmz%2F6huJkbclDkQnU2oEy3nJ1K%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;951&quot; height=&quot;351&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;351&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ClearCoat&lt;/b&gt;를 위쪽에 쌓고, 아래쪽에는 &lt;b&gt;CheckerPattern&lt;/b&gt;으로 &lt;b&gt;BaseCoat&lt;/b&gt;와 &lt;b&gt;Flakes&lt;/b&gt;를 조합했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;BaseCoat&lt;/b&gt;와 &lt;b&gt;Flakes&lt;/b&gt;를 조합하는 부분은 &lt;b&gt;Substrate Horizontal Blend &lt;/b&gt;노드를 사용했으며, &lt;b&gt;ClearCoat&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;를 쌓는 부분은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;Substrate Vertical Layer&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;노드를 활용했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 계층은 &lt;b&gt;Slab Node&lt;/b&gt;를 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3개&lt;/b&gt;의 계층 중에서 &lt;b&gt;Flakes&lt;/b&gt;의&lt;b&gt; Slab Node&lt;/b&gt;를 예시로 들어서 보여드리겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;789&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eKEUCu/dJMcaaTxVaE/LEPktOxKUZsjRLz8YDNmcK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eKEUCu/dJMcaaTxVaE/LEPktOxKUZsjRLz8YDNmcK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eKEUCu/dJMcaaTxVaE/LEPktOxKUZsjRLz8YDNmcK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeKEUCu%2FdJMcaaTxVaE%2FLEPktOxKUZsjRLz8YDNmcK%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;1568&quot; height=&quot;789&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;789&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 형태로 구성되어 있고, 필요한 파라미터만 변수로 만들어 노출했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MaterialInstance&lt;/b&gt;에서는 각 파라미터를 아래와 같이 셋팅했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;917&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d41NZw/dJMcacjrN4i/vPWkEy62SBkeBiyrEJZOSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d41NZw/dJMcacjrN4i/vPWkEy62SBkeBiyrEJZOSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d41NZw/dJMcacjrN4i/vPWkEy62SBkeBiyrEJZOSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd41NZw%2FdJMcacjrN4i%2FvPWkEy62SBkeBiyrEJZOSk%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;917&quot; height=&quot;410&quot; data-origin-width=&quot;917&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. Substrate의 코드 구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Substrate&lt;/b&gt; 기능과 관련된 코드는 엔진 곳곳에 있지만, 이번 시간에는 간단하게 &lt;b&gt;BasePass&lt;/b&gt;와 &lt;b&gt;LightingPass&lt;/b&gt;에서 Substrate 분기가 어떻게 구현되었는지만 보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Substrate의 코드 구현을 알아보기에 앞서 기존 방식의 &lt;b&gt;BasePass&lt;/b&gt;와 &lt;b&gt;LightingPass&lt;/b&gt;를 간단하게 살펴보겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;기존 방식의 BasePass와 LightingPass&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU에서는 이 흐름을 통해서 &lt;b&gt;BasePass&lt;/b&gt;를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DrawCall을 하는 부분은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;FDeferredShadingRenderer::Render&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt; &lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;FDeferredShadingRenderer::RenderBasePass&lt;/b&gt; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr; &lt;br /&gt;&lt;b&gt;FDeferredShadingRenderer::RenderBasePassInternal&lt;/b&gt; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr; &lt;br /&gt;&lt;b&gt;GraphBuilder.AddDispatchPass&lt;/b&gt;를 통해 &lt;b&gt;FParallelMeshDrawCommandPass::Dispatch&lt;/b&gt; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr;&lt;br /&gt;&lt;b&gt;TGraphTask&amp;lt;FDrawVisibleMeshCommandsAnyThreadTask&amp;gt;::CreateTask&lt;/b&gt;를 통해서 &lt;b&gt;FDrawVisibleMeshCommandAnyThreadTask::DoTask&lt;/b&gt; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr; &lt;br /&gt;&lt;b&gt;FInstanceCullingContext::SubmitDrawCommand&lt;/b&gt; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr; &lt;br /&gt;&lt;b&gt;FMeshDrawCommand::SubmitDrawEnd&lt;/b&gt; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr; &lt;br /&gt;&lt;b&gt;RHICommandList&lt;/b&gt;의 &lt;b&gt;DrawIndexedPrimitive&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shader를 &lt;b&gt;선택&lt;/b&gt;하는 부분과 &lt;b&gt;바인딩&lt;/b&gt;하는 부분은 따로 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Mesh에 맞는 &lt;b&gt;BasePass Shader&lt;/b&gt;를 &lt;b&gt;선택&lt;/b&gt;하는 부분은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FBasePassMeshProcessor::Process&lt;/b&gt; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr;&lt;/span&gt; &lt;br /&gt;&lt;b&gt;GetBasePassShaders&lt;/b&gt;를 호출해서 &lt;b&gt;Shader&lt;/b&gt;를 &lt;b&gt;Lookup&lt;/b&gt; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr;&lt;/span&gt; &lt;br /&gt;&lt;b&gt;BuildMeshDrawCommands&lt;/b&gt;에서 &lt;b&gt;PipelineStateObject&lt;/b&gt;(VS/PS/래스터라이저/블렌드/뎁스스텐실) 객체를 만들고 &lt;b&gt;PipelineID&lt;/b&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;Shader를 &lt;b&gt;바인딩&lt;/b&gt;하는 부분은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FMeshDrawCommand::&lt;/b&gt; &lt;b&gt;SubmitDrawBegin&lt;/b&gt; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MeshDrawCommand&lt;/b&gt;의 &lt;b&gt;PipelineID&lt;/b&gt;로 &lt;b&gt;PipelineStateObject&lt;/b&gt;를 찾아서 &lt;b&gt;SetGraphicsPipelineStateCheckApply&lt;/b&gt; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RHICommandList의 SetGraphicsPipelineState&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;GPU에서는 &lt;b&gt;BasePassPixelShader.usf&lt;/b&gt;의 &lt;b&gt;FPixelShaderInOut_MainPS&lt;/b&gt; 함수를 실행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 초반에 &lt;b&gt;FMaterialPixelParameters&lt;/b&gt; 구조체와 &lt;b&gt;FPixelMaterialInputs&lt;/b&gt; 구조체를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FMaterialPixelParameters&lt;/b&gt;는 &lt;b&gt;GetMaterialPixelParameters&lt;/b&gt; 함수를 통해서 값이 채워지는데, &lt;b&gt;VF(VertexFactory)&lt;/b&gt; 마다 함수 내용이 다르게 정의되어 있기 때문에 채워지는 값은 &lt;b&gt;VF&lt;/b&gt;&amp;nbsp;종류에 따라 다르며 &lt;b&gt;VS에서 PS로 넘어간 보간 값&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;UV, Vertex Color&lt;/b&gt; 등 지오메트리에서 바로 나오는 값들을 채워줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GetMaterialPixelParameters&lt;/b&gt; 함수를 호출한 이후에 &lt;b&gt;CalcMaterialParametersEx&lt;/b&gt; 함수를 통해서 &lt;b&gt;View&lt;/b&gt;에 의존적인 나머지 값들도 채워줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FPixelMaterialInputs&lt;/b&gt;는 &lt;b&gt;Material Graph&lt;/b&gt;에 의해서 출력된 값이 저장된다고 보면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CalcPixelMaterialInputs&lt;/b&gt; 함수를 통해서 값이 채워지는데, 해당 함수는 아래 처럼 토큰으로 구성되어 있고, 머터리얼 컴파일 타임 때 해당 토큰이 코드로 변환됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784008425184&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void CalcPixelMaterialInputs(in out FMaterialPixelParameters Parameters, in out FPixelMaterialInputs PixelMaterialInputs)
{

	...

	%{calc_pixel_material_inputs_initial_calculations}

	...

	%{calc_pixel_material_inputs_normal}

	...

	%{calc_pixel_material_inputs_other_inputs}

	...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;%{calc_pixel_material_inputs_other_inputs}&lt;/b&gt; 부분이 &lt;b&gt;Material Graph&lt;/b&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;PixelMaterialInputs.BaseColor = MaterialFloat3(0.00000000f,0.00000000f,0.00000000f);&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 같은 내용으로 &lt;b&gt;Material Node&lt;/b&gt;들을 &lt;b&gt;HLSL&lt;/b&gt; 코드로 치환해서&amp;nbsp;&lt;b&gt;PixelMaterialInputs&lt;/b&gt; 구조체에 값을 넘기는 코드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FMaterialPixelParameters(VS로부터 넘어온 값)&lt;/b&gt;과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FPixelMaterialInputs(Material Editor로부터 넘어온 값)&lt;/b&gt;이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전부 셋팅된 이후에는 &lt;b&gt;GBuffer&lt;/b&gt;에 필요한 값들을 넘기는 작업을 하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SetGBufferForShadingModel&lt;/b&gt;이라는 함수를 통해서 &lt;b&gt;FGBufferData&lt;/b&gt; 구조체에 값을 채웁니다.&lt;/p&gt;
&lt;pre id=&quot;code_1783997113786&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void SetGBufferForShadingModel(
	in out FGBufferData GBuffer, 
	in out FMaterialPixelParameters MaterialParameters,
	FPixelMaterialInputs PixelMaterialInputs,
	const float Opacity,
	const half3 BaseColor,
	...
	const uint ShadingModel)
{
	GBuffer.WorldNormal = MaterialParameters.WorldNormal;
	GBuffer.WorldTangent = MaterialParameters.WorldTangent;
	GBuffer.BaseColor = BaseColor;
	GBuffer.Metallic = Metallic;
	...
    
	if (ShadingModel == SHADINGMODELID_CLEAR_COAT)
	{
		float ClearCoat = saturate(GetMaterialCustomData0(PixelMaterialInputs));
		float ClearCoatRoughness = saturate(GetMaterialCustomData1(PixelMaterialInputs));
		GBuffer.CustomData.x = ClearCoat;
		GBuffer.CustomData.y = ClearCoatRoughness;
	}
	else if ...
	...
}&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;color: #000000;&quot;&gt;그 후 마지막 부분에서는 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;MRT(Multi Render Target)&lt;/b&gt;을&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; 사용해서 &lt;b&gt;FGBufferData&lt;/b&gt; 객체에서 값을 꺼내서 &lt;b&gt;Out.MRT[0], Out.MRT[1], Out.MRT[2]&lt;/b&gt; 에 값을 넘겨서 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Texture&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에 값을 씁니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1783997329426&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;float4 OutGBufferA = 0;
float4 OutGBufferB = 0;
float4 OutGBufferC = 0;

EncodeGBuffer(GBuffer, OutGBufferA, OutGBufferB, OutGBufferC, ...);

Out.MRT[1] = OutGBufferA;
Out.MRT[2] = OutGBufferB;
Out.MRT[3] = OutGBufferC;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;FGBufferData&lt;/b&gt; 구조체에 있던 값을 &lt;b&gt;RenderTarget&lt;/b&gt;으로 적절하게 &lt;b&gt;Packing&lt;/b&gt;해서 저장하는 과정을 &lt;b&gt;Encode&lt;/b&gt;라고 하고, &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;나중에 &lt;b&gt;Texture&lt;/b&gt;로부터 값을 읽어서 &lt;b&gt;FGBufferData&lt;/b&gt; 구조체에 값을 채우는 과정을 &lt;b&gt;Decode&lt;/b&gt;라고 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;EncodeGBuffer&lt;/b&gt; 함수는 아래와 같은 형태입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784093763335&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void EncodeGBuffer(
	FGBufferData GBuffer,
	out float4 OutGBufferA,
	out float4 OutGBufferB,
	out float4 OutGBufferC,
	...)
{
	float3 Normal = GBuffer.WorldNormal;
	uint   NormalFace = 0;
	EncodeNormal( Normal, NormalFace );

	OutGBufferA.rg = Normal.xy;
	OutGBufferA.b = 0;
	OutGBufferA.a = GBuffer.PerObjectGBufferData;

	OutGBufferB.r = GBuffer.Metallic;
	OutGBufferB.g = GBuffer.Specular;
	OutGBufferB.b = GBuffer.Roughness;
	OutGBufferB.a = EncodeShadingModelIdAndSelectiveOutputMask(GBuffer.ShadingModelID, GBuffer.SelectiveOutputMask);

	OutGBufferC.rgb = EncodeBaseColor( GBuffer.BaseColor );

	...
}&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;BasePass&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;GBuffer에 담긴 값들을 읽어서 처리하는 부분은 대표적으로 &lt;b&gt;LightingPass&lt;/b&gt;가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 이 흐름을 통해서 LightingPass 셰이더를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FDeferredShadingRenderer::Render&lt;/b&gt; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr; &lt;br /&gt;&lt;b&gt;FDeferredShadingRenderer::RenderLights&lt;/b&gt; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr; &lt;br /&gt;&lt;b&gt;RenderLight&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr;&lt;span&gt; &lt;br /&gt;&lt;b&gt;FDeferredLightPS&lt;/b&gt;&amp;nbsp; &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr; &lt;br /&gt;&lt;b&gt;DeferredLightPixelShaders.usf&lt;/b&gt;의 &lt;b&gt;DeferredLightPixelMain&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPU는 &lt;b&gt;DeferredLightPixelMain&lt;/b&gt; 함수 안에서 다음과 같은 흐름을 통해 GBuffer 값을 가져옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DeferredLightPixelMain &lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GetScreenSpaceData &lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GetGBufferData &lt;/b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&amp;rarr;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DecodeGBufferDataUV&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, &lt;b&gt;DecodeGBufferDataUV&lt;/b&gt; 함수는 코드 상으로 정의되어 있지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 이유는 &lt;b&gt;DecodeGBufferDataUV&lt;/b&gt; 함수는 코드로 만드는 함수이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ShaderGenerationUtil.cpp&lt;/b&gt; 파일의 &lt;b&gt;CreateGBufferDecodeFunctionVariation&lt;/b&gt; 함수에서 해당 함수를 만들어서 &lt;b&gt;AutogenShaderHeaders.ush&lt;/b&gt;라는 파일에 써두면 필요한 곳에서 해당 파일을 include해서 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 따로 만드는 이유는 프로젝트 셋팅마다 &lt;b&gt;GBuffer&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;color: #000000;&quot;&gt;&lt;b&gt;DecodeGBufferDataUV&lt;/b&gt; 함수와 해당 함수가 호출하는 &lt;b&gt;DecodeGBufferDataDirect&lt;/b&gt; 함수는 대략 아래와 같이 생겼습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784077133129&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// @param PixelPos relative to left top of the rendertarget (not viewport)
FGBufferData DecodeGBufferDataUV(float2 UV, bool bGetNormalizedNormal = true, bool bForceUnlitOrDefaultLit = false)
{
	...

	float4 InMRT1 = Texture2DSampleLevel(SceneTexturesStruct.GBufferATexture, SceneTexturesStruct_GBufferATextureSampler, UV, 0).xyzw;
	float4 InMRT2 = Texture2DSampleLevel(SceneTexturesStruct.GBufferBTexture, SceneTexturesStruct_GBufferBTextureSampler, UV, 0).xyzw;
	float4 InMRT3 = Texture2DSampleLevel(SceneTexturesStruct.GBufferCTexture, SceneTexturesStruct_GBufferCTextureSampler, UV, 0).xyzw;
	float4 InMRT4 = Texture2DSampleLevel(SceneTexturesStruct.GBufferDTexture, SceneTexturesStruct_GBufferDTextureSampler, UV, 0).xyzw;

	FGBufferData Ret = DecodeGBufferDataDirect(InMRT1,
		InMRT2,
		InMRT3,
		InMRT4,
		...);

	return Ret;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1783955020557&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FGBufferData  DecodeGBufferDataDirect(float4 InMRT1,
	float4 InMRT2,
	float4 InMRT3,
	float4 InMRT4,
	...)
{
	FGBufferData Ret = (FGBufferData)0;
    
	...
    
	WorldNormal_Compressed.x = InMRT1.x;
	WorldNormal_Compressed.y = InMRT1.y;
	WorldNormal_Compressed.z = InMRT1.z;
	Ret.PerObjectGBufferData.x = InMRT1.w;
	Ret.Metallic.x = InMRT2.x;
	Ret.Specular.x = InMRT2.y;
	Ret.Roughness.x = InMRT2.z;
	Ret.ShadingModelID.x = (((uint((float(InMRT2.w) * 255.0f) + .5f) &amp;gt;&amp;gt; 0) &amp;amp; 0x0f) &amp;lt;&amp;lt; 0);
	Ret.SelectiveOutputMask.x = (((uint((float(InMRT2.w) * 255.0f) + .5f) &amp;gt;&amp;gt; 4) &amp;amp; 0x0f) &amp;lt;&amp;lt; 0);
	Ret.BaseColor.x = InMRT3.x;
	Ret.BaseColor.y = InMRT3.y;
	Ret.BaseColor.z = InMRT3.z;
	Ret.GenericAO.x = InMRT3.w;
	Ret.CustomData.x = InMRT4.x;
	Ret.CustomData.y = InMRT4.y;
	Ret.CustomData.z = InMRT4.z;
	Ret.CustomData.w = InMRT4.w;
	
	...

	return Ret;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;보시다시피 &lt;b&gt;Texture&lt;/b&gt;에 써져 있는 값을 읽어서 리턴할 &lt;b&gt;FGBufferData&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;span style=&quot;color: #000000;&quot;&gt;이렇게 구한 &lt;b&gt;FGBufferData&lt;/b&gt;는 &lt;b&gt;Lighting&lt;/b&gt;을 할 때 유용하게 사용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;ShadingModels.ush&lt;/b&gt;를 보면 &lt;b&gt;IntegrateBxDF&lt;/b&gt; 함수가 있는데 대략 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784012502942&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FDirectLighting IntegrateBxDF(FGBufferData GBuffer, half3 N, half3 V, FAreaLight AreaLight, FShadowTerms Shadow)
{
	switch( GBuffer.ShadingModelID )
	{
		case SHADINGMODELID_DEFAULT_LIT:
			return DefaultLitBxDF(GBuffer, N, V, AreaLight, Shadow);
		case SHADINGMODELID_SUBSURFACE:
			return SubsurfaceBxDF(GBuffer, N, V, AreaLight, Shadow);
			...
		case SHADINGMODELID_EYE:
			return EyeBxDF(GBuffer, N, V, AreaLight, Shadow);
		default:
			return (FDirectLighting)0;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자로 넘어온 &lt;b&gt;GBuffer&lt;/b&gt; 값에서 &lt;b&gt;ShadingModelID&lt;/b&gt; 값을 가져와 적절한 &lt;b&gt;BxDF&lt;/b&gt; 함수를 고르고, &lt;b&gt;GBuffer&lt;/b&gt; 값을 넘겨줘서 &lt;b&gt;N&lt;/b&gt;과&amp;nbsp;&lt;b&gt;V&lt;/b&gt;에 따라 &lt;b&gt;Lighting&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;color: #000000;&quot;&gt;&lt;b&gt;DefaultLitBxDF&lt;/b&gt; 등의 함수에서는 실제 라이팅 연산을 해서 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;픽셀의 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Diffuse, Specular &lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;등을 계산해서 &lt;b&gt;FDirectLighting&lt;/b&gt; 구조체에 담아 리턴하고 이 구조체는 나중에 &lt;b&gt;SceneColor&lt;/b&gt;에 값을 쓰는데 사용됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 &lt;b&gt;LightingPass&lt;/b&gt;는 끝이 나게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Substrate의 BasePass와 LightingPass&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;본격적으로 Substrate의 동작 방식에 대해서 알아보기 전에 먼저 용어부터 알아봅시다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Slab&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;vs&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Closure&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Slab&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;은 Material Graph에서 하나의 BSDF 레이어를 뜻하는 노드입니다. 위 예제에서 본 그 Node입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Closure&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;는 그 Slab들이 Substrate Tree를 거쳐서 런타임에 실제로 평가 가능한 형태로 합쳐진 최종 셰이딩 단위입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하나의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Slab&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;은 하나의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Closure&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;로 대응되기도 하지만 아니기도 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;픽셀 당 Closure 개수가 Material이 Simple/Single/Comples로 분류되는 기준이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Adaptive &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;GBuffer&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; vs &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Blendable&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;GBuffer&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Adaptive &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;GBuffer는&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;GBuffer&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의 각 픽셀에 할당된 데이터가 상황에 맞게 늘어나거나 줄어들 수 있는 형태입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Blendable&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;GBuffer는 &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;GBuffer&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의 각 픽셀에 할당된 데이터가 고정된, 기존 방식과 비슷한 형태입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Substrate의 모든 기능을 활용하려면 Adaptive GBuffer를 사용하는게 좋습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다만, 추가적인 로직이 들어가기 때문에 비교적 무거운 연산이 들어가게 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Evaluation Blending&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt; vs Parameter Blending&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;Evaluation Blending은 각 Slab을 따로 라이팅 계산한 뒤 그 셰이딩 결과를 블렌딩하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;더 정확하지만 라이팅을 여러 번 평가해야 해서 비용이 더 듭니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Parameter Blending은 두 Slab의 파라미터(Albedo, Roughness, Normal 등)를 먼저 섞은 뒤 그 결과로 라이팅을 한 번만 계산하는 방식입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; letter-spacing: 0px;&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;Substrate&lt;/b&gt;를 사용했을 때의 &lt;b&gt;BasePass&lt;/b&gt;와 &lt;b&gt;LightingPass&lt;/b&gt;에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Substrate&lt;/b&gt;의 &lt;b&gt;BasePass&lt;/b&gt; 역시 기존의 &lt;b&gt;BasePass&lt;/b&gt;처럼 &lt;b&gt;Material Inputs&lt;/b&gt;을 받아서 &lt;b&gt;GBuffer&lt;/b&gt;에 값을 채우는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만,&lt;b&gt; Adaptive GBuffer&lt;/b&gt;를 사용하므로 한 픽셀에 여러 개의 &lt;b&gt;Closure&lt;/b&gt;가 담겨 있을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;b&gt;Material Editor&lt;/b&gt;에서 &lt;b&gt;Slab&lt;/b&gt;을 &lt;b&gt;Tree&lt;/b&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;Tree&lt;/b&gt; 구조를 어떻게 표현하는지 알아보기 위해 먼저 &lt;b&gt;Material&lt;/b&gt;의 &lt;b&gt;Slab&lt;/b&gt; 노드와 &lt;b&gt;VerticalLayering&lt;/b&gt; 노드가 어떻게 표현되는지 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FHLSLMaterialTranslator::SubstrateSlabBSDF&lt;/b&gt; 함수와&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FHLSLMaterialTranslator::SubstrateVertialLayering&lt;/b&gt; 함수를 보면 HLSL로 번역되는 내용을 볼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784035746365&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return AddCodeChunk(
	MCT_Substrate, TEXT(&quot;Parameters.%s.PromoteParameterBlendedBSDFToOperator(GetSubstrateSlabBSDF(Parameters.SubstratePixelFootprint,\n\
	/*Normal*/                           %s,\n\
	/*DiffuseAlbedo*/                    %s,\n\
	/*F0*/                               %s,\n\
	...&quot;),
	*GetParametersSubstrateTreeName(CurrentSubstrateCompilationContext),
	*SubstrateGetCastParameterCode(Normal,	MCT_Float3),
	*SubstrateGetCastParameterCode(DiffuseAlbedo,	MCT_Float3),
	*SubstrateGetCastParameterCode(F0,	MCT_Float3),
	...
);&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1784035929628&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;return AddCodeChunk(
	MCT_Substrate, TEXT(&quot;Parameters.%s.SubstrateVerticalLayering(%s, %s, %u, %u) /* Thickness = %s */&quot;),
	*GetParametersSubstrateTreeName(CurrentSubstrateCompilationContext),
	*GetParameterCode(Top),
	*GetParameterCode(Base),
	OperatorIndex,
	MaxDistanceFromLeaves,
	*ThicknessCode
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성된 코드는 &lt;b&gt;Legacy&lt;/b&gt; 방식에서 봤던 &lt;b&gt;CalcPixelMaterialInputs&lt;/b&gt; 함수의 &lt;b&gt;%{calc_pixel_material_inputs_other_inputs}&lt;/b&gt; 토큰을 치환하는 코드의 재료로 쓰이게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰 전체가 치환된 코드는 대략 아래와 같이 구성됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784036830225&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FSubstrateData Local3 = Parameters.SubstrateTree.PromoteParameterBlendedBSDFToOperator(GetSubstrateSlabBSDF(Parameters.SubstratePixelFootprint,
	/*Normal*/                           DERIV_BASE_VALUE(Local1),
	/*DiffuseAlbedo*/                    Material.PreshaderBuffer[3].xyz,
	/*F0*/                               Material.PreshaderBuffer[2].z,
	...
	Parameters.SharedLocalBases.Types)
	/* Normal = DERIV_BASE_VALUE(Local1) ; Tangent = NONE ; Thickness = Material.PreshaderBuffer[2].x */, 1, 0, 0, 0);
FSubstrateData Local6 = Parameters.SubstrateTree.PromoteParameterBlendedBSDFToOperator(GetSubstrateSlabBSDF(Parameters.SubstratePixelFootprint,...);
...
FSubstrateData Local18 = Parameters.SubstrateTree.PromoteParameterBlendedBSDFToOperator(GetSubstrateSlabBSDF(Parameters.SubstratePixelFootprint,...);
...
FSubstrateData Local33 = Parameters.SubstrateTree.SubstrateHorizontalMixing(Local6, Local18, Local32, 2, 1);
FSubstrateData Local34 = Parameters.SubstrateTree.SubstrateVerticalLayering(Local3, Local33, 0, 2) /* Thickness = Material.PreshaderBuffer[2].x */;
...
PixelMaterialInputs.FrontMaterial = Local34;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드상으로 &lt;b&gt;Tree&lt;/b&gt; 구조가 생성된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 도식화하면 아래와 같은 형태로 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1119&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/on79R/dJMcaixjoi1/5YknNrquyFeewzESMVIy2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/on79R/dJMcaixjoi1/5YknNrquyFeewzESMVIy2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/on79R/dJMcaixjoi1/5YknNrquyFeewzESMVIy2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fon79R%2FdJMcaixjoi1%2F5YknNrquyFeewzESMVIy2K%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;1119&quot; height=&quot;285&quot; data-origin-width=&quot;1119&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에 보이는 &lt;b&gt;Parameters&lt;/b&gt; 변수는 &lt;b&gt;Legacy&lt;/b&gt; 방식에서 봤던 &lt;b&gt;FMaterialPixelParameters&lt;/b&gt; 구조체입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 &lt;b&gt;FSubstrateTree&lt;/b&gt; 타입의 &lt;b&gt;SubstrateTree&lt;/b&gt; 변수를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FSubstrateTree&lt;/b&gt; 구조체는 대략적으로 아래와 같이 구성되어 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784084302651&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct FSubstrateTree
{
	int BSDFCount;
	uint OperatorCount;

	...

	FSubstrateBSDF BSDFs[SUBSTRATE_MAX_CLOSURE_COUNT];
	FSubstrateOperator Operators[SUBSTRATE_MAX_OPERATOR_COUNT];

	...

	FSubstrateData SubstrateHorizontalMixing(FSubstrateData Background, FSubstrateData Foreground, float Mix, int OperatorIndex, uint MaxDistanceFromLeaves);
	FSubstrateData SubstrateVerticalLayering(FSubstrateData Top, FSubstrateData Base, int OperatorIndex, uint MaxDistanceFromLeaves);
	FSubstrateData PromoteParameterBlendedBSDFToOperator(FSubstrateData SubstrateData, int OperatorIndex, int BSDFIndex, int LayerDepth, int bIsBottom);
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 사용한 &lt;b&gt;PromoteParameterBlendedBSDFToOperator&lt;/b&gt; 함수나, &lt;b&gt;SubstrateHorizontalMixing&lt;/b&gt; 함수는 대략적으로 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784084489156&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FSubstrateData FSubstrateTree::PromoteParameterBlendedBSDFToOperator(FSubstrateData SubstrateData, int OperatorIndex, int BSDFIndex, int LayerDepth, int bIsBottom)
{
	BSDFCount++;
	OperatorCount++;

	BSDFs[BSDFIndex] = SubstrateData.InlinedBSDF;
	
	Operators[OperatorIndex]	= GetInitialisedSubstrateOperator();
	...

	BSDFs[BSDFIndex].OperatorIndex	= OperatorIndex;
	...

	return SubstrateData;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1784084501700&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FSubstrateData FSubstrateTree::SubstrateHorizontalMixing(FSubstrateData Background, FSubstrateData Foreground, float Mix, int OperatorIndex, uint MaxDistanceFromLeaves)
{
	...

	FSubstrateData SubstrateData = GetInitialisedSubstrateData();
	SubstrateData.OperatorIndex = OperatorIndex;

	OperatorCount++;
	Operators[OperatorIndex]						= GetInitialisedSubstrateOperator();
	Operators[OperatorIndex].MaxDistanceFromLeaves	= MaxDistanceFromLeaves;
	Operators[OperatorIndex].Type					= SUBSTRATE_OPERATOR_HORIZONTAL;
	Operators[OperatorIndex].Weight					= Mix;
	Operators[OperatorIndex].LeftIndex				= Background.OperatorIndex;
	Operators[OperatorIndex].RightIndex				= Foreground.OperatorIndex;

	Operators[Background.OperatorIndex].ParentIndex	= OperatorIndex;
	Operators[Foreground.OperatorIndex].ParentIndex	= OperatorIndex;

	return SubstrateData;
}&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;BSDFs&lt;/b&gt; 배열과 &lt;b&gt;Operators&lt;/b&gt; 배열에 넘기는 작업을 하므로 &lt;b&gt;Tree&lt;/b&gt; 구조를 배열에 담는 역할을 한다고 보시면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음으로 해야 할 일은 이렇게 &lt;b&gt;Tree&lt;/b&gt; 구조를 내포하는 배열 데이터를 &lt;b&gt;Bit-Stream&lt;/b&gt;으로 &lt;b&gt;Packing&lt;/b&gt;하여 &lt;b&gt;LightingPass&lt;/b&gt;에 넘겨줄 수 있도록 하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SubstrateExport.ush&lt;/b&gt; 파일의 &lt;b&gt;PackSubstrateOut&lt;/b&gt; 함수를 사용합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784093923776&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void PackSubstrateOut(
	inout FRWSubstrateMaterialContainer SubstrateBuffer,
	RWTexture2DArray&amp;lt;uint&amp;gt; ExtraMaterialDataUAV,
	...
	FSubstratePixelHeader SubstratePixelHeader,
	FSubstrateData Substrate,
	...
)
{
	...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력으로 받은 &lt;b&gt;Operators&lt;/b&gt; 배열을 순회하여 &lt;b&gt;Operator Type&lt;/b&gt;에 따라 필요한 정보를 뽑아내고 &lt;b&gt;BSDFs&lt;/b&gt; 배열을 순회하며 &lt;b&gt;BSDF&lt;/b&gt; 타입 별로&amp;nbsp;&lt;b&gt;Buffer&lt;/b&gt;에 값을 채워 넣는 로직이 있는데 코드가 복잡하여 자세한 내용을 생략하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로는&amp;nbsp;나중에 LightingPass에서 사용할 &lt;b&gt;FRWSubstrateMaterialContainer&lt;/b&gt;와, &lt;b&gt;RWTexture2DArray&amp;lt;uint&amp;gt;&lt;/b&gt; 타입의 &lt;b&gt;Buffer&lt;/b&gt;에 값들을 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LightingPass&lt;/b&gt;에서는 &lt;b&gt;Packing&lt;/b&gt;된 &lt;b&gt;Bit-Stream&lt;/b&gt; 데이터를 읽어와서 원래의 의도대로 데이터를 해석해서 &lt;b&gt;Lighting&lt;/b&gt;을 합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Lighting&lt;/b&gt; 자체는 기존 방식이랑 비슷합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다른 점이 있다면 &lt;b&gt;Legacy&lt;/b&gt; 방식에서는 현재 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Pixel&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;의 &lt;b&gt;ShadingModel&lt;/b&gt;에 따라 알맞는&amp;nbsp;&lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;BxDF&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 구해서 바로 사용했다면,&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Substrate&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;에서는 한 픽셀에 여러 개의 &lt;b&gt;Closure&lt;/b&gt;가 있을 수 있으므로 데이터&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Unpack&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하고 &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Closure&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;를 순회해서 계산해서 &lt;b&gt;BSDF&lt;/b&gt;를 계산하고 조합합니다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;실제 &lt;b&gt;Lighting&lt;/b&gt; 루프는 &lt;b&gt;SubstrateDeferredLighting.ush&lt;/b&gt; 파일의 &lt;b&gt;SubstrateDeferredLighting&lt;/b&gt; 함수에 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;대략적으로 중요한 부분만 가져오면 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1784017059520&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FLighting SubstrateDeferredLighting(
    FLight Light, 
    float3 V, 
    float3 L, 
    ...
    FSubstratePixelHeader Header,
    FSubstrateMaterialContainer MaterialBuffer)
{
    FSubstrateDeferredLighting Lighting = 0;

    for (uint i = 0; i &amp;lt; Header.ClosureCount; ++i)
    {
        FSubstrateBSDF BSDF = Unpack(MaterialBuffer, i);
        FSubstrateBSDFContext Context = CreateBSDFContext(BSDF, V, L);

        FSubstrateEvaluateResult Result = EvaluateBSDF(Context, Light);

        // Tree가 구워둔 합성 가중치를 가져옴
        float3 Weight = LuminanceWeight(Context, BSDF);
        Lighting = Accumulate(Lighting, Result * Weight, IsTopLayer(BSDF));
    }

    return Lighting;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FSubstrateMaterialContainer&lt;/b&gt;는 해당 픽셀의 &lt;b&gt;Substrate&lt;/b&gt; 정보를 담은 &lt;b&gt;bitstream&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RWTexture2DArray&amp;lt;uint&amp;gt;&lt;/b&gt;로 &lt;b&gt;define&lt;/b&gt;되어 있어서 실질적으로는 &lt;b&gt;RWTexture2DArray&amp;lt;uint&amp;gt;&lt;/b&gt;로 볼 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FSubstrateBSDF&lt;/b&gt;는 현재 &lt;b&gt;Closure&lt;/b&gt;의 재질 정보이고, &lt;b&gt;FSubstrateBSDFContext&lt;/b&gt;는 &lt;b&gt;FSubstrateBSDF&lt;/b&gt; 외에 추가적으로 표면 자체의&amp;nbsp;정보를 담고 있는 구조체입니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Context&lt;/b&gt;와 &lt;b&gt;Light&lt;/b&gt; 정보를 이용해서 &lt;b&gt;현재 픽셀, 현재 Closure, 현재 Light&lt;/b&gt;에 대한 &lt;b&gt;BSDF 결과&lt;/b&gt;를 담고 이것을 모든 &lt;b&gt;Closure&lt;/b&gt;에 대해서 계산해서 합성하는 방식으로 &lt;b&gt;Lighting&lt;/b&gt;이 진행되게 됩니다.&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 알아본 것과 같이 &lt;b&gt;Substrate&lt;/b&gt;의 가장 큰 장점은 기존의 &lt;b&gt;고정된 Shading Model&lt;/b&gt; 방식에서 벗어나, 여러 재질의 특성을 자유롭게 조합해 &lt;b&gt;다양한 표면&lt;/b&gt;을 표현할 수 있다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;다만 새로운 개념이 많아 &lt;b&gt;학습 난이도&lt;/b&gt;가 높고, 복잡하게 구성할수록 &lt;b&gt;성능 비용&lt;/b&gt;도 커질 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;따라서 &lt;b&gt;표현력&lt;/b&gt;과 &lt;b&gt;최적화&lt;/b&gt; 사이의 균형을 고려해 사용하는 것이 중요합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; 앞으로 &lt;b&gt;Substrate&lt;/b&gt;가 더욱 안정화되고 최적화된다면, 적극적으로 사용해보는 것도 좋을 것 같습니다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>프로그래밍/Unreal</category>
      <category>Adaptive GBuffer</category>
      <category>Blendable GBuffer</category>
      <category>GBuffer</category>
      <category>Material</category>
      <category>substrate</category>
      <category>UE5</category>
      <category>UE5.7</category>
      <category>머터리얼</category>
      <category>써브스트레이트</category>
      <category>재질 표현</category>
      <author>merit nada</author>
      <guid isPermaLink="true">https://mathmakeworld.tistory.com/136</guid>
      <comments>https://mathmakeworld.tistory.com/136#entry136comment</comments>
      <pubDate>Sun, 28 Jun 2026 18:14:23 +0900</pubDate>
    </item>
    <item>
      <title>DirectX12로 RayTracing Shader 사용하기</title>
      <link>https://mathmakeworld.tistory.com/133</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 시간에는 &lt;b&gt;RayTracing Shader&lt;/b&gt;를 사용해서 &lt;b&gt;RayTracing&lt;/b&gt;을 구현하는 예시를 보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 결과 화면은 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1269&quot; data-origin-height=&quot;713&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQvnoM/dJMcahDEmgQ/kPYb1O2kYaekKWjixGugc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQvnoM/dJMcahDEmgQ/kPYb1O2kYaekKWjixGugc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQvnoM/dJMcahDEmgQ/kPYb1O2kYaekKWjixGugc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQvnoM%2FdJMcahDEmgQ%2FkPYb1O2kYaekKWjixGugc0%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;1269&quot; height=&quot;713&quot; data-origin-width=&quot;1269&quot; data-origin-height=&quot;713&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;300&quot; data-start=&quot;60&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ray Tracing&lt;/b&gt;에는, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Forward Ray Tracing&lt;/b&gt;&lt;/span&gt;과 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Backward Ray Tracing&lt;/b&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;1024&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cH4WOO/dJMcabwFRUT/nZ726wkH0vpZPxc1mlWiqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cH4WOO/dJMcabwFRUT/nZ726wkH0vpZPxc1mlWiqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cH4WOO/dJMcabwFRUT/nZ726wkH0vpZPxc1mlWiqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcH4WOO%2FdJMcabwFRUT%2FnZ726wkH0vpZPxc1mlWiqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;500&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-end=&quot;300&quot; data-start=&quot;60&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Forward Ray Tracing&lt;/b&gt;은 &lt;b&gt;실제 빛이 광원에서 출발해 물체를 거쳐 눈에 들어오는 과정&lt;/b&gt;을 그대로 따라가는 것이고, &lt;b&gt;Backward Ray Tracing&lt;/b&gt;은 &lt;b&gt;카메라(혹은 눈)에서부터 반대로 광선을 추적하는 방식&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-end=&quot;300&quot; data-start=&quot;60&quot; data-ke-size=&quot;size16&quot;&gt;빛이 광원에서 출발해 퍼지는 경로는 이론상 무한이므로, 컴퓨터 그래픽스에서는 보통 &lt;b&gt;Forward Ray Tracing&lt;/b&gt; 대신&amp;nbsp;&lt;b&gt;Backward Ray Tracing&lt;/b&gt;을 사용합니다.&lt;br /&gt;&lt;b&gt;Backward Ray Tracing&lt;/b&gt;은 최종적으로 화면에서 보게 되는 픽셀을 기준으로, 그 픽셀에 도달했을 법한 광선의 경로를 거꾸로 따라가면서 색을 계산하는 방식이라고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;300&quot; data-start=&quot;60&quot; data-ke-size=&quot;size16&quot;&gt;이번 예제에서도 &lt;b&gt;Backward Ray Tracing&lt;/b&gt;을 사용했습니다.&lt;/p&gt;
&lt;p data-end=&quot;300&quot; data-start=&quot;60&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;592&quot; data-start=&quot;302&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 점은, &lt;b&gt;Ray Tracing은 빛의 이동 경로를 어떻게 추적할 것인가에 대한 기법&lt;/b&gt;이라는 것입니다.&lt;br /&gt;즉, 이것은 &lt;b&gt;광선이 어떤 경로로 진행되고, 어디에 부딪히고, 반사되거나 굴절되는지를 시뮬레이션하는 방법&lt;/b&gt;이지, &lt;b&gt;표면을 어떤 셰이딩 모델로 표현할지&lt;/b&gt;를 결정하는 개념은 아닙니다.&lt;br /&gt;그래서 Ray Tracing을 사용한다고 해서 반드시 특정 셰이딩 방식을 써야 하는 것은 아니며, &lt;b&gt;Phong Shading&lt;/b&gt;을 사용할 수도 있고 &lt;b&gt;PBR Shading&lt;/b&gt;을 사용할 수도 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;806&quot; data-start=&quot;594&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;806&quot; data-start=&quot;594&quot; data-ke-size=&quot;size16&quot;&gt;Ray Tracing의 큰 장점은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;반사(Reflection)&lt;/b&gt;&lt;/span&gt; 와 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;굴절(Refraction)&lt;/b&gt; &lt;/span&gt;같은 효과를 매우 자연스럽고 정확하게 표현할 수 있다는 점입니다.&lt;br /&gt;빛이 실제 공간 안에서 어떻게 튕기고 통과하는지를 기반으로 계산하기 때문에, 기존의 근사 방식보다 훨씬 설득력 있는 결과를 얻을 수 있습니다.&lt;br /&gt;다만 그만큼 &lt;b&gt;계산 비용이 크다&lt;/b&gt;는 단점도 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;806&quot; data-start=&quot;594&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1081&quot; data-start=&quot;808&quot; data-ke-size=&quot;size16&quot;&gt;또한 &lt;b&gt;Ray Tracing&lt;/b&gt; 자체는 꼭 전용 하드웨어에서만 가능한 것은 아닙니다.&lt;br /&gt;원리적으로는 &lt;b&gt;일반적인 Pixel Shader만으로도 구현&lt;/b&gt;할 수 있습니다.&lt;br /&gt;하지만 이 경우 광선과 기하 교차 판정이나 가속 구조 탐색 같은 연산을 모두 범용 셰이더로 처리해야 해서 비효율적일 수 있습니다.&lt;br /&gt;그래서 이러한 연산을 더 효율적으로 처리하고 &lt;b&gt;하드웨어 가속의 이점&lt;/b&gt;을 얻기 위해, 현대 GPU에서는 별도의 &lt;b&gt;Ray Tracing Shader&lt;/b&gt;와 전용 파이프라인이 도입되었습니다.&lt;/p&gt;
&lt;p data-end=&quot;1081&quot; data-start=&quot;808&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1081&quot; data-start=&quot;808&quot; data-ke-size=&quot;size16&quot;&gt;예제에서는 &lt;b&gt;DirectX12&lt;/b&gt;의 &lt;b&gt;Ray Tracing Pipeline&lt;/b&gt;을 이용해서 &lt;b&gt;Ray Tracing&lt;/b&gt;을 구현했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DXR(DirectX RayTracing) 파이프 라인 전체 구조&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) 먼저 CPU에서 &lt;span style=&quot;color: #ee2323;&quot;&gt;Ray Tracing Pipeline&lt;/span&gt;을 위한 요소들을 셋팅합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; Ray Tracing State Object, Top Level Acceleration Structure, Bottom Level Acceleration Structure, Shader Table, Root Signature&lt;/b&gt; 등 Pipeline에서 사용되는 데이터들을 준비해둡니다.&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) 모든 요소들이 셋팅된 이후에 &lt;span style=&quot;color: #ee2323;&quot;&gt;DispatchRays&lt;/span&gt; 함수를 호출합니다. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 호출은 일반적인 그래픽스 파이프라인에서 Draw를 호출하는 것과 비슷하게, &lt;b&gt;레이 트레이싱 작업을 시작하라&lt;/b&gt;는 명령이라고 보면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) DispatchRays 함수가 호출되면 GPU에서는 &lt;span style=&quot;color: #ee2323;&quot;&gt;Ray Generation Shader&lt;/span&gt;가 호출됩니다.&lt;/b&gt;&lt;br /&gt;이 셰이더는 말 그대로 &lt;b&gt;각 레이를 어떻게 만들 것인지 정의하는 셰이더&lt;/b&gt;입니다.&lt;br /&gt;예를 들어 현재 픽셀 위치를 기준으로 카메라 원점과 방향을 계산하고, 그 방향으로 광선을 하나 발사할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;788&quot; data-start=&quot;568&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4) 레이가 발사되면, GPU 하드웨어는 내부적으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;Acceleration Structure Traversal&lt;/span&gt;을 수행하고 결과에 따라 &lt;span style=&quot;color: #ee2323;&quot;&gt;Hit Shader&lt;/span&gt;나 &lt;span style=&quot;color: #ee2323;&quot;&gt;Miss Shader&lt;/span&gt;가 호출 됩니다.&lt;/b&gt;&lt;br /&gt;즉, 레이가 장면 안에서 어떤 오브젝트와 충돌할 가능성이 있는지 &lt;b&gt;가속 구조를 따라 내려가며 탐색&lt;/b&gt;합니다.&lt;br /&gt;이 과정은 DXR의 핵심 중 하나로, 모든 삼각형을 전부 검사하지 않고, &lt;b&gt;가능성이 있는 후보만 빠르게 좁혀서&lt;/b&gt; 교차 판정을 진행합니다.&lt;/p&gt;
&lt;p data-end=&quot;984&quot; data-start=&quot;790&quot; data-ke-size=&quot;size16&quot;&gt;그 결과 레이가 어떤 기하와 충돌하면 &lt;b&gt;Hit Shader 계열&lt;/b&gt;이 실행되고, 아무것도 맞지 않으면 &lt;b&gt;Miss Shader&lt;/b&gt;가 실행됩니다.&lt;/p&gt;
&lt;p data-end=&quot;984&quot; data-start=&quot;790&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;984&quot; data-start=&quot;790&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5) 최종적으로 계산된 결과는 &lt;span style=&quot;color: #ee2323;&quot;&gt;Render Target&lt;/span&gt;이나 &lt;span style=&quot;color: #ee2323;&quot;&gt;UAV(Unordered Access View)&lt;/span&gt; 에 기록하면 됩니다.&lt;/b&gt;&lt;br /&gt;즉, DXR 역시 결과적으로는 &amp;ldquo;각 픽셀에 어떤 색을 쓸지 계산해서 버퍼에 저장한다&amp;rdquo;는 점에서는 일반 렌더링과 같습니다.&lt;br /&gt;차이는 그 색을 계산하는 방법이 &lt;b&gt;Rasterization&lt;/b&gt; 기반이 아니라, &lt;b&gt;광선을 추적하는 방식&lt;/b&gt;이라는 점입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Ray Tracing Shader 3종 (Ray Generation Shader, Closest Hit Shader, Miss Shader)&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ray Tracing Shader 관련해서 자주 쓰이는 Shader 3종이 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1) RayGeneration Shader&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ray Tracing의 시작점입니다.&lt;br /&gt;각 픽셀 또는 각 작업 단위에 대해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;어떤 레이를 어디서 어떤 방향으로 쏠지&lt;/b&gt;를 결정합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;보통 카메라 위치를 원점으로 하고, 화면상의 픽셀 좌표를 역변환해서 방향 벡터를 계산한 뒤, TraceRay를 호출해서 충돌점을 찾습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2) Closest Hit Shader&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;레이가 어떤 기하와 충돌했을 때, 그중&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;가장 가까운 교차 지점&lt;/b&gt;에 대해 호출되는 셰이더입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;보통 이 셰이더에서 표면의 재질과 조명을 바탕으로 shading을 수행하고, 필요하면 반사/굴절 ray를 추가로 생성합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;참고로 hit 관련 셰이더에는 교차 후보를 걸러내는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Any Hit Shader&lt;/b&gt;, 절차적 기하의 교차를 직접 계산하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Intersection Shader&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;가 있으며, 이들은 함께&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Hit Group&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;으로 묶여 사용됩니다.&lt;br /&gt;본 예제에서는 가장 단순한&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Closest Hit Shader&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;만 사용합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3) Miss Shader&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;3767&quot; data-end=&quot;3796&quot;&gt;레이가 아무 오브젝트에도 맞지 않았을 때 호출됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot; data-start=&quot;3798&quot; data-end=&quot;3805&quot;&gt;여기서는 보통 배경색을 반환하거나, 환경 맵을 샘플링하는 작업을 하게 됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;BLAS(Bottom Level Acceleration Structure) &amp;amp; TLAS(Top Level Acceleration Structure)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ray Tracing에서 가장 중요한 연산 중 하나는 &lt;b&gt;Ray와 삼각형의 교차 판정&lt;/b&gt;입니다.&lt;br /&gt;하지만 모든 레이에 대해 모든 삼각형과 교차 테스트를 하는 것은 비효율적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Ray Tracing에서는 미리 기하 구조를 감싸는 &lt;b&gt;Bounding Volume&lt;/b&gt;들을 계층적으로 구성해 두고, 레이가 닿을 가능성이 없는 영역은 빠르게 제외합니다.&lt;br /&gt;이 역할을 하는 것이 바로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Acceleration Structure&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-end=&quot;1950&quot; data-start=&quot;1780&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;BLAS(Bottom Level Acceleration Structure)&lt;/b&gt;&lt;/span&gt; 는 개별 기하 정보에 대한 가속 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 하나의 Cube Mesh가 있다면, 그 Cube를 구성하는 삼각형들에 대해 BLAS를 만들 수 있습니다.&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: #ee2323;&quot;&gt; &lt;b&gt;TLAS(Top Level Acceleration Structure)&lt;/b&gt; &lt;/span&gt;는 씬 전체를 기준으로 구성되는 상위 가속 구조입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TLAS 안에는 실제 삼각형 데이터가 직접 들어가는 것이 아니라, &lt;b&gt;BLAS를 참조하는 인스턴스들&lt;/b&gt;이 들어갑니다.&lt;/p&gt;
&lt;p data-end=&quot;2471&quot; data-start=&quot;2444&quot; data-ke-size=&quot;size16&quot;&gt;각 인스턴스는 보통 다음과 같은 정보를 가집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2569&quot; data-start=&quot;2473&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2489&quot; data-start=&quot;2473&quot; data-section-id=&quot;w62nw&quot;&gt;어떤 BLAS를 참조하는지&lt;/li&gt;
&lt;li data-end=&quot;2505&quot; data-start=&quot;2490&quot; data-section-id=&quot;4wcwh8&quot;&gt;자신의 Transform&lt;/li&gt;
&lt;li data-end=&quot;2505&quot; data-start=&quot;2490&quot; data-section-id=&quot;4wcwh8&quot;&gt;Instance ID&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, TLAS는 &lt;b&gt;&amp;ldquo;씬 안에 어떤 오브젝트가 어디에 몇 개 배치되어 있는가&amp;rdquo;&lt;/b&gt;를 관리하는 구조라고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;2759&quot; data-start=&quot;2635&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2759&quot; data-start=&quot;2635&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 처음 보였던 씬 같은 경우에는 BLAS가 총 3가지입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2759&quot; data-start=&quot;2635&quot;&gt;벽 Cube&lt;/li&gt;
&lt;li data-end=&quot;2759&quot; data-start=&quot;2635&quot;&gt;삼각형&lt;/li&gt;
&lt;li data-end=&quot;2759&quot; data-start=&quot;2635&quot;&gt;구&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, TLAS의 인스턴스는 5개입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;왼쪽 벽&lt;/li&gt;
&lt;li&gt;오른쪽 벽&lt;/li&gt;
&lt;li&gt;아래 벽&lt;/li&gt;
&lt;li&gt;삼각형&lt;/li&gt;
&lt;li&gt;구&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ray&lt;/b&gt;와 기하의 충돌 검사는 먼저 &lt;b&gt;TLAS&lt;/b&gt;부터 시작합니다.&lt;br /&gt;&lt;b&gt;TLAS&lt;/b&gt;는 씬 전체 인스턴스에 대한 &lt;b&gt;상위 가속 구조&lt;/b&gt;이므로, 각 노드의 &lt;b&gt;Bounding Volume&lt;/b&gt;과 &lt;b&gt;Ray&lt;/b&gt;의 교차 여부를 검사하면서 불필요한 인스턴스 영역을 빠르게 제거합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;593&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bP5M4c/dJMcajg8QJt/5wN7hDPkEVeLHOVuKcxCU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bP5M4c/dJMcajg8QJt/5wN7hDPkEVeLHOVuKcxCU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bP5M4c/dJMcajg8QJt/5wN7hDPkEVeLHOVuKcxCU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbP5M4c%2FdJMcajg8QJt%2F5wN7hDPkEVeLHOVuKcxCU1%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;527&quot; height=&quot;593&quot; data-origin-width=&quot;527&quot; data-origin-height=&quot;593&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;예를 들어 위와 같은 상황에서 &lt;b&gt;A 영역&lt;/b&gt;을 빠르게 스킵하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;B 영역&lt;/b&gt; 안에서도 &lt;b&gt;4번 인스턴스&lt;/b&gt;는 볼 필요가 없고, &lt;b&gt;5번 인스턴스&lt;/b&gt;랑 &lt;b&gt;6번 인스턴스&lt;/b&gt;만 확인하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정을 통해 교차 가능성이 있는 &lt;b&gt;인스턴스&lt;/b&gt;를 좁혀 나가고, 특정 &lt;b&gt;인스턴스&lt;/b&gt;에 도달하면 그 &lt;b&gt;인스턴스가&lt;/b&gt; 참조하는 &lt;b&gt;BLAS&lt;/b&gt;를 탐색합니다. 이때는 해당 인스턴스의 &lt;b&gt;Transform&lt;/b&gt;을 반영한 공간에서 &lt;b&gt;BLAS 순회&lt;/b&gt;가 이루어집니다.&lt;br /&gt;&lt;b&gt;BLAS&lt;/b&gt; 내부에서도 마찬가지로 군집한 삼각형 끼리 묶인 계층적 &lt;b&gt;Bounding Volume&lt;/b&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;이렇게 찾아낸 교차 결과들 중에서 조건에 맞는 가장 가까운 hit point가 최종 결과로 선택됩니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;RayTracing State Object, Shader Table, Root Signature&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일반 그래픽스 파이프라인이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Graphics PSO&lt;/b&gt;를 만들 듯이 DXR 에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;RayTracing State Object&lt;/b&gt;를 만들어야 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Graphics PSO&lt;/b&gt;가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Vertex Shader, Pixel Shader, Blend State, Rasterizer State, Depth State&lt;/b&gt;를 묶듯이&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Ray Tracing State Object&lt;/b&gt;는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Ray Gen Shader, Miss Shader, Hit Group, Shader Config, Pipeline Config, Global / Local Root Signature&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;같은 요소들을 묶습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, Ray Tracing State Object는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;ldquo;레이트레이싱 파이프라인을 어떤 셰이더와 어떤 설정으로 돌릴 것인가&amp;rdquo;&lt;/b&gt;를 정의하는 객체입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;하지만&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;State Object&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;만 있다고 해서 곧바로 레이트레이싱이 실행되는 것은 아닙니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;State Object&lt;/b&gt;가&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;ldquo;사용 가능한 셰이더 집합과 설정&amp;rdquo;&lt;/b&gt;을 정의하는 객체라면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Shader Table&lt;/b&gt;은 그중에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&amp;ldquo;실제로 어떤 셰이더를 어떤 데이터와 함께 실행할지&amp;rdquo;&lt;/b&gt;를 GPU가 읽을 수 있는 형태로 정리해 둔 테이블입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이때&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Shader Table&lt;/b&gt;을 구성하는 한 개의 엔트리를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Shader Record&lt;/b&gt;라고 부르며, 각 Record에는 보통&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Shader Identifier&lt;/b&gt;와 그 셰이더가 사용할&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Local Root Arguments&lt;/b&gt;가 함께 들어갑니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QSlAP/dJMcahcBLDt/1E60YxZX0zoLkRa0i6SG40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QSlAP/dJMcahcBLDt/1E60YxZX0zoLkRa0i6SG40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QSlAP/dJMcahcBLDt/1E60YxZX0zoLkRa0i6SG40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQSlAP%2FdJMcahcBLDt%2F1E60YxZX0zoLkRa0i6SG40%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;1099&quot; height=&quot;211&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;211&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DispatchRays&lt;/b&gt; 함수를 호출할 때는 &lt;b&gt;D3D12_DISPATCH_RAYS_DESC&lt;/b&gt; 타입 구조체가 필요한데, 이 구조체에는 다음과 같은 내용이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RayGenerationShaderRecord&lt;/li&gt;
&lt;li&gt;MissShaderTable&lt;/li&gt;
&lt;li&gt;HitGroupTable&lt;/li&gt;
&lt;li&gt;CallableShaderTable&lt;/li&gt;
&lt;li&gt;Width, Height, Depth&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;RayGenerationShader는 Record&lt;/b&gt;로 전달되고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Miss Shader와 Hit Group은 Table&lt;/b&gt;로 전달됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DispatchRays 호출이 시작점으로 사용할 Ray Generation Shader Record는 하나&lt;/b&gt;인 반면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;miss/hit&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;쪽은 실행 도중 여러&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;record&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;중 하나가 선택될 수 있기 때문입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;DXR의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Root Signature&lt;/b&gt;는 셰이더가 사용할 리소스를 어떤 방식으로 연결할지 정하는 규칙이며, 크게&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Global Root Signature&lt;/b&gt;와&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Local Root Signature&lt;/b&gt;로 나뉩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Global Root Signature&lt;/b&gt;는 레이트레이싱 파이프라인 전체에서 공통으로 접근하는 리소스를 바인딩하는 데 사용됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Local Root Signature&lt;/b&gt;는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Shader Record&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;단위로 적용되는 파라미터 레이아웃을 정의하며, 실제 값은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Shader Record&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;안의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Local Root Arguments&lt;/b&gt;로 전달됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예제에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Global Root Signature&lt;/b&gt;에 Rendering 결과를 담을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;UAV, TLAS, Light CBV, Camera CBV&lt;/b&gt;를 정의하였고&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Local Root Signature&lt;/b&gt;에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Mesh&lt;/b&gt;의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Vertex Buffer, Index Buffer&lt;/b&gt;를 담는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;SRV&lt;/b&gt;를 정의하여 사용했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Ray Generation Shader 구현 코드&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1773584326122&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[shader(&quot;raygeneration&quot;)]
void RayGen()
{
    // 현재 dispatch 중인 픽셀 좌표
    uint2 id = DispatchRaysIndex().xy;

    // 전체 dispatch 해상도
    uint2 dim = DispatchRaysDimensions().xy;

    // 픽셀 중심 좌표를 0 ~ 1 UV로 변환
    float2 uv = (float2(id) + 0.5) / float2(dim);

    // UV -&amp;gt; NDC(-1 ~ 1)
    float2 ndc = uv * 2.0 - 1.0;

    // 화면 좌표계와 NDC의 y축 방향 차이를 보정
    ndc.y = -ndc.y;

    // NDC 상의 near/far 점 구성
    // z=0, z=1을 사용해서 투영 역변환 후 광선 방향을 계산
    float4 clipNear = float4(ndc.x, ndc.y, 0.0, 1.0);
    float4 clipFar = float4(ndc.x, ndc.y, 1.0, 1.0);

    // Clip -&amp;gt; View 공간 복원
    float4 viewNear = mul(clipNear, g_InvProjection);
    viewNear /= viewNear.w;

    float4 viewFar = mul(clipFar, g_InvProjection);
    viewFar /= viewFar.w;

    // View -&amp;gt; World 공간 복원
    float4 worldNear = mul(viewNear, g_InvView);
    float4 worldFar = mul(viewFar, g_InvView);

    // 카메라에서 장면으로 향하는 primary ray 생성
    RayDesc ray;
    ray.Origin = g_EyePosition.xyz;
    ray.Direction = normalize(worldFar.xyz - worldNear.xyz);
    ray.TMin = 0.001; // self-intersection / near-plane 문제 완화
    ray.TMax = 1e5;   // 충분히 먼 최대 거리

    // 초기 payload
    Payload p;
    p.color = float4(0, 0, 0, 1);
    p.depth = 0;

    // TLAS를 대상으로 레이 추적 시작
    TraceRay(
        g_TLAS,         // 가속구조
        RAY_FLAG_NONE,  // 레이 플래그
        0xFF,           // 인스턴스 마스크
        0,              // RayContributionToHitGroupIndex
        1,              // MultiplierForGeometryContributionToHitGroupIndex
        0,              // MissShaderIndex
        ray,
        p
    );

    // 추적 결과를 출력 텍스처에 저장
    g_Output[id] = p.color;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Closest Hit Shader 구현 코드&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1773584426717&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[shader(&quot;closesthit&quot;)]
void CHS(inout Payload p, in BuiltInTriangleIntersectionAttributes attr)
{
    // 현재 맞은 삼각형의 primitive index
    uint primIdx = PrimitiveIndex();

    // 삼각형은 인덱스 3개로 구성되므로 16-bit 인덱스 3개를 읽음
    uint i0 = LoadIndex16(primIdx * 3 + 0);
    uint i1 = LoadIndex16(primIdx * 3 + 1);
    uint i2 = LoadIndex16(primIdx * 3 + 2);

    // barycentric 좌표 복원
    // attr.barycentrics는 보통 두 값만 주어지므로
    // 나머지 한 축은 1 - x - y 로 계산
    float3 bary = float3(
        1.0 - attr.barycentrics.x - attr.barycentrics.y,
        attr.barycentrics.x,
        attr.barycentrics.y
    );

    // 버텍스 노멀을 barycentric으로 보간하여 object space normal 계산
    float3 normalOS = normalize(
        g_Vertices[i0].normal * bary.x +
        g_Vertices[i1].normal * bary.y +
        g_Vertices[i2].normal * bary.z
    );

    // object space normal -&amp;gt; world space normal
    // row-vector 관점에서 inverse 쪽 행렬을 곱하는 방식
    // 실제 프로젝트에서는 비균일 스케일까지 고려해 normal transform을 엄밀히 다루기도 함
    float3 normal = normalize(mul(normalOS, (float3x3) WorldToObject3x4()));

    // 버텍스 컬러도 barycentric으로 보간
    float3 vertexColor =
        g_Vertices[i0].color * bary.x +
        g_Vertices[i1].color * bary.y +
        g_Vertices[i2].color * bary.z;

    // ----------------------------
    // 로컬 조명 계산 (Phong)
    // ----------------------------
    float3 N = normal;
    float3 L = normalize(-g_LightDirWS);
    float3 ambient = 0.15 * vertexColor;

    // Lambert diffuse
    float NdotL = max(dot(N, L), 0.0);
    float3 diffuse = NdotL * g_LightColor * vertexColor;

    // Phong/Blinn-Phong specular
    float3 V = normalize(-WorldRayDirection());
    float3 H = normalize(L + V);
    float NdotH = max(dot(N, H), 0.0);
    float3 specular = pow(NdotH, 32.0) * g_LightColor * 0.5;

    // 로컬 조명 결과
    float3 localColor = ambient + diffuse + specular;

    // 현재 인스턴스의 재질 파라미터 획득
    MaterialParams mat = GetMaterial(InstanceID());

    if (p.depth &amp;lt; 1 &amp;amp;&amp;amp; (mat.reflectionScale &amp;gt; 0.001 || mat.refractionScale &amp;gt; 0.001))
    {
        // 현재 히트 위치
        float3 hitPos = WorldRayOrigin() + WorldRayDirection() * RayTCurrent();

        // 현재 입사 광선 방향
        float3 incidentDir = normalize(WorldRayDirection());

        // 입사 방향과 노멀의 관계
        float cosI = dot(-incidentDir, normal);

        // faceNormal:
        // 광선이 바깥-&amp;gt;안으로 들어오는지, 안-&amp;gt;밖으로 나가는지에 따라
        // 굴절 계산에 사용할 법선을 정리
        float3 faceNormal = normal;

        float n1 = 1.0;     // 기본적으로 공기
        float n2 = mat.ior; // 물체의 굴절률

        // 표면 안쪽에서 바깥으로 나가는 경우
        if (cosI &amp;lt; 0.0)
        {
            faceNormal = -normal;
            cosI = -cosI;

            // 들어오는/나가는 매질을 서로 교환
            float tmp = n1;
            n1 = n2;
            n2 = tmp;
        }

        float3 reflectionColor = float3(0, 0, 0);
        float3 refractionColor = float3(0, 0, 0);

        // ----------------------------
        // 반사 레이
        // ----------------------------
        if (mat.reflectionScale &amp;gt; 0.001)
        {
            float3 reflDir = reflect(incidentDir, faceNormal);

            RayDesc reflRay;
            // 법선 방향으로 살짝 띄워서 자기 자신과 다시 충돌하는 문제 방지
            reflRay.Origin = hitPos + faceNormal * 0.001;
            reflRay.Direction = reflDir;
            reflRay.TMin = 0.001;
            reflRay.TMax = 1e5;

            Payload reflPayload;
            reflPayload.color = float4(0, 0, 0, 1);
            reflPayload.depth = p.depth + 1;

            TraceRay(g_TLAS, RAY_FLAG_NONE, 0xFF, 0, 1, 0, reflRay, reflPayload);

            // 반사 색에 반사 스케일 적용
            reflectionColor = reflPayload.color.rgb * mat.reflectionScale;
        }

        // ----------------------------
        // 굴절 레이
        // ----------------------------
        if (mat.refractionScale &amp;gt; 0.001)
        {
            // Snell's law에서 쓰이는 굴절률 비율
            float eta = n1 / n2;

            // 굴절 방향 계산
            float3 refrDir = refract(incidentDir, faceNormal, eta);

            // length가 거의 0이면 전반사로 간주
            if (length(refrDir) &amp;gt; 0.001)
            {
                RayDesc refrRay;

                // 굴절 광선은 표면 반대쪽으로 조금 밀어 넣어서 시작
                refrRay.Origin = hitPos - faceNormal * 0.001;
                refrRay.Direction = normalize(refrDir);
                refrRay.TMin = 0.001;
                refrRay.TMax = 1e5;

                Payload refrPayload;
                refrPayload.color = float4(0, 0, 0, 1);
                refrPayload.depth = p.depth + 1;

                TraceRay(g_TLAS, RAY_FLAG_NONE, 0xFF, 0, 1, 0, refrRay, refrPayload);

                // 굴절 색에 굴절 스케일 적용
                refractionColor = refrPayload.color.rgb * mat.refractionScale;
            }
            else
            {
                // 전반사 발생 시 간단히 반사 결과를 대신 사용
                refractionColor = reflectionColor;
            }
        }

        // 로컬 조명과 반사/굴절 결과를 단순 혼합
        float3 finalColor = lerp(localColor, reflectionColor + refractionColor, 0.2);
        p.color = float4(finalColor, 1.0);
    }
    else
    {
        // 반사/굴절이 없는 재질이면 로컬 조명만 사용
        p.color = float4(localColor, 1.0);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;356&quot; data-start=&quot;291&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 &lt;b&gt;DirectX 12&lt;/b&gt;에서 &lt;b&gt;Ray Tracing Pipeline&lt;/b&gt;을 구성하고 사용하는 방법에 대해 알아보았습니다.&lt;/p&gt;
&lt;p data-end=&quot;436&quot; data-start=&quot;358&quot; data-ke-size=&quot;size16&quot;&gt;파이프라인을 구성하기 위해 필요한 설정 항목은 많지만, 관련 기능을 모두 직접 구현하는 것에 비하면 훨씬 효율적이며 많은 이점을 제공합니다.&lt;/p&gt;
&lt;p data-end=&quot;539&quot; data-start=&quot;438&quot; data-ke-size=&quot;size16&quot;&gt;이번 예제에서는 &lt;b&gt;Reflection&lt;/b&gt;과 &lt;b&gt;Refraction&lt;/b&gt;만 간단히 다루었지만, &lt;b&gt;Ray Tracing Pipeline&lt;/b&gt;은 이 외에도 다양한 효과와 기능으로 확장하여 활용할 수 있습니다.&lt;/p&gt;
&lt;p data-end=&quot;539&quot; data-start=&quot;438&quot; data-ke-size=&quot;size16&quot;&gt;다음에 기회가 된다면 Any Hit Shader나 그림자 등도 구현해볼 예정입니다.&lt;/p&gt;</description>
      <category>프로그래밍/DirectX12</category>
      <category>Backward RayTracing</category>
      <category>CG</category>
      <category>Compute Graphics</category>
      <category>DXR</category>
      <category>gpu</category>
      <category>GPU RayTracing</category>
      <category>HitGroup</category>
      <category>raytracing</category>
      <category>RayTracing Shader</category>
      <category>레이 트레이싱</category>
      <author>merit nada</author>
      <guid isPermaLink="true">https://mathmakeworld.tistory.com/133</guid>
      <comments>https://mathmakeworld.tistory.com/133#entry133comment</comments>
      <pubDate>Sat, 15 Nov 2025 21:30:38 +0900</pubDate>
    </item>
    <item>
      <title>순열, 조합, 중복 순열, 중복 조합 (5/5)</title>
      <link>https://mathmakeworld.tistory.com/131</link>
      <description>&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 중복 조합에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mathmakeworld.tistory.com/127&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;첫 포스팅&lt;/a&gt;에서 알아봤듯이 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;중복 조합&lt;/b&gt;&lt;/span&gt;은 &lt;b&gt;&quot;n개에서 r개를 뽑되, 순서를 신경쓰지 않고, 복원 추출하는 경우의 수&quot;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;중복 조합의 경우의 수를 계산할 때는 보통 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;막대기를 배치하는 경우의 수&quot;&lt;/b&gt;&lt;/span&gt;로 치환해서 생각합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;막대기를 배치&quot;&lt;/b&gt;한다는게 어떤 의미인지 알아보기 위해 예제를 한 번 들어보겠습니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;막대기 배치 예시&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;a, b, c&lt;/b&gt;라고 써있는 영어 카드가 있다고 생각해봅시다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이때 순서를 신경쓰지 않고 &lt;b&gt;4번&lt;/b&gt; 복원 추출로 뽑았을 때 나올 수 있는 경우의 수는 몇 개일까요?&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 생각할 수 있는 것은&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;a가 네 번 나오는 경우 -&amp;gt; aaaa&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;b가 네 번 나오는 경우 -&amp;gt; bbbb&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;c가 네 번 나오는 경우 -&amp;gt; cccc입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그 외에 baaa, abaa, aaba, aaab등이 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;&quot;조합&quot;&lt;/b&gt;이므로 baaa와 abaa, aaba, aaab는 같은 경우로 칩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;abbc와 cbab도 같은 경우로 칠텐데 이런 것들을 다 고려하기 조금 복잡합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 대신에 &lt;b&gt;막대기&lt;/b&gt;라는 개념을 도입합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;왜 막대기를 도입하고 왜 이런식으로 해석하는지 처음에는 이해가 안 갈테지만 일단 진행해봅시다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 &lt;b&gt;뽑아야 하는 갯수&lt;/b&gt;만큼 카드를 배치합니다. 예제에서는 &lt;b&gt;4번&lt;/b&gt;이므로 &lt;b&gt;4개&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;막대기를 알파벳 갯수 - 1개&lt;/b&gt; 만큼 배치합니다. 예제에서는 &lt;b&gt;3 -1 = 2개&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;문자로 간략하게 표현하기 위해 &lt;b&gt;카드&lt;/b&gt;를 &lt;b&gt;X&lt;/b&gt;로 두고, &lt;b&gt;막대기&lt;/b&gt;를 &lt;b&gt;I&lt;/b&gt;로 둡시다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;만약 막대기를 앞에 전부 배치하고 카드를 뒤에 배치한다고 하면 &lt;b&gt;IIXXXX&lt;/b&gt;처럼 될 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 막대기끼리 구분하지 않고, 카드끼리 구분하지 않습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;&quot;막대기와 카드가 배열된 순서에 따라 특정 형태로 카드를 뽑았다&quot;&lt;/b&gt;고 생각하는 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 XXIXIX 이런식으로 카드들 사이에 막대기가 2개 있는 경우&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모든 막대기 왼쪽에 있는 카드들은 a&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;막대기 두 개 사이에 있는 카드는 b&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모든 막대기 오른쪽에 있는 카드는 c&lt;/b&gt;라고 보는 겁니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그럼 &lt;b&gt;XXIXIX&lt;/b&gt;라는 상황은 &lt;b&gt;aabc&lt;/b&gt;가 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;카드를 배열하는 또 다른 예를 들어보자면&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;XIXXIX -&amp;gt; abbc&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;IXXXIX -&amp;gt; bbbc&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;XXIIXX -&amp;gt; aacc&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 마지막으로는 이렇게 배치할 수 있는 경우의 수를 계산하는 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;이렇게 계산된 결과가 a, b, c 알파벳 카드를 중복 조합으로 뽑는 경우의 수와 같습니다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;막대기를 배치하는 경우의 수가 중복 조합의 경우의 수와 같은 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 aabc를 뽑은 경우에 대해서 생각해봅시다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;aabc는 중복 조합으로 봤을 때 abca, acba, aacb 순서로 뽑은 경우와 같은 경우로 칩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;&quot;a를 두 번 뽑고, b를 한 번 뽑고, c를 한 번 뽑는 모든 경우&quot;&lt;/b&gt;를 세고 나서 중복되는 것들을 없애고 하나로 남겨야 중복 조합의 경우의 수를 알 수 있게 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모든 경우의 수를 세보고 중복되는 것을 제거하는 것은 어렵기 때문에 막대기를 배치하는 방식을 사용하는 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;생각해보면 &lt;b&gt;&quot;a를 두 번 뽑고, b를 한 번 뽑고, c를 한 번 뽑는 모든 경우&quot;&lt;/b&gt; 을 막대기 방식을 통해서 표현할 수 있는 유일한 방법은 XXIXIX 이렇게 배치하는 경우 밖에 없습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;막대기를 하나라도 다른 위치에 배치한다면 &lt;b&gt;&quot;a를 두 번 뽑고, b를 한 번 뽑고, c를 한 번 뽑는&quot;&lt;/b&gt; 상황을 대응할 수 없습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 X&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;IX&lt;/b&gt;&lt;/span&gt;XIX 이렇게 바꿔버리면 미리 설정한 규칙에 의해서&lt;b&gt; &quot;a를 한 번 뽑고, b를 두 번 뽑고, c를 한 번 뽑는&quot;&lt;/b&gt; 상황이 되어버립니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;막대기 방식으로 배치할 수 있는 모든 경우의 수&lt;/b&gt;만 보는 것은 결국 &lt;b&gt;중복 조합에서 제거되어야 하는 모든 경우의 수를 제거한 경우&lt;/b&gt;만 보게 되는 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 막대기를 배치하는 경우의 수가 중복 조합의 경우의 수와 같게 되는 것입니다.&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;막대기를 배치하는 경우의 수를 계산하는 법&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이제 다음으로 알아야 할 것은 막대기를 배치하는 경우의 수를 어떻게 계산하느냐입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이것은 &lt;b&gt;막대기의 위치를 뽑는 경우의 수&lt;/b&gt;로 생각할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 예제에서 문자의 수는 &lt;b&gt;X가 4개&lt;/b&gt;, &lt;b&gt;I가 2개&lt;/b&gt; 해서 &lt;b&gt;총 6개&lt;/b&gt;였습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아직 문자들이 배치되지 않았다고 생각했을 때 막대기 하나를 배치할 수 있는 선택지는 6자리 중에 하나입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 다음 막대기를 배치할 수 있는 선택지는 나머지 5자리 중에 하나입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;첫 번째 막대기와 두 번째 막대기는 서로 다르다고 구분하지 않기 때문에 이 과정은 &lt;a href=&quot;https://mathmakeworld.tistory.com/130&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;저번 포스팅&lt;/a&gt;에서 이야기한 &lt;b&gt;&quot;조합&quot;&lt;/b&gt;과 같다는 것을 아실 수 있을 겁니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;6자리 중에 2개를 뽑는 것&lt;/b&gt; \(_{6}C_{2}\)가 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;막대기를 뽑으면 자동으로 카드의 위치가 정해지는 것이기 때문에, 카드의 위치를 정하는 경우의 수인 \(_{6}C_{4}\)와 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 6이라는 숫자는 문자의 수이므로, 가장 처음 막대기 예제를 이야기할 때 나온 규칙에 의해서 &lt;b&gt;n + r - 1&lt;/b&gt;로부터 도출된 수입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4라는 숫자는 뽑는 숫자이므로 &lt;b&gt;r&lt;/b&gt;로부터 도출된 숫자입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 예제를 일반화해서 &quot;&lt;b&gt;n개에서 r개를 중복 조합으로 뽑는 경우의 수&quot;&lt;/b&gt;를 수식으로 표현하면 아래와 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;$$_{n}H_{r}=_{n+r-1}C_{r}=_{n+r-1}C_{n-1}$$&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;지금까지 순열, 조합, 중복 순열, 중복 조합에 대해서 알아봤습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;공식을 외우는 것도 중요하지만 기왕이면 어떻게 공식이 유도되었는지까지 알아두면 좋을 것 같습니다.&lt;/p&gt;</description>
      <category>수학&amp;amp;물리</category>
      <category>Combination</category>
      <category>n+r-1Cr</category>
      <category>NCR</category>
      <category>nHr</category>
      <category>permutation</category>
      <category>경우의 수</category>
      <category>순열</category>
      <category>조합</category>
      <category>중복 순열</category>
      <category>중복 조합</category>
      <author>merit nada</author>
      <guid isPermaLink="true">https://mathmakeworld.tistory.com/131</guid>
      <comments>https://mathmakeworld.tistory.com/131#entry131comment</comments>
      <pubDate>Fri, 22 Aug 2025 23:58:27 +0900</pubDate>
    </item>
    <item>
      <title>순열, 조합, 중복 순열, 중복 조합 (4/5)</title>
      <link>https://mathmakeworld.tistory.com/130</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 조합에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;조합은 순열을 통해서 유도할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 &lt;a href=&quot;https://mathmakeworld.tistory.com/127&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;첫 포스팅&lt;/a&gt;에서 알아 보았던 &lt;b&gt;조합의 정의&lt;/b&gt;에 대해서 다시 한 번 생각해봅시다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;조합은 &lt;b&gt;&quot;n개 중에서 r개를 뽑을 때, 순서를 고려하지 않고, 비복원 추출하는 경우의 수&quot;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;순열과 조합의 다른 점은 &lt;b&gt;순서를 고려하냐 마냐&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;저번 포스팅의 예제인, 3개의 카드에서 2개를 뽑는 경우의 수를 다시 한 번 생각해봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;img.png&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQ06Ss/btsP43yAB78/7XL1YukFvWdGlNpPIUiM2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQ06Ss/btsP43yAB78/7XL1YukFvWdGlNpPIUiM2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQ06Ss/btsP43yAB78/7XL1YukFvWdGlNpPIUiM2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQ06Ss%2FbtsP43yAB78%2F7XL1YukFvWdGlNpPIUiM2K%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;346&quot; height=&quot;505&quot; data-filename=&quot;img.png&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;505&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 이미지에서 결과중 하나인 &lt;b&gt;(1,2)&lt;/b&gt;와 다른 결과인 &lt;b&gt;(2,1)&lt;/b&gt;은 조합 입장에서는 순서를 신경쓰지 않기 때문에 같은 &lt;b&gt;&quot;경우&quot;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉, 경우의 수를 계산할 때 2개가 아닌 1개로 친다는 의미이죠&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;똑같이, &lt;b&gt;(1,3)&lt;/b&gt;과 &lt;b&gt;(3,1)&lt;/b&gt;도 같은 것으로 치고, &lt;b&gt;(2,3)&lt;/b&gt;과 &lt;b&gt;(3,2)&lt;/b&gt;도 같은 것으로 칩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결론적으로 위 예제에서 순열의 모든 경우의 수에서 2를 나누면 조합의 경우의 수가 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이로써 순열과 조합이 어느 정도 연관성이 있음을 알 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;n과 r의 개수를 늘려 1~5까지의 숫자가 있는 5장의 카드 중에서 3장을 뽑는 경우의 수를 생각해보겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나올 수 있는 경우 중에는 1번, 2번, 3번 카드만 뽑았다고 가정했을 때 &lt;b&gt;(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), (3, 2, 1)&lt;/b&gt;가 있을 겁니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;순열 입장에서는 모두 다른 경우이지만, 조합 입장에서는 같은 경우입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;순열의 경우의 수를 계산할 때는 6개이지만, 조합의 경우의 수를 계산할 때는 1개가 된다는 의미입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 6이라는 숫자가 어떻게 나온 것인지 생각해봅시다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이는 1~3번 카드를 &lt;b&gt;재배열하는 경우의 수&lt;/b&gt;와 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mathmakeworld.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;저번 포스팅&lt;/a&gt;에서 알아봤듯이 결과는 \(3!=6\)이 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 이렇게 중복되는 경우는 1번 2번 4번 카드를 뽑았을 때, 1번 2번 5번 카드를 뽑았을 때, 2번 3번 4번 카드를 뽑았을 때도 존재합니다. 즉, 서로 다른 3장을 뽑는 모든 경우에 중복이 있으므로&lt;b&gt; 전체의 경우의 수에서 6으로 나눠줘야 한다&lt;/b&gt;는 의미입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결론적으로 &lt;b&gt;&quot;n개 중에 r개를 순서 상관없이 뽑는 경우의 수&quot;&lt;/b&gt;는 &lt;b&gt;&quot;n개 중에 r개를 순서 상관있이 뽑는 경우의 수&quot;&lt;/b&gt;에서 &lt;b&gt;&quot;r개를 재배열하는 경우의 수를 나눈 수&quot;&lt;/b&gt;가 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;수식으로 표현하면 아래와 같이 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;$$_{n}C_{r}=\frac{_{n}P_{r}}{n!}=\frac{n!}{(n-r)!r!}$$&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;수식이 조금 복잡해보일 수 있지만 천천히 생각해보면 많이 어렵지 않습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이로써 조합에 대해서 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mathmakeworld.tistory.com/131&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 포스팅&lt;/a&gt;에서는 마지막으로 &lt;b&gt;&quot;중복 조합&quot;&lt;/b&gt;에 대해서 알아보도록 하겠습니다.&lt;/p&gt;</description>
      <category>수학&amp;amp;물리</category>
      <category>Combination</category>
      <category>factorial</category>
      <category>permutation</category>
      <category>경우의 수</category>
      <category>뽑기</category>
      <category>수학</category>
      <category>순열</category>
      <category>조합</category>
      <category>통계학</category>
      <category>팩토리얼</category>
      <author>merit nada</author>
      <guid isPermaLink="true">https://mathmakeworld.tistory.com/130</guid>
      <comments>https://mathmakeworld.tistory.com/130#entry130comment</comments>
      <pubDate>Fri, 22 Aug 2025 23:58:09 +0900</pubDate>
    </item>
    <item>
      <title>순열, 조합, 중복 순열, 중복 조합 (3/5)</title>
      <link>https://mathmakeworld.tistory.com/129</link>
      <description>&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 중복 순열 다음으로 이해하기 쉬운 순열에 대해서 이야기하겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;중복 순열이 아니라 그냥 순열이므로 &lt;b&gt;n개에서 r개를 뽑을 때 n이 r보다 크거나 같아야&lt;/b&gt; 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mathmakeworld.tistory.com/128&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;저번 포스팅&lt;/a&gt;의 예제를 그대로 가져와서 &lt;b&gt;3개의 숫자 카드&lt;/b&gt; 중 &lt;b&gt;2개&lt;/b&gt;를 뽑는 경우의 수를 생각해보겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일단 첫 번째 시도에서 뽑을 수 있는 숫자는 1, 2, 3해서 &lt;b&gt;총 3가지&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;두 번째 시도에서 뽑을 수 있는 숫자는 첫 번째 시도에서 뽑은 숫자를 제외했기 때문에 &lt;b&gt;총 2가지&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;역시나 그림으로 보겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;505&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nTJf0/btsP2w95YMs/MEDWXk2Z3bl89FLoaRTlxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nTJf0/btsP2w95YMs/MEDWXk2Z3bl89FLoaRTlxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nTJf0/btsP2w95YMs/MEDWXk2Z3bl89FLoaRTlxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnTJf0%2FbtsP2w95YMs%2FMEDWXk2Z3bl89FLoaRTlxK%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;346&quot; height=&quot;505&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;505&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;n과 r이 작아서 잘 안 느껴질 수 있지만 생각해보면 처음에 뽑을 때 가능한 수는 &lt;b&gt;n개&lt;/b&gt;, 그 다음은 &lt;b&gt;n-1개&lt;/b&gt;, 그 다음은 &lt;b&gt;n-2개&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이렇게 총 &lt;b&gt;r번&lt;/b&gt; 뽑게 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결론적으로 수식으로 표현하면&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;$$n*(n-1)*(n-2)*...*(n-r+1)$$&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 된다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 식을 다른 방식으로 표현하면 흔히 보는 수식인&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;$$_{n}P_{r}=\frac{n}{(n-r)!}$$&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 n과 r이 같은 경우를 생각해봅시다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들면 &lt;b&gt;&quot;3장의 카드를 3번 뽑는 경우&quot;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;결과를 나열해보면 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;529&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vrolO/btsP2YSNE89/hOG9k8If4CRxSHtdpWmTa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vrolO/btsP2YSNE89/hOG9k8If4CRxSHtdpWmTa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vrolO/btsP2YSNE89/hOG9k8If4CRxSHtdpWmTa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvrolO%2FbtsP2YSNE89%2FhOG9k8If4CRxSHtdpWmTa0%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;487&quot; height=&quot;529&quot; data-origin-width=&quot;487&quot; data-origin-height=&quot;529&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;생각해보면 알 수 있지만 이것은 &lt;b&gt;&quot;3장의 카드를 재배열하는 경우의 수&quot;&lt;/b&gt;를 보는 것과 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;n개의 원소 중에서 n개를 중복 없이 뽑는 경우의 수는 n개의 원소를 재배열하는 경우의 수와 같다&lt;/b&gt;고 볼 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 상황을 수식으로 생각해봅시다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위 수식대로라면 \(n*(n-1)*(n-2)*...*2*1\)이 될 것이고, 결론적으로는 정의에 의해서 \(n!\)이 될 것입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;\( _{n}P_{r}=\frac{n}{(n-r)!}\) 형태의 수식에서도 만족하는지 한 번 봅시다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;\(n=r\)이므로 \(_{n}P_{n}=\frac{n}{(n-n)!}=\frac{n}{0!}\)&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이 됩니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;정의에 의해서 \(0!=1\)이므로 결과는 역시 \(n!\)로 동일함을 알 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이번 포스팅을 마치기 전에 오늘 내용을 요약해보겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1.n개에서 r개를 뽑는 경우의 수는 \(n*(n-1)*(n-2)*...*(n-r+1)\)과 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 다른 식으로 표현하면 \(\frac{n}{(n-r)!}\) 과 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. n개를 재배열할 때의 모든 경우의 수는 \(n!\)이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 순열에 대해서 알아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 &lt;a href=&quot;https://mathmakeworld.tistory.com/130&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;조합&lt;/a&gt;에 대해서 알아보도록 하겠습니다.&lt;/p&gt;</description>
      <category>수학&amp;amp;물리</category>
      <category>factorial</category>
      <category>permutation</category>
      <category>경우의 수</category>
      <category>수학</category>
      <category>순열</category>
      <category>조합</category>
      <category>중복 순열</category>
      <category>중복 조합</category>
      <category>통계학</category>
      <category>팩토리얼</category>
      <author>merit nada</author>
      <guid isPermaLink="true">https://mathmakeworld.tistory.com/129</guid>
      <comments>https://mathmakeworld.tistory.com/129#entry129comment</comments>
      <pubDate>Fri, 22 Aug 2025 17:44:49 +0900</pubDate>
    </item>
    <item>
      <title>순열, 조합, 중복 순열, 중복 조합 (2/5)</title>
      <link>https://mathmakeworld.tistory.com/128</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 &lt;b&gt;&quot;중복 순열&quot;&lt;/b&gt;에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;중복 순열&quot;&lt;/b&gt;을 가장 먼저 설명하는 이유는 중복 순열이 이해하기 가장 쉽기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞선 포스팅에서 조합, 순열, 중복 조합, 중복 순열은 &lt;b&gt;&quot;n개 중에 r개를 뽑는 경우의 수&quot;&lt;/b&gt;라고 설명을 드렸었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 그렇게 생각하면 n개가 r개 보다 크거나 같아야 할 것처럼 보입니다. 실제로 순열과 조합에서는 n이 r보다 크거나 같아야 합니다. 그래야 n개 중에서 r개를 뽑을 수 있기 때문입니다.&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;n개 중에서 r개를 뽑되, 뽑은 뒤에 다시 되돌리는 &lt;b&gt;&quot;복원 추출&quot;&lt;/b&gt; 방식이기 때문에 n이 꼭 r보다 크거나 같을 필요는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 복원 추출은 사실 &lt;b&gt;&quot;n개의 원소 각각이 무한히 있는 상황에서 r개를 뽑는 것&quot;&lt;/b&gt;이라고 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 말로 하면 &lt;b&gt;&quot;n개의 종류에서 r개를 뽑는다&quot;&lt;/b&gt;고도 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 다시 중복 순열로 돌아와서 &lt;b&gt;n개 중에 r개를 뽑는 경우의 수&lt;/b&gt;를 보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 예시로는 1~3까지 숫자가 써진 &lt;b&gt;3개의 숫자 카드&lt;/b&gt; 중에서 &lt;b&gt;2장&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;일단 첫 번째 시도에서 뽑을 수 있는 숫자는 1, 2, 3해서 &lt;b&gt;총 3가지&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뽑은 카드를 다시 넣고 두 번째 시도에서 뽑을 수 있는 숫자는 역시 1, 2, 3해서 &lt;b&gt;총 3가지&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림으로 표현해보면 아래와 같이 표현할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;522&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oypmD/btsP4SRhEvR/m2JlcpmAhs40gZKKVM8Tfk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oypmD/btsP4SRhEvR/m2JlcpmAhs40gZKKVM8Tfk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oypmD/btsP4SRhEvR/m2JlcpmAhs40gZKKVM8Tfk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoypmD%2FbtsP4SRhEvR%2Fm2JlcpmAhs40gZKKVM8Tfk%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;351&quot; height=&quot;522&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;351&quot; data-origin-height=&quot;522&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중복 순열은 순열이므로 &lt;b&gt;순서&lt;/b&gt;를 신경씁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;1을 뽑고 2를 뽑은 것&quot;&lt;/b&gt;과, &lt;b&gt;&quot;2를 뽑고 1을 뽑은 것&quot;&lt;/b&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;뽑는 숫자가 늘어나거나, 종류가 늘어나도 계속 이런 방식일테니 직관적으로 생각해보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$n^r$$&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 되는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시나 잘 이해가 안 간다면 n과 r을 좀 키워서 생각해보시면 이해가 될 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제에서는 &lt;b&gt;3개&lt;/b&gt; 중에 &lt;b&gt;2개&lt;/b&gt;를 &lt;b&gt;중복 순열&lt;/b&gt;로 뽑았으니 &lt;b&gt;\(3^2 =9\)&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://mathmakeworld.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다음 포스팅&lt;/a&gt;에서 나머지를 알아보도록 하겠습니다.&lt;/p&gt;</description>
      <category>수학&amp;amp;물리</category>
      <category>n개 중 r개</category>
      <category>경우의 수</category>
      <category>복원 추출</category>
      <category>수학</category>
      <category>순서</category>
      <category>순열</category>
      <category>조합</category>
      <category>중복 순열</category>
      <category>중복 조합</category>
      <category>통계학</category>
      <author>merit nada</author>
      <guid isPermaLink="true">https://mathmakeworld.tistory.com/128</guid>
      <comments>https://mathmakeworld.tistory.com/128#entry128comment</comments>
      <pubDate>Fri, 22 Aug 2025 17:43:45 +0900</pubDate>
    </item>
    <item>
      <title>순열, 조합, 중복 순열, 중복 조합 (1/5)</title>
      <link>https://mathmakeworld.tistory.com/127</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 시간에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;순열, 조합, 중복 순열, 중복 조합&quot;&lt;/b&gt;&lt;/span&gt;에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;순열&lt;/b&gt;과 &lt;b&gt;조합&lt;/b&gt;에 대해서 생각하면 가장 먼저 생각 나는 것이 아마 &lt;b&gt;C(Combination)&lt;/b&gt;과 &lt;b&gt;P(Permutation)&lt;/b&gt;일 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$$_{n}\!C_{r} , _{n}\!P_{r}$$&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;먼저 이 4가지 요소의 공통점은&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;n개에서 r개를 뽑을 때의 &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;경우의 수&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;라는 것입니다.&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: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 먼저&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;경우의 수&quot;&lt;/b&gt;&lt;/span&gt;를 생각해 봅시다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;경우의 수&quot;&lt;/b&gt;라 함은 말 그대로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;특정 행위를 했을 때 나올 수 있는 경우(Case)의 가지수&quot;&lt;/b&gt;입니다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;여기서 각각의 경우가 나올 확률은 고려하지 않습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어, 특정 행위를 했을 때 A라는 경우는 1%로 나오고, B라는 경우는 99%로 나온다 하더라도, 해당 행위를 했을 때는 A 아니면 B가 나오기 때문에 경우의 수는 2입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 말하는 &lt;b&gt;특정 행위&lt;/b&gt;는 바로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&quot;n개에서 r개를 뽑는다&quot;&lt;/b&gt;&lt;/span&gt;라는 행위입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 &lt;b&gt;&quot;5가지 숫자 카드에서 2개를 뽑는다&quot;&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;/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;다만, n개에서 r개를 뽑을 때 그냥 뽑는 것이 아니라 신경써야 하는 요소 두 가지가 있습니다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;1) 뽑는 과정에서 뽑을 때마다 뽑은 요소를 다시 되돌릴 것이냐 말 것이냐&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;2) 뽑는 과정에서 어떤 요소를 먼저 뽑았는지를 신경쓸 것이냐 말 것이냐&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1번에서 뽑은 요소를 되돌리는 것을 원래대로 복원한다는 의미에서 &lt;b&gt;&quot;복원 추출&quot;&lt;/b&gt;이라고 하고, 되돌리지 않는 것을 &lt;b&gt;&quot;비복원 추출&quot;&lt;/b&gt;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번에서 &lt;b&gt;&quot;신경을 쓴다&quot;&lt;/b&gt;는 말은 경우의 수를 따질 때 누구를 먼저 뽑았는지에 따라 다른 경우로 봐서 경우의 수에 일조한다는 뜻입니다. 반대로 &lt;b&gt;&quot;신경을 쓰지 않는다&quot;&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;이렇게 생각하면 4가지 경우에 대해서 아래 표로 깔끔하게 정리할 수 있습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 63px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;어떤 요소를 먼저 뽑았는지 신경씀&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;어떤 요소를 먼저 뽑았는지 신경 안씀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;뽑은 요소를 되돌림&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;중복 순열 $$_{n}\!\Pi_{r}$$&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;중복 조합 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;$$_{n}\!H_{r}$$&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 21px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;뽑은 요소를 되돌리지 않음&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;순열 $$_{n}\!P_{r}$$&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 21px;&quot;&gt;조합 $$_{n}\!C_{r}$$&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행과 열을 따로 나눠서 생각하면, 뽑은 요소를 되돌리면 &lt;b&gt;&quot;중복&quot;&lt;/b&gt;이 붙고, 안 되돌리면 &lt;b&gt;&quot;중복&quot;&lt;/b&gt;이 안 붙습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 뽑았는지 신경 쓰면 &lt;b&gt;&quot;순열&quot;&lt;/b&gt;이고, 신경 안 쓰면 &lt;b&gt;&quot;조합&quot;&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;표를 보시면 각 항에 대해서 &lt;b&gt;수학 기호&lt;/b&gt;로도 표현을 했습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;수학 기호에 문자가 들어가는데 해당 문자가 어떤 의미인지 알아보겠습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1) 순열은 영어로 번역하면 &lt;b&gt;Permutation&lt;/b&gt;이기 때문에 &lt;b&gt;P&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2) 조합은 영어로 번역하면 &lt;b&gt;Combination&lt;/b&gt;이기 때문에 &lt;b&gt;C&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3) 중복 순열에서 쓰인 문자는 대문자 &lt;b&gt;파이(pi)&lt;/b&gt;입니다. &lt;span&gt;중복 순열의 계산식이 \(n^r\)이라서 &lt;/span&gt;곱하는 것 &lt;b&gt;Product의 P&lt;/b&gt;로 쓰면 좋지만 이미 순열에서 &lt;b&gt;P&lt;/b&gt;를 쓰고 있기 때문에 대신 &lt;b&gt;Pi&lt;/b&gt;를 썼다고 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4) 중복 조합의 &lt;b&gt;H&lt;/b&gt;는 &lt;b&gt;Homogeneous&lt;/b&gt;의 첫 글자라고 합니다. 이는 동차 다항식&lt;b&gt;(homogeneous polynomials)&lt;/b&gt;과 관련이 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;\((x+y)^2\)의 항의 개수는 \(_{2}\!\Pi_{2}\)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;\((x+y+z)^5\)의 항의 개수는 &lt;span&gt; \(_{5}\!\Pi_{3}\) &lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;\((x_{1}+x_{2}+x_{3}+...+x_{r})^n\)의 항의 개수는 \(_{n}\!\Pi_{r}\)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이기 때문이라고 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;일단 이번 시간에는 순열, 조합, 중복 순열, 중복 조합에 대해서 전체적으로 알아보았습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각각이 어떤 상황을 의미하는지, 어떻게 계산되는지에 대해서는 다음 포스팅에서 알아보도록 하겠습니다.&lt;/p&gt;</description>
      <category>수학&amp;amp;물리</category>
      <category>NCR</category>
      <category>NPR</category>
      <category>경우의 수</category>
      <category>수학</category>
      <category>순열</category>
      <category>조합</category>
      <category>중복 순열</category>
      <category>중복 조합</category>
      <category>통계학</category>
      <category>확률</category>
      <author>merit nada</author>
      <guid isPermaLink="true">https://mathmakeworld.tistory.com/127</guid>
      <comments>https://mathmakeworld.tistory.com/127#entry127comment</comments>
      <pubDate>Sat, 16 Aug 2025 21:07:11 +0900</pubDate>
    </item>
    <item>
      <title>DirectX12로 Compute Shader 사용하기</title>
      <link>https://mathmakeworld.tistory.com/126</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 시간에는 &lt;b&gt;DirectX12&lt;/b&gt;로 &lt;b&gt;Compute Shader&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Compute Shader&lt;/b&gt;에서 &lt;b&gt;Compute&lt;/b&gt;는 계산이라는 뜻이고, &lt;b&gt;Shader&lt;/b&gt;는 컴퓨터 그래픽스의 &lt;b&gt;Vertex Shader/Pixel Shader&lt;/b&gt;에서의 &lt;b&gt;Shader&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 &lt;b&gt;Shader&lt;/b&gt;들은 &lt;b&gt;Vertex&lt;/b&gt;별로 실행되거나, &lt;b&gt;Pixel&lt;/b&gt;별로 실행되는 등 특정한 역할이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 컴퓨터 그래픽스 기술이 발전하고, &lt;b&gt;GPU&lt;/b&gt;가 발전하면서 &lt;b&gt;General&lt;/b&gt;한 연산을 &lt;b&gt;GPU&lt;/b&gt;의 도움을 받아 진행하고 싶은 니즈가 생겼고, 이를 해결하기 위해 나온 것인 &lt;b&gt;Compute Shader&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Compute Shader&lt;/b&gt;는 일단 간단하게 &lt;b&gt;&quot;General한 연산을 GPU로 실행하는 프로그램&quot;&lt;/b&gt; 정도로 이해하시면 될 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 &lt;b&gt;General&lt;/b&gt;한 목적으로&lt;b&gt; GPU&lt;/b&gt;를 사용하는 것을 &lt;b&gt;GPGPU&lt;/b&gt;(&lt;b&gt;G&lt;/b&gt;eneral-&lt;b&gt;P&lt;/b&gt;urpose computing on &lt;b&gt;G&lt;/b&gt;raphics &lt;b&gt;P&lt;/b&gt;rocessing &lt;b&gt;U&lt;/b&gt;nits)이라고 부르기도 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;사실 GPU를 사용하는 기술들은 &lt;b&gt;DirectX&lt;/b&gt;의 &lt;b&gt;Compute Shader&lt;/b&gt; 말고도 있습니다. &lt;b&gt;NVIDIA&lt;/b&gt;의 &lt;b&gt;Cuda&lt;/b&gt;라는 기술도 있죠.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만 &lt;b&gt;Compute Shader&lt;/b&gt;는 &lt;b&gt;DirectX12 API&lt;/b&gt;의 함수들을 이용해서 &lt;b&gt;GPU&lt;/b&gt;를 사용하는 것이기 때문에 기존 렌더링 파이프라인과 잘 연동이 되어 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;예를 들자면 &lt;b&gt;DirectX12&lt;/b&gt;에서 사용하는 &lt;b&gt;Graphics Resource&lt;/b&gt;에 바로 &lt;b&gt;Compute Shader&lt;/b&gt;의 연산 결과를 담고 사용할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 연기 시뮬레이션 등 렌더링과 관련된 병렬 처리가 무거운 작업들을 하기에 적합합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CUDA&lt;/b&gt;를 이용해서도 위와 같은 작업이 가능하기는 하지만, &lt;b&gt;CUDA&lt;/b&gt;를 사용하게 되면 코드가 복잡해질 수도 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;또한 &lt;b&gt;CUDA&lt;/b&gt;는 &lt;b&gt;NVIDIA&lt;/b&gt; 전용이기 때문에 &lt;b&gt;AMD GPU&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;b&gt;DirectX12&lt;/b&gt;로 &lt;b&gt;Compute Shader&lt;/b&gt;를 사용하는 예제를 만들어 보도록 하겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 시간에는 단순히 사용법에 대해서 알아보는 것이므로 아주 간단한 연산만 해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바로 픽셀의 위치에 따라 색이 다른 아래와 같은 텍스쳐를 만드는 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;715&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cass9f/btsQTyMvqtP/EBu1SpONkfu7HU9ACrXw11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cass9f/btsQTyMvqtP/EBu1SpONkfu7HU9ACrXw11/img.png&quot; data-alt=&quot;픽셀의 위치에 따라 색이 다른 텍스쳐&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cass9f/btsQTyMvqtP/EBu1SpONkfu7HU9ACrXw11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcass9f%2FbtsQTyMvqtP%2FEBu1SpONkfu7HU9ACrXw11%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;1264&quot; height=&quot;715&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;715&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;픽셀의 위치에 따라 색이 다른 텍스쳐&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 이와 같은 작업은 기존 렌더링 파이프라인에서 &lt;b&gt;Vertex Shader&lt;/b&gt;로 &lt;b&gt;Color&lt;/b&gt; 값을 보내고 &lt;b&gt;Pixel Shader&lt;/b&gt;에서 받도록 하면 중간에 &lt;b&gt;Rasterizer&lt;/b&gt;가 자동으로 해주는 일이긴 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들자면 &lt;b&gt;Quad&lt;/b&gt;를 &lt;b&gt;Draw&lt;/b&gt;하는 &lt;b&gt;Pass&lt;/b&gt;를 하나 만들고, &lt;b&gt;Vertex Shader&lt;/b&gt;와 &lt;b&gt;Pixel Shader&lt;/b&gt;를 아래와 같이 구현하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1759213644914&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct VS_INPUT
{
    float3 Position : POSITION;
    float3 Color    : COLOR;
};

struct PS_INPUT
{
    float4 Position : SV_Position;
    float4 Color : COLOR;
};

PS_INPUT main(VS_INPUT IN)
{
    PS_INPUT OUT;
    
    OUT.Position = float4(IN.Position, 1.0f);
    OUT.Color = float4(IN.Color, 1.0f);

    return OUT;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VertexShader에서는 인자로 받은 Color 값을 그대로 Pixel Shader로 넘겨주고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제에서는 Triangle을 2개 Rendering했고 각각 정점의 Color 값은 아래와 같이 설정했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;778&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o1Gqc/btsQVxmrVGI/8f67k4gOSB6FcalH2nbhVK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o1Gqc/btsQVxmrVGI/8f67k4gOSB6FcalH2nbhVK/img.png&quot; data-alt=&quot;Triangle 2개를 Render&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o1Gqc/btsQVxmrVGI/8f67k4gOSB6FcalH2nbhVK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo1Gqc%2FbtsQVxmrVGI%2F8f67k4gOSB6FcalH2nbhVK%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;1206&quot; height=&quot;778&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;778&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Triangle 2개를 Render&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1759213693377&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct PS_INPUT
{
    float4 Position : SV_Position;
    float4 Color : COLOR;
};

float4 main(PS_INPUT IN) : SV_TARGET
{
    return IN.Color;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;PixelShader&lt;/b&gt;를 위와 같이 구현하면 정점 사이에 있는 픽셀들의 &lt;b&gt;Color&lt;/b&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;이제 이 텍스쳐를 &lt;b&gt;Compute Shader&lt;/b&gt;를 통해서 그려보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Compute Shader&lt;/b&gt; 사용법에 대해서 간략하게 설명 드리기 위해 위와 같은 예제를 채택했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예제 요약&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제는 간략하게 다음과 같은 과정으로 진행됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) &lt;b&gt;텍스쳐 리소스&lt;/b&gt;를 만들기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 텍스쳐 리소스에 값을 쓰고 읽을 수 있도록 &lt;b&gt;UAV&lt;/b&gt;와 &lt;b&gt;SRV&lt;/b&gt;로 연결하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) &lt;b&gt;Compute PSO&lt;/b&gt;와 &lt;b&gt;RootSignature&lt;/b&gt;를 만들기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) &lt;b&gt;Dispatch&lt;/b&gt; &lt;b&gt;함수&lt;/b&gt;를 통해 Compute Shader를 돌려서 Texture에 값을 채우기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) &lt;b&gt;Graphics PSO&lt;/b&gt;와 &lt;b&gt;RootSignature&lt;/b&gt;를 만들기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6) &lt;b&gt;Draw 함수&lt;/b&gt;를 통해 Vertex Shader와 Pixel Shader를 돌려서 Texture의 값을 읽어서 화면에 보여주기&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예제 구현&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Texture를 만들고 &lt;b&gt;UAV(Unordered Access View)&lt;/b&gt;, &lt;b&gt;SRV(Shader Resource View)&lt;/b&gt;를 만드는 것은 어렵지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;device의 &lt;b&gt;CreateCommittedResource&lt;/b&gt; 함수를 호출해서 Texture Resource를 만들고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;device의 &lt;b&gt;CreateUnorderedAccessView&lt;/b&gt; 함수를 호출해서 UAV를 만들고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;device의 &lt;b&gt;CreateShaderResourceView&lt;/b&gt; 함수를 호출해서 SRV를 만들어서 방금 만든 Texture에 연결합니다.&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;UAV&lt;/b&gt;는 &lt;b&gt;ComputeShader&lt;/b&gt;에서 사용하며 연결된 &lt;b&gt;Texture&lt;/b&gt;에 값을 쓰는데 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SRV&lt;/b&gt;는 &lt;b&gt;PixelShader&lt;/b&gt;에서 사용하며 연결된 &lt;b&gt;Texture&lt;/b&gt;에 쓰인 값을 읽어 오는데 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Compute Shader&lt;/b&gt;를 사용하려면 &lt;b&gt;Compute&lt;/b&gt; 전용 &lt;b&gt;RootSignature&lt;/b&gt;와 &lt;b&gt;PSO(PipelineStateObject)&lt;/b&gt;를 생성해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;device의 &lt;b&gt;CreateRootSignature&lt;/b&gt; 함수를 호출해서&lt;b&gt; RootSignature&lt;/b&gt;를 만들고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;device의 &lt;b&gt;CreateComputePipelineState&lt;/b&gt; 함수를 호출해서 &lt;b&gt;PSO&lt;/b&gt;를 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PSO를 만들 때는 작성한 &lt;b&gt;Compute Shader&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;그 후 실제로 Dispatch를 하기 전에는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;commandList의 &lt;b&gt;SetComputeRootSignature&lt;/b&gt; 함수를 호출하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;commandList의 &lt;b&gt;SetComputeRootDescriptorTable&lt;/b&gt;등의 함수를 호출해서 Parameter를 설정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;commandList의 &lt;b&gt;SetPipelineState&lt;/b&gt; 함수로 아까 만든 PSO를 전달해서 셋팅을 마친 뒤 &lt;b&gt;Dispatch&lt;/b&gt; 함수를 호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dispatch&lt;/b&gt;를 하는 과정은 &lt;b&gt;Graphics Pipeline&lt;/b&gt;과 함수 몇 개만 다를 뿐 대체로 비슷한 과정을 거칩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미 &lt;b&gt;Graphics Pipeline&lt;/b&gt;을 통해서&lt;b&gt; Draw&lt;/b&gt;를 하는 코드가 있다면 어렵지 않게 &lt;b&gt;Compute Shader&lt;/b&gt;를 실행시킬 수 있을 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dispatch&lt;/b&gt; 함수는 아래와 같이 호출했으며&lt;/p&gt;
&lt;pre id=&quot;code_1759217125977&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;commandList-&amp;gt;Dispatch(WindowWidth / 32, WindowHeight / 18, 1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Compute Shader&lt;/b&gt;는 다음과 같이 정의하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1759212598616&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RWTexture2D&amp;lt;float4&amp;gt; OutTex : register(u0);

[numthreads(32, 18, 1)]
void main(uint3 DTid : SV_DispatchThreadID, 
          uint3 GTid : SV_GroupThreadID, 
          uint3 GID : SV_GroupID)
{
    OutTex[DTid.xy] = float4((float)DTid.x / 1280.0f, (float)DTid.y / 720.0f, 0.0f, 1.0f);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dispatch&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수가 호출되면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;ThreadGroup 개수 x ThreadGroup에 존재하는 Thread 개수&quot;&lt;/b&gt;만큼&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;ComputeShader&lt;/b&gt;의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Main&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수가 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dispatch&lt;/b&gt; 함수를 호출할 때 보내는 인자 3개가 &lt;b&gt;ThreadGroup&lt;/b&gt; 개수를 뜻하며, &lt;b&gt;Compute Shader&lt;/b&gt;의 &lt;b&gt;main&lt;/b&gt; 위에 있는 &lt;b&gt;numthreads&lt;/b&gt;에 사용된 인자 3개가 &lt;b&gt;&quot;Thread Group에 존재하는 Thread 개수&quot;&lt;/b&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;&lt;b&gt;Compute Shader&lt;/b&gt; 코드 가장 상단에 보이는 &lt;b&gt;RWTexture2D&amp;lt;float4&amp;gt; OutTex : register(u0)&lt;/b&gt;는 &lt;b&gt;UAV&lt;/b&gt;에 연결된 Texture를 선언한 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OutTex&lt;/b&gt;라는 이름으로 선언을 했고, &lt;b&gt;main&lt;/b&gt; 함수 내부에서 &lt;b&gt;OutTex[DTid.xy] = ...&lt;/b&gt; 구문을 통해 텍스쳐의 각 픽셀에 값을 썼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Compute Shader&lt;/b&gt; 함수 내부를 파악하기에 앞서 OutTex Texture에 어떤 값이 써졌는지 한 번 가시적으로 확인해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dispatch&lt;/b&gt;를 호출한 이후 해당 &lt;b&gt;Texture&lt;/b&gt;을 &lt;b&gt;Input&lt;/b&gt;으로 해서 &lt;b&gt;FullScreenQuad&lt;/b&gt;를 &lt;b&gt;Draw&lt;/b&gt;하면 &lt;b&gt;OutTex&lt;/b&gt;에 어떤 값이 써져있는지 확인할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1759326511416&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Texture2D gTex : register(t0);

struct PS_INPUT
{
    float4 Position : SV_Position;
    float4 Color : COLOR;
};

float4 main(PS_INPUT IN) : SV_TARGET
{
    float4 sampledColor = gTex.Load(int3(IN.Position.x, IN.Position.y, 0));
    return sampledColor;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FullScreenQuad&lt;/b&gt;를 Draw하는 &lt;b&gt;PixelShader&lt;/b&gt;는 위와 같이 구현하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 연결된 Texture로부터 값을 읽는 것 외에 아무것도 하지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Draw한 결과는 포스팅 처음에 보셨던 이미지입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;715&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cass9f/btsQTyMvqtP/EBu1SpONkfu7HU9ACrXw11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cass9f/btsQTyMvqtP/EBu1SpONkfu7HU9ACrXw11/img.png&quot; data-alt=&quot;픽셀의 위치에 따라 색이 다른 텍스쳐&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cass9f/btsQTyMvqtP/EBu1SpONkfu7HU9ACrXw11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcass9f%2FbtsQTyMvqtP%2FEBu1SpONkfu7HU9ACrXw11%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;1264&quot; height=&quot;715&quot; data-origin-width=&quot;1264&quot; data-origin-height=&quot;715&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;픽셀의 위치에 따라 색이 다른 텍스쳐&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;텍스쳐에 어떤 값이 써졌는지 색으로 확인했으므로 이제 다시 돌아가서 아까 보았던 &lt;b&gt;Compute Shader&lt;/b&gt; 내부 코드를 다시 살펴보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1759328856370&quot; class=&quot;angelscript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;RWTexture2D&amp;lt;float4&amp;gt; OutTex : register(u0);

[numthreads(32, 18, 1)]
void main(uint3 DTid : SV_DispatchThreadID, 
          uint3 GTid : SV_GroupThreadID, 
          uint3 GID : SV_GroupID)
{
    OutTex[DTid.xy] = float4((float)DTid.x / 1280.0f, (float)DTid.y / 720.0f, 0.0f, 1.0f);
}&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;처음보는 예약어 &lt;b&gt;numthreads&lt;/b&gt;가 있는데요&lt;/span&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;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;ThreadGroup&lt;/b&gt;이라는 개념을 알아야 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;ThreadGroup&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ThreadGroup&lt;/b&gt;이란 말 그대로 여러 개의 &lt;b&gt;Thread&lt;/b&gt;를 묶은 것을 뜻합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 &lt;b&gt;ThreadGroup&lt;/b&gt;에는 최대 &lt;b&gt;1024&lt;/b&gt;개의 &lt;b&gt;Thread&lt;/b&gt;를 묶을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Compute Shader&lt;/b&gt; 코드에서 &lt;b&gt;[numthreads(32, 18, 1)]&lt;/b&gt; 부분이 바로 하나의 &lt;b&gt;ThreadGroup&lt;/b&gt;에 존재하는 &lt;b&gt;Thread&lt;/b&gt; 개수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ThreadGroup&lt;/b&gt;에 있는 &lt;b&gt;Thread&lt;/b&gt;들은 &lt;b&gt;1차원&lt;/b&gt; 배열로 표현할 수 있지만, &lt;b&gt;2차원&lt;/b&gt;이나, &lt;b&gt;3차원&lt;/b&gt; 배열로 표현할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 총 &lt;b&gt;1024&lt;/b&gt;개의 Thread를 &lt;b&gt;1024 * 1 * 1&lt;/b&gt;로 표현할 수도 있고, &lt;b&gt;32 * 32 * 1&lt;/b&gt;이나, &lt;b&gt;32 * 16 * 2&lt;/b&gt;로 표현할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dispatch&lt;/b&gt; 함수에 인자로 들어가는 변수는 &lt;b&gt;ThreadGroup&lt;/b&gt;의 개수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제에서는 &lt;b&gt;(WindowWidth / 32, WindowHeight / 18, 1)&lt;/b&gt; 값을 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Window&lt;/b&gt;의 크기는 &lt;b&gt;1280, 720&lt;/b&gt;으로 각각 &lt;b&gt;32&lt;/b&gt;와 &lt;b&gt;18&lt;/b&gt;로 나누었을 때 &lt;b&gt;40&lt;/b&gt;이 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ThreadGroup&lt;/b&gt; 또한 &lt;b&gt;3차원&lt;/b&gt; 배열로 표현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ThreadGroup&lt;/b&gt;이 총 &lt;b&gt;64개&lt;/b&gt; 있다고 했을 때, &lt;b&gt;64 * 1 * 1&lt;/b&gt;로 표현할 수도 있고, &lt;b&gt;32 * 2 * 1&lt;/b&gt;이나, &lt;b&gt;16 * 2 * 2&lt;/b&gt;로 표현할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Dispatch&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수가 호출되면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;ThreadGroup 개수 * ThreadGroup에 존재하는 Thread 개수&quot;&lt;/b&gt;만큼&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;ComputeShader&lt;/b&gt;의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Main&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수가 호출됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 예제에서는 &lt;b&gt;ThreadGroup&lt;/b&gt; 개수는 &lt;b&gt;40 * 40&lt;/b&gt;개이고, &lt;b&gt;ThreadGroup&lt;/b&gt;에 존재하는 &lt;b&gt;Thread&lt;/b&gt; 개수는 &lt;b&gt;32 * 18&lt;/b&gt;개 이므로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;40 * 40 * 32 * 18 = 921,600&lt;/b&gt;번 호출되게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ThreadGroup&lt;/b&gt;이라는 개념이 생긴 이유는 하드웨어 효율을 높이기 위해서라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GPU&lt;/b&gt;의 실행 단위인 &lt;b&gt;Wavefront/Warps&lt;/b&gt;(AMD의 Wavefront는 64개, NVIDIA의 Warp는 32개) 개수에 맞춰서 &lt;b&gt;Thread&lt;/b&gt;를 묶으면 &lt;b&gt;SIMD&lt;/b&gt; 구조를 최대한 활용할 수 있기 때문이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ThreadGroup&lt;/b&gt;에 들어가는 &lt;b&gt;Thread&lt;/b&gt; 개수가 최대 1024개인 이유도 이와 관련이 있습니다.&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;ThreadGroup&lt;/b&gt;들과 그 안에 &lt;b&gt;Thread&lt;/b&gt;들을 표현할 때 &lt;b&gt;3차원&lt;/b&gt; 데이터로 표현하는 이유는, 그래픽스에서 &lt;b&gt;2차원&lt;/b&gt; 혹은 &lt;b&gt;3차원&lt;/b&gt; 데이터를 다룰 일이 많기 때문입니다. &lt;b&gt;2차원&lt;/b&gt;으로 표현하게 되면 &lt;b&gt;Texture&lt;/b&gt; 같은 데이터를 다룰 때 훨씬 간편하게 다룰 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;DispatchThreadID, GroupThreadID, GroupID&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 예제 코드로 돌아가서 &lt;b&gt;SV_DispatchThreadID, SV_GroupThreadID, SV_GroupID&lt;/b&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;&lt;b&gt;SV_DispatchThreadID:&lt;/b&gt; 모든 쓰레드 그룹의 쓰레드들 사이에서 하나의 쓰레드에 유일하게 정의된 ID입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SV_GroupThreadID:&lt;/b&gt; 하나의 쓰레드 그룹의 쓰레드들 사이에서 하나의 쓰레드에 유일하게 정의된 ID입니다. 다른 쓰레드 그룹에 같은 SV_GroupThreadID 값을 가진 쓰레드가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SV_GroupID:&lt;/b&gt; 모든 쓰레드 그룹 내에서 하나의 그룹에 유일하게 정의된 ID입니다, 그 내부의 모든 쓰레드는 같은 SV_GroupID 값을 가집니다.&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개의 변수에 대한 관계식은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SV_DispatchThreadID.x = SV_GroupID.x * numthreads.x + SV_GroupThreadID.x&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;y축이나 z축도 똑같이 계산이 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SV_DispatchThreadID.y = SV_GroupID.y * numthreads.y + SV_GroupThreadID.y&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SV_DispatchThreadID.z = SV_GroupID.z * numthreads.z + SV_GroupThreadID.z&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;따라서 사실 &lt;b&gt;SV_GroupThreadID&lt;/b&gt; 값과 &lt;b&gt;SV_GroupID&lt;/b&gt; 값, &lt;b&gt;numthreads&lt;/b&gt; 값만 있으면 &lt;b&gt;SV_DispatchThreadID&lt;/b&gt; 값을 유추할 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;각 변수의 의미를 좀 더 파악하기 위해 아래와 같은 예제를 만들었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1759240267904&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RWTexture2D&amp;lt;float4&amp;gt; OutTex : register(u0);

[numthreads(32, 18, 1)]
void main(uint3 DTid : SV_DispatchThreadID, 
          uint3 GTid : SV_GroupThreadID, 
          uint3 GID : SV_GroupID)
{
    OutTex[DTid.xy] = float4((float)GTid.x / 32.0f, (float)GTid.y / 18.0f, 0.0f, 1.0f);
}&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;OutTex[DTid.xy]&lt;/b&gt;의 뜻을 해석해보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OutTex&lt;/b&gt;는 &lt;b&gt;RWTexture2D&lt;/b&gt; 타입이므로 인덱스를 &lt;b&gt;2차원&lt;/b&gt; 형태로 받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DTid.xy(SV_DispatchThreadID)&lt;/b&gt; 값은 모든 그룹의 모든 쓰레드 사이에서 유일하게 정의된다고 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;commandList-&amp;gt;Dispatch(WindowWidth&amp;nbsp;/&amp;nbsp;32,&amp;nbsp;WindowHeight&amp;nbsp;/&amp;nbsp;18,&amp;nbsp;1);&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 형태로 Dispath 함수를 호출하고 &lt;b&gt;WindowWidth&lt;/b&gt;가 &lt;b&gt;1280&lt;/b&gt;, &lt;b&gt;WindowHeight&lt;/b&gt;가 &lt;b&gt;720&lt;/b&gt;이므로 쓰레드 그룹의 갯수는 &lt;b&gt;X축 40개&lt;/b&gt;, &lt;b&gt;Y축 40개&lt;/b&gt;가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[numthreads(32, 18, 1)] &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 형태로 &lt;b&gt;ComputeShader&lt;/b&gt;를 정의했으므로 한 그룹당 쓰레드 개수는 &lt;b&gt;X축 32&lt;/b&gt;개, &lt;b&gt;Y축 18&lt;/b&gt;개입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;X축&lt;/b&gt; 총 쓰레드 개수는 &lt;b&gt;1280개&lt;/b&gt;이고, &lt;b&gt;Y축&lt;/b&gt; 총 쓰레드 개수는 &lt;b&gt;720개&lt;/b&gt; 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;DTid.xy&lt;/b&gt;는 화면의 모든 픽셀을 1대 1로 대응하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등호 오른쪽을 보면 &lt;b&gt;GTid.x&lt;/b&gt;를 &lt;b&gt;32&lt;/b&gt;로 나눈 값과, &lt;b&gt;GTid.y&lt;/b&gt;를 &lt;b&gt;18&lt;/b&gt;로 나눈 값을 각각 x, y 값으로 넘겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GTid.x&lt;/b&gt;를 &lt;b&gt;32&lt;/b&gt;로 나눈 이유는 &lt;b&gt;GTid.x&lt;/b&gt; 값의 범위가&lt;b&gt; 0~31&lt;/b&gt;이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GTid.y&lt;/b&gt;를 &lt;b&gt;18&lt;/b&gt;로 나눈 이유 역시 &lt;b&gt;GTid.y&lt;/b&gt; 값의 범위가&lt;b&gt; 0~17&lt;/b&gt;이기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GTid(GroupThreadID)&lt;/b&gt;는 하나의 쓰레드 그룹의 쓰레드들 사이에서 유일하게 정의된 쓰레드라고 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 다른 나머지 쓰레드 그룹에도 같은 ID를 가진 쓰레드가 있다는 말입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로 &lt;b&gt;GTid&lt;/b&gt;를 이용해서 값을 채우게 되면 모든 쓰레드 그룹 각각이 담당하는 픽셀에 같은 값이 채워지게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 아래와 같은 이미지가 나오게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1275&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/O7xCh/btsQWh4Bdko/UUVzlqrkPZPTPwbYgofU71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/O7xCh/btsQWh4Bdko/UUVzlqrkPZPTPwbYgofU71/img.png&quot; data-alt=&quot;GTid를 이용해서 값을 채운 텍스쳐&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/O7xCh/btsQWh4Bdko/UUVzlqrkPZPTPwbYgofU71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FO7xCh%2FbtsQWh4Bdko%2FUUVzlqrkPZPTPwbYgofU71%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;1275&quot; height=&quot;720&quot; data-origin-width=&quot;1275&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;GTid를 이용해서 값을 채운 텍스쳐&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GTid&lt;/b&gt;를 사용했으므로 마지막으로 &lt;b&gt;GID&lt;/b&gt;도 사용해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 &lt;b&gt;Compute Shader&lt;/b&gt;를 정의했습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1759240287938&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RWTexture2D&amp;lt;float4&amp;gt; OutTex : register(u0);

[numthreads(32, 18, 1)]
void main(uint3 DTid : SV_DispatchThreadID, 
          uint3 GTid : SV_GroupThreadID, 
          uint3 GID : SV_GroupID)
{
    OutTex[DTid.xy] = float4((float)GID.x / 40.0f, (float)GID.y / 40.0f, 0.0f, 1.0f);
}&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;&lt;b&gt;등호 우변&lt;/b&gt;을 &lt;b&gt;GTid&lt;/b&gt; 대신 &lt;b&gt;GID&lt;/b&gt;를 사용했을 뿐입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;GID&lt;/b&gt;는 그룹 마다 다른 값을 가지므로 아래와 같은 이미지가 나오게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNKSRn/btsQVVAAW2E/pRKnkUFe5Nc0gEMUvWY7YK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNKSRn/btsQVVAAW2E/pRKnkUFe5Nc0gEMUvWY7YK/img.png&quot; data-alt=&quot;GID를 이용해서 값을 채운 텍스쳐&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNKSRn/btsQVVAAW2E/pRKnkUFe5Nc0gEMUvWY7YK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNKSRn%2FbtsQVVAAW2E%2FpRKnkUFe5Nc0gEMUvWY7YK%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;1278&quot; height=&quot;720&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;GID를 이용해서 값을 채운 텍스쳐&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지 &lt;b&gt;Compute Shader&lt;/b&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;Compute Shader&lt;/b&gt;를 정의할 때 인덱스를 다루는 부분이 어렵기는 하지만&amp;nbsp;제대로 활용하면 막강한 &lt;b&gt;GPU 자원&lt;/b&gt;을 이용해서 많은 일을 할 수 있을 것이라고 생각합니다.&lt;/p&gt;</description>
      <category>프로그래밍/DirectX12</category>
      <category>compute shader</category>
      <category>ComputeShader</category>
      <category>DirectX12</category>
      <category>DX12</category>
      <category>gpgpu</category>
      <category>gpu</category>
      <category>렌더링</category>
      <category>병렬처리</category>
      <category>최적화</category>
      <category>컴퓨트 셰이더</category>
      <author>merit nada</author>
      <guid isPermaLink="true">https://mathmakeworld.tistory.com/126</guid>
      <comments>https://mathmakeworld.tistory.com/126#entry126comment</comments>
      <pubDate>Tue, 12 Aug 2025 17:35:01 +0900</pubDate>
    </item>
    <item>
      <title>DirectX12를 활용한 Deferred Shading</title>
      <link>https://mathmakeworld.tistory.com/125</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 시간에는 &lt;b&gt;DirectX12&lt;/b&gt;를 활용해서 &lt;b&gt;Deferred Shading&lt;/b&gt;을 하는 방법에 대해서 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 단어를 분석해보겠습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&quot;&lt;b&gt;Deferred&quot;&lt;/b&gt;는 &lt;b&gt;&quot;지연된&quot;&lt;/b&gt;이라는 의미입니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&quot;Shading&quot;&lt;/b&gt;은 &lt;b&gt;&quot;음영 처리&quot;&lt;/b&gt;라는 의미입니다. 컴퓨터 그래픽스 분야에서는 조명에 따라 픽셀을 어떤 색으로 칠할지 정하는 것을 의미합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;&quot;Deferred Shading&quot;&lt;/b&gt;은 말 그대로 &lt;b&gt;&quot;조명 계산을 지연시켜 나중에 처리하는 렌더링 방식&quot;&lt;/b&gt;을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Deferred Shading&lt;/b&gt;을 사용하는 이유는 기존 방식인 &lt;b&gt;Forward Shading&lt;/b&gt;과 비교해서 장점이 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 장점이 있는지 알아보기 위해 &lt;b&gt;Deferred Shading&lt;/b&gt;의 반대되는 개념인 &lt;b&gt;Forward Shading&lt;/b&gt;을 먼저 알아봅시다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Deferred Shading vs Forward Shading&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Forward Shading&lt;/b&gt;은 각 오브젝트를 렌더링할 때 &lt;b&gt;조명 계산까지 포함하여 한 번에 처리&lt;/b&gt;하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 여러 개의 물체와 여러 개의 조명이 있는 경우를 생각해봅시다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;b&gt;Forward Shading&lt;/b&gt;에서는 물체 하나를 그릴 때 한 번의 렌더링 &lt;b&gt;Pass&lt;/b&gt;가 돌아서 &lt;b&gt;Vertex Shader&lt;/b&gt;와 &lt;b&gt;Pixel Shader&lt;/b&gt;가 동작합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pixel Shader&lt;/b&gt;에서는 모든 조명에 대해서 연산을 하고 해당 픽셀에 끼치는 영향을 합산해서 현재 픽셀의 색을 정합니다.&lt;/li&gt;
&lt;li&gt;위 과정을 모든 물체에 대해서 반복합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 위와 같이 조명 처리를 하게 되면, 다른 물체에 가려져서 화면에 보이지 않는 픽셀에 대해서도 조명 계산이 수행될 수 있기 때문에 비효율적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시 이미지에서 보이 듯이 9개의 물체를 그리기 위해서, 총 9번의 DrawCall이 있었고, 각 DrawCall에서 한 물체의 Shading까지 모두 끝냈습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2718&quot; data-origin-height=&quot;573&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQTiyn/btsPKTvY08h/RGg6qw5jdhA6aKsyYXqky1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQTiyn/btsPKTvY08h/RGg6qw5jdhA6aKsyYXqky1/img.png&quot; data-alt=&quot;Forward Shading&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQTiyn/btsPKTvY08h/RGg6qw5jdhA6aKsyYXqky1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQTiyn%2FbtsPKTvY08h%2FRGg6qw5jdhA6aKsyYXqky1%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;2718&quot; height=&quot;573&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2718&quot; data-origin-height=&quot;573&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Forward Shading&lt;/figcaption&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;반면 Deferred Shading은 &lt;b&gt;렌더링과 조명 처리를 분리&lt;/b&gt;하여 진행하는 방식입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1009&quot; data-start=&quot;899&quot;&gt;먼저 &lt;b&gt;G-Buffer&lt;/b&gt;라는 일련의 텍스처에 &lt;b&gt;Position, Normal, Color, Depth 등 오브젝트의 기초 정보를 렌더링&lt;/b&gt;합니다. 이 과정을 &lt;b&gt;Base Pass&lt;/b&gt;라고 합니다.&lt;/li&gt;
&lt;li data-end=&quot;1106&quot; data-start=&quot;1010&quot;&gt;이후 조명 계산은 &lt;b&gt;G-Buffer&lt;/b&gt;를 입력으로 받아 &lt;b&gt;Full-Screen Pass를 한 번만 돌려서 수행&lt;/b&gt;됩니다. 이 과정을 &lt;b&gt;Lighting Pass&lt;/b&gt;라고 부릅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방식으로 렌더링을 하면 다음과 같은 장점이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1193&quot; data-start=&quot;1132&quot;&gt;결론적으로 최종 화면에서 가려지는 픽셀은 이미 &lt;b&gt;Base Pass&lt;/b&gt;에서 처리가 되기 때문에, 화면에 표시되는 픽셀에 대해서만 조명 연산을 수행하므로 불필요한 계산을 줄일 수 있습니다.&lt;/li&gt;
&lt;li data-end=&quot;1250&quot; data-start=&quot;1194&quot;&gt;&lt;b&gt;모든 물체의 조명 연산을 한 번의 Pixel Shader에서 처리&lt;/b&gt;하므로, &lt;b&gt;GPU의 병렬 처리 성능&lt;/b&gt;을 극대화할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시 이미지를 보시면 &lt;b&gt;Base Pass&lt;/b&gt;에서는 9번의 DrawCall을 통해 &lt;b&gt;Position, Normal, Color&lt;/b&gt; 텍스쳐를 만들었고, &lt;b&gt;Lighting Pass&lt;/b&gt;에서 한 번에 조명 연산을 끝낸 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제처럼 &lt;b&gt;조명 연산이 크게 무겁지 않은 경우&lt;/b&gt;는 별 차이가 없을지 모르지만, 조명의 개수가 많고 계산식이 복잡한 경우에는 &lt;b&gt;조명 계산하는 픽셀을 줄이는 것&lt;/b&gt;이 최적화에 많은 도움이 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2432&quot; data-origin-height=&quot;517&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckPji5/btsPIDtDnDu/fKhhtrQNKD25sm8wveDJv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckPji5/btsPIDtDnDu/fKhhtrQNKD25sm8wveDJv0/img.png&quot; data-alt=&quot;Base Pass&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckPji5/btsPIDtDnDu/fKhhtrQNKD25sm8wveDJv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckPji5%2FbtsPIDtDnDu%2FfKhhtrQNKD25sm8wveDJv0%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;2432&quot; height=&quot;517&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2432&quot; data-origin-height=&quot;517&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Base Pass&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2322&quot; data-origin-height=&quot;470&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6s0Y6/btsPI9Tg5nG/mQkWol1Vui6eqpY7PKaXc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6s0Y6/btsPI9Tg5nG/mQkWol1Vui6eqpY7PKaXc0/img.png&quot; data-alt=&quot;Lighting Pass&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6s0Y6/btsPI9Tg5nG/mQkWol1Vui6eqpY7PKaXc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6s0Y6%2FbtsPI9Tg5nG%2FmQkWol1Vui6eqpY7PKaXc0%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;2322&quot; height=&quot;470&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2322&quot; data-origin-height=&quot;470&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Lighting Pass&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만 &lt;b&gt;Deferred Shading&lt;/b&gt;이 장점만 있는 것은 아닙니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일단 별도의 Texture들을 여러 장 만들고 유지해야 하기 때문에 &lt;b&gt;Forward Shading&lt;/b&gt;에 비해 &lt;b&gt;많은 VRAM&lt;/b&gt;이 필요합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Deferred Shading에서는 투명 물체 표현이 어렵습니다.&lt;/b&gt; 투명한 물체를 표현하려면 뒤쪽에 있는 물체의 색도 고려해야 하는데, G-Buffer는 일반적으로 최종적으로 그려지게 될 가장 앞쪽의 있는 물체의 정보만 저장하기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;구현&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1609&quot; data-start=&quot;1565&quot; data-ke-size=&quot;size16&quot;&gt;이제 본격적으로 &lt;b&gt;Deferred Shading&lt;/b&gt;을 어떻게 구현하는지 알아보겠습니다.&lt;/p&gt;
&lt;p data-end=&quot;1609&quot; data-start=&quot;1565&quot; data-ke-size=&quot;size16&quot;&gt;아까도 언급했지만 &lt;b&gt;Deferred Shading&lt;/b&gt; 구현은 일반적으로 다음과 같이 &lt;b&gt;2단계&lt;/b&gt;로 구성됩니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1829&quot; data-start=&quot;1611&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;1706&quot; data-start=&quot;1611&quot;&gt;&lt;b&gt;Base Pass&lt;/b&gt;: 각 오브젝트를 렌더링하면서 G-Buffer에 Position, Normal, Color, Depth 등의 정보를 저장합니다.&lt;/li&gt;
&lt;li data-end=&quot;1829&quot; data-start=&quot;1708&quot;&gt;&lt;b&gt;Lighting Pass&lt;/b&gt;: G-Buffer를 입력으로 받아, 전체 화면을 대상으로 조명 연산을 수행합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 과정을 요약해서 도식화 하면 아래와 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2839&quot; data-origin-height=&quot;645&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b39RH1/btsPJbdzawt/x5oHdxk4JC0G8NG17WkkU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b39RH1/btsPJbdzawt/x5oHdxk4JC0G8NG17WkkU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b39RH1/btsPJbdzawt/x5oHdxk4JC0G8NG17WkkU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb39RH1%2FbtsPJbdzawt%2Fx5oHdxk4JC0G8NG17WkkU0%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;2839&quot; height=&quot;645&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2839&quot; data-origin-height=&quot;645&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Base Pass&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Base Pass에서 신경써야 할 부분은 &lt;b&gt;MRT(Multi Render Target)&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Position Map에 Draw&lt;/b&gt;하고, &lt;b&gt;Normal Map에 Draw&lt;/b&gt;하고, &lt;b&gt;Color Map에 Draw&lt;/b&gt;를 하는 것도 가능하지만 기왕이면 &lt;b&gt;한 번 Draw&lt;/b&gt;를 할 때 &lt;b&gt;3개의 Map&lt;/b&gt;에 &lt;b&gt;Draw&lt;/b&gt;를 하는 것이 효율적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MRT&lt;/b&gt;를 사용하는 방법을 간단하게 요약하면 아래와 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;MRT로 사용할 &lt;b&gt;Texture(Resource)&lt;/b&gt;를 만듭니다. &lt;b&gt;device-&amp;gt;CreateCommitedResource&lt;/b&gt; 함수를 사용하면 됩니다.&lt;/li&gt;
&lt;li&gt;Texture를 Render Target 용도로 사용하기 위해 &lt;b&gt;Descriptor&lt;/b&gt;를 만듭니다. &lt;b&gt;device-&amp;gt;CreateRenderTargetView&lt;/b&gt; 함수를 사용하면 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PSO(Pipeline State Object)&lt;/b&gt;에 MRT 관련 정보를 넘겨줍니다. &lt;b&gt;device-&amp;gt;CreateGraphicsPipelineState&lt;/b&gt; 함수를 호출할 때 사용하는 psoDesc의 &lt;b&gt;NumRenderTargets&lt;/b&gt; 값을 수정하고, 각 &lt;b&gt;RTVFormats&lt;/b&gt;에 값을 설정해줍니다.&lt;/li&gt;
&lt;li&gt;Draw하기 전에 현재 Pass에서 어떤 Render Target에 그릴지 정합니다. &lt;b&gt;commandList-&amp;gt;OMSetRenderTargets&lt;/b&gt; 함수를 사용하면 됩니다.&lt;/li&gt;
&lt;li&gt;MRT로 사용할 Texture들의 &lt;b&gt;Resource Barrier&lt;/b&gt; 상태를 적절히 설정해줍니다. Render Target용으로 쓰일 때와 Shader Resource로 쓰일 때 각각 다르게 설정합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Pixel Shader&lt;/b&gt;에서 MRT에 데이터를 쓸 수 있도록 내용을 정의해줍니다. PixelShader에 Outpue용 구조체를 정의하고 각 변수에 &lt;b&gt;SV_TargetN&lt;/b&gt; 이라는 키워드를 추가해줍니다.&lt;/li&gt;
&lt;li&gt;모든 내용이 준비되었다면 &lt;b&gt;DrawCall&lt;/b&gt;을 합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 고려해야 할 부분은 여러 객체를 그릴 것이므로 객체별로 다른 &lt;b&gt;Model Matrix&lt;/b&gt;를 보내주는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CBV&lt;/b&gt;를 여러 개 만들고 &lt;b&gt;Root Paramter&lt;/b&gt;를 이용해서 데이터를 넘길 때 적절하게 설정해 주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 예제에서는 물체의 개수만큼 &lt;b&gt;Constant Buffer&lt;/b&gt;를 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 &lt;b&gt;Constant Buffer&lt;/b&gt;에는 &lt;b&gt;Model Matrix&lt;/b&gt; 정보를 담아두었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 같은 수의 &lt;b&gt;Constant Buffer View&lt;/b&gt;를 &lt;b&gt;Heap&lt;/b&gt;에 생성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Root Signature&lt;/b&gt;를 생성할 때는 &lt;b&gt;Descriptor Table&lt;/b&gt; 타입의 &lt;b&gt;Root Parameter&lt;/b&gt;를 하나만 만들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;Draw&lt;/b&gt;를 진행할 때 &lt;b&gt;SetGraphicsRootDescriptorTable&lt;/b&gt; 함수를 통해서 해당 &lt;b&gt;Root Parameter&lt;/b&gt;가 가리키는 D&lt;b&gt;escriptor Heap&lt;/b&gt;의 위치를 매번 알맞은 위치로 설정해주는 방식을 통해 여러 개의 &lt;b&gt;CBV&lt;/b&gt;에 담긴 &lt;b&gt;Model Matrix&lt;/b&gt; 정보를 사용할 수 있도록 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DrawInstanced Draw&lt;/b&gt;를 통해 같은 타입의 메쉬를 한 번에 그릴 수도 있지만 일단 현재 예제에서는 각각 &lt;b&gt;Draw&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;b&gt;Base Pass&lt;/b&gt;에서 쓰인 &lt;b&gt;Vertex Shader&lt;/b&gt;와 &lt;b&gt;Pixel Shader&lt;/b&gt;의 함수 내용은 아래와 같습니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1754531804040&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PS_INPUT main(VS_INPUT IN)
{
    PS_INPUT OUT;
    
    float4 WorldPosition = mul(M, float4(IN.Position.xyz, 1.0f));
    float4 ViewPosition = mul(V, float4(WorldPosition.xyz, 1.0f));

    OUT.Position = mul(P, float4(ViewPosition.xyz, 1.0f));
    OUT.Normal = float4(IN.Normal, 0.0f);

    float4x4 normalMatrix = transpose(inverse(M));

    OUT.WorldPosition = WorldPosition;
    OUT.WorldNormal = mul(normalMatrix, float4(IN.Normal, 0.0f));
    OUT.Color = float4(IN.Color, 0.0f);

    return OUT;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vertex Shader&lt;/b&gt;에서는 일단 물체를 랜더링하기 위해 입력 &lt;b&gt;Position&lt;/b&gt;에 &lt;b&gt;MVP&lt;/b&gt;를 곱해서&lt;b&gt; Clip Space&lt;/b&gt;에서의 &lt;b&gt;Position&lt;/b&gt;을 구해 넘겼습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;추가적으로 &lt;b&gt;Position&lt;/b&gt;에 &lt;b&gt;Model Matrix&lt;/b&gt;만을 곱해서 &lt;b&gt;WorldPosition&lt;/b&gt;을 만들어서 보냈고, &lt;b&gt;Normal&lt;/b&gt;에 &lt;b&gt;Model Matrix&lt;/b&gt;의 &lt;b&gt;inverse&lt;/b&gt;의 &lt;b&gt;transpose&lt;/b&gt;한 것을 보내서 &lt;b&gt;WorldNormal&lt;/b&gt;을 보냈습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Color&lt;/b&gt; 값은 변환없이 그대로 보냈습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Position과 Normal을 넘길 때 &lt;b&gt;World Space&lt;/b&gt; 값을 넘기든, &lt;b&gt;View Space&lt;/b&gt; 값을 넘기든 상관은 없습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다만, 나중에 있을 &lt;b&gt;Lighting Pass&lt;/b&gt;에서 조명 처리를 할 때 쓰는 &lt;b&gt;Light 정보&lt;/b&gt;, &lt;b&gt;Eye Position 정보&lt;/b&gt;등과 &lt;b&gt;Space&lt;/b&gt;를 맞춰 주기만 하면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1754534085618&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PS_OUTPUT main(PS_INPUT IN) : SV_TARGET
{
    PS_OUTPUT o;

    o.BaseColor = IN.Color;
    o.Position = IN.WorldPosition;
    o.Normal = IN.WorldNormal;
    
    return o;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pixel Shader에서는 단순히 입력 받은 정보들을 넘기는 용도로 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Graphics Pipeline을 구성할 때 3개의 Render Targets에 쓰도록 구성했으므로 &lt;b&gt;3개&lt;/b&gt;에 &lt;b&gt;Texture&lt;/b&gt;에 값이 넘어가게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 이미지는 모든 객체를 그리고 나서의 Position Map, Color Map, Normal Map입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1658&quot; data-origin-height=&quot;974&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o6Epd/btsPKfsGrHF/oKR5Hp7M6c1HZwJC1zy7mk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o6Epd/btsPKfsGrHF/oKR5Hp7M6c1HZwJC1zy7mk/img.png&quot; data-alt=&quot;좌상단 Color, 우상단 Position, 좌하단 Normal, 우하단 Depth&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o6Epd/btsPKfsGrHF/oKR5Hp7M6c1HZwJC1zy7mk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo6Epd%2FbtsPKfsGrHF%2FoKR5Hp7M6c1HZwJC1zy7mk%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;1658&quot; height=&quot;974&quot; data-origin-width=&quot;1658&quot; data-origin-height=&quot;974&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;좌상단 Color, 우상단 Position, 좌하단 Normal, 우하단 Depth&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Depth Map도 사용하고 있었으므로 Depth Map도 그려집니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Lighting Pass&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lighting Pass는 별도의 Pass이기 때문에 별도의 &lt;b&gt;Graphics Pipeline State Object&lt;/b&gt;, &lt;b&gt;Root Signature,&lt;/b&gt;&amp;nbsp;&lt;b&gt;Vertex Shader, Pixel Shader&lt;/b&gt;가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Draw 대상은 Post Processing를 구현할 때처럼 &lt;b&gt;FullScreen Quad&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;Lighting Pass에서 따로 신경써야 할 부분은 &lt;b&gt;G-Buffer&lt;/b&gt;를 &lt;b&gt;Shader&lt;/b&gt;에 제대로 넘겨주는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 만들었던 &lt;b&gt;G-Buffer Texture&lt;/b&gt;들에 &lt;b&gt;SRV(Shader Resource View)&lt;/b&gt;를 만들어서 연결해 주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;Root Paramter&lt;/b&gt;로 &lt;b&gt;SRV&lt;/b&gt;들을 연결해서 &lt;b&gt;Shader&lt;/b&gt;로 넘겨주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Shader&lt;/b&gt;에서는 만들어진 &lt;b&gt;G-Buffer&lt;/b&gt;에서 값들을 샘플링하고 &lt;b&gt;Space&lt;/b&gt;를 맞춘 뒤 &lt;b&gt;Lighting 연산&lt;/b&gt;을 진행하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Lighting Pass&lt;/b&gt;에서 쓰인 &lt;b&gt;Vertex Shader&lt;/b&gt;와 &lt;b&gt;Pixel Shader&lt;/b&gt;의 함수 내용은 아래와 같습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1754530071617&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;PS_INPUT main(VS_INPUT IN)
{
    PS_INPUT OUT;
    OUT.Position = float4(IN.Position, 1.0f);
    OUT.UV = IN.UV;
    return OUT;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Vertex Shader&lt;/b&gt;에서는 단순히 &lt;b&gt;Position&lt;/b&gt;과 &lt;b&gt;UV 값&lt;/b&gt;을 입력 받아 넘겼습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FullScreenQuad&lt;/b&gt;를 그릴 것이므로 &lt;b&gt;Position&lt;/b&gt;과 &lt;b&gt;UV 값&lt;/b&gt;에 별도의 &lt;b&gt;Transform&lt;/b&gt; 없이 그대로 넘겼습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1754530159279&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;float4 main(PS_INPUT IN ) : SV_TARGET
{
    float3 pos = positionMap.Sample(positionMapSampler, IN.UV.xy).rgb;
    float3 normal = normalMap.Sample(normalMapSampler, IN.UV.xy).rgb;
    float3 color = colorMap.Sample(colorMapSampler, IN.UV.xy).rgb;

    normal = normalize(normal);

    float3 lightDir = normalize(-LightDir.rgb);
    float3 viewDir = normalize(EyePosition.xyz - pos);

    float3 diffuse = max(dot(normal, lightDir), 0.0) * LightColor;
    float3 halfWayDir = normalize(lightDir + viewDir);
    float spec = pow(max(dot(normal, halfWayDir), 0.0f), 32.0f);
    float3 specular = LightColor * spec;

    float3 resColor = diffuse + specular;

    return float4(resColor, 1.0f);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pixel Shader&lt;/b&gt;에서는 &lt;b&gt;Phong Shading&lt;/b&gt; 방식으로 조명 처리를 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Directional Light&lt;/b&gt;가 하나 있다고 가정했고 가장 단순한 조명 처리를 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 가장 먼저 &lt;b&gt;positionMap, normalMap, colorMap&lt;/b&gt;으로부터 데이터를 얻었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 &lt;b&gt;Light&lt;/b&gt; 데이터와 &lt;b&gt;EyePosition&lt;/b&gt; 데이터를 이용해서, &lt;b&gt;lightDir, viewDir&lt;/b&gt;등을 구했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;lightDir, viewDir, normal&lt;/b&gt;이 구해졌으므로 조명 처리를 위한 벡터는 모두 구한 셈입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;normal&lt;/b&gt;과 &lt;b&gt;lightDir&lt;/b&gt;을 통해 &lt;b&gt;diffuse&lt;/b&gt; 값을 구하고, &lt;b&gt;lightDir&lt;/b&gt;과 &lt;b&gt;viewDir&lt;/b&gt;을 통해서 &lt;b&gt;specular&lt;/b&gt; 값을 구했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로는 &lt;b&gt;diffuse&lt;/b&gt; 값과 &lt;b&gt;specular&lt;/b&gt; 값을 더해서 최종적으로 그려질 색을 결정했습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;1999&quot; data-start=&quot;1903&quot; data-ke-size=&quot;size16&quot;&gt;이번 시간에는 &lt;b&gt;Deferred Shading의 개념과 Forward Shading과의 차이점&lt;/b&gt;, 그리고 &lt;b&gt;DirectX 12에서 구현 방식&lt;/b&gt;에 대해 살펴보았습니다.&lt;/p&gt;
&lt;p data-end=&quot;2141&quot; data-start=&quot;2001&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Deferred Shading&lt;/b&gt;은 &lt;b&gt;Unreal Engine&lt;/b&gt;을 포함한 최신 게임 엔진에서 기본적으로 채택하고 있는 방식입니다.&lt;br /&gt;복잡한 장면에서 효율적인 조명 처리를 가능하게 해주며, 다양한 조명 조건에서도 성능을 유지하는 데 큰 도움이 됩니다.&lt;/p&gt;
&lt;p data-end=&quot;2234&quot; data-start=&quot;2172&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2234&quot; data-start=&quot;2172&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Deferred Shading&lt;/b&gt;의 단점을 보완하거나 성능을 높이기 위한 기술들로는 다음과 같은 방법들이 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2452&quot; data-start=&quot;2236&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2295&quot; data-start=&quot;2236&quot;&gt;&lt;b&gt;Tiled Deferred Lighting&lt;/b&gt;: 조명 처리를 타일 단위로 병렬화하여 성능을 향상시킴&lt;/li&gt;
&lt;li data-end=&quot;2377&quot; data-start=&quot;2296&quot;&gt;&lt;b&gt;Clustered Deferred Shading&lt;/b&gt;: 타일보다 더 유연한 방식으로, 뷰 프러스텀을 클러스터로 나누어 조명을 효율적으로 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 포스팅에서는 다루지 않았지만 나중에 기회가 있다면 위 방식들도 더 진행해보도록 하겠습니다.&lt;/p&gt;</description>
      <category>프로그래밍/DirectX12</category>
      <author>merit nada</author>
      <guid isPermaLink="true">https://mathmakeworld.tistory.com/125</guid>
      <comments>https://mathmakeworld.tistory.com/125#entry125comment</comments>
      <pubDate>Tue, 22 Jul 2025 15:05:53 +0900</pubDate>
    </item>
    <item>
      <title>Dicey Tales 게임 후기</title>
      <link>https://mathmakeworld.tistory.com/124</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Dicey Tales라는 게임을 플레이 해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://store.steampowered.com/app/3279980/Dicey_Tales/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://store.steampowered.com/app/3279980/Dicey_Tales/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그라이크 요소와 SRPG를 합치면 어떤 모습일까 궁금했었는데 딱 궁금증을 해결해줄만한 게임이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임을 간단히 설명하고 게임을 플레이하면서 느꼈던 장점과 단점을 적어보고자 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;게임 규칙&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임 규칙은 전투 파트와 비전투 파트를 나눠서 설명하도록 하겠습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;게임 규칙 - 전투 파트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전투에 돌입하면 7x7 정도 되는 그리드에 적들이 배치되어 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;337&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwyC7U/btsOo790soO/wujJ48EF4ymEveKffqIG90/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwyC7U/btsOo790soO/wujJ48EF4ymEveKffqIG90/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwyC7U/btsOo790soO/wujJ48EF4ymEveKffqIG90/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwyC7U%2FbtsOo790soO%2FwujJ48EF4ymEveKffqIG90%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;600&quot; height=&quot;337&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;337&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맵의 크기는 전투마다 다르며 중간 중간 장애물이나 오브젝트가 배치되어 있는 경우도 있으나 흔치 않습니다.&lt;/p&gt;
&lt;p 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;/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;아군의 턴이 되면 주사위를 굴려 할 수 있는 행동들이 결정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주인공 캐릭터는 여러 개의 주사위를 가지고 있고, 모험 도중 합류한 용병 캐릭터는 한 개의 주사위를 가지고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 주사위를 한 번에 굴리며 주사위 하나 당 행동 하나가 결정됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결정된 행동을 누가 하게 될지는 플레이어가 골라서 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 주사위라고 할지라도 다른 유닛에게 할당하면 다른 행동을 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 한 명의 유닛이 한 턴에 할 수 있는 행동 수는 제한이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 주사위는 스킬을 쓸 때 재료로 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유닛마다 하나의 스킬을 가지고 있으며 스킬을 쓸 때 필요한 재료도 다릅니다.&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;한 턴에 2번 1개의 주사위를 다시 굴릴 수 있는 기회가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유닛들이 행동을 하다보면 적들의 다음 행동이 변화하는 경우가 있습니다.&lt;/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;적들의 행동이 끝나고 나면 다시 다음 턴 행동을 예견한 뒤 플레이어 턴이 됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;게임 규칙 - 비전투 파트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게임은 총 3개의 챕터로 이루어져 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 챕터마다 보스가 있으며 보스를 클리어하면 다음 챕터로 넘어갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;일반 적과, 정예 적, 보스를 조우해서 승리시에는 아군들 중 레벨이 낮은 3명을 골라서 그 중 하나를 레벨업 시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유닛을 레벨업시키면 보통은 스탯이 오르고, 특정 레벨에 도달하면 스킬을 강화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킬 강화를 할 수 있는 선택지는 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 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;유닛을 영입할 때는 선택지가 3개가 있고 그 중 하나를 선택해서 영입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스테이지 진행도에 따라 영입하는 유닛의 레벨도 달라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;성소는 기도를 드리면 축복이나 유물을 얻습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;축복의 효과로는 앞으로 3번의 전투 동안 유닛들의 공격력이 상승하는 등이 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;좋았던 점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 키보드를 이용하면 전투를 진행하는 것이 훨씬 용이해진다는 점이 좋았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 SRPG 특성상 하나 하나 클릭하다보면 전투가 길어지고 피로해질 수 밖에 없는데, 키보드와 마우스를 잘 조합해서 사용하면 원하는 행동을 빠르게 할 수 있다는 점이 좋았습니다. 게다가 키보드 키가 항시 떠있기 때문에 직관적으로 이해하고 사용하기 편했습니다.&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;캐릭터에게 적절히 행동을 분배하고 스킬에도 모두 채워넣어서 빈틈없이 주사위를 사용했을 때 재미가 있었습니다. 다만, 스킬의 효율이 월등히 좋기 때문에 후반에 갈수록 이동을 제외하고는 일반적인 행동을 할 이유가 많이 없었습니다.&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) 게임이 루즈해지지 않도록 전투가 빠르게 끝나도록 밸런싱된 점이 좋았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전투에 등장하는 적들의 숫자가 많지 않았고, 공격 한 두방으로 하나를 잡을 수 있을 정도록 체력이 적기 때문에 전투 피로도가 높지 않았습니다. 또 아군 유닛들도 빨리 죽기 때문에 긴장감을 높지 않을 수 있었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;아쉬웠던 점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 성장 방향이 거의 고정되어 있다는 점이 아쉬웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성장의 가장 큰축은 레벨업인데, 레벨업을 했을 때 기본적으로는 스텟업을 하고, 특정 레벨 때만 스킬 업을 하며, 스킬 업을 해도 드라마틱하게 바뀌지는 않기 때문에 성장 체감을 느끼기 어려웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 성장을 할 때 플레이어가 선택할 수 있는 것은 어떤 유닛을 레벨업 시킬지 정도이기 때문에 매번 효율이 좋은 유닛을 레벨업시키게 되면 플레이마다 새로운 경험을 하기가 어려웠습니다.&lt;/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;2) UX/UI가 불편한 점이 조금 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주사위를 다시 굴리기 위해서는 왼쪽에 있는 아이콘에 주사위를 드래그해야 하는데 별도의 설명이 없어서 알아보기 어려웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주인공 캐릭터가 여러 개의 주사위를 가지고 있고, 용병마다 마우스 오버를 했을 때 필요한 설명이 잘 안 나오는 경우가 있어서 불편한 점이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스킬이나 특수 효과에 대한 설명이 간결하다 보니 이해하기 어려운 부분이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적들의 의도, 데미지 부분이 좀 작게 써져있어서, .자세히 보지 않으면 잘 보이지 않았습니다.&lt;/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) 스토리의 부재가 아쉬웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 로그라이크 게임의 특성상 스토리에 많은 공을 들이지 않는 편이기 때문에 어느정도 이해는 되지만 게임의 컨셉이나, 캐릭터나 몬스터도 특색이 없었습니다. 게임을 시작하거나 종료할 때 나타나는 NPC도 정말 단순한 이야기만 할뿐 게임의 세계관에 대한 어떤 설명도 없었습니다. 랜덤 이벤트에서 맞닥뜨리는 상황들도 게임 세계과 연관성이 있거나 숨은 이야기가 있어 보이지 않았습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;총평&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇 가지 아쉬운 부분이 있었지만 전체적으로 플레이가 매끄럽고 즐거웠습니다.&lt;/p&gt;</description>
      <category>Game Review</category>
      <category>dicey tales</category>
      <category>SRPG</category>
      <category>srpg 로그라이크</category>
      <category>게임 분석</category>
      <category>게임 후기</category>
      <category>다이시 테이즐</category>
      <category>로그라이크</category>
      <author>merit nada</author>
      <guid isPermaLink="true">https://mathmakeworld.tistory.com/124</guid>
      <comments>https://mathmakeworld.tistory.com/124#entry124comment</comments>
      <pubDate>Tue, 27 May 2025 23:32:49 +0900</pubDate>
    </item>
  </channel>
</rss>