W3docs

API WebGL JavaScript

Apprenez l'API WebGL JavaScript : configurer un contexte de rendu, écrire et compiler des shaders, envoyer des tampons de sommets, dessiner un triangle et utiliser des uniforms pour des graphiques 3D accélérés par GPU dans le navigateur.

Introduction à WebGL

WebGL (Web Graphics Library, plus précisément la version 1.0) exploite la puissance d'OpenGL ES 2.0 dans les environnements web, permettant aux développeurs de rendre des graphiques 3D détaillés dans tout navigateur web compatible sans avoir besoin de plugins. Tous les exemples de ce chapitre utilisent l'API WebGL 1.0. Cette capacité est essentielle pour créer des jeux immersifs, des applications 3D interactives et des visualisations complexes directement dans le navigateur. Pour les projets modernes, envisagez WebGL 2.0, qui s'appuie sur OpenGL ES 3.0 et offre de meilleures performances et fonctionnalités.

Ce chapitre couvre ce dont vous avez besoin pour commencer à dessiner avec WebGL : demander un contexte de rendu, écrire et compiler des shaders, téléverser des données de sommets dans des tampons, et émettre des appels de dessin. À la fin, vous comprendrez l'intégralité du pipeline derrière un triangle rendu et saurez où aller ensuite pour l'éclairage, la texturation et l'animation.

WebGL vs. le Canvas 2D

WebGL effectue le rendu via le même élément <canvas> utilisé par l'API Canvas 2D, mais les deux sont très différents. Le contexte 2D (getContext('2d')) vous donne une surface de dessin de haut niveau — fillRect, arc, drawImage. WebGL vous donne un pipeline de bas niveau accéléré par GPU : vous décrivez la géométrie sous forme de tableaux de nombres et écrivez de petits programmes (shaders) qui s'exécutent sur le GPU pour décider où chaque sommet se place et quelle couleur reçoit chaque pixel. Cet effort supplémentaire offre l'accélération matérielle, une vraie 3D, et le débit nécessaire pour des milliers d'objets par image.

Vous demandez les deux contextes de la même façon, il est donc conseillé d'effectuer une détection de fonctionnalité :

const canvas = document.querySelector('#webglCanvas');
// 'webgl2' is preferred where available; fall back to 'webgl' (1.0).
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl');

if (!gl) {
  console.error('WebGL is not supported by this browser.');
}

Configuration de votre premier contexte WebGL

Pour commencer avec WebGL, il est essentiel de configurer un contexte de rendu attaché à un élément canvas dans votre HTML. Pour les projets modernes, vous pouvez également demander un contexte WebGL 2 en utilisant canvas.getContext('webgl2') pour de meilleures performances et fonctionnalités :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <title>Simple WebGL Example</title>
    <style>
        canvas {
            width: 400px;
            height: 400px;
            border: 1px solid black; /* Adds a border around the canvas */
        }
    </style>
</head>
<body>
    <canvas id="webglCanvas"></canvas>
    <script>
        // This script will run once the DOM content is fully loaded.
        document.addEventListener("DOMContentLoaded", function() {
            // Get the canvas element.
            const canvas = document.getElementById('webglCanvas');
            // Initialize the WebGL 1.0 context.
            const gl = canvas.getContext('webgl');

            // Check if WebGL is available.
            if (!gl) {
                console.error('WebGL is not supported by your browser.');
                return;
            }

            // Set the clear color to blue with full opacity.
            gl.clearColor(0.0, 0.0, 1.0, 1.0); // RGBA: Blue color

            // Clear the color buffer with the specified clear color.
            gl.clear(gl.COLOR_BUFFER_BIT);
        });
    </script>
</body>
</html>

Analyse du code

  1. Configuration HTML : La partie HTML configure un élément canvas où WebGL affichera son résultat. Une bordure est ajoutée pour identifier visuellement la zone du canvas sur la page web.
  2. Style CSS : Un style simple est appliqué pour s'assurer que le canvas a une taille spécifique et une bordure pour la visibilité.
  3. JavaScript pour WebGL :
    • Écouteur d'événement : Le code JavaScript est encapsulé dans un écouteur d'événement qui attend que le contenu DOM soit entièrement chargé avant de s'exécuter.
    • Initialisation du contexte WebGL : Il obtient le contexte WebGL 1.0 depuis le canvas. Si WebGL n'est pas pris en charge, le contexte sera null.
    • Vérification de la disponibilité de WebGL : Si le contexte est null, une erreur est consignée dans la console indiquant l'absence de prise en charge.
    • Définition de la couleur d'effacement : gl.clearColor(0.0, 0.0, 1.0, 1.0) définit la couleur (bleu, entièrement opaque) qui remplira le canvas lorsque le tampon de couleur sera effacé. Notez que cela stocke uniquement la couleur — rien n'est encore dessiné.
    • Effacement du tampon de couleur : gl.clear(gl.COLOR_BUFFER_BIT) peint effectivement le canvas avec la couleur d'effacement précédemment définie, produisant un carré bleu uni.

Cet exemple est fondamental mais constitue un bon point de départ pour comprendre le fonctionnement des configurations WebGL. Vous pouvez l'améliorer en ajoutant davantage de fonctionnalités WebGL telles que des shaders, des tampons et des commandes de dessin pour créer des sorties graphiques.

Rendu d'un triangle simple

L'une des premières étapes de l'apprentissage de WebGL est de rendre des formes simples. WebGL utilise un système de coordonnées normalisées (NDC) : la zone visible va de -1 à 1 sur les axes X et Y, avec (0, 0) au centre du canvas. Chaque forme que vous dessinez doit être décrite dans ces coordonnées (ou transformée en celles-ci par un shader).

Dessiner même un seul triangle nécessite le pipeline WebGL complet :

  1. Écrire un vertex shader qui positionne chaque coin.
  2. Écrire un fragment shader qui colore chaque pixel.
  3. Les compiler et les lier dans un programme shader.
  4. Téléverser les coordonnées des coins dans un tampon.
  5. Connecter le tampon à l'attribut du shader et émettre un appel de dessin.

L'exemple ci-dessous parcourt ces cinq étapes :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>WebGL Triangle Example</title>
    <style>
        canvas {
            width: 400px;
            height: 400px;
            border: 1px solid black;
        }
    </style>
</head>
<body>
    <canvas id="webglCanvas"></canvas>
    <script>
        // Function to create a shader, upload GLSL source code, and compile the shader
        function loadShader(gl, type, source) {
            const shader = gl.createShader(type);
            gl.shaderSource(shader, source);
            gl.compileShader(shader);

            if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
                console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
                gl.deleteShader(shader);
                return null;
            }

            return shader;
        }

        // Function to initialize the shader program
        function initShaderProgram(gl, vsSource, fsSource) {
            const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
            const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
            const shaderProgram = gl.createProgram();
            gl.attachShader(shaderProgram, vertexShader);
            gl.attachShader(shaderProgram, fragmentShader);
            gl.linkProgram(shaderProgram);

            if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
                console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
                return null;
            }

            return shaderProgram;
        }

        // Function to initialize WebGL
        function initWebGL() {
            const canvas = document.getElementById('webglCanvas');
            // Note: Use 'webgl2' for modern projects
            const gl = canvas.getContext('webgl');

            if (!gl) {
                console.error('WebGL is not supported by your browser.');
                return;
            }

            // Set internal canvas resolution to match CSS dimensions
            canvas.width = 400;
            canvas.height = 400;

            // Vertex shader program
            const vsSource = `
                attribute vec4 aVertexPosition;
                void main(void) {
                    gl_Position = aVertexPosition;
                }
            `;

            // Fragment shader program
            const fsSource = `
                void main(void) {
                    gl_FragColor = vec4(1.0, 0.5, 0.0, 1.0); // Orange color
                }
            `;

            const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
            const programInfo = {
                program: shaderProgram,
                attribLocations: {
                    vertexPosition: gl.getAttribLocation(shaderProgram, 'aVertexPosition')
                }
            };

            // Validate attribute location to prevent silent shader failures
            if (programInfo.attribLocations.vertexPosition === -1) {
                console.error('Failed to get the location of aVertexPosition');
                return;
            }

            // Create a buffer for the triangle's positions.
            const positionBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

            // Set the positions for the triangle.
            const positions = [
                0.0,  1.0,  // Vertex 1
                -1.0, -1.0, // Vertex 2
                1.0, -1.0  // Vertex 3
            ];
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);

            // Draw the scene
            function drawScene() {
                // Note: High-DPI scaling is omitted for simplicity.
                gl.viewport(0, 0, canvas.width, canvas.height); 
                gl.clearColor(0.0, 0.0, 0.0, 1.0);  // Clear to black, fully opaque
                gl.clear(gl.COLOR_BUFFER_BIT);

                // Tell WebGL to use our program when drawing
                gl.useProgram(programInfo.program);

                // Attach the position buffer.
                gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
                gl.vertexAttribPointer(
                    programInfo.attribLocations.vertexPosition,
                    2,   // Number of components per vertex attribute
                    gl.FLOAT, false, 0, 0);
                gl.enableVertexAttribArray(
                    programInfo.attribLocations.vertexPosition);

                // Execute WebGL program
                gl.drawArrays(gl.TRIANGLES, 0, 3);

                requestAnimationFrame(drawScene);
            }

            drawScene();
        }

        // Call the initWebGL function after the document has loaded to ensure the canvas is ready.
        document.addEventListener("DOMContentLoaded", initWebGL);
    </script>
</body>
</html>

Explication du code

  • Vertex Shader (vsSource) : Lit l'attribut aVertexPosition et l'assigne au gl_Position intégré, qui détermine où chaque sommet se place à l'écran.
  • Fragment Shader (fsSource) : Définit gl_FragColor pour que chaque pixel à l'intérieur du triangle soit rendu en orange (vec4(1.0, 0.5, 0.0, 1.0) est du RGBA orange).
  • Compilation des shaders (loadShader) : Compile un shader unique à partir du code source GLSL et signale les erreurs de compilation via getShaderInfoLog.
  • Initialisation du programme shader (initShaderProgram) : Lie les shaders de sommets et de fragments compilés en un programme exécutable unique qui s'exécute sur le GPU.
  • Boucle d'animation : drawScene() émet l'appel de dessin, puis requestAnimationFrame(drawScene) planifie la prochaine image, maintenant le rendu synchronisé avec le taux de rafraîchissement de l'écran. Pour une couverture plus approfondie de la planification des images, consultez JavaScript animations.

Pourquoi un appel de dessin nécessite autant de configuration

Si vous venez de l'API Canvas 2D, tout cet échafaudage peut sembler lourd pour un seul triangle. La raison est que WebGL est stateful et explicite : rien n'est dessiné tant que vous n'avez pas (1) un programme lié, (2) des données dans un tampon, (3) ce tampon connecté à un attribut de shader avec vertexAttribPointer, et (4) enableVertexAttribArray activé. Oubliez une étape et vous obtenez un canvas vide sans erreur — ce qui est la frustration WebGL la plus courante. La vérification attribLocations.vertexPosition === -1 dans l'exemple protège contre les noms d'attributs silencieusement mal orthographiés.

Utilisation des uniforms

Les attributs varient par sommet ; les uniforms restent constants pour tout un appel de dessin et constituent le moyen standard pour passer des valeurs changeantes — temps, couleur, matrices de transformation — de JavaScript vers un shader. C'est ainsi que vous animez ou recolorisez une scène sans re-téléverser la géométrie à chaque image.

Un exemple minimal de recoloration : déclarer un uniform dans le fragment shader, rechercher son emplacement une seule fois, puis le mettre à jour à chaque image.

// In the fragment shader source:
//   precision mediump float;
//   uniform vec4 uColor;
//   void main(void) { gl_FragColor = uColor; }

const colorLocation = gl.getUniformLocation(shaderProgram, 'uColor');

function render(timeMs) {
  const t = timeMs * 0.001;            // seconds
  const r = (Math.sin(t) + 1) / 2;     // oscillate 0..1
  gl.useProgram(shaderProgram);
  gl.uniform4f(colorLocation, r, 0.5, 1.0 - r, 1.0); // RGBA
  gl.drawArrays(gl.TRIANGLES, 0, 3);
  requestAnimationFrame(render);
}
requestAnimationFrame(render);

Comme la géométrie ne change jamais, seul l'uniform uColor est mis à jour par image — bien moins coûteux que de reconstruire des tampons.

Techniques avancées en WebGL

Au fur et à mesure de votre progression, WebGL offre des fonctionnalités étendues telles que l'éclairage, la texturation et la gestion de la géométrie :

  • Les textures vous permettent de mapper des images sur des surfaces avec gl.texImage2D et un uniform sampler2D dans le fragment shader.
  • Les tampons d'index (gl.ELEMENT_ARRAY_BUFFER + gl.drawElements) réutilisent les sommets partagés, réduisant la mémoire et le coût de dessin pour les maillages complexes.
  • Les matrices de transformation (modèle/vue/projection) passent de coordonnées 2D plates à une vraie perspective 3D ; des bibliothèques comme glMatrix gèrent les calculs mathématiques.
  • Les tests de profondeur (gl.enable(gl.DEPTH_TEST)) garantissent que les objets plus proches occultent correctement les objets plus éloignés.

Pour des implémentations concrètes, référez-vous aux exemples WebGL officiels de Khronos ou à une bibliothèque 3D établie comme Three.js, qui enveloppe l'API brute dans une abstraction beaucoup plus conviviale.

Meilleures pratiques pour le développement WebGL

  • Valider la création du contexte : Vérifiez toujours que getContext a renvoyé une valeur non nulle et prévoyez une solution de repli gracieuse pour les navigateurs sans prise en charge GPU.
  • Optimisation des performances : Minimisez les changements d'état (useProgram, bindBuffer), regroupez les appels de dessin et utilisez le dessin indexé pour les sommets partagés.
  • Gérer les ressources GPU : Supprimez les tampons, textures et programmes dont vous n'avez plus besoin avec gl.deleteBuffer, gl.deleteTexture et gl.deleteProgram pour éviter les fuites.
  • Tests multi-navigateurs : Assurez-vous que vos applications WebGL fonctionnent de manière cohérente sur différents navigateurs et appareils, et gérez l'événement rare webglcontextlost.
  • Interaction utilisateur : Pilotez les uniforms depuis les événements d'entrée pour rendre les scènes dynamiques — consultez JavaScript events pour la gestion des saisies utilisateur.

Conclusion

WebGL est un outil puissant pour les développeurs web souhaitant intégrer des graphiques 3D en temps réel dans leurs applications. Avec une planification soignée et une implémentation créative, vous pouvez créer des expériences visuelles époustouflantes qui s'exécutent parfaitement dans les navigateurs web. En maîtrisant WebGL grâce à des tutoriels complets et une pratique régulière, vous débloquerez un nouveau champ de possibilités pour le développement web.

Pratique

Pratique
Quelles capacités WebGL offre-t-il aux développeurs web ?
Quelles capacités WebGL offre-t-il aux développeurs web ?
Was this page helpful?