Qu'est-ce qui ne va pas avec ma perspective ?!!

a marqué ce sujet comme résolu.

NB : je m’adresse avant-tout à des informaticiens qui sont habitués aux matrices et qui ont déjà bien étudié la projection en perspective avec les triangles similaires, bien que j’en ré-explique le fonctionnement.

Salut à tous !

J’essaie de mettre en place un petit programme qui affiche des points initialement dans une scène 3D dans une image 2D. Pour ce faire, j’utilise la projection perspective avec les triangles similaires.

Mon programme est entièrement écrit, mais les points apparaissent toujours au même endroit et je ne sais pas pourquoi.

Présentation du topic

  1. Dans un premier temps, je vais vous décrire dans les grandes lignes le fonctionnement de mon programme ;

  2. Ensuite, je vais vous donner ses entrées ;

  3. Puis sa sortie ;

  4. Enfin, les sources.

Dans les grandes lignes…

Je souhaite donc dessiner des points initialement présents dans une scène 3D dans une image 2D.

Mon programme prend comme entrée les coordonnées $(x;y;z)$ de chacun de ces points, exprimés dans le système de coordonnées de cette scène 3D ("du monde") (fréquemment appelé "world coordinates system").

Puis il exprime ces coordonnées dans le système de coordonnées de la caméra ("camera coordinates system"). Pour ce faire, je donne en entrée à mon programme la matrice "world to camera", puis je multiplie chacun des points par cette matrice. Le produit de cette matrice avec un point donné est : $P_{camera}.X = P_{world}.X * M_{00} + P_{world}.Y * M_{10} + P_{world} * M_{20} + M_{30}$ avec $M$ la matrice "world to camera". Les lignes de cette matrice correspondent respectivement (de haut en bas) à l’axe $X$, l’axe $Y$, l’axe $Z$ et à la translation du système de coordonnées de la caméra par rapport au système de coordonnées de la scène 3D ("du monde").

Ici, j’ai donc exprimé mes points dans le système de coordonnées de la caméra. Il me reste à projeter ces points du système de coordonnées de la caméra sur mon canvas ("mon image"). Pour ce faire, j’utilise la notion de triangles similaires : Image utilisateur, $P_{projeté}.X = P_{camera}.X / P_{camera}.Z$ (le canvas se situe 1 unité devant, sur l’axe $Z$ donc, la caméra). On notera que, comme par convention le système de coordonnées de la caméra possède un axe Z dont la direction est à l’opposé de la direction de l’axe Z du système de coordonnées du monde ("de la scène 3D"), la conversion "world to camera" a donné un point exprimé dans la caméra tel que son $P_{camera}.Z$ est l’opposé du $P_{world}.Z$. Et seuls les points dont le $P_{camera}.Z$ du système de coordonnées de la caméra est négatif seront visibles. Or si tel est le cas, avec ma formule actuelle, si $P_{camera}.X$ est positif ça signifie que $P_{projeté}.X$ sera négatif… Il en est de même pour $$P_{projeté}.Y$ avec $P_{camera}.Y$. La solution pour corriger ce problème d’effet miroir est donc de modifier ma formule comme suit : $P_{projeté}.X = P_{camera}.X / -P_{camera}.Z$ (ajout du "$-$"). Au final ma formule est donc : $P_{projeté}.X = P_{camera}.X / -P_{camera}.Z$ et $P_{projeté}.Y = P_{camera}.Y / -P_{camera}.Z$.

Maintenant, mon point se situe bien sur mon canvas. Mais par définition, il est exprimé par rapport au système de coordonnées du canvas, dont l’origine est le centre du canvas. Je vais donc l’exprimer dans le système de coordonnées normalisé puis enfin, exprimer le point résultant dans le système de coordonnées de l’image ("rasterization"). $P_{normalized}.X = (P_{canvas}.X + WidthCanvas/2) / WidthCanvas$ et idem pour le $P_{normalized}.Y$. Enfin : $P_{image}.X = P_{normalized}.X \times ImageWidth$ et PAS idem pour le $P_{image}.Y$ : la formule correcte pour cette coordonnée est : $P_{image}.Y = (1 - P_{normalized}.Y) \times ImageHeight$ car l’axe $Y$ du système de coordonnées normalisé va vers le haut alors que le système de coordonnées rasterisé $Y$ va vers le bas, d’où le : $1 -$.

Et donc là, j’ai bel et bien projeté mon point sur le canvas.

Entrées de mon programme

Mes points, exprimés par rapport au système de coordonnées du monde ("scène 3D")

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

Matrice de projection (= système de coordonnées de la caméra, exprimé par rapport au système de coordonnées du monde)

NB : le format de cette matrice est différent de celui de la matrice $M$ présentée au début de ce message.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    val matrix_world_to_camera : Matrix = new Matrix(Seq(

      /*
      [
        x-axis-x   y-axis-x   z-axis-x   translation-x
        x-axis-y   y-axis-y   z-axis-y   translation-y
        x-axis-z   y-axis-z   z-axis-z   translation-z
        0          0          0          1
      ]
      */

      Seq(2, 4, -5, 4),
      Seq(5, 3, 1, 3),
      Seq(2, 2, -3, 4),
      Seq(0, 0, 0, 1)
    ))

`

Sortie du programme

NB : remarquez, dans la fenêtre affichée, le petit point en haut à gauche. Il devrait y en avoir 8 (puisque j’affiche un cube).

Image utilisateur

Sources

Main.scala : définition du cube, de la matrice de la caméra, conversion des points 3D vers le système de la caméra, projection des points exprimés par rapport au système de la caméra sur le canvas, normalisation te rasterization

 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
object Main {
  def main(args : Array[String]) : Unit = {
    val projector = new Projector()

    val world_cube_points : Seq[Seq[Double]] = Seq(
      Seq(0, 1, 0),
      Seq(0, 1, 1),
      Seq(0, 0, 0),
      Seq(0, 0, 1),
      Seq(-1, 1, 0),
      Seq(-1, 1, 1),
      Seq(-1, 0, 0),
      Seq(-1, 0, 1)
    )

    val matrix_world_to_camera : Matrix = new Matrix(Seq(
      /*
      [
        x-axis-x   y-axis-x   z-axis-x   translation-x
        x-axis-y   y-axis-y   z-axis-y   translation-y
        x-axis-z   y-axis-z   z-axis-z   translation-z
        0          0          0          1
      ]
      */

      Seq(2, 4, -5, 4),
      Seq(5, 3, 1, 3),
      Seq(2, 2, -3, 4),
      Seq(0, 0, 0, 1)
    ))

    val points_to_draw_on_canvas = projector.drawPointsOnCanvas(world_cube_points.map(point => {
      matrix_world_to_camera.product(point)
    }))
    new Canvas(points_to_draw_on_canvas).display

  }
}

`

Matrix.scala : produit d’une matrice avec un point de coordonnées $(x;y;z)$

 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
/**
  * Matrix in the shape of :
  * c00 c01 c02 c03
  * c10 c11 c12 c13
  * c20 c21 c22 c23
  * c30 c31 c32 c33
  *
  * @param content the content of the matrix
  */
class Matrix(val content : Seq[Seq[Double]]) {

  /**
    * Computes the product between a point P(x ; y ; z) and this matrix M.
    *
    * @param point a point P(x ; y ; z)
    * @return a new point P'(
    *         c30 + x * c00 + y * c10 + z * c20
    *         ;
    *         c31 + x * c01 + y * c11 + z * c21
    *         ;
    *         c32 + x * c02 + y * c12 + z * c22
    *         )
    */
  def product(point : Seq[Double]) : Seq[Double] = {
    point.zipWithIndex.map(
      couple => content(couple._2).dropRight(1).zip(point).map(couple => couple._1 * couple._2).sum + content(couple._2).last
    )
  }

}

`

Projector.scala : utilisation des triangles similaires pour imprimer sur le canevas les points exprimés dans la caméra

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Projector {

  /**
    * Computes the coordinates of the projection of the point P, projected on the canvas.
    * The canvas is assumed to be 1 unit forward the camera.
    * The computation uses the definition of the similar triangles.
    *
    * @param points the point P we want to project on the canvas. Its coordinates must be expressed in the coordinates
    *          system of the camera.
    * @return the point P', projection of P. Its coordinates are expressed in the coordinates system of the camera.
    */
  def drawPointsOnCanvas(points : Seq[Seq[Double]]) : Seq[Seq[Double]] = {
    points.map(point => {
      point.map(coordinate => {
        coordinate / -point(2)
      }).dropRight(1)
    })

  }

}

Canvas.scala : normalisation et rasterization, dernière fonction appelée par Main.scala : à l’issue de cela, on obtient l’image finale avec, si tout marchait bien, mon cube…

 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
import java.awt.Graphics
import javax.swing.JFrame

/**
  * Assumed to be 1 unit forward the camera.
  * Contains the drawn points.
  */
class Canvas(val drawn_points : Seq[Seq[Double]]) extends JFrame {

  val CANVAS_WIDTH = 200
  val CANVAS_HEIGHT = 200
  val IMAGE_WIDTH = 10
  val IMAGE_HEIGHT = 10

  def display = {
    setTitle("Perlin")
    setSize(CANVAS_WIDTH, CANVAS_HEIGHT)
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)
    setVisible(true)
  }

  override def paint(graphics : Graphics): Unit = {
    super.paint(graphics)
    drawn_points.foreach(point => {

      if(!(Math.abs(point.head) <= CANVAS_WIDTH / 2 || Math.abs(point(1)) <= CANVAS_HEIGHT / 2)) {
        println("WARNING : the point (" + point.head + " ; " + point(1) + ") can't be drawn in this canvas.")
      } else {
        val normalized_drawn_point = Seq((point.head + (CANVAS_WIDTH / 2)) / CANVAS_WIDTH, (point(1) + (CANVAS_HEIGHT / 2)) / CANVAS_HEIGHT)
        graphics.drawRect(normalized_drawn_point.head.toInt * IMAGE_WIDTH, (1 - normalized_drawn_point(1).toInt) * IMAGE_HEIGHT, 1, 1)
      }
    })
  }

}

Question

Bon bein la question est assez simple : pourquoi ça marche pas ? :p

J’ai pourtant bien compris le fonctionnement d’une projection en perspective, les fondements géométriques quoi. Y a aucun souci là-dessus je pense… Visiblement je me suis peut-être trompé au niveau des valeurs de la matrice caméra ? Bref je suis un peu déconcerté . . .

+0 -0

Ta matrice de projection fait peur!

J’ai l’impression que tu as essayé de mettre ton viewport, ainsi que les matrices de translation et de rotation dans cette dernière. Je pense que c’est une mauvaise idée.

Commence dans un premier temps par juste faire une matrice viewport, tu n’auras pas de 3D, mais tu auras tes points. Ensuite tu ajoutes (enfin, multiplies) une matrice de projection (une matrice identité avec la case à la 4ème ligne 3ème colonne pour la projection).

Fait au fur et à mesure, ça sera vraiment plus simple.

Qu’entends-tu par viewport ?

En fait j’ai suivi ce cours : https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point/mathematics-computing-2d-coordinates-of-3d-points et comme tu le vois, la matrice qui convertit les points exprimés par rapport au système de coordonnées du monde vers des points exp. Par rap. Au syst. Caméra contient effectivement les coordonnées de rotation, de scaling, de l’origine du repère et de translation (c’est leur format)

  1. As-tu testé ton programme avec un cube plus gros (coté du cube = 10, au lieu de 1) ?

  2. A la fin, quand tu fais graphics.drawRect( ..), il y a un point n°1 qui joue un rôle particulier, c’est bizarre. On a bien 8 points à cette étape ?

elegance

Non pas encore. Et nan plus=p, le "point(1)" signifie "point[1]" rn Java par exemple.

Ton cours est difficilement compréhensible, tout me parait fouillis :/

Je ne vois pas l’intérêt de mélanger toutes les matrices comme ils le font. Ils font des calculs avec une surcharge cognitive non nécessaire. En utilisant un système de coordonnées homogènes, il n’y a pas à sortir chaque coordonnées, mais juste a multiplier des matrices (et un vecteur pour chaque point).

Je te donne le lien d’un cours rédiger par un collègue (qui m’a fait ce cours quand j’étais étudiant).

Perspective projection Il est possiblement disponible en français, mais comme ton lien d’origine était en anglais, je ne pense pas que ça te dérangera plus que ça :) Le chapitre précédent traite de la projection orthographique.

Bonsoir JuDePom,

Merci pour ton doc !

J’ai quelques questions concernant :

  1. Le shearing / cisaillement : apparemment c’est l’une des deux opérations linéaires élémentaires (l’autre étant le scaling / la mise à l’échelle). L’auteur n’explique pas pourquoi $x + y/3$ par exemple (tu peux faire un CTRL + F dans le doc de "Okay, let us continue." et tu tomberas sur cet exemple) engendre un cisaillement. Je pensais que ça ferait une translation en fait. Tu me diras qu’il n’a pas non plus expliqué pourquoi $3x/2$ engendre un scaling mais pour le coup c’est évident.

  2. L’auteur ne dit pas non plus pourquoi y a du $cos$ et du $sin$ pour la rotation, alors que précédemment il explique qu’une rotation c’est une composition de 3 shearings.

Y a moyen qu’on m’éclaire ? Surtout pour le point N°1, pas forcément le N°2 car ça me paraît pas évident à expliquer. Enfin même le point N°1 j’avoue que c’est du détail mais j’aime bien comprendre en profondeur les choses :p

  1. Le shearing / cisaillement : apparemment c’est l’une des deux opérations linéaires élémentaires (l’autre étant le scaling / la mise à l’échelle). L’auteur n’explique pas pourquoi $x + y/3$ par exemple (tu peux faire un CTRL + F dans le doc de "Okay, let us continue." et tu tomberas sur cet exemple) engendre un cisaillement.

Pour le cisaillement, il est assez simple à comprendre en fait. Tu prends un carré de taille 1. Tu poses un angle en (0,0), un en (1,0), (1,1) et (0,1). Maintenant, tu appliques la transformation comme donnée dans le cours. Le segment en y=0 va rester sur place, mais le segment ((0,1), (1,1)) vas devenir ((0 + 1/3, 1), (1 + 1/3, 1)), d’où l’effet de cisaillement.

  1. L’auteur ne dit pas non plus pourquoi y a du $cos$ et du $sin$ pour la rotation, alors que précédemment il explique qu’une rotation c’est une composition de 3 shearings.

C’est toujours une composition de shearings. Sauf que les $sin$ et $cos$ sont les coefficients des shearings.

$x' = x*cos(\alpha) - y*sin(\alpha)$ $y' = y*cos(\alpha) + x*sin(\alpha)$

Si $\alpha = 0$, alors $x' = x$ et $y' = y$, ce qui est normal, vu que l’on ne voulais pas tourner. Maintenant, imaginons tourner à 180 degrés alors $x'= -x$ et $y' = -y$. Tous les points font donc une rotation autour du point d’origine (0,0).

Ce n’est ni plus ni moins que le cercle trigonométrique.

Le segment en y=0 va rester sur place, mais le segment ((0,1), (1,1)) vas devenir ((0 + 1/3, 1), (1 + 1/3, 1)), d’où l’effet de cisaillement.

Oui, ça je l’avais bien sûr compris. Mais c’est une explication purement géométrique. Je me demande pourquoi, en termes de données / de lois "de l’Univers" ou quoi, ce mécanisme donne cette géométrie en particulier. Comme je l’ai dit ça reste cependant un détail qui n’a peut-être pas même de réponse, mais ça m’intrigue. C’est pour moi trop facile de dire "hophophop l’effet de cisaillement (et ce qu’on appelle "cisaillement" c’est cette géométrie illustrée par tel ou tel exemple voire graphe de l’exemple), c’est cette matrice de transformation.". Mais bon j’entends bien qu’il doit être extrêmement compliqué voire impossible de donner une raison… :(

C’est toujours une composition de shearings. Sauf que les sin et cos sont les coefficients des shearings.

Ah bein oui :honte:

Oui, ça je l’avais bien sûr compris. Mais c’est une explication purement géométrique. Je me demande pourquoi, en termes de données / de lois "de l’Univers" ou quoi, ce mécanisme donne cette géométrie en particulier. Comme je l’ai dit ça reste cependant un détail qui n’a peut-être pas même de réponse, mais ça m’intrigue. C’est pour moi trop facile de dire "hophophop l’effet de cisaillement (et ce qu’on appelle "cisaillement" c’est cette géométrie illustrée par tel ou tel exemple voire graphe de l’exemple), c’est cette matrice de transformation.". Mais bon j’entends bien qu’il doit être extrêmement compliqué voire impossible de donner une raison… :(

C’est très simple à formuler si tu sais ce qu’est un tenseur. Le déviateur du tenseur de déformation correspond au cisaillement (et sa partie isotrope correspond au changement de volume). C’est trivial à montrer en utilisant les définitions de ces objets. Si tu ne sais pas ce qu’est un tenseur, il faut écrire la transformation dans un repère orthonormé et si tu mélanges les coordonnés entre elles (ou que tu étires pas de la même façon dans toutes les directions) alors tu fais du cisaillement.

+0 -0

Ton cours est difficilement compréhensible, tout me parait fouillis :/

Je ne vois pas l’intérêt de mélanger toutes les matrices comme ils le font. Ils font des calculs avec une surcharge cognitive non nécessaire. En utilisant un système de coordonnées homogènes, il n’y a pas à sortir chaque coordonnées, mais juste a multiplier des matrices (et un vecteur pour chaque point).

Je te donne le lien d’un cours rédiger par un collègue (qui m’a fait ce cours quand j’étais étudiant).

Perspective projection Il est possiblement disponible en français, mais comme ton lien d’origine était en anglais, je ne pense pas que ça te dérangera plus que ça :) Le chapitre précédent traite de la projection orthographique.

JuDePom

Pourquoi ton cours n’indique pas qu’il faut que le produit ${matrice de projection} \times {point}$ normalise la coordonnée z (entre autres objectifs), alors que c’est le cas pour ce cours https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/building-basic-perspective-projection-matrix ? (D’ailleurs c’est marrant car sur StackOverflow j’ai trouvé de nombreux pros qui recommandent ScratchaPixels, du coup je trouve ça un peu dommage que tu me l’aies déconseillé)

Je ne te l’ai pas déconseillé, j’ai juste dis que ce qu’ils présentaient était un peu compliqué pour pas grand chose (le mélange de toutes les matrices, c’est chiant, faire un calcul pour chaque coordonnée du point alors qu’avec les coordonnées homogènes c’est juste matrice x vecteur, etc.).

De plus, dans le cours que je t’ai linké, il y a aussi un lien vers scratchapixel.

D’acc, pas de souci.

Bon du coup après re-re-re-relecture des 3 cours (https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point + https://github.com/ssloy/tinyrenderer/wiki/Lesson-4:-Perspective-projection + https://www.scratchapixel.com/lessons/3d-basic-rendering/perspective-and-orthographic-projection-matrix/projection-matrix-introduction ) j’ai compris pourquoi mon programme beugue depuis 4 semaines (outre deux .toInt que j’ai placés trop tôt par étourderie).

===> dans la leçon que je t’ai montrée en premier dans ce topic, i.e. https://www.scratchapixel.com/lessons/3d-basic-rendering/computing-pixel-coordinates-of-3d-point , la matrice - que je note A - qui est utilisée contient des coefficients rangés d’une telle manière que, si l’on effectue un certain produit de cette matrice avec un point, on bouge ce point dans le repère de la caméra. C’est ce qui t’avait perturbé.

Effectivement, tu m’as demandé si ce ne serait pas plus pratique d’utiliser une matrice de projection perspective - que je note B - pour les arguments que tu rappelles dans ton dernier post (i.e. "e mélange de toutes les matrices, c’est chiant, faire un calcul pour chaque coordonnée du point alors qu’avec les coordonnées homogènes c’est juste matrice x vecteur, etc.").

En réalité, ces deux matrices A et B sont totalement différentes. B, par définition, contient presque toutes les opérations nécessaires à la projection d’un point sur le canvas (dont la division via les triangles similaires). A, non.

Actuellement, je réalise l’opération des triangles similaires, entre autres (y a aussi la normalisation des coordonnées de projection sur le canvas), sur B (qui certes n’est pas finie : pour le moment j’omets sciemment les clipping planes et le fov) et non sur A.

Du coup. Je vais rester sur A dans un premier temps, et ainsi finir la version de mon programme, qui sera moins esthétique que si j’utilisais B et le peu d’opérations qui seraient alors nécessaires pour mettre en place la projection. Dans un second temps et une fois ce programme terminé, j’utiliserai B.

Je ne pense pas que ta projection soit si anormale que ça, ta caméra étant en (0,0,0), il est normal d’avoir un truc ressemblant à ça :

1
2
3
4
5
6
     o------------o
    /|        /   |
  /  |    /       |
 o---|-o          |
 |   o-|----------o
 o-----o

Comme tu vas de -width/2 à width/2, pareil avec height, ton espace normalisé va de -1 à 1, donc tes vertices ne sont pas de part et d’autre de la caméra, mais tout dans un seul quart.


Cependant, il y a des choses étranges, qui ne viennent pas de la projection.

Par exemple, ton cube a des valeurs en X et Y allant de 100 à 400, mais en Z, tu vas de -1 à -4 (ce qui fait que ton cube est très très écrasé !)

J’ai implémenté un truc minimal ce matin, il y a une matrice viewport, projection et translation. Le rendu est celui que tu demandes (plus ou moins, il aurait faut corriger un peu la translation, mais flemme).

Les vertices sont en coordonnées homogènes, d’ou la multiplication directe, chaque matrice est initialisé comme une matrice identité.

 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
#include <vector>

#include "tgaimage.h"
#include "structures.h"

std::vector<vertex> vertices;

const int render_width = 100;
const int render_height = 100;

matrice viewport    = matrice(4, 4).identity();
matrice projection  = matrice(4, 4).identity();
matrice translation = matrice(4, 4).identity();


int main(int argc, char** argv) {

  // Matrices
  viewport.set(0, 0, render_width/2);
  viewport.set(1, 1, render_height/2);
  viewport.set(0, 3, render_width/2);
  viewport.set(1, 3, render_height/2);

  projection.set(3, 2, -1/5.);

  translation.set(0, 3, -200);
  translation.set(1, 3, -200);
  translation.set(2, 3, -1000);

  // compute all transformations
  matrice transforms = viewport * projection * translation;


  // Vertices
  vertices.emplace_back(100, 300, -400); // top left
  vertices.emplace_back(100, 300, -100); // top left z+1
  vertices.emplace_back(100, 0  , -400); // bottom left
  vertices.emplace_back(100, 0  , -100); // bottom left z+1
  vertices.emplace_back(400, 300, -400); // top right
  vertices.emplace_back(400, 300, -100); // top right z+1
  vertices.emplace_back(400, 0  , -400); // bottom right
  vertices.emplace_back(400, 0  , -100); // bottom right z+1


  // Draw
  TGAImage image(render_width, render_height, TGAImage::RGB);
  TGAColor white(255, 255, 255, 255);
  for (auto& vert : vertices){
    // applies the transformations for each vertex
    vertex transformed_vertex = vert * transforms;

    image.set(transformed_vertex.x, transformed_vertex.y, white);
  }

  image.flip_vertically(); // i want to have the origin at the left bottom corner of the image
  image.write_tga_file("output.tga");
  return 0;
}

Bonne journée.


EDIT: typo + suppression d’imports inutiles c++

+0 -0
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