반응형

이번 포스팅에서는 OpenGL에서 사각형을 그리는 방법을 설명합니다.  

지난번 삼각형 그렸던 코드를 수정하여 진행합니다.


Modern OpenGL 강좌 - 삼각형 그리기( 렌더링, Vertex Array Object, Vertex Buffer Object)

http://webnautes.tistory.com/1153




OpenGL 2.x와 달리 사각형을 그리기 위해서 두 개의 삼각형을 이용해야 합니다.


기존에 삼각형을 그리기 위해 사용했던 방법인 Vertex Buffer Object(VBO)를 사용하면 삼각형 하나당 3개의 vertex를 정의해줘야 하므로 총 6개의 vertex가 필요합니다.


다음처럼 position, color 배열에 각각 6개의 vertex에 대한 값이 저장되도록 수정합니다.(defineVertexArrayObject)


//#1
//사각형을 구성하는 vertex 데이터 - position과 color
float position[] = {
-0.5f,  0.5f, 0.0f, //vertex 1 : Top-left
0.5f, 0.5f, 0.0f, //vertex 2 : Top-right
0.5f, -0.5f, 0.0f, //vertex 3 : Bottom-right
0.5f, -0.5f, 0.0f, //vertex 4 : Bottom-right
-0.5f, -0.5f, 0.0f, //vertex 5 : Bottom-left
-0.5f,  0.5f, 0.0f //vertex 6 : Top-left
};

float color[] = {
1.0f, 0.0f, 0.0f, //vertex 1 : RED (1,0,0)
0.0f, 1.0f, 0.0f, //vertex 2 : GREEN (0,1,0)
0.0f, 0.0f, 1.0f,  //vertex 3 : BLUE (0,0,1)
0.0f, 0.0f, 1.0f,  //vertex 4 : BLUE (0,0,1)
1.0f, 1.0f, 1.0f,  //vertex 5 : WHITE (1,1,1)
1.0f, 0.0f, 0.0f //vertex 6 : RED (1,0,0)
};




두 개의 삼각형을 그리기위해 glDrawArrays 함수에서 6개의 vertex를 그리도록 수정합니다.


while (!glfwWindowShouldClose(window)) {  

      . . . . . . . . . . . . . . . . .


glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);


//glDrawArrays(GL_TRIANGLES, 0, 3);
glDrawArrays(GL_TRIANGLES, 0, 6);

count++;

glfwSwapBuffers(window);
glfwPollEvents();

}




실행한 결과입니다.





glDrawArrays 함수 호출 전에 glPolygonMode 함수를 다음처럼 호출해주면 2개의 삼각형으로 사각형이 구성되었음을 확인할 수 있습니다.


glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); //wireframe polygon 모드

//glDrawArrays(GL_TRIANGLES, 0, 3);
glDrawArrays(GL_TRIANGLES, 0, 6);





간단하게 사각형을 그릴 수 있었습니다. 하지만 이 방법에는 문제가 있습니다.

두 개의 삼각형이 하나의 선분(edge)를 공유하고 있는데도 vertex를 중복해서 정의한다는 겁니다.

아래 그림에서 처럼 vertex1, vertex6과 vertex3, vertex4가 중복된 vertex입니다.




복잡한 3D 모델을  화면에 그리는 경우(렌더링)  중복된 vertex가 많다면 메모리 낭비나 성능 저하등의 문제가 생길 수 있습니다.




Element Buffer Object(EBO)

위에서 언급된 중복된 vertex문제를 해결하기 위해 EBO를 사용합니다.

vertex에 인덱스를 부여하여 렌더링시 재사용할 수 있게 해줍니다.



앞에서 발생했던 중복 vertex를 제거하고  4개의 vertex에 대한 vertex 데이터만 배열에 저장합니다.  


추가로 vertex 데이터에 대한 인덱스를 저장하는 elements 배열을 선언하고, 6개의 인덱스를 정의합니다.  두 개의 삼각형을 그릴 때, vertices 배열에서 몇번째 vertex를 이용할지를 정수로 적어주는 것입니다.


//#1
//사각형을 구성하는 vertex 데이터 - position과 color
float position[] = {
-0.5f,  0.5f, 0.0f, //vertex 1 : Top-left
0.5f, 0.5f, 0.0f, //vertex 2 : Top-right
0.5f, -0.5f, 0.0f, //vertex 3 : Bottom-right
-0.5f, -0.5f, 0.0f //vertex 4 : Bottom-left
};

float color[] = {
1.0f, 0.0f, 0.0f, //vertex 1 : RED (1,0,0)
0.0f, 1.0f, 0.0f, //vertex 2 : GREEN (0,1,0)
0.0f, 0.0f, 1.0f,  //vertex 3 : BLUE (0,0,1)
1.0f, 1.0f, 1.0f  //vertex 4 : WHITE (1,1,1)
};

//vertex 데이터에 대한 인덱스
GLuint elements[] = {
0, 1, 2,
2, 3, 0




인덱스는 배열에 입력된 순서대로 정해집니다.


float position[] = {
-0.5f,  0.5f, 0.0f, //vertex 1 : Top-left     -- 인덱스 0
0.5f, 0.5f, 0.0f, //vertex 2 : Top-right      -- 인덱스 1
0.5f, -0.5f, 0.0f, //vertex 3 : Bottom-right  -- 인덱스 2
-0.5f, -0.5f, 0.0f //vertex 4 : Bottom-left   -- 인덱스 3
};




인덱스를 사용하게되면 vertex를 재사용할 수 있습니다.  

첫번째 삼각형은 인덱스 0,1,2에 대응되는 position을 사용하고 두번째 삼각형은 인덱스 2,3,0에 대응되는  position을 사용합니다.


GLuint elements[] = {
0, 1, 2,
2, 3, 0
};





Element Buffer Object(EBO)를 생성하여 vertex 데이터에 대한 인덱스인 elements 배열을 복사해줍니다.

주의할 점은 전 포스팅에서 설명한  Vertex Array Object(VAO)에 Element Buffer Object(EBO) 정보도 저장됩니다.

따라서 VAO를 생성 및 바인딩한 다음에 복사해주는 코드가 추가되어야 합니다.


//#6
glGenVertexArrays(1, &triangleVertexArrayObject);
glBindVertexArray(triangleVertexArrayObject);

//Element Buffer Object(EBO)를 생성하여 vertex 데이터에 대한 인덱스를 저장한다.
GLuint triangleElementBufferObject;
glGenBuffers(1, &triangleElementBufferObject);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangleElementBufferObject);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW);




인덱스를 사용하여 삼각형을 그리기위해서 glDrawArrays 함수 대신에  glDrawElements 함수를 사용합니다.


while (!glfwWindowShouldClose(window)) {  

    . . . . . . . . . . . . . . . . . .


glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);


//glDrawArrays(GL_TRIANGLES, 0, 6);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

   . . . . . . . . . . . . . . . . . .

}




실행결과는 전과 동일합니다.





전체 소코드입니다.


#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>

using namespace std;


int framebufferWidth, framebufferHeight;
GLuint triangleVertexArrayObject;
GLuint triangleShaderProgramID;
GLuint trianglePositionVertexBufferObjectID, triangleColorVertexBufferObjectID;


bool initShaderProgram() {

//#3
const GLchar* vertexShaderSource =
"#version 330 core\n"
"in vec3 positionAttribute;"
"in vec3 colorAttribute;"
"out vec3 passColorAttribute;"
"void main()"
"{"
"gl_Position = vec4(positionAttribute, 1.0);"
"passColorAttribute = colorAttribute;"
"}";


//#4
const GLchar* fragmentShaderSource =
"#version 330 core\n"
"in vec3 passColorAttribute;"
"out vec4 fragmentColor;"
"void main()"
"{"
"fragmentColor = vec4(passColorAttribute, 1.0);"
"}";



GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);

GLint result;
GLchar errorLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &result);
if (!result)
{
glGetShaderInfoLog(vertexShader, 512, NULL, errorLog);
cerr << "ERROR: vertex shader 컴파일 실패\n" << errorLog << endl;
glDeleteShader(vertexShader);
return false;
}


GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &result);
if (!result)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, errorLog);
cerr << "ERROR: fragment shader 컴파일 실패\n" << errorLog << endl;

return false;
}




//#5
triangleShaderProgramID = glCreateProgram();

glAttachShader(triangleShaderProgramID, vertexShader);
glAttachShader(triangleShaderProgramID, fragmentShader);

glLinkProgram(triangleShaderProgramID);


glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);


glGetProgramiv(triangleShaderProgramID, GL_LINK_STATUS, &result);
if (!result) {
glGetProgramInfoLog(triangleShaderProgramID, 512, NULL, errorLog);
cerr << "ERROR: shader program 연결 실패\n" << errorLog << endl;
return false;
}

return true;
}



bool defineVertexArrayObject() {

//#1
//삼각형을 구성하는 vertex 데이터 - position과 color
/*
float position[] = {
0.0f,  0.5f, 0.0f, //vertex 1  위 중앙
0.5f, -0.5f, 0.0f, //vertex 2  오른쪽 아래
-0.5f, -0.5f, 0.0f //vertex 3  왼쪽 아래
};

float color[] = {
1.0f, 0.0f, 0.0f, //vertex 1 : RED (1,0,0)
0.0f, 1.0f, 0.0f, //vertex 2 : GREEN (0,1,0)
0.0f, 0.0f, 1.0f  //vertex 3 : BLUE (0,0,1)
};
*/
//사각형을 구성하는 vertex 데이터 - position과 color
float position[] = {
-0.5f,  0.5f, 0.0f, //vertex 1 : Top-left
0.5f, 0.5f, 0.0f, //vertex 2 : Top-right
0.5f, -0.5f, 0.0f, //vertex 3 : Bottom-right
-0.5f, -0.5f, 0.0f //vertex 4 : Bottom-left
};

float color[] = {
1.0f, 0.0f, 0.0f, //vertex 1 : RED (1,0,0)
0.0f, 1.0f, 0.0f, //vertex 2 : GREEN (0,1,0)
0.0f, 0.0f, 1.0f,  //vertex 3 : BLUE (0,0,1)
1.0f, 1.0f, 1.0f  //vertex 4 : WHITE (1,1,1)
};

//vertex 데이터에 대한 인덱스
GLuint elements[] = {
0, 1, 2,
2, 3, 0
};


//#2
//Vertex Buffer Object(VBO)를 생성하여 vertex 데이터를 복사한다.
glGenBuffers(1, &trianglePositionVertexBufferObjectID);
glBindBuffer(GL_ARRAY_BUFFER, trianglePositionVertexBufferObjectID);
glBufferData(GL_ARRAY_BUFFER, sizeof(position), position, GL_STATIC_DRAW);

glGenBuffers(1, &triangleColorVertexBufferObjectID);
glBindBuffer(GL_ARRAY_BUFFER, triangleColorVertexBufferObjectID);
glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);



//#6
glGenVertexArrays(1, &triangleVertexArrayObject);
glBindVertexArray(triangleVertexArrayObject);

//Element Buffer Object(EBO)를 생성하여 vertex 데이터에 대한 인덱스를 저장한다.
GLuint triangleElementBufferObject;
glGenBuffers(1, &triangleElementBufferObject);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, triangleElementBufferObject);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(elements), elements, GL_STATIC_DRAW);


GLint positionAttribute = glGetAttribLocation(triangleShaderProgramID, "positionAttribute");
if (positionAttribute == -1) {
cerr << "position 속성 설정 실패" << endl;
return false;
}
glBindBuffer(GL_ARRAY_BUFFER, trianglePositionVertexBufferObjectID);
glVertexAttribPointer(positionAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);

glEnableVertexAttribArray(positionAttribute);

GLint colorAttribute = glGetAttribLocation(triangleShaderProgramID, "colorAttribute");
if (colorAttribute == -1) {
cerr << "color 속성 설정 실패" << endl;
return false;
}
glBindBuffer(GL_ARRAY_BUFFER, triangleColorVertexBufferObjectID);
glVertexAttribPointer(colorAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(colorAttribute);


glBindVertexArray(0);


return true;
}




void framebufferSizeCallback(GLFWwindow* window, int width, int height)
{
//처음 2개의 파라미터는 viewport rectangle의 왼쪽 아래 좌표
//다음 2개의 파라미터는 viewport의 너비와 높이이다.
//framebuffer의 width와 height를 가져와 glViewport에서 사용한다.
glViewport(0, 0, width, height);

framebufferWidth = width;
framebufferHeight = height;
}



void errorCallback(int errorCode, const char* errorDescription)
{
cerr << "Error: " << errorDescription << endl;
}



void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GLFW_TRUE);
}




int main()
{

glfwSetErrorCallback(errorCallback);   


if (!glfwInit()) {

cerr << "Error: GLFW 초기화 실패" << endl;
std::exit(EXIT_FAILURE);
}



glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
//glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
glfwWindowHint(GLFW_SAMPLES, 4);


GLFWwindow* window = glfwCreateWindow(
800,
600,
"OpenGL Example",
NULL, NULL);
if (!window) {

glfwTerminate();
std::exit(EXIT_FAILURE);
}


glfwMakeContextCurrent(window);


glfwSetKeyCallback(window, keyCallback);
glfwSetFramebufferSizeCallback(window, framebufferSizeCallback);



glewExperimental = GL_TRUE;
GLenum errorCode = glewInit();  
if (GLEW_OK != errorCode) {

cerr << "Error: GLEW 초기화 실패 - " << glewGetErrorString(errorCode) << endl;

glfwTerminate();
std::exit(EXIT_FAILURE);
}



if (!GLEW_VERSION_3_3) {

cerr << "Error: OpenGL 3.3 API is not available." << endl;

glfwTerminate();
std::exit(EXIT_FAILURE);
}


cout << "OpenGL version: " << glGetString(GL_VERSION) << endl;
cout << "GLSL version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << endl;
cout << "Vendor: " << glGetString(GL_VENDOR) << endl;
cout << "Renderer: " << glGetString(GL_RENDERER) << endl;




if (!initShaderProgram()) {

cerr << "Error: Shader Program 생성 실패" << endl;

glfwTerminate();
std::exit(EXIT_FAILURE);
}



if (!defineVertexArrayObject()) {

cerr << "Error: Shader Program 생성 실패" << endl;

glfwTerminate();
std::exit(EXIT_FAILURE);
}





glfwSwapInterval(1);



double lastTime = glfwGetTime();
int numOfFrames = 0;
int count = 0;




glUseProgram(triangleShaderProgramID);
glBindVertexArray(triangleVertexArrayObject);


while (!glfwWindowShouldClose(window)) {  


double currentTime = glfwGetTime();
numOfFrames++;
if (currentTime - lastTime >= 1.0) {

printf("%f ms/frame  %d fps \n", 1000.0 / double(numOfFrames), numOfFrames);
numOfFrames = 0;
lastTime = currentTime;
}



glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);


//glDrawArrays(GL_TRIANGLES, 0, 6);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

count++;

glfwSwapBuffers(window);
glfwPollEvents();

}


glUseProgram(0);
glBindVertexArray(0);


glDeleteProgram(triangleShaderProgramID);
glDeleteBuffers(1, &trianglePositionVertexBufferObjectID);
glDeleteBuffers(1, &triangleColorVertexBufferObjectID);
glDeleteVertexArrays(1, &triangleVertexArrayObject);
glfwTerminate();  

std::exit(EXIT_SUCCESS);
}


반응형

포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
댓글로 알려주시면 빠른 시일내에 답변을 드리겠습니다.

여러분의 응원으로 좋은 컨텐츠가 만들어집니다.
지금 본 내용이 도움이 되었다면 유튜브 구독 부탁드립니다. 감사합니다 : )

유튜브 구독하기


제가 쓴 책도 한번 검토해보세요.

  1. 2017.08.01 10:17

    비밀댓글입니다

  2. 공대생 2019.09.16 21:27

    사랑합니다 ㅠㅠ

    정말 큰 도움이 되었습니다.

  3. 안성민 2020.03.31 13:41

    잘봤습니다~ 유튜브 구독도 하고 갑니다!

+ Recent posts