跳转到内容

GLSL 编程/Blender/着色器调试

来自 Wikibooks,开放世界开放书籍
一张伪彩色卫星图像。

本教程介绍了 **属性变量**。它建立在 关于最小着色器的教程关于变化变量的 RGB 立方体教程 之上。

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

顶点数据从哪里来?

[编辑 | 编辑源代码]

RGB 立方体教程 中,您已经看到了片段着色器如何通过变化变量从顶点着色器获取数据。这里的问题是:顶点着色器从哪里获取数据?在 Blender 中,此数据由 **属性窗口** 中的设置(特别是 **对象数据选项卡**、**材质选项卡** 和 **纹理选项卡** 中的设置)为每个选定对象指定。对象的网格的所有数据都在每一帧发送到 OpenGL。(这通常称为“绘制调用”。请注意,每次绘制调用都有一些性能开销;因此,将一个大型网格发送到 OpenGL 比使用多个绘制调用发送多个较小的网格效率更高。)此数据通常包含一个三角形列表,每个三角形由三个顶点定义,每个顶点都具有某些属性,包括位置。这些属性通过属性变量在顶点着色器中可用。

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

[编辑 | 编辑源代码]

在 Blender 中,大多数标准属性(位置、颜色、表面法线和纹理坐标)都是内置的,即您不需要(事实上也不应该)定义它们。这些内置属性的名称实际上是由 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 
      // (usually normalized; also in object coordinates)
   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)
   ...

只有一个属性变量是由 Blender 提供但没有 OpenGL 中的标准名称,即切线向量,即一个与表面法线正交的向量。您应该将此变量自己定义为类型为 vec4 的属性变量,名称为 tangent,如下面的着色器所示

import bge

cont = bge.logic.getCurrentController()

VertexShader = """
   varying vec4 color;
   attribute vec4 tangent; // this attribute is specific to Blender 
      // and has to be defined explicitly

   void main()
   {
       color = gl_MultiTexCoord0; // set the varying to this attribute
    
       // 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;
   }
"""

FragmentShader = """
   varying vec4 color;

   void main()
   {   
      gl_FragColor = color;
   }
"""


mesh = cont.owner.meshes[0]
for mat in mesh.materials:
    shader = mat.getShader()
    if shader != None:
        if not shader.isValid():
            shader.setSource(VertexShader, FragmentShader, 1)
            shader.setAttrib(bge.logic.SHD_TANGENT)

请注意以下行

shader.setAttrib(bge.logic.SHD_TANGENT)

在 Python 脚本中,它告诉 Blender 为着色器提供切线属性。但是,Blender 仅会在 **属性窗口** 中的某些设置下提供几个属性,特别是应在 **对象数据选项卡** 中指定 **UV 贴图**(只需单击“+”按钮),应在 **材质选项卡** 中定义材质,应在 **纹理选项卡** 中定义纹理(例如,任何图像)。

RGB 立方体教程 中,我们已经看到了如何通过将片段颜色设置为这些值来可视化 gl_Vertex 坐标。在本例中,片段颜色设置为 gl_MultiTexCoord0,以便我们可以看到 Blender 为 **属性窗口** 中的某些设置提供的纹理坐标类型。

如何解释伪彩色图像

[编辑 | 编辑源代码]

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

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

这将变化变量的红色分量设置为 gl_MultiTexCoord0x 分量,但将绿色和蓝色分量设置为 0(以及 alpha 或不透明度分量设置为 1,但这在该着色器中并不重要)。

Blender 发送到顶点着色器的特定纹理坐标取决于 **对象数据选项卡** 中指定的 **UV 贴图** 和 **纹理选项卡** 中指定的 **映射**。

纹理坐标特别容易可视化,因为它们像颜色分量一样都在 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(1.0 - gl_MultiTexCoord0.w);


            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, gl_Vertex), 1.0);

函数 radians() 是否总是返回黑色?它有什么用?

            color = radians(gl_MultiTexCoord0);

请查阅 “Khronos OpenGL ES API 注册表” 中提供的“OpenGL ES 着色语言 1.0.17 规范”中的文档,以了解 radians() 的用途。

片段着色器中的特殊变量

[编辑 | 编辑源代码]

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

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

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

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

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

进一步阅读

[编辑 | 编辑源代码]

如果你还想了解更多


< GLSL 编程/Blender

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