今天顺风终于把我的<美国末日>寄到了,贴2张图. 嘿嘿~
嘛~玩了好久,终于看到pv里的小萝莉了.
要找个夜深人静的夜晚,一气呵成,才有感觉.
今天顺风终于把我的<美国末日>寄到了,贴2张图. 嘿嘿~
嘛~玩了好久,终于看到pv里的小萝莉了.
要找个夜深人静的夜晚,一气呵成,才有感觉.
逐像素的渲染的精度比顶点的要高,效果也要好.
一般做像素渲染最好的做法就是准备一张法线贴图.
说到法线贴图,实际上就是凹凸贴图. 其设计的目的是为了在简模(顶点数低)上实现复杂的凹凸效果. 而凹凸效果实际上是通过虚假的阴影绘制后给人眼造成的立体假象.
本例使用的是 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.
var jointsNum : int = joints.length;
cpuAnimMatrix ||= new Vector.
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
权重索引,权重对应的关节索引,权重值,权重位置
如果有多个网格,就是以上数据的重复。
之后是精简版解析器的源码,附带的中文的注释。 这几天看看把它扩展成多网格的版本。
先放2张图
第1张是没有HDR效果的。
第2张是HDR效果的
这个效果原本是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]
原计划是丢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.
{
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
我们要做的仅仅几步
[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效果~一定很棒!
之前的这篇文章
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 唉 真坑爹.