如何给三角网格中的一些三角形上色?

2022-04-18 00:00:00 java javafx fxml

我想用不同的颜色给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. 您需要在模型中定义映射到扩散映射图像(它是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);
    }
}

金字塔-Mesh.fxml

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

在本例中,材料是在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中定义尽可能多的数据,并且不使用 外部库?

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

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

Web上有以常见格式提供的预先创建的模型(但不包括fxml)。此类模型可以附带必要的图像贴图和纹理坐标定义来对其进行着色。

您可以使用用于各种3D模型格式的第三方导入器库将这些模型导入到JavaFX中。如果您进行搜索,可以在Web上找到这些JavaFX导入器库,例如Interactive Mesh和f(X)yz库。

相关问题

  1. Coloring individual triangles in a triangle mesh on javafx
  • 正如Jose所指出的:这实际上是启发FXyz3D中TexturedMesh的答案。
  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

  • 使用第三方库FXyz3D回答,其功能类似于TexturedMesh。
  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?

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

有一个非常好的教程(比我能想到的任何教程都好)就是Create 3D Shapes using MeshView。

本教程将图像映射到棱锥体的表面,就像您问题中的网格。它使用照片图像,但在您的情况下,您将使用不同的纯色区域。请特别查看纹理坐标部分。

点击教程中的图像,它将显示一个将纹理坐标映射到图像上的整齐覆盖。这确实有助于可视化正在发生的事情。

相关文章