Metal 加载纹理

MTLTexture

  一个MTLTexture对象代表了一个格式化后的图像数据的内存空间,它可以被用于顶点着色器、片段着色器和计算函数的资源,或者作为一个渲染目标附件。

创建一个MTLTexture
  • 通过MTLDevice创建,为纹理图像数据开辟一个新的内存空间并创建一个 MTLTexture 对象,它将根据传入的 MTLTextureDescriptor对象设置此纹理的属性

    - (nullable id )newTextureWithDescriptor:(MTLTextureDescriptor *)descriptor;
    
  • 通过MTLTexture创建,将会重新按照传入的像素格式诠释调用对象内存储的图像数据。传入的 MTLPixelFormat必须与原调用对象的 MTLPixelFormat 兼容.

    - (nullable id)newTextureViewWithPixelFormat:(MTLPixelFormat)pixelFormat;
    
  • 通过MTLBuffer创建一个与调用者共享内存空间的MTLTexture对象,由于它们共享相同的内存空间,所有在新纹理对象上的改动都会反映到调用对象上,反之亦然。在纹理和缓冲之间共享存储空间会防止使用某些纹理优化,如像素调整或平铺。

    - (nullable id )newTextureWithDescriptor:(MTLTextureDescriptor*)descriptor 
                                                  offset:(NSUInteger)offset 
                                             bytesPerRow:(NSUInteger)bytesPerRow
    
利用纹理描述符创建纹理对象(MTLTextureDescriptor )

 MTLTextureDescriptor 类定义了创建一个纹理对象所需要的属性,包括图片尺寸(宽、高、深度)、像素格式、布局格式(数组或立方体)以及 mipmap 的个数。

 MTLTextureDescriptor 仅用于创建 MTLTexture 对象的过程中,一旦创建完成,对 MTLTextureDescriptor 的属性修改将不再对 MTLTexture 生效。

纹理对象加载纹理图片数据

从调用者指针指向的纹理存储空间中拷贝一部分区域的数据到一个默认的纹理切片

- (void)replaceRegion:(MTLRegion)region 
          mipmapLevel:(NSUInteger)level 
            withBytes:(const void *)pixelBytes 
          bytesPerRow:(NSUInteger)bytesPerRow;
传递纹理图片数据到片元着色器
- (void)setFragmentTexture:(nullable id )texture 
                   atIndex:(NSUInteger)index;
完整的纹理加载流程
加载纹理.png
Shader文件
  1. 顶点着色器

    #include 
    //使用命名空间 Metal
    using namespace metal;
    
    // 导入Metal shader 代码和执行Metal API命令的C代码之间共享的头
    #import "CCShaderTypes.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次,三角形的每个顶点执行一次后,则执行管道中的下一个阶段.栅格化/光栅化.
    
  2. 片元着色器

    纹理采样

    fragment float4 fragmentShader(RasterizerData in [[stage_in]],
                                   texture2d 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.设置绘制纹理的像素格式
      mtkView.colorPixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;
    
      //2.从项目中加载所以的.metal着色器文件
      id defaultLibrary = [_device newDefaultLibrary];
      //从库中加载顶点函数
      id vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
      //从库中加载片元函数
      id fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
    
  2. 加载渲染管道

     //3.配置用于创建管道状态的管道
      MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
      //管道名称
      pipelineStateDescriptor.label = @"Simple Pipeline";
      //可编程函数,用于处理渲染过程中的各个顶点
      pipelineStateDescriptor.vertexFunction = vertexFunction;
      //可编程函数,用于处理渲染过程总的各个片段/片元
      pipelineStateDescriptor.fragmentFunction = fragmentFunction;
      //设置管道中存储颜色数据的组件格式
      pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
    
      //4.同步创建并返回渲染管线对象
      NSError *error = NULL;
      _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
                                                               error:&error];
      //判断是否创建成功
      if (!_pipelineState)
      {
          NSLog(@"Failed to created pipeline state, error %@", error);
      }
    
  3. 创建命令队列

    _commandQueue = [_device newCommandQueue];
    
  4. 处理顶点数据

     //1.根据顶点/纹理坐标建立一个MTLBuffer
        static const CCVertex quadVertices[] = {
            //像素坐标,纹理坐标
            { {  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.创建我们的顶点缓冲区,并用我们的Qualsits数组初始化它
        _vertices = [_device newBufferWithBytes:quadVertices length:sizeof(quadVertices) options:MTLResourceStorageModeShared];
        //3.通过将字节长度除以每个顶点的大小来计算顶点的数目
        _numVertices = sizeof(quadVertices) / sizeof(CCVertex);
    
  5. 处理纹理数据

    • 获取位图数据

    • 利用纹理描述符创建纹理对象(MTLTextureDescriptor

         //2.创建纹理描述对象
          MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc]init];
          //表示每个像素有蓝色,绿色,红色和alpha通道.其中每个通道都是8位无符号归一化的值.(即0映射成0,255映射成1);
          textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
          //设置纹理的像素尺寸
          textureDescriptor.width = image.width;
          textureDescriptor.height = image.height;
          //使用描述符从设备中创建纹理
          _texture = [_device newTextureWithDescriptor:textureDescriptor];
      
    • 纹理对象加载纹理图片数据-(void)replaceRegion:mipmapLevel: withBytes:bytesPerRow:

      //计算图像每行的字节数
      NSUInteger bytesPerRow = 4 * image.width;
      
      /*
       typedef struct
       {
       MTLOrigin origin; //开始位置x,y,z
       MTLSize   size; //尺寸width,height,depth
       } MTLRegion;
       */
      //MLRegion结构用于标识纹理的特定区域。 demo使用图像数据填充整个纹理;因此,覆盖整个纹理的像素区域等于纹理的尺寸。
      //3. 创建MTLRegion 结构体
      MTLRegion region = {
          {0,0,0},
          {image.width,image.height,1}
      };
      
      //4.复制图片数据到texture
      [_texture replaceRegion:region mipmapLevel:0 withBytes:image.data.bytes bytesPerRow:bytesPerRow];
      
渲染(drawInMTKView:)
  1. 创建命令缓冲区

       //1.为当前渲染的每个渲染传递创建一个新的命令缓冲区
        id commandBuffer = [_commandQueue commandBuffer];
        //指定缓存区名称
        commandBuffer.label = @"MyCommand";
    
  2. 获取渲染描述符

    //2. MTLRenderPassDescriptor:一组渲染目标,用作渲染通道生成的像素的输出目标。
        //currentRenderPassDescriptor 从currentDrawable's texture,view's depth, stencil, and sample buffers and clear values.
        MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
        //判断渲染目标是否为空
    
  3. 通过描述符创建渲染编码器

     //创建渲染命令编码器,这样我们才可以渲染到something
    id renderEncoder =
    [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
    //渲染器名称
    renderEncoder.label = @"MyRenderEncoder";
    
    //3.设置我们绘制的可绘制区域
    /*
     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. 设置渲染管道
    [renderEncoder setRenderPipelineState:_pipelineState];
    
  4. 设置顶点数据及视口大小

    //将_vertexBuffer 设置到顶点缓存区中
    [renderEncoder setVertexBuffer:_vertexBuffer
                            offset:0
                           atIndex:CCVertexInputIndexVertices];
    
    //将 _viewportSize 设置到顶点缓存区绑定点设置数据
    [renderEncoder setVertexBytes:&_viewportSize
                           length:sizeof(_viewportSize)
                          atIndex:CCVertexInputIndexViewportSize];
    
  5. 设置纹理对象- (void)setFragmentTexture:atIndex:

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

    [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
                      vertexStart:0
                      vertexCount:_numVertices];
    
    //7/表示已该编码器生成的命令都已完成,并且从NTLCommandBuffer中分离
    [renderEncoder endEncoding];
    
    //8.一旦框架缓冲区完成,使用当前可绘制的进度表
    [commandBuffer presentDrawable:view.currentDrawable];
    
    //9.最后,在这里完成渲染并将命令缓冲区推送到GPU
    [commandBuffer commit];
    

你可能感兴趣的:(Metal 加载纹理)