原理:
这里使用的是shadow mapping方式,其原理如下:
1、将场景的深度值预先渲染到 以光源位置为原点、光线发射方向为观察方向的投影坐标系中,形成深度纹理。转换为stage3D后的流程
1、获得深度图:将要投影的模型以光点的视角绘制成图,.xyz取值[0,1],储存深度
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.([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);
// 灯光坐标点
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.([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
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. = Vector.([
// 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. = Vector.([
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.([
// 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.([
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.,vertexNum:int):void{
var data32PerVertex:int = vecVb.length/vertexNum;
var vertexArr:Array = [];
for (var i:int=0;i\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.([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.([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.([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.([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.([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.([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=="";
}
}
}