前回はStage3Dで実際にswf描写できる、おそらくもっとも簡単な例について解説した。
各頂点それぞれに色情報を設定しておくことで、変換行列でスクリーンに投影したときに三角形の中の各ピクセルの色が各頂点の色を補間した形で設定されていた。
今度は色情報ではなく、uv座標を設定することでテクスチャを貼り付けてみよう。
demo ( 要 Flash Player 11 )
package { import com.adobe.utils.AGALMiniAssembler; import flash.display.Sprite; import flash.display.Stage3D; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.display3D.Context3D; import flash.display3D.Context3DProgramType; import flash.display3D.Context3DRenderMode; import flash.display3D.Context3DTextureFormat; import flash.display3D.Context3DVertexBufferFormat; import flash.display3D.IndexBuffer3D; import flash.display3D.Program3D; import flash.display3D.textures.Texture; import flash.display3D.VertexBuffer3D; import flash.events.Event; import flash.geom.Matrix3D; import flash.geom.Vector3D; /** * ... * @author */ [SWF(width="512",height="512")] public class Main extends Sprite { [Embed(source="fl.png")] static private const EmbedTexture:Class; // private var stage3D:Stage3D; private var context3D:Context3D; private var indexBuffer:IndexBuffer3D; private var mtx:Matrix3D; private var axis:Vector3D; private var point:Vector3D; public function Main():void { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; // stage3D = stage.stage3Ds[0]; stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreate); stage3D.requestContext3D(Context3DRenderMode.AUTO); } private function onContextCreate(e:Event):void { context3D = stage3D.context3D; context3D.enableErrorChecking = true; context3D.configureBackBuffer(512, 512, 0, true); // //vertex buffer var vertexBuffer:VertexBuffer3D = context3D.createVertexBuffer(4, 5); context3D.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); context3D.setVertexBufferAt(1, vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_2); vertexBuffer.uploadFromVector(Vector.<Number>([-0.5, -0.5, 0, 0, 1, -0.5, 0.5, 0, 0, 0, 0.5, -0.5, 0, 1, 1, 0.5, 0.5, 0, 1, 0]), 0, 4); //index buffer indexBuffer = context3D.createIndexBuffer(6); indexBuffer.uploadFromVector(Vector.<uint>([0, 1, 2, 1, 2, 3]), 0, 6); // //constant mtx = new Matrix3D(); axis = new Vector3D(0, 0, 1); point = new Vector3D(0, 0, 0); // //texture var texture:Texture = context3D.createTexture(256, 256, Context3DTextureFormat.BGRA, false); texture.uploadFromBitmapData(new EmbedTexture().bitmapData); context3D.setTextureAt(0, texture); // //vertex shader var vertexShader:AGALMiniAssembler = new AGALMiniAssembler(); vertexShader.assemble(Context3DProgramType.VERTEX, "m44 op, va0, vc0 \n" + "mov v0, va1\n"); //fragment shader var fragmentShader:AGALMiniAssembler = new AGALMiniAssembler(); fragmentShader.assemble(Context3DProgramType.FRAGMENT, "mov ft0 v0\n" + "tex ft0, ft0, fs0<2d,clamp,linear>\n" + "mov oc ft0\n"); // var program:Program3D = context3D.createProgram(); program.upload(vertexShader.agalcode, fragmentShader.agalcode); context3D.setProgram(program); // addEventListener(Event.ENTER_FRAME, onEnter); } private function onEnter(e:Event):void { mtx.appendRotation(4, axis, point); context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mtx, false); // context3D.clear(0, 0, 0, 1); context3D.drawTriangles(indexBuffer); context3D.present(); } } }
stage3D = stage.stage3Ds[0]; stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreate); stage3D.requestContext3D(Context3DRenderMode.AUTO);
まずはstageからStage3Dを1つ取得する。
このStage3Dに対してCONTEXT3D_CREATEイベントを登録しておいて、requestContext3D()メソッドでContext3Dオブジェクトを要求する。
context3D = stage3D.context3D; context3D.enableErrorChecking = true; context3D.configureBackBuffer(512, 512, 0, true);
Context3Dのエラーチェックをtrueにして(好み?)、バックバッファのサイズを設定する。
//vertex buffer
var vertexBuffer:VertexBuffer3D = context3D.createVertexBuffer(4, 5);
context3D.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
context3D.setVertexBufferAt(1, vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_2);
vertexBuffer.uploadFromVector(Vector.<Number>([-0.5, -0.5, 0, 0, 1, -0.5, 0.5, 0, 0, 0, 0.5, -0.5, 0, 1, 1, 0.5, 0.5, 0, 1, 0]), 0, 4);
まずは頂点を設定しよう。
Context3D.createVertexBuffer()メソッドでvertex bufferを作ってもらう。
頂点は4つで、頂点のもつ情報は(x,y,z)と(u,v)の5つだ。
そして1つめのattributeを設定する。(x,y,z)なのでFLOAT_3型で、第一引数が0なのでva0に入る。
2つめのattributeは(u,v)なのでFLOAT_2型で、第一引数が1なのでva1に入る。すでに3つ使っているので、bufferOffsetは3だ。
次に実際の頂点情報をvertex bufferにuploadする。
(-0.5, -0.5, 0) , (0, 1)
(-0.5, 0.5, 0) , (0, 0)
(0.5, -0.5, 0) , (1, 1)
(0.5, 0.5, 0) , (1, 0)
を設定してある。
テクスチャの左上が(0, 0)で、xyz座標とは異なるので注意。
//index buffer indexBuffer = context3D.createIndexBuffer(6); indexBuffer.uploadFromVector(Vector.<uint>([0, 1, 2, 1, 2, 3]), 0, 6);
同じようにindex bufferを作ってもらう。
正方形を1つ作るので三角形ポリゴンは2枚。よって結ばれる頂点は6つだ。
vertex bufferで設定した頂点をもとに(0→1→2)、(1→2→3)と結ぶ。
//constant mtx = new Matrix3D(); axis = new Vector3D(0, 0, 1); point = new Vector3D(0, 0, 0);
constantレジスタvc用の変換行列を作る。
今回、z軸に沿って回転させるようにしたいので、appendRotation()メソッドで使う回転軸と回転の中心座標を作っておく。
この変換行列を使うためにはsetProgramConstantsFromMatrix()メソッドを使ってセットしなくてはならないが、この操作は変換行列を更新するたびに行わなくては変換行列の変更が反映されない。
ENTER_FRAMEイベントで行列を回転させるのでsetProgramConstantsFromMatrix()メソッドもその時に行おう。
//texture var texture:Texture = context3D.createTexture(256, 256, Context3DTextureFormat.BGRA, false); texture.uploadFromBitmapData(new EmbedTexture().bitmapData); context3D.setTextureAt(0, texture);
テクスチャを作る。
テクスチャもContext3Dオブジェクトに作ってもらわなくてはならない。この際テクスチャのサイズと種類、このテクスチャにレンダリング結果を貼り付けることがあるかどうかを指定する。
テクスチャの種類はBGRAでいいだろう。おそらくCOMPRESSEDはATFフォーマット用だろう。
BitmapDataからテクスチャにアップロードする。BitmapDataを更新したらこの処理を行わなければ実際のテクスチャには反映されない。
fragment shader内で使うtexture samplerレジスタにテクスチャを設定する。この場合ではfs0にテクスチャにアクセスできるようになる。
//vertex shader var vertexShader:AGALMiniAssembler = new AGALMiniAssembler(); vertexShader.assemble(Context3DProgramType.VERTEX, "m44 op, va0, vc0 \n" + "mov v0, va1\n");
vertex shaderをAGALで書く。
va0レジスタ(xyz座標)をvc0レジスタの行列に4*4行列積演算を行い、出力する
va1レジスタ(uv座標)をv0レジスタに転送する
という処理だ。2番目の処理でv0レジスタには各ピクセルについて頂点データから補間されたuv座標が入る。
//fragment shader var fragmentShader:AGALMiniAssembler = new AGALMiniAssembler(); fragmentShader.assemble(Context3DProgramType.FRAGMENT, "mov ft0 v0\n" + "tex ft0, ft0, fs0<2d,clamp,linear>\n" + "mov oc ft0\n");
次にfragment shaderだ。
まずft0レジスタにv0レジスタの値(各ピクセルについて補間されたuv座標)を入れる
ft0レジスタのuv座標に基づいてfs0レジスタのテクスチャの色をft0レジスタに入れる
ft0レジスタの値(RGBA)を出力する
という処理になっている。
texオペコードの使い方は少々特殊で、
tex 0x28 texture sample (fragment shader only)
destination = load from texture source2 at coordinates source1. In this source2 must be in sampler
format.
と書いてある。source1の座標をもとにsource2のテクスチャから色を読み込んでdestinationに入れる。この際source2はサンプラのフォーマットでなくてはならない
* Assembly code for Sampler looks like this:
fs0 <2d,linear,nomip>
The options that can be written inside the brackets are:
2d, 3d, cube...texture dimension
nomip, mipnone, mipnearest...mip mapping
nearest, linear...texture filtering
repeat, wrap, clamp...texture repeat
このようにfs?<option,...>のフォーマットで指定する。テクスチャ次元、ミップマッピング、テクスチャ補間、繰り返し、が指定出来る。
今回は2Dテクスチャで繰り返しなし、線形補間を行う。
var program:Program3D = context3D.createProgram(); program.upload(vertexShader.agalcode, fragmentShader.agalcode); context3D.setProgram(program); // addEventListener(Event.ENTER_FRAME, onEnter);
最後にProgram3Dを作り、vertex shaderとfragment shaderのバイトコードをアップロードする。
そしてsetProgram()で次に使うシェーダプログラムとして登録する。
このsetProgram()を行うことでシェーダプログラムを差し替えることもできる。
準備ができたのでENTER_FRAMEイベントで毎フレーム描画していこう。
mtx.appendRotation(4, axis, point); context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, mtx, false); // context3D.clear(0, 0, 0, 1); context3D.drawTriangles(indexBuffer); context3D.present();
まず、枚フレーム回転させたいので、変換行列を回転させる。回転軸と回転の中心座標は先ほど設定してある。
そして変更した変換行列をvc0にsetProgramConstantsFromMatrix()メソッドで設定する。
これで毎フレーム少しづつ回転角度が変わる変換行列を使うことになる。
最後に描画処理を行う。
まずバッファを黒でクリアし、index bufferを引数にdrawTriangles()メソッドで実際の描画命令を行う。
これでバックバッファに描画がされたのでpresent()メソッドでバックバッファと画面を同期させる。
以上でテクスチャの貼られた、z軸を中心に回転する正方形ができるだろう。
テクスチャの利用は3Dの描画でもとても大事なことなのでActionScript側の準備とAGAL言語側の処理をしっかりと頭に入れておこう。
Author:9ballsyn
ActionScriptについて
最近はMolehill