如何为 TriangleMesh 中的一些三角形着色?
How to color some triangles in a TriangleMesh?
我想用不同的颜色给 TriangleMesh 的一些三角形上色。
最简单的方法是什么,甚至可以在 fxml 文件中实现?
java代码:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.scene.layout.Pane;
import javafx.scene.shape.MeshView;
import javafx.stage.Stage;
import java.io.IOException;
public class ColoredMesh extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(
ColoredMesh.class.getResource(
"mesh.fxml"
)
);
MeshView meshView = fxmlLoader.load();
// mesh.setDrawMode(DrawMode.LINE);
meshView.setTranslateX(-200);
meshView.setTranslateY(400);
meshView.setRotate(90);
Camera camera = new PerspectiveCamera();
camera.setRotate(90);
Scene scene = new Scene(new Pane(meshView), 800, 400);
scene.setCamera(camera);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
多边形/金字塔/三角形网格文件:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.MeshView?>
<?import javafx.scene.shape.TriangleMesh?>
<MeshView>
<mesh>
<TriangleMesh>
<points>
0 100 100
100 100 0
0 100 -100
-100 100 0
0 0 0
</points>
<texCoords>
0 0
</texCoords>
<faces>
0 0 4 0 1 0
1 0 4 0 2 0
2 0 4 0 3 0
3 0 4 0 0 0
0 0 1 0 2 0
0 0 2 0 3 0
</faces>
</TriangleMesh>
</mesh>
</MeshView>
我想这本可以作为重复项关闭(请参阅参考资料部分以获取对潜在重复项的引用)。
但是,我认为问题的构成方式很有趣且非常独特,以至于它使用 FXML 定义了大部分模型,所以我想我会根据答案进行调整。
高级步骤
- 您需要提供一个带有漫反射贴图的 PhongMaterial,漫反射贴图是一张将成为模型纹理的图像。
- 您需要在模型中定义 texCoords,它映射到 diffuseMap 图像中的位置(它是范围为 0 到 1 的比例图)。
- 定义面时,需要在纹理贴图中指定用于为面的顶点着色的索引。
完整的解释超出了我现在准备在这里写的内容,但我建议您参考其他资源,如果需要,您可以在其中找到更多信息。
输出
这是呈现为 gif,因此保真度不高,PC 上的实际输出看起来更好,gif 输出速度大大加快,但它确实说明了解决方案的作用。
texture.png
ColoredMesh.java
加载带纹理的模型并围绕 X 轴和 Y 轴设置动画。
import javafx.animation.*;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Point3D;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.paint.*;
import javafx.scene.shape.MeshView;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.io.IOException;
public class ColoredMesh extends Application {
private static final Color INDIA_INK = Color.rgb(60,61,76);
private static final Color AMBIENT_GRAY = Color.rgb(100, 100, 100);
private static final Duration ROTATION_STEP_TIME = Duration.seconds(5);
@Override
public void start(Stage stage) throws IOException {
MeshView meshView = loadModel();
Scene scene = createScene(meshView);
stage.setScene(scene);
stage.show();
animateNode(meshView);
}
private Scene createScene(MeshView meshView) {
PerspectiveCamera camera = new PerspectiveCamera();
AmbientLight ambientLight = new AmbientLight(AMBIENT_GRAY);
Scene scene = new Scene(
new Group(
ambientLight,
meshView
),
200, 200,
INDIA_INK
);
scene.setCamera(camera);
return scene;
}
private MeshView loadModel() throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(
ColoredMesh.class.getResource(
"pyramid-mesh.fxml"
)
);
MeshView meshView = fxmlLoader.load();
meshView.setTranslateX(100);
meshView.setTranslateY(40);
meshView.setTranslateZ(100);
// We have defined the material in the fxml which creates the MeshView.
// However, I leave this commented code here to show how the material
// can be defined in Java rather than FXML, if that were preferable.
// texture(meshView);
return meshView;
}
private void texture(MeshView meshView) {
PhongMaterial texturedMaterial = new PhongMaterial();
texturedMaterial.setDiffuseMap(
new Image(
ColoredMesh.class.getResource(
"texture.png"
).toExternalForm()
)
);
meshView.setMaterial(texturedMaterial);
}
private void animateNode(MeshView meshView) {
Animation rotateY = createRotationAnimation(Rotate.Y_AXIS, meshView);
Animation rotateX = createRotationAnimation(Rotate.X_AXIS, meshView);
rotateY.setOnFinished(e -> rotateX.play());
rotateX.setOnFinished(e -> rotateY.play());
rotateY.play();
}
private Animation createRotationAnimation(Point3D axis, Node node) {
RotateTransition animation = new RotateTransition(
ROTATION_STEP_TIME,
node
);
animation.setAxis(axis);
animation.setFromAngle(0);
animation.setToAngle(360);
return animation;
}
public static void main(String[] args) {
launch(args);
}
}
pyramid-mesh.fxml
纹理是定义为 PhongMaterial 的漫反射贴图的图像,该 PhongMaterial 是为作为网格节点的 MeshView 指定的。
在此示例中,material 是在 FXML 中定义的,但如果您愿意,您可以在代码中执行此操作(示例 Java 代码包含执行此操作的 commented-out 部分).
网格中的纹理坐标定义了纹理图像中每个色样的中点。
对于基于三角形模型的整体金字塔,使用了六个三角形多边形。对于底座,有两个三角形,但它们的颜色相同,因此只需要五个纹理坐标即可实现每个面的纯色。
定义面时,每个对顶点的引用后跟对纹理坐标的引用。每个三角形面被定义为对所有顶点使用相同的纹理坐标,这导致面的统一纯色与纹理图像中位于该纹理坐标处的颜色相匹配。
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.MeshView?>
<?import javafx.scene.shape.TriangleMesh?>
<?import javafx.scene.paint.PhongMaterial?>
<?import javafx.scene.image.Image?>
<MeshView>
<material>
<PhongMaterial>
<diffuseMap>
<Image url="@texture.png"/>
</diffuseMap>
</PhongMaterial>
</material>
<mesh>
<TriangleMesh>
<points>
0 100 100
100 100 0
0 100 -100
-100 100 0
0 0 0
</points>
<texCoords>
0.1 0.5
0.3 0.5
0.5 0.5
0.7 0.5
0.9 0.5
</texCoords>
<faces>
0 0 4 0 1 0
1 1 4 1 2 1
2 2 4 2 3 2
3 3 4 3 0 3
0 4 1 4 2 4
0 4 2 4 3 4
</faces>
</TriangleMesh>
</mesh>
</MeshView>
TextureMaker.java
您可以任意生成纹理图像。
对于这个演示,我编写了一个小程序,它根据在 JavaFX 中创建的快照创建图像。
如果有兴趣,这是纹理创建的代码,但没有必要将其用于运行演示。
这个概念被称为 TextureAtlas。
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.FlowPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
public class TextureMaker extends Application {
private static final int SWATCH_SIZE = 10;
private static final Color colors[] = {
Color.RED,
Color.GREEN,
Color.BLUE,
Color.MAGENTA,
Color.CYAN
};
@Override
public void start(Stage stage) throws IOException {
Rectangle[] colorSwatches = Arrays.stream(colors)
.map(this::createColoredRect)
.toArray(Rectangle[]::new);
FlowPane flowPane = new FlowPane(colorSwatches);
flowPane.setPrefSize(colors.length * SWATCH_SIZE, SWATCH_SIZE);
Scene scene = new Scene(
flowPane,
Color.rgb(60,61,76)
);
Image textureImage = scene.snapshot(null);
File textureFile = new File("texture.png");
ImageIO.write(
SwingFXUtils.fromFXImage(textureImage, null),
"png",
textureFile
);
System.out.println("Wrote: " + textureFile.getAbsolutePath());
stage.setScene(scene);
stage.show();
}
public Rectangle createColoredRect(Color color) {
return new Rectangle(SWATCH_SIZE,SWATCH_SIZE, color);
}
public static void main(String[] args) {
launch(args);
}
}
此实现创建图像并将其保存到磁盘,但您可以在主应用程序中即时创建图像,如果需要,无需将其保存到磁盘。在链接的类似问题中有这种方法的演示。
关于此解决方案
以上解决方案不是 general-purpose 对任意给定网格着色方法的回答。
相反,它更专注于回答特定问题:
- 如何只为问题中定义的金字塔的面着色,
尽可能多地在 fxml 中定义数据,而不是使用
外部图书馆?
关于复杂模型和重用 pre-made 模型的建议
对于复杂的模型,我认为我建议使用其他支持的标准 3d 格式(例如 .obj 或 .stl),而不是在 FXML 中定义网格数据(face/vertex/texture 坐标)软件。
网络上有 pre-created 种常见格式的模型(但不是 fxml)。这样的模型可以附带必要的图像贴图和纹理 co-ord 定义来为其着色。
您可以使用 third-party 各种 3D 模型格式的导入程序库将这些模型导入 JavaFX。这些 JavaFX 导入程序库可以在网上找到,如果您搜索,例如InteractiveMesh 和 f(x)yz 库。
相关问题
- 正如 Jose 所说:这实际上是启发 TexturedMesh in FXyz3D 的答案。
- Jose 对这个关于魔方覆盖的问题的回答我认为非常适合您的应用程序,即使问题标题是通用的。
- 使用第 3 方库回答 FXyz3D, which features things like a TexturedMesh。
Highly 理解纹理 3D 网格的推荐教程
一个非常好的教程(比我能给你的任何东西都好与) 因为这是 Create 3D Shapes using MeshView.
本教程将图像映射到金字塔的表面,就像您问题中的网格一样。它使用照片图像,但在您的情况下,您将使用不同区域的纯色。请特别注意纹理坐标部分。
单击教程中的图像,它会显示将纹理坐标映射到图像上的整洁叠加层。这确实有助于可视化正在发生的事情。
我想用不同的颜色给 TriangleMesh 的一些三角形上色。
最简单的方法是什么,甚至可以在 fxml 文件中实现?
java代码:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.scene.layout.Pane;
import javafx.scene.shape.MeshView;
import javafx.stage.Stage;
import java.io.IOException;
public class ColoredMesh extends Application {
@Override
public void start(Stage stage) throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(
ColoredMesh.class.getResource(
"mesh.fxml"
)
);
MeshView meshView = fxmlLoader.load();
// mesh.setDrawMode(DrawMode.LINE);
meshView.setTranslateX(-200);
meshView.setTranslateY(400);
meshView.setRotate(90);
Camera camera = new PerspectiveCamera();
camera.setRotate(90);
Scene scene = new Scene(new Pane(meshView), 800, 400);
scene.setCamera(camera);
stage.setScene(scene);
stage.show();
}
public static void main(String[] args) {
launch(args);
}
}
多边形/金字塔/三角形网格文件:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.MeshView?>
<?import javafx.scene.shape.TriangleMesh?>
<MeshView>
<mesh>
<TriangleMesh>
<points>
0 100 100
100 100 0
0 100 -100
-100 100 0
0 0 0
</points>
<texCoords>
0 0
</texCoords>
<faces>
0 0 4 0 1 0
1 0 4 0 2 0
2 0 4 0 3 0
3 0 4 0 0 0
0 0 1 0 2 0
0 0 2 0 3 0
</faces>
</TriangleMesh>
</mesh>
</MeshView>
我想这本可以作为重复项关闭(请参阅参考资料部分以获取对潜在重复项的引用)。
但是,我认为问题的构成方式很有趣且非常独特,以至于它使用 FXML 定义了大部分模型,所以我想我会根据答案进行调整。
高级步骤
- 您需要提供一个带有漫反射贴图的 PhongMaterial,漫反射贴图是一张将成为模型纹理的图像。
- 您需要在模型中定义 texCoords,它映射到 diffuseMap 图像中的位置(它是范围为 0 到 1 的比例图)。
- 定义面时,需要在纹理贴图中指定用于为面的顶点着色的索引。
完整的解释超出了我现在准备在这里写的内容,但我建议您参考其他资源,如果需要,您可以在其中找到更多信息。
输出
这是呈现为 gif,因此保真度不高,PC 上的实际输出看起来更好,gif 输出速度大大加快,但它确实说明了解决方案的作用。
texture.png
ColoredMesh.java
加载带纹理的模型并围绕 X 轴和 Y 轴设置动画。
import javafx.animation.*;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Point3D;
import javafx.scene.*;
import javafx.scene.image.Image;
import javafx.scene.paint.*;
import javafx.scene.shape.MeshView;
import javafx.scene.transform.Rotate;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.io.IOException;
public class ColoredMesh extends Application {
private static final Color INDIA_INK = Color.rgb(60,61,76);
private static final Color AMBIENT_GRAY = Color.rgb(100, 100, 100);
private static final Duration ROTATION_STEP_TIME = Duration.seconds(5);
@Override
public void start(Stage stage) throws IOException {
MeshView meshView = loadModel();
Scene scene = createScene(meshView);
stage.setScene(scene);
stage.show();
animateNode(meshView);
}
private Scene createScene(MeshView meshView) {
PerspectiveCamera camera = new PerspectiveCamera();
AmbientLight ambientLight = new AmbientLight(AMBIENT_GRAY);
Scene scene = new Scene(
new Group(
ambientLight,
meshView
),
200, 200,
INDIA_INK
);
scene.setCamera(camera);
return scene;
}
private MeshView loadModel() throws IOException {
FXMLLoader fxmlLoader = new FXMLLoader(
ColoredMesh.class.getResource(
"pyramid-mesh.fxml"
)
);
MeshView meshView = fxmlLoader.load();
meshView.setTranslateX(100);
meshView.setTranslateY(40);
meshView.setTranslateZ(100);
// We have defined the material in the fxml which creates the MeshView.
// However, I leave this commented code here to show how the material
// can be defined in Java rather than FXML, if that were preferable.
// texture(meshView);
return meshView;
}
private void texture(MeshView meshView) {
PhongMaterial texturedMaterial = new PhongMaterial();
texturedMaterial.setDiffuseMap(
new Image(
ColoredMesh.class.getResource(
"texture.png"
).toExternalForm()
)
);
meshView.setMaterial(texturedMaterial);
}
private void animateNode(MeshView meshView) {
Animation rotateY = createRotationAnimation(Rotate.Y_AXIS, meshView);
Animation rotateX = createRotationAnimation(Rotate.X_AXIS, meshView);
rotateY.setOnFinished(e -> rotateX.play());
rotateX.setOnFinished(e -> rotateY.play());
rotateY.play();
}
private Animation createRotationAnimation(Point3D axis, Node node) {
RotateTransition animation = new RotateTransition(
ROTATION_STEP_TIME,
node
);
animation.setAxis(axis);
animation.setFromAngle(0);
animation.setToAngle(360);
return animation;
}
public static void main(String[] args) {
launch(args);
}
}
pyramid-mesh.fxml
纹理是定义为 PhongMaterial 的漫反射贴图的图像,该 PhongMaterial 是为作为网格节点的 MeshView 指定的。
在此示例中,material 是在 FXML 中定义的,但如果您愿意,您可以在代码中执行此操作(示例 Java 代码包含执行此操作的 commented-out 部分).
网格中的纹理坐标定义了纹理图像中每个色样的中点。
对于基于三角形模型的整体金字塔,使用了六个三角形多边形。对于底座,有两个三角形,但它们的颜色相同,因此只需要五个纹理坐标即可实现每个面的纯色。
定义面时,每个对顶点的引用后跟对纹理坐标的引用。每个三角形面被定义为对所有顶点使用相同的纹理坐标,这导致面的统一纯色与纹理图像中位于该纹理坐标处的颜色相匹配。
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.shape.MeshView?>
<?import javafx.scene.shape.TriangleMesh?>
<?import javafx.scene.paint.PhongMaterial?>
<?import javafx.scene.image.Image?>
<MeshView>
<material>
<PhongMaterial>
<diffuseMap>
<Image url="@texture.png"/>
</diffuseMap>
</PhongMaterial>
</material>
<mesh>
<TriangleMesh>
<points>
0 100 100
100 100 0
0 100 -100
-100 100 0
0 0 0
</points>
<texCoords>
0.1 0.5
0.3 0.5
0.5 0.5
0.7 0.5
0.9 0.5
</texCoords>
<faces>
0 0 4 0 1 0
1 1 4 1 2 1
2 2 4 2 3 2
3 3 4 3 0 3
0 4 1 4 2 4
0 4 2 4 3 4
</faces>
</TriangleMesh>
</mesh>
</MeshView>
TextureMaker.java
您可以任意生成纹理图像。
对于这个演示,我编写了一个小程序,它根据在 JavaFX 中创建的快照创建图像。
如果有兴趣,这是纹理创建的代码,但没有必要将其用于运行演示。
这个概念被称为 TextureAtlas。
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.layout.FlowPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javax.imageio.ImageIO;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
public class TextureMaker extends Application {
private static final int SWATCH_SIZE = 10;
private static final Color colors[] = {
Color.RED,
Color.GREEN,
Color.BLUE,
Color.MAGENTA,
Color.CYAN
};
@Override
public void start(Stage stage) throws IOException {
Rectangle[] colorSwatches = Arrays.stream(colors)
.map(this::createColoredRect)
.toArray(Rectangle[]::new);
FlowPane flowPane = new FlowPane(colorSwatches);
flowPane.setPrefSize(colors.length * SWATCH_SIZE, SWATCH_SIZE);
Scene scene = new Scene(
flowPane,
Color.rgb(60,61,76)
);
Image textureImage = scene.snapshot(null);
File textureFile = new File("texture.png");
ImageIO.write(
SwingFXUtils.fromFXImage(textureImage, null),
"png",
textureFile
);
System.out.println("Wrote: " + textureFile.getAbsolutePath());
stage.setScene(scene);
stage.show();
}
public Rectangle createColoredRect(Color color) {
return new Rectangle(SWATCH_SIZE,SWATCH_SIZE, color);
}
public static void main(String[] args) {
launch(args);
}
}
此实现创建图像并将其保存到磁盘,但您可以在主应用程序中即时创建图像,如果需要,无需将其保存到磁盘。在链接的类似问题中有这种方法的演示。
关于此解决方案
以上解决方案不是 general-purpose 对任意给定网格着色方法的回答。
相反,它更专注于回答特定问题:
- 如何只为问题中定义的金字塔的面着色, 尽可能多地在 fxml 中定义数据,而不是使用 外部图书馆?
关于复杂模型和重用 pre-made 模型的建议
对于复杂的模型,我认为我建议使用其他支持的标准 3d 格式(例如 .obj 或 .stl),而不是在 FXML 中定义网格数据(face/vertex/texture 坐标)软件。
网络上有 pre-created 种常见格式的模型(但不是 fxml)。这样的模型可以附带必要的图像贴图和纹理 co-ord 定义来为其着色。
您可以使用 third-party 各种 3D 模型格式的导入程序库将这些模型导入 JavaFX。这些 JavaFX 导入程序库可以在网上找到,如果您搜索,例如InteractiveMesh 和 f(x)yz 库。
相关问题
- 正如 Jose 所说:这实际上是启发 TexturedMesh in FXyz3D 的答案。
- Jose 对这个关于魔方覆盖的问题的回答我认为非常适合您的应用程序,即使问题标题是通用的。
- 使用第 3 方库回答 FXyz3D, which features things like a TexturedMesh。
Highly 理解纹理 3D 网格的推荐教程
一个非常好的教程(比我能给你的任何东西都好与) 因为这是 Create 3D Shapes using MeshView.
本教程将图像映射到金字塔的表面,就像您问题中的网格一样。它使用照片图像,但在您的情况下,您将使用不同区域的纯色。请特别注意纹理坐标部分。
单击教程中的图像,它会显示将纹理坐标映射到图像上的整洁叠加层。这确实有助于可视化正在发生的事情。