いよいよ頂点制御。手さぐり勝手に意味を推測するのは長かった。
ネットで調べててよくDirectXやOpenGL関連のページによくたどりついたからそのへんの設計もってきてるんだろうね。
ていうかWindowsではDirectX9、MacとLinuxではOpenGL1.3で動くんだっけ。そりゃ当然だ。
これらをやってた人にはスラスラ入ってきて有利なんだろうな。
使うのはこないだ作ったPlane。どの頂点がどこにあるのかわかってるからね。
package { import alternativa.engine3d.core.Camera3D; import alternativa.engine3d.core.Object3D; import alternativa.engine3d.core.Resource; import alternativa.engine3d.core.VertexAttributes; import alternativa.engine3d.core.View; import alternativa.engine3d.materials.TextureMaterial; import alternativa.engine3d.resources.BitmapTextureResource; import alternativa.engine3d.resources.Geometry; import flash.display.Sprite; import flash.display.Stage3D; import flash.events.Event; /** * ... * @author */ public class Main extends Sprite { [Embed(source="texture.jpg")] static private const EmbedTexture:Class; private var camera:Camera3D; private var scene:Object3D; private var stage3D:Stage3D; // private var plane:Plane; private var count:int = 0; private var rad:Number = Math.PI / 180; private var vertex:Vector.<Number>; public function Main(){ stage.frameRate = 60; //create 3D world camera = new Camera3D(0.1, 10000); camera.view = new View(stage.stageWidth, stage.stageHeight, false, 0, 0, 4); camera.view.hideLogo(); addChild(camera.view); addChild(camera.diagram); scene = new Object3D(); scene.addChild(camera); new RoundCameraController(camera, stage); //create plane plane = new Plane(200, 200, 200, 1, true); plane.setMaterialToAllSurfaces(new TextureMaterial(new BitmapTextureResource(new EmbedTexture().bitmapData))); scene.addChild(plane); plane.rotationX = -90 * rad; //preset uv vector and its vertex buffer var uv:Vector.<Number> = plane.geometry.getAttributeValues(VertexAttributes.TEXCOORDS[0]); var numVertices:uint = plane.geometry.numVertices; vertex = new Vector.<Number>(numVertices * 5); for (var i:int = 0; i < numVertices; i++){ vertex[uint(i * 5 + 3)] = uv[uint(i * 2)]; vertex[uint(i * 5 + 4)] = uv[uint(i * 2 + 1)]; } //create Context3D stage3D = stage.stage3Ds[0]; stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreate); stage3D.requestContext3D(); } private function onContextCreate(e:Event):void { for each (var resource:Resource in scene.getResources(true)){ resource.upload(stage3D.context3D); } stage.addEventListener(Event.ENTER_FRAME, onRender); } private function onRender(e:Event):void { camera.render(stage3D); //vertex controll var geo:Geometry = plane.geometry; var pos:Vector.<Number> = geo.getAttributeValues(VertexAttributes.POSITION); var numVertex:uint = geo.numVertices; var theta:Number = count * 10 * rad; for (var i:int = 0; i < numVertex; i++){ pos[uint(i * 3 + 2)] = 20 * Math.sin(theta + (i / 2 >> 0) * 4 * rad); } if (0){ //upload way 1 geo.setAttributeValues(VertexAttributes.POSITION, pos); geo.upload(stage3D.context3D); } else { //upload way 2 for (var j:int = 0; j < numVertex; j++){ vertex[uint(j * 5)] = pos[uint(j * 3)]; vertex[uint(j * 5 + 1)] = pos[uint(j * 3 + 1)]; vertex[uint(j * 5 + 2)] = pos[uint(j * 3 + 2)]; } geo.updateVertexBufferInContextFromVector(0, vertex, 0, numVertex); } count++; } } }
最初はいつも通りの3Dの初期化。とばします。
RoundCameraControllerは原点を中心にマウスドラッグでぐるぐる回り、マウスホイールで距離を変えるカメラコントロールの自作クラスなので気にしない。
いちいち毎回カメラ操作書くのめんどいしいろんな角度から見れて便利だから使ってる。そのうち解説するかも。
//create plane
plane = new Plane(200, 200, 200, 1, true);
plane.setMaterialToAllSurfaces(new TextureMaterial(new BitmapTextureResource(new EmbedTexture().bitmapData)));
scene.addChild(plane);
plane.rotationY = 90 * rad;
まずはPlaneを作る。200*200で縦の分割数が1、横の分割数が200。頂点は合計402、ポリゴン数は両面なので800だ。
Alternativa3Dのrotationの指定方法は角度でなくラジアンなので注意。 radは先に計算しといたπ/180のこと。
この横に分割数の多いPlaneを旗のようにはためかせることにする。
//preset uv vector and its vertex buffer
var uv:Vector.<Number> = plane.geometry.getAttributeValues(VertexAttributes.TEXCOORDS[0]);
var numVertices:uint = plane.geometry.numVertices;
vertex = new Vector.<Number>(numVertices * 5);
for (var i:int = 0; i < numVertices; i++){
vertex[uint(i * 5 + 3)] = uv[uint(i * 2)];
vertex[uint(i * 5 + 4)] = uv[uint(i * 2 + 1)];
}
今回2通りの方法で頂点を制御しようと思う(と言っても途中までは一緒で転送方法が違うだけ)。
vertex streamをアップロードするupdateVertexBufferInContextFromVector()と、Geometryの情報を全部アップロードするupload()だ。
その1つ目の方法updateVertexBufferInContextFromVector()だが、このPlaneはvertex stream 0にxyz座標(VertexAttributes.POSITION)もuv座標(VertexAttributes.TEXCOORDS[0])も入っているため一緒にアップロードしなくてはならない。
けどuv座標は変えないので毎ステップ取得してvertex bufferに設定するのも無駄そうなので先に準備しとく。
まずattributes識別番号から対応したvertex buffer情報を得るgetAttributeValues()を使ってuv座標のVectorをもらっておく。
そしてvertex bufferに直接送るVectorであるvertexを作る。このvertexはイベントハンドラで使うからインスタンス変数にしとく。
vertexの長さは頂点数*attributes数だ。
頂点数はPlaneのGeometry.numVerticesで取得。
attributes数もgetVertexStreamAttributes(0).length(vertex stream 0のattributesをArrayでもらい、その長さ)でとれるけどまあxyz+uvの5だからそのまま5って書いといた。
このvertex streamには1つの頂点につき[x,y,z,u,v]が繰り返しセットされている。
だからこっちが作ってアップデートするvertexベクターも同じ順番でセットしなくてはならない。
uvベクターの値を順番に各頂点の(0から数えて)3番目と4番目にセットする。そんなコード。
コンストラクタの最後はいつも通りContex3Dの要求。CONTEXT3D_CREATEイベントハンドラもいつもどおりなので説明省略。
//vertex controll
var geo:Geometry = plane.geometry;
var pos:Vector.<Number> = geo.getAttributeValues(VertexAttributes.POSITION);
var numVertex:uint = geo.numVertices;
var theta:Number = count * 10 * rad;
for (var i:int = 0; i < numVertex; i++){
pos[uint(i * 3 + 2)] = 20 * Math.sin(theta + (i / 2 >> 0) * 4 * rad);
}
レンダリングした後(前のほうが普通かも)にまずPlaneのGeometryを取得し、そのvertex bufferからPOSITION情報をVectorで取得。このposをいじる。
countは0から始まり1フレームごとにcount++しておくカウンター。
thetaはsinに入れる角度だ。このthetaの増大が大きいほど振動の周期は短くなる。
今回は1フレームに10°進むことにした。
次にすべての頂点についてz座標を設定するが、旗のようにはためかせるには同じ列の頂点は同じ動きをしなくてはならない。
また、はためきが伝播するように見せるには角度の増え方は同じだが位相をずらさなくてはならない。Asin(ωt+α)のαをPlaneの左端を0とし、右に行くにつれて少しずつ増やしていく。
Planeを書くときに考えたように、こういった場合はoffset * (i / numH >> 0)とすれば、縦の列が同じ場合はiによらず同じ値になり、かつ列が変わるごとにoffsetぶんだけずれた値が得られる。
今回は縦の頂点数は2なので(i / 2 >> 0) * 4 * rad。列が変わるごとに4°ずれる。
横の頂点数は201なので、左端から右端にかけて800°振動が遅れて伝わる。二週ちょい。ちょうどいいかんじに旗っぽくなるはず。
今回のはためきのパラメータは振幅は適当に20。1フレームに10°進み、位相の遅れは1列につき4°だ。
posは[x,y,z]の繰り返しであり、zだけ上のように動かしたいので(0から数えて)2番目の要素に値を代入する。x,yはそのまま。
さて、実はこれで頂点制御の部分はおしまい。意外とあっさり。あとはいかに更新するかだけれど、2つの方法の1つめから見ていこう。
//upload way 1
geo.setAttributeValues(VertexAttributes.POSITION, pos);
geo.upload(stage3D.context3D);
getAttributeValues()ではVectorをもらうだけでそのVectorを書き換えただけではGPUのvertex bufferもvertex streamも更新されない。
まずはPlaneの時にしたようにsetAttributeValues()でPOSITIONのattributesに更新したVectorをセットする。
そしてそれでもGPUのvertex bufferは更新されないのでアップロードが必要だ。
Geometry.upload()でGeometryのResource(vertex bufferとindex buffer)をアップロードする。
でもindex bufferなんて動かさないしこれって無駄っぽいよね...
そこで2つめ。
//upload way 2
for (var j:int = 0; j < numVertex; j++){
vertex[uint(j * 5)] = pos[uint(j * 3)];
vertex[uint(j * 5 + 1)] = pos[uint(j * 3 + 1)];
vertex[uint(j * 5 + 2)] = pos[uint(j * 3 + 2)];
}
geo.updateVertexBufferInContextFromVector(0, vertex, 0, numVertex);
最初に作っておいたvertexベクターを使おう。
今このVectorにはuv座標だけセットしてある。
あとは更新したposの情報を加えてvertex stream 0として転送できる状態にしよう。
前述のとおりvertex stream 0は[x,y,z,u,v]なので0、1、2番目にxyzをセットしていく。
よく考えたら更新したのはzだけだからxとyもあらかじめセットしておいたほうがよかったかな
と思ったけどPlaneを動かす場合xyz座標は毎フレーム取得しなきゃいけないだろうからやっぱこれでいいかな。
Planeを動かさない自信があるならzだけでいいかも。
こうしてできあがったvertexベクターをupdateVertexBufferInContextFromVector()で転送しよう。
stream番号は0、Vectorはvertex、GPU上のvertex bufferの頂点0番目から全頂点ぶん更新するから引数は上記のようになる。
半分だけでよかったら(0, vertex, 0, numVertex/2)とすれば左半分だけ動くさまが見れるはず。
最後にcount++しておしまい。
Geometryごとuploadする方法は簡単だけど転送量多そうだ。
vertex streamで転送する方法はVectorを成型するのがめんどい。けどたぶんこっちのが速いんだろうな
uvとxyzでstream分ければ簡単になりそう。
Author:9ballsyn
ActionScriptについて
最近はMolehill