Everyday Stage3D (七) 常量寄存器的使用

有朋友跟我反映说这系列教程很不错也非常喜欢,但是唯一的缺憾是文章中出现的demo太少,干巴巴的文章没有意思。确实,在计划中本系列教程其实是有不少的实例,但是希望大家慢慢来看,因为AGAL的不少内容确实过枯燥,还请大家静下心来慢慢学习。 到目前位置,我们所学习的内容中还没有涉及到常量寄存器,也就是vc,fc的部分。那么这次我们就通过一个特效来向大家展示常量寄存器的使用方法。具体的效果也非常的简单,大家可以上手尝试一下,一个将图片变为黑白图像的效果。这里我们提供三种不同的实现方法。其实也是替换了三种不同的算法来实现灰度图的效果。其中一种方法请教自弑神,另外两种一个是查阅网上的资料另外一种是我不停尝试而尝试出的一种方法。 为了方便大家学习,我会随后将文章中的demo放到微博中分享,还希望大家关注我的微博。 我们来看第一种方法,这种方法没有用到fc或者vc,因为在这个算法中我们通过RGB值的不同变换而得到一个灰度图。来看一下算法。这是一个伪代码,大家可以自行用自己喜欢而熟悉的语言来进行编写。

val = max( R, G, B );
R = val;
G = val;
B = val;
不错,其实这种算法非常的简单。当我们一个图像中每一个像素点的RGB值相等,且值来自于RGB中最大值。那么这个图像就会编成黑白色。理解起来也非常的简单,也就是说当我们的RGB值想的的时候,实际上我们的颜色是从黑到白色之间的灰色,只不过深浅不同。对应的AGAL颜色处理代码如下:
var fp:String =
        "tex ft0, v0, fs0 <2d,linear,nomip>\n" +
        "max ft1.w, ft0.x, ft0.y\n" +
        "max ft1.x, ft1.w, ft0.z\n" +
        "mov ft0.x, ft1.x\n" +
        "mov ft0.y, ft1.x\n" +
        "mov ft0.z, ft1.x\n" +
        "mov oc,ft0";
简单解释一下,当我们的程序读取纹理中的一个像素点的时候,我们将它放置于ft0中。此时的ft0中的颜色还是一个彩色的。从第二行开始,我们使用ft1,计算ft0中RGB颜色值的最大值,具体的RGB颜色值取出是通过ft0.x,ft0.y,ft0.z来访问的。 通过max命令比较得出最大值,并将最后的结果赋予ft0。这样我们就可以得出了最终的图像。 最终效果如图:

[caption id=“attachment_332” align=“alignnone” width=“614”]agal_1 agal_1[/caption]

下面我们来看一下第二种算法,这里我们就需要用到fc这个常量寄存器。先来说说算法,对于这种算法我们也可以用一段伪代码来定义。

val = (R + B + G)/3;
R = val;
G = val;
B = val;
其实理解起来并不难,我们的RGB颜色值最终还是要求必须一样,只不过从来与的Max取值编成了三者的平均值。对于AGAL代码如下:
var fp:String =
        "tex ft0, v0, fs0 <2d,linear,nomip>\n" +
        "add ft1.x, ft0.x, ft0.y\n" +
        "add ft1.x, ft1.x, ft0.z\n" +
        "div ft1.w, ft1.x, fc0.x\n" +
        "mov ft0.x, ft1.w\n" +
        "mov ft0.y, ft1.w\n" +
        "mov ft0.z, ft1.w\n" +
        "mov oc,ft0";
这段代码中,我们将ft0中存放的rgb颜色值全部相加并且存放到ft1.w中,然后将ft1.w除以fc0.x(此值为3)得到平均值。这里需要注意的是div命令为除法命令,而fc0则是我们设置的常量寄存器。最终我们将ft1.w中的数值赋予ft0即可得到正确的颜色值。具体fc的来路我们是通过下面语句实现的。
this._context3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.<Number>([3, 0, 0, 0]) );
这句话就是我们本篇的重点,setProgramConstantsFromVector语句用于在程序运行的时候向常量寄存器中存入一个数值。那么他的参数解释如下:

第一个参数:这个参数表明你要将数据传入vc(顶点常量寄存器)还是fc(片段常量寄存器)。我们这里设置的为fc。 第二个参数:寄存器的编号,我们传入的为0,那么就标识fc0,如果你传入12,那么就标识fc12。但是请注意,你的片段常量寄存器只有28个,也就是说,你最大只能传入参数为27。对于顶点常量寄存器来说我们有128个,那么你可以最大传入127。 第三个参数:一个Number类型的Vector。这里就是具体数据,我们需要一个长度为4的数据但是只用到第一个数据,所以我们一开始的数值为3。对于上面的AGAL来说,最后ft1.w除以的数字是3。 了解了这些之后,你就可以编译你的程序,运行效果如下:

[caption id=“attachment_333” align=“alignnone” width=“614”]agal_2 agal_2[/caption]

从上面两个例子中我们可以看到,对于fc的使用还是非常方便的。那么设置常量寄存器我们有两个不同的API来实现,分别是setProgramConstantsFromVector和setProgramConstantsFromMatrix。你可以根据你的不同需要来进行选择。 到此位置细心的同学会发现,对于第一种算法,我们得到的灰度图偏亮,对于第二种算法得到的灰度图则偏暗。那么有没有一种显示比较完美的算法呢?有!具体算法如下:

val = 0.3*R + 0.59*G + 0.11*B;
R = val;
G = val;
B = val;
可能有很多人不明白这三个参数值从何而来,没关系,如果你此有兴趣可以联系我继续进行讨论。实际上这个公式是我从弑神那里的来的。公式的发明者既不是我也不是弑神。我们从一些文献中得到了这个经过N多年推算出来的公式,具体的演算过程也相当的复杂。对此这里不做过多的讲解。 那么如果使用AGAL来实现这个公式呢?很简单,我们通过fc将三个值传入fc1(这里我没有用fc0,为了和上一个demo区分开),然后对应的进行计算。AGAL代码如下:
var fp:String =
        "tex ft0, v0, fs0 <2d,linear,nomip>\n" +
        "mul ft1.x, ft0.x, fc1.x\n" +
        "mul ft1.y, ft0.y, fc1.y\n" +
        "mul ft1.z, ft0.z, fc1.z\n" +
        "add ft1.w, ft1.x, ft1.y\n" +
        "add ft1.w, ft1.w, ft1.z\n" +
        "mov ft0.x, ft1.w\n" +
        "mov ft0.y, ft1.w\n" +
        "mov ft0.z, ft1.w\n" +
        "mov oc,ft0";
然后我们编写上传fc数据的代码:
this._context3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.<Number>([0.3,0.59,0.11,0]) );
OK!我们来编译一下程序测试效果如图:

[caption id=“attachment_334” align=“alignnone” width=“614”]agal_3 agal_3[/caption]

我们可以看到这次的图片灰度相当的柔和。具体效果大家可以自行替换图片来观察。这次特别说明,上面三个demo中显示的图片均来自flashacheAcheGesture 手势库的logo,虽然我还没有和他打招呼,不过相信他不会怪我的。。OK!为了表示谢意,后面的会对 AcheGesture 手势库做专门的特别介绍。最后我们放出本篇文章的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 stage3ddemo4 extends Sprite
{
	[Embed(source="pics.png")]
	private var pic:Class;

	private var _stage3d:Stage3D;
	private var _context3d:Context3D;

	public function stage3ddemo4()
	{
		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\n" +
			"mov v0, va1";

		var vagal:AGALMiniAssembler = new AGALMiniAssembler();
		vagal.assemble( Context3DProgramType.VERTEX, vp );

		var fp:String = 
			"tex ft0, v0, fs0 <2d,linear,nomip>\n" +
			"mul ft1.x, ft0.x, fc1.x\n" +
			"mul ft1.y, ft0.y, fc1.y\n" +
			"mul ft1.z, ft0.z, fc1.z\n" +
			"add ft1.w, ft1.x, ft1.y\n" +
			"add ft1.w, ft1.w, ft1.z\n" +
			"mov ft0.x, ft1.w\n" +
			"mov ft0.y, ft1.w\n" +
			"mov ft0.z, ft1.w\n" +
			"mov oc,ft0";
		/*	
			"tex ft0, v0, fs0 <2d,linear,nomip>\n" +
			"add ft1.x, ft0.x, ft0.y\n" +
			"add ft1.x, ft1.x, ft0.z\n" +
			"div ft1.w, ft1.x, fc0.x\n" +
			"mov ft0.x, ft1.w\n" +
			"mov ft0.y, ft1.w\n" +
			"mov ft0.z, ft1.w\n" +
			"mov oc,ft0";
		*/
		/*		
		"tex ft0, v0, fs0 <2d,linear,nomip>\n" +
		"max ft1.w, ft0.x, ft0.y\n" +
		"max ft1.x, ft1.w, ft0.z\n" +
		"mov ft0.x, ft1.x\n" +
		"mov ft0.y, ft1.x\n" +
		"mov ft0.z, ft1.x\n" +
		"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._context3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.<Number>([3, 0, 0, 0]) );
		this._context3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.<Number>([0.3,0.59,0.11,0]) );

		update();
	}
	private var pm:Program3D;
	private var vbs:VertexBuffer3D;
	private var ibs:IndexBuffer3D;
	private var tex:Texture;

	//循环渲染
	private function update(evt:Event = null):void
	{
		this._context3d.clear();
		this._context3d.drawTriangles( this.ibs );
		this._context3d.present();
	}
}

} import flash.events.Event;