【bug】camera.convertToUINode计算不正确

版本:3.8.0
我有一个Target,固定世界坐标为:(x:-13.637,y:0, z:16.956),然后有一个GuideInRect,想把这个Target的世界坐标转到这个UI坐标下。这个GuideInRect的父节点直接为Canvas。镜头直接看向(0,0,0)点。
在lateUpdate里面计算这个Target的2D坐标:

const targetPosition = camera.convertToUINode(this.targetNode.worldPosition, this.rectTransform.node, __vec3_temp2);

当target坐标为(x:-13.637,y:0, z:16.956)时,返回{x: 11036.521058161705, y: 9703.351545436788, z: 1.5040917695197231}这个坐标表示在右上角,但正确应该是在右下角。
当镜头看向目标坐标为:{x: -0.000006611959463498351, y: -0.000005959371947028558, z: 0.9009009005304474}时,
这个target返回坐标为:{x: -16587.31206750669, y: -14792.334421307007, z: -0.018637158367324713},此时正确,是在右下角。

image
image
image
image

测试了一下该接口也遇到问题,
worldToScreen中Vec3.transformMat4(out, worldPos, this._matViewProj);变换时偏离相机中心3d位置越大(到了一个值后)转换出来的值非预期值,还需要官方大佬们确认一下,是否可以做一些修复,还是需要另外找处理方式

1赞

convertToUINode在低帧比如5的时候,有没有遇到计算错误的情况?

没有哦。。

难受了,我这获取屏幕分辨率都有问题

给个复现demo吧

复现的demo都整合在这里。
https://forum.cocos.org/t/topic/154931/84

你这里有个问题,如果要求取target node相对于guideDirection的方位是可以求取,但是camera理论上是要给的uicamera,但是由于ui的position是静止不动的,所以guideDirection永远会指向左下方。这种方案就不可取。
image
换一种思路,实际上要求的是target node相对于player node方位信息,那就把target node作为player node的子节点,求取它相对于player的方位信息,得到它的方位信息后把方位给到guideDirection,这个时候guideDirection就能旋转到正确方位。这是部分代码:

protected updateGuidePosition(): void {
Mat4.copy(srcNodeInvMat, this.sourceNode.worldMatrix);
srcNodeInvMat.invert();
const tarNodeMat = this.targetNode.worldMatrix;
const tarNodeLocalMat = srcNodeInvMat.multiply(tarNodeMat);
const targetPosition = tarNodeLocalMat.getTranslation(__vec3_temp1);
this.guideDirection.angle = Math.atan2(-targetPosition.z, targetPosition.x) / Math.PI * 180;
}

image

image

image
image

剩下额外的逻辑得你自己优化了
所以这个问题跟convertToUINode没什么关系的

首先我觉得我的思路应该是正确的。就是把targetNode的世界坐标通过主相机转到屏幕空间,再由屏幕空间转到rectTransform的UI节点下,然后对这个UI坐标进行位置和方向计算。
还有最重要的一点:这个计算在3.6.3版本下是正确的,在3.8.x版本是错误的
为了比较平滑的移动,你可以把里面的GuideInReectDemo的代码替换成以下代码:

import { Camera, Component, EventTouch, Node, Vec2, Vec3, _decorator } from 'cc';

const { ccclass, property } = _decorator;

const __vec2_temp1 = new Vec2()
const __vec2_temp2 = new Vec2()
const __vec3_temp1 = new Vec3()

@ccclass('GuideInRectDemo')
export class GuideInRectDemo extends Component {
    @property(Camera)
    readonly camera: Camera;
    @property(Node)
    readonly dragOperation: Node;
    @property(Node)
    readonly localPlayer: Node;

    private isMoving: boolean = false;
    private moveDirection = new Vec3;

    onLoad() {
        this.dragOperation.on(Node.EventType.TOUCH_START, this.touchStart, this);
        this.dragOperation.on(Node.EventType.TOUCH_MOVE, this.touchMove, this);
        this.dragOperation.on(Node.EventType.TOUCH_END, this.touchEnd, this);
        this.dragOperation.on(Node.EventType.TOUCH_CANCEL, this.touchEnd, this);
    }

    protected update(dt: number): void {
        if (!this.isMoving) return;
        const direction = Vec3.multiplyScalar(__vec3_temp1, this.moveDirection, 3 * dt);
        this.localPlayer.position = this.localPlayer.position.add(direction);
    }

    protected touchStart(event: EventTouch): void {
        this.isMoving = true;
    }
    protected touchMove(event: EventTouch): void {
        let startLocation = event.getUIStartLocation(__vec2_temp1);
        let currentLocation = event.getUILocation(__vec2_temp2);
        let dir = Vec2.subtract(__vec2_temp1, currentLocation, startLocation);
        let distance = dir.length();
        if (distance == 0) return;

        const rotation = this.camera.node.worldRotation;
        const direction = Vec3.transformQuat(__vec3_temp1, __vec3_temp1.set(dir.x, 0, -dir.y), rotation);
        direction.y = 0;
        this.moveDirection.set(direction).normalize();
    }
    protected touchEnd(event: EventTouch): void {
        this.isMoving = false;
    }
}


这个bug已经找到位置了。
是由于Vec3的transformMat4函数不同导致的问题。
3.6.3版本的transformMat4函数是长这样子的:
image
而3.8.3的transformMat4函数长这样:
image
就是少了一个Math.abs函数,所以导致错误。问题找到这个程度了,希望官方能修一下。

怎么感觉这里不取绝对值才是对的呢。这里不是齐次坐标做透视除法吗,就是应该除w本身而不是他的绝对值吧。

老哥这个问题现在解决了吗

解决了呀,我上面帖解决方案了呀。

没看到大佬说的解决方案是啥啊

大佬可以教教老弟,这个我去编辑器的ts代码里头改了,可是没有生效啊,这个是还需要在编辑一下吗

在类的外面,覆盖引擎的函数。

Vec3.transformMat4 = function <Out extends IVec3Like>(out: Out, a: IVec3Like, m: IMat4Like): Out {
    const x = a.x;
    const y = a.y;
    const z = a.z;
    let rhw = m.m03 * x + m.m07 * y + m.m11 * z + m.m15;
    rhw = rhw ? Math.abs(1 / rhw) : 1;
    out.x = (m.m00 * x + m.m04 * y + m.m08 * z + m.m12) * rhw;
    out.y = (m.m01 * x + m.m05 * y + m.m09 * z + m.m13) * rhw;
    out.z = (m.m02 * x + m.m06 * y + m.m10 * z + m.m14) * rhw;
    return out;
}