Эта статья предполагает, что вы прочитали многие другие статьи, начиная с основ. Если вы их не читали, пожалуйста, начните сначала с них.
Я не совсем знаю, под что подвести эту статью, потому что у неё есть две цели.
Показать вам самые маленькие WebGL программы.
Эти техники супер полезны для тестирования чего-то или при создании MCVE для Stack Overflow или при попытке сузить ошибку.
Обучение думать нестандартно
Я надеюсь написать еще несколько статей на эту тему, чтобы помочь вам увидеть большую картину, а не только общие паттерны. Вот одна.
Вот самая маленькая 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
Но иногда вы просто хотите протестировать. Допустим, вы хотите просто увидеть, как что-то рисуется.
Как насчет этого набора шейдеров
#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 это отличный способ минимизировать код. Как другой пример, допустим, мы рисуем в текстуры для постобработки эффекта, и мы хотим их визуализировать. Мы могли бы просто нарисовать одну большую точку для каждой, используя комбинацию этого примера и предыдущего с текстурой. Никаких сложных шагов буферов и атрибутов не нужно, отлично для отладки.