日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Metal之MTLBuffer批量加载顶点数量较多的图形渲染

發布時間:2024/5/21 编程问答 80 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Metal之MTLBuffer批量加载顶点数量较多的图形渲染 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

渲染原理

  • 本文是基于“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文件

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

  • 定義頂點著色器輸出
  • 初始化輸出剪輯空間位置
  • 獲取當前頂點坐標的xy:主要是因為繪制的圖形是2D的,其z都為0
  • 將傳入的視圖大小轉換為vector_float2二維向量類型
  • 頂點坐標歸一化:可以通過一行代碼同時分隔兩個通道x和y,并執行除法,然后將結果放入輸出的x和y通道中,即從像素空間位置轉換為裁剪空間位置
#include <metal_stdlib> // 使用命名空間 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) 將頂點顏色值傳遞給返回值*/// 定義outRasterizerData 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; }
二、 initWithMetalKitView

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

  • 獲取GPU設備device: 通過視圖控制器中初始化render對象時傳入的MTKView對象view,利用view來獲取GPU的使用權限
_device = mtkView.device;
  • 設置繪制紋理的像素格式
mtkView.colorPixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;
  • 從項目中加載所以的.metal著色器文件
// 從項目中加載所以的.metal著色器文件id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];// 從庫中加載頂點函數id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];// 從庫中加載片元函數id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
  • 配置用于創建管道狀態的管道描述符
// 配置用于創建管道狀態的管道MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];// 管道名稱pipelineStateDescriptor.label = @"Simple Pipeline";// 可編程函數,用于處理渲染過程中的各個頂點pipelineStateDescriptor.vertexFunction = vertexFunction;// 可編程函數,用于處理渲染過程總的各個片段/片元pipelineStateDescriptor.fragmentFunction = fragmentFunction;// 設置管道中存儲顏色數據的組件格式pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
  • 同步創建并返回渲染管線對象
// 同步創建并返回渲染管線對象NSError *error = NULL;_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptorerror:&error];
  • 獲取頂點數據
// 獲取頂點數據NSData *vertexData = [YDWRenderer generateVertexData];// 創建一個vertex buffer,可以由GPU來讀取_vertexBuffer = [_device newBufferWithLength:vertexData.lengthoptions: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); // 頂點數據 + (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數據復制到currentQuadmemcpy(currentQuad, &quadVertices, sizeof(quadVertices));// 遍歷currentQuad中的數據for (NSUInteger vertexInQuad = 0; vertexInQuad < NUM_VERTICES_PER_QUAD; vertexInQuad++) {//修改vertexInQuad中的positioncurrentQuad[vertexInQuad].position += upperLeftPosition;}// 更新索引currentQuad += 6;}}return vertexData; }
  • 創建命令隊列
// 創建命令隊列_commandQueue = [_device newCommandQueue];
三、drawInMTKView

主要加載頂點緩沖區數據

  • 為當前渲染的每個渲染傳遞創建一個新的命令緩沖區
// 為當前渲染的每個渲染傳遞創建一個新的命令緩沖區id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];// 指定緩存區名稱commandBuffer.label = @"MyCommand";
  • 創建渲染描述符
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;// 判斷渲染目標是否為空if(renderPassDescriptor != nil) {// 創建渲染命令編碼器,這樣才可以渲染到somethingid<MTLRenderCommandEncoder> renderEncoder =[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];// 渲染器名稱renderEncoder.label = @"MyRenderEncoder";}
  • 設置我們繪制的可繪制區域
/*設置繪制的可繪制區域**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}];
  • 設置渲染管道
// 設置渲染管道[renderEncoder setRenderPipelineState:_pipelineState];
  • 為了從OC代碼找發送數據預加載的MTLBuffer 到Metal 頂點著色函數中
// 將_vertexBuffer 設置到頂點緩存區中[renderEncoder setVertexBuffer:_vertexBufferoffset:0atIndex:CCVertexInputIndexVertices];// 將 _viewportSize 設置到頂點緩存區綁定點設置數據[renderEncoder setVertexBytes:&_viewportSizelength:sizeof(_viewportSize)atIndex:CCVertexInputIndexViewportSize];
  • 開始繪圖
[renderEncoder drawPrimitives:MTLPrimitiveTypeTrianglevertexStart:0vertexCount:_numVertices];
  • 結束編碼,表示已該編碼器生成的命令都已完成,并且從NTLCommandBuffer中分離
[renderEncoder endEncoding];
  • 一旦框架緩沖區完成,使用當前可繪制的進度表
[commandBuffer presentDrawable:view.currentDrawable];
  • 完成渲染并將命令緩沖區推送到GPU
[commandBuffer commit];

效果展示

完整示例

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

總結

以上是生活随笔為你收集整理的Metal之MTLBuffer批量加载顶点数量较多的图形渲染的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。