ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Modern OpenGL 강좌 - 삼각형 그리기( 렌더링, Vertex Array Object, Vertex Buffer Object)
    OpenGL/Modern OpenGL 강좌(3.3) 2017. 7. 14. 14:47

    삼각형을 구성하는 세 개의 vertex(정점, 꼭짓점)의 위치와 색상 데이터를 GPU로 전송하여 렌더링 결과를 화면에 출력하는 방법에 대해 설명합니다.



    기본적인 GLFW와 GLEW 라이브러리 사용법을 설명했던 다음 포스팅에 이어서 진행하겠습니다.


    Modern OpenGL 강좌 - GLFW와 GLEW 라이브러리 기본 사용방법

    http://webnautes.tistory.com/1103




    최초 작성.  2017.7.14

    OpenGL 3.3을 기준으로 하고 있습니다.

    Vulkan이 안드로이드에 포함된 이후라 좀 늦은 감있지만 준비해놓은 거라 올립니다.

    개인적으로 공부하며 정리한거라 어색한 점이나 틀린점이 있을 수 있으니 감안해주세요..





    1. OpenGL의 좌표계

    2. 삼각형을 구성하는 vertex 데이터 정의

    3. vertex 데이터를 GPU로 전송 -  Vertex Buffer Object(VBO)

    4. vertex 데이터를 처리할 코드 - Shader

       4.1. Vertex Shader

       4.2. Fragment Shader

    5. Shader를 GPU에서 실행할 수 있도록  - Program 객체

    6. 하나의 물체마다 하나씩 생성 - Vertex Array Object(VAO)

    7. vertex 데이터와 처리 코드 연결 - Vertex Shader의 속성과 Vertex Buffer Object(VBO) 연결

    8. 화면에 오브젝트 그리기(렌더링)

    9. 관련 포스팅

       9.1. Visual Studio 2017에 OpenGL 개발 환경 만들기 ( GLFW / GLEW )

       9.2. Modern OpenGL 강좌 - GLFW와 GLEW 라이브러리 기본 사용방법

    10. 전체 소스 코드

    11. 참고한 곳






    1. OpenGL의 좌표계

    그래픽스에서 보통 왼쪽 상단에 원점이 있는 좌표계를 사용합니다.

    그래서 아래로 갈수록 Y값이 증가하게 됩니다.





    OpenGL에서 vertex의 좌표를 지정할때 정규화된 디바이스 좌표계(Normalized Device Coordinates - NDC)를 사용합니다.  

    Y값이 위로 올라갈수록 커지며  중앙에 원점 (0,0)이 위치합니다.

    좌표로 실수값을 사용하며  X와 Y의 범위가 각각 [-1, 1]입니다.  

    3차원인 경우에는 Z축의 범위도 [-1, 1]이고 화면 안쪽으로 갈수록 값이 감소합니다.

    x,y,z의 좌표가 [-1,1] 범위를 벗어나면 화면에서 안보이게 됩니다.






    이제 삼각형을 그려보겠습니다.  보통 다음 순서로 진행됩니다.


    1. shader 소스 코드 로드 및 컴파일

    2. 버퍼 생성하여 vertex 데이터 로드

    3. 버퍼와 shader 변수 연결

    4. 렌더링




    2. 삼각형을 구성하는 vertex 데이터 정의

    OpenGL에서 삼각형을 그리려면 삼각형을 구성하는 vertex 3개에 대한 속성을 입력해주면 됩니다.

    여기에서는 vertex의 속성 중 position과 color를 각각 float 타입의 배열에 넣어서 입력으로 사용합니다.


    float position[] = {
       0.0f,  0.5f, 0.0f, //vertex 1  위 중앙
       0.5f, -0.5f, 0.0f, //vertex 2  오른쪽 아래
       -0.5f, -0.5f, 0.0f //vertex 3  왼쪽 아래
    };

    float color[] = {
       1.0f, 0.0f, 0.0f, //vertex 1 : RED (1,0,0)
       0.0f, 1.0f, 0.0f, //vertex 2 : GREEN (0,1,0)
       0.0f, 0.0f, 1.0f  //vertex 3 : BLUE (0,0,1)
    };




    다음 화면은 vertex 데이타를  가지고  실제 렌더링한 결과입니다.

    position으로 지정한 vertex  위치에 대응하는 color에서 지정한 색이 보입니다.

    평면 삼각형이므로 모든 vertex의 z좌표는 0.0입니다.




    삼각형을 구성하는 3개의 vertex에 대한 컬러만 지정했는데 렌더링 결과 삼각형을 구성하는 픽셀이 모두 컬러를 가졌습니다.

    이것은 래스터화(rasterization) 단계에서 각 fragment가 surface 상의 위치에 기반하여 보간(interpolation)된 컬러값을 갖기 때문입니다.  

    삼각형의 꼭지점에 red, green, blue가 위치하고 두 컬러 사이에는 다홍색, 노란색, 청록색이 위치하게 됩니다.

    그리고 세가지 컬러가 섞이는 중앙에는 흰색이 위치합니다.




    3. vertex 데이터를 GPU로 전송 -  Vertex Buffer Object(VBO)

    Buffer Object(VBO)는 vertex 데이터를 GPU 메모리에 복사하기 위해 사용됩니다.

    GPU의 메모리에 vertex 데이터(position, color, normal vector 등)를 미리 복사해줍니다.

    렌더링시 CPU를 거치지 않고 GPU에서 바로 데이터 접근해서 처리하는게 가능해지므로 렌더링 속도가 빨라집니다.


    position 배열과 color 배열에 저장되어 있는 vertex 속성 데이터를 각각 VBO에 복사해줍니다.

    CPU와 GPU간의 전송은 느리기 때문에 한번에 여러 vertex의 정보를 전송합니다.

    포스팅에서는  VBO당 하나의 속성을 저장하고 있지만 하나의 VBO에 여러가지 속성을 저장해도 됩니다.


    glGenBuffers(1, &trianglePositionVertexBufferObject);
    glBindBuffer(GL_ARRAY_BUFFER, trianglePositionVertexBufferObject);
    glBufferData(GL_ARRAY_BUFFER, sizeof(position), position, GL_STATIC_DRAW);

    glGenBuffers(1, &triangleColorVertexBufferObject);
    glBindBuffer(GL_ARRAY_BUFFER, triangleColorVertexBufferObject);
    glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);



    1. 새로운 버퍼 객체 ID 생성


    void glGenBuffers(GLsizei n​, GLuint * buffers​);


    glGenBuffers 함수는 n개의 새로운 버퍼 객체를 생성하고 객체를 참조할 때 사용할 n개의 부호없는 정수값인 ID를 buffers 배열에 리턴합니다.

    버퍼 객체에 할당되는 메모리는 OpenGL에 의해서 관리되기 때문에 메모리를 가리키는 포인터 대신에 인덱스가 반환됩니다.


    버퍼 객체에 저장할 데이터 타입은 다음 단계에서 결정된다.



    2. 버퍼 객체에 저장할 데이터 타입을 지정


    void glBindBuffer(GLenum target​, GLuint buffer​);


    버퍼 객체에 저장할 데이터 종류(target)를 지정해주어 최적의 메모리를 버퍼 객체에 할당할 준비를 합니다.


    target을 GL_ARRAY_BUFFER로 지정하면 Vertex 속성을 저장하기 위한 메모리를 버퍼 객체(buffer​에 저장된 ID로 참조)를 위해 준비합니다.



    3. 실제로 버퍼 객체를 위한 비디오 메모리 공간을 할당하고 vertex 데이터를  버퍼 객체에 복사합니다.

    void glBufferData(GLenum target, GLsizeiptr size, const GLvoid * data, GLenum usage);


    data로 지정한 메모리로부터 size 크기만큼 복사하게 됩니다.



    마지막 파라미터는 vertex 데이터를 어떻게 사용할지 결정합니다.  

    • GL_STATIC_DRAW - 한번 vertex 데이터 업로드 후, 변경이 없는 경우 사용한다.

    • GL_DYNAMIC_DRAW - 애니메이션처럼 vertex 데이터가 자주 바뀌는 경우 사용한다.  vertex 데이터 바뀔 때마다 다시 업로드 된다.

    • GL_STREAM_DRAW - 사용자 인터페이스처럼 계속해서 vertex 데이터가 변경되는 경우 사용한다. vertex 데이터 바뀔 때마다 다시 업로드 된다.



    마지막 파라미터는 접근 빈도와 접근 유형의 조합으로 구성됩니다.  (9가지)

    잘 선택하면 OpenGL 성능이 향상됩니다.


    접근 빈도

    • STREAM - VBO 데이터가 매 프레임마다 변경된다.

    • STATIC - 한번 데이터 업로드 후, VBO 데이터 변경없이 계속 사용됨.

    • DYNAMIC - VBO 데이터가 반복적으로 자주 변경된다.


    접근 유형

    • DRAW - 어플리케이션에서 기록한 데이터를 OpenGL에서 읽을때 사용한다. 예를 들어 렌더링에 사용되는 vertex 데이터는 DRAW로 지정한다.

    • READ - 어플리케이션의 요청으로 GL에서 데이터를 어플리케이션에게 전송한다.

    • COPY - GL에서 데이터를 읽어온 후, 다시 GL에서 화면에 그리는데 사용한다.





    4. vertex 데이터를 처리할 코드 - Shader

    shader 코드는 컴파일된 후, GPU에서 렌더링 파이프라인의 일부로써 실행됩니다.


    vertex 데이터는 하나 이상의 속성으로 구성됩니다.

    vertex 데이터의 각 속성은 vertex shader 코드의 입력 속성 변수에 연결되어 사용됩니다.


    속성은 하나 이상의 컴포넌트를 가지는 벡터로 정의할 수 있습니다. 최대 4개의 컴포넌트를 가집니다.

    속성이 shader 변수로 로드되었을 때, 제공되지 않은 컴포넌트 값들은 디폴트값으로 채워집니다.


    프로그램에서 저장시        vertex shader의 입력

    texture

    {s,t}                                   {s,t,0,1}


    position

    {x,y,z}                                 {x.y,z,1}


    color

    {r,g,b,a}                              {r,g,b,a}

    또는

    {r,g,b}




    4.1. Vertex Shader

    VBO에 저장되어 있는 데이터를 GPU상에서 처리하게 될 Vertex Shader 소스 코드를 작성합니다.

    vertex 속성 중 position만 처리하고 color나 texture coordinate 같은 속성은 다음 단계로 넘겨주는 역활을 합니다.


    • #version 전처리기 지시어는 사용되는 shader 언어의 버전을 명시합니다. OpenGL 3.3 이후 부터는 OpenGL 버전과 shader 언어의 버전이 일치합니다.

    • in 키워드를 사용하여 Vertex Shader의 입력(=vertex 속성)을 지정해 줍니다.

    • positionAttribute, colorAttribute는 각각 3차원 벡터(vec3)로 vertex 속성 중 (x,y,z), (r, g, b)값을 Vertex Shader의 입력으로 받는 변수입니다.

    • 앞에서 VBO에 저장한 vertex 데이터와 vertex 속성 변수간의 연결에 대해서는 나중에 다룹니다.

    • vec3 타입의 position 변수의 값을  vec4 타입의 gl_Position 전역변수에 대입하고 있는데 4번째 컴포넌트인 w를 위해 1.0을 지정해주고 있습니다. homogenous 좌표로 저장하기 위해서인데 자세한건 나중에 다룹니다.

    • 각 vertex의 color 속성의 경우 out 키워드로 지정된 출력 변수 passColorAttribute로 대입되어 Fragment Shader로 전달됩니다.


    const GLchar* vertexShaderSource =
    "#version 330 core\n"
    "in vec3 positionAttribute;"
    "in vec3 colorAttribute;"
    "out vec3 passColorAttribute;"
    "void main()"
    "{"
    "gl_Position = vec4(positionAttribute, 1.0);"

         "passColorAttribute = colorAttribute;"
    "}";






     스크린 상에서 vertex의 최종 위치를 결정. 3D 위치를 2D 스크린으로 투영하여 얻는다. ??? vertex의 positon을 clip 공간 position으로 변환합니다.??

    gl_Position은 screen space에서의 vertex 위치로 homogeneous 좌표를 사용합니다.??

    color 속성은 다음 단계로 넘겨주기 위해 out 변수로 선언된 color_from_vshader에 대입합니다.


    3D좌표를 2D 좌표로 변환(projection)하기 위해 모든 vertex에 projection matrix를 곱합니다. 그리고나서 GPU는 gl_Position의 W 컴포넌트로 나머지 컴포넌트를 나누어서 화면상의 좌표로 변환합니다.





    Shader 소스 코드를 GPU에서 사용하려면 컴파일을 해야 합니다. 다음 과정으로 이루어집니다.

    • glCreateShader 함수의 인자로 GL_VERTEX_SHADER를 사용하여 Vertex Shader 객체를 생성합니다.

    • glShaderSource 함수로 vertexShaderSource에 문자열로 저장되어 있는 Shader 소스 코드를 불러옵니다.

    • glCompileShader 함수로 Vertex Shader 객체를 컴파일 합니다.

    • glGetShaderiv 함수를 사용하여 Vertex Shader 객체의 컴파일 상태를 검사해서 문제 있으면 에러를 출력합니다.



    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    GLint result;
    GLchar errorLog[512];

    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &result);
    if (!result)
    {
       glGetShaderInfoLog(vertexShader, 512, NULL, errorLog);
       cerr << "ERROR: vertex shader 컴파일 실패\n" << errorLog << endl;
       glDeleteShader(vertexShader);
       return false;
    }




    4.2. Fragment Shader

    Vertex Shader에서 전달한 vertex의 color 속성인 passColorAttribute값을 받아서 출력 변수 fragmentColor에 전달합니다.


    Vertex Shader에서 3개의 vertex에 대한 처리를 했지만 Fragment Shader에서는 더 많은 수의 fragment를 대상으로 처리를 해야합니다.


    렌더링 파이프라인의 바로 전 단계인 래스터화(rasterization) 단계에서 vertex에 의해서 구성되는 primitive  내부 영역이 fragment로 변환되었기 때문입니다.

    이 때 color 속성도 fragment별로 보간된 값을 갖게 됩니다.


    OpenGL에서 color는 red, green, blue, alpha 컴포넌트로 구성되기 때문에 vec4를 출력 변수의 타입으로 사용합니다. color의 컴포넌트는 0.0~1.0 사이의 값을 갖습니다.


    const GLchar* fragmentShaderSource =
       "#version 330 core\n"
       "in vec3 passColorAttribute;"
       "out vec4 fragmentColor;"
       "void main()"
       "{"
       "fragmentColor = vec4(passColorAttribute, 1.0);"
       "}";




    컴파일 과정은 Vertex Shader와 동일하므로 생략합니다.




    5. Shader를 GPU에서 실행할 수 있도록  - Program 객체


    program 객체에 포함된 shader를 사용하려면 화면에 오브젝트를 그려주기 전에 glUseProgram함수를 호출하여 program 객체를 활성화 시켜주면 됩니다.



    glCreateProgram() 함수를 호출하여 객체를 생성합니다.

    컴파일된 Vertex Shader 객체와 Fragment Shader 객체를 glAttachShader()함수를 이용하여 Program 객체에 연결합니다.

    Program 객체를 OpenGL 코어에 연결하기 위해 glLinkProgram() 함수를 호출합니다.

    glGetProgramiv() 함수로 연결이 성공했는지 확인합니다.


    실제로 Program 객체에 포함된 Shader 객체들이 렌더링 과정을 처리하기 위해서는 glUseProgram() 함수가 호출되어야 합니다.


    triangleShaderProgramID = glCreateProgram();

    glAttachShader(triangleShaderProgramID, vertexShader);
    glAttachShader(triangleShaderProgramID, fragmentShader);

    glLinkProgram(triangleShaderProgramID);

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    glGetProgramiv(triangleShaderProgramID, GL_LINK_STATUS, &result);
    if (!result) {
       glGetProgramInfoLog(triangleShaderProgramID, 512, NULL, errorLog);
       cerr << "ERROR: shader program 연결 실패\n" << errorLog << endl;
       return false;
    }




    6. 하나의 물체마다 하나씩 생성 - Vertex Array Object(VAO)

    하나의 오브젝트를 구성하는 position, color같은 vertex 속성들을 개별 Vertex Buffer Object(VBO)에 저장하고 하나의 VAO로 묶을 수 있습니다.  


    VBO 다음에 다룹니다. 예를 들어 좌표축 오브젝트를 위한 VAO와 큐브 오브젝트를 위한 VAO를 따로 두고  원하는 오브젝트를 그리기전에 VAO를 바인딩해주고 draw 함수를 호출하면 됩니다.


    VAO를 생성 및 바인딩을 해준 이후, glVertexAttribPointer 호출하면 지정한 Vertex Shader 속성 변수와 Vertex 버퍼 객체(VBO)에 저장된 데이터간의 연결 정보가 VAO에 저장됩니다.

    VAO에는  Vertex 데이터가 직접 저장되는 게 아니라 연결 정보만 저장됩니다.


    VAO는 VBO에 저장된 데이터 타입과 어떤 속성 변수가 데이터를 가져가게 되는지 저장하고 있게 됩니다.



    glGetAttribLocation 호출전에 Vertex Array Object 생성하고 바인딩해야 합니다.


    glGenVertexArrays(1, &triangleVertexArrayObject);
    glBindVertexArray(triangleVertexArrayObject);




    Vertex Shader의 position/color vertex 속성을 VBO와 연결해줍니다. 자세한 내용은 다음 장에서 다룹니다.


    GLint positionAttribute = glGetAttribLocation(triangleShaderProgramID, "positionAttribute");
    if (positionAttribute == -1) {
       cerr << "position 속성 설정 실패" << endl;
       return false;
    }
    glBindBuffer(GL_ARRAY_BUFFER, trianglePositionVertexBufferObjectID);
    glVertexAttribPointer(positionAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(positionAttribute);


    GLint colorAttribute = glGetAttribLocation(triangleShaderProgramID, "colorAttribute");
    if (colorAttribute == -1) {
       cerr << "color 속성 설정 실패" << endl;

       return false;
    }
    glBindBuffer(GL_ARRAY_BUFFER, triangleColorVertexBufferObjectID);
    glVertexAttribPointer(colorAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(colorAttribute);




    Vertex Array Object를 사용하기 전까지는 언바인딩 상태로 둡니다.


    glBindVertexArray(0);




    7. vertex 데이터와 처리 코드 연결 - Vertex Shader의 속성과 Vertex Buffer Object(VBO) 연결

    Vertex Shader 코드에 선언된 vertex 속성 변수에서 Vertex Buffer Object에 저장된 vertex 데이터를 접근하는 방법을 지정해줘야 합니다.


    GLint positionAttribute = glGetAttribLocation(triangleShaderProgramID, "positionAttribute");
    if (positionAttribute == -1) {
       cerr << "position 속성 설정 실패" << endl;
       return false;
    }
    glBindBuffer(GL_ARRAY_BUFFER, trianglePositionVertexBufferObjectID);
    glVertexAttribPointer(positionAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(positionAttribute);



    동작 순서는 다음과 같습니다.

    1. glGetAttribLocation함수를 사용하여 triangleShaderProgramID 프로그램 객체로부터 이름이 positionAttribute인 속성 변수가 바인딩된 인덱스를 리턴받아서 positionAttribute 변수에 저장합니다.

    2. trianglePositionVertexBufferObjectID 버텍스 버퍼 객체를 바인딩합니다.

    3. glVertexAttribPointer함수를 사용하여 positionAttribute 변수에 저장된 인덱스가 가리키는 position 속성이  버텍스 버퍼 객체에서 어떻게 데이터를  가져올 수 있는지 지정해줍니다.

    4. Vertex Shader의 position 속성과 버텍스 버퍼 객체상의 position 데이터간의 연결이 동작하기 위해서는 glEnableVertexAttribArray 함수를 사용하여  positionAttribute을 활성화해야 합니다.




    glVertexAttribPointer(positionAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);


    • positionAttribute : 설정할 vertex 속성으로 position을 지정

    • 3 : vertex 속성의 크기를 지정. position vertex 속성은 vec3이므로 3개의 값으로 구성된다.

    • GL_FLOAT : 데이터 타입으로 GL_FLOAT 지정. GLSL에서 vec*는 float 타입으로 구성된다.

    • GL_FALSE : 실수 데이터를 직접 사용한다. GL_TRUE이면 정규화해서 사용

    • 0 : vertex 버퍼 객체에 연속적으로 저장되어 있는 vertex 속성 간의 간격이다. 여기에서는 position 속성만 배열에 있으므로 간격은 0이다.

    • 0 : position 데이터가 시작되는 위치이다.



    color 속성에 대해서도 같은 방식으로 지정해주므로 설명을 생략합니다.




    8. 화면에 오브젝트 그리기(렌더링)

    1. glUseProgram() 함수를 호출하여  triangleShaderProgramID 프로그램 객체에 포함된 쉐이더들을 렌더링 처리시 사용할 것임을 알립니다.

    2. glBindVertexArray() 함수를 호출하여 triangleVertexArrayObject에 포함되어 있는 버텍스 버퍼 객체(VBO)들을 드로잉 함수에서 사용할 것임을 알립니다.

    3. glDrawArrays()함수를 호출하면 실제 렌더링이 시작되어 화면에 삼각형을 그리게 됩니다.



    glUseProgram(triangleShaderProgramID);
    glBindVertexArray(triangleVertexArrayObject);


    while (!glfwWindowShouldClose(window)) {  

       . . . . . . . . . . . . . . . . . . . . . .  . . . . . . .

       glClearColor(0, 0, 0, 1); // Set background colour to BLACK
       glClear(GL_COLOR_BUFFER_BIT); //glClearColor함수에서 지정한 컬러로 화면을 지운다.


       //3개의 vertex를 사용하여 삼각형을 그린다.
       glDrawArrays(GL_TRIANGLES, 0, 3);


       . . . . . . . . . . . . . . . . . . . . . .  . . . . . . .



       glfwSwapBuffers(window);
       glfwPollEvents();

    }



    glDrawArrays의 첫번째 아규먼트는 그리고자하는 OpenGL primitive type입니다. 삼각형의 경우에는 GL_TRIANGLES입니다.

    두번째 아규먼트는 vertex array의 시작 인덱스이고 세번째 아규먼트는 몇개의 vertex를 사용하여 그릴건지 지정하는데 하나의 삼각형을 그릴것이므로 3입니다.




    GPU는 vertex를 연결하여 삼각형을 구성합니다.

    이를 위해 vertex array에서 지정한 순서대로 vertex를 꺼내어 3개씩 그룹화합니다.

    그룹화하는 방식에는 3가지가 있습니다.


    • GL_TRIANGLES - vertex 3개씩 가져와 각각 삼각형을 구성합니다.

    • GL_TRIANGLE_STRIP - 먼저 구성된 삼각형의 마지막 두개의 vertex를 재사용하여 다음에 만들어지는 삼각형의 처음 두개의 vertex로 사용합니다.

    • GL_TRIANGLE_FAN - 삼각형의 첫번째 vertex를 다음에 만들어지는 삼각형들에서 공유합니다.






    다음은 GL_TRIANGLE_FAN  타입으로 삼각형을 구성하는 과정입니다.




    실행결과입니다..




    9. 관련 포스팅

    9.1. Visual Studio 2017에 OpenGL 개발 환경 만들기 ( GLFW / GLEW )

    Visual Studio 2017에서 GLFW 라이브러리를 사용하여 OpenGL 개발을 시작하기 위해 필요한 설정 방법을 다룹니다.

    http://webnautes.tistory.com/1102


    9.2. Modern OpenGL 강좌 - GLFW와 GLEW 라이브러리 기본 사용방법

    GLFW 라이브러리를 사용하여 윈도우와 OpenGL context를 생성하고 GLEW 라이브러리를 사용하여 윈도우를 단색으로 채우는 간단한 예제를 설명합니다.

    http://webnautes.tistory.com/1103




    10. 전체 소스 코드

    #include <GL/glew.h>
    #include <GLFW/glfw3.h>
    #include <iostream>


    using namespace std;


    int framebufferWidth, framebufferHeight;
    GLuint triangleVertexArrayObject;
    GLuint triangleShaderProgramID;
    GLuint trianglePositionVertexBufferObjectID, triangleColorVertexBufferObjectID;


    bool initShaderProgram() {

    //#3
    const GLchar* vertexShaderSource =
    "#version 330 core\n"
    "in vec3 positionAttribute;"
    "in vec3 colorAttribute;"
    "out vec3 passColorAttribute;"
    "void main()"
    "{"
    "gl_Position = vec4(positionAttribute, 1.0);"
    "passColorAttribute = colorAttribute;"
    "}";


    //#4
    const GLchar* fragmentShaderSource =
    "#version 330 core\n"
    "in vec3 passColorAttribute;"
    "out vec4 fragmentColor;"
    "void main()"
    "{"
    "fragmentColor = vec4(passColorAttribute, 1.0);"
    "}";



    GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    GLint result;
    GLchar errorLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &result);
    if (!result)
    {
    glGetShaderInfoLog(vertexShader, 512, NULL, errorLog);
    cerr << "ERROR: vertex shader 컴파일 실패\n" << errorLog << endl;
    glDeleteShader(vertexShader);
    return false;
    }


    GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);

    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &result);
    if (!result)
    {
    glGetShaderInfoLog(fragmentShader, 512, NULL, errorLog);
    cerr << "ERROR: fragment shader 컴파일 실패\n" << errorLog << endl;

    return false;
    }




    //#5
    triangleShaderProgramID = glCreateProgram();

    glAttachShader(triangleShaderProgramID, vertexShader);
    glAttachShader(triangleShaderProgramID, fragmentShader);

    glLinkProgram(triangleShaderProgramID);


    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);


    glGetProgramiv(triangleShaderProgramID, GL_LINK_STATUS, &result);
    if (!result) {
    glGetProgramInfoLog(triangleShaderProgramID, 512, NULL, errorLog);
    cerr << "ERROR: shader program 연결 실패\n" << errorLog << endl;
    return false;
    }

    return true;
    }



    bool defineVertexArrayObject() {

    //#1
    //삼각형을 구성하는 vertex 데이터 - position과 color
    float position[] = {
    0.0f,  0.5f, 0.0f, //vertex 1  위 중앙
    0.5f, -0.5f, 0.0f, //vertex 2  오른쪽 아래
    -0.5f, -0.5f, 0.0f //vertex 3  왼쪽 아래
    };

    float color[] = {
    1.0f, 0.0f, 0.0f, //vertex 1 : RED (1,0,0)
    0.0f, 1.0f, 0.0f, //vertex 2 : GREEN (0,1,0)
    0.0f, 0.0f, 1.0f  //vertex 3 : BLUE (0,0,1)
    };



    //#2
    //Vertex Buffer Object(VBO)를 생성하여 vertex 데이터를 복사한다.
    glGenBuffers(1, &trianglePositionVertexBufferObjectID);
    glBindBuffer(GL_ARRAY_BUFFER, trianglePositionVertexBufferObjectID);
    glBufferData(GL_ARRAY_BUFFER, sizeof(position), position, GL_STATIC_DRAW);

    glGenBuffers(1, &triangleColorVertexBufferObjectID);
    glBindBuffer(GL_ARRAY_BUFFER, triangleColorVertexBufferObjectID);
    glBufferData(GL_ARRAY_BUFFER, sizeof(color), color, GL_STATIC_DRAW);



    //#6
    glGenVertexArrays(1, &triangleVertexArrayObject);
    glBindVertexArray(triangleVertexArrayObject);


    GLint positionAttribute = glGetAttribLocation(triangleShaderProgramID, "positionAttribute");
    if (positionAttribute == -1) {
    cerr << "position 속성 설정 실패" << endl;
    return false;
    }
    glBindBuffer(GL_ARRAY_BUFFER, trianglePositionVertexBufferObjectID);
    glVertexAttribPointer(positionAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);

    glEnableVertexAttribArray(positionAttribute);

    GLint colorAttribute = glGetAttribLocation(triangleShaderProgramID, "colorAttribute");
    if (colorAttribute == -1) {
    cerr << "color 속성 설정 실패" << endl;
    return false;
    }
    glBindBuffer(GL_ARRAY_BUFFER, triangleColorVertexBufferObjectID);
    glVertexAttribPointer(colorAttribute, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(colorAttribute);


    glBindVertexArray(0);


    return true;
    }




    void framebufferSizeCallback(GLFWwindow* window, int width, int height)
    {
    //처음 2개의 파라미터는 viewport rectangle의 왼쪽 아래 좌표
    //다음 2개의 파라미터는 viewport의 너비와 높이이다.
    //framebuffer의 width와 height를 가져와 glViewport에서 사용한다.
    glViewport(0, 0, width, height);

    framebufferWidth = width;
    framebufferHeight = height;
    }



    void errorCallback(int errorCode, const char* errorDescription)
    {
    cerr << "Error: " << errorDescription << endl;
    }



    void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
    {
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
    glfwSetWindowShouldClose(window, GLFW_TRUE);
    }




    int main()
    {

    glfwSetErrorCallback(errorCallback);   


    if (!glfwInit()) {

    cerr << "Error: GLFW 초기화 실패" << endl;
    std::exit(EXIT_FAILURE);
    }



    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    //glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
    glfwWindowHint(GLFW_SAMPLES, 4);


    GLFWwindow* window = glfwCreateWindow(
    800,
    600,
    "OpenGL Example",
    NULL, NULL);
    if (!window) {

    glfwTerminate();
    std::exit(EXIT_FAILURE);
    }


    glfwMakeContextCurrent(window);


    glfwSetKeyCallback(window, keyCallback);
    glfwSetFramebufferSizeCallback(window, framebufferSizeCallback);



    glewExperimental = GL_TRUE;
    GLenum errorCode = glewInit();  
    if (GLEW_OK != errorCode) {

    cerr << "Error: GLEW 초기화 실패 - " << glewGetErrorString(errorCode) << endl;

    glfwTerminate();
    std::exit(EXIT_FAILURE);
    }



    if (!GLEW_VERSION_3_3) {

    cerr << "Error: OpenGL 3.3 API is not available." << endl;

    glfwTerminate();
    std::exit(EXIT_FAILURE);
    }


    cout << "OpenGL version: " << glGetString(GL_VERSION) << endl;
    cout << "GLSL version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << endl;
    cout << "Vendor: " << glGetString(GL_VENDOR) << endl;
    cout << "Renderer: " << glGetString(GL_RENDERER) << endl;




    if (!initShaderProgram()) {

    cerr << "Error: Shader Program 생성 실패" << endl;

    glfwTerminate();
    std::exit(EXIT_FAILURE);
    }



    if (!defineVertexArrayObject()) {

    cerr << "Error: Shader Program 생성 실패" << endl;

    glfwTerminate();
    std::exit(EXIT_FAILURE);
    }





    glfwSwapInterval(1);



    double lastTime = glfwGetTime();
    int numOfFrames = 0;
    int count = 0;





    glUseProgram(triangleShaderProgramID);
    glBindVertexArray(triangleVertexArrayObject);


    while (!glfwWindowShouldClose(window)) {  


    double currentTime = glfwGetTime();
    numOfFrames++;
    if (currentTime - lastTime >= 1.0) {

    printf("%f ms/frame  %d fps \n", 1000.0 / double(numOfFrames), numOfFrames);
    numOfFrames = 0;
    lastTime = currentTime;
    }



    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);


    glDrawArrays(GL_TRIANGLES, 0, 3);


    count++;

    glfwSwapBuffers(window);
    glfwPollEvents();

    }


    glUseProgram(0);
    glBindVertexArray(0);


    glDeleteProgram(triangleShaderProgramID);
    glDeleteBuffers(1, &trianglePositionVertexBufferObjectID);
    glDeleteBuffers(1, &triangleColorVertexBufferObjectID);
    glDeleteVertexArrays(1, &triangleVertexArrayObject);
    glfwTerminate();  

    std::exit(EXIT_SUCCESS);
    }




    11. 참고한 곳


    https://learnopengl.com


    https://open.gl/context


    http://duriansoftware.com/joe/An-intro-to-modern-OpenGL.-Table-of-Contents.html


    https://www.ntu.edu.sg/home/ehchua/programming/opengl/CG_BasicsTheory.html


    https://en.wikipedia.org/wiki/Vertex_Buffer_Object




    포스트 작성시에는 문제 없었지만 이후 문제가 생길 수 있습니다.
    댓글로 알려주시면 빠른 시일내에 답변을 드리겠습니다.

    여러분의 응원으로 좋은 컨텐츠가 만들어집니다. 지금 본 내용이 도움이 되었다면 유튜브 구독 부탁드립니다. 감사합니다 : )

    유튜브 구독하기


    댓글 6

    • Favicon of https://whilescape.tistory.com BlogIcon whilescape 2019.03.09 00:48 신고


      webnautes님 글 잘 봤습니다!

      이전 방식의 opengl(glBegin, glEnd)로 opengl에 입문하고 programmable? opengl을 공부 중입니다.
      vao와 vbo의 연결 관계, 연결 방식에 대해서 확실하게 이해를 하지 못하여 이 곳을 찾게 되었습니다.

      // 질문있습니다. vao와 vbo 연결에 대한 문제

      glBindVertexArray(vao);
      ~~VBO 관련
      glBindVertexArray(0);
      // 이런 식으로 묶어주고?
      .
      .
      glUseProgram(program);
      glBindVertexArray(vao);
      // 필요할 때 사용한다

      라고 이해해도 되는 것일까요?

      • Favicon of https://webnautes.tistory.com BlogIcon webnautes 2019.03.09 09:18 신고


        예를 들어 로봇을 만든다면..

        머리,다리,팔,몸통를 각각의 VBO로 생성한 다음
        VAO를 사용해 하나로 묶어줍니다.

        프로그램을 사용하여 VAO를 화면에 보여주게 됩니다.

    • jangzzang 2019.03.23 21:39


      감사합니다.
      컴퓨터그래픽스 수업듣는데 이해에 많은 도움이 됐습니다.

    • 정윤성 2019.12.30 15:56


      opengl은 directx와 같이 코드에서 내가 뭘하는지 나오지가 않아 쉽지만 헷갈렸는데요, 이렇게 바인딩하는것을 예제와 함께 보여주시니 이해하기가 쉬웠습니다. 책에서는 제가 마치 할줄 아는냥 예제코드도 없이 숙숙 지나가니 안타까웠는데 고맙습니다.

Designed by Tistory.