Everyday Stage3D (六) Texture

贴图的操作是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();
	}
}

}