stage3d编程-基础3(网格,法线,面,顶点等)

昨天谈到了矩阵的变换, 今天开始谈一下关于顶点,面,法线,网格等基本术语。如果有时间,还会写一个stage3d的demo。




  1、顶点


  在3d世界里面,基本单位就是三角形,为什么会是三角形?因为三个坐标可以构成一个三角形,而一个三角形则可以表示一个面。一个四边形则是两个三角形组合,再也找不到比三角形更小的几何图形可以用来表示一个面。在想象一下,一个立方体。比如你手边的魔方,盒子等。你可以观察出来,它是由6个四边形组成,也就是由12个三角形组成。 盒子的尖角就是一个顶点。 也就是说,一个多边形两边相交的点就叫做顶点。一般我们也称定义位置的点为顶点。例如你手边的立方体盒子,就是由8个顶点定义,将这个8个顶点分别两两相连,就可以看到一个立方体由12个三角形组成。




  大家在想象一下,如果是很多个三角形,分的很细,那么是否就可以组成一个模型呢?例如一个人,汽车?轮子?




  大家注意看哪个透明的茶壶,你们就会发现它是由很多个三角形构成的。那么最终的这个形态呢,我们就称之为网格。


  2、网格


  网格的定义上面就说了。


  3、面


  最初就提到了,三个点构成的三角形就称之为面。


  4、法线


  上面提到三个点可以构成一个三角形。在基础0里面就讲到了关于向量。现在我们来看一下这三个点,假设点分别为v0, v1, v2。那么我们可以轻易的得到两个向量:A->v1-v0; B->v2-v0。AB这两个向量的起点都是v0,他们的终点为别为v1,v2。在前面就讲到,两个向量的叉积的结果也是一个向量,并且这个向量垂直于这两个向量。那么我们就可以通过A向量叉成B向量,得到一条垂直向量AB的向量N。这个向量不仅垂直向量AB,而且还垂直于AB向量夹角范围内与AB平行的所有向量。我们称这个向量N为该面的法线。法线的定义也就是:垂直与面的向量。既然向量N垂直这个面,那么这个法线其实也就代表了这个面的方向。只是面的方向和法线成90度而已。


   题外话:在3d世界中,一个灯光照射到物体上面,照不到的是黑的,照的到的是亮的,这个是怎么实现的呢?可以想象一下,假设一条平行光从上往下照射到模型上。其中模型面向显示器的有一个面正好与我们的显示器平行。假设就是一个摆放的立方体。我们平行看得到的那个面,就正好和显示器平行。那么那个面的法线就刚好和显示器垂直,也刚好和那一束平行光垂直。前面就提到过了,向量点积的结果可以表示两个向量的夹角关系。那么在这里,我们将这一条法线和平行光向量进行点积,法线值为0。然后我们把这个面涂黑,然后我们按照这个思路对所有的面进行这样的操作。根据点积的结果,将那个面进行不同程度的颜色变化。咦?灯光效果就出来了。。。。


  5、贴图


  大家看那个茶壶,它是一个网格结构,只有一个线框,中间还是空的,假设我们用一张图片按照一定的规则给它贴上去,那么它看起来就是一个茶壶咯。




  要是无法想通,就假设这个张图全黑,在给它贴上面,就有了一个黑不溜秋的茶壶了。。。那么这个图呢,我们就称之为贴图。有法线贴图。。。高光贴图。等等。




  关于3d里面最基本的术语呢,就先解释这么多。下面给出一个带有详细注释的demo。这个demo是我买的那本书里面的例子。








?

{

    import com.adobe.utils.*;

    import flash.display.*;

    import flash.display3D.*;

    import flash.display3D.textures.*;

    import flash.events.*;

    import flash.geom.*;

    import flash.utils.*;


    [SWF(width="640", height="480", frameRate="60", backgroundColor="#000000")]

    public class Stage3dGame extends Sprite

    {

        // 定义后台缓冲区的大小,一般来说这个大小也和swf的大小一样大,关于后台缓冲区可能明天就会谈到

        private const swfWidth:int = 640;

        private const swfHeight:int = 480;
         // 这个是贴图的大小,注意贴图的大小必须为2的幂,也就是说必须为2、4、8、16、32、64、128、256、512等形式,目前贴图最大为2048(stage3d的baseline模式)

        private const textureSize:int = 512;


        // context3d对象,它主要负责用于和显卡通讯,并且context3d各个对象之间数据不通用,也就是说,你不能把一个context3d的数据妄图放到另一个里面去使用显示

        private var context3D:Context3D;

        // 着色器程序,之前就说了,将一张图贴到模型上面,这个过程就需要着色器来做,着色器根据给的的数据从贴图里面取出像素然后显示出来。

        private var shaderProgramrogram3D;

        // 顶点数据buffer。前面说到一个模型是由很多个三角形组成,三角形呢又是由点构成,那么这个Buffer就用来存放各个顶点数据

        private var vertexBuffer:VertexBuffer3D;

        // 索引buffer。上面是顶点数据的buffer,但是我们只有顶点,我们以及计算机都不知道这些顶点怎么可以构成不同的三角形。因此我们需要提供一个顶点的索引给显卡,让显卡通过索引来取出顶点的数据,也就是说,假如我们有两个三角形,那么我们的索引数据就有6个。前三个索引指定一个三角形,后三个索引指定一个三角形

        private var indexBuffer:IndexBuffer3D;

        // 内存里面存放的顶点数据

        private var meshVertexData:Vector.;

        // 内存里面存放的索引数据

        private var meshIndexData:Vector.;


        // 投影矩阵,这个矩阵后面会说到。简要说一下:我们在3d里面物体如何显示到一个2d的显示屏幕上面?并且我们看远方,会发现远方的物体会很小,那么这个过程呢,其实就是这个矩阵完成。注意这个矩阵是继承的matrix3d。

        private var projectionMatrixerspectiveMatrix3D = new PerspectiveMatrix3D();
         // 模型矩阵,我在矩阵变换里面就提到了,如果通过矩阵来进行平移缩放旋转。那么这个矩阵就对应了模型,如果需要对模型就行这些操作,就在这个矩阵上下手吧。

        private var modelMatrix:Matrix3D = new Matrix3D();
         // 相机矩阵,在3d世界中,我们并不是直接观察3d世界的,我们是通过一个相机来观察的。想象一个你拍照的时候,从相机里面看到的世界。那么这个相机我们同样可以移动,我们可以把相机摆得近一些,看到的物体就清晰一些,也可以摆的远一些,看到的物体就模糊小一些。

        private var viewMatrix:Matrix3D = new Matrix3D();
         // 最终的矩阵,这个矩阵是模型矩阵*相机矩阵*投影矩阵得到的。(注意这个顺序是固定的,以后会谈到一个渲染管线的东西)

        private var modelViewProjection:Matrix3D = new Matrix3D();


        // a simple frame counter used for animation

        private var t:Number = 0;


        /* TEXTURE: Pure AS3 and Flex version:

         * if you are using Adobe Flash CS5 comment out the next two lines of code */

        [Embed (source = "texture.jpg")] private var myTextureBitmap:Class;

        private var myTextureData:Bitmap = new myTextureBitmap();


        /* TEXTURE: Flash CS5 version:

         * add the jpg to your library (F11)

         * right click it and edit the advanced properties so

         * it is exported for use in Actionscript and call it myTextureBitmap

         * if using Flex/FlashBuilder/FlashDevlop comment out the next two lines of code */

        //private var myBitmapDataObject:myTextureBitmapData = new myTextureBitmapData(textureSize, textureSize);

        //private var myTextureData:Bitmap = new Bitmap(myBitmapDataObject);


        // 这个呐,就是贴图

        private var myTexture:Texture;


        public function Stage3dGame()

        {

            if (stage != null)

                init();

            else

                addEventListener(Event.ADDED_TO_STAGE, init);

        }


        private function init(e:Event = null):void

        {


            if (hasEventListener(Event.ADDED_TO_STAGE))

                removeEventListener(Event.ADDED_TO_STAGE, init);


            stage.frameRate = 60;

            stage.scaleMode = StageScaleMode.NO_SCALE;

            stage.align = StageAlign.TOP_LEFT;



            stage.stage3Ds[0].addEventListener(

                Event.CONTEXT3D_CREATE, onContext3DCreate);
              // 向stage3ds[0]声请一个context3d对象,一般来说0就可以了,不同的显卡可用的stage3ds数量不同
              // 上面注释提到了一个baseline的模式,这个模式呢其实就是adobe为了支持手机,移动设备,其他设备,pc等等做出来的一个限制模式,保证大家都可以用这个模式下面的东西。
              // 我们在申请context3d的时候可以指定使用扩展模式,这里默认使用受限模式
              // stage.stage3D[0].requestContext3D.call(this,Context3DRenderMode.AUTO,profileMode);

?

            stage.stage3Ds[0].requestContext3D();

        }


        private function onContext3DCreate(event:Event):void

        {

            
              // 移除帧循环事件,因为有可能我们之前创建好了contex3d对象,但是当我们待机,睡眠,切换了浏览器tab或者浏览器失去了焦点,都有可能让我们的这个contex3d设备丢失。所以我们需要重新声请。

            if (hasEventListener(Event.ENTER_FRAME))

                removeEventListener(Event.ENTER_FRAME,enterFrame);


            // context3d对象是从stage3d对象里面获取的

            var t:Stage3D = event.target as Stage3D;                    

            context3D = t.context3D;   


            if (context3D == null)

            {

                // Currently no 3d context is available (error!)

                return;

            }


            // 开启context3d的错误检测。这个检测主要是针对显卡里面的错误检测。因为我们以后要对显卡进行编程。但是对显卡进行编程了,我们不好调试,所以adobe这煞笔提供了一个这么一个错误检测选项,开启了这个错误见检测,我们就可以看到错误消息,注意只是看得到错误消息。但是这个错误消息,我真心看不懂,写了这么久了,我也只是凭着经验来调试找bug。开启这个选项会消耗一定的性能,在产品的发布阶段这个就需要关闭了。

            context3D.enableErrorChecking = true;


            // 初始化顶点数据以及索引数据,这个数据目前我们是手动填入的,显示出来的也只是一个正方形平板。

            initData();


            // 设置后台缓冲区的大小,以及锯齿,是否启用深度以及模板测试(这个后面再谈)

            context3D.configureBackBuffer(swfWidth, swfHeight, 0, true);

              
                
              

            // 顶点着色器程序,专门用来处理顶点的

            var vertexShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();

            vertexShaderAssembler.assemble

            (

                Context3DProgramType.VERTEX,

                // op是输出目标,va0是我们指定的顶点buffer中的数据,vc0是我们传入的modelViewProj矩阵。m44就是表示,用顶点乘以modelViewProj矩阵,

                "m44 op, va0, vc0\n" +

                // 将顶点传入中间变量寄存器,因为顶点程序和片段着色器程序是分开的,他们的数据不能通用,因此需要一个中间变量哎传递,并且这个是单向的,只能从顶点程序到片段程序

                "mov v0, va0\n" +

                // 将指定的buffer中的数据1,也就是uv数据(后面再谈)

                "mov v1, va1\n"

            );         


            // 片段着色器程序,专门用来渲染颜色

            var fragmentShaderAssembler:AGALMiniAssembler = new AGALMiniAssembler();

            fragmentShaderAssembler.assemble

            (

                Context3DProgramType.FRAGMENT,  

                // grab the texture color from texture fs0

                // tex是采样命令,意思就是根据uv数据从传入的贴图fs0安照<2d,repeat,miplinear>方式进行获取颜色信息,存放在ft0中

                "tex ft0, v1, fs0 <2d,repeat,miplinear>\n" +  

                // oc是颜色输出目标,将ft0传入oc进行输出了。

                "mov oc, ft0\n"                                

            );


            // 通过context3d创建着色器程序

            shaderProgram = context3D.createProgram();
              // 向显卡上传指令

            shaderProgram.upload(vertexShaderAssembler.agalcode, fragmentShaderAssembler.agalcode);


            // 创建indexbuffer

            indexBuffer = context3D.createIndexBuffer(meshIndexData.length);
              // 向显卡上传索引数据,第一参数指定索引数据,第二个指定起始点,第三个指定长度,我们也可以指定某一段的索引,那么现实出来的模型也即是那一段索引对于的三角形了。也就是说我们可以把两个模型的顶点数据放到一个vertexbuffer里面,索引数据也放到一个indexbuffer里面,然后根据后面两个参数来指定索引。

            indexBuffer.uploadFromVector(meshIndexData, 0, meshIndexData.length);


            // 创建vertexbuffer,需要指定buffer的长度以及每一段的长度。大家可以想象成一个二维数组,一行8个表示一段数据,总共length/8段数据

            vertexBuffer = context3D.createVertexBuffer(meshVertexData.length/8, 8);

            vertexBuffer.uploadFromVector(meshVertexData, 0, meshVertexData.length/8);


            // 根据texturesize创建texture,这个大小不一定要和贴图一样大小

            myTexture = context3D.createTexture(textureSize, textureSize,

                Context3DTextureFormat.BGRA, false);
              // 下面这个操作是在做mip。将贴图进行不同等级的缩放,也就是当模型很远的时候,就用被缩放过的贴图贴到模型上面去。如果模型很近,就用没有缩放的贴图贴上去。

            var ws:int = myTextureData.bitmapData.width;

            var hs:int = myTextureData.bitmapData.height;

            var level:int = 0;

            var tmp:BitmapData;

            var transform:Matrix = new Matrix();

            tmp = new BitmapData(ws, hs, true, 0x00000000);

            while ( ws >= 1 && hs >= 1 )

            {

                tmp.draw(myTextureData.bitmapData, transform, null, null, null, true);
                   // 这里就是在上传不同等级贴图,这个过程称之为Mip。level越小,贴图质量越高。

                myTexture.uploadFromBitmapData(tmp, level);

                transform.scale(0.5, 0.5);

                level++;

                ws >>= 1;

                hs >>= 1;

                if (hs && ws)

                {

                    tmp.dispose();

                    tmp = new BitmapData(ws, hs, true, 0x00000000);

                }

            }

            tmp.dispose();


            // 将投影矩阵标准化,就是弄成单位矩阵

            projectionMatrix.identity();

            // 生成投影矩阵,0.01是看得最近的距离,100是看得远的距离,模型超过了就看不到了。adobe提供的这个算法有问题不靠谱,模型的面过多的时候,会造成图像剧烈抖动。以后我会提              // 供一个算法出来。这里注意看perspectiveFieldOfViewRH这个是生成右手系。

            projectionMatrix.perspectiveFieldOfViewRH(45.0, swfWidth / swfHeight, 0.01, 100.0);


            // create a matrix that defines the camera location

            viewMatrix.identity();

            // 将相机往后移动4米。右手系的y是向上,x是向右,z是朝屏幕内,详情看基础0。那么既然是z朝着屏幕内,那么将相机矩阵的z轴移动-4应该是向前面移动啊,怎么会变成往后了呢???大家注意。。。相机看到的东西是反的。。。。

            viewMatrix.appendTranslation(0,0,-4);


            // start animating

            addEventListener(Event.ENTER_FRAME,enterFrame);

        }


        private function enterFrame(e:Event):void

        {

            // 清楚后台缓冲区,因为显卡把当前帧的所有数据都绘制到了后台缓冲区,后台缓冲区的数据才是最终显示的,这一帧绘制完成之后,下一帧到来,它需要将上一帧的数据清空掉。

            context3D.clear(0,0,0);

            // 设置着色器程序

            context3D.setProgram ( shaderProgram );


            // 之前提到的模型矩阵

            modelMatrix.identity();

            modelMatrix.appendRotation(t*0.7, Vector3D.Y_AXIS);

            modelMatrix.appendRotation(t*0.6, Vector3D.X_AXIS);

            modelMatrix.appendRotation(t*1.0, Vector3D.Y_AXIS);

            modelMatrix.appendTranslation(0.0, 0.0, 0.0);

            modelMatrix.appendRotation(90.0, Vector3D.X_AXIS);


            // 旋转0.2

            t += 2.0;


            // 最终矩阵,使用前这里清空成了单位矩阵

            modelViewProjection.identity();

            modelViewProjection.append(modelMatrix);

            modelViewProjection.append(viewMatrix);

            modelViewProjection.append(projectionMatrix);


            // 注意看名称,设置顶点程序常量,0表示vc0,如果是矩阵,那么会占用4个寄存器,所有会占用vc0,vc1,vc2,vc3,true是否反转矩阵,这个也以后谈,也是一个坑儿。

            context3D.setProgramConstantsFromMatrix(

                Context3DProgramType.VERTEX,

                0, modelViewProjection, true );


            // associate the vertex data with current shader program

            // 设置顶点va,0表示va0,从vertexbuffer的一段里面的第0个位置开始取,取3个->顶点坐标信息

            context3D.setVertexBufferAt(0, vertexBuffer, 0,

                Context3DVertexBufferFormat.FLOAT_3);

            // 设置va1,从veftexbuffer的一段里面的第三个位置开始取(前面三个是顶点坐标信息啦),取三个数据

            context3D.setVertexBufferAt(1, vertexBuffer, 3,

                Context3DVertexBufferFormat.FLOAT_3);


            // 设置fs0为mytexture

            context3D.setTextureAt(0, myTexture);


            // 绘制三角形,传入索引buffer,并且指定绘制多少个三角形。三个索引对应一个三角形嘛。

            context3D.drawTriangles(indexBuffer, 0, meshIndexData.length/3);


            // 显示后台缓冲区的数据。

            context3D.present();

        }


        private function initData():void

        {

            // Defines which vertex is used for each polygon

            // In this example a square is made from two triangles

            meshIndexData = Vector.

            ([

                0, 1, 2,        0, 2, 3,

            ]);


            // Raw data used for each of the 4 verteces

            // Position XYZ, texture coordinate UV, normal XYZ

            meshVertexData = Vector.

            ( [

                //X,  Y,  Z,   U, V,   nX, nY, nZ      

                 -1, -1,  1,   0, 0,   0,  0,  1,

                  1, -1,  1,   1, 0,   0,  0,  1,

                  1,  1,  1,   1, 1,   0,  0,  1,

                 -1,  1,  1,   0, 1,   0,  0,  1

            ]);

        }      

    }

}

你可能感兴趣的:(Flash)