Использование 2D-текста в 3D-сценах в JavaFX приводит к размытым текстам

Я пытаюсь отобразить различные текстовые объекты внутри 3D-сцены.

Проблема в том, что когда я приближаюсь к тексту, он выглядит очень размытым.

Вот скриншот из моего приложения

введите здесь описание изображения

Вопрос: как улучшить графическое качество текста на экране?

Я попытался установить кеш, как в 2D-текст JavaFX с фоном в 3D-сцене потому что это, казалось, улучшило качество графики на снимках экрана с ответами, но в моем случае это не помогло.

Ниже приведен компилируемый пример, урезанный из моего приложения. Он просто создает несколько случайных текстовых объектов с позициями и дает вам возможность перемещаться. Обратите внимание, как тексты отображаются ужасно, когда вы увеличиваете масштаб.

Вы F10 и F11 для увеличения и уменьшения масштаба и перетаскивания мышью для перемещения камеры.

package timelinefx;

import static javafx.application.Application.launch;
import java.util.Random;
import javafx.application.Application;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.SubScene;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import javafx.scene.CacheHint;


public class TextInto3DScene extends Application {
    static Stage stage;
    static Scene scene;
    static Group root;
    static Group subSceneRoot;
    static PerspectiveCamera camera;
    static SubScene subScene;
    static boolean cached = true;
    static int width = 1000;
    static int height = width;

    @Override
    public void start(Stage primaryStage) throws Exception {
        createWindow();
    }

    protected static void createWindow(){
        stage = new Stage();
        root = new Group();
            root.setCache(cached);
            root.setCacheHint(CacheHint.QUALITY); //doesn't work
        scene = new Scene(root, 1500, 700, true, SceneAntialiasing.BALANCED);
        //Initialize the camera
        camera = new PerspectiveCamera(true);
        camera.setNearClip(0.1);
        camera.setFarClip(5000);
        camera.setTranslateZ(-200);

        //creates the SubScene and add the camera to it!
        subSceneRoot = new Group();
            subSceneRoot.setCache(cached); //doesn't work

        subScene = new SubScene(subSceneRoot, 1800, 700, true, SceneAntialiasing.BALANCED);
            subScene.setCache(cached);
            root.getChildren().add(subScene); 
        subScene.setFill(Color.WHITE);
        subScene.setCamera(camera);

        //Create random texts
        for( int i = 0;i<=100;++i){
            Text text = new Text(Math.random()* width-width/2, Math.random()*height-height/2, randomText() );
            text.setFill(  Color.BLACK  );
            text.setTranslateZ( Math.random()*750 );
            text.setCache(cached); //doesn't work
            text.setCacheHint(CacheHint.QUALITY);
            subSceneRoot.getChildren().add(text);
        }
        stage.setScene(scene);
        setEventHandlers();
        stage.show();
    }

    static void setEventHandlers() {
        scene.setOnMouseDragged((event) -> {

            //camera.setRotate(event.getX());
            double translateX = (event.getSceneX() - 0) / stage.getWidth();
            translateX = translateX * width - width/2;
            double translateY = (event.getSceneY() - 0) / stage.getHeight();
            translateY = translateY * height - height/2;
            double tz;
            if(!camera.getTransforms().isEmpty()){
                tz = camera.getTransforms().get(0).getTz();
            } else {
                tz = camera.getTranslateZ();
            }
            camera.getTransforms().clear();
            camera.getTransforms().addAll(  new Translate(translateX, translateY, tz)     );
        });

        //KEY PRESSED
        scene.setOnKeyPressed((event) -> {
            switch (event.getCode().toString()) {
                case "F10":
                    double amt = event.isControlDown() ? 100 : 30;
                    camera.setTranslateZ(camera.getTranslateZ() + amt);
                    break;
                case "F11":
                    amt = event.isControlDown() ? -100 : -30;
                    camera.setTranslateZ(camera.getTranslateZ() + amt);
                    break;
            }
        });
    }

    static String randomText(){
        String out = "";
        Random r = new Random();
        for(int i = 0;i<=10;++i){
            char c = (char) (r.nextInt(26) + 'a');
            out += c;
        }
        return out;
    }

    public static void main(String[] args) {
        launch(args);
    }

}

person Henri Augusto    schedule 24.03.2018    source источник


Ответы (1)


Основное различие между тем, что вы пытаетесь сделать, и упомянутым вопросом — это движение камеры: кеш работал в этом вопросе, потому что при вращении узла камера оставалась в том же положении.

Но в вашем случае вы визуализируете узлы Text в заданной позиции и с заданным качеством, и когда вы начинаете переводить камеру, эти узлы не отображаются по-разному (с разным размером или качеством), поэтому они становятся размытыми.

Если кеш не будет работать, есть еще одна возможность, как это предлагается в этом другом вопрос: использование 3D-узлов.

Это имеет очевидную стоимость работы с 3D-объектами, более тяжелым весом, чем 2D-текстовые узлы.

Основная идея такова: для заданного текста мы запишем текст в изображение и будем использовать это изображение в качестве диффузной карты трехмерного прямоугольного параллелепипеда. Затем мы добавим прямоугольный параллелепипед в подсцену и переведем его в координаты x,y,z.

Нам нужна CuboidMesh из библиотеки FXyz3D, так как встроенная 3D Box помещает одно и то же изображение на каждое лицо. , так что это не сработает. Мы также можем реализовать трехмерную призму с пользовательским TriangleMesh, но прямоугольный параллелепипед уже обеспечивает это.

Затем мы можем сгенерировать чистое изображение для прямоугольного параллелепипеда и настроить его так, чтобы у нас была нужная карта:

private CuboidMesh generateCard(String message) {

    Text text5 = new Text(message);
    text5.setFont(Font.font("Arial", FontWeight.BLACK, FontPosture.REGULAR, 60));
    GridPane grid = new GridPane();
    grid.setAlignment(Pos.CENTER);

    CuboidMesh contentShape = new CuboidMesh(40, 8, 0.01);
    PhongMaterial shader = new PhongMaterial();

    GridPane.setHalignment(text5, HPos.CENTER);

    grid.add(text5, 3, 1);

    double w = contentShape.getWidth() * 10; // more resolution
    double h = contentShape.getHeight() * 10;
    double d = contentShape.getDepth() * 10;
    final double W = 2 * d + 2 * w;
    final double H = 2 * d + h;

    ColumnConstraints col1 = new ColumnConstraints();
    col1.setPercentWidth(d * 100 / W);
    ColumnConstraints col2 = new ColumnConstraints();
    col2.setPercentWidth(w * 100 / W);
    ColumnConstraints col3 = new ColumnConstraints();
    col3.setPercentWidth(d * 100 / W);
    ColumnConstraints col4 = new ColumnConstraints();
    col4.setPercentWidth(w * 100 / W);
    grid.getColumnConstraints().addAll(col1, col2, col3, col4);

    RowConstraints row1 = new RowConstraints();
    row1.setPercentHeight(d * 100 / H);
    RowConstraints row2 = new RowConstraints();
    row2.setPercentHeight(h * 100 / H);
    RowConstraints row3 = new RowConstraints();
    row3.setPercentHeight(d * 100 / H);
    grid.getRowConstraints().addAll(row1, row2, row3);
    grid.setPrefSize(W, H);
    grid.setBackground(new Background(new BackgroundFill(Color.WHITESMOKE, CornerRadii.EMPTY, Insets.EMPTY)));
    new Scene(grid);
    WritableImage image = grid.snapshot(null, null);
    shader.setDiffuseMap(image);
    contentShape.setMaterial(shader);
    return contentShape;
}    

карта

Карта выглядит темной, но с окружающим освещением мы можем избавиться от ее темного фона.

Давайте сгенерируем 100 карт:

for (int i = 0; i <= 100; i++) {
    CuboidMesh card = generateCard(randomText());
    card.setTranslateX(Math.random() * width - width / 2);
    card.setTranslateY(Math.random() * height - height / 2);
    card.setTranslateZ(Math.random() * 750);
    subSceneRoot.getChildren().add(card);
}

Наконец установите подсцену:

subScene.setFill(Color.WHITESMOKE);
subSceneRoot.getChildren().add(new AmbientLight(Color.WHITE));

и запустите его:

100 карт

и увеличить с помощью камеры:

масштабирование

Текст больше не выглядит размытым. Если вам все еще нужно большее разрешение, вы можете создавать снимки большего размера, но это связано с затратами памяти.

Есть еще небольшая проблема, так как карты не прозрачны и закрывают тех, кто за ними (я старался максимально уменьшить их высоту).

person José Pereda    schedule 24.03.2018
comment
Большое спасибо за прояснение вопроса и за пример! Просто из любопытства, почему бы не использовать ImageView вместо Cuboid? Я только что сделал несколько тестов, и это работает. Я также только что сделал небольшую процедуру, которая проверяет белые пиксели на изображении (R, G и B = 0,8 для примера) и заменяет их прозрачным цветом, чтобы вы могли видеть сквозь отверстия в изображении. символы. - person Henri Augusto; 29.03.2018
comment
Я не пробовал ImageView, но очевидно, что если вы создаете снимок и показываете изображение с высоким разрешением, оно должно работать (и обеспечивать прозрачность, как вы упомянули). Всякий раз, когда мне приходится использовать 3D, я предпочитаю использовать только 3D-узлы, как вы уже поняли, хорошей смеси 2D и 3D не бывает. - person José Pereda; 29.03.2018