Как правильно визуализировать 3D-графику

Я пытался сделать кубик Рубика в javafx, но получил очень плохую модель, как показано на этом изображении. . Я даю свой исходный код для этого, где я использовал класс RectangleBuilder для создания прямоугольников и преобразования в 3d. Чтобы исправить графику, я также попытался построить прямоугольники, используя класс TriangleMesh, и после добавления к ним материалов преобразовал их в 3D, чтобы снова получить ту же плохую графику. Почему это происходит и как от этого избавиться?

import javafx.scene.transform.Rotate;
import javafx.scene.PerspectiveCamera;
import javafx.scene.transform.Translate;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.SceneAntialiasing;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.RectangleBuilder;
import javafx.scene.transform.Rotate;
import javafx.util.Duration;


    public class NewFXMain1 extends Application {


    public class Cube extends Group {
    final Rotate rx = new Rotate(0,Rotate.X_AXIS);
    final Rotate ry = new Rotate(0,Rotate.Y_AXIS);
    final Rotate rz = new Rotate(0,Rotate.Z_AXIS);
    public Cube(double size, Color back,Color bottom,Color right,Color left,Color top,Color front, double shade) {
        getTransforms().addAll(rz, ry, rx);
        getChildren().addAll(
            RectangleBuilder.create() // back face
                .width(size).height(size)
                .fill(back.deriveColor(0.0, 1.0, (1 - 0.5*shade), 1.0))
                .translateX(-0.5*size)
                .translateY(-0.5*size)
                .translateZ(0.5*size)
                    .smooth(true)
                    .stroke(Color.BLACK)
                .build(),
            RectangleBuilder.create() // bottom face
                .width(size).height(size)
                .fill(bottom.deriveColor(0.0, 1.0, (1 - 0.4*shade), 1.0))
                .translateX(-0.5*size)
                .translateY(0)
                .rotationAxis(Rotate.X_AXIS)
                .rotate(90)
                    .smooth(true)
                    .stroke(Color.BLACK)
                .build(),
            RectangleBuilder.create() // right face
                .width(size).height(size)
                .fill(right.deriveColor(0.0, 1.0, (1 - 0.3*shade), 1.0))
                .translateX(-1*size)
                .translateY(-0.5*size)
                .rotationAxis(Rotate.Y_AXIS)
                .rotate(90)
                    .smooth(true)
                    .stroke(Color.BLACK)
                .build(),
            RectangleBuilder.create() // left face
                .width(size).height(size)
                .fill(left.deriveColor(0.0, 1.0, (1 - 0.2*shade), 1.0))
                .translateX(0)
                .translateY(-0.5*size)
                .rotationAxis(Rotate.Y_AXIS)
                .rotate(90)
                    .smooth(true)
                    .stroke(Color.BLACK)
                .build(),
            RectangleBuilder.create() // top face
                .width(size).height(size)
                .fill(top.deriveColor(0.0, 1.0, (1 - 0.1*shade), 1.0))
                .translateX(-0.5*size)
                .translateY(-1*size)
                .rotationAxis(Rotate.X_AXIS)
                .rotate(90)
                    .smooth(true)
                    .stroke(Color.BLACK)
                .build(),
            RectangleBuilder.create() // front face
                .width(size).height(size)
                .fill(front)
                .translateX(-0.5*size)
                .translateY(-0.5*size)
                .translateZ(-0.5*size)
                    .smooth(true)
                    .stroke(Color.BLACK)
                .build()
        );
    }
    }



    PerspectiveCamera camera = new PerspectiveCamera(true);
    @Override public void start(Stage primaryStage) throws Exception {

    Group root = new Group();
    Scene scene=new Scene(root,600,600,true);

    camera.setNearClip(0.00001);
    camera.setFarClip(10000000.0);

    camera.getTransforms().addAll (
            new Rotate(0, Rotate.Y_AXIS),
            new Rotate(0, Rotate.X_AXIS),

            new Translate(0, 0, -1000));
    scene.setCamera(camera);
    Cube c1 = new Cube(50,Color.BLUE.darker(),Color.BLUE.darker(),Color.ORANGE.darker(),Color.BLUE.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
     c1.setTranslateX(100);

    Cube c2 = new Cube(50,Color.GREEN.darker(),Color.GREEN.darker(),Color.GREEN.darker(),Color.YELLOW.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
    c2.setTranslateX(50);

    Cube c3 = new Cube(50,Color.CYAN.brighter(),Color.GREEN.darker(),Color.GREEN.darker(),Color.YELLOW.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
    c3.setTranslateX(50);
    c3.setTranslateZ(50);

    Cube c4 = new Cube(50,Color.CYAN.brighter(),Color.GREEN.darker(),Color.ORANGE.darker(),Color.YELLOW.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
    c4.setTranslateX(100);
    c4.setTranslateZ(50);

    Cube c5 = new Cube(50,Color.BLUE.darker(),Color.GREEN.darker(),Color.ORANGE.darker(),Color.BLUE.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
     c5.setTranslateX(100);
     c5.setTranslateY(50);

    Cube c6 = new Cube(50,Color.GREEN.darker(),Color.GREEN.darker(),Color.GREEN.darker(),Color.YELLOW.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
    c6.setTranslateX(50);
    c6.setTranslateY(50);

    Cube c7 = new Cube(50,Color.CYAN.brighter(),Color.GREEN.darker(),Color.GREEN.darker(),Color.YELLOW.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
    c7.setTranslateX(50);
    c7.setTranslateZ(50);
    c7.setTranslateY(50);

    Cube c8 = new Cube(50,Color.CYAN.brighter(),Color.GREEN.darker(),Color.ORANGE.darker(),Color.YELLOW.darker(),Color.BLUE.darker(),Color.RED.darker(),1);
    c8.setTranslateX(100);
    c8.setTranslateZ(50);
    c8.setTranslateY(50);
    handleMouse(scene,root);
    Group k=new Group(c1,c2,c3,c4,c5,c6,c7,c8);
    k.setTranslateZ(70);
    root.getChildren().addAll(k);
    primaryStage.setScene(scene);
    primaryStage.show();
    }
    public static void main(String[] args) { launch(args); }
    private static final double CONTROL_MULTIPLIER = 0.1;   
    private static final double SHIFT_MULTIPLIER = 10.0;   
    private static final double MOUSE_SPEED = 0.1;    
  private static final double ROTATION_SPEED = 2.0; 
  double mousePosX,mousePosY,mouseOldX,mouseOldY,mouseDeltaX,mouseDeltaY;
  private void handleMouse(Scene scene, final Node root) {


    scene.setOnMousePressed(new EventHandler<MouseEvent>() {
        @Override public void handle(MouseEvent me) {
             mousePosX = me.getSceneX();
             mousePosY = me.getSceneY();
             mouseOldX = me.getSceneX();
             mouseOldY = me.getSceneY();
        }
    });
    scene.setOnMouseDragged(new EventHandler<MouseEvent>() {
        @Override public void handle(MouseEvent me) {
            mouseOldX = mousePosX;
            mouseOldY = mousePosY;
            mousePosX = me.getSceneX();
            mousePosY = me.getSceneY();
            mouseDeltaX = (mousePosX - mouseOldX); 
            mouseDeltaY = (mousePosY - mouseOldY);

           double modifier = 1.0;

           if (me.isControlDown()) {
                modifier = CONTROL_MULTIPLIER;
            } 
            if (me.isShiftDown()) {
                modifier = SHIFT_MULTIPLIER;
            }     
            if (me.isPrimaryButtonDown()) {
                camera.setRotationAxis(Rotate.Y_AXIS);camera.setRotate(camera.getRotate() -
                   mouseDeltaX*modifier*ROTATION_SPEED);  // 
               camera.setRotationAxis(Rotate.X_AXIS);camera.setRotate(camera.getRotate() +
                   mouseDeltaY*modifier*ROTATION_SPEED);  // -

            }
            else if (me.isSecondaryButtonDown()) {
                double z = camera.getTranslateZ();
                double newZ = z + mouseDeltaX*MOUSE_SPEED*modifier;
                camera.setTranslateZ(newZ);
            }

       }
   }); // setOnMouseDragged
 } //handleMouse
}

person Debarun Mukherjee    schedule 30.11.2015    source источник
comment
Существует действительно отличная запись в блоге о рубике. куб в JavaFX 3D Хосе Переда.   -  person jewelsea    schedule 30.11.2015
comment
Да, я прошел через это, а затем попытался построить один без импорта 3D-модели и создать его только в javafx.   -  person Debarun Mukherjee    schedule 30.11.2015
comment
Это связано с вашей текущей проблемой, но обратите внимание, что классы Builder устарели и, вероятно, будут удален из будущей версии JavaFX.   -  person jewelsea    schedule 30.11.2015
comment
Отныне я не буду их использовать, так как я узнал новый способ решения таких проблем из ответа и, вероятно, скоро перейду на FXML.   -  person Debarun Mukherjee    schedule 30.11.2015


Ответы (2)


РЕДАКТИРОВАТЬ:

Первоначально указанная здесь причина артефактов рендеринга была неверной, и предлагаемое решение может быть неподходящим*. Подробности можно найти в История изменений. Фактическое решение намного проще. Приносим извинения за неудобства.

Причина артефактов рендеринга заключается в том, что плоскости отсечения вашей камеры слишком далеко друг от друга. Вы устанавливаете

camera.setNearClip(0.00001);
camera.setFarClip(10000000.0);

что далеко за пределами того, что может быть представлено в обычном Z-буфере. Изменение этих строк на

camera.setNearClip(0.1);
camera.setFarClip(10000.0);

исправит ошибки рендеринга.


* В исходном решении предлагалось моделировать блоки из нескольких экземпляров Mesh. Это имеет то преимущество, что позволяет определить нормали и, таким образом, добиться «реалистичного» трехмерного эффекта, но требует немного больше усилий. См. Историю изменений для решения "настоящего 3D".

person Marco13    schedule 30.11.2015
comment
Не могли бы вы объяснить, как вы генерировали точки в массиве Point3D points[]? - person Debarun Mukherjee; 01.12.2015
comment
если я создаю прямоугольники, используя TriangleMesh, сопоставляю координаты текстуры изображений определенных цветов (скажем, красный для одного прямоугольника, зеленый для другого и т. д.) с прямоугольниками, а затем преобразовываю их в куб, я получаю ту же проблему. . Я делаю ту же ошибку, что и с классом RectangleBuilder, или это связано с какой-то другой причиной? - person Debarun Mukherjee; 01.12.2015
comment
Я не использую класс Rectangle. Я использую TriangleMesh для рисования прямоугольника TriangleMesh re1 = new TriangleMesh(); re1.getPoints().addAll(x1,y1,z1,.. ); re1.getTexCoords().addAll(u1,v1,..); re1.getFaces().addAll( //corresponding vertices with the texture coordinates ); MeshView rectangle1 = new MeshView(re1); Таким образом я создаю 6 прямоугольников, добавляю к ним изображения, а затем преобразовываю их в куб, что приводит меня к той же проблеме. - person Debarun Mukherjee; 01.12.2015
comment
Трудно обсуждать/отлаживать это в комментариях здесь. Как именно вы преобразуете прямоугольные сетки, чтобы сформировать куб? (Или в более общем плане: насколько ваш код отличается от чего-то вроде buildSceneGaph, который я разместил выше?) - person Marco13; 01.12.2015
comment
@DebarunMukherjee Я отредактировал пост. Причина, которую я изначально назвал, была неправильной. Простите за это. - person Marco13; 01.12.2015

Хотя @Marco13 — отличный и правильный ответ, если вы не хотите импортировать модель, как упоминает @jewelsea, есть также способ создать одну одиночную сетку для каждого куба и раскрасить грани. как требуется для кубика Рубика.

Этого можно добиться с помощью изображения net. чтобы покрасить грани сетки:

Cube Net

Если вы примените это изображение к Box:

Box cube = new Box();
PhongMaterial material = new PhongMaterial();
material.setDiffuseMap(new Image(getClass().getResource("cubeNet.png").toExternalForm()));
cube.setMaterial(material);

вы получите одно и то же изображение, повторяющееся для шести граней.

Таким образом, вы можете создать свой собственный блок для правильного отображения координат текстуры или использовать CuboidMesh из библиотеки FXyz.

CuboidMesh cube = new CuboidMesh();
cube.setTextureModeImage(getClass().getResource("cubeNet.png").toExternalForm());

Кубовидная текстура

Этот публикация показывает вам как можно раскрасить разные грани одной сетки.

ИЗМЕНИТЬ

Учитывая, что для любого из 27 кубов должно быть предоставлено другое изображение, лучшим подходом является просто использование изображения, подобного этому:

Палитра

а затем соответствующим образом измените индексы текстуры, как описано в этом ответе.

В общем, для каждого цвета:

public static final int RED     = 0;
public static final int GREEN   = 1;
public static final int BLUE    = 2;
public static final int YELLOW  = 3;
public static final int ORANGE  = 4;
public static final int WHITE   = 5;
public static final int GRAY    = 6;

его нормализованная координата текстуры x будет:

public static final float X_RED     = 0.5f / 7f;
public static final float X_GREEN   = 1.5f / 7f;
public static final float X_BLUE    = 2.5f / 7f;
public static final float X_YELLOW  = 3.5f / 7f;
public static final float X_ORANGE  = 4.5f / 7f;
public static final float X_WHITE   = 5.5f / 7f;
public static final float X_GRAY    = 6.5f / 7f;

Итак, используя TriangleMesh для создания поля:

private TriangleMesh createCube(int[] face) {
    TriangleMesh m = new TriangleMesh();
    m.getPoints().addAll(
     0.5f,  0.5f,  0.5f,
     0.5f, -0.5f,  0.5f,
     0.5f,  0.5f, -0.5f,
     0.5f, -0.5f, -0.5f,
    -0.5f,  0.5f,  0.5f,
    -0.5f, -0.5f,  0.5f,
    -0.5f,  0.5f, -0.5f,
    -0.5f, -0.5f, -0.5f
    );
    m.getTexCoords().addAll(
     X_RED, 0.5f, 
     X_GREEN, 0.5f,
     X_BLUE, 0.5f, 
     X_YELLOW, 0.5f, 
     X_ORANGE, 0.5f,  
     X_WHITE, 0.5f,
     X_GRAY, 0.5f
    );

Наконец, нам просто нужно добавить грани: список вершин и индексов текстуры. Создадим массив индексов, упорядоченных на основе общепринятых обозначений граней на кубике Рубика: F - R - U - B - L - D:

    m.getFaces().addAll(
     2, face[0], 3, face[0], 6, face[0],      // F      
     3, face[0], 7, face[0], 6, face[0],  

     0, face[1], 1, face[1], 2, face[1],      // R     
     2, face[1], 1, face[1], 3, face[1],         

     1, face[2], 5, face[2], 3, face[2],      // U   
     5, face[2], 7, face[2], 3, face[2],

     0, face[3], 4, face[3], 1, face[3],      // B      
     4, face[3], 5, face[3], 1, face[3],       

     4, face[4], 6, face[4], 5, face[4],      // L      
     6, face[4], 7, face[4], 5, face[4],    

     0, face[5], 2, face[5], 4, face[5],      // D      
     2, face[5], 6, face[5], 4, face[5]         
    );
    return m;
}

Теперь очень просто создать куб и его 27 кубов на основе одного куба и набора цветов.

Этот код

int[] p = new int[]{BLUE, GRAY, GRAY, GRAY, ORANGE, WHITE};
MeshView meshP = new MeshView();
meshP.setMesh(createCube(p));
PhongMaterial mat = new PhongMaterial();
mat.setDiffuseMap(new Image(getClass().getResourceAsStream("palette.png")));
meshP.setMaterial(mat);

создаст куб для положения спереди справа вверху.

Определив 27 позиций, это будет кубик Рубика:

Кубик Рубика

Код, необходимый для его создания, можно найти здесь.

person José Pereda    schedule 30.11.2015
comment
если я создаю прямоугольники, используя TriangleMesh, сопоставляю координаты текстуры изображений определенных цветов (скажем, красный для одного прямоугольника, зеленый для другого и т. д.) с прямоугольниками, а затем преобразовываю их в куб, я получаю ту же проблему. . Я делаю ту же ошибку, что и с классом RectangleBuilder, или это связано с какой-то другой причиной? - person Debarun Mukherjee; 01.12.2015
comment
Использование текстур было вариантом, который я тоже рассматривал (тем более, что меш всегда должен содержать координаты текстуры - по крайней мере, мне так кажется, согласно существующим VertexFormats). Однако есть некоторые тонкие трудности. Во-первых, правильно указать текстурные координаты, а во-вторых (в связи с этим): Кубики-элементы для кубика рубика все имеют разноцветные стороны - поэтому независимо от как точно они представлены, есть какие-то ручные, рукописные определения... - person Marco13; 01.12.2015
comment
Я отредактировал свой ответ, используя другой подход: вместо создания изображения для каждого куба я использую одно изображение с 7 требуемыми цветами и соответствующим образом изменяю индексы текстуры. - person José Pereda; 02.12.2015
comment
Вау, это делает куб намного точнее! Я буду следовать этому подходу. Спасибо, сэр ! - person Debarun Mukherjee; 02.12.2015