Содержание

WebGL2Fundamentals.org

Fix, Fork, Contribute

WebGL2 Самые маленькие программы

Эта статья предполагает, что вы прочитали многие другие статьи, начиная с основ. Если вы их не читали, пожалуйста, начните сначала с них.

Я не совсем знаю, под что подвести эту статью, потому что у неё есть две цели.

  1. Показать вам самые маленькие WebGL программы.

    Эти техники супер полезны для тестирования чего-то или при создании MCVE для Stack Overflow или при попытке сузить ошибку.

  2. Обучение думать нестандартно

    Я надеюсь написать еще несколько статей на эту тему, чтобы помочь вам увидеть большую картину, а не только общие паттерны. Вот одна.

Просто очистка

Вот самая маленькая WebGL программа, которая действительно что-то делает

const gl = document.querySelector('canvas').getContext('webgl2');
gl.clearColor(1, 0, 0, 1);  // красный
gl.clear(gl.COLOR_BUFFER_BIT);

Все, что делает эта программа - очищает canvas до красного, но она действительно что-то сделала.

Подумайте об этом. С помощью только этого вы можете фактически тестировать некоторые вещи. Допустим, вы рендерите в текстуру, но что-то не работает. Допустим, это точно как в примере в той статье. Вы рендерите 1 или более 3D вещей в текстуру, затем рендерите этот результат на куб.

Вы ничего не видите. Ну, как простой тест, остановите рендеринг в текстуру с шейдерами и просто очистите текстуру до известного цвета.

gl.bindFramebuffer(gl.FRAMEBUFFER, framebufferWithTexture)
gl.clearColor(1, 0, 1, 1);  // пурпурный
gl.clear(gl.COLOR_BUFFER_BIT);

Теперь рендерите с текстурой из framebuffer. Ваш куб становится пурпурным? Если нет, то ваша проблема не в части рендеринга в текстуру, это что-то другое.

Использование SCISSOR_TEST и gl.clear

SCISSOR_TEST обрезает как рисование, так и очистку до некоторого подпрямоугольника canvas (или текущего framebuffer).

Вы включаете тест ножниц с помощью

gl.enable(gl.SCISSOR_TEST);

и затем вы устанавливаете прямоугольник ножниц в пикселях относительно нижнего левого угла. Он использует те же параметры, что и gl.viewport.

gl.scissor(x, y, width, height);

Используя это, можно рисовать прямоугольники с помощью SCISSOR_TEST и gl.clear.

Пример

const gl = document.querySelector('#c').getContext('webgl2');

gl.enable(gl.SCISSOR_TEST);

function drawRect(x, y, width, height, color) {
  gl.scissor(x, y, width, height);
  gl.clearColor(...color);
  gl.clear(gl.COLOR_BUFFER_BIT);
}

for (let i = 0; i < 100; ++i) {
  const x = rand(0, 300);
  const y = rand(0, 150);
  const width = rand(0, 300 - x);
  const height = rand(0, 150 - y);
  drawRect(x, y, width, height, [rand(1), rand(1), rand(1), 1]);
}


function rand(min, max) {
  if (max === undefined) {
    max = min;
    min = 0;
  }
  return Math.random() * (max - min) + min;
}

Не говорю, что этот конкретный пример очень полезен, но все же хорошо знать.

Использование одной большой gl.POINTS

Как показывают большинство примеров, самая распространенная вещь для выполнения в WebGL

  • это создание буферов. Поместить данные вершин в эти буферы. Создать шейдеры с атрибутами. Настроить атрибуты для извлечения данных из этих буферов. Затем рисовать, возможно, с uniforms и текстурами, также используемыми вашими шейдерами.

Но иногда вы просто хотите протестировать. Допустим, вы хотите просто увидеть, как что-то рисуется.

Как насчет этого набора шейдеров

#version 300 es
// вершинный шейдер
void main() {
  gl_Position = vec4(0, 0, 0, 1);  // центр
  gl_PointSize = 120.0;
}
#version 300 es
// фрагментный шейдер
precision highp float;

out vec4 outColor;

void main() {
  outColor = vec4(1, 0, 0, 1);  // красный
}

И вот код для его использования

// настройка GLSL программы
const program = webglUtils.createProgramFromSources(gl, [vs, fs]);

gl.useProgram(program);

const offset = 0;
const count = 1;
gl.drawArrays(gl.POINTS, offset, count);

Никаких буферов для создания, никаких uniforms для настройки, и мы получаем одну точку в центре canvas.

О gl.POINTS: Когда вы передаете gl.POINTS в gl.drawArrays, вы также обязаны установить gl_PointSize в вашем вершинном шейдере в размер в пикселях. Важно отметить, что разные GPU/Драйверы имеют разный максимальный размер точки, который вы можете использовать. Вы можете запросить этот максимальный размер с помощью

const [minSize, maxSize] = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE);

Спецификация WebGL требует только максимальный размер 1.0. К счастью, большинство, если не все GPU и драйверы поддерживают больший размер.

После того, как вы установите gl_PointSize, когда вершинный шейдер завершится, любое значение, которое вы установили на gl_Position, преобразуется в экранное/canvas пространство в пикселях, затем генерируется квадрат вокруг этой позиции, который составляет +/- gl_PointSize / 2 во всех 4 направлениях.

Хорошо, я слышу, как вы думаете, ну и что, кто хочет рисовать одну точку.

Ну, точки автоматически получают бесплатные координаты текстуры. Они доступны во фрагментном шейдере с специальной переменной gl_PointCoord. Итак, давайте нарисуем текстуру на этой точке.

Сначала давайте изменим фрагментный шейдер.

#version 300 es
// фрагментный шейдер
precision highp float;

uniform sampler tex;

out vec4 outColor;

void main() {
  outColor = texture(tex, gl_PointCoord.xy);
}

Теперь, чтобы держать это простым, давайте сделаем текстуру с сырыми данными, как мы покрыли в статье о текстурах данных.

// 2x2 пиксельные данные
const pixels = new Uint8Array([
  0xFF, 0x00, 0x00, 0xFF,  // красный
  0x00, 0xFF, 0x00, 0xFF,  // зеленый
  0x00, 0x00, 0xFF, 0xFF,  // синий
  0xFF, 0x00, 0xFF, 0xFF,  // пурпурный
]);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
    gl.TEXTURE_2D,
    0,                 // уровень
    gl.RGBA,           // внутренний формат
    2,                 // ширина
    2,                 // высота
    0,                 // граница
    gl.RGBA,           // формат
    gl.UNSIGNED_BYTE,  // тип
    pixels,            // данные
);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

Поскольку WebGL по умолчанию использует текстуру 0 и поскольку uniforms по умолчанию равны 0, больше ничего настраивать не нужно

Это может быть отличным способом тестирования проблем, связанных с текстурами. Мы все еще не используем буферы, атрибуты, и нам не пришлось искать и устанавливать никаких uniforms. Например, если мы загрузили изображение, оно не отображается. Что если мы попробуем шейдер выше, показывает ли он изображение на точке? Мы рендерили в текстуру, а затем хотим просмотреть текстуру. Обычно мы бы настроили некоторую геометрию через буферы и атрибуты, но мы можем рендерить текстуру просто показывая её на этой единственной точке.

Использование множественных одиночных POINTS

Еще одно простое изменение к примеру выше. Мы можем изменить вершинный шейдер на этот

#version 300 es
// вершинный шейдер

in vec4 position;

void main() {
  gl_Position = position;
  gl_PointSize = 120.0;
}

атрибуты имеют значение по умолчанию 0, 0, 0, 1, поэтому с этим изменением примеры выше все еще будут продолжать работать. Но теперь мы получаем возможность установить позицию, если захотим.

const program = webglUtils.createProgramFromSources(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const colorLoc = gl.getUniformLocation(program, 'color');

И использовать их

gl.useProgram(program);

const numPoints = 5;
for (let i = 0; i < numPoints; ++i) {
  const u = i / (numPoints - 1);    // 0 до 1
  const clipspace = u * 1.6 - 0.8;  // -0.8 до +0.8
  gl.vertexAttrib2f(positionLoc, clipspace, clipspace);

  gl.uniform4f(colorLoc, u, 0, 1 - u, 1);

  const offset = 0;
  const count = 1;
  gl.drawArrays(gl.POINTS, offset, count);
}

И теперь мы получаем 5 точек с 5 цветами и мы все еще не должны были настраивать никакие буферы или атрибуты.

Конечно, это НЕ способ, которым вы должны рисовать много точек в WebGL. Если вы хотите рисовать много точек, вы должны сделать что-то вроде настройки атрибута с позицией для каждой точки и цветом для каждой точки и рисовать все точки в одном вызове отрисовки.

НО!, для тестирования, для отладки, для создания MCVE это отличный способ минимизировать код. Как другой пример, допустим, мы рисуем в текстуры для постобработки эффекта, и мы хотим их визуализировать. Мы могли бы просто нарисовать одну большую точку для каждой, используя комбинацию этого примера и предыдущего с текстурой. Никаких сложных шагов буферов и атрибутов не нужно, отлично для отладки.

Есть предложения или замечания? Создайте issue на GitHub.
comments powered by Disqus