JavaFX material's bump and spec maps

ajeh picture ajeh · Oct 27, 2013 · Viewed 8.1k times · Source

When JavaFX8 code loads the color, bump and spec maps, the color and spec work as expected, but bump map is causing strange effects. All three are Mercator maps of Earth. Generally, there is no 3d effect added by the bump map. Bump map only causes Himalaya and Andes appear on the lit side of the globe as black areas with shiny border and on the shaded side as they appear on the color map. What am I doing wrong?

Image diffMap = null;
Image bumpMap = null;
Image specMap = null;
diffMap = new Image(MoleculeSampleApp.class.getResource("Color Map1.jpg").toExternalForm());
bumpMap = new Image(MoleculeSampleApp.class.getResource("Bump1.jpg").toExternalForm());
specMap = new Image(MoleculeSampleApp.class.getResource("Spec Mask1.png").toExternalForm());
final PhongMaterial earthMaterial = new PhongMaterial(Color.WHITE, diffMap, specMap, bumpMap, null);
earthMaterial.setDiffuseColor(Color.WHITE);
earthMaterial.setSpecularColor(Color.WHITE);

Being new to 3d my first thought is that there should be some kind of scaling pixel color values of the bump map into elevation, which I am missing.

Answer

jewelsea picture jewelsea · Oct 27, 2013

Bump map for JavaFX is a normal map, not a height map, for more info see: normal map and height map info.

Here is a sample you can try.

enter image description here

The images for the maps are pretty large, so it might take a little while to download them before your scene shows.

Source I used for images was => Bored? Then Create a Planet (now a dead link).

import javafx.animation.*;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.*;
import javafx.scene.shape.Sphere;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;

public class EarthViewer extends Application {

  private static final double EARTH_RADIUS  = 400;
  private static final double VIEWPORT_SIZE = 800;
  private static final double ROTATE_SECS   = 30;

  private static final double MAP_WIDTH  = 4096;
  private static final double MAP_HEIGHT = 2048;

  private static final String DIFFUSE_MAP =
      "https://imgur.com/vrNnXIs.jpeg";
  private static final String NORMAL_MAP =
      "https://imgur.com/5T2oAuk.jpeg";
  private static final String SPECULAR_MAP =
      "https://imgur.com/GV11WNV.jpeg";

  private Group buildScene() {
    Sphere earth = new Sphere(EARTH_RADIUS);
    earth.setTranslateX(VIEWPORT_SIZE / 2d);
    earth.setTranslateY(VIEWPORT_SIZE / 2d);

    PhongMaterial earthMaterial = new PhongMaterial();
    earthMaterial.setDiffuseMap(
      new Image(
        DIFFUSE_MAP,
        MAP_WIDTH,
        MAP_HEIGHT,
        true,
        true
      )
    );
    earthMaterial.setBumpMap(
      new Image(
        NORMAL_MAP,
        MAP_WIDTH,
        MAP_HEIGHT,
        true,
        true
      )
    );
    earthMaterial.setSpecularMap(
      new Image(
        SPECULAR_MAP,
        MAP_WIDTH,
        MAP_HEIGHT,
        true,
        true
      )
    );

    earth.setMaterial(
        earthMaterial
    );

    return new Group(earth);
  }

  @Override
  public void start(Stage stage) {
    Group group = buildScene();

    Scene scene = new Scene(
      new StackPane(group),
      VIEWPORT_SIZE, VIEWPORT_SIZE,
      true,
      SceneAntialiasing.BALANCED
    );

    scene.setFill(Color.rgb(10, 10, 40));

    scene.setCamera(new PerspectiveCamera());

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

    stage.setFullScreen(true);

    rotateAroundYAxis(group).play();
  }

  private RotateTransition rotateAroundYAxis(Node node) {
    RotateTransition rotate = new RotateTransition(
      Duration.seconds(ROTATE_SECS), 
      node
    );
    rotate.setAxis(Rotate.Y_AXIS);
    rotate.setFromAngle(360);
    rotate.setToAngle(0);
    rotate.setInterpolator(Interpolator.LINEAR);
    rotate.setCycleCount(RotateTransition.INDEFINITE);

    return rotate;
  }

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

Normal? Why????

The JavaDoc states for the PhongMaterial bumpMapProperty states:

The bump map of this PhongMaterial, which is a normal map stored as a RGB Image.

A normal map is used rather than a height map because:

[normal maps] are much more accurate, as rather than only simulating the pixel being away from the face along a line, they can simulate that pixel being moved at any direction, in an arbitrary way.

A brief description of both normal mapping and height mapping is provided in the wikipedia bump mapping article.

Sample Images

Update, July 2021

Unfortunately the image source from "Bored? Then Create a Planet" is no longer available, so I updated the answer to link to different images (hopefully those will remain online). Because it is linked to different images, the resultant rendering of earth looks a bit different than the example image above, though it is similar. The code to render is basically no different, though the images changed.

Diffuse map

diffuse map

Normal map

normal map

Specular map

Specular map