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坐标的计算是常量, 因此将被提升到循环外
