Metal渲染加载颜色、三角形

2020-08-27  本文已影响0人  windy_3c22

1、渲染加载颜色

颜色、三角形案例demo

未命名.gif 渲染加载颜色

导入MetalKit工具包,@import MetalKit;
我们接受苹果的建议分离渲染循环。另建文件CCRender,遵循MTKViewDelegate协议。
在Controller中创建需要的MTKView、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,
_render = [[CCRender alloc]initWithMetalKitView:_mtkView];
_mtkView.delegate = _render;
_mtkView.preferredFramesPerSecond = 100;

CCRender 渲染循环类

_device = mtkView.device;
_commandQueue = [_device newCommandQueue];
    //获取颜色
    Color color = [self makeFancyColor];
    //设置清屏颜色
    view.clearColor = MTLClearColorMake(color.red, color.green, color.blue, color.alpha);
   //为当前渲染的每个渲染传递创建一个新的命令缓冲区
    id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    //指定缓存区名称
    commandBuffer.label = @"MyCommand";
//获取渲染描述符
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];
    }
[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

//1.获取GPU 设备
 _device = mtkView.device;
//5.创建命令队列
 _commandQueue = [_device newCommandQueue];
 //2.在项目中加载所有的(.metal)着色器文件
 // 从bundle中获取.metal文件
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
 //从库中加载顶点函数
id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"]; 
//从库中加载片元函数
id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
//3.配置用于创建管道状态的管道
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
//管道名称
pipelineStateDescriptor.label = @"Simple Pipeline";
//可编程函数,用于处理渲染过程中的各个顶点
pipelineStateDescriptor.vertexFunction = vertexFunction;
//可编程函数,用于处理渲染过程中各个片段/片元
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
//一组存储颜色数据的组件  
pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
_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];
}
上一篇下一篇

猜你喜欢

热点阅读