Away3Dにてテクスチャに別テクスチャの画像などを書き込んで表示するまで

=前置き=

 ミニゲーム作成のための描画まわりを作成していて、「BitmapDataやFilterではなくシェーダで書いた方がラクな画像加工」というのがあったので、そこら辺のメモ。
 今回の用途では端末のサイズに合わせてゲーム内で行う必要があったのでAway3Dを使っているものの、単なるコンバート目的であれば生のStage3Dだけでも普通にできると思う。

 ちなみにこんな感じでしばらくは検証とかが入るので、ミニゲームの作成は3ヶ月に1個というよりは6ヶ月に2個という感じのペースになりそう。(序盤は検証で時間を食いそう)

=解説=

 今回は「テクスチャに描いてあるものを別のテクスチャに普通に描画する」というところまで。実際の用途ではこの描画の際のシェーダをいじったり、複数のテクスチャを書き込んだりする。

 で、要点は以下のような感じ

  • 描画そのものはわりと普通の描画と同じ
  • setRenderToTextureで描画先をテクスチャに変更しておく
    • その際のテクスチャは普通に作成しておき、getTextureForStage3Dで得たものを使う
  • 描画元テクスチャを画面いっぱいに描く感じに設定しておくことで描画先テクスチャ全体に描く
    • ここらへんはDATA_VERTEXあたりの設定で行っている

=コード=

/*
一旦テクスチャに描いてから表示するところまで
・シェーダは普通なので現段階では通常描画とほぼ変わらない(linearとかsmoothとかの影響はあるが)
*/



package  
{
	//面倒なので一通りimportしてる
	import away3d.Away3D;
	import away3d.containers.*;
	import away3d.cameras.*;
	import away3d.cameras.lenses.*;
	import away3d.lights.*;
	import away3d.containers.*;
	import away3d.core.math.*;
	import away3d.core.managers.*;
	import away3d.materials.*;
	import away3d.materials.lightpickers.*;
	import away3d.materials.methods.*;
	import away3d.materials.utils.*;
	import away3d.loaders.*;
	import away3d.loaders.parsers.*;
	import away3d.events.*;
	import away3d.loaders.misc.*;
	import away3d.library.*;
	import away3d.library.assets.*;
	import away3d.library.utils.*;
	import away3d.events.*;
	import away3d.animators.*;
	import away3d.animators.data.*;
	import away3d.animators.nodes.*;
	import away3d.entities.*;
	import away3d.textures.*;
	import away3d.primitives.*;
	import com.adobe.utils.AGALMiniAssembler;

	import flash.display.*;
	import flash.display3D.*;
	import flash.display3D.textures.*;
	import flash.errors.*;
	import flash.events.*;
	import flash.net.*;
	import flash.geom.*;

	import mx.graphics.codec.*;


	[SWF(width="512", height="512", frameRate="60", backgroundColor="0xFFFFFF")]
	public class TexToTexTest extends Sprite
	{
		//==Embed==

		[Embed(source='test_grad_circle32.png')]
		 private static var Bitmap_Test: Class;


		//==Const==

		//実際の画面サイズ
		public function GetStageW():int{
			return stage.stageWidth;
//			return Capabilities.screenResolutionX;
		}
		public function GetStageH():int{
			return stage.stageHeight;
//			return Capabilities.screenResolutionY;
		}

		//テクスチャサイズ
		//- 実際の画面サイズよりも大きい2^nサイズ
		//- 起動直後に計算して設定
		static public var TEX_W:int;

		//ポリゴンサイズ
		//- 平行投影で画面の横サイズに合わせるので値の大きさにあまり意味はない(共通化することに意味がある)
		static public const GEOM_W:Number = 1;

		//元画像サイズ
		//- 本体から取るようにした方が良さそうだが今回はこれで
		static public const BMD_W:int = 32;


		//似非スーパーサンプリング用
		//- 頂点シェーダ
		private const SHADER_VERTEX:String = 
			"m44 op, va0, vc0	\n" +	//頂点座標 = 頂点座標×Matrix3D
			"mov v0, va1";              //V0 = UV
		//- ピクセルシェーダ
		private const SHADER_FRAGMENT:String =
			"tex ft0, v0, fs0 <2d,linear,clamp>	\n" +//ft0 = テクスチャ0[V0.x, V0.y]
			"mov oc, ft0";	//色 = ft0


		//==Var==

		//Away3D
		//- View
		private var m_View:View3D;
		//- View
		private var m_Scene:Scene3D;
		//- Camera
		private var m_Camera:Camera3D;

		//テクスチャ描画用
		private var m_TexToTex_Vertices:VertexBuffer3D;
		private var m_TexToTex_Indices:IndexBuffer3D;
		private var m_TexToTex_Program:Program3D;
		private var m_TexToTex_Matrix3D:Matrix3D;


		//==Function==

		///コンストラクタ
		public function TexToTexTest()
		{
			super();

			addEventListener(Event.ADDED_TO_STAGE, OnAddedToStage);
		}

		///Init
		private function OnAddedToStage(event:Event):void
		{
			//Calc Const Param
			{
				//TEX_W
				{
					for(TEX_W = BMD_W; TEX_W < GetStageW(); TEX_W *= 2){}
				}
			}

			//Away3D
			{
				//- View
				m_View = new View3D();
				{
					m_View.antiAlias = 4;
					addChild(m_View);
				}

				//- Scene
				m_Scene = m_View.scene;

				//- Camera
				m_Camera = m_View.camera;
				//- 最終表示の確認のため、カメラ位置の調整
				{
					m_Camera.x = 0;
					m_Camera.y = 100;
					m_Camera.z = 0;

					var cam_trg_pos:Vector3D = new Vector3D();
					m_Camera.lookAt(cam_trg_pos);
				}

				//- レンズ
				//-- 平行投影化
				{
					var lens:OrthographicLens = new OrthographicLens(GEOM_W);
					m_Camera.lens = lens;
				}
			}

			//Context3Dを使いたいので、残りはそれが生成されたあとに行う
			{
				m_View.stage3DProxy.addEventListener(Stage3DEvent.CONTEXT3D_CREATED, onContext3dCreated);
			}
		}

		private function onContext3dCreated(e:Event):void{
			var stage3DProxy:Stage3DProxy = m_View.stage3DProxy;
			var context : Context3D = stage3DProxy.context3D;

			//元画像のテクスチャ
			var tex_ori:BitmapTexture;
			{
				//Texture
				{
					var bmd_ori:BitmapData = new BitmapData(BMD_W, BMD_W, true, 0x00000000);

					//適当な画像を描画しておく
					bmd_ori.draw(new Bitmap_Test());

					tex_ori = new BitmapTexture(bmd_ori);
				}

				//こちらは最終的な描画には使わないのでGeometryなどの作成は行わずテクスチャの生成のみ
			}

			//描画確認用
			var tex:BitmapTexture;
			{
				//Geometry
				var geom:PlaneGeometry;
				{
					var segment_w:int = 1;
					var segment_h:int = 1;
					geom = new PlaneGeometry(GEOM_W, GEOM_W, segment_w, segment_h);
				}

				//Material
				var material:TextureMaterial;
				{
					//Texture
					{
						var bmd:BitmapData = new BitmapData(TEX_W, TEX_W, true, 0x00000000);

						tex = new BitmapTexture(bmd);
					}

					//smoothをオンにしてスーパーサンプリングっぽくする
					var smooth:Boolean = true;
					var repeat:Boolean = false;
					material = new TextureMaterial(tex, smooth, repeat);
				}

				//Mesh
				{
					var mesh:Mesh = new Mesh(geom, material);
					m_Scene.addChild(mesh);
				}
			}

			//テクスチャ描画用初期化
			{
				//頂点シェーダの作成
				var assembler_vertex:AGALMiniAssembler = new AGALMiniAssembler();
				assembler_vertex.assemble(Context3DProgramType.VERTEX, SHADER_VERTEX);
				//ピクセルシェーダの作成
				var assembler_fragment:AGALMiniAssembler = new AGALMiniAssembler();
				assembler_fragment.assemble(Context3DProgramType.FRAGMENT, SHADER_FRAGMENT);

				//シェーダをまとめるやつの作成
				m_TexToTex_Program = context.createProgram();
				m_TexToTex_Program.upload(assembler_vertex.agalcode, assembler_fragment.agalcode);

				//頂点情報
				var DATA_VERTEX:Vector.<Number> = Vector.<Number>([
					//X, Y, Z	U, V
					-1, -1, 0,	0, 1,
					-1,  1, 0,	0, 0,
					 1,  1, 0,	1, 0,
					 1, -1, 0,	1, 1,
				]);
				//頂点データ化
				//- 4行、5要素
				m_TexToTex_Vertices = context.createVertexBuffer(4, DATA_VERTEX.length/4);
				//- 4行のデータとして送る
				m_TexToTex_Vertices.uploadFromVector(DATA_VERTEX, 0, 4);

				//頂点Index情報
				var DATA_INDEX:Vector.<uint> = Vector.<uint>([
					0, 1, 2,
					2, 3, 0
				]);
				//頂点Indexデータ化
				m_TexToTex_Indices = context.createIndexBuffer(DATA_INDEX.length);
				m_TexToTex_Indices.uploadFromVector(DATA_INDEX, 0, DATA_INDEX.length);

				//Matrix3Dの生成
				m_TexToTex_Matrix3D = new Matrix3D();
			}

			//テクスチャからテクスチャへの描画
			{
				DrawTexToTex(
					tex_ori.getTextureForStage3D(stage3DProxy),//Src
					tex.getTextureForStage3D(stage3DProxy)//Dst
				);
			}

			//Update
			{
				addEventListener(Event.ENTER_FRAME, Update);
			}
		}

		///テクスチャからテクスチャへの描画
		public function DrawTexToTex(tex_src:TextureBase, tex_dst:TextureBase):void{
			var stage3DProxy:Stage3DProxy = m_View.stage3DProxy;
			var context : Context3D = stage3DProxy.context3D;

			//基本設定
			{
				//描画先を表示用テクスチャに切替
				context.setRenderToTexture(tex_dst, false, 0, 0);

				//透明でクリア
				context.clear(0, 0, 0, 0);

				//シェーダの設定
				context.setProgram(m_TexToTex_Program);

				//頂点データの設定
				//- Coord x 3
				context.setVertexBufferAt(0, m_TexToTex_Vertices, 0, Context3DVertexBufferFormat.FLOAT_3);
				//- UV x 2
				context.setVertexBufferAt(1, m_TexToTex_Vertices, 3, Context3DVertexBufferFormat.FLOAT_2);

				//テクスチャの設定
				stage3DProxy.setTextureAt(0, tex_src);

				//Matrix3D
				context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, m_TexToTex_Matrix3D);
			}

			//描画
			{
				//描画
				context.drawTriangles(m_TexToTex_Indices, 0, -1);
				//反映
				context.present();
			}

			//設定を戻す
			{
				stage3DProxy.setTextureAt(0, null);
				context.setVertexBufferAt(0, null);
				context.setVertexBufferAt(1, null);

				//presentで自動で戻るはずだが一応描画先を戻す
				context.setRenderToBackBuffer();
			}
		}

		///毎フレーム実行
		private function Update(event:Event):void
		{
			m_View.render();
		}
	}
}