首页 > 其他分享 >【Unity3D】使用GL绘制线段

【Unity3D】使用GL绘制线段

时间:2023-03-21 10:00:37浏览次数:62  
标签:Unity3D point 线段 Vector3 cam GL Vertex3 size

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 运行效果

img

​ 声明:本文转自【Unity3D】使用GL绘制线段

标签:Unity3D,point,线段,Vector3,cam,GL,Vertex3,size
From: https://www.cnblogs.com/zhyan8/p/17237943.html

相关文章

  • 【Unity3D】VideoPlayer组件
    1简介​AudioSource组件中介绍了音频的播放,本文将介绍基于VideoPlayer组件实现视频播放。​VideoPlayer属性面板如下:Source:视频源类型,有2种类型:VideoC......
  • 【Unity3D】固定管线着色器二
    1前言​固定管线着色器一中介绍了Shader中外部属性、光照、贴图等基础用法,本文将进一步讲解固定管线着色器,介绍正面与反面剔除、Alpha测试、深度测试、混合、渲......
  • 【Unity3D】固定管线着色器一
    1前言​着色器(Shader)是渲染管线中最重要的一环,Unity3D底层基于OpenGL实现,读者可以通过渲染管线了解Unity3D渲染流程。​OpenGL1.x为固定管线,2.x之后......
  • 【Unity3D】协同程序
    1简介​1)协程概念​协同程序(Coroutine)简称协程,是伴随主线程一起运行的程序片段,是一个能够暂停执行的函数,用于解决程序并行问题。协程是C#中的概念,由于Unity3......
  • 【Unity3D】顶点和片元着色器
    1前言​上文介绍了渲染管线、固定管线着色器和表面着色器,如下:渲染管线固定管线着色器一固定管线着色器二表面着色器​固定管线着色器通过命令方式实现光......
  • 【Unity3D】表面着色器
    1前言​固定管线着色器一、固定管线着色器二中介绍了ShaderLib的基本用法,本文将接着讲解表面着色器(SurfaceShader)的用法。固定管线着色器基于ShaderLib命令实......
  • [DS记录] 线段树 Beats
    0.前言所谓线段树Beats,就是吉老师打出来的线段树。1.基本操作P6242线段树3区间加区间取min区间最大值区间和区间历史最大值首先考虑134咋做。就......
  • [浅谈] 线段树优化建边
    \(\color{purple}\text{P6348[PA2011]Journeys}\)\(\color{green}\text{2023.1.1010:58}\)线段树优化建边。题意:给定一个图,每次对区间\([a,b]\)至区间\([c,d]\)建......
  • 【Unity3D】AudioSource组件
    1简介​1)AudioSource与AudioListener简介​AudioSource(音频源)组件用于控制播放AudioClip(音频片段),能够控制2D和3D(距离越远,声音越小)声音播放,它一般挂在产......
  • 【Unity3D】场景切换、全屏_恢复切换、退出游戏、截屏
    1前言​1)场景切换​场景切换可以使用SceneManager的LoadScene和LoadSceneAsync方法,如下:publicstaticvoidLoadScene(stringsceneName)publicstatic......