본문 바로가기

공부/Open GL ES를 이용한 3차원 컴퓨터 그래픽스 입문

[OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문] 챕터 6 - OpenGL ES와 쉐이더

728x90
반응형

 

 

 

 

앞선 내용 요약

  • GPU 파이프 라인에 입력되는 정점 배열은 정점 위치와 노멀 등의 데이터를 저장하는데, 정점 쉐이더는 한 번에 한 정점을 처리한다.
  • GPU는 수많은 코어(core)로 구성된 병렬 처리 구조를 가지고 있다. (다수의 정점을 동시에 처리하는데 적합하다.)

 

 

 


 

 

 

6.1 OpenGL ES와 쉐이딩 언어

  • OpenmGL ES는 정점 쉐이더(vertex shader)프래그먼트 쉐이더라는 두 가지 프로그램을 필요로 한다.
  • OpenGL ES의 쉐이더는 GPU에 특화된 언어로 작성하는데, 이를 OpenGL ES Shading Language라고 한다.

 

 

 

 

 

 

6.2 정점 쉐이더

  • GLSL은 C와 비슷한 언어이다.
  • 하지만, GPU에서 작동하는 GLSL은 CPU에서 작동하는 C와는 분명하게 구분되는 자신만의 특징을 가진다.
  • GLSL은 float와 같은 기본 타입에 더불어 최대 4개 원소를 가지는 벡터 타입을 제공한다.
  • 예를 들어, mat3와 mat4는 각각 3X3과 4X4 크기의 정사각행렬을, mat3X4는 3X4 크기의 행렬을 의미한다.

 

 

 

정점 쉐이더의 입력과 출력

  • 정점 쉐이더는 두 가지 종류의 입력을 받아들인다. 하나는 정점 배열에 저장된 정점별 애트리뷰트(attribute)들이고, 각 정점들은 위치(position), 노멀(normal), 텍스처 좌표(texCoord) 애트리뷰트를 가지고 있다. (텟트처 좌표에 관해서는 8장에서 자세히 기술)

 

 

 

정점 배열 1000개의 정점을 저장하고 있다면, 정점 쉐이더는 1000번 실행되는데, 1000번 실행되는 정점 쉐이더가 모두 공유하는 또 다른 종류의 입력 데이터가 있다.
  • 대표적인 예가 월드/뷰/투영 변환인데, 이러한 입력 데이터들을 유니폼(uniform)이라 부른다.

 

gl_Position

  • 정점은 결국 clip space로 들어가게 되며, vertex shader가 출력으로 반드시 해줘야 한다. (의무)
  • gl_Positionclip space position이고, 내장변수(built in variable)를 통해 나타내고 있다.

 

 

[예제 코드]

#version 300 es

uniform mat4 worldMat, viewMat, projMat;    // uniform (입력)

layout(location = 0) in vec3 position;    // attribute (입력)
layout(location = 1) in vec3 normal;      // layout은 위치를 표시하는 역할을 한다. (없어도 됨)
layout(location = 2) in vec2 texCoord;

out vec3 v_normal;    // 출력
out vec2 v_texCoord;

void main()
{
		gl_Position = projMat * viewMat * worldMat * vec4(position, 1.0);   // clip space가 나올 것. 맨 끝에 1.0을 해준 이유는 4X4 행렬로 맞춰주기 위해서
		v_normal = normalize(transpose(inverse(mat3(worldMat))) * normal);  // world space에 정의될 것
		v_texCoord = texCoord;
}
  • 메인 문을 보면 우선 오브젝트 공간에서 정의된 position을 클립 공간으로 변환한다. (모든 정점 쉐이더가 반드시 수행해야 하는 것)
  • 그 결과는 gl_Position이라는 내장 변수 (built in variable)에 저장해야 한다.
  • v_normal에서의 worldMat은 월드 행렬의 왼쪽 위 3X3 부분 행렬을 말한다. 이는 [L | t]로 표기된 월드 행렬 중 L을 의미한다.
  • L의 역전치 행렬이 normal에 곱해지고 정규화되어 출력 변수인 v_normal에 저장된다.
  • 함수의 마지막 줄은 texCoord를 v_texCoord에 그대로 복사하는데, 총 세 개의 출력 값(v_normal, v_texCoord, gl_Position)이 래스터라이저에게 전달될 것이다.

 

 

 


 

 

 

6.3 쉐이더를 위한 OpenGL ES 작업

  • GL 프로그램의 함수는 gl로, 데이터는 GL로 시작한다.

 

 

 

[예제 코드]

GLuint shader = glCreateShader(GL_VERTEX_SHADER);    // 쉐이더 오브젝트 생성
glShaderSource(shader, 1, &source, NULL);            // 만들어진 쉐이더 오브젝트를 저장
glCompileShader(shader);    // 쉐이더 오브젝트는 컴파일된다.
  • 쉐이더 오브젝트(shader object)는 glCreateShader가 생성
  • glShaderSource에 의해 쉐이더 오브젝트가 저장된다.
  • 쉐이더 오브젝트는 glCompileShader에 의해 컴파일된다.

 

 

 

[예제 코드]

GLuint program = glCreateProgram();    // program Object 생성
glAttachShader(program, shader);    // 아까 만든 shader를 붙이기
glLinkProgram(program);    // 링크
glUseProgram(program);    // 렌더링에 사용하기 위해서 호출 !

아까 만든 shader들은 program object에 붙여야 한다.

  • 정점 및 프래그먼트 쉐이더 오브젝트는 모두 프로그램 오브젝트(program object)에 붙여져야 한다.

 

 

 


 

 

 

6.4 애트리뷰터와 유니폼

  • GL 프로그램의 중요한 역할 중 하나는 정점 쉐이더가 애트리뷰트와 유니폼을 사용할 수 있도록 하는 것이다.
  • 이들을 정점 쉐이더에게 건네줘야 하고, 또한 이들이 어떤 구조를 가지는지 정점 쉐이더에게 알려줘야 한다.

 

 

 

[예제 코드]

struct Vertex
{
   glm::vec3 pos;    // position
	 glm::vec3 nor;    // normal
	 glm::vec2 tex;    // texture coordinates
};
typedef GLshort Index;

struct ObjData
{
	 std::vector<Vertex> vertices;
	 std::vector<Index> indices;
};

ObjData objData;
  • 여기까지 하면 메모리에 vertex array와 index array는 Load되어 있다.

 

CPU 메모리의 정점 및 인덱스 배열과 GPU 메모리의 버퍼 오브젝트

  • 현재 CPU 메모리에 저장되어 있는 정점 및 인덱스 배열은 렌더링을 위해서 GPU 메모리의 버퍼 오브젝트(buffer object)로 옮겨진다. (실제 렌더링은 GPU에서 하니까, GPU메모리에서 buffer object를 만든다 !)
  • 정점 배열은 배열 버퍼 오브젝트(array buffer obect)로, 인덱스 배열은 요소 배열 버퍼 오브젝트(element array buffer object)로 복사된다.

 

 

 

[예제 코드]

GLuint abo;
glGenBuffers(1, &abo);
glBindBuffer(GL_ARRAY_BUFFER, abo);
glBufferData(GL_ARRAY_BUFFER, (GLsizedi) objData.vertices.size() * sizeof(Vertex),
					objData.vertices.data(), GL_STATIC_DRAW);
  • 실제 옮기는 과정을 설명하는 부분
  • glGenBuffers를 통해 n개의 버퍼 오브젝트를 buffers에 저장한다.
  • 이 버퍼 오브젝트를 정점 배열에 바인드(bind)하기 위하여 GL_ARRAY_BUFFER를 파라미터로 glBindBuffer를 호출한다.
  • objData.vertices로 버퍼 오브젝트를 채우기 위하여 glBufferData를 호출한다.
    → GPU메모리에 vertex array가 복사가 된 것이다.

 

 

 

[예제 코드]

glEnableVertexAttribArray(0);    // position = attribute 0
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
					sizeof(Vertex), (const GLvoid*) offsetof(Vertex, pos));

glEnableVertexAttribArray(1);    // normal = attribute 1
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE,
					sizeof(Vertex), (const GLvoid*) offsetof(Vertex, nor));

glEnableVertexAttribArray(2);    // texture coordinates = attribute 2
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE,
					sizeof(Vertex), (const GLvoid*) offsetof(Vertex, tex));

 

GPU 버퍼 오브젝트에 저장된 정점 애트리뷰트

 

  • 인덱스 배열도 마찬가지로 처리된다.
  • 위 그림은 pos, nor, tex라는 정점 애트리뷰트들이 배열 버퍼 오브젝트에 어떤 방식으로 저장되어 있는지 보여준다.
  • GL 프로그램은 정점 쉐이더에게 다음과 같은 구조와 스트라이드를 알려줘야 한다. (어디서부터 어디까지 가지고 와야하는지)
  • glEnableVertexAttribArray를 이용하여 n번에 놓인 애트리뷰트를 활성화시킨다.
  • glVertexAttribPointer를 통해 해당 애트리뷰트에 대한 자세한 정보를 제공한다.
    → 이 과정에서 얼만큼 들고와야하는지에 대해 Vertex의 크기와 처음 위치를 매개변수로 전달해준다.

 

 

 

유니폼

[예제 코드]

glmLLmat4 worldMatrix;    // 매 프레임 갱신되어야하는 월드행렬

GLint loc = glGetUniformLocation(program, "worldMat");    // program object에서 "worldMat"의 위치를 찾기 위해
glUniformMatrix4fv(loc, 1, GL_FALSE, glm::value _ptr(worldMatrix);
  • 정점 쉐이더는 worldMat, viewMat, projMat 세 개의 유니폼을 사용하는데, 이 역시 GL프로그램이 제공해야 한다.
  • 가상 공간에서 물체가 움직이는 경우 월드 행렬은 매 프레임 갱신되어야 하는데, 이 행렬을 worldMatrix라고 할 때, worldMatrix를 정점 쉐이더 유니폼인 worldMat에 할당해야 하는데, 이를 위해서 GL 프로그램은 우선 worldMat의 위치를 알아내야 한다.
  • 프로그램 오브젝트(program)를 링크했을 때 worldMat의 위치가 결정되었는데, 이 위치를 알아내기 위해 GL프로그램은 glGetUniformLocation을 호출한다.
  • 정점 쉐이더의 유니폼 변수에 특정 값을 할당하기 위하여 4X4 행렬인 worldMat를 위해 glUniformMatrix4fv를 호출한다.

 

 

 


 

 

 

6.5 드로우콜

  • 다양한 함수를 이용해 정점 쉐이더가 필요로 하는 애트리뷰트와 유니폼을 GL 프로그램이 모두 제공했다면, 이제 해당 폴리곤 메시를 그리는 명령을 내려야 한다. 그걸 드로우콜(drawcall)이라고 한다.
  • 이를 그리기 위해서는 glDrawElements(GL_TRIANGLES, 정점의 개수, GL_USIGNED_SHORT, 0)을 호출한다. (인덱스 배열일 경우)
  • 인덱스 없이 표현되었다면, 즉 정점 배열로만 정의되었다면, glDrawArray(GL_TRIANGLES, 0, 정점의 개수)를 호출한다. (정점 배열일 경우)

 

 

 

 


 

 

 

 

출처

[OpenGL ES를 이용한 3차원 컴퓨터 그래픽스 입문]을 보고 공부하고 정리한 내용입니다.

728x90