[OpenGL] 3. 삼각형 그리기

WIGWAG·2023년 3월 14일
0

OpenGL

목록 보기
4/10
  • 화면에 삼각형 그리기
    • 화면에 삼각형 사사분면에 4개 그리기
      • 삼각형은 각각 다른 색상 설정
    • 마우스를 누르면 그 위치에 새로운 삼각형 을 그린다.
      • 순서대로 이전에 그린 사각형을 삭제된다.
        • 마우스 클릭 -> 첫번째 삭제되고 삭제되고 마우스 위치에 삼각형
        • 마우스 클릭 -> 두번째 삼각형 삭제되고 마우스 위치에 삼각형
      • 화면에는 항상 4개의 삼각형 만이 그려진다.

  • 회전시키기
    • 키보드 명령 수행
      • f: 도형 그리기 모드 변경 (GL_FILL or GL_LINE)
      • m: 4개의 삼각형이 임의의 방향으로 이동하고 가장자리에 도달하면 방향을 바꿔 계속 이동한다. (단 화면 밖으로 나가지 않는다)
      • s: 멈추기
      • c: 삼각형의 색상 바꾸기
      • q: 프로그램 종료하기

세이더 코드 작성하기

버텍스 세이더(glsl파일)

#version 330 core
		
in vec3 positionAttribute;
in vec3 colorAttribute;
out vec3 passColorAttribute;

void main()
{
	gl_Position = vec4(positionAttribute, 1.0);
	passColorAttribute = colorAttribute;
};

프래그먼트 세이더(glsl파일)

#version 330 core

in vec3 passColorAttribute;
out vec4 fragmentColor;

void main()
{
	fragmentColor = vec4(passColorAttribute, 1.0);
};

세이더 파일 버퍼에 저장하기

char* filetobuf(const char* file)
{
	ifstream in(file, ios_base::binary);

	if (!in)
	{
		cout << file << "파일 못찾음" << endl;
		exit(1);
	}

	in.seekg(0, ios_base::end);
	long len = in.tellg();
	char* buf = new char[len + 1];
	in.seekg(0, ios_base::beg);

	int cnt = -1;
	while (in >> noskipws >> buf[++cnt]){}
	buf[len] = 0;

	return buf;
}

세이더 코드로 세이더 프로그램 생성하기

bool initShaderProgram() {
	//세이더 코드 파일 불러오기
	const GLchar* vertexShaderSource = filetobuf("Resources/VertexShader.glsl");
	const GLchar* fragmentShaderSource = filetobuf("Resources/FragmentShader.glsl");

	//세이더 객체 만들기
	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;
		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;
	}

	//세이더 프로그램 생성
	shaderProgramID = glCreateProgram();
	//세이더 프로그램에 세이더 객체들을 붙이기
	glAttachShader(shaderProgramID, vertexShader);
	glAttachShader(shaderProgramID, fragmentShader);
	//세이더 프로그램 링크
	glLinkProgram(shaderProgramID);

	//세이더 객체 삭제하기
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	//프로그램 상태 가져오기
	glGetProgramiv(shaderProgramID, GL_LINK_STATUS, &result);
	if (!result) {
		glGetProgramInfoLog(shaderProgramID, 512, NULL, errorLog);
		cerr << "ERROR: shader program 연결 실패\n" << errorLog << endl;
		return false;
	}
	//세이더 프로그램 활성화
	glUseProgram(shaderProgramID);

	return true;
}

삼각형 그리기

삼각형 버텍스 위치와 색상 설정

struct Triangle {
	glm::vec3 v[3];
	glm::vec3 color[3];
};

Triangle tri[4] = {
	{ {glm::vec3(0.4f, 0.4f, 0.0f), glm::vec3(0.6f, 0.4f, 0.0f), glm::vec3(0.5f, 0.6f, 0.0f)}, {glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.7f, 0.3f, 0.0f), glm::vec3(0.7f, 0.0f, 0.3f)}},
	{ {glm::vec3(-0.4f, 0.4f, 0.0f), glm::vec3(-0.6f, 0.4f, 0.0f), glm::vec3(-0.5f, 0.6f, 0.0f)}, {glm::vec3(1.0f, 1.0f, 0.0f), glm::vec3(0.7f, 1.0f, 0.0f), glm::vec3(1.0f, 0.7f, 0.0f) }},
	{ {glm::vec3(-0.4f, -0.6f, 0.0f), glm::vec3(-0.6f, -0.6f, 0.0f), glm::vec3(-0.5f, -0.4f, 0.0f)}, {glm::vec3(0.0f, 0.0f, 0.1f), glm::vec3(0.0f, 0.3f, 0.7f), glm::vec3(0.3f, 0.0f, 0.7f) }},
	{ {glm::vec3(0.4f, -0.6f, 0.0f), glm::vec3(0.6f, -0.6f, 0.0f), glm::vec3(0.5f, -0.4f, 0.0f)}, {glm::vec3(0.0f, 1.0f, 1.0f), glm::vec3(0.0f, 0.7f, 1.0f), glm::vec3(0.0f, 1.0f, 0.7f) }}
};

VAO와 VBO 설정하기

bool defineVertexArrayObject() {
	//삼각형을 구성하는 vertex 데이터 - position과 color

	float position[9 * size(tri)];

	for (int cnt = -1, i = 0; i < size(tri); ++i)
	{
		for (int j = 0; j < 3; ++j)
		{
			position[++cnt] = tri[i].v[j].x;
			position[++cnt] = tri[i].v[j].y;
			position[++cnt] = tri[i].v[j].z;
		}
	}

	unsigned int positionIndex[] = {
		0, 1, 2, // 첫 번째 삼각형
		3, 4, 5, // 두 번째 삼각형
		6, 7, 8,
		9, 10, 11
	};

	float color[9 * size(tri)];

	for (int cnt = -1, i = 0; i < size(tri); ++i)
	{
		for (int j = 0; j < 3; ++j)
		{
			color[++cnt] = tri[i].color[j].x;
			color[++cnt] = tri[i].color[j].y;
			color[++cnt] = tri[i].color[j].z;
		}
	}

	//버텍스 배열 오브젝트 (VAO) 이름 생성
	glGenVertexArrays(1, &triangleVertexArrayObject);
	//VAO를 바인드한다.
	glBindVertexArray(triangleVertexArrayObject);

	//Vertex Buffer Object(VBO)를 생성하여 vertex 데이터를 복사한다.

	//버텍스 버퍼 오브젝트 (VBO) 이름 생성
	glGenBuffers(1, &trianglePositionVertexBufferObjectID);
	//버퍼 오브젝트를 바인드 한다.
	glBindBuffer(GL_ARRAY_BUFFER, trianglePositionVertexBufferObjectID);
	//버퍼 오브젝트의 데이터를 생성
	glBufferData(GL_ARRAY_BUFFER, sizeof(position), position, GL_STATIC_DRAW);

	//엘리멘트 버퍼 오브젝트 (EBO) 이름 생성
	glGenBuffers(1, &trianglePositionElementBufferObject);
	//버퍼 오브젝트를 바인드 한다.
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, trianglePositionElementBufferObject);
	//버퍼 오브젝트의 데이터를 생성
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(positionIndex), positionIndex, GL_STATIC_DRAW);

	//위치 가져오기 함수
	GLint positionAttribute = glGetAttribLocation(shaderProgramID, "positionAttribute");
	if (positionAttribute == -1) {
		cerr << "position 속성 설정 실패" << endl;
		return false;
	}
	//버텍스 속성 데이터의 배열을 정의
	glVertexAttribPointer(positionAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);
	//버텍스 속성 배열을 사용하도록 한다.
	glEnableVertexAttribArray(positionAttribute);

	//칼라 버퍼 오브젝트 (VBO) 이름 생성
	glGenBuffers(1, &triangleColorVertexBufferObjectID);
	//버퍼 오브젝트를 바인드 한다.
	glBindBuffer(GL_ARRAY_BUFFER, triangleColorVertexBufferObjectID);
	//버퍼 오브젝트의 데이터를 생성
	glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);

	//위치 가져오기 함수
	GLint colorAttribute = glGetAttribLocation(shaderProgramID, "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;
}

세이더 사용하여 렌더링하기

//--- 콜백 함수: 그리기 콜백 함수 
GLvoid drawScene()
{
	// 바탕색을 설정
	glClearColor(background_rgb.x, background_rgb.y, background_rgb.z, 1.0f);
	// 설정된 색으로 전체를 칠하기
	glClear(GL_COLOR_BUFFER_BIT);

	//만들어진 세이더 프로그램 사용하기
	glUseProgram(shaderProgramID);
	// 사용할 VAO 불러오기
	glBindVertexArray(triangleVertexArrayObject);

	//배열 데이터로부터 프리미티브 렌더링 하기
	glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);
	//glDrawArrays(GL_TRIANGLES, 0, 12);
	// 화면에 출력하기
	glutSwapBuffers();
}

완성코드

#include <GL/glew.h>
#include <GL/freeglut.h>
#include <GL/freeglut_ext.h>

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

#include <iostream>
#include <random>

using namespace std;

random_device seeder;
mt19937 eng(seeder());
uniform_real_distribution<double> dist(0.0f, 1.0f);

const int WIN_X = 10, WIN_Y = 10;
const int WIN_W = 800, WIN_H = 800;
const double RECT_WH = 0.4f;
const double HALFRECT_WH = RECT_WH / 2.0f;

const glm::vec3 background_rgb = glm::vec3(1.0f, 1.0f, 1.0f);

struct Triangle {
	glm::vec3 v[3];
	glm::vec3 color[3];
};

Triangle tri[4] = {
	{ {glm::vec3(0.4f, 0.4f, 0.0f), glm::vec3(0.6f, 0.4f, 0.0f), glm::vec3(0.5f, 0.6f, 0.0f)}, {glm::vec3(1.0f, 0.0f, 0.0f), glm::vec3(0.7f, 0.3f, 0.0f), glm::vec3(0.7f, 0.0f, 0.3f)}},
	{ {glm::vec3(-0.4f, 0.4f, 0.0f), glm::vec3(-0.6f, 0.4f, 0.0f), glm::vec3(-0.5f, 0.6f, 0.0f)}, {glm::vec3(1.0f, 1.0f, 0.0f), glm::vec3(0.7f, 1.0f, 0.0f), glm::vec3(1.0f, 0.7f, 0.0f) }},
	{ {glm::vec3(-0.4f, -0.6f, 0.0f), glm::vec3(-0.6f, -0.6f, 0.0f), glm::vec3(-0.5f, -0.4f, 0.0f)}, {glm::vec3(0.0f, 0.0f, 0.1f), glm::vec3(0.0f, 0.3f, 0.7f), glm::vec3(0.3f, 0.0f, 0.7f) }},
	{ {glm::vec3(0.4f, -0.6f, 0.0f), glm::vec3(0.6f, -0.6f, 0.0f), glm::vec3(0.5f, -0.4f, 0.0f)}, {glm::vec3(0.0f, 1.0f, 1.0f), glm::vec3(0.0f, 0.7f, 1.0f), glm::vec3(0.0f, 1.0f, 0.7f) }}
};

int whatTriRepos = 0;

//bool IsTimerAlive = true;

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

int framebufferWidth, framebufferHeight;
GLuint triangleVertexArrayObject;
GLuint shaderProgramID;
GLuint trianglePositionVertexBufferObjectID, triangleColorVertexBufferObjectID;
GLuint trianglePositionElementBufferObject;

char* filetobuf(const char* file)
{
	FILE* fptr;
	long length;
	char* buf;
	fptr = fopen(file, "rb"); // Open file for reading
	if (!fptr) // Return NULL on failure
	{
		cout << file << "파일 못찾음" << endl;
		exit(1);
	}
	fseek(fptr, 0, SEEK_END); // Seek to the end of the file
	length = ftell(fptr); // Find out how many bytes into the file we are
	buf = (char*)malloc(length + 1); // Allocate a buffer for the entire length of the file and a null terminator
	fseek(fptr, 0, SEEK_SET); // Go back to the beginning of the file
	fread(buf, length, 1, fptr); // Read the contents of the file in to the buffer
	fclose(fptr); // Close the file
	buf[length] = 0; // Null terminator
	return buf; // Return the buffer
}

bool initShaderProgram() {
	//세이더 코드 파일 불러오기
	const GLchar* vertexShaderSource = filetobuf("Resources/VertexShader.glsl");
	const GLchar* fragmentShaderSource = filetobuf("Resources/FragmentShader.glsl");

	//세이더 객체 만들기
	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;
		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;
	}

	//세이더 프로그램 생성
	shaderProgramID = glCreateProgram();
	//세이더 프로그램에 세이더 객체들을 붙이기
	glAttachShader(shaderProgramID, vertexShader);
	glAttachShader(shaderProgramID, fragmentShader);
	//세이더 프로그램 링크
	glLinkProgram(shaderProgramID);

	//세이더 객체 삭제하기
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	//프로그램 상태 가져오기
	glGetProgramiv(shaderProgramID, GL_LINK_STATUS, &result);
	if (!result) {
		glGetProgramInfoLog(shaderProgramID, 512, NULL, errorLog);
		cerr << "ERROR: shader program 연결 실패\n" << errorLog << endl;
		return false;
	}
	//세이더 프로그램 활성화
	glUseProgram(shaderProgramID);

	return true;
}

bool defineVertexArrayObject() {
	//삼각형을 구성하는 vertex 데이터 - position과 color

	float position[9 * size(tri)];

	for (int cnt = -1, i = 0; i < size(tri); ++i)
	{
		for (int j = 0; j < 3; ++j)
		{
			position[++cnt] = tri[i].v[j].x;
			position[++cnt] = tri[i].v[j].y;
			position[++cnt] = tri[i].v[j].z;
		}
	}

	unsigned int positionIndex[] = {
		0, 1, 2, // 첫 번째 삼각형
		3, 4, 5, // 두 번째 삼각형
		6, 7, 8,
		9, 10, 11
	};

	float color[9 * size(tri)];

	for (int cnt = -1, i = 0; i < size(tri); ++i)
	{
		for (int j = 0; j < 3; ++j)
		{
			color[++cnt] = tri[i].color[j].x;
			color[++cnt] = tri[i].color[j].y;
			color[++cnt] = tri[i].color[j].z;
		}
	}

	//버텍스 배열 오브젝트 (VAO) 이름 생성
	glGenVertexArrays(1, &triangleVertexArrayObject);
	//VAO를 바인드한다.
	glBindVertexArray(triangleVertexArrayObject);

	//Vertex Buffer Object(VBO)를 생성하여 vertex 데이터를 복사한다.

	//버텍스 버퍼 오브젝트 (VBO) 이름 생성
	glGenBuffers(1, &trianglePositionVertexBufferObjectID);
	//버퍼 오브젝트를 바인드 한다.
	glBindBuffer(GL_ARRAY_BUFFER, trianglePositionVertexBufferObjectID);
	//버퍼 오브젝트의 데이터를 생성
	glBufferData(GL_ARRAY_BUFFER, sizeof(position), position, GL_STATIC_DRAW);

	//엘리멘트 버퍼 오브젝트 (EBO) 이름 생성
	glGenBuffers(1, &trianglePositionElementBufferObject);
	//버퍼 오브젝트를 바인드 한다.
	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, trianglePositionElementBufferObject);
	//버퍼 오브젝트의 데이터를 생성
	glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(positionIndex), positionIndex, GL_STATIC_DRAW);

	//위치 가져오기 함수
	GLint positionAttribute = glGetAttribLocation(shaderProgramID, "positionAttribute");
	if (positionAttribute == -1) {
		cerr << "position 속성 설정 실패" << endl;
		return false;
	}
	//버텍스 속성 데이터의 배열을 정의
	glVertexAttribPointer(positionAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);
	//버텍스 속성 배열을 사용하도록 한다.
	glEnableVertexAttribArray(positionAttribute);

	//칼라 버퍼 오브젝트 (VBO) 이름 생성
	glGenBuffers(1, &triangleColorVertexBufferObjectID);
	//버퍼 오브젝트를 바인드 한다.
	glBindBuffer(GL_ARRAY_BUFFER, triangleColorVertexBufferObjectID);
	//버퍼 오브젝트의 데이터를 생성
	glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);

	//위치 가져오기 함수
	GLint colorAttribute = glGetAttribLocation(shaderProgramID, "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;
}

//--- 콜백 함수: 그리기 콜백 함수 
GLvoid drawScene()
{
	// 바탕색을 설정
	glClearColor(background_rgb.x, background_rgb.y, background_rgb.z, 1.0f);
	// 설정된 색으로 전체를 칠하기
	glClear(GL_COLOR_BUFFER_BIT);

	//만들어진 세이더 프로그램 사용하기
	glUseProgram(shaderProgramID);
	// 사용할 VAO 불러오기
	glBindVertexArray(triangleVertexArrayObject);

	//배열 데이터로부터 프리미티브 렌더링 하기
	glDrawElements(GL_TRIANGLES, 12, GL_UNSIGNED_INT, 0);
	//glDrawArrays(GL_TRIANGLES, 0, 12);
	// 화면에 출력하기
	glutSwapBuffers();
}

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

void Mouse(int button, int state, int x, int y)
{
	GLfloat half_w = WIN_W / 2.0f;
	if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
		mx = (x - half_w) / half_w;
		my = (half_w - y) / half_w;

		tri[whatTriRepos].v[0] = glm::vec3(mx - 0.1f, my - 0.1f, 0.0f); 
		tri[whatTriRepos].v[1] = glm::vec3(mx + 0.1f, my - 0.1f, 0.0f);
		tri[whatTriRepos].v[2] = glm::vec3(mx, my + 0.1f, 0.0f);

		whatTriRepos = ++whatTriRepos % 4;
	}
	defineVertexArrayObject();
	glutPostRedisplay();
}

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

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

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

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

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

실행화면

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

0개의 댓글