花了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

先放2张图
第1张是没有HDR效果的。
cryb

第2张是HDR效果的
crya

这个效果原本是GLSL的Shader. 我先移植到 Pixel Bender 上验证自己的理解没有错后,再移植到AGAL上,最后和之前的Blur效果融合。

这是Pixel Bender 的代码
[cc lang=”C++”]

kernel Lum
< namespace : "cn.dreamfairy"; vendor : "dreamfairy"; version : 1; >
{
input image4 baseTex;
input image4 bloomTex;
output pixel4 dsts;

//外部参数
parameter float fAvgLum
< minValue:float(0.1); maxValue:float(0.5); defaultValue:float(0.5); >;

parameter float fDimmer
< minValue:float(0); maxValue:float(1); defaultValue:float(1); >;

void
evaluatePixel()
{
float4 texCol = sampleNearest(baseTex,outCoord());
//计算平均亮度
float vLum = 0.27 * texCol.r + 0.67 * texCol.g + 0.06 * texCol.b;
//亮度缩放
float vLumScaled = fDimmer * vLum / fAvgLum;

texCol = float4(texCol.r * vLumScaled, texCol.g * vLumScaled,
texCol.b * vLumScaled, texCol.a * vLumScaled);
texCol = texCol / (float4(1.0) + texCol);

float4 texBloom = sampleNearest(bloomTex, outCoord());
dsts = texBloom + texCol;
}
}
[/cc]

Continue reading

原计划是丢HDR效果的,但是实际上HDR效果中要用到Blur效果的,因此先丢Blur

模糊效果是后期效果中比较简单效果. 这里不做径向模糊,不做正态分布,纯粹的模糊就是将当前像素的颜色以周围的像素为平均值来求出.
根据这个原理,当拿到uv值时,只需要做一次3*3的遍历,来求出周围8个像素,然后将像素叠加/8即可获得平均值,剩下的事就是用AGAL来求出了.

不上图了,做个代码笔记,之后就是Demo

周围像素的数量为圈数 n – 1 * 3 * 24, 其中 n > 1; 当 n 小于等于 1时. 像素的数量为8
整理为代码就是
var index : uint = step <= 1 ? 1 : (step - 1) * 3; var num : uint = 8 * index; 之后我们需要用拼接的方式来构造AGAL [cc lang="actionscript3"] private function getBlurAgal(step : uint, force : uint, coord : String, pTempIndex : int, pSaveIndex : int, pConstantOffset : int, pTextureIndex : int, constant : Vector., pInitIndex : int) : String
{
var data : Array = new Array();
var isFirstSave : Boolean = true;
var loopNum : uint = 0;
var stepX : Number = force / stage.stageWidth;
var stepY : Number = force / stage.stageHeight;
var index : uint = step <= 1 ? 1 : (step - 1) * 3; var num : uint = 8 * index; var tempIndex : int = pTempIndex; var saveIndex : int = pSaveIndex; var textureIndex : int = pTextureIndex; var initIndex : int = pInitIndex; var constantOffset : int = pConstantOffset; constant = constant || new Vector.();
constant.length = 0;

data.push(“mov ft” + tempIndex + “, fc” + initIndex);

//横向遍历
for(var i : int = -step; i <= step; i++) { //纵向遍历 for(var j : int = -step; j <= step; j ++) { //跳过中心区 if(i == 0 && j == 0) continue; constant.push(i * stepX, j * stepY); data.push("add ft" + tempIndex + ".xy, " + coord + ".xy, fc" + constantOffset + (loopNum % 2 == 0 ? ".xy" : ".zw")); data.push("tex ft" + tempIndex + ", ft" + tempIndex + ", fs" + textureIndex + "<2d, repeat, linear, mipnone>“);
data.push(
isFirstSave ? “mov ft” + saveIndex + “, ft” + tempIndex :
“add ft” + saveIndex + “, ft” + saveIndex + “, ft” + tempIndex);
isFirstSave = false;
if(++loopNum % 2 == 0) constantOffset++;
}
}

data.push(“div ft” + saveIndex + “.rgb, ft” + saveIndex + “.rgb, fc” + constantOffset + “.w”);
data.push(“mov oc, ft” + saveIndex);

if(constant.length / 4 > 28){
return getBlurAgal(1,1,coord,pTempIndex,pSaveIndex,pConstantOffset,pTextureIndex,constant,pInitIndex);
}

constant.push(0,0,0,num);
return data.join(“n”);
}
[/cc]

var stepX : Number = force / stage.stageWidth;
var stepY : Number = force / stage.stageHeight;
求出当前屏幕大小纹理的u和v的步进值

data.push(“mov ft” + tempIndex + “, fc” + initIndex);
初始化用来保存周围像素的值的寄存器, 初始化值这里是 0,0,0,0

(loopNum % 2 == 0 ? “.xy” : “.zw”)
由于常量寄存器数量只有28个,因此如果每个float4 我们只有xy,那么zw就被浪费了,Blur的层级也无法提升,因此基于物尽其用的原则,每2个uv使用一个float4.

if(constant.length / 4 > 28)
由于常量寄存器只有28个, 其中每4个长度为一个 float4 因此当超过28个寄存器时,将模糊滤镜重置为最小滤镜

data.push(
isFirstSave ? “mov ft” + saveIndex + “, ft” + tempIndex :
“add ft” + saveIndex + “, ft” + saveIndex + “, ft” + tempIndex);
isFirstSave = false;
用来记录当前累加颜色的寄存器必须初始化,可以给它丢个initIndex中的值,但实际上这里直接把第一次的得出的颜色值mov进去就当作它的初始化值就够了,不需要额外的初始化.

使用方法:
var m_blurAgal : String = getBlurAgal(3, 1, “v0″, 0, 1, 1, 0, m_blurConstant, 0); fragmentProgram.assemble(Context3DProgramType.FRAGMENT, m_blurAgal);

初始化一个圈数为3,强度为1,uv位置为”v0”,纹理位置在0,常量偏移为1,临时寄存器为1,常量数组,初始值在常量位置1
编译agal

最后是Demo: click me

Github地址: click me

也许后期处理这个词很难理解,实际上就是对 GPU渲染完成的帧再进行处理。
在stage3D中,我们可以将GPU的图形渲染到纹理中,然后对这张图片进行各种处理,比如雾化,HDR, 模糊, 扭曲 等等 …

实际操作起来非常简单,代码也非常的少。 先上效果图,然后是源码,最后是Demo

postProcessing

我们要做的仅仅几步
[cc lang=”actionscript3″]
private function renderPostProcessing() : void
{
// Render the scene to the scene texture
m_context.setRenderToTexture(m_sceneTexture, true);
renderCube();
m_context.setRenderToBackBuffer();

m_context.setProgram(m_normalShader);
m_context.setTextureAt(0, m_sceneTexture);
m_context.clear(0.5,0.5,0.5);

m_modelMatrix.identity();
m_modelMatrix.appendTranslation(0,0,1.21);

m_finalMatrix.identity();
m_finalMatrix.append(m_modelMatrix);
m_finalMatrix.append(m_viewMatrix);
m_finalMatrix.append(m_projMatrix);

m_context.setVertexBufferAt(0, m_sceneVertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
m_context.setVertexBufferAt(1,m_sceneVertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_2);

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

m_context.drawTriangles(m_sceneIndexBuffer,0,2);
m_context.setTextureAt(0,null);
m_context.setVertexBufferAt(0,null);
m_context.setVertexBufferAt(1,null);
}
[/cc]

重点就3句,设置GPU输出图像到指定的纹理。
因此事先需要创建一张和视口等大的贴图
m_context.setRenderToTexture(m_sceneTexture, true);
绘制原始的场景
renderCube();
现在纹理已经被填充了,因此我们设置GPU输出到后缓冲,也就是视口
m_context.setRenderToBackBuffer();

之后我们就可以对纹理做些奇怪的事情了。
你可以按1,2,3,4,5 来看各种效果, 按0重置
Demo is Here : demo

我已经把源码上传到github
https://github.com/dreamfairy/3DDemo/blob/master/StencilShadow/src/PostProcessing.as

接下来,我打算来实现HDR效果~一定很棒!