SceneKit unproject 一个点给出了奇怪的结果

Posted

技术标签:

【中文标题】SceneKit unproject 一个点给出了奇怪的结果【英文标题】:SceneKit unproject a point gives weird result 【发布时间】:2018-12-30 01:45:17 【问题描述】:

首先,我在场景中投影一个特定点。

SCNVector3 topLeft = SCNVector3Make(
    -50,
    -50,
    -UIScreen.mainScreen.bounds.size.width);
SCNVector3 projectedPoint = [self.sceneView projectPoint:topLeft];

当我打印该值时,我会在屏幕上得到一个预期的位置。

(-50 -50 -667) --> (309 212 1)

但是当我unproject这个的时候,基本上是在找反向操作。

SCNVector3 point = SCNVector3Make(309, 212, 1);
SCNVector3 unprojectedPoint = [self.sceneView unprojectPoint:point];

我得到了一些非常不同的东西。

(309, 212, 1) --> (-7.544 -7.544 -100)

我错过了什么/做错了什么?

【问题讨论】:

可能相关的问答(带有更多链接):***.com/q/46853925/957768 我不确定,但您的 unprojectPoint 取决于当前相机方向 【参考方案1】:

您的 SCNVector3 topLeft 使用与世界坐标系无关的屏幕宽度作为 z 坐标。

坐标系

SceneKit 使用右手坐标系,其中(默认情况下)视图方向沿负 z 轴,如下图所示。

https://developer.apple.com/documentation/scenekit/organizing_a_scene_with_nodes

从世界坐标到二维视图的投影点

projectPoint(_:)

将一个点从场景的 3D 世界坐标系投影到 渲染器的二维像素坐标系。

https://developer.apple.com/documentation/scenekit/scnscenerenderer/1524089-projectpoint

插图

这里我们看到一个维度为 2 的立方体。它位于世界坐标系的原点(x: 0, y: 0, z: 0)。相机在 x: 0, y: 0, z: 10。

现在要将立方体的位置 (0/0/0) 投影到 2D 视图,可以在 Objective-C 中编写:

SCNVector3 projectedPoint = [self.sceneView projectPoint:SCNVector3Make(0, 0, 0)];

在 iPhone 8 Plus 上,我们知道屏幕尺寸为 414 x 736。所以我们预计结果为 x:207 y:368。

如果我们记录输出,那么使用以下代码:

NSLog(@"projectedPoint: %f %f %f", projectedPoint.x, projectedPoint.y, projectedPoint.z);

一个人会得到类似的东西:

projectedPoint: 207.000000 368.000000 0.909091

所以现在使用这个projectedPoint 并取消投影它,我们应该再次获得原点:

SCNVector3 unprojectedPoint = [self.sceneView unprojectPoint:projectedPoint];
NSLog(@"unprojectedPoint: %f %f %f", unprojectedPoint.x, unprojectedPoint.y, unprojectedPoint.z);

确实输出是

unprojectedPoint: 0.000000 0.000000 0.000000

完整示例

这里是上述场景的完整代码:

@interface GameViewController ()

@property (nonatomic, weak) SCNView *scnView;
@property (nonatomic, strong) SCNNode *cameraNode;
@property (nonatomic, strong) SCNScene *scnScene;
@property (nonatomic, strong) SCNNode *boxNode;

@end

@implementation GameViewController

- (void)viewDidLoad 
    [super viewDidLoad];
    [self _setupView];
    [self _setupScene];
    [self _setupCamera];
    [self _setupTapping];
    [self _addCube];


-(void)_setupTapping 
    UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_tapHandler:)];
    self.scnView.gestureRecognizers = @[tapGestureRecognizer];


-(void)_tapHandler:(UIGestureRecognizer *)gestureRecognizer 
    NSLog(@"--------------");
    SCNVector3 projectedPoint = [self.scnView projectPoint:SCNVector3Make(0, 0, 0)];
    NSLog(@"projectedPoint: %f %f %f", projectedPoint.x, projectedPoint.y, projectedPoint.z);

    SCNVector3 unprojectedPoint = [self.scnView unprojectPoint:projectedPoint];
    NSLog(@"unprojectedPoint: %f %f %f", unprojectedPoint.x, unprojectedPoint.y, unprojectedPoint.z);

    CGPoint point = [gestureRecognizer locationInView:self.scnView];
    SCNVector3 scenePoint = [self.scnView unprojectPoint:SCNVector3Make(point.x, point.y, projectedPoint.z)];
    NSLog(@"point %f %f -> scenePoint: %f %f %f", point.x, point.y, scenePoint.x, scenePoint.y, scenePoint.z);


-(void)_setupView 
    self.scnView = (SCNView *)self.view;
    self.scnView.autoenablesDefaultLighting = YES;


-(void)_setupScene 
    self.scnScene = [SCNScene new];
    self.scnView.scene = self.scnScene;


- (void)_setupCamera 
    self.cameraNode = [SCNNode new];
    self.cameraNode.camera = [SCNCamera camera];
    self.cameraNode.position = SCNVector3Make(0, 0, 10);
    [self.scnScene.rootNode addChildNode:self.cameraNode];


- (void)_addCube 
    SCNBox *box = [SCNBox boxWithWidth:2 height:2 length:2 chamferRadius:0];
    SCNMaterial *mat = [SCNMaterial new];
    mat.diffuse.contents = @"art.scnassets/crosshair.png";
    box.materials = @[mat];
    self.boxNode = [SCNNode new];
    self.boxNode.geometry = box;
    self.boxNode.position = SCNVector3Make(0, 0, 0);
    [self.scnScene.rootNode addChildNode:self.boxNode];


- (BOOL)prefersStatusBarHidden 
    return YES;


@end

点击魔方的左上角

示例中的立方体具有十字准线纹理。在 iPhone 上,这看起来像这样:

所以现在您可以点按视图上的某个位置。

用下面的代码

CGPoint point = [gestureRecognizer locationInView:self.scnView];
SCNVector3 scenePoint = [self.scnView unprojectPoint:SCNVector3Make(point.x, point.y, projectedPoint.z)];
NSLog(@"point %f %f -> scenePoint: %f %f %f", point.x, point.y, scenePoint.x, scenePoint.y, scenePoint.z);

我们在上图中显示为网格的区域上输出未投影点(世界坐标系)。

由于立方体的维度为 2,并且位于原点,因此确切的世界坐标点将是 x:-1,y:-1,z:0。

例如,当我在模拟器中点击立方体的左上角时,我会得到如下信息:

point 141.000000 299.000000 -> scenePoint: -1.035465 1.082531 0.000000

【讨论】:

问题:为什么投影的 z 值是 0.90901?我知道这是一个介于 0 和 1 之间的值,其中 0 代表 zNear(默认 1.0),其中 1 代表 zFar(默认 100.0)。返回的 z 值不应该类似于 0.1 吗? (因为相机在 (0,0,10))。 深度缓冲区不是线性的。例如,在这里,您会发现更多信息非常直观:developer.nvidia.com/content/depth-precision-visualized 我的问题是假设我的视角相机旋转了 90 度,所以现在取消投影 3D 中的 2d 点,z 值为 1 和 z 值为 0 ,这与我们在没有相机时得到的值不同旋转有没有办法转换?我需要忽略相机旋转的取消投影点

以上是关于SceneKit unproject 一个点给出了奇怪的结果的主要内容,如果未能解决你的问题,请参考以下文章

使用 GLM 的 UnProject

拣货问题(自定义 unProject() 函数)

glm::unProject 问题,世界到屏幕空间对齐错误

SceneKit:围绕任意点进行轨道运行而无需相机跳跃或将锚点移动到中心?

SceneKit 自定义几何纹理错误

glm::unProject 将鼠标位置转换为 3 个坐标