第3章第5节 发布Wanlix操作系统

本文介绍了Wanlix操作系统的发布,这是一个小巧的任务切换系统,具备基本功能并为用户提供了一个简单的库文件形式。文章还探讨了编译与链接过程,并提出了版本控制的重要性。

源码请在https://github.com/ifreecoding/MbedRtos.git下载

第5节 发布Wanlix操作系统

经过前面3.1~3.3节循序渐进的开发,我们已经使Wanlix操作系统具备了最基本的任务切换功能,并在3.4节使用Wanlix开发了一个交通红绿灯控制系统,到此为止,我们已经完成了Wanlix操作系统的所有开发工作。Wanlix操作系统的定位就是一个非常小巧的操作系统,有关嵌入式操作系统更多的功能,我们将在第4章开发Mindows操作系统的过程中不断的引入。

前面几节的代码里不仅包含了Wanlix的代码,而且还使用了一些用户代码来演示操作系统的功能,现在我们将操作系统的代码单独整理出来,去掉用户代码,发布仅有操作系统的代码,当用户需要使用这个操作系统时,只要在这些操作系统代码基础上补充自己的代码即可使用。

Wanlix目录下的代码全部是操作系统代码,这个目录下的文件需要保留。srccode目录下是用户代码,需要删除,但wlx_userboot.c和wlx_userboot.h文件作为操作系统与用户代码的接口文件需要保留,在wlx_userboot.c文件里需要清空WLX_RootTask函数里面的内容,wlx_userboot.h文件里去掉对用户文件global.h的引用,去掉system目录下Keil开发环境使用的启动文件startup.s和链接文件ADuC702X.ld,最后剩下的就仅仅是操作系统的代码了!

这个小操作系统虽然功能简单,但绝对可以实现任务的调度功能,这点对于一个小项目的程序设计来说就已经方便很多了,而且更难能可贵的是它耗费的系统资源是如此之少,编译后仅仅占用了5、600个字节的程序空间(不同编译器编译的结果会有所不同)和16个字节的内存空间。

当然,这个操作系统目前只能用在ARM7芯片上,因为任务切换的核心代码是用汇编编写的,而不同芯片的汇编语言又不兼容。因此,如果你需要在其它芯片上使用这个操作系统就必须修改wlx_core_a.asm文件里的函数,芯片的出入栈方式也是需要考虑的。

这个操作系统还有一个限制:任务不能运行到结束。不管是像TEST_TestTask1任务那样是个死循环,还是像WLX_RootTask任务那样执行一次之后就永远不会再切换回来继续执行了,一定要保证被创建任务的函数永远不能执行到最后一条指令。因为一个函数执行完后,会通过跳转指令返回到它的父函数,而Wanlix操作系统在创建任务时并没有为被创建任务的函数提供返回地址,因此被创建任务的函数就不能结束,这就是任务不能结束的原因。为了防止出现这种问题,每个任务需要使用while构造成一个死循环。本着Wanlix只实现最简单的任务切换功能的原则,这个问题在这里就不解决了,我们将在Mindows操作系统中解决。

另外,任务切换函数WLX_TaskSwitch不能在中断中使用。该函数会备份恢复任务的上下文信息,如果在中断中调用该函数则会破坏中断栈中的数据,导致系统崩溃。

仅剩下的这些操作系统代码只能编译,却不能链接出可执行的最终目标文件。

所谓编译就是将我们所写的C、汇编等源代码翻译成芯片能理解的机器语言的过程,这个过程中会使用一些技巧,减少冗余的代码,提高效率,这就是优化,例如

i++;

i++;

可以优化为:

i += 2;

当然,这个过程并非只是像这个例子这么简单,而是包含了大量的相当复杂的处理,以至于编译器的开发在相当长的一段时间内进展不大。在编译时有很多优化选项可以使用,Wanlix所使用的O2选项就是其中的一个。

在程序出现的初期,是并没有编译这一概念的。那时候的电子设备没有键盘、显示器这样的输入输出设备,电子设备最小的功能单元就是一个个门电路,它只能识别0或者1的数字信号,因此,能想到的最方便的编程方法就是直接书写由0、1组成的程序,也就是直接写二进制机器码,类似2.2节中图6那样的格式,但这样编码不但效率低,而且极容易出错,修改、定位问题时更是痛苦万分。在编程过程中,人们逐渐发现,同一种操作都是对应同一个机器码,那么是否可以使用一个符号来代替这些机器码呢?这样就产生了汇编语言,例如在ARM7芯片上,使用汇编指令“ADD”来代替“加”操作的机器码“0b0100”,但这样替换后,也就需要一个翻译器将ADD符号及其后面的操作参数一起翻译成机器码,这就是编译器的概念。汇编语言的出现极大的提高了编程的效率,程序员们只需要记住汇编指令即可,不需要去查找复杂的机器码指令了。但汇编语言也只是简单的做了一些指令翻译的工作,程序运行的细节还需要程序员去关心,与硬件相关性也非常强,不同芯片的机器码也是不同的,汇编指令也是不同的,这给程序移植造成了很大的困难。这样编程语言就发展到了高级语言阶段,例如我们所使用的C语言。高级语言屏蔽掉了硬件层的概念,将程序语言抽象为接近人类逻辑的数字语言和自然语言,那么这个屏蔽硬件层、抽象语言的过程就完全由高级语言编译器来完成了。

源程序经过编译器的处理,被编译成了芯片可以识别的机器码,但此时还不能直接运行,因为编译过程只产生了机器码,并没有为这些机器码分配地址空间,前面我们介绍过,函数调用的过程就是PC指针跳转的过程,就是跳转到指令运行的地址空间取指的过程。每段程序必须有自己运行的空间,这是在链接过程中确定的,链接器会根据链接文件的配置,将已编译好的机器码分配到不同的地址空间,并计算各个函数、变量之间的地址关系,将他们关联起来,这样才会生成最终可执行的目标文件。

在链接过程,我们可以选择输出map文件,在Keil环境下可以选择“Project—>Options for target”,打开下面的对话框,选择“Listing”页,把红框内的“Memory Map”选上,重新编译链接就会生成map文件。

图 25  Keil中生成map文件的选项

map文件中包含了各个段、函数、全局等符号的地址分配情况,下面我截取了3.4节map文件中的一部分内容做个简单介绍:

006  Allocating common symbols

007  Common symbol       size              file

008  

009  gaucRootTaskStack   0x190             ../outfile/wlx_core_c.o

010  gpstrRootTaskTcb    0x4               ../outfile/wlx_core_c.o

011  guiCurSta           0x4               ../outfile/test.o

……

025  Memory Configuration

026  

027  Name             Origin             Length             Attributes

028  IntFLASH         0x00080000         0x0000f800         xr

029  IntRAM           0x00010000         0x00001b74         rw

030  *default*        0x00000000         0xffffffff

……

066   *(.text)

067   .text          0x00080108       0x54 ../outfile/wlx_core_a.o

068                  0x00080108                WLX_ContextSwitch

069                  0x0008013c                WLX_SwitchToTask

070   .text          0x0008015c      0x1f4 ../outfile/wlx_core_c.o

071                  0x0008015c                WLX_TaskTcbInit

……

227                  0x00010308                guiCurSta

从006行可知gaucRootTaskStack全局变量的大小是0x190字节,在wlx_core_c.c文件中定义的。从011行可知guiCurSta全局变量的大小是4字节,在test.c文件中定义的,从227行可以知道它位于0x00010308的地址空间。从028行可知软件中有一个IntFLASH段,它从0x00080000地址开始,长度为0x0000f800字节,属性是只读和可执行。从029行可知软件中有一个IntRAM段,从0x00010000地址开始,长度为0x00001b74字节,属性是可读可写。从067和068行可知,WLX_ContextSwitch函数位于wlx_core_a.asm文件中,它的起始地址是0x00080108。

map文件对软件开发还有会有一些帮助的,定位问题时我们可能会需要通过查找map文件来获得一些信息。不同工具生成的map文件格式是不同的,但内容大概都差不多,我这里只能抛砖引玉,遇到具体的情况还得读者自己分析。

    本节发布的Wanlix代码,由于没有启动文件,也没有链接文件,因此不能生成可执行的目标文件,但我们可以将它们编译成库文件。在Keil环境下可以选择“Project—>Options for target”,打开下面的对话框,选择“Output”页,选择红框内的“Create Library”,重新编译就会生成库文件。

图 26  Keil中生成库文件的选项

    库文件是编译的结果,它里面包含了源代码编译后的机器码以及符号表,因此它可以作为链接过程的输入。我们可以将操作系统编译成库文件提供给用户使用,用户只要在自己的工程里包含库文件,就可以直接调用库文件里的函数、全局变量就可以了,而不需要拥有库文件的源代码。库文件的方式屏蔽了源代码,只提供机器码,对于不开源的软件,往往就是使用这种方法。对于某些较大的项目也可以使用这种方式开发,底层软件人员将他们的代码编译成库文件,发布给上层软件人员使用,这样上层软件人员不需要全套代码就能编译出最终目标文件了。这样做不仅管理方便,而且也可以降低泄露产品全套源代码的风险。

图 27  不使用库文件和使用库文件Keil工程对比

图27中,左侧是没有使用库文件的工程文件树结构,使用Wanlix的源代码编译。右侧是使用了库文件的工程文件树结构,使用Wanlix的库文件编译。注意,当使用库文件时,必须将库中的接口函数声明到一个h头文件中,使用库文件的程序也必须包含此头文件。在Wanlix操作系统中,这个头文件就是wanlix.h,里面包含了Wanlix操作系统的全部对外接口函数,见附录1。

写到这里,我遇到一个问题,在GNU环境下编译出的是.a库文件,使用.a库文件链接时链接程序出错了,不知道问题处在哪了,使用Keil自带的RealView编译链接器则没有此问题。如果哪位知道原因的话请到论坛上反馈一下,多谢了!

Wanlix的开发到此就告一段落,我们最后为Wanlix设定一个版本号作为这一阶段的结束标志。版本号的格式为Major.Minor.Revision.Build,Major是主版本号,当软件功能或结构有重大改变时才修改此版本号,比如增加了多个重要功能或者整体架构发生变化。Minor是子版本号,基于原有功能、结构增加、修改一些功能时修改此版本号。Revision是修改版本号,当修改bug、完善一些小功能时就更改此版本号。Build是编译版本号,每次正式编译时该版本号加1。每当上一级版本号变动后,下一级版本号归0重新开始。

Major和Minor版本很重要,修改时需要产品相关人员参加讨论是否需要修改,如何修改,这2个版本往往影响了产品大的特性,对用户会有直接的影响。Revision版本一般限于项目组内由项目经理控制,但需要发布给其它项目组使用。Build版本一般限于项目组内部的修改测试,不对外公布。因此,产品发布新版本时,需要体现出Major、Minor和Revision版本,Build版本建议不体现。

版本控制也非常重要,不能随意乱发导致版本过多,过多的版本会给产品维护带来非常多的麻烦,并且会增加维护成本。发布版本前需要做好版本计划,规划好一段时间内的版本数量,需要解决哪些问题,需要在哪个版本解决,版本发布后需要记录已发版本的特性、产品不同模块之间版本的配合使用关系等等问题。

版本号

说明

Major

Minor

Revision

Build

001.001.001.000

主版本号

子版本号

修正版本号

编译版本号

表 5  Wanlix版本号格式

此次Wanlix发布的版本号为001.001.001.000,只提供任务切换功能,Wanlix后续若还有发展的话,就在此版本号基础上修改。

我最原始的计划只是将代码写到这里,写操作系统的初衷只是因为当时找不到一个适合小型嵌入式设备的操作系统,才萌生自己写一个只具有任务切换功能的操作系统的想法。但当我写到这里,实现了任务切换功能之后,我发现我还可以实现更多的功能,还可以讲述更多的原理,还可以让更多的人了解更多的操作系统知识,还可以继续写下去。因此,我将继续写下去,去编写一个功能更强大更完善的操作系统——Mindows操作系统。

接下来的章节,我们将开始设计Mindows操作系统内核,一个具有实时抢占性的嵌入式操作系统内核!在编写Mindows的过程中我们将了解操作系统更多的知识,实现操作系统更多的功能!

新鲜出炉,提供源码!wanlix和mindows嵌入式操作系统 好不好,看过文档再说,我觉得,里面的很多东西是你在别的书上看不到的,包括一些ARM和CORTEX芯片的知识。 从现在开始,本网站将陆续发布Wanlix和Mindows操作系统内核。 Wanlix是一个内核非常小的嵌入式操作系统,只有几百个字,但功能少,只提供任务切换功能,非常适合资源特别少但又需要任务切换的小项目。 Mindows可提供多种操作系统功能,是实时抢占式操作系统,任务支持多种优先级抢占调度,将实时性高的任务设置为高优先级就可以保证软件系统的实时性,用户也可根据自身需求选取需要的部分,也可在此基础上编写代码增加自己需要的功能,具有可裁剪性。 我将Wanlix和Mindows的开发过程记录下来,就形成了这本“底层工作者手册之嵌入式操作系统内核”一书,本手册不仅仅是从应用的角度介绍操作系统如何使用,更重要的是从原理的角度对操作系统的功能做了分析、设计,从无到有循序渐进一点点的增加操作系统的功能,并且每增加一个功能便配以一个例子加以演示,让读者能立刻看到代码运行的结果。 本手册记录了我从对操作系统内核不了解到写出操作系统内核的过程,这样的一个过程对你来说应该也是一个最好的学习过程。 如果你有一定的C语言基础,并且对硬件也有稍微的了解,那么我相信你一定会看明白本手册!也一定可以随心所欲的修改、扩展你需要的操作系统功能! 请登陆www.ifreecoding.com获取更多资料
//1.mesh构建 using System.Collections; using System.Collections.Generic; using UnityEngine; [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer), typeof(MeshCollider))] public class Potteryprototype : MonoBehaviour { &nbsp;&nbsp;&nbsp; MeshFilter meshFilter; &nbsp;&nbsp;&nbsp; MeshRenderer meshRenderer; &nbsp;&nbsp;&nbsp; MeshCollider meshCollider; &nbsp;&nbsp;&nbsp; Mesh mesh; &nbsp; &nbsp;&nbsp;&nbsp; public int details = 40; &nbsp;&nbsp;&nbsp; public int layer = 20; &nbsp;&nbsp;&nbsp; public float Height = 0.1f; &nbsp; &nbsp;&nbsp;&nbsp; public float OuterRadius = 1.0f; &nbsp;&nbsp;&nbsp; public float InnerRadius = 0.9f; &nbsp; &nbsp;&nbsp;&nbsp; List<Vector3> vertices; &nbsp;&nbsp;&nbsp; List<Vector2> UV; &nbsp;&nbsp;&nbsp; List<int> triangles; &nbsp; &nbsp;&nbsp;&nbsp; float EachAngle ; &nbsp;&nbsp;&nbsp; int SideCount; &nbsp; &nbsp;&nbsp;&nbsp; public MouseControl mouse; &nbsp; &nbsp;&nbsp;&nbsp; void Start() &nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; meshFilter = GetComponent<MeshFilter>(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; meshCollider = GetComponent<MeshCollider>(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; meshRenderer = GetComponent<MeshRenderer>(); &nbsp;&nbsp;&nbsp; } &nbsp; &nbsp;&nbsp;&nbsp; [ContextMenu("GeneratePottery")] &nbsp;&nbsp;&nbsp; void GeneratePrototype() &nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vertices = new List<Vector3>(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; triangles = new List<int>(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; UV = new List<Vector2>(); &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EachAngle = Mathf.PI * 2 / details; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i < layer; i++) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GenerateCircle(i); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Capping(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mesh = new Mesh(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mesh.vertices = vertices.ToArray(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mesh.triangles = triangles.ToArray(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mesh.uv = UV.ToArray(); &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mesh.RecalculateBounds(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mesh.RecalculateTangents(); &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; meshFilter.mesh = mesh; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mesh.RecalculateNormals(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; meshCollider.sharedMesh = mesh; &nbsp;&nbsp;&nbsp; } &nbsp; &nbsp;&nbsp;&nbsp; void GenerateCircle(int _layer) &nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //外顶点与内顶点分开存储,方便变化操作时的计算 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; List<Vector3> vertices_outside = new List<Vector3>(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; List<Vector3> vertices_inside = new List<Vector3>(); &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; List<Vector2> UV_outside = new List<Vector2>(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; List<Vector2> UV_inside = new List<Vector2>(); &nbsp; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //外侧和内侧顶点计算 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //注意这里让每一圈的首尾重合了,也就是开始和结尾的顶点坐标一致 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //目的是计算UV坐标时不会出现空缺 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (float i = 0; i <= Mathf.PI * 2+EachAngle; i += EachAngle) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector3 v1 = new Vector3(OuterRadius * Mathf.Sin(i),&nbsp; _layer * Height, OuterRadius * Mathf.Cos(i)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector3 v2 = new Vector3(OuterRadius * Mathf.Sin(i),&nbsp; (_layer +1)* Height, OuterRadius * Mathf.Cos(i)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector3 v3 = new Vector3(InnerRadius * Mathf.Sin(i),&nbsp; _layer * Height, InnerRadius * Mathf.Cos(i)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector3 v4 = new Vector3(InnerRadius * Mathf.Sin(i),&nbsp; (_layer+1) * Height, InnerRadius * Mathf.Cos(i)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vertices_outside.Add(v1); vertices_outside.Add(v2); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vertices_inside.Add(v3); vertices_inside.Add(v4); &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector2 uv1 = new Vector2(i / Mathf.PI*2, _layer*1.0f / layer * 1.0f); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector2 uv2 = new Vector2(i / Mathf.PI*2, (_layer + 1)*1.0f / layer * 1.0f); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector2 uv3 = new Vector2(i / Mathf.PI*2, _layer*1.0f / layer * 1.0f); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector2 uv4 = new Vector2(i / Mathf.PI*2, (_layer + 1) *1.0f/ layer * 1.0f); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; UV_outside.Add(uv1); UV_outside.Add(uv2); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; UV_inside.Add(uv3); UV_inside.Add(uv4); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vertices.AddRange(vertices_outside); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vertices.AddRange(vertices_inside); &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; UV.AddRange(UV_outside); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; UV.AddRange(UV_inside); &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SideCount = vertices_outside.Count; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int j = vertices_outside.Count * _layer * 2; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int n = vertices_outside.Count; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = j; i < j + vertices_outside.Count - 2; i += 2) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; triangles.Add(i); triangles.Add(i + 2); triangles.Add(i + 1); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; triangles.Add(i + 2); triangles.Add(i + 3); triangles.Add(i + 1); &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; triangles.Add(i + n); triangles.Add(i + n + 1); triangles.Add(i + n + 2); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; triangles.Add(i + n + 2); triangles.Add(i + n + 1); triangles.Add(i + n + 3); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp; //封顶,底面由于看不见就不用管了 &nbsp;&nbsp;&nbsp; void Capping() &nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (float i = 0; i <= Mathf.PI * 2+EachAngle; i += EachAngle) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector3 outer = new Vector3(OuterRadius * Mathf.Sin(i),layer * Height, OuterRadius * Mathf.Cos(i)); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector3 inner= new Vector3(InnerRadius * Mathf.Sin(i), layer * Height, InnerRadius * Mathf.Cos(i)); &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;vertices.Add(outer);vertices.Add(inner); &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector2 uv1 = new Vector2(i / Mathf.PI * 2,0); Vector2 uv2 = new Vector2(i / Mathf.PI * 2, 1); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; UV.Add(uv1); UV.Add(uv2); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int j = SideCount * layer * 2; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for (int i=j;i<vertices.Count-2;i+=2) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; triangles.Add(i);triangles.Add(i + 3);triangles.Add(i + 1); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; triangles.Add(i);triangles.Add(i + 2);triangles.Add(i + 3); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; triangles.Add(vertices.Count - 2);triangles.Add(j + 1);triangles.Add(vertices.Count - 1); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; triangles.Add(vertices.Count - 2);triangles.Add(j);triangles.Add(j + 1); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; } } &nbsp; //2.动态改变形状 //这个函数放在Update()里调用 &nbsp;&nbsp;&nbsp; void GetMouseControlTransform() &nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //从屏幕鼠标位置发射一条射线到模型上,获取这个坐标 &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RaycastHit info; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (Physics.Raycast(ray.origin, ray.direction, out info)) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //在Unity中无法直接修改MeshFilter中Mesh的信息,需要新建一个Mesh修改其引用关系 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Mesh mesh = meshFilter.mesh; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector3[] _vertices = mesh.vertices; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i < _vertices.Length; i++) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //x,z平面变换 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //顶点移动与Y值的关系限制在5倍单层高度 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //这里可以自行修改,限制高度越大,曲线越平滑 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;if (Mathf.Abs(info.point.y - transform.TransformPoint(_vertices[i]).y) < (5 * Height)) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //计算顶点移动方向的向量 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Vector3 v_xz = (transform.TransformPoint(_vertices[i]) - new Vector3(transform.position.x, transform.TransformPoint(_vertices[i]).y, transform.position.z)); &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //外顶点与内顶点移动时相对距离应该保持不变 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //因为我们知道顶点数组内的顺序关系,所以可以通过计算总顶点数除以每层单侧顶点数的商的奇偶关系来判断是外顶点还是内顶点 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int n = i / SideCount; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bool side = n % 2 == 0; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //判断顶面顶点内外关系 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; bool caps = (i - (SideCount * layer * 2)) % 2 == 0; &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //限制每个顶点最大和最小的移动距离 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float max; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float min; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (i < SideCount * layer * 2) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; max = side ? 2f * OuterRadius : 2f * OuterRadius - (OuterRadius - InnerRadius); &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; min = side ? 0.5f * OuterRadius : 0.5f * OuterRadius - (OuterRadius - InnerRadius); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; max = caps ? 2f * OuterRadius : 2f * OuterRadius - (OuterRadius - InnerRadius); ; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; min = caps ? 0.5f * OuterRadius : 0.5f * OuterRadius - (OuterRadius - InnerRadius); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //计算当前顶点到鼠标Y值之间的距离,再用余弦函数算出实际位移距离 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float dif = Mathf.Abs(info.point.y - transform.TransformPoint(_vertices[i]).y); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (Input.GetKey(KeyCode.RightArrow)) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float outer = max - v_xz.magnitude; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _vertices[i] += v_xz.normalized * Mathf.Min(0.01f * Mathf.Cos(((dif / 5 * Height) * Mathf.PI) / 2), outer); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if (Input.GetKey(KeyCode.LeftArrow)) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float inner = v_xz.magnitude - min; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; _vertices[i] -= v_xz.normalized * Mathf.Min(0.01f * Mathf.Cos(((dif / 5 * Height) * Mathf.PI) / 2), inner); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Y轴变换 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float scale_y = transform.localScale.y; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (Input.GetKey(KeyCode.UpArrow)) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; scale_y = Mathf.Min(transform.localScale.y + 0.000001f, 2.0f); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if (Input.GetKey(KeyCode.DownArrow)) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; scale_y = Mathf.Max(transform.localScale.y - 0.000001f, 0.3f); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; transform.localScale = new Vector3(transform.localScale.x, scale_y, transform.localScale.z); &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mesh.vertices = _vertices; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mesh.RecalculateBounds(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mesh.RecalculateNormals(); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; meshFilter.mesh = mesh; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; meshCollider.sharedMesh = mesh; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp; } } &nbsp; //计算时就把顶点坐标系转换为自身坐标系,求得向量后再转换为世界坐标系 &nbsp;&nbsp;&nbsp; Vector3 v_xz = transform.TransformDirection(transform.InverseTransformPoint(_vertices[i]) - transform.InverseTransformPoint(new Vector3(0, _vertices[i].y, 0))); &nbsp; //3.法线平均化 IEnumerator Print_Normals() &nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for (int i = 0; i < meshFilter.mesh.vertices.Length; i++) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (i % 2 == 0) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Debug.DrawRay(transform.TransformPoint(meshFilter.mesh.vertices[i]), transform.TransformDirection(meshFilter.mesh.normals[i] * 0.3f), Color.green, 1000f); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Debug.DrawRay(transform.TransformPoint(meshFilter.mesh.vertices[i]), transform.TransformDirection(meshFilter.mesh.normals[i] * 0.3f), Color.blue, 1000f); &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; yield return new WaitForSeconds(Time.deltaTime); &nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } &nbsp;&nbsp;&nbsp; } &nbsp; //回到项目上来。这段法线计算的代码就不放上来了,大致就是根据顶点在数组中的下标去判断位置是否相同,然后把该顶点的法线相加即可。大家自己构建Mesh时的顶点顺序可能会不太一样。 以上代码是否有问题,请帮我完善
最新发布
08-30
<think> 我们正在处理一个Unity项目,需要完善Mesh构建、动态改变形状和法线平均化的代码。 根据引用[1]和引用[2]: - 创建Mesh需要设置顶点、三角形、法线等。 - 法线信息是一个Vector3数组,每个顶点对应一个法线向量。 - 如果希望网格看起来平滑,需要对相邻面的法线进行平均(即平滑法线)。 - 引用[2]提到可以使用mesh.RecalculateNormals()自动计算法线,但它是根据相邻三角面计算平均法线,从而实现平滑效果。 然而,用户提到了“动态改变形状”和“法线平均化”,因此我们需要: 1. 动态更新Mesh的顶点(改变形状)。 2. 在顶点改变后,重新计算法线。我们可以使用RecalculateNormals(),但有时可能需要自定义法线计算(例如,实现特定的平均化方式)。 引用[2]中提到了一个偷懒的方法:mesh.RecalculateNormals(),它会自动计算每个顶点的法线(基于共享该顶点的所有三角面的法线平均值,并归一化)。 但如果我们想要自己计算法线(比如为了更精确的控制),则需要: - 首先计算每个三角面的法线(通过叉乘得到)。 - 然后对于每个顶点,收集所有共享该顶点的三角面的法线,求平均(并归一化)作为该顶点的法线。 考虑到动态改变形状,我们可能需要频繁更新法线,因此使用Unity内置的RecalculateNormals()可能是高效且方便的选择。但如果需要自定义(例如忽略某些面),则需要手动计算。 根据要求,我们提供两种方案: 方案1:使用Unity内置的RecalculateNormals()(简单,适用于一般平滑效果)。 方案2:手动计算平均法线(更灵活,但代码较多)。 我们先构建一个基本的Mesh构建类,包含: - 顶点数组(Vector3[] vertices) - 三角形数组(int[] triangles) - 法线数组(Vector3[] normals) - 初始化时构建网格并设置MeshFilter和MeshRenderer。 然后提供动态改变顶点的方法,并在改变后更新法线。 步骤: 1. 创建Mesh对象并设置顶点和三角形。 2. 计算法线(使用内置方法或自定义方法)。 3. 将网格应用于MeshFilter。 注意:动态改变形状时,我们可能会修改顶点数组,然后重新计算法线。 引用[3]提到法线对于渲染、照明等非常重要,所以正确设置法线很关键。 下面是一个示例类,包含: - 初始化网格(例如生成一个平面) - 修改顶点的方法 - 重新计算法线(可选择内置或自定义) 根据引用[2],我们也可以选择手动计算法线。这里我们提供两种方式,并通过一个标志位选择。 注意:手动计算法线需要遍历三角形,计算每个面的法线,然后对每个顶点的所有相邻面的法线求平均。 由于用户要求动态改变形状,我们假设顶点位置会变化,但拓扑结构(三角形连接)不变?或者也可能改变?这里我们先假设三角形索引不变,只有顶点位置变化。 如果拓扑结构也动态改变,那么三角形数组也需要更新,但法线计算同理。 代码结构: ```csharp using UnityEngine; [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class DynamicMesh : MonoBehaviour { private Mesh mesh; private Vector3[] vertices; private int[] triangles; private Vector3[] normals; // 是否使用Unity内置的法线计算(否则使用自定义计算) public bool useUnityRecalculateNormals = true; void Start() { GetComponent<MeshFilter>().mesh = mesh = new Mesh(); mesh.name = "Dynamic Mesh"; // 初始化网格数据(例如创建一个平面) GenerateInitialMesh(); UpdateMesh(); } // 生成初始网格(示例:一个四边形平面) private void GenerateInitialMesh() { // 4个顶点 vertices = new Vector3[4]; vertices[0] = new Vector3(0, 0, 0); vertices[1] = new Vector3(1, 0, 0); vertices[2] = new Vector3(0, 1, 0); vertices[3] = new Vector3(1, 1, 0); // 两个三角形(顺时针或逆时针,注意Unity是顺时针为正面) triangles = new int[] { 0, 2, 1, 1, 2, 3 }; // 法线数组初始化为零,后面会计算 normals = new Vector3[4]; } // 更新网格(在修改顶点后调用) public void UpdateMesh() { mesh.Clear(); mesh.vertices = vertices; mesh.triangles = triangles; // 计算法线 if (useUnityRecalculateNormals) { mesh.RecalculateNormals(); // 从mesh中取回计算后的法线(如果我们后续需要访问) normals = mesh.normals; } else { normals = CalculateNormals(); mesh.normals = normals; } // 如果需要,也可以重新计算切线等 } // 自定义法线计算:计算每个顶点的法线(通过相邻面的平均) private Vector3[] CalculateNormals() { // 初始化法线数组(每个顶点一个法线,初始为Vector3.zero) Vector3[] vertexNormals = new Vector3[vertices.Length]; // 遍历每个三角形(每3个索引一个三角形) for (int i = 0; i < triangles.Length; i += 3) { int index0 = triangles[i]; int index1 = triangles[i + 1]; int index2 = triangles[i + 2]; Vector3 v0 = vertices[index0]; Vector3 v1 = vertices[index1]; Vector3 v2 = vertices[index2]; // 计算三角形面的法线(叉乘) Vector3 normal = Vector3.Cross(v1 - v0, v2 - v0).normalized; // 将这个面的法线加到三个顶点的法线上 vertexNormals[index0] += normal; vertexNormals[index1] += normal; vertexNormals[index2] += normal; } // 归一化每个顶点的法线(得到平均方向) for (int i = 0; i < vertexNormals.Length; i++) { vertexNormals[i] = vertexNormals[i].normalized; } return vertexNormals; } // 示例:动态改变顶点位置(例如根据噪声或物理模拟) public void ModifyVertices() { // 这里只是一个示例:随机移动顶点(实际应用中可能是更复杂的形变) for (int i = 0; i < vertices.Length; i++) { vertices[i] += Random.insideUnitSphere * 0.1f; } // 更新网格 UpdateMesh(); } // 在Inspector中提供一个按钮来测试修改顶点 [ContextMenu("Modify Vertices")] public void ModifyVerticesContextMenu() { ModifyVertices(); } } ``` 说明: - 在Start中,我们创建初始网格(一个平面四边形),然后调用UpdateMesh来设置网格并计算法线。 - 我们提供了两种计算法线的方式:使用Unity内置的RecalculateNormals(默认)或自定义的CalculateNormals方法(遍历三角形,计算每个面的法线并累加到顶点,然后归一化)。 - 动态修改顶点后,调用UpdateMesh更新网格和法线。 注意: - 自定义法线计算中,我们假设网格是连续的(即共享顶点)。如果网格中有不连续的顶点(例如硬边),那么需要特殊处理(比如复制顶点),但这里我们只考虑平滑网格。 - 内置的RecalculateNormals方法也会考虑顶点共享的情况,所以对于平滑网格,使用内置方法即可。 用户可以根据需求选择使用内置方法还是自定义方法。如果希望实现硬边(不平滑),可能需要不同的顶点法线(例如,在硬边处,同一个位置有多个顶点,每个顶点属于不同的面,法线不同)。 关于动态改变形状:这里只是简单示例(随机移动顶点),实际应用中可能是根据物理模拟、函数变形等。 请根据实际项目需求调整初始网格的生成和顶点修改逻辑。 参考引用: [^1]: 引用1说明需要提供法线数组,并提到平滑法线需要平均相邻面的法线。 [^2]: 引用2提到可以使用mesh.RecalculateNormals()自动计算法线,并解释了其原理(计算与顶点相连的三角面法线的平均值并归一化)。 [^3]: 引用3强调了法线在渲染和光照中的重要性。 完善点: - 我们实现了动态改变顶点后更新法线(内置方法或自定义方法)。 - 我们提供了自定义法线计算的代码,以便用户需要更精细控制时使用。 如果用户需要硬边(非平滑)的效果,则需要复制顶点(使每个三角形使用独立的顶点),然后为这些顶点分别设置法线(即三角形面的法线)。但这不是当前问题的重点。 因此,以上代码应该能够满足用户对动态改变形状和法线平均化的需求。 §§相关问题§§ 1. 在Unity中,如何为具有硬边的网格计算法线? 2. 动态修改网格顶点时,如何优化性能? 3. 除了法线,动态网格还需要更新哪些属性以保证正确渲染? </think> 以下是针对 Unity 项目中 Mesh 构建、动态变形和法线平均化的代码检查与完善方案,整合了引用中的关键信息: ### 核心代码实现(含注释说明) ```csharp using UnityEngine; [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class DynamicMesh : MonoBehaviour { private Mesh mesh; private Vector3[] vertices; private Vector3[] normals; void Start() { // 初始化网格组件 GetComponent<MeshFilter>().mesh = mesh = new Mesh(); mesh.name = "Dynamic Mesh"; // 1. 基础网格构建(示例:平面) GenerateBaseMesh(); // 2. 动态更新顶点位置(示例:正弦波动) InvokeRepeating(nameof(UpdateShape), 0.1f, 0.1f); } // 生成基础网格(含UV和法线初始化) private void GenerateBaseMesh() { // 顶点设置(4个顶点构成平面) vertices = new Vector3[4] { new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(0, 1, 0), new Vector3(1, 1, 0) }; // 三角形索引(两个三角面) int[] triangles = new int[6] { 0, 2, 1, // 第一个三角形 1, 2, 3 // 第二个三角形 }; // UV坐标(必须设置) Vector2[] uv = new Vector2[4] { new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 1), new Vector2(1, 1) }; mesh.vertices = vertices; mesh.triangles = triangles; mesh.uv = uv; // 3. 法线计算(平滑平均) CalculateSmoothNormals(); // 使用自定义平滑法线 // mesh.RecalculateNormals(); // 或使用Unity内置方法[^2] } //=== 关键完善点1:平滑法线计算 === private void CalculateSmoothNormals() { // 初始化法线数组(与顶点一一对应)[^1] normals = new Vector3[vertices.Length]; // 步骤1:计算每个三角面的法线 Vector3[] faceNormals = new Vector3[mesh.triangles.Length / 3]; for (int i = 0; i < mesh.triangles.Length; i += 3) { int idx0 = mesh.triangles[i]; int idx1 = mesh.triangles[i + 1]; int idx2 = mesh.triangles[i + 2]; Vector3 v1 = vertices[idx1] - vertices[idx0]; Vector3 v2 = vertices[idx2] - vertices[idx0]; faceNormals[i / 3] = Vector3.Cross(v1, v2).normalized; } // 步骤2:顶点法线平均化[^1] for (int i = 0; i < vertices.Length; i++) { Vector3 normalSum = Vector3.zero; int count = 0; // 遍历所有三角形,找到共享当前顶点的面 for (int j = 0; j < mesh.triangles.Length; j++) { if (mesh.triangles[j] == i) { int faceIndex = j / 3; normalSum += faceNormals[faceIndex]; count++; } } // 计算平均值并单位化[^3] normals[i] = (count > 0) ? (normalSum / count).normalized : Vector3.up; } mesh.normals = normals; // 应用法线 } //=== 关键完善点2:动态改变形状 === private void UpdateShape() { // 动态修改顶点位置(示例:Y轴正弦波动) for (int i = 0; i < vertices.Length; i++) { float wave = Mathf.Sin(Time.time + i * 0.5f); vertices[i].y = wave * 0.2f; // 振幅控制 } mesh.vertices = vertices; // 更新顶点 mesh.RecalculateBounds(); // 重计算边界 // 动态更新法线(必须执行!) CalculateSmoothNormals(); // 若使用内置方法:mesh.RecalculateNormals()[^2] } } ``` ### 关键完善说明 1. **法线平均化优化** - 手动实现相邻面法线加权平均(`CalculateSmoothNormals`方法) - 替代方案:直接调用 `mesh.RecalculateNormals()`(Unity 内置的面法线平均算法)[^2] - **注意**:两种方法都要求顶点位置更新后**必须重新计算法线**,否则光照错误 2. **动态变形要点** - 修改 `vertices` 数组后需重新赋值给 `mesh.vertices` - 必须调用 `RecalculateBounds()` 更新碰撞检测范围 - UV 坐标在初始化后无需修改(除非做纹理动画) 3. **性能优化** ```csharp // 在UpdateShape()开头添加(减少GC) mesh.MarkDynamic(); // 声明为动态网格[^2] // 避免每帧创建新数组 vertices = mesh.vertices; // 直接获取引用 ``` 4. **探针与光照兼容** ```csharp // 确保MeshRenderer正确响应光照 GetComponent<MeshRenderer>().lightProbeUsage = LightProbeUsage.BlendProbes; GetComponent<MeshRenderer>().reflectionProbeUsage = ReflectionProbeUsage.BlendProbes; ``` ### 验证要点 1. **法线方向验证** - 在Scene视图开启 **Face Culling**,确认法线朝向一致 - 使用Debug.DrawRay绘制顶点法线辅助检查 2. **平滑度测试** - 对比 `mesh.RecalculateNormals()` 和自定义算法的平滑效果 - 硬边处理需分割顶点(非平均法线) > 引用说明:[^1] 手动计算法线需顶点与法线一一对应;[^2] `RecalculateNormals()` 使用面法线平均值;[^3] 单位化法线保证光照计算正确。
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值