Cg 编程/Unity/世界空间中的着色
本教程介绍了制服参数。它假设您熟悉“最小着色器”部分、“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 自动定义的,类似于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 特定的制服。
- 如何通过添加着色器属性使着色器更加有用和有价值。
如果您想了解更多
- 关于向量和矩阵运算(例如
distance()
函数),您应该阅读“向量和矩阵运算”部分。 - 关于标准顶点变换,例如模型矩阵和视图矩阵,您应该阅读“顶点变换”部分。
- 关于变换矩阵在点和方向上的应用,您应该阅读“应用矩阵变换”部分。
- 关于 Unity 的内置统一参数,您应该阅读 Unity 的文档关于“内置着色器变量”。
- 关于着色器属性的规范,您应该阅读 Unity 的文档关于“ShaderLab:属性”。