OpenGL
고친 사람 github-actions[bot]
OpenGL 은 프로그램이 그래픽 카드에게 그림을 그려 달라고 부탁할 때 쓰는 약속입니다. 어떤 함수를 부르면 무엇이 그려지는지를 문서 하나로 정해 둡니다. 그래픽 카드 회사들은 그 문서에 맞춰 각자 드라이버를 만듭니다. 그래서 한 번 짠 그리기 코드가 여러 회사의 그래픽 카드에서 돕니다.
쉽고 빠른 이해
OpenGL 은 그래픽 카드에게 그림을 시키는 함수 목록을 정한 약속입니다. 게임이나 3D 뷰어가 삼각형 하나를 화면에 찍을 때 이 목록의 함수를 부릅니다.
이게 없으면 그래픽 카드 회사마다 명령 방식이 다릅니다. 같은 화면을 그리려고 회사별로 코드를 따로 짜야 합니다.
어떻게 도나:
- 프로그램이 점 좌표와 색 같은 데이터를 그래픽 카드 메모리로 올립니다
- 그래픽 카드에서 돌 작은 프로그램을 넘겨, 점을 어디에 두고 픽셀을 무슨 색으로 칠할지 정합니다
- 그리라는 함수를 부르면 그래픽 카드가 결과를 화면용 메모리에 칠합니다
대가도 있습니다. 설정이 전역으로 켜져 있다가 다른 코드에 영향을 주기 쉽습니다. 드라이버가 뒤에서 하는 일이 많아 속도를 끝까지 짜내기 어렵습니다.
그래서 OpenGL 은 여러 기기에서 두루 도는 그리기를 비교적 짧은 코드로 짤 때 알맞습니다. 성능을 끝까지 짜내야 하는 프로그램은 코드가 더 길어지는 다른 그래픽 API 를 고릅니다.
상세
OpenGL(Open Graphics Library, 오픈 그래픽스 라이브러리)은 2D·3D 그래픽을 그리는 API(Application Programming Interface, 응용 프로그램 인터페이스)의 규격입니다. 함수 이름과 인자, 부른 뒤에 무엇이 일어나야 하는지를 문서로 정합니다. 그 함수를 실제로 돌리는 코드는 이 문서에 들어 있지 않습니다.
비유하면 전기 콘센트 규격과 비슷합니다. 규격은 구멍 모양과 전압만 정합니다. 발전소가 어디든 플러그를 꽂는 쪽은 신경 쓰지 않습니다.
이 절은 OpenGL 을 세 묶음으로 봅니다. 먼저 누가 문서를 쓰고 누가 구현하는지를 봅니다. 다음으로 코드가 OpenGL 에 말을 거는 방식과 그림이 그려지는 순서를 짧은 코드와 그림으로 따라갑니다. 끝으로 이 규격에서 갈라져 나간 형제들을 짚습니다.
규격과 구현이 갈린다
OpenGL 문서는 Khronos Group 이라는 업계 단체가 관리합니다. 이 단체는 함수와 동작만 정합니다. 코드는 내놓지 않습니다. OpenGL 은 설치하는 프로그램이 아니라 규격입니다.
구현은 그래픽 카드 회사가 합니다. 각 회사의 드라이버 안에 OpenGL 함수가 들어 있습니다. 드라이버는 운영체제와 하드웨어 사이에서 장치를 다루는 소프트웨어입니다.
프로그램이 OpenGL 함수를 부르면, 그 호출은 설치된 드라이버로 가서 GPU(Graphics Processing Unit, 그래픽 처리 장치)에 맞는 명령으로 바뀝니다. GPU 는 그래픽 카드에 올라간 계산 칩입니다.
flowchart TD
A["프로그램 · OpenGL 함수 호출"] --> B["OpenGL 규격 · 함수와 동작의 약속"]
B --> C["회사 A 드라이버"]
B --> D["회사 B 드라이버"]
C --> E["회사 A GPU"]
D --> F["회사 B GPU"]
그림에서 프로그램은 규격만 봅니다. 아래에서 어느 회사의 드라이버가 받든 같은 함수를 부릅니다. 대신 드라이버마다 규격을 해석하는 세부가 조금씩 달라서, 한 기계에서 되던 그림이 다른 기계에서 어긋나는 일이 생깁니다.
확장
규격에 없는 새 기능은 확장으로 먼저 나옵니다. 확장은 드라이버가 규격 밖에 덧붙인 함수와 상수입니다. 새 하드웨어 기능을 규격 개정까지 기다리지 않고 쓰게 하려는 장치입니다.
프로그램은 실행 중에 이 드라이버가 어떤 확장을 지원하는지 물어본 뒤 씁니다. 여러 회사가 두루 지원하는 확장은 다음 개정 때 규격 본문으로 들어가기도 합니다.
컨텍스트
OpenGL 함수를 부르려면 먼저 컨텍스트가 있어야 합니다. 컨텍스트는 한 프로그램이 쓰는 OpenGL 의 모든 설정과 객체를 담은 그릇입니다. 어느 창에 그릴지도 컨텍스트에 묶입니다.
컨텍스트를 만드는 방법은 OpenGL 규격에 없습니다. 창을 띄우는 일이 운영체제마다 달라서, 운영체제 쪽 함수나 창을 띄워 주는 별도 라이브러리가 맡습니다. OpenGL 은 컨텍스트가 이미 있다고 보고 거기서부터 시작합니다.
상태 기계
OpenGL 은 상태 기계로 동작합니다. 상태 기계는 지금 켜진 설정들을 기억해 둡니다. 다음 명령은 그 설정대로 처리합니다. 한 번 켠 설정은 누가 끌 때까지 모든 그리기에 걸립니다.
아래는 깊이 테스트를 켰다가 끄는 코드입니다. 깊이 테스트는 앞에 있는 물체가 뒤의 물체를 가리게 하는 설정입니다.
glEnable(GL_DEPTH_TEST); // 켜짐
glDisable(GL_DEPTH_TEST); // 꺼짐
glIsEnabled(GL_DEPTH_TEST); // GL_FALSE
이 함수들은 어느 물체에 적용할지를 인자로 받지 않습니다. 설정은 컨텍스트 전체에 걸립니다. 이 때문에 한 함수에서 켠 설정을 끄지 않으면, 전혀 다른 곳의 그리기가 영향을 받습니다.
객체와 이름
GPU 메모리에 올린 데이터는 버퍼 객체나 텍스처 같은 객체로 다룹니다. 프로그램은 객체를 포인터로 들고 있지 않습니다. OpenGL 이 돌려준 정수 하나를 이름으로 씁니다.
객체를 쓰려면 먼저 그 이름을 대상에 꽂아야 합니다. 대상은 GL_ARRAY_BUFFER 처럼 컨텍스트 안에 용도별로 하나씩 있는 칸입니다. 칸 하나에는 한 번에 객체 하나만 꽂힙니다.
이름을 대상 칸에 꽂는 동작이 바인딩입니다. 묶은 뒤의 함수는 인자로 객체를 받지 않습니다. 지금 칸에 묶여 있는 객체에 작업합니다. 상태 기계 방식이 객체에도 이어지는 셈입니다.
아래는 점 세 개를 GPU 로 올리고 삼각형 하나로 그리는 흐름입니다. pts 는 점 좌표를 담은 배열이라고 봅니다.
GLuint vbo;
glGenBuffers(1, &vbo); // vbo = 새 이름
glBindBuffer(
GL_ARRAY_BUFFER, vbo); // 대상 칸에 묶음
glBufferData(
GL_ARRAY_BUFFER, sizeof pts, pts,
GL_STATIC_DRAW); // GPU 로 복사
glDrawArrays(
GL_TRIANGLES, 0, 3); // 점 3개 = 삼각형
glBufferData 는 vbo 를 인자로 받지 않습니다. 바로 앞에서 GL_ARRAY_BUFFER 칸에 묶어 둔 버퍼에 데이터를 넣습니다. 마지막 인자 GL_STATIC_DRAW 는 이 데이터를 한 번 올려 두고 여러 번 그리는 데 쓴다고 드라이버에 알리는 힌트입니다.
실제로 그리려면 셰이더도 있어야 합니다. 셰이더는 GPU 에서 돌며 점의 위치와 픽셀 색을 정하는 작은 프로그램입니다. 셰이더 프로그램을 쓰도록 지정하는 호출이 따로 필요합니다. 위 코드는 흐름만 보이려고 그 호출을 뺐습니다.
그림이 그려지는 순서
OpenGL 의 그리기는 정해진 단계를 차례로 지납니다. 이렇게 차례로 이어진 단계 전체가 그래픽스 파이프라인입니다. 프로그램이 넘긴 점이 여러 단계를 거쳐 화면의 픽셀 색이 됩니다.
flowchart TD
A["정점 데이터 · 버퍼 객체"] --> B["정점 셰이더 · 점의 위치 계산"]
B --> C["프리미티브 · 점을 삼각형으로 묶음"]
C --> D["래스터화 · 삼각형이 덮는 픽셀 찾기"]
D --> E["프래그먼트 셰이더 · 픽셀 색 계산"]
E --> F["깊이 테스트 · 블렌딩(반투명 색 섞기)"]
F --> G["프레임버퍼 · 화면에 낼 색"]
첫 단계는 정점을 받습니다. 정점은 좌표와 색 같은 값을 가진 점 하나입니다. 정점 셰이더가 각 정점을 화면 기준 위치로 옮깁니다.
다음으로 정점들이 삼각형 같은 프리미티브로 묶입니다. 프리미티브는 점·선·삼각형처럼 GPU 가 한 번에 그리는 기본 도형입니다. 앞의 코드에서 GL_TRIANGLES 가 점 세 개를 삼각형 하나로 묶으라는 지시였습니다.
묶인 삼각형은 아직 좌표로만 된 도형입니다. 화면은 픽셀 격자라서, 도형이 어느 픽셀을 덮는지 따로 찾아야 합니다. 이 일이 래스터화입니다. 래스터화가 찾아낸 픽셀 후보 하나하나가 프래그먼트입니다.
프래그먼트 셰이더가 프래그먼트마다 색을 정합니다. 이어서 깊이 테스트가 다른 물체에 가려진 프래그먼트를 버립니다.
반투명한 색은 블렌딩으로 이미 칠해진 뒤쪽 색과 섞입니다. 유리창 너머로 뒤의 물체가 비치는 모습이 이렇게 만들어집니다.
마지막으로 남은 색이 프레임버퍼에 칠해집니다. 프레임버퍼는 앞에서 말한 화면용 메모리입니다. 화면에 낼 색을 픽셀마다 담아 둡니다.
셰이더
셰이더는 파이프라인의 한 단계에서 GPU 가 돌리는 작은 프로그램입니다. 정점 셰이더와 프래그먼트 셰이더는 프로그래머가 직접 써서 넘깁니다. 위치를 어떻게 옮기고 색을 어떻게 칠할지를 여기서 정합니다.
OpenGL 의 셰이더는 GLSL(OpenGL Shading Language, OpenGL 셰이딩 언어)로 씁니다. C 와 닮은 문법입니다. 프로그램은 GLSL 소스를 문자열로 드라이버에 넘깁니다. 드라이버가 그 소스를 실행 중에 컴파일합니다.
옛 방식과 지금 방식
초기 OpenGL 에서는 셰이더 없이도 그릴 수 있었습니다. 점을 glBegin 과 glEnd 사이에서 함수 호출 한 번에 하나씩 넘겼습니다. 조명과 색 계산은 드라이버에 고정된 방식으로 했습니다. 이 방식이 고정 기능 파이프라인입니다.
점마다 함수를 부르면 호출이 너무 많아집니다. 계산 방식도 바꿀 수 없습니다. 그래서 지금 방식은 점을 버퍼에 모아 한 번에 올립니다. 계산은 셰이더로 직접 씁니다.
두 방식은 프로파일로 갈립니다. 프로파일은 컨텍스트를 만들 때 고르는 기능 묶음입니다. 어느 프로파일을 골랐느냐에 따라 옛 함수를 부를 수 있는지가 정해집니다.
코어 프로파일은 옛 함수를 뺀 프로파일입니다. 호환 프로파일은 옛 함수를 남겨 둔 프로파일입니다. 새 코드는 보통 코어 프로파일로 씁니다.
오류를 알아내는 법
OpenGL 함수는 대부분 오류를 반환값으로 돌려주지 않습니다. 잘못된 호출이 나면 그 호출을 무시합니다. 내부에는 오류 표시만 남깁니다. 프로그램이 glGetError 를 불러야 그 표시를 읽을 수 있습니다.
glEnable(12345); // 없는 설정값
glGetError(); // GL_INVALID_ENUM
glGetError(); // GL_NO_ERROR
첫 glGetError 가 표시를 읽으면서 지웁니다. 두 번째 호출은 오류 없음을 돌려줍니다. 오류를 안 읽고 넘어가면 화면이 비어 있어도 원인을 찾기 어렵습니다.
형제 규격 · 뒤를 잇는 규격 · 나란한 API
OpenGL 에서 갈라져 나온 규격이 둘 있습니다. OpenGL ES 는 휴대폰 같은 임베디드 기기에 맞춰 기능을 덜어낸 규격입니다. ES 는 Embedded Systems(임베디드 시스템)를 줄인 말입니다.
WebGL 은 OpenGL ES 를 바탕으로 브라우저의 자바스크립트에서 GPU 로 그리게 한 규격입니다. 이름은 Web Graphics Library 에서 왔습니다.
OpenGL 의 뒤를 잇는 규격은 Vulkan 입니다. 같은 Khronos Group 이 만들었습니다. OpenGL 에서는 GPU 메모리를 언제 잡고 풀지를 드라이버가 알아서 정합니다. Vulkan 은 그런 일을 프로그램이 직접 지시하게 했습니다.
그만큼 Vulkan 코드는 길어집니다. 대신 성능을 더 짜낼 수 있습니다.
OpenGL 과 나란히 같은 역할을 하는 API 도 있습니다. 운영체제 회사가 자기 플랫폼용으로 낸 Direct3D 와 Metal 입니다.
관련 항목
OpenGL 이 속하는 상위 분류
그래픽스 · 렌더링 · API · 그래픽스 API · 컴퓨터 그래픽스
OpenGL 이 그리기를 거치는 처리 단계
그래픽스 파이프라인 · 정점 셰이더 · 프리미티브 · 래스터화 · 프래그먼트 셰이더 · 깊이 테스트 · 블렌딩 · 프레임버퍼
OpenGL 로 다루는 GPU 쪽 객체
버퍼 객체 · 정점 배열 객체 · 텍스처 · 셰이더 · 프로그램 객체 · 프레임버퍼 객체 · 유니폼
OpenGL 이 그리는 데이터를 이루는 구성 요소
정점 · 정점 속성 · 프래그먼트 · 법선 · 텍스처 좌표 · 감마 보정
OpenGL 과 이웃 API 가 쓰는 셰이더 언어
OpenGL 을 구현하고 실행하는 주체
Khronos Group · 디바이스 드라이버 · GPU · Mesa
OpenGL 을 쓰기 전에 필요한 준비 장치
OpenGL 컨텍스트 · GLFW · SDL · EGL · GLAD
OpenGL 의 동작 방식을 이루는 설계
상태 기계 · 바인딩 · OpenGL 확장 · 코어 프로파일 · 고정 기능 파이프라인 · 즉시 모드
OpenGL 에서 갈라져 나온 규격
OpenGL ES · WebGL · WebGL 2
OpenGL 을 대신할 수 있는 그래픽스 API
다른 이름: Open Graphics Library · 오픈GL