keil 生成三角波dac0832_UnityMesh编程(1) 使用代码生成网格

Mesh 翻译文章汇总

AmyBoy:Unity Mesh Basics(Unity Mesh基础)系列翻译汇总​zhuanlan.zhihu.com

原作者:Jasper Flick

由于水平有限,可能翻译的会有错误,请大家在评论区指出,我会及时更新改正。

这是篇译文,附上原文链接

https://catlikecoding.com/unity/tutorials/procedural-grid/​catlikecoding.com

本教程的目标

  • 创建网格点
  • 使用协程来分析它们的位置
  • 用三角形定义一个曲面
  • 自动生成法线
  • 添加纹理坐标和切线

在本教程中,我们将创建一个简单的顶点和三角形网格。

本教程是采用Unity2018.4.1版本。

acde80c6-fe14-eb11-8da9-e4434bdf6706.png
在复杂的外表之下隐藏着简单的几何形状

1 渲染事物

如果要在Unity中可视化某些内容,你需要使用到网格。这些网格可能是从另一个程序导出的3D模型,也可能是通过代码生成的网格。 它可能是Sprite,UI元素或粒子系统,Unity也为此使用了网格。甚至屏幕效果都用网格渲染。

那什么是网格呢?从概念上讲,网格是图形硬件用来绘制复杂图形的结构。它至少包含一组定义3D空间中的点的顶点集合,以及一组连接这些点的三角形(最基本的2D形状)。三角形构成网格所代表的任何表面。

由于三角形是平的,而且有直边,所以它们可以用来完美地显示平直的东西,就像立方体的面一样。曲面或圆面只能用许多小三角形拼接近似的表示。如果三角形看起来足够小(不超过一个像素)那么你就不会注意到这个近似值。通常这对于实时性能来说是不可行的,因此表面总是在某种程度上呈现锯齿状。

b1de80c6-fe14-eb11-8da9-e4434bdf6706.png

b3de80c6-fe14-eb11-8da9-e4434bdf6706.png
unity的默认Cube,capsule和sphere,阴影 与线框
如何在unity显示线框图 选择场景视图在其工具栏左侧的显示模式。前三个选项是阴影线框和阴影线框。

如果你想让一个游戏对象显示一个3D模型,它需要两个组件。第一个是MeshFilter。 此组件包含对要显示的网格的引用。 第二个是MeshRender。 你可以使用它来配置网格渲染的方式,应该使用哪种材质、是否应该反射或接收阴影,等等。

b6de80c6-fe14-eb11-8da9-e4434bdf6706.png
Unity 的默认Cube游戏对象
为什么会有一列的材质? 一个MeshRender(网格渲染器)可以有多个材质。这主要用于渲染具有多个独立三角形集的网格,称为子网格。这些主要用于导入的3D模型,本教程中不会介绍。

你可以通过调整材质来完全改变网格的外观。Unity的默认材质是纯白色。你可以通过 Assets /Create/Material 创建一个新的材质,然后拖拽到你的游戏对象上,用你自己的材质替换默认材质。新的材质默认使用 Unity 的标准着色器,它提供一组控件来调整表面的视觉效果。

为网格添加大量细节的一个快速方法是提供一个反照贴图。这是一种纹理,代表了一种材料的基本颜色。当然,我们需要知道如何将这个纹理投影到网格的三角形上。这是通过向顶点添加2D纹理坐标来实现的。纹理空间的两个维度被称为u和v,这就是为什么它们被称为UV坐标的原因。这些坐标通常位于(0,0)和(1,1)之间,它们覆盖了整个纹理。超出这个范围的坐标要么被限制,要么导致平铺,这取决于纹理设置。

b9de80c6-fe14-eb11-8da9-e4434bdf6706.png
一个 UV 测试纹理应用到Unity的网格

2 创建顶点网格

那么,如何制作自己的网格呢? 让我们通过生成一个简单的矩形网格来找出答案。网格将由单位长度的正方形块(四边形)组成。创建一个新的c#脚本,并将其转换为一个具有水平和垂直大小的网格组件。

using 

当我们把这个组件添加到一个游戏对象,我们需要给它一个MeshFilter和MeshRender。我们可以添加一个属性到类中,让Unity自动为我们添加它们。

[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]

现在你可以创建一个新的空游戏对象,并将网格组件添加到其中,它还将拥有其他两个组件。设置渲染器的材质,保持MeshFilter的网格未定义。我将网格的大小设置为10×5。

bdde80c6-fe14-eb11-8da9-e4434bdf6706.png
一个网格对象

当我们进入播放模式时,Grid脚本加载时,我们就生成实际的网格。

private 

让我们先关注顶点位置,稍后再讨论三角形。我们需要一个三维向量数组来存储这些点。顶点的数量取决于网格的大小。我们需要在每个四边形的角上有一个顶点,但是相邻的四边形可以共享同一个顶点。因此,面上的每个维度上的顶点数都会多一个。

(#x+1)(#y+1)

c0de80c6-fe14-eb11-8da9-e4434bdf6706.png
4 * 2网格的顶点和每个面四个顶点索引
private 

让我们把这些顶点可视化,这样我们就可以检查它们的位置是否正确。 我们可以通过添加 OnDrawGizmos 方法并在场景视图中为每个顶点绘制一个小黑球来实现。

private 
什么是Gizmos?
Gizmos是可以在编辑器中使用的可视化提示。默认情况下,它们在场景视图中可见,而在游戏视图中不可见,但你可以通过它们的工具栏进行调整。Gizmos实用工具类允许你绘制图标、线条和其他一些东西。
可以在OnDrawGizmos方法中绘制Gizmos,该方法由Unity编辑器自动调用。另一种方法是OnDrawGizmosSelected,它只对选定的对象调用。

这将产生错误,当我们不在播放模式,因为OnDrawGizmos方法也被调用时,Unity在编辑模式,当我们没有任何顶点。要防止此错误,请检查数组是否存在,如果不存在则跳出方法。

private 

c3de80c6-fe14-eb11-8da9-e4434bdf6706.png
一个Gizmo

在播放模式中,我们在原点只能看到一个球体。这是因为我们还没有给顶点定位,所以它们都在那个位置重叠。我们必须使用双循环遍历所有位置。

private 
为什么Gizmos不会随着物体移动?
Gizmos是直接在世界空间中绘制的,而不是在对象的局部空间中。如果你想让它们根据你的对象变化位置,你必须通过使用transform. transformpoint(vertices[i])来声明,代替vertices[i]。

我们现在可以看到顶点,但是它们被放置的顺序是不可见的。 我们可以使用颜色来显示这一点,但是我们也可以通过使用协程来减缓这一过程。

private 

3 创建网格

现在我们知道顶点的位置是正确的,我们可以处理实际的网格。 除了在我们自己的组件中保存对它的引用之外,我们还必须将它分配给MeshFilter。一旦我们处理了顶点,我们就可以把它们放到网格里。

private 
我们的组件是否需要持有网格的引用?
我们只需要在Generate方法中引用网格。但由于MeshFilter组件也有对网格引用,因此无论如何都需要将其持久化保存。我现在将其设置为全局变量,但因为本教程下一个逻辑步骤是对网格进行动画处理,所以我鼓励你先尝试一下将网格持久化。
ps:这的意思就是MeshFilter 在播放模式下才会有对网格的引用,这里将网格持久化保存,就是使MeshFilter组件在编辑模式也持有网格的引用。

c7de80c6-fe14-eb11-8da9-e4434bdf6706.png
网格在播放模式下出现

现在,我们在播放模式下有一个网格,但是它还没有显示出来,因为我们没有给它任何三角形。三角形是通过顶点索引数组定义的。由于每个三角形都有三个点,因此三个连续的索引描述一个三角形。让我们从一个三角形开始。

private 

现在我们有一个三角形,但是我们正在使用的三个点全部位于一条直线上。这将生成一个不可见的退化三角形。前两个顶点很好,但是接下来我们应该跳到下一行的第一个顶点。

triangles

这确实给了我们一个三角形,但仅从一个方向可见。在这种情况下,仅当沿Z轴的相反方向看时才可见。因此,您可能需要旋转视图才能看到它。

三角形从哪一侧可见是由其顶点索引的方向确定的。默认情况下,如果它们按顺时针方向排列,则该三角形被认为是朝前的且可见。逆时针三角形被丢弃,因此我们不需要花时间渲染对象的内部,因为它们本来就不应该被看到。

c9de80c6-fe14-eb11-8da9-e4434bdf6706.png
三角形的两条面

因此,当我们从z轴反方向(ps:就是默认unity方向)看时,要使三角形出现,我们必须更改其顶点的遍历顺序。我们可以通过交换最后两个索引来实现。

(ps:这里索引1与索引2交换位置,是受顶点位置的生成顺序影响,如下图,上面的点的对应顶点数组的索引为xSize+1,但按照顺时针顺序对应三角形对应索引为1。)

triangles

cfde80c6-fe14-eb11-8da9-e4434bdf6706.png
第一个三角形

现在,我们有一个三角形,覆盖了网格第一个图块的一半。要覆盖整个图块,我们需要的是第二个三角形。

int

d2de80c6-fe14-eb11-8da9-e4434bdf6706.png
由两个三角形组成的四边形

由于这些三角形共享两个顶点,我们可以将其减少到四行代码,只显式地声明每个顶点索引一次。

triangles

d5de80c6-fe14-eb11-8da9-e4434bdf6706.png
第一个图块

通过将其变成一个循环,我们可以创建整个第一行图块。在迭代顶点和三角形索引时,我们必须同时跟踪两者。让我们也将yield语句移入此循环,这样我们就不必等待顶点出现了。

int

现在将会显示所有的顶点,并且在短暂的等待后显示全部的三角形。为了看到图块一个接一个地出现,我们必须在每次迭代时更新网格,而不是仅在循环之后更新。

mesh

现在,通过将单循环变成双循环来填充整个网格。请注意,移至下一行需要将顶点索引增加一个。

int

dade80c6-fe14-eb11-8da9-e4434bdf6706.png
填满整个网格

正如你所看到的,整个网格现在充满了三角形,一次一行。 如果你对此效果满意,你可以删除所有的协同代码,这样网格将毫不延迟地被创建。

private 
为什么不使用一个四边形?
当我们创建一个扁平的矩形表面时,只要两个三角形就足够了。这是绝对正确的。更复杂的结构的要点是它允许更多的控制和表达。

4 生成附加的顶点数据

当前,我们的网格以特殊方式显示。那是因为我们还没有给网格赋予任何法线。默认的法线方向为(0,0,1),与我们所需的方向完全相反。

法线是如何工作的?
法向量是垂直于曲面的向量。我们总是使用单位长度的法线它们指向表面的外面,而不是里面。
法线可以用来确定光线照射表面的角度(如果有的话)。具体如何使用取决于着色器。
由于三角形总是平坦的,因此不需要提供有关法线的单独信息。然而,这样做我们可以作弊。实际上,顶点没有法线的,但三角形有。通过将自定义法线附加到顶点并在三角形之间进行插值,我们可以假装我们拥有一个平滑的曲面而不是一堆三角形。只要不注意网格的鲜明轮廓,这种现象就可以让人相信。

每个顶点都定义了法线,因此我们必须填充另一个矢量数组。或者,我们可以要求网格根据其三角形计算出法线本身。

private 
如何重新计算法线?
Mesh.RecalculateNormals方法通过确定哪些三角形与该顶点连接,确定那些三角形的法线,对其求平均并对结果进行归一化,来计算每个顶点的法线。

dede80c6-fe14-eb11-8da9-e4434bdf6706.png
没有法线与有法线

接下来是UV坐标。你可能已经注意到,尽管网格使用反照率纹理的材料,但当前具有统一的颜色。这是很容易理解,因为如果我们自己不提供UV坐标,那么它们全为零。

要使纹理适合整个网格,只需将顶点的位置除以网格尺寸即可。

vertices 

e1de80c6-fe14-eb11-8da9-e4434bdf6706.png
不正确的uv坐标、加紧与wrapping

纹理现在显示出来,但它没有覆盖整个网格。它的准确外观取决于纹理的填充模式是设置为clamp还是repeat。这是因为我们现在用整数除整数,结果是另一个整数。为了在整个网格中得到0和1之间的正确坐标,我们必须确保使用浮点数。

uv

纹理现在被投影到整个网格上。 当我将网格的大小设置为10 × 5时,纹理将显示为水平拉伸。 这可以通过调整材质的填充方式设置来解决。 将其设置为(2,1) ,u 坐标将加倍。 如果纹理被设置为repeat,那么我们将看到它的两个方块。

e4de80c6-fe14-eb11-8da9-e4434bdf6706.png
正确的UV坐标与平铺

e9de80c6-fe14-eb11-8da9-e4434bdf6706.png
平铺效果Tilling(1,1)与重复效果Tilling(2,1)

另一种增加表面细节的方法是使用法线贴图。这些贴图包含用颜色表示的法向量。将它们应用到一个表面上将会产生比仅使用顶点法线创建更详细的光效果。

ecde80c6-fe14-eb11-8da9-e4434bdf6706.png
凹凸不平的表面,具有金属感,具有戏剧性的效果。

将这些材料应用到我们的网格中并没有给我们带来任何波动的感觉。 我们首先需要给网格添加切向量。

切线是如何工作的?
法线贴图在切线空间中定义。这是一个围绕对象表面流动的3D空间。这种方法使我们可以在不同的位置和方向上应用相同的法线贴图。
表面法线在此空间中表示向上,但是哪个方向是正确的?那是由切线定义的。理想情况下,这两个向量之间的角度为90°。它们的叉积产生定义3D空间所需的第三方向。实际上,角度通常不是90°,但结果仍然足够好。
因此,切线是3D向量,但是Unity实际上使用4D向量。它的第四个分量总是-1或1,用来控制第三个切线空间的方向-向前或向后。这有助于法线贴图的镜像,法线贴图通常用于具有双边对称性的事物(例如人)的3D模型中。 Unity着色器执行此计算的方式要求我们使用-1。

因为我们有一个平面,所以所有切线都指向同一方向,即右边。

eede80c6-fe14-eb11-8da9-e4434bdf6706.png
凹凸不平的平面

网格除了需要顶点位置和三角形外,通常也需要UV坐标(最多四组)以及切线。你也可以添加顶点颜色,尽管Unity的标准着色器不使用这些颜色。

Unity包​catlikecoding.com Amy6922/UnityMeshDemo​github.com
f1de80c6-fe14-eb11-8da9-e4434bdf6706.png

现在,你知道了如何创建一个简单的网格来形成一个面,那么接下来试试,组合这些面来创建一个3D的物体吧。

AmyBoy:Unity Mesh编程(2) 使用代码生成圆角立方体​zhuanlan.zhihu.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
首先,你需要连接一个正弦波信号源到STM32H750的ADC输入引脚上。然后,你需要使用Keil软件创建一个新的工程,并选择适当的芯片型号和编译器。接下来,你需要配置ADC模块以获取输入信号并将其转换为数字值。下面是一些参考代码: ```c #include "stm32h7xx.h" // ADC配置 void ADC_Configuration(void) { ADC_HandleTypeDef hadc1; // 使能ADC1钟 __HAL_RCC_ADC12_CLK_ENABLE(); // 初始化ADC配置结构体 hadc1.Instance = ADC1; hadc1.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1; hadc1.Init.Resolution = ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode = DISABLE; hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DiscontinuousConvMode = DISABLE; hadc1.Init.NbrOfDiscConversion = 0; hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc1.Init.NbrOfConversion = 1; hadc1.Init.DMAContinuousRequests = DISABLE; hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV; if (HAL_ADC_Init(&hadc1) != HAL_OK) { // ADC初始化失败,进行错误处理 } // 配置ADC通道 ADC_ChannelConfTypeDef sConfig = {0}; sConfig.Channel = ADC_CHANNEL_0; sConfig.Rank = ADC_REGULAR_RANK_1; sConfig.SingleDiff = ADC_SINGLE_ENDED; sConfig.SamplingTime = ADC_SAMPLETIME_640CYCLES_5; sConfig.OffsetNumber = ADC_OFFSET_NONE; sConfig.Offset = 0; if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) { // ADC通道配置失败,进行错误处理 } } // 获取ADC采样值 uint16_t ADC_GetSample(void) { ADC_HandleTypeDef hadc1; hadc1.Instance = ADC1; // 启动ADC转换 HAL_ADC_Start(&hadc1); // 等待转换完成 HAL_ADC_PollForConversion(&hadc1, 100); // 读取采样值 uint16_t adc_value = HAL_ADC_GetValue(&hadc1); return adc_value; } int main(void) { // 初始化系统钟和其他外设 SystemInit(); // 配置ADC ADC_Configuration(); // 循环读取ADC采样值 while (1) { // 获取ADC采样值 uint16_t adc_value = ADC_GetSample(); // 计算峰峰值 // TODO: 计算峰峰值 // 计算频率 // TODO: 计算频率 } } ``` 在代码,`ADC_Configuration`函数用于配置ADC模块,包括钟源、分辨率、采样模式、采样间等。`ADC_GetSample`函数用于获取ADC采样值,并返回数字值。在`main`函数,你可以使用`ADC_GetSample`函数循环读取ADC采样值,并计算正弦波的峰峰值和频率。你需要根据你的具体应用场景来进行计算。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值