为了计算出三角形中每个像素的属性,我们会在光栅化处理期间对顶点着色器(或几何着色器)输出的顶点属性进行插值。随后,再将这些插值数据传至像素着色器中作为它的输入。现假设我们的程序未使用几何着色器,下图展示的即为当前顶点数据所流经的路径。
每个顶点元素都会与 D3D12_INPUT_ELEMENT_DESC 数组中指定的对应语义相关联。而顶点着色器中的每个参数也各附有一个语义,用于使顶点元素与顶点着色器参数相匹配。同样地,顶点着色器的每个输出参数以及像素着色器的每个输入参数也各附有一个语义,负责把顶点着色器的输出参数映射到像素着色器的输入参数
像素着色器与顶点着色器有些相似:前者是针对每一个像素片段 (pixel fragment) 而运行的函数,后者是针对每一个顶点而运行的函数(即在处理每一个像素(顶点)时都要执行一次)。只要为像素着色器指定了输入数据,它就会为像素片段计算出一个对应的颜色。值得我们注意的是,这些输入像素着色器像素片段有可能最终不会传入或留存在后台缓冲区中。因此,在确定后台缓冲区某一像素的过程中,可能会存在多个候选的像素片段。
“像素”和“像素片段”意义的区别:像素是最终写入后台缓冲区中数据,像素片段是写入此像素过程中的竞争者。
下面是一段简单的像素着色器代码,它与4.顶点着色器相呼应。考虑到代码的完整性,此处把顶点着色器部分也一并再次给出。
cbuffer cbPerObject : register(b0)
{
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 color;
}
在这个示例中,像素着色器只简单地返回了插值颜色数据。像素着色器的输入与顶点着色器的输出可以准确匹配,这也是必须满足的一点。像素着色器返回一个 4D 颜色值,而位于此函数参数列表后的 SV_TARGET 语义则表示该返回值的类型应当与渲染目标格式 (render target format) 相匹配(该输出值会被存于渲染目标之中)。
我们可以利用输入/输出结构体重写上述顶点着色器和像素着色器的等价实现。这与之前的表示方法有所不同,我们要将语义附加给输入/输出结构体中的成员,并通过一条用于输出结构体的返回语句代替之前的多个输出参数。
cbuffer cbPerObject : register(b0)
{
float4x4 gWorldViewProj;
};
struct VertexIn
{
float3 Pos : POSITION;
float4 Color : COLOR;
};
struct VertexOut
{
float4 PosH : SV_POSITION;
float4 Color : COLOR;
};
VertexOut VS(VertexIn vin)
{
VertexOut vout;
// 把顶点变换到齐次裁剪空间
vout.PosH = mul(float4(vin.Pos, 1.0f), gWorldViewProj);
// 直接将顶点颜色传至像素着色器
vout.Color = vin.Color;
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
return pin.Color;
}