Blending
- OpenGL에서 블렌딩은 오브젝트 내의 투명도(transparency)를 구현하는 기술로 알려져 있음.
- 투명도: 단색(solid color)를 가지지 않는 오브젝트에 관한것이지만, 오브젝트 자체와 그 뒤에 있는 오브젝트의 색상을 다양한 강도로 조합한것.
유리에는 자체 색상이 있지만, 결과적으로는 유리 뒤에 있는 모든 오브젝트의 색상이포함된다.
여러 색상(다른 오브젝트들)을 단일 색상으로 혼합하기 때문에 Blending 으로 부름.
- 투명도는 통과해서 볼 수 있게 한다.
투명한 오브젝트(Transparent objects)는 완전히 투명(모든 색상 통과)하거나 부분적으로 투명(일부 자신의 색상또한 보여줌)하다.
투명도는 색상의
alpha
값으로 정의된다.- 색상 벡터의 4번째 요소
- ex)
a = 0.5
는 50%의 투명도
일부 텍스처는 텍셀당 알파 값이 포함된 알파 채널이 포함되어 있음. - 이 알파 값은 텍스처의 어느 부분이 투명성을 갖고 있는지, 얼마만큼의 텍스처가 있는지 정확히 알려줌. - 아래 창에서 유리는 0.25, 바깥쪽 코너는 0.0이다. - 유리부분은 75%의 투명도이므로, 이를 통해 본 배경은 더 빨간색으로 보임
완전히 투명(fully transparent) 또는 완전히 불투명(fully opaque)에 대해 다룬다.
Discarding fragments
일부 effects는 부분 투명도를 신경쓰지 않지만, 텍스처의 색상 값을 기준으로 무언가를 표시하거나 전혀 표시하지 않으려는 경우도 았다.
잔디 같은것을 만들려고하면, 2d quad 에 잔디 텍스처를 붙이고, 그 quad를 장면에 배치한다.
- 잔디는 실제로 이와같은 모양이 아니므로, 잔디 텍스처의 일부분만 표시하고, 나머지는 무시해야한다.
아래의 잔디 텍스처는 완전 불투명과 완전 투명이 섞여 있다.
- 투명한 배경/ 불투명한 잔디
이 텍스처를 불투명한 부분만 보여줘야한다.
따라서 투명한 부분을 나타내는 fragment를 폐기해야한다.
알파 채널 텍스처 로드
stb_image
는 알파채널이 있는 경우 알아서 설정한다.- OpenGL은 다음과 같이 텍스처 생성할 때 알려줘야한다.
1
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
- 이제 셰이더도 다음과 같이 vec4로 수정해야함
1
2
3
4
5
void main()
{
// FragColor = vec4(vec3(texture(texture1, TexCoords)), 1.0);
FragColor = texture(texture1, TexCoords);
}
잔디 추가
예제는 depth testing의 예제에서 코드를 추가할것이다.
잔디들의 위치 벡터들을 선언하자
1
2
3
4
5
6
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를 다시 작성해야함.
- 적절한 정점 속성 포인터를 설정
1
2
3
4
5
6
7
8
9
glBindVertexArray(vegetationVAO);
glBindTexture(GL_TEXTURE_2D, grassTexture);
for(unsigned int i = 0; i < vegetation.size(); i++)
{
model = glm::mat4(1.0f);
model = glm::translate(model, vegetation[i]);
shader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
- 이제 실행하면 다음과 같다.
- 문제: OpenGL이 기본적으로 알파 값을 처리할 것인지, 버릴 것인지 모르기 때문에 발생
- 셰이더를 사용하여, fragment를 폐기하자
discard
: fragment 를 폐기함.- 특정 임계값보다 작으면 폐기하도록할 수 있음.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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;
}
- 위 셰이더는 거의 투명하지 않은 fragment 만 렌더링하게 해준다.(아래 이미지 참고)
GL_REAPEAT
로 텍스처 래핑 방식을 설정하면, 다음 repeat 값으로 border를 보간한다. 만약 투명한 값이 있을 경우, 투명한 값과 단색 값으로 보간된 투명값을 가져올 수 있다. 그려면 약간 반투명한 색 테두리가 텍스처 쿼드에 래핑될 수 있다. 이를 방지하기 위해 래핑 방식을GL_CLAMP_TO_EDGE
로 설정해야한다.
1
2
3
4
```cpp
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
위처럼 폐기하는것은 반 투명 이미지를 렌더링할 때 유연성을 주지 못한다.
서로 다른 투명도로 이미지를 렌더링하려면 블렌딩을 활성화해야한다.
GL_BLEND
를 활성화해 블렌딩을 활성화 할 수 있다.
1
glEnable(GL_BLEND);
블렌딩을 활성화 했으므로, 혼합되어야 하는 방법을 알려줄 필요가 있다.
OpenGL은 다음 방정식으로 블렌딩을 수행한다.
- $\bar{\color{green}C}_{source}$: 소스 색상 벡터, 텍스처에서 온 색 벡터
- $\bar{\color{red}C}_{destination}$: 목적지 색상 벡터, 현재 컬러 버퍼에 저장된 색상 벡터
- $\color{green}F_{source}$: 소스 factor 값, 소스 색상에 대한 알파 값의 영향을 설정
- $\color{red}F_{destination}$: 대상 factor 값, 알파 값이 대상 색상에 미치는 영향을 설정한다.
- fragment shader가 실행되고 모든 테스트가 통과된 후, 이 혼합 방정식이 프래그먼트의 색상 출력과 현재 색상 버퍼에 있는 것들에 영향을 미친다.
예시
- 붉은 square에 반투명한 녹색 사격형을 그려보자.
- 빨간색 사각형은 대상 색상이 됨(칼라 버퍼의 첫번째)
- 녹색의 알파가 60%라고 하면 기여도는 알파값과 동일한 기여도를 부여하는것이 좋으므로 식은 다음과 같다.
- 결과는 다음과 같다.
glBlendFunc
glBlendFunc(GLenum sfactor, GLenum dfactor)
두 매개변수로 소스 및 대상 factor에 대한 옵션을 설정할 수 있다.
소스 factor와 대상 factor는 다음과 같은 값을 가질 수 있다.
GL_ZERO
: 0GL_ONE
: 1GL_SRC_COLOR
: 소스 색상GL_ONE_MINUS_SRC_COLOR
: 1- 소스 색상GL_DST_COLOR
: 대상 색상GL_ONE_MINUS_DST_COLOR
: 1- 대상 색상GL_SRC_ALPHA
: 소스 알파GL_ONE_MINUS_SRC_ALPHA
: 1 - 소스 알파GL_DST_ALPHA
: 대상 알파GL_ONE_MINUS_DST_ALPHA
: 1 - 대상 알파GL_CONSTANT_COLOR
: 상수 색상GL_ONE_MINUS_CONSTANT_COLOR
: 1- 상수 색상GL_CONSTANT_ALPHA
: 상수 알파GL_ONE_MINUS_CONSTANT_ALPHA
: 1 - 상수 알파GL_SRC_ALPHA_SATURATE
: 소스 알파 채도(i,i,i,1)
위 사각형 예제를 다음과 같이
glBlendFunc
함수로 나타낼 수 있다.
1
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBlendFuncSeperate
glBlendFuncSeperate
를 사용해 RGB 및 알파 채널에 대해 다른 옵션을 개별적으로 설정할 수 있다.- 아래는 RGB는 이전과 같지만, 알파는 원본 값의 영향을 받도록 하는 코드이다.
1
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ZERO);
glBlendEquation
방정식 연산자에 더 많은 유연성을 줌.
GL*FUNC_ADD: the default, adds both colors to each other: \(\bar{C}*{result} = \color{green}{Src} + \color{red}{Dst}\)
GL*FUNC_SUBTRACT: subtracts both colors from each other: \(\bar{C}*{result} = \color{green}{Src} - \color{red}{Dst}\)
- GL*FUNC_REVERSE_SUBTRACT: subtracts both colors, but reverses order: \(\bar{C}*{result} = \color{red}{Dst} - \color{green}{Src}\)
- GL*MIN: takes the component-wise minimum of both colors: \(\bar{C}*{result} = min(\color{red}{Dst}, \color{green}{Src})\)
GL*MAX: takes the component-wise maximum of both colors: \(\bar{C}*{result} = max(\color{red}{Dst}, \color{green}{Src})\)
- 보통 기본값인 ADD 사용한다.(가장 선호됨.)
Rendering semi-transparent textures
반투명 윈도우를 여러개 추가할것이다.
잔디 대신 투명 창 텍스처를 사용할 것임.
- 먼저 초기화하는 동안 블렌딩을 활성화하고, 적절한 블렌딩 함수를 설정한다.
1
2
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- 블렌딩을 활성화했으므로, frag를 폐기할 필요가 없어서 fragment shader를 다음과 같이 되돌린다.
1
2
3
4
5
6
7
8
9
10
11
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D texture1;
void main()
{
FragColor = texture(texture1, TexCoords);
}
현재 fragment 색상과 알파 값에 따라 현재 칼라 버퍼에 결합된다.
이제 다음과 같은 결과가 나온다.
- 여기서 문제가 하나 생긴다.
- 앞에있는 창에서 뒷부분을 볼 수 없는것.
- 이는 깊이테스트 때문에 일어난다.
깊이 테스트는 frag가 투명도를 신경쓰지 않아, 투명부분이 다른 값으로 깊이 버퍼에 덮어씌워진다.
- 그 결과, 투명도에 관계없이 창의 투명도를 무시하고, 다른 불투명 오브젝트와 마찬가지로 깊이테스트가 수행된다.
- 해결방법
- 가장 먼 곳에서 가장 가까운 곳으로 수종으로 정렬하고 따라 그려야한다.
잔디 잎같은것은 단순히 폐기하여 이런 문제를 신경쓰지 않게 할 수 있다.
Don’t break the order
- 여러 투명도를 가진 오브젝트를 그리려면, 가장 먼 객체를 먼저 그리고, 가까운 객체를 마지막에 그려야한다.
그리고, 투명 오브젝트를 드로잉하기전에 일반 오브젝트를 먼저 그려야한다.
일반적인 개요는 다음과 같다.
- 모든 불투명 오브젝트를 먼저 그림
- 투명 오브젝트를 모두 정렬
- 정렬된 이 오브젝트들을 순서대로 그림
정렬 방법
- 뷰어의 관점에서 거리를 검색
- 카메라의 위치 벡터와 객체의 위치 벡터 사이의 거리를 취함
- 이 거리를 해당 위치 벡터와 함께 map에 저장.(key값을 기반으로 자동 정렬)
1 2 3 4 5 6
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]; }
- 렌더링은 역순으로 가져온다.
1 2 3 4 5 6 7
for(std::map<float,glm::vec3>::reverse_iterator it = sorted.rbegin(); it != sorted.rend(); ++it) { model = glm::mat4(1.0f); model = glm::translate(model, it->second); shader.setMat4("model", model); glDrawArrays(GL_TRIANGLES, 0, 6); }
- 이제 다음과 같이 제대로 렌더링된다.
이러한 정렬은 회전, 크기 조정 등의 다른 변환을 고려하지 않는다.
- 일반적인 모양의 객체는 단순히 위치 벡터와 다른 측정 기준이 필요하다.
장면에서 오브젝트를 정렬하는 것은 장면에 따라 크게 달라진다.
- 비용이 많이 소요.
- 완벽하고 투명한 오브젝트로 장면을 완전히 렌더링하는것은 어려움.
- 순서 독립적 투명성(
order independent transparency
)과 같은 고급 기술이 있음.