A闪的 BLOG 技术与人文
贴图的操作是GPU程序的必备功能之一。在stage3D中我们也可以很方便的使用位图数据作为我们程序的纹理贴图。对于纹理的操作本篇这涉及一小部分,更多的内容后面还会继续涉及到。在编写代码之前,我们要先来了解一下UV的概念。
简单来讲,任何一个知识都不是某些人凭空想像出来的,UV存在即有他存在的理由。当我们对一个模型进行贴图操作的时候,我们希望知道这个贴图每一个像素点对应的顶点位置。为了解决这个问题,我们就产生了UV的概念。由于所有的贴图都是一张矩形图片,而图片并非存在于三维空间中的,也就是说,我们要表达图片中某一个点的位置,可以使用XY来进行表示。但是在实际的程序开发中,我们不希望贴图坐标对应点这类数据与三维顶点的XYZ数据混淆,所以我们使用UV来进行表示。为了方便大家学习,这次的demo我们换了一个稍微复杂一点的,将原来的三角形变为了一个正方形。我们通过下面的图片来说明一个正方形的顶点数据。
这幅图中有一个正方形,包含四个顶点,坐标值已经标明在图中了。其中蓝色的线将红色的正放心分割为两个三角形,分别为T1和T2,当我们用GPU绘制三角形的时候,GPU是分别绘制T1和T2的,然后将绘制后的结构填充到同一张位图数据中,最后将位图打印到我们FLASH的容器当中。下面我们来看看位图数据。如图2
图中已经红色的方块表示一张位图,而黑色的线表示笛卡尔坐标轴,XY对应的也是我们的UV,图中的坐标值也是我们的UV值。我们可以看到,在UV中,我们的坐标系和顶点数据的坐标系是不同的。
OK!如果我们想让图片正确的显示在GPU中,我们要将贴图的UV数据和顶点数据一一对应,并且编写AGAL让GPU理解这些数据的关系以及含义。那么我们来看一下,对于顶点(0,0)来说,它应该对应的UV值为(0,1),具体关系可以参考上面的两幅图。同理,坐标为(1,1)的顶点应该对应UV值为(1,0),以此类推,我们可以得出四个顶点的所有UV值。通过上面的设想,我们设计了新的数据格式,每个顶点有四个值,每个值标识XYUV,代码如下。
//顶点数据
var vb:Vector.<Number> = Vector.<Number>([
0,0, 0,1,
1,0, 1,1,
1,1, 1,0,
0,1, 0,0
]);
由于现在有两个三角形,那么我们顶点索引数据也相应的变为了如下格式。
//顶点索引 var ib:Vector.<uint> = Vector.<uint>([ 0,3,1, 1,2,3 ]);
有了这些基础的数据之后,我们来看一下关于纹理的操作。在stage3D中,我们可以使用BitmapData作为纹理的数据源。也就是说,到目前为止,你可以不考虑图片的格式,只要是Flash能支持load进来的图片都可以作为我们的纹理。前提是你的纹理一定要为2的次方,比如1x1,128x128,512x512这样的尺寸。这里为了方便,我们选取了一张128x128的图片,并且使用下面的代码将纹理上载置GPU。
//纹理 tex = this._context3d.createTexture(128,128,Context3DTextureFormat.BGRA,true); var bit:Bitmap = new pic(); bit.x = 350; this.addChild( bit ); tex.uploadFromBitmapData( bit.bitmapData, 0 );
当程序运行后,我们的贴图纹理就已经被上载到GPU当中,下面我们来编写AGAL,首先是顶点操作。
//AGAL var vp:String = “mov op, va0 ” + “mov v0, va1”; var vagal:AGALMiniAssembler = new AGALMiniAssembler(); vagal.assemble( Context3DProgramType.VERTEX, vp );
和原来的操作相同,只不过这里的va0中存储的数据为xy,而va1中的数据存储的则是UV坐标值。纹理着色器AGAL如下:
var fp:String = “tex ft0, v0, fs0 <2d,repeat,linear,nomip> ” + “mov oc,ft0”; var fagal:AGALMiniAssembler = new AGALMiniAssembler(); fagal.assemble( Context3DProgramType.FRAGMENT, fp );
很多人看到这里就会有些疑问,为什么fs0后面跟着这么一长串乱七八糟的东西。我们来详细解释一下。首先fs0表示我们的纹理,也就是要处理的贴图数据。而v0中则存放着我们的UV坐标。那么tex的作用是什么样子的呢?
其实非常好理解,因为以前的操作大部分都可以用顶点着色器的方法去理解。我们不关心程序运行的过程,而只关心程序运行时候的某些特定的点。例如,某一个顶点的变换。其实纹理也是一样的,虽然看上去是一个面,但是纹理也是有众多的像素点组成,有了点的概念,这里理解起来也就简单多了。我们知道一个点的颜色值由RGBA组成,这里每运行到一个像素点的时候,我们就可以读取到这个像素点的RBGA颜色值,其实还是对一个点进行操作。那么上面的语句意思是将v0中对应点的数据从fs0中取出。也可以这样理解,我们从v0中知道了一个像素点的位置,并且在fs0中找到了这个像素点,取出他的RGBA颜色值放到ft0中。
关于tex命令就说明到这里,我们再来看看<2d,repeat,linear,nomip> 这一串代码属于sampler格式。他包含4位有效数据,我们来简单解释一下。
第一位 表示维度,可使用的标签为2d或者Cube。由于我们的demo中不涉及3D效果,所以我们这里写入2d
第二位 表示环绕,可使用标签有clamp(固定不变)和repeat(自动重复)。
第三位 表示滤镜,可使用标签有nearest(临近镜面)和linear (线性)。
第四位 表示mipmap,可使用标签有disable,nearest和linear
这四个标签代表了你贴图纹理的一些属性,通过修改这四个标签的内容,你可以从最终显示效果看到不一样的画面。
下面我们继续完善我们的代码,数据都上载完成后,我们编写代码,主要是set操作,以便指定我们GPU绘制的内容,代码如下:
this._context3d.setTextureAt( 0, this.tex ); this._context3d.setProgram( pm ); this._context3d.setVertexBufferAt(0,this.vbs,0, Context3DVertexBufferFormat.FLOAT_2 ); this._context3d.setVertexBufferAt(1,this.vbs,2, Context3DVertexBufferFormat.FLOAT_2);
这部分代码在前面几篇中都有涉及,再次不做过多的讲解。最后运行看一下我们的效果。
全部demo代码在下面:
package { import com.adobe.utils.AGALMiniAssembler;import flash.display.Bitmap; import flash.display.Sprite; import flash.display.Stage3D; import flash.display3D.Context3D; import flash.display3D.Context3DProgramType; import flash.display3D.Context3DTextureFormat; import flash.display3D.Context3DVertexBufferFormat; import flash.display3D.IndexBuffer3D; import flash.display3D.Program3D; import flash.display3D.VertexBuffer3D; import flash.display3D.textures.Texture; import flash.events.Event; [SWF(frameRate="60")] public class stage3ddemo2 extends Sprite { [Embed(source="pic.png")] private var pic:Class; private var _stage3d:Stage3D; private var _context3d:Context3D; public function stage3ddemo2() { trace( stage.stage3Ds.length ); this._stage3d = stage.stage3Ds[0]; this._stage3d.addEventListener(Event.CONTEXT3D_CREATE,created); this._stage3d.requestContext3D(); } private function created(evt:Event):void { trace( this._stage3d.context3D.driverInfo ); this._context3d = this._stage3d.context3D; this._context3d.enableErrorChecking = true; this._context3d.configureBackBuffer( 300,300,16,true ); init(); } private function init():void { //顶点数据 var vb:Vector.<Number> = Vector.<Number>([ 0,0, 0,1, 1,0, 1,1, 1,1, 1,0, 0,1, 0,0 ]); vbs = this._context3d.createVertexBuffer( vb.length/4, 4 ); vbs.uploadFromVector( vb,0, vb.length/4 ); //顶点索引 var ib:Vector.<uint> = Vector.<uint>([ 0,3,1, 1,2,3 ]); ibs = this._context3d.createIndexBuffer( ib.length ); ibs.uploadFromVector( ib, 0, ib.length ); //纹理 tex = this._context3d.createTexture( 128,128, Context3DTextureFormat.BGRA,true); var bit:Bitmap = new pic(); bit.x = 350; this.addChild( bit ); tex.uploadFromBitmapData( bit.bitmapData, 0 ); //AGAL var vp:String = "mov op, va0
” + “mov v0, va1”;
var vagal:AGALMiniAssembler = new AGALMiniAssembler(); vagal.assemble( Context3DProgramType.VERTEX, vp ); var fp:String = "tex ft0, v0, fs0 <2d,repeat,linear,nomip>
” + “mov oc,ft0”;
var fagal:AGALMiniAssembler = new AGALMiniAssembler(); fagal.assemble( Context3DProgramType.FRAGMENT, fp ); //shader pm = this._context3d.createProgram(); pm.upload( vagal.agalcode, fagal.agalcode ); this._context3d.setTextureAt( 0, this.tex ); this._context3d.setProgram( pm ); this._context3d.setVertexBufferAt(0,this.vbs,0, Context3DVertexBufferFormat.FLOAT_2 ); this._context3d.setVertexBufferAt(1,this.vbs,2, Context3DVertexBufferFormat.FLOAT_2); this.addEventListener(Event.ENTER_FRAME, update); } private var pm:Program3D; private var vbs:VertexBuffer3D; private var ibs:IndexBuffer3D; private var tex:Texture; //循环渲染 private function update(evt:Event):void { this._context3d.clear(); this._context3d.drawTriangles( this.ibs ); this._context3d.present(); } }
}