美文网首页
Metal 绘制网格

Metal 绘制网格

作者: Maji1 | 来源:发表于2020-08-27 15:17 被阅读0次

之前写过入门案例 Metal MTKView入门案例 请参考。

我们先来看下本案例的实现效果图:


首先说明一下该案例需要构建的几个类:

  • CQGridsVC:网格控制器。用于初始化MTKViewCQGridsRender
  • CQGrids.metal:编写 MSL 程序。
  • CQGridsRender:负责网格渲染的类。
  • CQGrids.h:用于定义 .metalCQGridsRender共用的数据类型。
一、首先看下CQGrids.metal代码:
#include <metal_stdlib>
using namespace metal;

#import "CQGrids.h"

struct VertexOut {
    float4 clipSpacePosition [[position]];
    float4 color;
};

vertex VertexOut vertexShaderGrid(uint vertexIndex [[vertex_id]],
                              constant CQVertexInput *vertexInput [[buffer(CQVertexInputIndexVertices)]],
                              constant vector_uint2 *viewportSizePointer [[buffer(CQVertexInputIndexViewportSize)]]) {
    //取出顶点坐标的xy,该案例中的位置是在像素维度中指定的。
    float2 pixelSpacePosition = vertexInput[vertexIndex].position.xy;
    //视口
    vector_float2 viewportSize = vector_float2 (*viewportSizePointer);
    //每个顶点着色器的输出位置在 剪辑空间中(也称为归一化设备坐标空间,NDC坐标空间)。
    //剪辑空间的(-1,-1)表示视口的左下角,剪辑空间的(1,1)表示视口的右上角。
    //为了从像素空间中的位置转换到剪辑空间的位置,我们将像素坐标除以视口大小的一半。
    float2 xy = pixelSpacePosition / (viewportSize / 2.0);
    
    VertexOut out;
    out.clipSpacePosition.xy = xy;
    out.clipSpacePosition.zw = vector_float2(0.0, 1.0);
    out.color = vertexInput[vertexIndex].color;
    return out;
}

fragment float4 fragmentShaderGrid(VertexOut in [[stage_in]]) {
    return in.color;
}

顶点函数片元函数 的相关名称这里就不做介绍了,不了解的可以参考下Metal MTKView入门案例

介绍下 修饰符 constant 常量地址空间: 指向的缓存对象也是从设备内存池分配的,但是它是只读的。
更多介绍在文章 Metal 语言规范 中查看。

因为我们这里传过来的 *vertexInput顶点数据指针中的 position是像素空间坐标位置,所以我们需要进行归一化处理。也就是这里传过来*viewportSizePointer视口大小的指针的原因。

坐标系转换

float2 xy = pixelSpacePosition / (viewportSize / 2.0);这句代码就是对上图坐标系归一化的处理。

二、看下CQGridsRender代码:

  • 2.1加载metal资源:
- (void)loadMetal:(MTKView *)mtkView {
    //1.
    id<MTLLibrary> library = [mtkView.device newDefaultLibrary];
    id<MTLFunction> vertexShaderFunction = [library newFunctionWithName:@"vertexShaderGrid"];
    id<MTLFunction> fragmentShaderFunction = [library newFunctionWithName:@"fragmentShaderGrid"];
    //2.
    MTLRenderPipelineDescriptor *renderPipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
    renderPipelineDescriptor.label = @"GridsRenderPipelineDescriptor";
    renderPipelineDescriptor.vertexFunction = vertexShaderFunction;
    renderPipelineDescriptor.fragmentFunction = fragmentShaderFunction;
    //设置管道中存储颜色数据的组件格式
    renderPipelineDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
    //3.
    NSError *error;
    _renderPipelineState = [_device newRenderPipelineStateWithDescriptor:renderPipelineDescriptor error:&error];
    if (!_renderPipelineState) {
        NSLog(@"Failed to created pipeline state, error %@", error);
        return;
    }
    //4.
    NSData *verticesData = [CQGridsRender generateVerticesData];
    //5.创建一个vertex buffer,可以由GPU来读取
    _vertexBuffer = [_device newBufferWithLength:verticesData.length options:MTLResourceStorageModeShared];
    /*
    memcpy(void *dst, const void *src, size_t n);
    dst:目的地
    src:源内容
    n: 长度
    */
    memcpy(_vertexBuffer.contents, verticesData.bytes, verticesData.length);
    _totalNumVertices = verticesData.length / sizeof(CQVertexInput);
    //6.
    _commandQueue = [_device newCommandQueue];
    
}
  • 1.加载 顶点函数 和 片元函数。
  • 2.创建渲染管线描述符MTLRenderPipelineDescriptor
  • 3.创建渲染管线状态id<MTLRenderPipelineState>对象。
  • 4.生成顶点数据verticesData.
  • 5.创建id<MTLBuffer>顶点缓冲区。
  • 6.创建命令队列id<MTLCommandQueue>

这里我们重点看下第4步生 成顶点数据verticesData

  • 2.2生成顶点数据:
+ (nonnull NSData *)generateVerticesData {
    const CQVertexInput gridVertices[] =
    {
        // Pixel 位置,       RGBA 颜色
        { { -20.0,  20.0 }, { 1.0, 0.0, 0.6, 1.0 } },
        { {  20.0,  20.0 }, { 1.0, 0.0, 0.6, 1.0 } },
        { { -20.0, -20.0 }, { 1.0, 0.0, 0.6, 1.0 } },
        
        { {  20.0, -20.0 }, { 0.0, 1.0, 0.0, 1.0 } },
        { { -20.0, -20.0 }, { 0.0, 1.0, 0.0, 1.0 } },
        { {  20.0,  20.0 }, { 0.0, 1.0, 0.0, 1.0 } },
    };
    
    const NSInteger columNum = 25;
    const NSInteger rowNum = 15;
    const float gridSpacing = 50.0;//四边形间距
    //单个网格顶点个数
    const NSInteger singleGridVertexNum = sizeof(gridVertices) / sizeof(CQVertexInput);
    //所有网格顶点数据占用内存大小
    NSUInteger gridsDataSize = sizeof(gridVertices) * columNum * rowNum;
    NSMutableData *verticsData = [[NSMutableData alloc] initWithLength:gridsDataSize];
    CQVertexInput * currentGrid = verticsData.mutableBytes;
    
    //获取顶点坐标
    for(NSUInteger rowIndex = 0; rowIndex < rowNum; rowIndex++) {
        for(NSUInteger columnIndex = 0; columnIndex < columNum; columnIndex++) {
            
            vector_float2 upperLeftPosition;//左上角的位置
            
            //计算 X,Y 位置,注意坐标系基于2D笛卡尔坐标系 中心点(0,0),所以会出现负数位置。
            upperLeftPosition.x = ((-((float)columNum) / 2.0) + columnIndex) * gridSpacing + gridSpacing/2.0;
            
            upperLeftPosition.y = ((-((float)rowNum) / 2.0) + rowIndex) * gridSpacing + gridSpacing/2.0;
            
            //将gridVertices数据复制到currentGrid
            memcpy(currentGrid, &gridVertices, sizeof(gridVertices));
            
            //遍历currentGrid中的数据
            for (NSUInteger vertexInGrid = 0; vertexInGrid < singleGridVertexNum; vertexInGrid++)
            {
                //修改vertexInGrid中的position
                currentGrid[vertexInGrid].position += upperLeftPosition;
            }
            
            //更新索引
            currentGrid += 6;
        }
    }
    
    return verticsData;
}
  • 该段代码中顶点坐标数组gridVertices[],类型是
typedef struct {
    vector_float2  position;//顶点位置
    vector_float4  color;//颜色
}CQVertexInput;

该顶点数组中的position的值为 像素坐标系 中的坐标,这也就是我们一开始在.metal文件中归一化的原因。

  • 顶点数据的生成逻辑在代码中做了详细介绍,参考注释理解一下。

  • 2.3修改视口的大小

- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size {
    _viewportSize.x = size.width;
    _viewportSize.y = size.height;
}

当视图大小发生变化时,我们这里需要同步修改下视口的大小。

  • 2.4绘制
- (void)drawInMTKView:(nonnull MTKView *)view {
    //1.
    id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    commandBuffer.label = @"GridsCommandBuffer";
    //2.
    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
    if (renderPassDescriptor) {
        //3.
        id<MTLRenderCommandEncoder> renderCommandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
        renderCommandEncoder.label = @"GridsRenderCommandEncoder";
        
        MTLViewport viewport = {0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0 };
        [renderCommandEncoder setViewport:viewport];//视口
        [renderCommandEncoder setRenderPipelineState:_renderPipelineState];//渲染管道状态
        //将_vertexBuffer 设置到顶点缓存区中
        [renderCommandEncoder setVertexBuffer:_vertexBuffer offset:0 atIndex:CQVertexInputIndexVertices];
        //将 _viewportSize 设置到顶点缓存区绑定点设置数据
        [renderCommandEncoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:CQVertexInputIndexViewportSize];
        //4.绘制
        [renderCommandEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:_totalNumVertices];
        //5.
        [renderCommandEncoder endEncoding];
        [commandBuffer presentDrawable:view.currentDrawable];
    }
    [commandBuffer commit];
}
  • 1.创建命令缓冲区id<MTLCommandBuffer>
  • 2.获取渲染过程描述符MTLRenderPassDescriptor。一组渲染目标,用作渲染通道生成的像素的输出目标。
  • 3.创建渲染命令编码器id<MTLRenderCommandEncoder>
  • 4.绘制。
  • 5.结束编码endEncoding。表示该编码器生成的命令都已完成,并且从MTLCommandBuffer中分离。
  • 6.presentDrawable:一旦框架缓冲区完成,使用当前可绘制的进度表。
  • 7.commit提交命令。完成渲染并将命令缓冲区推送到GPU

相关文章

  • Metal 绘制网格

    之前写过入门案例 Metal MTKView入门案例 请参考。 我们先来看下本案例的实现效果图: 首先说明一下该案...

  • Metal 加载纹理

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

  • Metal绘制流程

    Metal的基本绘制流程、多线程渲染参考:Metal多线程渲染

  • Metal应用--绘制图片

    用Metal绘制图片跟用Metal应用--绘制大量顶点整体流程类似,但是由于是纹理,在部分地方有些区别。 LeoS...

  • Metal入门教程(四)灰度计算

    前言 Metal入门教程(一)图片绘制Metal入门教程(二)三维变换Metal入门教程(三)摄像头采集渲染 前面...

  • Metal入门教程(三)摄像头采集渲染

    前言 Metal入门教程(一)图片绘制Metal入门教程(二)三维变换 前面的教程介绍了如何绘制一张图片和如何把图...

  • Metal入门教程总结

    前言 Metal入门教程(一)图片绘制Metal入门教程(二)三维变换Metal入门教程(三)摄像头采集渲染Met...

  • Metal入门教程(七)天空盒全景

    前言 Metal入门教程(一)图片绘制Metal入门教程(二)三维变换Metal入门教程(三)摄像头采集渲染Met...

  • Metal入门教程(八)Metal与OpenGL ES交互

    前言 Metal入门教程(一)图片绘制Metal入门教程(二)三维变换Metal入门教程(三)摄像头采集渲染Met...

  • Metal入门教程(五)视频渲染

    前言 Metal入门教程(一)图片绘制Metal入门教程(二)三维变换Metal入门教程(三)摄像头采集渲染Met...

网友评论

      本文标题:Metal 绘制网格

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