본 과제는 2024년 2학기 세종대학교 컴퓨터그래픽스(박상일 교수) 과제 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_size
는 0 ~ 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
함수에선 plain
을 X_SIZE, Y_SIZE
로 초기화 한 후 plain
의 init
메소드를 호출하여 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
을 연결하고, plain
의 draw
메소드를 호출하여 화면에 렌더링한다.
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로 vPosition
과 vColor
를 in
으로 받고 uTime, u_is_waving, u_x_size, u_y_size
를 uniform
으로 받는다.
position.z
는 u_is_waving
이 true
일 때 새로 계산하며 아래와 같이 계산한다.
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
로 계산되는 distance
와 uTime
에 따라 변한다.
먼저 중심으로부터 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로 position
과 color
를 in
으로 받는다.
fColor
를 color
로 초기화한 후, 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;
}
}
이 방법으로 했을 때 예제와 같은 결과를 보여줄 수 있다. 그러나 아래 그림과 같이 오른쪽 아래와 왼쪽 위의 대각선 표현이 부드럽지 않다는 문제가 있다. 이를 해결하기 위해 방법 2를 고안했다.
방법 1에서 아래 그림과 같은 결과를 얻은 가장 큰 이유는 사각형을 삼각형으로 나눈 기준 때문이다. 사각형을 왼쪽 위에서 오른쪽 아래로 나누었기 때문에 그와 반대되는 방향의 대각선을 표현하기 위해서는 두 삼각형이 접힌 것 처럼 표현된다.
이를 해결하기 위해 사각형의 위치에 따라 반대 방향(왼쪽 아래에서 오른쪽 위)으로 사각형을 나누는 과정을 추가했다.
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 보다 더 부드럽게 표현되는 것을 볼 수 있다.
방법 1과 방법 2에서는 모두 color
를 MyPlain
에서 계산한 후 shader에 보냈다. 그러나 plain
의 색상이 복잡하게 표현되지 않고, vertex의 위치에 따라 color
가 정해지기 때문에 shader에서 계산하는 편이 메모리 사용을 줄일 수 있다. 따라서 MyPlain
의 color
관련 코드를 지우고 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
에 해당하는 메모리 사용을 줄이고도 같은 결과를 낼 수 있다.
기존에는 division_num
을 변화시킬 때마다 main
에서 new
키워드를 이용해 MyPlain
을 새로 할당하였다. 그러나 이 방법을 사용했을 때 새로운 MyPlain
을 생성할 때마다 Array와 Buffer를 새로 생성하여 GPU의 메모리를 계속해서 추가 할당하는 것을 확인하였다. 때문에 이를 수정하여 처음 초기화 시에만 Array와 Buffer를 생성하고 init
에서 division_num
가 변할 때만 vao
와 vbo
에 bind
하는 방법으로 변경하였다.
이 방법을 이용해 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" 한다.
[1] 을 눌렀을 때 Division Number가 줄어든다.
[2]를 눌렀을 때 Division Number가 늘어난다.
[q]를 눌렀을 때 프로그램이 종료된다.