Lighting for JavaFX pano viewer

1.6k views Asked by At

I am currently trying to set up a panorama viewer in JavaFX using its 3D features. I think I got the geometry right (3D sub-scene, TriangleMesh, etc.) but I am fighting with the proper lighting. What I need is actually no lighting at all, just the geometrically correct display of the original colors of the texture image. JavaFX seems to introduce some unwanted default lighting if there is no explicit lighting specified and is driving me crazy.

Can anybody tell me how to set up the material and the light sources and maybe other related stuff if I do not want to have any lighting at all but just the plain image (texture) colors?

Edit: Here is the requested code example. When the box is rotating you clearly see the corners of the box due to the lighting. If you could switch off the lighting these corners should disappear.

import javafx.animation.Interpolator;
import javafx.animation.RotateTransition;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SubScene;
import javafx.scene.image.Image;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.CullFace;
import javafx.scene.shape.DrawMode;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.TriangleMesh;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;

public class SimplePanoViewer extends Application {

  private static final int VIEWPORT_SIZE = 1200;

  private static final double MODEL_SCALE_FACTOR = VIEWPORT_SIZE/2;

  private static final String textureLoc = "http://www.f-lohmueller.de/pov_tut/backgrnd/im/Cubemap_2_2048x1536.jpg";

  private Image texture;
  private PhongMaterial texturedMaterial = new PhongMaterial();

  private MeshView meshView = loadMeshView();

  private MeshView loadMeshView() {

    float[] points = {
            -0.5f, -0.5f, -0.5f,
            -0.5f, +0.5f, -0.5f,
            +0.5f, +0.5f, -0.5f,
            +0.5f, -0.5f, -0.5f,

            -0.5f, -0.5f, +0.5f,
            -0.5f, +0.5f, +0.5f,
            +0.5f, +0.5f, +0.5f,
            +0.5f, -0.5f, +0.5f
    };

    float TX0 = 0f;
    float TX1 = 1f/4f;
    float TX2 = 2f/4f;
    float TX3 = 3f/4f;
    float TX4 = 1f;

    float TY0 = 0f;
    float TY1 = 1f/3f;
    float TY2 = 2f/3f;
    float TY3 = 1f;

    float[] texCoords = {
            TX0, TY1,
            TX0, TY2,
            TX1, TY2,
            TX1, TY3,
            TX2, TY3,
            TX2, TY2,
            TX3, TY2,
            TX4, TY2,
            TX4, TY1,
            TX3, TY1,
            TX2, TY1,
            TX2, TY0,
            TX1, TY0,
            TX1, TY1,
        };
    int[] faces = {
            0, 0, 1, 1, 5, 2,  5, 2, 4, 13, 0, 0,           // 0
            4, 13, 5, 2, 6, 5,  6, 5, 7, 10, 4, 13,         // 1
            7, 10, 6, 5, 2, 6,  2, 6, 3, 9, 7, 10,          // 2
            3, 9, 2, 6, 1, 7,  1, 7, 0, 8, 3, 9,            // 3
            0, 12, 4, 13, 7, 10,  7, 10, 3, 11, 0, 12,      // 4
            5, 2, 1, 3, 2, 4,  2, 4, 6, 5, 5, 2             // 5
    };

    TriangleMesh mesh = new TriangleMesh();
    mesh.getPoints().setAll(points);
    mesh.getTexCoords().setAll(texCoords);
    mesh.getFaces().setAll(faces);

    return new MeshView(mesh);
  }

  private Group buildScene() {
    meshView.setTranslateX(VIEWPORT_SIZE / 2);
    meshView.setTranslateY(VIEWPORT_SIZE / 2 * 9.0 / 16);
    meshView.setTranslateZ(-VIEWPORT_SIZE );
    meshView.setScaleX(MODEL_SCALE_FACTOR);
    meshView.setScaleY(MODEL_SCALE_FACTOR);
    meshView.setScaleZ(MODEL_SCALE_FACTOR);

    return new Group(meshView);
  }

  @Override
  public void start(Stage stage) {
    texture = new Image(textureLoc);
    texturedMaterial.setDiffuseMap(texture);

    Group group = buildScene();

    RotateTransition rotate = rotate3dGroup(group);

    meshView.setCullFace(CullFace.NONE);
    meshView.setDrawMode(DrawMode.FILL);
    rotate.play();
    meshView.setMaterial(texturedMaterial);

    VBox layout = new VBox(
        createScene3D(group)
    );

    stage.setTitle("Model Viewer");

    Scene scene = new Scene(layout, Color.CORNSILK);
    stage.setScene(scene);
    stage.show();
  }

  private SubScene createScene3D(Group group) {
    SubScene scene3d = new SubScene(group, VIEWPORT_SIZE, VIEWPORT_SIZE * 9.0/16);
    scene3d.setFill(Color.rgb(10, 10, 40));
    PerspectiveCamera camera = new PerspectiveCamera();
    scene3d.setCamera(camera);
    return scene3d;
  }

  private RotateTransition rotate3dGroup(Group group) {
    RotateTransition rotate = new RotateTransition(Duration.seconds(30), group);
    rotate.setAxis(Rotate.Y_AXIS);
    rotate.setFromAngle(0);
    rotate.setToAngle(360);
    rotate.setInterpolator(Interpolator.LINEAR);
    rotate.setCycleCount(RotateTransition.INDEFINITE);

    return rotate;
  }

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

There are 1 answers

3
aw-think On BEST ANSWER

I'am not a solid profi on JavaFX 3D, but I'll try to answer. JavaFX 3D needs light to be able to show a perspective view. If you switch light off, your Subscene renders to black.

There are only two Lights in JavaFX, an AmbientLight and a PointLight. Both can be added to the group where the meshview is added. JavaDoc says about AmbientLight:

Ambient light is a light source that seems to come from all directions

Whereas PointLight got this description:

A light source that has a fixed point in space and radiates light equally in all directions away from itself.

If no Light was added to a Scene or SubScene, it adds a default PointLight that shines from top onto the 3D-Shape. If you add a Light (Ambient or Point) than the default one is removed.

If you would like to place a light, than you should do it in your build scene method.

  private Group buildScene() {
    meshView.setTranslateX(VIEWPORT_SIZE / 2);
    meshView.setTranslateY(VIEWPORT_SIZE / 2 * 9.0 / 16);
    meshView.setTranslateZ(-VIEWPORT_SIZE);
    meshView.setScaleX(MODEL_SCALE_FACTOR);
    meshView.setScaleY(MODEL_SCALE_FACTOR);
    meshView.setScaleZ(MODEL_SCALE_FACTOR);
    meshView.setCullFace(CullFace.NONE);
    Group group = new Group(meshView);

    AmbientLight ambient = new AmbientLight(); // default color white
    ambient.setLightOn(true); // switch it off and everything is black

    group.getChildren().add(ambient);
    return group;
  }

The lights shown below, are the Default light, PointerLight and AmbientLight. As you can see, the perspective is fully gone if you set ambient light.

Lights

Code for testing:

import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.control.Label;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.*;
import javafx.stage.Stage;

public class Shapes3DViewer extends Application {

  PhongMaterial material;

  @Override
  public void start(Stage stage) {
    material = new PhongMaterial();
    material.setDiffuseColor(Color.FIREBRICK);
    material.setSpecularColor(Color.YELLOW);

    PointLight pointLight = new PointLight(Color.WHITE);
    pointLight.setTranslateX(100);
    pointLight.setTranslateY(100);
    pointLight.setTranslateZ(-300);
    pointLight.setRotate(90);

    AmbientLight ambient = new AmbientLight();

    Group g1 = createSphereGroup(200, "Default light");
    Group g2 = createSphereGroup(200, "Point light");
    Group g3 = createSphereGroup(200, "Ambient light");

    g2.getChildren().add(pointLight);
    g3.getChildren().add(ambient);

    SubScene s1 = createSubScene(g1, 500, 500);
    SubScene s2 = createSubScene(g2, 500, 500);
    SubScene s3 = createSubScene(g3, 500, 500);

    HBox root = new HBox();
    root.getChildren().addAll(s1, s2, s3);

    Scene scene = new Scene(root);

    stage.setScene(scene);
    stage.show();
  }

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

  private Group createSphereGroup(double radius, String text) {
    Sphere c = new Sphere(radius);
    c.setMaterial(material);
    c.setDrawMode(DrawMode.FILL);
    c.setTranslateX(radius * 1.33);
    c.setTranslateY(radius * 1.33);
    Label lbl = new Label(text);
    lbl.setStyle("-fx-text-fill: red;-fx-font-size: 18pt;");
    return new Group(c, lbl);
  }

  private SubScene createSubScene(Group group, double width, double height) {
    SubScene s = new SubScene(group, width, height);
    s.setCamera(new PerspectiveCamera());
    s.setFill(Color.color(.1, .1, .1));
    return s;
  }
}