[AGAL个人笔记]第三篇: Hello Stage3D`s World(部分为翻译)

原文:Stage3D / AGAL from scratch. Part III – Hello Triangle

eg.如果你E文不错的话  还是看E文比较带劲。本人E文非常烂,若有偏差请指出。

国内的大牛FlashChe也有一篇类似的基础文章,传送门在上一篇开头。

让我们开始吧

在开始编码之前请确认你已经读过第一篇与第二篇,并且都已经理解其中的原理。

你可以先下载[源码]自行编译一遍,以确认你的硬件与编译环境可行。

这篇文章将会带您一起在GPU中渲染出第一个三角形图形,其中会涉及到一点点的关于AGAL的代码,仅仅几句而已非常容易理解,如果你汇编学得不错的话 那你一眼就能看明白,即使你的汇编非常的糟糕那也没关系  我将会一句一句的解释它。(后面这句我加的)稍微再解释一下顶点在GPU上渲染三角形的作用,所谓的顶点“Vertex”其实可以理解为内存中的指针,三个顶点确定一个三角形的形状和位置,正是依靠这三个”指针” ,GPU才能在指定位置画图,

星号间的为我个人理解

/**

通俗来讲,在我们小学学三角形的时候,老师让我们画一个三角形一般的步骤应该是:

1>画出三个不在同一直线的点

2>两点之间连线

3>如果要加颜色的话 在再三角形中涂上颜色,

这里的Vertex也可以这样理解。

一个Vertex的结构一般是:

//X,  Y, Z, r, g, b

(X,Y,Z)是确定点所在位置的坐标 X,Y大小有效的为  [-1,1] 闭区间之间,Z轴比较特殊,有效区间是[0,1]以后的教程会解释为什么

(r,g,b)是确定该点颜色的颜色值 r,g,b大小 有效的为 [0,1]    == [0x00,0xFF]

例如一组Vertex可以为

Vector.([
-0.3, -0.3, 0, 1, 0, 0
0, 0.3, 0, 0, 1, 0,
0.3, -0.3, 0, 0, 0, 1]);

一行上面的数字表示一个Vertex,各个Vertex的每一位数字不一定相同,但是各个Vertex的结构必须相同,否则在上传顶点的时候会报错。(除了要符合顶点缓冲的数据结构 ,顶点的各个数字也必须符合一定的大小,)

如果还不理解的话,下面这张 平面坐标系 应该足够说明问题了。Z轴暂时不讨论,后面会有更详细的讨论文章

以上绿色部分为整个显示窗口,Z轴都是0 所以只画了平面图。这就是顶点的神秘面纱

**/

顶点缓冲在内存中的形态可以看作如图:

每一个顶点有6位数字 每一位数字为32位的float,每一个顶点的前三位(X,Y,Z)为顶点坐标  后三位(r,g,b)为输出的颜色值,结合我画的平面直角坐标系应该非常容易理解,如你所见 每一个顶点都是由六位数字组成。接下来进入实际的代码解释阶段,如果你在文章开始的地方没有下载,,那请您下载了并且导入你的编译器后再回来继续往下看。

该是写点代码的时候了(红色部分为我根据7yue的PPT定义的步骤 )

请求Context3D

这个类的结构非常的清晰易懂,我们把第一步 请求Context3D放在初始化的函数 _init()中,注册的侦听事件也非常的直观(Event.CONTEXT3D_CREATE)

private function __init(event:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, __init);

W = stage.stageWidth;
H = stage.stageHeight;

// wait for Stage3D to provide us a Context3D
stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, __onCreate);
stage.stage3Ds[0].requestContext3D();
}

Stage3D程序的核心并不是在于Stage3D类,  而是Context3D对象,stage3D程序的第一步就是确定显示Stage3D层和请求Context3D实例,Stage3D各个层和Stage的显示关系如图:(摘自 7yue的PPT )

注册了侦听后 在响应事件时 代表已经完成了请求。在这一步 Flash会检查你的显卡是否支持硬件加速,如果支持硬件加速 则只可能是两种情况,要么是微软的DirectX或者是 安卓 苹果  LINUX的OpenGL(DirectX和OpenGL都是操作显卡的图形程序接口)。如果你的显卡不支持硬件加速  则依然会请求Context3D成功 但是确是使用到软件加速(硬件加速与软件加速的区别就是  硬件加速对象为GPU  软件加速对象为CPU ,GPU当然在图形上比CPU强大N倍 毕竟人家GPU中文名就叫 图形处理器 而CPU则是中央处理器)。

设置Context3D display buffer

当我们请求了Context3D对象之后,接下来则是设置Context3D display buffer

private function __onCreate(event:Event):void {
// // // CREATE CONTEXT // //
context = stage.stage3Ds[0].context3D;

// By enabling the Error reporting, you can get some valuable information about errors in your shaders
// But it also dramatically slows down your program.
// context.enableErrorChecking=true;

// Configure the back buffer, in width and height. You can also specify the antialiasing
// The backbuffer is the memory space where your final image is rendered.
context.configureBackBuffer(W, H, 4, true);

这一步申请的缓存区是整个程序渲染所用到的空间

创建顶点缓冲 创建索引缓冲


private function __createBuffers():void {
// // // CREATE BUFFERS // //
vertexBuffer = context.createVertexBuffer(3, 6);
indexBuffer = context.createIndexBuffer(3);
}

对于createVertexBuffer函数createVertexBuffer(numVertices:int, data32PerVertex:int):

numVertices:要在缓冲区中存储的顶点数量。单个缓冲区中的最大顶点数为 65535 , 这里是3因为是三角形撒;

data32PerVertex:与每个顶点关联的 32 位(4 字节)数据值的数量。每个顶点的 32 位数据元素数量最多为 64 个(或 256 个字节)。请注意,顶点着色器程序在任何给定时间只能访问 8 个属性寄存器。,这里是6,sizeof(X,Y,Z,r,g,b);

对于createIndexBuffer(numIndices:int):

numIndices:要在缓冲区中存储的顶点数量。单个缓冲区中的最大索引数为 524287

接下来是关于AGAL的部分(作者介绍的顺利和国内大牛们介绍的有偏差,不用太介意 看代码就一目了然了):

private function __createAndCompileProgram() : void {
// // // CREATE SHADER PROGRAM // //
// When you call the createProgram method you are actually allocating some V-Ram space
// for your shader program.
program = context.createProgram();

// Create an AGALMiniAssembler.
// The MiniAssembler is an Adobe tool that uses a simple
// Assembly-like language to write and compile your shader into bytecode
var assembler:AGALMiniAssembler = new AGALMiniAssembler();

// VERTEX SHADER
var code:String = "";
code += "mov op, va0\n"; // Move the Vertex Attribute 0 (va0), which is our Vertex Coordinate, to the Output Point
code += "mov v0, va1\n"; // Move the Vertex Attribute 1 (va1), which is our Vertex Color, to the variable register v0
// Variable register are memory space shared between your Vertex Shader and your Fragment Shader

// Compile our AGAL Code into ByteCode using the MiniAssembler
vertexShader = assembler.assemble(Context3DProgramType.VERTEX, code);
code = "mov oc, v0\n"; // Move the Variable register 0 (v0) where we copied our Vertex Color, to the output color

// Compile our AGAL Code into Bytecode using the MiniAssembler
fragmentShader = assembler.assemble(Context3DProgramType.FRAGMENT, code);
}

这段代码主要是创建Program3D实例,并且把使用AGAL的方法展现了出来:

>>定义AGALMiniAssembler对象

>>编写顶点和索引着色器汇编代码

>>调用assemble方法编译

上传数据到顶点缓冲 和 索引缓冲

private function __uploadBuffers():void {
var vertexData:Vector=Vector.([
-0.3, -0.3, 0, 1, 0, 0, 	// - 1st vertex x,y,z,r,g,b
0, 0.3, 0, 0, 1, 0, 		// - 2nd vertex x,y,z,r,g,b
0.3, -0.3, 0, 0, 0, 1		// - 3rd vertex x,y,z,r,g,b
]);

vertexBuffer.uploadFromVector(vertexData, 0, 3);
indexBuffer.uploadFromVector(Vector.([0, 1, 2]), 0, 3);
}

vertexBuffer.uploadFromVector(data:Vector.<Number>, startVertex:int, numVertices:int):void

作用:从矢量数组上载一组顶点的数据到渲染上下文。

startVertex:int — 要加载的第一个顶点的索引。startVertex的非零值可用于加载顶点数据的子区域。

numVertices:int — data表示的顶点数量。

indexBuffer.uploadFromVector(data:Vector.<uint>, startOffset:int, count:int):void

作用:存储在图形子系统顶点索引中。

data:Vector.<uint>  –顶点索引的矢量。仅使用每个索引值的低 16 位。矢量的长度必须大于或等于 count。

startOffset:int — 此 IndexBuffer3D 对象中的索引,要加载的第一个索引。不等于零的 startOffset 值可用于加载索引数据的子区域。

count:int — data 中索引的数量。

索引缓冲的Vector数据告诉我们,数据告诉GPU绘制三角形的顺序是按照顶点顺序 0 1 2进行绘制

再次提醒一下,X,Y,Z 与 r,g,b的大小是有限制的 详细的请翻前面的教程中我个人附加的部分内容。

上传数据到program3D:

private function __uploadProgram():void { 	// UPLOAD TO GPU PROGRAM

program.upload(vertexShader, fragmentShader); // Upload the combined program to the video Ram

}

在我们创建顶点缓冲的时候 我们就已经告诉了显卡我们每一个顶点关联的数值数量(每一个顶点包含的数字 例如这里的 6),这是一个关键点,如果设置错误则会发现编译错误,在执行过程中 显卡会按照创建顶点缓冲的时候设置的数值数量来操作你提供的顶点数组。

例如本例我们告诉GPU每一个顶点数值量为6,则GPU在渲染的时候 每次在数组中获取6位数字,之所以上传到缓存区  是因为每次调用的时候在缓冲区中操作的速度接近GPU,缓冲存在的目的就在一此。

接下来介绍的是GPU具体操作顶点缓冲数据的步骤:先见下图

如图,GPU先获取指定行的6个数据,setVertextBufferAt函数的第一个参数表示数组vertexBuffer中的顶点索引,0表示第一个 1表示第二个 以此类推。

然后就是使用寄存器操作了,啥叫寄存器自己百度。GPU的寄存器是128位的 也就是32+32+32+32  这里由于我们设置了6个数值为一个顶点,所以GPU把前三个数X,Y,Z放到 一个寄存器中  寄存器剩余的32位本来是”W”属性现在用0补充,把后三位颜色属性放到另外的寄存器中  剩余的32位数据本来是用来放“a”也就是alpha属性的 现在也用0补充。
下面代码第三个参数的 Context3DVertexBufferFormat.FLOAT_3 的意思就是使用寄存器中三个32位的float的意思,这个参数还有另外一个值是
Context3DVertexBufferFormat.BYTES_4 当然就是使用全部的意思啦。

private function __splitAndMakeChunkOfDataAvailableToProgram():void {
	// So here, basically, your telling your GPU that for each Vertex with a vertex being x,y,y,r,g,b
	// you will copy in register "0", from the buffer "vertexBuffer, starting from the postion "0" the FLOAT_3 next number
	context.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); // register "0" now contains x,y,z

	// Here, you will copy in register "1" from "vertexBuffer", starting from index "3", the next FLOAT_3 numbers
	context.setVertexBufferAt(1, vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_3); // register 1 now contains r,g,b
}

总结一下GPU操作顶点缓冲的规则:

1>取一个顶点大小的数据

2>把前面三位放到寄存器 0

3>把后面三位放到寄存器 1

4>寄存器0 存放的是地址指针 寄存器1 存放的是颜色信息

AGAL一般是在顶点着色器(Vertex Shader )和索引着色器(Fragment Shader)中使用

一般情况下只能访问顶点着色器  但是我们可以通过公用的寄存器来操作索引着色器

先来看看AGAL语法的一般格式:

opcode destination, source1[, source2][, options]

例如:

[cc lang=”asm”]mov  op,va0[/cc]

可以解释为:

[cc lang=”java”]op = va0[/cc]

在例如:

[cc lang=”java”]mul vt0, va0, vc0[/cc]

可以解释为:

[cc lang=”java”]vt0 = va0*vc0[/cc]

如果你大学汇编学得不错,,肯定不用解释都知道啥意思,,汇编真是好东西,算是我最喜欢的语言之一了.如果有时间 建议你也去学学.但是学汇编学再好也很难找工作哟  真是一个非常现实的问题。(by 译者)

下面解释几个对于上面使用到的寄存器:

va0:Vertex Attribute 0. 顶点属性寄存器,这是用来存放我们的X,Y,Z的顶点坐标数据的寄存器,一共8个  va0~va7;

op:Output Point.顶点输出位置,这是一个特别的寄存器,用来给输出顶点位置;

v0:Variable Register 0.变量寄存器  是顶点着色器和索引着色器公用的寄存器 类似汇编中的eax。一共八个 v0~v7;

oc : Output Color.片段输出色彩寄存器(索引输出颜色差不多的意思),是一个特别的寄存器 用来提供颜色输出;

现在大家了解这几个寄存器的作用之后 回头看看前面那几句AGAL代码:

顶点着色器部分:

 mov op, va0
 mov v0, va1

这个意思就是 op = va0,v0 = va1;
va0 va1 是什么?回去看看分析图 就知道
va0 = X + Y + Z + 0 ;
va1 = r + g + b + 0 ;

现在都理解了吧 是不是很简单?

索引着色器部分:

mov oc,v0

v0是什么?我们的代码是顶点着色器的先编译 所以v0 = va1 = r + g + b + 0 也就是顶点的颜色信息
确认shaders数据

 private function __setActiveProgram():void {
 // Set our program as the current active one
 context.setProgram(program);
 }

在屏幕上渲染出三角形:

private function render(event:Event):void {
	context.clear(1, 1, 1, 1); // Clear the backbuffer by filling it with the given color

	context.drawTriangles(indexBuffer); // Draw the triangle according to the indexBuffer instructions into the backbuffer
	context.present(); // render the backbuffer on screen.
}

代码一看就明白 这段代码是放在侦听每一帧的事件中的,现实清除屏幕 然后是drawTriangles 最后是 present。如果你配置没错的话 你会看到如下图:

这里的例子没有用到m44操作码 没有用到Matrix3D,,其实这是作者故意的。这里作者的意图只在让大家一步一步的理解其中的原理.

如果以上全部内容你都理解了 那下面的将会非常简单.

作者在后面给出了一个练习:通过修改AGAL 实现下面的图形~

译者给出的答案是:

修改顶点数据为:


private function __uploadBuffers():void {
var vertexData:Vector.<Number>=Vector.<Number>([
0, 0, 0, 0.5, 0, 0,     // - 1st vertex x,y,z,r,g,b
1, 0, 0, 0, 0, 0,         // - 2nd vertex x,y,z,r,g,b
0, 1, 0, 0, 0.3, 0        // - 3rd vertex x,y,z,r,g,b
]);

你 也可以试试。

Hope you liked it ! by 原作者

总结

这篇教程比较长,如果你能看到这里  那你应该除了真心想了解AGAL之外 也是比较有耐心的人。那恭喜你  如果你继续下去的话 你迟早也会是大牛。(好遥远的目标哟.)

这篇主要讲的是如果编写一个Stage3D程序  基本的结构 在源码中已经呈现  整偏教程都是在讲解一句一句的代码。很有小时候老师教文言文的感觉  呵呵。。可惜也许工作生活中  我们的PM LEADER 并没有这种耐心  所以必须自己背后好好努力~加油!

2 评论

发表评论

电子邮件地址不会被公开。 必填项已用*标注