在Stage3D中创建God Ray

传说中的上帝之光

实际上 上帝之光有2种实现方式
1.真的光
2.使用阴影体

阴影体的Demo 在前两篇文章里就有了。
本文是使用真实光来做

直接上Demo

具体做法,参考Demo中的左上角两张Debug图。

将所有遮挡物和光圈都渲染到纹理
然后在片段着色器中判断像素的颜色,是光圈则加亮,不是光圈则设置为无像素,有时候某些材质的颜色会近似光圈颜色,因此在渲染遮挡物的时候,直接将它们的Color输出为0即可,只需要它们的轮廓。
将光圈渲染到纹理并混合之前的轮廓纹理图,这样就能制造出被遮挡的光圈(残缺的光圈), 同时根据光圈的位置,对光圈的像素做指向性衰减像素采样。
最后和主场景的RTT混合即可。

ps: 原本想做HDR的, 但是10张的RTT性能实在是坑不起,还是算了。 过几天把这个GodRay 整合到之前的水体反射里看看效果。

在Stage3D中创建水体反射效果

对于水来说,应该具备下列的特征

  • 全反射,所有的物体都应该能被反射到,除了反射表面自身
  • 基于角度的反射
  • 深度穿透,近距离的水更加透明,远距离的水更加浑浊
  • 加入水纹或者水面法线

对于本文来说只实现前面2点

1.1视差
如果你之前渲染引擎使用单个pass,那么从现在开始,你需要至少2个passes(实际上至少是 N+1, N为可见的反射物体数量)

原因是,我们无法复用主场景的纹理用来反射。首先是因为视锥体可能非常大(比如,观察水表面从一个非常高的角度,我们只能在主场景中看到地面和水,还有被反射的大部分的天空)。 第二是因为视差,很不幸的是反射并不是主场景的完美复制,只是从不同的视觉位置的场景反射。下面的图表现了这个观点。

reflections

这意味着你需要渲染到纹理,我们将要渲染反射到纹理,然后使用这个纹理来渲染我们主场景的水面

因此,要得到反射纹理,我们首先需要通过反射相机来渲染我们的场景 如之前图中的 P` 位置。 首先我们需要找到反射相机的位置,或者更精确的说,相机矩阵的反射(因为我们还需要相机的角度), 我们能通过下面的算法得到反射矩阵

M’camera = Mreflection * Mcamera

Mreflection 是镜面反射矩阵,它能直接计算出基于平面一个点的反射

| 1-2Nx2 -2NxNy -2NxNz -2NxD |
Mreflection = | -2NxNy 1-2Ny2 -2NyNz -2NyD |
| -2NxNz -2NyNz 1-2Nz2 -2NzD |
| 0 0 0 1 |

(Nx,Ny,Nz,D) 是平面的方程 (xNx + yNy + zNz + D = 0) 的系数, 注意: (Nx, Ny, Nz) 也是这个平面的法线向量

Mcamera 是相机的矩阵变换, 在场景中也起着法线的功能, 要获取 ModelView 矩阵,你还需要将相机矩阵翻转

AS的计算函数如下

//计算平面投影矩阵,n为平面法向量,p为平面上一点
public static function getReflectionMatrix(n:Vector3D,p:Vector3D):Vector.{
n.normalize();
var d:Number=-n.dotProduct(p);

var raw:Vector.=Vector.([-2*n.x*n.x+1,-2*n.y*n.x,-2*n.z*n.x,0,
-2*n.x*n.y,-2*n.y*n.y+1,-2*n.z*n.y,0,
-2*n.x*n.z,-2*n.y*n.z,-2*n.z*n.z+1,0,
-2*n.x*d,-2*n.y*d,-2*n.z*d,1]);
return raw;
}

1.2 镜面几何体
事实上,在之前的图形中,我们作弊了,我们旋转了镜面纹理180°使得它与原始图更相似,使得视差的效果能被看到。实际上,镜面图应该看起来像这样。

注意:球型物体的排序顺序在反射贴图中被翻转了,因为三角形顺序在主场景中是逆时针,但在反射中是顺时针

这对于你来说并不是问题,如果你所有的材质都是双面渲染的(比如,你不切除后表面) 或者如果你能设置以类似的方reflections-2式渲染管线。
这样你就能改变剔除方向。在我的例子中,我尽量设置双面都不剔除, 所以所有的东西都会出现在反射纹理中,反则一些几何体不会被渲染。

我们将要开发一个功能,使得相机总是(至少在大多数应用中)与视觉方向成直角并且处于中央,因此我们近需要翻转相机的Y轴方向然后改变渲染顺序就行(在翻转反射纹理后,看起来像 第一张图片中的 (3) 部分).
我们能再通过一个反射矩阵来实现

M”camera = Mreflection * Mcamera * Mflip

1.3 水下翻转
看看下面这张图

reflections-3

我们加入一个水下物体Q到我们的场景中,现在它应该不会出现在反射中,因为因为它不会阻挡射线 PB’B 和 PA’A. 但是我们并没有做射线追踪。 我们通过移动相机到镜像位置 P’ 然后像一般纹理一样渲染反射。 但是如你所见,对象Q阻挡了射线 P’A’A 因此物体Q会出现在我们的反射中

因此我们需要确定,任何物体在反射平面(水面)之下的都不能出现。我们能通过下面3种方式来实现。

1.在GPU中使用额外的裁切面。这可能非常快,也可能非常慢,取决与你的图形卡。
2.在反射渲染中使用斜投影矩阵,你可以在 http://www.terathon.com/code/oblique.html 了解它的相关知识。这是一个非常cool的技术,但是它在远平面中的相机中表现的并不好。
3.手动修剪 Pixel Shaders. 这会浪费一些GPU的循环,但是却非常简单。

我选择使用方法3,因为斜投影矩阵并没有看起来那么好,当相机的角度很大(远平面都变成一个非常奇特的效果)。 剪裁功能非常容易加入到下面的代码中,在所有的 Pixel Shaders 开始之前(更确切的说是用在所有的反射物体上)

//float clipPos = dot (interpolatedVertexEye, clip_plane.xyz) + clip_plane.w;
dp3 result.x eyePos.xyz plane.xyz
add result.x result.x plane.www
//if (clipPos < 0.0) {
// discard;
//}
slt ifReg.x result.x fc0.x //fc0.x == 0

你必须计算顶点Shader(将顶点坐标转换到视野坐标下 VertexEye = Mmodelview * Vertex)中计算平面到视角的差值. 如果你不要裁剪,只要设置 clip_plane 法线(xyz) 为0. 这样所有的像素都会被渲染

1.4 把一切都合并起来
在开始主渲染pass之前(提前或者推迟)做下列这些事

1.创建一个需要被反射的渲染列表(参数为它们的反射面)之后 for each 反射面:
2.计算反射相机矩阵 M”camera = Mreflection * Mcamera * Mflip
3.设定相机矩阵(你可以通过使用翻转投影矩阵来优化,但是在这里不适用)
4.设置裁剪面到反射平面
5.渲染整个场景
6.保存反射纹理应用到反射物体上

如果你使用HDR, 你就不要用使用色调投影到反射纹理上,除非你想做一些非常奇特的效果。

2.渲染反射物体
这一步非常的简单,确保你掌握了所有必须的参数。你还需要决定哪一个 render stage 来做这些事。我使用Transparent.
这样水面就是基于一个场景的透明表面。但是你可以增加一些其他的pass在之前或者之后。

你需要掌握这些
反射相机矩阵 M”Camera
投影矩阵你需要渲染反射 Mprojectionreflection(通常是和你的主相机是使用的同一个矩阵)
反射纹理

2.1 顶点Shader
参数
vertex3D vertex;
Matrix3D o2v_projection;
vertex3D interpolatedVertexObject;

m44 vt0 02v_projection vertex3D(vertex.x,vertex.y,0.1)
interpolatedVertexObject = vt0

我们在这里增加了一个约束,水面在物体本地坐标系中是属于 XY 平面。 它不是确实不是必要的如果你有适当的反射平面,但是我发现这样做起来更容易。仅仅使用XY平面作为反射平面然后适当摆放你的物体(水体)

事实上,有另一个很酷的诀窍,我们可以使用水体的底部作为我们的水体。它将会在Shader中被扁平化,我们可以使用 Z data 来检测水中点的深度。 这部分以后再说。

o2v_projection 仅仅是 Projection * modelView 矩阵的名称。 我喜欢这样给矩阵命名,这样跟助于记忆, 使用它们所描述的坐标系统。 比如 Object To View * Projection

interolatedVertexObject 仅仅是顶点坐标在本地空间的坐标系统,我们将需要在反射纹理中看到它。

2.2 Fragment Shader
参数
matrix3D o2v_projection_reflection
sampler2D refletion_sampler
vertex3D interpolatedVertexObject

vClipReflection = o2v_projection_refletion * vectex(interpolatedVertexObject.xy,0.0,1.0);
//设备反射
vDeviceReflection = vClipReflection.st / vClipReflection.q
//纹理反射
vTextureReflection = vec2(0.5,0.5) + 0.5 * vDeveiceReflection;
//反射纹理颜色
reflectionTextureColor = texture2D(reflection_sampler, vTextureReflection);

//反射纹理的Alpha 可能 > 1
reflectionTextureColor = 1.0;

mov oc reflectionTextureColor

o2vProjection_reflection 是 Projection * ModelView 矩阵, 用来在反射期间进行渲染

Mprojectionreflection * (M”camera)-1 * Mobject

如名字表现的,这是将物体空间坐标系统转换到切面坐标系统的反射相机
在fragment shader 中,我们仅仅重复了整个完整的变换管线在反射渲染中。 渲染然后使用2D坐标系采样。要做到这样,我们首先需要初始化和为转换的顶点物体坐标,因此他们被差值通过vertex shader(interpolatedVertexObject)

设置反射alpha 为 1 因为我使用了 HDR 缓冲,因此它的的 alpha 最终会变成一个非常奇怪的值

继续阅读“在Stage3D中创建水体反射效果”

在Starling中创建一个条带效果

最近无聊把一堆东西移植到这个2D框架上, 这次是条带效果~~ 条带代码来自  @朝朝姐夫 做了些修改

和之前模型一样,还是已扩展类的形式存在,并且可以参与Starling的深度排序,坐标定位,缩放等

效果预览,移动鼠标即可

首先创建一个名为 RibbonTest 类 继承自 Starling 的 DisplayObject

public class RibbonTest extends DisplayObject


public function RibbonTest(speed:Number = 0.1, detailLevel:uint = 10, width:uint = 1)
        {
            super();
           
            this.speed = speed;
            this.touchable = false;
            ribbonWidth = width;
           
            controlPoints = new Vector.<vector3d>();
            for(var i:Number = 0;i < detailLevel; i++)
            {
                controlPoints.push(new Vector3D(0, 0, 0));
            }
            vertexList = new Vector.<Vector3D>(detailLevel*2,true);
            rawPositionsBuffer=new Vector.<number>(detailLevel*6,true);
            rawUvBuffer=new Vector.</number><number>(detailLevel*4,true);
            rawIndexBuffer=new Vector.<uint>((detailLevel-1)*6,true);

            createRibbon();
            createProgram();
            createBuffer();
        }

构造函数中 speed 为缓动的速度,后面会用到经典的缓动公式 (end-start)/speed;

detailLevel 控制点数量,控制点越多条带在转交的效果越细致

继续阅读“在Starling中创建一个条带效果”

在Starling中创建3D模型 widhout Away3D

好久没有写教程神马的了,最近比较忙的说。

回到正题,最近打算在游戏中创建一个3D模型,由于游戏是Starling 2D游戏,因此打算把模型丢到UI里,3D模型出现在2D游戏里的话一定是高端大气上档次!

之前老外写的教程都是 Starling 和 Away3D 杂交, 但是仅仅在UI上摆一个3D模型就整合一个Away3D 实在是大材小用,也可以说是浪费性能, 索性自己来写一个。

先上个图。

一切以兼容为主, 实现一个类 DisplayObject3D 继承自 Starling 的显示对象 DisplayObject

之后override 其的 render 方法

public override function render(support:RenderSupport, parentAlpha:Number):void

do you see that?  这里我们能获取到一个  RenderSupport.

这可是一个好东西, 我们能从中获取到 mvpMatrix 即 模型视图投影矩阵, 有3d 和 2d 2个版本。

然后,我们可以从 Starling.current.context 获取到 GPU的 API模组, 有了这些东西,想干啥都行了。

继续阅读“在Starling中创建3D模型 widhout Away3D”

在Stage3D中实现X-Ray透视效果

在stage3D中实现火炬之光遮挡透视的X-ray效果实际上很简单。
渲染流程如下

1.渲染所有物体+场景
2.正常渲染主角
3.使用X-ray Shader再渲染一次主角

这个渲染顺序是必须的,必须把遮挡物体都事先渲染完毕。
之后展开第3点看看具体的渲染流程

设置 depthFunc 为 Greater, writeDepth 为 false
此时渲染的主角只会显示在 遮挡物体上
之后对该角色的像素着色器,使之显示单一颜色,无论蓝色,黄色,红色都可以
为了使Shader只渲染出轮廓,可以将 相机位置当做灯光位置和模型的法线点乘,求出夹角,将该值作为输出像素的透明度值和RGB进行混合输出。

像素着色器AGAL如下

        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.<Number>([
                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);  
        }

fcXray 的值为
[10/255,72/255,247/255,0.5,1,0,0,0]
占用两个寄存器,前3个值可以调节输出的颜色
fcCameraPos 为外部传入的相机位置,已经归一化和反转过

使用四元数来渲染蒙皮动画

原本没计划使用四元数来渲染蒙皮动画的,但是由于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 来获取四元数

var datas : Vector.<Vector3D> = matrix3D.decompose(Orientation3D.QUATERNION);

datas 是一个数组,其实[0]是位移,[1]是四元数,[2]是缩放
有了四元数后,我们将其通过常量上传到Shader中,然后再Shader中,重新组装成一个 3 * 4 矩阵,然后其他都和之前的一样。

四元数转换回矩阵的方式(CPU模拟)

            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.<Number> = mat.rawData;
            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;

这个矩阵的解析已经被手动转置过了,如果使用CPU渲染,需要手动转置回来,又或者在创建rawData的时候就设置好其索引。

AOI3D整合了OGRE引擎的Mesh解析了,可以加载火炬之光2的所有模型啦

这周迎来了 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

差不多可以给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 来切换动作. 丢帧注意…

Shadow Mapping 1

所谓Shadow Mapping 就是阴影投影,又或是阴影图.
这2天绕了好大弯路,发现自己理解错了.今天突然醒悟过来,可以继续开工了.

之前认为Shadow Mapping 是后期效果.
第一次灯光相机视角渲染到纹理来创建一张阴影图
然后在真实相机视角再渲染一张纹理和阴影图进行对比.

实际这是错的,无解! 一张纹理图无法通过矩阵转换到另一张纹理图的坐标系下.

真相实际上是当取得阴影图后,将其作为公用资源对所有允许被光照的模型开放.
所有模型在自己的顶点程序中,现将顶点转换到灯光相机空间,传入像素程序,然后再转换回真实空间进行输出.
以相机空间的坐标进行着色,与阴影图的像素进行对比. 同时计算法线和灯光向量的角度差.

so~ 要继续扩展我的PlaneMesh 类,提供计算法线的方法
阴影图的实现就丢给下一篇文章~

Aoi3D Alpha

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

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