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:屬性”。