【Unity3D】使用GL绘制线段

news2024/12/23 14:57:22

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/77879.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python tk 小案例:制作一个问题搜索器

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 又到了学Python时刻~ 在逛百度搜东西的时候&#xff0c;有一些杂乱的词条容易混入进来‘ 那么&#xff1f;我们能不能自己创建一个类似百度的搜索器呢&#xff1f; 当然是可以的&#xff0c;今天博主就来分享一下如何…

Dell电脑搭配Win10休眠 = 黑屏

应该是Dell的硬件和win10操作系统的适配性不行。 Dell黑屏现象 刚买Dell笔记本的时候&#xff0c;就有“黑屏”问题。刚买的一个周、一个月、一年、两年都有这样的问题&#xff0c;而且一个月至少发生一次&#xff1a; 摁了开机键&#xff0c;也开机成功了&#xff0c;电脑就…

Python——旋转字符串

题目描述 给定两个字符串s和goal&#xff0c;如果在若干次旋转操作后s能够变成goal&#xff0c;那么就返回True s的旋转操作就是把s最左面的字符放到最右面 例如&#xff1a; s ‘abcde’ 旋转一次就是‘bceda’ 而如果goal是bceda&#xff0c;那么goal就是s的旋转字符串 P…

[附源码]JAVA毕业设计心理学网站(系统+LW)

[附源码]JAVA毕业设计心理学网站&#xff08;系统LW&#xff09; 项目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#…

MySQL数据库的索引以及事务详解

课前导读&#xff1a; 本章设计到MySQL数据库的索引和事务操作&#xff0c;索引操作设计的概念内容比较多&#xff0c;但是他涉及到数据库的内部运行效率和使用空间等多方面知识&#xff0c;相比比较重要&#xff0c;也需要进行相关学习。而事务操作更不用多说&#xff0c;是我…

WEB进销存管理系统源码带操作手册和源码安装说明文档

适用于大众化普通商品&#xff0c;包括鞋服、眼镜店等&#xff0c;前端使用js、html、css最基本的技术&#xff0c;后端使用sql、存储过程&#xff0c;前后端才有json交互&#xff0c;不依赖于任何第三方框架&#xff0c;简单易用易学&#xff0c;适合扩展。 源码类型&#…

Java十年功力还是涨不了薪,推荐必看《Java核心技术及面试指南》

很多程序员工作努力&#xff0c;但表现一般&#xff0c;导致面试失败或者涨不了薪资&#xff0c;在我看来&#xff0c;这种情况出现的原因有两个&#xff1a;一个是“盲人摸象&#xff0c;只知其一不知其二”&#xff0c;埋头做技术或者“CV工程师”&#xff0c;再做几年也没成…

JavaScript期末大作业 基于HTML+CSS+JavaScript技术制作web前端开发个人博客(48页)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

MOSFET 和 IGBT 栅极驱动器电路的基本原理学习笔记(四)高侧非隔离栅极驱动

高侧非隔离栅极驱动 1.适用于P沟道的高侧驱动器 2.适用于N沟道的高侧直接驱动器 1.适用于P沟道的高侧驱动器 高侧非隔离栅极驱动可按照所驱动的器件类型或涉及的驱动电路类型来分类。相应地&#xff0c;无论是使用P沟道还是 N沟道器件&#xff0c;是实施直接驱动、电平位移驱…

项目设置统一返回结果对象

一、统一返回数据格式 项目中我们会将响应封装成json返回&#xff0c;一般我们会将所有接口的数据格式统一&#xff0c; 使前端(iOS,Android, Web)对数据的操作更一致、轻松。 一般情况下&#xff0c;统一返回数据格式没有固定的格式&#xff0c;只要能描述清楚返回的数据状态以…

十四、使用 Vue Router 开发单页应用(4)

本周概要 导航守卫 全局守卫路由独享的守卫组件内守卫导航解析流程 14.10 导航守卫 在 14.4 嵌套路由 小节中已经使用过一个组件内的导航守卫&#xff1a;beforeRouteUpdate 。Vue Router 提供的导航守卫主要用于在导航过程中重定向或取消路由&#xff0c;或添加权限验证、…

python基于用户画像和协同过滤实现电影推荐系统

1、概要 传统电影推荐系统大多使用协同过滤算法实现电影推荐&#xff0c;主要实现机理是通过用户评分及用户观影历史数据抽象为多维向量利用欧式距离或其他向量计算公式实现推荐&#xff0c;本文中将采用常用的机器学习算法Kmeans聚类算法协同过滤算法word2vec搜索推荐模型多模…

【猿如意】MySQL的下载、安装、使用,这一文足够了~

大家好&#xff0c;我是笑小枫&#xff0c;本篇文章为大家分享一个好用的工具-【猿如意】 对于这个工具呢&#xff0c;怎么说呢&#xff1f;简单点就是&#xff1a;不经意间回首&#xff0c;放眼望去&#xff0c;满眼是你~ 下面就已Mysql下载安装的过程来和大家一起体验下我们的…

【代码审计-.NET】基于.NET框架开发的代码审计

目录 一、审计方法 1、从黑盒到白盒 2、白盒审计 3、灰盒审计 二、审计过程 1、功能点追踪 2、功能函数 3、目录扫描 4、getshell 5、安全模块未引用 6、自动扫描工具 一、审计方法 1、从黑盒到白盒 --->从一个网站前端 --->查看其页面的源代码&#xff0c;并分…

【日常系列】LeetCode《20·数据结构设计》

数据规模->时间复杂度 <10^4 &#x1f62e;(n^2) <10^7:o(nlogn) <10^8:o(n) 10^8<:o(logn),o(1) 内容 lc 155 【剑指 30】【top100】&#xff1a;最小栈 https://leetcode.cn/problems/min-stack/ 提示&#xff1a; -2^31 < val < 2^31 - 1 pop、top 和…

快看梅西射门了,这是梅西的大力抽射~阿根廷加油,我们是冠军

&#x1f5b1; ⌨个人主页&#xff1a;Love And Program的个人主页 &#x1f496;&#x1f496;如果对你有帮助的话希望三连&#x1f4a8;&#x1f4a8;支持一下博主 来自梅西的大力抽射&#x1f970;致昨晚的梅西思路加入阿根廷元素加入足球元素源码致昨晚的梅西 昨晚上阿根廷…

springcloud 从头开始构建分布式微服务脚手架

必备服务&#xff08;Windows开发本机环境&#xff09; Java maven mysql&#xff1a;自启动服务&#xff0c;后台运行 127.0.0.1:3306 MySQL57 root/root Redis&#xff1a;手动运行&#xff0c;前台运行 127.0.0.1:6379 执行命令redis-server.exe redis.windows.conf na…

Weblogic漏洞 - 通杀方式

文章目录简介恶意文件把恶意文件部署到攻击机&#xff0c;并开启http服务写入文件写入反弹shell命令执行反弹shell命令拿到目标机器权限[linux] WebLogic Server 版本: 12.2.1.3简介 最早在 CVE-2019-2725 被提出&#xff0c;对于所有Weblogic版本均有效。 构造一个XML文件&…

CALC-python和shell对字符的解析差异

好久没看题了&#xff0c;记录一道感觉还挺有意思的题目 一进去题目界面非常简洁&#xff0c;一个计算器 这个简洁的界面&#xff0c;好像似曾相识&#xff0c;总感觉好像以前做题时遇到的ssti题目的界面&#xff0c;果断来一波ssti emem有WAF&#xff0c;尝试下绕过&#xff…

C++ 实现守护进程

文章目录1.守护进程概念1.什么是守护进程2.守护进程的特点3.如何查看linux系统中已存在的守护进程2.守护进程编写的步骤3.示例1.守护进程概念 1.什么是守护进程 Linux Deamon守护进程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或者等待处理某些事…