跳转到内容

GLSL编程/Unity/世界空间着色

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

本教程介绍了**uniform变量**。它基于“最小着色器”部分“RGB立方体”部分“着色器调试”部分

在本教程中,我们将了解一个根据片段在世界中的位置改变片段颜色的着色器。这个概念并不复杂;然而,它有着极其重要的应用,例如使用灯光和环境贴图进行着色。我们还将了解现实世界中的着色器;即,如何让非程序员能够使用你的着色器?

从对象空间变换到世界空间

[编辑 | 编辑源代码]

“着色器调试”部分所述,属性gl_Vertex指定了对象坐标,即网格局部对象(或模型)空间中的坐标。对象空间(或对象坐标系)对每个游戏对象都是特定的;但是,所有游戏对象都变换到一个共同的坐标系——世界空间。

如果一个游戏对象直接放置到世界空间中,则对象到世界的变换由游戏对象的Transform组件指定。要查看它,请在**场景视图**或**层次结构视图**中选择对象,然后在**检查器视图**中找到Transform组件。Transform组件中包含“位置”、“旋转”和“缩放”参数,它们指定了顶点如何从对象坐标变换到世界坐标。(如果一个游戏对象是某个对象组的一部分,在层次结构视图中通过缩进显示,则Transform组件仅指定从游戏对象的对象坐标到父对象的对象坐标的变换。在这种情况下,实际的对象到世界变换由对象变换与其父级、祖父母等的变换组合给出。)顶点通过平移、旋转和缩放的变换,以及变换的组合及其作为4×4矩阵的表示,在“顶点变换”部分中讨论。

回到我们的示例:从对象空间到世界空间的变换被放入一个4×4矩阵中,该矩阵也被称为“模型矩阵”(因为该变换也被称为“模型变换”)。该矩阵在uniform变量unity_ObjectToWorld中可用(在Unity 5中,在旧版本中可能是_Object2World),它在以下着色器中定义和使用

Shader "GLSL shading in world space" {
   SubShader {
      Pass {
         GLSLPROGRAM

         uniform mat4 unity_ObjectToWorld; 
            // definition of a Unity-specific uniform variable 

         

         #ifdef VERTEX
         
         varying vec4 position_in_world_space;

         void main()
         {
            position_in_world_space = unity_ObjectToWorld * gl_Vertex;
               // transformation of gl_Vertex from object coordinates 
               // to world coordinates;
            
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         }
         
         #endif

         #ifdef FRAGMENT
         
         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
            }
         }
         
         #endif

         ENDGLSL
      }
   }
}

请注意,此着色器确保uniform的定义包含在顶点着色器和片段着色器中(尽管此特定的片段着色器不需要它)。这类似于“RGB立方体”部分中讨论的varying变量的定义。

通常,OpenGL应用程序必须设置uniform变量的值;但是,Unity负责始终设置预定义uniform变量(如unity_ObjectToWorld)的正确值;因此,我们不必担心它。

此着色器将顶点位置变换到世界空间,并将其作为varying传递给片段着色器。对于片段着色器,varying变量包含世界坐标中片段的插值位置。根据此位置到世界坐标系原点的距离,设置两种颜色之一。因此,如果你在编辑器中移动带有此着色器的对象,它将在世界坐标系原点附近变成绿色。远离原点,它将变成深灰色。

更多Unity特定的uniform变量

[编辑 | 编辑源代码]

事实上,有几个类似于unity_ObjectToWorld的预定义uniform变量。以下是一个简短的列表(包括unity_ObjectToWorld),它出现在几个教程的着色器代码中

      // The following built-in uniforms (except _LightColor0 and 
      // _LightMatrix0) are also defined in "UnityCG.glslinc", 
      // i.e. one could #include "UnityCG.glslinc" 
      uniform vec4 _Time, _SinTime, _CosTime; // time values from Unity
      uniform vec4 _ProjectionParams;
         // x = 1 or -1 (-1 if projection is flipped)
         // y = near plane; z = far plane; w = 1/far plane
      uniform vec4 _ScreenParams; 
         // x = width; y = height; z = 1 + 1/width; w = 1 + 1/height
      uniform vec4 unity_Scale; // w = 1/scale; see _World2Object
      uniform vec3 _WorldSpaceCameraPos;
      uniform mat4 unity_ObjectToWorld; // model matrix
      uniform mat4 unity_WorldToObject; // inverse model matrix 
         // (all but the bottom-right element have to be scaled 
         // with unity_Scale.w if scaling is important) 
      uniform vec4 _LightPositionRange; // xyz = pos, w = 1/range
      uniform vec4 _WorldSpaceLightPos0; 
         // position or direction of light source
      uniform vec4 _LightColor0; // color of light source 
      uniform mat4 _LightMatrix0; // matrix to light space

如注释所示,除了_LightColor0_LightMatrix0之外,你还可以包含文件UnityCG.glslinc来代替定义所有这些uniform变量。但是,由于某种未知的原因,_LightColor0_LightMatrix0没有包含在这个文件中;因此,我们必须单独定义它们

      #include "UnityCG.glslinc"
      uniform vec4 _LightColor0; 
      uniform mat4 _LightMatrix0;

Unity并不总是更新所有这些uniform变量。特别是,_WorldSpaceLightPos0_LightColor0_LightMatrix0仅针对适当标记的着色器传递正确设置,例如在Pass {...} 块的第一行使用Tags {"LightMode" = "ForwardBase"};另请参阅“漫反射”部分

更多OpenGL特定的uniform变量

[编辑 | 编辑源代码]

另一类内置uniform变量是为OpenGL兼容性配置文件定义的,例如mat4矩阵gl_ModelViewProjectionMatrix,它等效于其他两个内置uniform变量的矩阵乘积gl_ProjectionMatrix * gl_ModelViewMatrix。相应的变换在“顶点变换”部分中详细描述。

如你在上面的着色器中看到的,这些uniform变量不必定义;它们在Unity中的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_LightModelParameters { vec4 ambient; };
      uniform gl_LightModelParameters gl_LightModel;
      ...

事实上,OpenGL的兼容性配置文件定义了更多uniform变量;请参阅可在Khronos的OpenGL页面上获得的“OpenGL着色语言4.10.6规范”的第7章。Unity支持其中许多,但并非全部。

其中一些uniform变量是数组,例如gl_TextureMatrix。实际上,可以使用一个矩阵数组gl_TextureMatrix[0]gl_TextureMatrix[1]、…、gl_TextureMatrix[gl_MaxTextureCoords - 1],其中gl_MaxTextureCoords是一个内置整数。

计算视图矩阵

[编辑 | 编辑源代码]

传统上,习惯于在视空间中进行许多计算,视空间只是世界空间的旋转和平移版本(有关详细信息,请参见“顶点变换”部分)。因此,OpenGL 只提供模型矩阵和视图矩阵的乘积,即模型视图矩阵,它在统一变量gl_ModelViewMatrix中可用。视图矩阵不可用。Unity 也不提供它。

但是,unity_ObjectToWorld只是模型矩阵,而unity_WorldToObject是模型矩阵的逆矩阵。(除了右下角元素之外,所有元素都必须按untiy_Scale.w缩放。)因此,我们可以很容易地计算出视图矩阵。数学表达式如下所示

  

换句话说,视图矩阵是模型视图矩阵和模型矩阵的逆矩阵的乘积(除了右下角元素为 1 外,它是unity_WorldToObject * unity_Scale.w)。假设我们已经定义了统一变量unity_WorldToObjectunity_Scale,我们可以在 GLSL 中以这种方式计算视图矩阵

      mat4 modelMatrixInverse = _World2Object * unity_Scale.w;
      modelMatrixInverse[3][3] = 1.0; 
      mat4 viewMatrix = gl_ModelViewMatrix * modelMatrixInverse;

用户指定的统一变量:着色器属性

[编辑 | 编辑源代码]

还有一种重要的统一变量类型:用户可以设置的统一变量。实际上,在 Unity 中这些被称为着色器属性。您可以将它们视为着色器的参数。没有参数的着色器通常只由其程序员使用,因为即使是最小的必要更改也需要一些编程。另一方面,使用具有描述性名称的参数的着色器可以被其他人使用,即使是非程序员,例如 CG 艺术家。假设您在一个游戏开发团队中,并且一位 CG 艺术家要求您为其 100 次设计迭代中的每一次都调整您的着色器。显而易见的是,一些参数(即使 CG 艺术家也可以使用)可以为您节省大量时间。此外,假设您想出售您的着色器:参数通常会极大地提高着色器的价值。

由于 Unity 的 ShaderLab 参考中对着色器属性的描述已经相当不错,这里只举一个如何在我们的示例中使用着色器属性的例子。我们首先声明属性,然后定义相同名称和相应类型的统一变量。

Shader "GLSL shading in world space" {
   Properties {
      _Point ("a point in world space", Vector) = (0., 0., 0., 1.0)
      _DistanceNear ("threshold distance", Float) = 5.0
      _ColorNear ("color near to point", Color) = (0.0, 1.0, 0.0, 1.0)
      _ColorFar ("color far from point", Color) = (0.3, 0.3, 0.3, 1.0)
   }
   
   SubShader {
      Pass {
         GLSLPROGRAM

         // uniforms corresponding to properties
         uniform vec4 _Point;
         uniform float _DistanceNear;
         uniform vec4 _ColorNear;
         uniform vec4 _ColorFar;

         #include "UnityCG.glslinc" 
            // defines _Object2World and _World2Object
         
         varying vec4 position_in_world_space;

         #ifdef VERTEX
         
         void main()
         {
            mat4 modelMatrix = _Object2World;

            position_in_world_space = modelMatrix * gl_Vertex;
                        
            gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; 
         }
         
         #endif

         #ifdef FRAGMENT
                  
         void main()
         {
            float dist= distance(position_in_world_space, _Point);
            
            if (dist < _DistanceNear)
            {
               gl_FragColor = _ColorNear;
            }
            else
            {
               gl_FragColor = _ColorFar;
            }
         }
         
         #endif

         ENDGLSL
      }
   }
}

使用这些参数,非程序员可以修改我们着色器的效果。这很好;但是,着色器的属性(实际上,统一变量通常)也可以通过脚本设置!例如,附加到正在使用着色器的游戏对象的 JavaScript 可以使用以下几行代码设置属性

   renderer.sharedMaterial.SetVector("_Point", 
      Vector4(1.0, 0.0, 0.0, 1.0));
   renderer.sharedMaterial.SetFloat("_DistanceNear", 
      10.0);
   renderer.sharedMaterial.SetColor("_ColorNear", 
      Color(1.0, 0.0, 0.0));
   renderer.sharedMaterial.SetColor("_ColorFar", 
      Color(1.0, 1.0, 1.0));

如果您想更改使用此材质的所有对象的参数,请使用sharedMaterial;如果您只想更改一个对象的参数,请使用material。通过脚本,您可以例如将_Point设置为另一个对象的位置(即其 Transform 组件的position)。通过这种方式,您可以只通过在编辑器中移动另一个对象来指定一个点。为了编写这样的脚本,请在**项目视图**中选择**创建 > JavaScript**,然后复制并粘贴此代码

@script ExecuteInEditMode() // make sure to run in edit mode

var other : GameObject; // another user-specified object

function Update () // this function is called for every frame
{
   if (null != other) // has the user specified an object?
   {
      renderer.sharedMaterial.SetVector("_Point", 
         other.transform.position); // set the shader property 
         // _Point to the position of the other object
   }
}

然后,您应该将脚本附加到具有着色器的对象,并在**检查器视图**中将另一个对象拖放到脚本的other变量中。

恭喜你,你做到了!(如果你想知道:是的,我在这里也在自言自语。;))我们讨论了

  • 如何将顶点变换到世界坐标。
  • Unity 支持的最重要的特定于 Unity 的统一变量。
  • Unity 支持的最重要的特定于 OpenGL 的统一变量。
  • 如何通过添加着色器属性使着色器更有用和更有价值。

进一步阅读

[编辑 | 编辑源代码]

如果你想了解更多


< GLSL 编程/Unity

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