美文网首页
Metal实现动态背景和三角形渲染

Metal实现动态背景和三角形渲染

作者: 丸疯 | 来源:发表于2020-08-21 17:59 被阅读0次

Tips

我们开发Metal时,将渲染环节独立到成自己创建的类。使用单独的类,我们可以更好的管理初始化Metal,以及处理MTKViewDelegate。在此项目中我们创建了继承于NSObjectHelloRender类,来处理背景的渲染。TriangleRender处理三角形的渲染。

  • Metal的图形管道


    Metal图形管道图示.png
  • Metal命令对象之间的关系
    1 命令缓存区(command buffer)是从命令队列(command queue)创建的
    2 命令编码器(command encoders)将命令编码到命令缓冲区中
    3 提交命令缓冲区并将其发送到GPU
    4 GPU执行命令并将结果呈现为可绘制
    Metal命令对象之间的关系.png

实现效果

实现效果.gif

动态背景

HelloRender.h文件中
  • 我们重写HelloRender的初始化方法以获取到Render需要作用的MTKView
- (instancetype)initWithMetalKitView:(MTKView *)mtkView;
HelloRender.m文件中
  • 构建一个颜色结构体
// 颜色结构体
typedef struct{
    float red, green, blue, alph;
}Color;
  • Metal是驱使GPU来做渲染,在我们的HelloRender.m文件中,我们将设备(MTLDevice)和命令队列(MTLCommandQueue)构建出来
    id <MTLDevice> _device;
    id <MTLCommandQueue> _commandQueue;
  • 在Render的初始化方法中,获取到_device_commandQueue
        _device = mtkView.device;
        
        //所有应用程序需要与GPU交互的第一个对象是一个MTLCommandQueue对象
        //你使用MTLCommandQueue去创建对象,并且加入MTLCommandBuffer对象中.确保它们能够按照正确顺序发送到GPU.对于每一帧,一个新的MTLCommandBuffer 对象创建并且填满了由GPU执行的命令.
        _commandQueue = [_device newCommandQueue];
  • 实现MTKView的delegate的两个方法
    1 - (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size
    因为这里我们不会去对视图大小做出变化,所以这里不需要操作
    2 - (void)drawInMTKView:(nonnull MTKView *)view
// 获取颜色值
    Color color = [self getDisplayColor];
    
    //设置view的color
    view.clearColor = MTLClearColorMake(color.red, color.green, color.blue, color.alph);
    
    //Create a new command buffer for each render pass to the current drawable
    //使用MTLCommandQueue 创建对象并且加入到MTCommandBuffer对象中去.
    //为当前渲染的每个渲染传递创建一个新的命令缓冲区
    id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    commandBuffer.label = @"My Command";
    
    //从视图绘制中获得渲染描述符
    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
    
    if (renderPassDescriptor != nil) {
        //通过渲染描述符renderPassDescriptor创建MTLRenderCommandEncoder 对象
        id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
        renderEncoder.label = @"My RenderEncoder";
        
        //我们可以使用MTLRenderCommandEncoder 来绘制对象,但是这个demo我们仅仅创建编码器就可以了,我们并没有让Metal去执行我们绘制的东西,这个时候表示我们的任务已经完成.
        //即可结束MTLRenderCommandEncoder 工作
        [renderEncoder endEncoding];
        
        /*
         当编码器结束之后,命令缓存区就会接受到2个命令.
         1) present
         2) commit
         因为GPU是不会直接绘制到屏幕上,因此你不给出去指令.是不会有任何内容渲染到屏幕上.
        */
        //添加一个最后的命令来显示清除的可绘制的屏幕
        [commandBuffer presentDrawable:view.currentDrawable];
    }
    
    //在这里完成渲染并将命令缓冲区提交给GPU
    [commandBuffer commit];
  • 构建随机颜色函数- (Color)getDisplayColor;
    //增加/减少颜色的标记
    static BOOL growing = YES;
    //颜色通道值
    static NSUInteger primaryChannel = 0;
    //颜色通道数组
    static float colorChannels[] = {1.0, 0.0, 0.0, 1.0};
    //颜色调整步长
    const float dynamicColorRite = 0.020;
    
    //判断
    if (growing) {
        //动态通道索引(1,2,3,0)通道间切换
        NSUInteger dynamicChannelIndex = (primaryChannel + 1) % 3;
        
        colorChannels[dynamicChannelIndex] += dynamicColorRite;
        
        if (colorChannels[dynamicChannelIndex] >= 1.0) {
            
            //不再增长
            growing = NO;
            
            //将颜色通道修改为动态颜色通道
            primaryChannel = dynamicChannelIndex;
        }
    }else{
        //动态通道索引(1,2,3,0)通道间切换
        NSUInteger dynamicChannelIndex = (primaryChannel + 1) % 3;
        
        //将当前颜色的值 减去0.015
        colorChannels[dynamicChannelIndex] -= dynamicColorRite;
        
        //当颜色值小于等于0.0
        if(colorChannels[dynamicChannelIndex] <= 0.0) {
            //又调整为颜色增加
            growing = YES;
        }
    }
    
    Color color;
    color.red = colorChannels[0];
    color.green = colorChannels[1];
    color.blue = colorChannels[2];
    color.alph = colorChannels[3];
    
    return color;
  • 在controller中
_mtkView = [[MTKView alloc] init];
    [self.view addSubview:self.mtkView];
    [_mtkView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.bottom.top.equalTo(self.view);
    }];
    
    UILabel * label = [[UILabel alloc] init];
    label.textColor = [UIColor blackColor];
    label.font = [UIFont systemFontOfSize:40];
    label.text = @"Hello Metal";
    
    [self.view addSubview:label];
    [label mas_makeConstraints:^(MASConstraintMaker *make) {
        make.centerX.equalTo(self.view.mas_centerX);
        make.centerY.equalTo(self.view.mas_centerY);
    }];
    
    //为mtkViw设置device
    //一个MTLDevice 对象就代表这着一个GPU,通常我们可以调用方法MTLCreateSystemDefaultDevice()来获取代表默认的GPU单个对象.
    _mtkView.device = MTLCreateSystemDefaultDevice();
    
    if (!_mtkView.device) {
        NSLog(@"Metal is not supported on this device");
        return;
    }
    
    //创建helloRender
    //在我们开发Metal 程序时,将渲染循环分为自己创建的类,是非常有用的一种方式,使用单独的类,我们可以更好管理初始化Metal,以及Metal视图委托.
    _render = [[HelloRender alloc] initWithMetalKitView:_mtkView];
    
    if (!_render) {
        NSLog(@"Renderer failed initialization");
        return;
    }
    //设置MTKView 的代理(由CCRender来实现MTKView 的代理方法)
    _mtkView.delegate = _render;
 
    //视图可以根据视图属性上设置帧速率(指定时间来调用drawInMTKView方法--视图需要渲染时调用)
    _mtkView.preferredFramesPerSecond = 60;

三角形渲染

  • 在controller中基本没有什么变化,主要的操作在Render和Metal文件中,这样可以和controller中的业务分离开,便于处理
  • TriangleRender
    - (instancetype)initWithMetalkView:(MTKView *)mtkView函数中
    在获取Device之后,首先需要加载Metal文件
id <MTLLibrary> defaultLibrary = [_device newDefaultLibrary];

然后加载顶点函数和片元函数,这里的名字要对应.metal中的函数名

//从库中加载顶点函数
id <MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
//从库中加载片元函数
id <MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];

创建管道并指定可编程的顶点/片元函数,以及存储颜色的组件

//配置用于创建管道状态的管道
MTLRenderPipelineDescriptor * pipelineDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
//管道名称
pipelineDescriptor.label = @"simple pipeline";
//可编程函数,用于处理渲染过程中的各个顶点
pipelineDescriptor.vertexFunction = vertexFunction;
//可编程函数,用于处理渲染过程中的各个片元/片段
pipelineDescriptor.fragmentFunction = fragmentFunction;
//存储颜色数据的组件
pipelineDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
// 同步创建,并返回渲染管线状态
 _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineDescriptor error:&error];
//判断是否返回了管线状态
if (!_pipelineState) {
  //如果我们没有正确设置管道描述符,则管道状态创建可能失败
  NSLog(@"Failed to created pipeline state, error %@", error);
  return nil;
}

之后就是创建命令队列了。

  • - (void)drawInMTKView:(nonnull MTKView *)view函数中
    实现流程.png
    实现代码
    //设置背景色
    view.clearColor = MTLClearColorMake(1, 1, 1, 1);
   // 顶点、颜色数据
    static const TriangleVertex triangleVertices[] =
    {
        {{ 0.5, -0.25, 0.0, 1.0}, {1.0, 0.0, 0.0, 1.0}},
        {{-0.5, -0.25, 0.0, 1.0}, {0.0, 1.0, 0.0, 1.0}},
        {{-0.0,  0.25, 0.0, 1.0}, {0.0, 0.0, 1.0, 1.0}},
    };
    
    //为当前渲染的每个每个渲染传递创建一个新的命令缓冲区
    id <MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    //指定缓冲区名字
    commandBuffer.label = @"commandBuffer";
    
    //MTLRenderPassDescriptor:一组渲染目标,用作渲染通道生成的像素的输出目标(描述信息)
    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
    if (renderPassDescriptor != nil) {
        //创建渲染命令编码器,这样我们才可以渲染到something
        id <MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
        //命名
        renderEncoder.label = @"My RenderEncoder";
        
        //设置绘制区域
        /*
        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 setRenderPipelineState:_pipelineState];
        
        // 从应用中发送数据到metal顶点着色器函数
        //顶点数据+颜色数据
        //   1) 指向要传递给着色器的内存的指针
        //   2) 我们想要传递的数据的内存大小
        //   3)一个整数索引,它对应于我们的“vertexShader”函数中的缓冲区属性限定符的索引。
        [renderEncoder setVertexBytes:triangleVertices
                               length:sizeof(triangleVertices)
                              atIndex:TriangleVertexInputIndexVertices];
        
        //viewPortSize 数据
        //1) 发送到顶点着色函数中,视图大小
        //2) 视图大小内存空间大小
        //3) 对应的索引
        [renderEncoder setVertexBytes:&_viewportSize
                               length:sizeof(_viewportSize)
                              atIndex:TriangleVertexInputIndexViewpostSize];
        
        //画出三角形的三个顶点
        // @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];
        
        //表示已该编码器生成的命令都已完成,并且从MTLCommandBuffer中分离
        [renderEncoder endEncoding];
        
        //
        [commandBuffer presentDrawable:view.currentDrawable];
    }
    [commandBuffer commit];
  • TrianleShader.metal文件中
    定义了两个函数vertexShaderfragmentShader
    代码如下
vertex RasterizerData vertexShader(uint vertexId [[vertex_id]],
                                   constant TriangleVertex * vertices[[buffer(TriangleVertexInputIndexVertices)]],
                                   constant vector_uint2 * viewportSizePointer [[buffer(TriangleVertexInputIndexViewpostSize)]]
                                   ){
    /*
     处理顶点数据:
        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 = vertices[vertexId].position;
    
    //把我们输入的颜色直接赋值给输出颜色. 这个值将于构成三角形的顶点的其他颜色值插值,从而为我们片段着色器中的每个片段生成颜色值.
    out.color = vertices[vertexId].color;
    
    //完成! 将结构体传递到管道中下一个阶段:
    return out;
}
//当顶点函数执行3次,三角形的每个顶点执行一次后,则执行管道中的下一个阶段.栅格化/光栅化.
// 片元函数
//[[stage_in]],片元着色函数使用的单个片元输入数据是由顶点着色函数输出.然后经过光栅化生成的.单个片元输入函数数据可以使用"[[stage_in]]"属性修饰符.
//一个顶点着色函数可以读取单个顶点的输入数据,这些输入数据存储于参数传递的缓存中,使用顶点和实例ID在这些缓存中寻址.读取到单个顶点的数据.另外,单个顶点输入数据也可以通过使用"[[stage_in]]"属性修饰符的产生传递给顶点着色函数.
//被stage_in 修饰的结构体的成员不能是如下这些.Packed vectors 紧密填充类型向量,matrices 矩阵,structs 结构体,references or pointers to type 某类型的引用或指针. arrays,vectors,matrices 标量,向量,矩阵数组.
fragment float4 fragmentShader(RasterizerData in [[stage_in]]){
    //返回输入的片元颜色
    return in.color;
}

最后附上demo地址Metal案例Demo

相关文章

  • Metal实现动态背景和三角形渲染

    Tips 我们开发Metal时,将渲染环节独立到成自己创建的类。使用单独的类,我们可以更好的管理初始化Metal,...

  • 十九、Metal渲染实例

    一、利用Metal实现渲染图形 加载一个由N个三角形组成的双色四边形 加载流程 在VC中创建渲染Renderer,...

  • 二:Metal三角形

    创建Metal文件 创建C 与 OC的桥接文件 渲染循环类头文件 渲染循环类实现文件 渲染循环类的使用 Metal...

  • Metal实现视频文件的渲染

    如何通过Metal实现视频文件的渲染? 与Metal 实现摄像头采集内容的渲染 不同的是,获取到的媒体样本是YU...

  • Metal:实现摄像头的实时渲染

    前沿 实现摄像头的实时渲染我们需要做三个工作,1.Metal准备,2.获取实时渲染所需要的数据,3.实现Metal...

  • Metal入门003-案例:渲染背景色

    Metal入门001-初识MetalMetal入门002-Metal相关API 本文通过渲染背景色这个小案例,展现...

  • 2019-09-03metal绘制个三角形

    渲染一个简单的二维三角形。 本文内容来自苹果 本文主要讲述使用Metal绘制到屏幕时,将视图的内容删除为背景颜色。...

  • GPU的渲染过程

    什么是管道?GPU的渲染过程有哪些?Metal和MetalKit的区别?Metal渲染过程涉及哪些API?

  • Metal绘制流程

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

  • Metal 实现摄像头采集内容的渲染

    如何通过Metal实现摄像头采集内容的即时渲染? 实现摄像头采集内容渲染的流程 通过AVFoundation进行视...

网友评论

      本文标题:Metal实现动态背景和三角形渲染

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