美文网首页
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 加载纹理

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