5장 연습문제 5.2
문제
스프라이트 버텍스를 수정해서 각 버텍스가 자신과 관련된 RGB 색상을 가지도록 하자. 이 RGB 색상은 버텍스 색상으로 알려져 있다.
- 조건1: 버텍스 셰이더를 수정해서 이 버텍스 색상을 입력으로 받고 프래그먼트 셰이더로 버텍스 색상을 넘기자.
- 조건2: 프래그먼트 셰이더를 변경해서 입력받은 버텍스 색상과 텍스처 색상의 평균을 그려보자.
풀이
이번 문제는 셰이더 프로그래밍을 직접해보는 것이 취지이지만 교재에 나와있는 셰이더 문법은 문제 풀기에 조금 부족하다고 느껴졌다. 저자가 소개해준 Learn OpenGL 사이트에서 셰이더에 대한 간단한 문법정도를 참고하면 도움이 될 것이다. 한국어로 번역해주신 블로그는 여기에서 확인할 수 있다.
우선 구현 결과물을 먼저 보자. 우리의 최종 목적은 버텍스 색상과 텍스처 색상의 평균으로 스프라이트를 그리는 것이다. 필자는 버텍스 색상을 파랑색으로 정하고 출력을 만들었다. 아래와 같이 스프라이트 이미지에 파랑색 필터가 적용된것 처럼 출력되는 것을 확인할 수 있다.
구현 결과
구현
1단계 프래그먼트 셰이더 수정
최종 출력을 담당하는 프래그 먼트셰이더 부터 버텍스 셰이더, 버텍스 배열 순으로 구현해보았다.
위에서 소개한 Learn OpenGL 번역 블로그를 보면 Vector 데이터 타입으로 swizzling이라고 불리는 흥미롭고 유연한 요소 선택 문법을 따른다는 것을 확인 할 수 있다.
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
우선 프래그먼트 셰이더는 이 swizzling 문법을 활용해서 아래와 같이 수정하였다.
Sprite.frag
#version 330
in vec2 fragTexCoord;
in vec3 vertexColor; // 버텍스 셰이더로부터 입력받을 버텍스 색상
uniform sampler2D uTexture;
// 출력 색상을 저장하기 위해 out 변수 지정자를 사용해서 전역 변수를 선언
out vec4 outColor;
void main()
{
vec4 textureColor = texture(uTexture, fragTexCoord);
// 텍스처로부터 얻은 색상과 버텍스 색상의 평균을 outColor로 지정
outColor.xyz = (vertexColor.xyz + textureColor.xyz) / 2;
outColor.w = textureColor.w;
}
2단계 버텍스 셰이더 수정
버텍스 셰이더에서 수정한 부분은 2가지이다.
- 버텍스 색상 입력 부분
- 버텍스 색상 출력 부분
버텍스 색상을 C++ 코드로부터 입력받을 방법이 필요하다. 우리가 배운 방법은 아래의 두가지가 있다.
uniform
으로 입력받기layout
으로 입력받기
먼저 uniform
으로 입력을 받는 것은 버텍스마다 적용하기에 부적절하다고 판단했다. 왜냐하면 uniform
은 오브젝트 좌표계에서 세계 좌표계로 변환하는 World Transform이나 View Transform처럼 하나의 오브젝트에 동일하게 적용되는 데이터를 입력으로 받을 때 효과적으로 사용할 수 있다고 생각했기 때문이다.
반면 layout
을 활용할 경우 그 버텍스의 위치정보 뿐만아니라 텍스쳐 맵핑좌표와 같이 각각의 버텍스에 대해 데이터를 받을 수 있다. 때문에 버텍스 마다 색상을 지정해야 하는 이번 문제에서는 layout
을 활용해서 구현하는 것이 효과적이라고 판단했다.
#version 330
uniform mat4 uWorldTransform;
uniform mat4 uViewProj;
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec2 inTexCoord;
// 버텍스 색상을 2번 속성으로 받는다.
layout(location = 2) in vec3 inVertexColor;
out vec2 fragTexCoord;
out vec3 vertexColor;
void main()
{
vec4 pos = vec4(inPosition, 1.0);
gl_Position = pos * uWorldTransform * uViewProj;
fragTexCoord = inTexCoord;
// 입력받은 버텍스 색상을 그대로 프래그먼트 셰이더로 넘겨준다.
vertexColor = inVertexColor;
}
3단계 VertexArray
클래스 수정
교재에서 안내한 이번 버전의 VertexArray
클래스는 vertex에 추가정보를 담기 위해서는 수작업으로 직접 클래스를 수정해야한다. 기존 vertex의 정보에는 (x,y,z) 위치좌표가 0번 속성(layout(location = 0)
)에 해당하고 (u,v) 텍스처 매핑 좌표가 1번 속성(layout(location = 1)
)에 해당한다. 우리가 추가하고 싶은것은 버텍스 색상 (R,G,B) 값이기 때문에 다음과 같이 VertexArray
의 생성자에서 2번 속성을 만들자.
VertexArray.cpp의 VertexArray::VertexArray()
VertexArray::VertexArray(const float* verts, unsigned int numVerts,
const unsigned int* indices, unsigned int numIndices)
: mNumVerts(numVerts)
, mNumIndices(numIndices)
{
// ... 생략 ...
// 한 정점의 데이터 수가 5개(xyz, uv)에서 8개(xyz, uv, rgb)로 늘어나기 때문에 수정
glBufferData(
GL_ARRAY_BUFFER,
numVerts * 8 * sizeof(float), // 복사할 바이트 크기 (정점 수 * 8(x,y,z,u,v,r,g,b) * 4)
verts,
GL_STATIC_DRAW
);
// ... 생략 ...
// 속성0: 버텍스는 3차원 좌표값(x,y,z)을 가진다.
glEnableVertexAttribArray(0);
// 한 정점의 데이터 수가 많아지기 때문에 버텍스 데이터간의 간격도 늘어난다.
glVertexAttribPointer(
0,
3,
GL_FLOAT,
GL_FALSE,
sizeof(float) * 8, // 버텍스 간의 간격. 5->8
0
);
// 속성1: 버텍스는 2차원 텍스처좌표(u, v)를 가진다.
glEnableVertexAttribArray(1);
// 한 정점의 데이터 수가 많아지기 때문에 버텍스 데이터간의 간격도 늘어난다.
glVertexAttribPointer(
1,
2,
GL_FLOAT,
GL_FALSE,
sizeof(float) * 8, // 버텍스 간의 간격. 5->8
reinterpret_cast<void*> (sizeof(float) * 3)
);
// 세 번째 버텍스 속성(속성2)을 활성화
// 속성2: 버텍스는 (r,g,b) 색상 값을 가진다.
glEnableVertexAttribArray(2);
glVertexAttribPointer(
2,
3, // 요소의 수 r,g,b 3개의 컴포넌트 존재
GL_FLOAT,
GL_FALSE,
sizeof(float) * 8, // 버텍스 간의 간격. 5->8
reinterpret_cast<void*> (sizeof(float) * 5) // 오프셋 포인터
);
4단계 Game::CreateSpriteVerts()
함수 수정
이제 원하는 색상을 추가해서 아래와 같이 스프라이트 버텍스 배열을 만들어주기만 하면 완성. 필자는 구현 화면처럼 파랑색을 사용했다.
void Game::CreateSpriteVerts()
{
float vertices[] = {
// 버텍스 위치(x,y,z) 텍스처 맵핑(u,v) 버텍스 색상(r,g,b,)
// x, y, z, u, v, r, g, b
-0.5f, 0.5f, 0.f, 0.f, 0.f, 0.1f, 0.1f, 0.8f, // top left
0.5f, 0.5f, 0.f, 1.f, 0.f, 0.1f, 0.1f, 0.8f, // top right
0.5f, -0.5f, 0.f, 1.f, 1.f, 0.1f, 0.1f, 0.8f, // bottom right
-0.5f, -0.5f, 0.f, 0.f, 1.f, 0.1f, 0.1f, 0.8f, // bottom left
};
unsigned int indices[] = {
0, 1, 2,
2, 3, 0
};
// 스프라이트를 그리기 위한 4각형 스프라이트 VertexArray 생성과정
// 앞으로 모든 sprite들은 이 멤버변수를 사용한다.
mSpriteVerts = new VertexArray(vertices, 4, indices, 6);
}