PDA

Voir la version complète : Développement 3D



thomzon
11/10/2012, 13h19
Hello,

Ce petit topic pour poser une question assez simple, histoire d'avoir quelques réponses avant d'en avoir besoin.
Je suis un programmeur plus ou moins aguerri, j'ai fait quelques bricoles dans mon coin dans moultes langages (Java, C++, Objective-C), et mon premier "vrai" projet devrait sortir sur l'AppStore sous peu. Seulement tout ça n'a jamais été qu'en 2D (cocos2d :wub:).
Je n'ai pas encore d'objectif précis, mais j'aimerais combler une lacune, et apprendre les rudiments de la programmation 3D. Evidemment ce qui m'intéresse à terme est de pouvoir tâter un peu des frameworks à la mode comme Unity ou l'UDK, mais j'imagine qu'il est préférable pour ça de comprendre les bases. Et même si ce n'est pas obligatoire, je préfère comprendre ce qui se trame sous le capot quand j'utilise un outil.

Donc je viens demander quelques conseils de littérature sur la programmation 3D, quelque chose pour les débutants total en 3D, mais qui savent programmer. Si ça parle d'OpenGL c'est bien, et si le langage utilisé pour en parler est un langage que je connais c'est encore mieux. J'aime bien apprendre par l'exemple, donc si ce n'est pas que de la théorie c'est bien aussi.
Je suis ouvert à toute suggestion: sites, livres papier, livres pas papier, etc.

Merci d'avance.

gros_bidule
11/10/2012, 13h35
Coin,
comme référence, il y a "OpengL Superbible" (5ième édition, mais je ne la connais qu'en anglais).

Si tu veux faire du OpenGL via un bind Java, les tutoriels présents sur le site de LWJGL sont sympas (bind utilisé dans pas mal de jeux Java) : http://www.lwjgl.org/wiki/index.php?title=Main_Page
C'est du Java, mais les méthodes OpenGL sont calquées sur leurs versions originales. Donc la Superbible reste largement utilisable.
Et c'est un peu plus bas niveau qu'un framework plus évolué tel que jMonkeyEngine, parfait si tu veux mettre les mains dans le cambouis.

Sinon il y a un "fameux" tutoriel pour OpenGL, un truc méga connu et reconnu. Faudrait que je le retrouve...

thomzon
11/10/2012, 13h52
Ca a l'air pas mal la Suberbible, mais en lisant le contenu des chapitres, j'ai l'impression que ça saute une partie qui m'intéresse: la base de la base, comment fonctionne un rendu 3D, l'explication de tous les termes que je vois partout et auxquels je comprends rien (shaders, meshes, ...). Je me trompe ?

Tomaka17
11/10/2012, 14h12
L'épineuse question de "où commencer en développement 3D"

Je peux déjà te dire par où il ne faut pas commencer : n'importe quel tuto ou livre OpenGL antérieur à 2009 (y compris le célèbre tuto "Nehe" dont parle certainement gros_bidule) ou n'importe quel tuto ou livre DirectX9 ou moins
La raison est simple : il y a une mini-révolution qui s'est passée entre OpenGL2 et OpenGL3 (et du côté de DirectX, entre le 8 et le 10, avec le 9 qui a le cul entre deux chaises) et ce qui est avant est désormais obsolète

Le problème, c'est que tous les tutos d'avant te montrent des fonctions obsolètes, alors que tous les tutos de maintenant t'expliquent par comparaison avec la méthode d'avant (ils te disent "maintenant vous ne pouvez plus utiliser X, car il faut faire Y à la place", mais en supposant que tu connaisses déjà X)
Et pour ceux qui débutent entre temps, c'est plus ou moins le vide intersidéral, et moi personnellement je n'ai pas encore trouvé de bonne ressource pour débutants et à jour (faut dire que je cherche pas activement)

Faut dire qu'avec la "nouvelle méthode", il est beaucoup plus difficile de débuter par rapport à l'ancienne car même afficher un pauvre carré à l'écran nécessite énormément d'explication dont tu pouvais te passer au début auparavant


Sinon pour l'UDK et Unity, tu n'as absolument pas besoin de savoir comment ça marche à l'intérieur
Si au moins c'était simple, mais c'est tout de même relativement complexe et long à apprendre

thomzon
11/10/2012, 14h19
Merci pour ta réponse, même si un peu tristounette.
Peut-être quelqu'un d'autre aura une bonne ressource sous la main.

Tomaka17
11/10/2012, 14h55
Après c'est pas très compliqué, je peux même te faire un cours en direct :

En gros ton but en tant que dev ça va être de demander à la carte graphique de dessiner des triangles
Tu lui donnes une liste de triangles, elle les dessine
Ca tombe bien puisque tous les objets qu'on dessine à l'écran dans les jeux vidéos sont composés de triangles (ça j'imagine que tu le savais déjà)

Pour lui indiquer les triangles à dessiner, il faut lui inscrire dans sa mémoire les coordonnées de chaque triangle, et ensuite tu déclenches le dessin en lui disant "la liste des trucs à dessiner est à l'endroit X de ta mémoire"
Par exemple tu lui écris 1.4,3.5,9.7,-1.4,2.2,0.4 en mémoire, et elle te dessine un triangle qui relie les points (1.4,3.5) (9.7,-1.4) et (2.2,0.4)
L'emplacement où tu écris ces infos s'appelle un vertex buffer

Les coordonnées que tu lui donnes sont en 2D sur l'écran
Le bas-gauche de l'écran ce sont les coordonnées (-1,-1) et le haut-droit de l'écran ce sont les coordonnées (1,1), le milieu étant à (0,0)
Donc si tu lui demandes par exemple de dessiner le triangle qui relie les points (-1,-1) (-1,1) et (1,1), ça va te remplir la moitié nord-ouest de l'écran

Mais tu peux aussi demander à la carte graphique d'effectuer un traitement sur chaque point de chaque triangle
Tu écris donc un petit programme dans un langage de programmation reconnu par la carte (le HLSL pour DirectX et le GLSL pour OpenGL), et tu l'envoies lui aussi dans sa mémoire
Ce programme s'appelle un vertex shader
Quand tu demandes à la carte de dessiner tes triangles, tu rajoutes "je voudrais que tu exécutes ce programme sur chaque point de chaque triangle" en pointant ton vertex shader, et s'il y a 10000 points elle va exécuter 10000 fois le programme en passant à chaque fois un point différent comme paramètre

Par exemple si dans ton vertex shader tu fais "coordonnée x += 0.5", ça va décaler tous tes triangles que tu dessines de 0.5 sur la droite

À partir de ce principe, au lieu de donner à la carte graphique directement les coordonnées 2D à l'écran de tes triangles, tu vas lui donner n'importe quelles coordonnées de ton choix (y compris 3D), et tu vas coder ton vertex shader de façon à ce qu'il calcule les coordonnées à l'écran à partir des coordonnées de ton choix. Autrement dit, ton vertex shader va prendre les coordonnées 3D de chaque point et va calculer les coordonnées 2D à l'écran correspondantes.
Ce calcul se fait généralement à partir de matrices : tu multiplies les coordonnées 3D par une matrice et tu obtiens les coordonnées à l'écran (mais ça ce sont des maths)

D'ailleurs tu n'es même pas obligé de donner des coordonnées de chaque point dans le vertex buffer, tu peux en fait fournir n'importe quelle donnée de ton choix pour chaque point
Il faut juste que ton vertex shader renvoie les coordonnées du point à l'écran grâce à ces données qu'il reçoit en paramètre

Après pour un peu de vocabulaire : dans un shader les données arbitraires propres à chaque point ça s'appelle des varying, et les variables globales (static const) dans ton shader s'appellent des uniform
Tu peux modifier la valeur des uniforms entre deux commandes de dessin


Une fois que la carte graphique sait où à l'écran sont les triangles que tu veux dessiner, elle détermine quels sont les pixels affectés
Et le même principe s'applique cette fois pour les pixels : tu vas demander à la carte graphique d'effectuer un traitement sur chaque pixel à l'intérieur de chacun de tes triangles
Pour cela tu utilises ce qu'on appelle pixel shader (chez DirectX) ou fragment shader (chez OpenGL) (mais c'est exactement la même chose, juste le nom qui change)
Même principe que le vertex shader : tu codes un petit programme et tu l'envoies dans la mémoire de la carte
Le pixel shader doit renvoyer la couleur du pixel que la carte graphique va écrire. Tu peux faire passer des informations entre le vertex shader et le pixel shader

Par exemple si tu veux un triangle tout rouge, tu codes ton pixel shader de façon à ce qu'il renvoie toujours la couleur rouge
Si tu veux une image sur ton triangle, tu codes ton pixel shader de façon à ce qu'il copie les couleurs d'une texture sur le triangle
Si tu veux faire du bump mapping, tu calcules si chaque pixel est bien éclairé ou pas, et tu multiplies la couleur normale du pixel par un nombre entre 0 et 1 (0 si pas éclairé, 1 si très bien éclairé)
Et ainsi de suite


Voilà, ce sont les bases de la rastérisation 3D
Après t'as d'autres trucs : les geometry shaders qui permettent de fabriquer tes propres triangles directement à l'intérieur d'un shader, le blending qui consiste à ne pas écraser l'image actuelle à l'écran quand tu veux dessiner quelque chose de non opaque, le stencil buffer qui sert notamment à faire les ombres portées et les miroirs, etc.

Voilà, c'était pas compliqué :rolleyes:

Louck
11/10/2012, 15h52
Je profite de ce topic pour poser une question toute conne : J'ai du mal à comprendre ce qu'est vraiment un "Shader" ?

J'ai déjà taté du OpenGL mais que pour la 2D.

Tomaka17
11/10/2012, 16h18
C'est ce que j'ai expliqué :rolleyes:

Un shader c'est un programme que tu écris toi-même dans un langage de programmation (souvent HLSL ou GLSL, ça peut aussi être de l'assembleur ou bien Cg), puis que tu donnes à la carte graphique et qui est exécuté par celle-ci

Un vertex shader est exécuté une fois pour chaque point de ton objet, et a généralement pour rôle de placer l'objet dans le monde (comme j'ai expliqué : chaque point contient des données arbitraires dans un format que tu as choisi, et toi tu calcules sa vraie position à l'écran dans le shader à partir de ces données) et de "préparer le terrain" pour le pixel shader

Un pixel shader quant à lui est exécuté une fois pour chaque pixel que tu écris à l'écran (ou dans une texture pour le render to texture), et doit calculer la couleur qui sera affichée. C'est dans le pixel shader qu'est calculé l'éclairage et la texture.

Par exemple, un vertex shader tout simple en HLSL : (c'est tiré de mon vrai code perso, pas un truc que j'invente)

uniform row_major float4x4 uMatrix;

void main(in float2 iPosition : POSITION0, out float4 vPosition : SV_Position) {
vPosition = mul(float4(iPosition, 0, 1), uMatrix);
}

La fonction "main" est exécuté une fois pour chaque point de chaque triangle. La variable "iPosition" contient les infos provenant du vertex buffer
Le corps de la fonction multiplie bêtement iPosition par uMatrix, et écrit le résultat dans vPosition. "vPosition" a la sémantique "SV_Position" qui indique à DirectX que cette variable contiendra la position finale du point à l'écran
La variable "uMatrix" c'est un uniform, c'est à dire une variable globale en quelques sortes. Avant de dessiner l'objet je définis la valeur de cette variable dans le code du jeu, ce qui me permet de contrôler la position de cet objet depuis le code du jeu

Et voici le pixel shader, exécuté une fois pour chaque pixel :

float4 uColor;

void main(out float4 fColor : SV_Target) {
fColor = uColor;
}

Il est tout con, il renvoie comme couleur celle que j'ai dans la variable globale "uColor"
Même principe que uMatrix, je contrôle la valeur de uColor depuis le code du jeu.
Si je dessine un triangle en utilisant ce pixel shader, il sera entièrement d'une seule couleur qui est celle stockée dans uColor

---------- Post added at 16h18 ---------- Previous post was at 16h14 ----------

Enfin t'as les geometry shaders
Un geometry shader est appelé une fois par triangle, et renvoie en sortie une liste d'un ou plusieurs triangles

Le geometry shader "de base" n'a aucun effet : il renvoie bêtement ce qu'il reçoit en entrée
Mais un geometry shader permet par exemple d'affiner une sphère en ajoutant des triangles quand on zoome dessus

Les programmeurs attendaient beaucoup des geometry shaders, on pensait que ça permettrait de faire plein de trucs : générer des terrains procéduraux, affiner les détails, etc.
Mais en réalité il y a une limite assez faible du nombre de triangles maximum qu'on peut renvoyer en sortie, à cause du hardware qui n'est pas trop fait pour ça
Du coup ce fut un peu la grosse déception

Chez DirectX11 et OpenGL 3.3 on a aussi deux nouveaux types de shaders qui prennent en entrée une liste arbitraire de points et renvoient des triangles
Paraît que ça a un rôle différent du geometry shader, mais je ne connais pas bien ça, c'est encore trop récent

Voilà, en espérant que je plombe pas le topic

Louck
11/10/2012, 16h28
Du coup autre question con : Quel est l’intérêt du shader ?

De ce que tu dis, ca permet de rendre les manipulations graphiques (sur des triangles) flexibles, mais est ce vraiment performant/optimisé ? (ou est ce plus pratique de faire du gltranslate en opengl que d'utiliser un vertex shader, pour déplacer les formes géométriques ?).

Tomaka17
11/10/2012, 16h41
Dans mon premier post je parlais d'une révolution entre OpenGL 2 et 3

En fait dans OpenGL 2 tu utilises glTranslate, glScale, glRotate, gluProject et tout le bordel
Dans OpenGL 3 tout a disparu et remplacé par les vertex shaders (qui eux n'existaient pas dans la version 2, ou alors avec des extensions)

Dans DirectX 9 (qui a le cul entre deux chaises entre ces deux méthodes) tu as le choix : soit tu mets un vertex shader et celui-ci sera exécuté, soit tu n'en mets pas et ce sont les équivalents DirectX de glTranslate&co qui sont pris en compte (utiliser ces fonctions glTranslate&co c'est ce qu'on appelle le fixed pipeline)

Quant aux perfs, comme tu codes de manière précise pour ce que tu veux faire, ça élimine toutes les fioritures
Par exemple dans OpenGL2 tu as des fonctions qui te permettent d'inverser les couleurs de sortie, tous ces trucs là ça dégage et forcément ça va un peu plus vite


En fait les shaders ont pour le moment surtout un intérêt pour le pixel shader
Toutes les évolutions techniques depuis Doom 3 inclus (c'est à dire le bump mapping, le blur, le fait que les couleurs vives débordent (j'ai oublié le nom), les gouttes d'eau sur la caméra dans le FPS, etc.) sont dûes aux pixels shaders

Or à partir du moment où tu as un pixel shader, tu es obligé d'avoir un vertex shader
Je n'ai pas expliqué ça, mais en fait dans ton vertex shader tu peux sortir des données supplémentaires pour chaque point, qui sont alors interpolées et transmises au pixel shader

Par exemple dans ton vertex shader tu peux dire "le premier point a la valeur 1, le deuxième point a la valeur 2 et le troisième point a la valeur 3"
Ensuite dans ton pixel shader tu récupères cette valeur mais interpolée
C'est à dire qu'un pixel juste à côté du premier point aura une valeur proche de 1, un pixel à mi-chemin entre les points 1 et 2 aura une valeur à peu près égale à 1.5, et ainsi de suite
Et avoir ça c'est indispensable pour faire 95 % des trucs intéressants avec un pixel shader

Louck
11/10/2012, 17h00
D'accord je comprend mieux. Merci.

Bon du coup je reste avec le vieux OpenGL 2 pour faire de la 2D. A voir si j'ai du temps pour me mettre aux shaders après, dans mes projets :).

thomzon
11/10/2012, 20h24
Merci Tomaka pour tes explications, elles semblent claires je vais me pencher dessus. J'ai juste une question: est-ce que dans OpenGL ES aussi les les fonctions ont été remplacées par les shaders ?

Tomaka17
11/10/2012, 20h46
J'ai jamais utilisé OpenGL ES, mais de ce que j'ai vu c'est grosso modo la même chose que OpenGL 3 (c'est à dire avec des shaders)

Sinon j'ai cherché vite fait, et ce site a l'air sympatoche (http://www.opengl-tutorial.org/) si on enlève les fonds horribles en dessous du code

rOut
12/10/2012, 01h55
En fait pour faire plus précis, le rendu graphique se fait selon un pipeline de traitements qui fonctionne grosso modo comme sur cette image:

http://duriansoftware.com/joe/media/gl1-pipeline-01.png

En entrée du pipeline, il y a des attributs de points (vertices), par exemple leur coordonnées mais aussi leur couleur, et un état global (uniforms).

Une première étape consiste à transformer les attributs des vertices depuis les attributs fournis et un certain nombre d'autres paramètres (quelle projection utiliser, quels sont les points visibles, etc).
C'est le vertex shader qui se charge de ça. C'est un "programme" exécuté sur chacun des vertex fournis en entrée.
En sortie du vertex shader, les vertex sont groupés en primitives. Il y a différent types de primitives qui déterminent de quelle manière les vertices sont groupés :
- Triangle : les vertices sont groupés trois par trois.
- Line : les vertices sont groupés deux par deux.
- Point : les vertices sont groupés un par un.
- D'autres plus "complexes" par rapport à la manière de grouper les vertices.
Chaque vertex en sortie est représenté par ses attributs (coordonnées, couleur, etc...).

Sur chacune des primitives générées, s’exécute ensuite le tesselation shader et/ou le geometry shader.
Ils prennent en entrée une primitive, et fournissent en sortie n primitives. Ce qui permet par exemple de faire de la subdivision des triangles pour fournir plus de détails, mais ça a d'autres applications.

Ici s'opère un filtrage des primitives qui vont être visibles ou non, par une opération de "clipping". Les primitives qui seront en dehors de l'écran sont supprimées.

L'étape suivante est la rasterisation. Pour chaque primitive générée, La carte va interpoler les attributs de chaque vertice (les "varyings") et générer un fragment pour chaque pixel correspondant à un point sur la surface de la primitive projetée sur l'écran. Les fragments correspondent à un pixel, mais il peut très bien y avoir plusieurs fragments superposés si les primitives se superposent.
Chaque fragment possède les mêmes attributs que les vertices d'origine, mais leur valeur est interpolée entre les attribus des vertices formant la primitive.

Ensuite vient le fragment shader qui va s'éxecuter sur chaque fragment généré.
En entrée il prend donc les attributs d'un fragment, et en sortie va calculer des informations supplémentaires sur le fragment, comme sa couleur ou sa profondeur. Par exemple en récupérerant un élément de texture (texel) via son attribut "coordonnée de texture" et l'état global déterminant la texture chargée (uniform sampler).

Enfin pour terminer, les fragments font l'objet de tests divers : test de profondeur pour déterminer les fragments visibles, tests de blending pour mélanger les fragments translucides etc... et enfin projetés sous forme de pixel dans le framebuffer. Le framebuffer représente la sortie du pipeline, qui peut être affichée directement à l'écran ou dans un tampon mémoire que l'on va lire ensuite pour créer une image.

La différence entre OpenGL3 et OpenGL2.1 est qu'auparavant toutes les opérations de shaders étaient fixes (plus ou moins) et calculaient toujours les mêmes choses. Du coup il n'était pas possible d'ajouter de nouveaux attributs aux vertices ou de jouer avec les fragments après la rasterisation.
OpenGL3 et les versions suivantes ont introduit de plus en plus de souplesse en permettant aux développeurs d'écrire eux même les différents shaders et de les charger dans la carte graphique avant de demander le rendu d'une scène.

Tomaka17
12/10/2012, 09h33
À noter que si jamais tu essayes DirectX, il y a un petit piège à éviter : tous les triangles doivent être dans le sens des aiguilles d'une montre à l'écran
Par exemple le triangle (-1,-1) (-1,1) (1,1) est dans le sens des aiguilles d'une montre
Par contre (-1,-1) (1,1) (-1,1) qui est le même triangle mais dans un ordre différent, est dans le sens inverse et ne sera pas affiché

En fait c'est ce qu'on appelle le vertex culling
La carte graphique exécute tous les vertex shaders, et ensuite si le vertex culling est activé elle va supprimer tous les triangles qui sont dans le sens inverse des aiguilles d'une montre, ce qui augmente considérablement la vitesse de rendu
Le but étant de ne pas dessiner la face arrière des objets, c'est dur à faire comprendre sans illustration, mais si tu as un peu d'imagination tu remarques que si un triangle dans le sens anti-horaire est à l'arrière d'un objet et que tu tournes cet objet, il va passer dans le sens horaire au moment où il passe à l'avant

Dans OpenGL tout cela est désactivé par défaut et tu peux dessiner les triangles que tu veux, alors que dans DirectX c'est activé par défaut (et en plus c'est chiant à désactiver)

Froyok
12/10/2012, 10h00
Moi c'est ma bible : http://www.amazon.fr/Real-Time-Rendering-Tomas-Akenine-Moller/dp/1568814240/ref=sr_1_1?ie=UTF8&qid=1350028800&sr=8-1
Je pense pas qu'on puisse faire mieux pour comprendre le rendu temps-réel. :)
(Même si certains exemple sont issue de DirectX, c'est tellement ba niveau que c'est très similaire pour OpenGl. Il vaut largement son prix, un p'tit bijoux)

thomzon
12/10/2012, 14h43
Merci encore pour toutes vos explications.
Froyok ta bible m'a l'air bien, un peu cher mais c'est bientôt mon anniv' et j'ai pas d'idée.