정점 버퍼들이 정점 셰이더, 래스터화기 단계를 거치면, 정점 특성들이 삼각형의 픽셀들을 따라 보간한 결과를 픽셀 셰이더에 입력한다. 픽셀 셰이더도 정점 셰이더처럼 본질적으로 하나의 함수이다. 정점 셰이더랑 차이는, 각 정점이 아니라 각 픽셀 단편마다 실행된다는 것이다. 픽셀 단편이란 게 뭔 말인가? → 하나의 픽셀에 선정되기 위해서는 경쟁에서 이겨야 한다. 예를 들어 깊이 값이 더 작은 다른 픽셀 단편에 가려질 수도 있고, 픽셀 셰이더 자체에서 잘려 나갈 수도 있고, 스텐실 판정에서도 폐기될 수 있다. 예시 픽셀 셰이더를 보자.
cbuffer cbPerObject
{
float4x4 gWorldViewProj;
};
void VS(float3 iPosL : POSITION,
float4 iColor : COLOR,
out float4 oPosH : SV_POSITION,
out float4 oColor : COLOR)
{
oPosH = mul(float4(iPosL, 1.0f), gWorldViewProj);
oColor = iColor;
}
// 픽셀
float4 PS(float4 posH : SV_POSITION, float4 color : COLOR) : SV_Target
{
return pin.Color;
}
이 예에서 픽셀 셰이더 함수는 그냥 보간된 색상 값을 돌려준다. 픽셀 셰이더의 입력이 정점 셰이더의 출력과 정확히 일치한다. 이건 필수조건이다. 함수명 끝 부분의 SV_TARGET 의미소는, 이 함수의 반환값 형식이 렌더 대상 형식과 일치해야 함을 뜻한다. 다음 코드는 구조체를 사용하여 약간 다르게 표현하겠다.
cbuffer cbPerObject
{
float4x4 gWorldViewProj;
};
struct VertexIn
{
float3 PosL : POSITION;
float4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj);
vout.Color = vin.Color;
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
return pin.Color;
}
입력 받는 매개변수만 좀 달라졌다. 정점 셰이더랑 픽셀 셰이더는 이렇게 하나의 파일 안에 같이 넣어도 되고, 다르게 넣어도 된다.