Showing

[Open GL] opengl-tutorial 2 first triangle 본문

Open GL

[Open GL] opengl-tutorial 2 first triangle

RabbitCode 2023. 10. 27. 20:36

Tutorial 2 : 첫 삼각형 (opengl-tutorial.org)

 

Tutorial 2 : 첫 삼각형

이부분 또한 긴 튜토리얼이 될것입니다. OpenGL 3 은 복잡한 것을 작성하기 쉽지만, 간단한 삼각형을 그리는 것은 오히려 어렵습니다. 습관적으로 코드를 복사 붙여넣는 것을 잊지마세요. 만약 실

www.opengl-tutorial.org

* opengl-tutorial.org)을 보고 학습한 개인학습 기록 포스팅입니다.

 

 

1. 버텍스 정의

삼각형을 만들기 위해 세개의 3D 점이 필요하다

// 3 버텍스들을 표현하는 3 벡터들의 배열
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,
};

가장 처음 버틱스는 (-1,-1,0)이다.

변환(트랜스폼;Transform) 하기 전에는, 화면상에는 (-1,-1)로 표시된다.

 (-1,-1) 은 화면 좌측 최하단. (1,-1) 은 화면 우측 최하단. (0,1) 은 중앙 최상단

따라서 삼각형은 화면 대부분을 차지하게 된다.

 

// Include standard headers
#include <stdio.h>
#include <stdlib.h>

// Include GLEW
#include <GL/glew.h>

// Include GLFW
#include <GLFW/glfw3.h>
GLFWwindow* window;

// Include GLM
#include <glm/glm.hpp>
using namespace glm;

#include <common/shader.hpp>

int main( void )
{
	// Initialise GLFW
	if( !glfwInit() )
	{
		fprintf( stderr, "Failed to initialize GLFW\n" );
		getchar();
		return -1;
	}

	glfwWindowHint(GLFW_SAMPLES, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	// Open a window and create its OpenGL context
	window = glfwCreateWindow( 1024, 768, "Tutorial 02 - Red triangle", NULL, NULL);
	if( window == NULL ){
		fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" );
		getchar();
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);

	// Initialize GLEW
	glewExperimental = true; // Needed for core profile
	if (glewInit() != GLEW_OK) {
		fprintf(stderr, "Failed to initialize GLEW\n");
		getchar();
		glfwTerminate();
		return -1;
	}

	// Ensure we can capture the escape key being pressed below
	glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);

	// Dark blue background
	glClearColor(0.0f, 0.0f, 0.4f, 0.0f);

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

	// Create and compile our GLSL program from the shaders
	GLuint programID = LoadShaders( "SimpleVertexShader.vertexshader", "SimpleFragmentShader.fragmentshader" );


	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,
	};

	GLuint vertexbuffer;
    // 버퍼를 하나 생성
	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);

	do{

		// Clear the screen
		glClear( GL_COLOR_BUFFER_BIT );

		// Use our shader
		glUseProgram(programID);

		// 1rst attribute buffer : vertices
		glEnableVertexAttribArray(0);
		glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
		glVertexAttribPointer(
			0,                  // attribute 0. No particular reason for 0, but must match the layout in the shader.
			3,                  // size
			GL_FLOAT,           // type
			GL_FALSE,           // normalized?
			0,                  // stride
			(void*)0            // array buffer offset
		);

		// Draw the triangle !
		glDrawArrays(GL_TRIANGLES, 0, 3); // 3 indices starting at 0 -> 1 triangle

		glDisableVertexAttribArray(0);

		// Swap buffers
		glfwSwapBuffers(window);
		glfwPollEvents();

	} // Check if the ESC key was pressed or the window was closed
	while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS &&
		   glfwWindowShouldClose(window) == 0 );

	// Cleanup VBO
	glDeleteBuffers(1, &vertexbuffer);
	glDeleteVertexArrays(1, &VertexArrayID);
	glDeleteProgram(programID);

	// Close OpenGL window and terminate GLFW
	glfwTerminate();

	return 0;
}

위의 상태에서 쉐이더를 통해 색감을 입힐 수 있다.

 

2. VAO

일찍이 Vertex Array Object를 생성하고 이를 현재 객체로 설정했다.

 

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

창이 생성된 후(= OpenGL 컨텍스트 생성 후)와 다른 OpenGL 호출 전에 이 작업을 수행했다.

 

3. 셰이더

// Create and compile our GLSL program from the shaders
GLuint programID = LoadShaders( "SimpleVertexShader.vertexshader", "SimpleFragmentShader.fragmentshader" );

 

가능한 가장 간단한 구성에는 두 개의 셰이더가 필요하다.

하나는 각 정점에 대해 실행되는 Vertex Shader이고 다른 하나는 각 샘플에 대해 실행되는 Fragment Shader이다. 

 

셰이더는 OpenGL의 일부인 GLSL: GL Shader Language라는 언어로 프로그래밍된다.

C나 Java와 달리 GLSL은 런타임에 컴파일되어야 한다. 즉, 애플리케이션을 시작할 때마다 모든 셰이더가 다시 컴파일된다.

 

LoadShaders 코드

GLuint LoadShaders(const char * vertex_file_path,const char * fragment_file_path){

	// Create the shaders
	GLuint VertexShaderID = glCreateShader(GL_VERTEX_SHADER);
	GLuint FragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER);

	// Read the Vertex Shader code from the file
	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;
	}

	// Read the Fragment Shader code from the file
	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;


	// 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 ){
		std::vector<char> VertexShaderErrorMessage(InfoLogLength+1);
		glGetShaderInfoLog(VertexShaderID, InfoLogLength, NULL, &VertexShaderErrorMessage[0]);
		printf("%s\n", &VertexShaderErrorMessage[0]);
	}



	// 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 ){
		std::vector<char> FragmentShaderErrorMessage(InfoLogLength+1);
		glGetShaderInfoLog(FragmentShaderID, InfoLogLength, NULL, &FragmentShaderErrorMessage[0]);
		printf("%s\n", &FragmentShaderErrorMessage[0]);
	}



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

	// Check the program
	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;
}

 

(1) VertexShader

#version 330 core

// Input vertex data, different for all executions of this shader.
layout(location = 0) in vec3 vertexPosition_modelspace;

void main(){

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

}

두 번째 줄은 입력 데이터를 선언한다.

layout(location = 0) in vec3 vertexPosition_modelspace;

이 줄을 자세히 설명하겠습니다.

  • "vec3"은 GLSL의 3개 구성 요소로 구성된 벡터이다. 이는 삼각형을 선언하는 데 사용한 glm::vec3과 유사하지만 다르다.
  • "layout(location = 0)"은 vertexPosition_modelspace 속성을 제공하는 데 사용하는 버퍼를 나타낸다 . 각 정점은 위치, 하나 또는 여러 색상, 하나 또는 여러 텍스처 좌표, 기타 여러 가지 등 다양한 속성을 가질 수 있다. OpenGL은 색상이 무엇인지 모르고 단지 vec3만 볼 뿐이다. 따라서 어떤 버퍼가 어떤 입력에 해당하는지 알려주어야 한다. glVertexAttribPointer의 첫 번째 매개변수와 동일한 값으로 레이아웃을 설정하여 이를 수행한다. 값 "0" 자체는 중요하지 않습니다. 12일 수 있습니다(그러나 glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &v) 이하). 중요한 것은 양쪽의 숫자가 동일하다는 것이다.
glVertexAttribPointer(
			0,                  // attribute 0. No particular reason for 0, but must match the layout in the shader.
			3,                  // size
			GL_FLOAT,           // type
			GL_FALSE,           // normalized?
			0,                  // stride
			(void*)0            // array buffer offset
		);
  • "vertexPosition_modelspace"는 다른 이름을 가질 수 있으며 정점 셰이더의 각 실행에 대한 정점 위치가 포함된다.
  • "in"은 이것이 일부 입력 데이터임을 의미한다.("out" 키워드도 있음)

주요 기능은 정점의 위치를 ​​버퍼에 있던 대로 설정하는 것뿐이다. 따라서 (1,1)을 주면 삼각형의 정점 중 하나가 화면 오른쪽 상단에 있게 된다. 입력 위치에 대해 좀 더 흥미로운 계산도 수행 가능하다.

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

gl_Position은 몇 가지 내장 변수 중 하나입니다. *이 변수에 값을 할당해야만 한다. 다른 선택 사항들도 존재한다.

 

(2) FragmentShader

각 조각의 색상을 빨간색으로 설정한다. 

(4x AA를 사용하기 때문에 한 픽셀에 4개의 조각이 있다)

#version 330 core
out vec3 color;
void main(){
  color = vec3(1,0,0);
}

예, vec3(1,0,0)은 빨간색을 의미한다. 이는 컴퓨터 화면에서 색상이 빨간색, 녹색, 파란색의 순서로 표시되고, 따라서 (1,0,0)은 완전 빨간색, 녹색도 파란색도 없음을 의미한다.

 

 

최종

LoadShaders 함수를 가져와서,

#include <common/shader.hpp>

메인 루프 전에 LoadShaders 함수를 호출한다.

// Create and compile our GLSL program from the shaders
GLuint programID = LoadShaders( "SimpleVertexShader.vertexshader", "SimpleFragmentShader.fragmentshader" );

이제 메인 루프 내부에서 먼저 화면을 지운다.

이전 glClearColor(0.0f, 0.0f, 0.4f, 0.0f) 호출로 인해 배경색이 진한 파란색으로 변경된다.

glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

셰이더를 사용하고 싶다고 OpenGL에 알린다.

// Use our shader
glUseProgram(programID);
// Draw triangle...