本节主要介绍Camera API
Camera在javaFX场景中作为一个节点,并且允许围绕3D UI 布局进行旋转,这和2D布局中不同,2D布局中Camera只能停留在一个位置,
在javaFX场景空间坐标中,Camera默认的投影平面是 Z = 0,Camera的坐标系统如下:
X轴指向右边
Y轴指向下边
Z轴指向里边
视角Camera
javaFX提供了一个视角相机来呈现3D场景,这个视角相机定义了一个视锥,通过改变fieldOfView属性,就可以改变视锥。
下面代码可以创建一个视角相机
PerspectiveCamera()
PerspectiveCamera(boolean fixedEyeAtCameraZero)
后边的构造函数式javaFX8新添加的,通过一个参数fixedEyeAtCameraZero来指定你是否能控制这个相机的位置,被这个相机渲染的东西就
会呈现在3D场景中,
下面的代码经常会出现在3D图形的编程中:
PerspectiveCamera(true);
当fixedEyeAtCameraZero 这个参数设置成true的时候,这个视角相机的坐标是(0,0,0),不管你怎么改变窗口大小它都在那里。
当fixedEyeAtCameraZero 这个参数这只成false的时候,这个相机的坐标原点在面板的左上角,这个模型被用于2D UI 控件选中中,在3D
图形应用中不怎么使用。当屏幕大小改变的时候这个相机的位置也就改变了,因为要保持在面板的左上角,这正好是2DUI布局想要的,但
不是3D布局想要的。所以当你在处理3D图形变化的时候一定要设置这个属性的值为true。
创建一个相机并把它添加到场景中,使用下面的代码:
Camera camera = new PerspectiveCamera(true);
scene.setCamera(camera);
使用下面的代码把一个相机添加到场景图中
Group cameraGroup = new Group();
cameraGroup.getChildren().add(camera);
root.getChildren().add(cameraGroup);
使用下面的代码旋转相机
camera.rotate(45);
cameraGroup.setTranslateZ(-75);
视野
相机的视野可以通过下面的方法设置:
camera.setFieldOfView(double value);
视场越大,角度变形和大小差异增加越多。
Fisheye 是一个180度的镜头
Normal 是一个40到62度的镜头
Telephoto 是一个1到30度的镜头
你可以设置相机的剪切面附近的坐标系如下:
camera.setNearClip(double value);
你可以设置相机的剪切面远距离的局部坐标系如下:
camera.setFarClip(double value);
设置近或远的切面决定了视锥,如果近的切面太大,就会开始在场景的前面,如果设置的太小就会开始在场景的后面:
注意:
不要设置太小的近切面,也不要设置太大的远切面,否则就会出现奇怪的视觉效果。
切面的设置能使场景可见,但是可视范围不应该被设置的太大,否则会出现数值误差,如果近切面太大场景就会被切割,但是如果近切面太小,就会因为这个值过于接近零而出现不同的视觉效果。如果远切面太大,也会出现数值误差,尤其是在近切面设置太小的时候。
Y-down 和 Y-up
很多2D图形坐标系统都会在你向下拉屏幕的时候Y轴就会增加,比如PhotoShop.javaFX和IIIustrator。基本上所有的2D都以这种方式工作
。很所3D图形坐标系统是当你向上拉屏幕的时候Y轴就会增加。
Y down 和 Y up都是正确的。JavaFX中的相机坐标系统是Y-down,这就意味着,x轴指向右边,Y轴指向下边,Z轴执行屏幕里面。
我们考虑一个3D场景是Y-up的情况,创建一个Xform节点root3D,设置rx.setAngle属性为180度,把它颠倒一下。然后,给root3D增加3D的元
素或者把相机放到root3D下。代码如下:
root3D = new Xform();
root3D.rx.setAngle(180.0);
root.getChildren().add(root3D);
root3D.getChildren().add(...); // add all your 3D nodes here
你也可以向下面这样写:
Camera camera = new PerspectiveCamera(true);
Xform cameraXform = new Xform();
root.getChildren().add(cameraXform);
cameraXform.getChildren().add(camera);
cameraXform.rz.setAngle(180.0);
最好的方法是给相机添加一个180度的旋转,然后把相机添加到cameraXform。不同之处在于cameraXform会保持在原始的值的默认位置。
代码如下:
Camera camera = new PerspectiveCamera(true);
Xform cameraXform = new Xform();
root.getChildren().add(cameraXform);
cameraXform.getChildren().add(camera);
Rotate rz = new Rotate(180.0, Rotate.Z_AXIS);
camera.getTransforms().add(rz);
下面是使用视角Camera的两个简单实例:
package com.chu.helloworld;
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
Box testBox = new Box(5, 5, 5);
testBox.setMaterial(new PhongMaterial(Color.RED));
testBox.setDrawMode(DrawMode.LINE);
// Create and position camera
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));
// Build the Scene Graph
Group root = new Group();
root.getChildren().add(camera);
root.getChildren().add(testBox);
// Use a SubScene
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 main for when running without JavaFX launcher
*/
public static void main(String[] args) {
launch(args);
}
}
效果如下:
package com.chu.helloworld;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.application.ConditionalFeature;
import javafx.application.Platform;
import javafx.geometry.Point3D;
import javafx.scene.AmbientLight;
import javafx.scene.Camera;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.control.Slider;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Cylinder;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class MSAAApp extends Application {
@Override
public void start(Stage stage) {
if (!Platform.isSupported(ConditionalFeature.SCENE3D)) {
throw new RuntimeException(
"*** ERROR: common conditional SCENE3D is not supported");
}
stage.setTitle("JavaFX MSAA demo");
Group root = new Group();
Scene scene = new Scene(root, 1000, 800);
scene.setFill(Color.color(0.2, 0.2, 0.2, 1.0));
HBox hbox = new HBox();
hbox.setLayoutX(75);
hbox.setLayoutY(200);
PhongMaterial phongMaterial = new PhongMaterial(Color.color(1.0, 0.7,
0.8));
Cylinder cylinder1 = new Cylinder(100, 200);
cylinder1.setMaterial(phongMaterial);
SubScene noMsaa = createSubScene("MSAA = false", cylinder1,
Color.TRANSPARENT, new PerspectiveCamera(), false);
hbox.getChildren().add(noMsaa);
Cylinder cylinder2 = new Cylinder(100, 200);
cylinder2.setMaterial(phongMaterial);
SubScene msaa = createSubScene("MSAA = true", cylinder2,
Color.TRANSPARENT, new PerspectiveCamera(), true);
hbox.getChildren().add(msaa);
Slider slider = new Slider(0, 360, 0);
slider.setBlockIncrement(1);
slider.setTranslateX(425);
slider.setTranslateY(625);
cylinder1.rotateProperty().bind(slider.valueProperty());
cylinder2.rotateProperty().bind(slider.valueProperty());
root.getChildren().addAll(hbox, slider);
stage.setScene(scene);
stage.show();
}
private static Parent setTitle(String str) {
final VBox vbox = new VBox();
final Text text = new Text(str);
text.setFont(Font.font("Times New Roman", 24));
text.setFill(Color.WHEAT);
vbox.getChildren().add(text);
return vbox;
}
private static SubScene createSubScene(String title, Node node,
Paint fillPaint, Camera camera, boolean msaa) {
Group root = new Group();
PointLight light = new PointLight(Color.WHITE);
light.setTranslateX(50);
light.setTranslateY(-300);
light.setTranslateZ(-400);
PointLight light2 = new PointLight(Color.color(0.6, 0.3, 0.4));
light2.setTranslateX(400);
light2.setTranslateY(0);
light2.setTranslateZ(-400);
AmbientLight ambientLight = new AmbientLight(Color.color(0.2, 0.2, 0.2));
node.setRotationAxis(new Point3D(2, 1, 0).normalize());
node.setTranslateX(180);
node.setTranslateY(180);
root.getChildren().addAll(setTitle(title), ambientLight, light, light2,
node);
SubScene subScene = new SubScene(root, 500, 400, true,
msaa ? SceneAntialiasing.BALANCED : SceneAntialiasing.DISABLED);
subScene.setFill(fillPaint);
subScene.setCamera(camera);
return subScene;
}
/**
* @param args
* the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}
效果如下: