一、首先我們要知道如何劃分批量數(shù)據(jù)范圍以及如何處理小批量的頂點數(shù)據(jù)
- 蘋果的官方文檔中對數(shù)據(jù)有如下說明,當(dāng)數(shù)據(jù)量小于4KB的時候,我們可以稱為小批量數(shù)據(jù),當(dāng)頂點的數(shù)據(jù)量大于4KB時,數(shù)據(jù)量就很大了,我們可以叫大批量數(shù)據(jù)。主要區(qū)別在于蘋果對這兩種的數(shù)據(jù)量的處理方式不同。
- 當(dāng)我們的頂點數(shù)據(jù)小于4KB時候,我們可以直接將頂點數(shù)據(jù)放在數(shù)組中,并使用
- (void)setVertexBytes:(const void *)bytes length:(NSUInteger)length atIndex:(NSUInteger)index方法將數(shù)據(jù)傳遞到頂點著色器函數(shù)。 - 當(dāng)頂點數(shù)據(jù)量大于4KB時候,我們可以使用一個叫
MTLBuffer的對象,它能夠?qū)?shù)據(jù)存儲到頂點緩存區(qū)中,便于GPU對這些數(shù)據(jù)進(jìn)行快速的訪問處理。
二、如何使用Metal渲染一張圖片githubDemo地址
- 思路:
1.Render類設(shè)置頂點相關(guān)操作、設(shè)置渲染管道相關(guān)操作、加載紋理、- (void)drawInMTKView:(nonnull MTKView *)view代理方法進(jìn)行渲染;
2.BaseShaderTypes.h文件來橋接OC和Metal方法;
3.BaseShaders.metal設(shè)置Metal的頂點著色器函數(shù)和片元著色器函數(shù);
4.BaseImage類通過加載一個簡單的TGA文件初始化這個圖像.只支持32bit的TGA文件; -
BaseShaderTypes.h橋接文件的具體實現(xiàn),設(shè)置的結(jié)構(gòu)體以及枚舉即支持OC又支持Metal的調(diào)用,方法如下:
typedef enum BaseVertexInputIndex {
//頂點
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類的實現(xiàn)邏輯
一、設(shè)置頂點相關(guān)操作:
1.根據(jù)頂點/紋理坐標(biāo)建立一個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)建我們的頂點緩沖區(qū),并用我們的
Qualsits數(shù)組初始化它;
_vertices = [_device newBufferWithBytes:quadVertices
length:sizeof(quadVertices)
options:MTLResourceStorageModeShared];
3.通過將字節(jié)長度除以每個頂點的大小來計算頂點的數(shù)目;
_numVertices = sizeof(quadVertices) / sizeof(BaseVertex);
二、設(shè)置渲染管道相關(guān)操作:
1.創(chuàng)建我們的渲染通道;
//從項目中加載.metal文件,創(chuàng)建一個library
id<MTLLibrary>defalutLibrary = [_device newDefaultLibrary];
//從庫中加載頂點函數(shù)
id<MTLFunction>vertexFunction = [defalutLibrary newFunctionWithName:@"vertexShader"];
//從庫中加載片元函數(shù)
id<MTLFunction> fragmentFunction = [defalutLibrary newFunctionWithName:@"fragmentShader"];
2.配置用于創(chuàng)建管道狀態(tài)的管道;
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
//管道名稱
pipelineStateDescriptor.label = @"Texturing Pipeline";
//可編程函數(shù),用于處理渲染過程中的各個頂點
pipelineStateDescriptor.vertexFunction = vertexFunction;
//可編程函數(shù),用于處理渲染過程總的各個片段/片元
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
//設(shè)置管道中存儲顏色數(shù)據(jù)的組件格式
pipelineStateDescriptor.colorAttachments[0].pixelFormat = baseMTKView.colorPixelFormat;
3.同步創(chuàng)建并返回渲染管線對象;
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對象
BaseImage *image = [[BaseImage alloc]initWithTGAFileAtLocation:imageFileLocation];
//判斷圖片是否轉(zhuǎn)換成功
if(!image)
{
NSLog(@"Failed to create the image from:%@",imageFileLocation.absoluteString);
}
2.創(chuàng)建紋理描述對象
MTLTextureDescriptor *textureDescriptor = [[MTLTextureDescriptor alloc]init];
//表示每個像素有藍(lán)色,綠色,紅色和alpha通道.其中每個通道都是8位無符號歸一化的值.(即0映射成0,255映射成1);
textureDescriptor.pixelFormat = MTLPixelFormatBGRA8Unorm;
//設(shè)置紋理的像素尺寸
textureDescriptor.width = image.width;
textureDescriptor.height = image.height;
//使用描述符從設(shè)備中創(chuàng)建紋理
_texture = [_device newTextureWithDescriptor:textureDescriptor];
//計算圖像每行的字節(jié)數(shù)
NSUInteger bytesPerRow = 4 * image.width;
3.創(chuàng)建MTLRegion 結(jié)構(gòu)體
/*
typedef struct
{
MTLOrigin origin; //開始位置x,y,z
MTLSize size; //尺寸width,height,depth
} MTLRegion;
*/
//MLRegion結(jié)構(gòu)用于標(biāo)識紋理的特定區(qū)域。 demo使用圖像數(shù)據(jù)填充整個紋理;因此,覆蓋整個紋理的像素區(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代理方法實現(xiàn)
1.為當(dāng)前渲染的每個渲染傳遞創(chuàng)建一個新的命令緩沖區(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ù)存儲樣式);
//將數(shù)據(jù)加載到MTLBuffer --> 頂點函數(shù)
[renderEncoder setVertexBuffer:_vertices
offset:0
atIndex:CCVertexInputIndexVertices];
//將數(shù)據(jù)加載到MTLBuffer --> 頂點函數(shù)
[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:CCVertexInputIndexViewportSize];
7.設(shè)置紋理對象;
[renderEncoder setFragmentTexture:_texture atIndex:CCTextureIndexBaseColor];
8.繪制;
// @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:_numVertices];
9.表示已該編碼器生成的命令都已完成,并且從MTLCommandBuffer中分離;
[renderEncoder endEncoding];
10.一旦框架緩沖區(qū)完成,使用當(dāng)前可繪制的進(jìn)度表;
[commandBuffer presentDrawable:view.currentDrawable];
11.最后,在這里完成渲染并將命令緩沖區(qū)推送到GPU;
[commandBuffer commit];
-
BaseShaders.metal類的實現(xiàn)
1.設(shè)置結(jié)構(gòu)體
//結(jié)構(gòu)體
typedef struct
{
float4 clipSpacePosition [[position]];
float2 textureCoordinate;
} RasterizerData;
2.設(shè)置頂點著色函數(shù)
vertex RasterizerData
vertexShader(uint vertexID [[vertex_id]],
constant BaseVertex *vertexArray [[buffer(BaseVertexInputIndexVertices)]],
constant vector_float2 *viewportSizePointer [[buffer(BaseVertexInputIndexViewportSize)]])
{
/*
處理頂點數(shù)據(jù):
1) 執(zhí)行坐標(biāo)系轉(zhuǎn)換,將生成的頂點剪輯空間寫入到返回值中.
2) 將頂點顏色值傳遞給返回值
*/
//定義out
RasterizerData out;
//初始化輸出剪輯空間位置
out.clipSpacePosition = vector_float4(0.0, 0.0, 0.0, 1.0);
// 索引到我們的數(shù)組位置以獲得當(dāng)前頂點
// 我們的位置是在像素維度中指定的.
float2 pixelSpacePosition = vertexArray[vertexID].position.xy;
//將vierportSizePointer 從verctor_uint2 轉(zhuǎn)換為vector_float2 類型
vector_float2 viewportSize = vector_float2(*viewportSizePointer);
//每個頂點著色器的輸出位置在剪輯空間中(也稱為歸一化設(shè)備坐標(biāo)空間,NDC),剪輯空間中的(-1,-1)表示視口的左下角,而(1,1)表示視口的右上角.
//計算和寫入 XY值到我們的剪輯空間的位置.為了從像素空間中的位置轉(zhuǎn)換到剪輯空間的位置,我們將像素坐標(biāo)除以視口的大小的一半.
out.clipSpacePosition.xy = pixelSpacePosition / (viewportSize / 2.0);
out.clipSpacePosition.z = 0.0f;
out.clipSpacePosition.w = 1.0f;
//把我們輸入的顏色直接賦值給輸出顏色. 這個值將于構(gòu)成三角形的頂點的其他顏色值插值,從而為我們片段著色器中的每個片段生成顏色值.
out.textureCoordinate = vertexArray[vertexID].textureCoordinate;
//完成! 將結(jié)構(gòu)體傳遞到管道中下一個階段:
return out;
}
3.片元著色器函數(shù):當(dāng)頂點函數(shù)執(zhí)行3次,三角形的每個頂點執(zhí)行一次后,則執(zhí)行管道中的下一個階段.柵格化/光柵化.
// 片元函數(shù)
//[[stage_in]],片元著色函數(shù)使用的單個片元輸入數(shù)據(jù)是由頂點著色函數(shù)輸出.然后經(jīng)過光柵化生成的.單個片元輸入函數(shù)數(shù)據(jù)可以使用"[[stage_in]]"屬性修飾符.
//一個頂點著色函數(shù)可以讀取單個頂點的輸入數(shù)據(jù),這些輸入數(shù)據(jù)存儲于參數(shù)傳遞的緩存中,使用頂點和實例ID在這些緩存中尋址.讀取到單個頂點的數(shù)據(jù).另外,單個頂點輸入數(shù)據(jù)也可以通過使用"[[stage_in]]"屬性修飾符的產(chǎ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;
}
- 三、小批量頂點數(shù)據(jù)存儲樣式BaseMetalDemo地址
- (void)setVertexBytes:(const void *)bytes length:(NSUInteger)length atIndex:(NSUInteger)index使用該方法來處理低于4KB的數(shù)據(jù)量,
- (void)drawInMTKView:(nonnull MTKView *)view {
//1.頂點數(shù)據(jù)/顏色數(shù)據(jù)
static const BaseVertex 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.當(dāng)前渲染的每一個渲染創(chuàng)建一個新的命令緩存區(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ū)域
//為管道分配自定義視口需要通過調(diào)用setViewport:方法將MTLViewport結(jié)構(gòu)編碼為渲染命令編碼器。 如果未指定視口,Metal會設(shè)置一個默認(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)前渲染管道對象
[renderEncoder setRenderPipelineState:_pipelineState];
//7.從應(yīng)用程序OC 代碼 中發(fā)送數(shù)據(jù)給Metal 頂點著色器 函數(shù)
//頂點數(shù)據(jù)+顏色數(shù)據(jù)
// 1) 指向要傳遞給著色器的內(nèi)存的指針
// 2) 我們想要傳遞的數(shù)據(jù)的內(nèi)存大小
// 3)一個整數(shù)索引,它對應(yīng)于我們的“vertexShader”函數(shù)中的緩沖區(qū)屬性限定符的索引。
[renderEncoder setVertexBytes:&triangleVertices length:sizeof(triangleVertices) atIndex:BaseVertexInputIndexVertices];
//viewPortSize 數(shù)據(jù)
//1) 發(fā)送到頂點著色函數(shù)中,視圖大小
//2) 視圖大小內(nèi)存空間大小
//3) 對應(yīng)的索引
[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:BaseVertexInputIndexViewportSize];
//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.表示已該編碼器生成的命令都已完成,并且從MTLCommandBuffer中分離
[renderEncoder endEncoding];
//10.一旦框架緩沖區(qū)完成,使用當(dāng)前可繪制的進(jìn)度表
[commandBuffer presentDrawable:view.currentDrawable];
}
//11.最后,在這里完成渲染并將命令緩沖區(qū)推送到GPU
[commandBuffer commit];
}