[OpenGL] 5. OpenGL 프레임워크 만들기, 뷰잉 변환과 투영 변환 적용

WIGWAG·2023년 3월 29일
0

OpenGL

목록 보기
6/10
  • 로봇 그리기
    • 육면체를 사용하여 무대를 그리고, 키보드를 누르면 무대의 앞면이 위로 슬라이딩하여 열린다.
      • 무대는 육면을 가지고 있다. 각각 다른색으로 구현한다.
      • 육면은 각각 사각형으로 그려져 있고, 뒷면 제거를 적용한다 (GL_CULL_FACE)
      • o/O: 앞면이 올라간다.
    • 무대 위에는 로봇이 팔을 흔들며 걷고 있다.
      • 로봇은 머리, 몸통, 양 팔, 양 다리가 있고 팔, 다리를 흔들며 걷는다.
      • 머리의 앞면에 코를 붙이고(앞뒤 구분), 양팔과 양 다리는 다른색을 사용한다.(방향 구분)
    • 키보드 명령어를 사용하여 좌우로 이동 방향을 움직인다.
      • w/a/s/d: 로봇이 앞/뒤/좌/우 방향으로 이동 방향을 바꿔서 걷는다. 가장자리에 도달하면 반대방향으로 방향을 바꿔 걷는다.
      • j/J: 로봇이 제자리에서 점프한다.
      • i/I: 모든 변환을 리셋하고 다시 시작
      • q/Q: 프로그램 종료
    • 카메라 설정: 키보드 명령으로 카메라 이동
      • z/Z: 앞뒤로 이동
      • y/Y: 카메라 기준 y축에 대하여 자전
      • r/R: 카메라가 화면의 중심 y축을 기준으로 공전

뷰잉 변환

월드 좌표계를 유저의 시점인 view space로 변환한다.

뷰잉변환함수
glm::mat4 glm::lookAt (vec3 const &cameraPos, vec3 const &cameraDirection, vec3 const &cameraUp);

  • cameraPos: 카메라의 위치
  • cameraDirection: 카메라가 바라보는 기준점
  • cameraUp: 카메라의 상단이 가리키는 방향

투영 변환

객체가 놓이는 공간 설정을 설정한다.

원근투영함수
glm:: mat4 glm::perspective (float fovy, float aspect, float near, float far);

  • fovy: 뷰잉 각도(라디언), 뷰잉 공간이 얼마나 큰지를 설정
  • aspect: 종횡비 (앞쪽의 클리핑 평면의 폭(w)을 높이(h)로 나눈 값)
  • 종횡비: 화면의 가로방향에 대한 단위 길이를 나타내는 픽셀수에 대한 세로방향의 단위길이를 나타내는 픽셀 수의 비율.
    예) 종횡비가 0.5: 가로길이의 두 픽셀이 세로길이의 한 픽셀에 대응한다.
  • near: 관측자에서부터 가까운 클리핑 평면까지의 거리 (항상 양의 값)
  • far: 관측자에서 먼 클리핑 평면까지의 거리 (항상 양의 값)

버텍스 세이더 코드 수정

#version 330 core
layout (location = 0) in vec3 vPos;
layout (location = 1) in vec3 vColor;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

out vec3 passColor;

void main()
{
	gl_Position = projection * view * model * vec4(vPos, 1.0); 
	passColor = vColor;
}

프레임워크 제작

ShaderManger클래스

// 세이더 파일 코드를 버퍼에 저장한다.
char* File_To_Buf(const char* file);
// 세이더 프로그램을 생성한다.
bool Init_Program(GLuint& shader_program_ID);

Shape클래스

// 오브젝트를 색칠한다.
virtual void Color();
// 오브젝트의 색상 데이터를 수정한다.
virtual void Set_Colors(vector<glm::vec3> colors);
// 오브젝트 파일의 버텍스 속성정보를 버퍼에 저장한다.
bool  loadOBJ(const char* path);
// VAO와 VBO를 설정한다.
virtual bool Init_VAO(GLuint shader_program_ID);

Cube클래스(Shape클래스를 상속함)

// 오브젝트를 색칠한다.(오버라이딩)
virtual void Color();

Stage클래스(Shape클래스를 상속함)

//Stage를 제작하고 화면에 그린다.
void Init_And_Render(int model)
// 오브젝트를 색칠한다.(오버라이딩)
virtual void Color();

Face클래스(Shape클래스를 상속함)

//Face를 제작하고 화면에 그린다.
void Init_And_Render(int model)
// VAO와 VBO를 설정한다.(오버라이딩)
virtual bool Init_VAO(GLuint shader_program_ID);

Robot클래스

//Robot을 제작하고 화면에 그린다.
void Init_And_Render(int model)
//왼쪽 다리을 제작하고 화면에 그린다.
Init_And_Render_Left_Leg(model);
//오른쪽 다리을 제작하고 화면에 그린다.
Init_And_Render_Right_Leg(model);
//몸통을 제작하고 화면에 그린다.
Init_And_Render_Torso(model);
//왼쪽 팔을 제작하고 화면에 그린다.
Init_And_Render_Left_Arm(model);
//오른쪽 팔을 제작하고 화면에 그린다.
Init_And_Render_Right_Arm(model);
//머리을 제작하고 화면에 그린다.
Init_And_Render_Head(model);
//코를 제작하고 화면에 그린다.
Init_And_Render_Nose(model);

Camera클래스

//뷰잉 행렬을 설정한다.
void Init_View(GLuint shader_program_ID)
//투영 행렬을 설정한다.
void Init_Projection(GLuint shader_program_ID)

Stage클래스의 Init_And_Render함수

void Init_And_Render(int model) {
    glm::mat4 m_transform = glm::mat4(1.0f);
    //이동 변환
    m_transform = glm::translate(m_transform, glm::vec3(0.0, 0.0, 0.0));
    //스케일 변환
    m_transform = glm::scale(m_transform, glm::vec3(5.0, 5.0, 5.0));
    // uniform 변수에 모델링변환 행렬 주소를 저장한다.
    glUniformMatrix4fv(model, 1, GL_FALSE, glm::value_ptr(m_transform));

    glBindVertexArray(VAO_ID);
    //렌더링
    glDrawArrays(GL_TRIANGLES, 0, NUMOF_VERTICES);
}

Camera클래스의 Init_View함수

void Init_View(GLuint shader_program_ID) {
	//뷰 행렬 설정
	v_transform = glm::lookAt(cameraPos, cameraDirection, cameraUp); 

	//회전 변환(카메라 공전)
	v_transform = glm::rotate(v_transform, glm::radians(rotation), glm::vec3(0.0, 1.0, 0.0));
    //이동 변환
	v_transform = glm::translate(v_transform, glm::vec3(0.0, 0.0, -10.0f + z_move));
    //회전 변환(카메라 자전)
	v_transform = glm::rotate(v_transform, glm::radians(revolution), glm::vec3(0.0, 1.0, 0.0));

	// 프로그램에서 uniform 변수의 위치를 가져온다
	int view = glGetUniformLocation(shader_program_ID, "view");
    // uniform 변수에 뷰잉변환 행렬 주소를 저장한다.
	glUniformMatrix4fv(view, 1, GL_FALSE, glm::value_ptr(v_transform));
}

main.cpp

#include "stdafx.h"
#include "Shape.h"
#include "ShaderManager.h"
#include "Stage.h"
#include "Face.h"
#include "Camera.h"
#include "Robot.h"

GLvoid drawScene(GLvoid);
GLvoid Reshape(int w, int h);
GLvoid Keyboard(unsigned char key, int x, int y);
GLvoid TimerFunction1(int value);
GLvoid TimerFunction2(int value);
GLvoid TimerFunction3(int value);
GLvoid Mouse(int button, int state, int x, int y);

GLfloat mx = 0.0f;
GLfloat my = 0.0f;

GLuint shader_program_ID;

Stage stage;
Face face;
ShaderManager sm;
Camera cam;
Robot robot;

bool isTimer1On = false;
bool isTimer2On = false;
bool isTimer3On = false;

bool isAllStop = false;

GLvoid drawScene() //--- 콜백 함수: 그리기 콜백 함수 
{
    //--- 변경된 배경색 설정
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // 바탕색을 변경
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 설정된 색으로 전체를 칠하기
    glFrontFace(GL_CCW);
    glEnable(GL_DEPTH_TEST);
    //glEnable(GL_CULL_FACE);

    glUseProgram(shader_program_ID);

    cam.Init_View(shader_program_ID);
    cam.Init_Projection(shader_program_ID);

    int model = glGetUniformLocation(shader_program_ID, "model");
    stage.Init_And_Render(model);
    face.Init_And_Render(model);
    robot.Init_And_Render(model);

    glutSwapBuffers();
    isAllStop = false;
}

GLvoid Reshape(int w, int h) //--- 콜백 함수: 다시 그리기 콜백 함수 
{
    WIN_W = (float)w;
    WIN_H = (float)h;
    glViewport(0, 0, w, h);
}

GLvoid Keyboard(unsigned char key, int x, int y)
{
    switch (key) {
    case 'o':    case 'O':
        if(!isTimer1On)
            glutTimerFunc(100, TimerFunction1, 1);
        isTimer1On = true;
        break;
    case 'w':
        robot.dir = FRONT;
        robot.y_rotate = 0.0f;
        if (!isTimer2On)
            glutTimerFunc(100, TimerFunction2, 1);
        isTimer2On = true;
        break;
    case 'a':
        robot.dir = LEFT;
        robot.y_rotate = -90.0f;
        if (!isTimer2On)
            glutTimerFunc(100, TimerFunction2, 1);
        isTimer2On = true;
        break;
    case 's':
        robot.dir = BACK;
        robot.y_rotate = 180.0f;
        if (!isTimer2On)
            glutTimerFunc(100, TimerFunction2, 1);
        isTimer2On = true;
        break;
    case 'd':
        robot.dir = RIGHT;
        robot.y_rotate = 90.0f;
        if (!isTimer2On)
            glutTimerFunc(100, TimerFunction2, 1);
        isTimer2On = true;
        break;
    case 'j':    case 'J':
        if (robot.jump == LAND)
            robot.jump = UP;
        if (!isTimer3On)
            glutTimerFunc(100, TimerFunction3, 1);
        isTimer3On = true;
        break;
    case 'i':    case 'I':
        face.y_move = 0.0;
        robot.x_move = 0.0;
        robot.y_move = 0.0;
        robot.z_move = 0.0;
        robot.dir = STOP;
        isAllStop = true;
        isTimer1On = false;
        isTimer2On = false;
        isTimer3On = false;
        break;
    case 'q':
        exit(1);
        break;
    case 'z':
        //카메라 전진
        cam.z_move += 0.1f;
        break;
    case 'Z':
        //카메라 후진
        cam.z_move -= 0.1f;
        break;
    case 'y':    case 'Y':
        //카메라 자전
        cam.rotation += 5.0f;
        break;
    case 'r':    case 'R':
        //카메라 공전
        cam.revolution += 5.0f;
        break;
    }
    glutPostRedisplay(); //--- 배경색이 바뀔때마다 출력 콜백함수를 호출하여 화면을 refresh 한다
}

//앞면이 올라가고 로봇이 팔다리를 흔듦
GLvoid TimerFunction1(int value)
{
    if (isAllStop) {
        face.y_move -= face.move_rate;
        return;
    }

    if (face.y_move < 5.0)
        face.y_move += face.move_rate;

    if (robot.x_rotate > 30.0f or robot.x_rotate < -30.0f)
        robot.is_forward ^= 1;

    if (robot.is_forward)
        robot.x_rotate += robot.rotate_rate;
    else
        robot.x_rotate -= robot.rotate_rate;

    glutPostRedisplay(); // 화면 재 출력
    if (isTimer1On)
        glutTimerFunc(100, TimerFunction1, 1);
}

//앞뒤좌우로 걷는다.
GLvoid TimerFunction2(int value)
{
    if (robot.dir == FRONT) {
        if (robot.z_move < 2.3)
            robot.z_move += robot.move_rate;
        else {
            robot.dir = BACK;
            robot.y_rotate = 180.0f;
        }
    }
    else if (robot.dir == BACK) {
        if (robot.z_move > -2.3)
            robot.z_move -= robot.move_rate;
        else {
            robot.dir = FRONT;
            robot.y_rotate = 0.0f;
        }
    }
    else if (robot.dir == LEFT) {
        if (robot.x_move > -2.2)
            robot.x_move -= robot.move_rate;
        else {
            robot.dir = RIGHT;
            robot.y_rotate = 90.0f;
        }
    }
    else if (robot.dir == RIGHT) {
        if (robot.x_move < 2.2)
            robot.x_move += robot.move_rate;
        else {
            robot.dir = LEFT;
            robot.y_rotate = -90.0f;
        }
    }

    glutPostRedisplay(); // 화면 재 출력
    if (!isAllStop && isTimer2On)
        glutTimerFunc(100, TimerFunction2, 1);
}

//점프
GLvoid TimerFunction3(int value)
{
    if (robot.jump == UP) {
        if (robot.y_move < 0.4)
            robot.y_move += robot.move_rate;
        else
            robot.jump = DOWN;
    }

    if (robot.jump == DOWN) {
        if (robot.y_move >= 0.1)
            robot.y_move -= robot.move_rate;
        else
            robot.jump = LAND;
    }

    glutPostRedisplay(); // 화면 재 출력
    if (!isAllStop && isTimer3On)
        glutTimerFunc(100, TimerFunction3, 1);
}

GLvoid Mouse(int button, int state, int x, int y)
{
    if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
    }
    glutPostRedisplay();
}

int main(int argc, char** argv) //--- 윈도우 출력하고 콜백함수 설정 
{   //--- 윈도우 생성하기
    glutInit(&argc, argv); // glut 초기화
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH); // 디스플레이 모드 설정
    glutInitWindowPosition(WIN_X, WIN_Y); // 윈도우의 위치 지정
    glutInitWindowSize((int)WIN_W, (int)WIN_H); // 윈도우의 크기 지정
    glutCreateWindow("Example1"); // 윈도우 생성 (윈도우 이름)

    //--- GLEW 초기화하기
    glewExperimental = GL_TRUE;
    if (glewInit() != GLEW_OK) // glew 초기화
    {
        std::cerr << "Unable to initialize GLEW" << std::endl;
        exit(EXIT_FAILURE);
    }
    else
        std::cout << "GLEW Initialized\n";

    if (!sm.Init_Program(shader_program_ID)) {
        cerr << "Error: Shader Program 생성 실패" << endl;
        std::exit(EXIT_FAILURE);
    }

    stage.Color();
    if (!stage.Init_VAO(shader_program_ID)) {
        cerr << "Error: stage 생성 실패" << endl;
        std::exit(EXIT_FAILURE);
    }

    if (!face.Init_VAO(shader_program_ID)) {
        cerr << "Error: 앞면 생성 실패" << endl;
        std::exit(EXIT_FAILURE);
    }

    for (size_t i = 0; i < Robot::MAX_BODIES; i++)
    {
        robot.bodies[i].Color();

        if (!robot.bodies[i].Init_VAO(shader_program_ID)) {
            cerr << "Error: 큐브 생성 실패" << endl;
            std::exit(EXIT_FAILURE);
        }
    }


    glutDisplayFunc(drawScene); // 출력 함수의 지정
    glutReshapeFunc(Reshape); // 다시 그리기 함수 지정
    glutKeyboardFunc(Keyboard);
    glutMouseFunc(Mouse);
    glutMainLoop(); // 이벤트 처리 시작 
}

실행화면

profile
윅왁의 프로그래밍 개발노트

0개의 댓글