티스토리 뷰
[그래픽스] Hello Triangle - OpenGL에서 삼각형 띄워보기
0. 개요
opengl(GLEW/GLFW)을 이용하여 간단하게 빨간 삼각형을 띄워본다.
이 글에서 커버하는 지식 범위는 다음과 같다.
- 무식한 방식으로의 vertex/fragment shader 생성 및 컴파일
- VBO/VAO 다루기
완성된 코드를 바로 보고 싶다면 아래의 repo를 바로 참고해도 된다.
1. 삼각형을 띄우기 위한 work-flow 개략
다음과 같은 work-flow로 삼각형을 띄워보자.
- GLFW 초기화 및 셋업 / GLEW 초기화 및 셋업 / 창 관련 설정
- vertex 데이터로 삼각형 생성 후 context에 binding.
- 작성한 vertex/fragment 셰이더 추가 및 컴파일
- 렌더링 메인 루프 실행
2. 코드 작성
우선 전체 코드는 다음과 같다.(tab이 뭔가 이상하게 적용되었을 수 있다. 복붙이 필요하면 repo 확인을 추천)
#include "FirstTriangleRunner.h"
#include <iostream>
#include <string.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
const GLint WIDTH = 800, HEIGHT = 600;
GLuint VAO, VBO, shaderProgram;
// Create Vertex Shader in GLSL.
static const char* vShader =
"#version 330\n"
"layout (location = 0) in vec3 pos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(pos.x, pos.y, pos.z, 1.0);\n"
"}\n";
// Create Fragment Shader in GLSL.
static const char* fShader =
"#version 330\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
" color = vec4(1.0, 0.0, 0.0, 1.0);\n"
"}\n";
// Add Shader to Program.
// Shader에 대해 1. source 지정, 2. compile, 3. targetProgram에 attach.
void FirstTriangleRunner::AddShader(GLuint targetProgram, const char* rawShaderCode, GLenum shaderType)
{
GLuint targetShader = glCreateShader(shaderType);
const GLchar* code[1];
code[0] = rawShaderCode;
GLint codeLength[1];
codeLength[0] = strlen(rawShaderCode);
glShaderSource(targetShader, 1, code, codeLength);
glCompileShader(targetShader);
GLint result = 0;
GLchar eLog[1024] = { 0 };
glGetShaderiv(targetShader, GL_COMPILE_STATUS, &result);
if (!result)
{
glGetShaderInfoLog(targetShader, sizeof(eLog), NULL, eLog);
printf("Error compiling the %d shader: '%s'\n", shaderType, eLog);
return;
}
glAttachShader(targetProgram, targetShader);
return;
}
void FirstTriangleRunner::CompileShaders()
{
shaderProgram = glCreateProgram();
if (!shaderProgram)
{
printf("Error creating shader program!\n");
return;
}
AddShader(shaderProgram, vShader, GL_VERTEX_SHADER);
AddShader(shaderProgram, fShader, GL_FRAGMENT_SHADER);
GLint result = 0;
GLchar eLog[1024] = { 0 };
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &result);
// 제대로 링크되지 않았다면.
if (!result)
{
glGetProgramInfoLog(shaderProgram, sizeof(eLog), NULL, eLog);
printf("Error linking program: '%s'\n", eLog);
return;
}
// Validate program.
glValidateProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_VALIDATE_STATUS, &result);
if (!result)
{
glGetProgramInfoLog(shaderProgram, sizeof(eLog), NULL, eLog);
printf("Error validating program: '%s'\n", eLog);
return;
}
}
void FirstTriangleRunner::CreateTriangle()
{
// NDC 좌표계에서 삼각형을 구성하는 vertex들의 위치.
GLfloat vertices[] = {
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
0.0f, 1.0f, 0.0f
};
// VAO 생성 및 Bind - 이 밑에 생성되는 VBO들을 VAO에 자동으로 Bind.
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
// VBO 생성 및 Bind.
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// Attribute 선언 및 활성화
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
// VBO binding 다시 정리해주기
glBindBuffer(GL_ARRAY_BUFFER, 0);
// VAO binding 다시 정리해주기
glBindVertexArray(0);
}
// return NULL if failed.
GLFWwindow* FirstTriangleRunner::ReadyGlfwGlewEnv()
{
// init GLFW.
if(!glfwInit())
{
printf("GLFW init fail");
glfwTerminate();
return NULL;
}
// Setup GLFW window properties.
// OpenGL Version.
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
// Core profile = No Backwards Compatible.
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// Allow forward compatible.
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
GLFWwindow* mainWindow = glfwCreateWindow(WIDTH, HEIGHT, "Hello Window", NULL, NULL);
if (!mainWindow)
{
printf("glfwCreateWindow failed!");
glfwTerminate();
return NULL;
}
// Get Buffer Size Info.
int bufferWidth, bufferHeight;
glfwGetFramebufferSize(mainWindow, &bufferWidth, &bufferHeight);
// Set Context for GLEW to use.
glfwMakeContextCurrent(mainWindow);
// Allow modern extension feats.
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK)
{
printf("glew init failed");
glfwDestroyWindow(mainWindow); // 이 시점에서 window가 생성되었으니 지워야 한다.
glfwTerminate();
return NULL;
}
// Setup Viewport size.
glViewport(0, 0, bufferWidth, bufferHeight);
return mainWindow;
}
bool FirstTriangleRunner::Run()
{
GLFWwindow* mainWindow = ReadyGlfwGlewEnv();
if (!mainWindow)
{
return false;
}
CreateTriangle();
CompileShaders();
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
// loop til window closed.
while (!glfwWindowShouldClose(mainWindow))
{
// Get + handle user input events.
glfwPollEvents();
// Clear window.
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(mainWindow);
}
glBindVertexArray(0);
glUseProgram(0);
return true;
}
위의 GLEW/GLFW 준비를 위한 코드는 기존에 설명한 바 있으니 삼각형을 만드는 데 관련있는 로직에 대한 설명만 작성한다.
차근차근 가보자.
2.1. Vertex Data로 삼각형 생성 후 Context에 binding하기
삼각형을 생성하는 CreateTriangle
함수의 전체는 다음과 같다.
void FirstTriangleRunner::CreateTriangle()
{
// NDC 좌표계에서 삼각형을 구성하는 vertex들의 위치.
GLfloat vertices[] = {
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
0.0f, 1.0f, 0.0f
};
// VAO 생성 및 Bind - 이 밑에 생성되는 VBO들을 VAO에 자동으로 Bind.
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
// VBO 생성 및 Bind.
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// Attribute 선언 및 활성화
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
// VBO unbind
glBindBuffer(GL_ARRAY_BUFFER, 0);
// VAO unbind
glBindVertexArray(0);
}
한 부분씩 설명하겠다.
vertex input 준비
// NDC 좌표계에서 삼각형을 구성하는 vertex들의 위치.
GLfloat vertices[] = {
-1.0f, -1.0f, 0.0f,
1.0f, -1.0f, 0.0f,
0.0f, 1.0f, 0.0f
};
삼각형을 구성할 세 vertex의 좌표를 NDC 기준으로 선언한다.
화면에 꽉 찬 삼각형으로 구성했다.
NDC(Normalized Device Coordinates)란?
NDC란 View Plane(모니터에 실제 뜨는 화면)에 대해 정규화된 좌표계다.
3D Object 등도 결국 모니터에 뜰 때는 여러 변환 과정(Projection 등)을 거쳐 2D로 변환될 텐데, 이 때 최종적으로 표시되는 좌표계를 정규화한 것이 NDC다.
따라서 이 좌표계에서는 해상도에 따른 픽셀 좌표 차이를 고민할 필요가 없다.
정규화되어 있으므로 -1 ~ +1 사이의 값을 가진다.
VAO 및 VBO를 통해 vertex data 저장
우선 해당 data를 vertex shader 쪽으로 보내기 위해서는 다음 방법을 구성해야 한다.
- GPU에 메모리를 생성하여 vertex data 저장.
- opengl이 메모리를 해석하는 방식 구성.
- 그래픽 카드로 데이터를 전송하는 방법을 지정.
그래서 opengl에서는 VBO를 통해 이 메모리 관리 작업들을 진행할 수 있다.
VBO를 사용하면 다음과 같은 장점이 있다.
- 한 번에 GPU에 대량의 vertex batch를 보낼 수 있다.(느린 작업이기 때문에 한 번에 하는 게 좋음.)
- GPU에 대량의 data를 보관할 수 있다.
일단 VBO만 있어도 이론 상 삼각형을 띄울 수 있으나 state 및 attribute의 관리 용이성을 위해 VAO를 선언하자.
// VAO 생성 및 Bind - 이 밑에 생성되는 VBO들을 이 VAO에 자동으로 Bind.
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
이제 VBO를 만들고 vertex data를 넣어보자.
이전 챕터에서 선언한 것과 같이 생성과 context에의 binding을 진행한다.
// VBO 생성 및 Bind.
glGenBuffers(1, &VBO); //size & buffer pointer.
glBindBuffer(GL_ARRAY_BUFFER, VBO); // buffer type & buffer obj.
특히 glBindBuffer
에서 GL_ARRAY_BUFFER
는 buffer type으로 opengl에는 buffer type이 많기 때문에 따로 지정해주는 것이다.
이렇게 버퍼 생성 및 context binding이 끝났으면 우리가 GL_ARRAY_BUFFER 를 target으로 하는 모든 buffer call은 VBO를 구성하는 데 사용된다.
이제 버퍼를 채우기 위해 glBufferData
를 사용할 수 있다.
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBufferData는 buffer object에 데이터를 할당하는 데 사용되는 함수다.
(기존에 할당된 데이터가 있었다면 기존 데이터는 지워진다.)
GL_ARRAY_BUFFER target으로 지정된 buffer에 vertex 데이터만큼의 size를 확보하고 vertex data를 쓴다.
꽤 자주 사용되는 함수이므로 다음 API 읽어보면 좋다.
https://registry.khronos.org/OpenGL-Refpages/gl4/html/glBufferData.xhtml
Vertex Attribute 선언 및 Link
이전 글에서 설명했듯이 VBO 데이터 덩어리는 그 해석 방법이 없는 단순 데이터 쪼가리일 뿐이다.
xyz로 나열된건지 uv나 color값이 따로 지정된건지 모른다.
따라서 그걸 opengl에 이해시켜주기 위해 vertex의 속성을 나타내는 Vertex Attribute의 선언 및 링크가 필요하다.
우선 현재의 데이터 나열은 아래와 같이 해석해야 할 것이다.
- offset은 없다.(시작 시 위치 차이)
- stride는 12다.(한 종류의 데이터가 다시 등장할 떄의 보폭)
- xyz순서로 compact하게 나온다.
이 Attribute 정보를 선언하려면 다음과 같이 코드를 작성한다.
// Attribute 선언 및 활성화
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glVertexAttribPointer는 opengl이 어떻게 buffer data를 해석해야 하는지 알려주는 함수로서 파라미터는 다음 순서로 적는다.
- GLuint index: vertex attribute의 index. 현재 0으로 선언.
- GLint size: vertex attribute의 수, xyz로 3개.
- GLenum type: buffer에 저장된 데이터의 type. 현재 vertex는 GL_FLOAT로 선언.
- GLboolean normalized: 정규화를 해야하는지의 여부, 0과 1 밖에 있는 값이 전부 여기로 매핑된다.
- GLsizei stride: 보폭, 0으로 적으면 알아서 해준다.
- const void * pointer: buffer에서 data가 시작하는 위치의 offset에 대한 pointer. 0으로 시작하므로 그 위치에 대한 값을 적어주면 된다. 0으로 적어도 알아서 형 변환 될거니까 그냥 그렇게 적어도 된다. 정석은
(**void***)0
만약 glVertexAttribPointer
을 정석으로 적어본다면 다음과 같이 적을 수 있다.
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * **sizeof**(**float**), (**void***)0);
VBO VAO Unbind
이제 VBO와 VAO를 핸들하여 데이터를 삽입하고 attribute를 링크 및 활성화했으니 Context로부터 Unbind 해줘야 한다. pointer 0으로 해주면 된다.
// VBO unbind
glBindBuffer(GL_ARRAY_BUFFER, 0);
// VAO unbind
glBindVertexArray(0);
2.2. Vertex/Fragment Shader 작성 및 추가, 컴파일
이제 vertex에 대한 데이터 준비는 완료됐다.
어떻게 렌더링할 것인지를 알려줘야 한다.
그것이 vertex fragment shader의 stage다.
생각해보면 각각의 역할은 다음과 같다.
- vertex shader: vertex 위치에 대한 연산 처리.
- fragment(pixel) shader: pixel의 출력 color 결정.
우리는 삼각형을 띄우기 위해 다음과 같이 역할을 만들어 볼 것이다.
- vertex shader: 입력된 vertex의 좌표를 그대로 출력, 단 현재 z가 비어있으므로 고정된 1 값으로 지정.
- fragment shader: 빨간색으로 지정.
opengl 프레임워크에서 Shader를 이용하기 위해서는 다음과 같은 단계가 필요하다.
- Shader Code를 작성한다.
- Shader를 생성하고 Shader Code를 source로 지정.
- Shader를 컴파일한다.
- Shader를 Shader Program에 붙인다.
- Shader Program을 Link한다.
- Shader Program을 Validate한다.
- Shader Program을 Use한다.
이 과정이 다소 복잡하지만 절차가 헷갈리는 거야 계속 보다보면 익숙해지는 것이고, 결국 어떤 구조를 만들 것인지를 이해하는 게 중요한 것 같다.
우리는 위의 구조처럼 fShader, vShader를 만들고 컴파일하여 그것들을 부착한 ShaderProgram을 Use하는 방식으로 Shader를 사용할 것이다.
Vertex Shader 및 Fragment Shader의 작성
아래의 목적을 이루는 셰이더를 작성해보자.
- vertex shader: 입력된 vertex의 좌표를 그대로 출력, 단 현재 z가 비어있으므로 고정된 1 값으로 지정.
- fragment shader: 삼각형 내부를 빨간색으로 지정.
Vertex Shader의 작성
우선 Vertex Shader는 아래와 같이 작성할 수 있다.
// Create Vertex Shader in GLSL.
static const char* vShader =
"#version 330\n"
"layout (location = 0) in vec3 pos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(pos.x, pos.y, pos.z, 1.0);\n"
"}\n";
string화를 제외한 raw code는 아래와 같다.
#version 330
layout (location = 0) in vec3 pos;
void main()
{
gl_Position = vec4(pos.x, pos.y, pos.z, 1.0);
};
하나하나의 의미를 살펴보자.
#version 330
이 부분은 opengl 버전명시
layout (location = 0) in vec3 aPos;
이 부분은 Vertex Attribute를 선언하는 부분.
layout (location = 0)
→ 버텍스 속성의 슬롯(위치)을 0번으로 지정in vec3 aPos;
→ 버텍스 셰이더로 전달되는 입력 변수 선언 (3D 좌표값을 받음)
이제 gl_Position에 지정함으로서 vertex 값 연산을 완료한다.
우리는 별다른 연산 없이 z에 1만 갖다 박을거니까 그렇게 하면 된다.
void main()
{
gl_Position = vec4(pos.x, pos.y, pos.z, 1.0);
};
Fragment Shader의 작성
fragment Shader는 아래와 같이 작성할 수 있다.
// Create Fragment Shader in GLSL.
static const char* fShader =
"#version 330\n"
"out vec4 color;\n"
"void main()\n"
"{\n"
" color = vec4(1.0, 0.0, 0.0, 1.0);\n"
"}\n";
string화를 제외한 raw code는 아래와 같다.
#version 330
out vec4 color;
void main()
{
color = vec4(1.0, 0.0, 0.0, 1.0);
}
version 커맨드는 기존과 같은 역할이므로 따로 설명하지 않는다.
out vec4 color;
frament shader는 1개의 vec4 output을 필요로 한다.
따라서 out 키워드를 통해 선언해준다. 변수명은 상관 없다.
void main()
{
color = vec4(1.0, 0.0, 0.0, 1.0);
}
이 부분을 통해 컬러를 빨간색으로 지정해준다.
Shader 객체 생성 및 컴파일, ShaderProgram에의 부착.
작성한 GLSL rawcode를 source로 하는 Shader 객체를 생성하고 컴파일, Shader Program에 부착해보자.
이 과정을 하나의 함수로 묶어보면 다음과 같다.
// Add Shader to Program.
// Shader에 대해 1. source 지정, 2. compile, 3. targetProgram에 attach.
void FirstTriangleRunner::AddShader(GLuint targetProgram, const char* rawShaderCode, GLenum shaderType)
{
GLuint targetShader = glCreateShader(shaderType);
const GLchar* code[1];
code[0] = rawShaderCode;
GLint codeLength[1];
codeLength[0] = strlen(rawShaderCode);
glShaderSource(targetShader, 1, code, codeLength);
glCompileShader(targetShader);
GLint result = 0;
GLchar eLog[1024] = { 0 };
glGetShaderiv(targetShader, GL_COMPILE_STATUS, &result);
if (!result)
{
glGetShaderInfoLog(targetShader, sizeof(eLog), NULL, eLog);
printf("Error compiling the %d shader: '%s'\n", shaderType, eLog);
return;
}
glAttachShader(targetProgram, targetShader);
return;
}
parameter로 다음을 받는다.
- GLuint targetProgram: 타겟 ShaderProgram.
- const char* rawShaderCode: 아까 위에서 작성한 raw한 shader code.
- GLenum shaderType: 만들 셰이더 타입, vertex나 frag 등.
이제 shader object를 생성하고 compile을 진행해보자.
GLuint targetShader = glCreateShader(shaderType);
const GLchar* code[1];
code[0] = rawShaderCode;
GLint codeLength[1];
codeLength[0] = strlen(rawShaderCode);
glShaderSource(targetShader, 1, code, codeLength);
glCompileShader(targetShader);
하나씩 살펴보자.
우선 이 코드를 통해 shader object를 생성한다.
GLuint targetShader = glCreateShader(shaderType);
또한 code와 codeLength 배열을 준비하여 source code를 연결해준다.
const GLchar* code[1];
code[0] = rawShaderCode;
GLint codeLength[1];
codeLength[0] = strlen(rawShaderCode);
glShaderSource(targetShader, 1, code, codeLength);
source code 연결 이후 compile시킨다.
glCompileShader(targetShader);
그 다음의 코드들은 compile 정보를 로깅하고 compile error를 출력하는 부분이다.
GLint result = 0;
GLchar eLog[1024] = { 0 };
glGetShaderiv(targetShader, GL_COMPILE_STATUS, &result);
if (!result)
{
glGetShaderInfoLog(targetShader, sizeof(eLog), NULL, eLog);
printf("Error compiling the %d shader: '%s'\n", shaderType, eLog);
return;
}
별 에러가 없을 시 다음 부분에서 shader를 shaderProgram 객체에 붙인다.
glAttachShader(targetProgram, targetShader);
셰이더 최종 완성
앞선 빌드업 이후 셰이더 프로그램을 최종적으로 완성하는 부분.
void FirstTriangleRunner::CompileShaders()
{
shaderProgram = glCreateProgram();
if (!shaderProgram)
{
printf("Error creating shader program!\n");
return;
}
AddShader(shaderProgram, vShader, GL_VERTEX_SHADER);
AddShader(shaderProgram, fShader, GL_FRAGMENT_SHADER);
GLint result = 0;
GLchar eLog[1024] = { 0 };
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &result);
// 제대로 링크되지 않았다면.
if (!result)
{
glGetProgramInfoLog(shaderProgram, sizeof(eLog), NULL, eLog);
printf("Error linking program: '%s'\n", eLog);
return;
}
// Validate program.
glValidateProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_VALIDATE_STATUS, &result);
if (!result)
{
glGetProgramInfoLog(shaderProgram, sizeof(eLog), NULL, eLog);
printf("Error validating program: '%s'\n", eLog);
return;
}
}
우선 아래 부분을 이용하여 Shader Program Object를 만들어준다.
shaderProgram = glCreateProgram();
if (!shaderProgram)
{
printf("Error creating shader program!\n");
return;
}
glCreateProgram
은 Program Object를 만들어주는 함수다.
Program Object는 shader가 붙을 수 있는 객체를 의미한다.(이제 정체를 알았다..!)
해당 부분 이후 바로 위에서 만들었던 함수를 호출하여 vertex shader와 fragment shader object를 만들고 Program Object에 붙이자.
AddShader(shaderProgram, vShader, GL_VERTEX_SHADER);
AddShader(shaderProgram, fShader, GL_FRAGMENT_SHADER);
그 이후 Program Object를 링크하고 제대로 되지 않은 경우 로그를 확인한다.
GLint result = 0;
GLchar eLog[1024] = { 0 };
glLinkProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &result);
// 제대로 링크되지 않았다면.
if (!result)
{
glGetProgramInfoLog(shaderProgram, sizeof(eLog), NULL, eLog);
printf("Error linking program: '%s'\n", eLog);
return;
}
참고: Program Object의 링크 과정이란
→ 여러 개의 셰이더(예: 버텍스 셰이더, 프래그먼트 셰이더 등)를 하나의 실행 가능한 GPU 프로그램으로 결합하는 과정
이제 아래의 glValidateProgram
을 통해 유효한지 확인하고 셰이더의 준비를 끝낸다.
→ 정확히는 현재 opengl state에서 program에 존재하는 executable이 실행가능한 지 확인하는 과정.
// Validate program.
glValidateProgram(shaderProgram);
glGetProgramiv(shaderProgram, GL_VALIDATE_STATUS, &result);
if (!result)
{
glGetProgramInfoLog(shaderProgram, sizeof(eLog), NULL, eLog);
printf("Error validating program: '%s'\n", eLog);
return;
}
2.3. 렌더링 메인 루프 실행
좋아 이제 위에서
- vertex input 관련 준비
- 셰이더 준비
했으니 렌더링만 하면 된다.
앞뒤 맥락 파악 위해 렌더링 루프를 포함한 Run 메서드를 보면 다음과 같다.
bool FirstTriangleRunner::Run()
{
GLFWwindow* mainWindow = ReadyGlfwGlewEnv();
if (!mainWindow)
{
return false;
}
CreateTriangle();
CompileShaders();
// loop til window closed.
while (!glfwWindowShouldClose(mainWindow))
{
// Get + handle user input events.
glfwPollEvents();
// Clear window.
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glUseProgram(0);
glfwSwapBuffers(mainWindow);
}
return true;
}
우선 뭐 아래 부분은 GLEW/GLFW 관련 셋업 부분이다.
GLFWwindow* mainWindow = ReadyGlfwGlewEnv();
if (!mainWindow)
{
return false;
}
(ReadyGlfwGlewEnv()가 궁금하면 위의 repo링크를 타면 된다.)
이제 아까 만들어둔 삼각형 생성 및 셰이더 프로그램 생성을 진행한다.
둘 중 뭘 먼저 실행하든 상관 없다.
CreateTriangle();
CompileShaders();
이제 메인 루프를 실행하자.
메인 루프의 전체는 다음과 같다.
// loop til window closed.
while (!glfwWindowShouldClose(mainWindow))
{
// Get + handle user input events.
glfwPollEvents();
// Clear window.
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glUseProgram(0);
glfwSwapBuffers(mainWindow);
}
아래 부분을 통해 Shader Program Object의 사용을 선언해준다.
glUseProgram(shaderProgram);
이제 아까 만든 VBO가 binding되어있는 VAO object를 현재 context에 binding시킨다.
glBindVertexArray(VAO);
이제 그릴 건 명확히 삼각형이다.
VAO는 context에 binding되어있으므로 이제 그냥 그려달라고 말하면 된다.
glDrawArrays(GL_TRIANGLES, 0, 3);
glDrawArrays
함수는 현재 bind된 VBO의 vertex들을 그려주는 함수로, parameter 구성은 다음과 같다.
- GLenum mode: 그릴 때의 primitive를 뭘로 할건지(삼각형, 선, quad 등)
- GLint first: 현재 array에서 start index.
- GLsizei count: 몇 개의 vertex를 render할 건지.
이후 아래 부분을 통해 현재 bind된 object들을 context에서 unbind 해준다.
(매 프레임 돌아가므로 매 프레임 bind - unbind가 진행될 것이다..!)
glBindVertexArray(0);
glUseProgram(0);
이제 이전에 그려졌던 buffer와 현재 buffer를 swap해서 현재 buffer에 담긴 내용을 창에 render하자.
glfwSwapBuffers(mainWindow);
이럼 렌더링이 끝난다. 실행을 눌러보면 다음과 같이 결과가 뜬다… 짠!
참고: 여기서 눈치챈 사람도 있겠지만.. bind unbind는 아래 코드와 같이 rendering loop 전후로 빼놔도 프로그램은 별 이상 없이 실행된다.(그리고 아마 이게 맞는 방향일거다.)
하지만 조금 더 렌더링 로직을 뭉치기 위해 루프 안에 두었다..! 비난 방지용으로 적어둔다.
Refs
https://www.udemy.com/course/graphics-with-modern-opengl/
https://learnopengl.com/Getting-started/Hello-Triangle
https://registry.khronos.org/OpenGL-Refpages/gl4/html/glValidateProgram.xhtml
https://www.khronos.org/opengl/wiki/Shader_Compilation#Program_linking
https://registry.khronos.org/OpenGL-Refpages/gl4/html/glBufferData.xhtml
https://registry.khronos.org/OpenGL-Refpages/gl4/html/glVertexAttribPointer.xhtml
'그래픽스' 카테고리의 다른 글
[그래픽스] Rendering Pipeline 개요 (2) | 2025.02.09 |
---|---|
[그래픽스] Hello Window - GLEW/GLFW 이용하여 창 띄우기 (0) | 2025.02.06 |
[그래픽스] OpenGL과 GLEW/GLFW (0) | 2025.02.03 |
[그래픽스] C++에서 OpenGL(GLEW/GLFW) 개발 환경 구축 (0) | 2025.02.01 |
- Total
- Today
- Yesterday
- 신호 및 시스템
- 딥러닝
- 머신 러닝
- RGB이미지
- 연속 신호
- 자연어 처리
- Andrew ng
- CS
- 사진구조
- 컴퓨터 과학
- 순환 신경망
- 매트랩
- 이미지처리
- 영상처리
- 이산 신호
- 인덱스 이미지
- 매트랩 함수
- 신경망
- ML
- Neural Network
- Logistic Regression
- 밑바닥부터 시작하는 딥러닝
- rnn
- 컴퓨터과학
- 머신러닝
- gradient descent
- NLP
- 이미지
- 영상구조
- CNN
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |