在 JavaFX 中将 2D 文本用于 3D 场景会导致文本模糊

Use 2D Text into 3D scenes in JavaFX results in blurry texts

我正在尝试在 3D 场景中显示各种文本对象

问题是,当我放大到接近文本时,它看起来非常模糊。

这是我的应用程序的屏幕截图

问题:如何提高屏幕上文字的图形质量?

我试过设置缓存,如 3D 场景中带背景的 JavaFX 2D 文本 因为它似乎提高了答案屏幕截图的图形质量,但对我的情况没有帮助。

下面是从我的应用程序中删除的可编译示例。它只是创建一些带有位置的随机文本对象,并为您提供一种四处移动的方式。请注意当您放大时文本显示的非常糟糕。

F10F11 放大和缩小,鼠标拖动 移动相机。

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

}

您尝试执行的操作与参考的 之间的主要区别在于相机移动:缓存在该问题中有效,因为在旋转节点时,相机保持在同一位置。

但在您的情况下,您在给定位置以给定质量渲染文本节点,并且当您开始平移相机时,这些节点不会以不同方式呈现(具有不同大小或质量),因此它们变得模糊。

如果缓存不起作用,还有另一种可能性,如其他 中所建议:使用 3D 节点。

这有明显的处理3D对象的成本,比2D文本节点更重。

主要思想是:对于给定的文本,我们将文本写入图像,我们将使用此图像作为 3D 长方体的漫反射贴图。然后我们将长方体添加到子场景并转换为 x,y,z 坐标。

我们需要来自 FXyz3D libraryCuboidMesh,因为 built-in 3D Box 将相同的图像放置在每张脸上,所以这是行不通的。我们也可以使用自定义 TriangleMesh 实现 3D 棱镜,但是长方体已经提供了。

然后我们可以为长方体生成一个网络图像,并设置它,这样我们就有了想要的卡片:

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

和运行它:

并用相机变焦:

文字看起来不再模糊了。如果您仍然想要更高的分辨率,您可以创建更大的快照,但这会带来内存成本。

还有一个小问题,卡片不是透明的,会遮住后面的卡片(我尽量降低高度)。