Link

首页

Tweet

书单

about me

Command

Category

stage3D之简单的三棱锥


       前面两篇很多朋友说很迫切的看一下具体的代码,总是觉得前面两篇写到不痛不痒。其实在一开始写这套系列教程的时候我就刻意减低每篇文章的关联性,目的就是为了让大家节约时间。已经了解的会的部分可以直接忽略了。不了解或者感兴趣的部分可以拿过来直接学习。不过,作为一名技术人员,我们希望每篇文章都有代码,或多或少,或深或浅。这样大家会觉得有所收获。这也是跟朋友聊天时发现的,国内的技术人员很喜欢看代码,而老外则喜欢从理论开始将其,由浅入深。可能这样也导致我们在技术上理论和实践往往会脱节的原因。

      废话不说,下面带着代码来制作一个非常简单的DEMO,这篇文章的重要性我觉得可以划分为A级别了。因为这篇文章的内容将带你第一次全面了解stage3D的编码流程及其各个代码之间的关联性和意义。

      在本文章中出现的DEMO,引用了《stage3D之启动GPU加速》一问中的代码,所以大家如果要跟着教程编写code的话还要看一下这篇文章。

      当我们的stage3D功能启动后我们就可以使用Context3D这个万能王来进行3D内容编程了。但是请大家明确一点,虽然我们有了GPU的给力支持,但是它依然不能理解三维环境中的任何事情。它唯一能明白的就是二进制字节。所以,在任何编程之前我们需要定义自己的规则,定义自己的数据含义。首先第一点就是将我们三维物体的一些数据告知GPU,这些数据你可以自己在笔记本上计算得出,也可以通过一些3D软件来生成对应的数据。当然,一开始学习由于3D模型不是很复杂,我还是推荐大家用第一种方法,这样方便与理解。

      在stage3D中我们需要向GPU传递模型的顶点信息,这些顶点信息其实就是3D舞台的每个定点的x,y,z坐标值。其实大家不难理解,在GPU计算的时候,我们如何让机器能够理解我们的物体呢,我们很难用一些数字来表示一个物体的面,所以我们只能选取物体中的一些标志性点坐标来记性描述,在物体绘制的时候首先在点与点之间绘制直线,然后绘制出面。当我们所有面全部绘制完成后,我们可以根据他们的Z轴位置来判断哪些面在前面,哪些面在后面,从而得到正确的3D视角图像。

      好了,我们来看一下代码,这段代码实现了一个简单三棱锥,并且在程序运行的时候进行绕轴旋转。第一步我们需要建立一个三棱锥的类,这个类中存在我们最关键的代码,当然我们首先要进行对象定义,代码如下:

/*------ property ------*/
        private var _sceneWidth:uint = 512;
        private var _sceneHeight:uint = 512;
        private var _indexBuffer:IndexBuffer3D;
        private var _vertexBuffer:VertexBuffer3D;
        private var _program:Program3D;
        private var _context3D:Context3D;
        private var projection:PerspectiveMatrix3D = new PerspectiveMatrix3D();
        private var model:Matrix3D = new Matrix3D();
        private var view:Matrix3D = new Matrix3D();
        private var finalTransform:Matrix3D = new Matrix3D();
        private const pivot:Vector3D = new Vector3D();
        private var _projection:PerspectiveMatrix3D;

       这里是一些硬性数据,我们可以单独把它们看做是我们程序中必要的对象,而三维对象的数据在下面定义。

//indexBuffer3D数据
        private var _indexBufferData:Vector.<uint> = Vector.<uint>(
                    [
                        //正面
                        0,1,2, 
                        //右面
                        3,4,5,   
                        //背面
                        8,7,6,  
                        //底面
                        9,10,11
                        
                    ]
                    );

      在stage3D中定点坐标的顺序是一组非常总要的数据,因为这些数据将会告知GPU以何种顺序来逐步渲染我们的定点,对于这些定点index数据我们需要尤为注意的是,如果你某一组数据顺序出现错误,那么在stage3D画面生成的时候将会产生坡面的现象。

      下面我们来定义一个定点数据,那么这组数据是从何而来的呢?别着急,想让大家来看一张图片你就会明白数据中每个数据的含义了。

点击查看原图 

      关于这幅图片我九不多结束了,简单到不能再简单了,一个三维坐标系视图。我们来看一下代码中如何将三维物体坐标系表现出来。

private var _vertexBufferData:Vector.<Number> = Vector.<Number>(
                    [
                        //x    y     z       r   g   b
                          0.3, 0.3,  0,      1,  0,  0, //正面 0
                          0,   0,    0,      1,  0,  0,    // 1
                          0.3, 0,    0,      1,  0,  0,    // 2
                                          
                          0.3, 0.3,  0,      0,  1,  0,//右面 3
                          0.3, 0,    0,      0,  1,  0,   // 4
                          0.3, 0,    0.3,    0,  1,  0,   // 5
                                          
                          0.3, 0.3,  0,      0,  0,  1,//背面 6
                          0,   0,    0,      0,  0,  1,   // 7
                          0.3, 0,    0.3,    0,  0,  2,   // 8
                                          
                          0,   0,    0,      1,  1,  0,//底面 9
                          0.3, 0,    0.3,    1,  1,  0,   // 10
                          0.3, 0,    0,      1,  1,  0    // 11
                          
                    ]
                    );

      在定义三维物体坐标之后,我们就来编写AGAL代码,由于这篇文章AGAL不是我们的重点,所以这里就将AGAL代码粘贴到这,关于AGAL后面会有详细的讲解。

private var _vertexAGALcode:String = "m44 op, va0, vc0   
" +
                                            "mov v0 va1";
        private var _fragmentAGALcode:String = "mov oc, v0";
        private var _vertexAGAL:AGALMiniAssembler;
        private var _fragmentAGAL:AGALMiniAssembler;

       OK!所有数据都定义好之后我们就来开始编写我们的逻辑部分其实所谓的逻辑不过是将对应的数据上载到GPU中,然后set一下即可。在构造器函数中我们接受一个Context3D对象,这是我们文档类中创建的可编程管道。

/*------ function ------*/
        /**
         * 创建一个 TetraDisplay 对象
         * @param context3D
         * 
         */
        public function TetraDisplay( context3D:Context3D )
        {
            this._context3D = context3D;
            this._context3D.enableErrorChecking = true;
            this._context3D.configureBackBuffer( this._sceneWidth, this._sceneHeight, 2, true );
            this._context3D.setCulling( Context3DTriangleFace.BACK );
            this._indexBuffer = context3D.createIndexBuffer( this.indexBufferDataNum );
            this._vertexBuffer = context3D.createVertexBuffer( this.vertexBufferDataNum, 6 );
            this._program = context3D.createProgram();
            //初始化数据
            init();
        }

       我们可以看到,其实代码中的逻辑非常的简单,不过是普通的传值创建问题。在代码中我们首先定义了Context3D的舞台部分,这就相当于我们在flash中定义舞台的宽高一样,同时我们也设置的GPU的渲染质量,我们设置为2,表示低质量渲染。最后三句我们分别创建了IndexBuffer3D,VertexBuffer3D和Program3D对象,并且在创建的同时为其赋予了明确的数值。

      下面是我们调用的init函数,这个函数中我们要对所有的数据进行整理操作,实际上就是一个初始化数据的过程。

/*--------------------------------*/
        /*       protected function       */
        /*--------------------------------*/
        protected function init():void
        {
            //打印数据长度
            trace(this.indexBufferDataNum);
            trace(this.vertexBufferDataNum);
            //上载数据
            this._indexBuffer.uploadFromVector( this._indexBufferData, 0, this.indexBufferDataNum );
            this._vertexBuffer.uploadFromVector( this._vertexBufferData, 0, this.vertexBufferDataNum );
            //设置vertexBuffer数据
            this._context3D.setVertexBufferAt(0, this._vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3 );
            this._context3D.setVertexBufferAt(1, this._vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_3 );
            //生成AGAL二进制编码
            this._vertexAGAL = new AGALMiniAssembler();
            this._vertexAGAL.assemble( Context3DProgramType.VERTEX, this._vertexAGALcode, false );
            this._fragmentAGAL = new AGALMiniAssembler();
            this._fragmentAGAL.assemble( Context3DProgramType.FRAGMENT, this._fragmentAGALcode, false );
            //上载AGAL程序
            this._program.upload( this._vertexAGAL.agalcode, this._fragmentAGAL.agalcode )  ;
            //设置AGAL程序数据
            this._context3D.setProgram( this._program );
            //实例化舞台矩阵
            projection.perspectiveFieldOfViewRH( 45, 1, 1, 500 );   
            
            view.appendTranslation( 0, 0, -2 );   
            model.appendTranslation( 0, 0, 0 );
            model.appendRotation( -20, Vector3D.X_AXIS, pivot );
        }

       当我们的数据全部创建完成之后我们为外部开放一个名称为update的函数。这个函数的功能就负责刷新视图,根据我们的需要来呈现3D内容。

      

/*--------------------------------*/
        /*         public function        */
        /*--------------------------------*/
        //刷新数据
        public function update():void
        {
            model.appendRotation( 5, Vector3D.Y_AXIS, pivot );
            //矩阵计算
            finalTransform.identity();
            finalTransform.append( model );
            finalTransform.append( view );
            finalTransform.append( projection );
            //设置矩阵数据
            this._context3D.setProgramConstantsFromMatrix( 
                        Context3DProgramType.VERTEX,
                        0,
                        this.finalTransform,
                        true
                        );
            //清空舞台(stage3D[0]层)
            this._context3D.clear( 0, 0, 0 );
            this._context3D.drawTriangles( 
                            this._indexBuffer, 
                            0, 
                            this.trianglesNum
                            );
            this._context3D.present();
        }
       好了到此位置我们所有关于3D的编程工作已经完成了,下面就是通过我们的文档类来进行调用操作,调用的代码也非常的简单,首先是创建该类的实例,然后进行视图刷新操作。

创建对象:

this._tetradisplay = new TetraDisplay( this.context3d );
刷新视图操作:

this.addEventListener(Event.ENTER_FRAME, update );
private function update(evt:Event):void
        {
            this._tetradisplay.update();
        }
      当所有工作完成后我们可以来测试一下我们的成果了~!疯狂的点击debug按钮吧~!

点击查看原图