<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://smg-927.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://smg-927.github.io/" rel="alternate" type="text/html" /><updated>2026-05-31T02:42:08+00:00</updated><id>https://smg-927.github.io/feed.xml</id><title type="html">BloomerLab</title><subtitle>게임 개발 취준생의 블로그</subtitle><author><name>Song mingyu</name></author><entry><title type="html">[3D Programming with DirectX12] Chapter 17 : Picking</title><link href="https://smg-927.github.io/2026/05/30/DirectX12_Chap17/" rel="alternate" type="text/html" title="[3D Programming with DirectX12] Chapter 17 : Picking" /><published>2026-05-30T00:00:00+00:00</published><updated>2026-05-30T00:00:00+00:00</updated><id>https://smg-927.github.io/2026/05/30/DirectX12_Chap17</id><content type="html" xml:base="https://smg-927.github.io/2026/05/30/DirectX12_Chap17/"><![CDATA[<p>이번 장에서는 Picking에 대해 알아보자.</p>

<div style="display: flex; gap: 16px; align-items: flex-start;">
  <figure style="flex: 1; margin: 0; text-align: center;">
    <img src="/assets/images/chap17/PickingExample.gif" style="width: 120%;" />
    <figcaption>Picking Example</figcaption>
  </figure>
  <figure style="flex: 1; margin: 0; text-align: center;">
    <img src="/assets/images/chap17/pointing.png" style="width: 80%;" />
    <figcaption>가리킬 땐 손가락을 써보아요</figcaption>
  </figure>
</div>

<p>유니티 에디터 화면에서 오브젝트를 클릭하면 딱 선택이 된다. 분명 2D 스크린의 좌표를 클릭했을 뿐인데 어떻게 알고 3D 공간안에 오브젝트가 선택이 되는걸까? 우리가 살면서 보통 어떤 것을 가리킬때, 손가락을 이용한다. 그 이유는 방향을 알려주기 위함인데, 컴퓨터에서도 역시 화면안의 한 점에서부터 공간으로 선을 발사하여 닿는 물체를 선택하는 것이다.</p>

<h2 id="screen-to-projection-window-transform">Screen to Projection Window Transform</h2>

<figure>
  <img src="/assets/images/chap17/PickingFlow.jpg" />
  <figcaption>전체 Picking Flow</figcaption>
</figure>

<p>이것이 스크린 좌표를 3D로 옮기는 전체적인 플로우이다. 하나씩 수식을 통해 설명해보겠다.</p>

<h3 id="screen-to-ndc">Screen to NDC</h3>

<p>NDC(Normalized Device Coordinate)에서 Screen space 변환 Matrix는 다음과 같다.
\(\mathbf{M} = \begin{bmatrix}
\dfrac{Width}{2} &amp; 0 &amp; 0 &amp; 0 \\[4pt]
0 &amp; -\dfrac{Height}{2} &amp; 0 &amp; 0 \\[4pt]
0 &amp; 0 &amp; MaxDepth - MinDepth &amp; 0 \\[4pt]
TopLeftX + \dfrac{Width}{2} &amp; TopLeftY + \dfrac{Height}{2} &amp; MinDepth &amp; 1
\end{bmatrix}\)</p>

<p>여기서 depth buffer range는 0에서 1이고, TopLeftX = TopLeftY = 0 으로 둘 수 있으므로 식은</p>

\[\mathbf{M} = \begin{bmatrix}
\dfrac{w}{2} &amp; 0 &amp; 0 &amp; 0 \\[2pt]
0 &amp; -\dfrac{h}{2} &amp; 0 &amp; 0 \\[2pt]
0 &amp; 0 &amp; 1 &amp; 0 \\[2pt]
\dfrac{w}{2} &amp; \dfrac{h}{2} &amp; 0 &amp; 1
\end{bmatrix}\]

<p>로 단순화 가능하다. 이제 NDC위의 점 \(\mathbf{p}_{ndc} = (x_{ndc},\ y_{ndc},\ z_{ndc},\ 1)\)를 통해 계산을 하면</p>

\[[x_{ndc},\ y_{ndc},\ z_{ndc},\ 1]
\begin{bmatrix}
w/2 &amp; 0 &amp; 0 &amp; 0 \\[2pt]
0 &amp; -h/2 &amp; 0 &amp; 0 \\[2pt]
0 &amp; 0 &amp; 1 &amp; 0 \\[2pt]
w/2 &amp; h/2 &amp; 0 &amp; 1
\end{bmatrix}
=
\left[\frac{x_{ndc}w+w}{2},\ \frac{-y_{ndc}h+h}{2},\ z_{ndc},\ 1\right]\]

\[\mathbf{x_s} = \frac{\mathbf{x_ndc}w + w}{2}, \mathbf{x_ndc} = \frac{2x_s}{w} - 1\]

\[\mathbf{y_s} = \frac{-\mathbf{y_ndc}h + h}{2}, \mathbf{y_ndc} = -\frac{2y_s}{h} + 1\]

<p>이렇게 ndc내의 x,y좌표를 구할 수 있다.</p>

<h3 id="ndc-to-view">NDC to View</h3>

<p>이제 NDC위의 점을 Projection Window로 변환할 차례이다. NDC는 Viewport를 길이가 1인 정육면체로 만들어놓은 것이이므로 실제 Viewport의 화면비 \(\mathbf{r}\)을 적용시키면</p>

\[r = \frac{w}{h}\]

\[\mathbf{x_v} = r(\frac{\mathbf{x_ndc}w + w}{2})\]

\[\mathbf{y_v} = \frac{-\mathbf{y_ndc}h + h}{2}\]

<figure>
  <img src="/assets/images/chap17/ProjToView.png" />
  <figcaption> View </figcaption>
</figure>

<p>\(\mathbf{x_v , y_v}\)는 Projection Window 위에 있으므로 상황에 따라 다른 distance에 위치하므로 추가 계산을 통해 카메라로부터 z거리가 1인 점으로 변환이 필요하다. y시야각의 1/2인 \(\mathbf{\alpha}\)를 이용하면</p>

\[x'_v = \frac{x_v}{d} = \frac{x_v}{\cot\left(\frac{\alpha}{2}\right)} = x_v \cdot \tan\left(\frac{\alpha}{2}\right) = \left(\frac{2s_x}{w} - 1\right)r\tan\left(\frac{\alpha}{2}\right)\]

\[y'_v = \frac{y_v}{d} = \frac{y_v}{\cot\left(\frac{\alpha}{2}\right)} = y_v \cdot \tan\left(\frac{\alpha}{2}\right) = \left(-\frac{2s_y}{h} + 1\right)\tan\left(\frac{\alpha}{2}\right)\]

<p>이것이 View위의 z좌표가 1인 점이다.</p>

<h3 id="view-to-ray">View to Ray</h3>

<p>이제 카메라 위치좌표와 방금 구한 \(\mathbf{x'_v , y'_v}\)를 이용하면</p>

\[\mathbf{r_v(t)} = \mathbf{q + tu}\]

<p>를 구할 수 있고, View Matrix 역행렬을 곱하면</p>

\[\mathbf{r_w(t)} = \mathbf{qV^{-1} + tuV^{-1}} = \mathbf{q_w + tu_w}\]

<p>최종적으로 World space의 ray 공식을 얻을 수 있다.</p>

<h2 id="raymesh-intersection">Ray/Mesh Intersection</h2>

<p>이제 ray와 교차하는 오브젝트를 찾아야 한다. 오브젝트의 모든 폴리곤에 대해서 검사를 진행하는 것은 비효율적이기 때문에 전 장에서 배웠던 Bounding과 교차하는 지 검사하는 것이 더 좋은 방법이다.</p>

<h3 id="rayaabb-intersection">Ray/AABB Intersection</h3>

<figure>
  <img src="/assets/images/chap17/AABB_ray_inter.png" />
  <figcaption> Ray/AABB Intersection </figcaption>
</figure>

<p>box에는 각 x,y,z축에 평행한 6개의 평면이 있고 ray가 해당 평면을 지나는 지점을 찾는다. 그렇게 되면 그림과 같이 각각 \(\mathbf{x_min, x_max, y_min, y_max, z_min, z_max}\)를 구할 수 있고,</p>

\[\mathbf{p_enter = max(x_min , y_min, z_min)}\]

\[\mathbf{p_exit = min(x_max , y_max, z_max)}\]

<p>로 진입점과 탈출점을 구할 수 있다. 이때</p>

<p>\(\mathbf{ p_exit &gt; p_enter}\) 이면 선이 box를 교차하는 것으로 볼 수 있다.</p>

<h3 id="raysphere-intersection">Ray/Sphere Intersection</h3>

<figure>
  <img src="/assets/images/chap17/Sphere_ray_inter.png" />
  <figcaption> Ray/AABB Intersection </figcaption>
</figure>

<p>구와 선이 닿는지 판정하는 방법은 이미 고등학교에서 배웠던 개념이라 간단한 설명으로 대체하겠다.
선과 구 중심의 거리가 반지름보다 작으면 교차하는 것인데 선과 구 중심의 거리를 미지수로 놓는 이차방정식을 세우면 판별식을 통해 해가 있는지 알 수가 있다.</p>

<h3 id="raytriangle-intersection">Ray/Triangle Intersection</h3>
<figure>
  <img src="/assets/images/chap17/Triangle_ray_inter.png" />
  <figcaption> Ray/Triangle Intersection </figcaption>
</figure>

<p>삼각형 위의 점은 꼭짓점 $\mathbf{v}_0$을 origin으로, 두 edge를 축으로 하는 <strong>skewed coordinate system</strong>으로 표현한다.</p>

\[\mathbf{p}(u, v) = \mathbf{v}_0 + u(\mathbf{v}_1 - \mathbf{v}_0) + v(\mathbf{v}_2 - \mathbf{v}_0)\]

<hr />

<h3 id="연립방정식-세우기">연립방정식 세우기</h3>

<p>교차점에서 $\mathbf{r}(t) = \mathbf{p}(u, v)$ 이므로:</p>

\[\mathbf{q} + t\mathbf{u} = \mathbf{v}_0 + u(\mathbf{v}_1 - \mathbf{v}_0) + v(\mathbf{v}_2 - \mathbf{v}_0)\]

<p>표기 편의를 위해 다음과 같이 치환한다.</p>

\[\mathbf{e}_1 = \mathbf{v}_1 - \mathbf{v}_0, \quad \mathbf{e}_2 = \mathbf{v}_2 - \mathbf{v}_0, \quad \mathbf{m} = \mathbf{q} - \mathbf{v}_0\]

<p>정리하면</p>

\[-t\mathbf{u} + u\mathbf{e}_1 + v\mathbf{e}_2 = \mathbf{m}\]

<p>이걸 행렬 방정식 $\mathbf{A}\mathbf{x} = \mathbf{b}$ 형태로 쓰면:</p>

\[\begin{bmatrix} | &amp; | &amp; | \\ -\mathbf{u} &amp; \mathbf{e}_1 &amp; \mathbf{e}_2 \\ | &amp; | &amp; | \end{bmatrix} \begin{bmatrix} t \\ u \\ v \end{bmatrix} = \begin{bmatrix} | \\ \mathbf{m} \\ | \end{bmatrix}\]

<hr />
<p>$\mathbf{A}$가 invertible하면 Cramer’s Rule에 의해</p>

\[x_i = \frac{\det \mathbf{A}_i}{\det \mathbf{A}}\]

<p>여기서 $\mathbf{A}_i$는 $\mathbf{A}$의 $i$번째 열을 $\mathbf{b}$로 교체한 행렬이다.</p>

<p>각 미지수를 풀면</p>

\[t = \frac{\det[\mathbf{m},\ \mathbf{e}_1,\ \mathbf{e}_2]}{\det[-\mathbf{u},\ \mathbf{e}_1,\ \mathbf{e}_2]}\]

\[u = \frac{\det[-\mathbf{u},\ \mathbf{m},\ \mathbf{e}_2]}{\det[-\mathbf{u},\ \mathbf{e}_1,\ \mathbf{e}_2]}\]

\[v = \frac{\det[-\mathbf{u},\ \mathbf{e}_1,\ \mathbf{m}]}{\det[-\mathbf{u},\ \mathbf{e}_1,\ \mathbf{e}_2]}\]

<p>3×3 행렬식은 <strong>스칼라 삼중적(scalar triple product)</strong> 으로 계산한다.</p>

\[\det[\mathbf{a},\ \mathbf{b},\ \mathbf{c}] = \mathbf{a} \cdot (\mathbf{b} \times \mathbf{c})\]

<hr />

<p>$t$, $u$, $v$를 구한 뒤 아래 조건을 모두 만족해야 <strong>Hit</strong>이다.</p>

\[t \geq 0\]

\[0 \leq u \leq 1\]

\[0 \leq v \leq 1\]

\[u + v \leq 1\]

<p>$t &lt; 0$이면 교차점이 ray 뒤쪽이고, $u$, $v$ 범위를 벗어나면 삼각형 외부다.</p>

<h2 id="intersection-test의-추가-정보--demo">Intersection Test의 추가 정보 &amp; Demo</h2>

<p>어쨋든 이렇게 Picking을 구현할 수 있다. 다만 한 가지 고려할 점이 있다. GPU는 렌더링 시 각 오브젝트에 World Matrix를 곱해 월드 공간으로 변환하지만, CPU는 그 변환 결과를 직접 알지 못한다. 그렇다면 교차테스트를 CPU에서 하려면 모든 오브젝트를 월드 공간으로 직접 변환해야 할까?</p>

<p>더 나은 방법이 있다. Ray에 World Matrix의 역행렬을 곱하면, Ray가 오브젝트의 
로컬 좌표계로 변환된다. 이 상태에서 교차테스트를 수행하면 수학적으로 
월드 공간에서 테스트하는 것과 동일한 결과를 얻는다.</p>

<p>오브젝트를 월드 공간으로 옮기는 대신 Ray를 로컬 공간으로 내리는 것이다. 
버텍스 수가 많은 메시일수록 이 차이는 커진다.</p>

<figure>
  <img src="/assets/images/chap17/Demo.gif" />
  <figcaption> Demo </figcaption>
</figure>

<p>우클릭으로 폴리곤을 Picking 할 수 있는 모습이다.</p>

<h2 id="연습문제">연습문제</h2>

<ol>
  <li>수 천개의 오브젝트가 씬안에 위치한다고 하면 여전히 ray/bounding 테스트를 수 천번 진행해야 한다. Octree라는 방법을 조사해서 어떻게 효율적으로 테스트를 수행하는지 설명하라.</li>
</ol>

<p>Octree 방법은 3D 공간을 8등분해서 ray가 지나가지 않는 공간은 제외하고 계산하는 방법이다. 만약 A라는 공간을 지나가는데 A안의 오브젝트 개수가 기준보다 많을 시 A를 다시 8개의 공간으로 나눠서 계산한다.</p>

<p>지나지 않는 공간의 오브젝트는 소거하기 때문에 많은 오브젝트가 있을 때 성능을 유지할 수 있다.</p>

<p>이번 장은 수학이 꽤 많아서 좀 힘든 감이 있었다. 그래도 이제 5~6장 밖에 남지 않았다. 1장을 업로드한지 1년이 조금 안된거 같은데 벌써 17장까지 왔다니 참 신기하다 내가 꾸준히 뭔가를 하고 있다니 참… 어쨋든 계속 가보자고~</p>]]></content><author><name>Your Name</name></author><category term="DirectX12" /><summary type="html"><![CDATA[클릭으로 오브젝트를 선택하는 Picking System을 설명합니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://smg-927.github.io/assets/images/chap14/directX12_represent.jpg" /><media:content medium="image" url="https://smg-927.github.io/assets/images/chap14/directX12_represent.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[3D Programming with DirectX12] Chapter 16 : Instancing and Frustum Culling</title><link href="https://smg-927.github.io/2026/05/24/DirectX12_Chap16/" rel="alternate" type="text/html" title="[3D Programming with DirectX12] Chapter 16 : Instancing and Frustum Culling" /><published>2026-05-24T00:00:00+00:00</published><updated>2026-05-24T00:00:00+00:00</updated><id>https://smg-927.github.io/2026/05/24/DirectX12_Chap16</id><content type="html" xml:base="https://smg-927.github.io/2026/05/24/DirectX12_Chap16/"><![CDATA[<p>해당 장에서는 인스턴싱과 절두체 컬링(Frustum Culling)을 알아본다. 전 장에서 공부했던 Dynamic Indexing도 같이 활용한다.</p>

<h2 id="hardware-instancing">Hardware Instancing</h2>

<figure>
  <img src="/assets/images/chap16/Instancing_example.png" />
  <figcaption>게임 안에 스폰된 좀비들(주인장의 습작 게임속 화면)</figcaption>
</figure>

<p>게임 속에 여러 좀비들이 맵에 퍼져있다. 사실 이들은 모두 같은 오브젝트들인데, 좀비를 하나하나 그릴때마다 vertex와 index data를 만들어 사용한다면 엄청난 메모리 낭비가 될 것이다.</p>

<p>만약 하나의 vertex와 index정보를 저장해두고 렌더링 할 때마다 각기 다른 World Matrix와 material들을 넣어준다면 메모리를 아낄 수 있을 것이다.</p>

<p>사실 그럼에도 불구하고 Direct11 스타일의 Per-Object API 호출방식으로 인해 오버헤드가 발생하긴 하는데, 이전 장에서 배웠던 Dynamic Indexing을 사용해서 유연하게 성능을 올려보는게 Hardware Instancing의 목표이다.</p>

<h3 id="instance-data--material-data">Instance Data &amp; Material Data</h3>

<p><span class="code-fold-name">Instance Data &amp; Material Data</span></p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
</pre></td><td class="rouge-code"><pre><span class="k">struct</span> <span class="nc">InstanceData</span>
<span class="p">{</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT4X4</span> <span class="n">World</span> <span class="o">=</span> <span class="n">MathHelper</span><span class="o">::</span><span class="n">Identity4x4</span><span class="p">();</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT4X4</span> <span class="n">TexTransform</span> <span class="o">=</span> <span class="n">MathHelper</span><span class="o">::</span><span class="n">Identity4x4</span><span class="p">();</span>
	<span class="n">UINT</span> <span class="n">MaterialIndex</span><span class="p">;</span>
	<span class="n">UINT</span> <span class="n">InstancePad0</span><span class="p">;</span>
	<span class="n">UINT</span> <span class="n">InstancePad1</span><span class="p">;</span>
	<span class="n">UINT</span> <span class="n">InstancePad2</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="nc">MaterialData</span>
<span class="p">{</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT4</span> <span class="n">DiffuseAlbedo</span> <span class="o">=</span> <span class="p">{</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">1.0</span><span class="n">f</span> <span class="p">};</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT3</span> <span class="n">FresnelR0</span> <span class="o">=</span> <span class="p">{</span> <span class="mf">0.01</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.01</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.01</span><span class="n">f</span> <span class="p">};</span>
	<span class="kt">float</span> <span class="n">Roughness</span> <span class="o">=</span> <span class="mf">64.0</span><span class="n">f</span><span class="p">;</span>

	<span class="c1">// Used in texture mapping.</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT4X4</span> <span class="n">MatTransform</span> <span class="o">=</span> <span class="n">MathHelper</span><span class="o">::</span><span class="n">Identity4x4</span><span class="p">();</span>

	<span class="n">UINT</span> <span class="n">DiffuseMapIndex</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
	<span class="n">UINT</span> <span class="n">MaterialPad0</span><span class="p">;</span>
	<span class="n">UINT</span> <span class="n">MaterialPad1</span><span class="p">;</span>
	<span class="n">UINT</span> <span class="n">MaterialPad2</span><span class="p">;</span>
<span class="p">};</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>해당 구조체 안에는 Vertex 이외에 필요한 정보들이 들어있다. Instance Data에는 오브젝트의 월드행렬, 텍스처 좌표, Material 인덱스가 들어있고, Material Data에는 material 정보가 들어있는데 여기에 이전에 사용했던 Dynamic Indexing이 사용된다.</p>

<figure>
  <img src="/assets/images/chap16/Instancing_sturcture.jpg" />
  <figcaption>Instancing 예시</figcaption>
</figure>

<p>Instancing을 적용하게 되면 불필요한 DrawCall을 줄일 수 있어 성능을 향상시킬 수 있다.</p>

<hr />

<h1 id="frustum-culling절두체-컬링">Frustum Culling(절두체 컬링)</h1>

<figure>
  <img src="/assets/images/chap16/차근차근.jpg" width="50%" />
  <figcaption>차근차근 천천히 가보자</figcaption>
</figure>

<p>Frustum Culling은 카메라 시점에 보이지 않는 물체들을 잘라내는 과정이다. 게임 성능을 위해 이 과정은 필수적인데, 구현 과정 설명을 위한 설명을 위한 설명을 해야하기 때문에 차근차근 가보자.</p>

<h2 id="bounding-volumes">Bounding Volumes</h2>

<p>컴퓨터에서는 물체가 다른 물체랑 부딪히는걸 어떻게 알까? 간단하게 생각해보면 서로 표면이 겹치는 순간 부딪힌 것으로 판단하면 될 것이다.</p>

<figure>
  <img src="/assets/images/chap16/collider.jpg" width="50%" />
  <figcaption>Unity Collider 예시</figcaption>
</figure>

<p>그런데 3D 물체의 표면은 많으면 수만개의 폴리곤으로 이루어져있으므로 이것을 일일히 겹치는지 계산하는것은 말이 안된다. Unity 기능중 Collider를 보면 상대적으로 단순한 Cube, sphere, Cylinder 같은 도형들로만 동적 충돌 처리를 할 수 있는데 이것이 그 이유이다.</p>

<figure>
  <img src="/assets/images/chap16/boxNsphere.png" />
  <figcaption>Box Volume &amp; Sphere Volume</figcaption>
</figure>

<p>복잡한 물체의 충돌처리(표면 계산)을 위해서 간단한 도형으로 근사한 것을 Bounding Volume이라고 한다. 이번 장에서는 Cube와 Sphere 두 가지 Bounding을 알아볼 것이다.</p>

<h3 id="boxes">Boxes</h3>

<p>상자 모양의 volume을 만드는 방법은 크게 두 가지인데, 첫 번째는 AABB(Axis Aligned Bounding Box)이다.</p>

<figure>
  <img src="/assets/images/chap16/AABB.png" width="50%" />
  <figcaption>AABB 설명 사진</figcaption>
</figure>

<p>좌표계 안에 위치한 vertex들의 최대/최소를 구해서 \(\mathbf{V_min} , \mathbf{V_max}\)를 구한다. 이 두 개의 점으로 만든 직육면체가 AABB이다.’</p>
<figure>
  <img src="/assets/images/chap16/AABB&amp;OBB.png" />
  <figcaption>AABB OBB비교</figcaption>
</figure>

<p>사실 AABB는 실제 물체의 크기와 괴리가 있는 경우가 있다. 물체가 한 방향으로 튀어나와있는 경우에 끝의 vertex를 포함하기 때문이다. 그래서 나온 것이 OBB(Oriented Bounding Box)이다.</p>

<p>OBB는 원점 좌표와 extent 좌표, 회전 각을 가지고 있어서 로컬 축과 평행하지 않고 도형에 맞춘 Bounding box를 가질 수 있다.</p>

<h3 id="sphere">Sphere</h3>
<p>구 모양의 volume또한 크게 다르지 않다. 모든 vertex의 평균을 내서 중심점을 구하고, 가장 멀리 떨어진 vertex의 거리를 반지름으로 하는 구를 만들면 끝이다.</p>

<h2 id="frustum절두체">Frustum(절두체)</h2>

<figure>
  <img src="/assets/images/chap16/frustum.png" />
  <figcaption>frustum</figcaption>
</figure>

<p>다음으로는 5장에서 소개했던 Frustum에 대해 알아보자. 간단하게 시야 범위를 나타내는 역할을 한다. 총 6면체로 되어있고 x축을 중심으로 left, right, y축을 중심으로 top, bottom, z축을 중심으로 near, far로 각 면을 부른다. 우리가 원근 투영을 선택한다고 할 때 해당 6면체 안에 위치하지 않은 오브젝트들은 화면에 보이지 않을 것이다.</p>

<h3 id="frustum-intersection">Frustum Intersection</h3>
<p>그렇다면 절두체와 Bounding Volume(앞서 설명한 Box와 Sphere)이 교차하는지 어떻게 알 수 있을까?</p>

<figure>
  <img src="/assets/images/chap16/intersection_sphere.png" width="60%" />
  <figcaption>frustum/sphere Intersection</figcaption>
</figure>

<p>먼저 sphere의 경우 중심으로부터 평면까지의 거리 \(\mathbf{d}\)를 구한 후 \(\mathbf{r}\)과 비교했을 때, \(\mathbf{-d &lt; r &lt; d}\) 이면 해당 평면과 구가 교차하지 않는 것이다. 6개의 면에 대해서 모두 교차하지 않고 안에 위치하면 해당 구가 절두체 안에 있는 것으로 볼 수 있다.</p>

<figure>
  <img src="/assets/images/chap16/BoxIntersection.jpg" width="60%" />
  <figcaption>frustum/box Intersection</figcaption>
</figure>

<p>박스에 경우에는 먼저, 평면의 법선과 가장 평행하면서 물체의 중앙을 지나는 벡터를 정한다. 사진에 경우에는 법선 방향으로 가장 멀리 있는 꼭짓점 \(\mathbf{P}\)와 가장 가까운 꼭짓점 \(\mathbf{Q}\)를 구하면, 선분 \(\overline{PQ}\)가 그 역할을 한다. 이 선분을 기준으로 P가 평면의 양의 방향에 있고 Q가 음의 방향에 있으면 박스가 해당 평면과 교차한다고 판단할 수 있다.</p>

<figure>
  <img src="/assets/images/chap16/frustum_culling.jpg" width="40%" />
  <figcaption>frustum culling</figcaption>
</figure>

<p>이제야 원래의 목적에 도달했다. 이제 frustum과 교차하거나 안에 있는 오브젝트들은 그리되, 밖에 있는 오브젝트들은 과감히 없애는 것을 통해 성능을 향상시킬 수 있고 이것을 frustum culling이라고 한다. 이것은 CPU단에서 일어나고 따라서 InstanceData 메모리를 아낄 수도 있다.</p>

<h2 id="demo-소개">Demo 소개</h2>

<p>이번 장의 연습문제는 실습 대신 간단한 수학 증명문제들로 이루어져있어서 따로 소개하지 않는대신 Demo를 간단하게 소개하며 글을 마치겠다. 여러가지 머터리얼을 사용하는 해골들과 1인칭 카메라이동으로 구성되어있다.</p>

<figure>
  <img src="/assets/images/chap16/demo.gif" width="130%" />
  <figcaption>Demo.gif</figcaption>
</figure>]]></content><author><name>Your Name</name></author><category term="DirectX12" /><summary type="html"><![CDATA[Instancing과 Frustum Culling을 소개합니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://smg-927.github.io/assets/images/chap14/directX12_represent.jpg" /><media:content medium="image" url="https://smg-927.github.io/assets/images/chap14/directX12_represent.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[3D Programming with DirectX12] Chapter 15 : Building a first Person Camera and Dynamic indexing</title><link href="https://smg-927.github.io/2026/05/21/DirectX12_Chap15/" rel="alternate" type="text/html" title="[3D Programming with DirectX12] Chapter 15 : Building a first Person Camera and Dynamic indexing" /><published>2026-05-21T00:00:00+00:00</published><updated>2026-05-21T00:00:00+00:00</updated><id>https://smg-927.github.io/2026/05/21/DirectX12_Chap15</id><content type="html" xml:base="https://smg-927.github.io/2026/05/21/DirectX12_Chap15/"><![CDATA[<p>15장부터는 DX12의 응용을 배운다. 이번 장에서는 FPS게임에서 주로 쓰이는 1인칭 카메라, 그리고 dynamic indexing이라는 개념을 다룬다.</p>

<h2 id="1인칭-카메라">1인칭 카메라</h2>

<figure>
  <img src="/assets/images/chap15/FPS_demo.gif" />
  <figcaption>fps_demo</figcaption>
</figure>

<p>1인칭 카메라를 구현할 때 필요한 정보는 세 가지이다.</p>
<ul>
  <li>월드 좌표계</li>
  <li>카메라 위치</li>
  <li>카메라 좌표계</li>
</ul>

<p>이것을 이용하면 기존의 월드 Matrix를 카메라 시점 View Matrix로 바꿀 수 있다.</p>

<p style="text-align: center;">$$\mathbf{i}, \mathbf{j}, \mathbf{k} = world Coordinate$$</p>
<p style="text-align: center;">$$\mathbf{u}, \mathbf{v}, \mathbf{w} = camera Coordinate$$</p>

\[\mathbf{i} \cdot \mathbf{u} = u_x, \quad \mathbf{i} \cdot \mathbf{v} = v_x, \quad \mathbf{i} \cdot \mathbf{w} = w_x\]

\[\mathbf{j} \cdot \mathbf{u} = u_y, \quad \mathbf{j} \cdot \mathbf{v} = v_y, \quad \mathbf{j} \cdot \mathbf{w} = w_y\]

\[\mathbf{k} \cdot \mathbf{u} = u_z, \quad \mathbf{k} \cdot \mathbf{v} = v_z, \quad \mathbf{k} \cdot \mathbf{w} = w_z\]

\[R = \begin{bmatrix} u_x &amp; v_x &amp; w_x &amp; 0 \\ u_y &amp; v_y &amp; w_y &amp; 0 \\ u_z &amp; v_z &amp; w_z &amp; 0 \\ 0 &amp; 0 &amp; 0 &amp; 1 \end{bmatrix}\]

\[T(-Q) = \begin{bmatrix} 1 &amp; 0 &amp; 0 &amp; 0 \\ 0 &amp; 1 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 1 &amp; 0 \\ -Q_x &amp; -Q_y &amp; -Q_z &amp; 1 \end{bmatrix}\]

<p>여기서 \(Q\)는 카메라의 위치이다. 왜 마이너스를 붙여서 \(-Q_x\)를 사용하냐면 좌표계 자체가 \(Q\)를 중심으로 이동하기 때문이다.</p>

\[V = T(-Q) \cdot R = \begin{bmatrix} u_x &amp; v_x &amp; w_x &amp; 0 \\ u_y &amp; v_y &amp; w_y &amp; 0 \\ u_z &amp; v_z &amp; w_z &amp; 0 \\ -\mathbf{Q} \cdot \mathbf{u} &amp; -\mathbf{Q} \cdot \mathbf{v} &amp; -\mathbf{Q} \cdot \mathbf{w} &amp; 1 \end{bmatrix}\]

<p>따라서 해당 계산을 통해 View Matrix를 수학적으로 구할 수 있다.</p>

<h2 id="camera-class">Camera Class</h2>

<p><span class="code-fold-name">Camera class</span></p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
</pre></td><td class="rouge-code"><pre><span class="k">class</span> <span class="nc">Camera</span>
<span class="p">{</span>
<span class="nl">public:</span>

	<span class="n">Camera</span><span class="p">();</span>
	<span class="o">~</span><span class="n">Camera</span><span class="p">();</span>

	<span class="c1">// Get/Set world camera position.</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMVECTOR</span> <span class="n">GetPosition</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT3</span> <span class="n">GetPosition3f</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="kt">void</span> <span class="n">SetPosition</span><span class="p">(</span><span class="kt">float</span> <span class="n">x</span><span class="p">,</span> <span class="kt">float</span> <span class="n">y</span><span class="p">,</span> <span class="kt">float</span> <span class="n">z</span><span class="p">);</span>
	<span class="kt">void</span> <span class="n">SetPosition</span><span class="p">(</span><span class="k">const</span> <span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT3</span><span class="o">&amp;</span> <span class="n">v</span><span class="p">);</span>
	
	<span class="c1">// Get camera basis vectors.</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMVECTOR</span> <span class="n">GetRight</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT3</span> <span class="n">GetRight3f</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMVECTOR</span> <span class="n">GetUp</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT3</span> <span class="n">GetUp3f</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMVECTOR</span> <span class="n">GetLook</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT3</span> <span class="n">GetLook3f</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>

	<span class="c1">// Get frustum properties.</span>
	<span class="kt">float</span> <span class="n">GetNearZ</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">GetFarZ</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">GetAspect</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">GetFovY</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">GetFovX</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>

	<span class="c1">// Get near and far plane dimensions in view space coordinates.</span>
	<span class="kt">float</span> <span class="n">GetNearWindowWidth</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">GetNearWindowHeight</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">GetFarWindowWidth</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">GetFarWindowHeight</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	
	<span class="c1">// Set frustum.</span>
	<span class="kt">void</span> <span class="n">SetLens</span><span class="p">(</span><span class="kt">float</span> <span class="n">fovY</span><span class="p">,</span> <span class="kt">float</span> <span class="n">aspect</span><span class="p">,</span> <span class="kt">float</span> <span class="n">zn</span><span class="p">,</span> <span class="kt">float</span> <span class="n">zf</span><span class="p">);</span>

	<span class="c1">// Define camera space via LookAt parameters.</span>
	<span class="kt">void</span> <span class="n">LookAt</span><span class="p">(</span><span class="n">DirectX</span><span class="o">::</span><span class="n">FXMVECTOR</span> <span class="n">pos</span><span class="p">,</span> <span class="n">DirectX</span><span class="o">::</span><span class="n">FXMVECTOR</span> <span class="n">target</span><span class="p">,</span> <span class="n">DirectX</span><span class="o">::</span><span class="n">FXMVECTOR</span> <span class="n">worldUp</span><span class="p">);</span>
	<span class="kt">void</span> <span class="n">LookAt</span><span class="p">(</span><span class="k">const</span> <span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT3</span><span class="o">&amp;</span> <span class="n">pos</span><span class="p">,</span> <span class="k">const</span> <span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT3</span><span class="o">&amp;</span> <span class="n">target</span><span class="p">,</span> <span class="k">const</span> <span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT3</span><span class="o">&amp;</span> <span class="n">up</span><span class="p">);</span>

	<span class="c1">// Get View/Proj matrices.</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMMATRIX</span> <span class="n">GetView</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMMATRIX</span> <span class="n">GetProj</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>

	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT4X4</span> <span class="n">GetView4x4f</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT4X4</span> <span class="n">GetProj4x4f</span><span class="p">()</span><span class="k">const</span><span class="p">;</span>

	<span class="c1">// Strafe/Walk the camera a distance d.</span>
	<span class="kt">void</span> <span class="n">Strafe</span><span class="p">(</span><span class="kt">float</span> <span class="n">d</span><span class="p">);</span>
	<span class="kt">void</span> <span class="n">Walk</span><span class="p">(</span><span class="kt">float</span> <span class="n">d</span><span class="p">);</span>
	<span class="kt">void</span> <span class="n">Rotate</span><span class="p">(</span><span class="kt">float</span> <span class="n">angle</span><span class="p">);</span>

	<span class="c1">// Rotate the camera.</span>
	<span class="kt">void</span> <span class="n">Pitch</span><span class="p">(</span><span class="kt">float</span> <span class="n">angle</span><span class="p">);</span>
	<span class="kt">void</span> <span class="n">RotateY</span><span class="p">(</span><span class="kt">float</span> <span class="n">angle</span><span class="p">);</span>

	<span class="c1">// After modifying camera position/orientation, call to rebuild the view matrix.</span>
	<span class="kt">void</span> <span class="n">UpdateViewMatrix</span><span class="p">();</span>

<span class="nl">private:</span>

	<span class="c1">// Camera coordinate system with coordinates relative to world space.</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT3</span> <span class="n">mPosition</span> <span class="o">=</span> <span class="p">{</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span> <span class="p">};</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT3</span> <span class="n">mRight</span> <span class="o">=</span> <span class="p">{</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span> <span class="p">};</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT3</span> <span class="n">mUp</span> <span class="o">=</span> <span class="p">{</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span> <span class="p">};</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT3</span> <span class="n">mLook</span> <span class="o">=</span> <span class="p">{</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">1.0</span><span class="n">f</span> <span class="p">};</span>

	<span class="c1">// Cache frustum properties.</span>
	<span class="kt">float</span> <span class="n">mNearZ</span> <span class="o">=</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">mFarZ</span> <span class="o">=</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">mAspect</span> <span class="o">=</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">mFovY</span> <span class="o">=</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">mNearWindowHeight</span> <span class="o">=</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">;</span>
	<span class="kt">float</span> <span class="n">mFarWindowHeight</span> <span class="o">=</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">;</span>

	<span class="kt">bool</span> <span class="n">mViewDirty</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>

	<span class="c1">// Cache View/Proj matrices.</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT4X4</span> <span class="n">mView</span> <span class="o">=</span> <span class="n">MathHelper</span><span class="o">::</span><span class="n">Identity4x4</span><span class="p">();</span>
	<span class="n">DirectX</span><span class="o">::</span><span class="n">XMFLOAT4X4</span> <span class="n">mProj</span> <span class="o">=</span> <span class="n">MathHelper</span><span class="o">::</span><span class="n">Identity4x4</span><span class="p">();</span>
<span class="p">};</span>

<span class="cp">#endif // CAMERA_H
</span></pre></td></tr></tbody></table></code></pre></div></div>

<p>기존에는 카메라에 대한 정보들을 Constant 버퍼에 하나하나 담아서 보냈다. 이제는 Camera Class를 따로 두어서 관리한다. 오브젝트의 모든 정보를 담아두는 Render Item과 같은 맥락이다.</p>

<p><span class="code-fold-name">전/후 비교</span></p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
</pre></td><td class="rouge-code"><pre><span class="c1">// 적용 전</span>
	<span class="c1">// Convert Spherical to Cartesian coordinates.</span>
	<span class="n">mEyePos</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="n">mRadius</span> <span class="o">*</span> <span class="nf">sinf</span><span class="p">(</span><span class="n">mPhi</span><span class="p">)</span> <span class="o">*</span> <span class="n">cosf</span><span class="p">(</span><span class="n">mTheta</span><span class="p">);</span>
	<span class="n">mEyePos</span><span class="p">.</span><span class="n">z</span> <span class="o">=</span> <span class="n">mRadius</span> <span class="o">*</span> <span class="nf">sinf</span><span class="p">(</span><span class="n">mPhi</span><span class="p">)</span> <span class="o">*</span> <span class="n">sinf</span><span class="p">(</span><span class="n">mTheta</span><span class="p">);</span>
	<span class="n">mEyePos</span><span class="p">.</span><span class="n">y</span> <span class="o">=</span> <span class="n">mRadius</span> <span class="o">*</span> <span class="nf">cosf</span><span class="p">(</span><span class="n">mPhi</span><span class="p">);</span>

	<span class="c1">// Build the view matrix.</span>
	<span class="n">XMVECTOR</span> <span class="n">pos</span> <span class="o">=</span> <span class="n">XMVectorSet</span><span class="p">(</span><span class="n">mEyePos</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">mEyePos</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="n">mEyePos</span><span class="p">.</span><span class="n">z</span><span class="p">,</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">);</span>
	<span class="n">XMVECTOR</span> <span class="n">target</span> <span class="o">=</span> <span class="n">XMVectorZero</span><span class="p">();</span>
	<span class="n">XMVECTOR</span> <span class="n">up</span> <span class="o">=</span> <span class="n">XMVectorSet</span><span class="p">(</span><span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">);</span>

	<span class="n">XMMATRIX</span> <span class="n">view</span> <span class="o">=</span> <span class="n">XMMatrixLookAtLH</span><span class="p">(</span><span class="n">pos</span><span class="p">,</span> <span class="n">target</span><span class="p">,</span> <span class="n">up</span><span class="p">);</span>
	<span class="n">XMStoreFloat4x4</span><span class="p">(</span><span class="o">&amp;</span><span class="n">mView</span><span class="p">,</span> <span class="n">view</span><span class="p">);</span>


<span class="c1">// 적용 후</span>
	<span class="k">const</span> <span class="kt">float</span> <span class="n">dt</span> <span class="o">=</span> <span class="n">gt</span><span class="p">.</span><span class="n">DeltaTime</span><span class="p">();</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">GetAsyncKeyState</span><span class="p">(</span><span class="sc">'W'</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0x8000</span><span class="p">)</span>
		<span class="n">mCamera</span><span class="p">.</span><span class="n">Walk</span><span class="p">(</span><span class="mf">10.0</span><span class="n">f</span> <span class="o">*</span> <span class="n">dt</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">GetAsyncKeyState</span><span class="p">(</span><span class="sc">'S'</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0x8000</span><span class="p">)</span>
		<span class="n">mCamera</span><span class="p">.</span><span class="n">Walk</span><span class="p">(</span><span class="o">-</span><span class="mf">10.0</span><span class="n">f</span> <span class="o">*</span> <span class="n">dt</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">GetAsyncKeyState</span><span class="p">(</span><span class="sc">'A'</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0x8000</span><span class="p">)</span>
		<span class="n">mCamera</span><span class="p">.</span><span class="n">Strafe</span><span class="p">(</span><span class="o">-</span><span class="mf">10.0</span><span class="n">f</span> <span class="o">*</span> <span class="n">dt</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">GetAsyncKeyState</span><span class="p">(</span><span class="sc">'D'</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0x8000</span><span class="p">)</span>
		<span class="n">mCamera</span><span class="p">.</span><span class="n">Strafe</span><span class="p">(</span><span class="mf">10.0</span><span class="n">f</span> <span class="o">*</span> <span class="n">dt</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">GetAsyncKeyState</span><span class="p">(</span><span class="sc">'Q'</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0x8000</span><span class="p">)</span>
		<span class="n">mCamera</span><span class="p">.</span><span class="n">Rotate</span><span class="p">(</span><span class="mf">5.0</span><span class="n">f</span> <span class="o">*</span> <span class="n">dt</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">GetAsyncKeyState</span><span class="p">(</span><span class="sc">'E'</span><span class="p">)</span> <span class="o">&amp;</span> <span class="mh">0x8000</span><span class="p">)</span>
		<span class="n">mCamera</span><span class="p">.</span><span class="n">Rotate</span><span class="p">(</span><span class="o">-</span><span class="mf">5.0</span><span class="n">f</span> <span class="o">*</span> <span class="n">dt</span><span class="p">);</span>

	<span class="n">mCamera</span><span class="p">.</span><span class="n">UpdateViewMatrix</span><span class="p">();</span>

  <span class="n">XMMATRIX</span> <span class="n">view</span> <span class="o">=</span> <span class="n">mCamera</span><span class="p">.</span><span class="n">GetView</span><span class="p">();</span>
	<span class="n">XMMATRIX</span> <span class="n">proj</span> <span class="o">=</span> <span class="n">mCamera</span><span class="p">.</span><span class="n">GetProj</span><span class="p">();</span>

	<span class="n">XMMATRIX</span> <span class="n">viewProj</span> <span class="o">=</span> <span class="n">XMMatrixMultiply</span><span class="p">(</span><span class="n">view</span><span class="p">,</span> <span class="n">proj</span><span class="p">);</span>
	<span class="n">XMVECTOR</span> <span class="n">det</span> <span class="o">=</span> <span class="n">XMMatrixDeterminant</span><span class="p">(</span><span class="n">view</span><span class="p">);</span>
  <span class="p">.</span>
  <span class="p">.</span>
  <span class="p">.</span>

</pre></td></tr></tbody></table></code></pre></div></div>
<h2 id="dynamic-indexing">Dynamic Indexing</h2>
<p>해당 개념은 상대적으로 뚜렷하다. 우리는 동적 인덱스를 셰이더 프로그램의 리소스 배열 안에 넣을 것이다. 해당 인덱스는 다양한 방법으로 사용 가능하다.</p>
<ol>
  <li>인덱스는 Constant Buffer의 요소가 될 수 있다.</li>
  <li>인덱스는 System ID가 될 수 있다.(Ex. SV_PrimitiveID, SV_InstanceID ..)</li>
  <li>인덱스는 계산 결과가 될 수 있다.</li>
  <li>인덱스는 텍스처로부터 올 수도 있다.</li>
  <li>인덱스는 Vertex 구조의 요소로부터 올 수도 있다.</li>
</ol>

<p>해당 데모에서는 Material Buffer에 Dynamic Indexing을 적용하는데, 간단하게 작동 과정을 설명해보겠다.</p>

<div class="figure-row">
<figure>
  <img src="/assets/images/chap15/di_1.jpg" alt="중간보스1 호박 농사" />
  <figcaption>기존 방식</figcaption>
</figure>
<figure>
  <img src="/assets/images/chap15/di_2.jpg" alt="중간보스2 드론소환술" />
  <figcaption>dynamic indexing 적용 후</figcaption>
</figure>
</div>

<p>기존 방식은 셰이더에 매 드로우콜마다 머터리얼을 바인딩하고 셰이더에 보냈지만 dynamic indexing을 적용하면 머터리얼 배열을 셰이더에 한번에 보내놓고 드로우콜마다 인덱스만 전달하면 되기 때문에 드로우콜 오버헤드를 감소시킬 수 있게 된다.</p>

<h2 id="연습-문제">연습 문제</h2>
<h4 id="3-다섯-개의-box를-5개의-머터리얼을-사용해서-그려라-하지만-단일-매쉬를-이용해야-하고-texture-index를-vertex마다-부여해서-구현해야-한다">3.	다섯 개의 box를 5개의 머터리얼을 사용해서 그려라. 하지만 단일 매쉬를 이용해야 하고 texture index를 vertex마다 부여해서 구현해야 한다.</h4>

<p>기존 방식대로 하면 5개의 box를 그리기 위햇 5번의 드로우콜을 거쳐야 하지만 문제에서는 Dynamic Indexing을 이용해서 한번의 드로우콜로 그리는 것을 요구하고 있다.</p>

<p><span class="code-fold-name">BuildShapeGeometry</span></p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
</pre></td><td class="rouge-code"><pre><span class="kt">void</span> <span class="n">CameraAndDynamicIndexingApp</span><span class="o">::</span><span class="n">BuildShapeGeometry</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">GeometryGenerator</span> <span class="n">geoGen</span><span class="p">;</span>
	<span class="n">GeometryGenerator</span><span class="o">::</span><span class="n">MeshData</span> <span class="n">box</span> <span class="o">=</span> <span class="n">geoGen</span><span class="p">.</span><span class="n">CreateBox</span><span class="p">(</span><span class="mf">1.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>

	<span class="c1">// 박스 5개의 위치 정의</span>
	<span class="n">XMFLOAT3</span> <span class="n">boxPositions</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
		<span class="n">XMFLOAT3</span><span class="p">(</span><span class="o">-</span><span class="mf">8.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">),</span>
		<span class="n">XMFLOAT3</span><span class="p">(</span><span class="o">-</span><span class="mf">4.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">),</span>
		<span class="n">XMFLOAT3</span><span class="p">(</span><span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">),</span>
		<span class="n">XMFLOAT3</span><span class="p">(</span><span class="mf">4.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">),</span>
		<span class="n">XMFLOAT3</span><span class="p">(</span><span class="mf">8.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">),</span>
	<span class="p">};</span>

	<span class="n">UINT</span> <span class="n">singleBoxVertexCount</span> <span class="o">=</span> <span class="p">(</span><span class="n">UINT</span><span class="p">)</span><span class="n">box</span><span class="p">.</span><span class="n">Vertices</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
	<span class="n">UINT</span> <span class="n">singleBoxIndexCount</span> <span class="o">=</span> <span class="p">(</span><span class="n">UINT</span><span class="p">)</span><span class="n">box</span><span class="p">.</span><span class="n">Indices32</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>

	<span class="n">UINT</span> <span class="n">totalVertexCount</span> <span class="o">=</span> <span class="n">singleBoxVertexCount</span> <span class="o">*</span> <span class="mi">5</span><span class="p">;</span>
	<span class="n">UINT</span> <span class="n">totalIndexCount</span> <span class="o">=</span> <span class="n">singleBoxIndexCount</span> <span class="o">*</span> <span class="mi">5</span><span class="p">;</span>

	<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">CustomVertex</span><span class="o">&gt;</span> <span class="n">vertices</span><span class="p">(</span><span class="n">totalVertexCount</span><span class="p">);</span>
	<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="kt">uint16_t</span><span class="o">&gt;</span> <span class="n">indices</span><span class="p">;</span>

	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">b</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">b</span> <span class="o">&lt;</span> <span class="mi">5</span><span class="p">;</span> <span class="n">b</span><span class="o">++</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">UINT</span> <span class="n">vertexOffset</span> <span class="o">=</span> <span class="n">b</span> <span class="o">*</span> <span class="n">singleBoxVertexCount</span><span class="p">;</span>

		<span class="k">for</span> <span class="p">(</span><span class="n">UINT</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">singleBoxVertexCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">UINT</span> <span class="n">k</span> <span class="o">=</span> <span class="n">vertexOffset</span> <span class="o">+</span> <span class="n">i</span><span class="p">;</span>
			<span class="n">vertices</span><span class="p">[</span><span class="n">k</span><span class="p">].</span><span class="n">Position</span> <span class="o">=</span> <span class="n">XMFLOAT3</span><span class="p">(</span>
				<span class="n">box</span><span class="p">.</span><span class="n">Vertices</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">Position</span><span class="p">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">boxPositions</span><span class="p">[</span><span class="n">b</span><span class="p">].</span><span class="n">x</span><span class="p">,</span>
				<span class="n">box</span><span class="p">.</span><span class="n">Vertices</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">Position</span><span class="p">.</span><span class="n">y</span> <span class="o">+</span> <span class="n">boxPositions</span><span class="p">[</span><span class="n">b</span><span class="p">].</span><span class="n">y</span><span class="p">,</span>
				<span class="n">box</span><span class="p">.</span><span class="n">Vertices</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">Position</span><span class="p">.</span><span class="n">z</span> <span class="o">+</span> <span class="n">boxPositions</span><span class="p">[</span><span class="n">b</span><span class="p">].</span><span class="n">z</span>
			<span class="p">);</span>
			<span class="n">vertices</span><span class="p">[</span><span class="n">k</span><span class="p">].</span><span class="n">Normal</span> <span class="o">=</span> <span class="n">box</span><span class="p">.</span><span class="n">Vertices</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">Normal</span><span class="p">;</span>
			<span class="n">vertices</span><span class="p">[</span><span class="n">k</span><span class="p">].</span><span class="n">TangentU</span> <span class="o">=</span> <span class="n">box</span><span class="p">.</span><span class="n">Vertices</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">TangentU</span><span class="p">;</span>
			<span class="n">vertices</span><span class="p">[</span><span class="n">k</span><span class="p">].</span><span class="n">TexC</span> <span class="o">=</span> <span class="n">box</span><span class="p">.</span><span class="n">Vertices</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">TexC</span><span class="p">;</span>
			<span class="n">vertices</span><span class="p">[</span><span class="n">k</span><span class="p">].</span><span class="n">MatIndex</span> <span class="o">=</span> <span class="n">b</span><span class="p">;</span>
		<span class="p">}</span>

		<span class="k">for</span> <span class="p">(</span><span class="n">UINT</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">singleBoxIndexCount</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">indices</span><span class="p">.</span><span class="n">push_back</span><span class="p">((</span><span class="n">std</span><span class="o">::</span><span class="kt">uint16_t</span><span class="p">)(</span><span class="n">box</span><span class="p">.</span><span class="n">Indices32</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">+</span> <span class="n">vertexOffset</span><span class="p">));</span>
		<span class="p">}</span>
	<span class="p">}</span>

	<span class="c1">// Submesh 하나로 전체 통합</span>
	<span class="n">SubmeshGeometry</span> <span class="n">submesh</span><span class="p">;</span>
	<span class="n">submesh</span><span class="p">.</span><span class="n">IndexCount</span> <span class="o">=</span> <span class="n">totalIndexCount</span><span class="p">;</span>
	<span class="n">submesh</span><span class="p">.</span><span class="n">StartIndexLocation</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
	<span class="n">submesh</span><span class="p">.</span><span class="n">BaseVertexLocation</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

	<span class="k">const</span> <span class="n">UINT</span> <span class="n">vbByteSize</span> <span class="o">=</span> <span class="n">totalVertexCount</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">CustomVertex</span><span class="p">);</span>
	<span class="k">const</span> <span class="n">UINT</span> <span class="n">ibByteSize</span> <span class="o">=</span> <span class="n">totalIndexCount</span> <span class="o">*</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="kt">uint16_t</span><span class="p">);</span>

	<span class="k">auto</span> <span class="n">geo</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">make_unique</span><span class="o">&lt;</span><span class="n">MeshGeometry</span><span class="o">&gt;</span><span class="p">();</span>
	<span class="n">geo</span><span class="o">-&gt;</span><span class="n">Name</span> <span class="o">=</span> <span class="s">"shapeGeo"</span><span class="p">;</span>

	<span class="n">ThrowIfFailed</span><span class="p">(</span><span class="n">D3DCreateBlob</span><span class="p">(</span><span class="n">vbByteSize</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">geo</span><span class="o">-&gt;</span><span class="n">VertexBufferCPU</span><span class="p">));</span>
	<span class="n">CopyMemory</span><span class="p">(</span><span class="n">geo</span><span class="o">-&gt;</span><span class="n">VertexBufferCPU</span><span class="o">-&gt;</span><span class="n">GetBufferPointer</span><span class="p">(),</span> <span class="n">vertices</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="n">vbByteSize</span><span class="p">);</span>

	<span class="n">ThrowIfFailed</span><span class="p">(</span><span class="n">D3DCreateBlob</span><span class="p">(</span><span class="n">ibByteSize</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">geo</span><span class="o">-&gt;</span><span class="n">IndexBufferCPU</span><span class="p">));</span>
	<span class="n">CopyMemory</span><span class="p">(</span><span class="n">geo</span><span class="o">-&gt;</span><span class="n">IndexBufferCPU</span><span class="o">-&gt;</span><span class="n">GetBufferPointer</span><span class="p">(),</span> <span class="n">indices</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="n">ibByteSize</span><span class="p">);</span>

	<span class="n">geo</span><span class="o">-&gt;</span><span class="n">VertexBufferGPU</span> <span class="o">=</span> <span class="n">d3dUtil</span><span class="o">::</span><span class="n">CreateDefaultBuffer</span><span class="p">(</span><span class="n">md3dDevice</span><span class="p">.</span><span class="n">Get</span><span class="p">(),</span>
		<span class="n">mCommandList</span><span class="p">.</span><span class="n">Get</span><span class="p">(),</span> <span class="n">vertices</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="n">vbByteSize</span><span class="p">,</span> <span class="n">geo</span><span class="o">-&gt;</span><span class="n">VertexBufferUploader</span><span class="p">);</span>

	<span class="n">geo</span><span class="o">-&gt;</span><span class="n">IndexBufferGPU</span> <span class="o">=</span> <span class="n">d3dUtil</span><span class="o">::</span><span class="n">CreateDefaultBuffer</span><span class="p">(</span><span class="n">md3dDevice</span><span class="p">.</span><span class="n">Get</span><span class="p">(),</span>
		<span class="n">mCommandList</span><span class="p">.</span><span class="n">Get</span><span class="p">(),</span> <span class="n">indices</span><span class="p">.</span><span class="n">data</span><span class="p">(),</span> <span class="n">ibByteSize</span><span class="p">,</span> <span class="n">geo</span><span class="o">-&gt;</span><span class="n">IndexBufferUploader</span><span class="p">);</span>

	<span class="n">geo</span><span class="o">-&gt;</span><span class="n">VertexByteStride</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">CustomVertex</span><span class="p">);</span>
	<span class="n">geo</span><span class="o">-&gt;</span><span class="n">VertexBufferByteSize</span> <span class="o">=</span> <span class="n">vbByteSize</span><span class="p">;</span>
	<span class="n">geo</span><span class="o">-&gt;</span><span class="n">IndexFormat</span> <span class="o">=</span> <span class="n">DXGI_FORMAT_R16_UINT</span><span class="p">;</span>
	<span class="n">geo</span><span class="o">-&gt;</span><span class="n">IndexBufferByteSize</span> <span class="o">=</span> <span class="n">ibByteSize</span><span class="p">;</span>

	<span class="n">geo</span><span class="o">-&gt;</span><span class="n">DrawArgs</span><span class="p">[</span><span class="s">"box"</span><span class="p">]</span> <span class="o">=</span> <span class="n">submesh</span><span class="p">;</span>

	<span class="n">mGeometries</span><span class="p">[</span><span class="n">geo</span><span class="o">-&gt;</span><span class="n">Name</span><span class="p">]</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">move</span><span class="p">(</span><span class="n">geo</span><span class="p">);</span>
<span class="p">}</span>

</pre></td></tr></tbody></table></code></pre></div></div>

<p>핵심은 Geometry에서 박스 5개를 하나의 랜더 아이템에 넣어놓고, 새로운 Vertex Struct를 만들어서 Texture ID를 넣는 것이다. 그렇게 되면 각 vertex마다 셰이더에서 해당하는 텍스쳐를 확인하고 넣을 수 가 있게된다.</p>

<figure>
  <img src="/assets/images/chap15/Prob3_answer.png" />
  <figcaption>3번 문제 결과</figcaption>
</figure>]]></content><author><name>Your Name</name></author><category term="DirectX12" /><summary type="html"><![CDATA[1인칭 카메라 제어와 dynamic indexing을 소개합니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://smg-927.github.io/assets/images/chap14/directX12_represent.jpg" /><media:content medium="image" url="https://smg-927.github.io/assets/images/chap14/directX12_represent.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[농부는 대체되었다] 리뷰 (3.5/5)</title><link href="https://smg-927.github.io/2026/05/10/Farmer_is_replaced_review-copy/" rel="alternate" type="text/html" title="[농부는 대체되었다] 리뷰 (3.5/5)" /><published>2026-05-10T00:00:00+00:00</published><updated>2026-05-10T00:00:00+00:00</updated><id>https://smg-927.github.io/2026/05/10/Farmer_is_replaced_review%20copy</id><content type="html" xml:base="https://smg-927.github.io/2026/05/10/Farmer_is_replaced_review-copy/"><![CDATA[<p>농부는 대체 되었다 게임은 기본적으로 프로그래밍을 찍먹해본 사람들에게 추천할만 하다. 기초가 없더라도 친절한 튜토리얼과 함께 한다면 차근차근 해볼 수 있겠지만, 게임 중반부터 등장하는 호박, 선인장, 미로같은 컨텐츠들을 클리어하려면 여러 알고리즘 지식이 필요하기 때문에 진입장벽이 분명히 존재한다.</p>

<figure>
  <img src="/assets/images/Review/Farmer_was_Replaced/bitburner.jpg" alt="bitburner 게임 화면" />
  <figcaption>bitburner 게임 화면</figcaption>
</figure>

<p>이전에도 코딩을 이용한 게임인 bitburner을 플레이 해본적이 있다. 자바스크립트 기반의 해킹으로 돈을 버는 게임인데 텍스트 기반이다 보니 이걸 할 바엔 진짜 코딩을 하는게 더 재밌겠다라는 생각이 들어 그만 뒀다. 튜토리얼도 훠얼씬 복잡하다…</p>

<p>농부는 대체되었다도 마찬가지로 코딩을 해야하긴 하지만 분명한 시각적 보상이 존재한다. 드론이 내가 생각한 그대로 착착 움직이며 수확하는 모습을 보면 뿌듯함이 몰려오는데 이것이 이 게임의 묘미라고 볼 수 있겠다.</p>

<div class="figure-row">
<figure>
  <img src="/assets/images/Review/Farmer_was_Replaced/Pumpkin.png" alt="중간보스1 호박 농사" />
  <figcaption>중간보스1 호박 농사</figcaption>
</figure>
<figure>
  <img src="/assets/images/Review/Farmer_was_Replaced/multithread.png" alt="중간보스2 드론소환술" />
  <figcaption>중간보스2 드론소환술</figcaption>
</figure>
</div>

<p>추가 컨텐츠는 제외하고 내 생각에 이 게임에는 두 개의 그만두고싶어지는 순간이 온다.</p>

<p>첫 번째는 호박 농사. 호박은 이웃 호박들과 합쳐져서 큰 정사각형 모양이 될 수 있는데 큰 호박을 수확할 수록 효율이 높아진다. 문제는 확률적으로 썩은 호박이 생겨서 솎아내는 작업이 필요해진다.</p>

<p>두 번째는 드론소환술이다. 처음에는 드론을 한대만 사용할 수 있지만 나중에는 드론을 최대 32대까지 사용할 수 있다. 문제는 32대를 따로 쓰는게 아니고 메인 드론 한대가 그림자 분신술을 쓰듯이 사용해야 한다는 점이다.</p>

<p>드론소환술을 사용하기 위해 멀티쓰레딩에 대한 개념을 공부했는데 아직도 완벽히 쓰지는 못하는 거 같다 ㅎㅎ.</p>

<figure>
  <img src="/assets/images/Review/Farmer_was_Replaced/reset.png" alt="결국 마지막 과제는 나를 대체하기" />
  <figcaption>결국 마지막 과제는 나를 대체하기</figcaption>
</figure>

<p>어쨋든 14시간정도 플레이 후에 이 게임의 모든 단계를 해금했다. 그러자 위 사진이 떳는데 마지막 과제는 내가 지금까지 했던 모든 행동들을 코드로 자동화하는 것이었다. 게임 이름이 농부는 대체되었다인데 결국은 나까지 대체해버리는게 최종 과제라는 점이 좋은 결말이라는 생각이 들었다.</p>

<h2 id="게임을-마치며">게임을 마치며</h2>
<p>아무래도 나는 이런 류의 게임이 재밌나보다. (역시 난 비주류야 킥킥) 꾸준한 시간을 들여서 캐릭터를 키워나가는 RPG류의 게임들은 결말이 어느정도 정해져 있다보니 그걸 위해 많은 시간을 박는 노력을 들이고 싶지가 않다. 반면에 이런 게임들은 내가 생각을 해내거나 코드를 짜는데 성공하기만 하면 바로 클리어가 가능하기 때문에 재미를 느낀다.</p>

<figure>
  <img src="/assets/images/Review/Farmer_was_Replaced/miner_dlc.png" alt="두근두근 dlc소식" />
  <figcaption>두근두근 dlc소식</figcaption>
</figure>

<p>추후에 땅을 채굴하는 ‘광부는 대체되었다’ DLC도 나온다고 하는데 이것도 꼭 플레이해봐야겠다.</p>]]></content><author><name>Your Name</name></author><category term="Game Review" /><summary type="html"><![CDATA[코딩에 재미를 붙일 사람들을 위한 최고의 게임]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://smg-927.github.io/assets/images/Review/Farmer_was_Replaced/main.jpg" /><media:content medium="image" url="https://smg-927.github.io/assets/images/Review/Farmer_was_Replaced/main.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[3D Programming with DirectX12] Chapter 14 : The Tessellation Stage</title><link href="https://smg-927.github.io/2026/05/06/DirectX12_Chap14/" rel="alternate" type="text/html" title="[3D Programming with DirectX12] Chapter 14 : The Tessellation Stage" /><published>2026-05-06T00:00:00+00:00</published><updated>2026-05-06T00:00:00+00:00</updated><id>https://smg-927.github.io/2026/05/06/DirectX12_Chap14</id><content type="html" xml:base="https://smg-927.github.io/2026/05/06/DirectX12_Chap14/"><![CDATA[<h2 id="테셀레이션-스테이지">테셀레이션 스테이지</h2>

<figure>
  <img src="/assets/images/chap14/DirectX12_Chap14(2).png" alt="테셀레이션 이미지" />
  <figcaption>테셀레이션 이미지</figcaption>
</figure>

<p>테셀레이션은 원래 평면도형을 빈틈없이 붙이는 방식을 말한다. 그래픽스에서의 테셀레이션 스테이지는 버택스 셰이더에서 만들어진 삼각형을 더 작은 삼각형으로 쪼개는 과정이다.</p>

<p>테셀레이션 스테이지는 버택스 셰이더와 픽셀셰이더 사이에 있는데 이곳에서 정점을 추가하고 추가적인 계산이 가능하다. 물론 이것 역시 필수적인 단계는 아니다.</p>

<p><strong>그냥 처음부터 버택스를 많이 만들면 되는 문제 아닌가?</strong></p>

<p>맞는 말이지만 이렇게 따로 만든 데에는 세 가지 이유가 있다.</p>
<ul>
  <li>LOD(Level Of Detail)의 적용에 용이함.</li>
  <li>물리, 애니메이션에 효율성이 높아짐</li>
  <li>메모리를 아낄 수 있음.</li>
</ul>

<p>— (LOD는 멀리 있는 물체의 폴리곤을 줄여서 성능을 높이는 기술이다.) —</p>

<figure>
  <img src="/assets/images/chap14/DirectX12_Chap14(1).png" alt="테셀레이션 스테이지 단계 설명" />
  <figcaption>테셀레이션 스테이지 단계 설명</figcaption>
</figure>

<p>원래 알고있던 VS(버택스 셰이더)와 PS(프래그먼트 셰이더)사이에 처음보는 세단계가 있다. 차근차근 과정을 알아보자.</p>

<h2 id="헐-셰이더-hull-shader">헐 셰이더 (Hull Shader)</h2>

<p>헐 셰이더에서는 VS에서 전달받은 도형을 어떻게 나눌 것인지 정의한다. VS에서는 각 정점(버택스)라고 불리는 것을 해당 스테이지에서는 Control Point로 바꿔 부른다. 그 이유는 정점의 위치를 변화시키거나 개수를 늘리기 위한 스테이지이기 때문이라고 짐작해본다.</p>

<p>헐 셰이더는 두 가지 단계로 구분된다.</p>
<h3 id="1-constant-hull-shader">1. Constant Hull Shader</h3>
<p><span class="code-fold-name">PatchTess / ConstantHS</span></p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="k">struct</span>	<span class="nc">PatchTess</span>
<span class="p">{</span>
  <span class="kt">float</span>	<span class="n">EdgeTess</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">:</span> <span class="n">SV_TessFactor</span><span class="p">;</span> <span class="c1">// 모서리 divide 파라미터</span>
  <span class="kt">float</span>	<span class="n">InsideTess</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">:</span> <span class="n">SV_InsideTessFactor</span><span class="p">;</span> <span class="c1">// 내부 divide 파라미터</span>
<span class="p">};</span>

<span class="n">PatchTess</span> <span class="n">ConstantHS</span><span class="p">(</span><span class="n">InputPatch</span><span class="o">&lt;</span><span class="n">VertexOut</span><span class="p">,</span> <span class="mi">4</span><span class="o">&gt;</span> <span class="n">patch</span><span class="p">,</span>
<span class="n">uint</span> <span class="n">patchID</span> <span class="o">:</span> <span class="n">SV_PrimitiveID</span><span class="p">)</span>
<span class="p">{</span>
  <span class="n">PatchTess</span> <span class="n">pt</span><span class="p">;</span>

  <span class="n">pt</span><span class="p">.</span><span class="n">EdgeTess</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
  <span class="n">pt</span><span class="p">.</span><span class="n">EdgeTess</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
  <span class="n">pt</span><span class="p">.</span><span class="n">EdgeTess</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
  <span class="n">pt</span><span class="p">.</span><span class="n">EdgeTess</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
  <span class="n">pt</span><span class="p">.</span><span class="n">InsideTess</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
  <span class="n">pt</span><span class="p">.</span><span class="n">InsideTess</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">3</span><span class="p">;</span>
  <span class="k">return</span> <span class="n">pt</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>Patch : 상수 헐 셰이더에 한번에 들어오는 점의 집합</p>

<p>Tessellation factor : 도형을 어떻게 나눌 것인지 정하는 파라미터</p>

<p>상수 헐 셰이더에서는 들어온 patch를 이용해서 Tessellation factor를 정하고 내보내는 역할을 한다. PatchTess에 보면 4개의 인덱스를 가진 Edgetess, 2개의 인덱스를 가진 InsideTess가 있다.</p>

<h3 id="2-control-point-hull-shader">2. Control Point Hull Shader</h3>
<p><span class="code-fold-name">HullOut / HS</span></p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
</pre></td><td class="rouge-code"><pre><span class="k">struct</span>	<span class="nc">HullOut</span>
<span class="p">{</span>
  <span class="n">float3</span> <span class="n">PosL</span> <span class="o">:</span> <span class="n">POSITION</span><span class="p">;</span>
<span class="p">};</span>

<span class="p">[</span><span class="n">domain</span><span class="p">(</span><span class="err">“</span><span class="n">quad</span><span class="err">”</span><span class="p">)]</span> <span class="c1">// 패치의 모양 결정</span>
<span class="p">[</span><span class="n">partitioning</span><span class="p">(</span><span class="err">“</span><span class="n">integer</span><span class="err">”</span><span class="p">)]</span> <span class="c1">// </span>
<span class="p">[</span><span class="n">outputtopology</span><span class="p">(</span><span class="err">“</span><span class="n">triangle_cw</span><span class="err">”</span><span class="p">)]</span> <span class="c1">// culling face 결정</span>
<span class="p">[</span><span class="n">outputcontrolpoints</span><span class="p">(</span><span class="mi">4</span><span class="p">)]</span>
<span class="p">[</span><span class="n">patchconstantfunc</span><span class="p">(</span><span class="err">“</span><span class="n">ConstantHS</span><span class="err">”</span><span class="p">)]</span>
<span class="p">[</span><span class="n">maxtessfactor</span><span class="p">(</span><span class="mf">64.0</span><span class="n">f</span><span class="p">)]</span>
<span class="n">HullOut</span>	<span class="n">HS</span><span class="p">(</span><span class="n">InputPatch</span><span class="o">&lt;</span><span class="n">VertexOut</span><span class="p">,</span><span class="mi">4</span><span class="o">&gt;</span>	<span class="n">p</span><span class="p">,</span>
<span class="n">uint</span> <span class="n">i</span> <span class="o">:</span> <span class="n">SV_OutputControlPointID</span><span class="p">,</span>
<span class="n">uint</span> <span class="n">patchId</span> <span class="o">:</span> <span class="n">SV_PrimitiveID</span><span class="p">)</span>
<span class="p">{</span>
  <span class="n">HullOut</span>	<span class="n">hout</span><span class="p">;</span>
  <span class="n">hout</span><span class="p">.</span><span class="n">PosL</span>	<span class="o">=</span>	<span class="n">p</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">PosL</span><span class="p">;</span>
  <span class="k">return</span>	<span class="n">hout</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>해당 셰이더에서는 상수 헐 셰이더에서 전달받은 컨트롤 포인트들의 위치를 제어할 수 있다.</p>

<h2 id="테셀레이션">테셀레이션</h2>
<p>여기서는 실제로 도형이 어떻게 나누어지는지 알아보자. 패치는 quad, triangle 두 종류가 가장 대표적으로 쓰인다. quad는 4개의 edgeTess, 2개의 InsideTess를 사용하고 triangle은 3개, 1개를 사용한다.</p>

<div class="figure-row">
<figure>
  <img src="/assets/images/chap14/DirectX12_Chap14(3).png" alt="quad 테셀레이션" />
  <figcaption>quad 테셀레이션</figcaption>
</figure>
<figure>
  <img src="/assets/images/chap14/DirectX12_Chap14(4).png" alt="triangle 테셀레이션" />
  <figcaption>triangle 테셀레이션</figcaption>
</figure>
</div>

<p>edgeTess를 기준으로 모서리를 자르고 InsideTess를 기준으로 내부 레이어를 만드는 느낌으로 생각하면 될 것이다. 물론 이것은 하드웨어가 알아서 해주는 작업이기 때문에 우리는 Tessellation factor만 잘 넘겨주면 된다.</p>

<h4 id="tessellation-factor-결정-시-고려사항">Tessellation factor 결정 시 고려사항</h4>
<p>Tessellation factor에 따라 하드웨어가 알아서 나눠주기 때문에 factor를 잘 선택해야한다. 고려사항은 다음과 같다.</p>
<ul>
  <li>카메라로부터의 거리</li>
  <li>화면의 해상도</li>
  <li>카메라의 시점</li>
  <li>Roughness (일반적으로 거친 표면이 더 많은 테셀레이션 factor를 요구함)</li>
</ul>

<h2 id="domain-shader">Domain Shader</h2>
<p><span class="code-fold-name">DomainOut / DS</span></p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="k">struct</span> <span class="nc">DomainOut</span>
<span class="p">{</span>
  <span class="n">float4</span> <span class="n">PosH</span> <span class="o">:</span> <span class="n">SV_POSITION</span><span class="p">;</span>
<span class="p">};</span>
<span class="p">[</span><span class="n">domain</span><span class="p">(</span><span class="err">“</span><span class="n">quad</span><span class="err">”</span><span class="p">)]</span> <span class="c1">// 패치의 모양 결정</span>
<span class="n">DomainOut</span> <span class="n">DS</span><span class="p">(</span><span class="n">PatchTess</span> <span class="n">patchTess</span><span class="p">,</span>	
<span class="n">float2</span> <span class="n">uv</span> <span class="o">:</span> <span class="n">SV_DomainLocation</span><span class="p">,</span>	
<span class="k">const</span> <span class="n">OutputPatch</span><span class="o">&lt;</span><span class="n">HullOut</span><span class="p">,</span><span class="mi">4</span><span class="o">&gt;</span> <span class="n">quad</span><span class="p">)</span>
<span class="p">{</span>
  <span class="n">DomainOut</span> <span class="n">dout</span><span class="p">;</span>
  <span class="n">float3</span> <span class="n">v1</span> <span class="o">=</span> <span class="n">lerp</span><span class="p">(</span><span class="n">quad</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">PosL</span><span class="p">,</span> <span class="n">quad</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">PosL</span><span class="p">,</span> <span class="n">uv</span><span class="p">.</span><span class="n">x</span><span class="p">);</span>	
  <span class="n">float3</span> <span class="n">v2</span> <span class="o">=</span> <span class="n">lerp</span><span class="p">(</span><span class="n">quad</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">PosL</span><span class="p">,</span> <span class="n">quad</span><span class="p">[</span><span class="mi">3</span><span class="p">].</span><span class="n">PosL</span><span class="p">,</span> <span class="n">uv</span><span class="p">.</span><span class="n">x</span><span class="p">);</span>	
  <span class="n">float3</span> <span class="n">p</span> <span class="o">=</span> <span class="n">lerp</span><span class="p">(</span><span class="n">v1</span><span class="p">,</span> <span class="n">v2</span><span class="p">,</span> <span class="n">uv</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>	
  <span class="n">float4</span> <span class="n">posW</span> <span class="o">=</span> <span class="n">mul</span><span class="p">(</span><span class="n">float4</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">),</span> <span class="n">gWorld</span><span class="p">);</span>
  <span class="n">dout</span><span class="p">.</span><span class="n">PosH</span> <span class="o">=</span> <span class="n">mul</span><span class="p">(</span><span class="n">posW</span><span class="p">,</span> <span class="n">gViewProj</span><span class="p">);</span>
  <span class="k">return</span> <span class="n">dout</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<p>도메인 셰이더는 테셀레이션 스테이지에서 생성된 각 vertex마다 한번씩 호출된다. 파라미터로 받은 매개변수 좌표와 제어점을 이용해서 실제 3D정점 위치를 계산해야한다.</p>

<h2 id="tessellating-a-quad">Tessellating a Quad</h2>
<figure>
  <img src="/assets/images/chap14/DirectX12_Chap14(5).jpg" alt="Quad 테셀레이션 전체 구조" />
  <figcaption>Quad 테셀레이션 전체 구조</figcaption>
</figure>

<p>결국 전체적인 Tessellation의 과정은 다음과 같다고 볼 수 있다.</p>
<ol>
  <li>VS에서 Control Point를 넘겨주면 Constant HS에서 tessellation factor를 만든다.</li>
  <li>Control Point HS에서 Control Point들을 조정한다.</li>
  <li>Tessellation Stage에서는 하드웨어가 자동으로 도형을 나누어준다.</li>
  <li>Domain Shader에서는 정점을 하나씩 돌면서 정점의 위치를 조정한다.</li>
</ol>

<h2 id="3차-베지어-quad-patch">3차 베지어 Quad Patch</h2>
<p>이번에는 테셀레이션을 이용해서 곡면을 만드는 방법을 알아보자. 이를 위해서 베지어 곡선을 알아야 한다.</p>

<figure>
  <img src="/assets/images/chap14/DirectX12_Chap14(6).jpg" alt="Quad 테셀레이션 전체 구조" />
  <figcaption>Quad 테셀레이션 전체 구조</figcaption>
</figure>

<p>Blender나 Unity등등을 다루다 보면 그래프를 조절하는 방법중에 베지어 곡선을 본 적이 있을 것이다. 단순히 로그, 지수 형태로 선을 조절하지 않고 내가 원하는대로 곡선을 만들 수 있다는 장점이 있다.</p>

<p>포인트는 <strong>여러개의 점을 기준으로 선을 만들어내고, 복잡한 곡선일 수록 점이 여러개 필요하다.</strong></p>

<p>일직선으로 위치하지 않은 점 $\mathbf{p}_0$, $\mathbf{p}_1$, $\mathbf{p}_2$가 있다고 하자.</p>

\[\mathbf{p}_0^1 = (1-t)\mathbf{p}_0 + t\mathbf{p}_1\]

\[\mathbf{p}_1^1 = (1-t)\mathbf{p}_1 + t\mathbf{p}_2\]

\[\begin{aligned}
\mathbf{p}(t) &amp;= (1-t)\mathbf{p}_0^1 + t\mathbf{p}_1^1 \\
              &amp;= (1-t)\bigl((1-t)\mathbf{p}_0 + t\mathbf{p}_1\bigr) + t\bigl((1-t)\mathbf{p}_1 + t\mathbf{p}_2\bigr) \\
              &amp;= (1-t)^2\mathbf{p}_0 + 2(1-t)t\mathbf{p}_1 + t^2\mathbf{p}_2
\end{aligned}\]

<p>파라미터 $\mathbf{t}$로 선형보간을 하면 세 점 사이에 위치를 구할 수 있다. 해당 공식을 2차 베지어 곡선이라고 부른다.</p>

<div style="background:#1a1a24;border:1px solid #2a2a3a;border-radius:8px;padding:20px;margin:20px 0;text-align:center;">
  <canvas id="quadBezierCanvas" width="480" height="340" style="border:1px solid #2a2a3a;border-radius:6px;background:#111118;cursor:crosshair;max-width:100%;"></canvas>
  <div style="margin-top:12px;display:flex;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap;">
    <label style="font-weight:600;color:#7a7a90;font-size:0.9em;">t 값</label>
    <input type="range" id="quadBezierT" min="0" max="100" value="50" style="width:180px;accent-color:#7c6aff;" />
    <span id="quadBezierVal" style="font-family:'JetBrains Mono',monospace;color:#7c6aff;font-weight:600;font-size:0.9em;">t = 0.50</span>
  </div>
  <p style="font-size:0.8em;color:#4a4a60;margin-top:8px;">조절점을 드래그해서 곡선을 바꿔보세요</p>
</div>

<script>
(function() {
  var canvas = document.getElementById('quadBezierCanvas');
  var ctx = canvas.getContext('2d');
  var slider = document.getElementById('quadBezierT');
  var valLabel = document.getElementById('quadBezierVal');

  var pts = [
    { x: 60,  y: 300 },
    { x: 240, y: 40  },
    { x: 420, y: 300 }
  ];
  var dragging = null;

  function lerp(a, b, t) { return { x: a.x + (b.x - a.x) * t, y: a.y + (b.y - a.y) * t }; }

  function draw() {
    var t = slider.value / 100;
    valLabel.textContent = 't = ' + t.toFixed(2);
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 조절선 (점선)
    ctx.strokeStyle = '#2a2a3a'; ctx.lineWidth = 1.5; ctx.setLineDash([5,4]);
    ctx.beginPath(); ctx.moveTo(pts[0].x, pts[0].y); ctx.lineTo(pts[1].x, pts[1].y); ctx.lineTo(pts[2].x, pts[2].y); ctx.stroke();
    ctx.setLineDash([]);

    // 곡선
    ctx.strokeStyle = '#7c6aff'; ctx.lineWidth = 2.5;
    ctx.shadowColor = 'rgba(124,106,255,0.4)'; ctx.shadowBlur = 8;
    ctx.beginPath();
    for (var i = 0; i <= 100; i++) {
      var ti = i / 100, mt = 1 - ti;
      var x = mt*mt*pts[0].x + 2*mt*ti*pts[1].x + ti*ti*pts[2].x;
      var y = mt*mt*pts[0].y + 2*mt*ti*pts[1].y + ti*ti*pts[2].y;
      i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
    }
    ctx.stroke();
    ctx.shadowBlur = 0;

    // 보간 과정
    var Q0 = lerp(pts[0], pts[1], t);
    var Q1 = lerp(pts[1], pts[2], t);
    var B  = lerp(Q0, Q1, t);

    ctx.strokeStyle = 'rgba(255,106,124,0.6)'; ctx.lineWidth = 1.2; ctx.setLineDash([3,3]);
    ctx.beginPath(); ctx.moveTo(Q0.x, Q0.y); ctx.lineTo(Q1.x, Q1.y); ctx.stroke();
    ctx.setLineDash([]);

    // 시작/끝점
    [{ p: pts[0], c:'#e8e8f0', l:'P₀' }, { p: pts[2], c:'#e8e8f0', l:'P₂' }].forEach(function(o) {
      ctx.fillStyle = '#2a2a3a'; ctx.beginPath(); ctx.arc(o.p.x, o.p.y, 7, 0, Math.PI*2); ctx.fill();
      ctx.strokeStyle = o.c; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(o.p.x, o.p.y, 7, 0, Math.PI*2); ctx.stroke();
      ctx.fillStyle = '#7a7a90'; ctx.font = '12px JetBrains Mono,monospace'; ctx.fillText(o.l, o.p.x+11, o.p.y-9);
    });
    // 제어점
    ctx.fillStyle = 'rgba(124,106,255,0.2)'; ctx.beginPath(); ctx.arc(pts[1].x, pts[1].y, 7, 0, Math.PI*2); ctx.fill();
    ctx.strokeStyle = '#7c6aff'; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(pts[1].x, pts[1].y, 7, 0, Math.PI*2); ctx.stroke();
    ctx.fillStyle = '#7c6aff'; ctx.font = '12px JetBrains Mono,monospace'; ctx.fillText('P₁', pts[1].x+11, pts[1].y-9);

    // Q 점
    [{ p: Q0, l:'Q₀' }, { p: Q1, l:'Q₁' }].forEach(function(o) {
      ctx.fillStyle = 'rgba(255,106,124,0.7)'; ctx.beginPath(); ctx.arc(o.p.x, o.p.y, 4, 0, Math.PI*2); ctx.fill();
      ctx.fillStyle = '#ff6a7c'; ctx.font = '11px JetBrains Mono,monospace'; ctx.fillText(o.l, o.p.x+7, o.p.y-7);
    });

    // B(t)
    ctx.fillStyle = '#7c6aff'; ctx.shadowColor = 'rgba(124,106,255,0.6)'; ctx.shadowBlur = 10;
    ctx.beginPath(); ctx.arc(B.x, B.y, 8, 0, Math.PI*2); ctx.fill();
    ctx.shadowBlur = 0;
    ctx.fillStyle = '#e8e8f0'; ctx.font = 'bold 12px JetBrains Mono,monospace'; ctx.fillText('B(t)', B.x+12, B.y-10);
  }

  function hitTest(e) {
    var r = canvas.getBoundingClientRect();
    var mx = (e.clientX - r.left) * (canvas.width / r.width);
    var my = (e.clientY - r.top)  * (canvas.height / r.height);
    for (var i = 0; i < pts.length; i++) {
      var dx = pts[i].x - mx, dy = pts[i].y - my;
      if (Math.sqrt(dx*dx+dy*dy) < 14) return i;
    }
    return null;
  }

  canvas.addEventListener('mousedown',  function(e){ dragging = hitTest(e); });
  canvas.addEventListener('mousemove',  function(e){ if (dragging === null) return; var r = canvas.getBoundingClientRect(); pts[dragging].x = (e.clientX-r.left)*(canvas.width/r.width); pts[dragging].y = (e.clientY-r.top)*(canvas.height/r.height); draw(); });
  canvas.addEventListener('mouseup',   function(){ dragging = null; });
  canvas.addEventListener('mouseleave',function(){ dragging = null; });
  canvas.addEventListener('touchstart', function(e){ e.preventDefault(); dragging = hitTest(e.touches[0]); }, {passive:false});
  canvas.addEventListener('touchmove',  function(e){ e.preventDefault(); if (dragging===null) return; var r=canvas.getBoundingClientRect(); pts[dragging].x=(e.touches[0].clientX-r.left)*(canvas.width/r.width); pts[dragging].y=(e.touches[0].clientY-r.top)*(canvas.height/r.height); draw(); }, {passive:false});
  canvas.addEventListener('touchend',  function(){ dragging=null; });

  slider.addEventListener('input', draw);
  draw();
})();
</script>

<figure>
  <img src="/assets/images/chap14/DirectX12_Chap14(7).png" />
  <figcaption>Quad 테셀레이션 전체 구조</figcaption>
</figure>
<p>3차 베지어 곡선도 위와 같은 방식으로 구할 수 있다. 최종 공식은 다음과 같다.</p>

\[\begin{aligned}
\mathbf{p}(t) &amp;= (1-t)^3\mathbf{p}_0 + 3t(1-t)^2\mathbf{p}_1 + 3t^2(1-t)\mathbf{p}_2 + t^3\mathbf{p}_3
\end{aligned}\]

<div style="background:#1a1a24;border:1px solid #2a2a3a;border-radius:8px;padding:20px;margin:20px 0;text-align:center;">
  <canvas id="cubicBezierCanvas" width="480" height="340" style="border:1px solid #2a2a3a;border-radius:6px;background:#111118;cursor:crosshair;max-width:100%;"></canvas>
  <div style="margin-top:12px;display:flex;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap;">
    <label style="font-weight:600;color:#7a7a90;font-size:0.9em;">t 값</label>
    <input type="range" id="cubicBezierT" min="0" max="100" value="50" style="width:180px;accent-color:#7c6aff;" />
    <span id="cubicBezierVal" style="font-family:'JetBrains Mono',monospace;color:#7c6aff;font-weight:600;font-size:0.9em;">t = 0.50</span>
  </div>
  <p style="font-size:0.8em;color:#4a4a60;margin-top:8px;">조절점을 드래그해서 곡선을 바꿔보세요</p>
</div>

<script>
(function() {
  var canvas = document.getElementById('cubicBezierCanvas');
  var ctx = canvas.getContext('2d');
  var slider = document.getElementById('cubicBezierT');
  var valLabel = document.getElementById('cubicBezierVal');

  var pts = [
    { x: 50,  y: 300 },
    { x: 110, y: 40  },
    { x: 370, y: 40  },
    { x: 430, y: 300 }
  ];
  var dragging = null;

  function lerp(a, b, t) { return { x: a.x+(b.x-a.x)*t, y: a.y+(b.y-a.y)*t }; }

  function draw() {
    var t = slider.value / 100;
    valLabel.textContent = 't = ' + t.toFixed(2);
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // 조절선
    ctx.strokeStyle = '#2a2a3a'; ctx.lineWidth = 1.5; ctx.setLineDash([5,4]);
    ctx.beginPath(); ctx.moveTo(pts[0].x,pts[0].y); ctx.lineTo(pts[1].x,pts[1].y); ctx.lineTo(pts[2].x,pts[2].y); ctx.lineTo(pts[3].x,pts[3].y); ctx.stroke();
    ctx.setLineDash([]);

    // 곡선
    ctx.strokeStyle = '#7c6aff'; ctx.lineWidth = 2.5;
    ctx.shadowColor = 'rgba(124,106,255,0.4)'; ctx.shadowBlur = 8;
    ctx.beginPath();
    for (var i = 0; i <= 100; i++) {
      var ti=i/100, mt=1-ti;
      var x=mt*mt*mt*pts[0].x + 3*mt*mt*ti*pts[1].x + 3*mt*ti*ti*pts[2].x + ti*ti*ti*pts[3].x;
      var y=mt*mt*mt*pts[0].y + 3*mt*mt*ti*pts[1].y + 3*mt*ti*ti*pts[2].y + ti*ti*ti*pts[3].y;
      i===0 ? ctx.moveTo(x,y) : ctx.lineTo(x,y);
    }
    ctx.stroke();
    ctx.shadowBlur = 0;

    // De Casteljau
    var Q0=lerp(pts[0],pts[1],t), Q1=lerp(pts[1],pts[2],t), Q2=lerp(pts[2],pts[3],t);
    var R0=lerp(Q0,Q1,t), R1=lerp(Q1,Q2,t);
    var B=lerp(R0,R1,t);

    ctx.strokeStyle='rgba(255,106,124,0.5)'; ctx.lineWidth=1.2; ctx.setLineDash([3,3]);
    ctx.beginPath(); ctx.moveTo(Q0.x,Q0.y); ctx.lineTo(Q1.x,Q1.y); ctx.lineTo(Q2.x,Q2.y); ctx.stroke();
    ctx.strokeStyle='rgba(255,106,124,0.8)';
    ctx.beginPath(); ctx.moveTo(R0.x,R0.y); ctx.lineTo(R1.x,R1.y); ctx.stroke();
    ctx.setLineDash([]);

    // 시작/끝점
    [{ p:pts[0],l:'P₀'},{p:pts[3],l:'P₃'}].forEach(function(o){
      ctx.fillStyle='#2a2a3a'; ctx.beginPath(); ctx.arc(o.p.x,o.p.y,7,0,Math.PI*2); ctx.fill();
      ctx.strokeStyle='#e8e8f0'; ctx.lineWidth=1.5; ctx.beginPath(); ctx.arc(o.p.x,o.p.y,7,0,Math.PI*2); ctx.stroke();
      ctx.fillStyle='#7a7a90'; ctx.font='12px JetBrains Mono,monospace'; ctx.fillText(o.l,o.p.x+11,o.p.y-9);
    });
    // 제어점
    [{ p:pts[1],l:'P₁'},{p:pts[2],l:'P₂'}].forEach(function(o){
      ctx.fillStyle='rgba(124,106,255,0.2)'; ctx.beginPath(); ctx.arc(o.p.x,o.p.y,7,0,Math.PI*2); ctx.fill();
      ctx.strokeStyle='#7c6aff'; ctx.lineWidth=1.5; ctx.beginPath(); ctx.arc(o.p.x,o.p.y,7,0,Math.PI*2); ctx.stroke();
      ctx.fillStyle='#7c6aff'; ctx.font='12px JetBrains Mono,monospace'; ctx.fillText(o.l,o.p.x+11,o.p.y-9);
    });

    // Q, R 점
    [Q0,Q1,Q2].forEach(function(p,i){
      ctx.fillStyle='rgba(255,106,124,0.6)'; ctx.beginPath(); ctx.arc(p.x,p.y,4,0,Math.PI*2); ctx.fill();
      ctx.fillStyle='#ff6a7c'; ctx.font='11px JetBrains Mono,monospace'; ctx.fillText('Q'+i,p.x+6,p.y-6);
    });
    [R0,R1].forEach(function(p,i){
      ctx.fillStyle='rgba(255,106,124,0.9)'; ctx.beginPath(); ctx.arc(p.x,p.y,4,0,Math.PI*2); ctx.fill();
      ctx.fillStyle='#ff6a7c'; ctx.font='11px JetBrains Mono,monospace'; ctx.fillText('R'+i,p.x+6,p.y-6);
    });

    // B(t)
    ctx.fillStyle='#7c6aff'; ctx.shadowColor='rgba(124,106,255,0.7)'; ctx.shadowBlur=12;
    ctx.beginPath(); ctx.arc(B.x,B.y,8,0,Math.PI*2); ctx.fill();
    ctx.shadowBlur=0;
    ctx.fillStyle='#e8e8f0'; ctx.font='bold 12px JetBrains Mono,monospace'; ctx.fillText('B(t)',B.x+12,B.y-10);
  }

  function hitTest(e) {
    var r=canvas.getBoundingClientRect();
    var mx=(e.clientX-r.left)*(canvas.width/r.width);
    var my=(e.clientY-r.top)*(canvas.height/r.height);
    for (var i=0; i<pts.length; i++) { var dx=pts[i].x-mx,dy=pts[i].y-my; if(Math.sqrt(dx*dx+dy*dy)<14) return i; }
    return null;
  }

  canvas.addEventListener('mousedown',  function(e){ dragging=hitTest(e); });
  canvas.addEventListener('mousemove',  function(e){ if(dragging===null) return; var r=canvas.getBoundingClientRect(); pts[dragging].x=(e.clientX-r.left)*(canvas.width/r.width); pts[dragging].y=(e.clientY-r.top)*(canvas.height/r.height); draw(); });
  canvas.addEventListener('mouseup',   function(){ dragging=null; });
  canvas.addEventListener('mouseleave',function(){ dragging=null; });
  canvas.addEventListener('touchstart', function(e){ e.preventDefault(); dragging=hitTest(e.touches[0]); },{passive:false});
  canvas.addEventListener('touchmove',  function(e){ e.preventDefault(); if(dragging===null) return; var r=canvas.getBoundingClientRect(); pts[dragging].x=(e.touches[0].clientX-r.left)*(canvas.width/r.width); pts[dragging].y=(e.touches[0].clientY-r.top)*(canvas.height/r.height); draw(); },{passive:false});
  canvas.addEventListener('touchend',  function(){ dragging=null; });

  slider.addEventListener('input', draw);
  draw();
})();
</script>

<figure>
  <img src="/assets/images/chap14/DirectX12_Chap14(8).png" />
  <figcaption>3차 베지어 평면</figcaption>
</figure>

<p>어쨋든 해당 곡선들을 이용해서 4*4 쿼드 평면에 베지어 공식을 적용시키면 다음과 같은 곡면을 그릴 수 있게 된다.</p>

<h2 id="연습문제">연습문제</h2>

<h3 id="2번-거리에-따라-정이십면체를-tessellate해서-구를-그려라">2번. 거리에 따라 정이십면체를 Tessellate해서 구를 그려라.</h3>

<p>정이십면체는 삼각형으로 이루어져있으므로 예제의 quad가 아니라 triangle로 패치와 factor를 설정해주어야 한다.</p>

<ol>
  <li>GeometryGenerator 클래스를 이용해 정20면체 Geometry 생성</li>
  <li>Constant Hull Shader에서 카메라와 구의 거리를 받아와서 거리에 따라 Tessellation factor 설정</li>
  <li>Domain Shader에서 각 점을 normalize한 후 반지름 곱해주기 (구의 모양 완성)</li>
</ol>

<figure>
  <img src="/assets/images/chap14/DirectX12_Chap14(9).gif" />
  <figcaption>2번 문제 결과 화면</figcaption>
</figure>

<h3 id="4번-partitioningfractional_even--odd를-사용해서-테셀레이션을-구현하라">4번. [partitioning(“fractional_even / odd”)]를 사용해서 테셀레이션을 구현하라.</h3>

<p>앞서 Constant Hull Shader를 다시 보면</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
</pre></td><td class="rouge-code"><pre><span class="p">[</span><span class="n">partitioning</span><span class="p">(</span><span class="err">“</span><span class="n">integer</span><span class="err">”</span><span class="p">)]</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>이 부분이 있었는데 이 “integer”를 “fractional_even” 또는 “fractional_odd”로 바꿔보라는 문제이다.</p>

<p>원래 이런 느낌이었다면</p>

<div class="figure-row">
<figure>
  <img src="/assets/images/chap14/DirectX12_Chap14(10).gif" alt="quad 테셀레이션" />
  <figcaption>fractional_odd</figcaption>
</figure>
<figure>
  <img src="/assets/images/chap14/DirectX12_Chap14(11).gif" alt="triangle 테셀레이션" />
  <figcaption>fractional_even</figcaption>
</figure>
</div>
<p>둘 다 integer보다 더 부드럽게 파티셔닝이 되는 모습이다. 사실 odd 와 even의 차이는 크게 체감하지 못하겠다.</p>

<h3 id="8번-bezier-patch를-수정해서-라이팅의-효과를-받는-곡면을-그려라-삼각형의-세-점을-이용하면-normal-벡터를-구할-수-있다">8번. Bezier Patch를 수정해서 라이팅의 효과를 받는 곡면을 그려라. (삼각형의 세 점을 이용하면 normal 벡터를 구할 수 있다.)</h3>

<p>이번엔 라인이 아니라 라이팅이 구현된 곡면을 렌더링하는 문제이다. 문제에서 세 점의 외적을 통해 normal을 구하라고 했는데 나는 더 쉬운 방법으로 구현했다.</p>

<p><span class="code-fold-name">BernsteinBasis / DS / PS</span></p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
</pre></td><td class="rouge-code"><pre><span class="n">float4</span> <span class="nf">BernsteinBasis</span><span class="p">(</span><span class="kt">float</span> <span class="n">t</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">float</span> <span class="n">invT</span> <span class="o">=</span> <span class="mf">1.0</span><span class="n">f</span> <span class="o">-</span> <span class="n">t</span><span class="p">;</span>

    <span class="k">return</span> <span class="n">float4</span><span class="p">(</span> <span class="n">invT</span> <span class="o">*</span> <span class="n">invT</span> <span class="o">*</span> <span class="n">invT</span><span class="p">,</span>
                   <span class="mf">3.0</span><span class="n">f</span> <span class="o">*</span> <span class="n">t</span> <span class="o">*</span> <span class="n">invT</span> <span class="o">*</span> <span class="n">invT</span><span class="p">,</span>
                   <span class="mf">3.0</span><span class="n">f</span> <span class="o">*</span> <span class="n">t</span> <span class="o">*</span> <span class="n">t</span> <span class="o">*</span> <span class="n">invT</span><span class="p">,</span>
                   <span class="n">t</span> <span class="o">*</span> <span class="n">t</span> <span class="o">*</span> <span class="n">t</span> <span class="p">);</span>
<span class="p">}</span>

<span class="n">float4</span> <span class="n">dBernsteinBasis</span><span class="p">(</span><span class="kt">float</span> <span class="n">t</span><span class="p">)</span> <span class="c1">// 미분</span>
<span class="p">{</span>
    <span class="kt">float</span> <span class="n">invT</span> <span class="o">=</span> <span class="mf">1.0</span><span class="n">f</span> <span class="o">-</span> <span class="n">t</span><span class="p">;</span>

    <span class="k">return</span> <span class="n">float4</span><span class="p">(</span> <span class="o">-</span><span class="mi">3</span> <span class="o">*</span> <span class="n">invT</span> <span class="o">*</span> <span class="n">invT</span><span class="p">,</span>
                   <span class="mi">3</span> <span class="o">*</span> <span class="n">invT</span> <span class="o">*</span> <span class="n">invT</span> <span class="o">-</span> <span class="mi">6</span> <span class="o">*</span> <span class="n">t</span> <span class="o">*</span> <span class="n">invT</span><span class="p">,</span>
                   <span class="mi">6</span> <span class="o">*</span> <span class="n">t</span> <span class="o">*</span> <span class="n">invT</span> <span class="o">-</span> <span class="mi">3</span> <span class="o">*</span> <span class="n">t</span> <span class="o">*</span> <span class="n">t</span><span class="p">,</span>
                   <span class="mi">3</span> <span class="o">*</span> <span class="n">t</span> <span class="o">*</span> <span class="n">t</span> <span class="p">);</span>
<span class="p">}</span>

<span class="n">float3</span> <span class="n">CubicBezierSum</span><span class="p">(</span><span class="k">const</span> <span class="n">OutputPatch</span><span class="o">&lt;</span><span class="n">HullOut</span><span class="p">,</span> <span class="mi">16</span><span class="o">&gt;</span> <span class="n">bezpatch</span><span class="p">,</span> <span class="n">float4</span> <span class="n">basisU</span><span class="p">,</span> <span class="n">float4</span> <span class="n">basisV</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">float3</span> <span class="n">sum</span> <span class="o">=</span> <span class="n">float3</span><span class="p">(</span><span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">);</span>
    <span class="n">sum</span>  <span class="o">=</span> <span class="n">basisV</span><span class="p">.</span><span class="n">x</span> <span class="o">*</span> <span class="p">(</span><span class="n">basisU</span><span class="p">.</span><span class="n">x</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">PosL</span>  <span class="o">+</span> <span class="n">basisU</span><span class="p">.</span><span class="n">y</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">PosL</span>  <span class="o">+</span> <span class="n">basisU</span><span class="p">.</span><span class="n">z</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">2</span><span class="p">].</span><span class="n">PosL</span>  <span class="o">+</span> <span class="n">basisU</span><span class="p">.</span><span class="n">w</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">3</span><span class="p">].</span><span class="n">PosL</span> <span class="p">);</span>
    <span class="n">sum</span> <span class="o">+=</span> <span class="n">basisV</span><span class="p">.</span><span class="n">y</span> <span class="o">*</span> <span class="p">(</span><span class="n">basisU</span><span class="p">.</span><span class="n">x</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">4</span><span class="p">].</span><span class="n">PosL</span>  <span class="o">+</span> <span class="n">basisU</span><span class="p">.</span><span class="n">y</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">5</span><span class="p">].</span><span class="n">PosL</span>  <span class="o">+</span> <span class="n">basisU</span><span class="p">.</span><span class="n">z</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">6</span><span class="p">].</span><span class="n">PosL</span>  <span class="o">+</span> <span class="n">basisU</span><span class="p">.</span><span class="n">w</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">7</span><span class="p">].</span><span class="n">PosL</span> <span class="p">);</span>
    <span class="n">sum</span> <span class="o">+=</span> <span class="n">basisV</span><span class="p">.</span><span class="n">z</span> <span class="o">*</span> <span class="p">(</span><span class="n">basisU</span><span class="p">.</span><span class="n">x</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">8</span><span class="p">].</span><span class="n">PosL</span>  <span class="o">+</span> <span class="n">basisU</span><span class="p">.</span><span class="n">y</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">9</span><span class="p">].</span><span class="n">PosL</span>  <span class="o">+</span> <span class="n">basisU</span><span class="p">.</span><span class="n">z</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">10</span><span class="p">].</span><span class="n">PosL</span> <span class="o">+</span> <span class="n">basisU</span><span class="p">.</span><span class="n">w</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">11</span><span class="p">].</span><span class="n">PosL</span><span class="p">);</span>
    <span class="n">sum</span> <span class="o">+=</span> <span class="n">basisV</span><span class="p">.</span><span class="n">w</span> <span class="o">*</span> <span class="p">(</span><span class="n">basisU</span><span class="p">.</span><span class="n">x</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">12</span><span class="p">].</span><span class="n">PosL</span> <span class="o">+</span> <span class="n">basisU</span><span class="p">.</span><span class="n">y</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">13</span><span class="p">].</span><span class="n">PosL</span> <span class="o">+</span> <span class="n">basisU</span><span class="p">.</span><span class="n">z</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">14</span><span class="p">].</span><span class="n">PosL</span> <span class="o">+</span> <span class="n">basisU</span><span class="p">.</span><span class="n">w</span><span class="o">*</span><span class="n">bezpatch</span><span class="p">[</span><span class="mi">15</span><span class="p">].</span><span class="n">PosL</span><span class="p">);</span>

    <span class="k">return</span> <span class="n">sum</span><span class="p">;</span>
<span class="p">}</span>

<span class="p">[</span><span class="n">domain</span><span class="p">(</span><span class="s">"quad"</span><span class="p">)]</span>
<span class="n">DomainOut</span> <span class="n">DS</span><span class="p">(</span><span class="n">PatchTess</span> <span class="n">patchTess</span><span class="p">,</span>
             <span class="n">float2</span> <span class="n">uv</span> <span class="o">:</span> <span class="n">SV_DomainLocation</span><span class="p">,</span>
             <span class="k">const</span> <span class="n">OutputPatch</span><span class="o">&lt;</span><span class="n">HullOut</span><span class="p">,</span> <span class="mi">16</span><span class="o">&gt;</span> <span class="n">bezPatch</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">DomainOut</span> <span class="n">dout</span><span class="p">;</span>

	<span class="n">float4</span> <span class="n">basisU</span>  <span class="o">=</span> <span class="n">BernsteinBasis</span><span class="p">(</span><span class="n">uv</span><span class="p">.</span><span class="n">x</span><span class="p">);</span>
	<span class="n">float4</span> <span class="n">basisV</span>  <span class="o">=</span> <span class="n">BernsteinBasis</span><span class="p">(</span><span class="n">uv</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>
	<span class="n">float4</span> <span class="n">dbasisU</span> <span class="o">=</span> <span class="n">dBernsteinBasis</span><span class="p">(</span><span class="n">uv</span><span class="p">.</span><span class="n">x</span><span class="p">);</span>
	<span class="n">float4</span> <span class="n">dbasisV</span> <span class="o">=</span> <span class="n">dBernsteinBasis</span><span class="p">(</span><span class="n">uv</span><span class="p">.</span><span class="n">y</span><span class="p">);</span>

	<span class="n">float3</span> <span class="n">p</span>    <span class="o">=</span> <span class="n">CubicBezierSum</span><span class="p">(</span><span class="n">bezPatch</span><span class="p">,</span> <span class="n">basisU</span><span class="p">,</span>  <span class="n">basisV</span><span class="p">);</span>
	<span class="n">float3</span> <span class="n">dpdu</span> <span class="o">=</span> <span class="n">CubicBezierSum</span><span class="p">(</span><span class="n">bezPatch</span><span class="p">,</span> <span class="n">dbasisU</span><span class="p">,</span> <span class="n">basisV</span><span class="p">);</span>  <span class="c1">// u 방향 접선</span>
	<span class="n">float3</span> <span class="n">dpdv</span> <span class="o">=</span> <span class="n">CubicBezierSum</span><span class="p">(</span><span class="n">bezPatch</span><span class="p">,</span> <span class="n">basisU</span><span class="p">,</span>  <span class="n">dbasisV</span><span class="p">);</span> <span class="c1">// v 방향 접선</span>

	<span class="n">float3</span> <span class="n">normal</span> <span class="o">=</span> <span class="n">normalize</span><span class="p">(</span><span class="n">cross</span><span class="p">(</span><span class="n">dpdu</span><span class="p">,</span> <span class="n">dpdv</span><span class="p">));</span>

	<span class="n">float4</span> <span class="n">posW</span>  <span class="o">=</span> <span class="n">mul</span><span class="p">(</span><span class="n">float4</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">),</span> <span class="n">gWorld</span><span class="p">);</span>
	<span class="n">dout</span><span class="p">.</span><span class="n">PosH</span>    <span class="o">=</span> <span class="n">mul</span><span class="p">(</span><span class="n">posW</span><span class="p">,</span> <span class="n">gViewProj</span><span class="p">);</span>
	<span class="n">dout</span><span class="p">.</span><span class="n">PosW</span>    <span class="o">=</span> <span class="n">posW</span><span class="p">.</span><span class="n">xyz</span><span class="p">;</span>
	<span class="n">dout</span><span class="p">.</span><span class="n">NormalW</span> <span class="o">=</span> <span class="n">mul</span><span class="p">(</span><span class="n">normal</span><span class="p">,</span> <span class="p">(</span><span class="n">float3x3</span><span class="p">)</span><span class="n">gWorld</span><span class="p">);</span>

	<span class="k">return</span> <span class="n">dout</span><span class="p">;</span>
<span class="p">}</span>

<span class="n">float4</span> <span class="n">PS</span><span class="p">(</span><span class="n">DomainOut</span> <span class="n">pin</span><span class="p">)</span> <span class="o">:</span> <span class="n">SV_Target</span>
<span class="p">{</span>
	<span class="n">pin</span><span class="p">.</span><span class="n">NormalW</span> <span class="o">=</span> <span class="n">normalize</span><span class="p">(</span><span class="n">pin</span><span class="p">.</span><span class="n">NormalW</span><span class="p">);</span>

	<span class="n">float3</span> <span class="n">toEye</span> <span class="o">=</span> <span class="n">normalize</span><span class="p">(</span><span class="n">gEyePosW</span> <span class="o">-</span> <span class="n">pin</span><span class="p">.</span><span class="n">PosW</span><span class="p">);</span>

	<span class="n">float4</span> <span class="n">ambient</span> <span class="o">=</span> <span class="n">gAmbientLight</span> <span class="o">*</span> <span class="n">gDiffuseAlbedo</span><span class="p">;</span>

	<span class="k">const</span> <span class="kt">float</span> <span class="n">shininess</span> <span class="o">=</span> <span class="mf">1.0</span><span class="n">f</span> <span class="o">-</span> <span class="n">gRoughness</span><span class="p">;</span>
	<span class="n">Material</span> <span class="n">mat</span> <span class="o">=</span> <span class="p">{</span> <span class="n">gDiffuseAlbedo</span><span class="p">,</span> <span class="n">gFresnelR0</span><span class="p">,</span> <span class="n">shininess</span> <span class="p">};</span>
	<span class="n">float3</span> <span class="n">shadowFactor</span> <span class="o">=</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">;</span>
	<span class="n">float4</span> <span class="n">directLight</span> <span class="o">=</span> <span class="n">ComputeLighting</span><span class="p">(</span><span class="n">gLights</span><span class="p">,</span> <span class="n">mat</span><span class="p">,</span> <span class="n">pin</span><span class="p">.</span><span class="n">PosW</span><span class="p">,</span>
		<span class="n">pin</span><span class="p">.</span><span class="n">NormalW</span><span class="p">,</span> <span class="n">toEye</span><span class="p">,</span> <span class="n">shadowFactor</span><span class="p">);</span>

	<span class="n">float4</span> <span class="n">litColor</span> <span class="o">=</span> <span class="n">ambient</span> <span class="o">+</span> <span class="n">directLight</span><span class="p">;</span>
	<span class="n">litColor</span><span class="p">.</span><span class="n">a</span> <span class="o">=</span> <span class="n">gDiffuseAlbedo</span><span class="p">.</span><span class="n">a</span><span class="p">;</span>

	<span class="k">return</span> <span class="n">litColor</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>dBernsteinBasis는 베지어 곡선을 미분한 값을 구하는 함수인데, 곡선을 미분하면 해당 곡선의 접선이 나온다. 평면에서 u, v에 대해 미분하여 나온 접선 두 개를 외적하면 normal 벡터를 구할 수 있다.</p>

<figure>
  <img src="/assets/images/chap14/DirectX12_Chap14(12).png" alt="quad 테셀레이션" />
  <figcaption>8번 문제 결과 화면</figcaption>
</figure>
<p>이후에는 이전 장들에서 배운 라이팅 연산을 이용해 PS를 채워주면 끝난다.</p>

<p>이것으로 14장 정리 완료</p>]]></content><author><name>Your Name</name></author><category term="DirectX12" /><summary type="html"><![CDATA[DirectX12의 Tessellation 대해 소개합니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://smg-927.github.io/assets/images/chap14/directX12_represent.jpg" /><media:content medium="image" url="https://smg-927.github.io/assets/images/chap14/directX12_represent.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">[3D Programming with DirectX12] Chapter 13 : The Compute Shader</title><link href="https://smg-927.github.io/2026/04/26/DirectX12_Chap13/" rel="alternate" type="text/html" title="[3D Programming with DirectX12] Chapter 13 : The Compute Shader" /><published>2026-04-26T00:00:00+00:00</published><updated>2026-04-26T00:00:00+00:00</updated><id>https://smg-927.github.io/2026/04/26/DirectX12_Chap13</id><content type="html" xml:base="https://smg-927.github.io/2026/04/26/DirectX12_Chap13/"><![CDATA[<h2 id="compute-shader-개요">Compute Shader 개요</h2>
<p>그래픽스 분야에서 활발히 연구를 진행하던 시기에 혹자는 이런 생각을 했다.</p>

<blockquote>
  <p>GPU를 화면 그리는데만 쓰는게 아니라 다른데도 쓰면 안되나 …?</p>
</blockquote>

<p>GPU은 단순한 연산을 병렬로 처리하는데에 특화되어 있는데 이러한 특성을가진 계산에 GPU를 같이 사용한다면 효율이 늘어날 것이다.</p>

<p>이러한 아이디어를 기반으로 나온 것이 바로 Compute Shader이다.</p>

<h2 id="스레드와-스레드-그룹">스레드와 스레드 그룹</h2>
<p>GPU에서의 <strong>스레드</strong>는 최소작업단위 1개를 의미하고, 스레드들이 일정한 개수로 모인 것을 <strong>스레드 그룹</strong>이라고 한다.</p>

<p>컴퓨트 셰이더에서 스레드들은 여러 개의 스레드 그룹으로 나뉘고, 각 그룹은 같은 동기화된 <strong>공유 메모리</strong>를 가질 수 있다. 다른 그룹끼리는 당연히 공유되지 않는다.</p>

<p>Direct3D에서는 스레드 그룹 크기를 32의 배수가 아닌 크기로 지정할 수도 있지만 성능상의 이유로 스레드 그룹의 크기는 32의 배수로 설정하는게 좋다.</p>

<h2 id="간단한-컴퓨트-셰이더-코드">간단한 컴퓨트 셰이더 코드</h2>

<p><span class="code-fold-name">기본 CS</span></p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
</pre></td><td class="rouge-code"><pre><span class="n">Texture2D</span> <span class="n">gInputA</span><span class="p">;</span>	
<span class="n">Texture2D</span> <span class="n">gInputB</span><span class="p">;</span>
<span class="n">RWTexture2D</span><span class="o">&lt;</span><span class="n">float4</span><span class="o">&gt;</span>	<span class="n">gOutput</span><span class="p">;</span>

<span class="p">[</span><span class="n">numthreads</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span>	<span class="mi">16</span><span class="p">,</span>	<span class="mi">1</span><span class="p">)]</span>
<span class="kt">void</span> <span class="nf">CS</span><span class="p">(</span><span class="n">int3</span> <span class="n">dispatchThreadID</span> <span class="o">:</span>	<span class="n">SV_DispatchThreadID</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">gOutput</span><span class="p">[</span><span class="n">dispatchThreadID</span><span class="p">.</span><span class="n">xy</span><span class="p">]</span> <span class="o">=</span>	
    <span class="n">gInputA</span><span class="p">[</span><span class="n">dispatchThreadID</span><span class="p">.</span><span class="n">xy</span><span class="p">]</span> <span class="o">+</span>
    <span class="n">gInputB</span><span class="p">[</span><span class="n">dispatchThreadID</span><span class="p">.</span><span class="n">xy</span><span class="p">];</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>두 개의 배열을 합치는 아주 기본적인 컴퓨트 셰이더 코드이다.</p>
<ul>
  <li>Texture2D는 읽기 전용, RWTexture2D는 읽기,쓰기 모드 가능.</li>
  <li>x축 16개, y축 16개, z축 1개 (그룹 당 256개 스레드)</li>
  <li>dispatchTrheadID : 현재 스레드의 절대 좌표</li>
</ul>

<p>연결 방법은 다른 셰이더와 마찬가지로 전용 PSO(Pipeline State Object)를 통해 가능하다.</p>

<h2 id="스레드-식별-시스템">스레드 식별 시스템</h2>

<figure>
  <img src="/assets/images/chap13/DirectX12_Chap13(1).png" alt="스레드 그룹 설명" />
  <figcaption>스레드 그룹 설명</figcaption>
</figure>

<p>해당 스레드 그룹 그림을 보며 세 가지 식별자를 알아보자.</p>

<ul>
  <li>SV_groupID : 전체 디스패치 공간에서 내 그룹이 몇 번째인가. Thread T가 속한 그룹은 (1,1)이다.</li>
  <li>SV_GroupThreadID : 내 그룹 안에서 몇 번째 스레드인지. Thread T는 (2,3)이다.</li>
  <li>SV_DispatchThreadID : SV_GroupID * numthreads + SV_GroupThreadID, Thread T는 (8, 8)이다.</li>
</ul>

<h2 id="append--consume-버퍼">Append &amp; Consume 버퍼</h2>

<p><span class="code-fold-name">Particle / Append &amp; Consume CS</span></p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
</pre></td><td class="rouge-code"><pre><span class="k">struct</span> <span class="nc">Particle</span>
<span class="p">{</span>
    <span class="n">float3</span> <span class="n">Position</span><span class="p">;</span>
    <span class="n">float3</span> <span class="n">Velocity</span><span class="p">;</span>
    <span class="n">float3</span> <span class="n">Acceleration</span><span class="p">;</span>
<span class="p">};</span>

<span class="kt">float</span> <span class="n">TimeStep</span> <span class="o">=</span> <span class="mf">1.0</span><span class="n">f</span> <span class="o">/</span> <span class="mf">60.0</span><span class="n">f</span><span class="p">;</span>

<span class="n">ConsumeStructuredBuffer</span><span class="o">&lt;</span><span class="n">Particle</span><span class="o">&gt;</span> <span class="n">gInput</span><span class="p">;</span>
<span class="n">AppendStructuredBuffer</span><span class="o">&lt;</span><span class="n">Particle</span><span class="o">&gt;</span> <span class="n">gOutput</span><span class="p">;</span>

<span class="p">[</span><span class="n">numthreads</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span>	<span class="mi">16</span><span class="p">,</span>	<span class="mi">1</span><span class="p">)]</span>
<span class="kt">void</span> <span class="n">CS</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">Particle</span> <span class="n">p</span> <span class="o">=</span> <span class="n">gInput</span><span class="p">.</span><span class="n">Consume</span><span class="p">();</span>
    <span class="n">p</span><span class="p">.</span><span class="n">Velocity</span> <span class="o">+=</span> <span class="n">p</span><span class="p">.</span><span class="n">Acceleration</span><span class="o">*</span><span class="n">TimeStep</span><span class="p">;</span>
    <span class="n">p</span><span class="p">.</span><span class="n">Position</span> <span class="o">+=</span> <span class="n">p</span><span class="p">.</span><span class="n">Velocity</span><span class="o">*</span><span class="n">TimeStep</span><span class="p">;</span>
    <span class="n">gOutput</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span>	<span class="n">p</span><span class="p">);</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>
<p>Consume과 Append 버퍼는 컴퓨트 셰이더에서 제공하는 특수한 버퍼타입이다. 이 두 버퍼는 인덱스로 접근하지 않고 스택이나 큐처럼 인덱스 없이 데이터를 꺼내거나 집어넣는 방식이다. 중요한 점은 계산과정에서 <strong>데이터의 순서를 보장하지 않는다.</strong></p>

<p>또한 해당 버퍼는 동적으로 크기가 늘어나지 않으므로 슬롯을 미리 할당하므로 각 요소가 독립적으로 처리될 수 있는 경우에만 사용하는 것이 좋다.</p>

<h2 id="공유-메모리--synchronization">공유 메모리 &amp; Synchronization</h2>

<p>스레드 그룹에는 <strong>공유 메모리</strong>라고 불리는 전용 메모리 영역이 할당된다. 해당 메모리는 하드웨어 캐시와 비슷한 수준으로 빠르다.</p>

<p>하나의 스레드 그룹이 같은 메모리를 공유하게 된다면 포스트 프로세싱(ex. 블러처리)같은 작업에 유용하게 쓰일 수 있다.</p>

<p><span class="code-fold-name">공유 메모리 CS</span></p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
</pre></td><td class="rouge-code"><pre><span class="n">Texture2D</span> <span class="n">gInput</span><span class="p">;</span>
<span class="n">RWTexture2D</span> <span class="n">gOutput</span><span class="p">;</span>
<span class="n">groupshared</span> <span class="n">float4</span> <span class="n">gCache</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span>

<span class="p">[</span><span class="n">numthreads</span><span class="p">(</span><span class="mi">256</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)]</span>
<span class="kt">void</span> <span class="nf">CS</span><span class="p">(</span><span class="n">int3</span> <span class="n">groupThreadID</span> <span class="o">:</span> <span class="n">SV_GroupThreadID</span><span class="p">,</span>
        <span class="n">int3</span> <span class="n">dispatchThreadID</span> <span class="o">:</span> <span class="n">SV_DispatchThreadID</span><span class="p">)</span>
<span class="p">{</span>

    <span class="n">gCache</span><span class="p">[</span><span class="n">groupThreadID</span><span class="p">.</span><span class="n">x</span><span class="p">]</span> <span class="o">=</span> <span class="n">gInput</span><span class="p">[</span><span class="n">dispatchThreadID</span><span class="p">.</span><span class="n">xy</span><span class="p">];</span>

    <span class="n">GroupMemoryBarrierWithGroupSync</span><span class="p">();</span>

    <span class="n">float4</span> <span class="n">left</span>  <span class="o">=</span> <span class="n">gCache</span><span class="p">[</span><span class="n">groupThreadID</span><span class="p">.</span><span class="n">x</span> <span class="o">-</span> <span class="mi">1</span><span class="p">];</span>
    <span class="n">float4</span> <span class="n">right</span> <span class="o">=</span> <span class="n">gCache</span><span class="p">[</span><span class="n">groupThreadID</span><span class="p">.</span><span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">];</span>
    <span class="err">…</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<h4 id="-주의사항">✨ 주의사항</h4>
<p>그룹 내 모든 스레드는 동시에 처리된다는 보장이 없다. 따라서 같은 메모리에 여러 스레드들이 접근하므로 울타리를 걸어준다.</p>

<p>GroupMemoryBarrierWithGroupSync() 명령어는 모든 스레드가 완료될 때까지 기다리게하는 코드이다.</p>

<h2 id="blur-구현해보기">Blur 구현해보기</h2>

<p>화면을 흐리게 만드는 블러처리를 컴퓨트 셰이더를 이용해 구현해보자. 수학적인 내용은 따로 빼서 설명하는게 좋을 것 같고, 컴퓨터 셰이더로 구현하는 부분만 간략하게 설명하겠다.</p>

<figure>
  <img src="/assets/images/chap13/directX12_chap13(2).jpg" alt="파스텔 그림" />
  <figcaption>파스텔 그림</figcaption>
</figure>

<p>어릴 때 파스텔로 그림을 그리고 손으로 비비면 더 흐릿해지는 것을 경험했을 것이다. Blur처리는 이와 같은 원리로 중앙 픽셀의 색을 주변 색과 섞어서 구현한다.</p>

<p>각 픽셀의 가중치는 Gaussian function을 사용해서 11개의 가중치값을 구하고, 가로로 한번, 세로로 한번 계산한다.</p>

<figure>
  <img src="/assets/images/chap13/DirectX12_Chap13(3).png" alt="공유 메모리" />
  <figcaption>공유 메모리 설명</figcaption>
</figure>

<p>공유메모리는 N + 2R이다. 만약에 1920*1080 화면에 가우시안 가중치가 11개라면 가로 blur를 위해 필요한 공유메모리 인덱스는 1930칸이다.</p>

<figure>
  <img src="/assets/images/chap13/DirectX12_Chap13(4).png" alt="Blur 구현 화면" />
  <figcaption>Blur 구현 화면</figcaption>
</figure>

<p>Blur처리가 잘 된것을 알 수 있다.</p>

<h2 id="문제-풀이">문제 풀이</h2>
<h3 id="1-컴퓨트-셰이더를-이용해서-sobel-operator-구현하기">1. 컴퓨트 셰이더를 이용해서 Sobel Operator 구현하기</h3>
<p>Sobel Operator은 주변 픽셀과의 색상차이를 분석해서 엣지를 추출하는 필터이다. 수식적인 차이는 있지만 구현 방식은 blur와 크게 다르지 않다.</p>

<p><span class="code-fold-name">SobelCS</span></p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><table class="rouge-table"><tbody><tr><td class="rouge-gutter gl"><pre class="lineno">1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
</pre></td><td class="rouge-code"><pre><span class="n">Texture2D</span> <span class="n">gInput</span>            <span class="o">:</span> <span class="k">register</span><span class="p">(</span><span class="n">t0</span><span class="p">);</span>
<span class="n">RWTexture2D</span><span class="o">&lt;</span><span class="n">float4</span><span class="o">&gt;</span> <span class="n">gOutput</span> <span class="o">:</span> <span class="k">register</span><span class="p">(</span><span class="n">u0</span><span class="p">);</span>

<span class="kt">float</span> <span class="nf">CalcLuminance</span><span class="p">(</span><span class="n">float3</span> <span class="n">color</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="n">dot</span><span class="p">(</span><span class="n">color</span><span class="p">,</span> <span class="n">float3</span><span class="p">(</span><span class="mf">0.299</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.587</span><span class="n">f</span><span class="p">,</span> <span class="mf">0.114</span><span class="n">f</span><span class="p">));</span>
<span class="p">}</span>

<span class="p">[</span><span class="n">numthreads</span><span class="p">(</span><span class="mi">16</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">1</span><span class="p">)]</span>
<span class="kt">void</span> <span class="n">SobelCS</span><span class="p">(</span><span class="n">int3</span> <span class="n">dispatchThreadID</span> <span class="o">:</span> <span class="n">SV_DispatchThreadID</span><span class="p">)</span>
<span class="p">{</span>
    
	<span class="n">float4</span> <span class="n">c</span><span class="p">[</span><span class="mi">3</span><span class="p">][</span><span class="mi">3</span><span class="p">];</span>
	<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">;</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">;</span> <span class="o">++</span><span class="n">j</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">int2</span> <span class="n">xy</span> <span class="o">=</span> <span class="n">dispatchThreadID</span><span class="p">.</span><span class="n">xy</span> <span class="o">+</span> <span class="n">int2</span><span class="p">(</span><span class="o">-</span><span class="mi">1</span> <span class="o">+</span> <span class="n">j</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span> <span class="o">+</span> <span class="n">i</span><span class="p">);</span>
			<span class="n">c</span><span class="p">[</span><span class="n">i</span><span class="p">][</span><span class="n">j</span><span class="p">]</span> <span class="o">=</span> <span class="n">gInput</span><span class="p">[</span><span class="n">xy</span><span class="p">];</span>
		<span class="p">}</span>
	<span class="p">}</span>

	<span class="n">float4</span> <span class="n">Gx</span> <span class="o">=</span> <span class="o">-</span><span class="mf">1.0</span><span class="n">f</span><span class="o">*</span><span class="n">c</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">-</span> <span class="mf">2.0</span><span class="n">f</span><span class="o">*</span><span class="n">c</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">-</span> <span class="mf">1.0</span><span class="n">f</span><span class="o">*</span><span class="n">c</span><span class="p">[</span><span class="mi">2</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="mf">1.0</span><span class="n">f</span><span class="o">*</span><span class="n">c</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">2</span><span class="p">]</span> <span class="o">+</span> <span class="mf">2.0</span><span class="n">f</span><span class="o">*</span><span class="n">c</span><span class="p">[</span><span class="mi">1</span><span class="p">][</span><span class="mi">2</span><span class="p">]</span> <span class="o">+</span> <span class="mf">1.0</span><span class="n">f</span><span class="o">*</span><span class="n">c</span><span class="p">[</span><span class="mi">2</span><span class="p">][</span><span class="mi">2</span><span class="p">];</span>

	<span class="n">float4</span> <span class="n">Gy</span> <span class="o">=</span> <span class="o">-</span><span class="mf">1.0</span><span class="n">f</span><span class="o">*</span><span class="n">c</span><span class="p">[</span><span class="mi">2</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">-</span> <span class="mf">2.0</span><span class="n">f</span><span class="o">*</span><span class="n">c</span><span class="p">[</span><span class="mi">2</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">-</span> <span class="mf">1.0</span><span class="n">f</span><span class="o">*</span><span class="n">c</span><span class="p">[</span><span class="mi">2</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="mf">1.0</span><span class="n">f</span><span class="o">*</span><span class="n">c</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="mf">2.0</span><span class="n">f</span><span class="o">*</span><span class="n">c</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="mf">1.0</span><span class="n">f</span><span class="o">*</span><span class="n">c</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">2</span><span class="p">];</span>

	<span class="n">float4</span> <span class="n">mag</span> <span class="o">=</span> <span class="n">sqrt</span><span class="p">(</span><span class="n">Gx</span><span class="o">*</span><span class="n">Gx</span> <span class="o">+</span> <span class="n">Gy</span><span class="o">*</span><span class="n">Gy</span><span class="p">);</span>
	<span class="n">mag</span> <span class="o">=</span> <span class="mf">1.0</span><span class="n">f</span> <span class="o">-</span> <span class="n">saturate</span><span class="p">(</span><span class="n">CalcLuminance</span><span class="p">(</span><span class="n">mag</span><span class="p">.</span><span class="n">rgb</span><span class="p">));</span>

	<span class="n">gOutput</span><span class="p">[</span><span class="n">dispatchThreadID</span><span class="p">.</span><span class="n">xy</span><span class="p">]</span> <span class="o">=</span> <span class="n">mag</span><span class="p">;</span>
<span class="p">}</span>
</pre></td></tr></tbody></table></code></pre></div></div>

<figure>
  <img src="/assets/images/chap13/DirectX12_Chap13(5).png" alt="Sobel Operator 결과 화면" />
  <figcaption>Sobel Operator 결과 화면</figcaption>
</figure>]]></content><author><name>Your Name</name></author><category term="DirectX12" /><summary type="html"><![CDATA[DirectX12의 Comput Shader에 대해 소개합니다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://smg-927.github.io/assets/images/chap13/directX12_represent.jpg" /><media:content medium="image" url="https://smg-927.github.io/assets/images/chap13/directX12_represent.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>