【Unity3D】使用GL繪制線段

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繪制線段

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容