Metal 三、案例-Metal 大量頂點數據時處理方案 MTLBuffer + 加載紋理


一、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);
}

Demo 地址

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM