Graphics Programming

[Marco Scabia] 03 정점 셰이더와 픽셀 셰이더 본문

Season 1/플래시

[Marco Scabia] 03 정점 셰이더와 픽셀 셰이더

minseoklee 2011. 10. 20. 11:07

원문
http://www.adobe.com/devnet/flashplayer/articles/vertex-fragment-shaders.html


이 글에서는 셰이더를 소개합니다. 셰이더는 Stage3D 렌더링 파이프라인의 심장입니다. 여러분은 정점 셰이더와 픽셀 셰이더가 무엇이고 3D 렌더링 파이프라인에 어떻게 들어가며 왜 필요한가를 배울 것입니다.

셰이더 다루기

Stage3D가 어떻게 작동하는가에 관한 앞선 글에서 Stage3D가 프로그래밍 가능한 그래픽스 파이프라인에 기반하는 이유를 배웠습니다.

Stage3D의 프로그래밍 가능한 그래픽스 파이프라인은 여러분이 렌더링 동작을 프로그래밍할 수 있기 때문에 매우 유용합니다. 그저 데이터만 GPU에 넘기면 아무 제어권 없이 렌더링되는 구식 고정 기능 그래픽스 파이프라인보다 크게 향상된 것입니다.

하지만 Stage3D를 쓰려면 실제로 파이프라인을 프로그래밍 가능한 것으로 만드는, 프로그래밍 가능 비트들을 여러분이 짜야 합니다.

그래픽스 파이프라인 내의 프로그래밍 가능 코드는 셰이더라 부릅니다. 셰이더는 정점 셰이더와 단편 셰이더(픽셀 셰이더)로 나뉩니다. Stage3D로 무엇을 렌더링하려고 하든 적어도 하나의 정점 셰이더와 단편 셰이더를 작성해야 합니다. 그렇지 않으면 그래픽스 파이프라인이 프로그래밍되지 않고 따라서 작동하지 않습니다.

일반적인 액션스크립트 코드가 CPU에서 실행되는 것과 달리 셰이더는 여러분의 그래픽스 하드웨어 GPU에서 실행되는 작은 프로그램입니다.

세이더는 그저 GPU 프로그램입니다.

Stage3D API에서는 Program3D라는 특별한 클래스로 셰이더를 감쌉니다. Program3D는 세이더의 인스턴스를 생성하여 GPU에 업로드하기 위해 필요한 기능을 제공합니다. 그러고나면 셰이더 프로그램은 실행할 수 있게 되고 액션스크립트 코드와 셰이더 프로그램 사이의 상호작용이 가능해집니다.

셰이더는 빠릅니다. 액션스크립트가 느린 언어라 말하려는 것은 아니지만 여러분이 액션스크립트를 실행할 때 그 코드는 가상 기계에서 실행되기 때문에 C++ 같은 네이티브 언어보다 느리게 돌아갑니다. 오늘날의 많은 AAA 게임을 여전히 C++로 짜는 것은 그 때문입니다.

플래시에서는 최초의 일로, 여러분이 GPU 네이티브인 셰이더 프로그램을 작성할 수 있습니다. 때문에 셰이더는 최대의 네이티브 빠르기로 실행됩니다.

프로그래밍 가능한 그래픽스 파이프라인에 셰이더가 어떻게 들어가는지 이해하기

프로그래밍 가능한 그래픽스 파이프라인은 Stage3D 내용물을 렌더링하는 방법을 지시합니다(그림 1을 보세요).

Figure 1. Programmable graphics pipeline block diagram
그림 1. 프로그래밍 가능한 그래픽스 파이프라인 블록 다이어그램

위 다이어그램에서, 정점 셰이더와 단편 셰이더가 파이프라인 안에서 얼마나 중요한 블록을 구성하는지 보세요.

기하 구조를 렌더링할 때 여러분은 VertexBuffer라고 하는 정점 스트림을 받는데 VertexBuffer는 기하 삼각형을 정의합니다. 정점 버퍼로부터 온 정점 스트림은 정점 셰이더의 입력으로 공급되며 정점 셰이더는 프로그래밍 가능한 방식으로 정점 데이터를 처리합니다. 정점 셰이더의 출력을 가지고 GPU는 삼각형을 조립합니다. 삼각형들은 뷰포트에 맞춰 죽거나 잘려 래스터라이즈 블록으로 보내집니다. 이 블록에서는 단편이라는 것으로 구성되는 새로운 출력 스트림을 생성합니다. 단편은 작은 자료 구조로서 각 단편은 화면에 보이는 하나의 삼각형 픽셀과 연관됩니다.

단편의 데이터 내용은 전적으로 정점 셰이더가 결정합니다. 사실 정점 셰이더는 정점 속성(attribue) 매개변수를 출력으로서 내보낼 수 있습니다. 래스터라이저가 하는 일은 정점 셰이더가 뱉은 정점별 데이터를 삼각형에 맞춰 보간하여 화면 위의 각 단편(삼각형 픽셀)이 그 픽셀을 위한 알맞은 값을 얻도록 하는 것입니다.

예를 들어 여러분의 정점 버퍼가 정점 색깔을 정점 속성으로서 지정하고, 삼각형이 각각 흰색과 검은색으로 정의된 두 정점을 가진다고 상상해봅시다. 정점 셰이더는 이 정점별 색상을 출력하여 다음 블록에게 넘깁니다. 그러면 삼각형의 중간 어딘가에 관계된 단편은 흰 정점과 검은 정점이 보간된 회색을 받습니다. 이 회색은 흰 정점에 가까우면 밝고 검은 정점에 가까우면 어둡습니다.

이 보간되었지만 처리되지 않은 단편들은 단편 셰이더의 입력으로 들어가 최종 색깔을 내는 데 쓰입니다.

입력되는 단편 외에도, 단편 셰이더가 추출할 수 있는 텍스처를 액션스크립트를 통해 단편 셰이더의 입력으로서 전송할 수 있습니다.

정점 셰이더 다루기

정점 셰이더는 GPU에서 실행되는 작은 프로그램입니다. 이름이 내포하듯이 정점 셰이더는 정점을 처리합니다. 정점 셰이더는 정점을 처리하는 조그만 프로그램입니다.

대략 말하자면 기하 정보를 가지고 VertexBuffer 객체를 생성한 후 GPU에 전송합니다. 그리고 AGAL 같은 셰이더 언어로 정점 셰이더를 작성합니다. VertexBuffer 내의 모든 정점에 대해 정점 셰이더 프로그램이 호출됩니다.

마치 for 루프가 있고 정점 프로그램이 이런 일을 하는 것처럼 말입니다.

for (var i:int = 0; i < vertexBuffer.length; i++) {
 executeVertexShader(vertexBuffer[i]);
}

그런 for 루프를 볼 수는 없지만요. VertexBuffer 내의 모든 정점이 처리된다고 말하려 한 것입니다.

액션스크립트로부터 정점 셰이더로 상수 값을 상수 레지스터의 형태로 전송할 수도 있습니다. 셰이더를 실행하고자 할 때마다(메시를 렌더링하기 위해 Context3D::drawTriangles 메서드를 호출할 때마다) 다른 값을 전송할 수 있습니다. 셰이더는 그 상수 값을 사용하여 알고리즘과 출력을 조정할 수 있습니다.

정점 셰이더의 입력은 하나 이상의 정점 속성 스트림으로 구성된 VertexBuffer입니다. 정점 버퍼는 최소한 정점 위치는 포함해야 합니다. 정점 위치는 각 3D 모델마다 가지는 고유의 좌표계에서 참조됩니다(모델마다 고유의 원점을 가집니다). 정점 셰이더는 그 위치를 화면 좌표로 변환하여 올바르게 표시되도록 합니다. 정점 버퍼는 정점 속성, 정점 UV 좌표 같은 추가적인 속성을 포함할 수도 있습니다. 정점 셰이더는 이 속성들을 처리한 후 출력하고 래스터라이저는 그것들을 보간하고 단편 셰이더에 입력합니다.

정점 셰이더의 가장 명백하고 자연스런 사용법은 장면 내 기하 구조에 행렬 변환을 수행하는 것입니다. 로컬 공간 내의 모든 정점을 준비합니다. 그리고 정점 셰이더에 변환 행렬을 전송합니다. 정점 셰이더는 이 행렬을 사용하여 VertexBuffer 내의 모든 정점을 변환합니다. 아주 빨리 수행합니다. 하드웨어 가속을 받기 때문에 액션스크립트로 하는 것보다 겁나 빠릅니다.

정점 셰이더가 완전히 프로그래밍 가능하다는 것은 흥미있는 일입니다. 여러분이 원하는 대로 기하 구조를 변형할 수 있습니다. 예를 들어 정점 위치를 변형하는 일반적인 응용법으로 뼈대가 있습니다. 해골을 이루는 뼈대의 집합과 스킨(메시)을 정의합니다. 뼈대가 회전하면 뼈대는 계층도 안에 있기 때문에 스킨의 모양을 변형합니다. 그렇게 애니메이션되는 사람 모형을 제작하는 것입니다. 최선책은 정점 셰이더에 뼈대 회전(변환)을 전송하고 정점 셰이더가 스킨을 수정하도록 하여 알맞게 애니메이션되고 변형되게 보이도록 하는 것입니다.

정점 셰이더를 응용하는 방법은 많습니다. 부드러운 천을 흉내내거나 물체들을 모핑(morphing)할 수 있습니다. 정점 수가 같은 두 메시를 만들고 정점 셰이더가 모프 매개변수에 따라 메시1과 메시2를 모프하게 합니다.

단편 셰이더 다루기

정점 셰이더처럼 단편 셰이더도 GPU에서 실행되는 작은 프로그램입니다. 이름이 내포하듯이 단편 셰이더는 단편을 처리합니다. 렌더링되는 각 삼각형 픽셀의 최종 색깔을 출력할 책임을 맡습니다.

단편 셰이더는 이렇게 작동합니다. 정점 셰이더가 보내온 모든 단편을 입력으로 받습니다. 위에서 설명했듯이 단편 셰이더에 오는 단편들은 정점 셰이더가 출력한 정점 속성들이 보간된 것입니다.

단편 셰이더 실행 흐름은 숨은 루프 같은 것입니다. 처리되지 않은 단편들을 담은 fragmentStream이라는 스트림이 있어서 단편 셰이더를 실행하는 것이 다음과 같은 일을 하는 것과 동등하다고 상상하세요.

for (var i:int = 0; i < fragmentStream.length; i++) {
  executeFragmentShader(fragmentStream[i]);
}

입력인 fragmentStream은 말하자면 처리되지 않았습니다. 단편 셰이더가 이것을 처리하여 픽셀의 최종 색깔을 계산할 수 있다는 뜻입니다.

단편 셰이더는 프로그래밍 가능한 파이프라인의 핵심부에 있습니다. 단편 셰이더의 주된 사용법은 정점 속성 색깔이나, 텍스쳐와 관련된 정점 속성 UV 텍스처 좌표로부터 다양한 픽셀 색깔을 계산하는 것입니다. 

하지만 단편 셰이더는 그런 간단한 효과에 한정되지 않습니다. 사실 현대 3D 게임에서 볼 수 있는 그 모든 놀라운 3D 효과를 내는 데 단편 셰이더가 쓰입니다. 예를 들어 동적 조명 효과는 대부분 단편 셰이더를 통해 이뤄집니다. 그걸 생각해보자면 동적 조명은 그저 장면 내에 있는 조명들이 기하 구조와 기하 구조의 재질에 관하여 어느 위치에 있는지에 따라 픽셀 색상을 계산하는 것을 뜻합니다. 그래서 동적 조명은 대부분 단편 셰이더를 가지고 만듭니다.

물이나 환경 맵핑 같은 반사 효과도 모두 단편 셰이더로 만듭니다.  단편 셰이더로 만들 수 있는 효과는 아주 광범위하고 이런 기본적인 효과들은 가능성에 비해 빙산의 일각입니다.

결국 여러분이 화면에서 보는 것은 단편 셰이더가 결정한 것입니다. 즉 단편 셰이더는 실제로 렌더링되는 내용을 관리하는 코드입니다.

이제 어디로 가야 하나

이 튜토리얼에서는 정점 셰이더와 단편 셰이더로 작업하는 것에 대한 개념을 배웠습니다. 이 셰이더들은 Stage3D APi의 렌더링 파이프라인의 심장입니다. 셰이더를 쓰면 온갖 종류의 3D 그래픽스 효과를 만들 수 있고 셰이더 기반 기법을 다루는 온라인 자료는 아주 많습니다. 다음 튜토리얼에서는 셰이더 언어를 배우고 특히 AGAL(Adobe Graphics Assembly Language)을 살펴봅니다.

셰이더를 더 자세히 연구하고자 한다면 저는 유명한 nVidia의 "GPU Gems" 책을 추천합니다. 이 책은 온라인에 무료로 공개되었습니다.

Comments