Programmation JavaFX/Apparence 3D

L'apparence par défaut des objets de la scène construite au chapitre précédent est plutôt monotone :

Le but de ce chapitre est de montrer comment améliorer l'apparence des objets en leur donnant une couleur ou une texture.

L'apparence d'un objet (couleur, reflets, texture, ...) est défini par une instance de la classe abstraite Material. JavaFX fournit une classe concrète nommée PhongMaterial, nommée d'après le modèle d'ombrage de Phong.

Couleur

modifier

Les méthodes de la classe PhongMaterial sont les suivantes :

setDiffuseColor(Color)
Définit la couleur principale.
setSpecularColor(Color)
Définit la couleur des reflets des sources lumineuses.
setSpecularPower(float)
Définit la puissance de réflexion.

Exemple

modifier
PhongMaterial mat_blue = new PhongMaterial();
mat_blue.setDiffuseColor(Color.BLUE);
mat_blue.setSpecularColor(Color.LIGHTBLUE);
mat_blue.setSpecularPower(10.0);

On peut ensuite assigner l'apparence aux objets :

sphere.setMaterial(mat_blue);

Texture

modifier

Une texture est définie par une image pouvant définir la couleur de diffusion, de réflexion, d'illumination de l'objet.

Les méthodes de la classe PhongMaterial concernant les textures sont les suivantes :

setDiffuseMap(Image)
Définit la texture pour la couleur de diffusion.
setSpecularMap(Image)
Définit la texture pour la couleur de reflet.
setSelfIlluminationMap(Image)
Définit la texture pour l'intensité d'illumination.
setBumpMap(Image)
Définit la texture pour la granularité de l'objet.

Exemple

modifier
 
La texture utilisée

L'exemple ci-dessous utilise l'image Red-brick-wall-texture-1.jpg disponible sur commons.

//Image tex_im = new Image("file://///E:/Image/wall.png"); // Chemin local
Image tex_im = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/a/ac/Red-brick-wall-texture-1.jpg/640px-Red-brick-wall-texture-1.jpg");
PhongMaterial mat_wall = new PhongMaterial();
mat_wall.setDiffuseColor(Color.GRAY);
mat_wall.setSpecularColor(Color.WHITE);
mat_wall.setSpecularPower(8.0);
mat_wall.setDiffuseMap(tex_im);

Code complet

modifier
 
Formes 3D avec couleurs et textures
package org.wikibooks.fr.javafx;

import javafx.application.*;
import javafx.scene.*;
import javafx.scene.image.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.scene.transform.*;
import javafx.stage.*;

public class TestApparence3D extends Application
{
    private final Rotate rotate_x = new Rotate(0, Rotate.X_AXIS);
    private final Rotate rotate_y = new Rotate(0, Rotate.Y_AXIS);
    private final Translate translate = new Translate(0, 0, -1000);

    @Override
    public void start(Stage stage)
    {
        Image tex_im = new Image("https://upload.wikimedia.org/wikipedia/commons/thumb/a/ac/Red-brick-wall-texture-1.jpg/640px-Red-brick-wall-texture-1.jpg");
        PhongMaterial mat_wall = new PhongMaterial();
        mat_wall.setDiffuseColor(Color.GRAY);
        mat_wall.setSpecularColor(Color.WHITE);
        mat_wall.setSpecularPower(8.0);
        mat_wall.setDiffuseMap(tex_im);

        PhongMaterial mat_blue = new PhongMaterial();
        mat_blue.setDiffuseColor(Color.BLUE);
        mat_blue.setSpecularColor(Color.LIGHTBLUE);
        mat_blue.setSpecularPower(10.0);

        PhongMaterial mat_red = new PhongMaterial();
        mat_red.setDiffuseColor(Color.RED);
        mat_red.setSpecularColor(Color.ORANGERED);
        mat_red.setSpecularPower(10.0);

        int dx = 0, dy = 0, dz = 0;
        Sphere sphere = new Sphere(100);
        sphere.setTranslateX(dx);
        sphere.setTranslateY(dy);
        sphere.setTranslateZ(dz);
        sphere.setCursor(Cursor.OPEN_HAND);
        sphere.setMaterial(mat_blue);

        Box box = new Box(100, 200, 100);
        box.setTranslateX(dx + 250);
        box.setTranslateY(dy + 220);
        box.setTranslateZ(dz);
        box.setCursor(Cursor.OPEN_HAND);
        box.setMaterial(mat_wall);

        Cylinder cyl = new Cylinder(40, 220);
        cyl.setTranslateX(dx);
        cyl.setTranslateY(dy + 220);
        cyl.setTranslateZ(dz);
        cyl.setCursor(Cursor.OPEN_HAND);
        cyl.setMaterial(mat_red);

        TriangleMesh pyramid_mesh = new TriangleMesh(); // VertexFormat.POINT_TEXCOORD => P,T

        // Texture : coordonnées indépendantes de la taille de la texture
        // 0.5 = 50%, 1.0 = 100%, 2.0 = 200% (texture répétée 2 fois), ...
        pyramid_mesh.getTexCoords().addAll( 0   , 0    ); // --> T[0]
        pyramid_mesh.getTexCoords().addAll( 1.0F, 0    ); // --> T[1]
        pyramid_mesh.getTexCoords().addAll( 0.5F, 0.0F ); // --> T[2]
        pyramid_mesh.getTexCoords().addAll( 1.0F, 1.0F ); // --> T[3]
        pyramid_mesh.getTexCoords().addAll( 0   , 1.0F ); // --> T[4]

        float h = 150;                    // Height
        float s = 300;                    // Side
        pyramid_mesh.getPoints().addAll( // --> P[]
        //    X   Y    Z
              0,  0,   0,   // Point 0 - Top
              0,  h, -s/2,  // Point 1 - Front
            -s/2, h,   0,   // Point 2 - Left
              0,  h,  s/2,  // Point 3 - Back
             s/2, h,   0 ); // Point 4 - Right
        pyramid_mesh.getFaces().addAll( // --> F[] = index in P[],T[]
        //  P,T   P,T   P,T
            0,2,  2,4,  1,3,          // Front left face
            0,2,  1,4,  4,3,          // Front right face
            0,2,  4,4,  3,3,          // Back right face
            0,2,  3,4,  2,3,          // Back left face
            3,2,  1,3,  2,0,          // Bottom rear face
            3,2,  4,1,  1,4   );      // Bottom front face
        pyramid_mesh.getFaceSmoothingGroups().addAll(0,1,2,3,4,4);

        MeshView pyramid = new MeshView(pyramid_mesh);
        pyramid.setDrawMode(DrawMode.FILL);

        pyramid.setMaterial(mat_wall);
        pyramid.setTranslateX(dx);
        pyramid.setTranslateY(dy-150-h);
        pyramid.setTranslateZ(dz);

        Node[] allnodes = { sphere, cyl, pyramid, box };
        Color light_color = Color.LIGHTGREY;
        AmbientLight light = new AmbientLight(light_color);
        light.setTranslateX(dx - 180);
        light.setTranslateY(dy - 90);
        light.setTranslateZ(dz - 120);
        light.getScope().addAll(allnodes);

        PointLight light2 = new PointLight(light_color);
        light2.setTranslateX(dx + 180);
        light2.setTranslateY(dy + 190);
        light2.setTranslateZ(dz + 180);
        light2.getScope().addAll(allnodes);

        Group root = new Group(allnodes);

        root.getChildren().addAll(light, light2);

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

        scene.setFill(Color.BLACK);
        PerspectiveCamera camera = new PerspectiveCamera(true);
        camera.setNearClip(0.1);
        camera.setFarClip(10000.0);
        camera.getTransforms().addAll(rotate_x, rotate_y, translate);
        scene.setCamera(camera);

        stage.setTitle("3D JavaFX");
        stage.setScene(scene);
        stage.show();
    }

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

Ajustements

modifier

La texture de la base carrée fait apparaître les deux triangles car les 2 faces adjacentes sont associées à deux triangles qui ne sont pas adjacent dans la texture :

 

En ajustant la texture comme dans le code ci-dessous, on obtient une texture uniforme pour la base carrée :

pyramid_mesh.getFaces().addAll( // --> F[] = index in P[],T[]
//  P,T   P,T   P,T
    0,2,  2,4,  1,3,          // Front left face
    0,2,  1,4,  4,3,          // Front right face
    0,2,  4,4,  3,3,          // Back right face
    0,2,  3,4,  2,3,          // Back left face
    3,1,  1,4,  2,0,          // Bottom rear face
    3,1,  4,3,  1,4   );      // Bottom front face

 

Dans cette version, la caméra est fixe. Le chapitre suivant résout le problème en ajoutant la possibilité d'interagir avec la souris pour déplacer la caméra.