Unity Shader - 图形管道(The Graphics Pipeline)

Unlit Shader 的数据流:
Object Data -> appdata Struct -> Vertex Shader -> v2f Struct -> Fragment Shader -> Final Color

Vertex Data Structure

第一个结构体是 appdata,它对应于输入组装阶段。为了刷新记忆,这个 appdata 来自于最后一个 shader:

struct appdata
{
    float4 vertex : POSITION;
};

:POSITION是一个着色器语义,它是一个附加到着色器数据结构的字符串,传递关于成员预期用途的信息。通过将语义添加到appdata中,我们告诉输入程序集阶段我们想要的数据是什么。

在这种情况下,我们只要求顶点位置,这是您总是要求的最低点。 如果模型具有纹理,则可以请求更多数据,例如处理纹理的UV信息或顶点颜色。 语义和数据类型必须匹配。 如果你要给它一个POSITION语义的单个浮点数,那么float4的值会被无声地截断以浮动。

数据类型选择变量的“形状”,但语义选择它内部的内容。 多种语义可以填充float4,但是如果我们错误地选择了我们想要的语义,着色器会在运行时中断,可能会以微妙的方式破坏。 当着色器变得复杂时,微妙的着色器破坏是追踪最糟糕的问题之一,所以要小心。

这是收集每个顶点更改数据的唯一方法,但您可以从属性和全局属性中传递其他数据,这些数据不会在每个顶点变化。 OpenGL将这些每个顶点值调用得非常适当,并且可以变化,并且可以全局传递或者着色器中的属性称为uniform。

Vertex Function

下一个可编程阶段是顶点着色器函数被执行的顶点处理阶段。 它将appdata数据结构(appdata)作为参数并返回第二种类型的数据
结构(v2f):

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    return o;
}

这是在顶点着色器中需要完成的最低限度:将顶点的坐标转换为光栅化器可以使用的坐标空间。 你可以通过使用UnityObjectToClipPos函数来实现。 现在,不要过多担心坐标空间,因为我们将在下一章详细解释它们。

Fragment Data Structure

再次,我们包含在v2f数据结构中的成员决定了我们可以从顶点着色器传递哪些数据。 这是上一章的v2f结构:

struct v2f {
    float4 vertex : SV_POSITION;
};

它非常小,只覆盖处理过的顶点的2D位置。 我们仍然需要获得语义上的权利,但是这种数据结构对错误不那么敏感。 请记住,语义不能重复。 例如,您也不能将SV_POSITION语义分配给第二个成员。

Fragment Function

下一个可编程阶段是片段着色阶段,其中执行片段着色器
每个片段。 在第2章的非常简单的例子中,片段着色器只使用顶点的位置。

float4 frag (v2f i) : SV_Target
{
    return _Color;
}

实际上,如果你还记得,那个float4并没有在代码中的任何地方使用过。 但是如果你试图摆脱它,你会发现着色器根本没有渲染任何东西。 即使您没有看到它在着色器代码中反映出来,图形管道也会使用该值。

您可能会注意到,frag函数有一个输出语义,我们在前一章中没有提到。 这用于本书不会涵盖的特定技术。 你应该坚持使用SV_Target,这意味着它输出一个片段颜色。

这个简单的着色器向您展示了从3D场景空间和2D目标渲染空间转换而来的作品,因为我们确实可以将3D模型渲染到2D屏幕中。 但它没有清楚地显示光栅化器的作用。 让我们为这个着色器添加顶点颜色支持。 为此,您需要使用具有顶点颜色的网格。 其中一个包含在本章的示例源代码中。

Adding Vertex Colors Support

基本上,我们在顶点添加一个不同的额外值,这需要传递给光栅化器,光栅化器会插入它。

Appdata Additions

当向应该填充网格的顶点颜色的应用数据(如果网格具有它们)添加成员时,我们需要注意名称和语义。 最好的选择是使用颜色作为成员的名字,并使用COLOR作为语义。 取决于您的平台,仅使用带有不同名称变量的COLOR语义可能无效。 以下是添加此顶点颜色成员后结构的外观:

struct appdata
{
    float4 vertex : POSITION;
    float4 color : COLOR;
};

v2f Additions

在v2f中,您需要添加完全相同的成员。 和以前一样,这个数据结构可能不太挑剔,但是你自己承担风险就改变这个公式。 以下是添加此顶点颜色成员后结构的外观:

struct v2f {
    float4 vertex : SV_POSITION;
    float4 color : COLOR;
};

Assign the Color in the Vertex Function

使用适当的成员准备好结构后,我们在顶点函数中添加一行。 它将任何appdata的颜色成员分配给v2f的颜色成员。 “魔术”就在这里; 它使顶点颜色数据通过光栅化器,这将会适当地插值颜色:

v2f vert (appdata v)
{
    v2f o;
    o.vertex = UnityObjectToClipPos(v.vertex);
    o.color = v.color;
    return o;
}

Use the Color in the Fragment Function

和前面的着色器一样,片段函数只关注返回一种颜色,但在这种情况下,我们忽略了我们的属性(我们可以完全移除它)并使用v2f中包含的插值变量:

fixed4 frag (v2f i) : SV_Target
{
    return i.color; 
}