GLSL编程/Unity/世界空间着色
本教程介绍了**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_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"}
;另请参阅“漫反射”部分。
另一类内置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_WorldToObject
和unity_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 的统一变量。
- 如何通过添加着色器属性使着色器更有用和更有价值。
如果你想了解更多
- 关于向量和矩阵运算(例如
distance()
函数),您应该阅读“向量和矩阵运算”部分。 - 关于标准顶点变换,例如模型矩阵和视图矩阵,您应该阅读“顶点变换”部分。
- 关于将变换矩阵应用于点和方向,您应该阅读“应用矩阵变换”部分。
- 关于着色器属性的规范,您应该阅读 Unity 关于“ShaderLab 语法:属性”的文档。