美文网首页
Metal渲染加载颜色、三角形

Metal渲染加载颜色、三角形

作者: windy_3c22 | 来源:发表于2020-08-27 14:39 被阅读0次

    1、渲染加载颜色

    颜色、三角形案例demo

    未命名.gif 渲染加载颜色

    导入MetalKit工具包,@import MetalKit;
    我们接受苹果的建议分离渲染循环。另建文件CCRender,遵循MTKViewDelegate协议。
    在Controller中创建需要的MTKView、CCRender对象

    • MTKView对象,设置metal设备(GPU)。及渲染循环CCrender对象。
    //创建MTKView。设置代理为负责渲染的CCRender
     _mtkView = [[MTKView alloc]initWithFrame:CGRectMake(10, 10, self.view.frame.size.width-20, self.view.frame.size.height-20) device:MTLCreateSystemDefaultDevice()];
     [self.view addSubview:_mtkView];
    
    • 创建负责渲染CCRender
    //创建负责渲染的CCRender,
    _render = [[CCRender alloc]initWithMetalKitView:_mtkView];
    
    • 设置MTKView的代理为负责渲染CCRender
    _mtkView.delegate = _render;
    
    • 设置帧速率
      帧速率越大,每秒处理的帧数越多,变化越快。
      也可以不设置。使用默认帧速率值
    _mtkView.preferredFramesPerSecond = 100;
    

    CCRender 渲染循环类

    • 1、设置CCRender当前的deviceMTKViewdevice
    _device = mtkView.device;
    
    • 2、从当前设备device(GPU)创建命令队列MTLCommandQueue
    _commandQueue = [_device newCommandQueue];
    
    • 3、执行渲染的代理方法-(void)drawInMTKView:(nonnull MTKView *)view每当视图进行渲染是调用。
    • 3.1、获取变化的颜色、设置清屏颜色
        //获取颜色
        Color color = [self makeFancyColor];
        //设置清屏颜色
        view.clearColor = MTLClearColorMake(color.red, color.green, color.blue, color.alpha);
    
    • 3.2、为当前渲染创建新的命令缓冲区
      创建渲染缓存区,目的是为了将渲染对象加入到渲染缓存区,使用MTLCommandQueue 创建对象并且加入到MTCommandBuffer对象中,且为当前渲染的每个渲染传递创建一个新的命令缓冲区。
       //为当前渲染的每个渲染传递创建一个新的命令缓冲区
        id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
        //指定缓存区名称
        commandBuffer.label = @"MyCommand";
    
    • 3.3、从当前视图获取渲染描述符MTLRenderPassDescriptor,用于创建命令编译器MTLRenderCommandEncoder
    //获取渲染描述符
    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
    //创建命令编译器
    if(renderPassDescriptor != nil)
        {
            //4.创建渲染命令编码器,这样我们才可以渲染到something
            id<MTLRenderCommandEncoder> renderEncoder =[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
            //渲染器名称
            renderEncoder.label = @"MyRenderEncoder";
            [renderEncoder endEncoding];
            /*
             当编码器结束之后,命令缓存区就会接受到2个命令.
             1) present
             2) commit
             因为GPU是不会直接绘制到屏幕上,因此你不给出去指令.是不会有任何内容渲染到屏幕上.
            */
            //添加一个最后的命令来显示清除的可绘制的屏幕
            [commandBuffer presentDrawable:view.currentDrawable];
        }
    
    • 3.4、结束渲染。将命令缓冲区提交GPU
    [commandBuffer commit];
    
    • 设置颜色变换
    //颜色结构体
    typedef struct {
        float red,green,blue,alpha;
    }Color;
    
    -(Color)makeFancyColor{
        //增加颜色 、减少颜色标记
        static BOOL growing = YES;
        //颜色通道(0~3)
        static NSUInteger primaryChannel = 0;
        //3.颜色通道数组colorChannels(颜色值)
        static float      colorChannels[] = {1.0, 0.0, 0.0, 1.0};
        //4.颜色调整步长
        const float DynamicColorRate = 0.015;
        if (growing) {
            //动态信道索引(1,2,3,0)通道间的切换
            NSInteger dynamicChannelIndex = (primaryChannel + 1) % 3;
            //修改对应通道的颜色值 调整0.015
            colorChannels[dynamicChannelIndex] += DynamicColorRate;
            //当颜色通道值 = 1.0时
            if (colorChannels[dynamicChannelIndex] >= 1.0) {
                //设置为no
                growing = NO;
                //将颜色通道修改为动态颜色通道
                primaryChannel = dynamicChannelIndex;
            }
        }else{
            //获取动态颜色通道
            NSUInteger dynamicChannelIndex = (primaryChannel+2)%3;
            
            //将当前颜色的值 减去0.015
            colorChannels[dynamicChannelIndex] -= DynamicColorRate;
            
            //当颜色值小于等于0.0
            if(colorChannels[dynamicChannelIndex] <= 0.0)
            {
                //又调整为颜色增加
                growing = YES;
            }
        }
        //创建颜色
        Color color;
        
        //修改颜色的RGBA的值
        color.red   = colorChannels[0];
        color.green = colorChannels[1];
        color.blue  = colorChannels[2];
        color.alpha = colorChannels[3];
        
        //返回颜色
        return color;
    }
    

    2、绘制三角形

    三角形 绘制三角形

    创建Metal着色器文件
    定义顶点着色器的输入、片元着色器的输出

    // 顶点着色器输出和片段着色器输入
    //结构体
    typedef struct
    {
        //处理空间的顶点信息
        float4 clipSpacePosition [[position]];
        //颜色
        float4 color;
    } RasterizerData;
    

    顶点着色器函数、片元着色器函数

    /*
     * vertex:修饰符,表示是顶点着色器
     * RasterizerData:返回值
     * vertexShader:函数名称,自定义
     * vertexID:当前所处理的顶点号。metal自己反馈的id 
     * vertices:1、告诉存储的位置buffer。 2、告诉传递数据的入口是CCVertexInputIndexVertices
     * viewportSizePointer:视口
     * vertices 和 viewportSizePointer 都是通过CCRender传递进来的
     */
    
    //顶点着色函数
    vertex RasterizerData
    vertexShader(uint vertexID [[vertex_id]],
                 constant CCVertex *vertices [[buffer(CCVertexInputIndexVertices)]],
                 constant vector_uint2 *viewportSizePointer [[buffer(CCVertexInputIndexViewportSize)]])
    {
        //定义返回值
       RasterizerData out; 
       //当前所处理的顶点
       out.clipSpacePosition = vertices[vertexID].position;
    
      //当前处理的顶点的颜色值。桥接片元着色器
      //把我们输入的颜色直接赋值给输出颜色.
      out.color = vertices[vertexID].color;
    
      //完成! 将结构体传递到管道中下一个阶段:
       return out;
    }
    
    /* fragment:修饰符,表示片元着色器
     * float4:返回值,颜色值RBGA
     * fragmentShader:函数名称,自定义
     * RasterizerData:参数类型
     * in:函数中形参变量(可修改)
     * [[stage_in]]:表示单个片元输入(由顶点函数输出,然后经过光栅化生成)(不可修改),相当于OpenGL ES中的fragment中的varying与顶点着色器中桥接的对应
    */
    
    fragment float4 fragmentShader(RasterizerData in [[stage_in]])
    {
        //返回输入的片元颜色
        return in.color;
    }
    

    创建桥接文件
    创建此文件为了Metal shadersC/OBJC 源之间共享的类型和枚举常数。
    缓存区索引值 共享与shaderC 代码 为了确保Metal Shader缓存区索引能够匹配 Metal API Buffer设置的集合调用

    //缓存区索引值,表示向metal着色器传递数据的入口枚举值
    typedef enum CCVertexInputIndex
    {
        //顶点
        CCVertexInputIndexVertices     = 0,
        //视图大小
        CCVertexInputIndexViewportSize = 1,
    } CCVertexInputIndex;
    
    //图形数据的结构体: 顶点/颜色值
    typedef struct
    {
        // 像素空间的位置
        // 像素中心点(100,100)
        vector_float4 position;
    
        // RGBA颜色
        vector_float4 color;
    } CCVertex;
    

    Controller初始化
    创建MTKView对象设置设备device(GPU)
    创建CCRender对象,设置MTKView的代理

    //创建mtkView
     _mtkView = [[MTKView alloc]initWithFrame:CGRectMake(10, 10, self.view.frame.size.width-20, self.view.frame.size.height-20) device:MTLCreateSystemDefaultDevice()];
    [self.view addSubview:_mtkView];
    
     if (!_mtkView.device) {
          NSLog(@"Metal is not supported on this device");
          return;
      }
    _triangleRender = [[TriangleRender alloc]initMetalMtkView:_mtkView];
     _mtkView.delegate = _triangleRender; 
    [_triangleRender mtkView:_mtkView drawableSizeWillChange:_mtkView.drawableSize];
    

    负责渲染的CCRender

    • 设置当前设备device、创建命令队列MTLCommandQueue
    //1.获取GPU 设备
     _device = mtkView.device;
    //5.创建命令队列
     _commandQueue = [_device newCommandQueue];
    
    • 加载(.metal)着色器文件
     //2.在项目中加载所有的(.metal)着色器文件
     // 从bundle中获取.metal文件
    id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
     //从库中加载顶点函数
    id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"]; 
    //从库中加载片元函数
    id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
    
    • 配置渲染管道MTLRenderPipelineDescriptor
    //3.配置用于创建管道状态的管道
    MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
    //管道名称
    pipelineStateDescriptor.label = @"Simple Pipeline";
    //可编程函数,用于处理渲染过程中的各个顶点
    pipelineStateDescriptor.vertexFunction = vertexFunction;
    //可编程函数,用于处理渲染过程中各个片段/片元
    pipelineStateDescriptor.fragmentFunction = fragmentFunction;
    //一组存储颜色数据的组件  
    pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
    
    • 创建渲染管道对象MTLRenderPipelineDescriptor
      从当前device(GPU)创建配置好的渲染管道对象
    _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
    
    • 视图变化的代理方法
    //当前视图大小,
    vector_uint2 _viewportSize;
    
    //每当视图改变方向或调整大小时调用
    - (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size
    {
        // 保存可绘制的大小,因为当我们绘制时,我们将把这些值传递给顶点着色器
        _viewportSize.x = size.width;
        _viewportSize.y = size.height;
    }
    
    • 渲染代理方法
    //每当视图需要渲染帧时调用
    - (void)drawInMTKView:(nonnull MTKView *)view
    {
        //1. 顶点数据/颜色数据
        static const CCVertex triangleVertices[] =
        {
            //顶点,    RGBA 颜色值
            { {  0.5, -0.25, 0.0, 1.0 }, { 1, 0, 0, 1 } },
            { { -0.5, -0.25, 0.0, 1.0 }, { 0, 1, 0, 1 } },
            { { -0.0f, 0.25, 0.0, 1.0 }, { 0, 0, 1, 1 } },
        };
    
        //2.为当前渲染的每个渲染传递创建一个新的命令缓冲区
        id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
        //指定缓存区名称
        commandBuffer.label = @"MyCommand";
        
    
        // MTLRenderPassDescriptor:一组渲染目标,用作渲染通道生成的像素的输出目标。
        MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
        //判断渲染目标是否为空
        if(renderPassDescriptor != nil)
        {
            //4.创建渲染命令编码器,这样我们才可以渲染到something
            id<MTLRenderCommandEncoder> renderEncoder =[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
            //渲染器名称
            renderEncoder.label = @"MyRenderEncoder";
    
            //5.设置我们绘制的可绘制区域
            /*
            typedef struct {
                double originX, originY, width, height, znear, zfar;
            } MTLViewport;
             */
            //视口指定Metal渲染内容的drawable区域。 视口是具有x和y偏移,宽度和高度以及近和远平面的3D区域
            //为管道分配自定义视口需要通过调用setViewport:方法将MTLViewport结构编码为渲染命令编码器。 如果未指定视口,Metal会设置一个默认视口,其大小与用于创建渲染命令编码器的drawable相同。
            MTLViewport viewPort = {
                0.0,0.0,_viewportSize.x,_viewportSize.y,-1.0,1.0
            };
            [renderEncoder setViewport:viewPort];
            //[renderEncoder setViewport:(MTLViewport){0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0 }];
            
            //6.设置当前渲染管道状态对象
            [renderEncoder setRenderPipelineState:_pipelineState];
        
            
            //7.从应用程序OC 代码 中发送数据给Metal 顶点着色器 函数
            //顶点数据+颜色数据
            //   1) 指向要传递给着色器的内存的指针
            //   2) 我们想要传递的数据的内存大小
            //   3)一个整数索引,它对应于我们的“vertexShader”函数中的缓冲区属性限定符的索引。
    
            [renderEncoder setVertexBytes:triangleVertices
                                   length:sizeof(triangleVertices)
                                  atIndex:CCVertexInputIndexVertices];
    
            //viewPortSize 数据
            //1) 发送到顶点着色函数中,视图大小
            //2) 视图大小内存空间大小
            //3) 对应的索引
            [renderEncoder setVertexBytes:&_viewportSize
                                   length:sizeof(_viewportSize)
                                  atIndex:CCVertexInputIndexViewportSize];
    
           
            
            //8.画出三角形的3个顶点
            // @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:3];
    
            //9.表示已该编码器生成的命令都已完成,并且从NTLCommandBuffer中分离
            [renderEncoder endEncoding];
    
            //10.一旦框架缓冲区完成,使用当前可绘制的进度表
            [commandBuffer presentDrawable:view.currentDrawable];
        }
    
        //11.最后,在这里完成渲染并将命令缓冲区推送到GPU
        [commandBuffer commit];
    }
    

    相关文章

      网友评论

          本文标题:Metal渲染加载颜色、三角形

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