跳转到内容

Cg 编程/Unity/世界空间中的着色

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

本教程介绍了制服参数。它假设您熟悉“最小着色器”部分“RGB 立方体”部分“着色器调试”部分

在本教程中,我们将研究一个着色器,它根据片段在世界中的位置改变片段颜色。这个概念并不复杂;然而,它有极其重要的应用,例如用灯光和环境贴图进行着色。我们也将看看现实世界中的着色器;也就是说,为了让非程序员使用您的着色器需要做些什么?

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

[编辑 | 编辑源代码]

“着色器调试”部分所述,语义为POSITION的顶点输入参数指定了对象坐标,即网格局部对象(或模型)空间中的坐标。对象空间(或对象坐标系)是特定于每个游戏对象的;然而,所有游戏对象都转换到一个共同的坐标系——世界空间。

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

回到我们的示例:从对象空间到世界空间的转换被放入一个 4×4 矩阵中,该矩阵也被称为“模型矩阵”(因为这种转换也被称为“模型变换”。这个矩阵在制服参数unity_ObjectToWorld中可用,它是由 Unity 以这种方式自动定义的

         uniform float4x4 unity_ObjectToWorld;

由于它是自动定义的,我们不需要定义它(实际上我们不能定义它)。相反,我们可以在以下着色器中使用未定义的制服参数unity_ObjectToWorld

Shader "Cg shading in world space" {
   SubShader {
      Pass {
         CGPROGRAM

         #pragma vertex vert  
         #pragma fragment frag 
 
         // uniform float4x4 unity_ObjectToWorld; 
            // automatic definition of a Unity-specific uniform parameter

         struct vertexInput {
            float4 vertex : POSITION;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 position_in_world_space : TEXCOORD0;
         };

         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output; 
 
            output.pos =  UnityObjectToClipPos(input.vertex);
            output.position_in_world_space = 
               mul(unity_ObjectToWorld, input.vertex);
               // transformation of input.vertex from object 
               // coordinates to world coordinates;
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR 
         {
             float dist = distance(input.position_in_world_space, 
               float4(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)
            {
               return float4(0.0, 1.0, 0.0, 1.0); 
                  // color near origin
            }
            else
            {
               return float4(0.1, 0.1, 0.1, 1.0); 
                  // color far from origin
            }
         }
 
         ENDCG  
      }
   }
}

通常,应用程序必须设置制服参数的值;然而,Unity 负责始终设置诸如unity_ObjectToWorld之类的预定义制服参数的正确值;因此,我们不必担心它。

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

更多 Unity 特定的制服

[编辑 | 编辑源代码]

有一些内置制服参数,它们是由 Unity 自动定义的,类似于float4x4矩阵unity_ObjectToWorld。以下列出了几个在几个教程中使用的制服(包括unity_ObjectToWorld

   uniform float4 _Time, _SinTime, _CosTime; // time values
   uniform float4 _ProjectionParams;
      // x = 1 or -1 (-1 if projection is flipped)
      // y = near plane; z = far plane; w = 1/far plane
   uniform float4 _ScreenParams; 
      // x = width; y = height; z = 1 + 1/width; w = 1 + 1/height
   uniform float3 _WorldSpaceCameraPos;
   uniform float4x4 unity_ObjectToWorld; // model matrix
   uniform float4x4 unity_WorldToObject; // inverse model matrix 
   uniform float4 _WorldSpaceLightPos0; 
      // position or direction of light source for forward rendering

   uniform float4x4 UNITY_MATRIX_MVP; // model view projection matrix 
      // in some cases, UnityObjectToClipPos() just uses this matrix 
   uniform float4x4 UNITY_MATRIX_MV; // model view matrix
   uniform float4x4 UNITY_MATRIX_V; // view matrix
   uniform float4x4 UNITY_MATRIX_P; // projection matrix
   uniform float4x4 UNITY_MATRIX_VP; // view projection matrix
   uniform float4x4 UNITY_MATRIX_T_MV; 
      // transpose of model view matrix
   uniform float4x4 UNITY_MATRIX_IT_MV; 
      // transpose of the inverse model view matrix
   uniform float4 UNITY_LIGHTMODEL_AMBIENT; // ambient color

有关 Unity 内置制服的官方列表,请参阅 Unity 手册中的“内置着色器变量”部分。

其中一些制服实际上是在文件UnityShaderVariables.cginc中定义的,该文件从 Unity 的 4.0 版本开始被自动包含。

还有一些内置制服没有被自动定义,例如_LightColor0,它是在UnityLightingCommon.cginc中定义的。因此,我们必须要么显式地定义它(如果需要)

      uniform float4 _LightColor0;

要么包含具有其定义的文件

      #include "UnityLightingCommon.cginc"

要么包含包含UnityLightingCommon.cginc的文件,例如

      #include "Lighting.cginc"

Unity 并不总是更新所有这些制服。特别是,_WorldSpaceLightPos0_LightColor0仅针对标记适当的着色器通道正确设置,例如,使用Tags {"LightMode" = "ForwardBase"}作为Pass {...} 块中的第一行;另请参阅“漫反射”部分

用户指定的制服:着色器属性

[编辑 | 编辑源代码]

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

由于Unity 对着色器属性的描述非常好,这里只举一个如何在我们的示例中使用着色器属性的例子。我们首先声明属性,然后定义相同名称和对应类型的制服。

Shader "Cg 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 {
         CGPROGRAM

         #pragma vertex vert  
         #pragma fragment frag 
 
         #include "UnityCG.cginc" 
            // defines unity_ObjectToWorld and unity_WorldToObject

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

         struct vertexInput {
            float4 vertex : POSITION;
         };
         struct vertexOutput {
            float4 pos : SV_POSITION;
            float4 position_in_world_space : TEXCOORD0;
         };

         vertexOutput vert(vertexInput input) 
         {
            vertexOutput output; 
 
            output.pos =  UnityObjectToClipPos(input.vertex);
            output.position_in_world_space = 
               mul(unity_ObjectToWorld, input.vertex);
            return output;
         }
 
         float4 frag(vertexOutput input) : COLOR 
         {
             float dist = distance(input.position_in_world_space, 
               _Point);
               // computes the distance between the fragment position 
               // and the position _Point.
            
            if (dist < _DistanceNear)
            {
               return _ColorNear; 
            }
            else
            {
               return _ColorFar; 
            }
         }
 
         ENDCG  
      }
   }
}

使用这些参数,非程序员可以修改我们着色器的效果。这很好;但是,着色器的属性(实际上,制服本身)也可以由脚本设置!例如,附加到使用着色器的游戏对象的 C# 脚本可以使用以下几行代码设置属性

GetComponent<Renderer>().sharedMaterial.SetVector("_Point", new Vector4(1.0f, 0.0f, 0.0f, 1.0f));
GetComponent<Renderer>().sharedMaterial.SetFloat("_DistanceNear", 10.0f);
GetComponent<Renderer>().sharedMaterial.SetColor("_ColorNear", new Color(1.0f, 0.0f, 0.0f));
GetComponent<Renderer>().sharedMaterial.SetColor("_ColorFar", new Color(1.0f, 1.0f, 1.0f));

GetComponent<Renderer>() 返回Renderer组件。(您也可以编写(效率较低)GetComponent(typeof(Renderer)) as Renderer;GetComponent("Renderer") as Renderer。)如果您想更改使用此材质的所有对象的参数,请使用sharedMaterial;如果您只想更改一个对象的参数,请使用material。(但请注意,material可能会创建一个新的材质实例,该实例不会在游戏对象被销毁时自动销毁!)通过脚本,您可以,例如,将_Point设置为另一个对象的位置(即其 Transform 组件的position)。通过这种方式,您可以通过在编辑器中移动另一个对象来指定一个点。为了编写这样的脚本,在项目窗口中选择+/创建 > C# 脚本,并将其命名为ShadingInWorldSpace,然后复制并粘贴此代码

using UnityEngine;

[ExecuteInEditMode, RequireComponent(typeof(Renderer))]
public class ShadingInWorldSpace : MonoBehaviour {
	public GameObject other;
	Renderer rend;
	void Start() {
		rend = GetComponent<Renderer>();
	}

	// Update is called once per frame
	void Update () {
		if(other != null) {
			rend.sharedMaterial.SetVector("_Point", other.transform.position);
		}
	}
}

然后,您应该将脚本附加到具有着色器的对象(例如,通过将脚本拖放到对象上),并将另一个对象拖放到脚本的检查器窗口中的other变量上。现在,您可以通过更改另一个对象的位置来更改材质的_Point变量的值。

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

  • 如何将顶点转换为世界坐标。
  • Unity 支持的最重要的 Unity 特定的制服。
  • 如何通过添加着色器属性使着色器更加有用和有价值。

进一步阅读

[编辑 | 编辑源代码]

如果您想了解更多

< Cg 编程/Unity

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