Cg 编程/Unity/RGB 立方体
本教程讨论了顶点输出参数和片段输入参数。假定您已熟悉 “最小着色器”部分。
在本教程中,我们将编写一个着色器来渲染一个类似于左侧所示的 RGB 立方体。表面上每个点的颜色由其坐标决定;例如,位于位置 的点具有颜色 。例如,点 映射到颜色 ,即纯蓝色。(这是左侧图形右下角的蓝色角落。)
由于我们要创建一个 RGB 立方体,您首先需要创建一个立方体游戏对象。如 “最小着色器”部分 中对球体的描述,您可以通过从主菜单中选择 GameObject > 3D Object > Cube 来创建一个立方体游戏对象。继续创建材料和着色器对象,并将着色器附加到材料,并将材料附加到立方体,如 “最小着色器”部分 中所述。
以下是着色器代码,您应该将其复制并粘贴到您的着色器对象中
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
// for multiple vertex output parameters an output structure
// is defined:
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
output.pos = UnityObjectToClipPos(vertexPos);
output.col = vertexPos + float4(0.5, 0.5, 0.5, 0.0);
// Here the vertex shader writes output data
// to the output structure. We add 0.5 to the
// x, y, and z coordinates, because the
// coordinates of the cube are between -0.5 and
// 0.5 but we need them between 0.0 and 1.0.
return output;
}
float4 frag(vertexOutput input) : COLOR // fragment shader
{
return input.col;
// Here the fragment shader returns the "col" input
// parameter with semantic TEXCOORD0 as nameless
// output parameter with semantic COLOR.
}
ENDCG
}
}
}
如果您的立方体没有正确着色,请检查控制台中的错误消息(通过从主菜单中选择 Window > General > Console),确保您已保存着色器代码,并检查您是否已将着色器对象附加到材料对象,并将材料对象附加到游戏对象。
我们着色器的主要任务是在片段着色器中将片段输出颜色(即带有语义 COLOR
的片段输出参数)设置为顶点着色器中可用的顶点位置。实际上,情况并非完全如此:Unity 默认立方体中带有语义 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
)。事实上,只有语义用于确定哪些顶点输出参数对应于哪些片段输入参数。我们可以使用其他语义,例如 COLOR
,而不是语义 TEXCOORD0
,这里实际上没有区别,除了带有语义 COLOR
的参数通常被钳制到 0 到 1 之间的数值(在本例中是没问题的)。然而,通常情况下,对各种参数使用语义 TEXCOORD0
、TEXCOORD1
、TEXCOORD2
等。
下一个问题是指定多个顶点输出参数。由于 return 指令只能返回一个值,因此通常为所有必需的顶点输出参数定义一个结构。这里,此结构称为 vertexOutput
struct vertexOutput {
float4 pos : SV_POSITION;
float4 col : TEXCOORD0;
};
通过将此结构用作片段着色器函数的参数,我们确保语义匹配。请注意,在 Cg 中(与 C 相反),我们不必在定义此类型变量时编写 struct vertexOutput
,而是可以使用名称 vertexOutput
(不带 struct
)来表示相同的类型。
使用输出结构的另一种方法是使用顶点着色器函数带有 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
}
}
}
但是,在实践中使用输出结构更为常见,并且可以确保顶点输出参数和片段输入参数具有匹配的语义。
RGB 立方体代表了可用颜色的集合(即显示器的色域)。因此,它也可以用来展示颜色变换的效果。例如,颜色到灰度变换可以计算红色、绿色和蓝色颜色分量的平均值,即 ,然后将此值放入片段颜色的所有三个颜色分量中,以获得具有相同亮度的灰度值。除了平均值之外,还可以使用相对亮度,即 。当然,任何其他颜色变换(更改饱和度、对比度、色调等)也适用。
此着色器的另一种变体可以计算一个 CMY(青色、洋红、黄色)立方体:对于位置 ,你可以从纯白色中减去与 成比例的红色量,以产生青色。此外,你会减去与 分量成比例的绿色量,以产生洋红,以及与 成比例的蓝色量,以产生黄色。
如果你真的想变得更复杂,你可以计算一个 HSV(色调、饱和度、亮度)圆柱体。对于 和 坐标介于 -0.5 和 +0.5 之间,你可以得到一个角度 ,介于 0 到 360° 之间,在 Cg 中使用 180.0+degrees(atan2(z, x))
,以及一个距离 ,介于 0 到 1 之间,从 轴使用 2.0 * sqrt(x * x + z * z)
。Unity 内置圆柱体的 坐标介于 -1 和 1 之间,可以通过 转换为介于 0 到 1 之间的亮度值 。从 HSV 坐标计算 RGB 颜色在 维基百科关于 HSV 的文章 中有描述。
顶点输出参数的插值
[edit | edit source]关于顶点输出参数和片段输入参数的故事还没有结束。如果你选择立方体游戏对象,你会在场景视图中看到它只包含 12 个三角形和 8 个顶点。因此,顶点着色器可能只调用八次,并且只有八个不同的输出被写入顶点输出参数。然而,立方体上有更多不同的颜色。这是怎么回事呢?
事实上,顶点着色器只针对每个三角形的每个顶点调用。然而,不同顶点顶点输出参数的不同值会在三角形上进行插值。然后,片段着色器针对三角形覆盖的每个像素调用,并接收顶点输出参数的插值作为片段输入参数。 这种插值的细节在 “光栅化”部分 中有描述。
如果你想确保片段着色器从顶点着色器接收一个确切的、非插值的值(所谓平面着色),你可以确保顶点着色器为三角形的每个顶点写入顶点输出参数的相同值,或者你可以使用 HLSL 存储类修饰符 nointerpolation
。在本例中,你可以使用这个结构体
struct vertexOutput {
float4 pos : SV_POSITION;
nointerpolation float4 col : TEXCOORD0;
};
总结
[edit | edit source]本教程到此结束。恭喜你!在其他内容中,你已经了解到
- 什么是 RGB 立方体。
- 什么是输出结构,以及如何定义它。
- 输出结构如何用于确保顶点输出参数具有与片段输入参数相同的语义。
- 写入顶点输出参数的值如何在三角形上进行插值,然后作为片段着色器的输入参数接收。
进一步阅读
[edit | edit source]如果你想了解更多
- 关于进出顶点着色器和片段着色器的数据流的信息,你应该阅读 “可编程图形管道”部分 中的描述。
- 关于向量和矩阵运算(例如表达式
vertexPos + float4(0.5, 0.5, 0.5, 0.0)
),你应该阅读 “向量和矩阵运算”部分。 - 关于顶点输出参数的插值,你应该阅读 “光栅化”部分。
- 关于 Unity 关于在 Unity 的 ShaderLab 中编写顶点着色器和片段着色器的官方文档,你应该阅读 Unity 的 ShaderLab 参考关于“编写顶点和片段着色器”。