3 相机
本章介绍了包含在JavaFX 3D图形功能中的相机API。
相机现在是一个可以添加到JavaFX场景图中的节点,因此允许您在3D UI布局中移动相机。这与2D布局不同,其中相机保持在一个位置。
在JavaFX场景坐标空间中,默认相机的投影平面位于Z=0,相机坐标系统如下:
• X轴指向右侧
• Y轴指向下方
• Z轴指向远离观察者或进入屏幕。
透视相机
JavaFX提供了透视相机用于渲染3D场景。该相机定义了透视投影的视图体积。可以通过改变fieldOfView
属性的值来改变视图体积。
示例3-1展示了创建透视相机的两个构造函数:
后一个构造函数是JavaFX 8中的新构造函数,允许您使用指定的fixedEyeAtCameraZero
标志来控制相机位置,以便渲染3D环境中相机所看到的内容。
应该使用以下构造函数进行3D图形编程:
PerspectiveCamera(true);
当选项fixedEyeAtCameraZero
设置为true
时,透视相机在其坐标空间中的眼睛位置固定在(0, 0, 0),无论投影区域的尺寸变化或窗口调整大小。
当fixedEyeAtCameraZero
设置为默认值false
时,相机定义的坐标系统的原点位于面板的左上角。这种模式用于使用透视相机渲染的2D UI控件,但对于大多数3D图形应用程序来说并不实用。例如,当窗口调整大小时,相机会移动,以保持原点位于面板的左上角。这对于2D UI布局是正确的,但对于3D布局则不是。因此,在进行3D图形的转换或移动相机时,重要的是记住将fixedEyeAtCameraZero
属性设置为true
。
要创建相机并将其添加到场景中,请使用以下代码:
Camera camera = new PerspectiveCamera(true); scene.setCamera(camera);
使用以下代码将相机添加到场景图中。
Group cameraGroup = new Group(); cameraGroup.getChildren().add(camera); root.getChildren().add(cameraGroup);
要旋转相机并移动cameraGroup,请使用以下代码:
camera.rotate(45); cameraGroup.setTranslateZ(-75);
视野
可以通过以下方式设置相机的视野:
camera.setFieldOfView(double value);
视野越大,透视畸变和尺寸差异越大。
-
鱼眼
镜头的视野最高可达180度以上。 -
普通
镜头的视野在40到62度之间。 -
长焦
镜头的视野为1度(或更小)到30度。
剪裁平面
您可以在本地坐标系中设置相机的近剪裁平面,如下所示:
camera.setNearClip(double value);
要设置相机的远剪裁平面,请使用以下代码:
camera.setFarClip(double value);
设置近剪裁平面或远剪裁平面将确定视图体积。如果近剪裁平面太大,它将开始剪裁场景的前部。如果太小,则会开始剪裁场景的后部。
提示:
不要将近剪裁值设置为比所需值更小的值,也不要将远剪裁值设置为比所需值更大的值,否则可能会出现奇怪的视觉伪影。
剪裁平面需要设置得足够大,以便能够看到足够的场景。但是,视图范围不应设置得太大,以免遇到数值错误。如果近剪裁平面的值太大,场景将开始被剪裁。但是,如果近剪裁平面太小,由于值太接近零,将出现不同类型的视觉伪影。如果远剪裁平面的值太大,也会遇到数值错误,尤其是如果近剪裁平面的值太小。
Y-down与Y-up
大多数2D图形坐标系统(包括UI)的Y轴向下递增。这适用于PhotoShop、JavaFX和Illustrator等大多数2D软件包。许多3D图形坐标系统通常Y轴向上递增。一些3D图形坐标系统的Z轴向上递增,但大多数情况下Y轴向上递增。
Y-down和Y-up在各自的上下文中都是正确的。在JavaFX中,相机的坐标系统是Y-down,这意味着X轴指向右侧,Y轴指向下方,Z轴指向远离观察者或进入屏幕。
如果你想将一个3D场景视为Y-up,你可以在根节点下创建一个名为root3D
的Xform
节点,如示例3-2所示。将其rx.setAngle
属性设置为180度,基本上将其颠倒过来。然后,将你的3D元素添加到root3D
节点中,并将相机放在root3D
下。
示例3-2 创建Xform节点root3D
root3D = new Xform(); root3D.rx.setAngle(180.0); root.getChildren().add(root3D); root3D.getChildren().add(...); // 在这里添加所有的3D节点
你还可以创建一个名为cameraXform
的Xform节点,并将其放在根节点下,如示例3-3所示。将其颠倒过来,并将相机放在cameraXform
下。
示例3-3 创建一个cameraXform节点
Camera camera = new PerspectiveCamera(true); Xform cameraXform = new Xform(); root.getChildren().add(cameraXform); cameraXform.getChildren().add(camera); cameraXform.rz.setAngle(180.0);
更好的方法是在相机节点上添加一个180度的旋转,这样可以避免自动旋转。在示例3-4中,相机被旋转180度,然后作为cameraXform的子节点添加到相机中。微妙的区别在于cameraXform保留了非常原始的值,在其默认位置下,包括平移和旋转都被归零。
使用PerspectiveCamera的示例代码
示例代码Simple3DBoxApp
,如示例3-5所示,创建了一个3D盒子,并使用透视相机来渲染场景。这个示例应用程序是Ensemble 8 Samples的一部分,您可以从JavaFX Demos and Samples部分的http://www.oracle.com/technetwork/java/javase/downloads/
下载。
应用程序MSAAApp.java也提供了如何使用Camera
API的示例。
示例3-5 3D盒子示例应用程序
package simple3dbox; import javafx.application.Application; import javafx.scene.Group; import javafx.scene.Parent; import javafx.scene.PerspectiveCamera; import javafx.scene.Scene; import javafx.scene.SubScene; import javafx.scene.paint.Color; import javafx.scene.paint.PhongMaterial; import javafx.scene.shape.Box; import javafx.scene.shape.DrawMode; import javafx.scene.transform.Rotate; import javafx.scene.transform.Translate; import javafx.stage.Stage; public class Simple3DBoxApp extends Application { public Parent createContent() throws Exception { // 盒子 Box testBox = new Box(5, 5, 5); testBox.setMaterial(new PhongMaterial(Color.RED)); testBox.setDrawMode(DrawMode.LINE); // 创建并定位相机 PerspectiveCamera camera = new PerspectiveCamera(true); camera.getTransforms().addAll ( new Rotate(-20, Rotate.Y_AXIS), new Rotate(-20, Rotate.X_AXIS), new Translate(0, 0, -15)); // 构建场景图 Group root = new Group(); root.getChildren().add(camera); root.getChildren().add(testBox); // 使用子场景 SubScene subScene = new SubScene(root, 300,300); subScene.setFill(Color.ALICEBLUE); subScene.setCamera(camera); Group group = new Group(); group.getChildren().add(subScene); return group; } @Override public void start(Stage primaryStage) throws Exception { primaryStage.setResizable(false); Scene scene = new Scene(createContent()); primaryStage.setScene(scene); primaryStage.show(); } /** * Java主函数,用于在没有JavaFX启动器的情况下运行 */ public static void main(String[] args) { launch(args); } }