Metal之渲染绘制三角形

准备工作

  • Metal渲染的构建流程, 请参考:Metal之简单渲染动态切换屏幕颜色
  • Metal三角形的渲染显示与渲染构建流程大体一致, 本文主要介绍以下方面的修改和实现:
    ① metal渲染文件
    ② 创建C 与 OC 的桥接函数
    ③ initWithMetalKitView方法中需要加载metal文件
    ④ drawInMTKView方法中加载三角形数据
  • Metal三角形的渲染显示是基于世界坐标系下。

渲染流程

一、metal渲染文件
  • command + N --> Metal File 创建metal着色器文件, 新建metal文件:YDWShaders.metal

在这里插入图片描述

  • 定义顶点着色器输入和片元着色器输入,相当于OpenGL ES中的varying修饰的变量,即桥接变量;
// 顶点着色器输出和片段着色器输入
// 结构体
typedef struct {
    // 处理空间的顶点信息
    float4 clipSpacePosition [[position]];

    // 颜色
    float4 color;

} RasterizerData;
  • 定义顶点着色器函数和片元着色器函数:
    ① 处理顶点数据: 执行坐标系转换,将生成的顶点剪辑空间写入到返回值中; 将顶点颜色值传递给返回值;
    ② 初始化输出剪辑空间位置: 索引到我们的数组位置以获得当前顶点, 我们的位置是在像素维度中指定的, 每个顶点着色器的输出位置在剪辑空间中(也称为归一化设备坐标空间,NDC),剪辑空间中的(-1,-1)表示视口的左下角,而(1,1)表示视口的右上角, 计算和写入 XY值到我们的剪辑空间的位置.为了从像素空间中的位置转换到剪辑空间的位置,我们将像素坐标除以视口的大小的一半;
    ③ 把输入的颜色直接赋值给输出颜色, 这个值将于构成三角形的顶点的其他颜色值插值, 从而为片段着色器中的每个片段生成颜色值;
//顶点着色器函数
/*
 vertex:修饰符,表示是顶点着色器
 RasterizerData:返回值
 vertexShader:函数名称,可自定义
 
 vertexID:metal自己反馈的id
 vertices:1)告诉存储的位置buffer 2)告诉传递数据的入口是CJLVertexInputIndexVertices
 vertices 和 viewportSizePointer 都是通过CJLRenderer 传递进来的
 */
vertex RasterizerData
vertexShader(uint vertexID [[vertex_id]],
             constant CJLVertex *vertices [[buffer(CJLVertexInputIndexVertices)]],
             constant vector_uint2 *viewportSizePointer [[buffer(CJLVertexInputIndexViewportSize)]]) {
    // 定义out
    RasterizerData out; 
    // 初始化输出剪辑空间位置
    out.clipSpacePosition = vertices[vertexID].position;
    // 把输入的颜色直接赋值给输出颜色. 这个值将于构成三角形的顶点的其他颜色值插值,从而为片段着色器中的每个片段生成颜色值
    out.color = vertices[vertexID].color;
    // 完成,将结构体传递到管道中下一个阶段
    return out;
}

/*
 fragment:修饰符,表示是片元着色器
 float4:返回值,即颜色值RGBA
 fragmentShader:函数名称,可自定义
 
 RasterizerData:参数类型(可修改)
 in:形参变量(可修改)
 [[stage_in]]:属性修饰符,表示单个片元输入(由定点函数输出)(不可修改),相当于OpenGL ES中的varying
 */
fragment float4 fragmentShader(RasterizerData in [[stage_in]]) {
    // 返回输入的片元颜色
    return in.color;
}
二、创建C与OC的桥接文件

该头文件的目的是为了c代码与OC代码可以共享与 shader 和 C 代码 为了确保Metal Shader缓存区索引能够匹配 Metal API Buffer 设置的集合调用

  • 定义缓存区索引值,表示向metal着色器传递数据的入口枚举值,相当于OpenGL ES中GLSL语言定义的顶点坐标入口position
// 缓存区索引值 共享与shader和C代码, 为了确保Metal Shader缓存区索引能够匹配 Metal API Buffer 设置的集合调用
typedef enum YDWVertexInputIndex {
    // 顶点
    YDWVertexInputIndexVertices     = 0,
    // 视图大小
    YDWVertexInputIndexViewportSize = 1,
} YDWVertexInputIndex;
  • 定义图形数据的结构体,包含顶点和颜色值,类似于OpenGL ES中的顶点数据的结构体
// 结构体: 顶点/颜色值
typedef struct {
    // 像素空间的位置
    // 像素中心点(100,100)
    vector_float4 position;

    // RGBA颜色
    vector_float4 color;
} YDWVertex;
三、initWithMetalKitView方法中需要加载metal文件
  • 获取GPU设备device
  • 加载.metal着色器文件
  • 配置用于创建管道状态的管道描述符
  • 同步创建并返回渲染管线状态对象
  • 创建命令队列
// 初始化MTKView
 - (nonnull instancetype)initWithMetalKitView:(nonnull MTKView *)mtkView {
    self = [super init];
    if(self) {
        NSError *error = NULL;
        
        // 获取GPU 设备
        _device = mtkView.device;

        // 在项目中加载所有的(.metal)着色器文件
        // 从bundle中获取.metal文件
        id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
        // 从库中加载顶点函数
        id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
        // 从库中加载片元函数
        id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];

        // 配置用于创建管道状态的管道
        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];
        // 判断是否返回了管线状态对象
        if (!_pipelineState) {
            // 如果没有正确设置管道描述符,则管道状态创建可能失败
            NSLog(@"Failed to created pipeline state, error %@", error);
            return nil;
        }
        // 创建命令队列
        _commandQueue = [_device newCommandQueue];
    }
    return self;
}
四、drawInMTKView方法中加载三角形数据
  • 顶点数据/颜色数据
	// 顶点数据/颜色数据
    static const YDWVertex 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 } },
    };
  • 为当前渲染的每个渲染传递创建一个新的命令缓冲区
	// 为当前渲染的每个渲染传递创建一个新的命令缓冲区
    id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
    // 指定缓存区名称
    commandBuffer.label = @"MyCommand";
  • 创建命令描述符
	MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
  • 创建渲染命令编码器
	// 创建渲染命令编码器,这样才可以渲染到something
    id<MTLRenderCommandEncoder> renderEncoder =[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
    // 渲染器名称
    renderEncoder.label = @"MyRenderEncoder";
    
  • 设置我们绘制的可绘制区域:
    视口指定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];
  • 从应用程序OC代码中发送数据给 Metal 顶点着色器函数
    顶点数据+颜色数据: 指向要传递给着色器的内存的指针, 想要传递的数据的内存大小;
    一个整数索引,它对应于我们的“vertexShader”函数中的缓冲区属性限定符的索引。
// 指向要传递给着色器的内存的指针
// 想要传递的数据的内存大小
// 一个整数索引,它对应于“vertexShader”函数中的缓冲区属性限定符的索引
[renderEncoder setVertexBytes:triangleVertices
                               length:sizeof(triangleVertices)
                              atIndex:YDWVertexInputIndexVertices];
                              
 // 发送到顶点着色函数中,视图大小
 // 视图大小内存空间大小
 // 对应的索引
[renderEncoder setVertexBytes:&_viewportSize
                               length:sizeof(_viewportSize)
                              atIndex:YDWVertexInputIndexViewportSize];
  • 画出三角形的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];
  • 结束编码: 表示已该编码器生成的命令都已完成, 且从NTLCommandBuffer中分离
[renderEncoder endEncoding];
  • 一旦框架缓冲区完成,使用当前可绘制的进度表
[commandBuffer presentDrawable:view.currentDrawable];
  • 命令缓冲区提交GPU进行显示
[commandBuffer commit];

效果展示

在这里插入图片描述

完整示例

Metal之基于世界坐标系下渲染绘制三角形

猜你喜欢

转载自blog.csdn.net/Forever_wj/article/details/108209440