如何正确渲染3D图形

我试图在javafx制作一个rubiks立方体,结果是这个图像中给出的非常糟糕的模型。 我正在为此提供代码源代码,我使用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() { @Override public void handle(MouseEvent me) { mousePosX = me.getSceneX(); mousePosY = me.getSceneY(); mouseOldX = me.getSceneX(); mouseOldY = me.getSceneY(); } }); scene.setOnMouseDragged(new EventHandler() { @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 } 

编辑:

最初在此处给出的渲染工件的原因是错误的,并且建议的解决方案可能不合适*。 详细信息可在修订历史中找到。 实际的解决方案要简单得多。 对由此带来的任何不便表示歉意。

渲染瑕疵的原因是您的相机剪裁平面相距太远。 你在设置

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

这远远超出了在正常Z缓冲区中明显可以表示的范围。 将这些行更改为

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

将修复渲染错误。


*原始解决方案建议对几个Mesh实例中的框进行建模。 这具有以下优点:它允许定义法线并因此实现“逼真的”外观3D效果,但需要更多的努力。 请参阅“真实3D”解决方案的修订历史记录 。

虽然@ Marco13是一个伟大而有效的答案,如果您不想导入模型,正如@jewelsea所提到的那样,还有一种方法可以为每个立方体创建一个单独的网格,并根据Rubik的立方体的需要为面部着色。

这可以通过使用网络图像为网格的面部着色来实现:

立方网

如果您将此图像应用于Box

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

你会得到六张脸重复的相同图像。

因此,您可以创建自己的框以正确映射纹理坐标,或使用FXyz库中的CuboidMesh 。

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

最后,我们只需要添加面:顶点列表和纹理索引。 让我们创建一个基于Rubik立方体上面的常用符号排序的索引数组: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个位置,这将是魔方:

魔方

可以在此处找到创建它所需的代码。