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

目的:

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

预览效果:

原理:

这里使用的是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=="";
		}

	}
}

时间: 2024-10-07 16:20:49

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

加壳学习笔记(一)-基础知识

1.预备知识 1.关于栈,在windows里面的堆栈其实很简单,当学了才知道哈,呵呵,第一要记住的是windows里面的栈是向低地址生长的(extended,延伸,呵呵,顺便丰富下英语的单词量,确实是少的可怜),你可以这样认为,栈就像是一个倒立的箱子,箱子的口子是向下的,底是向上的,这里就表明了系统栈的分布也是延伸方向是由高地址向低地址extended,在最初的空栈阶段,栈顶(extended stack pointer,简称是esp)在栈底的低一个位置,呵呵,其实很好理解,就是说最初的箱子是空

Linux程序设计学习笔记----Socket网络编程基础之TCP/IP协议簇

转载请注明出处: ,谢谢! 内容提要 本节主要学习网络通信基础,主要涉及的内容是: TCP/IP协议簇基础:两个模型 IPv4协议基础:IP地址分类与表示,子网掩码等 IP地址转换:点分十进制\二进制 TCP/IP协议簇基础 OSI模型 我们知道计算机网络之中,有各种各样的设备,那么如何实现这些设备的通信呢? 显然是通过标准的通讯协议,但是,整个网络连接的过程相当复杂,包括硬件.软件数据封包与应用程序的互相链接等等,如果想要写一支将联网全部功能都串连在一块的程序,那么当某个小环节出现问题时,整只

MongoDB 学习笔记一 安装以及基础命令

一.MongoDB安装配置 1.获取最新版本: wget http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.0.2.tgz 2.解压缩即可执行 tar zxvf mongodb-linux-x86_64-2.0.2.tgz tar zxvf mongodb-linux-x86_64-2.0.2.tgz cd /usr/mongodb-linux-x86_64-2.0.2/bin 但是在运行前,需要创建mongodb需要的存放数据和日志的

汇编入门学习笔记 (一)—— 基础知识、寄存器

疯狂的暑假学习之  汇编入门学习笔记 (一) 参考:<汇编语言> 王爽  第一,二章 一.基础知识 1. 用汇编语言编写程序的工作过程 汇编本质就是为了方便程序员,把二进制代码用汇编指令来表示 汇编指令---------------->  编译器  ---------------->机器码----------------> 计算机执行 mov ax, bx                                                          100

MongoDB 学习笔记(一)基础篇

1.MongoDB 特点 面向集合存储,存储对象类型的数据方便 模式自由,不需要定义任何模式(schma) 动态查询 完全索引,包含内部对象 复制和故障恢复方便 高效的二进制数据存储 支持c# 平台驱动 2.体系结构 一台服务器可以创建多个Server 实例和数据库,(推荐一天server 机器创建一个实例), 数据库:MongoDB 中的一系列与磁盘有关的物理文件(数据文件,日志文件等). 数据逻辑结构:文档 (documnet) 集合(collection) 数据库(database). d

【zepto学习笔记01】核心方法$()(补)

前言 在很久之前,我们项目有一个动画功能,功能本身很简单,便是典型的右进左出,并且带动画功能 以当时来说,虽然很简单,但是受限于框架本身的难度,就直接使用了CSS3的方式完成了功能 当时主要使用transform与animation实现功能,并且用了一个settimeout执行回调,然后此事便不了了之了 但是出来混总是要还的,这不,最近相似的东西又提了出来,我们当然可以将原来的那套东西拿来用,但是看着那个settimeout总是不是滋味,因为这样捕捉回调的效果以及可能引起的BUG大家都懂,于是就

《solidity学习笔记》chapter 2-solidity基础知识

映射 映射本质上是存储和查找数据所用的键-值对. mapping (uint => string) intToStr; 地址 以太坊区块链由 account (账户)组成,你可以把它想象成银行账户.一个帐户的余额是 以太 (在以太坊区块链上使用的币种),你可以和其他帐户之间支付和接受以太币,就像你的银行帐户可以电汇资金到其他银行帐户一样.每个帐户都有一个"地址",你可以把它想象成银行账号.这是账户唯一的标识符. mapping (address => uint )publi

java学习笔记14--多线程编程基础1

本文地址:http://www.cnblogs.com/archimedes/p/java-study-note14.html,转载请注明源地址. 多线程编程基础 多进程 一个独立程序的每一次运行称为一个进程,例如:用字处理软件编辑文稿时,同时打开mp3播放程序听音乐,这两个独立的程序在同时运行,称为两个进程 进程要占用相当一部分处理器时间和内存资源 进程具有独立的内存空间 通信很不方便,编程模型比较复杂 多线程 一个程序中多段代码同时并发执行,称为多线程,线程比进程开销小,协作和数据交换容易

c 语言学习笔记(一)基础

近期工作上有对C语言算法上的需求,所以要学C,上学的时候没学过,只学过半年的Java(我是专升本,本科学的材料),2015年工作后也学过C,那时候的需求是能看懂就可以,也就没深入研究,用一周撸了一遍C基础,可以看懂了,现在要能自写出来,而且是算法相关的,两年过去了,一些细节更是早就忘光了,只能再深入学习一下了.最后说一句,每个人学东西都是从不会开始,除了班禅我从未听说有人生下来就会需要后天习得的技能,即便是动物也是通过游戏来习得捕猎的,所以说别怕晚,3个月你就可以入门了,而且除了相对论这种接近于