LayaAir HTML5引擎的3D坐标系与矩阵变换

发表于2017-01-20
评论1 9.5k浏览

  当看到了很多开发者在社区中提到的问题,80%以上的问题我的内心是崩溃的,感觉到大部分开发者是没有3D基础的,甚至很多连2D基础都没有,很多是因为这项新鲜的3D和VR H5产业吸引了他,然后就跟着官网的示例学习。但我觉得,如果没有3D的基础知识。就算你学会了用各种工具里导出3D资源,又能怎么样呢?即便是了解了一些API可以实现你想要的效果,但不知道3D游戏的原理是什么吗?也很难制作出来一个完整的游戏!所以建议大家在学习LayaAir 3D引擎之前,要先去了解一些3D的基础知识。LayaAir的3D教程文档,未来也尽可能多整理一些初级基础文档,希望大家能认真学习。


3D坐标系


  关于3D坐标系,LayaAir 3D与3DMax都是右手坐标系,而unity3D是左手坐标系。我主要讲解laya3d坐标系。

  先说轴向问题,看上面右边的图,别看那个诡异的手,伸出你的右手,手面朝着自己,让大拇指指向X轴方向,食指指向Y轴方向,其他的那一堆手指指向Z轴方向,也是指向自己,保持这个姿势,看好自己的右手,凝视它,记住他,这就是LayaAir 3D的坐标系,理解他,以后在3D场景中对物体进行平移旋转操作不要太简单。左手坐标系,同理,但一定要记住,LayaAir3D的坐标系统是右手坐标系,你能理解这个,也算我没白撸这些废话。

  然后是单位尺度,LayaAir 3D中的单位是米,而不是像素。3DMax中一般默认的系统单位是米,显示单位是毫米。Unity3D中默认的单位也是米。为什么要跟大家介绍这些东西,看下面就知道了,如果大家用过Layabox的fbxTool工具导出过3D模型,然后放到3D场景中,却发现怎么也显示不出来。那是因为,在3Dmax中看到的模型,感觉虽然不大,但那是以毫米为显示单位的,而系统单位是米,实际的模型是他的1000倍大,因为大多数物体都是单面渲染的,内部你是看不到的,因此放到我们LayaAir 3D场景中,你怎么移动照相机位置,也看不到模型,这就是问题所在,可以试试把模型缩小1000倍,此时即可看到。由于Unity3D中的单位尺度与LayaAir 3D相同,所以就不必以上操作。说句工具上的题外话,制作单个模型时,尽量把模型放在3D坐标系原点,会更利于你的开发!

  为了让大家更加深入的了解LayaAir 3D坐标系统,在这里我还要介绍一个简单的概念,矩阵变换,这里不做过深入的介绍,你只需要知道它可以控制3D精灵的旋转,平移,缩放即可;

  想要深入了解矩阵和3D空间物体的关系,可以仔细研究下我们引擎的源码,或者学习下webGL相关的知识!下来我会向大家介绍,怎么用LayaAir 3D API对空间物体进行矩阵变换也就是旋转平移缩放。

  首先我需要在3d空间中,创造点基本的元素,一个平面,一个方体,一个球体。以下我用LayaAir自带的方法创建,这个环节不做过多介绍。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//初始化3d画布
Laya3D.init(0, 0, true);
   
//设置全屏
Laya.stage.scaleMode = Stage.SCALE_FULL;
//显示统计数据
Stat.show();
   
//给舞台添加laya3d场景
var scene:Scene = Laya.stage.addChild(new Scene()) as Scene;
   
//初始化照相机
var camera:Camera = scene.addChild(new Camera()) as Camera;
camera.transform.position = new Vector3(0, 3, 3);
camera.transform.rotate(new Vector3( -45, 0, 0), true, false);
camera.addComponent(CameraMoveScript);
   
//生成平面,其实是一个box,只不过高度很小,可看成一个平面
var plane:MeshSprite3D = scene.addChild(new MeshSprite3D(new BoxMesh(4,4,0.001))) asMeshSprite3D;
var material:StandardMaterial = new StandardMaterial();
material.albedo = new Vector4(1.3, 1.3, 1.3, 1);
material.diffuseTexture = Texture2D.load("res/threeDimen/layabox.png");
plane.meshRender.material = material;
   
//生成坐标中心,其实是球体
var sphere:MeshSprite3D = scene.addChild(new MeshSprite3D(new SphereMesh(0.05,100,100))) as MeshSprite3D;
var material:StandardMaterial = new StandardMaterial();
material.albedo = new Vector4(0, 0, 0, 1);
sphere.meshRender.material = material;
sphere.transform.position = new Vector3(0, 0, 0);
   
//模拟x轴,其实是方体
var x:MeshSprite3D = scene.addChild(new MeshSprite3D(new BoxMesh(2,0.03,0.03))) asMeshSprite3D;
var material:StandardMaterial = new StandardMaterial();
material.albedo = new Vector4(1, 0, 0, 1);
x.meshRender.material = material;
x.transform.position = new Vector3(1, 0, 0);
   
//模拟y轴,其实是方体
var y:MeshSprite3D = scene.addChild(new MeshSprite3D(new BoxMesh(0.03,0.03,2))) asMeshSprite3D;
var material:StandardMaterial = new StandardMaterial();
material.albedo = new Vector4(0, 1, 0, 1);
y.meshRender.material = material;
y.transform.position = new Vector3(0, 0, 0);
   
//模拟z轴,其实是方体
var z:MeshSprite3D = scene.addChild(new MeshSprite3D(new BoxMesh(0.03,2,0.03))) asMeshSprite3D;
var material:StandardMaterial = new StandardMaterial();
material.albedo = new Vector4(0, 0, 1, 1);
z.meshRender.material = material;
z.transform.position = new Vector3(0, 0, 1);
   
//生成方体
var box:MeshSprite3D = scene.addChild(new MeshSprite3D(new BoxMesh(0.3,0.3,0.3))) asMeshSprite3D;
var material:StandardMaterial = new StandardMaterial();
material.albedo = new Vector4(0.5, 0.5, 0.5, 1);
box.meshRender.material = material;


  以上是该程序执行的效果,有一个平面,有一个模拟的LayaAir 3D坐标系,红色的为x轴,绿色的为y轴,蓝色的为z轴,还有一个处于坐标原点的正方体,看到这里,我想大家对laya3d坐标系有了更深的了解! 
  下来我简单介绍下Transform3d这个类的一些简单属性,看下面的代码和注释。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//设置世界位置,会使这个方体在整个世界坐标系的绝对位置为(x,y,z)
box.transform.position = new Vector3(x, y, z);
 
//设置局部位置,会使这个方体在父节点坐标系的相对位置为(x,y,z)
//如果没有加入别的节点中,则position与localPosition的效果相同
box.transform.localPosition = new Vector3(x, y, z);
 
//设置世界旋转,会使这个方体根据世界坐标系的坐标轴进行旋转
//Quaternion四元数,常用来表示3d旋转,可以根据各个轴的旋转弧度计算出
//Quaternion.createFromYawPitchRoll(yaw:Number, pitch:Number, roll:Number, out:Quaternion)
box.transform.rotation = new Quaternion(x, y, z, w);
 
//设置局部旋转,会使这个方体根据自身坐标系的坐标轴进行旋转
//如果没有进行过旋转,则rotation与localRotation效果相同
box.transform.localRotation = new Quaternion(x, y, z, w);
 
//
设置局部缩放,会使这个方体根据自身坐标轴方向进行缩放(x, y, z)倍
box.transform.localScale = new Vector3(x, y, z);

  对于没有接触过3D的同学,会对上面解释也许有些模糊,尤其对世界和局部有什么区别?下面将着重介绍介绍Transform3D两个方法,并在实践中向大家具体介绍。

  第一步:在原始没进行过任何旋转的基础上进行旋转


1
2
3
4
5
6
7
8
9
10
/**
 * 旋转变换。
 * @param   rotations 旋转幅度。
 * @param   isLocal 是否局部空间。
 * @param   isRadian 是否弧度制。
 */
//1.先对box,进行绕x轴世界旋转90度,
//box.transform.rotate(new Vector3(90, 0, 0), false, false);
//或者对box,进行绕x轴局部旋转90度,因为没有被旋转过,世界坐标轴和局部坐标轴相同,效果是一样的.
box.transform.rotate(new Vector3(90, 0, 0), true, false);


  上图,上边为原始的,下边为绕x轴进行世界/局部旋转后的效果。

  第二步:在步骤一的基础上,先进行局部旋转    


1
2
//2.对box,进行绕z轴局部旋转90度,
box.transform.rotate(new Vector3(0, 0, 90), true, false);


  上图,上边为第一步旋转后的,下变为本次局部旋转后的效果,为什么会出现现在的效果呢,因为当第一步进行旋转时,box内部的坐标系跟着同时旋转了,动动脑子,脑补下,进行第一步旋转后,z轴不再朝向你,而是朝向下面。

  第三步:在步骤一的基础上,进行世界旋转


1
2
//3.对box,进行绕z轴世界旋转90度,
box.transform.rotate(new Vector3(0, 0, 90), false, false);



  上图,上边为第一步旋转后的,下边为本次世界旋转后的效果,这个很好理解,就是绕着世界坐标系坐标轴的z轴进行旋转。还有一个小规律,旋转的方向问题,当旋转值为正时,旋转方向为你正对着该旋转轴的正方向的逆时针方向,自己理解。

  第四步:在步骤一的基础上,进行局部平移。


1
2
3
4
5
6
7
/**
 * 平移变换。
 * @param   translation 移动距离。
 * @param   isLocal 是否局部空间。
 */
//4.对box,进行x轴方向局部平移1米,
box.transform.translate(new Vector3(0, 1, 0), true);




  上图,上边为第一步旋转后的,下边为本次进行局部移动后的,由于进行了第一步的旋转,box内部的坐标轴y轴朝向的是自己,所以会看到box向自己移动了1米的距离。

  第五步:在步骤一的基础上,进行世界平移,到这里,我相信大家心中都会有正确的答案了,上代码的答案。


1
2
//5.对box,进行x轴方向世界平移1米,
box.transform.translate(new Vector3(0, 1, 0), false);




  上图,上边为第一步旋转后的,下边为本次进行世界移动后的,box不受自身影响,会向世界坐标系的y轴进行移动1米的距离。有的人或许有疑问,position/localPosition和translate有什么不同,前者是直接设置位置,后者是在当前位置基础上移动相应的距离,旋转同理。

  看到这里,相信大家对LayaAir 3D坐标系已经有了进一步的了解,同时也对3d精灵的矩阵变换有了更多的了解,反复阅读本节内容,相信您能在LayaAir引擎的3D场景中熟练的操纵任何一个物体的旋转平移,缩放。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引