본문 바로가기

Game/Graphics

Learn OpenGL - Advanced OpenGL : Framebuffers

link : https://learnopengl.com/Advanced-OpenGL/Framebuffers


Framebuffers


지금까지 우리는 여러 종류의 화면 버퍼를 사용했다:


색상 값을 쓰는 color buffer, 깊이 정보를 쓰는 depth buffer, 어떤 조건을 기반으로 특정 조각을 버릴 수 있는 stencil buffer.


이러한 버퍼의 조합을 framebuffer라고 하며 메모리의 어딘가에 저장된다. OpenGL은 우리 자신의 프레임 버퍼를 정의 할 수 있는


유연성을 제공하기 때문에 우리 자신의 색상 및 선택적으로는 깊이와 스텐실 버퍼를 정의 할 수도 있다.



 지금까지 수행한 렌더링 작업은 모두 기본 프레임 버퍼에 첨부된 렌더링 버퍼 위에 수행되었다. 기본 프레임 버퍼는 윈도우를


만들 때 만들어지고 구성된다. (GLFW가 이 작업을 수행해준다) 우리 자신의 프레임 버퍼를 생성함으로써 추가적인 렌더링


방법을 얻을 수 있다.



 프레임 버퍼의 적용은 즉시 이해가 되지 않을 수도 있지만 장면을 다른 프레임 버퍼로 렌더링하면 장면에 거울을 만들거나


후 처리 효과를 멋지게 처리 하는 등이 효과를 낼 수 있다. 먼저 실제로 작동하는 방법을 논의한 다음 멋진 후 처리 효과를 구현해


사용해보자.







Creating a framebuffer


 OpenGL의 다른 객체와 마찬가지로 glGenFramebuffers라는 함수를 사용해 프레임 버퍼 객체 (FBO로 약칭)를 만들 수 있다:

unsigned int fbo;
glGenFramebuffers(1, &fbo);

 이 객체 생성 및 사용 패턴은 수십번 보았다. 따라서 사용 함수는 이전에 보았던 다른 모든 객체와 비슷하다. 먼저 framebffer 객체를 만들고


활성 프레임 버퍼로 바인드하고, 일부 작업을 수행한 다음 프레임 버퍼를 바인딩 해제한다. 프레임 버퍼를 바인드하기 위해 glBindFramebuffer를


사용한다:

glBindFramebuffer(GL_FRAMEBUFFER, fbo);  

 GL_FRAMEBUFFER 타겟에 바인딩함으로써 다음의 모든 읽기 및 쓰기 프레임 버퍼 연산은 현재 바인드된 프레임 버퍼에 영향을 미친다.


GL_READ_FRAMEBUFFER 또는 GL_DRAW_FRAMEBUFFER에 각각 바인딩해 특별히 읽기 또는 쓰기 대상에 프레임 버퍼를 바인딩 할 수도 있다.


GL_READ_FRAMEBUFFER에 바인드된 프레임 버퍼는 glReadPixels와 같은 모든 읽기 연산에 사용되고, GL_DRAW_FRAMEBUFFER에 바인드된


프레임 버퍼는 렌더링, 지우기, 기타 쓰기 작업의 대상으로 사용된다.



 안타깝게도 아직 완료되지 않았기 때문에 프레임 버퍼를 사용할 수 없다. 프레임 버퍼를 완성하려면 다음 요구 사항을 만족해야한다.


- 하나 이상의 버퍼 (color, depth, stencil buffer)를 연결해야 한다.


- 하나 이상의 색상이 있어야한다.


- 모든 첨부 파일도 완전해야한다. (예약된 메모리)


- 각 버퍼는 동일한 수의 샘플을 가져야한다.


 샘플이 무엇인지 알지 못한다고 걱정하지 말아라. 나중에 튜토리얼에서 샘플을 얻을 수 있다.



 요구 사항들로부터 우리는 프레임 버퍼를 위한 어떤 종류의 부착물을 만들고 이 부착물을 프레임 버퍼에 부착해야한다는 것을 분명히


해야 한다. 모든 요구 사항을 완료 한 후에 glCheckFramebufferStatus를 GL_FRAMEBUFFER로 호출해 실제로 프레임 버퍼를 성공적으로


완료했는지 확인할 수 있다. 그런 다음 현재 바인딩된 프레임 버퍼를 검사하고 상태에서 발견된 값을 반환한다.


GL_FRAMEBUFFER_COMPLETE을 반환하면 다음을 수행할 수 있다:

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
  // execute victory dance

 이후의 모든 렌더링 작업은 현재 바인딩 된 프레임 버퍼의 첨부 파일로 렌더링된다. 프레임 버퍼가 기본 프레임 버퍼가 아니기 때문에


렌더링 명령은 윈도우의 시각적 출력에 영향을 미치지 않는다. 이러한 이유 때문에 다른 프레임 버퍼로 렌더링하는 동안 off-screen rendering이라고 한다.


모든 렌더링 작업이 기본 창에 시각적으로 영향을 미치도록하려면 기본 프레임 버퍼를 0으로 바인딩해 다시 활성화해야한다:

glBindFramebuffer(GL_FRAMEBUFFER, 0);   

 모든 프레임 버퍼 조작이 끝났으면 framebuffer 객체를 삭제하는 것을 잊지말아라:

glDeleteFramebuffers(1, &fbo);  

 이제 완성 검사가 실행되기 전에 하나 이상의 첨부 파일을 프레임 버퍼에 첨부해야 한다. 첨부 파일은 프레임 버퍼의 버퍼 역할을 할 수 있는


메모리 위치이며 이미지라고 생각할 수 있다. 첨부 파일을 만들 때 텍스처 또는 렌더 버퍼 객체의 두 가지 옵션이 있다:






Texture attachments


 텍스처를 프레임 버퍼에 첨부 할 때 모든 렌더링 명령은 일반 color/depth 또는 stencil buffer인 것처럼 텍스처에 기록한다.


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



 farmebuffer의 텍스처를 생성하는 것은 일반적인 텍스처와 대략 같다:

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);  

 주된 차이점은 화면 크기와 동일한 차원을 설정한다는 것이다. 텍스처의 데이터 매개 변수로 NULL을 전달한다. 이 텍스처의 경우 메모리를


할당하고 실제로 채우지 않는다. 텍스처를 채우는 것은 프레임 버퍼에 렌더링하자마자 발생한다. 또한, 대부분의 경우에는 mipmapping이나


wrappiung method를 필요로 하지 않으므로 신경 쓰지 않는다.

 전체 화면을 더 작거나 더 큰 크기의 텍스처로 렌더링하려면 텍스처의 새로운 크기로 glViewportagain(프레임 버퍼에 렌더링하기 전에)을 호출해야 한다. 그렇지 않으면 텍스처 또는 스크린의 작은 부분만 텍스처에 그려진다.

 이제 텍스처를 만들었으므로 마지막으로 해야 할 일은 실제로 프레임 버퍼에 첨부하는 것이다:

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);  

 glFrameBufferTexture2D에는 다음과 같은 매개 변수가 있다:


- target : 우리가 목표로 하는 프레임 버퍼 유형 (draw, read, or both)


- attachment : 첨부 할 첨부 파일의 유형. 지금 우리는 컬러 첨부 파일을 첨부하고 있다. 끝 부분의 0은 하나 이상의 색상 첨부를 첨부 할 수 있음을

   나타낸다. 나중에 튜토리얼에서 설명하겠다.


- textarget : 첨부하려는 텍스처의 유형


- texture : 첨부할 실제 텍스처


- level : 밉맵 레벨. 이것을 0으로 유지한다.


 컬러 첨부를 제외하고는 프레임 버퍼 객체에 depth와 stencil texture를 첨부 할 수 있다. 깊이 첨부를 첨부하기 위해 첨부 유형을 


GL_DEPTH_ATTACHMENT로 지정한다. 텍스처의 포맷과 내부 포맷 타입은 깊이 버퍼의 저장 포맷을 반영하기 위해


GL_DEPTH_COMPONENT가 되어야한다. 스텐실 버퍼를 연결하려면 GL_STENCIL_ATTACHMENT를 두 번째 인수로 사용하고 텍스처의


형식을 GL_STENCIL_INDEX로 지정해라.



 깊이 버퍼와 스텐실 버퍼를 단일 텍스처로 첨부하는 것도 가능하다. 텍스처의 각 32비트 값은 24비트의 깊이 정보와 8비트의 스텐실


정보로 구성된다. 깊이와 스텐실 버퍼를 하나의 텍스처로 연결하려면 GL_DEPTH_STENCIL_ATTCHMENT 유형을 사용하고,


결합된 깊이와 스텐실 값을 포함하도록 텍스처의 형식을 구성해라. depth 버퍼와 stencil 버퍼를 하나의 텍스처로 프레임 버퍼에 연결하는


예제는 다음과 같다:

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 객체는 Texture가 프레임 버퍼 부착물의 가능한 유형으로 OpenGL에 도입되었으므로 텍스처는 옛날에 사용된 유일한 부착물이었다.


텍스처 이미지와 마찬가지로 renderbuffer 객체는 실제 버퍼이다. 텍스처 이미지처럼, renderbuffer 객체는 실제 버퍼이다. (byte, integers, pixels 등의 배열)


renderbuffer 객체는 OpenGL의 기본 렌더링 형식으로 데이터를 저장하지만 프레임 버퍼에 대한 off-screen rendering에 최적화되도록 추가된 장점이 있다.



 Renderbuffer 객체는 모든 렌더링 데이터를 텍스처 특정 형식으로의 변환없이 버퍼에 직접 저장하므로 쓰기 가능한 저장 매체로서 더 빠르다.


그러나 renderbuffer 객체는 일반적으로 쓰기 전용이므로 텍스처 액세스와 같이 읽을 수는 없다. 현재 바인딩된 프레임 버퍼에서 지정된 픽셀 영역을


반환하지만, 첨부 파일 자체에서 직접 반환하지 않아서 glReadPixels를 통해 읽어야 한다.



 데이터가 원래 형식으로 되어 있기 때문에 데이터를 쓰거나 다른 버퍼에 데이터를 복사 할 때 매우 빠르다. 렌더링 버퍼 객체를 사용할 때


스위칭 버퍼와 같은 연산이 매우 빠르다. 각 반복 끝에 사용했던 glfwSwapBuffers 함수는 renderbuffer 객체로 구현 될 수 있다.


우리는 단순히 렌더링 버퍼 이미지에 쓰고 끝에서 다른 하나로 바꾸기만 하면 된다. Renderbuffer 객체는 이러한 종류의 작업에 이상적이다.



 renderbuffer 객체를 생성하는 것은 framebuffer의 코드와 비슷하다:

unsigned int rbo;
glGenRenderbuffers(1, &rbo);

 그리고 비슷하게 renderbuffer 객체를 바인드해 이후의 모든 렌더 버퍼 작업이 현재 rbo에 영향을 미치도록 한다:

glBindRenderbuffer(GL_RENDERBUFFER, rbo);  

 renderbuffer 객체는 일반적으로 쓰기 전용이므로 depth 및 stencil 버퍼로 값을 읽지 않아도 depth 및 stencil testing에 대해서는


신경 쓰지 않아도 되므로 대부분 깊이 및 스텐실 첨부로 사용된다. 테스트를 위해 깊이와 스텐실 값이 필요하지만 이 값을 샘플링 할 필요가


없으므로 렌더 버퍼 객체가 완벽하게 적합하다. 이러한 버퍼에서 샘플링하지 않을 때는 렌더링 버퍼 객체가 일반적으로 더 최적화되므로


선호된다.



 depth와 stencil 렌더버퍼 객체를 생성하는 것은 glRenderbufferStorage 함수를 호출해 수행된다:

glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);

 renderbuffer 객체를 생성하는 것은 텍스처 객체와 비슷하다. 차이점은 텍스처와 같은 범용 데이터 버퍼 대신 이 객체가 이미지로 사용되도록


특별히 설계된 것이다. 여기서 GL_DEPTH_24_STENCIL8을 depth 형식과 stencil buffer가 각 24비트와 8비트로 유지되는 내부 형식으로 선택했다.



 마지막으로 할 일은 실제로 renderbuffer 객체를 첨부하는 것이다:

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);  

 Renderbuffer 객체는 프레임 버퍼 프로젝트에서 몇 가지 최적화를 제공 할 수 있지만 renderbuffer 객체를 사용할 시기와 텍스처를


사용해야 하는 시점을 파악하는 것이 중요하다. 일반적으로 특정 버퍼에서 데이터를 샘플링 할 필요가 없다면 해당 버퍼에 렌더 버퍼 객체를


사용하는 것이 좋다. 언젠가 색상이나 깊이 값과 같은 특정 버퍼에서 데이터를 샘플링 해야하는 경우 대신 텍스처 첨부를 사용해야 한다.


성능면에서는 큰 영향을 미치지 않는다.








Rendering to a texture


 이제는 프레임 버퍼가 어떻게 작동하는지 알았으니 프레임 버퍼를 잘 사용해야 한다. 장면을 우리가 만든 프레임 버퍼 객체에 연결된


색상 텍스처로 렌더링 한 다음 이 텍스처를 전체 화면에 걸쳐있는 간단한 사각형 위로 그린다. 시간 출력은 프레임 버퍼가 없는 경우와


똑같지만 이번에는 모두 단일 쿼드 위에 인쇄된다. 왜 이것이 유용한가? 다음 섹션에서 이유에 대해 알아보자.



 먼저해야 할 일은 실제 프레임 버퍼 객체를 만들고 바인딩하는 것이다. 이 작업은 모두 비교적 간단하다:

unsigned int framebuffer;
glGenFramebuffers(1, &framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);    

 다음으로 우리는 프레임 버퍼에 색상 첨부로 첨부한 텍스처 이미지를 만든다. 텍스처의 크기를 창 너비-높이와 동일하게 설정하고,


데이터를 초기화하지 않은 상태로 유지한다:

// generate texture
unsigned int texColorBuffer;
glGenTextures(1, &texColorBuffer);
glBindTexture(GL_TEXTURE_2D, texColorBuffer);
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, texColorBuffer, 0);  

 우리는 또한 OpenGL이 깊이 테스트를 할 수 있는지 확인하고 싶다. (그리고 스텐실 테스트를 옵션으로 제공 할 수 있다)


따라서 프레임 버퍼에 depth/stencil 첨부 파일을 추가해야한다. 우리는 color buffer만을 샘플링 할 것이고, 다른 버퍼는 샘플링하지 않을 것이므로


이 목적을 위해 렌더 버퍼 객체를 생성 할 수 있다. 특정 버퍼에서 샘플링하지 않을 때는 좋은 선택이라는 것을 기억하고 있나?



 renderbuffer 객체를 만드는 것은 그리 어렵지 않다. 우리가 기억해야 할 것은 depth와 stencil attachment 렌더 버퍼 객체로 만들어야한다는 것이다.


내부 형식을 GL_DEPTH24_STENCIL8로 설정한다. 이 형식은 우리의 목적에 맞는 정밀도이다.

unsigned int rbo;
glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo); 
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 800, 600);  
glBindRenderbuffer(GL_RENDERBUFFER, 0);

 renderbuffer 객체에 충분한 메모리를 할당하면 renderbuffer를 unbind 할 수 있다.



 그런 다음 프레임 버퍼를 완성하기 전에 마지막 단계로 renderbuffer 객체를 프레임 버퍼의 깊이 및 스텐실 첨부에 첨부한다.

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);

 그런 다음 최종 측정 값으로 프레임 버퍼가 실제로 완료되었는지 확인하고 그렇지 않으면 오류 메시지를 인쇄한다.

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
	std::cout << "ERROR::FRAMEBUFFER:: Framebuffer is not complete!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0);  

 그런 다음 우발적인 프레임 버퍼로 렌더링하지 않도록 프레임 버퍼를 바인딩 해제해야 한다.



 framebuffer가 완성되었으므로 기본 framebuffers 대신 framebuffer의 버퍼로 렌더링하기 위해 필요한 모든 작업만 framebuffer 객체에 바인딩된다.


이후의 모든 렌더링 명령은 현재 바인드된 프레임 버퍼에 영향을 준다. 모든 depth 및 stencil 작업은 현재 바인딩된 프레임 버퍼의 depth와


stencil 첨부 파일을 읽는다. 예를 들어, 깊이 버퍼를 생략하면 현재 바인딩된 프레임 버퍼에 깊이 버퍼가 없으므로 모든 깊이 테스트 작업이


더 이상 작동하지 않는다.



 따라서 장면을 단일 텍스처로 그리려면 다음 단계를 수행해야한다:


1. 활성 프레임 버퍼로 바인딩된 새 프레임 버퍼로 평소와 같이 장면을 렌더링해라.


2. 기본 프레임 버퍼에 바인딩해라.


3. 새 프레임 버퍼의 색상 버퍼를 텍스처로 사용해 전체 화면에 걸쳐있는 쿼드를 그린다.


 우리는 depth test 튜토리얼에서 사용한 것과 동일한 장면을 그리겠지만, 이번에는 구식 컨테이너 텍스처를 사용할 것이다.



 쿼드를 그리려면 간단한 쉐이더 세트를 새로 만들 것이다. 우리는 vertex 좌표를 정규화 된 장치 좌표로 제공 할 것이므로 정교한


행렬 변환을 포함시키지 않을 것이다. 그래서 정점 쉐이더의 출력으로 직접 지정할 수 있다. 정점 쉐이더는 다음과 같다:

#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;
}  

 너무 화려하지는 않다:

#version 330 core
out vec4 FragColor;
  
in vec2 TexCoords;

uniform sampler2D screenTexture;

void main()
{ 
    FragColor = texture(screenTexture, TexCoords);
}

 그런 다음 화면 쿼드에 대한 VAO를 만들고 만들고 구성하는 것은 사용자의 몫이다. framebuffer procedure의 렌더링 반복에는


다음과 같은 구조가 있다:

// 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);  

 주목할 몇 가지 사항이 있다. 첫째, 우리가 사용하고 있는 각 프레임 버퍼에는 자체 버퍼 세트가 있으므로 glClear를 호출해 적절한 비트 세트로


각 버퍼를 지우고 싶다. 둘째, 쿼드를 그릴 때 깊이 테스트를 사용하지 않도록 설정한다. 단순한 쿼드를 그리기 때문에 깊이 테스트는 별로


신경쓰지 않기 때문이다. 우리는 정상적인 장면을 그릴 때 다시 깊이 테스트를 해야 한다.



 여기에 잘못 될 수 있는 몇 가지 단계가 있으므로 출력이 없으면 가능한 경우 디버그하고 튜토리얼의 곤련 섹션을 다시 읽어라.


모든 것이 성공적으로 완료되면 다음과 같은 시각적 결과를 얻게된다:


An image of a 3D scene in OpenGL rendered to a texture via framebuffers


 왼쪽은 depth 테스트 튜토리얼에서 본 것과 똑같은 시각적 출력을 보여 주지만, 이번에는 간단한 쿼드로 렌더링 된다.


장면을 와이어 프레임으로 렌더링하면 기본 프레임 버퍼에 단일 쿼드만 그렸다.



 그래서 다시 이것을 어떻게 사용할까? 이제 완전히 렌더링 된 장면의 각 픽셀을 단일 텍스처 이미지로 자유롭게 액세스 할 수 있기 때문에


조각 쉐이더에서 흥미로운 효과를 만들 수 있다. 이러한 모든 흥미로운 효과의 조합을 post-processing effect 라고 한다.







Post-processing


 이제 전체 장면이 단일 텍스처로 렌더링되었으므로 텍스처 데이터를 조작해 흥미로운 효과를 만들 수 있다. 이 섹션에서는 보다 대중적인


후 처리 효과와 추가된 독창성으로 자신만의 효과를 창조하는 방법을 보여준다.



 가장 간단한 post-processing effect 중 하나부터 시작해보자.



Inversion


 렌더링 출력의 각 색상에 액세스 할 수 있으므로 조각 쉐이더에서 이러한 색상의 역수를 반환하는 것은 그리 어렵지 않다.


우리는 화면 텍스처의 색상을 가져와서 1.0에서 그것을 빼서 역으로 나타낸다:

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

 반전은 비교적 간단한 후 처리 효과이지만 펑키한 효과를 만들 수 있다:


Post-processing image of a 3D scene in OpenGL with inversed colors


 전체 씬은 조각 쉐이더에서 한 줄의 코드로 모든 색상을 반전 시킨다.



Grayscale


 또 다른 재미있는 효과는 전체 이미지를 효과적으로 그레이스케일링하는 흰색, 회색, 검은색을 제외하고 장면에서 모든 색상을 제거하는 것이다.


이렇게하는 쉬운 방법은 모든 색상 구성 요소를 가져와서 결과를 평균화하는 것이다:

void main()
{
    FragColor = texture(screenTexture, TexCoords);
    float average = (FragColor.r + FragColor.g + FragColor.b) / 3.0;
    FragColor = vec4(average, average, average, 1.0);
}   

 이미 좋은 결과를 얻을 수 있지만 인간의 눈은 녹색에 민감하고 파란색에 가장 민감한 경향이 있다.

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);
}   

 

Post-processing image of a 3D scene in OpenGL with grayscale colors


 당신은 아마 차이를 알지 못할 것이다. 그러나 더 복잡한 장면에서 그러한 가중된 그레이 스케일 효과는 보다 현실적인 효과를 준다.



Kernel effects


 단일 텍스처 이미지에서 후 처리를 수행하는 또 다른 이점은 실제로 텍스처의 다른 부분에서 색상 값을 샘플링 할 수 있다는 것이다ㅣ.


예를 들어, 현제 텍스처 좌표를 중심으로 작은 영역을 취해 현재 텍스처 값을 중심으로 여러 텍스처 값을 샘플링 할 수 있다.


그런 다음 창의적인 방식으로 결합해 흥미로운 효과를 만들 수 있다.



 커널 (또는 convolution matrix)은 주변 픽셀 값에 커널 값을 곱하고, 그 값을 모두 더해 단일 값을 형성해 현재 픽셀을 중심으로 하는


작은 매트릭스 형 배열이다. 기본적으로 현재 픽셀의 주변 방향으로 텍스처 좌표에 작은 오프셋을 추가하고, 커널을 기반으로 결과를 결합한다.


커널의 예가 아래와 같다:



 이 커널은 8개의 주변 픽셀 값을 취해 2와 현재 픽셀에 -15를 곱한다. 이 예제 커널은 기본적으로 커널에서 결정된 가중치로 주변 픽셀을


곱하고, 현재 픽세렝 큰 음수 가중치를 곱해 결과의 균형을 맞춘다.

모든 가중치를 함께 추가하면 인터넷을 통해 찾을 수 있는 대부분의 커널이 모두 1로 합계된다. 최대 1을 더하지 않으면 결과 텍스처 컬러가 원래 텍스처 값보다 밝거나 어둡게 된다.

 커널은 사용하기 쉽고 실험하기가 쉽고 온라인에서 많은 예제를 찾을 수 있어 매우 유용한 후처리 도구이다. 조각 쉐이더가 실제로 커널을


지원하기 위해서는 약간 수정해야한다. 우리가 사용할 각 커널은 3x3 커널을 가정한다:

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은 사용자가 원하는대로 지정할 수 있는 상수 값이다.


그런 다음 우리는 커널을 정의한다. 이 커널은 모든 주변 픽셀을 흥미로운 방식으로 샘플링해 각 색상 값을 선명하게하는 선명한 커널이다.


마지막으로 샘플링 할 때 현재 텍스처 좌표에 각 오프셋을 추가 한 다음 이 텍스처 값을 우리가 함께 더하는 가중 커널 값으로 곱한다.


 이 particular sharpen kernel(특정 선명화 커널)은 다음과 같다:


Post-processing image of a 3D scene in OpenGL with blurred colors


 이것은 당신의 player에게 재미있는 효과를 줄 수 있다.



Blur


 흐림 효과를 생성하는 커널은 다음과 같이 정의된다:



 모든 값이 16까지 합쳐지기 때문에 샘플링된 색상을 결합해 반환하면 매우 밝은 색상이 되므로 커널의 각 값을 16으로 나눠야한다.


결과로 나타나는 커널 배열은 다음과 같다:

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  
);

 조각 쉐이더에서 커널 float 배열을 변경함으로써 우리는 후 처리 효과를 완전히 바꾸고 있다. 이제 다음과 같이 보인다:


Post-processing image of a 3D scene in OpenGL with sharpened colors


 이러한 흐림 효과는 흥미로운 가능성을 창출한다. 주인공이 안경을 쓰지 않을 때마다 술에 취한 사람의 효과를 내기 위해


예를 들어, 시간 경과에 따라 흐림 양을 변경하거나 흐림 효과를 높일 수 있다. Blurring은 색상 값을 매끄럽게하는 유용한 유틸리티를


제공하며 이후 튜토리얼에서 사용할 것이다.



 일단 작은 커널을 구현하면 멋진 후 처리 효과를 생성하는 것이 매우 쉽다는 것을 알 수 있다. 이 토론을 마치며 마지막 유명한 효과를 보여주겠다.



Edge detection


 아래에서 커널을 선명하게 하는 것과 유사한 edge detection 커널을 찾을 수 있다:



 이 커널은 모든 edge를 강조 표시하고 나머지는 어둡게 하므로 이미지의 edge만 신경 쓸 때 매우 유용하다:


Post-processing image of a 3D scene in OpenGL with edge detection filter


 이 같은 커널이 Photoshop과 같은 도구에서 이미지 조작 도구/필터로 사용되는 것은 놀랄 일이 아니다. 극단적인 병렬 기능을 갖춘 조각을


처리 할 수 있는 그래픽 카드의 기능으로 인해 픽셀 단위로 이미지를 비교적 쉽게 실시간으로 조작할 수 있다.


따라서 이미지 편집 도구는 이미지 처리를 위해 그래픽 카드를 더 자주 사용하는 경향이 있다.




** ** ** ** ** ** ** ** ** **


호우! 길고 긴 파트였다. Framebuffer가 다양한 효과를 넣는데 도움을 주고, 커널을 통해 이미지들을 다양하게 변경할 수 있는 것은 알았다.


하지만 세부적인 사항을 완벽하게 이해하지 못했다. 내일 찬행이형이랑 만나서 복습하면서 다시 되짚어봐야겠다.