美文网首页
Metal 加载纹理

Metal 加载纹理

作者: Maji1 | 来源:发表于2020-08-28 14:07 被阅读0次

本案例的讲解是在 Metal 绘制网格 案例基础上的讲解。

首先说明下改案例需要的几个类:

  • CQMetalTextureVC:纹理控制器。用于初始化MTKViewCQTextureRender
  • CQTexture.metal:编写 MSL 程序。
  • CQTextureRender:负责纹理渲染的类。
  • CQTexture.h:用于定义 .metalCQTextureRender共用的数据类型。

一、CQTexture.metal代码:

#include <metal_stdlib>
using namespace metal;

#import "CQTexture.h"

struct VertexOut {
    float4 clipSpacePosition [[position]];
    float2 textureCoordinate;
};

vertex VertexOut vertexShaderTexture(uint vertexIndex [[vertex_id]],
                              constant CQVertexTextureInput *vertexInput [[buffer(CQVInTextureIndexVertices)]],
                              constant vector_uint2 *viewportSizePointer [[buffer(CQVInIndexViewportSize)]]) {
    //取出顶点坐标的xy,该案例中的位置是在像素维度中指定的。
    float2 pixelSpacePosition = vertexInput[vertexIndex].position.xy;
    vector_float2 viewportSize = vector_float2 (*viewportSizePointer);
    float2 xy = pixelSpacePosition / (viewportSize / 2.0);
    
    VertexOut out;
    out.clipSpacePosition.xy = xy;
    out.clipSpacePosition.zw = vector_float2(0.0, 1.0);
    out.textureCoordinate = vertexInput[vertexIndex].textureCoordinate;
    return out;
}

fragment float4 fragmentShaderTexture(VertexOut in [[stage_in]],
                                      texture2d<half> colorTexture [[texture(CQTextureIndexBaseColor)]]) {
    constexpr sampler textureSampler(min_filter::linear,
                                     mag_filter::linear);
    const half4 colorSampler = colorTexture.sample(textureSampler, in.textureCoordinate);
    return float4(colorSampler);
}

该案例中顶点函数vertexShaderTexture中的代码跟Metal 绘制网格 中的顶点函数代码一致。
片元函数fragmentShaderTexture中我们传了一个参数:colorTexture

  • texture2d<half> colorTexture [[texture(CQTextureIndexBaseColor)]]该参数是用来传递我们的纹理。
  • constexpr:在Metal程序中初始化的采样器sampler必须使用constexpr修饰符声明。
  • min_filter::linear:线性缩小过滤模式。
  • mag_filter::linear:线性放大过滤模式。

二、CQTextureRender.m代码:

看下纹理渲染的准备工作:

- (instancetype)initWithMetalKitView:(MTKView *)mtkView {
    self = [super init];
    if(self) {
        _mtkView = mtkView;
        [self setupVertices];
        [self setupPipeline];
        [self loadTGATexture];
    }
    
    return self;
}
  • 1.设置顶点。
  • 2.设置管线。
  • 3.加载纹理。

2.1设置顶点:

- (void)setupVertices {
    static const CQVertexTextureInput vertices[] = {
        //像素坐标,纹理坐标
        { {  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 } },
    };
    
    _verticesBuffer = [_mtkView.device newBufferWithBytes:vertices length:sizeof(vertices) options:MTLResourceStorageModeShared];
    _verticesNumber = sizeof(vertices)/sizeof(CQVertexTextureInput);
}
  • _verticesBuffer:创建的顶点缓冲区MTLBuffer
  • _verticesNumber:顶点数量。

2.设置管线:

- (void)setupPipeline {
    //加载所有的metal文件
    id<MTLLibrary> defaultLibrary = [_mtkView.device newDefaultLibrary];
    id<MTLFunction> vertexShaderTexture = [defaultLibrary newFunctionWithName:@"vertexShaderTexture"];
    id<MTLFunction> fragmentShaderTexture = [defaultLibrary newFunctionWithName:@"fragmentShaderTexture"];
    
    MTLRenderPipelineDescriptor *renderPipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
    renderPipelineDescriptor.vertexFunction = vertexShaderTexture;
    renderPipelineDescriptor.fragmentFunction = fragmentShaderTexture;
    renderPipelineDescriptor.colorAttachments[0].pixelFormat = _mtkView.colorPixelFormat;
    
    NSError *error;
    _renderPipelineState = [_mtkView.device newRenderPipelineStateWithDescriptor:renderPipelineDescriptor error:&error];
    if (!_renderPipelineState) {
        NSLog(@"Make render pipeline state error:%@", error);
        return;
    }
    _commandQueue = [_mtkView.device newCommandQueue];   
}

3.1加载tga格式纹理:

- (void)loadTGATexture {
    //1.获取图片
    NSURL *imageUrl = [[NSBundle mainBundle] URLForResource:@"Image" withExtension:@"tga"];
    CCImage *image = [[CCImage alloc] initWithTGAFileAtLocation:imageUrl];
//    AAPLImage *image = [[AAPLImage alloc] initWithTGAFileAtLocation:imageUrl];
    if(!image) {
        NSLog(@"Failed to create the image from:%@",imageUrl.absoluteString);
    }
    //2.创建纹理描述符
    MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
    textureDescriptor.width = image.width;
    textureDescriptor.height = image.height;
    textureDescriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
    //3.创建纹理
    //识别图像中的像素。MTLOrigin通常用作纹理区域的左上角。
    MTLOrigin origion = MTLOriginMake(0, 0, 0);
    MTLSize size = MTLSizeMake(image.width, image.height, 1);
    MTLRegion region = {origion, size};
    
    _texture = [_mtkView.device newTextureWithDescriptor:textureDescriptor];
    [_texture replaceRegion:region mipmapLevel:0 withBytes:image.data.bytes bytesPerRow:image.width * 4];
}
  • CCImage:自定义的类处理tga格式的图片。
    大家可以参考一下官方案例 Creating and Sampling TexturesAAPLImage类对tga格式的图片处理。
  • MTLRegion是个结构体:用于标识纹理的特定区域,本案例使用图像数据填充整个纹理。因此,覆盖整个纹理的像素区域等于纹理的尺寸。
typedef struct  {
     MTLOrigin origin; //开始位置x,y,z
     MTLSize   size; //尺寸width,height,depth
 } MTLRegion;
  • _texture:通过纹理描述符创建的纹理对象id<MTLTexture>

- (void)replaceRegion:(MTLRegion)region mipmapLevel:(NSUInteger)level withBytes:(const void *)pixelBytes bytesPerRow:(NSUInteger)bytesPerRow;
region:像素区域在纹理中的位置
level:从零开始的值,指定哪个mipmap级别的目标。如果纹理没有mipmap,使用0。
pixelBytes:指向要复制图片的字节数。
bytesPerRow:每一行像素的所占字节大小。

3.2加载png、jpg格式纹理:

-(void)loadPNGTexture {
    UIImage *image = [UIImage imageNamed:@"scence.png"];
    MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
    //表示每个像素有蓝色,绿色,红色和alpha通道.其中每个通道都是8位无符号归一化的值.(即0映射成0,255映射成1);
    textureDescriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
    textureDescriptor.width = image.size.width;
    textureDescriptor.height = image.size.height;
  
    _texture = [_mtkView.device newTextureWithDescriptor:textureDescriptor];

    MTLOrigin origion = MTLOriginMake(0, 0, 0);
    MTLSize size = MTLSizeMake(image.size.width, image.size.height, 1);
    MTLRegion region = {origion, size};
    
    //获取图片数据
    Byte *imageBytes = [self loadImage:image];
    
    //UIImage的数据需要转成二进制才能上传,且不用jpg、png的NSData
    if (imageBytes) {
        [_texture replaceRegion:region
                        mipmapLevel:0
                          withBytes:imageBytes
                        bytesPerRow:4 * image.size.width];
        free(imageBytes);
        imageBytes = NULL;
    }
}
  • UIImage的数据需要转成二进制Byte才能上传,且不用jpg、pngNSData
  • 获取图片数据Byte:
- (Byte *)loadImage:(UIImage *)image {
    // 1.获取图片的CGImageRef
    CGImageRef spriteImage = image.CGImage;
    
    // 2.读取图片的大小
    size_t width = CGImageGetWidth(spriteImage);
    size_t height = CGImageGetHeight(spriteImage);
   
    //3.计算图片大小.rgba共4个byte
    Byte * spriteData = (Byte *)calloc(width * height * 4, sizeof(Byte));
    
    //4.创建画布
    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
    
    //5.在CGContextRef上绘图
    CGContextDrawImage(spriteContext, CGRectMake(0, 0, width, height), spriteImage);
    
    //6.图片翻转过来
    CGRect rect = CGRectMake(0, 0, width, height);
    CGContextTranslateCTM(spriteContext, rect.origin.x, rect.origin.y);
    CGContextTranslateCTM(spriteContext, 0, rect.size.height);
    CGContextScaleCTM(spriteContext, 1.0, -1.0);
    CGContextTranslateCTM(spriteContext, -rect.origin.x, -rect.origin.y);
    CGContextDrawImage(spriteContext, rect, spriteImage);
    
    //7.释放spriteContext
    CGContextRelease(spriteContext);
    
    return spriteData;
}

4.MTKViewDelegate绘制纹理

- (void)drawInMTKView:(nonnull MTKView *)view {
    
    id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    commandBuffer.label = @"TextureCommandBuffer";
    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
    if (renderPassDescriptor) {
        id<MTLRenderCommandEncoder> renderCommandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
        renderCommandEncoder.label = @"TextureRenderCommandEncoder";
        MTLViewport viewport = {0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0};
        [renderCommandEncoder setViewport:viewport];
        [renderCommandEncoder setRenderPipelineState:_renderPipelineState];
        [renderCommandEncoder setVertexBuffer:_verticesBuffer offset:0 atIndex:CQVInTextureIndexVertices];
        [renderCommandEncoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:CQVInIndexViewportSize];
        [renderCommandEncoder setFragmentTexture:_texture atIndex:CQTextureIndexBaseColor];
        
        [renderCommandEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:_verticesNumber];
        [renderCommandEncoder endEncoding];
        [commandBuffer presentDrawable:view.currentDrawable];
    }
    
    [commandBuffer commit];
}

- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size {
    _viewportSize.x = size.width;
    _viewportSize.y = size.height;
}
  • - (void)setVertexBuffer:(nullable id <MTLBuffer>)buffer offset:(NSUInteger)offset atIndex:(NSUInteger)index;
    将顶点缓冲区_verticesBuffer的顶点数据传递到顶点函数指定顶点的索引CQVInTextureIndexVertices
  • - (void)setVertexBytes:(const void *)bytes length:(NSUInteger)length atIndex:(NSUInteger)index;
    为给定 绑定位置的 顶点缓冲区 设置顶点数据(通过复制)。这将从 绑定的位置 移除任何现有的MTLBuffer
    该案例的目的是利用该函数 将视口大小的指针地址 传递到顶点函数。
  • - (void)setFragmentTexture:(nullable id <MTLTexture>)texture atIndex:(NSUInteger)index;
    在给定绑定位置索引的地方为所有的片元着色器设置全局的纹理。

绘制效果:


官方案例 Creating and Sampling Textures

相关文章

  • Metal 加载纹理

    本案例的讲解是在 Metal 绘制网格 案例基础上的讲解。 首先说明下改案例需要的几个类: CQMetalText...

  • Metal 加载纹理

    MTLTexture 一个MTLTexture对象代表了一个格式化后的图像数据的内存空间,它可以被用于顶点着色器、...

  • Metal 加载图片

    如何使用 Metal 加载纹理, 图片格式包括 .tga / .png / .jpg 加载TGA图片 加载流...

  • Metal 基础任务和概念 - 06

    创建和采样纹理 将图像数据加载到纹理中并将其应用于四边形 概述 您可以使用纹理在metal中绘制和处理图像。纹理是...

  • 四:Metal示例

    Metal示例demo Metal加载多顶点文件 主要代码: Metal加载多tga文件 主要代码: Metal加...

  • 004-GLKit加载图片

    使用GLKit加载图片 GLKTextureInfo 加载纹理, 封装了申请纹理, 绑定纹理, copy纹理数据到...

  • Metal - 纹理(二)

    纹理(二) sRGB 颜色空间 sRGB 是一种标准的颜色格式,它是站在我们人类肉眼的对不同颜色的可分辨度和敏感度...

  • Metal - 纹理(一)

    啥是馒头(Metal) 纹理(一) UV 坐标 UV 坐标是用于描述纹理贴纸的坐标系。所有的图像文件都是一个二维的...

  • Metal 创建和采样纹理

    您可以使用纹理在Metal中绘制和处理图像。纹理是纹理元素的结构化集合,通常称为纹理元素或像素。这些纹理元素的确切...

  • 案例08:隧道

    OpenGL + OpenGL ES +Metal 系列文章汇总 本案例主要目的多个纹理如何使用,加深对纹理的使用...

网友评论

      本文标题:Metal 加载纹理

      本文链接:https://www.haomeiwen.com/subject/kelksktx.html