본문 바로가기

Game/Graphics

Learn OpenGL - Advanced OpenGL : Anti Aliasing

link : https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing


Anti Aliasing


아마 렌더링을 하다보면 당신의 모델의 가장자리를 따라 톱니 모양의 패턴을 보게 될 것이다. 이 들쭉날쭉한 가장자리가


나타나는 이유는 래스터 라이저가 정점 데이터를 장면 뒤의 실제 조각으로 변환하는 방법 때문이다. 이 들쭉날쭉한 가장자리가


어떻게 생겼는지 보여주는 예제는 간단한 큐브를 그릴 때 이미 볼 수 있다:


Container with visible aliasing


 즉시 보이지는 않지만 큐브의 가장자리를 면밀히 살펴보면 지그재그 패턴을 볼 수 있다. 확대하면 다음을 볼 수 있다:


Zoomed in on contanier with visible aliasing


 이것은 분명히 응용 프로그램의 최종 버전에서 우리가 원하는 것이 아니다. 가장자리가 구성하는 픽셀 형성을 명확하게 보는 이 효과를


Aliasing 이라고 한다. anti aliasing 기술이라고 하는 꽤 많은 기술이 있어서 aliasing 동작이 정확하게 매끄러운 가장자리를 생성하기 위해


대립한다.



 처음에는 Super Sample Anti Aliasing (SSAA)이라는 기술을 사용해 장면을 렌더링하는 데 훨씬 높은 해상도를 임시로 사용하고,


시각적 출력이 프레임 버퍼에서 업데이트되면 해상도가 정상으로 다시 다운 샘플링해 해결했다. 이 여분의 해상도는 이러한


들쭉날쭉한 가장자리를 방지하는 데 사용되었다. 앨리어싱 문제에 대한 해결책을 제공했지만 평소보다 많은 조각을 그려야했기


때문에 성능에 큰 단점이 있었다. 이 기술은 그러므로 짧은 영광의 순간을 가졌다.



 이 기술은 SSAA의 개념에서 차용한 MSAA(Multisample anti-aliasing) 또는 더 효율적인 접근 방식을 구현하는 보다 현대적인 기술을


탄생시켰다. 이 자습서에서는 OpenGL에 내장된 이 MSAA 기술에 대해 광범위하게 논의할 것이다.




Multisampling


 멀티 샘플링이 무엇인지, 앨리어싱 문제를 해결하는 방법을 이해하려면 먼저 OpenGL의 래스터 라이저의 내부 동작을 좀 더 깊이 파고 들어야한다.



 래스터라이저는 최종 처리된 정점과 조각 쉐이더 사이에 있는 모든 알고리즘과 프로세스의 조합이다. 래스터라이저는 단일 프리미티브에


속하는 모든 정점을 가져와서 이를 조각 세트로 변환한다. 정점 좌표는 이론적으로 좌표를 가질 수 있지만 조각은 창 해상도에 구속되므로


절대 좌표를 지정할 수 없다. 정점 좌표와 조각 사이에 일대일 매핑이 거의 존재하지 않으므로 래스터라이저는 각 특정 정점이 어떤 조각/화면 좌표에


어떤 식으로 끝날지를 결정해야한다:


Image of a triangle being rasterized in OpenGL


 

 여기서 각 픽셀의 중심에는 픽셀이 삼각형으로 덮여 있는지를 결정하는데 사용되는 샘플 점이 들어있는 스크린 픽셀 격자가 있다.


빨간색 샘플 포인트는 삼각형으로 덮여 있으며 해당 커버리지 픽셀에 대해 조각이 생성된다. 삼각형 모서리의 일부분이 여전히


특정 화면 픽셀을 입력하더라도 픽셀의 샘플 점은 삼각형 안쪽으로 덮이지 않으므로 이 픽셀은 조각 쉐이더의 영향을 받지 않는다.



 이미 현재 별칭 지정의 출처를 파악할 수 있다. 삼각형의 렌더링된 전체 버전은 화면에 다음과 같이 표시된다:


Filled triangle as a result of rasterization in OpenGL


 화면 픽셀의 제한된 양 때문에 일부 픽셀은 가장자리를 따라 렌더링되고, 일부 픽셀은 가장자리를 따라 렌더링된다.


그 결과 우리는 이전에 보았던 들쭉날쭉한 가장자리를 발생시키는 부드럽지 않은 가장자리를 가진 프리미티브를 렌더링하고 있다.



 멀티 샘플링은 삼각형의 범위를 결정하기 위해 단일 샘플링 포인트를 사용하지 않고 여러 샘플 포인트를 사용한다.


각 픽셀의 중앙에 단일 샘플 지점 대신에 4개의 하위 샘플을 일반적인 패턴으로 배치하고, 이를 픽셀 범위를 결정하는데 사용한다.


이는 컬러 버퍼의 크기가 픽셀당 사용하는 하위 샘플 수만큼 증가한다는 것을 의미한다.


Multisampling in OpenGL


 이미지의 왼쪽은 우리가 보통 삼각형의 범위를 결정하는 방법을 보여준다. 이 특정 픽셀은 샘플 포인트가 삼각형으로 덮여 있지 않기


때문에 조각 쉐이더를 실행하지 않으므로 빈 상태로 유지된다. 이미지의 오른쪽에는 각 픽셀에 4개의 샘플 포인트가 포함된 멀티 샘플링


버전이 표시된다. 여기서 우리는 샘플 점 중 단지 2점이 삼각형을 포함한다는 것을 알 수 있다.

샘플 포인트의 양이 많아지면 더 정밀하게 렌더링하는데 도움이 된다.

 멀티 샘플링이 흥미로운 부분이다. 2개의 서브 샘플이 삼각형으로 덮여 있으므로 다음 단계는 이 특정 픽셀의 색상을 결정하는 것이다.


우리가 처음에 추측한 것은 각 커버된 서브 샘플에 대해 조각 쉐이더를 실행한 후에 픽셀당 각 서브 샘플의 색상을 평균화하는 것이다.


이 경우 각 하위 샘플의 보간된 정점 데이터에서 조각 쉐이더를 두 번 실행하고, 그 샘플 지점에 결과 색상을 저장한다. 이것은 다행스럽게도


작동 방식이 아니다. 이는 기본적으로 멀티 샘플링없이 많은 조각 쉐이더를 실행해야 한다는 것을 의미하기 때문에 성능이 크게 저하된다.



 MSAA가 실제로 작동하는 방식은 조각 쉐이더가 삼각형이 덮고 있는 서브 샘플 수에 관계없이 각 픽셀에 대해 한 번만 실행된다는 것이다.


조각 쉐이더는 픽셀 중심에 보간된 정점 데이터와 함께 실행되고 결과 색상은 덮여있는 각 하위 샘플 내부에 저장된다. 컬러 버퍼의


서브 샘플이 렌더링된 프리미티브의 모든 색상으로 채워지면 모든 색상이 픽셀당 평균화되어 픽셀당 하나의 색상이 된다. 4개의 샘플 중


2개만 이전 이미지에서 다뤄졌기 때문에 픽셀의 색상은 삼각형의 색상과 다른 2개의 샘플 포인트에 저장된 색상으로 평균화되어 연한 파란색을


띄게된다.



 결과는 모든 기본 가장자리가 더 매끄러운 패턴을 생성하는 색상 버퍼이다. 이전 삼각형의 범위를 다시 결정할 때 멀티 샘플링이 어떻게 생겼나


보자:


Rasterization of triangle with multisampling in OpenGL


 여기서 각 픽셀에는 파란색 하위 샘플이 삼각형으로 덮여있고 회색 샘플 점이 아닌 4개의 하위 샘플이 포함된다. 삼각형의 내부 영역 내에서


모든 픽셀은 조각 쉐이더를 한 번 실행하고 색상 출력은 모든 4개의 하위 샘플에 저장된다. 삼각형의 가장자리에서 모든 서브 샘플이 


커버되지는 않으므로 조각 쉐이더의 결과는 일부 서브 샘플에만 저장된다. 커버된 서브 샘플의 양에 기초해서 결과적인 픽셀 컬러는


삼각형 컬러 및 다른 서브 샘플의 저장된 컬러에 의해 결정된다.



 기본적으로 더 많은 샘플 포인트가 삼각형으로 덮여있을수록 최종 픽셀 색상은 삼각형의 색상이 된다. 이전에 멀티 샘플링되지 않은 삼각형을


사용한 것처럼 픽셀 색상을 채우면 다음 이미지가 생성된다:


Rasterized triangle with multisampling in OpenGL


 각 픽셀에 대해 삼각형의 일부가 적은 서브 샘플일수록 이미지에서 볼 수 있는 것처럼 삼각형의 색상을 취하지 않는다. 삼각형의 딱딱한


가장자리는 이제 실제 가장자리 색상보다 약간 가벼운 색상으로 둘러싸여 먼 거리에서 보았을 때 가장자리가 부드럽게 나타난다.



 색상 값은 멀티 샘플링의 영향을 받을 뿐만 아니라 깊이 및 스텐실 테스트에서도 여러 샘플 포인트를 사용한다. 깊이 테스트를 위해


깊이 테스트를 실행하기 전에 각 서브 샘플에 정점의 깊이 값을 보간하고 스텐실 테스트를 위해 픽셀 당 대신


서브 샘플 당 스텐실 값을 저장한다. 이것은 현재 깊이와 스텐실 버퍼의 크기가 픽셀당 서브 샘플의 양에 의해 증가된다는 것을 의미한다.



 지금까지 살펴본 내용은 멀티 샘플 안티 앨리어싱이 백그라운드에서 어떻게 작동하는지에 대한 기본적인 개요이다. 래스터 라이저 뒤에


있는 실제 논리는 여기에서 설명한 것보다 약간 복잡하지만 멀티 샘플 안티 앨리어싱의 개념과 논리를 이해할 수 있어야한다.




MSAA in OpenGL


 OpenGL에서 MSAA를 사용하려면 픽셀 당 하나 이상의 색상 값을 저장할 수 있는 색상 버퍼를 사용해야한다. 따라서 우리는 주어진 양의


멀티 샘플을 저장할 수 있는 새로운 유형의 버퍼가 필요하며 이것을 멀티 샘플 버퍼라고 한다.



 대부분의 윈도우 시스템은 기본 색상 버퍼 대신 다중 샘플 버퍼를 제공 할 수 있다. GLFW는 또한 우리에게 이 기능을 제공한다. GLFW는


창을 만들기 전에 glfwWindowHint를 호출해 일반 색상 버퍼 대신 N 샘플을 사용하는 멀티 샘플 버퍼를 사용하고자 한다:

glfwWindowHint(GLFW_SAMPLES, 4);

 이제 glfwCreateWindow를 호출하면 화면당 4개의 하위 샘플이 포함된 색상 버퍼가 있는 렌더링 창이 생성된다. 또한, GLFW는 픽셀당 4개의


하위 샘플을 사용해 깊이 및 스텐실 버퍼를 자동으로 생성한다. 이것은 모든 버퍼의 크기가 4씩 증가한다는 것을 의미한다.



 GLFW에 멀티 샘플 버퍼를 요청했으므로 glEnable을 호출하고 GL_MULTISAMPLE을 활성화해 멀티 샘플링을 활성화해야한다.


대부분의 OpenGL 드라이버에서 멀티 샘플링은 기본적으로 활성화되어 있으므로 이 호출은 약간 중복되지만 대개의 경우 활성화하는


것이 좋다. 이 방법으로 모든 OpenGL 구현은 멀티 샘플링이 가능하다.

glEnable(GL_MULTISAMPLE);  

 기본 프레임 버퍼에 멀티 샘플링 된 버퍼 첨부 파일이 있으면 멀티 샘플링을 사용하려면 glEnable을 호출하면 된다. 실제 멀티 샘플링 알고리즘은


OpenGL 드라이버의 래스터 라이저에 구현 되었기 때문에 우리가 해야 할 일이 별로 없다. 이 튜토리얼의 시작부터 녹색 큐브를 렌더링한다면


우리는 더 매끄러운 가장자리를 보아야한다:


Image of a multisampled cube in OpenGL


 이 컨테이너는 실제로 더 부드럽게 보이고 장면에서 그리는 다른 모든 객체에도 동일하게 적용된다.




Off-screen MSAA


GLFW가 멀티 샘플 버퍼 생성을 담당하기 때문에 MSAA를 사용하는 것은 매우 쉽다. 그러나 우리 자신의 프레임 버퍼를 사용하고 싶다면,


Off-screen 렌더링을 위해 멀티 샘플 버퍼를 생성해야한다. 이제 다중 샘플 버퍼를 생성해야한다.



 프레임 버퍼용 첨부 파일로 작동하는 멀티 샘플 버퍼를 만들 수 있는 두 가지 방법이 있다. 텍스처 첨부 파일과 렌더 버퍼 첨부 파일이다.


프레임 버퍼 튜토리얼에서 설명한 것처럼 일반적인 첨부 파일과 유사하다.



Multisampled texture attachments


여러 샘플 포인트의 저장을 지원하는 텍스처를 생성하기 위해서 glTexImage2D 대신 GLTexImage2DMultisample을 사용한다.


GL_TEXTURE_2D_MULTISAPLE을 텍스처 타겟으로 허용한다:

glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, tex);
glTexImage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, samples, GL_RGB, width, height, GL_TRUE);
glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, 0);  

 이제 두 번째 인수는 텍스처에 포함시킬 샘플 수를 설정한다. 마지막 인수가 GL_TRUE와 같으면 이미지는 동일한 샘플 위치와


각 텍셀에 대해 동일한 수의 하위 샘플을 사용한다.



 멀티 샘플링된 텍스처를 프레임 버퍼에 첨부하기 위해 glFramebufferTexture2D를 사용했지만 이번에는 GL_TEXTURE_2D_MULTISAMPLE을


텍스처 유형으로 사용했다:

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, tex, 0); 

 현재 바인드되고 있는 프레임 버퍼에는 텍스처 샘플의 형식으로 멀티 샘플링된 컬러 버퍼가 포함되어있다.



Multisampled renderbuffer objects


 텍스처와 마찬가지로 다중 샘플 렌더 버퍼 객체를 만드는 것도 어렵지 않다. 변경할 필요가 있는 것은 glRenderbufferStorage에 대한


호출인데, glRenderbufferStorageMultisample 렌더 버퍼의 메모리 저장소를 지정하는 것은 매우 쉽다:

glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, width, height);  

 여기에서 변경된 한 가지는 renderbuffer 대상 다음에 추가 매개 변수를 지정하는 것으로 이 예제에서는 4개의 샘플을 설정한다.




Render to multisampled framebuffer


 멀티 샘플링된 프레임 버퍼 객체에 대한 렌더링은 자동으로 진행된다. 프레임 버퍼 객체가 바인딩되어 있는 동안 우리가 무언가를


그릴 때마다 래스터라이저는 모든 멀티 샘플 작업을 처리한다. 그런 다음 멀티 샘플링된 컬러 버퍼 또는 깊이 및 스텐실 버퍼로 끝난다.


다중 샘플 버퍼는 약간 특수한 것이기 때문에 버퍼 이미지를 쉐이더에서 샘플링하는 것과 다른 작업에 직접 사용할 수는 없다.



 멀티 샘플링된 이미지에는 일반 이미지보다 훨씬 많은 정보가 포함되어 있으므로 이미지를 축소하거나 해결해야한다. 멀티 샘플링된


프레임 버퍼를 해결하는 것은 일반적으로 glBlitFramebuffer를 통해 이루어지며 이는 한 프레임 버퍼에서 다른 프레임 버퍼로 영역을


복사하는 동시에 멀티 샘플링 된 버퍼를 해결한다.



 glBlitFramebuffer는 4개의 스크린 공간 좌표로 정의된 주어진 소스 영역을 4개의 스크린 공간 좌표로 정의된 주어진 대상 영역으로


전송한다. 프레임 버퍼 튜토리얼에서 GL_FRAMEBUFFER에 바인드하면 우리는 프레임 버퍼 대상을 모두 읽고 바인딩 할 수 있다는 것을


기억할 것이다. 프레임 버퍼를 GL_READ_FRAMEBUFFER 및 GL_DRAW_FRAMEBUFFER에 각각 바인딩해 개별적으로 대상에 바인딩 할 수도 있다.


glBlitFramebuffer 함수는 소스와 대상 프레임 버퍼를 결정하기 위해 두 대상에서 읽는다. 그런 다음 이미지를 기본 프레임 버퍼에 blitting해


다중 샘플 프레임 버퍼 출력을 실제 화면으로 전송할 수 있다:

glBindFramebuffer(GL_READ_FRAMEBUFFER, multisampledFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST); 

 그런 다음 응용 프로그램을 렌더링 할 경우 프레임 버퍼가 없는 경우와 동일한 출력을 얻을 수 있다. MSAA를 사용해 표시되는 연두색


큐브가 있으므로 가장자리가 덜 들쭉날쭉하다:


Image of a multisampled cube in OpenGL


 그러나 다중 샘플 프레임 버퍼의 텍스처 결과를 post-processing과 같은 작업에 사용하려면 어떻게 해야 할까?


조각 쉐이더에서 멀티 샘플 텍스처를 직접 사용할 수는 없다. 우리가 할 수 있는 것은 멀티 샘플링되지 않은 버퍼를 다른 멀티 샘플링된


텍스처 첨부된 FBO에 blit 시키는 것이다. 그런 다음 이 일반 색상 첨부 텍스처를 post-processing에 사용해 멀티 샘플링을 통해


렌더링된 이미지를 효과적으로 후처리한다. 이는 다중 샘플 버퍼를 조각 쉐이더에서 사용할 수 있는 일반적인 2D 텍스처로 분해하기 위해


중간 프레임 버퍼 객체로만 작동하는 새로운 FBO를 생성해야 함을 의미한다. 이 프로세스는 의사 코드에서 다음과 같이 보인다:

unsigned int msFBO = CreateFBOWithMultiSampledAttachments();
// then create another FBO with a normal texture color attachment
...
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, screenTexture, 0);
...
while(!glfwWindowShouldClose(window))
{
    ...
    
    glBindFramebuffer(msFBO);
    ClearFrameBuffer();
    DrawScene();
    // now resolve multisampled buffer(s) into intermediate FBO
    glBindFramebuffer(GL_READ_FRAMEBUFFER, msFBO);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, intermediateFBO);
    glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    // now scene is stored as 2D texture image, so use that image for post-processing
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    ClearFramebuffer();
    glBindTexture(GL_TEXTURE_2D, screenTexture);
    DrawPostProcessingQuad();  
  
    ... 
}

 이를 프레임 버퍼 튜토리얼의 후 처리 코드에 구현하면 가장자리가 들쭉날쭉한 장면 텍스처에 모든 종류의 멋진 후 처리 효과를 만들 수 있다.


블러 커널 필터를 적용하면 다음과 같이 보일 것이다:


Image of post-processing on a scene drawn with MSAA in OpenGL

화면 텍스처는 단 하나의 샘플 포인트만으로도 보통의 텍스처이기 때문에 모서리 감지와 같은 일부 사후 처리 필터는 들쭉날쭉한 모서리를 다시 나타낸다. 이 문제를 해결하기 위해 나중에 텍스처를 흐리게 만들거나 고유한 안티 앨리어싱 알고리즘을 만들 수 있다.

 우리는 멀티 샘플링과 오프 스크린 렌더링을 결합할 때 몇 가지 추가 세부 사항을 추가해야한다는 것을 알 수 있다. 멀티 샘플링을 사용하면


장면의 시각적 품질을 크게 향상시킬 수 있기 때문에 모든 세부 사항을 추가로 테스트 할 가치가 있다. 멀티 샘플링을 활성화하면 사용하는


샘플이 많을수록 응용 프로그램의 성능이 현저히 떨어질 수 있다. 이 글을 쓰는 시점에서 MSAA를 4개 샘플과 함께 사용하는 것이 일반적으로


선호된다.




Custom Anti-Aliasing algorithm


 멀티 샘플링된 텍스처 이미지를 먼저 해석하는 대신 쉐이더에 직접 전달하는 것도 가능하다. 그런 다음 GLSL는 서브 샘플마다 텍스처 이미지를


샘플링 할 수 있는 옵션을 제공하므로 대용량 그래픽 응용 프로그램에서 할 수 있는 옵션을 제공하므로 대용량 그래픽 응용 프로그램에서


일반적으로 수행하는 자체 안티 앨리어싱 알고리즘을 만들 수 있다.



 하위 샘플당 색상 값을 가져오려면 일반 uniform2D 대신에 텍스처 균일화 샘플러를 sampler2D로 정의해야한다:

uniform sampler2DMS screenTextureMS;    

 texelFetch 함수를 사용하면 샘플당 색상 값을 검색 할 수 있다:

vec4 colorSample = texelFetch(screenTextureMS, TexCoords, 3);  // 4th subsample

 커스텀 안티 앨리어싱 기법을 만드는 방법에 대해서는 자세히 설명하지 않지만 이와 같은 기능을 구현하는 방법에 대한 지침만 제공한다.





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


저번 튜토리얼도 그렇고, 이번 튜토리얼도 그렇고 잔디 그리는게 자꾸 생각난다.