问题描述
我正在尝试使用 JavaFX (16) 在球体上绘制纹理。我添加了带有纹理的材质,但纹理被拉伸到整个表面。是否可以仅在表面的一部分上设置纹理?如下图(不是我的,取自 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,transparent 5%);
}
.pane-solid {
-fx-background-color: black;
}
(基于此answer)
半径为 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
代替红色矩形,这样您就可以使用更复杂的图像。