We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
在iOS 8里,苹果发布了一个新的接口叫做Metal,它是一个支持GPU加速3D绘图的API。
Metal和OpenGL ES相似,它也是一个底层API,负责和3D绘图硬件交互。它们之间的不同在于,Metal不是跨平台的。与之相反的,它设计的在苹果硬件上运行得极其高效,与OpenGL ES相比,它提供了更快的速度和更低的开销。
在这篇教程里,你将会获得亲身的经历,使用Metal和Swift来创建一个有基本脉络的应用:画一个简单的三角形。在这个过程中,你将会学习一些Metal里最重要的类,比如devices、command queues,等等。
这篇教程是设计为任何人可以阅读明白,无论你是否学习过3D绘图。但是,我们会过得很快。如果你之前有过3D编程或者是OpenGL编程的经历,你会发现它非常简单,因为里面的很多概念你已经很熟悉了。
这篇教程假设你已经熟悉Swift了。如果你还是个Swift新手,先学习这些教程吧,苹果Swift站点、一些Swift教程。
注意:Metal应用不能跑在iOS模拟器上,它们需要一个设备,设备上装载着苹果A7芯片或者更新的芯片。所以要学习这篇教程,你需要一台这样的设备(iPhone 5S,iPad Air,iPad mini2)来完成代码的测试
1)创建一个MTLDevice 使用Metal你要做的第一件事就是获取一个MTLDevice的引用。
你可以把一个MTLDevice想象成是你和CPU的直接连接。你将通过使用MTLDevice创建所有其他你需要的Metal对象(像是command queues,buffers,textures)。
为了完成这点,打开ViewController.swift 并添加下面的import语句到文件最上方: import Metal
这导入了Metal框架,所以你能够使用Metal的类(像这文件中的MTLDevice)。接着,在ViewController类中添加以下属性: var device: MTLDevice! = nil
你将要在viewDidLoad函数内初始化这个属性,而不是在一个init函数里,所以它不得不是一个optional。既然你知道你一定会在使用它前初始化它,你为了方便,把它标记为一个隐式不包裹的optional。最后,添加这一行到viewDidLoad函数的最后。 device = MTLCreateSystemDefaultDevice() 这个函数返回一个默认MTLDevice引用,你的代码将会用到它。
2)创建一个CAMetalLayer 在iOS里,你在屏幕上看见的所有东西,被一个CALayer所承载。存在不同特效的CALayer的子类,比如:渐变层(gradient layers)、形状层(shape layers)、重复层(replicator layers) 等等。
好的,如果你想要用Metal在屏幕上画一些东西,你需要使用一个特别的CALayer子类,CAMetalLayer。所以在你的viewcontroller中添加一个。
首先在这个文件的上方添加import语句。 import QuartzCore 你需要它因为CAMetalLayer是QuartzCore框架的部分,而不是Metal框架里的。
然后把新属性添加到类中: var metalLayer: CAMetalLayer! = nil 这将会存储你新layer的引用。
最后,把这行代码添加到viewDidLoad方法最后。 metalLayer = CAMetalLayer() // 1 metalLayer.device = device // 2 metalLayer.pixelFormat = .BGRA8Unorm // 3 metalLayer.framebufferOnly = true // 4 metalLayer.frame = view.layer.frame // 5 view.layer.addSublayer(metalLayer) // 6 让我们一行行来看:
a.你创建了一个CAMetalLayer b.你必须明确layer使用的MTLDevice,你简单地设置你早前获取的device。 c.你把像素格式(pixel format)设置为BGRA8Unorm,它代表”8字节代表蓝色、绿色、红色和透明度,通过在0到1之间单位化的值来表示”。这次两种用在CAMetalLayer的像素格式之一,一般情况下你这样写就可以了。 d.苹果鼓励你设置framebufferOnly为true,来增强表现效率。除非你需要对从layer生成的纹理(textures)取样,或者你需要在layer绘图纹理(drawable textures)激活一些计算内核,否则你不需要设置。(大部分情况下你不用设置) e.你把layer的frame设置为view的frame。 f.你把layer作为view.layer下的子layer添加。
3)创建一个Vertex Buffer 在Metal里每一个东西都是三角形。在这个应用里,你只需要画一个三角形,不过即使是极其复杂的3D形状也能被解构为一系列的三角形。
在Metal里,默认的坐标系是向量坐标系,这意味着默认的时候,一个2x2x1的立方体,中心点是(0,0,0.5)。
如果你认为z=0是平面,那么(-1,-1,0)就是左下角,(0,0,0)就是中心,(1,1,0)是右上角。在这篇教程中,你想要在这些点上画三角形:
让我们创建一个缓冲区。在你的类中添加下列的常量属性: let vertexData:[Float] = [ 0.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0] 这在CPU创建一个浮点数数组——你需要通过把它移动到一个叫MTLBuffer的东西,来发送这些数据到GPU。
添加另一个新的属性: var vertexBuffer: MTLBuffer! = nil 然后在 viewDidLoad 方法的最后添加以下代码: let dataSize = vertexData.count * sizeofValue(vertexData[0]) // 1 vertexBuffer = device.newBufferWithBytes(vertexData, length: dataSize, options: nil) // 2 让我们一行行来看: a.你需要获取vertex data的字节大小。你通过把第一个元素的大小和数组元素个数相乘来得到。 b.你在MTLDevice上调用newBufferWithBytes(length:options:) ,在GPU创建一个新的buffer,从CPU里输送data。你传递nil来接受默认的选项。
4)创建一个Vertex Shader 你之前创建的顶点将成为你接下来写的一个叫vertext shader的小程序的输入。
一个vertex shader 是一个在GPU上运行的小程序,它由像c++的一门语言编写,那门语言叫做Metal Shading Language。
一个vertex shader被每个顶点调用,它的工作是接受顶点的信息(如:位置和颜色、纹理坐标),返回一个潜在的修正位置(可能还有别的相关信息)。
为了把事情保持简单,你的vertex shader将会返回一个和传递位置相同的位置。
最简单的了解 vertex shader 的方法是,自己体验。点击File\New\File,选择iOS\Source\Metal File,然后点击Next。输入Shader.metal作为文件名上按回车,然后点击Create。
注意:在Metal里,你能够在一个Metal文件里包含多个shaders。你也能把你的shader 分散在多个Metal文件中。Metal会从任意Metal文件中加载你项目包含的shaders。 在Shaders.metal底部添加下列代码: vertex float4 basic_vertex( // 1 const device packed_float3* vertex_array [[ buffer(0) ]], // 2 unsigned int vid [[ vertex_id ]]) { // 3 return float4(vertex_array[vid], 1.0); // 4 } 让我们一行行来看: a.所有的vertex shaders必须以关键字vertex开头。函数必须至少返回顶点的最终位置——你通过指定float4(一个元素为4个浮点数的向量)。然后你给一个名字给vetex shader,以后你将用这个名字来访问这个vertex shader。 b.第一个参数是一个指向一个元素为packed_float3(一个向量包含3个浮点数)的数组的指针,如:每个顶点的位置。这个 [[ ... ]] 语法被用在声明那些能被用作特定额外信息的属性,像是资源位置,shader输入,内建变量。这里你把这个参数用 [[ buffer(0) ]] 标记,来指明这个参数将会被在你代码中你发送到你的vertex shader的第一块buffer data所遍历。 c.vertex shader会接受一个名叫vertex_id的属性的特定参数,它意味着它会被vertex数组里特定的顶点所装入。 d.现在你基于vertex id来检索vertex数组中对应位置的vertex并把它返回。同时你把这个向量转换为一个float4类型,最后的value设置为1.0(简单的来说,这是3D数学要求的)。
5)创建一个Fragment Shader 完成我们的vertex shader后,另一个shader,它被每个在屏幕上的fragment(think pixel)调用,它就是fragment shader。
fragment shader通过内插(interpolating)vertex shader的输出还获得自己的输入。比如:思考在三角形两个底顶点之间的fragment:
fragment的输入值将会由50%的左下角顶点和50%的右下角顶点组成。
fragment shader的工作是给每个fragment返回最后的颜色。为了简便,你将会把每个fragment返回白色。
在Shader.metal的底部添加下列代码: fragment half4 basic_fragment() { // 1 return half4(1.0); // 2 } 让我们一行行来看: a. 所有fragment shaders必须以fragment关键字开始。这个函数必须至少返回fragment的最终颜色——你通过指定half4(一个颜色的RGBA值)来完成这个任务。注意,half4比float4在内存上更有效率,因为,你写入了更少的GPU内存。 b. 这里你返回(1,1,1,1)的颜色,也就是白色。
6)创建一个Render Pipeline 现在你已经创建了一个vertex shader和一个fragment shader,你需要组合它们(加上一些配置数据)到一个特殊的对象,它名叫render pipeline。Metal一个很酷的地方是,渲染器(shaders)是预编译的,render pipeline 配置会在你第一次设置它的时候被编译,所以所有事情都极其高效。
首先在ViewController.swift里添加一个属性: var pipelineState: MTLRenderPipelineState! = nil 这会对你即将要创建的render pipeline ,在它被编译后进行跟踪。
接着,在 viewDidLoad 方法最后添加如下代码: // 1 let defaultLibrary = device.newDefaultLibrary() let fragmentProgram = defaultLibrary.newFunctionWithName("basic_fragment") let vertexProgram = defaultLibrary.newFunctionWithName("basic_vertex")
// 2 let pipelineStateDescriptor = MTLRenderPipelineDescriptor() pipelineStateDescriptor.vertexFunction = vertexProgram pipelineStateDescriptor.fragmentFunction = fragmentProgram pipelineStateDescriptor.colorAttachments[0].pixelFormat = .BGRA8Unorm
// 3 var pipelineError : NSError? pipelineState = device.newRenderPipelineStateWithDescriptor(pipelineStateDescriptor, error: &pipelineError) if !pipelineState { println("Failed to create pipeline state, error (pipelineError)") } 让我们分部分看这些代码: a.你可以通过调用device.newDefaultLibrary方法获得的MTLibrary对象访问到你项目中的预编译shaders。然后你能够通过名字检索每个shader。 b.你在这里设置你的render pipeline。它包含你想要使用的shaders、颜色附件(color attachment)的像素格式(pixel format)。(例如:你渲染到的输入缓冲区,也就是CAMetalLayer)。 c.最后,你把这个pipeline 配置编译到一个pipeline 状态(state)中,让它使用起来有效率。
7)创建一个Command Queue 你需要做的最终的一次性设置步骤,是创建一个MTLCommandQueue。
把这个想象成是一个列表装载着你告诉GPU一次要执行的命令。
要创建一个command queue,简单地添加一个属性: var commandQueue: MTLCommandQueue! = nil
把下面这行添加到 viewDidLoad 的最后: commandQueue = device.newCommandQueue() 恭喜,你的预设置的代码完成了。
渲染三角形 现在,是时候学习每帧执行的代码,来渲染这个三角形!
它将在五个步骤中被完成: 1.创建一个Display link。 2.创建一个Render Pass Descriptor 3.创建一个Command Buffer 4.创建一个Render Command Encoder 5.提交你Command Buffer的内容。
让我们深入来看!
注意:理论上这个应用实际上不需要每帧渲染,因为三角形被绘制之后不会动。但是,大部分应用会有物体的移动,所以我们会那样做。同时也为将来的教程打下基础。
1)创建一个Display Link 你想要一个函数,在每次设备屏幕刷新的时候被调用,这样你就可以重绘屏幕。
在iOS平台上,你通过CADisplayLink 类来实现。
为了使用它,在类里添加一个新的属性: var timer: CADisplayLink! = nil 然后在 viewDidLoad 方法的末尾像这样初始化它: timer = CADisplayLink(target: self, selector: Selector("gameloop")) timer.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode) 这会设置你的代码,让它每次刷新屏幕的时候调用一个名叫gameloop的方法。 func render() { // TODO }
func gameloop() { autoreleasepool { self.render() } } 这里 gameloop 函数简单地调用 render 函数,这时 render 函数只有一个空实现。让我们来实现它!
2)创建一个Render Pass Descriptor 下一步是创建一个MTLRenderPassDescriptor,它能配置什么纹理会被渲染到、什么是clear color,以及其他的配置。
简单地在 render 函数里添加以下行: var drawable = metalLayer.nextDrawable()
let renderPassDescriptor = MTLRenderPassDescriptor() renderPassDescriptor.colorAttachments[0].texture = drawable.texture renderPassDescriptor.colorAttachments[0].loadAction = .Clear renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 104.0/255.0, blue: 5.0/255.0, alpha: 1.0) 首先你在之前的metal layer上调用nextDrawable() ,它会返回你需要绘制到屏幕上的纹理(texture)。接下来,你配置你的render pass descriptor 来使用它。你设置load action为clear,也就是说在绘制之前,把纹理清空。然后你把绘制的背景颜色设置为绿色。
3)创建一个Command Buffer 下一步是创建一个command buffer。你可以把它想象为一系列这一帧想要执行的渲染命令。酷的是在你提交command buffer之前,没有事情会真正发生,这样给你对事物在何时发生有一个很好的控制。创建一个command buffer很简单,只要在render函数末尾加上这行代码: let commandBuffer = commandQueue.commandBuffer() 一个command buffer包含一个或多个渲染指令(render commands)。让我们下面创建一个。
4)创建一个渲染命令编码器(Render Command Encoder) 为了创建一个渲染命令(render command),你使用一个名叫render command encoder的对象。在render函数的最后添加以下代码: let renderEncoder = commandBuffer.renderCommandEncoderWithDescriptor(renderPassDescriptor) renderEncoder.setRenderPipelineState(pipelineState) renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, atIndex: 0) renderEncoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1) renderEncoder.endEncoding() 这里你创建一个command encoder,并指定你之前创建的pipeline和顶点。最重要的部分是,调用drawPrimitives(vertexStart:vertexCount:instanceCount:)。
这里你你告诉GPU,让它基于vertex buffer画一系列的三角形。每个三角形由三个顶点组成,从vertex buffer 下标为0的顶点开始,总共有一个三角形。
当你完成后,你只要调用 endEncoding()。
5)提交你的Command Buffer 最后一步是提交command buffer。在render函数最后添加这些代码: commandBuffer.presentDrawable(drawable) commandBuffer.commit() 第一行需要保证新纹理会在绘制完成后立即出现。然后你把事务(transaction)提交,把任务交给GPU。过去我们敲了不少代码,不过现在终于结束了。编译并运行这个应用:
The text was updated successfully, but these errors were encountered:
No branches or pull requests
在iOS 8里,苹果发布了一个新的接口叫做Metal,它是一个支持GPU加速3D绘图的API。
Metal和OpenGL ES相似,它也是一个底层API,负责和3D绘图硬件交互。它们之间的不同在于,Metal不是跨平台的。与之相反的,它设计的在苹果硬件上运行得极其高效,与OpenGL ES相比,它提供了更快的速度和更低的开销。
在这篇教程里,你将会获得亲身的经历,使用Metal和Swift来创建一个有基本脉络的应用:画一个简单的三角形。在这个过程中,你将会学习一些Metal里最重要的类,比如devices、command queues,等等。
这篇教程是设计为任何人可以阅读明白,无论你是否学习过3D绘图。但是,我们会过得很快。如果你之前有过3D编程或者是OpenGL编程的经历,你会发现它非常简单,因为里面的很多概念你已经很熟悉了。
这篇教程假设你已经熟悉Swift了。如果你还是个Swift新手,先学习这些教程吧,苹果Swift站点、一些Swift教程。
注意:Metal应用不能跑在iOS模拟器上,它们需要一个设备,设备上装载着苹果A7芯片或者更新的芯片。所以要学习这篇教程,你需要一台这样的设备(iPhone 5S,iPad Air,iPad mini2)来完成代码的测试
1)创建一个MTLDevice
使用Metal你要做的第一件事就是获取一个MTLDevice的引用。
你可以把一个MTLDevice想象成是你和CPU的直接连接。你将通过使用MTLDevice创建所有其他你需要的Metal对象(像是command queues,buffers,textures)。
为了完成这点,打开ViewController.swift 并添加下面的import语句到文件最上方:
import Metal
这导入了Metal框架,所以你能够使用Metal的类(像这文件中的MTLDevice)。接着,在ViewController类中添加以下属性:
var device: MTLDevice! = nil
你将要在viewDidLoad函数内初始化这个属性,而不是在一个init函数里,所以它不得不是一个optional。既然你知道你一定会在使用它前初始化它,你为了方便,把它标记为一个隐式不包裹的optional。最后,添加这一行到viewDidLoad函数的最后。
device = MTLCreateSystemDefaultDevice()
这个函数返回一个默认MTLDevice引用,你的代码将会用到它。
2)创建一个CAMetalLayer
在iOS里,你在屏幕上看见的所有东西,被一个CALayer所承载。存在不同特效的CALayer的子类,比如:渐变层(gradient layers)、形状层(shape layers)、重复层(replicator layers) 等等。
好的,如果你想要用Metal在屏幕上画一些东西,你需要使用一个特别的CALayer子类,CAMetalLayer。所以在你的viewcontroller中添加一个。
首先在这个文件的上方添加import语句。
import QuartzCore
你需要它因为CAMetalLayer是QuartzCore框架的部分,而不是Metal框架里的。
然后把新属性添加到类中:
var metalLayer: CAMetalLayer! = nil
这将会存储你新layer的引用。
最后,把这行代码添加到viewDidLoad方法最后。
metalLayer = CAMetalLayer() // 1
metalLayer.device = device // 2
metalLayer.pixelFormat = .BGRA8Unorm // 3
metalLayer.framebufferOnly = true // 4
metalLayer.frame = view.layer.frame // 5
view.layer.addSublayer(metalLayer) // 6
让我们一行行来看:
a.你创建了一个CAMetalLayer
b.你必须明确layer使用的MTLDevice,你简单地设置你早前获取的device。
c.你把像素格式(pixel format)设置为BGRA8Unorm,它代表”8字节代表蓝色、绿色、红色和透明度,通过在0到1之间单位化的值来表示”。这次两种用在CAMetalLayer的像素格式之一,一般情况下你这样写就可以了。
d.苹果鼓励你设置framebufferOnly为true,来增强表现效率。除非你需要对从layer生成的纹理(textures)取样,或者你需要在layer绘图纹理(drawable textures)激活一些计算内核,否则你不需要设置。(大部分情况下你不用设置)
e.你把layer的frame设置为view的frame。
f.你把layer作为view.layer下的子layer添加。
3)创建一个Vertex Buffer
在Metal里每一个东西都是三角形。在这个应用里,你只需要画一个三角形,不过即使是极其复杂的3D形状也能被解构为一系列的三角形。
在Metal里,默认的坐标系是向量坐标系,这意味着默认的时候,一个2x2x1的立方体,中心点是(0,0,0.5)。
如果你认为z=0是平面,那么(-1,-1,0)就是左下角,(0,0,0)就是中心,(1,1,0)是右上角。在这篇教程中,你想要在这些点上画三角形:
让我们创建一个缓冲区。在你的类中添加下列的常量属性:
let vertexData:[Float] = [
0.0, 1.0, 0.0,
-1.0, -1.0, 0.0,
1.0, -1.0, 0.0]
这在CPU创建一个浮点数数组——你需要通过把它移动到一个叫MTLBuffer的东西,来发送这些数据到GPU。
添加另一个新的属性:
var vertexBuffer: MTLBuffer! = nil
然后在 viewDidLoad 方法的最后添加以下代码:
let dataSize = vertexData.count * sizeofValue(vertexData[0]) // 1
vertexBuffer = device.newBufferWithBytes(vertexData, length: dataSize, options: nil) // 2
让我们一行行来看:
a.你需要获取vertex data的字节大小。你通过把第一个元素的大小和数组元素个数相乘来得到。
b.你在MTLDevice上调用newBufferWithBytes(length:options:) ,在GPU创建一个新的buffer,从CPU里输送data。你传递nil来接受默认的选项。
4)创建一个Vertex Shader
你之前创建的顶点将成为你接下来写的一个叫vertext shader的小程序的输入。
一个vertex shader 是一个在GPU上运行的小程序,它由像c++的一门语言编写,那门语言叫做Metal Shading Language。
一个vertex shader被每个顶点调用,它的工作是接受顶点的信息(如:位置和颜色、纹理坐标),返回一个潜在的修正位置(可能还有别的相关信息)。
为了把事情保持简单,你的vertex shader将会返回一个和传递位置相同的位置。
最简单的了解 vertex shader 的方法是,自己体验。点击File\New\File,选择iOS\Source\Metal File,然后点击Next。输入Shader.metal作为文件名上按回车,然后点击Create。
注意:在Metal里,你能够在一个Metal文件里包含多个shaders。你也能把你的shader 分散在多个Metal文件中。Metal会从任意Metal文件中加载你项目包含的shaders。
在Shaders.metal底部添加下列代码:
vertex float4 basic_vertex( // 1
const device packed_float3* vertex_array [[ buffer(0) ]], // 2
unsigned int vid [[ vertex_id ]]) { // 3
return float4(vertex_array[vid], 1.0); // 4
}
让我们一行行来看:
a.所有的vertex shaders必须以关键字vertex开头。函数必须至少返回顶点的最终位置——你通过指定float4(一个元素为4个浮点数的向量)。然后你给一个名字给vetex shader,以后你将用这个名字来访问这个vertex shader。
b.第一个参数是一个指向一个元素为packed_float3(一个向量包含3个浮点数)的数组的指针,如:每个顶点的位置。这个 [[ ... ]] 语法被用在声明那些能被用作特定额外信息的属性,像是资源位置,shader输入,内建变量。这里你把这个参数用 [[ buffer(0) ]] 标记,来指明这个参数将会被在你代码中你发送到你的vertex shader的第一块buffer data所遍历。
c.vertex shader会接受一个名叫vertex_id的属性的特定参数,它意味着它会被vertex数组里特定的顶点所装入。
d.现在你基于vertex id来检索vertex数组中对应位置的vertex并把它返回。同时你把这个向量转换为一个float4类型,最后的value设置为1.0(简单的来说,这是3D数学要求的)。
5)创建一个Fragment Shader
完成我们的vertex shader后,另一个shader,它被每个在屏幕上的fragment(think pixel)调用,它就是fragment shader。
fragment shader通过内插(interpolating)vertex shader的输出还获得自己的输入。比如:思考在三角形两个底顶点之间的fragment:
fragment的输入值将会由50%的左下角顶点和50%的右下角顶点组成。
fragment shader的工作是给每个fragment返回最后的颜色。为了简便,你将会把每个fragment返回白色。
在Shader.metal的底部添加下列代码:
fragment half4 basic_fragment() { // 1
return half4(1.0); // 2
}
让我们一行行来看:
a. 所有fragment shaders必须以fragment关键字开始。这个函数必须至少返回fragment的最终颜色——你通过指定half4(一个颜色的RGBA值)来完成这个任务。注意,half4比float4在内存上更有效率,因为,你写入了更少的GPU内存。
b. 这里你返回(1,1,1,1)的颜色,也就是白色。
6)创建一个Render Pipeline
现在你已经创建了一个vertex shader和一个fragment shader,你需要组合它们(加上一些配置数据)到一个特殊的对象,它名叫render pipeline。Metal一个很酷的地方是,渲染器(shaders)是预编译的,render pipeline 配置会在你第一次设置它的时候被编译,所以所有事情都极其高效。
首先在ViewController.swift里添加一个属性:
var pipelineState: MTLRenderPipelineState! = nil
这会对你即将要创建的render pipeline ,在它被编译后进行跟踪。
接着,在 viewDidLoad 方法最后添加如下代码:
// 1
let defaultLibrary = device.newDefaultLibrary()
let fragmentProgram = defaultLibrary.newFunctionWithName("basic_fragment")
let vertexProgram = defaultLibrary.newFunctionWithName("basic_vertex")
// 2
let pipelineStateDescriptor = MTLRenderPipelineDescriptor()
pipelineStateDescriptor.vertexFunction = vertexProgram
pipelineStateDescriptor.fragmentFunction = fragmentProgram
pipelineStateDescriptor.colorAttachments[0].pixelFormat = .BGRA8Unorm
// 3
var pipelineError : NSError?
pipelineState = device.newRenderPipelineStateWithDescriptor(pipelineStateDescriptor, error: &pipelineError)
if !pipelineState {
println("Failed to create pipeline state, error (pipelineError)")
}
让我们分部分看这些代码:
a.你可以通过调用device.newDefaultLibrary方法获得的MTLibrary对象访问到你项目中的预编译shaders。然后你能够通过名字检索每个shader。
b.你在这里设置你的render pipeline。它包含你想要使用的shaders、颜色附件(color attachment)的像素格式(pixel format)。(例如:你渲染到的输入缓冲区,也就是CAMetalLayer)。
c.最后,你把这个pipeline 配置编译到一个pipeline 状态(state)中,让它使用起来有效率。
7)创建一个Command Queue
你需要做的最终的一次性设置步骤,是创建一个MTLCommandQueue。
把这个想象成是一个列表装载着你告诉GPU一次要执行的命令。
要创建一个command queue,简单地添加一个属性:
var commandQueue: MTLCommandQueue! = nil
把下面这行添加到 viewDidLoad 的最后:
commandQueue = device.newCommandQueue()
恭喜,你的预设置的代码完成了。
渲染三角形
现在,是时候学习每帧执行的代码,来渲染这个三角形!
它将在五个步骤中被完成:
1.创建一个Display link。
2.创建一个Render Pass Descriptor
3.创建一个Command Buffer
4.创建一个Render Command Encoder
5.提交你Command Buffer的内容。
让我们深入来看!
注意:理论上这个应用实际上不需要每帧渲染,因为三角形被绘制之后不会动。但是,大部分应用会有物体的移动,所以我们会那样做。同时也为将来的教程打下基础。
1)创建一个Display Link
你想要一个函数,在每次设备屏幕刷新的时候被调用,这样你就可以重绘屏幕。
在iOS平台上,你通过CADisplayLink 类来实现。
为了使用它,在类里添加一个新的属性:
var timer: CADisplayLink! = nil
然后在 viewDidLoad 方法的末尾像这样初始化它:
timer = CADisplayLink(target: self, selector: Selector("gameloop"))
timer.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSDefaultRunLoopMode)
这会设置你的代码,让它每次刷新屏幕的时候调用一个名叫gameloop的方法。
func render() {
// TODO
}
func gameloop() {
autoreleasepool {
self.render()
}
}
这里 gameloop 函数简单地调用 render 函数,这时 render 函数只有一个空实现。让我们来实现它!
2)创建一个Render Pass Descriptor
下一步是创建一个MTLRenderPassDescriptor,它能配置什么纹理会被渲染到、什么是clear color,以及其他的配置。
简单地在 render 函数里添加以下行:
var drawable = metalLayer.nextDrawable()
let renderPassDescriptor = MTLRenderPassDescriptor()
renderPassDescriptor.colorAttachments[0].texture = drawable.texture
renderPassDescriptor.colorAttachments[0].loadAction = .Clear
renderPassDescriptor.colorAttachments[0].clearColor = MTLClearColor(red: 0.0, green: 104.0/255.0, blue: 5.0/255.0, alpha: 1.0)
首先你在之前的metal layer上调用nextDrawable() ,它会返回你需要绘制到屏幕上的纹理(texture)。接下来,你配置你的render pass descriptor 来使用它。你设置load action为clear,也就是说在绘制之前,把纹理清空。然后你把绘制的背景颜色设置为绿色。
3)创建一个Command Buffer
下一步是创建一个command buffer。你可以把它想象为一系列这一帧想要执行的渲染命令。酷的是在你提交command buffer之前,没有事情会真正发生,这样给你对事物在何时发生有一个很好的控制。创建一个command buffer很简单,只要在render函数末尾加上这行代码:
let commandBuffer = commandQueue.commandBuffer()
一个command buffer包含一个或多个渲染指令(render commands)。让我们下面创建一个。
4)创建一个渲染命令编码器(Render Command Encoder)
为了创建一个渲染命令(render command),你使用一个名叫render command encoder的对象。在render函数的最后添加以下代码:
let renderEncoder = commandBuffer.renderCommandEncoderWithDescriptor(renderPassDescriptor)
renderEncoder.setRenderPipelineState(pipelineState)
renderEncoder.setVertexBuffer(vertexBuffer, offset: 0, atIndex: 0)
renderEncoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
renderEncoder.endEncoding()
这里你创建一个command encoder,并指定你之前创建的pipeline和顶点。最重要的部分是,调用drawPrimitives(vertexStart:vertexCount:instanceCount:)。
这里你你告诉GPU,让它基于vertex buffer画一系列的三角形。每个三角形由三个顶点组成,从vertex buffer 下标为0的顶点开始,总共有一个三角形。
当你完成后,你只要调用 endEncoding()。
5)提交你的Command Buffer
最后一步是提交command buffer。在render函数最后添加这些代码:
commandBuffer.presentDrawable(drawable)
commandBuffer.commit()
第一行需要保证新纹理会在绘制完成后立即出现。然后你把事务(transaction)提交,把任务交给GPU。过去我们敲了不少代码,不过现在终于结束了。编译并运行这个应用:
The text was updated successfully, but these errors were encountered: