一柿赊、首先我們要知道如何劃分批量數(shù)據(jù)范圍以及如何處理小批量的頂點(diǎn)數(shù)據(jù)
- 蘋果的官方文檔中對(duì)數(shù)據(jù)有如下說(shuō)明闷游,當(dāng)數(shù)據(jù)量小于4KB的時(shí)候瞻鹏,我們可以稱為小批量數(shù)據(jù)朦佩,當(dāng)頂點(diǎn)的數(shù)據(jù)量大于4KB時(shí)尖滚,數(shù)據(jù)量就很大了喉刘,我們可以叫大批量數(shù)據(jù)。主要區(qū)別在于蘋果對(duì)這兩種的數(shù)據(jù)量的處理方式不同漆弄。
- 當(dāng)我們的頂點(diǎn)數(shù)據(jù)小于4KB時(shí)候睦裳,我們可以直接將頂點(diǎn)數(shù)據(jù)放在數(shù)組中,并使用
- (void)setVertexBytes:(const void *)bytes length:(NSUInteger)length atIndex:(NSUInteger)index
方法將數(shù)據(jù)傳遞到頂點(diǎn)著色器函數(shù)置逻。 - 當(dāng)頂點(diǎn)數(shù)據(jù)量大于4KB時(shí)候推沸,我們可以使用一個(gè)叫
MTLBuffer
的對(duì)象,它能夠?qū)?shù)據(jù)存儲(chǔ)到頂點(diǎn)緩存區(qū)中,便于GPU對(duì)這些數(shù)據(jù)進(jìn)行快速的訪問(wèn)處理鬓催。
二肺素、如何使用Metal渲染一張圖片githubDemo地址
- 思路:
1.Render
類設(shè)置頂點(diǎn)相關(guān)操作、設(shè)置渲染管道相關(guān)操作宇驾、加載紋理倍靡、- (void)drawInMTKView:(nonnull MTKView *)view
代理方法進(jìn)行渲染;
2.BaseShaderTypes.h
文件來(lái)橋接OC和Metal方法课舍;
3.BaseShaders.metal
設(shè)置Metal的頂點(diǎn)著色器函數(shù)和片元著色器函數(shù)塌西;
4.BaseImage
類通過(guò)加載一個(gè)簡(jiǎn)單的TGA文件初始化這個(gè)圖像.只支持32bit的TGA文件; -
BaseShaderTypes.h
橋接文件的具體實(shí)現(xiàn),設(shè)置的結(jié)構(gòu)體以及枚舉即支持OC又支持Metal的調(diào)用筝尾,方法如下:
typedef enum BaseVertexInputIndex {
//頂點(diǎn)
BaseVertexInputIndexVertices =0,
//視圖大小
BaseVertexInputIndexViewportSize=1,
}BaseVertexInputIndex;
//紋理索引
typedef enum BaseTextureIndex
{
BaseTextureIndexBaseColor = 0
}BaseTextureIndex;
//結(jié)構(gòu)體
typedef struct {
//像素空間位置
vector_float2 position;
//2D紋理
vector_float2 textureCoordinate;
}BaseVertex;
-
Render
類的實(shí)現(xiàn)邏輯
一捡需、設(shè)置頂點(diǎn)相關(guān)操作:
1.根據(jù)頂點(diǎn)/紋理坐標(biāo)建立一個(gè)MTLBuffer
;
static const BaseVertex quadVertices[] = {
//像素坐標(biāo),紋理坐標(biāo)
{ { 250, -250 }, { 1.f, 0.f } },
{ { -250, -250 }, { 0.f, 0.f } },
{ { -250, 250 }, { 0.f, 1.f } },
{ { 250, -250 }, { 1.f, 0.f } },
{ { -250, 250 }, { 0.f, 1.f } },
{ { 250, 250 }, { 1.f, 1.f } },
};
2.創(chuàng)建我們的頂點(diǎn)緩沖區(qū)筹淫,并用我們的
Qualsits
數(shù)組初始化它站辉;
_vertices = [_device newBufferWithBytes:quadVertices
length:sizeof(quadVertices)
options:MTLResourceStorageModeShared];
3.通過(guò)將字節(jié)長(zhǎng)度除以每個(gè)頂點(diǎn)的大小來(lái)計(jì)算頂點(diǎn)的數(shù)目;
_numVertices = sizeof(quadVertices) / sizeof(BaseVertex);
二损姜、設(shè)置渲染管道相關(guān)操作:
1.創(chuàng)建我們的渲染通道;
//從項(xiàng)目中加載.metal文件,創(chuàng)建一個(gè)library
id<MTLLibrary>defalutLibrary = [_device newDefaultLibrary];
//從庫(kù)中加載頂點(diǎn)函數(shù)
id<MTLFunction>vertexFunction = [defalutLibrary newFunctionWithName:@"vertexShader"];
//從庫(kù)中加載片元函數(shù)
id<MTLFunction> fragmentFunction = [defalutLibrary newFunctionWithName:@"fragmentShader"];
2.配置用于創(chuàng)建管道狀態(tài)的管道;
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
//管道名稱
pipelineStateDescriptor.label = @"Texturing Pipeline";
//可編程函數(shù),用于處理渲染過(guò)程中的各個(gè)頂點(diǎn)
pipelineStateDescriptor.vertexFunction = vertexFunction;
//可編程函數(shù),用于處理渲染過(guò)程總的各個(gè)片段/片元
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
//設(shè)置管道中存儲(chǔ)顏色數(shù)據(jù)的組件格式
pipelineStateDescriptor.colorAttachments[0].pixelFormat = baseMTKView.colorPixelFormat;
3.同步創(chuàng)建并返回渲染管線對(duì)象;
NSError *error = NULL;
_pipeLineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor error:&error];
//判斷是否創(chuàng)建成功
if (!_pipeLineState)
{
NSLog(@"Failed to created pipeline state, error %@", error);
}
4.使用
_device
創(chuàng)建commandQueue
;
_commandQueue = [_device newCommandQueue];
三饰剥、加載紋理TGA文件
1.獲取tag的路徑
NSURL *imageFileLocation = [[NSBundle mainBundle] URLForResource:@"Image"withExtension:@"tga"];
//將tag文件->CCImage對(duì)象
BaseImage *image = [[BaseImage alloc]initWithTGAFileAtLocation:imageFileLocation];
//判斷圖片是否轉(zhuǎn)換成功
if(!image)
{
NSLog(@"Failed to create the image from:%@",imageFileLocation.absoluteString);
}
2.創(chuàng)建紋理描述對(duì)象
MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc]init];
//表示每個(gè)像素有藍(lán)色,綠色,紅色和alpha通道.其中每個(gè)通道都是8位無(wú)符號(hào)歸一化的值.(即0映射成0,255映射成1);
textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
//設(shè)置紋理的像素尺寸
textureDescriptor.width = image.width;
textureDescriptor.height = image.height;
//使用描述符從設(shè)備中創(chuàng)建紋理
_texture = [_device newTextureWithDescriptor:textureDescriptor];
//計(jì)算圖像每行的字節(jié)數(shù)
NSUInteger bytesPerRow = 4 * image.width;
3.創(chuàng)建MTLRegion 結(jié)構(gòu)體
/*
typedef struct
{
MTLOrigin origin; //開(kāi)始位置x,y,z
MTLSize size; //尺寸width,height,depth
} MTLRegion;
*/
//MLRegion結(jié)構(gòu)用于標(biāo)識(shí)紋理的特定區(qū)域。 demo使用圖像數(shù)據(jù)填充整個(gè)紋理摧阅;因此汰蓉,覆蓋整個(gè)紋理的像素區(qū)域等于紋理的尺寸。
MTLRegion region = {
{0,0,0},
{image.width,image.height,1}
};
4.復(fù)制圖片數(shù)據(jù)到texture
[_texture replaceRegion:region mipmapLevel:0 withBytes:image.data.bytes bytesPerRow:bytesPerRow];
四棒卷、
MTKViewDelegate
代理方法實(shí)現(xiàn)
1.為當(dāng)前渲染的每個(gè)渲染傳遞創(chuàng)建一個(gè)新的命令緩沖區(qū);
id<MTLCommandBuffer> commandBuffer = [_commandQueue commandBuffer];
//指定緩存區(qū)名稱
commandBuffer.label = @"MyCommand";
2.currentRenderPassDescriptor
描述符包含currentDrawables
的紋理顾孽、視圖的深度、模板和sample
緩沖區(qū)和清晰的值娇跟。
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
3.創(chuàng)建渲染命令編碼器,這樣我們才可以渲染到something;
id<MTLRenderCommandEncoder> renderEncoder =
[commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
//渲染器名稱
renderEncoder.label = @"MyRenderEncoder";
4.設(shè)置我們繪制的可繪制區(qū)域;
[renderEncoder setViewport:(MTLViewport){0.0, 0.0, _viewportSize.x, _viewportSize.y, -1.0, 1.0 }];
5.設(shè)置渲染管道;
[renderEncoder setRenderPipelineState:_pipelineState];
6.加載數(shù)據(jù)(大批量數(shù)據(jù)存儲(chǔ)樣式);
//將數(shù)據(jù)加載到MTLBuffer --> 頂點(diǎn)函數(shù)
[renderEncoder setVertexBuffer:_vertices
offset:0
atIndex:CCVertexInputIndexVertices];
//將數(shù)據(jù)加載到MTLBuffer --> 頂點(diǎn)函數(shù)
[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:CCVertexInputIndexViewportSize];
7.設(shè)置紋理對(duì)象;
[renderEncoder setFragmentTexture:_texture atIndex:CCTextureIndexBaseColor];
8.繪制;
// @method drawPrimitives:vertexStart:vertexCount:
//@brief 在不使用索引列表的情況下,繪制圖元
//@param 繪制圖形組裝的基元類型
//@param 從哪個(gè)位置數(shù)據(jù)開(kāi)始繪制,一般為0
//@param 每個(gè)圖元的頂點(diǎn)個(gè)數(shù),繪制的圖型頂點(diǎn)數(shù)量
/*
MTLPrimitiveTypePoint = 0, 點(diǎn)
MTLPrimitiveTypeLine = 1, 線段
MTLPrimitiveTypeLineStrip = 2, 線環(huán)
MTLPrimitiveTypeTriangle = 3, 三角形
MTLPrimitiveTypeTriangleStrip = 4, 三角型扇
*/
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:_numVertices];
9.表示已該編碼器生成的命令都已完成,并且從MTLCommandBuffer
中分離;
[renderEncoder endEncoding];
10.一旦框架緩沖區(qū)完成岩齿,使用當(dāng)前可繪制的進(jìn)度表;
[commandBuffer presentDrawable:view.currentDrawable];
11.最后,在這里完成渲染并將命令緩沖區(qū)推送到GPU;
[commandBuffer commit];
-
BaseShaders.metal
類的實(shí)現(xiàn)
1.設(shè)置結(jié)構(gòu)體
//結(jié)構(gòu)體
typedef struct
{
float4 clipSpacePosition [[position]];
float2 textureCoordinate;
} RasterizerData;
2.設(shè)置頂點(diǎn)著色函數(shù)
vertex RasterizerData
vertexShader(uint vertexID [[vertex_id]],
constant BaseVertex *vertexArray [[buffer(BaseVertexInputIndexVertices)]],
constant vector_float2 *viewportSizePointer [[buffer(BaseVertexInputIndexViewportSize)]])
{
/*
處理頂點(diǎn)數(shù)據(jù):
1) 執(zhí)行坐標(biāo)系轉(zhuǎn)換,將生成的頂點(diǎn)剪輯空間寫入到返回值中.
2) 將頂點(diǎn)顏色值傳遞給返回值
*/
//定義out
RasterizerData out;
//初始化輸出剪輯空間位置
out.clipSpacePosition = vector_float4(0.0, 0.0, 0.0, 1.0);
// 索引到我們的數(shù)組位置以獲得當(dāng)前頂點(diǎn)
// 我們的位置是在像素維度中指定的.
float2 pixelSpacePosition = vertexArray[vertexID].position.xy;
//將vierportSizePointer 從verctor_uint2 轉(zhuǎn)換為vector_float2 類型
vector_float2 viewportSize = vector_float2(*viewportSizePointer);
//每個(gè)頂點(diǎn)著色器的輸出位置在剪輯空間中(也稱為歸一化設(shè)備坐標(biāo)空間,NDC),剪輯空間中的(-1,-1)表示視口的左下角,而(1,1)表示視口的右上角.
//計(jì)算和寫入 XY值到我們的剪輯空間的位置.為了從像素空間中的位置轉(zhuǎn)換到剪輯空間的位置,我們將像素坐標(biāo)除以視口的大小的一半.
out.clipSpacePosition.xy = pixelSpacePosition / (viewportSize / 2.0);
out.clipSpacePosition.z = 0.0f;
out.clipSpacePosition.w = 1.0f;
//把我們輸入的顏色直接賦值給輸出顏色. 這個(gè)值將于構(gòu)成三角形的頂點(diǎn)的其他顏色值插值,從而為我們片段著色器中的每個(gè)片段生成顏色值.
out.textureCoordinate = vertexArray[vertexID].textureCoordinate;
//完成! 將結(jié)構(gòu)體傳遞到管道中下一個(gè)階段:
return out;
}
3.片元著色器函數(shù):當(dāng)頂點(diǎn)函數(shù)執(zhí)行3次,三角形的每個(gè)頂點(diǎn)執(zhí)行一次后,則執(zhí)行管道中的下一個(gè)階段.柵格化/光柵化.
// 片元函數(shù)
//[[stage_in]],片元著色函數(shù)使用的單個(gè)片元輸入數(shù)據(jù)是由頂點(diǎn)著色函數(shù)輸出.然后經(jīng)過(guò)光柵化生成的.單個(gè)片元輸入函數(shù)數(shù)據(jù)可以使用"[[stage_in]]"屬性修飾符.
//一個(gè)頂點(diǎn)著色函數(shù)可以讀取單個(gè)頂點(diǎn)的輸入數(shù)據(jù),這些輸入數(shù)據(jù)存儲(chǔ)于參數(shù)傳遞的緩存中,使用頂點(diǎn)和實(shí)例ID在這些緩存中尋址.讀取到單個(gè)頂點(diǎn)的數(shù)據(jù).另外,單個(gè)頂點(diǎn)輸入數(shù)據(jù)也可以通過(guò)使用"[[stage_in]]"屬性修飾符的產(chǎn)生傳遞給頂點(diǎn)著色函數(shù).
//被stage_in 修飾的結(jié)構(gòu)體的成員不能是如下這些.Packed vectors 緊密填充類型向量,matrices 矩陣,structs 結(jié)構(gòu)體,references or pointers to type 某類型的引用或指針. arrays,vectors,matrices 標(biāo)量,向量,矩陣數(shù)組.
fragment float4 fragmentShader(RasterizerData in [[stage_in]],
texture2d<half> colorTexture [[texture(BaseTextureIndexBaseColor)]])
{
constexpr sampler textureSampler(mag_filter::linear,
min_filter::linear);
const half4 colorSampler = colorTexture.sample(textureSampler,in.textureCoordinate);
return float4(colorSampler);
//返回輸入的片元顏色
//return in.color;
}
- 三、小批量頂點(diǎn)數(shù)據(jù)存儲(chǔ)樣式BaseMetalDemo地址
- (void)setVertexBytes:(const void *)bytes length:(NSUInteger)length atIndex:(NSUInteger)index
使用該方法來(lái)處理低于4KB的數(shù)據(jù)量苞俘,
- (void)drawInMTKView:(nonnull MTKView *)view {
//1.頂點(diǎn)數(shù)據(jù)/顏色數(shù)據(jù)
static const BaseVertex triangleVertices[] =
{
//頂點(diǎn), 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.當(dāng)前渲染的每一個(gè)渲染創(chuàng)建一個(gè)新的命令緩存區(qū)
id<MTLCommandBuffer>commandBuffer = [_commandQueue commandBuffer];
//指定緩存區(qū)名字
commandBuffer.label = @"MyCommand";
//3.MTLRenderPassDescriptor:一組渲染目標(biāo)盹沈,用作渲染通道生成的像素的輸出目標(biāo)。
MTLRenderPassDescriptor *renderPassDescriptor = view.currentRenderPassDescriptor;
//判斷渲染目標(biāo)是否為空
if(renderPassDescriptor != nil){
//4.創(chuàng)建渲染命令編碼器吃谣,這樣我們才能渲染事物
id<MTLRenderCommandEncoder>renderEncoder = [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
//渲染器名稱
renderEncoder.label = @"MyRenderEncoder";
//5.設(shè)置可繪制的區(qū)域
/*
typedef struct {
double originX, originY, width, height, znear, zfar;
} MTLViewport;
*/
//視口指定Metal渲染內(nèi)容的drawable區(qū)域乞封。 視口是具有x和y偏移,寬度和高度以及近和遠(yuǎn)平面的3D區(qū)域
//為管道分配自定義視口需要通過(guò)調(diào)用setViewport:方法將MTLViewport結(jié)構(gòu)編碼為渲染命令編碼器岗憋。 如果未指定視口肃晚,Metal會(huì)設(shè)置一個(gè)默認(rèn)視口,其大小與用于創(chuàng)建渲染命令編碼器的drawable相同仔戈。
MTLViewport viewPort = {
0.0,0.0,_viewportSize.x,_viewportSize.y,-1.0,1.0
};
[renderEncoder setViewport:viewPort];
//6.設(shè)置當(dāng)前渲染管道對(duì)象
[renderEncoder setRenderPipelineState:_pipelineState];
//7.從應(yīng)用程序OC 代碼 中發(fā)送數(shù)據(jù)給Metal 頂點(diǎn)著色器 函數(shù)
//頂點(diǎn)數(shù)據(jù)+顏色數(shù)據(jù)
// 1) 指向要傳遞給著色器的內(nèi)存的指針
// 2) 我們想要傳遞的數(shù)據(jù)的內(nèi)存大小
// 3)一個(gè)整數(shù)索引关串,它對(duì)應(yīng)于我們的“vertexShader”函數(shù)中的緩沖區(qū)屬性限定符的索引拧廊。
[renderEncoder setVertexBytes:&triangleVertices length:sizeof(triangleVertices) atIndex:BaseVertexInputIndexVertices];
//viewPortSize 數(shù)據(jù)
//1) 發(fā)送到頂點(diǎn)著色函數(shù)中,視圖大小
//2) 視圖大小內(nèi)存空間大小
//3) 對(duì)應(yīng)的索引
[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:BaseVertexInputIndexViewportSize];
//8.畫出三角形的3個(gè)頂點(diǎn)
// @method drawPrimitives:vertexStart:vertexCount:
//@brief 在不使用索引列表的情況下,繪制圖元
//@param 繪制圖形組裝的基元類型
//@param 從哪個(gè)位置數(shù)據(jù)開(kāi)始繪制,一般為0
//@param 每個(gè)圖元的頂點(diǎn)個(gè)數(shù),繪制的圖型頂點(diǎn)數(shù)量
/*
MTLPrimitiveTypePoint = 0, 點(diǎn)
MTLPrimitiveTypeLine = 1, 線段
MTLPrimitiveTypeLineStrip = 2, 線環(huán)
MTLPrimitiveTypeTriangle = 3, 三角形
MTLPrimitiveTypeTriangleStrip = 4, 三角型扇
*/
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle vertexStart:0 vertexCount:3];
//9.表示已該編碼器生成的命令都已完成,并且從MTLCommandBuffer中分離
[renderEncoder endEncoding];
//10.一旦框架緩沖區(qū)完成,使用當(dāng)前可繪制的進(jìn)度表
[commandBuffer presentDrawable:view.currentDrawable];
}
//11.最后,在這里完成渲染并將命令緩沖區(qū)推送到GPU
[commandBuffer commit];
}