Quick Comparison
새 3.3 Core 코드는 버퍼에 정점 저장 → VAO에 해석 방식 설정 → 프로그램 사용 → draw 호출로 구성합니다. glBegin 예제는 레거시 코드이며 Core에서 실행되지 않습니다.
| 방식 | 데이터 전달 | 조건과 용도 |
|---|---|---|
| 즉시 모드 | glBegin 안에서 정점별 호출 | 1.0부터의 구형 코드 해석 |
| 클라이언트 배열 | CPU 배열 포인터와 배열 활성화 | 1.1부터의 구형 고정 기능 입력 |
| 버퍼 기반 입력 | VBO 저장소와 속성 설정 | VBO는 1.5, VAO는 3.0부터 코어. 3.3 Core 기본 경로 |
3.3 Core의 입력 계약
VBO는 정점 바이트를 저장하는 버퍼입니다. VAO는 데이터를 복사해서 담는 상자가 아니라, 어떤 버퍼를 어떤 형식으로 읽는지 기록하는 객체입니다. 셰이더의 layout(location = 0) in vec2 position과 CPU 쪽 속성 0의 설정이 맞아야 합니다.
// 현재 컨텍스트·로더가 준비된 GL 3.3 Core 초기화 구간입니다.
const float points[] = {-0.6f, -0.5f, 0.6f, -0.5f, 0.0f, 0.6f};
GLuint vao = 0, buffer = 0;
glGenVertexArrays(1, &vao);
glGenBuffers(1, &buffer);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr);
glEnableVertexAttribArray(0);
// 링크된 프로그램 사용 뒤, 프레임마다 호출합니다.
glDrawArrays(GL_TRIANGLES, 0, 3);
// 사용이 끝나고 컨텍스트가 유효할 때 정리합니다.
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &buffer);2 * sizeof(float)는 정점 하나를 건너뛸 바이트 수입니다. 마지막 nullptr는 버퍼 시작에서의 오프셋 0입니다. glBufferData의 크기는 바이트지만 glDrawArrays의 마지막 3은 정점 개수입니다. float 개수 6을 넘기면 정점 데이터 범위가 맞지 않습니다.
glVertexAttribPointer를 호출할 때의 GL_ARRAY_BUFFER 연결이 속성 설정에 기록됩니다. 이후 버퍼를 언바인딩한다고 이미 설정한 속성이 다른 버퍼를 가리키지는 않습니다. 반면 VAO의 요소 버퍼(EBO) 연결은 VAO 상태이므로 인덱스 예제를 추가할 때 같은 규칙으로 추측하지 않습니다.
전체 실행 예제
triangle.cpp는 초기화·셰이더 오류·정점·프레임·정리까지 포함합니다. 빌드 방법대로 준비한 뒤 실행 인수 0으로 실행하면 주황색 삼각형이 보입니다. 인수 1은 행렬 변환, 2는 픽셀 배열 텍스처를 사용합니다.
./build/triangle 0#include <glad/gl.h>
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <cstdio>
#include <cstdlib>
#include <vector>
// mode: 0 = triangle, 1 = matrix transform, 2 = texture.
GLuint compileShader(GLenum type, const char* source) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
GLint ok = GL_FALSE;
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
if (!ok) {
GLint length = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &length);
std::vector<char> log(length > 0 ? length : 1);
glGetShaderInfoLog(shader, static_cast<GLsizei>(log.size()), nullptr, log.data());
std::fprintf(stderr, "Shader: %s\n", log.data());
glDeleteShader(shader);
return 0;
}
return shader;
}
int main(int argc, char** argv) {
int mode = argc > 1 ? std::atoi(argv[1]) : 0;
if (mode < 0 || mode > 2) return 1;
glfwSetErrorCallback([](int code, const char* text) {
std::fprintf(stderr, "GLFW %d: %s\n", code, text);
});
if (!glfwInit()) return 1;
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, GLFW_TRUE);
GLFWwindow* window = glfwCreateWindow(640, 480, "RefDock OpenGL", nullptr, nullptr);
if (!window) {
glfwTerminate();
return 1;
}
glfwMakeContextCurrent(window);
if (!gladLoadGL(glfwGetProcAddress) || !GLAD_GL_VERSION_3_3) {
glfwDestroyWindow(window);
glfwTerminate();
return 1;
}
std::printf("GL: %s; GPU: %s\n", reinterpret_cast<const char*>(glGetString(GL_VERSION)),
reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
glfwSwapInterval(1);
const char* vertexSource = R"(#version 330 core
layout(location = 0) in vec2 position;
uniform mat4 transform;
out vec2 uv;
void main() {
gl_Position = transform * vec4(position, 0.0, 1.0);
uv = position * 0.5 + 0.5;
})";
const char* fragmentSource = mode == 2 ? R"(#version 330 core
in vec2 uv;
uniform sampler2D image;
out vec4 color;
void main() {
color = texture(image, uv);
}
)" : R"(#version 330 core
out vec4 color;
void main() {
color = vec4(0.9, 0.4, 0.1, 1.0);
}
)";
GLuint vertex = compileShader(GL_VERTEX_SHADER, vertexSource);
GLuint fragment = compileShader(GL_FRAGMENT_SHADER, fragmentSource);
if (!vertex || !fragment) {
if (vertex) glDeleteShader(vertex);
if (fragment) glDeleteShader(fragment);
glfwDestroyWindow(window);
glfwTerminate();
return 1;
}
GLuint program = glCreateProgram();
glAttachShader(program, vertex);
glAttachShader(program, fragment);
glLinkProgram(program);
glDeleteShader(vertex);
glDeleteShader(fragment);
GLint linked = GL_FALSE;
glGetProgramiv(program, GL_LINK_STATUS, &linked);
if (!linked) {
GLint length = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &length);
std::vector<char> log(length > 0 ? length : 1);
glGetProgramInfoLog(program, static_cast<GLsizei>(log.size()), nullptr, log.data());
std::fprintf(stderr, "Link: %s\n", log.data());
glDeleteProgram(program);
glfwDestroyWindow(window);
glfwTerminate();
return 1;
}
const float points[] = {-0.6f, -0.5f, 0.6f, -0.5f, 0.0f, 0.6f};
GLuint vao = 0, buffer = 0, texture = 0;
glGenVertexArrays(1, &vao);
glGenBuffers(1, &buffer);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), nullptr);
glEnableVertexAttribArray(0);
glUseProgram(program);
float matrix[] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1};
if (mode == 1) {
matrix[0] = 0.5f;
matrix[5] = 0.5f;
matrix[12] = 0.3f;
}
GLint transformLocation = glGetUniformLocation(program, "transform");
glUniformMatrix4fv(transformLocation, 1, GL_FALSE, matrix);
if (mode == 2) {
const unsigned char pixel[] = {255, 128, 0};
glGenTextures(1, &texture);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, pixel);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glUniform1i(glGetUniformLocation(program, "image"), 0);
}
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) break;
int width = 0, height = 0;
glfwGetFramebufferSize(window, &width, &height);
if (width == 0 || height == 0) {
glfwWaitEventsTimeout(0.05);
continue;
}
glViewport(0, 0, width, height);
glClearColor(0.03f, 0.05f, 0.08f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
}
if (texture) glDeleteTextures(1, &texture);
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &buffer);
glUseProgram(0);
glDeleteProgram(program);
glfwDestroyWindow(window);
glfwTerminate();
return 0;
}1.0 즉시 모드
다음은 1.0 또는 해당 호출을 지원하는 레거시/Compatibility 컨텍스트의 draw 구간입니다. viewport와 행렬이 적절히 설정되어 있고 조명·텍스처를 사용하지 않는 초기 상태를 전제로 합니다. Core용 GLAD 헤더로 컴파일하지 않습니다.
glBegin(GL_TRIANGLES);
glColor3f(0.9f, 0.4f, 0.1f);
glVertex2f(-0.6f, -0.5f);
glVertex2f( 0.6f, -0.5f);
glVertex2f( 0.0f, 0.6f);
glEnd();glColor3f로 정한 현재 색은 이후 정점에 적용됩니다. glBegin/glEnd 사이에서 임의의 초기화·행렬 호출을 할 수는 없습니다. 이 블록은 정점 제출 방식의 비교이며 창을 만들거나 화면을 교체하지 않습니다.
1.1 배열과 디스플레이 리스트
1.1 고정 기능 컨텍스트에서는 정점들을 배열로 제출할 수 있습니다. 다음 코드는 같은 points CPU 배열이 draw가 끝날 때까지 유효하다는 전제입니다. 이후 버퍼 객체가 추가된 Compatibility 환경에서 재사용한다면 배열 포인터 해석이 CPU 포인터인지 버퍼 오프셋인지도 확인합니다.
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, points);
glColor3f(0.9f, 0.4f, 0.1f);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableClientState(GL_VERTEX_ARRAY);디스플레이 리스트는 일부 GL 명령을 기록해 재호출하는 구형 방식입니다. 모든 호출과 클라이언트 상태를 똑같이 기록하지 않으며, 정점 저장소만 담당하는 VBO나 입력 해석을 담당하는 VAO와 일대일로 대응하지 않습니다. Core로 옮길 때는 기록된 명령을 자원 생성·상태 설정·draw로 나누어 옮깁니다.
실패를 줄이는 점검
- 활성 속성이 없으면 버퍼가 있어도 셰이더가 예상 위치를 읽지 못합니다.
- Core에서 VAO 0을 일반 VAO처럼 수정하거나 draw의 기본 객체로 사용하지 않습니다.
- 프로그램 링크 성공, 위치 출력, viewport, 컬링·깊이 상태도 함께 확인합니다.
- 매 draw마다 정점 데이터를 다시 업로드할 필요는 없습니다. 정적 메시와 동적 데이터를 구분합니다.
- 인덱스를 쓰지 않는 이 예제에 EBO를 추가하는 것은 필수 준비 작업이 아닙니다.
참고 링크
4 sources