Шейдеры

Те, кто знаком с традиционным OpenGL, могут подумать, что нам потребуется много кода для вывода треугольника. Однако WebGL основан на OpenGL 3.0, который использует шейдеры, что приводит к тому, что для вывода треугольника нужно заметно больше кода, чем при традиционном OpenGL. Поэтому сейчас мы будем говорить о шейдерах. Мы рассмотрим два основных шейдера: вершинный и фрагментный.

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

<script id="shader-fs" type="x-shader/x-fragment">
  void main(void) {
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
  }
</script>

Вершинный шейдер позволяет работать с вершинами объекта. Ниже приведён код простого вершинного шейдера, который определяет положение и форму каждой вершины.

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

Давайте рассмотрим процедуру initShaders(), которая загружает шейдеры и помещает их в HTML документ.

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("Не получилось инициилизировать шейдерную программу.");
  }

  gl.useProgram(shaderProgram);

  vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
  gl.enableVertexAttribArray(vertexPositionAttribute);
}

При помощи функции getShader(), которая будет рассмотрена ниже, мы получаем вершинный и фрагментный шейдеры.

Затем, используя функцию createProgram(), мы создаём шейдерную программу и сохраняем её в переменной shaderProgram. Вы можете удалить программу при помощи deleteProgram(program), где program это ссылка на программу которую надо удалить.

Используя функцию attachShader, присоединяем вершинный и фрагментный шейдеры к шейдерной программе. Обратите внимание, что шейдер может быть присоединён в любой момент. Кроме присоединения шейдера, вы также можете отсоединить шейдер при помощи detachShader(program, shader), где program это программа от которой нужно отсоединить шейдер, а shader это отсоединяемый шейдер.

После того как шейдеры были присоединены, мы можем собрать программу. Для этого служит функция linkProgram(). Затем, мы проверяем, получилось ли инициализировать шейдерную программу. Если инициализация прошла успешно, то мы можем использовать функцию useProgram(), чтобы сделать программу текущей.

Из приложения мы можем получить индекс атрибута, к которому привязана переменная с заданным именем, при помощи функции getAttribLocation(). При помощи функции enableVertexAttribArray(), мы включаем массив атрибутов вершин в список массивов атрибутов.

Рассмотрим функцию getShader(). Сначала мы ищем элемент с заданным идентификатором. Если он нашёлся, то преобразуем его в строку. Затем определяем тип шейдера и при помощи функции createShader() создаём шейдер заданного типа. Следующим шагом мы задаём исходный код для этого шейдера при помощи shaderSource().

После того как исходный код для шейдера был задан, следующим шагом будет компиляция шейдера при помощи функции compileShader(). Вызовом функции getShaderParameter(), мы узнаём были ли какие-то ошибки.

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;
  }

  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;
  }
  gl.shaderSource(shader, theSource);

  gl.compileShader(shader);

  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      alert("Ошибка компиляции шейдера: " + gl.getShaderInfoLog(shader));
      return null;
  }

  return shader;
}

Функция getShader() возвращает скомпилированный шейдер. Если произошли ошибки, то появится окно с предупреждением.

  < НАЗАД | ДАЛЕЕ >