美文网首页
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 绘制网格

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