美文网首页OpenGL & Metal
Metal应用--绘制大量顶点

Metal应用--绘制大量顶点

作者: 黑眼豆豆_ | 来源:发表于2020-09-03 14:11 被阅读0次

    这个案例中,我们用Metal Shading Language绘制一个图形,如下所示:

    Metal.png
    在这个案例中,我们可以看到,整个图形有无数个小的正方形,而一个正方形是有两个小的三角形组成。

    项目结构

    项目结构如图所示:


    项目结构
    • LeoRenderer
      分开渲染循环:在我们开发Metal 程序时,将渲染循环分为自己创建的类,是非常有用的一种方式,使用单独的类,我们可以更好管理初始化Metal,以及Metal视图委托。
    • LeoShaderType
      头文件包含了 Metal shaders 与C/OBJC 源之间共享的类型和枚举常数
    • LeoShaders.metal
      LeoShaders.metal就是用来编写Metal Shading Language的文件。
      创建方法如下:
      cmd + N,选择Metal File
      创建Metal.png

    LeoShaderType.h

    LeoShaderType.h的作用就是联通OC/C和Metal文件之间的桥梁。在这个项目中,通过定义LeoShaderType.h,我们可以让数据在LeoRendererLeoShaders.metal之间进行传递。

    • 我们定义一个枚举,其中包括顶点视图大小,由于OC传递到Metal的函数需要一个整数所以,所以Metal建议用枚举来表示整数索引,更易读。
    typedef enum {
        //顶点
        LeoVertexInputIndexVertices     = 0,
        //视图大小
        LeoVertexInputIndexViewportSize = 1,
    } LeoVertexInputIndex;
    
    • 定义一个结构体,包含顶点颜色值,用结构体存储顶点数据和颜色值。
    //结构体: 顶点/颜色值
    typedef struct {
        // 像素空间的位置
        // 像素中心点(100,100)
        //float float
        vector_float2 position;
        // RGBA颜色
        //float float float float
        vector_float4 color;
    } LeoVertex;
    

    LeoShaders.metal

    • 我们定义一个结构体用来封装从顶点着色器给片元着色器传的值
    // 顶点着色器输出和片段着色器输入
    //结构体
    typedef struct
    {
        //处理空间的顶点信息
        float4 clipSpacePosition [[position]];
        
        //颜色
        float4 color;
        
    } RasterizerData;
    
    • 顶点着色器
      由于我们的顶点是屏幕上真实的像素点,所以需要转换成归一化设备坐标空间,即取值从[-1,1]。
        // 我们的位置是在像素维度中指定的.
        float2 pixelSpacePosition = vertices[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);
    

    再将颜色数据存储到RasterizerData中传递给片元着色器

     //把我们输入的颜色直接赋值给输出颜色
    out.color = vertices[vertexID].color;
    //完成! 将结构体传递到管道中下一个阶段:
    return out;
    

    完整代码如下:

    //顶点着色器
    vertex RasterizerData vertexShader(uint vertexID[[vertex_id]],constant LeoVertex *vertices [[buffer(LeoVertexInputIndexVertices)]],constant vector_uint2 *viewportSizePointer [[buffer(LeoVertexInputIndexViewportSize)]]){
     
        /*
         处理顶点数据:
         1) 执行坐标系转换,将生成的顶点剪辑空间写入到返回值中.
         2) 将顶点颜色值传递给返回值
         */
        
        //定义out
        RasterizerData out;
        //初始化输出剪辑空间位置
        out.clipSpacePosition = vector_float4(0.0,0.0,0.0,1.0);
        
        // 索引到我们的数组位置以获得当前顶点
        // 我们的位置是在像素维度中指定的.
        float2 pixelSpacePosition = vertices[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.color = vertices[vertexID].color;
        
        //完成! 将结构体传递到管道中下一个阶段:
        return out;
    }
    
    • 片元着色器
      片元着色器的操作很简单,就是将拿到颜色直接进行返回。当然,在数据从顶点着色器到片元着色器之间,会进行图元装配、光栅化等操作。
    fragment float4 fragmentShader(RasterizerData in [[stage_in]]){
        //返回输入的片元颜色
        return in.color;
    }
    

    LeoRenderer

    • 定义一些变量
    @implementation LeoRenderer{
        //渲染的设备(GPU)
        id<MTLDevice> _device;
        
        //渲染管道:顶点着色器/片元着色器,存储于.metal shader文件中
        id<MTLRenderPipelineState> _pipelineState;
        
        //命令队列,从命令缓存区获取
        id<MTLCommandQueue> _commandQueue;
        
        //顶点缓存区
        id<MTLBuffer> _vertexBuffer;
        
        //当前视图大小,这样我们才能在渲染通道中使用此视图
        vector_uint2 _viewportSize;
        
        //顶点个数
        NSInteger _numVertices;
    }
    

    由于在这个案例中,我们有大量的顶点,所以我们需要把顶点存放在GPU中,否则无法显示,所以我们定义了一个id<MTLBuffer> _vertexBuffer来存放顶点。

    当顶点数据小于4kb 使用MTLRenderCommandEncoder的setVertexBytes:length:atIndex:方法传递顶点数据;
    当顶点数据大于4kb使用MTLRenderCommandEncoder的setVertexBufferOffset:atIndex:放大传递顶点数据

    • 配置渲染管道,并从Metal文件中读取顶点着色器函数片元着色器函数(函数名一定要和Metal中的函数名一致)
        //1.设置绘制纹理的像素格式
        mtkView.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
        
        //2.从项目中加载所以的.metal着色器文件
        id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
        //从库中加载顶点函数
        id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
        //从库中加载片元函数
        id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
        
        //3.配置用于创建管道状态的管道
        MTLRenderPipelineDescriptor *pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
        //管道名称
        pipelineDescriptor.label = @"Simple Pipeline";
        //可编程函数,用于处理渲染过程中的各个顶点
        pipelineDescriptor.vertexFunction = vertexFunction;
        //可编程函数,用于处理渲染过程总的各个片段/片元
        pipelineDescriptor.fragmentFunction = fragmentFunction;
        //设置管道中存储颜色数据的组件格式
        pipelineDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
        
        //4.同步创建并返回渲染管线对象
        NSError *error = NULL;
        _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
        
        //判断是否创建成功
        if (!_pipelineState) {
            NSLog(@"Failed to created pipeline state, error %@", error);
            return;
        }
    
    • 获取顶点数据
    //顶点数据
    + (nonnull NSData *)generateVertexData
    {
        //1.正方形 = 三角形+三角形
        const LeoVertex quadVertices[] =
        {
            // Pixel 位置, RGBA 颜色
            { { -20,   20 },    { 1, 0, 0, 1 } },
            { {  20,   20 },    { 1, 0, 0, 1 } },
            { { -20,  -20 },    { 1, 0, 0, 1 } },
            
            { {  20,  -20 },    { 0, 0, 1, 1 } },
            { { -20,  -20 },    { 0, 0, 1, 1 } },
            { {  20,   20 },    { 0, 0, 1, 1 } },
        };
        //行/列 数量
        const NSUInteger NUM_COLUMNS = 25;
        const NSUInteger NUM_ROWS = 15;
        //顶点个数
        const NSUInteger NUM_VERTICES_PER_QUAD = sizeof(quadVertices) / sizeof(LeoVertex);
        //四边形间距
        const float QUAD_SPACING = 50.0;
        //数据大小 = 单个四边形大小 * 行 * 列
        NSUInteger dataSize = sizeof(quadVertices) * NUM_COLUMNS * NUM_ROWS;
        
        //2. 开辟空间
        NSMutableData *vertexData = [[NSMutableData alloc] initWithLength:dataSize];
        //当前四边形
        LeoVertex * currentQuad = vertexData.mutableBytes;
        
        //3.获取顶点坐标(循环计算)
        //行
        for(NSUInteger row = 0; row < NUM_ROWS; row++)
        {
            //列
            for(NSUInteger column = 0; column < NUM_COLUMNS; column++)
            {
                //A.左上角的位置
                vector_float2 upperLeftPosition;
                
                //B.计算X,Y 位置.注意坐标系基于2D笛卡尔坐标系,中心点(0,0),所以会出现负数位置
                upperLeftPosition.x = ((-((float)NUM_COLUMNS) / 2.0) + column) * QUAD_SPACING + QUAD_SPACING/2.0;
                
                upperLeftPosition.y = ((-((float)NUM_ROWS) / 2.0) + row) * QUAD_SPACING + QUAD_SPACING/2.0;
                
                //C.将quadVertices数据复制到currentQuad
                memcpy(currentQuad, &quadVertices, sizeof(quadVertices));
                
                //D.遍历currentQuad中的数据
                for (NSUInteger vertexInQuad = 0; vertexInQuad < NUM_VERTICES_PER_QUAD; vertexInQuad++)
                {
                    //修改vertexInQuad中的position
                    currentQuad[vertexInQuad].position += upperLeftPosition;
                }
                
                //E.更新索引
                currentQuad += 6;
            }
        }
        return vertexData;
    }
    
    • 将顶点数据存储到显存中
        //5.获取顶点数据
        NSData *vertexData = [LeoRenderer generateVertexData];
        _vertexBuffer = [_device newBufferWithLength:vertexData.length options:(MTLResourceStorageModeShared)];
        //复制vertex data 到vertex buffer 通过缓存区的"content"内容属性访问指针
        /*
         memcpy(void *dst, const void *src, size_t n);
         dst:目的地
         src:源内容
         n: 长度
         */
        memcpy(_vertexBuffer.contents, vertexData.bytes, vertexData.length);
        //计算顶点个数 = 顶点数据长度 / 单个顶点大小
        _numVertices = vertexData.length / sizeof(LeoVertex);
        
        //6.创建命令队列
        _commandQueue = [_device newCommandQueue];
    
    • 用代理方法drawableSizeWillChange设置视图大小
    //每当视图改变方向或调整大小时调用
    - (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size{
        // 保存可绘制的大小,因为当我们绘制时,我们将把这些值传递给顶点着色器
        _viewportSize.x = size.width;
        _viewportSize.y = size.height;
    }
    
    • 具体绘制操作,执行代理方法drawInMTKView
    //每当视图需要渲染帧时调用
    -(void)drawInMTKView:(MTKView *)view{
        //1.为当前渲染的每个渲染传递创建一个新的命令缓冲区
        id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
        //指定缓存区名称
        commandBuffer.label = @"MyCommand";
        //2. MTLRenderPassDescriptor:一组渲染目标,用作渲染通道生成的像素的输出目标。
        //currentRenderPassDescriptor 从currentDrawable's texture,view's depth, stencil, and sample buffers and clear values.
        MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
        //判断渲染目标是否为空
        if (renderPassDescriptor != nil) {
            //创建渲染命令编码器,这样我们才可以渲染到something
            id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
            //渲染器名称
            renderEncoder.label = @"MyRenderEncoder";
            
            //3.设置我们绘制的可绘制区域
            /*
             typedef struct {
             double originX, originY, width, height, znear, zfar;
             } MTLViewport;
             */
            [renderEncoder setViewport:(MTLViewport){0.0,0.0,_viewportSize.x,_viewportSize.y,-1.0,1.0}];
            
            //4. 设置渲染管道
            [renderEncoder setRenderPipelineState:_pipelineState];
            
            //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:LeoVertexInputIndexVertices];
            
            //将 _viewportSize 设置到顶点缓存区绑定点设置数据
            [renderEncoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:LeoVertexInputIndexViewportSize];
            
            //6.开始绘图
            // @method drawPrimitives:vertexStart:vertexCount:
            //@brief 在不使用索引列表的情况下,绘制图元
            //@param 绘制图形组装的基元类型
            //@param 从哪个位置数据开始绘制,一般为0
            //@param 每个图元的顶点个数,绘制的图型顶点数量
            /*
             MTLPrimitiveTypePoint = 0, 点
             MTLPrimitiveTypeLine = 1, 线段
             MTLPrimitiveTypeLineStrip = 2, 线环
             MTLPrimitiveTypeTriangle = 3,  三角形
             MTLPrimitiveTypeTriangleStrip = 4, 三角型扇
             */
    
            [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:_numVertices];
            
            //7/表示已该编码器生成的命令都已完成,并且从NTLCommandBuffer中分离
            [renderEncoder endEncoding];
            
            //8.一旦框架缓冲区完成,使用当前可绘制的进度表
            [commandBuffer presentDrawable:view.currentDrawable];
        }
        
        //9.最后,在这里完成渲染并将命令缓冲区推送到GPU
        [commandBuffer commit];
    
    }
    

    所以LeoRenderer的整体代码如下:

    
    @import MetalKit;
    #import "LeoRenderer.h"
    
    //头,在C代码之间共享
    #import "LeoShaderType.h"
    
    @implementation LeoRenderer{
        //渲染的设备(GPU)
        id<MTLDevice> _device;
        
        //渲染管道:顶点着色器/片元着色器,存储于.metal shader文件中
        id<MTLRenderPipelineState> _pipelineState;
        
        //命令队列,从命令缓存区获取
        id<MTLCommandQueue> _commandQueue;
        
        //顶点缓存区
        id<MTLBuffer> _vertexBuffer;
        
        //当前视图大小,这样我们才能在渲染通道中使用此视图
        vector_uint2 _viewportSize;
        
        //顶点个数
        NSInteger _numVertices;
    }
    
    -(id)initWithMetalKitView:(MTKView *)mtkView{
        self = [super init];
        if (self) {
            //1.初始GPU设备
            _device = mtkView.device;
            //2.加载Metal文件
            [self loadMetal:mtkView];
        }
        return self;
    }
    
    -(void)loadMetal:(MTKView *)mtkView{
        //1.设置绘制纹理的像素格式
        mtkView.colorPixelFormat = MTLPixelFormatBGRA8Unorm;
        
        //2.从项目中加载所以的.metal着色器文件
        id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
        //从库中加载顶点函数
        id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
        //从库中加载片元函数
        id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
        
        //3.配置用于创建管道状态的管道
        MTLRenderPipelineDescriptor *pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
        //管道名称
        pipelineDescriptor.label = @"Simple Pipeline";
        //可编程函数,用于处理渲染过程中的各个顶点
        pipelineDescriptor.vertexFunction = vertexFunction;
        //可编程函数,用于处理渲染过程总的各个片段/片元
        pipelineDescriptor.fragmentFunction = fragmentFunction;
        //设置管道中存储颜色数据的组件格式
        pipelineDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
        
        //4.同步创建并返回渲染管线对象
        NSError *error = NULL;
        _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
        
        //判断是否创建成功
        if (!_pipelineState) {
            NSLog(@"Failed to created pipeline state, error %@", error);
            return;
        }
        
        //5.获取顶点数据
        NSData *vertexData = [LeoRenderer generateVertexData];
        _vertexBuffer = [_device newBufferWithLength:vertexData.length options:(MTLResourceStorageModeShared)];
        //复制vertex data 到vertex buffer 通过缓存区的"content"内容属性访问指针
        /*
         memcpy(void *dst, const void *src, size_t n);
         dst:目的地
         src:源内容
         n: 长度
         */
        memcpy(_vertexBuffer.contents, vertexData.bytes, vertexData.length);
        //计算顶点个数 = 顶点数据长度 / 单个顶点大小
        _numVertices = vertexData.length / sizeof(LeoVertex);
        
        //6.创建命令队列
        _commandQueue = [_device newCommandQueue];
    }
    
    
    //顶点数据
    + (nonnull NSData *)generateVertexData
    {
        //1.正方形 = 三角形+三角形
        const LeoVertex quadVertices[] =
        {
            // Pixel 位置, RGBA 颜色
            { { -20,   20 },    { 1, 0, 0, 1 } },
            { {  20,   20 },    { 1, 0, 0, 1 } },
            { { -20,  -20 },    { 1, 0, 0, 1 } },
            
            { {  20,  -20 },    { 0, 0, 1, 1 } },
            { { -20,  -20 },    { 0, 0, 1, 1 } },
            { {  20,   20 },    { 0, 0, 1, 1 } },
        };
        //行/列 数量
        const NSUInteger NUM_COLUMNS = 25;
        const NSUInteger NUM_ROWS = 15;
        //顶点个数
        const NSUInteger NUM_VERTICES_PER_QUAD = sizeof(quadVertices) / sizeof(LeoVertex);
        //四边形间距
        const float QUAD_SPACING = 50.0;
        //数据大小 = 单个四边形大小 * 行 * 列
        NSUInteger dataSize = sizeof(quadVertices) * NUM_COLUMNS * NUM_ROWS;
        
        //2. 开辟空间
        NSMutableData *vertexData = [[NSMutableData alloc] initWithLength:dataSize];
        //当前四边形
        LeoVertex * currentQuad = vertexData.mutableBytes;
        
        
        //3.获取顶点坐标(循环计算)
        //行
        for(NSUInteger row = 0; row < NUM_ROWS; row++)
        {
            //列
            for(NSUInteger column = 0; column < NUM_COLUMNS; column++)
            {
                //A.左上角的位置
                vector_float2 upperLeftPosition;
                
                //B.计算X,Y 位置.注意坐标系基于2D笛卡尔坐标系,中心点(0,0),所以会出现负数位置
                upperLeftPosition.x = ((-((float)NUM_COLUMNS) / 2.0) + column) * QUAD_SPACING + QUAD_SPACING/2.0;
                
                upperLeftPosition.y = ((-((float)NUM_ROWS) / 2.0) + row) * QUAD_SPACING + QUAD_SPACING/2.0;
                
                //C.将quadVertices数据复制到currentQuad
                memcpy(currentQuad, &quadVertices, sizeof(quadVertices));
                
                //D.遍历currentQuad中的数据
                for (NSUInteger vertexInQuad = 0; vertexInQuad < NUM_VERTICES_PER_QUAD; vertexInQuad++)
                {
                    //修改vertexInQuad中的position
                    currentQuad[vertexInQuad].position += upperLeftPosition;
                }
                
                //E.更新索引
                currentQuad += 6;
            }
        }
        return vertexData;
    }
    
    //每当视图改变方向或调整大小时调用
    - (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size{
        // 保存可绘制的大小,因为当我们绘制时,我们将把这些值传递给顶点着色器
        _viewportSize.x = size.width;
        _viewportSize.y = size.height;
    }
    
    //每当视图需要渲染帧时调用
    -(void)drawInMTKView:(MTKView *)view{
        //1.为当前渲染的每个渲染传递创建一个新的命令缓冲区
        id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
        //指定缓存区名称
        commandBuffer.label = @"MyCommand";
        //2. MTLRenderPassDescriptor:一组渲染目标,用作渲染通道生成的像素的输出目标。
        //currentRenderPassDescriptor 从currentDrawable's texture,view's depth, stencil, and sample buffers and clear values.
        MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
        //判断渲染目标是否为空
        if (renderPassDescriptor != nil) {
            //创建渲染命令编码器,这样我们才可以渲染到something
            id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
            //渲染器名称
            renderEncoder.label = @"MyRenderEncoder";
            
            //3.设置我们绘制的可绘制区域
            /*
             typedef struct {
             double originX, originY, width, height, znear, zfar;
             } MTLViewport;
             */
            [renderEncoder setViewport:(MTLViewport){0.0,0.0,_viewportSize.x,_viewportSize.y,-1.0,1.0}];
            
            //4. 设置渲染管道
            [renderEncoder setRenderPipelineState:_pipelineState];
            
            //5.我们调用-[MTLRenderCommandEncoder setVertexBuffer:offset:atIndex:] 为了从我们的OC代码找发送数据预加载的MTLBuffer 到我们的Metal 顶点着色函数中
            /* 这个调用有3个参数
                1) buffer - 包含需要传递数据的缓冲对象
                2) offset - 它们从缓冲器的开头字节偏移,指示“顶点指针”指向什么。在这种情况下,我们通过0,所以数据一开始就被传递下来.偏移量
                3) index - 一个整数索引,对应于我们的“vertexShader”函数中的缓冲区属性限定符的索引。注意,此参数与 -[MTLRenderCommandEncoder setVertexBytes:length:atIndex:] “索引”参数相同。
             */
            
            //将_vertexBuffer 设置到顶点缓存区中3
            [renderEncoder setVertexBuffer:_vertexBuffer offset:0 atIndex:LeoVertexInputIndexVertices];
            
            //将 _viewportSize 设置到顶点缓存区绑定点设置数据
            [renderEncoder setVertexBytes:&_viewportSize length:sizeof(_viewportSize) atIndex:LeoVertexInputIndexViewportSize];
            
            //6.开始绘图
            // @method drawPrimitives:vertexStart:vertexCount:
            //@brief 在不使用索引列表的情况下,绘制图元
            //@param 绘制图形组装的基元类型
            //@param 从哪个位置数据开始绘制,一般为0
            //@param 每个图元的顶点个数,绘制的图型顶点数量
            /*
             MTLPrimitiveTypePoint = 0, 点
             MTLPrimitiveTypeLine = 1, 线段
             MTLPrimitiveTypeLineStrip = 2, 线环
             MTLPrimitiveTypeTriangle = 3,  三角形
             MTLPrimitiveTypeTriangleStrip = 4, 三角型扇
             */
    
            [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:_numVertices];
            
            //7/表示已该编码器生成的命令都已完成,并且从NTLCommandBuffer中分离
            [renderEncoder endEncoding];
            
            //8.一旦框架缓冲区完成,使用当前可绘制的进度表
            [commandBuffer presentDrawable:view.currentDrawable];
        }
        
        //9.最后,在这里完成渲染并将命令缓冲区推送到GPU
        [commandBuffer commit];
    
    }
    @end
    

    ViewController

    最后在ViewController进行调用

    //
    //  ViewController.m
    //  OpenGL_ES_Test
    //
    //  Created by leosun on 2020/7/27.
    //  Copyright © 2020 leosun. All rights reserved.
    //
    
    #import "ViewController.h"
    #import "LeoRenderer.h"
    
    @interface ViewController (){
        MTKView *_view;
        LeoRenderer *_render;
    }
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        //1. 获取_view
        _view = (MTKView *)self.view;
        
        //2.为_view 设置MTLDevice(必须)
        //一个MTLDevice 对象就代表这着一个GPU,通常我们可以调用方法MTLCreateSystemDefaultDevice()来获取代表默认的GPU单个对象.
        _view.device = MTLCreateSystemDefaultDevice();
        
        //3.判断是否设置成功
        if (!_view.device) {
            NSLog(@"Metal is not supported on this device");
            return;
        }
        
        //4. 创建LeoRenderer
        //分开你的渲染循环:
        //在我们开发Metal 程序时,将渲染循环分为自己创建的类,是非常有用的一种方式,使用单独的类,我们可以更好管理初始化Metal,以及Metal视图委托.
        _render = [[LeoRenderer alloc] initWithMetalKitView:_view];
    
        //5.判断_render 是否创建成功
        if (!_render) {
            NSLog(@"Renderer failed initialization");
            return;
        }
        
        //用视图大小初始化渲染器
        [_render mtkView:_view drawableSizeWillChange:_view.drawableSize];
    
        //6.设置MTKView 的代理(由CCRender来实现MTKView 的代理方法)
        _view.delegate = _render;
    }
    
    @end
    

    相关文章

      网友评论

        本文标题:Metal应用--绘制大量顶点

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