Перейти к содержимому

Освоение WebGL для интерактивной 3D-графики в JavaScript

Введение в WebGL

WebGL (Web Graphics Library, версия 1.0) использует возможности OpenGL ES 2.0 в веб-среде, позволяя разработчикам отображать детализированную 3D-графику в любом совместимом браузере без необходимости установки плагинов. Все примеры в этой главе используют API WebGL 1.0. Эта возможность необходима для создания захватывающих игр, интерактивных 3D-приложений и сложных визуализаций непосредственно в браузере. Для современных проектов рекомендуется рассмотреть WebGL 2.0, который построен на основе OpenGL ES 3.0 и предлагает улучшенную производительность и новые функции.

Настройка первого контекста WebGL

Для начала работы с WebGL критически важно настроить контекст рендеринга, привязанный к элементу canvas в вашем HTML. Для современных проектов вы также можете запросить контекст WebGL 2, используя canvas.getContext('webgl2'), для получения улучшенной производительности и функций:


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

Разбор кода

  1. Настройка HTML: Часть HTML создает элемент canvas, на котором WebGL будет отображать результат. Добавлена граница для визуального выделения области canvas на веб-странице.
  2. Стилизация CSS: Применяется простой стиль, чтобы гарантировать, что canvas имеет определенный размер и границу для видимости.
  3. JavaScript для WebGL:
    • Обработчик событий: Код JavaScript обернут в обработчик событий, который ожидает полной загрузки содержимого DOM перед выполнением.
    • Инициализация контекста WebGL: Получается контекст WebGL 1.0 из canvas. Если WebGL не поддерживается, контекст будет равен null.
    • Проверка доступности WebGL: Если контекст равен null, в консоль выводится ошибка, указывающая на отсутствие поддержки.
    • Установка цвета очистки: Устанавливается цвет очистки в синий. Этот цвет заполнит canvas при очистке буфера цвета.
    • Очистка буфера цвета: Вызывается функция `gl.clear` с параметром `gl.COLOR_BUFFER_BIT` для применения цвета очистки.

Этот пример является базовым, но служит хорошей отправной точкой для понимания принципов настройки WebGL. Вы можете расширить его, добавив больше возможностей WebGL, таких как шейдеры, буферы и команды отрисовки, для создания графических выводов.

Отрисовка простого треугольника

Одним из первых шагов в изучении WebGL является отрисовка простых фигур. WebGL использует нормализованную систему координат устройства, где видимая область варьируется от -1 до 1 по осям X и Y. Ниже приведен пример того, как отрисовать треугольник:


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

Объяснение кода

  • Вершинный шейдер (vsSource): Определяет атрибут позиции и присваивает его `gl_Position`, определяя положение вершин.
  • Фрагментный шейдер (fsSource): Устанавливает цвет пикселей внутри треугольника в оранжевый.
  • Компиляция шейдеров (loadShader): Компилирует как вершинный, так и фрагментный шейдеры.
  • Инициализация программы шейдеров (initShaderProgram): Связывает скомпилированные вершинный и фрагментный шейдеры в единую исполняемую программу.
  • Цикл анимации: `drawScene()` вызывается один раз для запуска отрисовки, после чего `requestAnimationFrame(drawScene)` эффективно планирует последующие кадры.

Продвинутые техники в WebGL

По мере продвижения WebGL предлагает обширные возможности, такие как освещение, текстурирование и управление геометрией. Использование этих функций может значительно улучшить визуальный вывод ваших 3D-моделей и сцен. Для конкретных реализаций обращайтесь к официальным примерам Khronos WebGL или устоявшимся 3D-библиотекам, таким как Three.js.

Лучшие практики разработки в WebGL

  • Оптимизация производительности: Эффективно управляйте памятью и вычислительными ресурсами, например, используя индексную отрисовку и минимизируя изменения состояния.
  • Кроссбраузерное тестирование: Убедитесь, что ваши WebGL-приложения стабильно работают в различных веб-браузерах и на разных устройствах.
  • Взаимодействие с пользователем: Внедряйте элементы управления и взаимодействия, такие как обновление униформ для динамического освещения, чтобы сделать ваши 3D-сцены динамичными и увлекательными.

Заключение

WebGL — это мощный инструмент для веб-разработчиков, стремящихся интегрировать 3D-графику в реальном времени в свои приложения. При тщательном планировании и креативной реализации вы сможете создавать потрясающие визуальные впечатления, которые беспрепятственно работают в веб-браузерах. Освоив WebGL с помощью подробных руководств и регулярной практики, вы откроете для себя новые горизонты возможностей в веб-разработке.

Практика

Какие возможности предоставляет WebGL веб-разработчикам?

Считаете ли это полезным?

Предпросмотр dual-run — сравните с маршрутами Symfony на продакшене.