【学习笔记】3D图形核心基础精炼版-12:stage3D实战-动态阴影 shadow mapping 和范例工程4

本文介绍了一种基于shadowmapping的深度投影阴影技术,通过渲染物体到深度纹理并利用该纹理计算被投影物体上的阴影。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目的:
    物体投影在另一个物体身上,而另一个物体可能是平的,但大多数都是不平的多边形物体,这里考虑的是后者,这样可以适用于大多数场合的投影。

预览效果:

原理:

    这里使用的是shadow mapping方式,其原理如下:

       1、将场景的深度值预先渲染到 以光源位置为原点、光线发射方向为观察方向的投影坐标系中,形成深度纹理。

       2、再次渲染场景的过程中,将每个片断(像素)变换到前述眼坐标系中,并缩放到[0,1]的范围内以便查询纹理。

       3、以较暗的光照绘制场景

       4、以当前片断在眼坐标中的S、T坐标查询深度纹理获得深度值,将此深度值与当前片断的R坐标进行比较,若R坐标大于深度值,则当前片断在阴影中;否则当前片断受光照。

转换为stage3D后的流程

       1、获得深度图:将要投影的模型以光点的视角绘制成图,.xyz取值[0,1],储存深度

       2、绘制被投影物体:

                    -- 顶点仍然要以光点的视角转换(P),以便计算(但输出仍是实际相机视角输出)

                    -- 取得光点视角的点对应的深度图上的深度,通过将光视角上的点P转为设备坐标(范围[-1,1])P2,再转为深度图的坐标P3,

                        这样就相当于计算出了相对于深度图的正确UV坐标,这个时候可以用该UV去取得深度图的纹理了

                    -- 取得深度图中的该点对应P的深度值与该点P对比,大了就可以涂黑了

鼠绘图说明:
1、这个是以灯光视角查看物体和场地的效果预览,黄色是灯光,黑色是投影物体,蓝色是被投影的场地。

2、以相机视角查看效果预览,这里可以看得出理论上应该是投影成这个样子的(只是随手一画,并不严谨,参考即可)


3、我们以灯光视角来绘制投影物体(黑),但只储存深度信息(z轴),所以越远越白,越近越黑,储存到纹理上


4、绘制被投影物体,要拿刚才那个深度图纹理对比,取得的点必须正确,如果取得红色点的话就是错的了,就会像下图那样显示在物体中间了,其实应该只显示一半的



关键代码--获得深度图:

"注意,由于我们这里绘制的图是越离光近越黑越接近0,越远越白越接近1,所以默认让画布是白的表示其他地图最远,否则计算结果不正确"
context3d.clear(1,1,1);

// vc0 MLP
finalMatrix.identity(); // 归零
finalMatrix.append(modelMatrix); // M 投影物体的模型矩阵 
var lightMatrix:Matrix3D = new Matrix3D(); // 计算光点矩阵L(以光点为视角)
lightMatrix.appendTranslation(lightPos.x,lightPos.y,lightPos.z);
finalMatrix.append(lightMatrix.clone());
finalMatrix.append(projectionMatrix); // P矩阵

// fc0 用到两个常量 fc0.x=zFar  和  fc0.w 使生成的纹理.w=1
context3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,0,Vector.<Number>([zFar,0,0,1]));


// vertex shader
"m44 vt0 va0 vc0 \n" // 最终输出点vt0 = 原始顶点*MLP
"mov v1 vt0\n" + // 将它传输给片段着色器使用
"mov op vt0" // 将它输出

// fragment shader
"mov ft0.xyz v1.zzz\n" + // 让图片着色统一是z,取值范围 [0,zFar]
"mov ft0.w fc0.w\n" + // 图片色彩w = 1 即透明度
"div ft0.xyz ft0.xyz fc0.x\n" + // ft0.xyz = ft0.xyz / zFar 让取值变为范围[0,1]
"mov oc,ft0" // 输出,由于颜色值根据深度归为0~1,所以越远越接近1越白,越近越黑

// 获得图(由于这里不需要再在CPU上处理,直接GPU上处理即可,所以使用setRenderToTexture)
context3d.setRenderToTexture(texShadow,false,0,0); // 设置即将要渲染到的位图
context3d.clear(0,0,0); // 填色黑色
lightViewEntity(); // 渲染物体(绘制深度图)
context3d.setRenderToBackBuffer(); // 设置渲染到后台缓冲区(配合setRenderToTexture使用,用后恢复正常)


// 如果必须要到CPU上处理,还可以这样获得位图图源到bdShadow:BitmapData,
context3d.clear(0,0,0);
lightViewEntity();
context3d.drawToBitmapData(bdShadow);
texShadow.uploadFromBitmapData(bdShadow);<strong>
</strong>

关键代码--渲染被投影物体:

// 灯光坐标点
private var lightPos:Vector3D = new Vector3D(0,0,-15,0);

// vc0 MVP 相机视角最终矩阵
finalMatrix.identity();
finalMatrix.append(floorMatrix);
finalMatrix.append(viewMatrix);
finalMatrix.append(projectionMatrix);
context3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,finalMatrix,true);

// vc4 MLP 灯光视角最终矩阵
finalMatrix.identity();
finalMatrix.append(floorMatrix);
var lightMatrix:Matrix3D = new Matrix3D();
lightMatrix.appendTranslation(lightPos.x,lightPos.y,lightPos.z);
finalMatrix.append(lightMatrix.clone());
finalMatrix.append(projectionMatrix);
context3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,4,finalMatrix,true);

// -- 设置状态机当前的VB的坐标 到 va0 ,因为只有XYZ,所以偏移是0,长度是3
context3d.setVertexBufferAt(0,modelVbFloor,0,Context3DVertexBufferFormat.FLOAT_3);

// -- 设置状态机当前的VB的UV纹理坐标 到 va2
context3d.setVertexBufferAt(2,modelVbFloor,6,Context3DVertexBufferFormat.FLOAT_2);

// fc2 = x=zFar y=0.5(用于辅助计算)
context3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,2,Vector.<Number>([zFar,0.5,1,1]));


// vertex shader
"m44 vt0 va0 vc0 \n" + // vt0 = 原始顶点*MVP 相机视角
"m44 vt1 va0 vc4\n" + // vt1 = 原始顶点*MLP 光视角
"mov v1 vt1\n"+ // 灯光视角后的顶点传输到片段着色器
"mov v1 va2\n"+ // 被投影物体的UV
"mov op vt0\n" // 输出顶点(这个仍然是相机视角的)

// fragment shader
"tex ft1 v1 fs1<2d,linear,repeat,nomip>\n"+ // 被投影物的纹理采样
"div ft2.xy v0.xy v0.zz\n"+ // 转为设备空间,x和y的取值范围是[-1,1] 就是三维点转屏幕上的二维点
"mul ft2.xy ft2.xy fc2.y\n"+ // *0.5 为适应贴图坐标系([0,1]),我们将[-1,1]转为[0,1],转换方法是new=old*0.5+0.5,然后Y取反
"add ft2.xy ft2.xy fc2.y\n"+ // +0.5
"neg ft2.y ft2.y\n"+ // Y取反,因为屏幕坐标系Y是+往上,贴图坐标中Y是-往上,最终计算出来的可以说是UV坐标

"tex ft3 ft2.xy fs0<2d,linear,repeat,nomip>\n"+ // 根据计算出来的UV坐标和深度图纹理取得纹理
"mul ft5.z ft3.z fc2.x\n"+ // z扩大zFar,让原来的0-1变为0-zFar
"slt ft5.w ft5.z v0.z\n"+ // 如果深度图中的深度 小于 (顶点xMLP)的深度的话就是1,否则就是0 也就是说影子时1,非影子0

"mul ft5.w ft5.w fc2.y\n" + // 作出效果,让 影子1,非影子0 转为影子0.5,非影子1,这样直接与原始颜色相乘让影子部分的颜色等于原来的一半
"neg ft5.w ft5.w\n" +
"add ft5.w ft5.w fc2.y\n" +
"add ft5.w ft5.w fc2.y\n" +
"mul ft1 ft1 ft5.w\n"+

"mov oc ft1\n" // oc ft1<strong>
</strong>

注意点:

   1、绘制深度图时由于我们这里绘制的图是越离光近越黑越接近0,越远越白越接近1,所以默认让画布是白的表示其他地图最远,否则会认为黑色部分都是遮挡部分

   2、在绘制被投影物体的时候,如何转换坐标来对比,这里还是要临时转到灯光视角,然后计算到该视角中顶点对应的深度图纹理坐标,原理:

                 --- pMLP = 原始顶点 * 灯光最终矩阵 = p * MLP (这里的L就是V,只不过和灯光一个位置和角度了)

                 --- SB(pMLP) = pMLP.xy / pMLP.zz  (就是x和y分别除以z,这样就转为屏幕坐标系了,就是中心点为0,0,左边框x=-1,右边框x=1,上边框y=1,下边看y=-1的这个坐标系),也就是说x和y的取值范围都是 [-1,1]

                 --- 因为贴图纹理坐标是[0,1],所以这里要把屏幕中的坐标对应那张深度图(屏幕大小的截图)的坐标,要转换坐标,并且Y是相反的 UV(pMVP) *0.5 + 0.5 = [0,1] ,同时纹理y=1-屏幕坐标系的y
                          
范例工程下载:

链接:http://pan.baidu.com/s/1o84C3rk 密码:3cvo

全代码贴出(这里还在CPU上计算了顶点最终的对应的屏幕像素坐标,方便理解):

package
{
	import com.adobe.utils.AGALMiniAssembler;
	import com.adobe.utils.PerspectiveMatrix3D;
	
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.display.Stage3D;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DCompareMode;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DRenderMode;
	import flash.display3D.Context3DTextureFormat;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.VertexBuffer3D;
	import flash.display3D.textures.Texture;
	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.filters.BlurFilter;
	import flash.filters.GlowFilter;
	import flash.geom.Matrix3D;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.geom.Vector3D;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.ui.Keyboard;
	
	import utils.MatrixUtil;
	
	[SWF(width="1024", height="1024", frameRate="60", backgroundColor="#000000")]
	/**
	 * 锥形并且贴图纹理追加简单光照
	 * @author Xin Yan Kong 2016.4
	 * 
	 */
	public class Main extends Sprite
	{
		/**
		 * 是否首次创建3D环境
		 */		
		private var isFirstCreate:Boolean = true;
		/**
		 * 唯一状态机/画家 
		 */		
		private var context3d:Context3D;
		/**
		 * 模型M矩阵
		 */		
		private var modelMatrix:Matrix3D;
		/**
		 * 镜头V矩阵
		 */		
		private var viewMatrix:Matrix3D;
		/**
		 * 投影P矩阵,Adobe封装的,继承于Matrix3D
		 */		
		private var projectionMatrix:PerspectiveMatrix3D;
		/**
		 * 最终矩阵,这个应该是M+V+P 
		 */		
		private var finalMatrix:Matrix3D;
		/**
		 * flash要用到的顶点信息:顶点缓冲数据(模型) 
		 */		
		private var modelVb:VertexBuffer3D;
		/**
		 * flash要用到的顶点索引信息:索引缓冲数据(模型) 告诉系统点的绘制顺序,系统才知道有多少个面,是怎样的面
		 */		
		private var modelIb:IndexBuffer3D;
		/**
		 * flash要用的顶点信息:顶点缓冲数据(地板)
		 */		
		private var modelVbFloor:VertexBuffer3D;
		/**
		 * flash要用的顶点信息:索引缓冲数据(地板) 
		 */		
		private var modelIbFloor:IndexBuffer3D;
		
		/**
		 * 画家绘制的代码,就是你告诉他应该如何绘制,调色等,他会按照这个方式来绘制,当然也是程序,汇编的风格... 也就是说你可以预先定制好N个代码来选择哪一个来绘制
		 */		
		private var program:Program3D;
		
		private var programShadow:Program3D;
		
		
		private var programHasShadowFloor:Program3D; // 渲染含有阴影的图
		
		/**
		 * 当鼠标移动的时候记录的鼠标点 
		 */		
		private var onMouseDownPt:Point = new Point();
		/**
		 * 嵌入图片资源到SWF里 
		 */		
		[Embed(source = "wall.jpg")]  
		private var wallClass:Class;
		private var wallBmp:Bitmap = new wallClass() as Bitmap;
		/**
		 * 嵌入图片资源到SWF里 
		 */		
		[Embed(source = "ground2.jpg")]  
		private var groundClass:Class;
		private var groundBmp:Bitmap = new groundClass() as Bitmap;
		/**
		 * flash要用到的纹理信息 
		 */		
		private var tex:Texture;
		/**
		 * flash要用到的纹理信息 
		 */		
		private var texGround:Texture;
		/**
		 * 光照所在的点
		 */		
		private var lightPos:Vector3D = new Vector3D(0,0,-15,0);
		/**
		 * 光照强度 
		 */		
		private var lightStr:Number = 0.5;
		private var lightStrAdd:Number = -0.01;
		/**
		 * 深度图储存的texture 
		 */		
		private var texShadow:Texture;
		/**
		 * 测试显示深度图用的BitmapData 
		 */		
		private var bdShadow:BitmapData;
		/**
		 * 测试显示用的深度图Bitmap 
		 */		
		private var shadowMapBmp:Bitmap = new Bitmap();
		/**
		 * 最远的距离 
		 */		
		private var zFar:Number = 50;
		
		
		public function Main()
		{
			// 初始化
			init();
		}
		
		/**
		 * 初始化 
		 * 
		 */		
		private function init():void{
			// -- 如果没有3D环境的话
			if(stage.stage3Ds.length==0){
				throw("你没有可用的3D环境!");
				return;
			}
			// -- 取得一个stage3D舞台,这里取下标0,因为这个3D舞台100%存在。
			var stage3d:Stage3D = stage.stage3Ds[0];
			// -- 设置侦听:创建失败的情况
			stage3d.addEventListener(ErrorEvent.ERROR,onCreate3dError);
			// -- 设置侦听:创建成功的情况或设备恢复的情况
			stage3d.addEventListener(Event.CONTEXT3D_CREATE,onCreate3dSuccess);
			// -- 请求创建,如果你不请求创建,那么又有什么用呢?渲染模式为硬件模式,表示使用显卡GPU来计算
			stage3d.requestContext3D(Context3DRenderMode.AUTO);
		}
		/**
		 * 创建3D环境失败 
		 * @param e
		 * 
		 */		
		private function onCreate3dError(e:ErrorEvent):void{
			throw("创建3D环境失败!");
		}
		/**
		 * 创建3D环境成功的情况或设备恢复的情况 
		 * @param e
		 * 
		 */		
		private var debugTF:TextField = new TextField();
		private function onCreate3dSuccess(e:Event):void{
			// 如果找不到状态机context3d的话提示错误
			context3d = (e.target as Stage3D).context3D;
			if(context3d==null){
				throw("创建3D环境失败!");
				return;
			}
			// 当发生错误的时候会显示错误信息,着色语言阶段出错时开启此选项就会报具体的错误信息,你好因此排查错误(无论首次创建还是设备恢复都要重新设定此项)
			context3d.enableErrorChecking=true;
			// 设定后台缓冲区,一般就是要画画的区域大小了,还有抗锯齿为2的N次方(0表示不抗锯齿画的效率更高但画面更丑)(无论首次创建还是设备恢复都要重新设定此项)
			context3d.configureBackBuffer(stage.stageWidth, stage.stageHeight, 16, true);
			// 如果第一次创建的话而非设备恢复的情况
			if(isFirstCreate){
				// -- 标识下,说明不再是第一次创建3D环境了
				isFirstCreate = false;
				// -- 侦听:每帧渲染
				this.addEventListener(Event.ENTER_FRAME,onRender);
				// -- 侦听:键盘操控 
				stage.addEventListener(KeyboardEvent.KEY_DOWN,onKeyDown);
				// -- 侦听:鼠标操控
				stage.addEventListener(MouseEvent.MOUSE_DOWN,onMouseDown);
				// -- 初始化物体相关信息
				initModel();
				// -- 初始化shader:着色语言
				initShader();
				// -- 创建镜头V矩阵
				viewMatrix = new Matrix3D();
				// -- 镜头拉后一点,因为屏幕向里是+,所以这个镜头实际在屏幕外的地方
				viewMatrix.appendTranslation(10,0,-10);
				viewMatrix.appendRotation(20,Vector3D.Y_AXIS);
				
//				viewMatrix.appendTranslation(lightPos.x,lightPos.y,lightPos.z);
				
				// -- 创建投影P矩阵
				projectionMatrix = new PerspectiveMatrix3D();
				// -- 设定为透视矩阵,越远越小越接近屏幕中心的那种,右手坐标系RightHand  
				//    -- 第一个参数表示透视角度  
				//    -- 第二个参数表示宽高比例  
				//    -- 第三个参数表示裁剪的最近距离,比这个个还近就无法显示  
				//    -- 第四个参数表示裁剪的最远距离,比这个还远的东西就无法显示  
				projectionMatrix.perspectiveFieldOfViewRH(45.0,stage.stageWidth/stage.stageHeight,0.01,zFar);
				
				// -- 初始化深度图要用到的texture和bitmapData
				texShadow = context3d.createTexture(stage.stageWidth,stage.stageHeight,Context3DTextureFormat.BGRA,true);
				bdShadow = new BitmapData(stage.stageWidth,stage.stageHeight,true,0xffff00);
				this.addChild(shadowMapBmp);
				
				// -- for debug
				debugTF.background = true;
				debugTF.backgroundColor = 0x000000;
				debugTF.mouseEnabled = debugTF.selectable = false;
				debugTF.multiline = true;
				debugTF.textColor = 0xffffff;
				debugTF.alpha = 0.5;
				debugTF.autoSize = TextFieldAutoSize.LEFT;
				this.addChild(debugTF);
				
			}
				// 设备恢复的情况
			else{
				restore();
			}
		}
		/**
		 * 初始化物体相关信息
		 * 
		 */		
		private function initModel():void{
			// -- 最终矩阵
			finalMatrix = new Matrix3D();
			// -- 该物体的矩阵
			modelMatrix = new Matrix3D();
			// -- 该物体的顶点信息(坐标XYZ+对应的颜色RGB)这里5个顶点,我们要绘制一个锥形
			var vecVb:Vector.<Number> = Vector.<Number>([
				// X  Y Z R G B U V nX nY nZ
				-1, 1,0,1,0,0,0,0,0,0,0, // 0 左上角的点 + 红色 + UV坐标纹理的左上角点 + 法线(后面计算下)
				1, 1,0,0,1,0,1,0,0,0,0, // 1 右上角的点 + 绿色 + UV坐标纹理的右上角点 + 法线(后面计算下)
				1,-1,0,0,0,1,1,1,0,0,0, // 2 右下角的点 + 蓝色 + UV坐标纹理的右下角点 + 法线(后面计算下)
				-1,-1,0,1,1,0,0,1,0,0,0,  // 3 左下角的点 + 黄色 + UV坐标纹理的左下角点 + 法线(后面计算下)
				0,0,-5,0,1,1,0.5,0.5,0,0,0 // 4 里面中心的点 + 青色 + UV坐标纹理的中间的点 + 法线(后面计算下)
			]);
			calcNormal(vecVb,5);
			// -- 创建该物体的顶点缓冲:有多少个顶点(当然是4个),以及一个顶点包含多少个信息(上面一行的信息=11)
			modelVb = context3d.createVertexBuffer(vecVb.length/11,11);
			// -- 通过vector来上传顶点数据:顶点信息、偏移(由于这里vec全部信息只用于一个物体,偏移就是0)、顶点数
			modelVb.uploadFromVector(vecVb,0,vecVb.length/11);
			// -- 该物体的索引数据:按照下面的顺序来画三角形的
			var vecIdx:Vector.<uint> = Vector.<uint>([
				0,1,2, // 左上角的点 - 右上角的点 - 右下角的点   这里是顺时针绘制的吧,表示一个三角形
				2,3,0, // 右下角的点 - 左下角的点 - 左上角的点  这里也是顺时针绘制的吧,表示第二个三角形
				0,1,4, // 锥形面1
				0,4,3, // 锥形面2
				3,4,2, // 锥形面3
				1,2,4  // 锥形面4
			]);
			// -- 创建该物体的顶点索引缓冲:
			modelIb = context3d.createIndexBuffer(vecIdx.length);
			// -- 通过vector来上传顶点索引数据:顶点索引数据、偏移(同上由于整个vecIdx都用于一个物体了这里全部都是该物体的信息)、索引个数
			modelIb.uploadFromVector(vecIdx,0,vecIdx.length);
			// -- 创建纹理,由于使用BitmapData方式转为texture所以模式是RGBA,如果是ATF格式的话则是COMPRESSED,纹理必须是2的n次方宽高,如果不是可以转化一下
			tex = context3d.createTexture(wallBmp.width,wallBmp.height,Context3DTextureFormat.BGRA,false);
			// -- 上传纹理
			tex.uploadFromBitmapData(wallBmp.bitmapData);
			
			
			// -- 计算木板的顶点和索引
			vecVb = Vector.<Number>([
				// X  Y Z R G B U V nX nY nZ
				-10, 10,-20,1,0,0,0,0,0,0,1, // 0 左上角的点 + 红色 + UV坐标纹理的左上角点 + 法线(后面计算下)
				10, 10,-20,0,1,0,30,0,0,0,1, // 1 右上角的点 + 绿色 + UV坐标纹理的右上角点 + 法线(后面计算下)
				10,-10,-20,0,0,1,30,30,0,0,1, // 2 右下角的点 + 蓝色 + UV坐标纹理的右下角点 + 法线(后面计算下)
				-10,-10,-20,1,1,0,0,30,0,0,1,  // 3 左下角的点 + 黄色 + UV坐标纹理的左下角点 + 法线(后面计算下)
				0,0,-5,1,1,0,15,15,0,0,1
				
				
			]);
			modelVbFloor = context3d.createVertexBuffer(vecVb.length/11,11);
			modelVbFloor.uploadFromVector(vecVb,0,vecVb.length/11);
			vecIdx = Vector.<uint>([
				0,1,2, // 左上角的点 - 右上角的点 - 右下角的点   这里是顺时针绘制的吧,表示一个三角形
				2,3,0, // 右下角的点 - 左下角的点 - 左上角的点  这里也是顺时针绘制的吧,表示第二个三角形
				0,1,4, // 锥形面1
				0,4,3, // 锥形面2
				3,4,2, // 锥形面3
				1,2,4  // 锥形面4
			]);
			modelIbFloor = context3d.createIndexBuffer(vecIdx.length);
			modelIbFloor.uploadFromVector(vecIdx,0,vecIdx.length);
			// -- 创建纹理,由于使用BitmapData方式转为texture所以模式是RGBA,如果是ATF格式的话则是COMPRESSED,纹理必须是2的n次方宽高,如果不是可以转化一下
		    texGround = context3d.createTexture(groundBmp.width,groundBmp.height,Context3DTextureFormat.BGRA,false);
			// -- 上传纹理
			texGround.uploadFromBitmapData(groundBmp.bitmapData);
		}
		/**
		 * 计算法线 原理就是计算顶点所关联的所有三角形面的法线,然后取得它们平均值就是顶点的法线了(当然还有其他方式求得法线,比如权重)
		 * @param vecVb 顶点信息
		 * @param vertexNum 顶点数
		 * @param offsetNormal 法线的偏移值
		 * @param offsetVertex 顶点的偏移值
		 */		
		private function calcNormal(vecVb:Vector.<Number>,vertexNum:int):void{
			var data32PerVertex:int = vecVb.length/vertexNum;
			var vertexArr:Array = [];
			for (var i:int=0;i<vertexNum;i++){
				var x:Number = vecVb[data32PerVertex*i];
				var y:Number = vecVb[data32PerVertex*i+1];
				var z:Number = vecVb[data32PerVertex*i+2];
				vertexArr.push(new Vector3D(x,y,z));
			}
			var calcNormal:Function = function(p1:Vector3D,p2:Vector3D,p3:Vector3D):Vector3D{
				var source2:Vector3D = new Vector3D(p2.x-p1.x,p2.y-p1.y,p2.z-p1.z);
				var source1:Vector3D = new Vector3D(p3.x-p1.x,p3.y-p1.y,p3.z-p1.z);
				var norV:Vector3D = new Vector3D();
				norV.x = source1.y * source2.z - source1.z * source2.y;
				norV.y = source1.z * source2.x - source1.x * source2.z;
				norV.z = source1.x * source2.y - source1.y * source2.x;
				norV.normalize();
				return norV;
			}
			// 计算平均法线:就是将N个三角面的法线平均一下,这里由于锥顶在背面,我们就将结果法线反向一下,让背面受光,而正面不受光,如果要双面受光的话后面再说
			var calcNormalAvg:Function = function(arr:Array,offsetNormal:int):void{
				var sideLen:int = arr.length/3;
				var sideAvg:Vector3D = new Vector3D();
				for (var i:int=0;i<sideLen;i++){
					var side:Vector3D = calcNormal(vertexArr[arr[i*3]],vertexArr[arr[i*3+1]],vertexArr[arr[i*3+2]]);
					sideAvg=sideAvg.add(side);
				}
				sideAvg.x = sideAvg.x/sideLen;
				sideAvg.y = sideAvg.y/sideLen;
				sideAvg.z = sideAvg.z/sideLen;
				sideAvg.normalize();
				vecVb[offsetNormal] = -sideAvg.x;
				vecVb[offsetNormal+1] = -sideAvg.y;
				vecVb[offsetNormal+2] = -sideAvg.z;
			}
			// 4-顶点的平均法线
			calcNormalAvg([
				4,0,1,
				4,3,0,
				4,2,3,
				4,1,2
			],vertexNum*data32PerVertex-3);
			
			// 3-顶点的平均法线
			calcNormalAvg([
				3,0,2,   // 裁掉不受光的那个面,否则影响受光面的光照
				3,0,4,
				3,4,2
			],(vertexNum-1)*data32PerVertex-3);
			
			// 2-顶点的平均法线
			calcNormalAvg([
				2,3,4,
				2,3,1, // 裁掉不受光的那个面,否则影响受光面的光照
				2,4,1
			],(vertexNum-2)*data32PerVertex-3);
			
			// 1-顶点的平均法线
			calcNormalAvg([
				1,2,4,
				1,2,3,   // 裁掉不受光的那个面,否则影响受光面的光照
				1,4,0
			],(vertexNum-3)*data32PerVertex-3);
			
			// 0-顶点的平均法线
			calcNormalAvg([
				0,1,4,
				0,1,2,   // 裁掉不受光的那个面,否则影响受光面的光照
				0,4,3
			],(vertexNum-4)*data32PerVertex-3);
		}
		/**
		 * 初始化着色语言:一般物体渲染,带有光照效果
		 * 
		 */		
		private function initShader():void{
			// -- 创建一个着色语言
			program = context3d.createProgram();
			// -- 创建一个AGALMiniAssembler辅助类用于写顶点着色代码
			//    主要用于输出最终的顶点位置,以及传一些数据给片段着色代码使用
			var vShader:AGALMiniAssembler = new AGALMiniAssembler();
			vShader.assemble(Context3DProgramType.VERTEX,
				"m44 op va0 vc0 \n" + // 输出 = 4个顶点 * 最终矩阵 (这里的*就是m44方法,表示每个点都作用了该矩阵)
				"mov v1 va1\n" + // 将4个点的颜色传到中转站以便片段着色代码使用
				"mov v2 va2\n" +   // 将4个点的使用纹理的坐标点传到中转站以便片段着色代码使用
				"mov v3 va3" // 将4个点坐标点传到中转站以便片段着色代码使用
			);
			
			// -- 创建一个AGALMiniAssembler辅助类用于写片段着色代码
			var fShader:AGALMiniAssembler = new AGALMiniAssembler();
			fShader.assemble(Context3DProgramType.FRAGMENT,
				//------------------------------------------------------------------------------------------------------
				// 通用变量:
				//   -- 纹理颜色
				//   -- 世界光照点 globalLight
				//   -- 世界法线   globalNormal
				//------------------------------------------------------------------------------------------------------
				"tex ft0, v2, fs0 <2d,linear,repeat,nomip>\n"+ //临时变量 ft0 = v2(UV坐标点)和图片纹理作用的颜色点  关于linear repeat nomip后面再说
				"mov ft1 fc0\n" + // 世界光照点 ft1 = 光照点
				"mov ft2 v3\n" + // 本地法线 ft2 = 法线
				"m44 ft2 ft2 fc3\n" + // 世界法线 ft2 = 法线 m44 模型变换矩阵
				
				//------------------------------------------------------------------------------------------------------
				// 环境反射 fc1
				//------------------------------------------------------------------------------------------------------
				
				//------------------------------------------------------------------------------------------------------
				// 漫反射:
				//   -- 计算光线与法线的角度(点乘)
				//   -- 修正背面负数的情况反转,因为背面的话是负数,角度是负数的话就转为正数(取绝对值或多步骤实现反转)
				//   -- 计算光照加成的颜色 = 像素颜色*角度
				//------------------------------------------------------------------------------------------------------
				// -- 漫反射:计算角度
				"dp3 ft3.w ft1 ft2\n" + // 点乘,计算角度
				// -- 漫反射:修正背面负数的情况反转(取绝对值)
				"abs ft3.w ft3.w\n" + 
				// -- 漫反射:计算光照加成的颜色和根据强度系数调整
				"mul ft4 ft0 ft3.wwww\n" + // 光照加成颜色 ft4 = 原始颜色*光照
				"mul ft4 ft4 fc1.wwww\n" + // 根据强度系数调整
				
				//------------------------------------------------------------------------------------------------------
				// 镜面反射(高光):
				//   -- 计算入射角与光线一半的角度·世界法线
				//   
				//------------------------------------------------------------------------------------------------------
				//				// -- 镜面反射:计算角度  ft2 = 入射角的一半·模型法线
				"dp3 ft6.w fc2 ft2\n" + 
				// -- 镜面反射:修正背面负数的情况反转和计算高光颜色
				"abs ft6.w ft6.w\n" + 
				"mul ft7 fc4 ft6.wwww\n" + 
				
				//------------------------------------------------------------------------------------------------------
				// 计算最终输出的颜色
				//  -- 原始颜色
				//  -- += 漫反射
				//  -- += 高光
				//  -- += 环境光
				//  -- 输出
				//------------------------------------------------------------------------------------------------------
				// -- 镜面反射:高光加成颜色
				"add ft0 ft0 ft4\n" + // 原始颜色 += 漫反射光照加成颜色
				"add ft0 ft0 ft7\n" + // 原始颜色 += 高光加成颜色
				"add ft0 ft0 fc1\n" + // 颜色叠加
				"mov oc, ft0\n" // 直接输出颜色点 ft0  从这里看的出来shader就是用来处理点的颜色信息的,比如颜色混合、光照等等
			);
			program.upload(vShader.agalcode,fShader.agalcode);
			
			// -- 
			initShaderShadow();
			initShaderHasShadowFloor();
		}
		
		/**
		 * 用于输出深度图的shader
		 * 
		 */		
		private function initShaderShadow():void{
			// -- 创建一个着色语言
			programShadow = context3d.createProgram();
			// -- 创建一个AGALMiniAssembler辅助类用于写顶点着色代码
			//    主要用于输出最终的顶点位置,以及传一些数据给片段着色代码使用
			var vShader:AGALMiniAssembler = new AGALMiniAssembler();
			vShader.assemble(Context3DProgramType.VERTEX,
				"m44 vt0 va0 vc0 \n" + // 输出 = 4个顶点 * 最终矩阵 (这里的*就是m44方法,表示每个点都作用了该矩阵)
				"mov v1 vt0\n" +  // MLP z=0-50
				"mov op vt0\n"  // 顶点原始点 
			);
			// -- 创建一个AGALMiniAssembler辅助类用于写片段着色代码
			var fShader:AGALMiniAssembler = new AGALMiniAssembler();
			fShader.assemble(Context3DProgramType.FRAGMENT,
				//------------------------------------------------------------------------------------------------------
				// 通用变量:
				//   -- 纹理颜色
				//   -- 世界光照点 globalLight
				//   -- 世界法线   globalNormal
				//------------------------------------------------------------------------------------------------------
				"mov ft0.xyz v1.zzz\n" + // ft1.x=v3.z ft1.xyz = (顶点·MLP).zzz  将顶点经灯光视角转换的深度存储到xyz
				"mov ft0.w fc0.y\n" + // ft1.w = 1 固定为1
				"div ft0.xyz ft0.xyz fc0.x\n" + // ft0.xyz = ft0.xyz / zFar 因为颜色数值范围在 0-1 所以这里除以zFar来得到0-1 
				"mov oc,ft0" // 输出,这里离摄像机越近越黑(0),越远越白(1)
			);
			programShadow.upload(vShader.agalcode,fShader.agalcode);
			
		}
		/**
		 * 被投影者的代码,
		 * 
		 */	
		private function initShaderHasShadowFloor():void{
			// -- 创建一个着色语言
			programHasShadowFloor = context3d.createProgram();
			// -- 创建一个AGALMiniAssembler辅助类用于写顶点着色代码
			//    主要用于输出最终的顶点位置,以及传一些数据给片段着色代码使用
			var vShader:AGALMiniAssembler = new AGALMiniAssembler();
			vShader.assemble(Context3DProgramType.VERTEX,
				//投影到场景相机
				"m44 vt0 va0 vc0 \n" + // op 顶点****MVP
				//投影到场景灯光相机
				"m44 vt1 va0 vc4\n" + // vt1 = 顶点****MLP
				// 传输MLP
				"mov v0 vt1\n"+ // 投影到场景灯光相机
				// uv
				"mov v1 va2\n"+ // uv
				// 输出
				"mov op vt0\n"
			);
			
			// -- 创建一个AGALMiniAssembler辅助类用于写片段着色代码
			//1.将三维空间坐标转换到设备空间坐标[-1,1]上 (MLP后的坐标.xy/MLP后的坐标.z)
			//2.将设备空间转换到贴图坐标系上 设备空间坐标.xy*0.5+0.5,然后翻转y 这个时候我就知道我当前的点对应的是贴图坐标系中的哪个点了(类似UV坐标)
			//3.根据该计算出的UV坐标取得贴图,能够对应上点
			//4.直接深度对比后展现效果
			var fShader:AGALMiniAssembler = new AGALMiniAssembler();
			fShader.assemble(Context3DProgramType.FRAGMENT,
				// 木板纹理采样
				"tex ft1 v1 fs1<2d,linear,repeat,nomip>\n"+
				// 叠加,Z越大越黑
				"mov ft4.x v0.z\n" + 
				"div ft4.x ft4.x fc2.x\n" + 
				"sub ft1 ft1 ft4.x\n" +
				// 将坐标转换到设备空间坐标(屏幕坐标系) ft2.xy = MLP.xy/MLP.zz
				"div ft2.xy v0.xy v0.zz\n"+
				//将坐标转换到纹理UV fc2.y = 0.5   0.5 * xy + 0.5; 就可以让[-1,1] 转为[0,1]
				//ft2.xy *= 0.5
				"mul ft2.xy ft2.xy fc2.y\n"+
				//ft2.xy += 0.5;
				"add ft2.xy ft2.xy fc2.y\n"+
				//ft2.y = -ft2.y 以下两种写法都可以,因为纹理坐标超出会自动补差,比如1.2就是0.2,比如-0.2 就是0.8
//				"neg ft2.y ft2.y\n"+
				"sub ft2.y fc2.z ft2.y\n" +
				
				//阴影图深度采样
				"tex ft3 ft2.xy fs0<2d,linear,repeat,nomip>\n"+
				// ft5.z = ft3.z * fc2.x  = 深度 * zFar  因为保存的时候除以了zFar,现在还原来做对比
				"mul ft5.z ft3.x fc2.x\n"+
				// ft5.w = ft5.z>=v2.z?1:0 如果当前的深度 大于 (顶点xMLP)的深度的话就返回1  (1或者0)
				"slt ft5.w ft5.z v0.z\n"+ // 影子1,非影子0
				
				// 作出效果,让 影子1,非影子0 转为影子0.5,非影子1,这样直接与原始颜色相乘让影子部分的颜色等于原来的一半
				"mul ft5.w ft5.w fc2.y\n" +
				"neg ft5.w ft5.w\n" +
				"add ft5.w ft5.w fc2.y\n" +
				"add ft5.w ft5.w fc2.y\n" +
				"mul ft1 ft1 ft5.w\n"+
				// oc ft1
				"mov oc ft1\n"
				
			);
			programHasShadowFloor.upload(vShader.agalcode,fShader.agalcode);
		}
		
		/**
		 * 设备恢复的情况 
		 * 
		 */		
		private function restore():void{
			initModel();
			initShader();
		}
		/**
		 * 侦听回调:逐帧渲染 
		 * @param e
		 * 
		 */		
		private function onRender(e:Event):void{
			debugTF.text = "";
			// -- 如果设备丢失的话就暂时不绘制不然就报错了,不信你试试注释掉这个并且CTRL+ALT+DEL
			if(isContextDispose)return;
			// -- 设置深度
			context3d.setDepthTest(true,Context3DCompareMode.LESS);
			// -- 绘制到贴图中
			renderObject();
			// -- 颜色系数改变
			lightStr+=lightStrAdd;
			if(lightStr>1){
				lightStr=1;
				lightStrAdd=-lightStrAdd;
			}
			else if(lightStr<0){
				lightStr=0;
				lightStrAdd=-lightStrAdd;
			}
		}
		
		/**
		 * 将这个图作为tex来试试 
		 * 
		 */		
		
		private function renderObject():void{
			// 以光线角度绘制深度图(GPU模式,无需经过CPU)
			context3d.setRenderToTexture(texShadow,false,0,0);
			context3d.clear(1,1,1);
			lightViewVertebral();
			context3d.setRenderToBackBuffer();
			
			// 以光线角度绘制深度图(GPU绘制后传给CPU使用,如果CPU不使用的话你可以屏蔽掉此段)
			context3d.clear(1,1,1);
			lightViewVertebral();
			context3d.drawToBitmapData(bdShadow);
			texShadow.uploadFromBitmapData(bdShadow);
			shadowMapBmp.bitmapData = bdShadow;
			shadowMapBmp.scaleX=shadowMapBmp.scaleY=0.2;
			shadowMapBmp.x = stage.stageWidth - shadowMapBmp.width;
			
			// 正常渲染场景,附加上影子
			context3d.clear(0,0,1);
			renderFloorTest();
			renderVertebral();
			context3d.present();
		}
		/**
		 * 渲染物体前初始化 
		 * 
		 */		
		private function renderDefault():void{
			context3d.setVertexBufferAt(0, null);
			context3d.setVertexBufferAt(1, null);
			context3d.setVertexBufferAt(2, null);
			context3d.setVertexBufferAt(3, null);
			context3d.setTextureAt(0,null);
			context3d.setTextureAt(1,null);
		}
		/**
		 * 渲染锥形体,以相机视角,带光照和颜色变换效果
		 * 
		 */		
		private function renderVertebral():void{
			// -- 初始化
			renderDefault();
			// -- 最终矩阵先归零
			finalMatrix.identity();
			// -- 乘上M矩阵
			finalMatrix.append(modelMatrix);
			// -- 乘上V矩阵
			finalMatrix.append(viewMatrix);
			// -- 乘上P矩阵
			finalMatrix.append(projectionMatrix);
			// -- 设置状态机当前的VB的坐标 到 va0 ,因为只有XYZ,所以偏移是0,长度是3
			context3d.setVertexBufferAt(0,modelVb,0,Context3DVertexBufferFormat.FLOAT_3);
			// -- 设置状态机当前的VB的颜色 到 va1
			context3d.setVertexBufferAt(1,modelVb,3,Context3DVertexBufferFormat.FLOAT_3);
			// -- 设置状态机当前的VB的UV纹理坐标 到 va2
			context3d.setVertexBufferAt(2,modelVb,6,Context3DVertexBufferFormat.FLOAT_2);
			// -- 设置状态机当前的VB的法线 到 va3
			context3d.setVertexBufferAt(3,modelVb,8,Context3DVertexBufferFormat.FLOAT_3);
			// -- 设置状态机当前的vc0 静态常量:最终矩阵
			context3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,finalMatrix,true);
			// -- 设置状态机当前的fc0 静态常量:用于光照计算(这里的光照要进行模型的矩阵变换,因为它直接与法线作用,法线是固定的如果这里不变换的话模型即使变动了受光也是一样的)
			var light:Vector3D=lightPos.clone(); // 不含平移元素的变换
			light.normalize(); // 单位化
			context3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,0,Vector.<Number>([light.x,light.y,light.z,0]));
			// -- 设置状态机当前的fc1 静态常量:xyz用于 环境光颜色叠加 W用于漫反射光照强度   这里叠加一个红色环境光
			var ambientR:Number = lightStr*0.2;
			var ambientG:Number = (1-lightStr)*0.2;
			var ambientB:Number = (lightStr/2)*0.2;
			context3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,1,Vector.<Number>([ambientR,ambientG,ambientB,lightStr]));//lightStr
			// -- 设置状态机当前的fc2 静态常量:用于 高光点 这个点是视点和光线点夹角的一半
			var halfEyeLightPos:Vector3D = viewMatrix.position.clone();
			halfEyeLightPos=halfEyeLightPos.add(lightPos);
			//			halfEyeLightPos.scaleBy(0.5); 和下面的xyz/2是一个意思
			halfEyeLightPos.x/=2;
			halfEyeLightPos.y/=2;
			halfEyeLightPos.z/=2;
			halfEyeLightPos.normalize();
			context3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,2,Vector.<Number>([halfEyeLightPos.x,halfEyeLightPos.y,halfEyeLightPos.z,0]));
			// -- 设置状态机当前的fc3 静态常量:用于将模型矩阵变换传入
			context3d.setProgramConstantsFromMatrix(Context3DProgramType.FRAGMENT,3,modelMatrix);
			// -- 设置状态机当前的fc4 静态常量:高光颜色
			var specularStr:Number = lightStr*0.3;
			context3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,4,Vector.<Number>([1.0*specularStr,1.0*specularStr,1.0*specularStr,0]));
			// -- 设置状态机使用的纹理到当前的fs0
			context3d.setTextureAt(0,tex);
			// -- 设置状态机使用的代码
			context3d.setProgram(program);
			// -- 绘制三角形
			context3d.drawTriangles(modelIb);
			
		}
		
		/**
		 * 渲染深度图:以光线视角渲染锥形体, 
		 * 
		 */		
		private function lightViewVertebral():void{
			// -- 初始化
			renderDefault();
			// -- 最终矩阵先归零
			finalMatrix.identity();
			// -- 乘上M矩阵
			finalMatrix.append(modelMatrix);
			// -- 乘上V矩阵
			var lightMatrix:Matrix3D = new Matrix3D();
			lightMatrix.appendTranslation(lightPos.x,lightPos.y,lightPos.z);
			finalMatrix.append(lightMatrix.clone());
			// -- 乘上P矩阵
			finalMatrix.append(projectionMatrix);
			// -- 设置状态机当前的VB的坐标 到 va0 ,因为只有XYZ,所以偏移是0,长度是3
			context3d.setVertexBufferAt(0,modelVb,0,Context3DVertexBufferFormat.FLOAT_3);
			// -- 传递最终矩阵(MLP) vc0
			context3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,finalMatrix,true);
			// -- 设置状态机当前的fc0 静态常量:x=zFar y=1(为了让渲染图的w固定为1)
			context3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,0,Vector.<Number>([zFar,1,0,0]));
			// -- 设置状态机使用的代码
			context3d.setProgram(programShadow);
			
			// -- for debug
			var VertebralVec1:Vector3D = MatrixUtil.transformPoint3D(finalMatrix,new Vector3D(-1, 1,0));
			var VertebralVec2:Vector3D = MatrixUtil.transformPoint3D(finalMatrix,new Vector3D(1, 1,0));
			var VertebralVec3:Vector3D = MatrixUtil.transformPoint3D(finalMatrix,new Vector3D(1,-1,0));
			var VertebralVec4:Vector3D = MatrixUtil.transformPoint3D(finalMatrix,new Vector3D(-1,-1,0));
			var VertebralVec5:Vector3D = MatrixUtil.transformPoint3D(finalMatrix,new Vector3D(0, 0,-5));
			
			VertebralVec1.w = VertebralVec1.z / zFar;
			VertebralVec2.w = VertebralVec2.z / zFar;
			VertebralVec3.w = VertebralVec3.z / zFar;
			VertebralVec4.w = VertebralVec4.z / zFar;
			VertebralVec5.w = VertebralVec5.z / zFar;
			
			// -- 测试顶点·MLP 
			debugTF.appendText("==============锥形体的顶点-绝对3维坐标点"+"\n");
			debugTF.appendText(VertebralVec1.toString()+","+VertebralVec1.w+"\n");
			debugTF.appendText(VertebralVec2.toString()+","+VertebralVec2.w+"\n");
			debugTF.appendText(VertebralVec3.toString()+","+VertebralVec3.w+"\n");
			debugTF.appendText(VertebralVec4.toString()+","+VertebralVec4.w+"\n");
			debugTF.appendText(VertebralVec5.toString()+","+VertebralVec5.w+"\n");
			debugTF.appendText("==============锥形体的顶点-绝对设备2维坐标点"+"\n");
			var VertebralPoint:Point = new Point(VertebralVec1.x/VertebralVec1.z,VertebralVec1.y/VertebralVec1.z); 
			var Vertebra2Point:Point = new Point(VertebralVec2.x/VertebralVec2.z,VertebralVec2.y/VertebralVec2.z); 
			var Vertebra3Point:Point = new Point(VertebralVec3.x/VertebralVec3.z,VertebralVec3.y/VertebralVec3.z); 
			var Vertebra4Point:Point = new Point(VertebralVec4.x/VertebralVec4.z,VertebralVec4.y/VertebralVec4.z); 
			var Vertebra5Point:Point = new Point(VertebralVec5.x/VertebralVec5.z,VertebralVec5.y/VertebralVec5.z); 
			debugTF.appendText(VertebralPoint.toString()+"\n");
			debugTF.appendText(Vertebra2Point.toString()+"\n");
			debugTF.appendText(Vertebra3Point.toString()+"\n");
			debugTF.appendText(Vertebra4Point.toString()+"\n");
			debugTF.appendText(Vertebra5Point.toString()+"\n");
			// -- 转为像素图
			var toTexPointFunc:Function = function(p:Point):String{
				p.x = int((p.x *0.5+0.5)*stage.stageWidth);
				p.y = int((1-(p.y *0.5+0.5))*stage.stageHeight);
				return p.toString() + "\n";
			}
			debugTF.appendText("==============锥形体的顶点-屏幕坐标像素点"+"\n");
			debugTF.appendText(toTexPointFunc(VertebralPoint));
			debugTF.appendText(toTexPointFunc(Vertebra2Point));
			debugTF.appendText(toTexPointFunc(Vertebra3Point));
			debugTF.appendText(toTexPointFunc(Vertebra4Point));
			debugTF.appendText(toTexPointFunc(Vertebra5Point));
			
			// -- 绘制三角形
			context3d.drawTriangles(modelIb);
			
		}
		/**
		 * 渲染被投影的木板,要用到深度图对比,同时
		 * 
		 */		
		var testFloorRo:Number=0;
		var changeRo:Number = 0.5;
		var floorMatrix:Matrix3D = new Matrix3D();
		private function renderFloorTest():void{
			// -- 初始化
			renderDefault();
			
			// -- 最终矩阵先归零
			finalMatrix.identity();
			// -- 乘上M矩阵
			finalMatrix.append(floorMatrix);
			// -- 乘上V矩阵
			finalMatrix.append(viewMatrix);
			// -- 乘上P矩阵
			finalMatrix.append(projectionMatrix);
			// -- 设置状态机当前的vc0 静态常量:最终矩阵
			context3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,finalMatrix,true);
			// -- 灯光相机 vc4=MLP
			finalMatrix.identity();
			finalMatrix.append(floorMatrix);
			var lightMatrix:Matrix3D = new Matrix3D();
			lightMatrix.appendTranslation(lightPos.x,lightPos.y,lightPos.z);
			finalMatrix.append(lightMatrix.clone());
			finalMatrix.append(projectionMatrix);
			context3d.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,4,finalMatrix,true);
			
			// -- 设置状态机当前的VB的坐标 到 va0 ,因为只有XYZ,所以偏移是0,长度是3
			context3d.setVertexBufferAt(0,modelVbFloor,0,Context3DVertexBufferFormat.FLOAT_3);
			// -- 设置状态机当前的VB的UV纹理坐标 到 va2
			context3d.setVertexBufferAt(2,modelVbFloor,6,Context3DVertexBufferFormat.FLOAT_2);
			
			// fc2 = x=zFar y=0.5(用于辅助计算)
			context3d.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,2,Vector.<Number>([zFar,0.5,1,1]));
			
			// -- 设置状态机使用的纹理到当前的fs0
			context3d.setTextureAt(1,texGround);
			context3d.setTextureAt(0,texShadow);
			// -- 设置状态机使用的代码
			context3d.setProgram(programHasShadowFloor);
			// -- 绘制三角形
			context3d.drawTriangles(modelIbFloor);
			
			// -- for debug
			var VertebralVec1:Vector3D = MatrixUtil.transformPoint3D(finalMatrix,new Vector3D(-10, 10,-20));
			var VertebralVec2:Vector3D = MatrixUtil.transformPoint3D(finalMatrix,new Vector3D(10, 10,-20));
			var VertebralVec3:Vector3D = MatrixUtil.transformPoint3D(finalMatrix,new Vector3D(10,-10,-20));
			var VertebralVec4:Vector3D = MatrixUtil.transformPoint3D(finalMatrix,new Vector3D(-10,-10,-20));
			var VertebralVec5:Vector3D = MatrixUtil.transformPoint3D(finalMatrix,new Vector3D(0,0,-5));
			
			VertebralVec1.w = VertebralVec1.z / zFar;
			VertebralVec2.w = VertebralVec2.z / zFar;
			VertebralVec3.w = VertebralVec3.z / zFar;
			VertebralVec4.w = VertebralVec4.z / zFar;
			VertebralVec5.w = VertebralVec5.z / zFar;
			
			// -- 测试顶点·MLP
			debugTF.appendText("==============场地的顶点-绝对3维坐标点"+"\n");
			debugTF.appendText(VertebralVec1.toString()+","+VertebralVec1.w+"\n");
			debugTF.appendText(VertebralVec2.toString()+","+VertebralVec2.w+"\n");
			debugTF.appendText(VertebralVec3.toString()+","+VertebralVec3.w+"\n");
			debugTF.appendText(VertebralVec4.toString()+","+VertebralVec4.w+"\n");
			debugTF.appendText(VertebralVec5.toString()+","+VertebralVec5.w+"\n");
			debugTF.appendText("==============场地的顶点-绝对设备2维坐标点"+"\n");
			var VertebralPoint:Point = new Point(VertebralVec1.x/VertebralVec1.z,VertebralVec1.y/VertebralVec1.z); 
			var Vertebra2Point:Point = new Point(VertebralVec2.x/VertebralVec2.z,VertebralVec2.y/VertebralVec2.z); 
			var Vertebra3Point:Point = new Point(VertebralVec3.x/VertebralVec3.z,VertebralVec3.y/VertebralVec3.z); 
			var Vertebra4Point:Point = new Point(VertebralVec4.x/VertebralVec4.z,VertebralVec4.y/VertebralVec4.z); 
			var Vertebra5Point:Point = new Point(VertebralVec5.x/VertebralVec5.z,VertebralVec5.y/VertebralVec5.z); 
			debugTF.appendText(VertebralPoint.toString()+"\n");
			debugTF.appendText(Vertebra2Point.toString()+"\n");
			debugTF.appendText(Vertebra3Point.toString()+"\n");
			debugTF.appendText(Vertebra4Point.toString()+"\n");
			debugTF.appendText(Vertebra5Point.toString()+"\n");
			// -- 转为像素图
			var toTexPointFunc:Function = function(p:Point):String{
				p.x = int((p.x *0.5+0.5)*stage.stageWidth);
				p.y = int((1-(p.y *0.5+0.5))*stage.stageHeight);
				return p.toString() + "\n";
			}
			debugTF.appendText("==============场地的顶点-屏幕坐标像素点"+"\n");
			debugTF.appendText(toTexPointFunc(VertebralPoint));
			debugTF.appendText(toTexPointFunc(Vertebra2Point));
			debugTF.appendText(toTexPointFunc(Vertebra3Point));
			debugTF.appendText(toTexPointFunc(Vertebra4Point));
			debugTF.appendText(toTexPointFunc(Vertebra5Point));
			debugTF.appendText("==============按键:"+"\n");
			debugTF.appendText("WASD=锥形体位移"+"\n");
			debugTF.appendText("IJKL=场地位移"+"\n");
			debugTF.appendText("方向键=镜头平移"+"\n");
			debugTF.appendText("小键盘789=镜头旋转"+"\n");
			debugTF.appendText("鼠标拖拽=锥形体旋转"+"\n");
		}
		
		/**
		 * 侦听回调:键盘操作 
		 * @param e
		 * 
		 */		
		private function onKeyDown(e:KeyboardEvent):void{
			switch(e.keyCode){
				case Keyboard.W: 
					modelMatrix.appendTranslation(0,0,0.1);
					break;
				case Keyboard.S: 
					modelMatrix.appendTranslation(0,0,-0.1);
					break;
				case Keyboard.A: 
					modelMatrix.appendTranslation(0.1,0,0);
					break;
				case Keyboard.D:
					modelMatrix.appendTranslation(-0.1,0,0);
					break;
				case Keyboard.I: 
					floorMatrix.appendTranslation(0,0,0.1);
					break;
				case Keyboard.K: 
					floorMatrix.appendTranslation(0,0,-0.1);
					break;
				case Keyboard.J:
					floorMatrix.appendTranslation(0.1,0,0);
					break;
				case Keyboard.L:
					floorMatrix.appendTranslation(-0.1,0,0);
					break;
				case Keyboard.UP: 
					viewMatrix.appendTranslation(0,0,-0.1);
					break;
				case Keyboard.DOWN: 
					viewMatrix.appendTranslation(0,0,0.1);
					break;
				case Keyboard.LEFT: 
					viewMatrix.appendTranslation(-0.1,0,0);
					break;
				case Keyboard.RIGHT:
					viewMatrix.appendTranslation(0.1,0,0);
					break;
				case Keyboard.NUMPAD_7:
					viewMatrix.appendRotation(1,Vector3D.X_AXIS);
					break;
				case Keyboard.NUMPAD_8:
					viewMatrix.appendRotation(1,Vector3D.Y_AXIS);
					break;
				case Keyboard.NUMPAD_9:
					viewMatrix.appendRotation(1,Vector3D.Z_AXIS);
					break; 
			}
		}
		/**
		 * 鼠标移动旋转物体
		 * 原理无非就是根据每次移动时的像素差距来计算让物体矩阵M在当前的状态下再围绕X和Y旋转(至于围绕Z轴旋转可以自己添加试试)
		 * @param e
		 * 
		 */		
		private function onMouseDown(e:MouseEvent):void{
			onMouseDownPt.x = e.stageX;
			onMouseDownPt.y = e.stageY;
			stage.addEventListener(MouseEvent.MOUSE_MOVE,onMouseMove);
			stage.addEventListener(MouseEvent.MOUSE_UP,onMouseUp);
		}
		private function onMouseMove(e:MouseEvent):void{
			var dx:Number = e.stageX - onMouseDownPt.x;
			var dy:Number = e.stageY - onMouseDownPt.y;
			var degreesY:Number = dx/2;
			var degreesX:Number = dy/2;
			onMouseDownPt.x = e.stageX;
			onMouseDownPt.y = e.stageY;
			modelMatrix.appendRotation(degreesY,Vector3D.Y_AXIS);
			modelMatrix.appendRotation(degreesX,Vector3D.X_AXIS);
		}
		private function onMouseUp(e:MouseEvent):void{
			stage.removeEventListener(MouseEvent.MOUSE_MOVE,onMouseMove);
			stage.removeEventListener(MouseEvent.MOUSE_MOVE,onMouseUp);
		}
		/**
		 * 判断设备丢失 
		 * 
		 */		
		private function get isContextDispose():Boolean{
			return context3d==null||context3d.driverInfo=="Disposed"||context3d.driverInfo=="";
		}
		
		
	}
}





What this book covers Chapter 1, Let&#39;s Make a Game Using Molehill! In this chapter, we talk about what Stage3D (Molehill) is, what it can do, and the basic terminology you need to know when dealing with 3D games. Chapter 2, Blueprint of a Molehill. In this chapter, we compare the differences between old-fashioned Flash games and the new way of doing things, along with a description of the major classes we will be working with and the structure of a typical Stage3D game. Chapter 3, Fire up the Engines. In this chapter, we take the first step by setting up our tools and programming the initializations for our game. The result is a demo that gets Stage3D to animate a simple 3D mesh. Chapter 4, Basic Shaders: I can see Something! In this chapter, we learn about programming shaders using AGAL and adding text to the display. The result is an upgraded demo with four different animated shaders. Chapter 5, Building a 3D World. In this chapter, we create a way to fill the game world with complex 3D models by programming a mesh data file parser. The result is a game demo complete with high-poly spaceships and terrain instead of simple textured squares. Chapter 6, Textures: Making Things Look Pretty. In this chapter, we upgrade our game demo to include a keyboard input and special render modes that allow us to draw special effects such as transparent meshes, explosions, and more. The result is a demo that highlights these many new effects. Chapter 7, Timers, Inputs, and Entities: Gameplay Goodness! In this chapter, we program a timer and generic game entity class. In addition, we upgrade the GUI with a heads-up-display overlay and add a chase camera. The result is a demo with a spaceship that can fly around in an asteroid field that looks more like a real video game. Chapter 8, Eye-Candy Aplenty! In this chapter, we program a highly optimized GPU particle system for use in special effects. All geometry is rendered in large, reusable batches. The result is a game demo that is able to render hundreds of thousands of particles at 60fps. Chapter 9, A World Filled with Action. In this chapter, we upgrade our game engine to include simple game actor artificial intelligence, collision detection, and a map parsing mechanism to allow for easy world creation. The result is a fully functional engine ready for use in a real game. This material is copyright and is licensed for the sole use by REKHA NADENDLA on 2nd December 2011 375 N STEPHANIE ST SUITE 1411, HENDERSON, 89014 Preface [ 3 ] Chapter 10, 3... 2... 1... ACTION! In this chapter, we add the final touches to our game project such as a title screen, dealing with the score, damage and game over events, music and sound, and much more. The final result of our efforts is a fully playable 3D shooter game filled with action! Appendix A, AGAL Operand Reference. This appendix provides operand references that have been used in this book. Appendix B, Pop Quiz Answers. In this section, we provide answers to the pop quiz.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值