Cg Programming/Unity/Shading in World Space世界空间中的着色器

来源:互联网 发布:java多线程lock接口 编辑:程序博客网 时间:2024/06/04 19:06

本章介绍统一参数。假设你熟悉章节“小型着色器”、“RGB立方体”以及“着色器调试”。
在本章中我们将会查看一个着色器,它会根据世界空间中的位置改变片元颜色。这个概念并不复杂,这里有非常重要的应用,比如光照和环境映照的着色器。我们将会查看真实世界中的着色器;也就是说,让非程序员使用着色器的必要条件是什么?

从物体坐标向世界空间变换

就像在章节“着色器调试”中提到的,带有语义POSITION的顶点输入参数会指定对象坐标,即在一个网格的本地对象(或模型)空间的坐标。对象空间(或对象坐标系)是特定于每个游戏对象的;但是,所有的游戏对象会被变换到一个公共的坐标系—-世界空间。

如果一个游戏对象被直接放入世界空间中,那么对象到世界的变换就由游戏对象的Transform组件来指定。可以在 Scene View或者Hierarchy Window中选择它,然后就会在Inspector Window发现Transform组件。在Transform组件有“Position”、“Rotation”和“Scale”参数,它们会指定顶点如何从对象坐标变换到世界坐标的。(如果一个游戏对象是一组对象的一部l分,它通过缩进显示在Hierarchy Window中,那么Transform组件只是指定了从游戏对象的对象坐标到父对象坐标的变换。在这种情况下,实际的对象世界变换是由对象的变换以及父、爷等对象的变换的组合给出的。)通过平移、旋转和缩放的顶点变换以及变换的组合与它们的4×4矩阵表示,都在章节”顶点变换“中讨论过了。

回到我们的例子:从对象空间到世界空间的变换被放进一个4×4的矩阵中,这也被称为“模型矩阵”(因此这个变换也称为“模型变换”)。这个矩阵在统一参数_Object2World中是可用的,它以这种方式被Unity自动定义:

uniform float4x4 _Object2World;

因为它是自动定义的,我们就不需要定义它了(实际上也不需要)。无需定义我们就能在以下的着色器中使用统一参数_Object2World:

hader "Cg shading in world space" {   SubShader {      Pass {         CGPROGRAM         #pragma vertex vert           #pragma fragment frag          // uniform float4x4 _Object2World;             // Unity指定的统一参数自动定义         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 =  mul(UNITY_MATRIX_MVP, input.vertex);            output.position_in_world_space = mul(_Object2World, input.vertex);               // 顶点从对象坐标向世界坐标的变换            return output;         }         float4 frag(vertexOutput input) : COLOR          {             // 计算片元的位置和原点(对于点来说第4个坐标必须为1)之间的距离             float dist = distance(input.position_in_world_space,                float4(0.0, 0.0, 0.0, 1.0));            if (dist < 5.0)            {               // 靠近原点的颜色               return float4(0.0, 1.0, 0.0, 1.0);                               }            else            {               // 远离原点的颜色               return float4(0.1, 0.1, 0.1, 1.0);             }         }         ENDCG        }   }}

通常,应用必须设置统一参数的值;但是,像_Object2World,Unity照顾到设置预定义统一参数的正确值;因此我们无需担心这个。

该着色器把顶点位置变换到了世界空间,并且在输出结构体中传递给片元着色器。对于片元着色器,输出结构体中的参数包含了世界坐标中片元插值的位置。基于这个位置跟世界坐标系原点的距离,两个颜色中的一个会被设置。因此,如果你在编辑器中来回移动带有这个着色器的物体,它会在世界坐标系的原点附近变成绿色。离原点远的话,它会变成深灰色。

更多的Unity统一参数

这里有一些跟float4x4矩阵_Object2World类似的Unity自定义的内置统一参数。以下是在一些教程中使用到的uniforms(包括_Object2World)的简短清单:

   uniform float4 _Time, _SinTime, _CosTime; // 时间相关的值   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 _Object2World; // 模型矩阵   uniform float4x4 _World2Object; // 模型矩阵的逆矩阵    uniform float4 _WorldSpaceLightPos0; // 前向渲染中光源的位置或方向   uniform float4x4 UNITY_MATRIX_MVP; // 模型视图投影矩阵    uniform float4x4 UNITY_MATRIX_MV; // 模型视图矩阵   uniform float4x4 UNITY_MATRIX_V; // 视图矩阵   uniform float4x4 UNITY_MATRIX_P; // 投影矩阵   uniform float4x4 UNITY_MATRIX_VP; // 视图投影矩阵   uniform float4x4 UNITY_MATRIX_T_MV; // 模型视图矩阵的转置   uniform float4x4 UNITY_MATRIX_IT_MV; // 模型视图逆矩阵的转置   uniform float4 UNITY_LIGHTMODEL_AMBIENT; // 环境颜色

对于Unity内置uniforms的官方清单,可以参考Unity手册中的章节“内置着色器变量”。

有一些uniforms实际上是在文件UnityShaderVariables.cginc中定义的,它在Unity4.0版本后就会被自动包含进去。
还有一些内置的uniforms没有被自动包含进去,比如_LightColor0,它是在Lighting.cginc中定义的。因此,我们必须明确地定义它(如果有必要的话):

 uniform float4 _LightColor0;

或者包含以下定义的文件:

 #include "Lighting.cginc"

Unity并不总是会更新所有的uniforms。特别是,_WorldSpaceLightPos0和_LightColor0只有在着色器通道标记合适时才会被正确设置,比如在 Pass {…}代码块中的第一行Tags {“LightMode” = “ForwardBase”};也可以查阅“漫反射”。

用户自定义参数:着色器属性

统一参数还有个更重要的类型:用户可以自定义的参数。实际上,在Unity中它们被称作着色器属性。你可以认为它们是用户自定义的着色器统一参数。一个没有参数的的着色器通常只会被程序员使用,因为即使最小的必要改动都需要编程。另一方面,使用具有描述性名称的参数的着色器会被其它人使用,即使不是程序员,比如CG美术。设想你在一个游戏开发团队中,一个CG美术要求你为100个设计迭代中的每一个调整你的着色器。很显然提供一些甚至CG美术都能使用的参数,也许会节省你很多时间。或者,设想你想要出售你的着色器:参数会显著增加你着色器的价值。

既然Unity对着色器属性的描述相当不错,那么这里只有一个例子,即如何在例子中使用着色器属性。首先我们要声明属性并且用相同名字和相应类型定义uniforms。

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"             // 定义了_Object2World和_World2Object         // 对应于属性的uniforms         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 =  mul(UNITY_MATRIX_MVP, input.vertex);            output.position_in_world_space =                mul(_Object2World, input.vertex);            return output;         }         float4 frag(vertexOutput input) : COLOR          {            // 计算_Point位置和片元位置之间的距离            float dist = distance(input.position_in_world_space,_Point);                          if (dist < _DistanceNear)            {               return _ColorNear;             }            else            {               return _ColorFar;             }         }         ENDCG        }   }}

利用这些参数,一个非程序员就能够修改我们着色器的效果。这样非常棒;但是着色器(实际上通常是指uniforms)的参数也能被脚本设置!举例来说,一个使用着色器的游戏对象上的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组件。(你也可以写成(效率较低)GetComponent(typeof(Renderer)) as Renderer或者GetComponent(“Renderer”) as Renderer。)使用sharedMaterial如果你想改变所有使用该材质对象的参数,而使用material如果你只想改变一个对象的参数。(但是请注意那个material可能会创建新的material实例,当游戏对象销毁时它并不会自动销毁!)举例来说,你会用脚本把_Point设置为另一个对象的位置(也就是它Transform组件的位置)。在这种方法中,你可以通过移动编辑器中的另一个对象来指定一个点。对了写出这样的脚本,在Project Window中选择Create > C# Script,命名为ShadingInWorldSpace然后拷贝粘贴以下的代码:

using UnityEngine;[ExecuteInEditMode]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);        }    }}

然后,你可以把脚本挂载到使用该着色器的对象上去(通过在对象上拖拉脚本)以及在Inspector Window中拖拉另一个对象到脚本的其它变量上。现在你可以通过改变其它对象的位置来改变_Point变量的值。

总结

恭喜,你完成了本章的学习!我们讨论了:

  • 如何把一个顶点转换成世界坐标。
  • Unity支持的最重要的自定义uniforms 。
  • 如何通过增加着色器属性

深入阅读

如果你想知道得更多
- 关于向量和矩阵方程(比如函数distance()),你可以阅读章节“向量和矩阵操作”。
- 关于标准顶点变换,比如模型矩阵和视图矩阵,你可以阅读章节“顶点变换”。
- 关于到点和方向变换矩阵的应用,你可以阅读章节“应用矩阵变换”。
- 关于Unity内置uniform参数,你可以阅读Unity关于“内置着色器变量”的文档。
- 关于着色器属性的说明,你可以阅读Unity关于“ShaderLab: 属性”的文档

阅读全文
0 0
原创粉丝点击