跳轉到內容

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

來自華夏公益教科書,開放世界開放書籍
一條帶有控制點 P0、P1 和 P2 的平滑曲線。

本教程討論了在 Unity 中渲染二次貝塞爾曲線和樣條的一種方法。這是幾個教程中的第一個,演示了 Unity 有用的功能,這些功能與著色器沒有直接關聯;因此,不需要著色器程式設計,所有展示的程式碼都在 C# 中。

平滑曲線在計算機圖形學中有很多應用,特別是在動畫和建模中。在 3D 遊戲引擎中,通常會使用管狀網格,並透過頂點混合對其進行變形,如 “非線性變形”部分 中所述,而不是渲染曲線;然而,渲染曲線而不是變形網格可以提供顯著的效能優勢。

從 P0 到 P1 的線性貝塞爾曲線等同於線性插值。

線性貝塞爾曲線

[編輯 | 編輯原始碼]

最簡單的貝塞爾曲線是線性貝塞爾曲線 ,對於 從 0 到 1,在兩點 之間,它恰好與這兩點之間的線性插值相同

你可能很時髦,稱 為 1 次伯恩斯坦基多項式,但實際上它只是線性插值。

在帶有控制點 P0、P1 和 P2 的二次貝塞爾曲線上對取樣點進行動畫處理。

二次貝塞爾曲線

[編輯 | 編輯原始碼]

更有趣的貝塞爾曲線是二次貝塞爾曲線 ,對於 從 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**,雙擊它,複製並貼上上面的程式碼,儲存它,建立一個新的空遊戲物件(在主選單中:**遊戲物件>建立空**),然後將指令碼附加到它(將指令碼從 **專案視窗** 拖動到 **層次結構視窗** 中的空遊戲物件上)。

然後建立另外三個空遊戲物件(或任何其他遊戲物件),這些遊戲物件將具有不同的位置,用作控制點。選擇帶有指令碼的遊戲物件,並將其他遊戲物件拖動到 **檢查器** 中的 **開始**、**中間** 和 **結束** 槽位中。這將從指定為“開始”的遊戲物件渲染一條曲線,到指定為“結束”的遊戲物件,並朝“中間”彎曲。

由 8 條二次貝塞爾曲線組成的二次貝塞爾樣條曲線。

二次貝塞爾樣條曲線

[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 的文件。

< Cg 程式設計/Unity

除非另有說明,否則此頁面上的所有示例原始碼均授予公共領域。
華夏公益教科書