javafx 3d 球体局部纹理

问题描述

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

enter image description here

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

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,transparent 5%);
}

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

(基于此answer

半径为 400 的图像:

2D grid

每个正方形是 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();
    }

你得到:

3D grid

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

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);
    ...

结果:

1 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");

导致:

black sphere

现在由您来将这个想法应用到您的需求中。请注意,您可以使用尺寸为 50x50 或 100x100...的 ImageView 代替红色矩形,这样您就可以使用更复杂的图像。