14/17

7. Perspective

Pour l’instant aucune perspective n’a été appliquée sur les données qui sont visualisées directement dans l’espace image. En pratique, cela revient à visualiser les données suivant une projection orthographique le long de la direction \(z\). \((x,y) \in [-1,1]\) sont utilisés en tant que coordonnées dans l’espace image, et \(z\in[-1,1]\) correspond à la profondeur.

Dans le monde réel, les objets éloignés nous apparaissent plus petits de par l’effet de perspective. La projection orthographique ne permet pas d’obtenir un effet visuel de profondeur, et pour cela, il est nécessaire d’appliquer un effet de perspective.

La perspective est classiquement modélisée en supposant le mode vu depuis une caméra ayant un centre optique confondu en un point, et un écran plan situé à une distance \(f\) du centre optique. Modéliser cet effet de caméra revient à considérer que l’ensemble de l’espace 3D visible est situé dans une pyramide tronquée à base rectangulaire appelée frustum.

OpenGL définissant l’espace visible comme étant le cube de dimension \([-1,1]^3\), la perspective est modélisée comme la transformation de la pyramide tronquée vers le cube normalisé.

  • Le cone tronqué (frustum) est défini par

    • L’angle de vue \(\theta\) (field of view fov).

    • La plus petite distance à partir de laquelle un objet peut être vu \(z_{near}\) (\(\simeq\) distance de l’écran au centre optique).

    • La distance la plus grande où un objet peut être vu (base de la pyramide) \(z_{far}\)

    • Le rapport largeur/hauteur de l’écran (aspect ratio \(a=width/height\)).

projection

La matrice suivante est couramment utilisée

\(\mathrm{P}= \left( \begin{array}{rrrr} f_x & 0 & 0 & 0 \\ 0 & f_y & 0 & 0 \\ 0 & 0 & C & D \\ 0 & 0 & -1 & 0 \\ \end{array} \right)\), with \(\left\{ \begin{array}{l} f_y = 1/\tan(\theta/2) \\ f_x = f_y/a \\ L = z_{near}-z_{far} \\ C = (z_{far}+z_{near})/L \\ D = 2\,z_{far}\,z_{near}/L \end{array} \right.\).

On pourra noter que d’appliquer cette matrice (en coordonnées homogènes) à un point \((x,y,z,1)\) permet:

  • De mapper le point \(0,0,-z_{near}\) de l’espace au point \((0,0,-1)\) dans l’espace normalisé OpenGL

  • De mapper le point \(0,0,-z_{far}\) de l’espace au point \((0,0,+1)\) dans l’espace normalisé OpenGL

Application

Ce code implémente un effet de perspective de la manière suivante.

  • Une matrice de perspective est construite dans le programme C++ et envoyé au shader

/** Create a perspective matrix */
std::array<float,16> perspective_matrix(float angle_of_view, float image_aspect, float z_near, float z_far)
{
    const float fy = 1/std::tan(angle_of_view/2);
    const float fx = fy/image_aspect;
    const float L = z_near-z_far;

    const float C = (z_far+z_near)/L;
    const float D = (2*z_far*z_near)/L;

    return {
        fx,0,0,0,
        0,fy,0,0,
        0,0,C,D,
        0,0,-1,0
    };

}

    // Perspective projection matrix
    const auto perspective = perspective_matrix( 45.0f*M_PI/180.0f, 1.0f, 0.01f, 500.0f);
    glUniformMatrix4fv(glGetUniformLocation(shader_program, "perspective"), 1, GL_TRUE, &perspective[0]);
  • La matrice de perspective est appliquée à chaque position dans le vertex shader

#version 330 core

layout (location = 0) in vec4 position;
layout (location = 1) in vec4 color;

out vec4 color_frag;

uniform mat4 perspective;

void main()
{
    color_frag = color;
    gl_Position = perspective * position;
}


projection

  • Modifiez les coordonnées \(z\) de la forme, et observez l’effet de perspective: Plus un objet est éloigné, plus il apparaitra petit. Notez que par défaut, la profondeur est orientée suivant les \(z\) négatifs.