[OpenGL]Bezier Curve

후이재·2020년 9월 26일
1


(Tessellation 10)

(Tessellation 100)

Curve

  • Curve 어떻게 그릴 수 있을까

1. 함수를 이용한다.

Cirle과 Ellipse같이 함수가 정의되어있는것들은 함수를 통해 곡선을 그릴 수 있다.
implicit 형태의 함수를 explicit으로 바꾸어 x가 증가할 때 y가 얼마나 증가하는지에 대한 관계를 이용한 midpoint algorithm을 사용하면, 점의 다음 위치를 정할 수 있다.

midpoint algorithm은 implicit 형태의 함수를 이용하여 보다 아래, 위, 안, 밖을 판별한다.
예를들어 x를 1 증가했을 시 y를 0.5 증가시켜보고 너무 갔으면 그대로 아니면 +1을 한 값을 그린다.
곡선이 있는 함수는 symmetry를 이용하기 좋다. 원의 경우 (x, y), (-x, y), (x, -y), (-x, -y), (y, x), (-y, x), (y, -x), (-y, -x) 이렇게 8개의 점을 한번에 그릴 수 있다.

2. Spline으로 그린다.

Parametric 형태로도 함수를 표현할 수 있는데, 이를 이용하면, x, y를 독립적으로 찾아낼 수 있다.
u라는 그리는 단위를 매개변수로 갖는데, 이를 통해 curve가 지날 way point(control point)를 지정할 수 있다. 

이는 curve specification 방법 중 Interpolation에 속한다. 이 방법은 Fitting에 적합하다. 원하는 지점을 고정할 수 있으니 말이다.
Spline은 대부분 3차방정식인 Cubic으로 사용된다. x, y, z를 알아내기 위해 4가지의 미지수가 있으므로 시작, 끝점의 위치와 기울기를 지정해줘야한다. 행렬로 식을 나타네면 (u벡터 * M * P)가 되는데, P는 제어점이다. 
여기서 u벡터와 M을 곱한 벡터를 Basis Function이라고 한다. 이것은 4개의 그래프인데, 시작점과 끝점에서 1인 그래프가 하나씩있기 때문에 이 행렬계산을 거쳐 나온 Curve는 반드시 시작점과 끝점을 지난다.

이러한 Spline을 특정화 한 것이 Hermite Spline이다. control point를 g라고 하는것 말고 다른점이 없다. 
기울기를 어떻게 줄까 하면 기울기를 특정할 점을 주면 된다. 이 모양새는 ppt에서 커브를 그리는 것과 같다. 그렇다. ppt의 커브는 Hermite Spline이다. Hermite은 참 제어하기가 좋다.

여기서의 문제는 curve와 curve사이의 연결이다. 이것을 continuity라고 부른다.
continuity는 0부터 2가 존재한다.
c0 continuity는 연결지점의 위치가 같은것이다. x1(1) == x2(0) 
c1 continuity는 연결지점의 위치, 기울기가 같은것이다. x1'(1) == x2'(0)도 추가
c2 continuity는 연결지점의 위치, 기울기, 가속도가 같은것이다. x1''(1) == x2''도 추가

c0만 만족하면 연결부분의 불연속성이 보여진다
c1까지 만족하면 부드러워지나, 연결지점에서 가속도가 바뀌는 이유로 기계장치에 좋지 않다.
c2는 모든것을 충족하여 기계가 좋아하지만, 아름답지가 않다=> 이 단점을 보완하기 위해 있는것이 Geometrt continuity이다.

Geometry continuity는 기울기와 가속도에 상수 알파를 곱한다. 즉. 방향은 같아도 크기는 다를 수 있다 이말임 그러면 아름다운 곡선을 만들 수 있다. 

3. Bezuer Curve로 그린다.

이 단어를 꺼내기 위해 지금껏 써왔다. 이 방법은 Interpolation이 아닌 Appriximation curve이다.control point가 존재하지만, curve가 지나는 점의 의미가 아닌 근접한 점이다. 이 curve는 c2 continuity를 반드시 만족한다. 즉 부드러운 curve를 항상 만든다.

이것 또한 Basis Function이 존재하고 n이 높아지면 control point의 개수가 많아진다. 그리고 역시 3차의 cubic Bezier Curves가 일반적이다.
Bezier Curves의 Basis function의 계수의 합은 항상 1인 특성을 가진다. 이 특성을 이용해 보간이 가능하다. 

그리는 방법은 0과 1 사이에서 u를 조금씩 증가시켜가며 그린다. 증가시키는 단위는 tessellation이 결정한다. 100이면 100으로 나눈 값인 1/100씩 증가시킨다. 

closed curve, Weighted curve, Connected curve는 조금 특징을 잡아주면 그릴 수 있다. 단, connected curve는 connected특성을 만들기 위해서 무려 5개의 점을 미리 지정하고 들어간다는 점에서 융통성이 없어진다. 
보간의 방법은 De Casteljau's algorithm이 사용된다. 이름과 다르게 알고리즘은 간단하다. 2개씩 보간해나가다보면 하나가 된다. 이 방법이다. u가 0.3이라면 0.3이 되는 지점을이어놓고, 그 지점의 0.3 되는 지점을 이어놓고.. 반복한다. 

이러한 Bezier Curve는 제어점이 많아지면 차수가 너무 높아져 제어가 어려워지고 만다. 또한 point를 하나라도 건들이면 전체적인 모양이 바뀌는것도 큰 단점이라고 할 수 있다. 



그런 Bezier Curve를 그려보자.
+ Bezier Curve를 그리기 위해 Geometry Shader를 도입한다. 
왜? 그 많은점을 Vertexshader로 보내어 계산하기에는 너무 비효율적이다. 
따라서 받아온 점들(Control Points)을 segment단위로 처리해주는 GS를 사용하는것이다. 

VertexShader.txt

#version 400 core

in vec3 pos;
in vec3 col;
out vec3 v2gColor;

void main()
{	
	gl_Position = vec4 (pos, 1);
	v2gColor = col;
}

GeometryShader.txt

#version 400 core

layout (lines_adjacency) in;
layout (line_strip, max_vertices = 128) out;

uniform int tess;

in vec3 v2gColor[4];
out vec3 g2fColor;

void main(void) {
	float du = 1.0/float(tess);
	float u = 0;
	for (int i = 0; i<=tess; i++)
	{
		float term1 = 1.0 - u;
		float term2 = term1*term1;
		float term3 = term1*term2;
		float u2 = u*u;
		float u3 = u*u2;
		vec4 p = term3 * gl_in[0].gl_Position
		+ 3.0 * u * term2 * gl_in[1].gl_Position
		+ 3.0 * u2 * term1 * gl_in[2].gl_Position
		+ u3 * gl_in[3].gl_Position;
		g2fColor = v2gColor[0];
		gl_Position = p;
		EmitVertex();
		u += du;
}
	EndPrimitive();
} 

FragmentShader.txt

#version 400 core

in vec3 g2fColor;
out vec3 color;

void main()
{
	color = g2fColor;
}

main.cpp

#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <iostream>
#include <fstream>
#include <vector>

#include <GL/glew.h>
#include <GL/glut.h>
#include <algorithm>


using namespace std;
GLuint VertexArrayID[2]; // VAO
GLuint Buffers[2]; // VBO

vector<float> vertices = {
	0.6f, 0.0f, 0.0f,
	0.3f, 0.5f, 0.0f,
	-0.3f, 0.5f, 0.0f,
	-0.6f, 0.0f, 0.0f
};

vector<float> colors = {
	0.0, 0.2f, 1.0,
	0.0, 0.7f, 1.0,
	0.0, 0.9f, 1.0,
	0.0, 1.0, 1.0
};



GLuint LoadShaders(const char* vertex_file_path, const char* fragment_file_path, const char* geometry_file_path)
{
	//create the shaders
	GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
	GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);
	GLuint GeometryShaderID = glCreateShader(GL_GEOMETRY_SHADER);

	GLint Result = GL_FALSE;
	int InfoLogLength;

	//=========== Vertex Shader
	string VertexShaderCode;
	ifstream VertexShaderStream(vertex_file_path, ios::in);
	if (VertexShaderStream.is_open())
	{
		string Line = "";
		while (getline(VertexShaderStream, Line))
			VertexShaderCode += "\n" + Line;
		VertexShaderStream.close();
	}

	//Compile Vertex Shader
	printf("Compiling shader : %s\n", vertex_file_path);
	char const* VertexSourcePointer = VertexShaderCode.c_str();
	glShaderSource(VertexShaderID, 1, &VertexSourcePointer, NULL);
	glCompileShader(VertexShaderID);

	//Check Vertex Shader
	glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
	glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);

	if (InfoLogLength != 0)
	{
		vector<char> VertexShaderErrorMessage(InfoLogLength);
		glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
		fprintf(stdout, "%s\n", &VertexShaderErrorMessage[0]);
	}


	//=========== Geometry Shader
	string GeometryShaderCode;
	ifstream GeometryShaderStream(geometry_file_path, ios::in);
	if (GeometryShaderStream.is_open())
	{
		string Line = "";
		while (getline(GeometryShaderStream, Line))
			GeometryShaderCode += "\n" + Line;
		GeometryShaderStream.close();
	}

	//Compile Geometry Shader
	printf("Compiling shader : %s\n", geometry_file_path);
	char const* GeometrySourcePointer = GeometryShaderCode.c_str();
	glShaderSource(GeometryShaderID, 1, &GeometrySourcePointer, NULL);
	glCompileShader(GeometryShaderID);

	//Check Geometry Shader
	glGetShaderiv(GeometryShaderID, GL_COMPILE_STATUS, &Result);
	glGetShaderiv(GeometryShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);

	if (InfoLogLength != 0)
	{
		vector<char> GeometryShaderErrorMessage(InfoLogLength);
		glGetShaderInfoLog(GeometryShaderID, InfoLogLength, NULL, &GeometryShaderErrorMessage[0]);
		fprintf(stdout, "%s\n", &GeometryShaderErrorMessage[0]);
	}


	//=========== Fragment Shader
	string FragmentShaderCode;
	ifstream FragmentShaderStream(fragment_file_path, ios::in);
	if (FragmentShaderStream.is_open())
	{
		string Line = "";
		while (getline(FragmentShaderStream, Line))
			FragmentShaderCode += "\n" + Line;
		FragmentShaderStream.close();
	}

	//Compile Fragment Shader
	printf("Compiling shader : %s\n", fragment_file_path);
	char const* FragmentSourcePointer = FragmentShaderCode.c_str();
	glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer, NULL);
	glCompileShader(FragmentShaderID);

	//Check Fragment Shader
	glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
	glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);

	if (InfoLogLength != 0)
	{
		vector<char> FragmentShaderErrorMessage(InfoLogLength);
		glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
		fprintf(stdout, "%s\n", &FragmentShaderErrorMessage[0]);
	}



	//Link the program
	fprintf(stdout, "Linking program\n");
	GLuint ProgramID = glCreateProgram();
	glAttachShader(ProgramID, VertexShaderID);
	glAttachShader(ProgramID, GeometryShaderID);
	glAttachShader(ProgramID, FragmentShaderID);
	glLinkProgram(ProgramID);


	// Check the program
	glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
	glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
	vector<char> ProgramErrorMessage(std::max(InfoLogLength, int(1)));
	glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
	fprintf(stdout, "%s\n", &ProgramErrorMessage[0]);

	glDeleteShader(VertexShaderID);
	glDeleteShader(FragmentShaderID);

	return ProgramID;
}


void renderScene(void)
{
	//Clear all pixels
	glClear(GL_COLOR_BUFFER_BIT);
	//Let's draw something here

	glDrawArrays(GL_LINES_ADJACENCY, 0, vertices.size() /3);

	//Double buffer
	glutSwapBuffers();
}


void init()
{
	//initilize the glew and check the errors.
	GLenum res = glewInit();
	if (res != GLEW_OK)
	{
		fprintf(stderr, "Error: '%s' \n", glewGetErrorString(res));
	}

	//select the background color
	glClearColor(0.0, 0.0, 0.0, 0.0);
	glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
}

int main(int argc, char **argv)
{
	//init GLUT and create Window
	//initialize the GLUT
	glutInit(&argc, argv);
	//GLUT_DOUBLE enables double buffering (drawing to a background buffer while the other buffer is displayed)
	glutInitDisplayMode(/* GLUT_3_2_CORE_PROFILE | */ GLUT_DOUBLE | GLUT_RGBA);
	//These two functions are used to define the position and size of the window. 
	glutInitWindowPosition(200, 200);
	glutInitWindowSize(480, 480);
	//This is used to define the name of the window.
	glutCreateWindow("201611235 전희재");

	//call initization function
	init();

	//0. 
	//Generate ID
	GLuint programID = LoadShaders("hw4_VertexShader.txt", "hw4_FragmentShader.txt", "hw4_GeometryShader.txt");
	glUseProgram(programID);

	//1.
	//Generate VAO
	glGenVertexArrays(1, VertexArrayID);
	glBindVertexArray(VertexArrayID[0]);

	//2.
	//Generate VBO
	glGenBuffers(2, Buffers);
	
	// position
	glBindBuffer(GL_ARRAY_BUFFER, Buffers[0]); 
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * vertices.size(), vertices.data(), GL_STATIC_DRAW);

	GLint posAttrib = glGetAttribLocation(programID, "pos");
	glVertexAttribPointer(posAttrib, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(posAttrib);

	// color
	glBindBuffer(GL_ARRAY_BUFFER, Buffers[1]);
	glBufferData(GL_ARRAY_BUFFER, sizeof(float) * colors.size(), colors.data(), GL_STATIC_DRAW);

	GLint colAttrib = glGetAttribLocation(programID, "col");
	glVertexAttribPointer(colAttrib, 3, GL_FLOAT, GL_FALSE, 0, 0);
	glEnableVertexAttribArray(colAttrib);

	GLint tess = glGetUniformLocation(programID, "tess");
	glUniform1i(tess, 100);

	glutDisplayFunc(renderScene);


	//enter GLUT event processing cycle
	glutMainLoop();

	glDeleteVertexArrays(1, VertexArrayID);

	return 1;
}
profile
공부를 위한 벨로그

0개의 댓글