渲染原理
本文是基于“Metal渲染繪制三角形”這樣頂點較少圖形基礎之上的延伸, 在渲染三角形的時候, 頂點數據的存儲使用的是數組,當頂點傳遞時通過setVertexBytes(_:length:index:)方法,主要是由于繪制三角形時,所需的頂點只有三個,頂點數據很少,所以可以通過數組存儲,此時的數據是存儲在CPU中的; Metal三角形的渲染繪制請參考:Metal之渲染繪制三角形 對于小于4KB(即4096字節)的一次性數據,使用setVertexBytes(:length:index:),如果數據長度超過4KB 或者需要多次使用頂點數據時,需要創建一個MTLBuffer對象,創建的buffer的目的就是為了將頂點數據存儲到頂點緩存區,GPU可以直接訪問該緩存區獲取頂點數據,并且buffer緩存的數據需要通過setVertexBuffer(:offset:index:)方法傳遞到頂點著色器。 當圖形的頂點數據較多時, 頂點的傳遞與存儲過程如下: ① Metal -> MTLBuffer -> 緩存區(存儲非常多自定義數據,GPU直接訪問 -> 顯存) -> 存儲頂點數據; ② 創建的buffer的目的就是為了將頂點數據存儲到頂點緩存區,GPU可以直接訪問該緩存區獲取頂點數據,并且buffer緩存的數據需要通過 setVertexBuffer(_:offset:index:)方法傳遞到頂點著色器。
渲染流程
一、Metal文件
metal文件中,在頂點著色函數需要對頂點坐標進行歸一化處理,因為頂點數據初始化時使用的是物體坐標。頂點坐標的歸一化主要有以下步驟:
定義頂點著色器輸出 初始化輸出剪輯空間位置 獲取當前頂點坐標的xy:主要是因為繪制的圖形是2D的,其z都為0 將傳入的視圖大小轉換為vector_float2二維向量類型 頂點坐標歸一化:可以通過一行代碼同時分隔兩個通道x和y,并執行除法,然后將結果放入輸出的x和y通道中,即從像素空間位置轉換為裁剪空間位置
#include <metal_stdlib>
using namespace metal
;
#import "YDWShaderTypes.h"
typedef struct { float4 clipSpacePosition
[ [ position
] ] ; float4 color
; } RasterizerData
;
vertex RasterizerData
vertexShader ( uint vertexID
[ [ vertex_id
] ] , constant CCVertex
* vertices
[ [ buffer ( CCVertexInputIndexVertices
) ] ] , constant vector_uint2
* viewportSizePointer
[ [ buffer ( CCVertexInputIndexViewportSize
) ] ] ) { RasterizerData out
; out
. clipSpacePosition
= vector_float4 ( 0.0 , 0.0 , 0.0 , 1.0 ) ; float2 pixelSpacePosition
= vertices
[ vertexID
] . position
. xy
; vector_float2 viewportSize
= vector_float2 ( * viewportSizePointer
) ; out
. clipSpacePosition
. xy
= pixelSpacePosition
/ ( viewportSize
/ 2.0 ) ; out
. color
= vertices
[ vertexID
] . color
; return out
;
}
fragment float4
fragmentShader ( RasterizerData
in [ [ stage_in
] ] ) { return in . color
;
}
二、 initWithMetalKitView
主要需要加載metal文件來獲取頂點數據
獲取GPU設備device: 通過視圖控制器中初始化render對象時傳入的MTKView對象view,利用view來獲取GPU的使用權限
_device
= mtkView
. device
;
mtkView
. colorPixelFormat
= MTLPixelFormatBGRA8Unorm_sRGB
;
id
< MTLLibrary
> defaultLibrary
= [ _device newDefaultLibrary
] ; id
< MTLFunction
> vertexFunction
= [ defaultLibrary newFunctionWithName
: @"vertexShader" ] ; id
< MTLFunction
> fragmentFunction
= [ defaultLibrary newFunctionWithName
: @"fragmentShader" ] ;
MTLRenderPipelineDescriptor
* pipelineStateDescriptor
= [ [ MTLRenderPipelineDescriptor alloc
] init
] ; pipelineStateDescriptor
. label
= @"Simple Pipeline" ; pipelineStateDescriptor
. vertexFunction
= vertexFunction
; pipelineStateDescriptor
. fragmentFunction
= fragmentFunction
; pipelineStateDescriptor
. colorAttachments
[ 0 ] . pixelFormat
= mtkView
. colorPixelFormat
;
NSError
* error
= NULL ; _pipelineState
= [ _device newRenderPipelineStateWithDescriptor
: pipelineStateDescriptorerror
: & error
] ;
NSData
* vertexData
= [ YDWRenderer generateVertexData
] ; _vertexBuffer
= [ _device newBufferWithLength
: vertexData
. lengthoptions
: MTLResourceStorageModeShared
] ; memcpy ( _vertexBuffer
. contents
, vertexData
. bytes
, vertexData
. length
) ; _numVertices
= vertexData
. length
/ sizeof ( CCVertex
) ;
+ ( nonnull NSData
* ) generateVertexData
{ const CCVertex quadVertices
[ ] = { { { - 20 , 20 } , { 1 , 0 , 0 , 1 } } , { { 20 , 20 } , { 1 , 0 , 0 , 1 } } , { { - 20 , - 20 } , { 1 , 0 , 0 , 1 } } , { { 20 , - 20 } , { 0 , 0 , 1 , 1 } } , { { - 20 , - 20 } , { 0 , 0 , 1 , 1 } } , { { 20 , 20 } , { 0 , 0 , 1 , 1 } } , } ; const NSUInteger NUM_COLUMNS
= 25 ; const NSUInteger NUM_ROWS
= 15 ; const NSUInteger NUM_VERTICES_PER_QUAD
= sizeof ( quadVertices
) / sizeof ( CCVertex
) ; const float QUAD_SPACING
= 50.0 ; NSUInteger dataSize
= sizeof ( quadVertices
) * NUM_COLUMNS
* NUM_ROWS
; NSMutableData
* vertexData
= [ [ NSMutableData alloc
] initWithLength
: dataSize
] ; CCVertex
* currentQuad
= vertexData
. mutableBytes
; for ( NSUInteger row
= 0 ; row
< NUM_ROWS
; row
++ ) { for ( NSUInteger column
= 0 ; column
< NUM_COLUMNS
; column
++ ) { vector_float2 upperLeftPosition
; upperLeftPosition
. x
= ( ( - ( ( float ) NUM_COLUMNS
) / 2.0 ) + column
) * QUAD_SPACING
+ QUAD_SPACING
/ 2.0 ; upperLeftPosition
. y
= ( ( - ( ( float ) NUM_ROWS
) / 2.0 ) + row
) * QUAD_SPACING
+ QUAD_SPACING
/ 2.0 ; memcpy ( currentQuad
, & quadVertices
, sizeof ( quadVertices
) ) ; for ( NSUInteger vertexInQuad
= 0 ; vertexInQuad
< NUM_VERTICES_PER_QUAD
; vertexInQuad
++ ) { currentQuad
[ vertexInQuad
] . position
+ = upperLeftPosition
; } currentQuad
+ = 6 ; } } return vertexData
;
}
_commandQueue
= [ _device newCommandQueue
] ;
三、drawInMTKView
主要加載頂點緩沖區數據
id
< MTLCommandBuffer
> commandBuffer
= [ _commandQueue commandBuffer
] ; commandBuffer
. label
= @"MyCommand" ;
MTLRenderPassDescriptor
* renderPassDescriptor
= view
. currentRenderPassDescriptor
; if ( renderPassDescriptor
!= nil
) { id
< MTLRenderCommandEncoder
> renderEncoder
= [ commandBuffer renderCommandEncoderWithDescriptor
: renderPassDescriptor
] ; renderEncoder
. label
= @"MyRenderEncoder" ; }
[ renderEncoder setViewport
: ( MTLViewport
) { 0.0 , 0.0 , _viewportSize
. x
, _viewportSize
. y
, - 1.0 , 1.0 } ] ;
[ renderEncoder setRenderPipelineState
: _pipelineState
] ;
為了從OC代碼找發送數據預加載的MTLBuffer 到Metal 頂點著色函數中
[ renderEncoder setVertexBuffer
: _vertexBufferoffset
: 0 atIndex
: CCVertexInputIndexVertices
] ; [ renderEncoder setVertexBytes
: & _viewportSizelength
: sizeof ( _viewportSize
) atIndex
: CCVertexInputIndexViewportSize
] ;
[ renderEncoder drawPrimitives
: MTLPrimitiveTypeTrianglevertexStart
: 0 vertexCount
: _numVertices
] ;
結束編碼,表示已該編碼器生成的命令都已完成,并且從NTLCommandBuffer中分離
[ renderEncoder endEncoding
] ;
[ commandBuffer presentDrawable
: view
. currentDrawable
] ;
[ commandBuffer commit
] ;
效果展示
完整示例
Metal之MTLBuffer批量加載頂點數量較多的圖形渲染
總結
以上是生活随笔 為你收集整理的Metal之MTLBuffer批量加载顶点数量较多的图形渲染 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。