先来复习一下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的灰度图,嘛~比较不明显,要仔细看
depthMap

Continue reading

阴影图实现起来确实很多坑等着我…

没有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.([m_zFar,1,1,1]));
//1,255,255*255,255*255*255
m_context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,1, Vector.([1,255,65025,16581375]));
//[(1.0/255.0),(1.0/255.0),(1.0/255.0),0.0]
m_context.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT,2, Vector.([1/255,1/255,1/255,0]));

//使用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 类,提供计算法线的方法
阴影图的实现就丢给下一篇文章~

花了2天~把 AOI3D 引擎? 的 Alpha 版本撸出来了. so~ 今天是个直接纪念的日子,不管未来AOI3D会不会搁浅,总之是起了个头.
材质目前就实现2只. 纯色材质 ColorMaterial 和 纹理材质 TextureMaterial

多边形目前只有 Plane. 特别喜欢 Flare3D 的 Plane 的API. 于是山寨了过来, 可以定义 +xy,+xz,+yz 平面,以及顶点分割.

明天如果有空的话,把MD5模型 基于 MeshBase 加入到引擎中. 仅仅是无动画的纯Mesh
后天如果有空的话,把Shadow Map 加入引擎,并构建后期处理队列.

今天项目经理开会,貌似下周手头的项目要进入里程碑了,大概会很忙吧~ 希望能如期开始粒子方面的研究啊~~
也许周末都要拿来写代码的(最好不要)

AOI3D ALPHA 的 GIT-HUB: https://github.com/dreamfairy/3DDemo/blob/master/StencilShadow/src/ShadowMap.as

之后所有的功能都会在引擎的基础上搞,嘛~起步可能会艰难点,不过以后扩展开发就轻松了啊.

逐像素的渲染的精度比顶点的要高,效果也要好.
一般做像素渲染最好的做法就是准备一张法线贴图.

说到法线贴图,实际上就是凹凸贴图. 其设计的目的是为了在简模(顶点数低)上实现复杂的凹凸效果. 而凹凸效果实际上是通过虚假的阴影绘制后给人眼造成的立体假象.
本例使用的是 Doom3 模型的MD5模型,其附带的法线贴图是3通道贴图,即 r,g,b 分别表示法线的 x,y,z.
实际上为了节省硬碟空间,还有2通道贴图. 不过貌似现在大家显存和内存都很足的说~

如果将法线贴图丢入shader. 可以直接用float4进行采样,很容易就取出r,g,b 分量.
如果想用CPU计算Normal的话,用如下方式
[cc lang=”actionscript3]
//先取出颜色
var color : uint = bmd.getPixel(x,y);
//然后取出分量
var red : Number = ((color & 0xFF0000) >> 16)/256;
var green : Number = ((color & 0x00FF00) >> 8)/256;
var blue : Number = ((color & 0x0000FF)/256;
[/cc]

接下来shader就容易多了
将法线于灯光进行点乘
然后取出大于0的部分
对于这部分进行纹理混合即可.

doom3 法线贴图

doom3 漫反射贴图

fragmentShader Code
[cc lang=”actionscript3″]
fragmentShader = new AGALMiniAssembler();
fragmentShader.assemble(Context3DProgramType.FRAGMENT,
//纹理采样
“tex ft0, v0, fs0<2d, linear, repeat>n” +
“tex ft1, v0, fs1<2d, linear, repeat>n” +
//灯光点乘法线
“dp3 ft2, ft1, fc2n” +
“neg ft2, ft2n” +
“sat ft2, ft2n”+

//混合环境光
“mul ft3, ft0, ft2n” +
//混合灯光颜色
“mul ft3, ft3, fc1n” +
//输出
“add oc, ft3, fc0”);
[/cc]
ft0 是采样的是模型贴图
ft1 采样的就是法线贴图

直接取出法线贴图,进行灯光的dp3点乘即可。

GitHub : https://github.com/dreamfairy/3DDemo/blob/master/StencilShadow/src/MD5Test.as

Demo 地址: http://www.dreamfairy.cn/blog/work/flash/3d/stage3DNormalTex/md5test.html

啊~Demo中的灯光使用Cube做的,做完才觉得好难看~~ 下次用Sphere 来做吧!
原版计划开搞引擎的,嘛~突然又对shadow map 感兴趣了。 嘛~ 还是再挑战一下自己吧!

逐顶点光照渲染, 实际上就是根据顶点算出当前顶点的法线. 然后将该法线传入像素着色器中. 因为是逐顶点的,顶点之间的距离有时相对较大,因此当把法线传给像素程序的时候,顶点间的阴影是差值计算的.

一般在顶点shader中计算法线的话,都需要正方体或球体, 且中心点在绝对中心,这样算的法线比较准确. 只需要Normalize 各顶点即可. 如果是不规则的多边形,会算不准的说.

github:Source Code

在写这篇文章的时候,还参考了这篇博客: http://blog.norbz.net/2012/04/stage3d-agal-from-scratch-part-vii-let-there-be-light/
里面有很详细的讲解,不过貌似国内无法访问的样子~ 骚年们,翻墙吧!

最后Demo is here
http://www.dreamfairy.cn/blog/work/flash/3d/stage3dlight/

按计划,最近把光线搞完之后,就开始试着写3D引擎了。
虽说“每月一游戏”系列坑掉了,但也不枉费我几个月猛啃书籍的努力。在所开头的几个游戏也是借用别人的引擎。
Hell! It’s about time!

端午节玩了好多游戏~ <勿忘我> 通关了! <神秘海域2> 开坑! <暴雨> 到货! <美国末日> 发货!
玩过瘾先~ Demo什么的都是浮云!

[勿忘我] 通关啦! 剧情还是很赞的.就是打斗多到有点繁琐了. 老妈和老爸居然是最终BOSS. 还洗白了. 义军老大一开始就觉得他是坏人,居然结局是个自我牺牲的英雄! 三观尽毁!

md5Anim文件中各字段,我的理解是
hierarchy 是层级关系
数据内容为: 骨骼名称,父级索引,flag, 索引起始位
其中flag是一个用来做二进制求与关系来决定当前的数据是 偏移Tx,Ty,TZ 还是 旋转Qx,Qy,Qz
索引起始位是 frame 动画帧字段中数据的起始索引

bounds 是包围盒
通过 min 和 max 可以决定当前动作的 AABB. 不做碰撞检测的话,一般用不到

baseframe 基础帧信息
实际上模型的各骨骼动画都是以此为起始值进行偏移的
6个值分别是 Tx,Ty,Tz,Qx,Qy,Qz

frame 具体的动画帧信息
数据内容为: 帧索引, numAnimatedComponents 定义的长度数量的 动画数据。 其中的数值具体有什么用,需要查询 hierarchy 中的 flag.

具体转换骨骼动画的代码如下
[cc lang=”actionscript3″]
private function CalcMeshAnim(frameData : MD5FrameData) : void
{
//取出关节数据
var joints : Vector. = md5MeshParser.md5_joint;
var jointsNum : int = joints.length;

cpuAnimMatrix ||= new Vector.(jointsNum * 4, true);

var joint : MD5Joint;
var parentJoint : MD5Joint;
for(var i : int = 0; i < jointsNum; i++) { //从基本帧开始偏移 var baseFrame : MD5BaseFrameData = md5AnimParser.baseFrameData[i]; var animatedPos : Vector3D = baseFrame.position; var animatedOrient : Quaternion = baseFrame.orientation; //将帧数据替换掉基本帧中对应的数据 var hierachy : MD5HierarchyData = md5AnimParser.hierarchy[i]; var flags : int = hierachy.flags; var j : int = 0; if(flags & 1) //tx animatedPos.x = frameData.components[hierachy.startIndex + j++]; if(flags & 2) //ty animatedPos.y = frameData.components[hierachy.startIndex + j++]; if(flags & 4) animatedPos.z = frameData.components[hierachy.startIndex + j++]; if(flags & 8) animatedOrient.x = frameData.components[hierachy.startIndex + j++]; if(flags & 16) animatedOrient.y = frameData.components[hierachy.startIndex + j++]; if(flags & 32) animatedOrient.z = frameData.components[hierachy.startIndex + j++]; //计算w var t : Number = 1 - animatedOrient.x * animatedOrient.x - animatedOrient.y * animatedOrient.y - animatedOrient.z * animatedOrient.z; animatedOrient.w = t < 0 ? 0 : - Math.sqrt(t); var matrix3D : Matrix3D = animatedOrient.toMatrix3D(); matrix3D.appendTranslation(animatedPos.x, animatedPos.y, animatedPos.z); //取出当前关节 joint = joints[i]; if(joint.parentIndex < 0){ joint.bindPose = matrix3D; }else{ //如果该关节有父级,需要先附带上父级的旋转和偏移 parentJoint = joints[joint.parentIndex]; matrix3D.append(parentJoint.bindPose); joint.bindPose = matrix3D; } matrix3D = joint.inverseBindPose.clone(); matrix3D.append(joint.bindPose); var vc : int = i * 4; if(useCPU){ cpuAnimMatrix[vc] = matrix3D; }else{ context.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, vc, matrix3D, true); } } [/cc] Continue reading

虽然之前看了相关MD5的文件结构资料,自认为实现解析应该没问题,嘛~毕竟是停留在理论, 还是动手来实践一下。
之后看到某同学写了个MD5的解析Demo,于是研究了下发现,这实际上是个 Away3D移植出来的精简解析程序,只支持单个Mesh. 近期把它改进成支持多个Mesh吧!

网络上有很多人都发过MD5的文件格式讲解,嘛~我觉得按自己的思路整理一下也发一次。 just for myself;

numJoints 110 //总关节数
numMeshes 4 //总网格数

joints {} 关节节点,其数据顺序为
关节名称,关节父级ID,关节位置,关节旋转

关节在对无动画的纯md5Mesh模型来说没有意义,渲染时无需用到。

mesh {} 网格节点,其数据顺序为
vert token
顶点索引,顶点纹理U,顶点纹理V,顶点初始权重,顶点最大权重

tri token
三角形索引,构成三角形对应的三个顶点的索引

weight token
权重索引,权重对应的关节索引,权重值,权重位置

如果有多个网格,就是以上数据的重复。

之后是精简版解析器的源码,附带的中文的注释。 这几天看看把它扩展成多网格的版本。

Continue reading