【问题标题】:Computing the Viewport of a Perspective Camera JavaFX计算透视相机 JavaFX 的视口
【发布时间】:2017-12-18 21:37:10
【问题描述】:

您将如何计算 JavaFX 中透视相机视口的 x 和 y 边界? 'camera.getBoundsInParent' 方法可以用来确定相机的位置,但是如何使用位置来计算相机视口的大小呢?

【问题讨论】:

  • 肯定在透视图中,可见 x 和 y 坐标的边界取决于 z 坐标?
  • 不过,我确实不确定如何将有关位置、屏幕尺寸等的信息转换为边界。
  • fieldOfView 属性为您提供角度;你可以从中计算出来。
  • 计算究竟是如何工作的以及为什么这样做是问题所在。

标签: java javafx viewport bounds perspectivecamera


【解决方案1】:

根据documentation for PerspectiveCamera

默认情况下,此摄像机位于场景中心,并沿 Z 轴正方向观察。此相机定义的坐标系原点位于面板的左上角,Y 轴指向下方,Z 轴指向远离观察者(进入屏幕)。

fieldOfView property 定义了场景可见部分对摄像机的角度,默认情况下该角度为measured vertically

最后,默认情况下,

眼睛位置的 Z 值在 Z 中进行调整,以便使用指定的fieldOfView 生成的投影矩阵将在 Z = 0(投影平面)处生成与设备无关的像素的单位,与 @ 的匹配987654331@

换句话说,场景的可见部分Z=0固定在像素坐标中的场景大小

所以,假设这些默认值,我们可以画出下图:

这里,左边的点代表默认的相机位置。矩形是z=0 处屏幕的可见部分。 w 是场景的宽度,h 是场景的高度(因此对于某些z_c > 0,相机位于(w/2, h/2, -z_c))。

在 z=0 处与场景可见部分顶部中心相交的相机线在图中以任意 z 值延伸到场景可见部分顶部中心的某个点。很容易看出,上面的三角形是一个直角三角形,高-y,长z,类似于从相机到场景中心z=0形成的直角三角形。由于三角形相似,它有角度f/2,其中f 是视场角。因此,对于场景可见部分顶部的点(w/2, y, z),我们必须有

-y/z = tan(f/2)

y = -z tan(f/2) 

你可以在图片底部再建一个三角形,推导出y坐标满足

y = h + z tan(f/2)

对于宽度,只需注意场景可见部分的比例始终是比例w:h,因此对于位于场景可见部分左边缘中心的点(x, h/2, z),我们有

x = -z (w/h) tan(f/2)

在场景可见部分的右侧

x = w + z (w/h) tan(f/2)

所以,总而言之,场景的边界从

(-z (w/h) tan(f/2), -z tan(f/2))

在左上角,到

(w + z (w/h) tan(f/2), h + z tan(f/2))

在右下角,其中zz 坐标,w 是场景的宽度,h 是场景的高度,f 是场的(垂直)角度观点。

这是一个演示。这有四个球体,分别位于场景可见部分的顶部中心、底部中心、左侧中心和右侧中心。所有四个球体的z-坐标都是动画的(因此它们在z-方向上远离观察者,看起来越来越小),并且它们的x-或y-坐标被绑定,以便它们保持在场景可见部分的极端。 (因此,例如,红色球体沿上图顶部直角三角形的斜边进行动画处理,其他球体也以类似方式进行动画处理。)

import javafx.animation.TranslateTransition;
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Sphere;
import javafx.stage.Stage;
import javafx.util.Duration;

public class PerspectiveCameraTest extends Application {

    @Override
    public void start(Stage primaryStage) {
        Pane pane = new Pane();
        Scene scene = new Scene(pane, 800, 800, true);
        PerspectiveCamera camera = new PerspectiveCamera();
        camera.boundsInParentProperty().addListener((obs, oldB, newB) -> System.out.println(newB));

        scene.setCamera(camera);
        primaryStage.setScene(scene);
        primaryStage.show();

        Sphere top = new Sphere(40);
        top.setMaterial(new PhongMaterial(Color.RED));
        top.translateXProperty().bind(pane.widthProperty().divide(2));
        top.translateYProperty().bind(Bindings.createDoubleBinding(() ->  {
            double tanFOver2 = Math.tan(Math.toRadians(camera.getFieldOfView()/2));
            return -top.getTranslateZ() * tanFOver2 ;
        }, top.translateZProperty(), pane.heightProperty(), camera.fieldOfViewProperty()));

        Sphere bottom = new Sphere(40);
        bottom.setMaterial(new PhongMaterial(Color.BLUE));
        bottom.translateXProperty().bind(pane.widthProperty().divide(2));
        bottom.translateYProperty().bind(Bindings.createDoubleBinding(() -> {
            double tanFOver2 = Math.tan(Math.toRadians(camera.getFieldOfView()/2));
            return scene.getHeight() + bottom.getTranslateZ() * tanFOver2 ;
        }, bottom.translateZProperty(), pane.heightProperty(), camera.fieldOfViewProperty()));

        bottom.translateZProperty().bind(top.translateZProperty());

        Sphere left = new Sphere(40);
        left.setMaterial(new PhongMaterial(Color.GREEN));
        left.translateYProperty().bind(pane.heightProperty().divide(2));
        left.translateXProperty().bind(Bindings.createDoubleBinding(() -> {
            double tanFOver2 = Math.tan(Math.toRadians(camera.getFieldOfView()/2));
            return -left.getTranslateZ() * tanFOver2 * pane.getWidth() / pane.getHeight();
        }, left.translateZProperty(), pane.heightProperty(), pane.widthProperty(), camera.fieldOfViewProperty()));

        left.translateZProperty().bind(top.translateZProperty());

        Sphere right = new Sphere(40);
        right.setMaterial(new PhongMaterial(Color.GOLD));
        right.translateYProperty().bind(pane.heightProperty().divide(2));
        right.translateXProperty().bind(Bindings.createDoubleBinding(() -> {
            double tanFOver2 = Math.tan(Math.toRadians(camera.getFieldOfView()/2));
            return pane.getWidth() + right.getTranslateZ() * tanFOver2 * pane.getWidth() / pane.getHeight() ;
        }, right.translateZProperty(), pane.heightProperty(), pane.widthProperty(), camera.fieldOfViewProperty()));

        right.translateZProperty().bind(top.translateZProperty());


        TranslateTransition anim = new TranslateTransition(Duration.seconds(10), top);
        anim.setByZ(5000);
        anim.play();
        pane.getChildren().addAll(top, bottom, left, right);
    }

    public static void main(String[] args) {
        launch(args);
    }
}

如果您将相机添加到场景中并移动(并可能旋转)它,那么几何图形会变得相当复杂,尽管基本图片仍然是相同的(只是不方便与轴对齐)。对于更一般情况的类似计算超出了论坛帖子的范围(留给读者作为练习......)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-16
    • 2021-03-07
    • 1970-01-01
    • 2014-05-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多