首页 > 解决方案 > 如何为 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>

标签: javajavafxfxml

解决方案


我猜这可能已作为重复项关闭(有关潜在重复项的引用,请参阅资源部分)。

然而,我认为这个问题的框架很有趣而且非常独特,它使用 FXML 定义了大部分模型,所以我想我会适应这个答案。

高级步骤

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

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

输出

这被渲染为 gif,因此保真度不是很好,PC 上的实际输出看起来更好,并且 gif 输出被奇怪地大量加速,但它确实表明了解决方案的作用。

gif

纹理.png

纹理.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);
    }
}

金字塔-mesh.fxml

纹理是定义为 PhongMaterial 的漫反射贴图的图像,该 PhongMaterial 是为作为网格节点的 MeshView 指定的。

在此示例中,材料是在 FXML 中定义的,但如果您愿意,您可以在代码中执行此操作(示例 Java 代码包含执行此操作的注释掉的部分)。

网格中的纹理坐标定义了纹理图像中每个色样的中点。

对于基于三角形模型的金字塔整体,使用了六个三角形多边形。对于基础,有两个三角形,但它们的颜色相同,因此只需要五个纹理坐标即可实现每个面的纯色。

定义面后,对顶点的每个引用后跟对纹理坐标的引用。每个三角形面被定义为对所有顶点使用相同的纹理坐标,这导致面的统一纯色与纹理图像中位于该纹理坐标处的颜色相匹配。

<?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);
    }
}

此实现创建一个图像并将其保存到磁盘,但您可以在主应用程序中动态创建图像,如果需要,无需将其保存到磁盘。在链接的类似问题中有这种方法的演示。

关于此解决方案

上述解决方案不是对任意给定网格进行任意着色的方法的通用答案。

相反,它更专注于回答一个特定的问题:

  • 如何为问题中定义的金字塔的面着色,在 fxml 中定义尽可能多的数据而不使用外部库?

对复杂模型和重复使用预制模型的建议

对于复杂的模型,我建议不要在 FXML 中定义网格数据(面/顶点/纹理坐标),而是建议使用其他软件支持的标准 3d 格式(例如 .obj 或 .stl)。

网络上有以通用格式提供的预先创建的模型(但不是 fxml)。这样的模型可以带有必要的图像映射和纹理坐标定义来为它们着色。

您可以使用适用于各种 3D 模型格式的第三方导入器库将这些模型导入 JavaFX。如果您搜索,可以在网络上找到这些 JavaFX 导入器库,例如 InteractiveMesh 和 f(x)yz 库。

相关问题

  1. 在javafx上为三角形网格中的单个三角形着色
  1. 如何正确渲染 3D 图形
  • 我认为 Jose 对 Rubik 的立方体覆盖问题的回答非常适合您的应用程序,即使问题标题是通用的。
  1. 如何在 JavaFX 中创建具有不同颜色所有面的立方体

  2. 在javafx中将纹理应用于网格

  1. 如何在 JavaFX 中创建具有不同颜色所有面的立方体

  2. 在 JavaFX 中使用不同的纹理创建立方体

  3. 如何使用 javaFx TriangleMesh 创建这样的形状?

强烈推荐的了解纹理 3D 网格的教程

一个非常好的教程(比我能想到的任何东西都好)是使用 MeshView 创建 3D 形状

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

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


推荐阅读