一、效果图
二、与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