Cg 程式設計/Unity/貝塞爾曲線

本教程討論了在 Unity 中渲染二次貝塞爾曲線和樣條的一種方法。這是幾個教程中的第一個,演示了 Unity 有用的功能,這些功能與著色器沒有直接關聯;因此,不需要著色器程式設計,所有展示的程式碼都在 C# 中。
平滑曲線在計算機圖形學中有很多應用,特別是在動畫和建模中。在 3D 遊戲引擎中,通常會使用管狀網格,並透過頂點混合對其進行變形,如 “非線性變形”部分 中所述,而不是渲染曲線;然而,渲染曲線而不是變形網格可以提供顯著的效能優勢。

最簡單的貝塞爾曲線是線性貝塞爾曲線 ,對於 從 0 到 1,在兩點 和 之間,它恰好與這兩點之間的線性插值相同
你可能很時髦,稱 和 為 1 次伯恩斯坦基多項式,但實際上它只是線性插值。

更有趣的貝塞爾曲線是二次貝塞爾曲線 ,對於 從 0 到 1,在兩點 和 之間,但受中間的第三點 的影響。定義是
這定義了一條平滑曲線 ,其中 ,從位置 開始(當 )朝向 ,然後彎曲到 (並在 時到達那裡)。
在實踐中,人們通常會對從 0 到 1 的區間進行足夠多的點取樣,例如 ,然後在這些取樣點之間繪製直線。
曲線指令碼
[edit | edit source]要在 Unity 中實現這種曲線,我們可以使用 Unity 元件 LineRenderer。除了設定一些引數外,還應該使用函式 SetVertexCount 設定曲線上取樣點的數量。然後,需要計算取樣點並使用函式 SetPosition 設定它們。這可以透過以下方式實現:
float t;
Vector3 position;
for(int i = 0; i < numberOfPoints; i++)
{
t = i / (numberOfPoints - 1.0f);
position = (1.0f - t) * (1.0f - t) * p0 + 2.0f * (1.0f - t) * t * p1 + t * t * p2;
lineRenderer.SetPosition(i, position);
}
在這裡,我們使用從 0 到 numberOfPoints-1 的索引 i 來計數取樣點。從該索引 i 計算出從 0 到 1 的引數 t。下一行計算 ,然後使用函式 SetPosition 設定它。
程式碼的其餘部分只是設定 LineRenderer 元件並定義可用於定義控制點和曲線的某些渲染功能的公共變數。
using UnityEngine;
[ExecuteInEditMode, RequireComponent(typeof(LineRenderer))]
public class Bezier_Curve : MonoBehaviour
{
public GameObject start, middle, end;
public Color color = Color.white;
public float width = 0.2f;
public int numberOfPoints = 20;
LineRenderer lineRenderer;
void Start ()
{
lineRenderer = GetComponent<LineRenderer>();
lineRenderer.useWorldSpace = true;
lineRenderer.material = new Material(Shader.Find("Legacy Shaders/Particles/Additive"));
}
void Update ()
{
if( lineRenderer == null || start == null || middle == null || end == null)
{
return; // no points specified
}
// update line renderer
lineRenderer.startColor = color;
lineRenderer.endColor = color;
lineRenderer.startWidth = width;
lineRenderer.endWidth = width;
if (numberOfPoints > 0)
{
lineRenderer.positionCount = numberOfPoints;
}
// set points of quadratic Bezier curve
Vector3 p0 = start.transform.position;
Vector3 p1 = middle.transform.position;
Vector3 p2 = end.transform.position;
float t;
Vector3 position;
for(int i = 0; i < numberOfPoints; i++)
{
t = i / (numberOfPoints - 1.0f);
position = (1.0f - t) * (1.0f - t) * p0
+ 2.0f * (1.0f - t) * t * p1 + t * t * p2;
lineRenderer.SetPosition(i, position);
}
}
}
要使用此指令碼,請在 **專案視窗** 中 **建立** 一個 **C# 指令碼** 並將其命名為 **Bezier_Curve**,雙擊它,複製並貼上上面的程式碼,儲存它,建立一個新的空遊戲物件(在主選單中:**遊戲物件>建立空**),然後將指令碼附加到它(將指令碼從 **專案視窗** 拖動到 **層次結構視窗** 中的空遊戲物件上)。
然後建立另外三個空遊戲物件(或任何其他遊戲物件),這些遊戲物件將具有不同的位置,用作控制點。選擇帶有指令碼的遊戲物件,並將其他遊戲物件拖動到 **檢查器** 中的 **開始**、**中間** 和 **結束** 槽位中。這將從指定為“開始”的遊戲物件渲染一條曲線,到指定為“結束”的遊戲物件,並朝“中間”彎曲。

二次貝塞爾樣條曲線
[edit | edit source]二次貝塞爾樣條曲線只是一條連續的、平滑的曲線,它由二次貝塞爾曲線段組成。如果曲線的控制點是任意選擇的,那麼樣條曲線將既不連續也不平滑;因此,必須以特定方式選擇控制點。
一種常見的方法是使用一組使用者指定的控制點(圖中的綠色圓圈)作為各段的 控制點,並選擇相鄰兩個使用者指定控制點之間的中心位置作為 和 控制點(圖中的黑色矩形)。這實際上保證了樣條曲線是平滑的(在數學意義上也是連續的切向量)。
以下指令碼實現了這個想法。對於第 j 個段,它計算 作為第 j 個和第 (j+1) 個使用者指定的控制點的平均值, 設定為第 (j+1) 個使用者指定的控制點,以及 是第 (j+1) 個和第 (j+2) 個使用者指定的控制點的平均值。
p0 = 0.5f * (controlPoints[j].transform.position
+ controlPoints[j + 1].transform.position);
p1 = controlPoints[j + 1].transform.position;
p2 = 0.5f * (controlPoints[j + 1].transform.position
+ controlPoints[j + 2].transform.position);
然後,每個單獨的段都被計算為二次貝塞爾曲線。唯一的調整是,除了最後一個段之外,所有段都不應該達到 。如果它們確實達到了,那麼下一段的第一個樣本位置將位於相同的位置,這將在渲染中可見。完整的指令碼是
using System.Collections.Generic;
using UnityEngine;
[ExecuteInEditMode, RequireComponent(typeof(LineRenderer))]
public class Bezier_Spline : MonoBehaviour
{
public List<GameObject> controlPoints = new List<GameObject>();
public Color color = Color.white;
public float width = 0.2f;
public int numberOfPoints = 20;
LineRenderer lineRenderer;
void Start ()
{
lineRenderer = GetComponent<LineRenderer>();
lineRenderer.useWorldSpace = true;
lineRenderer.material = new Material(Shader.Find("Legacy Shaders/Particles/Additive"));
}
void Update ()
{
if (null == lineRenderer || controlPoints == null || controlPoints.Count < 3)
{
return; // not enough points specified
}
// update line renderer
lineRenderer.startColor = color;
lineRenderer.endColor = color;
lineRenderer.startWidth = width;
lineRenderer.endWidth = width;
if(numberOfPoints < 2)
{
numberOfPoints = 2;
}
lineRenderer.positionCount = numberOfPoints * (controlPoints.Count - 2);
Vector3 p0, p1 ,p2;
for(int j = 0; j < controlPoints.Count - 2; j++)
{
// check control points
if (controlPoints[j] == null || controlPoints[j + 1] == null
|| controlPoints[j + 2] == null)
{
return;
}
// determine control points of segment
p0 = 0.5f * (controlPoints[j].transform.position
+ controlPoints[j + 1].transform.position);
p1 = controlPoints[j + 1].transform.position;
p2 = 0.5f * (controlPoints[j + 1].transform.position
+ controlPoints[j + 2].transform.position);
// set points of quadratic Bezier curve
Vector3 position;
float t;
float pointStep = 1.0f / numberOfPoints;
if (j == controlPoints.Count - 3)
{
pointStep = 1.0f / (numberOfPoints - 1.0f);
// last point of last segment should reach p2
}
for(int i = 0; i < numberOfPoints; i++)
{
t = i * pointStep;
position = (1.0f - t) * (1.0f - t) * p0
+ 2.0f * (1.0f - t) * t * p1 + t * t * p2;
lineRenderer.SetPosition(i + j * numberOfPoints, position);
}
}
}
}
該指令碼必須命名為 Bezier_Spline,並且與貝塞爾曲線的指令碼工作方式相同,只是使用者可以指定任意數量的控制點。對於閉合樣條曲線,最後兩個使用者指定的控制點應與前兩個控制點相同。對於實際上到達端點的開放樣條曲線,應兩次指定第一個和最後一個控制點。
在本教程中,我們已經瞭解了
- 線性貝塞爾曲線和二次貝塞爾曲線以及二次貝塞爾樣條曲線的定義
- 使用 Unity 的 LineRenderer 元件實現二次貝塞爾曲線和二次貝塞爾樣條曲線。
如果您想了解更多
- 關於貝塞爾曲線(和貝塞爾樣條曲線),維基百科關於 “貝塞爾曲線” 的文章提供了一個良好的起點。
- 關於 Unity 的 LineRenderer,您應該閱讀 Unity 關於類 LineRenderer 的文件。