相比于上一篇helloWorld,这一篇内容增加了顶点数据和Metal的内容。
绘制流程:
绘制流程
具体代码实现
1,Metal文件
#import "HrShaderType.h"
typedef struct {
//处理空间的顶点信息
//position是关键字,类似于GLSL中的gl_Position
float4 clipSpacePosition [[position]];
//颜色
float4 color;
} RasterizerData;
vertex函数
vertex RasterizerData vertexShader(uint vertexId [[vertex_id]],
constant HRVertex *vertexs [[buffer(VertexInputIndexVertices)]],
constant vector_float2 *viewportSize [[buffer(VertexInputIndexViewPortSize)]])
{
RasterizerData out;
out.clipSpacePosition = vertexs[vertexId].position;
//把我们输入的颜色直接赋值给输出颜色.通过这种方式将颜色数据桥接到片元着色器
out.color = vertexs[vertexId].color;
return out;
}
- vertex:函数限定符,限定该函数为顶点函数
- RasterizerData:函数返回值,会将该参数经过光栅化后传递到片元函数
- vertexShader:函数自定义名称
- uint vertexId [[vertex_id]]:
- uint变量类型:无符号32位整型;
- vertexId变量名;
- [[vertex_id]]属性修饰符:代表顶点编号固定写法,开发者不得修改
- constant HRVertex *vertexs [[buffer(VertexInputIndexVertices)]]:
- constant变量限定符:存储在GPU的常量缓存区中;
- HRVertex:变量类型; vertexs:变量名;
fragment函数
fragment float4 fragmentShader(RasterizerData in [[stage_in]]) {
//返回该像素点的色值
return in.color;
}
- fragment函数限定符:片元函数
- float4:返回值,代表颜色RGBA
- fragmentShader:函数名
- RasterizerData in [[stage_in]]:
- RasterizerData变量类型;
- in变量名;
- [[stage_in]]属性修饰符:片元着色函数使用的单个片元输入数据是由顶点着色函数输出.然后经过光栅化生成的.
2,桥接文件
由于需要在Swift文件中使用OC头文件,需要通过桥接文件XXX-Bridging-Header
来导入.h文件
。
//定义了基本的向量、矩阵、四元数,该头文件同时存在于Metal Shader / swift | Objc中,方便相互传递数据
#include <simd/simd.h>
//该文件作用:通过文件引入的方式,将一些自定义的类型声明既传递到swift文件,同时也传递到metal文件中
typedef struct {
vector_float4 position;
vector_float4 color;
} HRVertex;
typedef enum {
//顶点数据
VertexInputIndexVertices = 0,
//视图大小
VertexInputIndexViewPortSize = 1,
}VertexInputIndex;
3,自定义Render渲染类
后续用到的相关全局参数
private var _device : MTLDevice?
private var commandQueue : MTLCommandQueue?
private var pielineState : MTLRenderPipelineState!
private var viewPortSize : vector_uint2 = vector_uint2(x: 0, y: 0)
初始化
init(view: MTKView) {
super.init()
_device = view.device
//1. 通过device创建commandQueue
commandQueue = _device?.makeCommandQueue()
//2. 加载metal文件
//2.1 makeDefaultLibrary:加载项目中所有.metal文件,当然也可以使用其他API来指定metal文件
let library = _device?.makeDefaultLibrary()
//2.2 从库中加载顶点函数、片元函数
let vertexShader = library?.makeFunction(name: "vertexShader")
let fragShader = library?.makeFunction(name: "fragmentShader")
//3. 创建渲染管道描述符
//3.1
let pielineDes = MTLRenderPipelineDescriptor()
//3.2 管道名称:可用于调试
pielineDes.label = "MyMTLRenderPipelineDescriptor"
//3.2 可编程函数,用于处理渲染过程中每个顶点、片元
pielineDes.vertexFunction = vertexShader
pielineDes.fragmentFunction = fragShader
//3.3 确定渲染管线中颜色附着点0的颜色组件;使用当前view颜色组件
pielineDes.colorAttachments[0].pixelFormat = view.colorPixelFormat
//4 创建渲染管线状态
do {
try pielineState = _device?.makeRenderPipelineState(descriptor: pielineDes)
} catch {
//如果我们没有正确设置管道描述符,则管道状态创建可能失败
print("pielineState failed \(error)")
}
}
Delegate-drawSize
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
viewPortSize.x = uint(size.width)
viewPortSize.y = uint(size.height)
}
Delegate-draw
func draw(in view: MTKView) {
//设置view的clearColor
view.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 1.0)
//5. 为每一次渲染创建一个新的命令缓冲区
let commandBuffer = commandQueue?.makeCommandBuffer()
commandBuffer?.label = "MyCommandBuffer"
//6. 用于保存渲染过程中的一组结果,渲染命令编码器描述符
if let des = view.currentRenderPassDescriptor {
//7. 创建渲染命令编码器,通过它来进行渲染的配置
let encoder = commandBuffer?.makeRenderCommandEncoder(descriptor: des)
encoder?.label = "MyCommandEncoder"
//8. 设置视口
encoder?.setViewport(MTLViewport(originX: 0, originY: 0,
width: Double(viewPortSize.x),
height: Double(viewPortSize.y),
znear: -1.0, zfar: 1.0))
//9. 设置当前渲染管道状态对象
encoder?.setRenderPipelineState(pielineState)
//10. 载入顶点数据
//通过VertexInputIndexVertices将数据传递到顶点函数的对应buffer中
encoder?.setVertexBytes(triangleVertices,
length: triangleVertices.count * MemoryLayout<HRVertex>.size,
index: Int(VertexInputIndexVertices.rawValue))
encoder?.setVertexBytes(&viewPortSize,
length: MemoryLayout<vector_uint2>.size,
index: Int(VertexInputIndexViewPortSize.rawValue))
//11. 绘制动作
/*
type: 设置图元链接方式
case point = 0
case line = 1
case lineStrip = 2 //线环
case triangle = 3 //三角形
case triangleStrip = 4 //三角形扇
*/
encoder?.drawPrimitives(type: .triangle,
vertexStart: 0,
vertexCount: triangleVertices.count)
//12. 结束编码
encoder?.endEncoding()
//13. 锁定缓存区, 等待缓冲区处理完成后绘制
if let currentDrawable = view.currentDrawable{
commandBuffer?.present(currentDrawable)
}
}
//14. 将命令缓存区提交给GPU
commandBuffer?.commit()
}
Buffer方式导入顶点数据
上方代码使用的是直接导入
的方式将顶点数据导入顶点函数
。当然还有其他方式,比如使用Buffer
的方式来导入。
init(view: MTKView) {
...
// 将数据放入buffer中,但是buffer是有大小上限:4KB
vertexBuffer = _device?.makeBuffer(bytes: triangleVertices,
length: triangleVertices.count * MemoryLayout<HRVertex>.size,
options: .storageModeShared)
}
func draw(in view: MTKView) {
// 通过buffer的方式载入顶点数据
encoder?.setVertexBuffer(vertexBuffer,
offset: 0,
index: Int(VertexInputIndexVertices.rawValue))
}
- 由于buffer最大可存储4KB的数据,所以不适合大量数据的导入
4,渲染类的使用
override func viewDidLoad() {
super.viewDidLoad()
//1.获取MTKView
if let vv = self.view as? MTKView{
_view = vv
//2.创建设备(GPU)
_view?.device = MTLCreateSystemDefaultDevice()
//3.render工具类创建
_render = HrRender(view: _view)
//4.mtkview的代理设置
_view?.delegate = _render
//5.初始化视图大小
//drawableSize当前view的可视区域
_render?.mtkView(vv, drawableSizeWillChange: _view.drawableSize)
}
}
网友评论