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 shaderProgram:Program3D; // 顶点数据buffer。前面说到一个模型是由很多个三角形组成,三角形呢又是由点构成,那么这个Buffer就用来存放各个顶点数据 private var vertexBuffer:VertexBuffer3D; // 索引buffer。上面是顶点数据的buffer,但是我们只有顶点,我们以及计算机都不知道这些顶点怎么可以构成不同的三角形。因此我们需要提供一个顶点的索引给显卡,让显卡通过索引来取出顶点的数据,也就是说,假如我们有两个三角形,那么我们的索引数据就有6个。前三个索引指定一个三角形,后三个索引指定一个三角形 private var indexBuffer:IndexBuffer3D; // 内存里面存放的顶点数据 private var meshVertexData:Vector.<Number>; // 内存里面存放的索引数据 private var meshIndexData:Vector.<uint>; // 投影矩阵,这个矩阵后面会说到。简要说一下:我们在3d里面物体如何显示到一个2d的显示屏幕上面?并且我们看远方,会发现远方的物体会很小,那么这个过程呢,其实就是这个矩阵完成。注意这个矩阵是继承的matrix3d。 private var projectionMatrix:PerspectiveMatrix3D = 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.<uint> ([ 0, 1, 2, 0, 2, 3, ]); // Raw data used for each of the 4 verteces // Position XYZ, texture coordinate UV, normal XYZ meshVertexData = Vector.<Number> ( [ //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 ]); } } }

  

  

  

  

 

posted on 2013-09-11 20:41  boblchen  阅读(896)  评论(0)    收藏  举报