本系列文章是对 http://metalkit.org 上面MetalKit内容的全面翻译和学习.
MetalKit系统文章目录
在本系列的第一部分中我们介绍了MetalKit框架.让我们回到part1
的项目中并继续.再看一遍render()函数,他应该看起来这样:
func render() {
let device = MTLCreateSystemDefaultDevice()!
self.device = device
let rpd = MTLRenderPassDescriptor()
let bleen = MTLClearColor(red: 0, green: 0.5, blue: 0.5, alpha: 1)
rpd.colorAttachments[0].texture = currentDrawable!.texture
rpd.colorAttachments[0].clearColor = bleen
rpd.colorAttachments[0].loadAction = .Clear
let commandQueue = device.newCommandQueue()
let commandBuffer = commandQueue.commandBuffer()
let encoder = commandBuffer.renderCommandEncoderWithDescriptor(rpd)
encoder.endEncoding()
commandBuffer.presentDrawable(currentDrawable!)
commandBuffer.commit()
}
让我们改进一下这段代码.首先,既然我们的类已经是MTKView的子类,它就已经有了自己的device
,所以没有必要再声明一个.这就可以把头两行变成一行:
device = MTLCreateSystemDefaultDevice()
第二步,上周我们说到我们应该确保currentDrawable和currentRenderPassDescriptor不为空否则应用会崩溃.为了简单点,我们在第1部分时没做,所以现在来添加一下.这将让我们能再减少几行代码.最终版看起来会像这样:
func render() {
device = MTLCreateSystemDefaultDevice()
if let rpd = currentRenderPassDescriptor, drawable = currentDrawable {
rpd.colorAttachments[0].clearColor = MTLClearColorMake(0, 0.5, 0.5, 1.0)
let command_buffer = device!.newCommandQueue().commandBuffer()
let command_encoder = command_buffer.renderCommandEncoderWithDescriptor(rpd)
command_encoder.endEncoding()
command_buffer.presentDrawable(drawable)
command_buffer.commit()
}
}
你可能会好奇colorAttachments[0]是什么.为了设置rendering pipeline state渲染管线状态
,Metal框架提供了3种类型的附件,来让我们写入:
- colorAttachments颜色附件
- depthAttachmentPixelFormat像素格式的深度附件
- stencilAttachmentPixelFormat像素格式的模板附件
我们目前只关心如何储存颜色数据,colorAttachments
是一个纹理数组,里面包含了绘制结果并将他们展示到屏幕上.我们目前只有一个这样的纹理-数组中的第一个元素(数组下标为0
). OK,现在是时候运行程序了,确保你仍然能看到像上次一样背景颜色.很棒!只用9行代码我们就创建了一个安全运行在GPU上的Metal
代码.
接下来,让我们深入研究一个新的Metal
话题-在屏幕上绘制几何体.所有的图形学教程比如和OpenGL
相关的都会以Hello,Triangle
类型程序开始,因为三角形是能绘制在屏幕上几何体中最简单的一个.它是2D图形学
基本元素,图形学中其他所有对象都是三角形组成的,所以它是个很好的入门切入点.想象屏幕坐标系统拥有自己的贯穿屏幕中心的坐标轴,中心点坐标为(0,0).相应的屏幕边缘应该为-1和1.让我们创建一组浮点数和一个缓冲器来保存三角形的顶点.在初始化device
后插入下面几行代码:
let vertex_data:[Float] = [-1.0, -1.0, 0.0, 1.0,
1.0, -1.0, 0.0, 1.0,
0.0, 1.0, 0.0, 1.0]
let data_size = vertex_data.count * sizeof(Float)
let vertex_buffer = device!.newBufferWithBytes(vertex_data, length: data_size, options: [])
上面的顶点数据是按顺序排列的:左下,右下,中上.我们注意到每个顶点使用4个浮点数来表示坐标.前两个是x和y轴.本次用不到的浮点数是:第三个深度(z轴)
和第四个w坐标
用来使坐标系齐次化.我们将在下一节讨论他们.然后我们计算这个数组的size大小为12个浮点数长度,最后我们用数组及其长度来创建一个缓冲器.现在我们已经储存好了我们的顶点,还需要找个办法将他们发送到GPU
以便能在屏幕上显示他们.让我们看看绘制到屏幕的整个处理过程(即管线
):
我们目前已经完成了第1阶段,储存顶点.下一步要求我们有一个新的构件称为shader着色器.一个shader
就是程序员能够用自定义函数来干预图形管线的地方.Metal
提供了几种类型的着色器,但今天我们只看其中两种:vertex shader顶点着色器负责点的位置,fragment shader片段着色器负责点的颜色.
Metal
框架提供了一个函数,我们可以在device
中调用,来创建一个函数(shader
)组成的Library库,如下:
let library = device!.newDefaultLibrary()!
let vertex_func = library.newFunctionWithName("vertex_func")
let frag_func = library.newFunctionWithName("fragment_func")
我们创建两个新的函数,将其指向对应的着色器(稍后会创建).下一步是创建一个Render Pipeline Descriptor渲染管线描述符来使用着色器:
let rpld = MTLRenderPipelineDescriptor()
rpld.vertexFunction = vertex_func
rpld.fragmentFunction = frag_func
rpld.colorAttachments[0].pixelFormat = .BGRA8Unorm
你可能会好奇.BGRA8Unorm是什么意思.它设置了像素格式,所以渲染管线中出来的所有东西的颜色组件都会是同一顺序(本例中按Blue
,Green
,Red
,Alpha
顺序),同时大小也会一致(本例中是8-bit
的颜色值,范围从0
到255
).最后一步是根据上面的descriptor描述符
创建一个Render Pipeline State渲染管线状态:
let rps = try! device!.newRenderPipelineStateWithDescriptor(rpld)
最后,我们只需要让命令编码器获取到我们的三角形就可以了,所以添加下面几行代码到创建encoder编码器
之后:
command_encoder.setRenderPipelineState(rps)
command_encoder.setVertexBuffer(vertex_buffer, offset: 0, atIndex: 0)
command_encoder.drawPrimitives(.Triangle, vertexStart: 0, vertexCount: 3, instanceCount: 1)
现在我们回到两个shaders
那里,我们在创建Library库
时说过需要创建的.为了创建shader
,我们需要创建一个Xcode
中的新文件.选择Metal File类型,命名为Shaders.metal或者其他类似名字,单击Create
.你将看到代码似乎不是Swift
的,因为Metal shading language着色语言
其实是基于C++
的.添加下面的代码:
#include
using namespace metal;
struct Vertex {
float4 position [[position]];
};
vertex Vertex vertex_func(constant Vertex *vertices [[buffer(0)]], uint vid [[vertex_id]]) {
return vertices[vid];
}
fragment float4 fragment_func(Vertex vert [[stage_in]]) {
return float4(0.7, 1, 1, 1);
}
代码相当直白.我们首先创建一个struct结构体
命名为Vertex,里面只有一个成员-一个包含位置数组的数组.我们注意到数组是float4类型,在着色语言中该类型和我们前面创建顶点时的4个浮点数是一样的.我们现在先不解释[[...]]语法的意思.然后我们看到vertex_func着色器返回当前顶点的location位置,fragment_func着色器返回当前顶点的color颜色.我们硬编码了一个特殊的颜色值,但是我们可以将color
结构体成员添加到Vertex
上,这样将每个顶点设置不同的颜色.
如果你运行应用,将会看到像这样的三角形:
下一部分我们将学习Metal shading language
也就是3D图形
是怎样渲染到GPUs
的.源代码source code 已发布在Github上.
下次见!