1身诺、渲染加載顏色
導入MetalKit工具包,@import MetalKit;
我們接受蘋果的建議分離渲染循環(huán)霉赡。另建文件CCRender橄务,遵循MTKViewDelegate協(xié)議。
在Controller中創(chuàng)建需要的MTKView穴亏、CCRender對象
- MTKView對象,設置metal設備(GPU)蜂挪。及渲染循環(huán)CCrender對象。
//創(chuàng)建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];
- 創(chuàng)建負責渲染CCRender
//創(chuàng)建負責渲染的CCRender棠涮,
_render = [[CCRender alloc]initWithMetalKitView:_mtkView];
- 設置MTKView的代理為負責渲染CCRender
_mtkView.delegate = _render;
- 設置幀速率
幀速率越大,每秒處理的幀數(shù)越多刺覆,變化越快严肪。
也可以不設置。使用默認幀速率值
_mtkView.preferredFramesPerSecond = 100;
CCRender 渲染循環(huán)類
- 1谦屑、設置
CCRender
當前的device
為MTKView
的device
_device = mtkView.device;
- 2驳糯、從當前設備
device(GPU)
創(chuàng)建命令隊列MTLCommandQueue
_commandQueue = [_device newCommandQueue];
- 3、執(zhí)行渲染的代理方法
-(void)drawInMTKView:(nonnull MTKView *)view
每當視圖進行渲染是調(diào)用伦仍。 - 3.1结窘、獲取變化的顏色、設置清屏顏色
//獲取顏色
Color color = [self makeFancyColor];
//設置清屏顏色
view.clearColor = MTLClearColorMake(color.red, color.green, color.blue, color.alpha);
- 3.2充蓝、為當前渲染創(chuàng)建新的命令緩沖區(qū)
創(chuàng)建渲染緩存區(qū)隧枫,目的是為了將渲染對象加入到渲染緩存區(qū),使用MTLCommandQueue
創(chuàng)建對象并且加入到MTCommandBuffer
對象中谓苟,且為當前渲染的每個渲染傳遞創(chuàng)建一個新的命令緩沖區(qū)官脓。
//為當前渲染的每個渲染傳遞創(chuàng)建一個新的命令緩沖區(qū)
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
//指定緩存區(qū)名稱
commandBuffer.label = @"MyCommand";
- 3.3、從當前視圖獲取渲染描述符
MTLRenderPassDescriptor
涝焙,用于創(chuàng)建命令編譯器MTLRenderCommandEncoder
卑笨。
//獲取渲染描述符
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
//創(chuàng)建命令編譯器
if(renderPassDescriptor != nil)
{
//4.創(chuàng)建渲染命令編碼器,這樣我們才可以渲染到something
id<MTLRenderCommandEncoder> renderEncoder =[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
//渲染器名稱
renderEncoder.label = @"MyRenderEncoder";
[renderEncoder endEncoding];
/*
當編碼器結束之后,命令緩存區(qū)就會接受到2個命令.
1) present
2) commit
因為GPU是不會直接繪制到屏幕上,因此你不給出去指令.是不會有任何內(nèi)容渲染到屏幕上.
*/
//添加一個最后的命令來顯示清除的可繪制的屏幕
[commandBuffer presentDrawable:view.currentDrawable];
}
- 3.4、結束渲染仑撞。將命令緩沖區(qū)提交
GPU
[commandBuffer commit];
- 設置顏色變換
//顏色結構體
typedef struct {
float red,green,blue,alpha;
}Color;
-(Color)makeFancyColor{
//增加顏色 赤兴、減少顏色標記
static BOOL growing = YES;
//顏色通道(0~3)
static NSUInteger primaryChannel = 0;
//3.顏色通道數(shù)組colorChannels(顏色值)
static float colorChannels[] = {1.0, 0.0, 0.0, 1.0};
//4.顏色調(diào)整步長
const float DynamicColorRate = 0.015;
if (growing) {
//動態(tài)信道索引(1妖滔,2,3桶良,0)通道間的切換
NSInteger dynamicChannelIndex = (primaryChannel + 1) % 3;
//修改對應通道的顏色值 調(diào)整0.015
colorChannels[dynamicChannelIndex] += DynamicColorRate;
//當顏色通道值 = 1.0時
if (colorChannels[dynamicChannelIndex] >= 1.0) {
//設置為no
growing = NO;
//將顏色通道修改為動態(tài)顏色通道
primaryChannel = dynamicChannelIndex;
}
}else{
//獲取動態(tài)顏色通道
NSUInteger dynamicChannelIndex = (primaryChannel+2)%3;
//將當前顏色的值 減去0.015
colorChannels[dynamicChannelIndex] -= DynamicColorRate;
//當顏色值小于等于0.0
if(colorChannels[dynamicChannelIndex] <= 0.0)
{
//又調(diào)整為顏色增加
growing = YES;
}
}
//創(chuàng)建顏色
Color color;
//修改顏色的RGBA的值
color.red = colorChannels[0];
color.green = colorChannels[1];
color.blue = colorChannels[2];
color.alpha = colorChannels[3];
//返回顏色
return color;
}
2座舍、繪制三角形
創(chuàng)建Metal著色器文件
定義頂點著色器的輸入、片元著色器的輸出
// 頂點著色器輸出和片段著色器輸入
//結構體
typedef struct
{
//處理空間的頂點信息
float4 clipSpacePosition [[position]];
//顏色
float4 color;
} RasterizerData;
頂點著色器函數(shù)陨帆、片元著色器函數(shù)
/*
* vertex:修飾符曲秉,表示是頂點著色器
* RasterizerData:返回值
* vertexShader:函數(shù)名稱,自定義
* vertexID:當前所處理的頂點號疲牵。metal自己反饋的id
* vertices:1承二、告訴存儲的位置buffer。 2纲爸、告訴傳遞數(shù)據(jù)的入口是CCVertexInputIndexVertices
* viewportSizePointer:視口
* vertices 和 viewportSizePointer 都是通過CCRender傳遞進來的
*/
//頂點著色函數(shù)
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:函數(shù)名稱读虏,自定義
* RasterizerData:參數(shù)類型
* in:函數(shù)中形參變量(可修改)
* [[stage_in]]:表示單個片元輸入(由頂點函數(shù)輸出,然后經(jīng)過光柵化生成)(不可修改)袁滥,相當于OpenGL ES中的fragment中的varying與頂點著色器中橋接的對應
*/
fragment float4 fragmentShader(RasterizerData in [[stage_in]])
{
//返回輸入的片元顏色
return in.color;
}
創(chuàng)建橋接文件
創(chuàng)建此文件為了Metal shaders
與C/OBJC
源之間共享的類型和枚舉常數(shù)。
緩存區(qū)索引值 共享與shader
和C
代碼 為了確保Metal Shader
緩存區(qū)索引能夠匹配 Metal API Buffer
設置的集合調(diào)用
//緩存區(qū)索引值灾螃,表示向metal著色器傳遞數(shù)據(jù)的入口枚舉值
typedef enum CCVertexInputIndex
{
//頂點
CCVertexInputIndexVertices = 0,
//視圖大小
CCVertexInputIndexViewportSize = 1,
} CCVertexInputIndex;
//圖形數(shù)據(jù)的結構體: 頂點/顏色值
typedef struct
{
// 像素空間的位置
// 像素中心點(100,100)
vector_float4 position;
// RGBA顏色
vector_float4 color;
} CCVertex;
Controller初始化
創(chuàng)建MTKView
對象設置設備device(GPU)
题翻,
創(chuàng)建CCRender
對象,設置MTKView
的代理
//創(chuàng)建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
腰鬼、創(chuàng)建命令隊列MTLCommandQueue
//1.獲取GPU 設備
_device = mtkView.device;
//5.創(chuàng)建命令隊列
_commandQueue = [_device newCommandQueue];
- 加載(.metal)著色器文件
//2.在項目中加載所有的(.metal)著色器文件
// 從bundle中獲取.metal文件
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
//從庫中加載頂點函數(shù)
id<MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
//從庫中加載片元函數(shù)
id<MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
- 配置渲染管道
MTLRenderPipelineDescriptor
//3.配置用于創(chuàng)建管道狀態(tài)的管道
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
//管道名稱
pipelineStateDescriptor.label = @"Simple Pipeline";
//可編程函數(shù),用于處理渲染過程中的各個頂點
pipelineStateDescriptor.vertexFunction = vertexFunction;
//可編程函數(shù),用于處理渲染過程中各個片段/片元
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
//一組存儲顏色數(shù)據(jù)的組件
pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
- 創(chuàng)建渲染管道對象
MTLRenderPipelineDescriptor
從當前device(GPU)
創(chuàng)建配置好的渲染管道對象
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
- 視圖變化的代理方法
//當前視圖大小,
vector_uint2 _viewportSize;
//每當視圖改變方向或調(diào)整大小時調(diào)用
- (void)mtkView:(nonnull MTKView *)view drawableSizeWillChange:(CGSize)size
{
// 保存可繪制的大小嵌赠,因為當我們繪制時,我們將把這些值傳遞給頂點著色器
_viewportSize.x = size.width;
_viewportSize.y = size.height;
}
- 渲染代理方法
//每當視圖需要渲染幀時調(diào)用
- (void)drawInMTKView:(nonnull MTKView *)view
{
//1. 頂點數(shù)據(jù)/顏色數(shù)據(jù)
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.為當前渲染的每個渲染傳遞創(chuàng)建一個新的命令緩沖區(qū)
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
//指定緩存區(qū)名稱
commandBuffer.label = @"MyCommand";
// MTLRenderPassDescriptor:一組渲染目標熄赡,用作渲染通道生成的像素的輸出目標姜挺。
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
//判斷渲染目標是否為空
if(renderPassDescriptor != nil)
{
//4.創(chuàng)建渲染命令編碼器,這樣我們才可以渲染到something
id<MTLRenderCommandEncoder> renderEncoder =[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
//渲染器名稱
renderEncoder.label = @"MyRenderEncoder";
//5.設置我們繪制的可繪制區(qū)域
/*
typedef struct {
double originX, originY, width, height, znear, zfar;
} MTLViewport;
*/
//視口指定Metal渲染內(nèi)容的drawable區(qū)域。 視口是具有x和y偏移彼硫,寬度和高度以及近和遠平面的3D區(qū)域
//為管道分配自定義視口需要通過調(diào)用setViewport:方法將MTLViewport結構編碼為渲染命令編碼器炊豪。 如果未指定視口,Metal會設置一個默認視口拧篮,其大小與用于創(chuàng)建渲染命令編碼器的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.設置當前渲染管道狀態(tài)對象
[renderEncoder setRenderPipelineState:_pipelineState];
//7.從應用程序OC 代碼 中發(fā)送數(shù)據(jù)給Metal 頂點著色器 函數(shù)
//頂點數(shù)據(jù)+顏色數(shù)據(jù)
// 1) 指向要傳遞給著色器的內(nèi)存的指針
// 2) 我們想要傳遞的數(shù)據(jù)的內(nèi)存大小
// 3)一個整數(shù)索引,它對應于我們的“vertexShader”函數(shù)中的緩沖區(qū)屬性限定符的索引串绩。
[renderEncoder setVertexBytes:triangleVertices
length:sizeof(triangleVertices)
atIndex:CCVertexInputIndexVertices];
//viewPortSize 數(shù)據(jù)
//1) 發(fā)送到頂點著色函數(shù)中,視圖大小
//2) 視圖大小內(nèi)存空間大小
//3) 對應的索引
[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:CCVertexInputIndexViewportSize];
//8.畫出三角形的3個頂點
// @method drawPrimitives:vertexStart:vertexCount:
//@brief 在不使用索引列表的情況下,繪制圖元
//@param 繪制圖形組裝的基元類型
//@param 從哪個位置數(shù)據(jù)開始繪制,一般為0
//@param 每個圖元的頂點個數(shù),繪制的圖型頂點數(shù)量
/*
MTLPrimitiveTypePoint = 0, 點
MTLPrimitiveTypeLine = 1, 線段
MTLPrimitiveTypeLineStrip = 2, 線環(huán)
MTLPrimitiveTypeTriangle = 3, 三角形
MTLPrimitiveTypeTriangleStrip = 4, 三角型扇
*/
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:3];
//9.表示已該編碼器生成的命令都已完成,并且從NTLCommandBuffer中分離
[renderEncoder endEncoding];
//10.一旦框架緩沖區(qū)完成缺虐,使用當前可繪制的進度表
[commandBuffer presentDrawable:view.currentDrawable];
}
//11.最后,在這里完成渲染并將命令緩沖區(qū)推送到GPU
[commandBuffer commit];
}