一、Metal 大量頂點數據處理
如上圖,setVertexByte: 方法對數據是有限制的 不能大於4K。當大量數據,超過了4K時,我們可以使用 MTLBuffer。
1、MTLBuffer
當頂點數量太多時,對CPU的消耗會增大,尤其在游戲、AI等場景中,為更好的擴展管理 (並不是為了圖形圖片的加載),Metal 提出了一個新的對象:MTLBuffer;
Metal --> MTLBuffer --> 緩存區(可以存儲了大量的自定義數據,GPU直接訪問 <-- 緩存區存在顯存中) --> 存儲頂點數據
1.1、當頂點數據超過了 4K 上限時,"setVertexBytes:length:atIndex:" 方法會不再生效 --> 簡單的繪制三角形 每個頂點 xyzw 占用4*8=32 字節 --> 我們可根據實際業務場景考量是否需要使用 MTLBuffer
2、MTLBuffer 使用
這里不再重復貼代碼,可 通過繪制三角形 Demo 進行對比。更多案例可通過 Metal Sample Code 官方 Demo 示例 來下載了解 Metal 相關 API 的使用。
通過 buffer 傳值:
// 5.調用 [MTLRenderCommandEncoder setVertexBuffer:offset:atIndex:] 是從 OC 代碼找 發送數據預加載的MTLBuffer 到 Metal 頂點着色函數中 /* 這個調用有3個參數 1) buffer - 包含需要傳遞數據的緩沖對象 2) offset - 它們從緩沖器的開頭字節偏移,指示“頂點指針”指向什么。在這種情況下,我們通過0,所以數據一開始就被傳遞下來.偏移量 3) index - 一個整數索引,對應於 “vertexShader” 函數中的 緩沖區屬性限定符 的 索引。注意,此參數與 -[MTLRenderCommandEncoder setVertexBytes:length:atIndex:] “索引”參數相同。 */ // 將 _vertexBuffer 設置到頂點緩存區中 [renderEncoder setVertexBuffer:_vertexBuffer offset:0 atIndex:MyVertexInputIndexVertices]; /* ##不需要設置緩沖區,普通傳遞頂點數據的方法 -- 具體流程使用見繪制三角形 demo // 頂點 + 顏色 [renderEncoder setVertexBytes:triangleVertices length:sizeof(triangleVertices) atIndex:MyVertexInputIndexVertices]; */
緩沖區的創建:
// 5.獲取頂點數據 NSData *vertexData = [CCRenderer generateVertexData]; // 創建一個vertex buffer,可以由GPU來讀取 _vertexBuffer = [_device newBufferWithLength:vertexData.length options:MTLResourceStorageModeShared]; // 復制vertex data 到vertex buffer 通過緩存區的"content"內容屬性訪問指針 --> 頂點數據從內存copy到緩沖區中 MTLBuffer 對象 /* memcpy(void *dst, const void *src, size_t n); dst:目的地 src:源內容 n: 長度 */ memcpy(_vertexBuffer.contents, vertexData.bytes, vertexData.length);
二、加載紋理
Metal 加載 .jpg .png 文件
1、加載流程
2、主要代碼
紋理處理代碼
// JPG 圖片 -(void)setupTextureJPG { // 1.獲取圖片 UIImage *image = [UIImage imageNamed:@"cat.jpg"]; // 2.紋理描述符 MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init]; // 表示每個像素有藍色,綠色,紅色和alpha通道.其中每個通道都是8位無符號歸一化的值.(即0映射成0,255映射成1); textureDescriptor.pixelFormat = MTLPixelFormatRGBA8Unorm; // 設置紋理的像素尺寸 textureDescriptor.width = image.size.width; textureDescriptor.height = image.size.height; // 3.使用描述符從設備中創建紋理 _texture = [_device newTextureWithDescriptor:textureDescriptor]; /* typedef struct { MTLOrigin origin; // 開始位置x,y,z MTLSize size; // 尺寸width,height,depth } MTLRegion; */ // MLRegion 結構用於標識紋理的特定區域。 demo 使用圖像數據填充整個紋理;因此,覆蓋整個紋理的像素區域等於紋理的尺寸。 // 4. 創建 MTLRegion 結構體 [紋理上傳的范圍] MTLRegion region = {{ 0, 0, 0 }, {image.size.width, image.size.height, 1}}; // 5.獲取圖片數據 Byte *imageBytes = [self loadImage:image]; // 6.UIImage 的數據需要轉成二進制才能上傳,且不用 jpg、png 的NSData if (imageBytes) { [_texture replaceRegion:region mipmapLevel:0 withBytes:imageBytes bytesPerRow:4 * image.size.width]; free(imageBytes); imageBytes = NULL; } }
Metal 代碼:
// 頂點函數 vertex RasterizerData vertexShader(uint vertexID [[vertex_id]], constant MyVertex *vertexArray [[buffer(MyVertexInputIndexVertices)]], constant vector_uint2 *viewportSizePointer [[buffer(MyVertexInputIndexViewportSize)]]) { /* 處理頂點數據: 1) 執行坐標系轉換,將生成的頂點剪輯空間寫入到返回值中. 2) 將頂點顏色值傳遞給返回值 */ // 定義 out RasterizerData out; // 初始化輸出剪輯空間位置 out.clipSpacePosition = vector_float4(0.0, 0.0, 0.0, 1.0); // 索引到我們的數組位置以獲得當前頂點 // 我們的位置是在像素維度中指定的. float2 pixelSpacePosition = vertexArray[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.clipSpacePosition.z = 0.0f; out.clipSpacePosition.w = 1.0f; // 把我們輸入的顏色直接賦值給輸出顏色. 這個值將於構成三角形的頂點的其他顏色值插值,從而為我們片段着色器中的每個片段生成顏色值. out.textureCoordinate = vertexArray[vertexID].textureCoordinate; // 完成! 將結構體傳遞到管道中下一個階段: return out; } // 當頂點函數執行3次,三角形的每個頂點執行一次后,則執行管道中的下一個階段 --> 柵格化/光柵化. // 片元函數 fragment float4 fragmentShader(RasterizerData in [[stage_in]], texture2d<half> colorTexture [[texture(MyTextureIndexBaseColor)]]) { constexpr sampler textureSampler(mag_filter::linear, min_filter::linear); const half4 colorSampler = colorTexture.sample(textureSampler,in.textureCoordinate); return float4(colorSampler); }