Metal-03-案例-加载三角形

445 阅读5分钟

《目录-iOS & OpenGL & OpenGL ES & Metal》

一、效果图

二、与GLSL加载三角形 流程对比

三、代码部分

1、ViewController.m

#import "ViewController.h"
#import "YRender.h"
@interface ViewController ()
{
    MTKView *_view;
    YRender *_render;
}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //1、
    _view = [[MTKView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
     
    [self.view addSubview:_view];

    //2、创建一个默认的device
    _view.device = MTLCreateSystemDefaultDevice();

    //3、判断是否设置成功,因为后面有很多地方需要用到device,如果不成功就没什么意义了
    if (!_view.device) {
        NSLog(@"Metal is not supported on this device");
        return;
    }


    //4、因为要遵从苹果的建议,加载渲染的类
    _render = [[YRender alloc]initWithMetalKitView:_view];

    //5、判断
    if (!_render) {
        NSLog(@"Renderer failed initialization");
        return;
    }

    //6、设置MTKView 的代理(由自定义的CustomRender来实现MTKView 的代理方法)
    _view.delegate = _render;


    //7、设置帧速率,也就是多少帧调用一次渲染代理方法
    _view.preferredFramesPerSecond = 60;
    
    //8、把mtkView的drawableSize传进去
    [_render mtkView:_view drawableSizeWillChange:_view.drawableSize];
    
}


@end


2、YShaderTypes.h

这是一个桥接文件,就是新建一个头文件

#ifndef YShaderTypes_h
#define YShaderTypes_h

//目的:为了桥接 metal shader和oc代码之间共用的部分。
//因为这里的代码 多个文件都要用到
//所以单独一个类出来,让shader和oc代码都能调用
//是一种习惯行为



//需要定义枚举值来传递数据
//这里就是需要用到多少就自定义
typedef enum CCVertexInputIndex
{
    //顶点
    CCVertexInputIndexVertices     = 0,
    //视图大小
    CCVertexInputIndexViewportSize = 1,
    
} CCVertexInputIndex;


//结构体: 顶点/颜色值
typedef struct
{
    // 像素空间的位置
    // 像素中心点(100,100)
    vector_float4 position;

    // RGBA颜色
    vector_float4 color;
    
} CCVertex;

#endif /* YShaderTypes_h */

3、YShaders.metal

metal文件


#include <metal_stdlib>
using namespace metal;

//导入桥接文件
#import "YShaderTypes.h"


//定义一个 顶点着色器输出 和 片段着色器输入 的结构体
typedef struct
{
    //处理空间的顶点信息
    float4 clipSpacePosition [[position]];

    //颜色
    float4 color;

} RasterizerData;


//顶点着色函数
/*
 vertex  顶点着色函数修饰符
 RasterizerData  函数返回值
 vertexShader 函数名
 传入参数(变量类型   变量名  [[变量在buffer中的索引位置,表示是哪一个]])
 */
vertex RasterizerData vertexShader(
             uint vertexID [[vertex_id]],
             constant CCVertex *vertices [[buffer(CCVertexInputIndexVertices)]],
             constant vector_uint2 *viewportSizePointer [[buffer(CCVertexInputIndexViewportSize)]])
{

    /*
    处理顶点数据:
       1) 执行坐标系转换,将生成的顶点剪辑空间写入到返回值中.
       2) 将顶点颜色值传递给返回值
    */
    
    //定义out
    RasterizerData out;
    
    out.clipSpacePosition = vertices[vertexID].position;
    
    out.color = vertices[vertexID].color;
    
    return out;
}



//片元着色函数
/*
 fragment 片元着色函数修饰符
 float4 返回值类型
 fragmentShader 函数名
  传入参数(变量类型   变量名  [[变量在buffer中的索引位置,表示是哪一个]][[stage_in]] 专门修饰 由顶点函数输出 经过光栅化生成 传入片元函数的数据
 */
fragment float4 fragmentShader(RasterizerData in [[stage_in]])
{
    
    //返回输入的片元颜色
    return in.color;
}

4、YRender.h

苹果建议的 独立的渲染类

#import <Foundation/Foundation.h>
#import <MetalKit/MetalKit.h>
//导入桥接文件
#import "YShaderTypes.h"

NS_ASSUME_NONNULL_BEGIN

@interface YRender : NSObject<MTKViewDelegate>

-(id)initWithMetalKitView:(MTKView *)mtkView;

@end

NS_ASSUME_NONNULL_END

5、YRender.m

#import "YRender.h"


@implementation YRender
//声明一些变量
{
    //我们用来渲染的设备(又名GPU)
    id<MTLDevice> _device;
 
    // 我们的渲染管道有顶点着色器和片元着色器 它们存储在.metal shader 文件中
    id<MTLRenderPipelineState> _pipelineState;

    //命令队列,从命令缓存区获取
    id<MTLCommandQueue> _commandQueue;

    //当前视图大小,这样我们才可以在渲染通道使用这个视图
    vector_uint2 _viewportSize;
}


//初始化方法
-(id)initWithMetalKitView:(MTKView *)mtkView
{
    
    self = [super init];
    if(self)
    {
        //1、获取GPU 设备
        _device = mtkView.device;


        //2、加载项目中的metal文件
        //1)从bundle中获取.metal文件
        id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
        //2)从库中加载顶点函数
        id<MTLFunction> vertexFun = [defaultLibrary newFunctionWithName:@"vertexShader"];
        //3)加载片元函数
        id<MTLFunction> fragmentFun = [defaultLibrary newFunctionWithName:@"fragmentShader"];
        
        
        
        //3、配置用于创建渲染管道的描述符
        //1)创建管道描述符
        MTLRenderPipelineDescriptor *pipelineDes = [[MTLRenderPipelineDescriptor alloc]init];
        //2)给描述符起名字
        pipelineDes.label = @"myPipelineDes";
        //3)顶点函数,用于处理渲染过程中的各个顶点
        pipelineDes.vertexFunction = vertexFun;
        //4)片元函数,用于处理渲染过程中各个片段/片元
        pipelineDes.fragmentFunction = fragmentFun;
        //5)一组存储颜色数据的组件
        pipelineDes.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
        
        
        //4、同时创建并返回 渲染关系状态对象
        NSError *error = NULL;
        _pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineDes error:&error];
        
        //判断
        if (!_pipelineState)
        {
           
            //如果我们没有正确设置管道描述符,则管道状态创建可能失败
            NSLog(@"Failed to created pipeline state, error %@", error);
            return nil;
        }
        
        
         
        //5、通过 device 创建 命令队列
        _commandQueue = [_device newCommandQueue];
    }

    return self;
    
    
}



//MTKViewDelegate代理方法
-(void)drawInMTKView:(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 = @"myCommandBuffer";
    
    //3、创建 渲染描述符
    MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
    //判断
    if (renderPassDescriptor != nil) {
        
        
        //4、根据描述符,创建 命令编码器
        id<MTLRenderCommandEncoder> renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
        //命名
        renderEncoder.label = @"myEncoder";
        
        
        
        
        //5、设置可绘制区域,也就是渲染区域 视口
        /*
        typedef struct {
            double originX, originY, width, height, znear, zfar;
        } MTLViewport;
         */
        MTLViewport viewPort = { 0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0};
        [renderEncoder setViewport:viewPort];
        
        
        //6、设置渲染管道状态对象
        [renderEncoder setRenderPipelineState:_pipelineState];
        
        
        
        //7、把顶点数据 从oc中 发送给 metal的 顶点着色函数
        
        /*
         参数1:要传递数据的 内存指针
         参数2:要传递数据的 内存大小
         参数3:整数索引,要对应vertexShader函数中缓冲区属性限定符的索引
         ** 注意,这个方法使用的前提啊,数据大小不能超过4096个字节。超过了就要用其他方法了
         */
        [renderEncoder setVertexBytes:triangleVertices
                               length:sizeof(triangleVertices)
                              atIndex:CCVertexInputIndexVertices];
        
        
        [renderEncoder setVertexBytes:&_viewportSize
                               length:sizeof(_viewportSize)
                              atIndex:CCVertexInputIndexViewportSize];
        
        
        
        //8、准备绘制,设置图元连接方式
        [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
                          vertexStart:0
                          vertexCount:3];
        
        
        //9、编码器生成的命令都已经完成,从MTLCommandBuffer中分离
        [renderEncoder endEncoding];
        
        
        //10、一旦框架缓冲区完成,使用当前可绘制的进度表,相当于提交渲染
        [commandBuffer presentDrawable:view.currentDrawable];
    }
    
    //11、渲染,把命令缓冲区推送到GPU
    [commandBuffer commit];
}



- (void)mtkView:(MTKView *)view drawableSizeWillChange:(CGSize)size
{

    // 保存可绘制的大小,因为当我们绘制时,我们将把这些值传递给顶点着色器
       _viewportSize.x = size.width;
       _viewportSize.y = size.height;
}


@end

源码链接

链接:pan.baidu.com/s/1VVG_BzNO… 密码:zvy9