버텍스 셰이더(Vertex Shader)
버텍스 셰이더는 그래픽 파이프라인 내에서 주로 버텍스 데이터를 처리하는 셰이더이다. 버텍스 셰이더의 주요 역할은 입력된 버텍스 데이터(예: 위치, 색상, 텍스처 좌표 등)에 대한 다양한 연산을 수행하여 다음 단계로 전달하는 것이다. 이를 통해 그래픽을 더욱 동적으로 반응하도록 만든다. 버텍스 셰이더가 실행되는 주요 단계는 다음과 같다:
-
입력 버텍스 처리: 버텍스 셰이더는 모델의 각 버텍스에 대해 호출되며, 각 버텍스의 속성(예: 위치, 법선 벡터, 텍스처 좌표 등)을 처리한다.
-
모델-뷰 변환: 버텍스 셰이더는 모델 좌표계를 월드 좌표계로 변환하고, 이를 다시 뷰 좌표계로 변환하는 과정을 거친다. 이를 위해 일반적으로 행렬 변환을 사용한다. 예를 들어, 모델-뷰 변환은 다음과 같이 표현될 수 있다:
여기서 \mathbf{v}는 원래의 버텍스 위치, \mathbf{M}_{model}은 모델 변환 행렬, \mathbf{M}_{view}는 뷰 변환 행렬이다.
- 투영 변환: 뷰 공간의 좌표를 클립 공간으로 변환한다. 이 변환은 주로 원근 투영이나 직교 투영을 위해 사용되며, 투영 행렬을 통해 이루어진다. 투영 행렬은 다음과 같이 표현될 수 있다:
여기서 \mathbf{M}_{projection}는 투영 변환 행렬이다.
- 출력 버텍스: 최종 변환된 버텍스 위치와 필요한 출력을 다음 셰이더 단계로 전달한다.
다음은 간단한 버텍스 셰이더의 예이다:
#version 330 core
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
out vec3 fragColor;
void main()
{
fragColor = inColor;
gl_Position = projection * view * model * vec4(inPosition, 1.0);
}
픽셀 셰이더(Fragment Shader)
픽셀 셰이더는 그래픽 파이프라인 내에서 주로 화면 픽셀 단위로 셰이딩을 담당하는 셰이더이다. 픽셀 셰이더의 주요 역할은 픽셀 데이터를 기반으로 색상과 텍스처를 계산하여 최종적으로 화면에 렌더링한다. 픽셀 셰이더가 실행되는 주요 단계는 다음과 같다:
-
입력 보간: 픽셀 셰이더는 래스터화 단계에서 생성된 보간된 값을 입력으로 받는다. 보간된 값은 보통 인접 버텍스의 출력(예: 색상, 텍스처 좌표)이다.
-
텍스처 맵핑: 텍스처 좌표를 사용하여 텍스처 맵에서 색상을 샘플링한다. 텍스처 맵핑은 다음과 같이 표현될 수 있다:
- 라이팅 계산: 광원 정보를 바탕으로 각 픽셀의 최종 색상을 계산한다. 예를 들어, Phong 라이팅 모델은 다음과 같은 방정식을 사용한다:
여기서 \mathbf{I}_a는 앰비언트(light) 조명, \mathbf{I}_d는 디퓨즈(diffuse) 조명, \mathbf{I}_s는 스펙큘러(specular) 조명 성분이며, \mathbf{N}, \mathbf{L}, \mathbf{R}, \mathbf{V}, n은 각각 법선 벡터, 광원 벡터, 반사 벡터, 뷰어 벡터, 스펙큘러 강조지수 이다.
- 출력 색상: 최종 계산된 색상을 출력하여 렌더 타겟(주로 프레임 버퍼)에 저장한다.
다음은 간단한 픽셀 셰이더의 예이다:
#version 330 core
in vec3 fragColor;
out vec4 color;
void main()
{
color = vec4(fragColor, 1.0);
}
셰이더 프로그래밍 언어
셰이더는 주로 GLSL(OpenGL Shading Language), HLSL(High-Level Shading Language), 또는 Cg(C for Graphics)와 같은 언어로 작성된다. 이 언어들은 각 그래픽 API와 호환되는 높은 수준의 언어로, 효율적인 그래픽 처리를 위해 셰이더를 쉽게 작성할 수 있도록 돕는다.
GLSL
GLSL은 OpenGL과 함께 사용하는 셰이더 언어로, 직관적이며 C 언어와 유사한 문법을 갖추고 있다. 최신 버전의 GLSL은 다양한 데이터 타입, 내장 함수, 그리고 사용자 정의 함수를 지원하여 복잡한 그래픽 연산을 수행할 수 있다.
다음은 간단한 GLSL 예제이다:
#version 330 core
layout(location = 0) in vec3 inPosition;
layout(location = 1) in vec3 inColor;
out vec3 fragColor;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
fragColor = inColor;
gl_Position = projection * view * model * vec4(inPosition, 1.0);
}
HLSL
HLSL은 Direct3D와 함께 사용하는 셰이더 언어이다. HLSL의 문법은 C++와 유사하며, Direct3D 그래픽 API와 밀접하게 통합되어 있다.
다음은 간단한 HLSL 예제이다:
cbuffer MatrixBuffer : register(b0)
{
matrix model;
matrix view;
matrix projection;
};
struct VertexInputType
{
float4 position : POSITION;
float4 color : COLOR;
};
struct PixelInputType
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
PixelInputType VertexShader(VertexInputType input)
{
PixelInputType output;
output.position = mul(input.position, model);
output.position = mul(output.position, view);
output.position = mul(output.position, projection);
output.color = input.color;
return output;
}
float4 PixelShader(PixelInputType input) : SV_TARGET
{
return input.color;
}
셰이더 모델
셰이더 프로그래밍 언어는 다양한 셰이더 모델을 지원한다. 셰이더 모델은 셰이더 명령어 집합의 집합체로, 수행할 수 있는 그래픽 기능의 집합을 정의한다. 높은 셰이더 모델은 더 많은 기능과 복잡한 연산을 지원한다.
셰이더 적용 및 디버깅
셰이더 적용
셰이더를 그래픽 파이프라인에 적용하는 과정은 여러 단계로 이루어진다:
-
셰이더 소스 코드 작성 및 컴파일: 셰이더 소스 코드를 작성하여 컴파일하고, 컴파일된 셰이더를 사용하여 셰이더 프로그램을 생성한다.
-
셰이더 프로그램 링크: 컴파일된 셰이더를 링크하여 셰이더 프로그램을 생성한다. 이는 여러 셰이더(버텍스 셰이더, 픽셀 셰이더 등)를 결합하여 전체 그래픽 파이프라인을 형성한다.
-
셰이더 프로그램 사용: 그래픽 렌더링을 수행하기 전에 생성된 셰이더 프로그램을 사용하여 그래픽 파이프라인에 적용한다.
다음은 OpenGL에서 셰이더를 적용하는 예제이다:
GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
GLuint shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
glUseProgram(shaderProgram);
셰이더 디버깅
셰이더 디버깅은 매우 중요하다. 잘못된 셰이더 코드는 예상치 못한 그래픽 오류를 발생시킬 수 있기 때문이다. 디버깅을 위해 다음과 같은 도구와 방법을 사용할 수 있다:
- 셰이더 컴파일 오류 확인: 셰이더를 컴파일할 때 발생하는 오류 메시지를 확인한다. 이는 컴파일 로그를 통해 가능한다.
cpp
GLint success;
GLchar infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if(!success) {
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "Vertex Shader Compilation Failed:\n" << infoLog << std::endl;
}
-
렌더링 결과 확인: 그래픽 장면을 시각적으로 검사하여 셰이더의 출력이 올바른지 확인한다. 예상치 못한 결과가 나오면 셰이더 코드를 수정한다.
-
셰이더 디버깅 도구: Pix, RenderDoc, NVIDIA Nsight와 같은 셰이더 디버깅 도구를 사용하여 셰이더 연산을 단계별로 디버깅한다.