基于flash的360虚拟现实引擎(cubicVR)源代码及其实现原理
flash的3D虚拟实境最早源自于德国的flash图形学大牛andre.michelle在其labsite:lab.andre-michelle.com上发表了一篇关于虚拟实境的文章,并提供了一个实例(NaN sourcecode).为了深入虚拟实境,在接下来的2个月里我陆续找到一些cubicVR的源文件并尝试破译了一些源码,我同时尝试开发了一个简单的flash3D引擎以及图片3D拉伸算法,所有这些的思想均来自于andre-michelle在其日志中提到的一些idea.现在在这篇文章中所用的实例依然是我对andre早期版本的整理,虽然目前我开发的新版本有更好的结构以及执行效率,但此版本的编程思想更通俗易懂。我想利用这个假期时间开发出flash球型虚拟实境,使它变得更真实。
在flash里部署3D虚拟实境无疑是一个革命性进步,我们再也不用去安装java虚拟机和QTVR了,事实上,在基于flash的可编程矢量图形界面基础上开发出来的虚拟现实可以通过创新的用户界面以达到完美的用户体验。city8.com既采用这种技术来部署他们的全景体验城市地图。
原理:
1.1点与投影
通过1点来产生远小近大的变化以产生空间感,在cubicVR中,设想观察者camera在一个正六面体中央,通过一点来计算每个面的在空间投影中的位置,以产生效果。
2.细分贴图
flash的matrix类只支持2D图形变换:切变、缩放、平面位移。位图无法实现3D变换,这与openGL不同,openGL只需要在定义多边型顶点和贴图法线,位图就会自动贴到一个面片上,而且整个贴图过程完全由GPU完成。而在flash中必须使用细分贴图的方法来欺骗人们的眼睛,既:将一张图片细分为n张小的三角面片,再将小三角面片进行平面切变,正因为如此,flash3D一旦涉及贴图必然导致大量占用cpu运算资源。
细分贴图的demo:http://godson./archives/2006/3708.shtml
3.消隐算法
如果没有消隐算法,那么观察者只能看到离屏幕最近的面,而不能看到整个3D空间,理论上消隐算法有3个步骤:1在一个六面体中,面法线与视锥体夹角大于180度则应设为不可见,2在同一个像素上,只显示与camera最近的多边形(Zbuffer算法),3投影在屏幕以外的多边型不可见,但这三种算法非常占用cpu资源,andrew设计了一种更简单的算法:1多边形的中心与camera重合,在camera坐标系中Z值<0的多边形不可见,2投影在屏幕以外的多边型不可见。 源代码:
- --------------------------------渲染部分------------------------------------
 - //读入位图数据,将其存放在数组中
 - var bitmapAry = new Array();
 - var bitmapSource = flash.display.BitmapData.loadBitmap("data2");
 - for (i=0; i<6; i++) {
 - var face = new flash.display.BitmapData(bitmapSource.width, bitmapSource.width, false);
 - face.copyPixels(bitmapSource, new flash.geom.Rectangle(0, bitmapSource.width*i, bitmapSource.width, bitmapSource.width*(i+1)), new flash.geom.Point(0, 0));
 - bitmapAry.push(face);
 - }
 - var showLine = true;
 - var inBitmapWidth = 1/bitmapAry[0].width;
 - var inBitmapHeight = 1/bitmapAry[0].height;
 - var bitmapWidth = bitmapAry[0].width;
 - var bitmapHeight = bitmapAry[0].height;
 - //鼠标按下时bitmap的变换距阵
 - this.bitmapMtrx = new flash.geom.Matrix();
 - var subBitmapMtix = new flash.geom.Matrix();
 - //舞台宽度变量申明
 - StWidth = Stage.width;
 - StHeight = Stage.height;
 - ox = StWidth/2;
 - oy = StHeight/2;
 - //焦距
 - focus = 300;
 - //摄影机角度变量
 - angU = 0;
 - angV = 0;
 - //细分段数
 - subdiv = 9;
 - subdivsubdivPic = subdiv*subdiv;
 - //
 - var subdivVy = new Array();
 - var subdivVx = new Array();
 - var subdivVz = new Array();
 - var subdivX = new Array();
 - var subdivY = new Array();
 - var subdivEnable = new Array();
 - var mtrx = new Array();
 - var mtrx2 = new Array();
 - //细分bitmap在map内部的X和Y
 - var subdivMapX = new Array();
 - var subdivMapY = new Array();
 - var subdivWidth = bitmapAry[0].width/(subdiv-1);
 - var subdivHeight = bitmapAry[0].height/(subdiv-1);
 - for (var i = 0; i<subdiv; i++) {
 - for (var j = 0; j<subdiv; j++) {
 - var subID = j+i*subdiv;
 - subdivMapX.push(j*subdivWidth);
 - subdivMapY.push(i*subdivHeight);
 - }
 - }
 - //
 - for (var i = 0; i<subdivPic; i++) {
 - mtrx.push(new flash.geom.Matrix());
 - mtrx2.push(new flash.geom.Matrix());
 - subdivVy.push(new Array(subdivPic));
 - subdivVx.push(new Array(subdivPic));
 - subdivVz.push(new Array(subdivPic));
 - subdivX.push(new Array(subdivPic));
 - subdivY.push(new Array(subdivPic));
 - subdivEnable.push(new Array(subdivPic));
 - }
 - // end of for
 - var mtrxSx = subdivWidth/bitmapWidth;
 - var mtrxSy = subdivHeight/bitmapHeight;
 - var mtrxSxy = subdivWidth/bitmapHeight;
 - var mtrxSyx = subdivHeight/bitmapWidth;
 - for (var i = 0; i<subdiv-1; ++i) {
 - for (var j = 0; j<subdiv-1; ++j) {
 - var subID = j+i*subdiv;
 - var mtrxTx = subdivMapX[subdiv*i+j];
 - var mtrxTy = subdivMapY[subdiv*i+j];
 - mtrx[subID].tx = mtrxTx;
 - mtrx[subID].ty = mtrxTy;
 - mtrx[subID].a = mtrxSx;
 - mtrx[subID].b = 0;
 - mtrx[subID].c = mtrxSxy;
 - mtrx[subID].d = mtrxSy;
 - mtrx[subID].invert();
 - mtrx2[subID].tx = mtrxTx;
 - mtrx2[subID].ty = mtrxTy;
 - mtrx2[subID].a = mtrxSx;
 - mtrx2[subID].b = mtrxSyx;
 - mtrx2[subID].c = 0;
 - mtrx2[subID].d = mtrxSy;
 - mtrx2[subID].invert();
 - }
 - }
 - //
 - mtrxmtrxA = mtrx[0].a;
 - mtrxmtrxB = mtrx[0].b;
 - mtrxmtrxC = mtrx[0].c;
 - mtrxmtrxD = mtrx[0].d;
 - mtrx2mtrx2A = mtrx2[0].a;
 - mtrx2mtrx2B = mtrx2[0].b;
 - mtrx2mtrx2C = mtrx2[0].c;
 - mtrx2mtrx2D = mtrx2[0].d;
 - // 初始每个bitmap细分结点在正则化3D视见体空间中的空间位置
 - function fun6(bitmapNo, new19, new20, new21, new22, new25, new23, new26, new24, new27) {
 - new15 = subdiv-1 >> 1;
 - for (var i = 0; i<subdiv; i++) {
 - for (var j = 0; j<subdiv; j++) {
 - var subID = j+i*subdiv;
 - subdivVy[bitmapNo][subID] = new19*new15+new22*(j-new15)+new25*(i-new15);
 - subdivVx[bitmapNo][subID] = new20*new15+new23*(j-new15)+new26*(i-new15);
 - subdivVz[bitmapNo][subID] = new21*new15+new24*(j-new15)+new27*(i-new15);
 - //trace(subdivVy[bitmapNo][subID]+"/"+subdivVx[bitmapNo][subID]+"/"+subdivVz[bitmapNo][subID]);
 - }
 - }
 - }
 - fun6(0, 1, 0, 0, 0, 0, -1, 0, 0, -1);
 - fun6(1, 0, -1, 0, -1, 0, 0, 0, 0, -1);
 - fun6(2, -1, 0, 0, 0, 0, 1, 0, 0, -1);
 - fun6(3, 0, 1, 0, 1, 0, 0, 0, 0, -1);
 - fun6(4, 0, 0, 1, 0, 1, -1, 0, 0, 0);
 - fun6(5, 0, 0, -1, 0, -1, -1, 0, 0, 0);
 - // 求每个subdiv的在屏幕坐标系的x和y将每个subdiv可见量存储于subdivEnable中
 - function fun7(bitmapNo) {
 - for (var i = 0; i<subdivPic; i++) {
 - //此为核心部分:细分贴图顶点在camera坐标空间中的坐标系变换运算 _loc5为z值,_loc5与_loc6的两个方程是对空间向量距阵计算的简化
 - var _loc6 = cos_angU*subdivVy[bitmapNo][i]+sin_angU*subdivVx[bitmapNo][i];
 - var _loc5 = cos_angV*_loc6+sin_angV*subdivVz[bitmapNo][i];
 - //如果顶点在视角前方则进行投影计算
 - if (_loc5>=0.1) {
 - var _loc7 = focus/_loc5;
 - //计算投影的x,y
 - subdivX[bitmapNo][i] = (sin_angU*subdivVy[bitmapNo][i]-cos_angU*subdivVx[bitmapNo][i])*_loc7+ox;
 - subdivY[bitmapNo][i] = (sin_angV*_loc6-cos_angV*subdivVz[bitmapNo][i])*_loc7+oy;
 - if (subdivX[bitmapNo][i]>0 and subdivX[bitmapNo][i]<StWidth and subdivY[bitmapNo][i]>0 and subdivY[bitmapNo][i]<StHeight) {
 - subdivEnable[bitmapNo][i] = 1;
 - } else {
 - subdivEnable[bitmapNo][i] = 0;
 - }
 - }
 - }
 - }
 - //
 - subdivsubdivV = subdiv-1;
 - function render() {
 - for (var i = 0; i<6; i++) {
 - fun7(i);
 - for (var j = 0; j<subdivV; ++j) {
 - for (var k = 0; k<subdivV; k++) {
 - var pointA = k+j*subdiv;
 - var pointC = k+(j+1)*subdiv;
 - var pointB = pointA+1;
 - var pointD = pointC+1;
 - var _loc2 = subdivEnable[i][pointA]+subdivEnable[i][pointD]+subdivEnable[i][pointB];
 - var _loc3 = subdivEnable[i][pointA]+subdivEnable[i][pointD]+subdivEnable[i][pointC];
 - //如果细分表面顶点有1个在屏幕内则渲染该面片
 - if (_loc2>0) {
 - //细分表面拉伸距阵计算
 - subBitmapMtix.a = mtrxA;
 - subBitmapMtix.b = mtrxB;
 - subBitmapMtix.c = mtrxC;
 - subBitmapMtix.d = mtrxD;
 - subBitmapMtix.tx = mtrx[pointA].tx;
 - subBitmapMtix.ty = mtrx[pointA].ty;
 - renderBitmap(i, subdivX[i][pointA], subdivY[i][pointA], subdivX[i][pointB], subdivY[i][pointB], subdivX[i][pointD], subdivY[i][pointD], subBitmapMtix);
 - }
 - //如果细分表面顶点有1个在屏幕内则渲染该面片
 - if (_loc3>0) {
 - //细分表面拉伸距阵计算
 - subBitmapMtix.a = mtrx2A;
 - subBitmapMtix.b = mtrx2B;
 - subBitmapMtix.c = mtrx2C;
 - subBitmapMtix.d = mtrx2D;
 - subBitmapMtix.tx = mtrx2[pointA].tx;
 - subBitmapMtix.ty = mtrx2[pointA].ty;
 - renderBitmap(i, subdivX[i][pointA], subdivY[i][pointA], subdivX[i][pointD], subdivY[i][pointD], subdivX[i][pointC], subdivY[i][pointC], subBitmapMtix);
 - }
 - }
 - }
 - }
 - }
 - //
 - renderBitmap = function (bitmapNo, point1X, point1Y, point2X, point2Y, point3X, point3Y, subBitmapMtix) {
 - //细分表面贴图距阵计算
 - this.bitmapMtrx.a = (point2X-point1X)*inBitmapWidth;
 - this.bitmapMtrx.b = (point2Y-point1Y)*inBitmapWidth;
 - this.bitmapMtrx.c = (point3X-point1X)*inBitmapHeight;
 - this.bitmapMtrx.d = (point3Y-point1Y)*inBitmapHeight;
 - this.bitmapMtrx.tx = point1X;
 - this.bitmapMtrx.ty = point1Y;
 - subBitmapMtix.concat(this.bitmapMtrx);
 - //贴图渲染
 - this.photo.beginBitmapFill(bitmapAry[bitmapNo], subBitmapMtix, false, false);
 - if (showLine == true) {
 - this.photo.lineStyle(1, 0x000000, 100);
 - }
 - this.photo.moveTo(point1X, point1Y);
 - this.photo.lineTo(point2X, point2Y);
 - this.photo.lineTo(point3X, point3Y);
 - this.photo.endFill();
 - };
 - //初始渲染
 - photo.clear();
 - cos_angU = Math.cos(angU);
 - sin_angU = Math.sin(angU);
 - cos_angV = Math.cos(angV);
 - sin_angV = Math.sin(angV);
 - render();
 - --------------------------------------鼠标控制部分---------------------------------
 - var myMouseListener = new Object();
 - var myKeyListener = new Object();
 - this.createEmptyMovieClip("photo", this.getNextHighestDepth());
 - this.createEmptyMovieClip("win", this.getNextHighestDepth());
 - lable.swapDepths(photo);
 - win.lineStyle(2, 0x000000, 100);
 - win.moveTo(0, 0);
 - win.lineTo(Stage.width, 0);
 - win.lineTo(Stage.width, Stage.height);
 - win.lineTo(0, Stage.height);
 - win.lineTo(0, 0);
 - myMouseListener.onMouseDown = function() {
 - onEnterFrame = function () {
 - var xWidth = _xmouse-dx;
 - var yWidth = _ymouse-dy;
 - if (xWidth != 0 || yWidth != 0) {
 - photo.clear();
 - angUangU = angU-xWidth*0.001;
 - angVangV = angV-yWidth*0.001;
 - cos_angU = Math.cos(angU);
 - sin_angU = Math.sin(angU);
 - cos_angV = Math.cos(angV);
 - sin_angV = Math.sin(angV);
 - render();
 - }
 - };
 - dx = _xmouse;
 - dy = _ymouse;
 - };
 - myMouseListener.onMouseUp = function() {
 - onEnterFrame = undefined;
 - photo.clear();
 - render();
 - };
 - myMouseListener.onMouseWheel = function(wheelMove) {
 - i = 0;
 - focusfocus = focus+wheelMove*20;
 - if (focus<180) {
 - focus = 180;
 - }
 - photo.clear();
 - render();
 - };
 - Mouse.addListener(myMouseListener);