
在本教程中,我们将介绍一个着色器,该着色器根据片元在世界中的位置来改变片元的颜色。这个概念并不复杂。但是有非常重要的应用。例如,灯光和环境贴图的阴影。我们还将看一下现实世界中的着色器,即不是程序员能够使用您的着色器需要什么?
从物体空间变换到世界空间
如“Debugging of Shaders”一节中提到的,顶点的输出参数具有POSITION语义指定的是对象坐标,也就是物体自身的空间坐标(也称为模型空间)。对象空间(或者说对象坐标系)是对于每一个游戏对象的。但是所有的游戏对象都变换到一个统一的公共坐标系——世界空间。
如果将游戏对象直接放入世界空间,那么游戏对象的“Transform”组件指的是对象到世界的变换。 要查看它,请在"Scene"窗口或者“Hierarchy”窗口中选择对象,然后在“Inspector”窗口中找到"Transform"组件。在“Transform“组件中有“Position”, “Rotation” 和 “Scale” 参数,用于指定如何将顶点从模型坐标变换到世界坐标。(如果一个游戏对象有父物体,通过缩进显示在”Hierarchy “窗口,那么该物体的”Transform“组件指定的是从该对象的模型坐标到父物体的模型坐标的变换,在这种情况下,实际的模型到世界的变换时通过对象与父物体,祖父母等对象的变换组合而成的)。顶点变换是通过”平移,旋转,缩放“,以及变换的组合而来,他们的表示形式是4x4的矩阵。
回到我们的示例中:从模型空间到世界空间的变换是把他们放到4x4的矩阵中,该矩阵也被称为”模型矩阵“(因此该变换也被称为模型变换)。这个矩阵可直接使用Unity的uniform参数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);
// 将input.vertex坐标从模型坐标系转到世界坐标系
return output;
}
float4 frag(vertexOutput input) : COLOR
{
float dist = distance(input.position_in_world_space,
float4(0.0, 0.0, 0.0, 1.0));
// 计算片元位置和原点的距离,第四分量坐标应该始终为1
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
,我们不必为此担心。
这个shader将顶点坐标变换到世界坐标,并将其作为顶点的输出结构传给片元着色器。对于片元着色器,输出结构中的参数包含了该片元在世界坐标中的插值位置。根据该位置到世界坐标系原点的位置,设置两种颜色之一。因此,如果在编辑器中四处移动带有此着色器的对象,则它将在世界坐标系的原点附近变为绿色。 离原点较远时,它将变为黑色。
更多的Unity自定义的Uniforms
这里有一些由Unity自定义的内置的uniform参数,类似float4x4类型的矩阵unity_ObjectToWorld
。这里是几个教程中使用到的uniforms的简单列表(包括unity_ObjectToWorld
):
uniform float4 _Time, _SinTime, _CosTime; // time values
uniform float4 _ProjectionParams;
// x = 1 or -1 (-1 是投影翻转)
// 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;
// forward rendering的光源位置或方向
uniform float4x4 UNITY_MATRIX_MVP; // model view projection matrix 模型视图投影矩阵
//在一些情况下,UnityObjectToClipPos() 就是使用的这个矩阵
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。UNITY_MATRIX_MV矩阵的转置
uniform float4x4 UNITY_MATRIX_IT_MV;
// transpose of the inverse model view matrix
uniform float4 UNITY_LIGHTMODEL_AMBIENT; // ambient color 环境光照
其中一些uniform实际上是在UnityShaderVariables.cginc
文件中定义的,该文件从4.0版本后都包含在Unity中。
还有一些内置的uniform没有被自动定义,例如在UnityLightingCommon.cginc
中定义的_LightColor0
,我们必须明确定义它(如果需要的话):
uniform float4 _LightColor0;
或者include定义了它的文件:
#include "UnityLightingCommon.cginc"
或者include一个包含了UnityLightingCommon.cginc
文件的文件:
#include "Lighting.cginc"
用户定义的Uniforms:着色器属性
uniform参数还有一种更重要的类型:可由用户设置的uniform。在unity中他们被称为着色器属性。您可以将他们视为着色器的用户指定的uniform参数。没有参数的shader通常仅被程序员使用,因为即使是最小的必要的更改也需要进行一些编程。另一方面,使用带有描述性参数的shader可以由其他人使用,即使他不是程序员,比如CG艺术家。想象您在一个游戏开发团队中,一个CG艺术家要求您为100个设计重复修改您的shader。很明显,那就需要一些CG艺术家能使用的参数,这可以节省您大量的时间。另外,假设您想出售着色器:参数通常会大大增加着色器的价值。
因为Unity对着色器属性的描述文档已经非常好了,因此这里只是一个示例,说明如何在我们的示例中使用着色器属性。我们首先声明属性,然后定义相同名称和相同类型的uniform。
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
}
}
}
使用这些参数,非程序员也能修改我们的shader效果。这很好。也可以通过脚本设置着色器的属性(实际上一般是uniform)。例如,附加到游戏对象的shader可以使用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组件。如果你想更改所有使用此材质对象的参数,请使用sharedMaterial
;如果只想改变该对象的材质参数,请使用material
。(但是请注意,material
可能将会创建新的实例材质,当销毁游戏对象时,该实例材质不会自动销毁!)。您可以使用脚本将_Point设置为另一个对象的位置(即另一个对象的Transform组件的position)。这样您可以通过在编辑器中移动另一个物体来指定一个_Point。为了编写一个这样的脚本,请创建一个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);
}
}
}
然后,你可以将脚本拖拽到游戏对象上。现在您可以通过改变一个对象的位置来更改材质的_Point变量
总结
本教程到此结束。本节我们讨论了:
- 如何将顶点转换为世界坐标
- Unity支持的主要的unity特定的uniform
- 如何通过添加着色器属性使着色器更有用和更有价值。