GLSL 编程/Unity/RGB 立方体
本教程介绍了**变化量变量**。它基于“最小着色器”部分。
在本教程中,我们将编写一个着色器来渲染类似于左侧所示的 RGB 立方体。表面上每个点的颜色由其坐标决定;即,位于位置 的点具有颜色。例如,点 映射到颜色,即纯蓝色。(这是左侧图形右下角的蓝色角。)
由于我们要创建一个 RGB 立方体,您首先需要创建一个立方体游戏对象。如“最小着色器”部分中对球体的描述,您可以通过从主菜单中选择**GameObject > Create Other > Cube** 来创建一个立方体游戏对象。继续创建材质和着色器对象,并将着色器附加到材质,并将材质附加到立方体,如“最小着色器”部分中所述。
以下是着色器代码,您应该将其复制并粘贴到您的着色器对象中
Shader "GLSL shader for RGB cube" {
SubShader {
Pass {
GLSLPROGRAM
#ifdef VERTEX // here begins the vertex shader
varying vec4 position;
// this is a varying variable in the vertex shader
void main()
{
position = gl_Vertex + vec4(0.5, 0.5, 0.5, 0.0);
// Here the vertex shader writes output data
// to the varying variable. 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.
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif // here ends the vertex shader
#ifdef FRAGMENT // here begins the fragment shader
varying vec4 position;
// this is a varying variable in the fragment shader
void main()
{
gl_FragColor = position;
// Here the fragment shader reads intput data
// from the varying variable. The red, gree, blue,
// and alpha component of the fragment color are
// set to the values in the varying variable.
}
#endif // here ends the fragment shader
ENDGLSL
}
}
}
如果您的立方体没有正确着色,请检查控制台中的错误消息(通过从主菜单中选择**Window > Console**),确保您已保存着色器代码,并检查您是否已将着色器对象附加到材质对象,并将材质对象附加到游戏对象。
我们的着色器的主要任务是在片段着色器中将输出片段颜色(gl_FragColor
)设置为顶点着色器中可用的位置(gl_Vertex
)。实际上,这并不完全正确:Unity 默认立方体的 gl_Vertex
中的坐标介于 -0.5 和 +0.5 之间,而我们希望颜色分量介于 0.0 和 1.0 之间;因此,我们需要向 x、y 和 z 分量添加 0.5,这由以下表达式完成:gl_Vertex + vec4(0.5, 0.5, 0.5, 0.0)
。
然而,主要问题是:我们如何从顶点着色器获取任何值到片段着色器?事实证明,做到这一点的**唯一**方法是使用变化量变量(简称变化量)。顶点着色器的输出可以写入变化量变量,然后片段着色器可以将其作为输入读取。这正是我们需要的。
要指定变化量变量,必须在顶点着色器和片段着色器中任何函数之外使用修饰符 varying
(在类型之前)来定义它;在我们的示例中:varying vec4 position;
。接下来是关于变化量变量的最重要规则
顶点着色器中变化量变量定义的类型和名称必须与片段着色器中变化量变量定义的类型和名称完全匹配,反之亦然。 |
这是为了避免 GLSL 编译器无法确定顶点着色器的哪个变化量变量应该与片段着色器的哪个变化量变量匹配的歧义情况。
顶点和片段着色器中变化量变量定义必须匹配的要求通常会导致错误,例如,如果程序员更改了顶点着色器中变化量变量的类型或名称,但忘记在片段着色器中更改它。幸运的是,Unity 中有一个很好的技巧可以避免这个问题。考虑以下着色器
Shader "GLSL shader for RGB cube" {
SubShader {
Pass {
GLSLPROGRAM // here begin the vertex and the fragment shader
varying vec4 position;
// this line is part of the vertex and the fragment shader
#ifdef VERTEX
// here begins the part that is only in the vertex shader
void main()
{
position = gl_Vertex + vec4(0.5, 0.5, 0.5, 0.0);
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
#endif
// here ends the part that is only in the vertex shader
#ifdef FRAGMENT
// here begins the part that is only in the fragment shader
void main()
{
gl_FragColor = position;
}
#endif
// here ends the part that is only in the fragment shader
ENDGLSL // here end the vertex and the fragment shader
}
}
}
正如此着色器中的注释所解释的那样,行 #ifdef VERTEX
实际上并没有标记顶点着色器的开头,而是标记了**仅**在顶点着色器中的一部分的开头。类似地,#ifdef FRAGMENT
标记了仅在片段着色器中的一部分的开头。实际上,两个着色器都以行 GLSLPROGRAM
开头。因此,GLSLPROGRAM
和第一个 #ifdef
行之间的任何代码都将由顶点着色器和片段着色器共享。(如果您熟悉 C 或 C++ 预处理器,您可能已经猜到了这一点。)
这对于变化量变量的定义非常完美,因为这意味着我们只需要键入一次定义,它将被放入顶点和片段着色器中;因此,保证了匹配的定义!即,我们只需要键入更少的代码,并且由于变化量变量定义之间的不匹配而不可能产生编译错误。(当然,代价是我们必须键入所有这些 #ifdef
和 #end
行。)
RGB 立方体代表了可用的颜色集(即显示器的色域)。因此,它也可以用来显示颜色变换的效果。例如,颜色到灰度的变换将计算红色、绿色和蓝色颜色分量的平均值,即,然后将此值放入片段颜色的所有三个颜色分量中以获得相同强度的灰色值。除了平均值之外,还可以使用相对亮度,即。当然,任何其他颜色变换(更改饱和度、对比度、色调等)也适用。
此着色器的另一种变体可以计算一个CMY(青色、洋红色、黄色)立方体:对于位置 你可以从纯白色中减去一个与 成比例的红色,以产生青色。此外,你将减去一个与 分量成比例的绿色,以产生洋红色,以及一个与 成比例的蓝色,以产生黄色。
如果你真的想变得更花哨,你可以计算一个HSV(色调、饱和度、明度)圆柱体。对于 和 坐标介于 -0.5 和 +0.5 之间,你可以得到一个角度 介于 0 和 360° 之间,在 GLSL 中用 180.0+degrees(atan(z, x))
表示,以及一个距离 介于 0 和 1 之间,从 轴用 2.0 * sqrt(x * x + z * z)
表示。Unity 内置圆柱体的 坐标介于 -1 和 1 之间,可以通过 转换为介于 0 和 1 之间的数值 。从 HSV 坐标计算 RGB 颜色在 维基百科上的 HSV 文章 中有描述。
关于变化变量的故事还没有结束。如果你选择立方体游戏对象,你将在场景视图中看到它只包含 12 个三角形和 8 个顶点。因此,顶点着色器可能只被调用了八次,并且只有八个不同的输出被写入变化变量。然而,立方体上有更多颜色。这是怎么发生的?
答案隐含在名称 变化 变量中。之所以这样称呼它们,是因为它们在三角形中变化。实际上,顶点着色器只针对每个三角形的每个顶点被调用。如果顶点着色器针对不同顶点将不同值写入变化变量,则这些值会在整个三角形中插值。然后为三角形覆盖的每个像素调用片段着色器,并接收变化变量的插值值。这种插值的详细信息在 “光栅化”部分 中有描述。
如果你想确保片段着色器接收顶点着色器的一个确切的、非插值值,你必须确保顶点着色器对三角形的每个顶点都将相同的值写入变化变量。
本教程到此结束。恭喜你!在其他内容中,你已经了解了
- 什么是 RGB 立方体。
- 变化变量有什么用以及如何定义它们。
- 如何确保变化变量在顶点着色器和片段着色器中具有相同的名称和类型。
- 顶点着色器写入变化变量的值如何在三角形中插值,然后被片段着色器接收。
如果你想了解更多
- 关于进出顶点和片段着色器的数据流,你应该阅读 “OpenGL ES 2.0 管道”部分 中的描述。
- 关于向量和矩阵运算(例如表达式
gl_Vertex + vec4(0.5, 0.5, 0.5, 0.0);
),你应该阅读 “向量和矩阵运算”部分。 - 关于变化变量的插值,你应该阅读 “光栅化”部分。
- 关于 Unity 官方文档中在 Unity 的 ShaderLab 中编写顶点着色器和片段着色器的描述,你应该阅读 Unity 的 ShaderLab 参考关于“GLSL 着色器程序”。