网站内链技巧开发网页系统一般多少钱

当前位置: 首页 > news >正文

网站内链技巧,开发网页系统一般多少钱,南通网站建设兼职,一般自己怎么做网站到目前为止#xff0c;您已经完成了 3D 模型和图形管道。现在#xff0c;是时候看看 Metal 中两个可编程阶段中的第一个阶段#xff0c;即顶点阶段#xff0c;更具体地说#xff0c;是顶点函数。 着色器函数 定义着色器函数时#xff0c;可以为其指定一个属性。您将在本… 到目前为止您已经完成了 3D 模型和图形管道。现在是时候看看 Metal 中两个可编程阶段中的第一个阶段即顶点阶段更具体地说是顶点函数。 着色器函数 定义着色器函数时可以为其指定一个属性。您将在本书中学到这些属性 • vertex顶点函数计算顶点的位置。 • fragment 片段函数 计算片段的颜色。 • kernel内核功能用于通用的并行计算例如图像处理。 在本章中您将只关注 vertex 函数。在第 7 章 “片段函数”中您将探索如何控制每个片段的颜色。在第 16 章 “GPU 计算编程” 中您将了解如何使用具有多个线程的并行编程来写入缓冲区和纹理。 到目前为止您应该已经熟悉顶点描述符以及如何使用它们来描述如何从加载的 3D 模型中排列顶点属性。回顾一下 • MDLVertexDescriptor使用Model I/O 顶点描述符读取 USD 文件。Model I/O 会创建缓冲区缓冲区会按我们所需的布局存放属性值例如位置、法线和纹理坐标。 • MTLVertexDescriptor在创建管线状态时使用 Metal 顶点描述符。GPU 顶点函数使用 [[stage_in]] 属性将传入数据与管线状态中的顶点描述符进行匹配。 在学习本章时您将在不使用顶点描述符情况下构建自己的顶点网格并将顶点发送到 GPU。您将学习如何在顶点函数中控制这些顶点然后升级到使用顶点描述符。在此过程中您将看到如何使用 Model I/O 导入网格从而为您完成许多繁重的工作。 开始项目 ➤ 打开本章的初始项目。 此 SwiftUI 项目包含一个简化的 Renderer以便您可以添加自己的网格并且着色器函数返回固定的值因此您可以构建它们。您尚未进行任何绘图因此在运行应用程序时看不到任何内容。 渲染一个四边形 您可以使用两个三角形创建一个四边形。每个三角形有 3 个顶点总共有 6 个顶点。 ➤ 创建一个名为 Quad.swift 的新 Swift 文件。 ➤ 将现有代码替换为 import MetalKit struct Vertex {var x: Floatvar y: Floatvar z: Float } struct Quad {var vertices: [Vertex] [Vertex(x: -1, y: 1, z: 0),Vertex(x: 1, y: -1, z: 0),Vertex(x: -1, y: -1, z: 0),Vertex(x: -1, y: 1, z: 0),Vertex(x: 1, y: 1, z: 0),Vertex(x: 1, y: -1, z: 0) ] } // triangle 1 // triangle 2 您可以创建一个结构体来组成一个具有 x、y 和 z 值的顶点。在这里顶点的环绕顺序 顶点顺序 是顺时针方向的这很重要。 ➤ 向 Quad 添加新的顶点缓冲区property并初始化它 let vertexBuffer: MTLBuffer init(device: MTLDevice, scale: Float 1) {vertices vertices.map {Vertex(x: \(0.x * scale, y: \)0.y * scale, z: $0.z * scale)}guard let vertexBuffer device.makeBuffer(bytes: vertices,length: MemoryLayoutVertex.stride * vertices.count,options: []) else {fatalError(Unable to create quad vertex buffer) }self.vertexBuffer vertexBuffer } 使用此代码您可以使用顶点数组初始化 Metal 缓冲区。将每个顶点乘以 scale这样就可以在初始化期间设置四边形的大小。 ➤ 打开 Renderer.swift并为 quad 网格添加一个新property lazy var quad: Quad {Quad(device: Self.device, scale: 0.8) }() 在这里您将使用 Renderer 的设备初始化 quad。您必须延迟初始化 quad因为在运行 initmetalView 之前device 不会初始化。您还可以调整四边形的大小以便可以清楚地看到它。 注意:如果您将比例保持在默认的1.0下四边形将覆盖整个屏幕。覆盖屏幕对于全屏绘图很有用因为您只能在渲染几何图形的区域绘制片段。 在 draw(in:) 中,在 // do drawing here 之后,添加: renderEncoder.setVertexBuffer(quad.vertexBuffer,offset: 0,index: 0) 您在渲染命令编码器上创建一个命令将顶点缓冲区在缓冲区参数表中的索引设置为 0。 ➤ 添加 draw 调用 renderEncoder.drawPrimitives(type: .triangle,vertexStart: 0,vertexCount: quad.vertices.count) 在这里您将绘制四边形的六个顶点。 ➤ 打开 Shaders.metal。 ➤ 将 vertex 函数替换为 vertex float4 vertex_main(constant float3 *vertices [[buffer(0)]],uint vertexID [[vertex_id]]) {float4 position float4(vertices[vertexID], 1);return position; } 此代码存在错误您将很快观察并修复该错误。 GPU 为每个顶点执行顶点函数。在绘制调用中您指定了有6个顶点。因此顶点函数将执行六次。 将指针传递到 vertex 函数时必须指定地址空间constant 或 device。constant 经过优化可在多个顶点函数上并行访问同一变量。device 最适合通过并行函数访问缓冲区的不同部分例如使用交错顶点和颜色数据的缓冲区时。 [[vertex_id]] 是一个属性限定符它为您提供当前顶点。您可以将其用作访问vertices 持有数组的入口。 您可能会注意到您正在向 GPU 发送一个缓冲区其中填充了一个 Vertexs 数组该数组由 3 个 Float 组成。在顶点函数中您读取的缓冲区与 float3 数组相同从而导致显示错误。 尽管您可能会获得不同的渲染但顶点位于错误的位置因为 float3 类型比具有三个 Float 类型成员的 Vertex 占用更多的内存。Float 长 4 字节Vertex 长 12 字节。SIMD float3 类型是被填充、字节对齐的占用与 float4 类型相同的内存即 16 字节。将此参数更改为 packed_float3 将修复错误因为 packed_float3占用 12 个字节。 注意:您可以在https://apple.co/2UT993x查看Metal着色语言规范中类型的大小。 在 vertex 函数中,将第一个参数中的 float3 改为 packed_float3。 编译并运行。 四边形现在显示正确了。或者您可以将 Float 数组顶点定义为 simdfloat3 数组。在这种情况下,您将在顶点函数中使用 float3因为这两种类型都需要 16 个字节。但是每个顶点发送 16 个字节的效率略低于每个顶点发送 12 个字节的效率。 计算位置 Metal不但支持绚丽的色彩也支持快速平滑的动画。在下一步我们会让我们的四边形上下移动。为了做到这个我们需要一个计时器每帧都更新四边形的位置。顶点shader函数就是我们更新顶点位置的地方我们会发送计时器数据到GPU。 在Renderer的头部添加如下属性 var timer: Float 0 然后在draw(in:), 在这一行前面 renderEncoder.setRenderPipelineState(pipelineState) 添加如下代码 // 1 timer 0.05 var currentTime sin(timer) // 2 renderEncoder.setVertexBytes(currentTime,length: MemoryLayoutFloat.stride,index: 11) 1每帧都更新计时器如果你希望你的四边形上下移动你需要使用一个在-1和1之间的值使用sin()函数是一个很好的限制值在-1到1之间的方法。你可以通过更改每帧中给timer增加的值来更改动画的速度。 2如果你发送少量的数据小于4kb给GPUsetVertexBytes(:length:index:)是一个创建MTLBuffer的较好选择。这里你将currentTime设置给缓冲参数表中索引为11的缓冲区。为顶点属性例如顶点位置保留缓冲区 1 到 10 有助于记住哪些缓冲区保存哪些数据。 在Shader.metal把vertex_main函数改成这样 vertex float4 vertex_main(constant packed_float3 *vertices [[buffer(0)]],constant float timer [[buffer(11)]],uint vertexID [[vertex_id]]) {float4 position float4(vertices[vertexID], 1);position.y timer;return position; } 您在缓冲区 11 中以浮点数的形式接收单个值timer。您将 timer 值添加到position.y并从函数返回新位置。 在下一章中您将开始学习如何使用矩阵乘法将顶点投影到 3D 空间中。但是您并不总是需要矩阵乘法来移动顶点在这里您可以使用简单的加法来实现沿着Y 轴平移。 ➤ 构建并运行应用程序您将看到一个可爱的动画四边形。 更高效的渲染 目前您正在使用 6 个顶点来渲染两个三角形。 在这些顶点中0 和 3 位于同一位置1 和 5 也是如此。如果您渲染具有数千个甚至数百万个顶点的网格则尽可能减少重复是非常重要的。您可以使用索引渲染来实现。 仅为不同的顶点位置创建结构体然后使用 indices 获取顶点的正确位置。 ➤ 打开 Quad.swift并将顶点重命名为 oldVertices。 ➤ 将以下结构添加到 Quad var vertices: [Vertex] [Vertex(x: -1, y: 1, z: 0),Vertex(x: 1, y: 1, z: 0),Vertex(x: -1, y: -1, z: 0),Vertex(x: 1, y: -1, z: 0) ] var indices: [UInt16] [0, 3, 2, 0, 1, 3 ] vertices 现在以任意顺序保存四边形的唯一四个点。indices 以正确的顶点顺序保存每个顶点的索引。请参阅 oldVertices 以确保您的索引正确无误。  ➤ 添加新的 Metal 缓冲区来保存索引 let indexBuffer: MTLBuffer 在 initdevicescale 的末尾添加 guard let indexBuffer device.makeBuffer(bytes: indices,length: MemoryLayoutUInt16.stride * indices.count,options: []) else {fatalError(Unable to create quad index buffer) } self.indexBuffer indexBuffer 创建索引缓冲区的方式与创建顶点缓冲区的方式相同。 ➤ 打开 Renderer.swift在 drawin 中在 draw 调用之前添加 renderEncoder.setVertexBuffer(quad.indexBuffer,offset: 0,index: 1) 在这里您将索引缓冲区发送到 GPU。 ➤ 将 draw 调用更改为  renderEncoder.drawPrimitives(type: .triangle,vertexStart: 0,vertexCount: quad.indices.count) 使用索引计数来表示要渲染的顶点数而不是顶点计数。 ➤ 打开 Shaders.metal并将顶点函数更改为  vertex float4 vertex_main(constant packed_float3 *vertices [[buffer(0)]],constant ushort *indices [[buffer(1)]],constant float timer [[buffer(11)]],uint vertexID [[vertex_id]]) {ushort index indices[vertexID];float4 position float4(vertices[index], 1);return position; } 此处vertexID 是缓冲区中的索引该缓冲区保存了四边形的索引。使用索引缓冲区中的值在顶点缓冲区中正确索引顶点。  ➤ 构建并运行。 当然你的四边形的位置与以前相同但现在你向 GPU 发送的数据更少。 从数组中的条目数量来看您实际上似乎在发送更多数据 — 但事实并非如此oldVertices 的内存占用为 72 字节而 vertices indices 的内存占用为 60 字节。 顶点描述器 使用索引渲染顶点时可以使用更高效的绘制调用。但是您首先需要在管道中设置顶点描述符。 始终使用顶点描述符是一个好主意因为大多数情况下您不仅会向 GPU 发送位置属性。您还可能发送法线、纹理坐标和颜色等属性。当可以布置自己的顶点数据时您可以更好地控制引擎处理模型网格的方式。 ➤ 创建一个名为 VertexDescriptor.swift 的新 Swift 文件。 ➤ 将代码替换为 import MetalKit extension MTLVertexDescriptor {static var defaultLayout: MTLVertexDescriptor {let vertexDescriptor MTLVertexDescriptor()vertexDescriptor.attributes[0].format .float3vertexDescriptor.attributes[0].offset 0vertexDescriptor.attributes[0].bufferIndex 0let stride MemoryLayoutVertex.stridevertexDescriptor.layouts[0].stride stridereturn vertexDescriptor} } 在这里您将设置一个只有一个属性的顶点布局。该属性描述每个顶点的位置。 顶点描述符包含一个属性数组attributes和一个缓冲区布局数组layouts。 • attributes对于每个属性你需要指定类型格式以及第一个属性值距离缓冲区开始位置的偏移量以字节为单位。您还可以指定存储该属性的缓冲区的索引。 • buffer layout你需要指定每个缓冲区中所有属性组合的步幅长度。这里可能会让人感到困惑因为你正在使用下标0 来索引layouts和attributes数组但layouts下标0 对应于attributes数组使用到的 bufferIndex 0也就是索引为0的缓冲区。 注意 stride 描述了每个实例之间的字节数。由于内部填充和字节对齐此值可能与 size 不同。有关大小、步幅和对齐的精彩解释请查看 Greg Heo 的文章网址为https://bit.ly/2V3gBJl. 对于 GPUvertexBuffer 现在如下所示 ➤ 打开 Renderer.swift并在 initmetalView 中找到创建管道状态的位置。 ➤ 在 do {} 中创建管道状态之前将以下代码添加到管道状态描述符中 pipelineDescriptor.vertexDescriptor MTLVertexDescriptor.defaultLayout GPU 现在期望顶点按描述符描述的格式存放。 ➤ 在 drawin 中删除  renderEncoder.setVertexBuffer(quad.indexBuffer,offset: 0,index: 1) 您将在 draw 调用中包含索引缓冲区。 ➤ 将 draw 调用更改为 renderEncoder.drawIndexedPrimitives(type: .triangle,indexCount: quad.indices.count,indexType: .uint16,indexBuffer: quad.indexBuffer,indexBufferOffset: 0) 此绘图调用期望索引缓冲区使用 UInt16这就是你在 Quad 中描述 indices 数组的方式。你没有显式例如调用setVertexBuffer方法地将 quad.indexBuffer 发送到 GPU因为这个 draw 调用会为你做这件事。 ➤ 打开 Shaders.metal。 ➤ 将 vertex 函数替换为 vertex float4 vertex_main(float4 position [[attribute(0)]] [[stage_in]],constant float timer [[buffer(11)]]) {return position; } 你在Swift 端的布局做了所有繁重的工作所以顶点函数的大小大大减小了。 您可以使用 [[stage_in]] 属性描述每个逐顶点的输入。GPU 现在查看管道状态的顶点描述符。 [[attribute(0)]] 是顶点描述符中描述位置的属性。即使您将原始顶点数据定义为包含三个浮点数的顶点类型也可以在此处将位置定义为 float4。GPU 可以进行转换。 值得注意的是当 GPU 将 w 信息添加到 xyz 位置时它会添加为1.0。正如您将在以下章节中看到的那样这个 w 值在栅格化过程中非常重要。 GPU 现在拥有计算每个顶点位置所需的所有信息。 ➤ 构建并运行应用程序以确保一切仍然有效。生成的渲染将与以前相同。 添加另一个顶点属性 您可能永远不会只有一个属性因此让我们为每个顶点添加一个 color 属性。 您可以选择是使用两个缓冲区或者在每个顶点位置之间交错存放颜色。如果您选择交错您将设置一个结构来保存位置和颜色。但是在此示例中添加新的颜色缓冲区以匹配每个顶点会更容易。 ➤ 打开 Quad.swift并添加新数组 var colors: [simd_float3] [[1, 0, 0], // red[0, 1, 0], // green[0, 0, 1], // blue[1, 1, 0] // yellow ] 现在您有四种 RGB 颜色来对应这四个顶点。 ➤ 创建一个新的缓冲区属性  let colorBuffer: MTLBuffer ➤ 在 initdevicescale 的末尾添加  guard let colorBuffer device.makeBuffer(bytes: colors,length: MemoryLayoutsimd_float3.stride * colors.count,options: []) else {fatalError(Unable to create quad color buffer)} self.colorBuffer colorBuffer 初始化 colorBuffer 的方式与前两个缓冲区相同。 ➤ 打开 Renderer.swift然后在 drawin 中在 draw 调用之前添加  renderEncoder.setVertexBuffer(quad.colorBuffer,offset: 0,index: 1) 使用缓冲区索引 1 将颜色缓冲区发送到 GPU该索引必须与顶点描述符layouts数组中的下标匹配。 ➤ 打开 VertexDescriptor.swift并在返回之前将以下代码添加到 defaultLayout vertexDescriptor.attributes[1].format .float3 vertexDescriptor.attributes[1].offset 0 vertexDescriptor.attributes[1].bufferIndex 1 vertexDescriptor.layouts[1].stride MemoryLayoutsimd_float3.stride 在这里您将描述索引为1的缓冲区中颜色缓冲区的布局。 ➤ 打开 Shaders.metal。 ➤ 您只能在一个参数上使用 [[stage_in]]因此在 Vertex 函数之前创建一个新结构体 struct VertexIn {float4 position [[attribute(0)]];float4 color [[attribute(1)]]; }; ➤ 将 vertex 函数更改为 vertex float4 vertex_main(VertexIn in [[stage_in]],constant float timer [[buffer(11)]]) {return in.position; } 这段代码仍然简短明了。GPU 知道如何从缓冲区中检索位置和颜色因为结构中的 [[attributen]] 限定符会查看管道状态的顶点描述符。 ➤ 构建并运行以确保您的蓝色四边形仍然渲染。 fragment 函数确定每个渲染片段的颜色。您需要将顶点的颜色传递给 fragment 函数。您将在第 7 章 “片段函数” 中了解有关 fragment 函数的更多信息。 ➤ 仍在 Shaders.metal 中在 vertex 函数之前添加以下结构  struct VertexOut {float4 position [[position]];float4 color; }; 现在您不仅可以从 vertex 函数返回 position还可以返回 position 和 color。您可以指定 [[position]]属性让 GPU 知道此结构中的哪个属性是 position。 ➤ 将 vertex 函数替换为  vertex VertexOut vertex_main(VertexIn in [[stage_in]],constant float timer [[buffer(11)]]) {VertexOut out {.position in.position,.color in.color}; return out; } 现在您返回 VertexOut 而不是 float4。 ➤ 将片段函数改为 fragment float4 fragment_main(VertexOut in [[stage_in]]) {return in.color; } [[stage_in]] 属性指示 GPU 应从顶点函数获取 VertexOut 输出并将其与栅格化片段匹配。在这里您将返回顶点颜色。请记住第 3 章 “渲染管道” 中每个片段的输入都会进行插值。 ➤ 构建并运行应用程序您将看到以美丽的颜色渲染的四边形。  渲染成点状 您可以使用点和线渲染而不是三角形。 ➤ 打开 Renderer.swift然后在 drawin 中更改 renderEncoder.drawIndexedPrimitives(type: .triangle, 为 renderEncoder.drawIndexedPrimitives(type: .point, 如果您现在构建并运行GPU 将使用点渲染但它不知道要使用什么大小的点因此它会在各种大小上闪烁。要解决此问题您需要从 vertex 函数返回数据时同时返回点尺寸。 ➤ 打开 Shaders.metal并将此属性添加到 VertexOut  float pointSize [[point_size]]; [[point_size]] 属性将告诉 GPU 要使用什么尺寸的点。 ➤ 在 vertex_main 中将 out 的初始化替换为  VertexOut out {.position in.position,.color in.color,.pointSize 30 }; 在这里您将设置点的大小为30。 ➤ 构建并运行以查看使用顶点颜色渲染的点 挑战 到目前为止您通过数组缓冲区已将顶点位置发送到GPU。但这并不完全必要。GPU 需要知道的是要绘制多少个顶点。您的挑战是删除顶点和索引缓冲区并在一个圆圈中绘制 50 个点。以下是您需要采取的步骤的概述以及一些帮助您入门的代码 1. 在 Renderer 中从管道中删除顶点描述符。 2. 替换 Renderer 中的 draw 调用使其不使用索引而是绘制 50个顶点。 3. 在 drawin 中删除所有 setVertexBuffer 命令。 4. GPU 需要知道总点数因此请以与缓冲区 0 中的 timer 相同的方式发送此值。 5. 将 vertex 函数替换为 vertex VertexOut vertex_main(constant uint count [[buffer(0)]],constant float timer [[buffer(11)]],uint vertexID [[vertex_id]]) {float radius 0.8;float pi 3.14159;float current float(vertexID) / float(count);float2 position;position.x radius * cos(2 * pi * current);position.y radius * sin(2 * pi * current);VertexOut out {.position float4(position, 0, 1),.color float4(1, 0, 0, 1),.pointSize 20 }; return out; } 请记住这是一个练习可帮助您了解如何在 GPU 上定位点而无需在 Swift 端保存任何等效数据。所以不要太担心数学。您可以使用当前的vertexID的正弦和余弦来绘制圆周围的点。 请注意GPU 上没有 pi 的内置值。 您将看到 50 个点被绘制成一个圆圈。 尝试通过将 timer 添加到 current 来为点添加动画。 如果你有任何困难你可以在本章的项目挑战目录中找到解决方案。  参考 https://zhuanlan.zhihu.com/p/385638027