在Spine中翻转骨骼动画播放 AS3 与 Cocos2dx

之前一直在用 cocos2dx 的 cocosStudio 自带的骨骼动画
但是为了由于Spine支持网格动画,于是手头的项目都转到支持Spine的骨骼的说。

最近想做一个动画倒播的功能,在之前的 cocosStudio 中,可以通过 API, gotoAndStop(frame) 来实现倒播
到了 Spine 里,貌似就没有这个功能啦

嘛~研究了一下,貌似改起来难度不高。。 自己的本本没装各种编程软件。。。(游戏本嘛) 只有Flash Profession CC, 于是就拿 AS3版本来改改先啦。

话说Spine也是蛮搞的,C++ 版本要合并 Spine C 和 Spine cocos2dx. 而 Flash版本要合并 Spine flash 和 Spine Starling. 而且还没有完整API,丰富的范例(就2个。。还没体现所有功能)

就拿AS3的版本举例好了,最后说下C++中对应的关系就行了
继续阅读“在Spine中翻转骨骼动画播放 AS3 与 Cocos2dx”

在Stage3D中创建God Ray

传说中的上帝之光

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

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

直接上Demo

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

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

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

AGAL2 is here

AGAL2

After a bit of a start and stop first round, the Adobe team recently stated that they were back and working on the AGAL2 specification, a feature that was previously available in an earlier beta version of the Flash player, but later dropped but a feature set that I had been eagerly awaiting.

Anyone who joined us at the last Stage3D online conference will have seen the Adobe Flash team showing off some of the great new player features, which included demonstrations of AGAL2 in action, amongst other features. Today, I’m really excited to see that the new beta player is available for download and the AGAL2 features will be available publicly in the next iteration, which is scheduled for public release next month. Meanwhile, we can download the latest beta player from Adobe labs:

 

What’s new in AGAL2?

AGAL2 offers a set of additional features that can be used to improve the visual fidelity of rendering output in your Stage3D projects. This means that Flash graphics are about to got a lot sexier. Features such as MRT help to increase the speed of rendering by referencing multiple render to target textures from within a single shader pass, we have additional features and memory space. So what can you expect to be playing with? Here is a quick overview of some of the new features:

  • Multiple Render Texture (MRT)
  • Anisotropic filters
  • Conditional forward jump
  • Increases in register space
  • Floating point textures (RGBA_HALF_FLOAT)
  • Partial derivatives (ddx, ddy)
  • Fragment depth (od)
  • Texture anti-aliasing

 

Multiple Render Targets (MRT)

MRT is a feature that will allow a shader to access a number of addressable render targets. This is useful when considering the concepts used in deferred rendering. Deferred rendering is a technique in which multiple images are rendered sequentially and then those images are composited back into a final result. The memory space used during this process is often referred to as the GBuffer and will contain channels such as; albedo, light, normals, depth etc. There are a number of advantages to this technique, perhaps most notably, the ability to render lights to an independent light map, which is then combined with the scene accordingly.

deferred-shading

 

Anisotropic Filtering

Anisotropic filtering is used to improve the artifacts that are caused by viewing a surface at an oblique angle. The image below illustrates the difference in output quality where the texture on the right is being anisotropically filtered.

anisotropic-compare

To make use of this feature is very simple. A new sampler parameter is now available that can be placed within a tex operation as such:

tex ft0, v0, fs0 <2d,anisotropic8x,miplinear,wrap,dxt1>

The anisotropic sampler parameters can be any of the following:

  • anisotropic2x
  • anisotropic4x
  • anisotropic8x
  • anisotropic16x

 

Conditional Forward Jump

If you are familiar with an if statement, you are already well equipped to dealing with conditional jumps on the GPU if you’ve used that with an else statement, then you’re really ready to roll. Prior to this, AGAL required various hacks to achieve similar functionality with branching that required checks against register data. The conditional flow controls of AGAL2 allow for execution of shader code upon a particular condition. These operations are:

  • IFE – (if equal)
  • INE – (if not equal)
  • IFG – (if greater than)
  • IFL – (if less than)
  • ELS – (else)
  • EIF – (else if)

Collectively they serve to provide developers with more granular control over the flow of their shader programs but I must add a quick note to say they should be used judiciously. Using conditional statements inherently means that you will be using more operations, which can impact the performance of your shader code. That being said, conditionals are great when used to simplify workflow and their advantages often outweigh the negative performance hit. It’s probably wise to see what works best and monitor execution speed of your shaders. There is always Adobe Scout or Intel GPA etc for such tasks ;)

 

Increased Register Space

In the previous AGAL spec, we had access to the usual set of registers. Vertex and fragment registers were made available, allowing us to intermediately store resources and make them available to the programmable graphic pipeline. Whilst this is exactly the same with AGAL2, the amount of memory that each shader can access has been increased. Here is a chart that shows a comparison between the numbers of available registers.

AGAL Resisters

We can see from the table that we have access to well over double the number of registers on most registers. Possibly the two most notable would be the number of tokens that can be used, which directly translates to the overall size of your shader programs, and the VC register. My guess is that the large increase of the VC register is balanced like this for one important reason, bone animation. Bone animations are used for manipulating vertices, the technique is also known as skinning, and requires that either matrices or quaternions are uploaded to the GPU via the VC register. The original AGAL spec was quite limited in this regard but the increase means that more than double the number of bones can be uploaded, offering the ability to produce more detailed animations.

 

Half Time Conclusion

Well, that’s half way! We can see that Adobe are really bringing exciting and much awaited set of features and whilst many of them are nothing new to the 3D community, it’s important to realize that the emphasis for Adobe is on platform reach. Adobe are seemingly striving to provide cross platform GPU support to as many users as possible by offering subsets of features that target as many GPU chipsets as possible. In this regard, it makes complete sense that the Flash Player team are trying to ensure that Stage3D doesn’t need to fall back on its software rendering mode and the result of that is a good overall experience to the end-users.

 

引用:http://plugin.io/agal2-is-here/

在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中创建水体反射效果”

在stage3D中创建热流扰动的效果

挖坑,慢慢填的事情, 我最喜欢做了 😀

滑动鼠标查看效果

之前一直想做这个效果的,但是一直没空研究的说。 最近 SParticle 更新了,增加了 Heat 和 Phantom. 后者效果不说了,残影的一贯做法。 正好这套工具 + Away3D 都是开源的,想看看做法,结果 Heat 貌似是 Sparticle 独有, Away3D 里看不到这个效果,索性自己实现, 发现这个并不难。

一共5个步骤
1.对背景进行一次 RenderToTexture, 如果背景有动态物体,则需要每帧 RTT
2.绘制刀光,水波,粒子等几何体顶点,随意
3.使用 RTT纹理 对几何体进行贴图,可以和原贴图 mul 或者 add
4.根据几何体坐标,算出几何体在在 RTT 纹理中的全局坐标,并计算在 RTT 纹理中的全局 uv
5.根据全局uv 对 RTT纹理 进行采样

在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的时候就设置好其索引。