2 ou 3 questions sur la projection perspective

H

a marqué ce sujet comme résolu.

Salut à tous,

Je reviens vers vous à propos du cours proposé par ScratchAPixel : https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point/mathematics-computing-2d-coordinates-of-3d-points .

Quelques zones d’ombre subsistent encore malgré le fait que j’ai correctement implémenté la projection perspective qu’ils présentent.

Remarques générales

Aucune matrice de projection perspective et aucun frustrum de vue ne sont utilisés.

Résumé précis du cours

A. Ecrire une matrice qui définit l’objet 3D dans le monde :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    val world_cube_points : Seq[Seq[Double]] =  Seq(
      Seq(-150, 150, -150),
      Seq(-150, 150, 150),
      Seq(150, 150, -150),
      Seq(150, 150, 150),

      Seq(-150, -150, -150),
      Seq(-150, -150, 150),
      Seq(150, -150, -150),
      Seq(150, -150, 150)
    )

B. Définir une matrice de transformation world-to-camera et multiplier chaque point de l’objet par cette matrice, afin de changer l’origine de ce dernier en remplaçant l’origine du monde par l’origine de world-to-camera :

B.1. Matrice world-to-camera (une simple translation) : Il faut se souvenir d’une convention qui implique que l’axe Z de la caméra ait la même direction que l’axe Z négatif du monde. D’où le -1 dans la matrice (une coordonnée z positive dans le monde sera négative dans la caméra). Seuls les points (définis dans le système de coordonnées de la caméra) avec un z négatif seront visibles.

1
2
3
4
5
6
val translation_matrix_world_to_camera : Matrix = new Matrix(Seq(
  Seq(1, 0, 0, 0),
  Seq(0, 1, 0, 0),
  Seq(0, 0, -1, 0),
  Seq(1, 1, -400, 1)
))

B.2. Opération de produit et appels à ce produit (la matrice est lue ligne par ligne, et non colonne par colonne, et comme me l’a fait remarquer@JuDePom, je ne respecte pas du coup les conventions des math/physiciens, ce qui peut donc vous perturber un petit peu à la lecture - je ferai une nouvelle version c par c plus tard)

1
2
3
4
5
6
7
8
9
  def product(point : Seq[Double]) : Seq[Double] = (0 to 2).map(i => (0 to 3).map(i2 => {
    var coefficient : Double = 0
    if(i2 == point.length) {
      coefficient = 1
    } else {
      coefficient = point(i2)
    }
    content(i2)(i) * coefficient
  }).sum)

Et les appels qui vont avec :

1
2
3
world_cube_points.map(point => {
      translation_matrix_world_to_camera.product(point)
    })

C. On projette les points sur le plan image (c’est un plan, du coup de taille infinie) qui contient le canvas (plan qui est perpendiculaire à l’axe Z de la caméra et qui est situé 1 unité devant) ; cette projection se fait en utilisant la notion des triangles similaires : Image utilisateur. A partir du fait que BC=B’C’ (1 propriété des triangles similaires), on en déduit les coordonnées du point projeté : PointProjeté(point_camera.X/point_camera.Z ; point_camera.Y/point_camera.Z ; 1). En conséquence de la puce N°B.1. (seuls les points visibles sont ceux qui ont un z négatif dans la caméra), on constate l’émergence d’un problème : PointProjeté.X et PointProjeté.Y auront chacun un signe contraire à celui de PointCaméra, puisque PointCaméra.Z sera négatif : le point projeté irait donc à droite alors que dans la caméra (et dans le monde), il va à gauche… Dès lors, pour corriger ce problème, les coordonnées du point projeté deviennent : PointProjeté(point_camera.X/-point_camera.Z ; point_camera.Y/-point_camera.Z ; 1).

C.1. La fonction de projection

1
2
3
4
5
6
7
  def projectPointsOnImageView(points : Seq[Seq[Double]]) : Seq[Seq[Double]] = {
    points.map(point => {
      point.map(coordinate => {
        coordinate / -point(2)
      })
    })
  }

C.2. L’appel :

1
2
3
    val points_to_draw_on_canvas = projector.projectPointsOnImageView(world_cube_points.map(point => {
      translation_matrix_world_to_camera.product(point)
    }))

D. Normaliser pour rasterizer Rendus à cette étapes, on constate que les points ont été projetés de la caméra sur le plan image.

Il s’agit donc, dans cette dernière étape, de les dessiner sur le canvas pour les montrer à l’utilisateur (donc exprimer les coordonnées en pixels, i.e. : rasterizer).

D.1. Normalisation et Rasterization L’idée c’est encore d’effectuer des translations pour passer de système de coordonnées en système de coordonnées, bref.

1
2
3
        val normalized_drawn_point = Seq((point.head + CANVAS_WIDTH * 0.5) / CANVAS_WIDTH, (point(1) + CANVAS_HEIGHT * 0.5) / CANVAS_HEIGHT)  // Normalization

        graphics.fillRect((normalized_drawn_point.head * IMAGE_WIDTH).toInt, ((1 - normalized_drawn_point(1)) * IMAGE_HEIGHT).toInt, 5, 5)  // Rasterization : drawing the pixel (pixel's dimensions : 5x5, to make it visible)

L’appel :

new Canvas(points_to_draw_on_canvas).display


Mes questions

Bon maintenant que j’ai résumé les explications de ScratchAPixel (ABREV. : "SAP"), je vais pouvoir vous poser les questions que j’ai toujours en tête à l’issue de mes nombreuses relectures du cours :p

!!! ABREV de ScratchAPixel : SAP !!!

Q.1. L’objet 3D est-il réellement défini dans le repère monde ?

SAP indique que oui. Or, sur un autre cours, j’ai trouvé la notion de "repère modèle". Tout objet 3D se définit d’abord dans son repère modèle, PUIS si on veut établir des relations d’espace entre eux, on doit les regrouper dans un même repère, nommé le "repère monde".

Or SAP indique que typiquement, mon objet 3D :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    val world_cube_points : Seq[Seq[Double]] =  Seq(
      Seq(-150, 150, -150),
      Seq(-150, 150, 150),
      Seq(150, 150, -150),
      Seq(150, 150, 150),

      Seq(-150, -150, -150),
      Seq(-150, -150, 150),
      Seq(150, -150, -150),
      Seq(150, -150, 150)
    )

… est bien défini dans un repère monde et, en outre, SAP ne parle jamais de repère modèle.

Est-il sous-entendu que ce repère modèle est aussi le repère monde, car un seul objet est présent dans la scène ?

Est-il réellement important d’utiliser le repère caméra dans cette implémentation-là ?

Le seul argument donné par SAP est que l’utilisation des triangles similaires pour aboutir à la trouvaille des coordonnées du point projeté nécessite le repère de la caméra.

C’est selon moi un mauvais argument, car on aurait tout aussi bien pu, d’après moi j’insiste, utiliser l’origine du repère monde comme extrémité de la ligne de vue, puis appliquer tranquillement, sans souci, les triangles similaires, etc. En effet, le repère monde est un repère comme un autre, et notamment comme celui de la caméra…

Ainsi, en quoi le repère de la caméra est-il réellement nécessaire ? Je rappelle qu’il n’y a même pas de frustrum de vue, du coup il n’y a aucune restriction quant à la visibilité des points de la scène 3D projetés en 2D. Enfin si, il y a bien UNE restriction de visibilité : la taille du canvas mais rien à voir avec la caméra…

SAP indique que la direction de l’axe Z de la caméra est la même que celle de l’axe Z négatif du monde, pourtant c’est faux

SAP explique que la seule façon d’avoir un mapping entre l’axe X de la caméra et l’axe X du monde et l’axe Y de la caméra et l’axe Y du monde est d’avoir une direction Z de la caméra = à la direction Z du monde.

OR C’EST FAUX, puisque si on effectue une rotation sur l’axe X de la caméra pour aligner son axe Z avec celui négatif du monde, on fait pointer l’axe Y de la caméra dans la direction opposée à l’axe Y du monde. Et à peu près pareil pour une rotation sur l’axe X de la caméra.

L’axe Z de la caméra pointe donc bel et bien sur l’axe Z positif du monde. PAR CONTRE, SAP multiplie par -1 la coordonnée z de chaque point du monde en train d’être transformé pour être placé dans la caméra. Mais en soi, j’insiste : "L’axe Z de la caméra pointe donc bel et bien sur l’axe Z positif du monde".

Je me trompe ?

Salut,

Est-il sous-entendu que ce repère modèle est aussi le repère monde, car un seul objet est présent dans la scène ?

Oui. La notion de repère modèle va être vachement utile si tu as pleins d’objets complexes. Prends un jeu vidéo de course de voitures, c’est beaucoup plus simple si tu définis chaque type voiture dans un repère propre (qui ne dépend pas du temps) puis que tu utilises une matrice de passage (qui va dépendre du temps) pour les placer dans ton repère monde que si tu les définis à chaque instant directement dans le repère monde.

Ainsi, en quoi le repère de la caméra est-il réellement nécessaire ?

Ben ça rend l’étape de projection (étape C dans ton laïus) vachement plus simple.

J’ai absolument rien compris à ton baratin sur les axes des Z. Tout n’est question que de définition et de convention. Dans le cas général, il n’y a aucune raison pour que l’axe Z de la caméra soit aligné avec un axe particulier du repère monde. L’orientation de l’axe Z du repère caméra n’est qu’une convention, visiblement la plus couramment utilisée est d’orienter l’axe vers la personne qui regarde à travers la caméra mais en soit on s’en fout pas mal. je pense que toute cette histoire d’axe Z de la caméra orienté selon le -Z du monde ne venait que d’un exemple particulier, ça ne vaut pas la peine de se prendre la tête dessus.

Connectez-vous pour pouvoir poster un message.
Connexion

Pas encore membre ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte