上一节交代了如何使用gpu绘制一个三角形,这一届会利用gpu来绘制一个简单的动画#
首先绘制一个矩形
在上一节的基础上,如果需要绘制一个矩形,只需要在原来的verices里面多添加几个定点即可,如下图所示,第一个三角形的定点是,v0,v1,v2,第二个是v2,v3,v0。
var vetices : [Float] = [
-1,1,0,
-1,-1,0,
1,-1,0,
1,-1,0,
1,1,0,
-1,1,0
]
使用index来减少vertex数量
vertex以后只负责记录有效顶点的位置,index则是负责,三角形的绘制顺序。
var vetices : [Float] = [
-1,1,0,
-1,-1,0,
1,-1,0,
1,1,0
]
var indices:[UInt16] = [
0,1,2,
2,3,0
]
在初始化的过程中,需要初始化一个indices的buffer。
indexBuffer = device.makeBuffer(bytes: indices, length: indices.count * MemoryLayout<UInt16>.size, options: []) //定点存储的位置
最后,代替drawPrimitives,使用drawIndexedPrimitives,
commandEncoder?.drawIndexedPrimitives(type: .triangle, indexCount: indices.count, indexType: .uint16, indexBuffer: indexBuffer, indexBufferOffset: 0)
传入动画参数
- 创建一个数据结构,来存储动画信息,这里只有移动的偏移量。
struct Constants {
var animateBy:Float = 0.0
}
var constants = Constants()
- 在commandCoder内设置一个vertextByte,其中index为1
commandEncoder?.setVertexBytes(&constants, length: MemoryLayout<Constants>.stride, index: 1)
- 在shader.metal里面,添加另外一个接受端的数据结构。添加Vertex的参数,constant Constants &constants [[buffer(1)]],这里buffer参数的1,对应的setVertexBytes 的最后一个参数 index。
struct Constants{
float animate_by;
};
- 修改vertex_shader为以下代码,从传入的constants变量中拿到animate_by,计算后得到新的坐标点。然后下面的fragment的shader保持不变。
vertex float4 vertex_shader(const device packed_float3 *vertices [[buffer(0)]] ,
constant Constants &constants [[buffer(1)]],
uint vertexId [[vertex_id]] ){
float4 pos = float4(vertices[vertexId],1);
pos.x += constants.animate_by;
return pos;
}
- 此时,每次传入的constants都是保持不变的,画面并不会根据帧数的改变而改变。因此我们在draw的时候,添加计算逻辑。其中,time作为一个成员变量来保存一个进度信息。
time += 1 / Float(view.preferredFramesPerSecond) //60fps
constants.animateBy = abs(sin(time)/2 + 0.5) // sin函数来模拟一个淡入淡出的效果
整体代码
import UIKit
import MetalKit
enum Colors {
static let wenderlichGreen = MTLClearColor(red:0.0,green: 0.4,blue: 0.21,alpha: 1.0)
}
struct Constants {
var animateBy:Float = 0.0
}
class MyViewController:UIViewController,MTKViewDelegate{
var vetices : [Float] = [
-1,1,0,
-1,-1,0,
1,-1,0,
1,1,0
]
var indices:[UInt16] = [
0,1,2,
2,3,0
]
var constants = Constants()
var metalView:MTKView{
return view as! MTKView
}
var device:MTLDevice!
var commandQueue:MTLCommandQueue!
var vetextBuffer:MTLBuffer!
var indexBuffer:MTLBuffer!
var piplineState:MTLRenderPipelineState!
var time:Float = 0
override func viewDidLoad() {
super.viewDidLoad()
metalView.device = MTLCreateSystemDefaultDevice()// 创建设备
device = metalView.device //设置到controller的成员变量中
metalView.clearColor = Colors.wenderlichGreen //设置背景颜色
commandQueue = device.makeCommandQueue() //为gpu准备指令队列
vetextBuffer = device.makeBuffer(bytes: vetices, length: vetices.count * MemoryLayout<Float>.size, options: []) //定点存储的位置
indexBuffer = device.makeBuffer(bytes: indices, length: indices.count * MemoryLayout<UInt16>.size, options: []) //定点存储的位置
let library = device.makeDefaultLibrary()//框架会自动找到项目中的Metal文件
let vertex_shader = library?.makeFunction(name: "vertex_shader")//编译vertex_shader函数
let fragment_shader = library?.makeFunction(name: "fragment_shader")//编译fragment_shader函数
let piplineDescriptor = MTLRenderPipelineDescriptor();//生成piplinDescriptor
piplineDescriptor.vertexFunction = vertex_shader //设置vertex_shader函数
piplineDescriptor.fragmentFunction = fragment_shader//设置fragment_shader函数
piplineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm //设置颜色格式,这个应该是固定写法
do {
piplineState = try device.makeRenderPipelineState(descriptor: piplineDescriptor)//通过piplineDescript生成piplineState
} catch let error as NSError {
print("error \(error.localizedDescription)")
}
metalView.delegate = self
}
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
}
func draw(in view: MTKView) {
guard let pState = piplineState else {
return
}
time += 1 / Float(view.preferredFramesPerSecond) //60fps
constants.animateBy = abs(sin(time)/2 + 0.5)
let commandBuffer = commandQueue.makeCommandBuffer() //为指令队列设置缓冲区
let commandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: metalView.currentRenderPassDescriptor!) //为缓冲区创建一个编码器
commandEncoder?.setRenderPipelineState(pState);
commandEncoder?.setVertexBuffer(vetextBuffer, offset: 0, index: 0)
commandEncoder?.setVertexBytes(&constants, length: MemoryLayout<Constants>.stride, index: 1)
//commandEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vetices.count)
commandEncoder?.drawIndexedPrimitives(type: .triangle, indexCount: indices.count, indexType: .uint16, indexBuffer: indexBuffer, indexBufferOffset: 0)
commandEncoder?.endEncoding()//停止编码
commandBuffer?.present(metalView.currentDrawable as! MTLDrawable) //绘制图像
commandBuffer?.commit() //提交给gpu
}
}