Metal之MTLBuffer批量加載頂點數量較多的圖形渲染

      網友投稿 863 2025-04-04

      本文是基于“Metal渲染繪制三角形”這樣頂點較少圖形基礎之上的延伸, 在渲染三角形的時候, 頂點數據的存儲使用的是數組,當頂點傳遞時通過setVertexBytes(_:length:index:)方法,主要是由于繪制三角形時,所需的頂點只有三個,頂點數據很少,所以可以通過數組存儲,此時的數據是存儲在CPU中的;

      Metal三角形的渲染繪制請參考:Metal之渲染繪制三角形

      對于小于4KB(即4096字節)的一次性數據,使用setVertexBytes(:length:index:),如果數據長度超過4KB 或者需要多次使用頂點數據時,需要創建一個MTLBuffer對象,創建的buffer的目的就是為了將頂點數據存儲到頂點緩存區,GPU可以直接訪問該緩存區獲取頂點數據,并且buffer緩存的數據需要通過setVertexBuffer(:offset:index:)方法傳遞到頂點著色器。

      當圖形的頂點數據較多時, 頂點的傳遞與存儲過程如下:

      ① Metal -> MTLBuffer -> 緩存區(存儲非常多自定義數據,GPU直接訪問 -> 顯存) -> 存儲頂點數據;

      ② 創建的buffer的目的就是為了將頂點數據存儲到頂點緩存區,GPU可以直接訪問該緩存區獲取頂點數據,并且buffer緩存的數據需要通過 setVertexBuffer(_:offset:index:)方法傳遞到頂點著色器。

      metal文件中,在頂點著色函數需要對頂點坐標進行歸一化處理,因為頂點數據初始化時使用的是物體坐標。頂點坐標的歸一化主要有以下步驟:

      定義頂點著色器輸出

      初始化輸出剪輯空間位置

      獲取當前頂點坐標的xy:主要是因為繪制的圖形是2D的,其z都為0

      將傳入的視圖大小轉換為vector_float2二維向量類型

      頂點坐標歸一化:可以通過一行代碼同時分隔兩個通道x和y,并執行除法,然后將結果放入輸出的x和y通道中,即從像素空間位置轉換為裁剪空間位置

      #include // 使用命名空間 Metal using namespace metal; // 導入Metal shader代碼和執行Metal API命令的C代碼之間共享的頭 #import "YDWShaderTypes.h" // 頂點著色器輸出和片段著色器輸入 // 結構體 typedef struct { // 處理空間的頂點信息 float4 clipSpacePosition [[position]]; // 顏色 float4 color; } RasterizerData; // 頂點著色函數 vertex RasterizerData vertexShader(uint vertexID [[vertex_id]], constant CCVertex *vertices [[buffer(CCVertexInputIndexVertices)]], constant vector_uint2 *viewportSizePointer [[buffer(CCVertexInputIndexViewportSize)]]) { /* 處理頂點數據: 1) 執行坐標系轉換,將生成的頂點剪輯空間寫入到返回值中 2) 將頂點顏色值傳遞給返回值 */ // 定義out RasterizerData out; // 初始化輸出剪輯空間位置 out.clipSpacePosition = vector_float4(0.0, 0.0, 0.0, 1.0); // 索引到數組位置以獲得當前頂點, 位置是在像素維度中指定的 float2 pixelSpacePosition = vertices[vertexID].position.xy; // 將vierportSizePointer 從verctor_uint2 轉換為vector_float2 類型 vector_float2 viewportSize = vector_float2(*viewportSizePointer); // 每個頂點著色器的輸出位置在剪輯空間中(也稱為歸一化設備坐標空間,NDC),剪輯空間中的(-1,-1)表示視口的左下角,而(1,1)表示視口的右上角. // 計算和寫入 XY值到我們的剪輯空間的位置.為了從像素空間中的位置轉換到剪輯空間的位置,我們將像素坐標除以視口的大小的一半. out.clipSpacePosition.xy = pixelSpacePosition / (viewportSize / 2.0); // 把輸入的顏色直接賦值給輸出顏色. 這個值將于構成三角形的頂點的其他顏色值插值,從而為我們片段著色器中的每個片段生成顏色值. out.color = vertices[vertexID].color; // 完成, 將結構體傳遞到管道中下一個階段 return out; } //當頂點函數執行3次,三角形的每個頂點執行一次后,則執行管道中的下一個階段.柵格化/光柵化. // 片元函數 // [[stage_in]],片元著色函數使用的單個片元輸入數據是由頂點著色函數輸出.然后經過光柵化生成的.單個片元輸入函數數據可以使用"[[stage_in]]"屬性修飾符. // 一個頂點著色函數可以讀取單個頂點的輸入數據,這些輸入數據存儲于參數傳遞的緩存中,使用頂點和實例ID在這些緩存中尋址.讀取到單個頂點的數據.另外,單個頂點輸入數據也可以通過使用"[[stage_in]]"屬性修飾符的產生傳遞給頂點著色函數. // 被stage_in 修飾的結構體的成員不能是如下這些.Packed vectors 緊密填充類型向量,matrices 矩陣,structs 結構體,references or pointers to type 某類型的引用或指針. arrays,vectors,matrices 標量,向量,矩陣數組. fragment float4 fragmentShader(RasterizerData in [[stage_in]]) { // 返回輸入的片元顏色 return in.color; }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      52

      53

      54

      55

      56

      57

      58

      59

      60

      61

      62

      63

      64

      主要需要加載metal文件來獲取頂點數據

      獲取GPU設備device: 通過視圖控制器中初始化render對象時傳入的MTKView對象view,利用view來獲取GPU的使用權限

      _device = mtkView.device;

      1

      設置繪制紋理的像素格式

      mtkView.colorPixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;

      1

      從項目中加載所以的.metal著色器文件

      // 從項目中加載所以的.metal著色器文件 id defaultLibrary = [_device newDefaultLibrary]; // 從庫中加載頂點函數 id vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"]; // 從庫中加載片元函數 id fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];

      Metal之MTLBuffer批量加載頂點數量較多的圖形渲染

      1

      2

      3

      4

      5

      6

      配置用于創建管道狀態的管道描述符

      // 配置用于創建管道狀態的管道 MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init]; // 管道名稱 pipelineStateDescriptor.label = @"Simple Pipeline"; // 可編程函數,用于處理渲染過程中的各個頂點 pipelineStateDescriptor.vertexFunction = vertexFunction; // 可編程函數,用于處理渲染過程總的各個片段/片元 pipelineStateDescriptor.fragmentFunction = fragmentFunction; // 設置管道中存儲顏色數據的組件格式 pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      同步創建并返回渲染管線對象

      // 同步創建并返回渲染管線對象 NSError *error = NULL; _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];

      1

      2

      3

      4

      獲取頂點數據

      // 獲取頂點數據 NSData *vertexData = [YDWRenderer generateVertexData]; // 創建一個vertex buffer,可以由GPU來讀取 _vertexBuffer = [_device newBufferWithLength:vertexData.length options:MTLResourceStorageModeShared]; /* 復制vertex data 到vertex buffer 通過緩存區的"content"內容屬性訪問指針 * * memcpy(void *dst, const void *src, size_t n); * dst:目的地 * src:源內容 * n: 長度 */ memcpy(_vertexBuffer.contents, vertexData.bytes, vertexData.length); // 計算頂點個數 = 頂點數據長度 / 單個頂點大小 _numVertices = vertexData.length / sizeof(CCVertex);

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      // 頂點數據 + (nonnull NSData *)generateVertexData { // 正方形 = 三角形+三角形 const CCVertex quadVertices[] = { // Pixel 位置, RGBA 顏色 { { -20, 20 }, { 1, 0, 0, 1 } }, { { 20, 20 }, { 1, 0, 0, 1 } }, { { -20, -20 }, { 1, 0, 0, 1 } }, { { 20, -20 }, { 0, 0, 1, 1 } }, { { -20, -20 }, { 0, 0, 1, 1 } }, { { 20, 20 }, { 0, 0, 1, 1 } }, }; // 行/列 數量 const NSUInteger NUM_COLUMNS = 25; const NSUInteger NUM_ROWS = 15; // 頂點個數 const NSUInteger NUM_VERTICES_PER_QUAD = sizeof(quadVertices) / sizeof(CCVertex); // 四邊形間距 const float QUAD_SPACING = 50.0; // 數據大小 = 單個四邊形大小 * 行 * 列 NSUInteger dataSize = sizeof(quadVertices) * NUM_COLUMNS * NUM_ROWS; // 開辟空間 NSMutableData *vertexData = [[NSMutableData alloc] initWithLength:dataSize]; // 當前四邊形 CCVertex * currentQuad = vertexData.mutableBytes; // 獲取頂點坐標(循環計算) // 行 for(NSUInteger row = 0; row < NUM_ROWS; row++) { // 列 for(NSUInteger column = 0; column < NUM_COLUMNS; column++) { // 左上角的位置 vector_float2 upperLeftPosition; // 計算X,Y 位置.注意坐標系基于2D笛卡爾坐標系,中心點(0,0),所以會出現負數位置 upperLeftPosition.x = ((-((float)NUM_COLUMNS) / 2.0) + column) * QUAD_SPACING + QUAD_SPACING/2.0; upperLeftPosition.y = ((-((float)NUM_ROWS) / 2.0) + row) * QUAD_SPACING + QUAD_SPACING/2.0; // 將quadVertices數據復制到currentQuad memcpy(currentQuad, &quadVertices, sizeof(quadVertices)); // 遍歷currentQuad中的數據 for (NSUInteger vertexInQuad = 0; vertexInQuad < NUM_VERTICES_PER_QUAD; vertexInQuad++) { //修改vertexInQuad中的position currentQuad[vertexInQuad].position += upperLeftPosition; } // 更新索引 currentQuad += 6; } } return vertexData; }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      10

      11

      12

      13

      14

      15

      16

      17

      18

      19

      20

      21

      22

      23

      24

      25

      26

      27

      28

      29

      30

      31

      32

      33

      34

      35

      36

      37

      38

      39

      40

      41

      42

      43

      44

      45

      46

      47

      48

      49

      50

      51

      52

      53

      54

      55

      56

      創建命令隊列

      // 創建命令隊列 _commandQueue = [_device newCommandQueue];

      1

      2

      主要加載頂點緩沖區數據

      為當前渲染的每個渲染傳遞創建一個新的命令緩沖區

      // 為當前渲染的每個渲染傳遞創建一個新的命令緩沖區 id commandBuffer = [_commandQueue commandBuffer]; // 指定緩存區名稱 commandBuffer.label = @"MyCommand";

      1

      2

      3

      4

      創建渲染描述符

      MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor; // 判斷渲染目標是否為空 if(renderPassDescriptor != nil) { // 創建渲染命令編碼器,這樣才可以渲染到something id renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor]; // 渲染器名稱 renderEncoder.label = @"MyRenderEncoder"; }

      1

      2

      3

      4

      5

      6

      7

      8

      9

      設置我們繪制的可繪制區域

      /*設置繪制的可繪制區域 * *typedef struct { double originX, originY, width, height, znear, zfar; } MTLViewport; */ [renderEncoder setViewport:(MTLViewport){ 0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0 }];

      1

      2

      3

      4

      5

      6

      7

      8

      9

      設置渲染管道

      // 設置渲染管道 [renderEncoder setRenderPipelineState:_pipelineState];

      1

      2

      為了從OC代碼找發送數據預加載的MTLBuffer 到Metal 頂點著色函數中

      // 將_vertexBuffer 設置到頂點緩存區中 [renderEncoder setVertexBuffer:_vertexBuffer offset:0 atIndex:CCVertexInputIndexVertices]; // 將 _viewportSize 設置到頂點緩存區綁定點設置數據 [renderEncoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:CCVertexInputIndexViewportSize];

      1

      2

      3

      4

      5

      6

      7

      8

      9

      開始繪圖

      [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:_numVertices];

      1

      2

      3

      結束編碼,表示已該編碼器生成的命令都已完成,并且從NTLCommandBuffer中分離

      [renderEncoder endEncoding];

      1

      一旦框架緩沖區完成,使用當前可繪制的進度表

      [commandBuffer presentDrawable:view.currentDrawable];

      1

      完成渲染并將命令緩沖區推送到GPU

      [commandBuffer commit];

      1

      Metal之MTLBuffer批量加載頂點數量較多的圖形渲染

      渲染

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:更改單元格引用樣式(修改單元格樣式)
      下一篇:Java基礎 第三節 第四課
      相關文章
      日韩亚洲国产二区| 国产亚洲精品成人久久网站| 亚洲高清视频一视频二视频三| 亚洲精品国产高清在线观看| 亚洲剧场午夜在线观看| 亚洲成aⅴ人片在线观| 亚洲熟妇色自偷自拍另类| 亚洲色图视频在线观看| 日本久久久久亚洲中字幕| 亚洲国产精品久久久久婷婷软件| 久久精品亚洲综合一品| 亚洲国产一区二区三区青草影视| 亚洲av日韩综合一区在线观看| 亚洲AV无码成人精品区蜜桃| 亚洲av不卡一区二区三区| 亚洲国产精品久久久久久| 日韩精品一区二区亚洲AV观看| 亚洲视频欧洲视频| 亚洲理论在线观看| 亚洲av永久无码精品三区在线4| 亚洲sss综合天堂久久久| 亚洲精品永久在线观看| 无码色偷偷亚洲国内自拍| 激情婷婷成人亚洲综合| 亚洲成av人片不卡无码久久| 国产乱辈通伦影片在线播放亚洲 | 久久久久亚洲av无码尤物| 亚洲av无码专区国产乱码在线观看| 国产亚洲一区二区精品| 亚洲AV永久无码精品成人| 亚洲国产综合精品中文第一区| 久久99亚洲网美利坚合众国| 亚洲综合激情另类小说区| 亚洲va在线va天堂va手机| 亚洲精品国产高清在线观看| 亚洲国产日韩成人综合天堂| 亚洲小说区图片区另类春色| 久久精品国产精品亚洲艾草网| 亚洲视频免费观看| 亚洲人成电影网站久久| 色窝窝亚洲av网|