如何为 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 定义了大部分模型,所以我想我会根据答案进行调整。

高级步骤

  1. 您需要提供一个带有漫反射贴图的 PhongMaterial,漫反射贴图是一张将成为模型纹理的图像。
  2. 您需要在模型中定义 texCoords,它映射到 diffuseMap 图像中的位置(它是范围为 0 到 1 的比例图)。
  3. 定义面时,需要在纹理贴图中指定用于为面的顶点着色的索引。

完整的解释超出了我现在准备在这里写的内容,但我建议您参考其他资源,如果需要,您可以在其中找到更多信息。

输出

这是呈现为 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 库。

相关问题

  1. Coloring individual triangles in a triangle mesh on javafx
  1. How to render 3D graphics properly
  • Jose 对这个关于魔方覆盖的问题的回答我认为非常适合您的应用程序,即使问题标题是通用的。
  1. How to create a cube with all faces of different colors in JavaFX

  2. applying texture to mesh in javafx

  1. How to create a cube with all faces of different colors in JavaFX

  2. Create a cube using different textures in JavaFX

  3. How to create such shape using javaFx TriangleMesh?

Highly 理解纹理 3D 网格的推荐教程

一个非常好的教程(比我能给你的任何东西都好与) 因为这是 Create 3D Shapes using MeshView.

本教程将图像映射到金字塔的表面,就像您问题中的网格一样。它使用照片图像,但在您的情况下,您将使用不同区域的纯色。请特别注意纹理坐标部分。

单击教程中的图像,它会显示将纹理坐标映射到图像上的整洁叠加层。这确实有助于可视化正在发生的事情。