Metal入门资料011-着色技术入门
写在前面:
对Metal技术感兴趣的同学,可以关注我的专题:Metal专辑
也可以关注我个人的简书账号:张芳涛
所有的代码存储的Github地址是:Metal
正文
今天我们将看看我们之前没有使用的唯一其他类型的Metal function
,kernel function
(内核函数)或compute shader
(计算着色器)。 可能我们经常会听到来自两者的混合词的变化。 内核用于compute
(计算)任务,即在GPU
上完成的大规模并行计算。 一些例子包括:图像处理,科学模拟等。 关于内核要记住的几个重要事实:没有渲染管道,函数总是返回void
,它的名字总是以kernel
关键字开头,就像我们之前使用的其他函数前面有vertex
和fragment
关键字一样。
让我们首先拆除我们在第8部分中使用的playground。首先,删除MathUtils.swift
,因为我们不再需要它了。 然后,在MetalView.swift
中删除createBuffers()
函数,以及它在初始化程序内的调用,以及两个缓冲区。 将MTLRenderPipelineState
声明替换为MTLComputePipelineState声明。 接下来,关于registerShaders()
函数。 以下是旧版本和新版本之间的差异:
请注意,我们不再使用descriptor
(描述符),而是直接使用内核函数创建我们的MTLComputePipelineState
。 接下来,让我们看一下drawRect()
函数的差异:
请注意,不再使用currentRenderPassDescriptor
。 命令编码器是使用computeCommandEncoder()
函数创建的。 显然,我们不再需要设置顶点缓冲区或绘制基元。 相反,使用内核函数,我们设置一个可以使用的纹理,创建线程组,然后调度它们来完成工作。 我们使用MTLSize
来设置每个线程组的维度以及将在每个计算调用中执行的线程组的数量。
最后,我们转到Shaders.metal
文件并使用以下代码替换内部的所有内容:
#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);
}
我们基本上只为纹理中的每个像素/位置设置相同的颜色。 现在,如果您转到操场的主页,如果您在Assistant editor
中显示Timeline
(时间轴),则应该具有类似的视图:
如果你看到上面的输出,现在就可以继续了。 从现在开始,我们将不再查看主机代码(MetalView.swift
),因为我们所有的工作都将在kernel shader
(内核着色器)中。
用以下代码替换内核函数的内容:
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);
我们得到纹理的宽度和高度,然后根据纹理中的像素位置计算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]
的范围。 最后,我们看看像素是否在内部并将其着色为black
(黑色),或者使用渐变对其进行着色,就像我们之前所做的那样。
让我们将内/外圆计算抽象为有用的距离函数:
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;
在视觉上,没有任何改变,但我们现在有一个我们可以在以后轻松重用的功能。 接下来,让我们看看我们如何根据与圆的距离而不仅仅是绝对像素位置来修改背景颜色。 我们通过计算从像素到圆的距离来更改alpha通道的值。 只需用这一个替换最后一个:
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;
你应该看到这样的东西:
满意了不?代码地址:代码地址