javafx 3d球体部分纹理

javafx 3d sphere partial texture

我正在尝试使用 JavaFX (16) 在球体上绘制纹理。我添加了带有纹理的 material 但纹理被拉伸到整个表面。可以只在表面的一部分设置纹理吗?就像下图(不是我的,取自SO):

到目前为止我的代码(非常简单):

public void start(Stage stage) throws Exception {
   Sphere sphere = new Sphere(200);
   PhongMaterial material = new PhongMaterial();
   material.setDiffuseMap(new Image(new File("picture.png").toURI().toURL().toExternalForm()));
   sphere.setMaterial(material);
   Group group = new Group(sphere);
   Scene scene = new Scene( new StackPane(group), 640, 480, true, SceneAntialiasing.BALANCED);
   scene.setCamera(new PerspectiveCamera());
   stage.setScene(scene);
   stage.show();
}

您应用的纹理被拉伸到整个球体的原因是定义 Sphere 网格的纹理坐标正在映射整个球体表面,因此,当您应用漫反射图像时,它被 1-1 平移到那个表面。

您可以使用自定义纹理坐标值创建自定义网格,但这可能更复杂。

另一种选择是根据您的需要“按需”创建漫反射图像。

对于球体,可以通过 2*r*PI x 2*r 矩形容器(我们的目的是 JavaFX Pane)定义可以环绕 3D 球体的 2D 图像。

然后,您可以在图像内部绘图,相应地缩放和平移它们。

最后,您需要一种将绘图转换为图像的方法,为此您可以使用 Scene::snapshot

为了尝试一下这个想法,我将创建一个矩形网格,将其包裹在球体周围,以便拥有某种坐标系。

    private Image getTexture(double r) {
        double h = 2 * r;
        double w = 2 * r * 3.125; // 3.125 is ~ PI, rounded to get perfect squares.

        Pane pane = new Pane();
        pane.setPrefSize(w, h);
        pane.getStyleClass().add("pane-grid");
       
        Group rootAux = new Group(pane);
        Scene sceneAux = new Scene(rootAux, rootAux.getBoundsInLocal().getWidth(), rootAux.getBoundsInLocal().getHeight());
        sceneAux.getStylesheets().add(getClass().getResource("/style.css").toExternalForm());
        SnapshotParameters sp = new SnapshotParameters();
        return rootAux.snapshot(sp, null);
    }

其中 style.css 有:

.pane-grid {
    -fx-background-color: #D3D3D333,
    linear-gradient(from 0.5px 0.0px to 50.5px  0.0px, repeat, black 5%, transparent 5%),
    linear-gradient(from 0.0px 0.5px to  0.0px 50.5px, repeat, black 5%, transparent 5%);
}

.pane-solid {
    -fx-background-color: black;
}

(基于此

半径为 400 时,您会得到此图像:

每个方格是50x50,还有50x16个方格。

如果将此漫反射贴图应用于 Sphere:

    @Override
    public void start(Stage stage) {
        PhongMaterial earthMaterial = new PhongMaterial();
        earthMaterial.setDiffuseMap(getTexture(400));

        final Sphere earth = new Sphere(400);
        earth.setMaterial(earthMaterial);
    
        Scene scene = new Scene(root, 800, 600, true);
        scene.setFill(Color.WHITESMOKE);
        stage.setScene(scene);
        stage.show();
    }

你得到:

理论上,现在您可以填充任何网格方块,例如:

private Image getTexture(double r) throws IOException {
        double h = 2 * r;
        double w = 2 * r * 3.125;

        Pane pane = new Pane();
        pane.setPrefSize(w, h);
        pane.getStyleClass().add("pane-grid");

        Rectangle rectangle = new Rectangle(50, 50, Color.RED);
        rectangle.setStroke(Color.WHITE);
        rectangle.setStrokeWidth(2);
// fill rectangle at 20 x 10
        rectangle.setTranslateX(20 * 50 + 1);
        rectangle.setTranslateY(10 * 50 + 1);
        Group rootAux = new Group(pane, rectangle);
    ...

结果:

现在您已经有了一个定位良好的图像(现在只是一个红色矩形),您可以摆脱网格,并简单地为纹理图像使用黑色:

   private Image getTexture(double r) throws IOException {
        double h = 2 * r;
        double w = 2 * r * 3.125;

        Pane pane = new Pane();
        pane.setPrefSize(w, h);
//        pane.getStyleClass().add("pane-grid");
        pane.getStyleClass().add("pane-solid");

导致:

现在您可以将这个想法应用到您的需要中了。请注意,您可以使用尺寸为 50x50 或 100x100 的 ImageView,而不是红色矩形,这样您就可以使用更复杂的图像。