본문 바로가기
<LIBRARY>/OPENGL

6. 삼각형 회전 시키기

by CodeGrimie 2021. 1. 25.

GLM을 사용해서 삼각형을 회전시키는 것은 매우 간단하다.

회전 행렬에 원하는 각도만큼 넣기만 하면 끝이다.

(물론 진짜 행렬 연산을 구현했다면 코드가 2배는 길어졌을 것이다.)

1 호도(Radian) 값 설정

회전을 한다는 것은 각도를 변경한다는 의미가 된다.

컴퓨터는 호도(Radian)으로 빠르게 연산하기 때문에 미리 1 호도값을 설정해서 사용하면 편리하다.

 

▼ 호도는 상수로 설정 해준다.

const GLint WIDTH = 720, HEIGHT = 480;

//---------------------------------------------
// 1 호도값
const float toRadians = 3.14159265f / 180.0f;
//---------------------------------------------

미리 1호도 값을 설정해뒀기 때문에 나중에 사용할 때엔 사람에게 익숙한 각도만 곱해주면 된다.

보다 자세한 설명은 알고리즘 수업 노트인 20210121(목)에 기록되어있다.

현재 각도 값을 저장할 변수 선언

마찬가지로 현재 각도값을 저장할 변수를 선언한다.

매 프레임마다 각도를 변경해서 삼각형이 계속 돌도록 만들기 위해서다.

 

▼ 각도는 소수점으로도 나올 수 있기 때문에 실수로 한다.

GLuint VAO, VBO, shader, uniformModel;

// 방향값(왼쪽, 오른쪽)
bool direction = true;
// 삼각형의 차이값
float triOffset = 0.0f;
// 삼각형의 최대 차이값
float triMaxOffset = 1.0f;
// 삼각형의 변화값
float triIncrement = 0.01f;

//---------------------------------------------
// 현재 각도값
float currentAngle = 0.0f;
//---------------------------------------------

회전 행렬 연산하기

이동 행렬을 연산하는 것이 glm::translate였듯이 회전역시 직관적이게 glm::rotate 함수가 존재한다.

추가로 각도가 들어가는 데 이때 위에서 선언해두었던 toRadians를 각도에 곱해서 컴퓨터가 라디안으로 받아들일 수 있도록 할 수 있다.

 

▼ 시험 삼아 90도를 회전시켜본다.

// mat4 model 초기화
glm::mat4 model = glm::mat4(1.0f);
// 우리가 원하는 값만큼 이동 행렬 연산
model = glm::translate(model, glm::vec3(triOffset, 0.0f, 0.0f));
//---------------------------------------------
// 회전 행렬 연산
model = glm::rotate(model, 90 * toRadians, glm::vec3(0.0f, 0.0f, 1.0f));
//---

주의해야 할 점은 Z축으로 회전하고 있다는 것이다.

OpenGL오른손 좌표계를 사용하는데 Z축이 깊이를 저장하고 시선의 방향이 -가 된다.

즉 화면을 기준으로 가까우면 + 멀어지면 -인 개념이다.

 

▼ 오른손 좌표계

그래서 Z축을 기준으로만 회전하기 때문에 Z값에 1.0f를 넣는다.

그리고 컴파일하고 실행하면 아래의 모습으로 출력된다.

 

반시계방향으로 90도 회전한 모습을 볼 수 있다.

매 프레임 마다 각도 변경하기

회전이 정상적으로 작동하지만 뭔가 밋밋하다.

기왕 회전하는 거 앞서서 선언했던 currentAngle을 사용해서 매 프레임마다 변하도록 설정한다.

 

▼ triOffset 바로 밑에 작성한다.

// 방향이 오른쪽인지 왼쪽인지
 if (direction == true)
{
    triOffset += triIncrement;
}
else
{
    triOffset -= triIncrement;
}

// 최대 차이값을 넘기게 되면 방향 전환
if (abs(triOffset) >= triMaxOffset)
{
    direction = !direction;
}

//---------------------------------------------
// 프레임 당 0.25도씩 증가
currentAngle += 0.25f;
if (currentAngle >= 360)
{
    currentAngle -= 360;
}
//---------------------------------------------

아주 간단한 코드다.

360도가 될 때 까지 0.25도증가하다가 360도를 넘는 그 순간에 360도를 빼버린다.

결과적으로는 0 ~ 360도 사이를 계속해서 반복할 것이다.

 

▼ 물론, 잊지말고 glm::rotate도 수정해야한다.

//---------------------------------------------
// 회전 행렬 연산
model = glm::rotate(model, currentAngle * toRadians, glm::vec3(0.0f, 0.0f, 1.0f));
//---------------------------------------------

이제 컴파일하고 실행해주기만 하면 된다.

 

▼ 사진으로는 티가 안 나지만 열심히 회전하고 있다.

▼ 전체 코드

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

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

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

const GLint WIDTH = 720, HEIGHT = 480;

//---------------------------------------------
// 1 호도값
const float toRadians = 3.14159265f / 180.0f;
//---------------------------------------------

// uniformXMove -> uniformModel
GLuint VAO, VBO, shader, uniformModel;

// 방향값(왼쪽, 오른쪽)
bool direction = true;
// 삼각형의 차이값
float triOffset = 0.0f;
// 삼각형의 최대 차이값
float triMaxOffset = 1.0f;
// 삼각형의 변화값
float triIncrement = 0.01f;

//---------------------------------------------
// 현재 각도값
float currentAngle = 0.0f;
//---------------------------------------------

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

layout (location = 0) in vec3 pos;

// model로 수정
uniform mat4 model;

void main()
{
    // model을 곱해준다.
    gl_Position = model * vec4(0.5 * pos.x, 0.5 * 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;
    }

    // unifomModel과 쉐이더의 model을 연결한다.
    uniformModel = glGetUniformLocation(shader, "model");
}

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();

        // 방향이 오른쪽인지 왼쪽인지
        if (direction == true)
        {
            triOffset += triIncrement;
        }
        else
        {
            triOffset -= triIncrement;
        }

        // 최대 차이값을 넘기게 되면 방향 전환
        if (abs(triOffset) >= triMaxOffset)
        {
            direction = !direction;
        }

        //---------------------------------------------
        // 프레임 당 0.25도씩 증가
        currentAngle += 0.25f;
        if (currentAngle >= 360)
        {
            currentAngle -= 360;
        }
        //---------------------------------------------

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

        // Shader 적용
        glUseProgram(shader);

        // mat4 model 초기화
        glm::mat4 model = glm::mat4(1.0f);
        // 우리가 원하는 값만큼 이동 행렬 연산
        model = glm::translate(model, glm::vec3(triOffset, 0.0f, 0.0f));
        //---------------------------------------------
        // 회전 행렬 연산
        model = glm::rotate(model, currentAngle * toRadians, glm::vec3(0.0f, 0.0f, 1.0f));
        //---------------------------------------------


        // Mat4를 uniformModel로 변환한다.
        glUniformMatrix4fv(uniformModel, 1, GL_FALSE, glm::value_ptr(model));

        // 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
3. 삼각형 그리기(2)  (0) 2021.01.17
2. 삼각형 그리기 (1)  (0) 2021.01.14
1. 기본 코드 작성하기  (0) 2021.01.11

댓글