공부/OpenGL

[OpenGL Tutorial]2. 첫 번째 삼각형

개발의 피 2024. 11. 5. 23:02

https://www.opengl-tutorial.org/beginners-tutorials/tutorial-2-the-first-triangle/

 

Tutorial 2 : The first triangle

This will be another long tutorial. OpenGL 3 makes it easy to write complicated stuff, but at the expense that drawing a simple triangle is actually quite difficult. Don’t forget to cut’n paste the code on a regular basis. If the program crashes at sta

www.opengl-tutorial.org

 

1. VAO
2. 화면 좌표
3. 삼각형 그리기
4. 셰이더 (컴파일 / 버텍스 셰이더 / 프래그먼트 셰이더)
5. 모두 합치기 

 

* 예시 코드

 

* 코드 설명 

1) 삼각형 그리기

* VAO : Vertex Array Object(버텍스 배열 객체) 

- 버텍스 속성들을 저장하는 객체를 생성하고 바인딩

- 창(OpenGL 컨텍스트)을 만든 후, 다른 OpenGL을 호출하기 전에 해당 작업 수행 

GLuint VertexArrayID;
glGenVertexArrays(1, &VertexArrayID);
glBindVertexArray(VertexArrayID);

 

* 화면 좌표

3D 그래픽 - 점 : 정점(vertex, vertices)으로 표현

-> 삼각형 : 세 개의 점으로 정의

=> 정점 : X, Y, Z의 세 개의 좌표가 있음 

 

<삼각형 세 좌표 (+ 오른손 법칙)>

X : 오른쪽 / 엄지 손가락
Y : 위 / 검지 손가락 
Z : 뒤쪽 (앞쪽 x) / 중지 

https://www.researchgate.net/figure/Hand-Shape-Coordinates-three-finger-rule-right-hand-Figure99-p212_fig7_308503457

-> 오른손 법칙의 유일한 단점 : 직관적이지 않은 Z

- 유의 : 손 = 자유롭게 움직일 수 있음 ! (= X, Y, Z도 마찬가지) 

 

- 삼각형을 만들기 위한 정점 데이터 (3개의 3차원 점)

// An array of 3 vectors which represents 3 vertices 
static const GLfloat g_vertex_buffer_data[] = {
   -1.0f, -1.0f, 0.0f,  // 왼쪽 아래
   1.0f, -1.0f, 0.0f,   // 오른쪽 아래
   0.0f,  1.0f, 0.0f    // 상단 중앙
};

 

https://www.opengl-tutorial.org/beginners-tutorials/tutorial-2-the-first-triangle/

 

* 삼각형 그리기

- GPU 메모리에 버텍스 데이터를 저장하기 위한 버퍼를 생성, 데이터 전송 

// 정점 버퍼 식별
GLuint vertexbuffer;
// 버퍼 1개 생성, 결과 식별자를 버텍스 버퍼에 넣음
glGenBuffers(1, &vertexbuffer);
// '버텍스 버퍼' 버퍼
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
// 정점을 OpenGL에 전송
glBufferData(GL_ARRAY_BUFFER, sizeof(g_vertex_buffer_data), g_vertex_buffer_data, GL_STATIC_DRAW);

 

- 렌더링 내부 : 그리기

// 쉐이더 프로그램 사용
glUseProgram(programID);

// 버텍스 속성 활성화 및 설정
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
glVertexAttribPointer(
    0,                  // 속성 0
    3,                  // 크기
    GL_FLOAT,          // 데이터 타입
    GL_FALSE,          // 정규화 여부
    0,                 // stride
    (void*)0           // 오프셋
);

// 삼각형 그리기
glDrawArrays(GL_TRIANGLES, 0, 3); // 버텍스 0에서 시작, 3 버텍시스 -> 삼각형 1
glDisableVertexAttribArray(0); // 버텍스 속성 비활성화

 

* 정리 코드 (마지막)

프로그램 종료 전, 생성했던 OpenGL 객체들을 정리 

glDeleteBuffers(1, &vertexbuffer); // 버퍼 정리 
glDeleteVertexArrays(1, &VertexArrayID); // VAO 정리
glDeleteProgram(programID); // 쉐이더 프로그램 정리

 

2) 셰이더

가능한 가장 간단한 구성 : 두 개의 셰이더

- Vertex Shader : 각 정점에 대해 실행

- Fragment Shader : 각 샘플에 대해 실행

-> 4x 앤티앨리어싱을 사용하므로, 각 픽셀에 4개의 샘플이 있음 

 

* 셰이더 : GLSL 언어로 프로그래밍됨 

- GLSL (GL Shader Language) : OpenGL의 일부

- GLSL : 런타임에 컴파일해야 함 

-> 애플리케이션을 실행할 때마다 모든 셰이더가 다시 컴파일 됨 ! 

- 직접 액세스할 수 없음 (버퍼와 마찬가지)

- ID만 있음 (실제 구현 : 드라이버 내부에 숨겨져 있음)

 

* 셰이더 컴파일 (common/loadShader.cpp) 

// 프로그램에서 한 번만 이 작업을 하는 경우가 많으므로, 완전히 이해할 필요 x 
GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){

	// 셰이더 생성
	GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
	GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

	// 파일에서 버텍스 셰이더 코드 읽어오기 
	std::string VertexShaderCode;
	std::ifstream VertexShaderStream(vertex_file_path, std::ios::in);
	if(VertexShaderStream.is_open()){
		std::stringstream sstr;
		sstr << VertexShaderStream.rdbuf();
		VertexShaderCode = sstr.str();
		VertexShaderStream.close();
	}else{
		printf("Impossible to open %s. Are you in the right directory ? Don't forget to read the FAQ !\n", vertex_file_path);
		getchar();
		return 0;
	}

	// 파일에서 프래그먼트 셰이더 코드 읽어오기 
	std::string FragmentShaderCode;
	std::ifstream FragmentShaderStream(fragment_file_path, std::ios::in);
	if(FragmentShaderStream.is_open()){
		std::stringstream sstr;
		sstr << FragmentShaderStream.rdbuf();
		FragmentShaderCode = sstr.str();
		FragmentShaderStream.close();
	}

	GLint Result = GL_FALSE;
	int InfoLogLength;

	// 버텍스 셰이더 컴파일
	printf("Compiling shader : %s\n", vertex_file_path);
	char const * VertexSourcePointer = VertexShaderCode.c_str();
	glShaderSource(VertexShaderID, 1, &VertexSourcePointer , NULL);
	glCompileShader(VertexShaderID);

	// 버텍스 셰이더 체크
	glGetShaderiv(VertexShaderID, GL_COMPILE_STATUS, &Result);
	glGetShaderiv(VertexShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
	if ( InfoLogLength > 0 ){
		std::vector<char> VertexShaderErrorMessage(InfoLogLength+1);
		glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
		printf("%s\n", &VertexShaderErrorMessage[0]);
	}

	// 프래그먼트 셰이더 컴파일
	printf("Compiling shader : %s\n", fragment_file_path);
	char const * FragmentSourcePointer = FragmentShaderCode.c_str();
	glShaderSource(FragmentShaderID, 1, &FragmentSourcePointer , NULL);
	glCompileShader(FragmentShaderID);

	// 프래그먼트 셰이더 체크 
	glGetShaderiv(FragmentShaderID, GL_COMPILE_STATUS, &Result);
	glGetShaderiv(FragmentShaderID, GL_INFO_LOG_LENGTH, &InfoLogLength);
	if ( InfoLogLength > 0 ){
		std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1);
		glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
		printf("%s\n", &FragmentShaderErrorMessage[0]);
	}

	// 프로그램 링크 
	printf("Linking program\n");
	GLuint ProgramID = glCreateProgram();
	glAttachShader(ProgramID, VertexShaderID);
	glAttachShader(ProgramID, FragmentShaderID);
	glLinkProgram(ProgramID);

	// 프로그램 체크 
	glGetProgramiv(ProgramID, GL_LINK_STATUS, &Result);
	glGetProgramiv(ProgramID, GL_INFO_LOG_LENGTH, &InfoLogLength);
	if ( InfoLogLength > 0 ){
		std::vector<char> ProgramErrorMessage(InfoLogLength+1);
		glGetProgramInfoLog(ProgramID, InfoLogLength, NULL, &ProgramErrorMessage[0]);
		printf("%s\n", &ProgramErrorMessage[0]);
	}
	
	glDetachShader(ProgramID, VertexShaderID);
	glDetachShader(ProgramID, FragmentShaderID);
	
	glDeleteShader(VertexShaderID);
	glDeleteShader(FragmentShaderID);

	return ProgramID;
}

 

* 버텍스 셰이더 (정점 셰이더) 

#version 330 core

void main(){

    gl_Position.xyz = vertexPosition_modelspace;
    gl_Position.w = 1.0;
}

 

 

* 프래그먼트 셰이더 

- 각 프래그먼트의 색상을 빨간색으로 설정 

- vec3(1, 0, 0) : 녹색, 파란색 없는 완전한 빨간색 

#version 330 core

out vec3 color;

void main(){
  color = vec3(1,0,0); // red
}

 

 

 

* 에러

 

* 파일 위치 : 같은 디렉터리 내, 셰이더 파일들 존재

 

* Run/Debug Configurations 수정

Working directory : 셰이더 파일들이 있는 디렉터리로 지정

 

 

* 실행결과

- 빨간색 삼각형 (예제)

 

 

- 다른 색

검정색

color = vec3(0,0,0);

흰색

color = vec3(1,1,1);

베이비핑크

color = vec3(1.0, 0.714, 0.757); // Baby pink color (255, 182, 193)