Hello pot.
メタセコイアから書き出した.OBJ形式のファイルを超ざっくり読み込んでパースし、表示してみました。
とりあえずライティングは頂点シェーダー側ですべて行っています。
/**
* Copyright ume ( http://wonderfl.net/user/ume )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/jf5p
*/
package {
import net.hires.debug.Stats;
import flash.display.Sprite;
import flash.text.TextField;
import com.adobe.utils.*;
import flash.utils.getTimer;
import flash.events.*;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.display3D.*;
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
[SWF(width="465", height="465", frameRate="60")]
public class HelloPot extends Sprite{
//3Dコンテクスト
private var context3D:Context3D;
private var program:Program3D;
//頂点・法線・インデックス
private var vertexbuffer:VertexBuffer3D;
private var normalbuffer:VertexBuffer3D;
private var indexBuffer:IndexBuffer3D;
//WVP
private var constMatrix:Matrix3D;
private var modelMatrix:Matrix3D;
private var cameraMatrix:Camera;
private var perspective:PerspectiveMatrix3D;
//もろもろ
private var movieWidth:Number = 465;
private var movieHeight:Number = 465;
private var msg:TextField;
private var canRender:Boolean;
private var objData:OBJParser;
public function HelloPot() {
//スタッツ等配置
var stats:Stats = new Stats();
stage.addChild(stats);
msg = createTF();
addChild(msg);
//サイズ固定
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.align = StageAlign.TOP_LEFT;
//レンダリングフラグ初期化
canRender = false;
//3D準備
stage.stage3Ds[0].addEventListener( Event.CONTEXT3D_CREATE, onContext );
stage.stage3Ds[0].addEventListener( ErrorEvent.ERROR, onErrContext );
stage.stage3Ds[0].requestContext3D();
}
private function onErrContext(e:ErrorEvent):void{
msg.text = "err:" + e.text;
}
//テキストボックス準備
private function createTF():TextField{
var msg:TextField = new TextField();
msg.multiline = true;
msg.width = 150;
msg.height = 100;
msg.border = true;
msg.x = stage.stageWidth - msg.width - 5;
msg.y = stage.stageHeight - msg.height - 5;
msg.text = "Hello";
return msg
}
//モデル読み込み完了時イベントハンドラメソッド
private function onComp():void{
msg.text = "OBJ読み込み完了";
//頂点情報セット
setBuffer();
msg.text = "頂点セット完了";
//プログラムセット
setProgram();
msg.text = "プログラムセット完了";
//マトリクス初期化
constMatrix = new Matrix3D();
modelMatrix = new Matrix3D();
cameraMatrix=new Camera();
cameraMatrix.InitView(new Vector3D(0,5,10), new Vector3D(0,0,0), Vector3D.Y_AXIS);
perspective = new PerspectiveMatrix3D();
perspective.perspectiveFieldOfViewRH(45*Math.PI/180, movieWidth / movieHeight, 0.1, 1000.0);
msg.text = "モデル読み込み完了\nポリゴン数:" + objData._rawIndexBuffer.length/3 + "\n頂点数:" + objData._rawVertexBuffer.length/3 + "\n法線数:" + objData._rawNormalBuffer.length/3 + "\n頂点共有してません><;"
this.canRender = true;
//OBJ解放
objData.dispose();
objData = null;
}
// コンテキスト準備完了時
private function onContext(e:Event):void{
msg.text = "コンテクスト準備完了";
context3D = stage.stage3Ds[0].context3D;
context3D.configureBackBuffer(stage.stageWidth, stage.stageHeight, 1, true);
addEventListener(Event.ENTER_FRAME, onRender);
//モデルデータ読み込み
try{
objData = new OBJParser("http://ciruelo.jp/assets/teapot.obj",0xFFFFFF,0.01);
//objData = new OBJParser("teapot.obj",0xFFFFFF,0.01);
msg.text = "OBJ読み込開始";
}catch(e:Error){
msg.text = "@エラー:" + e.message;
}
objData.compFunc = onComp;
}
//プログラムセット
private function setProgram():void{
//頂点シェーダーを生成 今回は頂点側でライティング計算まで行う
var vertexShaderAssembler : AGALMiniAssembler = new AGALMiniAssembler(true);
vertexShaderAssembler.assemble( Context3DProgramType.VERTEX,
//頂点出力
"m44 vt0, va0, vc0\n" +
"mov op, vt0\n" +
//ディフューズとアンビエント計算 最適化を考えずに地道に計算中
"m44 vt1, va1, vc4\n" +
"dp3 vt2.x vt1, vc8\n" +
"sat vt2.x, vt2.x\n" +
"add vt2.x, vt2.x, vc11.z\n" +
"mul vt2.xyz, vt2.xxx, vc9.xyz\n" +
"mov vt2.w vc9.w\n" +
//スペキュラ計算
"sub vt3, vc10, vt0\n" +
"nrm vt3.xyz, vt3\n" +
"mov vt3.w, vc9.w\n" +
"add vt3.xyz, vt3.xyz, vt1.xyz\n" +
"nrm vt3.xyz, vt3\n" +
"mov vt3.w, vc9.w\n" +
"dp3 vt4.x, vt3, vt1\n" +
"sat vt4.x, vt4.x\n" +
"pow vt4.x, vt4.x, vc11.y\n" +
"mul vt4.x, vt4.x, vc11.w\n" +
"mul vt5.xyz, vt4.xxx, vc12\n" +
//色を出力
"add vt2.xyz, vt2.xyz, vt5.xyz\n" +
"mov vt2.w vc9.w\n" +
"mov v0, vt2"
);
//フラグメントシェーダー(ピクセルシェーダー)今回はお仕事なっしん
var fragmentShaderAssembler : AGALMiniAssembler= new AGALMiniAssembler(true);
fragmentShaderAssembler.assemble( Context3DProgramType.FRAGMENT,
"mov oc, v0\n"
);
//プログラムを生成・セット
program = context3D.createProgram();
program.upload( vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode);
context3D.setProgram(program);
}
//頂点等セット
private function setBuffer():void{
//頂点・法線
var vertices:Vector.<Number> = objData._rawVertexBuffer;
var normals:Vector.<Number> = objData._rawNormalBuffer;
//頂点数・法線数・インデックス数を算出
var vertexLength:int = objData._rawVertexBuffer.length/3;
var normalLength:int = objData._rawNormalBuffer.length/3;
var indexLength:int = objData._rawIndexBuffer.length;
// 頂点バッファを作成・アップロード
vertexbuffer = context3D.createVertexBuffer(vertexLength, 3);
vertexbuffer.uploadFromVector(vertices, 0, vertexLength);
// 法線バッファを作成・アップロード
normalbuffer = context3D.createVertexBuffer(normalLength,3);
normalbuffer.uploadFromVector(normals, 0, normalLength);
// インデックスバッファを作成・アップロード
indexBuffer = context3D.createIndexBuffer(indexLength);
indexBuffer.uploadFromVector (objData._rawIndexBuffer, 0, indexLength);
// 頂点・対応する法線をレジスタva0・va1にセット
context3D.setVertexBufferAt(0, vertexbuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
context3D.setVertexBufferAt(1, normalbuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
}
// レンダリング
private function onRender(e:Event):void{
if ( !context3D ) return;
context3D.clear ( 0.3, 0.3, 0.3, 1 );
//変換マトリクス生成
//モデル回転
modelMatrix.identity()
modelMatrix.appendRotation(getTimer()/10,Vector3D.Y_AXIS);
constMatrix.identity();
constMatrix.append(modelMatrix);
constMatrix.append(cameraMatrix.matrix);
constMatrix.append(perspective);
constMatrix.appendTranslation(0,-2,0);
/* 頂点プログラムで使用する定数 */
//vc0-vc3:WVPマトリクス
context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0,constMatrix, true);
//vc4-vc7:ワールド座標変換用マトリクス
context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 4,modelMatrix, true);
//vc8:ライトの法線
context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 8,Vector.<Number>([0.5773502691896258, 0.5773502691896258, 0.5773502691896258, 1]));
//vc9:ベースカラー いわゆるマテリアルの色
context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX,9,Vector.<Number>([0.6,0,0,1]));
//vc10:カメラ座標 0,5,10固定で
context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX,10,Vector.<Number>([0, 5, 10, 1]));
//vc11:反射係数, power値
context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX,11,Vector.<Number>([1, 30, 0.25, 0.5]));
//vc12:スペキュラ色(白)
context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX,12,Vector.<Number>([1, 1, 1, 1]));
/* フラグメントプログラムで使用する定数 */
//今回はありませぬ。
//三角ポリゴン描画
context3D.drawTriangles(indexBuffer);
//取り出して画面に反映
context3D.present();
}
}
}
import flash.geom.Matrix3D;
import flash.geom.Vector3D;
/*
なんちゃってカメラ
*/
class Camera {
private var _view:Matrix3D;
public function Camera() {
_view = new Matrix3D();
}
//右手系でいわゆるlookat
public function InitView( from:Vector3D, at:Vector3D, up:Vector3D ) : void {
var vz:Vector3D = from.subtract( at );
vz.normalize();
var vx:Vector3D = up.crossProduct( vz );
vx.normalize();
var vy:Vector3D = vz.crossProduct( vx );
vy.normalize();
var vtx:Number = vx.dotProduct( from ) * -1;
var vty:Number = vy.dotProduct( from ) * -1;
var vtz:Number = vz.dotProduct( from ) * -1;
_view.identity();
_view.rawData = Vector.<Number>([
vx.x, vy.x, vz.x, 0,
vx.y, vy.y, vz.y, 0,
vx.z, vy.z, vz.z, 0,
vtx, vty, vtz, 1,
]);
}
public function get matrix():Matrix3D {
return _view;
}
}
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.events.Event;
import flash.events.SecurityErrorEvent;
import flash.events.IOErrorEvent;
import flash.system.Security;
/*
とりあえず、.objを読み込んでみて、頂点・法線・インデックスだけをパースするクラス
*/
class OBJParser{
private const LINE_FEED:String = String.fromCharCode(10);
private const SPACE:String = String.fromCharCode(32);
private const SLASH:String = "/";
private const VERTEX:String = "v";
private const NORMAL:String = "vn";
private const INDEX_DATA:String = "f";
private var _loader:URLLoader;
private var _color:uint;
//3種類の一時的バッファ
private var _vertexTemp:Array;
private var _normalTemp:Array;
private var _indexTemp:Array;
//3種類のアップロード用バッファ
public var _rawIndexBuffer:Vector.<uint> = new Vector.<uint>();
public var _rawVertexBuffer:Vector.<Number> = new Vector.<Number>();
public var _rawNormalBuffer:Vector.<Number> = new Vector.<Number>();
//スケール
private var _scale:Number;
//読み込み完了後のハンドラ用
public var compFunc:Function;
public function OBJParser(path:String, defaultColor:uint = 0xFFFFFF ,scale:Number = 0.01){
Security.loadPolicyFile("http://ciruelo.jp/assets/crossdomain.xml");
_color = defaultColor;
_scale = scale;
var req:URLRequest = new URLRequest(path);
_loader = new URLLoader(req);
_loader.addEventListener(Event.COMPLETE, _onLoadComp);
_loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR,_onErr);
}
//読み込みエラー
private function _onErr(e:SecurityErrorEvent):void{
var err:Error = e as Error;
throw err;
}
//読み込み完了
private function _onLoadComp(e:Event):void{
// 1行ごと取り出して読み込む
var data:String = String(_loader.data);
this._indexTemp = [];
this._vertexTemp = [];
this._normalTemp = [];
var lines:Array = data.split(LINE_FEED);
var loop:int = lines.length;
for (var i:int = 0; i < loop; ++i){
parseLine(lines[i]);
}
//バッファ用データに変換
_rawIndexBuffer = new Vector.<uint>();
_rawVertexBuffer = new Vector.<Number>();
_rawNormalBuffer = new Vector.<Number>();
parseBuffer();
//コールバック実行
compFunc();
}
//バッファ用データに変換
private function parseBuffer():void{
var vIndex:int = 0;
var nIndex:int = 0;
for(var i:int=0;i<this._indexTemp.length;i++){
//3つインデックスデータを取り出す
var poly1:Array = _indexTemp[i][0];
var poly2:Array = _indexTemp[i][1];
var poly3:Array = _indexTemp[i][2];
var v:int=3;
_rawVertexBuffer.push(
_vertexTemp[poly1[0]*v], _vertexTemp[poly1[0]*v+1], _vertexTemp[poly1[0]*v+2],
_vertexTemp[poly2[0]*v], _vertexTemp[poly2[0]*v+1], _vertexTemp[poly2[0]*v+2],
_vertexTemp[poly3[0]*v], _vertexTemp[poly3[0]*v+1], _vertexTemp[poly3[0]*v+2]
);
_rawNormalBuffer.push(
_normalTemp[poly1[2]*v], _normalTemp[poly1[2]*v+1], _normalTemp[poly1[2]*v+2],
_normalTemp[poly2[2]*v], _normalTemp[poly2[2]*v+1], _normalTemp[poly2[2]*v+2],
_normalTemp[poly3[2]*v], _normalTemp[poly3[2]*v+1], _normalTemp[poly3[2]*v+2]
);
//インデックスバッファ作成
_rawIndexBuffer.push(i*3, i*3+1, i*3+2);
}
}
//1行ごとの処理
private function parseLine(line:String):void{
// スペースごとに区切る
var words:Array = line.split(SPACE);
if (words.length > 0){
var data:Array = words.slice(1);
}else{
return;
}
var firstWord:String = words[0];
switch (firstWord){
case VERTEX :
parseVertex(data);
break;
case NORMAL :
parseNormal(data);
break;
case INDEX_DATA :
parseIndex(data);
break;
}
}
//頂点
private function parseVertex(data:Array):void{
var loop:int = 3;
for (var i:uint = 0; i < loop; i++){
var element:String = data[i];
_vertexTemp.push(Number(element)*_scale);
}
}
//法線
private function parseNormal(data:Array):void{
var loop:int = 3;
for (var i:uint = 0; i < loop; i++){
var element:String = data[i];
_normalTemp.push(Number(element));
}
}
//面
private function parseIndex(data:Array):void{
var poly:Array = [];
var loop:int = 3;
for(var i:int=0;i<loop;i++){
var v:Array = [];
var element:Array = data[i].split(SLASH);
for(var j:int=0;j<loop;j++){
//objの番号は、「1」から始まるのでひとつ減算
v.push(Number(element[j])-1)
}
poly.push(v);
}
_indexTemp.push(poly);
}
//データ解放
public function dispose():void{
_vertexTemp = _normalTemp = _indexTemp = null;
_rawVertexBuffer = _rawNormalBuffer = null;
_rawIndexBuffer = null;
}
}