IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Tutoriel de création d'un mini-moteur de rendu 3D en Java avec LWJGL

Le graphe de scène

Suite du moteur 3D cette fois-ci nous allons aller plus loin en créant ce qu’on appelle un graphe de scène. Cet article vous expliquera ce que c’est, son rôle et donnera également une implémentation possible.

Nous supposons ici que vous ayez lu et suivit l’introduction qui se trouve : Introduction sur la création d’un mini moteur 3D en Java grâce à LWJGLIntrodution sur la création d'un mini moteur 3D en Java grâce à LWJGL

Article lu   fois.

L'auteur

Profil ProSite personnelJHelp

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Le graphe, une façon de représenter un objet complexe

Code source du tutoriel

Code source
TéléchargerSélectionnez
Téléchargez le code source

Imaginons que nous voulions modéliser une trottinette.

Nous aimerions que le guidon puisse tourner par rapport à la base.

Que les deux roues soient indépendantes.

Modélisé la trottinette avec une seule maille, ne rendrait pas les choses faciles pour animer le guidon et les roues.

En effet on serait obligé de la recalculée à chaque rotation d’une roue ou du guidon.

C’est pour cela que nous allons utiliser, quatre mailles différentes et indépendantes.

Nous allons aussi vouloir stocker d’autres informations que les mailles, par exemple leur couleur.

Pour cela, on crée un objet 3D, qui contiendra la maille ainsi que d’autres informations, comme la couleur.

Notre trottinette sera donc constituée de quatre objets 3D :

  • Un objet 3D avec la maille modélisant la base.
  • Un objet 3D avec la maille modélisant le guidon.
  • Un objet 3D avec la maille modélisant la roue avant.
  • Un objet 3D avec la maille modélisant la roue arrière.

Ainsi, il suffira de bouger l’un de ces objets sans modifier les mailles pour créer une animation.

À chaque fois que la base bouge, on veut que les trois autres objets la suivent du même mouvement.

Sinon, la trottinette se « casserait ».

Le plus simple est de placer relativement ces trois objets par rapport à la base.

Ainsi, à chaque mouvement de la base, toutes les parties de la trottinette bougent de manière cohérente.

Quand on tourne le guidon, on souhaite que la roue avant fasse de même.

En suivant le même raisonnement, la roue avant sera plutôt placée relativement au guidon.

On a donc la base. Le guidon et la roue arrière placés relativement à la base.

Et la roue avant placée relativement au guidon.

On peut voir les choses ainsi, la base est le parent.

Elle a pour enfant direct le guidon et la roue arrière.

Le guidon quant à lui a pour enfant direct la roue avant.

Ce qui peut se représenter en graphe

Graphe de la trotinette

Ce raisonnement peut se reproduire pour tout un tas d’objets complexes

À chaque fois nous pouvons modéliser les parties indépendantes avec un graphe formé d’un ou plusieurs objets 3D indépendants.

Le fait de pour bouger les uns par rapport aux autres n’est pas le seul cas où l’on va découper en plusieurs objets.

Par exemples si le rendu est différent d’une partie à l’autre.

Par exemple une porte en bois vitrée.

Porte en bois vitrée

On aura un objet pour la partie bois, et au moins un pour la partie vitre.

Ainsi la partie boiss pourra être opaque et la ou les parties vitre semi-transparentes.

II. Les nœuds

Il est parfois utile de pouvoir placer des objets 3D relativement à un point virtuel.

Ce point virtuel est un peu comme un objet 3D qui n’aurait pas de maille.

Ce point virtuelle s’appelle un nœud.

En général on dit qu’un objet 3D est un nœud possédant une maille. Qui donne, d'un point de vue conception objet :

Hiérarchie des noeuds

III. Le graphe de scène

L’idée est de généraliser ce que nous venons de voir à toute la scène.

La scène dans son ensemble peut être perçue comme un objet très complexe.

Du coup on peut utiliser la représentation sous forme de graphe en lui donnant un nœud principal nommé racine.

Tous les objets de la scène seront relativement par rapport à ce nœud.

Ainsi pour bouger toute la scène, il suffit de déplacer ce nœud racine.

En décrivant ainsi la scène on obtient le graphe de scène.

Par exemple

Exemple de grpahe de scène

IV. Mise en place du graphe de scène

Comme nous l’avons vu, le graphe de scène est composé de nœuds (les objets 3D héritant de nœud)

Avant de créer un nœud, parlons de comment décrire sa position dans la 3D.

IV-A. Les nœuds et leur description de position

On veut non seulement pouvoir les positionner avec (X, Y, Z), mais aussi pouvoir faire des rotations et changer les facteurs d’échelle.

Pour la position (X, Y, Z) un triplet de 3 floats feront l’affaire.

Pour la rotation, on pourrait spécifier l’axe de rotation et un angle. Mais dans la pratique ce n’est pas très simple à utiliser.

Le plus simple est de définir un angle de rotation autour de chacun des trois axes X, Y et Z.

Donc trois floats, un pour chaque angle, feront l’affaire.

Pour les facteurs d’échelles on a à un sur chaque axe, donc également 3 floats.

Il faut bien garder en tête que cette description est relative au nœud parent.

Sauf bien sûr pour le nœud racine pour qui elles sont absolues

On ajoute au nœud ces enfants dans une liste de nœuds.

Ce qui donne pour le moment :

Node3D
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
package fr.developez.tutorial.java.dimension3.render;

import fr.developez.tutorial.java.dimension3.tool.NonNulltool.NonNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Nœud virtuel de la scènescène
 */
public class Node3D
{
    /**
     * Coordonne X de la position dans la 3D
     */
    public float x = 0f;
    /**
     * Coordonne Y de la position dans la 3D
     */
    public float y = 0f;
    /**
     * Coordonne Z de la position dans la 3D
     */
    public float z = 0f;

    /**
     * Angle de la rotation autour de l'axe X
     */
    public float angleX = 0f;
    /**
     * Angle de la rotation autour de l'axe Y
     */
    public float angleY = 0f;
    /**
     * Angle de la rotation autour de l'axe Z
     */
    public float angleZ = 0f;

    /**
     * Facteur d'échelle le long de l'axe X <br/>
     * La valeur doit toujours être strictement positive
     */
    public float scaleX = 1f;
    /**
     * Facteur d'échelle le long de l'axe Y <br/>
     * La valeur doit toujours être strictement positive
     */
    public float scaleY = 1f;
    /**
     * Facteur d'échelle le long de l'axe Z <br/>
     * La valeur doit toujours être strictement positive
     */
    public float scaleZ = 1f;

    /**
     * Liste des nœuds placés relativement à celui-ci
     */
    private final List<Node3D> children = new ArrayList<>();

    public Node3D()
    {
    }

    /**
     * Ajoute un nœud enfant
     */
    public final void addChild(@NonNull final Node3D node)
    {
        Objects.requireNonNull(node, "node must not be null");

        // On synchronize pour éviter l'accès concurrent
        synchronized (this.children)
        {
            this.children.add(node);
        }
    }
}

IV-B. « Dessiner » le nœud avec OpenGL

On a les propriétés du nœud, maintenant il faut dire à OpenGL de les utiliser afin de placer notre nœud.

Le principe va être de placer le nœud, puis chacun de ces enfants relativement à celui-ci de manière récursive.

On peut toujours considérer que lors du placement d'un nœud la matrice actuelle est celle du parent.

Cela marche aussi pour le nœud racine en voyant le parent comme le repère de départ.

Pour ne pas perturber les éventuels autres enfants due son parent, un nœud va devoir restaurer la matrice du parent comme à l'origine à la fin.

Comme nous l'avons vu dans l'introduction, OpenGL possède une mécanisme pour cela : la pile des matrices.

C'est pour cela que le code du placement va ressembler à :

Node3D
Sélectionnez
package fr.developez.tutorial.java.dimension3.render;

// ...
import fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import org.lwjgl.opengl.GL11;
// ...

/**
 * Nœud virtuel de la scène
 */
public class Node3D
{
    // ... 
    /**
     * Dessine le noeud
     */
    @ThreadOpenGL
    final void drawNode()
    {
        // On sauvegarde la matrice actuelle
        GL11.glPushMatrix();

        // TODO placer le nœud et ces enfants

        // On restaure la matrice sauvegardée
        GL11.glPopMatrix();
    }
}

Comme dit dans l'introduction, l'ordre des instructions de placements est important.

On veut que les rotations et les facteurs d'échelles soit relatifs à la position du nœud.

On va donc d'abord placer le nœud en (X, Y, Z)

On veut que les facteurs d'échelles n'influencent pas les rotations, ils seront donc après.

L'ordre des rotations des rotations entre elles n'a pas d'importance.

Tourner sur soi-même vers la droite, puis vers le haut, revient à tourner vers le haut, puis vers la droite, par exemple.

Preuve de rotation 1
Preuve de rotation 2

Par habitude, on mettra la rotation autour de X, puis Y, enfin Z.

Donc l'ordre sera

  1. Position (X,Y,Z)
  2. Rotation autour de X
  3. Rotation autour de Y
  4. Rotation autour de Z
  5. Facteurs d'échelles
Node3D
Sélectionnez
package fr.developez.tutorial.java.dimension3.render;

// ...
import fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import org.lwjgl.opengl.GL11;
// ...

/**
 * Noeud virtuel de la scène
 */
public class Node3D
{
    // ... 
    /**
     * Dessine le noeud
     */
    @ThreadOpenGL
    final void drawNode()
    {
        // On sauvegarde la matrice actuelle
        GL11.glPushMatrix();

        // *** Position du noeud ***
        // Position aux coordonnées X, Y, Z
        GL11.glTranslatef(this.x, this.y, this.z);
        // Rotation autour de l'axe X
        GL11.glRotatef(this.angleX, 1f, 0f, 0f);
        // Rotation autour de l'axe Y
        GL11.glRotatef(this.angleY, 0f, 1f, 0f);
        // Rotation autour de l'axe Z
        GL11.glRotatef(this.angleZ, 0f, 0f, 1f);
        // Application des facteurs d'échelle
        GL11.glScalef(this.scaleX, this.scaleY, this.scaleZ);

        // TODO placer les enfants

        // On restaure la matrice sauvegardée
        GL11.glPopMatrix();
    }
}

Avant de placer les enfants, nous allons préparer l'affichage spécifique. Pour un nœud générique, il n'y aura rien à y faire.

Mais, pour les objets 3D nous permettra de dessiner leur maille

Node3D
Sélectionnez
package fr.developez.tutorial.java.dimension3.render;

// ...
import fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import org.lwjgl.opengl.GL11;
// ...

/**
 * Nœud virtuel de la scène 
 */
public class Node3D
{
    // ... 
    /**
     * Dessine le noeud
     */
    @ThreadOpenGL
    final void drawNode()
    {
        // On sauvegarde la matrice actuelle
        GL11.glPushMatrix();

        // *** Position du nœud ***
        // Position aux coordonnées X, Y, Z
        GL11.glTranslatef(this.x, this.y, this.z);
        // Rotation autour de l'axe X
        GL11.glRotatef(this.angleX, 1f, 0f, 0f);
        // Rotation autour de l'axe Y
        GL11.glRotatef(this.angleY, 0f, 1f, 0f);
        // Rotation autour de l'axe Z
        GL11.glRotatef(this.angleZ, 0f, 0f, 1f);
        // Application des facteurs d'échelle
        GL11.glScalef(this.scaleX, this.scaleY, this.scaleZ);

        // Dessin spécifique
        this.drawSpecific();

        // TODO placer les enfants

        // On restaure la matrice sauvegardée
        GL11.glPopMatrix();
    }

    /**
     * Dessin supplémentaire pour le nœud 
     * <p>
     * A surcharger par les classes enfants
     * <p>
     * Ne fait rien par défaut
     */
    @ThreadOpenGL
    void drawSpecific()
    {
        // Rien à faire d'autres. Les classes enfants y mettront leur code spécifique
    }
}

Et maintenant, par récursion ajoutons les enfants

Node3D
Sélectionnez
package fr.developez.tutorial.java.dimension3.render;

// ...
import fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import org.lwjgl.opengl.GL11;
// ...

/**
 * Noeud virtuel de la scène
 */
public class Node3D
{
    // ... 
    /**
     * Dessine le noeud
     */
    @ThreadOpenGL
    final void drawNode()
    {
        // On sauvegarde la matrice actuelle
        GL11.glPushMatrix();

        // *** Position du noeud ***
        // Position aux coordonnées X, Y, Z
        GL11.glTranslatef(this.x, this.y, this.z);
        // Rotation autour de l'axe X
        GL11.glRotatef(this.angleX, 1f, 0f, 0f);
        // Rotation autour de l'axe Y
        GL11.glRotatef(this.angleY, 0f, 1f, 0f);
        // Rotation autour de l'axe Z
        GL11.glRotatef(this.angleZ, 0f, 0f, 1f);
        // Application des facteurs d'échelle
        GL11.glScalef(this.scaleX, this.scaleY, this.scaleZ);

        // Dessin specific
        this.drawSpecific();

        // Placement des noeuds enfants relativement à la position de ce noeud
        // On synchronize pour éviter l'accès concurrent
        synchronized (this.children)
        {
            for (final Node3D child : this.children)
            {
                child.drawNode();
            }
        }

        // On restaure la matrice sauvegardée
        GL11.glPopMatrix();
    }
    // …
}

Cet algorithme nous posera des soucis quand nous parleront de transparence dans un tutoriel dédié aux matériaux. Nous vous indiquerons à ce moment-là, la solution. Pour le moment, il nous suffit pour ce tutoriel.

IV-C. Les objets 3D et les mailles

Comme nous l'avons vu dans l'introduction, pour se dessiner, un objet 3D doit être constitué d'une maille.

Une maille est un ensemble de faces

Chaque face décrit un polygone de 3 ou 4 cotés en général.

On va définir les faces par les sommets du polygone.

Comme nous le verrons quand nous parlerons des textures ou des effets de lumière, le sommet possédera d'autre informations que ses coordonnées (X, Y, Z).

En infographie cela s'appelle un vertex.

Pour le moment il sera simplement, ce qui suit, il sera enrichit dans des prochains tutoriels.

Vertex
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
/**
 * Vertex décrivant un sommet d'une face d'une maille.
 * <br/>
 * Le vertex contient non seulement les coordonnées du point,
 * mais également d'autre informations utiles pour les textures et la lumiere
 */
public class Vertex
{
    public float x = 0f;
    public float y = 0f;
    public float z = 0f;

    public Vertex()
    {
    }

    public Vertex(float x, float y, float z)
    {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

Maintenant que nous avons notre vertex, on peut décrire une face

Face
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
package fr.developez.tutorial.java.dimension3.render.mesh;

import fr.developez.tutorial.java.dimension3.tool.NonNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Consumer;

/**
 * Décrit une face d'une maille
 */
public final class Face
{
    private final List<Vertex> vertexs = new ArrayList<>();

    public Face(Vertex... vertexs)
    {
        if (vertexs != null)
        {
            Collections.addAll(this.vertexs, vertexs);
        }
    }

    public void addVertex(float x, float y, float z)
    {
        this.addVertex(new Vertex(x, y, z));
    }

    public void addVertex(@NonNull final Vertex vertex)
    {
        Objects.requireNonNull(vertex, "vertex must not be null");

        synchronized (this.vertexs)
        {
            this.vertexs.add(vertex);
        }
    }

    public void forEachVertex(@NonNull final Consumer<Vertex> action)
    {
        Objects.requireNonNull(action, "action must not be null");

        synchronized (this.vertexs)
        {
            for (final Vertex vertex : this.vertexs)
            {
                action.accept(vertex);
            }
        }
    }
}

Quant à la maille

Mesh
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
package fr.developez.tutorial.java.dimension3.render;

import fr.developez.tutorial.java.dimension3.render.mesh.Face;
import fr.developez.tutorial.java.dimension3.tool.NonNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/**
 * Maille d'un objet
 */
public class Mesh
{
    private final List<Face> faces = new ArrayList<>();

    public Mesh()
    {
    }

    public void addFace(@NonNull final Face face)
    {
        Objects.requireNonNull(face, "face must not be null");

        synchronized (this.faces)
        {
            this.faces.add(face);
        }
    }
}

Et l'objet 3D

Object3D
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
package fr.developez.tutorial.java.dimension3.render;

import fr.developez.tutorial.java.dimension3.tool.NonNull;
import java.util.Objects;

/**
 * Objet 3D
 */
public class Object3D
        extends Node3D
{
    @NonNull
    public final Mesh    mesh  = new Mesh();

    public Object3D()
    {
    }
}

IV-D. Un peu de couleurs

On voudrait pouvoir appliquer une couleur à notre objet.

Au lieu de stocker les quatre informations d'une couleur, l'intensité de rouge, vert, bleu ainsi que le niveau d'opacité.

Nous allons créer un objet qui représente une couleur, ce qui nous permettra de créer quelques couleurs de bases.

Color4f
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
package fr.developez.tutorial.java.dimension3.render;

import fr.developez.tutorial.java.dimension3.tool.MathTools;
import fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import org.lwjgl.opengl.GL11;

/**
 * Représente une couleur
 */
public class Color4f
{
    public static final Color4f BLACK      = new Color4f(0f);
    public static final Color4f DARK_GREY  = new Color4f(0.25f);
    public static final Color4f GREY       = new Color4f(0.5f);
    public static final Color4f LIGHT_GREY = new Color4f(0.75f);
    public static final Color4f WHITE      = new Color4f(1f);

    public static final Color4f DARK_RED  = new Color4f(0.5f, 0f, 0f);
    public static final Color4f RED       = new Color4f(1f, 0f, 0f);
    public static final Color4f LIGHT_RED = new Color4f(1f, 0.5f, 0.5f);

    public static final Color4f DARK_GREEN  = new Color4f(0f, 0.5f, 0f);
    public static final Color4f GREEN       = new Color4f(0f, 1f, 0f);
    public static final Color4f LIGHT_GREEN = new Color4f(0.5f, 1f, 0.5f);

    public static final Color4f DARK_BLUE  = new Color4f(0f, 0f, 0.5f);
    public static final Color4f BLUE       = new Color4f(0f, 0f, 1f);
    public static final Color4f LIGHT_BLUE = new Color4f(0.5f, 0.5f, 1f);

    /**
     * Intensité de rouge
     */
    public final float red;
    /**
     * Intensité de vert
     */
    public final float green;
    /**
     * Intensité de bleu
     */
    public final float blue;
    /**
     * Niveau d'opacité
     */
    public final float alpha;

    /**
     * Construit la couleur à partir d'un description ARGB
     *
     * @param argb Description ARGB
     */
    public Color4f(int argb)
    {
        this((float) ((argb >> 16) & 0xFF) / 255f,
             (float) ((argb >> 8) & 0xFF) / 255f,
             (float) (argb & 0xFF) / 255f,
             (float) ((argb >> 24) & 0xFF) / 255f);
    }

    /**
     * Construit une couleur avec niveau de gris opaque
     *
     * @param grey Niveau de gris
     */
    public Color4f(float grey)
    {
        this(grey, grey, grey, 1f);
    }

    /**
     * Construit une couleur avec niveau de gris
     *
     * @param grey  Niveau de gris
     * @param alpha Niveau d'opacité
     */
    public Color4f(float grey, float alpha)
    {
        this(grey, grey, grey, alpha);
    }

    /**
     * Construit une couleur opaque
     *
     * @param red   Intensité de rouge
     * @param green Intensité de vert
     * @param blue  Intensité de bleu
     */
    public Color4f(float red, float green, float blue)
    {
        this(red, green, blue, 1f);
    }

    /**
     * Construit une couleur
     *
     * @param red   Intensité de rouge
     * @param green Intensité de vert
     * @param blue  Intensité de bleu
     * @param alpha Niveau d'opacité
     */
    public Color4f(float red, float green, float blue, float alpha)
    {
        this.red   = MathTools.bounds(red, 0f, 1f);
        this.green = MathTools.bounds(green, 0f, 1f);
        this.blue  = MathTools.bounds(blue, 0f, 1f);
        this.alpha = MathTools.bounds(alpha, 0f, 1f);
    }

    /**
     * Demande à OpenGL d'utilisé cette couleur
     */
    @ThreadOpenGL
    void apply()
    {
        GL11.glColor4f(this.red, this.green, this.blue, this.alpha);
    }
}

La méthode `apply` permettra de demander à l'OpenGL d'utiliser la couleur.

On ajoute l'information de couleur à notre objet 3D

Object3D
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
package fr.developez.tutorial.java.dimension3.render;

import fr.developez.tutorial.java.dimension3.tool.NonNull;
import java.util.Objects;

/**
 * Objet 3D
 */
public class Object3D
        extends Node3D
{
    private      Color4f color = Color4f.GREY;
    @NonNull
    public final Mesh    mesh  = new Mesh();

    public Object3D()
    {
    }

    @NonNull
    public Color4f getColor()
    {
        return this.color;
    }

    public void setColor(@NonNull final Color4f color)
    {
        this.color = Objects.requireNonNull(color, "color must not be null");
    }
}

IV-E. Dessiner la maille avec OpenGL

Pour chaque face de la maille, on la dessine grâce à ses sommets.

On doit dire à OpenGL où commence et où finit le polygone.

Le nouveau code de la maille

Mesh
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
package fr.developez.tutorial.java.dimension3.render;

import fr.developez.tutorial.java.dimension3.render.mesh.Face;
import fr.developez.tutorial.java.dimension3.tool.NonNull;
import fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.lwjgl.opengl.GL11;

/**
 * Maille d'un objet
 */
public class Mesh
{
    private final List<Face> faces = new ArrayList<>();

    public Mesh()
    {
    }

    public void addFace(@NonNull final Face face)
    {
        Objects.requireNonNull(face, "face must not be null");

        synchronized (this.faces)
        {
            this.faces.add(face);
        }
    }

    @ThreadOpenGL
    void drawMesh()
    {
        synchronized (this.faces)
        {
            for (final Face face : this.faces)
            {
                // Annonce à OpenGL le début du polygone
                GL11.glBegin(GL11.GL_POLYGON);

                face.forEachVertex(vertex -> GL11.glVertex3f(vertex.x, vertex.y, vertex.z));

                // Annonce à OpenGL la fin du polygone
                GL11.glEnd();
            }
        }
    }
}

Comme pour le moment un vertex n'est composé que des coordonnées, on les précise à OpenGL. Quand nous auront plus d'informations, on mettra les instructions supplémentaires au sein de la lambda

Remarque

Devoir dessiner toutes les faces à chaque boucle de rendu n'est pas très optimisé.

Heureusement, comme nous le verrons un peu plus loin, OpenGL à un mécanisme pour nous aider.

Puis il nous reste plus qu'a complété l'objet pour que celui-ci puisse être dessiné par l'OpenGL

Object3D
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
package fr.developez.tutorial.java.dimension3.render;

import fr.developez.tutorial.java.dimension3.tool.NonNull;
import fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import java.util.Objects;

/**
 * Objet 3D
 */
public class Object3D
        extends Node3D
{
    private      Color4f color = Color4f.GREY;
    @NonNull
    public final Mesh    mesh  = new Mesh();

    public Object3D()
    {
    }

    @NonNull
    public Color4f getColor()
    {
        return this.color;
    }

    public void setColor(@NonNull final Color4f color)
    {
        this.color = Objects.requireNonNull(color, "color must not be null");
    }

    @ThreadOpenGL
    @Override
    void drawSpecific()
    {
        this.color.apply();
        this.mesh.drawMesh();
    }
}

IV-F. Un exemple d’objet 3D : la boîte

Afin d'avoir un objet de base à représenter créons une boîte

Box
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
package fr.developez.tutorial.java.dimension3.geometry;

import fr.developez.tutorial.java.dimension3.render.Object3D;
import fr.developez.tutorial.java.dimension3.render.mesh.Face;

/**
 * Une boîte
 */
public class Box
        extends Object3D
{
    public Box()
    {
        // Face de la boîte
        Face face = new Face();
        face.addVertex(-0.5f, 0.5f, 0.5f);
        face.addVertex(0.5f, 0.5f, 0.5f);
        face.addVertex(0.5f, -0.5f, 0.5f);
        face.addVertex(-0.5f, -0.5f, 0.5f);
        this.mesh.addFace(face);

        // Dos de la boîte
        face = new Face();
        face.addVertex(-0.5f, -0.5f, -0.5f);
        face.addVertex(0.5f, -0.5f, -0.5f);
        face.addVertex(0.5f, 0.5f, -0.5f);
        face.addVertex(-0.5f, 0.5f, -0.5f);
        this.mesh.addFace(face);

        // Haut de la boîte
        face = new Face();
        face.addVertex(-0.5f, 0.5f, -0.5f);
        face.addVertex(0.5f, 0.5f, -0.5f);
        face.addVertex(0.5f, 0.5f, 0.5f);
        face.addVertex(-0.5f, 0.5f, 0.5f);
        this.mesh.addFace(face);

        // Bas de la boîte
        face = new Face();
        face.addVertex(-0.5f, -0.5f, 0.5f);
        face.addVertex(0.5f, -0.5f, 0.5f);
        face.addVertex(0.5f, -0.5f, -0.5f);
        face.addVertex(-0.5f, -0.5f, -0.5f);
        this.mesh.addFace(face);

        // Coté droit de la boîte
        face = new Face();
        face.addVertex(0.5f, -0.5f, 0.5f);
        face.addVertex(0.5f, 0.5f, 0.5f);
        face.addVertex(0.5f, 0.5f, -0.5f);
        face.addVertex(0.5f, -0.5f, -0.5f);
        this.mesh.addFace(face);

        // Coté gauche de la boîte
        face = new Face();
        face.addVertex(-0.5f, -0.5f, -0.5f);
        face.addVertex(-0.5f, 0.5f, -0.5f);
        face.addVertex(-0.5f, 0.5f, 0.5f);
        face.addVertex(-0.5f, -0.5f, 0.5f);
        this.mesh.addFace(face);
    }
}

Comme on le voit, il suffit de créer chaque face de la boîte en faisant bien attention au sens des sommets pour que la partie extérieure soit visible.

IV-G. Le graphe de scène

Maintenant que tout est prêt, on peut créer le graphe de scène.

SceneGraph
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
package fr.developez.tutorial.java.dimension3.render;

import fr.developez.tutorial.java.dimension3.tool.NonNull;
import fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import java.util.Objects;
import org.lwjgl.opengl.GL11;

/**
 * Le graphe de scène
 */
public class SceneGraph
{
    @NonNull
    public final Node3D  root            = new Node3D();
    private      Color4f backgroundColor = Color4f.WHITE;

    public SceneGraph()
    {
    }

    @NonNull
    public Color4f getBackground()
    {
        return this.backgroundColor;
    }

    public void setBackground(@NonNull final Color4f backgroundColor)
    {
        this.backgroundColor = Objects.requireNonNull(backgroundColor, "backgroundColor must not be null");
    }

    @ThreadOpenGL
    void drawScene()
    {
        GL11.glClearColor(this.backgroundColor.red, this.backgroundColor.green, this.backgroundColor.blue, 1f);
        this.root.drawNode();
    }
}

On remarque, que l'on a ajouté l'information de couleur de fond afin de pouvoir facilement la changer.

IV-H. Lier le graphe de scène avec la boucle de rendue

Maintenant, on peut ajouter notre graphe de scène à la boucle de rendu

Render3D
Sélectionnez
package fr.developez.tutorial.java.dimension3.render;

import fr.developez.tutorial.java.dimension3.tool.GLU;
import fr.developez.tutorial.java.dimension3.tool.NonNull;
import fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import java.util.Objects;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;

/**
 * Boucle de redu
 */
class Render3D
{
    /**
     * Graphe de scène associé
     */
    @NonNull
    final SceneGraph sceneGraph = new SceneGraph();

    Render3D()
    {
    }

// ...

    /**
     * Dessine la scène 3D
     */
    @ThreadOpenGL
    private void drawScene()
    {
        this.sceneGraph.drawScene();
    }
}

Exposons le graphe de scène afin de pouvoir décrire une scène.

Window3D
Sélectionnez
package fr.developez.tutorial.java.dimension3.render;

import fr.developez.tutorial.java.dimension3.thread.ThreadManager;
import fr.developez.tutorial.java.dimension3.tool.NonNull;
import fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import java.nio.IntBuffer;
import java.util.Objects;
import org.lwjgl.glfw.Callbacks;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWErrorCallback;
import org.lwjgl.glfw.GLFWVidMode;
import org.lwjgl.opengl.GL;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;

public class Window3D
{
    // ...
    
    @NonNull
    public SceneGraph getSceneGraph()
    {
        return this.render3D.sceneGraph;
    }

    // ...
}

IV-I. Exemple de scène

Un petit exemple pour utiliser ce qu’on nous venons d’écrire :

MianBlueAngGreenCubes
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
package fr.developez.tutorial.java.dimension3;

import fr.developez.tutorial.java.dimension3.geometry.Box;
import fr.developez.tutorial.java.dimension3.render.Color4f;
import fr.developez.tutorial.java.dimension3.render.SceneGraph;
import fr.developez.tutorial.java.dimension3.render.Window3D;
import fr.developez.tutorial.java.dimension3.tool.NonNull;

public class MainBlueAndGreenCubes
{
    public static void main(String[] args)
    {
        final Window3D window3D = new Window3D(800, 600, "Tutoriel 3D - Une boite blue et une verte");
        MainBlueAndGreenCubes.drawScene(window3D.getSceneGraph());
    }

    private static void drawScene(@NonNull final SceneGraph sceneGraph)
    {
        final Box blueBox  = new Box();
        final Box greenBox = new Box();
        sceneGraph.root.addChild(blueBox);
        sceneGraph.root.addChild(greenBox);

        blueBox.x      = -1f;
        blueBox.z      = -2f;
        blueBox.angleX = 22.5f;
        blueBox.angleY = 22.5f;
        blueBox.setColor(Color4f.BLUE);

        greenBox.x      = 1f;
        greenBox.z      = -2f;
        greenBox.angleX = 22.5f;
        greenBox.angleY = 22.5f;
        greenBox.setColor(Color4f.GREEN);
    }
}

Ce qui donne

Un boîte bleue et une verte

On remarque au passage que la construction et la modification de la scène ne sont pas faite dans le thread OpenGL.

Ainsi l'utilisateur de la fenêtre n'a pas à s'en occuper

V. Optimiser le dessin de la maille

La maille ne va pas changer souvent.

Même, la plupart du temps, une fois définit, elle ne va pas changer du tout.

Pour cela nous allons voir deux optimisations au sein de la maille elle même.

L'une OpenGL, l'autre Java.

V-A. Optimisation OpenGL : La notion de liste

L'idée de cette optimisation, est de dire à la carte 3D de se rappelée l'ensemble des faces de notre maille.

Pour cela OpenGL introduit la notion de liste d'instructions.

Une liste d'instructions va permettre de sauvegarder dans la carte 3D les différentes instructions OpenGL pour construire notre maille.

Cela va même plus loin, en fait cela va permettre de lui dire qu'elles font parties d'un même bloc et donc pouvoir les pré-compiler et les optimiser.

Bref en plus de gagner du temps en n'envoyant plus systématiquement toutes les instructions OpenGL.

On va gagner du temps car la carte 3D va utiliser directement un format à elle et optimiser pour elle.

Nous allons avoir besoin de garder une référence sur la liste. Cette référence sera un entier positif ou nul. C'est la seule chose que l'on peut être sûr.

Comme le pointeur sur la fenêtre 3D, c'est une valeur opaque, c'est-à-dire que seule la carte 3D connaît sa vraie signification.

Mais tout ce que nous avons besoin de savoir c'est qu'elle est unique par liste et positive ou nulle.

Si la maille change, il faudra dire à la carte 3D d'oublier notre liste devenue obsolète et en créer une autre

Mesh
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
package fr.developez.tutorial.java.dimension3.render;

import fr.developez.tutorial.java.dimension3.render.mesh.Face;
import fr.developez.tutorial.java.dimension3.tool.NonNull;
import fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.lwjgl.opengl.GL11;

/**
 * Maille d'un objet
 */
public class Mesh
{
    private final List<Face> faces = new ArrayList<>();

    /**
     * Identifiant OpenGL de la liste des polygones décrivant la maille
     */
    private int     idList                 = -1;
    /**
     * Indique que la maille à été modifiée et donc a besoin d'être rafraîchie par OpenGL
     */
    private boolean needReconstructTheList = true;

    public Mesh()
    {
    }

    public void addFace(@NonNull final Face face)
    {
        Objects.requireNonNull(face, "face must not be null");

        synchronized (this.faces)
        {
            this.faces.add(face);
        }

        // On a modifié la maille, il faut donc la rafraîchir
        this.needReconstructTheList = true;
    }

    /**
     * Indique qu'on a modifier l'une de faces ou l'un des vertex d'une des faces depuis l'extérieur.
     *
     * Dans ce cas on a besoin de rafraîchir la maille
     */
    public void forceRefresh()
    {
        this.needReconstructTheList = true;
    }

    @ThreadOpenGL
    void drawMesh()
    {
        // On va créer la liste des polygones la première fois et chaque fois que nécessaire
        if (this.idList < 0 || this.needReconstructTheList)
        {
            // On va construire la liste, donc on aura plus besoin de le faire au prochain tour
            this.needReconstructTheList = false;

            // On efface l'ancienne liste de la mémoire de la carte graphique, celle-ci étant devenu obsolète
            if (this.idList >= 0)
            {
                GL11.glDeleteLists(this.idList, 1);
            }

            // On demande une nouvelle référence de liste à la carte graphique
            this.idList = GL11.glGenLists(1);
            // On indique le début de la liste
            GL11.glNewList(this.idList, GL11.GL_COMPILE);

            synchronized (this.faces)
            {
                for (final Face face : this.faces)
                {
                    // Annonce à OpenGL le début du polygone
                    GL11.glBegin(GL11.GL_POLYGON);

                    face.forEachVertex(vertex -> GL11.glVertex3f(vertex.x, vertex.y, vertex.z));

                    // Annonce à OpenGL la fin du polygone
                    GL11.glEnd();
                }
            }

            // On indique la fin de la liste
            GL11.glEndList();
        }

        // On dessine la liste mémorisé
        GL11.glCallList(this.idList);
    }
}

Plus de détails :

  • `GL11.glDeleteLists(this.idList, 1)` libère de la carte 3D la liste. Étant devenue obsolete, autant libéré la mémoire qui lui correspond.
  • `GL11.glGenLists(1)` demande et réserve une nouvelle référence de liste. Cette référence ne sera plus attribuée tant qu'elle n'est pas détruite.
  • `GL11.glNewList(this.idList, GL11.GL_COMPILE)` Indique le début de la description de la liste. On a choisi l'option `COMPILE` afin de demander de la compiler à la fin afin de profiter au maximum des optimisations coté carte 3D.
  • `GL11.glEndList()` Indique la fin de la liste, et lance sa compilation. À noter que rien n'est dessiner à l'écran entre le `glNewList` et cette instruction.
  • `GL11.glCallList(this.idList)` demande d'afficher le contenu de la liste. Ici notre maille.

V-B. Optimisation Java : La notion de scellé

Nous avons maintenant la description de la maille en mémoire sur la carte 3D.

Si on est sûr que la maille ne va plus changer, on peut effacer de la mémoire de la JVM la description de celle-ci.

Nous allons ajouter la possibilité de sceller la maille. C'est-à-dire, la figée pour de bon.

Mesh
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
package fr.developez.tutorial.java.dimension3.render;

import fr.developez.tutorial.java.dimension3.render.mesh.Face;
import fr.developez.tutorial.java.dimension3.tool.NonNull;
import fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import org.lwjgl.opengl.GL11;

/**
 * Maille d'un objet
 */
public final class Mesh
{
    private final List<Face> faces = new ArrayList<>();

    /**
     * Identifiant OpenGL de la liste des polygones décrivant la maille
     */
    private int           idList                 = -1;
    /**
     * Indique que la maille à été modifiée et donc a besoin d'être rafraîchit par OpenGL
     */
    private boolean       needReconstructTheList = true;
    /**
     * Indique si la maille est scellée
     */
    private AtomicBoolean sealed                 = new AtomicBoolean(false);

    public Mesh()
    {
    }

    /**
     * Indique si la maille est scellée
     */
    public boolean sealed()
    {
        return this.sealed.get();
    }

    /**
     * Scelle la maille, elle ne pourra plus être modifiée
     */
    public void seal()
    {
        if (this.sealed.compareAndSet(false, true))
        {
            // On s'assure de prendre les éventuelles dernières modifications
            this.needReconstructTheList = true;
        }
    }

    public void addFace(@NonNull final Face face)
    {
        Objects.requireNonNull(face, "face must not be null");

        // Si la maille est scellée, on n'ajoute pas la face
        if (this.sealed.get())
        {
            return;
        }

        synchronized (this.faces)
        {
            this.faces.add(face);
        }

        // On a modifié la mail, il faut donc la rafraîchir
        this.needReconstructTheList = true;
    }

    /**
     * Indique qu'on a modifier l'une de faces ou l'un des vertex d'une des faces depuis l'extérieur.
     * <p>
     * Dans ce cas on a besoin de rafraîchir la maille
     */
    public void forceRefresh()
    {
        // La maille ne peut changée que si elle n'est pas scellée
        if(!this.sealed.get())
        {
            this.needReconstructTheList = true;
        }
    }

    @ThreadOpenGL
    void drawMesh()
    {
        // On va créer la liste des polygones la première fois et chaque fois que nécessaire
        if (this.idList < 0 || this.needReconstructTheList)
        {
            // On va construire la liste, donc on aura plus besoin de le faire au prochain tour
            this.needReconstructTheList = false;

            // On efface l'ancienne liste de la mémoire de la carte graphique, celle-ci étant devenu obsolète
            if (this.idList >= 0)
            {
                GL11.glDeleteLists(this.idList, 1);
            }

            // On demande une nouvelle référence de liste à la carte graphique
            this.idList = GL11.glGenLists(1);
            // On indique le début de la liste
            GL11.glNewList(this.idList, GL11.GL_COMPILE);

            synchronized (this.faces)
            {
                for (final Face face : this.faces)
                {
                    // Annonce à OpenGL le début du polygone
                    GL11.glBegin(GL11.GL_POLYGON);

                    face.forEachVertex(vertex -> GL11.glVertex3f(vertex.x, vertex.y, vertex.z));

                    // Annonce à OpenGL la fin du polygone
                    GL11.glEnd();
                }
            }

            // On indique la fin de la liste
            GL11.glEndList();

            // Si la maille est scellée, il s'agit de la dernière construction de la liste, on peut libérer la description de la maille de la mémoire
            if (this.sealed.get())
            {
                this.faces.clear();
            }
        }

        // On dessine la liste mémorisée
        GL11.glCallList(this.idList);
    }
}

Changeons un peu l'exemple de tout à l'heure pour illustrer notre propos

MainBlueAndGreenCubes
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
package fr.developez.tutorial.java.dimension3;

import fr.developez.tutorial.java.dimension3.geometry.Box;
import fr.developez.tutorial.java.dimension3.render.Color4f;
import fr.developez.tutorial.java.dimension3.render.SceneGraph;
import fr.developez.tutorial.java.dimension3.render.Window3D;
import fr.developez.tutorial.java.dimension3.tool.NonNull;

public class MainBlueAndGreenCubes
{
    public static void main(String[] args)
    {
        final Window3D window3D = new Window3D(800, 600, "Tutoriel 3D - Une boite bleue et une verte");
        MainBlueAndGreenCubes.drawScene(window3D.getSceneGraph());
    }

    private static void drawScene(@NonNull final SceneGraph sceneGraph)
    {
        final Box blueBox = new Box();
        blueBox.mesh.seal();
        final Box greenBox = new Box();
        greenBox.mesh.seal();
        sceneGraph.root.addChild(blueBox);
        sceneGraph.root.addChild(greenBox);

        blueBox.x      = -1f;
        blueBox.z      = -2f;
        blueBox.angleX = 22.5f;
        blueBox.angleY = 22.5f;
        blueBox.setColor(Color4f.BLUE);

        greenBox.x      = 1f;
        greenBox.z      = -2f;
        greenBox.angleX = 22.5f;
        greenBox.angleY = 22.5f;
        greenBox.setColor(Color4f.GREEN);
    }
}

Bien entendu, cela ne change rien visuellement.

VI. Les clones

Si deux objets 3D ont exactement la même description de maille, il est interéssant de pouvoir partagée celle-ci.

Cela évite d'avoir en mémoire JVM deux fois la même description et d'avoir coté carte 3D deux listes qui contiennent exactement la même chose.

C'est l'idée derrière les clones.

Clone3D
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
package fr.developez.tutorial.java.dimension3.render;

import fr.developez.tutorial.java.dimension3.tool.NonNull;
import fr.developez.tutorial.java.dimension3.tool.ThreadOpenGL;
import java.util.Objects;

/**
 * Clone d'un objet 3D
 */
public class Clone3D
        extends Node3D
{
    private       Color4f  color = Color4f.GREY;
    /**
     * Objet cloné
     */
    private final Object3D cloned;
    
    public Clone3D(@NonNull final Object3D cloned)
    {
        this.cloned = Objects.requireNonNull(cloned, "cloned must not be null");
    }

    @NonNull
    public Color4f getColor()
    {
        return this.color;
    }

    public void setColor(@NonNull final Color4f color)
    {
        this.color = Objects.requireNonNull(color, "color must not be null");
    }

    @ThreadOpenGL
    @Override
    void drawSpecific()
    {
        this.color.apply();
        // Ici on utilise la maille de l'objet cloné
        this.cloned.mesh.drawMesh();
    }
}

Modifions notre exemple

MainBlueAndGreenCubes
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
package fr.developez.tutorial.java.dimension3;

import fr.developez.tutorial.java.dimension3.geometry.Box;
import fr.developez.tutorial.java.dimension3.render.Clone3D;
import fr.developez.tutorial.java.dimension3.render.Color4f;
import fr.developez.tutorial.java.dimension3.render.SceneGraph;
import fr.developez.tutorial.java.dimension3.render.Window3D;
import fr.developez.tutorial.java.dimension3.tool.NonNull;

public class MainBlueAndGreenCubes
{
    public static void main(String[] args)
    {
        final Window3D window3D = new Window3D(800, 600, "Tutoriel 3D - Une boite blue et une verte");
        MainBlueAndGreenCubes.drawScene(window3D.getSceneGraph());
    }

    private static void drawScene(@NonNull final SceneGraph sceneGraph)
    {
        final Box blueBox = new Box();
        blueBox.mesh.seal();
        final Clone3D greenBox = new Clone3D(blueBox);
        sceneGraph.root.addChild(blueBox);
        sceneGraph.root.addChild(greenBox);

        blueBox.x      = -1f;
        blueBox.z      = -2f;
        blueBox.angleX = 22.5f;
        blueBox.angleY = 22.5f;
        blueBox.setColor(Color4f.BLUE);

        greenBox.x      = 1f;
        greenBox.z      = -2f;
        greenBox.angleX = 22.5f;
        greenBox.angleY = 22.5f;
        greenBox.setColor(Color4f.GREEN);
    }
}

Bien sûr l'aspect graphique n'a pas changé.

VII. Conclusion

Nous avons vu ce qu’est le graphe de scène, un exemple d’implémentation d’un et comment optimiser nos objets pour qu’ils soit plus rapidement dessiné et qu’ils prennent la mémoire juste nécessaire.

Dans le prochain tutoriel nous allons pouvoir jouer avec notre 3D en ajoutant de l’interaction avec l’utilisateur avec clavier, souris et manette de jeux. Nous verrons aussi comment capturer les objets sous la souris. Interaction avec l'utilisateurIntéraction avec l'utilisateur

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2020 Gérard Bourriaud. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.