Metal-图片加载

490 阅读5分钟

本文我们研究使用metal来加载图片,加载的图片我们区分tga格式的以及png和jpg格式的图片。加载tga跟png或者jpg格式的图片唯一区别在于图片解析部分,其余的完全相同。

流程步骤

1、SSShader类

设置MTKView

主要代码如下

  • ViewController

    @interface ViewController () { MTKView *_view; SSRender *render; }

    @end

    @implementation ViewController

    • (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view.

      _view = (MTKView *)self.view; _view.device = MTLCreateSystemDefaultDevice(); render = [[SSRender alloc] initWithMTKView:_view]; _view.delegate = render; }

  • SSRender初始化

    -(instancetype)initWithMTKView:(nonnull MTKView *)mtkView {
        self = [super init];
        if (self) {
            _device = mtkView.device;
            _mView = mtkView;
        }
        return self;
    }
    

设置顶点数据

-(void)setVerture {
    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 } },

    };
    _vertexBuffer = [_device newBufferWithBytes:quadVertices length:sizeof(quadVertices) options:MTLResourceStorageModeShared];
    _numVertices = sizeof(quadVertices) / sizeof(CCVertex);
}

设置渲染管道

渲染管道用来处理顶点、片元着色器,它们存储于.metal文件中

-(void)setupPipeLine
{
    //1.创建我们的渲染通道
    //从项目中加载.metal文件,创建一个library
    id<MTLLibrary>defalutLibrary = [_device newDefaultLibrary];
    //从库中加载顶点函数
    id<MTLFunction>vertexFunction = [defalutLibrary newFunctionWithName:@"vertexShader"];
    //从库中加载片元函数
    id<MTLFunction> fragmentFunction = [defalutLibrary newFunctionWithName:@"fragmentShader"];

    //2.配置用于创建管道状态的管道
    MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
    //管道名称
    pipelineStateDescriptor.label = @"Texturing Pipeline";
    //可编程函数,用于处理渲染过程中的各个顶点
    pipelineStateDescriptor.vertexFunction = vertexFunction;
    //可编程函数,用于处理渲染过程总的各个片段/片元
    pipelineStateDescriptor.fragmentFunction = fragmentFunction;
    //设置管道中存储颜色数据的组件格式
    pipelineStateDescriptor.colorAttachments[0].pixelFormat = ccMTKView.colorPixelFormat;

    //3.同步创建并返回渲染管线对象
    NSError *error = NULL;
    _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
    //判断是否创建成功
    if (!_pipelineState)
    {
        NSLog(@"Failed to created pipeline state, error %@", error);
    }

    //4.使用_device创建commandQueue
    _commandQueue = [_device newCommandQueue];
}

加载纹理

  • tga纹理数据处理

    -(void)setupTexture
    {
        //1.获取tag的路径
        NSURL *imageFileLocation = [[NSBundle mainBundle] URLForResource:@"Image"withExtension:@"tga"];
        //将tag文件->CCImage对象
        CCImage *image = [[CCImage alloc]initWithTGAFileAtLocation:imageFileLocation];
        //判断图片是否转换成功
        if(!image)
        {
            NSLog(@"Failed to create the image from:%@",imageFileLocation.absoluteString);
    
        }
    
        //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];
        //计算图像每行的字节数
        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];
    
    }
    

代码中的CCImage类是用来处理tga文件,并返回纹理需要的width、height以及data数据

  • png或jpg纹理数据处理

    -(void)setupPngTexture {
        UIImage *image = [UIImage imageNamed:@"yuanyuan.jpeg"];
        if (!image) {
            NSLog(@"图片错误");
            exit(0);
        }
        MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
        textureDescriptor.width = image.size.width;
        textureDescriptor.height = image.size.height;
        textureDescriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
    
        _texture = [_device newTextureWithDescriptor:textureDescriptor];
        MTLRegion region = {
            {0, 0, 0},
            {image.size.width, image.size.height, 1.0}
        };
        Byte *imageByte = [self getByteImage:image];
        if (imageByte) {
            [_texture replaceRegion:region mipmapLevel:0
                          withBytes:imageByte bytesPerRow:image.size.width * 4];
            free(imageByte);
            imageByte = NULL;
        }
    
    }
    -(Byte *)getByteImage:(UIImage *)image {
        CGImageRef imgRef = image.CGImage;
        size_t width = CGImageGetWidth(imgRef);
        size_t height = CGImageGetHeight(imgRef);
        Byte *spriteData = (Byte *)calloc(width * height * 4, sizeof(Byte));
    
        CGContextRef contentRef = CGBitmapContextCreate(spriteData, width, height, 8, 4 * width, CGImageGetColorSpace(imgRef), kCGImageAlphaPremultipliedLast);
        //contentRef上绘制图片
        CGContextDrawImage(contentRef, CGRectMake(0, 0, width, height), imgRef);
    
        //图片翻转
        CGRect rect = CGRectMake(0, 0, width, height);
        CGContextTranslateCTM(contentRef, rect.origin.x, rect.origin.y);
        CGContextTranslateCTM(contentRef, 0, rect.size.height);
        CGContextScaleCTM(contentRef, 1.0, -1.0);
        CGContextTranslateCTM(contentRef, -rect.origin.x, -rect.origin.y);
        CGContextDrawImage(contentRef, rect, imgRef);
    
        CGContextRelease(contentRef);
    
        return spriteData;
    
    }
    

Metal与OC公用的类

在这个类中主要定义一些metal与OC共同使用的一些结构体变量

// 缓存区索引值 共享与 shader 和 C 代码 为了确保Metal Shader缓存区索引能够匹配 Metal API Buffer 设置的集合调用
typedef enum CCVertexInputIndex
{
    //顶点
    CCVertexInputIndexVertices     = 0,
    //视图大小
    CCVertexInputIndexViewportSize = 1,
} CCVertexInputIndex;

//纹理索引
typedef enum CCTextureIndex {
    CCTextureIndexBaseColor = 0,
}CCTextureIndex;

//结构体: 顶点/纹理
typedef struct
{
    // 像素空间的位置
    // 像素中心点(100,100)
    //float float
    vector_float2 position;
    // 纹理
    vector_float2 textureCoordinate;
} CCVertex;

Metal类

metal中实现顶点着色器函数以及片元着色器函数的编写

#import "SSStruct.h"

// 顶点着色器输出和片段着色器输入
//结构体
typedef struct
{
    //处理空间的顶点信息
    float4 clipSpacePosition [[position]];

    //纹理
    float2 textureCoordinate;

} 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.textureCoordinate = vertices[vertexID].textureCoordinate;

    //完成! 将结构体传递到管道中下一个阶段:
    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]],
                               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);
}

效果如下

tga图片

png图片