跳转到内容

GLSL 编程/Blender/视图空间着色

来自 Wikibooks,开放世界开放书籍
一些变色龙能够根据周围的世界改变它们的颜色。

本教程介绍了制服变量。它建立在最小着色器教程RGB 立方体教程着色器调试教程的基础之上。

在本教程中,我们将研究一个着色器,它根据片段在“视图空间”中的位置改变片段颜色,“视图空间”只是世界空间的旋转版本,其中相机位于原点并指向负 轴,而 轴与相机的垂直轴对齐;请参阅“顶点变换”中的描述。这个概念并不复杂;然而,它有极其重要的应用,例如使用灯光和环境贴图进行着色。我们也将看看现实世界中的着色器;例如,如何让非程序员使用你的着色器?

从对象空间变换到视图空间

[编辑 | 编辑源代码]

正如着色器调试教程中提到的,属性gl_Vertex指定了对象坐标,即网格局部对象(或模型)空间中的坐标。对象空间(或对象坐标系)对每个对象都是唯一的;但是,所有对象都变换到一个共同的坐标系中——视图空间。从概念上讲,它们首先被变换到共同的世界空间(通过模型变换),然后变换到视图空间(通过视图变换)。在实践中,这两个变换被组合成一个变换(模型视图变换)。

如果一个对象直接放入世界空间,那么对象到世界的变换由 Blender 中对象的属性中的变换部分指定。要查看它,请在3D 视图中选择该对象,然后打开属性窗口对象选项卡,并展开变换部分。这里有“位置”、“旋转”和“缩放”参数,它们指定了顶点如何从对象坐标变换到世界坐标。顶点通过平移、旋转和缩放的变换,以及变换的组合及其作为 4×4 矩阵的表示,在“顶点变换”中进行了讨论。该页面还讨论了从世界空间到视图空间的变换,它只依赖于相机的 position 和方向(可以通过选择相机并打开属性窗口进行检查;可以通过在3D 视图菜单中选择视图 > 相机来激活它)。

在以下示例中,从对象空间到视图空间的变换被放入一个 4×4 矩阵中,它也被称为“模型视图矩阵”(因为它结合了“模型变换”和“视图变换”)。此矩阵在内置制服变量gl_ModelViewMatrix中可用(类似于我们在前面教程中使用的gl_ModelViewProjectionMatrix)。此制服在以下顶点着色器中使用,该着色器应该分配给最小着色器教程中 Python 脚本的变量VertexShader

         varying vec4 position_in_view_space;

         void main()
         {
            position_in_view_space = gl_ModelViewMatrix * gl_Vertex;
               // transformation of gl_Vertex from object coordinates 
               // to view coordinates;
            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }

片段着色器应该分配给变量FragmentShader

         
         varying vec4 position_in_view_space;

         void main()
         {
            float dist = distance(position_in_view_space, 
               vec4(0.0, 0.0, 0.0, 1.0));
               // computes the distance between the fragment position 
               // and the origin (4th coordinate should always be 1 
               // for points). The origin in view space is actually 
               // the camera position.
            
            if (dist < 5.0)
            {
               gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); 
                  // color near origin
            }
            else
            {
               gl_FragColor = vec4(0.3, 0.3, 0.3, 1.0); 
                  // color far from origin
            }
         }

通常,OpenGL 应用程序必须设置制服变量的值;但是,Blender 负责设置预定义制服变量的值,例如gl_ModelViewMatrixgl_ModelViewProjectionMatrix(实际上是矩阵乘积gl_ProjectionMatrix * gl_ModelViewMatrix);因此,我们不必担心它。(然而,在某些情况下,除非用3D 视图 > 视图 > 相机 > 活动相机激活相机视图,否则 Blender 似乎会错误地设置其中一些矩阵(特别是gl_ModelViewMatrix)。)

此着色器将顶点位置变换到视图空间,并将其以 varying 变量的形式提供给片段着色器。对于片段着色器,varying 变量包含片段在视图坐标中的插值位置。根据此位置到视图坐标系原点的距离,将设置两种颜色之一。因此,如果你使用此着色器移动一个对象,它将在靠近相机的地方变成绿色。离相机越远,它就会变成深灰色。

从视图空间变换到世界空间

[编辑 | 编辑源代码]

着色器中的许多计算可以在视图空间中执行。由于光源的位置是在视图空间中指定的,并且默认情况下没有提供到世界空间的变换,因此这通常是最方便和最有效的坐标系。但是,在某些情况下,需要在世界空间中工作(例如,使用位于世界空间中的环境贴图时)。在这些情况下,需要在制服变量中为着色器提供从视图空间到世界空间的变换。制服变量必须在使用它的任何着色器的 main 函数之外定义

uniform mat4 viewMatrixInverse;

mat4 只是 4×4 矩阵的数据类型,而uniform 表示它对所有顶点和片段的值都相同,它必须在外部设置。实际上,在 Blender 中,它是在 Python 脚本中使用以下行设置的

shader.setUniformMatrix4('viewMatrixInverse', value_of_uniform)

其中shader 是着色器程序的 Python 对象,'viewMatrixInverse' 是着色器中制服变量的名称,而value_of_uniform 是应该设置的值。setUnifomMatrix4 用于设置 4×4 矩阵制服,如Blender 的 Python API中所述。更多函数可用于设置其他类型制服。

这里,我们需要一个 4×4 矩阵,它将点从视图空间(也称为眼睛或相机空间)变换到世界空间,可以通过以下方式获得

value_of_uniform = bge.logic.getCurrentScene().active_camera.camera_to_world

即当前场景提供了一个活动相机,它提供了我们需要的变换。(仅仅为了完整性:相机还提供了world_to_camera变换的矩阵,即视图矩阵本身。)

我们现在可以重写示例,使其在世界空间中工作而不是视图空间,即如果片段在世界空间中靠近原点,则将它们着色为绿色

import bge

cont = bge.logic.getCurrentController()

VertexShader = """
         uniform mat4 viewMatrixInverse; // view to world 
            // transformation (the view matrix corresponds to the 
            // world to view transformation; thus, the inverse 
            // matrix corresponds to the view to world trafo)

         varying vec4 position_in_world_space;

         void main()
         {
            vec4 position_in_view_space = 
               gl_ModelViewMatrix * gl_Vertex;
               // transformation of gl_Vertex from object coordinates 
               // to view coordinates;
            position_in_world_space = viewMatrixInverse * 
               position_in_view_space;
               // transformation of gl_Vertex from view coordinates 
               // to world coordinates;
            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
"""

FragmentShader = """
         varying vec4 position_in_world_space;

         void main()
         {
            float dist = distance(position_in_world_space, 
               vec4(0.0, 0.0, 0.0, 1.0));
               // computes the distance between the fragment position 
               // and the origin (the 4th coordinate should always 
               // be 1 for points). 
            
            if (dist < 5.0)
            {
               gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); 
                  // color near origin
            }
            else
            {
               gl_FragColor = vec4(0.3, 0.3, 0.3, 1.0); 
                  // color far from origin
            }
         }
"""

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)
        value_of_uniform = \
            bge.logic.getCurrentScene().active_camera.camera_to_world
        shader.setUniformMatrix4('viewMatrixInverse', value_of_uniform)

请注意,我们应该在每一帧中设置制服的当前值,而我们只设置一次着色器的源代码。

还要注意,顶点着色器中还有一个额外的变换。这是一个矩阵向量乘法,它包含 16 次乘法,因此会消耗一些性能。如果可能,应该避免这种额外的变换。在实践中,因此我们将在视图空间中执行灯光计算,这样就不必将光源的视图空间位置变换到世界空间。

更多 OpenGL 特定的制服

[编辑 | 编辑源代码]

光源的位置实际上是作为内置制服提供的,类似于gl_ModelViewMatrixgl_ProjectionModelViewMatrix。所有这些内置制服都是为 OpenGL 兼容性配置文件定义的。相应的变换在“顶点变换”中进行了详细描述。

正如你在上面的着色器中看到的,这些制服不必定义;它们始终在 Blender 的 GLSL 着色器中可用。如果你必须定义它们,定义将如下所示

      uniform mat4 gl_ModelViewMatrix;
      uniform mat4 gl_ProjectionMatrix;
      uniform mat4 gl_ModelViewProjectionMatrix;
      uniform mat4 gl_TextureMatrix[gl_MaxTextureCoords];
      uniform mat3 gl_NormalMatrix; 
         // transpose of the inverse of gl_ModelViewMatrix
      uniform mat4 gl_ModelViewMatrixInverse;
      uniform mat4 gl_ProjectionMatrixInverse;
      uniform mat4 gl_ModelViewProjectionMatrixInverse;
      uniform mat4 gl_TextureMatrixInverse[gl_MaxTextureCoords];
      uniform mat4 gl_ModelViewMatrixTranspose;
      uniform mat4 gl_ProjectionMatrixTranspose;
      uniform mat4 gl_ModelViewProjectionMatrixTranspose;
      uniform mat4 gl_TextureMatrixTranspose[gl_MaxTextureCoords];
      uniform mat4 gl_ModelViewMatrixInverseTranspose;
      uniform mat4 gl_ProjectionMatrixInverseTranspose;
      uniform mat4 gl_ModelViewProjectionMatrixInverseTranspose;
      uniform mat4 gl_TextureMatrixInverseTranspose[
         gl_MaxTextureCoords];

      struct gl_MaterialParameters {
         vec4 emission; // = Properties > Material tab > Diffuse Color * 
            // Shading > Emit (alpha = 1)
         vec4 ambient; // = black
         vec4 diffuse; // = black 
            // if Properties > Material tab > Diffuse Color activated; 
            // diffuse = Properties > Object tab > Object Color 
         vec4 specular; // = Properties > Material tab > Specular Color
            // * Intensity (alpha = Intensity!)
         float shininess; 
            // = Properties > Material tab > Specular > Hardness / 4.0
      }; 
      uniform gl_MaterialParameters gl_FrontMaterial; // see above
      uniform gl_MaterialParameters gl_BackMaterial; 
         // same as gl_FrontMaterial

      struct gl_LightSourceParameters {
         vec4 ambient; // = black
         vec4 diffuse; // = Properties > Object Data tab > Color * 
            // Energy (alpha = 1)
         vec4 specular; // = Properties > Object Data tab > Color * 
            // Energy (alpha = 1)
         vec4 position; // in view space, w = 0 for Sun (directional), 
            // w = 1 otherwise 
         vec4 halfVector; // = average of vectors to light and viewer 
         vec3 spotDirection; // in view space
         float spotExponent; // = Properties > Object Data tab > 
            // Spot Shape > Blend * 128.0 
         float spotCutoff; // = Properties > Object Data tab > 
            // Spot Shape > Size / 2.0 (180.0 if not spotlight)
         float spotCosCutoff; // derived: cos(spotCutoff) 
            // (range: [1.0,0.0] or -1.0 if not spotlight)
         float constantAttenuation; // = 1.0
         float linearAttenuation; // = 0.0
         float quadraticAttenuation;// = 0.0
      };
      uniform gl_LightSourceParameters gl_LightSource[gl_MaxLights]; 
         // see above for each light

      struct gl_LightModelParameters 
      { 
         vec4 ambient; // = Properties > World tab > World > Ambient Color
            // * Material tab > Shading > Ambient
      };
      uniform gl_LightModelParameters gl_LightModel; // see above
      ...

实际上,OpenGL 的兼容性配置文件定义了更多(不太有趣的)制服;请参阅可从Khronos 的 OpenGL 页面获得的“OpenGL 着色语言 4.10.6 规范”的第 7 章。显然 Blender 支持其中很多,但并非全部。

其中一些制服是数组,例如gl_TextureMatrix。实际上,一个矩阵数组gl_TextureMatrix[0]gl_TextureMatrix[1]gl_TextureMatrix[2]、...、gl_TextureMatrix[gl_MaxTextureCoords - 1] 是可用的,其中gl_MaxTextureCoords 是一个内置整数。

其他的是结构体,例如gl_LightModel;因此,你必须使用点符号来访问它的成员,例如gl_LightModel.ambient 用于环境场景颜色。

计算模型矩阵

[编辑 | 编辑源代码]

正如我们上面所见,可以使用 Python 脚本访问视图矩阵 和逆视图矩阵 。另一方面,OpenGL 提供了模型矩阵 和视图矩阵 的乘积,即模型视图矩阵 ,它可以在 uniform gl_ModelViewMatrix 中获取。不太容易获取的是模型矩阵

但是,我们可以很容易地计算它。数学公式如下

  

换句话说,模型矩阵是逆视图矩阵和模型视图矩阵的乘积。假设我们已经定义了 uniform viewMatrixInverse,我们可以用 GLSL 中的这种方法来计算模型矩阵

      mat4 modelMatrix = viewMatrixInverse * gl_ModelViewMatrix;

用户指定的 Uniform:游戏属性

[编辑 | 编辑源代码]

Uniform 变量有一个重要的应用:用户可以设置的 uniform。实际上,这些在 Blender 中被称为游戏属性。你可以把它们看作是对象的参数,更准确地说,是它们着色器的参数。没有参数的着色器通常只被它的程序员使用,因为即使是最小的必要更改也需要一些编程。另一方面,使用具有描述性名称的参数的着色器可以被其他人使用,即使是非程序员,例如 CG 艺术家。想象一下,你是一个游戏开发团队,一个 CG 艺术家要求你为 100 次设计迭代中的每一次都调整你的着色器。显而易见,几个参数,即使是 CG 艺术家也可以玩弄它们,可能会为你节省大量的时间。此外,想象一下你想要出售你的着色器:参数通常会极大地提高你的着色器的价值。

有关游戏属性的 Blender 文档 描述了如何设置一个新的游戏属性:选择 Python 脚本附加到的对象。打开逻辑编辑器,从菜单中选择视图 > 属性(或者按n或点击左上角的小图标)。点击添加游戏属性,将名称设置为my_uniform,类型设置为Float。然后,我们可以在着色器中定义一个 uniform(如果我们想自找麻烦,它可以有不同的名称),并使用 Python 函数设置它

shader.setUniform1f('my_uniform', cont.owner.get('my_uniform'))

其中第一个'my_uniform'指的是着色器中的名称,第二个'my_uniform'指的是游戏属性的名称。setUniform1f用于设置一维浮点数 uniform,而其他函数可用于设置其他类型的 uniform,如 Blender 的 Python API 文档 中所述。

一个简单的例子可以使用下面的 Python 脚本,它使用游戏属性的值来设置片段颜色红色分量的强度

import bge

cont = bge.logic.getCurrentController()

VertexShader = """
   void main()
   {
      gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
   }

"""

FragmentShader = """
   uniform float my_uniform;

   void main()
   {   
      gl_FragColor = vec4(my_uniform, 0.0, 0.0, 1.0);
   }
"""

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.setUniform1f('my_uniform', cont.owner.get('my_uniform'))

恭喜你,你成功了!我们讨论了

  • 如何将顶点转换到视图坐标系。
  • 如何将顶点转换到世界坐标系。
  • Blender 支持的最重要的 OpenGL 特定 uniform。
  • 如何通过添加游戏属性来使着色器更有用、更有价值。

进一步阅读

[编辑 | 编辑源代码]

如果你想了解更多信息


< GLSL 编程/Blender

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