1 前言
線段渲染器LineRenderer、拖尾TrailRenderer、繪制物體表面三角形網格從不同角度介紹了繪制線段的方法,本文再介紹一種新的繪制線段的方法:使用 GL 繪制線段。
Graphics Library(簡稱 GL),包含一系列類似 OpenGL 的 Immediate 模式的渲染指令,比 Graphic.DrawMesh() 更高效。GL 是立即執行的,如果在Update() 方法里調用,它們將在相機渲染前執行,相機渲染前會清空屏幕,GL 渲染效果將無法看到。通常 GL 用法是:在相機上掛腳本,并在 OnPostRender() 方法里執行(MonoBehaviour的生命周期)。GL 渲染的圖像不需要 GameObject 承載,在 Hierarchy 窗口不會生成 GameObject 對象。
2 代碼實現
LinePainter.cs
using UnityEngine;
[RequireComponent(typeof(Camera))]
public class LinePainter : MonoBehaviour {
private Material lineMaterial; // 線段材質
private Vector3[][] circleVertices; // 3個圓環的頂點坐標
private Color[] colors; // 3 個圓環的顏色
private void Start() {
lineMaterial = new Material(Shader.Find("Hidden/Internal-Colored"));
colors = new Color[] {Color.red, Color.green, Color.blue};
circleVertices = new Vector3[3][];
for (int i = 0; i < 3; i++) {
circleVertices[i] = GetCircleLines(Vector3.up, Vector3.one, i, 20);
}
}
private void OnPostRender() { // GL處理不能放在Update里
for (int i = 0; i < 3; i++) {
DrawLines(circleVertices[i], colors[i], Color.yellow, 0.01f);
}
}
private Vector3[] GetCircleLines(Vector3 center, Vector3 radius, int axis, int num) { // 獲取圓環頂點數據
Vector3[] vertices = new Vector3[num + 1];
float ds = Mathf.PI * 2.0f / num;
float theta = 0;
if (axis == 0)
{
for (int i = 0; i <= num; i++) {
theta += ds;
Vector3 vec = new Vector3(0, Mathf.Cos(theta), Mathf.Sin(theta));
vertices[i] = new Vector3(vec.x * radius.x, vec.y * radius.y, vec.z * radius.z) + center;
}
} else if (axis == 1) {
for (int i = 0; i <= num; i++) {
theta += ds;
Vector3 vec = new Vector3(Mathf.Cos(theta), 0, Mathf.Sin(theta));
vertices[i] = new Vector3(vec.x * radius.x, vec.y * radius.y, vec.z * radius.z) + center;
}
} else {
for (int i = 0; i <= num; i++) {
theta += ds;
Vector3 vec = new Vector3(Mathf.Cos(theta), Mathf.Sin(theta), 0);
vertices[i] = new Vector3(vec.x * radius.x, vec.y * radius.y, vec.z * radius.z) + center;
}
}
return vertices;
}
private void DrawLines(Vector3[] points, Color lineColor, Color pointColor, float pointSize) { // 繪制線段
GL.PushMatrix();
GL.LoadIdentity();
GL.MultMatrix(GetComponent<Camera>().worldToCameraMatrix);
GL.LoadProjectionMatrix(GetComponent<Camera>().projectionMatrix);
lineMaterial.SetPass(0);
AddLines(points, lineColor);
if (pointColor != null && pointSize > 0) {
AddPoints(points, pointColor, pointSize);
}
GL.PopMatrix();
}
private void AddLines(Vector3[] points, Color color) { // 添加線段端點
GL.Begin(GL.LINE_STRIP);
GL.Color(color);
foreach (Vector3 point in points)
{
GL.Vertex3(point.x, point.y, point.z);
}
GL.End();
}
private void AddPoints(Vector3[] points, Color color, float size) { // 添加繪制點(通過繪制小立方模擬頂點)
GL.Begin(GL.QUADS);
GL.Color(color);
foreach (Vector3 point in points) {
// 前面
GL.Vertex3(point.x + size, point.y + size, point.z - size);
GL.Vertex3(point.x + size, point.y - size, point.z - size);
GL.Vertex3(point.x - size, point.y - size, point.z - size);
GL.Vertex3(point.x - size, point.y + size, point.z - size);
// 后面
GL.Vertex3(point.x + size, point.y + size, point.z + size);
GL.Vertex3(point.x + size, point.y - size, point.z + size);
GL.Vertex3(point.x - size, point.y - size, point.z + size);
GL.Vertex3(point.x - size, point.y + size, point.z + size);
// 上面
GL.Vertex3(point.x + size, point.y + size, point.z + size);
GL.Vertex3(point.x + size, point.y + size, point.z - size);
GL.Vertex3(point.x - size, point.y + size, point.z - size);
GL.Vertex3(point.x - size, point.y + size, point.z + size);
// 下面
GL.Vertex3(point.x + size, point.y - size, point.z + size);
GL.Vertex3(point.x + size, point.y - size, point.z - size);
GL.Vertex3(point.x - size, point.y - size, point.z - size);
GL.Vertex3(point.x - size, point.y - size, point.z + size);
// 左面
GL.Vertex3(point.x - size, point.y + size, point.z + size);
GL.Vertex3(point.x - size, point.y + size, point.z - size);
GL.Vertex3(point.x - size, point.y - size, point.z - size);
GL.Vertex3(point.x - size, point.y - size, point.z + size);
// 右面
GL.Vertex3(point.x + size, point.y + size, point.z + size);
GL.Vertex3(point.x + size, point.y + size, point.z - size);
GL.Vertex3(point.x + size, point.y - size, point.z - size);
GL.Vertex3(point.x + size, point.y - size, point.z + size);
}
GL.End();
}
}
說明: LinePainter 腳本組件需要掛在相機下。
SceneController.cs
using UnityEngine;
public class SceneController : MonoBehaviour {
private Transform cam; // 相機
private float nearPlan; // 近平面
private Vector3 preMousePos; // 上一幀的鼠標坐標
private int keyStatus = 0; // 鼠標樣式狀態
private bool isDraging = false; // 是否在拖拽中
void Start() {
cam = Camera.main.transform;
nearPlan = Camera.main.nearClipPlane;
}
void Update() {
keyStatus = GetKeyStatus();
UpdateScene(); // 更新場景(Ctrl+Scroll: 縮放場景, Ctrl+Drag: 平移場景, Alt+Drag: 旋轉場景)
}
private int GetKeyStatus() { // 獲取按鍵狀態(0: 默認, 1: 縮放或平移, 2: 旋轉)
if (isDraging) {
return keyStatus;
}
if (Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.LeftControl)) {
return 1;
}
if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.LeftAlt)) {
return 2;
}
return 0;
}
private void UpdateScene() { // 更新場景(Ctrl+Scroll: 縮放場景, Ctrl+Drag: 平移場景, Alt+Drag: 旋轉場景)
float scroll = Input.GetAxis("Mouse ScrollWheel");
if (!isDraging && keyStatus == 1 && Mathf.Abs(scroll) > 0) { // 縮放場景
ScaleScene(scroll);
} else if (Input.GetMouseButtonDown(0)) {
preMousePos = Input.mousePosition;
isDraging = true;
} else if (Input.GetMouseButtonUp(0)) {
isDraging = false;
} else if (Input.GetMouseButton(0)) {
Vector3 offset = Input.mousePosition - preMousePos;
if (keyStatus == 1) { // 移動場景
MoveScene(offset);
} else if (keyStatus == 2) { // 旋轉場景
RotateScene(offset);
}
preMousePos = Input.mousePosition;
}
}
private void ScaleScene(float scroll) { // 縮放場景
cam.position += cam.forward * scroll;
}
private void MoveScene(Vector3 offset) { // 平移場景
cam.position -= (cam.right * offset.x / 100 + cam.up * offset.y / 100);
}
private void RotateScene(Vector3 offset) { // 旋轉場景
Vector3 rotateCenter = GetRotateCenter(0);
cam.RotateAround(rotateCenter, Vector3.up, offset.x / 3); // 水平拖拽分量
cam.LookAt(rotateCenter);
cam.RotateAround(rotateCenter, -cam.right, offset.y / 5); // 豎直拖拽分量
}
private Vector3 GetRotateCenter(float planeY) { // 獲取旋轉中心
if (Mathf.Abs(cam.forward.y) < Vector3.kEpsilon || Mathf.Abs(cam.position.y) < Vector3.kEpsilon)
{
return cam.position + cam.forward * (nearPlan + 1 / nearPlan);
}
float t = (planeY - cam.position.y) / cam.forward.y;
float x = cam.position.x + t * cam.forward.x;
float z = cam.position.z + t * cam.forward.z;
return new Vector3(x, planeY, z);
}
}
說明: SceneController 腳本組件用于控制相機位置和姿態,便于從不同角度查看繪制的線段,其原理介紹見→縮放、平移、旋轉場景。
3 運行效果
聲明:本文轉自【Unity3D】使用GL繪制線段