原文https://developer.apple.com/documentation/metal/hello_triangle
你好,三角形
概覽
在設(shè)備和命令例子中,你學(xué)習(xí)了如何編寫一個使用Metal技術(shù)的應(yīng)用以及了解到一些GPU基礎(chǔ)的渲染命令.
在這個例子中,你將要學(xué)習(xí)使用Metal API 渲染基礎(chǔ)的幾何體. 尤其 , 你將要學(xué)習(xí)如何使用頂點數(shù)據(jù)和SIMD 類型, 配置 圖像渲染管線,書寫GPU 函數(shù)和 繪制調(diào)用相關(guān)的問題.
Metal 圖像渲染管線
Metal 圖像渲染管線是有多個圖像處理單元階段組成, 一些事可編程管線,還有一些是固定的 ,執(zhí)行繪制命令.
Metal 定義 輸入, 處理 和管線的輸出.作為 渲染命令應(yīng)用到中心數(shù)據(jù).
這是一個基礎(chǔ)的表格,管線接受 頂點作為輸入 和 渲染像素作為輸出.
這個例子,關(guān)注 三個主要的管線狀態(tài): 頂點函數(shù),光柵化狀態(tài) 和 片段函數(shù). 頂點函數(shù)和片段函數(shù) 作為可編程狀態(tài). 光柵化是固定的狀態(tài).
MTLRenderPipelineState
對象 展示出 一個 圖像渲染管線. 管線的多種狀態(tài) 能夠配置通過使用 MTLRenderPipelineDescriptor
對象. 這是定義大部分 如何Metal 處理 輸入 頂點 到 渲染 輸出像素.
頂點數(shù)據(jù)
一個頂點是一個 簡單的點 在空間中 ,2 個多活多條線.
頂點是 展示位 笛卡爾坐標(biāo)系 的集合 ,定義 指定的幾何體, 可選的數(shù)據(jù) 連接著 坐標(biāo)系.
案例中的渲染器是一個簡單的2D三角形,它由三個頂點構(gòu)成. 每一個頂點結(jié)構(gòu)體包含 左邊位置還有三角邊顏色.
位置是被要求的頂點屬性. 然后顏色是可選的.
在這個例子中, 這個管線 使用 兩個 頂點屬性 去渲染 顏色化的三角形 在 指定的 可繪畫對象范圍.
使用SIMD 數(shù)據(jù)類型
頂點數(shù)據(jù) 通常由 指定的模型軟件從包含3D 模型 數(shù)據(jù)的文件中載入.
詳細(xì)的模型 可能包含成千上萬個頂點和很多的屬性.但是 最終 它們需要被 打包,編碼 然后傳送到 GPU 中.
示例的三角形 每一個頂點 結(jié)構(gòu)體 定義 2D位置(x,y) 和 RGBA 顏色(red,green,alpha) .
硬編碼到結(jié)構(gòu)體的數(shù)組. 每一個元素表示為一個單一頂點.
結(jié)構(gòu)體 使用 數(shù)組元素的數(shù)據(jù)類型 定義 每一個頂點的內(nèi)存分布.
頂點數(shù)據(jù) 和3D圖形數(shù)據(jù) 是通用的, 通常 定義 向量數(shù)據(jù)類型 和 簡化的通用圖像算法 和GPU 處理.
這個例子使用 優(yōu)化的向量數(shù)據(jù)類型 , 用SIMD 庫提供, 展示三角形的頂點.
SIMD 庫 是獨立的 ,和Metal,MetalKit 分開.但是 非常推薦使用 開發(fā)Metal App , 主要優(yōu)點是 方便和高性能.
三角形的2D頂點 組件 加入到展示位 vector_float2
SIMD 數(shù)據(jù)類型,
2 32位 浮點值類型,
同樣的 三角形的RGBA 顏色組件 vector_float4
SIMD 數(shù)據(jù)類型 ,包含4個32位浮點值,
這些屬性 組成 AAPLVertex
結(jié)構(gòu)體.
typedef struct
{
// Positions in pixel space (i.e. a value of 100 indicates 100 pixels from the origin/center)
vector_float2 position;
// Floating point RGBA colors
vector_float4 color;
} AAPLVertex;
三角形的三個頂點是 直接硬編碼到 AAPLVertex
元素的數(shù)據(jù),因此需要為每一個頂點 定義準(zhǔn)確的屬性值.
static const AAPLVertex triangleVertices[] =
{
// 2D Positions, RGBA colors
{ { 250, -250 }, { 1, 0, 0, 1 } },
{ { -250, -250 }, { 0, 1, 0, 1 } },
{ { 0, 250 }, { 0, 0, 1, 1 } },
};
設(shè)置 Viewport
視窗 指定 可繪畫對象的面積, Metal 渲染內(nèi)容.
一個視窗是3D面積 有 x, y 偏移值 還有 寬度和高度, 近和遠(yuǎn)平面,(通過這些最新的不是需要 因為當(dāng)前例子僅僅渲染2D內(nèi)容)
設(shè)置管線的自定義視窗 要求 編碼MTLViewport
結(jié)構(gòu)體 到 渲染命令編碼器 調(diào)用 setViewport:
方法 ,
如果視窗 不指定,Metal 將設(shè)置默認(rèn)視窗大小和可繪畫對象(被用于創(chuàng)建渲染命令編碼器)一致.
寫頂點函數(shù)
主要的頂點函數(shù)任務(wù) 是處理 輸入的頂點數(shù)據(jù)和映射每一個頂點的位置到視窗(viewport),
子序列狀態(tài) 這個管線能夠 涉及 視窗位置和渲染像素 明確的坐標(biāo)在可繪畫對象.
頂點函數(shù) 完成任務(wù), 翻譯抽象的頂點坐標(biāo)系到 標(biāo)準(zhǔn)化設(shè)備坐標(biāo)系 (clip-space坐標(biāo)系).
clip-space 是一個2D坐標(biāo)系系統(tǒng) 映射 視窗面積 在 [-1.0 ,1.0]范圍 ,沿著x,y 軸 .
視窗 左下角 映射(-1.0,-1.0),右上角映射(1.0,1.0) 以及中心 映射(0.0,0.0)
頂點函數(shù)執(zhí)行一次 每一個頂點繪制一次,
在這個例子中,每一個幀,三個頂點被繪制組成為一個三角形,因此,頂點函數(shù)每一幀執(zhí)行三次.
頂點函數(shù)使用 Metal Shading Language 書寫. 這是一門基于C++ 14標(biāo)準(zhǔn)的語言. Metal Shading Language 代碼看起來和傳統(tǒng)的C/C++ 代碼相似.
但是有兩個最基本的不同,傳統(tǒng)的C/C++代碼 是被CPU 執(zhí)行,而Metal Shading Language 代碼是被 GPU 執(zhí)行.
GPU 提供大量處理帶寬和能工作 ,并行 大量的頂點和片段, 然而 ,需要少的內(nèi)存比 CPU , 不需要處理控制流操作 高效, 高延遲.
頂點函數(shù)是又名 頂點著色器 和它的簽名.
vertex RasterizerData
vertexShader(uint vertexID [[vertex_id]],
constant AAPLVertex *vertices [[buffer(AAPLVertexInputIndexVertices)]],
constant vector_uint2 *viewportSizePointer [[buffer(AAPLVertexInputIndexViewportSize)]])
聲明頂點函數(shù)參數(shù)
第一個參數(shù), vertexID
使用
[[vertex_id]]
屬性標(biāo)識符 和 當(dāng)前的頂點索引 執(zhí)行.
當(dāng)繪制調(diào)用 使用這個頂點函數(shù), 這個值開始為0 ,增加 每一個祈禱 頂點著色器, 一個參數(shù)使用 [[vertex_id]]
屬性標(biāo)識符 使用 索引 數(shù)組包含頂點.
第二個參數(shù), 頂點 , 是一個包含每一個頂點被定義為AAPLVertex
類型的頂點數(shù)組. 頂點數(shù)組結(jié)構(gòu)體指針.
第三個和最后一個參數(shù) viewportSizePointer
包含 視窗的大小 和 vector_uint2
數(shù)據(jù)類型.
vertices 和 viewportSizePointer 參數(shù) 使用SIMA 數(shù)據(jù)類型, 這個類型理解為 在C和 Metal Shading Language 代碼.
示例能夠定義 AAPLVertex
結(jié)構(gòu)體 在 AAPLShaderTypes.h
頭文件, 包含 AAPLRenderer.m
和 AAPLShaders.metal
代碼.
因此 這共享的頭文件 確保 三角形的頂點的數(shù)據(jù)類型 在 OC 聲明中 (triangleVertices
) 在Metal Shading Language 聲明(vertices
) ,使用SIMD 數(shù)據(jù)類型 在你的Metal app 確保 內(nèi)存分布正確 跨CPU/GPU 描述 , 加速發(fā)送頂點數(shù)據(jù) 從CPU 發(fā)送到GPU.
注意:
AAPLVertex結(jié)構(gòu)體的改變都會影響到AAPLRenderer.m 和 AAPLShaders.metal 代碼匹配.
在vertices
和 viewportSizePointer
參數(shù) 使用 [[buffer(index)]]
屬性標(biāo)識符.
AAPLVertexInputIndex
的值 和 AAPLVertexInputIndexViewportSize
索引 使用 表示 和 設(shè)置 頂點函數(shù)的輸入 在AAPLRenderer.m
和 AAPLShaders.metal
代碼.
聲明頂點函數(shù)返回值
RasterizerData
結(jié)構(gòu)體定義頂點著色器的返回值.
typedef struct
{
// The [[position]] attribute qualifier of this member indicates this value is the clip space
// position of the vertex when this structure is returned from the vertex function
float4 clipSpacePosition [[position]];
// Since this member does not have a special attribute qualifier, the rasterizer will
// interpolate its value with values of other vertices making up the triangle and
// pass that interpolated value to the fragment shader for each fragment in that triangle
float4 color;
} RasterizerData;
頂點函數(shù)必須返回 clip-space位置值, 為每一個頂點 通過 [[position]]
屬性標(biāo)識符.
clipSpacePosition
成員使用
當(dāng)屬性被聲明的時候, 接下里狀態(tài)就是光柵化, 使用 clipSpacePosition
值定義 三角形的角 的位置 和 決定 像素的渲染.
處理頂點數(shù)據(jù)
例子中 頂點函數(shù)的函數(shù)體 有兩個輸入頂點.
- 執(zhí)行 坐標(biāo)系統(tǒng)轉(zhuǎn)化, 寫 結(jié)果 頂點clip-space 到
out.clipSpacePosition
返回值. - 通過頂點顏色
out.color
返回值.
獲取輸入頂點, vertexID
參數(shù)被使用 索引 在 verteices array.
float2 pixelSpacePosition = vertices[vertexID].position.xy;
這個例子包含 2D 頂點坐標(biāo)系 在 position成員 每一個頂點元素 和 轉(zhuǎn)化為 clip-space position 寫入到 'out.clipSpacePosition' 返回值.
每一個輸入位置 定義相關(guān)的 像素個數(shù)在 X, Y 放心 從 視窗的中心, 因此, 去轉(zhuǎn)化 像素 坐標(biāo)到clip-space 坐標(biāo), 在頂點函數(shù) 分割為 視窗 大小的一般.
out.clipSpacePosition.xy = pixelSpacePosition / (viewportSize / 2.0);
最后 頂點函數(shù)訪問 每個頂點元素的顏色成員變量 和 通過 out.color
返回值 沒有執(zhí)行任何修改.
out.color = vertices[vertexID].color;
RasterizerData
的內(nèi)容返回值 是完整的 , 結(jié)構(gòu)體 根據(jù)管線到達(dá)下一個狀態(tài).
光柵化
在頂點函數(shù)執(zhí)行三次以后, 三角形的每一個頂點,下一個狀態(tài) 管線 光柵化 開始.
光柵化 狀態(tài), 管線的光柵化單元 生成片段.
一個片段包含原生的 預(yù)像素數(shù)據(jù) , 用來生產(chǎn) 可繪制對象渲染的像素.
每一個完整的三角形通過頂點函數(shù)生產(chǎn). 光柵化決定 目標(biāo)可繪畫對象的像素 是 被 三角形覆蓋.
每一個像素的中心 在可繪畫對象中 在 三角形里面.
接下來的示意圖, 僅僅片段 中心像素 在三角形 產(chǎn)生.
這些片段 被展示位灰色正方形.
光柵化 永陽 決定 被發(fā)送到接下來的狀態(tài)的值,在管線中 , 片段著色器 在 管線之前, 頂點函數(shù) 輸出 數(shù)據(jù)類型為 RasterizerData
結(jié)構(gòu)體的返回值, 包含 clip-space 坐標(biāo)
和 顏色.
clipSpacePosition
成員 要求使用 [[position]]
屬性標(biāo)識符.
指示 這些值能被直接使用 決定 三角形片段 面積.
這個顏色成員 沒有 屬性標(biāo)識符, 指示 這些值可能會被中斷 在通過三角形的片段時候.
光柵化通道 顏色值 到 片段函數(shù) 在 轉(zhuǎn)化 配一個頂點值 到 每一個片段值.
這個會話固定 中斷函數(shù) ,計算 單一權(quán)重顏色 起源于 三角形的三個頂點顏色值.
中斷函數(shù)的權(quán)重 (坐標(biāo)系) 是相關(guān)距離 每一個頂點位置 和 片段中心.
- 如何片段準(zhǔn)確在三角形中間, 和三角形的每一個頂點等距離. 每一個頂點顏色 權(quán)重為1/3. ,接下來的示意圖, 這展示出了, 灰度片段(0.33,0.33,0.33) 在三角形的中間.
- If a fragment is very close to one vertex and very far from the other two, the color of the close vertex is weighted toward 1 and the color of the far ones is weighted toward 0. In the following diagram, this is shown as the reddish fragment (0.5, 0.25, 0.25) near the bottom-right corner of the triangle.
如何片段非常接近一個頂點以及 跟另外兩個頂點非常遠(yuǎn)的話,這個接近頂點的顏色是趨向于1 ,并且與遠(yuǎn)的頂點顏色趨向于0,. 在接下來的示意圖中, 微紅片段(0.5,0.25,0.25)接近 , 三角形的有低部角.
- 如果片段是在三角形的邊緣, 在三角形任意兩個頂點之間的邊緣. 每一條頂點定義的邊緣的顏色權(quán)重是1/2, 顏色的無邊權(quán)重是0,
在接下來的示例圖中, 展示了青色片段(0,0.5,0.5)在 三角形的左邊緣.
Because rasterization is a fixed pipeline stage, its behavior can’t be modified by custom Metal shading language code. After the rasterizer creates a fragment, along with its associated values, the results are passed along to the next stage in the pipeline.
因為光柵化是固定的管線狀態(tài),他的行為是不能被Metal Shading Language 代碼修改的, 在光柵化創(chuàng)建片段以后, 復(fù)制, 這個結(jié)果是 傳遞到下一個狀態(tài).
編寫片段著色器
The main task of a fragment function (also known as fragment shader) is to process incoming fragment data and calculate a color value for the drawable’s pixels.
片段函數(shù)的主要任務(wù)是 處理 傳進(jìn)來的片段數(shù)據(jù),然后計算 可繪畫對象的像素的顏色值.
The fragment function in this sample is called fragmentShader and this is its signature
片段函數(shù) 被叫做 片段著色器, fragment
是函數(shù)標(biāo)識符.
fragment float4 fragmentShader(RasterizerData in [[stage_in]])
The function has a single parameter, in, that uses the same RasterizerData structure returned by the vertex function. The [[stage_in]] attribute qualifier indicates that this parameter comes from the rasterizer. The function returns a four-component floating-point vector, which contains the final RGBA color value to be rendered to the drawable.
這個函數(shù)有單一的參數(shù), in
使用相同的RasterizerData
結(jié)構(gòu)體 返回 頂點函數(shù) .
'[[stage_in]]' 屬性標(biāo)識符 指示 參數(shù)來自于 光柵化著色器. 這個函數(shù) 返回 4個浮點型向量, 包含 RGBA 顏色值, 渲染到 可繪畫對象.
This sample demonstrates a very simple fragment function that returns the interpolated color value from the rasterizer, without further processing. Each fragment renders its interpolated color value to its corresponding pixel in the triangle.
該示例 展示 一個簡單的片段著色器函數(shù),返回 中斷的顏色值從光柵化著色器中, 木有進(jìn)行處理.
每一個片段渲染 他的中斷顏色值 和 三角形對應(yīng)的像素.
return in.color;
包含函數(shù)庫和創(chuàng)建管線
當(dāng)編譯這個例子時候, Xcode 編譯AAPLShaders.metal
文件 和 Objective-C
代碼.
然后 ,Xcode 不能連接 vertexShader
和fragmentShader
函數(shù) 在編譯時刻,
該APP 需要明確連接這些函數(shù)在運行時.
Metal Shading Language 代碼 編譯在兩個時刻:
Front-end compilation happens in Xcode at build time. .metal files are compiled from high-level source code into intermediate representation (IR) files.
前端編譯發(fā)生在Xcode 編譯時刻, .Metal文件編譯在高級源碼 到IR 文件.
Back-end compilation happens in a physical device at runtime. IR files are then compiled into low-level machine code.
后端發(fā)生在物理設(shè)備在運行時,IR 文件編譯在底層機(jī)器碼.
每一個GPU 家族 有不同的指令集. 結(jié)果, Metal Shading language 代碼 僅僅能完全編譯到原生的GPU 代碼 在運行時, 物理設(shè)備.
前端 編譯 通過存儲IR 在 default.metal
文件 被打包在案例 .app 包中
default.metallib
文件是Metal Shading language 函數(shù)庫 展示了 MTLLibrary
對象處理 在運行時 調(diào)用newDefaultLibrary
方法
在這個library ,指定函數(shù) 展示 MTLFunction
對象 能夠處理.
// Load all the shader files with a metal file extension in the project
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
// Load the vertex function from the library
id <MTLFunction> vertexFunction = [defaultLibrary newFunctionWithName:@"vertexShader"];
// Load the fragment function from the library
id <MTLFunction> fragmentFunction = [defaultLibrary newFunctionWithName:@"fragmentShader"];
MTLFunction
對象 使用 MTLRenderStatePipeline
對象 展示 圖像渲染管線 調(diào)用newRenderPipelineStateWithDescriptor:error:
MTLDevice 對象 開始 后端編譯處理 , 連接帶vertexShader 和 fragmentShader 函數(shù) 返回全編譯的管線.
MTLRenderStatePipeline
對象包含 額外的管線 設(shè)置 配置 MTLRenderPipelineDescriptor
,而且 頂點和片段函數(shù), 該例子也通用配置 pixelFormat
值 在 第一個入口 colorAttachments
數(shù)組. 該例子是僅僅渲染但一個目標(biāo), 視圖的可繪畫對象(color Attachments[0]
)
MTLRenderPipelineDescriptor *pipelineStateDescriptor = [[MTLRenderPipelineDescriptor alloc] init];
pipelineStateDescriptor.label = @"Simple Pipeline";
pipelineStateDescriptor.vertexFunction = vertexFunction;
pipelineStateDescriptor.fragmentFunction = fragmentFunction;
pipelineStateDescriptor.colorAttachments[0].pixelFormat = mtkView.colorPixelFormat;
_pipelineState = [_device newRenderPipelineStateWithDescriptor:pipelineStateDescriptor
error:&error];
發(fā)送頂點數(shù)據(jù)到頂點函數(shù)
在管線創(chuàng)建之后, 它能夠賦值 一個 渲染命令編碼器, 這個操作 所有子序列渲染命令 將要處理 通過指定的管線.
[renderEncoder setRenderPipelineState:_pipelineState];
This sample uses the setVertexBytes:length:atIndex: method to send vertex data to a vertex function. As mentioned earlier, the signature of the sample’s vertexShader function has two parameters, vertices and viewportSizePointer, that use the [[buffer(index)]] attribute qualifier. The value of the index parameter in the setVertexBytes:length:atIndex: method maps to the parameter with the same index value in the [[buffer(index)]] attribute qualifier. Thus, calling the setVertexBytes:length:atIndex: method sets specific vertex data for a specific vertex function parameter.
The AAPLVertexInputIndexVertices and AAPLVertexInputIndexViewportSize values are defined in the AAPLShaderTypes.h header shared between the AAPLRenderer.m and AAPLShaders.metal files. The sample uses these values for the index parameter of both the setVertexBytes:length:atIndex: method and the [[buffer(index)]] attribute qualifier corresponding to the same vertex function. Sharing these values across different files makes the sample more robust by reducing potential index mismatches due to hard-coded integers (which could send the wrong data to the wrong parameter).
This sample sends the following vertex data to a vertex function:
The triangleVertices pointer is sent to the vertices parameter, using the AAPLVertexInputIndexVertices index value
The _viewportSize pointer is sent to viewportSizePointer parameter, using the AAPLVertexInputIndexViewportSize index value
// Here we're sending a pointer to our 'triangleVertices' array (and indicating its size).
// The AAPLVertexInputIndexVertices enum value corresponds to the 'vertexArray' argument
// in our 'vertexShader' function because its buffer attribute qualifier also uses
// AAPLVertexInputIndexVertices for its index
[renderEncoder setVertexBytes:triangleVertices
length:sizeof(triangleVertices)
atIndex:AAPLVertexInputIndexVertices];
// Here we're sending a pointer to '_viewportSize' and also indicate its size so the whole
// think is passed into the shader. The AAPLVertexInputIndexViewportSize enum value
/// corresponds to the 'viewportSizePointer' argument in our 'vertexShader' function
// because its buffer attribute qualifier also uses AAPLVertexInputIndexViewportSize
// for its index
[renderEncoder setVertexBytes:&_viewportSize
length:sizeof(_viewportSize)
atIndex:AAPLVertexInputIndexViewportSize];
繪制三角形
After setting a pipeline and its associated vertex data, issuing a draw call executes the pipeline and draws the sample’s single triangle. The sample encodes a single drawing command into the render command encoder.
Triangles are geometric primitives in Metal that require three vertices to be drawn. Other primitives include lines that require two vertices, or points that require just one vertex. The drawPrimitives:vertexStart:vertexCount: method lets you specify exactly what type of primitive to draw and which vertices, derived from the previously set vertex data, to use. Setting 0 for the vertexStart parameter indicates that drawing should begin with the first vertex in the array of vertices. This means that the first value of the vertex function’s vertexID parameter, which uses the [[vertex_id]] attribute qualifier, will be 0. Setting 3 for the vertexCount parameter indicates that three vertices should be drawn, producing a single triangle. (That is, the vertex function is executed three times with values of 0, 1, and 2 for the vertexID parameter).
// Draw the 3 vertices of our triangle
[renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
vertexStart:0
vertexCount:3];
該調(diào)用是最后一次調(diào)用需要編碼渲染命令為單個三角形.
當(dāng)繪制完成以后, 這個渲染循環(huán)能夠結(jié)束編碼.提交到Command Buffer
, 展示可繪畫對象包含渲染的三角形.
接下來
在這個例子中,你將要學(xué)習(xí)如何使用Metal API 來渲染基本的幾何體.
在Basic Buffers 示例中, 你將要學(xué)習(xí)如何使用頂點緩沖區(qū)來改善你的渲染效率.