3D变换

在前面《3D概念入门》讲过了LayaAir中的坐标系和几个基础数学工具,这些基础概念的一个重要应用,就是三维变换,三维变换也是三维图形世界的基础。LayaAir中实现三维变换用到了Transform3D这个类,包含了平移变换、旋转变换、缩放变换等。

本节以一个立方体模型演示这些变换。在LayaAir IDE中创建一个“3D空项目”,打开项目后,IDE已经默认创建了一个立方体。在Scene2D的属性面板中,新建一个组件脚本,添加装饰器暴露cube的属性,代码如下:

@property({ type: Laya.Sprite3D })
public cube: Laya.Sprite3D;

然后在IDE中,将Cube节点拖入装饰器暴露的属性入口,即可用代码控制Cube节点了。

1. 平移变换

平移变换的定义如下:

    /**
     * 平移变换。
     * @param     translation 移动距离。
     * @param     isLocal 是否局部空间。
     */
    translate(translation: Vector3, isLocal: boolean = true): void {
        if (isLocal) {
            Matrix4x4.createFromQuaternion(this.localRotation, Transform3D._tempMatrix0);
            Vector3.transformCoordinate(translation, Transform3D._tempMatrix0, Transform3D._tempVector30);
            Vector3.add(this.localPosition, Transform3D._tempVector30, this._localPosition);
            this.localPosition = this._localPosition;
        } else {
            Vector3.add(this.position, translation, this._position);
            this.position = this._position;
        }
    }

平移变换方法translate()有两个参数,第一个translation表示移动的距离,它是一个Vector3类型的变量,既包含移动距离,也包含了移动方向。第二个isLocal判断是否是局部空间,默认为true是局部空间,若传入false则是世界空间。

如果物体在局部空间和世界空间的起始位置都是一样的,并没有经过旋转等变换,那么它在两种空间中移动的结果是相同的,在第5节中会说明二者的区别。

使用平移变换移动立方体,在x、y和z方向上分别移动距离1,示例代码如下:

const { regClass, property } = Laya;

@regClass()
export class NewScript extends Laya.Script {

    @property({ type: Laya.Sprite3D })
    public cube: Laya.Sprite3D;

    // 平移距离
    private translate: Laya.Vector3 = new Laya.Vector3(1, 1, 1);

    constructor() {
        super();
    }

    /**
     * 组件被激活后执行,此时所有节点和组件均已创建完毕,此方法只执行一次
     */
    onAwake(): void {
        // 平移cube
        this.cube.transform.translate(this.translate, false);
    }

}

效果如图1-1所示,其中1是原位置,2是平移后的位置。

1-1

(图1-1)

2. 旋转变换

关于旋转,在Transform3D中提供了两种旋转接口:

2.1 rotate()

第一种是角度/弧度旋转rotate(),定义如下:

    /**
     * 旋转变换。
     * @param     rotation 旋转幅度。
     * @param     isLocal 是否局部空间。
     * @param     isRadian 是否弧度制。
     */
    rotate(rotation: Vector3, isLocal: boolean = true, isRadian: boolean = true): void {
        var rot: Vector3;
        if (isRadian) {
            rot = rotation;
        } else {
            Vector3.scale(rotation, Math.PI / 180.0, Transform3D._tempVector30);
            rot = Transform3D._tempVector30;
        }

        Quaternion.createFromYawPitchRoll(rot.y, rot.x, rot.z, Transform3D._tempQuaternion0);
        if (isLocal) {
            Quaternion.multiply(this._localRotation, Transform3D._tempQuaternion0, this._localRotation);
            this.localRotation = this._localRotation;
        } else {
            Quaternion.multiply(Transform3D._tempQuaternion0, this.rotation, this._rotation);
            this.rotation = this._rotation;
        }
    }

旋转方法rotate()有三个参数。第一个rotation表示旋转幅度,也就是绕x、y、z方向旋转的角度。第二个isLocal判断是否是局部空间,默认为true是局部空间,若传入false则是世界空间。第三个isRadian判断是否是弧度制,默认为true是弧度制,传入false则表示角度值。

使用旋转变换rotate()旋转立方体,绕y轴不停旋转,采用角度值,示例代码如下:

const { regClass, property } = Laya;

@regClass()
export class NewScript extends Laya.Script {

    @property({ type: Laya.Sprite3D })
    public cube: Laya.Sprite3D;

    // 旋转角度
    private rot: Laya.Vector3 = new Laya.Vector3(0, 1, 0);

    constructor() {
        super();
    }

    /**
     * 组件被激活后执行,此时所有节点和组件均已创建完毕,此方法只执行一次
     */
    onAwake(): void {
        //设置定时器执行,定时重复执行(基于帧率)
        Laya.timer.frameLoop(1, this, this.animate);
    }

    private animate(): void {
        // 旋转立方体(四元数)
        this.cube.transform.rotate(this.rot, false, false);
    }

}

旋转的效果如动图2-1所示:

2-1

(动图2-1)

2.2 localRotationEuler()

第二种是欧拉角旋转localRotationEuler():Vector3,它的使用非常简单,只需要给旋转角度进行赋值即可。将立方体绕x、y、z方向分别旋转45度,采用角度值,示例代码如下:

const { regClass, property } = Laya;

@regClass()
export class NewScript extends Laya.Script {

    @property({ type: Laya.Sprite3D })
    public cube: Laya.Sprite3D;

    // 旋转角度
    private rot: Laya.Vector3 = new Laya.Vector3(45, 45, 45);

    constructor() {
        super();
    }

    /**
     * 组件被激活后执行,此时所有节点和组件均已创建完毕,此方法只执行一次
     */
    onAwake(): void {
        // 旋转立方体(欧拉角)
        this.cube.transform.localRotationEuler = this.rot;
    }

}

旋转后的效果如图2-2所示:

2-2

(图2-2)

如果想实现动图2-1所示的效果,即绕Y轴进行旋转动画,则每帧需要增加localRotationEulerY的值,示例代码如下:

const { regClass, property } = Laya;

@regClass()
export class NewScript extends Laya.Script {

    @property({ type: Laya.Sprite3D })
    public cube: Laya.Sprite3D;

    // 旋转角度
    private rot: Laya.Vector3 = new Laya.Vector3(0, 1, 0);

    constructor() {
        super();
    }

    /**
     * 组件被激活后执行,此时所有节点和组件均已创建完毕,此方法只执行一次
     */
    onAwake(): void {
        //设置定时器执行,定时重复执行(基于帧率)
        Laya.timer.frameLoop(1, this, this.animate);
    }

    private animate(): void {
        // 旋转立方体(欧拉角)
        this.cube.transform.localRotationEulerY += this.rot.y;
    }

}

3. 缩放变换

缩放变换可以使用方法localScale(): Vector3。如果传入的Vector3是类似(2,2,2)、(3,3,3)这样的x、y、z方向相同的值,那么缩放变换进行的就是等比缩放,即等比放大2倍、3倍。

如果是非等比缩放,例如,将立方体按x轴方向放大为原来的2倍,y和z方向保持不变,示例代码如下:

const { regClass, property } = Laya;

@regClass()
export class NewScript extends Laya.Script {

    @property({ type: Laya.Sprite3D })
    public cube: Laya.Sprite3D;

    // 缩放倍数
    private scale: Laya.Vector3 = new Laya.Vector3(2, 1, 1);

    constructor() {
        super();
    }

    /**
     * 组件被激活后执行,此时所有节点和组件均已创建完毕,此方法只执行一次
     */
    onAwake(): void {
        // 缩放立方体
        this.cube.transform.localScale = this.scale;
    }

}

效果如图3-1所示:

3-1

(图3-1)

4. 其它变换

除了上面介绍的3种常用变换,Transform3D中还有一些其他常用的方法和属性:

lookAt(target: Vector3, up: Vector3, isLocal: boolean = false, isCamera: boolean = true): void:观察目标位置。

localPosition:Vector3:局部位置。

localMatrix:Matrix4x4:局部矩阵。

position:Vector3:世界位置。

worldMatrix:Matrix4x4:世界矩阵。

getRight(right: Vector3): void:获取向右方向。

getUp(up: Vector3): void:获取向上方向。

getForward(forward: Vector3):获取向前方向。

5. 局部空间与世界空间

局部空间,又称为物体空间,是相对于物体自身的坐标系。在局部空间中,物体自己的位置被视为原点(0,0,0),并且每个物体都有自己独立的局部空间。而世界空间是指一套用于描述所有物体位置的公共的,全局的坐标系。局部空间可以使物体的行为(如移动和旋转)变得独立,不受其他物体或全局空间的直接影响。

举一个例子来说明,当立方体绕x轴旋转45度后,在局部空间中移动立方体,是指按照其自身的坐标系方向进行移动,如动图5-1所示:

5-1

(动图5-1)

相对应的,在世界空间中移动立方体,是指按照全局的、固定的坐标系方向进行移动,如动图5-2所示:

5-2

(动图5-2)

本篇只是利用简单的立方体说明用法,对于复杂模型的变换与立方体是类似的,详细的示例可以参考”引擎API使用示例“中的TransformDemo场景。

6. 3D世界中的子父关系

在3D世界中父节点变换,其子节点会跟着响应变换。但是子节点发生变换并不会影响父节点。

例如,”引擎API使用示例“中的Sprite3DParent场景,如动图6-1所示,旋转父节点,子节点会跟着旋转。

6-1

(动图6-1)

旋转子节点,父节点不受子节点的影响,如动图6-2所示。

6-2

(动图6-2)

Copyright ©Layabox 2022 all right reserved,powered by LayaAir Engine更新时间: 2023-08-21 16:36:43

results matching ""

    No results matching ""