跳转到内容

GLSL 编程/Unity/切面

来自维基教科书,开放世界开放书籍
菲利波·布鲁内莱斯基在 1414-36 年绘制的佛罗伦萨大教堂圆顶剖面图。

本教程涵盖了丢弃片段、确定渲染的是正面还是背面,以及正面和背面的剔除。本教程假设您熟悉在“RGB 立方体”部分中讨论的变化变量。

本教程的主题是切断三角形或片段,即使它们是正在渲染的网格的一部分。主要有两个原因:我们想透过三角形或片段(就像左侧图中的屋顶,它只是部分被切断)或者我们知道三角形不可见;因此,我们可以通过不处理它来节省一些性能。OpenGL 以多种方式支持这些情况;我们将讨论其中的两种。

非常便宜的切面

[编辑 | 编辑源代码]

以下着色器是一种非常便宜的方式,可以切断网格的某些部分:所有在对象坐标中具有正坐标的片段都会被切断(即在它被建模的坐标系中;有关坐标系的详细信息,请参阅“顶点变换”部分)。以下是代码

Shader "GLSL shader using discard" {
   SubShader {
      Pass {
         Cull Off // turn off triangle culling, alternatives are:
         // Cull Back (or nothing): cull only back faces 
         // Cull Front : cull only front faces
         
         GLSLPROGRAM
               
         varying vec4 position_in_object_coordinates;
 
         #ifdef VERTEX         
         
         void main()
         {
            position_in_object_coordinates= gl_Vertex;
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         void main()
         {
            if (position_in_object_coordinates.y > 0.0) 
            {
               discard; // drop the fragment if y coordinate > 0
            }
            if (gl_FrontFacing) // are we looking at a front face?
            {
               gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); // yes: green
            }
            else
            {
               gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); // no: red
            }
         }
         
         #endif

         ENDGLSL
      }
   }
}

当您将此着色器应用于任何默认对象时,着色器将切断它们的一半。这是一种非常便宜的制作半球或开放圆柱体的方法。

丢弃片段

[编辑 | 编辑源代码]

让我们首先关注片段着色器中的discard指令。此指令基本上只是丢弃已处理的片段。(这在早期的着色语言中被称为片段“kill”;我可以理解片段更喜欢“discard”一词。)根据硬件的不同,这可能是一种相当昂贵的技术,因为渲染的性能可能会显著下降,只要有一个着色器包含discard指令(无论实际丢弃了多少片段,仅仅指令的存在就可能导致一些重要优化的停用)。因此,您应该尽可能避免使用此指令,特别是在遇到性能问题时。

还有一点需要注意:片段discard的条件只包含对象坐标。结果是,您可以以任何方式旋转和移动对象,切断部分将始终与对象一起旋转和移动。您可能想看看在世界空间中进行切割的效果:修改顶点和片段着色器,以便使用世界坐标在片段discard的条件中。提示:有关如何将顶点变换到世界空间,请参阅“在世界空间中进行着色”部分

更好的切面

[编辑 | 编辑源代码]

如果您不熟悉 Unity 中的脚本编写,您可以尝试以下想法来改进着色器:修改着色器,以便如果坐标大于某个阈值变量,则丢弃片段。然后引入一个着色器属性,允许用户控制此阈值。提示:有关着色器属性的讨论,请参阅“在世界空间中进行着色”部分

如果您熟悉 Unity 中的脚本编写,可以尝试以下想法:为一个对象编写一个脚本,该脚本将引用另一个球体对象,并将该球体对象的逆模型矩阵(renderer.worldToLocalMatrix)分配(使用renderer.sharedMaterial.SetMatrix())到着色器的mat4统一变量。在着色器中,计算片段在世界坐标系中的位置,并将另一个球体对象的逆模型矩阵应用于片段位置。现在您有了片段在另一个球体对象局部坐标系中的位置;在这里,很容易测试片段是否在球体内部,因为在这个坐标系中,所有球体都以半径 0.5 为中心,围绕原点。如果片段在另一个球体对象内部,则丢弃该片段。生成的脚本和着色器可以帮助您切断任何对象的表面上的点,并借助一个切割球体,该球体可以在编辑器中像任何其他球体一样进行交互式操作。

区分正面和背面

[编辑 | 编辑源代码]

片段着色器中有一个特殊的布尔变量gl_FrontFacing,它指定我们是否正在查看三角形的正面。通常,正面朝向网格的外部,背面朝向内部。(就像表面法线向量通常指向外部。)但是,区分正面和背面的实际方式是三角形中顶点的顺序:如果相机以逆时针顺序看到三角形的顶点,则它看到的是正面。如果它以顺时针顺序看到顶点,则它看到的是背面。

我们的片段着色器检查变量gl_FrontFacing,如果gl_FrontFacingtrue(即片段是正面三角形的一部分;即它面向外部),则将绿色分配给输出片段颜色;如果gl_FrontFacingfalse(即片段是背面三角形的一部分;即它面向内部),则将红色分配给输出片段颜色。事实上,gl_FrontFacing不仅允许您使用不同的颜色渲染表面的两个面,还可以使用完全不同的样式渲染。

请注意,根据三角形中顶点的顺序来定义正面和背面可能会在顶点被镜像时(即用负因子缩放)导致问题。Unity 试图解决这些问题;因此,只需在游戏对象的 Transform 组件中指定负缩放通常不会导致此问题。但是,由于 Unity 无法控制我们在顶点着色器中做什么,我们仍然可以通过将一个(或三个)坐标乘以 -1 来反转内部,例如,通过在顶点着色器中以这种方式分配gl_Position

            gl_Position = gl_ModelViewProjectionMatrix 
            * vec4(-gl_Vertex.x, gl_Vertex.y, gl_Vertex.z, 1.0);

这只是将坐标乘以 -1。对于球体,您可能认为什么都不会发生,但实际上它会将正面变成背面,反之亦然;因此,现在内部是绿色,外部是红色。(顺便说一下,这个问题也会影响表面法线向量。)因此,要小心镜子!

正面或背面的剔除

[编辑 | 编辑源代码]

最后,着色器(更准确地说,着色器通道)包含代码行Cull Off。此行必须出现在GLSLPROGRAM之前,因为它不是 GLSL 代码。实际上,它是Unity ShaderLab 的指令,用于关闭任何三角形剔除。这很有必要,因为默认情况下会剔除背面,就像指定了代码行Cull Back一样。您还可以使用Cull Front指定正面剔除。背面剔除默认激活的原因是,对象的内部通常是不可见的;因此,背面剔除可以通过避免栅格化这些三角形来节省性能,如下所述。当然,我们能够看到内部是因为我们丢弃了一些片段;因此,我们必须禁用背面剔除。

剔除是如何工作的呢?三角形和顶点按常规处理。但是,在将顶点从视口变换到屏幕坐标之后(参见“顶点变换”部分),图形处理器会确定三角形的顶点是在屏幕上以逆时针顺序还是顺时针顺序出现。根据此测试,每个三角形都被视为正面三角形或背面三角形。如果它是正面三角形,并且对正面三角形启用了剔除,它将被丢弃,即,它的处理停止并且不会被栅格化。类似地,如果它是背面三角形,并且对背面三角形启用了剔除。否则,三角形将按常规处理。

总结

[edit | edit source]

恭喜你完成了另一个教程。(如果你尝试过其中一个作业:做得很好!我还没尝试过。)我们已经了解了

  • 如何丢弃片段。
  • 如何在不同的颜色中渲染正面和背面三角形。
  • 如何禁用背面剔除的默认设置。
  • 如何启用正面剔除。

进一步阅读

[edit | edit source]

如果你还想了解更多


< GLSL 编程/Unity

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