본문 바로가기
Opengles 3.0 with Android

Chapter 3.3 VAO 사용하여 Grid 렌더링

by SimonLee 2024. 10. 29.

이 코드는 격자를 OpenGL로 렌더링할 준비를 완료하는 함수입니다. 배열로 정점 및 인덱스 데이터를 설정한 후, 이를 OpenGL의 VBO, IBO, VAO에 바인딩하여 효율적으로 GPU에서 접근할 수 있게 합니다.

void Grid::CreateGrid(GLfloat XDim, GLfloat ZDim, int XDiv, int ZDiv)
{
    // Calculate total vertices and indices.
    GLuint vertexNum        = 3 * (XDiv+1) * (ZDiv+1) * 2;
    GLuint indexNum         = ((XDiv+1) + (ZDiv+1)) * 2;
    
    // Allocate required space for vertex and indice location.
    GLfloat* gridVertex     = new GLfloat[ vertexNum ];
    GLushort* gridIndices   = new GLushort[ indexNum ];
    
    // Store unit division interval and half length of dimension.
    GLfloat xInterval       = XDim/XDiv;
    GLfloat zInterval       = ZDim/ZDiv;
    GLfloat xHalf           = XDim/2;
    GLfloat zHalf           = ZDim/2;
    int i                   = 0;

    // Assign vertices along X-axis.
    for( int j=0; j<XDiv+1; j++){
        gridVertex[i++] = j*xInterval - xHalf;
        gridVertex[i++] = 0.0f;
        gridVertex[i++] =  zHalf;
        gridVertex[i++] = j*xInterval - xHalf;
        gridVertex[i++] = 0.0f;
        gridVertex[i++] = -zHalf;
    }

    // Assign vertices along Z-axis.
    for( int j=0; j<ZDiv+1; j++){
        gridVertex[i++] = -xHalf;
        gridVertex[i++] = 0.0f;
        gridVertex[i++] = j*zInterval - zHalf;
        gridVertex[i++] = xHalf;
        gridVertex[i++] = 0.0f;
        gridVertex[i++] = j*zInterval - zHalf;
    }

    i = 0;
    
    // Assign indices along X-axis.
    for( int j=0; j<XDiv+1; j++ ){
        gridIndices[i++] = 2*j;
        gridIndices[i++] = 2*j+1;
    }

    // Assign indices along Z-axis.
    for( int j=0; j<ZDiv+1; j++ ){
        gridIndices[i++] = ((XDiv+1)*2) + (2*j);
        gridIndices[i++] = ((XDiv+1)*2) + (2*j+1);
    }
    
    // Create name buffer object ID
	GLuint size = vertexNum*sizeof(float);
    glGenBuffers(1, &vIdGrid);
    
    // Create VBO for Grid
	glBindBuffer( GL_ARRAY_BUFFER, vIdGrid);
	glBufferData( GL_ARRAY_BUFFER, size, 0, GL_STATIC_DRAW );
	glBufferSubData( GL_ARRAY_BUFFER, 0,			size,	gridVertex );
    
    // Create IBO for Grid
	unsigned short indexSize = sizeof( unsigned short )*indexNum;
    glGenBuffers(1, &iIdGrid);
	glBindBuffer( GL_ARRAY_BUFFER, iIdGrid );
	glBufferData( GL_ARRAY_BUFFER, indexSize, 0, GL_STATIC_DRAW );
	glBufferSubData( GL_ARRAY_BUFFER, 0, indexSize,	gridIndices );
    
    // Create Vertex Array Object
    glGenVertexArrays(1, &Vertex_VAO_Id);
    glBindVertexArray(Vertex_VAO_Id);
    
    // Create VBO  and set attribute parameters
    glBindBuffer( GL_ARRAY_BUFFER, vIdGrid );
    glEnableVertexAttribArray(VERTEX_LOCATION);
    glVertexAttribPointer(VERTEX_LOCATION, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
    glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, iIdGrid );
    
    // Make sure the VAO is not changed from outside code
    glBindVertexArray(0);
    
    // Unbind buffer
	glBindBuffer( GL_ARRAY_BUFFER, 0 );
	glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 );
    delete gridVertex;
    delete gridIndices;
}

 

코드 분석 >

GLuint vertexNum = 3 * (XDiv+1) * (ZDiv+1) * 2;
GLuint indexNum = ((XDiv+1) + (ZDiv+1)) * 2;
  • vertexNum: 각 X 및 Z축 방향으로 (XDiv + 1)개의 점을 생성하고, 각 점은 3개의 GLfloat 값(X, Y, Z 좌표)을 가지므로 총 크기를 계산합니다.
  • indexNum: X축과 Z축 각각에 대해 인덱스를 설정할 수 있는 크기입니다.
GLfloat* gridVertex = new GLfloat[vertexNum];
GLushort* gridIndices = new GLushort[indexNum];
  • gridVertex: 정점 좌표를 저장하는 배열입니다.
  • gridIndices: 정점 인덱스를 저장하는 배열입니다.
GLfloat xInterval = XDim / XDiv;
GLfloat zInterval = ZDim / ZDiv;
GLfloat xHalf = XDim / 2;
GLfloat zHalf = ZDim / 2;
  • xInterval 및 zInterval은 각각 X축과 Z축 방향의 격자 간격입니다.
  • xHalf와 zHalf는 X축 및 Z축의 절반 길이로, 격자를 중심에 배치하기 위해 사용됩니다.

 

for (int j = 0; j < XDiv + 1; j++) {
    gridVertex[i++] = j * xInterval - xHalf;
    gridVertex[i++] = 0.0f;
    gridVertex[i++] = zHalf;
    gridVertex[i++] = j * xInterval - xHalf;
    gridVertex[i++] = 0.0f;
    gridVertex[i++] = -zHalf;
}

for (int j = 0; j < ZDiv + 1; j++) {
    gridVertex[i++] = -xHalf;
    gridVertex[i++] = 0.0f;
    gridVertex[i++] = j * zInterval - zHalf;
    gridVertex[i++] = xHalf;
    gridVertex[i++] = 0.0f;
    gridVertex[i++] = j * zInterval - zHalf;
}

- XDiv+1 개의 점을 X축 방향으로 설정합니다.

- 각각의 점은 gridVertex 배열에 순차적으로 X, Y, Z 좌표로 저장됩니다.

- Z축 방향으로 동일한 방식으로 정점을 설정하여 gridVertex 배열에 추가합니다.

 

for (int j = 0; j < XDiv + 1; j++) {
    gridIndices[i++] = 2 * j;
    gridIndices[i++] = 2 * j + 1;
}
for (int j = 0; j < ZDiv + 1; j++) {
    gridIndices[i++] = ((XDiv + 1) * 2) + (2 * j);
    gridIndices[i++] = ((XDiv + 1) * 2) + (2 * j + 1);
}

- 각 X축 선에 대해 두 개의 인덱스를 추가하여 정점 연결을 정의합니다.

- Z축 선의 인덱스도 유사하게 추가하여 정점 연결을 정의합니다.

 

glGenBuffers(1, &vIdGrid);
glBindBuffer(GL_ARRAY_BUFFER, vIdGrid);
glBufferData(GL_ARRAY_BUFFER, vertexNum * sizeof(float), 0, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexNum * sizeof(float), gridVertex);

glGenBuffers(1, &iIdGrid);
glBindBuffer(GL_ARRAY_BUFFER, iIdGrid);
glBufferData(GL_ARRAY_BUFFER, indexSize, 0, GL_STATIC_DRAW);
glBufferSubData(GL_ARRAY_BUFFER, 0, indexSize, gridIndices);

- vIdGrid VBO를 생성하고, gridVertex 데이터를 GPU 메모리에 전송합니다.

- iIdGrid IBO를 생성하고, gridIndices 데이터를 GPU 메모리에 전송합니다.

 

glGenVertexArrays(1, &Vertex_VAO_Id);
glBindVertexArray(Vertex_VAO_Id);
glBindBuffer(GL_ARRAY_BUFFER, vIdGrid);
glEnableVertexAttribArray(VERTEX_LOCATION);
glVertexAttribPointer(VERTEX_LOCATION, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, iIdGrid);

glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
delete[] gridVertex;
delete[] gridIndices;
  • VAO를 생성하여 VBO 및 IBO의 설정을 저장합니다.
  • VAO를 활성화하여 정점 속성(VERTEX_LOCATION)을 설정하고, 버퍼를 VAO에 바인딩합니다.
  • VAO와 VBO, IBO의 바인딩을 해제하여 외부에서 변경되지 않도록 하고, CPU 메모리에 할당된 배열을 해제합니다.

 

렌더링 코드 >

void Grid::Render()
{
    glEnable( GL_DEPTH_TEST );

    glUseProgram( program->ProgramID );
    static int k = 0;
    TransformObj->TransformPushMatrix();
    TransformObj->TransformRotate(20, 1, 0, 0);
    TransformObj->TransformRotate(k++, 0, 1, 0);
    TransformObj->TransformTranslate(0.0f, -10, -7);
    glUniformMatrix4fv( MVP, 1, GL_FALSE,(float*)TransformObj->TransformGetModelViewProjectionMatrix() );
    TransformObj->TransformPopMatrix();
    
    glBindVertexArray(Vertex_VAO_Id);
    glDrawElements(GL_LINES, ((XDivision+1) + (ZDivision+1) )*2, GL_UNSIGNED_SHORT, (void*)0);
    
}
  • TransformPushMatrix():
    • 현재 행렬을 스택에 저장하여 이후 변환 작업에 영향을 주지 않도록 합니다. 나중에 TransformPopMatrix()를 호출하여 이 시점의 행렬 상태로 복원할 수 있습니다.
  • 첫 번째 회전 TransformRotate(20, 1, 0, 0);:
    • TransformRotate(20, 1, 0, 0);는 X축을 기준으로 20도 회전을 적용합니다.
    • 이는 오브젝트가 화면에서 위아래로 약간 기울어지도록 만듭니다.
  • 두 번째 회전 TransformRotate(k++, 0, 1, 0);:
    • Y축을 기준으로 k++도 만큼 회전을 적용합니다. 이때 k는 매번 증가하는 값이므로, 오브젝트는 Y축을 중심으로 계속해서 회전하게 됩니다.
    • 이 회전은 화면에서 오브젝트가 좌우로 도는 것처럼 보입니다.
  • 이동 TransformTranslate(0.0f, -10, -7);:
    • Z축 방향으로 -7만큼, Y축 방향으로 -10만큼 이동합니다.
    • 이는 격자를 카메라에서 일정 거리만큼 떨어진 위치에 배치하도록 설정하며, 화면의 아래쪽으로 이동시킵니다.
  • glUniformMatrix4fv를 사용한 MVP 행렬 설정:
    • TransformGetModelViewProjectionMatrix()를 통해 계산된 최종 변환 행렬(MVP)을 glUniformMatrix4fv로 셰이더에 전달합니다. 이 행렬은 모든 변환이 적용된 최종 행렬입니다.
    • MVP 행렬은 모델, 뷰, 투영 행렬을 합성한 것이며, 격자가 변환된 좌표 공간에서 올바르게 렌더링되도록 합니다.