1、渲染加载颜色
未命名.gif 渲染加载颜色导入MetalKit工具包,@import MetalKit;
我们接受苹果的建议分离渲染循环。另建文件CCRender,遵循MTKViewDelegate协议。
在Controller中创建需要的MTKView、CCRender对象
- MTKView对象,设置metal设备(GPU)。及渲染循环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
//创建负责渲染的CCRender,
_render = [[CCRender alloc]initWithMetalKitView:_mtkView];
- 设置MTKView的代理为负责渲染CCRender
_mtkView.delegate = _render;
- 设置帧速率
帧速率越大,每秒处理的帧数越多,变化越快。
也可以不设置。使用默认帧速率值
_mtkView.preferredFramesPerSecond = 100;
CCRender 渲染循环类
- 1、设置
CCRender
当前的device
为MTKView
的device
_device = mtkView.device;
- 2、从当前设备
device(GPU)
创建命令队列MTLCommandQueue
_commandQueue = [_device newCommandQueue];
- 3、执行渲染的代理方法
-(void)drawInMTKView:(nonnull MTKView *)view
每当视图进行渲染是调用。 - 3.1、获取变化的颜色、设置清屏颜色
//获取颜色
Color color = [self makeFancyColor];
//设置清屏颜色
view.clearColor = MTLClearColorMake(color.red, color.green, color.blue, color.alpha);
- 3.2、为当前渲染创建新的命令缓冲区
创建渲染缓存区,目的是为了将渲染对象加入到渲染缓存区,使用MTLCommandQueue
创建对象并且加入到MTCommandBuffer
对象中,且为当前渲染的每个渲染传递创建一个新的命令缓冲区。
//为当前渲染的每个渲染传递创建一个新的命令缓冲区
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
//指定缓存区名称
commandBuffer.label = @"MyCommand";
- 3.3、从当前视图获取渲染描述符
MTLRenderPassDescriptor
,用于创建命令编译器MTLRenderCommandEncoder
。
//获取渲染描述符
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];
}
- 3.4、结束渲染。将命令缓冲区提交
GPU
[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 shaders
与C/OBJC
源之间共享的类型和枚举常数。
缓存区索引值 共享与shader
和C
代码 为了确保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
- 设置当前设备
device
、创建命令队列MTLCommandQueue
//1.获取GPU 设备
_device = mtkView.device;
//5.创建命令队列
_commandQueue = [_device newCommandQueue];
- 加载(.metal)着色器文件
//2.在项目中加载所有的(.metal)着色器文件
// 从bundle中获取.metal文件
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
//从库中加载顶点函数
id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
//从库中加载片元函数
id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
- 配置渲染管道
MTLRenderPipelineDescriptor
//3.配置用于创建管道状态的管道
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
//管道名称
pipelineStateDescriptor.label = @"Simple Pipeline";
//可编程函数,用于处理渲染过程中的各个顶点
pipelineStateDescriptor.vertexFunction = vertexFunction;
//可编程函数,用于处理渲染过程中各个片段/片元
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
//一组存储颜色数据的组件
pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
- 创建渲染管道对象
MTLRenderPipelineDescriptor
从当前device(GPU)
创建配置好的渲染管道对象
_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];
}
网友评论