컴퓨터 그래픽 관련 지식이 전무한 상태에서 여러 인터넷 자료를 토대로 학습한 내용을 정리한다.
래스터와 벡터
컴퓨터 그래픽 결과물은 결국 2차원이다. 화면은 가로세로로 정렬된 픽셀들의 집합이고, 각 픽셀은 한 순간에 하나의 색만 표시할 수 있다. 이런 픽셀 집합의 색 정보는 프레임 버퍼(frame buffer)라는 메모리 블럭 안에 저장된다. 화면은 초당 여러 번 다시 그려지기 때문에 프레임 버퍼 안의 값이 바뀌면 화면의 그림도 거의 즉시 바뀐다. 즉 컴퓨터가 그림을 바꾼다는 것은 결국 “픽셀 색 정보를 저장한 메모리 내용을 바꾸는 것”이다.
CRT 모니터는 전자빔이 화면을 훑으면서 픽셀을 밝히는 방식으로 작동되었는데, 이를 래스터(raster)라고 한다. 강력한 자석이 빔을 이동시키고, 빔의 강도를 조절하여 색의 정도를 정하는 방식이다. 이때 프레임 버퍼에 저장된 색상 값은 전자빔의 강도를 결정하는 데 사용되었다. 현대의 평면 디스플레이는 동작 방식이 달라도 여전히 픽셀 격자와 색값이라는 개념을 사용하기에 래스터 그래픽(raster graphic)이라는 용어 또한 사용된다. 색상 값이 부여된 픽셀 격자로 이루어진 이미지라는 개념이 곧 래스터 그래픽이다.
그림을 꼭 픽셀 하나하나로만 다뤄야 하는 것은 아니다. 벡터 그래픽(vector graphic)은 이미지를 픽셀의 색 목록으로 저장하는 대신, 어떤 위치에 어떤 선과 도형이 있는지에 대한 정보의 목록으로 저장한다. 선의 두께, 도형의 채우기 색 같은 속성도 함께 저장할 수 있다. 즉 래스터가 결과 중심이라면 벡터는 구조 중심인 셈이다.
같은 이미지를 래스터 그래픽과 벡터 그래픽으로 저장할 때 필요한 정보량이 크게 다를 수 있다. 이를테면 1000개의 선분을 저장할 때, 벡터 그래픽의 경우 선분을 이루는 양 끝 점 2000개의 정보만 저장하면 그만이다. 반면 래스터 그래픽의 경우 전체 화면의 색상 정보를 전부 저장해야 하기에 부담이 가중된다. 따라서 초기에는 벡터 그래픽 디스플레이가 적극적으로 이용되었으나, 시간이 흐르며 래스터 방식의 경제성이 향상되었고, 모든 유형의 이미지를 잘 표현할 수 있다는 장점도 있었기에 벡터 디스플레이를 대체하게 되었다.
페인팅과 드로잉
래스터 그래픽과 벡터 그래픽의 구분은 컴퓨터 그래픽의 여러 영역에서 여전히 존재하며, 이미지 생성에 사용되는 프로그램에도 두 가지 범주가 적용된다. 페인팅 프로그램(painting program)은 기본적으로 래스터 방식이기에 결국 각 픽셀의 색상 정보를 저장한다. 반면 드로잉 프로그램(drawing program)은 벡터 그래픽 방식에 가깝다.
물론 실제 프로그램에서 이 두 방식이 완전히 분리되어 있는 경우는 드물다. 드로잉 프로그램 안에 래스터 이미지를 하나의 요소처럼 넣을 수 있고, 페인팅 프로그램에서도 레이어(layer)를 사용하면 서로 다른 요소를 겹쳐 관리할 수 있다. 즉 현실의 소프트웨어는 래스터와 벡터의 구분을 기반으로 하되, 실제 작업 편의를 위해 서로의 기능을 일부 받아들여 완성된다.
널리 알려진 그래픽 프로그램으로는 포토샵(Adobe Photoshop)과 일러스트레이터(Adobe Illustrator)가 있다. 이 중 포토샵은 페인팅 프로그램, 일러스트레이터는 드로잉 프로그램에 해당한다. 김프(GIMP)는 괜찮은 무료 페인팅 프로그램이며, 잉크스케이프(Inkscape)라는 무료 드로잉 프로그램 또한 존재한다.
이미지 파일 형식
이러한 차이는 그래픽 파일 형식에서도 드러난다. 파일에 저장된 비트에서 원본 이미지를 복원하려면 정해진 규격을 따라야 하며, 이러한 규격을 그래픽 파일 형식이라고 한다. 널리 사용되는 그래픽 파일 형식으로는 GIF, PNG, JPEG, WebP, SVG 등이 있다.
그 중 GIF, PNG, JPEG, WebP는 기본적으로 래스터 그래픽 형식이다. 이미지는 각 픽셀의 색상 값을 저장하여 표현된다. GIF는 간단한 애니메이션을 지원하며 최대 256색의 인덱스 색상 모델을 사용하고, PNG는 인덱스 색상 또는 24비트 전체 색상을 모두 사용할 수 있으며, JPEG는 풀 컬러 이미지에 적합하다고 한다.
래스터 이미지 표현에 필요한 데이터 양이 많을 수 있고, 일반적으로 데이터에는 중복되는 정보가 많기에 이미지를 압축하여 저장하기도 한다. 압축에는 압축된 자료로부터 원래의 자료를 정보 손실 없이 온전히 되돌릴 수 있는 무손실 압축과 원래 데이터를 일부 손실하여, 압축된 데이터로부터 원본 데이터를 완전히 복원할 수 없는 손실 압축 두 방식이 존재한다.
GIF와 PNG는 무손실 데이터 압축을 사용하기에 선명한 그림, 텍스트, 아이콘, 선화 같은 데 적합하다. 반면 JPEG는 손실 데이터 압축 알고리즘을 사용한다. 언뜻 보기에는 좋지 않아 보일 수 있지만 실제로 그 차이는 눈에 잘 띄지 않으며, 손실 압축을 사용하면 압축된 데이터 크기를 더 크게 줄일 수 있기에 사진 이미지에 적합하고 경계가 뚜렷한 이미지, 특히 선 그림이나 텍스트가 포함된 이미지에는 부적합하다. WebP는 무손실 및 손실 압축을 모두 사용할 수 있다.
반면 SVG는 2차원 벡터 그래픽 이미지를 기술하는 XML 기반 언어다. 이는 확장 가능한 벡터 그래픽(Scalable Vector Graphics)의 약자로 기본적으로 벡터 그래픽 형식이고, 이름 그대로 이미지를 확대 해도 품질에 손상이 일어나지 않는다. 그렇기에 로고나 아이콘, 확대가 잦은 그래픽 요소에 적합하다.
3차원 그래픽
모든 디지털 이미지는 결국 어떤 좌표계를 바탕으로 구성되고, 2차원에서는 각 점이 두 개의 숫자 값을 가지게 된다. 점의 두 좌표는 흔히 x 좌표와 y 좌표라고 불리지만 그 명칭은 임의적이다. 래스터 이미지에서는 각 픽셀이 행과 열에 따라 자연스럽게 좌표를 가지게 되는 반면, 벡터 이미지에서는 선과 도형을 보다 정밀하게 표현해야 하므로 보통 실수 좌표를 쓰는 것이 자연스럽다.
***
3차원 그래픽에서 이미지의 내용은 기하학적 객체의 목록으로 지정되며, 이를 기하학적 모델링(geometric modeling)이라 칭한다고 한다. 쉽게 말해 2D 래스터 그래픽은 보통 화면의 픽셀 하나하나를 다루는 방식에 가깝다면, 3D 그래픽은 먼저 장면 안에 물체들을 배치한다는 것이다. 3차원 공간 안에 물체를 놓으려면 각 위치를 숫자로 표현할 방법, 즉 좌표계가 필요하다. 세계 좌표계(world coordinates)는 보통 한 점을 (x, y, z)로 표현하며 “물체가 세계의 어디에 있는가”를 말하는 공통 기준이다. 세계 좌표계를 이용해 가상의 공간을 만들고, 그 안에 점, 선, 삼각형 같은 기본 도형들을 놓아 세상을 구성한다. 이후 이를 2차원으로 투영하는 것이다. 마치 세상을 카메라에 담듯.
3D 장면은 보통 아주 단순한 도형에서 시작한다. 이를테면 끝점 2개의 선분이나, 꼭짓점 3개의 삼각형 같은. 이를 기하학적 기본 요소(geometric primitives, 이하 프리미티브)라 부른다. 그러나 이런 도형만으로 세상을 그려내기엔 현실의 물체가 너무 복잡하다는 문제가 있다. 그래서 복잡한 물체를 재사용 가능한 작은 구성 요소로 나눈 뒤 조립해 사용할 필요가 있다. 이를 계층적 모델링(hierarchical modeling)이라 한다.
기하학적 모델링을 이용해 바퀴 모델을 제작했다고 할 때, 자동차의 앞왼쪽, 앞오른쪽, 뒤왼쪽, 뒤오른쪽에 놓으려면 결국 위치를 바꿔야 한다. 이 때 바퀴를 이루는 수많은 점과 삼각형의 좌표를 하나하나 따로 바꾸는 것이 아니라, 바퀴 전체에 하나의 규칙을 적용하면 된다. 이를 기하학적 변환(geometric transform)이라 하며, 대표적으로 크기 조정, 이동, 회전 세 가지 종류가 있다.
***
세상을 그려내기에 도형만으로는 부족하며 그 도형이 어떻게 보일지 또한 정해야 한다. 이를 위해 프리미티브에 색상 등의 속성을 부여할 수 있고, 3차원 그래픽에선 보통 이를 재질(material)이라 칭한다. 여기서의 재질이란 도형 표면과 빛이 상호작용하는 방식을 의미한다. 이는 광택, 거칠기, 투명도와 같은 속성들을 아우르는 표현이다.
여기서 텍스처(texture)라는 개념이 등장한다. 텍스처는 도형의 각 부분마다 속성(광택, 투명도 등)을 다르게 줄 수 있는 방법이다. 텍스처의 가장 흔한 사용법은 각 지점마다 다른 색상을 주는 것이며 이는 주로 2차원 이미지를 이용한다고 책은 설명한다. 그 이미지는 표면에 적용되며 그저 표면의 색을 칠하는 것처럼 보일 수 있으나 텍스처의 용도는 그 뿐이 아니라 투명도와 울퉁불퉁함 등의 속성을 각 지점별로 적용시길 수 있다. 텍스처를 사용하면 더 적은 프리미티브를 사용하여 보다 섬세한 표현을 할 수 있다.
현실 세계와 같이 3차원 세계 또한 빛이 없다면 아무것도 볼 수 없다. 그렇기에 장면(scene)에 조명(lighting)을 추가해야 한다. 한 장면에 여러 광원이 존재할 수 있으며, 고유의 색상, 위치, 강도, 방향을 가질 수 있다. 이런 광원으로부터 나오는 빛은 장면 내의 재질들과 상호작용하며, 비교적 단순한 규칙의 광원부터 복잡한 계산이 필요한 광원까지 다양하게 존재한다.
***
일반적으로 3차원 그래픽의 궁극적인 목표는 3차원 세계를 2차원 이미지로 구현하는 것이다. 그 과정 중 시점(view)과 투영(projection)이 반영된다. 시점을 설정하려면 보는 사람의 위치와 바라보는 방향을 지정해야 한다. 그것만으로 충분해 보이지만 “카메라 위쪽 방향” 또한 정할 필요가 있다. 같은 방향을 바라보더라도 카메라가 기울여져 있을 수 있기 때문이다.
3차원 그래픽의 마지막 단계는 2차원 이미지의 개별 픽셀에 색상을 지정하는 것이다. 이 과정을 래스터화(rasterization) 라고 하며, 이미지를 생성하는 전체 과정을 장면 렌더링(rendering) 이라고 한다.
많은 경우 궁극적인 목표는 단일 이미지를 만드는 것이 아니라, 서로 다른 시간대의 세상을 보여주는 일련의 이미지로 구성된 애니메이션을 만드는 것이다. 애니메이션은 기본적으로 시간에 따라 조금씩 다른 이미지들을 연속으로 보여주는 것을 의미하며, 좌표, 변환, 재질 속성, 시점 등 장면을 이루는 거의 모든 요소는 매 프레임 변화할 수 있다.
하드웨어와 소프트웨어
OpenGL은 3D 그래픽을 만들기 위한 그래픽 API다. API란 Application Programming Interface의 약자로, 프로그램이 특정 기능을 사용하기 위해 호출하는 명령 체계이다. OpenGL은 GPU와 소통하기 위한 규약이자 명령 체계인 셈이다. 초기 버전은 1992년에 고성능 워크스테이션 제조사인 실리콘 그래픽스(Silicon Graphics, SGI)에서 출시되었고, 본래 고성능 그래픽 워크스테이션을 위한 기술이었다. 그러나 지금은 데스크톱, 노트북, 모바일 등 많은 장치에서 지원되며, 웹에서 또한 WebGL이라는 형태로 지원된다.
초창기 데스크톱 컴퓨터에서는 CPU(Central Processing Unit)가 직접 화면의 픽셀을 관리했다. 예를 들어 선 하나를 그릴 때도 CPU가 직접 반복문을 실행하며 선 위의 픽셀을 일일이 색칠해야 했다. 당연히 처리 속도는 매우 느렸다. 이 문제를 해결하기 위해 GPU(Graphics Processing Unit)가 등장한다. GPU는 그래픽 계산 전용 하드웨어로, 병렬 작동이 가능한 그래픽 계산용 프로세서를 많이 포함하며 이미지나 좌표 목록과 같은 데이터를 저장하는 전용 메모리 또한 가지고 있었다. 특히 GPU 메모리에 저장된 데이터는 GPU가 매우 빠르게 읽을 수 있었기에 그래픽 처리의 경제성이 상당히 향상되었다.
그래픽 처리 작업 시 CPU와 GPU는 역할을 분담하여 수행하게 된다. CPU에서 전반적인 프로그램의 흐름을 제어하고 데이더와 구체적인 명령을 GPU에 전달하면 GPU는 이를 실제로 계산하고 출력하는 역할을 한다.
GPU가 이해하는 명령 집합을 GPU API, 혹은 그래픽 API 라고 한다. 각 GPU는 자체적으로 이해할 수 있는 명령 체계를 가지고 있고, 이는 GPU 드라이버를 통해 그래픽 API와 연결된다. GPU 드라이버는 GPU에 설치되는 소프트웨어로, 그래픽 API 명령을 GPU 자체 명령으로 번역하는 역할을 한다. 즉 우리가 OpenGL 등의 그래픽 API로 명령을 전달하면, 그래픽 드라이버가 이를 GPU가 이해할 수 있는 명령으로 변환하여 전달한다.
OpenGL이 유일한 그래픽 API인 것은 아니다. 현재 OpenGL을 관리하는 크로노스 그룹(The Khronos Group, Inc.)에서는 그래픽 API 불칸(Vulkan)을 OpenGL의 현대적 대안으로 제시하였다. 애플(Apple)과 마이크로소프트(Microsoft)에서 사용하는 Metal 과 Direct3D 같은 전용 API 또한 존재한다. 웹의 경우, WebGPU 라는 새로운 API가 오랫동안 개발되어 왔으며 이미 일부 웹 브라우저에 구현되어 있기도 하다. 이런 최신 API들은 공통적으로 더 저수준(low-level)이고 복잡한 대신 성능과 효율성은 뛰어나도록 설계되었다.
***
OpenGL은 하나의 API지만, 정확히는 여러 번 수정∙확장되어온 API의 계열이다. 그렇기에 1992년에 출시된 1.0버전과 2026년 4월 기준 가장 최신 버전인 2017년에 출시된 4.6버전은 매우 다르다. 휴대폰이나 태블릿과 같은 임베디드 시스템(embedded system)을 위한 OpenGL ES라는 특수 버전과, 이를 웹 브라우저에 포팅해 웹 환경에서 구동되게 한 WebGL이라는 버전 또한 존재한다.
OpenGL은 원래 client∙server 시스템으로 설계되었다. 서버는 디스플레이 제어와 그래픽 계산을 포함하는 GPU와 그 메모리이며, 클라이언트는 CPU와 그 위에서 실행되는 프로그램이다. OpenGL 명령은 CPU에서 실행되는 프로그램으로부터 비롯되지만, 실제로는 네트워크를 통해 원격으로 OpenGL 프로그램을 실행하는 것 또한 가능하다. 즉, 원격 컴퓨터(OpenGL 클라이언트)에서 응용 프로그램을 실행하고, 그래픽 연산과 디스플레이는 실제로 사용하고 있는 컴퓨터(OpenGL 서버)에서 처리할 수 있다.
핵심은 CPU와 GPU가 분리된 구성요소이며 둘 사이에 통신 채널이 있다는 점이다. 프로그램은 CPU에서 실행되지만 실제 연산은 GPU에서 수행되기에 OpenGL 명령과 필요한 데이터는 CPU에서 GPU로 이 채널을 통해 전송되어야 한다. 이 때 통신 채널의 용량은 그래픽 성능과 밀접한 연관이 있다. GPU 계산 자체가 매우 빠를지라도 CPU에서 GPU로 데이터를 전송하는 데 밀리초 단위의 시간이 걸린다면 GPU의 효용성은 무의미해진다. 즉, CPU—GPU 통신 비용이 성능 병목이 될 수 있다.
그렇기에 OpenGL은 CPU와 GPU 사이에서 오가는 데이터와 명령의 양을 줄이는 방향으로 발전을 거듭하게 된다. 그 중 한 가지 방법은 데이터를 GPU 메모리에 저장하는 것이다. 여러 번 사용되는 데이터는 GPU 메모리에 한 번만 저장하여 재사용하는 것이다. 처음 한 번만 전송하면 GPU에서 GPU메모리에 매우 빠르게 접근해 읽을 수 있기 때문이다.
또 다른 방법은 주어진 이미지를 그리기 위해 GPU로 전송해야 하는 OpenGL 명령의 수를 줄이는 것이다. OpenGL은 삼각형과 같은 프리미티브를 그린다. 이를 위해 각 정점(vertex)의 좌표와 속성 정보가 필요하다. 초기 OpenGL 1.0에선 꼭짓점 하나하나를 별도 명령으로 지정해야 했으며, 속성이 변경될 때마다 새로운 명령이 필요했다. 즉 삼각형 하나에도 여러 명령이 필요했고, 수천 개 삼각형으로 이루어진 물체는 수천, 수만 개 명령이 필요했다.
OpenGL 1.1에서는 수천 개의 명령어 대신 단 하나의 명령어로 이러한 객체를 그릴 수 있게 되었다. 정점 데이터를 배열(array) 로 묶어서 보낼 수 있게 되었기 때문이다. 물체의 정점 데이터를 배열에 모아두고 한꺼번에 GPU로 전달해 한 번의 draw 명령으로 많은 삼각형을 그릴 수 있게 되었다. 하지만 객체를 여러 번 그려야 하는 경우에는 매번 데이터를 다시 전송해야 하는 문제가 있었다.
이 문제는 OpenGL 1.5에서 VBO(vertex buffer object, 정점 버퍼 객체)를 통해 해결된다. VBO는 GPU 메모리 안에 존재하는 버퍼, 즉 정점 좌표나 속성 데이터를 저장하는 공간이다. 이를 통해 데이터를 사용할 때마다 CPU에서 GPU로 다시 전송할 필요 없이 데이터를 재사용할 수 있게 되며 VBO는 OpenGL 현대화의 핵심 개념 중 하나가 되었다. 정점 데이터뿐 아니라 이미지 데이터도 마찬가지이다. OpenGL 1.1부터는 텍스처 객체(texture object)를 도입하여 여러 이미지를 GPU에 한 번 저장하고 반복 사용 가능하게 했다. 즉, 텍스처 객체또한 “자주 쓰는 데이터를 GPU에 올려 두자”라는 철학을 따른다.
***
초기 OpenGL은 필요한 그래픽 기능을 API에 계속 추가하는 방식으로 발전하였다. 하지만 점차 새롭고 정교한 그래픽 처리 기술들이 등장하였고, 이들 중 일부를 OpenGL에 포함시키기도 했지만 추가된 기능들이 OpenGL을 더욱 복잡하게 만들기도 하였고, 결국 모든 사람들의 요구를 만족시킬 수는 없었다.
그렇기에 OpenGL 2.0부터는 GPU 그래픽 처리 과정의 일부를 직접 프로그램으로 작성해서 실행할 수 있게 되었다. 이전에는 OpenGL이 정해준 방식대로만 계산해야 했다면, 이후부터는 개발자가 원하는 계산 방식을 프로그램으로 작성해서 GPU에 보내면 GPU가 그 프로그램을 실행할 수 있게 된 것이다. 즉 OpenGL API는 모든 기능을 다 내장할 필요가 줄어들고 GPU에 프로그램을 전달해서 실행시키는 능력만 지원하면 되기에 기능 지원 부담을 줄이며 더욱 유연한 API가 된 것이다.
이 GPU에서 실행되는 프로그램을 셰이더(shader) 라고 부른다. 그러나 이 용어가 셰이더의 실제 기능을 완벽하게 설명하는 것은 아니다. 처음에는 말 그대로 shading(음영 처리)와 관련된 영역을 처리했지만 지금은 훨씬 넓은 역할을 수행한다. 최초로 도입된 셰이더는 정점 셰이더(vertex shader) 와 프래그먼트 셰이더(fragment shader)다.
프리미티브를 그릴 때 각 꼭짓점(vertex)마다 정점 좌표 변환, 조명 계산에 필요한 값 처리, 색상 계산, 속성 전달 등 해야 하는 일이 있다. 이런 정점 단위(per-vertex) 작업을 담당하는 프로그램이
정점 셰이더다. 즉 정점이 하나 하나 들어오면 그 정점에 대한 계산을 수행한다.
프리미티브 내부의 각 픽셀에 대해서도 최종 색 계산, 텍스처 적용, 조명 반영, 픽셀 단위 효과 처리 등의 계산이 필요하다. 이런 픽셀 단위(per-pixel) 작업을 담당하는 프로그램이 프라그먼트 셰이더다. 프라그먼트 셰이더는 픽셀 셰이더(pixel shader)라고도 불린다.
이러한 프로그래밍 가능한 그래픽 하드웨어(programmable graphics hardware) 방식은 매우 성공적이었다. GPU에서 직접 실행되므로 빠르고, 개발자가 원하는 기법을 유연하게 구현 가능하며, OpenGL API를 계속 비대하게 만들 필요가 줄어들었고, 고정된 기능 대신 확장 가능한 구조가 되었기 때문이다. 그래서 OpenGL은 점점 고정 기능(fixed-function) 방식에서 프로그래머블 방식(shader-based pipeline) 으로 이동하게 된다.
OpenGL 3.0부터는 OpenGL이 미리 정해 둔 방식으로 정점과 픽셀을 처리하는 기능이 더 이상 권장되지 않았고, OpenGL 3.1부터는 표준 핵심 기능에서 빠졌다. 하지만 데스크톱 OpenGL에서는 여전히 옵션 확장 형태로 남아 있고, 실제로는 오래된 기능들 또한 여전히 지원하며 하위 호환성이 비교적 강하다. 반면 임베디드 시스템 환경에서 OpenGL ES 2.0 이후 버전부터는 셰이더 사용이 필수적이 되었고, OpenGL 1.1의 많은 고정 기능 API가 제거되었다. WebGL 또한 OpenGL ES 기반이기에 셰이더 사용이 필수다. 즉 웹이나 모바일 쪽에서는 이미 오래전부터 셰이더 중심 방식이 기본이다.
OpenGL 셰이더는 GLSL(OpenGL Shading Language)로 작성된다. OpenGL 자체와 마찬가지로 GLSL도 여러 버전을 거쳐 발전해 왔으며, C 프로그래밍 언어와 유사한 구문을 사용한다.
***
서로 다른 정점(vertex)들에 대해 수행되는 계산은 대체로 서로 독립적이며 병렬적으로 처리된다. 프래그먼트(fragment)에 대한 계산도 마찬가지이다. 이 역시 서로 거의 영향을 주지 않기 때문에, 병렬 처리에 적합하다. 실제로 GPU에는 수백 개에서 수천 개에 이르는 프로세서가 들어 있을 수 있고, 이 프로세서들은 병렬적으로 작업할 수 있다. 물론 개별 프로세서 하나하나의 성능은 CPU보다 훨씬 약하지만, 정점 단위 계산이나 프래그먼트 단위 계산은 보통 그 자체가 아주 복잡한 작업은 아니기에 문제되지 않는다. 중요한 것은 이를 이용한 병렬 처리를 통해 높은 그래픽 성능을 얻을 수 있다는 사실이다.












