갬장장이
[OpenGL] 7. Vertex Array Object (VAO)
갬장장이
갬장장이의 코드 대장간
갬장장이
전체
오늘
어제
  • 분류 전체보기 (216)
    • 게임 연구소 (6)
    • 게임 제작 (15)
      • We need more guns (2024~) (1)
      • Rovenhell (2023) (2)
      • Geophyte (2020~2021) (5)
      • 아드레날린 러시 (2021) (2)
      • Treadmill (2019) (1)
      • 습작들 (2019~) (2)
      • 그 외 (~2018) (2)
    • mathematics (33)
      • game mathematics (30)
      • linear algebra (3)
    • networking (3)
    • computer graphics (46)
      • 3D Graphics (23)
      • DirectX (8)
      • OpenGL (13)
      • graphics theory (2)
    • game engines (10)
      • Unity (0)
      • Unreal Engine (10)
    • os (6)
      • Linux (0)
      • operating system (1)
      • multithreading, parallel co.. (5)
    • lang (34)
      • c++ (15)
      • .NET (5)
      • python (1)
      • java (3)
      • erlang, elixir (1)
      • js, ts (7)
    • software engineering (47)
      • PS (25)
      • algorithms (15)
      • data structures (2)
      • design patterns (4)
    • cs (4)
    • database (2)
      • SQL (1)
    • web (6)
      • web (3)
      • frameworks, libraries (3)
    • finance (0)
    • 음악 제작 (1)
    • life (3)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • [공지] 블로그 안내

인기 글

최근 댓글

최근 글

hELLO · Designed By 정상우.
computer graphics/OpenGL

[OpenGL] 7. Vertex Array Object (VAO)

2022. 3. 1. 19:29


VAO의 사용 목적은 Vertex buffer가 여러 개 있을 때 각 버퍼마다 Layout(Attribute를 지정해서 버퍼 내에 어떤 정보가 어디에 얼만큼 위치하는지를 지정해두는 것)을 개별적으로 지정해둘 필요가 없게 하는 것이다.

즉 Binding된 Vertex Buffer가 변경될 때마다 매번 그 버퍼가 가진 Attribute들에 대해 glVertexAttribPointer()를 호출할 필요가 없다는 의미다.

glBindBuffer(buffer1);
glVertexAttribPointer(...);

...

glBindBuffer(buffer2);
glEnableVertexAttribArray(...);

이런식으로 할 필요가 없어진다는 의미다.

참고: https://stackoverflow.com/questions/7617668/glvertexattribpointer-needed-everytime-glbindbuffer-is-called



참고글:
https://stackoverflow.com/a/57258564/14134221

 

What are Vertex Array Objects?

I am just starting to learn OpenGL today from this tutorial: http://openglbook.com/the-book/ I got to chapter 2, where I draw a triangle, and I understand everything except VAOs (is this acronym OK...

stackoverflow.com


쉽게 말해, 점들의 좌표값 4개를 저장하고 있는, 사각형을 나타내는 어떤 버퍼가 있다고 가정해보자.
이 사각형 버퍼가 두 종류가 있다면, 즉 사각형 A와 사각형 B가 있다면,
우리는 사각형A와 사각형B에 개별적으로 레이아웃을 지정해주어야 할 것이다. (즉 A 버퍼는 꼭짓점 4개가 float으로 들어있고, B 버퍼에도 꼭짓점 4개가 float으로 들어있다는 걸 개별적으로 두번 지정해주어야 한다)
그러나 VAO를 사용하면 이 과정을 줄일 수 있다.


참고: opengl에서의 바인딩이란?
https://stackoverflow.com/questions/9758884/concept-behind-opengls-bind-functions/9758960

 

Concept behind OpenGL's 'Bind' functions

I am learning OpenGL from this tutorial. My question is about the specification in general, not about a specific function or topic. When seeing code like the following: glGenBuffers(1, &

stackoverflow.com

간단히 말해, OpenGL에게 지금 선택한 오브젝트가 무엇이다 라고 전해주는 것이다.
포토샵 같은걸 쓸 때 마우스로 그림을 그리면 현재 선택된 레이어에 그려지는 것처럼, 미리 무언가를 선택한 이후(OpenGL에서는 버퍼나 셰이더 등 다양한 게 될 수 있다) 행위를 해야 그 선택한 대상에 행위가 처리된다. (선택한 레이어에 그림이 그려진다)

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

// Include GLFW
#include <GLFW/glfw3.h>

#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <assert.h>

#define ASSERT(x) if (!(x)) assert(false)

#define GLCall(x) GLClearError();\
    x;\
    ASSERT(GLCheckError())

static void GLClearError()
{
    while (glGetError() != GL_NO_ERROR);
}

static bool GLCheckError()
{
    while (GLenum error = glGetError())
    {
        
        std::cout << "[OpenGL Error] ";
          switch(error) {
              case GL_INVALID_ENUM :
                  std::cout << "GL_INVALID_ENUM : An unacceptable value is specified for an enumerated argument.";
                  break;
              case GL_INVALID_VALUE :
                  std::cout << "GL_INVALID_OPERATION : A numeric argument is out of range.";
                  break;
              case GL_INVALID_OPERATION :
                  std::cout << "GL_INVALID_OPERATION : The specified operation is not allowed in the current state.";
                  break;
              case GL_INVALID_FRAMEBUFFER_OPERATION :
                  std::cout << "GL_INVALID_FRAMEBUFFER_OPERATION : The framebuffer object is not complete.";
                  break;
              case GL_OUT_OF_MEMORY :
                  std::cout << "GL_OUT_OF_MEMORY : There is not enough memory left to execute the command.";
                  break;
              case GL_STACK_UNDERFLOW :
                  std::cout << "GL_STACK_UNDERFLOW : An attempt has been made to perform an operation that would cause an internal stack to underflow.";
                  break;
              case GL_STACK_OVERFLOW :
                  std::cout << "GL_STACK_OVERFLOW : An attempt has been made to perform an operation that would cause an internal stack to overflow.";
                  break;
              default :
                  std::cout << "Unrecognized error" << error;
          }
          std::cout << std::endl;
          return false;
    }
    return true;
}

struct ShaderProgramSource
{
    std::string VertexSource;
    std::string FragmentSource;
};

static struct ShaderProgramSource ParseShader(const std::string& filepath)
{
    enum class ShaderType
    {
        NONE = -1, VERTEX = 0, FRAGMENT = 1
    };

    std::ifstream stream(filepath);
    std::string line;
    std::stringstream ss[2];
    ShaderType type = ShaderType::NONE;

    while (getline(stream, line))
    {
        if (line.find("#shader") != std::string::npos)
        {
            if (line.find("vertex") != std::string::npos)
                type = ShaderType::VERTEX;
            else if (line.find("fragment") != std::string::npos)
                type = ShaderType::FRAGMENT;
        }
        else
        {
            ss[(int)type] << line << '\n';
        }
    }

    return { ss[0].str(), ss[1].str() };
}

static unsigned int CompileShader(unsigned int type, const std::string& source)
{
    GLCall( unsigned int id = glCreateShader(type) );
    const char* src = source.c_str();
    GLCall( glShaderSource(id, 1, &src, nullptr) );
    GLCall( glCompileShader(id) );

    // Error handling
    int result;
    GLCall( glGetShaderiv(id, GL_COMPILE_STATUS, &result) );
    std::cout << (type == GL_VERTEX_SHADER ? "vertex" : "fragment") << " shader compile status: " << result << std::endl;
    if ( result == GL_FALSE )
    {
        int length;
        GLCall( glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length) );
        char* message = (char*) alloca(length * sizeof(char));
        GLCall( glGetShaderInfoLog(id, length, &length, message) );
        std::cout 
            << "Failed to compile "
            << (type == GL_VERTEX_SHADER ? "vertex" : "fragment")
            << "shader"
            << std::endl;
        std::cout << message << std::endl;
        GLCall( glDeleteShader(id) );
        return 0;
    }

    return id;
}

static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{
    // create a shader program
    unsigned int program = glCreateProgram();
    unsigned int vs = CompileShader(GL_VERTEX_SHADER, vertexShader);
    unsigned int fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);

    GLCall( glAttachShader(program, vs) );
    GLCall( glAttachShader(program, fs) );

    GLCall( glLinkProgram(program) );

    GLint program_linked;

    GLCall( glGetProgramiv(program, GL_LINK_STATUS, &program_linked) );
    std::cout << "Program link status: " << program_linked << std::endl;
    if (program_linked != GL_TRUE)
    {
        GLsizei log_length = 0;
        GLchar message[1024];
        GLCall( glGetProgramInfoLog(program, 1024, &log_length, message) );
        std::cout << "Failed to link program" << std::endl;
        std::cout << message << std::endl;
    }

    GLCall( glValidateProgram(program) );

    GLCall( glDeleteShader(vs) );
    GLCall( glDeleteShader(fs) );

    return program;
}

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





	/*
    	OpenGL 3.3,
        OPENGL_COMPAT_PROFILE은 VAO 하나를 기본으로 생성한 채로 시작한다.
        OPENGL_CORE_PROFILE은 VAO 없이 시작한다.
        때문에 OPENGL_CORE_PROFILE을 선택했다면glGenVertexArrays함수를 이용해
        VAO 하나를 만들어주어야 한다.
    */
	glfwWindowHint(GLFW_SAMPLES, 4);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);




	// Open a window and create its OpenGL context
        GLFWwindow* 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;
	}

        std::cout << "Using GL Version: " << glGetString(GL_VERSION) << std::endl;

	// 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);

        float positions[] = {
            -0.5f, -0.5f, // 0
             0.5f, -0.5f, // 1
             0.5f,  0.5f, // 2
            -0.5f,  0.5f  // 3
        };

        unsigned int indices[] = {
          0, 1, 2,
          2, 3, 0
        };




        /*
        	VAO를 생성하고 바인딩한다
            위에서 OPENGL_CORE_PROFILE을 사용중이기 때문이다.
            만약 여기서 VAO를 바인딩해주지 않는다면 그동안 사용해왔던 코드인 glEnableVertexAttribArray(0);를 호출할 때
            에러가 발생한다. glEnableVertexAttribArray(0);는 기본적으로 opengl이 만들어준 0번 VAO에 AttribArray를 연결해준다는 의미인데,
            CORE_PROFILE에서는 0번 VAO를 만들어주지 않기 때문이다.
        */
        unsigned int vao;
        GLCall( glGenVertexArrays(1, &vao) ); // VAO 1개를 uint vao id로 만들어준다.
        GLCall( glBindVertexArray(vao) ); // 이렇게 만든 VAO를 bind해준다.

        // Vertex buffer (혹은 VBO)를 생성한다
        unsigned int buffer;
        GLCall( glGenBuffers(1, &buffer) );
        GLCall( glBindBuffer(GL_ARRAY_BUFFER, buffer) );
        GLCall( glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(float), positions, GL_STATIC_DRAW) );

        // define vertex layout (레이아웃, 즉 vertex buffer의 Attrib 들을 설정한다)
        /*
        	이 아랫줄 코드가 굉장히 중요하다.
            이 코드가 Vertex Array와 Vertex Buffer을 연결해주는 역할을 한다.
            glVertexAttribPointer의 첫 아규먼트인 0은 현재 바인딩된 Vertex Array의 0번 인덱스에
            해당 Attrib Pointer가 가리키는 Vertex Buffer을 연결한다는 의미이다.
            즉 만약 또 다른 Attrib Pointer을 연결해주려면
            GLCall( glVertexAttribPointer(1, ... , Attrib pointer); 형식으로 작성하면 된다.
            https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glVertexAttribPointer.xhtml
        */
        GLCall( glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0) );// This links the attrib pointer wih the buffer at index 0 in the vertex array object      
        GLCall( glEnableVertexAttribArray(0) ); //https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=jungwan82&logNo=20108365931



        // Create index buffer
        unsigned int ibo;
        GLCall( glGenBuffers(1, &ibo) );
        GLCall( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo) );
        GLCall( glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW) );

        ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");

        std::cout << "VERTEX" << std::endl << source.VertexSource << std::endl;
        std::cout << "FRAGMENT" << std::endl << source.FragmentSource << std::endl;

        unsigned int shader = CreateShader(source.VertexSource, source.FragmentSource);
        GLCall( glUseProgram(shader) );

        GLCall( unsigned int u_Color = glGetUniformLocation(shader, "u_Color") );
        ASSERT(u_Color != -1);

        float red = 0.0f;
        float step = 0.05f;



		// 여기서 glBindVertexArray(0);으로 vao의 바인딩을 풀어준 것을 확인할 수 있다.
        // 그러나 나중에 다시 vao를 바인딩하면 vao와 연결해두었던 attrib와 vertex buffer까지 알아서
        // 바인딩되는 것을 알 수 있다.
        GLCall( glUseProgram(0) );
        GLCall( glBindBuffer(GL_ARRAY_BUFFER, 0) );
        GLCall( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0) );
        GLCall( glBindVertexArray(0) );



	do{
		// Clear the screen
		GLCall( glClear( GL_COLOR_BUFFER_BIT );

                // set shader and set uniform color
                GLCall( glUseProgram(shader) );
                GLCall( glUniform4f(u_Color, red, 0.3, 0.8, 1.0) );

                // Bind vertex buffer and attribute layout
                // GLCall( glBindBuffer(GL_ARRAY_BUFFER, buffer) );
                // GLCall( glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), 0) );
                // GLCall( glEnableVertexAttribArray(0) );

                // Instead of binding vertex buffer, attrib pointer, just bind Vertex Array Object
                /*
                	이번 Vertex Array Object의 핵심이 되는 내용:
                    매 draw call 마다 vertex buffer, attrib pointer을 바인딩해줄 필요 없이
                    VAO 하나만 바인딩해주면 얘네 둘을 알아서 바인딩해준다.
                */
                GLCall( glBindVertexArray(vao) );

                // 인덱스 버퍼는 VAO에 포함되지 않으므로 따로 bind 해주어야함
                GLCall( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo) );

		// Draw
		GLCall( glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr)) );

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

                // increment red
                if (red < 0.0f || red > 1.0f)
                    step *= -1.0;
                red += step;

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




	// VBO, VAO 삭제
	GLCall( glDeleteBuffers(1, &buffer) );
	GLCall( glDeleteVertexArrays(1, &vao) );
	GLCall( glDeleteProgram(shader) );




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

	return 0;
}


VAO를 사용하는 것과 그냥 매번 Vertex Buffer와 Layout(즉 glVertexAttribPointer를 매번 호출)을 지정해주는 것중에 뭐가 더 좋을까? 상황에 따라 다르지만, 대부분의 상황에서 VAO를 사용하는 게 편의성과 성능면에서 우월하다. 실제로 VAO를 사용하는 것이 OpenGL이 권장하는 방식이기도 하다.

저작자표시 비영리 변경금지 (새창열림)

'computer graphics > OpenGL' 카테고리의 다른 글

[OpenGL] 9. Textures  (0) 2022.04.09
[OpenGL] 8. 추상화(Abstraction)와 렌더러  (0) 2022.04.02
[OpenGL] 6. Uniform  (0) 2022.02.27
[OpenGL] 5. Error Handling (1)  (0) 2022.02.24
[OpenGL] 4. Index Buffer  (0) 2022.02.19
'computer graphics/OpenGL' 카테고리의 다른 글
  • [OpenGL] 9. Textures
  • [OpenGL] 8. 추상화(Abstraction)와 렌더러
  • [OpenGL] 6. Uniform
  • [OpenGL] 5. Error Handling (1)
갬장장이
갬장장이
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.