Day5. Shaders
쉐이딩 프로그래밍 소개
Thirty Days of Metal — Day 5: Shaders
Continuing our exploration of graphics programming, we take our first look at shaders and the graphics pipeline.
medium.com
그래픽스에서 Shader에 대한 소개
Pixar RenderMan - Wikipedia
From Wikipedia, the free encyclopedia Jump to navigation Jump to search 3D rendering software used by Pixar Pixar RenderMan (formerly PhotoRealistic RenderMan)[1] is proprietary photorealistic 3D rendering software produced by Pixar Animation Studios. Pixa
en.wikipedia.org
픽사의 렌더맨 게임에서 출발한 개념이다.
처음 “쉐이더”라는 용어는 “픽셀마다 색을 어떻게 입힐지 결정한다”라는 의미로 사용했다.
하지만, 그래픽스가 발전하며 픽셀마다 색을 입히는것은 다양한 요소들을 결합하는 복잡한 작업이었다. 그리고 그래픽스에 기하학적인 영역도 GPU가 담당하기 시작했다.
예컨데, 픽셀 하나의 색을 결정하기 위해선. 3D 객체의 정점 ⇒ 조명 ⇒ 시야각 등의 색 처리 작업이 필요하다.
⇒ (사실 더 복잡함)
그래서 GPU에게 처리 작업을 각각의 특성에 맞는 처리 기법을 분류하기 시작했고 오늘날의 “쉐이더”는 GPU가 실행하는 작은 프로그램들이라는 더 일반화된 의미가 되었다.
Metal Shader Language
쉐이더는 GPU에게 직접적으로 명령어를 실행시키는 최소 단위이다. ⇒ C++ 함수와 같음
그렇기 때문에 쉐이더 작성 언어는 하드웨어와 밀접한 언어일 필요가 있다.
그래서 Metal은 C++을 확장한 자신의 쉐이더에 맞는 언어를 만들었다. 확장명은 .metal
이다.
그래픽 쉐이딩 함수는 크게 기하학을 다루는 Vertex Functions, Pixel별 색을 다루는 Fragment Functions로 나눈다.
Attribute specifier syntax ⇒ 속성 한정자 문법
Suddenly Attributes?
갑자기 Attributes를 언급한 이유는 MSL이 스위프트에서 작성한 메탈 관련 데이터를 C++ Attributes 기법으로 가져오기 때문이다.
What is attributes?
속성은 스위프트를 사용하는 iOS의 경우 @objc
나 #selector
와 같은 키워드로 사용한다.
스위프트 공식 문서는 만들어 놓은 속성들의 사용법을 설명해두었다.
Attributes — The Swift Programming Language (Swift 5.7)
Attributes There are two kinds of attributes in Swift—those that apply to declarations and those that apply to types. An attribute provides additional information about the declaration or type. For example, the discardableResult attribute on a function d
docs.swift.org
Attributes 기법은 C#
이나 JAVA
와 같은 객체지향 프로그램에서 클래스에 메타 데이터(Meta-data)를 담아서 빠르게 클래스의 사용 속성을 담기 위해 많이 사용한 기법이다.
한때 유행했던 기법으로 메타-프로그래밍으로 발전했으며, 그로 인해 사용자도 직접 Attributes를 만들고 사용할 수 있도록 만들었다.
Attributes (C#)
Learn how to use attributes to associate metadata or declarative information with code in C#. An attribute can be queried at run time by using reflection.
learn.microsoft.com
C#의 속성에 대한 공식 문서…
어쨌든 Attributes를 C++에도 사용할 수 있으며 C++ 기반으로 제작한 Metal Shader Language도 이 Attributes를 이용한다.
C++의 Attributes는 [[여기에 컴파일러에 알려줄 속성 키워드
]]로 사용한다.
#include <iostream>
int print(int a [[buffer(0)]]){
std::cout<<a<<std::endl;
}
MSL의 Attributes 속성은 컴파일러가 MSL에게 앱에 할당된 메모리 속 쉐이딩 프로그램에 사용해야하는 데이터의 위치를 알려주기 위한 용도이다.
Shader Vertex Functions
쉐이더 정점 함수의 목적은 정점 버퍼에서 가져온 정점 데이터를 가공해 최종 정점 데이터의 위치로
변환하고 리턴하는 것이다.
리턴한 데이터가 어디로 가는지, 어떤 type을 가져야하는지는 나중에…
예제 코드
vertex float4 vertex_main(
device float2 const* positions [[buffer(0)]],
uint vertexID [[vertex_id]])
{
float2 position = positions[vertexID];
return float4(position, 0.0, 1.0);
}
코드 밑줄 부분 설명
- vertex ⇒ MSL에서 정점에 관련된 쉐이더 함수인 것을 알려주는 키워드이다.
- device ⇒ 포인터를 사용하는 경우, 메탈이 이 장치를 사용하는 것을 명시해야한다.
(쓰레드 동기화 관련 문제) - [[buffer(0)]] ⇒ 앞서 설명한 MSL Attributes
⇒ Metal 라이브러리에서 미리 지정한 buffer(0)이라는 메모리 공간 속 데이터를 가져오겠다는 의미이다.
Shader Fragment Functions
쉐이더 프레그먼트 함수의 목적은 최종 정점 데이터의 위치에 맞는 색을 입히는 것이다.
예제 코드
fragment float4 fragment_main(float4 position [[stage_in]]) {
return float4(1.0, 0.0, 0.0, 1.0);
}
코드 밑줄 부분 설명
- fragment ⇒ MSL에서 프레그먼트 관련된 쉐이더 함수인 것을 알려주는 키워드이다.
- [[stage_in]] ⇒ Swift 코드로 작성한 다양한 메탈 명령어 descriptor 중 특정 구조에 맞춰서 알아서 메모리 공간 속 데이터를 가져오겠다는 의미이다.
- 지금의 stage_in을 설정한 데이터 타입은 float4로 메탈은 float4를 사용해 설정한 명령어 descriptor 가 있으면 이 데이터를 자동으로 fragment_main 쉐이더 함수에 넘긴다.
- 참고자료
- Chapter 5 : Meaning of stage_in
💡 MSL의 attributes는 계속 늘어나기 때문에 모든 attributes를 알 수 없다.
그렇기에 앞으로 예제코드를 보며 검색하며 배우는게 유일한 방법 같다…
Libraries
MSL 프로그램을 스토리보드나 UIKit에서 작성 중인 메탈 관련 코드와 연결시키기
작성한 메탈 쉐이딩 코드는 프로그램 실행 전 미리 컴파일 된 상태로 보관된다. (동적 컴파일이 아님)
메탈은 컴파일을 해 쉐이딩 코드를 저장하는 파일을 생성하며 이 파일을 library
라고 부르는 것이다. 그리고 이 파일은 .metallib
이라는 확장자를 가진다.
결국 UIKit이나 스토리보드에 작성 중인 메탈 관련 코드는 library를 통해 미리 어떤 쉐이딩 함수를 사용할지 설정 해야한다.
같은 프로젝트에서 만든 쉐이딩 파일 속 쉐이딩 함수를 쓰기
컴파일 시 전체 프로젝트의 쉐이딩 파일의 코드를 저장한다. 그리고 이런 코드들은 DefaultLibrary에 저장된다.
guard let library = device.makeDefaultLibrary() else {
fatalError("Unable to create default shader library")
}
...
for name in library.functionNames {
let function = library.makeFunction(name: name)!
print("\(function)")
}
...
위에 코드 흐름은 다음과 같다.
- 위의 코드는 mtldevice 객체에서 프로젝트의 DefaultLibrary를 가져온다.
- defaultlibrary는 모든 프로젝트의 쉐이딩 함수에 대한 이름과 정보를 가지고 있다. functionNames 프로퍼티로 library의 쉐이딩 함수 이름들을 가져온다. (문자열 타입)
- makeFunction(name: name)에 위에 functionNames에서 가져온 문자열로 입력해 MSL로 입력된 쉐이딩 함수를 스위프트로 가져올 수 있다.
💡 나중에 정점 데이터나 프래그먼트 데이터에 이 함수를 바인딩시켜서 사용하면 된다.
예시) vertexDataInformation.vertexFunction = vertexFunction