Aller au contenu

Programmation JavaFX/Apparence 3D

Un livre de Wikilivres.

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.

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

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.
La texture utilisée

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

PhongMaterial mat_wall = new PhongMaterial();
//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);
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);
    }
}

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 :

Formes 3D avec couleurs et textures

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

Formes 3D avec couleurs et textures ajustées

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.