W3docs

API WebGL do JavaScript

Aprenda a API WebGL do JavaScript: configure um contexto de renderização, escreva e compile shaders, carregue buffers de vértices, desenhe um triângulo e use uniforms para renderizar gráficos 3D acelerados por GPU no navegador.

Introdução ao WebGL

WebGL (Web Graphics Library, especificamente a versão 1.0) aproveita o poder do OpenGL ES 2.0 em ambientes web, permitindo que desenvolvedores renderizem gráficos 3D detalhados em qualquer navegador compatível sem a necessidade de plugins. Todos os exemplos neste capítulo utilizam a API WebGL 1.0. Essa capacidade é essencial para criar jogos imersivos, aplicações 3D interativas e visualizações complexas diretamente no navegador. Para projetos modernos, considere o WebGL 2.0, que se baseia no OpenGL ES 3.0 e oferece melhor desempenho e mais recursos.

Este capítulo cobre o que você precisa para começar a desenhar com WebGL: solicitar um contexto de renderização, escrever e compilar shaders, carregar dados de vértices em buffers e emitir chamadas de desenho. Ao final, você entenderá o pipeline completo por trás de um único triângulo renderizado e saberá para onde ir a seguir para iluminação, texturização e animação.

WebGL vs. o Canvas 2D

O WebGL renderiza por meio do mesmo elemento <canvas> usado pela API Canvas 2D, mas os dois são muito diferentes. O contexto 2D (getContext('2d')) oferece uma superfície de desenho de alto nível — fillRect, arc, drawImage. O WebGL fornece um pipeline de baixo nível acelerado por GPU: você descreve a geometria como arrays de números e escreve pequenos programas (shaders) que são executados na GPU para decidir onde cada vértice fica e qual cor cada pixel recebe. Esse esforço extra traz aceleração de hardware, 3D real e o throughput necessário para milhares de objetos por quadro.

Você solicita os dois contextos da mesma forma, portanto é uma boa prática detectar o recurso disponível:

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

Configurando Seu Primeiro Contexto WebGL

Para começar com WebGL, é fundamental configurar um contexto de renderização vinculado a um elemento canvas no seu HTML. Para projetos modernos, você também pode solicitar um contexto WebGL 2 usando canvas.getContext('webgl2') para melhor desempenho e mais recursos:

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

Explicação do Código

  1. Configuração HTML: A parte HTML define um elemento canvas onde o WebGL renderizará sua saída. Uma borda é adicionada para identificar visualmente a área do canvas na página.
  2. Estilização CSS: Um estilo simples é aplicado para garantir que o canvas tenha um tamanho específico e uma borda para visibilidade.
  3. JavaScript para WebGL:
    • Event Listener: O código JavaScript está encapsulado em um event listener que aguarda o carregamento completo do conteúdo DOM antes de executar.
    • Inicialização do Contexto WebGL: Obtém o contexto WebGL 1.0 do canvas. Se o WebGL não for suportado, o contexto será null.
    • Verificação de Disponibilidade do WebGL: Se o contexto for null, um erro é registrado no console indicando falta de suporte.
    • Definição da Cor de Limpeza: gl.clearColor(0.0, 0.0, 1.0, 1.0) define a cor (azul, totalmente opaco) que preencherá o canvas quando o buffer de cor for limpo. Note que isso apenas armazena a cor — nada é desenhado ainda.
    • Limpeza do Buffer de Cor: gl.clear(gl.COLOR_BUFFER_BIT) efetivamente pinta o canvas com a cor de limpeza definida anteriormente, produzindo um quadrado azul sólido.

Este exemplo é fundamental, mas fornece um bom ponto de partida para entender como as configurações do WebGL funcionam. Você pode aprimorá-lo adicionando mais funcionalidades WebGL como shaders, buffers e comandos de desenho para criar saídas gráficas.

Renderizando um Triângulo Simples

Um dos primeiros passos no aprendizado do WebGL é renderizar formas simples. O WebGL usa um sistema de coordenadas de dispositivo normalizado (NDC): a área visível vai de -1 a 1 tanto no eixo X quanto no eixo Y, com (0, 0) no centro do canvas. Cada forma que você desenha deve ser descrita nessas coordenadas (ou transformada para elas por um shader).

Desenhar mesmo um único triângulo requer o pipeline completo do WebGL:

  1. Escrever um vertex shader que posiciona cada canto.
  2. Escrever um fragment shader que colore cada pixel.
  3. Compilar e vinculá-los em um shader program.
  4. Carregar as coordenadas dos cantos em um buffer.
  5. Conectar o buffer ao atributo do shader e emitir uma draw call.

O exemplo abaixo percorre todas as cinco etapas:

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

Explicação do Código

  • Vertex Shader (vsSource): Lê o atributo aVertexPosition e o atribui ao gl_Position integrado, que determina onde cada vértice fica na tela.
  • Fragment Shader (fsSource): Define gl_FragColor para que cada pixel dentro do triângulo seja renderizado em laranja (vec4(1.0, 0.5, 0.0, 1.0) é RGBA laranja).
  • Compilação do Shader (loadShader): Compila um único shader a partir do código-fonte GLSL e reporta erros de compilação via getShaderInfoLog.
  • Inicialização do Shader Program (initShaderProgram): Vincula os vertex e fragment shaders compilados em um programa executável que roda na GPU.
  • Loop de Animação: drawScene() emite a draw call, e então requestAnimationFrame(drawScene) agenda o próximo quadro, mantendo a renderização sincronizada com a taxa de atualização do monitor. Para uma cobertura mais aprofundada sobre agendamento de quadros, consulte animações JavaScript.

Por Que uma Draw Call Precisa de Tanta Configuração

Se você vem da API Canvas 2D, toda essa estrutura pode parecer excessiva para um único triângulo. O motivo é que o WebGL é stateful e explícito: nada é desenhado até que você tenha (1) um programa vinculado, (2) dados em um buffer, (3) esse buffer conectado a um atributo do shader com vertexAttribPointer, e (4) enableVertexAttribArray ativado. Esquecer qualquer etapa resulta em um canvas em branco sem erro — que é a frustração mais comum no WebGL. A verificação attribLocations.vertexPosition === -1 no exemplo protege contra nomes de atributos incorretos que passam silenciosamente.

Trabalhando com Uniforms

Atributos variam por vértice; uniforms permanecem constantes durante toda uma draw call e são a forma padrão de passar valores em mudança — tempo, cor, matrizes de transformação — do JavaScript para um shader. É assim que você anima ou recolore uma cena sem recarregar a geometria a cada quadro.

Um exemplo mínimo de recoloração: declare um uniform no fragment shader, obtenha sua localização uma vez e atualize-a a cada quadro.

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

Como a geometria nunca muda, apenas o uniform uColor é atualizado por quadro — muito mais barato do que reconstruir buffers.

Técnicas Avançadas em WebGL

À medida que você avança, o WebGL oferece funcionalidades extensas como iluminação, texturização e gerenciamento de geometria:

  • Texturas permitem mapear imagens em superfícies com gl.texImage2D e um uniform sampler2D no fragment shader.
  • Index buffers (gl.ELEMENT_ARRAY_BUFFER + gl.drawElements) reutilizam vértices compartilhados, reduzindo memória e custo de draw para malhas complexas.
  • Matrizes de transformação (model/view/projection) movem de coordenadas 2D planas para uma perspectiva 3D real; bibliotecas como glMatrix lidam com a matemática.
  • Depth testing (gl.enable(gl.DEPTH_TEST)) garante que objetos mais próximos ocluam corretamente os mais distantes.

Para implementações concretas, consulte as amostras oficiais do Khronos WebGL ou uma biblioteca 3D estabelecida como o Three.js, que encapsula a API bruta em uma abstração muito mais amigável.

Melhores Práticas para Desenvolvimento WebGL

  • Valide a criação do contexto: Sempre verifique se getContext retornou um valor não-null e forneça um fallback elegante para navegadores sem suporte a GPU.
  • Otimização de Desempenho: Minimize mudanças de estado (useProgram, bindBuffer), agrupe draw calls e use desenho indexado para vértices compartilhados.
  • Gerencie recursos da GPU: Delete buffers, texturas e programas que você não precisa mais com gl.deleteBuffer, gl.deleteTexture e gl.deleteProgram para evitar vazamentos.
  • Testes entre Navegadores: Garanta que suas aplicações WebGL funcionem de forma consistente em diferentes navegadores e dispositivos, e lide com o raro evento webglcontextlost.
  • Interação com o Usuário: Controle uniforms a partir de eventos de input para tornar as cenas dinâmicas — consulte eventos JavaScript para lidar com a entrada do usuário.

Conclusão

O WebGL é uma ferramenta poderosa para desenvolvedores web que desejam integrar gráficos 3D em tempo real em suas aplicações. Com planejamento cuidadoso e implementação criativa, você pode criar experiências visuais impressionantes que rodam perfeitamente em navegadores web. Ao dominar o WebGL por meio de tutoriais abrangentes e prática consistente, você abrirá um novo universo de possibilidades para o desenvolvimento web.

Prática

Prática
Quais capacidades o WebGL oferece para desenvolvedores web?
Quais capacidades o WebGL oferece para desenvolvedores web?
Was this page helpful?