本教程讨论顶点输出参数和片段输入参数。假设您熟悉“最小的着色器”部分。

在本教程中,我们将编写一个着色器来渲染一个类似于下图所示的RGB多维数据集。表面上每个点的颜色由其坐标确定;
即位置上的一个点(x,y,z)有颜色(red,green,blue)=(x,y,z)。例如,点(x,y,z)=(0,0,1)被映射到颜色(red,green,blue)=(0,0,1),即纯蓝色。(这是下图的右下角的蓝色角落。)




Unity ASE中的立方体渐变透明 unity怎么换立方体颜色_shader


RGB立方体:x,y,z坐标映射到红色, 绿色和蓝色颜色分量。


准备

由于我们要创建一个RGB立方体(cube),所以首先必须创建一个立方体(cube)游戏对象。如“最小的着色 器””一节所述的球体,您可以通过从主菜单中选择GameObject> 3D Object> Cube来创建立方体(cube)游戏对象。然后创建材质和着色器,并将着色器附加到材质,然后把材质附加到立方体(cube)对象上,如“最小的着色器””部分所述。

着色器代码

下面是shader代码,您可以将其复制并粘贴到shader文件中,并保存。

Shader "Cg shader for RGB cube" { 
   SubShader { 
      Pass { 
         CGPROGRAM 

         // 先声明顶点着色器函数
         #pragma vertex vert 
         // 先声明片段着色器函数
         #pragma fragment frag 

         // 定义顶点输出参数的结构体
         struct vertexOutput {
            float4 pos:SV_POSITION;
            float4 col:TEXCOORD0;
         };

         // 顶点着色器函数 
         vertexOutput vert(float4 vertexPos:POSITION)
         {
            vertexOutput output; // 定义需要输出的结构体变量

            output.pos =  mul(UNITY_MATRIX_MVP, vertexPos);
            output.col = vertexPos + float4(0.5, 0.5, 0.5, 0.0);
            //这里,我们将顶点着色器的输出数据写入输出结构中。 
            //我们要将x,y,z坐标都加上0.5,因为立方体的坐标是在[-0.5,0.5]之间的范围内,
            //而我们需要的颜色值是在[0.0,1.0]之间。
            return output;
         }
         //片段着色器函数
         float4 frag(vertexOutput input):COLOR
         {
            return input.col; 
            //这里直接把顶点着色器的输出参数"col"(即是片段做色器的输入参数)颜色值,作为片段做色器
            //的输出参数返回出去,得到的就是顶点着色器的差值颜色值。(不懂别急,之后会讲到...)
         }

         ENDCG  
      }
   }
}

保存代码,返回Unity界面,查看是否得到想要的结果。如果报错了,自己找找原因吧囧…^_^

什么是语义

在本例的代码中,POSITION和SV_POSITION都是CG/HLSL中的语义(semantics),他们是不可忽略的,这些语义将告诉系统用户需要哪些输入值,以及用户的输出是什么。例如这里,POSITION将告诉Unity,把模型的定点坐标填充到输入参数vertexPos中,SV_POSITION将告诉Unity,定点着色器的输出是裁剪工间中的顶点坐标。如果没有这些语义才限定输入输出参数的话,渲染器就完全不知道用户的输入输出是什么,因此也会得到错误的结果。

顶点和片段着色器之间的通信

我们的shader主要任务是将顶点着色器的位置信息输出给片段着色器用,让片段做色器把位置信息作为颜色值进行输出(即,带有语义的片段输出参数COLOR)。实际上,这两者的数据并不完全相同,在Unity的默认立方体中,具有POSITION语义的顶点坐标输入参数的取值范围在[-0.5,0.5]之间,而我们需要的颜色值分量是在[0,1]之间。所以,我们需要把位置信息(x,y,z)坐标加上0.5来得到我们想要的值。它的表达式是:vertexPos + float4(0.5, 0.5, 0.5, 0.0)。

然而,主要的问题是:我们如何从顶点着色器获取值给片段着色器用?事实证明,这样做的唯一方法是让顶点着色器的输出参数和片段着色器的输入参数使用同样的语义对(这里用TEXCOORD0)。也就是说,要确定哪个顶点输出参数的语义和哪个片段输入参数的语义相对应。除了TEXCOORD0语义之外,我们还可以使用另一种语义,例如COLOR,在这里并不重要,除了具有语义的COLOR参数经常被钳制到0和1之间的值(在本章节的情况下可以这样做)之外,通常还可以使用的参数语义有:TEXCOORD0TEXCOORD1TEXCOORD2等等。

下一个问题是指定多个顶点输出参数。由于返回指令只能返回一个值,所以通常要为所有必要的顶点输出参数定义一个结构体。这里,这个结构体叫做vertexOutput:

struct vertexOutput  
{
    float4 pos:SV_POSITION; 
    float4 col:TEXCOORD0; 
};

我们把此结构体作为片段着色器函数的参数,我们要确保语义匹配。 请注意,在Cg(与C相反)中,我们不必在定义此类型的变量时编写struct vertexOutput,但我们可以使用相同类型的名称vertexOutput(不需要struct)。

out限定符

使用输出结构的另一种方法是使用out限定符作为顶点着色器函数的参数,例如:

Shader "Cg shader for RGB cube" { 
   SubShader { 
      Pass { 
         CGPROGRAM 

         // 先声明顶点着色器函数
         #pragma vertex vert 
         // 先声明片段着色器函数
         #pragma fragment frag 

         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 
      }
   }
}

然而,使用结构体的方式更为常见,并且要确保顶点着色器的输出参数和片段着色器的输入参数的语义要匹配。

这个shade的变体

RGB立方体表示可用颜色的集合(即显示器的色域)。因此,它也可以用于显示颜色变换的效果。例如,彩色到灰色变换,可以计算红色,绿色和蓝色分量的平均值,即(red + green + blue)/3,然后将该值放在片段着色器对应的三个颜色分量中,以获得相同亮度的灰度值,而不是平均值。也可以使用相对亮度,即0.21red + 0.72green + 0.07blue。当然,任何其他颜色转换(饱和度,对比度,色相等)也是适用的。

该着色器的另一个变体可以计算CMY(青色,品红色,黄色)立方体:对于位置(x,y,z) ,您可以从纯白色减去与之成比例的红色数量x以产生青色。此外,您还可以按比例减去一定数量的绿色 y产生品红色,并且按比例减去一定数量的蓝色z产生黄色。

顶点输出参数插值

关于顶点输出参数和片段输入参数的故事还没有完成。如果选择cube 游戏对象,您将在“Scene 视图”中看到它只包含12个三角形和8个顶点。因此,顶点着色器可能只被调用八次,只有八个不同的输出被写入顶点输出参数。但是,立方体上还有更多的颜色,这是怎么产生的呢?

实际上,顶点着色器仅被三角形的每个顶点调用。三角形不同顶点的顶点输出参数在三角形内被计算插值,然后被由三角形覆盖的每个像素进行调用片段着色器,并将顶点输出参数的内插值作为片段输入参数进行调用。该插值的细节将在“栅格化”部分中进行说明。

如果你不想片段着色器得到一个插值,你只需把三角形所有点的顶点着色器的值设置为相同的值即可。

语义详解

语义,实际上,这些是CG/HLSL提供的语义(semantics),如果读者从前接触过CG/HLSL编程的话,可能对这些语义很熟悉。语义实际上就是一个赋给Shader输入和输出的字符串。这个字符串表达了这个参数的含义”。通俗地讲,这些语义可以让Shader知道从哪里读取数据,并把数据输出到哪里,它们在CG/HLSL的Shader流水线管中是不可或缺的。需要注意的是,Unity井没有支持所有的语义。
通常情况下,这些输入输出变量并不需要有特别的意义,也就是说我们可以自行决定这些变量的用途。例如在上面的代码中,顶点着色器的输出结构体中我们用COLORO语义去描述color变量。color变量本身存储了什么,Shader流水线并不关心。

总结

恭喜!这是本章节的结尾。在本章节中,您学习到了:

  • 什么是RGB立方体。
  • 什么是输出结构体,以及如何定义它。
  • 如何使用输出结构体来确保顶点输出参数与片段输入参数具有相同的语义。
  • 片段着色器在接收输入参数前,三角形是如何对顶点输出参数进行插值的。