본문 바로가기
<LIBRARY>/OPENGL

3. 삼각형 그리기(2)

by CodeGrimie 2021. 1. 17.

지난 정리에 이어 이제 쉐이더 프로그램을 만들어보기 전에 이전 코드를 정리한다.

특정 기능을 하는 코드들을 되도록이면 묶어서 함수로 만드는 것이 좋은 것도 있지만 쉐이더의 코드 역시 꽤 길기 때문이다.

CreateTriangle 함수

▼ 삼각형 정점 좌표, VAO, VBO를 모두 함수로 빼낸다.

GLuint VAO, VBO;

void CreateTriangle()
{
    GLfloat vertices[] = {
        -1.0f, -1.0f, 0.0f,
        1.0f, -1.0f, 0.0f,
        0.0f, 1.0f, 0.0f
    };

    // OpenGL 정점 배열 생성기를 사용해서 VAO를 생성
    glGenVertexArrays(1, &VAO);
    // 우리가 생성한 VAO를 현재 수정 가능하도록 연결한다.
    glBindVertexArray(VAO);

    // OpenGL 정점 배열 생성기를 사용해서 VBO를 생성
    glGenBuffers(1, &VBO);
    // 우리가 생성한 VBO를 현재 수정 가능하도록 연결한다.
    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    // 우리가 만든 삼각형 정점 좌표를 VBO에 저장한다.
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    // VAO에 이 VAO를 어떻게 해석해야 할 지 알려줍니다.
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

    // VAO 사용 허용
    glEnableVertexAttribArray(0);
    // VBO 수정 종료 및 연결 초기화
    glBindBuffer(GL_ARRAY_BUFFER, 0);

    // 수정이 완료 되면 연결을 끊기 위해 초기값으로 연결한다.
    glBindVertexArray(0);
}

GLuint VAO, VBO;가 전역 변수로 이동한 걸 볼 수 있다.

이전 코드에서는 지역 변수로 받아올 수 있었지만 함수로 빼내면서 전역 변수여야 받아올 수 있기에 이동했다.

쉐이더 프로그램 생성하기

쉐이더(Shader)는 우리나라 말로 색조 생성기로 해석할 수 있다.

우리가 정점 좌표를 통해 삼각형 면은 만들었지만 아무 색조가 없는 검은색으로 나온 걸 기억할 것이다.

이는 색조를 만들어주는 쉐이더가 없기 때문에 발생한 문제였다.

 

쉐이더에는 정점(Vertex) 쉐이더, 조각(Fragment) 쉐이더 두개가 존재한다.

이 두 개의 쉐이더를 컴파일해서 OpenGL이 사용할 수 있도록 해주는 것이 쉐이더 프로그램이다.

 

이제 쉐이더 프로그램을 생성해본다.

 

▼ VAO, VBO 다음에 shader를 선언한다.

GLuint VAO, VBO, shader;

쉐이더 프로그램을 선언 했으니 초기화해줄 차례다.

Main 함수에서 CreateTriangle 함수 뒤에 바로 이어서 작업한다.

 

▼ 위의 코드 정리가 되었다면 아래와 같은 위치일 것이다.

int main()
{
    // 로케일 국가 한국 지정
    _wsetlocale(LC_ALL, L"Korean");

    // GLFW 초기화
    if (glfwInit() == GLFW_FALSE)
    {
        wprintf(L"GLFW 초기화 실패\n");
        glfwTerminate();
        return (1);
    }

    // OpenGL 버전 지정
    // OpenGL MAJOR.MINOR 방식으로 표현
    // 이번엔 3.3을 사용한다.
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);

    // OpenGL 코어 프로필 설정
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    // OpenGL 상위호환 활성화
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    // GLFW 윈도우 생성
    GLFWwindow* mainWindow = glfwCreateWindow(WIDTH, HEIGHT, "OpenGL TRIANGLE", NULL, NULL);
    if (mainWindow == NULL)
    {
        wprintf(L"GLFW 윈도우 생성이 실패했습니다.\n");
        glfwTerminate();
        return (1);
    }

    // 버퍼 가로, 버퍼 세로 선언
    int bufferWidth, bufferHeight;
    // mainWindow로부터 버퍼 가로 크기와 버퍼 세로 크기를 받아온다.
    glfwGetFramebufferSize(mainWindow, &bufferWidth, &bufferHeight);

    glfwMakeContextCurrent(mainWindow);

    // GLEW의 모든 기능 활성화
    glewExperimental = GL_TRUE;
    if (glewInit() != GLEW_OK)
    {
        wprintf(L"GLEW 초기화가 실패했습니다.\n");
        // mainWindow 삭제
        glfwDestroyWindow(mainWindow);
        glfwTerminate();
        return (1);
    }

    CreateTriangle();
    //---------------------------------------------
    // SHADER CODE
    //---------------------------------------------

    // OpenGL Viewport 생성
    glViewport(0, 0, bufferWidth, bufferHeight);

    // GLFW가 종료되지 않는 한 계속 도는 순환문
    while (glfwWindowShouldClose(mainWindow) == GLFW_FALSE)
    {
        // GLFW 이벤트 입력
        glfwPollEvents();

        // 연두색 화면 그리기
        glClearColor(0.4f, 0.6f, 0.2f, 1.0f);
        // OpenGL 배경색상 초기화
        glClear(GL_COLOR_BUFFER_BIT);

        //---------------------------------------------
        // VBO에 있는 데이터 바인딩
        glBindVertexArray(VBO);
        // 데이터를 바탕으로 그리기
        glDrawArrays(GL_TRIANGLES, 0, 3);
        // 데이터 바인딩 해제
        glBindVertexArray(0);
        //---------------------------------------------

        // GLFW 더블 버퍼링
        glfwSwapBuffers(mainWindow);
    }

    return (0);
}

숨은 그림 찾기 같지만 SHADER CODE 라고 적힌 영역에 이번 쉐이더 코드가 위치한다.

 

▼ 쉐이더 프로그램 생성 코드

shader = glCreateProgram();

if (shader == NULL)
{
    printf("Error Creating Shader Program!\n");
    return;
}

OpenGL이 기본적으로 C로 작성되어 있는 것도 있지만 어떤 프로그램이라도 버그를 미연에 방지할 수 있는 코드를 작성하는 것이 좋다.

 

glCreateProgram은 이름에서도 알 수 있듯이 쉐이더 프로그램을 만드는 함수다.

이제 이 쉐이더 프로그램에 필요한 정점 쉐이더와 조각 쉐이더를 작성해서 넣어주면 된다.

GLSL(OpenGL Shading Language)

GLSL(OpenGL Shading Language)은 쉐이더를 작성하기 위한 언어로 C의 문법과 비슷하다.

상용 게임엔진을 사용하는 것이 익숙해진 지금은 C로 대규모 프로젝트를 하거나 직접 게임 엔진을 짜는 경우가 많이 줄어들었다.

하지만 OpenGL의 쉐이더 프로그램이 등장할 때에는 별도의 언어를 공부할 필요 없이 편리하게 쉐이더를 작성할 수 있어서 많은 개발자들이 반가워했다고 한다.

정점 쉐이더(Vertex Shader) 코드 작성

▼ 정점 쉐이더 코드

// 정점 쉐이더
// CPP 11 이후 Raw 문자열 사용 방식
static const char* vShader = R"(
#version 330

layout (location = 0) in vec3 pos;

void main()
{
    gl_Position = vec4(pos.x, pos.y, pos.z, 1.0);
})";

//// 정점 쉐이더
// CPP 11 이전 일반 문자열 사용 방식 
//static const char* vShader = "                       \n\
//#version 330                                         \n\
//                                                     \n\
//layout (location = 0) in vec3 pos;                   \n\
//                                                     \n\
//void main()                                          \n\
//{                                                    \n\
//    gl_Position = vec4(pos.x, pos.y, pos.z, 1.0);    \n\
//}";

정점 쉐이더의 코드는 그리 길지 않다.

코드를 하나하나 어떤 의미를 가지는지 살펴본다.

 

#version 330 은 OpenGL 버전을 작성하는 부분이다.

우리가 제일 처음 OpenGL 공부를 시작할 때 Opengl3.3을 사용하기로 했었기 때문에 쉐이더에도 이를 명시해준다.

이는 OpenGL의 버전에 따라 쉐이더에서 제공하는 기능들도 변동사항이 있다는 걸 암시한다.

 

layout (location = 0) in vec3 pos; 가 조금 낯설것이다.

 

layout은 OpenGL Layout Qualifier라고 한다.

게임을 만들기 위해서는 당연히 하나 이상의 쉐이더를 작성해야 하고 경우에 따라서 변경하거나 재사용해야 할 경우가 많을 것이다.

여기서 레이아웃은 포토샵의 레이어와 비슷한 개념으로 layout (location  = 0)은 말 그대로 0번 레이아웃이라고 설정해주는 걸로 보인다.

 

아래의 크로노스 그룹의 글을 보아도 아직 확 와 닿지 않는다. 코드를 더 써봄으로써 이해해야 할 것 같다..

www.khronos.org/opengl/wiki/Layout_Qualifier_(GLSL)

 

Layout Qualifier (GLSL) - OpenGL Wiki

A number of OpenGL Shading Language variables and definitions can have layout qualifiers associated with them. Layout qualifiers affect where the storage for a variable comes from, as well as other user-facing properties of a particular definition. All lay

www.khronos.org

in vec3 pos;는 정점 쉐이더에 입력(in)되는 인자 값을 받는 것 같다.

이번엔 위치 좌표만 받아서 처리하기 때문에 pos만 작성한다.

 

쉐이더의 작동 코드는 C와 같이 Main 함수 안에 작성한다.

gl_Position = vec4(pos.x, pos.y, pos.z, 1.0);

단순히 우리가 작성한 정점 좌표들을 그대로 출력하기 때문에 어려운 코드는 없다.

 

추가로 예전부터 해온 쉐이더 코드를 받아오는 방법과 지금 현재 가능한 새로운 방법을 같이 정리했다.

기존의 방법은 '\n\'을 통해 코드끼리 줄 바꿈을 해줘야 했다.

지금의 CPP 11 이후는 Raw 문자열을 사용하여 줄 바꿈 기호 없이 직관적으로 작성할 수 있다.

조각 쉐이더(Fragment Shader) 코드 작성

▼ 조각 쉐이더 코드

// 조각 쉐이더
static const char* fShader = R"(
#version 330

out vec4 colour;

void main()
{
    colour = vec4(1.0, 1.0, 1.0, 1.0);
})";

정점 쉐이더와 달리 조각 쉐이더는 out으로 실질적 색조를 생성하여 출력한다.

보통 색상은 [적색(R), 녹색(G), 청색(B), 투명도(A)]로 이루어져 있어서 vec4로 받아온다.

 

그리고 colour는 오타가 아니다.

color와 colour는 둘 다 색상을 나타낸다.

미국에서는 대체로 color를 사용하고 그 외 영어권에서는 colour를 사용한다.

 

지금의 조각 쉐이더에서 우리는 흰색[적색(1.0), 녹색(1.0), 청색(1.0), 투명도(1.0)]을 채색하도록 작성했다.

우리의 예상이 맞다면 흰색 삼각형이 나와야 맞다. 

 

자 이제 정점 쉐이더와 조각 쉐이더를 다 작성했다!

이것이 끝이라면 정말 좋겠지만 이제 진짜 긴 코드들을 작성해야 한다.

쉐이더 프로그램에 쉐이더 소스 등록하기

작성한 쉐이더를 이제 쉐이더 프로그램에 등록을 해야 한다.

다행히 정점 쉐이더와 조각 쉐이더 둘 다 똑같은 방법을 사용하기 때문에 하나의 함수를 만들어서 재활용할 수 있다.

 

▼ AddShader(쉐이더 프로그램, 쉐이더 코드, 쉐이더 타입)을 만든다.

void AddShader(GLuint theProgram, const char* shaderCode, GLenum shaderType)
{
    // 쉐이더 생성
    GLuint theShader = glCreateShader(shaderType);

    // 쉐이더 코드를 저장할 배열 생성
    const GLchar* theCode[1];
    theCode[0] = shaderCode;

    // 쉐이더 코드 길이를 저장할 배열 생성
    GLint codeLength[1];
    codeLength[0] = strlen(shaderCode);

    // 쉐이더에 우리가 작성한 쉐이더 코드를 저장한다.
    glShaderSource(theShader, 1, theCode, codeLength);
    // 쉐이더 컴파일
    glCompileShader(theShader);

    // 에러 검출을 위한 변수 선언
    GLint result = 0;
    GLchar eLog[1024] = { 0 };

    // 쉐이더 컴파일 정상완료 여부 저장
    glGetShaderiv(theShader, GL_COMPILE_STATUS, &result);
    if (!result)
    {
        // 쉐이더 오류 로그를 저장하고 출력합니다.
        glGetShaderInfoLog(theShader, sizeof(eLog), NULL, eLog);
        printf("Error Compiling the %d shader: '%s'\n", shaderType, eLog);
        return;
    }

    // 쉐이더 프로그램에 쉐이더를 등록합니다.
    glAttachShader(theProgram, theShader);
}

전반적인 코드의 흐름은 아래와 같다.

 

<AddShader 함수 코드 흐름>

1. 쉐이더 생성

2. 작성한 쉐이더 코드, 길이 저장

3. 쉐이더에 쉐이더 코드를 저장

4. 쉐이더 컴파일

5. 쉐이더 오류 검출

6. 정상완료 시 쉐이더 프로그램에 쉐이더 등록

 

우리는 정점 쉐이더와 조각 쉐이더 모두 단 하나의 코드만 작성했기 때문에 배열의 크기가 1로 설정되었다.

에러 검출의 코드의 경우 구조를 잘 살펴보는 게 좋다.

좋은 C 코드의 에러 검출 방식이라고 생각된다.

 

참고로 glGetShaderiv에서 iv는 int vector라고 한다.

쉐이더 컴파일의 정상완료 여부를 int형 vector로 반환한다는 의미.

 

이제 실제로 이 함수를 사용해보자.

쉐이더 추가 함수를 사용하기

▼ 우리가 만든 쉐이더 프로그램 코드 바로 밑에 두면 된다.

shader = glCreateProgram();

if (shader == NULL)
{
    printf("Error Creating Shader Program!\n");
    return (-1);
}

AddShader(shader, vShader, GL_VERTEX_SHADER);
AddShader(shader, fShader, GL_FRAGMENT_SHADER);

함수를 만들지 않았다면 20줄이 넘는 사실상 같은 코드를 두 번이나 썼을 것이다.

덕분에 쉐이더 프로그램에 정점 쉐이더와 조각 쉐이더를 등록하는 것이 짧고 쉽게 끝났다.

 

이제 쉐이더 프로그램을 OpenGL과 연결하고 검증하는 것만 남았다.

쉐이더 프로그램 연결하고 검증하기

▼ 쉐이더와는 코드가 다르다. glGetProgramiv다.

GLint result = 0;
GLchar eLog[1024] = { 0 };

// 쉐이더 프로그램 연결
glLinkProgram(shader);
glGetProgramiv(shader, GL_LINK_STATUS, &result);
if (!result)
{
    glGetProgramInfoLog(shader, sizeof(eLog), NULL, eLog);
    printf("Error Linking Program: '%s'\n", eLog);
    return (-1);
}

// 쉐이더 프로그램 검증
glValidateProgram(shader);
glGetProgramiv(shader, GL_VALIDATE_STATUS, &result);
if (!result)
{
    glGetProgramInfoLog(shader, sizeof(eLog), NULL, eLog);
    printf("Error Validating Program: '%s'\n", eLog);
    return (-1);
}

실제로 문제가 없기 때문에 코드는 아래의 두 줄만 추가해도 문제없이 작동한다.

 

glLinkProgram(shader);

glValidateProgram(shader);

 

그렇지만 게임에서는 프로그래머가 게임을 개발하는 동안 에러가 발생하는 구간을 명확하게 아는 것이 중요하기 때문에 에러 검출 코드를 작성한다.

 

여기서 아무 생각 없이 쉐이더 코드를 복붙 하면 에러가 계속해서 발생한다.

이것 때문에 한 시간이나 이유를 몰라 당황했는데 glGetProgramivglGetShaderiv가 달라서 생긴 문제였다.

쉐이더 프로그램 사용 코드 작성

이제 작성한 쉐이더 프로그램을 사용해보자.

우리가 VBO에 있는 데이터를 통해 삼각형을 그렸던 위치로 간다.

 

▼ 쉐이더 프로그램 사용과 해제 코드

// GLFW가 종료되지 않는 한 계속 도는 순환문
while (glfwWindowShouldClose(mainWindow) == GLFW_FALSE)
{
    // GLFW 이벤트 입력
    glfwPollEvents();

    // 연두색 화면 그리기
    glClearColor(0.4f, 0.6f, 0.2f, 1.0f);
    // OpenGL 배경색상 초기화
    glClear(GL_COLOR_BUFFER_BIT);

    //---------------------------------------------
    // Shader 적용
    glUseProgram(shader);
    //---------------------------------------------
    
    // VBO에 있는 데이터 바인딩
    glBindVertexArray(VBO);
    // 데이터를 바탕으로 그리기
    glDrawArrays(GL_TRIANGLES, 0, 3);
    // 데이터 바인딩 해제
    glBindVertexArray(0);
    
    //---------------------------------------------
    // Shader 해제
    glUseProgram(0);
    //---------------------------------------------

    // GLFW 더블 버퍼링
    glfwSwapBuffers(mainWindow);
}

쉐이더 프로그램 사용과 해제 코드가 실제로 VBO에 있는 데이터를 그리는 코드를 감싸고 있다.

VBO와 마찬가지로 사용하면 반드시 해제를 해줘야 한다.

 

C에서는 메모리 세트는 단품 구매가 절대 안 된다는 걸 잊으면 안 된다.

자 이제 다시 컴파일하고 실행하면 이제 우리가 원했던 모습의 삼각형이 나온다.

실행결과

전체 코드

#include <cstdio>
#include <clocale>
#include <cstring>

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

const GLint WIDTH = 720, HEIGHT = 480;

GLuint VAO, VBO, shader;

// 정점 쉐이더
static const char* vShader = R"(
#version 330

layout (location = 0) in vec3 pos;

void main()
{
    gl_Position = vec4(pos.x, pos.y, pos.z, 1.0);
})";

// 조각 쉐이더
static const char* fShader = R"(
#version 330

out vec4 colour;

void main()
{
    colour = vec4(1.0, 1.0, 1.0, 1.0);
})";

void CreateTriangle()
{
	GLfloat vertices[] = {
		-1.0f, -1.0f, 0.0f,
		1.0f, -1.0f, 0.0f,
		0.0f, 1.0f, 0.0f
	};

	// OpenGL 정점 배열 생성기를 사용해서 VAO를 생성
	glGenVertexArrays(1, &VAO);
	// 우리가 생성한 VAO를 현재 수정 가능하도록 연결한다.
	glBindVertexArray(VAO);

	// OpenGL 정점 배열 생성기를 사용해서 VBO를 생성
	glGenBuffers(1, &VBO);
	// 우리가 생성한 VBO를 현재 수정 가능하도록 연결한다.
	glBindBuffer(GL_ARRAY_BUFFER, VBO);

	// 우리가 만든 삼각형 정점 좌표를 VBO에 저장한다.
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
	// VAO에 이 VAO를 어떻게 해석해야 할 지 알려줍니다.
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);

	// VAO 사용 허용
	glEnableVertexAttribArray(0);
	// VBO 수정 종료 및 연결 초기화
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// 수정이 완료 되면 연결을 끊기 위해 초기값으로 연결한다.
	glBindVertexArray(0);
}

void AddShader(GLuint theProgram, const char* shaderCode, GLenum shaderType)
{
	// 쉐이더 생성
	GLuint theShader = glCreateShader(shaderType);

	// 쉐이더 코드를 저장할 배열 생성
	const GLchar* theCode[1];
	theCode[0] = shaderCode;

	// 쉐이더 코드 길이를 저장할 배열 생성
	GLint codeLength[1];
	codeLength[0] = strlen(shaderCode);

	// 쉐이더에 우리가 작성한 쉐이더 코드를 저장한다.
	glShaderSource(theShader, 1, theCode, codeLength);
	// 쉐이더 컴파일
	glCompileShader(theShader);

	// 에러 검출을 위한 변수 선언
	GLint result = 0;
	GLchar eLog[1024] = { 0 };

	// 쉐이더 컴파일 정상완료 여부 저장
	glGetShaderiv(theShader, GL_COMPILE_STATUS, &result);
	if (!result)
	{
		// 쉐이더 오류 로그를 저장하고 출력합니다.
		glGetShaderInfoLog(theShader, sizeof(eLog), NULL, eLog);
		printf("Error Compiling the %d shader: '%s'\n", shaderType, eLog);
		return;
	}

	// 쉐이더 프로그램에 쉐이더를 등록합니다.
	glAttachShader(theProgram, theShader);
}

void CompileShader()
{
	shader = glCreateProgram();

	if (shader == NULL)
	{
		printf("Error Creating Shader Program!\n");
		return;
	}

	AddShader(shader, vShader, GL_VERTEX_SHADER);
	AddShader(shader, fShader, GL_FRAGMENT_SHADER);

	GLint result = 0;
	GLchar eLog[1024] = { 0 };

	// 쉐이더 프로그램 연결
	glLinkProgram(shader);
	glGetProgramiv(shader, GL_LINK_STATUS, &result);
	if (!result)
	{
		glGetProgramInfoLog(shader, sizeof(eLog), NULL, eLog);
		printf("Error Linking Program: '%s'\n", eLog);
		return;
	}

	// 쉐이더 프로그램 검증
	glValidateProgram(shader);
	glGetProgramiv(shader, GL_VALIDATE_STATUS, &result);
	if (!result)
	{
		glGetProgramInfoLog(shader, sizeof(eLog), NULL, eLog);
		printf("Error Validating Program: '%s'\n", eLog);
		return;
	}
}

int main()
{
	// 로케일 국가 한국 지정
	_wsetlocale(LC_ALL, L"Korean");

	// GLFW 초기화
	if (glfwInit() == GLFW_FALSE)
	{
		wprintf(L"GLFW 초기화 실패\n");
		glfwTerminate();
		return (1);
	}

	// OpenGL 버전 지정
	// OpenGL MAJOR.MINOR 방식으로 표현
	// 이번엔 3.3을 사용한다.
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);

	// OpenGL 코어 프로필 설정
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
	// OpenGL 상위호환 활성화
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

	// GLFW 윈도우 생성
	GLFWwindow* mainWindow = glfwCreateWindow(WIDTH, HEIGHT, "OpenGL TRIANGLE", NULL, NULL);
	if (mainWindow == NULL)
	{
		wprintf(L"GLFW 윈도우 생성이 실패했습니다.\n");
		glfwTerminate();
		return (1);
	}

	// 버퍼 가로, 버퍼 세로 선언
	int bufferWidth, bufferHeight;
	// mainWindow로부터 버퍼 가로 크기와 버퍼 세로 크기를 받아온다.
	glfwGetFramebufferSize(mainWindow, &bufferWidth, &bufferHeight);

	glfwMakeContextCurrent(mainWindow);

	// GLEW의 모든 기능 활성화
	glewExperimental = GL_TRUE;
	if (glewInit() != GLEW_OK)
	{
		wprintf(L"GLEW 초기화가 실패했습니다.\n");
		// mainWindow 삭제
		glfwDestroyWindow(mainWindow);
		glfwTerminate();
		return (1);
	}

	//---------------------------------------------
	CreateTriangle();
	CompileShader();

	// OpenGL Viewport 생성
	glViewport(0, 0, bufferWidth, bufferHeight);

	// GLFW가 종료되지 않는 한 계속 도는 순환문
	while (glfwWindowShouldClose(mainWindow) == GLFW_FALSE)
	{
		// GLFW 이벤트 입력
		glfwPollEvents();

		// 연두색 화면 그리기
		glClearColor(0.4f, 0.6f, 0.2f, 1.0f);
		// OpenGL 배경색상 초기화
		glClear(GL_COLOR_BUFFER_BIT);

		// Shader 적용
		glUseProgram(shader);

		// VBO에 있는 데이터 바인딩
		glBindVertexArray(VBO);
		// 데이터를 바탕으로 그리기
		glDrawArrays(GL_TRIANGLES, 0, 3);
		// 데이터 바인딩 해제
		glBindVertexArray(0);

		// Shader 해제
		glUseProgram(0);

		// GLFW 더블 버퍼링
		glfwSwapBuffers(mainWindow);
	}

	return (0);
}

'<LIBRARY> > OPENGL' 카테고리의 다른 글

5. GLM 라이브러리 적용하기  (0) 2021.01.19
4. 삼각형 움직이기  (0) 2021.01.17
2. 삼각형 그리기 (1)  (0) 2021.01.14
1. 기본 코드 작성하기  (0) 2021.01.11
0. OpenGL 개발환경 설정하기  (0) 2021.01.09

댓글