还有细胞分裂要通关。。。
主机还落下一堆游戏。。。暴雨,美国末日。。。
还有细胞分裂要通关。。。
主机还落下一堆游戏。。。暴雨,美国末日。。。
在stage3D中实现火炬之光遮挡透视的X-ray效果实际上很简单。
渲染流程如下
1.渲染所有物体+场景
2.正常渲染主角
3.使用X-ray Shader再渲染一次主角
这个渲染顺序是必须的,必须把遮挡物体都事先渲染完毕。
之后展开第3点看看具体的渲染流程
设置 depthFunc 为 Greater, writeDepth 为 false
此时渲染的主角只会显示在 遮挡物体上
之后对该角色的像素着色器,使之显示单一颜色,无论蓝色,黄色,红色都可以
为了使Shader只渲染出轮廓,可以将 相机位置当做灯光位置和模型的法线点乘,求出夹角,将该值作为输出像素的透明度值和RGB进行混合输出。
像素着色器AGAL如下
[cc lang=”actionscript3″]
public override function getFragmentProgram():ByteArray
{
var shaderConstant : ShaderConstants;
var index : uint;
index = m_params.fragmentShaderConstants.push(new ShaderConstants(fcXray)) – 1;
shaderConstant= m_params.fragmentShaderConstants[index];
shaderConstant.vector = Vector.
10/255,72/255,247/255,0.5,
1,0,0,0]);
shaderConstant.numRegisters = 2;
var code : String =
“nrm ft1.xyz v”+vProjPos+”.xyzn” +
“dp3 ft0.a ft1.xyz fc”+fcCameraPos+”.xyzn”+
“sat ft0.a ft0.an”+
“mov ft0.r fc”+fcXray+”.rn”+
“mov ft0.g fc”+fcXray+”.gn”+
“mov ft0.b fc”+fcXray+”.bn”+
“mul ft0.rgb ft0.rgb ft0.aaan”+
“mov oc ft0n”;
return new AGALMiniAssembler().assemble(Context3DProgramType.FRAGMENT, code);
}
[/cc]
fcXray 的值为
[10/255,72/255,247/255,0.5,1,0,0,0]
占用两个寄存器,前3个值可以调节输出的颜色
fcCameraPos 为外部传入的相机位置,已经归一化和反转过
因为工作原因,开始转向cocos2d-x开发方向了。
自然的,凭着之前引擎的经验偏向底层渲染研究。
在此期间看了两本书
《cocos2d-x 权威指南》
《cocos2d-x 高级开发教程》
不得不说,第一本书完全是API介绍,也许适合新手入门,但是于我,只是烂书一本。
第二本书还是不错的,最后的项目有全套源码,教程的风格也很有我的风格,哈哈,就是文章中不一定贴全代码,只贴核心,后期还要自己领会补全。
言归正传,在 《高级开发教程》 中有一篇是实现海底水纹效果的文章,里面代码不全,且代码和Shader并没有对应起来,于是我决定按照自己思路来将其补全,并贴上完整源码。
先上效果图:
动态GIF图地址: http://pan.baidu.com/share/link?shareid=1858640040&uk=3221530571
原本没计划使用四元数来渲染蒙皮动画的,但是由于stage3D的限制,因此考虑在AOI3D中,使用四元数来渲染吧。
由于Adobe的畏首畏尾,尽可能的向下兼容,又不肯把兼容的选择权交给开发者,因此stage3D只提供了128个常量寄存器。
在3D程式中,一般使用 4*4 矩陣來描述骨骼的旋轉-位移-縮放。 而一個常量寄存器只能存儲 4個浮點型(在DX10中已经支持整数型了,不过stage3D只有DX9级别),
因此我们只能渲染 128/4 = 32 根骨骼, 为了确定模型的位置,我们还需要上传 ModelViewProjection 矩阵, 那么我们只能使用31根骨骼了,而且还要放弃各种灯光及滤镜效果了。
使用四元数的优势,开源引擎Ogre的模型文件就是使用四元数进行蒙皮了,这也是AOI3D能支持蒙皮动画的原因。 一个四元数使用 x,y,z,w 来表示 3个轴的旋转,因此它只需要使用1个寄存器, 之后再使用一个寄存器来存储位移即可。 对于缩放,一般蒙皮动画的缩放都是 1, 因此可以忽略。 那么我们可以渲染 124 / 2 = 62 跟骨头了。
这里不讲解四元数的原理,直接说使用。
你可以通过支持四元数的动画文件直接解析出四元数的值,也可以通过 Matrix3D 来获取四元数
[cc lang=”actionscript3″]
var datas : Vector.
[/cc]
datas 是一个数组,其实[0]是位移,[1]是四元数,[2]是缩放
有了四元数后,我们将其通过常量上传到Shader中,然后再Shader中,重新组装成一个 3 * 4 矩阵,然后其他都和之前的一样。
四元数转换回矩阵的方式(CPU模拟)
[cc lang=”actionscript3″]
var quaIndex : uint = index / 2;
var rotation : Vector3D = m_jointConstant[quaIndex];
var translate : Vector3D = m_jointConstant[quaIndex + 1];
var mat : Matrix3D = new Matrix3D();
var vt0 : Vector3D = new Vector3D();
vt0.x = 2 * rotation.x * rotation.y;
vt0.y = 2 * rotation.x * rotation.z;
vt0.z = 2 * rotation.x * rotation.w;
vt0.w = 2 * rotation.y * rotation.z;
var vt1 : Vector3D = new Vector3D();
vt1.x = 2 * rotation.y * rotation.w;
vt1.y = 2 * rotation.z * rotation.w;
vt1.z = rotation.x * rotation.x;
vt1.w = rotation.y * rotation.y;
var vt2 : Vector3D = new Vector3D();
vt2.x = rotation.z * rotation.z;
vt2.y = rotation.w * rotation.w;
var rawData : Vector.
xx – yy – zz + ww
rawData[0] = vt1.z – vt1.w – vt2.x + vt2.y;
2xy – 2zw
rawData[1] = vt0.x – vt1.y;
2xz + 2yw
rawData[2] = vt0.y + vt1.x;
0
rawData[3] = translate.x;
2xy + 2zw
rawData[4] = vt0.x + vt1.y;
-xx + yy – zz + ww
rawData[5] = vt1.w – vt1.z – vt2.x + vt2.y;
2yz + 2xw
rawData[6] = vt0.w – vt0.z;
0
rawData[7] = translate.y;
2xz + 2yw
rawData[8] = vt0.y – vt1.x;
2yz – 2xw
rawData[9] = vt0.w + vt0.z;
-xx – yy + zz + ww
rawData[10] = vt2.x – vt1.z – vt1.w + vt2.y;
0
rawData[11] = translate.z;
0
rawData[12] = 0;
0
rawData[13] = 0;
0
rawData[14] = 0;
1
rawData[15] = 1;
mat.rawData = rawData;
[/cc]
这个矩阵的解析已经被手动转置过了,如果使用CPU渲染,需要手动转置回来,又或者在创建rawData的时候就设置好其索引。
天空盒这货,其实核心技术就是CubeMap. 给一个Cube的6个面,贴上不同的纹理。
最笨的方法是创建6个平面,每个面贴一个单张纹理罢了。
实际上CubeMap 的采样方式也差不多, 只不过是用面法线来代替UV 曲纹理罢了。
最开始查资料的时候,资料说用面法线来取6个面,我以为是是用6个法线来代表6个面, 然后就纠结在如果对应上片段着色器。后来经过试验,才发现网上说的并不细致,作为一个规则形状(立方体,球形),直接归一化的它们的顶点,比如立方体8个顶点生成8个法线,传入片段着色器即可,完全不要被6个面对应的6个法线这些网络上的讲解误导了。 GPU会根据顶点间的法线差值正确的取出对应的 纹理。
在 Flare3D 的Demo中,有6个面在一张纹理上的贴图, 我不知道是否真有如此的API,讲6张图自动的拆开上传。
我的做法是自己创建6个裁剪矩形进行6张图分拆,然后上传
[cc lang=”actionscript”]
private function initClipList() : void
{
m_clipList = new Vector.
m_clipList.push(new Rectangle(2*m_size,m_size,m_size,m_size));
m_clipList.push(new Rectangle(0,m_size,m_size,m_size));
m_clipList.push(new Rectangle(m_size,0,m_size,m_size));
m_clipList.push(new Rectangle(m_size,2*m_size,m_size,m_size));
m_clipList.push(new Rectangle(m_size,m_size,m_size,m_size));
m_clipList.push(new Rectangle(3*m_size,m_size,m_size,m_size));
}
[/cc]
m_clipList 的分拆顺序,就是CubeMap上传纹理的顺序
如下
[cc lang=”actionscript”]
public static const POSITIVE_X:uint = 0;
public static const NEGATIVE_X:uint = 1;
public static const POSITIVE_Y:uint = 2;
public static const NEGATIVE_Y:uint = 3;
public static const POSITIVE_Z:uint = 4;
public static const NEGATIVE_Z:uint = 5;
[/cc]
对于CubeMap 我们需要人为的上传所有miplevel
层级数量依照纹理大小而定, 如果你的纹理是 515 * 512
那么依次上传的level 是 512*512 -> 256 * 256 -> 128 * 128 -> 64 * 64 – > 32* 32 -> 16 * 16 -> 4 * 4 -> 2 * 2 -> 1* 1
6个面中,每个面都要这么上传。
到此为止,CubeMap 实际上已经是完成了。
如果要做成天空盒,我们还要修改一下渲染的Shader
在游戏中的天空盒一般是不随着玩家移动而缩放的, 给人一种无法到达的感觉。 因此我们可以以 viewPorj 的矩阵来渲染它, 由于没有model矩阵,本质就是天空盒总是以相机的位置为中心,同时接收相机的旋转,但不缩放。
如果要做不接受旋转的天空盒,甚至直接用 proj 矩阵渲染就够了。
是用的Shader如下
顶点着色器
[cc lang=”actionscript3″]
public override function getVertexProgram():ByteArray
{
return new AGALMiniAssembler().assemble(Context3DProgramType.VERTEX,
“m44 op va”+vaPos+” vc”+vcProjection+”n”+
“nrm vt0.xyz va”+vaPos+”.xyzn”+
“mov vt0.w vc”+vertexConstIndex+”.xn”+
“mov v0 vt0n”);
}
[/cc]
第一行是 proj 矩阵 * Cube的顶点
第二行是 归一化个顶点,计算出法线
第三行是 将该法线的w 分量设置为1(不缩放就是靠它了)
第四行是 将法线传给像素着色器去取 CubeMap
像素着色器
[cc lang=”actionscript3″]
public override function getFragmentProgram():ByteArray
{
return new AGALMiniAssembler().assemble(Context3DProgramType.FRAGMENT,
“tex oc v0 fs”+fcTexture+”
}
[/cc]
这周迎来了 ALPHA 0.2 更新速度开始加快啦。。。
同样的,工作室的任务也开始繁重了。。。艹,怎么一起都来了。。。
AOI3D ALPHA 0.2
新增了
封装了Shader. AGAL Code 不再Object3D 内部创建了,也不再由 IMaterial接口提供了。
修复了 PlaneMesh 创建的平面偏移值错误,以及顶点顺序错误的bug
添加了SKyBox
添加了Ogre引擎的Mesh模型解析
本周有时间的话,尽量完成下一步的计划更新
优化骨骼动画,目前是CPU模拟,丢帧严重
修复NormalMap
新增Sprite3D
新增视锥体剔除
新增Ogre骨骼动画解析
整合ShadowMapping
话说~回到家 居然拉不到gitHub 所以不能复写了。。
结果写博客的时候,居然又同步成功了。。。
算了~明天去公司整合后再穿github 吧~~
主页直接贴个Demo, 支持精确选取的说
差不多可以给AOI3D 来个版本号了吧~ 先来个 ALPHA 0.1
目前包含:
模型解析:MD5,OBJ
简易的模型骨骼动画播放器
灯光:点光,平行光
后期处理:HDR,BLUR,COLOR
投影:ShadowMapping, PlaneMapping
拾取:像素拾取
接下来的工作:
优化骨骼动画,目前是CPU模拟,丢帧严重
优化Shader,目前是零散的AGAL组合,之后要封装到Shader中
修复NormalMap
修复PlaneMesh, 目前虽然没问题,但总觉顶点没有对齐的样子
新增SkyBox
新增Sprite3D
最后:目前的Demo(精确拾取) Demo is Here
试试按键盘的 1,2,3,4 来切换动作. 丢帧注意…
先来复习一下ShadowMapping的原理.
首先以灯光的位置渲染场景,这里可以将灯光位置理解为场景的另一部相机.
在开启RTT(渲染到纹理) 的时候,开启深度测试,将深度信息纹理中.成为一张”深度图”
然后我们以正常的相机再渲染一次场景,在渲染的时候顶点着色器依然用正常的相机的投影矩阵输出,但是要把灯光试图投影矩阵转换后的顶点交给像素着色器.
在像素着色器中,将顶点和深度图的像素对齐,对比他们的深度值. 如果当前顶点的深度值大于深度图中的深度值,说明这个顶点肯定被小于它的某个顶点给挡住了,那么该顶点周围的像素都在阴影中. 否则就是在阳光中.
上一篇说到了制作深度图.
把颜色存进32位颜色中
通过
“mul ft0 ft0 fc1n”+
fc1 = 1,255,255*255,255*255*255
嘛~觉得这有点麻烦,毕竟解压的时候还需要将 RGB 反向解码.
换一种方式来搞深度图.
Vertex Program
[cc lang=”actionscript3”]
//将顶点转入灯光投影空间mvp
“m44 vt0 va0 vc0n”+
//输出顶点
“mov op vt0n”+
//传入像素着色器
“mov v0 vt0n”);
[/cc]
Fragment Program
[cc lang=”actionscript3″]
“mov ft0.xyz, v0.zzzn”+
“mov ft0.w fc0.yn”+
“div ft0.xyz, ft0.xyz, fc0.xn”+
“mov oc ft0n”);
[/cc]
ft0 即 rgb. 我们将 v0.zzz 赋值给 rgb 既是将深度值存进了rgb中
fc0.y = 1 将 ft0.w = 1 保持像素的透明度总是1
fc0.x = zFar div ft0.xyz ft0.xyz fc0.x 将深度值缩放到投影矩阵的 zFar 之内.
最后将像素输出.
输出后我们会得到如下的深度图. 由于是 0~255的灰度图,嘛~比较不明显,要仔细看
阴影图实现起来确实很多坑等着我…
没有stage3D的资料…是最坑,需要自己摸索
还有几个坑分别是
1.如何创建深度图
2.如何进行深度图采样
3.深度测试
4.AOI3D的封装
今天先从创建深度图开始
首先
[cc lang=”actionscript3″]
//准备阴影图,并开启为RTT优化
m_shaderMap = m_context.createTexture(
1024,
1024,
Context3DTextureFormat.BGRA,
true
);
/**
* 阴影shader
* 将顶点丢入fragmentShader
*/
var depthPassVertexShader : AGALMiniAssembler = new AGALMiniAssembler();
depthPassVertexShader.assemble(Context3DProgramType.VERTEX,
“m44 vt0 va0 vc0n”+
“mov op vt0n”+
“mov v0 vt0n”);
var depthPassFragmengShader : AGALMiniAssembler = new AGALMiniAssembler();
depthPassFragmengShader.assemble(Context3DProgramType.FRAGMENT,
//将深度缩放在 zFar 之内
“div ft0 v0.z fc0.xn”+
//将颜色编码为 32 位浮点型数据存入RGBA
“mul ft0 ft0 fc1n”+
//取出小数部分
“frc ft0 ft0n”+
//255掩码
“mul ft1 ft0.yzww fc2n”+
“sub ft0 ft0 ft1n”+
“mov oc ft0n”);
[/cc]
阴影图的大小决定阴影图的精度,越小的阴影图边缘锯齿越严重. 解决方案有很多啦,比如应用模糊滤镜后,再作为查询图.
然后准备我们的shader程序
之后开始渲染,先设置将接下来的模型操作渲染到阴影图.
[cc lang=”actionscript3″]
m_context.clear();
m_context.setRenderToTexture(m_shaderMap,true);
[/cc]
之后开始模型的渲染
准备灯光空间中, 灯光的位置,灯光的相机,灯光的投影
[cc lang=”actionscript3″]
m_lightModel.identity();
m_lightModel.appendRotation(-90,Vector3D.X_AXIS);
m_lightModel.appendRotation(t, Vector3D.Y_AXIS);
m_lightModel.appendScale(.1,.1,.1);
m_lightModel.appendTranslation(0,-6,-20);
m_lightView.identity();
m_lightView.appendTranslation(-50,0,0);
m_lightView.pointAt(new Vector3D(0,-6,-20),CAM_FACING,CAM_UP);
m_lightView.invert();
m_lightProj.identity();
m_lightProj.append(m_lightModel);
m_lightProj.append(m_lightView);
m_lightProj.append(m_projMatrix);
//vc0 灯光投影矩阵
m_context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, m_lightProj, true);
//fc0 m_zFar 最大深度
m_context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,0, Vector.
//1,255,255*255,255*255*255
m_context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,1, Vector.
//[(1.0/255.0),(1.0/255.0),(1.0/255.0),0.0]
m_context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,2, Vector.
//使用shadowMapShader渲染模型
m_context.setProgram(m_shaderPassShader);
for(var i : int = 0; i < md5Result.meshDataNum; i++){ var vertexBuffer : VertexBuffer3D = md5Result.vertexBufferList[i]; var uvBuffer : VertexBuffer3D = md5Result.uvBufferList[i]; var indexBuffer : IndexBuffer3D = md5Result.indexBufferList[i]; m_context.setVertexBufferAt(0, vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3); // m_context.setVertexBufferAt(1,uvBuffer,0,Context3DVertexBufferFormat.FLOAT_2); m_context.drawTriangles(indexBuffer); } //输出到纹理 m_context.present(); [/cc] 最后是这张阴影图的预览Demo 鼠标中键可控制相机. 原理相机的像素颜色加深~ Demo is here:http://www.dreamfairy.cn/blog/work/flash/3d/stage3dshadowMapping/ShadowMapFinalTest.html
下一篇再实现 深度图查询并渲染~
所谓Shadow Mapping 就是阴影投影,又或是阴影图.
这2天绕了好大弯路,发现自己理解错了.今天突然醒悟过来,可以继续开工了.
之前认为Shadow Mapping 是后期效果.
第一次灯光相机视角渲染到纹理来创建一张阴影图
然后在真实相机视角再渲染一张纹理和阴影图进行对比.
实际这是错的,无解! 一张纹理图无法通过矩阵转换到另一张纹理图的坐标系下.
真相实际上是当取得阴影图后,将其作为公用资源对所有允许被光照的模型开放.
所有模型在自己的顶点程序中,现将顶点转换到灯光相机空间,传入像素程序,然后再转换回真实空间进行输出.
以相机空间的坐标进行着色,与阴影图的像素进行对比. 同时计算法线和灯光向量的角度差.
so~ 要继续扩展我的PlaneMesh 类,提供计算法线的方法
阴影图的实现就丢给下一篇文章~