• Metal 二、案例-背景渲染+三角形绘制


    通过 Metal,实现 渲染颜色到屏幕 和 三角形的绘制 两个简单案例。

    一、Metal 使用

    Metal Programing Guide 官方文档

    类似于 OpenGL ES 的 GLKit, 苹果为开发者提供了 MetalKit 。并对开发者做了以下建议(当然这只是建议,不是必须遵循的规则):

    1、Separate Your Rendering Loop  --  单独处理你的渲染循环

    即:当我们使用 Metal 开发程序时,将 渲染循环单独做一个类来处理。使用单独的类,我们可以更好的管理 Metal 及 Metal 的视图委托。

    Metal 的相关处理 和 viewController 分开。

    // 示例:
    _render = [[YourRenderClass alloc] initWithMetalKitView:_view];
    _view.delegate = _render;

    2、Respond to View Event  --  视图响应事件

    // 在 MTKViewDelegate 协议中有 2 个方法
    //
    // 每当窗口大小变化或者重新布局(设备方向更改)时,视图就会调用此方法.
     - (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size;
        
    // 每当视图需要渲染时调用 
    // 视图可以根据视图属性上设置 View.preferredFramesPerSecond 帧速率(指定时间来调用 drawInMTKView 方法) --> 默认 60
    - (void)drawInMTKView:(nonnull MTKView *)view;

    3、Metal Command Object  --  Metal 命令对象

    Metal 命令对象之间的关系

    1)命令缓存区(command buffer) 是从 命令队列(command queue) 创建的

    2)命令编码器(command encoders) 将 命令 编码到 命令缓存区

    3)提交 命令缓冲区 并将其发送到 GPU

    4)GPU 执行命令 并将结果 呈现为可绘

    // 一个 MTLDevice 对象表示 GPU,
    // 方法 MTLCreateSystemDefaultDevice(): 获取 默认的 GPU 单个对象.
    _view.device = MTLCreateSystemDefaultDevice();
    
    // 应用程序 需要与 GPU 交互的第一个对象是 MTLCommandQueue 对象
    _commandQueue = [_device newCommandQueue];
    
    // Create a new command buffer for each render pass to the current drawable
    // 为当前渲染的每个渲染传递 创建一个新的命令缓冲区
    // 使用 MTLCommandQueue 创建对象并且加入到 MTCommandBuffer 对象中去。确保它们能够按照正确顺序发送到 GPU,对于每一帧,一个新的 MTLCommandBuffer 对象 创建且填满了由 GPU 执行的命令
    id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];

    二、案例

    此案例实现比较简单,主要是 Metal 的API 是使用,过程均在代码注释中。

    渲染管线的 3 大阶段:

    1、背景色渲染

    Render class:

      1 // .h
      2 #import <Foundation/Foundation.h>
      3 
      4 @import MetalKit;
      5 
      6 NS_ASSUME_NONNULL_BEGIN
      7 
      8 @interface MyMetalRender : NSObject <MTKViewDelegate>
      9 
     10 - (id)initWithMTKView:(MTKView *)mtkView;
     11 
     12 @end
     13 
     14 NS_ASSUME_NONNULL_END
     15 
     16 
     17 // .m
     18 #import "MyMetalRender.h"
     19 
     20 // 定义颜色结构体
     21 typedef struct {
     22     float red, green, blue, alpha;
     23 } Color;
     24 
     25 @implementation MyMetalRender
     26 {
     27     id<MTLDevice> _device;// GPU
     28     id<MTLCommandQueue> _commandQueue;
     29 }
     30 
     31 - (id)initWithMTKView:(MTKView *)mtkView {
     32     
     33     if (self = [super init]) {
     34         _device = mtkView.device;
     35         // 所有应用程序需要与GPU交互的第一个对象是一个对象。MTLCommandQueue.
     36         // 你使用MTLCommandQueue 去创建对象,并且加入MTLCommandBuffer 对象中.确保它们能够按照正确顺序发送到GPU.对于每一帧,一个新的MTLCommandBuffer 对象创建并且填满了由GPU执行的命令.
     37         _commandQueue = [_device newCommandQueue];
     38     }
     39     return self;
     40 }
     41 
     42 // 设置颜色
     43 - (Color)configColor {
     44     
     45     // 1. 增加颜色/减小颜色的 标记
     46     static BOOL       growing = YES;
     47     // 2.颜色通道值(0~3)
     48     static NSUInteger primaryChannel = 0;
     49     // 3.颜色通道数组 colorChannels(颜色值) --> 初始值:红色 透明度1
     50     static float      colorChannels[] = {1.0, 0.0, 0.0, 1.0};
     51     // 4.颜色调整步长 -- 每次变化
     52     const float DynamicColorRate = 0.015;
     53     
     54     // 5.判断
     55     if(growing) {
     56         // 动态信道索引 (1,2,3,0)通道间切换
     57         NSUInteger dynamicChannelIndex = (primaryChannel+1)%3;
     58         
     59         // 修改对应通道的颜色值 调整0.015
     60         colorChannels[dynamicChannelIndex] += DynamicColorRate;
     61         
     62         // 当颜色通道对应的颜色值 = 1.0
     63         if(colorChannels[dynamicChannelIndex] >= 1.0) {
     64             // 设置为NO
     65             growing = NO;
     66             
     67             // 将颜色通道修改为动态颜色通道
     68             primaryChannel = dynamicChannelIndex;
     69         }
     70     }
     71     else {
     72         // 获取动态颜色通道
     73         NSUInteger dynamicChannelIndex = (primaryChannel+2)%3;
     74         
     75         // 将当前颜色的值 减去0.015
     76         colorChannels[dynamicChannelIndex] -= DynamicColorRate;
     77         
     78         // 当颜色值小于等于0.0
     79         if(colorChannels[dynamicChannelIndex] <= 0.0) {
     80             // 又调整为颜色增加
     81             growing = YES;
     82         }
     83     }
     84     
     85     // 创建颜色
     86     Color color;
     87     // 修改颜色的RGBA的值
     88     color.red   = colorChannels[0];
     89     color.green = colorChannels[1];
     90     color.blue  = colorChannels[2];
     91     color.alpha = colorChannels[3];
     92     
     93     // 返回颜色
     94     return color;
     95 }
     96 
     97 #pragma mark - MTKView delegate -
     98 // 每当视图渲染时 调用
     99 - (void)drawInMTKView:(nonnull MTKView *)view {
    100     
    101     // 拿到颜色
    102     Color color = [self configColor];
    103     
    104     // 1. 设置颜色 --> 类似OpenGL ES 的 clearColor
    105     view.clearColor = MTLClearColorMake(color.red, color.green, color.blue, color.alpha);
    106     // 2. Create a new command buffer for each render pass to the current drawable
    107     // 使用MTLCommandQueue 创建对象并且加入到MTCommandBuffer对象中去.
    108     // 为当前渲染的每个渲染传递创建一个新的命令缓冲区
    109     id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    110     commandBuffer.label = @"MyCommandBuffer";
    111     
    112     // 3.从视图中,获得 渲染描述符
    113     MTLRenderPassDescriptor *renderDescriptor = view.currentRenderPassDescriptor;
    114     if (renderDescriptor) {
    115         
    116         // 4.通过渲染描述符 renderPassDescriptor  创建 MTLRenderCommandEncoder 对象
    117         id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderDescriptor];
    118         renderEncoder.label = @"MyRenderEncoder";
    119         
    120         // 5.我们可以使用 MTLRenderCommandEncoder 来绘制对象,此 demo 仅仅创建编码器就可以了,我们并没有让 Metal 去执行我们绘制的东西,这个时候表示我们的任务已经完成.
    121         // 即: 可结束 MTLRenderCommandEncoder 工作
    122         [renderEncoder endEncoding];
    123         
    124         /*
    125          当编码器结束之后,命令缓存区就会接受到 2 个命令.
    126          1) present
    127          2) commit
    128          因为 GPU 是不会直接绘制到屏幕上,因此若不给出指令,则不会有任何内容渲染到屏幕上.
    129         */
    130         // 6.添加最后一个命令 来显示清楚的可绘制的屏幕
    131         [commandBuffer presentDrawable:view.currentDrawable];
    132         
    133     }
    134     // 7. 完成渲染并将命令缓冲区提交给 GPU
    135     [commandBuffer commit];
    136 }
    137 
    138 // 当 MTKView 视图发生大小改变时调用
    139 - (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size {
    140     
    141 }
    142 
    143 @end

    viewController.m

     1 #import "ViewController.h"
     2 #import "MyMetalRender.h"
     3 
     4 @interface ViewController () {
     5     
     6     MTKView *_view;
     7     MyMetalRender *_render;
     8 }
     9 
    10 @end
    11 
    12 @implementation ViewController
    13 
    14 - (void)viewDidLoad {
    15     [super viewDidLoad];
    16     // Do any additional setup after loading the view.
    17     
    18     _view = (MTKView *)self.view;
    19     // 一个 MTLDevice 对象 表示 GPU
    20     _view.device = MTLCreateSystemDefaultDevice();
    21     // 判断是否设置成功
    22     if (!_view.device) {
    23         NSLog(@"Metal is not supported on this device");
    24         return;
    25     }
    26     
    27     // render
    28     _render = [[MyMetalRender alloc] initWithMTKView:_view];
    29     
    30     _view.delegate = _render;
    31     // 设置帧速率 --> 指定时间来调用 drawInMTKView 方法--视图需要渲染时调用 默认60
    32     _view.preferredFramesPerSecond = 60;
    33 }
    34 
    35 @end

    2、绘制三角形

    我们只给了三角形的三个顶点的颜色, Metal 会自动计算过度颜色差值,和 OpenGL 一样。

    主要代码:

     1 // 1. 顶点/颜色 数据
     2     static const MyVertex triangleVertices[] =
     3     {
     4         // 顶点 xyzw,                 颜色值RGBA
     5         { {  0.5, -0.25, 0.0, 1.0 }, { 1, 0, 0, 1 } },
     6         { { -0.5, -0.25, 0.0, 1.0 }, { 0, 1, 0, 1 } },
     7         { { -0.0f, 0.25, 0.0, 1.0 }, { 0, 0, 1, 1 } },
     8     };
     9     // 2.为当前渲染的每个渲染 传递 创建 一个新的命令缓冲区
    10     id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    11     commandBuffer.label = @"MyCommandBuffer";
    12     
    13     // 3.MTLRenderPassDescriptor:一组渲染目标,用作渲染通道生成的像素的输出目标。
    14     MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
    15     if (renderPassDescriptor) {
    16         // 4.创建 渲染命令编码
    17         id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
    18         renderEncoder.label = @"MyRenderEncoder";
    19         // 5.设置 可绘制区域 Viewport
    20         /*
    21         typedef struct {
    22             double originX, originY, width, height, znear, zfar;
    23         } MTLViewport;
    24          */
    25         // 视口指定 Metal 渲染内容的 drawable 区域。 视口是具有x和y偏移,宽度和高度以及近和远平面的 3D 区域
    26         // 为管道分配自定义视口,需要通过调用 setViewport:方法将 MTLViewport 结构 编码为渲染命令编码器。 如果未指定视口,Metal会设置一个默认视口,其大小与用于创建渲染命令编码器的 drawable 相同。
    27         MTLViewport viewport = {
    28             0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0
    29         };
    30         [renderEncoder setViewport:viewport];
    31 //        [renderEncoder setViewport:(MTLViewport){0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0 }];
    32         
    33         // 6.设置当前渲染管道状态对象
    34         [renderEncoder setRenderPipelineState:_pipelineState];
    35 
    36         // 7.数据传递给着色函数 -- 从应用程序(OC 代码)中发送数据给 Metal 顶点着色器 函数
    37         // 顶点 + 颜色
    38         //   1) 指向要传递给着色器的内存的指针
    39         //   2) 我们想要传递的数据的内存大小
    40         //   3)一个整数索引,它对应于我们的“vertexShader”函数中的缓冲区属性限定符的索引。
    41         [renderEncoder setVertexBytes:triangleVertices
    42                                length:sizeof(triangleVertices)
    43                               atIndex:MyVertexInputIndexVertices];
    44 
    45         // viewPortSize 数据
    46         //  1) 发送到顶点着色函数中,视图大小
    47         //  2) 视图大小内存空间大小
    48         //  3) 对应的索引
    49         [renderEncoder setVertexBytes:triangleVertices length:sizeof(_viewportSize) atIndex:MyVertexInputIndexViewportSize];
    50         
    51         
    52         // 8.画出 三角形的 3 个顶点
    53         // @method drawPrimitives:vertexStart:vertexCount:
    54         // @brief 在不使用索引列表的情况下,绘制图元
    55         // @param 绘制图形组装的基元类型
    56         // @param 从哪个位置数据开始绘制,一般为0
    57         // @param 每个图元的顶点个数,绘制的图型顶点数量
    58         /*
    59          MTLPrimitiveTypePoint = 0, 点
    60          MTLPrimitiveTypeLine = 1, 线段
    61          MTLPrimitiveTypeLineStrip = 2, 线环
    62          MTLPrimitiveTypeTriangle = 3,  三角形
    63          MTLPrimitiveTypeTriangleStrip = 4, 三角型扇
    64          */
    65         [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
    66         
    67         // 9.编码完成 - 表示该编码器生成的命令都已完成,并且从 MTLCommandBuffer 中分离
    68         [renderEncoder endEncoding];
    69         
    70         // 10.一旦框架缓冲区完成,使用当前可绘制的进度表
    71         [commandBuffer presentDrawable:view.currentDrawable];
    72     }
    73     // 11. 最后,完成渲染并将命令缓冲区推送到 GPU
    74     [commandBuffer commit];
    75     

    Metal 代码:

     1 // 顶点着色器输出和片段着色器输入 -- 经过了光栅化的数据
     2 // 结构体
     3 typedef struct {
     4     // 处理空间的顶点信息
     5     float4 clipSpacePosition[[position]];
     6     // 颜色
     7     float4 color;
     8 } RasterizerData;
     9 
    10 
    11 // 顶点着色函数
    12 /*
    13 处理顶点数据:
    14    1) 执行坐标系转换,将生成的顶点剪辑空间写入到返回值中.
    15    2) 将顶点颜色值传递给返回值
    16 */
    17 vertex RasterizerData vertexShader (uint vertexID [[vertex_id]],
    18                                     constant MyVertex *vertices [[buffer(MyVertexInputIndexVertices)]],
    19                                     constant vector_uint2 *viewportSizePointer [[buffer(MyVertexInputIndexViewportSize)]]) {
    20     
    21     // 定义输出
    22     RasterizerData out;
    23     out.clipSpacePosition = vertices[vertexID].position;
    24 
    25     out.color = vertices[vertexID].color;
    26     
    27     // 完成! 将结构体传递到管道中下一个阶段:
    28     return out;
    29 }
    30 
    31 
    32 // 当顶点函数执行3次,三角形的每个顶点都执行一次后,则执行管道中的下一个阶段 --> 栅格化/光栅化 之后 --> 片元函数
    33 
    34 
    35 // 片元函数
    36 fragment float4 fragmentShader (RasterizerData in [[stage_in]]) {
    37     
    38     // 返回输入的片元颜色
    39     return in.color;
    40 }

    Demo 地址

     

  • 相关阅读:
    javascript异步编程系列【十】—Jscex+Easeljs制作坦克大战
    博客园分页JQuery打造的分页无刷新的Repeater
    参赛作品
    摄像机、投影、3D旋转、缩放
    javascript异步编程系列【八】Jscex版火拼俄罗斯
    javascript异步编程系列【七】扫盲,我们为什么要用Jscex
    javascript异步编程系列【五】Jscex制作愤怒的小鸟
    javascript异步编程系列【六】Jscex版愤怒的小鸟之冲锋陷阵鸟
    每周优秀代码赏析—Jscex内核【一】
    javascript异步编程系列【一】用Jscex画圆
  • 原文地址:https://www.cnblogs.com/zhangzhang-y/p/13562846.html
Copyright © 2020-2023  润新知