跳转到内容

GLSL 编程/Unity/着色器调试

来自维基教科书,开放世界的开放书籍
一张伪彩色卫星图像。

本教程介绍了属性变量。它基于“最小着色器”部分“RGB 立方体”部分.

本教程还介绍了在 Unity 中调试着色器的主要技术:伪彩色图像,即通过将片段颜色的某个分量设置为某个值来可视化该值。然后,所得图像中该颜色分量的强度允许您对着色器中的值做出推断。这似乎是一种非常原始的调试技术,因为它确实是一种非常原始的调试技术。不幸的是,Unity 中没有其他选择。

顶点数据从哪里来?

[编辑 | 编辑源代码]

“RGB 立方体”部分中,您已经了解了片段着色器如何通过 varying 变量从顶点着色器获取数据。这里的问题是:顶点着色器从哪里获取数据?在 Unity 中,答案是游戏对象的 Mesh Renderer 组件在每一帧都将游戏对象的网格的所有数据发送到 OpenGL。(这通常称为“绘制调用”。请注意,每个绘制调用都有一定的性能开销;因此,将一个大型网格发送到 OpenGL 以执行一次绘制调用比发送多个小型网格以执行多次绘制调用要高效得多。)这些数据通常包含一个长长的三角形列表,其中每个三角形由三个顶点定义,每个顶点都具有某些属性,包括位置。这些属性通过属性变量在顶点着色器中可用。

内置属性变量及其可视化方法

[编辑 | 编辑源代码]

在 Unity 中,大多数标准属性(位置、颜色、表面法线和纹理坐标)都是内置的,也就是说,您不需要(实际上也不应该)定义它们。这些内置属性的名称实际上是由 OpenGL 的“兼容性配置文件”定义的,因为如果您将为固定功能管道编写的 OpenGL 应用程序与(可编程的)顶点着色器混合,则需要这些内置名称。如果您必须定义它们,则定义(仅在顶点着色器中)看起来像这样

   attribute vec4 gl_Vertex; // position (in object coordinates, 
      // i.e. local or model coordinates)
   attribute vec4 gl_Color; // color (usually constant)
   attribute vec3 gl_Normal; // surface normal vector 
      // (in object coordinates; usually normalized to unit length)
   attribute vec4 gl_MultiTexCoord0; //0th set of texture coordinates 
      // (a.k.a. “UV”; between 0 and 1) 
   attribute vec4 gl_MultiTexCoord1; //1st set of texture coordinates 
      // (a.k.a. “UV”; between 0 and 1)
   ...

Unity 提供了一个属性变量,但在 OpenGL 中没有标准名称,即切线向量,它是一个与表面法线正交的向量。您应该将此变量自己定义为类型为 vec4 的属性变量,并使用 Tangent 这个特定名称,如以下着色器所示

Shader "GLSL shader with all built-in attributes" {
   SubShader {
      Pass {
         GLSLPROGRAM

         #ifdef VERTEX

         varying vec4 color;

         attribute vec4 Tangent; // this attribute is specific to Unity 
         
         void main()
         {
            color = gl_MultiTexCoord0; // set the varying variable

            // other possibilities to play with:

            // color = gl_Vertex;
            // color = gl_Color;
            // color = vec4(gl_Normal, 1.0);
            // color = gl_MultiTexCoord0;
            // color = gl_MultiTexCoord1;
            // color = Tangent;
            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
               
         varying vec4 color;

         void main()
         {
            gl_FragColor = color; // set the output fragment color
         }
         
         #endif

         ENDGLSL
      }
   }
}

“RGB 立方体”部分中,我们已经看到了如何通过将片段颜色设置为这些值来可视化 gl_Vertex 坐标。在本例中,片段颜色被设置为 gl_MultiTexCoord0,这样我们就可以看到 Unity 提供了什么样的纹理坐标。

请注意,只有 Tangent 的前三个分量表示切线方向。缩放比例和第四个分量以特定方式设置,这主要对视差贴图有用(参见“凸起表面的投影”部分)。

如何解读伪彩色图像

[编辑 | 编辑源代码]

在尝试理解伪彩色图像中的信息时,重点关注一个颜色分量。例如,如果将球体的标准属性 gl_MultiTexCoord0 写入片段颜色,那么片段的红色分量将可视化 gl_MultiTexCoord0x 坐标,也就是说,输出颜色是最大纯红色还是最大黄色或最大洋红色并不重要,在所有情况下,红色分量都是 1。另一方面,对于红色分量来说,颜色是蓝色还是绿色或青色,或者任何强度也不重要,因为在所有情况下,红色分量都是 0。如果您从未学会只关注一个颜色分量,这可能很具挑战性;因此,您可能需要考虑一次只查看一个颜色分量。例如,通过在顶点着色器中使用以下行设置 varying 变量

            color = vec4(gl_MultiTexCoord0.x, 0.0, 0.0, 1.0);

这将 varying 变量的红色分量设置为 gl_MultiTexCoord0x 分量,但将绿色和蓝色分量设置为 0(以及 alpha 或不透明度分量设置为 1,但在本着色器中无关紧要)。

如果您专注于红色分量,或者仅可视化红色分量,您应该会看到它随着您绕球体移动从 0 增加到 1,并在 360° 后再次下降到 0。它实际上类似于行星表面上的经度坐标。(在球坐标系中,它对应于方位角。)

如果 gl_MultiTexCoord0x 分量对应于经度,那么人们会期望 y 分量对应于纬度(或球坐标系中的倾角)。但是,请注意纹理坐标始终介于 0 和 1 之间;因此,该值在底部(南极)为 0,在顶部(北极)为 1。您可以将 y 分量可视化为它本身的绿色,使用

            color = vec4(0.0, gl_MultiTexCoord0.y, 0.0, 1.0);

纹理坐标特别容易可视化,因为它们介于 0 和 1 之间,就像颜色分量一样。几乎与之一样好的是归一化向量(即长度为 1 的向量;例如,gl_Normal 通常是归一化的)的坐标,因为它们始终介于 -1 和 +1 之间。要将此范围映射到 0 到 1 的范围,您需要在每个分量中添加 1,并将所有分量除以 2,例如

            color = vec4((gl_Normal + vec3(1.0, 1.0, 1.0)) / 2.0, 1.0);

请注意,gl_Normal 是一个三维向量。黑色对应于坐标 -1,一个分量的完全强度对应于坐标 +1。

如果您要可视化的值不在 0 到 1 或 -1 到 +1 的范围内,您需要将其映射到 0 到 1 的范围,这是颜色分量的范围。如果您不知道要期望什么值,您只需进行尝试。这里有帮助的是,如果您指定了超出 0 到 1 范围的颜色分量,它们会自动限制在这个范围内。也就是说,小于 0 的值将被设置为 0,大于 1 的值将被设置为 1。因此,当颜色分量为 0 或 1 时,您至少知道该值小于或大于您假设的值,然后您可以迭代地调整映射,直到颜色分量介于 0 和 1 之间。

调试实践

[编辑 | 编辑源代码]

为了练习着色器的调试,本节包含一些代码行,当顶点着色器中对 color 的赋值被每一行替换时,这些代码行会产生黑色。您的任务是找出每行代码为什么导致结果为黑色。为此,您应该尝试可视化任何您不完全确定的值,并将小于 0 或大于 1 的值映射到其他范围,以便这些值可见,并且您至少知道它们在哪个范围内。请注意,大多数函数和运算符都在“向量和矩阵运算”部分中进行了说明。

            color = gl_MultiTexCoord0 - vec4(1.5, 2.3, 1.1, 0.0);


            color = vec4(gl_MultiTexCoord0.z);


            color = gl_MultiTexCoord0 / tan(0.0);

以下代码行需要一些关于点积和叉积的知识

            color = dot(gl_Normal, vec3(Tangent)) * gl_MultiTexCoord0;


            color = dot(cross(gl_Normal, vec3(Tangent)), gl_Normal) * 
               gl_MultiTexCoord0;


            color = vec4(cross(gl_Normal, gl_Normal), 1.0);


            color = vec4(cross(gl_Normal, vec3(gl_Vertex)), 1.0); 
               // only for a sphere!

radians()noise() 函数总是返回黑色吗?这对什么有用?

            color = radians(gl_MultiTexCoord0);


            color = noise4(gl_MultiTexCoord0);

请参阅“OpenGL ES 着色语言 1.0.17 规范”中的文档(可从“Khronos OpenGL ES API 注册表”获得),以了解 radians() 的用途以及 noise4() 的问题。

片段着色器中的特殊变量

[编辑 | 编辑源代码]

属性是顶点特有的,也就是说,它们通常在不同的顶点具有不同的值。片段着色器也有类似的变量,即对每个片段具有不同值的变量。但是,它们与属性不同,因为它们不是由网格(即三角形列表)指定的。它们也与变体不同,因为它们不是由顶点着色器显式设置的。

具体来说,一个四维向量gl_FragCoord可用,它包含正在处理的片段的屏幕(或窗口)坐标;有关屏幕坐标系的描述,请参见“顶点变换”部分

此外,还提供了一个布尔变量gl_FrontFacing,它指定正在渲染三角形的正面还是背面。正面通常朝向模型的“外部”,背面朝向模型的“内部”;但是,如果模型不是封闭的曲面,则没有明显的外部或内部。通常,表面法向量指向正面方向,但这不是必需的。实际上,正面和背面由顶点三角形的顺序指定:如果顶点以逆时针顺序出现,则正面可见;如果它们以顺时针顺序出现,则背面可见。一个应用显示在“截面”部分

恭喜,您已经完成了本教程!我们已经看到了

  • Unity 中的内置属性列表:gl_Vertexgl_Colorgl_Normalgl_MultiTexCoord0gl_MultiTexCoord1,以及特殊的Tangent
  • 如何通过设置输出片段颜色的分量来可视化这些属性(或任何其他值)。
  • 片段程序中可用的另外两个特殊变量:gl_FragCoordgl_FrontFacing

进一步阅读

[编辑 | 编辑源代码]

如果您仍然想要了解更多


< GLSL 编程/Unity

除非另有说明,否则本页面上的所有示例源代码均归属公共领域。
华夏公益教科书