2.Square Grid

1.Procedural Mesh Job Framework

2.A Grid of Quads


1.Procedural Mesh Job Framework

csharp 复制代码
1).通用顶点

定义一个通用的Vertex结构体来保存顶点数据
csharp 复制代码
将顶点结构放在一个命名空间namespace(ProceduralMeshs)中
csharp 复制代码
2).网格流

a.为了存储网格数据, 我们需要定义"顶点缓冲区和索引缓冲区"

b.这次, 我们通过一个接口ProceduralMeshes.IMeshStreams负责"设置顶点缓冲区和索引缓冲区", 对外隐藏细节
csharp 复制代码
它的首要职责是初始化网格数据, 我们通过一个Setup方法, 以网格数据, 顶点数和索引数作为参数
csharp 复制代码
将顶点的数据写入到顶点缓冲区中
csharp 复制代码
将索引写入到索引缓冲区中, 使用三角形比使用单独的索引更方便, 我们将定义一个SetTriangle方法, 以三角形索引和一个

int3顶点索引三元组作为参数
csharp 复制代码
该接口最直接的是单流方法, 我们将此类型命名为SingleStream, 它必须是"一个结构体才能与Burst作业"配合使用; 我们

将流实现分在ProceduralMeshes.Streams嵌套空间中
csharp 复制代码
添加Setup使用它来定义"网格的缓冲区", 立即设置子网格, 暂不考虑其边界
csharp 复制代码
我们引入一个新的私有结构体Stream0, 这样外部的Vertex应对需求变化, 增加新的字段时, 不会影响到这里的逻辑; 通过附

加StructLayout(LayoutKind.Sequential)属性来确保字段顺序固定
csharp 复制代码
SetVertex将顶点数据写入到顶点缓冲区中
csharp 复制代码
如果SetVertex方法变得复杂多, 比如: 将部分数据存储为16位值, 则需要转换; 这种情况下Burst可能不会对该方法进行内

联, 而是每次设置顶点时插入一条调用指令, 该方法速度较慢; 必须指定该方法可以被Burst进行内联, 可以给方法添加特性

[MethodImpl(MethodImplOptions.AggressiveInlining)]
csharp 复制代码
最后, 我们可以通过将索引数据重新解释为int3 triangle数据
csharp 复制代码
3).网格生成器

我们通过另一个接口Procedural.IMeshGenerator负责"生成网格的代码", 它定义了Job执行的代码, 因此需要有一个带有

index参数的Execute方法; 还需要一个IMeshStreams类型的参数
csharp 复制代码
生成网格, 需要知道网格的顶点数目和索引数目
csharp 复制代码
调度任务时还需要知道任务长度(数目)
csharp 复制代码
定义方形网格, 我们通过SquGrid实现该接口, 其中的参数目前保持默认值


csharp 复制代码
4).网格Job

定义一个Job System中Job来生成网格, 引入ProceduralMeshes.MeshJob类型; 这是一个泛型IJobFor结构体, 具有网格相

关的参数: IMeshGenerator和IMeshStreams参数; 其Execute方法调用直接转发给生成器, 传入索引和流


csharp 复制代码
由于我们生成网格时写入流而不从中读取, 因此可以添加WriteOnly属性, 间接将只写状态应用于IMeshStreams所实现包含

的原生数组
csharp 复制代码
最后创建一个静态方法ScheduleParallel, 该方法返回的参数是任务句柄, 该方法需要网格数据和任务依赖项作为参数
csharp 复制代码
5).创建程序化网格脚本(Component)
csharp 复制代码
Awake中创建网格对象, 生成网格, 并将其赋给MeshFilter; 将网格生成的代码放在单独的GenerateMesh方法
csharp 复制代码
分配网格可写的网格数据, 调用MeshJob中的ScheduleParallel方法, 使用SquareGrid和SingleStram类型


csharp 复制代码
6.生成一个四边形

由于我们未设置 SquareGrid.JobLength的数目, 因此Job没有被调度, 因此得到是一个空网格
csharp 复制代码
设置Job的数目是1
csharp 复制代码
进入播放模式时, 我们会收到一个无效操作异常, 提示两个容器可能是同一个对象; 提示两个容器可能是同一个对象, 这指

的是SingleStream的两个原生数组; Unity抱怨它们可能存在别名, 这意味着这些原生数组可能代表重叠的数据

原因是所有网格数据都是一个单一的非托管内存块, 我们的任务试图同时访问该数据的两个子部分------顶点部分和三角形索引部

分------而Unity不允许这样做, 因为这可能导致错误的结果
csharp 复制代码
通常, Unity的安全检查是有效的, 应当予以遵守; 但在本例中, 我们确定顶点和索引数据绝不会重叠; 因此, 我们将通过将

Unity.Collections.LowLevel.Unsafe命名空间中的"NativeDisableContainerSafetyRestriction"属性附加到两个原生数组

字段来禁用安全检查
csharp 复制代码
为了测试我们的框架, 我们首先生成一个四边形; 顶点数目为4
csharp 复制代码
四边形网格生成器中, 创建一个通用的顶点数据, 设置其法线和切线向量, 这些向量对所有顶点都是相同的; 由于所有值都

初始化为零, 我们只需设置非零向量即可

注:

"我们跳过了逐顶点处理的常规循环, 直接一次性生成一个四边形(两个三角形)的所有顶点数据; 为了实现这种高效操作," 

"我们关闭了Unity的安全检查系统"


csharp 复制代码
设置索引的数目为6, Excute的末尾设置两个三角形
csharp 复制代码
我们运行的时候还会出现一个参数异常的错误, 当在 SingleStream.Setup 中设置子网格时会发生这种情况; 当我们调用

SetSubMesn的时候, 会立即验证三角形索引并重新计算边界, 这肯定会失败, 因为任务尚未运行, 索引缓冲区包含的是任意

数据; 我们必须提供MeshUpdateFlags来指示SetSubMesh不应对数据执行任何操作 

"DontRecalculateBounds和DontValidateIndices(不要校验索引)" 
csharp 复制代码
7.Bounds 边界

我们的网格现在缺少的就是有效的边界, 网格生成器应该提供这些边界, 因此向IMeshGenerator接口添加一个属性来获取它
csharp 复制代码
SquareGrid中实现
csharp 复制代码
要设置边界, 为网格向MeshJob.SchduleParallel添加一个参数, 将其设为第一个参数; 可以在创建Job后立即设置网格边界
csharp 复制代码
还需要设置子网格的边界, 需要将边界作为第二个参数添加到IMeshStreams.Setup中
csharp 复制代码
调整SingleStream.Setup, 设置子网格的边界和顶点数
csharp 复制代码
直接使用网格边界赋值表达式的结果作为 Setup 的参数
csharp 复制代码
8).16位索引

将三角形索引从32位减少到16位, 这样索引缓冲区的大小减半; 在ProceduralMeshes.Streams命名空间中定义一个新的类型

"TriangleUInt16"
csharp 复制代码
更改三角形索引元素类型和索引缓冲区格式, 即可将SingleStream切换为16位索引
csharp 复制代码
9).多顶点流

创建一个新的类MultiStream, 将单一流替换为用于各个顶点属性的四个流

2.A Grid of Quads

csharp 复制代码
现在我们已经有了一个可用的框架, 接下来我们将生成一个由多个四边形组成的网格, 这些四边形被排列成一个规则的方形网

格

csharp 复制代码
1).网格分辨率

IMeshGenerator中添加一个属性Resolution, 表示网格的分辨率, 用于生成Resolution * Resolution的方形网格
csharp 复制代码
SquareGrid中实现该属性
csharp 复制代码
顶点数量, 索引数量, 任务长度现在取决于分辨率的平方
csharp 复制代码
向MeshJob.ScheduleParallel添加分辨率参数, 创建作业后使用它来设置生成器的分辨率
csharp 复制代码
然后向ProceduralMesh添加一个分辨率滑块, 并在生成网格时使用


csharp 复制代码
2).生成所有四边形

在Execute开始时确定正确的顶点和三角形索引, 传递给Execute的作业索引代表"四边形索引"; 其第一个顶点索引是该索引

的四倍, 第一个三角形索引是该索引的两倍
csharp 复制代码
设置顶点数据
csharp 复制代码
设置索引数据
csharp 复制代码
Execute中传递的参数是四边形的索引, 我们还需要确定当前的四边形在整个范围的具体位置, 比如说: 第几行, 第几列

因此将一维索引转换为二维索引
csharp 复制代码
使用一个float4值定义四边形所需的全部四个左边, 包括x, x + 1, y, y + 1; 刚开始使用0.9, 在四边形之间流出可见的

间隙
csharp 复制代码
对坐标进行重排操作来设置正确位置
csharp 复制代码
3).一个平面

网格通常用于平面, 因此调整网格, 使其位于XZ平面; 将y重命名为z, 同时闭合四边形的间隙
csharp 复制代码
通过将网格方向分配给顶点位置的XZ分量而非XY分量来改变其朝向
csharp 复制代码
更改法线向量, 使其指向上方
csharp 复制代码
让平面以原点为中心, 比如: (0, 2) -> (-0.5, 0.5)
csharp 复制代码
调整边界
csharp 复制代码
4).以行为单位生成四边形

我们当前的任务是独立生成网格中的每个四边形, 创建单个四边形的工作量不大, 每个四边形都必须单独计算, Unity的任务

框架还会增加额外的开销; 我们可以通过在单次调用中合并生成多个四边形来提高效率, 将单行的所有四边形一起生成是最合

理的做法; "这将使任务长度等于分辨率, 而不再是其平方"
csharp 复制代码
让每次Execute调用负责处理沿X轴的一整行四边形, 那么Z表示"第几行"
csharp 复制代码
现在我们不再使用固定的X偏移, 而是为整个行引入一个循环, 该循环包含了填充流的代码
csharp 复制代码
每次循环迭代后, 我们必须将顶点索引增加四, 将三角形索引增加二
csharp 复制代码
Burst可以检测循环内永不改变的代码, 并自动将其提取到循环外; 然而, 它不会拆分向量, 因此我们可以通过手动将坐标向

量拆分为独立的X和Z对来进行一些优化; Z坐标的计算是常量, 因此将被提升到循环外
相关推荐
qq_3994071811 小时前
2025年Unity国际版下载及安装
unity·游戏引擎
鹿野素材屋14 小时前
Unity做出果冻胸部的效果
unity·游戏引擎
两水先木示14 小时前
【Unity】坐标转换(屏幕坐标、世界坐标、UI坐标)
unity·游戏引擎·空间转换
老朱佩琪!16 小时前
Unity模板方法模式
unity·游戏引擎·模板方法模式
又来07717 小时前
Unity手柄按键映射表
unity·游戏引擎
dzj202118 小时前
Unity的旁门左道用法(科学计算):用shader调用GPU做k线MA5的计算——DuckDB + Compute Shader
unity·金融·gpu·shader·量化·compute shader
世洋Blog18 小时前
Unity使用Luban的一些常用点
unity·游戏策划·luban
nnsix1 天前
Unity的dllType Native和Managed的区别
unity·游戏引擎
Clank的游戏栈1 天前
AI游戏开发全自动编程课程体系(Cursor版,支持Unity/Cocos, Laya后续支持)
人工智能·unity·游戏引擎
鹿野素材屋1 天前
技术闲聊:为什么网游会在固定时间点,刷出固定的道具?
前端·网络·unity