本教程讨论顶点vertex的输出参数和片元fragment的输入参数。此教程假设您已经熟悉“Minimal Shader”部分。在本教程中,我们将编写一个shader来渲染类似上图的RGB立方体。表面上每一个点的颜色都由它的坐标决定。即,位置(x,y,z)上的点的颜色(red,green,blue)= (x,y,z)。例如,点(x,y,z)=(0,0,1)映射到的颜色(red,green,blue)=(0,0,1),也就是纯蓝色。(这就是该图右下角的蓝色角)。
准备工作
在我们创建RGB cube前,你必须先创建一个cube游戏对象。然后创建material和shader,并把material赋给cube。
Shader 代码
将shader代码复制粘贴到你的shader中:
Shader "Cg shader for RGB cube" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert // vert function is the vertex shader
#pragma fragment frag // frag function is the fragment shader
// 对于多个顶点输出参数,定义了输出的结构体
struct vertexOutput {
float4 pos : SV_POSITION;
float4 col : TEXCOORD0;
};
vertexOutput vert(float4 vertexPos : POSITION)
// vertex shader
{
vertexOutput output; // we don't need to type 'struct' here
//UnityObjectToClipPos将顶点坐标从模型空间转换到裁剪空间(MVP映射)
output.pos = UnityObjectToClipPos(vertexPos);
output.col = vertexPos + float4(0.5, 0.5, 0.5, 0.0);
//这里顶点着色器将输出写入输出结构vertexOutput中
// 我们将x,y和z坐标加0.5,因为cube的坐标在-0.5到0.5之间,我们需要他们在0.0到1之间
return output;
}
float4 frag(vertexOutput input) : COLOR // fragment shader
{
return input.col;
// 这里片元着色器返回输入input参数的col属性。
// col为TEXCOORD0纹理坐标
}
ENDCG
}
}
}
顶点和片元着色器之间的通信
shader的主要任务是设置片元着色器的输出颜色到顶点着色器中合适的顶点位置去。事实上,这并非完全正确:对于Unity默认的Cube,顶点输入参数的坐标POSITION
在-0.5到0.5之间,而我们希望的颜色分量在0.0到1.0之间。因此,我们需要在x,y和z分量上加上0.5,这是通过以下表达式完成的:vertexPos + float4(0.5,0.5,0.5,0.0)
但是,主要的问题是:如何从顶点着色器获取任何值到片元着色器?事实证明,这么做的唯一方法是使用具有相同语义的成对的顶点输出参数和片段输入参数(在这里是TEXCOORD0
)。事实上,这语义仅用于确定顶点的输出参数对应片元的输出参数。除了语义TEXCOORD0
,我们还可以使用另一种语语义,例如,COLOR
。
实际上在此处并不重要,除此之外带有COLOR
语义的参数,通常被限制为0到1之间的值。但是,对于各种参数通常都使用语义TEXCOORD0,TEXCOORD1,TEXCOORD2
等。
下一个问题是指定多个顶点的输出参数。因为只能返回一个值,所以通常对所有必要的顶点输出参数定义一种结构。在这里,这个结构被称为vertexOutput
:
struct vertexOutput {
float4 pos : SV_POSITION;
float4 col : TEXCOORD0;
};
通过使用这种结构体作为片元着色器函数的实际参数(argument),我们确保语义是匹配的。请注意,在Cg中(相对于C),在定义这种类型变量时,我们不需要编写结构体vertexOutput ,对于相同的类型我们可以仅使用名称为vertexOutput 。
out修饰符
使用输出结构的替代方法是将顶点着色器函数的参数与out限定符一起使用,例如:
Shader "Cg shader for RGB cube" {
SubShader {
Pass {
CGPROGRAM
#pragma vertex vert // vert function is the vertex shader
#pragma fragment frag // frag function is the fragment shader
void vert(float4 vertexPos : POSITION,
out float4 pos : SV_POSITION,
out float4 col : TEXCOORD0)
{
pos = mul(UNITY_MATRIX_MVP, vertexPos);
col = vertexPos + float4(0.5, 0.5, 0.5, 0.0);
return;
}
float4 frag(float4 pos : SV_POSITION,
float4 col : TEXCOORD0) : COLOR
{
return col;
}
ENDCG
}
}
}
但是,在实际中使用输出结构更为常见,并且可以确保顶点输出参数和顶点输入参数具有相匹配的语义。
在此Shader变化
这个RGB Cube代表可用颜色的集合(即显示器的色域)。因此它也可以用来显示色彩变换的效果。例如,一个颜色到灰色的变换可以计算红,绿,蓝分量的平均值,即(red+green+blue)/3
,然后将这个值赋给片元着色器的所有三个颜色的分量中获得相同亮度的灰度值。除了平均值,还可以使用相对亮度,即0.21red+0.72green+0.07blue
。当然,任何其他的颜色变换(更改饱和度,对比度,色相等)也同样适用。
另一个此Shader的变化是计算CMY(青色,品红,黄色)Cube:对于位置(x,y,z)你可以从纯白中减去与x成正比的红色量,以产生青色。减去与y分量成比例的绿色量以产生品红色,减去与z分量成比例的蓝色量将产生黄色。
如果您还想更花哨一点,你还可以计算一个HSV Cylinder:对于-0.5到0.5之间的x和z坐标,你可以通过Cg中的180.0+degrees(atan2(z, x))
得到0到360°之间的角H 和2.0 * sqrt(x * x + z * z)
得到在0到1之间的与y轴的距离S。Unity内置Cylinder的y坐标在-1到1之间,可以通过(y+1.0)/2.0
转换为0到1之间的值V。 从HSV坐标计算RGB颜色的方法在Wikipedia中有关HSV的文章中进行了描述。
顶点输出参数的插值
关于顶点输出参数和片元输入参数的故事还没有结束 。如果你选择Cube游戏对象,您将在Scene视图中看到它仅包含12个三角形和8个顶点(点击Scene右上角下拉菜单选择Wireframe可以看到)。因此,顶点着色器可能仅仅被调用了8次,并且只有8个不同的输出被写入顶点的输出参数。但是,Cube上面却有更多的颜色,那是怎么发生的?
事实上,顶点着色器仅被每个三角形的每个顶点调用。但是在整个三角形上插值了不同顶点的顶点输出参数的不同值。然后,为三角形覆盖的每一个像素调用片元着色器,并接收顶点输出参数的插值作为片元着色器的输入参数。
如果要确保一个片元着色器通过一个顶点着色器接收一个精确的值,非插值出的值(所谓的平面着色),你可以确保顶点着色器为所有三角形的顶点的顶点着色器的输出参数都写入相同的值。在我们的示例中,您可以使用以下结构:
struct vertexOutput {
float4 pos : SV_POSITION;
nointerpolation float4 col : TEXCOORD0;
};
总结
至此,本教程结束。您现在已经学会:
- 什么是RGB Cube
- 什么是输出结构以及如何定义它
- 如何使用输出结构来确保顶点输出参数与片元输入参数具有相同的语义
- 在片段着色器将输入到顶点输出参数的值作为输入参数接收之前,如何在整个三角形上插入写入顶点输出参数的值
更多参考阅读
如果您想了解更多
- 有关数据流入顶点着色器和片段着色器以及从顶点着色器和片段着色器流出的信息,您应该阅读可编程图形管线一节中的说明。
- 有关向量和矩阵运算(例如,表达式
vertexPos + float4(0.5,0.5,0.5,0.0)
),您应该阅读向量和矩阵运算一节中的描述 - 有关Unity在Unity’s ShaderLab中编写顶点着色器和片段着色器的官方文档,您应该阅读Unity的ShaderLab文档一节中的描述