本系列文章是对 metalkit.org 上面MetalKit内容的全面翻译和学习.c++
今天咱们关注Metal function
中没用过的类型,kernel function内核函数或compute shader计算着色器.你将常常听到它们两个的混合词变形词.内核是用于计算
任务,也就是在GPU
上进行大规模并行计算.例如:图像处理,科学模拟,等等.关于内核有一些重要特色:没有渲染管线,函数老是返回void
,而且名字老是以kernel关键字开头,就像咱们之前用过的前面带有vertex
和vertex
关键字的函数同样.github
让咱们从第8部分Part 8的playground处继续.首先,删除MathUtils.swift
由于咱们已经不须要了.而后,在MetalView.swift
中删除createBuffers()
函数及其在初始化中的调用,还有两个缓冲器.将MTLRenderPipelineState
声明替换为MTLComputePipelineState声明.下一步,来到registerShaders()
函数.下面是新旧两个版本的不一样:swift
注意,咱们不在使用descriptor
了,而是在内核函数中直接建立MTLComputePipelineState
.下一步,咱们看看drawRect()
函数的不一样:ide
注意,currentRenderPassDescriptor
也不用了.命令编码器则用computeCommandEncoder()
函数来建立.显然,咱们也再也不须要设置顶点缓冲器和绘制基本体了.做为替代,使用用一个设置了纹理的内核函数,建立线程组并指派它们干活.咱们用MTLSize
来设置线程组的维数,及每次计算调用中要执行的线程组的数量.函数
最后,咱们到Shaders.metal
文件中,用下面代码替换全部内容:post
#include <metal_stdlib>
using namespace metal;
kernel void compute(texture2d<float, access::write> output [[texture(0)]],
uint2 gid [[thread_position_in_grid]])
{
output.write(float4(0, 0.5, 0.5, 1), gid);
}
复制代码
咱们只简单地给纹理中的每一个像素/位置设置了相同的颜色.如今若是你到playground的主页面,并显示Assistant editor
中的Timeline
,你应该能看到相似的视图:学习
若是你看到了上面的输出,就说明准备好继续下去了.从当前开始,咱们将再也不关注主代码(MetalView.swift
)了,由于咱们全部的工做都将在内核着色器中完成.ui
好了,让咱们先从简单的开始.用下面的代码替换内核函数中的代码:编码
int width = output.get_width();
int height = output.get_height();
float red = float(gid.x) / float(width);
float green = float(gid.y) / float(height);
output.write(float4(red, green, 0, 1), gid);
复制代码
你可能已经猜到了,咱们拿到纹理的width
和height
,而后根据像素在纹理中的位置来计算red
和green
的值,而后将新颜色写入回纹理中.你将看到相似这样的东西:
接着,让咱们在屏幕中间画一个黑色的圆.用下面几行代码替换最后一行:
float2 uv = float2(gid) / float2(width, height);
uv = uv * 2.0 - 1.0;
bool inside = length(uv) < 0.5;
output.write(inside ? float4(0) : float4(red, green, 0, 1), gid);
复制代码
你会看到相似这样的东西:
咱们究竟是怎么作到的呢?其实,这是在着色中很经常使用的技术,叫作distance function.咱们使用length
函数来肯定像素是否在屏幕中心也就是咱们圆的中心的0.5倍以内.注意,咱们归一化了uv向量来匹配窗口坐标范围 [-1,1].最后,咱们判断像素若是在内部就是黑色,不然就像原来同样,给它一个渐变色.
让咱们抽出这个圆内部/外部计算到一个距离函数中:
float dist(float2 point, float2 center, float radius) {
return length(point - center) - radius;
}
复制代码
而后,用下面几行替换咱们定义内部
的那行:
float distToCircle = dist(uv, float2(0), 0.5);
bool inside = distToCircle < 0;
复制代码
看不到任何改变,但咱们如今有了一个能够轻易重用的函数.下一步,让咱们看看如何根据到圆的距离改变背景颜色,而不是仅根据像素的绝对位置.咱们经过计算像素到圆的距离来改变透明通道的值.用下面这行替换最后一行:
output.write(inside ? float4(0) : float4(1, 0.7, 0, 1) * (1 - distToCircle), gid);
复制代码
你应该看到相似这样的东西:
很漂亮,对吧?如今咱们让它变成了日食,让咱们将它变得更真实一些.咱们须要另外一个圆(太阳),而且咱们想要让初始的圆向左一点,向下一点,这样它们就都能看到了.用下面几行替换咱们定义内部
的那行:
float distToCircle2 = dist(uv, float2(-0.1, 0.1), 0.5);
bool inside = distToCircle2 < 0;
复制代码
你会看到相似下面的东西:
咱们如今只是学会了着色技术的皮毛.在下一章节咱们将学习更复杂和动态的计算任务.特别感谢Chris Wood的建议.
源代码source code 已发布在Github上.
下次见!