оглавление

WebGLFundamentals.org

Fix, Fork, Contribute

Небольшие программы WebGL

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

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

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

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

  2. Учимся мыслить нестандартно.

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

Просто очищение

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

const gl = document.querySelector('canvas').getContext('webgl');
gl.clearColor(1, 0, 0, 1);  // red
gl.clear(gl.COLOR_BUFFER_BIT);

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

Подумайте об этом. Только с помощью этого вы действительно можете протестировать некоторые вещи. Допустим, вы выполняете рендеринг текстуры, но что-то не работает. Допустим, это так же, как пример в этой статье. Вы визуализируете один или несколько трехмерных объектов в текстуру, а затем визуализируете результат в куб.

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

gl.bindFramebuffer(gl.FRAMEBUFFER, framebufferWithTexture)
gl.clearColor(1, 0, 1, 1);  // magenta
gl.clear(gl.COLOR_BUFFER_BIT);

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

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

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

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

gl.enable(gl.SCISSOR_TEST);

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

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

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

Пример

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

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 является создание буферов. Поместите данные вершин в эти буферы. Создавайте шейдеры с атрибутами. Настройте атрибуты для извлечения данных из этих буферов. Затем нарисуйте, возможно, с униформой и текстурой, также используемыми вашими шейдерами.

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

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

// vertex shader
void main() {
  gl_Position = vec4(0, 0, 0, 1);  // center
  gl_PointSize = 120.0;
}
// fragment shader
precision mediump float;

void main() {
  gl_FragColor = vec4(1, 0, 0, 1);  // red
}

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

// setup GLSL program
const program = webglUtils.createProgramFromSources(gl, [vs, fs]);

gl.useProgram(program);

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

Не нужно создавать буферы, не нужно настраивать униформы, и мы получаем одну точку в середине холста.

ПРИМЕЧАНИЕ. Safari до 15 версии не прошел тесты на соответствие WebGL для этой функции.

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

gl.POINTS в gl.drawArrays, gl_PointSize

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

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

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

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

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

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

// fragment shader
precision mediump float;

+uniform sampler2D tex;

void main() {
-  gl_FragColor = vec4(1, 0, 0, 1);  // red
+  gl_FragColor = texture2D(tex, gl_PointCoord.xy);
}

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

// 2x2 pixel data
const pixels = new Uint8Array([
  0xFF, 0x00, 0x00, 0xFF,  // red
  0x00, 0xFF, 0x00, 0xFF,  // green
  0x00, 0x00, 0xFF, 0xFF,  // blue
  0xFF, 0x00, 0xFF, 0xFF,  // magenta
]);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
    gl.TEXTURE_2D,
    0,                 // level
    gl.RGBA,           // internal format
    2,                 // width
    2,                 // height
    0,                 // border
    gl.RGBA,           // format
    gl.UNSIGNED_BYTE,  // type
    pixels,            // data
);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);

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

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

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

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

// vertex shader

+attribute vec4 position;

void main() {
-  gl_Position = vec4(0, 0, 0, 1);
+  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 numPoints = 5;
+for (let i = 0; i < numPoints; ++i) {
+  const u = i / (numPoints - 1);    // 0 to 1
+  const clipspace = u * 1.6 - 0.8;  // -0.8 to +0.8
+  gl.vertexAttrib2f(positionLoc, clipspace, clipspace);

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

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

// vertex shader

attribute vec4 position;
+uniform float size;

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

И давайте сделаем так, чтобы мы могли установить цвет точки. (примечание: я вернулся к коду без текстуры).

precision mediump float;

+uniform vec4 color;

void main() {
-  gl_FragColor = vec4(1, 0, 0, 1);   // red
+  gl_FragColor = color;
}

и нам нужно найти местоположение цвета

// setup GLSL program
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 to 1
  const clipspace = u * 1.6 - 0.8;  // -0.8 to +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 это отличный способ минимизировать код. В качестве другого примера предположим, что мы рисуем текстуры для постобработки и хотим их визуализировать. Мы могли бы просто нарисовать по одной большой точке для каждой, используя комбинацию этого примера и предыдущего с текстурой. Не требуется никаких сложных действий с буферами и атрибутами, что отлично подходит для отладки.

Нашли ошибку? Создайте задачу на github.
comments powered by Disqus