Gérer les différents modes écran

 

Mode portrait et paysage (ou landscape)

Sur Android, les portables peuvent être tenus en mode portrait ou paysage.

Cependant, le rendu est des plus effrayant !

En portrait:

Image non trouvée !

 

Et en paysage:

Image non trouvée !

Bref pas le même rendu, et c'est normal, la résolution de l'écran n'est pas la même dans un sens ou dans l'autre. Donc les proportions ne sont pas gardées:

de [-1 à 1] sur les axes x et y mais des résolutions autres sur l'écran physique (Ex. 1280x720 puis 720x1080)

La solution consiste à calculer un ratio qui sera appliqué ensuite lors du rendu de l'image.

 

Matrice de projection, caméra et matrice view

Dans OpenGL, il existe une matrice nommée matrice de projection. Elle permet en fait de gérer ce que vous pouvez voir à travers une caméra. Mais cette caméra pourra être positionnée n'importe où dans l'espace et regarder ce qu'elle veut, c'est la matrice view.

Matrice de projection

  • Projection en perspective

    La matrice de projection sera initialisée lors de changement de dimension de la surface. Ceci afin de faire en sorte que les proportions de votre objet soit toujours les mêmes.

     

    Ce ratio sera donc calculé dans votre classe GLSurfaceView.Renderer dans la méthode onSurfaceChanged qui reçoit la hauteur et la largeur de l'écran pour initialiser une matrice de projection.

    @Override
    public void onSurfaceChanged(GL10 unused, int width, int height) {
    GLES20.glViewport(0, 0, width, height);

    Suivant l'orientation de l'appareil, calcul du ratio

    float ratio = (float) width > height ?
    (float) width / (float) height : // Landscape
    (float) height / (float) width; // Portrait

    Il reste à définir une matrice de projection qui sera appliquée ensuite à la vue caméra

    private final float[] mProjectionMatrix = new float[16];

    Image non trouvée !Il s'agit ici de Matrix provenant de android.opengl. Ne pas prendre la classe Matrix de android.graphics !

    import android.opengl.Matrix;

    Il reste à ajuster la matrice de projection selon vos besoins, en terme de plan:

    void android.opengl.Matrix.frustumM(float[] m, int offset, float left, float right, float bottom, float top, float near, float far)

    qui attend:

    m : Un tableau de 16 float qui recevra le résultat de la matrice de projection
    offset : offset de départ dans le tableau: à 0 ici !
    left : limite à gauche
    right : limite à droite
    bottom : limite en bas
    top : limite en haut
    near : limite proche en profondeur (> 0)
    far : limite au loin.

    if (width > height)

    {

    // Landscape, appliquer le ratio sur x
    Matrix.frustumM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 1, 7);

    }

    else

    {

    // Portrait, appliquer le ratio sur y

    Matrix.frustumM(mProjectionMatrix, 0, -1, 1, -ratio, ratio, 1, 7);

    }

    Image non trouvée !frustnum a été buggé (correction fin juillet 2012, cette méthode multipliait les x par 2, pas y, d'où des affichages pouvant être bizarres (http://code.google.com/p/android/issues/detail?id=35646) sur de vieux appareils.

    Image non trouvée !Vous pouvez aussi utiliser public static void perspectiveM (float[] m, int offset, float fovy, float aspect, float zNear, float zFar) à la place, mais cette méthode n'est disponible qu'à partir de l'API 14.

     

  • Projection orthogonale

    Une autre solution serait de passer par une matrice de projection orthogonale:

    static void orthoM(float[] m, int mOffset, float left, float right, float bottom, float top, float near, float far)

     

    Remplacez les frustumM par orthoM de la façon suivante:

    Matrix.orthoM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, -1, 1);

    et

    Matrix.orthoM(mProjectionMatrix, 0, -1, 1, -ratio, ratio, -1, 1);

    Image non trouvée !Vous pouvez aussi inverser l'axe z s'il ne vous convenez pas:Matrix.orthoM(mProjectionMatrix, 0, -ratio, ratio, -1, 1, 1, -1);

 

Cf. annexe pour connaitre la différence entre une projection orthogonale et perspective

 

Caméra et matrice view

Reste à positionner cette caméra dont je parlais précédemment:

void android.opengl.Matrix.setLookAtM(float[] rm, int rmOffset, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)

Avec:

Parameters:
rm returns the result
rmOffset index into rm where the result matrix starts
eyeX
eyeY
eyeZ
centerX
centerY
centerZ
upX
upY
upZ

Les trois premières valeurs sont les coordonnées du point de vue (où se trouve la caméra), les trois suivantes, l'endroit où on regarde avec la caméra. Enfin les trois dernières correspondent à la verticale de la caméra (où encore comment vous tenez votre caméra dans les mains: 0, 1, 0 correspondant à une caméra tenu normalement, mais rien ne vous empêche de tenir votre caméra couchée, la partie basse vers le haut, voire l'objectif vers vous, mais dans ce cas, vous n'allez pas voir grand chose !).

private final float[] mViewMatrix = new float[16];

Matrix.setLookAtM(mViewMatrix, 0, 0, 0, -1, 0f, 0f, 0f, 0f, 1.0f, 0.0f);

Qui permet d'obtenir une matrice View

 

Appliquer les tranformations des deux matrices sur les coordonnées des vertice

Reste à calculer la matrice résultante de mProjectionMatrix x mViewMatrix et à l'appliquer sur les coordonnées des vertice afin de positionner correctement les objets à l'affichage:

private final float[] mPVMatrix = new float[16];

Matrix.multiplyMM(mPVMatrix, 0, mProjectionMatrix, 0, mViewMatrix, 0);

Conservez la bien, car maintenant il faut appliquer ce résultat à tous vos vertice !

Cela se passe donc au niveau du vertex shader...

gl_position contenant la position d'un vertex en cours, il suffit de le multiplier avec la matrice uPVMatrix (de type uniform, car cette variable ne va pas beaucoup évoluer dans le temps en principe !):

private final String vertexShaderCode =
"uniform mat4 uPVMatrix;" +
"attribute vec4 aPosition;\n" +
"void main() {" +
" gl_Position = uPVMatrix * aPosition;" +
"} ";

De plus, lors de la création du renderer, dans la méthode nSurfaceCreated après génération du shader, recherche de la variable uPVMatrix...

 

Exemple

D'où les modifications dans la classe GLES20Renderer vu dans Dessiner des objets géométriques.

 

 

Dans la classe triangle (vu dans Dessiner des objets géométriques), il faudra passer cette matrice résultante de la multiplication des matrices Projection et View.

 

Annexe

Différence entre une projection orthogonale et perspective:

La projection orthogonale est une représentation d'une vue 3d dans un cube.

La projection perspective est une représentation d'une vue dans une pyramide dont le sommet est tronqué, c'est depuis ce sommet que vous regardez.