• Metal 三、案例-Metal 大量顶点数据时处理方案 MTLBuffer + 加载纹理


    一、Metal 大量顶点数据处理

    如上图,setVertexByte: 方法对数据是有限制的 不能大于4K。当大量数据,超过了4K时,我们可以使用 MTLBuffer。

    1、MTLBuffer

    当顶点数量太多时,对CPU的消耗会增大,尤其在游戏、AI等场景中,为更好的扩展管理 (并不是为了图形图片的加载),Metal 提出了一个新的对象:MTLBuffer;

    Metal --> MTLBuffer --> 缓存区(可以存储了大量的自定义数据,GPU直接访问 <-- 缓存区存在显存中) --> 存储顶点数据

    1.1、当顶点数据超过了 4K 上限时,"setVertexBytes:length:atIndex:" 方法会不再生效 --> 简单的绘制三角形 每个顶点 xyzw 占用4*8=32 字节 --> 我们可根据实际业务场景考量是否需要使用 MTLBuffer

    2、MTLBuffer 使用

    这里不再重复贴代码,可 通过绘制三角形 Demo 进行对比。更多案例可通过 Metal Sample Code 官方 Demo 示例 来下载了解 Metal 相关 API 的使用。

    通过 buffer 传值:

    // 5.调用 [MTLRenderCommandEncoder setVertexBuffer:offset:atIndex:] 是从 OC 代码找 发送数据预加载的MTLBuffer 到 Metal 顶点着色函数中
    /* 这个调用有3个参数
            1) buffer - 包含需要传递数据的缓冲对象
            2) offset - 它们从缓冲器的开头字节偏移,指示“顶点指针”指向什么。在这种情况下,我们通过0,所以数据一开始就被传递下来.偏移量
            3) index - 一个整数索引,对应于 “vertexShader” 函数中的 缓冲区属性限定符 的 索引。注意,此参数与 -[MTLRenderCommandEncoder setVertexBytes:length:atIndex:] “索引”参数相同。
    */
    // 将 _vertexBuffer 设置到顶点缓存区中
    [renderEncoder setVertexBuffer:_vertexBuffer offset:0 atIndex:MyVertexInputIndexVertices];
    
    /* ##不需要设置缓冲区,普通传递顶点数据的方法 -- 具体流程使用见绘制三角形 demo
    // 顶点 + 颜色
    [renderEncoder setVertexBytes:triangleVertices  length:sizeof(triangleVertices) atIndex:MyVertexInputIndexVertices];
    */

    缓冲区的创建:

    // 5.获取顶点数据
    NSData *vertexData = [CCRenderer generateVertexData];
    // 创建一个vertex buffer,可以由GPU来读取
    _vertexBuffer = [_device newBufferWithLength:vertexData.length
                                          options:MTLResourceStorageModeShared];
    // 复制vertex data 到vertex buffer 通过缓存区的"content"内容属性访问指针 --> 顶点数据从内存copy到缓冲区中 MTLBuffer 对象
    /*
       memcpy(void *dst, const void *src, size_t n);
       dst:目的地
       src:源内容
       n: 长度
    */
    memcpy(_vertexBuffer.contents, vertexData.bytes, vertexData.length);

    二、加载纹理

    Metal 加载 .jpg .png 文件

    1、加载流程

    2、主要代码

    纹理处理代码

    // JPG 图片
    -(void)setupTextureJPG {
    
        // 1.获取图片
        UIImage *image = [UIImage imageNamed:@"cat.jpg"];
        // 2.纹理描述符
        MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc] init];
        // 表示每个像素有蓝色,绿色,红色和alpha通道.其中每个通道都是8位无符号归一化的值.(即0映射成0,255映射成1);
        textureDescriptor.pixelFormat = MTLPixelFormatRGBA8Unorm;
        // 设置纹理的像素尺寸
        textureDescriptor.width = image.size.width;
        textureDescriptor.height = image.size.height;
        
        // 3.使用描述符从设备中创建纹理
        _texture = [_device newTextureWithDescriptor:textureDescriptor];
        
        /*
         typedef struct
         {
         MTLOrigin origin; // 开始位置x,y,z
         MTLSize   size; // 尺寸width,height,depth
         } MTLRegion;
         */
        // MLRegion 结构用于标识纹理的特定区域。 demo 使用图像数据填充整个纹理;因此,覆盖整个纹理的像素区域等于纹理的尺寸。
        // 4. 创建 MTLRegion 结构体  [纹理上传的范围]
        MTLRegion region = {{ 0, 0, 0 }, {image.size.width, image.size.height, 1}};
        
        // 5.获取图片数据
        Byte *imageBytes = [self loadImage:image];
        
        // 6.UIImage 的数据需要转成二进制才能上传,且不用 jpg、png 的NSData
        if (imageBytes) {
            [_texture replaceRegion:region
                            mipmapLevel:0
                              withBytes:imageBytes
                            bytesPerRow:4 * image.size.width];
            free(imageBytes);
            imageBytes = NULL;
        }
    }

    Metal 代码:

    // 顶点函数
    vertex RasterizerData
    vertexShader(uint vertexID [[vertex_id]],
                 constant MyVertex *vertexArray [[buffer(MyVertexInputIndexVertices)]],
                 constant vector_uint2 *viewportSizePointer [[buffer(MyVertexInputIndexViewportSize)]]) {
    
        /*
         处理顶点数据:
         1) 执行坐标系转换,将生成的顶点剪辑空间写入到返回值中.
         2) 将顶点颜色值传递给返回值
         */
        
        // 定义 out
        RasterizerData out;
        
        // 初始化输出剪辑空间位置
        out.clipSpacePosition = vector_float4(0.0, 0.0, 0.0, 1.0);
        
        // 索引到我们的数组位置以获得当前顶点
        // 我们的位置是在像素维度中指定的.
        float2 pixelSpacePosition = vertexArray[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.clipSpacePosition.z = 0.0f;
        out.clipSpacePosition.w = 1.0f;
        
        // 把我们输入的颜色直接赋值给输出颜色. 这个值将于构成三角形的顶点的其他颜色值插值,从而为我们片段着色器中的每个片段生成颜色值.
        out.textureCoordinate = vertexArray[vertexID].textureCoordinate;
        
        // 完成! 将结构体传递到管道中下一个阶段:
        return out;
    }
    
    
    // 当顶点函数执行3次,三角形的每个顶点执行一次后,则执行管道中的下一个阶段 --> 栅格化/光栅化.
    
    
    // 片元函数
    fragment float4 fragmentShader(RasterizerData in [[stage_in]],
                                   texture2d<half> colorTexture [[texture(MyTextureIndexBaseColor)]]) {
    
        constexpr sampler textureSampler(mag_filter::linear,
                                         min_filter::linear);
        
        const half4 colorSampler = colorTexture.sample(textureSampler,in.textureCoordinate);
        
        return float4(colorSampler);
    }

    Demo 地址

  • 相关阅读:
    Redis学习笔记——环境搭建
    SQL 记录
    路径“D:svn.....”的访问被拒绝问题处理
    去除浏览器自动给input赋值的问题
    获取用户IP
    JS对身份证号码进行验证方法
    JS 实现倒计时
    SQL 游标
    .net上传图片实例
    生成唯一码
  • 原文地址:https://www.cnblogs.com/zhangzhang-y/p/13572630.html
Copyright © 2020-2023  润新知