지난 강좌에 이어.. 계속 삼각형 그리는 코드를 살펴볼 예정이다. 이번 강좌를 통해서는 다음의 내용을 배우게 될 것이다.
* 셰이더(shader) 기본 이해
* 셰이더 프로그램 생성 방법
우선 InitApp 함수의 셰이더 생성 부분을 살펴보자.
// 셰이더 프로그램 오브젝트 GLuint gShaderProgram; bool InitApp() { ... // 셰이더 파일 읽기 std::string vertShaderSource = ReadStringFromFile("BasicShader.glvs"); std::string fragShaderSource = ReadStringFromFile("BasicShader.glfs"); // 셰이더 오브젝트 생성 GLuint vertShaderObj = CreateShader(GL_VERTEX_SHADER, vertShaderSource); GLuint fragShaderObj = CreateShader(GL_FRAGMENT_SHADER, fragShaderSource); // 셰이더 프로그램 오브젝트 생성 gShaderProgram = glCreateProgram(); // 셰이더 프로그램에 버텍스 및 프래그먼트 셰이더 등록 glAttachShader(gShaderProgram, vertShaderObj); glAttachShader(gShaderProgram, fragShaderObj); // 셰이더 프로그램과 셰이더 링킹(일종의 컴파일) 그리고 확인 glLinkProgram(gShaderProgram); if (!CheckProgram(gShaderProgram)) { glDeleteProgram(gShaderProgram); return false; } // 사용된 셰이더 떼어냄 glDetachShader(gShaderProgram, vertShaderObj); glDetachShader(gShaderProgram, fragShaderObj); // 셰이더 삭제 glDeleteShader(vertShaderObj); glDeleteShader(fragShaderObj); ... }
길고 무서운 코드가 등장했다. 바로 코드설명을 들어가기 전에 적을 파악할 필요가 있지않겠는가? 셰이더에 대해 잠간 소개하고 넘어가자.
컴퓨터그래픽스 하드웨어 초기에는 기본적으로 제공되던 기능만으론 표현할 수 없는것들이 너무 많았다. 대표적으로 '그림자(shadow)'가 있는데, 이런 특별한 표현을 위해 모든 기능을 만들어주는 대신 '프로그래밍 가능한 GPU 렌더링 파이프라인'을 만들고, GPU 하드웨어용 프로그램을 만들 수 있는 언어를 제공해줬다. 이렇게 탄생한것이 셰이더 언어(shader language)이며 OpenGL에서는 GLSL(OpenGL Shader Language)라는 이름으로 불리운다.
현대에 이르러서는 초기 제공되던 기능들(고정 파이프라인 이라 불린다)은 더 이상 쓰지 않게 되었고 셰이더 언어를 더욱 강화하는 형태로 발전하였다. 즉, 아무리 간단한 도형이라 해도 GPU가 이를 어떻게 그릴것인지 하나하나 지정해줘야 한다는 뜻이다.
셰이더 프로그램 오브젝트는 GPU 파이프라인에서 수행될 프로그램의 핸들러이고, GPU 파이프라인의 각 단계를 설명하는 셰이더 스테이지로 구성된다. 여기서 셰이더 스테이지는 총 다섯개로 나뉘며 각 단계에서의 역할이 서로 다르고 열거된 순서대로 실행된다.
- vertex shader
- tessellation control shader
- tessellation evaluation shader
- geometry shader
- fragment shader
사실 compute shader라는 단계가 하나 더 있고 opengl 4.3부터 사용 가능한 특수 목적 셰이더이다. 기회가 되면 다뤄보겠지만 아직은 무시하도록 하자. 또한 GPU 파이프라인이라던가 셰이더 각 단계에 대한 자세한 설명은 조금 뒤의 강좌로 미뤄두겠다. 우선은 삼각형이나 빨리 그려보자. 현기증 날 것 같다.
각 단계의 셰이더는 glsl 언어로 작성해야하며 이들을 컴파일하여 프로그램을 만드는것이다. *.glvs, *.glfs 같은 확장자(?) 로 표현했지만 사실 파일명이나 확장자 따윈 아무 상관 없다. 위에서 다섯 단계의 셰이더가 있다고 했지만, 진한 글씨로 표현한 vertex shader와 fragment shader가 꼭 필요한 단계이며 나머지는 옵션이다. 앞으로 강좌가 진행되면서 다뤄보겠지만, 지금은 이 두 셰이더만 붙여볼거다.
그래서 셰이더 프로그램이 만들어지는 순서는 아래와 같다. '컴파일' 이라는 익숙한 단어가 설명하듯 .cpp 파일들이 한데엮여 하나의 프로그램이 만들어지는것과 유사하다. 하나하나 차근차근 설명할테니 두려워 말라.
- 각 단계의 셰이더 소스 준비
- 각 단계의 셰이더 오브젝트 생성 및 컴파일
- (셰이더) 프로그램 생성
- (셰이더) 프로그램에 셰이더 오브젝트 붙이기 및 연결
- 붙인 셰이더 오브젝트 떼어냄
- 다 쓴 셰이더 오브젝트 제거
std::string vertShaderSource = ReadStringFromFile("BasicShader.glvs"); std::string fragShaderSource = ReadStringFromFile("BasicShader.glfs");
첫번째로 셰이더의 소스의 준비는 소스를 따로 파일에 작성하해서 읽어오던, cpp코드에 직접 문자열로 밖던 상관없다. glsl 명세를 따라 제대로 작성된 코드이기만 하면 문제없다.
GLuint vertShaderObj = CreateShader(GL_VERTEX_SHADER, vertShaderSource); GLuint fragShaderObj = CreateShader(GL_FRAGMENT_SHADER, fragShaderSource);
다음으로 준비된 셰이더 소스로 셰이더 오브젝트를 생성하고 컴파일해야 한다. 이러한 작업을 해주는 CreateShader 함수를 들여다보자.
GLuint CreateShader(GLenum shaderType, const std::string& source) { GLuint shader = glCreateShader(shaderType); if (shader == 0) return 0; // set shader source const char* raw_str = source.c_str(); glShaderSource(shader, 1, &raw_str, NULL); // compile shader object glCompileShader(shader); // check compilation error if (!CheckShader(shader)){ glDeleteShader(shader); return 0; } return shader; }
셰이더 오브젝트의 생성은, glCreateShader 함수를 통해 지정된 타입의 셰이더 오브젝트를 만들고(할당받고) glShaderSource 함수 셰이더 소스(코드)를 지정한 후 glCompileShader 함수로 컴파일하면 끝이다.
여기서 타입은 어떤 단계의 셰이더 오브젝트를 만드느냐에 따라 달라지며 GL_VERTEX_SHADER, GL_FRAGMENT_SHADER 등이 있다.
바로 사용하기 전에 컴파일에 실패하는 경우를 위해 제대로 컴파일 되었나 체크 해보는것은 필수 과정이다.
gShaderProgram = glCreateProgram(); glAttachShader(gShaderProgram, vertShaderObj); glAttachShader(gShaderProgram, fragShaderObj);
정점 그리고 프래그먼트 셰이더 오브젝트가 모두 제대로 생성 및 컴파일되었다면 셰이더 프로그램을 만든 후 연결해주자.
glLinkProgram(gShaderProgram); if (!CheckProgram(gShaderProgram)) { glDeleteProgram(gShaderProgram); return false; } glDetachShader(gShaderProgram, vertShaderObj); glDetachShader(gShaderProgram, fragShaderObj); glDeleteShader(vertShaderObj); glDeleteShader(fragShaderObj);
마지막으로 프로그램을 링크하고, 문제가 없는지 체크한 후 붙였던 셰이더 오브젝트들을 깔끔하게 떼어내고 삭제해주는것으로 끝난다.
정작 중요한 셰이더 소스는 설명하지 않았는데, 다음 강좌에서 '정점 버퍼 오브젝트' 라는 것을 먼저 설명한 후 언급하겠다.
강좌의 예제코드는 모두 github에 올려두었다.
'Graphics Note' 카테고리의 다른 글
Ellipse (0) | 2015.07.06 |
---|---|
OpenGL 강좌 - 4. 삼각형 그리기(Vertex Buffer Object) (7) | 2015.05.25 |
OpenGL 강좌 - 2. 삼각형 그리기(설정) (0) | 2015.05.25 |
OpenGL 강좌 - 1. 소개와 준비 (24) | 2015.05.23 |
asymptotic discrete spot noise (ADSN) (0) | 2014.12.11 |