什么是 AGAL

  • Marco Scabia

2011 年 11 月 10 日

话题:语言 & 开发架构

目录

需求

预备知识

需要基本了解 Stage3D API。最好还拥有使用着色器的知识并理解可编程函数管道的操作原理。在阅读这些指令之前,一定要首先阅读这个关于 Stage3D 的系列中的前两篇教程(1. Stage3D 的工作原理,2. 顶点和片段着色器)。

用户水平

中级

需要的产品

本文将介绍使用着色语言。我将介绍使用 Stage3D API 中所包含的低级着色语言 AGAL(Adobe Graphics Assembly Language)的基本知识。您将了解 AGAL 是什么、它的工作原理,以及如何在基于 Stage3D 的 ActionScript 应用程序中使用它。

理解着色语言

在专门深入介绍 AGAL 之前,最好首先理解什么是着色语言,以及如何使用它创建着色器。

着色器不是使用 ActionScript 编写的。它们也不是使用 C++ 或任何其他通用语言编写的。

着色器通常使用一种称为着色语言的特殊语言来编写。

着色器是在 GPU 上运行的程序,所以编写着色器的最有效方式是使用一种专为 GPU 而设计的语言。这正是您将使用 3 种特殊的皂色语言编写着色器,而砽专为 CPU 编码而设计的通用语言的原因。

有几种着色语言已使用了多年,用于两种标准的原生 3D 平台(OpenGL 和 DirectX):GLSL 和 HLSL 是两种最常用的着色语言。

对于 Stage3D API,Adobe 创建了两种新的着色语言来为 GPU 创建程序:AGAL 和 Pixel Bender 3D。

AGAL 和 Pixel Bender 3D 概述

AGAL(Adobe Graphics Assembly Language)是一种汇编语言。它是一种非常低级的语言,非常接近于 GPU 实际执行的指令。GPU(以及 CPU)无法直接理解 ActionScript 等高级语言,这些语言中包含变量、类等。GPU 只能理解基础的机器语言命令。管道中的某个位置有一种编译器,它将高级语言的复杂命令翻译为一系列更简单、更低级的机器语言命令。

使用 AGAL,您可以直接在较低级别上编写命令,类似于 GPU 所理解的命令。

Pixel Bender 3D 是一种更高级的语言,所以它比 AGAL 更容易使用。Pixel Bender 3D 是 Pixel Bender 的一种扩展,但它没有更新来支持 3D 和着色器。

关于如何选择 Pixel Bender 3D 和 AGAL,二者各有利弊。Pixel Bender 3D 肯定是更容易使用的语言,所以它编写复杂的着色器所需的时间要少得多。

另一方面,AGAL 更接近 GPU 的工作方式。因此,您会更好地理解您的呈现管道中到底发生了什么。您可以手动优化着色器,而不让编译器来为您这么做。所以,如果您花时间学习了它,可能能够使用 AGAL 创建进一步优化的着色器。

如果您的目标是学习 Stage3D 的工作原理,AGAL 也是一个不错的选择。AGAL 使您能够在更接近 GPU 的地方运行命令,所以更容易理解呈现管道中到底发生了什么。

一定要注意,在使用 Pixel Bender 3D 时,您会在编译时预编译您的着色器,当使用 AGAL 时,您的着色器显示为着色器程序字符串的形式,它们在运行时汇编为对象代码。所以,可以使用 AGAL 动态地创建着色器。

出于指导使用 Stage3D 的目的,我相信最佳的选择是首先学习 AGAL。

了解 AGAL 的语法

AGAL 是一种汇编语言。如果您熟悉 ActionScript 中的代码语法,一种类似 AGAL 的汇编语言乍看起来会比较陌生。

以下是 AGAL 顶点着色器的一个示例:

m44 op, va0, vc0 
mov v0, va1 

我将介绍上面这个示例的语法,以便您可以理解每行汇编代码的含义。

着色器的每一行是一个命令,由一个称为操作码的 3 字符字符串指定。

一行 AGAL 代码的语法如下:

<opcode> <destination>, <source 1>, <source 2 or sampler>

此语法非常重要。请记住此语法,AGAL 很快就会变成看起来难念的语言。

在操作码之后,依据命令,可能是一个目标,以及一个或两个来源。目标和来源称为寄存器:GPU 中供着色器使用的小型内存区域。本章后面将更详细地介绍寄存器。来源包含操作中使用的值,目标是存储结恶果的地方。

识别 AGAL 中主要的操作码

AGAL 包含大约 30 个不同的操作码。可用操作码的完整列表可在Program 3D 参考文档中找到。以下一些最常用的操作码。

  • mov:将数据从来源 1 移动到目标,按成分运算
  • add:目标 = 来源 1 + 来源 2,按成分运算
  • sub:目标 = 来源 1 – 来源 2,按成分运算
  • mul:目标 = 来源 1 * 来源 2,按成分运算
  • div:目标 = 来源 1 / 来源 2,按成分运算
  • dp3:来源 1 和来源 2 之间的点乘(3 个成分)
  • dp4:来源 1 和来源 2 之间的点乘(4 个成分)
  • m44:来源 1 中的 4 个成本矢量和来源 2 中的 4×4 矩阵之间的倍乘
  • tex:纹理采样。从坐标“来源 1”处的“来源 2”处的纹理加载。

图 1 和图 2 概述了完整的 AGAL 命令集。

图 1. 与操作码相关的 AGAL 内存、算法、三角法和代数。(+) 查看大图

图 2. 与操作码相关的 AGAL 矢量和矩阵、条件和纹理采样。(+) 查看大图

使用 AGAL 寄存器

AGAL 不使用变量来存储数据,像 ActionScript 和其他高级语言所做的一样。AGAL 仅使用寄存器。

寄存器是 GPU 中的小型内存区域,AGAL 程序(着色器)可在执行期间使用它们。寄存器用于存储 AGAL 命令的来源和目标。

您也可以通过这些寄存器将参数传递到您的着色器。

每个寄存器为 128 位宽,这意味着它包含 4 个浮点值。每个值称为寄存器的一个成分。

寄存器组件可通过坐标存取器(xyzw)和通过颜色存取器(rgba)存取。

寄存器的第一个组件可这样存取:

<register name>.x 

也可以这样存取:

<register name>.r 

有时寄存器包含类似坐标的数据,而其他时候它们包含颜色数据。通过使用正确类型的存取器,您可以是代码更简洁和容易阅读。

上面的一些操作码(比如 add)按组件执行它们的运算。这意味着加运算逐个组件地执行,所以 x 组件与 x 组件相加,y 组件与 y 组件相加,依此类推。

有 6 种类型的寄存器。

1. 属性寄存器

这些寄存器引用作为顶点着色器输入的 VertexBuffer 的顶点属性数据。因此,它们仅可用于顶点着色器。

这是顶点着色器负责处理的主要数据流。VertexBuffer 中的每个顶点属性拥有自己的属性寄存器。

要将一个 VertexBuffer 属性分配给特定的属性寄存器,可以使用函数 Context3D:setVertexBufferAt() 和合适的索引。

然后从着色器,使用语法 va<n> 访问属性寄存器,其中 <n> 是属性寄存器的索引编号。

总共有 8 个属性寄存器可用于顶点着色器。

2. 常量寄存器

这些寄存器用于处理从 ActionScript 传递到着色器的参数。这由 Context3D::setProgramConstants() 系列函数执行。

这些寄存器使用以下语法从着色器访问:对于顶点着色器,使用 vc<n>;对于像素着色器,使用 fc<n>,其中 <n> 是常量寄存器的索引编号。

有 128 个常量寄存器可用于顶点着色器,28 个常量寄存器可用于像素寄存器。

3. 临时寄存器

这些寄存器可用于着色器,它们用于临时计算。因为 AGAL 不使用变量,您将使用临时寄存器来存储整个代码中的数据。

临时寄存器使用语法 vt<n>(顶点着色器)和 ft<n>(像素着色器)来访问,其中 <n> 是寄存器编号。

有 8 个临时寄存器可用于顶点着色器,8 个可用于像素寄存器。

4. 输出寄存器

暑促寄存器由顶点和像素寄存器用于存储它们的计算的输出。对于顶点着色器,此输出是顶点的位置。对于像素着色器,它是像素的颜色。

这些寄存器可使用以下语法访问:op 用于顶点着色器,oc 用于像素着色器。

显然只有一个输出寄存器可用于顶点和像素寄存器。

5. 可变寄存器

这些寄存器用于将数据从顶点着色器传递到像素着色器。传递的数据由 GPU 恰当地插入,使像素着色器能够收到被处理的像素的正确值。

以这种方式传入的典型数据是顶点颜色或纹理的 UV 坐标。

这些寄存器可使用语法 v<n> 访问,其中 <n> 是寄存器编号。

有 8 个可变寄存器可用。

6. 纹理采样器寄存器

纹理采样器寄存器用于基于 UI 坐标,从纹理挑选颜色值。

要使用的纹理通过 ActionScript 调用 Context3D::setTextureAt() 来指定。

使用纹理采样器的语法为:fs<n> <flags>,其中 <n> 是采样器索引,<flags> 是一个或多个指定应该如何采样的标志。

<flags> 是一个逗号分隔的字符串集,定义:

  • 纹理维度。选项:2d、cube
  • mip 映射。选项:nomip(或 mipnone,它们是相同的)、mipnearest、miplinear
  • 纹理过滤。选项:nearest、linear
  • 纹理重复。选项:repeat、wrap、clamp

例如,一个没有 MIP 映射和现象过滤的标准 2D 纹理可以使用以下代码采样到临时寄存器 ft1 中:

tex ft1, v0, fs0 <2d,linear,nomip> 

在上面的示例中,可变寄存器 v0 持有插入的纹理 UV。

创建示例 AGAL 着色器

在本节中,您将看到一个着色器示例,更好地理解它的操作原理。

假设顶点缓冲区中的顶点包含顶点位置(位于偏移 0)和顶点颜色(位于偏移 3)。该代码类似于:

var vertices:Vector.<Number> = Vector.<Number>([ 
-0.3,-0.3,0, 1, 0, 0, // x, y, z, r, g, b 
-0.3, 0.3, 0, 0, 1, 0, 
0.3, 0.3, 0, 0, 0, 1]); 

这段代码的目标是使顶点着色器正确地转换顶点位置,将每个顶点颜色传递到像素着色器。

您可以通过以下代码实现此目的:

m44 op, va0, vc0 // pos to clipspace 
mov v0, va1 // copy color 

第一行在顶点输入属性寄存器 0(va0)和一个从 ActionScript 传入的变换矩阵之间执行一个 4×4 矩阵乘。当在透视图中呈现时,这通常是从模型空间到剪贴空间的变换矩阵,我们假设它从 ActionScript 传递到常量寄存器 0(vc0)。

注意:剪贴空间和透视投影将在本系列的下一个教程中更详细探讨,该教程名为使用 Stage3D 和透视投影

矩阵可通过以下调用传入到着色器中的寄存器 vc0 中:

Context3D::setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, matrix, true ); 

顶点着色器的第二行将顶点颜色数据复制到可变寄存器 0(v0),以便它可以由 GPU 插入,传递到像素寄存器。

像素着色器将可变寄存器 v0 颜色内容复制到它的输出寄存器 oc:

mov oc, v0 

所以,这个顶点 / 像素着色器对使用从 ActionScript 传入的一个变换矩阵转换 3D 模型顶点,并应用顶点颜色。

就这么简单!您的第一个顶点和像素着色器。

使用 Program3D 和 AGAL Mini Assembler 构建示例 ActionScript 应用程序

那么,如何实际地将这些 AGAL 代码放在有效的 ActionScript 应用程序中?这正是 Stage3D API 发挥作用的地方。

var program:Program3D = context3D.createProgram(); 

在能够使用 Program3D(一个着色器)进行呈现之前,您首先需要将它上传到 GPU。为此,可以调用方法

Program3D::upload(vertexByteCode: ByteArray, fragmentByteCode:ByteArray); 

此方法调用需要以输入的形式获得顶点和片段着色器的已编译的对象代码版本,以便将它上传到 GPU。

一种将 AGAL 着色器编译为对象代码的不错方式是使用AGAL Mini Assembler:一个使用工具,以字符串形式接收顶点和像素着色器源代码作为输入,在运行时将它们编译为对象代码。

您可以在此处下载 AGAL Mini Assembler。

所以,首先您将使用 AGAL Mini Assembler 编译上面讨论的顶点和像素着色器。

var vertexShaderAssembler: AGALMiniAssembler = new AGALMiniAssembler();
vertexShaderAssembler.assemble(Context3DProgramType.VERTEX, "m44 op, va0, vc0\n" + // pos to clipspace 
"mov v0, va1" // copy color 
);
var fragmentShaderAssembler: AGALMiniAssembler = new AGALMiniAssembler();
fragmentShaderAssembler.assemble(Context3DProgramType.FRAGMENT, "mov oc, v0");

然后将顶点和像素着色器程序都上传到 GPU:

var program:Program3D = context3D.createProgram(); 
program.upload( vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode); 

使用 ActionScript 与 AGAL 通信

着色器无法自行运行。它由您基于 Stage3D 的主要的 ActionScript 应用程序使用。因此,它需要应用程序向它发送需要处理的数据。

一般而言,着色器将需要 VertexBuffer 数据(顶点属性)、纹理和着色器可能需要从 ActionSript 获得的其他参数,比如一个变换矩阵。

在呈现时,在使用 Program3D 以及相关的 VertexBuffer 和纹理之前,您需要使用以下调用启用它们:

  • Contex3D::setProgram(program:Program3D)
  • Context3D::setVertexBufferAt(index:int, buffer:VertexBuffer3D, bufferOffset:int, format:String)
  • Context3D::setTextureAt(sampler:int, texture:TextureBase)

请注意,setVertexBufferAt 在顶点缓冲区中某个偏移位置启用一个特定的顶点属性(参数 3),将它与索引(第一个参数)所指定的一个属性寄存器(流)相关联。

setTextureAt 调用启用一个 Texture,并将它与第一个参数“sampler”所指定的某个纹理采样器相关联。

以下是在呈现之前使用这些调用的语法:

// vertex position to attribute register 0 
context3D.setVertexBufferAt (0, vertexbuffer, 0, Context3DVertexBufferFormat.FLOAT_3); 
// assign texture to texture sampler 0 
context3D.setTextureAt( 0, texture ); 
// assign shader program 
context3D.setProgram( program ); 

在启用任何这些属性之前,您需要确保调用相应的上传方法将它们上传到了 GPU。

  • Program3D::upload()
  • VertexBuffer3D::uploadFromVector(data:Vector.<Number>, startVertex:int, numVertices:int)
  • Texture::uploadFromBitmapData(source:BitmapData, miplevel:uint = 0)

然后,您需要能够以常量的形式向您的着色器传入参数,将它们存储在常量寄存器中。为此,执行以下调用:

  • Context3D::setProgramConstantsFromVector(programType:String, firstRegister:int, data:Vector.<Number>, numRegisters:int = -1)
  • Context3D::setProgramConstantsFromMatrix(programType:String, firstRegister:int, matrix:Matrix3D, transposedMatrix:Boolean = false)

以便分别编写一个 ActionScript Vector 或 Matrix3D。

所以,如果您需要将一个基本的旋转矩阵传入您的着色器,您需要运行以下代码:

var m:Matrix3D = new Matrix3D(); 
m.appendRotation(getTimer()/50, Vector3D.Z_AXIS); 
context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, m, true); 

该矩阵存储在常量寄存器 0 中,供顶点着色器使用。

延伸阅读

本文向您介绍了着色器的概念和 AGAL 着色语言。如果您希望构建基于 Stage3D API 的 ActionScript 3D 应用程序,必须创建一个着色器。尽管我们还没有构建完全有用的 Stage3D 应用程序,但本文内容为使用 Stage3D API 的能力奠定了基础。在本系列的下一篇文章中,我会将所有知识点衔接起来,向您展示如何构建一个使用 Stage3D 呈现简单几何体的应用程序。

查看原文:What is AGAL

语言 & 开发架构