Надеюсь, эта небольшая статья будет полезна для вас. В нем дается обзор большой части того, что я узнал о WebGL и 3D в целом. Кстати, если я что-то не так понял, пожалуйста, поправьте меня, потому что я тоже все еще учусь!
Архитектура
Браузер - это всего лишь браузер. Все, что он делает, это представляет API WebGL (через JavaScript), с которым программист делает все остальное.
Насколько я могу судить, API WebGL, по сути, представляет собой просто набор (предоставляемых браузером) функций JavaScript, которые обертывают спецификацию OpenGL ES. Поэтому, если вы знакомы с OpenGL ES, вы можете довольно быстро освоить WebGL. Не путайте это с чистым OpenGL. «ES» важен.
Спецификация WebGL была намеренно оставлена на очень низком уровне, оставляя многое для
быть повторно реализован из одного приложения в другое. Это до
сообщество, чтобы написать рамки для автоматизации, и до разработчика
выбрать, какой фреймворк использовать (если есть). Это не совсем сложно
бросить свой собственный, но это означает много накладных расходов, потраченных на
заново изобретать колесо. (FWIW, я работал над своим WebGL
рамки под названием Jax на некоторое время
Теперь.)
Графический драйвер обеспечивает реализацию OpenGL ES, которая фактически выполняет ваш код. На данный момент он работает на оборудовании машины, даже ниже кода C. Хотя это то, что делает WebGL возможным в первую очередь, это также обоюдоострый меч, потому что ошибки в драйвере OpenGL ES (о котором я уже упоминал довольно много) будут появляться в вашем веб-приложении, и вы не будете Обязательно знайте об этом, если только вы не можете рассчитывать на свою пользовательскую базу для составления согласованных отчетов об ошибках, включая ОС, видеооборудование и версии драйверов. Вот как выглядит процесс отладки для таких проблем.
В Windows существует дополнительный слой, который существует между API WebGL и аппаратным обеспечением: ANGLE, или «Почти собственный движок графического уровня» . Поскольку драйверы OpenGL ES в Windows, как правило, отстой, ANGLE принимает эти вызовы и вместо этого переводит их в вызовы DirectX 9.
Рисование в 3D
Теперь, когда вы знаете, как соединяются части, давайте посмотрим на более низкое объяснение того, как все собирается вместе для создания трехмерного изображения.
JavaScript
Сначала код JavaScript получает трехмерный контекст из элемента HTML5 canvas . Затем он регистрирует набор шейдеров, которые написаны на GLSL ([Open] GL Shading Language) и по сути напоминают код C.
Остальная часть процесса очень модульная. Вам необходимо получить данные вершин и любую другую информацию, которую вы намереваетесь использовать (например, цвета вершин, координаты текстуры и т. Д.) Вплоть до графического конвейера, используя униформу и атрибуты, которые определены в шейдере, но точную компоновку и присвоение имен эта информация очень зависит от разработчика.
JavaScript устанавливает исходные структуры данных и отправляет их в API WebGL, который отправляет их либо в ANGLE, либо в OpenGL ES, что в конечном итоге отправляет их графическому оборудованию.
Вершинные шейдеры
Как только информация становится доступной для шейдера, шейдер должен преобразовать информацию в 2 этапа, чтобы получить трехмерные объекты. Первый этап - вершинный шейдер, который устанавливает координаты сетки. (Этот этап полностью выполняется на видеокарте, ниже всех API, описанных выше.) Чаще всего процесс, выполняемый на вершинном шейдере, выглядит примерно так:
gl_Position = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX * VERTEX_POSITION
, где VERTEX_POSITION
- это четырехмерный вектор (x, y, z и w, который обычно равен 1); VIEW_MATRIX
- матрица 4x4, представляющая взгляд камеры на мир; MODEL_MATRIX
- матрица 4x4, которая преобразует координаты пространства объекта (то есть, координаты, локальные для объекта до применения поворота или перемещения) в координаты мирового пространства; и PROJECTION_MATRIX
, который представляет объектив камеры.
Чаще всего VIEW_MATRIX
и MODEL_MATRIX
предварительно вычисляются и называются MODELVIEW_MATRIX
.Иногда все 3 предварительно вычисляются в MODELVIEW_PROJECTION_MATRIX
или просто MVP
.Они обычно подразумевают оптимизацию, хотя я хотел бы найти время для некоторых тестов.Вполне возможно, что предварительные вычисления на самом деле медленнее в JavaScript, если они выполняются каждый кадр, потому что сам JavaScript не так уж и быстр.В этом случае аппаратное ускорение, обеспечиваемое математическим анализом на графическом процессоре, может быть быстрее, чем на процессоре в JavaScript.Мы, конечно, можем надеяться, что будущие реализации JS разрешат эту потенциальную ошибку, просто быстрее.
Координаты обрезки
Когда все они будут применены, переменная gl_Position
будетиметь набор координат XYZ в пределах [-1, 1] и компонент W.Они называются координатами клипа.
Стоит отметить, что координаты клипа - это единственное, что действительно необходимо для создания вершинного шейдера.Вы можете полностью пропустить преобразования матрицы, выполненные выше, при условии, что вы получите результат координат клипа.(Я даже экспериментировал с заменой матриц для кватернионов; это работало просто отлично, но я отказался от проекта, потому что не получил улучшения производительности, на которое я надеялся.)
После того, как вы поставили клипкоординаты gl_Position
WebGL делит результат на gl_Position.w
, получая так называемые нормализованные координаты устройства.Оттуда, проецируя пиксель на экран, нужно просто умножить на 1/2 размера экрана и затем добавить 1/2 размера экрана. [1] Вот несколько примеров координат клипа, переведенных в2D координаты на дисплее 800x600:
clip = [0, 0]
x = (0 * 800/2) + 800/2 = 400
y = (0 * 600/2) + 600/2 = 300
clip = [0.5, 0.5]
x = (0.5 * 800/2) + 800/2 = 200 + 400 = 600
y = (0.5 * 600/2) + 600/2 = 150 + 300 = 450
clip = [-0.5, -0.25]
x = (-0.5 * 800/2) + 800/2 = -200 + 400 = 200
y = (-0.25 * 600/2) + 600/2 = -150 + 300 = 150
Пиксельные шейдеры
Как только определено, где должен быть нарисован пиксель, пиксель передается в пиксельный шейдер, который выбирает фактический цветпиксель будет.Это можно сделать множеством способов, начиная от простого жесткого кодирования определенного цвета до поиска текстур и заканчивая более сложным отображением нормалей и параллакса (которые по сути являются способами «обмана» поиска текстур для получения различных эффектов).
Глубина и буфер глубины
Теперь, пока мы проигнорировали компонент Z координат клипа.Вот как это работает.Когда мы умножили матрицу проекции, третий компонент клипа привел к некоторому числу.Если это число больше 1,0 или меньше -1,0, то это число выходит за пределы диапазона просмотра матрицы проекции, что соответствует значениям матрицы zFar и zNear соответственно.
Так что, если оно не находится в диапазоне[-1, 1] тогда оно полностью обрезается.Если в этом диапазоне равен , то значение Z масштабируется от 0 до 1 [2] и сравнивается с буфером глубины [3] .Буфер глубины равен размерам экрана, поэтому при использовании проекции 800x600 буфер глубины имеет ширину 800 пикселей и высоту 600 пикселей.У нас уже есть координаты X и Y пикселя, поэтому они подключены к буферу глубины, чтобы получить текущее сохраненное значение Z.Если значение Z больше, чем новое значение Z, то новое значение Z на ближе , чем то, что было нарисовано ранее, и заменяет его [4] .В этот момент можно осветить рассматриваемый пиксель (или, в случае WebGL, нарисовать пиксель на холсте) и сохранить значение Z в качестве нового значения глубины.
Если значение Z больше , чем сохраненное значение глубины, тогда оно считается "позади" того, что уже было нарисовано, и пиксель отбрасывается.
[1] фактическое преобразование использует настройки gl.viewport
для преобразования из нормализованных координат устройства в пиксели.
[2] Фактически оно масштабируется до настроек gl.depthRange
.По умолчанию от 0 до 1.
[3] Предполагается, что у вас есть буфер глубины и вы включили тестирование глубины с помощью gl.enable(gl.DEPTH_TEST)
.
[4] Вы можете установить, как значения Z сравниваются с gl.depthFunc