WebGL (Web-based Graphics Library) — программная библиотека для языка программирования JavaScript, позволяющая создавать на JavaScript интерактивную 3D-графику, функционирующую в широком спектре совместимых с ней веб-браузеров. За счёт использования низкоуровневых средств поддержки OpenGL, часть кода на WebGL может выполняться непосредственно на видеокартах.
WebGL позволяет веб-контенту использовать API для визуализации трехмерной графики без использования плагинов в HTML элементе canvas в браузерах, которые осуществляют его поддержку. WebGL программы состоят из кода управления, написанном на JavaScript и кода специальных эффектов (шейдерного кода), который выполняется на графическом процессоре. WebGL элементы могут быть смешаны с другими HTML элементами и собраны с другими частями веб-страницы или фоном веб-страницы.
Подготовка к визуализации в 3D
Первое, что вам понадобится для использования WebGL для визуализации в 3D - это элемент canvas. Фрагмент на HTML ниже содержит элемент canvas и определяет обработчик события onload, которое инициализирует наш контекст WebGL.
<body onload="start()"> <canvas id="glcanvas" width="640" height="480"> Your browser doesn't appear to support the HTML5 canvas element. </canvas> </body>
Подготовка контекста WebGL
Функция start(), в нашем JavaScript коде вызывается после загрузки документа. Ее назначение - настройка контекста WebGL и начать отрисовку содержимого.
var gl; // глобальная переменная для контекста WebGL function start() { var canvas = document.getElementById("glcanvas"); gl = initWebGL(canvas); // инициализация контекста GL // продолжать только если WebGL доступен и работает if (gl) { gl.clearColor(0.0, 0.0, 0.0, 1.0); // установить в качестве цвета очистки буфера цвета черный, полная непрозрачность gl.enable(gl.DEPTH_TEST); // включает использование буфера глубины gl.depthFunc(gl.LEQUAL); // определяет работу буфера глубины: более ближние объекты перекрывают дальние gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT); // очистить буфер цвета и буфер глубины. } }
Первое, что мы здесь делаем - получаем ссылку на элемент canvas, помещаем ее в переменную canvas. Очевидно, что если вам не требуется многократно получать ссылку на canvas, вы должны избежать сохранения этого значения глобально, а только сохранить ее в локальной переменной или в поле объекта.
Как только мы получили ссылку на canvas, мы вызываем функцию initWebGL(); Эту функцию мы определяем незамедлительно, ее работа - инициализировать контекст WebGL.
Если контекст успешно инициализирован, в gl будет содержаться ссылка на него. В этом случае, мы устанавливаем цвет очистки буфера цвета (цвет фона) на черный, затем очищаем контекст этим цветом. После этого, контекст конфигурируется параметрами настройки. В данном случае, мы включаем буфер глубины и определяем, что более близкие объекты будут перекрывать более дальние.
Всё вышеперечисленное необходимо сделать только для первоначальной инициализации. Чуть позже мы увидим работу по визуализации трехмерных объектов.
Создание контекста WebGL
function initWebGL(canvas) { gl = null; try { // Попытаться получить стандартный контекст. Если не получится, попробовать получить экспериментальный. gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"); } catch(e) {} // Если мы не получили контекст GL, завершить работу if (!gl) { alert("Unable to initialize WebGL. Your browser may not support it."); gl = null; } return gl; }
Чтобы получить контекст WebGL для canvas, мы запрашиваем у элемента canvas контекст именуемый как "webgl". Если данная попытка завершается неудачно, мы пытаемся получить контекст, именуемый как "experimental-webgl". Если данная попытка также завершается неудачно, мы отображаем окно с предупреждением, позволяющим пользователю понять, что его браузер не поддерживает WebGL. Это всё, что необходимо сделать. На данном этапе мы будем иметь в переменной gl либо значение null (означающее, что контекст WebGL не доступен), либо ссылку на контекст WebGL в котором, мы будем производить отрисовку.
Изменение размера контекста WebGL
Новый контекст WebGL будет иметь возможность задания размеров област
Рабочий пример в прии отображения в момент получения контекста путем задания высоты и ширины элемента canvas, без использования CSS. Редактирование стиля элемента canvas будет изменять его отображаемый размер, без изменения размеров области отрисовки. Редактирование атрибутов ширины и высоты элемента canvas после создания контекста не будет также изменять число пикселей для отрисовки. Чтобы изменить размер области отрисовки, с которой WebGL производит работу, например, когда пользователь изменяет размер окна на весь экран или когда вам необходимо менять настройки графики в самом приложении, вам необходимо вызвать контекстную функцию WebGL viewport(), чтобы подтвердить изменения.
Чтобы изменить размер области отрисовки контекста WebGL с переменными gl и canvas, использующимися в примере выше:
gl.viewport(0, 0, canvas.width, canvas.height);
Элемент canvas будет изменять размер, в случае, если его отрисовать с другими размерами области отрисовки, чем те, что указаны в его стилях CSS, согласно которым он занимает место на экране. Изменение размера с помощью CSS наиболее полезно для сохранения ресурсов, отрисовывая с низким разрешением и позволяя браузеру растягивать полученный результат.
Добавление двухмерного контента в контекст WebGL
После того, как вы успешно инициализировали WebGL, вы можете начинать отображать в нем графические объекты. Простейшая вещь, которую вы можете сделать - отрисовать простой 2D контент - объект без текстуры. Итак, начнём построение кода для отрисовки квадрата.
Освещение сцены
На данном этапе очень важно понять одну вещь: не смотря на то, что мы в этом примере отрисовываем двухмерный объект, мы по-прежнему отрисовываем его в трехмерном пространстве. По существу, нам по-прежнему необходимо создать шейдеры, которые будут освещать нашу простую сцену, и отрисовать наш объект. На данном шаге определим как квадрат будет освещаться.
Инициализация шейдеров
Шейдеры задаются при помощи языка высокого уровня для программирования шейдеров - OpenGL ES Shading Language. Для того, чтобы сделать проще процесс поддержки и обновления нашего контента, мы можем фактически написать наш код, загружающий шейдеры и помещающий их в HTML документ, вместо того, чтобы встраивать его весь в JavaScript. Давайте рассмотрим нашу процедуру initShaders(), которая выполнит эту задачу:
function initShaders() { var fragmentShader = getShader(gl, "shader-fs"); var vertexShader = getShader(gl, "shader-vs"); // создать шейдерную программу shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); // Если создать шейдерную программу не удалось, вывести предупреждение if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { alert("Unable to initialize the shader program."); } gl.useProgram(shaderProgram); vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); gl.enableVertexAttribArray(vertexPositionAttribute); }
Этой процедурой загружаются две шейдерные программы. Первая - фрагментный шейдер, загружается из элемента script с ID "shader-fs". Вторая - вершинный шейдер, загружается из элемента script с ID "shader-vs". Мы рассмотрим функцию getShader() чуть ниже. Эта процедура фактически отвечает за извлечение шейдерных программ из DOM.
Затем мы создаем шейдерную программу, вызывая функцию createProgram() объекта WebGL, присоединяя два шейдера к нему, и связывая шейдерную программу. После выполнения этого, проверяется значение параметра LINK_STATUS объекта gl для того, чтобы убедиться, что программа успешно скомпанована. Если это так, мы активируем новую шейдерную программу.
Загрузка шейдеров из DOM
Функция getShader() получает из DOM шейдерную программу с определенным именем, возвращая скомпилированную шейдерную программу вызывающему, или значение null, если шейдерная программа не может быть загружена или скомпилирована.
function getShader(gl, id) { var shaderScript, theSource, currentChild, shader; shaderScript = document.getElementById(id); if (!shaderScript) { return null; } theSource = ""; currentChild = shaderScript.firstChild; while(currentChild) { if (currentChild.nodeType == currentChild.TEXT_NODE) { theSource += currentChild.textContent; } currentChild = currentChild.nextSibling; }
Как только элемент с указанным ID найден, его текст помещается в переменную theSource.
if (shaderScript.type == "x-shader/x-fragment") { shader = gl.createShader(gl.FRAGMENT_SHADER); } else if (shaderScript.type == "x-shader/x-vertex") { shader = gl.createShader(gl.VERTEX_SHADER); } else { // неизвестный тип шейдера return null; }
После того, как код для шейдера считан, мы проверяем MIME тип шейдерного объекта, чтобы определить является он вершинным (MIME type "x-shader/x-vertex") или фрагментным (MIME type "x-shader/x-fragment") шейдером, а затем создаем соответствующий тип шейдера из полученного исходного кода.
gl.shaderSource(shader, theSource); // скомпилировать шейдерную программу gl.compileShader(shader); // Проверить успешное завершение компиляции if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { alert("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader)); return null; } return shader; }
В результате, исходный код, передан в переменную shader и скомпилирован. Если произошли ошибки во время компиляции кода шейдера, мы отображаем окно с предупреждением и возвращаем значение null; Иначе, возвращается новый скомпилированный шейдер.
Шейдеры
После этого нам необходимо добавить шейдерные программы в HTML описывающий наш документ. Подробная информация о том, как работают шейдеры выходит за рамки этой статьи, как и впрочем описание синтаксиса языка программирования шейдеров.
Фрагментный шейдер
Каждый пиксель в полигоне называется фрагментом в языке GL. Фрагментные шейдеры необходимы для назначения цвета для каждого пикселя. В данном случае, мы просто назначаем белый цвет каждому пикселю.
gl_FragColor - встроенная переменная GL, используемая для управления цветом фрагментов. Устанавливая ее значение назначаем цвет пикселям. Ниже приведен пример этого.
Вершинный шейдер
Вершинный шейдер определяет положение и форму каждой вершины.
<script id="shader-vs" type="x-shader/x-vertex"> attribute vec3 aVertexPosition; uniform mat4 uMVMatrix; uniform mat4 uPMatrix; void main(void) { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); } </script>
Создание объекта
Перед тем, как мы отрисуем наш квадрат, нам необходимо создать буфер, который содержит его вершины. Мы сделаем это, вызвав функцию initBuffers(). По мере ознакомления с другими концепциями WebGL, эта функция будет усложняться при создании более сложных трехмерных объектов.
var horizAspect = 480.0/640.0; function initBuffers() { squareVerticesBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer); var vertices = [ 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, -1.0, 0.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); }
Отрисовка сцены
Как только шейдеры установлены и объект создан, мы можем действительно отрисовать сцену. Поскольку в этом примере нет какой-либо анимации, наша функция drawScene() имеет очень простой вид. Она использует несколько утилитарных процедур, которые мы кратко рассмотрим ниже.
function drawScene() { gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); perspectiveMatrix = makePerspective(45, 640.0/480.0, 0.1, 100.0); loadIdentity(); mvTranslate([-0.0, 0.0, -6.0]); gl.bindBuffer(gl.ARRAY_BUFFER, squareVerticesBuffer); gl.vertexAttribPointer(vertexPositionAttribute, 3, gl.FLOAT, false, 0, 0); setMatrixUniforms(); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); }
Первый шаг - очистка цветом фона сцены контекста. Затем мы устанавливаем перспективу камеры. Мы устанавливаем угол обзора в 45°, с соотношением ширины к высоте равным 640/480 (размеры нашего объекта canvas). Мы также определяем, что мы хотим видеть отрисованными объекты на расстоянии от 0.1 до 100 единиц от камеры.
Затем мы устанавливаем позицию квадрата, загружая определенную позицию и размещая ее от камеры на 6 единиц. После этого, мы привязываем буфер, содержащий вершины квадрата к контексту, настраиваем его, и отрисовываем объект, вызывая метод drawArrays().
Полностью рабочий пример подключения и создания фигуры вы можете посмотреть в прикрепленных файлах.
Комментарии
И где эти прикрепленные файлы?
WebGL API Tutorial
WebGL wiki
Adding 2D content to a WebGL context