WebGL Essentials Partie I

WebGL est un moteur de rendu 3D intégré au navigateur basé sur OpenGL, qui vous permet d'afficher votre contenu 3D directement dans une page HTML5. Dans ce tutoriel, je couvrirai tous les éléments essentiels dont vous avez besoin pour commencer à utiliser ce framework..


introduction

Il y a quelques choses que vous devriez savoir avant de commencer. WebGL est une API JavaScript qui convertit le contenu 3D en un canevas HTML5. Pour cela, il utilise deux scripts connus dans le "monde 3D" sous le nom Shaders. Les deux shaders sont:

  • Le vertex shader
  • Le fragment shader

Maintenant, ne soyez pas trop nerveux quand vous entendez ces noms; c'est simplement une façon élégante de dire "calculateur de position" et "sélecteur de couleur" respectivement. Le fragment shader est le plus facile à comprendre; il indique simplement à WebGL quelle couleur doit être un point donné de votre modèle. Le vertex shader est un peu plus technique, mais il convertit les points de vos modèles 3D en coordonnées 2D. Tous les écrans d’ordinateur sont des surfaces 2D plates et, lorsque vous voyez des objets en 3D sur votre écran, ils ne sont qu’une illusion de perspective..

Si vous voulez savoir exactement comment fonctionne ce calcul, vous devez vous adresser à un mathématicien, car il utilise des multiplications avancées de matrices 4 x 4, qui dépassent un peu le didacticiel 'Essentiels'. Heureusement, vous n'avez pas besoin de savoir comment cela fonctionne, car WebGL se chargera de la plupart des tâches. Alors, commençons.


Étape 1: Configuration de WebGL

WebGL a beaucoup de petits paramètres que vous devez configurer presque chaque fois que vous dessinez quelque chose à l'écran. Afin de gagner du temps et de rendre votre code soigné, je vais créer un objet JavaScript qui contiendra toutes les informations "en coulisses" dans un fichier séparé. Pour commencer, créez un nouveau fichier appelé 'WebGL.js' et insérez-y le code suivant:

fonction WebGL (CID, FSID, VSID) var canvas = document.getElementById (CID); if (! canvas.getContext ("webgl") &&! canvas.getContext ("experimental-webgl")) alert ("votre navigateur ne supporte pas WebGL"); else this.GL = (canvas.getContext ("webgl"))? canvas.getContext ("webgl"): canvas.getContext ("experimental-webgl"); this.GL.clearColor (1,0, 1,0, 1,0, 1,0); // c'est la couleur this.GL.enable (this.GL.DEPTH_TEST); // Activer le test de profondeur this.GL.depthFunc (this.GL.LEQUAL); // Définir la vue en perspective this.AspectRatio = canvas.width / canvas.height; // Load Shaders Here

Cette fonction constructeur prend en compte les identifiants du canevas et des deux objets de shader. Tout d’abord, nous obtenons l’élément canvas et nous assurons qu’il prend en charge WebGL. Si tel est le cas, nous affectons le contexte WebGL à une variable locale appelée "GL". La couleur claire est simplement la couleur de fond et il est intéressant de noter que dans WebGL, la plupart des paramètres vont de 0,0 à 1,0, vous devez donc diviser vos valeurs rgb par 255. Ainsi, dans notre exemple, 1.0, 1.0, 1.0, 1.0 signifie un fond blanc avec 100% de visibilité (pas de transparence). Les deux lignes suivantes indiquent à WebGL de calculer la profondeur et la perspective afin qu'un objet plus proche de vous bloque les objets situés derrière lui. Enfin, nous définissons le format qui est calculé en divisant la largeur de la toile par sa hauteur..

Avant de continuer et de charger les deux shaders, écrivons-les. Je vais les écrire dans le fichier HTML où nous allons placer l'élément canvas. Créez un fichier HTML et placez les deux éléments de script suivants juste avant la balise de fermeture du corps:

 

Le vertex shader est créé en premier et nous définissons deux attributs:

  • la position du sommet, qui est l'emplacement des coordonnées x, y et z du sommet actuel (Point dans votre modèle)
  • la coordonnée de la texture; l'emplacement dans l'image de texture qui devrait être affecté à ce point

Ensuite, nous créons des variables pour les matrices de transformation et de perspective. Ceux-ci sont utilisés pour convertir le modèle 3D en une image 2D. La ligne suivante crée une variable partagée dans le fragment shader et dans la fonction principale, nous calculons la gl_Position (la position 2D finale). Nous assignons ensuite la «coordonnée de texture actuelle» à la variable partagée..

Dans le fragment shader, nous prenons simplement les coordonnées que nous avons définies dans le vertex shader et nous «échantillonnons» la texture à cette coordonnée. Fondamentalement, nous obtenons simplement la couleur dans la texture qui correspond au point actuel de notre géométrie..

Maintenant que nous avons écrit les shaders, nous pouvons recommencer à les charger dans notre fichier JS. Donc, remplacez le "// Load Shaders Here" avec le code suivant:

var FShader = document.getElementById (FSID); var VShader = document.getElementById (VSID); if (! FShader ||! VShader) alert ("Erreur, impossibilité de trouver des nuances"); else // Charger et compiler un fragment de shader var Code = LoadShader (FShader); FShader = this.GL.createShader (this.GL.FRAGMENT_SHADER); this.GL.shaderSource (FShader, Code); this.GL.compileShader (FShader); // Load and Compile Vertex Shader Code = LoadShader (VShader); VShader = this.GL.createShader (this.GL.VERTEX_SHADER); this.GL.shaderSource (VShader, Code); this.GL.compileShader (VShader); // Créer le programme shader this.ShaderProgram = this.GL.createProgram (); this.GL.attachShader (this.ShaderProgram, FShader); this.GL.attachShader (this.ShaderProgram, VShader); this.GL.linkProgram (this.ShaderProgram); this.GL.useProgram (this.ShaderProgram); // Lien attribut de position de sommet de shader this.VertexPosition = this.GL.getAttribLocation (this.ShaderProgram, "VertexPosition"); this.GL.enableVertexAttribArray (this.VertexPosition); // Lier l'attribut de coordonnées de texture du shader this.VertexTexture = this.GL.getAttribLocation (this.ShaderProgram, "TextureCoord"); this.GL.enableVertexAttribArray (this.VertexTexture); 

Vos textures doivent être de même taille en octets ou vous obtiendrez une erreur… comme 2x2, 4x4, 16x16, 32x32…

Nous nous assurons d’abord que les shaders existent, puis nous les chargeons un par un. Le processus récupère le code source du shader, le compile et le relie au programme shader central. Il existe une fonction, appelée LoadShader, qui récupère le code shader du fichier HTML; nous y arriverons dans une seconde. Nous utilisons le «programme shader» pour lier les deux shaders et nous donne accès à leurs variables. Nous stockons les deux attributs que nous avons définis dans les shaders; afin que nous puissions entrer notre géométrie en eux plus tard.

Examinons maintenant la fonction LoadShader. Vous devriez placer ceci en dehors de la fonction WebGL:

function LoadShader (Script) var Code = ""; var CurrentChild = Script.firstChild; while (CurrentChild) if (CurrentChild.nodeType == CurrentChild.TEXT_NODE) ​​Code + = CurrentChild.textContent; CurrentChild = CurrentChild.nextSibling;  Code de retour; 

En gros, il suffit de parcourir le shader et de collecter le code source.


Étape 2: Le cube "simple"

Pour dessiner des objets dans WebGL, vous aurez besoin des trois tableaux suivants:

  • sommets; les points qui composent vos objets
  • Triangles; indique à WebGL comment connecter les sommets aux surfaces
  • coordonnées de texture; définit comment les vertices sont mappés sur l'image de texture

Ceci est appelé cartographie UV. Pour notre exemple, créons un cube de base. Je vais diviser le cube en 4 sommets de chaque côté qui se connectent en deux triangles. faisons une variable qui contiendra les tableaux d'un cube.

var Cube = Sommets: [// Coordonnées X, Y, Z // Avant 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0, // Back 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, // Right 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0 , -1.0, 1.0, -1.0, -1.0, // Left -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, // Top 1,0, 1,0, 1,0, -1,0, -1,0, 1,0, 1,0, -1,0, -1,0, -1,0, -1,0, -1,0, // en bas 1,0, -1,0, 1,0, 1,0, -1,0, 1,0, 1,0, 1,0 , -1.0, -1.0, -1.0, -1.0, -1.0], Triangles: [// Également en groupes de trois pour définir les trois points de chaque triangle // Les nombres ici sont les numéros d'index dans le vertex array // Avant 0, 1, 2, 1, 2, 3, // arrière 4, 5, 6, 5, 6, 7, // droite 8, 9, 10, 9, 10, 11, // gauche 12, 13, 14, 13, 14, 15, // Top 16, 17, 18, 17, 18, 19, // Bottom 20, 21, 22, 21, 22, 23], Texture: [// Ce tableau est en groupes de deux, les coordonnées x et y (alias U, V) dans la texture // les nombres vont de 0,0 à 1,0, une paire pour chaque sommet // avant 1.0, 1.0, 1.0, 0.0 , 0.0, 1.0, 0.0, 0.0, // Back 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, // Right 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, // Gauche 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 0.0, //, Top 1.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, // Bottom 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1,0, 1,0];

Cela peut sembler être beaucoup de données pour un simple cube, cependant, dans la deuxième partie de ce didacticiel, je vais créer un script qui importera vos modèles 3D afin que vous n'ayez pas à vous soucier de les calculer..

Vous vous demandez peut-être aussi pourquoi j’ai fait 24 points (4 pour chaque côté), alors qu’il n’ya vraiment que huit points uniques au total sur un cube? J'ai fait cela parce que vous ne pouvez attribuer qu'une seule coordonnée de texture par sommet; donc, si nous ne mettions que les 8 points, le cube entier devrait alors se ressembler car il recouvrirait la texture autour de tous les côtés que le sommet toucherait. Mais de cette façon, chaque côté a ses propres points afin que nous puissions mettre une partie différente de la texture de chaque côté.

Nous avons maintenant cette variable de cube et sommes prêts à commencer à la dessiner. Revenons à la méthode WebGL et ajoutons un Dessiner une fonction.


Étape 3: La fonction Draw

La procédure pour dessiner des objets dans WebGL comporte de nombreuses étapes. alors c'est une bonne idée de créer une fonction pour simplifier le processus. L'idée de base est de charger les trois tableaux dans les tampons WebGL. Nous connectons ensuite ces tampons aux attributs définis dans les shaders, ainsi qu'aux matrices de transformation et de perspective. Ensuite, nous devons charger la texture dans la mémoire et, enfin, nous pouvons appeler le dessiner commander. Alors, commençons.

Le code suivant entre dans la fonction WebGL:

this.Draw = fonction (Object, Texture) var VertexBuffer = this.GL.createBuffer (); // Créer un nouveau tampon // Liez-le en tant que tampon actuel this.GL.bindBuffer (this.GL.ARRAY_BUFFER, VertexBuffer); // Remplissez-le avec les données this.GL.bufferData (this.GL.ARRAY_BUFFER, new Float32Array (Object.Vertices), this.GL.STATIC_DRAW); // Attache le tampon de connexion à l'attribut this.GL.vertexAttribPointer (this.VertexPosition, 3, this.GL.FLOAT, false, 0, 0); // Répéter pour les deux prochaines var TextureBuffer = this.GL.createBuffer (); this.GL.bindBuffer (this.GL.ARRAY_BUFFER, TextureBuffer); this.GL.bufferData (this.GL.ARRAY_BUFFER, nouveau Float32Array (Object.Texture), this.GL.STATIC_DRAW); this.GL.vertexAttribPointer (this.VertexTexture, 2, this.GL.FLOAT, false, 0, 0);
 var TriangleBuffer = this.GL.createBuffer (); this.GL.bindBuffer (this.GL.ELEMENT_ARRAY_BUFFER, TriangleBuffer); // Génère la matrice de perspective var PerspectiveMatrix = MakePerspective (45, this.AspectRatio, 1, 10000.0); var TransformMatrix = MakeTransform (Object); // Définit l'emplacement 0 comme texture active this.GL.activeTexture (this.GL.TEXTURE0); // Charge dans la texture à la mémoire this.GL.bindTexture (this.GL.TEXTURE_2D, Texture); // Mise à jour du sampler de texture dans le fragment shader pour utiliser l'emplacement 0 this.GL.uniform1i (this.GL.get.UniformLocation (this.ShaderProgram, "uSampler"), 0); // Définition des matrices de perspective et de transformation var pmatrix = this.GL.getUniformLocation (this.ShaderProgram, "PerspectiveMatrix"); this.GL.uniformMatrix4fv (pmatrix, false, new Float32Array (PerspectiveMatrix)); var tmatrix = this.GL.getUniformLocation (this.ShaderProgram, "TransformationMatrix"); this.GL.uniformMatrix4fv (tmatrix, false, new Float32Array (TransformMatrix)); // Dessine les triangles this.GL.drawElements (this.GL.TRIANGLES, Object.Trinagles.length, this.GL.UNSIGNED_SHORT, 0); ;

Le vertex shader positionne, fait pivoter et met à l'échelle votre objet en fonction des matrices de transformation et de perspective. Nous allons approfondir les transformations dans la deuxième partie de cette série.

J'ai ajouté deux fonctions: MakePerspective () et MakeTransform (). Celles-ci ne font que générer les matrices 4x4 nécessaires à WebGL. le MakePerspective () function accepte le champ de vision vertical, les proportions, et les points les plus proches et les plus éloignés comme arguments. Tout élément situé entre moins d'une unité et plus de 10000 unités ne sera pas affiché, mais vous pouvez éditer ces valeurs pour obtenir l'effet recherché. Voyons maintenant ces deux fonctions:

fonction MakePerspective (FOV, AspectRatio, le plus proche, le plus éloigné) var YLimit = Le plus proche * Math.tan (FOV * Math.PI / 360); var A = - (Farest + Closest) / (Farest - Closest); var B = -2 * Farest * Le plus proche / (Farest - Le plus proche); var C = (2 * le plus proche) / ((YLimit * AspectRatio) * 2); var D = (2 * le plus proche) / (YLimit * 2); retourne [C, 0, 0, 0, 0, D, 0, 0, 0, 0, 0, A, -1, 0, 0, B, 0];  fonction MakeTransform (Object) return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -6, 1]; 

Ces deux matrices affectent l'aspect final de vos objets, mais la matrice de perspective modifie votre «monde 3D», comme le champ de vision et les objets visibles, tandis que la matrice de transformation modifie les objets individuels, ainsi que leur échelle et leur position. Ceci fait, nous sommes presque prêts à dessiner. Il ne reste plus qu’une fonction permettant de convertir une image en texture WebGL..


Étape 4: Chargement des textures

Le chargement d'une texture est un processus en deux étapes. Nous devons d’abord charger une image comme vous le feriez dans une application JavaScript standard, puis la convertir en une texture WebGL. Commençons donc par la deuxième partie puisque nous sommes déjà dans le fichier JS. Ajoutez ce qui suit au bas de la fonction WebGL juste après la commande Draw:

this.LoadTexture = function (Img) // Crée une nouvelle texture et l'affecte comme variable active TempTex = this.GL.createTexture (); this.GL.bindTexture (this.GL.TEXTURE_2D, TempTex); // Flip Positive Y (Facultatif) this.GL.pixelStorei (this.GL.UNPACK_FLIP_Y_WEBGL, true); // Charge dans l'image this.GL.texImage2D (this.GL.TEXTURE_2D, 0, this.GL.RGBA, this.GL.RGBA, this.GL.UNSIGNED_BYTE, Img); // Configuration des propriétés de redimensionnement this.GL.texParameteri (this.GL.TEXTURE_2D, this.GL.TEXTURE_MAG_FILTER, this.GL.LINEAR); this.GL.texParameteri (this.GL.TEXTURE_2D, this.GL.TEXTURE_MIN_FILTER, this.GL.LINEAR_MIPMAP_NEAREST); this.GL.generateMipmap (this.GL.TEXTURE_2D); // Dissocie la texture et la renvoie. this.GL.bindTexture (this.GL.TEXTURE_2D, null); renvoyer TempTex; ;

Il est à noter que vos textures doivent avoir la même taille en octets, sinon vous recevrez une erreur. il doit donc s'agir de dimensions telles que 2x2, 4x4, 16x16, 32x32, etc. J'ai ajouté la ligne pour inverser les coordonnées Y simplement parce que les coordonnées Y de mon application 3D étaient en arrière, mais cela dépend de ce que vous utilisez. Cela est dû au fait que certains programmes font de 0 dans l’axe Y le coin supérieur gauche et que certaines applications en font le coin inférieur gauche. Les propriétés de mise à l'échelle que j'ai définies indiquent simplement à WebGL comment l'image doit être agrandie et réduite. Vous pouvez jouer avec différentes options pour obtenir différents effets, mais je pensais que cela fonctionnait mieux.

Maintenant que nous en avons terminé avec le fichier JS, revenons au fichier HTML et implémentons tout cela..


Étape 5: Envelopper

Comme je l'ai mentionné précédemment, WebGL rend à un élément de la toile. C'est tout ce dont nous avons besoin dans la section du corps. Après avoir ajouté l'élément canvas, votre page html devrait ressembler à ceci:

        Votre navigateur ne prend pas en charge le canevas HTML5.     

C'est une page assez simple. Dans la zone de tête, j'ai lié à notre fichier JS. Maintenant, implémentons notre fonction Ready, appelée lors du chargement de la page:

// Ceci contiendra notre variable WebGL var GL; // Notre texture finie var Texture; // Ceci contiendra les textures image var TextureImage; function Ready () GL = nouveau WebGL ("GLCanvas", "FragmentShader", "VertexShader"); TextureImage = new Image (); TextureImage.onload = function () Texture = GL.LoadTexture (TextureImage); GL.Draw (Cube, Texture); ; TextureImage.src = "Texture.png"; 

Nous créons donc un nouvel objet WebGL et transmettons les ID du canevas et des shaders. Ensuite, nous chargeons l'image de texture. Une fois chargé, nous appelons le Dessiner() méthode avec le cube et la texture. Si vous avez suivi, votre écran devrait avoir un cube statique avec une texture.

Maintenant, même si j'ai dit que nous couvririons les transformations la prochaine fois, je ne peux pas vous laisser avec un carré statique; ce n'est pas assez 3D. Revenons en arrière et ajoutons une petite rotation. Dans le fichier HTML, changez le en charge fonctionner pour ressembler à:

TextureImage.onload = function () Texture = GL.LoadTexture (TextureImage); setInterval (mise à jour, 33); ;

Cela appellera une fonction appelée Mettre à jour() toutes les 33 millisecondes, ce qui nous donne une cadence d'environ 30 images par seconde. Voici la fonction de mise à jour:

fonction Update () GL.GL.clear (16384 | 256); GL.Draw (GL.Cube, Texture); 

C'est une fonction assez simple. il efface l'écran, puis dessine le cube mis à jour. Passons maintenant au fichier JS pour ajouter le code de rotation.


Étape 6: Ajout d'un spin

Je ne vais pas pleinement implémenter les transformations, car je l'enregistre pour la prochaine fois, mais ajoutons une rotation autour de l'axe des ordonnées. La première chose à faire est d’ajouter une variable de rotation à notre objet Cube. Cela gardera une trace de l'angle actuel et nous permettra de continuer à incrémenter la rotation. Ainsi, le sommet de votre variable Cube devrait ressembler à ceci:

var Cube = Rotation: 0, // Les trois autres tableaux;

Maintenant mettons à jour le MakeTransform () fonction pour incorporer la rotation:

fonction MakeTransform (Object) var y = Object.Rotation * (Math.PI / 180.0); var A = Math.cos (y); var B = -1 * Math.sin (y); var C = Math.sin (y); var D = Math.cos (y); Object.Rotation + = .3; retourne [A, 0, B, 0, 0, 1, 0, 0, C, 0, D, 0, 0, 0, -6, 1]; 

Conclusion

Et c'est tout! Dans le prochain tutoriel, nous verrons comment charger des modèles et effectuer des transformations. J'espère que vous avez apprécié ce tutoriel; n'hésitez pas à laisser des questions ou des commentaires que vous pourriez avoir ci-dessous.