Написать пост

Основы WebGL: разбираемся в магическом коде и заливаем на хостинг

Аватар Типичный программист

Обложка поста Основы WebGL: разбираемся в магическом коде и заливаем на хостинг

WebGL — технология, которая «создает магию» в 2D-canvas’е HTML5. Рассказываем, как за счет градиента добиться такого интересного 3D-эффекта, как выпуклость, не используя дополнительных библиотек и подробно объяснив всю «магию». По завершению работы с кодом мы зальем наш проект на хостинг GPDHOST, чтобы вы могли поделиться результатом со знакомыми.

Код

Начнем с примера: нарисуем прямоугольник на экране. Этот код должен срендерить прямоугольник с градиентом:

			<canvas id="canvas" width="400" height="300"></canvas>
<script type="vertex">
  attribute vec2 a_pos;
  varying vec2 v_pos;
  void main() {
      v_pos = vec2(a_pos.xy);
      gl_Position = vec4(a_pos, 0, 1);
  }
</script>
<script type="fragment">
  precision highp float;
  varying vec2 v_pos;
  void main() {
    gl_FragColor = vec4(v_pos, 0.0, 1.0);
  }
</script>
<script>
  var canvas = document.getElementById("canvas");
  var gl = canvas.getContext('experimental-webgl');
  if (!gl) throw 'webgl сломался';

  try {
    var program = gl.createProgram();

    // вершинный шейдер: сопоставление пикселей, установка gl_Position в качестве vec4
    var vertShaderObj = gl.createShader(gl.VERTEX_SHADER);
    var vertexShaderSrc = document.querySelector('[type="vertex"]').textContent;
    gl.shaderSource(vertShaderObj, vertexShaderSrc);
    gl.compileShader(vertShaderObj);
    gl.attachShader(program, vertShaderObj);

    // фрагментный шейдер: раскраска пикселей, установка gl_FragColor в качестве vec4
    var fragShaderObj = gl.createShader(gl.FRAGMENT_SHADER);
    var fragmentShaderSrc = document.querySelector('[type="fragment"]').textContent;
    gl.shaderSource(fragShaderObj, fragmentShaderSrc);
    gl.compileShader(fragShaderObj);
    gl.attachShader(program, fragShaderObj);

    gl.linkProgram(program);
    gl.useProgram(program);
    if (!gl.getShaderParameter(vertShaderObj, gl.COMPILE_STATUS)) throw new Error("Could not compile shader: " + gl.getShaderInfoLog(vertShaderObj));
    if (!gl.getShaderParameter(fragShaderObj, gl.COMPILE_STATUS)) throw new Error("Could not compile shader: " + gl.getShaderInfoLog(fragShaderObj));
  } catch(e) {
    console.error(e && e.stack || e);
  }

  var positionLocation = gl.getAttribLocation(program, "a_pos");

  var buffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    // прямоугольники
    -1.0, -1.0,
    1.0, -1.0,
    -1.0, 1.0,

    -1.0, 1.0,
    1.0, -1.0,
    1.0, 1.0]), gl.STATIC_DRAW);
  gl.enableVertexAttribArray(positionLocation);
  gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);

  // рисование
  gl.drawArrays(gl.TRIANGLES, 0, 6);
</script>
		

На самом деле, код рисует два треугольника, X и Y координаты каждого пикселя используются как R и G значения цвета, а цвета внутри треугольников экстраполируются и в результате получается прямоугольник с градиентом.

Что происходит?

Мы создаем canvas объект, который используется для рендеринга, а также два «странных» тега script. В качестве типа у этих тегов вы можете указать все, что угодно — главное, чтобы вы могли затем как-то извлечь эти элементы из DOM через JS. Важно указать такой тип, который не будет распознаваться браузерами как «javascript». Это позволит нам добавить невидимое многострочное содержимое в документ, с которым мы сможем работать позже. В общем, эти теги не имеют какого-то специального значения, это просто такой трюк, чтобы добавить нужную нам информацию в JS.

В коде мы видим два таких тега script. Их содержимое принадлежит двум разным шейдерам, с помощью которых мы будем «делать магию».

О шейдерах

Простыми словами, шейдеры позволяют сопоставлять входные пиксели с выходными пикселями и пиксели с выходными цветами. В нашем случае есть два шейдера: вершинный (vertex) шейдер, который сопоставляет входные пиксели их координатам, и фрагментный (fragment) шейдер, который сопоставляет пиксели с их цветами.

В шейдерах существует 4 основных типа «переменных». Первый — это uniform. Вы можете определить такую переменную с помощью JS, но в шейдере она будет read-only. Второй тип это attribute, который, условно говоря, используется для получения определенных атрибутов точки. Третий — varying тип, он позволяет нам пересылать данные с одного шейдера в другой. Четвертый и последний это const, он позволяет создавать константы.

Кроме них есть ещё float для чисел с плавающей запятой, есть типы vec1, vec2, vec3 и vec4 для векторов, которые на самом деле являются просто списком чисел с плавающей запятой. Есть также типы xyz, rgba и тд. Очевидно, они не имеют особых различий, поэтому не будем рассматривать их подробно.

Как работает написанный выше код

JS-код начинается с простой инициализации и начальной загрузки: получения canvas, получения gl-контекста и т.д.

Сначала мы компилируем оба шейдеры в одну «программу»:

			var vertShaderObj = gl.createShader(gl.VERTEX_SHADER);
var vertexShaderSrc = document.querySelector('[type="vertex"]').textContent;
gl.shaderSource(vertShaderObj, vertexShaderSrc);
gl.compileShader(vertShaderObj);
gl.attachShader(program, vertShaderObj);
		

После компиляции проверяем, скомпилировались шейдеры или нет. Вы можете использовать что-то вроде:

			if (!gl.getShaderParameter(vertShaderObj, gl.COMPILE_STATUS))
  console.error("Could not compile shader: " + gl.getShaderInfoLog(vertShaderObj));
		

Так вы сможете получить больше информации о том, почему компиляция завершилась неуспешно. Для отладки желательно использовать Firefox, так как он выдает больше подробностей об ошибке, чем Chrome.

Функции main шейдеров не возвращают каких-либо значений. Вместо этого они должны устанавливать переменную, которую перехватит WebGL. Тем не менее, для удобства будем называть эту переменную возвращаемым значением функции. Например, вершинный шейдер должен установить переменную gl_Position, чтобы сообщить WebGL, какой именно пиксель canvas’а мы сейчас рисуем. Фрагментный шейдер должен установить переменную fl_FragColor, которая назначает цвет пикселя в формате RGBA. Устанавливаемое значение этих переменных может быть любым в диапазоне от −1.0 до 1.0.

var positionLocation = gl.getAttribLocation(program, "a_pos"); — это получение побайтового смещения для переменной a_pos в скомпилированной программе, которое нужно для установления значения positionLocation.

Далее создается буфер из точек, которые мы будем использовать в качестве параметров для рисования. Мы помещаем эти точки в буфер и говорим WebGL, как их интерпретировать:

			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
    // два прямоугольника
    -1.0, -1.0,
    1.0, -1.0,
    -1.0, 1.0,

    -1.0, 1.0,
    1.0, -1.0,
    1.0, 1.0]), gl.STATIC_DRAW);
		

Эта «строка» кода помещает координаты двух прямоугольников в буфер. Экранные координаты перечислены от −1 к 1, с верхнего левого угла к нижнему правому. То есть 12 значений выше — это, на самом деле, 6 координат вершин (<-1,-1>, <1,-1>, <-1,1>, <-1,1>, <1,-1>, <1,1>). STATIC_DRAW — это параметр для оптимизации, который для нас пока не очень важен.

Теперь мы сообщим WebGL, как использовать эти точки:

			gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
		

Здесь мы связываем каждую точку с a_post и переводим каждые два числа в число с плавающей запятой.

Само рисование треугольников происходит в последней строке: gl.drawArrays(gl.TRIANGLES, 0, 6);. Здесь три каждые три вектора объединяются в треугольники. Разберемся подробнее непосредственно в процедуре рисования.

Как WebGL «рисует»

Метод drawArrays работает с буфером, который мы подготовили: рисует из треугольник из трех пар координат.

Во время этого он применяет main-функцию каждого шейдера для каждой из координат. Вершинный шейдер передает эти координаты в gl_Position, устанавливая z равным 0 и w равным 1. Он также кладет координаты в varying переменные для использования во фрагментном шейдере. Фрагментный шейдер использует входные координаты, чтобы установить RGBA-значение переменной gl_FragColor: он берет vec2-координаты и создает из них vec4 для применения в качестве RGBA значения (r=x, g=y, b=0, a=1).

На самом деле мы всего лишь сообщаем WebGL, что нужно нарисовать те 6 точек (3 на каждый треугольник) и экстраполировать цвета между каждой, чтобы получить градиент. Та как мы рисуем три треугольника, они заполняют всю область просмотра. Никто реально не рисует прямоугольники, а составляет их из треугольников — это полезно для простоты и оптимизации.

Вот так будет выглядеть результат выполнения нашего кода:

Давайте теперь поделимся своим успехом с миром и зальем наше творчество на хостинг!

Размещение проекта на хостинге

Чтобы разместить наш проект, воспользуемся хостингом GPDHOST. Для нашего несложного эксперимента нам подойдет план Starter. Для удобства войдите на сайт — например, через аккаунт Google, а затем добавьте план Starter в корзину. Далее вам будет предложено выбрать доменное имя — выберите понравившееся вам. Любой один домен в зоне .site, .party или .pro предоставляется GPDHOST бесплатно.

После получения доступа к контрольной панели хостинга, можете переходить к размещению веб-страницы с JS-кодом на сайте. В разделе «Quick Server Logins» кликните по имени выбранного вами домена, и далее в «Quick Shortcuts» выберите пункт «File Manager». Вам откроется такое окно:

Основы WebGL: разбираемся в магическом коде и заливаем на хостинг 2

«Видимые» посетителям вашего сайта HTML-страницы должны располагаться в папке public_html, выберите ее в дереве каталогов слева. Далее кликните на «Upload» в верхней части страницы и загрузите файл index.html. Чтобы он корректно отображался при открытии вашего сайта, он должен называться именно index.html. Затем вернитесь на страницу, где был расположен раздел «Quick Shortcuts», и чуть выше этого раздела кликните «Visit Website» — теперь вы увидите на главной странице вашего сайта canvas с эффектом выпуклости.

Поздравляем, теперь вы можете поделиться своим проектом! Продолжайте изучать тему, чтобы дать своему проекту интересные пути развития.

Следите за новыми постами
Следите за новыми постами по любимым темам
8К открытий8К показов