<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>kkj-100-010-110.log</title>
        <link>https://velog.io/</link>
        <description>Hello everyone</description>
        <lastBuildDate>Fri, 13 Dec 2024 03:04:20 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>kkj-100-010-110.log</title>
            <url>https://velog.velcdn.com/images/kkj-100-010-110/profile/d92d18d5-c0a1-4d1b-955c-366c1d3636b3/social_profile.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. kkj-100-010-110.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/kkj-100-010-110" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[OpenGL_09, Colors]]></title>
            <link>https://velog.io/@kkj-100-010-110/OpenGL09-Colors</link>
            <guid>https://velog.io/@kkj-100-010-110/OpenGL09-Colors</guid>
            <pubDate>Fri, 13 Dec 2024 03:04:20 GMT</pubDate>
            <description><![CDATA[<p>실제로 색은 각 사물이 지니고 있는 고유의 색상과 어느 색상값을 가질 수 있다. 디지털 세계에서는 색상은 무한한 실제 색을 제한된 디지털 값으로 매핑해야하므로 모든 색은 디지털로 표현할 수 없다. 색상은 일반적으로 약어 RGB에서 Red, Green, Blue 원소를 사용하여 나타낸다. 이 세가지 값의 조합을 사용하여 0-1 사이의 범위안에서 어느 색을 표현할 수 있다.</p>
<pre><code class="language-cpp">glm::vec3 coral(1.0f, 0.5f, 0.31f);</code></pre>
<p>우리가 보는 사물의 색은 실제 그 사물이 가진 색이 아니다. 사물로부터 반사된 색이다. 사물로부터 흡수된(absorbed)게 아니라 우리가 인지하고 있는 것. 한 예시로 태양의 빛은 다양한 색으로 결합된 하얀 빛으로 인지된다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/b297a8b4-d8d2-41a0-ba04-8c7122149969/image.png" alt="">
이 하얀 빛을 파란 물체에 비추면 파란색을 제외한 모든 하얀색을 구성하는 부수적인 색들을 흡수한다. 이 물체는 파란색을 흡수하지 못하기때문에 반사시킨다. 이 반사된 빛이 눈에 들어오고, 물체가 파란색을 가진 것처럼 보이게 한다. 위 이미지는 산호색의 물체에 관한 것.</p>
<p>하얀 태양빛은 모든 색의 집합체이고 물체는 그 색상들의 큰 부분을 흡수. 물체의 색을 나타내는 색상들을 비추며 그 색상들의 조합이 우리가 인지하는 것. </p>
<p>OpenGL에서 광원을 정의할 때 이 빛을 하나의 색으로 준다. 이 빛의 색상값과 사물의 색상값을 곱하면 결과 색상값은 사물의 반사된 색상이다. </p>
<pre><code class="language-cpp">glm::vec3 lightColor(1.0f, 1.0f, 1.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (1.0f, 0.5f, 0.31f);</code></pre>
<p>물체의 색이 하얀 빛의 큰 부분을 흡수하지만 물체가 가진 색을 토대로 몇가지 빨강, 초록, 파랑 값들을 반사한다. 이는 실제로 색상이 어떻게 작용하는지 표현. 물체가 빛으로부터 반사되는 각각의 색상의 원소의 양으로 물체의 색을 정의. 만약 초록빛을 사용하면?</p>
<pre><code class="language-cpp">glm::vec3 lightColor(0.0f, 1.0f, 0.0f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (0.0f, 0.5f, 0.0f);</code></pre>
<p>물체는 흡수하거나 또는/그리고 반사할 수 있는 빨강과 파랑빛을 가지지 않는다. 이 물체는 또한 초록빛 값의 반을 흡수할뿐만아니라 반사한다. 이 물체의 색은 어두운 초록색이라 인지될 것. 초록빛을 사용하면 초록색의 원소만 반사되어 인식될 수 있다.</p>
<pre><code class="language-cpp">glm::vec3 lightColor(0.33f, 0.42f, 0.18f);
glm::vec3 toyColor(1.0f, 0.5f, 0.31f);
glm::vec3 result = lightColor * toyColor; // = (0.33f, 0.21f, 0.06f);</code></pre>
<p>위 예시는 dark olive-green 빛이다.</p>
<p>A lighting scene</p>
<p>먼저 빛을 발하는 물체가 필요하고 이전 챕터의 큐브를 생성해 사용할 것. 빛 자원이 3D 배경에서 어디에 위치하는지 나타낼 빛 오브젝트 필요. 간단하게 표현하기 위해서 큐브를 사용하여 광원을 나타낸다.</p>
<p>vertex buffer objects 채우고 vertex attribute pointer들을 세팅한다.</p>
<p>첫번째로 큐브 컨테이너를 그릴 vertex shader가 필요. 컨테이너의 vertex position은 똑같으므로 코드는 그대로다. </p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}</code></pre>
<p>vertex 데이터와 vertex attribute pointer 업데이트</p>
<pre><code class="language-cpp">unsigned int lightVAO;
glGenVertexArrays(1, &amp;lightVAO);
glBindVertexArray(lightVAO);
// we only need to bind to the VBO, the container’s VBO’s data
// already contains the data.
glBindBuffer(GL_ARRAY_BUFFER, VBO);
// set the vertex attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);</code></pre>
<br>
광원과 컨테이너를 fragment shader 정의

<pre><code class="language-cpp">#version 330 core
out vec4 FragColor;
uniform vec3 objectColor;
uniform vec3 lightColor;
void main()
{
    FragColor = vec4(lightColor * objectColor, 1.0);
}</code></pre>
<p>Fragment shader는 컨테이너의 색과 광원의 색을 <code>union</code>변수로 받을 수 있다. 여기서 물체에 반사된 색과 광원의 색을 곱한다. 광원의 색과 물체의 색을 설정</p>
<pre><code class="language-cpp">lightingShader.use();
lightingShader.setVec3(&quot;objectColor&quot;, 1.0f, 0.5f, 0.31f);
lightingShader.setVec3(&quot;lightColor&quot;, 1.0f, 1.0f, 1.0f);</code></pre>
<p>다음 챕터에서 lighting shaders를 업데이트 시작할 때 광원인 물체도 영향을 받을 것인데 이는 원치 않는 결과이다. 광원의 물체의 색이 빛을 계산하는 것으로부터 영향을 받지 않게 다른 것과 다르게 그대로 유지. 광원이 다른 색에 의해 영향을 받지 않고 상수 밝기의 색을 가지게 하게 한다.</p>
<p>위를 수행하기 위해서 다른 shader를 추가한다. 이는 광원을 그리고 lighting shader의 변화에 안전함을 위함이다.</p>
<p>광원의 fragment shader는 아래와 같이 생성</p>
<pre><code class="language-cpp">#version 330 core

out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0); // set all 4 vector values to 1.0
}</code></pre>
<p>light 쉐이더가 점더 실제적인 결과를 얻게 업데이트할 동안 광원은 광원 쉐이더의 정의에 따라 렌더링되고 그려지게 한다.</p>
<p>광원의 주 목적은 어디서 빛이 오는지 보여주는 것. 보통 광원의 위치를 scene의 어느 지점에 두는데 시각적 의미를 두지 않는다. scene의 빛의 상태에 상관없이 항상 하얀색으로 유지되게 광원 쉐이더에 정의된 컨테이너형태로 나타낸다. </p>
<p>world 공간 좌표에서의 광원의 위치를 전연벽수 <code>vec3</code>로 나타낸다.</p>
<pre><code class="language-cpp">glm::vec3 lightPos(1.2f, 1.0f, 2.0f);</code></pre>
<p>광원을 나타내는 물체를 광원의 지점으로 이동시키고 크기 조정을 하기 위해서 아래와 같이 코드 작성</p>
<pre><code class="language-cpp">model = glm::mat4(1.0f);
model = glm::translate(model, lightPos);
model = glm::scale(model, glm::vec3(0.2f));</code></pre>
<p>렌더링 하는 코드에서 광원의 물체에 관한 코드는 아래와 같이 작성할 수 있다.</p>
<pre><code class="language-cpp">lightCubeShader.use();
// set the model, view and projection matrix uniforms
[...]
// draw the light cube object
glBindVertexArray(lightCubeVAO);
glDrawArrays(GL_TRIANGLES, 0, 36);</code></pre>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/6036b9cf-abb6-47bc-bc32-0f0e0f18bc19/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Tree]]></title>
            <link>https://velog.io/@kkj-100-010-110/Tree</link>
            <guid>https://velog.io/@kkj-100-010-110/Tree</guid>
            <pubDate>Tue, 08 Oct 2024 04:38:38 GMT</pubDate>
            <description><![CDATA[<p>트리와 그래프에 대한 이해 → 복잡한 문제 해결</p>
<p>e.g. 데이터베이스(B-트리), 데이터 인코딩/압축(허프만 트리), 그래프 컬러링, 할당 문제, 최소 거리 문제 등</p>
<p>계층적 문제</p>
<ul>
<li>회사 조직도</li>
<li>교과 과정 계층도</li>
</ul>
<p>순환 종속성</p>
<ul>
<li>관계도(SNS 친구 관계)</li>
<li>도로망</li>
</ul>
<p><strong>Tree</strong></p>
<p>모든 노드는 자식 노드가 없거나 하나 이상의 자식 노드를 가질 수 있다.</p>
<p>루트 노드를 제외한 모든 노드는 하나의 부모 노드를 가진다.</p>
<p>어떤 노드의 degree는 그 노드가 가진 자식 노드의 수를 말한다.</p>
<p>같은 노드를 부모 노드를 가지는 노드들은 sibling이라 표현</p>
<p>degree에 따라서 leaf 노드인지 internal 노드인지 구별한다.</p>
<p><strong>경로(Path)</strong></p>
<p>edge의 개수는 경로의 길이</p>
<p><strong>Depth</strong></p>
<p>노드의 depth는 루트로부터의 해당 노드까지의 길이를 말한다. → 루트에서 그 노드까지의 경로의 길이 → edge의 개수</p>
<p><strong>Height</strong></p>
<p>가장 큰 depth → 트리의 Height</p>
<p>e.g. 루트 노드 하나라면 0, empty tree는 -1</p>
<p><strong>Ancestor and Descendant</strong></p>
<p>자기 자신은 조상이면서 자손</p>
<p>루트 노드는 모든 노드의 조상 ancestor</p>
<p><strong>트리 순회</strong></p>
<ul>
<li><p>전위 순회(preorder traversal)</p>
<ul>
<li><p>Current Node→Left→Right</p>
<pre><code class="language-cpp">preorder(전위 순회)(노드 주소)
  if 인자로 전달된 노드 주소가 없다면
      종료
  노드의 값 출력 또는 전달
  preorder(노드의 왼쪽에 연결견된 주소)
  preorder(노드의 오른쪽에 연결된 주소)</code></pre>
</li>
</ul>
</li>
<li><p>중위 순회(inorder traversal)</p>
<ul>
<li><p>Left→Current Node→Right</p>
<pre><code class="language-cpp">inorder(중위 순회)(노드 주소)
  if 인자로 전달된 노드 주소가 없다면
      종료
  inorder(노드의 왼쪽에 연결견된 주소)
  노드의 값 출력 또는 전달
  inorder(노드의 오른쪽에 연결된 주소)</code></pre>
</li>
</ul>
</li>
<li><p>후위 순회(postorder traversal)</p>
<ul>
<li><p>Left→Right→Current Node</p>
<pre><code class="language-cpp">postorder(중위 순회)(노드 주소)
  if 인자로 전달된 노드 주소가 없다면
      종료
  postorder(노드의 왼쪽에 연결견된 주소)
  postorder(노드의 오른쪽에 연결된 주소)
  노드의 값 출력 또는 전달</code></pre>
</li>
</ul>
</li>
<li><p>레벨 순서 순회(level order traversal)</p>
<ul>
<li><p>트리의 맨 위 레벨부터 아래 레벨까지, 왼쪽 노드에서 오른쪽 노드 순서로 방문. 트리의 루트 노드부터 단계별로 차례대로 나열하는 것과 같다.</p>
<pre><code class="language-cpp">레벨 순서 순회(level order traversal)(노드 주소)
  if 노드 주소가 없다면
      종료
  큐 생성 후 노드 주소 할당
  while 큐가 비어있지 않으면
      for 큐의 크기만큼
          큐에서 노드를 꺼내고 current에 저장
          current 노드의 값을 출력 또는 반환
          if current 노드의 left에 노드 존재 확인
              있으면 큐에 저장
          if current 노드의 right에 노드 존재 확인
              있으면 큐에 저장
</code></pre>
</li>
</ul>
</li>
</ul>
<p><strong>다양한 트리 구조</strong></p>
<ul>
<li><p>이진 검색 트리 BST, binary search tree</p>
<ul>
<li><p>특징</p>
<ul>
<li>부모 노드의 값 ≥ 왼쪽 자식 노드의 값</li>
<li>부모 노드의 값 ≤ 오른쪽 자식 노드의 값</li>
<li>BST가 마지막 레벨을 제외한 모든 노드에 두 개의 자식 노드가 있을 경우, 이 트리의 높이는 $\log_2N$이 되고 $N$은 원소의 개수이다. 이로서 BST 검색 및 삽입 동작은 $O(\log N)$의 시간 복잡도를 갖는다. (complete binary tree)</li>
</ul>
</li>
<li><p>이진 트리 다양한 형태들</p>
<ul>
<li><p>Full Binary Tree</p>
<ul>
<li><p>모든 노드가 자식이 없거나 2개의 자식을 가지고 있다.</p>
<p>  the number of internal nodes - $i$</p>
<p>  the total number of nodes - $n$</p>
<p>  number of leaves - $l$</p>
<p>  number of levels - $L$</p>
<ul>
<li>레벨 L의 트리의 높이(height)는 $L - 1$</li>
<li>leaf 노드 수는 $i + 1$</li>
<li>전체 노드 수는 $2i  + 1$</li>
<li>internal 노드 수는 $(n + 1) / 2$</li>
<li>총 노드 수 $2l - 1$</li>
<li>총 internal 노드 수는 $l - 1$</li>
<li>leaf 노드의 가장 많을 때는 $2^L - 1$</li>
</ul>
</li>
</ul>
</li>
<li><p>Complete Binary Tree</p>
<ul>
<li>모든 leaf 노드들은 같은 level에 있으면서(같은 depth를 가지면서) 왼쪽면에서 채워지는 트리. 마지막 레벨을 제외한 레벨은 full<ul>
<li>$d$ depth에서 complete binary tree의 노드 수는 $2^d$</li>
<li>$n$개의 노드를 가진 complete binary tree의 높이는 $\log(n+1)$</li>
</ul>
</li>
</ul>
</li>
<li><p>Perfect Binary Tree</p>
<ul>
<li>internal 노드들 모두 두 자식 노드들을 가지고 있으며 모든 leaf 노드들은 같은 level에 있다.<ul>
<li>$h$ 높이의 perfect binary tree는 $2^{h+1} - 1$개의 노드들을 갖는다.</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>구현</p>
<p>원소 검색</p>
<pre><code class="language-cpp">노드 주소 find(찾는 값, 노드 주소)
    if 노드 주소가 없다면
        종료 또는 현재 노드 반환(insert 함수 때 재사용시)
    if 노드 주소의 값 == 찾는 값
        노드 주소 반환
    if 노드 주소의 값 &gt; 찾는 값
        find(찾는 값, 노드 오른쪽 주소) 함수 반환
    find(찾는 값, 노드 왼쪽 주소) 함수 반환</code></pre>
<p>원소 삽입</p>
<pre><code class="language-cpp">insert(값)
    if root가 없으면
        root에 값 저장
    else
        _insert(root, 값)

_insert(노드, 값)
    if 값 &lt; 노드의 값
        if 노드의 왼쪽이 없으면
            노드의 왼쪽에 노드 생성 후 값 저장
        else
            _insert(노드의 왼쪽 주소, 값)
    else
        if 노드의 오른쪽이 없으면
            노드의 오른쪽에 노드 생성 후 값 저장
        else
            _insert(노드의 오른쪽 주소, 값)
---------------------------------
insert(값)
    find(값, 루트) 반환된 주소 값에 노드 생성 후 값 저장</code></pre>
<p>원소 삭제 - 여러 방식이 있다.</p>
<pre><code class="language-cpp">/* 자식 노드가 없는 경우
 * -&gt; 단순 삭제
 * 자식 노드가 하나만 있는 경우
 * -&gt; 노드 삭제 후 부모 노드의 포인터가 해당 자식 노드를 가리키게 한다.
 * 자식 노드가 두 개 있는 경우
 * -&gt; 노드 삭제 후, 현재 노드를 후속 노드로 대체
 * -&gt; successor를 구할지 predecessor를 구하는 건 마음이 가는대로.
 */

/* 전임자냐 후임자냐 == 삭제하는 노드의 값 바로 전의 값이나 후의 값으로 교체할지 결정 */
노드 successor(노드) // Left-most child of the right subtree of the target node
    current 노드 변수에 노드의 오른쪽 주소를 저장
    while(current가 없으면서 current의 왼쪽이 없으면)
        current에 current의 왼쪽 주소 저장
    current 반환 // successor 노드 반환

노드 predecessor(노드) // Right-most child of the left subtree of the target node
    current 노드 변수에 노드의 왼쪽 주소를 저장
    while(current가 없으면서 current의 오른쪽이 없으면)
        current에 current의 오른쪽 주소 저장
    current 반환 // predecessor 노드 반환
----------------------------------------------

노드 delete(노드, 찾는 값)
    if 찾는 값 &lt; 노드의 값
        노드의 왼쪽 = delete(노드의 왼쪽, 찾는 값)
    else if 찾는 값 &gt; 노드의 값
        노드의 오른쪽 = delete(노드의 오르쪽, 찾는 값)
    /* 위의 코드는 원소 검색 함수로 재사용 가능하다 */
    /* if find(노드)의 반환 값이 NULL이라면 NULL 반환 */
    else
        if 노드의 left가 없다면 // 자식 노드가 전혀 없거나, 왼쪽 자식 노드만 없는 경우
            노드의 오른쪽 주소를 변수에 저장
            노드 삭제
            저장한 주소 값 반환
        if 노드의 right가 없다면 // 오른쪽 자식 노드만 없는 경우
            노드의 왼쪽 주소를 변수에 저장
            노드 삭제
            저장한 주소 값 반환
        // 자식 노드가 둘 다 있는 경우
        노드의 후속노드를 가져와서 노드의 데이터에 후속노드의 데이터를 저장
        // 오른쪽 서브 트리에서 후속노드 찾아 삭제
        노드의 오른쪽에 delete(노드의 오른쪽, 후속노드의 데이터) 함수 반환 값 저장
    노드 반환
----------------------------------------------
</code></pre>
<p>트리 연산 시간 복잡도</p>
<ul>
<li>BST 검색에 필요한 시간 T(N) = T(N/2) + 1 = O(\log N)<ul>
<li>Complete binary tree가 아닌 한 이 시간 복잡도는 항상 성립하지 않는다.</li>
<li>균형 이진 트리가 이 문제를 해결</li>
</ul>
</li>
</ul>
<p>균형 balanced trees</p>
<p>문제는 BST가 어떻게 데이터가 삽입됐냐에 따라서 효율성이 나뉜다.</p>
<p>→ 최악의 경우 연결 리스트, 최상의 경우 complete binary tree</p>
<p>기준 노드의 양쪽 서브트리의 높이(h)의 차가 -1, 0, 1인 경우 balanced 균형잡힌 상태라 한다.</p>
<ul>
<li><p>용어</p>
<ul>
<li>mountain - 양쪽 노를 가지고 있는 형태</li>
<li>stick - 한방향 + 하나의 자식 노드만으로 이루어진 형태</li>
<li>rotation - 회전</li>
<li>balance factor - 두 서브트리 높이의 차. 여기서는 ‘BF = RSH - LSH’로 계산.</li>
<li>elbow - 하나의 자식 노드들로 이루어졌으며 오른쪽 자식 노드에서 왼쪽 자식노드를 가진 형태(팔 구부린 형상)</li>
</ul>
</li>
<li><p>목표</p>
<ul>
<li>balance factor가 큰 stick이나 elbow 형태인 트리를 rotation하여 mountain 형태로 변형</li>
</ul>
</li>
<li><p>Rotations</p>
<ul>
<li><p>balance factor를 구하고 거기에 맞춰서 회전을 하여 균형있는 형태로 변형</p>
</li>
<li><p>Left Rotation</p>
<ul>
<li>BF = RSH(2) - LSH(0) = 2 &amp; BF = RSH(1) - LSH(0) = 1
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/9cd07be0-6851-49cc-9682-6eed6ee2cf83/image.webp" alt=""></li>
</ul>
</li>
<li><p>Right Rotation</p>
<ul>
<li>BF = RSH(0) - LSH(2) = -2 &amp; BF = RSH(0) - LSH(1) = -1
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/059a8c54-992d-406d-b905-dec1cbcbc225/image.webp" alt=""></li>
</ul>
</li>
<li><p>Left-Right Rotation</p>
<ul>
<li>BF = RSH(0) - LSH(2) = -2 &amp; BF = RSH(1) - LSH(0) = 1</li>
<li>elbow → stick → mountain
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/c5315048-b7bd-426e-b2e0-62516d593d0c/image.webp" alt=""></li>
</ul>
</li>
<li><p>Right-Left Rotation</p>
<ul>
<li>BF = RSH(2) - LSH(0) = 2 &amp; BF = RSH(0) - LSH(1) = -1</li>
<li>elbow → stick → mountain
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/13812b43-cec0-4451-a2f7-848763c2cd3c/image.webp" alt=""></li>
</ul>
</li>
</ul>
</li>
<li><p>Rotation summary - BF = RSH - LSH로 한 표. LSH - RSH로 했을 때는 부호를 바꾸면 된다.</p>
</li>
</ul>
<pre><code>|  | Balance factor of the lowest point of imbalance | Balance factor of the node in the direction of imbalance |
| --- | --- | --- |
| Left Rotation | 2 | 1 |
| Right Rotation | -2 | -1 |
| L-R Rotat | -2 | 1 |
| R-L Rotation | 2 | -1 |</code></pre><p>  <img src="https://velog.velcdn.com/images/kkj-100-010-110/post/34722728-220e-47d6-9eb1-d6e34e1ce332/image.webp" alt=""></p>
<p>위의 균형 트리를 AVL 트리라 한다.</p>
<ul>
<li>이진 트리와 같다. 다만, 삽입과 삭제에서 더 많은 작업을 해야한다.<ul>
<li>어떤 작업?<ul>
<li>balance factor를 빠르게 계산하기 위해 각 노드의 높이를 저장</li>
<li>삽입 및 삭제시 회전하고 새로 높이를 업데이트한다.</li>
</ul>
</li>
</ul>
</li>
<li>연산<ul>
<li>탐색 - BST와 같다.</li>
<li>삽입 - BST와 같이 삽입하고 Balance Factor를 체크한 후 -1, 0 또는 1이 아닌 경우 Rotation 한다.</li>
<li>삭제 - BST와 같이 선임자 노드를 찾고 swap한 후 삭제하고 Balance Factor를 체크한 후  -1, 0 또는 1일 경우 Rotation한다.</li>
</ul>
</li>
</ul>
<p>코드 설명 : 기본 bst로 만든 dictionary 코드와 비교</p>
<p>BST</p>
<pre><code class="language-cpp">/* ADT */
template&lt;typename K, typename D&gt;
class Dictionary
{
public:
    Dictionary();
    const D&amp; find(const K&amp; key);
    void insert(const K&amp; key, const D&amp; data);
    const D&amp; remove(const K&amp; key);
    const D&amp; max() const;
    const D&amp; min() const;
    void print();

private:
    class TreeNode
    {
    public:
        const K&amp; key;
        const D&amp; data;
        TreeNode* left;
        TreeNode* right;
        TreeNode(const K&amp; key, const D&amp; data);
    };

    TreeNode* root_;

    // all in private to hide the information
    const D&amp; _max(TreeNode* cur) const;
    const D&amp; _min(TreeNode* cur) const;

    TreeNode*&amp; _find(const K&amp; key, TreeNode*&amp; cur) const;
    const D&amp; _remove(TreeNode*&amp; node);
    TreeNode*&amp; _predecessor(TreeNode*&amp; cur) const;
    TreeNode*&amp; _successor(TreeNode*&amp; cur) const;
    TreeNode*&amp; _rightmost(TreeNode*&amp; cur) const;
    TreeNode*&amp; _leftmost(TreeNode*&amp; cur) const;
    TreeNode*&amp; _swap(TreeNode*&amp; node1, TreeNode*&amp; node2);
};</code></pre>
<pre><code class="language-cpp">/* key값으로 node 찾기 */
template &lt;typename K, typename D&gt;
const D&amp; Dictionary&lt;K, D&gt;::find(const K&amp; key)
{
    TreeNode*&amp; node = _find(key, root_);
    if (node == nullptr) throw std::runtime_error(&quot;Error: key not found in _find()&quot;);
    return node-&gt;data;
}

template&lt;typename K, typename D&gt;
typename Dictionary&lt;K, D&gt;::TreeNode*&amp; Dictionary&lt;K, D&gt;::_find(const K&amp; key, TreeNode*&amp; cur) const
{
    if (cur == nullptr || key == cur-&gt;key)
        return cur;
    else if (key &lt; cur-&gt;key)
        return _find(key, cur-&gt;left);
    else
        return _find(key, cur-&gt;right);
}

/* find() 함수로 노드를 찾고 삽입하기 */
template &lt;typename K, typename D&gt;
void Dictionary&lt;K, D&gt;::insert(const K&amp; key, const D&amp; data)
{
    TreeNode*&amp; node = _find(key, root_);
    node = new TreeNode(key, data);
}</code></pre>
<pre><code class="language-cpp">
/* key값으로 노드 삭제 */
template &lt;typename K, typename D&gt;
const D&amp; Dictionary&lt;K, D&gt;::remove(const K&amp; key)
{
    TreeNode*&amp; node = _find(key, root_);
    return _remove(node);
}

template&lt;typename K, typename D&gt;
const D&amp; Dictionary&lt;K, D&gt;::_remove(TreeNode*&amp; node)
{
    std::cout &lt;&lt; std::endl;
    if (!node) {
        throw std::runtime_error(&quot;Error: _remove() used on non-existent key&quot;);
    }
    if (node-&gt;left == nullptr ) {
        const D&amp; data = node-&gt;data;
        TreeNode* tmp = node;
        node = node-&gt;right;
        delete tmp;
        return data;
    }
    if (node-&gt;right == nullptr) {
        const D&amp; data = node-&gt;data;
        TreeNode* tmp = node;
        node = node-&gt;left;
        delete tmp;
        return data;
    }
    TreeNode*&amp; predecessor = _predecessor(node);
    // TreeNode*&amp; successor = _successor(node);
    if (!predecessor)
        throw std::runtime_error(&quot;Error: predecessor not found&quot;);
    TreeNode *&amp;moved_node = _swap(node, predecessor);
    return _remove(moved_node);
}

template &lt;typename K, typename D&gt;
typename Dictionary&lt;K, D&gt;::TreeNode*&amp; Dictionary&lt;K, D&gt;::_predecessor(TreeNode*&amp; cur) const
{
    if (!cur)
        return cur;
    if (!(cur-&gt;left))
        return cur-&gt;left;
    return _rightmost(cur-&gt;left);
}

template &lt;typename K, typename D&gt;
typename Dictionary&lt;K, D&gt;::TreeNode*&amp; Dictionary&lt;K, D&gt;::_successor(TreeNode*&amp; cur) const
{
    if (!cur)
        return cur;
    if (!(cur-&gt;right))
        return cur-&gt;right;
    return _leftmost(cur-&gt;right);
}

template &lt;typename K, typename D&gt;
typename Dictionary&lt;K, D&gt;::TreeNode*&amp; Dictionary&lt;K, D&gt;::_rightmost(TreeNode*&amp; cur) const
{
    if (!cur || !(cur-&gt;right))
        return cur;
    return _rightmost(cur-&gt;right);
}

template &lt;typename K, typename D&gt;
typename Dictionary&lt;K, D&gt;::TreeNode*&amp; Dictionary&lt;K, D&gt;::_leftmost(TreeNode*&amp; cur) const
{
    if (!cur || !(cur-&gt;left))
        return cur;
    return _leftmost(cur-&gt;left);
}

template &lt;typename K, typename D&gt;
typename Dictionary&lt;K, D&gt;::TreeNode*&amp; Dictionary&lt;K, D&gt;::_swap(TreeNode*&amp; node1, TreeNode*&amp; node2)
{
    // for _predecessor()
    if (node1-&gt;left == node2) {
        TreeNode *tmp = node2;
        std::swap(node1-&gt;right, node2-&gt;right);
        node1-&gt;left = tmp-&gt;left;
        tmp-&gt;left = node1;
        node1 = tmp;
        return node1-&gt;left;
    }
    // for _successor()
    if (node1-&gt;right == node2)
    {
        TreeNode *tmp = node2;
        std::swap(node1-&gt;left, node2-&gt;left);
        node1-&gt;right = tmp-&gt;right;
        tmp-&gt;right = node1;
        node1 = tmp;
        return node1-&gt;right;
    }
    std::swap(node1-&gt;left, node2-&gt;left);
    std::swap(node1-&gt;right, node2-&gt;right);
    std::swap(node1, node2);
    return node2;
}</code></pre>
<pre><code class="language-cpp">/* 나머지 함수들의 body */
/** constructor of Dictionary class */
template &lt;typename K, typename D&gt;
Dictionary&lt;K, D&gt;::Dictionary()
    : root_(nullptr)
{}

/** constructor of TreeNode class in  Dictionary class */
template &lt;typename K, typename D&gt;
Dictionary&lt;K, D&gt;::TreeNode::TreeNode(const K&amp; key, const D&amp; data)
    : key(key), data(data), left(nullptr), right(nullptr)
{}

/** the right-most data of the tree */
template &lt;typename K, typename D&gt;
const D&amp; Dictionary&lt;K, D&gt;::max() const { return _max(root_); }

/** the left-most data of the tree */
template &lt;typename K, typename D&gt;
const D&amp; Dictionary&lt;K, D&gt;::min() const { return _min(root_); }

template &lt;typename K, typename D&gt;
const D&amp; Dictionary&lt;K, D&gt;::_max(TreeNode* cur) const
{
    while (cur &amp;&amp; cur-&gt;right)
        cur = cur-&gt;right;
    return cur-&gt;data;
}

template &lt;typename K, typename D&gt;
const D&amp; Dictionary&lt;K, D&gt;::_min(TreeNode* cur) const
{
    while (cur &amp;&amp; cur-&gt;left)
        cur = cur-&gt;left;
    return cur-&gt;data;
}</code></pre>
<p>AVL 트리 - ADT, 삽입, 삭제</p>
<pre><code class="language-cpp">template&lt;typename K, typename D&gt; // key &amp; data
class AVL
{
public:
    AVL() : head_(nullptr) {}

    const D&amp; find(const K&amp; key);
    bool contains(const K&amp; key);
    void insert(const K&amp; key, const D&amp; data);
    const D&amp; remove(const K&amp; key);
    bool empty() const;
    void clear();

private:
    class Node
    {
    public:
        const K &amp;key;
        const D &amp;data;
        Node *left;
        Node *right;
        int height;
        Node(const K &amp;key, const D &amp;data) : key(key), data(data), left(nullptr), right(nullptr), height(0) {}
    };

    Node* head_;

    Node*&amp; _find(const K&amp; key, Node*&amp; cur) const;
    void _find_and_insert(const K&amp; key, const D&amp; data, Node*&amp; cur);
    const D&amp; _find_and_remove(const K&amp; key, Node*&amp; cur);
    const D&amp; _remove(Node*&amp; node);

    // 삭제를 위해 삭제할 노드와 스왑할 in-order의 전임자를 찾기 위한 함수들
    const D&amp; _iopRemove(Node*&amp; targetNode);
    const D&amp; _iopRemove(Node*&amp; targetNode, Node*&amp; iopAncester, bool isInitialCall);
    Node*&amp; _swap_nodes(Node*&amp; node1, Node*&amp; node2);

    void _updateHeight(Node*&amp; cur);
    void _ensureBalance(Node*&amp; cur);

    void _rotateLeft(Node*&amp; cur);
    void _rotateRight(Node*&amp; cur);
    void _rotateLeftRight(Node*&amp; cur);
    void _rotateRightLeft(Node*&amp; cur);

    int _get_height(Node*&amp; node) const;
    int _get_balance_factor(Node*&amp; node) const;
};</code></pre>
<p>삽입</p>
<pre><code class="language-cpp">// BST와 같지만 Balance Factor를 계산해야한다.
template &lt;typename K, typename D&gt;
void AVL&lt;K, D&gt;::_find_and_insert(const K&amp; key, const D&amp; data, Node*&amp; cur)
{
    if (!cur) {
        cur = new Node(key, data);
        return;
    }
    else if (cur-&gt;key == key) {
        throw std::runtime_error(&quot;ERROR::INSERT::key already exists&quot;);
    }
    else if (cur-&gt;key &gt; key) {
        _find_and_insert(key, data, cur-&gt;left);
        _ensureBalance(cur);
        return ;
    }
    else {
        _find_and_insert(key, data, cur-&gt;right);
        _ensureBalance(cur);
    }
}

template &lt;typename K, typename D&gt;
void AVL&lt;K, D&gt;::_ensureBalance(Node*&amp; cur)
{
    if (!cur) return;

    // cur 노드의 balance factor 계산
    int initial_balance = _get_balance_factor(cur);

    // balance를 확인하고 불균형인 곳이 있으면 그곳의 방향을 찾아 회전시킨다.
    if (initial_balance == -2)
    {
        const int left_balance = _get_balance_factor(cur-&gt;left);
        if ((left_balance == -1) || (left_balance == 0))
            _rotateRight(cur);
        else if (left_balance == 1)
            _rotateLeftRight(cur);
        else
            // error check;
    }
    else if (initial_balance == 2)
    {
        const int right_balance = _get_balance_factor(cur-&gt;right);
        if ((right_balance == 1) || (right_balance == 0))
            _rotateLeft(cur);
        else if (right_balance == -1)
            _rotateRightLeft(cur);
        else
            // error check;
    }
        // 높이를 업데이트
    _updateHeight(cur);

    // 마지막 error check
    const int final_balance = _get_balance_factor(cur);
    if (final_balance &lt; -1 || final_balance &gt; 1)
    {
        //error check
    }
}

template &lt;typename K, typename D&gt;
void AVL&lt;K, D&gt;::_updateHeight(TreeNode*&amp; cur) 
{
  if (!cur) return;
  cur-&gt;height = 1 + std::max(_get_height(cur-&gt;left), _get_height(cur-&gt;right));
}

int _get_height(TreeNode*&amp; node) const 
{
      if (!node) 
        return -1;
      else
        return node-&gt;height;
}</code></pre>
<p>삭제</p>
<pre><code class="language-cpp">template &lt;typename K, typename D&gt;
const D &amp;AVL&lt;K, D&gt;::_find_and_remove(const K&amp; key, Node*&amp; cur)
{
    if (!cur) throw std::runtime_error(&quot;ERROR::FIND_AND_REMOVE::key not found&quot;);
    else if (cur-&gt;key == key) return _remove(cur);
    else if (cur-&gt;key &lt; key)
    {
        const D&amp; data = _find_and_remove(key, cur-&gt;right);
        _ensureBalance(cur);
        return data;
    }
    else
    {
        const D&amp; data = _find_and_remove(key, cur-&gt;left);
        _ensureBalance(cur);
        return data;
    }
}

template &lt;typename K, typename D&gt;
const D&amp; AVL&lt;K, D&gt;::_remove(Node*&amp; node)
{
    if (!node) throw std::runtime_error(&quot;ERROR::REMOVE::the function used on nullptr&quot;);

    if (!node-&gt;left &amp;&amp; !node-&gt;right) // 자식이 없을때
    {
        const D&amp; data = node-&gt;data;
        delete node;
        node = nullptr;
        return data;
    }
    else if (node-&gt;left &amp;&amp; !node-&gt;right) // 왼쪽만
    {
        const D&amp; data = node-&gt;data;
        Node* temp = node;
        node = node-&gt;left;
        delete temp;
        temp = nullptr;
        return data;
    }
    else if (!node-&gt;left &amp;&amp; node-&gt;right) // 오른쪽만
    {
        const D&amp; data = node-&gt;data;
        Node* temp = node;
        node = node-&gt;right;
        delete temp;
        temp = nullptr;
        return data;
    }
    else // 둘다
    {
        return _iopRemove(node);
    }
}

// 선임자 찾는 함수들
template &lt;typename K, typename D&gt;
const D&amp; AVL&lt;K, D&gt;::_iopRemove(Node*&amp; targetNode)
{
    if (!targetNode) throw std::runtime_error(&quot;ERROR::IOP_REMOVE:: this function called on nullptr&quot;);

    const D&amp; data = _iopRemove(targetNode, targetNode-&gt;left, true);

    if (targetNode-&gt;left)
        _ensureBalance(targetNode-&gt;left);
    _ensureBalance(targetNode);
    return data;
}

template &lt;typename K, typename D&gt;
const D&amp; AVL&lt;K, D&gt;::_iopRemove(Node*&amp; targetNode, Node*&amp; iopAncester, bool isInitialCall)
{
    if (!targetNode) throw std::runtime_error(&quot;ERROR::IOP_REMOVE:: targetNode is nullptr&quot;);
    if (!iopAncester) throw std::runtime_error(&quot;ERROR::IOP_REMOVE:: iopAncester is nullptr&quot;);

    if (!iopAncester-&gt;right)
    {
        const D &amp;data = _iopRemove(targetNode, iopAncester-&gt;right, false);
        if (!isInitialCall)
            if (iopAncester)
                _ensureBalance(iopAncester);
        return data;
    }
    else
    {
        Node*&amp; movedTarget = _swap_nodes(targetNode, iopAncester);
        const D&amp; data = _remove(movedTarget);
        return data;
    }
}

// 선임자를 찾고 그 노드와 삭제할 노드 스왑
template &lt;typename K, typename D&gt;
typename AVL&lt;K, D&gt;::Node*&amp; AVL&lt;K, D&gt;::_swap_nodes(Node*&amp; node1, Node*&amp; node2)
{
    Node* orig_node1 = node1;
    Node* orig_node2 = node2;
    std::swap(node1-&gt;height, node2-&gt;height);
    if (node1-&gt;left == node2)
    {
        std::swap(node1-&gt;right, node2-&gt;right);
        node1-&gt;left = orig_node2-&gt;left;
        orig_node2-&gt;left = node1;
        node1 = orig_node2;
        return node1-&gt;left;
    }
    else if (node1-&gt;right == node2)
    {
        std::swap(node1-&gt;left, node2-&gt;left);
        node1-&gt;right = orig_node2-&gt;right;
        orig_node2-&gt;right = node1;
        node1 = orig_node2;
        return node1-&gt;right;
    }
    else if (node2-&gt;left == node1)
    {
        std::swap(node2-&gt;right, node1-&gt;right);
        node2-&gt;left = orig_node1-&gt;left;
        orig_node1-&gt;left = node2;
        node2 = orig_node1;
        return node2-&gt;left;
    }
    else if (node2-&gt;right == node1)
    {
        std::swap(node2-&gt;left, node1-&gt;left);
        node2-&gt;right = orig_node1-&gt;right;
        orig_node1-&gt;right = node2;
        node2 = orig_node1;
        return node2-&gt;right;
    }
    else
    {
        std::swap(node1-&gt;left, node2-&gt;left);
        std::swap(node1-&gt;right, node2-&gt;right);
        std::swap(node1, node2);
        return node2;
    }
}</code></pre>
<p><a href="https://www.cs.usfca.edu/~galles/visualization/AVLtree.html">https://www.cs.usfca.edu/~galles/visualization/AVLtree.html</a></p>
<p>구현 후 비교하면서 에러 체크.</p>
<p><strong>Left-Leaning Red-Black Tree</strong></p>
<p>모든 레드 블랙 트리는 2-3 또는 2-3-4 트리 구현에 기초한다.</p>
<ul>
<li>재귀적 구현</li>
<li>모든 3-노드들은 왼쪽으로 기울게 한다.</li>
<li>트리로 올라가면서 Rotation을 수행.</li>
</ul>
<p>2-3 tree는 3차인 B-tree.</p>
<p>성질</p>
<ul>
<li>두 개의 자식노드를 가지고 있으면 2-node라 한다. 2-node는 하나의 데이터 값과 두 자식 노드를 갖는다.</li>
<li>세 개의 자식노드를 가지고 있으면 3-node라 한다. 3-node는 두 데이터 값들과 세 자식 노드를 갖는다.</li>
<li>데이터 값은 정렬되어 저장.</li>
<li>균형 트리</li>
<li>모든 자식 노드는 같은 레벨이다.</li>
</ul>
<p>Red Black Tree(Left Leaning Red Black Tree)</p>
<p>성질</p>
<ul>
<li>레드 링크는 왼쪽으로 기울어져있다.</li>
<li>부모로부터의 연결이 레드 링크면 레드 노드</li>
<li>어떤 노드도 레드 링크 두 개와 연결될 수 없다.</li>
<li>레드 노드의 자식은 블랙</li>
<li>null 노드는 블랙</li>
<li>루트 노드는 블랙</li>
<li>블랙을 기준으로 균형잡혀있다. (레드는 무시)<ul>
<li>어떤 노드에서 null 노드까지의 모든 경로의 블랙 거리는 같다.</li>
</ul>
</li>
</ul>
<p>연산(회전, 색 뒤집기)이 일어나는 상황</p>
<ul>
<li>레드 링크가 오른쪽에 있을때(right leaning red link)</li>
<li>트리가 편향되었을때 → 같은 방향으로 노드가 계속 추가 되었을때</li>
</ul>
<p>Insertion
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/f44b7907-2b69-44cf-b4e6-6e0ff341650c/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/b9761ece-c8fa-41a3-96dd-492a75278113/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/4ca6af49-ebd6-4cf0-88f6-eec78a679948/image.png" alt=""></p>
<p>삽입 코드</p>
<pre><code class="language-cpp">
void insert(const K&amp; key, const D&amp; data);

bool _isRed(Node*&amp; node) const;
void _insert(Node*&amp; node, const K&amp; key, const D&amp; data);
void _balance(Node*&amp; node);
void _rotateRight(Node*&amp; node);
void _rotateLeft(Node*&amp; node);
void _colorFlip(Node*&amp; node);
Color _colorChange(Color c);</code></pre>
<pre><code class="language-cpp">template &lt;typename K, typename D&gt;
bool RB&lt;K, D&gt;::_isRed(Node*&amp; node) const
{
    if (!node) return BLACK; // null(nil) nodes are black.
    return node-&gt;color == RED;
}

template &lt;typename K, typename D&gt;
void RB&lt;K, D&gt;::insert(const K&amp; key, const D&amp; data)
{
    _insert(root, key, data);
    root-&gt;color = BLACK; // 루트는  항상 블랙
}

template &lt;typename K, typename D&gt;
void RB&lt;K, D&gt;::_insert(Node*&amp; node, const K&amp; key, const D&amp; data)
{
    if (!node) {
        node = new Node(key, data, RED, 1);
        return;
    }
    else if (node-&gt;key == key) {
        Node* tmp = node;
        Node* n = new Node(node-&gt;key, data, node-&gt;color, node-&gt;size);
        n-&gt;left = node-&gt;left;
        n-&gt;right = node-&gt;right;
        node = n;
        delete tmp;
        return;
    }
    else if (node-&gt;key &lt; key) {
        _insert(node-&gt;right, key, data);
    }
    else { //node-&gt;key &gt; key
        _insert(node-&gt;left, key, data);
    }

    if (_isRed(node-&gt;right) &amp;&amp; !_isRed(node-&gt;left)) _rotateLeft(node);
    if (_isRed(node-&gt;left) &amp;&amp; _isRed(node-&gt;left-&gt;left)) _rotateRight(node);
    if (_isRed(node-&gt;left) &amp;&amp; _isRed(node-&gt;right)) _colorFlip(node);

    node-&gt;size = 1 + _size(node-&gt;left) + _size(node-&gt;right);
}

template &lt;typename K, typename D&gt;
void RB&lt;K, D&gt;::_rotateRight(Node*&amp; node)
{
    Node* tmp = node-&gt;left;
    node-&gt;left = tmp-&gt;right;
    tmp-&gt;right = node;
    tmp-&gt;color = node-&gt;color;
    node-&gt;color = RED;
    tmp-&gt;size = node-&gt;size;
    node-&gt;size = 1 + _size(node-&gt;left) + _size(node-&gt;right);
    node = tmp;
}

template &lt;typename K, typename D&gt;
void RB&lt;K, D&gt;::_rotateLeft(Node*&amp; node)
{
    Node* tmp = node-&gt;right;
    node-&gt;right = tmp-&gt;left;
    tmp-&gt;left = node;
    tmp-&gt;color = node-&gt;color;
    node-&gt;color = RED;
    tmp-&gt;size = node-&gt;size;
    node-&gt;size = 1 + _size(node-&gt;left) + _size(node-&gt;right);
    node = tmp;
}

template &lt;typename K, typename D&gt;
void RB&lt;K, D&gt;::_colorFlip(Node*&amp; node)
{
    node-&gt;color = _colorChange(node-&gt;color);
    node-&gt;left-&gt;color = _colorChange(node-&gt;left-&gt;color);
    node-&gt;right-&gt;color = _colorChange(node-&gt;right-&gt;color);
}

template &lt;typename K, typename D&gt;
typename RB&lt;K, D&gt;::Color RB&lt;K, D&gt;::_colorChange(Color c)
{
    return c == BLACK ? RED : BLACK;
}</code></pre>
<p>삭제 - 어렵다.</p>
<p>공통적으로 가장 인기있는 방식은 부모 포인터를 기반으로 둔다. 이는 상당한 오버헤드를 가지고 처리되어야할 것들이 줄어주지 않는다.</p>
<p>LLRB 2-3 트리에 관한 <code>delete()</code> 함수는 탑다운 2-3-4 트리에서 삽입 연산에 사용된 접근 방식의 역을 토대로 둔다. 이는 2-노드에서 탐색이 끝나지 않게 보장하기 위해서 탐색 경로로 내려가면서 회전과 색 변환을 수행한다. 그래서 가장 아래에서 노드를 삭제할 수 있다. 삽입 연산시 재귀 호출에 따라오는<code>colorFlip()</code>, <code>rotateRight()</code>와 <code>rotateLeft()</code>에 사용되는 코드를 공유하기 위해 코드를 <code>_fixUp()</code>함수로 사용. 이 함수덕에 탐색 경로를 따라 내려가면서 나오는 오른쪽의 레드 링크와 균형이 깨진 4-노드들은 그대로 나둘 수 있다. 남겨진 불균형 부분들은 이 함수를 통해 트리를 올라가면서 고쳐질 것이다. 여기서 오른쪽 노드가 4-노드일때 추가 회전이 요구된다.</p>
<pre><code class="language-cpp">// 삽입에서 사용하던 코드를 함수로 만들어 사용
template &lt;typename K, typename D&gt;
void RB&lt;K, D&gt;::_fixUp(Node*&amp; node)
{
    if (!node) return ;
    if (_isRed(node-&gt;right) &amp;&amp; !_isRed(node-&gt;left))
        _rotateLeft(node);
    if (_isRed(node-&gt;left) &amp;&amp; _isRed(node-&gt;left-&gt;left))
        _rotateRight(node);
    if (_isRed(node-&gt;left) &amp;&amp; _isRed(node-&gt;right))
        _colorFlip(node);

    node-&gt;size = 1 + _size(node-&gt;left) + _size(node-&gt;right);
}
// 삽입 코드는 아래와 같이 된다.
template &lt;typename K, typename D&gt;
void RB&lt;K, D&gt;::_insert(Node*&amp; node, const K&amp; key, const D&amp; data)
{
    if (!node) {
        node = new Node(key, data, RED, 1);
        return;
    }
    else if (node-&gt;key == key) {
        Node* tmp = node;
        Node* n = new Node(node-&gt;key, data, node-&gt;color, node-&gt;size);
        n-&gt;left = node-&gt;left;
        n-&gt;right = node-&gt;right;
        node = n;
        delete tmp;
        return;
    }
    else if (node-&gt;key &lt; key) {
        _insert(node-&gt;right, key, data);
    }
    else { //node-&gt;key &gt; key
        _insert(node-&gt;left, key, data);
    }
    _fixUp(node);
}</code></pre>
<p>먼저 웜업으로 <code>deletion-the-minimum</code> 연산을 고려해보면, 균형을 유지하면서 좌측 최하단의 노드를 삭제하는 것이 목적이다. 그렇게 하기 위해서 현재 노드나 그 노드의 왼쪽 자식이 레드인 것을 변하지 않게 유지한다. 현재 노드가 레드이고 그 노드의 왼쪽 자식과 왼쪽의 손자 노드 모두 블랙이 아닌 경우가 아니라면 왼쪽으로 이동함으로서 연산 수행할 수 있다. 이런 경우 <code>colorFlip</code>으로 트리가 변하지 않게 유지할 수 있지만 오른쪽으로 레드 링크로 이어진 노드를 맞이할 수 있다. 이 경우엔 두 회전과 <code>colorFlip</code>으로 교정할 수 있다. 이러한 연산들은 <code>moveRedLeft</code>에서 구현된다.</p>
<pre><code class="language-cpp">template &lt;typename K, typename D&gt;
void RB&lt;K, D&gt;::deleteMin()
{
    _deleteMin(root);
    root-&gt;color = BLACK;
}

template &lt;typename K, typename D&gt;
void RB&lt;K, D&gt;::_deleteMin(Node*&amp; node)
{
    if (!node-&gt;left) {
        delete node;
        node = nullptr;
        return;
    }

    if (!_isRed(node-&gt;left) &amp;&amp; !_isRed(node-&gt;left-&gt;left))
        _moveRedLeft(node);

    _deleteMin(node-&gt;left);
    _fixUp(node);
}

template &lt;typename K, typename D&gt;
void RB&lt;K, D&gt;::_moveRedLeft(Node*&amp; node)
{
    _colorFlip(node);
    if (_isRed(node-&gt;right-&gt;left))
    {
        _rotateRight(node-&gt;right);
        _rotateLeft(node);
        _colorFlip(node);
    }
}

template &lt;typename K, typename D&gt;
void RB&lt;K, D&gt;::deleteMax()
{
    _deleteMax(root);
    root-&gt;color = BLACK;
}

template &lt;typename K, typename D&gt;
void RB&lt;K, D&gt;::_deleteMax(Node*&amp; node)
{
    if (_isRed(node-&gt;left))
        rotateRight(node);

    if (!node-&gt;right)
    {
        delete node;
        node = nullptr;
        return;
    }

    if (!_isRed(node-&gt;right) &amp;&amp; !_isRed(node-&gt;right-&gt;left))
        _moveRedRight(node);

    _deleteMax(node-&gt;right);

    _fixUp(node);
}

template &lt;typename K, typename D&gt;
void RB&lt;K, D&gt;::_moveRedRight(Node*&amp; node)
{
    _colorFlip(node);
    if (_isRed(node-&gt;left-&gt;left))
    {
        _rotateRight(node);
        _colorFlip(node);
    }
}</code></pre>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/11db04db-2068-49ce-9beb-04941bb3c92e/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/5cc20c04-f619-4fd3-adbc-65e1a6676971/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/8314d553-8729-4154-9722-2ffb07a3e0fe/image.png" alt=""></p>
<p>앞서 말했듯이 Top-down 2-3-4트리로 조건에 맞게 3, 4-노드로 변환하면서 경로로 내려온다. 삭제 후 재귀적으로 <code>fixUp()</code> 호출하여 규칙에 맞게 다시 갖춰진다.<code>deleteMin()</code>과 <code>deleteMax()</code>를 잘 보고 그려보면 된다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/f7714338-c4c3-46d6-88d8-953c08935510/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/452c20fb-2330-4507-b3be-3880ef26c2a3/image.png" alt=""></p>
<p>일반적인 삭제는 <code>moveRedRight</code>가 필요. <code>deleteMax()</code>에서 사용해봤다. 이 함수는 <code>moveRedLeft()</code>와 유사하지만 더 간단하다. 트리를 불변하게 유지 시키기 위해서 왼쪽의 레드 링크를 탐색경로에서 오른쪽으로 회전시켜야한다. 만약 internal 노드를 삭제할 때 오른쪽 서브트리에서의 최소 노드와 값과 데이터를 바꾸고 그 최소노드를 삭제한다.</p>
<pre><code class="language-cpp">template &lt;typename K, typename D&gt;
void RB&lt;K, D&gt;::_remove(Node*&amp; node, const K&amp; key)
{
    if (key &lt; node-&gt;key) { // when the search path is towards the left sub-tree of the node, similar to deleteMin()
        if (!_isRed(node-&gt;left) &amp;&amp; !_isRed(node-&gt;left-&gt;left))
            _moveRedLeft(node);
        _remove(node-&gt;left, key);
    }
    else { // when the search path is towards the right sub-tree of the node, similar to deleteMax() and when it finds the target
        if (_isRed(node-&gt;left)) _rotateRight(node);

        // when found the key and its right-sub-tree doesn&#39;t exist
        if (key == node-&gt;key &amp;&amp; node-&gt;right == nullptr) {
            delete node;
            node = nullptr;
            return;
        }

        if (!_isRed(node-&gt;right) &amp;&amp; !_isRed(node-&gt;right-&gt;left))
            _moveRedRight(node);

        if (key == node-&gt;key) {
            Node* tmp = _min(node-&gt;right);
            Node* replace = new Node(tmp-&gt;key, tmp-&gt;data, node-&gt;color, node-&gt;size);
            replace-&gt;left = node-&gt;left;
            replace-&gt;right = node-&gt;right;
            tmp = node;
            node = replace;
            delete tmp;
            _deleteMin(node-&gt;right);
        }
        else
            _remove(node-&gt;right, key);
    }
    _fixUp(node);
}</code></pre>
<ul>
<li>출처 : 홍정모의 알고리듬 압축코스 part 1 &amp; <a href="https://sedgewick.io/wp-content/themes/sedgewick/papers/2008LLRB.pdf">https://sedgewick.io/wp-content/themes/sedgewick/papers/2008LLRB.pdf</a></li>
</ul>
<p>Dictionary, AVL and Red-Black tree 코드 : <a href="https://github.com/kkj-100-010-110/tree">https://github.com/kkj-100-010-110/tree</a></p>
<p><strong>B-Tree</strong></p>
<p>디스크 드라이브나 직접 접근 기억 장치들(direct-access secondary storage devices) 잘 작동하기 위해 디자인된 균형 이진 탐색 트리이다. 레드 블랙 트리와 비슷하지만 B-tree는 디스크 접근 연산들을 최소화 부분에서 더 낫다. 많은 데이터 베이스에서 이 트리를 사용한다.</p>
<p>B-tree 노드는 다양한 노드 수를 가질 수 있다. B-tree의 ‘branching factor’는 꽤 클 수 있다. 다만, 이 크기는 사용되는 디스크 드라이브에 의존적. log의 밑은 높이를 나타내는데 branching factor가 커질 수록 이 높이는 매우 낮아진다.</p>
<p>B-tree는 자연스럽게 이진 탐색 트리를 일반화한 것. </p>
<ul>
<li>Data structure on secondary storage</li>
</ul>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/826a9f04-2a54-443d-ac7e-3c8afcf2ffd8/image.png" alt=""></p>
<p>컴퓨터는 메모리 용량을 제공하는데 다양한 기술들을 이용. 메인 메모리는 실리콘 칩으로 구성되어있고 이는 테이프나 디스크 드라이브 같은 자기 저장 기계(magnetic storage technology)보다 빠르고 비싸다. 대부분의 컴퓨터는 solid-state drives(SSD)나 magnetic disk drive 형태의 secondary storage를 가진다. 상대적으로 느리지만 많은 데이터를 저장할 수 있다.
드라이버 구성</p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/865220ef-0d22-4bbc-b09e-83bd2196ac7d/image.jpg" alt="">
디스크 드라이브는 기계 부품을 움직여야 하므로 굉장히 느리다. 두 부품의 움직임이 있는데 이는 platter들의 회전과 arm들의 움직임이다. 회전의 속도는 분당 5,400-15,000 회전(RPM, revolution per minute)을 가진다. 일반적으로 서버 등급의 드라이브들은 15,000 RPM, 데스크탑 7,200 RPM, 랩탑 5,400 PRM을 가진다. 7,200 RPM은 빨라 보이지만 한번의 회전이 8.33 ms가 소요. 이는 메인 메모리에서 일반적으로 데이터 접근 시간 50 ns 보다 다섯자리 차이 난다. 다른 말로, 컴퓨터가 특정 아이템으로 read/write head에 위치하기 위한 full rotation을 대기하면, 그시간 동안 컴퓨터는 100,000 번 메인 메모리에 접근할 수 있다. 평균 wait 대기하는데 반만 회전하는데 이 또한 메모리 접근 시간에 비하면 굉장히 차이가 크다. arm들의 움직임도 시간이 조금 걸린다. 평균 4 ms 소요.
기계 움직임에 대기하는데 소요되는 시간(latency)를 줄이기 위해서 디스크 드라이브들은 하나의 아이템이 아닌 여러개의 아이템에 한번에 접근한다. 정보(information)는 많은 track 내부에서 연속적으로 나타나는 bit들의 동일한 크기의 block들로 나누어지고, 각 디스크가 읽고 쓰는데 하나 이상의 블록들을 가진다. 전형적인 디스크 블록 사이즈는 512 - 4096 bytes 크기. read/write head가 정확하게 위치해 있고 platter가 원하는 block의 시작점에 회전했을때 자기 저장 디스크 드라이브를 읽고 쓰는 것은 완전한 전자활동을 하며 큰 데이터의 양을 디스크 드라이브는 빠르게 읽고 쓸 수 있다.
종종 디스크 드라이브로부터 정보의 블록에 접근하고 읽는 것이 읽은 모든 정보들을 처리하는 시간보다 더 걸린다. 실행 시간의 두 가지 주요한 요소들은</p>
<ul>
<li>디스크 접근의 수(the number of disk accesses)</li>
<li>CPU 시간</li>
</ul>
<p>디스크 접근 수를 디스크 드라이브에 쓰거나 그것으로부터 읽어오는데 필요한 정보의 block 수로 측정. 디스크 접근 시간은 상수가 아님에도(현재 track과 요구되는 track 사이의 거리에 의존적이며, platter의 초기 회전하는 지점에  또한 의존적), 읽고 쓰여진 block의 수는 디스크 드라이브에 접근하는데 사용된 전체 시간의 괜찮은 1차 근사값(first-order approximation)을 제공한다.</p>
<p>전형적인 B-tree 응용에서 처리되는 데이터의 양은 모든 데이터가 메인 메모리에 한번에 알맞게 들어맞을 수 없을 정도로 크다. B-tree 알고리듬은 필요에 따라 디스크로부터 선택된 blocks을 메인 메모리에 복사하고 변경된 block들을 다시 디스크에 쓴다(write). B-tree 알고리듬은 언제든 메인 메모리에 일정한 크기의 block들을 유지하고 메인 메모리의 크기는 B-tree의 크기를 제한하지 않는다.</p>
<p>B-tree의 절차(procedure)는 디스크로부터 정보를 메인 메모리로 읽어(read)올 수 있어야하고 메인 메모리에서 정보를 디스크로 쓸(write) 수 있어야한다. 오브젝트 x가 있다고 가정. x가 현재 메인 메모리에 있다면, 코드는 x의 어트리뷰트들을 참조할 수 있다. 그렇지만 x가 디스크 드라이브에 있다면, 프로시져는 오브젝트 x를 가지고 있는 block을 메인 메모리로 읽기 위해서 x의 어트리뷰트들을 참조하기전에 DISK-READ(x)를 실행시켜야한다.(만약 x가 메인 메모리에 있으면 DISK-READ(x)는 disk accesss를 필요로 하지 않는다. 이를 ‘no-op’이라 한다.) 유사하게 오브젝트 x 어트리뷰트 변경된 내용들을 저장하고 담고 있는 block을 디스크에 쓰기(write) 위해 DISK-WRITE(x)를 호출한다.</p>
<pre><code class="language-cpp">x = a pointer to some object
DISK-READ(x)
operations that access and/or modify the attributes of x
DISK-WRITE(x) // omitted if no attributes of x were changed
operations that access but do not modify the attributes of x </code></pre>
<p>컴퓨터는 항상 오직 제한된 수의 block들을 메인 메모리에 유지할 수 있다. B-tree 알고리듬은 컴퓨터가 자동으로 더이상 사용하지 않는 block들을 메인 메모리로부터 보내는(flush) 것을 가정한다.</p>
<p>대부분 B-tree 알고리듬의 실행 시간은 DISK-READ와 DISK-WRITE 연산 수에 달려있다. 이 연산들 각각 가능한 많은 정보를 read 또는 write 하기 원한다. 그래서 B-tree의 노드는 보통 전체 디스크 block의 크기 만큼 크고 이 크기는 B-tree가 가질 수 있는 자식의 수를 제한한다.</p>
<p>디스크 드라이브에 저장된 사이즈가 큰 B-tree는 block의 크기에 비례한 key의 크기에 따라 종종 50-2,000 branching factor를 가진다. branching factor가 크다면 트리의 높이와 키를 찾는 디스크 접근의 수도 현저히 줄어든다. branching factor가 1001이고 height가 2인 트리는 키를 10억개 이상 저장할 수 있다. 만약 루트노드가 영구적으로 메인 메모리에 유지되면 트리에서 키를 찾는 경우 많아봐야 2번의 디스크 접근이면 충분하다. </p>
<p>출처 - Introduction Algorithms</p>
<p>참고 자료 <a href="https://www.cs.cornell.edu/courses/cs312/2004sp/lectures/lec24.html">https://www.cs.cornell.edu/courses/cs312/2004sp/lectures/lec24.html</a></p>
<ul>
<li>B-tree 특징 - (출처: 유튜브 - 쉬운코드)<ul>
<li>각 노드의 최대 자식 수를 M이라하면 각 노드의 최대 키의 수는 M-1이다.</li>
<li>루트와 리프 노드를 제외한 각 노드의 최소 자식 수는 $\lceil M/2 \rceil$(무조건 올림, e.g. M이 3이라면 2)</li>
<li>루트 노드를 제외한 각 노드의 최소 키의 수는.  $\lceil M/2 \rceil$ - 1</li>
<li>internal 노드의 키의 수가 x개라면 자녀 노드의 수는 x+1개이다.<ul>
<li>몇 차 B-tree인지 상관없이 internal 노드는 최소한 2개의 자식 노드를 가진다.</li>
<li>M이 정해지면 internal 노드들은 최소  $\lceil M/2 \rceil$ 개의 자식 노드를 가진다.</li>
</ul>
</li>
</ul>
</li>
<li>B-tree 특징 - (출처: geeksforgeeks)<ul>
<li>모든 리프 노드들은 같은 레벨이다.</li>
<li>최소 차수 ‘t’에 의해 정의, ‘t’의 값은 disk block 크기에 의존적</li>
<li>루트 노드를 제외한 모든 노드들은 최소 t-1 키들을 가져야한다. 루트 노드는 최소한 하나의 키를 가지고 있다.</li>
<li>루트를 포함한 모든 노드들은 최대 2*t-1 키들을 가질 수 있다.</li>
<li>한 노드의 자식 수는 그 노드의 키 수에 1 더한 값과 동일</li>
<li>한 노드의 모든 키는 오름차순으로 정렬되어있다. 두 키 k1과 k2 사이의 자식은 k1에서 k2까지의 범위에서 모든 키를 가질 수 있다.</li>
<li>이진 탐색 트리와 달리 루트로 부터 커지거나 줄어든다.</li>
<li>다른 균형 이진 탐색 트리들과 같이 탐색, 삽입, 삭제 연산에서 시간복잡도는 O(log n)</li>
<li>삽입은 항상 리프 노드에서 발생</li>
</ul>
</li>
</ul>
<p><a href="https://www.cs.usfca.edu/~galles/visualization/BTree.html">https://www.cs.usfca.edu/~galles/visualization/BTree.html</a></p>
<h2 id="to-be-continued">To Be Continued..</h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[수열]]></title>
            <link>https://velog.io/@kkj-100-010-110/%EC%88%98%EC%97%B4</link>
            <guid>https://velog.io/@kkj-100-010-110/%EC%88%98%EC%97%B4</guid>
            <pubDate>Tue, 04 Jun 2024 15:55:24 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/bda368a8-eb95-4386-b8e4-87a7c1e6eda6/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/21c80705-816f-44fc-a885-9ff1ff8aafd1/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/9f3f6bda-0ea1-4237-8b41-11a39405a7f2/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/2afa87da-a66b-4528-92c4-9d68bb1b3f1f/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/17279064-1a23-49cf-8432-f3c32d439cb3/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/6b4d6dbb-bc70-47e7-a82f-21c433e181c0/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/8fae27d1-a827-4b38-8720-107f62dd01a4/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/0292b423-f12c-4c13-8f7d-e0c73242429e/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/0b1c8618-33fa-48ce-9a54-b91a1cbd7e61/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/fb9a2740-a064-43bf-bd52-6387a224791c/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/12f32ebf-6b03-4d09-8faa-22d8c1e47e7d/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/dd60c4da-d5f5-40ef-b702-12cc38be54a8/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/fdc6b01b-d08d-4ca3-b6c0-6c32aff8a739/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/37a6c432-09fe-4607-b630-5996978e1402/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/372f093d-87c2-4ff0-a9e8-effde23ccf09/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/6f395ba5-714b-4005-8d7a-13feccfe9fda/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/1bac75d7-2b32-4edb-891f-caa74ae7d47f/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/7edc2f5e-385c-4e13-a3c8-38862e1fcc8a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenGL_08, Camera]]></title>
            <link>https://velog.io/@kkj-100-010-110/OpenGL08-Camera-spfov21a</link>
            <guid>https://velog.io/@kkj-100-010-110/OpenGL08-Camera-spfov21a</guid>
            <pubDate>Wed, 08 May 2024 21:49:23 GMT</pubDate>
            <description><![CDATA[<p>이전 챕터에서 view 행렬을 scene 주변으로 움직이기 위해 어떻게 사용하는지 알아봤다. OpenGL 그 자체로는 카메라라는 개념을 잘 모른다. 그러나 scene에 있는 모든 물체를 반대 방향으로 움직임으로서 view의 움직였다.</p>
<p>이번 챕터에서는 OpenGL에서 어떻게 카메라를 설정하는지 알아보고 3차원 공간에서 카메라가 어떻게 자유롭게 움직이게 할 수 있는지 알아본다. 또한 카메라 클래스 생성과 키보드, 마우스 입출력을 다룰 것.</p>
<h4 id="cameraview-space">Camera/View space</h4>
<p>view 행렬이 모든 world 좌표들을 카메라의 위치와 방향과 연관된 view 좌표로 변환하는 것. 카메라를 정의하기 위해서 world-space에서 카메라의 위치, 카메라로부터 바라보는 방향, 우측을 가리키는 벡터와 상단을 가리키는 벡터가 필요하다. 실제로 수직인 세 단위 축과 원점인 카메라의 위치로 좌표계를 생성.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/7ede64ef-6e11-410e-a390-997d48f37009/image.png" alt=""></p>
<h4 id="camera-position">Camera position</h4>
<p>카메라 위치를 갖는 것은 쉽다. 카메라 위치는 카메라 위치를 가리키는 world-space 공간의 하나인 벡터. 이전 챕터와 같이 설정</p>
<pre><code class="language-cpp">glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);</code></pre>
<p>양의 방향인 z축으로 움직이면 카메라는 뒤쪽으로 움직인다.</p>
<h4 id="camera-direction">Camera direction</h4>
<p>그 다음 벡터는 카메라의 방향을 요구한다. 지금은 카메라를 scene의 원점(0, 0, 0)을 향하게 했다. scene의 원점과 카메라의 위치 벡터 간의 차를 구하여 방향을 구한다. view 행렬의 좌표계에 관해 z축을 양의 방향이길 원하고 OpenGL 관습에 의해 카메라는 음의 z축을 가리키기 때문에 그 방향 벡터의 부호를 바꾸길 원한다. 벡터 간의 뻴셈의 순서를 바꾸면 카메라의 양의 z축을 가리키는 벡터를 갖는다.</p>
<pre><code class="language-cpp">glm::vec3 cameraTarget = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 cameraDirection = glm::normalize(cameraPos - cameraTarget);</code></pre>
<h4 id="right-axis">Right axis</h4>
<p>Camera-space의 양의 x축을 나타내는 right 벡터를 필요로 한다. 이 벡터를 갖기 위해서 world-space에서 위를 가르키는 up 벡터를 먼저 지정함으로 약간의 트릭을 사용한다. 이 up 벡터와 카메라의 방향 벡터를 외적한다. 외적의 결과는 두 벡터와 수직인 벡터이고 이 벡터는 양의 x축이다. 외적의 순서가 바뀌면 축의 부호도 바뀐다.</p>
<pre><code class="language-cpp">glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f);
glm::vec3 cameraRight = glm::normalize(glm::cross(up, cameraDirection));</code></pre>
<h4 id="up-axis">Up axis</h4>
<p>카메라의 양의 y축을 가르키는 벡터를 갖는 것은 조금 쉽다. right 벡터와 방향벡터를 외적하면 된다. </p>
<pre><code class="language-cpp">glm::vec3 cameraUp = glm::cross(cameraDirection, cameraRight);</code></pre>
<p>외적을 통하여 view/camera space에 관한 모든 벡터를 생성할 수 있다. Gram-Schmidt process로 알려져있다.</p>
<ul>
<li><p>Gram-Schmidt Process</p>
<p>  임의의 벡터 집합을 orthogonal 벡터들의 집합으로 변환</p>
<p>  orthnormal set은 위에서 구한 벡터들을 normalize한 벡터 집합</p>
<p>  <a href="http://en.wikipedia.org/wiki/Gram-Schmidt_process">en.wikipedia.org/wiki/Gram-Schmidt_process</a></p>
</li>
</ul>
<p>이러한 카메라 벡터들을 사용하여 LookAt 행렬을 생성할 수 있다.</p>
<h4 id="look-at">Look at</h4>
<p>세 수직인 축을 사용하여 좌표 공간을 정의하면 그 세 축과 이동 벡터와 함께 행렬을 만들 수 있고 이 행렬을 곱하는 것으로 그 좌표 공간에 어느 벡터도 변환할 수 있다. 이것이 LookAt 행렬의 역할이고 지금 세 축과 카메라 벡터를 정의하는 지점으로 LookAt 행렬을 만들 수 있다.</p>
<p>$LookAt = \begin{bmatrix} R_x \ R_y \ R_z \ 0 \  U_x \ U_y \ U_z \ 0 \  D_x \ D_y \ D_z \ 0 \  0 \quad 0 \quad 0 \quad 1  \end{bmatrix} \cdot  \begin{bmatrix} 1 \quad 0 \quad 0 \ -P_x \  0 \quad 1 \quad 0 \ -P_y \  0 \quad 0 \quad 1 \ -P_z \  0 \ \ \ \ 0 \ \ \ \ 0 \quad \ \ \ \  1  \end{bmatrix}$</p>
<p>R은 right 벡터, U는 up 벡터, D는 방향 벡터, P는 카메라의 위치 벡터이다. 회전(왼쪽 행렬)과 이동(오른쪽 행렬) 부분은 도치된다. world를 회전 시키고 이동시키기 때문에 카메라를 반대 반향으로 이동 시킨다. view 행렬로서 LookAt 행렬을 사용하는 것은 모든 world 좌표를 정의된 view 공간으로 효율적으로 변환시킨다. 그러면 LookAt 행렬은 정확하게 주어진 타겟을 보는 view 행렬을 생성하는 것.</p>
<p>GLM은 이미 이러한 작업을 가능하게 한다. 카메라의 위치, 타겟 지점, world-space의 up을 나타내는 벡터만 명시하면 된다. 그러면 GLM은 view 행렬을 사용할 수 있는 LookAt 행렬을 생성한다.</p>
<pre><code class="language-cpp">glm::mat4 view;
view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f),
                   glm::vec3(0.0f, 0.0f, 0.0f),
                   glm::vec3(0.0f, 1.0f, 0.0f));</code></pre>
<p><code>glm::lookAt</code> 함수는 위치, 타겟, up 벡터를 각각 요구. 위 예시는 view 행렬을 생성하는데 이전 챕터에서 생성한 view 행렬과 같다.</p>
<p>사용자 입력을 좀 더 알아보기 전에 scene 주위를 도는 카메라를 생성. 타겟을 (0, 0, 0)으로 둔다. 원에서 지점을 나타내는 각 프레임, x, z 좌표를 생성하기 위해 삼각법을 사용하고 이것을 카메라의 지점으로 둔다. 시간이 지남에 따라 x와 y 좌표를 재계산하는 것으로 원을 모든 지점을 순회할 것. 그렇게 하여 카메라가 scene 주위를 돈다. 이 원을 미리 지정한 반지름만큼 원을 확대하고 GLFW의 <code>glfwGetTime</code> 함수를 사용하여 새로운 view 행렬을 생성한다.</p>
<pre><code class="language-cpp">const float radius = 10.0f;
float camX = sin(glfwGetTime()) * radius;
float camZ = cos(glfwGetTime()) * radius;
glm::mat4 view;
view = glm::lookAt(glm::vec3(camX, 0.0, camZ), glm::vec3(0.0, 0.0, 0.0), glm::vec3(0.0, 1.0, 0.0));</code></pre>
<p>아래와 같이 반지름 기준으로 계속 회전하는 카메라를 볼 수 있다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/1da894a8-610b-4b56-90ef-867e3d3a7eb5/image.png" alt=""></p>
<h4 id="walk-around">Walk around</h4>
<p>카메라의 움직임을 조작하자. 먼저 카메라 시스템을 설정해야하므로 프로그램 상단에 카메라 변수를 정의하면 유용하다.</p>
<pre><code class="language-cpp">glm::vec3 cameraPos = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp = glm::vec3(0.0f, 1.0f, 0.0f);</code></pre>
<p><code>LookAt</code> 함수는 다음과 같이 된다.</p>
<pre><code class="language-cpp">view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);</code></pre>
<p>첫번째 <code>cameraPos</code>를 설정, 방향은 현재 position과 방향 벡터의 합. 이는 움직임에도 불구하고 카메라는 계속 목표지점을 바라보게 한다. 키를 입력했을 때 <code>cameraPos</code> 벡터가 업데이트된다. </p>
<p>앞에 정의해놓은 processInput 함수에 아래 코드를 추가</p>
<pre><code class="language-cpp">void processInput(GLFWwindow *window)
{
    ...
    const float cameraSpeed = 0.05f; // adjust accordingly
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        cameraPos += cameraSpeed * cameraFront;
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        cameraPos -= cameraSpeed * cameraFront;
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) *
        cameraSpeed;
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) *
        cameraSpeed;
}</code></pre>
<p>WASD 키 중 하나를 누를때마다 카메라의 위치는 키에 맞게 업데이트된다. 카메라를 앞이나 뒤로 움직일 때 speed 값에 의해 크기 조정된 위치 벡터로부터 방향 벡터를 더하거나 뺀다. 양쪽으로 움직일 때 right 벡터를 생성하기 위해서 외적하고 right 벡터따라 움직인다. 이는 카메라를 이용할 때 친숙한 strafe 효과를 생성한다.</p>
<p>여기서 right 벡터를 normalize한다. 만약 하지 않으면 외적의 결과는 cameraFront 변수를 토대로 다른 크기의 벡터들을 반환할지도 모른다. 그 벡터를 normalize하지 않으면 카메라의 방향을 근거로 느리거나 빠르게 움직일 것이다.</p>
<h4 id="movement-speed">Movement speed</h4>
<p>현재 움직임에 관해 상수값을 사용하고 있다. 이론적으로 문제없지만 실제로 컴퓨터들은 다른 처리 출력을 가지고 있으며 그 결과 몇몇은 매초 다른 것들보다 더 많은 프레임들을 렌더링 할 수 있다. 사용자가 다른 사용자보다 더 많은 프레임을 그릴때마다 더 자주 <code>processInput</code>을 호출한다. 그 결과는 설정에 따라 속도가 상이하다. 모든 하드웨어에 똑같은 속도로 애플리케이션을 작동할 수 있어야한다.</p>
<p>그래픽 애플리케이션과 게임은 마지막 프레임을 그린 시간을 저장하는 deltatime 변수를 기록한다. 그리고 모든 속도들을 deltatime과 곱한다. 하나의 프레임에서 큰 deltatime을 가질 때(마지막 프레임이 평균보다 더 많이 걸릴 때), 그 프레임에 관한 속도는 균형을 맞추기 위해 더 높을 것이다. 이를 이용하여 어느 pc에 상관없이 각 유저들이 똑같이 느낄 수 있도록 카메라의 속도 균형을 맞출 것.</p>
<p>deltatime을 계산하기 위해서 2개의 전역변수를 기록</p>
<pre><code class="language-cpp">float deltaTime = 0.0f; // Time between current frame and last frame
float lastFrame = 0.0f; // Time of last frame</code></pre>
<p>매 프레임 안에서 새로운 deltatime을 계산한다.</p>
<pre><code class="language-cpp">float currentFrame = glfwGetTime();
deltaTime = currentFrame - lastFrame;
lastFrame = currentFrame;</code></pre>
<p>속도를 계산할 때 deltatime을 계산에 넣는다.</p>
<pre><code class="language-cpp">void processInput(GLFWwindow *window)
{
    float cameraSpeed = 2.5f * deltaTime;
    [...]
}</code></pre>
<p>deltatime을 사용함으로 카메라는 초당 2.5 단위의 일정 속도로 움직일 것이다. 더불어 scene 주위를 도는 부드럽고 변함없이 일정한 카메라 시스템을 가졌다.</p>
<h4 id="look-around">Look around</h4>
<p>제한된 움직임에서 마우스 입력으로 회전 기능을 추가한다. 이를 위해서 마우스 입력을 토대로 <code>cameraFront</code>벡터를 바꿔야한다. 다만 마우스 회전에 맞게 방향 벡터를 바꾸는 것은 조금 복잡하고 삼각법을 요구한다.</p>
<h4 id="euler-angles">Euler angles</h4>
<p>오일러 각은 1700년대 오일러 레온하르트가 정의한 3D에서 회전을 표현할 수 있는 3가지 값이다. 3가지 오일러 각이 있다: pitch, yaw 그리고 roll.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/35aef64e-9e99-4868-bbb3-453af64188d1/image.png" alt=""></p>
<p>pitch 각은 어느 정도로 아래와 위를 볼 것인지 묘사하고 yaw는 좌우의 정도를 나타내고 roll은 빙글 빙글 도는 정도를 나타낸다. 각각의 오일러각은 단일 변수에 의해 표현되며 세가지 모두 조합하여 3D에서 회전 벡터를 계산할 수 있다.</p>
<p>현재 카메라 시스템에서 pitch와 yaw 값들만 신경쓰고 roll 값은 배제한다. 새로운 방향 벡터를 나타내는 3D 벡터에 주어진 pitch와 yaw 값을 변환한다. pitch와 yaw 값을 방향벡터로 변환하는 과정은 삼각함수를 요구.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/e8a5380d-4018-4d5a-b407-3992898e6309/image.png" alt="">
yaw의 값을 구한다.</p>
<pre><code class="language-cpp">glm::vec3 direction;
direction.x = cos(glm::radians(yaw)); // convert to radians first
direction.z = sin(glm::radians(yaw));</code></pre>
<p>pitch의 값을 구한다.</p>
<pre><code class="language-cpp">direction.y = sin(glm::radians(pitch));</code></pre>
<p>xz평면은 cos pitch에 영향을 받으므로 xz 각 축에 cos pitch를 곱한다.</p>
<pre><code class="language-cpp">direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
direction.y = sin(glm::radians(pitch));
direction.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));</code></pre>
<p>위 식에 대해서 약간의 설명</p>
<ul>
<li><p>direction vector(행벡터) * pitch matrix * yaw matrix 를 하면 위의 식이 나온다.</p>
<p>  (pitch matrix * yaw matrix)$^t$ * direction vector(열벡터)</p>
</li>
</ul>
<p>모든 설정은 끝났다. 주의할 것은 -z축 방향을 바라보도록 맞춰준다.</p>
<pre><code class="language-cpp">yaw = -90.0f;</code></pre>
<h4 id="mouse-input">Mouse input</h4>
<p>마우스의 수직 움직임으로 yaw와 수평 움직임으로 pitch 값을 갖게 한다. 이런 마우스의 움직임을 카메라에 적용.</p>
<p>먼저 GLFW가 커서를 숨기게하고 캡쳐하게 한다. 커서를 캡쳐하는 것은 애플리케이션이 초점을 가지는 순간 마우스 커서를 화면 창의 센터에 위치하게 하는 것. </p>
<pre><code class="language-cpp">glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);</code></pre>
<p>이 함수 호출 이후, 마우스를 움직이는 곳은 보이지 않고 창 밖으로 나갈 수 없다. 이는 완벽한 FPS 카메라 시스템이다.</p>
<p>pitch와 yaw 값을 계산하기 위해서 GLFW이 마우스 이벤트를 인식할 수 있게 해야한다. 이를 아래와 같은 프로토타입을 가진 함수를 생성함으로 수행한다.</p>
<pre><code class="language-cpp">void mouse_callback(GLFWwindow* window, double xpos, double ypos);</code></pre>
<p>여기서 <code>xpos</code>와 <code>ypos</code>는 현재 마우스의 위치를 나타낸다. 마우스가 움직일 때마다 <code>mouse_callback</code>함수가 호출되게 GLFW에 콜백함수를 등록한다.</p>
<pre><code class="language-cpp">glfwSetCursorPosCallback(window, mouse_callback);</code></pre>
<p>fly style 카메라에 관한 마우스 입력을 처리할 때, 카메라의 방향 벡터를 완전히 계산할 수 있기전에 몇 가지 해야하는 스텝들이 있다.</p>
<ol>
<li>마지막 프레임 이후 마우스의 offset 계산</li>
<li>카메라의 yaw와 pitch 값에 offset 값을 추가</li>
<li>pitch 값의 minimum/maximum 최소/최대값에 제약 추가</li>
<li>방향 벡터 계산</li>
</ol>
<p>첫번째 단계에서 먼저 애플리케이션에서 마지막 마우스 위치를 저장해야한다. 이는 스크린의 중앙에 위치하게 초기화 한다. 여기서 800 x 600이므로 아래와 같이 설정</p>
<pre><code class="language-cpp">float lastX = 400, lastY = 300;</code></pre>
<p>이후 마우스 콜백 함수에서 바로 이전의 프레임과 현재 프레임 사이의 offset movement 계산한다.</p>
<pre><code class="language-cpp">float xoffset = xpos - lastX;
float yoffset = lastY - ypos; // reversed: y ranges bottom to top
lastX = xpos;
lastY = ypos;

const float sensitivity = 0.1f;
xoffset *= sensitivity;
yoffset *= sensitivity;</code></pre>
<p>여기서 각 offset 값에 <code>sensitivity</code>를 곱한다. 만약 이를 생략하면 마우스의 움직임이 엄청 강할 것이다. 이는 <code>sensitivity</code> 값을 기호에 알맞게 조절할 수 있다.</p>
<p>다음에 전역으로 선언된 pitch와 yaw 값에 offset 값을 더한다.</p>
<pre><code class="language-cpp">yaw += xoffset;
pitch += yoffset;</code></pre>
<p>세번째로는 카메라에 몇가지 제약을 추가하는데 사용자들이 이상한 카메라 움직임을 가지지 못하게 할 수 있다. <code>LookAt</code>을 한번 뒤집어 방향벡터가 world의 up 방향과 평행이 되는 것도 못하게 할 수 있다. pitch가 89도 보다 더 높게 못보게 한다. 90도에서 뒤집힌 <code>LookAt</code>을 갖기 때문이다. -89 이하로도 못보게 한다. </p>
<pre><code class="language-cpp">if(pitch &gt; 89.0f)
    pitch = 89.0f;
if(pitch &lt; -89.0f)
    pitch = -89.0f;</code></pre>
<p>yaw에는 제약을 두지 않는데 만약 원한다면 비슷한 방식으로 두면 된다. </p>
<p>마지막으로 바로 이전에 구했던 식을 사용하여 실제 방향 벡터를 계산한다.</p>
<pre><code class="language-cpp">glm::vec3 direction;
direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
direction.y = sin(glm::radians(pitch));
direction.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
cameraFront = glm::normalize(direction);</code></pre>
<p>이는 방향 벡터를 계산하고 마우스의 움직임으로 계산된 모든 회전을 갖는다. glm의 LookAt 함수에 <code>cameraFront</code> 벡터가 이미 포함되어 있기에 준비는 다 되어있다.</p>
<p>지금 바로 실행하면 창이 생기자마자 커서에 위치에 따라 카메라가 바로 큰 급격한 변화가 생긴다. 이는 커서가 창에 들어서자마자 마우스 콜백 함수가 호출되고 <code>xpos</code>와 <code>ypos</code> 위치가 스크린으로부터 들어선 마우스 위치로 되기 때문이다. 이는 스크린의 중앙으로부터 많이 벗어난 위치가 될 수도 있고 이러한 큰 offset의 결과로 이같은 급격한 창 안의 scene의 변화를 가진다. 이러한 이슈를 피하는 방식은 <code>bool</code> 타입의 전역 변수를 정의하여 먼저 마우스의 입력 값을 받았는지 체크한다. 처음엔 초기 마우스 위치를 <code>xpos</code>와 <code>ypos</code> 값으로 업데이트한다.</p>
<pre><code class="language-cpp">if (firstMouse) // initially set to true
{
    lastX = xpos;
    lastY = ypos;
    firstMouse = false;
}</code></pre>
<p>최종적으로 아래의 코드가 된다.</p>
<pre><code class="language-cpp">void mouse_callback(GLFWwindow* window, double xpos, double ypos)
{
    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }
    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos;
    lastX = xpos;
    lastY = ypos;

    float sensitivity = 0.1f;
    xoffset *= sensitivity;
    yoffset *= sensitivity;

    yaw += xoffset;
    pitch += yoffset;

    if(pitch &gt; 89.0f)
        pitch = 89.0f;
    if(pitch &lt; -89.0f)
        pitch = -89.0f;

    glm::vec3 direction;
    direction.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
    direction.y = sin(glm::radians(pitch));
    direction.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
    cameraFront = glm::normalize(direction);
}</code></pre>
<p>이제 실행하면 마우스 입력을 통해서 회전하는 것을 볼 수 있다.</p>
<h4 id="zoom">Zoom</h4>
<p>zooming 인터페이스를 구현할 것이다. 이전 챕터에서 FoV(Field of View)가 scene을 얼마나 볼 수 있는지 대해서 알아봤고 FOV가 작아질 때 scene의 투영된 공간도 작아진다. 이 더 작아진 공간은 같은 NDC로 투영, zooming  in 된 것처럼 된다. 줌인을 위해서 마우스 스크롤 휠을 이용. 마우스 이동과 키보드 입력과 비슷하게 마우스 스크롤에 관한 콜백함수를 가질 것.</p>
<pre><code class="language-cpp">void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    Zoom -= (float)yoffset;
    if (Zoom &lt; 1.0f)
    Zoom = 1.0f;
    if (Zoom &gt; 45.0f)
    Zoom = 45.0f;
}</code></pre>
<p>스크롤할 때 yoffset 값은 수직으로 스크롤된 양을 말해준다. scrool_callback 함수가 호출되었을 때 전역으로 선언된 fov 변수의 내용을 변경한다. 45.0이 디폴트 fov 값이므로 zoom level을 1.0-45.0 사이로 제약을 둔다.</p>
<p>이제 GPU의 각 프레임에 perspective projection matrix를 올려야한다. 이때 fov 변수와 함께 올린다.</p>
<pre><code class="language-cpp">projection = glm::perspective(glm::radians(fov), 800.0f / 600.0f, 0.1f, 100.0f);</code></pre>
<p>마지막으로 스크롤 콜백 함수를 등록한다.</p>
<pre><code class="language-cpp">glfwSetScrollCallback(window, scroll_callback);</code></pre>
<p>그럼 이제 줌이되는 결과를 볼 수 있다.</p>
<p>참고 - projection 행렬은 이제 렌더 루프 안으로 가야한다.</p>
<h4 id="camera-class">Camera class</h4>
<p>Learn OpenGL github에서 확인. </p>
<p>여기서 소개된 카메라 시스템은 오일러 각을 이용하고 대부분의 작업과 목적에 잘 맞는 fly like 카메라이다. 다만, FPS나 flight simulation(비행시뮬레이션)과 같은 다른 카메라 시스템을 생성할 때 조심해야한다. 각 카메라 시스템은 자기만의 트릭과 기이한 점들을 가지고 있으므로 그에 대해 많은 공부를 해야한다. 예를들어 fly 카메라는 pitch 값이 90도이상이 안되고 roll 값을 고려하지 않을 때 static up 벡터 (0, 1, 0)는 작동하지 않는다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenGL_07, Coordinate Systems]]></title>
            <link>https://velog.io/@kkj-100-010-110/OpenGL07-Coordinate-Systems</link>
            <guid>https://velog.io/@kkj-100-010-110/OpenGL07-Coordinate-Systems</guid>
            <pubDate>Wed, 17 Apr 2024 06:51:16 GMT</pubDate>
            <description><![CDATA[<p>OpenGL은 모든 vertex들(vertices)이 각 vertex shade가 동작한 후 normalized device coordinate 안에 있길 예상한다. 이는 각 vertex의 x, y, z 좌표가 -1.0 ~ 1.0 사이에 있어야한다는 것. 이 범위 밖의 좌표들은 보이지 않는다. 보통 각자가 스스로 범위를 결정한 좌표계를 명시하고 vertex shader에서 이 좌표계를 NDC(normalized device cooordinate)로 변환한다. NDC는 스크린에 2D좌표,픽셀로 변환되기 위해서 rasterizer에게 주어진다.</p>
<p>좌표계를 NDC로 변환하는 것은 보통 최종적으로 물체들의 vertices들을 NDC로 변환되기 전에 그 vertices를 여러 좌표계로 변환하는 단계적 방식으로 하는 것. 좌표들을 여러 intermediate 좌표계들로 변환하는 이점은 몇가지 연산/계산들이 특정 좌표계에서는 더 쉬워진다는 것. 총 다섯가지 다른 좌표계들이 있다. 이는</p>
<ul>
<li>Local space (or Object space)</li>
<li>World space</li>
<li>View space</li>
<li>Clip space</li>
<li>Screen space</li>
</ul>
<p>여기 5가지 모두 다 다른 상태이며 vertices이 fragments로 되기전 변환되는 곳들이다.</p>
<p>The global picture</p>
<p>좌표들을 하나의 공간에서 다음 좌표 공간으로 변환하기 위해서 여러가지 변환 행렬을 사용할 것. 여기서 가장 중요한 것은 model, view, projection 행렬이다. vertex 좌표들이 제일 처음 지역(local) 좌표 또는 지역 공간에서 시작하고 그 후 world 좌표, view 좌표, clip 좌표 마지막으로 screen 좌표로 된다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/575e9d59-598d-4a52-b0f7-5ecfaf19490c/image.png" alt=""></p>
<ol>
<li>Local 좌표들은 local 원점과 연관있는 오브젝트의 좌표들, 오브젝트들이 시작하는 좌표.</li>
<li>다음 단계는 local 좌표를 world-space 좌표로 변환(더 넓은 세계의 좌표). 다른 오브젝트들도 이 world의 기준으로 위치 조정</li>
<li>그 다음 이 world 좌표들을 view-space로 변환 → 각각의 좌표는 카메라나 viewer의 시점인 방식</li>
<li>이 후 view-space의 좌표들을 clip 좌표로 투영. Clip 좌표들은 -1.0 ~ 1.0 범위로 처리하며 어떤 vertices들이 스크린으로 나타나는지 결정. 이 clip 좌표들을 원근 투영을 원하면 원근법 추가할 수 있다.</li>
<li>마지막으로 <code>glViewport</code>에 정해진 범위 -1.0 ~ 1.0으로 좌표들을 변환하는 viewport 변환을 호출하는 처리로 clip 좌표를 screen 좌표로 변환. 이 결과를 fragment들로 되게 하기 위해서 rasterizer로 전송.</li>
</ol>
<p>각 공간에 어떤 것을 사용할지 알고 있을 것이다. 각각의 다른 공간들에 vertices를 변환하는 이유는 특정 좌표계에서 몇몇 연산들은 좀 더 타당하고 쉽게 사용할 수 있는 점이다. 예를들어 오브젝트를 수정할 때 local space에서 연산하는게 가장 알맞고 반면에 오브젝트들의 위치에 관해서 계산하는 특정 연산들은 world-space에서 가장 알맞는다. Local-space에서 clip-space까지 한번에 하나의 변환 행렬을 할 수도 있다. 다만 좀 더 낮은 유연성을 지닌다. </p>
<h4 id="local-space">Local space</h4>
<p>Local space는 오브젝트가 시작하는 오브젝트의 지역에 관한 좌표 공간이다. Blender 같은 모델링 소프트웨어 패키지에서 큐브를 생성한다고 생각해보자. 마지막 응용단계에서 큐브의 위치는 다를지라도 큐브의 원점은 (0, 0, 0)일 것이다. 아마도 생성한 모든 모델 오브젝트들은 (0, 0, 0)의 시작 지점을 갖는다. 따라서 모든 모델의 vertices들은 local-space에 있다. </p>
<h4 id="world-space">World space</h4>
<p>모든 오브젝트들을 애플리케이션에 바로 불러오면 (0, 0, 0)의 world 좌표계의 원점에 서로 겹쳐져 있을 것. 더 큰 세계 안에 각 오브젝트들의 위치를 정의해야한다. World space의 좌표는 말 그대로 world에 연관된 vertices의 좌표. 어떤 장소 주변에 각 오브젝트들이 흩어져 있는 방식으로 오브젝트들을 변환하는 좌표 공간이다. 이는 model 행렬로 수행한다.</p>
<p>Model matrix는 오브젝트들이 world에 놓여지는 위치와 방향에 맞게 이동, 크기 조정, 회전 변환을 하는 행렬.</p>
<h4 id="view-space">View space</h4>
<p>OpenGL의 카메라로서 보통 언급되는 것이 view space이다. 이는 camera space 또는 eye space라고도 한다. View space는 world-space의 좌표를 사용자 시점 앞으로의 좌표로 변환한 결과이다. 더구나 view-space는 카메라의 시점으로부터 보여지는 공간이다. 회전과 이동 조합의 변환으로 카메라 앞으로 특정 오브젝트들이 변환되게 수행된다. 이런 결합된 변환은 보통 view matrix 안에 저장되어있다. </p>
<h4 id="clip-space">Clip space</h4>
<p>vertex shader의 동작 끝지점에 OpenGL은 좌표들이 특정 범위 안에 있는 것을 예상하며 범위를 벗어난 좌표들은 깎아내어진다(clipped). 깎여진 좌표들은 버려지고 남겨진 좌표들은 fragment로 되어 스크린에 보여진다.</p>
<p>보여지는 모든 좌표들을 -1.0 ~ 1.0 범위로 명시하는 것은 직관적이지 못하기 때문에 개별의 좌표계를 알맞게 설정하고 OpenGL이 알 수 있는 NDC로 변환한다.</p>
<p>vertex 좌표들을 view에서 clip space로 변환하기 위해서 좌표를 명시하는 projection matrix라 불리는 것을 정의한다. 예를들어 각 차원에서 -1000 ~ 1000인 좌표 범위. 그럼 projection matrix는 그 명시된 범위 안의 좌표들을 NDC(-1.0 ~ 1.0)으로 변환한다. 범위 밖의 좌표들은 -1.0 ~ 1.0 사이에서 맵핑되지 않고 짤리게 될 것. Projection matrix에서 명시된 좌표내에 좌표(1250, 500, 750)은 보여지지 않는다. 왜냐하면 x 좌표가 범위 밖이고 NDC로 변환되면 1.0보다 큰 값이기 때문이다. 그러므로 깎여진다.</p>
<p>Projection matrix가 생성하는 viewing box는 절두체(frustum)이라 불리고 이 frustum 내부에 속하는 각 좌표들은 스크린으로 보여진다. 명시된 범위 안의 좌표들을 2D view-space 좌표로 맵핑될 수 있는 NDC로 변환하는 총 과정을 projection이라 한다. 이 projection matrix는 3차원 좌표를 쉽게 2차원으로 맵핑하는 NDC로 투영하기 때문이다.</p>
<p>모든 vertices가 clip-space로 변환되었을때 마지막 연산인 perspective division이 작동하는데 이 작동하는 부분은 벡터의 동차좌표계의 원소 w에 의해 각 위치 x, y, z 요소가 나누어지는 곳이다. Perspective division은 4D clip space 좌표들을 3D NDC로 변환하는 것. 이 단계는 vertex shader 단계의 마지막 부분에 자동으로 수행된다.</p>
<p>이 단계 이후 결과로 나온 좌표들이 <code>glViewport</code>의 설정을 이용하여 스크린으로 맵핑되고 fragment로 된다. </p>
<p>view 좌표들을 clip 좌표로 변환하는 projection matrix는 보통 두 가지 다른 형식을 취한다. 각각의 형식은 특정 frustum을 정의한다. Orthographic(정사영) projection이나 perspective(원근) projection을 생성할 수 있다. </p>
<h4 id="orthographic-projection">Orthographic projection</h4>
<p>Orthographic projection matrix는 큐브와 같은 frustum box를 정의하는데 이 box는 이 box의 범위 밖의 각 vertex가 clipped(깎여진)된 곳인 clipping space를 정의한다. Orthographic projection matrix를 생성할 때 보이는 frustum의 길이, 폭, 높이를 명시한다. 이 frustum 안의 모든 좌표는 이 행렬의 변환 후 NDC 범위 안에 속할 것이고 clipped되지 않는다. 이 frustum은 컨테이너 같이 보여진다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/d0aa2b58-2bee-42bb-b9a6-e7412395982b/image.png" alt="">
이 frustum은 보여지는 좌표들을 정의하며 폭, 높이, 가까운 평면(near plane)과 먼 평면(far plane)으로 명시된다. 가까운 평면 앞의 좌표와 먼 평면의 뒤 좌표는 clipped된다. 이 orthographic frustum은 frustum 내의 모든 좌표를 NDC로 바로 맵핑한다. 이는 부작용(side effects)가 없는데 왜냐하면 변환된 벡터의 w 요소를 건들지 않기 때문이다. 만약 w 요소의 값이 1.0이라면 perspective division은 좌표를 바꾸지 않는다.</p>
<p>이 정투영 행렬을 생성하기 위해서 GLM의 built-in 함수 <code>glm::ortho</code>를 사용한다.</p>
<pre><code class="language-cpp">glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);</code></pre>
<p>첫번째 두 인자는 frustum의 좌우 좌표를 명시하고 세번째 인자와 네번째 인자는 위아래의 좌표를 명시한다. 이 네 점들로 가까운 평면과 먼 평면의 크기를 정의했고 다섯번째와 여섯번째의 인자는 두 평면의 거리를 정의한다. 이 특정 projection 행렬은 정해진 x, y, z 범위 내의 모든 좌표를 NDC로 변환한다.</p>
<p>Orthographic projection matrix는 비현실적 결과를 낳는데 이는 원근감을 고려하지 않았기 때문이다.</p>
<h4 id="perspective-projection">Perspective projection</h4>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/5dbc3d00-9c5f-458e-9dbd-7bf0c23f86b9/image.png" alt="">
그림처럼 원근법으로 인해 거리에 따라 차이가 보인다. Perspective projection matrix를 사용하여 이러한 효과를 모방한다. Projection matrix는 clip space에 주어진 frustum 범위에 매핑될 뿐만아니라 viewer로부터 거리가 멀수록 w 요소의 값은 더 올라가는 방식으로 각 vertex 좌표의 w 값을 조작한다. 좌표들이 clip-space로 변환되었을 때 좌표들은 -w ~ w 범위 안에 있다(범위 밖은 clipped). OpenGL은 보여지는 좌표들이 최종 vertex shader 출력값으로 -1.0 ~ 1.0 범위로 떨어지는 것을 요구한다. 이렇게하여 좌표들이 clip-space에 있을 때 perspective division은 clip-space 좌표들에 적용된다:</p>
<p> $out = \begin{pmatrix}x/w\y/w\z/w\end{pmatrix}$</p>
<p>vertex 좌표의 각 요소는 w 요소로 나누어진다. 이는 w요소가 왜 중요한지 말해준다. 결과로 나온 좌표들은 이제 NDS에 들어간다. </p>
<p><strong><em>참고자료 시작</em></strong></p>
<p>Projection matrix(<a href="https://www.songho.ca/opengl/gl_projectionmatrix.html">https://www.songho.ca/opengl/gl_projectionmatrix.html</a>)</p>
<p>   컴퓨터 모니터는 2D 표면, OpenGL에 의해 렌더링되는 3D 장면들은 2D 이미지로 스크린에 투영되어야한다. <code>GL_PROJECTION</code> 행렬은 이 projection 변환을 위해 사용된다. 첫번째 이것은 모든 vertex 데이터들을 view space 좌표에서 clip space 좌표로 변환한다. 이후 이 clip 좌표들은 clip 좌표 w 요소로 나누어지는 것에 의해 NDC로 변환된다. 그러므로 clipping과 NDC 변환은 <code>GL_PROJECTION</code>으로 통합되어 있다. 6개의 파라미터(left, right, bottom, top, near and far)로 projection 행렬을 만드는지 알아보자</p>
<p>   clipping은 clip 좌표들로 수행되는데 이는 $w_c$요소로 나누기 바로 전이다. clip 좌표 $x_c$, $y_c$, $z_c$는 $w_c$와 비교하여 검사된다. 만약 셋 중 어느 좌표가 $-w_c$보다 작으거나 $w_c$보다 크면 그 vertex는 버려진다. </p>
<p>   $-w_c &lt; x_c,,y_c,,z_c &lt; w_c$
    <img src="https://velog.velcdn.com/images/kkj-100-010-110/post/6b38b7b4-7ab9-423a-8e4c-986682f2e10f/image.png" alt="">
    그 이후 OpenGL은 clip이 발생한 다각형의 끝지점들을 재구성한다.</p>
<p>Perspective Projection</p>
<p>perspective projection에서 끝이 짤린 피라미드형의 절두체에서 3D point(view 좌표)는 큐브(NDC)에 매핑된다.</p>
<p>x 좌표의 범위는 [l, r] → [-1, 1], y 좌표의 범위는 [b, t] → [-1, 1], z 좌표의 범위는 [-n, -f] → [-1, 1]
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/d55b5a12-5599-45bd-adfe-4b6a264afbbb/image.png" alt="">
view 좌표는 오른손 좌표계, NDC는 왼손 좌표계. 이는 view space에서 원점에서 카메라는 -Z 축을 따라 보고 NDC에서는 +Z 축을 따라 본다. <code>glFrustum</code> 함수는 near와 far의 거리의 값을 양수값만 취하기 때문에 <code>GL_PROJECTION</code> 행렬의 구성 중에 이들 값에 부호를 반대로 해야한다.</p>
<p>OpenGL에서 view space에서 3D point는 near 평면에 투영된 것.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/59ba2c9c-18da-448e-9dee-cabd0802d0ca/image.png" alt="">
절두체의 상면도에서 view space의 $x$ 좌표 $x_e$는 $x_p$와 매핑된다. 측면도에서 view space의 $y$ 좌표 $y_e$는 $y_p$와 매핑된다. 여기서 $x_p$와 $y_p$는 $z_e$에 종속된다. 이 두 좌표는 $z_e$에 반비례. 이는 두 좌표가 $-z_e$로 나누어진다는 말. <code>GL_PROJECTION</code>을 구성하는 첫번째 단서. <code>GL_PROJECTION</code> 행렬 곱에 의해 view space 좌표가 변환된 후에 clip-space 좌표들은 여전히 동차좌표계이다. 마지막으로 clip 좌표들의 $w$ 요소로 나누어짐으로 의해 NDC가 된다. </p>
<p>$\begin{bmatrix}x_{clip}\y_{clip}\z_{clip}\w_{clip}\end{bmatrix} = M_{Projection} \cdot \begin{bmatrix}x_{view}\y_{view}\z_{view}\w_{view}\end{bmatrix}$, $\begin{bmatrix}x_{ndc}\y_{ndc}\z_{ndc}\end{bmatrix}=\begin{bmatrix}x_{clip}/w_{clip}\y_{clip}/w_{clip}\z_{clip}/w_{clip}\end{bmatrix}$</p>
<p>따라서 clip 좌표 $w$ 요소를 $-z_e$로 설정</p>
<p>$\begin{bmatrix}x_{clip}\y_{clip}\z_{clip}\w_{clip}\end{bmatrix} = \begin{bmatrix}\quad.\quad\quad .  \quad\quad . \quad\quad .\quad\.\quad\quad .  \quad\quad . \quad\quad .\.\quad\quad .  \quad\quad . \quad\quad .\ \quad 0\quad\quad 0\quad -1\quad\quad 0\quad\end{bmatrix} \cdot \begin{bmatrix}x_{view}\y_{view}\z_{view}\w_{view}\end{bmatrix}$</p>
<p><code>GL_PROJECTION</code> 행렬의 네번째 행의 요소들은 0, 0, -1, 0이며 $w_{view} = -z_e$ 이다.</p>
<p>다음은 $x_p$와 $y_p$를 NDC의 $x_n$과 $y_n$으로 일차 관계식으로 매핑하는 것.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/ab02753b-db33-46ac-b139-9a349d780555/image.png" alt="">
그다음 위 방정식에서 $x_p$와 $y_p$에 그 위에서 구했던 값을 대입한다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/ce38aa8a-69c6-4ed2-87d1-94bdf914cc4a/image.png" alt="">
각 방정식에서 두 항 모두 $x_c/w_c$, $y_c/w_c$ 에 맞게 $-z_e$로 나누어지게 만들었다. 이로부터 <code>GL_PROJECTION</code> 행렬의 첫번째와 두번째 행을 만든다.</p>
<p>$\begin{bmatrix}x_{clip}\y_{clip}\z_{clip}\w_{clip}\end{bmatrix} = \begin{bmatrix}\ \frac{2n}{r-l}\quad 0\quad \frac{r+l}{r-l} \quad 0\ \ \ 0\quad\frac{2n}{t-b}\quad \frac{t+b}{t-b}\quad 0\ \.\quad .  \quad . \quad .\ 0\quad 0\quad -1\quad 0\end{bmatrix} \cdot \begin{bmatrix}x_{view}\y_{view}\z_{view}\w_{view}\end{bmatrix}$</p>
<p>이제 세번째 행만 남았다. $z_n$을 찾는 것은 이전 것들과 조금 다른데 왜냐하면 view space에서 $z_e$는 항상 near 평면의 -n에 투영되기 때문이다. 그래서 clipping과 깊이 테스트에 관한 $z$ 값이 필요하다. 더해서 이에 대한 역변환도 가능해야한다. $z$값이 $x$와 $y$에 종속되지 않기 때문에 $z_e$와 $z_n$ 사이의 관계를 찾기 위해서 $w$ 요소를 빌린다. 따라서 세번째 행은 아래와 같다.</p>
<p>$\begin{bmatrix}x_{clip}\y_{clip}\z_{clip}\w_{clip}\end{bmatrix} = \begin{bmatrix}\ \frac{2n}{r-l}\quad 0\quad \frac{r+l}{r-l} \quad 0\ \ \ 0\quad\frac{2n}{t-b}\quad \frac{t+b}{t-b}\quad 0\ \ \ 0\quad \ 0 \quad \ A \quad \ B\ 0\quad 0\quad -1\quad 0\end{bmatrix} \cdot \begin{bmatrix}x_{view}\y_{view}\z_{view}\w_{view}\end{bmatrix}$, $z_n = z_c / w_c = \frac{Az_e + Bw_e}{-z_e}$</p>
<p>view-space에서 w_e는 1이므로 방정식은</p>
<p>$z_n = z_c / w_c = \frac{Az_e + B}{-z_e}$이 된다.</p>
<p>A, B, 계수를 찾기 위해서 ($z_n$, $z_e$) 관계식을 사용. (-n, 1) 과 (-f, 1) 그리고 이것들을 위 방정식에 대입하면
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/4b931067-dc67-431f-bc57-4fd0ca70b7f6/image.png" alt="">
GL_PROJECTION 행렬의 모든 요소를 찾았다. Projection 행렬은 아래와 같다.</p>
<p>$\begin{bmatrix}\frac{2n}{r-l}\quad 0\quad \frac{r+l}{r-l} \quad 0\ 0\quad\frac{2n}{t-b}\quad \frac{t+b}{t-b}\quad 0\ \ 0\quad0 \ \frac{-(f+n)}{f-n}\ \frac{-2fn}{f-n}\ 0\quad 0\quad -1\quad 0\end{bmatrix}$</p>
<p>이는 일반적인 frustum에 관한 projection 행렬이다. viewing volume이 대칭적이라면 $r = -l$ 과 같고 $t = -b$ 이므로 </p>
<p>$\begin{bmatrix}\frac{n}{r}\quad 0\quad 0 \quad 0\ 0\quad\frac{n}{t}\quad 0\quad 0\ \ 0\quad0 \ \frac{-(f+n)}{f-n}\ \frac{-2fn}{f-n}\ 0\quad 0\quad -1\quad 0\end{bmatrix}$</p>
<p>여기서 $z_e$와 $z_n$ 사이의 관계식을 보면 유리 함수이면 비선형 관계이다. 이는 near 평명에 매우 높은 정밀도가 있고 far 평면에서는 매우 낮은 정밀도가 있음을 의미한다. 만약 [-n, f]의 범위가 커지면 커질수록 깊이 정밀도 문제(depth precision problem)을 야기시킨다. far 평면에서의 $z_e$의 작은 변화는 $z_n$에 영향을 주지 않는다. 이러한 문제를 최소화하기 위해서 n과 f의 거리는 가능한 짧아야한다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/261dfb95-3f64-404c-b8c2-48e5799f1130/image.png" alt="">
Infinite Perspective Matrix</p>
<p>perspective projection matrix는 far 평면을 행렬의 세번째 행에서 무한대로 설정하여 간단하게 할 수 있다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/5a5775c9-b0db-48a2-bda4-9c968fd1155b/image.png" alt="">
$\begin{bmatrix}\frac{2n}{r-l}\quad 0\quad \frac{r+l}{r-l} \quad 0\ 0\quad\frac{2n}{t-b}\quad \frac{t+b}{t-b}\quad 0\ \ 0\quad0 \ -1\ -2n\ 0\quad 0 \ -1\quad 0\end{bmatrix}$,$\begin{bmatrix}\frac{n}{r}\quad 0\quad 0 \quad 0\ 0\quad\frac{n}{t}\quad 0\quad 0\ \ 0\quad0 \ -1\ -2n\ 0\quad 0\quad -1\quad 0\end{bmatrix}$</p>
<p>일반 perspective projection matrix와 대칭 perspective projection matirx에 무한 far 평면으로 했을 때 위와 같은 행렬들이 나온다.</p>
<p>이 또한 depth precision error를 갖는다.</p>
<p>Perspective matrix with Field of View(FOV)</p>
<p>특정 창 크기에서 perspective projection에 관한 주어진 near 평면과 far 평면과 4개의 매개변수를 제대로 결정하기 어렵다. 폭과 높이 그리고 수직, 수평의 FOV와 aspect ratio(화면비, 종횡비 등)으로부터 4개의 매개변수를 쉽게 끌어낼 수 있다. 하지만 이는 대칭 perspective projection matrix에서만 가능.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/5319f16f-2b57-444f-a89d-8b6acf5648d4/image.png" alt=""></p>
<pre><code class="language-cpp">// This creates a symmetric frustum with vertical FOV
// by converting 4 params (fovy, aspect=w/h, near, far)
// to 6 params (l, r, b, t, n, f) 
Matrix4 makeFrustum(float fovY, float aspectRatio, float front, float back)
{
    const float DEG2RAD = acos(-1.0f) / 180;

    float tangent = tan(fovY/2 * DEG2RAD);    // tangent of half fovY
    float top = front * tangent;              // half height of near plane
    float right = top * aspectRatio;          // half width of near plane

    // params: left, right, bottom, top, near(front), far(back)
    Matrix4 matrix;
    matrix[0]  =  front / right;
    matrix[5]  =  front / top;
    matrix[10] = -(back + front) / (back - front);
    matrix[11] = -1;
    matrix[14] = -(2 * back * front) / (back - front);
    matrix[15] =  0;
    return matrix;
}

// This creates a symmetric frustum with horizontal FOV
// by converting 4 params (fovx, aspect=w/h, near, far)
// to 6 params (l, r, b, t, n, f) 
Matrix4 makeFrustum(float fovX, float aspectRatio, float front, float back)
{
    const float DEG2RAD = acos(-1.0f) / 180;

    float tangent = tan(fovX/2 * DEG2RAD);    // tangent of half fovX
    float right = front * tangent;            // half width of near plane
    float top = right / aspectRatio;          // half height of near plane

    // params: left, right, bottom, top, near(front), far(back)
    Matrix4 matrix;
    matrix[0]  =  front / right;
    matrix[5]  =  front / top;
    matrix[10] = -(back + front) / (back - front);
    matrix[11] = -1;
    matrix[14] = -(2 * back * front) / (back - front);
    matrix[15] =  0;
    return matrix;
}</code></pre>
<p>Orthographic projection</p>
<p>Orthographic projection(정투영)에 관한 <code>GL_PROJECTION</code> 구현은 상대적으로 쉽다. view-space의 모든 요소는 NDC와 선형적으로 매핑된다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/3209e690-a7c6-4e45-8dd4-dbd521d3020b/image.png" alt="">
w 요소는 orthographic projection에 필요 없기때문에 네번째 행은 (0, 0, 0, 1)이다. </p>
<p>$\begin{bmatrix}\frac{2}{r-l}\quad 0\quad 0 \quad -\frac{r+l}{r-l}\ 0\quad\frac{2}{t-b}\quad 0\quad -\frac{t+b}{t-b}\ 0\quad0 \quad\ \frac{-2}{f-n}\quad -\frac{f+n}{f-n}\ 0\quad\ 0\quad\quad 0\quad\quad 1\end{bmatrix}$</p>
<p>view volume이 대칭이라면 좀 더 간단하게 할 수 있다. $r = -1$ 그리고 $t = -b$</p>
<p>$\begin{bmatrix}\frac{1}{r}\quad 0\quad 0 \quad 0\ 0\quad\frac{1}{t}\quad 0\quad 0\ 0\quad 0\quad \frac{-2}{f-n} -\frac{f+n}{f-n}\ 0\quad\ 0\quad 0\quad 1\end{bmatrix}$</p>
<p><strong><em>참고자료 끝</em></strong></p>
<p>perspective projection matrix는 GLM에서 생성가능하다.</p>
<pre><code class="language-cpp">glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width /
                                                                    (float)height, 0.1f, 100.0f);</code></pre>
<p>glm::perspective는 큰 절두체를 생성하는데 이는 visible space를 정의하고 clip-space 볼륨에서 벗어난 어느 것도 절두체에서 결과물로 나오지 않고 clip된다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/f226d217-6681-423d-8975-713a1af09077/image.png" alt="">
첫번째 매개변수는 FOV를 정의, 이는 field of view의 약어이며 viewspace를 얼마나 크게할지 설정. 사실적인 뷰를 위하여 45도로 설정, 하지만 좀 더 doom-style 결과를 원하면 좀 더 높게 설정할 수 있다. 두번째 매개변수는 aspect ratio(종횡비, 화면비 등)를 설정한다. 이는 viewport의 폭을 높이로 나눈 것으로 계산한다. 세번째와 네번째는 절두체의 near와 far 평면 설정. 보통 near 거리는 0.1 그리고 far 거리는 100.0으로 설정. near와 far 평면의 사이와 절두체 내부의 모든 vertices는 렌더링된다.</p>
<p>perspective 행렬의 near 값이 10.0과 같이 매우 높게 설정될 때마다 OpenGL은 카메라에 가까운 모든 좌표 즉 0과 10.0사이의 좌표들은 clip한다. 이는 아마 비디오게임하면서 경험해봤을 수도 있다. 특정 오브젝트에 가깝게 갔을때 그러한 결과를 볼 수 있다. </p>
<p>orthographic projection을 사용할 때 각 vertex 좌표는 clip space에 바로 매핑된다. orthographic projection은 perspective projection을 사용하지 않으므로 멀리 떨어진 물체들이 더 작게 보이지 않는다. 이는 기괴한 결과물을 보여준다. 이러한 이유로 orthographic projection은 주로 2D 렌더링에서 주로 사용되며 perspective에 의해 삐뚤어진 vertices를 가지지 않는 몇몇 건축학 또는 공학 애플리케이션에서 사용된다. 3D 모델링에 사용되는 Blender 같은 애플리케이션들은 가끔 orthographic projection을 사용하기도 한다. 이는 각 오브젝트들의 치수를 좀 더 정확하게 그리기 위함이다. Blender에서 두 projection을 한 경우를 보면 vertex간의 간격이 다르다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/16c5d636-de88-4afd-952d-9afc5efc21a0/image.png" alt=""></p>
<h4 id="putting-it-all-together">Putting it all together</h4>
<p>앞서 언급한 단계들 각각에 관한 변환 행렬들을 만들었다: model, view, projection matrix. vertex 좌표들은 이제 clip 좌표로 변환된다.</p>
<p>$V_{clip} = M_{projection}\cdot M_{view}\cdot M_{model}\cdot V_{local}$.</p>
<p>행렬 곱셈의 순서는 뒤집힌다. 행렬 곱셈을 왼쪽에서 오른쪽으로 하는 것을 기억하자. 이 결과로 나온 vertex들은 vertex shader에 있는 gl_Position에 할당되고 OpenGL은 자동으로 perspective division과 clipping을 수행한다.</p>
<ul>
<li>vertex shader의 출력값은 clip-space 좌표로 되기 위한 변환된 좌표를 요구하고 OpenGL을 clip-space 좌표들을 perspective division을 수행하고 NDC로 변환한다. 그리고 OpenGL은 <code>glViewport</code>로부터 매개변수를 사용하는데 이는 NDC를 screen 좌표로 매핑하기 위함이다. 이 처리는 viewport transformation이라 한다.</li>
</ul>
<h4 id="going-3d">Going 3D</h4>
<p>2D에서 3D로 바꿔볼 것.</p>
<p>3D로 그리기위해서 먼저 model 행렬을 생성한다. model 행렬은 모든 오브젝트들의 vertices를 global world space로 변환을 적용하기 원하는 이동, 크기 조정, 회전들로 구성되어있다.</p>
<pre><code class="language-cpp">glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));</code></pre>
<p>vertex 좌표를 model 행렬에 곱하여 vertex 좌표를 world space 좌표로 변환한다. </p>
<p>그 다음 view 행렬을 만든다. scene을 살짝 뒤로 움직여서 물체를 보이게 한다. </p>
<ul>
<li>카메라를 뒤로 움직이는 것은 전체 scene을 앞으로 움직이는 것과 같다.</li>
</ul>
<p>OpenGL은 오른손 좌표계이므로 -z 방향은 앞으로 이동, z방향은 뒤로 이동이다. scene을 -z 방향으로 움직인다. </p>
<p>scene 주위를 움직이는 것은 다음 챕터에서 자세히 다룰 것이고 여기서는 아래의 코드로 설정</p>
<pre><code class="language-cpp">glm::mat4 view = glm::mat4(1.0f);
// note that we’re translating the scene in the reverse direction
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));</code></pre>
<p>마지막으로 projection 행렬 정의한다. perspective projection을 사용할 것이며 설정은 아래와 같다.</p>
<pre><code class="language-cpp">glm::mat4 projection;
projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);</code></pre>
<p>이제 변환 행렬들을 다 만들었고 shader에게 전달한다. 먼저 vertex shader에서 <code>uniform</code>으로 변환 행렬들을 설정하고 vertex 좌표들과 곱한다.</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;
...
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    // note that we read the multiplication from right to left
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    ...
}</code></pre>
<p>이제 변환 행렬들을 shader로 보낸다.</p>
<pre><code class="language-cpp">int modelLoc = glGetUniformLocation(ourShader.ID, &quot;model&quot;);
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
... // same for View Matrix and Projection Matrix</code></pre>
<p>이제 vertex 좌표를 model, view, projection 행렬을 통해서 변환된다. 그 결과물은 아래와 같다.</p>
<ul>
<li>바닥과 뒤쪽으로 기울어졌다.</li>
<li>조금 멀리 떨어져있다.</li>
<li>원근감있다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/dea48804-e3c4-4b58-8666-266c68d13709/image.png" alt=""></li>
</ul>
<h4 id="more-3d">More 3D</h4>
<p>지금까지 3차원 공간에서 2차원 평면을 가지고 작업했다. 이제 2차원 평면을 3차원 큐브로 확장한다. 큐브를 렌더링하기 위해서 총 36개의 vertices(6 faces * 2 triangle * 3 vertices) 필요. </p>
<pre><code class="language-cpp">    float vertices[] = {
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
         0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
    };</code></pre>
<p>재미를 위해서 큐브를 회전 시킨다.</p>
<pre><code class="language-cpp">model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f),
glm::vec3(0.5f, 1.0f, 0.0f));</code></pre>
<p><code>glDrawArrays</code>을 사용하여 큐브를 그리는데 지금은 36개의 vertices을 사용하므로 이에 맞게 설정.</p>
<pre><code class="language-cpp">glDrawArrays(GL_TRIANGLES, 0, 36);</code></pre>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/a519b722-0a4d-4ef9-a63d-1c89e206e0a0/image.png" alt="">
큐브처럼 보이지만 무언가 잘못됐다. 이는 OpenGL이 triangle by triagle, fragment by fragment으로 그리고 이는 이전에 그려진 픽셀을 덮어 겹쳐 그려진다. OpenGL은 draw 함수 호출에서 렌더링된 삼각형들의 순서를 고려하지 않기때문에 다른 삼각형보다 앞에 있는 삼각형이 있음에도 그 위에 삼각형들이 그려진다.</p>
<p>OpenGL이 겹쳐 그릴지 말지 결정하게 해주는 z-buffer라 불리는 버퍼에 depth information(깊이 정보)를 저장한다. z-buffer를 사용하여 OpenGL이 depth-testing(깊이 테스트)를 할 수 있게 설정할 수 있다.</p>
<h4 id="z-buffer">Z-buffer</h4>
<p>OpenGL은 depth buffer라 불리는 z-buffer에 depth 정보를 저장한다. GLFW는 자동으로 이런 버퍼를 생성한다(출력 이미지의 색상을 저장하는 color-buffer를 가지는 것과 같다). depth는 각 fragment(fragment의 z값)에 저장되며 fragment가 색상을 출력하기 원할 때마다 OpenGL은 z-buffer와 fragment의 z 값을 비교한다. 만약 현재 fragment가 다른 fragment에 뒤에 있으면 현재의 것이 버려진다. 아닌 경우 덮어쓰여진다. 이러한 처리를 depth-testing이라 하며 OpenGL에 의해서 자동으로 수행된다.</p>
<p>OpenGL이 실제 depth testing을 수행하는지 확실하게 하고 싶으면 먼저 OpenGL이 depth testing을 가능하게 해야한다. 왜냐하면 기본값은 가능하지 않게 되어 있다. <code>glEnable</code>을 사용하여 설정한다. <code>glEnable</code>과 <code>glDisable</code> 함수는 OpenGL에서 특정 기능들을 사용 가능하게 하거나 사용 못하게 할 수 있다. 이 함수들로 설정한 부분들을 다시 설정할 함수들이 나와 설정을 변경할 때까지 설정 값들이 유지된다. <code>GL_DEPTH_TEST</code>를 가능하게 해준다.</p>
<pre><code class="language-cpp">glEnable(GL_DEPTH_TEST);</code></pre>
<p>depth 버퍼를 사용하기때문에 렌더 루프에서 매번 depth 버퍼를 정리해야한다. 아니면 depth 정보가 이전 프레임에 관한 것으로 남아있다. 이는 색상 버퍼를 정리해주는 것과 같다. <code>glClear</code>에 <code>DEPTH_BUFFER_BIT</code>을 명시하면 된다.</p>
<pre><code class="language-cpp">glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);</code></pre>
<p>아래와 같이 깔끔한 큐브가 나온다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/a8e8eea4-51e8-4bd7-baa0-e9a989a52d6c/image.png" alt=""></p>
<h4 id="more-cubes">More cubes</h4>
<p>10개의 큐브들을 이제 스크린에 나타내보자. 각 큐브는 똑같지만 회전과 world-space에서의 위치가 다르다. 그래픽적인 레이아웃은 이미 정의되어있으므로 버퍼와 attribute array는 바꿀 필요가 없다. 해야할 것은 각 오브젝트들을 world-space로 model 행렬을 통해 변환하는 것.</p>
<p>먼저 world-space에서 각 큐브에 관한 이동 벡터에 대한 위치를 정의. 10개의 큐브 위치를 <code>glm::vec3</code> 배열에 정의.</p>
<pre><code class="language-cpp">glm::vec3 cubePositions[] = {
    glm::vec3( 0.0f, 0.0f, 0.0f),
    glm::vec3( 2.0f, 5.0f, -15.0f),
    glm::vec3(-1.5f, -2.2f, -2.5f),
    glm::vec3(-3.8f, -2.0f, -12.3f),
    glm::vec3( 2.4f, -0.4f, -3.5f),
    glm::vec3(-1.7f, 3.0f, -7.5f),
    glm::vec3( 1.3f, -2.0f, -2.5f),
    glm::vec3( 1.5f, 2.0f, -2.5f),
    glm::vec3( 1.5f, 0.2f, -1.5f),
    glm::vec3(-1.3f, 1.0f, -1.5f)
};</code></pre>
<p>렌더 루프에서 glDrawArrays 10번 불러야하지만 이번에 draw call 보내기전에 다른 model 행렬을 vertex shader에 보낸다. 다른 model 행렬을 오브젝트를 10번 그리는 렌더 루프안에 작은 루프문을 매번 생성한다. 또한 각 컨테이너에 작은 특정 회전을 추가한다.</p>
<pre><code class="language-cpp">glBindVertexArray(VAO);
for(unsigned int i = 0; i &lt; 10; i++)
{
    glm::mat4 model = glm::mat4(1.0f);
    model = glm::translate(model, cubePositions[i]);
    float angle = 20.0f * i;
    model = glm::rotate(model, glm::radians(angle),
    glm::vec3(1.0f, 0.3f, 0.5f));
    ourShader.setMat4(&quot;model&quot;, model);
    glDrawArrays(GL_TRIANGLES, 0, 36);
}</code></pre>
<p>위 코드는 매번 새로운 큐브가 그려질 때 model 행렬을 업데이트를 총 10번한다. 아래의 결과물을 갖는다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/a1d44094-2f46-4267-b7f0-90c8d88e2155/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenGL_06, Transformations]]></title>
            <link>https://velog.io/@kkj-100-010-110/OpenGL06-Transformations</link>
            <guid>https://velog.io/@kkj-100-010-110/OpenGL06-Transformations</guid>
            <pubDate>Tue, 16 Apr 2024 05:52:52 GMT</pubDate>
            <description><![CDATA[<p>물체를 만들고 색상을 입히고 이것들에 대해 texture들을 사용하여 좀 더 세부적으로 나타내기까지 했다. 다만 이들은 정적인 물체들이다. 이 object들에 대해서 각 buffer frame을 재설정하고 vertex들(vertices)을 바꾸는 것으로 변화를 줄 수 있지만 이는 비용도 크고 상당히 성가시다. 행렬 object를 사용하여 물체의 transform(변화)를 주는 더 좋은 방식이 있다.</p>
<p>행렬은 매우 강력한 수학적 개념들이다. 처음엔 매우 어려워 보인다. 그렇지만 익숙해지면 매우 유용한 것을 알 수 있다. 몇몇 수학적 지식들에 뛰어들어야하고 이에 더 알아보기 원하면 여러 자료들을 찾아보면 된다.</p>
<p>transformation에 대한 완전한 이해를 위해서 행렬에 관해 얘기하기전에 먼저 벡터에 관해 좀 더 깊이 있게 탐구해야한다. 이 챕터는 후에 나오는 개념들을 이해하기 위해서 기본적인 수학적 개념들을 배운다. 조금 어렵다고 느껴지면 계속해서 이 챕터로 돌아와서 복습하면 된다.</p>
<p>기본적으로 Learn OpenGL의 책을 따라가면서 정리하는데 여기서는 수학적 개념을 설명하기에 여기서 나오는 개념들을 개인적으로 정리한 부분이 크다.</p>
<p>주로 벡터와 행렬, 즉 선형대수학인데 여기서 나오는 부분들만, 순서대로 개인적으로 알아보고 참고한 자료들로 채움.</p>
<p><a href="https://velog.io/@kkj-100-010-110/%EB%B2%A1%ED%84%B0">벡터</a></p>
<p><a href="https://velog.io/@kkj-100-010-110/%EA%B3%B5%EA%B0%84%EB%B2%A1%ED%84%B0">공간벡터</a></p>
<p><a href="https://velog.io/@kkj-100-010-110/%ED%96%89%EB%A0%AC">행렬</a></p>
<h4 id="외적-cross-product">외적 Cross Product</h4>
<p>외적은 삼차원 공간에서만 정의된다. 평행하지 않는 두 벡터를 받아서 두 벡터에 수직인 벡터를 산출한다. 만약 두 벡터가 수직이라면 결과로 나오는 벡터 포함하여 모든 벡터가 수직이다.</p>
<p>삼차식의 행렬의 기하학적 정의를 보면 외적에 의미를 조금 쉽게 볼 수 있다. 이차식의 행렬은 평행사변형의 면적이고 삼차식의 행렬은 평행육면체의 부피이다. 부피를 구할 때 한 면과 높이의 곱인데 그 높이를 구하기 위해서 외적의 결과가 필요하다.</p>
<ul>
<li>행렬식의 성질 중에서 행렬을 각각의 벡터로 가지고 그 벡터 중 중복되는 벡터가 있으면 행렬식은 0이된다. 만약 삼차식의 행렬 $M(\vec{a}, \vec{b}, \vec{c})$이 있을 때 그리고 벡터 $\vec{u}$를 $\begin{bmatrix}a_2b_3-a_3b_2 \ a_3b_1-a_1b_3\a_1b_2-a_2b_1\end{bmatrix}$ 로 놓으면(참고로 이게 외적의 결과다.) $\vec{c}$에 $\vec{a}$나 $\vec{b}$를 놓으면 행렬식의 성질에 따라 행렬식은 0이 나온다. 이는 $\vec{u}\cdot \vec{a} = 0$과 $\vec{u}\cdot \vec{b} = 0$으로 나타난다. 이는 벡터 $\vec{u}$가 두 벡터와 직교하고 두 벡터의 평행사변형에 수직이라는 것</li>
<li>외적의 성질
  <img src="https://velog.velcdn.com/images/kkj-100-010-110/post/bd6de5d6-6b4d-4446-9dee-23d8188f4bf7/image.png" alt=""></li>
</ul>
<h4 id="항등-행렬identity-matrix">항등 행렬(Identity Matrix)</h4>
<p>가장 간단한 변환 행렬은 항등 행렬이라고 생각할 수 있다. 항등 행렬은 대각에 있는 성분을 제외하고 0인 NxN 행렬을 말한다.</p>
<p>$\begin{bmatrix}1\ 0\ 0\ 0\ 0\ 1\ 0\ 0\0\ 0\ 1\ 0 \0\ 0\ 0\ 1\end{bmatrix} \cdot \begin{bmatrix}1\2\3 \4\end{bmatrix} = \begin{bmatrix}1 \cdot 1\1 \cdot 2\1 \cdot 3\1 \cdot 4\end{bmatrix} = \begin{bmatrix}1\2\3 \4\end{bmatrix}$ </p>
<p>어디서 사용? 항등 행렬은 다른 변환 행렬을 만들기 위한 시작점</p>
<p>추가</p>
<ul>
<li><p>대칭 이동</p>
<p>  $\begin{bmatrix}1\quad 0\\ 0\ -1\end{bmatrix} \cdot \begin{bmatrix}x\y\end{bmatrix} = \begin{bmatrix}x^\prime \ y^\prime\end{bmatrix}$  x축에 관한 대칭 이동</p>
<p>  $\begin{bmatrix}-1\quad 0\\ 0\ -1\end{bmatrix} \cdot \begin{bmatrix}x\y\end{bmatrix} = \begin{bmatrix}x^\prime \ y^\prime\end{bmatrix}$  원점에 관한 대칭 이동</p>
<p>  $\begin{bmatrix}-1\quad 0\\ 0\quad 1\end{bmatrix} \cdot \begin{bmatrix}x\y\end{bmatrix} = \begin{bmatrix}x^\prime \ y^\prime\end{bmatrix}$  y축에 관한 대칭 이동</p>
<p>  $\begin{bmatrix}\ 1\ 0\\ 0\ 1\end{bmatrix} \cdot \begin{bmatrix}x\y\end{bmatrix} = \begin{bmatrix}x^\prime \ y^\prime\end{bmatrix}$  y = x 선분에 관한 대칭 이동</p>
</li>
</ul>
<p>Scaling</p>
<p>벡터의 크기를 조정할 때, 원하는 크기의 양만큼 같은 방향을 유지하면서 길이를 증가할 벡터의 크기를 증가시킨다. 이차원이나 삼차원 벡터 둘 중 하나로 작업하므로 이차원이나 삼차원 scaling 변수에 의해 정의할 수 있다.</p>
<p>$\vec{v} = \begin{bmatrix} 3\2 \end{bmatrix}$를 x축으로 0.5, 즉 두 배로 줄이고 y축으로 2, 두배로 늘리면 $\vec{v} = \begin{bmatrix} 1.5\4 \end{bmatrix}$가 된다. OpenGL은 보통 이차원 경우에도 삼차원 공간에서 작동되므로 z축을 1로 둔다. 이러한 연산을 non-uniform scale인데 왜냐하면 각축에 대한 scaling 요소(factors)들이 똑가지 않기 때문. 만약 각축에 대한 스칼라가 같으면 uniform scale이라 부른다.</p>
<p>이제 위의 항등 행렬 1들을 바꾸면 scaling matrix를 만들 수 있다. 마지막은 1은 그냥 둔다.</p>
<p>$\begin{bmatrix}S_1\ 0\ 0\ 0\ 0\ S_2\ 0\ 0\0\ 0\ S_3\ 0 \0\ 0\ 0\ 1\end{bmatrix} \cdot \begin{bmatrix}x\y\z \1\end{bmatrix} = \begin{bmatrix}S_1 \cdot x\S_2 \cdot y\S_3 \cdot z\1 \cdot 1\end{bmatrix} = \begin{bmatrix}S_1\S_2\S_3 \1\end{bmatrix}$ </p>
<p>역행렬을 구하는 프로세스를 기하학적으로 간단하게</p>
<p>$\begin{bmatrix}\ \frac{1}{S_1}\ 0\ 0\ 0\ \ 0\ \frac{1}{S_2}\ 0\ 0\ \ 0\ 0\ \frac{1}{S_3}\ 0 \ \ 0\ \ 0\ \ 0\ \ 1\end{bmatrix}$ 이렇게 역행렬을 만들 수 있다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/67bfa031-6407-4d6c-96a5-2c12078bffb2/image.png" alt=""></p>
<h4 id="translation">Translation</h4>
<p>Translation은 다른 위치를 가진 새로운 벡터를 반환하기위해 기존 벡터에 다른 벡터를 더하는 과정 translation 벡터를 토대로 벡터를 움직이는 것.</p>
<p>4x4 행렬에 3개의 요소 값을 넣어서 특정 연산을 수행. Translation 벡터를 $(T_x, T_y, T_z)$로 나타내면 translation 행렬을 다음과 같이 정의할 수 있다.</p>
<p>$\begin{bmatrix}1\ 0\ 0\ T_x\ 0\ 1\ 0\ T_y\0\ 0\ 1\ T_z\0\ 0\ 0\ 1\end{bmatrix} \cdot \begin{bmatrix}x\y\z \1\end{bmatrix} = \begin{bmatrix}x+T_x\y+T_y\z+T_z\1\end{bmatrix}$</p>
<p>translation 값들은 벡터의 w열에 의해 곱해지고 벡터의 원래 값에 더해진다. 이는 3x3 행렬에는 불가능.</p>
<p>벡터의 w의 요소는 동차 좌표계(homogeneous coordinate)라 알려져있다. homogeneous 벡터로부터 3차원 벡터를 갖기 위해서 x, y, z를 w 좌표로 나눈다. 보통 w 요소가 1.0이기에 이를 신경쓰지 않음. 동차좌표계를 쓰는 것은 몇 가지 이점이 있다. 삼차원 벡터에서 행렬 translation을 할 수 있고 다음 장에서 w 값을 3D perspective를 생성하기 위해 사용한다. 동차 좌표계가 0이라면 그 벡터가 방향 벡터라는 것을 말해준다. 왜냐하면 0이여서 translation이 불가하기때문이다.</p>
<ul>
<li>이동의 역행렬은 마지막 열의 세가지 원소의 값이 음수면 된다.</li>
</ul>
<h4 id="rotation">Rotation</h4>
<p>이번 변환 행렬은 위 항등 행렬과 이동 행렬과는 달리 상대적으로 어렵다. </p>
<p>Khan Academy </p>
<p><a href="https://www.khanacademy.org/math/linear-algebra">Linear Algebra | Khan Academy</a></p>
<p>벡터의 rotation? 2D나 3D에서 rotation은 angle(각)으로 표현된다. 각은 도나 라디안으로 표현 가능. 여기서는 도로 설명.</p>
<p>라디안에서 도로 변환하는 것은 어렵지 않다.</p>
<pre><code class="language-cpp">PI = 3.14159265359
angle_in_degrees = angle_in_radians * (180 / PI)
angle_in_radians = angle_in_degrees * (PI / 180)</code></pre>
<p>반원을 회전하는 것은 180도, 원의 1/5 오른쪽 회전은 72도로 오른쪽으로 회전. 그림은 2차원 벡터 $\vec{k}$에서 1/5 오른쪽으로 회전했을때 $\vec{v}$를 나타낸다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/fe931c46-b038-474a-84c7-dce17d768ed0/image.png" alt="">
삼차원에서 회전은 각과 회전축으로 명시된다. 명시된 각은 주어진 회전축을 따라 물체를 회전시킨다. 삼각함수를 사용하여 벡터를 주어진 각에 맞게 새롭게 회전된 벡터들로 변환 가능하다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/d4a2cb62-2c8d-4ee1-be26-ed7683242115/image.png" alt="">
회전 변환의 선형성 만족 → 선형 변환
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/90856b00-7f69-4d1e-8344-d6e6a9853f5f/image.png" alt="">
각각의 축에 의한 회전 변환
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/6498c40a-72e5-4371-bcab-0dedcc1bb52b/image.png" alt="">
프로젝션(정사영)도 선형성 만족 → 선형 변환
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/c7eac230-8286-4a3a-8cc6-e59e47b6d97a/image.png" alt="">
Projection transformation
</br>
회전 변환 행렬 유도
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/120b7bfd-8da6-4d84-af6b-9dbed6220b24/image.png" alt="">
<a href="https://en.wikipedia.org/wiki/Rodrigues&#39;_rotation_formula">Rodrigues&#39; rotation formula</a></p>
<p>위의 역행렬은 각만 바꾸면 된다. 따라서 row-major일때는 $\vec{v}R_n$, column-major일 때는 $R_n^T\vec{v}$</p>
<p>위의 변환 행렬은 짐벌락이라는 문제점을 야기시킨다. 자유도 3 → 2로 되는 상황.</p>
<p><a href="https://en.wikipedia.org/wiki/Gimbal_lock">Gimbal lock</a></p>
<p>이 문제점을 예방하기 위해서 사원수(quaternion) 사용하여 회전을 표현해야하는데 이는 더 안전할 뿐만 아니라 컴퓨터 친화적이다. 뒤에서 더 다뤄볼 예정</p>
<h4 id="combining-matrices-affine-transformation">Combining matrices (Affine Transformation)</h4>
<p>여러가지 변환들을 묶어서 사용할 수 있는 것이 변환 행렬 사용의 큰 장점이다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/da0e1efc-7357-4bc0-9eee-ebbea9603cbb/image.png" alt="">
이제 이러한 수학적 개념을 사용하는데 OpenGL은 행렬과 벡터에 관한 형식을 가지고 있지 않다. 그래서 수학 클래스와 함수를 따로 정의해야한다. 다행히도 OpenGL에 맞춰진, 사용하기 쉬운 수학 라이브러리가 있다. </p>
<h4 id="glmopengl-mathematics">GLM(OpenGL Mathematics)</h4>
<p>GLM은 header-only 라이브러리이다. 이는 헤더만 include만 하면 끝이다. 링킹과 컴파일은 필요하지 않다.  <a href="http://glm.g-truc.net/0.9.8/index.html">glm.g-truc.net/0.9.8/index.html</a> 여기서 다운로드 가능하다. 헤더파일의 루트 디렉토리를 includes 폴더에 복사하여 사용.</p>
<ul>
<li>Eigen 이라는 C++로 만들어진 선형대수 라이브러리도 있다.</li>
</ul>
<p>우리가 필요한 GLM의 기능 대부분은 아래의 3가지 헤더에서 다 찾을 수 있다.</p>
<pre><code class="language-cpp">#include &lt;glm/glm.hpp&gt;
#include &lt;glm/gtc/matrix_transform.hpp&gt;
#include &lt;glm/gtc/type_ptr.hpp&gt;</code></pre>
<p>한번 테스트 겸 아래와 같이 코드를 작성해보자.</p>
<pre><code class="language-cpp">glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
glm::mat4 trans = glm::mat4(1.0f);
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
vec = trans * vec;
std::cout &lt;&lt; vec.x &lt;&lt; vec.y &lt;&lt; vec.z &lt;&lt; std::endl;</code></pre>
<p>위 코드를 살펴보면 GLM built-in 클래스를 사용하여 4차원 벡터를 <code>vec</code>으로 생성, 그 다음 4x4행렬을 <code>1.0f</code>만 입력하여 대각선이 다 1.0으로 초기화되는 항등 행렬을 생성한다. 만약 초기화 값 <code>1.0f</code>를 넣지 않으면 요소들 전부 0인 null 행렬이 된다. </p>
<p>다음은 변환 행렬을 생성하는데 <code>glm::translate</code>함수에 항등 행렬을 전달하고 translation 벡터 값도 함께 전달한다. 그리고 벡터와 변환 행렬을 곱하여 결과물을 출력해보면 210이 나온다.</p>
<p>한번 이전에 만들었던 컨테이너 오브젝트를 한번 크기 조정하고 회전해보자.</p>
<pre><code class="language-cpp">glm::mat4 trans = glm::mat4(1.0f);
trans = glm::rotate(trans, glm::radians(90.0f), glm::vec3(0.0, 0.0, 1.0));
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));</code></pre>
<p>먼저 각 축으로 0.5배 만큼 크기 조정하고 z축으로 90도 회전 시킨다. GLM은 그 각을 라디안으로 받기에 glm::radians함수로 변환해준다. 그리고 이전에 만든 오브젝트는 xy 평면에 있으므로 z축을 회전시킨다. 우리가 회전시키는 축은 단위 벡터이여야하는 것을 명심. X 또는 Y나 Z축으로 회전하는게 아니면 normalize하자. </p>
<p>이제 어떻게 shader로 전달할까? shader도 이전 GLSL에서 공부했듯이 <code>mat4</code> 형이 있다. 따라서 <code>uniform</code> 으로 <code>mat4</code> 형 변수를 만들어서 전달하면 된다.</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 TexCoord;

uniform mat4 transform;

void main()
{
    gl_Position = transform * vec4(aPos, 1.0f);
    TexCoord = vec2(aTexCoord.x, aTexCoord.y);
}</code></pre>
<p><code>gl_Position</code>에 <code>uniform</code> 변환 행렬을 벡터와 연산하여 전달한다. </p>
<pre><code class="language-cpp">unsigned int transformLoc = glGetUniformLocation(ourShader.ID,&quot;transform&quot;);
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));</code></pre>
<p>먼저 <code>uniform</code> 변수의 위치를 묻고 그 다음 <code>glUniform</code>에 Matrix4fv를 접미사를 붙여 사용하여 행렬 데이터를 shader로 보낸다.</p>
<p>첫번째 인자는 데이터를 보낼 uniform 변수의 위치, 두번째는 보낼 행렬의 수. 세번째 인자는 행렬을 전치 행렬로 바꾸길 원하는지에 대한 값을 넣어야한다(column과 row를 바꾸는 것). OpenGL 개발자는 종종 내부 행렬 레이아웃을 사용하며, 이는 column-major 형식이고 GLM에서의 디폴트 행렬 레이아웃이다. 따라서 전치할 필요가 없다. 마지막 매개변수에는 실제 행렬의 데이터를 전달하는데 OpenGL의 원하는 방식에 항상 부합하지 않는 방식으로 GLM은 행렬의 데이터를 저장하므로 데이터를 GLM built-in 함수인 <code>glm::value_ptr</code>함수를 사용하여 변환한다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/5206e328-a86e-469e-9e03-3ac6880291b3/image.png" alt="">
사이즈가 두배 작아지고 z축에서 90도로 회전시킨 결과물이 나온다.</p>
<p>여러가지 테스트를 해볼 수 있다. 다시 오브젝트를 우하단쪽으로 위치시키게 한다. 이 오브젝트를 시간이 지나는 것에 따라 회전시키기 위해서 변환 행렬을 렌더링 루프안에서 업데이트해줘야한다. 왜냐하면 각 프레임마다 업데이트해야하기 때문에.</p>
<pre><code class="language-cpp">glm::mat4 trans = glm::mat4(1.0f);
trans = glm::translate(trans, glm::vec3(0.5f, -0.5f, 0.0f));
trans = glm::rotate(trans, (float)glfwGetTime(),
glm::vec3(0.0f, 0.0f, 1.0f));</code></pre>
<p>GLFW의 시간 함수를 사용하여 시간의 변화에 따라 회전각도 변하게 한다. 이전 경우에는 변환 행렬을 어디든 선언할 수 있었지만 이같은 경우에는 변환 행렬을 렌더 루프안에서 다시 생성해야한다. 보통 렌더링할 때 각 프레임에 대한 새로운 값을 가지고 재생성된 여러가지 변환 행렬을 가진다.</p>
<p>여기서 처음에 컨테이너 오브젝트를 원점에서 회전시키고 회전된 버전을 우하단으로 이동시킨다. 실제 변환 순서는 반대인 것을 기억하자. 비록 코드에서는 이동시키고 회전하지만 실제 변환은 회전을 먼저 적용하고 이동시킨다. 이러한 모든 복합 변환들의 조합들을 이해하는 것과 어떻게 오브젝트들에 적용되는지 이해하기 어렵다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/e4649f16-7122-4484-b3a5-4a2468115d5d/image.png" alt="">
위와 같이 우하단에서 계속 회전하는 결과물을 얻을 수 있다. 이동과 회전을 하나의 행렬로 했다. 이는 그래픽스에서 매우 중요한 부분이다. 무한히 많은 양의 변환을 정의할 수 있고 이것들을 원하는 만큼 재사용할 수 있게 하나의 행렬로 결합할 수 있다. 이같은 변환을 vertex shader에서 사용하는 것은 vertex 데이터들을 다시 재정의하는 수고를 덜 수 있으며 처리하는 시간 또한 절약한다. 매번 데이터를 다시 보낼 필요가 없기 때문이다. 해야할 일은 변환 uniform을 업데이트하는 것이다.</p>
<p>참고</p>
<ul>
<li>DirectX는 row-major matrix, $\vec{u^\prime} = \vec{u}T$</li>
<li>HLSL는 column-major matrix, $\vec{u^\prime} = T\vec{u}$</li>
</ul>
<p>보충 자료 출처</p>
<ul>
<li>Khan Academy - Linear Algebra</li>
<li>Honglabs - 홍정모의 컴퓨터 그래픽스 새싹 코스 Part 2</li>
<li>수학 독본</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[행렬]]></title>
            <link>https://velog.io/@kkj-100-010-110/%ED%96%89%EB%A0%AC</link>
            <guid>https://velog.io/@kkj-100-010-110/%ED%96%89%EB%A0%AC</guid>
            <pubDate>Sun, 31 Mar 2024 22:46:26 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/7ebb6741-0d07-46cf-bf43-965c35d8855f/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/470faf17-78a7-4536-bb2c-e44ea2cf3db1/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/5e1db89d-98cb-4963-a8a3-a53d682743f6/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/765fa6dc-e381-4499-87ec-e987d620b1ea/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/f5d29a86-7e28-4b42-8d45-26fd5807a5e9/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/aa5ee11e-c085-4ef0-85a9-c72de4708cc1/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/43b28b3e-4159-4f6d-a035-befc1aeeb10d/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/80a983df-a17e-40de-abf7-9cab373b4af1/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/e011e7ca-82f7-4677-bcef-ffd1ec167750/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/979d4beb-d78e-4219-95c1-8db09e8217ec/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/fa521e5b-3c50-4dbb-a2eb-40a87d8ebba6/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/94b66594-54e6-4a32-8303-9ea0b06545b6/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/6b46e80e-acb6-4eaf-bcda-072f257bf110/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/b10f2391-983a-4004-bd3f-5b49586e7ea0/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/61f4ce14-362d-435f-aced-6663bdeaec0f/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/bf721a63-c6fe-4d6c-9b47-b58688bfe696/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/5d7fce85-76de-4f1f-8820-84fb500a41e4/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/36fc0894-6397-4b05-9d04-52ae6fca2fb1/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/267d7ac5-2290-4928-84a2-cdda13fc64fd/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/564fef25-2fb9-4ec8-bb90-b4d80fae0730/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/1fc96b4f-e8e0-46ab-8c4e-057639df2d01/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/624c5f92-c651-4201-8a1b-dc37a06f44de/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/8c20e1f7-c120-485c-9e48-8f5bbe03d599/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/9a971756-f8ee-4a8b-bac8-3ff8f79fc4e8/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/651f66f4-f706-40c2-bc12-7f4bb7238eec/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[공간벡터]]></title>
            <link>https://velog.io/@kkj-100-010-110/%EA%B3%B5%EA%B0%84%EB%B2%A1%ED%84%B0</link>
            <guid>https://velog.io/@kkj-100-010-110/%EA%B3%B5%EA%B0%84%EB%B2%A1%ED%84%B0</guid>
            <pubDate>Sun, 31 Mar 2024 22:42:51 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/733a2bd6-6b91-449b-90e6-f09054c04942/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/84b53dc0-8d59-4082-b744-63ddccfbd560/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/709bed6b-9e6d-4543-ba99-109a50b7b610/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/a66d61ef-ef46-4bfd-b00b-e57c5da3611c/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/0123f978-3d71-4afd-8a7b-039b74f9cb5f/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/73a75b0c-a915-486f-b27d-35c71b973431/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/6688976e-0a78-4928-9170-dd1474f7c504/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/10352b69-9c5e-4c00-8d68-4d169131acec/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/e132fce5-b82e-45c7-abcb-13c9d3141ba0/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/3eeadf32-14db-4bf8-86f6-ed21357eb5bd/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/bc6c4f63-4823-49de-a48e-e32af596ac16/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/ad407f37-8db0-4c02-b899-09b2dde46f2f/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/ff303ec8-a6c2-4430-b781-b5cad50671d7/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/e777075d-1848-42ef-a31b-1f44eefbde46/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/d4a7d32c-ffb8-4dbe-aa4f-f72dbf8108c9/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/dd6098ba-720e-4463-98bb-ef4ef93440a1/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/27c7de05-c106-44f5-a8a0-ce98bf81e0b8/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/82fcf5c4-19b5-449a-8f7c-b4563ccacef7/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/7e64d06d-4891-493b-a65f-203882bb5b66/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/b19a1a7f-df35-416b-82ee-a5c71b1f165c/image.jpg" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/26127a81-5246-4731-a0f4-45cbf6585d7a/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenGL_05, Texture]]></title>
            <link>https://velog.io/@kkj-100-010-110/OpenGL05-Texture</link>
            <guid>https://velog.io/@kkj-100-010-110/OpenGL05-Texture</guid>
            <pubDate>Mon, 25 Mar 2024 00:42:03 GMT</pubDate>
            <description><![CDATA[<p>현실적으로 수 많은 vertex와 색상 값을 설정하는 것은 오버헤드를 발생시킨다. 따라서 texture를 이용한다. texture란 object들을 더 세밀하게 만들기 위해 추가하는 2D(1D or 3D) image를 말한다.</p>
<p>예로 삼각형에 texture를 맵핑하기 위해서 삼각형 각 vertex가 texture의 어느부분과 일치하는지 명시해야한다. 각 vertex는 texture coordinate(텍스쳐 좌표계)를 가져야하는데 이는 texture 이미지의 어떤 부분을 샘플링하는지 연관되어있다. 그 후 fragment interpolation가 나머지 작업을 한다.</p>
<p>texture coordinate의 범위는 x와 y축에서 0부터 1까지이며 texture coordinate을 사용하여 texture 색상을 찾는 것을 샘플링이라고 한다. 텍스쳐 좌표계에서 (0, 0)은 텍스쳐 이미지의 좌하단 모서리이고 (1, 1)은 우상단 모서리이다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/67818005-7c96-457f-99f9-6b9d5bcb110e/image.png" alt="">
위 그림을 보듯이 세개의 텍스쳐 좌표 지점을 볼 수 있다. 좌측 하단(0, 0), 우측 하단(1, 0), 중앙 상단(0.5, 1)을 텍스쳐 좌표로 사용. 이 세 텍스쳐 좌표만 vertex shader로 보내고 보내진 이 좌표들을 토대로 fragment shader가 각각의 fragment에 관한 텍스쳐 좌표를 보간(interpolate)한다.</p>
<pre><code class="language-cpp">float texCoords[] = {
    0.0f, 0.0f, // lower-left corner
    1.0f, 0.0f, // lower-right corner
    0.5f, 1.0f // top-center corner
};</code></pre>
<p>텍스쳐 샘플링은 정밀하지 않은 interpolation을 가지며 다양한 방식으로 된다. 더해서 텍스쳐를 어떻게 샘플링할 것인지 OpenGL에 알려주는 것도 프로그래머의 일이다.</p>
<h4 id="texture-wrapping">texture wrapping</h4>
<p>앞서 말했듯이 텍스쳐 좌표의 범위는 (0, 0) ~ (1, 1)이다. 이를 벗어난다면? OpenGL은 기본적으로 이미지 반복으로 처리한다. 이에 대하여 다양한 옵션들을 제공한다.</p>
<ul>
<li><code>GL_REPEAT</code>: 디폴트 옵션, 이미지 반복</li>
<li><code>GL_MIRRORED_REPEAT</code>: 이미지 반복이지만 상이 바뀐다.</li>
<li><code>GL_CLAMP_TO_EDGE</code>: 범위를 벗어나면 텍스쳐 좌표의 끝지점이 이어진다.</li>
<li><code>GL_CLAMP_TO_BORDER</code>: 범위를 벗어나면 사용자 지정 경계 색상값으로 채워진다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/b46a3aa3-3c4b-47c3-bee6-e986cd641d68/image.png" alt="">
위의 옵션들은 <code>glTexParameter*</code>함수에서 텍스쳐 좌표계(s, t, p(만약 3D 이미지라면), 이는 x, y, z에 상응)에 맞게 설정된다.</li>
</ul>
<pre><code class="language-cpp">glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);</code></pre>
<p>첫번째 인자는 텍스쳐 타겟, 여기서 2D 텍스쳐를 사용하기에 <code>GL_TEXTURE_2D</code>. 두번째는 어느 텍스쳐 축에 적용할 것인지, 여기서 S(X), T(Y) 다 똑같이 설정. 마지막 인자는 어떤 모드로 텍스쳐 렙핑 할 것인지, 여기서 <code>GL_MIRRORED_REPEAT</code>으로 설정. 따라서 위 두 코드는 OpenGL에게 2D 텍스쳐를 사용하여 S, T 축 모두 범위 밖의 처리는 이미지가 상이 바뀌어 반복적으로 채워지게 설정하라는 것.</p>
<p>만약 <code>GL_CLAMP_TO_BORDER</code>로 설정하려면 색상값을 명시해줘야한다. <code>fv</code>를 전달하기에 그에 맞는 <code>glTexParameterfv</code>와 함께 옵션과 float array로 된 색상 값을 입력.</p>
<pre><code class="language-cpp">float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);</code></pre>
<h4 id="texture-filtering">texture filtering</h4>
<p>텍스쳐 좌표계는 해상도와 무관하지만 부동소수점 값일 수도 있다. 게다가 OpenGL은 텍스쳐 좌표에 맞는 맵핑을 위해 어느 텍스쳐 픽셀인지 계산해야한다. texture pixel을 texel이라 알려져있다. 매우 큰 object와 낮은 해상도의 texture를 가진 경우 이는 매우 중요해진다. OpenGL이 texture filtering에 관한 옵션을 가진 것을 예상할 것이며, 몇 가지 옵션들이 있는데 그 중 가장 중요한 것은 <code>GL_LINEAR</code>와 <code>GL_NEAREST</code>이다.</p>
<p><code>GL_NEAREST</code>(nearest neighbor 또는 point 필터링이라 불러진다)는 OpenGL의 기본 디폴트 텍스쳐 필터링 옵션이다. 이를 설정했을시 OpenGL은 텍스쳐 좌표가 가장 가까운 texel을 선택한다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/36c875df-ea97-4afe-a6d8-ddbb7ae91746/image.png" alt="">
<code>GL_LINEAR</code>((bi)linear 필터링이라 알려져있다) 는 텍스쳐 좌표의 이웃 texel들로부터 보간된(interpolated) 값을 가진다. texels 간 색상값을 근사치를 낸다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/67539728-51dc-400c-9e5b-e616c2f8be0b/image.png" alt="">
각 texture filtering 방식의 시각적 효과는?
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/9d1cbd46-40c1-4858-8beb-518a05c6ddbd/image.png" alt="">
<code>GL_NEARSET</code>는 사각형 패턴의 결과가 나타나면서 픽셀이 잘 보이는반면 <code>GL_LINEAR</code>는 좀 더 부드러운 형태를 나타내면서  픽셀의 경계가 흐릿하다.</p>
<p>texture filtering은 magnifying과 minifying 연산을 설정할 수 있다. 아래와 같이 <code>GL_NEAREST</code>을 축소하고 <code>GL_LINEAR</code>를 확대하는 식으로 함수호출하여 설정 가능하다.</p>
<pre><code class="language-cpp">glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);</code></pre>
<h4 id="mipmaps">Mipmaps</h4>
<p>엄청 큰 공간에 수천개의 objects와 각각 texture가 첨가됐다고 가정해보자. 보는 사람에게 가까운 objects처럼 고해상도 texture가 붙은 objects가 멀리 떨어져있을거고 멀리떨어져 있으므로 개수가 조금 적은 fragment을 가지므로 OpenGL은 고해상도 texture로부터 그 fragment에 관한 값을 찾는 것에 대한 어려움을 갖는다. texture의 큰 부분을 범위을 가지는 fragment에 관한 색상값을 골라야하기때문이다. 이는 작은 물체에 관해 굉장히 선명한 인공물 생산하며, 작은 물체에 고해상도의 텍스쳐를 사용함으로 메모리 사용의 낭비는 말할 필요도 없다. </p>
<p>이러한 문제를 해결하기위한 것이 mipmaps이다. mipmaps는 텍스쳐 이미지들을 모아놓은 것인데 그 이미지들은 서로 다른 사이즈를 가진 똑같은 이미지들이다. 각 이미지들은 이전 이미지들의 사이즈 두배 작은 것들이다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/94e3e346-40c5-4363-afd9-11bc93762297/image.png" alt="">
OpenGL은 objects의 거리에 따라서 최적의 텍스쳐를 mipmaps에서 찾아 사용할 것이다. 보는 사람으로부터 멀리 떨어진 물체에 조금더 낮은 해상도의 텍스쳐를 사용함으로 이질감을 줄인다. 따라서 OpenGL은 알맞는 texel을 샘플링할 수 있고 mipmap에서 그 부분을 샘플링할 때 더 적은 cache 메모리가 들어간다. </p>
<p>각 텍스쳐 이미지들에 대한 mipmap된 텍스쳐들을 모음을 수동으로 만드는 것은 복잡하다. 운좋게 하나의 함수 호출로 이 일을 할 수 있다. 텍스쳐를 생성한 뒤 <code>glGenerateMipmaps</code>을 호출한다.</p>
<p>렌더링 중 mipmap들에서 texture 스위칭이 일어날 때 OpenGL은 두 mipmap layer 사이에서 더 선명한 모서리들이 보이는 물체를 보여줄 수 있다. 일단 texture filtering과 같이 mipmap 단계 사이에 NEAREST와 LINEAR 옵션을 사용하여 filter를 줄 수 있다. </p>
<ul>
<li><code>GL_NEAREST_ MIPMAP_NEAREST</code>: 픽셀 사이즈에 맞는 nearest mipmap을 가지며 texture 샘플링에 관해서 nearest neighbor interpolation 사용</li>
<li><code>GL_LINEAR_MIPMAP_NEAREST</code>: 픽셀 사이즈에 맞는 nearest mipmap을 가지며 texture 샘플링에 관해서 linear interpolation 사용</li>
<li><code>GL_NEAREST_MIPMAP_LINEAR</code>: 픽셀 사이즈에 가장 가까운 두 mipmap들을 linear interpolation하며 샘플링에 관해서 nearest neighbor interpolation을 사용</li>
<li><code>GL_LINEAR_MIPMAP_LINEAR</code>: 픽셀 사이즈에 가장 가까운 두 mipmap들을 linear interpolation하며 샘플링에 관해서 linear interpolation 사용</li>
</ul>
<pre><code class="language-cpp">glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);</code></pre>
<p>흔한 실수 중 하나는 mipmap filtering 옵션에서 magnifying(확대) filter를 하는 것이다. mipmap은 주로 사이즈를 줄이는데 사용되기 때문이다. <code>GL_INVALID_ENUM</code> 에러코드 발생</p>
<h4 id="loading-and-creating-textures">Loading and creating textures</h4>
<p>Texture image는 여러가지 파일 형식으로 저장될 수 있다. 각각 파일형식에 맞는 구조와 데이터 순서가 있다. 그럼 어떻게 애플리케이션으로 가져올 것인가? 하나의 해결책은 하나의 파일형식의 선택하여, 예를들면 .PNG 그리고 그 파일형식을 바이트로 구성된 큰 배열로 변환하는 이미지 로더를 작성한다. 이는 크게 어렵지 않지만 여전히 번거롭다. 그리고 더 다양한 파일형식을 지원하고 싶다면? 각각의 파일형식에 맞는 이미지 로더를 작성해야한다.</p>
<p>또 다른 해결책은 여러 파일 형식을 지원하는 이미지 로딩 라이브러리를 사용하는 것. </p>
<h4 id="std_imageh">std_image.h</h4>
<p><code>std_image.h</code>는 Sean Barrett이 작성한 유명한 단일 헤더 이미지 로딩 라이브러리이다. 다양한 파일 형식의 이미지를 로드할 수 있으며 프로젝트에 쉽게 통합 가능하다. <a href="http://github.com/nothings/stb/blob/master/stb_image.h">github.com/nothings/stb/blob/master/stb_image.h</a> 에서 다운 받을 수 있다. 간단하게 단일 헤더 파일만 받고 프로젝트에 추가 하면 된다. </p>
<pre><code class="language-cpp">#define STB_IMAGE_IMPLEMENTATION
#include &quot;stb_image.h&quot;</code></pre>
<p><code>STB_IMAGE_IMPLEMENTATION</code> <code>define</code>하는 것은 전처리기가 관련 있는 definition 소스 코드만 가지면서 효과적으로 헤더파일을 cpp파일로 변환하기위해 헤더 파일을 수정한다.  <code>std_image.h</code>를 <code>include</code>하고 컴파일 하면 된다.</p>
<p>이미지 로드하기 위해서 <code>std_image.h</code>를 가지고 <code>stbi_load</code> 함수를 사용</p>
<pre><code class="language-cpp">int width, height, nrChannels;
unsigned char *data = stbi_load(&quot;container.jpg&quot;, &amp;width, &amp;height,
                                                                &amp;nrChannels, 0);</code></pre>
<p>함수 첫번째 인자는 이미지 파일 경로, 나머지 인자들은 <code>std_image.h</code>가 이미지의 폭, 높이 그리고 색상 채널 값을 채워줄 정수형 변수들을 넣는데 차후에 <code>width</code>, <code>height</code>, <code>nrChannels</code>을 사용할 예정이다.</p>
<h4 id="generating-a-texture">Generating a texture</h4>
<p>다른 objects들과 마찬가지로 texture도 ID를 받는다. </p>
<pre><code class="language-cpp">unsigned int texture;
glGenTextures(1, &amp;texture);</code></pre>
<p><code>glGenTextures</code> 함수의 첫번째 인자는 얼마나 많은 텍스쳐를 만들고 <code>unsigned int</code> 배열 저장할 것인지 두번째는 그 배열(<code>unsigned int</code> 배열). 다만, 여기서는 하나만 받으니 단일 변수로 처리했다. 다음도 다른 object들처럼 바인딩한다.</p>
<pre><code class="language-cpp">glBindTexture(GL_TEXTURE_2D, texture);</code></pre>
<p>텍스쳐가 바인딩이 된 후 이전에 로드된 이미지를 사용하여 텍스쳐를 만들 수 있다. <code>glTexImage2D</code> 함수로 만든다.</p>
<pre><code class="language-cpp">glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,
                         GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);</code></pre>
<p>이 함수는 조금 많은 인자들을 가진다.</p>
<ul>
<li>첫번째 인자는 texture target. 현재 바인딩된(bound) texture object에 texture을 만들기에 같은 target이여야한다. 그래서 <code>GL_TEXTURE_2D</code>. <code>GL_TEXTURE_1D</code>나 <code>GL_TEXTURE_3D</code>으로 바인된 texture에는 영향을 주지 않는다.</li>
<li>두번째 인자는 mipmap level. 각 mipmap level을 수동으로 설정할지 안할지에 대한 값.</li>
<li>세번째 인자는 OpenGL에 그 texture를 어떠한 형식으로 저장할지 알려준다. 여기서 image는 RGB만 가지므로 <code>GL_RGB</code></li>
<li>네번째와 다섯번째 인자는 폭과 높이</li>
<li>여섯번째는 항상 ‘0’. (legacy stuff)</li>
<li>일곱번째와 여덟번째 인자는 소스 이미지의 데이터 형식(<code>GL_RGB</code>)과 데이터 타입(<code>GL_UNSIGNED_BYTE</code>). 여기서 RGB값을 가진 로드했고 <code>char</code>s(bytes)로 저장했다. 따라서 이 정보를 명시한다.</li>
<li>마지막 인자는 실제 이미지 데이터</li>
</ul>
<p><code>glTexImage2D</code>가 호출되는 순간 현재 바인딩된 texture object가 텍스쳐 이미지 첨가된채로 가진 상태이다. 그러나 로드한 텍스쳐 이미지의 기초 단계를 가졌을 뿐이며 mipmap 사용을 원한다면 모든 이미지들에 대해서 매뉴얼로 명시하거나 <code>glGenerateMipmap</code> 함수를 texture을 만들어 낸 후에 호출한다. 이는 자동으로 현재 바인딩된 texture에 관한 모든 mipmap을 만들어낸다.</p>
<p>texture와 그에 맞는 mipmap을 만들어낸 후 이미지 메모리를 해제한다.</p>
<pre><code class="language-cpp">stbi_image_free(data);</code></pre>
<p>texture를 만들어내는 코드 전체적으로 보면</p>
<pre><code class="language-cpp">unsigned int texture;
glGenTextures(1, &amp;texture);
glBindTexture(GL_TEXTURE_2D, texture);
// set the texture wrapping/filtering options (on currently bound texture)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// load and generate the texture
int width, height, nrChannels;
unsigned char *data = stbi_load(&quot;container.jpg&quot;, &amp;width, &amp;height,
&amp;nrChannels, 0);
if (data)
{
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB,
    GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
    std::cout &lt;&lt; &quot;Failed to load texture&quot; &lt;&lt; std::endl;
}
stbi_image_free(data);</code></pre>
<h4 id="applying-texture">Applying texture</h4>
<p><code>glDrawElements</code> 함수로 직사각형 그린 vertices에 색상값과 텍스쳐 좌표계를 추가</p>
<pre><code class="language-cpp">float vertices[] = {
    // positions      // colors         // texture coords
    0.5f,   0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // top right
    0.5f,  -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // bottom right
    -0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom left
    -0.5f,  0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f  // top left
};</code></pre>
<p>이제 vertex attributes를 설정해줘야한다. 아래의 그림과 같이 맞춰야한다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/5eedb3d7-84b7-4a58-a93d-cc303c0fa35a/image.png" alt=""></p>
<pre><code class="language-cpp">glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float),
                                            (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);</code></pre>
<p>직사각형에 맞게 맞춘 코드도 <code>8 * sizeof(float)</code>으로 변경하고 아래에 위의 코드를 추가한다.</p>
<p>이제 vertex shader를 아래의 코드와 같이 수정한다.</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;
out vec3 ourColor;
out vec2 TexCoord;
void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor;
    TexCoord = aTexCoord;
}</code></pre>
<p>이제 fragment shader에 전달해주고 fragment shader가 텍스쳐 오브젝트에 접근해야한다. 어떻게 전달할 것인가? GLSL은 sampler라 불리는 텍스쳐 오브젝트에 관한 빌트인 데이터 타입을 가지고 있다. 이 <code>sampler</code> 타입은 텍스쳐 타입을 접미사로 함수명 뒤에 붙는다. e.g. <code>sampler1D</code>, <code>sampler3D</code>, 여기서는 <code>sampler2D</code>. 나중에 texture를 할당하기 위해 <code>uniform sampler2D</code>로 선언한다.</p>
<pre><code class="language-cpp">#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main()
{
    FragColor = texture(ourTexture, TexCoord);
}</code></pre>
<p>texture의 색상값을 sampling하기 위해서 GLSL의 빌트인 <code>texture</code> 함수를 사용. 첫번째 인자는 texture sampler, 두번째 인자는 상응하는 texture 좌표를 갖는다. <code>texture</code> 함수는 우리가 이전에 설정했던 texture parameter들을 사용하여 상응하는 색상값을 샘플링한다. fragment shader의 output은 보간된(interpolated) 텍스쳐 좌표에서 필터링된(filtered) 텍스쳐 색상값이다. </p>
<p>이제 남은 것은 <code>glDrawElements</code> 함수 이전에 texture를 바인딩하고 자동으로 fragment shader의 sampler에 texture가 할당되는 것.</p>
<pre><code class="language-cpp">glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);</code></pre>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/1797f2a2-ef40-497a-a148-186abb42e47d/image.png" alt="">
위와 같은 결과물이 나온다.</p>
<p>texture 색상값과 vertex 색상값을 섞을 수 있다. 간단하게 fragment shader에서 texture 색상값의 결과와 vertex 색상값을 곱한다.</p>
<pre><code class="language-cpp">FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);</code></pre>
<p>그럼 아래와 같은 결과물을 볼 수 있다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/49bad6b8-12fc-47c1-b419-ff316b67808b/image.png" alt=""></p>
<h4 id="texture-units">Texture units</h4>
<p>위에서 <code>glUniform</code>을 사용하여 값을 할당하는 것도 아닌데 왜 <code>sampler2D</code>가 <code>uniform</code>으로 선언되는지 궁금했을 것이다. <code>glUniform1i</code>을 사용하여 실제로 location 값을 texture sampler에 할당할 수 있고 fragment shader에서 여러 texture들을 한번에 설정할 수 있다. texture의 위치는 texture unit이라고 더 흔하게 말한다. 하나의 texture의 디폴트 texture unit은 0이다. 이는 default active texture unit이다. 그래서 이전에 location을 할당할 필요가 없었다. 주의할 것은 모든 그래픽 드라이버들이 default texture unit을 할당하지 않는다. 바로 전에 했던 예시들이 렌더링 안될 수도 있다.</p>
<p>texture unit의 주 목적은 shader에서 더 많은 texture를 사용하게 해주는 것. texture unit을 sampler에서 할당하는 것으로 그 상응하는 texture unit이 먼저 활성화되는 한 여러 texture들이 한번에 바인딩된다. <code>glBindTexture</code>와 같이 texture unit을 전달받는 <code>glActiveTexture</code>을 사용하여 texture unit들을 활성화할 수 있다.</p>
<pre><code class="language-cpp">glActiveTexture(GL_TEXTURE0); // activate texture unit first
glBindTexture(GL_TEXTURE_2D, texture);</code></pre>
<p>texture unit을 활성화한 후 다음 <code>glBindTexture</code> 호출은 현재 활성화된 texture unit에 texture를 바인딩한다. texture unit <code>GL_TEXTURE0</code>은 항상 디폴트로서 활성화되어 있다. 따라서 이전 예시들에서 <code>glBindTexture</code>를 사용할 때 활성화할 필요가 없었다.</p>
<p>OpenGL은 적어도 최저 16개의 texture unit을 사용할 수 있게 한다. 따라서 <code>GL_TEXTURE0</code>부터 <code>GL_TEXTURE15</code>를 사용해서 활성화할 수 있다. 또한 <code>GL_TEXTURE8</code>을 <code>GL_TEXTURE0 + 8</code>로 가질 수 있다. 이는 여러 texture unit을 루프로 가질 때 유용하다.</p>
<p>또 다른 sampler를 가지기 위해서 fragment shader를 수정해본다.</p>
<pre><code class="language-cpp">#version 330 core
...
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
    FragColor = mix(texture(texture1, TexCoord),
    texture(texture2, TexCoord), 0.2);
}</code></pre>
<p>최종 색상 값은 texture1과 texture2, 두 텍스쳐가 조합된 색. GLSL의 빌트인 <code>mix</code>함수는 입력의 두 값을 세번째 인자 값을 기본으로하여 선형적으로 보간(linearly interpolate)한다. 세번째 인자 값이 <code>0.0</code>이라면 첫번째 입력된 texture를 반환, <code>1.0</code>이면 두번째 texture를 반환. <code>0.2</code>는 첫번째 입력값은 80퍼센트 두번째는 20퍼센트로 두 텍스쳐가 섞여서 결과로 나온다.</p>
<p>이제 또 다른 texture를 생성하자. 먼저 또 다른 texture object 생성, 이미지 로드 그리고 <code>glTexImage2D</code>함수로 texture를 만든다. </p>
<pre><code class="language-cpp">unsigned char *data = stbi_load(&quot;awesomeface.png&quot;, &amp;width, &amp;height,
&amp;nrChannels, 0);
if (data)
{
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA,
    GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
}</code></pre>
<p>.png 이미지는 alpha값을 가지므로 이미지 데이터 인자로 <code>GL_RGBA</code>를 넣는다. </p>
<p>두번째 texture를 사용하기 위해서 해당하는 texture unit에 두 texture들을 바인딩함으로 렌더링 절차를 조금 바꿔야한다. </p>
<pre><code class="language-cpp">glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);</code></pre>
<p>glUniform1i를 사용하여 어떤 texture unit이 설정된 각 sampler에 각 shader sampler가 속하는지 OpenGL에 알려줘야한다. 이는 한번만 설정하고 render loop전에 할 수 있다.</p>
<pre><code class="language-cpp">shader.use(); // don’t forget to activate the shader first!
glUniform1i(glGetUniformLocation(shader.ID, &quot;texture1&quot;), 0); // manually
shader.setInt(&quot;texture2&quot;, 1); // or with shader class
while(...)
{
    [...]
}</code></pre>
<p>glUniform1i을 통해 sampler들을 설정함으로 각 uniform sampler을 올바른 texture unit에 일치하게 하하면 아래의 결과를 갖는다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/88ec5229-2a8c-4fb6-aadd-b745f238cb47/image.png" alt=""></p>
<p>위 아래가 뒤집힌 결과를 갖는데 이는 OpenGL이 y축에서 (0, 0) 좌표가 이미지의 아래쪽으로 인식한다. 이미지는 y축의 제일 위로 (0, 0) 좌표점을 가진다. 다행히도 stb_image.h 는 이미지 로딩 중 y축을 뒤집을 수 있다. 코드는 아래와 같다.</p>
<pre><code class="language-cpp">stbi_set_flip_vertically_on_load(true);</code></pre>
<p>아래와 같이 뒤집혀있지 않고 제대로 나타나는 것을 확인할 수 있다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/f7860bf7-9729-41fd-bfd4-a90e54269d9a/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenGL_04, Shader]]></title>
            <link>https://velog.io/@kkj-100-010-110/OpenGL04-Shader</link>
            <guid>https://velog.io/@kkj-100-010-110/OpenGL04-Shader</guid>
            <pubDate>Fri, 22 Mar 2024 20:08:19 GMT</pubDate>
            <description><![CDATA[<p>GPU안에 있는 작은 프로그램을 shader라고 한다. 이 프로그램들, 즉 shader들은 그래픽스 파이프라인의 각 특정 부분에서 동작한다. shader들은 입력값들을 출력값으로 변환해주는 프로그램일뿐 그이상이 아니다. shader들은 서로 input과 output을 통해서만 통신할 뿐 그외는 허용되지 않으므로 고립되어있다.</p>
<h4 id="glsl">GLSL</h4>
<p>shader들은 C형식의 언어 GLSL로 쓰여졌다. GLSL은 그래픽스 사용에 맞춰져있으며 특히 벡터와 행렬 조작에 특화된 유용한 기능들을 가지고 있다.</p>
<p>shader들은 항상 버전 선언으로 시작 그다음 input, output 변수들, 그리고 uniform들과 main함수가 나온다. 각 shader의 entry point는 main함수이며 이 함수에서 output 변수에 input 변수를 처리한 출력의 결과물을 대입한다.</p>
<pre><code class="language-cpp">#version version_number
in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;
void main()
{
    // process input(s) and do some weird graphics stuff
    ...
    // output processed stuff to output variable
    out_variable_name = weird_stuff_we_processed;
}</code></pre>
<p>이제 vertex shader에 대해 얘기하자면, 각 input 변수는 vertex attribute라 알려져있다. 이 vertex attribute의 최대수는 하드웨어에 정해져있다. OpenGL은 항상 최소 16, 4개의 component vertex attributes를 보장한다. <code>GL_MAX_VERTEX_ATTRIBS</code>로 현재 하드웨어의 최대허용치를 검색할 수 있다.</p>
<pre><code class="language-cpp">int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &amp;nrAttributes);
std::cout &lt;&lt; &quot;Maximum nr of vertex attributes supported: &quot; &lt;&lt; nrAttributes
&lt;&lt; std::endl;</code></pre>
<h4 id="데이터-타입">데이터 타입</h4>
<p>GLSL의 대부분의 데이터 타입은 C 형태의 언어의 데이터 타입을 가진다.</p>
<ul>
<li>int, float, double, uint 그리고 bool</li>
</ul>
<p>또한 벡터와 행렬을 사용하는 두 가지 컨테이너 타입을 가지고 있다.</p>
<p>vector</p>
<p>N은 차원을 나타낸다. e.g. 1, 2, 3, 4.</p>
<ul>
<li>vecN - the default vector of N floats</li>
<li>bvenN - a vector of N booleans</li>
<li>ivecN - a vector of N integers</li>
<li>uvecN - a vector of N unsigned integers</li>
<li>dvecN - a vector of N double components</li>
</ul>
<p>기본 vecN 형식을 가장 많이 사용하며 거의 모든 작업을 하기에 충분하다.</p>
<p>각 component에 접근하는 방식은 ‘.’을 사용한다. e.g. vec.x, vec.y, vec.z, vec.w</p>
<ul>
<li>xyzw == rgba(색상) == stpq(texture coordinate, 텍스쳐 좌표계)</li>
</ul>
<p>벡터 데이터 타입은 swizzling을 통해 component 선택을 할 수 있다.</p>
<pre><code class="language-cpp">vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;</code></pre>
<p>또한 이 문자들을 조합하여 벡터 타입을 생성시 사용 가능할 수 있다.</p>
<pre><code class="language-cpp">vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);</code></pre>
<h4 id="ins-and-outs">Ins and Outs</h4>
<p>GLSL은 <code>in</code>과 <code>out</code> 키워드를 이용하여 각 shader들이 주고 받을 수 있게 정의했다. 각 shader들은 이 키워드로 inputs과 outputs을 명시하고 output 변수와 다음 shader 단계의 input과 매칭되는 변수로 전달할 수 있다.</p>
<p>vertex shader는 input의 몇가지 형식을 받아야한다. 그렇지 않으면 좀 비효율적이다. vertex shader가 input과 다르다면 vertex data로부터 곧장 input을 받는 점이다. vertex data를 어떻게 정리할지 정의하기 위해서 location metadata와 함께 input 변수를 명시하여 CPU에 vertex attributes를 설정할 수 있다. <code>layout(location = 0)</code>처럼 vertex shader는 input에 관한 layout 명시를 요구하며 이는 vertex data와 연결할 수 있다.</p>
<p><code>layout(location = 0)</code> 을 생략 가능하며 <code>glGetAttribLocation</code> 함수로 attribute location에 관해 OpenGL으로부터 질의하여 답을 구할 수 있다. 다만 이는 더 많은 작업이 요구된다.</p>
<p>또 fragment shader는 vec4 색상 output 변수가 필요하다. 이는 fragment shader가 최종 색상 값을 출력해야기 때문이다. 만약 없다면 그 fragment에 관한 색상 버퍼 출력은 undefined(여기서 검정색이나 하얀색이 그려진다)</p>
<p>하나의 shader로부터 <code>out</code> 키워드로 정의된 변수의 데이터를 다른 shader에서 <code>in</code> 키워드로 정의된 데이터가 보내지는데 두 변수명은 같아야한다. </p>
<pre><code class="language-cpp">// vertex shader
#version 330 core
layout (location = 0) in vec3 aPos; // position has attribute position 0
out vec4 vertexColor; // specify a color output to the fragment shader
void main()
{
    gl_Position = vec4(aPos, 1.0); // we give a vec3 to vec4’s constructor
    vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // output variable to dark-red
}</code></pre>
<pre><code class="language-cpp">// fragment shader
#version 330 core
out vec4 FragColor;
in vec4 vertexColor; // input variable from vs (same name and type)
void main()
{
    FragColor = vertexColor;
}</code></pre>
<p>코드에서 vertex shader에서 <code>out</code> 키워드로 선언된 <code>vertexColor</code>는 fragment shader에서 <code>in</code> 키워드로 선언된 <code>vertexColor</code>와 연결된다.</p>
<p>result
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/ccd1616e-da67-4d1c-ae92-ea95fe447b87/image.png" alt=""></p>
<h4 id="uniforms">Uniforms</h4>
<p>CPU에 있는 application으로부터 GPU에 있는 shader로 데이터를 전달하는 또 다른 방식. uniform은 vertex attributes와 비교하여 약간의 차이가 있다. 첫번째로 uniforms은 global 변수이다. uniform 변수는 shader 프로그램 전 단계에서 각 shader가 접근 가능하다. 두번째로 uniform 값을 설정하면 그 값이 재설정되거나 업데이트될 때까지 유지된다. </p>
<pre><code class="language-cpp">#version 330 core
out vec4 FragColor;
uniform vec4 ourColor; // we set this variable in the OpenGL code.
void main()
{
    FragColor = ourColor;
}</code></pre>
<p>이제 OpenGL 코드에서 어떻게 하는지 보면</p>
<pre><code class="language-cpp">float timeValue = glfwGetTime();
float greenValue = (sin(timeValue) / 2.0f) + 0.5f;
int vertexColorLocation = glGetUniformLocation(shaderProgram, &quot;ourColor&quot;);
glUseProgram(shaderProgram);
glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);</code></pre>
<p>먼저 <code>glfwGetTime()</code>를 통해서 running time을 찾고 색상 범위는 <code>sin</code>함수를 통해 -1.0 ~ 1.0 사이로 만들고 <code>2.0f</code>로 나누고 <code>0.5f</code>를 더하면 0.0 ~ 1.0사이로 변화를 주고 그 값을 <code>greenValue</code>에 저장한다. <code>glGenUniformLocation</code> 함수를 사용하여 <code>ourColor</code> uniform의 위치를 질의하며 그 위치를 <code>vertexColorLocation</code>에 저장한다. <code>glGenUniformLocation</code> 함수가 -1을 리턴하면 두번째 인자명의 uniform 변수를 찾지 못한 것. 마지막으로 <code>glUniform4f</code> 함수를 통하여 uniform 값을 설정. uniform location을 찾는 것은 shader program을 먼저 사용하는 것을 요구하는게 아니라 uniform을 변경하는 것이 프로그램을 먼저 사용하는 것(<code>glUseProgram</code> 호출)을 요구하는 것. 왜냐하면 이는 현재 활성화된 shader program에서 uniform을 설정하기때문.</p>
<p>OpenGL은 그 내부(core)는 C library이기에 함수 오버로딩 지원을 하지않으므로 OpenGL은 새로운 함수들은 각 타입에 맞게 정의되어있고 그 타입에 맞게 호출되어야한다. <code>glUniform</code> 이 최적의 예시이다. 이 함수는 어떤 타입으로 uniform을 설정할 것인지 함수명에 접미사를 붙여야한다.</p>
<ul>
<li>f - float</li>
<li>i - int</li>
<li>ui - unsigned int</li>
<li>3f - 3 floats; Nf - N floats</li>
<li>fv - float vector/array</li>
</ul>
<p>위 코드를 render loop안 <code>glUseProgram</code>와 <code>glBindVertexArray(VAO)</code>사이에 넣으면 삼각형 색이 반복적으로 시간에 따라 초록색에서 점점 검정색으로 되었다가 다시 초록색으로 바뀌는 것을 볼 수 있다.</p>
<h4 id="more-attributes">More attributes</h4>
<p>다시 VAO와 VBO를 사용하여 색상값을 설정해보자. 아래와 같이 삼각형의 각 꼭지점을 표현하는 vertices 뒤에 RGB 색상값을 추가.</p>
<pre><code class="language-cpp">float vertices[] = {
    // positions // colors
    0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right
    -0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left
    0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top
};</code></pre>
<p>이제 vertex shader에 더 많은 데이터를 전달하기때문에 vertex shader를 수정해야한다. 아래의 코드로 수정한다.</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos; // position has attribute position 0
layout (location = 1) in vec3 aColor; // color has attribute position 1
out vec3 ourColor; // output a color to the fragment shader
void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor; // set ourColor to input color from the vertex data
}</code></pre>
<p>layout specifier와 함께 <code>aColor</code> attribute의 <code>location</code>을 설정</p>
<p>아래는 fragment shader로 uniform으로 지정한 변수를 <code>in</code>으로 하여 vertex shader로부터 전달 받는다.</p>
<pre><code class="language-cpp">#version 330 core
out vec4 FragColor;
in vec3 ourColor;
void main()
{
    FragColor = vec4(ourColor, 1.0);
}</code></pre>
<p>위와 같이 vertex attribute를 추가하였고 VBO memory를 수정했으므로 vertex attribute pointer를 재설정해야한다. 업데이트된 VBO 메모리는 아래와 같을 것이다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/23693ea9-9f59-4889-81b9-f96404e5ae38/image.png" alt="">
위와 같이 코드를 수정하면</p>
<pre><code class="language-cpp">// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float),
(void*)0);
glEnableVertexAttribArray(0);
// color attribute
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float),
(void*)(3* sizeof(float)));
glEnableVertexAttribArray(1);</code></pre>
<p>달라진 점은 VBO의 vertices에 따라 location이 추가됨에따라 glVertexAttribPointer 함수와 glEnableVertexAttribArray 함수 각각 하나씩 더 추가되었고, 첫번째 glVertexAttribPointer에서 수정한 부분은 5번째 인자의 stride 부분을 수정했다. 위치에 맞게 색상값을 건너뛰게 24바이트로 설정. 두번째 location 1 색상값을 설정하기 위해서 첫번째 인자는 1 그리고 위와 마찬가지로 stride 부분을 24바이트로 설정, 마지막 인자는 offset으로 색상 attribute가 위치 데이터 뒤에 시작하므로 12바이트로 설정.</p>
<p>result
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/aa7df671-1a76-4bee-93b3-448811ceb959/image.png" alt=""></p>
<p>설정해준 부분을 보면 삼각형의 세꼭지점의 지점과 그 지점의 색상값이다. 위 결과물을 보면 세꼭지점에서 중점으로 오면서 색상이 변한다. 이는 fragment shader에서 fragment interpolation으로 불리는 것의 결과다. 실제 삼각형을 렌더링할 때 rasterization 단계에서 원래 명시했던 vertices보다 더 많은 fragment들을 낳는다. rasterizer는 fragment가 삼각형 모양에 속하는 것을 토대로 각각의 fragment의 위치를 결정, 이 위치에 따라서 fragment shader의 input 변수 모두 interpolate(보간)한다.</p>
<p>나머지 부분들은 책에서 나오듯이 shader 클래스를 만들고 메인에서 코드 정리하면된다.
책에서 제공하는 코드도 참고하여 변경하면 된다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenGL_03, Hello Triangle]]></title>
            <link>https://velog.io/@kkj-100-010-110/OpenGL03-Hello-Triangle</link>
            <guid>https://velog.io/@kkj-100-010-110/OpenGL03-Hello-Triangle</guid>
            <pubDate>Thu, 21 Mar 2024 16:13:17 GMT</pubDate>
            <description><![CDATA[<p>OpenGL에서 모든 것은 3차원 공간에 있다. 그러나 스크린 또는 창은 2차원 픽셀 배열이다. 그래서 OpenGL 작업 중 큰 부분은 모든 3차원 좌표를 스크린에 맞춘 2차원 픽셀로 변환하는 것에 관한 것이다.</p>
<p>3차원 좌표를 2차원 픽셀로 변환하는 작업은 OpenGL의 그래픽 파이프라인에 의해서 관리된다. 그래픽 파이프라인은 크게 두 파트로 나눌 수 있다. 첫번째는 3차원을 2차원으로 변환하는 것, 두번째는 그 2차원을 실제 색상을 입힌 픽셀로 변환하는 것.</p>
<p>그래픽 파이프라인은 3차원 좌표들로 이루어진 집합을 입력값으로 받고 이를 색상을 입힌 2차원 픽셀로 변환하는 것. 그래픽 파이프라인은 여러 스텝으로 나누어질 수 있는데 각각의 단계에서 입력값으로 이전의 출력값을 요구한다. 이러한 모든 단계들은 각 단계에 맞게 분화되어있고 쉽게 병렬로 처리될 수 있다. 이러한 성질로 오늘날 그래픽 카드는 그래픽 파이프라인 내부에서 빠르게 데이터를 처리하기 위해서 수천개의 작은 프로세싱 코어들을 가진다. 이러한 코어들을 처리하는 것은 파이프라인 각각의 단계에 맞게 GPU에 있는 작은 프로그램을 실행한다. 이런 작은 프로그램들을 shader라 부른다.</p>
<p>현재 사용하는 디폴트 shader를 대체하기 위해서 몇몇 shader들은 개발자들 개인에게 맞는 shader를 작성할 수 있게 환경 설정이 가능하다. 이는 우리에게 파이프라인 특정부분에서 좀더 세세하게 제어할 수 있게 해주며 shader들이 GPU에서 작동하므로 CPU 처리(가장 중요한) 시간을 줄여준다. OpenGL Shading Language(GLSL)로 shader들은 작성되어있다. </p>
<p>그래픽 파이프라인의 전과정을 추상화한 것을 볼 수 있다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/a67058e0-a111-4e1b-843b-b651e0662dae/image.png" alt="">
각각의 단계를 간략하게 살펴보자</p>
<p>VERTEX DATA라 불리는 배열 하나의 삼각형 형식으로 되어야하는 세개의 3차원 좌표의 목록을 그래픽 파이프라인의 입력값으로 전달. vertex는 각 3차원 좌표에 해당하는 데이터 모음이다. vertex 데이터는 원하는 어떤 데이터든 담을 수 있는 vertex attribute(속성)을 사용하여 나타낸다. 간단하게 3차원 위치와 색상 값으로 구성되어 있다고 가정하자.</p>
<p><strong>OpenGL이 좌표들의 모음과 색상 값으로 무엇을 만들어낼건지 알기 위해서 OpenGL은 데이터에 맞는 형식으로 어떤 종류의 렌더 타입인지 알려주길 요구한다. 데이터가 점이나 삼각형 또는 긴 선분으로 그려지길 원하는가? 이런 힌트들은 primitives 불리며 그리는 명령어들이 호출되는 중에 OpenGL에 전달된다. 그것들 중에 대표적으로 <code>GL_POINTS</code>, <code>GL_TRIANGLES</code> 그리고 <code>GL_LINE_STRIP</code>이 있다.</strong></p>
<p>파이프라인의 첫 단계인 단일 vertex를 입력값으로 받는 vertex shader이다. 이 vertex shader의 주된 목적은 3차원 좌표를 또다른 3차원로 변환하고 vertex attribute에 몇 가지 기본적인 처리작업을 하게 해준다.</p>
<p>primitive assembly 단계는 주어진 primitive 모양에서 primitive 구성하고 모든 점을 모아 조합하는 vertex shader로부터 모든 vertices를 입력값으로 받거나 <code>GL_POINTS</code>이 선택되어있으면 vertex를 받는다. 여기서는 삼각형 모형.</p>
<p>primitive assembly 단계에서의 출력값은 geometry shader로 전달된다. primitive 형식인 vertices의 모음을 입력값으로 받고 새로운 거나 또 다른 primitive를 구성하기 위해서 새로운 vertices를 냄으로서 다른 모양을 생산할 능력을 가진다. 여기서는 주어진 모양에서 두번째 삼각형을 생성.</p>
<p>geometry shader의 출력값은 rasterization 단계에 전달. rasterization단계에서 결과물이 스크린에 상응하는 픽셀과 맵핑되며 fragment shader를 사용하기 위해 fragment로 된다. fragment shader가 실행되기 전에 clipping이 수행된다. clipping은 성능향상을 위해 view 바깥쪽의 fragment를 폐기한다.</p>
<p><strong>OpenGL에서 하나의 fragment는 OpenGL이 하나의 픽셀을 렌더하기 위해 요구되는 모든 데이터들이다.</strong></p>
<p>fragment shader의 주된 목적은 픽셀의 마지막 색상을 계산하는 것이며 이는 일반적으로 고급 OpenGL 효과들이 일어나는 단계이다. fragment sahder는 일반적으로 (광원, 그림자, 광원색 등과 같은) 마지막으로 색상을 계산하기 위해서 사용하는 3차원 장면에 관한 데이터들을 담고 있다. </p>
<p>상응하는 모든 색상 값들이 정해진 후 마지막 object(물체)가 alpha test와 blending로 불리는 단계를 통해서 전달된다. 이 단계에서 fragment의 depth와 stencil 값이 상응하는지 확인하고 다른 물체보다 앞에 있는지 뒤에 있는지 확인하기 위해서 그 값들을 사용하고 그것들에 맞춰 잘라낸다. 이 단계에서 또 alpha 값을 확인하고 그에 맞게 물체를 blend(조합)한다. fragment shader에서 픽셀 출력값이 계산된다고 하더라도, 마지막 픽셀 색상은 여러 삼각형들이 그려질 때 완전히 다른 어떤 색이 될 수 있다.</p>
<p>이렇듯 그래픽스 파이프라인은 전체적으로 복잡하고 많은 환경설정해야하는 부분들을 포함한다. 그렇지만, 거의 대부분의 경우 vertex, fragment shader 이 두 가지만 작업한다. geometry shader는 옵션이고 보통 디폴트로 놔둔다. 또 tessellation 단계와 변환 피드백 루프가 있지만 나중에 알아본다.</p>
<p>현대 OpenGL은 최소한 vertex shader와 fragment shader를 정의하길 요구된다. 이 이유로 배우기 어렵고 힘들다.</p>
<h4 id="vertex-input">Vertex Input</h4>
<p>무엇을 그리기전에 OpenGL에 입력 vertex 데이터를 주어야한다. OpenGL은 3차원 그래픽 라이브러리이며 OpenGL에서 명시한 좌표들은 전부 3차원이다. OpenGL은 간단하게 3차원을 2차원으로 변환하지 않는다. OpenGL은 좌표들이 세 축(x,y,z)에서 -1.0 ~ 1.0 범위안에 있을때만 3차원 좌표들을 처리. 모든 좌표가  <strong>normalized device coordinates</strong> 범위 안에 있으면 스크린에 나타난다.</p>
<p>단일 삼각형을 그리기 때문에 각 vertex가 3차원 위치를 갖는 세개의 vertices를 다 명시해야한다. float형 배열에 그 vertices를 normalized device coordinates로 정의한다.</p>
<pre><code class="language-cpp">float vertices[] = {
            -0.5f, -0.5f, 0.0f,
             0.5f, -0.5f, 0.0f,
             0.0f,  0.5f, 0.0f
};</code></pre>
<p>OpenGL은 3차원 공간에서 작업하므로 각각의 vertex의 z축은 0.0을 가진다.(2차원 삼각형이기때문에)</p>
<p><em>Normalized Device Coordinates(NDC)</em></p>
<p><em>vertex 좌표들이 vertex shader에서 처리되었다면, 그 좌표들은 NDC.</em></p>
<p><em>NDC는 glViewport 함수에 건네는 데이터를 사용해서 viewport transform을 통해서 screen-space coordinates로 변환된다. 그 후 screen-space coordinates는 fragment shader의 입력값으로 fragments로 변환된다.</em></p>
<p><em>일반적인 스크린 좌표계와는 달리 (0, 0)은 좌상단이 아닌 정중앙.</em></p>
<p><em>이 NDC는</em> <code>glViewport</code> <em>데이터를 사용하여 viewport transform을 거쳐서 screen-space coordinates로 변형되고 그 결과물은 fragment shader의 입력값으로 fragment로 변형된다.</em></p>
<p>그래픽 파이프라인의 첫 번째 과정에 입력값으로서 보내려고 정의된 vertex data는 vertex shader이다. 이는 GPU에서 메모리 생성으로부터 끝나는데 이 메모리는 vertex data를 저장하고 어떻게 OpenGL이 메모리를 해석하는지 환경 설정하고 그래픽 카드에 데이터를 어떻게 보낼지 명시하는 곳이다. 그러고 vertex shader는 그 메모리로부터 가능한 많은 vertices을 보낼 수 있게 양을 처리한다.</p>
<p>GPU 메모리에 아주 많은 vertices를 저장할 수 있는 vertex buffer object(VBO)라 불리는 곳을 통하여 메모리 관리. 이러한 버퍼 오브젝트를 사용하는 이점은 데이터를 한꺼번에 크게 묶어서 그래픽카드에 보낼 수 있게 해주며 한번에 하나의 데이터를 보내는 것 없이 메모리가 충분히 남았다면 메모리에서 데이터를 유지할 수 있다. CPU에서 그래픽 카드로 데이터를 보내는 것은 상대적으로 느리다. 그래서 한번에 모아서 보내려고 한다. 그래픽 카드에 데이터가 있자마자 vertex shader는 거의 즉각 vertices에 접근해서 빠르게 처리한다.</p>
<p>다른 object들과 같이 VBO는 버퍼에 상응하는 고유 ID를 가진다. <code>glGenBuffers</code> 함수를 이용해서 buffer ID와 buffer 생성</p>
<pre><code class="language-cpp">unsigned int VBO;
glGenBuffers(1, &amp;VBO);</code></pre>
<p>OpenGL은 다양한 타입의 버퍼 오브젝트를 가지고 있으며 VBO의 버퍼 타입은 <code>GL_ARRAY_BUFFER</code>이다. OpenGL은 버퍼들이 서로 다른 타입이라면 한번에 여러 버퍼들을 바인드 할 수 있게 해준다. 새로 생성된 버퍼를 <code>glBindBuffer</code> 함수를 사용해 <code>GL_ARRAY_BUFFER</code> 타겟으로 묶을 수 있다.</p>
<pre><code class="language-cpp">glBindBuffer(GL_ARRAY_BUFFER, VBO);
                                    target</code></pre>
<p><code>GL_ARRAY_BUFFER</code> 타겟에서 버퍼 호출들은 현재 바인드된 버퍼, 즉 VBO를 환경 설정하기 위해 사용될 것. 이후 버퍼 메모리에 이전에 정의된 버퍼들을 복사하는 <code>glBufferData</code> 함수를 호출할 수 있다.</p>
<pre><code class="language-cpp">glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);</code></pre>
<p><code>glBufferData</code> 사용자 정의된(user-defined) 데이터를 현재 바인드된(bound) 버퍼에 복사하기 위한 함수. 첫번째 인자는 데이터를 복사할 버퍼의 타입(여기서 GL_ARRAY_BUFFER 타겟에 바인드된 VBO). 두번째는 버퍼로 보낼 데이터의 사이즈, 세번째 인자는 실제 데이터, 네번째 인자는 주어진 데이터를 그래픽 카드가 어떻게 처리할지에 대한 정보, 여기에 3가지 형식이 있다.</p>
<p><code>GL_STREAM_DRAW</code>: 데이터는 한번만 설정되고 GPU에 의해 많아봐야 몇번 사용되어진다.
<code>GL_STATIC_DRAW</code>: 데이터는 변하지 않고 많이 사용된다.
<code>GL_DYNAMIC_DRAW</code>: 데이터는 정말 많이 변하고 많이 사용된다.</p>
<p>삼각형의 위치 데이터는 변하지 않으며 많이 사용되고 매번 렌더 호출 있을 때마다 같은 곳에 있기 때문에 이에 맞는 타입은 <code>GL_STATIC_DRAW</code>이다. 만약 예를들어 자주바뀌는 데이터를 가진 버퍼를 가진다면 <code>GL_DYNAMIC_DRAW</code> 타입의 사용이 그래픽 카드가 더 빠른 작업을 하게 메모리에 데이터를 놓아두게 해준다.</p>
<h4 id="vertex-shader">Vertex shader</h4>
<p>Vertex shader는 사용자가 프로그래밍 가능한 shader 중 하나. 현대 OpenGL은 최소한 vertex, fragment shader 설정하는 것을 요구.</p>
<p>GLSL shader 언어에서 vertex shader 작성이 첫번째 해야할 일이고 그리고 난 뒤 이 shader를 컴파일해서 사용할 애플리케이션에 적용.</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}</code></pre>
<p>위 코드를 보면 알겠지만 C와 굉장히 비슷하다. 각 shader들은 처음에 그 version을 선언. GLSL 버전과 OpenGL 버전과 매치해야한다(420이면 OpenGL 4.2). 또한 core profile 기능을 사용할거라고 명시.</p>
<p>다음은 vertex shader에 vertex 속성을 ‘in’이라는 키워드와 함께 입력값을 선언한다. 현재 위치 데이터만 신경쓰므로 단일 vertex attribute만 필요. GLSL는 postfix digit을 기반으로 1 ~ 4개의 float형을 담고 있는 벡터 데이터 타입을 가진다. 각각의 vertex는 3차원 좌표값을 가지고 있기에 aPos 명으로 <code>vec3</code> 변수를 생성. <code>layout (location = 0)</code>을 통해 입력 변수의 위치를 특정해서 설정한다. 이는 왜 필요한지 나중에 알아갈 것</p>
<p><em>컴퓨터 그래픽스에서의 Vector - 수학적 개념으로 많이 사용. 이는 점/위치 개념으로 사용하고 유용한 수학적 성질을 가진다. GLSL에서 벡터의 최대 사이즈는 4이며 각각의 값에 검색/접근은 <code>vec.x</code>, <code>vec.y</code>, <code>vec.z</code> 그리고 <code>vec.w</code>이다. <code>vec.w</code>의 성분은 공간의 위치 개념이 아닌 원근법을 나타낸다.</em></p>
<p>vertex shader의 출력값을 설정하기위해 미리 정의된 <code>gl_Position</code>(<code>vec4</code>) 변수에 위치 데이터를 할당해야한다. 메인 함수 끝에 <code>gl_Position</code>에 설정하는 어떤 값이든 vertex shader의 출력값으로 사용되어질 것. 여기서 사용되는 벡터의 크기는 <code>vec3</code>라서 <code>vec4</code>로 형변환을 해야한다. w 성분값에 1.0f를 넣는다.</p>
<p>위 예시는 가장 간단한 케이스다. 실제 응용 프로그램의 데이터들은 Normalized Device Coordinates가 안된 상태이기에 OpenGL 영역에 맞는 좌표로 변환해줘야한다.</p>
<h4 id="compiling-a-shader">Compiling a shader</h4>
<p>먼저 const C string을 코드 최상단에 놓고 저장</p>
<pre><code class="language-cpp">const char *vertexShaderSource = &quot;#version 330 core\n&quot;
    &quot;layout (location = 0) in vec3 aPos;\n&quot;
    &quot;void main()\n&quot;
    &quot;{\n&quot;
    &quot; gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n&quot;
    &quot;}\0&quot;;</code></pre>
<p>OpenGL이 shader를 사용할 수 있게 소스코드로부터 실행시간에 동적으로 컴파일해야 한다. 첫번째는 ID에 의해서 참조되게 shader object 생성. 그래서 <code>vertexShader</code>를 <code>unsinged int</code>로 선언하고 <code>glCreateShader</code>로 shader 생성.</p>
<pre><code class="language-cpp">unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);</code></pre>
<p><code>glCreateShader</code>의 인자로 원하는 shader type을 전달한다. vertex shader를 필요하니 <code>GL_VERTEX_SHADER</code>를 전달한다.</p>
<pre><code class="language-cpp">glShaderSource(vertexShader, 1, &amp;vertexShaderSource, NULL);
glCompileShader(vertexShader);</code></pre>
<p><code>glShaderSource</code> 함수는 컴파일 할 shader object를 첫번째 인자로 받고 두번째는 전달하는 소스코드가 얼마나 많은 문자열(string)이 있는지 명시. 세번째는 vertex shader의 실제 코드를 인자값으로 받는다. 여기서 네번째 인자는 NULL이다.</p>
<p><code>glCompileShader</code> 함수가 성공적으로 확인하기 원하면 컴파일 때 에러 체크를 하기위해서 아래와 같이 작성</p>
<pre><code class="language-cpp">int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &amp;success);</code></pre>
<p><code>glGetShaderiv</code> 함수를 이용해서 컴파일이 성공적인지 확인할 수 있다. 실패하면 <code>success</code> 변수에 0이 할당되고 아래와 같이 분기문을 만들어 그 분기문 안으로 들어가게 하여 <code>glGetShaderInfoLog</code> 함수를 호출하여 <code>infoLog</code>를 매개 변수로 전달하여 에러 메시지를 담는다.</p>
<pre><code class="language-cpp">if(!success)
{
    glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
    std::cout &lt;&lt; &quot;ERROR::SHADER::VERTEX::COMPILATION_FAILED\n&quot; &lt;&lt;
    infoLog &lt;&lt; std::endl;
}</code></pre>
<h4 id="fragment-shader">Fragment shader</h4>
<p>Fragment shader는 픽셀의 섹상 출력값을 계산한다. 여기서는 오렌지 계열 색상 이용</p>
<p>컴퓨터 그래픽스에서 색은 4개의 값으로된 배열을 표현. RGBA(빨강, 초록, 파랑, 투명도 / Red, Green, Blue, Alpha)로 나타낸다. OpenGL 또는 GLSL에서 색을 정의할 때, 성분의 값을 0.0에서 1.0 사이의 값으로 설정. 주어진 색상 3가지 성분으로 1600만개의 다른 색을 표현 수 있다.</p>
<pre><code class="language-cpp">#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}</code></pre>
<p>여기에서 fragment shader는 vec4로 최종 색상값이 정의된 하나의 출력 변수만 요구. ‘<code>out</code>’ 키워드와 함께 출력 변수 <code>FragColor</code> 선언. 그리고 그 변수에 vec4의 색상값 할당.</p>
<p>fragment shader를 컴파일하는 과정은 shader type으로 <code>GL_FRAGMENT_SHADER</code>인 거 말고는 vertex shader와 비슷하다.</p>
<pre><code class="language-cpp">unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &amp;fragmentShaderSource, NULL);
glCompileShader(fragmentShader);</code></pre>
<h4 id="shader-program">Shader program</h4>
<p>Shader program object는 결합된 여러 shader들의 최종 링크된 버전. 현재 컴파일된 shader들을 사용하기 위해서 shader program object에 링크해야하고 object들을 렌더링할 때 shader program을 활성화해야한다. 활성화된 shader program의 shader들은 렌더 호출이 일어날 때 사용될 것.</p>
<p>shader들을 프로그램으로 연결시킬 때 각각의 shader의 출력값들을 다음 shader에 연결한다. 여기서 또한 입력물과 출력물의 매칭이 잘못되면 에러.</p>
<pre><code class="language-cpp">unsigned int shaderProgram;
shaderProgram = glCreateProgram();</code></pre>
<p><code>glCreateProgram</code> 함수는 program을 생성하고 새로 생성된 program object의 ID 참조값을 리턴한다. <code>glLinkProgram</code> 함수를 사용하여 이전에 컴파일된 shader들을 program object에 붙이고 그것들을 연결한다. </p>
<pre><code class="language-cpp">glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);</code></pre>
<p>새로 생성된 program object를 인자로 받아서 <code>glUseProgram</code> 함수를 호출함으로써 program object를 활성화 할 수 있다.</p>
<pre><code class="language-cpp">glUseProgram(shaderProgram);</code></pre>
<p><code>glUseProgram</code> 이후 모든 쉐이더와 렌더링 호출은 이 프로그램 객체를 사용할 예정.</p>
<p>shader들을 program object에 링크한 이후 shader object들을 삭제해야한다.</p>
<pre><code class="language-cpp">glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);</code></pre>
<p>현재 입력 vertex 데이터를 GPU로 보냈고 vertex, fragment shader 안에 있는 vertex 데이터들을 어떻게 처리해야하는지 GPU에 알려줬다.</p>
<p>아직 OpenGL은 메모리에 있는 vertex 데이터를 어떻게 해석하는지 모르고 어떻게 vertex 데이터를 shader attribute와 연결하는지 모른다.</p>
<h4 id="linking-vertex-attributes">Linking vertex attributes</h4>
<p>vertex shader는 어느 입력값이든 vertex attribute의 형식에 맞게 명시할 수 있게 해준다. 이는 더 나은 유연성을 주지만 수동으로 vertex shader에서 어느 vertex attribute로 입력 데이터의 어떤 부분이 가는지 명시해야한다는 것. 이는 OpenGL에게 렌더링 전에 어떻게 입력 데이터를 해석해야하는지 명시하는 것.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/2e9b2b6e-afb5-4232-beae-9e59393b520e/image.png" alt="">
vertex buffer data는 위와 같이 구성되어있다.</p>
<ul>
<li>위치 데이터는 32-bit float 값으로 저장</li>
<li>각각의 위치는 세개의 값으로 구성</li>
<li>세가지 값 사이 공간이나 다른 값은 없다. 배열이 값들로 꽉 채워져있다.</li>
<li>첫번째 값은 버퍼의 시작점이다.</li>
</ul>
<p><code>glVertexAttribPointer</code> 함수로 OpenGL에게 vertex 데이터를 어떻게 해석해야하는지 알려준다.</p>
<pre><code class="language-cpp">glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),(void*)0);
glEnableVertexAttribArray(0);</code></pre>
<p><code>glVertexAttribPointer</code> 함수는 조금 많은 인자를 가진다.</p>
<ul>
<li><p>첫번째는 어떤 vertex attribute를 환경 설정 원하는지 명시. vertex shader에서 layout (location = 0)와 position vertex attribute의 location을 명시한 걸 기억. 이는 vertex attribute의 position을 0으로 설정했고 이 vertex attribute에 데이터를 보내길 원하기 때문에 0을 전달한다.</p>
</li>
<li><p>두번째는 vertex attribute의 크기를 명시. 여기서 vertex attribute는 <code>vec3</code>이기에 세개의 값으로 구성</p>
</li>
<li><p>세번째는 데이터 형식을 명시. GLSL에서 <code>vec*</code>은 float형으로 되어있다.</p>
</li>
<li><p>네번째는 정규화가 되어있는지 아닌지 명시. 만약에 integer 형의 데이터 타입을 넣는다면, GL_TRUE를 설정. 정수형 데이터는 float형으로 변환될 때 0(또는 부호가 있을 때 -1)과 1로 normalized 되어있다. 여기서는 <code>GL_FALSE</code>.</p>
</li>
<li><p>다섯번째 인자는 stride로 연속적인 vertex attribute들의 간의 크기를 말한다. 다음 위치 데이터의 설정은 정확히 stride로 값을 명시한 것처럼 float형의 3배 크기만큼 떨어져 있다. 배열이 꽉꽉 채워있기때문에(vertex attribute 간의 빈 공간이 없으므로) stride를 0으로 주어 OpenGL이 stride를 알아낼 수 있게 할 수 있다(vertex attribute 간 빈 공간이 없을 경우). vertex attribute가 더 많아질 수록 신경써서 그 간격의 공간을 정의</p>
</li>
<li><p>마지막 인자는 <code>void*</code>(보이드 포인터)형으로 버퍼에서 위치 데이터가 시작하는 곳의 offset. 여기서 배열의 데이터가 시작하는 점이 위치 데이터이므로 값 0이 전달.</p>
<p>  각각의 vertex attribute는 VBO에 의해 관리되는 메모리로부터 자기의 데이터를 가져오고 VBO가 그 데이터를 어디서 가져오는지는 <code>glVertexAttribPointer</code> 함수 호출시 현재 GL_ARRAY_BUFFER와 묶인(bound) VBO에 의해 결정. 이전에 정의된 VBO는 계속 <code>glVertexAttribPointer</code> 호출전에 묶여(bound)있기에 vertex attribute 0은 그 vertex 데이터와 관련있다.</p>
</li>
</ul>
<p>이제 OpenGL에게 vertex data를 어떻게 해석해야하는지 명시했고 vertex attribute location을 인자로 받는 <code>glEnableVertexAttribArray</code> 함수 활성화?해야한다. (OpenGL이 vertex attribute array에 접근 가능하게 하는 건지 vertex attribute array가 뭘 할 수 있게 하는지 잘 모르겠음). vertex attribute는 디폴트로 disable이다. 이점으로부터 모든게 준비되었다. → vertex buffer object 사용할 버퍼에 vertex data를 초기화, vertex와 fragment 쉐이더 설정과 OpenGL에 vertex shader의 vertex attribute에 vertex data를 어떻게 연결하는지 알려줬다. OpenGL에서 하나의 object를 그리는 것은 아래와 같다.</p>
<pre><code class="language-cpp">// 0. copy our vertices array in a buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. then set the vertex attributes pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),
(void*)0);
glEnableVertexAttribArray(0);
// 2. use our shader program when we want to render an object
glUseProgram(shaderProgram);
// 3. now draw the object
someOpenGLFunctionThatDrawsOurTriangle();</code></pre>
<p>object를 그릴 때마다 위와 같은 프로세스가 반복해야한다. 만약 수백개의 object를 위와 같이 해야한다면? 적절한 buffer object를 바인딩하고 각각의 object에 관한 vertex attribute를 설정하는 것은 굉장히 벅차고 힘든 일이된다. 이러한 모든 설정들을 하나의 object(객체)에 저장하고 그 상태를 되찾기 위해 그 object를 간단히 바인딩하는 방법이 있다면?</p>
<h4 id="vertex-array-object">Vertex array object</h4>
<p>vertex array object(VAO)는 VBO와 같이 묶여(bind)질 수 있고 차후 vertex attribute는 VAO안에 저장될 지점으로부터 호출. 이는 vertex attribute pointer들을 설정할 때 그러한 호출을 한번에 만들어주게 하고 object를 그리기 원할 때마다 그에 상응하는 VAO로 bind할 수 있다. 다른 VAO를 바인딩하는 거 만큼 쉽게 다른 vertex data와 attribute 설정 사이를 전환하게 만든다. 설정한 모든 상태(state)는 VAO안에 저장된다.</p>
<p>Core OpenGL은 VAO를 사용하여 vertex 입력값과 어떻게 할 것을 알려주는 것을 요구. VAO bind가 실패했을 때 OpenGL은 그리는 것을 거절할 것.</p>
<p>VAO는 다음을 저장</p>
<ul>
<li>glEnableVertexAttribArray 또는 glDisableVertexAttribArray 호출</li>
<li>glVertexAttribPointer을 통한 vertex attribute 환경 설정</li>
<li>glVertexAttribPointer 호출에 의한 vertex attribute들과 연관된 Vertex buffer objects
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/c738ba10-b453-4798-ab08-ab999580a018/image.png" alt="">
VAO를 생성하는 것은 VBO를 생성하는 것과 비슷하다.</li>
</ul>
<pre><code class="language-cpp">unsigned int VAO;
glGenVertexArrays(1, &amp;VAO);</code></pre>
<p>VAO를 사용하기 위해서 해야할 것은 glBindVertexArray 사용하여 VAO를 binding하는 것. 이지점으로부터 상응하는 VBO와 attribute pointer를 bind와 configure해야하고 그런 뒤에 나중에 사용을 위해서 VAO를 unbind한다. object를 그릴때 그전에 정해놓은 설정과 VAO를 간단하게 bind한다.</p>
<pre><code class="language-cpp">// ..:: Initialization code (done once (unless your object frequently changes)) :: ..

// 1. bind Vertex Array Object
glBindVertexArray(VAO);
// 2. copy our vertices array in a buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. then set our vertex attributes pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),
(void*)0);
glEnableVertexAttribArray(0);
[...]
// ..:: Drawing code (in render loop) :: ..
// 4. draw the object
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();</code></pre>
<p>다음 내용들 모두 이지점으로부터 이끌어진다. VAO는 vertex attribute 설정과 어떤 VBO을 사용할지 저장되어있다. 보통 여러 object들을 그릴 때 먼저 모든 VAO를 생성, 설정하고 저장. 여러 object들 중 하나를 그리는 순간 그에 상응하는 VAO를 바인드하고 그리고 언바인드한다. </p>
<p>선택한 오브젝트를 그리기위해 OpenGL은 <code>glDrawArrays</code> 함수를 제공한다. 이 함수는 현재 활성화된 shader, 이전에 정의된 vertex attributes 설정과 VAO를 통해 간접적으로 바인딩된 VBO의 vertex를 사용하여 primitives를 그린다.</p>
<pre><code class="language-cpp">glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);</code></pre>
<p><code>glDrawArrays</code> 함수는 첫번째 인자로 primitive type을 두번짼는 vertex array의 시작 index 마지막 세번째는 얼마나 많은 vertex(vertices)를 그릴 것인지 전달
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/d3c3334c-339c-49e9-af4f-afff5967f479/image.png" alt=""></p>
<h4 id="element-buffer-objects">Element Buffer Objects</h4>
<p>EBO(element buffer objects) 이 어떻게 동작하는지는 예를 통해서 보는게 좋다.</p>
<p>직사각형을 그린다고 하자 → 삼각형 두 개를 사용하여 그릴 수 있다. (OpenGL은 주로 삼각형을 대상으로 한다.)  그래서 두 vertex(vertices)를 만들어보면</p>
<pre><code class="language-cpp">float vertices[] = {
    // first triangle
    0.5f, 0.5f, 0.0f, // top right
    0.5f, -0.5f, 0.0f, // bottom right
    -0.5f, 0.5f, 0.0f, // top left
    // second triangle
    0.5f, -0.5f, 0.0f, // bottom right
    -0.5f, -0.5f, 0.0f, // bottom left
    -0.5f, 0.5f, 0.0f // top left
};</code></pre>
<p>여기서 bottom right와 top left는 겹친다. 4개의 정점만 있으면 만들어지는 직사각형이기때문에 이는 50퍼센트의 오버헤드다. 이는 더 복잡하고 큰 오브젝트를 그릴때 더 큰 오버헤드, 즉 겹치는 부분들이 무수히 많을 것이다. 그럼 어떻게 해야할까?</p>
<p>OpenGL은 이에 대한 해결책으로 EBO 제공, 이는 vertex buffer object와 같은 버퍼이며 OpenGL이 어떤 vertices를 그릴지 결정하기 위해 사용하는 indices를 저장하는 버퍼. 이는 indexed drawing이라 불리며 위에 언급된 문제를 정확하게 해결</p>
<pre><code class="language-cpp">float vertices[] = {
    0.5f, 0.5f, 0.0f, // top right
    0.5f, -0.5f, 0.0f, // bottom right
    -0.5f, -0.5f, 0.0f, // bottom left
    -0.5f, 0.5f, 0.0f // top left
};
unsigned int indices[] = { // note that we start from 0!
    0, 1, 3, // first triangle
    1, 2, 3 // second triangle
};</code></pre>
<p>vertices가 6개에서 4개로 줄었고 indices가 추가. 이 후 EBO 생성</p>
<pre><code class="language-cpp">unsigned int EBO;
glGenBuffers(1, &amp;EBO);</code></pre>
<p>VBO와 비슷하게 EBO도 바인드하고 <code>glBufferData</code>로 버퍼에 indices를 복사. <code>GL_ELEMENT_ARRAY_BUFFER</code>을 버퍼 타입으로 명시</p>
<pre><code class="language-cpp">glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,
GL_STATIC_DRAW);</code></pre>
<p>그리고 <code>glDrawArrays</code> 대신 <code>glDrawElements</code> 을 호출. 이는 현재 바인딩된 EBO에서 제공된 indices를 사용하여 그린다.</p>
<pre><code class="language-cpp">glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);</code></pre>
<p><code>glDrawArrays</code> 와 같이 첫번째 인자는 그리는 형식, 두번째는 elements의 수, 세번째는 indices의 타입, 마지막은 EBO에서 offset을 명시. 이 함수는 현재 <code>GL_ELEMENT_ARRAY_BUFFER</code> 에 바인딩된 EBO로부터 indices를 가진다. 이는 복잡하고 큰 indices를 가진 오브젝트를 렌더링할때마다 상응하는 EBO를 바인딩해야하는 것. VAO가 EBO 바인딩들을 계속 찾아가게 만든다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/c990f6fb-02a5-4feb-b639-696d0e845c9b/image.png" alt="">
VAO는 타겟이 <code>GL_ELEMENT_ARRAY_BUFFER</code> 이라면 <code>glBindBuffer</code> 호출을 저장. 이는 또한 unbind 함수도 저장하기에 VAO를 unbind하기전에 element array buffer를 unbind해서는 안되는 것. 그렇지 않으면 설정된 EBO를 가지지 못한다.</p>
<pre><code class="language-cpp">// ..:: Initialization code :: ..
// 1. bind Vertex Array Object
glBindVertexArray(VAO);
// 2. copy our vertices array in a vertex buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. copy our index array in a element buffer for OpenGL to use
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices,
GL_STATIC_DRAW);
// 4. then set the vertex attributes pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),
(void*)0);
glEnableVertexAttribArray(0);
[...]
// ..:: Drawing code (in render loop) :: ..
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);</code></pre>
<p>아래와 같은 결과물들을 얻을 수 있다.
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/61d0c57e-2fda-4d0a-8529-48f44c5d4962/image.png" alt="">
<img src="https://velog.velcdn.com/images/kkj-100-010-110/post/44b33fdc-67ca-42ca-86f6-ca1cbe9272a4/image.png" alt="">
두번째 결과물은 wireframe mode에서 나온 것. 아래의 코드를 render loop안에 추가하면 확인 가능.</p>
<pre><code class="language-cpp">// render loop
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);</code></pre>
<p>함수 첫번째 인자는 삼각형의 전면과 후면에 적용하는 플래그를 입력, 두번째는 선으로 나타내는 플래그 입력</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[C++ STL 관련 문법들 - 1(List, Stack, Queue)]]></title>
            <link>https://velog.io/@kkj-100-010-110/C-STL-%EA%B4%80%EB%A0%A8-%EB%AC%B8%EB%B2%95%EB%93%A4-1List-Stack-Queue</link>
            <guid>https://velog.io/@kkj-100-010-110/C-STL-%EA%B4%80%EB%A0%A8-%EB%AC%B8%EB%B2%95%EB%93%A4-1List-Stack-Queue</guid>
            <pubDate>Wed, 21 Feb 2024 23:31:42 GMT</pubDate>
            <description><![CDATA[<p>출처 - 코딩 테스트를 위한 자료 구조와 알고리즘 with C++</p>
<p><strong><em>std::array</em></strong></p>
<pre><code>// 전달 받는 배열의 크기 고정
void print(std::array&lt;int, 5&gt; arr)
{
    for (auto&amp; e : arr)
        std::cout &lt;&lt; e &lt;&lt; &quot; &quot;;
    std::cout &lt;&lt; std::endl;
}

// 깊은 복사 X, 범용적인 배열의 출력 가능
template&lt;size_t N&gt;
void print(const std::array&lt;int, N&gt;&amp; arr)
{
    for (auto&amp; e : arr)
        std::cout &lt;&lt; e &lt;&lt; &quot; &quot;;
    std::cout &lt;&lt; std::endl;
}</code></pre><p>참고 사이트 : </p>
<ol>
<li><a href="https://en.cppreference.com/w/cpp/language/template_parameters">https://en.cppreference.com/w/cpp/language/template_parameters</a></li>
<li><a href="https://en.cppreference.com/w/cpp/language/parameter_pack">https://en.cppreference.com/w/cpp/language/parameter_pack</a></li>
</ol>
<p>빠르고 범용적인 데이터 저장 컨테이너 만드는 방식</p>
<pre><code>// template&lt;typename ... Args&gt;
// std::array&lt;?, ?&gt; build_array(Args&amp;&amp; ... args)

// template&lt;typename ... Args&gt;
// auto build_array(Args&amp;&amp; ... args) -&gt; std::array&lt;typename std::common_type&lt;Args...&gt;::type, ?&gt;
// {
//     using commonType = typename std::common_type&lt;Args...&gt;::type;
//     // 배열 생성
// }

template&lt;typename ... Args&gt;
auto build_array(Args&amp;&amp;... args) -&gt; std::array&lt;typename std::common_type&lt;Args...&gt;::type, sizeof...(args)&gt;
{
    using commonType = typename std::common_type&lt;Args...&gt;::type;
    return {std::forward&lt;commonType&gt;((Args&amp;&amp;)args)...};
}

int main()
{
    auto data = build_array(1, 0u, &#39;a&#39;, 3.2f, false);
    // auto data2 = build_array(1, &quot;Packt&quot;, 2.0); error, there&#39;s no common type.

    for (auto&amp; e : data)
        std::cout &lt;&lt; e &lt;&lt; &quot; &quot;;
    std::cout &lt;&lt; std::endl;
}</code></pre><p><code>&lt;type_traits&gt;</code>
<code>std::common_type</code>
<code>std::forward</code></p>
<p><code>std::array</code>는 C 스타일 배열의 발전된 형태. 다만, 실제 개발에서 불편한 점이 있다.</p>
<ul>
<li><code>std::array</code>의 크기는 컴파일 시간에 결정되는 상수이어야한다. 실행 중 변경 x</li>
<li>크기가 고정되어 있어서 원소를 추가하거나 삭제 x</li>
<li><code>std::array</code>의 메모리 할당 방법을 변경할 수 없다. 항상 스택 메모리 사용</li>
</ul>
<p>대부분 실제 응용 프로그램에서 데이터는 동적. 데이터의 크기를 미리 알고 있으면 <code>std::array</code>가 좋다.
<br>
<strong><em>std::vector</em></strong></p>
<p><code>std::vector</code>는 C 스타일 배열과 <code>std::array</code>의 고정 크기 문제 보완.</p>
<pre><code>std::vector&lt;int&gt; vec;               // 크기가 0인 벡터 선언
std::vector&lt;int&gt; vec = {1,2,3,4,5}; // 지정한 초깃값으로 이루어진 크기가 5인 벡터 선언
std::vector&lt;int&gt; vec(10);           // 크기가 10인 벡터 선언
std::vector&lt;int&gt; vec(10, 5);        // 크기가 10이고, 모든 원소의 값이 5로 초기화하는 벡터 선언</code></pre><p><code>push_back()</code>함수</p>
<pre><code>push_back(val)
    if size &lt; capacity
        마지막 원소 뒤에 val 저장
        size + 1
    else (size == capacity)
        메모리 두 배로 새로 할당
        기존 원소들 복사
        데이터 포인터에 새로 할당한 메모리 주소로 지정
        마지막 원소 다음 val 저장
        size + 1</code></pre><p>공간이 남아 있을 때 삽입 시 시간 복잡도는 O(1)이며, 공간이 없을 때 O(n)의 시간이 걸리지만 두 배를 늘리는 경우가 많지 않다. 따라서 연산의 평균 속도는 O(1)에 가깝다. 참고로 +2씩 늘리는 경우 O(n^2)의 속도를 갖는다.</p>
<table>
<thead>
<tr>
<th>Add two capacity</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td>Resize</td>
<td>Array size</td>
<td>Copy n</td>
</tr>
<tr>
<td>0</td>
<td>2</td>
<td></td>
</tr>
<tr>
<td>1</td>
<td>4</td>
<td>2</td>
</tr>
<tr>
<td>2</td>
<td>6</td>
<td>4</td>
</tr>
<tr>
<td>3</td>
<td>8</td>
<td>6</td>
</tr>
<tr>
<td>4</td>
<td>10</td>
<td>8</td>
</tr>
<tr>
<td>5</td>
<td>12</td>
<td>10</td>
</tr>
<tr>
<td>$r = n / 2$</td>
<td></td>
<td>$2r$</td>
</tr>
</tbody></table>
<p>$\sum_{k=1}^{r}2\cdot k = 2\cdot ({r(r+1)}/2) = r^2 + r = (\frac{n}{2})^2 + \frac{n}{2} = O(n^2)$</p>
<table>
<thead>
<tr>
<th>double capacity</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td>Resize</td>
<td>Array size</td>
<td>Copy n</td>
</tr>
<tr>
<td>0</td>
<td>2</td>
<td></td>
</tr>
<tr>
<td>1</td>
<td>4</td>
<td>2</td>
</tr>
<tr>
<td>2</td>
<td>8</td>
<td>4</td>
</tr>
<tr>
<td>3</td>
<td>16</td>
<td>8</td>
</tr>
<tr>
<td>4</td>
<td>32</td>
<td>16</td>
</tr>
<tr>
<td>5</td>
<td>64</td>
<td>32</td>
</tr>
<tr>
<td>$r = \log(n)$</td>
<td></td>
<td>$2^r$</td>
</tr>
</tbody></table>
<p>$\sum_{k=1}^{r}2^k = 2\cdot (2^r-1) = 2\cdot (2^{\log(n)}-1) = 2(n-1) = O(n),;Avg;O(n) / n==O(1)$</p>
<p>위 두 테이블과 수식은 +2씩 증가하는 경우와 두 배로 증가하는 경우의 시간을 나타낸다.</p>
<p><code>insert()</code>의 경우 위치 다음의 모든 원소를 이동시켜야하는 연산이 필요. 따라서 $O(n)$의 시간이 걸린다.
<code>push_front()</code>는 없다. <code>vec.insert(vec.begin(), 0);</code>로 대체 가능.</p>
<p><code>push_back()</code> 과 <code>insert()</code>의 단점은 이 두 함수가 추가할 원소를 먼저 임시로 생성한 후, 벡터 버퍼 내부 위치로 복사 또는 이동을 수행한다는 점. 이 단점을 새로운 원소가 추가될 위치에서 해당 원소를 생성하는 방식으로 최적화하는 기능은 <code>emplace_back()</code> 또는 <code>emplace()</code> 함수에 구현되어있다.
몇가지 vector 멤버 함수들</p>
<pre><code>vec.pop_back() // O(1)
// erase() 함수는 O(n)
vec.erase(vec.begin()); // 맨 처음 원소 제거
vec.erase(vec.begin() + 1, vec.begin() + 4); // 1번째 원소부터 4번째 원소 &#39;앞&#39;까지 제거
vec.clear(); // 모든 원소 제거
vec.reserve(capacity); // 벡터에서 사용할 용량을 지정. 지정한 값이 현재 용량보다 크면 크기만큼 재할당, 같거나 작으면 작동 x
vec.shrink_to_fit(); // size == capacity, 벡터의 용량과 크기를 같게 설정
</code></pre><p><code>std::vector</code>할당자
<code>std::vector</code>는 template parameter에서 데이터 타입 다음에 할당자(allocator)를 전달할 수 있다. <code>std::array</code>의 단점 해결.
사용자 정의 할당자를 사용하려면 정해진 인터페이스를 따라야한다. 벡터는 메모리 접근과 관련된 대부분의 동작에서 할당자 함수를 사용하므로 할당자는 <code>allocator()</code>, <code>deallocate()</code>, <code>construct()</code>, <code>destroy()</code>등의 함수를 제공해야한다. 할당자는 메모리 할당과 해제, 그리고 여타 동작에서 데이터를 손상시키지 않도록 주의해야 한다. 일반적인 heap 메모리 대신 자체적인 메모리 풀(memory pool) 또는 이와 유사한 자원을 사용하거나 자동 메모리 관리가 필요한 응용 프로그램을 만들어야하는 경우에 사용자 정의 할당자를 사용하면 유용.
참고 사이트 : <a href="https://en.cppreference.com/w/cpp/named_req/Allocator">https://en.cppreference.com/w/cpp/named_req/Allocator</a>
<br>
<strong><em>std::forward_list</em></strong>
연결 리스트와같은 형태의 자료 구조. 연결 리스트 구현이 어렵지 않지만 자칫 잘못하면 찾기 어려운 버그를 양산. 따라서 C++는 기본적인 연결 리스트에 대한 래퍼 클래스인 <code>std::forward_list</code> 클래스 제공.
<code>std::forward_list</code>는 기본적인 연결 리스트의 성능을 유지하면서 추가적인 기능을 제공. 성능 유지를 위해 <code>std::forward_list</code>는 전체 리스트의 크기를 반환하거나 또는 첫 번째 원소를 제외한 나머지 원소에 직접 접근하는 기능을 제공하지 않는다.
원소 삽입과 삭제는 배열, vector와는 조금 다르게 동작. <code>push_front()</code> 함수로 연결 리스트 맨 앞에 새로운 원소 삽입하거나 <code>insert_after()</code> 함수를 사용. 배열같이 중간에 삽입하고 그 뒤 나머지 원소를 복사하는 과정이라면 리스트는 연결만 제대로 해주면 끝이다.</p>
<pre><code>std::forward_list&lt;int&gt; fwd_list = {1,2,3};
fwd_list.push_front(0);       // {0,1,2,3};
auto it = fwd_list.begin();
fwd_list.insert_after(it, 5); // {0,5,1,2,3}
fwd_list.insert_after(it, 6); // {0,6,5,1,2,3}</code></pre><p><code>std::vector</code>와 유사하게 emplace_front()와 emplace_after() 함수 제공. 삽입 함수와 같은 기능을 수행하지만 추가적인 복사나 이동을 하지 않아 더 효율적.
삭제같은 경우도 <code>pop_front()</code>, <code>erase_after()</code>와 같인 <code>std::vector</code>와 비슷.</p>
<pre><code>std::forward_list&lt;int&gt; fwd_list = {1,2,3,4,5};
fwd_list.pop_front();                     // {2,3,4,5}
auto it = fwd_list.begin();
fwd_list.erase_after(it);                 // {2,4,5}
fwd_list.erase_after(it, fwd_list.end()); // {2}</code></pre><p>기타 멤버 함수로 <code>remove()</code>와 <code>remove_if()</code> 함수도 제공. <code>remove()</code>함수는 삭제할 원소 값 하나를 인자로 받아서 이 매개변수와 같은 값과 일치하는 모든 원소를 찾아 삭제. 오직 등호 연산만으로 찾아내고 등호 연산이 지원되지 않는 타입이면 에러. 좀 더 유연한 <code>remove_if()</code>를 통해서 조건자 하나를 받아 조건자가 true를 반환하는 모든 원소들을 삭제. 조건자 자리에 람다 표현식(lambda expression)을 사용할 수 있다.</p>
<pre><code>struct citizen
{
    std::string name;
    int age;
};


int main()
{
    std::forward_list&lt;citizen&gt; citizens = {
        {&quot;Kim&quot;, 22}, {&quot;Lee&quot;, 25}, {&quot;Park&quot;, 18}, {&quot;Jin&quot;, 16}
    };

    auto citizens_copy = citizens;

    citizens.remove_if([](const citizen &amp;c){
        return (c.age &lt; 19);
    });
    // {&quot;Park&quot;, 18}, {&quot;Jin&quot;, 16} 삭제

    citizens_copy.remove_if([](const citizen &amp;c){
        return (c.age != 18);
    });
    // {&quot;Park&quot;, 18} 제외한 나머지 다 삭제

    return 0;
}</code></pre><p>참고 사이트 : <a href="https://en.cppreference.com/w/cpp/language/lambda">https://en.cppreference.com/w/cpp/language/lambda</a>
remove()와 remove_if()는 리스트 전체 순회하면서 조건에 맞는 원소 삭제하므로 $$O(n)$$ 시간을 갖는다.
<code>std::forward_list</code>는 <code>sort()</code> 멤버 함수를 갖는다. 그리고 <code>std::sort(first_iterator, last_iterator)</code> 함수를 사용 할 수 없다.
<code>sort()</code>멤버 함수는 두 가지 형태를 지원, 하나는 &lt; 연산자 기반으로 정렬, 다른 하나는 매개변수로 전달된 비교자(comparator)를 사용. 기본 <code>sort()</code> 함수는 <code>std::less&lt;value_type&gt;</code>을 비교자로 사용.</p>
<pre><code>std::forward_list fl{23, 0, 1, -3, 34, 32};
fl.sort();
// -3 0 1 23 32 34
fl.sort(std::greater&lt;int&gt;());
// 34 32 23 1 0 -3</code></pre><p>참고 사이트 - <a href="https://en.cppreference.com/w/cpp/container/forward_list/sort">https://en.cppreference.com/w/cpp/container/forward_list/sort</a>
기타 멤버 함수들</p>
<pre><code>    std::forward_list&lt;int&gt; list1 = {2, 53, 1, 0, 4, 10};
    // 2 53 1 0 4 10 
    // 10 4 0 1 53 2
    list1.reverse(); // 원소 거꾸로 나열

    list1 = {0, 1, 0, 1, -1, 10, 5, 5, 10, 0};
    list1.unique(); // 인접한 원소 간에 값이 동일하면 하나만 남기고 제거
    // 0 1 0 1 -1 10 5 10 0

    list1 = {0, 1, 0, 1, -1, 10, 5, 5, 10, 0};
    list1.sort();
    list1.unique();
    // -1 0 1 5 10

    // 리스트에서 특정 원소가 바로 앞 원소보다 2 이상 크지 않으면 삭제 
    list1.unique([](int a, int b) { return (b - a) &lt; 2; });
    // -1, 1, 5, 10
</code></pre><br>

<p><strong><em>반복자</em></strong>
참고 사이트 - <a href="https://en.cppreference.com/w/cpp/iterator">https://en.cppreference.com/w/cpp/iterator</a>
반복자는 포인터와 비슷하지만 STL 컨테이너에 대해 공통의 인터페이스 제공.
벡터와 배열 같은 경우 random access iterator, <code>std::forward_list</code>는 forward iterator.
반복자 타입에 따라 사용할 수 있는 함수들이 다르다. 순반향 반복자에 대해 <code>prev()</code> 함수 를 사용하면 에러 발생</p>
<p>아래는 단일 연결 리스트 구현 코드</p>
<pre><code>#include &lt;iostream&gt;
#include &lt;algorithm&gt;

struct singly_ll_node
{
    int data;
    singly_ll_node* next;
};

class singly_ll
{
public:
    using node = singly_ll_node;
    using node_ptr = node *;

private:
    node_ptr head;

public:
    void push_front(int val)
    {
        auto new_node = new node { val, NULL };
        if (head != NULL)
            new_node-&gt;next = head;
        head = new_node;
    }

    void pop_front()
    {
        auto first = head;
        if (head)
        {
            head = head-&gt;next;
            delete first;
        }
    }

    struct singly_ll_iterator
    {
    private:
        node_ptr ptr;

    public:
        singly_ll_iterator(node_ptr p) : ptr(p) {}
        int &amp;operator*() { return ptr-&gt;data; }
        node_ptr get() { return ptr; }
        singly_ll_iterator&amp; operator++()
        {
            ptr = ptr-&gt;next;
            return *this;
        }
        singly_ll_iterator operator++(int)
        {
            singly_ll_iterator result = *this;
            ++(*this);
            return result;
        }

        friend bool operator==(const singly_ll_iterator&amp; left, const singly_ll_iterator&amp; right)
        {
            return left.ptr == right.ptr;
        }

        friend bool operator!=(const singly_ll_iterator&amp; left, const singly_ll_iterator&amp; right)
        {
            return left.ptr != right.ptr;
        }
    };

    singly_ll_iterator begin() { return singly_ll_iterator(head); }
    singly_ll_iterator end() { return singly_ll_iterator(NULL); }
    singly_ll_iterator begin() const { return singly_ll_iterator(head); }
    singly_ll_iterator end() const { return singly_ll_iterator(NULL); }

    singly_ll() = default;

    singly_ll(const singly_ll&amp; other) : head(NULL)
    {
        if (other.head)
        {
            head = new node{0, NULL};
            auto cur = head;
            auto it = other.begin();
            while (true)
            {
                cur-&gt;data = *it;
                auto tmp = it;
                ++tmp;
                if (tmp == other.end())
                    break;
                cur-&gt;next = new node{0, NULL};
                cur = cur-&gt;next;
                it = tmp;
            }
        }
    }

    singly_ll(const std::initializer_list&lt;int&gt;&amp; ilist) : head(NULL)
    {
        for (auto it = std::rbegin(ilist); it != std::rend(ilist); it++)
            push_front(*it);
    }
};</code></pre><br>

<p><strong><em>std::list</em></strong>
<code>std::forward_list</code>는 아주 기본적인 형태, 메모리를 적게 쓰고 빠른 성능 유지한다. 다만, 기능이 매우 적다. 그래서 다양한 기능이 있는 <code>std::list</code>가 있다. 이는 양방향 연결 리스트이다.</p>
<pre><code>struct singly_ll_node
{
    int data;
    singly_ll_node* next;
};

struct doubly_ll_node
{
    int data;
    doubly_ll_node* next;
    doubly_ll_node* prev;
};</code></pre><p>따라서 <code>push_back()</code> 함수와 <code>size()</code> 함수 지원.
<code>std::list</code> 멤버 함수들은 <code>std::forward_list</code>의 멤버 함수들 중 <code>_after</code>로 끝나는 함수들이 <code>_after</code>없는 형태. 한 예로 <code>insert_after()</code>가 <code>insert()</code>로 된다.</p>
<pre><code>#include &lt;iostream&gt;
#include &lt;list&gt;

int main()
{
    std::list&lt;int&gt; list1 = {1,2,3,4,5};
    list1.push_back(6); // 1,2,3,4,5,6
    list1.insert(next(list1.begin()), 0); // 1,0,2,3,4,5,6
    list1.insert(list1.end(), 7); // 1,0,2,3,4,5,6,7
    list1.pop_back(); // 1,0,2,3,4,5,6
}</code></pre><p><code>std::forward_list</code>와 <code>std::list</code>의 공통 함수들의 시간복잡도는 같지만 <code>std::list</code>에서는 연산이 조금 더 필요로 한다. -&gt; 두 개의 포인터를 관리해야하기 때문
반복자 같은 경우 <code>std::forward_list</code>와는 달리 역방향(reverse iterator)으로 이동 가능하기에 더 유연하지만 ramdom-access iterator 만큼은 아니다. <code>std::list</code>의 반복자는 bidirectional iterators라 한다.
<br>
<strong><em>반복자 무효화</em></strong>
컨테이너가 변경되어 특정 노드 또는 원소의 메모리 주소가 바뀌면 사용하던 반복자가 무효화될 수 있다. undefined behaviour 발생
<code>vector::push_back()</code>이나 <code>vector::insert()</code>함수에서 메모리 공간을 재할당을 받을 경우 기존의 반복자와 포인터, 참조는 무효화된다. 벡터와 달리 리스트는 무효화가 되지는 않는다.</p>
<pre><code>std::vector&lt;int&gt; vec = {1,2,3,4,5};
auto v_it4 = vec.begin() + 4;
vec.insert(vec.begin() + 2, 0); // v_it4 반복자 무효

std::list&lt;int&gt; lst = {1,2,3,4,5};
auto l_it4 = next(lst.begin(), 4);
lst.insert(next(lst.begin(), 2), 0); // l_it4 반복자는 여전히 유효</code></pre><br>

<p><strong><em>std::deque</em></strong>
덱은 양방향 큐의 약자(double-ended queue).
덱은 단일 메모리 청크를 사용하지 않는다. 대신 크기가 같은 여러 개의 메모리 청크를 사용하여 데이터를 저장. 이때 청크의 인덱스 및 크기를 이용하여 특정 위치의 원소가 어느 청크에 저장되어 있는지를 알 수 있다. 덱이 다른 컨테이너들과 다른 점은 성능과 메모리 요구 사항이다. 덱은 다소 복잡한 구조를 가지고 있다.</p>
<br>

<p><strong><em>컨테이너 어댑터</em></strong>
앞의 컨테이너들은 바닥부터 만들어졌고 <code>std::stack</code>, <code>std::queue</code>, <code>std::priority_queue</code>는 이미 존재하는 컨테이너를 기반으로 만들어진 컨테이너들이다.
<code>std::stack</code>을 먼저 보면 LIFO(Last In First Out)구조. <code>std::deque</code>으로 만든 간단한 래퍼로서 스택 자료 구조에서 꼭 필요한 인터페이스만을 제공. 이러한 방식으로 만들어진 것을 container adaptor라 한다. <code>std::deque</code>이 기본 컨테이너로 사용하는데 이는 재할당 시 벡터처럼 원소 전체를 이동할 필요가 없기 때문이다. 그럼에도 몇몇 컨테이너가 더 효율적일 수도 있다. 아래와 같이 기본 컨테이너를 명시하여 사용 가능하다.</p>
<pre><code>std::stack&lt;int, std::vector&lt;int&gt;&gt; stk;
std::stack&lt;int, std::list&lt;int&gt;&gt; stk;</code></pre><p><code>std::queue</code>는 FIFO(First In First Out)구조이다. <code>std::queue</code>도 스택과 마찬가지로 덱을 기본 컨테이너로 사용.
<code>std::priority_queue</code> 우선순위 큐는 힙(heap)이라고 부르는 매우 유용한 구조를 제공. 힙은 컨테이너에서 가장 작거나 큰 원소에 빠르게 접근할 수 있는 자료 구조. 최소/최대 원소에 접근하는 동작은 $$O(n)$$이 걸리며 원소 삽입은 $$O(\log n)$$이 걸린다. 기본 컨테이너는 벡터를 사용한다.
삽입과 삭제 시 비교자(기본으로 <code>std::less</code>)를 사용하여 재정렬하는 heapify 알고리즘을 구현해야한다.
스택, 큐, 우선순위 큐에서 모든 원소를 순회하는 작업을 할 필요는 없다. 언제든 특정 위치에 있는 원소 하나만을 참조할 수 있으면 된다. 따라서 STL은 이 어댑터에 대해서는 반복자를 지원하지 않는다.
<br></p>
<p><strong><em>벤치마킹</em></strong>
각각의 컨테이너는 다양한 장단점을 지니고 있다. 모든 상황에 알맞는 컨테이너는 존재하지 않는다. 따라서 benchmarking-통계 데이터를 기반으로 더 나은 접근 방식을 결정하는 방법이 좋은 해결책.
<code>std::vector</code>와 <code>std::deque</code>에 맞는 문제가 있을 시 어느 것을 사용할 지 선택해야한다. 이러한 경우 실제 모델과 비슷한 작은 프로토타입을 만들고 두 컨테이너를 사용하여 구현하고 성능 측정하여 선택한다. 이런 경우 많은 오차와 다른 외부 요인들로 인해 정확한 결과를 갖기가 쉽지 않아서 많은 반복을 통해서 수행 시간을 측정하는 것이 좋다.
<a href="https://quick-bench.com/">https://quick-bench.com/</a> 이 사이트를 통해서 도움 받을 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘 문제 - 파일명 정렬]]></title>
            <link>https://velog.io/@kkj-100-010-110/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%AC%B8%EC%A0%9C-%ED%8C%8C%EC%9D%BC%EB%AA%85-%EC%A0%95%EB%A0%AC</link>
            <guid>https://velog.io/@kkj-100-010-110/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%AC%B8%EC%A0%9C-%ED%8C%8C%EC%9D%BC%EB%AA%85-%EC%A0%95%EB%A0%AC</guid>
            <pubDate>Tue, 20 Feb 2024 09:16:51 GMT</pubDate>
            <description><![CDATA[<p>프로그래머스 문제</p>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/17686?language=cpp">https://school.programmers.co.kr/learn/courses/30/lessons/17686?language=cpp</a></p>
<pre><code class="language-cpp">#include &lt;string&gt;
#include &lt;vector&gt;
// add
#include &lt;algorithm&gt;

using namespace std;

struct filename
{
    std::string head;
    int num;
    int idx;
};

bool compare(filename&amp; f1, filename&amp; f2)
{
    if (f1.head == f2.head) {
        if (f1.num == f2.num)
            return f1.idx &lt; f2.idx;
        return f1.num &lt; f2.num;
    }
    return f1.head &lt; f2.head;
}

vector&lt;string&gt; solution(vector&lt;string&gt; files) {
    vector&lt;string&gt; answer;
    vector&lt;filename&gt; list;
    for (int i = 0; i &lt; files.size(); ++i)
    {
        size_t pos1 = files[i].find_first_of(&quot;0123456789&quot;, 0);
        size_t pos2 = files[i].find_first_not_of(&quot;0123456789&quot;, pos1);
        if (pos2 == files[i].npos)
            pos2 = files[i].size() - 1;
        std::string head = files[i].substr(0, pos1);
        std::transform(head.begin()
                      , head.end()
                      , head.begin()
                      , [](unsigned char c) { return std::tolower(c); });

        list.push_back({head, stoi(files[i].substr(pos1, pos2)), i});
    }
    std::sort(list.begin(), list.end(), compare);
    for (auto&amp; e : list)
        answer.push_back(files[e.idx]);

    return answer;
}

/* lambda function instead */
#include &lt;string&gt;
#include &lt;vector&gt;
// add
#include &lt;algorithm&gt;

using namespace std;

struct filename
{
    std::string head;
    int num;
    int idx;
};

vector&lt;string&gt; solution(vector&lt;string&gt; files) {
    vector&lt;string&gt; answer;
    vector&lt;filename&gt; list;
    for (int i = 0; i &lt; files.size(); ++i)
    {
        size_t pos1 = files[i].find_first_of(&quot;0123456789&quot;, 0);
        size_t pos2 = files[i].find_first_not_of(&quot;0123456789&quot;, pos1);
        if (pos2 == files[i].npos)
            pos2 = files[i].size() - 1;
        std::string head = files[i].substr(0, pos1);
        std::transform(head.begin()
                      , head.end()
                      , head.begin()
                      , [](unsigned char c) { return std::tolower(c); });

        list.push_back({head, stoi(files[i].substr(pos1, pos2)), i});
    }
    std::sort(list.begin(), list.end(), [](filename &amp;f1, filename &amp;f2) {
        if (f1.head == f2.head) {
            if (f1.num == f2.num)
                return f1.idx &lt; f2.idx;
            return f1.num &lt; f2.num;
        }
        return f1.head &lt; f2.head;
    });
    for (auto&amp; e : list)
        answer.push_back(files[e.idx]);

    return answer;
}</code></pre>
<p>Memo</p>
<ul>
<li><code>std::string</code> 멤버 함수 <code>find_first_of()</code>, <code>find_first_not_of()</code></li>
<li><code>std::transform()</code></li>
<li><code>std::sort()</code></li>
</ul>
<p><a href="https://en.cppreference.com/w/cpp/algorithm/sort">std::sort - cppreference.com</a></p>
<p><a href="https://en.cppreference.com/w/cpp/algorithm/transform">std::transform - cppreference.com</a></p>
<p><a href="https://en.cppreference.com/w/cpp/string/basic_string/find_first_of">std::basic_string&lt;CharT,Traits,Allocator&gt;::find_first_of - cppreference.com</a></p>
<p><a href="https://en.cppreference.com/w/cpp/algorithm/find">std::find, std::find_if, std::find_if_not - cppreference.com</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[하노이의 탑(Tower of Hanoi)]]></title>
            <link>https://velog.io/@kkj-100-010-110/%ED%95%98%EB%85%B8%EC%9D%B4%EC%9D%98-%ED%83%91Tower-of-Hanoi</link>
            <guid>https://velog.io/@kkj-100-010-110/%ED%95%98%EB%85%B8%EC%9D%B4%EC%9D%98-%ED%83%91Tower-of-Hanoi</guid>
            <pubDate>Wed, 14 Feb 2024 03:31:38 GMT</pubDate>
            <description><![CDATA[<p>하노이의 탑(Tower of Hanoi)</p>
<p><strong>출처 : 구체수학, 수학독본</strong></p>
<ul>
<li>프랑스 수학자 에두아르 뤼카가 1883년에 고안한 퍼즐<ul>
<li>원반 여덟 개로 된 탑으로 시작</li>
<li>원반들은 세 개의 기둥 중 하나에 큰 것부터 크기순으로 쌓여 있다(꼭대기에 제일 작은 원반)</li>
<li>Goal: 원반을 하나씩 이동해서 탑 전체를 다른 기둥으로 옮기는 것<ul>
<li>단, 작은 원반 위에 큰 원반을 놓아서는 안된다는 규칙이 있다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<ol>
<li><p>일반화</p>
<ul>
<li>원반이 n개의 경우<ul>
<li>small case들을 살펴보는 것부터 시작</li>
</ul>
</li>
</ul>
</li>
<li><p>적절한 표기법</p>
<ul>
<li>규칙에 따라 다른 한 기둥으로 옮기는데 필요한 최소한의 이동 횟수를 $T_n$으로 표기<ul>
<li>$T_1$이 1이고 $T_2$는 3이다.</li>
</ul>
</li>
</ul>
</li>
<li><p>Think small</p>
<ul>
<li>$T_0$은 0</li>
</ul>
</li>
<li><p>Think big</p>
<ul>
<li><p>원반 세 개의 경우</p>
<ul>
<li><p>위에서 2번째 원반까지 가운데 기둥으로 옮기고, 마지막 제일 큰 원반을 목표 기둥으로 옮기고, 가운데 있는 두 원반을 세번째 기둥으로 옮기는 것이다.</p>
<ul>
<li><p>여기서 n개의 원반을 옮기는 문제의 힌트가 있다.</p>
<ol>
<li><p>가장 작은 원반부터 n-1개를 다른 기둥으로 옮기고(필요한 이동 횟수 $T_{n-1}$)</p>
</li>
<li><p>가장 큰 원반을 또 다른 기둥으로 옮기고(필요 이동 횟수 1)</p>
</li>
<li><p>남은 원반 n-1개를 옮기는 것(필요한 이동 횟수 $T_{n-1}$)</p>
<p>$\therefore;T_0=0,;T_n=2T_{n-1}+1,;n\gt0$</p>
<p>위 같은 등식을 점화식(recurrence formula)또는 점화관계식(recurrence relation)이라 부른다.</p>
</li>
</ol>
</li>
</ul>
</li>
</ul>
</li>
<li><p>수학적 귀납법</p>
<ul>
<li>원반의 개수에 따른 이동 횟수를 세어보면<ul>
<li>$T_3=2\cdot3+1=7$</li>
<li>$T_4=2\cdot7+1=15$</li>
<li>$T_5=2\cdot15+1=31$</li>
<li>$T_6=2\cdot31+1=63$</li>
<li>$T_n=2\cdot T_{n-1}+1=2^n-1, n\ge0$</li>
</ul>
</li>
<li>수학적 귀납법(mathematical induction)은 정수 n에 관한 어떤 명제가 모든 $n \ge n_0$에 대해 참임을 증명하는 방법.<ul>
<li>$n=1$일 때 $P(n)$이 성립하고 $n=k$일때 성립한다고 가정 → $n=k+1$일 때에도 $P(n)$성립, 즉 $P(k)→P(k+1)$<ul>
<li>$T_n=2^n - 1$에서 1을 넣으면 성립</li>
<li>$T_{k+1}=2\cdot T_k + 1=2^{k+1} - 1$에서 $T_k$는 $2^n - 1$이며 $2\cdot (2^n-1) + 1=2^{k+1} - 1$ 따라서 $k+1$일 때도 성립</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<p>풀이</p>
<p><strong>출처 : Accelerated Computer Science Fundamentals Specialization(Coursera)</strong></p>
<p>원반 4개인 경우</p>
<p>Solution 1.</p>
<ul>
<li>Check the movement and see the pattern. S is a stack, M is a movement.</li>
</ul>
<pre><code>| S | M | S |
| --- | --- | --- |
| 0 | → | 1 |
| 0 | → | 2 |
| 1  | → | 2 |
| 0 | → | 1 |
| 0 | ← | 2 |
| 1  | ← | 2 |
| 0 | → | 1 |
| 0 | → | 2 |
| 1  | → | 2 |
| 0 | ← | 1 |
| 0 | ← | 2 |
| 1  | → | 2 |
| 0 | → | 1 |
| 0 | → | 2 |
| 1  | → | 2 |
- 0, 0, 1과 1, 2, 2의 반복된 패턴을 볼 수 있다.
- 원반의 이동, 즉 화살표만 다르다.

```cpp
while (target 기둥의 원반 갯수가 4개인가?) // 목표 기둥에 원반 4개가 없으면 1, 아니면 0
{
    pattern(0, 1)
    pattern(0, 2)
    pattern(1, 2)
}

pattern(기둥의 인데스 1, 기둥의 인덱스 2) // 두 인자를 받는다.
{
    if 1의 기둥에 원반이 없으면서 2의 기둥에 원반이 있으면
        2에 있는 원반을 1로 옮긴다
    else if 1의 기둥에 원반이 있으면서 2의 기둥에 원반이 없으면
        1에 있는 원반을 2로 옮긴다.
    else if 1의 기둥에 원반이 있으면서 2의 기둥에도 원반이 있으면
    {
        1의 기둥에서 제일 위에 있는 원반의 크기와 2의 기둥에서 제일 위에 있는 원반의 크기 비교
        1의 기둥의 원반의 크기가 작으면 2의 기둥으로 이동
        아니면 2의 기둥의 원반을 1의 기둥으로 이동
    }
}
```</code></pre><p>Solution 2</p>
<p>원반 4개인 경우</p>
<ol>
<li><p>가장 큰 원반을 제외한 원반들을 하나의 기둥으로 옮긴다</p>
</li>
<li><p>마지막 제일 큰 원반을 목표 지점의 기둥으로 옮긴다.</p>
</li>
<li><p>나머지 원반들을 제일 큰 원반이 있는 기둥으로 옮긴다.</p>
<p> 여기서 마지막 원반이 없다고 가정하며, 목표 기둥은 위에서 제일 큰 원반이 있는 기둥이 아닌 다른 기둥</p>
<ol>
<li><p>가장 큰 원반을 제외한 원반들, 즉 2개의 원반을 하나의 기둥으로 옮긴다.</p>
</li>
<li><p>가장 큰 원반을 목표 기둥으로 옮긴다.</p>
</li>
<li><p>2개의 원반을 가장 큰 원반 위로 옮긴다.</p>
<p> 여기서 또 마지막 원반이 없다고 가정하며, 목표 기둥은 위의 기둥과 다른 기둥</p>
<ol>
<li>둘 중 작은 원반 하나를 다른 기둥으로 옮긴다.</li>
<li>큰 원반을 목표 지점으로 옮긴다.</li>
<li>작은 원반을 큰 원반 위로 올린다.<ul>
<li>원반이 하나 남았을때, 위의 목표 지점과는 다른 기둥으로 옮긴다.</li>
</ul>
</li>
</ol>
</li>
</ol>
</li>
</ol>
<pre><code>위에서 하나의 패턴을 볼 수 있다.

- 원반이 하나씩 없앨때마다 목표 기둥이 바뀐다.</code></pre><p>조금 더 자세히</p>
<p>4개의 원반인 경우</p>
<ul>
<li>높이는 4, 높이의 index 범위(0 ~ 3)</li>
<li>기둥 3개, index 범위(0 ~ 2), 0 - Source, 1 - Spare, 2 - Target.<ul>
<li>여기서 Target과 Spare는 높이에 따라 바뀐다.</li>
</ul>
</li>
</ul>
<p>Goal : 4개의 원반을 목표 기둥으로 이동</p>
<p><em>[num] - 기둥, {num} - 원반, → - movement</em></p>
<p>move(Source[0]{0..3} → Target[2])</p>
<ol>
<li>move(Source[0]{1..3} → Spare[1])<ol>
<li>move(Source[0]{2..3} → Spare[1])<ol>
<li>move(Source[0]{3..3} → Spare[1])<ul>
<li>move(Source[0]{3} → Spare[1])</li>
</ul>
</li>
<li>move(Source[0]{2} → Target[2])<ul>
<li>move(Source[0]{2} → Target[2])</li>
</ul>
</li>
<li>move(Spare[1]{3..3} → Target[2])<ul>
<li>move(Spare[1]{3} → Target[2])</li>
</ul>
</li>
</ol>
</li>
<li>move(Source[0]{1} → Target[1])<ul>
<li>move(Source[0]{1} → Target[1])</li>
</ul>
</li>
<li>move(Spare[2]{2..3} → Target[1])<ol>
<li>move(Source[2]{3..3} → Spare[0])<ul>
<li>move(Source[2]{3} → Spare[0])</li>
</ul>
</li>
<li>move(Source[2]{2} → Target[1])<ul>
<li>move(Source[2]{3} → Target[1])</li>
</ul>
</li>
<li>move(Spare[0]{3..3} → Target[1])<ul>
<li>move(Spare[0]{3} → Target[1])</li>
</ul>
</li>
</ol>
</li>
</ol>
</li>
<li>move(Source[0]{0} → Target[2])<ul>
<li>move(Source[0]{0} → Target[2])</li>
</ul>
</li>
<li>move(Spare[1]{1..3} → Target[2])<ol>
<li>move(Spare[1]{2..3} → Source[0])<ol>
<li>move(Spare[1]{3..3} → Target[2])<ul>
<li>move(Spare[1]{3} → Target[2])</li>
</ul>
</li>
<li>move(Spare[1]{2} → Source[0])<ul>
<li>move(Spare[1]{2} → Source[0])</li>
</ul>
</li>
<li>move(Target[2]{3..3} → Source[0])<ul>
<li>move(Target[2]{3} → Source[0])</li>
</ul>
</li>
</ol>
</li>
<li>move(Spare[1]{1} → Target[2])<ul>
<li>move(Spare[1]{1} → Target[2])</li>
</ul>
</li>
<li>move(Source[0]{2..3} → Target[2])<ol>
<li>move(Source[0]{3..3} → Spare[1])<ul>
<li>move(Source[0]{3} → Spare[1])</li>
</ul>
</li>
<li>move(Source[0]{2} → Target[2])<ul>
<li>move(Source[0]{2} → Target[2])</li>
</ul>
</li>
<li>move(Spare[1]{3..3} → Target[2])<ul>
<li>move(Spare[1]{3} → Target[2])</li>
</ul>
</li>
</ol>
</li>
</ol>
</li>
</ol>
<p>많이 헷갈릴 수 있다.</p>
<pre><code class="language-cpp">void move(uint16_t start, uint16_t end, Stack&amp; source, Stack&amp; spare, Stack&amp; target)
{
    if (start == end) {
        target.push_back(source.removeTop())
    } else {
        move(start+1, end  , source, target, spare);
        move(start  , start, source, spare , target);
        move(start+1, end. , spare , source, target);
    }
}

// 호출시 인자의 값을 확인
move(가장 낮은 높이, 가장 높은 높이, 시작 기둥, 여분 기둥, 목표 기둥);</code></pre>
<p>끝.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenGL_02, Hello Window]]></title>
            <link>https://velog.io/@kkj-100-010-110/OpenGL02-Hello-Window</link>
            <guid>https://velog.io/@kkj-100-010-110/OpenGL02-Hello-Window</guid>
            <pubDate>Thu, 01 Feb 2024 08:44:14 GMT</pubDate>
            <description><![CDATA[<pre><code class="language-cpp">#include &lt;glad/glad.h&gt;
#include &lt;GLFW/glfw3.h&gt;

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    return 0;
}</code></pre>
<p>GLFW를 초기화하고 <code>glfwWindowHint</code> 함수를 가지고 환경 설정한다.</p>
<ul>
<li>이 함수의 첫번째 인자는 어떤 옵션을 환경 설정하고 싶은지<ul>
<li><code>GLFW_</code>로 시작하는 옵션들이 <code>enum</code>로 되어있으며 여기서 선택 가능</li>
</ul>
</li>
<li>두번째 인자는 옵션의 값을 설정</li>
<li>모든 가능한 옵션들은 아래의 사이트에서 찾을 수 있다.</li>
</ul>
<p><a href="https://www.glfw.org/docs/latest/window.html#window_hints">GLFW: Window guide</a></p>
<p>여기서 3.3 version을 사용하기에 GLFW에 사용하는 OpenGL version을 알려주어야 GLFW는 OpenGL context를 생성할 때 제대로된 방식으로 만들 수 있다. 이는 사용자가 올바른 버전을 가지지 않았을 때 GLFW가 실행을 못하게 한다.</p>
<p>여기서 major와 minor를 3으로 설정하고 core profile을 사용한다고 명시한다고 GLFW에 알려준다. core profile을 사용하는 것을 알려주는 것은 더 이상 필요없는 구버전에 호환이 가능한 기능들 없이 OpenGL 기능의 더 작은 부분까지 접근할 수 있다는 것이다.</p>
<p>Mac에서는 마지막 줄을 추가해야한다.</p>
<p><code>glxinfo</code> 함수를 통해서 버전 확인 가능</p>
<p>다음은 윈도우 생성</p>
<pre><code class="language-cpp">GLFWwindow* window = glfwCreateWindow(800, 600, &quot;LearnOpenGL&quot;, NULL, NULL);
if (window == NULL)
{
    std::cout &lt;&lt; &quot;Failed to create GLFW window&quot; &lt;&lt; std::endl;
    glfwTerminate();
    return -1;
}
glfwMakeContextCurrent(window);</code></pre>
<p><code>glfwMakeContextCurrent</code></p>
<p>창 생성 이후 이 창의 context를 현재 스레드에서 메인 context로 지정한다고 GLFW에 알려준다.</p>
<h4 id="glad">GLAD</h4>
<p>GLAD는 OpenGL에 관한 함수 포인터들 관리, 그래서 OpenGL 함수를 사용하기 전에 초기화 함수 실행</p>
<pre><code class="language-cpp">if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
    std::cout &lt;&lt; &quot;Failed to initialize GLAD&quot; &lt;&lt; std::endl;
    return -1;
}</code></pre>
<p>OS마다 다른 OpenGL 함수 포인터들의 주소를 불러오는 GLAD 함수를 통한다. GLFW는 OS에 맞는 정확한 함수 정의하는 <code>glfwGetProcAddress</code> 함수를 준다.</p>
<h4 id="viewport">Viewport</h4>
<p>렌더링하기전 마지막 단계. 렌더링할 윈도우의 사이즈를 OpenGL에 알려준다. OpenGL은 윈도우에 맞게 어떻게 데이터와 좌표계를 디스플레이할 지 안다. </p>
<pre><code class="language-cpp">glViewport(0, 0, 800, 600);</code></pre>
<p><code>glViewport</code>의 첫 두 개의 인자는 윈도우의 좌하단의 위치 세번째 네번째 인자는 width와 height의 렌더링할 픽셀, 이는 윈도우의 크기와 같게 설정함.(물론 다르게 가능)</p>
<p>윈도우 창 크기 조절할 때마다 viewport의 크기도 조절되어야한다. </p>
<pre><code class="language-cpp">void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}</code></pre>
<p>위와 같이 콜백 함수를 만들고 아래의 함수 호출에 사용하면 크기 조절에 맞춰 viewport도 수정</p>
<pre><code class="language-cpp">glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);</code></pre>
<h4 id="ready-your-engines">Ready your Engines</h4>
<p>이미지를 바로 창에 띄우고 바로 종료되는 애플리케이션을 원하지 않고 명시적으로 종료하는 순간까지 이미지를 그리고 사용자입력을 받길 원하는 건 당연. 이러한 이유로 루프문 구현, 이 루프를 렌더 루프라 한다. 이 렌더 루프는 GLFW가 종료될 때까지 실행.</p>
<pre><code class="language-cpp">while(!glfwWindowShouldClose(window))
{
    glfwSwapBuffers(window);
    glfwPollEvents();
}</code></pre>
<p><code>glfwWindowShouldClose</code> 함수는 GLFW가 종료지시를 받았는지 루프 시작점에서 확인.</p>
<p><code>glfwPollEvents</code> 함수는 어떤 이벤트가 작동, 윈도우 창 업데이트, 그 이벤트에 맞는 함수 호출 체크.</p>
<p><code>glfwSwapBuffers</code> 함수는 렌더링 반복 중 렌더하기 위해 사용되는 색상 버퍼를 스왑한다. 그리고 스크린에 출력.</p>
<p>Double Buffer : front buffer는 마지막 출력물을 담고 스크린에 나타난다. 언제까지? back buffer에 다음 출력물이 그려질때까지 그리고 스왑한다. 이러한 식으로 렌더링 한다.</p>
<p>마무리</p>
<pre><code class="language-cpp">glfwTerminate();
return 0;</code></pre>
<p><code>glfwTerminate</code> 함수는 이제 종료되기 전에 GLFW의 자원을 정리하기 위해서 호출</p>
<h4 id="input">Input</h4>
<p>GLFW에서 입력 처리</p>
<pre><code class="language-cpp">void processInput(GLFWwindow *window)
{
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}</code></pre>
<p>GLFW의  <code>glfwGetKey</code> 함수를 사용. 이 함수의 인자는 window와 키값을 받는다.</p>
<p>키값이 입력되지 않으면 함수 리턴값으로 <code>GLFW_RELEASE</code>를 받는다. Escape 키를 누르면 분기문으로 들어가 <code>glfwSetWindowshouldClose</code> 함수 실행한다. </p>
<pre><code class="language-cpp">while (!glfwWindowShouldClose(window))
{
    processInput(window);
    glfwSwapBuffers(window);
    glfwPollEvents();
}</code></pre>
<p>위의 코드처럼 렌더 루프문에 넣어서 처리</p>
<h4 id="렌더링">렌더링</h4>
<pre><code class="language-cpp">// render loop
while(!glfwWindowShouldClose(window))
{
    // input
    processInput(window);
    // rendering commands here
    ...
    // check and call events and swap the buffers
    glfwPollEvents();
    glfwSwapBuffers(window);
}</code></pre>
<p>렌더링 명령어들을 모두 렌더 루프에 넣는다. </p>
<p><code>glClear</code> 스크린 색상 버퍼를 지우는 함수</p>
<pre><code class="language-cpp">glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);</code></pre>
<p><code>glClearColor</code> 함수는 <code>glClear</code> 함수 호출 시 색상 버퍼를 정리할 때 마다 색상 버퍼 전체는 <code>glClearColor</code>로 설정된 색으로 채워진다.</p>
<p><strong>여기서 <code>glClearColor</code>는 state-setting 함수, <code>glClear</code>는 state-using 함수이다.</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenGL_01, To Create a Window]]></title>
            <link>https://velog.io/@kkj-100-010-110/OpenGL01-To-Create-a-Window</link>
            <guid>https://velog.io/@kkj-100-010-110/OpenGL01-To-Create-a-Window</guid>
            <pubDate>Thu, 01 Feb 2024 08:43:12 GMT</pubDate>
            <description><![CDATA[<p>제일 먼저 필요한 것은 OpenGL context 생성과 창 생성</p>
<ul>
<li>위의 작업들은 OS마다 다르며, OpenGL은 의도적으로 위 작업들로부터 그 자체를 추상화하려고 한다.<ul>
<li>이는 창 생성, context 정의, 사용자 입력 처리 전부 개인 스스로 처리해야만 한다.</li>
<li>운좋게 위의 작업들을 위한 기능들을 제공하는 몇몇 라이브러리가 있다. 그 중 몇몇은 OpenGL에 특정되어 있다. 이런 라이브러리들은 모든 OS 특정 작업들을 하지 않도록 해주고 window와 OpenGL context를 제공</li>
<li>대중적인 라이브러리들 - GLUT, SDL, SFML, GLFW.<ul>
<li>여기서 GLFW를 사용, 대부분 다른 라이브러리는 GLFW의 설정과 유사</li>
</ul>
</li>
</ul>
</li>
</ul>
<h4 id="glfw">GLFW</h4>
<ul>
<li>OpenGL을 대상으로 C언어로 쓰여진 라이브러리이다.<ul>
<li>스크린에 멋진 그래픽을 렌더링에 요구되는 가장 기본적인 필수 기능들을 제공<ul>
<li>OpenGL context 생성, window 매개변수 정의와 사용자 입력 처리를 하게 해준다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Building GLFW</p>
<p><a href="https://www.glfw.org/download.html">https://www.glfw.org/download.html</a></p>
<p>CMake를 사용해서 프로젝트 관리</p>
<h4 id="glad">GLAD</h4>
<ul>
<li><p>OpenGL은 단순 설명서라고 했었다. 이는 특정 그래픽 카드가 지원하는 드라이버에 맞는 구현은 드라이버 제조사에 달려있다.</p>
</li>
<li><p>OpenGL 드라이버들의 다른 버전들이 많이 있기 때문에 그에 맞는 함수들 대부분의 위치는 컴파일 타임에 알지 못하며 런타임에 분기문이 필요.</p>
</li>
<li><p>필요한 함수의 위치를 찾거나 그 함수들을 함수 포인터에 저장하는 것은 개발자의 업무.</p>
</li>
<li><p>함수 위치들을 찾는 것은 OS마다 다르다.</p>
<pre><code class="language-cpp">  // Windows

  // define the function’s prototype
  typedef void (*GL_GENBUFFERS) (GLsizei, GLuint*);
  // find the function and assign it to a function pointer
  GL_GENBUFFERS glGenBuffers =
  (GL_GENBUFFERS)wglGetProcAddress(&quot;glGenBuffers&quot;);
  // function can now be called as normal
  unsigned int buffer;
  glGenBuffers(1, &amp;buffer);</code></pre>
<p>  위 코드를 보면 복잡하고 다루기 힘들다. GLAD를 사용하면 편하다.</p>
</li>
</ul>
<p>GLAD는 복잡하고 힘든 일을 다루는 오픈 소스 라이브러리, 다른 일반적인 오픈 소스 라이브러리와 조금은 다른 환경설정을 가진다. GLAD에 OpenGL 버전에 맞는 기능들을 불러오고 정의하고 싶은 것들을 GLAD 웹 서비스에서 확인해서 사용</p>
<p><a href="http://glad.dav1d.de/">http://glad.dav1d.de/</a></p>
<p>위 사이트를 들어가서 체크</p>
<ul>
<li>C++, version 3.3, Core → generate a loader → library files</li>
</ul>
<p><code>#include &lt;glad/glad.h&gt;</code></p>
<p>설정 코드 참고</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[OpenGL_00, What is OpenGL?]]></title>
            <link>https://velog.io/@kkj-100-010-110/OpenGL00-What-is-OpenGL</link>
            <guid>https://velog.io/@kkj-100-010-110/OpenGL00-What-is-OpenGL</guid>
            <pubDate>Thu, 01 Feb 2024 08:41:48 GMT</pubDate>
            <description><![CDATA[<p>출처 - <a href="https://learnopengl.com/">https://learnopengl.com/</a>, <a href="https://registry.khronos.org/OpenGL-Refpages/gl4/html/">https://registry.khronos.org/OpenGL-Refpages/gl4/html/</a></p>
<h2 id="opengl">OpenGL</h2>
<ul>
<li>그래픽과 이미지 조작하기 위해 사용할 수 있는 함수들을 모아둔 하나의 큰 세트를 제공하는 API(Application Programming interface)라고 주로 생각한다.</li>
<li>하지만 OpenGL 그자체는 API가 아니다. 크로노스 그룹이 만들고 유지보수하는 하나의 설명서일 뿐.</li>
<li>OpenGL 설명서는 각각의 함수들이 어떻게 작동하고 그 결과나 산출물이 정확히 무엇인지 명시한다.<ul>
<li>구현에 관한 세부사항들을 알려주지 않는다.</li>
</ul>
</li>
<li>실제 OpenGL 라이브러리를 개발하는 사람들은 그래픽 카드 회사 사람들이고 각각의 그래픽 카드는 그 그래픽 카드에 맞게 개발된 버전을 제공</li>
<li>크로노스 그룹에서 모든 버전에 관한 설명서들을 관리한다.<ul>
<li>크로노스 그룹 홈페이지 : <a href="https://www.khronos.org/">https://www.khronos.org/</a></li>
</ul>
</li>
<li>거의 모든 구현은 그래픽 카드 제조사들에 의해 설계되어있기 때문에 버그가 발생할 때마다 비디오 카드 드라이버를 업데이트 해주는 것으로 일반적으로 해결이 된다.<ul>
<li>그런 드라이버들은 새로운 OpenGL 버전을 포함한다</li>
<li>왜 항상 그래픽 드라이버를 업데이트하라는 이유 중 하나</li>
</ul>
</li>
</ul>
<h4 id="core-profile-vs-immediate-mode">Core-profile vs Immediate mode</h4>
<ul>
<li>예전에는 OpenGL을 사용하는 것은 Immediate mode에서 개발하는 것을 의미<ul>
<li>immediate mode == fixed function pipeline</li>
<li>이는 그래픽을 그리기 위한 쉬운 방식</li>
</ul>
</li>
<li>OpenGL의 거의 모든 기능은 라이브러리에 숨겨져 있고 개발자들은 OpenGL이 어떻게 계산을 하는지에 관한 제어권을 가지지 않았다.<ul>
<li>위 내용에 의해서 개발자들이 좀 더 유연성을 원하게 했고 결국 그래픽에 대한 제어권을 갖게 되었다.</li>
</ul>
</li>
<li>Immediate mode는 사용하고 이해하기 정말 쉬웠지만 비효율적이었다.<ul>
<li>이런 이유로 3.2 버전 이후로부터 immediate mode 기능들 사용을 반대하기 시작했다.<ul>
<li>Core-profile mode에서 개발하기를 권장했다.</li>
<li>중요도가 떨어졌던 예전 기능들을 삭제</li>
</ul>
</li>
</ul>
</li>
<li>OpenGL의 core-profile을 사용할 때 OpenGL은 현대식으로 사용하게 강요<ul>
<li>예전 deprecated 기능들을 사용할때마다 OpenGL은 에러를 띄우거나 그리는 것을 멈춤</li>
</ul>
</li>
<li>현대식으로 접근하는 것을 배우는 장점은 유연하고 효율적이지만 배우기 어려움</li>
<li>Immediate mode는 OpenGL이 수행하는 실제 연산들로부터 상당히 많이 추상화했지만 배우기 쉽다, 실제로 OpenGL이 어떻게 작동하는지 파악하기 힘들다.</li>
<li>현대적 접근방식은 OpenGL과 그래픽을 제대로 이해하는 것을 요구하는 반면, 어렵다. 이는 훨씬 효율적이고 유연하고 중요하다. → 더 나은 그래픽 프로그래밍의 이해</li>
<li>Learn OpenGL 책에서 core-profile OpenGL version 3.3 기준으로 작성.<ul>
<li>3.3 이후로 유용한 기능들이 추가될 뿐 실제 코어 머신은 바뀌지 않았다.</li>
<li>모든 컨셉과 기술들은 똑같다. 3.3 버전으로 완벽히 충분하다.</li>
</ul>
</li>
</ul>
<h4 id="extensions">Extensions</h4>
<ul>
<li><p>OpenGL의 큰 특징은 extensions의 지원이다. 그래픽 회사들이 새로운 기술들이나 렌더링 최적화를 가지고 나올 때마다 드라이버들 안에서 구현된 extension을 찾을 수 있다. 만약 하드웨어가 앞서 말한 extension들을 제공한다면, 개발자들은 그 extension으로부터 제공된 기능들을 사용하여 더 나은 더 효율적인 그래픽스를 사용할 수 있다. 단순하게 그래픽 카드가 지원하는 extension을 확인하는 것으로부터 차후 버전에서의 기능들을 포함하는 OpenGL을 기다려야하는 일 없이 새로운 렌더링 기술을 계속해서 사용할 수 있게 해준다.</p>
</li>
<li><p>개발자들은 이러한 extensions들이 사용되기전에 이용가능한지 물어야한다.</p>
<pre><code class="language-cpp">  if(GL_ARB_extension_name)
  {
  // Do cool new and modern stuff supported by hardware
  }
  else
  {
  // Extension not supported: do it the old way
  }</code></pre>
</li>
</ul>
<h4 id="state-machine">State machine</h4>
<ul>
<li>OpenGL은 큰 상태 머신 그 자체 : OpenGL이 현재 어떻게 연산해야하는지 정의하는 변수들의 집합<ul>
<li>또한 OpenGL의 상태(state)는 context라고 불린다.</li>
</ul>
</li>
<li>OpenGL을 사용할 때, 종종 몇몇 옵션을 세팅하고 버퍼를 조작하고 최신 context를 사용하여 그리는 것으로 그 상태를 변경한다.</li>
<li>예를들어 OpenGL에게 삼각형을 그리는 대신 지금 선분들을 그리길 원한다고 알릴 때마다 OpenGL이 어떻게 그려야하는지 설정하는 context 변수들을 바꿈으로써 OpenGL의 상태를 바꾼다. OpenGL에 선분들을 그려야하는 것을 알리는 것으로 context를 바꾸자마자 다음 그리는 명령어는 삼각형 대신 선분들을 그릴 것</li>
<li>OpenGL에서 작업 중 context를 바꾸는 state-changing functions과 OpenGL의 최신 상태를 근거로 연산을 수행하는 state-using functions를 발견할 것.</li>
<li>OpenGL이 기본적으로 하나의 큰 상태 머신이라는 것을 명심하기만 하면 대부분의 그 기능들의 의미를 더 이해할 것이다.</li>
</ul>
<h4 id="objects">Objects</h4>
<ul>
<li><p>OpenGL 라이브러리는 C로 쓰여졌으며 다른 언어들에서 많은 어원을 참작한다. 그러나 그 핵심은 C-library로 남아있다. C언어의 상당수가 다른 고차원 언어로 옮겨지지 않기 때문에 OpenGL은 몇가지 추상화를 유념하여 개발되었다. 그 추상화들 중 하나는 object이다.</p>
</li>
<li><p>OpenGL에서 object는 OpenGL의 상태의 부분집합을 표현하는 옵션들의 콜렉션이다. 예를들어 창을 그리는 설정들을 표현하는 object를 가질 수 있다. 그 사이즈, 몇가지 색상 등을 설정할 수 있다.</p>
<pre><code class="language-cpp">  struct object_name {
      float option1;
      int option2;
      char[] name;
  };

  // The State of OpenGL
  struct OpenGL_Context {
  ...
  object_name* object_Window_Target;
  ...
  };

  // create object
  unsigned int objectId = 0;
  glGenObject(1, &amp;objectId);
  // bind/assign object to context
  glBindObject(GL_WINDOW_TARGET, objectId);
  // set options of object currently bound to GL_WINDOW_TARGET
  glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
  glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
  // set context target back to default
  glBindObject(GL_WINDOW_TARGET, 0);</code></pre>
<ol>
<li>Create an object and store a reference to it as an id(실제 데이터는  scene 뒤에 저장) : object 색성과 id로서의 참조자를 저장 - <code>glGenObject()</code></li>
<li>Bind the object to the target location of the context(<code>GL_WINDOW_TARGET</code>) : context의 타겟 지점에 object를 바인드 - <code>glBindObject()</code></li>
<li>Set the window options : 창 옵션 세팅</li>
<li>Un-bind the object by setting the current object id of the window target to 0. : window target의 현재 object id 세팅으로 되어있는 object를 언바인드(0으로 바인드)</li>
</ol>
</li>
<li><p>이러한 objects를 사용하는 것에 관한 중요한 것은 애플리케이션에 하나 이상의 object를 정의할 수 있고, 옵션들을 설정할 수 있고, OpenGL의 상태를 사용하는 연산들을 시작할 때마다 선호하는 설정값으로 objects를 바인드할 수 있다.</p>
</li>
<li><p>여러 object들을 3D 모델 데이터로서 컨테이너 objects들로 역할을 할 수 있고 그 중 하나를 그리기 원할 때마다 그 object를 바인드한다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[벡터]]></title>
            <link>https://velog.io/@kkj-100-010-110/%EB%B2%A1%ED%84%B0</link>
            <guid>https://velog.io/@kkj-100-010-110/%EB%B2%A1%ED%84%B0</guid>
            <pubDate>Sun, 19 Nov 2023 23:43:20 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/16757add-870a-47f0-9c04-d18414f54d29/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/0d6f3c8e-15c6-421d-bed7-fdbb79c51932/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/99b8d64d-499a-4a77-b459-7f1b2dcb8fb4/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/3eaa1f5d-edd8-4479-be32-c2d6c1a69066/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/10e84c65-9af8-4443-82a0-e850c9f5b0a0/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/ecc5d07f-b198-46f5-9d55-2818c9bed1ad/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/b2fa8814-4635-422e-b91c-6277bb287776/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/be2c73bd-a951-403e-bc8f-2ec65e3ec1e9/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/176b2b51-6dc1-4f6e-9d92-2c3bd14a6b76/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/c6c945df-a1a7-4eaf-a71c-759ff51716d0/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/7f71e99e-65e0-44ea-bb95-e0b024e1e95a/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/ba0ee9d9-49d0-4c6b-b97a-91aacb22b7ca/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/aba6d64f-de65-4e67-a6cf-1f61df3356ca/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/1eea498d-7e76-4847-988c-8b38b5a12076/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/6b22e050-b3aa-4666-b500-f446888592c5/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/6ff73b36-fffb-4d7f-a509-52bce7e9eeee/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/0bdb5ffe-731d-4687-9b6e-4c1d4ac094a1/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/6025afae-29ca-437f-9ccc-90bb409759db/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/7b0d1d95-1193-496c-bb17-28a98fe648b8/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/9fc44174-a4f9-4864-8163-17b6f684ed38/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/b3841f9f-cc01-422e-a18b-da1b3c2f9636/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/84585ae9-c053-45de-b184-0863973bc16c/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/a952596b-cad0-4f15-90e7-5da88781042f/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/80bdc810-8b32-441d-b5aa-050909b7c1db/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/fd918fca-4112-4517-968f-8d24600320d4/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/b6d743e5-2e5d-47d1-afdd-23e064138771/image.jpg" alt=""></p>
<p>위 필기에서 오타 수정 : BC$\bot$CA → BH$\bot$CA</p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/5faf0d6e-ae83-4f0f-b16d-b1ff44475771/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/aa4d898a-b26f-453e-8ca7-3b273ece79ab/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/0427bcfb-ba1c-4db5-a940-a130761f1e52/image.jpg" alt=""></p>
<p>자료 출처: 수학독본 3권</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[삼각함수]]></title>
            <link>https://velog.io/@kkj-100-010-110/%EC%82%BC%EA%B0%81%ED%95%A8%EC%88%98</link>
            <guid>https://velog.io/@kkj-100-010-110/%EC%82%BC%EA%B0%81%ED%95%A8%EC%88%98</guid>
            <pubDate>Fri, 17 Nov 2023 21:29:30 GMT</pubDate>
            <description><![CDATA[<p>출처 : 디온통계 Youtube채널, 수학독본, Newton HIGHLIGHT 삼각함수의 세계</p>
<p>호도법과 일반각</p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/125c3e2c-8d8b-4b12-a96e-66c04416e4b8/image.jpg" alt=""></p>
<p>삼각비와 특수각에 따른 각각의 값들</p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/379bb912-d02d-487b-a0e2-599945fe509b/image.jpg" alt=""></p>
<p>삼각함수 용어 설명, 역수관계인 삼각함수들, 사영정리 및 피타고라스 정리</p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/91c73689-9fb3-420c-a62c-f5cd2985fe64/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/114627c1-2c62-4125-b03c-d0aaf62378ba/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/76532cef-00f2-4577-bdd6-1d66e690e657/image.jpg" alt=""></p>
<p><strong>삼각함수의 주요 공식</strong></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/c1275cd6-e6ec-4354-80de-f0671ada64a9/image.jpg" alt=""></p>
<p>삼각함수 그래프 특징</p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/fb05e0ae-b0e7-49c5-a4a2-24bf62f28d17/image.jpg" alt=""></p>
<p><strong>덧셈정리</strong></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/c4f35b13-34b6-4236-97df-ed862be5ec2b/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/6c150822-9ccf-4816-8562-ad58f270e8d4/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/6c87fd2b-7ce7-4b84-8c10-57c6dce0ce5c/image.jpg" alt=""></p>
<p><strong>삼각함수의 합성</strong></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/2ccc2b65-4d9e-4316-a1c9-08554361a987/image.jpg" alt=""></p>
<p>삼각함수 여러가지 공식</p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/1b196541-262f-4dd2-ab34-96423ebf22aa/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/bbd05836-bfac-45d5-950f-0ea38d09525b/image.jpg" alt=""></p>
<p>증명을 위한 기초 기하 몇 가지</p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/e9856545-46e0-49f4-9ecc-d39f5484d259/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/4ce09785-6ac2-4a1e-a30b-6b4e5c225f3f/image.jpg" alt=""></p>
<p><strong>삼각함수와 삼각형(사인정리, 코사인정리, 삼각형의 넓이 등)</strong></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/84fc1e37-470e-4825-b7a1-7342eaadc8f0/image.jpg" alt=""></p>
<p>사인정리 → 삼각형의 넓이</p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/39c4b750-41e2-4bf7-9fcc-0ecd17f57f26/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/9a1d7c6e-db0f-4a07-b33d-405d088724ab/image.jpg" alt=""></p>
<p>코사인 정리 → 덧셈정리</p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/f725d60f-7bc6-4c99-a4c5-0341446bd0e4/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/72d8c001-5df6-4969-a02d-f85d031478a0/image.jpg" alt=""></p>
<p>삼각함수 코사인정리, 피타고라스 정리</p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/5051c3fa-7b42-44a5-963b-907450207f8d/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/d0afbf7b-8fdc-45e1-aac8-3eb8e94324bb/image.jpg" alt=""></p>
<p>삼각형의 넓이</p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/3254ed83-4716-4506-981d-7aa6614ba036/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/ba01e8f1-3ace-43e1-846d-8743cc3704e6/image.jpg" alt=""></p>
<p>몇가지 흥미로운 문제</p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/5d017e1d-3051-4ed7-9b2b-4a528ec86b30/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/be1071ec-c049-4a54-b1d4-3601f9392a3f/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/fe00c747-67ad-4b14-b4a0-8a76491898f0/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/8c9c1a85-9af1-464c-8eab-99293156f688/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/52e002b3-45b0-4c00-9875-edd3034d7645/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/1caab64c-4e65-4ed1-9457-31bc6396498c/image.jpg" alt=""></p>
<p>삼각형의 오심</p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/2f01d265-6f41-4beb-b40d-da8719fb89b9/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/3b3b0c13-a004-4e71-9cc0-d37da78a11bb/image.jpg" alt=""></p>
<p><img src="https://velog.velcdn.com/images/kkj-100-010-110/post/5982de0a-cdd1-4372-a240-dd4c65eed134/image.jpg" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[연결 리스트 & 이중포인터]]></title>
            <link>https://velog.io/@kkj-100-010-110/%EC%97%B0%EA%B2%B0-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%9D%B4%EC%A4%91%ED%8F%AC%EC%9D%B8%ED%84%B0</link>
            <guid>https://velog.io/@kkj-100-010-110/%EC%97%B0%EA%B2%B0-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%9D%B4%EC%A4%91%ED%8F%AC%EC%9D%B8%ED%84%B0</guid>
            <pubDate>Tue, 07 Feb 2023 22:48:38 GMT</pubDate>
            <description><![CDATA[<p>연결 리스트(linked list)</p>
<ul>
<li>노드라 불리는 구조체의 체인이다.</li>
<li>각 노드들은 다음 노드를 가르키는 포인터를 가지고 있다.</li>
<li>마지막 노드는 null pointer를 가진다.<ul>
<li>[N] -&gt; [N] -&gt; [N] -&gt; [N] -&gt; NULL</li>
</ul>
</li>
<li>배열과 비교<ul>
<li>연결 리스트는 배열보다 더 유연하다.</li>
<li>연결리스트에 노드를 삽입, 삭제를 쉽게 할 수 있고 리스트를 더 키우거나 줄일 수 있다. 그러나 random access(인덱스로 접근)의 기능을 잃는다. 배열은 어떤 원소에 접근하는 시간(O(1))은 같다. 그러나 연결 리스트의 경우 접근하는 데이터의 노드가 가장 앞에 있을 때 빠르고(O(1)) 가장 뒤에 있을 때 느리다.(O(n))</li>
</ul>
</li>
</ul>
<p>연결 리스트 구현 (데이터는 int형, 단순 기능들(생성, 삭제, 검색, 추가 등))
linked_list.h</p>
<pre><code>#ifndef LINKED_LIST_H
#define LINKED_LIST_H

struct node {
    int data;
    struct node *next;
}

#include &lt;stdlib.h&gt; // memory allocation

struct node *create_node(int n);
struct node *add_to_list(struct node *list, int n);
void add_to_list(struct node **list, int n); // pointers to pointers
struct node *read_numbers(void);
struct node *search_list(struct node *list, int n);
struct node *delete_from_list(struct node *list, int n);
void delete_from_list(struct node **list, int n); // pointers to pointers
void delete_list(struct node **list);
struct node *insert_into_ordered_list(struct node *list, struct node *new);

#endif</code></pre><p>linked_list.c</p>
<pre><code>#include &quot;linked_list.h&quot;

struct node *create_node(int n)
{
    struct node *new;
    if (new == NULL) {
        printf(&quot;Error: malloc failed in create_node\n&quot;);
        return NULL;
    }
    new-&gt;data = n;
    new-&gt;next = NULL;

    return new;
}

struct node *add_to_list(struct node *list, int n)
{
    struct node *new_node;

    new_node = malloc(sizeof(struct node));
    if (new_node == NULL) {
        printf(&quot;Error: malloc failed in add_to_list\n&quot;);
        exit(EXIT_FAILURE);
    }
    new_node-&gt;value = n;
    new_node-&gt;next = list;
    return new_node;
}

// pointers to pointers
void add_to_list(struct node **list, int n)
{
    struct node *new_node;

    new_node = malloc(sizeof(struct node));
    if (new_node == NULL) {
        printf(&quot;Error: malloc failed in add_to_list\n&quot;);
        exit(EXIT_FAILURE);
    }
    new_node-&gt;value = n;
    new_node-&gt;next = *list;
    *list = new_node;
}

struct node *read_numbers(void)
{
    struct node *first = NULL;
    int n;

    printf(&quot;Enter a series of integers (0 to terminate): &quot;);
    for (;;) {
        scanf(&quot;%d&quot;, &amp;n);
        if (n == 0)
            return first;
        first = add_to_list(first, n);
    }
}

struct node *search_list(struct node *list, int n)
{
    while (list != NULL &amp;&amp; list-&gt;value != n)
        list = list-&gt;next;
    return list;
}

struct node *delete_from_list(struct node *list, int n)
{
    struct node *cur, *prev;

    for (cur = list, prev = NULL;
         cur != NULL &amp;&amp; cur-&gt;value != n;
         prev = cur, cur = cur-&gt;next)
        ;
    if (cur == NULL)
        return list;
    if (prev == NULL)
        list = list-&gt;next;
    else
        prev-&gt;next = cur-&gt;next;
    free(cur);
    return list;
}

void delete_from_list(struct node **list, int n)
{
    struct node *temp; // a pointer to the node you delete

    if ((*list)-&gt;value == n) {
        temp = *list;
        *list = (*list)-&gt;next;
        free(temp);
        return ;
    }
    while ((*list)-&gt;next != NULL &amp;&amp; (*list)-&gt;next-&gt;value != n)
        list = &amp;(*list)-&gt;next;
    if ((*list)-&gt;next != NULL) {
        temp = (*list)-&gt;next;
        (*list)-&gt;next = (*list)-&gt;next-&gt;next;
        free(temp);
    }
}

void delete_list(struct node **list)
{
    struct node *temp;

    if (*list == NULL)
        return;
    while (*list != NULL) {
        temp = *list;
        *list = (*list)-&gt;next;
        free(temp);
    }
}

struct node *insert_into_ordered_list(struct node *list, struct node *new)
{
    struct node *cur = list, *prev = NULL;

    while (cur != NULL &amp;&amp; cur-&gt;value &lt;= new_node-&gt;value) {
        prev = cur;
        cur = cur-&gt;next;
    }
    if (prev == NULL) {
        new-&gt;next = cur;
        list = new;
    } else {
        new-&gt;next = cur;
        prev-&gt;next = new;
    }
    return list;
}
</code></pre><p>몇 가지 실수했던 부분들</p>
<ul>
<li><p>포인터에 대한 이해도 있어야되지만 범위를 이해하고 있는게 더 중요하다.</p>
<ul>
<li><p>먼저 함수의 매개변수도 지역변수이므로 범위를 벗어나면 사라진다. 이 매개변수를 잘 이용하면 된다.</p>
<ul>
<li>단일 포인터 매개변수도 잘 이용할 수 있다. 어차피 범위를 벗어나면 사라지니 매개변수의 활용이 중요.<pre><code>void delete_from_list(struct node **list, int n)
...
// 아래 while문 안에서 매개변수를 이용해야한다. 아니면 원래 주소(매개변수가 가르키는 주소)의 이동이 발생
while ((*list)-&gt;next != NULL &amp;&amp; (*list)-&gt;next-&gt;value != n)
    list = &amp;(*list)-&gt;next;
if ((*list)-&gt;next != NULL) {
    temp = (*list)-&gt;next;
    // 여기서는 본 주소의 이동이 필요
    (*list)-&gt;next = (*list)-&gt;next-&gt;next;
    free(temp);
}
...
</code></pre></li>
</ul>
<pre><code></code></pre></li>
</ul>
</li>
<li><p>루프문 안에서 범위를 이용할 수도 있다. 괄호를 벗어나면 사라진다. 주소를 찍어보면 이해하기 수월하다. 책에서는 전역 포인터 변수를 사용하는 부분이 있는데 그 부분에서 유용했다.</p>
<pre><code>  struct node *delete_node(struct node *list, int n)
  {
      struct node *temp; // 삭제할 노드를 임시적으로 담을 포인터 변수

      for (struct node *p = list; p-&gt;next != NULL; p = p-&gt;next) {
          if (p-&gt;next-&gt;value == n) {
              temp = p-&gt;next;
              p-&gt;next = p-&gt;next-&gt;next;
              free(temp);
              // break; 또는 같은 값을 계속 찾아 지우게 나두면 된다.
          }
      }
      return list; // list의 주소에는 변화가 없다
  }</code></pre></li>
<li><p>포인터 같은 경우 주소를 일일이 대조해서 찍어보면 이해하는데 무리 없을 것이다.</p>
</li>
</ul>
<p>출처: K.N.KING C PROGRAMMING A MODERN APPROACH</p>
]]></description>
        </item>
    </channel>
</rss>