[컴퓨터그래픽스] 실습: RandomDot_GPU

Serun1017·2024년 10월 23일
0

컴퓨터그래픽스

목록 보기
14/31

이제 OpenGL Pipeline 에 따라 이전에 실습한 RandomDot_GPU.cpp 코드를 해석해보자.

1. Generate Data in CPU

먼저 CPU에서 우리가 그리고자 하는 points 데이터를 생성한다.

#include <vgl.h>
#include <math.h>
#include <InitShader.h>

struct vec2 {
	float x, y;
}
const int NUM_POINTS = 5000;

void init() {
	// 1. Generate Data in CPU
	vec2 points[5000];
	for (int i = 0; i < NUM_POINTS; i++) {
		points[i].x = (rand() % 1000) / 500.0f - 1;
		points[i].y = (rand() % 1000) / 500.0f - 1;
	}
}

2. Send Data(Array) to GPU

이제 CPU에서 만든 points의 vertex data를 GPU에 넘겨야 한다. 이때, 우리는 vertex data를 VBOs의 형태로 넘겨야 하는데, VBO는 반드시 VAO에 bind 되어야 한다.

2.1. Create a Vertex Array and bind the buffer

VAO를 생성한 후 bind 한다. 여기서 bind는 해당 VAO를 사용할 것을 임의로 알려주는 것이다.

// 2. Send Data(Array) to GPU
// 2.1. Create a Vertex Array and bind the buffer
GLuint vao; // OpenGL unsigned int
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);

이때 우리는 handler를 사용해 VAO를 bind 한다. handler 는 프로그램 밖 메모리, 즉, 운영체제가 관리하는 메모리에 접근하기 위해 사용하는 unsigned int 값이다.

void glGenVertexArrays(GLsize n, GLuint &arrays) 는 Vertex Arrays Objects를 GPU에 n 개 생성하고 이들을 관리할 수 있도록 handler 값을 *arrays 에 담는다. 따라서 glGenVertexArrays(1, &vao) 를 실행하면 GPU에 VAO가 한 개 생성되고 이를 관리할 수 있는 handler 가 GLuint vao 에 담긴다.

void glBindVertexArray(GLuint array) 는 VAO를 bind 하는 것으로, bind 한 다는 것은 handler 값으로 전달 받은 VAO를 사용하겠다는 것을 의미한다. 따라서 glBindVertexArray(vao) 를 실행하면 앞서 우리가 GPU에 생성한 VAO를 handler를 통해 사용할 수 있다.

2.2. Create a Vertex Buffer and bind the buffer

앞서 생성하고 bind 한 VAO에 VBO를 생성하고 bind 한다. 이때 VAO와 마찬가지로 handler를 이용하여 bind 한다.

// 2.2. Create a Vertex Buffer and bind the buffer
GLuint vbo;
glGenBuffers(1, &vbo);
glBindBuffer(GL_ARRAY_BUFFER, vbo);

void glGenBuffers(GLsize n, GLuint * buffers) 는 현재 bind 된 VAO 안에 VBO를 n 개 생성하고 이들을 관리할 수 있도록 handler 값을 * buffers 에 담는다. 따라서 glGenBuffers(1, &vbo) 를 실행하면 VAO 안에 VBO가 한 개 생성되고 이를 관리할 수 있는 handler 가 GLuint vbo 에 담긴다.

void glBindBuffer(GLenum target, GLuint buffer) 는 어떤 종류의 버퍼를 bind 할지를 지정한다. 이때 target은 특정 버퍼 타입을 알려주고, 그에 맞게 해당 버퍼를 활성화 한다.

Buffer Binding TargetPurpose
GL_ARRAY_BUFFERVertex attributes
GL_ATOMIC_COUNTER_BUFFERAtomic counter storage
GL_COPY_READ_BUFFERBuffer copy source
GL_COPY_WRITE_BUFFERBuffer copy destination
GL_DISPATCH_INDIRECT_BUFFERIndirect compute dispatch commands
GL_DRAW_INDIRECT_BUFFERIndirect command arguments
GL_ELEMENT_ARRAY_BUFFERVertex array indices
GL_PIXEL_PACK_BUFFERPixel read target
GL_PIXEL_UNPACK_BUFFERTexture data source
GL_QUERY_BUFFERQuery result buffer
GL_SHADER_STORAGE_BUFFERRead-write storage for shaders
GL_TEXTURE_BUFFERTexture data buffer
GL_TRANSFORM_FEEDBACK_BUFFERTransform feedback buffer
GL_UNIFORM_BUFFERUniform block storage

따라서 glBindBuffer(GL_ARRAY_BUFFER, vbo) 를 실행하면 handler 값으로 전달 받은 vbo를 GL_ARRAY_BUFFER 타입으로 사용한 다는 것을 의미한다. 이때 GL_ARRAY_BUFFER 는 버퍼에 vertex attributes를 넣을 것을 의미하며 배열 형태로 저장될 것을 의미한다.

2.3. copy my data to the buffer

이제 bind한 VBO에 데이터를 전송하여 저장한다.

// 2.3. copy my data to the buffer
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);

void glBufferData(GLenum target, GLsizeiptr size, const void * data, GLenum usage) 는 앞서 bind 한 VBO에 CPU에 저장되어 있던 vertex data 를 GPU로 전송하여 저장한다.

  • GLenum target 는 어떤 종류의 버퍼에 데이터를 넣을 것인지 명시한다.
  • GLsizeiptr size 는 데이터 크기를 바이트 단위로 지정한다.
  • const void * data 는 실제 전송할 데이터에 대한 포인터 이다.
  • GLenum usage 는 버퍼가 어떻게 사용될 것 인지 대한 정보(예상)를 전달한다.
    • 종류: GL_STREAM_DRAW, GL_STREAM_READ, GL_STREAM_COPY, GL_STATIC_DRAW, GL_STATIC_READ, GL_STATIC_COPY, GL_DYNAMIC_DRAW, GL_DYNAMIC_READ, GL_DYNAMIC_COPY

3. Load Shaders

이제 Shader 들을 load 하고 handler를 통해 사용을 한다.

// 3. Load Shaders
GLuint program;
program = InitShader("vshader.glsl", "fhsader.glsl");
glUseProgram(program);

현재 shaderprogram은 앞서 bind 한 VAO를 사용한다.

4. Connect Data with Shader

Shader 는 별도의 프로그램을 GPU에서 모든 vertex data 에 대해 각각 실행된다. 이때 Shader 는 GPU에 전달된 VBO의 구조를 알지 못하기 때문에 data의 구조를 전달해 줘야 한다.

// 4. Connect Data with Shader
GLuint vPosition = glGetAttribLocation(program, "vPosition");
glEnableVertexAttribArray(vPosition);
glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0));

GLuint glGetAttribLocation(GLuint program, )const GLchar *name) 은 vertex shader 에서 사용하는(in 으로 넣은) data를 handler로 연결한다. 때문에 glGetAttribLocation(program, "vPosition") 을 실행하면 vshader.glslin vPosition 을 사용할 수 있는 handler를 반환한다.

void glEnableVertexAttribArray(GLuint index) 는 handler 를 받아 해당 특정 속성(attribute)을 활성화 한다.

void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void * pointer)는 정점 속성 배열을 설정한다. 이를 통해 shader가 GLuint index 의 구조를 알 수 있도록 한다. 따라서 glVertexAttribPointer(vPosition, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET(0)) 을 실행하면 handler로 권한을 받은 shader의vPosition의 구조가 GL_FLOAT 2개의 숫자로 이루어져 있고, stride 가 0이고 VBO의 0번 부터 시작함을 shader가 알 수 있도록 한다.

5. Display Callback

[[Retained Mode Graphics|이전]] 의 display Callback 함수와 달리 GPU Array에 VBO 형태로 vertex data를 전송하였기 때문에 해당 VBO를 그려줄 것을 요청한다.

void display() {
	glClear(GL_COLOR_BUFFER_BIT);
	glDrawArrays(GL_POINTS, 0, NUM_POINTS);
	glFlush();
}

void glDrawArrays(GLenum mode, GLint first, GLsizei count) 는 VAO에 저장된 VBO(vertex data)를GLenum mode 형태로 GLint first 부터 GLsizei count 까지 렌더링 한다.

6. Main Function

main 함수에서는 display Callback 함수를 등록하기 전에 반드시 GPU에 vertex data를 먼저 보내줘야 한다. 따라서 init 함수를 display Callback 함수를 등록하기 전에 실행해야 한다.

int main(int argc, char** argv) {
	glutInit(&argc, argv);
	
	glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);
	glutInitWindowSize(800, 800);
	glutCreateWindow("Hello GL");

	glewExperimental = true;
	glewInit();

	// Initialize vertex data and send to GPU, connect shaders
	init();

	// Sets display callback for the current window
	glutDisplayFunc(display);
	glutMainLoop();

	return 0;
}

7. vshader.glsl

vshader.glsl 은 vertex shader로 vec4 gl_Position을 out 하여 vertex의 position data 를 파이프라인에 전달한다.

#version 330

in vec4 vPosition;
out vec4 pos;

void main() {
	gl_Position = vPosition;
	gl_Position.x = vPosition.x / 2;
	gl_Position.y = vPosition.y / 2;

	pos = vPosition;
}

8. fshader.glsl

fshader.glsl 은 fragment shader로 vec4 fColorout 하여 vertex의 color data를 파이프라인에 전달한다.

#version 330

in vec4 pos;
out vec4 fColor;

void main() {
	float r = sqrt(pos.x * pos.x + pos.y * pos.y);

	// R G B A(투명도)
	fColor = vec4(r, r, r, 1.0);
}

Shader에 대한 자세한 설명은 다음 장을 보자.

0개의 댓글