<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>RenderLog</title>
    <link>https://scahp.tistory.com/</link>
    <description>Graphics or UE4/UE5 를 주요 주제로 다루는 블로그 입니다.</description>
    <language>ko</language>
    <pubDate>Wed, 15 Apr 2026 02:52:47 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>scahp</managingEditor>
    <image>
      <title>RenderLog</title>
      <url>https://tistory1.daumcdn.net/tistory/3617069/attach/ca99366f73734c5586d9a06c4d7dffd9</url>
      <link>https://scahp.tistory.com</link>
    </image>
    <item>
      <title>[UE5] Nanite (5/5)</title>
      <link>https://scahp.tistory.com/130</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[UE5] Nanite (5/5)&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: right;&quot; data-ke-size=&quot;size16&quot;&gt;최초 작성 : 2024-03-25&lt;br /&gt;마지막 수정 : 2024-03-25&lt;br /&gt;최재호&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&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: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 환경&lt;/b&gt;&lt;br /&gt;&lt;b&gt;2. 목표&lt;/b&gt;&lt;br /&gt;&lt;b&gt;3. 내용&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; 3.1. DrawBasePass 전체 레이아웃 확인&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; 3.2. FInitializeMaterialsCS&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; 3.3. FClassifyMaterialsCS&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; 3.4. FFinalizeMaterialsCS&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; 3.5. BuildNaniteMaterialPassCommands&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &lt;b&gt;&amp;nbsp; 3.6. DrawNaniteMaterialPass&lt;/b&gt; &lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; 3.7. FNaniteIndirectMaterialVS 쉐이더 코드 확인&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&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: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp; 4.1.&amp;nbsp;MaterialDepth&amp;nbsp;값에&amp;nbsp;생성에&amp;nbsp;대해서&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 레퍼런스&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 환경&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Unreal Engine 5.3.2 (release branch 072300df18a94f18077ca20a14224b5d99fee872)&lt;br /&gt;개인적으로 분석한 내용이라 틀린 점이 있을 수 있습니다. 그런 부분은 알려주시면 감사하겠습니다.&lt;br /&gt;&lt;br /&gt;이번 글은 한 번에 많은 길이의 코드를 분석하는 부분이 종종 등장합니다. 그래서 글을 2개 띄우고 한쪽은 설명 부분을 한쪽은 코드 이미지를 최대화해서 보는 것을 추천합니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 목표&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Nanite 에 들어간 핵심 기술들을 파악하고 BasePass 렌더링 과정을 코드레벨로 이해해 봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이번 글에서는 생성된 Visibility Buffer 로부터 G-Buffer 를 생성해 내는 Material Pass 를 확인해 봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dpgpOS/btsF3muq5zQ/tJHkS6oWITj9xzYCME9oN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dpgpOS/btsF3muq5zQ/tJHkS6oWITj9xzYCME9oN1/img.png&quot; data-alt=&quot;그림1. 오늘 알아볼 부분을 렌더독으로 캡쳐 (출처 : 직접 촬영)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dpgpOS/btsF3muq5zQ/tJHkS6oWITj9xzYCME9oN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdpgpOS%2FbtsF3muq5zQ%2FtJHkS6oWITj9xzYCME9oN1%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;536&quot; height=&quot;288&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. 오늘 알아볼 부분을 렌더독으로 캡쳐 (출처 : 직접 촬영)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;이전 글에서 이야기했던 것처럼 이 글은 총 5개로 구성될 예정입니다.&lt;br /&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://scahp.tistory.com/126&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;1. Nanite 1/5 : Nanite 에서 사용하는 주요 기술과 MeshDrawCommand 생성 및 VisibilityBuffer 초기화 과정 리뷰&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://scahp.tistory.com/127&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;2.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://scahp.tistory.com/127&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;Nanite 2/5 :&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://scahp.tistory.com/127&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp;TwoPassOcclusionCulling 의 전체 레이아웃을 확인하고 MainPass 의 노드 및 클러스터 컬링 리뷰&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a style=&quot;color: #666666;&quot; href=&quot;https://scahp.tistory.com/128&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;3.&amp;nbsp;Nanite 3/5 :&amp;nbsp;&amp;nbsp;SW, HW 레스터라이저와 PostPass 리뷰&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;a style=&quot;color: #9d9d9d;&quot; href=&quot;https://scahp.tistory.com/129&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;4.&amp;nbsp;Nanite 4/5 :&amp;nbsp;&amp;nbsp;Visibility Buffer 로 부터 Depth/Stencil 텍스쳐를 생성하는 부분 리뷰&lt;/span&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;a style=&quot;color: #333333;&quot; href=&quot;https://scahp.tistory.com/130&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;5.&amp;nbsp;Nanite 5/5 :&amp;nbsp;&amp;nbsp;Visibility Buffer 로 부터 G-Buffer 생성하는 MaterialPass 리뷰&lt;/b&gt;&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3.&amp;nbsp;내용&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.1. DrawBasePass 전체 레이아웃 확인&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;NaniteBasePass&amp;nbsp;에&amp;nbsp;진입합니다.&amp;nbsp;여기까지&amp;nbsp;오면&amp;nbsp;BasePass&amp;nbsp;에서&amp;nbsp;사용할&amp;nbsp;Depth&amp;nbsp;Texture&amp;nbsp;와&amp;nbsp;Visibility&amp;nbsp;Buffer&amp;nbsp;는&amp;nbsp;모두&amp;nbsp;준비되어&amp;nbsp;있을&amp;nbsp;것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;우리는&amp;nbsp;UseComputeMaterials&amp;nbsp;을&amp;nbsp;사용하지&amp;nbsp;않으므로&amp;nbsp;넘어가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. FShaderBinning 은 ShadingBinMeta, ShadingBinArgs, ShadingBinData 와 같은 데이터가 들어있습니다. 이 부분을 앞으로 채울 것 같습니다. 그리고 VisibilityBuffer 와 MaterialDepth 텍스쳐를 바인딩합니다. MaterialDepth 텍스쳐는 &lt;a href=&quot;https://scahp.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;Nanite (4/5)&lt;/a&gt;의&amp;nbsp;그림5에서&amp;nbsp;다뤘습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. VisibleClusterSWHW 또한 준비하며, MaxMaterialSlots 는 16384 로 설정됩니다. 이걸로 최대 동시에 사용가능한 머터리얼 수가 16384 개라는 것을 알 수 있습니다. MaterialIndirectArgs 를 생성합니다. 이 버퍼 DrawIndexedIndirect, DispatchIndirect 두 가지를 담는 것으로 보입니다만 우리가 리뷰할 ComputeShader 를 사용하지 않는 방식에서는 DrawIndexedIndirect 부분만 사용합니다. IndirectArgsStride 는 각 요소가 4 byte 이므로 4로 나눠주는 것으로 보이네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. MaterialSlot 의 가장 큰 값을 가져와서 32 단위로 묶었을 때 몇 개가 나오는지를 HighestMaterialSlot 에 담습니다. 계속해서 화면의 총 픽셀을 기준으로 타일을 나누는데, 64x64 픽셀 크기의 타일을 만듭니다. 생성된 타일의 총 개수를 32 단위로 묶었을 때 몇개가 나오는지를 TileRemaps 에 담습니다. 마지막으로 MaterialTileRemap 이라는 버퍼를 TileRemaps * MaxMaterialSlots 개의 uint32 를 크기로 할당합니다. MaterialTileRemap 에는 특정 머터리얼이 사용되는 타일의 정보를 기록하는 버퍼입니다. 앞으로 볼 코드는 머터리얼 개수 별로 드로콜이 생성되는데, 이 버퍼를 사용하여 &amp;ldquo;해당 머터리얼이 전체 타일 중 어떤 타일들을 렌더링 한다&amp;rdquo;를 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. FInitializeMaterialsCS 쉐이더를 실행합니다. 여기서는 MaterialIndirectArgs 버퍼를 초기화합니다. 자세한 내용은 그림3 에서 확인해 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. FClassifyMaterialsCS 쉐이더를 실행합니다. 여기서 MaterialTileRemap 버퍼와 MaterialIndirectArgs 가 채워집니다. 자세한 내용은 그림4 에서 확인해 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. FFinalizeMaterialsCS 입니다. 여기서는 Compute Shader 부분을 위해서 64x64 크기의 타일을 8x8 크기로 만들었을 때의 총개수를 만들어줍니다. 하지만 여기서는 사용하지 않습니다. 제한 내용은 그림5 에서 확인해 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. FScene 으로 부터 Base 에서 사용하는 NaniteMaterials 의 총개수를 얻어옵니다. 이 NaniteMaterials 정보는 &lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;Nanite (1/5)&lt;/a&gt;&amp;nbsp;의&amp;nbsp;그림6&amp;nbsp;에서&amp;nbsp;SceneProxy&amp;nbsp;등록&amp;nbsp;시에&amp;nbsp;생성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. Nanite 머터리얼이 하나 이상 있다고 가정하고 조건문내로 진입합니다. bWPOInSecondPass 는 &amp;ldquo;r.VelocityOutputPass&amp;rdquo; 이 켜져 있는 경우 활성화되는데, 기본 옵션이 활성화되어있기 때문에 활성화되어있다고 가정합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11.&amp;nbsp;Nanite&amp;nbsp;Material&amp;nbsp;Shading&amp;nbsp;을&amp;nbsp;위해&amp;nbsp;필요한&amp;nbsp;파라메터를&amp;nbsp;설정합니다.&amp;nbsp;TileGridSize,&amp;nbsp;TileRemaps(타일&amp;nbsp;XY&amp;nbsp;각각을&amp;nbsp;32&amp;nbsp;개로&amp;nbsp;묶었을&amp;nbsp;때&amp;nbsp;몇&amp;nbsp;묶음인지),&amp;nbsp;기타&amp;nbsp;VisibilityBuffer&amp;nbsp;와&amp;nbsp;ShadingMask&amp;nbsp;등을&amp;nbsp;바인딩&amp;nbsp;합니다.&amp;nbsp;중요한&amp;nbsp;내용인&amp;nbsp;것&amp;nbsp;같지는&amp;nbsp;않아서&amp;nbsp;파라메터를&amp;nbsp;바인딩하는구나?&amp;nbsp;하고&amp;nbsp;넘어가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12. ParamsAndInfo 에는 bWPOInSecondPass 가 켜져 있기 때문에 2가 들어갑니다. 실제 렌더링 시에 WPO 가 없는 경우를 모아서 첫 번째 패스에 그리고 두 번째 패스에는 WPO 가 있는 경우 렌더링 하며 이때 Velocity 정보도 같이 기록됩니다.&amp;nbsp;&lt;a href=&quot;https://scahp.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;Nanite(4/5)&lt;/a&gt;&amp;nbsp;의&amp;nbsp;그림2에&amp;nbsp;7을&amp;nbsp;보면&amp;nbsp;WPO&amp;nbsp;가&amp;nbsp;켜져 있는&amp;nbsp;경우&amp;nbsp;Velocity&amp;nbsp;를&amp;nbsp;기록하지&amp;nbsp;않는데&amp;nbsp;여기서&amp;nbsp;켜주는&amp;nbsp;것을&amp;nbsp;볼&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;이제&amp;nbsp;ParamsAndInfo&amp;nbsp;변수에&amp;nbsp;PassParams&amp;nbsp;를&amp;nbsp;채우는데,&amp;nbsp;첫 번째&amp;nbsp;패스는&amp;nbsp;기본&amp;nbsp;EmitGBuffer&amp;nbsp;를&amp;nbsp;사용하고,&amp;nbsp;두 번째&amp;nbsp;패스에는&amp;nbsp;EmitGBufferWithVelocity&amp;nbsp;를&amp;nbsp;사용하여&amp;nbsp;Velocity&amp;nbsp;도&amp;nbsp;같이&amp;nbsp;출력하도록&amp;nbsp;설정하는&amp;nbsp;것을&amp;nbsp;볼&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;기타&amp;nbsp;렌더타겟&amp;nbsp;텍스쳐나&amp;nbsp;VRS&amp;nbsp;2.0&amp;nbsp;을&amp;nbsp;위한&amp;nbsp;ShadingRate&amp;nbsp;텍스쳐&amp;nbsp;바인딩하는&amp;nbsp;부분도&amp;nbsp;있네요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;13. BuildNaniteMaterialPassCommands 에서는 FScene 에 등록된 BasePass 의 NaniteMaterials 들을 FNaniteMaterialPassCommand 로 만들어서 OutNaniteMaterialPassCommands 배열에 담아줍니다. 그리고 12 과정에서 나눈 ParamsAndInfo 의 두 패스를 구분하기 위한 FNaniteMaterialPassInfo 정보를 만드는데, OutNaniteMaterialPassCommands 배열에서 몇 번째 인덱스부터 두 번째 패스를 사용하는지 정보가 담깁니다. 자세한 내용은 그림6 에서 추가로 알아봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;14. MaterialPass 에서 사용할 FNaniteIndirectMaterialVS 입니다. 넘겨받은 InstanceIndex 를 MaterialTileRemap 를 사용하여 자신이 그릴 TileIndex 로 변환시킨 뒤 해당 타일을 위한 Vertex 의 위치를 확정해 줍니다. FNaniteIndirectMaterialVS 의 코드는 그림8 에서 자세히 확인해 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;15. 실제 Material Pass 를 수행합니다. 첫 번째 패스는 EmitGBuffer, 두 번째 패스는 EmitGBufferWithVelocity 입니다. 각 패스는 Material 개수만큼 Indirect draw call 을 만들어냅니다. 자세한 내용은 그림7 에서 확인해 봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;4066&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DwvFV/btsF3FUSRFF/JeqEVsc70GIWB0v7ADvGtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DwvFV/btsF3FUSRFF/JeqEVsc70GIWB0v7ADvGtk/img.png&quot; data-alt=&quot;그림2. Material Pass 전체 코드 레이아웃 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DwvFV/btsF3FUSRFF/JeqEVsc70GIWB0v7ADvGtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDwvFV%2FbtsF3FUSRFF%2FJeqEVsc70GIWB0v7ADvGtk%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;1102&quot; height=&quot;4066&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;4066&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림2. Material Pass 전체 코드 레이아웃 (출처 : 레퍼런스1)&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.2. FInitializeMaterialsCS&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FInitializeMaterialsCS&amp;nbsp;는&amp;nbsp;MaterialPass&amp;nbsp;에&amp;nbsp;사용할&amp;nbsp;Indirect&amp;nbsp;draw&amp;nbsp;argument&amp;nbsp;와&amp;nbsp;MaterialTileRemap&amp;nbsp;버퍼를&amp;nbsp;초기화하는&amp;nbsp;패스입니다.&amp;nbsp;MaterialPass&amp;nbsp;는&amp;nbsp;Nanite&amp;nbsp;Material&amp;nbsp;개수만큼&amp;nbsp;Indirect&amp;nbsp;draw&amp;nbsp;call&amp;nbsp;이&amp;nbsp;발생하며,&amp;nbsp;각각의&amp;nbsp;draw&amp;nbsp;call&amp;nbsp;은&amp;nbsp;각각의&amp;nbsp;머터리얼이&amp;nbsp;렌더링 하는&amp;nbsp;타일(64x64)&amp;nbsp;영역을&amp;nbsp;렌더링 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;1. 그림2 의 6번 코드를 보면 MaterialSlot 개수를 64 개씩 묶었을 때의 개수를 WorkGroup 개수로 사용하여 Compute shader 를 Dispatch 했습니다. 그리고 각 WorkGroup 이 64 개의 thread 를 가지고 있는 것을 볼 수 있습니다. 그렇다면 SV_DispatchThreadID 는 MaterialSlot 의 인덱스일 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;유효한&amp;nbsp;MaterialSlot&amp;nbsp;인지&amp;nbsp;확인하고&amp;nbsp;조건문&amp;nbsp;내로&amp;nbsp;진입합니다.&amp;nbsp;그리고&amp;nbsp;현재&amp;nbsp;MateriaSlot&amp;nbsp;이&amp;nbsp;사용하는&amp;nbsp;IndirectArgOffset&amp;nbsp;을&amp;nbsp;구합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp;Graphics&amp;nbsp;Pipeline&amp;nbsp;에&amp;nbsp;사용할&amp;nbsp;Indirect&amp;nbsp;draw&amp;nbsp;argument&amp;nbsp;를&amp;nbsp;만듭니다.&amp;nbsp;첫 번째는&amp;nbsp;Vertex&amp;nbsp;개수인데,&amp;nbsp;Material&amp;nbsp;Pass&amp;nbsp;는&amp;nbsp;Tile&amp;nbsp;Quad&amp;nbsp;로&amp;nbsp;렌더링&amp;nbsp;될&amp;nbsp;거라&amp;nbsp;삼각형이&amp;nbsp;2개&amp;nbsp;사용됩니다.&amp;nbsp;그래서&amp;nbsp;Vetex&amp;nbsp;는&amp;nbsp;6&amp;nbsp;개입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.&amp;nbsp;인스턴스&amp;nbsp;개수는&amp;nbsp;앞으로&amp;nbsp;채울&amp;nbsp;것이기&amp;nbsp;때문에&amp;nbsp;0으로&amp;nbsp;초기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.&amp;nbsp;Compute&amp;nbsp;Shader&amp;nbsp;관련&amp;nbsp;Indirect&amp;nbsp;draw&amp;nbsp;argument&amp;nbsp;를&amp;nbsp;초기화합니다.&amp;nbsp;하지만&amp;nbsp;기본&amp;nbsp;패스에서는&amp;nbsp;사용되지&amp;nbsp;않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6.&amp;nbsp;MaterialTileRemap&amp;nbsp;버퍼를&amp;nbsp;모두&amp;nbsp;0으로&amp;nbsp;초기화&amp;nbsp;합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bk06Ex/btsF1dy35LP/tc3PUHNpkD8KXKM9eMH4ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bk06Ex/btsF1dy35LP/tc3PUHNpkD8KXKM9eMH4ok/img.png&quot; data-alt=&quot;그림3. FInitializeMaterialsCS 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bk06Ex/btsF1dy35LP/tc3PUHNpkD8KXKM9eMH4ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbk06Ex%2FbtsF1dy35LP%2Ftc3PUHNpkD8KXKM9eMH4ok%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;1076&quot; height=&quot;502&quot; data-origin-width=&quot;1076&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림3. FInitializeMaterialsCS 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.3. FClassifyMaterialsCS&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FClassifyMaterialsCS&amp;nbsp;쉐이더입니다.&amp;nbsp;이&amp;nbsp;쉐이더는&amp;nbsp;Dispatch&amp;nbsp;한&amp;nbsp;WorkGroup&amp;nbsp;이&amp;nbsp;화면&amp;nbsp;픽셀을&amp;nbsp;가로세로&amp;nbsp;각각&amp;nbsp;64&amp;nbsp;로&amp;nbsp;묶었을&amp;nbsp;때&amp;nbsp;나오는&amp;nbsp;개수를&amp;nbsp;사용했습니다.&amp;nbsp;예를 들면,&amp;nbsp;화면&amp;nbsp;해상도가&amp;nbsp;(1466,&amp;nbsp;784)&amp;nbsp;라면&amp;nbsp;Dispatch&amp;nbsp;된&amp;nbsp;WorkGroup&amp;nbsp;은&amp;nbsp;(23,&amp;nbsp;13,&amp;nbsp;1)&amp;nbsp;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;Nanite&amp;nbsp;에서&amp;nbsp;한 번에&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;최대&amp;nbsp;Unique&amp;nbsp;material&amp;nbsp;수는&amp;nbsp;NANITE_MAX_MATERIAL_SLOTS&amp;nbsp;은&amp;nbsp;(16384)&amp;nbsp;입니다.&amp;nbsp;그리고&amp;nbsp;이&amp;nbsp;Material&amp;nbsp;을&amp;nbsp;BitMask&amp;nbsp;를&amp;nbsp;사용하여&amp;nbsp;관리하도록&amp;nbsp;합니다.&amp;nbsp;uint&amp;nbsp;변수는&amp;nbsp;32&amp;nbsp;bit&amp;nbsp;변수이고,&amp;nbsp;각각의&amp;nbsp;비트를&amp;nbsp;1개의&amp;nbsp;MaterialSlot&amp;nbsp;의&amp;nbsp;사용&amp;nbsp;여부를&amp;nbsp;표시하는 데&amp;nbsp;사용한다면&amp;nbsp;NANITE_MAX_MATERIAL_BINS&amp;nbsp;==&amp;nbsp;(NANITE_MAX_MATERIAL_SLOTS&amp;nbsp;/&amp;nbsp;32u)&amp;nbsp;개의&amp;nbsp;uint&amp;nbsp;변수가&amp;nbsp;필요합니다.&amp;nbsp;그&amp;nbsp;외에&amp;nbsp;중요한&amp;nbsp;변수는&amp;nbsp;아래와&amp;nbsp;같습니다. &lt;br /&gt;-&amp;nbsp;MaterialRemapCount&amp;nbsp;:&amp;nbsp;화면&amp;nbsp;픽셀&amp;nbsp;기준으로&amp;nbsp;나눈&amp;nbsp;타일(64x64)&amp;nbsp;개수를&amp;nbsp;32&amp;nbsp;개로&amp;nbsp;묶었을&amp;nbsp;때&amp;nbsp;총&amp;nbsp;몇&amp;nbsp;묶음이&amp;nbsp;나오는지&amp;nbsp;개수 &lt;br /&gt;-&amp;nbsp;MaterialTileCount&amp;nbsp;:&amp;nbsp;화면&amp;nbsp;픽셀&amp;nbsp;기준으로&amp;nbsp;나눈&amp;nbsp;타일(64x64)&amp;nbsp;의&amp;nbsp;총&amp;nbsp;개수 &lt;br /&gt;-&amp;nbsp;MaterialSlotCount&amp;nbsp;의&amp;nbsp;최대&amp;nbsp;개수 &lt;br /&gt;-&amp;nbsp;MaterialBinCount&amp;nbsp;는&amp;nbsp;MaterialSlotCount&amp;nbsp;을&amp;nbsp;32&amp;nbsp;개로&amp;nbsp;묶었을&amp;nbsp;때&amp;nbsp;총&amp;nbsp;묶음&amp;nbsp;개수. &lt;br /&gt;- MaterialIndirectArgs : Material Pass 에 사용될 Indirect draw argument 를 저장할 버퍼&lt;br /&gt;-&amp;nbsp;MaterialTileRemap&amp;nbsp;:&amp;nbsp;MaterialSlot&amp;nbsp;이&amp;nbsp;주어질&amp;nbsp;때,&amp;nbsp;해당&amp;nbsp;MaterialSlot&amp;nbsp;이&amp;nbsp;그리는&amp;nbsp;타일&amp;nbsp;정보를&amp;nbsp;담고&amp;nbsp;있음(Bit&amp;nbsp;mask&amp;nbsp;로&amp;nbsp;담고&amp;nbsp;있음).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. TileMaterialBins[NANITE_MAX_MATERIAL_BINS] 는 총 16384 머터리얼 슬롯을 담당합니다. 여기서 NANITE_MAX_MATERIAL_BINS 가 512 이고, 각각의 uint 가 32 bit 를 다루기 때문에 512 * 32 = 16384 입니다. 그리고 TileMaterialBins 는 group shared 타입의 변수이기 때문에 현재 Tile 이 사용하는 MaterialSlot 위치에 1 bit mask 를 설정하기 위해서 사용되는 배열입니다. 현재 Tile 에 대한 MaterialSlot 의 사용 여부 mask 설정을 마치면 아래의 X 코드에서 MaterialSlot 별 사용하는 Tile 인덱스 정보로 변환해서 MaterialTileRemap 버퍼에 저장합니다. IndirectArgStride 는 Indirect draw argument 의 위치를 찾기 위한 Stride 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp;하나의&amp;nbsp;WorkGroup&amp;nbsp;이&amp;nbsp;16x16&amp;nbsp;의&amp;nbsp;thread&amp;nbsp;를&amp;nbsp;작동시킵니다.&amp;nbsp;SV_GroupThreadID&amp;nbsp;는&amp;nbsp;현재&amp;nbsp;실행&amp;nbsp;중인&amp;nbsp;thread&amp;nbsp;가&amp;nbsp;WorkGroup&amp;nbsp;내에서&amp;nbsp;몇 번째&amp;nbsp;인덱스를&amp;nbsp;가지는지입니다.&amp;nbsp;즉,&amp;nbsp;([0~15],&amp;nbsp;[0~15],&amp;nbsp;0)&amp;nbsp;범위의&amp;nbsp;값이&amp;nbsp;들어옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.&amp;nbsp;기본&amp;nbsp;변수들을&amp;nbsp;할당합니다. &lt;br /&gt;-&amp;nbsp;TileSlotOffset&amp;nbsp;:&amp;nbsp;WorkGroup&amp;nbsp;내에서&amp;nbsp;Thread&amp;nbsp;의&amp;nbsp;인덱스를&amp;nbsp;선형으로&amp;nbsp;표시 &lt;br /&gt;-&amp;nbsp;TileBin1Offset,&amp;nbsp;TileBin2Offset&amp;nbsp;:&amp;nbsp;이&amp;nbsp;쉐이더가&amp;nbsp;Tile&amp;nbsp;1&amp;nbsp;개에&amp;nbsp;대한&amp;nbsp;정보를&amp;nbsp;처리하는&amp;nbsp;것을&amp;nbsp;다시&amp;nbsp;떠올려봅시다.&amp;nbsp;그리고&amp;nbsp;각&amp;nbsp;Tile&amp;nbsp;은&amp;nbsp;최대&amp;nbsp;NANITE_MAX_MATERIAL_SLOTS(16384)&amp;nbsp;개의&amp;nbsp;머터리얼을&amp;nbsp;가질&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;WorkGroup&amp;nbsp;내의&amp;nbsp;스레드가&amp;nbsp;16x16=256&amp;nbsp;개&amp;nbsp;동작할&amp;nbsp;것입니다.&amp;nbsp;256&amp;nbsp;개의&amp;nbsp;thread&amp;nbsp;가&amp;nbsp;16384&amp;nbsp;개의&amp;nbsp;MaterialSlot&amp;nbsp;을&amp;nbsp;공평하게&amp;nbsp;나눠서&amp;nbsp;64&amp;nbsp;개씩&amp;nbsp;책임지게&amp;nbsp;됩니다.&amp;nbsp;이&amp;nbsp;때&amp;nbsp;책임지게&amp;nbsp;되는&amp;nbsp;작업은&amp;nbsp;MaterialTileRemap&amp;nbsp;에&amp;nbsp;현재&amp;nbsp;작업&amp;nbsp;중인&amp;nbsp;Tile&amp;nbsp;이&amp;nbsp;특정&amp;nbsp;MaterialSlot&amp;nbsp;&amp;nbsp;에&amp;nbsp;사용된다고&amp;nbsp;Masking&amp;nbsp;해주는&amp;nbsp;것입니다.&amp;nbsp;각&amp;nbsp;thread&amp;nbsp;가&amp;nbsp;담당하는&amp;nbsp;64&amp;nbsp;개의&amp;nbsp;MaterialSlot&amp;nbsp;정보는&amp;nbsp;uint&amp;nbsp;2개를&amp;nbsp;사용하여&amp;nbsp;64&amp;nbsp;개의&amp;nbsp;bit&amp;nbsp;mask&amp;nbsp;를&amp;nbsp;할당&amp;nbsp;받을&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;2개의&amp;nbsp;uint&amp;nbsp;위치는&amp;nbsp;각각&amp;nbsp;TileBin1Offset,&amp;nbsp;TileBin2Offset&amp;nbsp;를&amp;nbsp;사용하여&amp;nbsp;얻게&amp;nbsp;됩니다.&amp;nbsp;TileBin2Offset&amp;nbsp;에는&amp;nbsp;256&amp;nbsp;을&amp;nbsp;더해주게&amp;nbsp;되는데,&amp;nbsp;NANITE_MAX_MATERIAL_SLOTS(16384)&amp;nbsp;를&amp;nbsp;32&amp;nbsp;bit&amp;nbsp;mask&amp;nbsp;로&amp;nbsp;표현한&amp;nbsp;개수는&amp;nbsp;NANITE_MAX_MATERIAL_BINS(512)&amp;nbsp;입니다.&amp;nbsp;여기서&amp;nbsp;16x16&amp;nbsp;thread&amp;nbsp;=&amp;nbsp;256&amp;nbsp;이기&amp;nbsp;때문에&amp;nbsp;512&amp;nbsp;개의&amp;nbsp;Bin&amp;nbsp;을&amp;nbsp;모두&amp;nbsp;커버하려면&amp;nbsp;TileBin2Offset&amp;nbsp;에&amp;nbsp;[0~255],&amp;nbsp;TileBin2Offset&amp;nbsp;에&amp;nbsp;[256~511]&amp;nbsp;의&amp;nbsp;인덱스를&amp;nbsp;할당해주면&amp;nbsp;될&amp;nbsp;것입니다.&amp;nbsp;그래서&amp;nbsp;TileBin2Offset&amp;nbsp;에는&amp;nbsp;256&amp;nbsp;을&amp;nbsp;더해줍니다. &lt;br /&gt;- GroupPixelStart 는 현재 처리 중인 타일이 화면 픽셀 기반해서 Offset XY 어느 위치 인지를 저장합니다. 이 값은 ShadingMask 버퍼를 참조해서 Shading 정보를 얻어오는 데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.&amp;nbsp;group&amp;nbsp;shared&amp;nbsp;변수인&amp;nbsp;TileMaterialBins&amp;nbsp;를&amp;nbsp;0으로&amp;nbsp;초기화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Tile 당 64x64 픽셀을 커버하는 것을 떠올린 후 코드를 봅시다. 현재 WorkGroup 이 16x16x1 thread 를 실행하기 때문에 Tile 1개의 픽셀 모두를 방문하려면 16x4 = 64 로 4번 반복문을 수행하면 됩니다. Y 축에 대해서 4 번 for loop 를 stride 16 으로 돌고 있는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7.&amp;nbsp;이제&amp;nbsp;X&amp;nbsp;축에&amp;nbsp;대해서&amp;nbsp;4&amp;nbsp;번&amp;nbsp;for&amp;nbsp;loop&amp;nbsp;를&amp;nbsp;돌고&amp;nbsp;있습니다.&amp;nbsp;그리고&amp;nbsp;ShadingMask&amp;nbsp;버퍼로부터&amp;nbsp;해당&amp;nbsp;픽셀의&amp;nbsp;ShadingMask&amp;nbsp;정보를&amp;nbsp;얻습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8.&amp;nbsp;다시&amp;nbsp;4&amp;nbsp;번의&amp;nbsp;for&amp;nbsp;loop&amp;nbsp;을&amp;nbsp;돌면서&amp;nbsp;7에서&amp;nbsp;준비한&amp;nbsp;ShadingMask&amp;nbsp;에&amp;nbsp;대한&amp;nbsp;처리를&amp;nbsp;시작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. 이전 과정에서 준비한 ShadingMask 정보를 Unpack 합니다. 그리고 ShadingMask 에 기록된 ShadingBin 변수를 통해 어떤 머터리얼을 사용하는지 알 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10.&amp;nbsp;&amp;nbsp;현재&amp;nbsp;처리 중인&amp;nbsp;픽셀이&amp;nbsp;Nanite&amp;nbsp;픽셀인지,&amp;nbsp;어떤&amp;nbsp;ShadingBin&amp;nbsp;인덱스를&amp;nbsp;사용하는지&amp;nbsp;확인합니다.&amp;nbsp;이전에&amp;nbsp;사용한&amp;nbsp;ShadingBin&amp;nbsp;과&amp;nbsp;다른&amp;nbsp;경우만&amp;nbsp;조건문&amp;nbsp;내로&amp;nbsp;들어가는데,&amp;nbsp;현재&amp;nbsp;타일이&amp;nbsp;사용하는&amp;nbsp;Material&amp;nbsp;정보는&amp;nbsp;한 번만&amp;nbsp;마스킹하면&amp;nbsp;되기&amp;nbsp;때문에&amp;nbsp;조건&amp;nbsp;비교를&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11. 이 코드는 TileMaterialBins 에 &amp;ldquo;현재 처리 중인 Tile 이 사용하는 MaterialSlot 의 비트를 설정&amp;rdquo; 해줍니다. 이 코드도 Wave intrinsic 을 사용하여 Atomic operation 을 줄여주는 아주 좋은 코드 예제입니다. 간단히 설명하면 이 코드에는 총 256 개의 thread 가 동시에 진입할 수 있으며 각각의 VGPR 은 서로 다른 ShadingBin 을 가지고 있을 것입니다. 이때 256 thread 중 맨 첫 번째 lane 의 값이 bWrite = true 를 설정합니다. 그리고 while(ShadingBin &amp;ne; ToScalarMemory(ShadingBin)) 에 도달하면 bWrite 를 설정한 thread 와 동일한 ShadingBin 값을 가진 thread 는 모두 비활성화 됩니다. 그리고 다음 loop 에서는 Active thread 중 맨 첫번째 lane 의 bWrite = true 가 설정될 것입니다. 그리고 while(ShadingBin != ToScalarMemory(ShadingBin)) 를 만나면서 과정이 반복됩니다. 예를 들어 256 개의 thread 에 앞쪽 128 개는 ShadingBin 이 3, 뒤쪽 128 개는 ShadingBin 이 8 이라고 합니다. 그리고 VGPR 변수인 bWrite 는 thread 0 번과 thread 128 번만 bWrite 로 변할 것입니다. 그러면 InterlockedOr 연산은 단 2개의 thread 에서만 일어납니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12. 현재 처리 중인 Tile 이 리니어 인덱스 기준으로 어느 위치인지 알아내 TileLinearOffset 에 저장합니다. 그리고 &amp;gt;&amp;gt; 5 는 32 로 나누는 연산이기 때문에 TileRemapBinIndex 는 TileLinearOffset / 32 입니다. 아마도 TileLinearOffset 정보를 bit masking 처리하려는 것처럼 보입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;13. MaterialSlot 의 범위를 비교하고 조건문 내로 진입합니다. 현재 thread 가 담당하는 MaterialSlot 의 비트를 얻어옵니다. TileMaterialBins 는 group shared 변수로 11 과정에서 현재 Tile 이 사용되는 MaterialSlot 의 Index 정보를 bit mask 로 기록했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;14. TileBin1Offset * 32 를 곱하면 MaterialSlot1Base 에 저장합니다. bit mask 기반 인덱스가 아닌 실제 인덱스 정보를 만들 준비를 합니다. 여기에 32 개의 bit mask 에 대한 정보를 더 해야만 최종 Index 가 구성될 것입니다. 1로 Bit masking 된 것이 있는 경우 while loop 내로 진입합니다. 그리고 1 로 bit mask 된 인덱스를 가져오고, 해당 bit 를 0 으로 설정해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;15.&amp;nbsp;14&amp;nbsp;번에서&amp;nbsp;준비한&amp;nbsp;MaterialSlot1Base&amp;nbsp;+&amp;nbsp;Bin1Index(32&amp;nbsp;bit&amp;nbsp;mask&amp;nbsp;를&amp;nbsp;참조해&amp;nbsp;얻어온&amp;nbsp;인덱스)&amp;nbsp;를&amp;nbsp;사용하여&amp;nbsp;현재&amp;nbsp;처리 중인&amp;nbsp;Tile&amp;nbsp;이&amp;nbsp;사용 중인&amp;nbsp;MaterialSlot&amp;nbsp;의&amp;nbsp;인덱스를&amp;nbsp;얻어왔습니다.&amp;nbsp;MaterialSlot&amp;nbsp;인덱스를&amp;nbsp;알&amp;nbsp;수&amp;nbsp;있으니&amp;nbsp;해당&amp;nbsp;MaterialSlot&amp;nbsp;이&amp;nbsp;렌더링&amp;nbsp;할&amp;nbsp;Tile&amp;nbsp;개수가&amp;nbsp;1&amp;nbsp;증가해야 할&amp;nbsp;것입니다.&amp;nbsp;그&amp;nbsp;부분을&amp;nbsp;해줍니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;16. MaterialTileRemap 은 MaterialSlot 이 주어졌을 때, 화면의 어떤 Tile 을 그릴 것인지 정보를 bit mask 로 저장합니다. MaterialRemapCount 는 화면을 64x64 Tile 로 묶었을 때 나오는 총 Tile 수를 32 개 기준으로 묶었을 때 묶인 개수입니다(이것도 bit mask 로 표현하기 위해서임). MaterialRemapCount * MaterialSlot1 을 보면, 각 MaterialSlot 은 Tile 개수만큼의 비트를 할당받고 있음을 알 수 있습니다. 이 코드를 실행하고 나면 MaterialTileRemap 를 사용하여 MaterialSlot 을 알고 있으면 어떤 Tile 에 MaterialSlot 이 영향을 주는지 알 수 있게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;17~20&amp;nbsp;번&amp;nbsp;과정은&amp;nbsp;13~16&amp;nbsp;번&amp;nbsp;과정과&amp;nbsp;동일합니다.&amp;nbsp;WorkGroup&amp;nbsp;당&amp;nbsp;512&amp;nbsp;개의&amp;nbsp;MaterialSlotBin(각&amp;nbsp;bin&amp;nbsp;은&amp;nbsp;32개의&amp;nbsp;MaterialSlot&amp;nbsp;저장)&amp;nbsp;을&amp;nbsp;담당하기&amp;nbsp;위해서&amp;nbsp;13~16&amp;nbsp;번&amp;nbsp;과정에서는&amp;nbsp;[0,&amp;nbsp;255],&amp;nbsp;17~20&amp;nbsp;번&amp;nbsp;과정에서는&amp;nbsp;[256,&amp;nbsp;511]&amp;nbsp;범위를&amp;nbsp;담당합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;2471&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/usE0K/btsF1cmDiMa/riwhVEYqCpFHUeQ0xIHjpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/usE0K/btsF1cmDiMa/riwhVEYqCpFHUeQ0xIHjpK/img.png&quot; data-alt=&quot;그림4. FClassifyMaterialsCS 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/usE0K/btsF1cmDiMa/riwhVEYqCpFHUeQ0xIHjpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FusE0K%2FbtsF1cmDiMa%2FriwhVEYqCpFHUeQ0xIHjpK%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;1040&quot; height=&quot;2471&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;2471&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림4. FClassifyMaterialsCS 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.4. FFinalizeMaterialsCS&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FFinalizeMaterialsCS&amp;nbsp;는&amp;nbsp;MaterialSlot&amp;nbsp;개수를&amp;nbsp;64&amp;nbsp;개로&amp;nbsp;묶은&amp;nbsp;묶음&amp;nbsp;수만큼&amp;nbsp;WorkGroup&amp;nbsp;을&amp;nbsp;Dispatch&amp;nbsp;합니다.&amp;nbsp;쉐이더&amp;nbsp;내부에서는&amp;nbsp;Material&amp;nbsp;Pass&amp;nbsp;를&amp;nbsp;위한&amp;nbsp;Indirect&amp;nbsp;draw&amp;nbsp;argument&amp;nbsp;중&amp;nbsp;Compute&amp;nbsp;shader&amp;nbsp;쪽을&amp;nbsp;위한&amp;nbsp;micro&amp;nbsp;tile(8x8)&amp;nbsp;로&amp;nbsp;groups&amp;nbsp;의&amp;nbsp;Dispatch&amp;nbsp;수를&amp;nbsp;결정합니다.&amp;nbsp;하지만&amp;nbsp;우리가&amp;nbsp;리뷰하는&amp;nbsp;기본설정에서는&amp;nbsp;해당&amp;nbsp;Indirect&amp;nbsp;draw&amp;nbsp;arguments&amp;nbsp;를&amp;nbsp;사용하지&amp;nbsp;않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;FinalizeMaterials&amp;nbsp;쉐이더&amp;nbsp;코드에&amp;nbsp;진입합니다.&amp;nbsp;각&amp;nbsp;WorkGroup&amp;nbsp;당&amp;nbsp;64&amp;nbsp;개의&amp;nbsp;thread&amp;nbsp;를&amp;nbsp;실행합니다.&amp;nbsp;그래서&amp;nbsp;SV_DispatchThreadID&amp;nbsp;는&amp;nbsp;MaterialSlot&amp;nbsp;개수가&amp;nbsp;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;MaterialSlot&amp;nbsp;이&amp;nbsp;범위를&amp;nbsp;넘어서는지&amp;nbsp;확인하고&amp;nbsp;조건문&amp;nbsp;안으로&amp;nbsp;진입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp;IndirectArgOffset&amp;nbsp;은&amp;nbsp;MaterialSlot&amp;nbsp;*&amp;nbsp;IndirectArgsStride&amp;nbsp;로&amp;nbsp;구합니다.&amp;nbsp;그리고&amp;nbsp;Graphics&amp;nbsp;Pipeline&amp;nbsp;Indirect&amp;nbsp;draw&amp;nbsp;argument&amp;nbsp;에&amp;nbsp;기록한&amp;nbsp;MaterialSlot&amp;nbsp;이&amp;nbsp;렌더링&amp;nbsp;해야&amp;nbsp;할&amp;nbsp;Tile&amp;nbsp;개수를&amp;nbsp;얻습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.&amp;nbsp;총&amp;nbsp;타일&amp;nbsp;수에&amp;nbsp;Tile&amp;nbsp;당&amp;nbsp;렌더링하는&amp;nbsp;픽셀&amp;nbsp;수를&amp;nbsp;곱하여&amp;nbsp;8x8&amp;nbsp;크기&amp;nbsp;타일로&amp;nbsp;나누는&amp;nbsp;경우&amp;nbsp;몇 개가&amp;nbsp;나오는지를&amp;nbsp;DispatchGroup.X&amp;nbsp;에&amp;nbsp;저장합니다.&amp;nbsp;(8x8&amp;nbsp;=&amp;nbsp;64&amp;nbsp;이기&amp;nbsp;때문에&amp;nbsp;DivideAndRoundUp&amp;nbsp;을&amp;nbsp;수행)&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-origin-width=&quot;867&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCKS8C/btsF4wXH1da/Lhcn4TQUvSzubjNFhSdKFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCKS8C/btsF4wXH1da/Lhcn4TQUvSzubjNFhSdKFK/img.png&quot; data-alt=&quot;그림5. FFinalizeMaterialsCS 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCKS8C/btsF4wXH1da/Lhcn4TQUvSzubjNFhSdKFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCKS8C%2FbtsF4wXH1da%2FLhcn4TQUvSzubjNFhSdKFK%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;867&quot; height=&quot;349&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;349&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림5. FFinalizeMaterialsCS 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.5. BuildNaniteMaterialPassCommands&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;코드는&amp;nbsp;CPU&amp;nbsp;측에&amp;nbsp;있는&amp;nbsp;코드로&amp;nbsp;이제&amp;nbsp;Material&amp;nbsp;Pass&amp;nbsp;를&amp;nbsp;위한&amp;nbsp;MeshDrawCommand&amp;nbsp;를&amp;nbsp;준비합니다.&amp;nbsp;WPO&amp;nbsp;사용&amp;nbsp;여부에&amp;nbsp;따라서&amp;nbsp;2개의&amp;nbsp;패스로&amp;nbsp;나누게&amp;nbsp;되는데,&amp;nbsp;WPO&amp;nbsp;를&amp;nbsp;사용하면&amp;nbsp;두 번째&amp;nbsp;패스에서&amp;nbsp;렌더링&amp;nbsp;되며&amp;nbsp;Velocity&amp;nbsp;Buffer&amp;nbsp;에&amp;nbsp;Velocity&amp;nbsp;를&amp;nbsp;출력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. BuildNaniteMaterialPassCommands 함수에 진입합니다. 이 함수를 수행하고 나면OutNaniteMaterialPassCommands(FNaniteMaterialPassCommand 배열)이 채워지고, WPO 에 따라 나뉘는 2개의 패스 각각이 사용한 FNaniteMaterialPassCommand 의 범위를 OutNaniteMaterialPassCommands 에서 찾을 수 있도록 Offset 과 Command 수를 기록하는 OutMaterialPassInfo(FNaniteMAterialPassInfo)의 값을 설정해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;OutMaterialPassInfo&amp;nbsp;의&amp;nbsp;Offset,&amp;nbsp;Num&amp;nbsp;값을&amp;nbsp;0&amp;nbsp;초기화해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp;GetMaterialPass&amp;nbsp;람다함수는&amp;nbsp;Material&amp;nbsp;이&amp;nbsp;WPO&amp;nbsp;를&amp;nbsp;사용하는&amp;nbsp;경우&amp;nbsp;EmitGBufferWithVelocity&amp;nbsp;그렇지&amp;nbsp;않은&amp;nbsp;경우&amp;nbsp;EmitGBuffer&amp;nbsp;를&amp;nbsp;리턴해줍니다.&amp;nbsp;EmitGBufferWithVelocity&amp;nbsp;면&amp;nbsp;두 번째&amp;nbsp;패스에서&amp;nbsp;렌더링&amp;nbsp;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.&amp;nbsp;InserPassCommand&amp;nbsp;람다함수는&amp;nbsp;생성된&amp;nbsp;FNaniteMaterialPassCommand&amp;nbsp;를&amp;nbsp;OutNaniteMaterialPassCommands&amp;nbsp;에&amp;nbsp;넣어줍니다.&amp;nbsp;이&amp;nbsp;때&amp;nbsp;전달되는&amp;nbsp;PassIndex&amp;nbsp;를&amp;nbsp;기반으로&amp;nbsp;첫번째&amp;nbsp;or&amp;nbsp;두번째&amp;nbsp;패스에&amp;nbsp;Command&amp;nbsp;를&amp;nbsp;추가해 주며,&amp;nbsp;OutMaterialPassInfo&amp;nbsp;에&amp;nbsp;Offset&amp;nbsp;과&amp;nbsp;Num&amp;nbsp;값을&amp;nbsp;경신해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.&amp;nbsp;BucketMaps&amp;nbsp;개수만큼&amp;nbsp;반복문을&amp;nbsp;수행합니다.&amp;nbsp;이때&amp;nbsp;BucketMap&amp;nbsp;을&amp;nbsp;가져온&amp;nbsp;MaterialCommands&amp;nbsp;는&amp;nbsp;Scene.NaniteMaterials[ENaniteMeshPass::BasePass]&amp;nbsp;입니다.&amp;nbsp;BucketMap&amp;nbsp;은&amp;nbsp;FNaniteMaterialEntry&amp;nbsp;항목을&amp;nbsp;갖고&amp;nbsp;있는데&amp;nbsp;Nanite&amp;nbsp;BasePass&amp;nbsp;에서&amp;nbsp;소유하고&amp;nbsp;있는&amp;nbsp;모든&amp;nbsp;머터리얼로&amp;nbsp;보면&amp;nbsp;됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. FNaniteMaterialEntry 가 사용할 MeshDrawCommand 를 가져옵니다. 이 MeshDrawCommand 는 SceneProxy 가 FScene 에 등록될 때 준비해 뒀으며, &lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;Nanite (1/5)&lt;/a&gt; 의&amp;nbsp;그림5&amp;nbsp;에서&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;Visibility&amp;nbsp;여부를&amp;nbsp;확인하는데&amp;nbsp;현재는&amp;nbsp;항상&amp;nbsp;그린다고&amp;nbsp;하고&amp;nbsp;계속해서&amp;nbsp;코드를&amp;nbsp;보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7.&amp;nbsp;FNaniteMaterialEntry&amp;nbsp;로&amp;nbsp;부터&amp;nbsp;필요한&amp;nbsp;Material&amp;nbsp;정보를&amp;nbsp;얻습니다.&amp;nbsp;중요한&amp;nbsp;정보는&amp;nbsp;MaterialDepth&amp;nbsp;정보입니다.&amp;nbsp;이&amp;nbsp;값은&amp;nbsp;현재&amp;nbsp;Material&amp;nbsp;을&amp;nbsp;대표하는&amp;nbsp;Unique&amp;nbsp;값으로&amp;nbsp;&lt;a href=&quot;https://scahp.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;Nanite (4/5)&lt;/a&gt; 의&amp;nbsp;그림5&amp;nbsp;에서&amp;nbsp;생성한&amp;nbsp;MaterialDepth&amp;nbsp;버퍼를&amp;nbsp;Depth&amp;nbsp;Buffer&amp;nbsp;로&amp;nbsp;바인딩하고&amp;nbsp;이&amp;nbsp;MaterialDepth&amp;nbsp;값을&amp;nbsp;Vertex&amp;nbsp;Shader&amp;nbsp;에서&amp;nbsp;사용하도록&amp;nbsp;합니다.&amp;nbsp;Material&amp;nbsp;Pass&amp;nbsp;에서는&amp;nbsp;Depth&amp;nbsp;Test&amp;nbsp;를&amp;nbsp;Equal&amp;nbsp;로&amp;nbsp;설정하여&amp;nbsp;현재&amp;nbsp;렌더링 하는&amp;nbsp;MaterialSlot&amp;nbsp;정보와&amp;nbsp;동일한&amp;nbsp;픽셀을&amp;nbsp;가진&amp;nbsp;DepthBuffer&amp;nbsp;의&amp;nbsp;픽셀만&amp;nbsp;Depth&amp;nbsp;Test&amp;nbsp;에&amp;nbsp;통과하게&amp;nbsp;하는&amp;nbsp;방식으로&amp;nbsp;쉐이딩 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8.&amp;nbsp;MeshDrawCommand&amp;nbsp;SortKey&amp;nbsp;를&amp;nbsp;설정합니다.&amp;nbsp;우리는&amp;nbsp;bVelocityPassEnabled&amp;nbsp;조건문&amp;nbsp;내로&amp;nbsp;진입하게&amp;nbsp;되며,&amp;nbsp;PassIndex&amp;nbsp;0&amp;nbsp;이&amp;nbsp;먼저&amp;nbsp;그려지고,&amp;nbsp;그&amp;nbsp;다음&amp;nbsp;Velocity&amp;nbsp;를&amp;nbsp;저장하는&amp;nbsp;PassIndex&amp;nbsp;1&amp;nbsp;이&amp;nbsp;렌더링&amp;nbsp;되어야&amp;nbsp;하기&amp;nbsp;때문에&amp;nbsp;거기에&amp;nbsp;맞게&amp;nbsp;SortKey&amp;nbsp;를&amp;nbsp;변경해 주는&amp;nbsp;부분을&amp;nbsp;볼&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9.&amp;nbsp;현재&amp;nbsp;처리 중인&amp;nbsp;Material&amp;nbsp;에&amp;nbsp;대한&amp;nbsp;FNaniteMaterialPassCommand&amp;nbsp;가&amp;nbsp;모두&amp;nbsp;준비되었기&amp;nbsp;때문에&amp;nbsp;4&amp;nbsp;에서&amp;nbsp;만든&amp;nbsp;InsertPassCommand&amp;nbsp;를&amp;nbsp;통해서&amp;nbsp;추가해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10.&amp;nbsp;생성한&amp;nbsp;FNaniteMaterialPassCommand&amp;nbsp;를&amp;nbsp;정렬합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1027&quot; data-origin-height=&quot;1754&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sEW86/btsF4XHIsoU/fTuvYNXJ1Amuycx0TJ7Bok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sEW86/btsF4XHIsoU/fTuvYNXJ1Amuycx0TJ7Bok/img.png&quot; data-alt=&quot;그림6.BuildNaniteMaterialPassCommands 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sEW86/btsF4XHIsoU/fTuvYNXJ1Amuycx0TJ7Bok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsEW86%2FbtsF4XHIsoU%2FfTuvYNXJ1Amuycx0TJ7Bok%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;1027&quot; height=&quot;1754&quot; data-origin-width=&quot;1027&quot; data-origin-height=&quot;1754&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림6.BuildNaniteMaterialPassCommands 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.6. DrawNaniteMaterialPass&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제&amp;nbsp;준비한&amp;nbsp;FNaniteMaterialPassCommand&amp;nbsp;를&amp;nbsp;렌더링 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;DrawNaniteMaterialPass&amp;nbsp;함수로&amp;nbsp;진입합니다.&amp;nbsp;WPO&amp;nbsp;여부에&amp;nbsp;따라서&amp;nbsp;2개의&amp;nbsp;패스로&amp;nbsp;나뉘어&amp;nbsp;실행되는데&amp;nbsp;각&amp;nbsp;패스별로&amp;nbsp;실행해야&amp;nbsp;할&amp;nbsp;FNaniteMaterialPassCommand&amp;nbsp;의&amp;nbsp;범위는&amp;nbsp;TArrayView&amp;nbsp;를&amp;nbsp;통해서&amp;nbsp;건네받습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;렌더링&amp;nbsp;할&amp;nbsp;FNaniteMaterialPassCommand&amp;nbsp;가&amp;nbsp;없다면&amp;nbsp;early&amp;nbsp;exit&amp;nbsp;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp;코드&amp;nbsp;리뷰를&amp;nbsp;간결하게&amp;nbsp;하기&amp;nbsp;위해서&amp;nbsp;Parallel&amp;nbsp;이&amp;nbsp;아닌&amp;nbsp;패스로&amp;nbsp;보도록&amp;nbsp;하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.&amp;nbsp;Viewport&amp;nbsp;를&amp;nbsp;설정하고,&amp;nbsp;SubmitNaniteIndirectMaterial&amp;nbsp;을&amp;nbsp;각각의&amp;nbsp;FNaniteMaterialPassCommand&amp;nbsp;별로&amp;nbsp;호출합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.&amp;nbsp;SubmitNaniteIndirectMaterial&amp;nbsp;로&amp;nbsp;진입합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. FNaniteMaterialPassCommand 에 설정했던 MaterialDepth 정보를 가져옵니다. 이 값을 8 번 과정에서 VertexShader PassParameter 에 넣어줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. SubmitDrawIndirectBegin 함수 내부에는 SubmitDrawBegin 함수가 있습니다. 이 부분은 렌더링에 필요한 사전 작업을 수행합니다. 자세한 내용은 MeshDrawCommand 를 다루는&amp;nbsp;&lt;a style=&quot;color: #111111; text-align: left;&quot; href=&quot;https://scahp.tistory.com/75&quot;&gt;[UE5] MeshDrawCommand (2/2)&lt;/a&gt; 의 그림20 에서 확인해 볼 수 있습니다.&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;8. VertexShader 에 필요한 데이터를 바인딩합니다. MaterialDepth 와 MaterialSlot, 그리고 InstanceFactor 는 현재 Material 이 렌더링 하는 타일의 개수입니다. 이것을 32 개로 묶은 경우 묶음의 개수를 TileRemapCount 에 담습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. MaterialIndirectArgs 버퍼는 MaterialSlot 개수만큼 Indirect draw argument 가 있기 때문에 거기에 맞게 사용할 퍼 Offset 을 구한 다음 SubmitDrawIndirectEnd 를 호출합니다. 이 함수에서는 실제 DrawIndexed 와 같은 렌더링 드로콜을 호출합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PelHB/btsF2DwFYwX/UKtDKgW5czuckbdlx4XGAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PelHB/btsF2DwFYwX/UKtDKgW5czuckbdlx4XGAk/img.png&quot; data-alt=&quot;그림7. FNaniteIndirectMaterialVS 버택스 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PelHB/btsF2DwFYwX/UKtDKgW5czuckbdlx4XGAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPelHB%2FbtsF2DwFYwX%2FUKtDKgW5czuckbdlx4XGAk%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;1050&quot; height=&quot;1204&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;1204&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림7. FNaniteIndirectMaterialVS 버택스 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.7. FNaniteIndirectMaterialVS 쉐이더 코드 확인&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 FNaniteIndirectMaterialVS 입니다. 이 Vertex Shader 는 순차적으로 증가하는 InstanceID 로부터 MaterialSlot 내에 그려야 할 Tile 의 인덱스(비 순차) 를 얻어내고, Depth 값이 MaterialDepth 값을 설정한 다음 Verte shader 를 마칩니다. 이때 바인딩 된 MaterialDepth Buffer 와 Vertex shader 에서 전달한 MaterialDepth 이 같은 픽셀만 Shading 이 수행됩니다.&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. 그림7의 6 번 코드에서 바인딩한 MaterialDepth, MaterialSlot, TileRemapCount 변수들과 VertexShader 의 진입점인 FullScreenVS 입니다. 이름은 Fullscreen 이지만 실제로는 64x64 의 타일 1개를 만듭니다. 여기서 SV_InstanceID 는 [0~MaterialSlot 이 그릴 타일 수] 중 하나가 들어옵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;GridSize&amp;nbsp;는&amp;nbsp;Tile&amp;nbsp;의&amp;nbsp;총&amp;nbsp;X,&amp;nbsp;Y&amp;nbsp;별&amp;nbsp;개수입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp;TileIndex&amp;nbsp;를&amp;nbsp;InstanceIndex&amp;nbsp;로&amp;nbsp;설정합니다.&amp;nbsp;현재&amp;nbsp;렌더링&amp;nbsp;중인&amp;nbsp;MaterialSlot&amp;nbsp;를&amp;nbsp;사용하는&amp;nbsp;Tile&amp;nbsp;의&amp;nbsp;인덱스가&amp;nbsp;0,&amp;nbsp;1,&amp;nbsp;2&amp;nbsp;일지?&amp;nbsp;0,&amp;nbsp;5,&amp;nbsp;20&amp;nbsp;일지는&amp;nbsp;모르기&amp;nbsp;때문에&amp;nbsp;이&amp;nbsp;TileIndex&amp;nbsp;값을&amp;nbsp;실제&amp;nbsp;현재&amp;nbsp;MaterialSlot&amp;nbsp;이&amp;nbsp;사용하는&amp;nbsp;값을&amp;nbsp;변환시킬&amp;nbsp;것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.&amp;nbsp;MaterialTileRemap&amp;nbsp;에는&amp;nbsp;MaterialSlot&amp;nbsp;을&amp;nbsp;알면&amp;nbsp;어떤&amp;nbsp;Tile&amp;nbsp;Index&amp;nbsp;를&amp;nbsp;렌더링&amp;nbsp;해야&amp;nbsp;할지&amp;nbsp;정보가&amp;nbsp;들어&amp;nbsp;있다고&amp;nbsp;했었습니다.&amp;nbsp;그리고&amp;nbsp;InstanceIndex&amp;nbsp;는&amp;nbsp;[0,&amp;nbsp;MaterialSlot&amp;nbsp;이&amp;nbsp;그릴&amp;nbsp;타일&amp;nbsp;수]&amp;nbsp;가&amp;nbsp;들어온다고&amp;nbsp;하였습니다.&amp;nbsp;이&amp;nbsp;선형적으로&amp;nbsp;증가하는&amp;nbsp;InstanceIndex&amp;nbsp;숫자를&amp;nbsp;실제&amp;nbsp;사용하는&amp;nbsp;Tile&amp;nbsp;Index&amp;nbsp;에&amp;nbsp;매핑하기&amp;nbsp;위해서&amp;nbsp;아주&amp;nbsp;간단한&amp;nbsp;방법을&amp;nbsp;사용합니다.&amp;nbsp;바로&amp;nbsp;[0,&amp;nbsp;MaterialSlot&amp;nbsp;이&amp;nbsp;그릴&amp;nbsp;타일&amp;nbsp;수]&amp;nbsp;범위의&amp;nbsp;bit&amp;nbsp;앞에서&amp;nbsp;부터&amp;nbsp;차례로&amp;nbsp;세면서&amp;nbsp;(InstanceIndex&amp;nbsp;+&amp;nbsp;1)&amp;nbsp;개수와&amp;nbsp;일치하는&amp;nbsp;Tile&amp;nbsp;Index&amp;nbsp;를&amp;nbsp;찾아서&amp;nbsp;그립니다.&amp;nbsp;계속해서&amp;nbsp;코드를&amp;nbsp;봅시다.&amp;nbsp;그전에&amp;nbsp;MaterialTileRemap&amp;nbsp;에서&amp;nbsp;현재&amp;nbsp;MaterialSlot&amp;nbsp;까지의&amp;nbsp;Offset&amp;nbsp;을&amp;nbsp;구합니다.&amp;nbsp;그&amp;nbsp;값을&amp;nbsp;RemapBaseOffset&amp;nbsp;에&amp;nbsp;저장합니다.&amp;nbsp;그리고&amp;nbsp;TargetTileCount&amp;nbsp;=&amp;nbsp;TileIndex&amp;nbsp;+&amp;nbsp;1&amp;nbsp;로&amp;nbsp;설정합니다.&amp;nbsp;(1&amp;nbsp;로&amp;nbsp;설정된&amp;nbsp;비트&amp;nbsp;개수가&amp;nbsp;TargetTileCount&amp;nbsp;와&amp;nbsp;같아지면&amp;nbsp;해당&amp;nbsp;Tile&amp;nbsp;Index&amp;nbsp;를&amp;nbsp;찾은&amp;nbsp;것임!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.&amp;nbsp;TileRemapCount&amp;nbsp;는&amp;nbsp;타일의&amp;nbsp;총개수를&amp;nbsp;32&amp;nbsp;개로&amp;nbsp;묶었을&amp;nbsp;때&amp;nbsp;묶음&amp;nbsp;수입니다.&amp;nbsp;각&amp;nbsp;uint&amp;nbsp;값은&amp;nbsp;32&amp;nbsp;개의&amp;nbsp;bit&amp;nbsp;masking&amp;nbsp;이&amp;nbsp;가능하기&amp;nbsp;때문에&amp;nbsp;모든&amp;nbsp;Tile&amp;nbsp;을&amp;nbsp;순회하는데&amp;nbsp;TileRemapCount&amp;nbsp;횟수만큼&amp;nbsp;수행하면&amp;nbsp;됩니다.&amp;nbsp;MaterialTileRemap&amp;nbsp;에서&amp;nbsp;설정된&amp;nbsp;bit&amp;nbsp;를&amp;nbsp;가져와서&amp;nbsp;설정된&amp;nbsp;bit&amp;nbsp;의&amp;nbsp;수만큼&amp;nbsp;ValidTiles&amp;nbsp;를&amp;nbsp;증가시켜줍니다.&amp;nbsp;그리고&amp;nbsp;ValidTiles&amp;nbsp;&amp;gt;&amp;nbsp;TargetCount&amp;nbsp;가&amp;nbsp;된다면&amp;nbsp;현재&amp;nbsp;RemapData(MaterialTileRemap&amp;nbsp;에서&amp;nbsp;가져온&amp;nbsp;32&amp;nbsp;개의&amp;nbsp;bit)&amp;nbsp;중&amp;nbsp;하나에&amp;nbsp;현재&amp;nbsp;Instance&amp;nbsp;가&amp;nbsp;그려야&amp;nbsp;할&amp;nbsp;Tile&amp;nbsp;Index&amp;nbsp;가&amp;nbsp;있다는&amp;nbsp;것입니다.&amp;nbsp;그럼&amp;nbsp;여기서&amp;nbsp;멈추고&amp;nbsp;계속해서&amp;nbsp;RemapData&amp;nbsp;내의&amp;nbsp;32&amp;nbsp;bit&amp;nbsp;mask&amp;nbsp;를&amp;nbsp;읽어봐야겠죠?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6.&amp;nbsp;RemapData&amp;nbsp;를&amp;nbsp;앞에서부터&amp;nbsp;하나씩&amp;nbsp;읽어가면서&amp;nbsp;ValidTiles&amp;nbsp;==&amp;nbsp;TargetTileCount&amp;nbsp;인&amp;nbsp;경우까지&amp;nbsp;진행합니다.&amp;nbsp;반복문을&amp;nbsp;마치고&amp;nbsp;나면,&amp;nbsp;RemapBinBit&amp;nbsp;는&amp;nbsp;현재&amp;nbsp;비트가&amp;nbsp;32&amp;nbsp;비트&amp;nbsp;중&amp;nbsp;몇&amp;nbsp;번째&amp;nbsp;index&amp;nbsp;인지를&amp;nbsp;담고&amp;nbsp;있기&amp;nbsp;때문에&amp;nbsp;TileIndex&amp;nbsp;=&amp;nbsp;(RemapBin&amp;nbsp;*&amp;nbsp;32)&amp;nbsp;+&amp;nbsp;RemapBinBit&amp;nbsp;로&amp;nbsp;구할&amp;nbsp;수&amp;nbsp;있을&amp;nbsp;것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7.&amp;nbsp;이&amp;nbsp;값은&amp;nbsp;&amp;ldquo;Tile&amp;nbsp;(64x64)&amp;nbsp;가&amp;nbsp;NxM&amp;nbsp;개&amp;nbsp;있을&amp;nbsp;때&amp;nbsp;해상도&amp;nbsp;/&amp;nbsp;실제&amp;nbsp;해상도&amp;rdquo;&amp;nbsp;의&amp;nbsp;비율을&amp;nbsp;갖고&amp;nbsp;있습니다.&amp;nbsp;64x64&amp;nbsp;타일&amp;nbsp;단위로&amp;nbsp;쪼개게&amp;nbsp;되면,&amp;nbsp;실제&amp;nbsp;해상도가&amp;nbsp;정확한&amp;nbsp;크기의&amp;nbsp;64&amp;nbsp;사이즈로&amp;nbsp;잘리지&amp;nbsp;않기&amp;nbsp;때문에&amp;nbsp;남는&amp;nbsp;부분이&amp;nbsp;생깁니다.&amp;nbsp;이&amp;nbsp;부분에&amp;nbsp;대한&amp;nbsp;비율을&amp;nbsp;구하는&amp;nbsp;것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8.&amp;nbsp;6에서&amp;nbsp;구한&amp;nbsp;TileIndex&amp;nbsp;를&amp;nbsp;PixelShader&amp;nbsp;로&amp;nbsp;보내기&amp;nbsp;위해서&amp;nbsp;설정해 줍니다.&amp;nbsp;GridStep&amp;nbsp;은&amp;nbsp;Tile&amp;nbsp;한 개가&amp;nbsp;UV(0~1&amp;nbsp;사이값)&amp;nbsp;을&amp;nbsp;기준으로&amp;nbsp;했을&amp;nbsp;때&amp;nbsp;얼마의&amp;nbsp;크기인지에&amp;nbsp;대한&amp;nbsp;값입니다.&amp;nbsp;GridPos&amp;nbsp;는&amp;nbsp;현재&amp;nbsp;TileIndex&amp;nbsp;가&amp;nbsp;선형인덱스&amp;nbsp;기반인&amp;nbsp;x,&amp;nbsp;y&amp;nbsp;기반으로&amp;nbsp;변경해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9.&amp;nbsp;현재&amp;nbsp;Tile&amp;nbsp;의&amp;nbsp;Vertex&amp;nbsp;가&amp;nbsp;UV&amp;nbsp;좌표를&amp;nbsp;기반으로&amp;nbsp;어떤&amp;nbsp;값을&amp;nbsp;가지는지&amp;nbsp;계산합니다.&amp;nbsp;Tile&amp;nbsp;을&amp;nbsp;위한&amp;nbsp;4&amp;nbsp;개의&amp;nbsp;Vertex&amp;nbsp;가&amp;nbsp;이제&amp;nbsp;UV&amp;nbsp;값으로&amp;nbsp;변환되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10.&amp;nbsp;이제&amp;nbsp;최종&amp;nbsp;Position&amp;nbsp;값을&amp;nbsp;확정합니다.&amp;nbsp;위에서&amp;nbsp;구한&amp;nbsp;Tile&amp;nbsp;의&amp;nbsp;UV&amp;nbsp;좌표를&amp;nbsp;NDC&amp;nbsp;공간으로&amp;nbsp;변환하고,&amp;nbsp;Depth&amp;nbsp;값을&amp;nbsp;Material&amp;nbsp;Depth&amp;nbsp;로&amp;nbsp;설정합니다.&amp;nbsp;이제&amp;nbsp;나머지는&amp;nbsp;MaterialDepth&amp;nbsp;Buffer&amp;nbsp;의&amp;nbsp;Depth&amp;nbsp;값과&amp;nbsp;Vertex&amp;nbsp;shader&amp;nbsp;에서&amp;nbsp;설정한&amp;nbsp;Material&amp;nbsp;Depth&amp;nbsp;값이&amp;nbsp;일치하는&amp;nbsp;픽셀에&amp;nbsp;대해서&amp;nbsp;Shading&amp;nbsp;한&amp;nbsp;결과를&amp;nbsp;각각의&amp;nbsp;G-Buffer&amp;nbsp;RT&amp;nbsp;에&amp;nbsp;저장합니다.&amp;nbsp;Pixel&amp;nbsp;Shader&amp;nbsp;의&amp;nbsp;경우&amp;nbsp;기존의&amp;nbsp;BasePassPixelShader.usf&amp;nbsp;와&amp;nbsp;동일하기&amp;nbsp;때문에&amp;nbsp;따로&amp;nbsp;다루지&amp;nbsp;않아도&amp;nbsp;될&amp;nbsp;것&amp;nbsp;같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;1813&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nusFQ/btsF0EXYBpy/rfREe8txNiXsp2jtMs1opK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nusFQ/btsF0EXYBpy/rfREe8txNiXsp2jtMs1opK/img.png&quot; data-alt=&quot;그림8. DrawNaniteMaterialPass 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nusFQ/btsF0EXYBpy/rfREe8txNiXsp2jtMs1opK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnusFQ%2FbtsF0EXYBpy%2FrfREe8txNiXsp2jtMs1opK%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;1114&quot; height=&quot;1813&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;1813&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림8. DrawNaniteMaterialPass 코드 (출처 : 레퍼런스1)&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Material Pass 에서 Tile 을 렌더링을 디버깅해보면 아래와 같이 머터리얼이 영향을 주는 Tile 영역만 렌더링 된 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;944&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccU7hL/btsF4WCUg4Z/HOgvMaQlQQfIw9HmYhPhW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccU7hL/btsF4WCUg4Z/HOgvMaQlQQfIw9HmYhPhW1/img.png&quot; data-alt=&quot;그림9. Material Pass Tile 렌더링 Wireframe 으로 렌더독에서 확인 (출처 : 직접촬영)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccU7hL/btsF4WCUg4Z/HOgvMaQlQQfIw9HmYhPhW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FccU7hL%2FbtsF4WCUg4Z%2FHOgvMaQlQQfIw9HmYhPhW1%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;1102&quot; height=&quot;944&quot; data-origin-width=&quot;1102&quot; data-origin-height=&quot;944&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림9. Material Pass Tile 렌더링 Wireframe 으로 렌더독에서 확인 (출처 : 직접촬영)&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;&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;1084&quot; data-origin-height=&quot;813&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GJkh2/btsF3EbAOpo/cDgDmtfnAjoiYuYrHzRG40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GJkh2/btsF3EbAOpo/cDgDmtfnAjoiYuYrHzRG40/img.png&quot; data-alt=&quot;그림10. Material Pass Tile 렌더링 시 Material Depth 를 사용하여 관련된 픽셀만 Depth Test 를 통과하도록 한 것을 렌더독으로 디버깅 (출처 : 직접 촬영)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GJkh2/btsF3EbAOpo/cDgDmtfnAjoiYuYrHzRG40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGJkh2%2FbtsF3EbAOpo%2FcDgDmtfnAjoiYuYrHzRG40%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;1084&quot; height=&quot;813&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;813&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림10. Material Pass Tile 렌더링 시 Material Depth 를 사용하여 관련된 픽셀만 Depth Test 를 통과하도록 한 것을 렌더독으로 디버깅 (출처 : 직접 촬영)&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 부록&lt;/b&gt;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4.1. MaterialDepth 값에 생성에 대해서&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://scahp.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Nanite (4/5)&lt;/a&gt; 의 그림5 에서 만든 MaterialDepth Buffer 는 MaterialDepthId 값을 사용하여 MaterialDepth 값을 구성합니다. 이 값은 MaterialDepthTable 이라는 버퍼에서 가져온 데이터를 통해서 구성되는데요. 이 버퍼는 어떻게 구성되는지? BuildNaniteMaterialPassCommands (그림6 의 7번 코드)의 과정에서 FNaniteCommandInfo::GetDepthId(MaterialId) 와 정말 같은 값을 사용하여 만들어졌는지 확인해 보면 좋을 것 같아서 부록으로 해당 내용을 추가했습니다.&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;먼저 BuildNaniteMaterialPassCommands 에서는 아래와 같은 코드를 통해서 FNaniteIndirectMaterialVS 가 MaterialDepth 를 사용할 수 있도록 값을 전달해 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1711366018613&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BuildNaniteMaterialPassCommands 과정에서...
const int32 MaterialId = Iter.GetElementId().GetIndex();
PassCommand.MaterialDepth = FNaniteCommandInfo::GetDepthId(MaterialId);
static float GetDepthId(int32 StateBucketId)
{
	return float(StateBucketId + 1) / float(NANITE_MAX_STATE_BUCKET_ID);
}&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;a style=&quot;background-color: #e6f5ff; color: #0070d1; text-align: start;&quot; href=&quot;https://scahp.tistory.com/129&quot;&gt;Nanite (4/5)&lt;/a&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;의 그림5 의 4번 코드를 보면 MaterailDepthTable 에서 Material 값을 로드합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MaterialDepthTable 은 GPUScene 에 MaterialSlot 값이 업데이트될 때 같이 업데이트됩니다. 아래 코드를 봐주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 보면 MaterialDepthUploader 를 통해 MaterialSlot 위치에 MaterialEntry.MaterialId 를 저장하는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1711366111132&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void FNaniteMaterialCommands::FUploader::Lock(FRHICommandListBase&amp;amp; RHICmdList)
{
...
  for (const FMaterialUploadEntry&amp;amp; MaterialEntry : DirtyMaterialEntries)
  {
    *static_cast&amp;lt;uint32*&amp;gt;(MaterialDepthUploader-&amp;gt;Add_GetRef(MaterialEntry.MaterialSlot))
      = MaterialEntry.MaterialId;
...
  }
  DirtyMaterialEntries.Empty();
}&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;그럼 MaterialEntry.MaterialId 는 어떻게 만들어지는 걸까요? 아래코드를 봐주세요. 아래 코드에서 MaterialEntry.MaterialId 를 생성할 때 사용한 함수와 PassCommand.MaterialDepth = FNanitecommandInfo::GetDepthId(MaterialId) 가 동일한 함수와 아규먼트를 통해 MaterialDepth 를 생성한 것을 알 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1711366415155&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FNaniteCommandInfo FNaniteMaterialCommands::Register(FMeshDrawCommand&amp;amp; Command, FCommandHash CommandHash, uint32 InstructionCount, bool bWPOEnabled)
{
  FNaniteCommandInfo CommandInfo;
  FCommandId CommandId = FindOrAddIdByHash(CommandHash, Command);
  CommandInfo.SetStateBucketId(CommandId.GetIndex());        // ** 여기서 설정한 StateBucketId 를 사용하여 MaterialId 를 만듬.

  FNaniteMaterialEntry&amp;amp; MaterialEntry = GetPayload(CommandId);
  if (MaterialEntry.ReferenceCount == 0)
  {
    check(MaterialEntry.MaterialSlot == INDEX_NONE);
    MaterialEntry.MaterialSlot = MaterialSlotAllocator.Allocate(1);
    MaterialEntry.MaterialId = CommandInfo.GetMaterialId();    // ** GetMaterialId 함수를 호출하여 최종적으로 MaterialId 를 만들어냄
...

// 아래는 FNaniteCommandInfo 의 멤버 함수들...
inline void SetStateBucketId(int32 InStateBucketId)
{
  check(InStateBucketId &amp;lt; NANITE_MAX_STATE_BUCKET_ID);
  StateBucketId = InStateBucketId;
}
inline int32 GetStateBucketId() const
{
  check(StateBucketId &amp;lt; NANITE_MAX_STATE_BUCKET_ID);
  return StateBucketId;
}
inline uint32 GetMaterialId() const
{
  return GetMaterialId(GetStateBucketId());
}
static uint32 GetMaterialId(int32 StateBucketId)
{
  float DepthId = GetDepthId(StateBucketId);
  return *reinterpret_cast&amp;lt;uint32*&amp;gt;(&amp;amp;DepthId);
}
static float GetDepthId(int32 StateBucketId)
{
  return float(StateBucketId + 1) / float(NANITE_MAX_STATE_BUCKET_ID);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전글&amp;nbsp;&lt;a href=&quot;https://scahp.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #0070d1;&quot;&gt;[UE5] Nanite (4/5)&lt;/span&gt;&lt;/span&gt; &lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 레퍼런스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://github.com/EpicGames/UnrealEngine/commit/072300df18a94f18077ca20a14224b5d99fee872&quot;&gt;https://github.com/EpicGames/UnrealEngine/commit/072300df18a94f18077ca20a14224b5d99fee872&lt;/a&gt;&lt;br /&gt;2.&amp;nbsp;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=eviSykqSUUw&quot;&gt;https://www.youtube.com/watch?v=eviSykqSUUw&lt;/a&gt;&amp;nbsp;&lt;a style=&quot;color: #666666; text-align: left;&quot; href=&quot;https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;(Slide link)&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;3.&amp;nbsp;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://docs.unrealengine.com/5.0/en-US/nanite-virtualized-geometry-in-unreal-engine/&quot;&gt;https://docs.unrealengine.com/5.0/en-US/nanite-virtualized-geometry-in-unreal-engine/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. &lt;a href=&quot;https://scahp.tistory.com/129&quot;&gt;https://scahp.tistory.com/129&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. &lt;a href=&quot;https://scahp.tistory.com/126&quot;&gt;https://scahp.tistory.com/126&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>UE4 &amp;amp; UE5/Rendering</category>
      <category>GPU Driven Rendering</category>
      <category>Grid</category>
      <category>Material Pass</category>
      <category>MaterialDepth</category>
      <category>Nanite</category>
      <category>ShadingMask</category>
      <category>tile</category>
      <category>UE5</category>
      <category>velocity</category>
      <category>WPO</category>
      <author>scahp</author>
      <guid isPermaLink="true">https://scahp.tistory.com/130</guid>
      <comments>https://scahp.tistory.com/130#entry130comment</comments>
      <pubDate>Mon, 25 Mar 2024 20:44:13 +0900</pubDate>
    </item>
    <item>
      <title>[UE5] Nanite (4/5)</title>
      <link>https://scahp.tistory.com/129</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[UE5] Nanite (4/5)&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size16&quot;&gt;최초 작성 : 2024-03-23&lt;br /&gt;마지막 수정 : 2024-03-23&lt;br /&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;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 style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 환경&lt;/b&gt;&lt;br /&gt;&lt;b&gt;2. 목표&lt;/b&gt;&lt;br /&gt;&lt;b&gt;3. 내용&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; 3.1. EmitDepthTargets&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; 3.2. FEmitSceneDepthPS&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; 3.3. FEmitSceneStencilPS&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; 3.4. FEmitMaterialDepthPS&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; 3.5. 현재 프레임의 최종 HZB 생성&lt;/b&gt;&lt;br /&gt;&lt;b&gt;4. 레퍼런스&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 환경&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Unreal Engine 5.3.2 (release branch 072300df18a94f18077ca20a14224b5d99fee872)&lt;br /&gt;개인적으로 분석한 내용이라 틀린 점이 있을 수 있습니다. 그런 부분은 알려주시면 감사하겠습니다.&lt;br /&gt;&lt;br /&gt;이번 글은 한 번에 많은 길이의 코드를 분석하는 부분이 종종 등장합니다. 그래서 글을 2개 띄우고 한쪽은 설명 부분을 한쪽은 코드 이미지를 최대화해서 보는 것을 추천합니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 목표&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Nanite 에 들어간 핵심 기술들을 파악하고 BasePass 렌더링 과정을 코드레벨로 이해해 봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이번 글에서는 생성 된 Visibility Buffer 로 부터 Depth / Stencil Buffer 를 추출하고, 현재 프레임에 사용할 완성된 HZB 를 생성하는 부분을 확인해봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이전 글에서 이야기 했던 것 처럼 이 글은 총 5개로 구성될 예정입니다.&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;1. Nanite 1/5 : Nanite 에서 사용하는 주요 기술과 MeshDrawCommand 생성 및 VisibilityBuffer 초기화 과정 리뷰&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/127&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;2.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://scahp.tistory.com/127&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;Nanite 2/5 :&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://scahp.tistory.com/127&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp;TwoPassOcclusionCulling 의 전체 레이아웃을 확인하고 MainPass 의 노드 및 클러스터 컬링 리뷰&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/128&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;3.&amp;nbsp;Nanite 3/5 :&amp;nbsp;&amp;nbsp;SW, HW 레스터라이저와 PostPass 리뷰&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;4.&amp;nbsp;Nanite 4/5 :&amp;nbsp;&amp;nbsp;Visibility Buffer 로 부터 Depth/Stencil 텍스쳐를 생성하는 부분 리뷰&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/130&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;5.&amp;nbsp;Nanite 5/5 :&amp;nbsp;&amp;nbsp;Visibility Buffer 로 부터 G-Buffer 생성하는 MaterialPass 리뷰&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3.&amp;nbsp;내용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FRenderer::DrawGeometry 를 호출하고, FRenderer::ExtractResults 를 호출하고 나면 RasterResults 에는 VisibilityBuffer 가 준비되어 있습니다. 아직 BasePass 를 통해 G-Buffer 를 만들어내려면 머터리얼 쉐이딩 과정이 추가로 필요합니다. 그 전에 Depth Buffer 와 HZB 를 만드는 부분을 확인해봅시다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.1. EmitDepthTargets&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. EmitDepthTargets 내부로 진입합니다.&lt;br /&gt;2. VisibilityBuffer 로 부터 Depth Buffer 를 추출 할 것이므로 해당 버퍼를 VisBuffer64 에 옮겨둡니다.&lt;br /&gt;3. 기본 설정에서는 UseComputeDepthExport 를 사용하지 않기 때문에 else 분기로 이동합니다.&lt;br /&gt;4. Depth buffer 를 출력하는 렌더커맨드를 실행합니다. FullScreen Quad 로 렌더링되며 FEmitSceneDepthPS 쉐이더가 사용 됩니다. 자세한 내용은 그림2 에서 봅시다.&lt;br /&gt;5. Stencil buffer 출력하는 렌더커맨드를 싫애합니다. 마찬가지로 FullScreen Quad 로 렌더링되며 FEmitSceneStencilPS 쉐이더를 사용합니다. 자세한 내용은 그림4 에서 봅시다.&lt;br /&gt;6. 기본 설정이 UseLegacyCulling 을 사용하기 때문에 조건문 내부로 진입합니다. 역시 FullScreen Quad 로 렌더링되며 FEmitMaterialDepthPS 를 사용합니다. 이 Shader 는 G-Buffer 생성을 위한 Shading 과정에서 Depth Test 를 위해서 필요한 값을 렌더타겟에 출력합니다. 자세한 내용은 그림5 에서 봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;3492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfSBDb/btsF0FI9kQq/xv5H0u5V1IaEa1Gf08qAV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfSBDb/btsF0FI9kQq/xv5H0u5V1IaEa1Gf08qAV0/img.png&quot; data-alt=&quot;그림1. EmitDepthTargets 의 CPU 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfSBDb/btsF0FI9kQq/xv5H0u5V1IaEa1Gf08qAV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfSBDb%2FbtsF0FI9kQq%2Fxv5H0u5V1IaEa1Gf08qAV0%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;1052&quot; height=&quot;3492&quot; data-origin-width=&quot;1052&quot; data-origin-height=&quot;3492&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. EmitDepthTargets 의 CPU 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.2. FEmitSceneDepthPS&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;FEmitSceneDepthPS 를 실행하기 전에 Shader permutation 을 확인해봅시다. 그림1의 4번 코드를 보면 LegacyCullingDim, FVelocityExportDim, ShadingMaskExportDim 세가지가 있습니다. 이 부분이 모두 활성화 되어있다고 가정하고 코드를 봅시다. 쉐이더 코드에는 각각이 LEGACY_CULLING, VELOCITY_EXPORT, SHADING_MASK_EXPORT 에 해당합니다. 여기서 ShadingMask 는 해당 픽셀을 쉐이딩하는데 필요한 정보들이 들어있는데, 라이팅 채널, ShadingBin, DecalReceiver 여부 등등이 기록됩니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. EmitSceneDepthPS 에 진입합니다. ShadingMask, Velocity, Depth 를 기록할 RT 가 차례로 바인딩 된 것을 볼 수 있습니다.&lt;br /&gt;2. Visibility Buffer 로 부터 &lt;span style=&quot;color: #333333;&quot;&gt;현재 픽셀에 대한 &lt;/span&gt;VisPixel 을 얻어옵니다.&lt;br /&gt;3. 우리는 &lt;a href=&quot;https://scahp.tistory.com/128&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Nanite (3/5)&lt;/span&gt;&lt;/a&gt;&amp;nbsp;에서 Visibility Buffer 에는 Depth, ClusterIndex, TriangleIndex 로 총 3개의 값을 저장하는 것을 봤습니다. 해당 값을 가져옵니다.&lt;br /&gt;4. ClusterIndex 가 있다면 조건문내로 진입합니다. 조건문내로 진입하지 못하면 else 구문으로 이동해 모든 RT 에 0 을 대입하고 쉐이더를 종료합니다. Cluster 정보를 기반으로 해당 클러스터의 정보와 관련된 View 정보 등등을 얻습니다.&lt;br /&gt;5. SHADING_MASK_EXPORT 를 사용하는 경우 FCluster 를 준비합니다. 우리는 LEGACY_CULLING 을 사용하기 때문에 GetMaterialLegacyShadingId 에서 부터 ShadingBin 을 얻습니다. GetMaterialLegacyShadingId 와 GetMaterialShadingBin 의 차이는 MaterialSlot 으로 부터 ShadingBin 을 가져올 때, MaterialSlot 의 LegacyShadingId 을 가져올지? ShadingBin 을 가져올지 여부의 차이 입니다. ShadingBin 의 경우 &lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Nanite (1/5)&lt;/span&gt;&lt;/a&gt;&amp;nbsp;의 그림5 에 16번 코드 바로 밑에 보면 bAllowComputeMaterials 조건문 내로 진입하는 경우 ShadingBin 을 할당합니다. 하지만 우리는 LegacyShading 방식을 사용하기 때문에 bAllowComputeMaterials 는 false 였고, 우리가 가진것은 LegacyShadingId 뿐일 것입니다. LegacyShadingId 는 &lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Nanite (1/5)&lt;/span&gt;&lt;/a&gt;&amp;nbsp;의 추가그림1 에 3.3번 코드에서 할당 된 MaterialSlot 변수 입니다.&lt;br /&gt;6. 5에서 준비한 ShadingBin 을 포함하여 현재 픽셀이 쉐이딩에 사용하는 기능들을 마스크로 명세합니다. 그림3 에서 PackShadingMask 함수를 볼 수 있으며 어떤 내용이 기록되는지 알 수 있습니다.&lt;br /&gt;7. 계속해서 Velocity 정보를 준비합니다. 여기 보면 WPO 가 켜져있는 경우 bOutputVelocity 가 꺼지게 되는 것을 볼 수 있습니다. WPO 를 쓰는 경우 Velocity 정보가 깨지면서 TAA 같이 Reprojection 이 필요한 부분에 영향을 줄 수 있어 보입니다. WPO 가 켜져있는 경우 Velocity 정보를 기록하는 부분은 Material Pass 입니다. &lt;a href=&quot;https://scahp.tistory.com/130&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Nanite (5/5)&lt;/a&gt; 그림 6 의 3 코드에서 WPO 에 따라서 Velocity 를 렌더링하는 패스에 MeshDrawCommand 를 넣는 부분을 확인할 수 있습니다.&lt;br /&gt;8. Velocity 를 구하기 위해서 현재 프레임의 이전 프레임 정보와 현재 프레임의 정보를 기반으로 Velocity 를 생성하는 것을 볼 수 있습니다. Velocity 계산과 텍스쳐에 저장을 위해 어떻게 인코딩 하는지는 Nanite 의 범위를 벗어나기 때문에 생략합니다.&lt;br /&gt;9. 마지막으로 Depth 값을 float 로 변환해서 저장하는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;1768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bltui5/btsF157QSut/G2aWF6lyYukh9wpp6wLGT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bltui5/btsF157QSut/G2aWF6lyYukh9wpp6wLGT1/img.png&quot; data-alt=&quot;그림2. FEmitSceneDepthPS 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bltui5/btsF157QSut/G2aWF6lyYukh9wpp6wLGT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbltui5%2FbtsF157QSut%2FG2aWF6lyYukh9wpp6wLGT1%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;1094&quot; height=&quot;1768&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;1768&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림2. FEmitSceneDepthPS 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;279&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cozxXz/btsF0G89T5b/KYmBATb9qkiilK6X5p7YSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cozxXz/btsF0G89T5b/KYmBATb9qkiilK6X5p7YSk/img.png&quot; data-alt=&quot;그림3. PackShadingMask 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cozxXz/btsF0G89T5b/KYmBATb9qkiilK6X5p7YSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcozxXz%2FbtsF0G89T5b%2FKYmBATb9qkiilK6X5p7YSk%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;730&quot; height=&quot;279&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;279&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림3. PackShadingMask 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&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;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.3. FEmitSceneStencilPS&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 Stencil 정보를 출력합니다.&lt;br /&gt;1. EmitSceneStencilPS 함수에 진입합니다. 이번에는 Depth Buffer 만 바인딩 되어있습니다.&lt;br /&gt;2.&amp;nbsp;그림2 코드에서 생성한 ShadingMask 를 얻어옵니다.&lt;br /&gt;3. ShadingMask 에 NanitePixel 이고, bDecalReceiver 가 켜져있다면 Depth 를 출력합니다. 이렇게 보면 바로전 단계에서 Depth Buffer 를 출력한 것과 다른 것이 없는데, 어떤 점이 다를까요? Stencil 값을 어떤 값으로 갱신할까요? 그림1 에 초록색 네모 부분을 보면 Stencil 마스크를 설정하는 부분이 있습니다. 그 부분을 따라가보면 비밀이 풀립니다. 계속해서 관련 코드를 봅시다.&lt;br /&gt;4. 편의를 위해서 그림1의 초록색 부분의 코드를 가져왔습니다. DecalRecive 에 대한 Stencil Mask 가 따로 정의되어 있는 것을 볼 수 있습니다. 7 bit 이므로 1 &amp;lt;&amp;lt; 7 이면 128 이 됩니다.&lt;br /&gt;5. DepthStencilState 를 설정하는데, StencilMask 에 DistanceField 도 같이 Masking 하는 것을 볼 수 있습니다. 4에 보면 DistanceField 의 경우 2 이므로 1 &amp;lt;&amp;lt; 2 이면 4 가 됩니다. 그래서 Stencil Mask 로 132(0x84) 가 저장됩니다. StencilTest Pass 의 상태에서 저장됩니다.&lt;br /&gt;6. 렌더독의 파이프라인 스테이트를 보면 Depth 는 GEqual 이기 때문에 같은 Depth 값을 출력하는 경우 다시 기록 되었을 것입니다. 그리고 StencilTest Pass 의 경우 0x84 로 교체한다는 파이프라인 스테이트를 확인할 수 있습니다. 이 과정을 지나면 Nanite 픽셀이고 bIsDecalReciver ShadingMask 가 있는 픽셀에는 Stencil 에 모두 132 값이 설정됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;1122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOxflH/btsF02EgPqB/wBOT4tklpwikCxEmlq19x1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOxflH/btsF02EgPqB/wBOT4tklpwikCxEmlq19x1/img.png&quot; data-alt=&quot;그림4. EmitSceneStencilPS 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOxflH/btsF02EgPqB/wBOT4tklpwikCxEmlq19x1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOxflH%2FbtsF02EgPqB%2FwBOT4tklpwikCxEmlq19x1%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;880&quot; height=&quot;1122&quot; data-origin-width=&quot;880&quot; data-origin-height=&quot;1122&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림4. EmitSceneStencilPS 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.4. FEmitMaterialDepthPS&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 EmitMaterialDepth 입니다. 여기서 출력되는 결과는 &lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Nanite (1/5)&lt;/span&gt;&lt;/a&gt;&amp;nbsp;의 그림5 에 8.2 에서 DepthEqual 설정을 해주는 이유와 관련되어 있습니다. 자세한 내용은 Material Pass 를 다루는 다음글에서 확인해보고 이번 쉐이더에서 하는 일을 확인해봅시다.&lt;br /&gt;1. 출력은 Depth Buffer 밖에 없습니다.&lt;br /&gt;2. ShadingMask 를 로드 가능하면, 기존에 Mask 로 부터 손쉽게 NanitePixel 여부와 ShadingBin 정보를 얻어 옵니다.&lt;br /&gt;3. 그렇지 않은 경우 그림2에서 했던 것 처럼 NanitePixel 여부와 ShadingBin 을 얻어옵니다. 해당 내용은 그림2에서 봤기 때문에 넘어갑니다.&lt;br /&gt;4. NanitePixel 인 경우 ShadingBin 을 사용하여 MaterialSlotTable 로 부터 MaterialDepthId 를 얻어와서 저장합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;908&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UXrx2/btsF3FG9I7L/COMwsa8lI3ioxkOSPUqqz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UXrx2/btsF3FG9I7L/COMwsa8lI3ioxkOSPUqqz1/img.png&quot; data-alt=&quot;그림5. FEmitMaterialDepthPS 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UXrx2/btsF3FG9I7L/COMwsa8lI3ioxkOSPUqqz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUXrx2%2FbtsF3FG9I7L%2FCOMwsa8lI3ioxkOSPUqqz1%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;942&quot; height=&quot;908&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;908&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림5. FEmitMaterialDepthPS 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.5. 현재 프레임의 최종 HZB 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 Depth Buffer 가 준비 되었기 때문에 이번 프레임에 사용할 최종 HZB 의 생성이 가능합니다. HZB 의 생성 부분을 보는 것은 Nanite 코드 리뷰의 범위를 벗어나기 때문에 어느 지점에서 생성되는지만 확인해봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아래 그림6 을 보면 BeginOcclusionTests 를 할 때, 내부에서 HZB 를 빌드하는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;392&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEKMZx/btsF3VC9wvy/9DPMjkkRHZp6kReVL0H3jK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEKMZx/btsF3VC9wvy/9DPMjkkRHZp6kReVL0H3jK/img.png&quot; data-alt=&quot;그림6. 현재 프레임의 최종 HZB 를 생성하는 부분 렌더독 (출처 : 직접 촬영)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEKMZx/btsF3VC9wvy/9DPMjkkRHZp6kReVL0H3jK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEKMZx%2FbtsF3VC9wvy%2F9DPMjkkRHZp6kReVL0H3jK%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;618&quot; height=&quot;392&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;392&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림6. 현재 프레임의 최종 HZB 를 생성하는 부분 렌더독 (출처 : 직접 촬영)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이전글&amp;nbsp;&lt;a href=&quot;https://scahp.tistory.com/128&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #0070d1;&quot;&gt;[UE5] Nanite (3/5)&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;다음글 &lt;a href=&quot;https://scahp.tistory.com/130&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[UE5] Nanite (5/5)&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 레퍼런스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;&lt;a href=&quot;https://github.com/EpicGames/UnrealEngine/commit/072300df18a94f18077ca20a14224b5d99fee872&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/EpicGames/UnrealEngine/commit/072300df18a94f18077ca20a14224b5d99fee872&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;2.&amp;nbsp;&lt;a href=&quot;https://www.youtube.com/watch?v=eviSykqSUUw&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://www.youtube.com/watch?v=eviSykqSUUw&lt;/span&gt;&lt;/a&gt;&amp;nbsp;&lt;a href=&quot;https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;(Slide link)&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;3.&amp;nbsp;&lt;a href=&quot;https://docs.unrealengine.com/5.0/en-US/nanite-virtualized-geometry-in-unreal-engine/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://docs.unrealengine.com/5.0/en-US/nanite-virtualized-geometry-in-unreal-engine/&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;4. &lt;a href=&quot;https://scahp.tistory.com/128&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://scahp.tistory.com/128&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;5. &lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://scahp.tistory.com/126&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;6. &lt;a href=&quot;https://scahp.tistory.com/130&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://scahp.tistory.com/130&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>UE4 &amp;amp; UE5/Rendering</category>
      <category>BasePass</category>
      <category>Depth</category>
      <category>GPU Driven Rendering</category>
      <category>hzb</category>
      <category>Nanite</category>
      <category>ShadingMask</category>
      <category>stencil</category>
      <category>UE5</category>
      <category>velocity</category>
      <author>scahp</author>
      <guid isPermaLink="true">https://scahp.tistory.com/129</guid>
      <comments>https://scahp.tistory.com/129#entry129comment</comments>
      <pubDate>Sat, 23 Mar 2024 22:16:41 +0900</pubDate>
    </item>
    <item>
      <title>[UE5] Nanite (3/5)</title>
      <link>https://scahp.tistory.com/128</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[UE5] Nanite (3/5)&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size16&quot;&gt;최초 작성 : 2024-03-22&lt;br /&gt;마지막 수정 : 2024-03-22&lt;br /&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;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 style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 환경&lt;/b&gt;&lt;br /&gt;&lt;b&gt;2. 목표&lt;/b&gt;&lt;br /&gt;&lt;b&gt;3. 내용&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; 3.1. AddPass_Rasterize(MainPass) &lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; 3.1.1.&amp;nbsp;FRasterizerPass&amp;nbsp;준비 &lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; 3.1.2.&amp;nbsp;AddPass_Binning(MainPass)&amp;nbsp;전체&amp;nbsp;레이아웃 &lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; 3.1.2.1.&amp;nbsp;RasterBinCount &lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; 3.1.2.2.&amp;nbsp;RasterBinReserve &lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; 3.1.2.3.&amp;nbsp;RasterBinScatter &lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; 3.1.3.&amp;nbsp;HW&amp;nbsp;Rasterize &lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; 3.1.4.&amp;nbsp;SW&amp;nbsp;Rasterize &lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; 3.2. 현재 프레임 기반 HZB 생성 &lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; 3.3.&amp;nbsp;PostPass&amp;nbsp;와&amp;nbsp;MainPass&amp;nbsp;의&amp;nbsp;차이점&amp;nbsp;정리 &lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; 3.3.1. FInstanceCull_CS 차이점&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; 3.3.2. InstanceCull Shader 차이점&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; 3.3.3.&amp;nbsp;&amp;nbsp;FNodeAndClusterCull_CS 차이점&lt;/b&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;b&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; 3.3.4. FNodeAndClusterCull_CS Shader 차이점&lt;/b&gt;&lt;br /&gt;&lt;b&gt;4. 레퍼런스&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 환경&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Unreal Engine 5.3.2 (release branch 072300df18a94f18077ca20a14224b5d99fee872)&lt;br /&gt;개인적으로 분석한 내용이라 틀린 점이 있을 수 있습니다. 그런 부분은 알려주시면 감사하겠습니다.&lt;br /&gt;&lt;br /&gt;이번 글은 한 번에 많은 길이의 코드를 분석하는 부분이 종종 등장합니다. 그래서 글을 2개 띄우고 한쪽은 설명 부분을 한쪽은 코드 이미지를 최대화해서 보는 것을 추천합니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 목표&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Nanite 에 들어간 핵심 기술들을 파악하고 BasePass 렌더링 과정을 코드레벨로 이해해 봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이번 글에서는 컬링된 결과로 얻은 Cluster 를 레스터라이즈 하기 위해서 필요한 RasterBinning 작업과 SW, HW 레스터라이즈 하는 부분은 확인해봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;그런 뒤 레스터라이즈 결과로 생성된 Visibility Buffer 로 부터 현재 프레임 기준 HZB 를 생성합니다. 마지막으로 MainPass 와 거의 동일한 PostPass 를 한번 더 진행합니다.&lt;br /&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;432&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfsSNj/btsF2uzy4TN/FlvwaV4vXApJYsCsv1ORAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfsSNj/btsF2uzy4TN/FlvwaV4vXApJYsCsv1ORAK/img.png&quot; data-alt=&quot;추가그림1. 오늘 알아볼 부분을 렌더독으로 캡쳐 (출처 : 직접 촬영)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfsSNj/btsF2uzy4TN/FlvwaV4vXApJYsCsv1ORAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfsSNj%2FbtsF2uzy4TN%2FFlvwaV4vXApJYsCsv1ORAK%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;432&quot; height=&quot;314&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;추가그림1. 오늘 알아볼 부분을 렌더독으로 캡쳐 (출처 : 직접 촬영)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이전 글에서 이야기 했던 것 처럼 이 글은 총 5개로 구성될 예정입니다.&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;1. Nanite 1/5 : Nanite 에서 사용하는 주요 기술과 MeshDrawCommand 생성 및 VisibilityBuffer 초기화 과정 리뷰&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/127&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;2.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://scahp.tistory.com/127&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;Nanite 2/5 :&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://scahp.tistory.com/127&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&amp;nbsp;TwoPassOcclusionCulling 의 전체 레이아웃을 확인하고 MainPass 의 노드 및 클러스터 컬링 리뷰&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/128&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;&lt;b&gt;3.&amp;nbsp;Nanite 3/5 :&amp;nbsp;&amp;nbsp;SW, HW 레스터라이저와 PostPass 리뷰&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;4.&amp;nbsp;Nanite 4/5 :&amp;nbsp;&amp;nbsp;Visibility Buffer 로 부터 Depth/Stencil 텍스쳐를 생성하는 부분 리뷰&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/130&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;5.&amp;nbsp;Nanite 5/5 :&amp;nbsp;&amp;nbsp;Visibility Buffer 로 부터 G-Buffer 생성하는 MaterialPass 리뷰&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3.&amp;nbsp;내용&lt;/b&gt;&lt;/h3&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.1.&amp;nbsp;AddPass_Rasterize(MainPass)&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;AddPass_Rasterize&amp;nbsp;내부의&amp;nbsp;내용은&amp;nbsp;크게&amp;nbsp;3가지가&amp;nbsp;있습니다.&lt;br /&gt;1.&amp;nbsp;BasePass&amp;nbsp;가&amp;nbsp;소유한&amp;nbsp;모든&amp;nbsp;머터리얼에&amp;nbsp;대해서&amp;nbsp;FRasterizerPass&amp;nbsp;를&amp;nbsp;만듭니다.&lt;br /&gt;2.&amp;nbsp;AddPass_Binning&amp;nbsp;함수를&amp;nbsp;호출합니다.&amp;nbsp;여기서는&amp;nbsp;렌더링하려는&amp;nbsp;클러스터&amp;nbsp;내의&amp;nbsp;삼각형들과&amp;nbsp;머터리얼을&amp;nbsp;연결해줍니다.&lt;br /&gt;-&amp;nbsp;RasterBinMeta&amp;nbsp;와&amp;nbsp;RasterBinData&amp;nbsp;버퍼가&amp;nbsp;채워지는&amp;nbsp;데&amp;nbsp;ClusterIndex&amp;nbsp;가&amp;nbsp;주어지면&amp;nbsp;Cluster&amp;nbsp;와&amp;nbsp;연관된&amp;nbsp;RasterBin&amp;nbsp;정보를&amp;nbsp;얻&lt;br /&gt;을&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;해줍니다.&lt;br /&gt;3.&amp;nbsp;Hardware,&amp;nbsp;Software&amp;nbsp;Rasterize&amp;nbsp;를&amp;nbsp;수행합니다.&lt;br /&gt;-&amp;nbsp;Rasterize&amp;nbsp;를&amp;nbsp;마치면,&amp;nbsp;VisibilityBuffer&amp;nbsp;가&amp;nbsp;채워지며&amp;nbsp;64&amp;nbsp;bit&amp;nbsp;uint&amp;nbsp;에&amp;nbsp;Depth&amp;nbsp;|&amp;nbsp;ClusterIndex&amp;nbsp;|&amp;nbsp;TriangleIndex&amp;nbsp;를&amp;nbsp;저장합니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.1.1.&amp;nbsp;FRasterizerPass&amp;nbsp;준비&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이제 실제 AddPass_Rasterize 코드를 봅시다. 먼저 FRasterizerPass 를 만드는 과정을 봅시다.&lt;br /&gt;1.&amp;nbsp;ClusterOffsetSWHW&amp;nbsp;버퍼를&amp;nbsp;준비합니다.&amp;nbsp;이전&amp;nbsp;과정에서&amp;nbsp;자주&amp;nbsp;본&amp;nbsp;것처럼&amp;nbsp;PostPass&amp;nbsp;경우&amp;nbsp;MainPass&amp;nbsp;가&amp;nbsp;생성한&amp;nbsp;Cluster&amp;nbsp;뒤쪽에&amp;nbsp;PostPass&amp;nbsp;의&amp;nbsp;Cluster&amp;nbsp;가&amp;nbsp;배치되기&amp;nbsp;때문에&amp;nbsp;PostPass&amp;nbsp;의&amp;nbsp;경우만&amp;nbsp;ClusterOffset&amp;nbsp;을&amp;nbsp;사용한다고&amp;nbsp;플래그를&amp;nbsp;설정합니다.&lt;br /&gt;2.&amp;nbsp;BasePass&amp;nbsp;가&amp;nbsp;소유한&amp;nbsp;총&amp;nbsp;RasterBinCount&amp;nbsp;를&amp;nbsp;계산합니다.&amp;nbsp;이것은&amp;nbsp;BasePass&amp;nbsp;에서&amp;nbsp;사용하는&amp;nbsp;Unique&amp;nbsp;Material&amp;nbsp;개수입니다.&lt;br /&gt;3. 우리가 만들 FRasterizerPass 객체 입니다. 어떤 Shader 를 사용할지? 어떤 Material 을 사용하는지? RasterBin 인덱스 등등&amp;hellip; 레스터라이즈 패스에 필요한 모든 것을 담고 있습니다. 이 내용은 캐싱하여 재사용 가능하게 준비합니다.&lt;br /&gt;4.&amp;nbsp;FPassData&amp;nbsp;에&amp;nbsp;레스터라이즈에&amp;nbsp;필요한&amp;nbsp;모든&amp;nbsp;정보를&amp;nbsp;다&amp;nbsp;모읍니다.&lt;br /&gt;-&amp;nbsp;RasterizerPasses&amp;nbsp;는&amp;nbsp;이번&amp;nbsp;레스터라이즈에&amp;nbsp;필요한&amp;nbsp;모든&amp;nbsp;FRasterizerPass&amp;nbsp;를&amp;nbsp;담고&amp;nbsp;있습니다.&lt;br /&gt;- MetaBufferData 에는 레스터라이즈에 필요한 정보가 채워지는데 BinSWCount, BinHWCount 에는 각 머터리얼 당 렌더링해야 할 클러스터가 개수(Vertex Shader 를 사용한 경우). 그리고 레스터라이즈에 필요한 데이터를 인덱싱 할 수 있는 ClusterOffset 정보를 가집니다. 이 ClusterOffset 은 추후 제작할 RasterBinData 에 사용됩니다.&lt;br /&gt;-&amp;nbsp;ActiveRasterBins&amp;nbsp;는&amp;nbsp;활성화&amp;nbsp;된&amp;nbsp;RasterBin(머터리얼)&amp;nbsp;정보를&amp;nbsp;가집니다.&amp;nbsp;(현재는&amp;nbsp;다&amp;nbsp;활성화&amp;nbsp;되었다고&amp;nbsp;가정하고&amp;nbsp;볼&amp;nbsp;예정)&lt;br /&gt;-&amp;nbsp;FixedFunctionPassIndex&amp;nbsp;는&amp;nbsp;RasterizerPasses&amp;nbsp;를&amp;nbsp;인덱싱&amp;nbsp;하기&amp;nbsp;위해서&amp;nbsp;사용합니다.&amp;nbsp;Programmable&amp;nbsp;RasterBin&amp;nbsp;이&amp;nbsp;아닌&amp;nbsp;경우&amp;nbsp;레스터라이즈&amp;nbsp;시&amp;nbsp;WPO&amp;nbsp;등으로&amp;nbsp;픽셀이&amp;nbsp;이동되지&amp;nbsp;않을&amp;nbsp;것입니다.&amp;nbsp;이런&amp;nbsp;경우&amp;nbsp;기본&amp;nbsp;머터리얼로&amp;nbsp;레스터라이즈&amp;nbsp;가능할&amp;nbsp;것입니다.&amp;nbsp;그래서&amp;nbsp;기본&amp;nbsp;머터리얼로&amp;nbsp;된&amp;nbsp;FRasterizerPass&amp;nbsp;하나를&amp;nbsp;만들어두고&amp;nbsp;해당&amp;nbsp;FixedFunctionPassIndex&amp;nbsp;로&amp;nbsp;필요&amp;nbsp;시&amp;nbsp;참조&amp;nbsp;가능하게&amp;nbsp;합니다.&lt;br /&gt;5.&amp;nbsp;3번에서&amp;nbsp;얻은&amp;nbsp;RasterBinCount&amp;nbsp;개수만큼&amp;nbsp;MetaBufferData&amp;nbsp;를&amp;nbsp;할당합니다.&lt;br /&gt;6. 강제로 Programmable 을 끄는 경우는 모두 기본 머터리얼로 레스터라이즈 하면 되기 때문에 RasterBins 가 1개만 있어도 될 것입니다. 그렇지 않은 경우 조건문의 else 로 넘어 갑니다. 여기서는 BasePass 가 가진 모든 RasterPipelines (Unique Material) 수 만큼 루프를 반복하며 실제 활성화된 ActiveRasterBins 를 마킹합니다. 우리는 일단 모두 활성화 되었다고 생각하고 봅니다.&lt;br /&gt;7.&amp;nbsp;ActiveBin&amp;nbsp;의&amp;nbsp;수가&amp;nbsp;8개가&amp;nbsp;넘어가면&amp;nbsp;비동기&amp;nbsp;아니면&amp;nbsp;동기&amp;nbsp;방식으로&amp;nbsp;AddSetupTask&amp;nbsp;에&amp;nbsp;전달된&amp;nbsp;람다&amp;nbsp;함수가&amp;nbsp;실행됩니다.&amp;nbsp;일단&amp;nbsp;동기로&amp;nbsp;돈다고&amp;nbsp;생각하고&amp;nbsp;코드를&amp;nbsp;봐도&amp;nbsp;무방할&amp;nbsp;것&amp;nbsp;같습니다.&lt;br /&gt;8.&amp;nbsp;Hardware&amp;nbsp;rasterze&amp;nbsp;에&amp;nbsp;필요한&amp;nbsp;VS,&amp;nbsp;MS,&amp;nbsp;PS,&amp;nbsp;CS&amp;nbsp;Shader&amp;nbsp;에&amp;nbsp;Permutation&amp;nbsp;정보를&amp;nbsp;기록합니다.&amp;nbsp;크게&amp;nbsp;중요한&amp;nbsp;부분은&amp;nbsp;없어서&amp;nbsp;함수&amp;nbsp;내부는&amp;nbsp;건너뛰겠습니다.&lt;br /&gt;9.&amp;nbsp;기본&amp;nbsp;머터리얼로&amp;nbsp;구성된&amp;nbsp;FixedMaterial&amp;nbsp;을&amp;nbsp;준비합니다.&amp;nbsp;그리고&amp;nbsp;FillFixedMaterialShaders&amp;nbsp;람다&amp;nbsp;함수를&amp;nbsp;정의합니다.&amp;nbsp;이&amp;nbsp;람다함수는&amp;nbsp;FRasterizerPass&amp;nbsp;를&amp;nbsp;FixedMaterial&amp;nbsp;로&amp;nbsp;채워줍니다.&amp;nbsp;람다함수&amp;nbsp;내부&amp;nbsp;구현을&amp;nbsp;계속해서&amp;nbsp;봅시다.&lt;br /&gt;10. 여기서는 코드리뷰의 편의를 위해서 Vertex Shader 로 Rasterize 한다고 가정합니다. FHWRasterizeVS 와 FHWRasterzePS 에 필요한 Permutation 정보를 설정하고 실제 해당 Permutation 에 맞는 Shader 를 생성합니다. 이렇게 FixedMaterial 에 대한 HW 레스터라이즈 Shader 를 준비했습니다.&lt;br /&gt;11.&amp;nbsp;FMicropolyRasterizeCS&amp;nbsp;는&amp;nbsp;SW&amp;nbsp;레스터라이즈를&amp;nbsp;위한&amp;nbsp;Compute&amp;nbsp;Shader&amp;nbsp;입니다.&amp;nbsp;이렇게&amp;nbsp;FixedMaterial&amp;nbsp;에&amp;nbsp;대한&amp;nbsp;SW&amp;nbsp;레스터라이즈&amp;nbsp;Shader&amp;nbsp;를&amp;nbsp;준비했습니다.&lt;br /&gt;12.&amp;nbsp;각각의&amp;nbsp;Shader&amp;nbsp;가&amp;nbsp;사용하는&amp;nbsp;Material&amp;nbsp;정보를&amp;nbsp;FixedMaterial&amp;nbsp;로&amp;nbsp;설정합니다.&lt;br /&gt;13. CacheRasterizerPass 람다 함수도 확인해봅시다. 이 함수는 전달받은 FNaniteRasterEntry 를 기반으로 RasterizerPass 를 채웁니다. 그리고 캐싱하여 재사용 시 빠르게 데이터를 준비할 수 있게 해줍니다. 참고로 FNaniteRasterEntry 는 &lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Nanite (1/5)&lt;/span&gt;&lt;/a&gt;&amp;nbsp;의&amp;nbsp;그림8&amp;nbsp;에서&amp;nbsp;NaniteSceneProxy&amp;nbsp;를&amp;nbsp;FScene&amp;nbsp;에&amp;nbsp;등록할&amp;nbsp;때&amp;nbsp;추가한&amp;nbsp;데이터&amp;nbsp;입니다.&lt;br /&gt;14.&amp;nbsp;5번&amp;nbsp;과정에서&amp;nbsp;할당해둔&amp;nbsp;MetaBufferData의&amp;nbsp;MaterialBitFlags&amp;nbsp;를&amp;nbsp;채우기&amp;nbsp;위해&amp;nbsp;준비합니다.&lt;br /&gt;15. 캐시 된 것이 있다면 RasterMaterialCache 로 부터 정보를 얻고 그렇지 않으면 RasterPipeline 으로 부터 머터리얼을 얻어와서 MaterialBitFlags 를 만들어서 설정합니다. 그리고 이 때 만들어진 내용은 RasterMaterialCache 에 캐싱해 다음에 재사용 하도록 해줍니다. MaterialFlags 는 WPO, PDO, 등등을 사용하는지 여부가 기록 됩니다. 이렇게 생성한 MaterialFlags 를 기반으로 Vertex, Pixel shader 가 Programmable 인지? Tessellation은 사용하는지? 등에 대한 정보를 기록합니다.&lt;br /&gt;16. RasterizeMaterialCache.bFinalize 가 true 이면, 이미 현재 FNaniteRasterEntry 에 대응되는 FRasterizerPass 객체가 캐싱이 완료되었다는 의미입니다. 그 경우 캐시된 데이터를 복사하고 FRasterizerPass 생성 작업을 마칩니다.&lt;br /&gt;17.&amp;nbsp;만약&amp;nbsp;캐싱이&amp;nbsp;되어있지&amp;nbsp;않고,&amp;nbsp;Programmable&amp;nbsp;기능을&amp;nbsp;사용하는&amp;nbsp;머터리얼의&amp;nbsp;경우&amp;nbsp;사용할&amp;nbsp;Vertex,&amp;nbsp;Pixel,&amp;nbsp;Compute&amp;nbsp;를&amp;nbsp;결정하고&amp;nbsp;사용하는&amp;nbsp;Material&amp;nbsp;정보도&amp;nbsp;준비합니다.&amp;nbsp;이&amp;nbsp;정보는&amp;nbsp;모두&amp;nbsp;RasterizerPass&amp;nbsp;에&amp;nbsp;담겨집니다.&lt;br /&gt;18.&amp;nbsp;만약&amp;nbsp;캐싱이&amp;nbsp;되어있지&amp;nbsp;않고,&amp;nbsp;Programmable&amp;nbsp;기능을&amp;nbsp;사용하지&amp;nbsp;않는&amp;nbsp;머터리얼의&amp;nbsp;경우라면,&amp;nbsp;FixedMaterial&amp;nbsp;타입입니다.&amp;nbsp;그래서&amp;nbsp;9번&amp;nbsp;과정에서&amp;nbsp;봤던&amp;nbsp;람다함수를&amp;nbsp;호출하여&amp;nbsp;FixedMaterial&amp;nbsp;타입의&amp;nbsp;FRasterizerPass&amp;nbsp;를&amp;nbsp;준비합니다.&lt;br /&gt;19.&amp;nbsp;방금&amp;nbsp;전까지는&amp;nbsp;FRasterizerPass&amp;nbsp;를&amp;nbsp;생성할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;람다함수를&amp;nbsp;봤습니다.&amp;nbsp;이제&amp;nbsp;해당&amp;nbsp;함수를&amp;nbsp;실제&amp;nbsp;사용합니다.&amp;nbsp;먼저&amp;nbsp;NANITE_RENDER_FLAG_DISABLE_PROGRAMMABLE&amp;nbsp;인&amp;nbsp;경우는&amp;nbsp;모든&amp;nbsp;클러스터가&amp;nbsp;FixedMaterial&amp;nbsp;타입을&amp;nbsp;사용할&amp;nbsp;것입니다.&amp;nbsp;GetFixedFunctionPipeline&amp;nbsp;은&amp;nbsp;기본&amp;nbsp;머터리얼을&amp;nbsp;사용한&amp;nbsp;경우&amp;nbsp;필요한&amp;nbsp;FNaniteRasterPipeline&amp;nbsp;리턴합니다.&amp;nbsp;최종적으로&amp;nbsp;CacheRasterizerPass&amp;nbsp;함수를&amp;nbsp;호출하여&amp;nbsp;FRasterizerPass&amp;nbsp;를&amp;nbsp;채웁니다.&lt;br /&gt;20.&amp;nbsp;이&amp;nbsp;경우&amp;nbsp;각각의&amp;nbsp;BasePass&amp;nbsp;가&amp;nbsp;가진&amp;nbsp;RasterPipelines(유니크&amp;nbsp;머터리얼&amp;nbsp;개수&amp;nbsp;만큼&amp;nbsp;있음)&amp;nbsp;만큼&amp;nbsp;FRasterizerPass&amp;nbsp;를&amp;nbsp;생성합니다.&amp;nbsp;그리고&amp;nbsp;RasterizerPasses&amp;nbsp;컨테이너를&amp;nbsp;유니크&amp;nbsp;머터리얼&amp;nbsp;개수로&amp;nbsp;예약합니다.&lt;br /&gt;21.&amp;nbsp;RasterPipelines&amp;nbsp;개수&amp;nbsp;만큼&amp;nbsp;루프를&amp;nbsp;돕니다.&amp;nbsp;ActiveRasterBins&amp;nbsp;은&amp;nbsp;현재는&amp;nbsp;모두&amp;nbsp;Active&amp;nbsp;상태라고&amp;nbsp;가정하고&amp;nbsp;보기&amp;nbsp;때문에&amp;nbsp;항상&amp;nbsp;통과합니다.&amp;nbsp;RasterBInIndex++&amp;nbsp;은&amp;nbsp;여기서&amp;nbsp;즉시&amp;nbsp;실행되지&amp;nbsp;않고&amp;nbsp;for&amp;nbsp;loop&amp;nbsp;문의&amp;nbsp;Scope&amp;nbsp;를&amp;nbsp;벗어나는&amp;nbsp;경우&amp;nbsp;증가합니다.&amp;nbsp;(즉,&amp;nbsp;기존&amp;nbsp;index&amp;nbsp;기반&amp;nbsp;for&amp;nbsp;와&amp;nbsp;같은&amp;nbsp;형태로&amp;nbsp;작동)&lt;br /&gt;22. 이제 FNaniteRasterEntry 로 부터 FRasterizerPass 를 채워봅니다. 먼저 RasterizerPass 을 하나 준비하고, RasterBin 값을 채웁니다. 이 값은 GPU 에서 사용하는 MaterialId 입니다. 특이한 점은&amp;nbsp;&amp;nbsp;BinIndexTranslator.Translate 함수를 통해서 BinIndex 를 변환시키는데요. Programmable 타입의 경우 BinIndex 를 뒤에서 부터 증가하도록 합니다. 해당 코드는 &lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Nanite(1/5)&lt;/span&gt;&lt;/a&gt;&amp;nbsp;의 그림8 부분에 나와있습니다. 현재는 그냥 GPU 에서 매칭할 수 있는 MaterialId 를 RasterBin 에 넣었다. 라고만 생각해도 충분할 것 같습니다. 계속해서 Material 들은 Fixed 타입으로 기본값을 채워주고, 캐싱 된 데이터를 검색하기 위해서 RasterMaterialCacheKey 를 생성합니다.&lt;br /&gt;23.&amp;nbsp;방금&amp;nbsp;전&amp;nbsp;생성한&amp;nbsp;RasterMaterialCacheKey&amp;nbsp;를&amp;nbsp;사용하여&amp;nbsp;캐싱&amp;nbsp;데이터를&amp;nbsp;얻어옵니다.&amp;nbsp;만약&amp;nbsp;캐싱한&amp;nbsp;적이&amp;nbsp;없다면,&amp;nbsp;이번에&amp;nbsp;값을&amp;nbsp;채울&amp;nbsp;것이고&amp;nbsp;있다면&amp;nbsp;이&amp;nbsp;것을&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있을&amp;nbsp;것입니다.&amp;nbsp;이런&amp;nbsp;작업은&amp;nbsp;CacheRasterizerPass&amp;nbsp;함수를&amp;nbsp;호출하여&amp;nbsp;FRasterizerPass&amp;nbsp;를&amp;nbsp;채우면서&amp;nbsp;동시에&amp;nbsp;수행합니다.&lt;br /&gt;24. 각각의 RasterBin(머터리얼)은 Indirect draw argument 를 별도로 가집니다. 왜냐하면 각 머터리얼이 렌더링 해야할 클러스터의 개수 정보가 다를 것이기 때문입니다. 현재 RasterBin 이 사용한 Indirect draw argument 를 버퍼의 Byte offset 을 얻어옵니다. 그리고 만약 현재 추가한 Material 이 Fixed 타입이고, 처음 추가한 Fixed 타입이라면 해당 FRasterizerPass 의 인덱스를 FixedFunctionPassIndex 에 캐싱합니다. Hidden Material 을 사용하는 경우도 해당 플래그를 남겨줍니다.&lt;br /&gt;25.&amp;nbsp;준비한&amp;nbsp;FRasterizerPass&amp;nbsp;의&amp;nbsp;루프를&amp;nbsp;돕니다.&amp;nbsp;여기서는&amp;nbsp;실제&amp;nbsp;사용할&amp;nbsp;Shader&amp;nbsp;와&amp;nbsp;Material&amp;nbsp;이&amp;nbsp;빈&amp;nbsp;경우에&amp;nbsp;대한&amp;nbsp;추가&amp;nbsp;처리를&amp;nbsp;수행합니다.&amp;nbsp;그리고&amp;nbsp;처음&amp;nbsp;캐싱하는&amp;nbsp;중이라면&amp;nbsp;캐싱을&amp;nbsp;완료해줍니다.&lt;br /&gt;26. 현재는 레스터라이즈에 Vertex Shader 를 사용한다고 가정하고 보기 때문에 이쪽 조건문만 보겠습니다. Shader 나 Material 이 비어있는 경우 적절한 데이터로 채워지는 것을 볼 수 있습니다.&lt;br /&gt;27.&amp;nbsp;캐싱&amp;nbsp;완료한&amp;nbsp;적이&amp;nbsp;없다면,&amp;nbsp;캐싱&amp;nbsp;데이터를&amp;nbsp;복사하고&amp;nbsp;bFinalized&amp;nbsp;를&amp;nbsp;true&amp;nbsp;로&amp;nbsp;설정해서&amp;nbsp;이후에는&amp;nbsp;캐싱된&amp;nbsp;데이터를&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;7183&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sJDXp/btsF0miotYS/a3d0LgYcKhMsAagKDyqjak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sJDXp/btsF0miotYS/a3d0LgYcKhMsAagKDyqjak/img.png&quot; data-alt=&quot;그림1. AddPass_Rasterize 에서 FRasterizerPass 를 준비하는 코&amp;amp;amp;amp;amp;amp;amp;nbsp; (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sJDXp/btsF0miotYS/a3d0LgYcKhMsAagKDyqjak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsJDXp%2FbtsF0miotYS%2Fa3d0LgYcKhMsAagKDyqjak%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;1152&quot; height=&quot;7183&quot; data-origin-width=&quot;1152&quot; data-origin-height=&quot;7183&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. AddPass_Rasterize 에서 FRasterizerPass 를 준비하는 코&amp;amp;amp;amp;amp;amp;nbsp; (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&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;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.1.2.&amp;nbsp;AddPass_Binning(MainPass)&amp;nbsp;전체&amp;nbsp;레이아웃&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;다음으로&amp;nbsp;AddPass_Binning&amp;nbsp;함수를&amp;nbsp;호출합니다.&amp;nbsp;이&amp;nbsp;함수를&amp;nbsp;통해서&amp;nbsp;얻는&amp;nbsp;것은&amp;nbsp;아래와&amp;nbsp;같습니다.&lt;br /&gt;-&amp;nbsp;RasterMetaData&amp;nbsp;와&amp;nbsp;RasterBinData&amp;nbsp;에&amp;nbsp;데이터를&amp;nbsp;채움&lt;br /&gt;&amp;nbsp;-&amp;gt;&amp;nbsp;RasterBinData&amp;nbsp;:&amp;nbsp;RasterBin(MaterialId)&amp;nbsp;가&amp;nbsp;레스터라이즈를&amp;nbsp;수행하는&amp;nbsp;클러스터&amp;nbsp;내의&amp;nbsp;삼각형&amp;nbsp;범위.&amp;nbsp;(클러스터내에서&amp;nbsp;다양한&amp;nbsp;머터리얼을&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있기&amp;nbsp;때문에&amp;nbsp;삼각형&amp;nbsp;범위가&amp;nbsp;중요함)&lt;br /&gt;&amp;nbsp;-&amp;gt;&amp;nbsp;RasterMetaData&amp;nbsp;:&amp;nbsp;RasterBin(MaterialId)&amp;nbsp;가&amp;nbsp;그려야&amp;nbsp;할&amp;nbsp;SW/HW&amp;nbsp;Cluster&amp;nbsp;Count&amp;nbsp;(VertexShader&amp;nbsp;의&amp;nbsp;경우)&amp;nbsp;그리고&amp;nbsp;RasterBin&amp;nbsp;과&amp;nbsp;연관된&amp;nbsp;RasterBinData&amp;nbsp;를&amp;nbsp;찾기&amp;nbsp;위한&amp;nbsp;위한&amp;nbsp;Offset&amp;nbsp;정보&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;AddPass_Binning&amp;nbsp;은&amp;nbsp;RasterBinCount,&amp;nbsp;RasterBinReserve,&amp;nbsp;RasterBinScatter&amp;nbsp;총&amp;nbsp;3개의&amp;nbsp;패스로&amp;nbsp;구성됩니다.&amp;nbsp;먼저&amp;nbsp;C++&amp;nbsp;코드&amp;nbsp;쪽의&amp;nbsp;전체&amp;nbsp;레이아웃을&amp;nbsp;보고&amp;nbsp;각각의&amp;nbsp;패스의&amp;nbsp;Shader&amp;nbsp;코드를&amp;nbsp;확인해봅시다.&lt;br /&gt;1. FBinningData 에 이 함수의 실행 결과로 생성되는 모든 데이터가 들어갑니다. BinCount 를 BasePass 가 소유한 Unique Nanite Material 수로 설정하고 FixedFunctionBin 을 설정합니다. 이것은 그림1 의 24 에서 구한 PassData.RasterizerPasses[PassData.FixedFunctionPassIndex].RasterBin 에 해당합니다. 사용하는 Nanite material 이 있으므로 BinCount &amp;gt; 0 이기 때문에 조건문 내로 진입합니다.&lt;br /&gt;2.&amp;nbsp;RasterMetaData&amp;nbsp;를&amp;nbsp;위한&amp;nbsp;버퍼를&amp;nbsp;생성합니다.&amp;nbsp;그리고&amp;nbsp;Raster&amp;nbsp;과정을&amp;nbsp;위한&amp;nbsp;Indirect&amp;nbsp;draw&amp;nbsp;argument&amp;nbsp;버퍼&amp;nbsp;또한&amp;nbsp;생성합니다.&lt;br /&gt;3.&amp;nbsp;RasterBinData&amp;nbsp;버퍼도&amp;nbsp;생성합니다.&lt;br /&gt;4.&amp;nbsp;RasterBinBuild&amp;nbsp;패스에&amp;nbsp;필요한&amp;nbsp;버퍼를&amp;nbsp;바인딩&amp;nbsp;합니다.&amp;nbsp;컬링에서&amp;nbsp;살아남은&amp;nbsp;VisibleClustersSWHW&amp;nbsp;와&amp;nbsp;실제&amp;nbsp;FCluster&amp;nbsp;가&amp;nbsp;가진&amp;nbsp;삼각형&amp;nbsp;등의&amp;nbsp;Raw&amp;nbsp;정보를&amp;nbsp;담고&amp;nbsp;있는&amp;nbsp;ClusterPage&amp;nbsp;정보,&amp;nbsp;NaniteSceneProxy&amp;nbsp;생성&amp;nbsp;시&amp;nbsp;준비해둔&amp;nbsp;MaterialSlot&amp;nbsp;정보(&lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Nanite(1/5)&lt;/span&gt;&lt;/a&gt;&amp;nbsp;의 추가그림1 참고), ClusterCount 와 Offset 정보, IndirectArgs 에는 ClusterClassifyArgs 를 바인딩 하는데 ClusterClassifyArgs[0] 에는 SW와 HW 클러스터 수의 합을 64 개의 묶음으로 만드는 경우 몇 개의 묶음인지? 가 기록되어있습니다. 이 IndirectArgs 버퍼는 RasterBinCount Compute shader 를 Dispatch 할 때 사용하는 WorkGroup 개수로 사용됩니다. 그리고 나머지 필요한 정보들을 설정합니다.&lt;br /&gt;5. 필요한 Shader Permutation 정보를 설정하고, RasterBinBuild shader 를 시작합니다. ClusterClassifyArgs 를 사용하여 WorkGroup 을 설정해주며, Dispatch 정보는 (SW cluster count + HW cluster count) 를 64 개로 배치로 한 경우 총 배치&amp;nbsp;&amp;nbsp;개수가 들어갑니다. 아마 내부에서 64 개의 thread 가 각각의 clsuter 를 처리하나 봅니다. 자세한 내용은 그림3 에서 봅시다.&lt;br /&gt;6. RasterBinReserve 패스를 수행합니다. 패스 수행 전에 RangeAllocatorBuffer 를 만듭니다. 이 버퍼는 uint32 타입이며 RasterBin 이 RasterBinData 의 어느 부분을 사용할 수 있을지 Memroy Offset 정보를 계산하기 위해서 사용됩니다. 이 Offset 은 RasterMetaData 에 ClusterOffset 에 저장됩니다. 이번 패스 또한 Compute Shader 이고 Dispatch 되는 WorkGroup 은 BinCount(Nanite unique material 수) 를 64 개로 묶은 경우 배치 개수 입니다. 자세한 내용은 그림5 에서 봅시다.&lt;br /&gt;7. RasterBinScatterPass 입니다. 이전 과정에서 RasterBinData 정보를 채울 범위를 할당 받았으므로 최종적으로 해당 내용을 기록하는 패스입니다. RasterBinCount 와 같이 ClusterClassifyArgs 를 사용하여 WorkGroup 을 설정해주며, Dispatch 정보는 (SW cluster count + HW cluster count) 를 64 개로 배치로 한 경우 총 배치&amp;nbsp;&amp;nbsp;개수가 들어갑니다. 자세한 내용은 그림6 에서 확인해봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1055&quot; data-origin-height=&quot;2394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pEbQh/btsF17jTu3z/JhjmkkSwbk4jOfBPEfp7bK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pEbQh/btsF17jTu3z/JhjmkkSwbk4jOfBPEfp7bK/img.png&quot; data-alt=&quot;그림2. AddPass_Binning(MainPass) 전체 레이아웃 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pEbQh/btsF17jTu3z/JhjmkkSwbk4jOfBPEfp7bK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpEbQh%2FbtsF17jTu3z%2FJhjmkkSwbk4jOfBPEfp7bK%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;1055&quot; height=&quot;2394&quot; data-origin-width=&quot;1055&quot; data-origin-height=&quot;2394&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림2. AddPass_Binning(MainPass) 전체 레이아웃 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;RasterBinBuild 쉐이더 코드를 확인해봅시다.&lt;br /&gt;1. RasterBinBuild 함수 내로 진입합니다. 각 WorkGroup 은 64 개를 스레드를 사용합니다. 그렇게 때문에 SV_DispatchThreadID 는 ClusterIndex 가 됩니다.&lt;br /&gt;2. SW, HW 클러스터 수와 현재 처리하는 클러스터가 SW 방식으로 처리될지 여부를 기록합니다. ClusterCount 는 SW+HW 클러스터 수의 합니다.&lt;br /&gt;3.&amp;nbsp;현재&amp;nbsp;thread&amp;nbsp;가&amp;nbsp;처리중인&amp;nbsp;Cluster&amp;nbsp;가&amp;nbsp;최대&amp;nbsp;Cluster&amp;nbsp;범위&amp;nbsp;내인지&amp;nbsp;확인하고&amp;nbsp;조건문&amp;nbsp;내부로&amp;nbsp;진입합니다.&amp;nbsp;HW&amp;nbsp;의&amp;nbsp;경우에도&amp;nbsp;0&amp;nbsp;에서&amp;nbsp;부터&amp;nbsp;인덱스를&amp;nbsp;가질&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;준비합니다.&lt;br /&gt;4.&amp;nbsp;PrevData&amp;nbsp;내용은&amp;nbsp;현재&amp;nbsp;확인하지&amp;nbsp;않고&amp;nbsp;있기&amp;nbsp;때문에&amp;nbsp;무시합니다.&lt;br /&gt;5.&amp;nbsp;PostPass&amp;nbsp;의&amp;nbsp;경우&amp;nbsp;기존&amp;nbsp;MainCluster&amp;nbsp;의&amp;nbsp;Cluster&amp;nbsp;뒤에&amp;nbsp;기록되기&amp;nbsp;때문에&amp;nbsp;그만큼&amp;nbsp;Offset&amp;nbsp;을&amp;nbsp;더합니다.&lt;br /&gt;6.&amp;nbsp;VisibleClusterIndex&amp;nbsp;를&amp;nbsp;만드는데,&amp;nbsp;우리는&amp;nbsp;PrevData&amp;nbsp;가&amp;nbsp;없다고&amp;nbsp;가정하기&amp;nbsp;때문에&amp;nbsp;기존&amp;nbsp;그대로&amp;nbsp;RelativeClusterIndex&amp;nbsp;가&amp;nbsp;됩니다.&amp;nbsp;그리고&amp;nbsp;HW&amp;nbsp;의&amp;nbsp;경우&amp;nbsp;인덱스가&amp;nbsp;뒤에서&amp;nbsp;부터&amp;nbsp;자라나기&amp;nbsp;때문에&amp;nbsp;그것을&amp;nbsp;고려하여&amp;nbsp;최종&amp;nbsp;VisibleClusterIndex&amp;nbsp;를&amp;nbsp;구해냅니다.&amp;nbsp;이&amp;nbsp;것을&amp;nbsp;사용하여&amp;nbsp;FVisibleCluster,&amp;nbsp;FInstanceSceneData,&amp;nbsp;FCluster&amp;nbsp;를&amp;nbsp;얻어냅니다.&amp;nbsp;FCluster&amp;nbsp;는&amp;nbsp;실제&amp;nbsp;Cluster&amp;nbsp;가&amp;nbsp;사용하는&amp;nbsp;머터리얼&amp;nbsp;정보와&amp;nbsp;삼각형&amp;nbsp;정보들이&amp;nbsp;포함되어&amp;nbsp;있습니다.&amp;nbsp;WPO&amp;nbsp;가&amp;nbsp;있지만&amp;nbsp;비활성&amp;nbsp;된&amp;nbsp;경우는&amp;nbsp;bSecondaryBin&amp;nbsp;을&amp;nbsp;사용하기&amp;nbsp;때문에&amp;nbsp;해당&amp;nbsp;플래그도&amp;nbsp;준비합니다(해당&amp;nbsp;내용은&amp;nbsp;&lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Nanite(1/5)&lt;/span&gt;&lt;/a&gt;&amp;nbsp;6번에&amp;nbsp;있음).&lt;br /&gt;7.&amp;nbsp;FCluster&amp;nbsp;가&amp;nbsp;사용하는&amp;nbsp;머터리얼이&amp;nbsp;3개&amp;nbsp;이하면&amp;nbsp;FastPath&amp;nbsp;를&amp;nbsp;통해&amp;nbsp;바로&amp;nbsp;FCluster&amp;nbsp;의&amp;nbsp;데이터로&amp;nbsp;부터&amp;nbsp;정보를&amp;nbsp;얻습니다.&lt;br /&gt;7.1. FCluster 는 내부적으로 최대 버택스 수 256, 최대 삼각형 수 128 개를 가집니다. 그리고 Vertices 나 삼각형의 수가 32개 넘어갈 때마다 Batch 하나를 생성합니다. 그림4 에 관련 코드를 모아뒀으니 참고해주세요. GetMaterialRasterBinFromIndex 는 FCluster 의 MaterialIndex 를 사용하여 MaterialSlot&amp;nbsp;으로&amp;nbsp;부터&amp;nbsp;RasterBin&amp;nbsp;의&amp;nbsp;인덱스를&amp;nbsp;얻습니다.&lt;br /&gt;7.2. 만약 Cluster 내에 생성된 삼각현 배치 중 동일한 머터리얼을 사용한다면 해당 배치를 합치는 것이 좋을 것입니다. 그 부분을 수행합니다.&lt;br /&gt;7.3.&amp;nbsp;ExportRasterBin&amp;nbsp;을&amp;nbsp;호출하여&amp;nbsp;RasterBin&amp;nbsp;과&amp;nbsp;클러스터내에서&amp;nbsp;RasterBin&amp;nbsp;을&amp;nbsp;사용하는&amp;nbsp;삼각형의&amp;nbsp;범위를&amp;nbsp;저장합니다.&lt;br /&gt;7.4.&amp;nbsp;ExportRasterBin&amp;nbsp;의&amp;nbsp;내부를&amp;nbsp;확인해봅시다.&lt;br /&gt;7.5. RasterBin 의 BinMaterialFlag 를 얻어옵니다. bUseBatch 정보를 설정하는데, HW 방식이고, Mesh나 Primitive Shader 를 사용하며, VertexProgrammable 방식이라면 bUseBatch 가 활성화됩니다. 현재는 VertexShader 쪽으로 분석중이기 때문에 이 값은 항상 0입니다. Tessellation 기능이 켜져서 bDisplacement 가 true 인 경우는 레스터라이즈를 항상 Software 방식으로 동작하도록 하며 bUseBatch 도 사용하도록 설정합니다.&lt;br /&gt;7.6.&amp;nbsp;bUseBatch&amp;nbsp;를&amp;nbsp;사용하지&amp;nbsp;않기&amp;nbsp;때문에&amp;nbsp;BatchCount&amp;nbsp;는&amp;nbsp;1입니다.&lt;br /&gt;7.7.&amp;nbsp;현재&amp;nbsp;RasterBinBuild&amp;nbsp;과정이기&amp;nbsp;때문에,&amp;nbsp;#if&amp;nbsp;RASTER_BIN_COUNT&amp;nbsp;조건&amp;nbsp;내로&amp;nbsp;들어가&amp;nbsp;IncrementRasterBinCount&amp;nbsp;를&amp;nbsp;실행합니다.&lt;br /&gt;7.8. IncrementRasterBinCount 내에서는 RasterBinMeta[RasterBin] 의 BinSWCount, BinHWCount 를 BatchCount 개수만큼 증가시켜줍니다. 이 때 bUseBatch 가 false 이므로 BatchCount 는 1 이며 클러스터 당 1 개의 배치로 해당 RasterBin 을 모두 렌더링하는 것을 알 수 있습니다.&lt;br /&gt;8. 이쪽 조건문으로 들어온 경우 머터리얼의 수가 3개 이상이 된 경우입니다. 이 경우 FCluster 에서 데이터를 바로 로드할 수 없고 Cluster 의 Page 로 부터 데이터를 로드합니다. 그리고 머터리얼 테이블 개수만큼 루프를 돕니다.&lt;br /&gt;9.&amp;nbsp;GetMaterialRasterBinFromIndex&amp;nbsp;로&amp;nbsp;RasterBin&amp;nbsp;정보를&amp;nbsp;얻은&amp;nbsp;후&amp;nbsp;Raster&amp;nbsp;정보를&amp;nbsp;머지&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있다면&amp;nbsp;하고&amp;nbsp;그렇지&amp;nbsp;않다면&amp;nbsp;ExportRasterBin&amp;nbsp;을&amp;nbsp;호출하여&amp;nbsp;7.4&amp;nbsp;에서&amp;nbsp;한&amp;nbsp;것과&amp;nbsp;같은&amp;nbsp;방식으로&amp;nbsp;IncrementRasterBinCount&amp;nbsp;를&amp;nbsp;호출합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;4637&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cp54q8/btsF13hAq2y/9DPuRI4pgQSX5RkkG1kfR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cp54q8/btsF13hAq2y/9DPuRI4pgQSX5RkkG1kfR1/img.png&quot; data-alt=&quot;그림3. RasterBinBuild 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cp54q8/btsF13hAq2y/9DPuRI4pgQSX5RkkG1kfR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcp54q8%2FbtsF13hAq2y%2F9DPuRI4pgQSX5RkkG1kfR1%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;995&quot; height=&quot;4637&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;4637&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림3. RasterBinBuild 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&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;847&quot; data-origin-height=&quot;1013&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dI29tE/btsF1b8v8H2/4LJ0i10aZ9GYo8R03AwjYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dI29tE/btsF1b8v8H2/4LJ0i10aZ9GYo8R03AwjYK/img.png&quot; data-alt=&quot;그림4. BuildVertReuseBatches, Cluster 당 최대 삼각형은 128, 버택스는 256, 삼각형 배치는 버택스 or 삼각형이 32개를 넘으면 분리됨. (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dI29tE/btsF1b8v8H2/4LJ0i10aZ9GYo8R03AwjYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdI29tE%2FbtsF1b8v8H2%2F4LJ0i10aZ9GYo8R03AwjYK%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;847&quot; height=&quot;1013&quot; data-origin-width=&quot;847&quot; data-origin-height=&quot;1013&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림4. BuildVertReuseBatches, Cluster 당 최대 삼각형은 128, 버택스는 256, 삼각형 배치는 버택스 or 삼각형이 32개를 넘으면 분리됨. (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.1.2.2.&amp;nbsp;RasterBinReserve&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;RasterBinBuild 과정을 마치면, RasterBinMeta 에는 SW / HW Cluster 의 개수(Vertex Shader 사용 경우), 또는 Vertex Batch 의 개수가 기록되어 있을 것입니다. 여기서는 Vertex Shader 를 보고 있다고 가정하고 코드를 계속 보겠습니다.&lt;br /&gt;1.&amp;nbsp;RasterBinReserve&amp;nbsp;함수&amp;nbsp;내부로&amp;nbsp;진입합니다.&amp;nbsp;각각의&amp;nbsp;WorkGroup&amp;nbsp;은&amp;nbsp;RasterBin&amp;nbsp;개수를&amp;nbsp;64&amp;nbsp;개로&amp;nbsp;묶은&amp;nbsp;경우&amp;nbsp;묶음의&amp;nbsp;수고,&amp;nbsp;WorkGroup&amp;nbsp;내의&amp;nbsp;thread&amp;nbsp;는&amp;nbsp;64&amp;nbsp;개이기&amp;nbsp;때문에&amp;nbsp;SV_DispatchThreadID&amp;nbsp;는&amp;nbsp;RasterBinIndex&amp;nbsp;입니다.&lt;br /&gt;2.&amp;nbsp;RasterBinIndex&amp;nbsp;가&amp;nbsp;최대&amp;nbsp;범위를&amp;nbsp;넘어가는지&amp;nbsp;여부를&amp;nbsp;확인하고&amp;nbsp;내부&amp;nbsp;조건문으로&amp;nbsp;진입합니다.&lt;br /&gt;3. RasterBinIndex 를 GetRasterBinCapacity 에 전달하여 바로 전 과정에서 계산한 SW, HW 의 Cluster 개수의 합을 얻습니다.&lt;br /&gt;3.1. GetRasterBinCapacity 내부 코드를 봅시다. 내부에서는 GetRasterBinCount 를 호출하여 해당 RasterBinMeta[BinIndex] 로 부터 BinSWCount + BinHWCount 를 리턴합니다.&lt;br /&gt;4. 두번째로 AllocateRasterBinRange 함수를 호출합니다. 이 때 SW, HW 클러스터 총합(Vertex shader 의 경우) 도 같이 전달 합니다. 이것으로 얻게 되는 것은 Offset 입니다. 이 Offset 은 RasterBinData 에서 어느 위치에 현재 RasterBin 이 렌더링 할 Cluster 와 Cluster 의 삼각형 범위 정보가 있는지 여부입니다.&lt;br /&gt;4.1.&amp;nbsp;AllocateRasterBinRange&amp;nbsp;함수에&amp;nbsp;진입합니다.&lt;br /&gt;4.2.&amp;nbsp;RangeAllocator[0]&amp;nbsp;은&amp;nbsp;uint32&amp;nbsp;변수이며,&amp;nbsp;현재&amp;nbsp;RasterBin&amp;nbsp;이&amp;nbsp;렌더링&amp;nbsp;할&amp;nbsp;데이터를&amp;nbsp;저장할&amp;nbsp;메모리&amp;nbsp;위치를&amp;nbsp;계산하는데&amp;nbsp;사용됩니다.&lt;br /&gt;5. 위의 과정에서 구해진 RasterBinOffset 을 SetRasterBinOffset 을 사해 RasterBinMeta[RasterBinIndex].ClusterOffset 에 저장합니다.&lt;br /&gt;5.1. 위의 내용을 SetRasterBinOffset 의 내부를 통해 확인할 수 있습니다.&lt;br /&gt;6. 마지막으로 WriteRasterizerArgsSWHW 를 사용하여 RasterBinArgsSWHW 의 현재 RasterBin 에 해당하는 Indirect draw argument 버퍼를 초기화 합니다.&lt;br /&gt;6.1.&amp;nbsp;WriteRasterizerArgsSWHW&amp;nbsp;함수&amp;nbsp;내부도&amp;nbsp;한번&amp;nbsp;확인해봅시다.&amp;nbsp;Indirect&amp;nbsp;draw&amp;nbsp;argument&amp;nbsp;는&amp;nbsp;총&amp;nbsp;8개의&amp;nbsp;uint&amp;nbsp;로&amp;nbsp;구성됩니다.&amp;nbsp;그&amp;nbsp;중&amp;nbsp;앞의&amp;nbsp;4개에는&amp;nbsp;SW&amp;nbsp;렌더링을&amp;nbsp;위한&amp;nbsp;argument&amp;nbsp;가&amp;nbsp;기록됩니다.&lt;br /&gt;6.2. 앞의 4개에 [0] 인덱스에는 NumClustersSW 를 기록합니다. ThreadGroupCountX 라고 된 것을 보면 SW 는 Compute Shader 로 Rasterize 될 것임을 알 수 있습니다.&lt;br /&gt;6.3.&amp;nbsp;HW&amp;nbsp;이고&amp;nbsp;Vertex&amp;nbsp;Shader&amp;nbsp;를&amp;nbsp;사용하는&amp;nbsp;경우&amp;nbsp;[4]&amp;nbsp;인덱스에는&amp;nbsp;인스턴스&amp;nbsp;당&amp;nbsp;사용하는&amp;nbsp;버택스&amp;nbsp;개수,&amp;nbsp;[5]&amp;nbsp;인덱스에는&amp;nbsp;인스턴스&amp;nbsp;개수,&amp;nbsp;[6]&amp;nbsp;인덱스&amp;nbsp;에는&amp;nbsp;시작&amp;nbsp;버택스&amp;nbsp;위치인데&amp;nbsp;항상&amp;nbsp;처음부터&amp;nbsp;시작하도록&amp;nbsp;0&amp;nbsp;입니다.&amp;nbsp;arguemnt&amp;nbsp;를&amp;nbsp;보면&amp;nbsp;HW&amp;nbsp;의&amp;nbsp;경우는&amp;nbsp;일반&amp;nbsp;레스터라이즈&amp;nbsp;파이프라인으로&amp;nbsp;실행되는&amp;nbsp;것임을&amp;nbsp;알&amp;nbsp;수&amp;nbsp;있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;1351&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ONl5f/btsF0i1kOg7/fKXzvtxpZBO0z4grOOh8c0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ONl5f/btsF0i1kOg7/fKXzvtxpZBO0z4grOOh8c0/img.png&quot; data-alt=&quot;그림5. RasterBinReserve 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ONl5f/btsF0i1kOg7/fKXzvtxpZBO0z4grOOh8c0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FONl5f%2FbtsF0i1kOg7%2FfKXzvtxpZBO0z4grOOh8c0%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;978&quot; height=&quot;1351&quot; data-origin-width=&quot;978&quot; data-origin-height=&quot;1351&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림5. RasterBinReserve 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.1.2.3.&amp;nbsp;RasterBinScatter&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;RasterBinReserve&amp;nbsp;까지&amp;nbsp;수행하고&amp;nbsp;나면&amp;nbsp;RasterBinData&amp;nbsp;를&amp;nbsp;채울&amp;nbsp;준비를&amp;nbsp;마치게&amp;nbsp;됩니다.&amp;nbsp;이제&amp;nbsp;RasterBinScatter&amp;nbsp;를&amp;nbsp;사용하여&amp;nbsp;RasterBinData&amp;nbsp;를&amp;nbsp;채워봅시다.&amp;nbsp;이&amp;nbsp;과정에서는&amp;nbsp;RasterBin&amp;nbsp;을&amp;nbsp;사용하는&amp;nbsp;Cluster&amp;nbsp;와&amp;nbsp;해당&amp;nbsp;클러스터&amp;nbsp;내에서도&amp;nbsp;어떤&amp;nbsp;삼각형만&amp;nbsp;레스터라이즈&amp;nbsp;하는지에&amp;nbsp;대한&amp;nbsp;정보를&amp;nbsp;기록합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;RasterBinBuild 패스와 동일하게 RasterBinBuild 함수를 수행합니다. 이 때 다른 점은 ExportRasterBin 을 호출한 경우 #elif RASTER_BIN_SCATTER 조건문 내로 진입한다는 것입니다. 그림3 의 RasterBinBuild 내용에서 RASTER_BIN_SCATTER 나오기 전까지 봐주세요. 그리고 아래는 RASTER_BIN_SCATTER&amp;nbsp;&amp;nbsp;부분만 확인해봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1.&amp;nbsp;AllocateRasterBinCluster&amp;nbsp;는&amp;nbsp;실제&amp;nbsp;RasterBinReserve&amp;nbsp;과정에서&amp;nbsp;초기화&amp;nbsp;해둔&amp;nbsp;RasterBinArgsSWHW&amp;nbsp;의&amp;nbsp;정보를&amp;nbsp;채웁니다.&amp;nbsp;그리고&amp;nbsp;RasterBinData&amp;nbsp;에&amp;nbsp;기록할&amp;nbsp;Offset&amp;nbsp;정보를&amp;nbsp;계산합니다.&amp;nbsp;내부로&amp;nbsp;들어가봅시다.&lt;br /&gt;1.1. 우리는 bUseBatch 가 1이기 때문에 전달 받은 BatchCount 는 1입니다. 먼저 Offset 에 해당 RasterBin 이 사용할 Indirect draw argument 버퍼의 Offset 을 구합니다.&amp;nbsp;&lt;br /&gt;1.2.&amp;nbsp;OutRasterBinArgsSWHW[Offset]&amp;nbsp;이&amp;nbsp;Cluster&amp;nbsp;개수를&amp;nbsp;기록하는&amp;nbsp;곳이&amp;nbsp;되도록&amp;nbsp;하기&amp;nbsp;위해서&amp;nbsp;추가&amp;nbsp;연산을&amp;nbsp;합니다.&amp;nbsp;SW&amp;nbsp;의&amp;nbsp;경우&amp;nbsp;[0]&amp;nbsp;위치이기&amp;nbsp;때문에&amp;nbsp;더&amp;nbsp;이상&amp;nbsp;해줄&amp;nbsp;것이&amp;nbsp;없고,&amp;nbsp;HW&amp;nbsp;이고&amp;nbsp;Vertex&amp;nbsp;shader&amp;nbsp;의&amp;nbsp;경우&amp;nbsp;[5]&amp;nbsp;였습니다.&lt;br /&gt;1.3. 현재 RasterBin 에 대한 Indirect draw argument 에 Cluster 개수를 1 더해줍니다. 이때 Offset 은 SW or HW 에 맞게 이미 설정되어 있을 것입니다. WaveIntrinsic 을 사용하여 Atomic Lock 횟수를 최소화한 아주 좋은 코드 예제 입니다. 이 부분을 분석하는 것도 좋지만 현재는 Nanite 코드 이해에 집중하기 위해서 자세한 내용을 다루진 않겠습니다.&lt;br /&gt;1.4. HW 의 경우 Cluster 의 Offset 이 거꾸로 자라납니다. 그래서 GetRasterBinCapacity 를 통해 현재 RasterBin 이 렌더링할 최대 클러스터 개수를 사용하여 HW 용 ClusterOffset 을 완성합니다. 이번에 구한 ClusterOffset 은 현재 RasterBin 이 사용하는 Cluster 를 기준으로 하는 LocalClusterOffset 입니다.&lt;br /&gt;1.5. GetRasterBinOffset 함수로 RasterReserve 과정에 구한 RasterBinData 의 전역 ClusterOffset 을 구합니다. 여기에 1.4 과정에서 구한 LocalClusterOffset 을 더하여 현재 Cluster 의 정보를 저장할 Offset 을 최종적으로 구합니다.&lt;br /&gt;2.&amp;nbsp;이제&amp;nbsp;BatchCount&amp;nbsp;수&amp;nbsp;만큼&amp;nbsp;루프를&amp;nbsp;도는데,&amp;nbsp;우리는&amp;nbsp;bUseBatch&amp;nbsp;를&amp;nbsp;사용하지&amp;nbsp;않는&amp;nbsp;코드를&amp;nbsp;보기&amp;nbsp;때문에&amp;nbsp;1회&amp;nbsp;만&amp;nbsp;실행할&amp;nbsp;것입니다.&lt;br /&gt;3. 1.5 과정에서 얻은 BinClusterMapping 인덱스를 사용하여 RasterBinData 에 ClusterIndex 과 Cluster 내에서 현재 RasterBin 을 사용하는 삼각형의 범위를 기록합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;1181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tkMLG/btsF1LnVM1a/OiGZZPL9kxY0akRGTekWd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tkMLG/btsF1LnVM1a/OiGZZPL9kxY0akRGTekWd0/img.png&quot; data-alt=&quot;그림6. RasterBinScatter 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tkMLG/btsF1LnVM1a/OiGZZPL9kxY0akRGTekWd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtkMLG%2FbtsF1LnVM1a%2FOiGZZPL9kxY0akRGTekWd0%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;1072&quot; height=&quot;1181&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;1181&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림6. RasterBinScatter 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.1.3.&amp;nbsp;HW&amp;nbsp;Rasterize&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;CPU 측의 AddPass_Rasterize 함수내에서 HW Rasterize 를 실행하는 코드를 먼저 확인해보고 VS, PS Shader 를 차례로 확인해봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 레스터라이즈에 필요한 FRasterizePassParameter 를 만듭니다. 이전 과정에서 구한 RasterMetaData 와 RasterBinData 와 Cluster 개수를 기반으로 생성한 Indirect draw argument, Cluster 관련 데이터 등등을 모두 바인딩 합니다. 이 데이터는 SW 레스터라이즈 때에도 재사용 됩니다.&lt;br /&gt;2. HW Rasterize 패스를 RDG 에 추가합니다.&lt;br /&gt;3. RasterizerPasses 개수 만큼(Unique material 개수) 레스터라이즈를 실행할 것인데 없다면 Early exit 합니다.&lt;br /&gt;4. 모든 RasterizerPasses 에 대해서 반복문을 수행합니다.&lt;br /&gt;5. Hidden 이거나 Tessellation 의 경우는 HW 를 사용하지 않습니다.&lt;br /&gt;6. Parameters.ActiveRasterBin 에는 현재 RasterizerPass.RasterBin 을 담아줍니다. 여기서 Parameters 는 1번 과정에서 생성한 FRasterzePassParameter 입니다.&lt;br /&gt;7. 그런 뒤 HW 레스터라이즈를 위해 필요한 파이프라인을 설정하고 Indirect draw argument buffer 를 사용하여 드로콜을 실행합니다. 이때 IndirectOffset 에 16 을 더해주는 부분은&amp;nbsp;&amp;nbsp;(uint 4 개 * 4 byte) 로 HW Rasterize indirect argument 를 사용하기 위해 추가된 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;2747&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceKMO5/btsF1DKqK5X/SKOja3RhmS7XvhKUk0kjz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceKMO5/btsF1DKqK5X/SKOja3RhmS7XvhKUk0kjz1/img.png&quot; data-alt=&quot;그림7. Hardware Rasterize CPU 측 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceKMO5/btsF1DKqK5X/SKOja3RhmS7XvhKUk0kjz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceKMO5%2FbtsF1DKqK5X%2FSKOja3RhmS7XvhKUk0kjz1%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;1132&quot; height=&quot;2747&quot; data-origin-width=&quot;1132&quot; data-origin-height=&quot;2747&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림7. Hardware Rasterize CPU 측 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이제&amp;nbsp;HW&amp;nbsp;Rasterize&amp;nbsp;의&amp;nbsp;VertexShader&amp;nbsp;를&amp;nbsp;봅시다.&lt;br /&gt;1. HWRasterizeVS 로 진입합니다. SV_VertexID 와 SV_InstanceID 가 들어옵니다. 이 값은 Indirect draw argument 로 부터 자동으로 생성되는 변수입니다. 드로콜이 만들어질 때 HW Indirect draw arguments 는 2가지 정보가 있었습니다. 하나는 인스턴스 당 버택스 개수, 두번째는 인스턴스 개수입니다. 여기서 인스턴스는 Cluster 입니다. 그림 4 의 상단 부분을 봐주세요. 클러스터 당 가질 수 있는 최대 삼각형 숫자는 NANITE_MAX_CLUSTER_TRIANGLES(128) 입니다. 그렇기 때문에 Vertex 수는 128 * 3 = 384 가 됩니다. 그렇다면 Cluster 가 384 개의 버택스를 모두 사용하지 않는다면 어떻게 되나요? 그냥 버려집니다. 계속해서 코드를 봅시다.&lt;br /&gt;2.&amp;nbsp;FTriRange&amp;nbsp;는&amp;nbsp;삼각형의&amp;nbsp;시작&amp;nbsp;지점과&amp;nbsp;개수를&amp;nbsp;멤버변수로&amp;nbsp;갖고&amp;nbsp;있습니다.&amp;nbsp;GetIndexAndTriRangeHW&amp;nbsp;를&amp;nbsp;호출하여&amp;nbsp;해당&amp;nbsp;정보를&amp;nbsp;얻습니다.&lt;br /&gt;2.1.&amp;nbsp;GetIndexAndTriRangeHW&amp;nbsp;함수&amp;nbsp;내부로&amp;nbsp;들어가면&amp;nbsp;FetchHWRasterBin&amp;nbsp;함수를&amp;nbsp;호출합니다.&lt;br /&gt;2.2. FetchHWRasterBin 함수 내부로 들어가면 RasterBinData[ActiveRasterBin] 로 부터 클러스터 내에서 그려야 할 삼각형의 범위 정보를 추출합니다. 이때 ActiveRasterBin 변수는 그림6 의 6 에서 설정한 값입니다.&lt;br /&gt;3.&amp;nbsp;LocalTriIndex&amp;nbsp;와&amp;nbsp;VertexID&amp;nbsp;를&amp;nbsp;구합니다. &lt;br /&gt;- LocalTriIndex : 현재 처리 중인 Vertex 가 포함된 삼각형의 인덱스로 Cluster 내부를 기준으로 하기 때문에 LocalTriIndex 로 부름&lt;br /&gt;-&amp;nbsp;VertexID&amp;nbsp;:&amp;nbsp;현재&amp;nbsp;처리&amp;nbsp;중인&amp;nbsp;삼각형을&amp;nbsp;기준으로&amp;nbsp;이&amp;nbsp;Vertex&amp;nbsp;가&amp;nbsp;몇&amp;nbsp;번째&amp;nbsp;버택스&amp;nbsp;인지&amp;nbsp;여부를&amp;nbsp;나타내며&amp;nbsp;0~2&amp;nbsp;값&amp;nbsp;중&amp;nbsp;하나임.&lt;br /&gt;4. VSOut 을 초기화 합니다. 이 때 Out.Position = float4(0, 0, 0, 1); 으로 초기화 하는데 사용하지 않는 Vertex 는 이렇게 처리하여 레스터라이즈 되지 못하도록 퇴화(Degenerate) 시킵니다. 그리고 Cluster, InstanceSceneData, PrimitiveSceneData, NaniteView 등등 필요한 정보들을 준비합니다.&lt;br /&gt;5. 실제 삼각형의 정보를 가져올 수 있는 FCluster 를 준비합니다. 만약 그릴 삼각형의 수가 비어있으면 Clsuter 가 가진 삼각형 수로 설정합니다.&lt;br /&gt;6.&amp;nbsp;현재&amp;nbsp;처리중인&amp;nbsp;버택스가&amp;nbsp;클러스터가&amp;nbsp;소유한&amp;nbsp;삼각형의&amp;nbsp;범위&amp;nbsp;내라면&amp;nbsp;조건문&amp;nbsp;내로&amp;nbsp;진입합니다.&amp;nbsp;&lt;br /&gt;7.&amp;nbsp;TriIndex&amp;nbsp;에&amp;nbsp;클러스터의&amp;nbsp;전체&amp;nbsp;삼각형&amp;nbsp;기준&amp;nbsp;삼각형&amp;nbsp;인덱스를&amp;nbsp;구합니다.&amp;nbsp;그리고&amp;nbsp;DecodeTriangleIndices&amp;nbsp;를&amp;nbsp;호출하여&amp;nbsp;이&amp;nbsp;삼각형의&amp;nbsp;Vertex&amp;nbsp;Index&amp;nbsp;정보를&amp;nbsp;얻습니다.&lt;br /&gt;8. PixelValue 에 ClusterIndex + 1 값과 삼각형 인덱스 값을 넣어줍니다. 이 값은 추후에 VisibilityBuffer 에 들어가는 값의 일부 입니다.&lt;br /&gt;9. CommonRasterizerVS 를 호출하여 나머지 과정을 수행합니다. 그림9 에서 계속해서 봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;1533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnFeHk/btsF2EhvdWc/zLcivHzCCbNke7WZZGOC91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnFeHk/btsF2EhvdWc/zLcivHzCCbNke7WZZGOC91/img.png&quot; data-alt=&quot;그림8. Hardware Rasterize 버택스 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnFeHk/btsF2EhvdWc/zLcivHzCCbNke7WZZGOC91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnFeHk%2FbtsF2EhvdWc%2FzLcivHzCCbNke7WZZGOC91%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;1344&quot; height=&quot;1533&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;1533&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림8. Hardware Rasterize 버택스 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;계속해서 &lt;span style=&quot;color: #333333;&quot;&gt;CommonRasterizerVS 입니다.&lt;/span&gt;&lt;br /&gt;1.&amp;nbsp;FetchAndDeformLocalNaniteVertex&amp;nbsp;함수를&amp;nbsp;통해서&amp;nbsp;FCluster&amp;nbsp;로&amp;nbsp;부터&amp;nbsp;Local&amp;nbsp;space&amp;nbsp;vertex&amp;nbsp;를&amp;nbsp;얻어옵니다.&amp;nbsp;함수&amp;nbsp;내부&amp;nbsp;코드는&amp;nbsp;중요하지&amp;nbsp;않을&amp;nbsp;것&amp;nbsp;같아서&amp;nbsp;더&amp;nbsp;추적하지는&amp;nbsp;않겠습니다.&lt;br /&gt;2. Programmable Vertex Shader 의 경우 머터리얼 쉐이더로 부터 적용할 WorldPositionOffset 정보를 얻어옵니다.&lt;br /&gt;3. Local space vertex 를 World space 로 변환한 후에 바로 전 과정에서 구한 WorldPositionOffset 을 적용합니다. 그리고 Clip space 로 변환합니다. 변환 후에 Clip space 에서 offset, scale 을 적용하는 것 같습니다만 기본 설정은 이 값이 Clip space 공간을 변환시키지 않기 때문에 무시합니다. 그리고 ViewRect 를 설정합니다.&lt;br /&gt;4. PointClipPixel 값을 준비합니다. 이 값은 Pixel Shader 에서 Screen space 좌표 + float2(0.5, 0.5) 위치로 얻을 수 있게 하기 위해서 준비한 값입니다. 마지막으로 PixelValue 와 ViewId 를 설정합니다. PixelValue 에는 ClusterIndex + 1 과 TriangleIndex 가 기록되어 있습니다.&lt;br /&gt;5.&amp;nbsp;Clip&amp;nbsp;space&amp;nbsp;로&amp;nbsp;변환한&amp;nbsp;결과를&amp;nbsp;Position&amp;nbsp;변수에&amp;nbsp;넣어서&amp;nbsp;하드웨어&amp;nbsp;레스터라이즈가&amp;nbsp;실행&amp;nbsp;될&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;합니다.&lt;br /&gt;6.&amp;nbsp;Vertex&amp;nbsp;shader&amp;nbsp;위해&amp;nbsp;채워진&amp;nbsp;값을&amp;nbsp;리턴&amp;nbsp;합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;1130&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RnVnF/btsF03iv0Lp/vBOymixGBOV3rkTE6OeID0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RnVnF/btsF03iv0Lp/vBOymixGBOV3rkTE6OeID0/img.png&quot; data-alt=&quot;그림9. Hardware Rasterize 버택스 쉐이더의 CommonRasterizerVS 함수 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RnVnF/btsF03iv0Lp/vBOymixGBOV3rkTE6OeID0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRnVnF%2FbtsF03iv0Lp%2FvBOymixGBOV3rkTE6OeID0%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;1028&quot; height=&quot;1130&quot; data-origin-width=&quot;1028&quot; data-origin-height=&quot;1130&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림9. Hardware Rasterize 버택스 쉐이더의 CommonRasterizerVS 함수 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;계속해서&amp;nbsp;HW&amp;nbsp;Rasterize&amp;nbsp;의&amp;nbsp;픽셀&amp;nbsp;쉐이더&amp;nbsp;부분입니다.&lt;br /&gt;1. 그림9 의 3 에서 구한 PointClipPixel 을 사용하여 SvPosition 을 구합니다.&lt;br /&gt;2.&amp;nbsp;VisibilityBuffer&amp;nbsp;에&amp;nbsp;저장할&amp;nbsp;PixelValue&amp;nbsp;를&amp;nbsp;추출합니다.&lt;br /&gt;3.&amp;nbsp;VERTEX_TO_TRIANGLE_MASKS&amp;nbsp;는&amp;nbsp;사용하지&amp;nbsp;않으므로&amp;nbsp;건너뜁니다.&lt;br /&gt;4.&amp;nbsp;레스터라이즈&amp;nbsp;결과가&amp;nbsp;ViewRect&amp;nbsp;범위&amp;nbsp;내에&amp;nbsp;들어온다면&amp;nbsp;조건문&amp;nbsp;내로&amp;nbsp;진입&amp;nbsp;합니다.&lt;br /&gt;5.&amp;nbsp;SvPosition.z&amp;nbsp;로&amp;nbsp;부터&amp;nbsp;DeviceZ&amp;nbsp;를&amp;nbsp;얻습니다.&amp;nbsp;이&amp;nbsp;값&amp;nbsp;또한&amp;nbsp;VisibilityBuffer&amp;nbsp;에&amp;nbsp;기록됩니다.&lt;br /&gt;6.&amp;nbsp;FVisBufferPixel&amp;nbsp;객체에&amp;nbsp;PixelPos,&amp;nbsp;PixelValue,&amp;nbsp;DeviceZ&amp;nbsp;정보를&amp;nbsp;넘겨&amp;nbsp;VisibilityBuffer&amp;nbsp;에&amp;nbsp;값을&amp;nbsp;기록할&amp;nbsp;준비를&amp;nbsp;합니다.&lt;br /&gt;7.&amp;nbsp;Pixel.WriteOverdraw()&amp;nbsp;는&amp;nbsp;오버드로우를&amp;nbsp;체크하기&amp;nbsp;위한&amp;nbsp;함수로&amp;nbsp;Visualize&amp;nbsp;활성&amp;nbsp;시에만&amp;nbsp;의미가&amp;nbsp;있습니다.&lt;br /&gt;8. 기존 기록된 Depth 값과 현재 Depth 값을 비교하여 기록해야 하는지 확인하고 필요 없다면 더 이상 진행하지 않습니다.&lt;br /&gt;9. Mesh or Prim Shader 를 사용하지 않는다면 BARYCENTRIC_MODE_EXPORT 로 들어오게 됩니다. Baricentrics.UVW 를 구합니다. 여기서 구한 Baricentrics 정보를 기반으로 현재 처리중인 Pixel 에 대한 FMaterialPixelParameters 를 구합니다. FetchNaniteMaterialPixelParameters 내부에서는 TexCoord, VertexColor, WorldPosition 등등의 정보를 Baricentrics 를 사용하여 보간하여 구합니다. 이 부분도 크게 중요하지 않은 것 같아서 자세한 내용을 다루진 않을 것입니다.&lt;br /&gt;10.&amp;nbsp;이제&amp;nbsp;FMaterialPixelParameters&amp;nbsp;를&amp;nbsp;얻었습니다.&amp;nbsp;이&amp;nbsp;정보를&amp;nbsp;기반으로&amp;nbsp;머터리얼&amp;nbsp;에디터에서&amp;nbsp;만든&amp;nbsp;쉐이더를&amp;nbsp;실행하기&amp;nbsp;위해서&amp;nbsp;CalcMaterialParameters&amp;nbsp;함수에&amp;nbsp;전달하여&amp;nbsp;필요한&amp;nbsp;연산을&amp;nbsp;수행합니다.&amp;nbsp;그리고&amp;nbsp;PixelDepthOffset&amp;nbsp;도&amp;nbsp;적용합니다.&lt;br /&gt;11. 마지막으로 Write 함수를 호출해서 해당 부분을 따라가면 결국 WritePixel 함수를 호출합니다. 여기서 VisibilityBuffer 에 데이터를 기록합니다. 기록할 때 InterlockedMax 로 기록하는 것을 볼 수 있습니다. 이것은 Depth 테스트를 위한 기능입니다. Visibility Buffer 는 Depth | ClusterIndex + 1 | TriangleIndex 로 구성되기 때문에 Depth 가 가장 상위 비트를 차지합니다. 상위 비트의 Depth 를 기준으로 Depth 가 더 큰 값을 가진 픽셀이(더 카메라에 근접한 픽셀이) 살아남을 수 있도록 하는 것이 이 코드의 목적입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;2819&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2zlPp/btsF3dDN3VV/wUCvTIqkCvLZxgYqkzv7N0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2zlPp/btsF3dDN3VV/wUCvTIqkCvLZxgYqkzv7N0/img.png&quot; data-alt=&quot;그림10. Hardware Rasterize 픽셀 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2zlPp/btsF3dDN3VV/wUCvTIqkCvLZxgYqkzv7N0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2zlPp%2FbtsF3dDN3VV%2FwUCvTIqkCvLZxgYqkzv7N0%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;1120&quot; height=&quot;2819&quot; data-origin-width=&quot;1120&quot; data-origin-height=&quot;2819&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림10. Hardware Rasterize 픽셀 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이제&amp;nbsp;SW&amp;nbsp;Raterize&amp;nbsp;코드를&amp;nbsp;확인해봅시다.&amp;nbsp;먼저&amp;nbsp;CPU&amp;nbsp;측에서&amp;nbsp;렌더커맨드를&amp;nbsp;생성하는&amp;nbsp;부분입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1.&amp;nbsp;HW&amp;nbsp;Rasterize&amp;nbsp;와&amp;nbsp;동일하게&amp;nbsp;모든&amp;nbsp;RasterizePass&amp;nbsp;가&amp;nbsp;있는지&amp;nbsp;비교하고&amp;nbsp;Early&amp;nbsp;exit&amp;nbsp;을&amp;nbsp;수행합니다.&lt;br /&gt;2.&amp;nbsp;그리고&amp;nbsp;모든&amp;nbsp;RasterizerPasses&amp;nbsp;에&amp;nbsp;대해서&amp;nbsp;순회합니다.&amp;nbsp;이&amp;nbsp;때&amp;nbsp;Hidden&amp;nbsp;이거나&amp;nbsp;Shader&amp;nbsp;가&amp;nbsp;빈&amp;nbsp;경우&amp;nbsp;스킵합니다.&lt;br /&gt;3.&amp;nbsp;HW&amp;nbsp;Rasterize&amp;nbsp;와&amp;nbsp;같이&amp;nbsp;Parameters.ActiveRasterBin&amp;nbsp;을&amp;nbsp;설정해줍니다.&lt;br /&gt;4.&amp;nbsp;Indirect&amp;nbsp;draw&amp;nbsp;argument&amp;nbsp;버퍼를&amp;nbsp;사용하여&amp;nbsp;Dispatch&amp;nbsp;함수를&amp;nbsp;호출합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1053&quot; data-origin-height=&quot;969&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m4FNk/btsF04hteOq/XR8UvOd48rAk9x6GC7b4F0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m4FNk/btsF04hteOq/XR8UvOd48rAk9x6GC7b4F0/img.png&quot; data-alt=&quot;그림11. Software Rasterize CPU 측 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m4FNk/btsF04hteOq/XR8UvOd48rAk9x6GC7b4F0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm4FNk%2FbtsF04hteOq%2FXR8UvOd48rAk9x6GC7b4F0%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;1053&quot; height=&quot;969&quot; data-origin-width=&quot;1053&quot; data-origin-height=&quot;969&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림11. Software Rasterize CPU 측 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이제 Software Rasterizer 의 Compute shader 쪽을 보겠습니다.&lt;br /&gt;1. MicropolyRasterize 함수에 진입 합니다. 이 때 THREADGROUP_SIZE 는 NANITE_VERT_REUSE_BATCH 사용 여부에 따라 결정됩니다. NANITE_VERT_REUSE_BATCH 사용하는 경우 32, 그렇지 않은 경우 64 가 됩니다. SW Raterize 는 Programmable 타입을 사용하는 경우 NANITE_VERT_REUSE_BATCH 를 활성화 합니다. 활성화 되었다고 가정하고 코드를 봅시다.&lt;br /&gt;2. ClusterRasterize 함수를 호출합니다. 전달되는 GroupID 는 WrokGroup 의 인덱스로 ClusterIndex 입니다. GroupIndex 는 WorkGroup 내의 thread 인덱스로 NANITE_VERT_REUSE_BATCH 를 사용하기 때문에 [0, 31] 중 하나의 값이 될 것입니다.&lt;br /&gt;3.&amp;nbsp;ClusterRasterize&amp;nbsp;함수로&amp;nbsp;진입합니다.&amp;nbsp;HW&amp;nbsp;Raterize&amp;nbsp;때와&amp;nbsp;마찬가지로&amp;nbsp;GetIndexAndTriRangeSW&amp;nbsp;함수를&amp;nbsp;사용하여&amp;nbsp;현재&amp;nbsp;RasterBin&amp;nbsp;이&amp;nbsp;VisibleIndex&amp;nbsp;의&amp;nbsp;Cluster&amp;nbsp;내에서&amp;nbsp;렌더링&amp;nbsp;해야&amp;nbsp;하는&amp;nbsp;삼각형의&amp;nbsp;범위를&amp;nbsp;구합니다.&lt;br /&gt;4.&amp;nbsp;FVisibleCluster,&amp;nbsp;FInstanceSceneData,&amp;nbsp;FPrimitiveSceneData,&amp;nbsp;FNaniteView&amp;nbsp;등등&amp;nbsp;필요한&amp;nbsp;정보들을&amp;nbsp;로드합니다.&amp;nbsp;그리고&amp;nbsp;HW&amp;nbsp;때와&amp;nbsp;마찬가지로&amp;nbsp;FCluster&amp;nbsp;정보를&amp;nbsp;얻어오고&amp;nbsp;삼각형&amp;nbsp;개수가&amp;nbsp;없다면&amp;nbsp;Clsuter&amp;nbsp;전체&amp;nbsp;삼각형&amp;nbsp;수로&amp;nbsp;설정해줍니다.&lt;br /&gt;5. FMaterialShader 에 필요한 정보들을 채웁니다. CalculateNaniteVertexTransforms 는 Nanite Vertex 를 변환하기 위해서 필요한 각종 Matrix 정보들이 있습니다.&lt;br /&gt;6.&amp;nbsp;Raster&amp;nbsp;에&amp;nbsp;필요한&amp;nbsp;Viewport&amp;nbsp;정보를&amp;nbsp;담습니다.&amp;nbsp;ViewportScale,&amp;nbsp;ViewBias,&amp;nbsp;ScissorRect&amp;nbsp;정보를&amp;nbsp;만듭니다.&lt;br /&gt;7. 우리는 테셀레이션은 없다고 가정하고 코드를 보기 때문에 NANITE_PIXEL_PROGRAMMABLE 쪽으로 넘어옵니다. 이 부분은 그림13 에서 다루겠습니다. 만약 PROGRAMMABLE 이 아닌 경우라면 NANITE_VERT_REUSE_BATCH 를 사용하지 않을 것이고 THREADGROUP_SIZE 도 64 일 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;1218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SUNKA/btsF0157mgu/K5LKiNKHWaMnUDOjamypUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SUNKA/btsF0157mgu/K5LKiNKHWaMnUDOjamypUK/img.png&quot; data-alt=&quot;그림12. Software Rasterize 인 MicropolyRasterize 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SUNKA/btsF0157mgu/K5LKiNKHWaMnUDOjamypUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSUNKA%2FbtsF0157mgu%2FK5LKiNKHWaMnUDOjamypUK%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;972&quot; height=&quot;1218&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;1218&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림12. Software Rasterize 인 MicropolyRasterize 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. NANITE_PIXEL_PROGRAMMABLE 를 사용하는 경우 TSlidingWindowVertexCache 를 사용합니다. 이 객체는 32개의 Thread 가 각각의 1개의 Vertex 를 변환 시킨 다음, 이웃 Thread 에서 Transform 한 Vertex 가 있다면 가져다 쓰는 방식으로 구현되어 있습니다. 한 가지 가정이 있는데 이웃 인덱스에서 변환 된 Vertex 를 가져올 때 현재 인데스와 이웃 인덱스의 차이가 WindowSize(32) 이상 나지 않는다는 가정을 하고 구현되어 있습니다. 그림14 의 주석과 PullExisting 함수 내부 구현중 초록색 네모를 참고해주세요. &lt;br /&gt;2. 각각의 Thread 는 삼각형 한 개를 담당하는 것을 알 수 있습니다. TriIndex 에 현재 클러스터 기준 삼각형 인덱스와 bTriValid 에 현재 처리중인 삼각형이 범위를 넘어섰는지 여부를 기록합니다. &lt;br /&gt;3. 처리 가능한 삼각형이라면, 삼각형의 VertexIndex 정보를 얻습니다. &lt;br /&gt;4. VertexCache.PullExisting 에서는 이전에 변환된 Vertex 정보 중 내가 쓸 수 있는 것이 있다면 가져다 씁니다. 2번에서 각 Thread 는 삼각형 1개를 담당하고 있다고 했습니다. 하지만 VertexCache 를 사용할 때는 각 Thread 가 Vertex 1 개를 담당합니다.&lt;br /&gt;5. 이제 각각의 32개 Thread 가 VertexCache.CacheVert 에 자신이 맡은 삼각형을 변환 시킵니다. &lt;br /&gt;6. PullRemaining 에서는 이웃이 변환한 Vertex 중 내가 사용하는 것이 있으면 가져옵니다. 그림14의 PullRemaining 함수 내에 초록색 네모 영역이 바로 그 부분입니다.&lt;br /&gt;7.&amp;nbsp;이제&amp;nbsp;삼각형을&amp;nbsp;구성하는데&amp;nbsp;필요한&amp;nbsp;변환된&amp;nbsp;Vertex&amp;nbsp;3&amp;nbsp;개를&amp;nbsp;모두&amp;nbsp;구했습니다.&amp;nbsp;MaterialShader&amp;nbsp;에&amp;nbsp;넣어줍니다. &lt;br /&gt;8.&amp;nbsp;FRasterTri&amp;nbsp;객체를&amp;nbsp;만들어서&amp;nbsp;삼각형이&amp;nbsp;커버하는&amp;nbsp;영역을&amp;nbsp;Rasterize&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;준비합니다.&amp;nbsp;SetupTriangle&amp;nbsp;내부를&amp;nbsp;자세히&amp;nbsp;다루지는&amp;nbsp;않겠습니다. &lt;br /&gt;9.&amp;nbsp;HW&amp;nbsp;때와&amp;nbsp;마찬가지로&amp;nbsp;VisibilityBuffer&amp;nbsp;에&amp;nbsp;저장할&amp;nbsp;데이터를&amp;nbsp;준비합니다.&amp;nbsp;PixelValue&amp;nbsp;는&amp;nbsp;ClusterIndex&amp;nbsp;+&amp;nbsp;1&amp;nbsp;과&amp;nbsp;TriIndex&amp;nbsp;를&amp;nbsp;저장합니다. &lt;br /&gt;10. RasterizeTri_Adaptive 함수를 호출하여 레스터라이즈를 수행합니다. 그림15 에서 계속 추적해봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;1330&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wNPO0/btsF0bOTEnI/gR8rmTzCB3LSA6G4Eqkgw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wNPO0/btsF0bOTEnI/gR8rmTzCB3LSA6G4Eqkgw1/img.png&quot; data-alt=&quot;그림13. Software Rasterize 인 MicropolyRasterize 코드2 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wNPO0/btsF0bOTEnI/gR8rmTzCB3LSA6G4Eqkgw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwNPO0%2FbtsF0bOTEnI%2FgR8rmTzCB3LSA6G4Eqkgw1%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;962&quot; height=&quot;1330&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;1330&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림13. Software Rasterize 인 MicropolyRasterize 코드2 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&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;901&quot; data-origin-height=&quot;1031&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDfymF/btsF01ZjxzT/77BIDxlh3144l0OkVLOKUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDfymF/btsF01ZjxzT/77BIDxlh3144l0OkVLOKUK/img.png&quot; data-alt=&quot;그림14. TSlidingWindowVertexCache 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDfymF/btsF01ZjxzT/77BIDxlh3144l0OkVLOKUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDfymF%2FbtsF01ZjxzT%2F77BIDxlh3144l0OkVLOKUK%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;901&quot; height=&quot;1031&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;1031&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림14. TSlidingWindowVertexCache 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;11.&amp;nbsp;삼각형의&amp;nbsp;크기가&amp;nbsp;일정&amp;nbsp;크기보다&amp;nbsp;크다면&amp;nbsp;Scanline&amp;nbsp;방식,&amp;nbsp;그렇지&amp;nbsp;않은&amp;nbsp;경우&amp;nbsp;Rect&amp;nbsp;방식으로&amp;nbsp;레스터라이즈를&amp;nbsp;수행합니다.&lt;br /&gt;12.&amp;nbsp;간단할&amp;nbsp;것&amp;nbsp;같은&amp;nbsp;Rect&amp;nbsp;방식으로&amp;nbsp;들어가봅시다.&amp;nbsp;x,&amp;nbsp;y&amp;nbsp;를&amp;nbsp;MinPixel&amp;nbsp;부터&amp;nbsp;MaxPixel&amp;nbsp;까지&amp;nbsp;증가시키면서&amp;nbsp;WritePixel&amp;nbsp;함수를&amp;nbsp;호출하는&amp;nbsp;것을&amp;nbsp;볼&amp;nbsp;수&amp;nbsp;있습니다.&lt;br /&gt;13. WritePixel 함수를 타고 들어가면 TNaniteWritePixel 의 operator() 내부로 들어옵니다. Tri.DepthPlane 으로 부터 Depth 값을 얻고, FVisBufferPixel 에 PixelPos, PixelValue, DeviceZ 를 전달합니다. HW 와 같이 WriteOverdraw(), EarlyDepthTest() 를 호출합니다.&lt;br /&gt;14. 마지막으로 EvaluatePixel 함수에서 FetNaniteMaterialPixelParameters 함수를 호출하여 FMaterialPixelParameters 를 구합니다. CalcMaterialParameters 와 PixelDepthOffset 을 적용하고 Masked Material 의 경우 discard 를 처리할지 여부를 확인합니다.&lt;br /&gt;15. 모든 과정을 통과하면 Write 함수를 호출하여 WritePixel 함수를 통해 VisibilityBuffer 에 평가된 데이터를 기록합니다. 여기서도 HW 와 마찬가지로 VisibilityBuffer 에 저장할 데이터의 상위 비트에 Depth 값을 넣고 InterlockedMax 함수를 사용하여 데이터를 저장해 Depth Buffer 를 사용하여 기록하는 것과 같은 효과를 내도록 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;2860&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OOeXY/btsF1h8JJli/xQNVoKI0vNpwQvRVy2wKxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OOeXY/btsF1h8JJli/xQNVoKI0vNpwQvRVy2wKxk/img.png&quot; data-alt=&quot;그림15. RasterizeTri_Adaptive 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OOeXY/btsF1h8JJli/xQNVoKI0vNpwQvRVy2wKxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOOeXY%2FbtsF1h8JJli%2FxQNVoKI0vNpwQvRVy2wKxk%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;1139&quot; height=&quot;2860&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;2860&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림15. RasterizeTri_Adaptive 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.2. 현재 프레임 기반 HZB 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HZB 생성 과정은 쉐이더 코드까지 자세히 다루진 않고 CPU 측에서 어떻게 호출되는지만 확인해봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. BuildPreviousOccluderHZB 패스에서는 MainPass 에서 생성된 Visibility Buffer 로 부터 HZB 를 생성합니다.&lt;br /&gt;2. 생성한 HZB 를 CullingParameters 에 바인딩하는 것을 볼 수 있습니다.&lt;br /&gt;3. 이제 PostPass 를 진행하며, MainPass 와 비슷한 과정을 반복합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;879&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EXI5m/btsF14nedUs/SK0hv4XFf1UR7q4Of9g9x1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EXI5m/btsF14nedUs/SK0hv4XFf1UR7q4Of9g9x1/img.png&quot; data-alt=&quot;그림16. HZB 생성 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EXI5m/btsF14nedUs/SK0hv4XFf1UR7q4Of9g9x1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEXI5m%2FbtsF14nedUs%2FSK0hv4XFf1UR7q4Of9g9x1%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;814&quot; height=&quot;879&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;879&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림16. HZB 생성 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&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;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.3.&amp;nbsp;PostPass&amp;nbsp;와&amp;nbsp;MainPass&amp;nbsp;의&amp;nbsp;차이점&amp;nbsp;정리&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;PostPass 는 MainPass 와 대부분의 코드를 공유합니다. 그래서 차이점을 정리한 뒤 다시 &lt;a href=&quot;https://scahp.tistory.com/127&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Nanite(1/2)&lt;/span&gt;&lt;/a&gt;&amp;nbsp;의 MainPass 를 리뷰해보는 것으로 충분할 것 같습니다. InstanceCulling 과 NodeAndClusterCulling 부분이 다르기 때문에 해당 부분을 집중적으로 확인해봅시다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size18&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;3.3.1. FInstanceCull_CS 차이&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;FInstanceCull_CS::FParameter 설정할 때, OutOccludedInstances 와 OutOccludedInstancesArgs 설정하지 않음.&lt;/li&gt;
&lt;li&gt;FInstanceCull_CS&amp;rarr;IndirectArgs = OccludedInstancesArgs 를 설정하여 Indirect draw 를 사용함. Occluded 된 인스턴스만 다시 체크하기 위해서 임.&lt;/li&gt;
&lt;/ul&gt;
&lt;p 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;3.3.2. InstanceCull Shader 차이&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;MainAndPostNodesAndClusterBatches 등을 저장할때 PostPass 에 맞는 인덱스에 저장하도록 함.&lt;/li&gt;
&lt;li&gt;QueueState 사용 시에도 PostPass 에서 사용하는 인덱스에 Offset 정보를 저장하도록 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;3.3.3.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;b&gt; FNodeAndClusterCull_CS 차이&lt;/b&gt;&lt;br /&gt;VisibleClustersArgsSWHW 에 PostRasterizeArgsSWHW 를 바인딩하며, OffsetClustersArgsSWHW 에 MainRasterizeArgsSWHW 를 바인딩 함.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;3.3.4. FNodeAndClusterCull_CS Shader 차이&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ProcessCluster 수행 중 bUseHWRaster 여부 파악 할 때 SmallEnoughToDraw 함수 사용하지 않고 MainPass 에서 전달해준 Flag 그대로 사용함.&lt;/li&gt;
&lt;li&gt;VisibleClustersSWHW 에 VisibleCluster 정보를 저장할 때, 기존 MainPass 에서 기록한 내용이 있기 때문 OffsetClustersArgsSWHW(MainRasterizeArgsSWHW) 를 사용해 Offset 을 더해줌.&lt;/li&gt;
&lt;li&gt;Occlued 된 Cluster 를 MAinAndPostCandidiateClusters 에 기록하지 않음. 더 이상 재차 확인할 패스가 없기 때문임.&lt;/li&gt;
&lt;li&gt;GroupOccludedBitmask 를 사용하지 않음. 자식 노드 중 Occluded 된 경우가 있는 경우 PostPass 에서 다시 체크해보기 위해 사용하지만 더이상 재차 확인할 패스가 없기 때문에 사용안함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이전글&amp;nbsp;&lt;a href=&quot;https://scahp.tistory.com/127&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #0070d1;&quot;&gt;[UE5] Nanite (2/5)&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;다음글 &lt;a href=&quot;https://scahp.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[UE5] Nanite (4/5)&lt;/a&gt;&lt;/span&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 레퍼런스&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;&lt;a href=&quot;https://github.com/EpicGames/UnrealEngine/commit/072300df18a94f18077ca20a14224b5d99fee872&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/EpicGames/UnrealEngine/commit/072300df18a94f18077ca20a14224b5d99fee872&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;2.&amp;nbsp;&lt;a href=&quot;https://www.youtube.com/watch?v=eviSykqSUUw&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://www.youtube.com/watch?v=eviSykqSUUw&lt;/span&gt;&lt;/a&gt;&amp;nbsp;&lt;a href=&quot;https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;(Slide link)&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;3.&amp;nbsp;&lt;a href=&quot;https://docs.unrealengine.com/5.0/en-US/nanite-virtualized-geometry-in-unreal-engine/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://docs.unrealengine.com/5.0/en-US/nanite-virtualized-geometry-in-unreal-engine/&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;4. &lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://scahp.tistory.com/126&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;5. &lt;a href=&quot;https://scahp.tistory.com/127&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://scahp.tistory.com/127&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>UE4 &amp;amp; UE5/Rendering</category>
      <category>cluster</category>
      <category>GPU Driven Rendering</category>
      <category>hzb</category>
      <category>Nanite</category>
      <category>PostPass</category>
      <category>rasterbin</category>
      <category>Rasterize</category>
      <category>Rasterizer</category>
      <category>rendering</category>
      <category>UE5</category>
      <author>scahp</author>
      <guid isPermaLink="true">https://scahp.tistory.com/128</guid>
      <comments>https://scahp.tistory.com/128#entry128comment</comments>
      <pubDate>Fri, 22 Mar 2024 23:42:08 +0900</pubDate>
    </item>
    <item>
      <title>[UE5] Nanite (2/5)</title>
      <link>https://scahp.tistory.com/127</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[UE5] Nanite (2/5)&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size16&quot;&gt;최초 작성 : 2024-03-21&lt;br /&gt;마지막 수정 : 2024-03-21&lt;br /&gt;최재호&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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 style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 환경&lt;/b&gt;&lt;br /&gt;&lt;b&gt;2. 목표&lt;/b&gt;&lt;br /&gt;&lt;b&gt;3. 내용&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&amp;nbsp;&lt;/b&gt;&lt;b&gt;3.1. &lt;/b&gt;&lt;b&gt;TwoPassOcclusionCulling 의 컬링파트&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; 3.2. FRenderer 생성 및 InitArgs 코드 분석&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; 3.3. Main/Post Culling 전체 코드 레이아웃&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 3.3.1. AddPassInitNodesAndClusterBatchesUAV&lt;/b&gt;&lt;br /&gt;&lt;b&gt; &lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 3.3.2. &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt; AddPass_InstanceHierarchyAndClusterCull 전체 레이아웃&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;b&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 3.3.2.1. FInstanceCull_CS (MainPass)&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 3.3.2.2. AddPass_NodeAndClusterCull (MainPass) 전체 레이아웃&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 3.3.2.2.1. LoadCandidateNodeDataToGroup&lt;/b&gt;&lt;br /&gt;&lt;b&gt; &lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 3.3.2.2.2.&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt; ProcessNodeBatch &lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;b&gt; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 3.3.2.2.3.&lt;/b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt; ProcessClusterBatch &lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 3.3.2.3. CalculateSafeRasterizerArgs&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;4. 레퍼런스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 환경&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Unreal Engine 5.3.2 (release branch 072300df18a94f18077ca20a14224b5d99fee872)&lt;br /&gt;개인적으로 분석한 내용이라 틀린 점이 있을 수 있습니다. 그런 부분은 알려주시면 감사하겠습니다.&lt;br /&gt;&lt;br /&gt;이번 글은 한 번에 많은 길이의 코드를 분석하는 부분이 종종 등장합니다. 그래서 글을 2개 띄우고 한쪽은 설명 부분을 한쪽은 코드 이미지를 최대화해서 보는 것을 추천합니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 목표&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Nanite 에 들어간 핵심 기술들을 파악하고 BasePass 렌더링 과정을 코드레벨로 이해해 봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이번 글에서는 TwoPassOcclusionCulling 의 MainPass 중 인스턴스와 클러스터 컬링에 대한 부분을 확인해봅시다. MainPass 는 이전 프레임에 생성된 HZB 에 대해서 인스턴스와 클러스터 컬링을 수행합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;아마 이번 글에서 다루는 내용이 전체 Nanite 내용 중 가장 복잡하고 내용이 많을 것입니다. 하지만 이 부분만 넘어가면 그 이후로는 편하다는 뜻도 됩니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;314&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ujM5K/btsF2Ty3j2M/hwUNE2cuKkw3gfvdeZkCTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ujM5K/btsF2Ty3j2M/hwUNE2cuKkw3gfvdeZkCTk/img.png&quot; data-alt=&quot;추가그림1. 오늘 알아볼 부분을 렌더독으로 캡쳐 (출처 : 직접 촬영)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ujM5K/btsF2Ty3j2M/hwUNE2cuKkw3gfvdeZkCTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FujM5K%2FbtsF2Ty3j2M%2FhwUNE2cuKkw3gfvdeZkCTk%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;432&quot; height=&quot;314&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;314&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;추가그림1. 오늘 알아볼 부분을 렌더독으로 캡쳐 (출처 : 직접 촬영)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이전 글에서 이야기 했던 것 처럼 이 글은 총 5개로 구성될 예정입니다.&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. Nanite 1/5 : Nanite 에서 사용하는 주요 기술과 MeshDrawCommand 생성 및 VisibilityBuffer 초기화 과정 리뷰&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/127&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;2.&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://scahp.tistory.com/127&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;Nanite 2/5 :&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;a href=&quot;https://scahp.tistory.com/127&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt; TwoPassOcclusionCulling 의 전체 레이아웃을 확인하고 MainPass 의 노드 및 클러스터 컬링 리뷰&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/128&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;3.&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;Nanite 3/5 :&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;SW, HW 레스터라이저와 PostPass 리뷰&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;4.&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;Nanite 4/5 :&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;Visibility Buffer 로 부터 Depth/Stencil 텍스쳐를 생성하는 부분 리뷰&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://scahp.tistory.com/130&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;5.&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;Nanite 5/5 :&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;Visibility Buffer 로 부터 G-Buffer 생성하는 MaterialPass 리뷰&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3.&amp;nbsp;내용&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.1. TwoPassOcclusionCulling 의 컬링 파트&lt;/b&gt;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.2.&amp;nbsp;FRenderer&amp;nbsp;생성&amp;nbsp;및&amp;nbsp;InitArgs&amp;nbsp;코드&amp;nbsp;분석&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;TwoPassOcclusionCulling&amp;nbsp;과&amp;nbsp;Visibility&amp;nbsp;Buffer&amp;nbsp;Rendering&amp;nbsp;을&amp;nbsp;위해&amp;nbsp;필요한&amp;nbsp;정보를&amp;nbsp;담고&amp;nbsp;있는&amp;nbsp;FRenderer&amp;nbsp;를&amp;nbsp;생성합니다. &lt;br /&gt;2.&amp;nbsp;GPU&amp;nbsp;에서&amp;nbsp;사용할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;렌더링&amp;nbsp;관련&amp;nbsp;비트&amp;nbsp;플래그를&amp;nbsp;설정합니다. &lt;br /&gt;3. PageConstants.X 에는 InstanceSceneDataSOAStride 가 들어가는데, 실제 이 값을 사용하는 곳이 없기 때문에 더 깊게 보지 않겠습니다. PageConstants.Y 에는 MaxStreamingPages 가 들어갑니다. 이 값은 렌더링에 필요한 Cluster 를 실시간으로 스트리밍 할 때 사용할 최대 페이지 수로 보입니다.&lt;br /&gt;4. QueueState 버퍼를 생성합니다. QueueState 는 FQueueState 구조체로 쉐이더에 선언 되어있습니다. 멤버로 uint TotalClusters, FQueuePassState PassState[2] 가 있습니다. PassState 는 2개의 요소를 가진 배열인데, 0번 인덱스에는 MainPass, 1번 인덱스에는 PostPass 에 대한 정보가 기록됩니다. 이 정보는 Node 와 Cluster 를 특정 버퍼에 기록하고 읽을 버퍼의 Offset 정보가 있습니다. QueueState 는 Node 와 Cluster 의 기록/읽기 위치에 대한 정보를 가지고 있기 때문에 상당히 중요한 데이터 입니다. 추가로 MainPass, PostPass 용어는 TwoPassOcclusionCulling 의 첫번째, 두번째 패스를 말합니다.&amp;nbsp;&lt;br /&gt;5. VisibleClustersSWHW 는 Culling 과정 이후 Visible Cluster 를 기록하는 버퍼입니다. FVisibleCluster 를 Packing 하여 저장 합니다. FVisibleCluster 에는 ViewId, InstanceId, ClusterIndex 등과 같이 Cluster 에 대한 정보를 갖고 있습니다. VisibleClustersSWHW 에서 SW 는 Software rasterize, HW 는 Hardware rasterize 를 의미합니다. SW 로 그려질 VisibleCluster 는 VisibleClustersSWHW 의 Offset 0 에서 부터 차례로 기록되며, HW 로 그려질 VisibleCluster 는 VisibleClustersSWHW 의 맨 뒤에서부터 역으로 기록되며 자라납니다. &lt;br /&gt;6. MainRasterizeArgsSWHW 는 Rasterize 에 사용할 Indirect draw argument 정보를 담습니다. NANITE_RASTERIZER_ARG_COUNT(8) 인데, SW 를 위해서 3개를 사용하고 1개는 패딩, HW 를 위해서 4~7 인덱스를 사용합니다.&amp;nbsp; &lt;br /&gt;7. SafeMainRasterizeArgsSWHW 는 생성한 SW/HW Rasterize Indirect draw argument 정보가 허용 범위를 넘어서지 않도록 처리된 결과를 저장하는 버퍼입니다. 추후에 쉐이더의 CalculateSafeRasterizerArgs 에서 생성됩니다. 이 글의 가장 마지막에서 볼 것입니다.&lt;br /&gt;8. 현재는 TwoPassOcclusionCulling 을 사용하기 때문에 조건문 내에 무조건 들어옵니다. PostRasterizeArgsSWHW 와 SafePostRasterzeArgsSWHW 는 7에서 Main 과 동일한 이유로 생성된 버퍼입니다. OccludedInstances 는 HZB 테스트에서 Occluded 된 경우 ViewId 와 InstanceId 를 기록하는데 사용됩니다. OccludedInstancesArgs[0] 에는 Occluded 된 인스턴스를 64 개의 단위로 그룹을 만든 경우 총 몇개의 그룹인지? OccludedInstancesArgs[3] 에는 Occluded 된 총 인스턴스 수가 기록됩니다. 이렇게 64개의 그룹으로 묶는 이유는 OccludedInstance 는 PostPass 의 InstanceCull 과정에서 한번 더 Visibility 검사를 수행하는데, 이 때 쉐이더의 thread 가 WorkGroup 당 64 개 이기 때문입니다. 그리고 64 는 한 개의 Wavefront 가 동시에 돌릴 수 있는 thread 의 최대 개수입니다. &lt;br /&gt;9.&amp;nbsp;ClusterCountSWHW&amp;nbsp;도&amp;nbsp;CalculateSafeRasterizerArgs&amp;nbsp;과정에서&amp;nbsp;채워지며,&amp;nbsp;NumClustersSW,&amp;nbsp;NumClustersHW&amp;nbsp;정보가&amp;nbsp;각각&amp;nbsp;기록됩니다. &lt;br /&gt;10. 화면에 렌더링 되어야 하는데 없는 클러스터 데이터를 로드하기 위한 요청을 담을 버퍼이며, GPU 측 RequestPageRange 함수에서 채워집니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;1947&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DSvnH/btsFYOqDY9U/uMJwm1Zk1lkKJwllTL0RJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DSvnH/btsFYOqDY9U/uMJwm1Zk1lkKJwllTL0RJ0/img.png&quot; data-alt=&quot;그림1. FRenderer 생성과 컬링에 필요한 버퍼 생성 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DSvnH/btsFYOqDY9U/uMJwm1Zk1lkKJwllTL0RJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDSvnH%2FbtsFYOqDY9U%2FuMJwm1Zk1lkKJwllTL0RJ0%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;1266&quot; height=&quot;1947&quot; data-origin-width=&quot;1266&quot; data-origin-height=&quot;1947&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. FRenderer 생성과 컬링에 필요한 버퍼 생성 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이제 FRenderer 를 만들고 나면 FRenderer 의 멤버함수인 DrawGeometry 를 호출해서 이 함수 내에서 모든 TwoPassOcclusionCulling 작업이 징해되도록 합니다. &lt;span style=&quot;color: #333333;&quot;&gt;DrawGeometry&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt; 내부로 들어가봅시다.&lt;br /&gt;&lt;br /&gt;1. Nanite View 버퍼를 만들어 GPU 에 전달할 준비를 합니다. Nanite 는 여러 View 에 대해서 동시에 렌더링 가능합니다.&lt;br /&gt;2. OptionalInstanceDraws 를 사용하지 않는다고 가정하므로, NumInstancePreCull 에는 GPUScene 에 할당된 InstanceId 수를 저장합니다. &lt;br /&gt;3. CullingParameters 에 Culling 에 필요한 데이터를 채웁니다. NaniteView 정보와 PrevHZB, MaxCandidateClusters, MaxVisibleClusters, RenderFlags 등등 입니다. 여기서 주목할 점은 MainPass 에서는 PrevHZB 가 들어가며, PostPass 에서는 MainPass 를 통해 Rasterize 된 DepthBuffer 기준으로 생성된 HZB 가 들어간다는 점입니다. 나머지는 Culling 과정을 수행할 때 차차 알아봅시다. &lt;br /&gt;4. 그림1 에서 FRenderer 를 만들 때 생성한 버퍼들을 초기화 합니다. Node 와 Cluster 의 읽기/쓰기 Offset 을 담는 중요한 버퍼인 QueueState 와 Main/PostRasterizeArgsSWHW, OccludedInstancesArgs 등등 이 보입니다. &lt;br /&gt;5.&amp;nbsp;Compute&amp;nbsp;Shader&amp;nbsp;를&amp;nbsp;수행하여&amp;nbsp;초기화를&amp;nbsp;시작합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;1358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/be1RN1/btsFUXOToyt/DSIywxoWiQ5pVWww7okqM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/be1RN1/btsFUXOToyt/DSIywxoWiQ5pVWww7okqM0/img.png&quot; data-alt=&quot;그림2. Culling 에 필요한 버퍼를 초기화 하는 FInitArgs_CS (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/be1RN1/btsFUXOToyt/DSIywxoWiQ5pVWww7okqM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbe1RN1%2FbtsFUXOToyt%2FDSIywxoWiQ5pVWww7okqM0%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;1114&quot; height=&quot;1358&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;1358&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림2. Culling 에 필요한 버퍼를 초기화 하는 FInitArgs_CS (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;계속해서 FInitArgs_CS 쉐이더 코드를 확인해봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. ComputeShader 에서는 InitArgs 함수에 진입하였습니다. 우리는 Mesh or Primitive Shader 를 사용하지 않는다고 가정하기 때문에 GetHWClusterCounterIndex 는 5 를 리턴합니다. 이 인덱스 는 MainPost/RasterizeArgsSWHW 의 HWClusterCount 의 초기화에 사용됩니다. &lt;br /&gt;2.&amp;nbsp;QueueState&amp;nbsp;에&amp;nbsp;Offset&amp;nbsp;정보들을&amp;nbsp;0&amp;nbsp;으로&amp;nbsp;초기화&amp;nbsp;합니다. &lt;br /&gt;3. DrawnClusterCounts 에는 지금까지 렌더링된 Cluster 수를 저장합니다. 이번 예제에서는 이 데이터는 사용하지 않습니다. WriteRasterizerArgsSWHW 함수를 호출해서 MainPassRasterizeArgsSWHW 를 초기화 합니다. SW, HW 클러스터를 모두 0으로 초기화 해줍니다. &lt;br /&gt;4. TwoPassOcclusionCulling 을 사용하기 때문에 OCCLUSION_CULLING 은 1 입니다. 먼저 OccludedInstancesArgs 를 초기화 합니다. 그리고 WriteRasterizerArgsSWHW 로 PostPassRasterizeArgsSWHW 를 초기화 합니다. SW, HW 클러스터를 모두 0으로 초기화 해줍니다.&amp;nbsp; &lt;br /&gt;5. DRAW_PASS_INDEX 는 여기서는 항상 0 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;802&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceeZMN/btsFU0EQQEZ/Kk4ncD1DjtWEvEeUv78Y00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceeZMN/btsFU0EQQEZ/Kk4ncD1DjtWEvEeUv78Y00/img.png&quot; data-alt=&quot;그림3. FInitArgs_CS 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceeZMN/btsFU0EQQEZ/Kk4ncD1DjtWEvEeUv78Y00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceeZMN%2FbtsFU0EQQEZ%2FKk4ncD1DjtWEvEeUv78Y00%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;968&quot; height=&quot;802&quot; data-origin-width=&quot;968&quot; data-origin-height=&quot;802&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림3. FInitArgs_CS 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;InitArgs 이후에 바로 MainPass Culling 코드가 등장합니다. 이 MainPass 는 PostPass 와 거의 동일한 코드를 사용하기 때문에 Main/Post Culling 패스는 같이 볼 수 있습니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.3.&amp;nbsp;Main/Post&amp;nbsp;Culling&amp;nbsp;전체&amp;nbsp;코드&amp;nbsp;레이아웃&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 MainPass Culling 의 CPU 측 코드를 확인해봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. MainAndPostNodesAndClusterBatchesBuffer 를 생성합니다. 이 버퍼는 명세가 바뀌지 않는 이상 한번 만들어두고 계속 재활용 합니다. 버퍼의 총 크기는 4 byte * (MaxClusterBatches * 2 + MaxNodes * (2 + 3)) 크기로 생성합니다.&lt;br /&gt;-&amp;nbsp;각&amp;nbsp;요소를&amp;nbsp;분석해보면,&amp;nbsp;MaxClusterBatches&amp;nbsp;*&amp;nbsp;2&amp;nbsp;는&amp;nbsp;ClusterBatch&amp;nbsp;에&amp;nbsp;클러스터의&amp;nbsp;개수를&amp;nbsp;기록하기&amp;nbsp;위한&amp;nbsp;버퍼를&amp;nbsp;위한&amp;nbsp;공간입니다.&amp;nbsp;*&amp;nbsp;2&amp;nbsp;인&amp;nbsp;이유는&amp;nbsp;Main/Post&amp;nbsp;모두에&amp;nbsp;사용하기&amp;nbsp;때문입니다. &lt;br /&gt;-&amp;nbsp;MaxNodes&amp;nbsp;*&amp;nbsp;(2&amp;nbsp;+&amp;nbsp;3)&amp;nbsp;는&amp;nbsp;MainPass&amp;nbsp;에서&amp;nbsp;Node&amp;nbsp;저장시에는&amp;nbsp;uint&amp;nbsp;2개,&amp;nbsp;Post&amp;nbsp;에서는&amp;nbsp;uint&amp;nbsp;3개를&amp;nbsp;사용하기&amp;nbsp;때문입니다.&lt;br /&gt;- (그림11 버퍼 레이아웃 그림 참고) &lt;br /&gt;2. MainAndPostCandididateClustersBuffer 를 생성합니다. 이 버퍼에는 FVisibleCluster 를 패킹해서 저장합니다. 이 때 패킹된 VisibleCluster 의 크기는 8 입니다(GetCandidateClusterSize() 함수 참고). 그래서 GetMaxCandidateClusters() * 2 * 4 가 됩니다. MainPass 의 CandidateCluster 의 경우 인덱스 0 부터, PostPass 의 경우는 뒤에서 부터 인덱스가 자라납니다. &lt;br /&gt;3. Nanite 의 핵심 기능에 집중하기 위해서 Nanite 테셀레이션 기능은 이번 리뷰에서는 제외하고 진행합니다. AddPass_PrimitiveFilter 함수는 추가로 필터링 할 프리미티브들을 등록할 버퍼를 생성합니다만 중요한 내용은 아니므로 넘어가겠습니다. &lt;br /&gt;4.&amp;nbsp;AddPass_InstanceHierarchyAndClusterCull&amp;nbsp;에서는&amp;nbsp;드디어&amp;nbsp;MainPass&amp;nbsp;를&amp;nbsp;진행합니다. &lt;br /&gt;5.&amp;nbsp;4번&amp;nbsp;과정에서&amp;nbsp;살아남은&amp;nbsp;클러스터를&amp;nbsp;기반으로&amp;nbsp;레스터라이즈를&amp;nbsp;진행합니다. &lt;br /&gt;6.&amp;nbsp;TwoPassOcclusionCulling&amp;nbsp;을&amp;nbsp;진행하고&amp;nbsp;있기&amp;nbsp;때문에&amp;nbsp;조건문&amp;nbsp;안으로&amp;nbsp;진입합니다. &lt;br /&gt;7.&amp;nbsp;MainPass&amp;nbsp;에서&amp;nbsp;레스터라이즈&amp;nbsp;한&amp;nbsp;결과로&amp;nbsp;생성된&amp;nbsp;DepthBuffer&amp;nbsp;를&amp;nbsp;사용하여&amp;nbsp;HZB&amp;nbsp;를&amp;nbsp;생성합니다.&amp;nbsp;(MainPass&amp;nbsp;에서는&amp;nbsp;이전&amp;nbsp;프레임의&amp;nbsp;HZB&amp;nbsp;를&amp;nbsp;기준으로&amp;nbsp;컬링&amp;nbsp;후&amp;nbsp;레스터라이즈&amp;nbsp;진행) &lt;br /&gt;8.&amp;nbsp;새로&amp;nbsp;생성한&amp;nbsp;이번&amp;nbsp;프레임&amp;nbsp;기준&amp;nbsp;HZB&amp;nbsp;를&amp;nbsp;사용하여&amp;nbsp;컬링을&amp;nbsp;수행합니다.&amp;nbsp;그리고&amp;nbsp;컬링&amp;nbsp;후&amp;nbsp;살아남은&amp;nbsp;클러스터에&amp;nbsp;레지스터라이즈를&amp;nbsp;진행합니다. &lt;br /&gt;9. NANITE_RENDER_FLAG_HAS_PREV_DRAW_DATA 관련 내용은 보지 않을 것이기 때문에 무시하며, DrawPassIndex 는 항상 0으로 둡니다.&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-origin-width=&quot;1306&quot; data-origin-height=&quot;2294&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WHVc1/btsFTZ0URHx/2mnkpnu3KdFuIkf2IgJNfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WHVc1/btsFTZ0URHx/2mnkpnu3KdFuIkf2IgJNfK/img.png&quot; data-alt=&quot;그림4. Main/Post Pass 컬링의 전체 레이아웃 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WHVc1/btsFTZ0URHx/2mnkpnu3KdFuIkf2IgJNfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWHVc1%2FbtsFTZ0URHx%2F2mnkpnu3KdFuIkf2IgJNfK%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;1306&quot; height=&quot;2294&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;2294&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림4. Main/Post Pass 컬링의 전체 레이아웃 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.3.1.&amp;nbsp;AddPassInitNodesAndClusterBatchesUAV&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼&amp;nbsp;이제&amp;nbsp;Main/Post&amp;nbsp;Culling&amp;nbsp;의&amp;nbsp;전체&amp;nbsp;코드&amp;nbsp;레이아웃에서&amp;nbsp;본&amp;nbsp;코드들의&amp;nbsp;세부사항을&amp;nbsp;뜯어보도록&amp;nbsp;합시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 먼저 AddPassInitNodesAndClusterBatchesUAV 함수로 진입합니다. 이 함수는 MainAndPostNodesAndClusterBatches 버퍼 생성 시 딱 한번 실행됩니다.&lt;br /&gt;2. OutMainAndPostNodesAndClusterBatches 에 생성한 버퍼를 바인딩하고 MaxCandidateClusters 와 MaxNodes 수를 설정하고 Compute Shader 를 실행합니다. 4번 항목에서 볼 InitCandidateNodes 를 사용하여 Node 관련 내용을 초기화 합니다. GetGroupCountWrapped 를 사용하여 MaxNodes 가 64 개당 1개의 WorkGroup 으로 묶일 수 있도록 WorkGroup 을 조정하여 실행합니다.&lt;br /&gt;3. 이전과 같은 데이터를 바인딩하고 Compute Shader 를 실행합니다. 이번에는 12번 항목에서 볼 InitClusterBatches 를 실행하여 Cluster 관련 내용을 초기화 합니다. 마찬가지로 WorkGroup 이 64 개의 Thread 를 가질 수 있도록 GetGroupCountWrapped 를 사용합니다.&lt;br /&gt;4.&amp;nbsp;InitCandidateNodes&amp;nbsp;의&amp;nbsp;Compute&amp;nbsp;shader&amp;nbsp;는&amp;nbsp;WorkGroup&amp;nbsp;당&amp;nbsp;64&amp;nbsp;개의&amp;nbsp;thread&amp;nbsp;를&amp;nbsp;다룹니다.&lt;br /&gt;5. Shader 실행시 전달받은 데이터를 GetUnWrappedDispatchThreadId 함수에 넘겨서 사용하여 Node 인덱스로 변환합니다. 이 인덱스는 0 ~ (MaxNodes-1) 입니다. 내부에서는 ClearCandidateNode 를 두 번 호출하는데 각각은 Main/Post 패스에 대해서 호출하는 것 입니다.&lt;br /&gt;6.&amp;nbsp;ClearCandidateNode&amp;nbsp;내부&amp;nbsp;구현도&amp;nbsp;한번&amp;nbsp;봅시다.&lt;br /&gt;7. StoreCandidateNodeData 함수를 호출하여 MainAndPostNodesAndClusterBatches 버퍼의 NodeIndex 위치를 0xFFFFFFFF 로 초기화 합니다. 계속해서 내부로 들어가봅시다.&lt;br /&gt;8.&amp;nbsp;StoreCandidateNodeData&amp;nbsp;함수&amp;nbsp;내부에서&amp;nbsp;인덱스&amp;nbsp;계산도&amp;nbsp;확인해봅시다.&lt;br /&gt;9. Main/Post 여부에 따라서 Node 정보를 저장할 Offset을 만듭니다. 그리고 MainAndPostNodesAndClusterBatches 에 전달 받은 초기화 값을 저장합니다. 이 때 Main 의 경우 8 byte, Post 의 경우 12 byte 입니다. Main의 경우 uint 2 개, Post의 경우 uint 3 개를 사용하기 때문입니다.&lt;br /&gt;10. Shader 에 정의된 CandidateNode 와 CandidateCluster 의 크기입니다. 바로 전에 확인 한 것 처럼 노드의 경우 Main 은 uint 2 개, Post 는 uint 3 개 크기인 것을 알 수 있습니다. 그리고 CandidateClusterSize 는 8 byte 인 것을 확인할 수 있습니다.&lt;br /&gt;11. 여기서는 9번 과정에서 Offset 을 만들 때 사용한 함수들에 대해서 알아봅시다. 그때 사용한 함수는GetNodesAndBatchesOffset(bPostPass) + GetCandidateNodesOffset() 입니다. GetNodesAndBatchesOffset 는 Node 를 저장할 오프셋을 얻어오는 함수입니다. MainPass 의 Node 는 바로 0 Offset 에 저장되지 않습니다. MaxCandidateClusterBatch 다음 위치에서 저장을 시작합니다. PostPass 의 경우는 (GetCandidateNodesOffset() + MaxNodes * GetCandidateNodeSize(false)) + GetCandidateNodesOffset() 위치에서 저장을 시작합니다. PostPass 에서는 MainPass 에서 저장된 CandidateCluster 와 CandidateNode 의 저장 공간을 고려하고 그 뒤에 PostPass 에서 사용할CandidateClusterBatch Offset 을 두기 위해서 이렇게 합니다. 이 부분은 앞으로 코드를 보며 종종 나오기 때문에 여기서 확실히 정리하면 좋습니다. 앞서 설명한 버퍼의 전체 레이아웃은 그림11 에 추가했습니다.&lt;br /&gt;12.&amp;nbsp;다음은&amp;nbsp;InitClusterBatches&amp;nbsp;Compute&amp;nbsp;Shader&amp;nbsp;를&amp;nbsp;수행하여&amp;nbsp;Cluster&amp;nbsp;를&amp;nbsp;저장할&amp;nbsp;공간을&amp;nbsp;초기화&amp;nbsp;합니다.&lt;br /&gt;13. Node 와 마찬가지로 GetUnWrappedDispatchThreadId 함수에 WorkGroup 과 ThreadId 를 전달하여 Cluster 의 인덱스로 변환합니다. 그리고 ClearClusterBatch 함수를 Main/Post 함수에 대해서 각각 호출합니다.&lt;br /&gt;14. ClearClusterBatch 함수 내부로 들어가서 Offset 계산을 봅시다. 이런 과정을 한번 해두는 것이 MainAndPostNodesAndClusterBatches 의 메모리 레이아웃을 이해하는데 도움이 될 것입니다.&lt;br /&gt;15. Offset 생성을 먼저 확인해보면, GetClusterBatchesOffset 은 11번 코드를 보면 0이라는 것을 알 수 있습니다. 왜냐하면 Main/Post 의 ClusterBatch 정보가 Node 정보보다 앞쪽에 배치되기 때문입니다. GetNodesAndBatchesOffset 은 바로 위에서 확인했기 때문에 추가 설명하지 않겠습니다. 이렇게 ClusterBatch 정보도 ClusterIndex 별로 모두 초기화 하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;1583&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/daB8T1/btsFYFtM3nZ/pupF2MhwbIN4P2KkdXtNWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/daB8T1/btsFYFtM3nZ/pupF2MhwbIN4P2KkdXtNWK/img.png&quot; data-alt=&quot;그림5. MainAndPostNodesAndClusterBatches 버퍼 초기화 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/daB8T1/btsFYFtM3nZ/pupF2MhwbIN4P2KkdXtNWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdaB8T1%2FbtsFYFtM3nZ%2FpupF2MhwbIN4P2KkdXtNWK%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;1114&quot; height=&quot;1583&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;1583&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림5. MainAndPostNodesAndClusterBatches 버퍼 초기화 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3.3.2.&amp;nbsp;AddPass_InstanceHierarchyAndClusterCull&amp;nbsp;전체&amp;nbsp;레이아웃&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 컬링을 수행할 준비를 마쳤습니다. 컬링 과정도 여러 단계로 쪼개져 있기 때문에 전체 레이아웃을 먼저 파악해봅시다. 이 코드의 내용은 &lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Nanite(1/5)&lt;/span&gt;&lt;/a&gt;&amp;nbsp;의 그림1의&amp;nbsp;InstanceCulling,&amp;nbsp;Persistent&amp;nbsp;Hierarchy/Cluster&amp;nbsp;Culling&amp;nbsp;과&amp;nbsp;컬링&amp;nbsp;결과를&amp;nbsp;기반으로&amp;nbsp;레스터라이즈를&amp;nbsp;수행할&amp;nbsp;Indirect&amp;nbsp;command&amp;nbsp;argument&amp;nbsp;를&amp;nbsp;생성합니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1.&amp;nbsp;AddPass_InstanceHierarchyAndClusterCull&amp;nbsp;함수로&amp;nbsp;진입합니다.&lt;br /&gt;2.&amp;nbsp;DispatchInstanceCullPass&amp;nbsp;라는&amp;nbsp;람다함수를&amp;nbsp;만드는데&amp;nbsp;여기서&amp;nbsp;InstanceCulling&amp;nbsp;을&amp;nbsp;수행합니다.&amp;nbsp;함수&amp;nbsp;내부를&amp;nbsp;확인해봅시다.&lt;br /&gt;3. FInstanceCull_CS::FParameter 에 쉐이더에서 필요한 정보를 채웁니다. 총 인스턴스 수, 그리고 OccludedInstances, OccludedInstancesArgs 그리고 기타 정보를 채웁니다. 컬링 과정에서 이전 프레임의 HZB 도 들어가는데, CullingParameters 에 미리 바인딩 해뒀기 때문에 해당 부분은 보이지 않습니다. 참고로, OccludedInstances 에는 ViewId, InstanceId 가 기록되며, OccludedInstancesArgs 에는 [0] 에는 64 개를 한 그룹으로 했을 때 그룹의 총 개수, [3] 에는 Occluded 된 인스턴수가 기록됩니다. 이 때, Occluded 된 정보들은 이전 프레임의 HZB 를 기반으로 결정된 것이기 때문에 현재 프레임을 기반으로 HZB 가 만들어지면 한번 더 체크합니다. (이전 프레임에 없었다가 이번 프레임에 새로 등장한 인스턴스를 위해서)&lt;br /&gt;4.&amp;nbsp;필요한&amp;nbsp;Shader&amp;nbsp;permutation&amp;nbsp;정보를&amp;nbsp;설정합니다.&lt;br /&gt;5.&amp;nbsp;PostPass&amp;nbsp;의&amp;nbsp;경우&amp;nbsp;OccludedInstanceArgs&amp;nbsp;를&amp;nbsp;Indirect&amp;nbsp;command&amp;nbsp;argument&amp;nbsp;로&amp;nbsp;사용하여&amp;nbsp;InstanceCulling&amp;nbsp;을&amp;nbsp;수행합니다.&amp;nbsp;이전&amp;nbsp;프레임에&amp;nbsp;없었다가&amp;nbsp;새로&amp;nbsp;보이는&amp;nbsp;인스턴스&amp;nbsp;재확인&amp;nbsp;과정이며,&amp;nbsp;그림1의&amp;nbsp;PostPass&amp;nbsp;의&amp;nbsp;InstanceCulling&amp;nbsp;에&amp;nbsp;해당합니다.&lt;br /&gt;6. MainPass 의 InstanceCulling 입니다. 여기서는 현재 할당되어있는 인스턴스 개수를 64 개의 그룹으로 묶어서 WorkGroup 을 생성합니다. 아마도 Compute Shader 에서는 64 개의 thread 가 culling 을 위해 배칭될 것으로 예상됩니다.&lt;br /&gt;7.&amp;nbsp;바로&amp;nbsp;위에서&amp;nbsp;확인한&amp;nbsp;람다함수를&amp;nbsp;실제로&amp;nbsp;호출합니다.&amp;nbsp;이&amp;nbsp;때&amp;nbsp;전달된&amp;nbsp;InstanceWorkGroupParameters&amp;nbsp;는&amp;nbsp;기본&amp;nbsp;설정에서는&amp;nbsp;유효하지&amp;nbsp;않기&amp;nbsp;때문에&amp;nbsp;관련해서&amp;nbsp;추적하지는&amp;nbsp;않을&amp;nbsp;것입니다.&lt;br /&gt;8. AddPass_NodeAndClusterCull 함수를 호출합니다. 이 함수가 바로 &lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_self&quot;&gt;&lt;span&gt;Nanite(1/5)&lt;/span&gt;&lt;/a&gt;&amp;nbsp;의 그림1 의 Persistent Hierarchy/Cluster culling 입니다. 여기까지 오면 컬링에서 살아남은 Instance 의 Root node 정보들이 수집한 상태입니다. 그리고 해당 Node 를 순회하여 Visible Cluster 를 선별합니다. Nanite 코드 중에 이 부분이 가장 재미있던 것 같습니다. 해당 부분은 Compute Shader 를 thread pool 처럼 활용하여 노드 순회와 클러스터 컬링을 순회합니다.&lt;br /&gt;9.&amp;nbsp;컬링을&amp;nbsp;마치고&amp;nbsp;얻어진&amp;nbsp;Cluster&amp;nbsp;들을&amp;nbsp;레스터라이즈&amp;nbsp;시켜야&amp;nbsp;하기&amp;nbsp;때문에&amp;nbsp;레지스터라이즈를&amp;nbsp;위해&amp;nbsp;필요한&amp;nbsp;버퍼들을&amp;nbsp;설정합니다.&lt;br /&gt;10.&amp;nbsp;Main/Post&amp;nbsp;패스&amp;nbsp;여부에&amp;nbsp;대한&amp;nbsp;Permutation&amp;nbsp;을&amp;nbsp;설정합니다.&lt;br /&gt;11.&amp;nbsp;CalculateSafeRasterizerArgs&amp;nbsp;ComputeShader&amp;nbsp;를&amp;nbsp;실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;2444&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/U1YQ9/btsFRzWbpPQ/wVDMs0K9oAFQPkFjaIyOV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/U1YQ9/btsFRzWbpPQ/wVDMs0K9oAFQPkFjaIyOV0/img.png&quot; data-alt=&quot;그림6.&amp;amp;amp;amp;amp;nbsp; AddPass_InstanceHierarchyAndClusterCull 의 전체 레이아웃 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/U1YQ9/btsFRzWbpPQ/wVDMs0K9oAFQPkFjaIyOV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FU1YQ9%2FbtsFRzWbpPQ%2FwVDMs0K9oAFQPkFjaIyOV0%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;1146&quot; height=&quot;2444&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;2444&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림6.&amp;amp;amp;amp;nbsp; AddPass_InstanceHierarchyAndClusterCull 의 전체 레이아웃 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;3.3.2.1.&amp;nbsp;FInstanceCull_CS&amp;nbsp;(MainPass)&lt;/b&gt;&lt;br /&gt;InstanceCulling&amp;nbsp;의&amp;nbsp;Compute&amp;nbsp;Shader&amp;nbsp;에&amp;nbsp;바로&amp;nbsp;진입해봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1.&amp;nbsp;64&amp;nbsp;개의&amp;nbsp;인스턴스를&amp;nbsp;배치로&amp;nbsp;수행합니다.&lt;br /&gt;2.&amp;nbsp;GroupId&amp;nbsp;와&amp;nbsp;ThreadIndex&amp;nbsp;로&amp;nbsp;부터&amp;nbsp;DispatchIndex&amp;nbsp;를&amp;nbsp;얻어옵니다.&amp;nbsp;이&amp;nbsp;값은&amp;nbsp;&amp;ldquo;0&amp;nbsp;~&amp;nbsp;인스턴스&amp;nbsp;개수&amp;rdquo;&amp;nbsp;범위이며,&amp;nbsp;InstanceId&amp;nbsp;와&amp;nbsp;매칭됩니다.&lt;br /&gt;3.&amp;nbsp;Main/Post&amp;nbsp;Pass&amp;nbsp;여부&amp;nbsp;플래그를&amp;nbsp;생성합니다.&lt;br /&gt;4. MainPass 의 경우 전체 인스턴스 개수인 NumInstances 가 컬링 테스트 할 인스턴스 수가 되며, PostPass 에서는 Occluded 된 인스턴스를 재 확인하는 과정이기 때문에 OccludedInstancesArgs[3] 에서 컬링 테스트 할 인스턴스 수를 얻습니다. 이전에 이야기 한 것 처럼 OccludedInstancesArgs[3] 에는 총 Occluded 된 인스턴스의 수가 들어갑니다.&lt;br /&gt;5.&amp;nbsp;처리하려는&amp;nbsp;InstanceId&amp;nbsp;가&amp;nbsp;최대&amp;nbsp;범위를&amp;nbsp;넘어가는지&amp;nbsp;확인합니다.&lt;br /&gt;6. MainPass 의 경우는 DispatchIndex 가 바로 InstanceId 입니다. 하지만 Occluded 된 인스턴스의 경우는 Id 를 OccludedInstances 버퍼에서 얻어와야 할 것입니다. InInstanceDraws 버퍼가 PostPass 에서는 바로 OccludedInstances 입니다.&lt;br /&gt;7.&amp;nbsp;LoadInstancePrimitiveIdAndFlags&amp;nbsp;함수는&amp;nbsp;InstanceId&amp;nbsp;로&amp;nbsp;부터&amp;nbsp;PrimitiveId&amp;nbsp;와&amp;nbsp;InstanceFlags&amp;nbsp;정보를&amp;nbsp;얻어옵니다.&amp;nbsp;바로&amp;nbsp;옆에&amp;nbsp;있는&amp;nbsp;초록색&amp;nbsp;네모에&amp;nbsp;함수&amp;nbsp;내부가&amp;nbsp;있습니다.&lt;br /&gt;8. InstanceId 로 부터 Instance 정보를 얻어옵니다. FInstanceSceneData 에 대한 자세한 내용은 &lt;a href=&quot;https://scahp.tistory.com/85&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;레퍼런스5&lt;/span&gt;&lt;/a&gt;&amp;nbsp;에&amp;nbsp;있습니다.&lt;br /&gt;9.&amp;nbsp;FPrimitiveSeneData&amp;nbsp;를&amp;nbsp;얻어옵니다.&amp;nbsp;이것에&amp;nbsp;대한&amp;nbsp;자세한&amp;nbsp;내용&amp;nbsp;또한 &lt;a href=&quot;https://scahp.tistory.com/84&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;레퍼런스6&lt;/span&gt;&lt;/a&gt;&amp;nbsp;에&amp;nbsp;있습니다.&lt;br /&gt;10. Filtering 할 프리미티브 인지 확인하고 그렇다면 바로 컬링 해버립니다. PostPass 에서 재확인 할 필요도 없기 때문에 더이상의 처리를 하지 않습니다.&lt;br /&gt;11. IsPrimitiveShown 함수에서는 PrimitiveData.Flag 와 RenderFlag 를 사용하여 프리미티브가 Visibility 를 체크합니다. 그림8 에서 내부를 확인해봅시다.&lt;br /&gt;12. 현재 인스턴스를 각각의 View 에 대해서 테스트합니다. 우리는 일단 View 가 1개로 가정하고 계속 코드를 봅시다.&lt;br /&gt;13.&amp;nbsp;FInstanceDynamicData&amp;nbsp;를&amp;nbsp;만듭니다.&amp;nbsp;이&amp;nbsp;것은&amp;nbsp;현재&amp;nbsp;처리&amp;nbsp;중인&amp;nbsp;인스턴스를&amp;nbsp;현재&amp;nbsp;View&amp;nbsp;에&amp;nbsp;맞게&amp;nbsp;Transform&amp;nbsp;시킨&amp;nbsp;정보를&amp;nbsp;캐싱&amp;nbsp;합니다.&amp;nbsp;코드&amp;nbsp;바로&amp;nbsp;우측&amp;nbsp;상단&amp;nbsp;초록색&amp;nbsp;네모에&amp;nbsp;함수의&amp;nbsp;내부가&amp;nbsp;있습니다.&lt;br /&gt;14.&amp;nbsp;HZB&amp;nbsp;에&amp;nbsp;컬링을&amp;nbsp;수행하기&amp;nbsp;위해서&amp;nbsp;FBoxCull&amp;nbsp;객체를&amp;nbsp;생성합니다.&amp;nbsp;View&amp;nbsp;와&amp;nbsp;BoundBox&amp;nbsp;와&amp;nbsp;Location&amp;nbsp;정보&amp;nbsp;등이&amp;nbsp;설정됩니다.&amp;nbsp;PostPass&amp;nbsp;의&amp;nbsp;경우는&amp;nbsp;MainPass&amp;nbsp;에서&amp;nbsp;이미&amp;nbsp;수행했고&amp;nbsp;변하지&amp;nbsp;않는&amp;nbsp;컬링&amp;nbsp;정보인&amp;nbsp;FrustumCull,&amp;nbsp;GlobalClipPlaneCull&amp;nbsp;을&amp;nbsp;수행하지&amp;nbsp;않도록&amp;nbsp;설정합니다.&lt;br /&gt;15. DistanceCulling 을 수행합니다. WPO 를 사용하고 있다면 bHasMoved 를 true 로 설정합니다. (GetGPUSceneFrameNumber() == InstanceData.LastUpdateSceneFrameNumber) 이 경우도 bHasMoved 로 설정되는데, GPUScene 에 인스턴스 데이터가 업데이트 된 경우에 true 가 되며 이때 이동했다고 판정합니다. 그림9 에서 함수 내부를 확인해봅시다.&lt;br /&gt;16. GlobalClipPlaneCull 을 수행합니다. GlobalClipPlaneCull 의 세부사항은 분량 조절을 위해 여기서 다루지 않겠습니다.&lt;br /&gt;17. 아직도 Visible 상태라면 FrustumCull 과 HZB Culling 을 수행합니다. 이 부분 또한 분량 조절을 위해 여기서 다루지 않겠습니다.&lt;br /&gt;18.&amp;nbsp;MainPass&amp;nbsp;의&amp;nbsp;경우&amp;nbsp;Occluded&amp;nbsp;되었다면,&amp;nbsp;WriteOccludedInstance&amp;nbsp;함수를&amp;nbsp;사용하여&amp;nbsp;해당&amp;nbsp;인스턴스가&amp;nbsp;Occluded&amp;nbsp;되었다고&amp;nbsp;설정합니다.&amp;nbsp;그리고&amp;nbsp;PostPass&amp;nbsp;에서&amp;nbsp;Occluded&amp;nbsp;인스턴스에&amp;nbsp;대해서&amp;nbsp;현재&amp;nbsp;프레임의&amp;nbsp;HZB&amp;nbsp;를&amp;nbsp;사용하여&amp;nbsp;다시&amp;nbsp;컬링을&amp;nbsp;수행할&amp;nbsp;것입니다.&lt;br /&gt;19. 이 부분은 임포스터로 렌더링할지 여부를 결정하는 부분입니다. 임포스터로 렌더링 할 정도로 크기가 작다면, DrawImposter 로 임포스터로 렌더링 될 수 있도록하고 Visible 을 false 로 설정하는 것을 볼 수 있습니다. 임포스터의 렌더링 과정까지 보면 코드를 볼 범위가 너무 커질 것 같아서 생략합니다.&lt;br /&gt;20. 인스턴스가 Visible 이고, Occluded 되지도 않았다면 QueueState 에 인스턴스의 Root Node 를 기록합니다.&lt;br /&gt;21. 각 스레드의 VGPR 변수 NodeOffset 에 Node 를 기록할 Offset 을 담습니다. QueueState 의 MainPass 는 QueueStateIndex 0 번 인덱스, PostPass 는 1 번 인덱스에 Node 읽기/쓰기에 대한 Offset 정보 및 총 Node 수를 담습니다.&amp;nbsp;&amp;nbsp;WaveInterlockedAddScalar, WaveInterlockedAddScalar_ 두 함수도 앞으로 자주보게 될 함수입니다. 이 함수는 Wave intrinsic 을 사용하여 64 개의 thread가 각자의 위치의 NodeOffset 를 Atomic 연산 1회만으로 동시에 받을 수 있게 해줍니다. 각각의 Wave 함수가 하는 일을 알아봅시다.&lt;br /&gt;- WaveInterlockedAddScalar(NodeCount, 1) 는 Active thread 의 개수 당 1 씩 NodeCount 를 증가시킵니다. 즉 Active thread 가 64 개 중 10 개라면 NodeCount 는 10이 증가합니다.&lt;br /&gt;- WaveInterlockedAddScalar_(NodeWriteOffset, 1, NodeOffset) 은&amp;nbsp;&amp;nbsp;Active thread 의 개수 당 1 씩 NodeWriteOffset 을 증가 시킵니다. 그리고 NodeWriteOffset + Active thread index(64 개 범위에서) 를 더해서 돌려줍니다. 만약 NodeWriteOffset 이 15 였고, Active thread 가 10 였다고 합시다. 그러면 각각의 Active thread 0~9 는 NodeOffset 에 15, 16, 17&amp;hellip;24 를 담아서 돌려받습니다.&lt;br /&gt;- Wave intrinsic 에 대한 자세한 내용은 &lt;a href=&quot;https://github.com/Microsoft/DirectXShaderCompiler/wiki/Wave-Intrinsics&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;레퍼런스7&lt;/span&gt;&lt;/a&gt;&amp;nbsp;링크에서&amp;nbsp;알아&amp;nbsp;볼&amp;nbsp;수&amp;nbsp;있습니다.&lt;br /&gt;22. 현재 처리중인 Node 가 최대 Node 개수를 초과하지 않았는지 확인합니다. 그렇지 않다면 FCandidateNode 객체에 인스턴스 정보를 담습니다. 그리고 StoreCandidateNode 함수를 사용하여 MainAndPostNodesAndClusterBatches 버퍼에 저장합니다. 그림10 에서 내부를 확인해봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;3020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJqrbM/btsFRuUFkI2/jMoRWN7UuhMuowNfUBbW2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJqrbM/btsFRuUFkI2/jMoRWN7UuhMuowNfUBbW2k/img.png&quot; data-alt=&quot;그림7. InstanceCull 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJqrbM/btsFRuUFkI2/jMoRWN7UuhMuowNfUBbW2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJqrbM%2FbtsFRuUFkI2%2FjMoRWN7UuhMuowNfUBbW2k%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;1456&quot; height=&quot;3020&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;3020&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림7. InstanceCull 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;IsPrimitiveShown 함수 내부 입니다.&lt;br /&gt;11.1.&amp;nbsp;Flag&amp;nbsp;를&amp;nbsp;사용하여&amp;nbsp;bool&amp;nbsp;상태&amp;nbsp;변수를&amp;nbsp;생성합니다.&lt;br /&gt;11.2.&amp;nbsp;생성한&amp;nbsp;bool&amp;nbsp;상태&amp;nbsp;변수를&amp;nbsp;기반으로&amp;nbsp;Visible&amp;nbsp;여부를&amp;nbsp;리턴합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;1201&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ch5HpX/btsFR9irIvr/K2Kqw3D2TFoKfY3JuEEdJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ch5HpX/btsFR9irIvr/K2Kqw3D2TFoKfY3JuEEdJK/img.png&quot; data-alt=&quot;그림8. IsPrimitiveShown 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ch5HpX/btsFR9irIvr/K2Kqw3D2TFoKfY3JuEEdJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fch5HpX%2FbtsFR9irIvr%2FK2Kqw3D2TFoKfY3JuEEdJK%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;1140&quot; height=&quot;1201&quot; data-origin-width=&quot;1140&quot; data-origin-height=&quot;1201&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림8. IsPrimitiveShown 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;Distance 함수 내부를 확인해봅시다.&lt;br /&gt;15.1.&amp;nbsp;DistanceCull&amp;nbsp;의&amp;nbsp;내부로&amp;nbsp;들어가봅시다.&lt;br /&gt;15.2.&amp;nbsp;WPO&amp;nbsp;를&amp;nbsp;사용하는지?&amp;nbsp;WPO&amp;nbsp;를&amp;nbsp;거리에&amp;nbsp;따라서&amp;nbsp;끄는&amp;nbsp;기능을&amp;nbsp;사용하는지?&amp;nbsp;거리에&amp;nbsp;따라&amp;nbsp;Draw&amp;nbsp;여부를&amp;nbsp;결정하는지?&amp;nbsp;에&amp;nbsp;대한&amp;nbsp;플래그를&amp;nbsp;확인합니다.&lt;br /&gt;15.3.&amp;nbsp;현재&amp;nbsp;인스턴스가&amp;nbsp;Visible&amp;nbsp;이면&amp;nbsp;Distance&amp;nbsp;Culling&amp;nbsp;을&amp;nbsp;수행합니다.&lt;br /&gt;15.4.&amp;nbsp;View&amp;nbsp;당&amp;nbsp;설정되는&amp;nbsp;값&amp;nbsp;기반으로&amp;nbsp;Camera(View)&amp;nbsp;에서&amp;nbsp;일정거리&amp;nbsp;이상&amp;nbsp;떨어진&amp;nbsp;인스턴스를&amp;nbsp;컬링합니다.&lt;br /&gt;15.5.&amp;nbsp;인스턴스&amp;nbsp;당&amp;nbsp;설정되는&amp;nbsp;값&amp;nbsp;기반으로&amp;nbsp;Camera(View)&amp;nbsp;의&amp;nbsp;위치를&amp;nbsp;기반으로&amp;nbsp;인스턴스가&amp;nbsp;충분히&amp;nbsp;멀다면&amp;nbsp;컬링을&amp;nbsp;수행&amp;nbsp;합니다.&lt;br /&gt;15.6.&amp;nbsp;WPO&amp;nbsp;Disable&amp;nbsp;기능을&amp;nbsp;사용하는&amp;nbsp;경우&amp;nbsp;충분히&amp;nbsp;거리가&amp;nbsp;멀다면&amp;nbsp;WPO&amp;nbsp;를&amp;nbsp;꺼줍니다.&lt;br /&gt;15.7.&amp;nbsp;리턴값은&amp;nbsp;WPO&amp;nbsp;를&amp;nbsp;사용하고&amp;nbsp;Visible&amp;nbsp;이면&amp;nbsp;true&amp;nbsp;입니다.&amp;nbsp;Invisible&amp;nbsp;이면&amp;nbsp;WPO&amp;nbsp;를&amp;nbsp;사용하지&amp;nbsp;않는&amp;nbsp;것으로&amp;nbsp;합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1045&quot; data-origin-height=&quot;806&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYWWQc/btsFRC6rrd8/rZ80qoUf0Cs1BvVdmY6OK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYWWQc/btsFRC6rrd8/rZ80qoUf0Cs1BvVdmY6OK0/img.png&quot; data-alt=&quot;그림9. DistanceCull 을 수행하는 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYWWQc/btsFRC6rrd8/rZ80qoUf0Cs1BvVdmY6OK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYWWQc%2FbtsFRC6rrd8%2FrZ80qoUf0Cs1BvVdmY6OK0%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;1045&quot; height=&quot;806&quot; data-origin-width=&quot;1045&quot; data-origin-height=&quot;806&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림9. DistanceCull 을 수행하는 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;StoreCandidateNode 함수의 내부를 확인해봅시다.&lt;br /&gt;22.1. StoreCandidateNode 의 인덱스 계산을 한번 확인해봅시다. 여기서는 FCandidateNode 를 패킹하여 StoreCandidateNodeData 에 전달합니다.&lt;br /&gt;22.2. GetNodesAndBatchesOffset 과 GetCandidateNodesOffset 함수를 사용하여 Main/Post 패스에 맞는 버퍼 Offset 을 저장합니다. MainAndPostNodesAndClusterBatches 버퍼에 Cluster 저장 공간을 사용하게 됩니다.&lt;br /&gt;22.3.&amp;nbsp;CandidateNode&amp;nbsp;의&amp;nbsp;인덱스&amp;nbsp;계산에&amp;nbsp;필요한&amp;nbsp;함수들입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ldnoE/btsFUJDgf62/OJoZktwWeqNyTq9HL7f3N1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ldnoE/btsFUJDgf62/OJoZktwWeqNyTq9HL7f3N1/img.png&quot; data-alt=&quot;그림10. CandidateNode 를 저장하는 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ldnoE/btsFUJDgf62/OJoZktwWeqNyTq9HL7f3N1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FldnoE%2FbtsFUJDgf62%2FOJoZktwWeqNyTq9HL7f3N1%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;1139&quot; height=&quot;500&quot; data-origin-width=&quot;1139&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림10. CandidateNode 를 저장하는 쉐이더 코드 (출처 : 레퍼런스1)&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D4znk/btsFYoeJKnE/Yx7MeKVgKE5chal2Oq2vm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D4znk/btsFYoeJKnE/Yx7MeKVgKE5chal2Oq2vm1/img.png&quot; data-alt=&quot;그림11. MainAndPostNodesAndClusterBatches 버퍼 레이아웃 (출처 : 직접 그림)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D4znk/btsFYoeJKnE/Yx7MeKVgKE5chal2Oq2vm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD4znk%2FbtsFYoeJKnE%2FYx7MeKVgKE5chal2Oq2vm1%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;609&quot; height=&quot;240&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림11. MainAndPostNodesAndClusterBatches 버퍼 레이아웃 (출처 : 직접 그림)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;3.3.2.2.&amp;nbsp;AddPass_NodeAndClusterCull&amp;nbsp;(MainPass)&amp;nbsp;전체&amp;nbsp;레이아웃&lt;/b&gt;&lt;br /&gt;이제 &lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Nanite(1/5)&lt;/span&gt;&lt;/a&gt;&amp;nbsp;의 그림1 에 나온 Persistent Hierarchy/Cluster Culling 단계입니다. 이 과정 또한 여러 단계로 나뉘기 때문에 전체 레이아웃을 한번 확인해보고 세부 사항을 보도록 합시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 우리가 보는 코드는 NanitePersistentThreadsCulling 을 사용합니다. 이 Compute Shader 는 이름에서 알 수 있듯이 생성한 Thread 를 계속해서 유지한 채로 스레드 풀을 유지하면서 작업할 일감이 담긴 큐가 바닥날 때까지 계속 작업을 수행합니다.&lt;br /&gt;2.&amp;nbsp;AddPass_NodeAndClusterCull&amp;nbsp;함수&amp;nbsp;내부로&amp;nbsp;들어가봅시다.&lt;br /&gt;3. 앞서 데이터를 채워넣었던 QueueState 와 MainAndPostNodesAndClusterBatchesBuffer 를 바인딩 합니다. MainPass 를 막 마친 상태라면 MainAndPostNodesAndClusterBatchesBuffer 에는 현재 Node 루트 정보만 담겨있을 것입니다. MainAndPostCandididateClusters 에는 PackedVisibleCluster 를 저장 할 버퍼로 Culling 을 모두 마친 Visible Cluster 가 저장됩니다.&lt;br /&gt;4. 이번 패스에서 사용할 VisibleClustersArgsSWHW 를 바인딩하는데, MainPass 에서는 MainRasterizeArgsSWHW 를 바인딩 합니다. 이 버퍼는 NANITE_RASTERIZER_ARG_COUNT(8) 크기로 만들었었습니다. SW 를 위해서 3 개를 사용하고 1개는 패딩, HW 를 위해서 4~7 인덱스를 사용합니다.&lt;br /&gt;-&amp;nbsp;RasterizerArgsSWHW[0~3]&amp;nbsp;=&amp;nbsp;(NumClustersSW,&amp;nbsp;1,&amp;nbsp;1,&amp;nbsp;0)&lt;br /&gt;-&amp;nbsp;RasterizerArgsSWHW[4~7]&amp;nbsp;=&amp;nbsp;(Max&amp;nbsp;Cluster&amp;nbsp;Triangle&amp;nbsp;(128개),&amp;nbsp;NumClustersHW,&amp;nbsp;0,&amp;nbsp;0)&amp;nbsp;//&amp;nbsp;Mesh&amp;nbsp;or&amp;nbsp;Primitive&amp;nbsp;shader&amp;nbsp;가&amp;nbsp;아닌&amp;nbsp;경우&lt;br /&gt;5. 계속해서 PostPass 의 경우 VisibleClustersArgsSWHW 바인딩에 관한 코드입니다. 여기서는 바인딩 하는 버퍼를 MainPass 와는 조금 다르게 합니다. OffsetClustersArgsSWHW 에 MainRasterizeArgsSWHW 를 넣고, VisibleClustersArgsSWHW 에는 PostRasterizeArgsSWHW 를 바인딩 합니다. PostPass 에서는 ClusterStore 를 수행할 때, MainPass 의 기록 뒤쪽에 붙여서 기록하기 위해서 MainRasterizeArgsSWHW 에서 사용한 SW, HW Cluster 의 수를 활용합니다. 그래서 OffsetClustersArgsSWHW 에 MainRasterizeArgsSWHW 를 바인딩 합니다.&lt;br /&gt;6. VisibleClustersSWHW 를 바인딩합니다. 이 버퍼는 컬링 과정을 마치고 렌더링 할 클러스터를 담습니다. FVisibleCluster 를 PackedVisibleCluster 로 만들어서 저장하며, VSM 이 아닌 경우 8 byte, VSM 은 12 byte 입니다. FVisibleClusters 의 주요 정보는 ViewId, InstanceId, ClusterIndex 입니다. SW 는 인덱스 0 부터 자라나며, HW FVisibleCluster 는 인덱스가 맨 마지막 인덱스에서 거꾸로 자라납니다. GPU 에서 클러스터 데이터를 스트리밍 요청하기 위한 StreamingRequests 버퍼도 여기서 바인딩 합니다.&lt;br /&gt;7.&amp;nbsp;NodeAndClusterCull&amp;nbsp;을&amp;nbsp;하기&amp;nbsp;위해&amp;nbsp;필요한&amp;nbsp;Permutation&amp;nbsp;정보를&amp;nbsp;설정합니다.&lt;br /&gt;8.&amp;nbsp;NodeAndClusterCull&amp;nbsp;을&amp;nbsp;수행합니다.&amp;nbsp;주목할&amp;nbsp;부분은&amp;nbsp;GRHIPersistentThreadGroupCount&amp;nbsp;인데,&amp;nbsp;DX12&amp;nbsp;에서는&amp;nbsp;1440&amp;nbsp;입니다.&amp;nbsp;이&amp;nbsp;값은&amp;nbsp;플랫폼&amp;nbsp;마다&amp;nbsp;조금씩&amp;nbsp;다르며,&amp;nbsp;1440&amp;nbsp;개의&amp;nbsp;WorkGroup&amp;nbsp;이&amp;nbsp;ThreadPool&amp;nbsp;처럼&amp;nbsp;동작하게&amp;nbsp;됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;1852&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OLroW/btsFXb7cRqy/Rw6wJ7cdmdH8bKgCYdMnC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OLroW/btsFXb7cRqy/Rw6wJ7cdmdH8bKgCYdMnC1/img.png&quot; data-alt=&quot;그림12.AddPass_NodeAndClusterCull 전체 레이아웃 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OLroW/btsFXb7cRqy/Rw6wJ7cdmdH8bKgCYdMnC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOLroW%2FbtsFXb7cRqy%2FRw6wJ7cdmdH8bKgCYdMnC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1030&quot; height=&quot;1852&quot; data-origin-width=&quot;1030&quot; data-origin-height=&quot;1852&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림12.AddPass_NodeAndClusterCull 전체 레이아웃 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이제 Persistent Hierarchy / Cluster Culling 의 CPU 측 코드 확인을 마쳤습니다. 계속해서 Compute Shader 쪽의 전체 레이아웃을 확인해봅시다. &lt;br /&gt;1. NodeAndClusterCull 은 WorkGroup 당 NANITE_PERSISTENT_CLUSTER_CULLING_GROUP_SIZE(64) 개의 thread 를 수행 합니다. GroupIndex : SV_GroupIndex 는 64개의 thread 에 0~63 의 인덱스를 각각 전달합니다.&amp;nbsp;&lt;br /&gt;2. 우리가 볼 Culling 방식은 PersistentNodeAndClusterCull 입니다. 해당 함수로 들어갑니다. SV_GroupIndex 인 GroupIndex 변수도 같이 전달합니다.&lt;br /&gt;3.&amp;nbsp;PersistentNodeAndClusterCull&amp;nbsp;내부로&amp;nbsp;진입합니다.&amp;nbsp;&lt;br /&gt;4.&amp;nbsp;초기화&amp;nbsp;값을&amp;nbsp;설정합니다.&lt;br /&gt;- bProcessNodes 는 Node 에 대한 처리를 진행 중인지 여부입니다. 여기서는 처리할 노드가 있다면 Node 를 먼저 처리하기 때문에 초기값은 true 로 설정됩니다.&lt;br /&gt;-&amp;nbsp;NodeBatchReadyOffset&amp;nbsp;은&amp;nbsp;NANITE_MAX_BVH_NODES_PER_GROUP&amp;nbsp;으로&amp;nbsp;초기화&amp;nbsp;되는데&amp;nbsp;이&amp;nbsp;값은&amp;nbsp;16&amp;nbsp;입니다.&lt;br /&gt;- NodeBatchStartIndex 는 0으로 초기화 되는데, 추후 QueueState 로 부터 처리할 Node 정보를 가져올 때 현재 WorkGroup 이 읽어야 할 NodeReadOffset 를 담습니다. (WorkGroup 내 thread 는 동일한 값을 가짐)&lt;br /&gt;- ClusterBatchStartIndex 는 추후 Node 처리가 완료 되고 클러스터를 처리할 때 사용합니다. 이 값은 QueueState 로 부터 처리할 Cluster 정보를 가져올 때 현재 WorkGroup 이 읽어야 할 ClusterBatchReadOffset 을 담는데 사용합니다. (WorkGroup 내 thread 는 동일한 값을 가짐)&lt;br /&gt;5.&amp;nbsp;이제&amp;nbsp;Node&amp;nbsp;Hierarchy&amp;nbsp;/&amp;nbsp;Cluster&amp;nbsp;Culling&amp;nbsp;이&amp;nbsp;마칠&amp;nbsp;때&amp;nbsp;까지&amp;nbsp;이&amp;nbsp;반복문을&amp;nbsp;계속&amp;nbsp;돕니다.&lt;br /&gt;6. WorkGroup 내의 첫 번째 thread 가 대표로 GroupNumCandidateNodes 와 GroupNodeMask 를 0 으로 초기화 합니다. 그리고 OnPreProcessNodeBatch 함수를 통해 GroupOccludedBitmask[0~63] 을 0으로 초기화 합니다. 이 두가지 변수는 모두 group shared 타입(WorkGroup 내에서 공유) 입니다.&lt;br /&gt;- GroupNumCandidateNodes 는 현재 WorkGroup 에서 노드 순회 중 추가로 순회해야 하는 Child Node 가 있는 경우 해당 Node 의 수를 저장합니다.&lt;br /&gt;- GroupNodeMask 는 현재 WorkGroup 에서 LoadCandidateNodeDataToGroup 함수로 Node 정보를 로드를 성공하여 Node 순회를 진행할 수 있는 thread 정보를 비트 마스크로 마킹합니다. (1 &amp;lt;&amp;lt; GroupIndex 방식)&lt;br /&gt;7. 노드 처리를 위해서 조건문 안으로 진입합니다. NodeReadyMask 를 초기화 하는데 바로 전 6 에서 groupshared 변수 GroupNodeMask 를 현재 thread 에 VGPR 에 있는 NodeReadyMask 저장합니다. 아마도 이렇게 하는 것은 이후 Mask 연산에서 각 스레드가 자신의 VGPR 접근하는 것이 groupshared 인 L1 캐시에 접근하는 것보다 더 이득이 되기 때문이 아닐까 생각됩니다.&lt;br /&gt;8. 아마도 처음 진입한다면 이 조건문 안으로 진입할 것입니다. 그리고 16 개의 Node 를 모두 처리하면 두번째 루프에서 또다시 여기에 진입합니다.&lt;br /&gt;9. 조건문 내에서는 QueueState 로 부터 현재 WorkGroup 이 읽을 NodeReadOffset 을 가져와 GroupNodeBatchStartIndex 에 저장합니다. 물론 이 과정은 WorkGroup 당 한번만 실행하면 되기 때문에 WorkGroup 의 첫번째 thread 가 대표로 진행합니다.&lt;br /&gt;10. NodeBatchReadyOffset 을 0으로 초기화 하여 WorkGroup 에서 처리한 노드의 개수를 카운팅 할 수 있게 준비합니다. 그리고 9에서 받아온 GroupNodeBatchStartIndex(현재 WorkGroup 읽어야 하는 NodeReadOffset) 을 VGPR 인 NodeBatchStartIndex 로 옮깁니다. 만약 NodeBatchStartIndex 가 Nanite 가 처리할 수 있는 Node 의 최대 수를 넘어갔다면 노드 처리를 중단하고 Cluster 처리를 시작합니다.&lt;br /&gt;11. NodeIndex 는 현재 thread 가 처리해야 할 NodeReadOffset 입니다. NANITE_MAX_BVH_NODES_PER_GROUP 은 16 입니다. WorkGroup(64) 중 앞쪽 16 개의 경우 bNodeReady 를 true 로 설정합니다.&lt;br /&gt;12. WorkGroup 의 앞쪽 16개에 대해서 LoadCandidateNodeDataToGroup 함수를 호출합니다. 이 함수는 Node 데이터가 정상인지 확인하고 정상인 경우 true 를 리턴합니다. 그리고 groupshared 인 변수 GroupNodeData[NANITE_MAX_BVH_NODES_PER_GROUPS] 에 Node 데이터를 저장해둡니다. 여기서 로드 된 데이터는 바로 InstanceCulling 때 저장한 PackedCandidateNode 입니다. LoadCandidateNodeDataToGroup 상세 내역은 그림14 에서 확인해봅시다.&lt;br /&gt;13. 그리고 WorkGroup 의 앞쪽 16개 중 Node 데이터를 정상적으로 로드 가능했던 GroupIndex 는 GroupNodeMask 에 1 bit 를 마킹해 둡니다. 그리고 해당 마스크를 VGPR 변수인 NodeReadyMask 에 복사해줍니다.&lt;br /&gt;14. NodeReadyMask 에 처리할 것이 있다면? 조건문 내부로 진입합니다. firstbitlow(~NodeReadyMask) 는 총 사용 가능한 Node 의 개수를 얻을 수 있습니다. 이것을 BatchSize 에 넣습니다. 모두 사용 가능하다면 NANITE_MAX_BVH_NODES_PER_GROUPS(16) 일 것 입니다.&lt;br /&gt;15. ProcessNodeBatch 함수에서 Node 의 ChildNode 를 추가로 순회해야 할지 결정합니다. 그림15 에서 상세 내역을 확인해봅시다.&lt;br /&gt;16. ProcessNodeBatch 를 실행한 thread 들은 내부 조건문으로 진입합니다. 그리고 처리 완료한 노드에 대한 정보를 Clear 시켜줍니다. 이전에 그림5 의 4 에서 InitCandidateNodes 함수를 볼 때 봤었기 때문에 넘어가겠습니다.&lt;br /&gt;17. 처리 완료한 BatchSize 를 NodeBatchReadyOffset 에 더한 뒤 continue; 를 실행하여 다시 Node Batch 를 처리하도록&lt;br /&gt;16 개의 노드가 모두 처리되었다면 9번으로 이동하여 QueueState 로 부터 또 다시 새로운 NodeReadOffset 를 얻어올 것입니다.&lt;br /&gt;18. 만약 모든 Node 에 대한 처리가 완료되었다면, 이제 Cluster 를 처리합니다. ClusterBatchStartIndex 는 4 에서 처음에 0xFFFFFFFF 로 초기화되었기 때문에 조건문 내로 진입합니다.&lt;br /&gt;19. WorkGroup 내의 첫 번째 thread 가 대표로 ClusterBatchReadOffset 을 얻어옵니다. 그리고 이것을 VGPR 인 ClusterBatchStartIndex 에 복사합니다.&lt;br /&gt;20. 만약 처리할 Node 도 더 이상 없고, 최대 클러스터 배치 수를 넘어갔다면 더 이상 클러스터를 처리하지 않고 thread 를 종료합니다.&lt;br /&gt;21. WorkGroup 내의 첫 번째 thread 가 대표로 현재 NodeCount 를 얻어옵니다. 그리고 LoadClusterBatch 함수로 ClusterBatch 의 개수를 얻습니다. 그림23 에서 다룰 것입니다.&lt;br /&gt;22. VGPR 로 ClusterBatchReadySize 를 옮긴 후, 처리할 클러스터가 없다면 더 이상 클러스터를 처리하지 않고 thread 를 종료합니다.&lt;br /&gt;23. 처리 할 클러스터가 있는 경우 조건문 내로 진입합니다. 그리고 ProcessClusterBatch 를 실행하여 Cluster 의 컬링을 수행합니다. 자세한 내용은 그림23 에서 다룰 것입니다.&lt;br /&gt;24.&amp;nbsp;더&amp;nbsp;이상&amp;nbsp;처리할&amp;nbsp;Node&amp;nbsp;가&amp;nbsp;없다면&amp;nbsp;노드&amp;nbsp;처리를&amp;nbsp;비활성&amp;nbsp;하여&amp;nbsp;Cluster&amp;nbsp;만&amp;nbsp;처리하도록&amp;nbsp;합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;2436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biSKjr/btsFTdTr6Jt/4z35TkxqkLAMUkFqKWxmAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biSKjr/btsFTdTr6Jt/4z35TkxqkLAMUkFqKWxmAk/img.png&quot; data-alt=&quot;그림13. PersistentNodeAndClusterCull 쉐이더 코드 전체 레이아웃 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biSKjr/btsFTdTr6Jt/4z35TkxqkLAMUkFqKWxmAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiSKjr%2FbtsFTdTr6Jt%2F4z35TkxqkLAMUkFqKWxmAk%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;1008&quot; height=&quot;2436&quot; data-origin-width=&quot;1008&quot; data-origin-height=&quot;2436&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림13. PersistentNodeAndClusterCull 쉐이더 코드 전체 레이아웃 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;3.3.2.2.1.&amp;nbsp;LoadCandidateNodeDataToGroup&lt;/b&gt;&lt;br /&gt;1.&amp;nbsp;LoadCandidateNodeDataToGroup&amp;nbsp;내부를&amp;nbsp;확인해봅시다.&lt;br /&gt;2. LoadCandidateNodeData 함수를 사용하여 MainAndPostNodesAndClusterBatches 로 부터 NodeIndex 에 위치한 Node 데이터를 얻어옵니다.&lt;br /&gt;2.1. GetNodesAndBatchesOffset 와 GetCandidateNodesOffset 을 사용하여 데이터를 로드할 Offset 을 얻어오고, Main/Post Pass 여부에 따라서 노드 데이터를 로드합니다. 이 부분은 그림5 의 11 에서 확인했기 때문에 자세히 다루지 않겠습니다.&lt;br /&gt;3. 로드한 노드 데이터가 유효한지 확인하고 그렇다면 SetGroupNodeData 함수를 통해서 groupshared 변수인 GroupNodeData[0~15] 에 Node 데이터를 저장하며 이후 사용합니다.&lt;br /&gt;3.1. Node 의 유효성 여부는 RawData 가 0xFFFFFFFF 인지 확인하는 것입니다. 이것은 그림5 의 7 에서 MainAndPostNodesAndClusterBatches 의 데이터를 초기화 할 때&amp;nbsp;&amp;nbsp;ClearCandidateNode 함수를 호출하면 0xFFFFFFFF 로 설정하기 때문입니다. 만약 Node 데이터가 기록되지 않았다면 초기화 데이터가 그대로 남아있었을 겁니다.&lt;br /&gt;3.2. groupshared 변수인 GroupnodeData[0~15] 와 데이터를 읽고/쓰는 인터페이스를 보여줍니다. GROUP_NODE_SIZE 는 Main 의 경우 uint2, Post 의 경우 uint3 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;808&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cE6UBF/btsFU93EpLD/0BpK3qkRz3WQxBQuUMOzx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cE6UBF/btsFU93EpLD/0BpK3qkRz3WQxBQuUMOzx0/img.png&quot; data-alt=&quot;그림14. (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cE6UBF/btsFU93EpLD/0BpK3qkRz3WQxBQuUMOzx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcE6UBF%2FbtsFU93EpLD%2F0BpK3qkRz3WQxBQuUMOzx0%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;869&quot; height=&quot;808&quot; data-origin-width=&quot;869&quot; data-origin-height=&quot;808&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림14. (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;3.3.2.2.2.&amp;nbsp;ProcessNodeBatch&lt;/b&gt;&lt;br /&gt;ProcessNodeBatch 는 현재 Node 의 ChildNode 로 순회가 필요한지 확인하고 필요한 경우 순회합니다. 코드를 확인해봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. ProcessNodeBatch 함수에 진입합니다. BatchSize 는 현재 WorkGroup 내에서 ProcessNodeBatch 를 수행하는 thread 수(대부분 16), GroupIndex 는 WorkGroup 내에서 현재 thread 의 인덱스(0~15), QueueStateIndex 는 0이면 MainPass, 1 이면 PostPass 의 의미입니다.&lt;br /&gt;2. LocalNodeIndex, ChildIndex, FetchIndex 는 전달 받은 파라메터로 부터 생성됩니다. NANITE_MAX_BVH_NODE_FANOUT_BITS 는 2, NANITE_MAX_BVH_NODE_FANOUT_MASK 는 오른쪽에서 2개 비트를 1로 설정한 것입니다. 이 변수들이 어떤 값을 가질 지 WorkGroup 이 16 개라고 가정(출력 결과를 간단하게 하기 위해)하고 출력해봤습니다. 그림16 를 봐주세요. LocalNodeIndex 00, 01, 10, 11 와, ChildIndex 00, 01, 10, 11 의 조합으로 총 16 개의 조합이 나옵니다. 만약 WorkGroup 64 개라면 LocalNodeIndex 는 16 개, 그리고 4 개의 ChildIndex 조합으로 총 64 개의 thread 를 가득채워 작업을 진행할 수 있을 것입니다.&lt;br /&gt;3. FNaniteTraversalCallback 객체를 생성하고, Init 함수를 호출합니다. 이 함수에서는 바로 전 과정 그림14 의 LoadCandidateNodeDataToGroup 에서 저장했던 groupshared 변수인 GroupNodeData 로 부터 NodeData 를 얻어옵니다. 그리고 NaniteView 와 InstanceData 정보를 준비해둡니다.&lt;br /&gt;4. FHierarchyNodeSlice 는 현재 Node 의 Child 정보에 대한 Node 정보를 담고 있습니다. FHierarchyNodeSlice 정보는 GetHierarchyNodeSlice 함수를 통해 얻어옵니다. 이 때 전달되는 GetHierarchyNodeIndex() 은 InstanceData.NaniteHierarchyOffset + CandidateNode.NodeIndex 로 구성됩니다. NaniteHierarchyOffset 은 &lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;Nanite(1/5) &lt;/span&gt;&lt;/a&gt;의 그림4 의 2 에서 본 FResource 에서 부터 얻어오며 Nanite 에셋이 생성될 때 결정됩니다. CandidateNode.NodeIndex 는 현재 thread 가 처리중인 NodeIndex 를 의미합니다. 그리고 두번째로는 ChildIndex 가 전달됩니다. 이제 Child Node 의 Visibility 확인을 위해 필요한 정보를 모두 준비했습니다.&lt;br /&gt;5.&amp;nbsp;얻어온&amp;nbsp;HierarchyNode&amp;nbsp;정보가&amp;nbsp;유효한지&amp;nbsp;스트리밍되어&amp;nbsp;올라와있는지&amp;nbsp;확인할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;정보를&amp;nbsp;bool&amp;nbsp;변수에&amp;nbsp;담아둡니다.&amp;nbsp;그리고&amp;nbsp;BatchSize&amp;nbsp;보다&amp;nbsp;더&amp;nbsp;큰&amp;nbsp;LocalNodeIndex&amp;nbsp;를&amp;nbsp;위한&amp;nbsp;범위체크를&amp;nbsp;수행하여&amp;nbsp;Visible&amp;nbsp;상태를&amp;nbsp;설정합니다.&lt;br /&gt;6. ShouldVisitChild 함수를 사용하여 ChildNode 로 교체할 수 있을지 판정하여 그 결과를 bool 값으로 돌려줍니다. 내부에서는 HZB 컬링 등을 체크하며, 상세한 함수 내부는 사항은 그림17 에서 확인해봅시다.&lt;br /&gt;7. 현재 노드가 Leaf 가 아니면서 ChildNode 를 방문할 수 있고, 현재 노드 데이터가 정상적으로 로드되었다면, 현재 Node 의 ChildNode 를 처리할 수 있도록 해줍니다. WaveInterlockedAddScalar_ 함수를 사용하여 GroupNumCandidateNodes 에 ChildNode 를 방문할 Node 의 개수만큼 증가시켜줍니다. 그리고 각각의 thread 는 CandidateNodesOffset 에 자신이 저장할 CandidateNodesOffset 정보를 얻게 됩니다.&lt;br /&gt;8. WorkGroup 내에 첫번째 thread 가 대표로 QueueState 로 부터 NodeWriteOffset 을 GroupNumCandidateNodes 개수 만큼 할당 받습니다. 그리고 QueueState 에 NodeCount 또한 GroupNumCandidateNodes 개수만큼 증가시켜 줍니다.&lt;br /&gt;9. 현재 thread 가 ChildNode 를 방문할 예정이라면 조건문 내부에 진입합니다. 7번 과정에서 준비한 CandidateNodesOffset 에 8번 과정에서 얻어온 QueueState의 NodeWriteOffset 정보를 더해줘서 실제 ChildNode 정보를 기록할 Offset 을 완성합니다. 이 Offset 은 MainAndPostNodesAndClusterBatches 버퍼에서의 Offset 입니다. 그리고 완성한 Offset 정보가 최대 Node 수를 넘어가지 않는다면, StoreChildNode 함수를 호출하여 추가로 순회할 노드를 등록합니다. StoreChildNode 는 그림18에서 자세히 알아봅시다.&lt;br /&gt;10. 만약 현재 노드가 Leaf 노드라면, 더 이상 등록할 노드가 없을 것입니다. 이때는 현재 노드의 Cluster 를 등록하여 이후 과정에서 Cluster 컬링을 처리할 수 있도록 해줍니다. 조건문 내부로 진입해봅시다.&lt;br /&gt;11.&amp;nbsp;현재&amp;nbsp;노드가&amp;nbsp;가지고&amp;nbsp;있는&amp;nbsp;총&amp;nbsp;클러스터&amp;nbsp;수를&amp;nbsp;얻습니다.&lt;br /&gt;12.&amp;nbsp;QueueState&amp;nbsp;의&amp;nbsp;TotalClusters&amp;nbsp;에&amp;nbsp;현재&amp;nbsp;노드가&amp;nbsp;갖고&amp;nbsp;있는&amp;nbsp;클러스터&amp;nbsp;수를&amp;nbsp;더해주고&amp;nbsp;Cluster&amp;nbsp;를&amp;nbsp;기록할&amp;nbsp;인덱스를&amp;nbsp;얻습니다.&amp;nbsp;만약&amp;nbsp;저장할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;최대&amp;nbsp;클러스터&amp;nbsp;수를&amp;nbsp;넘어간다면,&amp;nbsp;넘지&amp;nbsp;않도록&amp;nbsp;clamp&amp;nbsp;시켜줍니다.&lt;br /&gt;13. QueueState 의 ClusterWriteOffset 으로 부터 클러스터 개수 만큼의 Offset 범위를 얻어오며, 각각의 thread 는 자신만의 ClusterWriteOffset 을 &lt;span style=&quot;color: #333333;&quot;&gt;VGPR 변수인 CandidateClustersOffset 에 저장합니다.&lt;/span&gt;&lt;br /&gt;14. BaseClusterIndex, StartIndex, EndIndex 를 준비합니다. StartIndex, EndIndex 는 바로 전 과정에서 구한 CandidateClustersOffset 을 기반으로한 Node 정보를 기록할 인덱스 정보입니다. BaseClusterIndex 는 스트리밍된 Page 데이터로부터 FCluster 정보를 읽어올 때 사용하는 인덱스 입니다. 추후 Cluster 컬링 을 처리할때 사용합니다.&lt;br /&gt;15. 현재 노드가 보유하고 있는 Cluster 정보를 기록합니다. StoreCluster 함수를 통해 기록하며 함수 내부는 그림19 에서 확인해봅시다.&lt;br /&gt;16.&amp;nbsp;클러스터를&amp;nbsp;배칭해서&amp;nbsp;처리할&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;배치&amp;nbsp;정보를&amp;nbsp;만들어서&amp;nbsp;AddToClusterBatch&amp;nbsp;함수를&amp;nbsp;호출합니다.&lt;br /&gt;17. NANITE_PERSISTENT_CLUSTER_CULLING_GROUP_SIZE 는 64 입니다. 인덱스 계산의 이해를 빠르게 하기 위해서 직접 인덱스를 출력해봤습니다. 출력 결과가 너무 길어질 것 같아서 NANITE_PERSISTENT_CLUSTER_CULLING_GROUP_SIZE을 4 로 설정하여 출력했습니다. 그림20 를 함께 봐주세요. BatchIndex 가 4 개마다 바뀌지는 실제로는 64 개마다 변경될 것임을 생각하고 봐주셔야 합니다. 직접 출력한 결과에는 NextIndex 를 사용하여 인덱스를 증가 시키지 않았기 때문에 출력 결과에서 빨간 줄을 그은 부분만 실제로 실행된다고 보시면 됩니다.&lt;br /&gt;- BatchIndex : 64 개를 1개의 배치로 한 경우 현재 클러스터가 포함된 Batch 의 Index 입니다.&lt;br /&gt;-&amp;nbsp;NextIndex&amp;nbsp;:&amp;nbsp;다음&amp;nbsp;배치의&amp;nbsp;Cluster&amp;nbsp;Index&amp;nbsp;에&amp;nbsp;대한&amp;nbsp;정보입니다.&amp;nbsp;for&amp;nbsp;loop&amp;nbsp;에서&amp;nbsp;Index&amp;nbsp;증가&amp;nbsp;분을&amp;nbsp;NextIndex&amp;nbsp;를&amp;nbsp;사용하여&amp;nbsp;계산합니다.&lt;br /&gt;-&amp;nbsp;MaxIndex&amp;nbsp;:&amp;nbsp;현재&amp;nbsp;배치에서&amp;nbsp;최대&amp;nbsp;Cluster&amp;nbsp;Index&amp;nbsp;를&amp;nbsp;의미합니다.&amp;nbsp;64&amp;nbsp;의&amp;nbsp;배수로&amp;nbsp;증가할&amp;nbsp;것입니다.&lt;br /&gt;-&amp;nbsp;Num&amp;nbsp;:&amp;nbsp;현재&amp;nbsp;배치가&amp;nbsp;저장할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;Cluster&amp;nbsp;수를&amp;nbsp;의미하며&amp;nbsp;특별한&amp;nbsp;경우가&amp;nbsp;아니라면&amp;nbsp;64&amp;nbsp;일&amp;nbsp;것입니다.&lt;br /&gt;18. 17 에서 생성한 BatchIndex 와 Num 정보를 갖고 AddToClusterBatch 를 호출합니다. 함수 내부는 그림21 에서 확인해 봅시다.&lt;br /&gt;19. WorkGroup 내의 첫번째 thread 가 대표로 QueueState 의 NodeCount&amp;nbsp;&amp;nbsp;를 감소시킵니다. 현재 WorkGroup 에서 처리한 Node 개수 만큼 노드 개수를 줄여줍니다.&lt;br /&gt;20. OnPostNodeVisit 에서는 현재 노드에 대한 처리를 마무리합니다. 만약 현재 노드가 Leaf 고 데이터를 스트리밍 해야한다면 RequestPageRange 로 스트리밍 데이터를 CPU 측에 요청합니다. 그리고 현재 노드의 자식 노드들 중 Occluded 된 노드가 있다면 현재 노드를 PostPass 에서 다시 컬링을 수행할 수 있도록 FCandidateNode 로 등록해줍니다. 함수 내부 구현은 그림22 에서 봅시다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1145&quot; data-origin-height=&quot;1854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t2IZG/btsFVrYAgjF/mqF0vTrpbWdkP5MLM1k91K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t2IZG/btsFVrYAgjF/mqF0vTrpbWdkP5MLM1k91K/img.png&quot; data-alt=&quot;그림15. ProcessNodeBatch 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t2IZG/btsFVrYAgjF/mqF0vTrpbWdkP5MLM1k91K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft2IZG%2FbtsFVrYAgjF%2FmqF0vTrpbWdkP5MLM1k91K%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;1145&quot; height=&quot;1854&quot; data-origin-width=&quot;1145&quot; data-origin-height=&quot;1854&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림15. ProcessNodeBatch 쉐이더 코드 (출처 : 레퍼런스1)&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OP4ix/btsFV6fDEH4/RtjBk4IillssDNBQ8jhS00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OP4ix/btsFV6fDEH4/RtjBk4IillssDNBQ8jhS00/img.png&quot; data-alt=&quot;그림16. LocalNodeIndex, ChildIndex, FetchIndex 변화 확인 (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OP4ix/btsFV6fDEH4/RtjBk4IillssDNBQ8jhS00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOP4ix%2FbtsFV6fDEH4%2FRtjBk4IillssDNBQ8jhS00%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;969&quot; height=&quot;406&quot; data-origin-width=&quot;969&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림16. LocalNodeIndex, ChildIndex, FetchIndex 변화 확인 (출처 : 직접구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;ShouldVisitChild 함수는 현재 처리중인 노드의 Child 노드에 대한 FHierarchyNodeSlice 정보를 받고 해당 노드의 Occluded 여부를 처리합니다. &lt;br /&gt;1.&amp;nbsp;전달&amp;nbsp;받은&amp;nbsp;현재&amp;nbsp;노드의&amp;nbsp;&amp;nbsp;Visible&amp;nbsp;정보를&amp;nbsp;설정해줍니다.&lt;br /&gt;2. EnabledBitmask 는 자식 노드 중 Occluded 된 것이 있는지 여부를 확인하는 비트 마스크 입니다. ChildNode 중 Occluded 된 노드가 있다면 Visible 플래그를 false 로 설정해줍니다. 이 값은 아래의 8번 과정에서 설정해주게 됩니다.&lt;br /&gt;3.&amp;nbsp;현재&amp;nbsp;노드가&amp;nbsp;Visible&amp;nbsp;이면,&amp;nbsp;조건문&amp;nbsp;내부로&amp;nbsp;진입합니다.&lt;br /&gt;4.&amp;nbsp;컬링을&amp;nbsp;위해&amp;nbsp;필요한&amp;nbsp;Node&amp;nbsp;의&amp;nbsp;바운드&amp;nbsp;정보와&amp;nbsp;Primitive&amp;nbsp;정보들을&amp;nbsp;준비합니다.&amp;nbsp;이미&amp;nbsp;앞에&amp;nbsp;본&amp;nbsp;부분도&amp;nbsp;있고&amp;nbsp;중요하지&amp;nbsp;않은&amp;nbsp;부분인&amp;nbsp;것&amp;nbsp;같아서&amp;nbsp;세부사항은&amp;nbsp;넘어가겠습니다.&lt;br /&gt;5.&amp;nbsp;현재&amp;nbsp;노드에&amp;nbsp;대해서&amp;nbsp;InstanceCulling&amp;nbsp;때와&amp;nbsp;마찬가지로&amp;nbsp;Distance,&amp;nbsp;GlobalClipPlane&amp;nbsp;컬링을&amp;nbsp;차례로&amp;nbsp;수행합니다.&lt;br /&gt;6.&amp;nbsp;5의&amp;nbsp;컬링&amp;nbsp;테스트&amp;nbsp;후에도&amp;nbsp;여전히&amp;nbsp;Visible&amp;nbsp;상태라면&amp;nbsp;ShouldVisitChildInternal&amp;nbsp;함수로&amp;nbsp;진입합니다.&lt;br /&gt;6.1. 여기서 실제 화면에 프로젝션 된 크기를 기반으로 해당 노드를 컬링할지 여부를 결정합니다. 현재는 GetProjectedEdgeScales 함수가 하는 정확한 동작 방식을 이해하지 못한 상태입니다. 해당 부분을 알게 된다면 추후 여기에 내용을 추가하겠습니다.&lt;br /&gt;7.&amp;nbsp;MainPass&amp;nbsp;의&amp;nbsp;경우&amp;nbsp;이전&amp;nbsp;프레임의&amp;nbsp;HZB,&amp;nbsp;PostPass&amp;nbsp;의&amp;nbsp;경우&amp;nbsp;현재&amp;nbsp;프레임의&amp;nbsp;경우&amp;nbsp;HZB&amp;nbsp;를&amp;nbsp;기반으로&amp;nbsp;컬링을&amp;nbsp;수행합니다.&lt;br /&gt;8. 이전 프레임에는 없다가 현재 프레임에 새로 등장한 인스턴스를 처리하기 위해서, MainPass 에서는 컬링된 경우 PostPass 에서 한번 더 컬링 테스트를 수행합니다. 그래서 Visible &amp;amp;&amp;amp; Loaded &amp;amp;&amp;amp; Occluded 된 경우 PostPass 를 위해서 GroupOccludedBitmask 에 현재 ChildIndex 를 기록해둡니다. 이 정보는 그림22 에서 OnPostNodeVisit 함수에서 Child Node 를 추가로 순회할 수 있도록 FCandidateNode 를 저장할지 여부를 판단하는데 사용됩니다.&lt;br /&gt;9.&amp;nbsp;Occluded&amp;nbsp;되지&amp;nbsp;않았다면,&amp;nbsp;최종적으로&amp;nbsp;Visible&amp;nbsp;을&amp;nbsp;설정합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1145&quot; data-origin-height=&quot;1342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl93zp/btsFXPcT9Bc/iS05oBccA778LYMavUL3ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl93zp/btsFXPcT9Bc/iS05oBccA778LYMavUL3ok/img.png&quot; data-alt=&quot;그림17. ShouldVisitChild 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl93zp/btsFXPcT9Bc/iS05oBccA778LYMavUL3ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl93zp%2FbtsFXPcT9Bc%2FiS05oBccA778LYMavUL3ok%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;1145&quot; height=&quot;1342&quot; data-origin-width=&quot;1145&quot; data-origin-height=&quot;1342&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림17. ShouldVisitChild 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;StoreChildNode 내부 구현을 확인해봅시다.&lt;br /&gt;1. StoreChildNode 내부 진입합니다. 저장할 NodeOffset 와 ChildNode 에 대한 HierarchyNodeSlice 정보를 전달 받습니다.&lt;br /&gt;2.&amp;nbsp;전달&amp;nbsp;받은&amp;nbsp;정보를&amp;nbsp;기반으로&amp;nbsp;FCandidateNode&amp;nbsp;객체를&amp;nbsp;만들고&amp;nbsp;StoreCandidateNode&amp;nbsp;함수를&amp;nbsp;호출합니다.&lt;br /&gt;3. StoreCandidateNode 함수는 NodeAndClusterBatches 버퍼와 저장할 NodeIndex, FCandidateNode, 그리고 Main/Post Pass 에 대한 정보를 전달 받습니다.&lt;br /&gt;4.&amp;nbsp;FCandidateNode&amp;nbsp;를&amp;nbsp;패킹하여&amp;nbsp;StoreCandidateNodeData&amp;nbsp;함수를&amp;nbsp;한번&amp;nbsp;더&amp;nbsp;호출합니다.&lt;br /&gt;5. StoreCandidateNodeData 내부로 진입합니다.&lt;br /&gt;6. 그림5 의 11 에서 확인했던 MainAndPostNodesAndClusterBatches 의 기록할 Node 계산 부분입니다.&lt;br /&gt;7. Offset 계산을 위해 필요한 함수들입니다. 참고 차 추가했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5R5jY/btsFV6zSLm8/fbDpI3kmfnJHQQQPrWXGtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5R5jY/btsFV6zSLm8/fbDpI3kmfnJHQQQPrWXGtk/img.png&quot; data-alt=&quot;그림18. StoreChildNode 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5R5jY/btsFV6zSLm8/fbDpI3kmfnJHQQQPrWXGtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5R5jY%2FbtsFV6zSLm8%2FfbDpI3kmfnJHQQQPrWXGtk%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;1156&quot; height=&quot;734&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;734&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림18. StoreChildNode 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;HierarchyNodeSlice 가 Leaf 노드의 경우 Cluster 를 저장하는 StoreCluster 함수를 확인해봅시다.&lt;br /&gt;1. StoreCluster 함수가 호출됩니다. Cluster 가 저장될 MainAndPostCandidateClusters 에서 Offset 과 HierarchyNodeSlice, 이후 과정에서 실제 Cluster 데이터를 Load 하는데 사용 할 ClusterIndex 정보를 전달 받습니다.&lt;br /&gt;2.&amp;nbsp;전달&amp;nbsp;받은&amp;nbsp;파라메터로&amp;nbsp;FVisibleCluster&amp;nbsp;를&amp;nbsp;만듭니다.&lt;br /&gt;3. FVisibleCluster 를 패킹합니다. 그리고 MainAndPostCandidateClusters 버퍼에 저장합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1137&quot; data-origin-height=&quot;822&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/COuuo/btsFV58Qnwz/Mu5K1ras8GaAIEdqa7Prz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/COuuo/btsFV58Qnwz/Mu5K1ras8GaAIEdqa7Prz0/img.png&quot; data-alt=&quot;그림19. StoreCluster 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/COuuo/btsFV58Qnwz/Mu5K1ras8GaAIEdqa7Prz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCOuuo%2FbtsFV58Qnwz%2FMu5K1ras8GaAIEdqa7Prz0%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;1137&quot; height=&quot;822&quot; data-origin-width=&quot;1137&quot; data-origin-height=&quot;822&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림19. StoreCluster 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&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;535&quot; data-origin-height=&quot;551&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ofIt4/btsFX2JMwE2/F8cIQFpX3WGjmcfdm3fXHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ofIt4/btsFX2JMwE2/F8cIQFpX3WGjmcfdm3fXHk/img.png&quot; data-alt=&quot;그림20. BatchIndex, NextIndex, MaxIndex, Num 변수의 변화 확인 (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ofIt4/btsFX2JMwE2/F8cIQFpX3WGjmcfdm3fXHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FofIt4%2FbtsFX2JMwE2%2FF8cIQFpX3WGjmcfdm3fXHk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;535&quot; height=&quot;551&quot; data-origin-width=&quot;535&quot; data-origin-height=&quot;551&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림20. BatchIndex, NextIndex, MaxIndex, Num 변수의 변화 확인 (출처 : 직접구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이 함수는 Cluster 를 64 개 단위로 묶었을 때 각 배치가 몇개의 Cluster 를 가지고 있는지를 MainAndPostCandidateClusters 에 저장합니다.&lt;br /&gt;1. 전달 받은 BatchIndex 는 Cluster 를 64 개 단으로 묶었을 때의 인덱스입니다. MainAndPostCandidateClusters 에서 Cluster 를 저장하는 Offset 을 구하고 index 는 uint 타입이기 때문에 * 4 해줍니다. 그리고 Add 변수는 추가할 Cluster 의 개수 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yS0ty/btsFVVkKl7a/YOUTAWksAiIx5R9qBfV3Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yS0ty/btsFVVkKl7a/YOUTAWksAiIx5R9qBfV3Kk/img.png&quot; data-alt=&quot;그림21. AddToClusterBatch 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yS0ty/btsFVVkKl7a/YOUTAWksAiIx5R9qBfV3Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyS0ty%2FbtsFVVkKl7a%2FYOUTAWksAiIx5R9qBfV3Kk%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;1014&quot; height=&quot;120&quot; data-origin-width=&quot;1014&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림21. AddToClusterBatch 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;ProcessNodeBatch&amp;nbsp;함수의&amp;nbsp;가장&amp;nbsp;마지막에&amp;nbsp;호출되는&amp;nbsp;함수인&amp;nbsp;OnPostNodeVisit&amp;nbsp;함수입니다.&amp;nbsp;내부&amp;nbsp;구현을&amp;nbsp;확인해봅시다.&lt;br /&gt;1. OnPostNodeVisit 함수 내부에 진입합니다. FHierarchyNodeSlice 는 현재 노드의 자식 노드의 정보가 담겨있습니다.&lt;br /&gt;2. 전달 받은 자식 노드가 Leaf 노드인 경우 클러스터 정보를 GPU 에서 읽을 수 있어야 합니다. 만약 현재 GPU 에서 사용할 수 없다면 RequestPageRange 함수를 호출해서 해당 데이터를 스트리밍 해달라고 CPU 에 요청합니다. Nanite 는 이런 방식으로 현재 렌더링에 필요한 페이지만 로드하여 런타임 메모리를 절약합니다.&lt;br /&gt;3.&amp;nbsp;MainPass&amp;nbsp;이고&amp;nbsp;첫번째&amp;nbsp;자식인&amp;nbsp;경우&amp;nbsp;대표로&amp;nbsp;해당&amp;nbsp;코드를&amp;nbsp;실행합니다.&amp;nbsp;현재&amp;nbsp;노드의&amp;nbsp;자식노드들&amp;nbsp;중&amp;nbsp;Occluded&amp;nbsp;된&amp;nbsp;노드가&amp;nbsp;있다면&amp;nbsp;조건문&amp;nbsp;내부로&amp;nbsp;진입합니다.&lt;br /&gt;4. Occluded 된 자식이 있는 경우 현재 노드를 PostPass 에서 다시 수행할 수 있도록 하기 위해서 QueueState 에 NodeWriteOffset 과 NodeCount 를 갱신하고 FCandidateNode 를 저장할 Offset 을 얻습니다.&lt;br /&gt;5. 최대 노드의 범위를 넘어가지 않는다면 현재 Node 에 대한 FCandidateNode 정보를 MainAndPostNodesAndClusterBatches 에 기록합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;561&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m6Aza/btsFYOc9gcM/bgSXob7SbdRgSLMpU1KZMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m6Aza/btsFYOc9gcM/bgSXob7SbdRgSLMpU1KZMK/img.png&quot; data-alt=&quot;그림22. OnPostNodeVisit 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m6Aza/btsFYOc9gcM/bgSXob7SbdRgSLMpU1KZMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm6Aza%2FbtsFYOc9gcM%2FbgSXob7SbdRgSLMpU1KZMK%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;970&quot; height=&quot;561&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;561&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림22. OnPostNodeVisit 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;LoadClusterBatch 코드입니다.&lt;br /&gt;전달받은 ClusterBatchIndex 에 있는 정보를 로드합니다. 사용된 GetCluserBatchesOffset() 는 0 입니다. 그리고 GetNodesAndBatchesOffset(bPostPass) 은 그림5의 11에서 확인했기 때문에 자세한 내용은 생략합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lb6n4/btsFX2iLsD2/VvPrahiQtlFSTcby6V5Lkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lb6n4/btsFX2iLsD2/VvPrahiQtlFSTcby6V5Lkk/img.png&quot; data-alt=&quot;그림23. LoadClusterBatch 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lb6n4/btsFX2iLsD2/VvPrahiQtlFSTcby6V5Lkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flb6n4%2FbtsFX2iLsD2%2FVvPrahiQtlFSTcby6V5Lkk%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;1153&quot; height=&quot;289&quot; data-origin-width=&quot;1153&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림23. LoadClusterBatch 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;3.3.2.2.3. ProcessClusterBatch&lt;/b&gt;&lt;br /&gt;Node&amp;nbsp;Hierarchy&amp;nbsp;순회를&amp;nbsp;마친&amp;nbsp;후&amp;nbsp;Cluster&amp;nbsp;컬링을&amp;nbsp;수행합니다.&amp;nbsp;ProcessClusterBatch&amp;nbsp;함수&amp;nbsp;내부를&amp;nbsp;확인해봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 전달 받은 파라메터 중 BatchStartIndex 는 QueueState 의 ClusterBatchReadOffset 입니다. 그리고 BatchSize 는 보통은 64 일 것이고, GroupIndex 의 경우 WorkGroup 내의 0~63 인덱스 중 하나 입니다. 이 정보들로 CandidateIndex 를 생성하는데, Cluster 가 64 개로 묶여 있기 때문에 NANITE_PERSISTENT_CLUSTER_CULLING_GROUP_SIZE(64) 를 BatchStartIndex 에 곱한 뒤 WorkGroup 의 인덱스(0~63)을 더하여 현재 thread 가 처리해야 할 Cluster 의 ReadOffset Index 를 최종적으로 얻을 수 있습니다. 이 인덱스를 LoadPackedCluster 에 전달해 패킹된 FVisibleCluster 를 얻습니다.&lt;br /&gt;1.1. LoadPackedCluster 는 그림19 의 3 에서 MainAndPostCandidateClusters 에서 저장한 데이터입니다. MainPass 의 경우 0 인덱스 부터 저장되며, PostPass 의 경우 인덱스가 거꾸로 자라납니다.&lt;br /&gt;2.&amp;nbsp;ProcessCluster&amp;nbsp;함수를&amp;nbsp;호출하여&amp;nbsp;클러스터&amp;nbsp;컬링을&amp;nbsp;수행합니다.&lt;br /&gt;2.1.&amp;nbsp;FVisibleCluster,&amp;nbsp;InstanceSceneData,&amp;nbsp;PrimitiveData,&amp;nbsp;View,&amp;nbsp;FCluster&amp;nbsp;그리고&amp;nbsp;컬링에&amp;nbsp;필요한&amp;nbsp;정보들을&amp;nbsp;준비합니다.&lt;br /&gt;2.2.&amp;nbsp;FBoxCull&amp;nbsp;객체를&amp;nbsp;만들어&amp;nbsp;현재&amp;nbsp;Cluster&amp;nbsp;에&amp;nbsp;대한&amp;nbsp;Distance,&amp;nbsp;GlobalClip&amp;nbsp;컬링을&amp;nbsp;수행합니다.&lt;br /&gt;2.3. SmallEnoughToDraw 는 SW or HW Raterize 중 어떤 것을 할지 결정하는 함수입니다. 그림25 에서 함수 내부를 볼 수 있습니다만 이 함수도 GetProjectedEdgeScales 함수를 사용합니다. 현재는 해당 함수가 하는 일을 정확히 이해하지 못해서 내부는 분석하지 않았습니다. 그 외에 코드를 보면 SW 레스터라이즈의 경우 일정 크기 이상이 되면 효율이 더 나빠지기 때문에 HW 레스터라이즈를 사용할 수 있도록 bUseHWRaster 플래그를 갱신해주는 부분을 볼 수 있습니다.&lt;br /&gt;2.4. PostPass 라면 FVisibleCluster 에 설정된 NANITE_CULLING_FLAG_USE_HW 여부로 HW 레스터라이즈를 사용할지 결정합니다. 이 부분은 PostPass 는 이미 MainPass 에서 SmallEnoughToDraw 함수를 수행하여 결과를 가지고 있기 때문에 해당 내용을 Flag 로 저장하여 로드하여 사용하는 것을 알 수 있습니다.&lt;br /&gt;2.5.&amp;nbsp;HZB&amp;nbsp;컬링을&amp;nbsp;수행합니다.&amp;nbsp;그리고&amp;nbsp;GlobalClip&amp;nbsp;컬링이&amp;nbsp;된&amp;nbsp;경우&amp;nbsp;bNeedsClipping&amp;nbsp;이&amp;nbsp;true&amp;nbsp;가&amp;nbsp;되어&amp;nbsp;HW&amp;nbsp;레스터라이즈를&amp;nbsp;수행하게&amp;nbsp;됩니다.&lt;br /&gt;2.6.&amp;nbsp;PostPass&amp;nbsp;라면&amp;nbsp;Occlued&amp;nbsp;된&amp;nbsp;경우&amp;nbsp;더&amp;nbsp;이상&amp;nbsp;재차&amp;nbsp;컬링&amp;nbsp;테스트를&amp;nbsp;하지&amp;nbsp;않을&amp;nbsp;것이므로&amp;nbsp;Occluded&amp;nbsp;&amp;nbsp;된&amp;nbsp;경우&amp;nbsp;Visible&amp;nbsp;을&amp;nbsp;꺼줍니다.&lt;br /&gt;2.7. 현재 thread 가 처리할 HW, SW 를 위한 ClusterOffset 을 위한 변수 초기화를 수행합니다.&lt;br /&gt;2.8. Visible 이고 Occluded 되지 않은 Cluster 는 그려질 것이므로 VisibleClusterArgs 의 SW, HW 레스터라이즈 항목중 Cluster 개수를 기록하는 위치에 Cluster 를 증가 시켜줍니다.&lt;br /&gt;2.9. 컬링 결과 Visible 인 경우 조건문 내로 진입합니다.&lt;br /&gt;2.10. Occluded 되지 않았다면 레스터라이즈를 수행하면 되기 때문에 첫 번째 조건문으로 진입합니다.&lt;br /&gt;2.11. HW Raster 를 사용하는 경우 ClusterOffsetHW 를 사용합니다. 추가로 PostPass 의 경우 OffsetClustersArgsSWHW[5] 을 Offset 에 더해줍니다. 이 것은 MainPass 에서 저장한 VisibleCluster 뒤에 PostPass 추가할 Cluster 를 넣어줘야 하기 때문에 들어간 부분입니다. 최대 클러스터 개수를 넘지 않는다면 StoreVisibleCluster 함수를 호출하여 FVisibleCluster 를 VisibleClustersSWHW 에 저장합니다. 이 때 HW 의 경우 버퍼의 마지막 부분에서 부터 기록하는 것을 볼 수 있습니다.&lt;br /&gt;2.12. SW Raster 사용하는 경우 ClusterOffsetSW 를 사용합니다. 마찬가지로 PostPass 의 경우 MainPass 에서 추가한 부분 뒤에 VisibleCluster 를 기록하기 위해서 OffsetClustersArgsSWHW[0] 을 부터 Offset 에 더합니다. 최대 클러스터 개수를 넘지 않는다면 StoreVisibleCluster 함수를 호출하여 FVisibleCluster 를 VisibleClustersSWHW 에 저장합니다. SW 의 경우 버퍼의 첫 부분 부터 기록하는 것을 볼 수 있습니다.&lt;br /&gt;2.13. MainPass 에서 Occluded 된 경우입니다. 이 경우는 PostPass 를 위해서 컬링 검사를 한번 더하기 위해서 준비합니다. QueueState 의 TotalClusters 를 ( 현재 WorkGroup 내에 이 코드를 수행하는) Active thread 수 만큼 증가 시켜줍니다(즉, Occluded 된 클러스터 수 만큼 증가).&lt;br /&gt;2.14. PostPass 의 QueueState 로 부터 현재 thread 기록할 ClusterWriteOffset 을 얻어옵니다. 그리고 MainPass 에서 HW Raster 를 사용하는 경우 NANITE_CULLING_FLAG_USE_HW 를 설정하여 PostPass 에서 활용할 수 있도록 합니다.&lt;br /&gt;2.15. StoreCandidateCluster 함수를 호출하여 Cluster 를 저장합니다. PostPass 에서 사용할 것이기 때문에 인덱스를 뒤쪽에서 부터 저장하도록 합니다.&lt;br /&gt;2.16. AddToClusterBatch 함수를 사용하여 지금 추가한 Cluster 의 배치(64개 묶음)의 총 개수를 증가 시켜줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;2595&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cNTdFL/btsFXI5N4BP/0qtIhQp5LyKUK5Monqku7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cNTdFL/btsFXI5N4BP/0qtIhQp5LyKUK5Monqku7K/img.png&quot; data-alt=&quot;그림24. ProcessClusterBatch 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cNTdFL/btsFXI5N4BP/0qtIhQp5LyKUK5Monqku7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcNTdFL%2FbtsFXI5N4BP%2F0qtIhQp5LyKUK5Monqku7K%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;1112&quot; height=&quot;2595&quot; data-origin-width=&quot;1112&quot; data-origin-height=&quot;2595&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림24. ProcessClusterBatch 쉐이더 코드 (출처 : 레퍼런스1)&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhxDAd/btsFWjySKFX/Qcqb2lHGdHmpRaWKCiF4J1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhxDAd/btsFWjySKFX/Qcqb2lHGdHmpRaWKCiF4J1/img.png&quot; data-alt=&quot;그림25. SmallEnoughToDraw 쉐이더 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhxDAd/btsFWjySKFX/Qcqb2lHGdHmpRaWKCiF4J1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhxDAd%2FbtsFWjySKFX%2FQcqb2lHGdHmpRaWKCiF4J1%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;1186&quot; height=&quot;443&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;443&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림25. SmallEnoughToDraw 쉐이더 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;b&gt;3.3.2.3. CalculateSafeRasterizerArgs&lt;/b&gt;&lt;br /&gt;이제 Instance와 Cluster 컬링을 모두 마치고 Visible Cluster 에 대해서 레스터라이즈를 진행할 차례입니다. FRenderer::AddPass_InstanceHierarchyAndClusterCull 에서 AddPass_NodeAndClusterCull 호출 직후에 있는 코드입니다. 내용은 레스터라이즈를 위해서 필요한 Indirect draw argument 를 생성하는 과정이며 코드를 확인해봅시다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;1. 레스터라이즈에 필요한 Indirect draw argument 생성은 Compute shader 에서 수행합니다. 쉐이더를 실행하기 위해서 필요한 파라메터를 설정합니다. MainPass 의 경우 MainRasterizeArgsSWHW 를 InRasterizerArgsSWHW 에 바인딩하고 PostPass 에서는 PostRasterizeArgsSWHW 를 InRasterizerArgsSWHW 에 바인딩합니다. PostPass 는 추가로 OffsetClustersArgsSWHW 에 MainRasterizeArgsSWHW 를 바인딩합니다. 이것은 MainPass 에 있는 Cluster 정보 뒤에 PostPass 의 Cluster 정보가 있기 때문에 필요합니다. 레스터라이즈를 위한 최종 Indirect draw arguments 는 MainPass 는 SafeMainRasterizeArgsSWHW, PostPass 는 SafePostRasterizeArgsSWHW 에 기록 됩니다. ClusterCountSWHW 는 uint2 버퍼이며 SW/HW Cluster 개수가 각각 담깁니다.&lt;br /&gt;2.&amp;nbsp;CalculateSafeRasterizerArgs&amp;nbsp;Compute&amp;nbsp;Shader&amp;nbsp;를&amp;nbsp;실행합니다.&lt;br /&gt;3.&amp;nbsp;ComputeShader&amp;nbsp;쪽으로&amp;nbsp;넘어왔습니다.&amp;nbsp;1&amp;nbsp;에서&amp;nbsp;CPU&amp;nbsp;측에서&amp;nbsp;설정한&amp;nbsp;버퍼들&amp;nbsp;입니다.&lt;br /&gt;4.&amp;nbsp;CalculateSafeRasterizerArgs&amp;nbsp;함수&amp;nbsp;내부로&amp;nbsp;들어왔습니다.&amp;nbsp;기본&amp;nbsp;설정에서는&amp;nbsp;PREV_DRAW_DATA&amp;nbsp;가&amp;nbsp;없기&amp;nbsp;때문에&amp;nbsp;이&amp;nbsp;부분은&amp;nbsp;건너뜁니다.&lt;br /&gt;5.&amp;nbsp;HWClusterCountIndex&amp;nbsp;를&amp;nbsp;얻어오는데,&amp;nbsp;Primitive,&amp;nbsp;Mesh&amp;nbsp;Shader&amp;nbsp;를&amp;nbsp;사용하지&amp;nbsp;않기&amp;nbsp;때문에&amp;nbsp;여기서는&amp;nbsp;5&amp;nbsp;가&amp;nbsp;리턴됩니다.&lt;br /&gt;6. PostPass 에서는 MainPass 에서 사용한 SW/HW Cluster 가 있기 때문에 그 부분 만큼 Offset 을 증가 시켜줍니다. 그리고 이번 Pass 에서 처리할 SW, HW Cluster 의 개수를 NumClustersSW, NumClustersHW 에 각각 저장합니다. 모든 Pass 의 클러스터 개수를 합산하여 TotalClustersSW, TotalClustersHW 에 저장합니다. 그리고 이 값이 MaxVisibleClusters 를 넘어가지 않도록 NumClusterSW, HW 를 적절히 Clamp 시켜줍니다.&lt;br /&gt;7. WriteDispatchArgsSWHW 함수를 사용하여 Rasterize 를 위한 SW, HW Indirect draw argument 를 SafeRasterizerArgsSWHW 버퍼에 기록 합니다. 어떤 데이터가 argument 로 기록될지 계속해서 봅시다.&lt;br /&gt;7.1. WriteDispatchArgsSWHW 함수 내부를 보면, SW, HW 모두 ThreadGroup 의 X 에만 정보가 설정되는 것을 볼 수 있습니다. 그리고 총 Cluster 수를 64 개 단위로 묶었을 때, 총 배치 개수를 ThreadGroup 의 X 에 저장합니다. 아마도 Rasterize 단계에서도 WorkGroup 당 64 개의 thread 씩 작업을 배칭 하도록 Compute Shader 가 구현되어있지 않을까? 예상됩니다.&lt;br /&gt;8.&amp;nbsp;ClusterCountSWHW&amp;nbsp;는&amp;nbsp;uint2&amp;nbsp;타입으로&amp;nbsp;NumClustersSW,&amp;nbsp;NumClustersHW&amp;nbsp;를&amp;nbsp;각각&amp;nbsp;저장합니다.&lt;br /&gt;9.&amp;nbsp;ClusterClassifyArgs[0]&amp;nbsp;에는&amp;nbsp;SW,&amp;nbsp;HW&amp;nbsp;Cluster&amp;nbsp;총&amp;nbsp;개수를&amp;nbsp;64&amp;nbsp;개&amp;nbsp;단위로&amp;nbsp;묶었을&amp;nbsp;때&amp;nbsp;총&amp;nbsp;배치&amp;nbsp;개수를&amp;nbsp;저장합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;2054&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cs1EC9/btsFWjZUwBr/NMs0Dfpk0ubrWGkYSm3Ttk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cs1EC9/btsFWjZUwBr/NMs0Dfpk0ubrWGkYSm3Ttk/img.png&quot; data-alt=&quot;그림26. CalculateSafeRasterizerArgs 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cs1EC9/btsFWjZUwBr/NMs0Dfpk0ubrWGkYSm3Ttk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcs1EC9%2FbtsFWjZUwBr%2FNMs0Dfpk0ubrWGkYSm3Ttk%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;1143&quot; height=&quot;2054&quot; data-origin-width=&quot;1143&quot; data-origin-height=&quot;2054&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림26. CalculateSafeRasterizerArgs 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;이번 글은 굉장히 호흡이 길었습니다. 또한 GPU 아키텍쳐에 대한 이해도 있어야 해서 개인적으로는 이 부분이 Nanite 코드 중 시간을 가장 많이 쓴 부분 같습니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이전글&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #0070d1;&quot;&gt;[UE5] Nanite (1/5)&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;다음글 &lt;a href=&quot;https://scahp.tistory.com/128&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[UE5] Nanite (3/5)&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&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; 4. 레퍼런스 &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/EpicGames/UnrealEngine/commit/072300df18a94f18077ca20a14224b5d99fee872&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://github.com/EpicGames/UnrealEngine/commit/072300df18a94f18077ca20a14224b5d99fee872&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=eviSykqSUUw&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://www.youtube.com/watch?v=eviSykqSUUw&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #666666;&quot;&gt;(Slide link)&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.unrealengine.com/5.0/en-US/nanite-virtualized-geometry-in-unreal-engine/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://docs.unrealengine.com/5.0/en-US/nanite-virtualized-geometry-in-unreal-engine/&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;4. &lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://scahp.tistory.com/126&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;5. &lt;a href=&quot;https://scahp.tistory.com/85&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://scahp.tistory.com/85&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;6. &lt;a href=&quot;https://scahp.tistory.com/84&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://scahp.tistory.com/84&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;7. &lt;a href=&quot;https://github.com/Microsoft/DirectXShaderCompiler/wiki/Wave-Intrinsics&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://github.com/Microsoft/DirectXShaderCompiler/wiki/Wave-Intrinsics&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>UE4 &amp;amp; UE5/Rendering</category>
      <category>cluster</category>
      <category>Culling</category>
      <category>GPU Driven Rendering</category>
      <category>hzb</category>
      <category>Nanite</category>
      <category>node</category>
      <category>Persistent thread</category>
      <category>rendering</category>
      <category>Thread pool</category>
      <category>UE5</category>
      <author>scahp</author>
      <guid isPermaLink="true">https://scahp.tistory.com/127</guid>
      <comments>https://scahp.tistory.com/127#entry127comment</comments>
      <pubDate>Thu, 21 Mar 2024 00:14:39 +0900</pubDate>
    </item>
    <item>
      <title>[UE5] Nanite (1/5)</title>
      <link>https://scahp.tistory.com/126</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;[UE5] Nanite&amp;nbsp;(1/5)&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: right;&quot; data-ke-size=&quot;size16&quot;&gt;최초 작성 : 2024-03-14&lt;br /&gt;마지막 수정 : 2024-03-14&lt;br /&gt;최재호&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&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: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 환경&lt;/b&gt;&lt;br /&gt;&lt;b&gt;2. 목표&lt;/b&gt;&lt;br /&gt;&lt;b&gt;3. 내용&lt;/b&gt;&lt;br /&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 3.1.&amp;nbsp;Nanite&amp;nbsp;에서&amp;nbsp;주요&amp;nbsp;기술&amp;nbsp;둘러보기 &lt;/b&gt;&lt;br /&gt;&lt;b&gt; &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; 3.1.1. MeshShader 와 같은 Cluster 기반 렌더링 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 3.1.2. TwoPassOcclusionCulling &lt;/b&gt;&lt;br /&gt;&lt;b&gt; &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; &amp;nbsp; 3.1.3. Visibility Buffer Rendering &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; 3.2. Nanite MeshDrawCommandCaching &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; 3.3. Nanite PrimitiveSceneProxy 의 생성 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; 3.4. 새로 생성한 Nanite PrimitiveSceneProxy 를 UpdateAllPrimitiveSceneInfos 으로 FScene 에 등록 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; 3.5. GPU Scene MaterialSlot 데이터 업로드 &lt;/b&gt;&lt;br /&gt;&lt;b&gt;&amp;nbsp; 3.6. Visibility Buffer 초기화&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 레퍼런스&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 환경&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Unreal Engine 5.3.2 (release branch 072300df18a94f18077ca20a14224b5d99fee872)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;개인적으로 분석한 내용이라 틀린 점이 있을 수 있습니다. 그런 부분은 알려주시면 감사하겠습니다.&lt;br /&gt;&lt;br /&gt;이번 글은 한 번에 많은 길이의 코드를 분석하는 부분이 종종 등장합니다. 그래서 글을 2개 띄우고 한쪽은 설명 부분을 한쪽은 코드 이미지를 최대화해서 보는 것을 추천합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 목표&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Nanite 에 들어간 핵심 기술들을 파악하고 BasePass 렌더링 과정을 코드레벨로 이해해 봅시다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Nanite 는 시네마틱 퀄리티 장면을 구현하기 위해서 아주 작은 크기의 삼각형으로 이뤄진 복잡한 지오메트리를 빠르게 렌더링 합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Nanite 코드는 기존 렌더링 파이프라인에 비해서 상당히 읽기가 어렵습니다. 그 이유는 대부분의 렌더링 패스가 Compute Shader 를 기반으로 하기 때문입니다. 그래서 Nanite 를 이해하기 전에 Shader 가 코드를 실행하는 방식에 대해서 이해하는 것이 좋습니다. 특히 Shader 는 동일한 코드를 Wavefront 단위로 실행한다는 점을 잘 이해해야 합니다. 아래의 리스트는 Nanite 코드 이해를 위해 알면 좋은 내용들입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- Shader code 의 동작 방식은 레퍼런스5, 6 를 참고하시면 좋습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- Compute Shader 는 GPU 에 대한 이해는 레퍼런스7, 8, 9 를 참고해 주세요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- Visibility Buffer Renderer 는 레퍼런스10 을 참고해 주세요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;- UE5 렌더링의 기본 단위인 MeshDrawCommand 에 대한 이해도 있으면 좋습니다. 해당 내용은 레퍼런스11, 12 를 참고해 주세요.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Nanite 사용을 위해서 에셋으로부터 Nanite 데이터를 생성하는 과정도 있지만 (메시로부터 클러스터를 생성하고 클러스터 계층구조를 만드는 작업), 이 시리즈에서는 Nanite 를 렌더링 하기 위해 필요한 데이터는 모두 준비된 상태라 가정하고 Nanite BasePass 렌더링 과정에만 집중할 예정입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이 글은 총 5개로 구성될 예정입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://scahp.tistory.com/126&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;1. Nanite 1/5 : Nanite 에서 사용하는 주요 기술과 MeshDrawCommand 생성 및 VisibilityBuffer 초기화 과정 리뷰&lt;/b&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;color: #333333; text-align: left;&quot; href=&quot;https://scahp.tistory.com/127&quot;&gt;2.&amp;nbsp;&lt;span style=&quot;text-align: left;&quot;&gt;Nanite 2/5 :&amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;TwoPassOcclusionCulling 의 전체 레이아웃을 확인하고 MainPass 의 노드 및 클러스터 컬링 리뷰&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://scahp.tistory.com/128&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;3.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;Nanite 3/5 :&amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;SW, HW 레스터라이저와 PostPass 리뷰&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://scahp.tistory.com/129&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;4.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;Nanite 4/5 :&amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Visibility Buffer 로 부터 Depth/Stencil 텍스쳐를 생성하는 부분 리뷰&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://scahp.tistory.com/130&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;5.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;text-align: left;&quot;&gt;Nanite 5/5 :&amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Visibility Buffer 로 부터 G-Buffer 생성하는 MaterialPass 리뷰&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 내용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3.1. Nanite 에서 주요 기술 둘러보기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3.1.1. &lt;/b&gt;&lt;b&gt;MeshShader 와 같은 Cluster 기반 렌더링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UE5 의 Nanite 지원 에셋의 경우 에디터에서 미리 메시의 삼각형을 클러스터화 시켜서 에셋에 저장해 둡니다. 그리고 실제 렌더링 시에는 클러스터화 한 데이터를 필요한 만큼 스트리밍 로드 하여 사용합니다(마치 버추어 텍스쳐처럼). 물론 컬링 또한 메시가 아닌 클러스터 단위로 결정됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt; &lt;b&gt;3.1.2.&lt;span&gt; &lt;/span&gt;&lt;/b&gt;TwoPassOcclusionCulling&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;ldquo;Patch-Based Occlusion Culling for Hardware Tessellation&amp;rdquo; 논문의 아이디어를 기반으로 한 내용입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이전 프레임과 현재 프레임의 변화에 큰 차이가 없다는 것을 기반으로 구현된 아이디어입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;처리 순서는 아래와 같습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MainPass
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InstanceCulling : 이전 프레임의 HZB 를 기반으로 전체 인스턴스들을 빠르게 컬링 합니다.&lt;/li&gt;
&lt;li&gt;Persistent Hierarchy Cluster Culling : 이전 단계에서 살아남은 인스턴스를 클러스터 단위로 컬링 테스트를 하고 살아남은 것들을 추려 레스터라이즈를 준비합니다.&lt;/li&gt;
&lt;li&gt;전 과정에서 살아남은 클러스터에 대해서 SW/HW Rasterizer 를 수행합니다. 결과는 Visibility Buffer 에 기록됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MainPass 의 결과로 부터 현재 프레임의 HZB 를 생성합니다.&lt;/li&gt;
&lt;li&gt;PostPass (저번 프레임에 안보였다가 이번 프레임에 보이는 인스턴스를 위한 패스)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MainPass 의 InstanceCulling, Persistent Hierarchy Cluster Culling 단계에서 컬링 되었던 인스턴스와 클러스터에 대해서 현재 프레임을 기준으로 생성된 HZB 에 대해 컬링 테스트를 진행합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;InstanceCulling, Persistent Hierarchy Cluster Culling 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;전 과정에서 살아남은 클러스터에 대해서 SW/HW Rasterizer 를 수행합니다. 결과는 VisibilityBuffer 에 기록됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이제 최종 HZB 를 빌드하고, 이 후과정에서 사용합니다.&lt;/li&gt;
&lt;li&gt;Material Passes 에서는 VisibilityBuffer 의 데이터를 기반으로 GBuffer 를 생성합니다. 이 부분이 Visibility Buffer Rendering 에 해당합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKXVr0/btsFHth9SYc/3frorKvQVWOfA61fGsuDYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKXVr0/btsFHth9SYc/3frorKvQVWOfA61fGsuDYK/img.png&quot; data-alt=&quot;그림1. TwoPassOcclusionCulling (출처 : 레퍼런스2)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKXVr0/btsFHth9SYc/3frorKvQVWOfA61fGsuDYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKXVr0%2FbtsFHth9SYc%2F3frorKvQVWOfA61fGsuDYK%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;887&quot; height=&quot;498&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. TwoPassOcclusionCulling (출처 : 레퍼런스2)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3.1.3. Visibility Buffer Rendering&lt;/b&gt; &lt;br /&gt;- 더 사실적인 장면을 렌더링 하기 위해서는 더 작은 삼각형이 필요할 수 있습니다. 극단적으로는 삼각형이 1개 픽셀 크기 수준으로 작아질 수도 있을 겁니다. PixelShader 에서는 1 개의 픽셀을 쉐이딩 할 때는 MipMap 결정 및 DDX, DDY 같은 연산을 위해서 실제로 4개의 픽셀을 쉐이딩 합니다. 삼각형의 크기가 충분히 크다면 쉐이딩 한 4개의 픽셀을 인접 픽셀에서 공유하여 쓸 수 있을 것입니다. 만약 삼각형당 1개의 픽셀을 사용하게 된다면 1 Activie + 3 Helper lane 으로 구성되게 될 것입니다. 이 경우 Helper lane 이 공유되지 못하게 되므로 픽셀당 최대 4 배의 연산이 필요하게 됩니다. Visibility Buffer Renderer 는 이런 부분을 해결해 줍니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.2. Nanite MeshDrawCommandCaching&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Raster/ShadingBin 에 대한 코드는 레퍼런스13 에 좋은 설명이 있어서 참고했습니다. 해당 자료를 확인해 보는 것도 좋을 것 같습니다. Raster/ShadingBin 은 CPU와 GPU 에서 공유하는 MaterialID 로 볼 수 있습니다. RasterBin 은 VisibilityBuffer 생성 단계에서 Rasterize 단계, ShadingBin 은 VisibilityBuffer 에서 GBuffer 를 생성해 내는 MaterialPass 에서 사용하게 됩니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.3. Nanite PrimitiveSceneProxy 의 생성&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;먼저 Nanite 용 메시가 UPrimitiveComponent 에서 Rendering Thread 에 있는 FScene 에 등록되는 과정을 확인해 봅시다. &lt;br /&gt;1. BatchAddPrimitives 로 부터 UPrimitiveComponent 들을 넘겨받습니다.&lt;br /&gt;2. 기존과 동일하게 UPrimitiveComponent 의 CreateSceneProxy 를 사용하여 FPrimitiveSceneProxy 를 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;727&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHGBck/btsFIyb8KYU/7SE9G6CXMz1sAWDjakhHDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHGBck/btsFIyb8KYU/7SE9G6CXMz1sAWDjakhHDK/img.png&quot; data-alt=&quot;그림2. UPrimitiveComponent 로 부터 SceneProxy 를 생성하여 FScene 에 등록하려는 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHGBck/btsFIyb8KYU/7SE9G6CXMz1sAWDjakhHDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHGBck%2FbtsFIyb8KYU%2F7SE9G6CXMz1sAWDjakhHDK%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;746&quot; height=&quot;727&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;727&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림2. UPrimitiveComponent 로 부터 SceneProxy 를 생성하여 FScene 에 등록하려는 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;문제의 범위를 좁히기 위해서 UStaticMeshComponent 라고 가정하고 코드를 계속해서 보겠습니다.&lt;br /&gt;1. FMaterialAudit 이라는 클래스는 ShouldCreateNaniteProxy 함수가 호출될 때 넘겨지는 데, 만약 이 UStaticMeshComponent 가 Nanite 를 사용하면 렌더링에 필요한 Material 정보를 FMaterialAudit 에 담아줍니다. &lt;br /&gt;1.1. ShouldCreateNaniteProxy 내부로 들어왔습니다. &lt;br /&gt;1.2. AuditMaterials 함수를 호출하여 Material 정보를 모읍니다. &lt;br /&gt;2. AuditMaterials 내부로 들어갑니다. &lt;br /&gt;3. UStaticMeshComponent 가 가지고 있는 모든 머터리얼을 차례로 순회합니다. &lt;br /&gt;4. FMaterialAuditEntry 라는 객체에 해당 머터리얼의 정보를 담아줍니다. &lt;br /&gt;4.1. AuditMaterials 를 호출하여 얻어온 Nanite에 사용할 Material 정보를 OutNaniteMaterials 에 넘겨줘서 함수 외부에서 사용할 수 있게 합니다. &lt;br /&gt;5. 이제 얻어온 NaniteMaterials 정보를 사용하여 CreateStaticMaterialSceneProxy 를 호출하여 Nanite 용 SceneProxy 를 생성합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;1578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6CEdz/btsFOUY3jkR/k7uprXz6Fs1fbkbNjd1MDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6CEdz/btsFOUY3jkR/k7uprXz6Fs1fbkbNjd1MDK/img.png&quot; data-alt=&quot;그림3. CreateSceneProxy 중 FMaterialAudit 을 채우는 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6CEdz/btsFOUY3jkR/k7uprXz6Fs1fbkbNjd1MDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6CEdz%2FbtsFOUY3jkR%2Fk7uprXz6Fs1fbkbNjd1MDK%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;852&quot; height=&quot;1578&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;1578&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림3. CreateSceneProxy 중 FMaterialAudit 을 채우는 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1. 계속해서 CreateStaticMeshSceneProxy 내부로 들어가면 Nanite::FSceneProxy 를 생성할 수 있는지 확인하고 생성합니다. 현재는 Nanite 에 대해서 알아보는 중이기 때문에 Nanite::FSceneProxy 생성자 내부로 들어가 봅시다.&lt;br /&gt;2. UStaticMeshComponent 로 부터 FResources 라는 데이터를 얻어옵니다. 아마 에디터에 생성한 Nanite 에 필요한 데이터들 같습니다. 클러스터 스트리밍에 필요한 데이터나 클러스터의 Hierarchy 정보가 담긴 FPackedHierarchyNode 등이 있는 것을 볼 수 있습니다.&lt;br /&gt;3. Mesh 의 각 섹션을 순회합니다. 그리고 반복문 아래에 있는 FMaterialSections 배열에 메시 섹션에 대한 머터리얼에 대한 정보를 모읍니다.&lt;br /&gt;4. 머터리얼에서 Hidden 으로 체크되어있지 않다면, ShadingMaterial 을 얻기 위해서 준비합니다. Nanite 에는 Rasterize, ShadingMaterial 로 머터리얼 타입이 나뉩니다. Rasterize 는 삼각형을 Rasterize 하여 Visibility Buffer 를 구성하기 위해 사용됩니다. Shading 은 Visibility Buffer Rendering 단계에서 GBuffer 를 만들 때 사용하는 머터리얼 입니다. 이렇게 두 종류로 나뉘는 이유는 Rasterize 중에는 픽셀의 Shading 에 대한 연산을 할 필요가 없기 때문입니다. 그래서 특별한 경우가 아니라면 Default Material 을 Rasterize 머터리얼로 사용하게 됩니다.&lt;br /&gt;5. 이전 단계에서 얻어온 MaterialAudit 에서 머터리얼 인덱스를 통해 현재 섹션에서 사용하려고 하는 머터리얼을 가져옵니다. 이 머터리얼을 ShadingMaterial 에 할당합니다.&lt;br /&gt;6. 소유한 머터리얼이 없는 경우, 메시 섹션이 Hidden 이면 NaniteHiddenSectionMaterial 을 설정하고 그렇지 않은 경우 Default 머터리얼을 설정합니다.&lt;br /&gt;7. 마지막으로 OnMaterialsUpdated 함수를 호출합니다.&lt;br /&gt;8. 위의 3에 있는 반복을 통해 채워진 MaterialSections 배열을 순회하며 MaterialSection 의 필요한 것들을 갱신합니다. 어떤 것을 갱신하는지 계속해서 봅시다.&lt;br /&gt;9. ProgrammableRaster 인 경우인지 확인합니다. 이 경우는 World Position Offset, PixelDepthOffset, Masked, Displacedment 기능을 사용하는 경우로 Default 머터리얼로 Rasterize 를 수행할 수 없는 상태입니다.&lt;br /&gt;10. RasterMaterial 를 등록합니다. Hidden 머터리얼의 경우 ShadingMaterial 와 동일하게 NaniteHiddenSectionMaterial 을 등록하고, ProgrammableRaster 의 경우 ShadingMaterial 을 등록하고 그렇지 않으면 Default 머터리얼을 등록합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1083&quot; data-origin-height=&quot;2704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CDAlc/btsFIjM3j85/CjUESOAOoiWULioyq1HFak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CDAlc/btsFIjM3j85/CjUESOAOoiWULioyq1HFak/img.png&quot; data-alt=&quot;그림4. CreateStaticSceneProxy 중 FMaterialAudit 을 사용하여 Shading, Raster Material 을 등록하는 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CDAlc/btsFIjM3j85/CjUESOAOoiWULioyq1HFak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCDAlc%2FbtsFIjM3j85%2FCjUESOAOoiWULioyq1HFak%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;1083&quot; height=&quot;2704&quot; data-origin-width=&quot;1083&quot; data-origin-height=&quot;2704&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림4. CreateStaticSceneProxy 중 FMaterialAudit 을 사용하여 Shading, Raster Material 을 등록하는 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.4. 새로 생성한 Nanite PrimitiveSceneProxy 를 UpdateAllPrimitiveSceneInfos 으로 FScene 에 등록&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;FPrimitiveSceneProxy&amp;nbsp;가&amp;nbsp;생성된&amp;nbsp;다음에는&amp;nbsp;렌더스레드에서&amp;nbsp;UpdateAllPrimitiveSceneInfos&amp;nbsp;를&amp;nbsp;호출하여&amp;nbsp;등록&amp;nbsp;대기 중인&amp;nbsp;FPrimitiveSceneProxy&amp;nbsp;를&amp;nbsp;일괄&amp;nbsp;등록해 줍니다. &lt;br /&gt;1. UpdateAllPrimitveSceneInfos 함수에 진입합니다. &lt;br /&gt;2. 이번에 추가되거나 갱신된 PrimitiveSceneInfo 의 수를 계산합니다. &lt;br /&gt;3. 2번 과정에서 얻는 수를 사용하여 SceneInfoWidthAddToScene 컨테이너의 메모리를 예약합니다. &lt;br /&gt;4. StaticMesh 의 경우 CacheNaniteDrawCommand 함수를 호출하여 Nanite 의 MeshDrawCommand 를 캐싱합니다. 스태틱 메시인 것으로 가정하고 코드를 계속 확인해 봅시다.&lt;br /&gt;5. CacheNaniteDrawCommands 함수로 진입합니다. &lt;br /&gt;6. 각각의 FPrimitiveSceneInfo 에 대해서 BuildNaniteDrawCommands 를 호출합니다. &lt;br /&gt;7. BuildNaniteDrawCommands 내부로 진입합니다. &lt;br /&gt;8. MeshDrawCommand 를 만들기 위해서 NaniteMeshProcessor 를 생성합니다. &lt;br /&gt;8.1. NaniteMeshProcessor 를 만들어주는 CreateNaniteMeshProcessor 함수 내로 진입합니다. &lt;br /&gt;8.2. DepthStencilState 설정이 조금 특이한데, Depth Equal 상태로 설정합니다. 이 부분은 추후에 보겠지만 동일한 MaterialID를 가진 픽셀만 렌더링 하기 위해서 추가된 부분입니다. &lt;br /&gt;8.3. 최종적으로 FNaniteMeshProcessor 를 생성합니다. &lt;br /&gt;9. PassBody 라는 람다함수를 호출합니다.&lt;br /&gt;10. PassBody 내부에서는 PrimitiveSceneInfo 가 소유한 StaticMeshes 배열을 순회합니다. StaticMeshes 배열은 FStaticMeshBatch 정보가 담겨 있습니다.&lt;br /&gt;11. Nanite 를 지원하는 경우라면 NaniteMeshProcessor 에 FStaticMeshBatch 를 전달하여 MeshDrawCommand 를 생성하고 캐싱합니다. &lt;br /&gt;11.1. AddMeshBatch 내부로 진입하고 계속해서 TryAddMeshBatch 로 진입합니다. &lt;br /&gt;11.2. FNaniteIndirectMaterialVS 를 버택스 쉐이더로 사용합니다. 이 쉐이더는 FullscreenQuad 를 위한 VS 입니다.&lt;br /&gt;11.3. 그리고 사용할 BasePassPixelShader 를 얻어옵니다. &lt;br /&gt;11.4. 앞에서 얻은 버택스, 픽셀 쉐이더를 바인딩합니다. 여기서 버택스 쉐이더만 Nanite 용 FullscreenQuad 로 사용하는데 레스터라이즈 방식만 바뀌고 픽셀을 쉐이딩 하는 방식은 유사하지 않을까? 하고 추측할 수 있습니다. &lt;br /&gt;11.5. 이제 최종적으로 BuildMeshDrawCommands 를 호출하여 MeshDrawCommand 를 생성합니다. 생성 및 캐싱 과정은 레퍼런스11 에도 나와있기 때문에 생략합니다. &lt;br /&gt;12. MeshDrawCommand 생성을 마치고 나서 Nanite::SceneProxy 에 생성해 둔 MaterialSections 을 얻어옵니다. &lt;br /&gt;13. 그리고 DrawListContext.DeferredPipelines[MeshPass] 에 FDeferredPipelines 를 생성하여 정보를 채워줍니다. FDeferredPipelines 에는 FNaniteRasterPipeline, FNaniteShadingPipeline 에 대한 정보들이 담깁니다. &lt;br /&gt;14. Nanite::SceneProxy 가 가진 MaterialSections 정보를 순회합니다. &lt;br /&gt;15. 여기서는 우선 FNaniteRasterPipeline 정보만 설정합니다. NaniteMaterialSections 로부터 RasterMaterial 과 기타 Rasterize 에 필요한 머터리얼 정보들을 설정합니다. &lt;br /&gt;16. WPO 가 거리에 따라 꺼지는 경우 해당 메시는 WPO 의 활성 여부에 따라 서로 다른 머터리얼을 &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;Rasterize 에 &lt;/span&gt;사용해야 할 것입니다. 그래서 WPODisableDistance 를 평가한 뒤 Secondary FNaniteRasterPipline 을 생성하는 데 사용합니다.&lt;br /&gt;17. FNaniteDrawListContexts 에 Apply 함수를 호출하여 FScene 에 생성한 Nanite 머터리얼을 FNaniteRasterPipelines, FNaniteShadingPipelines 형태로 등록해 줍니다. 함수의 내부는 그림6 에서 추가로 더 알아봅시다. &lt;br /&gt;18. bAllowComputeMaterials 가 true 면 BuildShadingCommands 함수를 호출하지만 기본 설정으로는 bAllowComputeMaterials 가 false 입니다. 그래서 bAllowComputeMaterials 관련 코드는 더 이상 추적하지 않을 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;4405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7Hk3N/btsFI5udLhc/oJkWkHi137kcV2YuQ01VDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7Hk3N/btsFI5udLhc/oJkWkHi137kcV2YuQ01VDK/img.png&quot; data-alt=&quot;그림5. UpdateAllPrimitiveSceneInfos 과정을 통해 NaniteMeshDrawCommand 가 캐싱되는 과정 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7Hk3N/btsFI5udLhc/oJkWkHi137kcV2YuQ01VDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7Hk3N%2FbtsFI5udLhc%2FoJkWkHi137kcV2YuQ01VDK%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;1192&quot; height=&quot;4405&quot; data-origin-width=&quot;1192&quot; data-origin-height=&quot;4405&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림5. UpdateAllPrimitiveSceneInfos 과정을 통해 NaniteMeshDrawCommand 가 캐싱되는 과정 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1. FNaniteDrawListContext 의 Apply 함수 내부입니다. 모든 ENaniteMeshPass 에 대해서 순회합니다. ENaniteMeshPass 는 BasePass 와 LumenCardCapture 두 가지입니다. 이제 MeshDrawCommand 를 만들면서 준비한 머터리얼 파이프라인 데이터들을 FScene 에 등록합니다. FNaniteMaterialCommands 는 머터리얼 슬롯 정보, FNaniteRasterPipelines 은 Rasterize 관련 머터리얼 정보, FNaniteShadingPipelines 는 Shading 관련 머터리얼 정보를 담고 있습니다. &lt;br /&gt;2. DeferredCommands 멤버로부터 는 그림5 의 15 에서 우리가 생성해 둔 FNaniteRasterPipeline 정보를 얻어옵니다. &lt;br /&gt;3. FNaniteMaterialCommands 타입인 ShadingCommands.Register 함수를 통해 FNaniteCommandInfo 를 생성합니다. Register 의 내부 코드는 그림7 에서 계속 추적합니다.&lt;br /&gt;4. AddShadingCommand 함수를 호출하여 PrimitiveSceneInfo 에 방금 새로 만든 FNaniteCommandInfo 를 등록합니다. &lt;br /&gt;5.&amp;nbsp;이번에는&amp;nbsp;DeferredPipelines&amp;nbsp;를&amp;nbsp;순회합니다. &lt;br /&gt;6.&amp;nbsp;RasterizePipelines&amp;nbsp;을&amp;nbsp;순회합니다.&amp;nbsp; &lt;br /&gt;7. FScene 의 RasterPipelines.Register 함수를 호출하여 RasterPipeline 을 등록합니다. 리턴값으로 FNaniteRasterBin 을 돌려받는데 Register 함수 내부에서는 CPU 에서 RasterPipeline 을 얻을 수 있는 키인 BinId 와 GPU 에서 얻을 수 있는 키인 BinIndex 를 갖고 있습니다. Register 의 내부 코드는 그림8 에서 계속 추적합니다.&lt;br /&gt;8. WPO 가 꺼져 있는 경우 RasterPipelines.Register 함수로 SecondaryRaster 를 추가 등록해 줍니다. &lt;br /&gt;9. AddRasterBin 함수를 사용하여 PrimaryRasterBin 과 SecondaryRasterBin 을 PrimitiveSceneInfo 에 등록해 줍니다. AddRasterBin 함수 내부는 그림9에서 계속해서 추적합니다.&lt;br /&gt;10. RasterBins 는 기본 설정에서는 nullptr 이기 때문에 여기서는 무시합니다. &lt;br /&gt;11. RefreshNaniteRasterBins() 는 CustomDepth 렌더링에 대한 추가 작업을 수행합니다. 코드의 범위를 최소화하기 위해서 여기서는 CustomDepth 를 사용하지 않는다고 가정하고 넘어가겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;1114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ywoK3/btsFNwRd8ts/PPIfV42FI9kwvPLqmfbQ71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ywoK3/btsFNwRd8ts/PPIfV42FI9kwvPLqmfbQ71/img.png&quot; data-alt=&quot;그림6. Nanite MeshDrawCommand 를 준비한 뒤, FScene 에 Nanite Material 정보를 등록하는 코드 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ywoK3/btsFNwRd8ts/PPIfV42FI9kwvPLqmfbQ71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FywoK3%2FbtsFNwRd8ts%2FPPIfV42FI9kwvPLqmfbQ71%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;1176&quot; height=&quot;1114&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;1114&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림6. Nanite MeshDrawCommand 를 준비한 뒤, FScene 에 Nanite Material 정보를 등록하는 코드 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;아래 내용은 ShadingCommands.Register 함수 내부입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;여기서는 MaterialSlot 의 인덱스를 할당 받습니다. 그리고 그림7 의 AddShadingCommand 함수에서 MaterialSlot 의 인덱스를 LegacyShadingId 에 등록합니다. MaterialSlot 에는 Raster, Shading Bin Index (GPU 에서의 인덱스) 또한 포함되어 있습니다. GPU 측에서는 MaterialSlot 을 통해 현재 처리 중인 프리미티브의 머터리얼 정보를 얻을 때 사용합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;3.1. 로빈후드해시맵에 FMeshDrawCommand 해시를 기반으로 FNaniteMaterialEntry 를 등록합니다. 등록 후 돌려받은 해시키를 CommandInfo 저장합니다. 이 키는 CPU 에서 FNaniteMaterialEntry 를 찾을 때 사용됩니다.&lt;br /&gt;3.2. FNaniteMaterialEntry 가 처음 등록된 경우 Entry 에 필요한 정보를 설정합니다. MaterialSlotAllocator 로 부터 MaterialSlot 을 할당받는 부분이 가장 중요합니다. 이 부분은 GPU 에서 Material 을 찾을 때 사용되는 Index 입니다. 이후에는 ShadingBin 이라는 이름으로 불립니다.&lt;br /&gt;3.3. CommandInfo 에 Entry 에 있는 MaterialSlot 을 설정해 줍니다.&lt;br /&gt;3.4. MaterialEntry 의 레퍼런스 카운트를 증가시켜 줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;729&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAyQ3N/btsFMzA9IBd/wxEDsPQyFTcHx4OecKD3E1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAyQ3N/btsFMzA9IBd/wxEDsPQyFTcHx4OecKD3E1/img.png&quot; data-alt=&quot;추가그림1. NaniteMaterialCommands::Register 로 MaterialSlot 할당 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAyQ3N/btsFMzA9IBd/wxEDsPQyFTcHx4OecKD3E1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAyQ3N%2FbtsFMzA9IBd%2FwxEDsPQyFTcHx4OecKD3E1%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;1176&quot; height=&quot;729&quot; data-origin-width=&quot;1176&quot; data-origin-height=&quot;729&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;추가그림1. NaniteMaterialCommands::Register 로 MaterialSlot 할당 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;AddShadingCommand 함수는 PrimitiveSceneInfo 에 Nanite 관련 정보를 기록하는데 사용됩니다. 그림6의 3번 과정에서 생성한 FNaniteCommandInfo 를 PrimitiveSceneInfo 에 등록하고, PrimitiveSceneInfo.NaniteMaterialSlots 에 MaterialSlot 또한 기록합니다. MaterialSlot 은 그림7의 3.2 과정에서 생성한 GPU 에서 사용할 Material Index 입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhS0GS/btsFMC5FQyU/TA0C7fGQ9r277hqfC6P3e1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhS0GS/btsFMC5FQyU/TA0C7fGQ9r277hqfC6P3e1/img.png&quot; data-alt=&quot;그림7. FNaniteMaterialCommands::Register 함수 내부 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhS0GS/btsFMC5FQyU/TA0C7fGQ9r277hqfC6P3e1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhS0GS%2FbtsFMC5FQyU%2FTA0C7fGQ9r277hqfC6P3e1%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;971&quot; height=&quot;222&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림7. FNaniteMaterialCommands::Register 함수 내부 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;아래 내용은 RasterPipelines.Register 함수의 내용입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;7.1. Register 함수에 진입합니다.&lt;br /&gt;7.2. FNaniteRasterPipeline 을 사용하여 해시를 만들어서 로빈후드해시맵에 FNaniteRasterEntry 를 생성합니다. 그리고 CPU 에서 로빈후드해시맵으로부터 FNaniteRasterEntry 을 얻어올 수 있는 키를 BinId 에 설정해 줍니다.&lt;br /&gt;7.3. FNaniteRasterEntry 가 처음 생성되었다면 AllocateBin 을 사용하여 GPU 에서 FNaniteRasterEntry 를 얻을 수 있는 BinIndex 를 할당받습니다. AllocateBin 에 연결된 화살표를 따라가보면, BinIndex 를 할당받을 때, bPerPixelEval 이 true 면 인덱스가 거꾸로 자라나고, false 면 0에서부터 자라나는 것을 볼 수 있습니다.&amp;nbsp;&lt;br /&gt;7.4. FNaniteRasterEntry 의 레퍼런스 카운트를 증가시킵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;904&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rAmSK/btsFNnOx6fb/LIpFQM00pHsb6gXltTvdWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rAmSK/btsFNnOx6fb/LIpFQM00pHsb6gXltTvdWK/img.png&quot; data-alt=&quot;그림8. FNaniteRasterPipelines::Register 함수 내부 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rAmSK/btsFNnOx6fb/LIpFQM00pHsb6gXltTvdWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrAmSK%2FbtsFNnOx6fb%2FLIpFQM00pHsb6gXltTvdWK%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;948&quot; height=&quot;904&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;904&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림8. FNaniteRasterPipelines::Register 함수 내부 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;아래는 그림8에서 생성한 FNaniteRasterBin 정보를 PrimitiveSceneInfo 에 등록하는 과정입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;9.1. Primary, Secondary RasterBin 을 PrimitiveSceneInfo 의 NaniteRasterBins[MeshPass] 컨테이너에 등록해 줍니다.&lt;br /&gt;9.2. PrimitiveSceneInfo.NaniteMaterialSlots[MeshPass] 에 FNaniteMaterialSlot 을 추가해 줍니다. FNaniteMaterialSlot 의 RasterBin, SecondaryRasterBin 에는 GPU 에서 RasterMaterial 을 찾을 수 있는 키를 BinIndex 를 등록합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;375&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bX03dR/btsFK6zJLxc/3TKm6RDDbpYZOYnJFyZug0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bX03dR/btsFK6zJLxc/3TKm6RDDbpYZOYnJFyZug0/img.png&quot; data-alt=&quot;그림9. FNaniteDrawListContext::AddRasterBin 함수 내부 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bX03dR/btsFK6zJLxc/3TKm6RDDbpYZOYnJFyZug0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbX03dR%2FbtsFK6zJLxc%2F3TKm6RDDbpYZOYnJFyZug0%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;748&quot; height=&quot;375&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;375&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림9. FNaniteDrawListContext::AddRasterBin 함수 내부 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.5. GPU Scene MaterialSlot 데이터 업로드&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;위에서&amp;nbsp;준비한&amp;nbsp;FNaniteMaterialSlot&amp;nbsp;은&amp;nbsp;GPUScene&amp;nbsp;의&amp;nbsp;Update&amp;nbsp;시점에&amp;nbsp;GPU&amp;nbsp;로&amp;nbsp;올려줍니다. &lt;br /&gt;1. FGPUScene::UpdateInternal 함수로 UploadGeneral&amp;lt;FUploadDataSourceAdapterScenePrimitives&amp;gt; 가 호출됩니다.&lt;br /&gt;2. UploadGeneral 은 FUploadDataSourceAdapterScenePrimitives 를 사용하고 있기 때문에 bUpdateNaniteMaterialTables 가 항상 true 입니다. 조건문 내부로 진입합니다. &lt;br /&gt;3. 모든 Nanite 패스에 대해서 순회하면서 FScene 에 있는 FNaniteMaterialCommands NaniteMaterials[ENaniteMeshPass::Num]; 이 MaterialSlot 데이터를 업로드할 수 있게 Begin 함수를 호출합니다. &lt;br /&gt;3.1. FNaniteMaterialCommands 의 Begin 함수 내부로 진입합니다. &lt;br /&gt;3.2. MaterialSlotDataBuffer 에 NumPrimitiveUpdates * MaxMaterials * MaterialSlotSize 만큼의 데이터를 업로드하기 위해서 준비합니다. &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;MaxMaterials 로 보아 Primitive 당 최대 머터리얼이 정해진 것을 알 수 있습니다.&lt;/span&gt;&lt;br /&gt;4. NumPrimitiveDataUploads 수만큼 순회합니다. &lt;br /&gt;5. MaterialSlot 을 업로드할 Dest 위치를 가진 NaniteMaterialUploader 와 MaterialSlot 의 원본 정보를 가진 PrmitiveSceneInfo-&amp;gt;NaniteMaterialSlots 를 준비합니다. &lt;br /&gt;6. MaterialSlot 을 복사합니다. &lt;br /&gt;7. FNaniteMaterialCommands 의 Finish 함수를 호출하여 업로드를 마칩니다. &lt;br /&gt;8. 마지막으로 업로드한 MaterialSlot 과 Shader 에서 MaterialSlot 로드하는 부분을 확인해보려고 합니다. 그전에 MaterialSlot 을 복사하기 위해서 호출한 NaniteMaterialUploader&amp;rarr;GetMaterialSlotPtr&amp;nbsp;&amp;nbsp;내부에 대해 봅시다. (초록색 선을 따라 이동) GetMaterialSlotPtr 함수를 보면 PrimitiveIndex * MaxMaterials 를 하여 해당 위치에 데이터를 저장할 수 있게 하는 것으로 보입니다. 여기서 MaxMaterials 는 64 입니다. 코드를 조금 더 따라가보면 NANITE_MAX_MATERIALS 로 정의되어있고, Nanite 메시는 최대 64 개의 머터리얼을 가질 수 있다는 것을 알 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1968&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yMg6r/btsFLvTxoDd/PVefd17D1vBJ5VPw2E0qKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yMg6r/btsFLvTxoDd/PVefd17D1vBJ5VPw2E0qKk/img.png&quot; data-alt=&quot;그림10. FScene 을 통해서 MaterialSlot 을 GPUScene 에 업로드하는 과정 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yMg6r/btsFLvTxoDd/PVefd17D1vBJ5VPw2E0qKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyMg6r%2FbtsFLvTxoDd%2FPVefd17D1vBJ5VPw2E0qKk%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;1448&quot; height=&quot;1968&quot; data-origin-width=&quot;1448&quot; data-origin-height=&quot;1968&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림10. FScene 을 통해서 MaterialSlot 을 GPUScene 에 업로드하는 과정 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이번에는 MaterialSlot 을 로드하는 Shader 를 확인해 봅시다.&lt;br /&gt;1.&amp;nbsp;LoadMaterialSlot&amp;nbsp;이&amp;nbsp;호출되면&amp;nbsp;업로드 시와&amp;nbsp;마찬가지로&amp;nbsp;PrimitiveIndex&amp;nbsp;*&amp;nbsp;MaxMaterials&amp;nbsp;를&amp;nbsp;하여&amp;nbsp;GlobalMaterialIndex&amp;nbsp;를&amp;nbsp;얻어오는&amp;nbsp;것을&amp;nbsp;볼&amp;nbsp;수&amp;nbsp;있습니다.&amp;nbsp;그리고&amp;nbsp;이것을&amp;nbsp;기반으로&amp;nbsp;ByteAddressBuffer&amp;nbsp;로&amp;nbsp;부터&amp;nbsp;FNaniteMaterialSlot&amp;nbsp;을&amp;nbsp;로드합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;2. RemapMaterialIndexToOffset 함수는 PrimitiveIndex, MaterialIndex 를 사용하여 MaterialSlot 을 로드할 Offset 을 얻어냅니다. BytesPerMaterials 는 총 8 byte 인데, MaterialSlot 을 Packing 한 사이즈입니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;3. MaxMaterials 는 그림10 의 8 에서 최대 머터리얼 수 64 개인 것을 확인할 수 있었습니다. 그리고 CPU 측에서 업로드 시에 사용한 것과 같이 PrimitiveIndex * MaxMaterial 위치를 얻어오고 MaterialIndex 를 더하여 최종 GlobalMaterialIndex 를 만들어냅니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nFooQ/btsFLTNqYmI/gprXjHC38xpXP9dSCvppf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nFooQ/btsFLTNqYmI/gprXjHC38xpXP9dSCvppf1/img.png&quot; data-alt=&quot;그림11. Shader 에서 MaterialSlot 데이터를 로드하는 함수 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nFooQ/btsFLTNqYmI/gprXjHC38xpXP9dSCvppf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnFooQ%2FbtsFLTNqYmI%2FgprXjHC38xpXP9dSCvppf1%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;913&quot; height=&quot;493&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림11. Shader 에서 MaterialSlot 데이터를 로드하는 함수 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.6. Visibility Buffer 초기화&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;이제 본격적으로 Nanite 를 보기 위한 준비를 마쳤습니다. 아래 코드는 FDeferredShadingSceneRenderer 의 Render 함수 내부입니다. 먼저 전체 Nanite 코드의 레이아웃을 확인합니다. 각각의 함수들은 코드 덩치가 아주 크기 때문에 이후 세부사항도 차근차근 볼 예정입니다. &lt;br /&gt;1. Nanite 의 Rasterize 결과인 VisibilityBuffer 는 NaniteRasterResults 에 담깁니다. 그리고 PrimaryNaniteViews 는 Nanite 렌더링에 사용될 View 정보가 담깁니다. 현재는 BasePass 에 대해서 알아보는 중이기 때문에 View 의 개수는 1개로 고정이라고 생각하고 코드를 계속 보겠습니다. &lt;br /&gt;2. InitRasterContext 함수에서는 Nanite 의 Rasterize 패스를 진행하기 위해서 필요한 초기화를 수행합니다. 함수 내부 내용은&amp;nbsp;&amp;nbsp;그림13 에서 다시 봅시다. &lt;br /&gt;3.&amp;nbsp;CreateNaniteViews&amp;nbsp;람다&amp;nbsp;함수는&amp;nbsp;Nanite&amp;nbsp;에&amp;nbsp;사용할&amp;nbsp;View&amp;nbsp;를&amp;nbsp;만들어줍니다. &lt;br /&gt;4.&amp;nbsp;모든&amp;nbsp;View&amp;nbsp;에&amp;nbsp;대해서&amp;nbsp;NaniteView&amp;nbsp;를&amp;nbsp;만들어줍니다.&amp;nbsp;우리는&amp;nbsp;1개의&amp;nbsp;View&amp;nbsp;라고&amp;nbsp;생각하고&amp;nbsp;계속&amp;nbsp;코드를&amp;nbsp;보겠습니다. &lt;br /&gt;5.&amp;nbsp;CreateNaniteViews&amp;nbsp;람다&amp;nbsp;함수를&amp;nbsp;사용하여&amp;nbsp;NaniteView&amp;nbsp;를&amp;nbsp;실제로&amp;nbsp;만듭니다. &lt;br /&gt;6. Nanite::IRenderer::Create 는 FRenderer 를 생성해 줍니다. 이 클래스가 Culling 에서 Rasterize 까지 필요한 모든 자료를 갖고 있습니다.&lt;br /&gt;7.&amp;nbsp;&amp;nbsp;DrawGemoetry 함수에서 Two-Pass Occlusion Culling 과 SW, HW Rasterize 를 수행합니다. SceneInstanceCullQuery 는 기본적으로 nullptr 이기 때문에 무시합니다. &lt;br /&gt;8. Rasterize 결과를 FRasterResults 구조체로 옮겨줍니다. &lt;br /&gt;9.&amp;nbsp;Visibility&amp;nbsp;Buffer 로부터&amp;nbsp;Depth,&amp;nbsp;Stencil&amp;nbsp;과&amp;nbsp;같은&amp;nbsp;정보들을&amp;nbsp;추출합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1247&quot; data-origin-height=&quot;2936&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx2SCw/btsFKRW8iD6/7J3XC9BvWmLOx1BWkYFqjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx2SCw/btsFKRW8iD6/7J3XC9BvWmLOx1BWkYFqjk/img.png&quot; data-alt=&quot;그림12. Nanite 코드 중 Culling 부터 Visibility Buffer 생성 과정까지 전체 코드 레이아웃 (출처 : 레퍼런스12)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx2SCw/btsFKRW8iD6/7J3XC9BvWmLOx1BWkYFqjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx2SCw%2FbtsFKRW8iD6%2F7J3XC9BvWmLOx1BWkYFqjk%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;1247&quot; height=&quot;2936&quot; data-origin-width=&quot;1247&quot; data-origin-height=&quot;2936&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림12. Nanite 코드 중 Culling 부터 Visibility Buffer 생성 과정까지 전체 코드 레이아웃 (출처 : 레퍼런스12)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;InitRasterContext&amp;nbsp;내부&amp;nbsp;구현을&amp;nbsp;확인해 봅시다. &lt;br /&gt;1.&amp;nbsp;코드&amp;nbsp;읽는&amp;nbsp;복잡도를&amp;nbsp;줄이기&amp;nbsp;위해서&amp;nbsp;RasterScheduling&amp;nbsp;방식은&amp;nbsp;HardwareThenSoftware&amp;nbsp;로&amp;nbsp;가정하고&amp;nbsp;봅니다. &lt;br /&gt;2.&amp;nbsp;64&amp;nbsp;bit&amp;nbsp;uint&amp;nbsp;를&amp;nbsp;지원여부에&amp;nbsp;따라&amp;nbsp;VisibilityBuffer&amp;nbsp;의&amp;nbsp;포맷을&amp;nbsp;설정합니다. &lt;br /&gt;3.&amp;nbsp;DepthBuffer&amp;nbsp;와&amp;nbsp;VisibilityBuffer&amp;nbsp;를&amp;nbsp;생성합니다. &lt;br /&gt;4.&amp;nbsp;VisibilityBuffer&amp;nbsp;의&amp;nbsp;UAV&amp;nbsp;를&amp;nbsp;생성합니다. &lt;br /&gt;5.&amp;nbsp;AddClearVisBufferPass&amp;nbsp;함수를&amp;nbsp;호출해&amp;nbsp;VisibilityBuffer&amp;nbsp;의&amp;nbsp;내용을&amp;nbsp;초기화합니다. &lt;br /&gt;6.&amp;nbsp;AddClearVisBufferPass&amp;nbsp;함수의&amp;nbsp;내부에&amp;nbsp;진입합니다. &lt;br /&gt;7. 기본 설정의 RasterMode 는 DepthOnly 가 아닙니다. #define RASTER_CLEAR_DEPTH 0 &lt;br /&gt;8. 기본 설정의 bTiled 는 false 입니다. #define RASTER_CLEAR_TILED 0 &lt;br /&gt;9. CPU 측에서 RasterClear Shader 를 실행합니다. 그리고 GPU 측에서 VisibilityBuffer 를 0으로 초기화합니다. (초록색 화살표 참고)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1377&quot; data-origin-height=&quot;1770&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qMXBD/btsFLPEiFsR/BhmNeFF2kA10rqQUMKHFA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qMXBD/btsFLPEiFsR/BhmNeFF2kA10rqQUMKHFA0/img.png&quot; data-alt=&quot;그림13. Visibility Buffer 초기화 함수 (출처 : 레퍼런스1)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qMXBD/btsFLPEiFsR/BhmNeFF2kA10rqQUMKHFA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqMXBD%2FbtsFLPEiFsR%2FBhmNeFF2kA10rqQUMKHFA0%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;1377&quot; height=&quot;1770&quot; data-origin-width=&quot;1377&quot; data-origin-height=&quot;1770&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림13. Visibility Buffer 초기화 함수 (출처 : 레퍼런스1)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; 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;a href=&quot;https://scahp.tistory.com/127&quot;&gt;[UE5] Nanite (2/5)&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 레퍼런스&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1. &lt;a href=&quot;https://github.com/EpicGames/UnrealEngine/commit/072300df18a94f18077ca20a14224b5d99fee872&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/EpicGames/UnrealEngine/commit/072300df18a94f18077ca20a14224b5d99fee872&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;2. &lt;a href=&quot;https://www.youtube.com/watch?v=eviSykqSUUw&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=eviSykqSUUw&lt;/a&gt; &lt;a href=&quot;https://advances.realtimerendering.com/s2021/Karis_Nanite_SIGGRAPH_Advances_2021_final.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(Slide link)&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;3. &lt;a href=&quot;https://docs.unrealengine.com/5.0/en-US/nanite-virtualized-geometry-in-unreal-engine/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://docs.unrealengine.com/5.0/en-US/nanite-virtualized-geometry-in-unreal-engine/&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;4. &lt;a href=&quot;https://advances.realtimerendering.com/s2015/aaltonenhaar_siggraph2015_combined_final_footer_220dpi.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://advances.realtimerendering.com/s2015/aaltonenhaar_siggraph2015_combined_final_footer_220dpi.pdf&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;5. &lt;a href=&quot;https://scahp.tistory.com/41&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://scahp.tistory.com/41&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;6. &lt;a href=&quot;https://scahp.tistory.com/42&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://scahp.tistory.com/42&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;7. &lt;a href=&quot;https://scahp.tistory.com/97&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://scahp.tistory.com/97&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;8. &lt;a href=&quot;https://scahp.tistory.com/98&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://scahp.tistory.com/98&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;9. &lt;a href=&quot;https://scahp.tistory.com/99&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://scahp.tistory.com/99&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;10. &lt;a href=&quot;https://scahp.tistory.com/81&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://scahp.tistory.com/81&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;11. &lt;a href=&quot;https://scahp.tistory.com/74&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://scahp.tistory.com/74&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;12. &lt;a href=&quot;https://scahp.tistory.com/75&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://scahp.tistory.com/75&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;13. &lt;a href=&quot;https://zhuanlan.zhihu.com/p/603861270&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://zhuanlan.zhihu.com/p/603861270&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>UE4 &amp;amp; UE5/Rendering</category>
      <category>cluster</category>
      <category>Culling</category>
      <category>GPU Driven Rendering</category>
      <category>hzb</category>
      <category>mesh shader</category>
      <category>Meshdrawcommand</category>
      <category>Nanite</category>
      <category>rendering</category>
      <category>UE5</category>
      <category>visibility buffer renderer</category>
      <author>scahp</author>
      <guid isPermaLink="true">https://scahp.tistory.com/126</guid>
      <comments>https://scahp.tistory.com/126#entry126comment</comments>
      <pubDate>Thu, 14 Mar 2024 20:51:47 +0900</pubDate>
    </item>
    <item>
      <title>PathTracing (1/2)</title>
      <link>https://scahp.tistory.com/125</link>
      <description>&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;PathTracing(1/2)&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: right;&quot;&gt;최초 작성 : 2024-03-01&lt;br&gt;마지막 수정 : 2024-03-01&lt;br&gt;최재호&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&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;size14&quot; style=&quot;text-align: left;&quot;&gt;1.&amp;nbsp;목표&lt;br&gt;2.&amp;nbsp;내용&lt;br&gt;3.&amp;nbsp;구현&amp;nbsp;결과&lt;br&gt;&amp;nbsp; 3.1. Cornell box orig scene&lt;br&gt;&amp;nbsp; 3.2. Cornell box sphere scene &lt;br&gt;&amp;nbsp; 3.3. Hyperion sphere light scene &lt;br&gt;&amp;nbsp; 3.4. Hyperion rect lights scene &lt;br&gt;4.&amp;nbsp;실제&amp;nbsp;구현&amp;nbsp;코드 &lt;br&gt;5.&amp;nbsp;레퍼런스&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 목표&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;다양한 BSDF 를 평가해보고 변형해볼 수 있는 테스트 베드를 위한 PathTracer 를 제작합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이 글은 총 2개로 나뉘며, 첫 번째는 PathTracing 의 구현 기반을 마련하는 것을 목표로 합니다. 두 번째 글은 다양한 BSDF 를 도입하여 여러 가지 재질을 표현하는 것을 목표로 할 예정입니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;사전지식&lt;/b&gt;&lt;br&gt;- DXR or VkRaytracing 에 대한 이해&lt;br&gt;- PBR 에 대한 이해 (&lt;a href=&quot;https://scahp.tistory.com/96&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #111111;&quot;&gt;[UE4 PBR] Split sum appoximation 리뷰&lt;/span&gt;&lt;/span&gt;&lt;/a&gt; 참고)&lt;br&gt;- &lt;a href=&quot;https://scahp.tistory.com/53&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #111111;&quot;&gt;Radiometric Quantities&lt;/span&gt;&lt;/span&gt;&lt;/a&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; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;구현은 DirectX12 DXR, Vulkan VkRaytracing 을 사용하여 구현하며, 구현에 사용한 그래픽카드는 RTX3070 입니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 내용&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;PathTracer 에 사용할 Scene 은 레퍼런스1 에서 활용하고 있는 Scene 을 그대로 활용했습니다. 여러 Scene 중 이번에 사용할 Scene 은 Cornell box 와 Disney 의 hyperion scene 입니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이번 글에서는 아래 내용을 다룹니다.&lt;br&gt;&amp;nbsp;- 기본적인 Lambertian BRDF 와 Microfacet Specular BRDF 를 사용 (이 부분은 &lt;a href=&quot;https://scahp.tistory.com/96&quot; target=&quot;_self&quot;&gt;&lt;span&gt;&lt;span style=&quot;color: #111111;&quot;&gt;[UE4 PBR] Split sum appoximation 리뷰&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot;color: #111111;&quot;&gt; 참고)&lt;/span&gt;&lt;br&gt;&amp;nbsp;- PathTrace 를 통한 Radiance 계산 식&lt;br&gt;&amp;nbsp;- Glass sphere 의 Transmittance 기본 처리 (굴절만 하며, 100% 투과로 가정하고 사용함)&lt;br&gt;&amp;nbsp;&lt;br&gt;먼저 PathTracing 에 대해서 알아봅시다. PathTracing 은 눈에서부터 광원까지의 경로를 추척해가며 최종적으로 눈으로 들어오는 Radiance 를 평가합니다. Radiance 를 평가하는 동안 다양한 재질을 거쳐서 광원으로 이동하게 되며, 이 재질들은 각각의 특성에 따라서 빛을 특정 범위로 산란시킬 것입니다. 아래 그림1을 봐주세요. 그림1은 여러 산란 경로 중 하나를 보여줍니다. 눈과 광원인 P0, P3 를 제외한 P1, P2 에서는 재질에 따른 산란이 발생합니다. 리얼타임 렌더링에서 한 프레임에 이 수많은 경로를 모두 평가할 수 없기 때문에 PathTracing 에서는 경로에 대한 평가들을 매프레임 계속 누적합니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;861&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/S1Fs4/btsFqZzY7vw/tGIiLllMHYERIPFrMiaGvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/S1Fs4/btsFqZzY7vw/tGIiLllMHYERIPFrMiaGvk/img.png&quot; data-alt=&quot;그림1. PathTracing 의 한 경로에 대한 예제 f 는 BSDF, G 는 두 점의 감쇄정보를 나태나는 geometry term (출처 : 레퍼런스5)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/S1Fs4/btsFqZzY7vw/tGIiLllMHYERIPFrMiaGvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FS1Fs4%2FbtsFqZzY7vw%2FtGIiLllMHYERIPFrMiaGvk%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;1356&quot; height=&quot;861&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;861&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. PathTracing 의 한 경로에 대한 예제 f 는 BSDF, G 는 두 점의 감쇄정보를 나태나는 geometry term (출처 : 레퍼런스5)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;눈으로 입사하는 Radiancce 를 평가하기 위해서 우리는 Light Transport Equation 을 사용합니다. 그림2 의 식을 참고해 주세요.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;611&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wg0Av/btsFlQq5JQv/qaxnprsshVcDqnsjdaDSu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wg0Av/btsFlQq5JQv/qaxnprsshVcDqnsjdaDSu1/img.png&quot; data-alt=&quot;그림2. PathTracing (출처 : 레퍼런스5)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wg0Av/btsFlQq5JQv/qaxnprsshVcDqnsjdaDSu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWg0Av%2FbtsFlQq5JQv%2FqaxnprsshVcDqnsjdaDSu1%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;611&quot; height=&quot;70&quot; data-origin-width=&quot;611&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림2. PathTracing (출처 : 레퍼런스5)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;P1 점에서 P0(눈) 으로 들어오는 Radiance 를 계산하기 위해서는 P2, P3 에서 들어오는 Radiance 정보 또한 모두 모아야 합니다. 그림3 은 거기에 대한 식을 보여줍니다.&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;189&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coQExn/btsFmkefbz3/AYLWmlQKy4UvT8bAph7uYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coQExn/btsFmkefbz3/AYLWmlQKy4UvT8bAph7uYk/img.png&quot; data-alt=&quot;그림3. P1 -&amp;amp;amp;amp;gt; P0 으로 들어오는 Radiance 를 나타내는 식 (출처 : 레퍼런스5)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coQExn/btsFmkefbz3/AYLWmlQKy4UvT8bAph7uYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoQExn%2FbtsFmkefbz3%2FAYLWmlQKy4UvT8bAph7uYk%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;664&quot; height=&quot;189&quot; data-origin-width=&quot;664&quot; data-origin-height=&quot;189&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림3. P1 -&amp;amp;amp;gt; P0 으로 들어오는 Radiance 를 나타내는 식 (출처 : 레퍼런스5)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;실제로는 적분시 Monte Carlo 방식을 사용하기 때문에 적분식 내에 있는 식은 그림4 의 식으로 대체될 것입니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfWy3m/btsFp9bKmgI/76FrTmzfYeVkoGLnURs8R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfWy3m/btsFp9bKmgI/76FrTmzfYeVkoGLnURs8R1/img.png&quot; data-alt=&quot;그림4. 적분식을 Monte Carlo 방식으로 변경, p 는 pdf 를 나타냄. (출처 : 레퍼런스5)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfWy3m/btsFp9bKmgI/76FrTmzfYeVkoGLnURs8R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfWy3m%2FbtsFp9bKmgI%2F76FrTmzfYeVkoGLnURs8R1%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;322&quot; height=&quot;84&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;84&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림4. 적분식을 Monte Carlo 방식으로 변경, p 는 pdf 를 나타냄. (출처 : 레퍼런스5)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이 식을 코드로 나타내면 아래와 같이 나타낼 수 있습니다. 실제 손으로 P0~P3 의 지점에 적절한 데이터를 넣고 계산해 보면, 여러 프레임 동안 얻은 PathTracing 결과를 중첩 시 그림3과 같이 식이 된다는 것을 알 수 있습니다. (아래&lt;span style=&quot;color: #333333;&quot;&gt; 코드는 레퍼런스1, 2 를 참고하였습니다)&lt;/span&gt;&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// reygen shader
while (payload.RecursionDepth &amp;lt; MAX_RECURSION_DEPTH)
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Called TraceRay.. and then evaluate below code
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// payload.Radiance += material.emission;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// payload.Attenuation = BRDF_Cos / SamplePDF;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;/////////

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;radiance += attenuation * payload.Radiance;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;attenuation = max(0, attenuation * payload.Attenuation);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;...
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;P0~P3 지점까지 재귀적으로 Path 를 타고 들어가게 되는데 이 부분은 while 을 사용하여 들어갈 수 있는 최대 재귀 Depth 를 제한합니다. TraceRay 를 호출을 완료할 때마다 ClosestHit 으로 부터 얻어오는 payload 에서 다음 Ray 에 대한 정보를 얻고 계속해서 해당 Ray 를 반사합니다. 아래 코드를 확인해 주세요.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;while (payload.RecursionDepth &amp;lt; MAX_RECURSION_DEPTH)
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (payload.IsRayPenetratingInstance()) // for glass sphere transmittance
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TraceRay(Scene, RAY_FLAG_CULL_FRONT_FACING_TRIANGLES, ~0, 0, 0, 0, ray, payload);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TraceRay(Scene, RAY_FLAG_CULL_BACK_FACING_TRIANGLES, ~0, 0, 0, 0, ray, payload);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;radiance += attenuation * payload.Radiance;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;attenuation = max(0, attenuation * payload.Attenuation);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ray.Origin = payload.HitPos;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ray.Direction = payload.HitReflectDir;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;++payload.RecursionDepth;
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;RayTracer 를 만들면서 한 가지 고민되었던 점은 Glass 타입의 재질의 경우 투과를 고려해야 된다는 점입니다. 투과가 되는 경우 매질이 달라지기 때문에 굴절율이 생기고 매질이 달라지는 지점에서 빛의 경로를 굴절 시켜줘야 합니다. DXR, VkRayTracing 의 경우 TraceRay 를 사용할 때 RAY_FLAG_CULL_BACK_FACING_TRIANGLES와 RAY_FLAG_CULL_FRONT_FACING_TRIANGLES 를 사용하여 충돌할 삼각형의 감기 방향을 고려할 수 있습니다. 그래서 Glass 재질 타입을 전면에서 부딧치는 경우 payload 에 PenetratingInstnace 플래그를 활성화해주고, Penetrating 상태면, 삼각형의 후면과 충돌할 수 있도록 변경하여 물체 안을 뚫고 지나갈 수 있도록 구현했습니다. 위의 코드에서 해당 내용을 확인할 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;그리고 아래코드는 Glass material 의 BSDF 에 대한 코드입니다. 굴절을 해야 하는 경우 투과가 진행됩니다. 여기서 Normal 과 View 방향이 같은 경우 Glass material 에 진입, 그 반대의 경우 탈출의 경우로 판정하고 Penetrating 정보를 설정/해제하는 것을 볼 수 있습니다. 그리고 이 정보에 따라서 TraceRay 호출 시 Facing 방향을 조정합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;Glass material 은 현재 Radiance 가 흡수, 산란이 없는 100% 투과로 구현되어 있습니다. 이 부분은 다음글에서 BSDF 를 본격적으로 다루면서 수정할 예정입니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;void SamplingBRDF(out float3 SampleDir, out float SamplePDF, out float3 BRDF_Cos
, in float3 WorldNormal, in float3 WorldFaceNormal, in float3 SurfaceToView, in MaterialUniformBuffer mat, inout RayPayload payload)
{
...
else if (r3 &amp;lt; cdf[3]) // glassPart
{
...
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (Random_0_1(payload.seed) &amp;lt; F &amp;amp;&amp;amp; NoV &amp;gt; 0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SamplePDF = 1;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;BRDF_Cos = 1;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SampleDir = reflect(-SurfaceToView, WorldHalf);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SamplePDF = 1;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;BRDF_Cos = 1;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SampleDir = refract(-SurfaceToView, WorldHalf, eta);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (NoV &amp;gt; 0) // In glass material
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;payload.SetPenetratingInstnaceIndex(InstanceIndex());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else // Out from glass material
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;payload.ResetPenetratingInstanceIndex();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cosine_theta = 1.0f;
}
...
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;PathTracer 의 SamplingBSDF 의 나머지 두 파트인 Diffuse, Specular 파트는 레퍼런스4 의 UE5 PBR 에 있는 식을 기반으로 사용했습니다. Specular BRDF 에서는 Anisotropy 가 적용되지 않았는데, 다음글에서 해당내용을 개선하면 좋을 것 같습니다. 아래 코드를 참고해 주세요.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;void SamplingBRDF(out float3 SampleDir, out float SamplePDF, out float3 BRDF_Cos
, in float3 WorldNormal, in float3 WorldFaceNormal, in float3 SurfaceToView, in MaterialUniformBuffer mat, inout RayPayload payload)
{
...
float r3 = Random_0_1(payload.seed);
if (r3 &amp;lt; cdf[0]) // diffusePart
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Lambertian surface
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SampleDir = CosWeightedSampleHemisphere(payload.seed);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cosine_theta = SampleDir.z;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SampleDir = ToWorld(WorldNormal, SampleDir);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (diffusePart &amp;gt; 0.0f)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (cosine_theta &amp;lt; 0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Underneath skip.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float3 BRDF = INV_PI * mat.baseColor;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;BRDF_Cos += BRDF * diffusePart;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SamplePDF += (INV_PI * cosine_theta) * diffuseWeight;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}
else if (r3 &amp;lt; cdf[1]) // specularPart
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Cook torrance brdf from https://cdn2.unrealengine.com/Resources/files/2013SiggraphPresentationsNotes-26915738.pdf
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float r1 = Random_0_1(payload.seed);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float r2 = Random_0_1(payload.seed);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WorldHalf = normalize(ImportanceSampleGGX(float2(r1, r2), mat.roughness, WorldNormal));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SampleDir = reflect(-SurfaceToView, WorldHalf);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;cosine_theta = saturate(dot(WorldNormal, SampleDir));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (specularPart &amp;gt; 0.0f)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float NoV = saturate(dot(WorldNormal, SurfaceToView));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float NoL = saturate(dot(WorldNormal, SampleDir));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float NoH = saturate(dot(WorldNormal, WorldHalf));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float VoH = saturate(dot(SurfaceToView, WorldHalf));

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (cosine_theta &amp;lt; 0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Underneath skip.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;else
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float3 F0 = mat.baseColor;

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float D = DistributionGGX(WorldNormal, WorldHalf, mat.roughness);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float G = GeometrySmith(mat.roughness, NoV, NoL);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float3 F = FresnelSchlick(cosine_theta, F0);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;float3 BRDF = F * (D * G / (4 * NoL * NoV));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;BRDF_Cos += BRDF * specularPart;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SamplePDF += (D * NoH / (4 * VoH)) * specularWeight;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}
...
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;구현에 대한 내용은 여기까지입니다. 다음글에서는 이 PathTracer 를 기반으로 다양한 BSDF 를 평가해 보겠습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3.&amp;nbsp;구현&amp;nbsp;결과&lt;/b&gt;&lt;/h3&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;3.1. Cornell box orig scene&lt;/h4&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bawz4o/btsFoc7SpGF/cNaN1NdbBX2aZ6D1cjn6pK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bawz4o/btsFoc7SpGF/cNaN1NdbBX2aZ6D1cjn6pK/img.png&quot; data-alt=&quot;그림5. Cornell box orig (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bawz4o/btsFoc7SpGF/cNaN1NdbBX2aZ6D1cjn6pK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbawz4o%2FbtsFoc7SpGF%2FcNaN1NdbBX2aZ6D1cjn6pK%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;1282&quot; height=&quot;752&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림5. Cornell box orig (출처 : 직접구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;3.2. Cornell box sphere scene&lt;/h4&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bR7Zn7/btsFoeSaQ2r/3CsMYlh1HyLDvgrBge4d91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bR7Zn7/btsFoeSaQ2r/3CsMYlh1HyLDvgrBge4d91/img.png&quot; data-alt=&quot;그림6. Cornell box sphere (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bR7Zn7/btsFoeSaQ2r/3CsMYlh1HyLDvgrBge4d91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbR7Zn7%2FbtsFoeSaQ2r%2F3CsMYlh1HyLDvgrBge4d91%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;1282&quot; height=&quot;752&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림6. Cornell box sphere (출처 : 직접구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;3.3. Hyperion sphere light scene&lt;/h4&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bASc2h/btsFm5OKSa7/evgX5rqEBbNE799pvXq4V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bASc2h/btsFm5OKSa7/evgX5rqEBbNE799pvXq4V1/img.png&quot; data-alt=&quot;그림7. Hyperion sphere light (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bASc2h/btsFm5OKSa7/evgX5rqEBbNE799pvXq4V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbASc2h%2FbtsFm5OKSa7%2FevgX5rqEBbNE799pvXq4V1%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;1282&quot; height=&quot;752&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림7. Hyperion sphere light (출처 : 직접구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot; data-ke-size=&quot;size20&quot;&gt;3.4. Hyperion rect lights scene&lt;/h4&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zVFPg/btsFkWZiHMI/fCbKbiRcdRX1P0q2UEo4q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zVFPg/btsFkWZiHMI/fCbKbiRcdRX1P0q2UEo4q1/img.png&quot; data-alt=&quot;그림8. Hyperion sphere light (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zVFPg/btsFkWZiHMI/fCbKbiRcdRX1P0q2UEo4q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzVFPg%2FbtsFkWZiHMI%2FfCbKbiRcdRX1P0q2UEo4q1%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;1282&quot; height=&quot;752&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림8. Hyperion sphere light (출처 : 직접구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4.&amp;nbsp;실제&amp;nbsp;구현&amp;nbsp;코드&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://github.com/scahp/jEngine/tree/PathTracingBasis&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://github.com/scahp/jEngine/tree/PathTracingBasis&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5.&amp;nbsp;레퍼런스&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;1. &lt;a href=&quot;https://github.com/knightcrawler25/GLSL-PathTracer&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://github.com/knightcrawler25/GLSL-PathTracer&lt;/span&gt;&lt;/a&gt;&lt;br&gt;2. &lt;a href=&quot;https://github.com/phgphg777/DXR-PathTracer&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://github.com/phgphg777/DXR-PathTracer&lt;/span&gt;&lt;/a&gt;&lt;br&gt;3. &lt;a href=&quot;https://pbr-book.org/4ed/contents&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://pbr-book.org/4ed/contents&lt;/span&gt;&lt;/a&gt;&lt;br&gt;4. &lt;a href=&quot;https://scahp.tistory.com/96&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://scahp.tistory.com/96&lt;/span&gt;&lt;/a&gt;&lt;br&gt;5. &lt;a href=&quot;https://pbr-book.org/4ed/Light_Transport_I_Surface_Reflection/Path_Tracing&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://pbr-book.org/4ed/Light_Transport_I_Surface_Reflection/Path_Tracing&lt;/span&gt;&lt;/a&gt;&lt;br&gt;6. &lt;a href=&quot;https://scahp.tistory.com/53&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://scahp.tistory.com/53&lt;/span&gt;&lt;/a&gt;&lt;br&gt;7. &lt;a href=&quot;https://pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/The_Light_Transport_Equation&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://pbr-book.org/3ed-2018/Light_Transport_I_Surface_Reflection/The_Light_Transport_Equation&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Graphics/Graphics</category>
      <category>BRDF</category>
      <category>BSDF</category>
      <category>DX12</category>
      <category>DXR</category>
      <category>glass</category>
      <category>PathTracer</category>
      <category>PathTracing</category>
      <category>transmittance</category>
      <category>VkRayTracing</category>
      <category>vulkan</category>
      <author>scahp</author>
      <guid isPermaLink="true">https://scahp.tistory.com/125</guid>
      <comments>https://scahp.tistory.com/125#entry125comment</comments>
      <pubDate>Fri, 1 Mar 2024 23:30:57 +0900</pubDate>
    </item>
    <item>
      <title>AsyncCompute - DX12, Vulkan</title>
      <link>https://scahp.tistory.com/124</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;AsyncCompute&amp;nbsp;-&amp;nbsp;DX12,&amp;nbsp;Vulkan&lt;/b&gt;&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: right;&quot;&gt;최초 작성 : 2024-02-13&lt;br&gt;마지막 수정 : 2024-02-13&lt;br&gt;최재호&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;size14&quot;&gt;1.&amp;nbsp;목표 &lt;br&gt;2.&amp;nbsp;내용 &lt;br&gt;&amp;nbsp; 2.1. 사전에 알아야 할 것들 &lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; &lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; 2.1.1.&amp;nbsp;CommandQueue &lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; &lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; 2.1.2.&amp;nbsp;동일한&amp;nbsp;CommandQueue&amp;nbsp;내&amp;nbsp;렌더커맨드의&amp;nbsp;실행&amp;nbsp;순서 &lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; &lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; 2.1.3.&amp;nbsp;Resource&amp;nbsp;Barrier &lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; 2.2.&amp;nbsp;CommandQueue&amp;nbsp;간의&amp;nbsp;동기화 &lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; 2.3.&amp;nbsp;CommandQueue&amp;nbsp;간의&amp;nbsp;동기화&amp;nbsp;구현 &lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; &lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; 2.3.1.&amp;nbsp;현재&amp;nbsp;CommandQueue&amp;nbsp;에서&amp;nbsp;실행&amp;nbsp;한&amp;nbsp;렌더커맨드가&amp;nbsp;모두&amp;nbsp;실행될&amp;nbsp;때까지&amp;nbsp;기다릴&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;동기화&amp;nbsp;정보를&amp;nbsp;제공 &lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; &lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; 2.3.2.&amp;nbsp;특정&amp;nbsp;CommandQueue&amp;nbsp;가&amp;nbsp;앞서&amp;nbsp;다른&amp;nbsp;CommandQueue&amp;nbsp;에서&amp;nbsp;받은&amp;nbsp;동기화&amp;nbsp;정보를&amp;nbsp;사용&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; 2.4.&amp;nbsp;실제&amp;nbsp;사용&amp;nbsp;예제 &lt;br&gt;3.&amp;nbsp;테스트&amp;nbsp;케이스 &lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; 3.1.&amp;nbsp;Graphics&amp;nbsp;Queue&amp;nbsp;에서&amp;nbsp;모두&amp;nbsp;실행하는&amp;nbsp;경우 &lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; 3.2. Atmospheric 가 모두 실행된 후 AsyncComputeTest 가 모두 실행되고나서 PostProcess 가 실행된 경우&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; 3.3. Atmospheric 가 모두 실행된 후 AsyncComputeTest 가 수행되고, AsyncComputeTest 와 PostProcessPass 는 겹침&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; 3.4. BasePass 가 모두 실행된 후 AsyncComputeTest 가 수행되고, 이후 모든 패스와 겹침&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; 3.5. ShadowPass 가 모두 실행된 후 AsyncComputeTestPass 가 수행되고, 이후 모든 패스와 겹침&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt; 3.6. AsyncComputeTest 가 바로 수행되어 모든 패스와 겹침 &lt;br&gt;4.&amp;nbsp;레퍼런스&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 목표&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;AsyncCompute 를 사용하는데 고려해야 할 것들을 알아봅니다.&lt;br&gt;DX12, Vulkan 에 AsyncCompute 를 구현하여 테스트해 봅니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;사전지식&lt;/b&gt;&lt;br&gt;DX12, Vulkan API 렌더링 커맨드의 생성과 실행&lt;br&gt;Graphics API : Fence, Semaphore&lt;br&gt;Vulkan 의 vkQueueSubmit 에 사용되는 Signal, Wait semaphore 에 대한 이해&lt;/p&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 내용&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 AsyncCompute 에 대해서 알아볼 것입니다. GPU 에 렌더커맨드를 전달할 때, 일반적으로 Graphics CommandQueue 에 필요한 렌더커맨드를 제출합니다. 하지만 특정 렌더커맨드는 GPU 내부의 Shader core 를 충분히 채우지 못해서 처리량이 낮을 수 있습니다. 이런 경우 AsyncCompute 가 좋은 대안이 될 수 있습니다. AsyncCompute 는 Graphics CommandQueue 에서 실행 중인 작업과 종속성이 없는 Compute Shader 작업들을 Compute CommandQueue 에 밀어 넣어서 사용하지 못하던 Shader core 를 채울 수 있습니다. 이런 방식으로 작업 처리량을 늘리는 것이 AsyncCompute 를 사용하는 목적입니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;물론 Graphics CommandQueue 내에서 적절하게 종속성을 잘 관리해 줘서 추가 CommandQueue 사용하지 않아도 되면 좋겠지만 CommandQueue 내에서 관리되는 Barrier 나 다양한 상황에서 이 부분을 완벽하게 해결하기는 어렵습니다. 레퍼런스1 에서는 이런 점을 아주 잘 설명해주고 있습니다. (개인적으로는 레퍼런스2 의 GDC 의 요약본을 먼저 보고 보시는 게 이해하는데 더 쉽다고 생각됩니다.)&lt;br&gt;&amp;nbsp;&lt;br&gt;AsyncCompute 를 본격적으로 시작하기 전 먼저 알아야 할 부분들을 가볍게 둘러봅시다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.1. 사전에 알아야 할 것들&lt;/b&gt;&lt;/h4&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.1.1. CommandQueue&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;실제 렌더링에 사용되는 렌더커맨드들을 커맨드리스트에 녹화한 다음에 GPU 에게 실행해 달라고 전달해야 하는 통로가 바로 CommandQueue 입니다. CommandQueue 는 총 3가지 타입이 있습니다. 각각의 타입은 하위 타입의 기능을 모두 가지고 있습니다. (Graphics &amp;gt; Compute &amp;gt; Copy)&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1474&quot; data-origin-height=&quot;643&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FbnGW/btsEIO7VVWY/BRdT7g8JL2uKC0dBNXucL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FbnGW/btsEIO7VVWY/BRdT7g8JL2uKC0dBNXucL1/img.png&quot; data-alt=&quot;그림1. 3가지 타입의 CommandQueue, 3D Queue 는 Graphics, Compute, Copy 커맨드를 받을 수 있음. Compute Queue 는 Compute, Copy 커맨드를 받을 수 있음. Copy Queue 는 Copy 커맨드를 받을 수 있음. (출처 : 레퍼런스3)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FbnGW/btsEIO7VVWY/BRdT7g8JL2uKC0dBNXucL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFbnGW%2FbtsEIO7VVWY%2FBRdT7g8JL2uKC0dBNXucL1%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;686&quot; height=&quot;299&quot; data-origin-width=&quot;1474&quot; data-origin-height=&quot;643&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. 3가지 타입의 CommandQueue, 3D Queue 는 Graphics, Compute, Copy 커맨드를 받을 수 있음. Compute Queue 는 Compute, Copy 커맨드를 받을 수 있음. Copy Queue 는 Copy 커맨드를 받을 수 있음. (출처 : 레퍼런스3)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.1.2. 동일한 CommandQueue 내 렌더커맨드의 실행 순서&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그림2의 렌더독에서 캡처한 렌더커맨드를 보면 모든 커맨드들이 순서대로 실행된 것처럼 보입니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;499&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t9Z24/btsEQcT8mTH/G2GK6SCqiKjCztjXEiMjmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t9Z24/btsEQcT8mTH/G2GK6SCqiKjCztjXEiMjmK/img.png&quot; data-alt=&quot;그림2. RederDoc 에서 실행중인 렌더커맨드는 녹화한 순서대로 실행되는 것 보여짐. 하지만 실제로는 병렬로 동작함.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t9Z24/btsEQcT8mTH/G2GK6SCqiKjCztjXEiMjmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft9Z24%2FbtsEQcT8mTH%2FG2GK6SCqiKjCztjXEiMjmK%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;364&quot; height=&quot;356&quot; data-origin-width=&quot;510&quot; data-origin-height=&quot;499&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림2. RederDoc 에서 실행중인 렌더커맨드는 녹화한 순서대로 실행되는 것 보여짐. 하지만 실제로는 병렬로 동작함.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;하지만 내부적으로는 렌더커맨드들이 병렬로 동작하고 있습니다. 아래 그림3의 PIX 에서 촬영한 렌더커맨드를 보면 커맨드들이 병렬로 동작하는 것을 볼 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;669&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbwu1B/btsERxXNS3h/XZ11VXiHhvyfrOdsI8b0Kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbwu1B/btsERxXNS3h/XZ11VXiHhvyfrOdsI8b0Kk/img.png&quot; data-alt=&quot;그림3. PIX 로 촬영해보면 렌더커맨드가 순서대로 호출되는게 아니라 병렬로 동시에 실행되는 것을 볼 수 있음. 파란색 라인이 Graphics Queue 실행되는 명령어들을 나타내며 각각의 라인들이 DrawIndexedInstanced 를 나타냄.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbwu1B/btsERxXNS3h/XZ11VXiHhvyfrOdsI8b0Kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbwu1B%2FbtsERxXNS3h%2FXZ11VXiHhvyfrOdsI8b0Kk%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;541&quot; height=&quot;559&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;669&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림3. PIX 로 촬영해보면 렌더커맨드가 순서대로 호출되는게 아니라 병렬로 동시에 실행되는 것을 볼 수 있음. 파란색 라인이 Graphics Queue 실행되는 명령어들을 나타내며 각각의 라인들이 DrawIndexedInstanced 를 나타냄.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;하지만 항상 렌더커맨드들이 병렬로 실행될 수는 없으며, 렌더커맨드 사이에 종속성이 필요한 경우도 있습니다. 예를 들면 앞서 실행된 렌더커맨드1 은 텍스쳐A 에 렌더링을 하고, 이어서 실행되는 렌더커맨드2 는 렌더커맨드1 을 통해 렌더링 된 텍스쳐A 를 사용하여 추가 처리를 한다고 합시다. 이런 경우는 렌더커맨드1 이 완료된 다음 렌더커맨드2 가 실행되어야 할 것입니다. 그리고 이 실행 순서만 동기화되는 것이 아니라 실행 결과로 갱신된 메모리들도 다른 Shader core 들에서 사용(관찰, Visibility) 할 수 있어야 합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;렌더커맨드 사이의 종속성을 관리하기 위해서는 Resource Barrier 를 사용할 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.1.3. Resource Barrier&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Barrier 는 동일한 CommandQueue 내의 실행 순서 보장(Execution dependency) 와 실행 결과로 갱신된 메모리의 Visibility 를 보장해 줍니다.&lt;br&gt;DX12 에서는 Resource Barrier, Vulkan 에서는 Pipeline Barrier 라 불리는 기능을 통해 이 과정을 수행합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;Barrier 가 어떻게 종속성을 관리할지 생각해 봅시다. 여기서는 DX12 를 기준으로 Barrier 를 소개하겠습니다. Barrier 사용에 대한 차이는 jEngine 의 ResourceBarrierBatcher 클래스를 비교해 볼 수 있습니다.&lt;br&gt;- Vulkan BarrierBatcher&amp;nbsp; :&amp;nbsp;&lt;a href=&quot;https://github.com/scahp/jEngine/blob/AsyncCompute/jEngine/RHI/Vulkan/jResourceBarrierBatcher_Vulkan.cpp&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://github.com/scahp/jEngine/blob/AsyncCompute/jEngine/RHI/Vulkan/jResourceBarrierBatcher_Vulkan.cpp&lt;/span&gt;&lt;/a&gt;&lt;br&gt;- DX12 BarrierBatcher : &lt;a href=&quot;https://github.com/scahp/jEngine/blob/AsyncCompute/jEngine/RHI/DX12/jResourceBarrierBatcher_DX12.cpp&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://github.com/scahp/jEngine/blob/AsyncCompute/jEngine/RHI/DX12/jResourceBarrierBatcher_DX12.cpp&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;추가로 Vulkan 의 Pipeline Barrier 의 경우 Execution dependency 와 Memory Visibility 를 더 세밀하게 컨트롤할 수 있습니다. 레퍼런스5 영상에 Vulkan Barrier 에 대한 좋은 내용이 있습니다. 이 글에서는 AsyncCompute 에 초점을 두고 있기 때문에 Barrier 에 대한 더 자세한 내용까지는 다루지 않을 예정입니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;Barrier 를 사용하는 간단한 예를 확인해 봅시다.&lt;br&gt;텍스쳐가 하나 있고, 해당 텍스쳐를 D3D12_RESOURCE_STATE_RENDER_TARGET(쓰기) → D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE(읽기) 상태로 Resource Transition 을 요청했다고 합시다. 아래 코드는 DX12 의 Barrier 를 사용하여 리소스 상태를 전환하는 예제입니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = InResource;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
CommandList-&amp;gt;ResourceBarrier(1, &amp;amp;barrier);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;GPU 는 이 Barrier 가 호출된 시점을 기준으로 2가지를 알 수 있을 것입니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;Barrier 보다 앞서 실행된 Command 가 텍스쳐 리소스를 RenderTarget 으로 사용하여 데이터를 기록함.&lt;/li&gt;&lt;li&gt;Barrier 의 실행 이후 다음 실행된 Command 가 해당 텍스쳐의 데이터를 &lt;span style=&quot;color: #333333;&quot;&gt;PixelShader 내에서&lt;/span&gt; 읽는 데 사용함.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 정보를 알고 있다면 Barrier 전/후 렌더커맨드는 서로 실행에 종속성이 걸려있고, Memory visibility 또한 보장되어야 한다고 알 수 있을 것입니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;앞에서 설명한 종속성은 Resource 가 기록되고 읽혀지는 상태 기반으로 종속성을 알아냅니다. 하지만 Compute Shader 가 연속적으로 실행된다면 어떨까요? Compute Shader 는 UAV 타입의 리소스를 사용하여 읽기/쓰기를 동시에 처리할 수 있습니다. Compute Shader 1, 2, 3 이 차례로 실행되지만 Resource Transition 이 전혀 필요 없을 수 있습니다. 이럴 때 사용할 수 있는 것이 UAV Barrier 입니다. 아래 코드를 참고해 주세요.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_UAV;
barrier.UAV.pResource = InResource;
CommandList-&amp;gt;ResourceBarrier(1, &amp;amp;barrier);&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;2.2. CommandQueue 간의 동기화&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;다시 AsyncCompute 로 돌아왔습니다. AsyncCompute 는 Graphics ComandQueue 에 전달한 작업이 GPU 의 Shader core 사용율을 가득 채울 수 없는 경우에 Compute CommandQueue 를 통해 추가로 렌더커맨드를 제출하여 Shader core 의 사용율 높일 수 있다고 하였습니다. Compute CommandQueue 에서 실행이 마친 후 생성된 데이터(텍스쳐나 버퍼)는&amp;nbsp; Graphics CommandQueue 에서 사용되어서 최종 결과물에 영향을 미칠 것입니다. 그렇다는 말은 AsyncCompute 를 사용하기 위해서는 CommandQueue 간의 동기화 기능이 추가로 필요하다는 것입니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;예를 들면, Graphics Queue 에서 ShadowDepth 를 렌더링하는 동안 Compute Command Queue 에서 SSAO 작업을 동시에 수행합니다. 그리고 Graphics Queue 에서 Compute CommandQueue 에서 생성한 SSAO 를 최종 렌더타겟에 적용합니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이런 과정을 위해서는 CommandQueue 간의 동기화를 책임져줄 동기화 객체가 필요합니다. 아래 그림4는 Fence 를 사용하여 CommandQueue 간의 동기화를 맞추는 것을 보여줍니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wIiTZ/btsEPHNEVpj/bl9HG3GnnhI0yJljYxAFmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wIiTZ/btsEPHNEVpj/bl9HG3GnnhI0yJljYxAFmk/img.png&quot; data-alt=&quot;그림4. DX12 에서 Fence 를 사용하여 CommandQueue 간의 동기화를 보여줌. (출처 : 레퍼런스4)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wIiTZ/btsEPHNEVpj/bl9HG3GnnhI0yJljYxAFmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwIiTZ%2FbtsEPHNEVpj%2Fbl9HG3GnnhI0yJljYxAFmk%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;753&quot; height=&quot;603&quot; data-origin-width=&quot;753&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림4. DX12 에서 Fence 를 사용하여 CommandQueue 간의 동기화를 보여줌. (출처 : 레퍼런스4)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;br&gt;위에서 본 것처럼 DX12 는 CommandQueue 간의 동기화를 위해서 Fence 를 사용합니다. Fence 는 CPU ↔ GPU 간의 동기화에도 사용할 수 있고 CommandQueue 의 Signal, Wait 함수를 통하여 동기화를 구현합니다. Fence 에 대한 더 자세한 설명은 레퍼런스6 을 참고해 주세요.&lt;br&gt;&amp;nbsp;&lt;br&gt;Vulkan 의 경우는 Semaphore 나 Fence 를 사용할 수 있는데 여기서는 Timeline Semaphore 를 사용하여 동기화 하는 것을 알아볼 예정입니다. Timeline Semaphore 는 DX12 의 Fence 와 같이 내부적으로 uint64 변수를 갖고 있으며 계속해서 해당 값이 증가하는 형태로 구현됩니다. Timeline Semaphore 에 대해서는 레퍼런스5 를 확인해주세요. Fence 로도 동기화 할 수 있지만 Fence 의 경우 CPU ↔ GPU 간의 동기화까지 고려하고 있습니다. 여기서는 CommandQueue 간의 동기화만 만들면 되기 때문에 GPU 의 Queue 간의 동기화만 할 수 있는 Timeline Semaphore 를 선택하는 것이 좋다고 생각했습니다.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2.3. CommandQueue 간의 동기화 구현&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;AsyncCompute 에 실행할 작업을 특정 렌더패스(Graphics Queue 를 사용하는)에서 발행하고, Compute Queue 의 작업을 기다렸다가 작업을 마친 결과를 특정 렌더패스에서 사용하는 형태로 사용하게 될 것입니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnCrsc/btsEGNVZiif/kAp74JhEDHsekU56Xf0k20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnCrsc/btsEGNVZiif/kAp74JhEDHsekU56Xf0k20/img.png&quot; data-alt=&quot;그림5. AsyncCompute 작업 예시 (출처 : 직접그림)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnCrsc/btsEGNVZiif/kAp74JhEDHsekU56Xf0k20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnCrsc%2FbtsEGNVZiif%2FkAp74JhEDHsekU56Xf0k20%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;794&quot; height=&quot;219&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;219&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림5. AsyncCompute 작업 예시 (출처 : 직접그림)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;이 부분을 고려해 봤을 때, 구현해야 할 기능은 크게 2가지입니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;현재 CommandQueue 에서 실행 한 렌더커맨드가 모두 실행될 때까지 기다릴 수 있도록 동기화 정보를 제공&lt;/li&gt;&lt;li&gt;특정 CommandQueue 가 앞서 다른 CommandQueue 에서 받은 동기화 정보를 사용하여 해당 동기화 지점까지 렌더커맨드가 실행되는 것을 기다릴 수 있어야 함.&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;2.3.1. &lt;/b&gt;&lt;b&gt;현재 CommandQueue 에서 실행 한 렌더커맨드가 모두 실행될 때까지 기다릴 수 있도록 동기화 정보를 제공&lt;/b&gt;&lt;br&gt;현재 CommandQueue 에 전달한 렌더커맨드가 잘 실행되었는지 알 수 있으려면 아래 내용들이 필요할 것입니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;어떤 CommandQueue 에 렌더커맨드를 넣었나?&lt;/li&gt;&lt;li&gt;CommandQueue 의 실행이 완료되었음을 알 수 있는 동기화 객체&lt;/li&gt;&lt;/ul&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;jEngine 에서는 jSyncAcrossComputeQueue 를 통해서 위의 정보를 넣어줬습니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// Make a syncronization between CommandQueues(Graphics, Compute, Copy)
struct jSyncAcrossCommandQueue : std::enable_shared_from_this&amp;lt;jSyncAcrossCommandQueue&amp;gt;
{
&amp;nbsp;&amp;nbsp;virtual ~jSyncAcrossCommandQueue() {}
&amp;nbsp;&amp;nbsp;virtual void WaitSyncAcrossCommandQueue(ECommandBufferType InWaitCommandQueueType) {};
};

// DX12 is using Fence for sync between CommandQueues
struct jSyncAcrossCommandQueue_DX12 : public jSyncAcrossCommandQueue
{
&amp;nbsp;&amp;nbsp;jSyncAcrossCommandQueue_DX12(ECommandBufferType InType, jFence_DX12* InFence, uint64 InFenceValue = -1);
&amp;nbsp;&amp;nbsp;virtual ~jSyncAcrossCommandQueue_DX12() {}
&amp;nbsp;&amp;nbsp;virtual void WaitSyncAcrossCommandQueue(ECommandBufferType InWaitCommandQueueType) override;

&amp;nbsp;&amp;nbsp;ECommandBufferType Type = ECommandBufferType::MAX;
&amp;nbsp;&amp;nbsp;jFence_DX12* Fence = nullptr;
&amp;nbsp;&amp;nbsp;uint64 FenceValue = 0;
};

// Vulkan is using Semaphore for sync between CommandQueues
struct jSyncAcrossCommandQueue_Vulkan : public jSyncAcrossCommandQueue
{
&amp;nbsp;&amp;nbsp;jSyncAcrossCommandQueue_Vulkan(ECommandBufferType InType, jSemaphore_Vulkan* InWaitSemaphore, uint64 InSemaphoreValue = -1);
&amp;nbsp;&amp;nbsp;virtual ~jSyncAcrossCommandQueue_Vulkan() {}
&amp;nbsp;&amp;nbsp;virtual void WaitSyncAcrossCommandQueue(ECommandBufferType InWaitCommandQueueType) override;

&amp;nbsp;&amp;nbsp;ECommandBufferType Type = ECommandBufferType::MAX;
&amp;nbsp;&amp;nbsp;jSemaphore_Vulkan* WaitSemaphore = nullptr;
&amp;nbsp;&amp;nbsp;uint64 SemaphoreValue = 0;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// for timeline semaphore
};&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;&lt;b&gt;2.3.2. 특정&amp;nbsp;CommandQueue&amp;nbsp;가&amp;nbsp;앞서&amp;nbsp;다른&amp;nbsp;CommandQueue&amp;nbsp;에서&amp;nbsp;받은&amp;nbsp;동기화&amp;nbsp;정보를&amp;nbsp;사용&lt;/b&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;이제 jSyncAcrossComputeQueue 를 갖고 있으면 언제든 WaitSyncAcrossComputeQueue 함수를 사용하여 앞서 다른 CommandQueue 에서 실행한 렌더커맨드의 실행을 기다릴 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;DX12 와 Vulkan 에서의 구현이 각각 다른데 차례로 알아봅시다.&lt;br&gt;DX12 의 경우는 Fence 를 사용하기 때문에 간단히 CommandQueue 에 Wait 함수를 호출하여 Fence 가 Signal 되기를 기다립니다. 아래 코드를 확인해 주세요.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// Passing jSyncAcrossCommandQueue to TargetCommandQueue that want to wait for
void jSyncAcrossCommandQueue_DX12::WaitSyncAcrossCommandQueue(ECommandBufferType InWaitCommandQueueType)
{
&amp;nbsp;&amp;nbsp;if (!ensure(InWaitCommandQueueType != Type))&amp;nbsp;&amp;nbsp;// Can't wait same type queue
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return;

&amp;nbsp;&amp;nbsp;auto CommandBufferManager = g_rhi-&amp;gt;GetCommandBufferManager(InWaitCommandQueueType);
&amp;nbsp;&amp;nbsp;CommandBufferManager-&amp;gt;WaitCommandQueueAcrossSync(shared_from_this());
}

// The CommandQueue wait for Fence of jSyncAcrossCommandQueue
void jCommandBufferManager_DX12::WaitCommandQueueAcrossSync(const std::shared_ptr&amp;lt;jSyncAcrossCommandQueue&amp;gt;&amp;amp; InSync)
{
&amp;nbsp;&amp;nbsp;auto Sync_DX12 = (jSyncAcrossCommandQueue_DX12*)InSync.get();
&amp;nbsp;&amp;nbsp;CommandQueue-&amp;gt;Wait(Sync_DX12-&amp;gt;Fence-&amp;gt;Fence.Get(), Sync_DX12-&amp;gt;FenceValue);
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;Vulkan 의 경우 즉시 Wait 되는 것이 아니라 vkQueueSubmit 이 호출되는 시점에 &lt;span style=&quot;color: #333333;&quot;&gt;Semaphore 를 같이 전달하여&lt;/span&gt; 종속성을 만들어 줍니다. 그래서 WaitSyncAcrossCommandQueue 를 호출하면 즉시 동기화 지점을 기다리지 않고, jSyncAcrossCommandQueue 가 가지고 있는 동기화 객체를 기다릴 CommandQueue 에 다음 vkQueueSubmit 이 있을 때 해당 동기화 객체를 사용할 수 있게 등록만 합니다. 그리고 최종적으로 vkQueueSubmit 이 진행될 때 동기화 객체로부터 WaitSemaphore 와 WaitSemaphoreValue 를 추출하여 전달합니다. 아래 코드를 확인해 주세요.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// Enqueued jSyncAcrossCommandQueue to WaitSemaphoreAcrossQueues then use it at vkQueueSubmit.
void jCommandBufferManager_Vulkan::WaitCommandQueueAcrossSync(
&amp;nbsp;&amp;nbsp;const std::shared_ptr&amp;lt;jSyncAcrossCommandQueue&amp;gt;&amp;amp; InSync)
{
&amp;nbsp;&amp;nbsp;WaitSemaphoreAcrossQueues.push_back(InSync);
}

// Extract Semaphore and Semaphore Value which to wait, It will use at vkQueueSubmit.
void jCommandBufferManager_Vulkan::GetWaitSemaphoreAndValueThenClear(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;std::vector&amp;lt;VkSemaphore&amp;gt;&amp;amp; InOutSemaphore, std::vector&amp;lt;uint64&amp;gt;&amp;amp; InOutSemaphoreValue)
{
&amp;nbsp;&amp;nbsp;if (WaitSemaphoreAcrossQueues.empty())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return;

&amp;nbsp;&amp;nbsp;check(InOutSemaphore.size() == InOutSemaphoreValue.size());

&amp;nbsp;&amp;nbsp;InOutSemaphore.reserve(InOutSemaphore.size() + WaitSemaphoreAcrossQueues.size());
&amp;nbsp;&amp;nbsp;InOutSemaphoreValue.reserve(InOutSemaphoreValue.size() + WaitSemaphoreAcrossQueues.size());

&amp;nbsp;&amp;nbsp;for(int32 i=0;i&amp;lt; WaitSemaphoreAcrossQueues.size();++i)
&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;auto Sync_Vulkan = (jSyncAcrossCommandQueue_Vulkan*)WaitSemaphoreAcrossQueues[i].get();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;InOutSemaphore.push_back(Sync_Vulkan-&amp;gt;WaitSemaphore-&amp;gt;Semaphore);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;InOutSemaphoreValue.push_back(Sync_Vulkan-&amp;gt;SemaphoreValue);
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;WaitSemaphoreAcrossQueues.clear();
}

// Submit commandqueue the commandBuffer
std::shared_ptr&amp;lt;jSyncAcrossCommandQueue_Vulkan&amp;gt; jCommandBufferManager_Vulkan::QueueSubmit(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jCommandBuffer_Vulkan* InCommandBuffer, jSemaphore* InWaitSemaphore, jSemaphore* InSignalSemaphore)
{
...
&amp;nbsp;&amp;nbsp;// Extract Semaphore and Semaphore Value which to wait
&amp;nbsp;&amp;nbsp;std::vector&amp;lt;VkSemaphore&amp;gt; WaitSemaphores;
&amp;nbsp;&amp;nbsp;std::vector&amp;lt;uint64&amp;gt; WaitSemaphoreValues;
&amp;nbsp;&amp;nbsp;CurrentInCommandBufferManager-&amp;gt;GetWaitSemaphoreAndValueThenClear(WaitSemaphores, WaitSemaphoreValues);

&amp;nbsp;&amp;nbsp;// Added WaitSemaphore which will signal from previous vkQueueSubmit.
&amp;nbsp;&amp;nbsp;if (InWaitSemaphore)
&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WaitSemaphores.push_back((VkSemaphore)InWaitSemaphore-&amp;gt;GetHandle());
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;WaitSemaphoreValues.push_back(InWaitSemaphore-&amp;gt;GetValue());
&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;check(WaitSemaphoreValues.size() == WaitSemaphores.size());
...
&amp;nbsp;&amp;nbsp;uint64 signalValue = 0;
&amp;nbsp;&amp;nbsp;const bool UseWaitTimeline = InWaitSemaphore &amp;amp;&amp;amp; InWaitSemaphore-&amp;gt;GetType() == ESemaphoreType::TIMELINE;
&amp;nbsp;&amp;nbsp;const bool UseSignalTimeline = InSignalSemaphore &amp;amp;&amp;amp; InSignalSemaphore-&amp;gt;GetType() == ESemaphoreType::TIMELINE;

&amp;nbsp;&amp;nbsp;// The timeline semaphore needs additional structure of VkTimelineSemaphoreSubmitInfo.
&amp;nbsp;&amp;nbsp;VkTimelineSemaphoreSubmitInfo timelineSemaphoreSubmitInfo = {};
&amp;nbsp;&amp;nbsp;if (UseWaitTimeline || UseSignalTimeline)
&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timelineSemaphoreSubmitInfo.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timelineSemaphoreSubmitInfo.waitSemaphoreValueCount = (uint32)WaitSemaphoreValues.size();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timelineSemaphoreSubmitInfo.pWaitSemaphoreValues = WaitSemaphoreValues.data();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;signalValue = InSignalSemaphore-&amp;gt;IncrementValue();
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timelineSemaphoreSubmitInfo.signalSemaphoreValueCount = 1;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timelineSemaphoreSubmitInfo.pSignalSemaphoreValues = &amp;amp;signalValue;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;submitInfo.pNext = &amp;amp;timelineSemaphoreSubmitInfo;
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;std::vector&amp;lt;VkPipelineStageFlags&amp;gt; WaitStages(WaitSemaphores.size(), WaitStage);

&amp;nbsp;&amp;nbsp;// Set WaitSemaphores to vkSubmitInfo
&amp;nbsp;&amp;nbsp;submitInfo.pWaitSemaphores = WaitSemaphores.data();
&amp;nbsp;&amp;nbsp;submitInfo.waitSemaphoreCount = (uint32)WaitSemaphores.size();
&amp;nbsp;&amp;nbsp;submitInfo.pWaitDstStageMask = WaitStages.data();
&amp;nbsp;&amp;nbsp;submitInfo.commandBufferCount = 1;
&amp;nbsp;&amp;nbsp;submitInfo.pCommandBuffers = &amp;amp;vkInCommandBuffer;

&amp;nbsp;&amp;nbsp;...
&amp;nbsp;&amp;nbsp;// Do QueueSubmit
&amp;nbsp;&amp;nbsp;auto queueSubmitResult = vkQueueSubmit(CurrentQueue.Queue, 1, &amp;amp;submitInfo, vkFence);
&amp;nbsp;&amp;nbsp;...
&amp;nbsp;&amp;nbsp;// Make a jSyncAcrossCommandQueue_Vulkan for subsequent vkQueueSubmit
&amp;nbsp;&amp;nbsp;return std::make_shared&amp;lt;jSyncAcrossCommandQueue_Vulkan&amp;gt;(InCommandBuffer-&amp;gt;Type, (jSemaphore_Vulkan*)InSignalSemaphore);
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.4. 실제 사용 예제&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예제는 jEngine 의 AsyncCompute 를 사용하는 코드 예제입니다.&lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. 맨 첫줄은 jRenderFrameContext 에서&lt;/span&gt; AsyncComputeRenderFrameContext 를 생성합니다.&lt;br&gt;2. AsyncCompute 에 실행할 커맨드를 녹화합니다.&lt;br&gt;3. Compute CommandQueue 제출하는 과정을 보여줍니다. CommandQueue 에 제출한 후에 제출한 작업을 기다릴 수 있도록 하기 위해서 jSyncAcrossCommandQueue 를 생성하여 SyncPtr 에 담아줍니다.&lt;br&gt;4.&amp;nbsp;마지막 줄에 있는 SyncPtr-&amp;gt;WaitSyncAcrossCommandQueue(ECommandBufferType::GRAPHICS) 는 제출한 Compute Queue 를 다음에 제출할 Graphics Queue 에 작업을 제출하기 전에 완료해 달라는 의미입니다.&lt;/p&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// 1. 어싱크 컴퓨트 용 jRenderFrameContext 를 생성함. 이제 이 Context 를 통해서 렌더커맨드를 전송하면 Compute Queue 에 들어감.
//&amp;nbsp;&amp;nbsp; - 이 때 전달한 SyncAcrossCommandQueuePtr 이 nullptr 이 아니면 해당 동기화 객체를 기다림.
std::shared_ptr&amp;lt;jRenderFrameContext&amp;gt; AsyncRenderFrameContextPtr 
&amp;nbsp;&amp;nbsp;= RenderFrameContextPtr-&amp;gt;CreateRenderFrameContextAsync(SyncAcrossCommandQueuePtr);

...
// 2. 렌더커맨드 녹화
...

// 3. 렌더커맨드 CommandQueue 에 서밋
auto SyncPtr = AsyncRenderFrameContextPtr-&amp;gt;SubmitCurrentActiveCommandBuffer(jRenderFrameContext::None, false);

// 4. AsyncCompute 에 Submit 한 렌더커맨드의 실행을 후속 Graphics CommandQueue 가 기다려야 하는 경우 아래 코드 실행.
if (gOptions.WaitSubsequentGraphicsQueueTask)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SyncPtr-&amp;gt;WaitSyncAcrossCommandQueue(ECommandBufferType::GRAPHICS);&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 테스트 케이스&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 테스트 케이스는 &lt;a href=&quot;https://github.com/scahp/jEngine/tree/AsyncCompute&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://github.com/scahp/jEngine/tree/AsyncCompute&lt;/span&gt;&lt;/a&gt; 를 사용하여 진행했습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;렌더패스는 아래와 같은 형태로 구성됩니다. 그리고 AsyncCompute 를 테스트하기 위해서 “AsyncComputeTestPass(A→B→C)” 패스를 만들었습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;ShadowPass → BasePass → AtmosphericPass → &lt;b&gt;AsyncComputeTestPass(A→B→C)&lt;/b&gt; → PostProcessPass&lt;br&gt;&amp;nbsp;&lt;br&gt;아래 코드는 AsyncComputeTest 함수입니다.&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;AsyncComputeTest 에서 실행되는 렌더커맨드 A, B, C 는 Compute CommandQueue 에서 실행됩니다.&lt;/li&gt;&lt;li&gt;사전에 다른 CommandQueue 에 들어간 다른 렌더커맨드를 기다릴 수 있도록 std::shared_ptr&amp;lt;jSyncAcrossCommandQueue&amp;gt; SyncAcrossCommandQueuePtr 를 Argument 로 전달받습니다. (None, ShadowPass, BasePass, AtmosphericPass 이 될 수 있음)&lt;/li&gt;&lt;li&gt;AsyncComputeTestPass 의 후속 작업에는 PostProcessPass 가 있습니다. 그래서 PostProcess 가 실행되기 전 AsyncComputeTestPass 를 완료를 기다릴지 여부를 결정할 수 있습니다.&lt;/li&gt;&lt;/ul&gt;&lt;pre data-ke-type=&quot;codeblock&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;void jRenderer::AsyncComputeTest(std::shared_ptr&amp;lt;jSyncAcrossCommandQueue&amp;gt; SyncAcrossCommandQueuePtr)
{
...
&amp;nbsp;&amp;nbsp;{
	std::shared_ptr&amp;lt;jRenderFrameContext&amp;gt; CurrentRenderFrameContextPtr = gOptions.UseAsyncComputeQueue ? RenderFrameContextPtr-&amp;gt;CreateRenderFrameContextAsync(SyncAcrossCommandQueuePtr) : RenderFrameContextPtr;
...
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 1. AsyncComputeTest_A 제출
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SCOPE_CPU_PROFILE(AsyncComputeTest_A);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SCOPE_GPU_PROFILE(CurrentRenderFrameContextPtr, AsyncComputeTest_A);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DEBUG_EVENT_WITH_COLOR(CurrentRenderFrameContextPtr, &quot;AsyncComputeTest_A&quot;, Vector4(0.8f, 0.0f, 0.0f, 1.0f));

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jRHIUtil::DispatchCompute(CurrentRenderFrameContextPtr, AsyncComputeTestPtr-&amp;gt;GetTexture()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;, [&amp;amp;](const std::shared_ptr&amp;lt;jRenderFrameContext&amp;gt;&amp;amp; RenderFrameContextPtr, jShaderBindingArray&amp;amp; InOutShaderBindingArray, jShaderBindingResourceInlineAllocator&amp;amp; InOutResourceInlineAllactor)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;InOutShaderBindingArray.Add(jShaderBinding::Create(InOutShaderBindingArray.NumOfData, 1, EShaderBindingType::UNIFORMBUFFER_DYNAMIC, EShaderAccessStageFlag::COMPUTE
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;, InOutResourceInlineAllactor.Alloc&amp;lt;jUniformBufferResource&amp;gt;(OneFrameUniformBuffer.get()), true));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;, [](const std::shared_ptr&amp;lt;jRenderFrameContext&amp;gt;&amp;amp; InRenderFrameContextPtr)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jShaderInfo shaderInfo;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shaderInfo.SetName(jNameStatic(&quot;AyncComputeTestCS&quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shaderInfo.SetShaderFilepath(jNameStatic(&quot;Resource/Shaders/hlsl/AyncComputeTest_cs.hlsl&quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shaderInfo.SetShaderType(EShaderAccessStageFlag::COMPUTE);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shaderInfo.SetEntryPoint(jNameStatic(&quot;AsyncComputeTest_A&quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jShader* Shader = g_rhi-&amp;gt;CreateShader(shaderInfo);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return Shader;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;g_rhi-&amp;gt;UAVBarrier(CurrentRenderFrameContextPtr-&amp;gt;GetActiveCommandBuffer(), AsyncComputeTestPtr-&amp;gt;GetTexture());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 2. AsyncComputeTest_B 제출
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SCOPE_CPU_PROFILE(AsyncComputeTest_B);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SCOPE_GPU_PROFILE(CurrentRenderFrameContextPtr, AsyncComputeTest_B);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DEBUG_EVENT_WITH_COLOR(CurrentRenderFrameContextPtr, &quot;AsyncComputeTest_B&quot;, Vector4(0.0f, 0.8f, 0.0f, 1.0f));

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jRHIUtil::DispatchCompute(CurrentRenderFrameContextPtr, AsyncComputeTestPtr-&amp;gt;GetTexture()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;, [&amp;amp;](const std::shared_ptr&amp;lt;jRenderFrameContext&amp;gt;&amp;amp; RenderFrameContextPtr, jShaderBindingArray&amp;amp; InOutShaderBindingArray, jShaderBindingResourceInlineAllocator&amp;amp; InOutResourceInlineAllactor)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;InOutShaderBindingArray.Add(jShaderBinding::Create(InOutShaderBindingArray.NumOfData, 1, EShaderBindingType::UNIFORMBUFFER_DYNAMIC, EShaderAccessStageFlag::COMPUTE
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;, InOutResourceInlineAllactor.Alloc&amp;lt;jUniformBufferResource&amp;gt;(OneFrameUniformBuffer.get()), true));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;, [](const std::shared_ptr&amp;lt;jRenderFrameContext&amp;gt;&amp;amp; InRenderFrameContextPtr)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jShaderInfo shaderInfo;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shaderInfo.SetName(jNameStatic(&quot;AyncComputeTestCS&quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shaderInfo.SetShaderFilepath(jNameStatic(&quot;Resource/Shaders/hlsl/AyncComputeTest_cs.hlsl&quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shaderInfo.SetShaderType(EShaderAccessStageFlag::COMPUTE);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shaderInfo.SetEntryPoint(jNameStatic(&quot;AsyncComputeTest_B&quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jShader* Shader = g_rhi-&amp;gt;CreateShader(shaderInfo);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return Shader;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;g_rhi-&amp;gt;UAVBarrier(CurrentRenderFrameContextPtr-&amp;gt;GetActiveCommandBuffer(), AsyncComputeTestPtr-&amp;gt;GetTexture());

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 3. AsyncComputeTest_C 제출
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SCOPE_CPU_PROFILE(AsyncComputeTest_C);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SCOPE_GPU_PROFILE(CurrentRenderFrameContextPtr, AsyncComputeTest_C);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DEBUG_EVENT_WITH_COLOR(CurrentRenderFrameContextPtr, &quot;AsyncComputeTest_C&quot;, Vector4(0.0f, 0.0f, 0.8f, 1.0f));

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jRHIUtil::DispatchCompute(CurrentRenderFrameContextPtr, AsyncComputeTestPtr-&amp;gt;GetTexture()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;, [&amp;amp;](const std::shared_ptr&amp;lt;jRenderFrameContext&amp;gt;&amp;amp; RenderFrameContextPtr, jShaderBindingArray&amp;amp; InOutShaderBindingArray, jShaderBindingResourceInlineAllocator&amp;amp; InOutResourceInlineAllactor)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;InOutShaderBindingArray.Add(jShaderBinding::Create(InOutShaderBindingArray.NumOfData, 1, EShaderBindingType::UNIFORMBUFFER_DYNAMIC, EShaderAccessStageFlag::COMPUTE
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;, InOutResourceInlineAllactor.Alloc&amp;lt;jUniformBufferResource&amp;gt;(OneFrameUniformBuffer.get()), true));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;, [](const std::shared_ptr&amp;lt;jRenderFrameContext&amp;gt;&amp;amp; InRenderFrameContextPtr)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jShaderInfo shaderInfo;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shaderInfo.SetName(jNameStatic(&quot;AyncComputeTestCS&quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shaderInfo.SetShaderFilepath(jNameStatic(&quot;Resource/Shaders/hlsl/AyncComputeTest_cs.hlsl&quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shaderInfo.SetShaderType(EShaderAccessStageFlag::COMPUTE);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;shaderInfo.SetEntryPoint(jNameStatic(&quot;AsyncComputeTest_C&quot;));
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;jShader* Shader = g_rhi-&amp;gt;CreateShader(shaderInfo);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return Shader;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 4. AsyncCompute 작업 Queue 에 제출 후, 후속 Graphics Queue 작업이 시작되기전 Compute CommandQueue 에 들어간 작업을 완료해달라고 요청
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (gOptions.UseAsyncComputeQueue)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;auto SyncPtr = CurrentRenderFrameContextPtr-&amp;gt;SubmitCurrentActiveCommandBuffer(jRenderFrameContext::None, false);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (gOptions.WaitSubsequentGraphicsQueueTask)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SyncPtr-&amp;gt;WaitSyncAcrossCommandQueue(ECommandBufferType::GRAPHICS);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;아래의 AsyncCompute 조정 UI 를 통해서 AsyncCompute 기능을 테스트합니다.&lt;br&gt;- UseAsyncComputeQueue 는 AsyncCompute 활성 여부입니다.&lt;br&gt;- WaitPrerequsiteGraphicsQueueTask 는 AsyncCompute 실행 전에 실행 완료를 보장해야 할 Graphics 렌더패스입니다.&lt;br&gt;- WaitSubsequentGraphicsQueueTask 는 AsyncCompute 실행 후에 다음 Graphics 렌더패스 실행 전에 AsyncCompute 의 작업이 완료 되도록 보장할지 여부입니다. 만약 이 옵션이 켜져 있다면, AsyncComputeTest 단계와 PostProcess 는 겹칠 수 없습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IEx2Y/btsEI9qEtAw/2F7RkI25ul5SvphgDBuCsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IEx2Y/btsEI9qEtAw/2F7RkI25ul5SvphgDBuCsk/img.png&quot; data-alt=&quot;그림6. AsyncCompute 옵션 (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IEx2Y/btsEI9qEtAw/2F7RkI25ul5SvphgDBuCsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIEx2Y%2FbtsEI9qEtAw%2F2F7RkI25ul5SvphgDBuCsk%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;1282&quot; height=&quot;752&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림6. AsyncCompute 옵션 (출처 : 직접구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;렌더커맨드가 잘 겹쳐서 실행되는지 여부는 Nvidia Nsight 를 통해서 확인합니다. 이 프로그램은 Nvidia 그래픽 카드에서만 작동합니다.&lt;br&gt;Nsight 에 표시되는 1 frame 에 소모된 GPU time 은 실행시마다 캡쳐 상황에 따라 다르기 때문에 큰 의미를 두지 않고 봐주세요.&lt;br&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.1. Graphics Queue 에서 모두 실행하는 경우&lt;/b&gt;&lt;/h4&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;285&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KGcsZ/btsEMYIZnQQ/wR1lVObh6XSubfhtnQT3Qk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KGcsZ/btsEMYIZnQQ/wR1lVObh6XSubfhtnQT3Qk/img.png&quot; data-alt=&quot;그림7. Vulkan : AsyncCompute 없이 작동한 GPU timing (출처 : 직접 구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KGcsZ/btsEMYIZnQQ/wR1lVObh6XSubfhtnQT3Qk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKGcsZ%2FbtsEMYIZnQQ%2FwR1lVObh6XSubfhtnQT3Qk%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;878&quot; height=&quot;285&quot; data-origin-width=&quot;878&quot; data-origin-height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림7. Vulkan : AsyncCompute 없이 작동한 GPU timing (출처 : 직접 구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;265&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKbHc1/btsEHrkOKFz/WoCXw78slK95KCNtwUYb2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKbHc1/btsEHrkOKFz/WoCXw78slK95KCNtwUYb2k/img.png&quot; data-alt=&quot;그림8. DX12 : AsyncCompute 없이 작동한 GPU timing (출처 : 직접 구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKbHc1/btsEHrkOKFz/WoCXw78slK95KCNtwUYb2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKbHc1%2FbtsEHrkOKFz%2FWoCXw78slK95KCNtwUYb2k%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;826&quot; height=&quot;265&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;265&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림8. DX12 : AsyncCompute 없이 작동한 GPU timing (출처 : 직접 구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.2. Atmospheric 가 모두 실행된 후 AsyncComputeTest 가 모두 실행되고나서 PostProcess 가 실행된 경우&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그림9를 봐주세요.&amp;nbsp;&lt;/span&gt; Nsight 는 Vulkan 의 경우 Semaphore 의 Signal, Wait 연결을 보여줍니다. Signal or Wait 를 선택하면 연결된 Signal or Wait 를 빨간 선으로 연결해서 보여줍니다. Signal 의 경우 초록색, Wait 의 경우를 노란색 박스로 표시하였습니다. AsyncComputeTest 작업들이 AtmosphericPass 가 끝나고 호출되는 Signal 을 기다리고 있는 것을 확인할 수 있습니다. 그리고 PostProcess 는 AsyncComputeTest 패스가 호출하는 Signal 을 기다리고 있는 것을 볼 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;493&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csgji1/btsELpGMLx6/0DmTOZ3P1k9rprAzVKzWiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csgji1/btsELpGMLx6/0DmTOZ3P1k9rprAzVKzWiK/img.png&quot; data-alt=&quot;그림9. Vulkan : AtmosphericPass 와 PostProcess 사이에서 AsyncCompute 를 수행하고 서로 겹치지 않는 경우. (출처 : 직접 구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csgji1/btsELpGMLx6/0DmTOZ3P1k9rprAzVKzWiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcsgji1%2FbtsELpGMLx6%2F0DmTOZ3P1k9rprAzVKzWiK%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;1276&quot; height=&quot;493&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;493&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림9. Vulkan : AtmosphericPass 와 PostProcess 사이에서 AsyncCompute 를 수행하고 서로 겹치지 않는 경우. (출처 : 직접 구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br&gt;DX12 의 경우는 별도의 Fence 에 대한 정보를 보여주진 않습니다. 하지만 AtmosphericPass 와 AsyncComputeTest, PostProcess 가 겹치지 않고 잘 동작한 것을 확인할 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AupPr/btsEIPluTHx/QbePkhP2THnd36UFDuZSbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AupPr/btsEIPluTHx/QbePkhP2THnd36UFDuZSbk/img.png&quot; data-alt=&quot;그림10. DX12 : AtmosphericPass 와 PostProcess 사이에서 AsyncCompute 를 수행하고 서로 겹치지 않는 경우. (출처 : 직접 구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AupPr/btsEIPluTHx/QbePkhP2THnd36UFDuZSbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAupPr%2FbtsEIPluTHx%2FQbePkhP2THnd36UFDuZSbk%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;826&quot; height=&quot;451&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;451&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림10. DX12 : AtmosphericPass 와 PostProcess 사이에서 AsyncCompute 를 수행하고 서로 겹치지 않는 경우. (출처 : 직접 구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.3.&amp;nbsp;Atmospheric&amp;nbsp;가&amp;nbsp;모두&amp;nbsp;실행된&amp;nbsp;후&amp;nbsp;AsyncComputeTest&amp;nbsp;가&amp;nbsp;수행되고,&amp;nbsp;AsyncComputeTest&amp;nbsp;와&amp;nbsp;PostProcessPass&amp;nbsp;는&amp;nbsp;겹침&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우는 AsyncComputeTest 에서 생성된 jSyncAcrossCommandQueue 를 후속 작업으로 올 Graphics 렌더패스(PostProcess) 가 사용하지 않는 상황입니다. 예상대로 AtmosphericPass 와 AsyncComputeTest 사이에만 종속성이 걸려있기 때문에 AsyncComputeTest 와 PostProcess 가 잘 겹쳐서 실행되는 것을 볼 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdobh0/btsEORpgc8a/KK1uil66GyssHKO6Uj6rW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdobh0/btsEORpgc8a/KK1uil66GyssHKO6Uj6rW1/img.png&quot; data-alt=&quot;그림11. Vulkan : AtmosphericPass 가 완전히 수행된 다음 AsyncCompute 와 PostProcess 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdobh0/btsEORpgc8a/KK1uil66GyssHKO6Uj6rW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcdobh0%2FbtsEORpgc8a%2FKK1uil66GyssHKO6Uj6rW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;907&quot; height=&quot;476&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림11. Vulkan : AtmosphericPass 가 완전히 수행된 다음 AsyncCompute 와 PostProcess 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;447&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DdXx2/btsEQd6AIAq/4IFAyAlAEMF7G8aHTSYuGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DdXx2/btsEQd6AIAq/4IFAyAlAEMF7G8aHTSYuGk/img.png&quot; data-alt=&quot;그림12. DX12 : AtmosphericPass 가 완전히 수행된 다음 AsyncCompute 와 PostProcess 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DdXx2/btsEQd6AIAq/4IFAyAlAEMF7G8aHTSYuGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDdXx2%2FbtsEQd6AIAq%2F4IFAyAlAEMF7G8aHTSYuGk%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;822&quot; height=&quot;447&quot; data-origin-width=&quot;822&quot; data-origin-height=&quot;447&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림12. DX12 : AtmosphericPass 가 완전히 수행된 다음 AsyncCompute 와 PostProcess 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.4.&amp;nbsp;BasePass&amp;nbsp;가&amp;nbsp;모두&amp;nbsp;실행된&amp;nbsp;후&amp;nbsp;AsyncComputeTestPass&amp;nbsp;가&amp;nbsp;수행되고,&amp;nbsp;이후 모든 패스와 겹침&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;이제 AsyncComputeTest 가 Graphics CommandQueue 의 BasePass 의 실행 완료만 기다리고, 이후 과정은 Graphics Compute CommandQueue 가 겹쳐서 실행되는 예제입니다. Vulkan, DX12 모두 비슷한 결과를 볼 수 있습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;472&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clFh4o/btsEPLCwUID/3R8i3R0NjFdK5hfdUbo3BK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clFh4o/btsEPLCwUID/3R8i3R0NjFdK5hfdUbo3BK/img.png&quot; data-alt=&quot;그림13. Vulkan : BasePass 가 완전히 수행된 다음 이후 모든 패스와 AsyncCompute 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clFh4o/btsEPLCwUID/3R8i3R0NjFdK5hfdUbo3BK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FclFh4o%2FbtsEPLCwUID%2F3R8i3R0NjFdK5hfdUbo3BK%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;798&quot; height=&quot;472&quot; data-origin-width=&quot;798&quot; data-origin-height=&quot;472&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림13. Vulkan : BasePass 가 완전히 수행된 다음 이후 모든 패스와 AsyncCompute 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4ckA8/btsEHHOHglm/OC0prYVKUOyR48raOCZaB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4ckA8/btsEHHOHglm/OC0prYVKUOyR48raOCZaB1/img.png&quot; data-alt=&quot;그림14. DX12 : BasePass 가 완전히 수행된 다음 이후 모든 패스와 AsyncCompute 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4ckA8/btsEHHOHglm/OC0prYVKUOyR48raOCZaB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4ckA8%2FbtsEHHOHglm%2FOC0prYVKUOyR48raOCZaB1%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;762&quot; height=&quot;450&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림14. DX12 : BasePass 가 완전히 수행된 다음 이후 모든 패스와 AsyncCompute 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.5.&amp;nbsp;ShadowPass&amp;nbsp;가&amp;nbsp;모두&amp;nbsp;실행된&amp;nbsp;후&amp;nbsp;AsyncComputeTestPass&amp;nbsp;가&amp;nbsp;수행되고,&amp;nbsp;이후 모든 패스와 겹침&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 당겨서 ShadowPass 실행 완료된 후 바로 AsyncComputeTest 를 겹쳐서 실행하는 예제입니다. BasePass 부터 같이 겹쳐서 실행되는 것을 볼 수 있습니다.&lt;br&gt;&amp;nbsp;&lt;br&gt;이번 케이스의 경우 오히려 BasePass 와 AsyncComputeTest 의 실행시간이 더 길어졌습니다. 이런 경우 AsyncCompute 에 사용할 Shader core 가 충분히 남아있지 않거나 또는 다른 이슈(캐시 쓰레싱, 계산 부하) 때문에 Graphics 와 Compute 작업이 서로 경쟁하게 된 상황일 수 있습니다. 이 경우 Nsight 에서 제공하는 지표를 확인하여 서로 겹쳐서 실행하기 좋은 렌더패스인지 확인해 보는 것이 좋습니다. 만약 확인 결과 서로 겹쳐서 실행하기 어려운 경우 AsyncCompute 를 사용하지 않는 편이 더 나을 것입니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5sZp0/btsERmINH11/UYvgKvZg8viFIy0KpJ8EOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5sZp0/btsERmINH11/UYvgKvZg8viFIy0KpJ8EOK/img.png&quot; data-alt=&quot;그림15. Vulkan : ShadowPass 가 완전히 수행된 다음 이후 모든 패스와 AsyncCompute 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5sZp0/btsERmINH11/UYvgKvZg8viFIy0KpJ8EOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5sZp0%2FbtsERmINH11%2FUYvgKvZg8viFIy0KpJ8EOK%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;977&quot; height=&quot;442&quot; data-origin-width=&quot;977&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림15. Vulkan : ShadowPass 가 완전히 수행된 다음 이후 모든 패스와 AsyncCompute 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;471&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/digulm/btsEGNuTeyg/G6Po8uBgmgGxrGVEgg4CJk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/digulm/btsEGNuTeyg/G6Po8uBgmgGxrGVEgg4CJk/img.png&quot; data-alt=&quot;그림16. DX12 : ShadowPass 가 완전히 수행된 다음 이후 모든 패스와 AsyncCompute 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/digulm/btsEGNuTeyg/G6Po8uBgmgGxrGVEgg4CJk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdigulm%2FbtsEGNuTeyg%2FG6Po8uBgmgGxrGVEgg4CJk%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;761&quot; height=&quot;471&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;471&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림16. DX12 : ShadowPass 가 완전히 수행된 다음 이후 모든 패스와 AsyncCompute 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3.6.&amp;nbsp;AsyncComputeTest 가 바로 수행되어 모든 패스와 겹침&lt;/b&gt;&lt;/h4&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 AsyncComputeTestPass 를 즉시 실행 할 수 있도록 하였습니다. 이전에 관찰되던 AsyncComputeTest 의 앞에 있던 Semaphore 가 없는 것도 확인할 수 있습니다. CommandQueue 제출한 렌더커맨드가 즉시 실행되는 것이 아니라 Driver 나 OS 에 의해 적절한 스케쥴링이 적용되기 때문에 이런 결과가 나온 것 같습니다.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k73ap/btsEGOtR76N/yutY0ulIv0WpigEIYpJJkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k73ap/btsEGOtR76N/yutY0ulIv0WpigEIYpJJkK/img.png&quot; data-alt=&quot;그림17. Vulkan : 모든 Graphics 패스와 AsyncCompute 가 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k73ap/btsEGOtR76N/yutY0ulIv0WpigEIYpJJkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk73ap%2FbtsEGOtR76N%2FyutY0ulIv0WpigEIYpJJkK%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;792&quot; height=&quot;478&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림17. Vulkan : 모든 Graphics 패스와 AsyncCompute 가 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;463&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJIwuy/btsEHGCf6F8/KhpIXoyKOnRJhlkzKTqWwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJIwuy/btsEHGCf6F8/KhpIXoyKOnRJhlkzKTqWwK/img.png&quot; data-alt=&quot;그림18. DX12 : 모든 Graphics 패스와 AsyncCompute 가 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJIwuy/btsEHGCf6F8/KhpIXoyKOnRJhlkzKTqWwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJIwuy%2FbtsEHGCf6F8%2FKhpIXoyKOnRJhlkzKTqWwK%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;776&quot; height=&quot;463&quot; data-origin-width=&quot;776&quot; data-origin-height=&quot;463&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림18. DX12 : 모든 Graphics 패스와 AsyncCompute 가 서로 겹쳐서 수행가능 한 경우 (출처 : 직접 구현)&lt;/figcaption&gt;
&lt;/figure&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;4. 레퍼런스&lt;/b&gt;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;a href=&quot;https://therealmjp.github.io/posts/breaking-down-barriers-part-1-whats-a-barrier/&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://therealmjp.github.io/posts/breaking-down-barriers-part-1-whats-a-barrier/&lt;/span&gt;&lt;/a&gt; (6개 시리즈 전체)&lt;br&gt;2. &lt;a href=&quot;https://gpuopen.com/gdc-presentations/2019/gdc-2019-agtd5-breaking-down-barriers.pdf&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://gpuopen.com/gdc-presentations/2019/gdc-2019-agtd5-breaking-down-barriers.pdf&lt;/span&gt;&lt;/a&gt;&lt;br&gt;3. &lt;a href=&quot;https://gpuopen.com/wp-content/uploads/2017/03/GDC2017-Asynchronous-Compute-Deep-Dive.pdf&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://gpuopen.com/wp-content/uploads/2017/03/GDC2017-Asynchronous-Compute-Deep-Dive.pdf&lt;/span&gt;&lt;/a&gt;&lt;br&gt;4. &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/win32/direct3d12/user-mode-heap-synchronization&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://learn.microsoft.com/en-us/windows/win32/direct3d12/user-mode-heap-synchronization&lt;/span&gt;&lt;/a&gt;&lt;br&gt;5. &lt;a href=&quot;https://youtu.be/GiKbGWI4M-Y?si=J48ZjZL0tQESimsH&quot; target=&quot;_blank&quot;&gt;&lt;span&gt;https://youtu.be/GiKbGWI4M-Y?si=J48ZjZL0tQESimsH&lt;/span&gt;&lt;/a&gt;&lt;br&gt;6. &lt;a href=&quot;https://codingfarm.tistory.com/579&quot; target=&quot;_self&quot;&gt;&lt;span&gt;https://codingfarm.tistory.com/579&lt;/span&gt;&lt;/a&gt;&lt;br&gt;&amp;nbsp;&lt;br&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Graphics/Graphics</category>
      <category>async</category>
      <category>AsyncCompute</category>
      <category>CommandQueue</category>
      <category>DX12</category>
      <category>Fence</category>
      <category>Nsight</category>
      <category>semaphore</category>
      <category>TimelineSemaphore</category>
      <category>vkQueueSubmit</category>
      <category>vulkan</category>
      <author>scahp</author>
      <guid isPermaLink="true">https://scahp.tistory.com/124</guid>
      <comments>https://scahp.tistory.com/124#entry124comment</comments>
      <pubDate>Tue, 13 Feb 2024 20:31:25 +0900</pubDate>
    </item>
    <item>
      <title>RayTraced Ambient Occlusion(RTAO)</title>
      <link>https://scahp.tistory.com/123</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;RayTraced Ambient Occlusion(RTAO)&lt;/h2&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size16&quot;&gt;최초 작성 : 2024-01-31&lt;br /&gt;마지막 수정 : 2024-01-31&lt;br /&gt;최재호&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;목차&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;2. 내용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2.1. DispatchRay 코드 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2.2. Desnosing&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 2.2.1. RayPerPixel 개수를 늘린 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 2.2.2. Temporal&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 2.2.2.1. RayAccumulation&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 2.2.2.2. Reprojection&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; 2.2.3. Spatial&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 2.2.3.1. Jittering&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 2.2.3.2. Filtering&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2.2.3.2.1. GaussianBlur(Separable)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2.2.3.2.2. BilateralFilter&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&amp;nbsp;구현&amp;nbsp;결과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4.&amp;nbsp;실제&amp;nbsp;구현&amp;nbsp;코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5.&amp;nbsp;레퍼런스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 목표&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RayTracing 을 사용한 Ambient Occlusion 을 구현해 보며 RayTracing 에 익숙해져 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리얼타임 렌더링을 위해서 픽셀 당 사용 가능 한 Ray 수는 그렇게 많지 않습니다. 이런 부분을 개선할 수 있는 방식에 대해서 알아봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사전지식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DXR or VkRaytracing 에 대한 이해&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;이후 글에서는 RayTranced Ambient Occlusion 을 RTAO 라고 부르겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현에 사용한 그래픽카드는 RTX3070 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5kRQR/btsEdZWlY63/ydOhxFSXV7Auhwcj1EFgnK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5kRQR/btsEdZWlY63/ydOhxFSXV7Auhwcj1EFgnK/img.png&quot; data-alt=&quot;그림1. RTAO with Sponza (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5kRQR/btsEdZWlY63/ydOhxFSXV7Auhwcj1EFgnK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5kRQR%2FbtsEdZWlY63%2FydOhxFSXV7Auhwcj1EFgnK%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;1282&quot; height=&quot;752&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. RTAO with Sponza (출처 : 직접구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 내용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RayTracing 을 시작하면서 마주치는 첫 번째 이슈는 렌더타겟의 픽셀 당 발사할 수 있는 Ray 가 굉장히 제한적이라는 것입니다. 렌더타겟의 픽셀 당 2개 이상의 레이를 발사 할 수도 있지만 발사하는 Ray 가 늘어날수록 성능이 급격하게 떨어지는 것을 확인할 수 있을 것입니다. 그림2 봐주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOux61/btsEeINGDQf/khUmVDhJfhiKTrcEgu6AeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOux61/btsEeINGDQf/khUmVDhJfhiKTrcEgu6AeK/img.png&quot; data-alt=&quot;그림2. 픽셀당 발사하는 Ray 를 1, 3, 10 개로 올리는 경우 RaytracingAO 처리시간이 늘어남. RaytracingAO 하위의 것들은 Ray 개수에 영향을 받지 않기 때문에 분석할 필요 없음. (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOux61/btsEeINGDQf/khUmVDhJfhiKTrcEgu6AeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOux61%2FbtsEeINGDQf%2FkhUmVDhJfhiKTrcEgu6AeK%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;794&quot; height=&quot;129&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;129&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림2. 픽셀당 발사하는 Ray 를 1, 3, 10 개로 올리는 경우 RaytracingAO 처리시간이 늘어남. RaytracingAO 하위의 것들은 Ray 개수에 영향을 받지 않기 때문에 분석할 필요 없음. (출처 : 직접구현)&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;그림2 의 좌측에서부터 각각 픽셀 당 1 개, 3 개, 10개 의 Ray 를 사용한 경우 GPU 에서의 처리시간 차이를 보여줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 이유로 이 글의 RTAO 구현에서는 렌더타겟의 각 픽셀 당 1개의 Ray 를 발사하는 것을 목표로 합니다. 또한 기본적인 RayTracing 방식은 Camera 의 위치에서 각 렌더타겟의 샘플로 Ray를 발사하지만 그 단계를 줄이기 위해서 Hybrid RayTracing 방식을 사용합니다. GBuffer 생성까지는 기존의 디퍼드렌더링 패스를 따라가고 이후에 Raytracing 을 시작하는 시점에는 GBuffer 의 Position, Normal 정보를 기반으로 Ray 를 발사합니다. 이번 RTAO 예제에도 그런 방식을 따르고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.1. DispatchRay 코드 작성&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RTAO&amp;nbsp;의&amp;nbsp;Raytracing&amp;nbsp;코드는&amp;nbsp;자체는&amp;nbsp;아주&amp;nbsp;간단합니다.&amp;nbsp;AO&amp;nbsp;의&amp;nbsp;차폐&amp;nbsp;여부만&amp;nbsp;비교하면&amp;nbsp;되기&amp;nbsp;때문입니다.&amp;nbsp;아래&amp;nbsp;코드를&amp;nbsp;봐주세요.&amp;nbsp;RTAO&amp;nbsp;에서&amp;nbsp;사용하는&amp;nbsp;Shader&amp;nbsp;종류는&amp;nbsp;아래와&amp;nbsp;같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 처음 Ray 를 생성해 주는 RayGeneration Shader&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- Hit 처리를 담당하는 AnyHit , ClosestHit (Closest 는 Hit 한 것 중 가장 가까운 것) Shader&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;- 아무것도 Hit 하지 못했을 때 호출되는 Miss Shader 입니다. 이 경우 보통은 이 경우 환경맵 샘플링 할 것입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1706711134045&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 MyRaygenShader()
{
  float2 UV = DispatchRaysIndex().xy / g_sceneCB.ViewRect.zw;
  UV += g_sceneCB.HaltonJitter.xy; // Apply Jittering from Halton Sequence
		
  // Get WorldPos, WorldNormal from G-Buffer
  float3 WorldPos = GBuffer0_Pos.SampleLevel(AlbedoTextureSampler, UV, 0).xyz;
  float3 WorldNormal = GBuffer1_Normal.SampleLevel(AlbedoTextureSampler, UV, 0).xyz;
...
  // Shoot rays 'RayPerPixel' times
  for(int i=0;i&amp;lt;g_sceneCB.RayPerPixel;++i)
  {
    RayDesc ray;
    ray.Origin = WorldPos;
    ray.Direction = random_in_hemisphere(WorldNormal, WorldPos);
    ray.TMin = 0.001; // Small epsilon to avoid self intersection.
    ray.TMax = g_sceneCB.AORadius;
  
    RayPayload payload = { float4(1.0f, 1.0f, 1.0f, 1.0f) };
    TraceRay(Scene, RAY_FLAG_CULL_BACK_FACING_TRIANGLES, ~0, 0, 0, 0, ray, payload);

    ++AccumulateCount;

    // Incremental Average : https://blog.demofox.org/2016/08/23/incremental-averaging/
    FinalAO = lerp(FinalAO.xyz, payload.color.xyz, 1.0 / AccumulateCount);
  }
  RenderTarget[DispatchRaysIndex().xy] = float4(FinalAO, AccumulateCount);
}

[shader(&quot;anyhit&quot;)]
void MyAnyHitShader(inout RayPayload payload, in MyAttributes attr)
{
  payload.color = float4(0, 0, 0, 1);
}

[shader(&quot;closesthit&quot;)]
void MyClosestHitShader(inout RayPayload payload, in MyAttributes attr)
{
  payload.color = float4(0, 0, 0, 1);
}

[shader(&quot;miss&quot;)]
void MyMissShader(inout RayPayload payload)
{
  payload.color = float4(1, 1, 1, 1);
}&lt;/code&gt;&lt;/pre&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;먼저&amp;nbsp;간단한&amp;nbsp;Hit&amp;nbsp;Shader&amp;nbsp;시리즈를&amp;nbsp;확인해 봅시다.&amp;nbsp;AO&amp;nbsp;는&amp;nbsp;차폐되는&amp;nbsp;경우&amp;nbsp;검은&amp;nbsp;그림자가&amp;nbsp;드리웁니다.&amp;nbsp;그래서&amp;nbsp;Hit&amp;nbsp;되는&amp;nbsp;경우는&amp;nbsp;암부로&amp;nbsp;표현해줘야&amp;nbsp;할&amp;nbsp;것입니다.&amp;nbsp;그래서&amp;nbsp;Hit,&amp;nbsp;Miss&amp;nbsp;Shader&amp;nbsp;를&amp;nbsp;보면&amp;nbsp;Hit&amp;nbsp;한&amp;nbsp;경우&amp;nbsp;Color&amp;nbsp;를&amp;nbsp;검은색,&amp;nbsp;Miss&amp;nbsp;인&amp;nbsp;경우&amp;nbsp;흰색으로&amp;nbsp;설정합니다.&amp;nbsp;&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;다음으로 RayGeneration Shader 입니다. 이 글에서는 Hybrid RayTracing 을 사용하기 때문에 RayGeneration 을 GBuffer 기준으로 합니다. GBuffer 에서 얻어온 WorldPos 와 WorldNormal 을 사용하여 TraceRay 함수를 호출합니다. WorldPos 는 그대로 사용하면 됩니다. 하지만 Ray 방향의 경우 매 WorldNormal 을 중심으로 한 반구 범위 내에서 랜덤 방향으로 Ray 를 발사합니다. AO 는 현재 위치와 인접한 위치로 인해서 얼마나 차폐 되는지 표현하는 것이 목표입니다. 주변 환경이 얼마나 차폐되어 있는지 알 수 있는 방법은 현재 위치에서 여러 방향으로 Ray 를 발사하여 차폐 결과를 누적하는 것 입니다. 차폐 여부를 판단하는 방식만 보더라도 픽셀 한개의 정확한 AO 를 처리 하는데 아주 많은 수의 Ray 가 필요하다는 것을 알 수 있습니다. 실제로 Ray 를 수 없이 발사하는 것으로 결정할 수도 있지만 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;우리는&amp;nbsp;이&amp;nbsp;글의&amp;nbsp;시작에서&amp;nbsp;Ray&amp;nbsp;발사&amp;nbsp;비용이&amp;nbsp;아주&amp;nbsp;비싸기&amp;nbsp;때문에&amp;nbsp;픽셀&amp;nbsp;당&amp;nbsp;1개의&amp;nbsp;Ray&amp;nbsp;를&amp;nbsp;발사하는&amp;nbsp;것을&amp;nbsp;목표로&amp;nbsp;하자고&amp;nbsp;했었습니다.&amp;nbsp;그럼&amp;nbsp;이제&amp;nbsp;어떻게&amp;nbsp;이&amp;nbsp;문제를&amp;nbsp;해결할지&amp;nbsp;차근차근&amp;nbsp;알아봅시다.&lt;/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;1282&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/u9mle/btsEb9rM9A8/kHEudvJHkFzkPks9CyNPkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/u9mle/btsEb9rM9A8/kHEudvJHkFzkPks9CyNPkK/img.png&quot; data-alt=&quot;그림3. 픽셀당 Ray 를 1개만 발사하는 경우 AO 결과 (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/u9mle/btsEb9rM9A8/kHEudvJHkFzkPks9CyNPkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fu9mle%2FbtsEb9rM9A8%2FkHEudvJHkFzkPks9CyNPkK%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;1282&quot; height=&quot;752&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림3. 픽셀당 Ray 를 1개만 발사하는 경우 AO 결과 (출처 : 직접구현)&lt;/figcaption&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2. Desnosing&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Ray&amp;nbsp;를&amp;nbsp;여러번&amp;nbsp;발사&amp;nbsp;할&amp;nbsp;수&amp;nbsp;없다면&amp;nbsp;Ray&amp;nbsp;를&amp;nbsp;여러번&amp;nbsp;발사한&amp;nbsp;것&amp;nbsp;처럼&amp;nbsp;추가&amp;nbsp;데이터를&amp;nbsp;활용해야&amp;nbsp;합니다.&amp;nbsp;이런&amp;nbsp;데이터를&amp;nbsp;Temporal,&amp;nbsp;Spatial&amp;nbsp;방식을&amp;nbsp;통해서&amp;nbsp;얻어올&amp;nbsp;수&amp;nbsp;있을&amp;nbsp;것입니다.&amp;nbsp;Temporal&amp;nbsp;의&amp;nbsp;경우&amp;nbsp;이전&amp;nbsp;프레임의&amp;nbsp;데이터를&amp;nbsp;재활용하는&amp;nbsp;것이며,&amp;nbsp;Spatial&amp;nbsp;은&amp;nbsp;필터링과&amp;nbsp;같이&amp;nbsp;인접&amp;nbsp;위치의&amp;nbsp;정보를&amp;nbsp;활용하여&amp;nbsp;Noise&amp;nbsp;를&amp;nbsp;완화하는&amp;nbsp;것입니다. &lt;br /&gt;&lt;br /&gt;본격적으로&amp;nbsp;들어가기&amp;nbsp;전에&amp;nbsp;Ray&amp;nbsp;를&amp;nbsp;개수를&amp;nbsp;단순히&amp;nbsp;늘려보는&amp;nbsp;결과도&amp;nbsp;확인해 봅시다.&amp;nbsp;얼마나&amp;nbsp;많은&amp;nbsp;Ray&amp;nbsp;를&amp;nbsp;발사해야&amp;nbsp;안정적인&amp;nbsp;AO&amp;nbsp;가&amp;nbsp;나오는지&amp;nbsp;퍼포먼스는&amp;nbsp;얼마나&amp;nbsp;더&amp;nbsp;사용하게&amp;nbsp;되는지&amp;nbsp;확인해봅시다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2.1. RayPerPixel 개수를 늘린 경우&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그림4 에서 보는 것과 같이 픽셀 당 30 개의 Ray 를 발사해서 샘플을 추가해 줬습니다. 결과물은 더 나아졌지만 RayTracing 비용이 기존 대비 7ms 이상 증가했습니다. 60FPS 를 유지하기 위해서 16ms 내에 모든 렌더링 과정을 마쳐야 되는 게임과 같은 상황에서는 사용할 수 없는 수준입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kDZEz/btsEcajVFmz/ZSBEkuK0kb7k01uBeIxum0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kDZEz/btsEcajVFmz/ZSBEkuK0kb7k01uBeIxum0/img.png&quot; data-alt=&quot;그림4. Ray Per Pixel 을 늘려서 Noise 를 완화 시킴 (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kDZEz/btsEcajVFmz/ZSBEkuK0kb7k01uBeIxum0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkDZEz%2FbtsEcajVFmz%2FZSBEkuK0kb7k01uBeIxum0%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;1282&quot; height=&quot;752&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림4. Ray Per Pixel 을 늘려서 Noise 를 완화 시킴 (출처 : 직접구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;position: absolute;&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2.2. Temporal&lt;/b&gt;&lt;/h4&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2.2.1. RayAccumulation&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 프레임에 발사할 수 있는 Ray 의 개수가 제한적이라면 Temporal 데이터를 추가로 활용해 볼 수 있을 것입니다. 간단한 방법으로는 발사한 Ray 를 계속해서 AO 렌더타겟에 계속 누적하는 것입니다. 그림5 를 보면 픽셀 당 1개의 Ray 를 발사하는데 비해서 Noise 가 상당히 줄어든 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/enIVI5/btsEiA8IaIn/dKJkTu6U6Mql892xUBKLy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/enIVI5/btsEiA8IaIn/dKJkTu6U6Mql892xUBKLy0/img.png&quot; data-alt=&quot;그림5. RayAccumulation 을 적용하여 RayTracing 한 결과를 AO 렌더타겟 누적시킨 경우도 Noise 감소를 확인할 수 있음. (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/enIVI5/btsEiA8IaIn/dKJkTu6U6Mql892xUBKLy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FenIVI5%2FbtsEiA8IaIn%2FdKJkTu6U6Mql892xUBKLy0%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;1282&quot; height=&quot;752&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림5. RayAccumulation 을 적용하여 RayTracing 한 결과를 AO 렌더타겟 누적시킨 경우도 Noise 감소를 확인할 수 있음. (출처 : 직접구현)&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;아래 코드를 통해서 AO 를 Accumulate 시키며, 최대 500개의 픽셀까지만 누적시킵니다.&lt;/p&gt;
&lt;pre id=&quot;code_1706712413090&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 MyRaygenShader()
{
  float2 UV = DispatchRaysIndex().xy / g_sceneCB.ViewRect.zw;
  UV += g_sceneCB.HaltonJitter.xy; // Apply Jittering from Halton Sequence

  float3 WorldPos = GBuffer0_Pos.SampleLevel(AlbedoTextureSampler, UV, 0).xyz;
  float3 WorldNormal = GBuffer1_Normal.SampleLevel(AlbedoTextureSampler, UV, 0).xyz;

  float3 FinalAO = 0;
  float AccumulateCount = 0.0f;
  if (!g_sceneCB.Clear) // Clear Accumulated AO when moving camera or option changed
  {
    FinalAO = RenderTarget[DispatchRaysIndex().xy].xyz;
    AccumulateCount = RenderTarget[DispatchRaysIndex().xy].w;
  }

  // Max accumulateCount is 500
  if (AccumulateCount &amp;gt; 500)
    return;
 ...
 }&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;이 방식은 사용 중인 렌더타겟에 AO 정보를 계속 누적하는 방식이기 때문에 제약사항이 있습니다. 예를 들면 화면을 이동 중인 물체가 있거나 카메라가 이동하는 경우 기존의 누적한 AO 를 전부 버리고 새로 누적해야 하는 단점이 있습니다. 이런 부분을 개선해 주는 것이 바로 Reprojection 입니다. 계속해서 해당 내용을 확인해 봅시다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2.2.2. Reprojection&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reprojection 은 VelocityBuffer 를 사용하여 현재 쉐이딩 중인 픽셀이 이전 프레임에는 어느 위치에서 그려지고 있었는지 정보를 얻습니다. 그리고 이전 프레임의 위치를 얻을 수 있었다면, HistoryBuffer 에서 이전 프레임에 기록한 데이터를 가져와서 재활용합니다. 여기서 HistoryBuffer 는 이전 프레임에 렌더링 된 AO 정보를 담고 있는 렌더타겟 입니다. 이 부분은 TAA(Temporal Antialiasing) 에서 사용되는 기법이며 레퍼런스7 를 참고하였습니다.&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;아래 코드는 Reprojection 을 수행하는 코드입니다. VelocityBuffer 를 통해서 이전 프레임에 렌더링 된 위치의 AO 를 얻어옵니다. 이 값을 HistoryColor 로 둡니다. 그런 뒤 HistoryColor 와 이번 프레임에 Ray 를 발사하여 만든 CurrentColor 정보를 혼합해줍니다. 여기서는 HistoryColor 를 90% 사용하도록 하였습니다. 그림6 를 보면 Reprojection 후 Ray 의 Noise 가 많이 감소한 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bznlWP/btsEdZPzTVi/ONwpyROMo2KH2jxrqJQovK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bznlWP/btsEdZPzTVi/ONwpyROMo2KH2jxrqJQovK/img.png&quot; data-alt=&quot;그림6. Reprojection 후 Noise 가 많이 감소한 것을 볼 수 있음. 앞서 확인한 RayAccumulation 과 다르게 화면을 이동하여도 Noise 가 크게 눈에 띄지 않음. (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bznlWP/btsEdZPzTVi/ONwpyROMo2KH2jxrqJQovK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbznlWP%2FbtsEdZPzTVi%2FONwpyROMo2KH2jxrqJQovK%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;1282&quot; height=&quot;752&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림6. Reprojection 후 Noise 가 많이 감소한 것을 볼 수 있음. 앞서 확인한 RayAccumulation 과 다르게 화면을 이동하여도 Noise 가 크게 눈에 띄지 않음. (출처 : 직접구현)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706711275604&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;float4 AOReprojectionPS(VSOutput input) : SV_TARGET
{
  // Get previous frame's UV
  float4 Velocity = VelocityBuffer.Sample(TextureSampler, input.TexCoord);
  float2 OldUV = input.TexCoord - (Velocity.xy / float2(ComputeCommon.Width * ComputeCommon.InvScaleToOriginBuffer, ComputeCommon.Height * ComputeCommon.InvScaleToOriginBuffer));
  
  // Fetch current and previous frame's AO
  float3 currentColor = CurrentTexture.Sample(TextureSampler, input.TexCoord).xyz;
  float3 historyColor = HistoryBuffer.Sample(TextureSampler, OldUV).xyz;
  
  float ReprojectionWeight = 0.9;
#if USE_DISCONTINUITY_WEIGHT
  // Use Reprojection Data depending on Discontinuity of depth.
  float DiscontinuityWeight = abs(DepthBuffer.Sample(TextureSampler, input.TexCoord).x - HistoryDepthBuffer.Sample(TextureSampler, input.TexCoord).x) &amp;lt; 0.01;
  ReprojectionWeight *= DiscontinuityWeight;
#endif // USE_DISCONTINUITY_WEIGHT
  
  return float4(lerp(currentColor, historyColor, ReprojectionWeight), 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-ke-size=&quot;size16&quot;&gt;하지만 이 Reprojection 을 수행하는 경우 그림7 와 같이 Ghosting 현상이 발생합니다. 이전 위치의 정보를 재활용하기 때문에 렌더링 하는 오브젝트가 움직이거나 카메라가 움직이는 경우 이전 픽셀을 정보가 남을 것입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BXUjc/btsEigoYUEk/eDypuCcQGEHNgIHJRAipp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BXUjc/btsEigoYUEk/eDypuCcQGEHNgIHJRAipp0/img.png&quot; data-alt=&quot;그림7. Reprojection 때문에 발생한 Ghosting 현상 (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BXUjc/btsEigoYUEk/eDypuCcQGEHNgIHJRAipp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBXUjc%2FbtsEigoYUEk%2FeDypuCcQGEHNgIHJRAipp0%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;536&quot; height=&quot;582&quot; data-origin-width=&quot;536&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림7. Reprojection 때문에 발생한 Ghosting 현상 (출처 : 직접구현)&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;이 부분을 완화하기 위해서 화면상의 Discontinuity 정보를 활용할 수 있을 것입니다. 예를 들면, Color Intensity, Depth, Normal Direction 정보 같은 부분들이 좋은 후보가 될 수 있을 것입니다. 이 구현에서는 간단한 Depth 정보를 사용하여 Ghosting 현상을 줄였습니다. 만약 Discontinuity 가 발견된 지점이라면 Reprojection 을 포기하고 현재 픽셀을 사용합니다. 그림8 를 보면 Ghosting 이슈가 많이 완화된 것을 볼 수 있습니다만 현재 프레임에 생성한 AO 를 그대로 사용했기 때문에 해당 부분이 Noise 가 강조됩니다. 하지만 이 부분은 뒤에서 볼 Spatial Filter 를 적용하고 나면 상당 부분 완화되며, 오브젝트의 테두리가 더 선명하게 유지되는 점도 확인 할 수 있습니다. 그림8 의 우측 이미지를 참고해 주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;498&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/N4TEl/btsEf2SAj9K/sb4j9RbCd1awnmAcsKEr9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/N4TEl/btsEf2SAj9K/sb4j9RbCd1awnmAcsKEr9K/img.png&quot; data-alt=&quot;그림8. 좌측 이미지는 Discontinuity 를 사용하여 Ghosting 현상을 완화하기 위해 Reprojection 을 사용하지 않은 픽셀에 Noise 가 생긴 것을 볼 수 있음. 이 부분은 추후 알아볼 Filtering 을 통하여 거의 눈에 띄지 않게됨. 우측이 이미지는 좌측 이미지에 Bilateral Filtering 을 적용한 결과. (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/N4TEl/btsEf2SAj9K/sb4j9RbCd1awnmAcsKEr9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FN4TEl%2FbtsEf2SAj9K%2Fsb4j9RbCd1awnmAcsKEr9K%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;852&quot; height=&quot;498&quot; data-origin-width=&quot;852&quot; data-origin-height=&quot;498&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림8. 좌측 이미지는 Discontinuity 를 사용하여 Ghosting 현상을 완화하기 위해 Reprojection 을 사용하지 않은 픽셀에 Noise 가 생긴 것을 볼 수 있음. 이 부분은 추후 알아볼 Filtering 을 통하여 거의 눈에 띄지 않게됨. 우측이 이미지는 좌측 이미지에 Bilateral Filtering 을 적용한 결과. (출처 : 직접구현)&lt;/figcaption&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;지금까지는 Temporal 정보를 사용하여 Noise 를 감소 시켰습니다. 이제부터는 Spatial 정보를 사용하여 Noise 를 감소시키는 과정을 보겠습니다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2.3. Spatial&lt;/b&gt;&lt;/h4&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2.3.1. Jittering&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Jittering 은 Ray 발사에 무작위성을 추가로 더 부여합니다. Halton Sequence 를 사용하여 매 프레임 Ray 가 발사되는 WorldPos 에 약간의 Offset 을 더해줍니다. Halton Sequence 는 레퍼런스7 의 TAA 에서 제시 된 16개의 데이터를 약간 수정하여 그대로 사용했습니다.&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;아래 코드는 Halton Sequence 를 G-Buffer 를 Fetch 할 UV 에 적용하는 것을 보여줍니다. 또한 C++ 코드에서 생성하는 Halton sequence 를 보여줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1706713160462&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Raygen shader
[shader(&quot;raygeneration&quot;)]
void MyRaygenShader()
{
  float2 UV = DispatchRaysIndex().xy / g_sceneCB.ViewRect.zw;
  UV += g_sceneCB.HaltonJitter.xy; // Apply Jittering from Halton Sequence

  float3 WorldPos = GBuffer0_Pos.SampleLevel(AlbedoTextureSampler, UV, 0).xyz;
  float3 WorldNormal = GBuffer1_Normal.SampleLevel(AlbedoTextureSampler, UV, 0).xyz;
...
}

// C++ 코드 의 Halton sequence 생성 코드
static Vector2 HaltonJitter[]={
    Vector2(0.0f,      -0.333334f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(-0.5f,     0.333334f)  / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(0.5f,      -0.777778f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(-0.75f,    -0.111112f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(0.25f,     0.555556f)  / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(-0.25f,    -0.555556f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(0.75f,     0.111112f)  / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(-0.875f,   0.777778f)  / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(0.125f,    -0.925926f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(-0.375f,   -0.259260f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(0.625f,    0.407408f)  / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(-0.625f,   -0.703704f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(0.375f,    -0.037038f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(-0.125f,   0.629630f)  / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(0.875f,    -0.481482f) / Vector2((float)RayRTWidth, (float)RayRTHeight),
    Vector2(-0.9375f,  0.185186f)  / Vector2((float)RayRTWidth, (float)RayRTHeight)
};&lt;/code&gt;&lt;/pre&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;그림9 의 상단은 Reprojection 만 적용한 상태입니다. 이 경우 아치형 표면에서 Aliasing 이 발생합니다. 그림 9 의 하단은 Jittering 을 켠 후 결과입니다. 이 경우 Aliasing 이 제거된 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAjiMs/btsEhqlk4Ao/dxzXGYBXvt3Bn2lTBWmexk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAjiMs/btsEhqlk4Ao/dxzXGYBXvt3Bn2lTBWmexk/img.png&quot; data-alt=&quot;그림9. 상단 이미지는 Reprojection 만 적용한 경우, 하단 이미지는 Jittering 을 추가 적용한 경우. Aliasing 이 사라진 것을 확인할 수 있음. (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAjiMs/btsEhqlk4Ao/dxzXGYBXvt3Bn2lTBWmexk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAjiMs%2FbtsEhqlk4Ao%2FdxzXGYBXvt3Bn2lTBWmexk%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;757&quot; height=&quot;752&quot; data-origin-width=&quot;757&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림9. 상단 이미지는 Reprojection 만 적용한 경우, 하단 이미지는 Jittering 을 추가 적용한 경우. Aliasing 이 사라진 것을 확인할 수 있음. (출처 : 직접구현)&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 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2.3.2. Filtering&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;다음으로는 이미지 필터링 방식을 봅시다. 이번에는 간단히 Gaussian 과 Bilateral Filter 를 사용하여 Noise 를 줄여봅니다.&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2.3.2.1. GaussianBlur(Separable)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GaussianBlur 의 경우 좋은 설명이 레퍼런스3 에 있습니다. 그래서 추가로 해당 내용에 대한 설명은 하지 않을 것입니다. 바로 결과 차이를 확인해 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림10 를 보면 GaussianBlur 를 통해서 이미지의 High frequency noise 가 많이 감소한 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LNeqb/btsEbEZIwr7/wDGPgv776xn2obGEK5bJK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LNeqb/btsEbEZIwr7/wDGPgv776xn2obGEK5bJK0/img.png&quot; data-alt=&quot;그림10. GaussianBlur 를 적용 결과 (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LNeqb/btsEbEZIwr7/wDGPgv776xn2obGEK5bJK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLNeqb%2FbtsEbEZIwr7%2FwDGPgv776xn2obGEK5bJK0%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;1282&quot; height=&quot;752&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림10. GaussianBlur 를 적용 결과 (출처 : 직접구현)&lt;/figcaption&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2.3.2.2. BilateralFilter&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BilateralFiltering 의 경우도 좋은 설명이 레퍼런스4 에 있습니다. GaussianBlur 와 비교하고 차이점을 간단히 설명하면 다음과 같습니다.&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;GaussianBlur 는 이미지의 모든 영역에 대한 High frequency 를 동일하게 감소시키지만 BilateralFilter 의 경우는 선택적으로 high frequency 정보를 유지할 수 있습니다. 이 글에서는 Depth 정보를 기반으로 AO 에서 오브젝트의 테두리 정보를 보존하도록 했습니다. 그림11 를 보면 기존 GaussianBlur 보다 Edge 정보가 더 잘 보존 되는 것을 볼 수 있습니다. 이것을 통해서 앞쪽에 있는 오브젝트의 AO 가 뒤쪽에 있는 오브젝트의 AO 에 묻어나는 것을 피할 수 있을 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAskUd/btsEdU1VdBd/zp6ejnlg10rvqHVciEZzAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAskUd/btsEdU1VdBd/zp6ejnlg10rvqHVciEZzAk/img.png&quot; data-alt=&quot;그림11. Bilateral Filtering 적용 결과 (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAskUd/btsEdU1VdBd/zp6ejnlg10rvqHVciEZzAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAskUd%2FbtsEdU1VdBd%2Fzp6ejnlg10rvqHVciEZzAk%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;1282&quot; height=&quot;752&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림11. Bilateral Filtering 적용 결과 (출처 : 직접구현)&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;아래 코드의 &amp;lsquo;// Bilateral with depth&amp;rsquo; 주석 하단 코드가 Bilateral Filter 를 적용하는 부분입니다. 현재 구현 중인 RTAO 의 경우 Linear Depth 를 사용하지 않기 때문에 Depth 의 Range 가 0.9~1.0 사이에 거의 몰려있습니다. 그래서 Bilateral 에 사용하는 sigma(표준편차) 를 거기에 맞게 작은 값으로 유지해줘야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706711415943&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[numthreads(16, 16, 1)]
void Bilateral(uint3 GlobalInvocationID : SV_DispatchThreadID)
{
  if (GlobalInvocationID.x &amp;gt;= ComputeCommon.Width || GlobalInvocationID.y &amp;gt;= ComputeCommon.Height)
    return;

  int kernelSize = ComputeCommon.KernalSize;
  int center = kernelSize / 2;
  int2 PixelPos = int2(GlobalInvocationID.xy);
  float2 CenterUV = PixelPos / float2(ComputeCommon.Width, ComputeCommon.Height);
  float CenterDepth = DepthBuffer.SampleLevel(DepthSampler, CenterUV, 0).x;
  
  float3 Color = float3(0, 0, 0);
  float TotalWeight = 0;
  for (int i = 0; i &amp;lt; kernelSize; ++i)
  {
    for (int j = 0; j &amp;lt; kernelSize; ++j)
    {
      int x = (i - center);
      int y = (j - center);
      float Gs = GetGaussian2DKernel(i, j);
      
      float2 CurPixelPos = clamp(PixelPos + float2(x, y), float2(0, 0), float2(ComputeCommon.Width, ComputeCommon.Height));
      float3 CurrentPixel = inputImage[CurPixelPos].xyz;
      
      // Bilateral with depth
      float2 CurUV = CurPixelPos / float2(ComputeCommon.Width, ComputeCommon.Height);
      float DepthDifference = abs(DepthBuffer.SampleLevel(DepthSampler, CurUV, 0).x - CenterDepth);
      float Gi = Gaussian1D(DepthDifference, ComputeCommon.SigmaForBilateral);
      
      #if USE_GAUSSIAN_INSTEAD
      Color += Gs * CurrentPixel;
      TotalWeight += Gs;
      #else
      Color += Gs * Gi * CurrentPixel;
      TotalWeight += Gs * Gi;
      #endif
    }
  }
  Color /= TotalWeight;
  resultImage[int2(GlobalInvocationID.xy)] = float4(Color, 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-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉬운 점은 Bilateral Filtering 의 경우 Separable Filter 가 아닙니다. 그래서 이 RTAO 에서는 필터 크기가 커질 수록 Bilateral 이 Gaussian 에 비해 연산양이 기하급수 적으로 늘어난다는 점입니다. 이 부분을 개선한 Bilateral Filtering 이 있다면 적용해보면 더 좋을 것 같습니다.&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;RTAO 에 적용한 내용은 여기까지입니다. 구현하면서 느낀 점은 &amp;ldquo;발사되는 Ray 의 수를 줄이기 위해서 좋은 Denoiser 가 중요하다.&amp;rdquo; 입니다. 추후에 Spatiotemporal Variance-Guided Filtering 에 대해서도 다뤄보면 좋을 것 같습니다. 혹시 해당 내용을 다루게 된다면 여기에 링크를 달아두겠습니다.&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;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3.&amp;nbsp;구현&amp;nbsp;결과&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;RTAO 를 위한 렌더패스를 정리하면 아래와 같습니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DispatchRay &amp;rarr; ReprojectionAO &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;CopyDepthBuffer&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; BilateralFilter &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;CopyHistoryBuffer&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;rarr; ApplyAO&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;CopyDepth 와 CopyHistory Buffer 는 이전 프레임의 RenderTarget 을 재활용하여 복사를 제거할 수도 있을 것 같습니다. 하지만 여기서는 명시적으로 이전 렌더타겟 정보를 보존해야 함을 보기 위해서 그대로 남겨뒀습니다.&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;그림12 는 RTAO 의 Denosing 과정별 결과 차이를 보여줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Denosing적용과정.gif&quot; data-origin-width=&quot;1273&quot; data-origin-height=&quot;744&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxScK8/btsEdBhfAGq/iMWy8lyK8STnfNCSZg6IJk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxScK8/btsEdBhfAGq/iMWy8lyK8STnfNCSZg6IJk/img.gif&quot; data-alt=&quot;그림12. Denosing 을 적용하는 과정. Ray Accumulation -&amp;amp;gt; Jittering -&amp;amp;gt; Reprojection -&amp;amp;gt; Bilateral Filtering 순으로 적용. (출처 : 직접 구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxScK8/btsEdBhfAGq/iMWy8lyK8STnfNCSZg6IJk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bxScK8/btsEdBhfAGq/iMWy8lyK8STnfNCSZg6IJk/img.gif&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;1273&quot; height=&quot;744&quot; data-filename=&quot;Denosing적용과정.gif&quot; data-origin-width=&quot;1273&quot; data-origin-height=&quot;744&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림12. Denosing 을 적용하는 과정. Ray Accumulation -&amp;gt; Jittering -&amp;gt; Reprojection -&amp;gt; Bilateral Filtering 순으로 적용. (출처 : 직접 구현)&lt;/figcaption&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;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그림13 은 RTAO 를 Sponza Scene 에 적용하기 전과 후의 비주얼 차이를 보여줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;RTAO적용전후.gif&quot; data-origin-width=&quot;1273&quot; data-origin-height=&quot;744&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cE5wnq/btsEhrdtFqj/ktSIlmD986c1mBywnMBWjK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cE5wnq/btsEhrdtFqj/ktSIlmD986c1mBywnMBWjK/img.gif&quot; data-alt=&quot;그림13. RTAO 적용 전, 후 차이 비교 (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cE5wnq/btsEhrdtFqj/ktSIlmD986c1mBywnMBWjK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cE5wnq/btsEhrdtFqj/ktSIlmD986c1mBywnMBWjK/img.gif&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;1273&quot; height=&quot;744&quot; data-filename=&quot;RTAO적용전후.gif&quot; data-origin-width=&quot;1273&quot; data-origin-height=&quot;744&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림13. RTAO 적용 전, 후 차이 비교 (출처 : 직접구현)&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;아래 동영상은 옵션을 변경하면서 AO 적용 결과를 확인해 본 것입니다. Denosing 이 모두 적용된 경우 AO 렌더타겟을 50%(가로세로를 절반으로 줄여서 1/4 크기)로 한 경우도 Noise 가 크게 눈에 안 띄는 것을 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/Kv7q5g_Q-H0?si=b_sjCbj-nLoyv9--&quot; width=&quot;560&quot; height=&quot;315&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그림14. RTAO 영상 (출처 : 직접구현)&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;2560x1369 (대략 2K) 에서 AO Resolution 을 변경해 보면서 성능을 측정해 보면 아래 그림15와 같습니다. 여기서 표시하는 비율은 가로, 세로 각각에 곱해지는 비율입니다. 즉, 50% 면 가로세로 2배 줄어들기 때문에 최종 해상도는 1/4 배로 줄어듭니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVBdZU/btsEcsxYBuW/6ocnnGq5FFIFddF5Uc45LK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVBdZU/btsEcsxYBuW/6ocnnGq5FFIFddF5Uc45LK/img.png&quot; data-alt=&quot;그림15.&amp;amp;amp;nbsp; 2560x1369 해상도에서 AO Resolution 를 변경해가면서 AO 생성에 사용된 시간 확인.&amp;amp;amp;nbsp; 즉, 50% 면 가로세로 2배 줄어들기 때문에 최종 해상도는 1/4 배로 줄어듬. (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVBdZU/btsEcsxYBuW/6ocnnGq5FFIFddF5Uc45LK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVBdZU%2FbtsEcsxYBuW%2F6ocnnGq5FFIFddF5Uc45LK%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;884&quot; height=&quot;138&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;138&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림15.&amp;amp;nbsp; 2560x1369 해상도에서 AO Resolution 를 변경해가면서 AO 생성에 사용된 시간 확인.&amp;amp;nbsp; 즉, 50% 면 가로세로 2배 줄어들기 때문에 최종 해상도는 1/4 배로 줄어듬. (출처 : 직접구현)&lt;/figcaption&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4.&amp;nbsp;실제&amp;nbsp;구현&amp;nbsp;코드&lt;/b&gt;&lt;/h4&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;아래 경로에서 실제 구현코드를 받아 실행해 볼 수 있습니다. DirectX12, Vulkan 중 선택하여 실행 가능합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;/span&gt;&lt;a href=&quot;https://github.com/scahp/jEngine/tree/RTAO&quot;&gt;https://github.com/scahp/jEngine/tree/RTAO&lt;/a&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;h4 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5.&amp;nbsp;레퍼런스&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;a href=&quot;https://cwyman.org/code/dxrTutors/tutors/Tutor5/tutorial05.md.html&quot;&gt;https://cwyman.org/code/dxrTutors/tutors/Tutor5/tutorial05.md.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;a href=&quot;https://simonstechblog.blogspot.com/2019/09/dxr-ao.html&quot;&gt;https://simonstechblog.blogspot.com/2019/09/dxr-ao.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;a href=&quot;https://www.youtube.com/watch?v=-LD9MxBUFQo&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=-LD9MxBUFQo&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. &lt;a href=&quot;https://www.youtube.com/watch?v=7FP7ndMEfsc&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.youtube.com/watch?v=7FP7ndMEfsc&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. &lt;a href=&quot;https://developer.nvidia.com/downloads/ray-tracing-games-nvidia-rtx-pdf&quot;&gt;https://developer.nvidia.com/downloads/ray-tracing-games-nvidia-rtx-pdf&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. &lt;a href=&quot;https://youtu.be/yag6e2Npw4M&quot;&gt;https://youtu.be/yag6e2Npw4M&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. &lt;a href=&quot;https://scahp.tistory.com/77&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://scahp.tistory.com/77&lt;/a&gt; (원문 : &lt;a href=&quot;https://sugulee.wordpress.com/2021/06/21/temporal-anti-aliasingtaa-tutorial/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sugulee.wordpress.com/2021/06/21/temporal-anti-aliasingtaa-tutorial/&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Graphics/Graphics</category>
      <category>Ambient Occlusion</category>
      <category>ao</category>
      <category>Bilateral</category>
      <category>DirectX12</category>
      <category>DX12</category>
      <category>gaussian</category>
      <category>RayTracing</category>
      <category>Reprojection</category>
      <category>RTAO</category>
      <category>vulkan</category>
      <author>scahp</author>
      <guid isPermaLink="true">https://scahp.tistory.com/123</guid>
      <comments>https://scahp.tistory.com/123#entry123comment</comments>
      <pubDate>Wed, 31 Jan 2024 23:53:14 +0900</pubDate>
    </item>
    <item>
      <title>Bindless Resource - DX12, Vulkan</title>
      <link>https://scahp.tistory.com/122</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: center;&quot; data-ke-size=&quot;size26&quot;&gt;Bindless Resource - DX12, Vulkan&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: right;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;최초 작성 : 2024-01-17&lt;br /&gt;마지막 수정 : 2024-01-17&lt;br /&gt;최재호&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;목차&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1.&amp;nbsp;목표 &lt;br /&gt;2.&amp;nbsp;내용 &lt;br /&gt;2.1.&amp;nbsp;Bindless&amp;nbsp;Resource&amp;nbsp;에&amp;nbsp;대해서 &lt;br /&gt;2.2.&amp;nbsp;API&amp;nbsp;별&amp;nbsp;Bindless&amp;nbsp;Resource &lt;br /&gt;&amp;nbsp;&amp;nbsp;2.2.1.&amp;nbsp;Vulkan&amp;nbsp;Bindless&amp;nbsp;Resource &lt;br /&gt;&amp;nbsp;&amp;nbsp;2.2.2.&amp;nbsp;DX12&amp;nbsp;Bindless&amp;nbsp;Resource &lt;br /&gt;&amp;nbsp;&amp;nbsp;2.2.3.&amp;nbsp;DX12&amp;nbsp;Dynamic&amp;nbsp;Resources &lt;br /&gt;3.&amp;nbsp;레퍼런스&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 목표&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레이트레이싱을 시작하면서 Bindless Resource 필요성을 느껴서 관련해서 알아본 것을 정리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bindless 리소스가 무엇인지 알아보고 어떤 장점이 있는지 알아봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DX12, Vulkan 의 Bindless Resource 의 사용법과 특징을 알아봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 API 모두 HLSL 을 기준으로 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사전지식&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Vulkan 의 Resource Binding 방식의 이해(DescriptorSetLayout, DescriptorSet 생성과 DescriptorSet 에 리소스 기록 방식)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DX12 의 RootSignaure, DescriptorHeap(Shader visible 에 대한 것은 레퍼런스4 참고) 에 대한 이해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 내용&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.1. Bindless Resource 에 대해서&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Shader 를 실행하려면, C++ 코드에서 Shader 에 사용할 리소스들(시그니처)들을 전달해줘야 합니다. 이 리소스를 정의할 때 보통은 아래와 같은 형태로 어떤 리소스가 들어갈 것인지? 몇 개의 리소스가 들어갈 것인지 명시하게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705495504888&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RaytracingAccelerationStructure Scene : register(t0, space0); // Raytracing 용 AccelerationStructure
RWTexture2D&amp;lt;float4&amp;gt; RenderTarget[2] : register(u1, space0); // RenderTarget 2개
ConstantBuffer&amp;lt;SceneConstantBuffer&amp;gt; g_sceneCB : register(b2, space0); // Scene 에 대한 정보를 담는 ConstantBuffer
SamplerState AlbedoTextureSampler : register(s3, space0); // Texture fetch 를 위한 SamplerState&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;Bindless 타입은 아래와 같은 형태로 리소스를 선언하며 몇개의 리소스가 들어갈지는 C++ 코드에서 바인딩하는 수에 맡기게 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705496110540&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;StructuredBuffer&amp;lt;RenderObjectUniformBuffer&amp;gt; RenderObjParamArray[] : register(t0, space5); // RenderObject 의 UniformBuffer
ByteAddressBuffer VerticesBindlessArray[] : register(t0, space6); // VertexBuffer list
Texture2D AlbedoTextureArray[] : register(t0, space7); // Abedo texture list&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;Bindless 를 사용하는 경우 제약이 하나 생기는데 아래와 같이 Bindless 는 register space 1 개에 매핑할 수 있습니다. 왜냐하면 Bindless 로 바인딩된 리소스는 몇 개의 리소스가 있을지 모르기 때문에 아래 코드처럼 t0 에 매핑을 했지만 t10 까지 사용할지 t1000 까지 사용할지는 알 수 없기 때문입니다.&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;Bindless 를 사용하게 되면 얻게 되는 가장 큰 장점은 C++ 에서 명시해줘야 하는 Resource Binding 정보를 최소화할 수 있다는 것입니다. DX12 의 RootSignature 나 Vulkan 의 DescriptorLayout 을 만들 때 훨씬 더 간결하게 만들 수 있습니다. 그리고 Shader code 에서는 Bindless resource 를 적절하게 인덱싱하여 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2.2. API 별 Bindless Resource&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DX12 와 Vulkan 모두 Bindless Resource 를 지원하며, DX12 는 여기서 한 단계 더 나아가서 Dynamic Resources 를 사용할 수 있습니다. Dynamic Resources 를 사용하면 RootSignature 에 Resource 시그니처의 명세가 필요하지 않습니다. 이 기능도 뒤에서 알아봅시다.&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.2.1. Vulkan Bindless Resource&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vulkan 의 Bindless Resource 사용을 위해서 생각보다 기존 방식에 비해서 큰 변화는 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 하나 아쉬웠던 점은 Vulkan 의 경우 register space 를 0~N 개까지 순차적으로 할당할 수 있는 점입니다. DX12 의 경우 register space 를 자유롭게 배정할 수 있어서 &quot;register space 100 번 이후는 Bindless reousrce 로 사용하자&quot; 와 같은 가정을 할 수 있지만 Vulkan 은 그런 것은 안됩니다.&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;그럼 실제 C++ 코드에서 Bindless Resource 를 위한 코드를 봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Bindless Resource 를 사용하기 위해서 API 단에서 활성화해야 할 것들이 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705497366298&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// API 에서 bindless resource 를 위한 기능을 지원하는지 확인
VkPhysicalDeviceDescriptorIndexingFeatures indexing_features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT, nullptr };
VkPhysicalDeviceFeatures2 device_features{ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, &amp;amp;indexing_features };
vkGetPhysicalDeviceFeatures2(PhysicalDevice, &amp;amp;device_features);

bool bindless_supported = indexing_features.descriptorBindingPartiallyBound &amp;amp;&amp;amp; indexing_features.runtimeDescriptorArray;
check(bindless_supported);&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;VkDevice 를 생성하기 위한 VkDeviceCreateInfo 에도 아래 코드와 같이 Feature 를 활성화해줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705497481936&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VkPhysicalDeviceDescriptorIndexingFeaturesEXT physicalDeviceDescriptorIndexingFeatures{};
physicalDeviceDescriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT;
physicalDeviceDescriptorIndexingFeatures.shaderInputAttachmentArrayDynamicIndexing = true;
physicalDeviceDescriptorIndexingFeatures.shaderUniformTexelBufferArrayDynamicIndexing = true;
physicalDeviceDescriptorIndexingFeatures.shaderStorageTexelBufferArrayDynamicIndexing = true;
physicalDeviceDescriptorIndexingFeatures.shaderUniformBufferArrayNonUniformIndexing = true;
physicalDeviceDescriptorIndexingFeatures.shaderSampledImageArrayNonUniformIndexing = true;
physicalDeviceDescriptorIndexingFeatures.shaderStorageBufferArrayNonUniformIndexing = true;
physicalDeviceDescriptorIndexingFeatures.shaderStorageImageArrayNonUniformIndexing = true;
physicalDeviceDescriptorIndexingFeatures.shaderInputAttachmentArrayNonUniformIndexing = true;
physicalDeviceDescriptorIndexingFeatures.shaderUniformTexelBufferArrayNonUniformIndexing = true;
physicalDeviceDescriptorIndexingFeatures.shaderStorageTexelBufferArrayNonUniformIndexing = true;
physicalDeviceDescriptorIndexingFeatures.descriptorBindingUniformBufferUpdateAfterBind = true;
physicalDeviceDescriptorIndexingFeatures.descriptorBindingSampledImageUpdateAfterBind = true;
physicalDeviceDescriptorIndexingFeatures.descriptorBindingStorageImageUpdateAfterBind = true;
physicalDeviceDescriptorIndexingFeatures.descriptorBindingStorageBufferUpdateAfterBind = true;
physicalDeviceDescriptorIndexingFeatures.descriptorBindingUniformTexelBufferUpdateAfterBind = true;
physicalDeviceDescriptorIndexingFeatures.descriptorBindingStorageTexelBufferUpdateAfterBind = true;
physicalDeviceDescriptorIndexingFeatures.descriptorBindingUpdateUnusedWhilePending = true;
physicalDeviceDescriptorIndexingFeatures.descriptorBindingPartiallyBound = true;
physicalDeviceDescriptorIndexingFeatures.descriptorBindingVariableDescriptorCount = true;
physicalDeviceDescriptorIndexingFeatures.runtimeDescriptorArray = true;
physicalDeviceDescriptorIndexingFeatures.pNext = (void*)createInfo.pNext;
createInfo.pNext = &amp;amp;physicalDeviceDescriptorIndexingFeatures;&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;Vulkan 은 DescriptorPool 을 통해서 Descriptor 를 할당합니다. 이 Descriptor 또한 Bindless Resource 지원에 필요한 옵션을 사용하여 생성해야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705497579307&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;VkDescriptorPoolCreateInfo PoolInfo{};
PoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
PoolInfo.poolSizeCount = NumOfPoolSize;
PoolInfo.pPoolSizes = Types;
PoolInfo.maxSets = InMaxDescriptorSets;
PoolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_UPDATE_AFTER_BIND_BIT;       // for bindless resources

verify(VK_SUCCESS == vkCreateDescriptorPool(g_rhi_vk-&amp;gt;Device, &amp;amp;PoolInfo, nullptr, &amp;amp;DescriptorPool));&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;이제 DescriptorSetLayout 과 DescriptorSet 의 할당을 확인해 봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1705498804831&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Descriptor Layout Creation 
std::vector&amp;lt;VkDescriptorSetLayoutBinding&amp;gt; bindings;
bindings.reserve(InShaderBindingArray.NumOfData);

std::vector&amp;lt;VkDescriptorBindingFlagsEXT&amp;gt; bindingFlags;
bindingFlags.reserve(InShaderBindingArray.NumOfData);

// Iteration for Shader Binding Resource List
for (int32 i = 0; i &amp;lt; (int32)InShaderBindingArray.NumOfData; ++i)
{
    VkDescriptorSetLayoutBinding binding = {};
    ...
    bindings.push_back(binding);
    if (IsBindless)
        bindingFlags.push_back(VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT | VK_DESCRIPTOR_BINDING_UPDATE_AFTER_BIND_BIT);
    else
        bindingFlags.push_back(0);
}

VkDescriptorSetLayoutCreateInfo layoutInfo = {};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = static_cast&amp;lt;uint32&amp;gt;(bindings.size());                     // Number of bindless resources here!
layoutInfo.pBindings = bindings.data();
layoutInfo.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_UPDATE_AFTER_BIND_POOL_BIT;      // for bindless resources

// for bindless resources
if (bindingFlags.size() &amp;gt; 0)
{
    VkDescriptorSetLayoutBindingFlagsCreateInfoEXT setLayoutBindingFlags{};
    setLayoutBindingFlags.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO_EXT;
    setLayoutBindingFlags.bindingCount = (uint32)bindingFlags.size();
    setLayoutBindingFlags.pBindingFlags = bindingFlags.data();
    layoutInfo.pNext = &amp;amp;setLayoutBindingFlags;
}

// DescriptorSet Creation - nothing different
VkDescriptorSetAllocateInfo DescriptorSetAllocateInfo{};
DescriptorSetAllocateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
DescriptorSetAllocateInfo.descriptorPool = DescriptorPool;
DescriptorSetAllocateInfo.descriptorSetCount = 1;
DescriptorSetAllocateInfo.pSetLayouts = &amp;amp;InLayout;

VkDescriptorSet NewDescriptorSet = nullptr;
vkAllocateDescriptorSets(g_rhi_vk-&amp;gt;Device, &amp;amp;DescriptorSetAllocateInfo, &amp;amp;NewDescriptorSet);&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;마지막으로 DescriptorSet 에 Resource 를 실제로 바인딩하는 과정입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705499100375&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Bindless resource 에 리소스 할당
for(int32 bindlessIndex = 0;bindlessIndex&amp;lt;NumOfBindlessResources;++bindlessIndex)
{
    VkWriteDescriptorSet&amp;amp; CurDescriptorWrite = descriptorWrites[k];
    CurDescriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
    CurDescriptorWrite.dstSet = InDescriptorSet;
    CurDescriptorWrite.dstBinding = bindingIndex;
    CurDescriptorWrite.dstArrayElement = bindlessIndex;         // bindless resources array index
    CurDescriptorWrite.descriptorType = GetVulkanShaderBindingType(InShaderBindingArray[i]-&amp;gt;BindingType);
    CurDescriptorWrite.descriptorCount = 1;
}
vkUpdateDescriptorSets(g_rhi_vk-&amp;gt;Device, static_cast&amp;lt;uint32&amp;gt;(CurDescriptorWrite.size())
    , CurDescriptorWrite.data(), 0, nullptr);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.2.2. DX12 Bindless Resource&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DX12 의 Bindless Resource 도 기존 방식에 비해서 큰 변화는 없습니다. 하지만 Vulkan 에 비해서 조금 더 고려해야 할 점은 Resource 에 대한 Descriptor 를 DescriptorHeap 에 바인딩할 때, 해당 Descriptor 의 Offset 이 얼마인지 잘 계산해줘야 한다는 점입니다. 이런 부분들을 레퍼런스7 을 참고해 보면, 별도의 CBV 를 통해서 사용하고자 하는 Bindless Resource 의 Index 를 추가 리소스로 바인딩하기도 합니다. 이렇게 별도의 디스크립터의 Offset 를 CBV 로 전달하는 방식은 2.2.3. DX12 Dynamic Resources 에서는 반드시 필요합니다. 아래 그림1을 참고해 주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;577&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AFykT/btsDCRYbY4T/SZOgU4fE7UXo811nJgs1k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AFykT/btsDCRYbY4T/SZOgU4fE7UXo811nJgs1k0/img.png&quot; data-alt=&quot;그림1. CBV 를 통해서 Bindless Resource 의 시작 지점을 레퍼런싱 해줌 (출처 : 레퍼런스8)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AFykT/btsDCRYbY4T/SZOgU4fE7UXo811nJgs1k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAFykT%2FbtsDCRYbY4T%2FSZOgU4fE7UXo811nJgs1k0%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;559&quot; height=&quot;395&quot; data-origin-width=&quot;817&quot; data-origin-height=&quot;577&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림1. CBV 를 통해서 Bindless Resource 의 시작 지점을 레퍼런싱 해줌 (출처 : 레퍼런스8)&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;아쉽게도 Vulkan 에서는 Dynamic Resources 방식을 지원하지 않기 때문에 &lt;a href=&quot;https://scahp.tistory.com/120&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;jEngine&lt;/a&gt; 의 Raytracing 작업을 추가할 때는 이 기능을 사용하지 않았습니다. 그 대신 Descriptor Index CBV 를 만들지 않고, D3D12_DESCRIPTOR_RANGE1 의 OffsetInDescriptorsFromTableStart 에 해당 Bindless 리소스의 시작 인덱스를 명시하는 방식을 사용하여 추가 CBV 생성을 피했습니다.&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;DX12 의 DescriptorHeap 이 Vulkan 의 DescriptorPool 에 비해 좀 더 어려웠던 점은 SetGraphicsRootDescriptorTable 의&amp;nbsp; 통해 바인딩한 DescriptorHeap 으로 부터 Descriptor 의 Offset 이 얼마나 떨어져 있는지 여부를 모두 고려해줘야 한다는 점입니다. Index 를 관리하는 CBV 버퍼 같은 부분은 Vulkan 에서는 사용하지 않아도 되는 점을 고려해 보면 Vulkan 이 Bindless Resource 를 사용하기 더 편한 것 같다고 생각됩니다.&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;그럼 실제 Bindless Resource 사용을 위해 변경되는 점을 확인해 봅시다.&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;먼저 Descriptor 의 생성과 RootSignature 의 생성입니다. 아래 코드를 참고해 주세요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Bindless Resource 는 DescriptorTable 을 통해 생성할 수 있는데 이 경우 Descriptor 1 개는 D3D12_DESCRIPTOR_RANGE1 로 정의 됩니다. 여기서 Bindless Resource 의 Offset 을 정의해 주기 위해서 OffsetInDescriptorsFromTableStart 에 해당 Descriptor 가 DescriptorHeap 의 시작위치에서 얼마나 떨어져 있는지 명시해 주면 됩니다. (DescriptorHeap 의 시작위치는 SetGraphicsRootDescriptorTable&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;을 통해 바인딩한&lt;span&gt; DescriptorHeap 의 D3D12_GPU_DESCRIPTOR_HANDLE 기준)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DX12 의 경우 RootSignaure 생성 시&amp;nbsp; D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED 를 명시해줘야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705499870454&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// SamplerState 에 대한 Descriptor 를 정의함. DescriptorTable 에 사용될 예정
D3D12_DESCRIPTOR_RANGE1 range = {};
range.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
range.NumDescriptors = ShaderBinding-&amp;gt;NumOfDescriptors;
range.BaseShaderRegister = BindingIndex;
range.RegisterSpace = InRegisterSpace;
if (IsBindless)
{
    range.OffsetInDescriptorsFromTableStart = InOutSamplerDescriptorOffset; // Bindless resource 의 시작점이 DescriptorHeap 에서 얼마나 떨어져있는지 명시!
    range.Flags = D3D12_DESCRIPTOR_RANGE_FLAG_DESCRIPTORS_VOLATILE;
}
else
{
    range.OffsetInDescriptorsFromTableStart = InOutSamplerDescriptorOffset;
    range.Flags = D3D12_DESCRIPTOR_RANGE_FLAG_NONE;
}
InOutSamplerDescriptorOffset += ShaderBinding-&amp;gt;NumOfDescriptors; // 다음 리소스를 위해서 Descriptor Offset 을 누적시킴
SamplerDescriptors.emplace_back(range);


// RootSignature 생성
D3D12_ROOT_SIGNATURE_DESC1 rootSignatureDesc = {};
rootSignatureDesc.NumParameters = (uint32)DescriptorExtractor.RootParameters.size();
rootSignatureDesc.pParameters = &amp;amp;DescriptorExtractor.RootParameters[0];
rootSignatureDesc.NumStaticSamplers = 0;
rootSignatureDesc.pStaticSamplers = nullptr;
rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
    | D3D12_ROOT_SIGNATURE_FLAG_CBV_SRV_UAV_HEAP_DIRECTLY_INDEXED | D3D12_ROOT_SIGNATURE_FLAG_SAMPLER_HEAP_DIRECTLY_INDEXED;        // Support for BindlessResource

D3D12_VERSIONED_ROOT_SIGNATURE_DESC versionedDesc = { };
versionedDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1;
versionedDesc.Desc_1_1 = rootSignatureDesc;

ComPtr&amp;lt;ID3DBlob&amp;gt; signature;
ComPtr&amp;lt;ID3DBlob&amp;gt; error;
if (JFAIL_E(D3D12SerializeVersionedRootSignature(&amp;amp;versionedDesc, &amp;amp;signature, &amp;amp;error), error))
{
    return nullptr;
}

ComPtr&amp;lt;ID3D12RootSignature&amp;gt; RootSignature;
if (JFAIL(g_rhi_dx12-&amp;gt;Device-&amp;gt;CreateRootSignature(0, signature-&amp;gt;GetBufferPointer(), signature-&amp;gt;GetBufferSize(), IID_PPV_ARGS(&amp;amp;RootSignature))))
    return nullptr;&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;나머지 과정은 기존과정과 동일합니다. Descriptor 를 Shader Visible Descriptor Heap 에 복사하여 RootSignature 에 명시한 Descriptor 의 Layout 과 일치하도록 DescriptorHeap 을 구성해 주면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1705501909554&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// SRV,UAV,CBV Shader Visible Descriptor Heap
if (Descriptors.size() &amp;gt; 0)
{
...
    for (int32 i = 0; i &amp;lt; Descriptors.size(); ++i)
    {
        SrcDescriptor.Add(Descriptors[i].Descriptor.CPUHandle);

        // // Get next descriptor handle from shader visible descriptor heap
        jDescriptor_DX12 Descriptor = InCommandList-&amp;gt;OnlineDescriptorHeap-&amp;gt;Alloc();
        DestDescriptor.Add(Descriptor.CPUHandle); // CPUHandle == D3D12_CPU_DESCRIPTOR_HANDLE
    }

    g_rhi_dx12-&amp;gt;Device-&amp;gt;CopyDescriptors((uint32)DestDescriptor.NumOfData, &amp;amp;DestDescriptor[0], nullptr
        , (uint32)SrcDescriptor.NumOfData, &amp;amp;SrcDescriptor[0], nullptr, D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
}&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;2.2.3. DX12 Dynamic Resources&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 알아볼 것은 Dynamic Resources 입니다. 여기서부터는 더 이상 RootSignaure 가 필요 없습니다. 프로그래머는 그냥 DescriptorHeap 에 원하는 방식대로 Descriptor 를 구성해두고 Shader 내에서는 ResourceDescriptorHeap 를 인덱싱하여 SRV, UAV, CBV 의 리소스를 참조할 수 있으며 SamplerState 의 경우 SamplerDescriptorHeap 를 사용할 수 있습니다. 그래서 이 경우 별도의 CBV 를 통해 내가 DescriptorHeap 에 배치한 리소스의 시작 인덱스를 Shader 에 알려줘야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ResourceDescriptorHeap 과 SamplerDescriptorHeap 은 다양한 형태의 리소스로 자유롭게 형변환 됩니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;아래 코드는 Dynamic Resource 의 예제입니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1705502366509&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// SRV,UAV,CBV
StructuredBuffer&amp;lt;RenderObjectUniformBuffer&amp;gt; RenderObjParam = ResourceDescriptorHeap[(PerPrimOffset++) + InstanceIdx * Stride];
ByteAddressBuffer VerticesBindless = ResourceDescriptorHeap[(PerPrimOffset++) + InstanceIdx * Stride];
Texture2D AlbedoTexture = ResourceDescriptorHeap[(PerPrimOffset++) + InstanceIdx * Stride];

// SamplerState
SamplerState AlbedoTextureSampler = SamplerDescriptorHeap[(PerPrimOffset++) + InstanceIdx * Stride];&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;개인적으로는 RootSignaure 를 구성하는 게 굉장히 고통스러운 과정이라 이 부분 자체를 없애준 것에 대해서 굉장히 반가운 기능인 것 같습니다. 하지만 이 방식은 CBV 의 인덱스를 사용하여 암묵적으로 리소스를 변환시켜 사용하기 때문에 이슈가 발생했을 때 디버깅하기는 어려울 것 같습니다.&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;Bindless Reosurce 는 Raytracing 에서 여러 오브젝트들의 Vertex 나 Index 정보를 전달하는 데 사용할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/57J1q/btsDGuoeiHK/G9WtWmGmfYWQfJOUquKpYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/57J1q/btsDGuoeiHK/G9WtWmGmfYWQfJOUquKpYK/img.png&quot; data-alt=&quot;그림2. Bindless Resource 를 사용한 DXR, Vulkan RaytracingInOneWeekend 예제 (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/57J1q/btsDGuoeiHK/G9WtWmGmfYWQfJOUquKpYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F57J1q%2FbtsDGuoeiHK%2FG9WtWmGmfYWQfJOUquKpYK%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;1282&quot; height=&quot;752&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림2. Bindless Resource 를 사용한 DXR, Vulkan RaytracingInOneWeekend 예제 (출처 : 직접구현)&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwZ7Z8/btsDETAXJ7O/Tkrd1uktFLMV83IYLM83eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwZ7Z8/btsDETAXJ7O/Tkrd1uktFLMV83IYLM83eK/img.png&quot; data-alt=&quot;그림3. Bindless Resource 를 사용하여 여러 종류의 메시를 Vertex 와 Index 정보를 Shader 에서 InstanceID 를 기반으로 Fetch 하여 렌더링. DXR, VkRaytracing 기능 테스트용 예제 (출처 : 직접구현)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwZ7Z8/btsDETAXJ7O/Tkrd1uktFLMV83IYLM83eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwZ7Z8%2FbtsDETAXJ7O%2FTkrd1uktFLMV83IYLM83eK%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;1282&quot; height=&quot;752&quot; data-origin-width=&quot;1282&quot; data-origin-height=&quot;752&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;그림3. Bindless Resource 를 사용하여 여러 종류의 메시를 Vertex 와 Index 정보를 Shader 에서 InstanceID 를 기반으로 Fetch 하여 렌더링. DXR, VkRaytracing 기능 테스트용 예제 (출처 : 직접구현)&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;그림2 의 구현코드 : &lt;a href=&quot;https://github.com/scahp/jEngine/tree/RaytracingOneWeekend&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/scahp/jEngine/tree/RaytracingOneWeekend&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림3 의 구현코드 : &lt;a href=&quot;https://github.com/scahp/jEngine/tree/64afe3394d3224dc2d430a8f0fd5023821462819&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/scahp/jEngine/tree/64afe3394d3224dc2d430a8f0fd5023821462819&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 레퍼런스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;a href=&quot;https://alextardif.com/Bindless.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://alextardif.com/Bindless.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;a href=&quot;https://jorenjoestar.github.io/post/vulkan_bindless_texture/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jorenjoestar.github.io/post/vulkan_bindless_texture/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;a href=&quot;https://microsoft.github.io/DirectX-Specs/d3d/HLSL_SM_6_6_DynamicResources.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://microsoft.github.io/DirectX-Specs/d3d/HLSL_SM_6_6_DynamicResources.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. &lt;a href=&quot;https://scahp.tistory.com/117&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://scahp.tistory.com/117&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. &lt;a href=&quot;https://rtarun9.github.io/blogs/bindless_rendering/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://rtarun9.github.io/blogs/bindless_rendering/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. &lt;a href=&quot;https://wickedengine.net/2021/04/06/bindless-descriptors/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://wickedengine.net/2021/04/06/bindless-descriptors/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. &lt;a href=&quot;https://github.com/TheRealMJP/DeferredTexturing&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/TheRealMJP/DeferredTexturing&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. &lt;a href=&quot;https://developer.nvidia.com/ray-tracing-gems-ii&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://developer.nvidia.com/ray-tracing-gems-ii&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Graphics/Graphics</category>
      <category>bindless</category>
      <category>descriptor</category>
      <category>DescriptorHeap</category>
      <category>DescriptorPool</category>
      <category>DX12</category>
      <category>DynamicResource</category>
      <category>RayTracing</category>
      <category>resourcebinding</category>
      <category>RootSignautre</category>
      <category>vulkan</category>
      <author>scahp</author>
      <guid isPermaLink="true">https://scahp.tistory.com/122</guid>
      <comments>https://scahp.tistory.com/122#entry122comment</comments>
      <pubDate>Wed, 17 Jan 2024 23:55:55 +0900</pubDate>
    </item>
    <item>
      <title>jEngine Library 빌드 관련</title>
      <link>https://scahp.tistory.com/121</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Library 빌드 관련해서 겪은 이슈들을 정리합니다. 추가 사항이 있는 경우 계속 이 문서를 업데이트 할 예정입니다.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: right;&quot; data-ke-size=&quot;size16&quot;&gt;최초 작성 : 2023-12-29&lt;br /&gt;마지막 수정 : 2024-03-23&lt;br /&gt;최재호&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;목차&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. ShaderConductor [Deprecated]&lt;br /&gt;2. DirectXTex&lt;br /&gt;3. xxHash, CityHash&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. DirectX12 Agility SDK&lt;/p&gt;
&lt;hr 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;1. ShaderConductor [Deprecated]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Updated 2024-01-11 : Shader Language 를 HLSL, DXC 로 DX12 와 Vulkan 쉐이더 컴파일에 문제 없습니다. 추후 Metal 이나 다른 API로 포팅이 필요한 경우 다시 사용할 예정입니다.&lt;br /&gt;&amp;nbsp;&lt;br /&gt;ShaderConductor 는 hlsl 을 사용하여 &lt;span style=&quot;color: #333333;&quot;&gt;Vulkan, &lt;/span&gt;DirectX12 의 shader cross compile 을 지원합니다.&lt;br /&gt;하지만 ShaderConductor 는 2년 전 업데이트를 멈춘 상태입니다. 때문에 Raytracing 에 사용하기 위해 Bindless resources 기능을 사용하려고 하면 쉐이더 컴파일 에러가 발생합니다.&lt;br /&gt;ShaderConductor Git 링크:&amp;nbsp; &lt;a href=&quot;https://github.com/microsoft/ShaderConductor&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://github.com/microsoft/ShaderConductor&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;cpp&quot;&gt;&lt;code&gt;// SM6.6 으로 컴파일 한 상황

// Issue 1 : SM6.6 의 HLSL Dynamic Resource 기능 중 하나인 ResourceDescriptorHeap 사용 불가
error: use of undeclared identifier 'ResourceDescriptorHeap'

// Issue 2 : Bindless resources 기능을 사용하기 위해 Unbounded range 설정 시 Validation error 발생
RWTexture2D&amp;lt;float4&amp;gt; RenderTarget[] : register(u0);&amp;nbsp;&amp;nbsp; // 이렇게 선언 시, 아래 처럼 에러남.
----
error: validation errors
hlsl.hlsl:728:5: error: Access to out-of-bounds memory is disallowed.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ShaderConductor 가 사용하는 DirectXShaderCompiler 최신 버전(1.7.2308)에서는 문제가 해결되는 것을 확인하여 해당 라이브러리를 별도로 빌드하여 dll 을 교체해줬습니다. 컴파일된 dxcompiler.dll 은 ShaderConductor/Lib/{Configurations} 각각 넣어줬습니다.&lt;br /&gt;DirectXShaderCompiler 를 컴파일 방법은 &lt;a href=&quot;https://github.com/microsoft/DirectXShaderCompiler&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://github.com/microsoft/DirectXShaderCompiler&lt;/span&gt;&lt;/a&gt; 의 ReadMe 의 &lt;span style=&quot;color: #333333;&quot;&gt;Building Sources&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;&lt;/span&gt;에 잘 나옵니다. 명세된 프로그램들을 설치 한 후 설명대로 그대로 진행하면 됩니다.&lt;br /&gt;추가로 한글과 스샷으로 설치방법을 친절하게 설명해주는 블로그도 있습니다. 아래 링크 참고해주세요.&lt;br /&gt;&lt;a href=&quot;https://lifeisforu.tistory.com/504&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;[&amp;nbsp;Vulkan&amp;nbsp;연구&amp;nbsp;]&amp;nbsp;HLSL&amp;nbsp;to&amp;nbsp;SPIR-V&amp;nbsp;:&amp;nbsp;2.&amp;nbsp;DXC&amp;nbsp;빌드&lt;/span&gt;&lt;/a&gt;, &lt;a href=&quot;https://lifeisforu.tistory.com/505&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;[&amp;nbsp;Vulkan&amp;nbsp;연구&amp;nbsp;]&amp;nbsp;HLSL&amp;nbsp;to&amp;nbsp;SPIR-V&amp;nbsp;:&amp;nbsp;3.&amp;nbsp;SPIR-V&amp;nbsp;CodeGen&amp;nbsp;빌드&lt;/span&gt;&lt;/a&gt;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. DirectXTex&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDS, HDR, png, jpg 등등의 이미지 로드를 해주는 라이브러리입니다. MS 에서 제작한 것이라 DirectX12 에서만 사용가능한 줄 알았는데 Vulkan 에서도 사용할 수 있는 것이 확인하여 이미지 로드는 DirectXTex 라이브러리로 수행합니다. DirectXTex 는 Mipmap 을 생성해주는 기능도 포함하고 있어서 별도로 Mipmap 을 생성하는 렌더패스를 구현하지 않아도 되서 편리합니다. 아래 코드를 참고해주세요.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;DirectX::ScratchImage image;
if (ExtName == ExtDDS) // DDS 이미지 로드
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (JFAIL(DirectX::LoadFromDDSFile(FilenameWChar.c_str(), DirectX::DDS_FLAGS_NONE, nullptr, image)))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return std::weak_ptr&amp;lt;jImageData&amp;gt;();
}
else if (ExtName == ExtHDR) // HDR 이미지 로드
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (JFAIL(DirectX::LoadFromHDRFile(FilenameWChar.c_str(), nullptr, image)))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return std::weak_ptr&amp;lt;jImageData&amp;gt;();
}
else // png, jpg 와 같은 일반 이미지 로드
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DirectX::ScratchImage imageOrigin;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (JFAIL(DirectX::LoadFromWICFile(FilenameWChar.c_str(), DirectX::WIC_FLAGS_FORCE_RGB, nullptr, imageOrigin)))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return std::weak_ptr&amp;lt;jImageData&amp;gt;();

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// 밉맵 생성도 가능함. png, jpg 의 경우 mipmap 이 포함되지 않기 때문에 이 과정을 추가해줌.
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int32 mipLevel = jTexture::GetMipLevels((int32)imageOrigin.GetMetadata().width, (int32)imageOrigin.GetMetadata().height);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DirectX::GenerateMipMaps(*imageOrigin.GetImages(), DirectX::TEX_FILTER_BOX | DirectX::TEX_FILTER_SEPARATE_ALPHA, mipLevel, image);
}

// 이후 과정에서 DirectX::ScratchImage 를 사용하여 Vulkan, DX12 의 텍스쳐 생성 규약에 맞게 생성함.
// 해당 부분은 아래 링크 파일을 참고해주세요.
// https://github.com/scahp/jEngine/blob/f7982eed34ba4d7ce75d2a9520dfb304901cc675/jEngine/FileLoader/jImageFileLoader.cpp#L123
...&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;3. xxHash, CityHash&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 구조체 내에 일부 멤버변수들에 대한 Hash 를 구해야하는 경우가 있을 수 있습니다. 이런 Hash 를 사용할 멤버 변수 마다 별도로 해시 함수를 호출해야 하기 때문에 함수 호출을 여러번해야 되고 이런 이유로 퍼포먼스가 떨어질 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;struct INeedHash
{
	int32 A; // Hash
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;int32 B; // Hash
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const char* C; // No Hash
};

// I want to generate Hash without member variable C.
// So, I call XXH64 for each members. It would be bad performance.
INeedHash Data;
Hash = XXH64(Data.A, Hash);
Hash = XXH64(Data.B, Hash);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;이 부분을 해결하기 위해서 아래와 같이 InstantStruct 를 사용합니다. 전달받은 변수들을 기반으로 Trivially copyable 형태의 struct 를 정의합니다. 그리고 전달 받은 멤버 변수의 값으로 초기화합니다. 이렇게 되면 1개의 struct 데이터 덩어리가 되며, 한번의 해시함수 호출로 모든 과정을 마칠 수 있습니다. 실제로 각각의 해시 함수를 호출 할 때 보다 성능이 더 좋았으며 CPU 병목지점이 Hash 함수 생성에서 다른 지점으로 옮겨갔습니다.&lt;br /&gt;InstantStruct 클래스의 전체 코드는 &lt;a href=&quot;https://github.com/scahp/jEngine/blob/main/jEngine/Core/TInstantStruct.h&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;https://github.com/scahp/jEngine/blob/main/jEngine/Core/TInstantStruct.h&lt;/span&gt;&lt;/a&gt; 에 있습니다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// Easy hash generation from InstantStruct, it is slow calling 'Hash Generation Function' for each variables Indivisually.
#define GETHASH_FROM_INSTANT_STRUCT(...) \
[&amp;amp;]() -&amp;gt; uint64 { auto InstanceStruct = jInstantStruct(__VA_ARGS__); return XXH64(&amp;amp;InstanceStruct, sizeof(InstanceStruct), 0); }()

// RenderTarget.h
...
size_t GetHash() const
{
&amp;nbsp;&amp;nbsp;// Generate struct and set all member variables, then calling the XXH64 function once.
&amp;nbsp;&amp;nbsp;return GETHASH_FROM_INSTANT_STRUCT(Type, Format, Width, Height, LayerCount, IsGenerateMipmap
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;, SampleCount, RTClearValue.GetHash(), TextureCreateFlag, IsUseAsSubpassInput, IsMemoryless);&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; // without ResourceName
}
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;br /&gt;&amp;nbsp;struct 로부터 Hash 를 얻어낼 때 주의할 점은 struct 에 컴파일러가 넣을 수 있는 패딩 값에 의해서 쓰레기 값이 struct 의 인스턴스에 들어갈 수 있다는 점입니다. 그래서 같은 데이터를 사용하더라도 Hash 결과가 달라질 수 있습니다. 이런 점을 피하기 위해서 패딩값이 적절히 잘 초기화 될 수 있도록 하거나 struct 를 #pragma pack(push, 1) ~ #pragma pack(pop) 으로 감싸줘서 패딩 값이 해시 생성에 포함되지 않도록 해줘야 합니다.&amp;nbsp;&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. DirectX12 Agility SDK &lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Windows10 에서 SM6.6 이 지원되지 않는 것을 발견했습니다. Windows10 의 마지막 업데이트에는 SM6.6 을 지원하는 버전이 포함되지 않기 때문이었습니다. (Windows11 은 SM6.6 이 지원되는 DirectX 라이브러리가 포함되어 있음)&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;구글링 해본 결과 DirectX 라이브러리 업데이트 정책이 바뀌었는데,&lt;span&gt; &lt;/span&gt;&lt;/span&gt;예전에는 윈도우 업데이트를 통해서 DirectX 라이브러리를 업데이트 했지만 윈도우 업데이트 보다 상대적으로 자주 변경되는 DirectX 라이브러리를 관리하기 위해서 DirectX12 Agility SDK 를 사용하게 변경되었다고 합니다. 그래서 jEngine 이 필요한 버전의 lib 를 DirectX 12 Agility SDK 에서 받은 것으로 로드 할 수 있게 했습니다. 현재 613 버전이 적용되어 있습니다.&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;DirectX 12 Agility SDK 를 사용방법은&amp;nbsp;&lt;a href=&quot;https://devblogs.microsoft.com/directx/gettingstarted-dx12agility/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;Getting Started with the Agility SDK&lt;/a&gt;, 그리고 다운로드는&amp;nbsp;&lt;a href=&quot;https://devblogs.microsoft.com/directx/directx12agility/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;DirectX 12 Agility SDK Downloads&lt;/a&gt; 를 참고해주세요.&lt;br /&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Graphics/Renderer Lab</category>
      <category>DirectXShaderCompiler</category>
      <category>DX12</category>
      <category>jEngine</category>
      <category>library</category>
      <category>ShaderConductor</category>
      <category>vulkan</category>
      <author>scahp</author>
      <guid isPermaLink="true">https://scahp.tistory.com/121</guid>
      <comments>https://scahp.tistory.com/121#entry121comment</comments>
      <pubDate>Fri, 29 Dec 2023 22:55:58 +0900</pubDate>
    </item>
  </channel>
</rss>