[컴퓨터그래픽스: 부록] 과제1: Waving_Plane

Serun1017·2024년 10월 23일
0

컴퓨터그래픽스

목록 보기
31/31

과제 1_Waving Plane

본 과제는 2024년 2학기 세종대학교 컴퓨터그래픽스(박상일 교수) 과제 1 입니다.

Condition

  • Draw a checker pattern plain (create a "MyPlain" class)
  • Draw waving pattern when [w] key is pressed using only shader
  • Start rotating and "WAVING" when space bar is pressed
  • Increase or decrease the Number of Division when pressing [1] or [2] key.
  • Stop when space bar is pressed again.
  • Quit when [q] is pressed

예시

Example_1.png

Example_2.png

개발

방법 1

MyPlain.h

먼저 MyPlain Class 를 선언한다.

class MyPlain {
protected:
	unsigned int DIVISION_NUM;
	unsigned int NUM_VERTEX;
	float X_SIZE;
	float Y_SIZE;
	bool is_init;

	const vec4 GRAY = vec4(0.745098, 0.745098, 0.745098, 1);
	const vec4 LIGHT_GRAY = vec4(0.827451, 0.827451, 0.827451, 1);

	vec4* vertex;
	vec4* colors;
	GLuint vao;
	GLuint vbo;
}

생성자

MyPlain의 생성자는 화면상에 표시될 평면의 사이즈(x_size, y_size)를 생성자로 받아 초기화한다. 이때 x_size, y_size0 ~ 1 사이의 float 값으로, 각각 평면의 절반 만큼의 크기를 나타낸다.

MyPlain(float x_size, float y_size) {
	DIVISION_NUM = 2;
	NUM_VERTEX = DIVISION_NUM * DIVISION_NUM * 2 * 3;

	X_SIZE = x_size;
	Y_SIZE = y_size;

	vao = NULL;
	vbo = NULL;
}

init

init 메소드에선 division_num 을 입력 받아 초기화 한 후, generate_plain() 에서vertex data를 생성한다. 이후 init 메소드에서 vertex data를 buffer에 저장한다.

void init(int division_num) {
	DIVISION_NUM = division_num;
	NUM_VERTEX = DIVISION_NUM * DIVISION_NUM * 2 * 3;

	vertex = new vec4[NUM_VERTEX];
	colors = new vec4[NUM_VERTEX];

	generate_plain();
	glGenVertexArrays(1, &vao);
	glBindVertexArray(vao);

	glGenBuffers(1, &vbo);
	glBindBuffer(GL_ARRAY_BUFFER, vbo);

	glBufferData(GL_ARRAY_BUFFER, sizeof(vec4) * NUM_VERTEX, vertex, GL_STATIC_DRAW);
	glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vec4) * NUM_VERTEX, vertex);
	glBufferSubData(GL_ARRAY_BUFFER, sizeof(vec4) * NUM_VERTEX, sizeof(vec4) * NUM_VERTEX, colors);

	delete[] vertex;
	delete[] colors;
}

generate_plain

generate_plain 메소드에선 plain의 vertex data를 생성하고 저장한다.

rect_points 2차원 배열에 plain 내의 점 좌표를 계산하여 넣는다. 이 후 rect_points 배열의 점 좌표들을 이용해 삼각형을 만들어 vertex data를 생성하고 이들을 vertex 배열과 colors 배열에 저장한다.

void generate_plain() {
	vec4** rect_points = new vec4*[DIVISION_NUM + 1];
	
	for (int i = 0; i < DIVISION_NUM + 1; i++)
		rect_points[i] = new vec4[DIVISION_NUM + 1];

	// set rectangle points coordinate
	const float division_x_size = (X_SIZE * 2) / DIVISION_NUM;
	const float division_y_size = (Y_SIZE * 2) / DIVISION_NUM;

	for (int i = 0; i < DIVISION_NUM + 1; i++)
		for (int j = 0; j < DIVISION_NUM + 1; j++)
			rect_points[i][j] = vec4(-X_SIZE + j * division_x_size, -Y_SIZE + i * division_y_size, 0, 1);

	// divide rectangle points to triangle points
	for (int i = 0, vNum = 0; i < DIVISION_NUM; i++) {
		for (int j = 0; j < DIVISION_NUM; j++) {
			vec4 rect[] = {rect_points[i][j], rect_points[i][j + 1], rect_points[i + 1][j + 1], rect_points[i + 1][j] };
			vec4* triangle;

			// divide rect->triangle
			triangle = divide_rect(rect);

			// save vertex
			for (int k = 0; k < 6; k++) 
				vertex[vNum++] = triangle[k];

			// save color
			if ((i + j) % 2 == 0) {
				for (int j = 0; j < 6; j++) {
					colors[vNum] = GRAY; vNum++;
				}
			}
			else {
				for (int j = 0; j < 6; j++) {
					colors[vNum] = LIGHT_GRAY; vNum++;
				}
			}
		}
	}

	// delete points
	for (int i = 0; i < DIVISION_NUM + 1; i++)
		delete[] rect_points[i];
	delete[] rect_points;
}

divide_rect

divide_rect 메소드는 사각형의 네 꼭짓점을 입력받아 이를 2개의 삼각형으로 나누어 삼각형의 꼭짓점 좌표 6개를 배열로 반환한다.

vec4* divide_rect(vec4* rect) {
	vec4 triangle[] = { rect[0], rect[1], rect[3],
						 rect[2], rect[3], rect[1] };
	return triangle;
}

그 외

void draw(GLuint program) {
	glBindVertexArray(vao);
	connect_shader(program);
	glDrawArrays(GL_TRIANGLES, 0, NUM_VERTEX);
}
// shader 연결
void connect_shader(GLuint program) {
	GLuint vPosition = glGetAttribLocation(program, "vPosition");
	glEnableVertexAttribArray(vPosition);
	glVertexAttribPointer(vPosition, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

	GLuint vColor = glGetAttribLocation(program, "vColor");
	glEnableVertexAttribArray(vColor);
	glVertexAttribPointer(vColor, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(sizeof(vec4) * NUM_VERTEX));
}

unsigned int get_division_num() {
	return DIVISION_NUM;
}
unsigned int get_triangle_num() {
	return NUM_VERTEX / 3;
}
unsigned int get_vertex_num() {
	return NUM_VERTEX;
}

main.cpp

main

main 함수에서 initialize 함수를 호출하여 plain을 초기화 한다. 이후 display, idle, processNormalKey를 각각 callback 함수로 등록한다.

GLuint program;
MyPlain *plain;

bool is_waving = false;
bool is_rotating = false;

const float X_SIZE = 0.5;
const float Y_SIZE = 0.5;
unsigned int DIVISION_NUM = 30;
float myTime = 0;

int main(int argc, char** argv) {
	glutInit(&argc, argv);

	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA | GLUT_DEPTH);
	glutInitWindowSize(800, 800);
	glutCreateWindow("A Waving Plain");

	glewExperimental = true;
	glewInit();

	printf("A Waving Color Plain\n");
	printf("Programming Assignment #1 for Computer Graphics\n");
	printf("Heo-JinSu, 19011625, Department of Software, Sejong University\n");

	printf("\n------------------------------------------------------------\n");
	printf("'1' key: Decreasing the Number of Division\n");
	printf("'2' key: Increasing the Number of Division\n");
	printf("'w' key: Showing/Hiding the waving pattern\n");
	printf("Spacebar: Starting/Stoping rotating and waving\n");
	printf("\n'Q' key: Exit the program.\n");
	printf("\n------------------------------------------------------------\n");

	initialize();

	glutDisplayFunc(display);
	glutIdleFunc(idle);
	glutKeyboardFunc(processNormalKey);

	glutMainLoop();

	delete plain;
	return 0;
}

initialize

initialize 함수에선 plainX_SIZE, Y_SIZE로 초기화 한 후 plaininit 메소드를 호출하여 vertex data를 buffer에 저장한다.

이후 shader 를 호출한다.

void initialize() {
	plain = new MyPlain(X_SIZE, Y_SIZE);
	plain->init(DIVISION_NUM);

	program = InitShader("vshader.glsl", "fshader.glsl");
	glUseProgram(program);
}

display

display 함수에선 shader에 unifrom 으로 등록된 uTime, u_is_waving, x_size, y_size, division_num 을 연결하고, plaindraw 메소드를 호출하여 화면에 렌더링한다.


void display() {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_DEPTH_TEST);

	glUseProgram(program);

	// Time
	GLuint uTime = glGetUniformLocation(program, "uTime");
	glUniform1f(uTime, myTime);

	// Waving
	GLuint u_is_waving = glGetUniformLocation(program, "u_is_waving");
	glUniform1i(u_is_waving, is_waving);

	// Size
	GLuint x_size = glGetUniformLocation(program, "u_x_size");
	glUniform1f(x_size, X_SIZE);

	GLuint y_size = glGetUniformLocation(program, "u_y_size");
	glUniform1f(y_size, Y_SIZE);

	// Division Number
	GLuint division_num = glGetUniformLocation(program, "u_division_num");
	glUniform1i(division_num, DIVISION_NUM);

	plain->draw(program);
	glFlush();
}

idle

idle 함수는 프로그램이 실행될 때 지속적으로 실행되는 callback 함수로 plane이 회전하도록 설정되어있으면 30 frame 마다 화면을 새로고침 한다.

void idle() {
	if (is_rotating) {
		myTime += 0.0333f;
		Sleep(33);

		glutPostRedisplay();
	}
}

processNormalKey

processNormalKey는 키보드 입력을 받는 callback 함수로, 다음과 같이 정의한다.
q 를 입력할 시 프로그램이 종료된다.
1 을 입력할 시 새로운 plain 객체를 생성하고 DIVISION_NUM을 1만큼 줄여 새로고침 한다.
2를 입력할 시 새로운 plain 객체를 생성하고 DIVISION_NUM을 1만큼 늘려 새로고침 한다.
w를 입력할 시 is_waving 의 부호를 변경하고 새로고침 한다.
spacebar를 입력할 시 is_rotating의 부호를 변경한다.

void processNormalKey(unsigned char key, int x, int y) {
	if (key == 'q') {
		exit(0);
	}
	else if (key == '1') {
		// minimum division num = 2
		if (DIVISION_NUM > 2) {
			DIVISION_NUM--;
			
			delete plain;
			plain = new MyPlain(X_SIZE, Y_SIZE);
			plain->init(DIVISION_NUM);

			printf("Division: %d, Num.of Triangles: %d, Num. of Vertices: %d\n", plain->get_division_num(),
																				 plain->get_triangle_num(),
																				 plain->get_vertex_num());
		} 
		glutPostRedisplay();
	}
	else if (key == '2') {
		DIVISION_NUM++;
		
		delete plain;
		plain = new MyPlain(X_SIZE, Y_SIZE);
		plain->init(DIVISION_NUM);

		printf("Division: %d, Num.of Triangles: %d, Num. of Vertices: %d\n", plain->get_division_num(),
																			 plain->get_triangle_num(),
																			 plain->get_vertex_num());
		glutPostRedisplay();
	}
	else if (key == 'w') {
		is_waving = !is_waving;
		glutPostRedisplay();
	}
	else if (key == ' ') {
		is_rotating = !is_rotating;
	}
}

vshader.glsl

vshader.glsl 은 vertex shader로 vPositionvColorin 으로 받고 uTime, u_is_waving, u_x_size, u_y_sizeuniform으로 받는다.

position.zu_is_wavingtrue일 때 새로 계산하며 아래와 같이 계산한다.

float distance = sqrt(pow(position.x, 2) + pow(position.y, 2));
float speed = 5;
if (distance < (u_x_size > u_y_size ? u_y_size : u_x_size) && u_is_waving) {
	position.z = sin(10 * distance * PI - (uTime*speed)) * ((u_x_size > u_y_size ? u_y_size : u_x_size) - distance);
}

position.z-1 ~ +1사이의 값이므로 이는 sin 함수로 만들 수 있다. 이때 z 값은 x, y 로 계산되는 distanceuTime에 따라 변한다.

먼저 중심으로부터 distance 에 따라 z의 값이 다르므로 sin(10 * distance * PI)로 나타낼 수 있다. 이때 10은 주파수를 높이기 위해 설정한 임의의 상수이다.

또한 sin 값은 uTime에 의해 변하므로 이를 반영하면 sin(10 * distance * PI - (uTime * speed)) 이 된다.

또한 z는 중심으로부터 distance가 멀어질수록 낮아지므로 size - distance에 비례한다. 따라서 이를 추가하면 position.z = sin(10 * distance * PI - (uTime*speed)) * ((u_x_size > u_y_size ? u_y_size : u_x_size) - distance) 이 된다.

마지막으로 x-rotation과 z-rotation을 적용한다.

vshader.glsl

#version 330

uniform float uTime;
uniform bool u_is_waving;
uniform float u_x_size;
uniform float u_y_size;

in vec4 vPosition;
in vec4 vColor;

out vec4 position;
out vec4 color;

void main() {
	const float PI = 3.141592f;

	position = vPosition;
	color = vColor;

	float distance = sqrt(pow(position.x, 2) + pow(position.y, 2));
	float speed = 5;
	if (distance < (u_x_size > u_y_size ? u_y_size : u_x_size) && u_is_waving) {
		position.z = sin(10 * distance * PI - (uTime*speed)) * ((u_x_size > u_y_size ? u_y_size : u_x_size) - distance);
	}

	float x_ang = 45 / 180.0f * PI;
	float z_ang = 20 * uTime / 180.0f * PI;

	mat4 x_rotate = mat4(1.0f);
	mat4 z_rotate = mat4(1.0f);

	x_rotate[1][1] = cos(x_ang);
	x_rotate[2][1] = -sin(x_ang);
	x_rotate[1][2] = sin(x_ang);
	x_rotate[2][2] = cos(x_ang);

	z_rotate[0][0] = cos(z_ang);
	z_rotate[1][0] = -sin(z_ang);
	z_rotate[0][1] = sin(z_ang);
	z_rotate[1][1] = cos(z_ang);

	if (uTime > 0)
		gl_Position = x_rotate * z_rotate * position;
	else
		gl_Position = x_rotate * position;
}

fshader.glsl

fshader.glsl은 fragment shader로 positioncolorin으로 받는다.
fColorcolor로 초기화한 후, position.z.의 높이에 따라 색을 더한다.

if (position.z > 0.01f) {
	fColor += BLUE * position.z;
	fColor.a = 1;
}
else if (position.z < -0.01f) {
	fColor -= RED * position.z;
	fColor.a = 1;
}

여기서 RED 색을 더할 때 -를 사용한 이유는 position.z 값이 음수이기 때문이다.

fshader.glsl

#version 330

uniform float u_x_size;
uniform float u_y_size;

uniform int u_division_num;

in vec4 position;
in vec4 color;

out vec4 fColor;

void main() {
	const vec4 RED = vec4(1, 0, 0, 1);
	const vec4 BLUE = vec4(0, 0, 1, 1);
	
	fColor = color;
	
	if (position.z > 0.01f) {
		fColor += BLUE * position.z;
		fColor.a = 1;
	}
	else if (position.z < -0.01f) {
		fColor -= RED * position.z;
		fColor.a = 1;
	}
}

결과 및 문제점

Solution_1_result.png

이 방법으로 했을 때 예제와 같은 결과를 보여줄 수 있다. 그러나 아래 그림과 같이 오른쪽 아래와 왼쪽 위의 대각선 표현이 부드럽지 않다는 문제가 있다. 이를 해결하기 위해 방법 2를 고안했다.

Solution_1_problem.png

방법 2

방법 1에서 아래 그림과 같은 결과를 얻은 가장 큰 이유는 사각형을 삼각형으로 나눈 기준 때문이다. 사각형을 왼쪽 위에서 오른쪽 아래로 나누었기 때문에 그와 반대되는 방향의 대각선을 표현하기 위해서는 두 삼각형이 접힌 것 처럼 표현된다.

Solution_1_problem.png

이를 해결하기 위해 사각형의 위치에 따라 반대 방향(왼쪽 아래에서 오른쪽 위)으로 사각형을 나누는 과정을 추가했다.

generate_plain

generate_plain 메소드의 사각형을 삼각형으로 나누는 부분을 아래와 같이 수정한다.

// divide rect->triangle
if ((rect_points[i][j].x < 0 && rect_points[i][j].y < 0) || (rect_points[i][j].x > 0 && rect_points[i][j].y > 0))
				triangle = divide_rect(rect, true);
else
				triangle = divide_rect(rect, false);

divide_rect

divide_rect 메소드를 다음과 같이 수정한다.

vec4* divide_rect(vec4* rect, bool left) {
	if (left) {
		vec4 triangle[] = { rect[0], rect[1], rect[3],
							 rect[2], rect[3], rect[1] };
		return triangle;
	}
	else {
		vec4 triangle[] = { rect[0], rect[1], rect[2],
							 rect[2], rect[3], rect[0] };
		return triangle;
	}
}

결과

방법 1 보다 더 부드럽게 표현되는 것을 볼 수 있다.

Solution_2_result_1.png

Solution_2_result_2.png

방법 3

방법 1과 방법 2에서는 모두 colorMyPlain에서 계산한 후 shader에 보냈다. 그러나 plain의 색상이 복잡하게 표현되지 않고, vertex의 위치에 따라 color가 정해지기 때문에 shader에서 계산하는 편이 메모리 사용을 줄일 수 있다. 따라서 MyPlaincolor 관련 코드를 지우고 fshader에 아래의 코드를 추가한다.

fshader.glsl

const vec4 GRAY = vec4(0.745098, 0.745098, 0.745098, 1);
const vec4 LIGHT_GRAY = vec4(0.827451, 0.827451, 0.827451, 1);
	
vec2 division_coordinate = vec2(0, 0);

division_coordinate.x = (position.x + u_x_size) / (2 * u_x_size / u_division_num);
division_coordinate.y = (position.y + u_y_size) / (2 * u_y_size / u_division_num);

if ((int(division_coordinate.x) + int(division_coordinate.y)) % 2 > 0) {
	fColor = GRAY;
}
else {
	fColor = LIGHT_GRAY;
}

위 계산과정을 fshader에 추가함으로서 color에 해당하는 메모리 사용을 줄이고도 같은 결과를 낼 수 있다.

방법 4

기존에는 division_num을 변화시킬 때마다 main에서 new 키워드를 이용해 MyPlain을 새로 할당하였다. 그러나 이 방법을 사용했을 때 새로운 MyPlain을 생성할 때마다 Array와 Buffer를 새로 생성하여 GPU의 메모리를 계속해서 추가 할당하는 것을 확인하였다. 때문에 이를 수정하여 처음 초기화 시에만 Array와 Buffer를 생성하고 init에서 division_num가 변할 때만 vaovbobind하는 방법으로 변경하였다.

이 방법을 이용해 GPU에서 발생하던 메모리 누수 현상을 잡을 수 있다.

최종 코드

MyPlain.h

#ifndef _MY_PLAIN_
#define _MY_PLAIN_

#include <vgl.h>
#include <vec.h>

class MyPlain {
protected:
	unsigned int DIVISION_NUM;
	unsigned int NUM_VERTEX;
	float X_SIZE;
	float Y_SIZE;
	bool is_init;

	//const vec4 GRAY = vec4(0.745098, 0.745098, 0.745098, 1);
	//const vec4 LIGHT_GRAY = vec4(0.827451, 0.827451, 0.827451, 1);

	vec4* vertex;
	//vec4* colors;
	GLuint vao;
	GLuint vbo;
public:
	// 0 < size < 1
	MyPlain(float x_size, float y_size) {
		DIVISION_NUM = 2;
		NUM_VERTEX = DIVISION_NUM * DIVISION_NUM * 2 * 3;

		X_SIZE = x_size;
		Y_SIZE = y_size;

		vao = NULL;
		vbo = NULL;

		is_init = false;
	}
	~MyPlain() {
		//delete[] vertex;
		//delete[] colors;
	}
	void init(int division_num) {
		if (is_init && DIVISION_NUM == division_num) return;
		DIVISION_NUM = division_num;
		NUM_VERTEX = DIVISION_NUM * DIVISION_NUM * 2 * 3;

		vertex = new vec4[NUM_VERTEX];
		//colors = new vec4[NUM_VERTEX];

		generate_plain();
		if (!is_init) {
			glGenVertexArrays(1, &vao);
			glBindVertexArray(vao);

			glGenBuffers(1, &vbo);
			glBindBuffer(GL_ARRAY_BUFFER, vbo);
			is_init = true;
		}
		else {
			glBindVertexArray(vao);
			glBindBuffer(GL_ARRAY_BUFFER, vbo);
		}
		glBufferData(GL_ARRAY_BUFFER, sizeof(vec4) * NUM_VERTEX, vertex, GL_STATIC_DRAW);
		//glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vec4) * NUM_VERTEX, vertex);
		//glBufferSubData(GL_ARRAY_BUFFER, sizeof(vec4) * NUM_VERTEX, sizeof(vec4) * NUM_VERTEX, colors);

		delete[] vertex;
	}
	void draw(GLuint program) {
		glBindVertexArray(vao);
		connect_shader(program);
		glDrawArrays(GL_TRIANGLES, 0, NUM_VERTEX);
	}
	// shader 연결
	void connect_shader(GLuint program) {
		GLuint vPosition = glGetAttribLocation(program, "vPosition");
		glEnableVertexAttribArray(vPosition);
		glVertexAttribPointer(vPosition, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

		//GLuint vColor = glGetAttribLocation(program, "vColor");
		//glEnableVertexAttribArray(vColor);
		//glVertexAttribPointer(vColor, 4, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(sizeof(vec4) * NUM_VERTEX));
	}

	unsigned int get_division_num() {
		return DIVISION_NUM;
	}
	unsigned int get_triangle_num() {
		return NUM_VERTEX / 3;
	}
	unsigned int get_vertex_num() {
		return NUM_VERTEX;
	}

protected:
	void generate_plain() {
		vec4** rect_points = new vec4*[DIVISION_NUM + 1];
		
		for (int i = 0; i < DIVISION_NUM + 1; i++)
			rect_points[i] = new vec4[DIVISION_NUM + 1];

		// set rectangle points coordinate
		const float division_x_size = (X_SIZE * 2) / DIVISION_NUM;
		const float division_y_size = (Y_SIZE * 2) / DIVISION_NUM;

		for (int i = 0; i < DIVISION_NUM + 1; i++)
			for (int j = 0; j < DIVISION_NUM + 1; j++)
				rect_points[i][j] = vec4(-X_SIZE + j * division_x_size, -Y_SIZE + i * division_y_size, 0, 1);

		// divide rectangle points to triangle points
		for (int i = 0, vNum = 0; i < DIVISION_NUM; i++) {
			for (int j = 0; j < DIVISION_NUM; j++) {
				vec4 rect[] = {rect_points[i][j], rect_points[i][j + 1], rect_points[i + 1][j + 1], rect_points[i + 1][j] };
				vec4* triangle;

				// divide rect->triangle
				if ((rect_points[i][j].x < 0 && rect_points[i][j].y < 0) || (rect_points[i][j].x > 0 && rect_points[i][j].y > 0))
					triangle = divide_rect(rect, true);
				else
					triangle = divide_rect(rect, false);

				// save vertex
				for (int k = 0; k < 6; k++) 
					vertex[vNum++] = triangle[k];

				//// save color
				//if ((i + j) % 2 == 0) {
				//	for (int j = 0; j < 6; j++) {
				//		colors[vNum] = GRAY; vNum++;
				//	}
				//}
				//else {
				//	for (int j = 0; j < 6; j++) {
				//		colors[vNum] = LIGHT_GRAY; vNum++;
				//	}
				//}
			}
		}

		// delete points
		for (int i = 0; i < DIVISION_NUM + 1; i++)
			delete[] rect_points[i];
		delete[] rect_points;
	}

	vec4* divide_rect(vec4* rect, bool left) {
		if (left) {
			vec4 triangle[] = { rect[0], rect[1], rect[3],
								 rect[2], rect[3], rect[1] };
			return triangle;
		}
		else {
			vec4 triangle[] = { rect[0], rect[1], rect[2],
								 rect[2], rect[3], rect[0] };
			return triangle;
		}
	}

	
};

#endif

main.cpp

#include <vgl.h>
#include <vec.h>
#include <InitShader.h>
#include "MyPlain.h"

GLuint program;
MyPlain *plain;

bool is_waving = false;
bool is_rotating = false;

const float X_SIZE = 0.5;
const float Y_SIZE = 0.5;
unsigned int DIVISION_NUM = 30;
float myTime = 0;

void initialize() {
	plain = new MyPlain(X_SIZE, Y_SIZE);
	plain->init(DIVISION_NUM);

	program = InitShader("vshader.glsl", "fshader.glsl");
	glUseProgram(program);
}

void display() {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glEnable(GL_DEPTH_TEST);

	glUseProgram(program);

	// Time
	GLuint uTime = glGetUniformLocation(program, "uTime");
	glUniform1f(uTime, myTime);

	// Waving
	GLuint u_is_waving = glGetUniformLocation(program, "u_is_waving");
	glUniform1i(u_is_waving, is_waving);

	// Size
	GLuint x_size = glGetUniformLocation(program, "u_x_size");
	glUniform1f(x_size, X_SIZE);

	GLuint y_size = glGetUniformLocation(program, "u_y_size");
	glUniform1f(y_size, Y_SIZE);

	// Division Number
	GLuint division_num = glGetUniformLocation(program, "u_division_num");
	glUniform1i(division_num, DIVISION_NUM);

	plain->draw(program);
	glFlush();
}

void idle() {
	if (is_rotating) {
		myTime += 0.0333f;
		Sleep(33);

		glutPostRedisplay();
	}
}

void processNormalKey(unsigned char key, int x, int y) {
	if (key == 'q') {
		exit(0);
	}
	else if (key == '1') {
		// minimum division num = 2
		if (DIVISION_NUM > 2) {
			DIVISION_NUM--;
			plain->init(DIVISION_NUM);

			printf("Division: %d, Num.of Triangles: %d, Num. of Vertices: %d\n", plain->get_division_num(),
																				 plain->get_triangle_num(),
																				 plain->get_vertex_num());
		} 
		glutPostRedisplay();
	}
	else if (key == '2') {
		DIVISION_NUM++;
		plain->init(DIVISION_NUM);

		printf("Division: %d, Num.of Triangles: %d, Num. of Vertices: %d\n", plain->get_division_num(),
																			 plain->get_triangle_num(),
																			 plain->get_vertex_num());
		glutPostRedisplay();
	}
	else if (key == 'w') {
		is_waving = !is_waving;
		glutPostRedisplay();
	}
	else if (key == ' ') {
		is_rotating = !is_rotating;
	}
}

int main(int argc, char** argv) {
	glutInit(&argc, argv);

	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA | GLUT_DEPTH);
	glutInitWindowSize(800, 800);
	glutCreateWindow("A Waving Plain");

	glewExperimental = true;
	glewInit();

	printf("A Waving Color Plain\n");
	printf("Programming Assignment #1 for Computer Graphics\n");
	printf("Heo-JinSu, 19011625, Department of Software, Sejong University\n");

	printf("\n------------------------------------------------------------\n");
	printf("'1' key: Decreasing the Number of Division\n");
	printf("'2' key: Increasing the Number of Division\n");
	printf("'w' key: Showing/Hiding the waving pattern\n");
	printf("Spacebar: Starting/Stoping rotating and waving\n");
	printf("\n'Q' key: Exit the program.\n");
	printf("\n------------------------------------------------------------\n");

	initialize();

	glutDisplayFunc(display);
	glutIdleFunc(idle);
	glutKeyboardFunc(processNormalKey);

	glutMainLoop();

	delete plain;
	return 0;
}

vshader.glsl

#version 330

uniform float uTime;
uniform bool u_is_waving;
uniform float u_x_size;
uniform float u_y_size;

in vec4 vPosition;

out vec4 position;

void main() {
	const float PI = 3.141592f;

	position = vPosition;

	float distance = sqrt(pow(position.x, 2) + pow(position.y, 2));
	float speed = 5;
	if (distance < (u_x_size > u_y_size ? u_y_size : u_x_size) && u_is_waving) {
		position.z = sin(10 * distance * PI - (uTime*speed)) * ((u_x_size > u_y_size ? u_y_size : u_x_size) - distance);
	}

	float x_ang = 45 / 180.0f * PI;
	float z_ang = 20 * uTime / 180.0f * PI;

	mat4 x_rotate = mat4(1.0f);
	mat4 z_rotate = mat4(1.0f);

	x_rotate[1][1] = cos(x_ang);
	x_rotate[2][1] = -sin(x_ang);
	x_rotate[1][2] = sin(x_ang);
	x_rotate[2][2] = cos(x_ang);

	z_rotate[0][0] = cos(z_ang);
	z_rotate[1][0] = -sin(z_ang);
	z_rotate[0][1] = sin(z_ang);
	z_rotate[1][1] = cos(z_ang);

	if (uTime > 0)
		gl_Position = x_rotate * z_rotate * position;
	else
		gl_Position = x_rotate * position;
}

fshader.glsl

#version 330

uniform float u_x_size;
uniform float u_y_size;

uniform int u_division_num;

in vec4 position;

out vec4 fColor;

void main() {
	const vec4 GRAY = vec4(0.745098, 0.745098, 0.745098, 1);
	const vec4 LIGHT_GRAY = vec4(0.827451, 0.827451, 0.827451, 1);
	const vec4 RED = vec4(1, 0, 0, 1);
	const vec4 BLUE = vec4(0, 0, 1, 1);
	
	vec2 division_coordinate = vec2(0, 0);

	division_coordinate.x = (position.x + u_x_size) / (2 * u_x_size / u_division_num);
	division_coordinate.y = (position.y + u_y_size) / (2 * u_y_size / u_division_num);
	
	if ((int(division_coordinate.x) + int(division_coordinate.y)) % 2 > 0) {
		fColor = GRAY;
	}
	else {
		fColor = LIGHT_GRAY;
	}

	if (position.z > 0.01f) {
		fColor += BLUE * position.z;
		fColor.a = 1;
	}
	else if (position.z < -0.01f) {
		fColor -= RED * position.z;
		fColor.a = 1;
	}
}

최종 결과

[w]를 눌렀을 때 waving pattern 이 적용된다.
[spacebar]를 눌렀을 때 평면이 회전한다. 또한 waving pattern이 적용되었다면 평면이 회전할 때 동시에 "WAVING" 한다.

Result_1.png

[1] 을 눌렀을 때 Division Number가 줄어든다.

Result_2.png

[2]를 눌렀을 때 Division Number가 늘어난다.

Result_3.png

[q]를 눌렀을 때 프로그램이 종료된다.

0개의 댓글