Pull to refresh

Знакомство с WebGL

Reading time 6 min
Views 44K

Введение


Статья создана с целью показать основные действия, необходимые для отображения 3d в современном браузере, используя технологию WebGL. Для достижения цели рассмотрим задачу построения нескольких линий в трехмерном пространстве.

Схема работы:
  1. Получаем WebGL контекст из canvas'а.
  2. Загружаем программу шейдеров. А именно:
    • создаем программу шейдоров;
    • получаем исходный код отдельно для вершинного и фрагментного шейдеров;
    • компилируем коды шейдеров;
    • присоединяем к программе;
    • активируем программу.

  3. Устанавливаем две матрицы: model-view и projection.
  4. Размещаем, заполняем, активируем буферы данных вершин.
  5. Рисуем.



1. WebGL-контекст

WebGL контекст возможно получить из DOM-элемента canvas, вызвав метод getContext(“experimental-webgl”). Следует заметить, что Khronos Group рекомендует (https://www.khronos.org/webgl/wiki/FAQ) для получения контекста WebGL использовать следующий способ:

var names = ["webgl", "experimental-webgl", "webkit-3d", "moz-webgl"];
    gl = null;
    for (var ii = 0; ii < names.length; ++ii) {
        try {
            gl = canvas.getContext(names[ii]);
        } catch(e) {}
        if (gl) {
            break;
        }
    }


При успешном получении контекста объект gl имеет методы, названия которых очень похожи на функции OpenGL ES. Например, функция clear(COLOR_BUFFER_BIT) для WebGL будет gl.clear(gl.COLOR_BUFFER_BIT), что очень удобно. Но следует помнить, что не все функции WebGL имеют такой же синтаксис, как и функции OpenGL ES 2.0.

2. Шейдеры

Шейдерная программа является неотъемлемой частью построения изображений с помощью WebGL. Именно через нее задается положение и цвет каждой вершины наших линий. В нашей задаче используется два шейдера: вершинный и фрагментный. При построении линий в трехмерном пространстве вершинный шейдер отвечает за положение вершин в пространстве, основываясь на значениях видовой матрицы и матрицы перспективной проекции. Фрагментный шейдер используется для вычисления цвета наших линий.

Вершинный шейдер

    attribute vec3 aVertexPosition;
    attribute vec4 aVertexColor;
    uniform mat4 mvMatrix;
    uniform mat4 prMatrix;
    varying vec4 color;

    void main(void) 
    {
	    gl_Position = prMatrix * mvMatrix * vec4 ( aVertexPosition, 1.0 );
	    color       = aVertexColor;
    }


Фрагментный шейдер

     
     #ifdef GL_ES
       precision highp float;
    #endif
    varying vec4 color;
    void main(void) 
    {
        gl_FragColor = color;
    }


То, что определено после «uniform» является общим для всех вершин. Здесь это матрицы преобразования: видовая и перспективная. То, что определено после «attribute» используется при вычислении каждой вершины. Здесь это положение вершины и ее цвет. После «varying» определяем переменную, которая будет передана из вершинного во фрагментный шейдер. Результат вычисления положения присваиваем переменной gl_Position, цвета — gl_FragColor.

3. Модельно-видовая матрица и матрица перспективной проекции

Обе матрицы имеют размер 4х4 и используются для расчета отображения трехмерных объектов на двумерную плоскость. Их различие в том, что видовая матрица определяет, как объекты будут выглядеть для наблюдателя, например, при изменении его положения, а матрица проекции изначально задает способ проецирования.
В нашей программе значения матрицы проекции задаются при вызове функции gluPerspective на этапе инициализации, в дальнейшем эта матрица не меняет своих значений. Функция gluPerspective не является стандартной, ее мы определили сами. Ее аргументы: fovy, aspect, zNear, zFar. fovy — область угла просмотра по вертикали в градусах; aspect — отношение ширины области просмотра к высоте; zNear — расстояние до ближней плоскости отсечения (всё что ближе — не рисуется); zFar — расстояние до дальней плоскости отсечения (всё что дальше — не рисуется).

Для задания значений модельно-видовой матрицы можно использовать несколько подходов. Например, создать и использовать функцию gluLookAt ( camX, camY, camZ, tarX, tarY, tarZ, upX, upY, upZ) – аналог функции для OpenGL, которая принимает в качестве аргументов координаты положение камеры, координаты цели камеры и up-вектор камеры. Другой способ, это создание и использование функций glTranslate, glRotate, glScale, которые производят сдвиг, вращение, масштабирование относительно наблюдателя (камеры). Для первичного определения положения камеры можно использовать gluLookAt, а для последующих преобразований использовать glTranslate, glRotate, glScale. Так или иначе, эти функции лишь изменяют значения одной и той же модельно-видовой матрицы. Для удобства вычисления матриц можно использовать библиотеку sylvester.js, что мы и будем делать.

Теперь, когда нашли способ изменять значения обеих матриц, рассмотрим их передачу программе шейдеров. В нашем вершинном шейдере для модельно-видовой матрицы мы используем переменную «mvMatrix». Чтобы передать этой переменной значения матрицы, нам нужно сначала получить ее индекс в программе. Для этого используем функцию loc=gl.getUniformLocation ( shaderProgram, name ), которая является стандартной. Как несложно догадаться, первый аргумент – переменная, указывающая на программу шейдеров, которая получена на втором этапе, а аргумент «name» — имя переменной, которой мы хотим передать значение, в нашем случае name= «mvMatrix». Теперь, получив индекс, используем функцию gl.uniformMatrix4fv ( loc, false, new Float32Array(mat.flatten())) для передачи значения матрицы mat. Аналогично, получаем индекс и устанавливаем значение для матрицы проекции. Следует помнить, что видовую матрицу в шейдерной программе нужно обновлять всякий раз при изменении ее значений, чтобы они вступили в силу.

4. Буферы данных

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

    /* Создаем буфер  */
    vPosBuffer = gl.createBuffer();
    
    /* Активируем буфер*/
    gl.bindBuffer(gl.ARRAY_BUFFER, vPosBuffer);

    /* Копируем в буфер координаты вершин */
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verticies), gl.DYNAMIC_DRAW);
    
    /* Определяем, что координаты вершин имеют определенный индекс атрибута и содержат 3 floats на вершину */
    gl.vertexAttribPointer(vPosLoc, 3, gl.FLOAT, false, 0, 0);

    /* Задействуем индекс аттрибута */
    gl.enableVertexAttribArray(vPosLoc);


Здесь verticies – массив координат точек линий. Координаты идут по 6 штук, первые 3 из которых – x-, y-, z-координата начала линии, следующие, соответственно, конца. vPosLoc – это индекс атрибута «aVertexPosition» в шейдерной программе. Т.к. в нашей программе были явно заданы индексы с помощью gl.bindAttribLocation (shaderProgram, loc, shadersAttrib) на этапе сборки шейдерной программы, то получать их еще раз не нужно. Если бы такого не было, то следует получить индекс, используя команду «vPosLoc = getAttribLocation(shaderProgram, «aVertexPosition»)». Аналогичные действия проводятся и со вторым буфером, отличаться будет данными (вместо verticies массив цветов) и индексом в шейдерной программе (вместо vPosLoc).

5. Рисуем

Очистка буфера цвета или, проще говоря, задание фона произведем стандартными командами

gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);


Теперь выполним рисование
gl.drawArrays(gl.LINES, drawArrayIndexStart, drawArrayLength);

Первый аргумент этой функции говорит, что будем рисовать именно линии, второй – индекс в буфере, с которого начнем рисование, drawArrayLength – количество элементов для рисования. Т.к. мы в буфер передаем координаты вершин из массива verticies, то

drawArrayLength = verticies.length / 3;


Если у нас изменились прямые, то выполняем шаги 4, 5 для перерисовки. Если у нас изменилось положение камеры, то выполняем шаг 3 и шаг 5.

Заключение


Задача построения прямых линий взята не с потолка. Есть программа, которая решает систему дифференциальных уравнений и строит результат в 3d, используя OpenGL. Было решено перенести программу на php и отображать результат, используя WebGL. Для решения задачи об отображении в трехмерном пространстве прямых были изучены современные engine из списка (http://ru.wikipedia.org/wiki/WebGL): WebGLU, GLGE, C3DL, Copperlicht, SpiderGL и SceneJS. Для этого был создан интерфейс, позволяющий универсализировать общение основной программы со сторонними движками. Результатов удалось добиться с WebGLU, C3DL. В других либо отсутствует простой способ построения линии, либо он неоптимальный. В одном из них функция линии задокументирована, но на форуме проекта дали понять, что использовать ее не удастся, и предложили, рисовать ее многоугольниками.

К сожалению, при использовании C3DL еще не удалось оптимизировать процесс, что привело к низкому значению fps. При работе с WebGLU была допущена ошибка, которая так же повлияла на значение fps. Это заставило написать свой движок, который сейчас и используется. Я ни в коем случае не хочу упрекнуть сторонние движки, они созданы для более широкого круга задач, чем простое рисование линий.

Пару слов о браузерах. Тестировалось на Firefox 4 beta 8, Chrome 8 с –enable-webgl. На данной задаче Firefox показывал значение fps выше Chrome в 1,5-2 раза. При обновлении Chrome до beta 9 показатели не изменились. Показатели fps не изменились и при обновлении Firefox beta 8 до beta 9, разве что в консоли стало больше непонятных ошибок и стало некорректно отображаться сцена с использованием WebGLU.

Ссылки на рабочую версию




Список литературы


Tags:
Hubs:
+66
Comments 15
Comments Comments 15

Articles