Metal 加載紋理

MTLTexture

??一個MTLTexture對象代表了一個格式化后的圖像數(shù)據(jù)的內(nèi)存空間,它可以被用于頂點著色器、片段著色器和計算函數(shù)的資源,或者作為一個渲染目標(biāo)附件。

創(chuàng)建一個MTLTexture
  • 通過MTLDevice創(chuàng)建,為紋理圖像數(shù)據(jù)開辟一個新的內(nèi)存空間并創(chuàng)建一個 MTLTexture 對象,它將根據(jù)傳入的 MTLTextureDescriptor對象設(shè)置此紋理的屬性

    - (nullable id <MTLTexture>)newTextureWithDescriptor:(MTLTextureDescriptor *)descriptor;
    
  • 通過MTLTexture創(chuàng)建,將會重新按照傳入的像素格式詮釋調(diào)用對象內(nèi)存儲的圖像數(shù)據(jù)。傳入的 MTLPixelFormat必須與原調(diào)用對象的 MTLPixelFormat 兼容.

    - (nullable id<MTLTexture>)newTextureViewWithPixelFormat:(MTLPixelFormat)pixelFormat;
    
  • 通過MTLBuffer創(chuàng)建一個與調(diào)用者共享內(nèi)存空間的MTLTexture對象,由于它們共享相同的內(nèi)存空間,所有在新紋理對象上的改動都會反映到調(diào)用對象上,反之亦然。在紋理和緩沖之間共享存儲空間會防止使用某些紋理優(yōu)化,如像素調(diào)整或平鋪。

    - (nullable id <MTLTexture>)newTextureWithDescriptor:(MTLTextureDescriptor*)descriptor 
                                                  offset:(NSUInteger)offset 
                                             bytesPerRow:(NSUInteger)bytesPerRow
    
利用紋理描述符創(chuàng)建紋理對象(MTLTextureDescriptor )

?MTLTextureDescriptor 類定義了創(chuàng)建一個紋理對象所需要的屬性,包括圖片尺寸(寬、高、深度)、像素格式、布局格式(數(shù)組或立方體)以及 mipmap 的個數(shù)。

?MTLTextureDescriptor 僅用于創(chuàng)建 MTLTexture 對象的過程中,一旦創(chuàng)建完成,對 MTLTextureDescriptor 的屬性修改將不再對 MTLTexture 生效。

紋理對象加載紋理圖片數(shù)據(jù)

從調(diào)用者指針指向的紋理存儲空間中拷貝一部分區(qū)域的數(shù)據(jù)到一個默認(rèn)的紋理切片

- (void)replaceRegion:(MTLRegion)region 
          mipmapLevel:(NSUInteger)level 
            withBytes:(const void *)pixelBytes 
          bytesPerRow:(NSUInteger)bytesPerRow;
傳遞紋理圖片數(shù)據(jù)到片元著色器
- (void)setFragmentTexture:(nullable id <MTLTexture>)texture 
                   atIndex:(NSUInteger)index;
完整的紋理加載流程
加載紋理.png
Shader文件
  1. 頂點著色器

    #include <metal_stdlib>
    //使用命名空間 Metal
    using namespace metal;
    
    // 導(dǎo)入Metal shader 代碼和執(zhí)行Metal API命令的C代碼之間共享的頭
    #import "CCShaderTypes.h"
    
    // 頂點著色器輸出和片段著色器輸入
    //結(jié)構(gòu)體
    typedef struct
    {
        //處理空間的頂點信息
        float4 clipSpacePosition [[position]];
    
        //顏色
        float4 color;
    
    } RasterizerData;
    
    //頂點著色函數(shù)
    vertex RasterizerData
    vertexShader(uint vertexID [[vertex_id]],
                 constant CCVertex *vertices [[buffer(CCVertexInputIndexVertices)]],
                 constant vector_uint2 *viewportSizePointer [[buffer(CCVertexInputIndexViewportSize)]])
    {
        /*
         處理頂點數(shù)據(jù):
         1) 執(zhí)行坐標(biāo)系轉(zhuǎn)換,將生成的頂點剪輯空間寫入到返回值中.
         2) 將頂點顏色值傳遞給返回值
         */
    
        //定義out
        RasterizerData out;
    
        //初始化輸出剪輯空間位置
        out.clipSpacePosition = vector_float4(0.0, 0.0, 0.0, 1.0);
    
        // 索引到我們的數(shù)組位置以獲得當(dāng)前頂點
        // 我們的位置是在像素維度中指定的.
        float2 pixelSpacePosition = vertices[vertexID].position.xy;
    
        //將vierportSizePointer 從verctor_uint2 轉(zhuǎn)換為vector_float2 類型
        vector_float2 viewportSize = vector_float2(*viewportSizePointer);
    
        //每個頂點著色器的輸出位置在剪輯空間中(也稱為歸一化設(shè)備坐標(biāo)空間,NDC),剪輯空間中的(-1,-1)表示視口的左下角,而(1,1)表示視口的右上角.
        //計算和寫入 XY值到我們的剪輯空間的位置.為了從像素空間中的位置轉(zhuǎn)換到剪輯空間的位置,我們將像素坐標(biāo)除以視口的大小的一半.
        out.clipSpacePosition.xy = pixelSpacePosition / (viewportSize / 2.0);
    
        //把我們輸入的顏色直接賦值給輸出顏色. 這個值將于構(gòu)成三角形的頂點的其他顏色值插值,從而為我們片段著色器中的每個片段生成顏色值.
        out.color = vertices[vertexID].color;
    
        //完成! 將結(jié)構(gòu)體傳遞到管道中下一個階段:
        return out;
    }
    
    //當(dāng)頂點函數(shù)執(zhí)行3次,三角形的每個頂點執(zhí)行一次后,則執(zhí)行管道中的下一個階段.柵格化/光柵化.
    
  2. 片元著色器

    紋理采樣

    fragment float4 fragmentShader(RasterizerData in [[stage_in]],
                                   texture2d<half> colorTexture [[texture(CCTextureIndexBaseColor)]])
    {
        constexpr sampler textureSampler(mag_filter::linear,
                                         min_filter::linear);
    
        const half4 colorSampler = colorTexture.sample(textureSampler,in.textureCoordinate);
    
        return float4(colorSampler);
    
        //返回輸入的片元顏色
        //return in.color;
    }
    
初始化
  1. 加載Shader文件

    //1.設(shè)置繪制紋理的像素格式
      mtkView.colorPixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;
    
      //2.從項目中加載所以的.metal著色器文件
      id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
      //從庫中加載頂點函數(shù)
      id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
      //從庫中加載片元函數(shù)
      id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
    
  2. 加載渲染管道

     //3.配置用于創(chuàng)建管道狀態(tài)的管道
      MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
      //管道名稱
      pipelineStateDescriptor.label = @"Simple Pipeline";
      //可編程函數(shù),用于處理渲染過程中的各個頂點
      pipelineStateDescriptor.vertexFunction = vertexFunction;
      //可編程函數(shù),用于處理渲染過程總的各個片段/片元
      pipelineStateDescriptor.fragmentFunction = fragmentFunction;
      //設(shè)置管道中存儲顏色數(shù)據(jù)的組件格式
      pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
    
      //4.同步創(chuàng)建并返回渲染管線對象
      NSError *error = NULL;
      _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
                                                               error:&error];
      //判斷是否創(chuàng)建成功
      if (!_pipelineState)
      {
          NSLog(@"Failed to created pipeline state, error %@", error);
      }
    
  3. 創(chuàng)建命令隊列

    _commandQueue = [_device newCommandQueue];
    
  4. 處理頂點數(shù)據(jù)

     //1.根據(jù)頂點/紋理坐標(biāo)建立一個MTLBuffer
        static const CCVertex quadVertices[] = {
            //像素坐標(biāo),紋理坐標(biāo)
            { {  250,  -250 },  { 1.f, 0.f } },
            { { -250,  -250 },  { 0.f, 0.f } },
            { { -250,   250 },  { 0.f, 1.f } },
    
            { {  250,  -250 },  { 1.f, 0.f } },
            { { -250,   250 },  { 0.f, 1.f } },
            { {  250,   250 },  { 1.f, 1.f } },
    
        };
    
        //2.創(chuàng)建我們的頂點緩沖區(qū),并用我們的Qualsits數(shù)組初始化它
        _vertices = [_device newBufferWithBytes:quadVertices length:sizeof(quadVertices) options:MTLResourceStorageModeShared];
        //3.通過將字節(jié)長度除以每個頂點的大小來計算頂點的數(shù)目
        _numVertices = sizeof(quadVertices) / sizeof(CCVertex);
    
  5. 處理紋理數(shù)據(jù)

    • 獲取位圖數(shù)據(jù)

    • 利用紋理描述符創(chuàng)建紋理對象(MTLTextureDescriptor

         //2.創(chuàng)建紋理描述對象
          MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc]init];
          //表示每個像素有藍色,綠色,紅色和alpha通道.其中每個通道都是8位無符號歸一化的值.(即0映射成0,255映射成1);
          textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
          //設(shè)置紋理的像素尺寸
          textureDescriptor.width = image.width;
          textureDescriptor.height = image.height;
          //使用描述符從設(shè)備中創(chuàng)建紋理
          _texture = [_device newTextureWithDescriptor:textureDescriptor];
      
    • 紋理對象加載紋理圖片數(shù)據(jù)-(void)replaceRegion:mipmapLevel: withBytes:bytesPerRow:

      //計算圖像每行的字節(jié)數(shù)
      NSUInteger bytesPerRow = 4 * image.width;
      
      /*
       typedef struct
       {
       MTLOrigin origin; //開始位置x,y,z
       MTLSize   size; //尺寸width,height,depth
       } MTLRegion;
       */
      //MLRegion結(jié)構(gòu)用于標(biāo)識紋理的特定區(qū)域。 demo使用圖像數(shù)據(jù)填充整個紋理;因此,覆蓋整個紋理的像素區(qū)域等于紋理的尺寸。
      //3. 創(chuàng)建MTLRegion 結(jié)構(gòu)體
      MTLRegion region = {
          {0,0,0},
          {image.width,image.height,1}
      };
      
      //4.復(fù)制圖片數(shù)據(jù)到texture
      [_texture replaceRegion:region mipmapLevel:0 withBytes:image.data.bytes bytesPerRow:bytesPerRow];
      
渲染(drawInMTKView:)
  1. 創(chuàng)建命令緩沖區(qū)

       //1.為當(dāng)前渲染的每個渲染傳遞創(chuàng)建一個新的命令緩沖區(qū)
        id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
        //指定緩存區(qū)名稱
        commandBuffer.label = @"MyCommand";
    
  2. 獲取渲染描述符

    //2. MTLRenderPassDescriptor:一組渲染目標(biāo),用作渲染通道生成的像素的輸出目標(biāo)。
        //currentRenderPassDescriptor 從currentDrawable's texture,view's depth, stencil, and sample buffers and clear values.
        MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
        //判斷渲染目標(biāo)是否為空
    
  3. 通過描述符創(chuàng)建渲染編碼器

     //創(chuàng)建渲染命令編碼器,這樣我們才可以渲染到something
    id<MTLRenderCommandEncoder> renderEncoder =
    [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
    //渲染器名稱
    renderEncoder.label = @"MyRenderEncoder";
    
    //3.設(shè)置我們繪制的可繪制區(qū)域
    /*
     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 }];
    
    //4. 設(shè)置渲染管道
    [renderEncoder setRenderPipelineState:_pipelineState];
    
  4. 設(shè)置頂點數(shù)據(jù)及視口大小

    //將_vertexBuffer 設(shè)置到頂點緩存區(qū)中
    [renderEncoder setVertexBuffer:_vertexBuffer
                            offset:0
                           atIndex:CCVertexInputIndexVertices];
    
    //將 _viewportSize 設(shè)置到頂點緩存區(qū)綁定點設(shè)置數(shù)據(jù)
    [renderEncoder setVertexBytes:&_viewportSize
                           length:sizeof(_viewportSize)
                          atIndex:CCVertexInputIndexViewportSize];
    
  5. 設(shè)置紋理對象- (void)setFragmentTexture:atIndex:

    
     [renderEncoder setFragmentTexture:_texture atIndex:CCTextureIndexBaseColor];
    
  6. 繪制

    [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
                      vertexStart:0
                      vertexCount:_numVertices];
    
    //7/表示已該編碼器生成的命令都已完成,并且從NTLCommandBuffer中分離
    [renderEncoder endEncoding];
    
    //8.一旦框架緩沖區(qū)完成,使用當(dāng)前可繪制的進度表
    [commandBuffer presentDrawable:view.currentDrawable];
    
    //9.最后,在這里完成渲染并將命令緩沖區(qū)推送到GPU
    [commandBuffer commit];
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容