之前的这篇文章
http://www.dreamfairy.cn/blog/index.php/2013/05/05/picking-object-in-stage3d.html

其中的公式可能有点难懂=.= , 而且有个bug, 即相机旋转后无法选择物体.

嘛~今天我们来换一种方式来实现,并且支持旋转的相机.

世界上代码,里面有注释

[cc lang=”actionscript3″]
/**
* 包围球射线检测
* param o 射线原点
* param d 射线角度
* param c 圆心
* param r 半径
*/
public static function RaySphereIntersect(o : Vector3D, d : Vector3D, c : Vector3D, r : Number) : Boolean
{
var intersect : Boolean;
var s : Number;
var l : Vector3D = c.subtract(o); //圆心到射线原点的距离
s = d.dotProduct(l); //以射线起点开始,以射线的方向步进和包围球的长度的距离

var ll : Number = l.length * l.length; //以射线的起点为圆心,以和包围球的距离为半径假设一个球形
var rr : Number = r * r;

if(s < 0 && ll > rr) return false; //s < 0 表示目标在射线之后, ll > rr 表示射线不在圆内

var mm : Number = ll – s*s; //圆心垂直于射线方向的长度
if(mm > rr) return false; //和射线构成的三角形不在圆内

var q : Number = Math.sqrt(rr – mm); //圆心垂直于射线方向的点到射线碰撞的圆表面点的距离
var t : Number; //射线起点和圆相交点的距离
if(ll > rr) //当射线在圆外
t = s – q;
else
t = s + q;

return true;
}
[/cc]

[cc lang=”actionscript3″]
stage.addEventListener(MouseEvent.CLICK, onClick);
private function onClick(e:MouseEvent) : void
{
//将鼠标点击位置偏移视口中央
var sceenX : Number = stage.mouseX;
var sceenY : Number = stage.mouseY;

var viewX : Number = (sceenX * 2 / stage.stageWidth) – 1;
var viewY : Number = (-sceenY * 2 / stage.stageHeight) + 1;

var ray : Ray = new Ray();

//将射线转换到相机所在空间
var viewMat : Matrix3D = m_camera.getViewMatrix().clone();
ray.origin = viewMat.transformVector(new Vector3D(0,0,0));

//将相机反转,相机的平移和旋转总是和世界坐标系相反的
viewMat.invert();
ray.direction = viewMat.deltaTransformVector(new Vector3D(viewX,viewY,1));
ray.direction.normalize();

//取出相机的缩放值
var scale : Vector3D = m_proj.decompose()[2];

//将射线转到目标所在空间
var cubeMesh : CubeMesh;
for each(cubeMesh in m_cubeList)
{

//相交检测
var cubePos : Vector3D = cubeMesh.transform.position;
cubePos.x *= scale.x;
cubePos.y *= scale.y;
cubePos.z *= scale.z;

if(Ray.RaySphereIntersect(ray.origin, ray.direction, cubePos, 2))
trace(“碰撞”, m_cubeList.indexOf(cubeMesh));
}
}
[/cc]

btw: 该来的总会来的,工作开始转向用cocos2d-x来制作手游了. stage3D这块还会继续研究的,只是成为副业了. o(︶︿︶)o 唉 真坑爹.

继续地形渲染的教程~

现在我们有了凹凸不平的地形了,要如何在上面行走呢~
我们只需要一个函数就可以完成,下面我将逐行讲解这些代码,最后放出完整的函数

[cc lang=”actionscript3″ nowrap=”false”]
public function getHeight(x : Number, z : Number) : Number
[/cc]

创建一个函数来获取高度,传进来的值为要行走的物体的世界坐标x,y. 记住这个函数是以地形的中心点在世界中心为前提计算了,如果你的地形有偏移,请加载偏移值.

[cc lang=”actionscript3″ nowrap=”false”]
x = (m_width >> 1) + x;
z = (m_depth >> 1) – z;
//地形上的x,y. 就是以地形的中心点为前提再偏移. m_width >> 1 和 m_depth >> 1 就是将xz平面的地方 / 2 后,取到中心点.

x /= m_cellSpacing;
z /= m_cellSpacing;

var col : int = Math.floor(x);
var row : int = Math.floor(z);
//我们的地形的每个顶点的间距是 m_cellSpacing ,本demo中的值是10. /= 的方法就是求 col 和 row 的索引值,相信做过2D游戏A*寻路,或者地图区块平铺的同学知道.
[/cc]

现在我们取到当前的物体所在地形上的哪个顶点上, 要计算当前顶点所在平面的法线,也可以说是计算当前地形的高度和倾斜角度.
为了计算这些东西,我们需要这个平面的4个顶点,然后计算他们的差值.

[cc lang=”actionscript3″ nowrap=”false”]
var A : Number = getHeightMapEntry(row, col);
var B : Number = getHeightMapEntry(row, col + 1);
var C : Number = getHeightMapEntry(row + 1, col);
var D : Number = getHeightMapEntry(row + 1, col + 1);
[/cc]

这个 getHeightMapEntry 是上一教程中的函数,其内容就是取出灰度图中的数值. 内部是这样的
return m_heightMap[row * m_numVertsPerRow + col];

[cc lang=”actionscript3″]
var dx : Number = x – col;
var dz : Number = z – row;
//我们将当前位置偏移到这个平面的左上角

var height : Number = 0;
if(dz < 1 - dx) //当 dz < 1-dx 时,表示我们在左上半部分的三角形上 { var uy : Number = B - A; var vy : Number = C - A; height = A + Utils.lerp(0,uy,1 - dx) + Utils.lerp(0,vy,1 - dz); //计算这个三角形邻边和对边的线性插值. Utils.lerp就是计算线性插值的函数. }else{ //当 dz > 1-dx 时,表示我们在右下半部分的三角形上
var uy : Number = C – D;
var vy : Number = B – D;

height = D + Utils.lerp(0,uy,1 – dx) + Utils.lerp(0,vy,1 – dz);
}

return height;
[/cc]

最后线性差值的函数为
[cc lang=”actionscript3″]
public static function lerp(a : Number, b : Number, t : Number) : Number
{
return a – (a * t) + (b * t);
}
[/cc]

最近写教程写的有点虚脱,so~ 这篇就是纯粹的吐槽. 上个月有看到老外聊天室中交流flash3D的东西,然后一个小哥做了个flare3D的帆船效果,很赞,里面包含了海浪,粒子,投影. 除了粒子,就海浪我没有实现了,于是突发奇想的自己也实现下.

实现方式其实就是先使用 Bitmapdata创建柏林噪音的灰度图, 利用shaderJob的多线程将灰度图的像素填充进byteArray. 最后用byteArray 更新海面的顶点. 海面的阴影可以利用垂直光计算,填充到贴图上.

下面这是Demo啦.

by the way~ 其实描边什么的我也都实现了,下周开始转向模型了.

思来想去,我决定先写这篇教程,地形阴影留着明晚再写.拾取物体涉及到射线,这块比较难懂,自认为比地形什么要难得多,于是决定先写拾取物体来梳理下自己的思路.

首先 屏幕上点称为 S(x,y); 视口上的点称为 P(x,y) 实际上在flash中,屏幕和视口的宽高一样,只是中心点屏幕在左上角,视口在屏幕中央.
但这里还是列举下它们的关系

Sx = Px(Width / 2) + X + Width / 2;
变形得出 Px = (2 * Sx) / Width – 1
这公式将屏幕的坐标从左上角移动到 屏幕中央(视口的起点) 后再偏移

Sy = -Py(Height / 2) + Y + Height / 2;
变形得出 Py =-(2 * Sy) / Height + 1
这公式将屏幕的坐标从左上角移动到 屏幕中央(视口的起点) 后再偏移

由于视口是投影的平面,所以我们的Pz 总是 1
所有的物体通过透视矩阵显示在屏幕上的时候,会由于相机缩放会影响到物体的大小.
这些缩放值存储在透视矩阵的 00 和 11 位置即
[00 01 02 03]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
因此我们需要把我们求出的 Px, 和Py 除以缩放值,得出经过透视投影后物体坐标

然后我们要定义一个射线类,其中包含了射线的起点 和 射线的方向. 射线的方向是个单位向量.
[cc lang=”actionscript3″]
package
{
import flash.geom.Vector3D;

public class Ray
{
/**起点**/
public var origin : Vector3D;

/**方向**/
public var direction : Vector3D;

public function Ray()
{
}
}
}
[/cc]

射线的起点总是视口的中心点,即屏幕中央. 所以 origin 总是 0,0,0
用一个等式来表示方向向量 u = P – P0; P0即起点, origin.
如果P点是射线穿过投影平面上的点,那么射线的方向向量 u = 投影点P – origin = (Px,Py,1) – (0,0,0) = P; 因此我们只需要直接把投影坐标标准化转换成单位向量表示方向即可.

下面的函数来帮助我们创建一条射线,起点是屏幕中央,方向是点击屏幕后转换到投影平面的坐标标准化.

[cc lang=”actionscript3″]
/**
* 计算射线
*/
private function calcPicingRay(x : int, y : int) : Ray
{
var px : Number = 0;
var py : Number = 0;

var vp : Rectangle = new Rectangle(0,0,stage.stageWidth,stage.stageHeight);

var rawData : Vector. = m_proj.rawData;
px = (((2 * x) / vp.width) – 1.0) / rawData[0];
py = (((-2 * y) / vp.height) + 1.0) / rawData[5];

var ray : Ray = new Ray();
ray.origin = new Vector3D(0,0,0);
ray.direction = new Vector3D(px,py,1);

return ray;
}
[/cc]

在代码中,你可能发现 ray.direction 没有进行标准化 .normalize() .不必担心,在另一个函数里还要对射线进行加工.

现在我们已经有一条射线了,但是它只停留在投影平面上,为了进行跟3D物体的相交测试,我们还需要把射线转换到3D空间中.

做法很简单,只需要把我们的射线右乘相机矩阵即可. 即VM Vector * Matrix; 乘以的结果还是一个向量. 在转换原点时,要将原点向量的分量w = 1. 转换方向时,分量 w = 0;
转换完毕后,我们将方向的标准化 补上.

代码如下
[cc lang=”actionscript3″]
/**
* 转换射线
*/
private function transformRay(ray : Ray, t : Matrix3D) : void
{
//转换射线的原点, w = 1
ray.origin.w = 1;
ray.origin = Utils.subjectMat(ray.origin,t,ray.origin);

//转换射线的方向, w = 0;
ray.direction.w = 0;
ray.direction = Utils.subjectMat(ray.direction,t,ray.direction);

ray.direction.normalize();
}
[/cc]

Utils.subjectMat 是我自己写的向量乘以矩阵的实现函数, 我不知道stage3D中是否有该API,如果有,请告知我是哪个. 这里我也提供下这个函数

[cc lang=”actionscript3″]
//向量左乘矩阵
public static function subjectMat(vec : Vector3D, mat : Matrix3D, out : Vector3D) : Vector3D
{
var data : Vector. = mat.rawData;

if(null == out) out = new Vector3D();

out.x = (vec.x * data[0] + vec.y * data[4] + vec.z * data[8] + vec.w * data[12]);
out.y = (vec.x * data[1] + vec.y * data[5] + vec.z * data[9] + vec.w * data[13]);
out.z = (vec.x * data[2] + vec.y * data[6] + vec.z * data[10] + vec.w * data[14]);
out.w = (vec.x * data[3] + vec.y * data[7] + vec.z * data[11] + vec.w * data[15]);

return out;
}
[/cc]

现在我们有了3D空间中的射线了,可以做相交测试了.
我们可以遍历视口中的所有三角形,判断哪些三角形被射线击中了,虽然很精确,但是性能并不高.
因此在本例中我们用包围球来碰撞,实际上你也可以自己试试AABB碰撞.

给出包围球的圆心C 和 半径R. 使用下面的恒等式就能测试点 P 是否在包围球上.

||P-C|| – R = 0;

向量 P 到 圆心 C 的长度表示为 ||P-C||. 如果等于半径C 表示P在包围球上.
现在把我们的射线公式带入这个相交的公式中 P = P0 +tU;
|| P0 + tU – C || – R = 0;

由此我们我们可以退出一元二次方程
At^2 + Bt + C = 0; 这个公式很熟悉吧~ 貌似初中就学过了.
t0 = (-B + Sqrt(B * B – 4AC) / 2;
t1 = (-B – Sqrt(B * B – 4Ac) / 2;
因为U是表示方向的单位向量,因此A = 1; 带入即可求出 t0,t1
当t0 或者 t1 >= 1 时,表示切线,相交 等情况. <0 表示完全没有相交,或者在物体的前方. 最后我们通过下面的函数来做碰撞检测 由于仅仅是为了做碰撞检测,这里没有具体的实现包围球,但是本例的Cube顶点的间距是 从 -1 ~ 1 而圆心是在 Cube 的中央,因此 Cube的直径是 2. 那么半径 cube.radius 我们就知道是 1 了. [cc lang="actionscript3"] /** * 碰撞检测 */ private function raySphereIntTest(ray : Ray, cube : CubeMesh) : Boolean { var v : Vector3D = ray.origin.subtract(cube.position); var b : Number = 2 * ray.direction.dotProduct(v); var c : Number = v.dotProduct(v) - (cube.radius * cube.radius); var discriminant : Number = (b * b) - (4 * c); if(discriminant < 0) return false; discriminant = Math.sqrt(discriminant); var s0 : Number = (-b + discriminant) / 2; var s1 : Number = (-b - discriminant) / 2; if(s0 >= 0 || s1 >= 0)
return true;

return false;
}
[/cc]

最后的最后我们将这些代码串起来,就是下面的Demo了.
首先是 stage.addEventListener(MouseEvent.CLICK, onClick);
然后是
[cc lang=”actionscript3″]
private function onClick(e:MouseEvent) : void
{
var ray : Ray = calcPicingRay(e.stageX,e.stageY);
var viewInverse : Matrix3D = m_viewMatrix.clone();
viewInverse.invert();
transformRay(ray,viewInverse);

if(raySphereIntTest(ray,m_cubeList[0])){
tip.text = “碰撞”;
}
else{
tip.text = “W,S,A,D 控制飞船移动n方向键控制相机移动”;
}
}
[/cc]

你想问 m_cubeList[0] 是哪个? 我实际上我创建的30个Cube中的第一个,而且为了让我们知道哪个是它,我让它一直旋转.

每次刷新 Cube的位置都会变化.
Demo is Here

昨天生成地形后贴图
terrain

这张图,用肉眼很难分辨高海拔和低海拔区域,毕竟没有参考物.类似雪盲症的效果.
那么我们可以动态创建贴图,在海拔较高的地方填充白色,感觉像雪山.海拔较低的地方填充亮黄色,像沙地,其他部分就填充绿色.

还记得之前我们灰度图读取出来的值都存在 m_heightMap 中吗? 这个数组存储的长度和我们贴图的像素数量一样的.这样我们就可以遍历像素的同时,取出当前像素对应的高度值,然后根据高度值填充我们想要的颜色.

代码如下
[cc lang=”actionscript3″]
public function genTexture(light : Vector3D) : void
{
var texWidth : int = m_numVertsPerRow;
var texHeight : int = m_numVertsPerCol;

var emptyBmd : BitmapData = new BitmapData(texWidth, texHeight,false);
emptyBmd.lock();

for(var i : int = 0; i < texHeight; i ++) { for(var j : int = 0; j < texWidth; j++) { var c : uint; var height : Number = getHeightMapEntry(i,j) / m_heightScale; if(height < 42.5) c = Utils.BEACH_SAND; else if(height < 85) c = Utils.LIGHT_YELLOW_GREEN; else if(height < 127.5) c = Utils.PUREGREEN; else if(height < 170) c = Utils.DARK_YELLOW_GREEN; else if(height < 212.5) c = Utils.DARKBROWN; else c = Utils.WHITE; emptyBmd.setPixel32(j,i,c); } } emptyBmd.unlock(); setTexture(emptyBmd); } public function getHeightMapEntry(row : int, col : int) : int { return m_heightMap[row * m_numVertsPerRow + col]; } public function setTexture(data : BitmapData) : void { m_texture = Utils.getTextureByBmd(data, m_context3D); } [/cc] 当创建好这张动态贴图并贴上之后,就是下图的样子了. followHeight

Continue reading

本文已抄送到天地会 : http://bbs.9ria.com/thread-192787-1-1.html

这是效果图
terrain

首先先准备一张高度图
heightMap
你可以用Photoshop制作,在创建图片的时候就选择灰度图. 当你制作完毕一张灰度图的时候,保存的格式最好是8Bit的RAW文件,这样每个像素都被保存在 unsignedbyte 中,且没有各种烦人的文件头信息.有且只有像素信息.

为了方便测试,在AS中我们将这个RAW文件嵌入到项目中,用下面代码

[cc lang=”actionscript3″]
[Embed(source=”../../source/coastMountain64.raw”,mimeType = “application/octet-stream”)]
private var terrainData : Class;
[/cc]

当文件嵌入后,我们就开始着手解析了.解析的方法很简单,按顺序读取字节流,然后存进一个 Vector. 数组中.

[cc lang=”actionscript3″]
private function parseHeightMaps(data : ByteArray) : Boolean
{
if(null == data || 0 == data.bytesAvailable) return false;

var len : uint = data.bytesAvailable;
for(var i : int = 0; i < len; i++){ m_heightMap.push(data.readUnsignedByte()); } return true; } [/cc] 现在我们有了地形的数据了,为了让其显示出来,我们开始创建顶点和索引 顶点的格式是 [x,y,z,u,v] 5个数据 如果每个顶点之间是紧密排列的话,其中u,v的递增量可以通过下面的方式计算出. [cc lang="actionscript3"] var uCoordIncrementSize : Number = 1.0 / 每列顶点数; var vCoordIncrementSize : Number = 1.0 / 每行顶点数; [/cc] 由于高度图是 64 * 64 的大小,我们做 64 * 64的循环来取顶点 startZ = 0, endZ = 63; startX = 0, endX = 63; 如果顶点间是紧密排列的. 那么 m_cellSpacing 为1.0 [cc lang="actionscript3"] for(z = startZ; z >= endZ; z -= m_cellSpacing)
{
j = 0;
for(x = startX; x <= endX; x += m_cellSpacing) { //计算当前顶点缓冲的索引,避免死循环 var index : int = i * m_numVertsPerRow + j; m_rawVertex.push(x,m_heightMap[index],z,j * uCoordIncrementSize,i * vCoordIncrementSize); j++; } i++; } [/cc] 解析完顶点后,就要开始计算索引了. indexbuffer

通过这张图,可以理解到,2个三角形和顶点数组中索引对应的上下两行的关系.
当前顶点的 index, index + 1 和下一行的 index 构成第一个三角形, 另一半三角形也同样. 他们共用 C 顶点.
转换成代码就是如下的形式
[cc lang=”actionscript3″]
private function computeIndices() : void
{
var baseIndex : int = 0;

for(var i : int = 0; i < m_numCellsperCol; i++) { for(var j : int = 0; j < m_numCellsPerRow; j++) { m_rawIndex[baseIndex] = i * m_numVertsPerRow + j; m_rawIndex[baseIndex + 1] = i * m_numVertsPerRow + j + 1; m_rawIndex[baseIndex + 2] = (i + 1) * m_numVertsPerRow + j; m_rawIndex[baseIndex + 3] = (i + 1) * m_numVertsPerRow + j; m_rawIndex[baseIndex + 4] = i * m_numVertsPerRow + j + 1; m_rawIndex[baseIndex + 5] = (i + 1) * m_numVertsPerRow + j + 1; baseIndex += 6; } } trace("索引解析完毕"); } [/cc] 现在我们有了顶点,也有了索引.就可以将其显示出来了. 贴图什么随便设定就好了. demo is here

你可能会觉得地形很难看出高低的感觉,这是因为没有地形没有阴影,当然这是下一篇进阶的功能了.

对于投影来说,本质和镜面反射一样。
创建一个模型,然后将该模型的所有顶点通过矩阵坍缩到一个平面上。
然后在对应的平面使用模板缓冲,再将该模型绘制到这个平面上。
当然直接投影的话,地面上的阴影并不像影子,因为他的贴图跟模型是一样,因此我们要把它的贴图换成黑色贴图,半透明也可。

先上图
shadow

转换成投影顶点的矩阵为函数为
[cc lang=”actionscript3″]
//计算光照投影矩阵
public static function getShadowMatrix(n : Vector3D, p : Vector3D, light : Vector3D, out : Vector.) : Vector.
{
n.normalize();
//平行光的话,需要标准化光线方向,并逆转平面法线
if(light.w == 0){
light.normalize();
n.scaleBy(-1);
}
var d:Number=-n.dotProduct(p);
var k:Number=n.x*light.x+n.y*light.y+n.z*light.z+d*light.w;

if(null == out) out = new Vector.();

out.push(k-n.x*light.x, -n.x*light.y, -n.x*light.z, -n.x*light.w);
out.push(-n.y*light.x, k-n.y*light.y, -n.y*light.z, -n.y*light.w);
out.push(-n.z*light.x, -n.z*light.y, k-n.z*light.z, -n.z*light.w);
out.push(-d*light.x, -d*light.y, -d*light.z, k-d*light.w);

return out;
}
[/cc]

之后还是跟渲染镜面反射一样, 开启模板缓冲
[cc lang=”actionscript3″]
/**
* 绘制投影
*/

//关闭深度测试
m_context.setDepthTest(false,Context3DCompareMode.LESS);

//设置模板值为0,之后绘制的三角形会使模板值递增
m_context.setStencilReferenceValue(0);
m_context.setStencilActions(Context3DTriangleFace.BACK,
Context3DCompareMode.EQUAL, Context3DStencilAction.INCREMENT_SATURATE);

m_wall.render(m_cameraMatrix, m_projMatrix, m_shader);

m_context.setStencilReferenceValue(1);

//混合模式为 1 * dest + 0 * 0 = dest. 目标颜色为墙体颜色和阴影颜色的混合
m_context.setBlendFactors(Context3DBlendFactor.SOURCE_COLOR, Context3DBlendFactor.DESTINATION_COLOR);

m_shadowTeapot.render(m_cameraMatrix,m_projMatrix,m_shader);
m_shadowTeapot.moveTo(m_teapot.position.x,m_teapot.position.y,m_teapot.position.z);
m_shadowTeapot.rotation(t, Vector3D.Y_AXIS);

m_context.setCulling(Context3DTriangleFace.FRONT);
[/cc]

绘制完投影后,我们要清空缓存,然后在绘制镜面来填充模板,之后都和镜面反射的render一样了.

[cc lang=”actionscript3″]
/**
* 绘制反射
*/

//清空模板
m_context.clear(0,0,0,1,1,0,Context3DClearMask.STENCIL);

//设置模板值为0,之后绘制的三角形会使模板值递增
m_context.setStencilReferenceValue(0);
m_context.setStencilActions(Context3DTriangleFace.FRONT,
Context3DCompareMode.EQUAL, Context3DStencilAction.INCREMENT_SATURATE);

m_mirrorMesh.render(m_cameraMatrix,m_projMatrix,m_shader);

//混合模式为 1 * dest + 0 * 0 = dest. 目标颜色为镜子颜色和飞船颜色的混合
m_context.setBlendFactors(Context3DBlendFactor.DESTINATION_COLOR, Context3DBlendFactor.ZERO);
m_context.setCulling(Context3DTriangleFace.BACK);
m_context.setStencilReferenceValue(1);

m_mirrorTeapot.render(m_cameraMatrix,m_projMatrix,m_shader);
m_mirrorTeapot.moveTo(m_teapot.position.x,m_teapot.position.y,m_teapot.position.z);
m_mirrorTeapot.rotation(t, Vector3D.Y_AXIS);
[/cc]

demo is here

昨天那一帖的延续. http://www.dreamfairy.cn/blog/index.php/2013/04/30/stencil-buffer-in-stage3d.html

由于模板缓冲总是在进行的,因此,很多并不想被缓冲的物体也会被牵连,结果就是绘制不出,又或者是错误的也被缓冲了.
那么对于无辜者的那些三角形,在绘制前,我们可以设置一个模板检测方式为总是检测,这样模板总是能检测成功,成功条件为不改变模板值,这样为之后要用到的对象正确的来改变它.

同时,对于不同深度的物体,有时候无法被缓冲,因为有可能是他们确实位于某些面之后,这里我们需要关闭深度缓存再绘制,绘制完毕后,再开启深度缓冲.

那么昨天的 renderScene() 函数要做些修改,并且加上一个作为无辜者的大矩形放在后面.
buffer
[cc lang=”actionscript”]
private function onEnter(e:Event) : void
{
renderScene();
}

private function renderScene() : void
{
m_context.clear(0,0,0,1,1,0);

m_context.setDepthTest(true,Context3DCompareMode.LESS);
m_context.setStencilActions(Context3DTriangleFace.FRONT_AND_BACK,
Context3DCompareMode.ALWAYS, Context3DStencilAction.KEEP);
drawBiggerCube();

m_context.setDepthTest(false,Context3DCompareMode.LESS);
m_context.setStencilReferenceValue(0);
m_context.setStencilActions(Context3DTriangleFace.FRONT_AND_BACK,
Context3DCompareMode.EQUAL, Context3DStencilAction.INCREMENT_SATURATE);

drawCube();

m_context.setStencilReferenceValue(1);

drawTriangle();
m_context.present();
}

private function drawBiggerCube() : void
{
t += .1;
m_modelMatrix.identity();
m_modelMatrix.appendTranslation(0,0,-1);
m_modelMatrix.appendScale(2,2,2);
m_finalMatrix.identity();
m_finalMatrix.append(m_modelMatrix);
m_finalMatrix.append(m_viewMatrix);
m_finalMatrix.append(m_projMatrix);

m_context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,m_finalMatrix,true);

m_context.setProgram(m_textureShader);
m_context.setTextureAt(0,brickTexture);
m_context.setVertexBufferAt(0, m_rectVertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
m_context.setVertexBufferAt(1,m_rectVertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_2);
m_context.drawTriangles(m_rectIndexBuffer,0,2);
}

private function drawTriangle() : void
{
m_modelMatrix.identity();
m_finalMatrix.identity();
m_finalMatrix.append(m_modelMatrix);
m_finalMatrix.append(m_viewMatrix);
m_finalMatrix.append(m_projMatrix);

m_context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,m_finalMatrix,true);

m_context.setProgram(m_shader);
m_context.setTextureAt(0,null);
m_context.setVertexBufferAt(0, m_triangleVertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
m_context.setVertexBufferAt(1,null);
m_context.drawTriangles(m_triangleIndexBuffer,0,1);
}

private var t : Number = 0.0;
private function drawCube() : void
{
t += .1;
m_modelMatrix.identity();
m_modelMatrix.appendTranslation(Math.sin(t),0,0);
m_finalMatrix.identity();
m_finalMatrix.append(m_modelMatrix);
m_finalMatrix.append(m_viewMatrix);
m_finalMatrix.append(m_projMatrix);

m_context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX,0,m_finalMatrix,true);

m_context.setProgram(m_textureShader);
m_context.setTextureAt(0,texture);
m_context.setVertexBufferAt(0, m_rectVertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
m_context.setVertexBufferAt(1,m_rectVertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_2);
m_context.drawTriangles(m_rectIndexBuffer,0,2);
}
[/cc]

好了,接下来,就是今天的重点,镜面反射了.
Continue reading

模板缓冲在传统的DirectX中,广泛使用在镜面反射,遮罩,投影等效果.
在stage3d中同样可以使用,而且API被极大的整合了.
DirectX中的API:略

模板缓冲的区域就是用户的屏幕大小,它可以用来做一些检测,然后设置像素的填充方式.
简单说来,当缓冲为空时,我们可以让程序做一些事情,当缓冲被填满时,我们可以做另一些事情.做完后,考虑清空缓冲.

以龙书中镜面效果来说
我们先设置模板缓冲为空.
然后我们在屏幕上画一面镜子,镜子的像素将被绘制到缓冲中.
我们在镜子的表面画一个茶壶,茶壶的投影仅在缓冲区中有镜子的像素的位置进行替换.
最后清空缓冲.

当然,在绘制中还有很多注意事项的,比如为了让茶壶紧贴镜子,必须关闭深度缓冲.待投影绘制完毕后,开启深度缓冲.

下面是一个简单的demo.
先开启模板缓冲,当绘制三角形后,缓冲在有像素的地方从0递增到1
我们先绘制一个矩形,并随便贴一张图.
在值为1的缓冲区域绘制一个三角形.

结果就是,三角形仅在有矩形的区域内被绘制了.
地址如下:http://www.dreamfairy.cn/blog/work/flash/3d/stage3dStencilTest/StencilTest0.html

完全的镜面效果,就下篇文章再发了,也许明天心情好的话~
主要代码如下:
Continue reading