новый ответ
Я просто повторяю то же самое снова (в третий раз?)
Скопировано снизу
WebGL основан на пункте назначения. Это означает, что он будет перебирать пиксели линии / точки / треугольника, которые он рисует, и для каждой точки вызывать фрагментный шейдер и спрашивать «какое значение я должен хранить здесь»?
Это пункт назначения . Каждый пиксель будет нарисован ровно один раз. Для этого пикселя будет задан вопрос «какого цвета мне сделать это»
цикл на основе пункта назначения
for (let i = start; i < end; ++i) {
fragmentShaderFunction(); // must set gl_FragColor
destinationTextureOrCanvas[i] = gl_FragColor;
Вы можете видеть в вышеприведенном цикле, что нет никакого случайного назначения. Там нет установки какой-либо части назначения дважды. Он просто будет работать с start
до end
и ровно один раз для каждого пикселя в пункте назначения между началом и концом спрашивать, какого цвета должен быть этот пиксель.
Как настроить начало и конец? Опять же, чтобы упростить задачу, давайте примем текстуру 200x1, чтобы мы могли игнорировать Y. Она работает следующим образом
vertexShaderFunction(); // must set gl_Position
const start = clipspaceToArrayspaceViaViewport(viewport, gl_Position.x);
vertexShaderFunction(); // must set gl_Position
const end = clipspaceToArrayspaceViaViewport(viewport, gl_Position.x);
for (let i = start; i < end; ++i) {
fragmentShaderFunction(); // must set gl_FragColor
texture[i] = gl_FragColor;
}
см. Ниже для clipspaceToArrayspaceViaViewport
Что такое viewport
? viewport
- это то, что вы устанавливаете, когда вызываете `gl.viewport (x, y, width, height)
Итак, установите gl_Position.x
в -1 и +1, viewport.x в 0 и viewport.width = 200 (ширина текстуры), тогда start
будет 0, end
будет 200
установите gl_Position.x
в .25 и .75, viewport.x в 0 и viewport.width = 200 (ширина текстуры). start
будет 125, а end
будет 175
Честно говоря, я чувствую, что этот ответ ведет вас по неверному пути. Это не так сложно. Вам не нужно понимать это, чтобы использовать WebGL IMO.
Простой ответ:
Вы задаете для gl.viewport под прямоугольник, на который хотите повлиять в пункте назначения (холст или текстура это не имеет значения)
Вы создаете вершинный шейдер, который каким-то образом устанавливает gl_Position
для обрезки пространственных координат (они идут от -1 до +1) по текстуре
Эти координаты пространства клипа преобразуются в пространство области просмотра. Это базовая математика для отображения одного диапазона в другой , но в основном это , не важно . Кажется интуитивно понятным, что -1 будет рисовать до viewport.x
пикселя, а +1 - до viewport.x + viewport.width - 1
пикселя. Вот что означает «отображение из пространства клипа в настройках области просмотра».
Чаще всего в настройках области просмотра обычно (x = 0, y = 0, ширина = ширина целевой текстуры или холста, высота = высота целевой текстуры или холста)
Так что остается только то, что вы установили gl_Position
. Эти значения находятся в пространстве клипа , как это объясняется в этой статье .
Вы можете сделать это просто, если хотите, преобразовав из пиксельного пространства в пространство клипа , как это объясняется в этой статье
zeroToOne = someValueInPixels / destinationDimensions;
zeroToTwo = zeroToOne * 2.0;
clipspace = zeroToTwo - 1.0;
gl_Position = clipspace;
Если вы продолжите статьи, они также покажут добавление значения (перевод) и умножение на значение (масштаб)
Используя только эти 2 вещи и единицу площади (от 0 до 1), вы можете выбрать любой прямоугольник на экране. Вы хотите увеличить от 123 до 127. Это 5 единиц, поэтому масштаб = 5, перевод = 123. Затем примените приведенную выше математическую схему для преобразования пикселей в пространство клипов, и вы получите нужный прямоугольник.
Если продолжить эти статьи, вы в конечном итоге получите точку, в которой эта математика сделана с матрицами , но вы можете делать эту математику как хотите. Это все равно что спросить «как мне вычислить значение 3». Ну, 1 + 1 + 1, или 3 + 0, или 9/3, или 100 - 50 + 20 * 2/30, или (7 ^ 2 - 19) / 10, или ????
Я не могу сказать вам, как установить gl_Position
. Я могу только сказать вам make up whatever math you want and set it to *clip space*
, а затем привести пример преобразования пикселей в пространство клипа (см. Выше) в качестве одного из примеров возможной математики.
старый ответ
Я понимаю, что это может быть не ясно, я не знаю, как помочь.WebGL рисует линии, точки или треугольники в двухмерном массиве.Этот 2D-массив представляет собой холст, текстуру (как вложение кадрового буфера) или рендер-буфер (как вложение кадрового буфера).
Размер области определяется размером холста, текстуры, рендеринга буфера.
Вы пишете вершинный шейдер.Когда вы звоните gl.drawArrays(primitiveType, offset, count)
, вы говорите WebGL вызывать ваш вершинный шейдер count
раз.Если предположить, что primitiveType равен gl.TRIANGLES
, то для каждых 3 вершин, сгенерированных вашим вершинным шейдером, WebGL нарисует треугольник.Вы указываете этот треугольник, устанавливая gl_Position
в пространство клипа .
Предполагая, что gl_Position.w
равен 1, Пространство клипа изменяется от -1 до +1 в X и Y по целевому холсту / текстуре / визуализирующему буферу.(gl_Position.x и gl_Position.y делятся на gl_Position.w
), что не очень важно для вашего случая.
Чтобы преобразовать обратно в фактически пиксели, ваши X и Y конвертируются на основе настроек gl.viewport
,Давайте просто сделаем X
pixelX = ((clipspace.x / clipspace.w) * .5 + .5) * viewport.width + viewport.x
WebGL на основе пункта назначения.Это означает, что он будет перебирать пиксели линии / точки / треугольника, которые он рисует, и для каждой точки вызывать фрагментный шейдер и спрашивать «какое значение я должен хранить здесь»?
Давайте переведем это в JavaScript в 1D.Предположим, у вас есть 1D массив
const dst = new Array(100);
Давайте создадим функцию, которая принимает начало и конец и устанавливает значения в диапазоне
function setRange(dst, start, end, value) {
for (let i = start; i < end; ++i) {
dst[i] = value;
}
}
. Вы можете заполнить весь массив из 100 элементов 123
const dst = new Array(100);
setRange(dst, 0, 99, 123);
Чтобы установить в последней половине массива значение 456
const dst = new Array(100);
setRange(dst, 50, 99, 456);
Давайте изменим это, чтобы использовать пространство клипа как координаты
function setClipspaceRange(dst, clipStart, clipEnd, value) {
const start = clipspaceToArrayspace(dst, clipStart);
const end = clipspaceToArrayspace(dst, clipEnd);
for (let i = start; i < end; ++i) {
dst[i] = value;
}
}
function clipspaceToArrayspace(array, clipspaceValue) {
// convert clipspace value (-1 to +1) to (0 to 1)
const zeroToOne = clipspaceValue * .5 + .5;
// convert zeroToOne value to array space
return Math.floor(zeroToOne * array.length);
}
Эта функция теперь работает простокак и предыдущий, за исключением того, что вместо индексов массива используются значения пространства клипа
// fill entire array with 123
const dst = new Array(100);
setClipspaceRange(dst, -1, +1, 123);
Установите для последней половины массива значение 456
setClipspaceRange(dst, 0, +1, 456);
Теперь абстрагируйте еще раз.Вместо использования длины массива используйте настройку
// viewport looks like `{ x: number, width: number} `
function setClipspaceRangeViaViewport(dst, viewport, clipStart, clipEnd, value) {
const start = clipspaceToArrayspaceViaViewport(viewport, clipStart);
const end = clipspaceToArrayspaceViaViewport(viewport, clipEnd);
for (let i = start; i < end; ++i) {
dst[i] = value;
}
}
function clipspaceToArrayspaceViaViewport(viewport, clipspaceValue) {
// convert clipspace value (-1 to +1) to (0 to 1)
const zeroToOne = clipspaceValue * .5 + .5;
// convert zeroToOne value to array space
return Math.floor(zeroToOne * viewport.width) + viewport.x;
}
Теперь, чтобы заполнить весь массив 123
const dst = new Array(100);
const viewport = { x: 0, width: 100; }
setClipspaceRangeViaViewport(dst, viewport, -1, 1, 123);
Установите для последней половины массива значение 456, теперь есть 2 способа.Первый способ такой же, как и предыдущий, с 0 до + 1
setClipspaceRangeViaViewport(dst, viewport, 0, 1, 456);
Вы также можете настроить область просмотра так, чтобы она начиналась на полпути через массив
const halfViewport = { x: 50, width: 50; }
setClipspaceRangeViaViewport(dst, halfViewport, -1, +1, 456);
Я не знаю, так ли этобыло полезно или нет.
Единственное, что нужно добавить - вместо value
заменить это функцией, которая вызывается при каждой итерации для предоставления value
function setClipspaceRangeViaViewport(dst, viewport, clipStart, clipEnd, fragmentShaderFunction) {
const start = clipspaceToArrayspaceViaViewport(viewport, clipStart);
const end = clipspaceToArrayspaceViaViewport(viewport, clipEnd);
for (let i = start; i < end; ++i) {
dst[i] = fragmentShaderFunction();
}
}
Обратите внимание, что этото же самое, что сказано в этой статье и несколько прояснено в этой статье .