W3docs

JavaScript WebGL API

JavaScript WebGL API: Rendering-Kontext einrichten, Shader schreiben, Vertex-Buffer hochladen, ein Dreieck zeichnen und GPU-beschleunigte 3D-Grafiken im Browser rendern.

Einführung in WebGL

WebGL (Web Graphics Library, speziell Version 1.0) nutzt die Leistungsfähigkeit von OpenGL ES 2.0 in Webumgebungen und ermöglicht es Entwicklern, detaillierte 3D-Grafiken in jedem kompatiblen Webbrowser ohne Plugins zu rendern. Alle Beispiele in diesem Kapitel verwenden die WebGL 1.0 API. Diese Fähigkeit ist essenziell für die Erstellung immersiver Spiele, interaktiver 3D-Anwendungen und komplexer Visualisierungen direkt im Browser. Für moderne Projekte sollten Sie WebGL 2.0 in Betracht ziehen, das auf OpenGL ES 3.0 aufbaut und verbesserte Leistung und Funktionen bietet.

Dieses Kapitel behandelt alles, was Sie zum Zeichnen mit WebGL benötigen: einen Rendering-Kontext anfordern, Shader schreiben und kompilieren, Vertex-Daten in Buffer hochladen und Draw Calls ausgeben. Am Ende verstehen Sie die vollständige Pipeline hinter einem einzelnen gerenderten Dreieck und wissen, wie Sie mit Beleuchtung, Texturierung und Animation fortfahren können.

WebGL vs. das 2D-Canvas

WebGL rendert über dasselbe <canvas>-Element, das auch von der 2D Canvas API verwendet wird, doch die beiden unterscheiden sich grundlegend. Der 2D-Kontext (getContext('2d')) bietet eine High-Level-Zeichenfläche — fillRect, arc, drawImage. WebGL hingegen bietet eine Low-Level-, GPU-beschleunigte Pipeline: Sie beschreiben Geometrie als Arrays von Zahlen und schreiben kleine Programme (Shader), die auf der GPU laufen und bestimmen, wo jeder Vertex landet und welche Farbe jedes Pixel erhält. Dieser zusätzliche Aufwand ermöglicht Hardware-Beschleunigung, echtes 3D und den Durchsatz, der für Tausende von Objekten pro Frame benötigt wird.

Sie fordern beide Kontexte auf dieselbe Weise an, daher ist es gute Praxis, die Unterstützung zu prüfen:

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.');
}

Den ersten WebGL-Kontext einrichten

Um mit WebGL zu beginnen, ist die Einrichtung eines Rendering-Kontexts, der mit einem Canvas-Element in Ihrem HTML verknüpft ist, entscheidend. Für moderne Projekte können Sie auch einen WebGL 2-Kontext mit canvas.getContext('webgl2') anfordern, um verbesserte Leistung und Funktionen zu erhalten:

<!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>

Erklärung des Codes

  1. HTML-Aufbau: Der HTML-Teil richtet ein Canvas-Element ein, auf dem WebGL seine Ausgabe rendert. Ein Rahmen wird hinzugefügt, um den Canvas-Bereich auf der Webseite visuell zu kennzeichnen.
  2. CSS-Styling: Ein einfaches Style wird angewendet, um sicherzustellen, dass das Canvas eine bestimmte Größe und einen Rahmen zur Sichtbarkeit hat.
  3. JavaScript für WebGL:
    • Event Listener: Der JavaScript-Code ist in einen Event Listener eingebettet, der wartet, bis der DOM-Inhalt vollständig geladen ist, bevor er ausgeführt wird.
    • WebGL-Kontext-Initialisierung: Es wird der WebGL 1.0-Kontext vom Canvas abgerufen. Wenn WebGL nicht unterstützt wird, ist der Kontext null.
    • WebGL-Verfügbarkeitsprüfung: Wenn der Kontext null ist, wird ein Fehler in der Konsole protokolliert, der auf fehlende Unterstützung hinweist.
    • Clear-Color-Einstellung: gl.clearColor(0.0, 0.0, 1.0, 1.0) legt die Farbe fest (Blau, vollständig opak), die das Canvas füllt, wenn der Farb-Buffer geleert wird. Beachten Sie, dass dies nur die Farbe speichert — es wird noch nichts gezeichnet.
    • Farb-Buffer leeren: gl.clear(gl.COLOR_BUFFER_BIT) bemalt das Canvas tatsächlich mit der zuvor festgelegten Clear-Color und erzeugt ein solides blaues Quadrat.

Dieses Beispiel ist grundlegend, bietet aber einen guten Einstiegspunkt für das Verständnis der Funktionsweise von WebGL-Setups. Sie können dies erweitern, indem Sie weitere WebGL-Funktionalitäten wie Shader, Buffer und Zeichenbefehle hinzufügen, um grafische Ausgaben zu erstellen.

Ein einfaches Dreieck rendern

Einer der ersten Schritte beim Erlernen von WebGL ist das Rendern einfacher Formen. WebGL verwendet ein normiertes Gerätekoordinatensystem (NDC): Der sichtbare Bereich reicht von -1 bis 1 auf beiden Achsen X und Y, wobei (0, 0) im Mittelpunkt des Canvas liegt. Jede Form, die Sie zeichnen, muss in diesen Koordinaten beschrieben werden (oder durch einen Shader darin transformiert werden).

Selbst das Zeichnen eines einzelnen Dreiecks erfordert die vollständige WebGL-Pipeline:

  1. Schreiben Sie einen Vertex-Shader, der jeden Eckpunkt positioniert.
  2. Schreiben Sie einen Fragment-Shader, der jeden Pixel einfärbt.
  3. Kompilieren und verknüpfen Sie sie zu einem Shader-Programm.
  4. Laden Sie die Eckpunktkoordinaten in einen Buffer hoch.
  5. Verbinden Sie den Buffer mit dem Attribut des Shaders und führen Sie einen Draw Call aus.

Das folgende Beispiel durchläuft alle fünf Schritte:

<!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>

Erklärung des Codes

  • Vertex-Shader (vsSource): Liest das Attribut aVertexPosition und weist es dem eingebauten gl_Position zu, der bestimmt, wo jeder Vertex auf dem Bildschirm landet.
  • Fragment-Shader (fsSource): Setzt gl_FragColor, sodass jedes Pixel innerhalb des Dreiecks orange gerendert wird (vec4(1.0, 0.5, 0.0, 1.0) ist RGBA-Orange).
  • Shader-Kompilierung (loadShader): Kompiliert einen einzelnen Shader aus GLSL-Quellcode und meldet Kompilierungsfehler über getShaderInfoLog.
  • Shader-Programm-Initialisierung (initShaderProgram): Verknüpft die kompilierten Vertex- und Fragment-Shader zu einem ausführbaren Programm, das auf der GPU läuft.
  • Animations-Loop: drawScene() gibt den Draw Call aus, dann plant requestAnimationFrame(drawScene) den nächsten Frame und hält das Rendering synchron mit der Bildwiederholrate des Displays. Für eine eingehendere Behandlung der Frame-Planung, siehe JavaScript-Animationen.

Warum ein Draw Call so viel Vorbereitung erfordert

Wenn Sie von der 2D Canvas API kommen, kann dieses Gerüst für ein einzelnes Dreieck überwältigend wirken. Der Grund ist, dass WebGL zustandsbehaftet und explizit ist: Nichts wird gezeichnet, bis Sie (1) ein verknüpftes Programm, (2) Daten in einem Buffer, (3) diesen Buffer mit einem Shader-Attribut über vertexAttribPointer verbunden und (4) enableVertexAttribArray aktiviert haben. Vergessen Sie einen Schritt, erhalten Sie ein leeres Canvas ohne Fehlermeldung — was die häufigste WebGL-Frustrationsquelle ist. Die Prüfung attribLocations.vertexPosition === -1 im Beispiel schützt vor stillschweigend falsch geschriebenen Attributnamen.

Mit Uniforms arbeiten

Attribute variieren pro Vertex; Uniforms bleiben für einen gesamten Draw Call konstant und sind die Standardmethode, um sich ändernde Werte — Zeit, Farbe, Transformationsmatrizen — von JavaScript in einen Shader zu übergeben. So animieren oder färben Sie eine Szene um, ohne Geometrie bei jedem Frame neu hochzuladen.

Ein minimales Beispiel zur Neueinfärbung: Deklarieren Sie ein Uniform im Fragment-Shader, ermitteln Sie seinen Speicherort einmalig und aktualisieren Sie ihn dann bei jedem Frame.

// 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);

Da sich die Geometrie nie ändert, wird pro Frame nur das Uniform uColor aktualisiert — weit günstiger als das Neuaufbauen von Buffern.

Fortgeschrittene Techniken in WebGL

Im weiteren Verlauf bietet WebGL umfangreiche Funktionalität wie Beleuchtung, Texturierung und Geometrieverwaltung:

  • Texturen ermöglichen es, Bilder mit gl.texImage2D und einem sampler2D-Uniform im Fragment-Shader auf Oberflächen abzubilden.
  • Index-Buffer (gl.ELEMENT_ARRAY_BUFFER + gl.drawElements) verwenden gemeinsame Vertices wieder und reduzieren Speicher- und Zeichenkosten für komplexe Meshes.
  • Transformationsmatrizen (Modell/Ansicht/Projektion) ermöglichen den Übergang von flachen 2D-Koordinaten in echte 3D-Perspektive; Bibliotheken wie glMatrix übernehmen die Mathematik.
  • Tiefentest (gl.enable(gl.DEPTH_TEST)) stellt sicher, dass nähere Objekte weiter entfernte korrekt verdecken.

Für konkrete Implementierungen verweisen wir auf die offiziellen Khronos WebGL-Beispiele oder eine etablierte 3D-Bibliothek wie Three.js, die die rohe API in eine deutlich benutzerfreundlichere Abstraktion kapselt.

Best Practices für die WebGL-Entwicklung

  • Kontext-Erstellung validieren: Überprüfen Sie immer, dass getContext einen Nicht-null-Wert zurückgegeben hat, und bieten Sie einen eleganten Fallback für Browser ohne GPU-Unterstützung an.
  • Performance-Optimierung: Minimieren Sie Zustandsänderungen (useProgram, bindBuffer), bündeln Sie Draw Calls und verwenden Sie indexiertes Zeichnen für gemeinsame Vertices.
  • GPU-Ressourcen verwalten: Löschen Sie Buffer, Texturen und Programme, die Sie nicht mehr benötigen, mit gl.deleteBuffer, gl.deleteTexture und gl.deleteProgram, um Speicherlecks zu vermeiden.
  • Browserübergreifendes Testen: Stellen Sie sicher, dass Ihre WebGL-Anwendungen konsistent in verschiedenen Browsern und Geräten funktionieren, und behandeln Sie das seltene webglcontextlost-Ereignis.
  • Benutzerinteraktion: Steuern Sie Uniforms über Eingabeereignisse, um Szenen dynamisch zu gestalten — siehe JavaScript-Ereignisse für die Verarbeitung von Benutzereingaben.

Fazit

WebGL ist ein leistungsstarkes Werkzeug für Webentwickler, die Echtzeit-3D-Grafiken in ihre Anwendungen integrieren möchten. Mit sorgfältiger Planung und kreativer Umsetzung können Sie beeindruckende visuelle Erlebnisse schaffen, die nahtlos in Webbrowsern laufen. Durch die Beherrschung von WebGL mithilfe umfassender Tutorials und konsequenter Übung erschließen Sie sich eine neue Welt voller Möglichkeiten für die Webentwicklung.

Übungen

Übung
Welche Möglichkeiten bietet WebGL für Webentwickler?
Welche Möglichkeiten bietet WebGL für Webentwickler?
Was this page helpful?