A闪的 BLOG 技术与人文
前面一篇如果大家跟着做也能够作出同样的效果,但是对于AGAL部分我们没有进行讲解,因为这部分内容连接着另外一个世界。第三篇我来带着大家把这个世界的大门打开。
相信大家开始接触AGAL的时候都会感觉很蛋疼,确实,作者开始接触的时候也感觉蛋疼。不得不说AGAL的资料网上并不多,adobe player开发者中心的资料也不是非常多。大家都觉得百度googleBing了变天还是那几篇文章,没有太多的资料。别纠结,笔者也找不到。但是当你发现了AGAL的奥秘之后,你就会觉得这东西其实非常简单。单单从语法上来讲会非常的容易。
AGAL的命令或者说opcode并不多,而且大部分opcode都是进行数学运算的,所以理解起来并不难,举个简单的例子:
mov vt0, va0
这个命令其实就是一个赋值语句,我们可以把它理解为
vt0 = va0;
至于vt0,va0是什么东西待会说。上面AGAL中的mov就是一个命令(opcode),这样的命令一个有32个,至于都是那些,都可以干什么下面有个表格,大家看完就可以理解其中的意思了。
名称 | opcode | 操作 | 说明 |
mov | 0x00 | 移动 | 将数据从 source1 移动到 destination,按组件 |
add | 0x01 | 相加 | destination = source1 + source2,按组件 |
sub | 0x02 | 相减 | destination = source1 - source2,按组件 |
mul | 0x03 | 相乘 | destination = source1 * source2,按组件 div 0x04 |
div | 0x04 | 除以 | destination = source1 / source2,按组件 |
rcp | 0x05 | 倒数 | destination = 1/source1,按组件 |
min | 0x06 | 最小值 | destination = minimum(source1,source2),按组件 |
max | 0x07 | 最大值 | destination = maximum(source1,source2) destination = maximum(source1,source2),按组件 |
frc | 0x08 | 分数 | destination = source1 - (float)floor(source1) destination = source1 - (float)floor(source1),按组件 |
sqt | 0x09 | 平方根 | destination = sqrt(source1),按组件 |
rsq | 0x0a | 平方根倒数 | destination = 1/sqrt(source1),按组件 |
pow | 0x0b | 幂 | destination = pow(source1,source2),按组件 |
log | 0x0c | 对数 | destination = log_2(source1),按组件 |
exp | 0x0d | 指数 | destination = 2^source1,按组件 |
nrm | 0x0e | 标准化 | destination = normalize(source1),按组件(仅生成一个 3 组件结果, 目标必须遮罩为 .xyz 或更小) |
sin | 0x0f | 正弦 | destination = sin(source1),按组件 |
cos | 0x10 | 余弦 | destination = cos(source1),按组件 |
crs | 0x11 | 叉积 | destination.x = source1.y * source2.z - source1.z * source2.y destination.y = source1.z * source2.x - source1.x * source2.z destination.z = source1.x * source2.y - source1.y * source2.x (仅生成一个 3 组件结果,目标必须遮罩为 .xyz 或更小) |
dp3 | 0x12 | 点积 | destination = source1.x*source2.x + source1.y*source2.y + source1.z*source2.z |
dp4 | 0x13 | 点积 | destination = source1.x*source2.x + source1.y*source2.y + source1.z*source2.z + source1.w*source2.w |
abs |
0x14 | 取绝对值 | destination = abs(source1),按组件 |
neg | 0x15 | 求反 | destination = -source1,按组件 |
sat | 0x16 | 饱和 | destination = maximum(minimum(source1,1),0),按组件 |
m33 | 0x17 | 矩阵连乘3x3 | destination.x = (source1.x * source2[0].x) + (source1.y * source2[0].y) + (source1.z * source2[0].z) destination.y = (source1.x * source2[1].x) + (source1.y * source2[1].y) + (source1.z * source2[1].z) destination.z = (source1.x * source2[2].x) + (source1.y * source2[2].y) + (source1.z * source2[2].z) (仅生成一个 3 组件结果,目标必须遮罩为 .xyz 或更小) |
m44 | 0x18 | 矩阵连乘4x4 | destination.x = (source1.x * source2[0].x) + (source1.y * source2[0].y) + (source1.z * source2[0].z) + (source1.w * source2[0].w) destination.y = (source1.x * source2[1].x) + (source1.y * source2[1].y) + (source1.z * source2[1].z) + (source1.w * source2[1].w) destination.z = (source1.x * source2[2].x) + (source1.y * source2[2].y) + (source1.z * source2[2].z) + (source1.w * source2[2].w) destination.w = (source1.x * source2[3].x) + (source1.y * source2[3].y) + (source1.z * source2[3].z) + (source1.w * source2[3].w) |
m34 | 0x19 | 矩阵连乘3x4 | destination.x = (source1.x * source2[0].x) + (source1.y * source2[0].y) + (source1.z * source2[0].z) + (source1.w * source2[0].w) destination.y = (source1.x * source2[1].x) + (source1.y * source2[1].y) + (source1.z * source2[1].z) + (source1.w * source2[1].w) destination.z = (source1.x * source2[2].x) + (source1.y * source2[2].y) + (source1.z * source2[2].z) + (source1.w * source2[2].w) (仅生成一个 3 组件结果,目标必须遮罩为 .xyz 或更小) |
kil | 0x27 | 丢弃 | 如果单个标量源组件小于零,则将丢弃片段并不会将其绘制到帧缓冲区。 (目标寄存器必须全部设置为 0) |
tex | 0x28 | 纹理取样 | destination 等于从坐标 source1 上的纹理 source2 进行加载。在这种情况 下, source2 必须采用取样器格式 |
sge | 0x29 | 大于等于时设置 | destination = source1 >= source2 ? 1 : 0,按组件 |
slt | 0x2a | 小于时设置 | destination = source1 < source2 ? 1 : 0,按组件 |
seq | 0x2c | 相等时设置 | destination = source1 == source2 ? 1 : 0,按组件 |
sne | 0x2d | 不相等时设置 | destination = source1 != source2 ? 1 : 0,按组件 |
opcode表
看完了上述的表格之后,我们对照着来说明一下如何使用这些opcode。首先你要知道AGAL的格式。很多教程将这部分定位为语法,笔者认为这是不太准确的,因为AGAL本书是一段二进制的shader,而我们目前能拿到的是AGAL的二进制格式,也就是说,你既然用后了数据格式,那么至于数据如何生成,以什么形式生成都无所谓了。甚至你可以通过自己编写一套引擎来对你自己的编码习惯进行翻译。不过大部分工作我们目前都不考虑,毕竟adobe已经为我们提供了一个工具类,这个类可以很方便直观的帮助我们编写AGAL代码。我们先从格式开始说起,关于二进制的其他数据内容我们先不做过多的讨论,因为在后面的章节中,我将带领大家编写一个逆向工程工具,用于将二进制的AGAL还原为可以直接读取的源代码。
[opcode][destination][source1][source2 or sampler]
上面这行内容是AGAL最核心的内容,也是你将直接使用的内容,其中opcode为上面表格中的32个命令,destination则是目标内容,source1,source2则数数据源。大家可以自行根据你需要的功能编写一个AGAL尝试一下。至于实际使用,后面会根据不同内容的算法来向大家实际展示AGAL的最后编写结果。
下面我要说明的则是destination和source1,source2中的内容。这三部分的值到底从何二来,有如何使用呢?
首先我们要理解寄存器,这部分和我们的AS3程序息息相关,环环相扣。在我们的AGAL中
寄存器的访问也非常简单,你无需访问创建或者销毁之类的操作。而相关的寄存器也非常的简单,让我们以分类的形式来记忆。我们将寄存器按照功能分为两大类,对于顶点的操作我们有op,vc,vt,va,对于像素的操作我们有oc,ft,fs,fc。另外还有一个比较特殊的v。很多教程中对于每个寄存器都有一个非常让人琢磨不透的名字,这些名字看上去晦涩难懂,但当你了解他的功能之后你会发现它的名字非常的直观。让我们来一一看一下。
va 属性寄存器
很多人看到这个名字的时候都会认为va中应该存的是某一个对象的属性,或者一些其他什么东西。但实事并非如此。当你上载一些顶点数据之后,我们通过AS3来制定这些顶点所存在的位置,那么你就可以通过va来直接使用。举个简单的例子,在上一篇文章中,我们使用了va0,而va0的数据又是从etVertexBufferAt来直接指定的,所以在你编写代码的时候,你的AGAL和AS3有着直接的联系。
vc 常量寄存器
顾名思义,与程序中的常量概念类似,这部分数据是从外部直接传递进来的,至于你如何使用完全取决于你自己。关于用法我们在后面会涉及到。
vt 临时寄存器
应该说vt的概念最接近于我们程序中的变量,因为他可以被你临时读取,并且允许你在AGAL运行时来动态改变其中的数值。
op 输出寄存器
好吧,当你吧所有的操作都完成后,那么你的结果应该有一个归宿,这个op就是你顶点数据最后的归宿。每次运行顶点数据后,你都应该看到op,否则你的程序运行后没有任何效果。
fs 纹理采样寄存器
教程到目前位置,我们还没有看到任何和贴图有关系的内容。不错,笔者打算将这部分内容放在后面讲解。因为还有不少好东西放在后面等着大家呢。这里简单解释一下,所谓纹理采样,则是调用你显存中的纹理数据。纹理我们可以理解为贴图,或者理解为你上载到GPU的那张图片。
fc 常量寄存器
又遇到常量了。这里和刚才的vc其实是一样的,因为顶点和纹理这两部分的程序不可交叉访问,所以贴图也有自己的常量寄存器。
ft 临时寄存器
和vt概念相同,但只用于纹理操作。
oc 输出寄存器
和op相同,但只能用于纹理操作。
v 插值寄存器
这是比较特殊的一个寄存器,因为刚才我们所说的8个寄存器类型只能在自己负责的领域操作,顶点的寄存器不可访问纹理的内容,同样,纹理操作中也无法访问顶点数据,那么如果他们的数据有交集如何呢?这里就体现了v的作用(当然,在微博中,v也是一种身份的象征)。v可以在在顶点操作中使用,也可以在纹理操作中使用。
上面说了这么多,相信大家对AGAL中的语法和opcode有了一个大致的了解。下面我们就来解释一下上一篇中的AGAL代码。
首先是顶点处理。
var vagalcode:String = “mov op, va0 ” + “mov v0, va1”;
这一步处理中,我们将va0直接赋值给op,也就是说va0本身就是我们的顶点数据。将顶点数据原封不懂的输出到op中以便得到结果。
第二句中我们将va1中的数据移动到v0中,这里va1的数据是我们当前顶点的RGB颜色值。移动到v0是为了让我们的纹理着色器访问并使用。
下面我们来解释一下纹理着色器。AGAL代码如下:
var fagalcode:String = “mov oc, v0”;
仅此一句,因为刚才在顶点着色器中我们已经把RGB数值传递给了v0,这里我们将RGB颜色值原封不动的输出到oc中,以便GPU来进行渲染。之后我们就可以得到一个正确的图像结果。