Home [learn-opengl] Advanced OpenGL: Framebuffers
Post
Cancel

[learn-opengl] Advanced OpenGL: Framebuffers

Framebuffers

  • 지금까지 여러 스크린 버퍼들을 사용해왔다.
    • color buffer: 컬러 값 작성
    • depth buffer: 깊이 정보 작성
    • stencil buffer: 특정 조건에 의해 해당 fragment를 폐기함.
  • 이러한 버퍼들을 결합한 것을 Framebuffer라고 한다.

  • 지금까지 우리가 수행했던 렌더링 작업들은 모두 기본 framebuffer에 있는 렌더 buffer의 위에 동작되었다.

    • 기본 framebuffer는 윈도우 창을 생성할 때 생성된다.(glfw가 자동으로 생성)
    • 자신만의 framebuffer를 생성하면, 렌더링하는 데에 추가적인 기능들을 얻을 수 있다.
  • scene을 여러가지 framebuffer로 렌더링하면, 거울을 생성할 수 있고, 멋진 전처리 효과들을 생성할 수 있다.

Creating a framebuffer

  • glGenFramebuffers: 새로운 framebuffer 객체를 생성한다.(FBO)
1
2
unsigned int fbo;
glGenFramebuffers(1, &fbo);
  • 먼저 framebuffer 객체를 생성하고 바인딩하여 framebuffer를 활성화 시킨다.
    • 그 후에 조작을 하고, framebuffer를 언바인딩한다.
    • framebuffer를 바인딩하기위해, glBindFramebuffer를 사용한다.
1
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
  • GL_FRAMEBUFFER 타겟에 바인딩함으로써 이후에 나오는 모든 framebuffer 읽기, 쓰기 명령이 현재 바인딩된 framebuffer에 영향을 미친다.

  • framebuffer를 GL_READ_FRAMEBUFFER, GL_DRAW_FRAMEBUFFER 타겟에 바인딩하여 읽기, 쓰기 명령을 구분할 수도 있다.

    • GL_READ_FRAMEBUFFER에 바인딩된 framebuffer는 glReadPixel과 같은 모든 읽기 명령에 사용된다.
    • GL_DRAW_FRAMEBUFFER에 바인딩된 framebuffer는 렌더링, 비우기, 다른 쓰기 연산에 대한 목적지로서 사용된다.
    • 대부분의 경우 GL_FRAMEBUFFER에 바인딩하여 읽고 쓴다.

framebuffer 요구사항

  • 완전하게 만들기 위한 요구사항

    • 최소한 하나의 buffer(color, depth 혹은 stencil buffer)를 첨부(attach)해야 합니다.
    • 최소한 하나의 color attachment가 존재해야 합니다.
    • 모든 attachment buffer들은 완전해야 합니다(메모리가 할당).
    • 각 buffer들은 샘플의 갯수가 같아야 합니다.
  • 샘플은 Anti Aliasing에서 다룬다.

  • 요구사항에 따르면 framebuffer에 첨부할 것들을 생성하고 첨부해야한다.

    • glCheckFramebufferStatus 함수에 GL_FRAMEBUFFER 를 인자로 넘겨주어 호출하여 준비가 다 되었는지 확인할 수 있다.
    • GL_FRAMEBUFFER_COMPLETE 를 반환하면 성공하고, 그렇지 않으면 실패.
    1
    2
    
    if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
    // execute victory dance
    
  • 이후의 모든 렌더링 작업들은 이제 현재 바인딩된 framebuffer에 첨부된 것들에 렌더링하게 된다.

    • 이 버퍼가 기본 framebuffer가 아니기 때문에, 렌더링 명령들이 윈도우 창의 출력에 아무런 영향을 주지않는다.
    • 즉, off-screen rendering이라고 부른다.
    • 모든 렌더링 작업들을 메인 윈도우 창에 나타내기 위해 0을 바인딩하여 다시 기본 frambuffer를 활성화 시켜야한다.
    1
    
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    
    • 모든 framebuffer 작업을 완료하면 framebuffer 객체를 제거해야한다.
    1
    
    glDeleteFramebuffers(1, &fbo);
    
  • 완전히 생성되었는지 확인하기전에 하나 이상의 첨부물을 framebuffer에 첨부해야한다.

    • 첨부물(attachment)들은 framebuffer에서 buffer처럼 행동하는 메모리 위치(memory location)이다.
    • 첨부물을 생성할 때, 두 개의 옵션을 가지고 있다.
    • 텍스처 or render buffer objects

Texture attachments

  • 텍스처를 프레임 버퍼에 첨부할 때, 마치 모든 렌더링 명령이 일반 color/depth/stencil buffer인 것처럼 텍스처에 쓴다.

  • 텍스처를 사용하면, 모든 렌더링 작업의 결과가 셰이더에서 쉽게 사용할 수 있는 텍스처 이미지로 저장된다는 장점이 있다.

  • framebuffer의 텍스처를 생성하는 것은 일반적인 텍스처와 같다.

1
2
3
4
5
6
7
8
unsigned int texture;
glGenTextures(1, &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);
  • 차이점은 두가지,

    • 화면의 크기와 동일한 크기(dimensions)로 설정한다.
    • 텍스처의 데이터 매개변수로 NULL 을 전달한다.
  • 오직 메모리만 할당하고 실제로 채워넣진 않는다.
  • 텍스처를 채우는 것은 framebuffer에 렌더링할 때 수행 될 것이다.
    • 대부분의 경우, wrapping methods 이나 mipmapping 를 필요로 하지 않으므로 신경쓰지 않는다.

전체화면을 더 작거나 큰 크기의 텍스처에 렌더링하고 싶다면, glViewport 함수를 다시 호출하여(framebuffer에 렌더링하기 전에) 텍스처의 새로운 크기의 인자로 넘겨주어야한다. (그러지 않으면 화면의 일부분만 텍스처에 그려짐)

  • 이제 이 텍스처를 첨부해야한다.
1
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
  • glFramebufferTexture2D

    • target: 텍스처를 첨부할 타겟인 framebuffer(draw, read or both)
    • attachment: 첨부될 첨부물의 타입, 여기서는 color, 마지막에 붙은 0은 하나 이상의 color 첨부물을 첨부 할 수 있다는 것을 암시한다.
    • textarget: 첨부하기 원하는 텍스처 유형
    • texture: 첨부할 실제 텍스처
    • level: Mipmap 레벨, 0으로 유지할것임.
  • color 말고 depth, stencil 텍스처를 첨부할 수 있다.

    • depth type: GL_DEPTH_ATTACHMENT
    • depth format, internalformat: GL_DEPTH_COMPONENT
    • stencil type: GL_STENCIL_ATTACHMENT
    • stencil format: GL_STENCIL_INDEX
  • depth, stencil 버퍼를 하나의 텍스처로 만들어 첨부 가능

    • 텍스처의 각 32비트 값은 24비트의 depth 정보, 8비트의 stencil 정보로 이루어짐
    • GL_DEPTH_STENCIL_ATTACHMENT를 사용하고, 텍스처 형식을 depth 와 stencil 값을 결합한 것으로 설정한다.
    1
    2
    3
    4
    5
    6
    
    glTexImage2D(
      GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 800, 600, 0,
      GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, NULL
    );
    
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, texture, 0);
    

Renderbuffer object attachments

  • Renderbuffer 객체는 텍스처 다음에 새로나온 framebuffer 첨부물의 유형이다.

    • 그래서 텍스처는 옛날에만 쓰였던 첨부물
    • 텍스처 이미지와 마찬가지로 이는 실제 buffer다
    • ex) 바이트, 정수형, 픽셀 등의 배열
    • 이 객체는 읽기 불가능한 대신, framebuffer에대한 off-screen 렌더링에서 텍스처보다 더 최적화되어 있다.
  • Renderbuffer객체는 모든 렌더링 데이터들을 그들의 buffer에 텍스처 형식으로 변환하지 않고 직접적으로 저장한다.

    • 따라서 좀 더 빠른 저장공간을 형성
    • 하지만, renderbuffer 객체는 일반적으로 쓰기만 가능하다.
    • 그들로부터 데이터를 읽을 수 없다.(그러므로 메모리 최적화를 수행할 수 있음)
    • 느리지만, glReadPixels 함수를 통해 현재 바인딩된 framebuffer로부터 픽셀의 특정 영역을 읽을 수 있긴하다. (첨부물 자체에서 직접적으로 읽어오는것은 아님)
  • 데이터가 기본(native) 포맷이기 때문에 데이터를 쓰거나 다른 버퍼에 데이터를 복사할 때 매우 빠르다.

    • 따라서 버퍼 전환과같은 작업들은 이 객체를 사용할 때 빠르다.
    • glfwSwapBuffers함수 또한 renderBuffer 객체로 구현되어있다.
    • 단순히 렌더 버퍼 이미지에 쓰고, 마지막에 다른 이미지로 교체한다.
  • 생성은 다음과 같이 한다.

1
2
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
  • 그리고 비슷하게 바인딩한다. 이후 모든 renderbuffer 작업들은 현재 rbo에 영향을 미친다.
1
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
  • Renderbuffer 객체가 일반적으로 쓰기만 가능하기 때문에 종종 depth, stencil 첨부몰로 사용된다.(대부분 값을 읽을 필요가 없음)

    • 하지만, test시에는 값을 읽어야함.
    • 그럼에도 불구하고, 이 값을 샘플링할 필요가 없으므로, 렌더 버퍼 객체를 사용하는것은 적합함.
    • 샘플링하지 않을 때는 렌더링 버퍼 객체가 일반적으로 더 최적하므로 더 선호됨.
  • Depth, Stencil renderbuffer객체 생성은 glRenderbufferStorage 함수를 호출하여 수행된다.

    • GL_DEPTH24_STENCIL8을 설정하여 24/8 비트로 나눔
1
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
  • 첨부는 다음과 같이 한다.
1
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
  • Renderbuffer 객체는 framebuffer 프로젝트에 최적화를 제공해준다.
    • 주의: 샘플링할 필요가 없다면 그 특정 버퍼에 render buffer를 사용하는것이 현명하다. 하지만, buffer로부터 color 값이나 깊이 값 처럼 데이터를 sample 해야 한다면, 텍스처를 사용해야한다.

Rendering to a texture

  • 생성한 framebuffer 객체에 첨부된 color 텍스처에 scene 을 렌더링할 것이다.

    • 그런 다음 이 텍스처를 화면을 가득채운 간단한 사각형에 그릴 것이다.
    • 시각적 출력은 앞에서와 동일하다
    • 하지만 모든것이 하나의 사각형 위에 그려진다.
  • 먼저 framebuffer 객체를 생성하고 바인딩하자.

1
2
3
unsigned int framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
  • 그 다음, framebuffer에 color 첨부물로 텍스처 이미지를 첨부할것이다.
    • 데이터 지정 x, 윈도우 창과 크기 같음
1
2
3
4
5
6
7
8
9
10
11
// generate texture
unsigned int textureColorbuffer;
glGenTextures(1, &textureColorbuffer);
glBindTexture(GL_TEXTURE_2D, textureColorbuffer);
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);
glBindTexture(GL_TEXTURE_2D, 0);

// attach it to currently bound framebuffer object
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorbuffer, 0);
  • 또한 OpenGL이 depth testing(또는 stencil testing)을 할 수 있도록 depth와 stencil을 추가할 것이다.
    • 이는 renderbuffer로 생성할 것이다.
1
2
3
4
5
unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);
glBindRenderbuffer(GL_RENDERBUFFER, 0);
  • 이 renderbuffer 객체에 충분한 메모리가 할당되면, renderbuffer를 언바운딩하자

  • 그 후, 이 객체를 첨부하자

1
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
  • 그런 다음, 완성됬는지 확인하자
1
2
3
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
	std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);
  • 마지막으로 framebuffer를 언바운딩하여, 우연히 잘못된 framebuffer에 렌더링되는일이 발생하는것을 방지하자.

  • 이제 framebuffer가 완성됬으므로, 기본 framebuffer 대신 이 객체를 바운딩하면된다.

    • 그러면, 이 후의 모든 명령은 현재 바운딩된 framebuffer에 영향을 미친다.
    • 모든 depth, stencil 작업들은 현재 바인딩된 framebuffer의 depth, stencil 첨부물로부터 값을 읽는다.(이용가능할 경우)
    • 만약 depth 버퍼를 첨부하지 않았다면, depth testing 작업들은 동작하지 않는다.
  • 하나의 텍스처에 scene을 그리기 위해 다음과 같은 단계를 거친다.

    1. 활성화된 framebuffer로서 바인딩된 새로운 framebuffer에 평상시대로 scene을 렌더링한다..
    2. 기본 framebuffer를 바인딩한다.
    3. 새 프레임 버퍼의 색상 버퍼를 텍스처로 사용하여 전체 화면에 걸쳐있는 사각형(quad)을 그린다.
  • depth testing 강좌에서 사용했던 것과 동일한 scene을 그릴 것이다.

    • 이번에는 이 컨테이너 텍스처를 사용한다.
  • 사각형을 그리기 위해 간단한 shader 세트를 작성할 것이다.

    • 행렬 변환을 포함시키지 않을 것이며, vertex 좌표만을 제공할 것이다.
    • vertex shader는 다음과 같다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    #version 330 core
    layout (location = 0) in vec2 aPos;
    layout (location = 1) in vec2 aTexCoords;
    
      out vec2 TexCoords;
    
      void main()
      {
          gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
          TexCoords = aTexCoords;
      }
    
    • fragment shader는 다음과 같다.(오직 텍스처를 sample 함)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    #version 330 core
    out vec4 FragColor;
    
    in vec2 TexCoords;
    
    uniform sampler2D screenTexture;
    
    void main()
    {
        FragColor = texture(screenTexture, TexCoords);
    }
    
  • 그런 다음 화면 쿼드에 대한 VAO를 만들고 구성하는 것은 사용자 몫이다.
  • 프레임 버퍼 과정의 단일 렌더링은 다음과 같은 구조이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// first pass
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClearColor(0.1f, 0.1f, 0.1f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // we're not using the stencil buffer now
glEnable(GL_DEPTH_TEST);
DrawScene();

// second pass
glBindFramebuffer(GL_FRAMEBUFFER, 0); // back to default
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

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

  • 주의

    1. 각 framebuffer들은 그들만의 버퍼를 가지고 있으므로, glClear로 각 버퍼를 비워주어야한다.
    2. 사각형을 그릴때 간단한 사각형을 그리므로 depth testing 을 신경쓰지 않아도 된다.(비활성화, 그리고 일반적인 scene을 그릴때 다시 활성화)
  • 결과는 다음과 같다.

  • 왼쪽은 동일한 결과.(하지만 이는 그저 사각형(quad))
    • 이 scene을 wireframe으로 렌더링 한다면, 오른쪽 처럼 하나의 사각형만 그려진다.

전체 소스

  • 이는 하나의 텍스처 이미지로서 완전히 렌더링 된 scene을 자유롭게 접근할 수 있기 때문에 fragment shader에서 흥미로운 효과들을 생성할 수 있다.
    • 이런 효과를 post-processing(전처리)효과라고 부른다.

Post-processing

  • scene texture를 조작해보자.

Inversion(반전)

  • Fragment shader에서 렌더링 출력의 각 컬러들에 접근하여 컬러를 반전해보자

  • 단순히 컬러에 1을 빼서 이 효과를 얻을 수 있다.

1
2
3
4
void main()
{
    FragColor = vec4(vec3(1.0 - texture(screenTexture, TexCoords)), 1.0);
}

  • frag shader 한줄에 의해 모두 반전된 컬러를 가지고 있다.

Grayscale

  • 단순히 색을 평균내어 이 효과를 얻을 수 있다.
1
2
3
4
5
6
void main()
{
    FragColor = texture(screenTexture, TexCoords);
    float average = (FragColor.r + FragColor.g + FragColor.b) / 3.0;
    FragColor = vec4(average, average, average, 1.0);
}
  • 정확한 그레이 스케일은 아래와 같이 가중치를 둔다.
    • 사람의 눈은 녹색에 예민, 파란색에 덜 예민
1
2
3
4
5
6
void main()
{
    FragColor = texture(screenTexture, TexCoords);
    float average = 0.2126 * FragColor.r + 0.7152 * FragColor.g + 0.0722 * FragColor.b;
    FragColor = vec4(average, average, average, 1.0);
}

Kernel effects

  • 텍스처 이미지로 scene을 그리는 것의 또 다른 장점

    • 해당 fragment에 국한되지 않은 텍스처의 다른 부분에서 색상 값을 샘플링 할 수 있다는 것이다.
  • Kernel(convolution matrix)은 주변 픽셀 값에 커널 값을 곱한 후, 현재 값을 모두 더하여, 하나의 값을 형성하는 현재 픽셀을 중심으로하는 작은 행렬이다.

  • 현재 픽셀의 주변 방향의 텍스처 좌표에 작은 오프셋을 추가하고 커널을 기반으로 결과를 결합한다.

  • 커널은 아래와 같은 행렬이다.

\[\begin{bmatrix}2 & 2 & 2 \\ 2 & -15 & 2 \\ 2 & 2 & 2 \end{bmatrix}\]
  • 이 커널은 8개의 둘러싸인 픽셀 값들을 취하고, 이들을 2와 곱한다. 그리고 현재 픽셀에 -15를 곱한다.

    • 이 예제 커널은 주변 픽셀에 커널에서 결정된 여러 가중치를 곱하고 현재 픽셀에 큰 음수 가중치를 곱하여 결과의 ​​균형을 맞춥니다.

    대부분 커널은 합이 1로 되게한다. 그렇지 않으면 결과 텍스처가 원래보다 밝거나 어둡게된다.

  • 이 커널은 post-processing에 대해 아주 유용한 도구이다.

    • kernel을 지원하기 위해서는 fragment를 약간 수정해야한다.
  • 우리가 사용할 각 kernel은 3x3 kernel이라고가정한다.(대부분 그럼)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 < 9; i++)
    {
        sampleTex[i] = vec3(texture(screenTexture, TexCoords.st + offsets[i]));
    }
    vec3 col = vec3(0.0);
    for(int i = 0; i < 9; i++)
        col += sampleTex[i] * kernel[i];

    FragColor = vec4(col, 1.0);
}
  • 먼저 각 텍스처 좌표에 대한 9 개의 vec2 offset 배열을 생성한다.
    • 이 offset은 간단히, 원하는대로 정할 수 있는 상수 값이다.
  • 그런다음 커널을 정의한다.

    • 이런 패턴의 커널을 sharpen kernel 이라고 한다.
  • 효과는 다음과 같다

Blur

  • blur 효과를 내는 커널은 아래와 같다.
\[\begin{bmatrix} 1 & 2 & 1 \\ 2 & 4 & 2 \\ 1 & 2 & 1 \end{bmatrix} / 16\]
  • 모든 값의 합이 16이므로, 결과는 매우 밝을을 것이므로, 16으로 나눠야한다.

  • 커널 배열을 다음과 같이 설정하자

1
2
3
4
5
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
);
  • 이제 아래와 같이 보인다.

  • 이런 효과는 주인공이 안경을 쓰지 않을 때 혹은 술취했을 때 연출할 때 쓰기 좋다.

  • 또한 블러는 색을 부드럽게하는데 유용한 도구가 될 수 있다.

  • 이러한 작은 kernel을 구현함으로써 손쉽게 post-processing 효과를 낼 수 있다.

Edge detection

  • 커널은 다음과 같다. \(\begin{bmatrix} 1 & 1 & 1 \\ 1 & -8 & 1 \\ 1 & 1 & 1 \end{bmatrix}\)

  • 이 커널은 모든 모서리를 강조하고, 나머지를 어둡게한다. 우리가 이미지의 모서리를 신경써야 할 때 유용하게 사용할 수 있다.

  • 이와 같은 커널들이 포토샵같은 툴에서 이미지 조작 툴/필터로 사용된다.
    • 병렬 기능으로 fragment들을 쉽게 처리할 수 있는 그래픽카드 덕분에, 실시간으로 픽셀마다 이미지를 조작할 수 있다.
    • 그러므로 이미지 편집 도구들은 이미지 처리에 대해 그래픽 카드를 더 자주 사용하는 경향이 있다.

출처

Framebuffers

Framebuffers

This post is licensed under CC BY 4.0 by the author.

[learn-opengl] Advanced OpenGL: Face culling

[learn-opengl] Advanced OpenGL: Cubemaps