Everyday Stage3D (三) AGAL的基本概念

前面一篇如果大家跟着做也能够作出同样的效果,但是对于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来进行渲染。之后我们就可以得到一个正确的图像结果。