본문으로 바로가기

5장 연습문제 5.2

문제

스프라이트 버텍스를 수정해서 각 버텍스가 자신과 관련된 RGB 색상을 가지도록 하자. 이 RGB 색상은 버텍스 색상으로 알려져 있다.

  • 조건1: 버텍스 셰이더를 수정해서 이 버텍스 색상을 입력으로 받고 프래그먼트 셰이더로 버텍스 색상을 넘기자.
  • 조건2: 프래그먼트 셰이더를 변경해서 입력받은 버텍스 색상과 텍스처 색상의 평균을 그려보자.

풀이

이번 문제는 셰이더 프로그래밍을 직접해보는 것이 취지이지만 교재에 나와있는 셰이더 문법은 문제 풀기에 조금 부족하다고 느껴졌다. 저자가 소개해준 Learn OpenGL 사이트에서 셰이더에 대한 간단한 문법정도를 참고하면 도움이 될 것이다. 한국어로 번역해주신 블로그는 여기에서 확인할 수 있다.

 

우선 구현 결과물을 먼저 보자. 우리의 최종 목적은 버텍스 색상과 텍스처 색상의 평균으로 스프라이트를 그리는 것이다. 필자는 버텍스 색상을 파랑색으로 정하고 출력을 만들었다. 아래와 같이 스프라이트 이미지에 파랑색 필터가 적용된것 처럼 출력되는 것을 확인할 수 있다.

Exercise_5_2 전체 소스코드

구현 결과

▲ 연습문제 5.2 시연 화면

구현

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