→ z-buffer는 현재 가장 널리 쓰이는 hidden surface removal method로 객체들이 겹치는 부분을 처리하는데 매우 중요하다. z-buffer 방식은 새로운 object를 그릴 때 마다 pixel 마다 depth test를 진행하여 새로운 object가 더 가까우면 새로운 object로 update하고 아니면 현 상태를 유지한다. 장점은 object를 정렬할 필요가 없고 하드웨어 구현으로 매우 빠르다는 것이다. 하지만 framebuffer 크기 정도의 추가 메모리가 필요하다는 단점이 존재한다.
glEnable(GL_DEPTH_TEST);
를 통해 z-buffer를 켜주고
glDepthRange(0.0, 1.0);
glClearDepth(1.0F);
를 통해 z-buffer 값을 설정해주고 (optional)
glClear(GL_DEPTH_BUFFER_BIT);
으로 z-buffer를 clear 해줌으로써 z-buffer를 사용할 수 있다.
double buffering은 2개의 프레임 버퍼를 사용하여 partial update 문제를 해결한다. 화면에 front buffer를 보여주는 동안 OpenGL이 back buffer에 draw하여 두 buffer를 swap한다. 이로 인해 끊임없는 애니메이션이 가능해졌지만 하드웨어 비용이 증가했다.
glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_FALSE);
를 통해 window 생성 시 double buffer를 사용하도록 설정하고
glfwSwapBuffers(win);
을 통해 front buffer와 back buffer를 swap 할 수 있다.
backface culling 기법은 화면에 보이지 않는 backface를 primitive assembly시에 pipeline에서 culling 하는 기법으로, 평균적으로 face 개수의 절반이 culling 되기 때문에 속도가 2배정도 빨라진다. 하지만 투명한 물체이거나 완전히 둘러싸이지 않은 물체의 경우 backface culling을 사용하지 못한다.
glFrontFace();
로 CCW, CW 중 frontface를 define하고
glCullFace();
로 front, back 혹은 front&back 중 어떤 face를 culling 할지 결정한다.
glEnable();
로 face culling feature을 켜고 끌 수 있다.
vertex processing
→ vertex processing은 vertex buffer에 저장된 정점들을 좌표 변환을 통해 적절한 공간으로 이동시킨후 사용하기 위해 각 정점에 대해 여러 변환을 수행하는 단계를 의미한다.
vs는 해당 단계를 GPU에서 병렬 처리되도록 만든 단계를 뜻한다.
in은 attribute register, out은 varying register 그리고 gl_Position에 vertex position을 저장함으로써 사용할 수 있다.
primitive assembly
→ 변환된 정점들을 결합하여 graphics primitive를 생성한다. 이때 primitive는 점, 선분, 삼각형일 수 있다.
fragment processing
→ fragment processing은 각 pixel의 좌표와 color, depth를 받아와 각 fragment에 대해 색상 등을 결정하는 단계이다.
fs는 해당 단계를 GPU에서 병렬처리되도록 만든 단계를 뜻한다.
in은 varying register, out은 framebuffer update를 뜻한다.
우선 v에 삼각형의 세 꼭짓점에 대한 정보를 넘겨준다. vs는 해당 꼭짓점들을 변환하여 gl_Position에 vertex position을 저장해둔다. 그 후 primitive assembly 과정에서 삼각형이 그려지고 rasterization 과정을 통해 삼각형을 pixel로 매핑하여 turned-on pixel들을 결정한다. 그리고 fs는 turned-on된 pixel에 대해 shader program을 실행하여 각 pixel에 대한 색상을 부여한다. 마지막으로 그림이 그려진 back-buffer와 front-buffer가 swap되면 빨간 삼각형이 화면에 그려지게 된다.
vs에 꼭짓점과 각 꼭짓점에 대한 색을 같이 넘겨준다. vs는 해당 정보들을 변환하여 gl_Position에 vPos를 저장하고 out으로 varying에 꼭짓점에 대한 색을 저장한다. 그 후 primitive assembly, rasterization 과정을 거친 후 fs에서 varying에 저장되어 있는 색을 output에 넘겨주어 각 꼭짓점에 맞는 색을 할당한다. 꼭짓점과 꼭짓점 사이에 있는 점들은 interpolation 되어 색이 결정되고 결국 삼각형의 세 꼭짓점의 색이 바뀌게 된다.
1. GLSL 디버그
// compile 상태
glGetShaderiv(vert, GL_COMPILE_STATUS, &status);
// information log
glGetShaderInfoLog(vert, sizeof(buf), NULL, buf);
// program link 상태
glGetProgramiv(prog, GL_LINK_STATUS, &status);
glGetProgramInfoLog(prog, sizeof(buf), NULL, buf);
// program validity check
glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
glGetProgramInfoLog(prog, sizeof(buf), NULL, buf);
2. OpenGL 디버그
GLenum err = glGetError();
glEnable(GL_DEBUG_OUTPUT);
glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); // output 즉각적으로 반환
glDebugMessageControl(GLenum source,
GLenum type,
GLenum severity,
GLsizei count,
const GLuint *ids,
GLboolean enabled);
glDebugMessageCallback(DEBUGPROC callback, const void * userParam);
extension은 기술의 빠른 발달로 개발된 최신 기능을 더 효과적으로 구현할 수 있다. OpenGL extension은 core 기능이 아니기 때문에 반드시 필요한 기능이어야 포함시켜준다. 그렇기 때문에 최신 기능을 더 효과적으로 구현하기 위해 extension은 필요하다. glew 라이브러리를 통해 어떤 extension이 사용가능한지 체크 가능하다. glewInit()을 실행하고 glewIsSupported() 메서드를 통해 확인이 가능하다.
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glew32.lib")
#pragma comment(lib, "glfw3.lib")
#pragma warning(disable: 4711 4710 4100)
#include <stdio.h>
#include <string.h> // for strrchr()
#include "./common.c"
const unsigned int WIN_W = 300; // window size in pixels, (Width, Height)
const unsigned int WIN_H = 300;
const unsigned int WIN_X = 100; // window position in pixels, (X, Y)
const unsigned int WIN_Y = 100;
// vertex shader
const char* vertSource =
"#version 330 core \n\
in vec4 vertexPos; \n\
void main(void) { \n\
gl_Position = vertexPos; \n\
}";
// fragment shader
const char* fragSource =
"#version 330 core \n\
out vec4 FragColor; \n\
void main(void) { \n\
FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n\
}";
GLuint vert = 0; // vertex shader ID number
GLuint frag = 0; // fragment shader ID number
GLuint prog = 0; // shader program ID number
void initFunc(void) {
// vert: vertex shader
vert = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vert, 1, &vertSource, NULL);
glCompileShader(vert); // compile to get .OBJ
// frag: fragment shader
frag = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(frag, 1, &fragSource, NULL);
glCompileShader(frag); // compile to get .OBJ
// prog: program
prog = glCreateProgram();
glAttachShader(prog, vert);
glAttachShader(prog, frag);
glLinkProgram(prog); // link to get .EXE
// execute it!
glUseProgram(prog);
}
GLfloat vertPos[] = {
-0.5F, -0.5F, 0.0F, 1.0F,
+0.5F, -0.5F, 0.0F, 1.0F,
-0.5F, +0.5F, 0.0F, 1.0F,
};
void drawFunc(void) {
// clear in gray color
glClear(GL_COLOR_BUFFER_BIT);
// provide the vertex attributes
GLuint loc = glGetAttribLocation(prog, "vertexPos");
glEnableVertexAttribArray(loc);
glVertexAttribPointer(loc, 4, GL_FLOAT, GL_FALSE, 0, vertPos);
// draw a triangle
glDrawArrays(GL_TRIANGLES, 0, 3);
// done
glFinish();
}
void refreshFunc(GLFWwindow* window) {
// refresh
drawFunc();
// GLFW action
glfwSwapBuffers(window);
}
void keyFunc(GLFWwindow* window, int key, int scancode, int action, int mods) {
switch (key) {
case GLFW_KEY_ESCAPE:
if (action == GLFW_PRESS) {
glfwSetWindowShouldClose(window, GL_TRUE);
}
break;
}
}
int main(int argc, char* argv[]) {
const char* basename = getBaseName( argv[0] );
// start GLFW & GLEW
glfwInit();
GLFWwindow* window = glfwCreateWindow(WIN_W, WIN_H, basename, NULL, NULL);
glfwSetWindowPos(window, WIN_X, WIN_Y);
glfwMakeContextCurrent(window);
glewInit();
// prepare
glfwSetWindowRefreshCallback(window, refreshFunc);
glfwSetKeyCallback(window, keyFunc);
glClearColor(0.5F, 0.5F, 0.5F, 1.0F);
// main loop
initFunc();
while (! glfwWindowShouldClose(window)) {
// draw
drawFunc();
// GLFW actions
glfwSwapBuffers(window);
glfwPollEvents();
}
// done
glfwTerminate();
return 0;
}