본문 바로가기

Game/Graphics

LearnOpenGL - Advanced OpenGL : Blending

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


Blending


 OpenGL에서 블렌딩은 일반적으로 객체 내에서 투명성을 구현하는 기술로 알려져 있다. 투명성은 단색을 가지지 않는 오브젝트에


관한 것이지만 오브젝트 자체와 그 뒤에 있는 다른 오브젝트의 색상을 다양한 강도로 조합한 것이다. (유색 유리창은 투명한 물체이다)


유리에는 자체 색상이 있지만 결과 색상에는 유리 뒤에 있는 모든 물체의 색상이 포함된다.


여러 색상(여러 오브젝트)을 단일 색상으로 혼합하기 때문에 블렌딩이라는 이름이 붙여진 까닭이기도 하다.


투명성은 객체를 통과해서 볼 수 있게 한다.


Image of full transparent window and partially transparent window


투명 오브젝트는 완전히 투명할 수 있다. (모든 색상을 통과시킬 수 있다는 말이다)


또는 부분적으로 투명하다. (색상을 통과시키지만 일부 색상은 보여준다는 말이다)


객체의 투명도는 색상의 알파 값으로 정의된다. 알파 색상 값은 현재 자주 보았던 색상 벡터의 4번째 구성 요소이다.


이 튜토리얼을 시작하기 전에는 항상 4번째 구성 요소의 값을 1.0으로 유지해 객체 0.0의 투명도를 지정하고 알파 값 0.0을


지정하면 객체의 투명도가 완전히 높아진다. 알파 값 0.5는 50%의 투명도를 나타낸다.



 지금가지 사용한 텍스처는 모두 빨강, 녹색, 파랑의 3가지 색상 구성 요소로 이루어져 있지만 일부 텍스처에는 텍셀당 알파 값이


포함된 알파 채널이 포함되어 있다. 이 알파 값은 텍스처의 어느 부분이 투명성을 갖고 있는지 그리고 얼마만큼의 텍스처가 있는지를


정확하게 알려준다. 예를 들어, 아래에 있는 윈도우 텍스처는 유리 부분에 0.25의 알파 값을 갖고, 코너의 알파 값은 0.0이다:


Texture image of window with transparency


 우리는 곧 이 윈도우 텍스처를 장면에 추가할 것이다. 하지만 먼저 완전히 투명하거나 완전 불투명한 텍스처에 대해 투명도를


구현하는 보다 쉬운 기술에 대해 살펴보겠다.







Discarding fragments


 일부 이미지는 부분 투명도는 신경쓰지 않지만 텍스처의 색상 값을 기준으로 무언가를 표시하거나 전혀 표시하지 않으려는 경우도


있다. 잔디를 생각해보자. 작은 노력으로 잔디와 같은 것을 만드려면 일반적으로 잔디 텍스처를 2D 쿼드에 붙여넣고 그 쿼드를 장면에


배치한다. 그러나 잔디는 2D 사각형과 똑같은 모양이 아니므로 잔디 텍스처의 일부분만 표시하고 나머지는 무시하면 된다.



 다음 텍스처는 완전 불투명 또는 완전 투명이고, 그 사이에 아무것도 없는 텍스처와 정확히 같다. 잔디가 없는 곳이라면 그 이미지가


웹 사이트의 배경색이 아니라 자체 웹 사이트의 배경색임을 알 수 있다.


Texture image of grass with transparency


 따라서 잔디와 같은 초목을 장면에 추가 할 때는 잔디의 정사각형 이미지를 보여주지 않고, 실제 풀만 보여주고 나머지 이미지는 필요없다.


색상 버퍼에 조각을 저장하지 않고 텍스처의 투명한 부분을 나타내는 조각을 버리려고 한다. 먼저 투명 텍스처를 로드하는 방법을 배워야한다.



 알파 값으로 텍스처를 로드하려면 변경할 필요가 거의 없다. stb_image는 이미지의 알파 채널을 사용할 수 있는 경우 자동으로 로드하지만,


OpenGL에 알려야 할 필요가 있다. 이제 텍스처가 텍스처 생성 절차에서 알파 채널을 사용한다:

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);  

 또한, RGB 구성 요소가 아닌 조각 쉐이더에서 텍스처의 4가지 색상 구성 요소를 모두 검색해야한다:

void main()
{
    // FragColor = vec4(vec3(texture(texture1, TexCoords)), 1.0);
    FragColor = texture(texture1, TexCoords);
}

 투명 텍스처를 로드하는 방법을 알았으므로 이제 깊이 테스트 튜토리얼에서 소개된 기본 장면 전체에 이러한 잔디 잎을 몇 개 추가하여 테스트해보자.



 우리는 몇 개의 glm::vec3 변수를 추가해 잔디 잎의 위치를 나타내는 작은 벡터를 만든다:

vector<glm::vec3> vegetation;
vegetation.push_back(glm::vec3(-1.5f,  0.0f, -0.48f));
vegetation.push_back(glm::vec3( 1.5f,  0.0f,  0.51f));
vegetation.push_back(glm::vec3( 0.0f,  0.0f,  0.7f));
vegetation.push_back(glm::vec3(-0.3f,  0.0f, -2.3f));
vegetation.push_back(glm::vec3( 0.5f,  0.0f, -0.6f));  

 잔디 개체 각각은 잔디 텍스처가 첨부된 단일 쿼드로 렌더링된다. 잔디를 완벽하게 3D로 표현하는 것은 아니지만 복잡한 모델을 실제로


로드하는 것보다 훨씬 효율적이다. 같은 위치에 여러 개의 회전된 잔디 쿼드를 추가하는 것과 같은 몇 가지 트릭을 사용하면 여전히 좋은


결과를 얻을 수 있다.



 잔디 텍스처가 쿼드 객체에 추가되므로 다른 VAO를 다시 작성하고, VBO를 채우고, 적절한 정점 속성 포인터를 설정해야한다.


그런 다음 바닥과 2개의 큐브를 그려 잔디 잎을 그린다:

glBindVertexArray(vegetationVAO);
glBindTexture(GL_TEXTURE_2D, grassTexture);  
for(unsigned int i = 0; i < vegetation.size(); i++) 
{
    model = glm::mat4();
    model = glm::translate(model, vegetation[i]);				
    shader.setMat4("model", model);
    glDrawArrays(GL_TRIANGLES, 0, 6);
}  

 이제 응용 프로그램을 실행하면 다음과 같이 보일 것이다:


Not discarding transparent parts of texture results in weird artifacts in OpenGL


 이것은 OpenGL이 기본적으로 알파 값을 처리할 것인지 또는 이를 버릴 것인지를 모르기 때문에 발생한다. 우리는 수동으로 이 작업을


직접 수행해야한다. 운좋게도 쉐이더를 사용하면 아주 쉽다. GLSL은 파편이 더 이상 처리되지 않아 결국 컬러 버퍼에 들어가지 않게하는


discard 명령을 제공한다. 이 명령 덕분에 조각 쉐이더가 특정 임계 값 아래의 알파 값을 얻는지 여부를 조각 쉐이더에서 확인할 수 있다.


그렇다면 조각 쉐이더가 처리되지 않은 것처럼 조각을 버린다:

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D texture1;

void main()
{             
    vec4 texColor = texture(texture1, TexCoords);
    if(texColor.a < 0.1)
        discard;
    FragColor = texColor;
}

 여기에 샘플링된 텍스처 색상에 임계 값 0.1보다 낮을 알파값이 포함되어 있는지 확인하고, 그렇다면 조각을 버린다.


이 조각 쉐이더는 (거의) 완전히 투명하지 않은 단편만 렌더링한다는 것을 보장한다. 이제 다음과 같이 보일 것이다:


Image of grass leaves rendered with fragment discarding in OpenGL 경계에서 텍스처를 샘플링 할 때 OpenGL은 텍스처의 다음 반복 값으로 테두리 값을 보간한다. (랩핑 매개 변수를 GL_REPEAT로 설정했기 때문에) 이것은 일반적으로 괜찮지만 투명한 값을 사용하기 때문에 텍스처 이미지의 맨 위가 아래쪽 테두리의 단색 값으로 보간된 투명 값을 가져온다. 그러면 약간 반투명한 색 테두리가 텍스처 쿼드에 감겨있다. 이를 방지하려면 텍스처 래핑 방법을 GL_CLAMP_TO_EDGE로 설정해야한다.

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);	
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);    








Blending


 조각을 버리는 것은 훌륭하지만 조각을 렌더링하거나 완전히 버리기 때문에 반 투명 이미지를 렌더링할 수 있는 유연성을 제공하지는 못한다.


서로 다른 투명도로 이미지를 렌더링하려면 블렌딩을 사용해야한다. 대부분의 OpenGL 기능과 마찬가지로 GL_BLEND를 활성화해 블렌딩을


활성화 할 수 있다:

glEnable(GL_BLEND);  

 블렌딩을 활성화 했으므로 OpenGL에게 실제로 혼합되어야 하는 방법을 알려줄 필요가 있다.



 OpenGL에서 블렌딩은 다음 방정식으로 수행된다:




- C¯source : 소스 색상 벡터. 텍스처에서 유래하는 색 벡터이다.


- C¯destination : 목적지 색 벡터. 이것은 현재 색상 버퍼에 저장된 색상 벡터이다.


- F source : 소스 요소 값이다. 소스 색상에 대한 알파 값의 영향을 설정한다.


- F destination : 대상 요소 값이다. 알파 값이 대상 색상에 미치는 영향을 설정한다.



 조각 쉐이더가 실행되고 모든 테스트가 통과 된 후, 이 블렌드 방정식은 조각의 컬러 출력과 현재 컬러 버퍼에 있는 것에 대해 느슨해진다.


원본 및 대상 색상은 OpenGL에서 자동으로 설정되지만 원본 및 대상 요소는 우리가 선택한 값으로 설정할 수 있다. 간단한 예부터 시작해보자:


Two squares where one has alpha value lower than 1


 우리는 붉은 square에 반투명한 녹색 사각형을 그려야한다. 빨간색 사각형은 대상 색상이 될 것이므로 (따라서 색상 버퍼에서 첫번째여야함)


이제 빨간색 사각형 위에 녹색 사각형을 그린다.



 그런 다음 문제가 발생한다: 요인 값을 어떻게 설정해야하나? 음, 녹색 사각형에 알파 값을 곱해서 Fsrc를 소스 색 벡터의 알파 값 0.6으로


설정하려고 한다. 그런 다음 대상 사각형에 알파 값의 나머지와 동일한 기여도를 부여하는 것이 좋다. 초록색 사각형이 최종 색상에 60%를


기여하면 적색 사각형이 최종 색상의 40%를 차지하도록 하자. 그래서 F destestination을 소스 색 벡터의 알파 값에서 1을 뺀 값으로 설정한다.


따라서 방정식은 다음과 같다:


 결과적으로 결합된 정사각형 조각에는 녹색 60% 및 빨간색 40%의 색상이 포함되어있어 더러운 색상을 나타낸다:


Two containers where one has alpha value lower than 1


 그 결과 색상은 이전 색상을 대체하는 색상 버퍼에 저장된다.


 그렇다면 이 모든 것이 중요하다. 하지만 OpenGL에게 이러한 요소를 사용하도록 실제로 어떻게 말할까? glBlendFunc라는 함수가 있따.



 glBlendFunc (GLenum sfactor, GLenum dfactor) 함수는 소스 및 대상 요소에 대한 옵션을 설정하는 두 개의 매개 변수를 필요로 한다.


OpenGL은 우리에게 다음과 같은 가장 일반적인 옵션을 나열 할 수 있는 몇 가지 옵션을 정의했다. 상수 색 벡터 C_constant는 glBlendColor 함수를


통해 별도로 설정할 수 있다.


OptionValue
GL_ZEROFactor is equal to 0.
GL_ONEFactor is equal to 1.
GL_SRC_COLORFactor is equal to the source color vector C¯source.
GL_ONE_MINUS_SRC_COLORFactor is equal to 1 minus the source color vector: 1C¯source.
GL_DST_COLORFactor is equal to the destination color vector C¯destination
GL_ONE_MINUS_DST_COLORFactor is equal to 1 minus the destination color vector: 1C¯destination.
GL_SRC_ALPHAFactor is equal to the alpha component of the source color vector C¯source.
GL_ONE_MINUS_SRC_ALPHAFactor is equal to 1alpha of the source color vector C¯source.
GL_DST_ALPHAFactor is equal to the alpha component of the destination color vector C¯destination.
GL_ONE_MINUS_DST_ALPHAFactor is equal to 1alpha of the destination color vector C¯destination.
GL_CONSTANT_COLORFactor is equal to the constant color vector C¯constant.
GL_ONE_MINUS_CONSTANT_COLORFactor is equal to 1 - the constant color vector C¯constant.
GL_CONSTANT_ALPHAFactor is equal to the alpha component of the constant color vector C¯constant.
GL_ONE_MINUS_CONSTANT_ALPHAFactor is equal to 1alpha of the constant color vector C¯constant.


 먼저 두 개의 사각형에서 얻은 블렌딩 결과를 얻으려면 원본 요소에 대한 원본 색 벡터의 알파와 대상 요소에 대한 1-alpha를 가져 오려고 한다.


이것은 다음과 같이 glBlendFunc로 변환된다:

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);  

 glBlendFuncSeperate를 사용해 RGB 및 알파 채널에 대해 다른 옵션을 개별적으로 설정할 수도 있다:

glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);

 이 함수는 이전에 설정한대로 RGB 구성 요소를 설정하지만 결과 알파 구성 요소가 원본 알파 값의 영향을 받도록 허용한다.



 OpenGL은 우리가 방정식의 소스와 목적지 부분 사이의 연산자를 변경할 수 있게 ㅎ마으로써 더 많은 유연성을 제공한다.


현재 원본 및 대상 구성 요소가 함께 추가되지만 원할 경우 해당 구성 요소를 빼낼 수도 있다. glBlendEquation은 우리가 이 작업을


설정할 수 있게 하며 가능한 세 가지 옵션을 가지고 있다:


- GL_FUNC_ADD : 기본값이며 두 구성 요소를 서로 추가한다 : 


- GL_FUNC_SUBTRACT : 두 구성 요소를 서로 뺀다 : 


- GL_FUNC_REVERSE_SUBTRACT : 두 구성 요소를 모두 뺀 다음 순서를 바꾼다 : 



 보통 glBlendEquation에 대한 호출을 생략 할 수 있다. GL_FUNC_ADD가 대부분의 연산에 선호되는 블렌딩 방정식이기 때문이다.


하지만 주류 회로를 깨기 위해 최선의 노력을 기울이고 있다면, 다른 방정식이 필요에 따라 달라질 수 있다.







Rendering semi-transparent textures


 블렌딩과 관련해 OpenGL이 어떻게 작동하는지 알게되었으므로 여러 개의 반투명 윈도우를 추가해 지식을 테스트에 적용할 시간이다.


이 튜토리얼의 시작 부분과 동일한 장면을 사용할 것이다. 그러나 잔디 텍스처를 렌더링하는 대신 이 튜토리얼의 시작부터 투명 창 텍스처를


사용할 것이다.



 먼저 초기화하는 동안 블렌딩을 활성화하고, 적절한 블렌딩 함수를 설정한다:

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

 블렌딩을 활성화했으므로 조각을 버릴 필요가 없어서 조각 쉐이더를 원래 버전으로 재설정한다:

#version 330 core
out vec4 FragColor;

in vec2 TexCoords;

uniform sampler2D texture1;

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

 이번에는 현재 조각의 색상과 알파 값에 따라 현재 색상 버퍼에 있는 조각 색상이 결합된다. 윈도우 텍스처의 유리 부분이 반투명하기 때문에


이 윈도우를 통해 장면의 나머지 부분을 볼 수 있어야한다.


A blended scene in OpenGL where order is incorrect.


 그러나 자세히 살펴보면 무언가가 꺼져있음을 알 수 있다. 앞면 윈도우의 투명한 부분이 배경에 있는 윈도우를 가리고 있다.


왜 이런 일이 일어나는걸까?



 그 이유는 깊이 테스트가 블렌딩과 조금 복잡해지기 때문이다. 깊이 버퍼에 쓸 때, 깊이 테스트는 조각이 투명성을 가지는지


아닌지 상관하지 않으므로 투명 부분이 다른 값으로 깊이 버퍼에 기록된다. 결과적으로 투명도에 관계없이 창의 전체 쿼드가


깊이 테스트를 확인한다. 투명한 부분에 창을 표시해야한다고 해도 depth test는 이를 무시한다.



 따라서 우리는 단순히 창을 렌더링 할 수는 없지만 깊이 버퍼가 모든 문제를 해결할 것으로 기대한다. 블렌딩이 조금 불쾌해지기도 한다.


윈도우 뒤에 윈도우를 표시하려면 윈도우를 먼저 배경으로 그려야한다. 즉, 윈도우를 가장 먼 곳에서 가장 가까운 곳으로 수동으로 정렬하고


그에 따라 그려라.

 잔디 잎과 같이 완전히 투명한 객체를 사용하면 투명한 조각을 블렌딩하는 대신 단순히 폐기해 깊이 문제를 없앨 수 있다.







Don't break the order


 블렌딩을 여러 객체에 대해 수행하려면 가장 먼 객체를 먼저 그리고, 가까운 객체를 마지막으로 그려야한다. 혼합되지 않은 일반 오브젝트는


깊이 버퍼를 사용해 정상적으로 그려질 수 있으므로 정렬할 필요가 없다. 우리는 투명 오브젝트를 드로잉하기 전에 그 오브젝트가 먼저


그려지도록 해야한다. 투명하지 않은 투명한 오브젝트로 장면을 그릴 때 일반적인 개요는 일반적으로 다음과 같다.


1. 모든 불투명한 객체를 먼저 그린다.


2. 투명 오브젝트를 모두 정렬해라.


3. 모든 투명 오브젝트를 정렬된 순서로 그린다.


 투명한 오브젝트를 정렬하는 한 가지 방법은 뷰어의 관점에서 오브젝트의 거리를 검색하는 것이다. 이것은 카메라의 위치 벡터와 객체의


위치 벡터 사이의 거리를 취함으로써 달성할 수 있다. 그런 다음 이 거리를 해당 위치 벡터와 함께 STL 라이브러리의 map 데이터 구조에 저장한다.


map은 키를 기반으로 값을 자동으로 정렬하므로 거리를 키로 하여 모든 위치를 추가하면 거리 값에 따라 자동 정렬된다:

std::map<float, glm::vec3> sorted;
for (unsigned int i = 0; i < windows.size(); i++)
{
    float distance = glm::length(camera.Position - windows[i]);
    sorted[distance] = windows[i];
}

 결과는 가장 낮은 거리에서 가장 먼 거리까지의 거리 키 값을 기반으로 각 창 위치를 저장하는 정렬된 컨테이너 객체이다.



 그런 다음 이번에는 렌더링 할 때 각 지도 값을 역순으로 가져와서 해당 윈도우를 올바른 순서로 그린다:

for(std::map<float,glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it) 
{
    model = glm::mat4();
    model = glm::translate(model, it->second);				
    shader.setMat4("model", model);
    glDrawArrays(GL_TRIANGLES, 0, 6);
}  

 지도의 역 반복자를 사용해 각 항목을 역순으로 반복한 다음 각 윈도우 쿼드를 해당 윈도우 위치로 변환한다. 투명 오브젝트를 정렬하는


비교적 단순한 접근 방식은 이전 문제점을 수정하고 이제 장면은 다음과 같다:


Image of an OpenGL scene with blending enabled, objects are sorted from far to near


 거리를 기준으로 객체를 정렬하는 이러한 접근 방식은 이 특정 시나리오에서 잘 작동하지만 회전, 크기 조정 등의 다른 변형을


고려하지 않으며 이상한 모양의 객체는 단순히 위치 벡터와 다른 측정 기준을 필요로 한다.



 장면에서 오브젝트를 정렬하는 것은 장면의 유형에 크게 좌우되며, 추가 처리 능력은 물론 비용이 많이 소요된다. 완벽하고 투명한


객체로 장면을 완전히 렌더링하는 것은 쉽지 않다. 순서 독립적 투명성과 같은 고급 기술이 있지만 이 튜토리얼의 범위를 벗어난다.


이제는 객체를 정상적으로 블렌딩해야만 한다. 하지만 주의를 기울이고 제한 사항을 알면 여전히 상당한 블렌딩 구현을 얻을 수 있다.




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


블렌딩이 끝났다. 깊이 테스트와 엮어서 공부하는데 음! 순서를 잘 알고 써야하다니.


상용 엔진을 사용할 때는 몰랐는데 새로운 지식을 얻어서 기분이 좋다.