<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>HukPak_Graphics</title>
        <link>https://velog.io/</link>
        <description>그래픽스 하는 퍼그</description>
        <lastBuildDate>Tue, 14 Jul 2026 16:54:38 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>HukPak_Graphics</title>
            <url>https://velog.velcdn.com/images/vfx_master/profile/448b1448-8245-4769-b0d0-0b8518ef7482/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. HukPak_Graphics. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/vfx_master" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[티스토리로 옮겨요]]></title>
            <link>https://velog.io/@vfx_master/%ED%8B%B0%EC%8A%A4%ED%86%A0%EB%A6%AC%EB%A1%9C-%EC%98%AE%EA%B2%A8%EC%9A%94</link>
            <guid>https://velog.io/@vfx_master/%ED%8B%B0%EC%8A%A4%ED%86%A0%EB%A6%AC%EB%A1%9C-%EC%98%AE%EA%B2%A8%EC%9A%94</guid>
            <pubDate>Tue, 14 Jul 2026 16:54:38 GMT</pubDate>
            <description><![CDATA[<p>벨로그 수식 좀 들어가고
사진 좀 넣고
코드 많아지면 글자 적을때마다 렉걸림</p>
<p>그리고 통계도 서드파티 이용해서 확인해야하고
그마저도 일반통계임</p>
<p>걍 티스토리 넘어감</p>
<p>글도 다 옮겨놓음</p>
<p><a href="https://gamewindowcoder.tistory.com/">https://gamewindowcoder.tistory.com/</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(15) - Parallax Mapping]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL15-Parallax-Mapping</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL15-Parallax-Mapping</guid>
            <pubDate>Tue, 14 Jul 2026 14:04:14 GMT</pubDate>
            <description><![CDATA[<h1 id="parallax-mapping">Parallax mapping?</h1>
<p>Parallax 는 시차라는 뜻임</p>
<blockquote>
<p>관찰자의 위치나 시점에 따라 어떤 물체의 상대적인 위치나 방향이 달라져 보이는 시각적 현상을 뜻함</p>
</blockquote>
<p>그럼 parallax mapping은 뭐냐?
사용자의 시점에 맞춰 텍스쳐에 깊이를 더하는것과 같은 효과를 주는 매핑임</p>
<p>어? 그거 normal mapping아님??</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/60645903-9cc1-4d57-a9ee-cf712a8198b1/image.png" alt=""></p>
<p>normal mapping은 문제점이 뚜렷함
분명히 텍스쳐의 정면과 비슷한 각도에서 바라보면 높낮이가 보임
하지만 비스듬한 각도로 이동하면 결국 평면의 이미지이므로, 평면처럼 보이는 문제가 있음</p>
<p>하지만 parallax mapping은 다름
실제로 어느각도에서 보든 오브젝트의 깊이감이 생기게 됨</p>
</br>

<hr>
</br>

<p>parallax mapping은 지정된 정점들의 높이를 height map이란걸 이용해 
정점들을 이동시키는 등의 행위로 실제 높낮이를 부여하는 매핑임</p>
<p>이렇게 정점을 이동시키는 mapping을 displacement mapping이라고 함</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/vfx_master/post/81be687e-2a6d-488b-8c76-be773a9de718/image.png" alt="">displacement/height map</th>
<th><img src="https://velog.velcdn.com/images/vfx_master/post/7e81417b-bdfe-4761-8cdd-63b71b1e937f/image.png" alt="">normal map</th>
</tr>
</thead>
</table>
<p>여기서 문제점이 나옴
깊이감을 주기위해서 정점들을 매핑할 수 있는 텍스쳐를 사용해야한다면
어마어마한 수의 삼각형을 이용해서 이를 매핑해야할거임</p>
<p>하지만 실제로는 1개의 텍스쳐, 즉 2개의 삼각형만으로 매핑이 가능하다는거임</p>
<h2 id="parallax-mapping-작동방식">Parallax mapping 작동방식</h2>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/fad04387-235d-42b3-a871-5ef312667f13/image.png" alt=""></p>
<p>이렇게 fragment$A$에서 시점까지의 경로를 $\overrightarrow{V}$라고 함
이때 height map과 $\overrightarrow{V}$의 실제 교점을 $B$라고 함</p>
<p>parallax mapping의 핵심은 아래임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/de4f8567-cc25-412a-b99c-dceefc91a914/image.png" alt=""></p>
<p>점$A$에서 표면에 수직으로 선을이어 height map과 생긴 교점을 H(A)라고 함
그리고 이를 이은 벡터를 $\overrightarrow{HA}$라고 함</p>
<p>$\overrightarrow{V} * \overrightarrow{HA} = \overrightarrow{P}$가 됨</p>
<p>점$A + overrightarrow{P}$의 좌표가 바로 실제 교점 $B$와 근사한 좌표인거임</p>
<p>하지만 문제가 있음</p>
<h3 id="parallax-mapping문제">parallax mapping문제</h3>
<ol>
<li>높이 차이가 급격하면 근사치가 근사치가 아니게 됨</li>
</ol>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/0efe1241-0568-4a2b-8299-9562294c90f3/image.png" alt="">
실제교점 B와 점$A + overrightarrow{P}$좌표를 보자
실제 교점 B는 점$A + overrightarrow{P}$좌표와 비슷하지만
height map기준에서보면 점$A + overrightarrow{P}$좌표는 height map과의 차이가 큼
따라서 실제로 비현실적으로 보이게 될 가능성이 큼</p>
<ol start="2">
<li>회전, 크기, 이동등 변환행렬</li>
</ol>
<p>normal mapping에서 얘기한것처럼
<a href="https://velog.io/@vfx_master/Advanced-OpenGL14-Normal-mapping#%EC%9D%BC%EB%B0%98%EC%A0%81%EC%9D%B8-normal-mapping%EB%AC%B8%EC%A0%9C">Advanced OpenGL(14) - Normal mapping, normal mapping문제점</a>
변환행렬이 수행되었을때 정확한 텍스쳐의 좌표를 얻을 수 없다는거임</p>
<p>따라서 이를 해결하기위해 마찬가지로 탄젠트 공간에서 parallax mapping을 수행하게 됨</p>
<h1 id="parallax-mapping-구현">Parallax mapping 구현</h1>
<p>가장 중요한게 하나 있음</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/81be687e-2a6d-488b-8c76-be773a9de718/image.png" alt=""></p>
<p>이걸 잘 보면 흰부분은 높이, 어두운 부분은 깊이라는걸 알 수 있음</p>
<p>이걸 반전시킬거임</p>
<h2 id="반전된-height-map">반전된 height map</h2>
<p>왜냐면 어짜피 parallax mapping은 깊이를 표현하는 테크닉이고,
높이를 바꾸는것보다 대부분의 경우에서 깊이를 바꾸는게 더 직관적임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/46abe1bc-c8ec-48bf-b613-766c0eb91ae0/image.png" alt=""></p>
<p>따라서 점$A$에서 $\overrightarrow{V}$를 빼면 $\overrightarrow{P}$가 나옴</p>
<p>연산은 단순하게 
$\overrightarrow{HA} * -\overrightarrow{V}$로 바꾸면 됨</p>
<p>여기서 하나 개념을 짚어야함</p>
<p>$HA$는 높이임
height map에서 texture메서드로 얻은 높이값ㅇㅇ</p>
<p>여기서 $\vec{P}$를 구하는 순서는 아래와 가틍ㅁ</p>
<ol>
<li>$\overrightarrow{V}$의 원근값 : $V_{xy} / V_z$<ul>
<li>텍스쳐 좌표는 2차원임
하지만 viewsms 3차원임
따라서 원근값 z를 통해 실제 x,y를 조절해서 2차원에서의 xy를 비율적으로 3차원과 비슷하게 조절하는거임</li>
</ul>
</li>
<li>$\overrightarrow{HA}$는 height map에서의 높이임</li>
<li>$\overrightarrow{V} * \overrightarrow{HA}$를 통해 실제 $\overrightarrow{P}$를 알아냄</li>
<li>여기에 displacement strength를 곱해 실제 적용되는 displacement강도를 조절함</li>
<li>4번까지의 과정을 통해 실제 사용될 $\overrightarrow{P}$를 구하고 depth map으로 우린 사용하므로, 텍스쳐 좌표에서 뺄셈을 하면 됨</li>
</ol>
<h2 id="구현">구현</h2>
<p><a href="https://velog.io/@vfx_master/Advanced-OpenGL14-Normal-mapping#tbn%ED%96%89%EB%A0%AC-%EC%BD%94%EB%93%9C-%EA%B5%AC%ED%98%84">Advanced OpenGL(14) - Normal mapping#TBN행렬 구현</a></p>
<p>여기서 vertex shader를 찾아서 그대로 사용하면 됨</p>
<p>왜냐면.... 동일한 개념의 탄젠트 공간에서 사용하는거걸랑ㅇㅇ</p>
<p>fragment shader만 수정해주면 됨</p>
<pre><code class="language-glsl">#version 330 core
out vec4 FragColor;

in VS_OUT {
    vec3 FragPos;
    vec2 TexCoords;
    vec3 TangentLightPos;
    vec3 TangentViewPos;
    vec3 TangentFragPos;
} fs_in;

uniform sampler2D diffuseMap;
uniform sampler2D normalMap;
uniform sampler2D depthMap;

uniform float heightScale;

vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
    float height =  1 - texture(depthMap, texCoords).r;
    return texCoords - (viewDir.xy) * (height * heightScale);
}

void main()
{
    // offset texture coordinates with Parallax Mapping
    vec3 viewDir = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos);
    vec2 texCoords = fs_in.TexCoords;

    texCoords = ParallaxMapping(fs_in.TexCoords,  viewDir);
    if(texCoords.x &gt; 1.0 || texCoords.y &gt; 1.0 || texCoords.x &lt; 0.0 || texCoords.y &lt; 0.0) discard;

    // obtain normal from normal map
    vec3 normal = texture(normalMap, texCoords).rgb;
    normal = normalize(normal * 2.0 - 1.0);

    // get diffuse color
    vec3 color = texture(diffuseMap, texCoords).rgb;
    // ambient
    vec3 ambient = 0.1 * color;
    // diffuse
    vec3 lightDir = normalize(fs_in.TangentLightPos - fs_in.TangentFragPos);
    float diff = max(dot(lightDir, normal), 0.0);
    vec3 diffuse = diff * color;
    // specular    
    vec3 reflectDir = reflect(-lightDir, normal);
    vec3 halfwayDir = normalize(lightDir + viewDir);
    float spec = pow(max(dot(normal, halfwayDir), 0.0), 32.0);

    vec3 specular = vec3(0.2) * spec;
    FragColor = vec4(ambient + diffuse + specular, 1.0);
}</code></pre>
<p>parallax mapping부분은 위에서 설명한 그대로임ㅇㅇ</p>
<p>중요한 부분은 <code>if(texCoords.x &gt; 1.0 || texCoords.y &gt; 1.0 || texCoords.x &lt; 0.0 || texCoords.y &lt; 0.0) discard;</code>임</p>
<p>viewDir은 정규화되어있음
따라서 viewDir.z는 0<del>1사이의 값임
만약 정</del>말 비스듬하게 본다면 z값이 0과 가까워지므로 텍스쳐좌표의 최대인 1보다 훨씬 커질 수 있음
따라서 그 값들을 discard해줘야함</p>
<p>그냥 위 코드처럼 viewDir.z로 나누는걸 없애도 되고... 많이 사용하기도 함</p>
<p>또, 텍스쳐좌표를 벗어나는 크기의 텍셀은 없애주는게 나으므로
<code>if(texCoords.x &gt; 1.0 || texCoords.y &gt; 1.0 || texCoords.x &lt; 0.0 || texCoords.y &lt; 0.0) discard;</code>이게 꼭 필요함</p>
<p>전체코드가 궁금하면</p>
<p><a href="https://learnopengl.com/code_viewer_gh.php?code=src/5.advanced_lighting/5.1.parallax_mapping/parallax_mapping.cpp">LearnOpenGL - Parallax Mapping.cpp</a> 이걸 보도록!</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/250372a6-1cd4-4e61-8ea8-0b0d4792ffef/image.png" alt="">
이쁘게 나온걸 볼 수 있음</p>
<p>하지만 문제가 있음</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/vfx_master/post/63a2365d-7738-4a81-b89b-e8081aa20cf7/image.png" alt=""><code>viewDir.xy</code></th>
<th><img src="https://velog.velcdn.com/images/vfx_master/post/123f0d22-09e2-4239-86be-55fcb7c7a09f/image.png" alt=""><code>viewDir.xy/viewDir.z</code></th>
</tr>
</thead>
</table>
<p>비스듬한 각도에서 바라보면 둘 다 깊이가 휘어지고
깊이표현도 제대로 되지 않는단걸 볼 수 있음</p>
<p>이건 어떻게 해결해야할까?</p>
<p>실제 교점 B와 근사한 좌표를 더 정확하게 찾을 수 있다면 어떨까?</p>
<h1 id="steep-parallax-mapping">steep parallax mapping</h1>
<p>height/depth map의 전체 깊이는 0~1사이의 텍스쳐 범위임</p>
<p>이 깊이의 특정 구간을 나누는 steep을 나누고,
$\overrightarrow{P}$의 경로에서 각 steep에서의 교차점을 확인후
height와 계산해서 height map안쪽에 교차점이 있으면 사용하는거임</p>
<p>이게 뭔소리냐면...
<img src="https://velog.velcdn.com/images/vfx_master/post/8fa4c792-baed-4b77-b387-70635defa10d/image.png" alt=""></p>
<p>이걸 보면, 알 수 있음</p>
<ol>
<li>보라색 벡터 = $\overrightarrow{P}$</li>
<li>보라색 점 = steep과의 교차점</li>
<li>각 steep과의 교차점과 해당 지점에서의 height와의 비교</li>
<li>steep의 교차점이 height보다 값이 작은 지점이 최근사좌표</li>
</ol>
<p>easyeasy~</p>
<h2 id="steep-parallax-mapping구현">steep parallax mapping구현</h2>
<p>fragment shader의 parallax map메서드만 바꾸면 됨</p>
<pre><code class="language-glsl">vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
    //steep수 결정
    const float numLayers = 10;
    // 각 steep 차이
    float layerDepth = 1.0 / numLayers;
    // 현재 벡터의 깊이
    float currentLayerDepth = 0.0;
    // 시점 ~ 점A까지 벡터P
    vec2 P = viewDir.xy * displacementScale;
    // 벡터P를 steep단위로 나눈 차이 벡터
    vec2 deltaTexCoords = P / numLayers;

    // 초기 값
    vec2  currentTexCoords     = texCoords;
    float currentDepthMapValue = texture(depthMap, currentTexCoords).r;

    while(currentLayerDepth &lt; currentDepthMapValue)
    {
        // depth map이므로 현재 texture좌표에서 벡터P를 steep단위로 나눈 차이 벡터를 뺌
        currentTexCoords -= deltaTexCoords;
        //해당 steep의 depth 값 할당
        currentDepthMapValue = texture(depthMap, currentTexCoords).r;
        // 다음 steep의 텍스쳐로 이동
        currentLayerDepth += layerDepth;
    }

    return currentTexCoords;
}</code></pre>
<p>어려운 코드는 없음</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/79ef3444-0e2f-4a54-956f-8f2106fecf94/image.png" alt=""></p>
<p>비스듬한 각에서도 잘 보이는걸 알수 있음</p>
<h3 id="개선">개선</h3>
<p>이 코드를 약간 개선할 수 있음</p>
<p>일명 <code>동적 레이어</code>를 이용하는거임</p>
<p>아이디어는 간단함</p>
<ol>
<li>정면에서 바라볼때는 displacement가 많이 필요하지 않음</li>
<li>비스듬히 바라볼때는 displacement가 많이 필요함</li>
</ol>
<p>따라서 이걸 동적으로 바뀌도록 하면 되는데...</p>
<p>이걸 어떻게 하지?</p>
<p>내적이용하면됨ㅇㅇ</p>
<p>결국 비스듬히 바라볼때는 z와 viewDir이 비슷하다는거임
두 벡터가 비슷할수록 내적은 1이됨</p>
<pre><code class="language-cpp">//steep수 결정
const float minLayer = 4;
const float maxLayer = 48;
const float similar = abs(dot(viewDir, vec3(0.0, 0.0, 1.0)));
const float numLayer = mix(maxLayer, minLayer, similar);</code></pre>
<p>내적을 절대값 사용하는 이유는 두 벡터가 완전히 다른방향일때 -1을 반환하는데, 
이렇게 두 벡터가 같으면 1이 바환되므로 minLayer가 반환되고,
두 벡터가 완전히 다르면 0이 반환되므로 maxLayer가 반환되고,
그 사이는 보간처리되어서 적절한 값을 가지도록 함</p>
<br>

<hr>
<br>

<p><img src="https://velog.velcdn.com/images/vfx_master/post/1eaff18b-b475-4de1-9902-673a86896f56/image.png" alt="">
이걸 보자 ㅇㅇ</p>
<p>가까이서 보면 역시나 문제가 생김</p>
<p>역시 layer수가 한정적이라서 그런데, 
layer수를 무한정 늘리면 성능에 부담이 심하지ㅇㅇ</p>
<p>해결하려면??</p>
<p>relief parallax map이나 parallax occlusion map을 사용하면 됨</p>
<p>relief방식은 이진탐색을 이용해서 정확한 실제교점 B를 구함
대신 연산이 비쌈ㅇㅇ... 그래서 RT에서는 잘 사용못함</p>
<p>그래서 보통 사용하는게 parallax occlusion map임</p>
<h1 id="parallax-occlusion-mapping">parallax occlusion mapping</h1>
<p>POM이라고 부름</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/46abe1bc-c8ec-48bf-b613-766c0eb91ae0/image.png" alt="">
이사진에서 점 $A$가 $\overrightarrow{P}$만큼 이동한 후의 좌표를 $T_P$라고 해보자</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/88ac82b2-3489-401f-b52f-32287a00ac42/image.png" alt=""></p>
<p>여기서 볼 수 있듯이 POM은</p>
<p>steep parallax occlusion가 비슷하지만</p>
<p>$T_P$를 기준으로 전후로 지나가는 steep과의 교차점 $T_2$, $T_3$를 이용함</p>
<ol>
<li>steep parallax mapping을 함</li>
<li>$\overrightarrow{P}$의 경로에서 $T_P$를 지나기 직전, 직후의 steep의 교차점을 찾음</li>
<li>해당 교차점들을 각각 관통하는 ray를 표면에 수직방향으로 쏴서, height map과 만나는 두 지점 $H(T_3)$, $H(T_2)$를찾음</li>
<li>$H(T_3)$, $H(T_2)$를 선형보간으로 이어줌</li>
</ol>
<pre><code class="language-cpp">vec2 ParallaxMapping(vec2 texCoords, vec3 viewDir)
{
    //steep수 결정
    const float minLayer = 4;
    const float maxLayer = 48;
    float similar = abs(dot(viewDir, vec3(0.0, 0.0, 1.0)));
    float numLayers = mix(maxLayer, minLayer, similar);

    // 각 steep 차이
    float layerDepth = 1.0 / numLayers;
    // 현재 벡터의 깊이
    float currentLayerDepth = 0.0;
    // 시점 ~ 점A까지 벡터P
    vec2 P = viewDir.xy * displacementScale;
    // 벡터P를 steep단위로 나눈 차이 벡터
    vec2 deltaTexCoords = P / numLayers;

    // 초기 값
    vec2  currentTexCoords     = texCoords;
    float currentDepthMapValue = texture(depthMap, currentTexCoords).r;

    while(currentLayerDepth &lt; currentDepthMapValue)
    {
        // depth map이므로 현재 texture좌표에서 벡터P를 steep단위로 나눈 차이 벡터를 뺌
        currentTexCoords -= deltaTexCoords;
        //해당 steep의 depth 값 할당
        currentDepthMapValue = texture(depthMap, currentTexCoords).r;
        // 다음 steep의 텍스쳐로 이동
        currentLayerDepth += layerDepth;
    }

    // depth map이라는 개념을 이용하므로 delta를 더해주면 previous 텍스쳐 좌표가 나옴
    vec2 prevTexCoords = currentTexCoords + deltaTexCoords;

    // 이후, 이전 layer에서 height까지의 거리
    float afterDepth  = currentDepthMapValue - currentLayerDepth;
    float beforeDepth = texture(depthMap, prevTexCoords).r - currentLayerDepth + layerDepth;

    // afterDepth, beforeDepth를 이용해 가중치, 이후 depth와 이전 depth의 비율이 가중치가 됨
    float weight = afterDepth / (afterDepth - beforeDepth);
    //선형보간
    vec2 finalTexCoords = prevTexCoords * weight + currentTexCoords * (1.0 - weight);

    return finalTexCoords;
}</code></pre>
<p>아래 몇줄만 추가해주면 됨</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/a983a1e5-2588-48e6-b55d-782df89473f5/image.png" alt=""></p>
<p>AA와 여전히 조금이 layer마다 나뉘는 문제가 있긴하지만,
이정도면 ㅅㅌ치임!!!!</p>
<br>

<hr>
<br>

<p>보통 pm이나 pom은 벽이나 바닥에 사용을 함</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(14) - Normal mapping]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL14-Normal-mapping</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL14-Normal-mapping</guid>
            <pubDate>Mon, 13 Jul 2026 14:01:02 GMT</pubDate>
            <description><![CDATA[<h1 id="normal-mapping">Normal mapping</h1>
<p>평면의 텍스쳐에 normal map이라는걸 이용해서 입체감을 주는 방식임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/78f7aa0f-8620-44d4-8d28-ad21a06a9272/image.png" alt=""></p>
<p>왼쪽의 텍스쳐를 normal map을 이용하면 오른쪽 텍스쳐가 되는거임</p>
<p>normal map은 간단하게 설명하면
개별의 fragment의 normal을 임의로 수정한 텍스쳐를 이용해서 빛 반사를 다르게 만드는거임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/f8069e94-e6a6-4ea8-939c-2db7458046a6/image.png" alt=""></p>
<p>또한 normal mapping을 bump mapping이라고도 함</p>
<h1 id="normal-map-사용">Normal map 사용</h1>
<p>기본적으로 normal vector, 법선 벡터는 -1~1 사이의 값을 가짐</p>
<blockquote>
<p>왜 -1~1사이의 값이냐면... 정규화된 길이의 벡터이기 때문ㅇㅇ</p>
</blockquote>
<p>하지만 우리가 제공받는 normal map텍스쳐는 0~1사이의 값임
따라서 이를 사용하려면 다시 -1~1사이의 값을 가지도록 값을 바꿔줘야함</p>
<pre><code class="language-cpp">uniform sampler2D normalMap;

void main()
{           
    // [0,1] 범위의 노멀 맵에서 법선 얻기
    normal = texture(normalMap, fs_in.TexCoords).rgb;
    // 법선 벡터를 [-1,1] 범위로 변환
    normal = normalize(normal * 2.0 - 1.0);   

    [...]
    // 정상적으로 조명 처리 진행
}</code></pre>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/6cdc04e8-725d-4365-a15c-b7429c7e9a8a/image.png" alt="">
기본적으로 normal map텍스쳐는 이렇게 생김</p>
<p>이런 색상인 이유는 대부분 텍셀에 대해 z축이 사용자를 바라보는, 1의 값이므로 파란색상을 띄는거임
상단은 양의 y축을 바라보고 있으니, y가 1인 녹색에 가까움ㅇㅇ
벽돌 사이사이는 어두운색인 이유도 이거임ㅇㅇ</p>
<p>근데 문제가 잇음</p>
<h2 id="일반적인-normal-mapping문제">일반적인 normal mapping문제</h2>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/16bdaf8b-1f41-4667-a3bf-b823ca2f887b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/104b7d15-19f7-4941-937d-c7540bf0a386/image.png" alt=""></p>
<p>이 두 이미지의 차이가 보임?
아래의 텍스쳐는 제대로 normal mapping이 된것처럼 보이지 않음</p>
<p>그 이유는 normal map텍스쳐는 위에서 설명했듯이, 양의 z축으로 normal이 이루어져 있음</p>
<p>근데 z축방향에서 빛이 오는게 아닌 y축이나 x축의 비스듬한 각도에서 빛이 오면??
normal은 z축을 바라보고 있는데, 빛은 그 축이 아니니 이상하게 normal mapping이 된거임</p>
<p>그럼 이걸 어떻게 해결하냐??</p>
<ol>
<li>표면의 모든 가능한 방향에서의 normal map텍스쳐를 만들어서 사용</li>
<li>x,y,z를 월드공간으로 이동시키는 변환처리</li>
</ol>
<p>1번은 고해상도 텍스쳐의 경우 그걸 다 만들기에는 무리가 있음
고해상도 텍스쳐가 아니라면 변환행렬 이용해서 계산하는것보다는 효율이 괜찮을지도??</p>
<p>보통은 2번을 사용함
2번과 같은 방식을 행하는 공간이 탄젠트 공간임</p>
<h1 id="tangent-space">tangent space</h1>
<p>오브젝트는 삼각형들로 이루어져 있음</p>
<p>이러한 오브젝트는 월드공간에서 문제가 없음</p>
<p>다만 오브젝트의 텍스쳐가 문제가 있는거임</p>
<p>위에서 설명했다싶이
x,y,z가 고정된 상태로 어떤 면은 아래, 어떤면은 위, 어떤면은 오른쪽,,,
이렇게 바라보는데 빛의 방향이 바뀔때도 이게 유지가 된다면...
빛 반사에 오류가 생김</p>
<p>이때 텍스쳐의 x,y,z는 각 표면의 삼각형에 국한된, 
표면에 relate한 좌표라는거임
이 좌표공간이 tangent space임</p>
<p>따라서 이 각 표면 삼각형에 relate한 탄젠트 공간 좌표를 밖으로 꺼내서,
월드 공간 좌표로 변환해, 빛이 어느방향에서 오든 알맞은 방향으로 빛이 발산되도록 하는게 목표인거임</p>
<p>이때 탄젠트 공간 -&gt; 월드 공간으로의 변환을 수행하는 행렬을
TBN행렬이라고 함</p>
<h2 id="tbn행렬">TBN행렬</h2>
<p>Tangent, BitTangent, Normal이라는 3개의 벡터로 이루어진 변환행렬임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/7403dfae-cf9e-4407-b2a2-011a32851f18/image.png" alt=""></p>
<p>사실 uv에 normal을 합쳐서 재명칭을 붙였다고 봐야함</p>
<p>x축이 tangent벡터, y축이 bittangent벡터, z축이 normal벡터가 되는거임</p>
<h3 id="tbn변환행렬-구하는-법유도">TBN변환행렬 구하는 법/유도</h3>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/3186b2f0-ca82-4106-a2a5-5b3e107340b9/image.png" alt=""></p>
<p>내부 검은 삼각형을 $\triangle{E_1E_2}$라고 해보자</p>
<p>검은 삼각형은 해당 텍스쳐가 매핑하게 될 오브젝트의 특정 삼각형이라는 가정임</p>
<p>$E_1$을 빗변으로 가지는 삼각형을 $\triangle{U_1V_1}$
$E_2$을 빗변으로 가지는 삼각형을 $\triangle{U_2V_2}$
라고 함</p>
<h4 id="1-overrightarrowe_1-구하기">1. $\overrightarrow{E_1}$ 구하기</h4>
<p>우리는 $\triangle{U_1V_1}$에서 $\overrightarrow{U_1}$이 $\tan$ 혹은 $U$의 일부분이라는걸 알고 있음</p>
<p>마찬가지로 $\overrightarrow{V_1}$은 $bit\tan$ 혹은 $V$의 일부분이라는것도 알고 있음</p>
<p>벡터의 삼각형법에 의해 $\overrightarrow{U_1} + \overrightarrow{V_1} = \overrightarrow{T_1} + \overrightarrow{B_1} = \overrightarrow{E_1}$이 됨</p>
<h4 id="2-overrightarrowe_2-구하기">2. $\overrightarrow{E_2}$ 구하기</h4>
<p>1번과 마찬가지로 식에 대입하면 </p>
<p>$\overrightarrow{U_2} + \overrightarrow{V_2} = \overrightarrow{T_2} + \overrightarrow{B_2} = \overrightarrow{E_2}$가 되는걸 할 수 있음</p>
<h4 id="3-식-분해">3. 식 분해</h4>
<p>$\overrightarrow{T_1} + \overrightarrow{B_1} = \overrightarrow{E_1}$라는 식은
$(E_{1x}, E_{1y}, E_{1z}) = \Delta U_1 (T_x, T_y, T_z) + \Delta V_1 (B_x, B_y, B_z)$ 으로 분해가 가능</p>
<p>$\overrightarrow{T_2} + \overrightarrow{B_2} = \overrightarrow{E_2}$라는 식은
$(E_{2x}, E_{2y}, E_{2z}) = \Delta U_2 (T_x, T_y, T_z) + \Delta V_2 (B_x, B_y, B_z)$ 으로 분해가 가능</p>
<h4 id="4-월드-좌표로의-행렬-변환">4. 월드 좌표로의 행렬 변환</h4>
<p>먼저 월드 좌표로의 행렬 변환이 되어야 하는 이유가 있음</p>
<blockquote>
<p>텍스쳐는 UV라는 좌표계를 이용함
ㅤ
단순히 텍스쳐 로컬에서만 살펴본다면 $T_{xyz} = (1,0,0)$, $B_{xyz} = (0,1,0)$임
ㅤ
하지만 이 좌표는 말했듯, 텍스쳐 로컬 좌표고, 오브젝트에 매핑되는 순간 우리는 해당 텍스쳐의 좌표를 알지 못함
ㅤ
우리는 정확한 normal mapping을 위해 로컬 텍스쳐 좌표가 아닌, 월드 텍스쳐 좌표
즉, 오브젝트 위에서 사용된 텍스쳐의 월드 좌표가 필요한거임
ㅤ
따라서 오브젝트는 삼각형으로 만들어져있고,
매핑된 텍스쳐의 삼각형 부분을 사용해서 텍스쳐를 월드 좌표로 변환시키는거임</p>
</blockquote>
<p>위 2개의 분해된 연립일차방정식을 행렬로 표현하면 아래와 같음</p>
<p>$$\begin{bmatrix} E_{1x} &amp; E_{1y} &amp; E_{1z} \\ E_{2x} &amp; E_{2y} &amp; E_{2z} \end{bmatrix} 
= \begin{bmatrix} \Delta U_1 &amp; \Delta V_1 \\ \Delta U_2 &amp; \Delta V_2 \end{bmatrix} \begin{bmatrix} T_x &amp; T_y &amp; T_z \\ B_x &amp; B_y &amp; B_z \end{bmatrix}$$</p>
<p>우리는 $(T_x, T_y, T_z), (B_x, B_y, B_z)$가 필요하므로, 행렬의 이항정리를 통해 식을 약간 바꿔줌</p>
<p>$$\begin{bmatrix} \Delta U_1 &amp; \Delta V_1 \\ \Delta U_2 &amp; \Delta V_2 \end{bmatrix}^{-1}
\begin{bmatrix} E_{1x} &amp; E_{1y} &amp; E_{1z} \\ E_{2x} &amp; E_{2y} &amp; E_{2z} \end{bmatrix} 
= \begin{bmatrix} T_x &amp; T_y &amp; T_z \\ B_x &amp; B_y &amp; B_z \end{bmatrix}$$</p>
<p>역행렬은 행렬식을 사용해 표현할 수 있음</p>
<blockquote>
<p>역행렬
ㅤ
$$A = \begin{bmatrix}a &amp; b \\ c &amp; d \end{bmatrix}$$라는 행렬이 있음
ㅤ
$A^{-1} = \frac{1}{|A|} adjA$임</p>
<blockquote>
<ol>
<li>$|A|$는 행렬 A에 대한 행렬식임
행렬식 공식은 쉬움
$\frac{1}{ad - bc}$
ㅤ</li>
<li>$adj A$는 행렬 A의 수반행렬임
<a href="https://m.blog.naver.com/midlemoon0/223416442833">역행렬 구하기 - 수학하는 수영</a>을 참고해서 구하면 됨
2*2크기의 수반행렬은 그냥 대각성분 바꾸고 부호만 제대로 넣어주면 됨
$$adj A = \begin{bmatrix}d &amp; -b \\ -c &amp; a \end{bmatrix}$$
ㅤ</li>
</ol>
</blockquote>
</blockquote>
<p>따라서 식을 전개해보면 </p>
<p>$$\begin{bmatrix} T_x &amp; T_y &amp; T_z \\ B_x &amp; B_y &amp; B_z \end{bmatrix}
= \frac{1}{\Delta U_1\Delta V_2 - \Delta U_2\Delta V_1} \begin{bmatrix} \Delta V_2 &amp; -\Delta V_1 \\ -\Delta U_2 &amp; \Delta U_1 \end{bmatrix}
\begin{bmatrix} E_{1x} &amp; E_{1y} &amp; E_{1z} \\ E_{2x} &amp; E_{2y} &amp; E_{2z} \end{bmatrix} $$
이 된다는걸 알 수 있음</p>
<h3 id="tbn행렬-코드-구현">TBN행렬 코드 구현</h3>
<h4 id="1--tangent-bittangent구하기">1.  tangent, bittangent구하기</h4>
<pre><code class="language-cpp">// positions
glm::vec3 pos1(-1.0f,  1.0f, 0.0f); //좌상단
glm::vec3 pos2(-1.0f, -1.0f, 0.0f); //좌하단
glm::vec3 pos3( 1.0f, -1.0f, 0.0f); //우하단
glm::vec3 pos4( 1.0f,  1.0f, 0.0f); //우상단
// texture coordinates
glm::vec2 uv1(0.0f, 1.0f); //좌상단
glm::vec2 uv2(0.0f, 0.0f); //좌하단
glm::vec2 uv3(1.0f, 0.0f); //우하단
glm::vec2 uv4(1.0f, 1.0f); //우상단
// normal vector
glm::vec3 nm(0.0f, 0.0f, 1.0f);</code></pre>
<p>먼저 위 코드처럼 좌표계에 맞춰
순서를 맞춰 position과 texture좌표와 normal을 선언함</p>
<p>사실 이부분은 모델을 쓰면 자동으로 들어와있음</p>
<p>그리고 우리는 $E_1$, $E_2$를 먼저 벡터 뺄셈을 통해 구할 수 있음
마찬가지로 uv의 $\Delta$도 구할 수 있음</p>
<pre><code class="language-cpp">glm::vec3 edge1 = pos2 - pos1;
glm::vec3 edge2 = pos3 - pos1;
glm::vec2 deltaUV1 = uv2 - uv1;
glm::vec2 deltaUV2 = uv3 - uv1; </code></pre>
<p>그리고 이제 이걸 이용해서 위에서 유도한 공식에 대입해서
tangent space -&gt; world space변환행렬을 구하면 됨</p>
<pre><code class="language-cpp">glm::vec3 tangent1, bitangent1;
glm::vec3 tangent2, bitangent2;

float f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y); //행렬식

// triangle 1
// ----------
glm::vec3 edge1 = pos2 - pos1;
glm::vec3 edge2 = pos3 - pos1;
glm::vec2 deltaUV1 = uv2 - uv1;
glm::vec2 deltaUV2 = uv3 - uv1;

tangent1.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
tangent1.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
tangent1.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);

bitangent1.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);
bitangent1.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
bitangent1.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);

// triangle 2
// ----------
edge1 = pos3 - pos1;
edge2 = pos4 - pos1;
deltaUV1 = uv3 - uv1;
deltaUV2 = uv4 - uv1;

f = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV2.x * deltaUV1.y);

tangent2.x = f * (deltaUV2.y * edge1.x - deltaUV1.y * edge2.x);
tangent2.y = f * (deltaUV2.y * edge1.y - deltaUV1.y * edge2.y);
tangent2.z = f * (deltaUV2.y * edge1.z - deltaUV1.y * edge2.z);

bitangent2.y = f * (-deltaUV2.x * edge1.y + deltaUV1.x * edge2.y);
bitangent2.z = f * (-deltaUV2.x * edge1.z + deltaUV1.x * edge2.z);
bitangent2.x = f * (-deltaUV2.x * edge1.x + deltaUV1.x * edge2.x);</code></pre>
<blockquote>
<p>각각의 tangent를 구하는게 왜이런건지 모르겠다면</p>
<p>$$A = \begin{pmatrix} a &amp; b \ c &amp; d \end{pmatrix}, B = \begin{pmatrix} x &amp; y &amp; z \ w &amp; v &amp; u \end{pmatrix}$$
$$A \times B = \begin{pmatrix} ax+bw &amp; ay+bv &amp; az+bu \ cx+dw &amp; cy+dv &amp; cz+du \end{pmatrix}$$
이렇게 전개되는 행렬곱을 생각해보쟈~</p>
</blockquote>
<h4 id="2-tangent-bittangent-월드공간변환-쉐이더">2. tangent, bittangent 월드공간변환 쉐이더</h4>
<p>먼저 vertex shader</p>
<pre><code class="language-glsl">#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in vec3 aTangent;
layout (location = 4) in vec3 aBitangent;

out VS_OUT {
    vec3 FragPos;
    vec2 TexCoords;
    vec3 TangentLightPos;
    vec3 TangentViewPos;
    vec3 TangentFragPos;
} vs_out;

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

uniform vec3 lightPos;
uniform vec3 viewPos;

void main()
{
    //1
    vs_out.FragPos = vec3(model * vec4(aPos, 1.0));
    vs_out.TexCoords = aTexCoords;

    //2
    mat3 normalMatrix = transpose(inverse(mat3(model)));
    //3
    vec3 T = normalize(normalMatrix * aTangent);
    vec3 N = normalize(normalMatrix * aNormal);
    T = normalize(T - dot(T, N) * N);
    vec3 B = cross(N, T);

    //4
    mat3 TBN = transpose(mat3(T, B, N));
    vs_out.TangentLightPos = TBN * lightPos;
    vs_out.TangentViewPos  = TBN * viewPos;
    vs_out.TangentFragPos  = TBN * vs_out.FragPos;

    gl_Position = projection * view * model * vec4(aPos, 1.0);
}</code></pre>
<p>천천히 보면 쉬움
1~4까지 설명해줌</p>
<ol>
<li><p>정점들에 model행렬을 곱해서 world공간으로 정점을 이동</p>
</li>
<li><p>normal벡터의 변환행렬로 Normal Matrix, 법선 행렬로 불림</p>
<ol>
<li>world공간으로 이동시켜주는 model을 이용
이동, 회전, 크기 조절을 하면 normal벡터가 왜곡됨
그걸 해결하기 위해 model을 NormalMatrix로 바꿔서 어떠한 변환이 있더라도 normal을 유지하게 해줌</li>
</ol>
</li>
<li><p>tangent, bittangent, normal을 탄젠트 공간에서 world공간으로 넘겨주기 위해 구한 NormalMatrix를 곱해줌</p>
<ol>
<li><p><code>T = normalize(T - dot(T, N) * N);</code>는 그람 슈미트 과정이라고 부름</p>
<blockquote>
<p>그람 슈미트 과정은 하나의 벡터를 이용해 직교(90도)벡터를 만드는 공식임</p>
</blockquote>
<p>T,N을 구했을때, 두 벡터는 완전한 직교상태가 아닐 수 있으므로, 이를 완벽하게 정규직교상태로 만들어주기 위해 그람 슈미트를 이용함</p>
<ol start="2">
<li><code>vec3 B = cross(N, T);</code>는 입력으로 받은 bitTangent를 사용하지 않고 있음
이유는 1번에서 T,N을 정규직교화 했기때문에, 기존의 aTangent, aNormal을 변환했을때와 약간의 차이가 있을 수 있음
따라서 우리는 새롭게 구한 T,N으로 bitTangent를 새롭게 구해줌</li>
</ol>
</li>
</ol>
</li>
</ol>
<h5 id="tbn행렬-이용법-2가지">TBN행렬 이용법 2가지</h5>
<p>여기서 4번이 조금 문제임</p>
<p>우리는 2가지 경우를 생각할 수 있음</p>
<ol>
<li>TBN행렬을 fragment shader로 보내서 normal, light, view, FragPos와 연산 후 사용</li>
<li>TBN행렬을 역행렬,전치행렬 연산을 통해 vertex shader에서 직접 빛, 뷰, 월드공간 변환된 정점좌표(FragPos)를 구하고 fragment shader로 넘기기</li>
</ol>
<p>1번이 가장 직관적임
그냥 TBN구한걸로 행렬 만들어서 넘기면 됨</p>
<h6 id="그람-슈미트-역tbn행렬">그람 슈미트, 역TBN행렬</h6>
<p>2번은 조금 복잡함</p>
<pre><code class="language-cpp">mat3 normalMatrix = transpose(inverse(mat3(model)));

vec3 T = normalize(normalMatrix * aTangent);
vec3 N = normalize(normalMatrix * aNormal);
T = normalize(T - dot(T, N) * N);
vec3 B = cross(N, T);</code></pre>
<p>우리는 벡터 T와 N을 그람 슈미트 과정을 통해 정규 직교가 되도록 만들었음
그리고 서로 수직인 두 벡터를 외적하면 외적된 벡터또한 두 벡터에 대해 수직임</p>
<p>그리고 <strong>직교기저의 역행렬 = 전치행렬</strong>임
월드공간의 TBN행렬을 다시 접선 공간으로 바꿔 계산해도 연산결과는 동일하다는거임ㄷㄷ</p>
<p>따라서 월드공간의 TBN행렬을 접선공간(전치)으로 다시 바꾼 후,
할 일이 많은 fragment shader가 아닌, vertex shader에서 미리 빛, view, FragPos를 계산해서 사용하는거임
그럼 자연스럽게 normal을 따로 TBN행렬을 이용해 연산할 필요도 없어지는겨~</p>
<table>
<thead>
<tr>
<th>1번</th>
<th>2번</th>
</tr>
</thead>
<tbody><tr>
<td>접선 공간의 벡터들을 월드 공간으로 바꿔 계산</td>
<td>월드 공간에 존재하는 TBN행렬을 접선 공간으로 바꿔서 light,view,FragPos만 먼저 계산후 빛 반사만 fragment에서 연산</td>
</tr>
<tr>
<td>fragment shader에서 연산</td>
<td>vertex shader에서 연산</td>
</tr>
</tbody></table>
<p>따라서 4번의</p>
<pre><code class="language-cpp">mat3 TBN = transpose(mat3(T, B, N));
vs_out.TangentLightPos = TBN * lightPos;
vs_out.TangentViewPos  = TBN * viewPos;
vs_out.TangentFragPos  = TBN * vs_out.FragPos;</code></pre>
<p>라고
TBN의 전치를 통해 접선공간으로 바꾼후...</p>
<blockquote>
<p>왜 접선공간인지 모르겠다면, AI에게 TBN행렬의 전치행렬이 왜 접선공간으로 바꾸는 행렬이 되는건지 물어보도록! 그냥 수식 계산이라 귀찮음...
ㅤ
단순하게 접근하면 TBN행렬은 탄젠트-&gt;월드 공간으로의 변환행렬이므로,
월드-&gt;탄젠트 공간으로 변환하려면 TBN의 역행렬을 사용하면 됨
그리고 직교기저의 전치행렬 = 역행렬이어서 연산비용이 적은 전치행렬로 같은 효과를 낼 수 있어 사용하는거
<a href="https://claude.ai/share/6e5d7957-14d5-4daf-b22b-4eb48cf6cd22">TBN행렬의 전치행렬이 왜 접선공간으로의 행렬이 되는지 알려줘</a></p>
</blockquote>
<p>light, view, FragPos를 계산해서 그걸 fragment shader로 옮긴 후 사용함
최적화도 됨!</p>
<h5 id="fragment-shader">fragment shader</h5>
<p>그냥 입력된 값들로 blinn phong을 쓰면 됨</p>
<pre><code class="language-cpp">#version 330 core
out vec4 FragColor;

in VS_OUT {
    vec3 FragPos;
    vec2 TexCoords;
    vec3 TangentLightPos;
    vec3 TangentViewPos;
    vec3 TangentFragPos;
} fs_in;

uniform sampler2D diffuseMap;
uniform sampler2D normalMap;

uniform vec3 lightPos;
uniform vec3 viewPos;

void main()
{
    // obtain normal from normal map in range [0,1]
    vec3 normal = texture(normalMap, fs_in.TexCoords).rgb;
    // normal벡터를 0-1범위에서 -1~1범위로 바꿈
    normal = normalize(normal * 2.0 - 1.0);  // 탄젠트공간

    // get diffuse color
    vec3 color = texture(diffuseMap, fs_in.TexCoords).rgb;
    // ambient
    vec3 ambient = 0.1 * color;
    // diffuse
    vec3 lightDir = normalize(fs_in.TangentLightPos - fs_in.TangentFragPos);
    float diff = max(dot(lightDir, normal), 0.0);
    vec3 diffuse = diff * color;
    // specular
    vec3 viewDir = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos);
    vec3 reflectDir = reflect(-lightDir, normal);
    vec3 halfwayDir = normalize(lightDir + viewDir);
    float spec = pow(max(dot(normal, halfwayDir), 0.0), 32.0);

    vec3 specular = vec3(0.2) * spec;
    FragColor = vec4(ambient + diffuse + specular, 1.0);
}</code></pre>
<h4 id="결과">결과</h4>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/ad93bd6d-d783-4967-a127-a053ffddda14/image.gif" alt=""></p>
<p>평면의 texture인데도 불구하고 빛의 위치, 반사가 바로바로 적용되는걸 볼 수 이뜸~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(13) - Omnidirectional Shadow Mapping]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL13-Omnidirectional-Shadow-Mapping</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL13-Omnidirectional-Shadow-Mapping</guid>
            <pubDate>Fri, 10 Jul 2026 15:03:04 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@vfx_master/Advanced-OpenGL12-Shadow-Mapping">Advanced OpenGL(12) - Directional Shadow Mapping</a></p>
<p>여기서 우리는 방향광에 대한 동적 그림자 매핑 기법을 살펴봄</p>
<p>마지막에 원근/직교 투영 그림자 매핑의 차이점을 소개했음</p>
<p>이번에 살펴볼게 바로 원근투영을 이용한 그림자매핑임</p>
<p>이러한 기술을 Omnidirectional Shadow Mapping이라고 함</p>
<h1 id="omnidirectional-shadow-mapping">Omnidirectional shadow mapping</h1>
<p>omnidirectional은 omni(모든) + directional(방향)이 합쳐져서
전방향이라는 의미를 가짐</p>
<p>이전챕터에서 살펴본
depth mapFBO를 만들고 렌더링을 통해 depth map텍스쳐를 채운 후 
그걸 이용해서 그림자를 만드는것과 비슷함</p>
<p>다만 directional shadow mapping과의 차이점은
사용하게 될 depth map이 다르다는거임</p>
<p>directional shadow mapping에서는 2D공간인 광원공간에서 depth를 체킹해서 그림자가 질 fragment의 색을 빼는식으로 구현했음</p>
<p>하지만 전방향이라는 말에서 알 수 있듯이, 특정 방향만이 아닌 360도 모든 방향에서 빛이 나와 그림자를 체킹해야하므로 일반적인 2D 광원공간에서의 depth map을 생성할수가 없다는거임</p>
<p>그럼 일반적인 2D텍스쳐가 안된다면...우리가 사용할 수 있는 텍스쳐는 뭘까?</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/0349003b-1b8b-403b-b56e-c1221fa27234/image.png" alt=""></p>
<p>전방향, 즉 상하좌우전후 6개의 면에 그림자를 만드는것과 같은 의미임</p>
<p>따라서 우리는 cubemap을 이용해서 depth map을 만들고, dynamic omnidirectional shadow mapping을 할 수 있게 되는거임ㅇㅇ</p>
<p>따라서 우리는 그림자용 depth map을 cubemap으로 생성하고 텍스쳐를 채운 후
이전챕터에서 다룬 그림자 매핑에 대한 모든걸 그대로 하면 되는거임</p>
<h1 id="depth-cubemap-예시">Depth Cubemap, 예시</h1>
<h2 id="핵심-아이디어">핵심 아이디어</h2>
<p>기본적으로 우리가 생각할 수 있는 방법이 있음</p>
<p>6개의 텍스쳐를 각각 생성하고, 
해당 텍스쳐를 각각 정점 쉐이더로 보내서 하나씩 계산한다음 합치는 방법임</p>
<p>이 방법이 좋긴한데 몇가지 문제점이 있음</p>
<ol>
<li>draw call이 많음<ul>
<li>6번의 추가적인 렌더링을 해야함</li>
</ul>
</li>
<li>cpu &lt;-&gt; gpu간의 통신횟수가 잦음<ul>
<li>결국 광원공간에서 6번 다른방향으로 depth를 계산한다는것과 같으므로, cpu의 광원공간으로 이동시키기 위한 변환행렬을 gpu로 옮겨서 계산을 수행해야함</li>
</ul>
</li>
</ol>
<p>따라서 우리가 사용할 방법은 geometry shader를 사용하는거임</p>
<p>핵심 아이디어는 아래와 같음</p>
<p><strong>Geometry shader를 통해 gpu에서 한번에 cubemap을 depth map함</strong></p>
<ol>
<li>vertex shader에서 변환처리가 끝난 월드(Scene)의 모든 오브젝트의 개별 정점 데이터(삼각형 정점 데이터)가 geometry shader로 넘어옴</li>
<li>geometry shader에서는 넘어온 오브젝트 각각의 삼각형 정점(<code>gs_in[0]</code>, <code>gs_in[1]</code>, <code>gs_in[2]</code>)들을 각각 6번 복제<ul>
<li>반복문을 통해 각 면을 선택하고, 해당 면에 알맞은 변환행렬을 정점에 곱해 광원공간으로 이동시킴</li>
<li>광원공간으로 이동된 정점을 <code>EmitVertex</code>를 통해 적용</li>
<li>면마다 반복</li>
</ul>
</li>
</ol>
<h2 id="예시">예시</h2>
<h3 id="1-cubemap-생성">1. cubemap 생성</h3>
<pre><code class="language-cpp">uint32_t depthCubemap;
glGenTextures(1, &amp;depthCubemap);
const unsigned int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubemap);
for (unsigned int i = 0; i &lt; 6; ++i)
{
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthCubemap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);</code></pre>
<p>depthCubemap을 생성함</p>
<p>cubemap의 각면에 대해 그림자 해상도를 이용해서 값 설정</p>
<p>texture parameter는 cubemap이므로 각 면이 끝나는 지점에서 끊기지 않게 GL_CLAMP_TO_EDGE사용</p>
<h3 id="2-렌더">2. 렌더</h3>
<pre><code class="language-cpp">// 1. first render to depth cubemap
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
    glClear(GL_DEPTH_BUFFER_BIT);
    ConfigureShaderAndMatrices();
    RenderScene();
glBindFramebuffer(GL_FRAMEBUFFER, 0);

// 2. then render scene as normal with shadow mapping (using depth cubemap)
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
ConfigureShaderAndMatrices();
glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubemap);
RenderScene();</code></pre>
<p>directional shadow mapping과 마찬가지로 먼저 depth map을 렌더링을 통해 매핑시키고, 화면에 출력</p>
<h3 id="3-광원공간-변환행렬">3. 광원공간 변환행렬</h3>
<p>directional과는 다르게 6면이므로, 각 면에 대해서 변환행렬을 만들어줘야함</p>
<p>그리고 직교투영이 아닌 원근투영으로  그림자가 올바르게 mapping되도록 할거임</p>
<pre><code class="language-cpp">//1
float aspect = (float)SHADOW_WIDTH/(float)SHADOW_HEIGHT;
float near = 1.0f;
float far = 25.0f;
glm::mat4 shadowProj = glm::perspective(glm::radians(90.0f), aspect, near, far);

//2
std::vector&lt;glm::mat4&gt; shadowTransforms;
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 1.0, 0.0, 0.0), glm::vec3(0.0,-1.0, 0.0));
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3(-1.0, 0.0, 0.0), glm::vec3(0.0,-1.0, 0.0));
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 0.0, 1.0, 0.0), glm::vec3(0.0, 0.0, 1.0));
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 0.0,-1.0, 0.0), glm::vec3(0.0, 0.0,-1.0));
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 0.0, 0.0, 1.0), glm::vec3(0.0,-1.0, 0.0));
shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 0.0, 0.0,-1.0), glm::vec3(0.0,-1.0, 0.0));</code></pre>
<ol>
<li><p>그림자 해상도의 비율, near/far절두체를 이용해서 
시야각이 90도인 그림자 해상도 비율을 가지는 near/far절두체를 사용하는 원근투영행렬을 만들음</p>
<ul>
<li>시야각이 90도일때, 한면을 가득 채우는 범위를 가짐</li>
</ul>
</li>
<li><p>광원공간으로 이동시키는 변환행렬 6개를 cubemap의 각 방향에 맞게 설정</p>
<ul>
<li>lookAt메서드는 eye(위치), center(중앙방향), up(위방향)을 넣어줘야함</li>
<li>lightPos에서 바라보고, light pos기준 x,-x,y,-y,z,-z방향의 cubemap center방향을 선택</li>
<li>세번째 파라미터가 중요한데, cubemap이라는건 텍스쳐를 의미함
하지만 opengl의 좌표를 이용해서 오른손 좌표계를 통해 x = 0, x = -1, z = 0, z = -1일때의 up방향을 (0,1,0)으로 해버리면, 텍스쳐 좌표에서의 up인 (0,-1,0)과 반대방향이기때문에 뒤집혀버림
따라서 (0,-1,0)으로 y축의 up방향을 만들어줘야함</li>
</ul>
</li>
</ol>
<h3 id="4-depth-map용-shaders">4. depth map용 shaders</h3>
<p>여기까지 했으면 vertex, fragment, geometry 3개의 쉐이더를 정의해줘야함
geometry가 당연히 중요하긋지?</p>
<h4 id="vertex-shader">vertex shader</h4>
<p>이제 쉐이더들을 정의해야함</p>
<p>directional과 다르게 vertex에서는 단순하게 월드공간으로 정점을 변환시켜주기만 하면 됨</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;
uniform mat4 model;
void main()
{
    gl_Position = model * vec4(aPos, 1.0);
}</code></pre>
<h4 id="geometry-shader">geometry shader</h4>
<pre><code class="language-cpp">#version 330 core
layout (triangles) in;
layout (triangle_strip, max_vertices=18) out;
uniform mat4 shadowMatrices[6];
out vec4 FragPos; // FragPos from GS (output per emitvertex)

void main()
{
    for(int face = 0; face &lt; 6; ++face)
    {
        gl_Layer = face; // built-in variable that specifies to which face we render.
        for(int i = 0; i &lt; 3; ++i) // for each triangle vertex
        {
            FragPos = gl_in[i].gl_Position;
            gl_Position = shadowMatrices[face] * FragPos;
            EmitVertex();
        }
        EndPrimitive();
    }
}</code></pre>
<ol>
<li>입력을 우리가 그릴 삼각형으로 받음</li>
<li>큐브맵 6개의 면에 입력으로 받는 삼각형을 렌더링할것이므로 최대 정점 18개로 그림</li>
<li>각면에 대해 <code>gl_Layer</code>를 반복문을 통해 face로 바꿔주면서 0~5의 큐브맵에 삼각형이 렌더링 되도록 함</li>
<li>이제 입력된 삼각형 정점을 광원공간으로 변환하고 정점을 Emit후, 해당 cubemap면에 적용시킴</li>
</ol>
<p>어렵지 않지?</p>
<p>핵심은 <code>gl_Layer</code>임!</p>
<h4 id="fragment-shader">fragment shader</h4>
<p>directional처럼 아무것도 안넘겨도 괜찮은걸까 싶지만
그렇지 않음</p>
<p>일단 directional은 직교투영이기 때문에 따로 뭐 해줄필요가 없이 자동으로 gl_FragCoords를 이용해 gl_FragDepth 값이 업데이트 되었는데</p>
<p>우리는 원근투영을 사용함
그리고 원근투영에서는 w가중치를 이용해 비선형적으로 depth를 체킹함</p>
<blockquote>
<p>비선형적?
ㅤ
카메라와 가까울때 더 높은 정밀도로 z index를 계산하고, 카메라와 멀어질수록 z index의 정밀도가 낮아지는걸 비선형적이라고 함</p>
</blockquote>
<p>하지만 비선형적이라는 말은
광원에서 물체까지의 정확한 거리(z index)가 아니게 됨</p>
<blockquote>
<p>이게 무슨뜻이고?
ㅤ
예를들어 광원에서부터 거리가 5만큼 떨어진 물체가 있다고 해보자
비선형적 z index와 선형적 z index는 다름</p>
</blockquote>
<p>따라서 우리는 비선형적으로 계산한 z index가 아닌
직접 선형적으로 z index를 계산해서 depth값으로 넣어줘야함</p>
<p>그리고 이건 어렵지 않음</p>
<pre><code class="language-cpp">#version 330 core
in vec4 FragPos;
uniform vec3 lightPos;
uniform float far_plane;

void main()
{
    // get distance between fragment and light source
    float lightDistance = length(FragPos.xyz - lightPos);

    // map to [0;1] range by dividing by far_plane
    lightDistance = lightDistance / far_plane;

    // write this as modified depth
    gl_FragDepth = lightDistance;
}</code></pre>
<ol>
<li>Fragment좌표에서 광원좌표까지의 거리를 구함</li>
<li>0<del>1사이로 값을 정하고 일정한 간격을 가지도록 하기 위해
광원좌표까지의 거리 / far절두체를 해서 선형적으로 0</del>1사이의 값을 가지도록 정규화함</li>
<li>이 값을 Depth로 사용해줌</li>
</ol>
<p>간단하지?</p>
<h3 id="5-shadow-shader">5. shadow shader</h3>
<h4 id="렌더링-준비물">렌더링 준비물</h4>
<pre><code class="language-cpp">// renders the 3D scene
// --------------------
void renderScene(const Shader &amp;shader)
{
    // room cube
    glm::mat4 model = glm::mat4(1.0f);
    model = glm::scale(model, glm::vec3(10.0f));
    shader.setMat4(&quot;model&quot;, model);
    glDisable(GL_CULL_FACE); //큐브 안쪽에서 확인할거라, 큐브의 밖이 아닌 안쪽이 렌더링 되어야함
    shader.setInt(&quot;reverse_normals&quot;, 1); //안쪽이 렌더링된다는걸 의미
    renderCube(); //여러 큐브를 감쌀 큰 큐브 1개만 만들기
    shader.setInt(&quot;reverse_normals&quot;, 0); //큰 큐브 만들었으면 다시 바깥쪽 렌더링으로 바꾸기
    glEnable(GL_CULL_FACE);//큰 큐브 만들었으면 다시 바깥쪽 렌더링으로 바꾸기
    // cubes
    model = glm::mat4(1.0f);
    model = glm::translate(model, glm::vec3(4.0f, -3.5f, 0.0));
    model = glm::scale(model, glm::vec3(0.5f));
    shader.setMat4(&quot;model&quot;, model);
    renderCube();
    model = glm::mat4(1.0f);
    model = glm::translate(model, glm::vec3(2.0f, 3.0f, 1.0));
    model = glm::scale(model, glm::vec3(0.75f));
    shader.setMat4(&quot;model&quot;, model);
    renderCube();
    model = glm::mat4(1.0f);
    model = glm::translate(model, glm::vec3(-3.0f, -1.0f, 0.0));
    model = glm::scale(model, glm::vec3(0.5f));
    shader.setMat4(&quot;model&quot;, model);
    renderCube();
    model = glm::mat4(1.0f);
    model = glm::translate(model, glm::vec3(-1.5f, 1.0f, 1.5));
    model = glm::scale(model, glm::vec3(0.5f));
    shader.setMat4(&quot;model&quot;, model);
    renderCube();
    model = glm::mat4(1.0f);
    model = glm::translate(model, glm::vec3(-1.5f, 2.0f, -3.0));
    model = glm::rotate(model, glm::radians(60.0f), glm::normalize(glm::vec3(1.0, 0.0, 1.0)));
    model = glm::scale(model, glm::vec3(0.75f));
    shader.setMat4(&quot;model&quot;, model);
    renderCube();
}

// renderCube() renders a 1x1 3D cube in NDC.
// -------------------------------------------------
unsigned int cubeVAO = 0;
unsigned int cubeVBO = 0;
void renderCube()
{
    // initialize (if necessary)
    if (cubeVAO == 0)
    {
        float vertices[] = {
            // back face
            -1.0f, -1.0f, -1.0f,  0.0f,  0.0f, -1.0f, 0.0f, 0.0f, // bottom-left
             1.0f,  1.0f, -1.0f,  0.0f,  0.0f, -1.0f, 1.0f, 1.0f, // top-right
             1.0f, -1.0f, -1.0f,  0.0f,  0.0f, -1.0f, 1.0f, 0.0f, // bottom-right         
             1.0f,  1.0f, -1.0f,  0.0f,  0.0f, -1.0f, 1.0f, 1.0f, // top-right
            -1.0f, -1.0f, -1.0f,  0.0f,  0.0f, -1.0f, 0.0f, 0.0f, // bottom-left
            -1.0f,  1.0f, -1.0f,  0.0f,  0.0f, -1.0f, 0.0f, 1.0f, // top-left
            // front face
            -1.0f, -1.0f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f, 0.0f, // bottom-left
             1.0f, -1.0f,  1.0f,  0.0f,  0.0f,  1.0f, 1.0f, 0.0f, // bottom-right
             1.0f,  1.0f,  1.0f,  0.0f,  0.0f,  1.0f, 1.0f, 1.0f, // top-right
             1.0f,  1.0f,  1.0f,  0.0f,  0.0f,  1.0f, 1.0f, 1.0f, // top-right
            -1.0f,  1.0f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f, 1.0f, // top-left
            -1.0f, -1.0f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f, 0.0f, // bottom-left
            // left face
            -1.0f,  1.0f,  1.0f, -1.0f,  0.0f,  0.0f, 1.0f, 0.0f, // top-right
            -1.0f,  1.0f, -1.0f, -1.0f,  0.0f,  0.0f, 1.0f, 1.0f, // top-left
            -1.0f, -1.0f, -1.0f, -1.0f,  0.0f,  0.0f, 0.0f, 1.0f, // bottom-left
            -1.0f, -1.0f, -1.0f, -1.0f,  0.0f,  0.0f, 0.0f, 1.0f, // bottom-left
            -1.0f, -1.0f,  1.0f, -1.0f,  0.0f,  0.0f, 0.0f, 0.0f, // bottom-right
            -1.0f,  1.0f,  1.0f, -1.0f,  0.0f,  0.0f, 1.0f, 0.0f, // top-right
            // right face
             1.0f,  1.0f,  1.0f,  1.0f,  0.0f,  0.0f, 1.0f, 0.0f, // top-left
             1.0f, -1.0f, -1.0f,  1.0f,  0.0f,  0.0f, 0.0f, 1.0f, // bottom-right
             1.0f,  1.0f, -1.0f,  1.0f,  0.0f,  0.0f, 1.0f, 1.0f, // top-right         
             1.0f, -1.0f, -1.0f,  1.0f,  0.0f,  0.0f, 0.0f, 1.0f, // bottom-right
             1.0f,  1.0f,  1.0f,  1.0f,  0.0f,  0.0f, 1.0f, 0.0f, // top-left
             1.0f, -1.0f,  1.0f,  1.0f,  0.0f,  0.0f, 0.0f, 0.0f, // bottom-left     
            // bottom face
            -1.0f, -1.0f, -1.0f,  0.0f, -1.0f,  0.0f, 0.0f, 1.0f, // top-right
             1.0f, -1.0f, -1.0f,  0.0f, -1.0f,  0.0f, 1.0f, 1.0f, // top-left
             1.0f, -1.0f,  1.0f,  0.0f, -1.0f,  0.0f, 1.0f, 0.0f, // bottom-left
             1.0f, -1.0f,  1.0f,  0.0f, -1.0f,  0.0f, 1.0f, 0.0f, // bottom-left
            -1.0f, -1.0f,  1.0f,  0.0f, -1.0f,  0.0f, 0.0f, 0.0f, // bottom-right
            -1.0f, -1.0f, -1.0f,  0.0f, -1.0f,  0.0f, 0.0f, 1.0f, // top-right
            // top face
            -1.0f,  1.0f, -1.0f,  0.0f,  1.0f,  0.0f, 0.0f, 1.0f, // top-left
             1.0f,  1.0f , 1.0f,  0.0f,  1.0f,  0.0f, 1.0f, 0.0f, // bottom-right
             1.0f,  1.0f, -1.0f,  0.0f,  1.0f,  0.0f, 1.0f, 1.0f, // top-right     
             1.0f,  1.0f,  1.0f,  0.0f,  1.0f,  0.0f, 1.0f, 0.0f, // bottom-right
            -1.0f,  1.0f, -1.0f,  0.0f,  1.0f,  0.0f, 0.0f, 1.0f, // top-left
            -1.0f,  1.0f,  1.0f,  0.0f,  1.0f,  0.0f, 0.0f, 0.0f  // bottom-left        
        };
        glGenVertexArrays(1, &amp;cubeVAO);
        glGenBuffers(1, &amp;cubeVBO);
        // fill buffer
        glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
        glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
        // link vertex attributes
        glBindVertexArray(cubeVAO);
        glEnableVertexAttribArray(0);
        glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
        glEnableVertexAttribArray(1);
        glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float)));
        glEnableVertexAttribArray(2);
        glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glBindVertexArray(0);
    }
    // render Cube
    glBindVertexArray(cubeVAO);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    glBindVertexArray(0);
}</code></pre>
<p>이런 두 렌더링용 메서드가 있음
scene을 렌더링할때 사용하게 될거임</p>
<p>render scene메서드에서 가장 최상단 큐브 1개를 먼저 만들때가 중요</p>
<p>우리는 omnidirectional 그림자가 제대로 매핑되는지 확인하기 위해 방 전체를 큐브로 감쌀거임</p>
<p>따라서 가장 큰 큐브, 우리가 들어가서 확인할 큐브는,,,,
바깥쪽에서 렌더링되는게 아닌 안쪽에서 렌더링 되어야함</p>
<p>따라서 culling face잠시 disable해서 카메라에서 보이지 않는 면에 있더라도 렌더링하도록 바꾸고,
큰 큐브를 그린다음,
culling face를 enable해서 안보이는 면은 렌더링 되지 않도록 바꿈</p>
<h4 id="vertex-shader-1">vertex shader</h4>
<p>정점들을 받아서 스크린 좌표로 이동시켜주면 됨</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;

out vec2 TexCoords;

out VS_OUT {
    vec3 FragPos;
    vec3 Normal;
    vec2 TexCoords;
} vs_out;

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

uniform bool reverse_normals;

void main()
{
    vs_out.FragPos = vec3(model * vec4(aPos, 1.0));
    if (reverse_normals) // a slight hack to make sure the outer large cube displays lighting from the &#39;inside&#39; instead of the default &#39;outside&#39;.
    {
        vs_out.Normal = transpose(inverse(mat3(model))) * (-1.0 * aNormal);
    }
    else
    {
        vs_out.Normal = transpose(inverse(mat3(model))) * aNormal;
    }

    vs_out.TexCoords = aTexCoords;
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}</code></pre>
<p>여기서 눈여겨 봐야할건 normal을 뒤집는거임
위에서 reverse normal을 넘겨서 normal을 뒤집음</p>
<p>normal을 뒤집는 이유는 빛 계산과 관련이 있음</p>
<blockquote>
<p>큰 큐브에서 normal이 그대로라면
ㅤ
<code>vec3 lightDir = normalize(lightPos - fs_in.FragPos);</code>의 계산결과인 pixel-&gt;light로 향하는 방향과 정반대이므로 
빛계산시에 dot product를 하면 그림자가 진것처럼 표현되게 됨</p>
</blockquote>
<h4 id="fragment-shader-1">fragment shader</h4>
<pre><code class="language-cpp">#version 330 core
out vec4 FragColor;

in VS_OUT {
    vec3 FragPos;
    vec3 Normal;
    vec2 TexCoords;
} fs_in;

uniform sampler2D diffuseTexture;
uniform samplerCube depthMap;

uniform vec3 lightPos;
uniform vec3 viewPos;

uniform float far_plane;

float ShadowCalculation(vec3 fragPos)
{
    // get vector between fragment position and light position
    vec3 fragToLight = fragPos - lightPos;
    // ise the fragment to light vector to sample from the depth map    
    float closestDepth = texture(depthMap, fragToLight).r;
    // it is currently in linear range between [0,1], let&#39;s re-transform it back to original depth value
    closestDepth *= far_plane;
    // now get current linear depth as the length between the fragment and light position
    float currentDepth = length(fragToLight);
    // test for shadows
    float bias = 0.05; // we use a much larger bias since depth is now in [near_plane, far_plane] range
    float shadow = currentDepth -  bias &gt; closestDepth ? 1.0 : 0.0;
    // display closestDepth as debug (to visualize depth cubemap)
    // FragColor = vec4(vec3(closestDepth / far_plane), 1.0);    

    return shadow;
}

void main()
{
    vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb;
    vec3 normal = normalize(fs_in.Normal);
    vec3 lightColor = vec3(0.3);
    // ambient
    vec3 ambient = 0.3 * lightColor;
    // diffuse
    vec3 lightDir = normalize(lightPos - fs_in.FragPos);
    float diff = max(dot(lightDir, normal), 0.0);
    vec3 diffuse = diff * lightColor;
    // specular
    vec3 viewDir = normalize(viewPos - fs_in.FragPos);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);
    spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0);
    vec3 specular = spec * lightColor;
    // calculate shadow
    float shadow = ShadowCalculation(fs_in.FragPos) ;
    vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;

    FragColor = vec4(lighting, 1.0);
}</code></pre>
<p>하나씩 천천히 보자</p>
<h5 id="shadowcalculation메서드">ShadowCalculation메서드</h5>
<pre><code class="language-cpp">float ShadowCalculation(vec3 fragPos)
{
    // get vector between fragment position and light position
    vec3 fragToLight = fragPos - lightPos;
    // ise the fragment to light vector to sample from the depth map    
    float closestDepth = texture(depthMap, fragToLight).r;
    // it is currently in linear range between [0,1], let&#39;s re-transform it back to original depth value
    closestDepth *= far_plane;
    // now get current linear depth as the length between the fragment and light position
    float currentDepth = length(fragToLight);
    // test for shadows
    float bias = 0.05; // we use a much larger bias since depth is now in [near_plane, far_plane] range
    float shadow = currentDepth -  bias &gt; closestDepth ? 1.0 : 0.0;
    // display closestDepth as debug (to visualize depth cubemap)
    // FragColor = vec4(vec3(closestDepth / far_plane), 1.0);    

    return shadow;
}</code></pre>
<ol>
<li>먼저 light에서 fragment로 향하는 방향벡터를 구함</li>
<li>depth map에서 해당 방향벡터의 좌표에 해당되는 값을 가져옴</li>
<li>가져온 depth는 0~1로 정규화되어 잇는 값이므로, far_plane을 곱해 실제 depth로 바꿔줌</li>
<li>현재 fragment의 depth는 1번에서 구한 방향벡터의 길이임</li>
<li>acne를 방지하기위해 약간의 bias(offset)을 주고, 조건에 맞춰 1혹은 0을 넘김</li>
</ol>
<p>크게 어렵지 않은 코드임 이건ㅇㅇ</p>
<h5 id="main메서드">main메서드</h5>
<pre><code class="language-cpp">void main()
{
    vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb;
    vec3 normal = normalize(fs_in.Normal);
    vec3 lightColor = vec3(0.3);
    // ambient
    vec3 ambient = 0.3 * lightColor;
    // diffuse
    vec3 lightDir = normalize(lightPos - fs_in.FragPos);
    float diff = max(dot(lightDir, normal), 0.0);
    vec3 diffuse = diff * lightColor;
    // specular
    vec3 viewDir = normalize(viewPos - fs_in.FragPos);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);
    spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0);
    vec3 specular = spec * lightColor;
    // calculate shadow
    float shadow = ShadowCalculation(fs_in.FragPos) ;
    vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;

    FragColor = vec4(lighting, 1.0);
}</code></pre>
<p>그냥 blinn phong임ㅇㅇ</p>
<h3 id="6-렌더링">6. 렌더링</h3>
<pre><code class="language-cpp">while (!glfwWindowShouldClose(window))
{
    //...

    // move light position over time
    lightPos.z = static_cast&lt;float&gt;(sin(glfwGetTime() * 0.5) * 3.0);

    // render
    // ------
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 0. create depth cubemap transformation matrices
    // -----------------------------------------------
    float near_plane = 1.0f;
    float far_plane  = 25.0f;
    glm::mat4 shadowProj = glm::perspective(glm::radians(90.0f), (float)SHADOW_WIDTH / (float)SHADOW_HEIGHT, near_plane, far_plane);
    std::vector&lt;glm::mat4&gt; shadowTransforms;
    shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 1.0f,  0.0f,  0.0f), glm::vec3(0.0f, -1.0f,  0.0f)));
    shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3(-1.0f,  0.0f,  0.0f), glm::vec3(0.0f, -1.0f,  0.0f)));
    shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 0.0f,  1.0f,  0.0f), glm::vec3(0.0f,  0.0f,  1.0f)));
    shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 0.0f, -1.0f,  0.0f), glm::vec3(0.0f,  0.0f, -1.0f)));
    shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 0.0f,  0.0f,  1.0f), glm::vec3(0.0f, -1.0f,  0.0f)));
    shadowTransforms.push_back(shadowProj * glm::lookAt(lightPos, lightPos + glm::vec3( 0.0f,  0.0f, -1.0f), glm::vec3(0.0f, -1.0f,  0.0f)));

    // 1. render scene to depth cubemap
    // --------------------------------
    glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
    glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
        glClear(GL_DEPTH_BUFFER_BIT);
        simpleDepthShader.use();
        for (unsigned int i = 0; i &lt; 6; ++i)
            simpleDepthShader.setMat4(&quot;shadowMatrices[&quot; + std::to_string(i) + &quot;]&quot;, shadowTransforms[i]);
        simpleDepthShader.setFloat(&quot;far_plane&quot;, far_plane);
        simpleDepthShader.setVec3(&quot;lightPos&quot;, lightPos);
        renderScene(simpleDepthShader);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    // 2. render scene as normal 
    // -------------------------
    glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    shader.use();
    glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
    glm::mat4 view = camera.GetViewMatrix();
    shader.setMat4(&quot;projection&quot;, projection);
    shader.setMat4(&quot;view&quot;, view);
    // set lighting uniforms
    shader.setVec3(&quot;lightPos&quot;, lightPos);
    shader.setVec3(&quot;viewPos&quot;, camera.Position);
    shader.setFloat(&quot;far_plane&quot;, far_plane);
    glActiveTexture(GL_TEXTURE0);
    glBi![](https://velog.velcdn.com/images/vfx_master/post/9fd5529a-4d6e-4380-bdb6-0bf1aa6fefb2/image.gif)
ndTexture(GL_TEXTURE_2D, woodTexture);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubemap);
    renderScene(shader);

    //...
}</code></pre>
<p>천천히 보면
다 알만한 내용들임</p>
<p>그럼 결과는,.....?</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/f213f1b8-50c1-45f9-8fa9-7a3f3da1fe4c/image.gif" alt=""></p>
<h1 id="pcf">PCF</h1>
<p>한번 여기에서도 PCF를 적용해보자</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/888c851d-4868-48ea-ae12-148399c36a3e/image.png" alt=""></p>
<pre><code class="language-cpp">//1
float shadow  = 0.0;
float bias    = 0.05;
float samples = 4.0;
float offset  = 0.1;

//2
for(float x = -offset; x &lt; offset; x += offset / (samples * 0.5))
{
    for(float y = -offset; y &lt; offset; y += offset / (samples * 0.5))
    {
        for(float z = -offset; z &lt; offset; z += offset / (samples * 0.5))
        {
            float closestDepth = texture(depthMap, fragToLight + vec3(x, y, z)).r;
            closestDepth *= far_plane;
            if(currentDepth - bias &gt; closestDepth)
                shadow += 1.0;
        }
    }
}

//3
shadow /= (samples * samples * samples);</code></pre>
<p>3차원이므로 x,y,z방향으로 그림자 샘플링 할거임</p>
<ol>
<li>samples수만큼의 가로,높이,깊이를 샘플링함</li>
<li>-offset~offset까지 샘플수만큼 샘플링하면서 계산
light에서 fragment방향인 벡터에 3차원 샘플좌표의 depth를 구함
조건에 따라서 shadow에 1을 더함</li>
<li>최대 샘플수는 가로 * 높이 * 깊이 이므로 64를 1씩 더한 shadow에서 나눠줌</li>
</ol>
<h2 id="pcf개선">PCF개선</h2>
<p>현재 64개의 샘플을 샘플링중임
프레임 한번에 최소 64번의 계산이 필요하다는거니까...
따라서 샘플링 횟수를 조금 줄이는게 좋음
그렇다고 샘플이 2개면 여전히 텍셀단위로 그림자가 출력됨ㅇㅇㅇ</p>
<p>즉, 샘플수를 줄이면서 PCF는 유지되도록 해야함</p>
<p>사실 우리는 한 정점에서 x,y,z축으로 각각 -1, 0, 1의 방향에 있는 면과 공간에 대해서 PCF를 적용하면 됨</p>
<p>따라서 3개의 축에 3가지 경우의 수니 $3^3 = 27$개 방향으로만 샘플링하면 됨!</p>
<blockquote>
<p>은 무슨!!!!!!!
ㅤ
일단 1개는 확실히 빠져야함
0벡터 ㅇㅇ
ㅤ
fragToLight + 0벡터 = fragToLight임
근데 자기자신을 샘플링에 포함시키면 가중치가 자기자신 픽셀이 쏠리게 됨
ㅤ
그리고 추가로 빠져야할 벡터들이 있음
?바로 축방향임
(0,0,1), (0,0,-1), (0,1,0)...이런 방향임
이런 축방향을 추가하면 여러 픽셀에서 해당 축방향으로 그림자가 계속 중첩됨
즉, 더 높은 밀도를 가지게 된다는거임</p>
<blockquote>
<p>ㅤ잘생각해보면 인접한 2개의 픽셀이 특정 축방향벡터에 그림자 계산을 하게되면, 인접한 2개의 픽셀의 그림자가 뚜렷해지게 보이게 될거임ㅇㅇ</p>
</blockquote>
<p>따라서 이런 축방향도 없애줘야함</p>
</blockquote>
<p>사실 위 인용문구처럼 0벡터, 축방향벡터를 없앤다는게 기하학적 의미가 있음</p>
<blockquote>
<p>특정 픽셀에서 모든 모서리와 모든 edge(변)에만 샘플링에 추가된다는거임
즉, 모든 위치에서 동일하게 샘플링 할 수 있다는 의미임</p>
</blockquote>
<p>그 벡터들의 집합은 아래와 같음</p>
<pre><code class="language-cpp">vec3 sampleOffsetDirections[20] = vec3[]
(
   vec3( 1,  1,  1), vec3( 1, -1,  1), vec3(-1, -1,  1), vec3(-1,  1,  1),
   vec3( 1,  1, -1), vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1,  1, -1),
   vec3( 1,  1,  0), vec3( 1, -1,  0), vec3(-1, -1,  0), vec3(-1,  1,  0),
   vec3( 1,  0,  1), vec3(-1,  0,  1), vec3( 1,  0, -1), vec3(-1,  0, -1),
   vec3( 0,  1,  1), vec3( 0, -1,  1), vec3( 0, -1, -1), vec3( 0,  1, -1)
);</code></pre>
<p>따라서 우린 이걸로 샘플링을 하면됨
귀찮게 offset, sample, 반복문사용하지 말구 ㅇㅇ</p>
<pre><code class="language-cpp">float shadow = 0.0;
float bias   = 0.15;
int samples  = 20;
float diskRadius = 0.05;
//float viewDistance = length(viewPos - fragPos);
//float diskRadius = (1.0 + (viewDistance / far_plane)) / 25.0;
for(int i = 0; i &lt; samples; ++i)
{
    float closestDepth = texture(depthMap, fragToLight + sampleOffsetDirections[i] * diskRadius).r;
    closestDepth *= far_plane;   // 0~1 매핑 복구
    if(currentDepth - bias &gt; closestDepth)
        shadow += 1.0;
}
shadow /= float(samples);

// display closestDepth as debug (to visualize depth cubemap)
// FragColor = vec4(vec3(closestDepth / far_plane), 1.0);    

return shadow;</code></pre>
<p>중요한 부분은 <code>float closestDepth = texture(depthMap, fragToLight + sampleOffsetDirections[i] * diskRadius).r;</code>임</p>
<p>diskRadius를 어떻게 잡느냐에 따라 그림자의 흐릿함정도가 커지거나 줄어들음</p>
<p>만약 diskRadius가 1이라면...
우리가 설정한 모든 샘플방향 크기만큼 샘플링이 되므로 흐릿함이 엄청 커질거고
만약 diskRadius가 0이라면...
우리가 설정한 모든 샘플방향이 무시되므로, 텍셀이 그대로 보이게 될거임ㅇㅇ</p>
<blockquote>
</blockquote>
<pre><code class="language-cpp">float viewDistance = length(viewPos - fragPos);
float diskRadius = (1.0 + (viewDistance / far_plane)) / 25.0;</code></pre>
<blockquote>
<p>이건 비선형적으로 diskRadius를 변화시킨거임
가까운 거리일수록 높은 정밀도의 그림자를 가지고,
먼 거리일수록 낮은 정밀도의 그림자를 가지도록 하는거임ㅇㅇ</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/35d2c848-4523-432d-99de-f74518e8f4f1/image.png" alt=""></p>
<p>잘 적용되었다<del>~</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(12) - Directional Shadow Mapping]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL12-Shadow-Mapping</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL12-Shadow-Mapping</guid>
            <pubDate>Thu, 09 Jul 2026 15:36:56 GMT</pubDate>
            <description><![CDATA[<p>그림자를 만드는법임
이제 좀 어려운거 나온다 이거이거 클났노</p>
<p>그림자를 다루는 알고리즘은 있긴한데
real time rendering에서 잘 사용되지는 않음</p>
<p>그래서 사용하는게 shadow mapping리는 테크닉임</p>
<p>shadow mapping이라는 테크닉을 사용하면 
더 고급기술인 omnidirectional shadow mapping(전방향 그림자 매핑), cascade shadow mapping이라는 그림자 매핑 기술로 확장가능함</p>
<h1 id="shadow-mapping">shadow mapping</h1>
<p>아이디어 진짜 간단함</p>
<p>빛(광원)에서 빛이 쏴진 후, 해당 빛이 닿는 지점의 뒷부분에 있는 곳에는 모두 그림자가 생기도록 하면 됨</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/cfcfc59f-5304-4c7d-8cb5-e0542da54e91/image.png" alt=""></p>
<p>이런거임ㅇㅇ</p>
<p>근데 광원에서 닿을 수 있는 모든곳에 ray를 쏴서 닿는 지점을 판단하고
그 뒷부분에 있는곳은 따로 저장해서 그림자가 생기도록 한다는건
정확도는 높을지언정 너무 무겁고 비효율적이게 됨</p>
<p>우리가 사용할건 <strong>깊이</strong>임</p>
<p>깊이 테스트에서 우리는 화면에 출력될 fragment를 어떻게 결정짓는지 공부했음</p>
<p>0-1사이의 정규화된 깊이값(z index가 낮을수록 더 높은 정밀도를 가지는 비선형적 깊이테스팅)이 존재하고, 
더 높은 우선순위를 가지는 fragment를 출력하는거임</p>
<p>그럼 shadow mapping은 어떻게 이걸 사용하냐?</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/56eb0cc3-96bd-4eba-9dd9-f11ef2e4e482/image.png" alt="">
위 사진을 보면서 아래를 읽어보자</p>
<ol>
<li>광원에서의 깊이 테스팅 결과를 depth map, shadow map이라고 부르는 텍스쳐에 저장함</li>
<li>우리 화면에 출력되는 fragment의 특정 점을 $P$라고 부름</li>
<li>사용자 시야~점$P$의 방향을 transform을 이용해 광원 좌표로 이동시킴<ul>
<li>광원에서 점$P$까지의 벡터로 바뀜</li>
</ul>
</li>
<li>depth map을 이용해 해당 벡터에서 z index가 더 높은게 있다면 점$P$에 해당되는 fragment는 그림자가 생김</li>
</ol>
<h2 id="depth-map을-이용해-그림자-계산-예시">depth map을 이용해 그림자 계산 예시</h2>
<p>depth map/shadow map이라는 텍스쳐를 사용해서 화면에 출력해야함
그럼 커스텀 프레임버퍼를 만들어서 거기에 붙여서 약간 후처리 느낌으로 동작하게 해야함</p>
<h3 id="1-depth-mapdepth-map-fbo-만들기">1. depth map/depth map fbo 만들기</h3>
<pre><code class="language-cpp">const unsigned int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
unsigned int depthMapFBO;
glGenFramebuffers(1, &amp;depthMapFBO);
// create depth texture
unsigned int depthMap;
glGenTextures(1, &amp;depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// attach depth texture as FBO&#39;s depth buffer
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);</code></pre>
<ol>
<li>depth map전용 그림자 해상도를 설정</li>
<li>depth map 부착용 프레임버퍼 생성</li>
<li>depth map텍스쳐 생성후 depth map 부착용 프레임버퍼에 부착</li>
<li>단순히 깊이버퍼 정보만 필요하므로 컬러버퍼용 읽기/그리기는 모두 <code>GL_NONE</code>으로 비활성화(색상 렌더링 안함)</li>
</ol>
<p>그리고 이제 fragment를 특정 공간으로 이동시켜 
depth map을 통해 색상을 결정지어야함</p>
<h3 id="2-빛-크기-방향-계산">2. 빛 크기, 방향 계산</h3>
<pre><code class="language-cpp">glm::vec3 lightPos = glm::vec3 lightPos(-2.0f, 4.0f, -1.0f);

//1
float near_plane = 1.0f, far_plane = 7.5f;
glm::mat4 lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane);

//2
glm::mat4 lightView = glm::lookAt(glm::vec3(-2.0f, 4.0f, -1.0f), 
                                  glm::vec3( 0.0f, 0.0f,  0.0f), 
                                  glm::vec3( 0.0f, 1.0f,  0.0f));

//3
glm::mat4 lightSpaceMatrix = lightProjection * lightView;</code></pre>
<ol>
<li>원근투영으로 그림자를 위한 depth를 계산하면 왜곡이 생김
따라서 직교 투영으로 모두 원본 크기에서의 그림자용 depth를 계산할거임<ul>
<li>일정한 near와 far frustum을 이용해 직교투영</li>
<li>빛의 닿는 영역을 정의</li>
</ul>
</li>
<li>lookAt을 이용해 빛이 바라보는 방향을 정의함</li>
<li>1번과 2번행렬의 곱을 이용해 특정 world좌표를 빛이 닿는 영역내부로 clipping해주는 광원용 clip공간 변환행렬로 만들어줌<ul>
<li>사실 쉐이더로 그냥 넘겨도 되는데, 저 값은 <strong>일단</strong> 변하지 않는 값이라, fragment계산시에 부하 줄이기 위해서 cpu에서 연산 후 넘김</li>
</ul>
</li>
</ol>
<h3 id="3-depth-map용-쉐이더">3. depth map용 쉐이더</h3>
<p>vertex shader</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;

uniform mat4 lightSpaceMatrix;
uniform mat4 model;

void main()
{
    gl_Position = lightSpaceMatrix * model * vec4(aPos, 1.0);
}</code></pre>
<p>단순하게 모든 정점을 우리가 정의한 
빛 절두체, 빛 방향인 광원 공간으로 이동시킴</p>
<p>fragment shader</p>
<pre><code class="language-cpp">#version 330 core

void main()
{
    // gl_FragDepth = gl_FragCoord.z;
}</code></pre>
<p>해당 정점들의 z index를 정의해주면 되는데,
사실 자동으로 적용되어서 크게 의미없는 코드뭉치</p>
<h3 id="4-depth-map-적용">4. depth map 적용</h3>
<pre><code class="language-cpp">// 1. 먼저 깊이 맵으로 렌더링
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
    glClear(GL_DEPTH_BUFFER_BIT);
    ConfigureShaderAndMatrices();
    RenderScene();
glBindFramebuffer(GL_FRAMEBUFFER, 0);</code></pre>
<ol>
<li>glViewPort로 그림자 해상도를 먼저 설정해줌</li>
<li>그림자 맵 FBO바인딩</li>
<li>기본적으로 depth를 이용해서 그림자 map을 정의하므로 매프레임 depth 초기화</li>
<li><code>ConfigureShaderAndMatrices</code>는 임의의 쉐이더 사용, uniform, 값 설정 로직이 들어가면 됨</li>
<li>원하는 씬을 렌더링 해주면 자동적으로 depth map FBO에 깊이값이 저장될거임</li>
</ol>
<h3 id="5-텍스쳐-부착을-통해-전체-화면quad-렌더링">5. 텍스쳐 부착을 통해 전체 화면(quad) 렌더링</h3>
<h4 id="vertex-shader">vertex shader</h4>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

void main()
{
    TexCoords = aTexCoords;
    gl_Position = vec4(aPos, 1.0);
}</code></pre>
<p>depth map렌더링이 끝난 텍스쳐 부착해야하니 설정</p>
<h4 id="fragment-shader">fragment shader</h4>
<pre><code class="language-cpp">#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D depthMap;
uniform float near_plane;
uniform float far_plane;

// required when using a perspective projection matrix
float LinearizeDepth(float depth)
{
    float z = depth * 2.0 - 1.0; // Back to NDC 
    return (2.0 * near_plane * far_plane) / (far_plane + near_plane - z * (far_plane - near_plane));
}

void main()
{
    float depthValue = texture(depthMap, TexCoords).r;
    // FragColor = vec4(vec3(LinearizeDepth(depthValue) / far_plane), 1.0); // perspective
    FragColor = vec4(vec3(depthValue), 1.0); // orthographic
}</code></pre>
<p>일단은 간단하게 <code>texture()</code>를 이용해서 렌더링되어 texture 부착을 위해 들어온 depth map을 이용해 TexCoord좌표로 매핑한 후, r값만을 이용해 색상을 결정지을거임</p>
<h4 id="디버그용-렌더로직">디버그용 렌더로직</h4>
<pre><code class="language-cpp">glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// render Depth map to quad for visual debugging
// ---------------------------------------------
ConfigureShaderAndMatrices();
renderQuad();</code></pre>
<p>그림자 해상도에서 스크린해상도로 바꿔준후</p>
<p>출력되는건 color와 depth니까 두개 프레임마다 초기화</p>
<p>shader에 들어갈 변수들 설정해주고
quad를 렌더하면....</p>
<h3 id="6-중간-결과">6. 중간 결과</h3>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/e90d2dcc-3a62-44b6-9caa-d563c948223c/image.png" alt=""></p>
<p>일단 뭔가 까만거하고 머시기 출력되긴함!</p>
<h3 id="7-그림자-렌더링">7. 그림자 렌더링</h3>
<p>이제 위에서 계산한 depth map으로 그림자를 렌더링해볼 차례임</p>
<p>쉐이더 추가하고 값만 넣어주면 됨</p>
<h4 id="vertex-shader-1">vertex shader</h4>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;

out vec2 TexCoords;

out VS_OUT {
    vec3 FragPos;
    vec3 Normal;
    vec2 TexCoords;
    vec4 FragPosLightSpace;
} vs_out;

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

void main()
{
    vs_out.FragPos = vec3(model * vec4(aPos, 1.0));
    vs_out.Normal = transpose(inverse(mat3(model))) * aNormal;
    vs_out.TexCoords = aTexCoords;
    vs_out.FragPosLightSpace = lightSpaceMatrix * vec4(vs_out.FragPos, 1.0);
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}</code></pre>
<p>뭔가 복잡해보이는데 걱정하지 말자</p>
<p>일단 핵심 아이디어는 광원 공간과 실제 오브젝트 정점의 공간을 각각 계산해서 fragment shader로 넘기는거임</p>
<ol>
<li>VS_OUT에서 눈여겨 봐야할건 FragPosLightSpace임
아까 위 3번에서 depth map용 쉐이더 vertex shader쪽에서 광원공간으로 이동시키는걸 설명했음
그냥 그림자 계산용인거임ㅇㅇㅇ</li>
<li>그 외 VS_OUT의 변수들은 blinn phong모델을 적용하기 위해 필요한 준비물임</li>
<li>오브젝트 정점들의 공간을 먼저 광원공간으로 이동시켜서 그림자 계산에 이용</li>
<li>그림자 용이 아닌 오브젝트의 정점은 스크린공간으로 따로 이동</li>
</ol>
<h4 id="fragment-shader-1">fragment shader</h4>
<p>이제 fragment shader를 살펴보자
해줘야하는건 그림자 계산 + original fragment색상 계산임</p>
<pre><code class="language-cpp">#version 330 core
out vec4 FragColor;

in VS_OUT {
    vec3 FragPos;
    vec3 Normal;
    vec2 TexCoords;
    vec4 FragPosLightSpace;
} fs_in;

uniform sampler2D diffuseTexture;
uniform sampler2D shadowMap;

uniform vec3 lightPos;
uniform vec3 viewPos;

float ShadowCalculation(vec4 fragPosLightSpace)
{
    // perform perspective divide
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    // transform to [0,1] range
    projCoords = projCoords * 0.5 + 0.5;
    // get closest depth value from light&#39;s perspective (using [0,1] range fragPosLight as coords)
    float closestDepth = texture(shadowMap, projCoords.xy).r;
    // get depth of current fragment from light&#39;s perspective
    float currentDepth = projCoords.z;
    // check whether current frag pos is in shadow
    float shadow = currentDepth &gt; closestDepth  ? 1.0 : 0.0;

    return shadow;
}

void main()
{
    vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb;
    vec3 normal = normalize(fs_in.Normal);
    vec3 lightColor = vec3(0.3);
    // ambient
    vec3 ambient = 0.3 * lightColor;
    // diffuse
    vec3 lightDir = normalize(lightPos - fs_in.FragPos);
    float diff = max(dot(lightDir, normal), 0.0);
    vec3 diffuse = diff * lightColor;
    // specular
    vec3 viewDir = normalize(viewPos - fs_in.FragPos);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);
    spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0);
    vec3 specular = spec * lightColor;

    // calculate shadow
    float shadow = ShadowCalculation(fs_in.FragPosLightSpace);
    vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;

    FragColor = vec4(lighting, 1.0);
}</code></pre>
<p>main의 <code>//calculate shadow</code>주석 전까지는 전형적인 블린 퐁 모델 구현임
색상 계산은 아니고, 색상의 ambient, diffuse, specular의 강도를 구한거임</p>
<p>이제 <code>ShadowCalculate</code>를 통해 계산한 그림자색상과 합쳐서 색상을 결정지을거임</p>
<h5 id="shadowcalculate메서드">ShadowCalculate메서드</h5>
<pre><code class="language-cpp">float ShadowCalculation(vec4 fragPosLightSpace)
{
    // perform perspective divide
    vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w;
    // transform to [0,1] range
    projCoords = projCoords * 0.5 + 0.5;
    // get closest depth value from light&#39;s perspective (using [0,1] range fragPosLight as coords)
    float closestDepth = texture(shadowMap, projCoords.xy).r;
    // get depth of current fragment from light&#39;s perspective
    float currentDepth = projCoords.z;
    // check whether current frag pos is in shadow
    float shadow = currentDepth &gt; closestDepth  ? 1.0 : 0.0;

    return shadow;
}</code></pre>
<p>천천히 봐보자
무서울거 없음</p>
<ol>
<li>FragPosLightSpace는 정점들이 우리가 정의한 광원공간으로 변환된 좌표임</li>
<li>FragPosLightSpace의 xyz를 w로 나누어서 원근가중치를 줌</li>
<li>2번의 결과는 -1<del>1사이의 값이므로, 0</del>1사이의 읽을수있는 좌표로 이동</li>
<li>3번의 정규화된 좌표로 이동된 정점좌표를 이용해 uniform으로 입력받은 depth map을 매핑해서 텍스쳐로 사용<ul>
<li>이렇게 해서 나온 텍스쳐는 화면에 그려지는 텍스쳐이므로 화면에 표현되는, 즉, 가장 가까운 좌표를 의미하므로 해당 텍스쳐의 r값을 이용해 depth를 가져옴(어짜피 xyz 똑같음, 흑백이미지라 ㅇㅇ)</li>
</ul>
</li>
<li>2번 원근가중치가 섞인 정점좌표의 z축과 비교</li>
<li>4번이 크면 0, 5번이 크면 1을 반환</li>
</ol>
<p>이제 왜 1, 0을 반환하는지는 밑의 로직을 봐보자</p>
<h5 id="그림자-계산-완성">그림자 계산 완성</h5>
<pre><code class="language-cpp">// calculate shadow
float shadow = ShadowCalculation(fs_in.FragPosLightSpace);
vec3 lighting = (ambient + (1.0 - shadow) * (diffuse + specular)) * color;</code></pre>
<p>만약 현재 좌표가 광원 공간 텍스쳐에 가려진 depth를 가지고 있다면 1이 반환될거임
그럼 무슨 color를 곱해도 ambient만 남아서 주변광만 남음</p>
<p>만약 현재 좌표가 광원 공간 텍스쳐에 표시되는 depth를 가지고 있으면 0이 반환될거임
그럼 무슨 color를 곱해도 color값이 블린 퐁 로직을 통해 도출됨</p>
<h4 id="렌더-로직">렌더 로직</h4>
<pre><code class="language-cpp">// 1. depth map 생성 후 텍스쳐 채우기 변환행렬
// --------------------------------------------------------------
glm::mat4 lightProjection, lightView;
glm::mat4 lightSpaceMatrix;

float near_plane = 1.0f, far_plane = 7.5f;

lightProjection = glm::ortho(-10.0f, 10.0f, -10.0f, 10.0f, near_plane, far_plane);
lightView = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0.0, 1.0, 0.0));

lightSpaceMatrix = lightProjection * lightView;

simpleDepthShader.use();
simpleDepthShader.setMat4(&quot;lightSpaceMatrix&quot;, lightSpaceMatrix);

//광원공간에서 렌더링해서 depth map텍스쳐 채우기
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, woodTexture);
renderScene(simpleDepthShader);
glBindFramebuffer(GL_FRAMEBUFFER, 0);

//뷰포트 스크린좌표로 바꾸기
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 2. 광원공간이 아닌 일반 공간에서 렌더링
// --------------------------------------------------------------
shadowShader.use();
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();
shadowShader.setMat4(&quot;projection&quot;, projection);
shadowShader.setMat4(&quot;view&quot;, view);
// set light uniforms
shadowShader.setVec3(&quot;viewPos&quot;, camera.Position);
shadowShader.setVec3(&quot;lightPos&quot;, lightPos);
shadowShader.setMat4(&quot;lightSpaceMatrix&quot;, lightSpaceMatrix);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, woodTexture);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, depthMap);
renderScene(shadowShader);</code></pre>
<p>위에서 사용한 debugQuad는 잠깐 꺼두자</p>
<h3 id="8-최종-결과">8. 최종 결과</h3>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/bdd2bd21-e320-4526-ae70-b97b41ae80e9/image.png" alt=""></p>
<p>좋다</p>
<h1 id="그림자-에러-수정">그림자 에러 수정</h1>
<h2 id="shadow-acne">Shadow Acne</h2>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/643c5067-2521-48a6-bb65-ccd28caff6b7/image.png" alt=""></p>
<p>가끔 인터넷에 올라온 사진 줌아웃 하거나, 사진 컴퓨터로 업로드해서 보면
저런 자글자글?한 이상한 효과가 생기는걸 볼 수 있음
저런 무늬를 Moiré라고 부름</p>
<p>그래픽스에서 그림자에 생긴 저런 무늬를 shadow acne라고 부름</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/10dd2d52-4465-4705-bf9d-a437fd759bf5/image.png" alt="">
가까이서보면 일정한 무늬가 연속해서 나타나고 있다는걸 알 수 있음</p>
<p>이게 이유가 간단함</p>
<p>우리는 depth map이라는 텍스쳐를 이용해 우리는 그림자를 계산중임
이게 정면으로 빛을 받을때는 문제가 없는데,
빛이 비스듬하게 들어오면 문제가 생김</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/77e54118-ab3c-4f45-a902-c5fd5f869366/image.png" alt=""></p>
<p>depth map은 3차원 연속적인 공간을 텍셀단위로 저장함
위 그림을 보면 비스듬한 빛이 오게 되면, 텍셀단위로 끊어서 그림자를 계산하므로, 이전 텍셀이 다음 텍셀을 조금이라도 가린다면 그림자가 생기는거임</p>
<p>만약 그림자의 해상도가 굉<del>~</del>장히 높다면 문제가 없을수도 있음
하지만 그렇게 되면 연산량도 많아짐</p>
<p>이걸 해결하는건 간단한데, 약간의 offset을 이용하면 됨</p>
<pre><code class="language-cpp">float offset = 0.005;
float shadow = currentDepth - offset &gt; closestDepth  ? 1.0 : 0.0;</code></pre>
<p>이렇게 offset을 사용해 해당범위가 포함되면 shadow를 계산하도록 하면 됨</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/a9ec564a-f2af-42c6-9b84-c5bd467dcc80/image.png" alt=""></p>
<p>이렇게 shadow acne가 사라진걸 볼 수 있음</p>
<p>다만 이방법도 문제가 있음</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/36a62f9f-8704-4b06-8dca-839b5d4c8e50/image.png" alt=""></p>
<p>이렇게 빛이 매~~우 비스듬한 각도에서 도착하면 여전히 shadow acne가 생기게 됨</p>
<p>따라서 offset을 계산하는 로직을 수정해야됨</p>
<pre><code class="language-cpp">vec3 lightDir = normalize(lightPos - fs_in.FragPos);
float offset = max(0.5 * (1.0 - dot(lightDir, fs_in.Normal)), 0.005);</code></pre>
<p>이렇게 normal과 수직인 각도로 빛이 오면 내적의 결과가 1에 가까워지므로,
0.005라는 최저값을 가지고, 
그게 아니라면 적절한 값을 가지도록 함</p>
<p>0.5와 0.005는 특수 offset으로 적절히 shadow acne가 안생기도록 조절하면 됨</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/21810505-e400-44d7-ad43-7c30c9de2415/image.png" alt=""></p>
<p>해결완료~~</p>
<h2 id="peter-panning">peter panning</h2>
<p>조명과 offset을 잘 조절해보면</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/06d8d5a8-9004-432f-8155-f18f26b3e0e5/image.png" alt=""></p>
<p>정면의 정육면체는 땅에 붙어있음에도 공중에 뜬것처럼 제대로 그림자가 렌더링되지 않고 있는 모습을 볼 수 잇음</p>
<p>이걸 peter panning이라고 함</p>
<p>이게 생기는 이유는 우리가 위에서 offset을 이용해서 그림자의 위치가 어긋나 보이는거임</p>
<p>해결법은 간단함
depth map을 렌더링하는 동안에 front face culling을 통해 앞면을 없애버리면 됨
기본적으로 OpenGL은 back face culling임
<img src="https://velog.velcdn.com/images/vfx_master/post/827db554-6cde-4ae2-acd3-b9130739a711/image.png" alt="">
이렇게 적용되도록 꼼수부리는건데</p>
<pre><code class="language-cpp">glCullFace(GL_FRONT);
RenderSceneToDepthMap();
glCullFace(GL_BACK); // 원래의 컬링 면으로 되돌리는 것을 잊지 마세요</code></pre>
<p>사실 과도한 bias가 문제라 어떤 방식으로 peter panning을 해결해도 같은 문제가 나타날 수 잇음</p>
<p>따라서 bias를 적절하게 조절하면 됨</p>
<h2 id="over-sampling">over sampling</h2>
<h3 id="해결법-1-textureparameter-수정">해결법 1. TextureParameter 수정</h3>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/11ea4f78-eec8-477f-949e-d28898bef072/image.png" alt=""></p>
<p>우리는 절두체로 빛이 닿는 곳과 닿지 않는 곳이 구분되고 있음
어두운 부분이 절두체가 끊기는 부분인데,
너무 차이가 심함</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_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);</code></pre>
<p>이유는 우리가 GL_REPEAT으로 설정되어있기때문임</p>
<blockquote>
<p>만약 텍스쳐좌표가 1.01이면, GL_REPEAT일때 0.01로 매핑되게 됨
그로인해 그림자처럼 어두워지는거임</p>
</blockquote>
<p>따라서 우리는 parameter옵션을 0-1사이로 가두는 GL_CLAMP_TO_BORDER를 사용하고
보더의 색상은 흰색으로 하여 기본 fragment색상에 영향이 없도록 설정하면 됨</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_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
float borderColor[] = {1.0, 1.0, 1.0, 1.0}; 
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);</code></pre>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/21ba6586-0d8d-4ec9-be62-22f3a89d2ec1/image.png" alt=""></p>
<p>하지만 여전히 문제가 남아있는것을 볼 수 있음</p>
<p>이 방법은 depth map이 0~1사이가 아닐때 적용되는 해결법임</p>
<h3 id="해결법-2-절두체-밖-z-index에-따라-shadow-조절">해결법 2. 절두체 밖 z index에 따라 shadow 조절</h3>
<p>우리가 정의한 광원공간에서 정점의 z좌표가 음수이거나 1을 초과했다는건
우리가 정의한 near, far절두체 범위 사이에 속하지 않는다는거임</p>
<p>근데 near절두체보다 더 가까이 있는 물체는 이미
<code>projCoords = projCoords * 0.5 + 0.5;</code>라는 정규화과정에서 음수가 되어</p>
<p><code>float shadow = currentDepth - offset &gt; closestDepth  ? 1.0 : 0.0;</code>여기서 무조건 false가 되어 0.0이 되게 됨</p>
<p>따라서 우리는 far절두체 바깥쪽인, <code>projCoords.z</code>가 1을 초과하는 경우에 대해서 shadow계산을 안하도록 하면 됨</p>
<pre><code class="language-cpp">if(projCoords.z &gt; 1.0)
    shadow = 0.0;</code></pre>
<p>이런 코드만 추가해주면 됨</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/562abe32-8772-45b2-a913-6461aeb4fa57/image.png" alt=""></p>
<p>그럼 이렇게 정확한 그림자가 렌더링 되게 됨</p>
<h2 id="pcf">PCF</h2>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/779931ce-d4d8-424d-bbd4-68821ecbb064/image.png" alt=""></p>
<p>그림자의 해상도가 고정되어 있어서
여러 fragment에 그림자 계산이 더해져 톱니모양이 부각되는 현상이 있음</p>
<p>그럼 AA적용하면 되겠네?</p>
<p>ㅇㅇ그게 바로 PCF, percentage closer filtering이라는 기법임</p>
<p>PCF는 그림자에서만 적용되지 않고, 
거칠거나 딱딱한 부분을 뭉그러트리는 기법을 포괄해서 말하는거임</p>
<p>로직은 간단함</p>
<pre><code class="language-cpp">//1
float shadow = 0.0;
vec2 texelSize = 1.0 / textureSize(shadowMap, 0);
//2
for(int x = -5; x &lt;= 5; ++x)
{
    for(int y = -5; y &lt;= 5; ++y)
    {
        //3
        float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r; 
        shadow += currentDepth - bias &gt; pcfDepth ? 1.0 : 0.0;        
    }    
}
//4
shadow /= 121.0;</code></pre>
<ol>
<li>textureSize메서드를 이용하면 우리가 사용하는 텍스쳐의 x,y를 알 수 있음
1/textureSize를 통해 텍셀 1개당 간격을 구함</li>
<li>텍셀 1개에 대하여 텍셀포함 가로세로 총 121개의 텍셀에 대해 3번을 수행함</li>
<li>해당 121개의 텍셀 좌표에 대해 빛을 받으면 0, 아니면 1을 반환</li>
<li>121개의 텍셀 좌표의 색상이 합쳐졌으므로 121로 나누어 해당 텍셀의 평균 그림자 세기를 구함</li>
</ol>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/4083ae85-ac2f-4484-8c7c-ac60fb67f6ee/image.png" alt=""></p>
<p>그럼 이렇게 그림자가 뭉개져서 나옴
자세히 보면 약간 각져있긴한데 이정도도 충분한 편이긴함</p>
<p>다시 차이를 보면</p>
<table>
<thead>
<tr>
<th><img src="https://velog.velcdn.com/images/vfx_master/post/779931ce-d4d8-424d-bbd4-68821ecbb064/image.png" alt="">PCF 적용전</th>
<th><img src="https://velog.velcdn.com/images/vfx_master/post/4083ae85-ac2f-4484-8c7c-ac60fb67f6ee/image.png" alt="">PCF 적용후</th>
</tr>
</thead>
</table>
<p>PCF는 단순히 이런용도뿐만이 아니긴함
그리고 그림자를 뭉개는것에도 PCF만 있는것도 아님
하지만 그건 매우 고급<del>스끼리</del></p>
<h1 id="방향광에-대해-직교-원근-투영의-그림자-차이">방향광에 대해 직교, 원근 투영의 그림자 차이</h1>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/ebbdfd11-12ad-4980-9782-28daaa82b768/image.png" alt=""></p>
<p>왼쪽이 방향광에서 직교투영, 오른쪽이 방향광에서 원근투영임</p>
<p>딱봐도 오른쪽이 더 깊고 원근감에 의해 그림자가 생긴다는걸 알 수 있음</p>
<p>이로인해 방향광에서는 주로 직교투영을 사용하고</p>
<p>점광, 스포트라이트에서는 원근투영을 주로 사용함</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(11) - Gamma Correction]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL11-Gamma-Correction</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL11-Gamma-Correction</guid>
            <pubDate>Wed, 08 Jul 2026 12:50:25 GMT</pubDate>
            <description><![CDATA[<h1 id="감마보정">감마보정</h1>
<p><a href="https://velog.io/@vfx_master/CPP%EB%A1%9C-RayTracing-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-5.-Matte-%EC%9E%AC%EC%A7%88-%EA%B5%AC%ED%98%84#%EA%B0%90%EB%A7%88-%EB%B3%B4%EC%A0%95">CPP로 RayTracing 구현하기 - 5. 빛 반사 (Matte무광 재질)</a>
여기에서 감마보정에 대해 설명했음</p>
<p>다시 짧고 간결하게 설명하면</p>
<h2 id="what-is-감마-correction">what is 감마 correction?</h2>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/c09335dc-c544-48d6-837c-5c5d37e64764/image.png" alt="">
이 사진을 봐보자</p>
<p>아래줄은 실질적인 밝기의 변화임
윗줄은 사람이 올바르다고 느끼는 밝기의 변화임</p>
<p>그럼 문제가 뭐냐?</p>
<p>게임에서는 실질적인 밝기로 표현을 하는데, 사람이 보기에는 어색하다는거임
그래서 감마 보정을 통해 밝기를 어둡게 바꿈
그 값이 2.2승 정도임</p>
<p>따라서 모니터에 출력을 할때, 인간이 어색하지 않기 위해 더 어둡게 보정을 함</p>
<p>밝기가 0.5인 색이 있다고 가정, 감마보정을 통해 $0.5^{2.2} \approx 0.218$이라는 밝기가 됨
그럼 밝기가 1.0인 색이 있다고 해보자. 위의 예시인 0.5의 2배임.
따라서 0.436 approx가 되어야하지 않나?
$1^{2.2} = 1$임
따라서 2배 밝기 조절을 했음에도 불구하고 감마 보정을 한 후에는 약 4.5배의 차이가 생기게 되는거임</p>
<p>따라서 감마 보정을 할때 감마 인코딩, 감마 디코딩이라는 과정을 거침</p>
<h3 id="감마-인코딩보정">감마 인코딩/보정</h3>
<p>$밝기^{\frac{1}{2.2}}$를 해서 이미지를 출력하면 됨</p>
<p>기본적으로 색상계산이 끝난 후 픽셀이 그대로 모니터를 통해 출력되면
우리 눈에는 너무 어둡거나 우리가 원하는 방향으로 쉐이더가 적용된것처럼 보이지 않음</p>
<p>따라서 밝기를 약간 올려줘서 모니터에 출력될때
모니터 감마특성이 적용되어 우리눈에 안정감 있는 밝기로 출력하는거임</p>
<h3 id="감마-디코딩">감마 디코딩</h3>
<p>몇몇 이미지들은 이미 감마 인코딩이 되어있음
이러한 색상을 sRGB라고 함</p>
<p>이러한 이미지들을 이용해 텍스쳐 더하기, 뭐 섞기를 하면
이미 감마 인코딩이 되어있는 이미지라, 
결과가 왜곡되게 됨</p>
<p>따라서 sRGB이미지는 디코딩을 통해 인코딩된걸 해제해줘야함</p>
<p>간단하긴한데</p>
<p>$밝기^{2.2}$가 끝임ㅋㅋㅋ</p>
<hr>
<p><strong>가장 중요한건 최종 출력 직전에 감마 보정을 해줘야함</strong>
<strong>감마 디코딩 같은 특수한 경우가 아니고서야 미리 감마 보정을 하면 왜곡됨</strong></p>
<h1 id="opengl에서-감마-인코딩보정-하기">opengl에서 감마 인코딩/보정 하기</h1>
<p>쉐이더에서 직접 할수도 있고
opengl한테 제어권 넘길수도 잇음</p>
<p>opengl한테 제어권 넘기는건</p>
<pre><code class="language-cpp">glEnable(GL_FRAMEBUFFER_SRGB);</code></pre>
<p>으로 끝임ㅋㅋㅋ</p>
<p>쉐이더에서 하는것도 마지막 출력 직전에</p>
<pre><code class="language-cpp">void main()
{
    //스게-나 빛 계산로직...
    [...]
    // 감마 보정 적용
    float gamma = 2.2;
    FragColor.rgb = pow(fragColor.rgb, vec3(1.0/gamma));
}</code></pre>
<h1 id="opengl에서-감마-디코딩-하기">opengl에서 감마 디코딩 하기</h1>
<p>이것또한 쉐이더에서 직접 할수도 있고
opengl을 통해 할수도 있음</p>
<p>opengl을 통해 하는건</p>
<pre><code class="language-cpp">glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);</code></pre>
<p>이렇게 특정 텍스쳐/이미지의 색상타입이 sRGB로 되어있다고 알려서 감마 디코딩을 하도록 하면 됨</p>
<p>쉐이더에서는</p>
<pre><code class="language-cpp">float gamma = 2.2;
vec3 diffuseColor = pow(texture(diffuse, texCoords).rgb, vec3(gamma));</code></pre>
<p>이렇게 텍스쳐/이미지 로드 후에 2.2제곱을 해주면 됨ㅇㅇ</p>
<blockquote>
<p>감마 디코딩할때 주의해야할 점
ㅤ
보통 사용하게 될 이미지는 텍스쳐파일임
normal, metalic, diffuse, ao등등
ㅤ
이중에서 디코딩을 해줘야하는 텍스쳐는 주로 diffuse뿐임
ㅤ
픽셀의 색상을 결정짓는 이미지는 디코딩하고
그외에는 디코딩 하지 않는다고 보면 됨</p>
</blockquote>
<h1 id="빛의-감쇠">빛의 감쇠</h1>
<p>일반적으로 빛의 감쇠는 거리의 제곱만큼 밝기가 감쇠한다고 함</p>
<p>따라서</p>
<pre><code class="language-cpp">float attenuation = 1.0 / (distance * distance);</code></pre>
<p>이게 감쇠값이 되어서 밝기에 더해져 해당 거리에서 어느정도의 빛이 오냐...가 되는거징ㅇㅇ</p>
<p>근데 모니터에 출력될때 감마가 조정되지?
저상태로 밝기를 조절해서 모니터에 출력하면 겁나 어두워짐ㅇㅇ</p>
<p>따라서 감쇠도 그에 따라 값을 변화시켜야함</p>
<pre><code class="language-cpp">float attenuation = pow((1.0 / distance), 2.2);</code></pre>
<p>가 되는거임</p>
<p>$(\frac{1}{distance})^{2.2}$ㅇㅇㅇ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(10) - Anti-Aliasing]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL10-Anti-Aliasing</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL10-Anti-Aliasing</guid>
            <pubDate>Wed, 08 Jul 2026 09:56:01 GMT</pubDate>
            <description><![CDATA[<h1 id="안티-앨리어싱">안티 앨리어싱</h1>
<p>학교 그래픽스 시험에서 안티 앨리어싱 문제가 나왔는데 개어려웟음
근데 공부 오지게 한 보람있게 안티 앨리어싱 문제 맞추고 총합 A맞으면서 그래픽스 과락안함 개쩐당</p>
<p>기본적으로 필터가 있음
박스 필터, 가우시안 필터 등...</p>
<p>이 필터들은 해당 픽셀의 색상을 결정지을때, 주변의 픽셀에서 어느정도의 가중치를 가지고 픽셀을 결정지을거냐를 말함</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/cda507d7-71a7-447b-93b6-5ac41ce41ea1/image.png" alt=""></p>
<p>왼쪽이 가우시안 필터, 오른쪽이 박스 필터임
가우시안 필터는 가우시안 분포를 통해 근사 가중치를 이용함
박스 필터는 주변 모든 픽셀의 균등 가중치를 이용함</p>
<p>결국 kernel을 이용해서 픽셀의 색상을 결정짓는다고 생각하면 됨</p>
<p>주로 거론되는 안티 앨리어싱은
TAA, MSAA, SSAA, DLSS등이 있음
TAA는 가우시안 필터를 이용해 이전 프레임과 현재 프레임의 픽셀 색상을 계산하는 안티 앨리어싱
MSAA는 박스 필터를 이용해 polygon의 가장자리(테두리) 픽셀을 2X, 4X, 8X등으로 쪼개어서 픽셀의 평균 색상을 계산하는 안티 앨리어싱
SSAA는 전체 화면에 대해 x배수로 크기를 키운다음 색상을 계산하고 다시 다운그레이드 시키는 방식으로 색상을 적용하는 안티 앨리어싱
DLSS는 딥러닝을 이용한 안티 앨리어싱임</p>
<p>가장 구현하기 쉽고 성능도 나쁘지 않은 안티 앨리어싱은 MSAA임
따라서 그걸 해보겠음</p>
<p><a href="https://velog.io/@vfx_master/CPP%EB%A1%9C-RayTracing-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0-4.-%EC%95%88%ED%8B%B0%EC%95%A8%EB%A6%AC%EC%96%B4%EC%8B%B1#render%EB%A9%94%EC%84%9C%EB%93%9C-%EC%88%98%EC%A0%95">CPP로 RayTracing 구현하기 - 4. 안티앨리어싱</a>
여기서 박스 필터링이 어떻게 구현되는지 살펴봤음
궁금하면 체크 ㄱㄱ</p>
<h1 id="msaa">MSAA</h1>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/0fbaa504-d795-416e-84f7-5ea75851c9d0/image.png" alt=""></p>
<p>이렇게 rasterizated 되었다고 해보자</p>
<p>이 픽셀들에 fragment shader가 빨간색으로 씌워지면
<img src="https://velog.velcdn.com/images/vfx_master/post/fef018f1-ba67-4b32-aa31-0dc64ce9c2d5/image.png" alt="">
이렇게 보일거임</p>
<p>여기서 MSAA를 적용하려면 어떻게 해야하냐...
바로 픽셀을 n등분하는거임
<img src="https://velog.velcdn.com/images/vfx_master/post/2bd0fc3d-edb1-40cd-a167-8368cb1c45c6/image.png" alt=""></p>
<blockquote>
<p>bresenham 알고리즘</p>
<p>픽셀사이의 선을 이을때, 해당 선이 픽셀위에서 지나가는데,
진행방향이 가로냐 세로냐에 따라 픽셀의 중앙을 설정하고,
해당 중앙을 넘어가면 해당 픽셀에 점을 찍고, 
중앙을 넘어가지 못하면 가로, 세로에 따라 알맞은 방향 - 1위치의 픽셀에 점을 찍어서 이어버리는 rasterize알고리즘임</p>
</blockquote>
<p>만약 특정 픽셀의 일부분이 덮여있다고 플래그가 켜진다면...
MSAA를 하게됨</p>
<p>근데 문제가 있음</p>
<p>fragment shader를 서브샘플 수만큼, 예를들어 8x MSAA라면 8번 수행해야한다?
말도 안되는거임
그럼 SSAA랑 다를게 없는거임</p>
<p>fragment shader는 단 1번만 실행될거임</p>
<p>그럼 어떻게 색상 결정을 하냐?</p>
<ol>
<li>깊이/스텐실 버퍼를 이용해 서브샘플 범위를 설정함 (4X MSAA면 4X범위)</li>
<li>4개로 쪼개진 서브 샘플에서, 픽셀의 중앙이나 특정 위치에서 계산된 단일 색상이 통째로 덮어씌워 색상을 결정함</li>
</ol>
<p>그냥 kernel처럼 작동하는거임</p>
<table>
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/vfx_master/post/d245be73-c268-400a-9690-f3611963e8b1/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/vfx_master/post/60aab3fc-4e67-4372-845c-5409f3ae8cec/image.png" alt=""></td>
</tr>
</tbody></table>
<p>그냥 이거임ㅇㅇ</p>
<p>그럼 gl에서는 어떻게 사용하냐?
그걸 이제부터 알아보자 ㅇㅇ</p>
<h1 id="msaa-in-opengl">MSAA in OpenGL</h1>
<p>위에 과정을 보면 좀 무서운데
실제로는 그냥 옵션 몇개만 추가해주면됨</p>
<p>먼저 sample수를 지정해주고, msaa를 활성화시켜주면 끝임
백그라운드에서 래스터라이저가 알아서 aa해줌</p>
<pre><code class="language-cpp">glfwWindowHint(GLFW_SAMPLES, 4);
glEnable(GL_MULTISAMPLE);</code></pre>
<p>렌더 루프 들어가기전에 저 2개만 작성해주면 됨
숫자는 원하는 서브샘플 수를 적으면 됨
<img src="https://velog.velcdn.com/images/vfx_master/post/c9a7b446-b3db-4947-b9af-39d25b42d112/image.png" alt=""><img src="https://velog.velcdn.com/images/vfx_master/post/bb4a64c7-c5ce-4e08-b586-8c7995cef471/image.png" alt=""></p>
<p>차이가 보이는가 ㅇㅇ</p>
<h1 id="커스텀-프레임버퍼-aa적용">커스텀 프레임버퍼 AA적용</h1>
<p>만약 우리가 후처리효과를 넣고싶은데,
만약 AA가 적용된 화면에 후처리효과를 넣고싶으면?
커스텀 프레임버퍼를 통해서 AA를 적용해야지 ㅇㅇ</p>
<p>위의 경우는 제공되는 프레임버퍼에서 사용했을 경우라 저렇게 설정이 많이 필요없는거임</p>
<p>커스텀 프레임버퍼를 사용할때는 직접 멀티샘플 버퍼를 만들어줘야함</p>
<p>이걸 하는 방법은 2가지임</p>
<p>texture attachment, render buffer object임
겁먹을 필요 없음
공부했던 내용 반복이라...</p>
<p><a href="https://velog.io/@vfx_master/Advanced-OpenGL4-frame-buffers">Advanced OpenGL(4) - frame buffers
</a></p>
<p>이부분에서 텍스쳐/렌더 버퍼로 붙이는 쪽임</p>
<blockquote>
<p>텍스쳐 부착은 쉐이더에서 값을 읽어서 사용해야할때 사용하고
렌더버퍼는 쉐이더에서 사용하지 않고, 단순히 데이터 쓰기만 해야할때 사용</p>
</blockquote>
<h2 id="multi-sample-texture-attachment">multi-sample texture attachment</h2>
<p>개쉬움</p>
<pre><code class="language-cpp">glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGB, width, height, GL_TRUE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0);</code></pre>
<ol>
<li>GL_TEXTURE_2D_MULTISAMPLE에 바인딩</li>
<li>glTexImage2DMultisample에 원하는 텍스쳐, 샘플수등을 넣어줌<ul>
<li>마지막 GL_TRUE는 모든 텍셀에 대해 같은 수의 서브샘플을 가진다는걸 의미함</li>
</ul>
</li>
<li>프레임버퍼에 texture attachment를 함</li>
</ol>
<p>쉽지?
GL_TRUE이게 좀 중요함</p>
<p>그리고 이렇게 자동적으로 렌더링을 통해 들어온 데이터는
쉐이더로 넘겨서 사용가능하지~</p>
<blockquote>
<p>참고로 멀티샘플 텍스쳐를 쉐이더에서 사용하려면
ㅤ
sampler2DMS로 데이터를 받아서 texelFetch을 이용해 샘플당 색상값을 가져올 수 있음</p>
</blockquote>
<h2 id="multi-sample-render-buffer-object">multi-sample render buffer object</h2>
<p>멀티샘플 프레임버퍼는 약간의 후조정이 필요함</p>
<p>일단 멀티샘플 프레임버퍼를 직접 만들면, 래스터라이저가 알아서 MSAA를 해줌
다만,</p>
<p>여기서 MSAA가 되었다는건 해당 렌더버퍼가 더 많은 데이터를 가지고 있다는걸 말함
따라서 데이터의 크기를 줄이는 resolve라는 과정을 거쳐야함
그걸 Blitting이라고 함</p>
<blockquote>
<p>4X MSAA를 예시로 들면
화면크기가 400*400일때, 하나의 프레임이 400*400*4의 데이터를 가지게 됨
따라서 400*400에 맞게 데이터 크기를 조절해줘야함
ㅤ
그게 Resolve임</p>
</blockquote>
<p>아이디어는 간단!</p>
<ol>
<li>멀티샘플 렌더버퍼에 MSAA적용</li>
<li>프레임버퍼에 멀티샘플 프레임버퍼 blitting</li>
</ol>
<p>끝!</p>
<p>코드로는 아래와 같음</p>
<pre><code class="language-cpp">// ===== MSAA FBO 생성 =====
unsigned int multisampledFBO;
glGenFramebuffers(1, &amp;multisampledFBO);
glBindFramebuffer(GL_FRAMEBUFFER, multisampledFBO);

// 멀티샘플 &quot;컬러&quot; 렌더버퍼 생성
unsigned int colorRBO;
glGenRenderbuffers(1, &amp;colorRBO);
glBindRenderbuffer(GL_RENDERBUFFER, colorRBO);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGB8, SCR_WIDTH, SCR_HEIGHT);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRBO);

// 멀티샘플 깊이/스텐실 렌더버퍼 생성
unsigned int rbo;
glGenRenderbuffers(1, &amp;rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    std::cout &lt;&lt; &quot;ERROR::FRAMEBUFFER:: Multisampled Framebuffer is not complete!&quot; &lt;&lt; std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);

// ===== 일반 FBO(리졸브 대상) 생성 =====
unsigned int normalFBO;
glGenFramebuffers(1, &amp;normalFBO);
glBindFramebuffer(GL_FRAMEBUFFER, normalFBO);

// 리졸브된 결과를 담을 일반 2D 텍스처 생성
unsigned int screenTexture;
glGenTextures(1, &amp;screenTexture);
glBindTexture(GL_TEXTURE_2D, screenTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0);

if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    std::cout &lt;&lt; &quot;ERROR::FRAMEBUFFER:: Normal Framebuffer is not complete!&quot; &lt;&lt; std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);

//메인 렌더링 루프
while (!glfwWindowShouldClose(window))
{
    // --- 첫 번째 단계: MSAA FBO에 3D 장면 렌더링 ---
    glBindFramebuffer(GL_FRAMEBUFFER, multisampledFBO);
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);

    // (이 부분에 3D 물체를 그리는 코드가 들어갑니다)
    // DrawScene(); 

    // --- 두 번째 단계: MSAA FBO의 내용을 일반 FBO로 리졸브(블리팅) ---
    glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampledFBO);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, normalFBO);
    glBlitFramebuffer(0, 0, SCR_WIDTH, SCR_HEIGHT, 0, 0, SCR_WIDTH, SCR_HEIGHT, GL_COLOR_BUFFER_BIT, GL_NEAREST);

    // --- 세 번째 단계: 일반 FBO의 텍스처를 사용하여 화면에 2D 사각형 렌더링 (후처리 적용) ---
    glBindFramebuffer(GL_FRAMEBUFFER, 0); // 화면 출력으로 변경
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 
    glClear(GL_COLOR_BUFFER_BIT);
    glDisable(GL_DEPTH_TEST); // 화면 전체를 덮는 사각형이므로 깊이 테스트 비활성화

    // (이 부분에 후처리 셰이더 활성화 코드가 들어갑니다)
    // screenShader.use();

    glBindTexture(GL_TEXTURE_2D, screenTexture);

    // (이 부분에 화면 크기의 사각형(Quad)을 그리는 코드가 들어갑니다)
    // DrawPostProcessingQuad();

    glfwSwapBuffers(window);
    glfwPollEvents();
}

    glfwTerminate();
    return 0;
}</code></pre>
<p>천천히 코드보면 알수이쓸거임ㅇㅇ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(9) - Instancing]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL9-Instancing</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL9-Instancing</guid>
            <pubDate>Tue, 07 Jul 2026 13:29:29 GMT</pubDate>
            <description><![CDATA[<h1 id="instancing">Instancing</h1>
<p>만약 수천, 수만개의 particle, 나뭇잎등을 렌더링 할때</p>
<p>1개씩 렌더링을 하면 속도가 느려터질거임
<code>glDrawArrays</code>는 딱 1개씩 draw를 하는거잖아 ㅇㅇ</p>
<p>한번에 수천, 수만개의 도형을 한번에 그리는게 instancing임</p>
<p>이때 사용되는게</p>
<p><code>glDrawArraysInstanced</code>, <code>glDrawElementsInstanced</code>, 인스턴스 배열임</p>
<p>그리고 instance의 index를 다룰 수 있는 glsl 내장 변수 <code>gl_InstanceID</code>가 있음
59번째의 instance라면 <code>gl_InstanceID</code>는 58이 되는거지 ㅇㅇ</p>
<h1 id="gldrawarraysinstanced-gldrawelementsinstanced-gl_instanceid-사용예시"><code>glDrawArraysInstanced</code>, <code>glDrawElementsInstanced</code>, <code>gl_InstanceID</code> 사용예시</h1>
<pre><code class="language-cpp">float quadVertices[] = {
    // 위치          // 색상
    -0.05f,  0.05f,  1.0f, 0.0f, 0.0f,
     0.05f, -0.05f,  0.0f, 1.0f, 0.0f,
    -0.05f, -0.05f,  0.0f, 0.0f, 1.0f,

    -0.05f,  0.05f,  1.0f, 0.0f, 0.0f,
     0.05f, -0.05f,  0.0f, 1.0f, 0.0f,    
     0.05f,  0.05f,  0.0f, 1.0f, 1.0f                
};</code></pre>
<p>이런 4각형 vertex들을 정의해줌
이걸 변환과 instancing을 통해 100개를 10*10사이즈의 사각형으로 출력해볼거임</p>
<p>fragment shader는 아래와 같음</p>
<pre><code class="language-cpp">#version 330 core
out vec4 FragColor;
in vec3 fColor;
void main()
{
    FragColor = vec4(fColor, 1.0);
}</code></pre>
<p>이건 간단함</p>
<p>이제 핵심구역인 vertex shader를 보자</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;

out vec3 fColor;
uniform vec2 offsets[100];

void main()
{
    vec2 offset = offsets[gl_InstanceID];
    gl_Position = vec4(aPos + offset, 0.0, 1.0);
    fColor = aColor;
}</code></pre>
<p>uniform으로 offsets를 받아줄거임
그리고 해당 offset으로 각 인스턴스들을 옮길거임
여기서 <code>gl_InstanceID</code>가 쓰인걸 볼 수 있음</p>
<p>즉, instance에 해당되는 id를 이용해 offset을 찾고, 해당 offset을 instance에 이용해서 위치를 옮기는거임</p>
<p>그럼 main으로 돌아와서 렌더링 들어가기전에 offset들을 초기화 해주자</p>
<pre><code class="language-cpp">glm::vec2 translations[100];
int index = 0;
float offset = 0.1f;
for(int y = -10; y &lt; 10; y += 2)
{
    for(int x = -10; x &lt; 10; x += 2)
    {
        glm::vec2 translation;
        translation.x = (float)x / 10.0f + offset;
        translation.y = (float)y / 10.0f + offset;
        translations[index++] = translation;
    }
}</code></pre>
<p>그리고 렌더 루프에서 uniform값 넣어주면...</p>
<pre><code class="language-cpp">shader.use();
for(unsigned int i = 0; i &lt; 100; i++)
{
    shader.setVec2((&quot;offsets[&quot; + std::to_string(i) + &quot;]&quot;)), translations[i]);
}

glBindVertexArray(quadVAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100);</code></pre>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/e631f975-76a8-4963-ae4d-c711dcca8494/image.png" alt=""></p>
<p>kuuuu~~</p>
<h1 id="인스턴스-배열-glvertexattribdivisor">인스턴스 배열, glVertexAttribDivisor</h1>
<p>이젠 최적화의 영역임
지금은 100개라서 문제가 없었지만,,
만약 10000개라면?</p>
<p>uniform 최대 개수는 한정적이라 불가능할거임</p>
<p>그럼 어떻게 해야하나?</p>
<p>일반적인 layout location을 통해 offset을 한번에 받아주는 전략을 사용할거임</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aOffset;

out vec3 fColor;

void main()
{
    gl_Position = vec4(aPos + aOffset, 0.0, 1.0);
    fColor = aColor;
}</code></pre>
<p>모델의 VBO를 만드는것처럼 인스턴스의 VBO를 만들어주면 됨</p>
<pre><code class="language-cpp">glm::vec2 translations[100];
int index = 0;
float offset = 0.1f;
for(int y = -10; y &lt; 10; y += 2)
{
    for(int x = -10; x &lt; 10; x += 2)
    {
        glm::vec2 translation;
        translation.x = (float)x / 10.0f + offset;
        translation.y = (float)y / 10.0f + offset;
        translations[index++] = translation;
    }
}

unsigned int instanceVBO;
glGenBuffers(1, &amp;instanceVBO);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(glm::vec2) * 100, &amp;translations[0], GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);</code></pre>
<p>이런식으로 VBO를 만들고 GL_ARRAY_BUFFER에 바인딩 시켜준 다음에
vec2의 사이즈만큼 100개의 인스턴스, translation배열을 참조로 넘겨주면 됨</p>
<p>이제 vertex attribute를 넘겨줘야함</p>
<pre><code class="language-cpp">glEnableVertexAttribArray(2);
glBindBuffer(GL_ARRAY_BUFFER, instanceVBO);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);   
glVertexAttribDivisor(2, 1);</code></pre>
<p>코드를 천천히 보면</p>
<ol>
<li>2번 location으로 데이터를 넘겨줌</li>
<li>GL_ARRAY_BUFFER에 VBO바인딩</li>
<li>VBO가 어떤 형식으로 이뤄져있는지 넘김<ul>
<li>2번통로, vec2이니 2개의 float, GL_FLOAT타입, normalize안함, 2개의 float, 0번 offset부터</li>
</ul>
</li>
<li>버퍼 바인딩 해제 후...</li>
</ol>
<p>핵심인 <code>glVertexAttribDivisor(2, 1);</code>등장</p>
<h2 id="glvertexattribdivisor">glVertexAttribDivisor</h2>
<p>그냥 간단한 개념임</p>
<p><code>glVertexAttribDivisor(GLuint index, GLuint divisor)</code></p>
<p>index는 우리가 설정한 location을 의미함
divisor는 몇개의 인스턴스마다 vertex attributes를 갱신할건지를 의미함</p>
<p>divisor가 핵심인데
만약 100개의 물체를 인스터싱하는데, divisor가 2면, 2개마다 vertex attributes가 갱신되어서 2개씩 같은 vertex attributes를 사용하게 되는거임</p>
<hr>
<p>그리고 그냥 </p>
<pre><code class="language-cpp">cubeShader.use();
glBindVertexArray(VAO);
glDrawArraysInstanced(GL_TRIANGLES, 0, 6, 100);</code></pre>
<p>이렇게 glDrawArraysInstanced를 통해 draw만 해주면...</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/0c297485-3342-48a0-8f99-12d5977dd7dc/image.png" alt=""></p>
<p>같은 형식으로 출력되는걸 볼 수 있지비</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/7301ccd3-3eba-4fe5-bab3-794ef3361f56/image.png" alt=""></p>
<p>이런 재밋는 짓거리도
vertex shader수정을 통해 할 수 이뜸</p>
<hr>
<h1 id="목성like-출력">목성like 출력</h1>
<p>목성은 큰 행성이 있고, 주변을 떠다니는 위성조각들이 있음</p>
<p>이걸 구현해볼거임</p>
<p>당연히 uniform으로 하면 터질거라
인스턴스 배열로 처리하는걸 보여줌</p>
<p>아마 여기서 코드를 설명하는것보다
<a href="https://learnopengl.com/code_viewer_gh.php?code=src/4.advanced_opengl/10.3.asteroids_instanced/asteroids_instanced.cpp">LearnOpenGL - 목성과 위성</a> 여기서 전체코드를 보고 아래 해설 1개만 봐도 문제가 없을듯</p>
<p>가장중요한 부분인 asteroid.vsh와 인스턴스 배열을 이용하는 구간임</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in mat4 instanceMatrix;
out vec2 TexCoords;
uniform mat4 projection;
uniform mat4 view;
void main()
{
    gl_Position = projection * view * instanceMatrix * vec4(aPos, 1.0); 
    TexCoords = aTexCoords;
}</code></pre>
<p>location = 3으로 instanceMatrix라는 matrix를 불러올거임</p>
<p>이제 인스턴스 배열을 사용하는거임</p>
<pre><code class="language-cpp">//instanceMatrices 만들어짐....

unsigned int buffer;
glGenBuffers(1, &amp;buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, amount * sizeof(glm::mat4), &amp;modelMatrices[0], GL_STATIC_DRAW);

for(unsigned int i = 0; i &lt; rock.meshes.size(); i++)
{
    unsigned int VAO = rock.meshes[i].VAO;
    glBindVertexArray(VAO);
    // 정점 속성
    std::size_t vec4Size = sizeof(glm::vec4);
    glEnableVertexAttribArray(3); 
    glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)0);
    glEnableVertexAttribArray(4); 
    glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(1 * vec4Size));
    glEnableVertexAttribArray(5); 
    glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(2 * vec4Size));
    glEnableVertexAttribArray(6); 
    glVertexAttribPointer(6, 4, GL_FLOAT, GL_FALSE, 4 * vec4Size, (void*)(3 * vec4Size));

    glVertexAttribDivisor(3, 1);
    glVertexAttribDivisor(4, 1);
    glVertexAttribDivisor(5, 1);
    glVertexAttribDivisor(6, 1);

    glBindVertexArray(0);
}</code></pre>
<ol>
<li>버퍼를 만들고 만들어놓은 matrices로 데이터 채움</li>
<li>location = 3으로 matrix를 넣어야함</li>
<li>mat4는 vec4 * 4의 크기임</li>
<li>따라서  location = 3,4,5,6으로 자동적으로 각 행(vec4)이 이동됨</li>
<li>그에 맞춰 3,4,5,6번 location으로 넣어주고, divisor을 1로 만들어 각 인스턴스마다 vertex attributes 업데이트!</li>
</ol>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/19d72de0-19e7-426a-80f3-9f600d4cad9b/image.gif" alt=""></p>
<p>구우우욷</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(8) - Geometry Shader]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL8-Geometry-Shader</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL8-Geometry-Shader</guid>
            <pubDate>Tue, 07 Jul 2026 09:42:58 GMT</pubDate>
            <description><![CDATA[<h1 id="geometry-shader">Geometry Shader</h1>
<p>사용법은 엄청 간단함</p>
<pre><code class="language-cpp">#version 330 core
layout (points) in;
layout (line_strip, max_vertices = 2) out;

void main() {    
    gl_Position = gl_in[0].gl_Position + vec4(-0.1, 0.0, 0.0, 0.0); 
    EmitVertex();

    gl_Position = gl_in[0].gl_Position + vec4( 0.1, 0.0, 0.0, 0.0);
    EmitVertex();

    EndPrimitive();
}</code></pre>
<p>이게 거의 전부라고 봐도 됨</p>
<h2 id="in-layout">in layout</h2>
<p>in 입력 옵션은 어떤 형식의 도형으로 입력이 주어지는지 알려주는거임</p>
<table>
<thead>
<tr>
<th>옵션</th>
<th>glDrawArrays 그리기 옵션</th>
<th>필요 정점 수</th>
</tr>
</thead>
<tbody><tr>
<td><code>points</code></td>
<td>GL_POINTS 원시를 그릴 때</td>
<td>(1개)</td>
</tr>
<tr>
<td><code>lines</code></td>
<td>GL_LINES 또는 GL_LINE_STRIP을 그릴 때</td>
<td>(2개)</td>
</tr>
<tr>
<td><code>lines_adjacency</code></td>
<td>GL_LINES_ADJACENCY 또는 GL_LINE_STRIP_ADJACENCY</td>
<td>(4개)</td>
</tr>
<tr>
<td><code>triangles</code></td>
<td>GL_TRIANGLES, GL_TRIANGLE_STRIP 또는 GL_TRIANGLE_FAN</td>
<td>(3개)</td>
</tr>
<tr>
<td><code>triangles_adjacency</code></td>
<td>GL_TRIANGLES_ADJACENCY 또는 GL_TRIANGLE_STRIP_ADJACENCY</td>
<td>(6개)</td>
</tr>
</tbody></table>
<p>우리가 만약 <code>glDrawArrays</code>에서 GL_TRIANGLE을 그리고자 한다면
옵션을 triangle로 맞춰줘야함</p>
<h2 id="out-layout">out layout</h2>
<p>그리려고하는 도형과 출력할 정점의 최대 개수를 제공해주면 됨</p>
<table>
<thead>
<tr>
<th>도형</th>
<th>옵션</th>
</tr>
</thead>
<tbody><tr>
<td>점</td>
<td>points</td>
</tr>
<tr>
<td>선</td>
<td>line_strip</td>
</tr>
<tr>
<td>삼각형</td>
<td>triangle_strip</td>
</tr>
</tbody></table>
<p>이렇게 그리려는 도형을 제공해주고</p>
<p>예를들어 삼각형 2개로 사각형을 그린다고 했을때
필요한 최대 정점 개수는 4개이므로</p>
<p><code>layout(triangle_strip, max_vertices = 4) out;</code></p>
<p>이렇게 제공해주면 됨</p>
<blockquote>
<p><code>triangle_strip</code>의 삼각형 그려지는 순서</p>
<p>제공되는 정점이 7개이면 7-2개의 삼각형이 만들어짐
그리고 만들어지는 삼각형은 (1,2,3) (2,3,4) (3,4,5) (4,5,6) (5,6,7)의 정점 순서로 만들어짐</p>
</blockquote>
<h2 id="emitvertex-endprimitive">EmitVertex, EndPrimitive</h2>
<p>먼저 glsl에서는 </p>
<pre><code class="language-cpp">in gl_Vertex
{
    vec4  gl_Position;
    float gl_PointSize;
    float gl_ClipDistance[];
} gl_in[];</code></pre>
<p>이런 인터페이스를 제공함</p>
<p>이 인터페이스는 이전단계의 쉐이더에서 넘겨진 데이터들임</p>
<p>따라서 Geometry Shader에서 직접 gl_Position에 뭔가를 제공하고싶으면
<code>gl_Position = gl_in[0].glPosition;</code>
이런식으로 제공해주면 됨</p>
<p>그리고 인터페이스가 배열형식으로 제공되는 이유는 정점이 여러개라서 그럼</p>
<p>그럼
<code>EmitVertex</code>가 뭐겠음?
Geometry shader에서 바꾼/만든 정점 적용하는거임</p>
<p>그럼 
<code>EndPrimitive</code>는?
Geometry Shader에서 모든게 끝났으니 이제 마무리 짓는거임</p>
<h1 id="예시">예시</h1>
<pre><code class="language-cpp">float points[] = 
{
    -0.5f,  0.5f, // top-left
     0.5f,  0.5f, // top-right
     0.5f, -0.5f, // bottom-right
    -0.5f, -0.5f  // bottom-left
};</code></pre>
<p>이렇게 4개의 정점이 있음</p>
<pre><code class="language-cpp">#version 330 core
layout (points) in;
layout (triangle_strip, max_vertices = 5) out;

void build_house(vec4 position)
{    
    gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0);    // 1:bottom-left
    EmitVertex();   
    gl_Position = position + vec4( 0.2, -0.2, 0.0, 0.0);    // 2:bottom-right
    EmitVertex();
    gl_Position = position + vec4(-0.2,  0.2, 0.0, 0.0);    // 3:top-left
    EmitVertex();
    gl_Position = position + vec4( 0.2,  0.2, 0.0, 0.0);    // 4:top-right
    EmitVertex();
    gl_Position = position + vec4( 0.0,  0.4, 0.0, 0.0);    // 5:top
    EmitVertex();
    EndPrimitive();
}

void main() {    
    build_house(gl_in[0].gl_Position);
}</code></pre>
<p>그리고 이런 Geometry shader를 만들음
출력할 도형은 점인데,
쉐이더에서 삼각형으로 변환하는거임</p>
<p>각 정점에 대해 특정 vector값을 더해준다음 그걸로 삼각형을 그릴거임</p>
<p>그리고 출력해줌</p>
<pre><code class="language-cpp">glDrawArrays(GL_POINTS, 0, 2);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glDrawArrays(GL_POINTS, 2, 2);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);</code></pre>
<p>그럼 아래처럼 나옴</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/2d1e3222-d12a-4b25-a5a7-028cbf9b70a4/image.png" alt=""></p>
<p>딱 3개의 삼각형으로 그려진거 확인가능~</p>
<p>그럼 여기에 색상을 입혀보자</p>
<p>Geometry Shader에 in을 통해 변수를 받아야하는데...
일단 먼저 points를 약간 수정</p>
<pre><code class="language-cpp">float points[] = {
    -0.5f,  0.5f,     1.0f, 0.0f, 0.0f, // top-left
     0.5f,  0.5f,     0.0f, 1.0f, 0.0f, // top-right
     0.5f, -0.5f,     0.0f, 0.0f, 1.0f, // bottom-right
    -0.5f, -0.5f,     1.0f, 1.0f, 0.0f  // bottom-left
};  </code></pre>
<p>vertex shader를 약간 수정</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec3 aColor;

out VS_OUT
{
    vec3 color;
}vs_out;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
    vs_out.color = aColor;
}</code></pre>
<p>이전 챕터에서 공부한 인터페이스 블록 사용함</p>
<p>그리고 geometry에서 받아주면 되는데...</p>
<pre><code class="language-cpp">#version 330 core
layout (points) in;
layout (triangle_strip, max_vertices = 5) out;

in VS_OUT
{
    vec3 color;
}gs_in[];

out vec3 fColor;

void build_house(vec4 position)
{
    fColor = gs_in[0].color;
    gl_Position = position + vec4(-0.2, -0.2, 0.0, 0.0);    // 1:bottom-left
    EmitVertex();
    gl_Position = position + vec4( 0.2, -0.2, 0.0, 0.0);    // 2:bottom-right
    EmitVertex();
    gl_Position = position + vec4(-0.2,  0.2, 0.0, 0.0);    // 3:top-left
    EmitVertex();
    gl_Position = position + vec4( 0.2,  0.2, 0.0, 0.0);    // 4:top-right
    EmitVertex();
    gl_Position = position + vec4( 0.0,  0.4, 0.0, 0.0);    // 5:top
    //additional
    fColor = vec3(1.0, 1.0, 1.0);
    EmitVertex();

    EndPrimitive();
}

void main() 
{
    build_house(gl_in[0].gl_Position);
}</code></pre>
<p>이렇게 사용함
geometry shader는 입력이 정점들의 집합이므로 무조건 배열형식으로 in을 받아주면 됨</p>
<blockquote>
<p><code>in vec3 outColor[];</code>도 가능</p>
</blockquote>
<p>쉐이더는 간단하게 vertex - geometry - fragment로 이어짐
따라서 geometry shader를 만든 이상, fragment로 색상을 넘겨줘야하니 이렇게 하는거 ㅇㅇ</p>
<p>그리고 fragment shader는 그냥 in받으면</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/e8d5460c-6022-4df5-b0ca-93025602d467/image.png" alt=""></p>
<p>이런 화면이 출력됨<del>~</del></p>
<p>코드를 놓쳤다면</p>
<p><a href="https://learnopengl.com/code_viewer_gh.php?code=src/4.advanced_opengl/9.1.geometry_shader_houses/geometry_shader_houses.cpp">LearnOpenGL - geometry_shader_houses.cpp</a> 이걸 참고!</p>
<h1 id="geometry-shader-사용-이유">Geometry Shader 사용 이유</h1>
<p>cpu에서 정점 할당하고 gpu로 넘겨서 사용하는것보다
간단한 도형(e.g 잔디, 나뭇잎 등)은 gpu에 특정 정점만 주어지고,
그 정점을 토대로 gpu에서 정점을 만들어 특정 도형을 만드는게 더 빠르고 간단함</p>
<p>즉 gpu의 속도에서 바로바로 만들어내기때문에 속도가 어마어마하게 빠르게 동작하는거임</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 - 점프와 순간이동, cpp]]></title>
            <link>https://velog.io/@vfx_master/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A0%90%ED%94%84%EC%99%80-%EC%88%9C%EA%B0%84%EC%9D%B4%EB%8F%99-cpp-9vv1kjfh</link>
            <guid>https://velog.io/@vfx_master/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A0%90%ED%94%84%EC%99%80-%EC%88%9C%EA%B0%84%EC%9D%B4%EB%8F%99-cpp-9vv1kjfh</guid>
            <pubDate>Mon, 06 Jul 2026 15:50:20 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/cd69da54-5e43-454c-af09-18fda09d7237/image.png" alt="">
<img src="https://velog.velcdn.com/images/vfx_master/post/b6c6fe60-84c0-4955-b1e6-7641999cdf35/image.png" alt=""></p>
<h1 id="해설">해설</h1>
<p>딱 보자마자 bfs개꿀~
하면서 풀었는데
아무리 최적화를 해도</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/a5df9676-0976-4c63-a7bc-d236be218db5/image.png" alt="">
효율성 테스트 폭망</p>
<p>그래서 뭐가 문제인지 곰곰히 생각해봄</p>
<p>결국 bfs의 문제는 최종 경로로 가는 모든 경우를 따진다는거임
하지만 문제의 답은 1개로 최적의 경로를 통해 딱 n까지만 도착하면 된다는거 ㅇㅇ</p>
<p>그니까 답은 단 1개인데, 난 답을 여러개로 정해두고 풀려고 했다는거임
제공되는 수가 10억까지인데, 1에서 시작해 10억까지 갈 수 있는 모든 경우를 따지려고 한거라 느린거지 ㅇㅇ</p>
<p>심지어 경로를 전부 저장하니 ㅇㅇ...</p>
<h2 id="제대로-된-풀이">제대로 된 풀이</h2>
<p>이건 그리디 문제임</p>
<p>역순으로 돌아가면 됨
어짜피 정확히 n까지만 도착하면 되고, n + 1의 위치에서 n으로 되돌아올수있는 경우도 없음</p>
<p>그니까 혼자 최적의 경로로 돌아가도록 하면 됨
최적의 경로로 갈 수 있는 경우는
최대한 많이 2배의 거리를 뛰면 되는거구...ㅇㅇ</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;queue&gt;
#include &lt;vector&gt;
using namespace std;

int solution(int n)
{
    int ans = 0;

    while(n &gt; 0)
    {
        if(n % 2 == 0)
        {
            n /= 2;
        }
        else
        {
            n--;
            ans++;
        }
    }

    return ans;
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(7) - Advanced GLSL]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL5-Advanced-GLSL</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL5-Advanced-GLSL</guid>
            <pubDate>Mon, 06 Jul 2026 14:58:00 GMT</pubDate>
            <description><![CDATA[<p>쉐이더는 파이프라인화 되어있음
따라서 그 방식에 맞춰 uniform, vertex attributes, sampler등을 이용해 쉐이더 외부의 값을 이용해 쉐이더 파이프라인 내부에서 사용하도록 하는법을 공부했었음</p>
<p>GLSL내부에서 데이터를 입출력할 수 있는 몇가지 메서드가 있음
<code>gl_</code>로 시작하는 메서드인데 몇가지를 알아보도록 하자</p>
<h1 id="gl_pointsize">gl_PointSize</h1>
<pre><code class="language-cpp">glDrawArrays(GL_TRIANGLES, 0, 36);</code></pre>
<p>이렇게 렌더링 옵션을 설정해줄 수 있었음</p>
<p>여기서 사용할 수 있는게
<code>GL_POINTS</code>임</p>
<p>그리고 <code>gl_PointSize = gl_position.z</code>
이런식으로 사용해서 pointSize를 결정지어주면 됨</p>
<p>cpu, opengl에서 <code>glPointSize</code>를 해도 되지만
gpu에서 <code>gl_PointSize</code>하는게 더 낫겠지?</p>
<p>전체 사용은 다음과 같음</p>
<pre><code class="language-cpp">//...
glEnable(GL_GL_PROGRAM_POINT_SIZE);
//rendering
//...
glDrawArrays(GL_POINTS, 0, 36);
//...

//---------------
//vertex shader
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

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

void main()
{
    TexCoords = aTexCoords;
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    gl_PointSize = gl_Position.z;
}</code></pre>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/c3e5f878-6e41-4618-9162-fbadc9b47ca5/image.gif" alt=""></p>
<p>이렇게 멀어질수록 커지고, 가까워질수록 작아지는 큐브형태 점 완성~~</p>
<h1 id="gl_vertexid">gl_VertexID</h1>
<p>cpu에서 VBO를 통해 gpu로 데이터를 보내지 않고
gpu에서 바로 vertex id지정해서 사용할 수 있음</p>
<pre><code class="language-cpp">glDrawArrays(GL_TRIANGLES, 0, 6);</code></pre>
<p>여기서 6개의 정점을 사용한다고 했음
그럼 gpu에서는 vertexId가 0~5까지 증가하는 id를 제공함</p>
<p>만약 <code>glDrawElements</code>를 사용한다면 index에 해당되는 번호를 제공함</p>
<p>따라서 vertex shader에서</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

void main()
{
    vec2 pos;
    if (gl_VertexID == 0) pos = vec2(-0.7,  1.0);
    else if (gl_VertexID == 1) pos = vec2(-1.0, -1.0);
    else if (gl_VertexID == 2) pos = vec2(1.0,  1.0);
    else if (gl_VertexID == 3) pos = vec2(-1.0, -1.0);
    else if (gl_VertexID == 4) pos = vec2(1.0, -1.0);
    else pos = vec2(1.0, 1.0);

    gl_Position = vec4(pos, 0.0, 1.0);
    TexCoords = pos * 0.5 + 0.5;
}</code></pre>
<p>이렇게 vertexID를 이용해 삼각형을 그릴 수 있음</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/c8495698-c89f-4fee-a3e8-6444f936d180/image.png" alt=""></p>
<p>굳~~</p>
<h1 id="gl_fragcoord">gl_FragCoord</h1>
<p><code>gl_FragCoord</code>는 읽기 전용 변수임</p>
<p>fragment픽셀의 화면공간좌표 값임
vec4자료형으로 x,y,z,$\frac{1}{w}$값을 가짐</p>
<p>x,y는 화면공간의 x,y를 의미함
그냥 화면 가로/세로에서의 fragment좌표 인거임</p>
<p>z는 해당 fragment의 깊이를 의미함</p>
<p>$\frac{1}{w}$는 원근 투영시에 보간되는 값을 의미함
<a href="https://velog.io/@vfx_master/LearnOpenGL6-%EC%A2%8C%ED%91%9C-%EC%8B%9C%EC%8A%A4%ED%85%9C#%ED%81%B4%EB%A6%BD-%EA%B3%B5%EA%B0%84">LearnOpenGL(6) - 좌표 시스템, 클립 공간</a>의 원근 분할쪽을 보면 됨</p>
<blockquote>
<p>$\frac{1}{w}$의 이유</p>
</blockquote>
<p>w값은 원근투영시에 원근 분할시에 
x,y,z와 각각 나누어 원근감을 주게 됨</p>
<blockquote>
</blockquote>
<p>하지만 나눗셈 연산은 연산비용이 좀 있음
따라서 각 fragment에 대해 w로 나누기보다 
w의 역수인 $\frac{1}{w}$를 저장해두고 이를 곱하는 방식으로 연산비용을 줄일 수 있음</p>
<blockquote>
</blockquote>
<p>즉, 2D 화면공간에서 텍스처나 색상 데이터가 원근감에 맞게 왜곡 없이 
보간되기 위해$\frac{1}{w}$가 쓰이는 거임</p>
<p>그럼 이걸 어떻게 사용하냐?</p>
<p>fragment shader를 수정하면 됨</p>
<pre><code class="language-cpp">#version 330 core
out vec4 FragColor;

void main()
{
    if (gl_FragCoord.x &lt; 640 &amp;&amp; gl_FragCoord.y &lt; 360)
    {
        FragColor = vec4(0.0, 0.0, 1.0, 1.0);
    }
    else if (gl_FragCoord.x &lt; 640 &amp;&amp; gl_FragCoord.y &gt; 360)
    { 
        FragColor = vec4(0.0, 1.0, 0.0, 1.0); 
    }
    else if (gl_FragCoord.x &gt; 640 &amp;&amp; gl_FragCoord.y &lt; 360)
    {
        FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    }
    else
    {
        FragColor = vec4(1.0, 1.0, 0.0, 1.0);
    }
}</code></pre>
<p>간단하게 fragment를 4사분면으로 나눠서
각각 다른 색상을 지정해준거임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/d122a176-4084-40c8-93b7-7739f6015432/image.gif" alt=""></p>
<p>굳<del>~</del></p>
<h1 id="gl_frontfacing">gl_FrontFacing</h1>
<p>그냥 fragment shader에서 읽기 전용인 변수임</p>
<p>해당 fragment가 앞면이면 true, 아니면 false를 반환함</p>
<p>따라서 보이는 면의 texture와 안보이는 면의 texture를 다르게 설정해줄 수 있음</p>
<p>하지만 <a href="https://velog.io/@vfx_master/Advanced-OpenGL4-face-culling#face-culling-%ED%95%98%EB%8A%94%EB%B2%95">Advanced OpenGL(4) - face culling
</a> 여기서 살펴본 face culling을 활성화한다면 <code>gl_FrontFacing</code>는 무용지물이 됨</p>
<h1 id="gl_fragdepth">gl_FragDepth</h1>
<p>이번엔 읽기변수가 아닌 입력변수임</p>
<p>값을 수정할 수 있다는거</p>
<p><code>gl_FragDepth</code>는 fragment의 깊이를 수정할 수 있는 변수임
이 변수를 사용하지 않을때는 자동으로 <code>gl_FragCoord.z</code>를 사용함</p>
<p>하지만 직접 사용하지 않는 편이 최적화 관점에서는 더 좋음</p>
<p><a href="https://velog.io/@vfx_master/Advanced-OpenGL-Depth-test#depth-test">Advanced OpenGL(1) - Depth test, early depth test</a>에서 알 수 있듯
조기 깊이 테스트를 할 수 없음</p>
<h2 id="opengl-42이상에서는-가능">openGL 4.2이상에서는 가능</h2>
<p>!!!!! 하지만 <code>GL4.2</code>버전 이상에서부턴 shader 위에 layout으로 설정을 통해 가능하게 할 수 있음</p>
<pre><code class="language-cpp">#version 420 core // 4.2 OPENGL
out vec4 FragColor;
layout (depth_&lt;condition&gt;) out float gl_FragDepth;

void main()
{             
    FragColor = vec4(1.0);
    gl_FragDepth = gl_FragCoord.z + 0.1;
} </code></pre>
<p>여기 layout의 <code>depth_&lt;condition&gt;</code>를 이용해 조기 깊이테스트를 가능하게 할 수 있음</p>
<p>condition은 다음과 같음</p>
<table>
<thead>
<tr>
<th>condition</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>any</td>
<td>기본값, 조기 깊이 테스트가 비활성화</td>
</tr>
<tr>
<td>greater</td>
<td>gl_FragCoord.z에 비해 깊이 값을 더 크게만 만들 수 있음</td>
</tr>
<tr>
<td>less</td>
<td>gl_FragCoord.z에 비해 깊이 값을 더 작게만 만들 수 있음</td>
</tr>
<tr>
<td>unchanged</td>
<td>gl_FragCoord.z에 사용중인 값이 그대로 사용됨</td>
</tr>
</tbody></table>
<p>unchanged는 z값에 변경이 없다는걸 의미함
any를 제외한 모든 condition은 early depth testing이 활성화된다는거 잊지말고~~</p>
<hr>
<h1 id="인터페이스-블록">인터페이스 블록</h1>
<p>앞의 모든 챕터에서 shader를 다룰때, 
in, out이라는걸 이용해 각 shader사이에서 데이터를 사용하도록 했음</p>
<p>하지만 어플리케이션의 규모가 커지면
당연히 in, out해야할 변수도 많아지고
이걸 하나로 관리하고 싶어질때가 올거임</p>
<p>이때 사용하는게 구조체랑 비슷한 인터페이스 블록임</p>
<p>코드로 보는게 더 이해가 빠를거임</p>
<p>아래는 vertex shader코드</p>
<pre><code class="language-cpp">#version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;

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

out VS_OUT
{
    vec2 TexCoords;
} vs_out; //이름 변경 가능

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);    
    vs_out.TexCoords = aTexCoords;
}</code></pre>
<p>아래는 fragment shader코드</p>
<pre><code class="language-cpp">#version 330 core

out vec4 FragColor;

in VS_OUT //인터페이스 이름 통일
{
    vec2 TexCoords;
} fs_in; //원하는 이름으로 사용 가능

uniform sampler2D texture;

void main()
{             
    FragColor = texture(texture, fs_in.TexCoords);   
}</code></pre>
<p>이렇게 볼 수있듯이</p>
<p>인터페이스 이름과 내부 변수의 이름만 통일하고(필수)
사용할 인터페이스 변수명은 다르게 가져갈 수 있음</p>
<h1 id="uniform-buffer-object">Uniform buffer object</h1>
<p>지금까지 모든 챕터에서 glsl의 uniform을 설정하려고 할때
각각 개별의 uniform으로 설정했음</p>
<p>예를들어 오브젝트를 화면 공간에 넣어서 출력하기위해 계산하는 여러 행렬이 있었음</p>
<p>model, view, projection행렬이 그것이었음 ㅇㅇ</p>
<p>이 각각의 행렬을 uniform을 통해 </p>
<pre><code class="language-cpp">cubeShader.setMat4(&quot;model&quot;, model);
cubeShader.setMat4(&quot;view&quot;, view);
cubeShader.setMat4(&quot;projection&quot;, projection);</code></pre>
<p>이렇게 각각 데이터를 넣어줬음</p>
<p>근데 ubo(Uniform buffer object)를 이용하면 이렇게 각각 넣어줄 필요 없이
uniform을 공통되는 역할로 묶어 하나의 uniform으로 관리할 수 있음</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;

layout (std140) uniform Matrices
{
    mat4 projection;
    mat4 view;
};

uniform mat4 model;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}</code></pre>
<p>사용예시로 Matrices라는 uniform블럭 안에 projection, view를 냅두고 model은 따뤄 함 냅둬봄</p>
<p>그럼에도 불구하고 제대로 동작한다는거임</p>
<p>자, 이제 여기서 1가지 모르겠는 부분이 코드에서 보일거임</p>
<p>천천히 살펴보자</p>
<h2 id="uniform-block-layout">Uniform block layout</h2>
<p>유니폼 블럭 레이아웃이라 부르는게 있음</p>
<p><code>layout (std140)</code>ㅇㅇ</p>
<p>해당 uniform block이 어떻게 gpu에 저장될지를 결정짓는 요소임</p>
<p>예를들어</p>
<pre><code class="language-cpp">layout (std140) uniform ExampleBlock
{
    float value;
    vec3  vector;
    mat4  matrix;
    float values[3];
    bool  boolean;
    int   integer;
};</code></pre>
<p>이런 uniform block이 있다고 해보자</p>
<blockquote>
<p>기본 layout은 <code>layout (shared)</code>임
shared layout은 gpu가 자체적으로 최적화를 진행해서
cpu에서 우리가 구조체를 만들어 넘겨줘도 메모리 위치를 확실하게 모르기때문에</p>
</blockquote>
<ol>
<li><code>glGetUniformBlockIndex</code>를 이용해 shader프로그램의 uniform을 읽음</li>
<li><code>glGetUniformIndices</code>를 이용해 해당 uniform의 블럭 변수들을 다시 읽음</li>
<li><code>glGetActiveUniformsiv</code>를 이용해 각 블럭 변수들의 offset을 가져옴</li>
<li><code>glBufferSubData</code>등을 이용해 값 넣기</li>
</ol>
<blockquote>
<p><code>layout(packed)</code>도 존재함</p>
</blockquote>
<p>이는 offset을 최대한으로 압축해서 사용하는 layout임
컴파일러마다 내부 구조가 달라져서 사실상 공유되는 uniform일수 없는거임</p>
<p>float은 <code>n</code>, vec3는 <code>4n</code>, vec2는 <code>2n</code>, mat4는 <code>4*4n</code>, 배열은 <code>4\xn</code>, bool은 <code>n</code>, int는 <code>n</code> 이런식으로 offset이 결정되게 됨</p>
<p>layout은 우리가 원하는 offset으로 지정할 수 있지만,
혹은 하드웨어에서 직접 원하는 방식대로 offset을 지정하고 최적화 하도록 할 수도 있음</p>
<p>이 offset을 올바르게 지정하지 못하면 무슨 에러가 생길지 모름
따라서 되도록이면 우리가 직접 지정하고 사용하는게 좋음</p>
<p>그렇게 사용자가 직접 지정하는 엄격한 offset을 <code>layout(std140)</code>이라고 하는거임
OpenGL과 cpp의 primitive 타입은 결국 상호호환되니까 ㅇㅇ
그리고 업계 표준이 <code>layout(std140)</code>이고 ㅇㅇ</p>
<p>위 예시 코드의 offset을 계산하면 아래와 같음</p>
<pre><code class="language-cpp">layout (std140) uniform ExampleBlock
{
                     // 기본 정렬  // 정렬된 오프셋
    float value;     // 4               // 0 
    vec3 vector;     // 16              // 16  (오프셋은 16의 배수여야 하므로 4-&gt;16)
    mat4 matrix;     // 16              // 32  (열 0)
                     // 16              // 48  (열 1)
                     // 16              // 64  (열 2)
                     // 16              // 80  (열 3)
    float values[3]; // 16              // 96  (values[0])
                     // 16              // 112 (values[1])
                     // 16              // 128 (values[2])
    bool boolean;    // 4               // 144
    int integer;     // 4               // 148
};</code></pre>
<h2 id="uniform-block-uniform-buffer-object사용">uniform block, uniform buffer object사용</h2>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/e3f0d03e-1065-4d0b-b7ab-4fdd9c9fa301/image.png" alt=""></p>
<p>이 사진을 봐보자</p>
<p>여러 shader에서 binding points라는걸 통해 값을 받아올 수 있음
이때 binding points에 데이터는 ubo를 통해 넣어주게 됨</p>
<p>그럼 어떤 binding points에 어떤 데이터를 넣어줘야하는지
어떻게 결정할까?</p>
<p>정답은 우리가 직접 결정해주면됨</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

layout(std140) uniform UniformBlockView
{
    mat4 view;
};
layout(std140) uniform UniformBlockProjection
{
    mat4 projection;
};

uniform mat4 model;

void main()
{
    TexCoords = aTexCoords;
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}</code></pre>
<p>이렇게 vertex shader를 수정해줌</p>
<p>일부러 여러개의 binding points를 설정하는걸 보여주기위해 예제를 짬</p>
<p>여러개의 shader에서 동일한 2개의 uniform block을 사용할때 사용한다는 가정하에 코드를 짜는거지비</p>
<pre><code class="language-cpp">uint32_t redUBView = glGetUniformBlockIndex(redCubeShader.ID, &quot;UniformBlockView&quot;);
uint32_t redUBProjection = glGetUniformBlockIndex(redCubeShader.ID, &quot;UniformBlockProjection&quot;);
uint32_t greenUBView = glGetUniformBlockIndex(greenCubeShader.ID, &quot;UniformBlockView&quot;);
uint32_t greenUBProjection = glGetUniformBlockIndex(greenCubeShader.ID, &quot;UniformBlockProjection&quot;);
glUniformBlockBinding(redCubeShader.ID, redUBView, 0);
glUniformBlockBinding(redCubeShader.ID, redUBProjection, 1);
glUniformBlockBinding(greenCubeShader.ID, greenUBView, 0);
glUniformBlockBinding(greenCubeShader.ID, greenUBProjection, 1);

uint32_t uboView, uboProjection;
glGenBuffers(1, &amp;uboView);
glGenBuffers(1, &amp;uboProjection);

glBindBuffer(GL_UNIFORM_BUFFER, uboView);
glBufferData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), nullptr, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
// binding point에 들어갈 데이터의 크기 정의
//glBindBufferBase(GL_UNIFORM_BUFFER, 0, uboView);
glBindBufferRange(GL_UNIFORM_BUFFER, 0, uboView, 0, sizeof(glm::mat4));

glBindBuffer(GL_UNIFORM_BUFFER, uboProjection);
glBufferData(GL_UNIFORM_BUFFER, sizeof(glm::mat4), nullptr, GL_STATIC_DRAW);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
// binding point에 들어갈 데이터의 크기 정의
//glBindBufferBase(GL_UNIFORM_BUFFER, 1, uboProjection);
glBindBufferRange(GL_UNIFORM_BUFFER, 1, uboProjection, 0, sizeof(glm::mat4));</code></pre>
<p>이렇게 binding points를 공유하도록 하면 됨</p>
<p>원하는 형식, 크기, 값을 넘겨주면 됨</p>
<ol>
<li><code>glGetUniformBlockIndex</code>를 이용해 uniform block의 index를 찾고,</li>
<li><code>glUniformBlockBinding</code>를 이용해 해당 uniform block index에 binding points 번호를 부여</li>
<li><code>glBufferData</code>로 사용할 버퍼의 크기 부여 </li>
<li><code>glBindBufferBase</code>혹은<code>glBindBufferRange</code>로 binding points의 번호에 데이터 넣기</li>
</ol>
<p>이제 렌더링에서 사용하기만 하면 되는데 엄청 간단함</p>
<pre><code class="language-cpp">while(...)
{
    //...

    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glm::mat4 model = glm::mat4(1.0f);

    glm::mat4 view = camera.GetViewMatrix();
    glBindBuffer(GL_UNIFORM_BUFFER, uboView);
    glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(view));
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

    glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
    glBindBuffer(GL_UNIFORM_BUFFER, uboProjection);
    glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(glm::mat4), glm::value_ptr(projection));
    glBindBuffer(GL_UNIFORM_BUFFER, 0);

    // cubes
    glBindVertexArray(cubeVAO);
    redCubeShader.use();
    model = glm::mat4(1.0);
    model = glm::translate(model, glm::vec3(-0.7f, 0.0f, 0.0f));
    redCubeShader.setMat4(&quot;model&quot;, model);
    glDrawArrays(GL_TRIANGLES, 0, 36);

    greenCubeShader.use();
    model = glm::mat4(1.0);
    model = glm::translate(model, glm::vec3(0.7f, 0.0f, 0.0f));
    greenCubeShader.setMat4(&quot;model&quot;, model);
    glDrawArrays(GL_TRIANGLES, 0, 36);
}</code></pre>
<ol>
<li>uniform block에 해당되는 uniform 값을 할당함</li>
<li>draw를 함</li>
</ol>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/1ae12e11-1935-4311-b3d2-30610ae8cec8/image.png" alt=""></p>
<p>그럼 이렇게 되어버리는거지비</p>
<h2 id="uniform-block사용-이유">uniform block사용 이유</h2>
<ol>
<li>여러 uniform을 개별적으로 데이터를 입력하는것보다, 하나의 uniform block에 할당하고 데이터를 입력하는게 더 빠름</li>
<li>여러 쉐이더에서 동시에 변경되어야 하는 uniform변수의 경우 단 한번의 수정으로 해결가능 -&gt; 유지보수굳</li>
<li>일반 uniform보다 uniform block에 할당가능한 메모리 크기가 더 큼</li>
</ol>
<p>3번이 무슨소리인가 하면...
skeletal animation이라는게 있음
단순하게 설명하면 3D모델을 리깅을 통해 애니메이팅한거임</p>
<p>리깅을 하려면 bone이라는 개념이 필요함
bone은 말그대로 뼈라는 뜻으로 관절을 의미하는데,
요구사항에 따라 다르겠지만 고퀄 3D모델의 애니메이션에는 많은 관절이 필요함</p>
<p>일반 uniform으로 해당 bone들의 shader program을 짜야하면 uniform의 크기는 제한적이기 때문에 쉐이더가 터질 가능성이 있음</p>
<p>이때 사용하는게 uniform block으로 일반 uniform보다 더 많은 메모리를 할당할 수 있기때문에, 이를 사용하는편이 정신건강에 더 좋은거임</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(6) - Advanced Data]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL5-Advanced-Data</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL5-Advanced-Data</guid>
            <pubDate>Mon, 06 Jul 2026 08:51:27 GMT</pubDate>
            <description><![CDATA[<h1 id="glbuffersubdata"><code>glBufferSubData</code></h1>
<p><code>glBufferData</code>라는 메서드가 있음</p>
<pre><code class="language-cpp">unsigned int cubeVBO;
glGenBuffers(1, &amp;cubeVBO);
glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &amp;cubeVertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);</code></pre>
<p>이런식으로 vbo의 사이즈를 지정하고, data를 넘겨서 gpu메모리에 박을때 사용하는거임</p>
<p>만약 gpu메모리에 박힌 vbo를 사용하던중에 임의의 vbo데이터 변경이 생겨야한다면??</p>
<p>뭐 다시 <code>glBufferData</code>를 호출할 수도 있겠음ㅇㅇ
하지만 기본적으로 <code>glBufferData</code>는 메모리 초기화, 재할당을 하게됨</p>
<p>따라서 최적화를 위해 <code>glBufferSubData</code>라는걸 사용해주면 좋음</p>
<pre><code class="language-cpp">glBufferSubData(GL_ARRAY_BUFFER, 24, sizeof(data), &amp;data); // Range: [24, 24 + sizeof(data)]</code></pre>
<p>파라미터는 순서대로</p>
<p>target, offset, size, data임
target버퍼에 offset만큼 건너뛴 메모리 공간에 변경할 값들의 size와 값 변경할 data를 넣어주면 됨</p>
<p>간단스~</p>
<p>대신 <code>glBufferSubData</code>는 이미 할당된 메모리 공간을 사용해야하므로,
<code>glBufferData</code>로 먼저 메모리를 할당해두고 사용해야함</p>
<p>꼼수로</p>
<pre><code class="language-cpp">glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), nullptr, GL_STATIC_DRAW);</code></pre>
<p>이렇게 data를 비워뒀다가
<code>glBufferSubData</code>로 나중에 값이 완전해진후에 채워도 됨</p>
<h1 id="glmapbuffer"><code>glMapBuffer</code></h1>
<p>gpu 버퍼를 cpu의 포인터로 연결해서 사용할 수 있도록 하는거임</p>
<p>위의 <code>glBufferSubData</code>는 gpu내에서 직접 버퍼에 수정을 하는거였다면
<code>glMapBuffer</code>는 버퍼를 cpu로 불러와서 cpu에서 수정을 하거나 읽기만 하도록 할 수 있는거임</p>
<pre><code class="language-cpp">float data[] = {
  0.5f, 1.0f, -0.35f
  [...]
};

glBindBuffer(GL_ARRAY_BUFFER, buffer);

// mapBuffer로 포인터 얻기
void *ptr = glMapBuffer(GL_ARRAY_BUFFER, GL_WRITE_ONLY);

// 복사
memcpy(ptr, data, sizeof(data));

// 포인터 해제
glUnmapBuffer(GL_ARRAY_BUFFER);</code></pre>
<h1 id="정점속성-일괄처리batching">정점속성 일괄처리(batching)</h1>
<p>위의 <code>glBufferSubData</code>를 이용해 여러개로 나뉘어진 정점 속성을 하나로 묶어서 더 간편하고 알아보기 쉽게 사용할 수 있음</p>
<p>지금까지 우리는 </p>
<pre><code class="language-cpp">float cubeVertices[] = {
    // positions          // texture Coords
    -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,
    //...
};</code></pre>
<p>이런식으로 하나의 data를 이용해 </p>
<pre><code class="language-cpp">glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));</code></pre>
<p>직접 offset을 계산해서 넣어주는 방식으로 사용했음</p>
<p>하지만 <code>glBufferSubData</code>를 사용하면 이제 다르게 사용가능함</p>
<pre><code class="language-cpp">float positions[] = { ... };
float normals[] = { ... };
float tex[] = { ... };

uint32_t VAO, VBO;
glGenVertexArrays(1, &amp;VAO);
glBindVertexArray(VAO);
glGenBuffers(1, &amp;VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals) + sizeof(tex), nullptr, GL_STATIC_DRAW)

// fill buffer
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(positions), &amp;positions);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions), sizeof(normals), &amp;normals);
glBufferSubData(GL_ARRAY_BUFFER, sizeof(positions) + sizeof(normals), sizeof(tex), &amp;tex);

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), 0);  
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)(sizeof(positions)));  
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)(sizeof(positions) + sizeof(normals)));</code></pre>
<p>확실히 코드가 더 직관적이게 바뀜
3개의 정점마다 vertex attributes매핑
이지~~</p>
<h1 id="버퍼-복사-glcopybuffersubdata">버퍼 복사 <code>glCopyBufferSubData</code></h1>
<p>자 위의 방식들은 
버퍼의 값 변경,
gpu버퍼를 cpu에서 사용하도록 포인터 오픈
이었음</p>
<p>그럼 버퍼를 버퍼로 복사하려면 어떻게 해야할까?</p>
<p>걍 개쉬움</p>
<pre><code class="language-cpp">void glCopyBufferSubData(GLenum readtarget, GLenum writetarget, GLintptr readoffset, GLintptr writeoffset, GLsizeiptr size);</code></pre>
<p>를 사용하면 됨</p>
<p>readtarget의 버퍼를 writetarget으로 복사를 하는데
readtarget버퍼의 readoffset을 시작으로
writetarget버퍼의 writeoffset부터 size만큼 버퍼를 채움</p>
<p>예를들어
<code>GL_ARRAY_BUFFER</code> -&gt; <code>GL_ELEMENT_ARRAY_BUFFER</code>
이렇게 복사하는거임</p>
<p>근데 문제가 있음
<code>GL_ARRAY_BUFFER</code>에서 <code>GL_ARRAY_BUFFER</code>로 복사를 하려고 하면??</p>
<p>readtarget과 writetarget에 같은 버퍼를 넣을순 없음</p>
<p>이걸 해결하기위해 존재하는 버퍼가
<code>GL_COPY_READ_BUFFER</code> &amp; <code>GL_COPY_WRITE_BUFFER</code>임</p>
<p>그래서 대충 사용법을 보면</p>
<pre><code class="language-cpp">glBindBuffer(GL_COPY_READ_BUFFER, vbo1);
glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 8 * sizeof(float));

//_______________________________________________

float vertexData[] = { ... };
glBindBuffer(GL_ARRAY_BUFFER, vbo1);
glBindBuffer(GL_COPY_WRITE_BUFFER, vbo2);
glCopyBufferSubData(GL_ARRAY_BUFFER, GL_COPY_WRITE_BUFFER, 0, 0, 8 * sizeof(float));</code></pre>
<p>이렇게 되는거임</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[길고 긴 이사 끝]]></title>
            <link>https://velog.io/@vfx_master/%EA%B8%B8%EA%B3%A0-%EA%B8%B4-%EC%9D%B4%EC%82%AC-%EB%81%9D</link>
            <guid>https://velog.io/@vfx_master/%EA%B8%B8%EA%B3%A0-%EA%B8%B4-%EC%9D%B4%EC%82%AC-%EB%81%9D</guid>
            <pubDate>Sat, 04 Jul 2026 16:02:03 GMT</pubDate>
            <description><![CDATA[<p>역시 시스템이 다른 해외에서는 이사도 고역...</p>
<p>자그만치 2룸 방을 이사하는데 1주일이 넘게 걸렸넹...</p>
<p>내일은 데스크 세팅, 짐 정리 모두 다 하고
월요일부터 다시 공부 드가자<del>~</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(5) - cubemaps]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL5-cubemaps</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL5-cubemaps</guid>
            <pubDate>Thu, 25 Jun 2026 12:48:42 GMT</pubDate>
            <description><![CDATA[<h1 id="큐브맵">큐브맵</h1>
<p>텍스쳐중에선 큐브맵이라고 불리는 텍스쳐가 있음</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/f0565489-7e0c-4c07-a768-c271bfd18c81/image.png" alt=""></p>
<p>이렇게 매핑이 되는 면을 가지고 큐브형태로 감싸지는 텍스쳐임</p>
<h2 id="큐브맵-매핑-필터링-사용-코드-예시">큐브맵 매핑, 필터링, 사용 코드 예시</h2>
<p>그리고 말그대로 큐브이기때문에 vertex를 큐브형태로 선언해서 사용하면 됨
그리고 결국 6개의 면으로 이루어진거라, <code>glTexImage2D</code>로 텍스쳐 매핑을 할때도 6번을 해야한다는거임</p>
<p>이때 사용하는게 </p>
<table>
<thead>
<tr>
<th>텍스처 타겟</th>
<th>방향</th>
</tr>
</thead>
<tbody><tr>
<td>GL_TEXTURE_CUBE_MAP_POSITIVE_X</td>
<td>오른쪽</td>
</tr>
<tr>
<td>GL_TEXTURE_CUBE_MAP_NEGATIVE_X</td>
<td>왼쪽</td>
</tr>
<tr>
<td>GL_TEXTURE_CUBE_MAP_POSITIVE_Y</td>
<td>위쪽</td>
</tr>
<tr>
<td>GL_TEXTURE_CUBE_MAP_NEGATIVE_Y</td>
<td>아래쪽</td>
</tr>
<tr>
<td>GL_TEXTURE_CUBE_MAP_POSITIVE_Z</td>
<td>뒤쪽</td>
</tr>
<tr>
<td>GL_TEXTURE_CUBE_MAP_NEGATIVE_Z</td>
<td>앞쪽</td>
</tr>
</tbody></table>
<p>이렇게 X-Y-Z순서로 int값이 증가하면서 이루어진 enum값으로 텍스쳐를 매핑해주면 됨</p>
<p>그래서 사용예시는 아래와 같음</p>
<pre><code class="language-cpp">unsigned int textureID;
glGenTextures(1, &amp;textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);

int width, height, nrChannels;
unsigned char *data;  
for(unsigned int i = 0; i &lt; textureFacesLoc.size(); i++)
{
    data = stbi_load(textureFacesLoc[i].c_str(), &amp;width, &amp;height, &amp;nrChannels, 0);
    glTexImage2D(
        GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 
        0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
    );
}</code></pre>
<p>중요한 부분은 <code>GL_TEXTURE_CUBE_MAP_POSITIVE_X + i</code>부분임</p>
<p>위에서도 설명했듯이 enum값은 1씩 선형적으로 증가하므로, 큐브맵의 면의 개수만큼 증가시키면서 매핑을 해주면 되는거임</p>
<p>그리고 텍스쳐를 로딩했으면 텍스쳐를 어떻게 매핑하고 필터링해야하는지 알려줘야함</p>
<pre><code class="language-cpp">glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);</code></pre>
<p>min,mag 필터는 그냥 선형적으로 처리를 하면 되고,
가로$x$(S), 세로$y$(T), 깊이$z$(R)에 대해 <code>GL_CLAMP_TO_EDGE</code>래핑을 해, 항상 가장자리 반환을 하도록 하면 됨</p>
<p>그리고 glsl에서는 아래처럼 쓰면 됨</p>
<pre><code class="language-cpp">in vec3 textureDir; // direction vector representing a 3D texture coordinate
uniform samplerCube cubemap; // cubemap texture sampler

void main()
{             
    FragColor = texture(cubemap, textureDir);
} </code></pre>
<p>sampler2D가 아닌 samplerCube를 통해 샘플링하고
vec3값인 textureDir을 이용해서 fragment color를 결정하게 됨</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/474d84d0-12a5-43ef-854a-04a07c0aba0b/image.png" alt=""></p>
<p>여기서 노란선이 textureDir이고,
해당 방향과 cubemap의 hit지점에 해당되는 색상을 결정짓는거라고 보면 됨</p>
<h1 id="skybox">skybox</h1>
<p>위에서 설명한걸 그대로 사용하는게 skybox라는 큐브맵임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/f0565489-7e0c-4c07-a768-c271bfd18c81/image.png" alt=""></p>
<p>이게 skybox큐브맵의 예시임</p>
<p>저렇게 6개의 면이 이어붙여져서 제공될수도 있음
그럴때는 각 면을 분리하는 작업을 수행해줘야함</p>
<p>보통은 각각의 면을 따로따로 제공해줌</p>
<p>위 skybox 큐브맵을 사용하고,
위에서 살펴본 매핑, 필터링, 사용예시 코드를 이용해 <code>loadCube</code>메서드와 실제로 사용을 해보겠음</p>
<h2 id="1-skybox큐브맵-사용">1. skybox큐브맵 사용</h2>
<p>먼저 모든 NDC좌표를 커버치는 skybox용 vertex를 정의해줌
그리고 vertex array에 바인딩해주고,
<code>loadCubemap</code>커스텀 메서드를 이용해 경로의 skybox텍스쳐를 로드해줄거임</p>
<pre><code class="language-cpp">    //...
    float skyboxVertices[] = 
    {
        // positions          
        -1.0f,  1.0f, -1.0f,
        -1.0f, -1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,
         1.0f,  1.0f, -1.0f,
        -1.0f,  1.0f, -1.0f,

        -1.0f, -1.0f,  1.0f,
        -1.0f, -1.0f, -1.0f,
        -1.0f,  1.0f, -1.0f,
        -1.0f,  1.0f, -1.0f,
        -1.0f,  1.0f,  1.0f,
        -1.0f, -1.0f,  1.0f,

         1.0f, -1.0f, -1.0f,
         1.0f, -1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f,  1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,

        -1.0f, -1.0f,  1.0f,
        -1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f, -1.0f,  1.0f,
        -1.0f, -1.0f,  1.0f,

        -1.0f,  1.0f, -1.0f,
         1.0f,  1.0f, -1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
        -1.0f,  1.0f,  1.0f,
        -1.0f,  1.0f, -1.0f,

        -1.0f, -1.0f, -1.0f,
        -1.0f, -1.0f,  1.0f,
         1.0f, -1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,
        -1.0f, -1.0f,  1.0f,
         1.0f, -1.0f,  1.0f
    };

    unsigned int skyboxVAO, skyboxVBO;
    glGenVertexArrays(1, &amp;skyboxVAO);
    glGenBuffers(1, &amp;skyboxVBO);
    glBindVertexArray(skyboxVAO);
    glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &amp;skyboxVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

    //...
    vector&lt;std::string&gt; faces = 
    {
        &quot;Resources/skybox/right.jpg&quot;,
        &quot;Resources/skybox/left.jpg&quot;,
        &quot;Resources/skybox/top.jpg&quot;,
        &quot;Resources/skybox/bottom.jpg&quot;,
        &quot;Resources/skybox/front.jpg&quot;,
        &quot;Resources/skybox/back.jpg&quot;
    };
    unsigned int cubemapTexture = loadCubemap(faces);

    //...
    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        //...

        // draw skybox as last
        skyboxShader.use();
        skyboxShader.setMat4(&quot;view&quot;, view);
        skyboxShader.setMat4(&quot;projection&quot;, projection);
        // skybox cube
        glBindVertexArray(skyboxVAO);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

        //...
    }</code></pre>
<p>대충 이런 코드가 완성이 됨</p>
<p>이제 vbo를 받아서 정점 처리를 해줄 glsl코드를 만들어보자</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;

out vec3 TexCoords;

uniform mat4 projection;
uniform mat4 view;

void main()
{
    TexCoords = aPos;
    gl_Position = projection * view * vec4(aPos, 1.0);
}

//---------------------------------------

#version 330 core
out vec4 FragColor;

in vec3 TexCoords;

uniform samplerCube skybox;

void main()
{
    FragColor = texture(skybox, TexCoords);
}</code></pre>
<p>이렇게 하면 거의 끝~~</p>
<p>왜 거의 끝인지는 결과를 보고!</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/86585c18-c4c9-4f86-8b7a-18ff46959e3b/image.png" alt=""></p>
<p>????</p>
<h2 id="2-특정-변환행렬-제거">2. 특정 변환행렬 제거</h2>
<p>이렇게 된건 이유가 있음</p>
<p>일단 문제점을 짚어보면</p>
<ol>
<li>플레이어가 skybox외부에 존재함</li>
<li>skybox는 플레이어 위치에 따라서 작아지고 커짐</li>
</ol>
<p>즉, 
skybox도 큐브이기 때문에 변환행렬의 영향을 받는거임</p>
<p>그럼 문제가 있는 변환은 뭘까?</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/f2ad9bd5-d877-4d9d-8012-eb9d7c718c5a/image.png" alt="">
이게 변환행렬을 통해 어떤 공간으로 오브젝트가 변환되는지를 순차적으로 나타낸거임
기억나지??</p>
<p>model에서 world변환행렬을 더하면 world공간으로 바뀜</p>
<p>world공간에서 view변환행렬을 더하면 camera공간으로 바뀜</p>
<p>camera공간에서 projection변환행렬을 더하면 clip공간으로 바뀜</p>
<p>여기서 world공간은 월드좌표니 필수,
clip공간은 말그대로 클리핑을 해서 사용자 화면에 출력만해주는거니 필수,</p>
<p>결국 문제는 view변환행렬이후 camera 공간으로 바뀌는게 문제라는걸 알 수 있음</p>
<p>그럼 뭐가 문제일까??</p>
<p>결국 플레이어가 움직인다는건 camera좌표에 따라서 물체가 회전, 이동, 크기등이 바뀐다는거임</p>
<p>플레이어가 움직임 == camera가 움직임 -&gt; 사실 camera는 가만히 있고, camera주변의 물체들이 움직임</p>
<p>따라서 이렇게 camera에 의해 skybox가 움직이는 영향을 없애면 되는거임
그러니까, skybox의 위치는 고정하고, 회전만 따라하도록 하면 됨</p>
<pre><code class="language-cpp">glm::mat4 view = camera.GetViewMatrix();</code></pre>
<p>현재 view변환행렬은 다음과 같음</p>
<p>getViewMatrix는 4x4행렬임
이걸 3x3행렬로 바꾸고, 다시 4x4행렬로 바꾸면 
변환행렬의 변환파트를 없앨 수 있음</p>
<p>그리고 depthfunc를 이용해 약간의 depth test를 손봐야함
만약 기본값인 <code>GL_LESS</code>를 이용하게 되면
skybox의 특정면이 무조건 다른 물체보다 depth가 작으므로,
skybox만 그려지게 되고, 다른 물체는 안그려짐</p>
<p>따라서 skybox를 그리기전에 <code>depthFunc</code>를 다른값으로 바꿔주고, 다시 기본값으로 돌려놔주면 됨</p>
<pre><code class="language-cpp">glDepthFunc(GL_LEQUAL);  // depth가 작거나 같을때 렌더링
skyboxShader.use();
view = glm::mat4(glm::mat3(camera.GetViewMatrix())); // remove translation from the view matrix
skyboxShader.setMat4(&quot;view&quot;, view);
skyboxShader.setMat4(&quot;projection&quot;, projection);
// skybox cube
glBindVertexArray(skyboxVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glBindVertexArray(0);
glDepthFunc(GL_LESS);</code></pre>
<p>이렇게 코드가 완성됨</p>
<p>아래는 렌더링 전체코드임</p>
<pre><code class="language-cpp">//...

// settings
const unsigned int SCR_WIDTH = 1280;
const unsigned int SCR_HEIGHT = 720;

// camera
Camera camera(glm::vec3(0.0f, 0.0f, 3.0f));
float lastX = (float)SCR_WIDTH / 2.0;
float lastY = (float)SCR_HEIGHT / 2.0;
bool firstMouse = true;

// timing
float deltaTime = 0.0f;
float lastFrame = 0.0f;

int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, &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);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // tell GLFW to capture our mouse
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout &lt;&lt; &quot;Failed to initialize GLAD&quot; &lt;&lt; std::endl;
        return -1;
    }

    // configure global opengl state
    // -----------------------------
    glEnable(GL_DEPTH_TEST);

    // build and compile shaders
    // -------------------------
    Shader cubeShader(&quot;VertexShaders/CubeShader.vsh&quot;, &quot;FragmentShaders/CubeShader.fsh&quot;);
    Shader skyboxShader(&quot;VertexShaders/SkyboxShader.vsh&quot;, &quot;FragmentShaders/SkyboxShader.fsh&quot;);

    // set up vertex data (and buffer(s)) and configure vertex attributes
    // ------------------------------------------------------------------
    float cubeVertices[] = {
        // positions          // texture Coords
        -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
    };
    float skyboxVertices[] = {
        // positions          
        -1.0f,  1.0f, -1.0f,
        -1.0f, -1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,
         1.0f,  1.0f, -1.0f,
        -1.0f,  1.0f, -1.0f,

        -1.0f, -1.0f,  1.0f,
        -1.0f, -1.0f, -1.0f,
        -1.0f,  1.0f, -1.0f,
        -1.0f,  1.0f, -1.0f,
        -1.0f,  1.0f,  1.0f,
        -1.0f, -1.0f,  1.0f,

         1.0f, -1.0f, -1.0f,
         1.0f, -1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f,  1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,

        -1.0f, -1.0f,  1.0f,
        -1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f, -1.0f,  1.0f,
        -1.0f, -1.0f,  1.0f,

        -1.0f,  1.0f, -1.0f,
         1.0f,  1.0f, -1.0f,
         1.0f,  1.0f,  1.0f,
         1.0f,  1.0f,  1.0f,
        -1.0f,  1.0f,  1.0f,
        -1.0f,  1.0f, -1.0f,

        -1.0f, -1.0f, -1.0f,
        -1.0f, -1.0f,  1.0f,
         1.0f, -1.0f, -1.0f,
         1.0f, -1.0f, -1.0f,
        -1.0f, -1.0f,  1.0f,
         1.0f, -1.0f,  1.0f
    };

    // cube VAO
    unsigned int cubeVAO, cubeVBO;
    glGenVertexArrays(1, &amp;cubeVAO);
    glGenBuffers(1, &amp;cubeVBO);
    glBindVertexArray(cubeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &amp;cubeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    // screen quad VAO
    unsigned int skyboxVAO, skyboxVBO;
    glGenVertexArrays(1, &amp;skyboxVAO);
    glGenBuffers(1, &amp;skyboxVBO);
    glBindVertexArray(skyboxVAO);
    glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), &amp;skyboxVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);

    // load textures
    // -------------
    unsigned int cubeTexture = loadTexture(&quot;D:/01_Develope/CPP/learn_opengl/learn_opengl/Resources/legacy/container2_diffuse.png&quot;);

    vector&lt;std::string&gt; faces = 
    {
        &quot;Resources/skybox/right.jpg&quot;,
        &quot;Resources/skybox/left.jpg&quot;,
        &quot;Resources/skybox/top.jpg&quot;,
        &quot;Resources/skybox/bottom.jpg&quot;,
        &quot;Resources/skybox/front.jpg&quot;,
        &quot;Resources/skybox/back.jpg&quot;
    };
    unsigned int cubemapTexture = loadCubemap(faces);

    // shader configuration
    // --------------------
    cubeShader.use();
    cubeShader.setInt(&quot;texture1&quot;, 0);

    skyboxShader.use();
    skyboxShader.setInt(&quot;skybox&quot;, 0);

    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // per-frame time logic
        // --------------------
        float currentFrame = static_cast&lt;float&gt;(glfwGetTime());
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // input
        // -----
        processInput(window);


        // render
        // ------
        // 커스텀 프레임버퍼
        glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        cubeShader.use();
        glm::mat4 model = glm::mat4(1.0f);
        glm::mat4 view = camera.GetViewMatrix();
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        cubeShader.setMat4(&quot;model&quot;, model);
        cubeShader.setMat4(&quot;view&quot;, view);
        cubeShader.setMat4(&quot;projection&quot;, projection);
        // cubes
        glBindVertexArray(cubeVAO);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, cubeTexture);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);

        // draw skybox as last
        glDepthFunc(GL_LEQUAL);  // change depth function so depth test passes when values are equal to depth buffer&#39;s content
        skyboxShader.use();
        view = glm::mat4(glm::mat3(camera.GetViewMatrix())); // remove translation from the view matrix
        skyboxShader.setMat4(&quot;view&quot;, view);
        skyboxShader.setMat4(&quot;projection&quot;, projection);
        // skybox cube
        glBindVertexArray(skyboxVAO);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
        glDrawArrays(GL_TRIANGLES, 0, 36);
        glBindVertexArray(0);
        glDepthFunc(GL_LESS);


        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glDeleteVertexArrays(1, &amp;cubeVAO);
    glDeleteBuffers(1, &amp;cubeVBO);
    glDeleteVertexArrays(1, &amp;skyboxVAO);
    glDeleteBuffers(1, &amp;skyboxVBO);

    glfwTerminate();
    return 0;
}
/...</code></pre>
<p>그럼 결과는 아래처럼 나옴</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/fea891bb-3027-47de-8b71-b1629ab378cf/image.gif" alt=""></p>
<p>이게 머임???</p>
<p>skybox의 깊이값이 변해서 그럼</p>
<h2 id="3-skybox깊이값-고정">3. skybox깊이값 고정</h2>
<p>skybox의 깊이값은 계속 변경될 필요가 없음</p>
<p>어짜피 특정 위치에 고정되어서 사용자가 그 공간내에 계속 위치하도록 하기만 하면 됨</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec3 aPos;

out vec3 TexCoords;

uniform mat4 projection;
uniform mat4 view;

void main()
{
    TexCoords = aPos;
    vec4 pos = projection * view * vec4(aPos, 1.0);
    gl_Position = pos.xyww;
}</code></pre>
<p>vertex shader를 이렇게 고쳐줌</p>
<p>핵심 아이디어는 
Texture에는 x,y,z값을 그대로 사용하고
정점에 대해서는 w값을 1.0으로 초기화하고
z(깊이)값을 해당 w값으로 사용해서, 
depth testing시에 무조건 우선순위를 최하위로 가져가도록 하는거임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/d5d45711-d828-4a8d-9774-5d3ed98aa5ea/image.png" alt=""></p>
<p>그럼 이렇게 잘 렌더링된 큐브를 볼 수 있지비~~</p>
<hr>
<p><a href="https://learnopengl.com/Advanced-OpenGL/Cubemaps">LearnOpenGL - Cubemaps/Environment mapping</a>을 보고
추가적으로 환경반사, 굴절등을 구현해보는것도 좋다~~</p>
<p>난 이미 전에 다 알아봤으므로 패스~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(4) - frame buffers]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL4-frame-buffers</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL4-frame-buffers</guid>
            <pubDate>Mon, 22 Jun 2026 11:13:10 GMT</pubDate>
            <description><![CDATA[<h1 id="frame-buffer">frame buffer</h1>
<p>지금까지 learnOpenGL을 공부하면서
Color, Depth, Stencil buffer를 알아봤음</p>
<p>매 프레임마다 buffer를 clear해주고 머시기 하고 ㅇㅇ</p>
<p>이런 프레임별로 화면에 띄울 fragment를 결정짓는 버퍼를 뭉뚱그려서 스크린버퍼 라고 부름
Color buffer, Depth buffer, Stencil buffer가 스크린 버퍼들인거지비 ㅇㅇ</p>
<p>그리고 이러한 버퍼들이 GPU 메모리에 하나로 모이면, 
그 버퍼들의 집합을 frame buffer라고 부르는거임</p>
<p><a href="https://velog.io/@vfx_master/LearnOpenGL1-%EC%B0%BD-%EB%9D%84%EC%9A%B0%EA%B8%B0#%EC%B0%BD-%EB%9D%84%EC%9B%8C%EB%B3%B4%EA%B8%B0">LearnOpenGL(1) - 창 띄우기</a>
여기에서 <code>glfwSwapBuffers</code>라는걸 사용하는걸 볼 수 있음</p>
<p>이게 위에서 설명한 프레임 버퍼를 프레임단위로 swap하는거임</p>
<p>이렇게 glfw의 기본 프레임버퍼만 사용 할 수도 있지만</p>
<p>우리가 직접 추가적으로 프레임 버퍼를 만들어서 
거울에 따로 비치는 장면을 만든다던지,
원하는 특수효과(포스트 프로세싱)를 넣을 수 잇음</p>
<h1 id="프레임-버퍼-사용하기">프레임 버퍼 사용하기</h1>
<p>binding해서 쓰던것처럼 쓰면 됨</p>
<pre><code class="language-cpp">uint32_t fbo;
glGenFramebuffers(1, &amp;fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);</code></pre>
<p>아주 easy~~
Bind할때 bind 가능한 버퍼종류가 있긴함</p>
<ul>
<li><code>GL_FRAMEBUFFER</code> : 읽기, 쓰기 모두 가능한 frame buffer</li>
<li><code>GL_READ_FRAMEBUFFER</code> : 읽기만 가능한 frame buffer</li>
<li><code>GL_FRAW_FRAMEBUFFER</code> : 쓰기만 가능한 frame buffer</li>
</ul>
<p>보통은 읽기, 쓰기 모두 가능한 프레임 버퍼를 쓰긴함ㅇㅇ
그리고 프레임 버퍼를 저상태로 쓸수있는건 아님
몇가지 조건이 만족되어야 그때부터 커스텀 프레임 버퍼를 쓸 수 있음</p>
<ol>
<li>최소 1개 이상의 버퍼를 연결해야함 (color, depth, stencil)</li>
<li>최소 1개 이상의 색상을 결정짓는 요소가 연결되어 있어야함<ul>
<li>e.g. color buffer, 텍스쳐 등</li>
</ul>
</li>
<li>프레임 버퍼와 연결된 모든 attachments는 메모리에 할당되어있어야함<ul>
<li>텍스쳐가 메모리에 올라가있던지, color buffer가 메모리에 올라간 상태이던지 ㅇㅇ</li>
</ul>
</li>
<li>연결된 버퍼는 모두 같은 수의 샘플을 사용해야함(안티앨리어싱 샘플링)</li>
</ol>
<p>이게 조건이 모두 충족되면 이제 커스텀 프레임 버퍼를 사용할 수 있는거지비</p>
<p>그럼 사용가능한지 판단을 어떻게 하냐??</p>
<pre><code class="language-cpp">glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;</code></pre>
<p>이렇게 COMPLETE가 반환되면 사용할 수 있다는거지비 ㅇㅇ</p>
<h2 id="주의사항">주의사항</h2>
<ol>
<li>기본 프레임 버퍼 렌더링이 안됨!!</li>
</ol>
<p>커스텀 프레임 버퍼를 바인딩하면
이후 모든 attachments들은 커스텀 버퍼에게 연결되게 됨
그래서 기본 프레임 버퍼는 렌더링 되지 않음</p>
<p>따라서 기본 프레임 버퍼를 렌더링하려고 하면</p>
<pre><code class="language-cpp">glBindFramebuffer(GL_FRAMEBUFFER, 0);</code></pre>
<p>를 해주어야함</p>
<ol start="2">
<li>모든 작업이 끝난 후에는 반드시 메모리 해제!</li>
</ol>
<pre><code class="language-cpp">glDeleteFramebuffers(1, &amp;fbo);</code></pre>
<p>무조건무조건!</p>
<h2 id="1-texture-attachment">1. Texture Attachment</h2>
<pre><code class="language-cpp">uint32_t texture;
glGenTextures(1, &amp;texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);</code></pre>
<p>texture를 만들고 <code>GL_TEXTURE_2D</code>에 바인딩한 후,</p>
<p><code>glTexImage2D</code>를 이용해 texture를 묶어줄건데
텍스쳐의 크기는 보통 화면크기(거울과 같으면 거울의 크기)를 사용하고,
texture data는 NULL을 사용함</p>
<p>왜냐면 지금당장 texture를 넣을게 아니고,
화면에 그릴 프레임이기때문에, 렌더링단계에서 텍스쳐(화면)이 채워질거임ㅇㅇ</p>
<blockquote>
<p>더 작은 화면에 그리고 싶으면
프레임 버퍼에 그려지기 전에(렌더링)
원하는 텍스쳐(화면)크기에 맞춰 <code>glViewPort</code>를 호출해줘야함!</p>
</blockquote>
<p>그리고 마찬가지로 texture parameter의 WRAP, MIPMAP모두 필요없음
그냥 축소, 확대될때 어떻게 필터링할건지만 지정해주자</p>
<p>이제 프레임버퍼의 필수조건인 색상 부착물(텍스쳐)을 프레임버퍼에 바인딩해주면 됨</p>
<pre><code class="language-cpp">uint32_t texture;
glGenTextures(1, &amp;texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

uint32_t fbo;
glGenFramebuffers(1, &amp;fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);</code></pre>
<p><code>glFramebufferTexture2D</code>의 파라미터는 다음과 같음</p>
<ul>
<li>첫번째 인자 : target, 사용하고자 하는 프레임 버퍼 종류</li>
<li>두번째 인자 : attchment, 붙이려고하는 텍스쳐를 어느 프레임버퍼의 슬롯(color, depth, stencil)에 붙이려고하는지 지정<ul>
<li><code>GL_COLOR_ATTACHMENT?</code>, <code>GL_DEPTH_ATTACHMENT</code>, <code>GL_STENCIL_ATTACHMENT</code>, <code>GL_DEPTH_STENCIL_ATTACHMENT</code> 등</li>
</ul>
</li>
<li>세번째 인자 : textarget, 붙이고자 하는 텍스쳐의 유형</li>
<li>마지막 인자 : mipmap, 대게는 0</li>
</ul>
<h3 id="텍스쳐-연결-예시">텍스쳐 연결 예시</h3>
<p>각 attachment마다 코드가 어떻게 달라지는지 보여줌</p>
<p><code>GL_COLOR_ATTACHMENT</code></p>
<pre><code class="language-cpp">GLuint colorTexture;
glGenTextures(1, &amp;colorTexture);
glBindTexture(GL_TEXTURE_2D, colorTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 800, 600, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

// 프레임버퍼에 텍스처 연결
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);</code></pre>
<p><code>GL_DEPTH_ATTACHMENT</code></p>
<pre><code class="language-cpp">GLuint depthTexture;
glGenTextures(1, &amp;depthTexture);
glBindTexture(GL_TEXTURE_2D, depthTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, 800, 600, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

// 프레임버퍼에 깊이 텍스처 연결
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);</code></pre>
<p><code>GL_STENCIL_ATTACHMENT</code></p>
<pre><code class="language-cpp">GLuint stencilTexture;
glGenTextures(1, &amp;stencilTexture);
glBindTexture(GL_TEXTURE_2D, stencilTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_STENCIL_INDEX8, 800, 600, 0, GL_STENCIL_INDEX, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

// 프레임버퍼에 스텐실 텍스처 연결
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, stencilTexture, 0);</code></pre>
<p><code>GL_DEPTH_STENCIL_ATTACHMENT</code></p>
<pre><code class="language-cpp">GLuint depthStencilTexture;
glGenTextures(1, &amp;depthStencilTexture);
glBindTexture(GL_TEXTURE_2D, depthStencilTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

// 프레임버퍼에 깊이-스텐실 결합 텍스처 연결
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depthStencilTexture, 0);</code></pre>
<p>중요한건 <code>glTexImage2D</code>에서 3번째 인자인 internal format이 바뀌는걸 확인할수있도록</p>
<table>
<thead>
<tr>
<th>텍스쳐 종류</th>
<th>internal format</th>
<th>format</th>
</tr>
</thead>
<tbody><tr>
<td><code>GL_COLOR_ATTACHMENT</code></td>
<td><code>GL_RGB</code> or something RGB related</td>
<td><code>GL_RGB</code> or something RGB related</td>
</tr>
<tr>
<td><code>GL_DEPTH_ATTACHMENT</code></td>
<td>GL_DEPTH_COMPONENT</td>
<td>GL_DEPTH_COMPONENT</td>
</tr>
<tr>
<td><code>GL_STENCIL_ATTACHMENT</code></td>
<td>GL_DEPTH24_STENCIL8</td>
<td>GL_STENCIL_INDEX</td>
</tr>
<tr>
<td><code>GL_DEPTH_STENCIL_ATTACHMENT</code></td>
<td>GL_DEPTH24_STENCIL8</td>
<td>GL_DEPTH_STENCIL</td>
</tr>
</tbody></table>
<h2 id="2-renderbuffer-attachment">2. Renderbuffer Attachment</h2>
<p>gl이 도입한 개념임</p>
<blockquote>
</blockquote>
<p>위의 texture attachment는 gpu에서 읽기,쓰기 모두 가능한 텍스쳐(color, depth, stencil)에 사용하는거임
프레임버퍼에 저장된 텍스쳐를 glsl에서 <code>texture()</code>를 이용해 불러와서 쓸수있다는게 읽기 가능하다는거임</p>
<p>반면 지금 설명할 render buffer object는 주로 쓰기만 가능한 객체임</p>
<p>읽기가 필요없는 객체??
뭐지 그게?</p>
<p>자자 바로 Advanced OpenGL시리즈를 시작할때 설명한 
depth, stencil buffer가 대표적인 읽기가 필요없는 객체임</p>
<p>depth, stencil buffer는 정말 중요한 자원임
물체의 앞뒤를 구분해주고, 필요한 부분만 잘라서 렌더링할 수 있도록 해주니깐 ㅇㅇ</p>
<p>근데 이 depth, stencil buffer를 꼭 읽기 가능해야만 할까?
사실 그렇지 않음
읽기가 없고 쓰기만 가능해도 바로 렌더링 결과에서 확인가능하고,
실제로 쓰기만 잘하면 되는 buffer임</p>
<p>따라서 이 render buffer object는 depth, stencil을 위해서만 사용됨
또, 쓰기가 엄청 빠름
사용자의 GPU에서 지원하는 자체적인 format으로 데이터가 저장이 됨
그래서 쓰기가 엄청 빠르고,
또, 자체적인 format을 이용하기때문에 copy하기도 용이함
따라서 프레임버퍼 교체를 해야할때, 속도가 어마무시한거지 ㅇㅇ</p>
<p>따라서 프레임버퍼에 render buffer object를 사용하면 성능 이점이 확실함</p>
<blockquote>
<p>render buffer object도 읽기가 가능함</p>
</blockquote>
<p><code>glReadPixel</code>을 이용해서 프레임버퍼 전체에게 해당 픽셀의 데이터를 cpu로 읽어오도록 할 수 있는거임
그리고 이 방식은 texture attachment에서도 사용가능함</p>
<blockquote>
</blockquote>
<p>하지만, rbo는 gpu에서 바로 값을 읽어 수정할 수 없다는거임
그래서 glsl <code>texture()</code>이 불가능함</p>
<h3 id="render-buffer-object-사용하기">render buffer object 사용하기</h3>
<pre><code class="language-cpp">uint32_t rbo;
glGenRenderbuffers(1, &amp;rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);</code></pre>
<p>이렇게 buffer생성, 바인딩하는것처럼 사용하면 됨</p>
<p>그리고 위 texture attachment에서 <code>GL_DEPTH_STENCIL</code>을 이용하던것처럼</p>
<pre><code class="language-cpp">glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);</code></pre>
<p>이렇게 <code>glRenderbufferStorage</code>를 이용해 RenderBuffer에 사용할 형식을 지정해주고
<code>glFramebufferRenderbuffer</code>를 이용해 프레임버퍼에 rbo를 attach해주면 됨</p>
<h2 id="정리-texture-attachment-vs-render-buffer-object-attachment">정리. texture attachment vs render buffer object attachment</h2>
<p>texture attachment는 gpu에서 읽기,쓰기 모두 가능한 텍스쳐(color, depth, stencil)에 사용함
프레임버퍼에 저장된 텍스쳐를 glsl에서 <code>texture()</code>를 이용해 불러와서 쓸수있다는게 읽기 가능함</p>
</br>

<p>render buffer object attachment는 gpu에서 쓰기만 가능한 버퍼임
gpu에서 데이터를 읽어 수정하는 glsl의 <code>texture()</code>이 불가능함</p>
</br>

<p>하지만 gpu의 값을 cpu로 읽어서 사용하는건 
texture attachment와 render buffer object attachment 모두 가능함
<code>glReadPixel</code>을 이용해서 프레임버퍼 전체에게 특정 픽셀의 데이터를 cpu로 읽어온 후, 원하는 작업을 수행할 수 있음
예를들어 사용자의 클릭 위치의 픽셀을 찾아 이벤트 수행 등등...</p>
<hr>
<p>즉, 기존프레임 버퍼는 한번 렌더링하면 수정하기가 힘들어짐
따라서 해당 화면 전체에 특수효과(블러, 머시기, 그레이스케일, 머시기)등을 입히고 싶다고 해도 쉽지않음...</p>
<p>따라서 커스텀 프레임 버퍼는 기존프레임 버퍼에서 하지 못하는
렌더링된 화면 전체를 픽셀마다 캡쳐해서 텍스쳐로 활용해 
특수효과(포스트 프로세싱)을 할 수 있는거임 ㄷㄷㄷ</p>
<h1 id="프레임버퍼로-렌더링하기">프레임버퍼로 렌더링하기</h1>
<p>위에서 말한대로</p>
<p>기존 프레임버퍼에서의 fragment를 
우리가 만들 커스텀 프레임버퍼로 복사를하고
그 위에 특수효과를 입혀볼거임</p>
<pre><code class="language-cpp">// 커스텀 프레임버퍼
// -------------------------
unsigned int framebuffer;
glGenFramebuffers(1, &amp;framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
// 텍스쳐 프레임버퍼
unsigned int textureColorbuffer;
glGenTextures(1, &amp;textureColorbuffer);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);
// 뎁스/스텐실용 렌더 버퍼 오브젝트
unsigned int rbo;
glGenRenderbuffers(1, &amp;rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, SCR_WIDTH, SCR_HEIGHT);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

//커스텀 프레임버퍼 체킹
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
    cout &lt;&lt; &quot;ERROR::FRAMEBUFFER:: Framebuffer is not complete!&quot; &lt;&lt; endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);</code></pre>
<p>이렇게 텍스쳐 프레임버퍼, 렌더 버퍼 오브젝트를 만들어서
프레임버퍼에 바인딩 시켜주고
제대로 바인딩 되었는지 확인 후 기존 프레임버퍼로 버퍼 바꿔주기!</p>
<h2 id="커스텀-프레임버퍼-렌더링-요구사항">커스텀 프레임버퍼 렌더링 요구사항</h2>
<ol>
<li>커스텀 프레임버퍼에 먼저 렌더링</li>
<li>기존 프레임버퍼로 버퍼 바꾸기</li>
<li>기존 프레임버퍼에 위에서 설명한 컬러 텍스쳐를 사용해 큰 사각형을 그리고 위에 색칠</li>
</ol>
<p>이렇게 하는 이유가 있음</p>
<p>일단 간단하게 다시 설명하면</p>
<ol>
<li>커스텀 프레임버퍼 렌더링</li>
<li>기존 프레임버퍼 변경</li>
<li>기존 프레임버퍼 위에 다시 그리기</li>
</ol>
<p>가 됨</p>
<p>특히, 2번과정에서 포스트 프로세싱 효과를 커스텀 프레임버퍼에 주게되면,
그걸 그대로 기존 프레임 버퍼위에 덧씌우기만 하는거니까
여러 효과를 줄 수 있게 되는거임</p>
<hr>
<p>먼저 기존 프레임버퍼용 vertex shader, fragment shader를 만들어주자</p>
<pre><code class="language-cpp">#version 330 core
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

void main()
{
    TexCoords = aTexCoords;
    gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
}  

//----------------------------------------

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D screenTexture;

void main()
{
    vec3 col = texture(screenTexture, TexCoords).rgb;
    FragColor = vec4(col, 1.0);
} </code></pre>
<p>그냥 간단함
위에서 사용 요구사항에서 설명했듯이
커스텀 프레임버퍼에 렌더링 된거를 
그대로 화면을 가득 채우는 사각형 위에 그릴거임</p>
<p>이제 렌더링 코드를 보자</p>
<pre><code class="language-cpp">float quadVertices[] = 
{
    // positions   // texCoords
    -1.0f,  1.0f,  0.0f, 1.0f,
    -1.0f, -1.0f,  0.0f, 0.0f,
     1.0f, -1.0f,  1.0f, 0.0f,

    -1.0f,  1.0f,  0.0f, 1.0f,
     1.0f, -1.0f,  1.0f, 0.0f,
     1.0f,  1.0f,  1.0f, 1.0f
    };

while (!glfwWindowShouldClose(window))
{
    //.....

    // render
    // ------
    // 커스텀 프레임버퍼
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glEnable(GL_DEPTH_TEST);
    glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    shader.use();
    glm::mat4 model = glm::mat4(1.0f);
    glm::mat4 view = camera.GetViewMatrix();
    glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
    shader.setMat4(&quot;view&quot;, view);
    shader.setMat4(&quot;projection&quot;, projection);
    // cubes
    glBindVertexArray(cubeVAO);
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, cubeTexture);
    model = glm::translate(model, glm::vec3(-1.0f, 0.0f, -1.0f));
    shader.setMat4(&quot;model&quot;, model);
    glDrawArrays(GL_TRIANGLES, 0, 36);
    model = glm::mat4(1.0f);
    model = glm::translate(model, glm::vec3(2.0f, 0.0f, 0.0f));
    shader.setMat4(&quot;model&quot;, model);
    glDrawArrays(GL_TRIANGLES, 0, 36);

    //기존 프레임버퍼
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glDisable(GL_DEPTH_TEST);
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    screenShader.use();
    glBindVertexArray(quadVAO);
    glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
    glDrawArrays(GL_TRIANGLES, 0, 6);


    // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
    // -------------------------------------------------------------------------------
    glfwSwapBuffers(window);
    glfwPollEvents();
}</code></pre>
<p>기존 프레임버퍼를 채울 삼각형 2개로 사각형을 만들어줌
이때 NDC를 이용해서 가득 채우면 됨</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/b2f6843d-b939-4138-82a1-02cd7bb09bcb/image.png" alt=""></p>
<p>그럼 이런 화면이 렌더링됨</p>
<p>근데 뭐가 다른거지??</p>
<p><code>glPolygonMode</code>를 이용해서 와이어프레임을 그려보면 티가남</p>
<p>화면의 오브젝트들이 
<img src="https://velog.velcdn.com/images/vfx_master/post/f027100d-262e-4777-827f-51a24e7beee7/image.png" width = 70%/></p>
<p>와이어프레임으로 표시되지않고
화면 전체가 2개의 삼각형으로 표시되어있음</p>
<img src="https://velog.velcdn.com/images/vfx_master/post/66c983ac-57cc-4873-98a7-a8be0cfc63de/image.png" width="70%"/>


<p>그니까 커스텀 프레임버퍼는 진짜 딱 그리고 빠진거지
그리고 화면에 표시되는건 
기존 프레임버퍼로 복사된 커스텀 프레임버퍼에서 렌더링된 화면인거고ㅇㅇ</p>
<h1 id="포스트-프로세싱-예시">포스트 프로세싱 예시</h1>
<p>뭐 여러 효과가 있음
블룸, 블러, 그레이스케일, 샤프닝 등등...</p>
<p>이 중 커널을 이용한걸 해보겠음</p>
<p><a href="https://velog.io/@vfx_master/%EA%B7%B8%EB%9E%98%ED%94%BD%EC%8A%A4-%EB%B8%94%EB%9F%AC-%EB%B8%94%EB%A3%B8">그래픽스 - 블러, 블룸</a>
여기서 kernel에 대해 설명함ㅇㅇ</p>
<p>kernel은 간단하게 말하면
현재 픽셀이 중앙에 오는 행렬을 이용해서 
주변 픽셀의 색상에 값을 더하고 곱하고 별짓을 하여 만드는 특수한 필터임</p>
<p>fragment shader에서 수행하면 끝인거임ㅇㅇ</p>
<h2 id="sharpen">sharpen</h2>
<pre><code class="language-cpp">#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D screenTexture;

const float offset = 1.0 / 300.0;

void main()
{
    vec2 offsets[9] = vec2[](
            vec2(-offset,  offset), // top-left
            vec2( 0.0f,    offset), // top-center
            vec2( offset,  offset), // top-right
            vec2(-offset,  0.0f),   // center-left
            vec2( 0.0f,    0.0f),   // center-center
            vec2( offset,  0.0f),   // center-right
            vec2(-offset, -offset), // bottom-left
            vec2( 0.0f,   -offset), // bottom-center
            vec2( offset, -offset)  // bottom-right    
    );

    float kernel[9] = float[](
            -1, -1, -1,
            -1,  9, -1,
            -1, -1, -1
    );

    vec3 sampleTex[9];
    for(int i = 0; i &lt; 9; i++)
    {
        sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));
    }
    vec3 col = vec3(0.0);
    for(int i = 0; i &lt; 9; i++)
    col += sampleTex[i] * kernel[i];

    FragColor = vec4(col, 1.0);
}</code></pre>
<p>기본 예시로 sharpen 커널을 이용한거임</p>
<p>offset은 간단하게 수행되는 색상계산의 세기로 
값이 작아지면 더 좁은 범위의 픽셀만 계산 -&gt; 필터 강도 심해짐
값이 커지면 더 넓은 범위 픽셀 계산 -&gt; 필터 강도 약해짐</p>
<table>
<thead>
<tr>
<th>offset : 1/3000</th>
<th>offset : 1/300</th>
</tr>
</thead>
<tbody><tr>
<td><img src="https://velog.velcdn.com/images/vfx_master/post/43b520ad-a9df-4fa4-a319-662dee84fca9/image.png" alt=""></td>
<td><img src="https://velog.velcdn.com/images/vfx_master/post/1e56954a-039d-4d9a-9dd5-25dfe2e58b91/image.png" alt=""></td>
</tr>
</tbody></table>
<p>이런 결과가 나옴</p>
<h2 id="blur">blur</h2>
<pre><code class="language-cpp">float kernel[9] = float[](
    1.0 / 16, 2.0 / 16, 1.0 / 16,
    2.0 / 16, 4.0 / 16, 2.0 / 16,
    1.0 / 16, 2.0 / 16, 1.0 / 16  
);</code></pre>
<p>위 코드에서 kernel만 수정해주면 됨</p>
<p>극단적으로 offset을 1/30으로만 했을때</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/502a9662-cc01-469a-a89b-f5a24b3de2d5/image.png" alt=""></p>
<p>이렇게 심한 블러링이 됨</p>
<h2 id="edge-detecting">edge detecting</h2>
<pre><code class="language-cpp">float kernel[9] = float[](
        1, 1, 1, 
        1, -8, 1,
        1, 1, 1
);</code></pre>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/159a587e-3a31-44a9-b81e-9363538cff92/image.png" alt=""></p>
<p>ㅇㅇ끄읕~~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로그래머스 - 짝지어 제거하기, cpp]]></title>
            <link>https://velog.io/@vfx_master/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A7%9D%EC%A7%80%EC%96%B4-%EC%A0%9C%EA%B1%B0%ED%95%98%EA%B8%B0-cpp</link>
            <guid>https://velog.io/@vfx_master/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A7%9D%EC%A7%80%EC%96%B4-%EC%A0%9C%EA%B1%B0%ED%95%98%EA%B8%B0-cpp</guid>
            <pubDate>Fri, 19 Jun 2026 14:58:35 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/b00cf8a5-2941-4e20-add0-0b3dfdbdb1b1/image.png" alt=""></p>
<h1 id="투포인터는-정답이-아님">투포인터는 정답이 아님</h1>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;string&gt;

using namespace std;

int solution(string s)
{
    int min = 0;
    int l = 0;
    int r = 1;

    while(true)
    {
        if(l &gt;= s.size() &amp;&amp; r &gt;= s.size()) return 1;
        else if(l &lt; s.size() &amp;&amp; r &gt;= s.size()) return 0;

        while(l &gt;= 0 &amp;&amp; s[l] == &#39;*&#39;) l--;

        if(l &lt; 0) { 
            l = r; 
            r++; 
            continue; 
        }

        while(r &lt; s.size() &amp;&amp; s[r] == &#39;*&#39;) r++;

        if(r &gt;= s.size()) return 0;

        if(s[l] == s[r])
        {
            s[l] = &#39;*&#39;;
            s[r] = &#39;*&#39;;

            if(l == min)
            {
                l = r+1;
                min = l;
                r += 2;
            }
            else
            {
                l--;
                r++;
            }
        }
        else
        {
            l = r;
            r++;
        }
    }

    return 1;
}</code></pre>
<p>이런 코드를 짯음</p>
<p>애초에 100만개의 char문자열이라
2중 for문만 피하자는 생각으로 최대한 O(n)에 맞추기 위해 노력했음</p>
<p>결국 반복횟수는 더하고 빼고 합쳐도 선형시간에서 벗어나지 않으니까
추가적으로 공간 복잡도도 고려해서 100만개의 bool배열을 사용하기보다
문자열을 바꾸면서 최대한 공간복잡도도 아끼고자 했음</p>
<p>테스트케이스는 다 맞췄지만 효율성에서 터져나가는걸 보고
내가 잘못됐다는걸 깨달음</p>
<p>문제점은 하나임
최악의 경우 $O(n^2)$이 된다는거임</p>
<p>min으로 더이상 l을 움직일 수 없는경우 새로 이동을 시키고 있음
l과 r이 문자열 처음에서부터 매칭이 시작되었다고 해보자
예를들어 <code>aabbccddeeff...aabcc</code>이런 문자열이었던거지</p>
<p>거의 끝지점에 이상한 매칭이 안되는 문자가 하나 껴있음
이미 n번의 반복횟수를 가진채로 끝까지 갔는데, 
틀린 문자부분만 체킹하는게 아닌 min까지 l이 내려왔다가 다시 매칭이 안된 부분으로 돌아가게 됨</p>
<p>그럼 $O(n^2)$가 되는거지</p>
<h1 id="스택">스택</h1>
<p>스택을 활용하는게 가장 좋은 방법임</p>
<pre><code class="language-cpp">#include &lt;iostream&gt;
#include &lt;string&gt;
#include &lt;stack&gt;

using namespace std;

int solution(string s)
{
    stack&lt;char&gt; st;

    for(char c : s)
    {
        if(!st.empty() &amp;&amp; st.top() == c)
        {
            st.pop();
        }
        else
        {
            st.push(c);
        }
    }

    return st.empty() ? 1 : 0;
}</code></pre>
<p>아이디어도 간단함</p>
<p>스택에서 제일 윗 문자와 현재 주어지는 문자를 비교해서 같다면 pop을 통해 선입후출구조에 맞춰
이번에 매칭된 문자보다 먼저 들어왔지만 매칭되지 않은 문자가 위에 올라오게 되고,
비어있거나 매칭되지 않으면 다시 새로운 주어진 문자를 push하고
그게 아니라면 pop을 하면 됨</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(4) - face culling]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL4-face-culling</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL4-face-culling</guid>
            <pubDate>Thu, 18 Jun 2026 13:04:57 GMT</pubDate>
            <description><![CDATA[<h1 id="face-culling">face culling</h1>
<p>culling을 한국말로 표현하면
솎다임</p>
<p>솎다는 많은 물체에서 몇개를 뽑아 없애버린다는 뉘앙스로
머리 숱을 솎다, 씨앗을 솎다 이런식으로 사용함</p>
<p>그럼 face culling은 무슨뜻일까?
face는 표면, 면이라는 뜻으로 사용되니까
표면을 솎아내는 행위를 face culling이라고 부르는걸 알 수 있음</p>
<h1 id="winding-order">winding order</h1>
<p>감기 순서?
뭘 감는거지? 뭘 감는 순서라는거임?</p>
<pre><code class="language-cpp">float positions[] = 
{
    // positions
    0.0f,  0.5f,  0.0f, //0
    0.0f, -0.5f,  0.0f, //1
    1.0f, -0.5f,  0.0f, //2

    0.0f,  0.5f,  0.0f, //0
    1.0f, -0.5f,  0.0f, //1
    1.0f,  0.5f,  0.0f, //2
};</code></pre>
<p>이렇게 2개의 삼각형으로 사각형을 렌더링한다고 해보자</p>
<p>1, 2번째 모든 삼각형의 3개의 좌표로 이루어진 삼각형을 보면
0-&gt;1-&gt;2로 이동했을때 방향을 그려보면 왼쪽으로 감기고 있다는걸 볼 수 있음</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/ecc4753c-26b2-4895-be25-bbea0547b06c/image.png" alt=""></p>
<p>대충 좌표가 위 사진과 같을때는
저렇게 바뀌는거지
첫번째 삼각형의 정점 좌표를 기준으로 어느방향으로 회전중인지를 찾으면 되는거임</p>
<p>이는 좌표계와 관계없이
유저가 정면으로 삼각형을 정점 순서대로 바라볼때
정점이 어느방향으로 주어졌는지에 따라 달라지는거임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/ba0b07ec-7eba-404d-86fd-b4685366bf7a/image.png" alt=""></p>
<p>이 사진이 딱이야 아주그냥~</p>
<h1 id="face-culling-하는법">face culling 하는법</h1>
<p>기본적으로 gl에서 culling은 disabled된 옵션임</p>
<p>따라서 먼저 설정을 켜줘야함</p>
<pre><code class="language-cpp">glEnabled(GL_CULL_FACE);</code></pre>
<p>그리고 두가지 옵션을 설정해주면 됨</p>
<h2 id="glcullface-glfrontface">glCullFace, glFrontFace</h2>
<p><code>glCullFace</code>는 어느 면을 culling하고 싶냐는거임
즉, 어느 면을 안보이도록 하고 싶냐는거라서,</p>
<table>
<thead>
<tr>
<th>옵션</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>GL_BACK</td>
<td>뒷면 안보이게</td>
</tr>
<tr>
<td>GL_FRONT</td>
<td>앞면 안보이게</td>
</tr>
<tr>
<td>GL_FRONT_AND_BACK</td>
<td>앞뒷면 둘다 안보이게</td>
</tr>
</tbody></table>
<p><code>glFrontFace</code>는 정점이 어느 방향으로 회전하냐는거지</p>
<p><code>GL_CCW</code>가 기본값, <code>GL_CW</code>도 사용가능~</p>
<hr>
<p>이런 오브젝트가 있음</p>
<pre><code class="language-cpp">//...

int main()
{
    //...

    float cubeVertices[] = 
    {
        // positions          // texture Coords
        // Back face
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f, // Bottom-left
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-right
         0.5f, -0.5f, -0.5f,  1.0f, 0.0f, // bottom-right         
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-right
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f, // bottom-left
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f, // top-left
        // Front face
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-left
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f, // bottom-right
         0.5f,  0.5f,  0.5f,  1.0f, 1.0f, // top-right
         0.5f,  0.5f,  0.5f,  1.0f, 1.0f, // top-right
        -0.5f,  0.5f,  0.5f,  0.0f, 1.0f, // top-left
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-left
        // Left face
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // top-right
        -0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-left
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // bottom-left
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // bottom-left
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-right
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // top-right
        // Right face
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // top-left
         0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // bottom-right
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-right         
         0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // bottom-right
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // top-left
         0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-left     
        // Bottom face
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // top-right
         0.5f, -0.5f, -0.5f,  1.0f, 1.0f, // top-left
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f, // bottom-left
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f, // bottom-left
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f, // bottom-right
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f, // top-right
        // Top face
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f, // top-left
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // bottom-right
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f, // top-right     
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f, // bottom-right
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f, // top-left
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f  // bottom-left 
    };

    // cube VAO
    unsigned int cubeVAO, cubeVBO;
    glGenVertexArrays(1, &amp;cubeVAO);
    glGenBuffers(1, &amp;cubeVBO);
    glBindVertexArray(cubeVAO);
    glBindBuffer(GL_ARRAY_BUFFER, cubeVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(cubeVertices), &amp;cubeVertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));

    // load textures
    unsigned int cubeTex = loadTexture(&quot;D:/01_Develope/CPP/learn_opengl/learn_opengl/Resources/container2_diffuse.png&quot;);

    std::vector&lt;glm::vec3&gt; cubePos 
    {
        glm::vec3(-1.0f, 0.0f, 1.0f),//좌하단
        glm::vec3( -1.0f, 0.0f, -1.0f),//좌상단
        glm::vec3( 1.0f, 0.0f, 1.0f),//우하단
        glm::vec3( 1.0f, 0.0f, -1.0f),//우상단
    };


    shader.use();
    shader.setInt(&quot;texture1&quot;, 0);

    // render loop
    while (!glfwWindowShouldClose(window))
    {
        //...

        // draw objects
        shader.use();
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        glm::mat4 view = camera.GetViewMatrix();
        glm::mat4 model = glm::mat4(1.0f);
        shader.setMat4(&quot;projection&quot;, projection);
        shader.setMat4(&quot;view&quot;, view);

        // cubeTex
        glBindVertexArray(cubeVAO);
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, cubeTex);
        for (unsigned int i = 0; i &lt; cubePos.size(); i++)
        {
            model = glm::mat4(1.0f);
            model = glm::translate(model, cubePos[i]);
            shader.setMat4(&quot;model&quot;, model);
            glDrawArrays(GL_TRIANGLES, 0, 36);
        }

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}
//...</code></pre>
<p>대충 이런 코드의 결과임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/78a11433-02b6-43a7-adbc-8f012946513a/image.png" alt=""></p>
<p>이제 위에서 살펴본 culling코드를 추가해보자
먼저 렌더링 바깥에</p>
<pre><code class="language-cpp">glEnable(GL_CULL_FACE);</code></pre>
<p>를 추가해주고..</p>
<pre><code class="language-cpp">//rendering in...

// draw objects
shader.use();
glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
glm::mat4 view = camera.GetViewMatrix();
glm::mat4 model = glm::mat4(1.0f);
shader.setMat4(&quot;projection&quot;, projection);
shader.setMat4(&quot;view&quot;, view);

// grassTex
glBindVertexArray(cubeVAO);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, cubeTex);
for (unsigned int i = 0; i &lt; cubePos.size(); i++)
{
    if (i % 2 == 0)
    {
        glCullFace(GL_BACK);
        glFrontFace(GL_CW);
    }
    else
    {
        glCullFace(GL_FRONT);
    }

    model = glm::mat4(1.0f);
    model = glm::translate(model, cubePos[i]);
    shader.setMat4(&quot;model&quot;, model);
    glDrawArrays(GL_TRIANGLES, 0, 36);
}</code></pre>
<p>렌더링 안쪽에서
cubePos의 인덱스마다 다른 컬링과 winding order를 정해줬음</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/a3c7106e-534d-49e0-995b-46fae635859a/image.png" alt=""></p>
<p>요로코롬 바뀌어버림ㅋㅎㅋㅎ</p>
<h1 id="결론">결론</h1>
<p><code>glFrontFace</code>를 이용해 CCW, CW로 정점의 룰을 정해주고
<code>glCullFace</code>를 이용해 해당 삼각형이 정해진 룰을 기준으로 앞면인지 뒷면인지 판단하고</p>
<p>지정한 컬링룰에 따라 앞면이면 컬링, 뒷면이면 컬링
이런식으로 정해버리면 됨</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(3) - blending]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL3-blending</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL3-blending</guid>
            <pubDate>Thu, 18 Jun 2026 09:54:04 GMT</pubDate>
            <description><![CDATA[<h1 id="discard">discard</h1>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/940c301b-5fda-4458-b16a-27c78f643b99/image.png" alt="">
이런 오브젝트를 띄워볼거임</p>
<p>이미지 중에는 png형식의 이미지가 있음
그리고 png는 alpha값을 담을 수 있는 이미지 형식임</p>
<p>alpha값이 높으면 완전 불투명, alpha값이 낮으면 완전 투명
이렇게 되는거임ㅇㅇ</p>
<p>그럼 어떻게 이런 알파값을 사용할 수 있을까??</p>
<pre><code class="language-cpp">glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);</code></pre>
<p><a href="https://velog.io/@vfx_master/LearnOpenGL4-Texture#image-pixel-data-to-gpu">LearnOpenGL(4) - texture, image pixel data to gpu</a>
이 부분을 보면
<code>glTexImage</code>메서드에 대한 설명이 나옴
저기서 색상 포맷을 <code>RGBA</code>로 바꿔주면 이제 alpha값을 사용할 수 있게 됨</p>
<p>그리고 이제 fragment shader로 가서...</p>
<pre><code class="language-cpp">uniform sampler2D texture1;

void main()
{
    vec4 texColor = texture(texture1, TexCoords);
    FragColor = texColor;
}
</code></pre>
<p>이렇게 사용하면 되지비</p>
<pre><code class="language-cpp">    float grassVertices[] = 
    {
        // positions         // texture Coords (swapped y coordinates because texture is flipped upside down)
        0.0f,  0.5f,  0.0f,  0.0f,  0.0f,
        0.0f, -0.5f,  0.0f,  0.0f,  1.0f,
        1.0f, -0.5f,  0.0f,  1.0f,  1.0f,

        0.0f,  0.5f,  0.0f,  0.0f,  0.0f,
        1.0f, -0.5f,  0.0f,  1.0f,  1.0f,
        1.0f,  0.5f,  0.0f,  1.0f,  0.0f
    };</code></pre>
<p>이렇게 VBO, VAO용 풀때기 정점 속성 매핑해주고..</p>
<pre><code class="language-cpp">    std::vector&lt;glm::vec3&gt; vegetation 
    {
        glm::vec3(-1.5f, 0.0f, -0.48f),
        glm::vec3( 1.5f, 0.0f, 0.51f),
        glm::vec3( 0.0f, 0.0f, 0.7f),
        glm::vec3(-0.3f, 0.0f, -2.3f),
        glm::vec3 (0.5f, 0.0f, -0.6f)
    };

    //render
    while()
    {
        //...

        // draw objects
        shader.use();
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        glm::mat4 view = camera.GetViewMatrix();
        glm::mat4 model = glm::mat4(1.0f);
        shader.setMat4(&quot;projection&quot;, projection);
        shader.setMat4(&quot;view&quot;, view);

        // vegetation
        glBindVertexArray(transparentVAO);
        glBindTexture(GL_TEXTURE_2D, transparentTexture);
        for (unsigned int i = 0; i &lt; vegetation.size(); i++)
        {
            model = glm::mat4(1.0f);
            model = glm::translate(model, vegetation[i]);
            shader.setMat4(&quot;model&quot;, model);
            glDrawArrays(GL_TRIANGLES, 0, 6);
        }

        //...
    }</code></pre>
<p>이렇게 월드좌표로 변환해주면...</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/7ea809a6-2b45-4c0a-a0b2-35f5f1a3c8e3/image.png" alt=""></p>
<p>이런 텍스쳐가 나옴...</p>
<p>이유가 뭘까?</p>
<p>바로 GL은 스스로 어떤 alpha값을 가진 fragment를 없애야하는지 모르는거임</p>
<p>그럼 그걸 어떻게 판단하도록 할 수 있을까?</p>
<p><code>discard</code>라는 glsl명령어을 쓰면 됨</p>
<p>fragment shader에</p>
<pre><code class="language-cpp">if(texColor.a &lt; 0.1) discard;</code></pre>
<p>이 한줄만 추가해주면 됨</p>
<p>따라서 전체 fragment shader코드는</p>
<pre><code class="language-cpp">#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D texture1;

void main()
{
    vec4 texColor = texture(texture1, TexCoords);

    if(texColor.a &lt; 0.1) discard;

    FragColor = texColor;
}</code></pre>
<p>이렇게 되는거임</p>
<p>그냥 재미로 alpha임계값을 <code>&gt;</code>로 해볼까?</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/25454662-e895-4134-a6f8-c5875783979d/image.png" alt="">
제대로 표시되어야할 풀때기만 사라진 모습적인 모습</p>
<p>그럼 제대로 하면?
<img src="https://velog.velcdn.com/images/vfx_master/post/43baeeb6-64aa-4119-9d8c-037a8e360187/image.png" alt=""></p>
<p>ㅇㅇㅇㅇ</p>
<h1 id="색상-혼합">색상 혼합</h1>
<p>색유리 뒤의 오브젝트는 어떻게 색상이 결정되는걸까?</p>
<blockquote>
<p>$C_R = \textcolor{red}C_s * \textcolor{red}F_s + \textcolor{green}C_d + \textcolor{green}F_d$</p>
</blockquote>
<ol>
<li>$C_R$ = 색상 결과</li>
<li>$\textcolor{red}C_s$ = 영향을 주는 물체의 색상</li>
<li>$\textcolor{red}F_s$ = 영향을 주는 물체의 색상의 지수(강하기)</li>
<li>$\textcolor{green}C_d$ = 영향을 받는 물체의 색상</li>
<li>$\textcolor{green}F_d$ = 영향을 받는 물체의 색상의 지수(강하기)<blockquote>
</blockquote>
</li>
</ol>
<p>그럼 factor(지수)는 어떻게 구할까?</p>
<p>영향을 주는 물체의 색상지수는 
해당 텍스쳐의 alpha값을 그대로 사용함</p>
<p>영향을 받는 물체의 색상지수는
1 - (영향을 주는 물체의 색상지수)를 사용함</p>
<p>예를들어
$\textcolor{red}C_s$ : $(\textcolor{red}0, \textcolor{green}1, \textcolor{blue}0, \textcolor{orchid}{0.4})$
$\textcolor{red}F_s$ : $\textcolor{orchid}{0.4}$
$\textcolor{green}C_d$ : $(\textcolor{red}1, \textcolor{green}{0.5}, \textcolor{blue}{0.2}, \textcolor{orchid}{0.8})$
$\textcolor{green}F_d$ : $(1 - \textcolor{orchid}{0.4})$</p>
<p>가 되는거임</p>
<p>따라서 식에 대입해보면</p>
<blockquote>
<p>$\begin{aligned}
C_R &amp;= 
\begin{pmatrix}
\textcolor{red}0\ 
\textcolor{green}1\ 
\textcolor{blue}0\ 
\textcolor{orchid}{0.4}
\end{pmatrix} * 
\textcolor{orchid}{0.4} + 
\begin{pmatrix}
\textcolor{red}1\ 
\textcolor{green}{0.5}\ 
\textcolor{blue}{0.2}\ 
\textcolor{orchid}{0.8}
\end{pmatrix} * 
(1 -\textcolor{orchid}{0.4}) 
\&amp;= 
\begin{pmatrix}
\textcolor{red}0\ 
\textcolor{green}{0.4}\ 
\textcolor{blue}0\ 
\textcolor{orchid}{0.16}
\end{pmatrix} + 
\begin{pmatrix}
\textcolor{red}{0.6}\ 
\textcolor{green}{0.3}\ 
\textcolor{blue}{0.12}\ 
\textcolor{orchid}{0.48}
\end{pmatrix}
\&amp;= 
\begin{pmatrix}
\textcolor{red}{0.6}\ 
\textcolor{green}{0.7}\ 
\textcolor{blue}{0.12}\ 
\textcolor{orchid}{0.64}
\end{pmatrix}
\end{aligned}
$</p>
</blockquote>
<p>요로코롬 되어버리는겨~</p>
<p>그럼 이걸 어떻게 GL에 적용?</p>
<h2 id="glblendfunc">glBlendFunc</h2>
<p>먼저</p>
<pre><code class="language-cpp">glEnabled(GL_BLEND);</code></pre>
<p>로 BLEND옵션을 켜주자</p>
<p>그리고 사용할것이</p>
<p><code>glBlendFunc</code>임</p>
<pre><code class="language-cpp">glBlendFunc(GLenum sfactor, GLenum dfactor);</code></pre>
<p>여기서 GLenum으로 사용할 수 있는 값들이 있음</p>
<h3 id="블렌딩-팩터-옵션">블렌딩 팩터 옵션</h3>
<table>
<thead>
<tr>
<th align="left">옵션</th>
<th align="left">가치 / 중요성</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><code>GL_ZERO</code></td>
<td align="left">인수는 0과 같습니다.</td>
</tr>
<tr>
<td align="left"><code>GL_ONE</code></td>
<td align="left">인수는 1과 같습니다.</td>
</tr>
<tr>
<td align="left"><code>GL_SRC_COLOR</code></td>
<td align="left">팩터는 소스 색상 벡터 $C_{source}$와 같습니다.</td>
</tr>
<tr>
<td align="left"><code>GL_ONE_MINUS_SRC_COLOR</code></td>
<td align="left">팩터는 1에서 소스 색상 벡터를 뺀 값($1 - C_{source}$)과 같습니다.</td>
</tr>
<tr>
<td align="left"><code>GL_DST_COLOR</code></td>
<td align="left">팩터는 목적지 색상 벡터 $C_{destination}$과 같습니다.</td>
</tr>
<tr>
<td align="left"><code>GL_ONE_MINUS_DST_COLOR</code></td>
<td align="left">팩터는 1에서 목적지 색상 벡터를 뺀 값($1 - C_{destination}$)과 같습니다.</td>
</tr>
<tr>
<td align="left"><code>GL_SRC_ALPHA</code></td>
<td align="left">팩터는 소스 색상 벡터 $C_{source}$의 알파(alpha) 성분과 같습니다.</td>
</tr>
<tr>
<td align="left"><code>GL_ONE_MINUS_SRC_ALPHA</code></td>
<td align="left">팩터는 소스 색상 벡터 $C_{source}$의 1 - 알파(1 - alpha) 성분과 같습니다.</td>
</tr>
<tr>
<td align="left"><code>GL_DST_ALPHA</code></td>
<td align="left">팩터는 목적지 색상 벡터 $C_{destination}$의 알파(alpha) 성분과 같습니다.</td>
</tr>
<tr>
<td align="left"><code>GL_ONE_MINUS_DST_ALPHA</code></td>
<td align="left">팩터는 목적지 색상 벡터 $C_{destination}$의 1 - 알파(1 - alpha) 성분과 같습니다.</td>
</tr>
<tr>
<td align="left"><code>GL_CONSTANT_COLOR</code></td>
<td align="left">팩터는 상수 색상 벡터 $C_{constant}$와 같습니다.</td>
</tr>
<tr>
<td align="left"><code>GL_ONE_MINUS_CONSTANT_COLOR</code></td>
<td align="left">팩터는 1에서 상수 색상 벡터를 뺀 값($1 - C_{constant}$)과 같습니다.</td>
</tr>
<tr>
<td align="left"><code>GL_CONSTANT_ALPHA</code></td>
<td align="left">팩터는 상수 색상 벡터 $C_{constant}$의 알파(alpha) 성분과 같습니다.</td>
</tr>
<tr>
<td align="left"><code>GL_ONE_MINUS_CONSTANT_ALPHA</code></td>
<td align="left">팩터는 상수 색상 벡터 $C_{constant}$의 1 - 알파(1 - alpha) 성분과 같습니다.</td>
</tr>
</tbody></table>
<p>즉, 위 옵션에서 살펴보면
우리가 사용할 두 옵션은 
<code>GL_SRC_ALPHA</code>와 <code>GL_ONE_MINUS_SRC_COLOR</code>가 되겠음~~</p>
<h3 id="glblendfuncseparate">glBlendFuncSeparate</h3>
<p><code>glBlendFunc</code>는 색상의 RGB와 A 모두에 동시적으로 적용되는 blending옵션을 주는 메서드라면,,,</p>
<p><code>glBlendFuncSeparate</code>는 RGB와 A에 따로따로 적용되는 blending옵션을 줄 수 있음</p>
<pre><code class="language-cpp">glBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha)</code></pre>
<p>이렇게!</p>
<h3 id="glblendequation">glBlendEquation</h3>
<blockquote>
<p>$C_R = \textcolor{red}C_s * \textcolor{red}F_s + \textcolor{green}C_d + \textcolor{green}F_d$</p>
</blockquote>
<p>이 식이 색상 결정하는 식이란걸 위에서 알려줘씀</p>
<p>그럼 <code>glBlendEquation</code>는 뭐하는걸까</p>
<blockquote>
<p>$C_R = \textcolor{red}C_s * \textcolor{red}F_s$ OPERATOR $\textcolor{green}C_d + \textcolor{green}F_d$</p>
</blockquote>
<p>저 OPERATOR를 결정짓는거임</p>
<table>
<thead>
<tr>
<th align="left">상수</th>
<th align="left">설명</th>
<th align="left">연산 공식</th>
</tr>
</thead>
<tbody><tr>
<td align="left">GL_FUNC_ADD</td>
<td align="left">소스와 대상의 결과를 더함 (기본값)</td>
<td align="left">$Result = (Src * sfactor) + (Dst * dfactor)$</td>
</tr>
<tr>
<td align="left">GL_FUNC_SUBTRACT</td>
<td align="left">소스 결과에서 대상 결과를 뺌</td>
<td align="left">$Result = (Src * sfactor) - (Dst * dfactor)$</td>
</tr>
<tr>
<td align="left">GL_FUNC_REVERSE_SUBTRACT</td>
<td align="left">대상 결과에서 소스 결과를 뺌</td>
<td align="left">$Result = (Dst * dfactor) - (Src * sfactor)$</td>
</tr>
<tr>
<td align="left">GL_MIN</td>
<td align="left">소스와 대상 중 더 작은 값을 선택</td>
<td align="left">$Result = min((Src * sfactor), (Dst * dfactor))$</td>
</tr>
<tr>
<td align="left">GL_MAX</td>
<td align="left">소스와 대상 중 더 큰 값을 선택</td>
<td align="left">$Result = max((Src * sfactor), (Dst * dfactor))$</td>
</tr>
</tbody></table>
<hr>
<h2 id="depth-error">depth error</h2>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/0791850f-3211-4365-8978-20054b924889/image.png" alt="">
이런 창문을 그릴거임</p>
<pre><code class="language-cpp">    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    unsigned int windowTex = loadTexture(&quot;D:/01_Develope/CPP/learn_opengl/learn_opengl/Resources/blend/blending_transparent_window.png&quot;);

    while()
    {
        //...

        // grassTex
        glBindVertexArray(transparentVAO);
        glBindTexture(GL_TEXTURE_2D, grassTex);
        for (unsigned int i = 0; i &lt; vegetation.size(); i++)
        {
            model = glm::mat4(1.0f);
            model = glm::translate(model, vegetation[i]);
            shader.setMat4(&quot;model&quot;, model);
            glDrawArrays(GL_TRIANGLES, 0, 6);
        }

        //windowTex
        glBindVertexArray(transparentVAO);
        glBindTexture(GL_TEXTURE_2D, windowTex);
        for (unsigned int i = 0; i &lt; vegetation.size(); i++)
        {
            model = glm::mat4(1.0f);
            glm::vec3 newVec(vegetation[i].x, vegetation[i].y, vegetation[i].z + 1);
            model = glm::translate(model, newVec);
            shader.setMat4(&quot;model&quot;, model);
            glDrawArrays(GL_TRIANGLES, 0, 6);
        }

        //...
    }</code></pre>
<p>이렇게 BlendFunc와 grass보다 z축으로 약간 앞쪽에 창문을 그림</p>
<p>그럼..
<img src="https://velog.velcdn.com/images/vfx_master/post/71322ad6-aa11-4bb6-8212-1c63384f9948/image.png" alt=""></p>
<p>이런 텍스쳐가 렌더링 되는데,
잘보면 창문쪽이 이상함</p>
<p>이렇게 depth가 제대로 계산되고 있지 않은 모습을 볼 수 있음</p>
<p>depth test는 기본적으로 텍스쳐의 alpha를 고려하지 않음
따라서 alpha로 인해 투명하게 적용되어야 하는 부분이 고려되지 않아
저렇게 창문이 서로 block이 된것처럼 렌더링 되는거임</p>
<p>그럼 이걸 어떻게 해결하나...?</p>
<h2 id="depth-error해결법">depth error해결법</h2>
<p>먼저 순서가 중요함</p>
<ol>
<li>불투명한 모든 오브젝트를 렌더링함</li>
<li>투명한 모든 오브젝트를 <code>정렬</code>함</li>
<li>투명한 모든 오브젝트를 렌더링함</li>
</ol>
<p>이때 정렬이 중요함
카메라 위치에서부터 먼곳에 있는 오브젝트 -&gt; 가까운곳에 있는 오브젝트 순서로 정렬이 되어야함</p>
<p>아이디어는 간단함
cpp의 map을 이용하는거임</p>
<p>map은 기본적으로 sorted map이라서, key값을 기준으로 오름차순 정렬이 되며 값이 추가됨</p>
<pre><code class="language-cpp">std::vector&lt;glm::vec3&gt; windows 
{
    glm::vec3(-1.5f, 0.0f, 0.52f),
    glm::vec3( 1.5f, 0.0f, 1.51f),
    glm::vec3( 0.0f, 0.0f, 1.7f),
    glm::vec3(-0.3f, 0.0f, -1.3f),
    glm::vec3 (0.5f, 0.0f, 0.4f)
};

std::map&lt;float, glm::vec3&gt; sorted;
for (unsigned int i = 0; i &lt; windows.size(); i++)
{
    float distance = glm::length(camera.Position - windows[i]);
    sorted[distance] = windows[i];
}</code></pre>
<p>먼저 windows위치변환을 위한 배열을 만들어주고
map을 이용해 오름차순으로 정렬해줌</p>
<p>문제가 있음</p>
<p>위에서 분명히 먼 거리에 있는 오브젝트부터 렌더링한다고 했는데?
지금 오름차순이면 가까운거리-&gt;먼거리 순서로 정렬되어 있자나?</p>
<p>그거 그냥 cpp의 rbegin, rend를 사용하면
역순으로 iteration가능해짐</p>
<pre><code class="language-cpp">for(std::map&lt;float,glm::vec3&gt;::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it) 
{
    model = glm::mat4(1.0f);
    model = glm::translate(model, it-&gt;second);                
    shader.setMat4(&quot;model&quot;, model);
    glDrawArrays(GL_TRIANGLES, 0, 6);
} </code></pre>
<p>그래서 이렇게 하면됨</p>
<p>그럼 대충 코드는</p>
<pre><code class="language-cpp">//...

int main()
{
    //...

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

     // build and compile shaders
    // -------------------------
    Shader shader(&quot;VertexShaders/BlendShader.vsh&quot;, &quot;FragmentShaders/BlendShader.fsh&quot;);

    float grassVertices[] = 
    {
        // positions         // texture Coords (swapped y coordinates because texture is flipped upside down)
        0.0f,  0.5f,  0.0f,  0.0f,  0.0f,
        0.0f, -0.5f,  0.0f,  0.0f,  1.0f,
        1.0f, -0.5f,  0.0f,  1.0f,  1.0f,

        0.0f,  0.5f,  0.0f,  0.0f,  0.0f,
        1.0f, -0.5f,  0.0f,  1.0f,  1.0f,
        1.0f,  0.5f,  0.0f,  1.0f,  0.0f
    };

    // transparent VAO
    unsigned int transparentVAO, transparentVBO;
    glGenVertexArrays(1, &amp;transparentVAO);
    glGenBuffers(1, &amp;transparentVBO);

    glBindVertexArray(transparentVAO);
    glBindBuffer(GL_ARRAY_BUFFER, transparentVBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(grassVertices), grassVertices, GL_STATIC_DRAW);

    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(1);
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glBindVertexArray(0);

    // load textures
    unsigned int grassTex = loadTexture(&quot;D:/01_Develope/CPP/learn_opengl/learn_opengl/Resources/blend/grass.png&quot;);
    unsigned int windowTex = loadTexture(&quot;D:/01_Develope/CPP/learn_opengl/learn_opengl/Resources/blend/blending_transparent_window.png&quot;);

    std::vector&lt;glm::vec3&gt; vegetation 
    {
        glm::vec3(-1.5f, 0.0f, -0.48f),
        glm::vec3( 1.5f, 0.0f, 0.51f),
        glm::vec3( 0.0f, 0.0f, 0.7f),
        glm::vec3(-0.3f, 0.0f, -2.3f),
        glm::vec3 (0.5f, 0.0f, -0.6f)
    };

    std::vector&lt;glm::vec3&gt; windows 
    {
        glm::vec3(-1.5f, 0.0f, 0.52f),
        glm::vec3( 1.5f, 0.0f, 1.51f),
        glm::vec3( 0.0f, 0.0f, 1.7f),
        glm::vec3(-0.3f, 0.0f, -1.3f),
        glm::vec3 (0.5f, 0.0f, 0.4f)
    };

    shader.use();
    shader.setInt(&quot;texture1&quot;, 0);

    // render loop
    while (!glfwWindowShouldClose(window))
    {
        //...

        // draw objects
        shader.use();
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        glm::mat4 view = camera.GetViewMatrix();
        glm::mat4 model = glm::mat4(1.0f);
        shader.setMat4(&quot;projection&quot;, projection);
        shader.setMat4(&quot;view&quot;, view);

        // grassTex
        glBindVertexArray(transparentVAO);
        glBindTexture(GL_TEXTURE_2D, grassTex);
        for (unsigned int i = 0; i &lt; vegetation.size(); i++)
        {
            model = glm::mat4(1.0f);
            model = glm::translate(model, vegetation[i]);
            shader.setMat4(&quot;model&quot;, model);
            glDrawArrays(GL_TRIANGLES, 0, 6);
        }

        //sort window Pos
        std::map&lt;float, glm::vec3&gt; sorted;
        for (unsigned int i = 0; i &lt; windows.size(); i++)
        {
            float distance = glm::length2(camera.Position - windows[i]);
            sorted[distance] = windows[i];
        }

        //windowTex
        glBindVertexArray(transparentVAO);
        glBindTexture(GL_TEXTURE_2D, windowTex);
        for(std::map&lt;float,glm::vec3&gt;::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it) 
        {
            model = glm::mat4(1.0f);
            model = glm::translate(model, it-&gt;second);                
            shader.setMat4(&quot;model&quot;, model);
            glDrawArrays(GL_TRIANGLES, 0, 6);
        } 

        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    glfwTerminate();
    return 0;
}

//...</code></pre>
<p>요로코롬 되고...</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/318c68c2-979d-474f-84ff-616c5a805b48/image.png" alt=""></p>
<p>이렇게 잘 되어버림~~</p>
<h2 id="추가적으로">추가적으로,,,</h2>
<p>order independent transparency라는 기법이 있음</p>
<p>지금은 정렬을 하고 있는데,
이게 계속 위치가 변경되는 오브젝트라면
map에 추가하는 연산으로 인해 코드가 길어지고 성능이 안좋아짐</p>
<p>그래서 사용되는게 order independent transparency임</p>
<p>순서에 구애받지 않는 투명성이라고 직역가능한데,
이건 나중에 리얼타임렌더링 공부할때 depth buffer를 더 자세하게 공부하면
그때 다루도록 하겠음!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(2) - Stencil  test]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL2-Stencil-test</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL2-Stencil-test</guid>
            <pubDate>Tue, 16 Jun 2026 14:33:08 GMT</pubDate>
            <description><![CDATA[<h1 id="stencil">stencil?</h1>
<p>unity나 뭐 다른엔진 사용해보면
오브젝트의 특정 부분만 가리고 싶을때 mask라는걸 사용할거임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/fdeaaf74-2327-4b02-855a-87f443625cc2/image.png" alt=""></p>
<p>이렇게 특정 부분에서 fragment를 지워주는게 stencil임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/71453d43-e97e-4309-8b73-6b83871312d5/image.png" alt=""></p>
<p>이렇게 stencil buffer를 통해
screen space에 출력되어야할 fragment들을 지워버리는거지비 ㅇㅇ</p>
<p>이런 작업을 수행하는 연산을
stencil test라고 함</p>
<p>stencil test는 fragment shader이후에 실행되고,
early depth test가 아닌 경우에</p>
<p><code>fragment shader -&gt; stencil test -&gt; depth test</code>순서로 실행됨</p>
<p>stencil buffer는 보통 8비트의 값을 가짐
따라서 픽셀당 $2^8$의 경우의 수를 가지므로 최대 총 256개의 서로다른 스텐실 값을 가지게 되는거지</p>
<h1 id="stencil-buffer-사용법">stencil buffer 사용법</h1>
<p>간단함</p>
<ol>
<li>stencil buffer를 만들어줌</li>
<li>buffer를 쓰기형식으로 바꾸어, 원하는 값을 채워넣을 준비</li>
<li>값을 채워넣음</li>
<li>buffer를 읽기형식으로 바꾸어, 수정이 안되도록 잠금</li>
<li>buffer를 이용해서 masking</li>
</ol>
<h2 id="stencil-mask">stencil mask</h2>
<p>depth buffer처럼
<code>glEnable</code>을 통해 먼저 설정을 enable시켜주고
매 프레임마다 stencil buffer를 초기화해주면 됨</p>
<pre><code class="language-cpp">glEnable(GL_STENCIL_TEST); 
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);</code></pre>
<p><a href="https://velog.io/@vfx_master/Advanced-OpenGL-Depth-test#gldepthmask">Advenced OpenGL(1) - Depth test</a></p>
<p>여기서 <code>glDepthMask</code>를 보면
<code>GL_TRUE</code>, <code>GL_FALSE</code>로 DepthBuffer에
쓰기, 읽기로 바꿔주는걸 확인할 수 있음</p>
<p>stencil test도 마찬가지임</p>
<p>스텐실 값과 <code>AND</code>연산을 통해 연산이 수행될 비트마스크를 설정할 수 있음</p>
<pre><code class="language-cpp">glStencilMask(0xFF);
glStencilMask(0x00);</code></pre>
<ul>
<li><code>0xFF</code>(255) : 쓰기 허용, 특정 값 n과 AND연산을 하면 n이 됨</li>
<li><code>0x00</code>(0) : 쓰기 차단, 어떠한 값 n과 AND연산을 해도 0이됨</li>
</ul>
<h2 id="glstencilfunc">glStencilFunc</h2>
<p>stencil설정에 필요한 메서드임</p>
<pre><code class="language-cpp">glStencilFunc(GLenum func, GLint ref, GLuint mask);</code></pre>
<p>이렇게 되어있음</p>
<ul>
<li>func : <code>glDepthFunc</code>에 사용되는 값과 의미가 동일함</li>
<li>ref : stencil test에 이용될 stencil 참조 값임</li>
<li>mask : 지정된 ref 스텐실 값과 AND연산이 수행되는 마스크, 
기본값은 <code>0xFF</code></li>
</ul>
<p><code>glStencilFunc</code>는 스텐실 버퍼에 따라 특정 fragment를 통과시킬지 없애버릴지 결정을 하는거임</p>
<h2 id="glstencilop">glStencilOp</h2>
<p>stencil test의 성공여부에 따라 스텐실 버퍼를 어떻게 업데이트 할건지 정할 수 있음</p>
<pre><code class="language-cpp">glStencilOp(GLenum sfail, GLenum dpfail, GLenum dppass)</code></pre>
<ul>
<li>sfail : stencil test실패시의 버퍼 업데이트</li>
<li>dpfail : stencil test는 통과했지만, depth test 실패시의 버퍼 업데이트</li>
<li>dppass : stencil test, depth test모두 성공시의 버퍼 업데이트</li>
</ul>
<p>각각의 옵션에 사용할 수 있는 값은 다음과 같음</p>
<table>
<thead>
<tr>
<th>조치</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>GL_KEEP</td>
<td>현재 저장된 스텐실 값을 유지</td>
</tr>
<tr>
<td>GL_ZERO</td>
<td>스텐실 값을 0으로 설정</td>
</tr>
<tr>
<td>GL_REPLACE</td>
<td>스텐실 값을 glStencilFunc로 설정된 참조 값으로 대체</td>
</tr>
<tr>
<td>GL_INCR</td>
<td>스텐실 값이 최대값보다 낮으면 1을 증가</td>
</tr>
<tr>
<td>GL_INCR_WRAP</td>
<td>GL_INCR과 동일하지만 최대값(기본 8비트 <code>0xFF</code>)을 초과하면 0으로 돌아옴</td>
</tr>
<tr>
<td>GL_DECR</td>
<td>스텐실 값이 최소값보다 높으면 1을 감소</td>
</tr>
<tr>
<td>GL_DECR_WRAP</td>
<td>GL_DECR과 동일하지만 0보다 낮아지면 최대값으로 돌아옴</td>
</tr>
<tr>
<td>GL_INVERT</td>
<td>현재 스텐실 버퍼 값을 비트 단위로 반전</td>
</tr>
</tbody></table>
<p>기본옵션은 순서대로
<code>GL_KEEP</code>, <code>GL_KEEP</code>, <code>GL_KEEP</code>임</p>
<h1 id="outline그리기">outline그리기</h1>
<p>stencil 예시로 외곽선 그리는걸 해볼거임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/81f823fc-274a-48cd-921c-ad60afd1cb26/image.png" alt=""></p>
<p>이 배낭의 외곽선을 흰색으로 그려보도록 하게뜸</p>
<p>먼저 rendering 외부에 stencil 설정을 해줌</p>
<pre><code class="language-cpp">glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);</code></pre>
<p>전략은 다음과 같음</p>
<ol>
<li>stencil test활성화</li>
<li>ref로 넘겨지는 값(<code>1</code>)을 <code>0xFF</code>와 비교한 후, 
도출된 stencil 결과 값이 저장된 stencil 값과 다르면
(<code>GL_NOTEQUAL</code>) stencil test통과</li>
<li>sfail시 stencil buffer유지, 
dpfail시 stencil buffer유지, 
stencil, depth test성공시 stencil buffer교체</li>
</ol>
<p>그리고 외곽선을 위한 쉐이더를 만들음</p>
<pre><code class="language-cpp">//vsh
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

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

void main()
{
    TexCoords = aTexCoords;
    gl_Position = projection * view * model * vec4(aPos, 1.0f);
}

//----------------------------

//fsh
#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}</code></pre>
<p>이제 while문에서 수정만 해주면 됨</p>
<pre><code class="language-cpp">    while (!glfwWindowShouldClose(window))
    {
        // ...

        // render
        // ------
        glClearColor(0.05f, 0.05f, 0.05f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

        //1. 기본배낭 shader 설정
        modelMatShader.use();
        glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        glm::mat4 view = camera.GetViewMatrix();
        glm::mat4 model = glm::mat4(1.0f);
        modelMatShader.setMat4(&quot;projection&quot;, projection);
        modelMatShader.setMat4(&quot;view&quot;, view);
        modelMatShader.setMat4(&quot;model&quot;, model);

        //1. 기본크기 배낭 렌더링
        glStencilFunc(GL_ALWAYS, 1, 0xFF);
        glStencilMask(0xFF);

        myModel.Draw(modelMatShader);

        //2. 약간 큰 크기 배낭 shader 설정
        float scale = 1.01f;
        stencilShader.use();
        model = glm::scale(model, glm::vec3(scale, scale, scale));
        stencilShader.setMat4(&quot;model&quot;, model);
        stencilShader.setMat4(&quot;view&quot;, view);
        stencilShader.setMat4(&quot;projection&quot;, projection);

        //2. 외곽선용 약간 큰 크기 배낭 렌더링
        glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
        glStencilMask(0x00); 

        //3
        glDisable(GL_DEPTH_TEST);

        myModel.Draw(stencilShader);

        //mode redering logic...

        //4
        glStencilMask(0xFF);
        glStencilFunc(GL_ALWAYS, 0, 0xFF);
        glEnable(GL_DEPTH_TEST);

        //...
    }</code></pre>
<p>1,2,3,4가 있지?
그걸 차례대로 설명해줌</p>
<pre><code class="language-cpp">        //1. 기본크기 배낭 렌더링
        glStencilFunc(GL_ALWAYS, 1, 0xFF);
        glStencilMask(0xFF);</code></pre>
<ol>
<li>그려질 모델에 대하여 stencil 설정하기
<code>1</code> &amp; <code>0xFF</code>를 한 값을 <code>GL_ALWAYS</code>전략을 이용해 stencil buffer 채우는 옵션 설정
추가로 stencil 쓰기 허용을 함으로써 현재 그려질 모델을 이용해 stencil buffer 채우기~~</li>
</ol>
<pre><code class="language-cpp">        //2. 외곽선용 약간 큰 크기 배낭 렌더링
        glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
        glStencilMask(0x00);</code></pre>
<ol start="2">
<li>그려질 모델에 대하여 stencil 설정하기
<code>1</code> &amp; <code>0xFF</code>를 한 값을 <code>GL_NOTEQUAL</code>전략을 이용해 stencil buffer 채우는 옵션 설정
추가로 stencil 쓰기를 허용하지 않음으로써 현재 그려질 모델은 stencil buffer에 영향을 주지 않음<ul>
<li>현재 stencil buffer의 값과 다른 값에 대해서만 통과 설정</li>
</ul>
</li>
</ol>
<pre><code class="language-cpp">        //3
        glDisable(GL_DEPTH_TEST);</code></pre>
<ol start="3">
<li>depth test를 일시적으로 비활성화
외곽선을 그리게 될 모델은 원본 모델과 비슷한 크기, 위치에 그려짐
따라서 depth test가 수행되면 이상하게 모델링 될 가능성이 생김</li>
</ol>
<pre><code class="language-cpp">        //4
        glStencilMask(0xFF);
        glStencilFunc(GL_ALWAYS, 0, 0xFF);
        glEnable(GL_DEPTH_TEST);</code></pre>
<ol start="4">
<li>하나의 렌더링 프레임 종료 전 수행해야되는 옵션들임<ul>
<li><code>glStencilMask</code> : <code>0xFF</code>로 돌려놓음으로써 다음 프레임에 버퍼를 clear할때, 성공적으로 쓰기가 되도록 만들어놓음</li>
<li><code>glStencilFunc</code> : 매 프레임 초기에 buffer clear를 하기때문에 필요는 없음</li>
<li><code>glEnable</code> : 다음 프레임 실행될때 depth test가 꺼져있으면 buffer clear를 하나마나 소용이 없어 이상하게 렌더링 됨</li>
</ul>
</li>
</ol>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/d7df859d-6963-435b-9f85-459b87f03fcd/image.png" alt=""></p>
<p>크으~~~~</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Advanced OpenGL(1) - Depth test]]></title>
            <link>https://velog.io/@vfx_master/Advanced-OpenGL-Depth-test</link>
            <guid>https://velog.io/@vfx_master/Advanced-OpenGL-Depth-test</guid>
            <pubDate>Tue, 16 Jun 2026 11:15:02 GMT</pubDate>
            <description><![CDATA[<h1 id="depth-test">depth test</h1>
<p><a href="https://velog.io/@vfx_master/%EB%82%98%EB%A7%8C%EC%9D%98-tiny-renderer-%EB%A7%8C%EB%93%A4%EA%B8%B0-4-Z-Buffer-%EC%95%A0%ED%8C%8C%EC%9D%B8%EC%95%84%ED%95%80-%EB%B3%80%ED%99%98">나만의 tiny renderer 만들기(4) - zbuffer, 애파인변환</a></p>
<p>여기서 설명하는 zbuffer의 원리를 이용한게
DepthBuffer(Z-Buffer)임</p>
<p>Depth test라는건
프래그먼트의 z를 z-buffer에 기록하기 위해 수행하는 연산을 의미함</p>
<p>이론적으로는
depth test는 fragment shader가 수행된 후, 
screen space에서 stencil test이후 실행됨</p>
<p>위 링크에서 볼 수 있다싶이
depth test를 거치는 과정은 다음과 같음</p>
<ol>
<li>fragment가 z-buffer에 기록된 값보다 우선순위가 낮은 z값을 가지고 있으면 아무것도 안하고 출력 취소</li>
</ol>
<ol>
<li>fragment가 z-buffer에 기록된 값보다 우선순위가 높은 z값을 가지고 있으면</li>
<li>z-buffer를 현재 fragment로 갱신해주고,</li>
<li>현재 fragment를 렌더링</li>
</ol>
<p>이때 fragment들에 대한 depth test는 픽셀단위로 됨
그리고 위의 링크에서는 <code>uint8_t</code>를 사용했지만
실제에서는 16, 24(정배), 32비트 부동소수점를 사용함</p>
<h1 id="early-depth-test-early-z">early depth test, early Z</h1>
<p>이론상의 depth test는 fragment shader이후에 수행되어
출력되지 않을 fragment를 결정짓고 출려한다고 했음</p>
<p>근데 이렇게 되면
fragment shader할 필요가 없는 부분에 대하여 쉐이딩을 하게 되니
성능상의 이슈가 있을수밖에 없음</p>
<p>그래서 등장한 개념이 early depth test임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/9383d007-c1cf-4b42-9955-707b0b434400/image.png" alt=""></p>
<p>다시 이 사진을 보자</p>
<p>fragment shader에서는 주로 fragment의 색상을 결정지음
그리고 정점의 변환은 주로 vertex shader, geometry shader에서 일어남</p>
<p>그럼 정점 변환이 일어난 후에 depth test를 해버려서 무거운 색상 계산 shader인 fragment shader에서의 부담을 줄일 수 있지 않을까?</p>
<p>맞음!</p>
<p>그래서 등장한게 eraly depth test임</p>
<p>래스터라이저에서는 shape assembly에서 정점끼리 연결된 면이 넘어오면 
그 면에 대한 각 픽셀의 fragment를 만들게 됨
그리고 early depth test를 수행시켜서 
fragment shader이전에 버려야할 fragment를 버리는거지 ㅇㅇ</p>
<p>다만 early Z를 지양해할 경우도 있음</p>
<blockquote>
<ol>
<li>fragment shader에서 직접적으로 fragment의 z를 수정할때</li>
<li>RGBA로 alpha값이 보간되어야하는 fragment에 대해서는 동작하지 않을 수 있음</li>
</ol>
</blockquote>
<hr>
<h1 id="depth-buffer-사용">depth buffer 사용</h1>
<p>depth buffer를 사용하는건 간단함</p>
<pre><code class="language-cpp">//rendering 전 준비단계
glEnable(GL_DEPTH_TEST);

//rendering단계
while(...)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //optional
    //glDepthMask(GL_FALSE);
    //glDepthMask(GL_TRUE);
}</code></pre>
<p>위에서 glDepthMask라는 옵션을 볼 수 있음</p>
<h2 id="gldepthmask">glDepthMask</h2>
<p><code>glDepthMask</code>는 depth test를 수행하지만, 
프래그먼트의 z 값은 depth test이후의 z buffer를 갱신할때 사용하지 않을때 사용하는거임</p>
<p>예를들어 유리창이 있을수 있음</p>
<ol>
<li>유리창이 있고, 유리창 뒤에 책상이 있음</li>
<li>유리창과 책상은 depth test를 통해 유리창이 더 앞에 있다는걸 확인함</li>
<li>유리창은 반투명한 물체로 다른 refraction, reflection등이 존재함</li>
<li>z-buffer에 유리창의 z는 등록하지 않음으로 유리창 너머의 책상은 유리창의 쉐이더 로직을 통해 색상이 결정되어 보이게 됨</li>
</ol>
<table>
<thead>
<tr>
<th>상태</th>
<th>설명</th>
<th>결과</th>
</tr>
</thead>
<tbody><tr>
<td><code>GL_TRUE</code> (기본값)</td>
<td>깊이 버퍼 쓰기 허용</td>
<td>테스트를 통과한 프래그먼트의 깊이 값이 깊이 버퍼에 저장</td>
</tr>
<tr>
<td><code>GL_FALSE</code></td>
<td>깊이 버퍼 쓰기 차단</td>
<td>테스트를 통과해도 깊이 버퍼는 업데이트되지 않움 (읽기 전용 모드)</td>
</tr>
</tbody></table>
<p>따라서 
<code>GL_TRUE</code>를 하게 되면
zbuffer에도 갱신이 일어나서 완전 불투명한 물체를 렌더링 할때 사용하게 되고
<code>GL_FALSE</code>를 하게되면
zbuffer에는 갱신이 되지 않지만 물체끼리의 z index비교는 수행하여 반투명한 물체의 back-to-front렌더링을 수행하게 됨</p>
<hr>
<h1 id="gldepthfunc">glDepthFunc</h1>
<p>glDepthTest의 테스팅 방식을 결정짓는 메서드임</p>
<p><code>glDepthFunc(GL_LESS)</code>가 기본값</p>
<table>
<thead>
<tr>
<th>함수</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>GL_ALWAYS</td>
<td>깊이 테스트가 항상 통과</td>
</tr>
<tr>
<td>GL_NEVER</td>
<td>깊이 테스트가 절대 통과되지 않음</td>
</tr>
<tr>
<td>GL_LESS</td>
<td>프래그먼트의 깊이 값이 저장된 깊이 값보다 작으면 통과</td>
</tr>
<tr>
<td>GL_EQUAL</td>
<td>프래그먼트의 깊이 값이 저장된 깊이 값과 같으면 통과</td>
</tr>
<tr>
<td>GL_LEQUAL</td>
<td>프래그먼트의 깊이 값이 저장된 깊이 값보다 작거나 같으면 통과</td>
</tr>
<tr>
<td>GL_GREATER</td>
<td>프래그먼트의 깊이 값이 저장된 깊이 값보다 크면 통과</td>
</tr>
<tr>
<td>GL_NOTEQUAL</td>
<td>프래그먼트의 깊이 값이 저장된 깊이 값과 다르면 통과</td>
</tr>
<tr>
<td>GL_GEQUAL</td>
<td>프래그먼트의 깊이 값이 저장된 깊이 값보다 크거나 같으면 통과</td>
</tr>
</tbody></table>
<hr>
<h1 id="z-index-계산법">Z index 계산법</h1>
<p>제일 위에서 z buffer의 z는 16, 24, 32비트 부동소수점 방식을 이용해 처리한다고 했음</p>
<p>그럼 z index는 어떻게 계산하는걸까?</p>
<p>기본적인 개념은</p>
<p>$F_{depth} = \frac{z - near}{far - near}$
<img src="https://velog.velcdn.com/images/vfx_master/post/b4d50c37-d198-4594-95a5-9606bc6b3586/image.png" alt=""></p>
<p>그래픽스에서 이런 공식 많이 보이는데
정확히 뭐라부르는지 모르겠음...</p>
<p>대충 2개의 near, far절두체를 두고
near와 가까울때는 z = near 
far와 가까울때는 z = far로 
선형 보간이 이뤄지는 방식임</p>
<p>하지만 이것만으로는 부족함
실제로 여러 물체가 거<del>~</del>의 비슷한 z index를 가지고 있어서
특정 범위내의 물체까지는 z index를 더 정밀한 값으로 계산을 해야한다고 하면?</p>
<p>즉,
가까이 있는 물체는 더 높은 정밀도로 처리가 되고
멀리 있는 물체는 비교적 중요도가 낮으므로 낮은 정밀도로 처리되도록 하는거임</p>
<p>그 공식이 바로...
$F_{deph} = \frac{1/z - 1/near}{1/far - 1/near}$임
<img src="https://velog.velcdn.com/images/vfx_master/post/8b391cf5-8d6a-4064-b9a0-f0848d8b4a98/image.png" alt=""></p>
<p>이렇게 z index가 낮을수록 더 높은 정밀도로 depth를 판별하게되고
z index가 높을수록 낮은 정밀도로 depth를 판별하게 됨</p>
<pre><code class="language-cpp">void main()
{             
    FragColor = vec4(vec3(gl_FragCoord.z), 1.0);
} </code></pre>
<p>fragment shader의 로직을 위처럼 바꿔보자
<code>gl_FragCoord</code>가 바로 z index를 나타내어주는 좌표계임</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/77f831c5-b945-4133-80ea-4c8349d6d07e/image.gif" alt=""></p>
<p>이렇게 멀때는 흰색이다가
가까워질수록 색이 검은색으로 급격하게 변하게 됨</p>
<p>이게 바로 gl의 depth test를 할때 z값 계산이
비선형적이라는 증거임</p>
<p>선형적으로 나타낼땐 어떻게 보이냐?</p>
<p>대충</p>
<pre><code class="language-cpp">float near = 0.1;
float far  = 10.0;
float z = gl_FragCoord.z * 2.0 - 1.0;
float depth = (2.0 * near * far / (far + near - z * (far - near))) / far;
FragColor = vec4(vec3(depth), 1.0);</code></pre>
<p>이렇게 <code>0~1</code>범위가 되어있던 z값을 ndc좌표계인 <code>-1~1</code>범위로 바꿔주면...</p>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/864e59a9-e5a3-4531-ab79-8c227dd56cfe/image.gif" alt=""></p>
<p>요로코롬 선형적으로 바뀜<del>~</del></p>
<h1 id="z-fighting">Z fighting</h1>
<p><img src="https://velog.velcdn.com/images/vfx_master/post/a29a83ad-b89d-432b-bdbd-4a6c74583ca0/image.gif" alt=""></p>
<p>이런걸 본적있을거임</p>
<p>이게 바로 물체간의 z값의 차이가 정<del>~</del>말 계속 z buffer가 변경되며 생기는 문제인
z fighting임</p>
<p>이를 해결하는 방법이 몇가지 있음</p>
<ol>
<li>두 물체를 완전히 겹치지 않도록 특정 물체의 y축을 살짝만 조정함</li>
<li>near평면을 가능한 한 최대한 멀리 이동시키기<ul>
<li>near평면에 z값이 가까울수록 더 높은 정밀도를 가지기 때문</li>
<li>다만 이렇게 하면 아주 가까이에 있는 물체가 클리핑 될 수 잇음</li>
</ul>
</li>
<li>buffer를 기본 24비트 부동소수점이 아닌 32비트 부동소수점 등 성능을 약간 희생하고 더 높은 정밀도를 가지도록 하기</li>
</ol>
]]></description>
        </item>
    </channel>
</rss>