<?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://blog.hybrid3d.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://blog.hybrid3d.dev/" rel="alternate" type="text/html" /><updated>2026-04-03T10:20:01+09:00</updated><id>https://blog.hybrid3d.dev/feed.xml</id><title type="html">Hybrid3D</title><subtitle>Computer Graphics, Machine Learning, Quantum Computer
</subtitle><entry><title type="html">AI 시대의 프로그래머</title><link href="https://blog.hybrid3d.dev/2026-04-03-programmer-in-the-ai-era" rel="alternate" type="text/html" title="AI 시대의 프로그래머" /><published>2026-04-03T00:30:00+09:00</published><updated>2026-04-03T00:30:00+09:00</updated><id>https://blog.hybrid3d.dev/programmer-in-the-AI-Era</id><content type="html" xml:base="https://blog.hybrid3d.dev/2026-04-03-programmer-in-the-ai-era"><![CDATA[<h1 id="ai-시대의-프로그래머">AI 시대의 프로그래머</h1>

<p>우연히 어떤 분의 블로그를 봤다. 요즘 흔치 않은, 그래픽스 렌더링 기법들에 관한 글들이 잔뜩 올라온 귀한 블로그였다. 마치 막 이력서를 내면서 그동안 써두었다가 공개 안한 글들을 한꺼번에 공개하는 것으로 보여 응원하면서 글을 읽었다.</p>

<p>여러 노력들로 인한 응원의 마음은 곧 아쉬움과 안타까움으로 바뀌었다. <code class="language-plaintext highlighter-rouge">**굵은 글씨**</code> 와같이 마크 다운 문법이 그대로 노출 되는걸 포함해서 누가봐도 AI의 글을 그대로 옮겨적은 흔적들이 보였다. 분명 직접 작성한 내용들이 대부분이고 노력한 흔적은 보이나, 그 노력은 안타깝게도 “AI 복붙”으로 희석 되었다.</p>

<blockquote>
  <p><code class="language-plaintext highlighter-rouge">**글자**</code>은 마크다운(markdown) 문법으로 ‘글자’를 <strong>굵은 글씨</strong>의 <strong>‘글자’</strong>로 표시하기 위한 문법이다.
ChatGPT 같은 AI들은 강조를 할 때 마크다운으로 보여준다. 그것을 보여줄 때는 효과가 적용되어 보이지만 내용을 복사 할 때는 평문이 그대로 복사가 될 때가 있다. 구글 검색 결과에서도 가끔 마크다운 문법이 그대로 보일 때가 많다.</p>
</blockquote>

<hr />

<p>AI 가 대부분의 프로그래밍을 대신해주는 <strong>**AI 시대</strong>**에서, 생각 보다 많은 일들은 별거 아닌 일들이 됐다. 인간이 완전히 무력해지는 순간, 기계가 인간을 지배하는 시간은 오히려 고민 거리가 없다. 기계가 인간을 지배하기 전까지 그때까지 인간은 무엇을 해야할까, 당장 몇년 후 프로그래머, 소프트웨어 개발자는 무엇을 해야할까? 누군가의 말 처럼 소프트웨어 개발자라는 직군은 없어질까?</p>

<p>Jeff Pesis <sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> 라는 사람이 이런 말을 했다.</p>
<blockquote>
  <p><strong>“하드웨어란 컴퓨터에서 발로 찰 수 있는 부분이다.”</strong></p>

  <p><strong>“Hardware: the parts of a computer that can be kicked.”</strong></p>
</blockquote>

<p>다소 비약이 섞인 말이지만, 지금 막 지나가는 소프트웨어의 시대를 지나, AI 시대에서는 저 말이 다른 의미로 핵심을 찌르는 말이다. 저 발로 차이는 부분이 이제는 바로 사람의 역할이다. 대부분의 일은 AI가 하고, 문제가 있어서 발로 차이는 것은 사람이다. 이는 단순히 물리적으로 고통을 받는 부분이 아니다. 이것을 인간적인 표현으로 하자면 <strong>발로 차이는 역할</strong>이라는 것은 <strong>책임을 지는 역할</strong>이다.</p>

<p>AI로 인한 위험을 지금처럼 느끼지 못할 때, AI가 마치 나 대신 일을 해줄 때 AI에게 일을 시키고 월급 루팡을 상상하던 사람들이 제법 있었다(몇몇은 농담이 아니라 진심인듯 했다). 당연하게도 내 일을 AI가 대신 해준다면 내 고용인은 나를 쓸 필요가 없다. AI 가 하는 일을 왜 돈을 주고 AI 를 대신하게 시키겠는가. 만약 내가 AI 를 조작하는 일을 한다면 내 역할은 명확하다. 내가 AI의 일을 책임지는 것이다. 이게 잘못 됐을 때 내가 발로 차이는 것과 사실상 같은 것이다. 진짜 발로 차이진 않아도 정신적으로, 개념적으로는 같은 것이다.</p>

<p>고용주가 직접 AI를 써서 문제가 잘못되면 누굴 탓할 것인가? AI가 만든 코드가 서버를 터트리고, 아이템이나 돈 같은 재화를 복제할 때 고용주는 누구를 탓할 것인가? OpenAI? Claude? Google 같은 AI 제공자를 탓할 것인가? 그들이 책임을 져줄 것인가 하면 그렇지 않다. 책임은 AI 사용자의 몫이다. 며칠전 있었던 공영 방송사 자동 번역 사고<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">2</a></sup>를 포함해서 여러 사건 사고들을 보면 AI는 책임을 지지 않고 질 수도 없다는 것을 알 수 있다.</p>

<p>AI 시대에 프로그래머가 살아 남는 다면 어떤 프로그래머가 살아 남을까? AI의 역할을 곰곰이 생각해보면 AI 시대에도 살아남는 프로그래머는 AI를 사용하든 손 코딩을 하든 자기 결과물에 책임을 지고 <strong>결과물(product)</strong>을 내는 사람일 것이다. AI/손 코딩은 수단일 뿐이고 결국 작업물이 아닌 결과물을 내는 것이 근본이다. 물론 그 결과물을 내는 <code class="language-plaintext highlighter-rouge">사람의 수</code>는 지금 보다 적어지겠지만, 그들만이 살아 남는다.</p>

<p>John Carmack은 누군가와의 트위터 DM 대화에서 다음과 같은 말을 한적이 있다<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">3</a></sup>.</p>

<blockquote>
  <p>지금은 손 코딩이었다가 나중에 AI로 바뀌더라도 만약 제품을 만드는 전반적인 <strong>역량(product skills)</strong>를 갖추고 그때그때 가장 적절한 도구를 사용할 수 있다면 괜찮을 겁니다.</p>

  <p>If you build full “product skills” and use the best tools for the job, which today might be hand coding, but later may be Al guiding, you will probably be fine.</p>
</blockquote>

<p class="image-caption"><img data-action="zoom" src="/images/quotes/FrgaJleX0AA7kw6.jpeg" alt="" style="width: 40%;" />
그림 3. John Carmack DM</p>

<p>AI 관련해서 들었던 그 어떤 말 보다도 이 말이 가장 와 닿았다. 바꿔 말하면 프로그래머는 그냥 단순히 프로그래밍 언어를 가지고 프로그램을 짜는 사람이 아니다. 모든 훌륭한 프로그래머들은 (설령 실패를 하더라도) 결과물을 내는 사람들이다.</p>

<p>재미있는 점은 이런 특성의 프로그래머가 중요한 것은 AI가 프로그래머는 대체하지 않는 지금도 마찬가지라는 것이다. 시키는 일을 시키는 대로만 작업 완료하는 것에 목적이 있지 않고, 본인의 작업물이 결과물로써 잘 동작하도록 책임을 지고 완성 하는 사람이 일을 잘한다고 인정받는다. 지금도 소위 일 잘 하는 사람은 결과물을 내는 것으로 본인을 증명하는 것인데 생각해보면 AI 시대도 전혀 다르지 않다.</p>

<hr />

<p>이 서두에는 AI의 흔적이 남아 있는 블로그에 대한 아쉬움을 이야기 했다. AI의 도움을 받은 것이 아쉬운 것이 아니다. 사실 어떤 글을 작성할 때 그 글 하나도 내가 책임을 질 수 있다. 완벽할 필요 없다. 오히려 불완벽, 불완전함이 직접 했다는 증명이 되기도 한다. 책임을 졌다는 증거물이다. 내 글(결과물)에 AI를 쓸 것이냐, AI의 결과물에 나의 생각이 첨부 된 것이냐는 많은 것이 다르다. 그 글을 다듬고 일관성 있게 바꾸고 나의 형태로 만드는 것이 AI의 글을 쓰는 것을 내 것으로 만드는 작업의 일부이다.</p>

<p>글이라 하는 것은 쓰는 사람만의 일관성이 있다. 어투가 있고 자주 사용하는 단어와 표현이 있다. 물론 AI가 그것을 흉내낼 수 있으므로 그것 또한 감출 수는 있으나 그것을 흉내 낸다고 내 것이 되는 것은 아니다.</p>

<p>이것은 글에 국한 되지 않고 코드, 심지어 코드 주석과 커밋(메세지) 또한 마찬가지다(다른 분야도 대부분 마찬가지다). 그것을 그대로 배포하는 것과, 그것을 책임지고 테스트 하고 재정비하고, 삭제하고 첨언 하는 것, 그 모든 것이 AI의 결과물을 내 결과물로 만드는 것이다. AI 글도 써주고, 코드와 주석도 잘 써주지만 그것은 AI의 결과물이다. 사람은 사람의 결과물이 필요하다.</p>

<hr />

<p>AI 시대에 나는 무슨 일을 하게 될까? 계속 일을 할 수는 있을까 하는 고민은 이러한 과도기에서는 사라질 수 없는 골치 아픈 고민 일 것이지만 한가지는 확실하다. 내가 딸깍 하고 뭔가를 만들어냈을 때, 그걸 필요로 하는 사람이 내가 아니라면, 나의 역할은 필요 없는 것이다. 그걸 원하는 사람이 딸깍을 하는 편이 더 효율적이다.</p>

<p>간단한 웹서버 정도면 딸깍 하고 만들 수 있는 시대다. 그것이 나 혼자 필요하다면 이 딸깍으로 만드는 웹 서버는 어쩌면 이대로도 충분하지만, 수억, 수십, 수백억이 걸린 서버를 만든다면 이건 딸깍으로 만든 것을 그대로 사용할 수는 없다. 내가 서버 프로그래머라면 내가 딸깍하고 만든 서버를 검증하고 검증해서 문제가 없도록 하는 책임을 지는 것이 프로그래머로써의 역할이다.</p>

<p>글을 쓴 다는 것은 (공을 들이는 방식엔 차이가 있겠지만) 나의 생각이나 나의 결과물을 밖으로 공개하는 것이다. 이 자체로서 내 생각이라는 책임을 지는 것이다. 프로그래머로서, 직장인으로서, 혹은 사업자로서의 나는 내 결과물을 가지고 책임을 지는 사람이다. 앞으로는, 종국에는,(사기꾼이 아닌한) 책임을 지는 사람만, 책임을 질 줄 아는 사람만 살아남는 시대가 될 것이라고 조심스럽게 예측해본다.</p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>유명세에 비해 이 인용의 1차 출처는 알려지지 않았다고 한다. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>KBS 에서 자동 번역 자막을 송출 하였는데 잘못된 번역으로 욕설이 들어갔다. 그 자동 번역을 잘 못 만든 회사의 잘못일까? 아니다. 그걸 선택한 KBS의 잘못이다. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>https://x.com/ID_AA_Carmack/status/1637087219591659520 <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="general" /><summary type="html"><![CDATA[AI 시대의 프로그래머]]></summary></entry><entry><title type="html">쉐이더에서 IF 문이 느린 이유</title><link href="https://blog.hybrid3d.dev/2020-12-21-reason-for-slow-of-if-statement-in-shader" rel="alternate" type="text/html" title="쉐이더에서 IF 문이 느린 이유" /><published>2020-12-21T18:00:00+09:00</published><updated>2020-12-21T18:00:00+09:00</updated><id>https://blog.hybrid3d.dev/reason-for-slow-of-if-statement-in-shader</id><content type="html" xml:base="https://blog.hybrid3d.dev/2020-12-21-reason-for-slow-of-if-statement-in-shader"><![CDATA[<h2 id="개요">개요</h2>
<p>쉐이더에서 if 문이 성능에 좋지 않다는 것은 프로그래머가 아니더라도 많이들 알고 있는 사실이다.</p>

<p>중요한 것은 단순히 if 문이 느리다는 것이 아니다, if 문이 필요해서 if 문을 쓰는 것인데 느리다는 사실을 아는 것만으로는 별로 도움이 되지 않는다.
왜 if 문이 느린 것인지를 아는 것이 중요하고, 그것을 제대로 알아야 if 문을 우회하는데 도움이 될 수 있다<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.</p>

<p>최적화 이슈에서 이유를 잘못 알고 어떤 결과만 아는 것은 독이 될 수도 있다. 잘못된 결론을 내버리면 잘못된 최적화로 최적화가 안될 뿐 더러 오히려 더 느려질 수도 있다. 따라서 왜 느린지를 제대로 아는 것은 특히 프로그래머라면 매우 중요하다.</p>

<h2 id="gpu의-동작-방식">GPU의 동작 방식</h2>
<p>CPU의 코어는 개별적으로 도는데에 비해 GPU의 코어는 묶음으로 돈다. <strong>SIMT(Single Instruction, Multiple Threads)</strong>라는 용어가 그 특성을 잘 설명한다. GPU에서는 여러 쓰레드(코어)가 동시에 하나의 명령어를 수행한다<sup id="fnref:thread" role="doc-noteref"><a href="#fn:thread" class="footnote" rel="footnote">2</a></sup>. 일반적으로 이 묶음은 32개 혹은 64개다<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">3</a></sup>. 동시에 32개를 계산하니 빠름과 동시에 여러 단점도 존재한다. 대표적인 예가 if/else 문에 의한 <strong>분기 발산(brance divergence)</strong>이다<sup id="fnref:branch_divergence" role="doc-noteref"><a href="#fn:branch_divergence" class="footnote" rel="footnote">4</a></sup>.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 코드 1</span>
<span class="k">if</span> <span class="p">(</span><span class="n">cond</span><span class="p">)</span> <span class="c1">// line 1.</span>
<span class="p">{</span>
    <span class="n">finalColor</span> <span class="o">=</span> <span class="n">hairShading</span><span class="p">();</span> <span class="c1">// line 3. 머리카락 쉐이딩 계산 (무거움)</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
    <span class="n">finalColor</span> <span class="o">=</span> <span class="n">clothShading</span><span class="p">();</span> <span class="c1">// line 7. 옷 쉐이딩 계산 (무거움)</span>
<span class="p">}</span>
</code></pre></div></div>

<p>위의 극단적인 코드를 보자. <em>cond</em>의 상태에 따라 머리카락 쉐이딩을 계산하거나 옷 쉐이딩을 계산한다. 여기서 머리카락이면서 옷인 경우는 없다. GPU는 이런 계산을 할 때 32개(64개도 있지만 여기서는 32개로 고정하겠다) 쓰레드가 동시에 같은 명령어를 처리하는데 위에서 설명한 GPU의 분기 발산의 상황에서는 hairShading과 clothShading을 모두 호출해서 계산하게 된다. 여기서 이 두 함수는 매우 무거운 함수다.</p>

<p>구체적으로 말하면 다음과 같은 순서로 수행 된다.</p>

<ol>
  <li>cond 에 해당하는 플래그를 각 쓰레드의 레지스터에 넣는다. (line 1)</li>
  <li>hairShading 을 수행한다. 이때 위에서 저장한 cond 플래그가 <strong>true</strong> 인 경우만 finalColor 변수에 결과를 넣는다. true 가 아닌 쓰레드도 있을 텐데 그 쓰레드도 일단 동시에 계산한다. 그리고 true 아닌 쓰레드의 결과 값은 버린다. (line 3)</li>
  <li>clothShading 을 수행한다. 이때 위에서 저장한 cond 플래그가 <strong>false</strong> 인 경우만 finalColor 변수에 결과를 넣는다. 마찬가지로 true 인 쓰레드는 계산만 하고 다른 것은 버린다. (line 7)</li>
</ol>

<p>2번과 3번에서 플래그가 맞지 않은 계산은 계산은 하고 그냥 버린다. GPU는 동시에 계산을 해야하기 때문에 일단 계산은 하고 조건에 맞는 결과만 저장한다. 즉, 분기 발산에서는 <strong>모든 분기를 불필요하게 다 계산한다</strong>.
물론 CPU에서는 이럴 필요가 없다. if 문 조건에 맞아 수행을 하면 GOTO 문으로 분기를 빠져나간다. GPU에서 처럼 모든 분기를 다 수행하는 것이 분기 발산이다.</p>

<p>여기서 오해를 할 수도 있으므로 다른 코드를 보자.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 코드 2</span>
<span class="n">float4</span> <span class="n">hairShadingResult</span> <span class="o">=</span> <span class="n">hairShading</span><span class="p">();</span>
<span class="n">float4</span> <span class="n">clothShadingResult</span> <span class="o">=</span> <span class="n">clothShading</span><span class="p">();</span>

<span class="k">if</span> <span class="p">(</span><span class="n">cond</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">finalColor</span> <span class="o">=</span> <span class="n">hairShadingResult</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
    <span class="n">finalColor</span> <span class="o">=</span> <span class="n">clothShadingResult</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>비싼 계산을 if/else 안에서 하지 않고 밖에서 한 후 <strong>if/else 안에서는 대입만 했다</strong>. 이런 경우 분기 양쪽을 계산한다고 해도 대입만 하기 때문에 브랜칭이 별거 아닌 것처럼 오해할지도 모른다. 하지만 각 분기에 필요한 모든 계산을 한다는 점을 보면 이전 코드와 전혀 다를게 없어서 전혀 이득이 없다.</p>

<p>이때 어셈블리 수준에서는 if + mov 명령어가 movc 하나로 바뀌는데 어셈블리 수준에서 <strong>if + mov 명령어가 느린 것은 아니기 때문에</strong> 이 명령어 몇개 줄어든다고 차이가 성능 이점을 가져올 가능성은 사실상 없다. 오히려 분기 발산을 명확하게 만들어버리기 때문에 성능에 큰 하락을 가져온다.</p>

<p><strong>이 코드의 근본적인 문제는 hairShadingResult와 clothShadingResult의 계산 자체가 느린 것이다</strong>. 하나의 쉐이더 안에서 불필요하게 두번 하는 것이 문제다. 머리카락이면서 옷인 재질이라서 위 함수를 모두 수행해야한다면 어쩔 수 없지만 둘 중 하나만 처리해도 되는 상황에서 둘다 처리하는 것이 분기 발산의 문제점이다. 이는 실제로 성능에 굉장히 큰 영향을 준다(두 종류가 아니라 더 여러 종류라면 상황은 더 심각하다)<sup id="fnref:ue4" role="doc-noteref"><a href="#fn:ue4" class="footnote" rel="footnote">5</a></sup>.</p>

<p>참고로 코드 2번의 경우 혹시나 어떤 방식으로든 GPU가 최적화를 해줄 수도 있지 않을까 해서 최신 하드웨어에서 실제로 테스트 해보았다(RTX 3080). <strong>실제로 불필요한 분기를 계산하는 것을 확인했다.</strong></p>

<h3 id="다이나믹-브랜칭-dynamic-branching">다이나믹 브랜칭 (dynamic branching)</h3>
<p>분기 발산 자체가 문제지만 if 문을 사용한 분기가 항상 문제가 되는 것은 아니다. 만약 32개의 쓰레드의 <strong>cond 값이 모두 true 이거나 false 이면</strong> 걱정할 필요가 없다.</p>

<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="n">cond</span><span class="p">)</span> <span class="c1">// cond 는 모두 ture 이거나 false</span>
<span class="p">{</span>
    <span class="n">finalColor</span> <span class="o">=</span> <span class="n">hairShading</span><span class="p">();</span>
<span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span>
    <span class="n">finalColor</span> <span class="o">=</span> <span class="n">clothShading</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>

<p>32개의 쓰레드에 대해서 모두 cond 값이 같을 경우는 브랜칭 문제가 생기지 않는다. 가령 32개의 쓰레드의 cond 가 모두 동시에 true 이면 hairShading 만 처리하고 굳이 clothShading 은 처리 할 필요가 없다는 것을 GPU가 런타임에 알 수 있기 때문에 else 부분은 수행하지 않고 넘어간다. 이것을 <strong>다이나믹 브랜칭 (dynamic branching)</strong>이라고 부른다. 물론 이런 최적화는 처음부터 되었던건 아니다. 옛날 GPU는 cond 의 상태와 무관하게 둘다 동작했지만 지금은 똑똑하게 처리해준다<sup id="fnref:flatten" role="doc-noteref"><a href="#fn:flatten" class="footnote" rel="footnote">6</a></sup>. 참고로 이렇게 동일한 분기로 처리가 될 때 <strong>응집성(coherence)</strong>이 좋다고 말한다.</p>

<p>코드 2번은 사실상 다이나믹 브랜칭으로 효과적으로 계산 될 가능성이 사라지고 분기 발산(branch divergence)이 고정 되었기 때문에 느리다고 할 수 있다. 고성능 CPU나 미래의 GPU는 <strong>동적 분기 예측(dynamic branch prediction)</strong>을 통해 코드 2번도 효과적으로 처리할 수 있겠지만 현재(그리고 근 미래)의 GPU는 이를 아직 지원하지 않는다.</p>

<h3 id="문제의-상황">문제의 상황</h3>
<p>다이나믹 브랜칭 덕에 상황은 나아졌지만 진짜 문제가 되는 상황은 32개의 쓰레드 내에서 머리카락이거나 옷이 섞여 있을 경우다. 일반적으로 여기 예시와 같은 옷과 머리카락의 쉐이딩 처리는 매우 무겁다.
머리카락과 옷이 떨어져 있어서 섞여 있지 않다면 대체적으로 문제가 없겠지만 섞여 있는 부분이 화면에 많으면 많을 수록 심각하게 느려지는 요인이 된다. 실제로 프로파일링을 해보면 옷과 머리카락이 섞여 있는 경계 부분에 성능을 많이 잡아 먹는 것을 프로파일러를 이용하여 시각적으로도 쉽게 확인할 수 있다.</p>

<h2 id="해결-방법">해결 방법</h2>
<p>해결 방법은 생각보다 쉽지 않다. 이미 언급했듯이 if 문은 필요에 의해 쓰는 것이기 때문에 쉽게 없앨 수 있는 것은 아니다.</p>

<p>그래도 몇가지 방법이 있다.</p>

<h3 id="1-다이나믹-브랜칭을-방해하는-코드를-짜지-말-것">1. 다이나믹 브랜칭을 방해하는 코드를 짜지 말 것</h3>
<p>2번과 같은 코드를 의미한다. 2번과 같은 코드는 movc 로 어셈블리 명령어가 줄어들지만 이 성능 차이는 상대적으로 매우 무의미하고 <strong>컴파일러가 최적화 할 수 있는 것을 방해한다</strong>. 그냥 <em>if 문이 느리다</em>, <em>생각보다 빠르다</em> 같이 단순한 결론만 가지고 판단을 하면 안되고 왜 느린지를 알아야 느린 if 문을 피할 수 있다. 2번과 같은 코드는 if 문이 왜 느린지를 모르고 잘못작성 한 것이기 때문에 더 느려지는 것이다<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">7</a></sup>.</p>

<h3 id="2-가능한-공용-코드는-한번만-계산한다">2. 가능한 공용 코드는 한번만 계산한다</h3>
<p>가령 머리카락과 옷의 라이팅 계산 중 디퓨즈 계산의 경우 (처리가 동일하다면) 각 함수 안에서 하는 것 보다 밖으로 빼는게 낫다. 디퓨즈 계산 자체야 별거 안되지만 레지스터 사용량 등을 생각했을 때 굳이 안에서 할 이유는 없어보인다(물론 상황마다 다르다).
가급적 공통적인 계산은 브랜치와 무관하게 공통으로 계산하는 것이 두번째 방법이다. 하지만 이는 레지스터나 캐시의 상황에 따라 다를 수 있기 때문에 이것 역시 단순하게 판단할 부분은 아니다.</p>

<h3 id="3-쉐이더-분리-쉐이더-퍼뮤테이션">3. 쉐이더 분리 (쉐이더 퍼뮤테이션)</h3>
<p>언리얼 엔진에서는 쉐이더 퍼뮤테이션(shader permutation)이라는 용어를 사용하는데 유니티에서는 쉐이더 베리언트(shader variants)라는 용어를 사용한다. 근본적으로는 같다. 쉐이더 코드 안에서 #define 으로 하나의 코드를 여러 코드로 분리하는 것이다.</p>

<p>쉐이더를 분리한다고 여전히 간단히 해결 되는 것은 아니다. hairShading / clothShading 각각을 분리한 후 공용으로 처리하는 쉐이더를 또 따로 방법도 있고,
hairShading 과 clothShading 만 분리해서 겹치는 지역에는 각각 한번씩 돌리는 방법이 있다. 이 경우 스텐실 등을 이용해서 불필요한 경우 관련된 처리가 아예 돌아가지 않도록 보장 해야한다.
쉐이더가 두번 도는 오버헤드가 존재하지만 머리카락과 옷의 처리가 매우 무겁다면 이 오버헤드를 감안하고 성능향상이 있을 수 있다.</p>

<h2 id="마무리-및-요약">마무리 및 요약</h2>
<p>if 문에 의한 분기는 비싸다. 하지만 중요한 것은 <strong>if 문이 비싸다는 단순한 결론</strong> 보다는 왜 비싼지를 아는 것이다. <strong>if 문 자체가 비싼 것이 아니다. 양쪽 분기를 모두 수행하는 것이 문제다.</strong> 이런 과정을 좀 더 자세히 안다면 우리는 GPU를 좀 더 이해할 수 있고 코드를 잘못 작성 하는 것을 줄일 수 있다.</p>

<p>다시 정리하자면 GPU에서는 32개의 쓰레드 단위로 동시에 수행하기 때문에 분기 문에서 if/else 코드를 둘다 계산해버린다. 간단한 계산이라면 큰 의미는 없겠지만 이때 관련 된 처리가 비싸다면 불필요한 성능 하락이 있을 수 있다.</p>

<p>그래도 응집성(coherence)이 높다면 다이나믹 브랜칭으로 성능 하락은 완화 되겠지만 응집성이 낮은 경우 심각한 성능 하락을 가져올 수 있다. 따라서 여러 비싼 처리를 분기 처리할 때는 매우 조심해야한다. 이에 대한 해결 방법은 여러가지가 있지만 항상 그렇게 단순한 것은 아니기 때문에 위험성을 항상 염두하는 것이 중요하고 엄밀한 테스트를 이용해서 상황에 맞는 방법을 잘 찾아 최적화 해야한다.</p>

<h3 id="참고-문헌">참고 문헌</h3>
<ol>
  <li><a href="https://developer.nvidia.com/gpugems/gpugems2/part-iv-general-purpose-computation-gpus-primer/chapter-34-gpu-flow-control-idioms">GPU Gems 2 - Chapter 34. GPU Flow-Control Idioms</a></li>
</ol>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>if 문은 필요에 의해서 쓰는 것이기 때문에 if 문 자체를 줄일 수 있는 방법은 많지 않다. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:thread" role="doc-endnote">
      <p>비프로그래머를 위해서 비유를 들자면 여기서 쓰레드는 포스트프로세스 수행시 하나의 픽셀 계산에 해당한다고 보면 비슷하다. <a href="#fnref:thread" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>이 묶음은 NVIDIA에서는 warp 라는 단어를 사용하며 32개의 쓰레드로 구성 되어 있다. 반면 AMD는 wavefront 라는 단어를 사용하며 64개의 쓰레드다. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:branch_divergence" role="doc-endnote">
      <p>여기서 branch divergence 를 분기 발산이라는 용어로 번역했다. 의미가 정확히 전달 될 좋은 번역인지는 모르겠지만, divergence 는 일반적인 수학 용법에서는 발산으로 번역한다. <a href="#fnref:branch_divergence" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:ue4" role="doc-endnote">
      <p>언리얼 엔진 4에는 이런 코드들이 사실상 매우 많이 있다. 가급적으로 퍼뮤테이션(#define)으로 처리하기도 하지만 어쩔 수 없이 브랜칭하는 경우도 많다. 언리얼 엔진은 이 경우 브랜칭을 안고 가기로 했고 실제로 무거운 재질이 섞여 있는 경계 부분에서는 성능 하락이 제법 있다. <a href="#fnref:ue4" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:flatten" role="doc-endnote">
      <p>필요에 의해 의도적으로 양쪽 분기를 다 타게 만들 수도 있다. 그때 사용하는 HLSL 힌트 명령어는 <a href="https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-if">flatten</a>이다. 일반적으로는 필요 없지만 리소스 바인딩 관련된 이슈로 종종 필요할 수 있다. <a href="#fnref:flatten" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>상황에 따라 다이나믹 브랜칭 보다 분기 발산이 더 나은 경우도 있다. 그것은 테스트 후 다음 기회에 다시 다루도록 하겠다. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="computer-grahics" /><category term="shader" /><category term="optimization" /><summary type="html"><![CDATA[개요 쉐이더에서 if 문이 성능에 좋지 않다는 것은 프로그래머가 아니더라도 많이들 알고 있는 사실이다.]]></summary></entry><entry><title type="html">0과 1 사이의 PBR 메탈릭</title><link href="https://blog.hybrid3d.dev/2020-06-03-metallic-between-0-and-1" rel="alternate" type="text/html" title="0과 1 사이의 PBR 메탈릭" /><published>2020-06-03T23:30:00+09:00</published><updated>2020-06-03T23:30:00+09:00</updated><id>https://blog.hybrid3d.dev/metallic_between_0_and_1</id><content type="html" xml:base="https://blog.hybrid3d.dev/2020-06-03-metallic-between-0-and-1"><![CDATA[<p>PBR에서 메탈릭은 0과 1로만 두는 것이 정법이다. 0이나 1사이 값을 사용하지 말라는 가이드를 하기도 하는데 현실적으로나 물리적으로나 그렇게 옳은 가이드라고만 하긴 힘들다.
메탈릭을 무분별하게 0과 1사이의 값을 사용하는 것은 문제가 있지만 메탈릭을 0 혹은 1으로만 제한을 둘 경우에도 여러 현실적인 한계에 부딫힌다. 여기서는 메탈릭 값에 대한 현실적인 의미와 물리적인 의미를 통해 몇가지 메탈릭에 관한 사용법을 다뤄보겠다.</p>

<p>글은 세 파트로 두었다.</p>

<ol>
  <li>메탈릭을 0 혹은 1로만 두는 이유</li>
  <li>실시간 PBR의 한계</li>
  <li><strong>블렌딩과 마이크로 픽셀</strong></li>
  <li>마무리 및 권장 사용법</li>
</ol>

<p>3번이 이 글에서 강조하고 싶은 내용이다.</p>

<h2 id="1-메탈릭을-0-혹은-1로만-두는-이유">1. 메탈릭을 0 혹은 1로만 두는 이유</h2>

<p>먼저 메탈릭을 0 혹은 1로 제한을 거는 이유부터 살펴보자. 이러한 제약의 이유를 가장 원론적으로 말하자면 실제로 금속 재질은 완전히 금속이거나 금속이 아니거나 하는 두 종류만 존재하기 때문이다. 반만 금속인 건 일반적으로 없다<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.</p>

<p>비금속의 경우 메탈릭은 그냥 0으로 두는 것이 정석이다. 하지만 비금속에 메탈릭을 살짝 올릴 경우는 아티스트 손에서 흔하게 발생한다. 아티스트가 메탈릭을 올리는 이유는 대부분 스페큘러가 강해지고 더 좋게 보이기 때문이다. 사용할 수 있는 폴리곤 수의 제약, 실시간 GI의 한계로 밋밋해 보이는 물체(PBR을 따른 물체는 대체적으로 밋밋해보인다)를 입체감있게 만들기에는 메탈릭을 사용하는 것은 유혹적인 방식이다<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>.</p>

<p>게임은 어차피 가상의 광원을 많이 추가하기도 하니 단순히 스페큘러만 강하게 보인다면 큰 문제가 없을 수도 있다.
물체의 음영이 커져서 더 괜찮아보이는 착각이 생길 수도 있는데 메탈릭이 들어간다는건 디퓨즈 성분이 줄어든다는 의미고, <strong>이것은 결국 (디퓨즈) GI가 어두워지는 결과로 이어진다.</strong> 좀 더 쉽게 말해서 비금속에 메탈릭을 올려버리면 어두운 곳에서 지나치게 어두워질 수 있다. 또한 상대적으로 강해진 스페큘러 때문에 베이스 컬러를 약하게 주는 경우로 이어지기도 한다. 다시 말해 메탈릭을 꼼수로 올린다면 다른 꼼수로 메꿔야 하는 상황이 생기고 이러한 꼼수만으로는 모든 상황을 다 대처하진 못하기 때문에 예상치 못하는 문제가 생길 수 있다.</p>

<p>그러면 메탈릭은 0이나 1로만 두고 그 사이 값은 절대 사용하면 안되는 것일까? 프로그래머 입장에서 아티스트에게 그렇게 가이드하면 해결 되는 것일까? 물론 그렇지 않다. 만약 그랬다면 UE4 같은 엔진에서 굳이 0과 1사이의 값을 넣도록 하지 않았을 것이다. 실제로 0과 1사이의 값을 사용하는 것이 때로 <strong>물리적으로 맞기도 하다.</strong> 그것을 설명하는 것이 이 글의 진짜 취지다.</p>

<h2 id="2-실시간-pbr의-한계">2. 실시간 PBR의 한계</h2>

<p>일단 실시간 PBR은 완벽하지가 않다. 현실적으로 몇가지 제약들이 많이 숨어 있다. 여기서는 다이아몬드를 예로 들겠다. 다이아몬드는 비금속인데 (UE4 기준으로 보면) 물리적으로 제대로 표현할 수 없다.</p>

<p>첫번째 한계는 (UE4 기준으로) 반사율을 올바르게 표현할 수 없다는 점이다. 다이아몬드는 굴절률(refractive index)이 2.42 정도 된다.</p>

<p>UE4에서 반사율은 머티리얼 노드에서 specular 핀 값에 0~1의 값을 넣는 것으로 조절하는데, 스페큘러 0은 0% 반사를 의미하고, 스페큘러 0.5(기본값)는 4%의 반사율, 스페큘러 1은 8%의 반사율을 의미한다. 다이아몬드는 17%의 반사율을 지니므로 스페큘러 값이 2.15가 되어야 한다(참고: <a href="/2017-07-05-meaning-of-specular-0-5">언리얼 엔진의 PBR 스페큘러 기본값 0.5의 의미</a>). 이것은 UE4 에서 입력할 수 있는 메탈릭 범위를 넘어간다.</p>

<p>두번째 한계는 반투명의 한계다. 보석은 반사율이 굉장히 높아보이는데 위에서 언급한대로 17% 밖에 안된다. 그럼에도 불구하고 반짝이고 반사를 잘하는 것처럼 보이는 이유는 투과를 같이 하기 때문이다. 투과를 한 것이 일부는 반사를 하고 일부는 또 투과를 하니 결과적으로 반사율이 높아 보이는 것이다. 이런걸 전부 표현할 수 없는 이상 다이아몬드를 비금속으로 두고 정법으로 풀기에는 다소 한계가 있다. 제대로 된 투과와 굴절을 표현할 수 없을 바에야 차라리 메탈릭을 통해 반사율을 높이는게 다이아몬드를 표현하기에 더 좋을 수도 있다.</p>

<p>그렇다면 실시간 PBR로 풀 수 없는 물체에만 예외로 두면 될까 하는 물음을 하게 된다. 그 역시 그렇지 않다. PBR로 풀 수 있는 보편적인 물체에 대해서도 0과 1사이의 메탈릭을 사용하는 상황이 생긴다.</p>

<h2 id="3-블렌딩과-서브-픽셀">3. 블렌딩과 서브 픽셀</h2>

<p>금속의 경우 현실에서 100% 금속만 ‘보이는’ 경우는 무척 드물다. 순수 100% 금속일지라 하더라도 현실에서는 녹이 슬거나 먼지가 쌓인다.</p>

<p class="image-caption"><img data-action="zoom" src="/images/metallic/metal-1685908_1920.jpg" alt="" style="" />
그림 1. 녹슨 금속 <br />출처: <a href="https://pixabay.com/photos/metal-rust-background-iron-rusty-1685908/">Pixbay by wilhei</a></p>

<p>위와 같이 녹이 슨 금속이 있다. 녹은 금속이 아니고 녹이 슬지 않은 부분은 금속이니 우리가 무한정 정교한 텍스쳐를 만든다고 하면 비금속과 금속을 거의 완벽히 구분할 수 있다. 그런 상황에서는 메탈릭은 0과 1로 나눌 수 있다.</p>

<!--  -->
<p><img src="images/metallic/rust-metal_1.jpg" alt="그림" /></p>

<!-- 그림 2. (실제로는 불가능하지만) 무한히 확대할 수 있다면 픽셀 단위로 금속과 비금속을 분리할 수 있다. -->
<p class="image-caption"><img src="images/metallic/rust-metal_2.png" alt="그림" /></p>

<p class="image-caption">그림 2. (실제로는 불가능하지만) 무한히 확대할 수 있다면 픽셀 단위로 금속과 비금속을 분리할 수 있다.</p>

<p>한없이 가까이서 본다면 비금속과 금속을 구분할 수 있다고 해도 렌더링 세계에서는 해상도의 한계란게 존재한다(화면의 해상도와 텍스쳐의 해상도 모두에 대해 해당 되는 이야기다).
아래의 그림의 경우 4개의 사각형이 한개의 픽셀(텍셀)에 대응 한다.</p>

<!-- 그림 3. 하나의 픽셀이 (가상의) 4개의 서브 픽셀에 해당한다.\\
왼쪽 그림에는 네개의 픽셀(텍셀)이 있고, 그 중 왼쪽 상단 픽셀을 확대한 것이 오른쪽 그림이다.\\
왼쪽: 4개의 픽셀(각 픽셀은 4등분 되어있다), 오른쪽: 4등분 된 하나의 픽셀(텍셀) -->
<p><img src="images/metallic/rust-metal_3.png" alt="그림" /></p>

<p class="image-caption">그림 3. 하나의 픽셀이 (가상의) 4개의 서브 픽셀에 해당한다.<br />
왼쪽 그림에는 네개의 픽셀(텍셀)이 있고, 그 중 왼쪽 상단 픽셀을 확대한 것이 오른쪽 그림이다.<br />
왼쪽: 4개의 픽셀(각 픽셀은 4등분 되어있다), 오른쪽: 4등분 된 하나의 픽셀(텍셀)</p>

<p>스크린의 픽셀 (혹은 텍스쳐의 텍셀) 기준으로 한 단계 멀리 떨어져서 본다면 위의 4개의 서브 픽셀 당 하나의 픽셀로 생각해야한다. 이때 픽셀은 25% 금속(회색)이고 75%는 비금속(갈색)이다. 이런 상황에서 렌더링은 메탈릭을 0.25로 두는 것이 <strong>물리적으로 올바르다.</strong></p>

<p>다시 말해 해상도는 한계라는게 존재하기 때문에 한없이 구분할 수 없으므로 하나의 픽셀에 몇 퍼센트의 금속이 들어있느냐를 메탈릭으로 둔다. 이러니 당연히 0 과 1 사이의 값이 나오는 것이 자연스럽다.</p>

<p>금속 표면 중 일부만 녹으로 변한 상황 말고 금속에 먼지가 쌓이는 상황을 생각해보자. 아무리 금속이 있다고 해도 시간이 어느정도 지나면 먼지가 쌓이기 마련이다. 금속 가루가 떠다니는게 아니라면 일반적으로 이 먼지는 비금속이다. 금속 위 먼지 역시 무한정 고해상도인 상황에서는 먼지와 금속을 완벽하게 나눌 수 있겠다. 하지만 조금만 멀리 떨어져보면 금속 전체가 흐릿하게 보인다. 육안으로는 금속과 비금속의 입자를 구분할 수 없다. 이 경우는 그냥 금속 자체의 메탈릭을 낮추는 것이 자연스러워 보일 뿐더러 <strong>물리적으로 올바르다.</strong></p>

<h2 id="4-마무리-및-권장-사용법">4. 마무리 및 권장 사용법</h2>

<p>근본적인 물음은 0 혹은 1이 아닌 중간 값의 메탈릭을 사용하는 것이 물리적으로 올바른가이다.</p>

<ol>
  <li>첫번째로, 물리적으로 올바르지 않더라도 어쩔 수 없이 사용할 수 밖에 없는 경우가 제법 존재하고(다이아몬드의 예)</li>
  <li>두번째로는 상당히 많은 상황에서는 중간 값도 물리적으로 올바르다.</li>
</ol>

<p>그럼 어떤 경우에는 0과 1사이의 메탈릭 값을 사용해도 좋을까?</p>

<ol>
  <li>사용하는 엔진의 실시간 PBR 한계에서 원하는 표현을 정말 할 수 없을 때는 비물리적인 값을 사용할 수 밖에 없을 때가 존재한다.</li>
  <li>금속의 메탈릭 값의 경우 약간 낮추는건 좋은 표현이다. 금속 표면에 비금속 성분이 뿌려지는건 일반적인 상황이다. 오히려 먼지 없이 100% 금속 성분만 보이는 건 엄밀하게 보면 그다지 현실적이지 않다<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>.</li>
  <li>비금속 재질에 메탈릭 값을 높이는건 권장하지 않는다. 비금속 표면에 금속 가루를 뿌린다면 모르겠다. 그렇게 명백하게 금속성을 생각할 수 있는 상황이 아니라면 비금속 메탈릭 값을 올리는 것은 권장하지 않는다.</li>
  <li>비금속 재질이 금속 재질과 블렌딩 되는 경우는 부드럽게 만들어주는 것이 자연스럽고 물리적으로 올바른 표현이다.</li>
</ol>

<h3 id="41-여담">4.1. 여담</h3>
<p>이 주제는 지금 UE5로 핫한 REYES(render everything your eyes saw)와 마이크로 폴리곤에 관해서도 이야기를 이어갈 수 있다. 쉽게 말하면 위에 말한 25%의 비금속, 75%의 금속의 경우 하나의 픽셀을 4개의 마이크로 폴리곤으로 나누고 각각 금속 비금속에 대해서 렌더링을 하고 4개의 결과를 하나의 픽셀로 안티얼라이어싱 하듯이 합치면 된다. 이 주제는 나의 최근 관심사인데 나중에 다시 다루도록 하겠다.</p>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>일반적으로 없다고 표현한 이유는 있긴 있다는 뜻이다. 준금속(metalloid)은 금속과 비금속으로 딱 잘라서 분류할 수가 없다. 준금속의 예로는 저마늄(germanium)과 실리콘(silcon) 등이 있다. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>아티스트가 메탈릭을 사용하는 또 다른 예로는 천 재질이다. 실제로 옷감(cloth)의 BRDF 계산은 메탈릭을 약간 올린 것으로 흉내 낼 수 있을 정도로 어느정도 흡사한면이 있는 것은 사실이다. 이 경우 옷감 BRDF를 사용하거나 (성능 등의 이유로) 메탈릭을 부분적으로 허용하는 방식으로 해결하면 되겠다. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>렌더링 계산 식에 따라 금속의 메탈릭이 1인 경우는 디퓨즈 라이팅이 0%가 된다. 스페큘러 계산의 한계에 따라 이는 다소 아쉬울 때가 존재한다. 약간 낮추어 어느정도 디퓨즈의 느낌을 주는 것도 좋을 뿐더러 실제로 메탈릭을 약간 낮추면 세월이 지난 금속의 느낌이 매우 쉽게 나타나기도 한다. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="computer-graphics" /><category term="pbr" /><summary type="html"><![CDATA[PBR에서 메탈릭은 0과 1로만 두는 것이 정법이다. 0이나 1사이 값을 사용하지 말라는 가이드를 하기도 하는데 현실적으로나 물리적으로나 그렇게 옳은 가이드라고만 하긴 힘들다. 메탈릭을 무분별하게 0과 1사이의 값을 사용하는 것은 문제가 있지만 메탈릭을 0 혹은 1으로만 제한을 둘 경우에도 여러 현실적인 한계에 부딫힌다. 여기서는 메탈릭 값에 대한 현실적인 의미와 물리적인 의미를 통해 몇가지 메탈릭에 관한 사용법을 다뤄보겠다. 글은 세 파트로 두었다. 메탈릭을 0 혹은 1로만 두는 이유 실시간 PBR의 한계 블렌딩과 마이크로 픽셀 마무리 및 권장 사용법 3번이 이 글에서 강조하고 싶은 내용이다. 1. 메탈릭을 0 혹은 1로만 두는 이유 먼저 메탈릭을 0 혹은 1로 제한을 거는 이유부터 살펴보자. 이러한 제약의 이유를 가장 원론적으로 말하자면 실제로 금속 재질은 완전히 금속이거나 금속이 아니거나 하는 두 종류만 존재하기 때문이다. 반만 금속인 건 일반적으로 없다1. 비금속의 경우 메탈릭은 그냥 0으로 두는 것이 정석이다. 하지만 비금속에 메탈릭을 살짝 올릴 경우는 아티스트 손에서 흔하게 발생한다. 아티스트가 메탈릭을 올리는 이유는 대부분 스페큘러가 강해지고 더 좋게 보이기 때문이다. 사용할 수 있는 폴리곤 수의 제약, 실시간 GI의 한계로 밋밋해 보이는 물체(PBR을 따른 물체는 대체적으로 밋밋해보인다)를 입체감있게 만들기에는 메탈릭을 사용하는 것은 유혹적인 방식이다2. 게임은 어차피 가상의 광원을 많이 추가하기도 하니 단순히 스페큘러만 강하게 보인다면 큰 문제가 없을 수도 있다. 물체의 음영이 커져서 더 괜찮아보이는 착각이 생길 수도 있는데 메탈릭이 들어간다는건 디퓨즈 성분이 줄어든다는 의미고, 이것은 결국 (디퓨즈) GI가 어두워지는 결과로 이어진다. 좀 더 쉽게 말해서 비금속에 메탈릭을 올려버리면 어두운 곳에서 지나치게 어두워질 수 있다. 또한 상대적으로 강해진 스페큘러 때문에 베이스 컬러를 약하게 주는 경우로 이어지기도 한다. 다시 말해 메탈릭을 꼼수로 올린다면 다른 꼼수로 메꿔야 하는 상황이 생기고 이러한 꼼수만으로는 모든 상황을 다 대처하진 못하기 때문에 예상치 못하는 문제가 생길 수 있다. 그러면 메탈릭은 0이나 1로만 두고 그 사이 값은 절대 사용하면 안되는 것일까? 프로그래머 입장에서 아티스트에게 그렇게 가이드하면 해결 되는 것일까? 물론 그렇지 않다. 만약 그랬다면 UE4 같은 엔진에서 굳이 0과 1사이의 값을 넣도록 하지 않았을 것이다. 실제로 0과 1사이의 값을 사용하는 것이 때로 물리적으로 맞기도 하다. 그것을 설명하는 것이 이 글의 진짜 취지다. 2. 실시간 PBR의 한계 일단 실시간 PBR은 완벽하지가 않다. 현실적으로 몇가지 제약들이 많이 숨어 있다. 여기서는 다이아몬드를 예로 들겠다. 다이아몬드는 비금속인데 (UE4 기준으로 보면) 물리적으로 제대로 표현할 수 없다. 첫번째 한계는 (UE4 기준으로) 반사율을 올바르게 표현할 수 없다는 점이다. 다이아몬드는 굴절률(refractive index)이 2.42 정도 된다. UE4에서 반사율은 머티리얼 노드에서 specular 핀 값에 0~1의 값을 넣는 것으로 조절하는데, 스페큘러 0은 0% 반사를 의미하고, 스페큘러 0.5(기본값)는 4%의 반사율, 스페큘러 1은 8%의 반사율을 의미한다. 다이아몬드는 17%의 반사율을 지니므로 스페큘러 값이 2.15가 되어야 한다(참고: 언리얼 엔진의 PBR 스페큘러 기본값 0.5의 의미). 이것은 UE4 에서 입력할 수 있는 메탈릭 범위를 넘어간다. 두번째 한계는 반투명의 한계다. 보석은 반사율이 굉장히 높아보이는데 위에서 언급한대로 17% 밖에 안된다. 그럼에도 불구하고 반짝이고 반사를 잘하는 것처럼 보이는 이유는 투과를 같이 하기 때문이다. 투과를 한 것이 일부는 반사를 하고 일부는 또 투과를 하니 결과적으로 반사율이 높아 보이는 것이다. 이런걸 전부 표현할 수 없는 이상 다이아몬드를 비금속으로 두고 정법으로 풀기에는 다소 한계가 있다. 제대로 된 투과와 굴절을 표현할 수 없을 바에야 차라리 메탈릭을 통해 반사율을 높이는게 다이아몬드를 표현하기에 더 좋을 수도 있다. 그렇다면 실시간 PBR로 풀 수 없는 물체에만 예외로 두면 될까 하는 물음을 하게 된다. 그 역시 그렇지 않다. PBR로 풀 수 있는 보편적인 물체에 대해서도 0과 1사이의 메탈릭을 사용하는 상황이 생긴다. 3. 블렌딩과 서브 픽셀 금속의 경우 현실에서 100% 금속만 ‘보이는’ 경우는 무척 드물다. 순수 100% 금속일지라 하더라도 현실에서는 녹이 슬거나 먼지가 쌓인다. 그림 1. 녹슨 금속 출처: Pixbay by wilhei 위와 같이 녹이 슨 금속이 있다. 녹은 금속이 아니고 녹이 슬지 않은 부분은 금속이니 우리가 무한정 정교한 텍스쳐를 만든다고 하면 비금속과 금속을 거의 완벽히 구분할 수 있다. 그런 상황에서는 메탈릭은 0과 1로 나눌 수 있다. 그림 2. (실제로는 불가능하지만) 무한히 확대할 수 있다면 픽셀 단위로 금속과 비금속을 분리할 수 있다. 한없이 가까이서 본다면 비금속과 금속을 구분할 수 있다고 해도 렌더링 세계에서는 해상도의 한계란게 존재한다(화면의 해상도와 텍스쳐의 해상도 모두에 대해 해당 되는 이야기다). 아래의 그림의 경우 4개의 사각형이 한개의 픽셀(텍셀)에 대응 한다. 그림 3. 하나의 픽셀이 (가상의) 4개의 서브 픽셀에 해당한다. 왼쪽 그림에는 네개의 픽셀(텍셀)이 있고, 그 중 왼쪽 상단 픽셀을 확대한 것이 오른쪽 그림이다. 왼쪽: 4개의 픽셀(각 픽셀은 4등분 되어있다), 오른쪽: 4등분 된 하나의 픽셀(텍셀) 스크린의 픽셀 (혹은 텍스쳐의 텍셀) 기준으로 한 단계 멀리 떨어져서 본다면 위의 4개의 서브 픽셀 당 하나의 픽셀로 생각해야한다. 이때 픽셀은 25% 금속(회색)이고 75%는 비금속(갈색)이다. 이런 상황에서 렌더링은 메탈릭을 0.25로 두는 것이 물리적으로 올바르다. 다시 말해 해상도는 한계라는게 존재하기 때문에 한없이 구분할 수 없으므로 하나의 픽셀에 몇 퍼센트의 금속이 들어있느냐를 메탈릭으로 둔다. 이러니 당연히 0 과 1 사이의 값이 나오는 것이 자연스럽다. 금속 표면 중 일부만 녹으로 변한 상황 말고 금속에 먼지가 쌓이는 상황을 생각해보자. 아무리 금속이 있다고 해도 시간이 어느정도 지나면 먼지가 쌓이기 마련이다. 금속 가루가 떠다니는게 아니라면 일반적으로 이 먼지는 비금속이다. 금속 위 먼지 역시 무한정 고해상도인 상황에서는 먼지와 금속을 완벽하게 나눌 수 있겠다. 하지만 조금만 멀리 떨어져보면 금속 전체가 흐릿하게 보인다. 육안으로는 금속과 비금속의 입자를 구분할 수 없다. 이 경우는 그냥 금속 자체의 메탈릭을 낮추는 것이 자연스러워 보일 뿐더러 물리적으로 올바르다. 4. 마무리 및 권장 사용법 근본적인 물음은 0 혹은 1이 아닌 중간 값의 메탈릭을 사용하는 것이 물리적으로 올바른가이다. 첫번째로, 물리적으로 올바르지 않더라도 어쩔 수 없이 사용할 수 밖에 없는 경우가 제법 존재하고(다이아몬드의 예) 두번째로는 상당히 많은 상황에서는 중간 값도 물리적으로 올바르다. 그럼 어떤 경우에는 0과 1사이의 메탈릭 값을 사용해도 좋을까? 사용하는 엔진의 실시간 PBR 한계에서 원하는 표현을 정말 할 수 없을 때는 비물리적인 값을 사용할 수 밖에 없을 때가 존재한다. 금속의 메탈릭 값의 경우 약간 낮추는건 좋은 표현이다. 금속 표면에 비금속 성분이 뿌려지는건 일반적인 상황이다. 오히려 먼지 없이 100% 금속 성분만 보이는 건 엄밀하게 보면 그다지 현실적이지 않다3. 비금속 재질에 메탈릭 값을 높이는건 권장하지 않는다. 비금속 표면에 금속 가루를 뿌린다면 모르겠다. 그렇게 명백하게 금속성을 생각할 수 있는 상황이 아니라면 비금속 메탈릭 값을 올리는 것은 권장하지 않는다. 비금속 재질이 금속 재질과 블렌딩 되는 경우는 부드럽게 만들어주는 것이 자연스럽고 물리적으로 올바른 표현이다. 4.1. 여담 이 주제는 지금 UE5로 핫한 REYES(render everything your eyes saw)와 마이크로 폴리곤에 관해서도 이야기를 이어갈 수 있다. 쉽게 말하면 위에 말한 25%의 비금속, 75%의 금속의 경우 하나의 픽셀을 4개의 마이크로 폴리곤으로 나누고 각각 금속 비금속에 대해서 렌더링을 하고 4개의 결과를 하나의 픽셀로 안티얼라이어싱 하듯이 합치면 된다. 이 주제는 나의 최근 관심사인데 나중에 다시 다루도록 하겠다. 일반적으로 없다고 표현한 이유는 있긴 있다는 뜻이다. 준금속(metalloid)은 금속과 비금속으로 딱 잘라서 분류할 수가 없다. 준금속의 예로는 저마늄(germanium)과 실리콘(silcon) 등이 있다. &#8617; 아티스트가 메탈릭을 사용하는 또 다른 예로는 천 재질이다. 실제로 옷감(cloth)의 BRDF 계산은 메탈릭을 약간 올린 것으로 흉내 낼 수 있을 정도로 어느정도 흡사한면이 있는 것은 사실이다. 이 경우 옷감 BRDF를 사용하거나 (성능 등의 이유로) 메탈릭을 부분적으로 허용하는 방식으로 해결하면 되겠다. &#8617; 렌더링 계산 식에 따라 금속의 메탈릭이 1인 경우는 디퓨즈 라이팅이 0%가 된다. 스페큘러 계산의 한계에 따라 이는 다소 아쉬울 때가 존재한다. 약간 낮추어 어느정도 디퓨즈의 느낌을 주는 것도 좋을 뿐더러 실제로 메탈릭을 약간 낮추면 세월이 지난 금속의 느낌이 매우 쉽게 나타나기도 한다. &#8617;]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.hybrid3d.dev/images/metallic/metal-1685908_1920.jpg" /><media:content medium="image" url="https://blog.hybrid3d.dev/images/metallic/metal-1685908_1920.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">pow(x, y)와 x의 y승의 차이</title><link href="https://blog.hybrid3d.dev/2020-03-08-differences-between-pow-and-2-2" rel="alternate" type="text/html" title="pow(x, y)와 x의 y승의 차이" /><published>2020-03-08T00:00:00+09:00</published><updated>2020-03-08T00:00:00+09:00</updated><id>https://blog.hybrid3d.dev/differences-between-pow-and-2-2</id><content type="html" xml:base="https://blog.hybrid3d.dev/2020-03-08-differences-between-pow-and-2-2"><![CDATA[<p>이 글에선 수학 함수의 사용에 대한 주의점을 다루려고 한다. pow 함수를 통해 실제 문제가 되는 사례를 중심으로 수학 함수를 다룰 때 함수가 정의하는 영역(정의역/공역)을 다루는 것이 중요하다는 것을 알아본다.</p>

<p><strong>참고로 이 내용은 GPU 기준으로 시작한다.</strong> CPU도 근본적으로는 비슷한 문제가 있지만 라이브러리나 컴파일러가 좀 더 똑똑하게 처리하기 때문에 걱정을 덜 해도 된다. 좀 더 정확히 말하자면 단순히 GPU/CPU의 차이라기보다는 프로그래밍 언어와 컴파일러의 환경에서 나오는 차이고 CPU에서는 똑똑하게 처리할 여지가 더 크다.</p>

<h1 id="powx-2와-x의-제곱">pow(x, 2)와 x의 제곱</h1>
<p>pow(x, y)은 x의 y 승을 나타내는 수학 함수다. 가령 x 와 y가 2라면 pow(2, 2)는 4를 나타내고, $2^2$도 4를 나타내니 이 둘은 같다고 할 수 있다.
하지만 pow 함수는 그렇게 호락호락한 함수가 아니라서 주의를 필요로 한다. 예를 들어 -2의 제곱을 계산하려고 한다면 어떨까?</p>

<p>$(-2)^2$은 4고 직관적으로라면 컴퓨터는 이 정도의 계산은 무리 없이 해야 한다. 하지만 그렇지 않다.</p>

<p>수학적으로 x의 2승은 $x\times x$과 같다. 만약 pow(-2, 2)를 수행하는 컴파일러가 2승이 정수라는 것을 눈치채고 똑똑하게도 $(-2)\times (-2)$ 로 바꿔준다면 아무런 문제가 없다.
하지만 컴파일러가 그렇지 못하다면(이러한 동작 방식은 컴파일러마다 다르다) 이 계산은 조금 복잡해지고 결과적으로 NaN(Not a Number)이 된다.
알다시피 NaN이 나오면 그 계산은 오염되어 그 이후의 계산이 모두 망가진다.</p>

<p>그럼 왜 pow(-2, 2)가 NaN이 되는 것일까? 결론부터 말하자면 pow의 x는 음수를 입력으로 받지 않는다는 것이고 음수를 입력으로 받지 않는 건 pow 함수의 계산 방식 때문에 그렇다.</p>

<h1 id="한계">한계</h1>
<p>당연한 말이지만 컴퓨터는 덧셈 하나도 마음껏 하지 못한다. 일반적으로 어느 이상 숫자가 올라가 해당 자료형이 담을 수 있는 숫자를 넘기면 오버플로우(overflow)가 발생한다. 뺄셈도 마찬가지고, 곱셈, 나눗셈도 각자의 여러 난항이 있다.</p>

<p>그래도 사칙 연산 정도는 CPU/GPU의 어셈블리 단에서 명령어 처리가 가능하다. 그 명령어 처리가 어떻게 가능한지는 흔히 말하는 논리 회로의 영역이다. 하지만 현실적으로 논리 회로로 모든 수학 함수를 다 나타낼 순 없다. 그중에서도 pow 함수는 의외로 다루는 영역(정의역/공역)이 넓은 편인데다 허수가 나오기도 하여 그렇게 쉽게 계산 할 수 있는 것은 아니다.</p>

<p>pow 함수는 일반적으로는 아래의 공식을 따른다.</p>

\[x^y = 2^{y\log_2 x}\]

<p>더 복잡한 수식이 나와버린 듯 하지만 컴퓨터가 발전하면서 $2^x$ 를 구하는 함수, $log_2 x$ 를 구하는 함수 정도는 어셈블리 명령어로 만들어 두었기 때문에
곱하기, $2^x$, $log_2 x$ 를 이용하면 일반적인 pow 함수를 계산할 수 있다<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.</p>

<h1 id="제약">제약</h1>

<p>\[x^y = 2^{y\log_2 x}\]</p>

<p>이 공식에는 원래 의도와는 다른 제약이 들어간다. $2^x $ 의 경우 모든 실수를 입력으로 받지만 log 함수의 x의 정의역은 실수 전체가 아니라 0보다 큰 실수이다. x가 0이거나 0보다 작은 경우는 정의되지 않아서 넣으면 안된다<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>. 그러한 입력에는 <strong>NaN</strong>이 출력 되는 것이다.</p>

<p>하지만 x가 0이고 y 가 0보다 큰 경우의 $x^y$는 수학적으로 0이라서 NaN으로 두기 아깝다. 이런 경우는 분기문을 이용하면 어렵지 않게 처리할 수 있다. 실제로 pow 함수 내부에서는 분기 처리로 이러한 문제를 우회한다. 이러한 상황은 HLSL(High Level Shader Language)의 pow 함수의 스펙을 살펴보면 좀 더 명확해진다(<a href="https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-pow" title="pow">참고 1</a>).</p>

<table class="table table-bordered">
  <thead>
    <tr>
      <th style="text-align: center">X</th>
      <th style="text-align: center">Y</th>
      <th style="text-align: center">결과</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: center">&lt;0</td>
      <td style="text-align: center">any</td>
      <td style="text-align: center">NaN</td>
    </tr>
    <tr>
      <td style="text-align: center">&gt;0</td>
      <td style="text-align: center">==0</td>
      <td style="text-align: center">1</td>
    </tr>
    <tr>
      <td style="text-align: center">==0</td>
      <td style="text-align: center">&gt;0</td>
      <td style="text-align: center">0</td>
    </tr>
    <tr>
      <td style="text-align: center">==0</td>
      <td style="text-align: center">&lt;0</td>
      <td style="text-align: center">$\infty$</td>
    </tr>
    <tr>
      <td style="text-align: center">&gt;0</td>
      <td style="text-align: center">&lt;0</td>
      <td style="text-align: center">$1/x^{-y}$</td>
    </tr>
    <tr>
      <td style="text-align: center">==0</td>
      <td style="text-align: center">==0</td>
      <td style="text-align: center">0 or NaN (GPU마다 다름)</td>
    </tr>
  </tbody>
</table>

<p>GPU 어셈블리를 보면 분기 과정이 더 명확해지겠지만 그 부분은 생략하겠다.</p>

<h1 id="컴파일러간-차이">컴파일러간 차이</h1>
<p>C++의 CPU의 pow 함수는 훨씬 더 쓰기 편하다. pow(x, y)에서 y가 정수면(float 형이더라도) 알아서 문제가 없도록 처리해준다(gcc 9.2 기준). 예를 들어 pow(-2.0f, 2.0f) 같은 것도 알아서 $(-2)\times (-2)=4$를 계산해준다(<a href="https://en.cppreference.com/w/cpp/numeric/math/pow" title="std::pow">참고 2</a>).</p>

<p>이보다 조금은 나은 방법은 pow(-2, 2) 형태로 정수를 쓰는 것이다.
이 경우 컴파일러가 $(-2)\times (-2)$로 치환을 해주어 많은 어셈블리 명령어를 절약할 수도 있다(CPU에 해당하는 이야기다).
물론 가장 좋은건 함수에 넣기 전 abs(-x) 형태를 취하거나 처음부터 $(-2)\times (-2)$로 쓰는 것이다.</p>

<p>이처럼 GPU와 CPU는 근본적인 환경 차이 때문에 동작이 제법 많이 다르다. CPU는 물론이고 GPU에서도 쉐이더 언어나 컴파일러(혹은 GPU 및 드라이버)에 따라 동작이 다를 수 있다.
특정 입력에 대해 결과값이 항상 NaN으로 나오는 경우는 어쩌면 그나마 나은 상황이다. HLSL pow 함수에서 pow(0, 0) = 0 or NaN인 경우처럼 GPU마다 동작이 다르다면
이 부분에 버그가 있을 때 찾아내기 훨씬 힘들어질 것이다.</p>

<h1 id="마무리">마무리</h1>
<p>프로그래머가 수학 함수를 다룰 때는 그냥 수식 그대로를 옮기는 것으로 끝나지 말아야한다. 실제 수학 함수의 정의역과 치역을 알고 있어야 하며, 그것에 해당하는 프로그래밍 함수는 어떠한 제약이 있는지를 알고 있어야 한다. 초반에 말했듯이 컴퓨터는 덧셈 하나도 맘놓고 못하기 때문에 마음 놓고 쓸 수 있는 수학 함수란 사실상 없다.</p>

<p>log 함수를 다룰 때는 x가 0 이하가 되지 않도록 하듯이, 나눗셈을 할때는 반드시 0으로 나누지 않도록 조심해야하고 sqrt 의 경우는 음수를 계산하지 않도록 해야 한다.
이러한 부분들은 문제를 겪고 디버깅을 하면서 습관이 되기도 하지만 미리 조심하면 많은 버그를 사전에 방지할 수 있다.</p>

<p>성능 문제도 있다. <a href="https://en.wikipedia.org/wiki/Exponentiation_by_squaring" title="Exponentiation by squaring">Exponentiation by squaring</a>을 보면 알 수 있듯이 정수의 pow 계산은 비용이 그렇게 싸진 않다. 실제 CPU 수학 라이브러리 안에서 어떤 계산을 사용하는지는 확인을 하지 못했는데 pow 함수 계산하는 것은 이처럼 비용이 싸진 않다는 것을 확인하기 좋다(부동소수점과는 달리 정수의 계산은 정확해야하기 때문에 $2^{y\,log_2 x}$ 형태를 사용하기에는 무리가 있다).</p>

<p>수학 함수를 다루는 팁은 별도의 글로 적으려 했었다. 일반적인 팁 위주로 나중에 다시 적도록 하겠다.</p>

<h1 id="참고-문헌">참고 문헌</h1>
<ol>
  <li><a href="https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-pow" title="pow">HLSL의 pow 함수</a></li>
  <li><a href="https://en.cppreference.com/w/cpp/numeric/math/pow" title="std::pow">C++의 std::pow 함수</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Exponentiation_by_squaring" title="Exponentiation by squaring">Exponentiation by squaring</a> - pow(x, y)의 y가 양의 정수일 경우 계산하는 알고리즘이다.</li>
  <li><a href="https://www.quickanddirtytips.com/education/math/how-to-prove-that-1-2" title="How to Prove That 1 = 2?">How to Prove That 1 = 2?</a> - 0으로 나눴을 때 어떻게 수식 체계가 망가지는지를 알 수 있는 흔한 사례다.</li>
</ol>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>$2^x$, $log_2 x$ 명령어가 없는 CPU의 경우 계산 테이블로 미리 만들어두는 것도 흔히 쓰는 방식이다. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>수학적으로 정의되지 않음(undefined)은 그냥 무조건 피해야하는 것이라고 보면 된다. 정의 되지 않는 것을 사용할 경우 수학의 수식 체계가 망가진다. 이게 망가지면 흔한 유머처럼 1=2와 같은 잘못된 수식이 만들어진다. (예시, <a href="https://www.quickanddirtytips.com/education/math/how-to-prove-that-1-2" title="How to Prove That 1 = 2?">How to Prove That 1 = 2?</a>) <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="software-engineering" /><category term="수학" /><category term="수학 함수" /><category term="pow" /><category term="hlsl" /><summary type="html"><![CDATA[이 글에선 수학 함수의 사용에 대한 주의점을 다루려고 한다. pow 함수를 통해 실제 문제가 되는 사례를 중심으로 수학 함수를 다룰 때 함수가 정의하는 영역(정의역/공역)을 다루는 것이 중요하다는 것을 알아본다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.hybrid3d.dev/images/unsplash/antoine-dautry-05A-kdOH6Hw-unsplash.jpg" /><media:content medium="image" url="https://blog.hybrid3d.dev/images/unsplash/antoine-dautry-05A-kdOH6Hw-unsplash.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">레이트레이싱, 패스 트레이싱, 디노이징</title><link href="https://blog.hybrid3d.dev/2019-11-15-raytracing-pathtracing-denoising" rel="alternate" type="text/html" title="레이트레이싱, 패스 트레이싱, 디노이징" /><published>2019-11-15T09:00:00+09:00</published><updated>2019-11-15T09:00:00+09:00</updated><id>https://blog.hybrid3d.dev/raytracing-pathtracing-denoising</id><content type="html" xml:base="https://blog.hybrid3d.dev/2019-11-15-raytracing-pathtracing-denoising"><![CDATA[<p><strong>레이트레이싱(ray tracing)</strong> 시대가 오면서 <strong>패스 트레이싱(path tracing)</strong>이 더 자주 언급 되고 있다<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.</p>

<p>오프라인 렌더링(비실시간 렌더링) 세계에서는 익숙한 용어들이지만 실시간 게임 렌더링에서는 이제 막 레이트레이싱이라는게 등장했는데 갑자기 패스 트레이싱이라는게 같이 등장해버린다. 패스 트레이싱은 레이트레이싱을 포함하기 때문에 실시간 렌더링에 조예가 깊더라도 오프라인 렌더링에 익숙하지 않은 사람들에게는 다소 혼란스러울 수 있다.</p>

<p>이 글에서는 둘의 차이를 알아보고 게임 렌더링에서는 어떤 의미가 있는지를 살펴본다. 그리고 그 와중에 반드시 언급되어야 하는 <strong>디노이징(denosing)</strong>까지 같이 알아보도록 하겠다.</p>

<h2 id="래스터라이제이션과-레이트레이싱">래스터라이제이션과 레이트레이싱</h2>
<p>레이트레이싱과 비교해서 <strong>래스터라이제이션</strong>의 가장 큰 특장을 하나만 말하라고 하면 래스터라이제이션은 반드시 카메라가 필요하다는 점을 들 수 있겠다.
래스터라이제이션에서는 렌더링에 필요한 삼각형을 카메라 앞에 두고 투영 행렬을 이용해서 화면에 맞게 변환하고 2D로 눌러서 화면의 픽셀 하나하나를 렌더링 한다.</p>

<!-- 그림 출처: [Rasterization: a Practical Implementation](https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation) -->
<p><img src="images/cg/raytracing-raster2.png" alt="그림" /></p>

<p class="image-caption">그림 출처: <a href="https://www.scratchapixel.com/lessons/3d-basic-rendering/rasterization-practical-implementation">Rasterization: a Practical Implementation</a></p>

<p>반면 레이트레이싱은 카메라와 무관하게 렌더링에 필요한 계산을 할 수 있고 이것이 레이트레이싱이 반사에 강한 이유이다. 래스터라이제이션에서는 (물론 여러 트릭도 있지만) 거울과 같이 별도의 카메라를 설치할 수 있는 상황에서만 반사를 제대로 계산할 수 있는 반면 레이트레이싱에서는 어디서나 레이를 쏠 수 있기 때문에 어느 지점에서도 원하는대로 반사 계산을 할 수 있다.</p>

<h2 id="레이트레이싱과-패스-트레이싱">레이트레이싱과 패스 트레이싱</h2>
<p>레이트레이싱(ray tracing)과 패스 트레이싱(path tracing)은 결과적으로는 비슷해보이는데 패스 트레이싱이 좀 더 오래 걸리고 정확한 기법이다. 가령 일반적인 레이트레이싱 알고리즘에서는 픽셀당 광선을 하나씩 쏜다. 하나씩 쏜 후 거울 등을 표현하기 위해 재귀적으로 두 세번 더 트레이싱한다. 일반적으로는 여기까지가 일반적인 레이트레이싱 알고리즘이다.</p>

<!-- 거울 표현을 잘하는게 레이트레이싱의 특징이다.\\
그림 출처: [Fast and Fun: My First Real-Time Ray Tracing Demo](https://devblogs.nvidia.com/my-first-ray-tracing-demo/) -->
<p><img src="images/cg/RTinOneWeekend-1024x577.png" alt="그림" /></p>

<p class="image-caption">거울 표현을 잘하는게 레이트레이싱의 특징이다.<br />
그림 출처: <a href="https://devblogs.nvidia.com/my-first-ray-tracing-demo/">Fast and Fun: My First Real-Time Ray Tracing Demo</a></p>

<p>기술적으로 본다면 위와 같은 기본적인 레이트레이싱은 주로 반사/스페큘러만 계산 한다. 조명 방향으로 레이를 한번 더 쏴서 그림자 여부를 판단 한 후 직접 조명을 계산한다. 이 때 GI(global illumination)의 스페큘러 영역만 계산하는 것이기 때문에 GI의 디퓨즈 영역은 결여 되어 있다.</p>

<p><strong>패스 트레이싱</strong>은 쉽게 말하면 레이트레이싱을 이용해서 <strong>GI</strong>를 표현하는 것이다. 스페큘러와 디퓨즈 모두 계산을 하는 것이고 더 정확히 말하면 레이트레이싱을 이용해서 렌더링 공식(rendering equation)을 계산한다고 보면 된다. 주목할만한 특징은 패스 트레이싱에서는 직접 조명을 따로 계산하지 않아도 된다는 것이다.</p>

<p>아래 영상은 패스 트레이싱이 뭔지 쉽게 설명해주는 것으로 그림으로 매우 쉽게 설명을 한다.</p>

<div class="video-container"><iframe width="560" height="315" src="https://www.youtube.com/embed/frLwRLS_ZR0" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></div>

<p>위 영상으로도 알 수 있다시피 패스 트레이싱은 무수히 많은 광선을 쏴야 하기 때문에 실시간으로 계산하기에는 무리가 많다.</p>

<h2 id="글로시와-디노이징">글로시와 디노이징</h2>
<p>여기서 말하는 디노이징(denoising)은 단순히 화면의 노이즈를 줄이는 것이 아니다. 렌더링 공식을 계산하기 위해서는 수십개를 쏘야야 한다고 말했었다. 수백까지 쏘는건 따라갈 수 없겠지만, 현재 계산하고자 하는 픽셀과 주변 픽셀이 레이트레이싱으로 비슷한 곳을 샘플링한다면 그 샘플링한 결과물들을 공유할 수 있을 것이다. 가령 반사 광선을 픽셀당 하나씩 쏜다면 주변 픽셀과 그걸 공유해서 각 픽셀이 여러개를 공유하는 것처럼 만들 수 있다.</p>

<p>패스 트레이싱을 제외하고 실시간 레이트레이싱만 살펴 보자. 단순한 반사는 반사 레이를 한번 더 쏘면 되기 때문에 매우 처리가 간단하다. 하지만 글로시(glossy)의 경우 계산 해야하는 범위가 넓어지기 때문에 수십개에서 수백개의 레이를 쏴야만 만족스러운 결과를 알 수 있다.</p>

<!-- (완벽한) 거울은 추가 레이 하나로 처리가 가능하다. 일반적인 스페큘러는 수십개/수백개를 쏴야한다.\\
그림 출처: [[NDC 2019] 드래곤 하운드의 PBR과 레이트레이싱 렌더링 기법](https://speakerdeck.com/hybrid3d/ndc-2019-deuraegon-haundeuyi-pbrgwa-reiteureising-rendeoring-gibeob) -->
<p><img src="images/cg/ndc19-brdf-specular.png" alt="그림" /></p>

<p class="image-caption">(완벽한) 거울은 추가 레이 하나로 처리가 가능하다. 일반적인 스페큘러는 수십개/수백개를 쏴야한다.<br />
그림 출처: <a href="https://speakerdeck.com/hybrid3d/ndc-2019-deuraegon-haundeuyi-pbrgwa-reiteureising-rendeoring-gibeob">[NDC 2019] 드래곤 하운드의 PBR과 레이트레이싱 렌더링 기법</a></p>

<p>이 글로시는 흔히 스페큘러의 모양을 만드는 것과 같은 것이다. 반사 되는 영역이 얼마나 퍼지는지를 나타내는 것으로 퐁 모델(phong model)에서는 지수를 가지고 처리했고, PBR 모델에서는 러프니스로 처리하는 영역이다.</p>

<!-- 그림 출처: [[NDC 2019] 드래곤 하운드의 PBR과 레이트레이싱 렌더링 기법](https://speakerdeck.com/hybrid3d/ndc-2019-deuraegon-haundeuyi-pbrgwa-reiteureising-rendeoring-gibeob) -->
<p><img src="images/cg/before-after-denoising.png" alt="그림" /></p>

<p class="image-caption">그림 출처: <a href="https://speakerdeck.com/hybrid3d/ndc-2019-deuraegon-haundeuyi-pbrgwa-reiteureising-rendeoring-gibeob">[NDC 2019] 드래곤 하운드의 PBR과 레이트레이싱 렌더링 기법</a></p>

<p>디노이징은 화면 기준(screen-space)으로 처리 되는데, 가급적 많은 정보를 이용한다. 월드 좌표, 노말, 러프니스 등을 이용해서 주변 픽셀과 데이터를 공유하고 여기에 TAA(temporal anti-aliasing)과 같은 시간 필터(temporal filter)까지 사용한다면 이전 프레임에서 쏜것도 같이 공유할 수 있다.</p>

<p>디노이징의 과정은 Tomasz Stachowiak의 <a href="https://www.ea.com/frostbite/news/stochastic-screen-space-reflections" title="Stochastic Screen-Space Reflections by Tomasz Stachowiak">Stochastic Screen-Space Reflections (SIGGRAPH 2015)</a>에도 잘 나와 있고 좀 더 심도 있는 알고리즘은 <a href="https://research.nvidia.com/publication/2017-07_Spatiotemporal-Variance-Guided-Filtering%3A" title="Spatiotemporal variance-guided filtering: Real-time reconstruction for path-traced global illumination by Christoph Schied et al.">Spatiotemporal variance-guided filtering: Real-time reconstruction for path-traced global illumination</a>을 참고하면 좋다.</p>

<h2 id="마무리">마무리</h2>
<p>이 글에서는 레이트레이싱을 래스터라이제이션과 패스 트레이싱과 간단하게 비교해보았다. 렌더링 공식이나 패스 트레이싱, 디노이저 같은 주제들을 지금 보다는 더 깊게 다루려 했지만 각각의 주제가 워낙 깊은 주제이기 때문에 간략하게만 다뤘다.</p>

<p>래스터라이제이션과 레이트레이싱이 어떻게 다른지, 레이트레이싱과 패스 트레이싱이 어떻게 다른지, 디노이저는 왜 필요한지가 간략하게 전달되었으면 좋겠다.</p>

<h3 id="레퍼런스">레퍼런스</h3>
<ul>
  <li><a href="https://www.ea.com/frostbite/news/stochastic-screen-space-reflections" title="Stochastic Screen-Space Reflections by Tomasz Stachowiak">Stochastic Screen-Space Reflections (SIGGRAPH 2015) by Tomasz Stachowiak</a></li>
  <li><a href="https://research.nvidia.com/publication/2017-07_Spatiotemporal-Variance-Guided-Filtering%3A" title="Spatiotemporal variance-guided filtering: Real-time reconstruction for path-traced global illumination by Christoph Schied et al.">Spatiotemporal variance-guided filtering: Real-time reconstruction for path-traced global illumination (HPG 2017) by Christoph Schied et al.</a></li>
</ul>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>레이트레이싱은 영문으로도 raytracing과 ray tracing 으로 그때그때 다르게 표현 된다. 워낙 널리 쓰이기 때문에 붙인 채로 고유 명사처럼 사용되기도 한다. 최근 NVIDIA 통해 대두된 레이트레이싱은 ray tracing 처럼 띄어쓰기를 하는 추세이지만, 붙여 쓰기를 한 옛날 문서들도 많을 뿐더러 DXR의 DirectX Raytracing 은 붙여쓰기를 하는 것이 공식 명칭이다. 따라서 영어에서는 붙여쓰기 쓰기에 대한 공식적인 표기법은 없다. 이 블로그에서는 레이트레이싱은 붙여쓰기를 쓰도록 하고, 패스 트레이싱은 띄어 쓰기로 한다. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="computer-grahics" /><category term="raytracing" /><category term="path tracing" /><category term="denoising" /><summary type="html"><![CDATA[레이트레이싱(ray tracing) 시대가 오면서 패스 트레이싱(path tracing)이 더 자주 언급 되고 있다1. 레이트레이싱은 영문으로도 raytracing과 ray tracing 으로 그때그때 다르게 표현 된다. 워낙 널리 쓰이기 때문에 붙인 채로 고유 명사처럼 사용되기도 한다. 최근 NVIDIA 통해 대두된 레이트레이싱은 ray tracing 처럼 띄어쓰기를 하는 추세이지만, 붙여 쓰기를 한 옛날 문서들도 많을 뿐더러 DXR의 DirectX Raytracing 은 붙여쓰기를 하는 것이 공식 명칭이다. 따라서 영어에서는 붙여쓰기 쓰기에 대한 공식적인 표기법은 없다. 이 블로그에서는 레이트레이싱은 붙여쓰기를 쓰도록 하고, 패스 트레이싱은 띄어 쓰기로 한다. &#8617;]]></summary></entry><entry><title type="html">한참 늦은 블로그 이전 안내</title><link href="https://blog.hybrid3d.dev/2019-08-08-new-blog" rel="alternate" type="text/html" title="한참 늦은 블로그 이전 안내" /><published>2019-08-08T00:27:00+09:00</published><updated>2019-08-08T00:27:00+09:00</updated><id>https://blog.hybrid3d.dev/new-blog</id><content type="html" xml:base="https://blog.hybrid3d.dev/2019-08-08-new-blog"><![CDATA[<p>제대로 블로그를 꾸민건 <a href="http://www.textcube.org/">텍스트큐브</a>를 썼을 때였다. 
오래동안 사용한 텍스트 큐브와 <a href="https://wordpress.org/">워드프레스</a>를 거쳐서 지금의 <a href="https://pages.github.com/">GitHub Pages</a>(jekyll+github.io)로 왔다.
몇개월 됐는데 이제서야 짧막한 후기를 남긴다.</p>

<p><strong>워드프레스</strong>에 큰 불만은 없었는데 가장 큰 문제가 성능이어서 그걸 개선하다가 정적 블로그로 오게 되었다.
GitHub Pages, 즉 jekyll이 루비를 이용한 컴파일형 정적 블로그이다보니 일단 빠르다.
GitHub Pages를 거치느라 플러그인에 한계는 있지만 핵심적인건 잘 장착 되어 있다. 수정 및 개선도 상대적으로 훨씬 쉬워서 그동안 이전하지 않은게 좀 후회 된다.
LaTeX 표현, 마크다운, 이미지 관리 등 개인적으로 중요하게 생각하는 부분이 매우 만족스럽다.
블로그는 워드프레스의 글들을 옮기면서 리다이렉션 하도록 설정해두었다. 도메인도 한번 바꿔도 한동안 이중으로 접속되도록 해두었다.</p>

<p>댓글은 <a href="https://disqus.com/">DISQUS</a>를 사용한다.
댓글을 달 사용자는 개별 블로그에 로그인 하는 것이 아니라 DISQUS에 로그인해서 댓글을 달기 때문에 개인 정보 등에 안전하다.
설치해둔지는 오래 됐는데 아마 정상 작동은 오늘부터 되지 않을까.</p>

<p>예전 블로그들은 <strong>방문객</strong> 페이지 같은게 있었는데 요즘 트렌드에는 잘 쓰이지 않나보다.
비슷한 개념으로 Q&amp;A 페이지를 만들까 했는데 막상 만들어도 쓰는 사람이 있을까 싶다.</p>

<p>최근엔 짧은 SNS가 주로 인기고 <a href="https://www.google.com/reader/about/">Google Reader</a>가 사라진 후 RSS가 그렇게 활발한거 같진 않지만
RSS도 여전히 사용하고 있다. 나는 <a href="https://feedly.com">Feedly</a>를 사용해서 여러 사람들의 블로그를 구독하고 있다.
더 괜찮은 RSS 구독 서비스가 있으면 추천해주시라. 
그리고 이 블로그의 RSS는 <a href="http://feeds.feedburner.com/hybrid3d">feedburner</a>를 통해 배포되고 있다.
<a href="http://feeds.feedburner.com/hybrid3d">http://feeds.feedburner.com/hybrid3d</a> 주소를 구독 서비스에 등록하면 된다.</p>

<p>블로그에서 <a href="https://jekyllrb-ko.github.io/">jekyll</a>을 다룬 기술적인 내용은 나중에 다시 다루도록 하겠다. 카테고리, 태그 시스템 등 흔히들 쓰는 시스템부터 만들어놓고 최근엔 이미지 시스템을 독특하게 관리하고 있다.</p>]]></content><author><name></name></author><category term="general" /><category term="잡담" /><category term="블로그" /><summary type="html"><![CDATA[제대로 블로그를 꾸민건 텍스트큐브를 썼을 때였다. 오래동안 사용한 텍스트 큐브와 워드프레스를 거쳐서 지금의 GitHub Pages(jekyll+github.io)로 왔다. 몇개월 됐는데 이제서야 짧막한 후기를 남긴다.]]></summary></entry><entry><title type="html">양자 컴퓨터 - 슈뢰딩거 고양이 만들기 - 파트 2: 양자 컴퓨터에 돌려보기</title><link href="https://blog.hybrid3d.dev/2019-06-22-schrodinger-cat-part2" rel="alternate" type="text/html" title="양자 컴퓨터 - 슈뢰딩거 고양이 만들기 - 파트 2: 양자 컴퓨터에 돌려보기" /><published>2019-06-22T23:36:24+09:00</published><updated>2019-06-22T23:36:24+09:00</updated><id>https://blog.hybrid3d.dev/schrodinger-cat-part2</id><content type="html" xml:base="https://blog.hybrid3d.dev/2019-06-22-schrodinger-cat-part2"><![CDATA[<p><a href="/2019-05-15-schrodinger-cat" title="양자 컴퓨터 - 슈뢰딩거 고양이 만들기">이전 글</a>에서 양자 컴퓨터에서 슈뢰딩거의 고양이를 구성하는 법을 다뤘다. 유명한 슈뢰딩거의 고양이의 개념을 이용해서 <strong>큐빗(qubit)</strong>을 설명했다. 여기서는 슈뢰딩거 고양이를 실제 양자 컴퓨터로 돌려보는 것을 해본다.</p>

<p><a href="https://quantum-computing.ibm.com/" title="IBM Q Experience">IBM Q Experience</a>에서는 양자 컴퓨터를 웹에서 클라우드로 돌려볼 수 있다. 여기서 회로를 만들거나 QASM으로 코딩을 하면 <strong>ibmqx4</strong>라는 <code class="language-plaintext highlighter-rouge">큐빗 5개짜리 양자 컴퓨터</code>로 내가 만든 코드가 돈다. 아주 단순한 것도 몇분 걸려서 나오니 실용적이라기보다는 테스트와 학습용이라고 볼 수 있다. 이 글에서는 IBM Q Experience를 이용해서 슈뢰딩거 고양이를 양자 컴퓨터로 돌려보도록 한다. 여기서 툴 사용법을 다 다룰 순 없으니 핵심적인 것만 다루도록 하겠다.</p>

<h2 id="1-회로-구성-및-실행">1. 회로 구성 및 실행</h2>

<p class="image-caption"><img data-action="zoom" src="/images/qc/cat2/1.png" alt="" style="width: 90%;" />
그림 1. <a href="https://quantum-computing.ibm.com/" title="IBM Q Experience">IBM Q Experience</a> - Circuit composer</p>

<p>IBM Q Experience에 들어가서 <strong>Circuit composer</strong>를 열어보자. 기본적으로는 q[0]~q[4]으로 다섯개의 큐빗이 있다. 일반 고전(classical) 레지스터도 5개가 존재하며 c5로 표시되고 있다. <strong>Circuit editor</strong>에서는 QASM을 직접 수정할 수 있는데 우리는 많은 레지스터가 필요 없으니 먼저 QASM을 직접 고쳐서 다음과 같이 큐빗과 고전 레지스터를 각각 1개로 만들어보자.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>OPENQASM 2.0;
include "qelib1.inc";

qreg q[1];
creg c[1];
</code></pre></div></div>

<p>이제 q[0]만 남았다. 그 옆에는 <code class="language-plaintext highlighter-rouge">|0&gt;</code>이라는 기호가 있는데 이건 큐빗이 <strong>0 상태(state)</strong>라는 뜻이다. 입력 큐빗은 0 상태로 시작한다. <strong>1 상태</strong>는 <code class="language-plaintext highlighter-rouge">|1&gt;</code>로 표시한다. 앞선 글에서 0은 고양이가 죽은 걸 의미하고, 1은 산걸 의미한다고 했다. 죽어 있는걸 가지고 다루는게 관념적으로는 말이 안되지만 아무튼 이 서킷의 큐빗은 모두 0 상태에서 시작한다.</p>

<p>새로운 기호가 나왔으니 잠시 이전 글의 복습을 해보자. |0&gt;는 고양이의 죽은 상태를 나타내는 벡터다. 벡터로는 (1, 0)로 표현했다. |1&gt; 산 상태를 나타내고 벡터로는 (0, 1)로 표현했다. 큐빗이 0 상태로 들어온다는건 무의 상태가 들어오는 것이 아니라 (1, 0)벡터가 들어오는 것이다.</p>

<p>회로의 입력이 0 상태인 |0&gt; 들어오는건 회로를 봄으로써 알고 있지만 이전 글에서 강조했던 <strong>측정</strong>을 해보자. 측정은 미터기 표시에 z 글자가 있는 아이콘(<strong>z measurement</strong>)이다. 코드로 넣을 수도 있고 드래그해서 넣을 수도 있다. 다음과 같이 구성해보자.</p>

<p class="image-caption"><img data-action="zoom" src="/images/qc/cat2/2.png" alt="" style="width: 30%" />
그림 2. 큐빗(|0&gt;) 측정(z measurement) 회로</p>

<p>여기서 아래 화살표로 0이 표시 된건 0번 고전 레지스터에 결과값을 저장한다는 의미이다. 다시 설명하면, 입력 큐빗(|0&gt;)을 측정(z measurement)해서 0번째 고전 레지스터(c[0])에 저장한다.</p>

<p>이렇게 구성하고 왼쪽의 차트 모양의 아이콘을 클릭해서 <strong>Visualizations</strong>의 Statevector를 보자. 아래와 같이 에뮬레이션 된 결과를 볼 수 있다. 100%(세로 축 1)로 0 상태가 된다는 뜻이다.</p>

<p class="image-caption"><img data-action="zoom" src="/images/qc/cat2/3.png" alt="" style="" />
그림 3. |0&gt;의 측정 결과(Statevector)</p>

<p>실제 양자 컴퓨터에 넣어보자. 세이브를 하고 오른쪽 위의 <strong>Run</strong> 버튼을 누른다.</p>

<p class="image-caption"><img data-action="zoom" src="/images/qc/cat2/4.png" alt="" style="" />
그림 4. 제작한 회로를 양자 컴퓨터에서 실행</p>

<p>이때 굳이 4개짜리 큐빗을 쓸 필요도 없고 4천, 8천개의 실행을 돌릴 필요도 없다. 큐빗 2개 짜리인 ibmqx2 와 숫자 1024 를 고른 후 다시한번 Run 을 누른다. 그러면 화면 아래의 <strong>Pending results</strong>에 방금의 실행이 대기 상태로 들어간다. 결과는 몇분 걸린다. 1번이 아닌 몇천번의 결과를 돌리는 이유는 양자 컴퓨터의 결과는 확률적이기 때문이다. 원하는 결과가 맞는지는 확률적으로 알 수 있다.</p>

<p>실행을 기다리면 <strong>Status: COMPLETED</strong>가 뜨고 결과를 볼 수 있다.</p>

<p class="image-caption"><img data-action="zoom" src="/images/qc/cat2/5.png" alt="" style="width: 90%" />
그림 5. |0&gt;을 그대로 측정한 결과. 0이 94.434%, 1이 5.566%. 이론적으로는 항상 0이 나와야 한다.</p>

<p>오잉? 0 상태가 100% 이어야 하는데, 94.434% 이고 1인 확률이 5.566%나 된다. 이건 현재 양자 컴퓨터의 기술적 한계로 오차는 점점 줄어 들고 있지만 아직까지는 이론적인 100%가 실제 100%이진 않다. 사실 고전 컴퓨터도 많은 오차가 있었다가 줄어든 것이고 여전히 없진 않다. 에러 검출을 이용해서 보정을 하기도 하니 양자 컴퓨터라고 딱히 비교 못할 문제가 있는 것은 아니다.</p>

<h2 id="2-슈뢰딩거-고양이-회로-만들기">2. 슈뢰딩거 고양이 회로 만들기</h2>
<p>이제 오차를 뒤로 하고 상태 0과 1이 각각 50%, 50%가 되는 회로를 만들기 위해 한단계 더 나아가보자.</p>

<h3 id="21-x-게이트">2.1. X 게이트</h3>
<p>큐빗의 기본 입력은 모두 |0&gt; 이다. 이건 회로에서 다른 걸로 변환 할 수 있다. 예를 들면 0 값을 지닌 비트는 NOT 연산으로 1로 바꿀 수 있는데 이에 대응하는 것은 <strong>X 게이트</strong>라고 불리우는 <strong>Pouli X 게이트</strong>이다. X 자 아이콘으로 표시되어 있다. 이걸 측정 아이콘 사이에 넣어보자.</p>

<p class="image-caption"><img data-action="zoom" src="/images/qc/cat2/6.png" alt="" style="width: 30%" />
그림 6. |0&gt;에 X 게이트를 적용한 회로</p>

<p>아까처럼 Visualizations의 Statevector를 보면 이번엔 1 상태가 100% 인것을 볼 수 있다. 원한다면 다시한번 Run을 해봐도 좋은데 이 글에서는 다음 단계로 바로 넘어가겠다.</p>

<p class="image-caption"><img data-action="zoom" src="/images/qc/cat2/7.png" alt="" style="" />
그림 7. |0&gt;에 X 게이트를 적용한 Statevector(이론적인 결과)</p>

<p>이렇게 하면 |0&gt;에 X 게이트를 적용 시켜서 |1&gt;로 바꾼다. 이걸 수식으로 표현하면 다음과 같다.</p>

\[X|0&gt; = |1&gt;\]

<h3 id="22-ry-게이트를-이용한-슈뢰딩거-고양이-회로-만들기">2.2. Ry 게이트를 이용한 슈뢰딩거 고양이 회로 만들기</h3>

<p>하지만 우리가 원하는건 |0&gt;이나 |1&gt;이 아니다. 50%의 값. 0.5의 확률, 이전 글에서 말한 확률 진폭으로 표현하면, \(\frac{1}{\sqrt{2}}=0.707\) 로 이 둘을 표현 해야한다. 먼저 수식으로 표현하면 다음과 같다.</p>

\[\frac{1}{\sqrt{2}}|0&gt; + \frac{1}{\sqrt{2}}|1&gt;\]

<p>|0&gt; 을 가지고 이걸 표현하려면 Ry 라는 아이콘으로 표시 되어 있는 <strong>Ry 게이트</strong>를 사용해야한다.</p>

\[Ry|0&gt; = \frac{1}{\sqrt{2}}|0&gt; + \frac{1}{\sqrt{2}}|1&gt;\]

<p>Ry 게이트의 진짜 역할이 뭐냐는건 이 글의 범위를 넘어가지만 기본적으로는 회전을 해주는 게이트라고 보면 된다.</p>

<p class="image-caption"><img data-action="zoom" src="/images/qc/cat2/8.png" alt="" style="" />
그림 8. |0&gt;을 2차원 그래프의 벡터로 표현. 이때 벡터는 길이가 1인 단위 벡터이다. 길이는 확률을 뜻한다. |0&gt; 방향으로 길이가 1인건 |0&gt;으로 측정될 확률이 100%라는 의미이다.</p>

<p>|0&gt; 의 2차원 그래프. |0&gt;과 |1&gt;은 흔히 보면 xy 2차원 그래프와 비슷하게 구성할 수 있다. 그림 8에서는 |0&gt;을 벡터로 표현했다.</p>

<p class="image-caption"><img data-action="zoom" src="/images/qc/cat2/9.png" alt="" style="" />
그림 9. Ry|0&gt;를 그래프로 표현. 벡터의 길이는 1인데 각 축으로의 길이는 0.707이다.</p>

<p>그림 9는 |0&gt;을 Ry 게이트로 회전한 것이다. 2차원 회전하면 행렬이 생각날 수도 있는데, 실제로 맞다. X 게이트와 Ry 게이트는 모두 행렬로 표현할 수 있다. 여기서 행렬 자체를 보여주진 않겠다. 중요한건 0 상태를 Ry 게이트로 회전하면 50%, 50%를 나타내는 벡터로 변환 된다는 것이다.</p>

<p class="image-caption"><img data-action="zoom" src="/images/qc/cat2/10.png" alt="" style="" />
그림 10. 슈뢰딩거 고양이 회로</p>

<p>이렇게 구성해서 Statevector를 확인해보면 0 상태와 1 상태 모두 <strong>0.707</strong>(\(=\frac{1}{\sqrt{2}}\))인걸 확인할 수 있다.</p>

<p>이제 Run 을 누르고 ibmqx2 와 1024를 골라서 다시 양자 컴퓨터에 실행 시켜본다.</p>

<p class="image-caption"><img data-action="zoom" src="/images/qc/cat2/11.png" alt="" style="" />
그림 11. 슈뢰딩거 고양이 회로의 실제 측정 결과</p>

<p>이번에도 오차가 있었다. |0&gt;일 확률은 53.711% 이고 |1&gt;일 확률은 46.289% 이다. 오차가 크긴 하지만 이 정도면 나쁘지 않은 결과라고 할 수 있겠다.</p>

<h2 id="3-마무리">3. 마무리</h2>
<p>여기까지 슈뢰딩거 고양이를 나타내는 회로(그림 10)를 만들어봤다. 정확하게 말하자면 측전 전 Ry 게이트까지만 적용된 상태가 슈뢰딩거 고양이이다. 하지만 우리는 그 상태를 직접적으로 볼 수 없으므로 상자 열기라는 측정을 통해서(z measurement 아이콘) 죽은 고양이(|0&gt;)인지 산 고양이(|1&gt;)인지를 알 수 있게 된다.</p>

<h3 id="faq">FAQ</h3>
<h4 id="1-왜-rx-대신-ry-를-사용한-것이며-차이는-무엇인가">1. 왜 Rx 대신 Ry 를 사용한 것이며 차이는 무엇인가?</h4>
<p>Rx 게이트를 사용해도 결과적인 확률은 같지만 계산은 약간 달라진다. 확률 진폭은 음수를 가질수도 있고 실수가 아닌 복소수로 구성되어 있다.
그래서 Ry 게이트를 사용하면 실수 확률 진폭만을 갖게 되지만,</p>

\[Ry|0&gt; = \frac{1}{\sqrt{2}}|0&gt; + \frac{1}{\sqrt{2}}|1&gt;\]

\[Rx|0&gt; = \frac{1}{\sqrt{2}}|0&gt; - \frac{1}{\sqrt{2}}i|1&gt;\]

<p>Rx 게이트를 사용하면 \(\frac{1}{\sqrt{2}}\) 대신 \(-\frac{1}{\sqrt{2}}i\)를 얻게 된다. 복소수와의 관계는 설명이 너무 길어지므로 더 자세한 설명은 생략하겠다.</p>

<h4 id="2-오차가-너무-많이-난다-오차를-줄이는-시도가-있는가">2. 오차가 너무 많이 난다. 오차를 줄이는 시도가 있는가?</h4>
<p><a href="https://www.youtube.com/watch?v=dxQCmm5OMZQ" title="Noise cancelling for quantum bits">Noise cancelling for quantum bits</a> 영상을 보면 이러한 오차를 줄이기 위한 시도를 쉽게 설명해준다. 중간에 3D 렌더링으로 보여주는 부분도 있어서 쉽게 감을 잡을 수 있다.</p>

<h4 id="3-0-1-은-생소한-표현이다">3. |0&gt;, |1&gt; 은 생소한 표현이다.</h4>
<p>유명한 물리학자 디락(Paul A.M. Dirac)이 만든 <a href="https://en.wikipedia.org/wiki/Bra%E2%80%93ket_notation" title="Bra–ket notation - Wikipedia">브라켓 표기법(bra-ket notation)</a>으로 <strong>디락 노테이션</strong>이라고 불리우기도 한다. 복소수 벡터를 간결하게 표기할 수 있는 표기법으로 양자 컴퓨팅과 양자역학 분야에서 널리 쓰인다.</p>

<h3 id="참고문헌">참고문헌</h3>
<ol>
  <li><a href="https://quantum-computing.ibm.com/" title="IBM Q Experience">IBM Q Experience</a>: 실제 코드로 돌려볼 수 있다.</li>
  <li><a href="https://developer.ibm.com/kr/cloud/whats-new/2017/12/05/ibm-q-experience/" title="IBM Q Experience로 시작하는 양자컴퓨터 프로그래밍 실습">IBM Q Experience로 시작하는 양자컴퓨터 프로그래밍 실습</a>: IBM Q Experience와 QISKit(Quantum Information Software Kit)을 사용하는 방법을 설명하는 IBM의 글이다(한국어).</li>
  <li><a href="https://www.youtube.com/watch?v=dxQCmm5OMZQ" title="Noise cancelling for quantum bits">Noise cancelling for quantum bits</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Bra%E2%80%93ket_notation" title="Bra–ket notation - Wikipedia">Bra–ket notation - Wikipedia</a></li>
</ol>]]></content><author><name></name></author><category term="quantum-computer" /><category term="quantum computer" /><category term="Schrödinger" /><category term="고양이" /><category term="확률" /><summary type="html"><![CDATA[이전 글에서 양자 컴퓨터에서 슈뢰딩거의 고양이를 구성하는 법을 다뤘다. 유명한 슈뢰딩거의 고양이의 개념을 이용해서 큐빗(qubit)을 설명했다. 여기서는 슈뢰딩거 고양이를 실제 양자 컴퓨터로 돌려보는 것을 해본다.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.hybrid3d.dev/images/alive-dead-cats.jpg" /><media:content medium="image" url="https://blog.hybrid3d.dev/images/alive-dead-cats.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">깃 비공개 커밋 팁</title><link href="https://blog.hybrid3d.dev/2019-06-16-git-private-commit-tip" rel="alternate" type="text/html" title="깃 비공개 커밋 팁" /><published>2019-06-16T18:00:00+09:00</published><updated>2019-06-16T18:00:00+09:00</updated><id>https://blog.hybrid3d.dev/git-private-commit-tip</id><content type="html" xml:base="https://blog.hybrid3d.dev/2019-06-16-git-private-commit-tip"><![CDATA[<p><strong>깃(git</strong>)을 사용할 땐 종종 <strong>비공개(private) 커밋(commit)</strong>을 사용하고 싶어질때가 있다. 작업 중간 과정은 감추면서 원하는 단계에서만 공개가 되었으면 하는 것이다. 푸쉬를 하여 공개하는 것과는 다른데 일반적인 커밋과 푸시로는 모든 커밋이 다 남아 있기 때문이다. 깃을 잘 모를 때는 그런 방법이 없는 줄 알았는데 좀 익숙해지니까 뻔한 방법으로 가능했다. 아마 깃에 익숙한 사람은 첫줄만 보고도 뭔지 눈치채지 않았을까.</p>

<p>방법은 간단하다. 머지할 때 <strong>스쿼시(squash)</strong>를 이용하면 되고 이중으로 <strong>비공개 리모트(remote)</strong>를 따로 관리하면 된다. 비공개 커밋 작업은 비공개 리모트에서 커밋하고 머지 할 때 스쿼시 머지를 하면 된다.</p>

<p>예를 들어서 공개용 origin 리모트가 있으면 추가로 비공개 리파지토리를 만들어서 origin-private 라는 리모트 이름으로 추가한다. 요즘 <a href="https://bitbucket.org/">bitbucket</a> 이나 <a href="https://github.com/">github</a>에 비공개 리파지토리를 만들 수 있으니 새로 만들기 편하다.</p>

<p>비공개로 할 작업이 있다면 feature/work 라는 브랜치를 만든다. 이 비공개 브랜치는 origin 에는 올리지 않고 origin-private 에서만 올리면서 작업한다. 그 후 나중에 이 브랜치를 master 에 머지할때 squash merge 로 머지하면 된다.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">(</span>master 브랜치에서<span class="o">)</span>
<span class="nv">$ </span>git merge <span class="nt">--squash</span> feature/work
</code></pre></div></div>

<p>이때 입력하는 커밋 메세지는 공개용으로 정식으로 작성하고 origin 에 푸쉬한다.</p>

<p>여담으로 이 블로그도 이런 식으로 작업하고 있다. 비공개 브랜치에서 _draft 디렉토리에서 글을 작성하고 공개 할 경우 _posts로 옮긴다<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>. 글을 _posts 로 옮긴 후 스쿼시 머지를 이용해서 공개 브랜치에 옮긴다.</p>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>_draft, _posts 는 블로그 프레임워크 jekyll 의 디렉토리 규칙이다. _draft 에 있는 글은 공개 되지 않는 글이고 _posts 의 글이 공개 되는 글이다. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="general" /><category term="git" /><category term="merge" /><summary type="html"><![CDATA[깃(git)을 사용할 땐 종종 비공개(private) 커밋(commit)을 사용하고 싶어질때가 있다. 작업 중간 과정은 감추면서 원하는 단계에서만 공개가 되었으면 하는 것이다. 푸쉬를 하여 공개하는 것과는 다른데 일반적인 커밋과 푸시로는 모든 커밋이 다 남아 있기 때문이다. 깃을 잘 모를 때는 그런 방법이 없는 줄 알았는데 좀 익숙해지니까 뻔한 방법으로 가능했다. 아마 깃에 익숙한 사람은 첫줄만 보고도 뭔지 눈치채지 않았을까.]]></summary></entry><entry><title type="html">양자 컴퓨터 - 슈뢰딩거 고양이 만들기</title><link href="https://blog.hybrid3d.dev/2019-05-15-schrodinger-cat" rel="alternate" type="text/html" title="양자 컴퓨터 - 슈뢰딩거 고양이 만들기" /><published>2019-05-15T01:00:00+09:00</published><updated>2019-05-15T01:00:00+09:00</updated><id>https://blog.hybrid3d.dev/schrodinger-cat</id><content type="html" xml:base="https://blog.hybrid3d.dev/2019-05-15-schrodinger-cat"><![CDATA[<h2 id="들어가기-전">들어가기 전</h2>

<p>이 글은 추후에 공개 될 시리즈 글 중 한 부분이다(아마도 두번째 글). 양자 컴퓨터에 대한 간단한 소개 글을 쓰고 있는데, 그 중 이 글은 따로 떼어놓고 공개해도 될 것 같아서 미리 공개한다.</p>

<p>추후에 시리즈 글이 공개 될 때 다시 수정 될 수도 있고 아니면 아예 다른 글로 재작성될 수도 있다. 다만 이 글은 앞뒤 글과 무관하게 별도로 읽을 수 있어서 먼저 공개한다.</p>

<p>제대로 알고 알고 있는 내용들만 다루었다. 하지만 혹시나 틀린 부분들이 있을 수 있다. 댓글로 남겨주시면 매우 감사하겠다.</p>

<h2 id="개요">개요</h2>

<p><strong>슈뢰딩거(Schrödinger) 고양이</strong>는 일반인들도 꼭 들어봤을 법한 유명한 비유다. 슈뢰딩거의 고양이는 양자역학을 설명하는데 쓰이지만, 실제로는 에르빈 슈뢰딩거(Erwin Schrödinger)가 <code class="language-plaintext highlighter-rouge">이게 말이 되냐</code>고 모순점을 증명하려고 내놓은 비유다<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>. 하지만 결과적으로는 <code class="language-plaintext highlighter-rouge">응 그거 맞아</code>인 상황이라서 이 비유를 널리 쓰게 되었다<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>.</p>

<h2 id="슈뢰딩거-고양이">슈뢰딩거 고양이</h2>

<p class="image-caption"><img data-action="zoom" src="/images/alive-dead-cats.jpg" alt="" style="" />
그림 1. 슈뢰딩거의 고양이 <br />출처: <a href="https://www.shutterstock.com/ko/image-illustration/cartoon-black-cat-drawing-skeleton-cute-1202505370">Shutter Stock by Sudowoodo</a></p>

<p>사고 실험이다. 어떤 독이 있다. 이 독은 기가 막히게 50%의 확률로만 고양이를 죽인다. 어떤 고양이는 면역이 있고, 어떤 고양이는 면역이 없기 때문이다. 딱 50%의 확률이다. 이때 고양이를 상자에 가두고 이 독가스를 주입한다. 그러면 50%의 확률로 살거나 죽을 것이다. 이때 상자를 미리 볼 순 없고 상자를 열어야만 우리는 고양이의 생사를 알 수 있다.</p>

<p>양자역학의 해석으로는 이 때 고양이는 죽은 상태와 산 상태가 <strong>공존</strong>한다. 즉, 살기도 하고 죽은 것이기도 한데 박스를 여는 순간 그 상태가 <strong>결정</strong>된다.</p>

<p>이게 말이 되나 싶은 얘기다. 일반적인 상식으로는 산거면 산거고 죽은거면 죽은 것이고, 박스 안 고양이는 아직 박스 안에 있기 때문에 단지 우리가 <strong>모르는 것</strong>일 뿐이다. 하지만 양자역학의 관점에서는 산 상태와 죽은 상태가 공존하고 있다는게 실험적으로 증명되었다. 물론 고양이로는 그 실험을 재현할 수 없지만, 미시적, 즉, 양자역학의 관점에서는 이미 증명되었다.</p>

<p>이렇게 상태가 공존하는 걸 <strong>중첩(superposition)</strong>이라고 하고, 상자를 열어서 상태를 확인하는 걸 <strong>측정(measurement)</strong>이라고 한다.</p>

<p>고전적(혹은 거시적) 관점에서는 측정이 대상에 큰 영향을 주지 않는다. 즉, 몰래 엿보기가 가능하다. 실험을 아주 잘 하면 실험 대상의 상태는 변하지 않는다. 하지만 양자역학 세계에서는 엿보기가 불가능하고 우리가 그걸 관찰할 때 상태가 결정 된다. 그래서 측정이 매우 중요한 역할을 한다. 이 세계에서는 측정은 엿보기가 아니라 상태를 결정 시키는 것이다. 물론 확률적으로.</p>

<h2 id="동전-던지기">동전 던지기</h2>

<p>동전 던지기도 좋은 비유가 될 수 있다. 동전을 팽이처럼 굴린다고 하자. 시간이 지나면 동전은 회전력을 잃고 <strong>앞면</strong>이나 <strong>뒷면</strong>으로 상태가 결정 될 것이다.</p>

<p>고전적인 상태에서는 동전은 회전은 끝났고 이미 앞면이나 뒷면을 보이고 있는데 우리가 손바닥으로 가려서 모르고 있는 것이다. 상태는 이미 정해져있다. 우리가 모를 뿐이다. 어쩌면 몰래 엿보기도 가능하다.</p>

<p>반면 양자역학의 동전은 끊임 없이 회전하고 있다. 그래서 앞면일 수도 뒷면일 수도 있고 아직 결정되지 않았다. 그걸 손바닥으로 내리치면 그때 앞면이나 뒷면이 결정 된다.</p>

<h2 id="비트bit와-큐빗qubit">비트(bit)와 큐빗(qubit)</h2>

<p>고전 컴퓨터 데이터의 최소 단위는 <strong>비트</strong>이지만, 양자 컴퓨터의 최소 단위는 <strong>큐빗</strong>이다.</p>

<p>비트는 0이거나 1이다. 우리는 특정 비트의 내용을 모를 수도 있지만, 그건 우리가 모를 뿐이지 이미 메모리에 저장되어 있다. 반면 큐빗은 0인지 1인지는 아직 모르고(결정되지 않았고), 0일 수도, 1일 수도 있는 상태를 표현해야한다. 생각보다 복잡해보는데, 의외로 복잡하지 않긴 하다. 하지만…. 어.. 음.. 물론 이 글을 넘어가 나중엔 계산이 좀 복잡해진다.</p>

<p>양자 컴퓨터를 고전 컴퓨터로 시뮬레이션 한다고 생각해보자. 시뮬레이터를 만들어보는 이유는 그래야 고전 컴퓨터의 용어로 쉽게 설명할 수 있기 때문이다.</p>

<p>큐빗은 확률을 표현해야하기 때문에 1비트로는 표현이 불가능하고 실수(float, double)로 표현을 해야한다. 즉 앞면 50%, 뒷면 50%를 위해 0.5, 0.5 로 표현을 한다. 이걸 2차원 벡터로 표현해보자.</p>

<p class="image-caption"><img data-action="zoom" src="/images/superposed-cat.jpg" alt="" style="width: 30%;" />
그림 2. 삶과 죽음이 중첩 된 슈뢰딩거의 고양이</p>

\[(0.5, 0.5)\]

<p>여기서 벡터의 앞쪽(왼쪽) 요소는 고양이가 죽을 확률(혹은 동전이 앞면일 확률)이고, 뒷쪽(오른쪽) 요소는 고양이가 살 확률(혹은 동전이 뒷면일 확률)이다. 산 확률과 죽은 확률이 <strong>동시에 존재</strong>한다.</p>

<p class="image-caption"><img data-action="zoom" src="/images/alive-cat.jpg" alt="" style="width: 40%;" />
그림 3. 슈뢰딩거의 고양이 (삶)</p>

<p>이때 중첩된 고양이가 아닌 위와 같이 살아 있는 고양이는 \((1, 0)\)로 표현하고</p>

<p class="image-caption"><img data-action="zoom" src="/images/dead-cat.jpg" alt="" style="width: 40%;" />
그림 4. 슈뢰딩거의 고양이 (죽음)</p>

<p>죽은 고양이는 \((0, 1)\)로 표현한다. 비트로 표현한다면, \((1, 0)\)은 1로 표현하고 \((0, 1)\)은 0으로 표현하면 적절할 것이다.</p>

<p>\((살 확률, 죽은 확률)\)로 표현하는 <strong>큐빗</strong>은 고전 컴퓨터의 비트에 대응하는 양자 컴퓨터의 최소 단위이다. 고전 컴퓨터에서 1비트로 표현하던걸 갑자기 2차원 실수형 벡터로 표현하게 되어버렸다. 너무 뻥튀기 된거 같은 느낌이 든거 같으면 정확하다. 이 때문에 양자 컴퓨터는 뭔가 대단한걸 할 수 있는 컴퓨터인거고 그만큼 만들기도 힘든 것이라고 할 수 있겠다.</p>

<p>결과적으로 이 글의 거창한 제목인 <code class="language-plaintext highlighter-rouge">슈뢰딩거 고양이 만들기</code>의 슈뢰딩거 고양이는  <strong>(0.5, 0.5)</strong> 으로 만들었다고 할 수 있다. 고양이가 살았는지 죽었는지를 0, 1의 비트가 아닌 확률로 표기된다는 점, 그 확률은 2차원 실수형 벡터로 표기한다는 점을 알면 슈뢰딩거 고양이를 만들 수 있다.</p>

<h2 id="측정-measurement">측정 (measurement)</h2>

<p>쉽게 설명하기 위해서 좀 생략한게 있다. 큐빗은 그 자체가 확률을 가지고 있진 않다. 확율에 대응하는 <strong>확률 진폭(probility amplitude)</strong>을 가지고 있다. 확률 진폭의 절대값에 제곱을 하면<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup> 그게 진짜 확률이다.</p>

<p>그래서 실제 슈뢰딩거 고양이를 정확한 큐빗으로 표현하면 (0.5, 0.5) 대신</p>

\[(\frac{1}{\sqrt{2}}, \frac{1}{\sqrt{2}})\]

<p>으로 표현해야 한다(\(\left(\frac{1}{\sqrt{2}}\right)^2 = \frac{1}{2} = 0.5\)). 측정이란 확률 진폭을 제곱해서 확률로 바꾸고 그 확률을 토대로 상태를 결정하는 것이다.</p>

<p>예를 들어 만약 독가스가 80%는 살리고, 20%만 죽인다고 해보자. 그러면 이 박스 안 슈뢰딩거의 고양이는 \((\sqrt{0.8}, \sqrt{0.2}) = (0.89427, 0.4)\)으로 표현한다. 이때 박스를 열면 우리는 80%, 20%의 중첩 된 고양이는 볼 수 없고 80%의 확률로 산 고양이(1, 0)를 보거나 20%의 확률로 죽은 고양이(0, 1)를 보게 된다.</p>

<p>이렇게 확률 진폭을 절대값 제곱해서 확률을 얻은 확률을 가지고 상태가 큐빗을 만드는 과정을 <strong>측정한다(measuring)</strong>고 말한다. 어떤 큐빗을 가지고 있다가 상태가 뭔지를 알기 위해 측정을 하면 그에 맞는 그 확률에 맞게, 0인지 1인지가 결정 된다.</p>

<p class="image-caption"><img data-action="zoom" src="/images/cat-graph.jpg" alt="" style="width: 100%" />
그림 5.</p>

<p>이때 그 결정된 큐빗은 이전에 측정하기 전 큐빗과 같은 큐빗이 아니다. 측정된 상태로 변한 것이다. 다시 말해서 박스를 열고 나면(측정을 하고 나면) 이제 죽은 상태와 산 산태가 중첩 된 고양이는 존재하지 않는다. 박스를 여는 순간 이제 죽었거나 산 고양이만 남을 뿐 죽기도 하고 살기도 했던 예전의 상자 속 고양이와는 상태가 다른 고양이만 남게 된다.</p>

<h2 id="마무리">마무리</h2>

<p>이런 궁금증이 생길 수 있다. 어차피 확률 진폭과 확률이 똑같이 대응 되는 값이라면 그냥 처음부터 확률 진폭 대신 확률을 써도 되지 않을까? 그건 이 글이 다루는 범위를 넘어가는데, 실제로 양자역학이나 양자 컴퓨터에서는 확률 진폭을 가지고 직접 계산을 하는 경우가 있고, 이 경우 확률만으로는 정확한 계산을 할 수가 없다. 확률만으로 계산할 수 있었다면 양자역학이 고전적인 물리와 다르지 않았을 것이다. 확률 진폭을 가지고 계산을 하고 또 그게 완벽히 맞아 떨어지기 때문에 현대 물리는 큐빗 혹은 입자는 확률 진폭을 가지고 있다고 <code class="language-plaintext highlighter-rouge">해석</code>을 하고 있다.</p>

<p>또한 결국에는 저 중첩이라는 것이 특별한 역할을 하고 있다. 중첩을 위해서 일반 컴퓨터에서는 많은 데이터를 사용해야하는데 양자 컴퓨터에서는 중첩 자체가 자연 현상이니까 그걸 그대로 복잡한 연산에 사용할 수 있는 것이다.</p>

<h2 id="참고문헌">참고문헌</h2>

<p>여기서 양자역학 책을 레퍼런스로 삼으면 그렇게 적절하진 않을 것이다. 여러 책을 봤지만 <a href="https://www.amazon.com/Quantum-Computing-Computer-Scientists-Yanofsky/dp/0521879965" title="Quantum Computing for Computer Scientists by Noson S. Yanofsky  and Mirco A. Mannucci">Quantum Computing for Computer Scientists</a> 책이 가장 훌륭했다. 제목대로 프로그래머를 위한 책이고, 수학/물리적인 내용들을 다 다루면서 진행한다. 이 책은 따로 리뷰 글을 남기도록 하겠다.</p>

<ol>
  <li><a href="https://en.wikipedia.org/wiki/Schr%C3%B6dinger%27s_cat" title="Schrödinger's cat">슈뢰딩거의 고양이(Schrödinger’s cat)</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Copenhagen_interpretation" title="Copenhagen interpretation">코펜하겐 해석(Copenhagen interpretation)</a></li>
  <li><a href="https://www.amazon.com/Quantum-Computing-Computer-Scientists-Yanofsky/dp/0521879965" title="Quantum Computing for Computer Scientists by Noson S. Yanofsky  and Mirco A. Mannucci">Quantum Computing for Computer Scientists by Noson S. Yanofsky  and Mirco A. Mannucci</a></li>
</ol>

<hr />

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>실제 사고 실험과 백그라운드는 여기에 설명된 것보다 더 복잡하다. 위키피디아를 보면 배경이 좀 더 설명되어 있다. 참고문헌 1. Schrödinger’s cat. <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>이걸 코펜하겐 해석(Copenhagen interpretation)이라고 한다. 참고문헌 2. Copenhagen interpretation. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:3" role="doc-endnote">
      <p>제곱을 하면 절대값 한것과 같은데 굳이 왜 절대값을 씌우는지 궁금할 것이다. 확률 진폭은 실수가 아니라 복소수이기 때문이다. 그 차이만 빼면 나머지 과정은 동일하다. <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="quantum-computer" /><category term="quantum computer" /><category term="Schrödinger" /><category term="고양이" /><category term="확률" /><summary type="html"><![CDATA[들어가기 전]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blog.hybrid3d.dev/images/alive-dead-cats.jpg" /><media:content medium="image" url="https://blog.hybrid3d.dev/images/alive-dead-cats.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">SSPM 커스틱 영상</title><link href="https://blog.hybrid3d.dev/2019-05-12-sspm-caustics-video" rel="alternate" type="text/html" title="SSPM 커스틱 영상" /><published>2019-05-12T01:00:00+09:00</published><updated>2019-05-12T01:00:00+09:00</updated><id>https://blog.hybrid3d.dev/sspm-caustics-video</id><content type="html" xml:base="https://blog.hybrid3d.dev/2019-05-12-sspm-caustics-video"><![CDATA[<p><strong>레이트레이싱 젬스(Ray Tracing Gems)</strong>에서 기고하면서 구현했던 <strong>SSPM(Screen-Space Photon Mapping)</strong>이다. NDC 발표에서는 이 영상을 빨리감기로 편집해서 보여줬었다.</p>

<center><iframe width="560" height="315" src="https://www.youtube.com/embed/T_b6StxmMfQ" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe></center>

<p>사실 이 영상은 제작 초기 영상으로 완벽한 세팅 버전의 동영상이 아니다. 제대로 튜닝한 버전에서는 커스틱이 좀 더 부드럽다.</p>

<p>새 버전의 영상을 못 만들고 있는 이유가 있는데, 저 작업물이 윈도우 RS4 버전의 영상이라 지금 RS5 버전인 윈도우에서는 돌릴 수 없다. 윈도우를 RS4 로 내리긴 귀찮고, 구현을 RS5 버전으로 이전하면 되는데 다른 일로 아직 진행을 못하고 있다.</p>

<p>현재는 틈틈히 Falcor 엔진에 포팅하고 있다. 이건 원래 1월달에 끝났어야 할 일인데, 여러가지 일로 바쁘다보니 많이 미뤄졌다. 리소스와 씬 세팅이 제일 귀찮고 고된 일이었다.</p>

<p>소스 공개는 언젠가는 분명 할 것이고, 구현하다만 커스텀 디노이저도 구현 할 것이다. 그 내용들은 나중에 다시 다루겠다.</p>]]></content><author><name></name></author><category term="computer-graphics" /><category term="raytracing" /><category term="rtg" /><category term="sspm" /><category term="photon_mapping" /><summary type="html"><![CDATA[레이트레이싱 젬스(Ray Tracing Gems)에서 기고하면서 구현했던 SSPM(Screen-Space Photon Mapping)이다. NDC 발표에서는 이 영상을 빨리감기로 편집해서 보여줬었다.]]></summary></entry></feed>