Everyday Stage3D (二) Triangle

        上一篇中给大家看了一个简单的demo,我们通过设备开启GPU,并创建了一个stage3D舞台。这一篇中我将给大家带来一个可以看得见的demo。

        从副标题中我们可以看出,这篇讲的和三角形有关系。笔者多年前接触3D的时候会经常疑问,为什么现在所有的3D技术都要从三角形开始,而非其他形状。其实仔细想想也非常简单,因为在一个3D可视化的界面中,我们能够从几何的概念上将一个画面元素分为三种,点,线,面。点是几何中最基本的单位,而两点成线则标识拥有两个点可以连接为一条直线,面则是由三个点组成的,正所谓三点成面。而我们在3D画面中所看到的所有物体都是由面来组成的,换句话说,三角形的面正式我们视图元素中的最小单位。在stage3D中我们没有直接可操作面的API,我们唯一能操作的是点,这也是大部分3D技术的绘图模式。另外还有一种绘图模式称之为“原子级别渲染”,这种技术对于硬件要求之高,通常用于医疗和科研项目中。

        那么我们要在画面中显示一个三角形需要的则是3个点的数据,我们可以将3个点的坐标值上传到GPU,并且制定他的渲染顺序,剩下的渲染工作就完全交给GPU来执行了。

        在本系列教程的前半部分,我们只会涉及到2D内容,因为2D与3D底层的渲染规则实际上是相同的,所以理解2D的渲染方法,3D的也就迎刃而解了。那么如何编写程序来让我们的GPU老老实实的工作呢。我们先来看一段代码:

//三角形顶点数据
var triangleData:Vector.<Number> = Vector.<Number>([
//	x, y, r, g, b
0, 1, 0, 0, 1,
1, 0, 0, 1, 0,
0, 0, 1, 0, 0
]);
this._vb = this._context3d.createVertexBuffer( triangleData.length/5, 5 );
this._vb.uploadFromVector( triangleData,0,triangleData.length/5 );

这段代码中我们定一个数组,数组内的数据则是我们三角形三个顶点的数据,大家注意格式,由于我们需要显示出一个三角形的,所以我们在后面加上了一个RGB颜色值,一边我们看到效果。这里的_vb变量是一个VertexBuffer3D类型对象,它的作用简单而明了,就是负责顶点数据上传的,你在这个类的API文档中只能找到三个function,通过名字也可以轻易知道他们的作用。由于我们使用的是Vector数据类型的顶点数据,那么我们也就要对应的使用uploadFromVector这个API来上载数据。另外一种二进制的格式上传后面我将为大家做详细介绍。

        这里要注意的地方是,VertexBuffer3D对象并不能简单的通过new操作来实现创建,我们只能通过context3D来创建VertexBuffer3D对象。创建的方法在上面也有,请注意参数的内容,简单解释一下,

参数一:顶点的数量,我们这里有三个顶点,由于每个顶点包含5个数据,所有需要用数组长度除以5.

参数二:每个顶点数据的长度,因为我们每个顶点数据包含xyrgb五个值,所以参数为5.

       好,关于顶点的内容先说道这里,下面我们来说说顶点索引。

       当我们要渲染一个场景的时候,我们可能拥有上百万个三角形,每个三角形有三个顶点,以一百个三角形为例,最糟糕的情况是我们拥有300个顶点数据(具体为什么糟糕我将在后面的文章中讲解)。你需要告诉GPU每个顶点的渲染顺序,这就和我们每天做北京地铁一样,上车的时候大家都需要排队,如果大家不排队,那么将会酿成惨案。这里我们需要通过上载一条数据,告诉GPU哪一个顶点排在前面哪一个顶点排在后面,让大家依此接受GPU的渲染。具体操作代码如下:

//三角形索引数据
var indexData:Vector.<uint> = Vector.<uint>([
0, 1, 2
]);
this._ib = this._context3d.createIndexBuffer( indexData.length );
this._ib.uploadFromVector( indexData, 0, indexData.length );

        由于顶点顺序非常的简单,是一个线形的数据,所以我们用一个数据表示即可,数组中的内容则是刚才顶点数据的下标,我们有三个顶点数据,那么他们对应的下标则是0,1,2。这里没有复杂的顺序关系,所以直接写入顺序即可。顶点索引的对象_ib为IndexBuffer3D类型,同样不可以通过new来创建,使用context3D中的createIndexBuffer函数来创建即可。参数则为顶点索引的长度。

最后通过uploadFromVector将顶点索引数据上载到GPU

   到目前位置,我们执行程序你看不到任何效果,但是你刚才所做的操作,已经将数据全部上载到了GPU的寄存器内。换句话说,数据已经准备好了,后面的工作要告诉GPU如何去渲染我们的画面,也就是大家说的开始要编写shader了。

        在开始编写那些讨人厌的shader之前,我先来想大家说明一件事情。我曾经不停的寻找过stage3D的学习方法,当时每当我想到AGAL的时候,我发现这部分内容的需诶曲线非常之大。而且这并非一个线性的学习过程,我们需要并行学习多种知识才能将里面的内容融会贯通。所以后面的内容可能看上去并无任何关联,但是每一个都需要认真学习。当你遇到一个非常复杂而难缠的问题时,请不要忘记你可能在某一天的某一个时间,恰恰实践过一个简单的小程序,而这个原理碰巧可能帮到你。

         shader代码如下:

//AGAL
var vagalcode:String = "mov op, va0\n" +
"mov v0, va1";
var vagal:AGALMiniAssembler = new AGALMiniAssembler();
vagal.assemble( Context3DProgramType.VERTEX, vagalcode );
var fagalcode:String = "mov oc, v0";
var fagal:AGALMiniAssembler = new AGALMiniAssembler();
fagal.assemble( Context3DProgramType.FRAGMENT, fagalcode );
this._pm = this._context3d.createProgram();
this._pm.upload( vagal.agalcode, fagal.agalcode );

这里的AGALMiniAssembler类是adobe为我们提供的一个工具类,可以将我们字符串形式的AGAL代码转为AGAL的二进制格式。希望大家不要重复早轮子了,这个东西没太多意义。如果为了学习,你也可以进行一个逆向工程的demo。就是将已经编译好的一个二进制格式的shader程序还原为AGAL。

        上载AGAL的操作工作完全归功于Program3D这个类。他的API也非常的简单,甚至比VertexBuffer3D还要简单。大家可以自行参考API文档。这里要说明的是我不在此对这段AGAL进行讲解,因为内容扩展出来非常的多,下一篇内容中我将详细讲解。

        到此位置,我们所需要的所有数据都已经完成,并且当你程序运行的时候这些内容已经存在与GPU当中。后面我们要做的是将数据取出来,供AGAL计算使用。具体代码如下:

private function draw():void
{
this._context3d.clear( 0, 0, 0);
this._context3d.setVertexBufferAt( 0, this._vb, 0, Context3DVertexBufferFormat.FLOAT_2 );
this._context3d.setVertexBufferAt( 1, this._vb, 2, Context3DVertexBufferFormat.FLOAT_3 );
this._context3d.setProgram( this._pm );
this._context3d.drawTriangles( this._ib );
this._context3d.present();
}

这段代码目前我讲解了你可能也很难理解,后面学习过AGAL之后你就会明白为什么这些API参数是这个样子的了。

其实这段代码简单的可以分为四部。

一、清除画面内容

二、设置数据供AGAL使用

三、执行渲染

四、将渲染后结果打印到我们的画面当中。

         编写好代码之后我们来debug一下,如果你的代码正确那么你应该看到的是下面这幅图片。

屏幕快照 2013-01-16 下午1.38.08

完整代码请看这里:

package
{
import com.adobe.utils.AGALMiniAssembler;
import flash.display.Sprite;
import flash.display.Stage3D;
import flash.display3D.Context3D;
import flash.display3D.Context3DProgramType;
import flash.display3D.Context3DRenderMode;
import flash.display3D.Context3DVertexBufferFormat;
import flash.display3D.IndexBuffer3D;
import flash.display3D.Program3D;
import flash.display3D.VertexBuffer3D;
import flash.events.ErrorEvent;
import flash.events.Event;
import flash.events.MouseEvent;
public class stage3ddemo1 extends Sprite
{
private var _stage3d:Stage3D;
private var _context3d:Context3D;
public function stage3ddemo1()
{
trace( "stage3D层数量:",stage.stage3Ds.length );
if( stage.stage3Ds.length > 0 )
{
this._stage3d = stage.stage3Ds[0];
this._stage3d.addEventListener(Event.CONTEXT3D_CREATE,created_handler);
this._stage3d.addEventListener(ErrorEvent.ERROR, created_error_handler );
this._stage3d.requestContext3D( Context3DRenderMode.AUTO );
}
}
private function created_error_handler(evt:ErrorEvent):void
{
trace("GPU启动失败或设备丢失!");
}
private function created_handler (evt:Event):void
{
trace( "GPU设备类型:",this._stage3d.context3D.driverInfo );
this._context3d = this._stage3d.context3D;
this._context3d.configureBackBuffer( 300,300,0,true);
//	 stage.addEventListener(MouseEvent.CLICK, click_handler );
this.addTriangle();
this.draw();
}
private var _vb:VertexBuffer3D;
private var _ib:IndexBuffer3D;
private var _pm:Program3D;
private function addTriangle():void
{
//三角形顶点数据
var triangleData:Vector.<Number> = Vector.<Number>([
//	x, y, r, g, b
0, 1, 0, 0, 1,
1, 0, 0, 1, 0,
0, 0, 1, 0, 0
]);
this._vb = this._context3d.createVertexBuffer( triangleData.length/5, 5 );
this._vb.uploadFromVector( triangleData,0,triangleData.length/5 );
//三角形索引数据
var indexData:Vector.<uint> = Vector.<uint>([
0, 1, 2
]);
this._ib = this._context3d.createIndexBuffer( indexData.length );
this._ib.uploadFromVector( indexData, 0, indexData.length );
//AGAL
var vagalcode:String = "mov op, va0\n" +
"mov v0, va1";
var vagal:AGALMiniAssembler = new AGALMiniAssembler();
vagal.assemble( Context3DProgramType.VERTEX, vagalcode );
var fagalcode:String = "mov oc, v0";
var fagal:AGALMiniAssembler = new AGALMiniAssembler();
fagal.assemble( Context3DProgramType.FRAGMENT, fagalcode );
this._pm = this._context3d.createProgram();
this._pm.upload( vagal.agalcode, fagal.agalcode );
}
private function draw():void
{
this._context3d.clear( 0, 0, 0);
this._context3d.setVertexBufferAt( 0, this._vb, 0, Context3DVertexBufferFormat.FLOAT_2 );
this._context3d.setVertexBufferAt( 1, this._vb, 2, Context3DVertexBufferFormat.FLOAT_3 );
this._context3d.setProgram( this._pm );
this._context3d.drawTriangles( this._ib );
this._context3d.present();
}
private function click_handler(evt:MouseEvent):void
{
this._context3d.dispose();
}
}
}