diff --git a/webgl/lessons/ru/webgl-2d-matrices.md b/webgl/lessons/ru/webgl-2d-matrices.md
index d66886f0b..367819bdf 100644
--- a/webgl/lessons/ru/webgl-2d-matrices.md
+++ b/webgl/lessons/ru/webgl-2d-matrices.md
@@ -120,7 +120,7 @@ c = Math.cos(angleToRotateInRadians);
+
Что такое clientWidth
и clientHeight
?
+
До этого момента, когда я ссылался на размеры холста,
+я использовал canvas.width
и canvas.height
,
+но выше, когда я вызывал m3.projection
, я вместо этого использовал
+canvas.clientWidth
и canvas.clientHeight
.
+Почему?
+
Матрицы проекции касаются того, как взять clip space
+(-1 до +1 в каждом измерении) и конвертировать его обратно
+в пиксели. Но в браузере есть 2 типа пикселей, с которыми мы
+имеем дело. Один - это количество пикселей в
+самом холсте. Так, например, холст, определенный так.
+
+ <canvas width="400" height="300"></canvas>
+
+
или один, определенный так
+
+ var canvas = document.createElement("canvas");
+ canvas.width = 400;
+ canvas.height = 300;
+
+
оба содержат изображение 400 пикселей в ширину на 300 пикселей в высоту.
+Но этот размер отделен от того размера,
+который браузер фактически отображает этот 400x300 пиксельный холст.
+CSS определяет, какого размера отображается холст.
+Например, если мы сделали холст так.
+
+ <style>
+ canvas {
+ width: 100%;
+ height: 100%;
+ }
+ </style>
+ ...
+ <canvas width="400" height="300"></canvas>
+
+
Холст будет отображаться любого размера, каким является его контейнер.
+Это, вероятно, не 400x300.
+
Вот два примера, которые устанавливают CSS размер отображения холста на
+100%, так что холст растягивается,
+чтобы заполнить страницу. Первый использует canvas.width
+и canvas.height
. Откройте его в новом
+окне и измените размер окна. Обратите внимание, как 'F'
+не имеет правильного соотношения сторон. Она искажается.
+{{{example url="../webgl-canvas-width-height.html" width="500" height="150" }}}
+
В этом втором примере мы используем canvas.clientWidth
+и canvas.clientHeight
. canvas.clientWidth
+и canvas.clientHeight
сообщают
+размер, который холст фактически отображается браузером, так что
+в этом случае, даже хотя холст все еще имеет только 400x300 пикселей,
+поскольку мы определяем наше соотношение сторон на основе размера, который холст
+отображается, F
всегда выглядит правильно.
+{{{example url="../webgl-canvas-clientwidth-clientheight.html" width="500" height="150" }}}
+
Большинство приложений, которые позволяют изменять размер их холстов, пытаются сделать
+canvas.width
и canvas.height
соответствующими
+canvas.clientWidth
и canvas.clientHeight
,
+потому что они хотят, чтобы был
+один пиксель в холсте для каждого пикселя, отображаемого браузером.
+Но, как мы видели выше, это не
+единственный вариант. Это означает, что почти во всех случаях более
+технически правильно вычислять
+соотношение сторон матрицы проекции, используя canvas.clientHeight
+и canvas.clientWidth
. Тогда вы получите правильное соотношение сторон
+независимо от того, соответствуют ли ширина и высота холста
+размеру, который браузер рисует холст.
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-drawing-without-data.md b/webgl/lessons/ru/webgl-drawing-without-data.md
index cbd3e0ae8..12953879e 100644
--- a/webgl/lessons/ru/webgl-drawing-without-data.md
+++ b/webgl/lessons/ru/webgl-drawing-without-data.md
@@ -196,4 +196,360 @@ function render(time) {
const offset = 0;
gl.drawArrays(gl.POINTS, offset, numVerts);
-}
\ No newline at end of file
+}
+```
+
+{{{example url="../webgl-no-data-point-rain-linear.html"}}}
+
+Это дает нам POINTS, идущие вниз по экрану, но они все
+в порядке. Нам нужно добавить некоторую случайность. В GLSL нет
+генератора случайных чисел. Вместо этого мы можем использовать
+функцию, которая генерирует что-то, что кажется достаточно случайным.
+
+Вот одна
+
+```glsl
+// hash функция из https://www.shadertoy.com/view/4djSRW
+// дано значение между 0 и 1
+// возвращает значение между 0 и 1, которое *выглядит* довольно случайным
+float hash(float p) {
+ vec2 p2 = fract(vec2(p * 5.3983, p * 5.4427));
+ p2 += dot(p2.yx, p2.xy + vec2(21.5351, 14.3137));
+ return fract(p2.x * p2.y * 95.4337);
+}
+```
+
+и мы можем использовать это так
+
+```glsl
+void main() {
+ float u = float(gl_VertexID) / float(numVerts); // идет от 0 до 1
+ float x = hash(u) * 2.0 - 1.0; // случайная позиция
+ float y = fract(time + u) * -2.0 + 1.0; // 1.0 -> -1.0
+
+ gl_Position = vec4(x, y, 0, 1);
+ gl_PointSize = 2.0;
+}
+```
+
+Мы передаем `hash` наше предыдущее значение от 0 до 1, и оно дает нам
+обратно псевдослучайное значение от 0 до 1.
+
+Давайте также сделаем точки меньше
+
+```glsl
+ gl_Position = vec4(x, y, 0, 1);
+ gl_PointSize = 2.0;
+```
+
+И увеличим количество точек, которые мы рисуем
+
+```js
+const numVerts = 400;
+```
+
+И с этим мы получаем
+
+{{{example url="../webgl-no-data-point-rain.html"}}}
+
+Если вы посмотрите очень внимательно, вы можете увидеть, что дождь повторяется.
+Ищите какую-то группу точек и смотрите, как они падают с
+низа и появляются обратно сверху.
+Если бы на заднем плане происходило больше, как если бы
+этот дешевый эффект дождя происходил поверх 3D игры,
+возможно, никто никогда не заметил бы, что он повторяется.
+
+Мы можем исправить повторение, добавив немного больше случайности.
+
+```glsl
+void main() {
+ float u = float(gl_VertexID) / float(numVerts); // идет от 0 до 1
+ float off = floor(time + u) / 1000.0; // изменяется раз в секунду на вершину
+ float x = hash(u + off) * 2.0 - 1.0; // случайная позиция
+ float y = fract(time + u) * -2.0 + 1.0; // 1.0 -> -1.0
+
+ gl_Position = vec4(x, y, 0, 1);
+ gl_PointSize = 2.0;
+}
+```
+
+В коде выше мы добавили `off`. Поскольку мы вызываем `floor`,
+значение `floor(time + u)` будет эффективно давать нам
+секундный таймер, который изменяется только раз в секунду для каждой вершины.
+Это смещение синхронизировано с кодом, перемещающим точку вниз по экрану,
+так что в тот же момент, когда точка прыгает обратно наверх
+экрана, добавляется небольшое количество к значению,
+которое передается в `hash`, что означает, что эта конкретная точка
+получит новое случайное число и, следовательно, новую случайную горизонтальную позицию.
+
+Результат - эффект дождя, который не кажется повторяющимся
+
+{{{example url="../webgl-no-data-point-rain-less-repeat.html"}}}
+
+Можем ли мы делать больше, чем `gl.POINTS`? Конечно!
+
+Давайте сделаем круги. Для этого нам нужны треугольники вокруг
+центра, как ломтики пирога. Мы можем думать о каждом треугольнике
+как о 2 точках вокруг края пирога, за которыми следует 1 точка в центре.
+Затем мы повторяем для каждого ломтика пирога.
+
+

+
+Итак, сначала мы хотим какой-то счетчик, который изменяется раз на ломтик пирога
+
+```glsl
+int sliceId = gl_VertexID / 3;
+```
+
+Затем нам нужен счет вокруг края круга, который идет
+
+ 0, 1, ?, 1, 2, ?, 2, 3, ?, ...
+
+Значение ? не имеет значения, потому что, глядя на
+диаграмму выше, 3-е значение всегда в центре (0,0),
+так что мы можем просто умножить на 0 независимо от значения.
+
+Чтобы получить паттерн выше, это сработает
+
+```glsl
+int triVertexId = gl_VertexID % 3;
+int edge = triVertexId + sliceId;
+```
+
+Для точек на краю против точек в центре нам нужен
+этот паттерн. 2 на краю, затем 1 в центре, повторять.
+
+ 1, 1, 0, 1, 1, 0, 1, 1, 0, ...
+
+Мы можем получить этот паттерн с
+
+```glsl
+float radius = step(1.5, float(triVertexId));
+```
+
+`step(a, b)` это 0, если a < b, и 1 в противном случае. Вы можете думать об этом как
+
+```js
+function step(a, b) {
+ return a < b ? 0 : 1;
+}
+```
+
+`step(1.5, float(triVertexId))` будет 1, когда 1.5 меньше `triVertexId`.
+Это верно для первых 2 вершин каждого треугольника и ложно
+для последней.
+
+Мы можем получить вершины треугольника для круга так
+
+```glsl
+int numSlices = 8;
+int sliceId = gl_VertexID / 3;
+int triVertexId = gl_VertexID % 3;
+int edge = triVertexId + sliceId;
+float angleU = float(edge) / float(numSlices); // 0.0 до 1.0
+float angle = angleU * PI * 2.0;
+float radius = step(float(triVertexId), 1.5);
+vec2 pos = vec2(cos(angle), sin(angle)) * radius;
+```
+
+Собрав все это вместе, давайте просто попробуем нарисовать 1 круг.
+
+```glsl
+#version 300 es
+uniform int numVerts;
+uniform vec2 resolution;
+
+#define PI radians(180.0)
+
+void main() {
+ int numSlices = 8;
+ int sliceId = gl_VertexID / 3;
+ int triVertexId = gl_VertexID % 3;
+ int edge = triVertexId + sliceId;
+ float angleU = float(edge) / float(numSlices); // 0.0 до 1.0
+ float angle = angleU * PI * 2.0;
+ float radius = step(float(triVertexId), 1.5);
+ vec2 pos = vec2(cos(angle), sin(angle)) * radius;
+
+ float aspect = resolution.y / resolution.x;
+ vec2 scale = vec2(aspect, 1);
+
+ gl_Position = vec4(pos * scale, 0, 1);
+}
+```
+
+Обратите внимание, мы вернули `resolution`, чтобы не получить эллипс.
+
+Для 8-срезового круга нам нужно 8 * 3 вершин
+
+```js
+const numVerts = 8 * 3;
+```
+
+и нам нужно рисовать `TRIANGLES`, а не `POINTS`
+
+```js
+const offset = 0;
+gl.drawArrays(gl.TRIANGLES, offset, numVerts);
+```
+
+{{{example url="../webgl-no-data-triangles-circle.html"}}}
+
+А что, если бы мы хотели нарисовать несколько кругов?
+
+Все, что нам нужно сделать, это придумать `circleId`, который мы
+можем использовать для выбора некоторой позиции для каждого круга, которая
+одинакова для всех вершин в круге.
+
+```glsl
+int numVertsPerCircle = numSlices * 3;
+int circleId = gl_VertexID / numVertsPerCircle;
+```
+
+Например, давайте нарисуем круг из кругов.
+
+Сначала давайте превратим код выше в функцию,
+
+```glsl
+vec2 computeCircleTriangleVertex(int vertexId) {
+ int numSlices = 8;
+ int sliceId = vertexId / 3;
+ int triVertexId = vertexId % 3;
+ int edge = triVertexId + sliceId;
+ float angleU = float(edge) / float(numSlices); // 0.0 до 1.0
+ float angle = angleU * PI * 2.0;
+ float radius = step(float(triVertexId), 1.5);
+ return vec2(cos(angle), sin(angle)) * radius;
+}
+```
+
+Теперь вот оригинальный код, который мы использовали для рисования
+круга из точек в начале этой статьи.
+
+```glsl
+float u = float(gl_VertexID) / float(numVerts); // идет от 0 до 1
+float angle = u * PI * 2.0; // идет от 0 до 2PI
+float radius = 0.8;
+
+vec2 pos = vec2(cos(angle), sin(angle)) * radius;
+
+float aspect = resolution.y / resolution.x;
+vec2 scale = vec2(aspect, 1);
+
+gl_Position = vec4(pos * scale, 0, 1);
+```
+
+Нам просто нужно изменить его, чтобы использовать `circleId` вместо
+`vertexId` и делить на количество кругов
+вместо количества вершин.
+
+```glsl
+void main() {
+ int circleId = gl_VertexID / numVertsPerCircle;
+ int numCircles = numVerts / numVertsPerCircle;
+
+ float u = float(circleId) / float(numCircles); // идет от 0 до 1
+ float angle = u * PI * 2.0; // идет от 0 до 2PI
+ float radius = 0.8;
+
+ vec2 pos = vec2(cos(angle), sin(angle)) * radius;
+
+ vec2 triPos = computeCircleTriangleVertex(gl_VertexID) * 0.1;
+
+ float aspect = resolution.y / resolution.x;
+ vec2 scale = vec2(aspect, 1);
+
+ gl_Position = vec4((pos + triPos) * scale, 0, 1);
+}
+```
+
+Затем нам просто нужно увеличить количество вершин
+
+```js
+const numVerts = 8 * 3 * 20;
+```
+
+И теперь у нас есть круг из 20 кругов.
+
+{{{example url="../webgl-no-data-triangles-circles.html"}}}
+
+И, конечно, мы могли бы применить те же вещи, которые мы делали
+выше, чтобы сделать дождь из кругов. Это, вероятно, не имеет
+смысла, поэтому я не буду проходить через это, но это показывает
+создание треугольников в вершинном шейдере без данных.
+
+Вышеуказанная техника могла бы использоваться для создания прямоугольников
+или квадратов вместо этого, затем генерации UV координат,
+передачи их в фрагментный шейдер и текстурирования
+нашей сгенерированной геометрии. Это могло бы быть хорошо для
+падающих снежинок или листьев, которые фактически переворачиваются в 3D,
+применяя 3D техники, которые мы использовали в статьях
+о [3D перспективе](webgl-3d-perspective.html).
+
+Я хочу подчеркнуть, что **эти техники** не являются обычными.
+Создание простой системы частиц может быть полу-обычным или
+эффект дождя выше, но создание чрезвычайно сложных вычислений
+повредит производительности. В общем, если вы хотите производительности,
+вы должны попросить ваш компьютер делать как можно меньше работы,
+так что если есть куча вещей, которые вы можете предварительно вычислить во время инициализации
+и передать в шейдер в той или иной форме, вы
+должны сделать это.
+
+Например, вот экстремальный вершинный шейдер,
+который вычисляет кучу кубов (предупреждение, есть звук).
+
+
+
+Как интеллектуальное любопытство головоломки "Если бы у меня не было данных,
+кроме vertex id, мог бы я нарисовать что-то интересное?" это
+довольно аккуратно. Фактически [весь этот сайт](https://www.vertexshaderart.com) о
+головоломке, если у вас есть только vertex id, можете ли вы сделать что-то
+интересное. Но для производительности было бы намного намного быстрее использовать
+более традиционные техники передачи данных вершин куба
+в буферы и чтения этих данных с атрибутами или другими техниками,
+которые мы рассмотрим в других статьях.
+
+Есть некоторый баланс, который нужно найти. Для примера дождя выше, если вы хотите точно этот
+эффект, то код выше довольно эффективен. Где-то между
+двумя лежит граница, где одна техника более производительна,
+чем другая. Обычно более традиционные техники намного более гибкие
+также, но вам нужно решать на основе случая за случаем, когда использовать один
+способ или другой.
+
+Цель этой статьи в основном познакомить с этими идеями
+и подчеркнуть другие способы мышления о том, что WebGL
+фактически делает. Снова ему все равно, что вы устанавливаете `gl_Position`
+и выводите цвет в ваших шейдерах. Ему все равно, как вы это делаете.
+
+
+
Проблема с gl.POINTS
+
+Одна вещь, для которой техника вроде этой может быть полезной, это симуляция рисования
+с gl.POINTS
.
+
+
+Есть 2 проблемы с
gl.POINTS
+
+
+- У них максимальный размер
Большинство людей, использующих gl.POINTS
, используют маленькие размеры,
+но если этот максимальный размер меньше, чем вам нужно, вам нужно будет выбрать другое решение.
+
+- Как они обрезаются, когда за пределами экрана, непоследовательно
+Проблема здесь в том, что представьте, что вы устанавливаете центр точки в 1 пиксель от левого края
+canvas, но вы устанавливаете gl_PointSize
в 32.0.
+
+Согласно спецификации OpenGL ES 3.0
+то, что должно произойти, это то, что поскольку 15 столбцов из этих 32x32 пикселей все еще на canvas,
+они должны быть нарисованы. К сожалению, OpenGL (не ES) говорит прямо противоположное.
+Если центр точки за пределами canvas, ничего не рисуется. Еще хуже, OpenGL до
+недавнего времени был печально известен недостаточным тестированием, поэтому некоторые драйверы рисуют эти пиксели,
+а некоторые нет 😭
+
+
+
+Итак, если любая из этих проблем является проблемой для ваших потребностей, то как решение вам нужно рисовать свои собственные квады
+с gl.TRIANGLES
вместо использования gl.POINTS
.
+ Если вы сделаете это, обе проблемы решены.
+Проблема максимального размера исчезает, как и проблема непоследовательной обрезки. Есть различные
+способы рисовать много квадов. Один из них использует техники вроде тех, что в этой статье.
+
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-fundamentals.md b/webgl/lessons/ru/webgl-fundamentals.md
index 9c66748a0..789e23b3b 100644
--- a/webgl/lessons/ru/webgl-fundamentals.md
+++ b/webgl/lessons/ru/webgl-fundamentals.md
@@ -197,4 +197,419 @@ canvas. Вот простой пример WebGL, который показыв
Фактически, большинство 3D движков генерируют GLSL шейдеры на лету, используя различные типы шаблонов, конкатенацию и т.д.
Для примеров на этом сайте, однако, ни один из них не достаточно сложен, чтобы нуждаться в генерации GLSL во время выполнения.
-> ПРИМЕЧАНИЕ: `#version 300 es` **ДОЛЖНА БЫТЬ САМОЙ ПЕРВОЙ СТРОКОЙ ВАШЕГО ШЕЙДЕРА**. Никаких комментариев или
\ No newline at end of file
+> ПРИМЕЧАНИЕ: `#version 300 es` **ДОЛЖНА БЫТЬ САМОЙ ПЕРВОЙ СТРОКОЙ ВАШЕГО ШЕЙДЕРА**. Никаких комментариев или
+> пустых строк не допускается перед ней! `#version 300 es` говорит WebGL2, что вы хотите использовать язык шейдеров WebGL2,
+> называемый GLSL ES 3.00. Если вы не поставите это как первую строку, язык шейдеров
+> по умолчанию будет использовать GLSL ES 1.00 WebGL 1.0, который имеет много различий и гораздо меньше функций.
+
+Далее нам нужна функция, которая создаст шейдер, загрузит исходный код GLSL и скомпилирует шейдер.
+Обратите внимание, что я не написал никаких комментариев, потому что из названий функций должно быть ясно,
+что происходит.
+
+ function createShader(gl, type, source) {
+ var shader = gl.createShader(type);
+ gl.shaderSource(shader, source);
+ gl.compileShader(shader);
+ var success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
+ if (success) {
+ return shader;
+ }
+
+ console.log(gl.getShaderInfoLog(shader));
+ gl.deleteShader(shader);
+ }
+
+Теперь мы можем вызвать эту функцию для создания 2 шейдеров
+
+ var vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
+ var fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
+
+Затем нам нужно *связать* эти 2 шейдера в *программу*
+
+ function createProgram(gl, vertexShader, fragmentShader) {
+ var program = gl.createProgram();
+ gl.attachShader(program, vertexShader);
+ gl.attachShader(program, fragmentShader);
+ gl.linkProgram(program);
+ var success = gl.getProgramParameter(program, gl.LINK_STATUS);
+ if (success) {
+ return program;
+ }
+
+ console.log(gl.getProgramInfoLog(program));
+ gl.deleteProgram(program);
+ }
+
+И вызвать её
+
+ var program = createProgram(gl, vertexShader, fragmentShader);
+
+Теперь, когда мы создали программу GLSL на GPU, нам нужно предоставить ей данные.
+Большая часть API WebGL посвящен настройке состояния для предоставления данных нашим программам GLSL.
+В данном случае наш единственный ввод в программу GLSL - это `a_position`, который является атрибутом.
+Первое, что мы должны сделать, - это найти местоположение атрибута для программы,
+которую мы только что создали
+
+ var positionAttributeLocation = gl.getAttribLocation(program, "a_position");
+
+Поиск местоположений атрибутов (и uniform'ов) - это то, что вы должны
+делать во время инициализации, а не в цикле рендеринга.
+
+Атрибуты получают свои данные из буферов, поэтому нам нужно создать буфер
+
+ var positionBuffer = gl.createBuffer();
+
+WebGL позволяет нам манипулировать многими ресурсами WebGL на глобальных точках привязки.
+Вы можете думать о точках привязки как о внутренних глобальных переменных внутри WebGL.
+Сначала вы привязываете ресурс к точке привязки. Затем все остальные функции
+ссылаются на ресурс через точку привязки. Итак, давайте привяжем буфер позиций.
+
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
+
+Теперь мы можем поместить данные в этот буфер, ссылаясь на него через точку привязки
+
+ // три 2d точки
+ var positions = [
+ 0, 0,
+ 0, 0.5,
+ 0.7, 0,
+ ];
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
+
+Здесь происходит много всего. Первое - у нас есть `positions`, который является
+массивом JavaScript. WebGL, с другой стороны, нужны строго типизированные данные, поэтому часть
+`new Float32Array(positions)` создает новый массив 32-битных чисел с плавающей точкой
+и копирует значения из `positions`. `gl.bufferData` затем копирует эти данные в
+`positionBuffer` на GPU. Он использует буфер позиций, потому что мы привязали
+его к точке привязки `ARRAY_BUFFER` выше.
+
+Последний аргумент, `gl.STATIC_DRAW`, является подсказкой для WebGL о том, как мы будем использовать данные.
+WebGL может попытаться использовать эту подсказку для оптимизации определенных вещей. `gl.STATIC_DRAW` говорит WebGL,
+что мы вряд ли будем часто изменять эти данные.
+
+Теперь, когда мы поместили данные в буфер, нам нужно сказать атрибуту, как получать данные
+из него. Сначала нам нужно создать коллекцию состояния атрибутов, называемую Vertex Array Object.
+
+ var vao = gl.createVertexArray();
+
+И нам нужно сделать это текущим массивом вершин, чтобы все наши настройки атрибутов
+применялись к этому набору состояния атрибутов
+
+ gl.bindVertexArray(vao);
+
+Теперь мы наконец настраиваем атрибуты в массиве вершин. Сначала нам нужно включить атрибут.
+Это говорит WebGL, что мы хотим получать данные из буфера. Если мы не включим атрибут,
+то атрибут будет иметь постоянное значение.
+
+ gl.enableVertexAttribArray(positionAttributeLocation);
+
+Затем нам нужно указать, как извлекать данные
+
+ var size = 2; // 2 компонента на итерацию
+ var type = gl.FLOAT; // данные - это 32-битные float'ы
+ var normalize = false; // не нормализовать данные
+ var stride = 0; // 0 = двигаться вперед на size * sizeof(type) каждый раз, чтобы получить следующую позицию
+ var offset = 0; // начать с начала буфера
+ gl.vertexAttribPointer(
+ positionAttributeLocation, size, type, normalize, stride, offset)
+
+Скрытая часть `gl.vertexAttribPointer` заключается в том, что она привязывает текущий `ARRAY_BUFFER`
+к атрибуту. Другими словами, теперь этот атрибут привязан к
+`positionBuffer`. Это означает, что мы свободны привязать что-то еще к точке привязки `ARRAY_BUFFER`.
+Атрибут продолжит использовать `positionBuffer`.
+
+Обратите внимание, что с точки зрения нашего GLSL вершинного шейдера атрибут `a_position` является `vec4`
+
+ in vec4 a_position;
+
+`vec4` - это 4 значения float. В JavaScript вы могли бы думать об этом как о чем-то вроде
+`a_position = {x: 0, y: 0, z: 0, w: 0}`. Выше мы установили `size = 2`. Атрибуты
+по умолчанию равны `0, 0, 0, 1`, поэтому этот атрибут получит свои первые 2 значения (x и y)
+из нашего буфера. z и w будут по умолчанию 0 и 1 соответственно.
+
+Перед тем как рисовать, мы должны изменить размер холста, чтобы он соответствовал размеру отображения. Холсты, как и изображения, имеют 2 размера.
+Количество пикселей, фактически находящихся в них, и отдельно размер, в котором они отображаются. CSS определяет размер,
+в котором отображается холст. **Вы всегда должны устанавливать размер, который вы хотите для холста, с помощью CSS**, поскольку это намного
+более гибко, чем любой другой метод.
+
+Чтобы количество пикселей в холсте соответствовало размеру, в котором он отображается,
+[я использую вспомогательную функцию, о которой вы можете прочитать здесь](webgl-resizing-the-canvas.html).
+
+Почти во всех этих примерах размер холста составляет 400x300 пикселей, если пример запущен в собственном окне,
+но растягивается, чтобы заполнить доступное пространство, если он находится внутри iframe, как на этой странице.
+Позволяя CSS определять размер, а затем настраивая соответствие, мы легко обрабатываем оба этих случая.
+
+ webglUtils.resizeCanvasToDisplaySize(gl.canvas);
+
+Нам нужно сказать WebGL, как конвертировать из значений clip space,
+которые мы будем устанавливать в `gl_Position`, обратно в пиксели, часто называемые screen space.
+Для этого мы вызываем `gl.viewport` и передаем ему текущий размер холста.
+
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
+
+Это говорит WebGL, что clip space -1 +1 отображается на 0 <-> `gl.canvas.width` для x и 0 <-> `gl.canvas.height`
+для y.
+
+Мы очищаем холст. `0, 0, 0, 0` - это красный, зеленый, синий, альфа соответственно, поэтому в данном случае мы делаем холст прозрачным.
+
+ // Очищаем холст
+ gl.clearColor(0, 0, 0, 0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+Далее нам нужно сказать WebGL, какую программу шейдеров выполнять.
+
+ // Говорим использовать нашу программу (пару шейдеров)
+ gl.useProgram(program);
+
+Затем нам нужно сказать, какой набор буферов использовать и как извлекать данные из этих буферов для
+предоставления атрибутам
+
+ // Привязываем набор атрибутов/буферов, который мы хотим.
+ gl.bindVertexArray(vao);
+
+После всего этого мы наконец можем попросить WebGL выполнить нашу программу GLSL.
+
+ var primitiveType = gl.TRIANGLES;
+ var offset = 0;
+ var count = 3;
+ gl.drawArrays(primitiveType, offset, count);
+
+Поскольку count равен 3, это выполнит наш вершинный шейдер 3 раза. В первый раз `a_position.x` и `a_position.y`
+в нашем атрибуте вершинного шейдера будут установлены на первые 2 значения из positionBuffer.
+Во второй раз `a_position.xy` будет установлен на вторые два значения. В последний раз он будет
+установлен на последние 2 значения.
+
+Поскольку мы установили `primitiveType` в `gl.TRIANGLES`, каждый раз, когда наш вершинный шейдер запускается 3 раза,
+WebGL нарисует треугольник на основе 3 значений, которые мы установили в `gl_Position`. Неважно, какого размера
+наш холст, эти значения находятся в координатах clip space, которые идут от -1 до 1 в каждом направлении.
+
+Поскольку наш вершинный шейдер просто копирует значения positionBuffer в `gl_Position`,
+треугольник будет нарисован в координатах clip space
+
+ 0, 0,
+ 0, 0.5,
+ 0.7, 0,
+
+Конвертируя из clip space в screen space, если размер холста
+оказался 400x300, мы получили бы что-то вроде этого
+
+ clip space screen space
+ 0, 0 -> 200, 150
+ 0, 0.5 -> 200, 225
+ 0.7, 0 -> 340, 150
+
+WebGL теперь отрендерит этот треугольник. Для каждого пикселя, который он собирается нарисовать, WebGL вызовет наш фрагментный шейдер.
+Наш фрагментный шейдер просто устанавливает `outColor` в `1, 0, 0.5, 1`. Поскольку Canvas является 8-битным
+на канал холстом, это означает, что WebGL собирается записать значения `[255, 0, 127, 255]` в холст.
+
+Вот живая версия
+
+{{{example url="../webgl-fundamentals.html" }}}
+
+В случае выше вы можете видеть, что наш вершинный шейдер ничего не делает,
+кроме передачи наших данных позиции напрямую. Поскольку данные позиции уже
+в clip space, работы делать нечего. *Если вы хотите 3D, вам нужно предоставить
+шейдеры, которые конвертируют из 3D в clip space, потому что WebGL - это только
+API растеризации*.
+
+Вы можете задаться вопросом, почему треугольник начинается в центре и идет к верхнему правому углу.
+Clip space в `x` идет от -1 до +1. Это означает, что 0 находится в центре, а положительные значения будут
+справа от этого.
+
+Что касается того, почему он находится сверху, в clip space -1 находится внизу, а +1 сверху. Это означает,
+что 0 находится в центре, и поэтому положительные числа будут выше центра.
+
+Для 2D вещей вы, вероятно, предпочли бы работать в пикселях, чем в clip space, поэтому
+давайте изменим шейдер так, чтобы мы могли предоставить позицию в пикселях и иметь
+его конвертировать в clip space для нас. Вот новый вершинный шейдер
+
+ #version 300 es
+
+ // атрибут - это вход (in) в вершинный шейдер.
+ // Он будет получать данные из буфера
+ in vec2 a_position;
+
+ uniform vec2 u_resolution;
+
+ void main() {
+ // конвертируем позицию из пикселей в 0.0 до 1.0
+ vec2 zeroToOne = a_position / u_resolution;
+
+ // конвертируем из 0->1 в 0->2
+ vec2 zeroToTwo = zeroToOne * 2.0;
+
+ // конвертируем из 0->2 в -1->+1 (clip space)
+ vec2 clipSpace = zeroToTwo - 1.0;
+
+ gl_Position = vec4(clipSpace, 0, 1);
+ }
+
+Некоторые вещи, которые стоит заметить об изменениях. Мы изменили `a_position` на `vec2`, поскольку мы
+используем только `x` и `y` в любом случае. `vec2` похож на `vec4`, но имеет только `x` и `y`.
+
+Далее мы добавили `uniform` под названием `u_resolution`. Чтобы установить это, нам нужно найти его местоположение.
+
+ var resolutionUniformLocation = gl.getUniformLocation(program, "u_resolution");
+
+Остальное должно быть ясно из комментариев. Устанавливая `u_resolution` в разрешение
+нашего холста, шейдер теперь будет принимать позиции, которые мы поместили в `positionBuffer`, предоставленные
+в координатах пикселей, и конвертировать их в clip space.
+
+Теперь мы можем изменить наши значения позиции из clip space в пиксели. На этот раз мы собираемся нарисовать прямоугольник,
+сделанный из 2 треугольников, по 3 точки каждый.
+
+ var positions = [
+ 10, 20,
+ 80, 20,
+ 10, 30,
+ 10, 30,
+ 80, 20,
+ 80, 30,
+ ];
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
+
+И после того, как мы установим, какую программу использовать, мы можем установить значение для uniform, который мы создали.
+`gl.useProgram` похож на `gl.bindBuffer` выше в том, что он устанавливает текущую программу. После
+этого все функции `gl.uniformXXX` устанавливают uniforms на текущей программе.
+
+ gl.useProgram(program);
+
+ // Передаем разрешение холста, чтобы мы могли конвертировать из
+ // пикселей в clip space в шейдере
+ gl.uniform2f(resolutionUniformLocation, gl.canvas.width, gl.canvas.height);
+
+И конечно, чтобы нарисовать 2 треугольника, нам нужно, чтобы WebGL вызвал наш вершинный шейдер 6 раз,
+поэтому нам нужно изменить `count` на `6`.
+
+ // рисуем
+ var primitiveType = gl.TRIANGLES;
+ var offset = 0;
+ var count = 6;
+ gl.drawArrays(primitiveType, offset, count);
+
+И вот он
+
+Примечание: Этот пример и все следующие примеры используют [`webgl-utils.js`](/webgl/resources/webgl-utils.js),
+который содержит функции для компиляции и связывания шейдеров. Нет причин загромождать примеры
+этим [boilerplate](webgl-boilerplate.html) кодом.
+
+{{{example url="../webgl-2d-rectangle.html" }}}
+
+Снова вы можете заметить, что прямоугольник находится рядом с нижней частью этой области. WebGL считает положительный Y
+вверх, а отрицательный Y вниз. В clip space левый нижний угол -1,-1. Мы не изменили никаких знаков,
+поэтому с нашей текущей математикой 0, 0 становится левым нижним углом.
+Чтобы получить более традиционный левый верхний угол, используемый для 2D графических API,
+мы можем просто перевернуть координату y clip space.
+
+ gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
+
+И теперь наш прямоугольник находится там, где мы ожидаем.
+
+{{{example url="../webgl-2d-rectangle-top-left.html" }}}
+
+Давайте сделаем код, который определяет прямоугольник, функцией, чтобы
+мы могли вызывать её для прямоугольников разных размеров. Пока мы этим занимаемся,
+мы сделаем цвет настраиваемым.
+
+Сначала мы делаем фрагментный шейдер принимающим uniform ввода цвета.
+
+ #version 300 es
+
+ precision highp float;
+
+ uniform vec4 u_color;
+
+ out vec4 outColor;
+
+ void main() {
+ outColor = u_color;
+ }
+
+И вот новый код, который рисует 50 прямоугольников в случайных местах и случайных цветах.
+
+ var colorLocation = gl.getUniformLocation(program, "u_color");
+ ...
+
+ // рисуем 50 случайных прямоугольников в случайных цветах
+ for (var ii = 0; ii < 50; ++ii) {
+ // Настраиваем случайный прямоугольник
+ setRectangle(
+ gl, randomInt(300), randomInt(300), randomInt(300), randomInt(300));
+
+ // Устанавливаем случайный цвет.
+ gl.uniform4f(colorLocation, Math.random(), Math.random(), Math.random(), 1);
+
+ // Рисуем прямоугольник.
+ var primitiveType = gl.TRIANGLES;
+ var offset = 0;
+ var count = 6;
+ gl.drawArrays(primitiveType, offset, count);
+ }
+ }
+
+ // Возвращает случайное целое число от 0 до range - 1.
+ function randomInt(range) {
+ return Math.floor(Math.random() * range);
+ }
+
+ // Заполняет буфер значениями, которые определяют прямоугольник.
+
+ function setRectangle(gl, x, y, width, height) {
+ var x1 = x;
+ var x2 = x + width;
+ var y1 = y;
+ var y2 = y + height;
+
+ // ПРИМЕЧАНИЕ: gl.bufferData(gl.ARRAY_BUFFER, ...) повлияет на
+ // любой буфер, привязанный к точке привязки `ARRAY_BUFFER`,
+ // но пока у нас только один буфер. Если бы у нас было больше одного
+ // буфера, мы бы хотели привязать этот буфер к `ARRAY_BUFFER` сначала.
+
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
+ x1, y1,
+ x2, y1,
+ x1, y2,
+ x1, y2,
+ x2, y1,
+ x2, y2]), gl.STATIC_DRAW);
+ }
+
+И вот прямоугольники.
+
+{{{example url="../webgl-2d-rectangles.html" }}}
+
+Я надеюсь, вы можете видеть, что WebGL на самом деле довольно простой API.
+Хорошо, простой может быть неправильным словом. То, что он делает, простое. Он просто
+выполняет 2 пользовательские функции, вершинный шейдер и фрагментный шейдер, и
+рисует треугольники, линии или точки.
+Хотя это может стать более сложным для 3D, эта сложность
+добавляется вами, программистом, в виде более сложных шейдеров.
+Сам API WebGL - это просто растеризатор и концептуально довольно прост.
+
+Мы рассмотрели небольшой пример, который показал, как предоставлять данные в атрибуте и 2 uniforms.
+Обычно иметь несколько атрибутов и много uniforms. Ближе к началу этой статьи
+мы также упомянули *varyings* и *текстуры*. Они появятся в последующих уроках.
+
+Прежде чем мы двинемся дальше, я хочу упомянуть, что для *большинства* приложений обновление
+данных в буфере, как мы делали в `setRectangle`, не является обычным. Я использовал этот
+пример, потому что думал, что его легче всего объяснить, поскольку он показывает координаты пикселей
+как ввод и демонстрирует выполнение небольшого количества математики в GLSL. Это не неправильно, есть
+множество случаев, где это правильная вещь для делать, но вы должны [продолжить читать, чтобы найти
+более обычный способ позиционировать, ориентировать и масштабировать вещи в WebGL](webgl-2d-translation.html).
+
+Если вы на 100% новичок в WebGL и не имеете представления о том, что такое GLSL или шейдеры или что делает GPU,
+тогда посмотрите [основы того, как WebGL действительно работает](webgl-how-it-works.html).
+Вы также можете взглянуть на эту
+[интерактивную диаграмму состояния](/webgl/lessons/resources/webgl-state-diagram.html)
+для другого способа понимания того, как работает WebGL.
+
+Вы также должны, по крайней мере кратко прочитать о [boilerplate коде, используемом здесь](webgl-boilerplate.html),
+который используется в большинстве примеров. Вы также должны хотя бы бегло просмотреть
+[как рисовать несколько вещей](webgl-drawing-multiple-things.html), чтобы дать вам некоторое представление
+о том, как структурированы более типичные WebGL приложения, потому что, к сожалению, почти все примеры
+рисуют только одну вещь и поэтому не показывают эту структуру.
+
+Иначе отсюда вы можете пойти в 2 направлениях. Если вас интересует обработка изображений,
+я покажу вам [как делать некоторую 2D обработку изображений](webgl-image-processing.html).
+Если вас интересует изучение трансляции,
+вращения и масштабирования, тогда [начните отсюда](webgl-2d-translation.html).
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-gpgpu.md b/webgl/lessons/ru/webgl-gpgpu.md
index 78abc6fd0..9bb0627a8 100644
--- a/webgl/lessons/ru/webgl-gpgpu.md
+++ b/webgl/lessons/ru/webgl-gpgpu.md
@@ -377,7 +377,7 @@ void main() {
const program = webglUtils.createProgramFromSources(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const srcTexLoc = gl.getUniformLocation(program, 'srcTex');
-+const dstDimensionsLoc = gl.getUniformLocation(program, 'dstDimensions');
+const dstDimensionsLoc = gl.getUniformLocation(program, 'dstDimensions');
```
и установить его
@@ -385,15 +385,14 @@ const srcTexLoc = gl.getUniformLocation(program, 'srcTex');
```js
gl.useProgram(program);
gl.uniform1i(srcTexLoc, 0); // говорим шейдеру, что исходная текстура находится на texture unit 0
-+gl.uniform2f(dstDimensionsLoc, dstWidth, dstHeight);
+gl.uniform2f(dstDimensionsLoc, dstWidth, dstHeight);
```
и нам нужно изменить размер назначения (canvas)
```js
const dstWidth = 3;
--const dstHeight = 2;
-+const dstHeight = 1;
+const dstHeight = 1;
```
и с этим у нас теперь есть результирующий массив, способный выполнять математику
@@ -980,4 +979,1004 @@ for each point
minDistanceSoFar = MAX_VALUE
for each line segment
compute distance from point to line segment
-```
\ No newline at end of file
+ if distance is < minDistanceSoFar
+ minDistanceSoFar = distance
+ closestLine = line segment
+```
+
+Для 500 точек, каждая проверяющая 1000 линий, это 500,000 проверок.
+Современные GPU имеют сотни или тысячи ядер, поэтому если мы могли бы сделать это на
+GPU, мы могли бы потенциально работать в сотни или тысячи раз быстрее.
+
+На этот раз, хотя мы можем поместить точки в буфер, как мы делали для частиц,
+мы не можем поместить отрезки линий в буфер. Буферы предоставляют свои данные через
+атрибуты. Это означает, что мы не можем случайно обращаться к любому значению по требованию, вместо
+этого значения присваиваются атрибуту вне контроля шейдера.
+
+Итак, нам нужно поместить позиции линий в текстуру, которая, как мы указали
+выше, является другим словом для 2D массива, хотя мы все еще можем обращаться с этим 2D
+массивом как с 1D массивом, если хотим.
+
+Вот вершинный шейдер, который находит ближайшую линию для одной точки.
+Это точно алгоритм перебора, как выше
+
+```js
+ const closestLineVS = `#version 300 es
+ in vec3 point;
+
+ uniform sampler2D linesTex;
+ uniform int numLineSegments;
+
+ flat out int closestNdx;
+
+ vec4 getAs1D(sampler2D tex, ivec2 dimensions, int index) {
+ int y = index / dimensions.x;
+ int x = index % dimensions.x;
+ return texelFetch(tex, ivec2(x, y), 0);
+ }
+
+ // из https://stackoverflow.com/a/6853926/128511
+ // a - это точка, b,c - это отрезок линии
+ float distanceFromPointToLine(in vec3 a, in vec3 b, in vec3 c) {
+ vec3 ba = a - b;
+ vec3 bc = c - b;
+ float d = dot(ba, bc);
+ float len = length(bc);
+ float param = 0.0;
+ if (len != 0.0) {
+ param = clamp(d / (len * len), 0.0, 1.0);
+ }
+ vec3 r = b + bc * param;
+ return distance(a, r);
+ }
+
+ void main() {
+ ivec2 linesTexDimensions = textureSize(linesTex, 0);
+
+ // находим ближайший отрезок линии
+ float minDist = 10000000.0;
+ int minIndex = -1;
+ for (int i = 0; i < numLineSegments; ++i) {
+ vec3 lineStart = getAs1D(linesTex, linesTexDimensions, i * 2).xyz;
+ vec3 lineEnd = getAs1D(linesTex, linesTexDimensions, i * 2 + 1).xyz;
+ float dist = distanceFromPointToLine(point, lineStart, lineEnd);
+ if (dist < minDist) {
+ minDist = dist;
+ minIndex = i;
+ }
+ }
+
+ closestNdx = minIndex;
+ }
+ `;
+```
+
+Я переименовал `getValueFrom2DTextureAs1DArray` в `getAs1D` просто чтобы сделать
+некоторые строки короче и более читаемыми.
+В противном случае это довольно прямолинейная реализация алгоритма перебора,
+который мы написали выше.
+
+`point` - это текущая точка. `linesTex` содержит точки для
+отрезка линии парами, первая точка, за которой следует вторая точка.
+
+Сначала давайте создадим некоторые тестовые данные. Вот 2 точки и 5 линий. Они
+дополнены 0, 0, потому что каждая будет храниться в RGBA текстуре.
+
+```js
+const points = [
+ 100, 100,
+ 200, 100,
+];
+const lines = [
+ 25, 50,
+ 25, 150,
+ 90, 50,
+ 90, 150,
+ 125, 50,
+ 125, 150,
+ 185, 50,
+ 185, 150,
+ 225, 50,
+ 225, 150,
+];
+const numPoints = points.length / 2;
+const numLineSegments = lines.length / 2 / 2;
+```
+
+Если мы нанесем их на график, они будут выглядеть так
+
+

+
+Линии пронумерованы от 0 до 4 слева направо,
+поэтому если наш код работает, первая точка (
красная)
+должна получить значение 1 как ближайшая линия, вторая точка
+(
зеленая), должна получить значение 3.
+
+Давайте поместим точки в буфер, а также создадим буфер для хранения вычисленного
+ближайшего индекса для каждого
+
+```js
+const closestNdxBuffer = makeBuffer(gl, points.length * 4, gl.STATIC_DRAW);
+const pointsBuffer = makeBuffer(gl, new Float32Array(points), gl.DYNAMIC_DRAW);
+```
+
+и давайте создадим текстуру для хранения всех конечных точек линий.
+
+```js
+function createDataTexture(gl, data, numComponents, internalFormat, format, type) {
+ const numElements = data.length / numComponents;
+
+ // вычисляем размер, который будет содержать все наши данные
+ const width = Math.ceil(Math.sqrt(numElements));
+ const height = Math.ceil(numElements / width);
+
+ const bin = new Float32Array(width * height * numComponents);
+ bin.set(data);
+
+ const tex = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, tex);
+ gl.texImage2D(
+ gl.TEXTURE_2D,
+ 0, // mip level
+ internalFormat,
+ width,
+ height,
+ 0, // border
+ format,
+ type,
+ bin,
+ );
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+ return {tex, dimensions: [width, height]};
+}
+
+const {tex: linesTex, dimensions: linesTexDimensions} =
+ createDataTexture(gl, lines, 2, gl.RG32F, gl.RG, gl.FLOAT);
+```
+
+В данном случае мы позволяем коду выбрать размеры текстуры
+и позволяем ему дополнить текстуру. Например, если мы дадим ему массив
+с 7 записями, он поместит это в текстуру 3x3. Он возвращает
+и текстуру, и размеры, которые он выбрал. Почему мы позволяем ему выбрать
+размер? Потому что текстуры имеют максимальный размер.
+
+В идеале мы хотели бы просто смотреть на наши данные как на 1-мерный массив
+позиций, 1-мерный массив точек линий и т.д. Поэтому мы могли бы просто
+объявить текстуру как Nx1. К сожалению, GPU имеют максимальный
+размер, и это может быть всего 1024 или 2048. Если лимит
+был 1024 и нам нужно было 1025 значений в нашем массиве, нам пришлось бы поместить данные
+в текстуру, скажем, 512x2. Помещая данные в квадрат, мы не
+достигнем лимита, пока не достигнем максимального размера текстуры в квадрате.
+Для лимита размера 1024 это позволило бы массивы более 1 миллиона значений.
+
+Далее компилируем шейдер и находим локации
+
+```js
+const closestLinePrg = createProgram(
+ gl, [closestLineVS, closestLineFS], ['closestNdx']);
+
+const closestLinePrgLocs = {
+ point: gl.getAttribLocation(closestLinePrg, 'point'),
+ linesTex: gl.getUniformLocation(closestLinePrg, 'linesTex'),
+ numLineSegments: gl.getUniformLocation(closestLinePrg, 'numLineSegments'),
+};
+```
+
+И создаем вершинный массив
+
+```js
+const closestLineVA = makeVertexArray(gl, [
+ [pointsBuffer, closestLinePrgLocs.point],
+]);
+```
+
+И создаем transform feedback
+
+```js
+const closestLineTF = makeTransformFeedback(gl, closestNdxBuffer);
+```
+
+Теперь мы можем запустить вычисление
+
+```js
+gl.useProgram(closestLinePrg);
+gl.bindVertexArray(closestLineVA);
+gl.uniform1i(closestLinePrgLocs.linesTex, 0);
+gl.uniform1f(closestLinePrgLocs.numLineSegments, numLineSegments);
+
+gl.activeTexture(gl.TEXTURE0);
+gl.bindTexture(gl.TEXTURE_2D, linesTex);
+
+gl.enable(gl.RASTERIZER_DISCARD);
+gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, closestLineTF);
+gl.beginTransformFeedback(gl.POINTS);
+gl.drawArrays(gl.POINTS, 0, numPoints);
+gl.endTransformFeedback();
+gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
+gl.disable(gl.RASTERIZER_DISCARD);
+```
+
+И читаем результаты
+
+```js
+const results = new Int32Array(numPoints);
+gl.bindBuffer(gl.ARRAY_BUFFER, closestNdxBuffer);
+gl.getBufferSubData(gl.ARRAY_BUFFER, 0, results);
+console.log('results:', results);
+```
+
+Результаты должны быть `[1, 3]`, что означает, что точка 0 ближе всего к линии 1,
+а точка 1 ближе всего к линии 3.
+
+{{{example url="../webgl-gpgpu-closest-line-results-transformfeedback.html"}}}
+
+## Следующий пример: Динамический transform feedback
+
+В предыдущем примере мы использовали transform feedback для записи результатов
+в буфер. Но что, если мы хотим использовать эти результаты в следующем кадре?
+
+Вот пример, где мы используем transform feedback для создания анимации.
+Мы создаем частицы, которые движутся по кругу, и используем transform feedback
+для обновления их позиций каждый кадр.
+
+```js
+const vs = `#version 300 es
+in vec4 position;
+in vec4 velocity;
+in float age;
+
+uniform float u_time;
+uniform float u_deltaTime;
+
+out vec4 v_position;
+out vec4 v_velocity;
+out float v_age;
+
+void main() {
+ v_position = position;
+ v_velocity = velocity;
+ v_age = age;
+}
+`;
+
+const fs = `#version 300 es
+precision highp float;
+
+in vec4 v_position;
+in vec4 v_velocity;
+in float v_age;
+
+uniform float u_time;
+uniform float u_deltaTime;
+
+out vec4 outColor;
+
+void main() {
+ // обновляем позицию
+ vec4 newPosition = v_position + v_velocity * u_deltaTime;
+
+ // обновляем скорость (добавляем небольшое ускорение)
+ vec4 newVelocity = v_velocity + vec4(0.0, -9.8, 0.0, 0.0) * u_deltaTime;
+
+ // увеличиваем возраст
+ float newAge = v_age + u_deltaTime;
+
+ // если частица слишком старая, сбрасываем её
+ if (newAge > 5.0) {
+ newPosition = vec4(0.0, 0.0, 0.0, 1.0);
+ newVelocity = vec4(
+ sin(u_time + gl_FragCoord.x * 0.01) * 100.0,
+ cos(u_time + gl_FragCoord.y * 0.01) * 100.0,
+ 0.0, 0.0
+ );
+ newAge = 0.0;
+ }
+
+ outColor = vec4(newPosition.xyz, newAge);
+}
+`;
+
+const numParticles = 1000;
+const positions = new Float32Array(numParticles * 4);
+const velocities = new Float32Array(numParticles * 4);
+const ages = new Float32Array(numParticles);
+
+// инициализируем частицы
+for (let i = 0; i < numParticles; ++i) {
+ const angle = (i / numParticles) * Math.PI * 2;
+ const radius = 100 + Math.random() * 50;
+
+ positions[i * 4 + 0] = Math.cos(angle) * radius;
+ positions[i * 4 + 1] = Math.sin(angle) * radius;
+ positions[i * 4 + 2] = 0;
+ positions[i * 4 + 3] = 1;
+
+ velocities[i * 4 + 0] = Math.cos(angle) * 50;
+ velocities[i * 4 + 1] = Math.sin(angle) * 50;
+ velocities[i * 4 + 2] = 0;
+ velocities[i * 4 + 3] = 0;
+
+ ages[i] = Math.random() * 5;
+}
+
+const positionBuffer = makeBuffer(gl, positions, gl.DYNAMIC_DRAW);
+const velocityBuffer = makeBuffer(gl, velocities, gl.DYNAMIC_DRAW);
+const ageBuffer = makeBuffer(gl, ages, gl.DYNAMIC_DRAW);
+
+const updateProgram = createProgram(gl, [vs, fs], ['v_position', 'v_velocity', 'v_age']);
+
+const updateProgramLocs = {
+ position: gl.getAttribLocation(updateProgram, 'position'),
+ velocity: gl.getAttribLocation(updateProgram, 'velocity'),
+ age: gl.getAttribLocation(updateProgram, 'age'),
+ time: gl.getUniformLocation(updateProgram, 'u_time'),
+ deltaTime: gl.getUniformLocation(updateProgram, 'u_deltaTime'),
+};
+
+const updateVA = makeVertexArray(gl, [
+ [positionBuffer, updateProgramLocs.position],
+ [velocityBuffer, updateProgramLocs.velocity],
+ [ageBuffer, updateProgramLocs.age],
+]);
+
+const updateTF = makeTransformFeedback(gl, positionBuffer);
+
+let then = 0;
+function render(time) {
+ time *= 0.001;
+ const deltaTime = time - then;
+ then = time;
+
+ webglUtils.resizeCanvasToDisplaySize(gl.canvas);
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ // обновляем частицы
+ gl.useProgram(updateProgram);
+ gl.bindVertexArray(updateVA);
+ gl.uniform1f(updateProgramLocs.time, time);
+ gl.uniform1f(updateProgramLocs.deltaTime, deltaTime);
+
+ gl.enable(gl.RASTERIZER_DISCARD);
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, updateTF);
+ gl.beginTransformFeedback(gl.POINTS);
+ gl.drawArrays(gl.POINTS, 0, numParticles);
+ gl.endTransformFeedback();
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
+ gl.disable(gl.RASTERIZER_DISCARD);
+
+ // рисуем частицы
+ gl.useProgram(drawProgram);
+ gl.bindVertexArray(drawVA);
+ gl.uniformMatrix4fv(
+ drawProgramLocs.matrix,
+ false,
+ m4.orthographic(-gl.canvas.width/2, gl.canvas.width/2,
+ -gl.canvas.height/2, gl.canvas.height/2, -1, 1));
+ gl.drawArrays(gl.POINTS, 0, numParticles);
+
+ requestAnimationFrame(render);
+}
+requestAnimationFrame(render);
+```
+
+## Следующий пример: Визуализация результатов
+
+В предыдущем примере мы вычислили, какая линия ближе всего к каждой точке,
+но мы только вывели результаты в консоль. Давайте создадим визуализацию,
+которая покажет точки, линии и соединит каждую точку с ближайшей к ней линией.
+
+Сначала нам нужны шейдеры для рисования линий и точек:
+
+```js
+const drawLinesVS = `#version 300 es
+in vec4 position;
+void main() {
+ gl_Position = position;
+}
+`;
+
+const drawLinesFS = `#version 300 es
+precision highp float;
+out vec4 outColor;
+void main() {
+ outColor = vec4(0.5, 0.5, 0.5, 1); // серый цвет для всех линий
+}
+`;
+
+const drawClosestLinesVS = `#version 300 es
+in int closestNdx;
+
+uniform sampler2D linesTex;
+uniform mat4 matrix;
+uniform float numPoints;
+
+out vec4 v_color;
+
+vec3 hsv2rgb(vec3 c) {
+ vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+ vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
+void main() {
+ // получаем координаты линии из текстуры
+ ivec2 texelCoord = ivec2(closestNdx, 0);
+ vec4 lineData = texelFetch(linesTex, texelCoord, 0);
+
+ // выбираем начальную или конечную точку линии
+ int linePointId = closestNdx * 2 + gl_VertexID % 2;
+ vec2 linePoint = mix(lineData.xy, lineData.zw, gl_VertexID % 2);
+
+ gl_Position = matrix * vec4(linePoint, 0, 1);
+
+ // вычисляем цвет на основе ID точки
+ float hue = float(gl_InstanceID) / numPoints;
+ v_color = vec4(hsv2rgb(vec3(hue, 1, 1)), 1);
+}
+`;
+
+const drawClosestLinesPointsFS = `#version 300 es
+precision highp float;
+in vec4 v_color;
+out vec4 outColor;
+void main() {
+ outColor = v_color;
+}
+`;
+
+const drawPointsVS = `#version 300 es
+in vec2 point;
+
+uniform mat4 matrix;
+uniform float numPoints;
+
+out vec4 v_color;
+
+vec3 hsv2rgb(vec3 c) {
+ vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
+ vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
+ return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
+}
+
+void main() {
+ gl_Position = matrix * vec4(point, 0, 1);
+ gl_PointSize = 10.0;
+
+ // вычисляем цвет на основе ID точки
+ float hue = float(gl_VertexID) / numPoints;
+ v_color = vec4(hsv2rgb(vec3(hue, 1, 1)), 1);
+}
+`;
+```
+
+Мы передаем `closestNdx` как атрибут. Это результаты, которые мы сгенерировали.
+Используя это, мы можем найти конкретную линию. Нам нужно нарисовать 2 точки на линию,
+поэтому мы будем использовать [инстансированное рисование](webgl-instanced-drawing.html)
+для рисования 2 точек на `closestNdx`. Затем мы можем использовать `gl_VertexID % 2`
+для выбора начальной или конечной точки.
+
+Наконец, мы вычисляем цвет, используя тот же метод, который мы использовали при рисовании точек,
+чтобы они соответствовали своим точкам.
+
+Нам нужно скомпилировать все эти новые программы шейдеров и найти местоположения:
+
+```js
+const closestLinePrg = createProgram(
+ gl, [closestLineVS, closestLineFS], ['closestNdx']);
+const drawLinesPrg = createProgram(
+ gl, [drawLinesVS, drawLinesFS]);
+const drawClosestLinesPrg = createProgram(
+ gl, [drawClosestLinesVS, drawClosestLinesPointsFS]);
+const drawPointsPrg = createProgram(
+ gl, [drawPointsVS, drawClosestLinesPointsFS]);
+
+const closestLinePrgLocs = {
+ point: gl.getAttribLocation(closestLinePrg, 'point'),
+ linesTex: gl.getUniformLocation(closestLinePrg, 'linesTex'),
+ numLineSegments: gl.getUniformLocation(closestLinePrg, 'numLineSegments'),
+};
+const drawLinesPrgLocs = {
+ linesTex: gl.getUniformLocation(drawLinesPrg, 'linesTex'),
+ matrix: gl.getUniformLocation(drawLinesPrg, 'matrix'),
+};
+const drawClosestLinesPrgLocs = {
+ closestNdx: gl.getAttribLocation(drawClosestLinesPrg, 'closestNdx'),
+ linesTex: gl.getUniformLocation(drawClosestLinesPrg, 'linesTex'),
+ matrix: gl.getUniformLocation(drawClosestLinesPrg, 'matrix'),
+ numPoints: gl.getUniformLocation(drawClosestLinesPrg, 'numPoints'),
+};
+const drawPointsPrgLocs = {
+ point: gl.getAttribLocation(drawPointsPrg, 'point'),
+ matrix: gl.getUniformLocation(drawPointsPrg, 'matrix'),
+ numPoints: gl.getUniformLocation(drawPointsPrg, 'numPoints'),
+};
+```
+
+Нам нужны массивы вершин для рисования точек и ближайших линий:
+
+```js
+const closestLinesVA = makeVertexArray(gl, [
+ [pointsBuffer, closestLinePrgLocs.point],
+]);
+
+const drawClosestLinesVA = gl.createVertexArray();
+gl.bindVertexArray(drawClosestLinesVA);
+gl.bindBuffer(gl.ARRAY_BUFFER, closestNdxBuffer);
+gl.enableVertexAttribArray(drawClosestLinesPrgLocs.closestNdx);
+gl.vertexAttribIPointer(drawClosestLinesPrgLocs.closestNdx, 1, gl.INT, 0, 0);
+gl.vertexAttribDivisor(drawClosestLinesPrgLocs.closestNdx, 1);
+
+const drawPointsVA = makeVertexArray(gl, [
+ [pointsBuffer, drawPointsPrgLocs.point],
+]);
+```
+
+Итак, во время рендеринга мы вычисляем результаты, как мы делали раньше, но
+мы не ищем результаты с помощью `getBufferSubData`. Вместо этого мы просто
+передаем их в соответствующие шейдеры.
+
+Сначала рисуем все линии серым цветом:
+
+```js
+// рисуем все линии серым цветом
+gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
+
+gl.bindVertexArray(null);
+gl.useProgram(drawLinesPrg);
+
+// привязываем текстуру линий к текстуре unit 0
+gl.activeTexture(gl.TEXTURE0);
+gl.bindTexture(gl.TEXTURE_2D, linesTex);
+
+// Говорим шейдеру использовать текстуру на текстуре unit 0
+gl.uniform1i(drawLinesPrgLocs.linesTex, 0);
+gl.uniformMatrix4fv(drawLinesPrgLocs.matrix, false, matrix);
+
+gl.drawArrays(gl.LINES, 0, numLineSegments * 2);
+```
+
+Затем рисуем все ближайшие линии:
+
+```js
+gl.bindVertexArray(drawClosestLinesVA);
+gl.useProgram(drawClosestLinesPrg);
+
+gl.activeTexture(gl.TEXTURE0);
+gl.bindTexture(gl.TEXTURE_2D, linesTex);
+
+gl.uniform1i(drawClosestLinesPrgLocs.linesTex, 0);
+gl.uniform1f(drawClosestLinesPrgLocs.numPoints, numPoints);
+gl.uniformMatrix4fv(drawClosestLinesPrgLocs.matrix, false, matrix);
+
+gl.drawArraysInstanced(gl.LINES, 0, 2, numPoints);
+```
+
+и наконец рисуем каждую точку:
+
+```js
+gl.bindVertexArray(drawPointsVA);
+gl.useProgram(drawPointsPrg);
+
+gl.uniform1f(drawPointsPrgLocs.numPoints, numPoints);
+gl.uniformMatrix4fv(drawPointsPrgLocs.matrix, false, matrix);
+
+gl.drawArrays(gl.POINTS, 0, numPoints);
+```
+
+Прежде чем запустить, давайте сделаем еще одну вещь. Добавим больше точек и линий:
+
+```js
+function createPoints(numPoints, ranges) {
+ const points = [];
+ for (let i = 0; i < numPoints; ++i) {
+ points.push(...ranges.map(range => r(...range)));
+ }
+ return points;
+}
+
+const r = (min, max) => min + Math.random() * (max - min);
+
+const points = createPoints(8, [[0, gl.canvas.width], [0, gl.canvas.height]]);
+const lines = createPoints(125 * 2, [[0, gl.canvas.width], [0, gl.canvas.height]]);
+const numPoints = points.length / 2;
+const numLineSegments = lines.length / 2 / 2;
+```
+
+и если мы запустим это:
+
+{{{example url="../webgl-gpgpu-closest-line-transformfeedback.html"}}}
+
+Вы можете увеличить количество точек и линий,
+но в какой-то момент вы не сможете сказать, какие
+точки соответствуют каким линиям, но с меньшим числом
+вы можете хотя бы визуально проверить, что это работает.
+
+Просто для удовольствия, давайте объединим пример с частицами и этот
+пример. Мы будем использовать техники, которые мы использовали для обновления
+позиций частиц, чтобы обновить точки. Для
+обновления конечных точек линий мы сделаем то, что мы делали в
+начале, и запишем результаты в текстуру.
+
+Для этого мы копируем `updatePositionFS` вершинный шейдер
+из примера с частицами. Для линий, поскольку их значения
+хранятся в текстуре, нам нужно переместить их точки в
+фрагментном шейдере:
+
+```js
+const updateLinesVS = `#version 300 es
+in vec4 position;
+void main() {
+ gl_Position = position;
+}
+`;
+
+const updateLinesFS = `#version 300 es
+precision highp float;
+
+uniform sampler2D linesTex;
+uniform sampler2D velocityTex;
+uniform vec2 canvasDimensions;
+uniform float deltaTime;
+
+out vec4 outColor;
+
+vec2 euclideanModulo(vec2 n, vec2 m) {
+ return mod(mod(n, m) + m, m);
+}
+
+void main() {
+ // вычисляем координаты текселя из gl_FragCoord;
+ ivec2 texelCoord = ivec2(gl_FragCoord.xy);
+
+ // получаем данные линии
+ vec4 lineData = texelFetch(linesTex, texelCoord, 0);
+
+ // получаем скорость для этой линии
+ vec2 velocity = texelFetch(velocityTex, texelCoord, 0).xy;
+
+ // обновляем позиции
+ vec2 newStart = euclideanModulo(lineData.xy + velocity * deltaTime, canvasDimensions);
+ vec2 newEnd = euclideanModulo(lineData.zw + velocity * deltaTime, canvasDimensions);
+
+ outColor = vec4(newStart, newEnd);
+}
+`;
+```
+
+Теперь нам нужны буферы для хранения скоростей линий и программа для их обновления:
+
+```js
+const lineVelocities = new Float32Array(numLineSegments * 2);
+for (let i = 0; i < numLineSegments; ++i) {
+ lineVelocities[i * 2 + 0] = (Math.random() - 0.5) * 100;
+ lineVelocities[i * 2 + 1] = (Math.random() - 0.5) * 100;
+}
+
+const lineVelocityBuffer = makeBuffer(gl, lineVelocities, gl.DYNAMIC_DRAW);
+const lineVelocityTex = makeDataTexture(gl, lineVelocities, numLineSegments, 1);
+
+const updateLinesPrg = createProgram(gl, [updateLinesVS, updateLinesFS]);
+
+const updateLinesPrgLocs = {
+ linesTex: gl.getUniformLocation(updateLinesPrg, 'linesTex'),
+ velocityTex: gl.getUniformLocation(updateLinesPrg, 'velocityTex'),
+ canvasDimensions: gl.getUniformLocation(updateLinesPrg, 'canvasDimensions'),
+ deltaTime: gl.getUniformLocation(updateLinesPrg, 'deltaTime'),
+};
+```
+
+Теперь в нашем цикле рендеринга мы обновляем линии, затем точки, затем рисуем все:
+
+```js
+function render(time) {
+ time *= 0.001;
+ const deltaTime = time - then;
+ then = time;
+
+ webglUtils.resizeCanvasToDisplaySize(gl.canvas);
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ // обновляем линии
+ gl.bindFramebuffer(gl.FRAMEBUFFER, linesFramebuffer);
+ gl.viewport(0, 0, numLineSegments, 1);
+
+ gl.useProgram(updateLinesPrg);
+ gl.bindVertexArray(null);
+
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, linesTex);
+ gl.uniform1i(updateLinesPrgLocs.linesTex, 0);
+
+ gl.activeTexture(gl.TEXTURE1);
+ gl.bindTexture(gl.TEXTURE_2D, lineVelocityTex);
+ gl.uniform1i(updateLinesPrgLocs.velocityTex, 1);
+
+ gl.uniform2f(updateLinesPrgLocs.canvasDimensions, gl.canvas.width, gl.canvas.height);
+ gl.uniform1f(updateLinesPrgLocs.deltaTime, deltaTime);
+
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
+
+ // обновляем точки
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
+
+ gl.useProgram(updateProgram);
+ gl.bindVertexArray(updateVA);
+ gl.uniform1f(updateProgramLocs.time, time);
+ gl.uniform1f(updateProgramLocs.deltaTime, deltaTime);
+
+ gl.enable(gl.RASTERIZER_DISCARD);
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, updateTF);
+ gl.beginTransformFeedback(gl.POINTS);
+ gl.drawArrays(gl.POINTS, 0, numPoints);
+ gl.endTransformFeedback();
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
+ gl.disable(gl.RASTERIZER_DISCARD);
+
+ // вычисляем ближайшие линии
+ gl.bindFramebuffer(gl.FRAMEBUFFER, closestLineFramebuffer);
+ gl.viewport(0, 0, numPoints, 1);
+
+ gl.useProgram(closestLinePrg);
+ gl.bindVertexArray(closestLinesVA);
+ gl.uniform1i(closestLinePrgLocs.linesTex, 0);
+ gl.uniform1f(closestLinePrgLocs.numLineSegments, numLineSegments);
+
+ gl.enable(gl.RASTERIZER_DISCARD);
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, closestLineTF);
+ gl.beginTransformFeedback(gl.POINTS);
+ gl.drawArrays(gl.POINTS, 0, numPoints);
+ gl.endTransformFeedback();
+ gl.bindTransformFeedback(gl.TRANSFORM_FEEDBACK, null);
+ gl.disable(gl.RASTERIZER_DISCARD);
+
+ // рисуем все
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
+
+ // рисуем все линии серым цветом
+ gl.bindVertexArray(null);
+ gl.useProgram(drawLinesPrg);
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, linesTex);
+ gl.uniform1i(drawLinesPrgLocs.linesTex, 0);
+ gl.uniformMatrix4fv(drawLinesPrgLocs.matrix, false, matrix);
+ gl.drawArrays(gl.LINES, 0, numLineSegments * 2);
+
+ // рисуем ближайшие линии
+ gl.bindVertexArray(drawClosestLinesVA);
+ gl.useProgram(drawClosestLinesPrg);
+ gl.activeTexture(gl.TEXTURE0);
+ gl.bindTexture(gl.TEXTURE_2D, linesTex);
+ gl.uniform1i(drawClosestLinesPrgLocs.linesTex, 0);
+ gl.uniform1f(drawClosestLinesPrgLocs.numPoints, numPoints);
+ gl.uniformMatrix4fv(drawClosestLinesPrgLocs.matrix, false, matrix);
+ gl.drawArraysInstanced(gl.LINES, 0, 2, numPoints);
+
+ // рисуем точки
+ gl.bindVertexArray(drawPointsVA);
+ gl.useProgram(drawPointsPrg);
+ gl.uniform1f(drawPointsPrgLocs.numPoints, numPoints);
+ gl.uniformMatrix4fv(drawPointsPrgLocs.matrix, false, matrix);
+ gl.drawArrays(gl.POINTS, 0, numPoints);
+
+ requestAnimationFrame(render);
+}
+requestAnimationFrame(render);
+```
+
+{{{example url="../webgl-gpgpu-closest-line-dynamic-transformfeedback.html"}}}
+
+## Важные замечания
+
+* GPGPU в WebGL1 в основном ограничен использованием 2D массивов в качестве вывода (текстуры).
+ WebGL2 добавляет возможность просто обрабатывать 1D массив произвольного размера через
+ transform feedback.
+
+ Если вам интересно, посмотрите [ту же статью для webgl1](https://webglfundamentals.org/webgl/lessons/webgl-gpgpu.html), чтобы увидеть, как все это было сделано, используя только возможность
+ вывода в текстуры. Конечно, с небольшим размышлением это должно быть очевидно.
+
+ Версии WebGL2, использующие текстуры вместо transform feedback, также доступны,
+ поскольку использование `texelFetch` и наличие большего количества форматов текстур немного изменяет
+ их реализации.
+
+ * [частицы](../webgl-gpgpu-particles.html)
+ * [результаты ближайших линий](../webgl-gpgpu-closest-line-results.html)
+ * [визуализация ближайших линий](../webgl-gpgpu-closest-line.html)
+ * [динамические ближайшие линии](../webgl-gpgpu-closest-line-dynamic.html)
+
+* Ошибка Firefox
+
+ Firefox начиная с версии 84 имеет [ошибку](https://bugzilla.mozilla.org/show_bug.cgi?id=1677552) в том,
+ что он неправильно требует наличия по крайней мере одного активного атрибута, который использует делитель 0 при вызове
+ `drawArraysIndexed`. Это означает, что пример выше, где мы рисуем ближайшие линии, используя
+ `drawArraysIndexed`, не работает.
+
+ Чтобы обойти это, мы можем создать буфер, который просто содержит `[0, 1]` в нем, и использовать его
+ на атрибуте для того, как мы использовали `gl_VertexID % 2`. Вместо этого мы будем использовать
+
+ ```glsl
+ in int endPoint; // нужно для firefox
+
+ ...
+ -int linePointId = closestNdx * 2 + gl_VertexID % 2;
+ +int linePointId = closestNdx * 2 + endPoint;
+ ...
+ ```
+
+ что [сделает это работающим в firefox](../webgl/webgl-gpgpu-closest-line-dynamic-transformfeedback-ff.html).
+
+* GPU не имеют той же точности, что и CPU.
+
+ Проверьте ваши результаты и убедитесь, что они приемлемы.
+
+* Есть накладные расходы на GPGPU.
+
+ В первых нескольких примерах выше мы вычислили некоторые
+ данные, используя WebGL, а затем прочитали результаты. Настройка буферов и текстур,
+ установка атрибутов и uniform переменных занимает время. Достаточно времени, чтобы для чего-либо
+ меньше определенного размера было бы лучше просто сделать это в JavaScript.
+ Фактические примеры умножения 6 чисел или сложения 3 пар чисел
+ слишком малы для того, чтобы GPGPU был полезен. Где находится эта граница
+ не определено. Экспериментируйте, но просто догадка, что если вы не делаете по крайней мере
+ 1000 или больше вещей, оставьте это в JavaScript.
+
+* `readPixels` и `getBufferSubData` медленные
+
+ Чтение результатов из WebGL медленное, поэтому важно избегать этого
+ как можно больше. В качестве примера ни система частиц выше, ни
+ пример динамических ближайших линий никогда
+ не читают результаты обратно в JavaScript. Где можете, держите результаты
+ на GPU как можно дольше. Другими словами, вы могли бы сделать что-то
+ вроде
+
+ * вычисляем что-то на GPU
+ * читаем результат
+ * подготавливаем результат для следующего шага
+ * загружаем подготовленный результат на GPU
+ * вычисляем что-то на GPU
+ * читаем результат
+ * подготавливаем результат для следующего шага
+ * загружаем подготовленный результат на GPU
+ * вычисляем что-то на GPU
+ * читаем результат
+
+ тогда как через творческие решения было бы намного быстрее, если бы вы могли
+
+ * вычисляем что-то на GPU
+ * подготавливаем результат для следующего шага, используя GPU
+ * вычисляем что-то на GPU
+ * подготавливаем результат для следующего шага, используя GPU
+ * вычисляем что-то на GPU
+ * читаем результат
+
+ Наш пример динамических ближайших линий делал это. Результаты никогда не покидают
+ GPU.
+
+ В качестве другого примера я однажды написал шейдер для вычисления гистограммы. Затем я прочитал
+ результаты обратно в JavaScript, вычислил минимальные и максимальные значения,
+ затем нарисовал изображение обратно на canvas, используя эти минимальные и максимальные значения
+ как uniform переменные для автоматического выравнивания изображения.
+
+ Но оказалось, что вместо чтения гистограммы обратно в JavaScript
+ я мог вместо этого запустить шейдер на самой гистограмме, который генерировал
+ 2-пиксельную текстуру с минимальными и максимальными значениями в текстуре.
+
+ Я мог затем передать эту 2-пиксельную текстуру в 3-й шейдер, который
+ мог читать для минимальных и максимальных значений. Нет необходимости читать их из
+ GPU для установки uniform переменных.
+
+ Аналогично для отображения самой гистограммы я сначала читал данные гистограммы
+ из GPU, но позже я вместо этого написал шейдер, который мог
+ визуализировать данные гистограммы напрямую, убрав необходимость читать их
+ обратно в JavaScript.
+
+ Делая это, весь процесс оставался на GPU и, вероятно, был намного
+ быстрее.
+
+* GPU могут делать много вещей параллельно, но большинство не могут многозадачно так же, как
+ CPU может. GPU обычно не могут делать "[вытесняющую многозадачность](https://www.google.com/search?q=preemptive+multitasking)".
+ Это означает, что если вы дадите им очень сложный шейдер, который, скажем, занимает 5 минут для
+ выполнения, они потенциально заморозят всю вашу машину на 5 минут.
+ Большинство хорошо сделанных ОС справляются с этим, заставляя CPU проверять, сколько времени прошло
+ с тех пор, как они дали последнюю команду GPU. Если прошло слишком много времени (5-6 секунд)
+ и GPU не ответил, то их единственный вариант - сбросить GPU.
+
+ Это одна из причин, почему WebGL может *потерять контекст* и вы получите сообщение "Aw, rats!"
+ или подобное.
+
+ Легко дать GPU слишком много работы, но в графике это не *так*
+ часто доводить до уровня 5-6 секунд. Обычно это больше похоже на уровень 0.1
+ секунды, что все еще плохо, но обычно вы хотите, чтобы графика работала быстро
+ и поэтому программист, надеюсь, оптимизирует или найдет другую технику
+ для поддержания отзывчивости их приложения.
+
+ GPGPU, с другой стороны, вы можете действительно захотеть дать GPU тяжелую задачу
+ для выполнения. Здесь нет простого решения. Мобильный телефон имеет гораздо менее мощный
+ GPU, чем топовый ПК. Помимо собственного тайминга, нет способа
+ точно знать, сколько работы вы можете дать GPU, прежде чем это "слишком медленно"
+
+ У меня нет решения для предложения. Только предупреждение, что в зависимости от того, что вы
+ пытаетесь сделать, вы можете столкнуться с этой проблемой.
+
+* Мобильные устройства обычно не поддерживают рендеринг в текстуры с плавающей точкой
+
+ Есть различные способы обойти эту проблему. Один из способов - вы можете
+ использовать функции GLSL `floatBitsToInt`, `floatBitsToUint`, `IntBitsToFloat`,
+ и `UintBitsToFloat`.
+
+ В качестве примера, [версия на основе текстур примера с частицами](../webgl-gpgpu-particles.html)
+ должна записывать в текстуры с плавающей точкой. Мы могли бы исправить это так, чтобы это не требовало их, объявив
+ нашу текстуру как тип `RG32I` (32-битные целочисленные текстуры), но все еще
+ загружать float значения.
+
+ В шейдере нам нужно будет читать текстуры как целые числа и декодировать их
+ в float, а затем кодировать результат обратно в целые числа. Например:
+
+ ```glsl
+ #version 300 es
+ precision highp float;
+
+ -uniform highp sampler2D positionTex;
+ -uniform highp sampler2D velocityTex;
+ +uniform highp isampler2D positionTex;
+ +uniform highp isampler2D velocityTex;
+ uniform vec2 canvasDimensions;
+ uniform float deltaTime;
+
+ out ivec4 outColor;
+
+ vec2 euclideanModulo(vec2 n, vec2 m) {
+ return mod(mod(n, m) + m, m);
+ }
+
+ void main() {
+ // будет одна скорость на позицию
+ // поэтому текстура скорости и текстура позиции
+ // имеют одинаковый размер.
+
+ // кроме того, мы генерируем новые позиции
+ // поэтому мы знаем, что наше назначение того же размера
+ // что и наш источник
+
+ // вычисляем координаты текстуры из gl_FragCoord;
+ ivec2 texelCoord = ivec2(gl_FragCoord.xy);
+
+ - vec2 position = texelFetch(positionTex, texelCoord, 0).xy;
+ - vec2 velocity = texelFetch(velocityTex, texelCoord, 0).xy;
+ + vec2 position = intBitsToFloat(texelFetch(positionTex, texelCoord, 0).xy);
+ + vec2 velocity = intBitsToFloat(texelFetch(velocityTex, texelCoord, 0).xy);
+ vec2 newPosition = euclideanModulo(position + velocity * deltaTime, canvasDimensions);
+
+ - outColor = vec4(newPosition, 0, 1);
+ + outColor = ivec4(floatBitsToInt(newPosition), 0, 1);
+ }
+ ```
+
+ [Вот рабочий пример](../webgl-gpgpu-particles-no-floating-point-textures.html)
+
+Я надеюсь, что эти примеры помогли вам понять ключевую идею GPGPU в WebGL
+- это просто тот факт, что WebGL читает из и записывает в массивы **данных**,
+а не пикселей.
+
+Шейдеры работают аналогично функциям `map` в том, что функция, которая вызывается
+для каждого значения, не может решить, где будет храниться ее значение.
+Скорее это решается извне функции. В случае WebGL
+это решается тем, как вы настраиваете то, что рисуете. Как только вы вызываете `gl.drawXXX`
+шейдер будет вызван для каждого нужного значения с вопросом "какое значение я должен
+сделать этим?"
+
+И это действительно все.
+
+---
+
+Поскольку мы создали некоторые частицы через GPGPU, есть [это замечательное видео](https://www.youtube.com/watch?v=X-iSQQgOd1A), которое во второй половине
+использует compute шейдеры для симуляции "слизи".
+
+Используя техники выше
вот это переведено в WebGL2.
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-how-it-works.md b/webgl/lessons/ru/webgl-how-it-works.md
index aa9e3ed68..e8864453b 100644
--- a/webgl/lessons/ru/webgl-how-it-works.md
+++ b/webgl/lessons/ru/webgl-how-it-works.md
@@ -197,4 +197,197 @@ v_color, который мы объявили.
// ищем, куда должны идти данные вершин.
var positionLocation = gl.getAttribLocation(program, "a_position");
- + var colorLocation = gl.getAttribLocation(program, "a_color");
\ No newline at end of file
+ + var colorLocation = gl.getAttribLocation(program, "a_color");
+ ...
+ + // Создаем буфер для цветов.
+ + var buffer = gl.createBuffer();
+ + gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
+ +
+ + // Устанавливаем цвета.
+ + setColors(gl);
+
+ // настраиваем атрибуты
+ ...
+ + // говорим атрибуту цвета, как извлекать данные из текущего ARRAY_BUFFER
+ + gl.enableVertexAttribArray(colorLocation);
+ + var size = 4;
+ + var type = gl.FLOAT;
+ + var normalize = false;
+ + var stride = 0;
+ + var offset = 0;
+ + gl.vertexAttribPointer(colorLocation, size, type, normalize, stride, offset);
+
+ ...
+
+ +// Заполняем буфер цветами для 2 треугольников
+ +// которые составляют прямоугольник.
+ +function setColors(gl) {
+ + // Выбираем 2 случайных цвета.
+ + var r1 = Math.random();
+ + var b1 = Math.random();
+ + var g1 = Math.random();
+ +
+ + var r2 = Math.random();
+ + var b2 = Math.random();
+ + var g2 = Math.random();
+ +
+ + gl.bufferData(
+ + gl.ARRAY_BUFFER,
+ + new Float32Array(
+ + [ r1, b1, g1, 1,
+ + r1, b1, g1, 1,
+ + r1, b1, g1, 1,
+ + r2, b2, g2, 1,
+ + r2, b2, g2, 1,
+ + r2, b2, g2, 1]),
+ + gl.STATIC_DRAW);
+ +}
+
+И вот результат.
+
+{{{example url="../webgl-2d-rectangle-with-2-colors.html" }}}
+
+Обратите внимание, что у нас есть 2 треугольника сплошного цвета. Тем не менее, мы передаем значения
+в *varying*, поэтому они варьируются или интерполируются по
+треугольнику. Просто мы использовали тот же цвет на каждой из 3 вершин
+каждого треугольника. Если мы сделаем каждый цвет разным, мы увидим
+интерполяцию.
+
+ // Заполняем буфер цветами для 2 треугольников
+ // которые составляют прямоугольник.
+ function setColors(gl) {
+ // Делаем каждую вершину разным цветом.
+ gl.bufferData(
+ gl.ARRAY_BUFFER,
+ new Float32Array(
+ * [ Math.random(), Math.random(), Math.random(), 1,
+ * Math.random(), Math.random(), Math.random(), 1,
+ * Math.random(), Math.random(), Math.random(), 1,
+ * Math.random(), Math.random(), Math.random(), 1,
+ * Math.random(), Math.random(), Math.random(), 1,
+ * Math.random(), Math.random(), Math.random(), 1]),
+ gl.STATIC_DRAW);
+ }
+
+И теперь мы видим интерполированный *varying*.
+
+{{{example url="../webgl-2d-rectangle-with-random-colors.html" }}}
+
+Не очень захватывающе, я полагаю, но это демонстрирует использование более чем одного
+атрибута и передачу данных от вершинного шейдера к фрагментному шейдеру. Если
+вы посмотрите на [примеры обработки изображений](webgl-image-processing.html),
+вы увидите, что они также используют дополнительный атрибут для передачи координат текстуры.
+
+## Что делают эти команды буфера и атрибута?
+
+Буферы - это способ получения данных вершин и других данных на вершину на
+GPU. `gl.createBuffer` создает буфер.
+`gl.bindBuffer` устанавливает этот буфер как буфер для работы.
+`gl.bufferData` копирует данные в текущий буфер.
+
+Как только данные находятся в буфере, нам нужно сказать WebGL, как извлекать данные из
+него и предоставлять их атрибутам вершинного шейдера.
+
+Для этого сначала мы спрашиваем WebGL, какие локации он назначил
+атрибутам. Например, в коде выше у нас есть
+
+ // ищем, куда должны идти данные вершин.
+ var positionLocation = gl.getAttribLocation(program, "a_position");
+ var colorLocation = gl.getAttribLocation(program, "a_color");
+
+Как только мы знаем локацию атрибута, мы выдаем 2 команды.
+
+ gl.enableVertexAttribArray(location);
+
+Эта команда говорит WebGL, что мы хотим предоставить данные из буфера.
+
+ gl.vertexAttribPointer(
+ location,
+ numComponents,
+ typeOfData,
+ normalizeFlag,
+ strideToNextPieceOfData,
+ offsetIntoBuffer);
+
+И эта команда говорит WebGL получать данные из буфера, который был последним
+привязан с gl.bindBuffer, сколько компонентов на вершину (1 - 4), какой
+тип данных (`BYTE`, `FLOAT`, `INT`, `UNSIGNED_SHORT`, и т.д.), шаг
+который означает, сколько байт пропустить, чтобы получить от одного куска данных к
+следующему куску данных, и смещение для того, как далеко в буфере находятся наши данные.
+
+Количество компонентов всегда от 1 до 4.
+
+Если вы используете 1 буфер на тип данных, то и шаг, и смещение могут
+всегда быть 0. 0 для шага означает "использовать шаг, который соответствует типу и
+размеру". 0 для смещения означает начать с начала буфера. Установка
+их в значения, отличные от 0, более сложна, и хотя это может иметь некоторые
+преимущества с точки зрения производительности, это не стоит усложнения, если только
+вы не пытаетесь довести WebGL до его абсолютных пределов.
+
+Я надеюсь, что это проясняет буферы и атрибуты.
+
+Вы можете взглянуть на эту
+[интерактивную диаграмму состояния](/webgl/lessons/resources/webgl-state-diagram.html)
+для другого способа понимания того, как работает WebGL.
+
+Далее давайте пройдемся по [шейдерам и GLSL](webgl-shaders-and-glsl.html).
+
+
Для чего нужен normalizeFlag в vertexAttribPointer?
+
+Флаг нормализации предназначен для всех не-плавающих типов. Если вы передаете
+false, то значения будут интерпретироваться как тип, которым они являются. BYTE идет
+от -128 до 127, UNSIGNED_BYTE идет от 0 до 255, SHORT идет от -32768 до 32767 и т.д...
+
+
+Если вы устанавливаете флаг нормализации в true, то значения BYTE (-128 до 127)
+представляют значения -1.0 до +1.0, UNSIGNED_BYTE (0 до 255) становятся 0.0 до +1.0.
+Нормализованный SHORT также идет от -1.0 до +1.0, просто у него больше разрешения, чем у BYTE.
+
+
+Самое распространенное использование нормализованных данных - для цветов. Большую часть времени цвета
+идут только от 0.0 до 1.0. Использование полного float для каждого красного, зеленого, синего и альфа
+использовало бы 16 байт на вершину на цвет. Если у вас сложная геометрия, это
+может сложиться в много байт. Вместо этого вы могли бы конвертировать ваши цвета в UNSIGNED_BYTE,
+где 0 представляет 0.0, а 255 представляет 1.0. Теперь вам понадобилось бы только 4 байта на цвет
+на вершину, экономия 75%.
+
+
Давайте изменим наш код, чтобы делать это. Когда мы говорим WebGL, как извлекать наши цвета, мы бы использовали
+
+ var size = 4;
+* var type = gl.UNSIGNED_BYTE;
+* var normalize = true;
+ var stride = 0;
+ var offset = 0;
+ gl.vertexAttribPointer(colorLocation, size, type, normalize, stride, offset);
+
+
И когда мы заполняем наш буфер цветами, мы бы использовали
+
+// Заполняем буфер цветами для 2 треугольников
+// которые составляют прямоугольник.
+function setColors(gl) {
+ // Выбираем 2 случайных цвета.
+ var r1 = Math.random() * 256; // 0 до 255.99999
+ var b1 = Math.random() * 256; // эти значения
+ var g1 = Math.random() * 256; // будут обрезаны
+ var r2 = Math.random() * 256; // когда сохранены в
+ var b2 = Math.random() * 256; // Uint8Array
+ var g2 = Math.random() * 256;
+
+ gl.bufferData(
+ gl.ARRAY_BUFFER,
+ new Uint8Array( // Uint8Array
+ [ r1, b1, g1, 255,
+ r1, b1, g1, 255,
+ r1, b1, g1, 255,
+ r2, b2, g2, 255,
+ r2, b2, g2, 255,
+ r2, b2, g2, 255]),
+ gl.STATIC_DRAW);
+}
+
+
+Вот этот пример.
+
+
+{{{example url="../webgl-2d-rectangle-with-2-byte-colors.html" }}}
+
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-image-processing-continued.md b/webgl/lessons/ru/webgl-image-processing-continued.md
index 344cacbdc..d5f7da233 100644
--- a/webgl/lessons/ru/webgl-image-processing-continued.md
+++ b/webgl/lessons/ru/webgl-image-processing-continued.md
@@ -195,4 +195,83 @@ TOC: Продвинутая обработка изображений
var count = 6;
gl.drawArrays(primitiveType, offset, count);
}
-```
\ No newline at end of file
+```
+
+Вот рабочая версия с немного более гибким UI. Отметьте эффекты,
+чтобы включить их. Перетаскивайте эффекты, чтобы изменить порядок их применения.
+
+{{{example url="../webgl-2d-image-processing.html" }}}
+
+Некоторые вещи, которые я должен объяснить.
+
+Вызов `gl.bindFramebuffer` с `null` говорит WebGL, что вы хотите рендерить
+на canvas вместо одного из ваших framebuffer'ов.
+
+Также framebuffer'ы могут работать или не работать в зависимости от того, какие привязки
+вы на них помещаете. Есть список того, какие типы и комбинации привязок
+должны всегда работать. Используемая здесь, одна текстура `RGBA`/`UNSIGNED_BYTE`,
+назначенная точке привязки `COLOR_ATTACHMENT0`, должна всегда работать.
+Более экзотические форматы текстур и/или комбинации привязок могут не работать.
+В этом случае вы должны привязать framebuffer и затем вызвать
+`gl.checkFramebufferStatus` и посмотреть, возвращает ли он `gl.FRAMEBUFFER_COMPLETE`.
+Если да, то все в порядке. Если нет, вам нужно будет сказать пользователю использовать
+что-то другое. К счастью, WebGL2 поддерживает многие форматы и комбинации.
+
+WebGL должен преобразовывать из [clip space](webgl-fundamentals.html) обратно в пиксели.
+Он делает это на основе настроек `gl.viewport`. Поскольку framebuffer'ы,
+в которые мы рендерим, имеют другой размер, чем canvas, нам нужно установить
+viewport соответствующим образом в зависимости от того, рендерим ли мы в текстуру или canvas.
+
+Наконец, в [оригинальном примере](webgl-fundamentals.html) мы переворачивали координату Y
+при рендеринге, потому что WebGL отображает canvas с 0,0 в левом нижнем углу
+вместо более традиционного для 2D левого верхнего угла. Это не нужно
+при рендеринге в framebuffer. Поскольку framebuffer никогда не отображается,
+какая часть является верхом и низом, не имеет значения. Все, что имеет значение,
+это то, что пиксель 0,0 в framebuffer соответствует 0,0 в наших вычислениях.
+Чтобы справиться с этим, я сделал возможным установить, переворачивать или нет, добавив
+еще один uniform вход в вызов шейдера `u_flipY`.
+
+```
+...
++uniform float u_flipY;
+...
+
+void main() {
+ ...
++ gl_Position = vec4(clipSpace * vec2(1, u_flipY), 0, 1);
+ ...
+}
+```
+
+И затем мы можем установить это при рендеринге с помощью
+
+```
+ ...
++ var flipYLocation = gl.getUniformLocation(program, "u_flipY");
+
+ ...
+
++ // не переворачиваем
++ gl.uniform1f(flipYLocation, 1);
+
+ ...
+
++ // переворачиваем
++ gl.uniform1f(flipYLocation, -1);
+```
+
+Я сохранил этот пример простым, используя одну GLSL программу, которая может достичь
+множественных эффектов. Если бы вы хотели делать полноценную обработку изображений, вам, вероятно,
+понадобилось бы много GLSL программ. Программа для настройки оттенка, насыщенности и яркости.
+Другая для яркости и контрастности. Одна для инвертирования, другая для настройки
+уровней и т.д. Вам нужно будет изменить код для переключения GLSL программ и обновления
+параметров для этой конкретной программы. Я рассматривал написание этого примера,
+но это упражнение лучше оставить читателю, потому что множественные GLSL программы, каждая
+со своими потребностями в параметрах, вероятно, означает серьезный рефакторинг, чтобы все
+не превратилось в большую путаницу спагетти-кода.
+
+Я надеюсь, что этот и предыдущие примеры сделали WebGL немного более
+доступным, и я надеюсь, что начало с 2D помогает сделать WebGL немного легче для
+понимания. Если я найду время, я попробую написать [еще несколько статей](webgl-2d-translation.html)
+о том, как делать 3D, а также больше деталей о [том, что WebGL действительно делает под капотом](webgl-how-it-works.html).
+Для следующего шага рассмотрите изучение [как использовать 2 или более текстур](webgl-2-textures.html).
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-image-processing.md b/webgl/lessons/ru/webgl-image-processing.md
index 5665f0090..a04a6387a 100644
--- a/webgl/lessons/ru/webgl-image-processing.md
+++ b/webgl/lessons/ru/webgl-image-processing.md
@@ -190,5 +190,140 @@ in vec2 v_texCoord;
out vec4 outColor;
void main() {
-+ vec2 onePixel = vec2(1) / vec2(textureSize(u_image, 0));
-```
\ No newline at end of file
+ vec2 onePixel = vec2(1) / vec2(textureSize(u_image, 0));
+
+ // усредняем левый, средний и правый пиксели
+ outColor = (
+ texture(u_image, v_texCoord) +
+ texture(u_image, v_texCoord + vec2( onePixel.x, 0.0)) +
+ texture(u_image, v_texCoord + vec2(-onePixel.x, 0.0))) / 3.0;
+}
+```
+
+Сравните с неразмытым изображением выше.
+
+{{{example url="../webgl-2d-image-blend.html" }}}
+
+Теперь, когда мы знаем, как ссылаться на другие пиксели, давайте используем свёрточное ядро
+для выполнения множества распространённых операций обработки изображений. В этом случае мы будем использовать ядро 3x3.
+Свёрточное ядро — это просто матрица 3x3, где каждый элемент матрицы представляет
+насколько умножить 8 пикселей вокруг пикселя, который мы рендерим. Затем мы
+делим результат на вес ядра (сумма всех значений в ядре)
+или 1.0, в зависимости от того, что больше. [Вот довольно хорошая статья об этом](https://docs.gimp.org/2.6/en/plug-in-convmatrix.html).
+И [вот ещё одна статья, показывающая реальный код, если бы
+вы писали это вручную на C++](https://www.codeproject.com/KB/graphics/ImageConvolution.aspx).
+
+В нашем случае мы будем делать эту работу в шейдере, так что вот новый фрагментный шейдер:
+
+```
+#version 300 es
+
+// фрагментные шейдеры не имеют точности по умолчанию, поэтому нужно
+// выбрать одну. highp — хороший выбор по умолчанию. Это "высокая точность"
+precision highp float;
+
+// наша текстура
+uniform sampler2D u_image;
+
+// данные свёрточного ядра
+uniform float u_kernel[9];
+uniform float u_kernelWeight;
+
+// координаты текстуры, переданные из вершинного шейдера
+in vec2 v_texCoord;
+
+// объявляем выход для фрагментного шейдера
+out vec4 outColor;
+
+void main() {
+ vec2 onePixel = vec2(1) / vec2(textureSize(u_image, 0));
+
+ vec4 colorSum =
+ texture(u_image, v_texCoord + onePixel * vec2(-1, -1)) * u_kernel[0] +
+ texture(u_image, v_texCoord + onePixel * vec2( 0, -1)) * u_kernel[1] +
+ texture(u_image, v_texCoord + onePixel * vec2( 1, -1)) * u_kernel[2] +
+ texture(u_image, v_texCoord + onePixel * vec2(-1, 0)) * u_kernel[3] +
+ texture(u_image, v_texCoord + onePixel * vec2( 0, 0)) * u_kernel[4] +
+ texture(u_image, v_texCoord + onePixel * vec2( 1, 0)) * u_kernel[5] +
+ texture(u_image, v_texCoord + onePixel * vec2(-1, 1)) * u_kernel[6] +
+ texture(u_image, v_texCoord + onePixel * vec2( 0, 1)) * u_kernel[7] +
+ texture(u_image, v_texCoord + onePixel * vec2( 1, 1)) * u_kernel[8] ;
+ outColor = vec4((colorSum / u_kernelWeight).rgb, 1);
+}
+```
+
+В JavaScript нам нужно предоставить свёрточное ядро и его вес:
+
+ function computeKernelWeight(kernel) {
+ var weight = kernel.reduce(function(prev, curr) {
+ return prev + curr;
+ });
+ return weight <= 0 ? 1 : weight;
+ }
+
+ ...
+ var kernelLocation = gl.getUniformLocation(program, "u_kernel[0]");
+ var kernelWeightLocation = gl.getUniformLocation(program, "u_kernelWeight");
+ ...
+ var edgeDetectKernel = [
+ -1, -1, -1,
+ -1, 8, -1,
+ -1, -1, -1
+ ];
+
+ // задаём ядро и его вес
+ gl.uniform1fv(kernelLocation, edgeDetectKernel);
+ gl.uniform1f(kernelWeightLocation, computeKernelWeight(edgeDetectKernel));
+ ...
+
+И вуаля... Используйте выпадающий список для выбора разных ядер.
+
+{{{example url="../webgl-2d-image-3x3-convolution.html" }}}
+
+Я надеюсь, что эта статья убедила вас, что обработка изображений в WebGL довольно проста. Далее
+я расскажу [как применить более одного эффекта к изображению](webgl-image-processing-continued.html).
+
+
+
Что такое текстурные юниты?
+Когда вы вызываете
gl.draw???
ваш шейдер может ссылаться на текстуры. Текстуры привязаны
+к текстурным юнитам. Хотя машина пользователя может поддерживать больше, все реализации WebGL2
+обязаны поддерживать как минимум 16 текстурных юнитов. К какому текстурному юниту ссылается каждый sampler uniform,
+устанавливается путём поиска местоположения этого sampler uniform и затем установки
+индекса текстурного юнита, на который вы хотите, чтобы он ссылался.
+
+Например:
+
+var textureUnitIndex = 6; // используем текстурный юнит 6.
+var u_imageLoc = gl.getUniformLocation(
+ program, "u_image");
+gl.uniform1i(u_imageLoc, textureUnitIndex);
+
+
+Чтобы установить текстуры на разных юнитах, вы вызываете gl.activeTexture и затем привязываете текстуру, которую хотите на этом юните. Пример:
+
+
+// Привязываем someTexture к текстурному юниту 6.
+gl.activeTexture(gl.TEXTURE6);
+gl.bindTexture(gl.TEXTURE_2D, someTexture);
+
+
+Это тоже работает:
+
+
+var textureUnitIndex = 6; // используем текстурный юнит 6.
+// Привязываем someTexture к текстурному юниту 6.
+gl.activeTexture(gl.TEXTURE0 + textureUnitIndex);
+gl.bindTexture(gl.TEXTURE_2D, someTexture);
+
+
+
+
+
Что означают префиксы a_, u_, и v_ перед переменными в GLSL?
+
+Это просто соглашение об именовании. Они не обязательны, но для меня это делает легче увидеть с первого взгляда,
+откуда приходят значения. a_ для атрибутов, которые являются данными, предоставленными буферами. u_ для uniform'ов,
+которые являются входами в шейдеры, v_ для varying'ов, которые являются значениями, переданными из вершинного шейдера во
+фрагментный шейдер и интерполированными (или изменёнными) между вершинами для каждого нарисованного пикселя.
+Смотрите Как это работает для более подробной информации.
+
+
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-instanced-drawing.md b/webgl/lessons/ru/webgl-instanced-drawing.md
index 9f4000e22..5ecefd98b 100644
--- a/webgl/lessons/ru/webgl-instanced-drawing.md
+++ b/webgl/lessons/ru/webgl-instanced-drawing.md
@@ -205,14 +205,53 @@ void main() {
`;
```
----
+Теперь нам нужно изменить шейдеры, чтобы использовать атрибуты вместо uniform'ов:
-Теперь нам нужно реализовать инстансинг через атрибуты и буферы:
+```js
+const vertexShaderSource = `#version 300 es
+in vec4 a_position;
+in vec4 color;
+in mat4 matrix;
+
+out vec4 v_color;
+
+void main() {
+ // Умножаем позицию на матрицу
+ gl_Position = matrix * a_position;
+
+ // Передаём цвет вершины во фрагментный шейдер
+ v_color = color;
+}
+`;
+
+const fragmentShaderSource = `#version 300 es
+precision highp float;
+
+in vec4 v_color;
+
+out vec4 outColor;
+
+void main() {
+ outColor = v_color;
+}
+`;
+```
+
+Теперь нам нужно получить локации атрибутов вместо uniform'ов:
```js
-// настраиваем матрицы, по одной на экземпляр
-const numInstances = 5;
-// создаём типизированный массив с одним view на матрицу
+const program = webglUtils.createProgramFromSources(gl,
+ [vertexShaderSource, fragmentShaderSource]);
+
+const positionLoc = gl.getAttribLocation(program, 'a_position');
+const colorLoc = gl.getAttribLocation(program, 'color');
+const matrixLoc = gl.getAttribLocation(program, 'matrix');
+```
+
+Теперь нам нужно создать буферы для матриц и цветов. Для матриц мы создадим один большой буфер:
+
+```js
+// Создаём буфер для всех матриц
const matrixData = new Float32Array(numInstances * 16);
const matrices = [];
for (let i = 0; i < numInstances; ++i) {
@@ -223,29 +262,64 @@ for (let i = 0; i < numInstances; ++i) {
byteOffsetToMatrix,
numFloatsForView));
}
+```
+Таким образом, когда мы хотим ссылаться на данные всех матриц,
+мы можем использовать `matrixData`, но когда мы хотим любую отдельную матрицу,
+мы можем использовать `matrices[ndx]`.
+
+Нам также нужно создать буфер на GPU для этих данных.
+Нам нужно только выделить буфер в этот момент, нам не нужно
+предоставлять данные, поэтому 2-й параметр для `gl.bufferData`
+- это размер, который просто выделяет буфер.
+
+```js
const matrixBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
+// просто выделяем буфер
gl.bufferData(gl.ARRAY_BUFFER, matrixData.byteLength, gl.DYNAMIC_DRAW);
+```
+
+Обратите внимание, что мы передали `gl.DYNAMIC_DRAW` как последний параметр. Это *подсказка*
+для WebGL, что мы будем часто изменять эти данные.
+
+Теперь нам нужно настроить атрибуты для матриц.
+Атрибут матрицы - это `mat4`. `mat4` фактически использует
+4 последовательных слота атрибутов.
+```js
const bytesPerMatrix = 4 * 16;
for (let i = 0; i < 4; ++i) {
const loc = matrixLoc + i;
gl.enableVertexAttribArray(loc);
- // stride и offset
- const offset = i * 16;
+ // обратите внимание на stride и offset
+ const offset = i * 16; // 4 float на строку, 4 байта на float
gl.vertexAttribPointer(
- loc,
- 4,
- gl.FLOAT,
- false,
- bytesPerMatrix,
- offset,
+ loc, // location
+ 4, // размер (сколько значений брать из буфера за итерацию)
+ gl.FLOAT, // тип данных в буфере
+ false, // нормализовать
+ bytesPerMatrix, // stride, количество байт для перехода к следующему набору значений
+ offset, // смещение в буфере
);
+ // эта строка говорит, что этот атрибут изменяется только раз в 1 экземпляр
gl.vertexAttribDivisor(loc, 1);
}
+```
+
+Самая важная точка относительно инстансированного рисования - это
+вызов `gl.vertexAttribDivisor`. Он устанавливает, что этот
+атрибут переходит к следующему значению только раз в экземпляр.
+Это означает, что атрибуты `matrix` будут использовать первую матрицу для
+каждой вершины первого экземпляра, вторую матрицу для
+второго экземпляра и так далее.
-// настраиваем цвета, по одному на экземпляр
+Далее нам нужны цвета также в буфере. Эти данные не будут
+изменяться, по крайней мере в этом примере, поэтому мы просто загрузим
+данные.
+
+```js
+// настраиваем цвета, один на экземпляр
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER,
@@ -257,42 +331,109 @@ gl.bufferData(gl.ARRAY_BUFFER,
0, 1, 1, 1, // циан
]),
gl.STATIC_DRAW);
+```
+
+Нам также нужно настроить атрибут цвета:
+
+```js
+// устанавливаем атрибут для цвета
gl.enableVertexAttribArray(colorLoc);
gl.vertexAttribPointer(colorLoc, 4, gl.FLOAT, false, 0, 0);
+// эта строка говорит, что этот атрибут изменяется только раз в 1 экземпляр
gl.vertexAttribDivisor(colorLoc, 1);
+```
-// В рендере:
-function render(time) {
- time *= 0.001;
- webglUtils.resizeCanvasToDisplaySize(gl.canvas);
- gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
- gl.useProgram(program);
- gl.bindVertexArray(vao);
+Во время отрисовки вместо цикла по каждому экземпляру,
+установки uniform'ов матрицы и цвета, а затем вызова draw,
+мы сначала вычислим матрицу для каждого экземпляра.
- // обновляем все матрицы
- matrices.forEach((mat, ndx) => {
- m4.translation(-0.5 + ndx * 0.25, 0, 0, mat);
- m4.zRotate(mat, time * (0.1 + 0.1 * ndx), mat);
- });
- // загружаем все матрицы в буфер
- gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
- gl.bufferSubData(gl.ARRAY_BUFFER, 0, matrixData);
-
- // рисуем все экземпляры одной командой
- gl.drawArraysInstanced(
- gl.TRIANGLES,
- 0, // offset
- numVertices, // количество вершин на экземпляр
- numInstances // количество экземпляров
- );
+```js
+// обновляем все матрицы
+matrices.forEach((mat, ndx) => {
+ m4.translation(-0.5 + ndx * 0.25, 0, 0, mat);
+ m4.zRotate(mat, time * (0.1 + 0.1 * ndx), mat);
+});
+```
- requestAnimationFrame(render);
-}
-requestAnimationFrame(render);
+Поскольку наша библиотека матриц принимает необязательную матрицу назначения
+и поскольку наши матрицы - это просто представления `Float32Array` в
+большем `Float32Array`, когда мы закончили, все данные матриц
+готовы для прямой загрузки на GPU.
+
+```js
+// загружаем новые данные матриц
+gl.bindBuffer(gl.ARRAY_BUFFER, matrixBuffer);
+gl.bufferSubData(gl.ARRAY_BUFFER, 0, matrixData);
```
-Теперь мы вызываем только одну команду отрисовки, и WebGL сам перебирает экземпляры, используя данные из буферов для каждого экземпляра.
+Наконец мы можем нарисовать все экземпляры одним вызовом draw.
+
+```js
+gl.drawArraysInstanced(
+ gl.TRIANGLES,
+ 0, // offset
+ numVertices, // количество вершин на экземпляр
+ numInstances, // количество экземпляров
+);
+```
{{{example url="../webgl-instanced-drawing.html"}}}
-Инстансинг — мощный способ ускорить отрисовку множества одинаковых объектов с разными параметрами.
\ No newline at end of file
+В примере выше у нас было 3 вызова WebGL на фигуру * 5 фигур,
+что составляло 15 вызовов всего. Теперь у нас всего 2 вызова для всех 5 фигур,
+один для загрузки матриц, другой для рисования.
+
+Я думаю, это должно быть очевидно, но, возможно,
+это очевидно только мне, потому что я делал это слишком много. Код
+выше не учитывает соотношение сторон canvas.
+Он не использует [матрицу проекции](webgl-3d-orthographic.html)
+или [матрицу вида](webgl-3d-camera.html). Он был предназначен только
+для демонстрации инстансированного рисования. Если бы вы хотели проекцию и/или
+матрицу вида, мы могли бы добавить вычисление в JavaScript. Это означало бы
+больше работы для JavaScript. Более очевидный способ - добавить
+один или два uniform'а в вершинный шейдер.
+
+```js
+const vertexShaderSource = `#version 300 es
+in vec4 a_position;
+in vec4 color;
+in mat4 matrix;
+uniform mat4 projection;
+uniform mat4 view;
+
+out vec4 v_color;
+
+void main() {
+ // Умножаем позицию на матрицу
+ gl_Position = projection * view * matrix * a_position;
+
+ // Передаём цвет вершины во фрагментный шейдер
+ v_color = color;
+}
+`;
+```
+
+и затем найти их локации во время инициализации:
+
+```js
+const positionLoc = gl.getAttribLocation(program, 'a_position');
+const colorLoc = gl.getAttribLocation(program, 'color');
+const matrixLoc = gl.getAttribLocation(program, 'matrix');
+const projectionLoc = gl.getUniformLocation(program, 'projection');
+const viewLoc = gl.getUniformLocation(program, 'view');
+```
+
+и установить их соответствующим образом во время рендеринга.
+
+```js
+gl.useProgram(program);
+
+// устанавливаем матрицы вида и проекции, поскольку
+// они используются всеми экземплярами
+const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
+gl.uniformMatrix4fv(projectionLoc, false,
+ m4.orthographic(-aspect, aspect, -1, 1, -1, 1));
+gl.uniformMatrix4fv(viewLoc, false, m4.zRotation(time * .1));
+```
+
+{{{example url="../webgl-instanced-drawing-projection-view.html"}}}
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-less-code-more-fun.md b/webgl/lessons/ru/webgl-less-code-more-fun.md
index c3ba16672..090e7ee4e 100644
--- a/webgl/lessons/ru/webgl-less-code-more-fun.md
+++ b/webgl/lessons/ru/webgl-less-code-more-fun.md
@@ -196,4 +196,250 @@ var uniforms = {
gl.useProgram(program);
// Привязываем VAO, в котором уже все буферы и атрибуты
-```
\ No newline at end of file
+gl.bindVertexArray(vao);
+
+// Устанавливаем все uniform'ы и текстуры
+twgl.setUniforms(uniformSetters, uniforms);
+
+gl.drawArrays(...);
+```
+
+Это кажется намного меньше, проще и с меньшим количеством кода.
+
+Вы даже можете использовать несколько JavaScript-объектов для uniform'ов, если это подходит. Например:
+
+```
+// При инициализации
+var uniformSetters = twgl.createUniformSetters(gl, program);
+var attribSetters = twgl.createAttributeSetters(gl, program);
+
+// Настраиваем все буферы и атрибуты
+var attribs = {
+ a_position: { buffer: positionBuffer, numComponents: 3, },
+ a_normal: { buffer: normalBuffer, numComponents: 3, },
+ a_texcoord: { buffer: texcoordBuffer, numComponents: 2, },
+};
+var vao = twgl.createVAOAndSetAttributes(gl, attribSetters, attribs);
+
+// При инициализации или отрисовке
+var uniformsThatAreTheSameForAllObjects = {
+ u_lightWorldPos: [100, 200, 300],
+ u_viewInverse: computeInverseViewMatrix(),
+ u_lightColor: [1, 1, 1, 1],
+};
+
+var uniformsThatAreComputedForEachObject = {
+ u_worldViewProjection: perspective(...),
+ u_world: computeWorldMatrix(),
+ u_worldInverseTranspose: computeWorldInverseTransposeMatrix(),
+};
+
+var objects = [
+ { translation: [10, 50, 100],
+ materialUniforms: {
+ u_ambient: [0.1, 0.1, 0.1, 1],
+ u_diffuse: diffuseTexture,
+ u_specular: [1, 1, 1, 1],
+ u_shininess: 60,
+ u_specularFactor: 1,
+ },
+ },
+ { translation: [-120, 20, 44],
+ materialUniforms: {
+ u_ambient: [0.1, 0.2, 0.1, 1],
+ u_diffuse: someOtherDiffuseTexture,
+ u_specular: [1, 1, 0, 1],
+ u_shininess: 30,
+ u_specularFactor: 0.5,
+ },
+ },
+ { translation: [200, -23, -78],
+ materialUniforms: {
+ u_ambient: [0.2, 0.2, 0.1, 1],
+ u_diffuse: yetAnotherDiffuseTexture,
+ u_specular: [1, 0, 0, 1],
+ u_shininess: 45,
+ u_specularFactor: 0.7,
+ },
+ },
+];
+
+// При отрисовке
+gl.useProgram(program);
+
+// Настраиваем части, общие для всех объектов
+gl.bindVertexArray(vao);
+twgl.setUniforms(uniformSetters, uniformsThatAreTheSameForAllObjects);
+
+objects.forEach(function(object) {
+ computeMatricesForObject(object, uniformsThatAreComputedForEachObject);
+ twgl.setUniforms(uniformSetters, uniformsThatAreComputedForEachObject);
+ twgl.setUniforms(uniformSetters, object.materialUniforms);
+ gl.drawArrays(...);
+});
+```
+
+Вот пример использования этих вспомогательных функций:
+
+{{{example url="../webgl-less-code-more-fun.html" }}}
+
+Давайте сделаем ещё один маленький шаг дальше. В коде выше мы настроили переменную `attribs` с буферами, которые создали.
+Не показан код для настройки этих буферов. Например, если вы хотите создать позиции, нормали и координаты текстуры,
+вам может понадобиться такой код:
+
+ // один треугольник
+ var positions = [0, -10, 0, 10, 10, 0, -10, 10, 0];
+ var texcoords = [0.5, 0, 1, 1, 0, 1];
+ var normals = [0, 0, 1, 0, 0, 1, 0, 0, 1];
+
+ var positionBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
+
+ var texcoordBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texcoords), gl.STATIC_DRAW);
+
+ var normalBuffer = gl.createBuffer();
+ gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normals), gl.STATIC_DRAW);
+
+Похоже на паттерн, который мы тоже можем упростить:
+
+ // один треугольник
+ var arrays = {
+ position: { numComponents: 3, data: [0, -10, 0, 10, 10, 0, -10, 10, 0], },
+ texcoord: { numComponents: 2, data: [0.5, 0, 1, 1, 0, 1], },
+ normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1], },
+ };
+
+ var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
+ var vao = twgl.createVAOFromBufferInfo(gl, setters, bufferInfo);
+
+Намного короче!
+
+Вот это:
+
+{{{example url="../webgl-less-code-more-fun-triangle.html" }}}
+
+Это будет работать даже если у нас есть индексы. `createVAOFromBufferInfo`
+настроит все атрибуты и установит `ELEMENT_ARRAY_BUFFER`
+с вашими `indices`, так что когда вы привяжете этот VAO, вы сможете вызвать
+`gl.drawElements`.
+
+ // индексированный квадрат
+ var arrays = {
+ position: { numComponents: 3, data: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0], },
+ texcoord: { numComponents: 2, data: [0, 0, 0, 1, 1, 0, 1, 1], },
+ normal: { numComponents: 3, data: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1], },
+ indices: { numComponents: 3, data: [0, 1, 2, 1, 2, 3], },
+ };
+
+ var bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
+ var vao = twgl.createVAOFromBufferInfo(gl, setters, bufferInfo);
+
+и во время рендеринга мы можем вызвать `gl.drawElements` вместо `gl.drawArrays`.
+
+ ...
+
+ // Рисуем геометрию
+ gl.drawElements(gl.TRIANGLES, bufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
+
+Вот это:
+
+{{{example url="../webgl-less-code-more-fun-quad.html" }}}
+
+Наконец, мы можем пойти, как я считаю, возможно, слишком далеко. Учитывая, что `position` почти всегда имеет 3 компонента (x, y, z),
+`texcoords` почти всегда 2, индексы 3, а нормали 3, мы можем просто позволить системе угадать количество
+компонентов.
+
+ // индексированный квадрат
+ var arrays = {
+ position: [0, 0, 0, 10, 0, 0, 0, 10, 0, 10, 10, 0],
+ texcoord: [0, 0, 0, 1, 1, 0, 1, 1],
+ normal: [0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1],
+ indices: [0, 1, 2, 1, 2, 3],
+ };
+
+И эта версия:
+
+{{{example url="../webgl-less-code-more-fun-quad-guess.html" }}}
+
+Я не уверен, что лично мне нравится этот стиль. Угадывание меня беспокоит, потому что оно может угадать неправильно. Например,
+я могу решить добавить дополнительный набор координат текстуры в мой атрибут texcoord, и он
+угадает 2 и будет неправ. Конечно, если он угадает неправильно, вы можете просто указать это, как в примере выше.
+Я думаю, я беспокоюсь, что если код угадывания изменится, вещи людей могут сломаться. Это решать вам. Некоторым людям
+нравится, когда вещи максимально простые, как они считают.
+
+Почему бы нам не посмотреть на атрибуты в шейдерной программе, чтобы выяснить количество компонентов?
+Это потому, что часто предоставляют 3 компонента (x, y, z) из буфера, но используют `vec4` в
+шейдере. Для атрибутов WebGL автоматически установит `w = 1`. Но это означает, что мы не можем легко
+знать намерение пользователя, поскольку то, что они объявили в шейдере, может не соответствовать количеству
+компонентов, которые они предоставляют.
+
+Ища больше паттернов, есть это:
+
+ var program = twgl.createProgramFromSources(gl, [vs, fs]);
+ var uniformSetters = twgl.createUniformSetters(gl, program);
+ var attribSetters = twgl.createAttributeSetters(gl, program);
+
+Давайте упростим и это до просто:
+
+ var programInfo = twgl.createProgramInfo(gl, ["vertexshader", "fragmentshader"]);
+
+Который возвращает что-то вроде:
+
+ programInfo = {
+ program: WebGLProgram, // программа, которую мы только что скомпилировали
+ uniformSetters: ..., // setters, как возвращённые из createUniformSetters
+ attribSetters: ..., // setters, как возвращённые из createAttribSetters
+ }
+
+И это ещё одно небольшое упрощение. Это пригодится, когда мы начнём использовать
+несколько программ, поскольку это автоматически держит setters с программой, с которой они связаны.
+
+{{{example url="../webgl-less-code-more-fun-quad-programinfo.html" }}}
+
+Ещё одно, иногда у нас есть данные без индексов, и мы должны вызывать
+`gl.drawArrays`. В других случаях есть индексы, и мы должны вызывать `gl.drawElements`.
+Учитывая данные, которые у нас есть, мы можем легко проверить что именно, посмотрев на `bufferInfo.indices`.
+Если он существует, нам нужно вызвать `gl.drawElements`. Если нет, нам нужно вызвать `gl.drawArrays`.
+Так что есть функция `twgl.drawBufferInfo`, которая делает это. Она используется так:
+
+ twgl.drawBufferInfo(gl, bufferInfo);
+
+Если вы не передаёте 3-й параметр для типа примитива для рисования, он предполагает
+`gl.TRIANGLES`.
+
+Вот пример, где у нас есть неиндексированный треугольник и индексированный квадрат. Поскольку
+мы используем `twgl.drawBufferInfo`, код не должен изменяться, когда мы
+переключаем данные.
+
+{{{example url="../webgl-less-code-more-fun-drawbufferinfo.html" }}}
+
+В любом случае, это стиль, в котором я пытаюсь писать свои собственные WebGL-программы.
+Для уроков в этих туториалах, однако, я чувствовал, что должен использовать стандартные **многословные**
+способы, чтобы люди не путались в том, что является WebGL, а что моим собственным стилем. В какой-то момент
+показ всех шагов мешает сути, поэтому в будущем некоторые уроки будут
+использовать этот стиль.
+
+Не стесняйтесь использовать этот стиль в своём собственном коде. Функции `twgl.createProgramInfo`,
+`twgl.createVAOAndSetAttributes`, `twgl.createBufferInfoFromArrays` и `twgl.setUniforms`
+и т.д. являются частью библиотеки, которую я написал на основе этих идей. [Она называется `TWGL`](https://twgljs.org).
+Она рифмуется с wiggle и означает `Tiny WebGL`.
+
+Далее, [рисование множественных объектов](webgl-drawing-multiple-things.html).
+
+
+
Можем ли мы использовать setters напрямую?
+
+Для тех из вас, кто знаком с JavaScript, вы можете задаться вопросом, можете ли вы использовать setters
+напрямую, как это:
+
+
{{#escapehtml}}
+// При инициализации
+var uniformSetters = twgl.createUniformSetters(program);
+
+// При отрисовке
+uniformSetters.u_ambient([1, 0, 0, 1]); // установить цвет окружения в красный
+{{/escapehtml}}
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-load-obj-w-mtl.md b/webgl/lessons/ru/webgl-load-obj-w-mtl.md
index f3f66f014..6caa93e97 100644
--- a/webgl/lessons/ru/webgl-load-obj-w-mtl.md
+++ b/webgl/lessons/ru/webgl-load-obj-w-mtl.md
@@ -265,236 +265,79 @@ void main () {
`;
```
-Теперь у нас есть текстуры!
-
-{{{example url="../webgl-load-obj-w-mtl-w-textures.html"}}}
-
-Если посмотреть на .MTL-файл, можно увидеть `map_Ks` — это чёрно-белая текстура, которая определяет, насколько поверхность блестящая, или, иначе говоря, сколько specular-отражения используется.
-
-

-
-Чтобы использовать её, нужно обновить шейдер, ведь мы уже загружаем все текстуры.
-
-```js
-const fs = `#version 300 es
-precision highp float;
-
-in vec3 v_normal;
-in vec3 v_surfaceToView;
-in vec2 v_texcoord;
-in vec4 v_color;
-
-uniform vec3 diffuse;
-uniform sampler2D diffuseMap;
-uniform vec3 ambient;
-uniform vec3 emissive;
-uniform vec3 specular;
-uniform sampler2D specularMap;
-uniform float shininess;
-uniform float opacity;
-uniform vec3 u_lightDirection;
-uniform vec3 u_ambientLight;
-
-out vec4 outColor;
-
-void main () {
- vec3 normal = normalize(v_normal);
-
- vec3 surfaceToViewDirection = normalize(v_surfaceToView);
- vec3 halfVector = normalize(u_lightDirection + surfaceToViewDirection);
-
- float fakeLight = dot(u_lightDirection, normal) * .5 + .5;
- float specularLight = clamp(dot(normal, halfVector), 0.0, 1.0);
- vec4 specularMapColor = texture(specularMap, v_texcoord);
- vec3 effectiveSpecular = specular * specularMapColor.rgb;
-
- vec4 diffuseMapColor = texture(diffuseMap, v_texcoord);
- vec3 effectiveDiffuse = diffuse * diffuseMapColor.rgb * v_color.rgb;
- float effectiveOpacity = opacity * diffuseMapColor.a * v_color.a;
-
- outColor = vec4(
- emissive +
- ambient * u_ambientLight +
- effectiveDiffuse * fakeLight +
- effectiveSpecular * pow(specularLight, shininess),
- effectiveOpacity);
-```
-
-Также стоит добавить значение по умолчанию для материалов без карты specular:
-
-```js
-const defaultMaterial = {
- diffuse: [1, 1, 1],
- diffuseMap: textures.defaultWhite,
- ambient: [0, 0, 0],
- specular: [1, 1, 1],
- specularMap: textures.defaultWhite,
- shininess: 400,
- opacity: 1,
-};
-```
-
-В .MTL-файле значения specular могут быть не очень наглядными, поэтому для наглядности можно «взломать» параметры specular:
-
-```js
-// хак: делаем specular заметнее
-Object.values(materials).forEach(m => {
- m.shininess = 25;
- m.specular = [3, 2, 1];
-});
-```
-
-Теперь видно, что только окна и лопасти отражают свет.
-
-{{{example url="../webgl-load-obj-w-mtl-w-specular-map.html"}}}
-
-Меня удивило, что лопасти отражают свет. Если посмотреть на .MTL-файл, там shininess `Ns` = 0.0, что означает очень сильные specular-блики. Но illum = 1 для обоих материалов. По документации illum 1 означает:
-
-```
-color = KaIa + Kd { SUM j=1..ls, (N * Lj)Ij }
-```
-
-То есть:
-
-```
-color = ambientColor * lightAmbient + diffuseColor * sumOfLightCalculations
-```
-
-Как видно, specular тут не участвует, но в файле всё равно есть specular map! ¯\_(ツ)_/¯ Для specular-бликов нужен illum 2 или выше. Это типичная ситуация с .OBJ/.MTL: часто приходится вручную дорабатывать материалы. Как исправлять — решать вам: можно править .MTL, можно добавить код. Мы выбрали второй путь.
-
-Последняя карта в этом .MTL — `map_Bump` (bump map). На самом деле файл — это normal map.
-
-

-
-В .MTL нет опции явно указать normal map или что bump map — это normal map. Можно использовать эвристику: если в имени файла есть 'nor', или просто считать, что все `map_Bump` — это normal map (по крайней мере в 2020+). Так и поступим.
-
-Для генерации тангенсов используем код из [статьи про normal mapping](webgl-3d-lighting-normal-mapping.html):
-
-```js
-const parts = obj.geometries.map(({material, data}) => {
- ...
-
- // генерируем тангенсы, если есть данные
- if (data.texcoord && data.normal) {
- data.tangent = generateTangents(data.position, data.texcoord);
- } else {
- // Нет тангенсов
- data.tangent = { value: [1, 0, 0] };
- }
-
- // создаём буфер для каждого массива
- const bufferInfo = twgl.createBufferInfoFromArrays(gl, data);
- const vao = twgl.createVAOFromBufferInfo(gl, meshProgramInfo, bufferInfo);
- return {
- material: {
- ...defaultMaterial,
- ...materials[material],
- },
- bufferInfo,
- vao,
- };
-});
-```
-
-Также добавим normal map по умолчанию для материалов, у которых его нет:
-
-```js
-const textures = {
- defaultWhite: twgl.createTexture(gl, {src: [255, 255, 255, 255]}),
- defaultNormal: twgl.createTexture(gl, {src: [127, 127, 255, 0]}),
-};
+И теперь мы получаем normal maps. Примечание: я приблизил камеру, чтобы их было легче увидеть.
+
+{{{example url="../webgl-load-obj-w-mtl-w-normal-maps.html"}}}
+
+Уверен, что в .MTL-файле есть гораздо больше возможностей, которые мы могли бы поддержать.
+Например, ключевое слово `refl` указывает карты отражения, что является другим словом
+для [environment map](webgl-environment-maps.html). Также показано, что различные
+ключевые слова `map_` принимают множество опциональных аргументов. Несколько из них:
+
+* `-clamp on | off` указывает, повторяется ли текстура
+* `-mm base gain` указывает смещение и множитель для значений текстуры
+* `-o u v w` указывает смещение для координат текстуры. Вы бы применили их, используя матрицу текстуры, аналогично тому, что мы делали в [статье про drawImage](webgl-2d-drawimage.html)
+* `-s u v w` указывает масштаб для координат текстуры. Как и выше, вы бы поместили их в матрицу текстуры
+
+Я не знаю, сколько .MTL-файлов используют эти настройки.
+
+Более важный момент заключается в том, что добавление поддержки каждой функции делает
+шейдеры больше и сложнее. Выше у нас есть форма *uber shader*,
+шейдер, который пытается обработать все случаи. Чтобы заставить его работать, мы передали различные
+значения по умолчанию. Например, мы установили `diffuseMap` как белую текстуру, чтобы если мы
+загружаем что-то без текстур, это всё равно отображалось. Diffuse цвет будет
+умножен на белый, что равно 1.0, поэтому мы просто получим diffuse цвет.
+Аналогично мы передали белый цвет вершины по умолчанию на случай, если нет
+цветов вершин.
+
+Это распространённый способ заставить вещи работать, и если это работает достаточно быстро для ваших
+потребностей, то нет причин это менять. Но более распространено генерировать
+шейдеры, которые включают/выключают эти функции. Если нет цветов вершин, то
+генерируйте шейдер, как в манипуляции со строками шейдеров, чтобы у них не было атрибута
+`a_color` и всего связанного кода. Аналогично, если у материала нет diffuse map, то
+генерируйте шейдер, у которого нет `uniform sampler2D diffuseMap` и удалите весь связанный код.
+Если у него нет никаких карт, то нам не нужны координаты текстуры, поэтому мы их тоже оставим.
+
+Когда вы сложите все комбинации, может быть тысячи вариаций шейдеров.
+Только с тем, что у нас есть выше, есть:
+
+* diffuseMap да/нет
+* specularMap да/нет
+* normalMap да/нет
+* цвета вершин да/нет
+* ambientMap да/нет (мы не поддерживали это, но .MTL файл поддерживает)
+* reflectionMap да/нет (мы не поддерживали это, но .MTL файл поддерживает)
+
+Только эти представляют 64 комбинации. Если мы добавим, скажем, от 1 до 4 источников света, и эти
+источники света могут быть spot, или point, или directional, мы получим 8192 возможных
+комбинации функций шейдера.
+
+Управление всем этим — это много работы. Это одна из причин, почему многие люди
+выбирают 3D движок, такой как [three.js](https://threejs.org), вместо того, чтобы делать это
+всё самим. Но, по крайней мере, надеюсь, эта статья даёт некоторое представление о
+типах вещей, связанных с отображением произвольного 3D контента.
+
+
+
Избегайте условных операторов в шейдерах где возможно
+
Традиционный совет — избегать условных операторов в шейдерах. В качестве примера
+мы могли бы сделать что-то вроде этого
+
{{#escapehtml}}
+uniform bool hasDiffuseMap;
+uniform vec4 diffuse;
+uniform sampler2D diffuseMap
...
-
-const defaultMaterial = {
- diffuse: [1, 1, 1],
- diffuseMap: textures.defaultWhite,
- normalMap: textures.defaultNormal,
- ambient: [0, 0, 0],
- specular: [1, 1, 1],
- specularMap: textures.defaultWhite,
- shininess: 400,
- opacity: 1,
-};
+ vec4 effectiveDiffuse = diffuse;
+ if (hasDiffuseMap) {
+ effectiveDiffuse *= texture2D(diffuseMap, texcoord);
+ }
...
-```
-
-И, наконец, вносим изменения в шейдеры, как в [статье про normal mapping](webgl-3d-lighting-normal-mapping.html):
-
-```js
-const vs = `#version 300 es
-in vec4 a_position;
-in vec3 a_normal;
-in vec3 a_tangent;
-in vec2 a_texcoord;
-in vec4 a_color;
-
-uniform mat4 u_projection;
-uniform mat4 u_view;
-uniform mat4 u_world;
-uniform vec3 u_viewWorldPosition;
-
-out vec3 v_normal;
-out vec3 v_tangent;
-out vec3 v_surfaceToView;
-out vec2 v_texcoord;
-out vec4 v_color;
-
-void main() {
- vec4 worldPosition = u_world * a_position;
- gl_Position = u_projection * u_view * worldPosition;
- v_surfaceToView = u_viewWorldPosition - worldPosition.xyz;
-
- mat3 normalMat = mat3(u_world);
- v_normal = normalize(normalMat * a_normal);
- v_tangent = normalize(normalMat * a_tangent);
-
- v_texcoord = a_texcoord;
- v_color = a_color;
-}
-`;
-
-const fs = `#version 300 es
-precision highp float;
-
-in vec3 v_normal;
-in vec3 v_tangent;
-in vec3 v_surfaceToView;
-in vec2 v_texcoord;
-in vec4 v_color;
-
-uniform vec3 diffuse;
-uniform sampler2D diffuseMap;
-uniform vec3 ambient;
-uniform vec3 emissive;
-uniform vec3 specular;
-uniform sampler2D specularMap;
-uniform float shininess;
-uniform sampler2D normalMap;
-uniform float opacity;
-uniform vec3 u_lightDirection;
-uniform vec3 u_ambientLight;
-
-out vec4 outColor;
-
-void main () {
- vec3 normal = normalize(v_normal);
- vec3 tangent = normalize(v_tangent);
- vec3 bitangent = normalize(cross(normal, tangent));
-
- mat3 tbn = mat3(tangent, bitangent, normal);
- normal = texture(normalMap, v_texcoord).rgb * 2. - 1.;
- normal = normalize(tbn * normal);
-
- vec3 surfaceToViewDirection = normalize(v_surfaceToView);
- vec3 halfVector = normalize(u_lightDirection + surfaceToViewDirection);
-
- float fakeLight = dot(u_lightDirection, normal) * .5 + .5;
- float specularLight = clamp(dot(normal, halfVector), 0.0, 1.0);
- vec4 specularMapColor = texture(specularMap, v_texcoord);
- vec3 effectiveSpecular = specular * specularMapColor.rgb;
-
- vec4 diffuseMapColor = texture(diffuseMap, v_texcoord);
- vec3 effectiveDiffuse = diffuse * diffuseMapColor.rgb * v_color.rgb;
- float effectiveOpacity = opacity * diffuseMapColor.a * v_color.a;
-```
\ No newline at end of file
+{{/escapehtml}}
+
Условные операторы, такие как этот, обычно не рекомендуются, потому что в зависимости от
+GPU/драйвера они часто не очень производительны.
+
Либо делайте, как мы сделали выше, и попытайтесь сделать код без условных операторов. Мы использовали
+один 1x1 белый пиксель текстуры, когда нет текстуры, чтобы наша математика работала
+без условного оператора.
+
Или используйте разные шейдеры. Один, у которого нет функции, и один, у которого есть,
+и выбирайте правильный для каждой ситуации.
+
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-load-obj.md b/webgl/lessons/ru/webgl-load-obj.md
index 54947608c..4ecaec231 100644
--- a/webgl/lessons/ru/webgl-load-obj.md
+++ b/webgl/lessons/ru/webgl-load-obj.md
@@ -179,77 +179,77 @@ f 1//1 2//2 3//3 # индексы для позиций и нормалей
```js
function parseOBJ(text) {
-+ // потому что индексы основаны на 1, давайте просто заполним 0-е данные
-+ const objPositions = [[0, 0, 0]];
-+ const objTexcoords = [[0, 0]];
-+ const objNormals = [[0, 0, 0]];
-+
-+ // тот же порядок, что и индексы `f`
-+ const objVertexData = [
-+ objPositions,
-+ objTexcoords,
-+ objNormals,
-+ ];
-+
-+ // тот же порядок, что и индексы `f`
-+ let webglVertexData = [
-+ [], // позиции
-+ [], // текстурные координаты
-+ [], // нормали
-+ ];
-+
-+ function addVertex(vert) {
-+ const ptn = vert.split('/');
-+ ptn.forEach((objIndexStr, i) => {
-+ if (!objIndexStr) {
-+ return;
-+ }
-+ const objIndex = parseInt(objIndexStr);
-+ const index = objIndex + (objIndex >= 0 ? 0 : objVertexData[i].length);
-+ webglVertexData[i].push(...objVertexData[i][index]);
-+ });
-+ }
-+
-+ const keywords = {
-+ v(parts) {
-+ objPositions.push(parts.map(parseFloat));
-+ },
-+ vn(parts) {
-+ objNormals.push(parts.map(parseFloat));
-+ },
-+ vt(parts) {
-+ objTexcoords.push(parts.map(parseFloat));
-+ },
-+ f(parts) {
-+ const numTriangles = parts.length - 2;
-+ for (let tri = 0; tri < numTriangles; ++tri) {
-+ addVertex(parts[0]);
-+ addVertex(parts[tri + 1]);
-+ addVertex(parts[tri + 2]);
-+ }
-+ },
-+ };
-+
-+ const keywordRE = /(\w*)(?: )*(.*)/;
-+ const lines = text.split('\n');
-+ for (let lineNo = 0; lineNo < lines.length; ++lineNo) {
-+ const line = lines[lineNo].trim();
-+ if (line === '' || line.startsWith('#')) {
-+ continue;
-+ }
-+ const m = keywordRE.exec(line);
-+ if (!m) {
-+ continue;
-+ }
-+ const [, keyword, unparsedArgs] = m;
-+ const parts = line.split(/\s+/).slice(1);
-+ const handler = keywords[keyword];
-+ if (!handler) {
-+ console.warn('unhandled keyword:', keyword, 'at line', lineNo + 1);
-+ continue;
-+ }
-+ handler(parts, unparsedArgs);
-+ }
+ // потому что индексы основаны на 1, давайте просто заполним 0-е данные
+ const objPositions = [[0, 0, 0]];
+ const objTexcoords = [[0, 0]];
+ const objNormals = [[0, 0, 0]];
+
+ // тот же порядок, что и индексы `f`
+ const objVertexData = [
+ objPositions,
+ objTexcoords,
+ objNormals,
+ ];
+
+ // тот же порядок, что и индексы `f`
+ let webglVertexData = [
+ [], // позиции
+ [], // текстурные координаты
+ [], // нормали
+ ];
+
+ function addVertex(vert) {
+ const ptn = vert.split('/');
+ ptn.forEach((objIndexStr, i) => {
+ if (!objIndexStr) {
+ return;
+ }
+ const objIndex = parseInt(objIndexStr);
+ const index = objIndex + (objIndex >= 0 ? 0 : objVertexData[i].length);
+ webglVertexData[i].push(...objVertexData[i][index]);
+ });
+ }
+
+ const keywords = {
+ v(parts) {
+ objPositions.push(parts.map(parseFloat));
+ },
+ vn(parts) {
+ objNormals.push(parts.map(parseFloat));
+ },
+ vt(parts) {
+ objTexcoords.push(parts.map(parseFloat));
+ },
+ f(parts) {
+ const numTriangles = parts.length - 2;
+ for (let tri = 0; tri < numTriangles; ++tri) {
+ addVertex(parts[0]);
+ addVertex(parts[tri + 1]);
+ addVertex(parts[tri + 2]);
+ }
+ },
+ };
+
+ const keywordRE = /(\w*)(?: )*(.*)/;
+ const lines = text.split('\n');
+ for (let lineNo = 0; lineNo < lines.length; ++lineNo) {
+ const line = lines[lineNo].trim();
+ if (line === '' || line.startsWith('#')) {
+ continue;
+ }
+ const m = keywordRE.exec(line);
+ if (!m) {
+ continue;
+ }
+ const [, keyword, unparsedArgs] = m;
+ const parts = line.split(/\s+/).slice(1);
+ const handler = keywords[keyword];
+ if (!handler) {
+ console.warn('unhandled keyword:', keyword, 'at line', lineNo + 1);
+ continue;
+ }
+ handler(parts, unparsedArgs);
+ }
return {
position: webglVertexData[0],
@@ -569,4 +569,256 @@ function addVertex(vert) {
Вы можете увидеть пример загрузки .GLTF файла в [статье о скиннинге](webgl-skinning.html).
Если у вас есть .OBJ файлы, которые вы хотите использовать, лучшая практика - конвертировать их
- в какой-то другой формат сначала, офлайн, а затем использовать лучший формат на вашей странице.
\ No newline at end of file
+ в какой-то другой формат сначала, офлайн, а затем использовать лучший формат на вашей странице.
+
+## Загрузка .OBJ с цветами вершин
+
+Некоторые .OBJ файлы содержат цвета вершин. Это данные, которые хранятся в каждой вершине,
+а не в материале. Давайте добавим поддержку для этого.
+
+Сначала нужно обновить парсер, чтобы он обрабатывал ключевое слово `vc` (vertex color):
+
+```js
+function parseOBJ(text) {
+ const objPositions = [];
+ const objTexcoords = [];
+ const objNormals = [];
+ const objColors = [];
+ const objVertexData = [
+ objPositions,
+ objTexcoords,
+ objNormals,
+ objColors,
+ ];
+
+ // индексы для webgl используют 0 как базу
+ const webglIndices = [];
+ let geometry;
+ let material = 'default';
+ let object = 'default';
+
+ const noop = () => {};
+
+ const keywords = {
+ v(parts) {
+ // если есть 4 значения, то это позиция + цвет
+ if (parts.length === 6) {
+ objPositions.push(parts[0], parts[1], parts[2]);
+ objColors.push(parts[3], parts[4], parts[5]);
+ } else {
+ objPositions.push(parts[0], parts[1], parts[2]);
+ }
+ },
+ vn(parts) {
+ objNormals.push(parts[0], parts[1], parts[2]);
+ },
+ vt(parts) {
+ objTexcoords.push(parts[0], parts[1]);
+ },
+ f(parts) {
+ setGeometry();
+ addFace(parts);
+ },
+ s: noop, // smoothing group
+ mtllib(parts, unparsedArgs) {
+ // материал библиотека
+ materialLib = unparsedArgs;
+ },
+ usemtl(parts, unparsedArgs) {
+ material = unparsedArgs;
+ setGeometry();
+ },
+ g(parts, unparsedArgs) {
+ object = unparsedArgs;
+ setGeometry();
+ },
+ o(parts, unparsedArgs) {
+ object = unparsedArgs;
+ setGeometry();
+ },
+ };
+
+ function setGeometry() {
+ if (geometry) {
+ geometry = undefined;
+ }
+ }
+
+ function addFace(parts) {
+ const numTriangles = parts.length - 2;
+ for (let tri = 0; tri < numTriangles; ++tri) {
+ addVertex(parts[0]);
+ addVertex(parts[tri + 1]);
+ addVertex(parts[tri + 2]);
+ }
+ }
+
+ const keywordRE = /(\w*)(?: )*(.*)/;
+ const lines = text.split('\n');
+ for (let lineNo = 0; lineNo < lines.length; ++lineNo) {
+ const line = lines[lineNo].trim();
+ if (line === '' || line.startsWith('#')) {
+ continue;
+ }
+ const m = keywordRE.exec(line);
+ if (!m) {
+ continue;
+ }
+ const [, keyword, unparsedArgs] = m;
+ const parts = line.split(/\s+/).slice(1);
+ const handler = keywords[keyword];
+ if (!handler) {
+ console.warn('unhandled keyword:', keyword);
+ continue;
+ }
+ handler(parts, unparsedArgs);
+ }
+
+ for (const geometry of Object.values(geometries)) {
+ geometry.data = {};
+ if (geometry.objVertexData[0].length > 0) {
+ geometry.data.position = geometry.objVertexData[0];
+ }
+ if (geometry.objVertexData[1].length > 0) {
+ geometry.data.texcoord = geometry.objVertexData[1];
+ }
+ if (geometry.objVertexData[2].length > 0) {
+ geometry.data.normal = geometry.objVertexData[2];
+ }
+ if (geometry.objVertexData[3].length > 0) {
+ geometry.data.color = geometry.objVertexData[3];
+ }
+ }
+
+ return {
+ geometries: Object.values(geometries),
+ };
+}
+```
+
+Затем нужно обновить шейдеры, чтобы они использовали цвета вершин:
+
+```js
+const vs = `#version 300 es
+in vec4 a_position;
+in vec3 a_normal;
+in vec2 a_texcoord;
+in vec3 a_color;
+
+uniform mat4 u_projection;
+uniform mat4 u_view;
+uniform mat4 u_world;
+uniform vec3 u_viewWorldPosition;
+
+out vec3 v_normal;
+out vec3 v_surfaceToView;
+out vec2 v_texcoord;
+out vec3 v_color;
+
+void main() {
+ vec4 worldPosition = u_world * a_position;
+ gl_Position = u_projection * u_view * worldPosition;
+ v_surfaceToView = u_viewWorldPosition - worldPosition.xyz;
+
+ v_normal = mat3(u_world) * a_normal;
+ v_texcoord = a_texcoord;
+ v_color = a_color;
+}
+`;
+
+const fs = `#version 300 es
+precision highp float;
+
+in vec3 v_normal;
+in vec3 v_surfaceToView;
+in vec2 v_texcoord;
+in vec3 v_color;
+
+uniform vec3 diffuse;
+uniform sampler2D diffuseMap;
+uniform vec3 ambient;
+uniform vec3 emissive;
+uniform vec3 specular;
+uniform sampler2D specularMap;
+uniform float shininess;
+uniform float opacity;
+uniform vec3 u_lightDirection;
+uniform vec3 u_ambientLight;
+
+out vec4 outColor;
+
+void main () {
+ vec3 normal = normalize(v_normal);
+
+ vec3 surfaceToViewDirection = normalize(v_surfaceToView);
+ vec3 halfVector = normalize(u_lightDirection + surfaceToViewDirection);
+
+ float fakeLight = dot(u_lightDirection, normal) * .5 + .5;
+ float specularLight = clamp(dot(normal, halfVector), 0.0, 1.0);
+ vec4 specularMapColor = texture(specularMap, v_texcoord);
+ vec3 effectiveSpecular = specular * specularMapColor.rgb;
+
+ vec4 diffuseMapColor = texture(diffuseMap, v_texcoord);
+ vec3 effectiveDiffuse = diffuse * diffuseMapColor.rgb * v_color;
+ float effectiveOpacity = opacity * diffuseMapColor.a;
+
+ outColor = vec4(
+ emissive +
+ ambient * u_ambientLight +
+ effectiveDiffuse * fakeLight +
+ effectiveSpecular * pow(specularLight, shininess),
+ effectiveOpacity);
+}
+`;
+```
+
+Также нужно обновить код, который создаёт буферы, чтобы он обрабатывал цвета вершин.
+Наша [вспомогательная библиотека](webgl-less-code-more-fun.html) обрабатывает это для нас, если
+мы установим данные для этого атрибута как `{value: [1, 2, 3, 4]}`. Итак, мы можем
+проверить, если нет цветов вершин, то если так, установить атрибут цвета вершины
+как константный белый.
+
+```js
+const parts = obj.geometries.map(({data}) => {
+ // Потому что data - это просто именованные массивы, как это
+ //
+ // {
+ // position: [...],
+ // texcoord: [...],
+ // normal: [...],
+ // }
+ //
+ // и потому что эти имена соответствуют атрибутам в нашем вершинном
+ // шейдере, мы можем передать это напрямую в `createBufferInfoFromArrays`
+ // из статьи "less code more fun".
+
+ if (data.color) {
+ if (data.position.length === data.color.length) {
+ // это 3. Наша вспомогательная библиотека предполагает 4, поэтому нам нужно
+ // сказать ей, что их только 3.
+ data.color = { numComponents: 3, data: data.color };
+ }
+ } else {
+ // нет цветов вершин, поэтому просто используем константный белый
+ data.color = { value: [1, 1, 1, 1] };
+ }
+
+ // создаём буфер для каждого массива, вызывая
+ // gl.createBuffer, gl.bindBuffer, gl.bufferData
+ const bufferInfo = twgl.createBufferInfoFromArrays(gl, data);
+ const vao = twgl.createVAOFromBufferInfo(gl, meshProgramInfo, bufferInfo);
+ return {
+ material: {
+ u_diffuse: [1, 1, 1, 1],
+ },
+ bufferInfo,
+ vao,
+ };
+});
+```
+
+И с этим мы можем загрузить .OBJ файл с цветами вершин.
+
+{{{example url="../webgl-load-obj-w-vertex-colors.html"}}}
+
+Что касается парсинга и использования материалов, [см. следующую статью](webgl-load-obj-w-mtl.html)
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-matrix-vs-math.md b/webgl/lessons/ru/webgl-matrix-vs-math.md
index 9623a88c1..b0c0802e7 100644
--- a/webgl/lessons/ru/webgl-matrix-vs-math.md
+++ b/webgl/lessons/ru/webgl-matrix-vs-math.md
@@ -196,4 +196,38 @@ const someTranslationMatrix = [
Итак, с этим соглашением называть строки "столбцами" некоторые вещи проще, но другие могут быть более запутанными, если вы математик.
-Я поднимаю все это, потому что эти статьи написаны с точки зрения программиста, а не математика. Это означает, что как и каждый другой одномерный массив, который обрабатывается как двумерный массив, строки идут поперек.
\ No newline at end of file
+Я поднимаю все это, потому что эти статьи написаны с точки зрения программиста, а не математика. Это означает, что как и каждый другой одномерный массив, который обрабатывается как двумерный массив, строки идут поперек.
+
+```js
+const someTranslationMatrix = [
+ 1, 0, 0, 0, // строка 0
+ 0, 1, 0, 0, // строка 1
+ 0, 0, 1, 0, // строка 2
+ tx, ty, tz, 1, // строка 3
+];
+```
+
+точно так же, как
+
+```js
+// изображение смайлика
+const dataFor7x8OneChannelImage = [
+ 0, 255, 255, 255, 255, 255, 0, // строка 0
+ 255, 0, 0, 0, 0, 0, 255, // строка 1
+ 255, 0, 255, 0, 255, 0, 255, // строка 2
+ 255, 0, 0, 0, 0, 0, 255, // строка 3
+ 255, 0, 255, 0, 255, 0, 255, // строка 4
+ 255, 0, 255, 255, 255, 0, 255, // строка 5
+ 255, 0, 0, 0, 0, 0, 255, // строка 6
+ 0, 255, 255, 255, 255, 255, 0, // строка 7
+]
+```
+
+и поэтому эти статьи будут ссылаться на них как на строки.
+
+Если вы математик, вы можете найти это запутанным. Мне жаль, что у меня
+нет решения. Я мог бы назвать то, что явно является строкой 3, столбцом,
+но это также было бы запутанно, поскольку это не соответствует никакому другому программированию.
+
+В любом случае, надеюсь, это помогает прояснить, почему ни одно из объяснений не выглядит как что-то из математической книги. Вместо этого они выглядят как код и используют соглашения кода. Я надеюсь, что это помогает объяснить, что происходит,
+и это не слишком запутанно для тех, кто привык к математическим соглашениям.
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-multiple-views.md b/webgl/lessons/ru/webgl-multiple-views.md
index 67a47c58f..de1b580f7 100644
--- a/webgl/lessons/ru/webgl-multiple-views.md
+++ b/webgl/lessons/ru/webgl-multiple-views.md
@@ -160,8 +160,6 @@ const fieldOfViewRadians = degToRad(120);
function render() {
twgl.resizeCanvasToDisplaySize(gl.canvas);
- gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
-
gl.enable(gl.CULL_FACE);
gl.enable(gl.DEPTH_TEST);
@@ -185,6 +183,22 @@ function render() {
// центрируем 'F' вокруг его начала
worldMatrix = m4.translate(worldMatrix, -35, -75, -5);
+ // рисуем левый вид
+ const left = 0;
+ const bottom = 0;
+ const width = gl.canvas.width / 2;
+ const height = gl.canvas.height;
+
+ gl.viewport(left, bottom, width, height);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix, worldMatrix);
+
+ // рисуем правый вид
+ const left2 = width;
+ const bottom2 = 0;
+ const width2 = width;
+ const height2 = height;
+
+ gl.viewport(left2, bottom2, width2, height2);
drawScene(perspectiveProjectionMatrix, cameraMatrix, worldMatrix);
}
render();
@@ -197,4 +211,400 @@ render();
{{{example url="../webgl-multiple-views-one-view.html"}}}
Теперь давайте сделаем так, чтобы он рисовал 2 вида 'F' бок о бок,
-используя `gl.viewport`
\ No newline at end of file
+используя `gl.viewport`
+
+```js
+function render() {
+ twgl.resizeCanvasToDisplaySize(gl.canvas);
+
+ gl.enable(gl.CULL_FACE);
+ gl.enable(gl.DEPTH_TEST);
+
+ const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
+ const near = 1;
+ const far = 2000;
+
+ // Вычисляем матрицу перспективной проекции
+ const perspectiveProjectionMatrix =
+ m4.perspective(fieldOfViewRadians, aspect, near, far);
+
+ // Вычисляем матрицу камеры, используя look at.
+ const cameraPosition = [0, 0, -75];
+ const target = [0, 0, 0];
+ const up = [0, 1, 0];
+ const cameraMatrix = m4.lookAt(cameraPosition, target, up);
+
+ // поворачиваем F в мировом пространстве
+ let worldMatrix = m4.yRotation(degToRad(settings.rotation));
+ worldMatrix = m4.xRotate(worldMatrix, degToRad(settings.rotation));
+ // центрируем 'F' вокруг его начала
+ worldMatrix = m4.translate(worldMatrix, -35, -75, -5);
+
+ // рисуем левый вид
+ const left = 0;
+ const bottom = 0;
+ const width = gl.canvas.width / 2;
+ const height = gl.canvas.height;
+
+ gl.viewport(left, bottom, width, height);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix, worldMatrix);
+
+ // рисуем правый вид
+ const left2 = width;
+ const bottom2 = 0;
+ const width2 = width;
+ const height2 = height;
+
+ gl.viewport(left2, bottom2, width2, height2);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix, worldMatrix);
+}
+render();
+```
+
+{{{example url="../webgl-multiple-views.html"}}}
+
+Теперь у нас есть 2 вида 'F' бок о бок. Каждый вид использует половину canvas.
+
+Давайте добавим еще один вид, чтобы у нас было 3 вида
+
+```js
+function render() {
+ twgl.resizeCanvasToDisplaySize(gl.canvas);
+
+ gl.enable(gl.CULL_FACE);
+ gl.enable(gl.DEPTH_TEST);
+
+ const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
+ const near = 1;
+ const far = 2000;
+
+ // Вычисляем матрицу перспективной проекции
+ const perspectiveProjectionMatrix =
+ m4.perspective(fieldOfViewRadians, aspect, near, far);
+
+ // Вычисляем матрицу камеры, используя look at.
+ const cameraPosition = [0, 0, -75];
+ const target = [0, 0, 0];
+ const up = [0, 1, 0];
+ const cameraMatrix = m4.lookAt(cameraPosition, target, up);
+
+ // поворачиваем F в мировом пространстве
+ let worldMatrix = m4.yRotation(degToRad(settings.rotation));
+ worldMatrix = m4.xRotate(worldMatrix, degToRad(settings.rotation));
+ // центрируем 'F' вокруг его начала
+ worldMatrix = m4.translate(worldMatrix, -35, -75, -5);
+
+ // рисуем левый вид
+ const left = 0;
+ const bottom = 0;
+ const width = gl.canvas.width / 3;
+ const height = gl.canvas.height;
+
+ gl.viewport(left, bottom, width, height);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix, worldMatrix);
+
+ // рисуем средний вид
+ const left2 = width;
+ const bottom2 = 0;
+ const width2 = width;
+ const height2 = height;
+
+ gl.viewport(left2, bottom2, width2, height2);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix, worldMatrix);
+
+ // рисуем правый вид
+ const left3 = width * 2;
+ const bottom3 = 0;
+ const width3 = width;
+ const height3 = height;
+
+ gl.viewport(left3, bottom3, width3, height3);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix, worldMatrix);
+}
+render();
+```
+
+{{{example url="../webgl-multiple-views-clear-fixed.html"}}}
+
+Теперь у нас есть 3 вида. Каждый вид использует треть canvas.
+
+Обратите внимание, что каждый вид рисует ту же сцену. Это может быть полезно для отладки или для создания интерфейса с множественными видами.
+
+Давайте также добавим scissor тест, чтобы убедиться, что мы не рисуем за пределами каждого viewport
+
+```js
+function render() {
+ twgl.resizeCanvasToDisplaySize(gl.canvas);
+
+ gl.enable(gl.CULL_FACE);
+ gl.enable(gl.DEPTH_TEST);
+ gl.enable(gl.SCISSOR_TEST);
+
+ const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
+ const near = 1;
+ const far = 2000;
+
+ // Вычисляем матрицу перспективной проекции
+ const perspectiveProjectionMatrix =
+ m4.perspective(fieldOfViewRadians, aspect, near, far);
+
+ // Вычисляем матрицу камеры, используя look at.
+ const cameraPosition = [0, 0, -75];
+ const target = [0, 0, 0];
+ const up = [0, 1, 0];
+ const cameraMatrix = m4.lookAt(cameraPosition, target, up);
+
+ // поворачиваем F в мировом пространстве
+ let worldMatrix = m4.yRotation(degToRad(settings.rotation));
+ worldMatrix = m4.xRotate(worldMatrix, degToRad(settings.rotation));
+ // центрируем 'F' вокруг его начала
+ worldMatrix = m4.translate(worldMatrix, -35, -75, -5);
+
+ // рисуем левый вид
+ const left = 0;
+ const bottom = 0;
+ const width = gl.canvas.width / 3;
+ const height = gl.canvas.height;
+
+ gl.viewport(left, bottom, width, height);
+ gl.scissor(left, bottom, width, height);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix, worldMatrix);
+
+ // рисуем средний вид
+ const left2 = width;
+ const bottom2 = 0;
+ const width2 = width;
+ const height2 = height;
+
+ gl.viewport(left2, bottom2, width2, height2);
+ gl.scissor(left2, bottom2, width2, height2);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix, worldMatrix);
+
+ // рисуем правый вид
+ const left3 = width * 2;
+ const bottom3 = 0;
+ const width3 = width;
+ const height3 = height;
+
+ gl.viewport(left3, bottom3, width3, height3);
+ gl.scissor(left3, bottom3, width3, height3);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix, worldMatrix);
+}
+render();
+```
+
+{{{example url="../webgl-multiple-views-clear-issue.html"}}}
+
+Теперь у нас есть scissor тест, который гарантирует, что мы не рисуем за пределами каждого viewport.
+
+Давайте также добавим очистку для каждого viewport
+
+```js
+function render() {
+ twgl.resizeCanvasToDisplaySize(gl.canvas);
+
+ gl.enable(gl.CULL_FACE);
+ gl.enable(gl.DEPTH_TEST);
+ gl.enable(gl.SCISSOR_TEST);
+
+ const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
+ const near = 1;
+ const far = 2000;
+
+ // Вычисляем матрицу перспективной проекции
+ const perspectiveProjectionMatrix =
+ m4.perspective(fieldOfViewRadians, aspect, near, far);
+
+ // Вычисляем матрицу камеры, используя look at.
+ const cameraPosition = [0, 0, -75];
+ const target = [0, 0, 0];
+ const up = [0, 1, 0];
+ const cameraMatrix = m4.lookAt(cameraPosition, target, up);
+
+ // поворачиваем F в мировом пространстве
+ let worldMatrix = m4.yRotation(degToRad(settings.rotation));
+ worldMatrix = m4.xRotate(worldMatrix, degToRad(settings.rotation));
+ // центрируем 'F' вокруг его начала
+ worldMatrix = m4.translate(worldMatrix, -35, -75, -5);
+
+ // рисуем левый вид
+ const left = 0;
+ const bottom = 0;
+ const width = gl.canvas.width / 3;
+ const height = gl.canvas.height;
+
+ gl.viewport(left, bottom, width, height);
+ gl.scissor(left, bottom, width, height);
+ gl.clearColor(0.2, 0.2, 0.2, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix, worldMatrix);
+
+ // рисуем средний вид
+ const left2 = width;
+ const bottom2 = 0;
+ const width2 = width;
+ const height2 = height;
+
+ gl.viewport(left2, bottom2, width2, height2);
+ gl.scissor(left2, bottom2, width2, height2);
+ gl.clearColor(0.2, 0.2, 0.2, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix, worldMatrix);
+
+ // рисуем правый вид
+ const left3 = width * 2;
+ const bottom3 = 0;
+ const width3 = width;
+ const height3 = height;
+
+ gl.viewport(left3, bottom3, width3, height3);
+ gl.scissor(left3, bottom3, width3, height3);
+ gl.clearColor(0.2, 0.2, 0.2, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix, worldMatrix);
+}
+render();
+```
+
+{{{example url="../webgl-multiple-views-clear-fixed.html"}}}
+
+Теперь у нас есть очистка для каждого viewport, что гарантирует, что каждый вид имеет чистый фон.
+
+Давайте также добавим возможность рисовать разные сцены в каждом viewport
+
+```js
+function render() {
+ twgl.resizeCanvasToDisplaySize(gl.canvas);
+
+ gl.enable(gl.CULL_FACE);
+ gl.enable(gl.DEPTH_TEST);
+ gl.enable(gl.SCISSOR_TEST);
+
+ const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
+ const near = 1;
+ const far = 2000;
+
+ // Вычисляем матрицу перспективной проекции
+ const perspectiveProjectionMatrix =
+ m4.perspective(fieldOfViewRadians, aspect, near, far);
+
+ // Вычисляем матрицу камеры, используя look at.
+ const cameraPosition = [0, 0, -75];
+ const target = [0, 0, 0];
+ const up = [0, 1, 0];
+ const cameraMatrix = m4.lookAt(cameraPosition, target, up);
+
+ // поворачиваем F в мировом пространстве
+ let worldMatrix = m4.yRotation(degToRad(settings.rotation));
+ worldMatrix = m4.xRotate(worldMatrix, degToRad(settings.rotation));
+ // центрируем 'F' вокруг его начала
+ worldMatrix = m4.translate(worldMatrix, -35, -75, -5);
+
+ // рисуем левый вид
+ const left = 0;
+ const bottom = 0;
+ const width = gl.canvas.width / 3;
+ const height = gl.canvas.height;
+
+ gl.viewport(left, bottom, width, height);
+ gl.scissor(left, bottom, width, height);
+ gl.clearColor(0.2, 0.2, 0.2, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix, worldMatrix);
+
+ // рисуем средний вид с другой камерой
+ const left2 = width;
+ const bottom2 = 0;
+ const width2 = width;
+ const height2 = height;
+
+ gl.viewport(left2, bottom2, width2, height2);
+ gl.scissor(left2, bottom2, width2, height2);
+ gl.clearColor(0.2, 0.2, 0.2, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+ // используем другую камеру для среднего вида
+ const cameraPosition2 = [0, 0, -50];
+ const cameraMatrix2 = m4.lookAt(cameraPosition2, target, up);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix2, worldMatrix);
+
+ // рисуем правый вид с другой камерой
+ const left3 = width * 2;
+ const bottom3 = 0;
+ const width3 = width;
+ const height3 = height;
+
+ gl.viewport(left3, bottom3, width3, height3);
+ gl.scissor(left3, bottom3, width3, height3);
+ gl.clearColor(0.2, 0.2, 0.2, 1);
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+ // используем другую камеру для правого вида
+ const cameraPosition3 = [0, 0, -100];
+ const cameraMatrix3 = m4.lookAt(cameraPosition3, target, up);
+ drawScene(perspectiveProjectionMatrix, cameraMatrix3, worldMatrix);
+}
+render();
+```
+
+{{{example url="../webgl-multiple-views-items.html"}}}
+
+Теперь у нас есть 3 вида с разными камерами. Каждый вид показывает ту же сцену с разных ракурсов.
+
+Конечно, вы могли бы рисовать целые 3D сцены или что угодно для каждого элемента.
+Пока вы правильно устанавливаете viewport и scissor, а затем настраиваете
+вашу матрицу проекции, чтобы соответствовать аспекту области, это должно работать.
+
+Еще одна примечательная вещь о коде - мы перемещаем canvas
+с этой строкой
+
+```
+gl.canvas.style.transform = `translateY(${window.scrollY}px)`;
+```
+
+Почему? Мы могли бы вместо этого установить canvas в `position: fixed;`, в этом случае
+он не прокручивался бы со страницей. Разница была бы тонкой.
+Браузер пытается прокручивать страницу как можно более плавно. Это может быть
+быстрее, чем мы можем рисовать наши объекты. Из-за этого у нас есть 2 варианта.
+
+1. Использовать canvas с фиксированной позицией
+
+ В этом случае, если мы не можем обновляться достаточно быстро, HTML перед canvas будет прокручиваться, но сам canvas
+ не будет, поэтому на несколько мгновений они будут не синхронизированы
+
+

+
+2. Перемещать canvas под контентом
+
+ В этом случае, если мы не можем обновляться достаточно быстро, canvas будет прокручиваться в синхронизации
+ с HTML, но новые области, где мы хотим рисовать вещи, будут пустыми, пока мы не получим
+ шанс нарисовать.
+
+

+
+ Это решение, используемое выше
+
+Надеюсь, эта статья дала вам некоторые идеи о том, как рисовать множественные виды.
+Мы будем использовать эти техники в нескольких будущих статьях, где
+возможность видеть множественные виды полезна для понимания.
+
+
+
Координаты пикселей
+
Координаты пикселей в WebGL
+ссылаются на их края. Так, например, если у нас был
+canvas размером 3x2 пикселя, и мы установили viewport
+как
+
+gl.viewport(
+ 0, // left
+ 0, // bottom
+ 3, // width
+ 2, // height
+);
+
+
Тогда мы действительно определяем этот прямоугольник, который окружает 3x2 пикселя
+
+
Это означает, что значение пространства отсечения X = -1.0 соответствует левому краю этого прямоугольника,
+а значение пространства отсечения X = 1.0 соответствует правому. Выше я сказал, что X = -1.0 соответствует самому левому пикселю,
+но на самом деле соответствует левому краю
+
diff --git a/webgl/lessons/ru/webgl-picking.md b/webgl/lessons/ru/webgl-picking.md
index fdb830946..53a2794fe 100644
--- a/webgl/lessons/ru/webgl-picking.md
+++ b/webgl/lessons/ru/webgl-picking.md
@@ -1,56 +1,61 @@
-Title: WebGL2 Пикинг (выбор объектов)
+Title: WebGL2 Выбор объектов
Description: Как выбирать объекты в WebGL
-TOC: Пикинг (клик по объектам)
+TOC: Выбор объектов (клик по объектам)
-Эта статья о том, как использовать WebGL, чтобы позволить пользователю выбирать или выделять объекты.
+Эта статья о том, как использовать WebGL для того, чтобы пользователь мог выбирать или выделять
+объекты.
-Если вы читали другие статьи на этом сайте, вы, вероятно, уже поняли,
-что сам WebGL — это просто библиотека растеризации. Он рисует треугольники,
-линии и точки на canvas, поэтому у него нет понятия «объекты для выбора».
-Он просто выводит пиксели через ваши шейдеры. Это значит,
-что любая концепция «пикинга» должна реализовываться в вашем коде. Вы должны
-определить, что это за объекты, которые пользователь может выбрать.
-То есть, хотя эта статья может охватить общие концепции, вам нужно будет
-самостоятельно решить, как применить их в вашем приложении.
+Если вы читали другие статьи на этом сайте, вы, надеюсь, поняли,
+что WebGL сам по себе - это просто библиотека растеризации. Он рисует треугольники,
+линии и точки на canvas, поэтому у него нет концепции "объектов для
+выбора". Он просто выводит пиксели через шейдеры, которые вы предоставляете. Это означает,
+что любая концепция "выбора" чего-либо должна исходить из вашего кода. Вам нужно
+определить, что это за вещи, которые вы позволяете пользователю выбирать.
+Это означает, что хотя эта статья может охватывать общие концепции, вам нужно будет
+самостоятельно решить, как перевести то, что вы видите здесь, в применимые
+концепции в вашем собственном приложении.
## Клик по объекту
-Один из самых простых способов определить, по какому объекту кликнул пользователь —
-присвоить каждому объекту числовой id, затем отрисовать
+Один из самых простых способов выяснить, на какую вещь кликнул пользователь, это
+придумать числовой id для каждого объекта, затем мы можем нарисовать
все объекты, используя их id как цвет, без освещения
-и текстур. Это даст нам изображение силуэтов
-каждого объекта. Буфер глубины сам отсортирует объекты.
-Затем мы можем считать цвет пикселя под мышью — это даст нам id объекта, который был отрисован в этой точке.
+и без текстур. Это даст нам изображение силуэтов
+каждого объекта. Буфер глубины будет обрабатывать сортировку за нас.
+Затем мы можем прочитать цвет пикселя под
+мышью, который даст нам id объекта, который был отрендерен там.
-Чтобы реализовать этот метод, нам нужно объединить несколько предыдущих
-статей. Первая — [о рисовании множества объектов](webgl-drawing-multiple-things.html),
-потому что она показывает, как рисовать много объектов, которые мы и будем выбирать.
+Для реализации этой техники нам нужно будет объединить несколько предыдущих
+статей. Первая - это статья о [рисовании множественных объектов](webgl-drawing-multiple-things.html),
+которую мы будем использовать, потому что, учитывая, что она рисует множество вещей, мы можем попытаться
+выбрать их.
-Обычно мы хотим рендерить эти id вне экрана —
-[рендеря в текстуру](webgl-render-to-texture.html), так что добавим и этот код.
+Помимо этого, мы обычно хотим рендерить эти id вне экрана,
+[рендеря в текстуру](webgl-render-to-texture.html), поэтому мы
+также добавим этот код.
-Начнем с последнего примера из
-[статьи о рисовании множества объектов](webgl-drawing-multiple-things.html),
-который рисует 200 объектов.
+Итак, давайте начнем с последнего примера из
+[статьи о рисовании множественных вещей](webgl-drawing-multiple-things.html),
+которая рисует 200 объектов.
-Добавим к нему framebuffer с прикреплённой текстурой и depth-буфером из
-последнего примера в [статье о рендере в текстуру](webgl-render-to-texture.html).
+К нему давайте добавим framebuffer с присоединенной текстурой и буфером глубины из
+последнего примера в [статье о рендеринге в текстуру](webgl-render-to-texture.html).
```js
-// Создаем текстуру для рендера
+// Создаем текстуру для рендеринга
const targetTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
-// создаем depth renderbuffer
+// создаем буфер глубины
const depthBuffer = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
function setFramebufferAttachmentSizes(width, height) {
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
- // задаем размер и формат уровня 0
+ // определяем размер и формат уровня 0
const level = 0;
const internalFormat = gl.RGBA;
const border = 0;
@@ -65,41 +70,43 @@ function setFramebufferAttachmentSizes(width, height) {
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
}
-// Создаем и биндим framebuffer
+// Создаем и привязываем framebuffer
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
-// прикрепляем текстуру как первый цветовой attachment
+// присоединяем текстуру как первое цветовое вложение
const attachmentPoint = gl.COLOR_ATTACHMENT0;
const level = 0;
gl.framebufferTexture2D(gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);
-// создаем depth-буфер такого же размера, как targetTexture
+// делаем буфер глубины того же размера, что и targetTexture
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);
```
-Мы вынесли код установки размеров текстуры и depth renderbuffer в функцию, чтобы
-можно было вызывать её при изменении размера canvas.
+Мы поместили код для установки размеров текстуры и
+буфера глубины в функцию, чтобы мы могли
+вызывать ее для изменения их размера в соответствии с размером
+canvas.
-В рендер-цикле, если размер canvas изменился,
-мы подгоним текстуру и renderbuffer под новые размеры.
+В нашем коде рендеринга, если canvas изменяет размер,
+мы скорректируем текстуру и renderbuffer, чтобы они соответствовали.
```js
function drawScene(time) {
time *= 0.0005;
-- webglUtils.resizeCanvasToDisplaySize(gl.canvas);
-+ if (webglUtils.resizeCanvasToDisplaySize(gl.canvas)) {
-+ // canvas изменился, подгоняем framebuffer attachments
-+ setFramebufferAttachmentSizes(gl.canvas.width, gl.canvas.height);
-+ }
+ if (webglUtils.resizeCanvasToDisplaySize(gl.canvas)) {
+ // canvas был изменен, делаем вложения framebuffer соответствующими
+ setFramebufferAttachmentSizes(gl.canvas.width, gl.canvas.height);
+ }
...
```
-Далее нам нужен второй шейдер. В примере используется рендер по цветам вершин, но нам нужен
-шейдер, который будет рисовать сплошным цветом (id).
-Вот наш второй шейдер:
+Далее нам нужен второй шейдер. Шейдер в
+примере рендерит, используя цвета вершин, но нам нужен
+тот, который мы можем установить в сплошной цвет для рендеринга с id.
+Итак, сначала вот наш второй шейдер
```js
const pickingVS = `#version 300 es
@@ -126,13 +133,13 @@ const pickingFS = `#version 300 es
`;
```
-И нам нужно скомпилировать, связать и найти локации
-используя наши [хелперы](webgl-less-code-more-fun.html).
+И нам нужно скомпилировать, связать и найти местоположения,
+используя наши [помощники](webgl-less-code-more-fun.html).
```js
-// настройка GLSL программ
-// важно: нам нужно, чтобы атрибуты совпадали между программами
-// чтобы можно было использовать один и тот же vertex array для разных шейдеров
+// настройка GLSL программы
+// примечание: нам нужны позиции атрибутов, чтобы соответствовать между программами
+// чтобы нам нужен был только один vertex array на форму
const options = {
attribLocations: {
a_position: 0,
@@ -143,15 +150,19 @@ const programInfo = twgl.createProgramInfo(gl, [vs, fs], options);
const pickingProgramInfo = twgl.createProgramInfo(gl, [pickingVS, pickingFS], options);
```
-В отличие от большинства примеров на сайте, здесь нам нужно рисовать одни и те же данные двумя разными шейдерами.
-Поэтому нам нужно, чтобы локации атрибутов совпадали между шейдерами. Это можно сделать двумя способами. Первый — явно указать их в GLSL:
+Одно отличие выше от большинства примеров на этом сайте, это один
+из немногих случаев, когда нам нужно было рисовать те же данные с 2 разными
+шейдерами. Из-за этого нам нужны местоположения атрибутов, чтобы соответствовать
+между шейдерами. Мы можем сделать это 2 способами. Один способ - установить их
+вручную в GLSL
```glsl
layout (location = 0) in vec4 a_position;
layout (location = 1) in vec4 a_color;
```
-Второй — вызвать `gl.bindAttribLocation` **до** линковки программы:
+Другой - вызвать `gl.bindAttribLocation` **до** связывания
+шейдерной программы
```js
gl.bindAttribLocation(someProgram, 0, 'a_position');
@@ -159,15 +170,21 @@ gl.bindAttribLocation(someProgram, 1, 'a_color');
gl.linkProgram(someProgram);
```
-Этот способ нечасто используется, но он более
+Этот последний стиль необычен, но он более
[D.R.Y.](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself).
-Наша хелпер-библиотека вызывает `gl.bindAttribLocation` за нас,
-если мы передаем имена атрибутов и нужные локации — это и происходит выше.
+Наша библиотека помощников вызовет `gl.bindAttribLocation` для нас,
+если мы передадим имена атрибутов и местоположение, которое мы хотим,
+что и происходит выше.
-Это гарантирует, что атрибут `a_position` будет использовать локацию 0 в обеих программах, так что мы можем использовать один и тот же vertex array.
+Это означает, что мы можем гарантировать, что атрибут `a_position` использует
+местоположение 0 в обеих программах, поэтому мы можем использовать тот же vertex array
+с обеими программами.
-Далее нам нужно уметь рендерить все объекты дважды: сначала обычным шейдером, потом — только что написанным.
-Вынесем код рендера всех объектов в функцию.
+Далее нам нужно иметь возможность рендерить все объекты
+дважды. Один раз с любым шейдером, который мы назначили
+им, и снова с шейдером, который мы только что написали,
+поэтому давайте извлечем код, который в настоящее время рендерит
+все объекты в функцию.
```js
function drawObjects(objectsToDraw, overrideProgramInfo) {
@@ -179,4 +196,527 @@ function drawObjects(objectsToDraw, overrideProgramInfo) {
gl.useProgram(programInfo.program);
// Настраиваем все нужные атрибуты.
- gl.bindVertexArray(vertexArray);
\ No newline at end of file
+ gl.bindVertexArray(vertexArray);
+
+ // Устанавливаем uniforms.
+ twgl.setUniforms(programInfo, object.uniforms);
+
+ // Рисуем (вызывает gl.drawArrays или gl.drawElements)
+ twgl.drawBufferInfo(gl, object.bufferInfo);
+ });
+}
+```
+
+`drawObjects` принимает опциональный `overrideProgramInfo`,
+который мы можем передать, чтобы использовать наш picking шейдер вместо
+назначенного объекту шейдера.
+
+Давайте вызовем его один раз, чтобы нарисовать в текстуру с
+id, и снова, чтобы нарисовать сцену на canvas.
+
+```js
+// Рисуем сцену.
+function drawScene(time) {
+ time *= 0.0005;
+
+ ...
+
+ // Вычисляем матрицы для каждого объекта.
+ objects.forEach(function(object) {
+ object.uniforms.u_matrix = computeMatrix(
+ viewProjectionMatrix,
+ object.translation,
+ object.xRotationSpeed * time,
+ object.yRotationSpeed * time);
+ });
+
++ // ------ Рисуем объекты в текстуру --------
++
++ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
++ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
++
++ gl.enable(gl.CULL_FACE);
++ gl.enable(gl.DEPTH_TEST);
++
++ // Очищаем canvas И буфер глубины.
++ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
++
++ drawObjects(objectsToDraw, pickingProgramInfo);
++
++ // ------ Рисуем объекты на canvas
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
+
+ drawObjects(objectsToDraw);
+
+ requestAnimationFrame(drawScene);
+}
+```
+
+И с этим мы должны иметь возможность двигать мышью по
+сцене, и объект под мышью будет мигать
+
+{{{example url="../webgl-picking-w-gpu.html" }}}
+
+Одна оптимизация, которую мы можем сделать, мы рендерим
+id в текстуру того же размера,
+что и canvas. Это концептуально самая простая
+вещь для выполнения.
+
+Но мы могли бы вместо этого просто рендерить пиксель
+под мышью. Для этого мы используем усеченную пирамиду,
+математика которой будет покрывать только пространство для этого
+1 пикселя.
+
+До сих пор для 3D мы использовали функцию под названием
+`perspective`, которая принимает в качестве входных данных поле зрения, соотношение сторон и
+ближнее и дальнее значения для z-плоскостей и создает
+матрицу перспективной проекции, которая преобразует из
+усеченной пирамиды, определенной этими значениями, в clip space.
+
+Большинство 3D математических библиотек имеют другую функцию под названием
+`frustum`, которая принимает 6 значений, левое, правое, верхнее,
+и нижнее значения для ближней z-плоскости, а затем
+z-ближнее и z-дальнее значения для z-плоскостей и генерирует
+матрицу перспективы, определенную этими значениями.
+
+Используя это, мы можем сгенерировать матрицу перспективы для
+одного пикселя под мышью
+
+Сначала мы вычисляем края и размер того, чем была бы наша ближняя плоскость,
+если бы мы использовали функцию `perspective`
+
+```js
+// вычисляем прямоугольник, который покрывает ближняя плоскость нашей усеченной пирамиды
+const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
+const top = Math.tan(fieldOfViewRadians * 0.5) * near;
+const bottom = -top;
+const left = aspect * bottom;
+const right = aspect * top;
+const width = Math.abs(right - left);
+const height = Math.abs(top - bottom);
+```
+
+Итак, `left`, `right`, `width` и `height` - это
+размер и позиция ближней плоскости. Теперь на этой
+плоскости мы можем вычислить размер и позицию
+одного пикселя под мышью и передать это в
+функцию `frustum` для генерации матрицы проекции,
+которая покрывает только этот 1 пиксель
+
+```js
+// вычисляем часть ближней плоскости, которая покрывает 1 пиксель
+// под мышью.
+const pixelX = mouseX * gl.canvas.width / gl.canvas.clientWidth;
+const pixelY = gl.canvas.height - mouseY * gl.canvas.height / gl.canvas.clientHeight - 1;
+
+const subLeft = left + pixelX * width / gl.canvas.width;
+const subBottom = bottom + pixelY * height / gl.canvas.height;
+const subWidth = width / gl.canvas.width;
+const subHeight = height / gl.canvas.height;
+
+// делаем усеченную пирамиду для этого 1 пикселя
+const projectionMatrix = m4.frustum(
+ subLeft,
+ subLeft + subWidth,
+ subBottom,
+ subBottom + subHeight,
+ near,
+ far);
+```
+
+Для использования этого нам нужно внести некоторые изменения. Как сейчас наш шейдер
+просто принимает `u_matrix`, что означает, что для рисования с другой
+матрицей проекции нам нужно будет пересчитывать матрицы для каждого объекта
+дважды каждый кадр, один раз с нашей нормальной матрицей проекции для рисования
+на canvas и снова для этой матрицы проекции 1 пикселя.
+
+Мы можем убрать эту ответственность из JavaScript, переместив это
+умножение в вершинные шейдеры.
+
+```html
+const vs = `#version 300 es
+
+in vec4 a_position;
+in vec4 a_color;
+
+-uniform mat4 u_matrix;
++uniform mat4 u_viewProjection;
++uniform mat4 u_world;
+
+out vec4 v_color;
+
+void main() {
+ // Умножаем позицию на матрицу.
+- gl_Position = u_matrix * a_position;
++ gl_Position = u_viewProjection * u_world * a_position;
+
+ // Передаем цвет в фрагментный шейдер.
+ v_color = a_color;
+}
+`;
+
+...
+
+const pickingVS = `#version 300 es
+ in vec4 a_position;
+
+- uniform mat4 u_matrix;
++ uniform mat4 u_viewProjection;
++ uniform mat4 u_world;
+
+ void main() {
+ // Умножаем позицию на матрицу.
+- gl_Position = u_matrix * a_position;
++ gl_Position = u_viewProjection * u_world * a_position;
+ }
+`;
+```
+
+Затем мы можем сделать наш JavaScript `viewProjectionMatrix` общим
+среди всех объектов.
+
+```js
+const objectsToDraw = [];
+const objects = [];
+const viewProjectionMatrix = m4.identity();
+
+// Создаем информацию для каждого объекта для каждого объекта.
+const baseHue = rand(0, 360);
+const numObjects = 200;
+for (let ii = 0; ii < numObjects; ++ii) {
+ const id = ii + 1;
+
+ // выбираем форму
+ const shape = shapes[rand(shapes.length) | 0];
+
+ const object = {
+ uniforms: {
+ u_colorMult: chroma.hsv(eMod(baseHue + rand(0, 120), 360), rand(0.5, 1), rand(0.5, 1)).gl(),
+ u_world: m4.identity(),
+ u_viewProjection: viewProjectionMatrix,
+ u_id: [
+ ((id >> 0) & 0xFF) / 0xFF,
+ ((id >> 8) & 0xFF) / 0xFF,
+ ((id >> 16) & 0xFF) / 0xFF,
+ ((id >> 24) & 0xFF) / 0xFF,
+ ],
+ },
+ translation: [rand(-100, 100), rand(-100, 100), rand(-150, -50)],
+ xRotationSpeed: rand(0.8, 1.2),
+ yRotationSpeed: rand(0.8, 1.2),
+ };
+ objects.push(object);
+
+ // Добавляем его в список вещей для рисования.
+ objectsToDraw.push({
+ programInfo: programInfo,
+ bufferInfo: shape.bufferInfo,
+ vertexArray: shape.vertexArray,
+ uniforms: object.uniforms,
+ });
+}
+```
+
+И где мы вычисляем матрицы для каждого объекта, нам больше не нужно
+включать матрицу проекции вида
+
+```js
+function computeMatrix(translation, xRotation, yRotation) {
+ let matrix = m4.translation(
+ translation[0],
+ translation[1],
+ translation[2]);
+ matrix = m4.xRotate(matrix, xRotation);
+ return m4.yRotate(matrix, yRotation);
+}
+```
+
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
+
+ drawObjects(objectsToDraw);
+
+ requestAnimationFrame(drawScene);
+}
+```
+
+Нашему picking шейдеру нужен `u_id`, установленный в id, поэтому давайте
+добавим это к нашим данным uniform, где мы настраиваем наши объекты.
+
+```js
+// Создаем информацию для каждого объекта для каждого объекта.
+const baseHue = rand(0, 360);
+const numObjects = 200;
+for (let ii = 0; ii < numObjects; ++ii) {
+ const id = ii + 1;
+
+ // выбираем форму
+ const shape = shapes[rand(shapes.length) | 0];
+
+ const object = {
+ uniforms: {
+ u_colorMult: chroma.hsv(eMod(baseHue + rand(0, 120), 360), rand(0.5, 1), rand(0.5, 1)).gl(),
+ u_matrix: m4.identity(),
+ u_id: [
+ ((id >> 0) & 0xFF) / 0xFF,
+ ((id >> 8) & 0xFF) / 0xFF,
+ ((id >> 16) & 0xFF) / 0xFF,
+ ((id >> 24) & 0xFF) / 0xFF,
+ ],
+ },
+ translation: [rand(-100, 100), rand(-100, 100), rand(-150, -50)],
+ xRotationSpeed: rand(0.8, 1.2),
+ yRotationSpeed: rand(0.8, 1.2),
+ };
+ objects.push(object);
+
+ // Добавляем его в список вещей для рисования.
+ objectsToDraw.push({
+ programInfo: programInfo,
+ bufferInfo: shape.bufferInfo,
+ vertexArray: shape.vertexArray,
+ uniforms: object.uniforms,
+ });
+}
+```
+
+Это будет работать, потому что наша [библиотека помощников](webgl-less-code-more-fun.html)
+обрабатывает применение uniforms для нас.
+
+Нам пришлось разделить id по R, G, B и A. Потому что формат/тип нашей
+текстуры - `gl.RGBA`, `gl.UNSIGNED_BYTE`,
+мы получаем 8 бит на канал. 8 бит представляют только 256 значений,
+но, разделив id по 4 каналам, мы получаем 32 бита всего,
+что составляет > 4 миллиарда значений.
+
+Мы добавляем 1 к id, потому что мы будем использовать 0 для обозначения
+"ничего под мышью".
+
+Теперь давайте выделим объект под мышью.
+
+Сначала нам нужен код для получения позиции мыши относительно canvas.
+
+```js
+// mouseX и mouseY находятся в CSS display space относительно canvas
+let mouseX = -1;
+let mouseY = -1;
+
+...
+
+gl.canvas.addEventListener('mousemove', (e) => {
+ const rect = canvas.getBoundingClientRect();
+ mouseX = e.clientX - rect.left;
+ mouseY = e.clientY - rect.top;
+});
+```
+
+Обратите внимание, что с кодом выше `mouseX` и `mouseY`
+находятся в CSS пикселях в display space. Это означает,
+что они находятся в пространстве, где отображается canvas,
+а не в пространстве того, сколько пикселей в canvas.
+Другими словами, если у вас был canvas как этот
+
+```html
+
+```
+
+тогда `mouseX` будет идти от 0 до 33 по canvas и
+`mouseY` будет идти от 0 до 44 по canvas. Смотрите [это](webgl-resizing-the-canvas.html)
+для получения дополнительной информации.
+
+Теперь, когда у нас есть позиция мыши, давайте добавим код
+для поиска пикселя под мышью
+
+```js
+const pixelX = mouseX * gl.canvas.width / gl.canvas.clientWidth;
+const pixelY = gl.canvas.height - mouseY * gl.canvas.height / gl.canvas.clientHeight - 1;
+const data = new Uint8Array(4);
+gl.readPixels(
+ pixelX, // x
+ pixelY, // y
+ 1, // width
+ 1, // height
+ gl.RGBA, // format
+ gl.UNSIGNED_BYTE, // type
+ data); // typed array to hold result
+const id = data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24);
+```
+
+Код выше, который вычисляет `pixelX` и `pixelY`, преобразует
+из `mouseX` и `mouseY` в display space в пиксели в пространстве canvas.
+Другими словами, учитывая пример выше, где `mouseX` шел от
+0 до 33 и `mouseY` шел от 0 до 44. `pixelX` будет идти от 0 до 11
+и `pixelY` будет идти от 0 до 22.
+
+В нашем фактическом коде мы используем нашу утилитную функцию `resizeCanvasToDisplaySize`
+и мы делаем нашу текстуру того же размера, что и canvas, поэтому display
+размер и размер canvas совпадают, но, по крайней мере, мы готовы к случаю,
+когда они не совпадают.
+
+Теперь, когда у нас есть id, чтобы фактически выделить выбранный объект,
+давайте изменим цвет, который мы используем для его рендеринга на canvas.
+Шейдер, который мы использовали, имеет uniform `u_colorMult`,
+который мы можем использовать, поэтому если объект под мышью, мы найдем его,
+сохраним его значение `u_colorMult`, заменим его цветом выделения,
+и восстановим его.
+
+```js
+// mouseX и mouseY находятся в CSS display space относительно canvas
+let mouseX = -1;
+let mouseY = -1;
+let oldPickNdx = -1;
+let oldPickColor;
+let frameCount = 0;
+
+// Рисуем сцену.
+function drawScene(time) {
+ time *= 0.0005;
+ ++frameCount;
+
+ webglUtils.resizeCanvasToDisplaySize(gl.canvas);
+```
+
+Перед рендерингом id вне экрана мы устанавливаем матрицу проекции вида,
+используя нашу матрицу проекции для 1 пикселя, а при рисовании на canvas
+используем исходную матрицу проекции.
+
+```js
+// Вычисляем матрицу камеры с помощью lookAt.
+const cameraPosition = [0, 0, 100];
+const target = [0, 0, 0];
+const up = [0, 1, 0];
+const cameraMatrix = m4.lookAt(cameraPosition, target, up);
+
+// Создаём матрицу вида из матрицы камеры.
+const viewMatrix = m4.inverse(cameraMatrix);
+
+// Вычисляем матрицы для каждого объекта.
+objects.forEach(function(object) {
+ object.uniforms.u_world = computeMatrix(
+ object.translation,
+ object.xRotationSpeed * time,
+ object.yRotationSpeed * time);
+});
+
+// ------ Рисуем объекты в текстуру --------
+
+// Определяем, какой пиксель под мышью, и настраиваем
+// усечённую пирамиду для рендера только этого пикселя
+
+{
+ // вычисляем прямоугольник, который покрывает ближнюю плоскость нашей усечённой пирамиды
+ const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
+ const top = Math.tan(fieldOfViewRadians * 0.5) * near;
+ const bottom = -top;
+ const left = aspect * bottom;
+ const right = aspect * top;
+ const width = Math.abs(right - left);
+ const height = Math.abs(top - bottom);
+
+ // вычисляем часть ближней плоскости, которая покрывает 1 пиксель под мышью
+ const pixelX = mouseX * gl.canvas.width / gl.canvas.clientWidth;
+ const pixelY = gl.canvas.height - mouseY * gl.canvas.height / gl.canvas.clientHeight - 1;
+
+ const subLeft = left + pixelX * width / gl.canvas.width;
+ const subBottom = bottom + pixelY * height / gl.canvas.height;
+ const subWidth = width / gl.canvas.width;
+ const subHeight = height / gl.canvas.height;
+
+ // создаём усечённую пирамиду для этого 1 пикселя
+ const projectionMatrix = m4.frustum(
+ subLeft,
+ subLeft + subWidth,
+ subBottom,
+ subBottom + subHeight,
+ near,
+ far);
+ m4.multiply(projectionMatrix, viewMatrix, viewProjectionMatrix);
+}
+
+gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
+gl.viewport(0, 0, 1, 1);
+
+gl.enable(gl.CULL_FACE);
+gl.enable(gl.DEPTH_TEST);
+
+gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+drawObjects(objectsToDraw, pickingProgramInfo);
+
+// читаем 1 пиксель
+const data = new Uint8Array(4);
+gl.readPixels(
+ 0, // x
+ 0, // y
+ 1, // width
+ 1, // height
+ gl.RGBA, // format
+ gl.UNSIGNED_BYTE, // type
+ data); // typed array to hold result
+const id = data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24);
+
+// восстанавливаем цвет объекта
+if (oldPickNdx >= 0) {
+ const object = objects[oldPickNdx];
+ object.uniforms.u_colorMult = oldPickColor;
+ oldPickNdx = -1;
+}
+
+// выделяем объект под мышью
+if (id > 0) {
+ const pickNdx = id - 1;
+ oldPickNdx = pickNdx;
+ const object = objects[pickNdx];
+ oldPickColor = object.uniforms.u_colorMult;
+ object.uniforms.u_colorMult = (frameCount & 0x8) ? [1, 0, 0, 1] : [1, 1, 0, 1];
+}
+
+// ------ Рисуем объекты на canvas
+
+{
+ // Вычисляем матрицу проекции
+ const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
+ const projectionMatrix =
+ m4.perspective(fieldOfViewRadians, aspect, near, far);
+
+ m4.multiply(projectionMatrix, viewMatrix, viewProjectionMatrix);
+}
+
+gl.bindFramebuffer(gl.FRAMEBUFFER, null);
+gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
+
+drawObjects(objectsToDraw);
+
+requestAnimationFrame(drawScene);
+}
+```
+
+Как видно, математика работает: мы рендерим только один пиксель
+и всё равно определяем, что находится под мышью.
+
+{{{example url="../webgl-picking-w-gpu-1pixel.html"}}}
+
+Эта оптимизация может быть полезна, если у вас много объектов
+и вы хотите минимизировать использование памяти. Вместо создания
+текстуры размером с canvas, вы создаете текстуру размером 1x1 пиксель.
+
+Но есть компромисс. Теперь мы должны вычислять усеченную пирамиду
+для каждого пикселя, что может быть дороже, чем просто создание
+большей текстуры. Это зависит от вашего случая использования.
+
+Также обратите внимание, что мы больше не читаем пиксель из позиции
+мыши. Мы читаем пиксель из позиции (0,0), потому что теперь мы
+рендерим только 1 пиксель в позиции (0,0) нашей 1x1 текстуры.
+
+Это одна из многих техник, которые вы можете использовать для выбора
+объектов в WebGL. Другие включают:
+
+1. **Ray casting** - бросание луча из позиции мыши в 3D пространство
+2. **Bounding box/sphere testing** - проверка, находится ли точка внутри ограничивающего прямоугольника/сферы
+3. **GPU picking** - то, что мы только что сделали
+4. **Hierarchical picking** - выбор на основе иерархии объектов
+
+Каждая техника имеет свои преимущества и недостатки в зависимости
+от вашего случая использования.
diff --git a/webgl/lessons/ru/webgl-planar-projection-mapping.md b/webgl/lessons/ru/webgl-planar-projection-mapping.md
index 26a01d169..538f653ce 100644
--- a/webgl/lessons/ru/webgl-planar-projection-mapping.md
+++ b/webgl/lessons/ru/webgl-planar-projection-mapping.md
@@ -45,14 +45,20 @@ in vec2 a_texcoord;
uniform mat4 u_projection;
uniform mat4 u_view;
uniform mat4 u_world;
+uniform mat4 u_textureMatrix;
out vec2 v_texcoord;
+out vec4 v_projectedTexcoord;
void main() {
- gl_Position = u_projection * u_view * u_world * a_position;
+ vec4 worldPosition = u_world * a_position;
+
+ gl_Position = u_projection * u_view * worldPosition;
// Передаём текстурные координаты во фрагментный шейдер.
v_texcoord = a_texcoord;
+
+ v_projectedTexcoord = u_textureMatrix * worldPosition;
}
`;
```
@@ -66,14 +72,29 @@ precision highp float;
// Передано из вершинного шейдера.
in vec2 v_texcoord;
+in vec4 v_projectedTexcoord;
uniform vec4 u_colorMult;
uniform sampler2D u_texture;
+uniform sampler2D u_projectedTexture;
out vec4 outColor;
void main() {
- outColor = texture(u_texture, v_texcoord) * u_colorMult;
+ // делим на w, чтобы получить правильное значение. См. статью о перспективе
+ vec3 projectedTexcoord = v_projectedTexcoord.xyz / v_projectedTexcoord.w;
+
+ bool inRange =
+ projectedTexcoord.x >= 0.0 &&
+ projectedTexcoord.x <= 1.0 &&
+ projectedTexcoord.y >= 0.0 &&
+ projectedTexcoord.y <= 1.0;
+
+ vec4 projectedTexColor = texture(u_projectedTexture, projectedTexcoord.xy);
+ vec4 texColor = texture(u_texture, v_texcoord) * u_colorMult;
+
+ float projectedAmount = inRange ? 1.0 : 0.0;
+ outColor = mix(texColor, projectedTexColor, projectedAmount);
}
`;
```
@@ -192,4 +213,481 @@ function drawScene(projectionMatrix, cameraMatrix) {
```js
const settings = {
cameraX: 2.75,
- cameraY: 5,
\ No newline at end of file
+ cameraY: 5,
+};
+const fieldOfViewRadians = degToRad(60);
+
+function render() {
+ twgl.resizeCanvasToDisplaySize(gl.canvas);
+
+ // Говорим WebGL, как конвертировать из clip space в пиксели
+ gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
+
+ gl.enable(gl.CULL_FACE);
+ gl.enable(gl.DEPTH_TEST);
+
+ // Очищаем canvas И буфер глубины.
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
+
+ // Вычисляем матрицу проекции
+ const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
+ const projectionMatrix =
+ m4.perspective(fieldOfViewRadians, aspect, 1, 2000);
+
+ // Вычисляем матрицу камеры, используя look at.
+ const cameraPosition = [settings.cameraX, settings.cameraY, 7];
+ const target = [0, 0, 0];
+ const up = [0, 1, 0];
+ const cameraMatrix = m4.lookAt(cameraPosition, target, up);
+
+ drawScene(projectionMatrix, cameraMatrix);
+}
+render();
+```
+
+Теперь у нас есть простая сцена с плоскостью и сферой.
+Я добавил несколько слайдеров, чтобы вы могли изменить позицию камеры
+и лучше понять сцену.
+
+{{{example url="../webgl-planar-projection-setup.html"}}}
+
+Теперь давайте планарно спроецируем текстуру на сферу и плоскость.
+
+Первое, что нужно сделать — [загрузить текстуру](webgl-3d-textures.html).
+
+```js
+function loadImageTexture(url) {
+ // Создаём текстуру.
+ const texture = gl.createTexture();
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ // Заполняем текстуру 1x1 синим пикселем.
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
+ new Uint8Array([0, 0, 255, 255]));
+ // Асинхронно загружаем изображение
+ const image = new Image();
+ image.src = url;
+ image.addEventListener('load', function() {
+ // Теперь, когда изображение загружено, копируем его в текстуру.
+ gl.bindTexture(gl.TEXTURE_2D, texture);
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
+ // предполагаем, что эта текстура имеет размер степени 2
+ gl.generateMipmap(gl.TEXTURE_2D);
+ render();
+ });
+ return texture;
+}
+
+const imageTexture = loadImageTexture('resources/f-texture.png');
+```
+
+Вспомним из [статьи о визуализации камеры](webgl-visualizing-the-camera.html),
+мы создали куб от -1 до +1 и нарисовали его, чтобы представить усечённую пирамиду камеры.
+Наши матрицы сделали так, что пространство внутри этой пирамиды представляет некоторую
+область в форме усечённой пирамиды в мировом пространстве, которая преобразуется
+из этого мирового пространства в clip space от -1 до +1. Мы можем сделать аналогичную вещь здесь.
+
+Давайте попробуем. Сначала в нашем фрагментном шейдере мы будем рисовать спроецированную текстуру
+везде, где её текстурные координаты находятся между 0.0 и 1.0.
+За пределами этого диапазона мы будем использовать текстуру-шахматку.
+
+```js
+const fs = `#version 300 es
+precision highp float;
+
+// Передано из вершинного шейдера.
+in vec2 v_texcoord;
+in vec4 v_projectedTexcoord;
+
+uniform vec4 u_colorMult;
+uniform sampler2D u_texture;
+uniform sampler2D u_projectedTexture;
+
+out vec4 outColor;
+
+void main() {
+ // делим на w, чтобы получить правильное значение. См. статью о перспективе
+ vec3 projectedTexcoord = v_projectedTexcoord.xyz / v_projectedTexcoord.w;
+
+ bool inRange =
+ projectedTexcoord.x >= 0.0 &&
+ projectedTexcoord.x <= 1.0 &&
+ projectedTexcoord.y >= 0.0 &&
+ projectedTexcoord.y <= 1.0;
+
+ vec4 projectedTexColor = texture(u_projectedTexture, projectedTexcoord.xy);
+ vec4 texColor = texture(u_texture, v_texcoord) * u_colorMult;
+
+ float projectedAmount = inRange ? 1.0 : 0.0;
+ outColor = mix(texColor, projectedTexColor, projectedAmount);
+}
+`;
+```
+
+Для вычисления спроецированных текстурных координат мы создадим
+матрицу, которая представляет 3D пространство, ориентированное и позиционированное
+в определённом направлении, точно так же, как камера из [статьи о визуализации камеры](webgl-visualizing-the-camera.html).
+Затем мы спроецируем мировые позиции
+вершин сферы и плоскости через это пространство. Там, где
+они находятся между 0 и 1, код, который мы только что написали, покажет
+текстуру.
+
+Давайте добавим код в вершинный шейдер для проецирования мировых позиций
+сферы и плоскости через это *пространство*.
+
+```js
+const vs = `#version 300 es
+in vec4 a_position;
+in vec2 a_texcoord;
+
+uniform mat4 u_projection;
+uniform mat4 u_view;
+uniform mat4 u_world;
+uniform mat4 u_textureMatrix;
+
+out vec2 v_texcoord;
+out vec4 v_projectedTexcoord;
+
+void main() {
+ vec4 worldPosition = u_world * a_position;
+
+ gl_Position = u_projection * u_view * worldPosition;
+
+ // Передаём текстурные координаты во фрагментный шейдер.
+ v_texcoord = a_texcoord;
+
+ v_projectedTexcoord = u_textureMatrix * worldPosition;
+}
+`;
+```
+
+Теперь всё, что осталось — это фактически вычислить матрицу, которая
+определяет это ориентированное пространство. Всё, что нам нужно сделать — это вычислить
+мировую матрицу, как мы бы делали для любого другого объекта, а затем взять
+её обратную. Это даст нам матрицу, которая позволяет нам ориентировать
+мировые позиции других объектов относительно этого пространства.
+Это точно то же самое, что делает матрица вида из
+[статьи о камерах](webgl-3d-camera.html).
+
+Мы будем использовать нашу функцию `lookAt`, которую мы создали в [той же статье](webgl-3d-camera.html).
+
+```js
+const settings = {
+ cameraX: 2.75,
+ cameraY: 5,
+ posX: 3.5,
+ posY: 4.4,
+ posZ: 4.7,
+ targetX: 0.8,
+ targetY: 0,
+ targetZ: 4.7,
+};
+
+function drawScene(projectionMatrix, cameraMatrix) {
+ // Получаем view-матрицу из матрицы камеры.
+ const viewMatrix = m4.inverse(cameraMatrix);
+
+ let textureWorldMatrix = m4.lookAt(
+ [settings.posX, settings.posY, settings.posZ], // позиция
+ [settings.targetX, settings.targetY, settings.targetZ], // цель
+ [0, 1, 0], // up
+ );
+ textureWorldMatrix = m4.scale(
+ textureWorldMatrix,
+ settings.projWidth, settings.projHeight, 1,
+ );
+
+ // используем обратную этой мировой матрицы, чтобы сделать
+ // матрицу, которая будет преобразовывать другие позиции
+ // относительно этого мирового пространства.
+ const textureMatrix = m4.inverse(textureWorldMatrix);
+
+ // устанавливаем uniforms, которые одинаковы для сферы и плоскости
+ twgl.setUniforms(textureProgramInfo, {
+ u_view: viewMatrix,
+ u_projection: projectionMatrix,
+ u_textureMatrix: textureMatrix,
+ u_projectedTexture: imageTexture,
+ });
+
+ // ------ Рисуем сферу --------
+
+ // Настраиваем все нужные атрибуты.
+ gl.bindVertexArray(sphereVAO);
+
+ // Устанавливаем uniforms, уникальные для сферы
+ twgl.setUniforms(textureProgramInfo, sphereUniforms);
+
+ // вызывает gl.drawArrays или gl.drawElements
+ twgl.drawBufferInfo(gl, sphereBufferInfo);
+
+ // ------ Рисуем плоскость --------
+
+ // Настраиваем все нужные атрибуты.
+ gl.bindVertexArray(planeVAO);
+
+ // Устанавливаем uniforms, уникальные для плоскости
+ twgl.setUniforms(textureProgramInfo, planeUniforms);
+
+ // вызывает gl.drawArrays или gl.drawElements
+ twgl.drawBufferInfo(gl, planeBufferInfo);
+
+ // ------ Рисуем каркасный куб --------
+
+ gl.useProgram(colorProgramInfo.program);
+
+ // Настраиваем все нужные атрибуты.
+ gl.bindVertexArray(cubeLinesVAO);
+
+ // Устанавливаем uniforms для каркасного куба
+ twgl.setUniforms(colorProgramInfo, {
+ u_view: viewMatrix,
+ u_projection: projectionMatrix,
+ u_world: textureWorldMatrix,
+ u_color: [1, 1, 1, 1], // белый
+ });
+
+ // Рисуем линии
+ gl.drawElements(gl.LINES, cubeLinesBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
+}
+```
+
+Конечно, вам не обязательно использовать `lookAt`. Вы можете создать
+мировую матрицу любым выбранным способом, например, используя
+[граф сцены](webgl-scene-graph.html) или [стек матриц](webgl-2d-matrix-stack.html).
+
+Перед тем как запустить, давайте добавим какой-то масштаб.
+
+```js
+const settings = {
+ cameraX: 2.75,
+ cameraY: 5,
+ posX: 3.5,
+ posY: 4.4,
+ posZ: 4.7,
+ targetX: 0.8,
+ targetY: 0,
+ targetZ: 4.7,
+ projWidth: 2,
+ projHeight: 2,
+};
+
+function drawScene(projectionMatrix, cameraMatrix) {
+ // Получаем view-матрицу из матрицы камеры.
+ const viewMatrix = m4.inverse(cameraMatrix);
+
+ let textureWorldMatrix = m4.lookAt(
+ [settings.posX, settings.posY, settings.posZ], // позиция
+ [settings.targetX, settings.targetY, settings.targetZ], // цель
+ [0, 1, 0], // up
+ );
+ textureWorldMatrix = m4.scale(
+ textureWorldMatrix,
+ settings.projWidth, settings.projHeight, 1,
+ );
+
+ // используем обратную этой мировой матрицы, чтобы сделать
+ // матрицу, которая будет преобразовывать другие позиции
+ // относительно этого мирового пространства.
+ const textureMatrix = m4.inverse(textureWorldMatrix);
+
+ ...
+}
+```
+
+И с этим мы получаем спроецированную текстуру.
+
+{{{example url="../webgl-planar-projection.html"}}}
+
+Я думаю, может быть трудно увидеть пространство, в котором находится текстура.
+Давайте добавим каркасный куб для визуализации.
+
+Сначала нам нужен отдельный набор шейдеров. Эти шейдеры
+могут просто рисовать сплошной цвет, без текстур.
+
+```js
+const colorVS = `#version 300 es
+in vec4 a_position;
+
+uniform mat4 u_projection;
+uniform mat4 u_view;
+uniform mat4 u_world;
+
+void main() {
+ // Умножаем позицию на матрицы.
+ gl_Position = u_projection * u_view * u_world * a_position;
+}
+`;
+```
+
+```js
+const colorFS = `#version 300 es
+precision highp float;
+
+uniform vec4 u_color;
+
+out vec4 outColor;
+
+void main() {
+ outColor = u_color;
+}
+`;
+```
+
+Затем нам нужно скомпилировать и связать эти шейдеры тоже.
+
+```js
+// настройка GLSL программ
+const textureProgramInfo = twgl.createProgramInfo(gl, [vs, fs]);
+const colorProgramInfo = twgl.createProgramInfo(gl, [colorVS, colorFS]);
+```
+
+И нам нужны данные для рисования куба из линий.
+
+```js
+const sphereBufferInfo = primitives.createSphereBufferInfo(
+ gl,
+ 1, // радиус
+ 12, // делений по кругу
+ 6, // делений по высоте
+);
+const sphereVAO = twgl.createVAOFromBufferInfo(
+ gl, textureProgramInfo, sphereBufferInfo);
+const planeBufferInfo = primitives.createPlaneBufferInfo(
+ gl,
+ 20, // ширина
+ 20, // высота
+ 1, // делений по ширине
+ 1, // делений по высоте
+);
+const planeVAO = twgl.createVAOFromBufferInfo(
+ gl, textureProgramInfo, planeBufferInfo);
+```
+
+Теперь давайте создадим данные для каркасного куба.
+
+```js
+// Создаём данные для каркасного куба
+const cubeLinesBufferInfo = twgl.createBufferInfoFromArrays(gl, {
+ position: {
+ numComponents: 3,
+ data: [
+ // передняя грань
+ -1, -1, 1,
+ 1, -1, 1,
+ 1, 1, 1,
+ -1, 1, 1,
+ // задняя грань
+ -1, -1, -1,
+ 1, -1, -1,
+ 1, 1, -1,
+ -1, 1, -1,
+ ],
+ },
+ indices: {
+ numComponents: 2,
+ data: [
+ // передняя грань
+ 0, 1, 1, 2, 2, 3, 3, 0,
+ // задняя грань
+ 4, 5, 5, 6, 6, 7, 7, 4,
+ // соединяющие линии
+ 0, 4, 1, 5, 2, 6, 3, 7,
+ ],
+ },
+});
+const cubeLinesVAO = twgl.createVAOFromBufferInfo(
+ gl, colorProgramInfo, cubeLinesBufferInfo);
+```
+
+Теперь давайте добавим код для рисования каркасного куба в нашу функцию `drawScene`.
+
+```js
+function drawScene(projectionMatrix, cameraMatrix) {
+ // Получаем view-матрицу из матрицы камеры.
+ const viewMatrix = m4.inverse(cameraMatrix);
+
+ let textureWorldMatrix = m4.lookAt(
+ [settings.posX, settings.posY, settings.posZ], // позиция
+ [settings.targetX, settings.targetY, settings.targetZ], // цель
+ [0, 1, 0], // up
+ );
+ textureWorldMatrix = m4.scale(
+ textureWorldMatrix,
+ settings.projWidth, settings.projHeight, 1,
+ );
+
+ // используем обратную этой мировой матрицы, чтобы сделать
+ // матрицу, которая будет преобразовывать другие позиции
+ // относительно этого мирового пространства.
+ const textureMatrix = m4.inverse(textureWorldMatrix);
+
+ // устанавливаем uniforms, которые одинаковы для сферы и плоскости
+ twgl.setUniforms(textureProgramInfo, {
+ u_view: viewMatrix,
+ u_projection: projectionMatrix,
+ u_textureMatrix: textureMatrix,
+ u_projectedTexture: imageTexture,
+ });
+
+ // ------ Рисуем сферу --------
+
+ // Настраиваем все нужные атрибуты.
+ gl.bindVertexArray(sphereVAO);
+
+ // Устанавливаем uniforms, уникальные для сферы
+ twgl.setUniforms(textureProgramInfo, sphereUniforms);
+
+ // вызывает gl.drawArrays или gl.drawElements
+ twgl.drawBufferInfo(gl, sphereBufferInfo);
+
+ // ------ Рисуем плоскость --------
+
+ // Настраиваем все нужные атрибуты.
+ gl.bindVertexArray(planeVAO);
+
+ // Устанавливаем uniforms, уникальные для плоскости
+ twgl.setUniforms(textureProgramInfo, planeUniforms);
+
+ // вызывает gl.drawArrays или gl.drawElements
+ twgl.drawBufferInfo(gl, planeBufferInfo);
+
+ // ------ Рисуем каркасный куб --------
+
+ gl.useProgram(colorProgramInfo.program);
+
+ // Настраиваем все нужные атрибуты.
+ gl.bindVertexArray(cubeLinesVAO);
+
+ // Устанавливаем uniforms для каркасного куба
+ twgl.setUniforms(colorProgramInfo, {
+ u_view: viewMatrix,
+ u_projection: projectionMatrix,
+ u_world: textureWorldMatrix,
+ u_color: [1, 1, 1, 1], // белый
+ });
+
+ // Рисуем линии
+ gl.drawElements(gl.LINES, cubeLinesBufferInfo.numElements, gl.UNSIGNED_SHORT, 0);
+}
+```
+
+И с этим мы получаем каркасный куб, который показывает пространство проекции.
+
+{{{example url="../webgl-planar-projection-with-lines.html"}}}
+
+Теперь вы можете видеть каркасный куб, который показывает, где проецируется текстура.
+Вы можете изменить настройки, чтобы переместить проектор и изменить его ориентацию.
+
+Это планарное проекционное отображение. Текстура проецируется как плоскость.
+Если вы хотите перспективное проекционное отображение, где текстура увеличивается
+с расстоянием, вам нужно будет изменить матрицу проекции.
+
+Для перспективного проекционного отображения вы можете использовать
+матрицу перспективы вместо ортографической матрицы в `u_textureMatrix`.
+Это создаст эффект, где текстура увеличивается с расстоянием от проектора,
+точно так же, как настоящий кинопроектор.
+
+Проекционное отображение — это мощная техника для создания
+реалистичных эффектов, таких как проецирование изображений на стены,
+создание голографических эффектов или добавление динамического освещения
+к статическим объектам.
diff --git a/webgl/lessons/ru/webgl-points-lines-triangles.md b/webgl/lessons/ru/webgl-points-lines-triangles.md
index fc0962c4c..c93c2800c 100644
--- a/webgl/lessons/ru/webgl-points-lines-triangles.md
+++ b/webgl/lessons/ru/webgl-points-lines-triangles.md
@@ -128,4 +128,6 @@ WebGL рисует точки, линии и треугольники. Он де
показать контуры многоугольников в программе 3D моделирования, использование `LINES`
может быть отличным, но если вы хотите нарисовать структурированную графику, такую как
SVG или Adobe Illustrator, то это не будет работать, и вам придется
-[рендерить ваши линии каким-то другим способом, обычно из треугольников](https://mattdesl.svbtle.com/drawing-lines-is-hard).
\ No newline at end of file
+[рендерить ваши линии каким-то другим способом, обычно из треугольников](https://mattdesl.svbtle.com/drawing-lines-is-hard).
+
+
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-precision-issues.md b/webgl/lessons/ru/webgl-precision-issues.md
index d7e35546d..bdd32621b 100644
--- a/webgl/lessons/ru/webgl-precision-issues.md
+++ b/webgl/lessons/ru/webgl-precision-issues.md
@@ -254,18 +254,18 @@ gl.texImage2D(

-Одна вещь для заметки в том, что по умолчанию WebGL может дизерить свои результаты, чтобы сделать
-градации, как эта, выглядеть более гладкими. Вы можете выключить дизеринг с
+Одна вещь, которую стоит отметить, это то, что по умолчанию WebGL может дизерить свои результаты, чтобы сделать
+такие градации более гладкими. Вы можете отключить дизеринг с помощью
```js
gl.disable(gl.DITHER);
```
-Если я не выключаю дизеринг, то мой смартфон производит это.
+Если я не отключаю дизеринг, то мой смартфон производит это.

Сходу единственное место, где это действительно возникло бы, это если бы вы
-использовали некоторый формат текстуры с более низким битовым разрешением как цель рендеринга и не
+использовали какую-то текстуру с более низким битовым разрешением как цель рендеринга и не
тестировали на устройстве, где эта текстура действительно имеет это более низкое разрешение.
-Если вы только тестировали на настольном компьютере, любые проблемы, которые это вызывает, могут не быть очевидными.
\ No newline at end of file
+Если вы тестировали только на настольном компьютере, любые проблемы, которые это вызывает, могут быть не очевидны.
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-pulling-vertices.md b/webgl/lessons/ru/webgl-pulling-vertices.md
index 236166edb..fd2d62f93 100644
--- a/webgl/lessons/ru/webgl-pulling-vertices.md
+++ b/webgl/lessons/ru/webgl-pulling-vertices.md
@@ -195,4 +195,256 @@ gl.bufferData(gl.ARRAY_BUFFER, new Uint32Array(positionIndexUVIndex), gl.STATIC_
// Включаем атрибут индекса позиции
gl.enableVertexAttribArray(posTexIndexLoc);
-// Говорим атрибуту индекса позиции/texcoord, как забирать данные из буфера
\ No newline at end of file
+// Говорим атрибуту индекса позиции/texcoord, как забирать данные из буфера
+// positionIndexUVIndexBuffer (ARRAY_BUFFER)
+{
+ const size = 2; // 2 компонента на итерацию
+ const type = gl.INT; // данные - 32-битные целые числа
+ const stride = 0; // 0 = двигаться вперёд на size * sizeof(type) каждый раз для получения следующей позиции
+ const offset = 0; // начинать с начала буфера
+ gl.vertexAttribIPointer(
+ posTexIndexLoc, size, type, stride, offset);
+}
+```
+
+Обратите внимание, что мы вызываем `gl.vertexAttribIPointer`, а не `gl.vertexAttribPointer`.
+`I` означает integer и используется для целочисленных и беззнаковых целочисленных атрибутов.
+Также заметьте, что size равен 2, поскольку на вершину приходится 1 индекс позиции и 1 индекс texcoord.
+
+Хотя нам нужно только 24 вершины, мы всё равно должны рисовать 6 граней, 12 треугольников
+каждая, 3 вершины на треугольник = 36 вершин. Чтобы указать, какие 6 вершин
+использовать для каждой грани, мы будем использовать [индексы вершин](webgl-indexed-vertices.html).
+
+```js
+const indices = [
+ 0, 1, 2, 2, 1, 3, // front
+ 4, 5, 6, 6, 5, 7, // right
+ 8, 9, 10, 10, 9, 11, // back
+ 12, 13, 14, 14, 13, 15, // left
+ 16, 17, 18, 18, 17, 19, // top
+ 20, 21, 22, 22, 21, 23, // bottom
+];
+// Создаём индексный буфер
+const indexBuffer = gl.createBuffer();
+gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
+// Кладём индексы в буфер
+gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
+```
+
+Поскольку мы хотим нарисовать изображение на самом кубе, нам нужна 3-я текстура
+с этим изображением. Давайте просто создадим ещё одну 4x4 data-текстуру с шахматной доской.
+Мы будем использовать `gl.LUMINANCE` как формат, поскольку тогда нам нужен только один байт на пиксель.
+
+```js
+// Создаём текстуру-шахматку
+const checkerTexture = gl.createTexture();
+gl.bindTexture(gl.TEXTURE_2D, checkerTexture);
+// Заполняем текстуру 4x4 серой шахматкой
+gl.texImage2D(
+ gl.TEXTURE_2D,
+ 0,
+ gl.LUMINANCE,
+ 4,
+ 4,
+ 0,
+ gl.LUMINANCE,
+ gl.UNSIGNED_BYTE,
+ new Uint8Array([
+ 0xDD, 0x99, 0xDD, 0xAA,
+ 0x88, 0xCC, 0x88, 0xDD,
+ 0xCC, 0x88, 0xCC, 0xAA,
+ 0x88, 0xCC, 0x88, 0xCC,
+ ]),
+);
+gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
+gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
+```
+
+Переходим к вершинному шейдеру... Мы можем получить пиксель из текстуры так:
+
+```glsl
+vec4 color = texelFetch(sampler2D tex, ivec2 pixelCoord, int mipLevel);
+```
+
+Итак, по целочисленным координатам пикселя код выше извлечёт значение пикселя.
+
+Используя функцию `texelFetch`, мы можем взять 1D индекс массива
+и найти значение в 2D текстуре так:
+
+```glsl
+vec4 getValueByIndexFromTexture(sampler2D tex, int index) {
+ int texWidth = textureSize(tex, 0).x;
+ int col = index % texWidth;
+ int row = index / texWidth;
+ return texelFetch(tex, ivec2(col, row), 0);
+}
+```
+
+Итак, учитывая эту функцию, вот наш шейдер:
+
+```glsl
+#version 300 es
+in ivec2 positionAndTexcoordIndices;
+
+uniform sampler2D positionTexture;
+uniform sampler2D texcoordTexture;
+
+uniform mat4 u_matrix;
+
+out vec2 v_texcoord;
+
+vec4 getValueByIndexFromTexture(sampler2D tex, int index) {
+ int texWidth = textureSize(tex, 0).x;
+ int col = index % texWidth;
+ int row = index / texWidth;
+ return texelFetch(tex, ivec2(col, row), 0);
+}
+
+void main() {
+ int positionIndex = positionAndTexcoordIndices.x;
+ vec3 position = getValueByIndexFromTexture(
+ positionTexture, positionIndex).xyz;
+
+ // Умножаем позицию на матрицу
+ gl_Position = u_matrix * vec4(position, 1);
+
+ int texcoordIndex = positionAndTexcoordIndices.y;
+ vec2 texcoord = getValueByIndexFromTexture(
+ texcoordTexture, texcoordIndex).xy;
+
+ // Передаём texcoord в фрагментный шейдер
+ v_texcoord = texcoord;
+}
+```
+
+Внизу это по сути тот же шейдер, который мы использовали
+в [статье о текстурах](webgl-3d-textures.html).
+Мы умножаем `position` на `u_matrix` и выводим
+texcoord в `v_texcoord` для передачи в фрагментный шейдер.
+
+Разница только в том, как мы получаем position и
+texcoord. Мы используем переданные индексы и получаем
+эти значения из соответствующих текстур.
+
+Чтобы использовать шейдер, нужно найти все локации:
+
+```js
+// настраиваем GLSL программу
+const program = webglUtils.createProgramFromSources(gl, [vs, fs]);
+
+// ищем, куда должны идти вершинные данные
+const posTexIndexLoc = gl.getAttribLocation(
+ program, "positionAndTexcoordIndices");
+
+// ищем uniform'ы
+const matrixLoc = gl.getUniformLocation(program, "u_matrix");
+const positionTexLoc = gl.getUniformLocation(program, "positionTexture");
+const texcoordTexLoc = gl.getUniformLocation(program, "texcoordTexture");
+const u_textureLoc = gl.getUniformLocation(program, "u_texture");
+```
+
+Во время рендеринга настраиваем атрибуты:
+
+```js
+// Говорим использовать нашу программу (пару шейдеров)
+gl.useProgram(program);
+
+// Устанавливаем буфер и состояние атрибутов
+gl.bindVertexArray(vao);
+```
+
+Затем нужно привязать все 3 текстуры и настроить все
+uniform'ы:
+
+```js
+// Устанавливаем матрицу
+gl.uniformMatrix4fv(matrixLoc, false, matrix);
+
+// кладём текстуру позиций на texture unit 0
+gl.activeTexture(gl.TEXTURE0);
+gl.bindTexture(gl.TEXTURE_2D, positionTexture);
+// Говорим шейдеру использовать texture unit 0 для positionTexture
+gl.uniform1i(positionTexLoc, 0);
+
+// кладём текстуру texcoord на texture unit 1
+gl.activeTexture(gl.TEXTURE0 + 1);
+gl.bindTexture(gl.TEXTURE_2D, texcoordTexture);
+// Говорим шейдеру использовать texture unit 1 для texcoordTexture
+gl.uniform1i(texcoordTexLoc, 1);
+
+// кладём текстуру-шахматку на texture unit 2
+gl.activeTexture(gl.TEXTURE0 + 2);
+gl.bindTexture(gl.TEXTURE_2D, checkerTexture);
+// Говорим шейдеру использовать texture unit 2 для u_texture
+gl.uniform1i(u_textureLoc, 2);
+```
+
+И наконец рисуем:
+
+```js
+// Рисуем геометрию
+gl.drawElements(gl.TRIANGLES, 6 * 6, gl.UNSIGNED_SHORT, 0);
+```
+
+И получаем куб с текстурой, используя только 8 позиций и
+4 текстурные координаты:
+
+{{{example url="../webgl-pulling-vertices.html"}}}
+
+Несколько вещей для заметки. Код ленивый и использует 1D
+текстуры для позиций и текстурных координат.
+Текстуры могут быть только такой ширины. [Насколько широкими - зависит от машины](https://web3dsurvey.com/webgl/parameters/MAX_TEXTURE_SIZE), что можно запросить с помощью:
+
+```js
+const maxSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
+```
+
+Если бы мы хотели обработать больше данных, чем это, нам нужно было бы
+выбрать какой-то размер текстуры, который подходит нашим данным, и распределить
+данные по нескольким строкам, возможно
+дополняя последнюю строку, чтобы получился прямоугольник.
+
+Ещё одна вещь, которую мы делаем здесь - используем 2 текстуры,
+одну для позиций, одну для текстурных координат.
+Нет причин, по которым мы не могли бы положить оба данных в
+ту же текстуру либо чередуя:
+
+ pos,uv,pos,uv,pos,uv...
+
+либо в разных местах в текстуре:
+
+ pos,pos,pos,...
+ uv, uv, uv,...
+
+Нам просто пришлось бы изменить математику в вершинном шейдере,
+которая вычисляет, как их извлекать из текстуры.
+
+Возникает вопрос: стоит ли делать такие вещи?
+Ответ: "зависит от обстоятельств". В зависимости от GPU это
+может быть медленнее, чем более традиционный способ.
+
+Цель этой статьи была в том, чтобы ещё раз указать,
+что WebGL не заботится о том, как вы устанавливаете `gl_Position` с
+координатами clip space, и не заботится о том, как вы
+выводите цвет. Ему важно только, чтобы вы их установили.
+Текстуры - это действительно просто 2D массивы данных с произвольным доступом.
+
+Когда у вас есть проблема, которую вы хотите решить в WebGL,
+помните, что WebGL просто запускает шейдеры, и эти шейдеры
+имеют доступ к данным через uniform'ы (глобальные переменные),
+атрибуты (данные, которые приходят за итерацию вершинного шейдера),
+и текстуры (2D массивы с произвольным доступом). Не позволяйте
+традиционным способам использования WebGL помешать вам
+увидеть настоящую гибкость, которая там есть.
+
+
+
Почему это называется Vertex Pulling?
+
Я на самом деле слышал этот термин только недавно (июль 2019),
+хотя использовал технику раньше. Он происходит из
+статьи OpenGL Insights "Programmable Vertex Pulling" от Daniel Rakos.
+
+
Это называется vertex *pulling* (вытягивание вершин), поскольку это вершинный шейдер
+решает, какие вершинные данные читать, в отличие от традиционного способа, где
+вершинные данные поставляются автоматически через атрибуты. По сути
+вершинный шейдер *вытягивает* данные из памяти.
+
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-accessing-textures-by-pixel-coordinate-in-webgl2.md b/webgl/lessons/ru/webgl-qna-accessing-textures-by-pixel-coordinate-in-webgl2.md
deleted file mode 100644
index 5909ea41a..000000000
--- a/webgl/lessons/ru/webgl-qna-accessing-textures-by-pixel-coordinate-in-webgl2.md
+++ /dev/null
@@ -1,46 +0,0 @@
-Title: Доступ к текстурам по координатам пикселей в WebGL2
-Description: Доступ к текстурам по координатам пикселей в WebGL2
-TOC: Доступ к текстурам по координатам пикселей в WebGL2
-
-## Вопрос:
-
-Из https://webgl2fundamentals.org/webgl/lessons/webgl-image-processing.html
-
-> WebGL2 добавляет возможность читать текстуру, используя координаты пикселей. Какой способ лучше - решать вам. Я считаю, что более распространено использование координат текстуры, чем координат пикселей.
-
-Нигде больше это не упоминается, кроме как передачи uniform с размерами текстуры в пикселях и вычисления оттуда, есть ли способ получить доступ к этим координатам пикселей без вычислений, как предполагается здесь?
-
-
-
-## Ответ:
-
-Вы можете читать отдельные пиксели/тексели из текстуры в WebGL2 с помощью `texelFetch`:
-
- vec4 color = texelFetch(someUniformSampler, ivec2(pixelX, pixelY), intMipLevel);
-
-Например, вычислить средний цвет текстуры, читая каждый пиксель:
-
-{{{example url="../webgl-qna-accessing-textures-by-pixel-coordinate-in-webgl2-example-1.html"}}}
-
-примечания: поскольку холст RGBA8 может получить только целочисленный результат. Можно изменить на какой-то формат с плавающей точкой, но это усложнит пример, который не о рендеринге, а о `texelFetch`.
-
-Конечно, просто изменив данные с R8 на RGBA8, мы можем делать 4 массива одновременно, если мы чередуем значения:
-
-{{{example url="../webgl-qna-accessing-textures-by-pixel-coordinate-in-webgl2-example-2.html"}}}
-
-Чтобы сделать больше, нужно придумать какой-то способ организовать данные и использовать вход в фрагментный шейдер, чтобы понять, где находятся данные. Например, мы снова чередуем данные, 5 массивов, так что данные идут 0,1,2,3,4,0,1,2,3,4,0,1,2,3,4.
-
-Давайте вернемся к R8 и сделаем 5 отдельных массивов. Нам нужно нарисовать 5 пикселей. Мы можем сказать, какой пиксель рисуется, посмотрев на `gl_FragCoord`. Мы можем использовать это для смещения, какие пиксели мы смотрим, и передать, сколько пропустить.
-
-{{{example url="../webgl-qna-accessing-textures-by-pixel-coordinate-in-webgl2-example-3.html"}}}
-
-
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
bogersja
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-apply-a-displacement-map-and-specular-map.md b/webgl/lessons/ru/webgl-qna-apply-a-displacement-map-and-specular-map.md
deleted file mode 100644
index d9a40039a..000000000
--- a/webgl/lessons/ru/webgl-qna-apply-a-displacement-map-and-specular-map.md
+++ /dev/null
@@ -1,326 +0,0 @@
-Title: Применение карты смещения и карты бликов
-Description: Применение карты смещения и карты бликов
-TOC: Применение карты смещения и карты бликов
-
-## Вопрос:
-
-Я пытаюсь применить как карту смещения, так и карту бликов для Земли, и только карту смещения для Луны.
-
-Я мог бы преобразовать карту высот в карту нормалей, но если я использую ту же карту высот для применения карты смещения, это не работает так, как я ожидал.
-
-Вот пример изображения:
-
-[![Пример 1][1]][1]
-
-как вы можете видеть, неровности вокруг Земли и Луны, но нет реальных различий в высоте.
-
-Если я применяю карту бликов к Земле, Земля становится такой:
-
-[![Пример 2][2]][2]
-
-Я хочу, чтобы только океан Земли блестел, но мой код превращает Землю в полностью черную, я вижу только некоторые белые точки на Земле...
-
-Эти текстуры взяты с этого [сайта][3]
-
-Вот мой код вершинного шейдера и фрагментного шейдера:
-
- "use strict";
- const loc_aPosition = 3;
- const loc_aNormal = 5;
- const loc_aTexture = 7;
- const VSHADER_SOURCE =
- `#version 300 es
- layout(location=${loc_aPosition}) in vec4 aPosition;
- layout(location=${loc_aNormal}) in vec4 aNormal;
- layout(location=${loc_aTexture}) in vec2 aTexCoord;
-
-
- uniform mat4 uMvpMatrix;
- uniform mat4 uModelMatrix; // Матрица модели
- uniform mat4 uNormalMatrix; // Матрица преобразования нормали
-
- uniform sampler2D earth_disp;
- uniform sampler2D moon_disp;
-
- //uniform float earth_dispScale;
- //uniform float moon_dispScale;
-
- //uniform float earth_dispBias;
- //uniform float moon_dispBias;
-
- uniform bool uEarth;
- uniform bool uMoon;
-
-
- out vec2 vTexCoord;
- out vec3 vNormal;
- out vec3 vPosition;
-
-
- void main()
- {
-
- float disp;
-
- if(uEarth)
- disp = texture(earth_disp, aTexCoord).r; //Извлечение цветовой информации из изображения
- else if(uMoon)
- disp = texture(moon_disp, aTexCoord).r; //Извлечение цветовой информации из изображения
-
- vec4 displace = aPosition;
-
- float displaceFactor = 2.0;
- float displaceBias = 0.5;
-
- if(uEarth || uMoon) //Использование карты смещения
- {
- displace += (displaceFactor * disp - displaceBias) * aNormal;
- gl_Position = uMvpMatrix * displace;
- }
- else //Не используем карту смещения
- gl_Position = uMvpMatrix * aPosition;
-
- // Вычисляем позицию вершины в мировой системе координат
- vPosition = vec3(uModelMatrix * aPosition);
-
- vNormal = normalize(vec3(uNormalMatrix * aNormal));
- vTexCoord = aTexCoord;
-
- }`;
-
- // Программа фрагментного шейдера
- const FSHADER_SOURCE =
- `#version 300 es
- precision mediump float;
-
- uniform vec3 uLightColor; // Цвет света
- uniform vec3 uLightPosition; // Позиция источника света
- uniform vec3 uAmbientLight; // Цвет окружающего света
-
- uniform sampler2D sun_color;
- uniform sampler2D earth_color;
- uniform sampler2D moon_color;
-
- uniform sampler2D earth_bump;
- uniform sampler2D moon_bump;
-
- uniform sampler2D specularMap;
-
-
- in vec3 vNormal;
- in vec3 vPosition;
- in vec2 vTexCoord;
- out vec4 fColor;
-
- uniform bool uIsSun;
- uniform bool uIsEarth;
- uniform bool uIsMoon;
-
-
-
- vec2 dHdxy_fwd(sampler2D bumpMap, vec2 UV, float bumpScale)
- {
- vec2 dSTdx = dFdx( UV );
- vec2 dSTdy = dFdy( UV );
- float Hll = bumpScale * texture( bumpMap, UV ).x;
- float dBx = bumpScale * texture( bumpMap, UV + dSTdx ).x - Hll;
- float dBy = bumpScale * texture( bumpMap, UV + dSTdy ).x - Hll;
- return vec2( dBx, dBy );
- }
-
- vec3 pertubNormalArb(vec3 surf_pos, vec3 surf_norm, vec2 dHdxy)
- {
- vec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );
- vec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );
- vec3 vN = surf_norm; // нормализованная
- vec3 R1 = cross( vSigmaY, vN );
- vec3 R2 = cross( vN, vSigmaX );
- float fDet = dot( vSigmaX, R1 );
- fDet *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );
- vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );
- return normalize( abs( fDet ) * surf_norm - vGrad );
- }
-
-
-
- void main()
- {
- vec2 dHdxy;
- vec3 bumpNormal;
- float bumpness = 1.0;
- if(uIsSun)
- fColor = texture(sun_color, vTexCoord);
- else if(uIsEarth)
- {
- fColor = texture(earth_color, vTexCoord);
- dHdxy = dHdxy_fwd(earth_bump, vTexCoord, bumpness);
- }
- else if(uIsMoon)
- {
- fColor = texture(moon_color, vTexCoord);
- dHdxy = dHdxy_fwd(moon_bump, vTexCoord, bumpness);
- }
-
-
-
- // Нормализуем нормаль, потому что она интерполируется и больше не имеет длину 1.0
- vec3 normal = normalize(vNormal);
-
-
- // Вычисляем направление света и делаем его длину равной 1.
- vec3 lightDirection = normalize(uLightPosition - vPosition);
-
-
-
- // Скалярное произведение направления света и ориентации поверхности (нормали)
- float nDotL;
- if(uIsSun)
- nDotL = 1.0;
- else
- nDotL = max(dot(lightDirection, normal), 0.0);
-
-
-
- // Вычисляем финальный цвет из диффузного отражения и окружающего отражения
- vec3 diffuse = uLightColor * fColor.rgb * nDotL;
- vec3 ambient = uAmbientLight * fColor.rgb;
- float specularFactor = texture(specularMap, vTexCoord).r; //Извлечение цветовой информации из изображения
-
-
-
-
- vec3 diffuseBump;
- if(uIsEarth || uIsMoon)
- {
- bumpNormal = pertubNormalArb(vPosition, normal, dHdxy);
- diffuseBump = min(diffuse + dot(bumpNormal, lightDirection), 1.1);
- }
-
- vec3 specular = vec3(0.0);
- float shiness = 12.0;
- vec3 lightSpecular = vec3(1.0);
-
- if(uIsEarth && nDotL > 0.0)
- {
- vec3 v = normalize(-vPosition); // Позиция глаза
- vec3 r = reflect(-lightDirection, bumpNormal); // Отражение от поверхности
- specular = lightSpecular * specularFactor * pow(dot(r, v), shiness);
- }
-
- //Обновляем финальный цвет
- if(uIsEarth)
- fColor = vec4( (diffuse * diffuseBump * specular) + ambient, fColor.a); // Блики
- else if(uIsMoon)
- fColor = vec4( (diffuse * diffuseBump) + ambient, fColor.a);
- else if(uIsSun)
- fColor = vec4(diffuse + ambient, fColor.a);
- }`;
-
-
-Можете ли вы сказать мне, где мне нужно проверить?
-
- [1]: https://i.stack.imgur.com/eJgLg.png
- [2]: https://i.stack.imgur.com/zRSZu.png
- [3]: http://planetpixelemporium.com/earth.html
-
-## Ответ:
-
-Если бы это был я, я бы сначала упростил шейдер до самой простой вещи и посмотрел, получаю ли я то, что хочу. Вы хотите блики, так получаете ли вы блики только с расчетами бликов в ваших шейдерах?
-
-Обрезка ваших шейдеров до простого рисования плоского освещения по Фонгу не дала правильных результатов.
-
-Эта строка:
-
-```
-fColor = vec4( (diffuse * specular) + ambient, fColor.a);
-```
-
-должна была быть:
-
-```
-fColor = vec4( (diffuse + specular) + ambient, fColor.a);
-```
-
-Вы добавляете блики, а не умножаете на них.
-
-{{{example url="../webgl-qna-apply-a-displacement-map-and-specular-map-example-1.html"}}}
-
-Теперь мы можем добавить карту бликов:
-
-{{{example url="../webgl-qna-apply-a-displacement-map-and-specular-map-example-2.html"}}}
-
-Затем вам, возможно, не стоит использовать много булевых условий в вашем шейдере. Либо создайте разные шейдеры, либо найдите способ сделать это без булевых значений. Так, например, нам не нужны:
-
-```
-uniform sampler2D earth_disp;
-uniform sampler2D moon_disp;
-
-uniform sampler2D sun_color;
-uniform sampler2D earth_color;
-uniform sampler2D moon_color;
-
-uniform sampler2D earth_bump;
-uniform sampler2D moon_bump;
-
-uniform bool uIsSun;
-uniform bool uIsEarth;
-uniform bool uIsMoon;
-```
-
-мы можем просто иметь:
-
-```
-uniform sampler2D displacementMap;
-uniform sampler2D surfaceColor;
-uniform sampler2D bumpMap;
-```
-
-Затем мы можем установить `displacementMap` и `bumpMap` на текстуру одного пикселя 0,0,0,0, и не будет ни смещения, ни неровностей.
-
-Что касается разного освещения для солнца, учитывая, что солнце не использует ни карту неровностей, ни карту смещения, ни даже освещение вообще, возможно, было бы лучше использовать другой шейдер, но мы также можем просто добавить значение `maxDot` так:
-
-```
-uniform float maxDot;
-
-...
-
- nDotL = max(dot(lightDirection, normal), maxDot)
-```
-
-Если `maxDot` равен нулю, мы получим нормальное скалярное произведение. Если `maxDot` равен единице, мы не получим освещения.
-
-{{{example url="../webgl-qna-apply-a-displacement-map-and-specular-map-example-3.html"}}}
-
-Что касается смещения, смещение работает только на вершинах, поэтому вам нужно много вершин в вашей сфере, чтобы увидеть любое смещение.
-
-Также была ошибка, связанная со смещением. Вы передаете нормали как vec4, и эта строка:
-
- displace += (displaceFactor * disp - displaceBias) * aNormal;
-
-В итоге добавляет смещение vec4. Другими словами, допустим, вы начали с `a_Position` равным `vec4(1,0,0,1)`, что было бы на левой стороне сферы. `aNormal`, потому что вы объявили его как `vec4`, вероятно, тоже `vec4(1,0,0,1)`. Предполагая, что вы фактически передаете данные vec3 нормали через атрибуты из вашего буфера, значение по умолчанию для W равно 1. Допустим, `disp` равен 1, `displaceFactor` равен 2, а `displaceBias` равен 0.5, что у вас было. Вы получаете:
-
- displace = vec4(1,0,0,1) + (2 * 1 + 0.5) * vec4(1,0,0,1)
- displace = vec4(1,0,0,1) + (1.5) * vec4(1,0,0,1)
- displace = vec4(1,0,0,1) + vec4(1.5,0,0,1.5)
- displace = vec4(2.5,0,0,2.5)
-
-Но вы не хотите, чтобы W был 2.5. Одно исправление - просто использовать xyz часть нормали:
-
- displace.xyz += (displaceFactor * disp - displaceBias) * aNormal.xyz;
-
-Более нормальное исправление - объявить атрибут нормали только как vec3:
-
- in vec3 aNormal;
-
- displace.xyz += (displaceFactor * disp - displaceBias) * aNormal;
-
-В моем примере выше сферы имеют только радиус = 1, поэтому мы хотим только немного скорректировать это смещение. Я установил `displaceFactor` равным 0.1 и `displaceBias` равным 0.
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
ZeroFive005
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-can-anyone-explain-what-this-glsl-fragment-shader-is-doing-.md b/webgl/lessons/ru/webgl-qna-can-anyone-explain-what-this-glsl-fragment-shader-is-doing-.md
deleted file mode 100644
index 804af4c88..000000000
--- a/webgl/lessons/ru/webgl-qna-can-anyone-explain-what-this-glsl-fragment-shader-is-doing-.md
+++ /dev/null
@@ -1,154 +0,0 @@
-Title: Может ли кто-нибудь объяснить, что делает этот GLSL фрагментный шейдер?
-Description: Может ли кто-нибудь объяснить, что делает этот GLSL фрагментный шейдер?
-TOC: Can anyone explain what this GLSL fragment shader is doing?
-
-## Вопрос:
-
-Я понимаю, что это вопрос, ориентированный на математику, но... если вы посмотрите на [эту веб-страницу](https://threejs.org/examples/?q=shader#webgl_shader). (и у вас есть хорошая видеокарта)
-
-Если вы посмотрите на исходный код, вы заметите страшно выглядящий фрагментный шейдер.
-
-Я не ищу подробного объяснения, но идею о том, что происходит, или источник информации о том, что именно происходит здесь.. Я не ищу руководство по GLSL, но информацию о математике. Я понимаю, что это может быть лучше подходит для сайта Math StackExchange, но подумал, что попробую здесь сначала...
-
-
-
-## Ответ:
-
-[Monjori](http://www.pouet.net/prod.php?which=52761) из демо-сцены.
-
-Простой ответ - он использует формулу для генерации паттерна. WebGL будет вызывать эту функцию один раз для каждого пикселя на экране. Единственные вещи, которые будут меняться, это time и gl_FragCoord, который является местоположением пикселя, который рисуется.
-
-Давайте разберем это немного
-
-
- // это разрешение окна
- uniform vec2 resolution;
-
- // это счетчик в секундах.
- uniform float time;
-
- void main() {
- // gl_FragCoord - это позиция пикселя, который рисуется
- // поэтому этот код делает p значением, которое идет от -1 до +1
- // x и y
- vec2 p = -1.0 + 2.0 * gl_FragCoord.xy / resolution.xy;
-
- // a = время ускоренное в 40 раз
- float a = time*40.0;
-
- // объявляем кучу переменных.
- float d,e,f,g=1.0/40.0,h,i,r,q;
-
- // e идет от 0 до 400 по экрану
- e=400.0*(p.x*0.5+0.5);
-
- // f идет от 0 до 400 вниз по экрану
- f=400.0*(p.y*0.5+0.5);
-
- // i идет от 200 + или - 20 на основе
- // sin от e * 1/40 + замедленное время / 150
- // или другими словами замедлить еще больше.
- // e * 1/40 означает e идет от 0 до 1
- i=200.0+sin(e*g+a/150.0)*20.0;
-
- // d это 200 + или - 18.0 + или - 7
- // первый +/- это cos от 0.0 до 0.5 вниз по экрану
- // второй +/- это cos от 0.0 до 1.0 по экрану
- d=200.0+cos(f*g/2.0)*18.0+cos(e*g)*7.0;
-
- // Я останавливаюсь здесь. Вы, вероятно, можете разобрать остальное
- // смотрите ответ
- r=sqrt(pow(i-e,2.0)+pow(d-f,2.0));
- q=f/r;
- e=(r*cos(q))-a/2.0;f=(r*sin(q))-a/2.0;
- d=sin(e*g)*176.0+sin(e*g)*164.0+r;
- h=((f+d)+a/2.0)*g;
- i=cos(h+r*p.x/1.3)*(e+e+a)+cos(q*g*6.0)*(r+h/3.0);
- h=sin(f*g)*144.0-sin(e*g)*212.0*p.x;
- h=(h+(f-e)*q+sin(r-(a+h)/7.0)*10.0+i/4.0)*g;
- i+=cos(h*2.3*sin(a/350.0-q))*184.0*sin(q-(r*4.3+a/12.0)*g)+tan(r*g+h)*184.0*cos(r*g+h);
- i=mod(i/5.6,256.0)/64.0;
- if(i<0.0) i+=4.0;
- if(i>=2.0) i=4.0-i;
- d=r/350.0;
- d+=sin(d*d*8.0)*0.52;
- f=(sin(a*g)+1.0)/2.0;
- gl_FragColor=vec4(vec3(f*i/1.6,i/2.0+d/13.0,i)*d*p.x+vec3(i/1.3+d/8.0,i/2.0+d/18.0,i)*d*(1.0-p.x),1.0);
- }
-
-Одна из вещей, которую хорошо попробовать, чтобы увидеть, что происходит, это вставить ранние выходы в шейдер. Сначала вы можете увидеть шейдер здесь
-
-http://glsl.heroku.com/e#1579.0
-
-или
-
-https://www.shadertoy.com/view/lsfyRS
-
-Если мы перейдем к строке 11
-
- e=400.0*(p.x*0.5+0.5);
-
-и вставим сразу после нее что-то вроде этого
-
- e=400.0*(p.x*0.5+0.5);
- gl_FragColor = vec4(e / 400.0, 0, 0, 1);
- return;
-
-Пока мы конвертируем значение в что-то от 0 до 1, мы можем увидеть результат
-
-например, спускаясь к строке 14
-
- d=200.0+cos(f*g/2.0)*18.0+cos(e*g)*7.0;
-
-Поскольку мы знаем, что это идет от 200 +/- 18 +/- 7, это 175 + 225, поэтому конвертируем это в 0 до 1 с
-
- d=200.0+cos(f*g/2.0)*18.0+cos(e*g)*7.0;
- float tmp = (d - 175.0) / 50.0;
- gl_FragColor = vec4(tmp, 0, 0, 1);
- return;
-
-даст вам некоторое представление о том, что он делает.
-
-Я уверен, что вы можете разобрать остальное.
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 3.0 от
-
Alex
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-creating-a-smudge-liquify-effect.md b/webgl/lessons/ru/webgl-qna-creating-a-smudge-liquify-effect.md
deleted file mode 100644
index aa70eafdc..000000000
--- a/webgl/lessons/ru/webgl-qna-creating-a-smudge-liquify-effect.md
+++ /dev/null
@@ -1,73 +0,0 @@
-Title: Создание эффекта смазывания/жидкости (smudge/liquify)
-Description: Создание эффекта смазывания/жидкости (smudge/liquify)
-TOC: Создание эффекта смазывания/жидкости (smudge/liquify)
-
-## Вопрос:
-
-Я пытаюсь найти информацию или примеры, которые можно использовать для создания эффекта смазывания/жидкости, который постепенно анимируется обратно к исходному состоянию.
-
-Сначала я рассматривал вариант с использованием three.js или pixi.js для рендеринга текста, а затем с помощью событий мыши и ray casting вытягивать меш из позиции. Ближе всего, что я нашёл — это:
-
-https://codepen.io/shshaw/pen/qqVgbg
-
- let renderer = PIXI.autoDetectRenderer(window.innerWidth,
- window.innerHeight, { transparent: true });
-
-Думаю, в идеале я бы рендерил текст как изображение, а затем применял бы эффект смазывания к пикселям, которые медленно возвращались бы к исходному состоянию. Похоже на это:
-
-http://www.duhaihang.com/#/work/
-
-Думаю, мне нужен кастомный GLSL-шейдер и какой-то буфер для хранения исходного и текущего состояния пикселей изображения.
-
-Любая помощь или направление будет очень полезно.
-
-## Ответ:
-
-Оба варианта довольно прямолинейны.
-
-Первый, как вы и сказали, — делаете меш (сетку) вершин, рисующую плоскость. Накладываете текстуру на плоскость, при перемещении мыши добавляете смещение каждой вершине, которую касается мышь. Со временем сбрасываете смещение обратно к 0 (т.е. 0 смещения).
-
-Вот пример: здесь смещается только одна вершина на случайную величину, а не что-то более предсказуемое. В конце я просто сохраняю время, к которому смещение должно исчезнуть, затем в шейдере делаю простой линейный lerp (можно сделать более сложный для эффекта bounce и т.д.). Так почти всё происходит в шейдере.
-
-{{{example url="../webgl-qna-creating-a-smudge-liquify-effect-example-1.html"}}}
-
-Во втором варианте вместо смещения вершин делаете текстуру смещений, со временем сбрасываете это смещение к 0.
-
-Пример затухания можно посмотреть [здесь](https://stackoverflow.com/a/38407507/128511). Если взять этот пример и вместо рисования случайного квадрата рисовать под мышью, а затем использовать эту текстуру как смещение для основного изображения. Под смещением я имею в виду, что обычно вы берёте текстуру во фрагментном шейдере так:
-
- vec4 color = texture2D(someTexture, someTextureCoords);
-
-Вместо этого вы хотите сместить координаты текстуры с помощью displacement, примерно так:
-
- // если текстура смещений того же размера, что и основная, можно использовать те же координаты
-
- // сначала получаем смещение и переводим в диапазон -1 <-> 1
- // используем только R и G каналы, которые станут U и V смещениями для координат текстуры
- vec2 displacement = texture2D(displacementTexture, someTextureCoords).rg * 2. - 1.;
-
- vec2 uv = someTextureCoords + displacement * displacementRange;
- vec4 color = texture2d(someTexture, uv);
-
-Вот пример выше, используемый для смещения:
-
-{{{example url="../webgl-qna-creating-a-smudge-liquify-effect-example-2.html"}}}
-
-Осталось только рисовать под мышью, а не в случайном месте:
-
-{{{example url="../webgl-qna-creating-a-smudge-liquify-effect-example-3.html"}}}
-
-Точный эффект из вашего второго примера, похоже, использует какое-то шумовое преобразование displacement. Можно воспользоваться [WebGL Inspector](https://benvanik.github.io/WebGL-Inspector/) или [Shader Editor](https://github.com/spite/ShaderEditorExtension), чтобы посмотреть, что происходит в шейдерах.
-
-[Вот ещё пример](https://codepen.io/greggman/pen/bgXgvr), который создаёт текстуру смещений, смещающую больше к центру, чем к краю.
-
-Пояснение: я не смотрел детали того, как работают ваши примеры, я лишь предполагаю, что они делают что-то *похожее*. Лучший способ узнать, что они реально делают — посмотреть их код и воспользоваться инструментами выше, чтобы заглянуть внутрь. Может быть, они используют не прямое смещение, а, например, нормали как displacement. Может быть, вместо рисования сплошным цветом (2-й и 3-й примеры) или текстурой (4-й пример), они используют процедурный паттерн или экранные координаты для повторяющейся текстуры. Может быть, текстура смещений — отдельная текстура, а есть ещё "mix mask", которую рисуют белым и затухают в чёрный, чтобы определить, сколько displacement применять. В WebGL бесконечно много способов реализовать подобные эффекты.
-
-
-
-
-
Вопрос и цитируемые части взяты по лицензии CC BY-SA 3.0 у
-
plexus
- с сайта
-
stackoverflow
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-fps-like-camera-movement-with-basic-matrix-transformations.md b/webgl/lessons/ru/webgl-qna-fps-like-camera-movement-with-basic-matrix-transformations.md
deleted file mode 100644
index ce95d9d26..000000000
--- a/webgl/lessons/ru/webgl-qna-fps-like-camera-movement-with-basic-matrix-transformations.md
+++ /dev/null
@@ -1,38 +0,0 @@
-Title: FPS-подобное движение камеры с базовыми матричными трансформациями
-Description: FPS-подобное движение камеры с базовыми матричными трансформациями
-TOC: FPS-подобное движение камеры с базовыми матричными трансформациями
-
-## Вопрос:
-
-У меня простая сцена в WebGL, где я храню каждую трансформацию (для камеры и моделей) в одной модели/видовой матрице и устанавливаю их, вращая и перемещая эту матрицу.
-
-Я хочу иметь возможность вращать камеру вокруг и когда я "двигаюсь вперёд", двигаться в направлении, куда указывает камера.
-
-Пока что я изменил [этот][1] код на это:
-
- mat4.identity(mvMatrix);
- mat4.rotateX(mvMatrix, degToRad(elev), mvMatrix);
- mat4.rotateY(mvMatrix, degToRad(ang), mvMatrix);
- mat4.rotateZ(mvMatrix, degToRad(-roll), mvMatrix);
- mat4.translate(mvMatrix, [-px, -py, -pz], mvMatrix);
-поскольку это не работало как было, и это вроде работает, пока вы не делаете экстремальное вращение (больше 90 градусов).
-
-Это не критично для того, что я делаю, но я хочу знать. Это лучшее, что я могу получить, не отходя от расчёта ориентации камеры таким образом?
-
- [1]: https://stackoverflow.com/questions/18463868/webgl-translation-after-rotation-of-the-camera-as-an-fps
-
-## Ответ:
-
-WebGL камеры обычно указывают по оси -Z, так что чтобы двигаться в направлении, куда смотрит камера, вы просто добавляете Z-ось камеры (элементы 8, 9, 10) к позиции камеры, умноженной на некоторую скорость.
-
-{{{example url="../webgl-qna-fps-like-camera-movement-with-basic-matrix-transformations-example-1.html"}}}
-
-
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-get-audio-data-into-a-shader.md b/webgl/lessons/ru/webgl-qna-how-to-get-audio-data-into-a-shader.md
deleted file mode 100644
index e06cea7de..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-get-audio-data-into-a-shader.md
+++ /dev/null
@@ -1,45 +0,0 @@
-Title: Как получить аудио данные в шейдер
-Description: Как получить аудио данные в шейдер
-TOC: Как получить аудио данные в шейдер
-
-## Вопрос:
-
-Как я могу добавить поддержку аудио визуализации к этому классу, я хотел бы добавить объект Audio() как вход в GLSL фрагментный шейдер. Пример этого https://www.shadertoy.com/view/Mlj3WV. Я знаю, что такие вещи можно делать в Canvas 2d с анализом формы волны, но этот метод opengl намного более плавный.
-
-
-```
-/* Код из https://raw.githubusercontent.com/webciter/GLSLFragmentShader/1.0.0/GLSLFragmentShader.js */
-
-/* функция рендеринга */
-
-/* установить uniform переменные для шейдера */
- gl.uniform1f( currentProgram.uniformsCache[ 'time' ], parameters.time / 1000 );
-gl.uniform2f( currentProgram.uniformsCache[ 'mouse' ], parameters.mouseX, parameters.mouseY );
-
-/* я хотел бы что-то вроде этого */
-gl.uniform2f( currentProgram.uniformsCache[ 'fft' ], waveformData );
-
-
-```
-
-Шейдер в примере ShaderToy принимает float как fft, но это просто обновляет всю строку полосок, а не отдельные значения полосок. Я хотел бы манипуляции в реальном времени всех полосок.
-
-Я искал в MDN, но не понимаю, как это включить, я также смотрел исходный код shadertoy.com, но не могу понять, как они достигли этого.
-
-## Ответ:
-
-ShaderToy не предоставляет FFT как float. Он предоставляет данные FFT как текстуру
-
-
-{{{example url="../webgl-qna-how-to-get-audio-data-into-a-shader-example-1.html"}}}
-
-
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
David Clews
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-get-code-completion-for-webgl-in-visual-studio-code.md b/webgl/lessons/ru/webgl-qna-how-to-get-code-completion-for-webgl-in-visual-studio-code.md
deleted file mode 100644
index 200d862b9..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-get-code-completion-for-webgl-in-visual-studio-code.md
+++ /dev/null
@@ -1,83 +0,0 @@
-Title: Как получить автодополнение кода для WebGL в Visual Studio Code
-Description: Как получить автодополнение кода для WebGL в Visual Studio Code
-TOC: Как получить автодополнение кода для WebGL в Visual Studio Code
-
-## Вопрос:
-
-У меня есть школьный проект, и мне нужно использовать WEBGL. Но довольно сложно писать весь код без автодополнения. Я не нашел подходящего расширения. У вас есть идеи?
-
-## Ответ:
-
-Для того чтобы Visual Studio Code давал вам автодополнение, ему нужно знать типы переменных.
-
-Так, например, если у вас есть это
-
-```
-const gl = init();
-```
-
-VSCode не имеет представления о том, какой тип у переменной `gl`, поэтому он не может автодополнять. Но вы можете сказать ему тип, добавив JSDOC стиль комментария выше, как это
-
-```
-/** @type {WebGLRenderingContext} */
-const gl = init();
-```
-
-Теперь он будет автодополнять
-
-[![enter image description here][1]][1]
-
-
-То же самое верно для HTML элементов. Если вы делаете это
-
-```
-const canvas = document.querySelector('#mycanvas');
-```
-
-VSCode не имеет представления о том, какой это тип элемента, но вы можете сказать ему
-
-```
-/** @type {HTMLCanvasElement} */
-const canvas = document.querySelector('#mycanvas');
-```
-
-Теперь он будет знать, что это `HTMLCanvasElement`
-
-[![enter image description here][2]][2]
-
-И, поскольку он знает, что это `HTMLCanvasElement`, он знает, что `.getContext('webgl')` возвращает `WebGLRenderingContext`, поэтому он автоматически предложит автодополнение для контекста тоже
-
-[![enter image description here][3]][3]
-
-Обратите внимание, что если вы передаете canvas в какую-то функцию, то снова VSCode не имеет представления о том, что возвращает эта функция. Другими словами
-
-```
-/** @type {HTMLCanvasElement} */
-const canvas = document.querySelector('#mycanvas');
-const gl = someLibraryInitWebGL(canvas);
-```
-
-Вы больше не получите автодополнение, поскольку VSCode не имеет представления о том, что возвращает `someLibraryInitWebGL`, поэтому следуйте правилу сверху и скажите ему.
-
-```
-/** @type {HTMLCanvasElement} */
-const canvas = document.querySelector('#mycanvas');
-
-/** @type {WebGLRenderingContext} */
-const gl = someLibraryInitWebGL(canvas);
-```
-
-Вы можете увидеть другие JSDOC аннотации [здесь](https://jsdoc.app/), если хотите документировать свои собственные функции, например, их аргументы и типы возврата.
-
- [1]: https://i.stack.imgur.com/8mvFM.png
- [2]: https://i.stack.imgur.com/oArWf.png
- [3]: https://i.stack.imgur.com/7zR4q.png
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
Nikola Kovač
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-get-pixelize-effect-in-webgl-.md b/webgl/lessons/ru/webgl-qna-how-to-get-pixelize-effect-in-webgl-.md
deleted file mode 100644
index 19663808e..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-get-pixelize-effect-in-webgl-.md
+++ /dev/null
@@ -1,55 +0,0 @@
-Title: Как получить эффект пикселизации в WebGL?
-Description: Как получить эффект пикселизации в WebGL?
-TOC: Как получить эффект пикселизации в WebGL?
-
-## Вопрос:
-
-Я хочу симулировать эффект старого ПК с низким разрешением, как Atari или Commodore, в WebGL. Есть ли способ нарисовать изображение, а затем как-то сделать пиксели больше?
-
-Я новичок в WebGL, так как мне начать делать этот эффект?
-
-Я нашёл [это](https://threejs.org/examples/#webgl_postprocessing_nodes), там есть эффект мозаики, но он использует three.js, а я хочу сделать это без фреймворков.
-
-## Ответ:
-
-Есть много способов сделать это. Самый простой — просто рендерить в низкоразрешающую текстуру, прикрепив её к framebuffer, а затем рендерить эту текстуру на canvas с фильтрацией текстуры, установленной на `NEAREST`.
-
-Вот пример. Он использует [TWGL](http://twgljs.org), который не является фреймворком, просто помощник, чтобы сделать WebGL менее многословным. Смотрите комментарии (и [документацию](http://twgljs.org/docs/)), если хотите перевести это в многословный сырой WebGL.
-
-Если вы новичок в WebGL, [я бы предложил начать отсюда](http://webglfundamentals.org):
-
-{{{example url="../webgl-qna-how-to-get-pixelize-effect-in-webgl--example-1.html"}}}
-
-Также распространено рендерить в текстуру (как выше), но текстуру более высокого разрешения, а затем фильтровать её вниз, используя шейдеры, мипы и/или линейную фильтрацию. Преимущество в том, что вы получите больше сглаживания:
-
-{{{example url="../webgl-qna-how-to-get-pixelize-effect-in-webgl--example-2.html"}}}
-
----
-
-# обновление
-
-В 2020 году, возможно, самое простое, что вы можете сделать — просто сделать canvas с разрешением, которое вы хотите, например 32x32, и установить его CSS размер больше, а затем использовать настройку CSS `image-rendering: pixelated`, чтобы сказать браузеру не сглаживать его при масштабировании изображения:
-
-```
-
-```
-
-{{{example url="../webgl-qna-how-to-get-pixelize-effect-in-webgl--example-3.html"}}}
-
-
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-implement-zoom-from-mouse-in-2d-webgl.md b/webgl/lessons/ru/webgl-qna-how-to-implement-zoom-from-mouse-in-2d-webgl.md
deleted file mode 100644
index 9f1b8e5b8..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-implement-zoom-from-mouse-in-2d-webgl.md
+++ /dev/null
@@ -1,142 +0,0 @@
-Title: Как реализовать зум от мыши в 2D WebGL
-Description: Как реализовать зум от мыши в 2D WebGL
-TOC: Как реализовать зум от мыши в 2D WebGL
-
-## Вопрос:
-
-Я сейчас делаю 2D-рисовалку на WebGL. Хочу реализовать зум к точке под курсором мыши, как в [этом примере](https://stackoverflow.com/a/53193433/2594567). Но не могу понять, как применить решение из того ответа в своём случае.
-
-Я сделал базовый зум через масштабирование матрицы камеры. Но он увеличивает к левому верхнему углу canvas, потому что это начало координат (0,0), заданное проекцией (насколько я понимаю).
-
-Базовый пан и зум реализованы:
-
-
-### Моя функция draw (включая вычисления матриц) выглядит так:
-
-```javascript
-var projection = null;
-var view = null;
-var viewProjection = null;
-
-function draw(gl, camera, sceneTree){
- // projection matrix
- projection = new Float32Array(9);
- mat3.projection(projection, gl.canvas.clientWidth, gl.canvas.clientHeight);
-
- // camera matrix
- view = new Float32Array(9);
- mat3.fromTranslation(view, camera.translation);
- mat3.rotate(view, view, toRadians(camera.rotation));
- mat3.scale(view, view, camera.scale);
- // view matrix
- mat3.invert(view, view)
-
- // VP matrix
- viewProjection = new Float32Array(9);
- mat3.multiply(viewProjection, projection, view);
-
- // go through scene tree:
- // - build final matrix for each object
- // e.g: u_matrix = VP x Model (translate x rotate x scale)
-
- // draw each object in scene tree
- // ...
-}
-```
-
-### Вершинный шейдер:
-```
-attribute vec2 a_position;
-
-uniform mat3 u_matrix;
-
-void main() {
- gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
-}
-```
-
-### Функция зума:
-
-```javascript
-
-function screenToWorld(screenPos){
- // normalized screen position
- let nsp = [
- 2.0 * screenPos[0] / this.gl.canvas.width - 1,
- - 2.0 * screenPos[1] / this.gl.canvas.height + 1
- ];
-
- let inverseVP = new Float32Array(9);
- mat3.invert(inverseVP, viewProjection);
-
- let worldPos = [0, 0];
- return vec2.transformMat3(worldPos, nsp, inverseVP);
-}
-
-var zoomRange = [0.01, 2];
-
-canvas.addEventListener('wheel', (e) => {
- let oldZoom = camera.scale[0];
- let zoom = Math.min(Math.max(oldZoom + e.deltaX / 100, zoomRange[0]), zoomRange[1]);
-
- camera.scale = [zoom, zoom];
-
- let zoomPoint = screenToWorld([e.clientX, e.clientY]);
- // totally breaks if enable this line
- //vec2.copy(camera.translation, zoomPoint);
-
- // call draw function again
- draw();
-
-}, false);
-```
-
-
-Если я применяю `zoomPoint` к трансляции камеры, значения `zoomPoint` (и позиция камеры соответственно) начинают неуправляемо расти при каждом событии зума (неважно, увеличиваю я или уменьшаю), и объекты в сцене сразу выходят из вида.
-
-Буду очень благодарен за любые идеи или подсказки, что я делаю не так. Спасибо.
-
-## Ответ:
-
-Поскольку вы не выложили **минимальный воспроизводимый пример** в самом вопросе, я не мог проверить с вашей библиотекой матриц. Используя свою, я смог реализовать зум так:
-
-```
- const [clipX, clipY] = getClipSpaceMousePosition(e);
-
- // позиция до зума
- const [preZoomX, preZoomY] = m3.transformPoint(
- m3.inverse(viewProjectionMat),
- [clipX, clipY]);
-
- // умножаем движение колеса на текущий уровень зума
- // чтобы зумить меньше при большом увеличении и больше при малом
- const newZoom = camera.zoom * Math.pow(2, e.deltaY * -0.01);
- camera.zoom = Math.max(0.02, Math.min(100, newZoom));
-
- updateViewProjection();
-
- // позиция после зума
- const [postZoomX, postZoomY] = m3.transformPoint(
- m3.inverse(viewProjectionMat),
- [clipX, clipY]);
-
- // камеру нужно сдвинуть на разницу до и после
- camera.x += preZoomX - postZoomX;
- camera.y += preZoomY - postZoomY;
-```
-
-Обратите внимание, что зум — это противоположность scale. Если zoom = 2, то я хочу, чтобы всё выглядело в 2 раза больше. Для этого нужно *уменьшить* пространство камеры, то есть масштабировать его на 1 / zoom.
-
-Пример:
-
-{{{example url="../webgl-qna-how-to-implement-zoom-from-mouse-in-2d-webgl-example-1.html"}}}
-
-Обратите внимание, что я добавил camera.rotation, чтобы убедиться, что всё работает и при повороте. Похоже, работает. [Вот пример с зумом, панорамой и вращением](https://jsfiddle.net/greggman/mdpxw3n6/)
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-import-a-heightmap-in-webgl.md b/webgl/lessons/ru/webgl-qna-how-to-import-a-heightmap-in-webgl.md
deleted file mode 100644
index 8a5b80e9c..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-import-a-heightmap-in-webgl.md
+++ /dev/null
@@ -1,211 +0,0 @@
-Title: Как импортировать карту высот в WebGL
-Description: Как импортировать карту высот в WebGL
-TOC: Как импортировать карту высот в WebGL
-
-## Вопрос:
-
-Я знаю, что в теории нужно сначала найти координаты на карте высот, например (x = `ширина HM / ширина Terrain * x Terrain`) и y координату (`y = высота HM / высота Terrain * y Terrain`), и после получения местоположения на карте высот мы получаем реальную высоту по формуле `min_height + (colorVal / (max_color - min_color) * *max_height - min_height`), возвращая Z значение для конкретного сегмента.
-
-Но как я могу реально импортировать карту высот и получить её параметры? Я пишу на JavaScript без дополнительных библиотек (three, babylon).
-
-**edit**
-
-Сейчас я жёстко кодирую Z значения на основе диапазонов x и y:
-
- Plane.prototype.modifyGeometry=function(x,y){
- if((x>=0&&x<100)&&(y>=0&&y<100)){
- return 25;
- }
- else if((x>=100&&x<150)&&(y>=100&&y<150)){
- return 20;
- }
- else if((x>=150&&x<200)&&(y>=150&&y<200)){
- return 15;
- }
- else if((x>=200&&x<250)&&(y>=200&&y<250)){
- return 10;
- }
- else if((x>=250&&x<300)&&(y>=250&&y<300)){
- return 5;
- }
- else{
- return 0;
- }
-
-** edit **
-
-Я могу получить плоскую сетку (или с случайно сгенерированными высотами), но как только я добавляю данные изображения, получаю пустой экран (хотя ошибок нет). Вот код (я немного изменил его):
-
-
-
- var gl;
- var canvas;
-
- var img = new Image();
- // img.onload = run;
- img.crossOrigin = 'anonymous';
- img.src = 'https://threejsfundamentals.org/threejs/resources/images/heightmap-96x64.png';
-
-
- var gridWidth;
- var gridDepth;
- var gridPoints = [];
- var gridIndices = [];
- var rowOff = 0;
- var rowStride = gridWidth + 1;
- var numVertices = (gridWidth * 2 * (gridDepth + 1)) + (gridDepth * 2 * (gridWidth + 1));
-
-
- //создание плоскости
- function generateHeightPoints() {
-
- var ctx = document.createElement("canvas").getContext("2d"); //используем 2d canvas для чтения изображения
- ctx.canvas.width = img.width;
- ctx.canvas.height = img.height;
- ctx.drawImage(img, 0, 0);
- var imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
-
- gridWidth = imgData.width - 1;
- gridDepth = imgData.height - 1;
-
- for (var z = 0; z <= gridDepth; ++z) {
- for (var x = 0; x <= gridWidth; ++x) {
- var offset = (z * imgData.width + x) * 4;
- var height = imgData.data[offset] * 10 / 255;
- gridPoints.push(x, height, z);
- }
- }
- }
-
- function generateIndices() {
- for (var z = 0; z<=gridDepth; ++z) {
- rowOff = z*rowStride;
- for(var x = 0; x
-
-Читайте его, загружая изображение, рисуя на 2D canvas, вызывая getImageData. Затем просто читайте красные значения для высоты.
-
-{{{example url="../webgl-qna-how-to-import-a-heightmap-in-webgl-example-2.html"}}}
-
-Затем вместо создания сетки линий создайте сетку треугольников. Есть много способов это сделать. Вы можете поставить 2 треугольника на квадрат сетки. Этот код ставит 4. Вам также нужно генерировать нормали. Я скопировал код для генерации нормалей из [этой статьи](https://webglfundamentals.org/webgl/lessons/webgl-3d-geometry-lathe.html), который является довольно универсальным кодом генерации нормалей. Будучи сеткой, вы можете создать генератор нормалей, специфичный для сетки, который будет быстрее, поскольку будучи сеткой вы знаете, какие вершины общие.
-
-Этот код также использует [twgl](https://twgljs.org), потому что WebGL слишком многословен, но должно быть понятно, как сделать это в обычном WebGL, читая названия функций twgl.
-
-{{{example url="../webgl-qna-how-to-import-a-heightmap-in-webgl-example-3.html"}}}
-
-
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
cosmo
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-load-images-in-the-background-with-no-jank.md b/webgl/lessons/ru/webgl-qna-how-to-load-images-in-the-background-with-no-jank.md
deleted file mode 100644
index d8c9e3a5c..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-load-images-in-the-background-with-no-jank.md
+++ /dev/null
@@ -1,70 +0,0 @@
-Title: Как загружать изображения в фоне без задержек
-Description: Как загружать изображения в фоне без задержек
-TOC: Как загружать изображения в фоне без задержек
-
-## Вопрос:
-
-В нашем WebGL приложении я пытаюсь загружать и декодировать текстуры в web worker, чтобы избежать задержек рендеринга в основном потоке. Использование createImageBitmap в worker и передача image bitmap обратно в основной поток работает хорошо, но в Chrome это использует **три** или больше (возможно, в зависимости от количества ядер?) отдельных workers (ThreadPoolForegroundWorker), которые вместе с основным потоком и моим собственным worker дают пять потоков.
-
-Я предполагаю, что это вызывает оставшиеся нарушения рендеринга на моём четырёхъядерном процессоре, поскольку я вижу необъяснимо долгие времена в функции Performance в DevTools Chrome.
-
-Итак, могу ли я как-то ограничить количество workers, используемых createImageBitmap? Даже если я передаю изображения как blobs или array buffers в основной поток и активирую createImageBitmap оттуда, его workers будут конкурировать с моим собственным worker и основным потоком.
-
-Я пробовал создавать обычные Images в worker вместо этого, чтобы явно декодировать их там, но Image не определён в контексте worker, как и document, если я хочу создать их как элементы. И обычные Images тоже не передаваемые, так что создание их в основном потоке и передача в worker тоже не кажется осуществимой.
-
-Жду любых предложений...
-
-## Ответ:
-
-Нет причин использовать createImageBitmap в worker (ну, см. внизу). Браузер уже декодирует изображение в отдельном потоке. Делать это в worker ничего не даёт. Большая проблема в том, что ImageBitmap не может знать, как вы собираетесь использовать изображение, когда наконец передадите его в WebGL. Если вы запросите формат, отличный от того, что декодировал ImageBitmap, то WebGL должен конвертировать и/или декодировать его снова, и вы не можете дать ImageBitmap достаточно информации, чтобы сказать ему, в каком формате вы хотите его декодировать.
-
-Кроме того, WebGL в Chrome должен передавать данные изображения из процесса рендеринга в GPU процесс, что для большого изображения является относительно большой копией (1024x1024 по RGBA это 4 мега)
-
-Лучший API, на мой взгляд, позволил бы вам сказать ImageBitmap, какой формат вы хотите и где вы хотите его (CPU, GPU). Таким образом браузер мог бы асинхронно подготовить изображение и не требовал бы тяжёлой работы при завершении.
-
-В любом случае, вот тест. Если вы снимите галочку "update texture", то текстуры всё ещё скачиваются и декодируются, но просто не вызывается `gl.texImage2D` для загрузки текстуры. В этом случае я не вижу задержек (не доказательство, что это проблема, но я думаю, что проблема там)
-
-{{{example url="../webgl-qna-how-to-load-images-in-the-background-with-no-jank-example-1.html"}}}
-
-Я довольно уверен, что единственный способ, которым вы могли бы гарантировать отсутствие задержек — это декодировать изображения самостоятельно в worker, передавать в основной поток как arraybuffer и загружать в WebGL по несколько строк за кадр с `gl.bufferSubData`.
-
-{{{example url="../webgl-qna-how-to-load-images-in-the-background-with-no-jank-example-2.html"}}}
-
-Примечание: Я не знаю, что это тоже будет работать. Несколько мест, которые пугают и зависят от реализации браузера
-
-1. Какие проблемы производительности при изменении размера canvas. Код изменяет размер OffscreenCanvas в worker. Это может быть тяжёлая операция с последствиями для GPU.
-
-2. Какая производительность рисования bitmap в canvas? Снова большие проблемы производительности GPU, поскольку браузер должен передать изображение на GPU, чтобы нарисовать его в GPU 2D canvas.
-
-3. Какая производительность getImageData? Снова браузер должен потенциально заморозить GPU, чтобы прочитать память GPU и получить данные изображения.
-
-4. Возможный удар по производительности при изменении размера текстуры.
-
-5. Только Chrome в настоящее время поддерживает OffscreenCanvas
-
-1, 2, 3 и 5 могут быть решены декодированием [jpg](https://github.com/notmasteryet/jpgjs), [png](https://github.com/arian/pngjs) изображения самостоятельно, хотя действительно отстойно, что браузер имеет код для декодирования изображения, просто вы не можете получить доступ к коду декодирования никаким полезным способом.
-
-Для 4, если это проблема, это может быть решено выделением текстуры максимального размера изображения и затем копированием меньших текстур в прямоугольную область. Предполагая, что это проблема
-
-{{{example url="../webgl-qna-how-to-load-images-in-the-background-with-no-jank-example-3.html"}}}
-
-примечание: jpeg декодер медленный. Если найдёте или сделаете более быстрый, пожалуйста, оставьте комментарий
-
----
-
-# Обновление
-
-Я просто хочу сказать, что `ImageBitmap` **должен** быть достаточно быстрым и что некоторые из моих комментариев выше о том, что у него недостаточно информации, могут быть не совсем правильными.
-
-Моё текущее понимание в том, что вся суть `ImageBitmap` была в том, чтобы сделать загрузки быстрыми. Это должно работать так: вы даёте ему blob и асинхронно он загружает это изображение в GPU. Когда вы вызываете `texImage2D` с ним, браузер может "blit" (рендерить с GPU) это изображение в вашу текстуру. Я не знаю, почему есть задержки в первом примере, учитывая это, но я вижу задержки каждые 6 или около того изображений.
-
-С другой стороны, хотя загрузка изображения в GPU была целью `ImageBitmap`, браузеры не обязаны загружать в GPU. `ImageBitmap` всё ещё должен работать, даже если у пользователя нет GPU. Суть в том, что браузер решает, как реализовать функцию, и будет ли она быстрой или медленной, без задержек или с ними — полностью зависит от браузера.
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-make-a-smudge-brush-tool.md b/webgl/lessons/ru/webgl-qna-how-to-make-a-smudge-brush-tool.md
deleted file mode 100644
index cd9169b2c..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-make-a-smudge-brush-tool.md
+++ /dev/null
@@ -1,54 +0,0 @@
-Title: Как сделать инструмент размазывающей кисти
-Description: Как сделать инструмент размазывающей кисти
-TOC: Как сделать инструмент размазывающей кисти
-
-## Вопрос:
-
-Мне нужна идея, как я могу сделать кисть, которая может размазывать цвет.
-
-Пример на картинке: правая сторона - рисование базовой кистью с двумя разными цветами, слева также рисование, но дополнительно используется инструмент размазывания, **результат должен быть чем-то вроде левой стороны**
-
-![enter image description here][1]
-
-Мне нужен совет, как я могу попробовать это сделать
-
- [1]: http://i.stack.imgur.com/oyaBs.png
-
-## Ответ:
-
-Вот одна попытка
-
-1. При mousedown захватить копию области под мышью в отдельный canvas
-
-2. При mousemove рисовать эту копию по одному пикселю за раз от предыдущей позиции мыши к текущей позиции мыши с 50% прозрачностью, захватывая новую копию после каждого движения.
-
-В псевдокоде
-
-```
-on mouse down
- grab copy of canvas at mouse position
- prevMousePos = currentMousePos
-
-on mouse move
- for (pos = prevMousePos to currentMousePos step 1 pixel)
- draw copy at pos with 50% alpha
- grab new copy of canvas at pos
- prevMousePos = currentMousePos
-```
-
-Кисть размывается путем рисования радиального градиента от rgba(0,0,0,0) до rgba(0,0,0,1) над ней, используя `globalCompositeOperation = 'destination-out'`.
-
-
-{{{example url="../webgl-qna-how-to-make-a-smudge-brush-tool-example-1.html"}}}
-
-
-
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 3.0 от
-
Your choice
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-make-webgl-canvas-transparent.md b/webgl/lessons/ru/webgl-qna-how-to-make-webgl-canvas-transparent.md
deleted file mode 100644
index 7ef38b3ac..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-make-webgl-canvas-transparent.md
+++ /dev/null
@@ -1,62 +0,0 @@
-Title: Как сделать canvas WebGL прозрачным
-Description: Как сделать canvas WebGL прозрачным
-TOC: Как сделать canvas WebGL прозрачным
-
-## Вопрос:
-
-Можно ли сделать **WebGL canvas** с прозрачным фоном?
-Я хочу, чтобы содержимое веб-страницы было видно сквозь canvas.
-
-Вот что у меня сейчас: http://i50.tinypic.com/2vvq7h2.png
-
-Как видно, текст за WebGL canvas не виден. Когда я меняю стиль элемента Canvas в CSS и добавляю
-
- opacity: 0.5;
-
-Страница выглядит так:
-https://imgur.com/BMgHWsZ
-
-Что почти то, что мне нужно, но не совсем — цвет текста из-за CSS alpha, конечно, не такой же черный, а цвет синей фигуры не такой же синий, как на первом изображении.
-
-Спасибо за любую помощь!
-https://imgur.com/hSu5tyM
-
-## Ответ:
-
-WebGL по умолчанию поддерживает прозрачность. Вот пример
-
-{{{example url="../webgl-qna-how-to-make-webgl-canvas-transparent-example-1.html"}}}
-
-Обратите внимание, что браузер предполагает, что пиксели в canvas представлены в формате PRE-MULTIPLIED-ALPHA. Это значит, например, если вы измените цвет очистки на (1, 0, 0, 0.5), вы получите то, что не увидите больше нигде в HTML.
-
-Я имею в виду, что pre-multiplied alpha означает, что RGB-части уже умножены на alpha. То есть если у вас 1,0,0 для RGB и alpha 0.5, то после умножения RGB на alpha получится 0.5, 0, 0 для RGB. Именно это браузер ожидает по умолчанию.
-
-Если пиксели в WebGL — 1,0,0,0.5, это не имеет смысла для браузера, и вы получите странные эффекты.
-
-Смотрите, например:
-
-{{{example url="../webgl-qna-how-to-make-webgl-canvas-transparent-example-2.html"}}}
-
-Обратите внимание, что черный текст стал красным, хотя вы могли бы подумать, что alpha 0.5 = 50% черного текста и 50% красного WebGL canvas. Это потому, что красный не был pre-multiplied.
-
-Вы можете решить это, убедившись, что значения, которые вы создаете в WebGL, представляют собой pre-multiplied значения, или вы можете сказать браузеру, что ваши пиксели WebGL не pre-multiplied, когда создаете контекст webgl так:
-
- const gl = canvas.getContext("webgl", { premultipliedAlpha: false });
-
-Теперь пиксели 1,0,0,0.5 снова работают. Пример:
-
-{{{example url="../webgl-qna-how-to-make-webgl-canvas-transparent-example-3.html"}}}
-
-Какой способ использовать — зависит от вашего приложения. Многие GL-программы ожидают не pre-multiplied alpha, тогда как все остальные части HTML5 ожидают pre-multiplied alpha, поэтому WebGL дает вам оба варианта.
-
-
-
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 3.0 от
-
Jack Sean
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-optimize-rendering-a-ui.md b/webgl/lessons/ru/webgl-qna-how-to-optimize-rendering-a-ui.md
deleted file mode 100644
index 1ed856cc1..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-optimize-rendering-a-ui.md
+++ /dev/null
@@ -1,115 +0,0 @@
-Title: Как оптимизировать рендеринг UI
-Description: Как оптимизировать рендеринг UI
-TOC: Как оптимизировать рендеринг UI
-
-## Вопрос:
-
-Я только начинаю с WebGL. Я следовал простому руководству для начинающих на YouTube. Теперь я пытаюсь создать простую 2D игру.
-
-В этой игре я хочу рендерить простой инвентарь с изображениями. Когда я это делаю, мой FPS падает до 2 после 10 секунд. Если я убираю код для рендеринга инвентаря, он остаётся на 60.
-
-Я знаю, что моя проблема в строке 82 в `game/js/engine/inventory/inventory.js`. Там я рендерю 35 изображений с классом sprite, который я сделал, смотря руководство. Я думаю, что поскольку я смотрел простое руководство, код, который рендерит изображение, не оптимизирован и, вероятно, не лучший способ это делать. Класс sprite находится в `game/js/engine/material.js:127`. В классе sprite я настраиваю простые переменные, которые можно передать в мой вершинный и фрагментный шейдер.
-
-## Настройка Sprite ##
-В методе setup я настраиваю все параметры для моего изображения.
-```
-gl.useProgram(this.material.program);
-
-this.gl_tex = gl.createTexture();
-
-gl.bindTexture(gl.TEXTURE_2D, this.gl_tex);
-gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT);
-gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT);
-gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
-gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
-gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this.image);
-gl.bindTexture(gl.TEXTURE_2D, null);
-
-this.uv_x = this.size.x / this.image.width;
-this.uv_y = this.size.y / this.image.height;
-
-this.tex_buff = gl.createBuffer();
-gl.bindBuffer(gl.ARRAY_BUFFER, this.tex_buff);
-gl.bufferData(gl.ARRAY_BUFFER, Sprite.createRenderRectArray(0, 0, this.uv_x, this.uv_y), gl.STATIC_DRAW);
-
-this.geo_buff = gl.createBuffer();
-gl.bindBuffer(gl.ARRAY_BUFFER, this.geo_buff);
-gl.bufferData(gl.ARRAY_BUFFER, Sprite.createRectArray(0, 0, this.size.x, this.size.y), gl.STATIC_DRAW);
-
-gl.useProgram(null);
-```
-
-## Рендеринг Sprite ##
-В методе render я сначала привязываю текстуру. Затем привязываю буфер текстурных координат, геометрический буфер и некоторые смещения для моего мира. Наконец, рисую массивы.
-```
-let frame_x = Math.floor(frames.x) * this.uv_x;
-let frame_y = Math.floor(frames.y) * this.uv_y;
-
-let oMat = new M3x3().transition(position.x, position.y);
-gl.useProgram(this.material.program);
-
-this.material.set("u_color", 1, 1, 1, 1);
-
-gl.activeTexture(gl.TEXTURE0);
-gl.bindTexture(gl.TEXTURE_2D, this.gl_tex);
-this.material.set("u_image", 0);
-
-gl.bindBuffer(gl.ARRAY_BUFFER, this.tex_buff);
-this.material.set("a_texCoord");
-
-gl.bindBuffer(gl.ARRAY_BUFFER, this.geo_buff);
-this.material.set("a_position");
-
-this.material.set("u_texeloffset", 0.5 / (this.image.width * scale.x), 0.5 / (this.image.height * scale.y));
-this.material.set("u_frame", frame_x, frame_y);
-this.material.set("u_world", worldSpaceMatrix.getFloatArray());
-this.material.set("u_object", oMat.getFloatArray());
-
-gl.drawArrays(gl.TRIANGLE_STRIP, 0, 6);
-gl.useProgram(null);
-```
-Github: [https://github.com/DJ1TJOO/2DGame/][1]
-
-У кого-нибудь есть идея, как я могу исправить/оптимизировать это?
-Или может быть есть лучший способ рендерить инвентарь?
-
-Если вы найдёте любой другой способ улучшить мой WebGL или JavaScript, пожалуйста, скажите.
-
-[1]: https://github.com/DJ1TJOO/2DGame/
-
-## Ответ:
-
-> есть лучший способ рендерить инвентарь?
-
-Есть несколько способов оптимизации, которые приходят в голову.
-
-1. Может быть быстрее просто использовать HTML для вашего инвентаря
-
- Серьёзно: Вы также получаете простое международное рендеринг шрифтов, стили,
- отзывчивость с CSS и т.д... Много игр делают это.
-
-2. Обычно быстрее использовать texture atlas (одну текстуру с множеством разных изображений), затем генерировать вершины в vertex buffer для всех частей вашего инвентаря. Затем рисовать всё одним вызовом draw. Так работает, например, [Dear ImGUI](https://github.com/ocornut/imgui), чтобы делать [все эти удивительные GUI](https://github.com/ocornut/imgui/issues/3075). Он сам ничего не рисует, он просто генерирует vertex buffer с позициями и текстурными координатами для texture atlas.
-
-3. Делайте #2, но вместо генерации всего vertex buffer каждый кадр просто обновляйте части, которые изменяются.
-
- Так, например, допустим ваш инвентарь показывает
-
- [gold ] 123
- [silver] 54
- [copper] 2394
-
- Допустим, вы всегда рисуете `[gold ]`, `[silver]` и `[copper]`, но только числа изменяются. Вы могли бы генерировать vertex buffers, которые содержат все позиции для каждой буквы как sprite, и затем сказать 6 placeholder'ов символов для каждого значения. Вам нужно обновлять только числа, когда они изменяются, помня, где они находятся в vertex buffers. Для любой цифры, которую вы не хотите рисовать, вы можете просто переместить её вершины за экран.
-
-4. Рисуйте инвентарь в текстуру (или части его). Затем рисуйте текстуру на экране. Обновляйте только части текстуры, которые изменяются.
-
- Это в основном [то, что делает сам браузер](https://www.html5rocks.com/en/tutorials/speed/layers/). На основе различных настроек CSS части страницы разделяются на текстуры. Когда какой-то HTML или CSS изменяется, только те текстуры, в которых что-то изменилось, перерисовываются, а затем все текстуры рисуются для рекомпозиции страницы.
-
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
DJ1TJOO
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-prevent-texture-bleeding-with-a-texture-atlas.md b/webgl/lessons/ru/webgl-qna-how-to-prevent-texture-bleeding-with-a-texture-atlas.md
deleted file mode 100644
index 7c462415a..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-prevent-texture-bleeding-with-a-texture-atlas.md
+++ /dev/null
@@ -1,125 +0,0 @@
-Title: Как предотвратить просачивание текстур с атласом текстур
-Description: Как предотвратить просачивание текстур с атласом текстур
-TOC: Как предотвратить просачивание текстур с атласом текстур
-
-## Вопрос:
-
-Я применил два необходимых шага, указанных в этом ответе https://gamedev.stackexchange.com/questions/46963/how-to-avoid-texture-bleeding-in-a-texture-atlas, но я всё ещё получаю просачивание текстур.
-
-У меня есть атлас, который заполнен сплошными цветами на границах: `x y w h: 0 0 32 32, 0 32 32 32, 0 64 32 32, 0 32 * 3 32 32`
-
-Я хочу отображать каждый из этих кадров, используя WebGL без просачивания текстур, только сплошные цвета как есть.
-
-Я отключил мипмаппинг:
-
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
-
- //gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
-
-Я применил коррекцию полпикселя:
-
- const uvs = (src, frame) => {
- const tw = src.width,
- th = src.height;
-
- const getTexelCoords = (x, y) => {
- return [(x + 0.5) / tw, (y + 0.5) / th];
- };
-
- let frameLeft = frame[0],
- frameRight = frame[0] + frame[2],
- frameTop = frame[1],
- frameBottom = frame[1] + frame[3];
-
- let p0 = getTexelCoords(frameLeft, frameTop),
- p1 = getTexelCoords(frameRight, frameTop),
- p2 = getTexelCoords(frameRight, frameBottom),
- p3 = getTexelCoords(frameLeft, frameBottom);
-
- return [
- p0[0], p0[1],
- p1[0], p1[1],
- p3[0], p3[1],
- p2[0], p2[1]
- ];
- };
-
-Но я всё ещё получаю просачивание текстур. Сначала я попробовал использовать pixi.js, и я тоже получил просачивание текстур, затем я попробовал использовать vanilla js.
-
-Я исправил это, изменив эти строки:
-
- let frameLeft = frame[0],
- frameRight = frame[0] + frame[2] - 1,
- frameTop = frame[1],
- frameBottom = frame[1] + frame[3] - 1;
-
-Как вы можете видеть, я вычитаю 1 из правого и нижнего краёв. Ранее эти индексы были 32, что означает начало другого кадра, это должно быть 31 вместо этого. Я не знаю, является ли это правильным решением.
-
-## Ответ:
-
-Ваше решение правильно.
-
-Представьте, что у нас есть текстура 4x2 с двумя спрайтами 2x2 пикселя
-
-```
-+-------+-------+-------+-------+
-| | | | |
-| E | F | G | H |
-| | | | |
-+-------+-------+-------+-------+
-| | | | |
-| A | B | C | D |
-| | | | |
-+-------+-------+-------+-------+
-```
-
-Буквы представляют центры пикселей в текстурах.
-
-```
-(pixelCoord + 0.5) / textureDimensions
-```
-
-Возьмите спрайт 2x2 в A, B, E, F. Если ваши текстурные координаты идут где-либо между B и C, то вы получите некоторую смесь C, если у вас включена фильтрация текстур.
-
-Изначально вы вычисляли координаты A, A + width, где width = 2. Это привело вас от A до C. Добавив -1, вы получаете только от A до B.
-
-К сожалению, у вас есть новая проблема, которая заключается в том, что вы отображаете только половину A и B. Вы можете решить это, добавив отступы к спрайтам. Например, сделайте его 6x2 с пикселем между ними, который повторяет края спрайта
-
-```
-+-------+-------+-------+-------+-------+-------+
-| | | | | | |
-| E | F | Fr | Gr | G | H |
-| | | | | | |
-+-------+-------+-------+-------+-------+-------+
-| | | | | | |
-| A | B | Br | Cr | C | D |
-| | | | | | |
-+-------+-------+-------+-------+-------+-------+
-```
-
-Выше Br - это B повторённый, Cr - это C повторённый. Установка repeat как `gl.CLAMP_TO_EDGE` повторит A и D для вас. Теперь вы можете использовать края.
-
-Координаты спрайта CDGH:
-
- p0 = 4 / texWidth
- p1 = 0 / texHeigth
- p2 = (4 + spriteWidth) / texWidth
- p3 = (0 + spriteHeigth) / texHeight
-
-Лучший способ увидеть разницу - нарисовать 2 спрайта крупно, используя обе техники, без отступов и с отступами.
-
-{{{example url="../webgl-qna-how-to-prevent-texture-bleeding-with-a-texture-atlas-example-1.html"}}}
-
-
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
eguneys
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-process-particle-positions.md b/webgl/lessons/ru/webgl-qna-how-to-process-particle-positions.md
deleted file mode 100644
index ff3cf2acb..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-process-particle-positions.md
+++ /dev/null
@@ -1,42 +0,0 @@
-Title: Как обрабатывать позиции частиц
-Description: Как обрабатывать позиции частиц
-TOC: Как обрабатывать позиции частиц
-
-## Вопрос:
-
-Используя сетку 4x4x4 в качестве примера, у меня есть 64 вершины (которые я буду называть частицами), которые начинают с определённых позиций относительно друг друга. Эти 64 частицы будут двигаться в направлениях x, y и z, теряя свои начальные позиции относительно друг друга. Однако каждый цикл новые позиции частиц и скорости должны вычисляться на основе исходных начальных отношений между частицей и её исходными соседями.
-
-Я узнал, что мне нужно использовать текстуры и, следовательно, Framebuffers для этого, и теперь я могу записывать две 3DTextures, которые переключаются, чтобы обеспечить функциональность записи и чтения для выполнения этого. Однако в следующем цикле, когда gl_FragCoord передаётся в фрагментный шейдер с новой позицией частицы (которая может быть переключена с другой частицей, например), я не вижу никакого механизма, с помощью которого исходная координата текстуры, которая содержала информацию о частице, будет записана с текущей информацией частицы. Есть ли какой-то механизм, который я не понимаю, который позволяет движущимся частицам иметь свои данные, хранящиеся в статической сетке (3D текстура), с данными каждой частицы, всегда заполняющими ту же координату, чтобы я мог использовать texelFetch для получения данных частицы, а также данных исходных соседей? Могу ли я изменить gl_FragCoord и иметь вывод пикселя там, где я хочу, или это неизменяемая входная переменная?
-
-Как только я решу эту проблему, я надеюсь затем реализовать Transform Feedback для выполнения фактического движения вершин без сброса текстуры в CPU и извлечения данных позиции и повторной загрузки их в GPU для следующего цикла.
-
-Есть ли какие-либо предложения о том, как отслеживать исходную позицию каждой частицы, исходных соседей и текущую позицию относительно этих исходных соседей, используя текстуры, записанные в Framebuffers?
-
-## Ответ:
-
-Я запутался в вашей путанице
-
-Вот простая система частиц только на JavaScript. Каждая частица начинается в случайном месте и движется в случайном направлении
-
-{{{example url="../webgl-qna-how-to-process-particle-positions-example-1.html"}}}
-
-Вот та же система частиц, всё ещё в JavaScript, но работающая больше как работает WebGL. Я не знаю, будет ли это более или менее запутанно. Важные моменты заключаются в том, что код, который обновляет позиции частиц, называемый `fragmentShader`, не может выбирать, что он обновляет. Он просто обновляет `gl.outColor`. У него также нет входов, кроме `gl.fragCoord` и `gl.currentProgram.uniforms`. currentParticleState - это массив массивов из 4 значений, где, как и раньше, это был массив объектов со свойством position. particleParameters также просто массив массивов из 4 значений вместо массива объектов со значением velocity. Это для имитации того факта, что это были бы текстуры в реальном WebGL, поэтому любое значение, такое как `position` или `velocity`, теряется.
-
-Код, который фактически рисует частицы, не имеет значения.
-
-{{{example url="../webgl-qna-how-to-process-particle-positions-example-2.html"}}}
-
-Вот тот же код в реальном WebGL
-
-{{{example url="../webgl-qna-how-to-process-particle-positions-example-3.html"}}}
-
-
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
billvh
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-read-a-single-component-with-readpixels.md b/webgl/lessons/ru/webgl-qna-how-to-read-a-single-component-with-readpixels.md
deleted file mode 100644
index a6df3a16c..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-read-a-single-component-with-readpixels.md
+++ /dev/null
@@ -1,295 +0,0 @@
-Title: Как читать один компонент с помощью readPixels
-Description: Как читать один компонент с помощью readPixels
-TOC: Как читать один компонент с помощью readPixels
-
-## Вопрос:
-
-Я конвертировал RGBA изображение в оттенки серого с помощью WebGL.
-
-При чтении пикселей с помощью `gl.readPixels()` с форматом `gl.RGBA` получаю значения для каждого пикселя как YYYA, потому что RGBA пиксель конвертируется в YYYA и присваивается `gl_FragColor`. Я хочу только 1 байт Y-компонента для каждого пикселя вместо 4 байт.
-
-Попробовал читать пиксели с форматом `gl.RED` (вместо `gl.RGBA`)
-
-```
-gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RED, gl.UNSIGNED_BYTE, pixels);
-```
-
-но получаю следующую ошибку в Chrome и только нули:
-
-```
-WebGL: INVALID_ENUM: readPixels: invalid format
-```
-
-1. Можно ли заставить `gl_FragColor` выводить 1 байт на пиксель в режиме LUMINANCE вместо RGBA, но входная текстура должна быть RGBA?
-2. Если формат рендеринга gl нельзя изменить, можно ли читать только первый байт каждого 4-байтового пикселя при вызове `gl.readPixels()`?
-
-Примечание:
-3. Я уже делаю копию вывода `gl.readPixels()` в другой массив, перепрыгивая каждые 4 байта. Но хочу избежать этой копии, так как это занимает больше времени.
-4. Также нужно, чтобы решение было совместимо с мобильными браузерами (iOS Safari и Android Chrome).
-
-
-
-
-
- function webGL() {
- var gTexture;
- var gFramebuffer;
- var srcCanvas = null;
- var programs = {};
- var program;
- var pixels;
-
- this.convertRGBA2Gray = function(inCanvas, inArray) {
- // Y компонент из YCbCr
- const shaderSourceRGB2GRAY = `
- precision mediump float;
-
- uniform sampler2D u_image;
- uniform vec2 u_textureSize;
- vec4 scale = vec4(0.299, 0.587, 0.114, 0.0);
- void main() {
- vec4 color = texture2D(u_image, gl_FragCoord.xy / u_textureSize);
- gl_FragColor = vec4(vec3(dot(color,scale)), color.a);
- }`;
-
- if (srcCanvas === null) {
- console.log('Setting up webgl');
- srcCanvas = inCanvas;
- _initialize(srcCanvas.width, srcCanvas.height);
- program = _createProgram("rgb2grey", shaderSourceRGB2GRAY);
- }
- pixels = inArray;
- _run(program);
- }
-
- ///////////////////////////////////////
- // Приватные функции
-
- var _getWebGLContext = function(canvas) {
- try {
- return (canvas.getContext("webgl", {premultipliedAlpha: false, preserveDrawingBuffer: true}) || canvas.getContext("experimental-webgl", {premultipliedAlpha: false, preserveDrawingBuffer: true}));
- }
- catch(e) {
- console.log("ERROR: %o", e);
- }
- return null;
- }
-
- var gl = _getWebGLContext(document.createElement('canvas'));
-
- var _initialize = function(width, height) {
- var canvas = gl.canvas;
- canvas.width = width;
- canvas.height = height;
-
- if (this.originalImageTexture) {
- return;
- }
-
- this.originalImageTexture = gl.createTexture();
- gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture);
-
- gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
-
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
-
- gTexture = gl.createTexture();
- gl.bindTexture(gl.TEXTURE_2D, gTexture);
-
- gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
-
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
-
- gl.texImage2D(
- gl.TEXTURE_2D, 0, gl.RGBA, canvas.width, canvas.height, 0,
- gl.RGBA, gl.UNSIGNED_BYTE, null);
-
- gFramebuffer = gl.createFramebuffer();
- gl.bindFramebuffer(gl.FRAMEBUFFER, gFramebuffer);
-
- var positionBuffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
- -1.0, 1.0,
- 1.0, 1.0,
- 1.0, -1.0,
-
- -1.0, 1.0,
- 1.0, -1.0,
- -1.0, -1.0
- ]), gl.STATIC_DRAW);
-
- gl.framebufferTexture2D(
- gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, gTexture, 0);
-
- gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture);
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, srcCanvas);
- }
-
- var _createProgram = function(name, fragmentSource, vertexSource) {
- shaderProgram = programs[name];
-
- if (shaderProgram){
- console.log('Reusing program');
- gl.useProgram(shaderProgram);
- return shaderProgram;
- }
-
- function createShader(type, source){
- var shader = gl.createShader(type);
-
- gl.shaderSource(shader, source);
-
- gl.compileShader(shader);
-
- return shader;
- }
-
- var vertexShader, fragmentShader;
-
- if (!vertexSource){
- vertexShader = createShader(gl.VERTEX_SHADER, `attribute vec2 a_position;
- void main() { gl_Position = vec4(a_position, 0.0, 1.0); }`
- );
- } else {
- vertexShader = createShader(gl.VERTEX_SHADER, vertexSource);
- }
- fragmentShader = createShader(gl.FRAGMENT_SHADER, fragmentSource);
-
- shaderProgram = gl.createProgram();
- gl.attachShader(shaderProgram, vertexShader);
- gl.attachShader(shaderProgram, fragmentShader);
- gl.linkProgram(shaderProgram);
-
- gl.useProgram(shaderProgram);
- return shaderProgram;
- }
-
- var _render = function(gl, program){
- var positionLocation = gl.getAttribLocation(program, "a_position");
-
- var u_imageLoc = gl.getUniformLocation(program, "u_image");
- var textureSizeLocation = gl.getUniformLocation(program, "u_textureSize");
-
- gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
- gl.enableVertexAttribArray(positionLocation);
-
- var width = gl.canvas.width,
- height = gl.canvas.height;
-
- gl.bindFramebuffer(gl.FRAMEBUFFER, gFramebuffer);
-
- gl.uniform2f(textureSizeLocation, width, height);
-
- gl.uniform1i(u_imageLoc, 0);
-
- gl.viewport(0, 0, width, height);
-
- gl.drawArrays(gl.TRIANGLES, 0, 6);
-
- }
-
- var _run = function(program){
- let t0 = performance.now();
- _render(gl, program);
- gl.bindTexture(gl.TEXTURE_2D, gTexture);
- let t1 = performance.now();
-
- // gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
- gl.readPixels(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight, gl.RED, gl.UNSIGNED_BYTE, pixels);
-
- let t2 = performance.now();
- console.log('_render dur = ' + Number((t1-t0).toFixed(3)) + ' ms');
- console.log('_run dur = ' + Number((t2-t0).toFixed(3)) + ' ms');
- }
-
- };
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-## Ответ:
-
-> Можно ли заставить gl_FragColor выводить 1 байт на пиксель в режиме LUMINANCE вместо RGBA, но входная текстура должна быть RGBA?
-
-Не переносимо. Спецификация WebGL1 говорит, что рендеринг в текстуру должен поддерживаться только для gl.RGBA / gl.UNSIGNED_BYTE. Все остальные форматы опциональны.
-
-> Если формат рендеринга gl нельзя изменить, можно ли читать только первый байт каждого 4-байтового пикселя при вызове gl.readPixels()?
-
-Нет, [Спецификация](https://www.khronos.org/registry/OpenGL/specs/es/2.0/es_full_spec_2.0.pdf) раздел 4.3.1 говорит, что поддерживается только `gl.RGBA`, `gl.UNSIGNED_BYTE`. Все остальные форматы опциональны и зависят от реализации. То же самое в WebGL2. Даже если вы создадите R8 текстуру (только красный, 8 бит), зависит от реализации, можете ли вы читать её как `gl.RED`/`gl.UNSIGNED_BYTE`.
-
-Смотрите [Webgl1](https://webglfundamentals.org/webgl/lessons/webgl-readpixels.html) и [Webgl2](https://webgl2fundamentals.org/webgl/lessons/webgl-readpixels.html)
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
rayen
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-render-large-scale-images-like-32000x32000.md b/webgl/lessons/ru/webgl-qna-how-to-render-large-scale-images-like-32000x32000.md
deleted file mode 100644
index e61c45f9d..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-render-large-scale-images-like-32000x32000.md
+++ /dev/null
@@ -1,113 +0,0 @@
-Title: Как рендерить изображения большого масштаба как 32000x32000
-Description: Как рендерить изображения большого масштаба как 32000x32000
-TOC: Как рендерить изображения большого масштаба как 32000x32000
-
-## Вопрос:
-
-Я хочу получить снимок моего WebGL canvas, и я хочу высокое разрешение, поэтому я увеличил размер моего canvas. Это автоматически изменяет `gl.drawingBufferWidth` и `gl.drawingBufferHeight`. Затем я устанавливаю viewport и рендерю сцену.
-
-Мой код работает правильно в низком разрешении (под 4000*4000), но в более высоких разрешениях есть много проблем.
-
-Если разрешение немного выше, снимок не показывает полностью. См. прикреплённый файл. Если разрешение увеличивается больше, ничего не показывается. И наконец, при некоторых разрешениях мой экземпляр WebGL уничтожается, и мне приходится перезапускать браузер, чтобы снова запустить WebGL.
-
-Есть ли способ получить снимок с WebGL canvas с высоким разрешением? Могу ли я использовать другое решение?
-
-## Ответ:
-
-4000x4000 пикселей - это 4000x4000x4 или 64 мегабайта памяти. 8000x8000 - это 256 мегабайт памяти. Браузеры не любят выделять такие большие куски памяти и часто устанавливают лимиты на страницу. Так, например, у вас есть WebGL canvas 8000x8000, который требует 2 буфера. Drawingbuffer И текстура, отображаемая на странице. Drawingbuffer может быть сглажен. Если это 4x MSAA, то потребуется гигабайт памяти только для этого буфера. Затем вы делаете скриншот, так что ещё 256 мегабайт памяти. Так что да, браузер по той или иной причине, скорее всего, убьёт вашу страницу.
-
-Помимо этого, WebGL имеет свои собственные лимиты в размере. Вы можете посмотреть этот лимит, который эффективно [`MAX_TEXTURE_SIZE`](https://web3dsurvey.com/webgl/parameters/MAX_TEXTURE_SIZE) или [`MAX_VIEWPORT_DIMS`](https://web3dsurvey.com/webgl/parameters/MAX_VIEWPORT_DIMS). Вы можете увидеть из тех, что около 40% машин не могут рисовать больше 4096 (хотя если вы [отфильтруете только десктоп, это намного лучше](https://web3dsurvey.com/webgl/parameters/MAX_VIEWPORT_DIMS?platforms=0000ff03c02d20f201)). Это число означает только то, что может делать оборудование. Оно всё ещё ограничено памятью.
-
-Один способ, который может решить эту проблему, - рисовать изображение по частям. Как вы это делаете, будет зависеть от вашего приложения. Если вы используете довольно стандартную матрицу перспективы для всего вашего рендеринга, вы можете использовать немного другую математику для рендеринга любой части вида. Большинство 3D математических библиотек имеют функцию `perspective`, и большинство из них также имеют соответствующую функцию `frustum`, которая немного более гибкая.
-
-Вот довольно стандартный стиль WebGL простой пример, который рисует куб, используя типичную функцию `perspective`
-
-{{{example url="../webgl-qna-how-to-render-large-scale-images-like-32000x32000-example-1.html"}}}
-
-И вот тот же код, рендерящий в 400x200 в восьми частях 100x100, используя типичную функцию `frustum` вместо `perspective`
-
-{{{example url="../webgl-qna-how-to-render-large-scale-images-like-32000x32000-example-2.html"}}}
-
-Если вы запустите фрагмент выше, вы увидите, что он генерирует 8 изображений
-
-Важные части - это
-
-Сначала нам нужно решить общий размер, который мы хотим
-
- const totalWidth = 400;
- const totalHeight = 200;
-
-Затем мы создадим функцию, которая будет рендерить любую меньшую часть этого размера
-
- function renderPortion(totalWidth, totalHeight, partX, partY, partWidth, partHeight) {
- ...
-
-Мы установим canvas на размер части
-
- gl.canvas.width = partWidth;
- gl.canvas.height = partHeight;
-
- gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
-
-И затем вычислим, что нам нужно передать в функцию `frustum`. Сначала мы вычисляем прямоугольник на zNear, который матрица перспективы создала бы, учитывая наши значения field of view, aspect и zNear
-
- // углы на zNear для общего изображения
- const zNearTotalTop = Math.tan(fov) * 0.5 * zNear;
- const zNearTotalBottom = -zNearTotalTop;
- const zNearTotalLeft = zNearTotalBottom * aspect;
- const zNearTotalRight = zNearTotalTop * aspect;
-
- // ширина, высота на zNear для общего изображения
- const zNearTotalWidth = zNearTotalRight - zNearTotalLeft;
- const zNearTotalHeight = zNearTotalTop - zNearTotalBottom;
-
-Затем мы вычисляем соответствующую область на zNear для части, которую мы хотим рендерить, и передаём их в `frustum` для генерации матрицы проекции.
-
- const zNearPartLeft = zNearTotalLeft + partX * zNearTotalWidth / totalWidth; const zNearPartRight = zNearTotalLeft + (partX + partWidth) * zNearTotalWidth / totalWidth;
- const zNearPartBottom = zNearTotalBottom + partY * zNearTotalHeight / totalHeight;
- const zNearPartTop = zNearTotalBottom + (partY + partHeight) * zNearTotalHeight / totalHeight;
-
- const projection = m4.frustum(zNearPartLeft, zNearPartRight, zNearPartBottom, zNearPartTop, zNear, zFar);
-
-Затем мы просто рендерим как обычно
-
-Наконец, снаружи у нас есть цикл для использования функции, которую мы только что сгенерировали, чтобы рендерить столько частей, сколько мы хотим, с любым разрешением, которое мы хотим.
-
- const totalWidth = 400;
- const totalHeight = 200;
- const partWidth = 100;
- const partHeight = 100;
-
- for (let y = 0; y < totalHeight; y += partHeight) {
- for (let x = 0; x < totalWidth; x += partWidth) {
- renderPortion(totalWidth, totalHeight, x, y, partWidth, partHeight);
- const img = new Image();
- img.src = gl.canvas.toDataURL();
- // сделать что-то с изображением.
- }
- }
-
-Это позволит вам рендерить в любой размер, который вы хотите, но вам понадобится другой способ собрать изображения в одно большее изображение. Вы можете или не можете сделать это в браузере. Вы можете попробовать создать гигантский 2D canvas и рисовать каждую часть в него (это предполагает, что 2D canvas не имеет тех же лимитов, что и WebGL). Для этого нет необходимости создавать изображения, просто рисуйте WebGL canvas в 2D canvas.
-
-В противном случае вам, возможно, придётся отправить их на сервер, который вы создадите, чтобы собрать изображение, или в зависимости от вашего случая использования позволить пользователю сохранить их и загрузить их все в программу редактирования изображений.
-
-Или если вы просто хотите отобразить их, браузер, вероятно, будет лучше работать с 16x16 изображениями 1024x1024, чем с одним изображением 16kx16k. В этом случае вы, вероятно, хотите вызвать `canvas.toBlob` вместо использования dataURL и затем вызвать `URL.createObjectURL` для каждого blob. Таким образом, у вас не будет этих гигантских строк dataURL.
-
-Пример:
-
-{{{example url="../webgl-qna-how-to-render-large-scale-images-like-32000x32000-example-3.html"}}}
-
-Если вы хотите, чтобы пользователь мог скачать изображение 16386x16386 вместо 256 изображений 1024x1024, то ещё одно решение - использовать код рендеринга частей выше и для каждой строки (или строк) изображений записать их данные в blob для ручной генерации PNG. [Этот пост в блоге](https://medium.com/the-guardian-mobile-innovation-lab/generating-images-in-javascript-without-using-the-canvas-api-77f3f4355fad) охватывает ручную генерацию PNG из данных, и [этот ответ предлагает, как сделать это для очень больших данных](https://stackoverflow.com/a/51247740/128511).
-
-## обновление:
-
-Просто для развлечения я написал эту [библиотеку, чтобы помочь генерировать гигантские PNG в браузере](https://github.com/greggman/dekapng).
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
MHA15
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-simulate-a-3d-texture-in-webgl.md b/webgl/lessons/ru/webgl-qna-how-to-simulate-a-3d-texture-in-webgl.md
deleted file mode 100644
index 000bf26db..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-simulate-a-3d-texture-in-webgl.md
+++ /dev/null
@@ -1,105 +0,0 @@
-Title: Как симулировать 3D текстуру в WebGL
-Description: Как симулировать 3D текстуру в WebGL
-TOC: Как симулировать 3D текстуру в WebGL
-
-## Вопрос:
-
-Итак, в WebGL я могу хранить текстуру максимум в 2 измерениях и читать её в шейдере с помощью texture2D(whatever);
-
-Если я хочу хранить 3-мерную текстуру, чтобы читать 3-мерные данные в шейдере, как это сделать?
-
-Вот мои идеи — и я хочу знать, правильно ли я подхожу к этому:
-
-В JavaScript:
-
- var info = [];
-
- for (var x = 0; x < 1; x+=.1) {
- for (var y = 0; y < 1; y+=.1) {
- for (var z = 0; z < 1; z+=.1) {
-
- info.push (x*y*z);
- info.push(0);
- info.push(0);
- info.push(0);
-
- }
- }
- }
-
- //bind texture here- whatever
-
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 10, 100, 0,
- gl.RGBA, gl.FLOAT, data_on_shader);
-
- //other texture stuff
-
-В шейдере:
-
- uniform sampler data_on_shader;
- x= texture.r//
- y = texture.g//
- z = texture.b//
-
- xfixed = floor(x*10.)/10. + .5;
- yfixed = floor(y*10.)/10. + .5;
- zfixed = floor(z*10.)/10. + .5;
-
- float data_received = texture2D(data_on_shader, vec2(xfixed, yfixed*10. + zfixed)).r;
-
-Что-то вроде использования row major order в 2D текстуре?
-Мысли?
-
-Спасибо заранее!
-
-## Ответ:
-
-Вы можете симулировать 3D текстуру, сохраняя каждую плоскость 3D текстуры в 2D текстуре
-
-Затем функция вроде этой позволит вам использовать её как 3D текстуру:
-
- vec4 sampleAs3DTexture(sampler2D tex, vec3 texCoord, float size) {
- float sliceSize = 1.0 / size; // пространство 1 среза
- float slicePixelSize = sliceSize / size; // пространство 1 пикселя
- float sliceInnerSize = slicePixelSize * (size - 1.0); // пространство size пикселей
- float zSlice0 = min(floor(texCoord.z * size), size - 1.0);
- float zSlice1 = min(zSlice0 + 1.0, size - 1.0);
- float xOffset = slicePixelSize * 0.5 + texCoord.x * sliceInnerSize;
- float s0 = xOffset + (zSlice0 * sliceSize);
- float s1 = xOffset + (zSlice1 * sliceSize);
- vec4 slice0Color = texture2D(tex, vec2(s0, texCoord.y));
- vec4 slice1Color = texture2D(tex, vec2(s1, texCoord.y));
- float zOffset = mod(texCoord.z * size, 1.0);
- return mix(slice0Color, slice1Color, zOffset);
- }
-
-Если ваша 3D текстура была 8x8x8, то вы создаёте 2D текстуру 64x8 и помещаете каждую плоскость 3D текстуры в вашу 2D текстуру. Затем, зная, что изначально было 8x8x8, вы передаёте `8.0` для размера в `sampleAs3DTexture`
-
- precision mediump float;
- uniform sampler2D u_my3DTexture;
- varying vec3 v_texCoord;
-
- ...
-
- #define CUBE_SIZE 8.0
-
- void main() {
- gl_FragColor = sampleAs3DTexture(u_my3DTexture, v_texCoord, CUBE_SIZE);
- }
-
-Примечание: функция выше предполагает, что вы хотите билинейную фильтрацию между плоскостями. Если не хотите, можете упростить функцию.
-
-Есть [видео-объяснение этого кода здесь][1], которое взято из [этого примера][2].
-
-
- [1]: http://www.youtube.com/watch?v=rfQ8rKGTVlg#t=26m00s
- [2]: http://webglsamples.googlecode.com/hg/color-adjust/color-adjust.html
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 3.0 от
-
Skorpius
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-support-both-webgl-and-webgl2.md b/webgl/lessons/ru/webgl-qna-how-to-support-both-webgl-and-webgl2.md
deleted file mode 100644
index 037476ad2..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-support-both-webgl-and-webgl2.md
+++ /dev/null
@@ -1,97 +0,0 @@
-Title: Как поддерживать и WebGL, и WebGL2
-Description: Как поддерживать и WebGL, и WebGL2
-TOC: Как поддерживать и WebGL, и WebGL2
-
-## Вопрос:
-
-У меня есть библиотека, которая использует WebGL1 для рендеринга.
-Она активно использует float-текстуры и instanced rendering.
-
-Сейчас поддержка WebGL1 довольно странная: некоторые устройства поддерживают, например, WebGL2 (где эти расширения встроены), но не поддерживают WebGL1, или поддерживают, но без нужных расширений.
-
-В то же время поддержка WebGL2 не идеальна. Возможно, когда-нибудь будет, но пока нет.
-
-Я начал думать, что потребуется для поддержки обеих версий.
-
-Для шейдеров, думаю, можно обойтись `#define`-ами. Например, `#define texture2D texture` и подобные вещи.
-
-С расширениями сложнее, так как объекты расширений больше не существуют.
-В качестве эксперимента я пробовал копировать методы расширения в сам контекст, например: `gl.drawArraysInstanced = (...args) => ext.drawArraysInstancedANGLE(...args)`.
-
-С текстурами менять почти ничего не нужно, разве что добавить что-то вроде `gl.RGBA8 = gl.RGBA` при запуске в WebGL1, чтобы код "просто работал" и в WebGL2.
-
-Вопрос: кто-нибудь пробовал так делать?
-Я опасаюсь, что это повредит производительности, особенно из-за лишней обёртки для вызова функций.
-Это также усложнит чтение кода, если предполагать, что он может работать в WebGL1. Ведь в WebGL1 нет `drawArraysInstanced` или `RGBA8`. Это также мешает типизации в Typescript и другим мелочам.
-
-Второй вариант — делать ветвления по всему коду. Две версии шейдеров (или `#ifdef`-трюки), много ветвлений для каждого места, где нужны форматы текстур, и для каждого места, где используется instancing.
-Вот так по всему коду — не очень красиво:
-
- if (version === 1) {
- instancedArrays.vertexAttribDivisorANGLE(m0, 1);
- instancedArrays.vertexAttribDivisorANGLE(m1, 1);
- instancedArrays.vertexAttribDivisorANGLE(m2, 1);
- instancedArrays.vertexAttribDivisorANGLE(m3, 1);
- } else {
- gl.vertexAttribDivisor(m0, 1);
- gl.vertexAttribDivisor(m1, 1);
- gl.vertexAttribDivisor(m2, 1);
- gl.vertexAttribDivisor(m3, 1);
- }
-
-Может, есть и третий способ, о котором я не подумал.
-
-Есть рекомендации?
-
-## Ответ:
-
-К сожалению, большинство ответов тут будут субъективными.
-
-Первый вопрос — зачем поддерживать оба? Если ваша идея работает в WebGL1 — просто используйте WebGL1. Если вам обязательно нужны фичи WebGL2 — используйте WebGL2 и смиритесь, что на старых устройствах не будет поддержки.
-
-Если вы всё же хотите поддерживать оба, [twgl](https://twgljs.org) пытается упростить это, [предлагая функцию, которая копирует все расширения WebGL1 в их WebGL2 API-позиции](http://twgljs.org/docs/module-twgl.html#.addExtensionsToContext). Как вы и упомянули, вместо
-
- ext = gl.getExtension('ANGLE_instanced_arrays');
- ext.drawArraysInstancedANGLE(...)
-
-Вы делаете
-
- twgl.addExtensionsToContext(gl);
- gl.drawArraysInstanced(...);
-
-Я не думаю, что будет заметная разница в производительности. Эти функции вызываются всего несколько сотен раз за кадр, и обёртка не станет узким местом.
-
-Но суть не в том, чтобы поддерживать WebGL1 и WebGL2 одновременно. Скорее, чтобы писать код одинаково для обоих API.
-
-Тем не менее, есть реальные различия между API. Например, чтобы использовать FLOAT RGBA текстуру в WebGL1:
-
- gl.texImage2D(target, level, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, ...)
-
-В WebGL2:
-
- gl.texImage2D(target, level, gl.RGBA32F, width, height, 0, gl.RGBA, gl.FLOAT, ...)
-
-WebGL2 выдаст ошибку, если вызвать его так же, как WebGL1. [Есть и другие отличия](https://webgl2fundamentals.org/webgl/lessons/webgl1-to-webgl2.html).
-
-Впрочем, вот такой вызов будет работать и в WebGL1, и в WebGL2. Спецификация прямо говорит, что такая комбинация даёт RGBA8 в WebGL2.
-
-Обратите внимание, что ваш пример с RGBA8 не совсем верен.
-
- gl.texImage2D(target, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, ...)
-
-Главное отличие — нет смысла использовать WebGL2, если вам хватает WebGL1. Или наоборот: если вам нужен WebGL2, то откатиться на WebGL1 будет сложно.
-
-Вы упомянули define-ы для шейдеров, но что делать с фичами WebGL2, которых нет в WebGL1? Например, `textureFetch`, оператор `%`, integer attributes и т.д. Если они нужны — придётся писать отдельный шейдер только для WebGL2. Если не нужны — зачем тогда WebGL2?
-
-Конечно, если хочется, можно сделать более продвинутый рендерер для WebGL2 и попроще для WebGL1.
-
-TL;DR: На мой взгляд, выберите что-то одно.
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
user2503048
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-tell-if-an-image-has-an-alpha-channel.md b/webgl/lessons/ru/webgl-qna-how-to-tell-if-an-image-has-an-alpha-channel.md
deleted file mode 100644
index dc9262c73..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-tell-if-an-image-has-an-alpha-channel.md
+++ /dev/null
@@ -1,81 +0,0 @@
-Title: Как определить, есть ли у изображения альфа-канал
-Description: Как определить, есть ли у изображения альфа-канал
-TOC: Как определить, есть ли у изображения альфа-канал
-
-## Вопрос:
-
-Когда вы загружаете текстуру WebGL напрямую из DOM-изображения, как узнать, есть ли у изображения альфа-канал? Можно ли это определить, кроме как угадывать по имени файла (например, если есть .PNG — возможно RGBA, иначе RGB)? В DOM-изображении есть ширина и высота, но я не вижу ничего, что бы говорило о формате. Например:
-
- const img = await loadDOMImage(url);
- const format = gl.RGBA; //Это всегда должно быть RGBA? Я трачу память, если там только RGB
- const internalFormat = gl.RGBA;
- const type = gl.UNSIGNED_BYTE; //Это точно правильно? DOM не поддерживает HDR форматы?
- gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, format, type, img);
-
-Моя функция загрузки выглядит так:
-
- async loadDOMImage(url) {
- return new Promise(
- (resolve, reject)=>{
- const img = new Image();
- img.crossOrigin = 'anonymous';
- img.addEventListener('load', function() {
- resolve(img);
- }, false);
- img.addEventListener('error', function(err) {
- reject(err);
- }, false);
-
- img.src = uri;
-
- }
- );
- }
-
-
-
-
-
-## Ответ:
-
-> как узнать, есть ли у изображения альфа-канал?
-
-Никак. Можно только догадываться.
-
-1. Можно посмотреть, заканчивается ли URL на .png, и предположить, что там есть альфа. Можно ошибиться.
-
-2. Можно нарисовать изображение в 2D canvas, затем вызвать getImageData, прочитать все альфа-значения и посмотреть, есть ли среди них не 255.
-
-> ```
-> const format = gl.RGBA;
-> ```
-> Это всегда должно быть RGBA? Я трачу память, если там только RGB
-
-Скорее всего, память не тратится зря. Большинство GPU лучше работают с RGBA, так что даже если вы выберете RGB, это вряд ли сэкономит память.
-
-> ```
-> const type = gl.UNSIGNED_BYTE;
-> ```
-> Это точно правильно?
-
-`texImage2D` берёт изображение, которое вы передаёте, и конвертирует его в `type` и `format`. Затем эти данные передаются на GPU.
-
-> DOM не поддерживает HDR форматы?
-
-Это не определено и зависит от браузера. Мне не известны HDR форматы, поддерживаемые браузерами. Какие форматы поддерживает браузер — решает сам браузер.
-
----
-
-Частый вопрос: нужно ли включать смешивание/прозрачность для конкретной текстуры. Некоторые ошибочно думают, что если у текстуры есть альфа, то надо, иначе — нет. Это неверно. Нужно ли использовать смешивание/прозрачность — это отдельный вопрос, не связанный с форматом изображения, и это нужно хранить отдельно.
-
-То же самое касается других вещей, связанных с изображениями. В каком формате загружать изображение в GPU — это зависит от приложения и задачи, и не связано с форматом самого файла изображения.
-
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
griffin2000
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-use-a-2d-sprite-s-transparency-as-a-mask.md b/webgl/lessons/ru/webgl-qna-how-to-use-a-2d-sprite-s-transparency-as-a-mask.md
deleted file mode 100644
index 0ec43ca64..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-use-a-2d-sprite-s-transparency-as-a-mask.md
+++ /dev/null
@@ -1,88 +0,0 @@
-Title: Как использовать прозрачность 2D-спрайта как маску
-Description: Как использовать прозрачность 2D-спрайта как маску
-TOC: Как использовать прозрачность 2D-спрайта как маску
-
-## Вопрос:
-
- ```javascript
-if (statuseffect) {
- // Очистка stencil buffer
- gl.clearStencil(0);
- gl.clear(gl.STENCIL_BUFFER_BIT);
-
-
- gl.stencilFunc(gl.ALWAYS, 1, 1);
- gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);
-
-
- gl.colorMask(false, false, false, false);
-
- gl.enable(gl.STENCIL_TEST);
-
- // Рисуем маску через gl.drawArrays L111
- drawImage(statuseffectmask.texture, lerp(-725, 675, this.Transtion_Value), 280, 128 * 4, 32 * 4)
-
- // Говорим stencil теперь рисовать/оставлять только пиксели, равные 1 — что мы установили ранее
- gl.stencilFunc(gl.EQUAL, 1, 1);
- gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
-
- // возвращаем обратно цветовой буфер
- gl.colorMask(true, true, true, true);
-
-
- drawImage(statuseffect.texture, lerp(-725, 675, this.Transtion_Value), 280, 128 * 4, 32 * 4)
-
-
- gl.disable(gl.STENCIL_TEST);
- }
-
-
-```
-Я пытаюсь добиться такого эффекта [![enter image description here][1]][1]
-Где берётся прозрачность спрайта, и затем другой спрайт рисуется только в областях, где нет прозрачности. Спасибо.
-
-[1]: https://i.stack.imgur.com/ESdGp.png
-
-## Ответ:
-
-Не совсем понятно, зачем вам использовать stencil для этого. Обычно вы бы просто [настроили смешивание (blending) и использовали прозрачность для смешивания](https://webglfundamentals.org/webgl/lessons/webgl-text-texture.html).
-
-Если вы действительно хотите использовать stencil, вам нужно сделать шейдер, который вызывает `discard`, если прозрачность (alpha) меньше определённого значения, чтобы stencil устанавливался только там, где спрайт не прозрачный:
-
-```
-precision highp float;
-
-varying vec2 v_texcoord;
-
-uniform sampler2D u_texture;
-uniform float u_alphaTest;
-
-void main() {
- vec4 color = texture2D(u_texture, v_texcoord);
- if (color.a < u_alphaTest) {
- discard; // не рисовать этот пиксель
- }
- gl_FragColor = color;
-}
-```
-
-Но дело в том, что это уже нарисует текстуру с прозрачностью и без использования stencil.
-
-{{{example url="../webgl-qna-how-to-use-a-2d-sprite-s-transparency-as-a-mask-example-1.html"}}}
-
-Если же вы действительно хотите использовать stencil, теперь, когда код отбрасывает некоторые пиксели, всё должно работать, и ваш код был верным. Обратите внимание, что код ниже не очищает stencil, потому что он по умолчанию очищается каждый кадр
-
-{{{example url="../webgl-qna-how-to-use-a-2d-sprite-s-transparency-as-a-mask-example-2.html"}}}
-
-Также отмечу, что это, вероятно, лучше делать с помощью альфа-смешивания, передавая обе текстуры в один шейдер и передавая ещё одну матрицу или другие uniforms, чтобы применить альфу одной текстуры к другой. Это будет более гибко, так как можно смешивать по всем значениям от 0 до 1, тогда как с помощью stencil можно только маскировать 0 или 1.
-
-Я не говорю "не используйте stencil", а лишь отмечаю, что бывают ситуации, когда это лучший вариант, и когда — нет. Только вы можете решить, какой способ лучше для вашей задачи.
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
Evan Wrynn
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-use-textures-as-data.md b/webgl/lessons/ru/webgl-qna-how-to-use-textures-as-data.md
deleted file mode 100644
index 09ca2727e..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-use-textures-as-data.md
+++ /dev/null
@@ -1,183 +0,0 @@
-Title: Как использовать текстуры как данные
-Description: Как использовать текстуры как данные
-TOC: Как использовать текстуры как данные
-
-## Вопрос:
-
-Я изучал уроки по WebGL, такие как [webglfundamentals](https://webglfundamentals.org/), и столкнулся с проблемой — мне кажется, что мне нужно использовать текстуру, которую я создам, чтобы передавать информацию напрямую во фрагментный шейдер, но у меня не получается правильно индексировать текстуру.
-
-Цель — передать информацию об источниках света (местоположение и цвет), которая будет учитываться при расчёте цвета фрагмента. В идеале эта информация должна быть динамической как по значению, так и по длине.
-
-## Воспроизведение
-Я создал упрощённую версию проблемы в этом fiddle: [WebGL - Data Texture Testing](https://jsfiddle.net/oclyke/muf0deoL/86/)
-
-Вот часть кода.
-
-В **одноразовой инициализации** мы создаём текстуру, заполняем её данными и применяем, как кажется, самые надёжные настройки (без mips, без проблем с упаковкой байтов[?])
-```
- // lookup uniforms
- var textureLocation = gl.getUniformLocation(program, "u_texture");
-
- // Create a texture.
- var texture = gl.createTexture();
- gl.bindTexture(gl.TEXTURE_2D, texture);
-
- // fill texture with 1x3 pixels
- const level = 0;
- const internalFormat = gl.RGBA; // Я также пробовал gl.LUMINANCE
- // но это сложнее отлаживать
- const width = 1;
- const height = 3;
- const border = 0;
- const type = gl.UNSIGNED_BYTE;
- const data = new Uint8Array([
- // R, G, B, A (не используется) // : индекс 'texel' (?)
- 64, 0, 0, 0, // : 0
- 0, 128, 0, 0, // : 1
- 0, 0, 255, 0, // : 2
- ]);
- const alignment = 1; // для этой текстуры не обязательно, но
- gl.pixelStorei(gl.UNPACK_ALIGNMENT, alignment); // думаю, это не мешает
- gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, width, height, border,
- internalFormat, type, data);
-
- // set the filtering so we don't need mips and it's not filtered
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
-```
-
-В **отрисовке** (которая происходит только один раз, но теоретически может повторяться) мы явно указываем программе использовать нашу текстуру
-```
- // Сказать шейдеру использовать текстурный юнит 0 для u_texture
- gl.activeTexture(gl.TEXTURE0); // добавил эту и следующую строку для уверенности...
- gl.bindTexture(gl.TEXTURE_2D, texture);
- gl.uniform1i(textureLocation, 0);
-```
-
-Наконец, во фрагментном шейдере мы просто пытаемся надёжно использовать один 'texel' для передачи информации. У меня не получается понять, как надёжно получить значения, которые я сохранил в текстуре.
-```
-precision mediump float;
-
-// Текстура.
-uniform sampler2D u_texture;
-
-void main() {
-
- vec4 sample_00 = texture2D(u_texture, vec2(0, 0));
- // Этот сэмпл обычно правильный.
- // Изменение данных для B-канала texel
- // индекса 0, как ожидается, добавляет синий
-
- vec4 sample_01 = texture2D(u_texture, vec2(0, 1));
- vec4 sample_02 = texture2D(u_texture, vec2(0, 2));
- // Эти сэмплы, как я ожидал, должны работать, так как
- // ширина текстуры установлена в 1
- // Почему-то 01 и 02 показывают один и тот же цвет
-
- vec4 sample_10 = texture2D(u_texture, vec2(1, 0));
- vec4 sample_20 = texture2D(u_texture, vec2(2, 0));
- // Эти сэмплы просто для теста — не думаю,
- // что они должны работать
-
- // выбираем, какой сэмпл показать
- vec4 sample = sample_00;
- gl_FragColor = vec4(sample.x, sample.y, sample.z, 1);
-}
-```
-
-## Вопрос(ы)
-
-Является ли использование текстуры лучшим способом для этого? Я слышал, что можно передавать массивы векторов, но текстуры, кажется, более распространены.
-
-Как правильно создавать текстуру? (особенно когда я указываю 'width' и 'height', я должен иметь в виду размеры texel или количество элементов gl.UNSIGNED_BYTE, которые я буду использовать для создания текстуры?? [документация texImage2D](https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glTexImage2D.xml))
-
-Как правильно индексировать текстуру во фрагментном шейдере, если не использовать 'varying' типы? (т.е. я просто хочу получить значение одного или нескольких конкретных texel — без интерполяции [почти не связано с вершинами])
-
-### Другие ресурсы
-Я прочитал всё, что мог на эту тему. Вот не полный список:
-
-* JMI Madison утверждает, что разобрался, но [решение](https://stackoverflow.com/questions/34873832/webgl-fragment-shader-pass-array) утопает в проектном коде
-* [webglfundamentals](https://webglfundamentals.org/) почти подходит — [пример с 3x2 data texture](https://webglfundamentals.org/webgl/lessons/webgl-data-textures.html) — но там используется интерполяция и это не совсем мой случай
-* Вот кто-то обсуждает [попытку использовать массивы vec3](https://community.khronos.org/t/passing-array-of-vec3-to-fragment-shader/74450)
-* И конечно я сравнивал с документацией OpenGL ([texImage2D](https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glTexImage2D.xml) и [texture2d](https://thebookofshaders.com/glossary/?search=texture2D))
-
-**Edit** Вот ещё ресурс: [Hassles with array access in WebGL, and a couple of workarounds](https://www.john-smith.me/hassles-with-array-access-in-webgl-and-a-couple-of-workarounds.html). Обнадёживает.
-
-Это меня очень беспокоит.
-
-Спасибо заранее!
-
-## Ответ:
-
-Обращение к отдельным пикселям текстуры в WebGL1 использует такую формулу
-
-```
-vec2 pixelCoord = vec2(x, y);
-vec2 textureDimension = vec2(textureWidth, textureHeight)
-vec2 texcoord = (pixelCoord + 0.5) / textureDimensions;
-vec4 pixelValue = texture2D(someSamplerUniform, texcoord);
-```
-
-Потому что текстурные координаты считаются по краям. Если у вас текстура 2x1
-
-```
-1.0+-------+-------+
- | | |
- | A | B |
- | | |
-0.0+-------+-------+
- 0.0 0.5 1.0
-```
-
-Текстурная координата в центре пикселя A = 0.25, 0.5. В центре пикселя B = 0.75, 0.5
-
-Если не использовать формулу выше, а просто pixelCoord / textureDimensions, то вы попадёте между пикселями, и из-за ошибок округления получите не тот пиксель.
-
-Конечно, если вы используете текстуры для данных, вам также нужно установить фильтрацию `gl.NEAREST`.
-
-В WebGL2 можно просто использовать `texelFetch`
-
-```
-ivec2 pixelCoord = ivec2(x, y);
-int mipLevel = 0;
-vec4 pixelValue = texelFetch(someSamplerUniform, texcoord, mipLevel);
-```
-
-Рабочий пример использования текстур для данных: [здесь](https://webglfundamentals.org/webgl/lessons/webgl-pulling-vertices.html)
-
-> Является ли использование текстуры лучшим способом для этого? Я слышал, что можно передавать массивы векторов, но текстуры, кажется, более распространены.
-
-Для чего? Не совсем понятно, что вы хотите сделать. Каждый пиксель будет иметь свой источник света?
-
-> Как правильно создавать текстуру? (особенно когда я указываю 'width' и 'height', я должен иметь в виду размеры texel или количество элементов gl.UNSIGNED_BYTE, которые я буду использовать для создания текстуры?? документация texImage2D)
-
-Делайте так, как проще или как требуется. Например, если у вас 5 данных на объект, я бы поместил каждое значение на отдельную строку текстуры. Тогда можно сделать
-
-```
-vec4 datum1 = texture2D(dataTexture, vec2(indexTexCoordX, rowTexCoordY0));
-vec4 datum2 = texture2D(dataTexture, vec2(indexTexCoordX, rowTexCoordY1));
-vec4 datum3 = texture2D(dataTexture, vec2(indexTexCoordX, rowTexCoordY2));
-vec4 datum4 = texture2D(dataTexture, vec2(indexTexCoordX, rowTexCoordY3));
-```
-
-Где indexTexCoordX и rowTexCoordY0-3 вычисляются по формуле выше. rowTexCoordY0-3 могут быть даже константами.
-
-У текстур есть ограничения по размеру, так что если данных больше, чем помещается в одном измерении, придётся упаковывать плотнее и делать больше вычислений для извлечения.
-
-Имейте в виду, что у текстур есть кэш, поэтому желательно, чтобы данные, которые вы извлекаете, были рядом с теми, что вы извлекали до этого. Если вы каждый раз прыгаете по текстуре за следующим значением, производительность упадёт (хотя всё равно может быть быстрее других решений, в зависимости от задачи).
-
-> Как правильно индексировать текстуру во фрагментном шейдере, если не использовать 'varying' типы? (т.е. я просто хочу получить значение одного или нескольких конкретных texel — без интерполяции [почти не связано с вершинами])
-
-Единственные изменяемые входы во фрагментный шейдер — varyings, `gl_FragCoord` (координата пикселя, в который пишется) и `gl_PointCoord`, доступен только при рисовании `POINTS`. Так что нужно использовать один из них, иначе все остальные значения будут одинаковы для всех пикселей.
-
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
oclyke
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-how-to-use-the-stencil-buffer.md b/webgl/lessons/ru/webgl-qna-how-to-use-the-stencil-buffer.md
deleted file mode 100644
index 6cebd840d..000000000
--- a/webgl/lessons/ru/webgl-qna-how-to-use-the-stencil-buffer.md
+++ /dev/null
@@ -1,93 +0,0 @@
-Title: Как использовать stencil buffer
-Description: Как использовать stencil buffer
-TOC: Как использовать stencil buffer
-
-## Вопрос:
-
-Как я могу использовать stencil buffer для самой простой программы?
-Я прочитал много разных тем об этом, но не нашел подробного руководства.
-Я хочу вырезать отверстие на каждой стороне созданного тетраэдра.
-
-[![enter image description here][1]][1]
-
-Пожалуйста, объясните мне пошагово, как использовать stencil buffer?
-
-[Ссылка на мою программу][2]
-
-
- [1]: https://i.stack.imgur.com/yV9oD.png
- [2]: https://dropfiles.ru/filesgroup/62503e88028a16b1055f78a7e2b70456.html
-
-## Ответ:
-
-Чтобы использовать stencil buffer, вы должны сначала запросить его при создании контекста webgl
-
- const gl = someCanvasElement.getContext('webgl', {stencil: true});
-
-
-Затем включите тест трафарета (stencil test)
-
-```
- gl.enable(gl.STENCIL_TEST);
-```
-
-Настройте тест так, чтобы он всегда проходил, и установите опорное значение в 1
-
-```
- gl.stencilFunc(
- gl.ALWAYS, // тест
- 1, // опорное значение
- 0xFF, // маска
- );
-```
-
-И задайте операцию так, чтобы мы устанавливали stencil в опорное значение, когда оба теста (stencil и depth) проходят
-
-```
- gl.stencilOp(
- gl.KEEP, // что делать, если stencil тест не прошёл
- gl.KEEP, // что делать, если depth тест не прошёл
- gl.REPLACE, // что делать, если оба теста прошли
- );
-```
-
-Теперь рисуем первый внутренний треугольник
-
-```
-... много настроек для одного треугольника ...
-
-gl.drawArrays(...) или gl.drawElements(...)
-```
-
-Затем меняем тест так, чтобы он проходил только если stencil равен нулю
-
-```
- gl.stencilFunc(
- gl.EQUAL, // тест
- 0, // опорное значение
- 0xFF, // маска
- );
- gl.stencilOp(
- gl.KEEP, // что делать, если stencil тест не прошёл
- gl.KEEP, // что делать, если depth тест не прошёл
- gl.KEEP, // что делать, если оба теста прошли
- );
-
-```
-
-и теперь мы можем нарисовать что-то ещё (больший треугольник), и оно будет рисоваться только там, где в stencil buffer стоит 0, то есть везде, кроме области, где был нарисован первый треугольник.
-
-Пример:
-
-{{{example url="../webgl-qna-how-to-use-the-stencil-buffer-example-1.html"}}}
-
-
-
-
-
Вопрос и процитированные части являются
- CC BY-SA 4.0 от
-
AnatoliyC
- из
-
здесь
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-i-get-invalid-type-error-when-calling-readpixels.md b/webgl/lessons/ru/webgl-qna-i-get-invalid-type-error-when-calling-readpixels.md
deleted file mode 100644
index 67aa8b6c4..000000000
--- a/webgl/lessons/ru/webgl-qna-i-get-invalid-type-error-when-calling-readpixels.md
+++ /dev/null
@@ -1,43 +0,0 @@
-Title: Ошибка invalid type при вызове readPixels
-Description: Ошибка invalid type при вызове readPixels
-TOC: Ошибка invalid type при вызове readPixels
-
-## Вопрос:
-
- context.readPixels(0, 0, context.drawingBufferWidth, context.drawingBufferHeight, context.RGBA, context.FLOAT, pixels);
-
-Вот этот код. В консоли появляется ошибка:
-**WebGL: INVALID_ENUM: readPixels: invalid type**
-
-Но вот так всё работает нормально:
-
- context.readPixels(0, 0, context.drawingBufferWidth, context.drawingBufferHeight, context.RGBA, context.UNSIGNED_BYTE, pixels);
-
-Float или int вроде как должны поддерживаться, но работает только unsigned_byte.
-В интернете нет ресурсов, как правильно применить тип, который, кажется, должен работать.
-Везде разные примеры.
-
-## Ответ:
-
-FLOAT не гарантирован к поддержке. Единственная комбинация формат/тип, которая гарантированно поддерживается — RGBA/UNSIGNED_BYTE. [См. спецификацию, раздел 4.3.1](https://www.khronos.org/registry/OpenGL/specs/es/2.0/es_full_spec_2.0.pdf)
-
-Кроме этого, одна другая **зависящая от реализации** комбинация формат/тип может поддерживаться в зависимости от того, что вы читаете. Её можно узнать так:
-
-```
-const altFormat = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT);
-const altType = gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE);
-```
-
-{{{example url="../webgl-qna-i-get-invalid-type-error-when-calling-readpixels-example-1.html"}}}
-
-Код выше создаёт текстуру RGBA/FLOAT, прикрепляет её к framebuffer, а затем проверяет альтернативную комбинацию формат/тип для чтения. В Chrome это RGBA/UNSIGNED_BYTE, в Firefox — RGBA/FLOAT. Оба варианта валидны, так как альтернативная комбинация **зависит от реализации**.
-
-
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-show-a-night-view-vs-a-day-view-on-a-3d-earth-sphere.md b/webgl/lessons/ru/webgl-qna-show-a-night-view-vs-a-day-view-on-a-3d-earth-sphere.md
deleted file mode 100644
index f51c169f5..000000000
--- a/webgl/lessons/ru/webgl-qna-show-a-night-view-vs-a-day-view-on-a-3d-earth-sphere.md
+++ /dev/null
@@ -1,235 +0,0 @@
-Title: Показать ночной вид против дневного вида на 3D сфере Земли
-Description: Показать ночной вид против дневного вида на 3D сфере Земли
-TOC: Показать ночной вид против дневного вида на 3D сфере Земли
-
-## Вопрос:
-
-Я использую Three.js как фреймворк для разработки космического симулятора и пытаюсь, но не могу заставить работать ночные огни.
-
-Симулятор доступен здесь:
-
-[orbitingeden.com][1]
-
-и страница, запускающая фрагмент кода ниже, находится здесь:
-
-[orbitingeden.com/orrery/soloearth.html][2]
-
-Код для примера страницы здесь. Я даже не знаю, с чего начать. Я пытался рендерить два глобуса на несколько единиц друг от друга, один ближе к солнцу (дневная версия) и один дальше (ночная версия), но есть много проблем, не в последнюю очередь то, что они начинают перекрывать друг друга странными додекаэдрическими способами. Я принял идею tDiffuse2 из этого [оррери][3], но не смог заставить её работать.
-
-
-
-
- three.js webgl - earth
-
-
-
-
-
-
-
-
-
-
- [1]: http://orbitingeden.com
- [2]: http://orbitingeden.com/orrery/soloearth.html
- [3]: http://www.esfandiarmaghsoudi.com/Apps/SolarSystem/
-
-## Ответ:
-
-Если я правильно понимаю ваш вопрос....
-
-Я не знаю three.js, но в общем я бы сделал это, имея шейдер, которому передаются и дневная, и ночная текстуры, а затем выбирая одну или другую в шейдере. Например:
-
- uniform sampler2D dayTexture;
- uniform sampler2D nightTexture;
- varying vec3 v_surfaceToLight; // assumes this gets passed in from vertex shader
- varying vec4 v_normal; // assumes this gets passed in from vertex shader
- varying vec2 v_texCoord; // assumes this gets passed in from vertex shader
-
- void main () {
- vec3 normal = normalize(v_normal);
- vec3 surfaceToLight = normalize(v_surfaceToLight);
- float angle = dot(normal, surfaceToLight);
- vec4 dayColor = texture2D(dayTexture, v_texCoords);
- vec4 nightColor = texture2D(nightTexture, v_texCoord);
- vec4 color = angle < 0.0 ? dayColor : nightColor;
-
- ...
-
- gl_FragColor = color * ...;
- }
-
-В основном вы берёте расчёт освещения и вместо использования его для освещения используете его для выбора текстуры. Расчёт освещения обычно использует скалярное произведение между нормалью поверхности и направлением света (солнца) от поверхности. Это даёт вам косинус угла между этими двумя векторами. Косинус идёт от -1 до 1, так что если значение от -1 до 0, это обращено от солнца, если от 0 до +1, это обращено к солнцу.
-
-Строка
-
- vec4 color = angle < 0.0 ? dayColor : nightColor;
-
-выбирает день или ночь. Это будет резкий переход. Вы можете поэкспериментировать с чем-то более размытым, например:
-
-
- // convert from -1 <-> +1 to 0 <-> +1
- float lerp0To1 = angle * 0.5 + 0.5;
-
- // mix between night and day
- vec4 color = mix(nightColor, dayColor, lerp0to1);
-
-
-Это даст вам 100% день в точке, прямо обращённой к солнцу, и 100% ночь в точке, прямо противоположной солнцу, и смесь между ними. Вероятно, не то, что вы хотите, но вы можете поиграть с числами. Например:
-
- // sharpen the mix
- angle = clamp(angle * 10.0, -1.0, 1.0);
-
- // convert from -1 <-> +1 to 0 <-> +1
- float lerp0To1 = angle * 0.5 + 0.5;
-
- // mix between night and day
- vec4 color = mix(nightColor, dayColor, lerp0to1);
-
-
-Надеюсь, это имело смысл.
-
-----
-
-Так что я потратил немного времени на создание примера Three.js, отчасти чтобы изучить Three.js. Пример здесь.
-
-{{{example url="../webgl-qna-show-a-night-view-vs-a-day-view-on-a-3d-earth-sphere-example-1.html"}}}
-
-Шейдер, который я использовал, это:
-
- uniform sampler2D dayTexture;
- uniform sampler2D nightTexture;
-
- uniform vec3 sunDirection;
-
- varying vec2 vUv;
- varying vec3 vNormal;
-
- void main( void ) {
- vec3 dayColor = texture2D( dayTexture, vUv ).rgb;
- vec3 nightColor = texture2D( nightTexture, vUv ).rgb;
-
- // compute cosine sun to normal so -1 is away from sun and +1 is toward sun.
- float cosineAngleSunToNormal = dot(normalize(vNormal), sunDirection);
-
- // sharpen the edge beween the transition
- cosineAngleSunToNormal = clamp( cosineAngleSunToNormal * 10.0, -1.0, 1.0);
-
- // convert to 0 to 1 for mixing
- float mixAmount = cosineAngleSunToNormal * 0.5 + 0.5;
-
- // Select day or night texture based on mixAmount.
- vec3 color = mix( nightColor, dayColor, mixAmount );
-
- gl_FragColor = vec4( color, 1.0 );
-
- // comment in the next line to see the mixAmount
- //gl_FragColor = vec4( mixAmount, mixAmount, mixAmount, 1.0 );
- }
-
-Большая разница от предыдущего в том, что поскольку солнце обычно считается направленным источником света, поскольку оно так далеко, то всё, что вам нужно — это его направление. Другими словами, в какую сторону оно указывает относительно земли.
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-the-fastest-way-to-draw-many-circles.md b/webgl/lessons/ru/webgl-qna-the-fastest-way-to-draw-many-circles.md
deleted file mode 100644
index dd68944cb..000000000
--- a/webgl/lessons/ru/webgl-qna-the-fastest-way-to-draw-many-circles.md
+++ /dev/null
@@ -1,76 +0,0 @@
-Title: Самый быстрый способ нарисовать много кругов
-Description: Самый быстрый способ нарисовать много кругов
-TOC: Самый быстрый способ нарисовать много кругов
-
-## Вопрос:
-
-Сейчас я рисую тысячи кругов, инстанцируя [геометрию круга](https://threejs.org/docs/#api/en/geometries/CircleBufferGeometry) (много треугольников).
-
-[![enter image description here][1]][1]
-
-Альтернативно, я мог бы просто инстанцировать quad (2 треугольника), а круг "вырезать" во фрагментном шейдере с помощью distance-функции и `discard`.
-
-[![enter image description here][2]][2]
-
-Какой подход будет быстрее? — дороже ли рисовать много треугольников, чем вычисления во фрагментном шейдере?
-
- [1]: https://i.stack.imgur.com/CZhOa.png
- [2]: https://i.stack.imgur.com/BG4p5.png
-
-## Ответ:
-
-Самый быстрый способ может зависеть от GPU и множества других факторов: как вы рисуете круги (2D, 3D), используете ли смешивание (blending), z-buffer и т.д. Но в целом — меньше треугольников быстрее, чем больше, и меньше пикселей быстрее, чем больше. Так что... всё, что мы можем — это попробовать.
-
-Сначала просто нарисуем текстурированные квадраты без blending. У меня на GPU в этом 300x150 canvas с инстансингом получается 20-30 тысяч quad'ов на 60fps.
-
-{{{example url="../webgl-qna-the-fastest-way-to-draw-many-circles-example-1.html"}}}
-
-И такая же производительность при 60fps, если использовать повторяющуюся геометрию вместо инстансинга. Это удивительно, потому что 7-8 лет назад повторяющаяся геометрия была на 20-30% быстрее. Почему — не знаю: лучше GPU, драйвер или что-то ещё.
-
-{{{example url="../webgl-qna-the-fastest-way-to-draw-many-circles-example-2.html"}}}
-
-Дальше — текстуры или вычисление круга во фрагментном шейдере.
-
-{{{example url="../webgl-qna-the-fastest-way-to-draw-many-circles-example-3.html"}}}
-
-Разницы не заметил. Пробую вашу функцию круга:
-
-{{{example url="../webgl-qna-the-fastest-way-to-draw-many-circles-example-4.html"}}}
-
-Опять разницы не заметил. Замечу: как уже говорил, результаты WebGL очень нестабильны. Первый тест — 28k на 60fps, второй — 23k. Ожидал, что второй будет быстрее, но потом снова первый — 23k, последний — 29k, потом предыдущий — 29k. Короче, тестировать производительность в WebGL почти невозможно. Слишком много факторов, всё многопроцессное, получить стабильные результаты невозможно.
-
-Можно попробовать discard:
-
-{{{example url="../webgl-qna-the-fastest-way-to-draw-many-circles-example-5.html"}}}
-
-Судя по ощущениям, discard медленнее. Насколько помню, discard медленный, потому что без него GPU знает заранее, что обновит z-buffer, а с discard — только после выполнения шейдера, и это мешает оптимизациям.
-
-На этом остановлюсь, потому что слишком много комбинаций для тестов.
-
-Можно попробовать включить blending. Blending обычно медленнее, потому что нужно смешивать (читать фон), но медленнее ли, чем discard — не знаю.
-
-Включён ли depth test? Если да, порядок отрисовки важен.
-
-Можно попробовать не-quads, а, например, шестиугольники или восьмиугольники — тогда меньше пикселей попадёт во фрагментный шейдер. Думаю, разницу увидите только на больших кругах. Например, quad 100x100 — это 10k пикселей, идеальный круг — pi*r^2 ≈ 7853, то есть на 21% меньше. Шестиугольник — ~8740 пикселей, на 11% меньше. Восьмиугольник — где-то между. Рисовать на 11-21% меньше пикселей — обычно плюс, но для шестиугольника будет в 3 раза больше треугольников, для восьмиугольника — в 4 раза. Всё надо тестировать.
-
-Это ещё раз показывает, что для больших кругов на большом canvas относительные результаты будут другими: больше пикселей на круг, больше времени на пиксели, меньше — на вершины и/или переключения GPU.
-
-## Обновление
-
-Тесты в Chrome и Firefox: в Chrome на той же машине 60-66k во всех случаях. Почему разница такая большая — не знаю, ведь WebGL почти ничего не делает. Все 4 теста — по одному draw call на кадр. Но, по крайней мере, на 2019-10 Chrome в этом случае в 2 раза быстрее Firefox.
-
-Есть идея: у меня ноутбук с двумя GPU. При создании контекста можно указать, что вы хотите, через атрибут `powerPreference`:
-
- const gl = document.createContext('webgl', {
- powerPreference: 'high-performance',
- });
-
-Варианты: 'default', 'low-power', 'high-performance'. 'default' — "пусть браузер решает", но в итоге всё равно решает браузер. В любом случае, у меня в Firefox это ничего не меняет.
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-webgl-2d-tilemaps.md b/webgl/lessons/ru/webgl-qna-webgl-2d-tilemaps.md
deleted file mode 100644
index 2bae37212..000000000
--- a/webgl/lessons/ru/webgl-qna-webgl-2d-tilemaps.md
+++ /dev/null
@@ -1,42 +0,0 @@
-Title: WebGL 2D тайлмапы
-Description: WebGL 2D тайлмапы
-TOC: WebGL 2D тайлмапы
-
-## Вопрос:
-
-Я создаю простую 2D веб-игру с типичной тайл-картой и спрайтами.
-
-Особенность в том, что мне нужны плавные элементы управления камерой — как перемещение, так и масштабирование (зум).
-
-Я пробовал и Canvas 2D API, и WebGL, но в обоих случаях не могу избежать артефактов сетки (bleeding grid line artifacts), при этом поддерживая правильное масштабирование.
-
-Если это важно: все мои тайлы размером 1, масштабируются до нужного размера, все координаты — целые числа, и я использую текстуру-атлас.
-
-Вот пример картинки с моим WebGL-кодом, где тонкие красные/белые линии нежелательны:
-[![enter image description here][1]][1]
-
- [1]: https://i.stack.imgur.com/ZziiR.png
-
-Я помню, как писал спрайтовые тайлмапы годами назад на desktop GL, иронично используя похожий код (более или менее эквивалентный тому, что можно сделать с WebGL 2), и там никогда не было таких проблем.
-
-Сейчас думаю попробовать DOM-элементы, но боюсь, что это не будет выглядеть или работать плавно.
-
-## Ответ:
-
-Одно из решений — рисовать тайлы во фрагментном шейдере.
-
-У вас есть карта, скажем, `Uint32Array`. Разбиваете её на единицы по 4 байта каждая. Первые 2 байта — ID тайла, последний байт — флаги.
-
-Когда вы проходите по quad'у для каждого пикселя, вы смотрите в текстуру тайлмапы, какой это тайл, затем используете это для вычисления UV-координат, чтобы получить пиксели этого тайла из текстуры тайлов. Если у вашей текстуры тайлов установлена выборка gl.NEAREST, то bleeding никогда не будет.
-
-Заметьте, что в отличие от традиционных тайлмапов, ID каждого тайла — это X,Y-координата тайла в текстуре тайлов. Другими словами, если ваша текстура тайлов имеет 16x8 тайлов, и вы хотите показать тайл на позиции 7 по горизонтали и 4 по вертикали, то ID этого тайла — 7,4 (первый байт 7, второй байт 4), тогда как в традиционной CPU-системе ID тайла был бы, вероятно, 4*16+7 или 71 (71-й тайл). Можно добавить код в шейдер для более традиционной индексации, но поскольку шейдер всё равно должен конвертировать ID в 2D-координаты текстуры, проще использовать 2D-ID.
-
-{{{example url="../webgl-qna-webgl-2d-tilemaps-example-1.html"}}}
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-webgl-droste-effect.md b/webgl/lessons/ru/webgl-qna-webgl-droste-effect.md
deleted file mode 100644
index 4e2c9dcef..000000000
--- a/webgl/lessons/ru/webgl-qna-webgl-droste-effect.md
+++ /dev/null
@@ -1,90 +0,0 @@
-Title: Эффект Дросте в WebGL
-Description: Эффект Дросте в WebGL
-TOC: Эффект Дросте в WebGL
-
-## Вопрос:
-
-Я пытаюсь использовать WebGL для создания [эффекта Дросте](https://en.wikipedia.org/wiki/Droste_effect) на гранях куба. В viewport'е один меш — куб, и все его грани используют одну текстуру. Для создания эффекта Дросте я обновляю текстуру на каждом кадре, делая снимок `canvas`, в WebGL-контекст которого рисую, что со временем даёт эффект Дросте, так как снимок содержит всё больше и больше вложенных прошлых кадров.
-
-Демо того, что у меня сейчас работает, здесь:
-
-https://tomashubelbauer.github.io/webgl-op-1/?cubeTextured
-
-Код выглядит так:
-
-```
-// Set up fragment and vertex shader and attach them to a program, link the program
-// Create a vertex buffer, an index buffer and a texture coordinate buffer
-// Tesselate the cube's vertices and fill in the index and texture coordinate buffers
-const textureCanvas = document.createElement('canvas');
-textureCanvas.width = 256;
-textureCanvas.height = 256;
-const textureContext = textureCanvas.getContext('2d');
-
-// In every `requestAnimationFrame`:
-textureContext.drawImage(context.canvas, 0, 0);
-const texture = context.createTexture();
-context.bindTexture(context.TEXTURE_2D, texture);
-context.texImage2D(context.TEXTURE_2D, 0, context.RGBA, context.RGBA, context.UNSIGNED_BYTE, textureCanvas);
-context.generateMipmap(context.TEXTURE_2D);
-// Clear the viewport completely (depth and color buffers)
-// Set up attribute and uniform values, the projection and model view matrices
-context.activeTexture(context.TEXTURE0);
-context.bindTexture(context.TEXTURE_2D, texture);
-context.uniform1i(fragmentShaderTextureSamplerUniformLocation, 0);
-context.drawElements(context.TRIANGLES, 36, context.UNSIGNED_SHORT, 0)
-```
-
-Это основная часть. Есть отдельный canvas от WebGL, на который рисуется WebGL canvas перед каждым WebGL кадром, и этот canvas используется для создания текстуры для данного кадра, которая применяется к граням куба согласно буферу координат текстуры и uniform'у сэмплера текстуры, переданному во фрагментный шейдер, который просто использует `gl_FragColor = texture2D(textureSampler, textureCoordinate)`, как и ожидается.
-
-Но это очень медленно (30 FPS на этом простом демо с одним кубом, тогда как все мои другие демо с порядком больше треугольников всё ещё держат 60 FPS `requestAnimationFrame`).
-
-Также странно делать это "вне" WebGL, используя внешний canvas, когда кажется, что это должно быть достижимо только с помощью WebGL.
-
-Я знаю, что WebGL держит два буфера — один для активного кадра и back buffer для недавно нарисованного кадра, и они меняются местами с каждым кадром для мгновенного обновления экрана. Можно ли получить доступ к этому back buffer и использовать его как текстуру? Можете ли вы предоставить пример кода, как это сделать?
-
-## Ответ:
-
-Из [этой статьи](https://webglfundamentals.org/webgl/lessons/webgl-render-to-texture.html)
-
-Обычный способ — рендерить в текстуру, прикрепив её к framebuffer.
-
-```
-const fb = gl.createFramebuffer();
-gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
-gl.framebufferTexture2D(
- gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0 /* level */)
-```
-
-Теперь для рендеринга в текстуру:
-
-```
-gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
-gl.viewport(0, 0, textureWidth, textureHeight);
-```
-
-Для рендеринга в canvas:
-
-```
-gl.bindFramebuffer(gl.FRAMEBUFFER, null);
-gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
-```
-
-Для того, что вы хотите, нужны 2 текстуры, поскольку нельзя одновременно читать из и писать в одну текстуру, так что вы рисуете:
-
-* Рисуете изображение в TextureA
-* Рисуете предыдущий кадр (TextureB) в TextureA
-* Рисуете куб с TextureA в TextureB
-* Рисуете TextureB в Canvas
-
-{{{example url="../webgl-qna-webgl-droste-effect-example-1.html"}}}
-
-Что касается canvas и его 2 буферов — нет, нельзя напрямую использовать их как текстуры. Можно вызвать `gl.copyTexImage2D` или `gl.copyTexSubImage2D` для копирования части canvas в текстуру, так что это другое решение. Оно менее гибкое и, думаю, медленнее, чем метод framebuffer.
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-what-is-the-local-origin-of-a-3d-model-.md b/webgl/lessons/ru/webgl-qna-what-is-the-local-origin-of-a-3d-model-.md
deleted file mode 100644
index d88c9508e..000000000
--- a/webgl/lessons/ru/webgl-qna-what-is-the-local-origin-of-a-3d-model-.md
+++ /dev/null
@@ -1,565 +0,0 @@
-Title: Что такое локальное начало координат 3D модели?
-Description: Что такое локальное начало координат 3D модели?
-TOC: Что такое локальное начало координат 3D модели?
-
-## Вопрос:
-
-В этом коде я не могу понять, почему вращение не происходит по круговой траектории. Это может быть базовая логика, но я не понимаю, почему траектория такая случайная.
-
-Я вращаю камеру по орбитальному движению, но она не следует за ним.
-
-Насколько я понимаю, я создал орбитальную камеру, и обратная к ней — это матрица вида. Значит, матрица вида будет трансформировать мировое пространство для этого результата. Есть ли ошибка в моём мышлении?
-
-
-
-
-
- "use strict";
-
- const vertexShader = `#version 300 es
-
- in vec4 a_position;
- in vec4 a_color;
-
- out vec4 v_color;
-
- uniform mat4 u_matrix;
-
- void main(){
- gl_Position = u_matrix*a_position;
- v_color = a_color;
- }
- `;
-
-
- const fragShader = `#version 300 es
-
- precision highp float;
-
- in vec4 v_color;
- out vec4 frag_color;
-
- void main(){
- frag_color = v_color;
- }
- `;
-
- var cameraAngleDegree = 0;
- var cameraAngle = 0;
- const radius = 100;
- var increment = 1;
- var numFs = 5;
- function main() {
-
- var canvas = document.querySelector("#canvas");
- var gl = canvas.getContext("webgl2");
- if (!gl) {
- return;
- }
- requestAnimationFrame(function() {
- init(gl);
- });
- }
-
- function init(gl) {
-
-
- const program = webglUtils.createProgramFromSources(gl, [vertexShader, fragShader]);
-
- const apositionLoc = gl.getAttribLocation(program, 'a_position');
- const acolorLoc = gl.getAttribLocation(program, 'a_color');
- const umatrixLoc = gl.getUniformLocation(program, 'u_matrix');
-
- let vao = gl.createVertexArray();
- gl.bindVertexArray(vao);
-
- let positionBuffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
- setGeometry(gl);
- gl.enableVertexAttribArray(apositionLoc);
-
- let size = 3;
- let type = gl.FLOAT;
- let normalize = false;
- let stride = 0;
- let offset = 0;
- gl.vertexAttribPointer(apositionLoc, size, type, normalize, stride, offset);
-
- let colorBuffer = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
- setColor(gl);
- gl.enableVertexAttribArray(acolorLoc);
-
- size = 3;
- type = gl.UNSIGNED_BYTE;
- normalize = true;
- stride = 0;
- offset = 0;
- gl.vertexAttribPointer(acolorLoc, size, type, normalize, stride, offset);
-
- let fov = degreeToRadian(60);
- cameraAngle = degreeToRadian(cameraAngleDegree);
-
- function degreeToRadian(deg) {
- return deg * Math.PI / 180;
- }
-
- function radToDegree(rad) {
- return rad * (180) / Math.PI;
- }
-
- drawScene();
-
- // webglLessonsUI.setupSlider("#cameraAngle", { value: radToDegree(cameraAngle), slide: updateCameraAngle, min: -360, max: 360 });
-
- // function updateCameraAngle(event, ui) {
- // cameraAngle = degreeToRadian(ui.value);
- // drawScene();
- // }
-
-
- function drawScene() {
-
- webglUtils.resizeCanvasToDisplaySize(gl.canvas);
-
- gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
-
- gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
-
- gl.enable(gl.CULL_FACE);
-
- gl.enable(gl.DEPTH_TEST);
-
- gl.useProgram(program);
-
- let aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
-
- let projection = m4.perspective(fov, aspect, 1, 1000);
-
- const fPosition = [radius, 0, 0];
-
- cameraAngleDegree += increment;
-
- cameraAngle =degreeToRadian(cameraAngleDegree);
-
- let camera = m4.yRotation(cameraAngle);
- camera = m4.translate(camera, 0, 100, 300);
-
- let cameraPosition = [camera[12], camera[13], camera[14]];
-
- // let up = [0, 1, 0];
-
- // camera = m4.lookAt(cameraPosition, fPosition, up);
-
- let viewMatrix = m4.inverse(camera);
-
- let viewProjection = m4.multiply(projection, viewMatrix);
-
- for (var ii = 0; ii < numFs; ++ii) {
- var angle = ii * Math.PI * 2 / numFs;
-
- var x = Math.cos(angle) * radius - 50;
- var z = Math.sin(angle) * radius - 15;
- var matrix = m4.translate(viewProjection, x, 0, z);
-
- // Set the matrix.
- gl.uniformMatrix4fv(umatrixLoc, false, matrix);
-
- // Draw the geometry.
- var primitiveType = gl.TRIANGLES;
- var offset = 0;
- var count = 16 * 6;
- gl.drawArrays(primitiveType, offset, count);
- }
- // gl.uniformMatrix4fv(umatrixLoc, false, viewProjection);
-
- // var primitives = gl.TRIANGLES;
- // var count = 16 * 6;
- // var offset = 0;
- // gl.drawArrays(primitives, offset, count);
-
- // }
-
- requestAnimationFrame(function() {
- init(gl)
- });
-
- }
- }
-
- function setGeometry(gl) {
-
- let positions = new Float32Array([
-
- 0, 0, 0,
- 0, 150, 0,
- 30, 0, 0,
- 0, 150, 0,
- 30, 150, 0,
- 30, 0, 0,
-
- // top rung front
- 30, 0, 0,
- 30, 30, 0,
- 100, 0, 0,
- 30, 30, 0,
- 100, 30, 0,
- 100, 0, 0,
-
- // middle rung front
- 30, 60, 0,
- 30, 90, 0,
- 67, 60, 0,
- 30, 90, 0,
- 67, 90, 0,
- 67, 60, 0,
-
- // left column back
- 0, 0, 30,
- 30, 0, 30,
- 0, 150, 30,
- 0, 150, 30,
- 30, 0, 30,
- 30, 150, 30,
-
- // top rung back
- 30, 0, 30,
- 100, 0, 30,
- 30, 30, 30,
- 30, 30, 30,
- 100, 0, 30,
- 100, 30, 30,
-
- // middle rung back
- 30, 60, 30,
- 67, 60, 30,
- 30, 90, 30,
- 30, 90, 30,
- 67, 60, 30,
- 67, 90, 30,
-
- // top
- 0, 0, 0,
- 100, 0, 0,
- 100, 0, 30,
- 0, 0, 0,
- 100, 0, 30,
- 0, 0, 30,
-
- // top rung right
- 100, 0, 0,
- 100, 30, 0,
- 100, 30, 30,
- 100, 0, 0,
- 100, 30, 30,
- 100, 0, 30,
-
- // under top rung
- 30, 30, 0,
- 30, 30, 30,
- 100, 30, 30,
- 30, 30, 0,
- 100, 30, 30,
- 100, 30, 0,
-
- // between top rung and middle
- 30, 30, 0,
- 30, 60, 30,
- 30, 30, 30,
- 30, 30, 0,
- 30, 60, 0,
- 30, 60, 30,
-
- // top of middle rung
- 30, 60, 0,
- 67, 60, 30,
- 30, 60, 30,
- 30, 60, 0,
- 67, 60, 0,
- 67, 60, 30,
-
- // right of middle rung
- 67, 60, 0,
- 67, 90, 30,
- 67, 60, 30,
- 67, 60, 0,
- 67, 90, 0,
- 67, 90, 30,
-
- // bottom of middle rung.
- 30, 90, 0,
- 30, 90, 30,
- 67, 90, 30,
- 30, 90, 0,
- 67, 90, 30,
- 67, 90, 0,
-
- // right of bottom
- 30, 90, 0,
- 30, 150, 30,
- 30, 90, 30,
- 30, 90, 0,
- 30, 150, 0,
- 30, 150, 30,
-
- // bottom
- 0, 150, 0,
- 0, 150, 30,
- 30, 150, 30,
- 0, 150, 0,
- 30, 150, 30,
- 30, 150, 0,
-
- // left side
- 0, 0, 0,
- 0, 0, 30,
- 0, 150, 30,
- 0, 0, 0,
- 0, 150, 30,
- 0, 150, 0,
-
- ]);
-
- gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW)
- }
-
- function setColor(gl) {
- gl.bufferData(
- gl.ARRAY_BUFFER,
- new Uint8Array([
- // left column front
- 200, 70, 120,
- 200, 70, 120,
- 200, 70, 120,
- 200, 70, 120,
- 200, 70, 120,
- 200, 70, 120,
-
- // top rung front
- 200, 70, 120,
- 200, 70, 120,
- 200, 70, 120,
- 200, 70, 120,
- 200, 70, 120,
- 200, 70, 120,
-
- // middle rung front
- 200, 70, 120,
- 200, 70, 120,
- 200, 70, 120,
- 200, 70, 120,
- 200, 70, 120,
- 200, 70, 120,
-
- // left column back
- 80, 70, 200,
- 80, 70, 200,
- 80, 70, 200,
- 80, 70, 200,
- 80, 70, 200,
- 80, 70, 200,
-
- // top rung back
- 80, 70, 200,
- 80, 70, 200,
- 80, 70, 200,
- 80, 70, 200,
- 80, 70, 200,
- 80, 70, 200,
-
- // middle rung back
- 80, 70, 200,
- 80, 70, 200,
- 80, 70, 200,
- 80, 70, 200,
- 80, 70, 200,
- 80, 70, 200,
-
- // top
- 70, 200, 210,
- 70, 200, 210,
- 70, 200, 210,
- 70, 200, 210,
- 70, 200, 210,
- 70, 200, 210,
-
- // top rung right
- 200, 200, 70,
- 200, 200, 70,
- 200, 200, 70,
- 200, 200, 70,
- 200, 200, 70,
- 200, 200, 70,
-
- // under top rung
- 210, 100, 70,
- 210, 100, 70,
- 210, 100, 70,
- 210, 100, 70,
- 210, 100, 70,
- 210, 100, 70,
-
- // between top rung and middle
- 210, 160, 70,
- 210, 160, 70,
- 210, 160, 70,
- 210, 160, 70,
- 210, 160, 70,
- 210, 160, 70,
-
- // top of middle rung
- 70, 180, 210,
- 70, 180, 210,
- 70, 180, 210,
- 70, 180, 210,
- 70, 180, 210,
- 70, 180, 210,
-
- // right of middle rung
- 100, 70, 210,
- 100, 70, 210,
- 100, 70, 210,
- 100, 70, 210,
- 100, 70, 210,
- 100, 70, 210,
-
- // bottom of middle rung.
- 76, 210, 100,
- 76, 210, 100,
- 76, 210, 100,
- 76, 210, 100,
- 76, 210, 100,
- 76, 210, 100,
-
- // right of bottom
- 140, 210, 80,
- 140, 210, 80,
- 140, 210, 80,
- 140, 210, 80,
- 140, 210, 80,
- 140, 210, 80,
-
- // bottom
- 90, 130, 110,
- 90, 130, 110,
- 90, 130, 110,
- 90, 130, 110,
- 90, 130, 110,
- 90, 130, 110,
-
- // left side
- 160, 160, 220,
- 160, 160, 220,
- 160, 160, 220,
- 160, 160, 220,
- 160, 160, 220,
- 160, 160, 220,
- ]),
- gl.STATIC_DRAW);
- }
-
- main();
-
-
-
-
-
-
-
- Traingle Webgl 2
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-## Ответ:
-
-Если я правильно понимаю ваш вопрос, проблема в том, что кажется, будто камера то приближается к F, то удаляется от них.
-
-Проблема в том, что данные вершин для F построены так, что верхний левый передний угол находится в 0,0,0, оттуда они идут +X на 100 единиц (ширина 100 единиц), +Y на 150 единиц (высота 150 единиц), и +Z на 30 единиц (глубина 30 единиц).
-
-Поэтому когда вы рисуете их вокруг круга радиусом 100 единиц, их начало координат — это та часть, которую вы позиционируете, и получается это:
-
-[![enter image description here][1]][1]
-
-Изображение сверху, так что F — это просто прямоугольники. Зелёный круг — это локальное начало координат каждой F, её локальные 0,0,0. Другие вершины F относительно этого локального начала, поэтому они ближе к внешнему кругу (орбите камеры) с одной стороны и дальше с другой.
-
-Можно исправить, сдвинув F на -50 по X и -15 по Z. Другими словами:
-
-```
- var angle = ii * Math.PI * 2 / numFs;
-
- var x = Math.cos(angle) * radius - 50;
- var z = Math.sin(angle) * radius - 15;
-```
-
-Что даёт такую ситуацию:
-
-[![enter image description here][2]][2]
-
-Локальное начало координат каждой F больше не на круге.
-
-Также можно исправить, центрировав данные вершин F — пройти по всем вершинам и вычесть 50 из X и 15 из Z. Это даст такую ситуацию:
-
-[![enter image description here][3]][3]
-
-Теперь начало координат каждой F центрировано, и её локальное начало на круге.
-
-Ещё один способ исправить — вычислить экстенты группы F, вычислить центр экстентов, переместить центр орбиты камеры туда, что даст такую ситуацию:
-
-[![enter image description here][4]][4]
-
- [1]: https://i.stack.imgur.com/bW9Xo.png
- [2]: https://i.stack.imgur.com/CejYi.png
- [3]: https://i.stack.imgur.com/MiQ6Z.png
- [4]: https://i.stack.imgur.com/oysVQ.png
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-when-to-choose-highp--mediump--lowp-in-shaders.md b/webgl/lessons/ru/webgl-qna-when-to-choose-highp--mediump--lowp-in-shaders.md
deleted file mode 100644
index 10a1b388b..000000000
--- a/webgl/lessons/ru/webgl-qna-when-to-choose-highp--mediump--lowp-in-shaders.md
+++ /dev/null
@@ -1,65 +0,0 @@
-Title: Когда выбирать highp, mediump, lowp в шейдерах
-Description: Когда выбирать highp, mediump, lowp в шейдерах
-TOC: Когда выбирать highp, mediump, lowp в шейдерах
-
-## Вопрос:
-
-Какие лучшие практики для них? Есть ли разница в производительности?
-
-## Ответ:
-
-> Какие лучшие практики для них?
-
-В основном это важно только на мобильных устройствах. Спецификация говорит, что реализация может всегда использовать более высокую точность, поэтому на десктопе и вершинный шейдер, и фрагментный шейдер всегда работают в highp. (Я не знаю десктопных GPU, для которых это не так)
-
-Из [спецификации](https://www.khronos.org/files/opengles_shading_language.pdf) раздел 4.5.2:
-
-> ## 4.5.2 Precision Qualifiers
->
-> ...
->
-> Precision qualifiers declare a minimum range and precision that the underlying implementation must use
-> when storing these variables. Implementations may use greater range and precision than requested, but
-> not less.
-
-Для мобильных устройств и планшетов есть несколько ответов. Нет лучшего. Это зависит от вас:
-
-1. используйте самую низкую точность, которую можете, но которая всё ещё делает то, что вам нужно.
-
-2. используйте highp и игнорируйте проблемы производительности и старые телефоны, где это не работает
-
-3. используйте mediump и игнорируйте баги (см. ниже)
-
-4. проверьте, поддерживает ли устройство пользователя highp, если нет — используйте разные шейдеры с меньшим количеством функций.
-
-WebGL по умолчанию использует highp для вершинных шейдеров, а фрагментные шейдеры не имеют значения по умолчанию, и вы должны указать одно. Более того, highp во фрагментном шейдере — это опциональная функция, и некоторые мобильные GPU её не поддерживают. Я не знаю, какой это процент в 2019 году. Насколько я знаю, большинство или даже все телефоны, выпущенные в 2019 году, поддерживают highp, но старые телефоны (2011, 2012, 2013) не поддерживают.
-
-Из спецификации:
-
-> The vertex language requires any uses of `lowp`, `mediump` and `highp` to compile and link without error.
-> The fragment language requires any uses of `lowp` and `mediump` to compile without error. **Support for
-> `highp` is optional**.
-
-Примеры мест, где обычно нужен highp. Точечные источники света с затенением по Фонгу обычно нуждаются в highp. Так что, например, вы можете использовать только направленные источники света на системе, которая не поддерживает highp, ИЛИ вы можете использовать только направленные источники света на мобильных для производительности.
-
-> Есть ли разница в производительности?
-
-Да, но как сказано выше, реализация может использовать более высокую точность. Так что если вы используете mediump на десктопном GPU, вы не увидите разницы в производительности, поскольку он действительно всегда использует highp. На мобильных вы увидите разницу в производительности, по крайней мере в 2019 году. Вы также можете увидеть, где вашим шейдерам действительно нужен highp.
-
-Вот шейдер Фонга, настроенный на использование mediump. На десктопе, поскольку mediump на самом деле highp, он работает:
-
-
-
-На мобильных, где mediump действительно mediump, он ломается:
-
-
-
-Пример, где mediump был бы хорош, по крайней мере во фрагментном шейдере — это большинство 2D игр.
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-working-around-gl_pointsize-limitations-webgl.md b/webgl/lessons/ru/webgl-qna-working-around-gl_pointsize-limitations-webgl.md
deleted file mode 100644
index 514a1c04c..000000000
--- a/webgl/lessons/ru/webgl-qna-working-around-gl_pointsize-limitations-webgl.md
+++ /dev/null
@@ -1,121 +0,0 @@
-Title: Обход ограничений gl_PointSize в WebGL
-Description: Как обойти ограничения gl_PointSize в WebGL
-TOC: Обход ограничений gl_PointSize в WebGL
-
-## Вопрос:
-
-Я использую three.js для создания интерактивной визуализации данных. В этой визуализации рендерится 68000 узлов, каждый из которых имеет свой размер и цвет.
-
-Сначала я пытался делать это через рендеринг мешей, но это оказалось слишком дорого по производительности. Сейчас я использую систему частиц three.js, где каждая точка — это узел визуализации.
-
-Я могу управлять цветом и размером точки, но только до определённого предела. На моей видеокарте максимальный размер точки gl равен 63. Когда я увеличиваю масштаб визуализации, точки становятся больше — до определённого момента, а затем остаются 63 пикселя.
-
-Я использую собственные vertex и fragment шейдеры:
-
-vertex shader:
-
- attribute float size;
- attribute vec3 ca;
- varying vec3 vColor;
-
- void main() {
- vColor = ca;
- vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
- gl_PointSize = size * ( 300.0 / length( mvPosition.xyz ) );
- gl_Position = projectionMatrix * mvPosition;
- }
-
-fragment shader:
-
- uniform vec3 color;
- uniform sampler2D texture;
-
- varying vec3 vColor;
-
- void main() {
- gl_FragColor = vec4( color * vColor, 1.0 );
- gl_FragColor = gl_FragColor * texture2D( texture, gl_PointCoord );
- }
-
-Эти шейдеры почти дословно скопированы из одного из примеров three.js.
-
-Я совсем новичок в GLSL, но ищу способ рисовать точки больше 63 пикселей. Могу ли я, например, рисовать меш для точек больше определённого размера, а для остальных использовать gl_point? Есть ли другие способы обойти это ограничение и рисовать точки больше 63 пикселей?
-
-## Ответ:
-
-Вы можете сделать свою собственную систему точек, используя массивы unit quad'ов + центральную точку, а затем масштабировать их по размеру в GLSL.
-
-То есть, у вас будет 2 буфера. Один буфер — это просто 2D unitQuad, повторённый столько раз, сколько точек вы хотите нарисовать.
-
- var unitQuads = new Float32Array([
- -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
- -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
- -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
- -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
- -0.5, 0.5, 0.5, 0.5, -0.5, -0.5, 0.5, -0.5,
- ]);
-
-Второй буфер — это ваши точки, но позиции каждой точки нужно повторить 4 раза:
-
- var points = new Float32Array([
- p1.x, p1.y, p1.z, p1.x, p1.y, p1.z, p1.x, p1.y, p1.z, p1.x, p1.y, p1.z,
- p2.x, p2.y, p2.z, p2.x, p2.y, p2.z, p2.x, p2.y, p2.z, p2.x, p2.y, p2.z,
- p3.x, p3.y, p3.z, p3.x, p3.y, p3.z, p3.x, p3.y, p3.z, p3.x, p3.y, p3.z,
- p4.x, p4.y, p4.z, p4.x, p4.y, p4.z, p4.x, p4.y, p4.z, p4.x, p4.y, p4.z,
- p5.x, p5.y, p5.z, p5.x, p5.y, p5.z, p5.x, p5.y, p5.z, p5.x, p5.y, p5.z,
- ]);
-
-Настройте ваши буферы и атрибуты:
-
- var buf = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, buf);
- gl.bufferData(gl.ARRAY_BUFFER, unitQuads, gl.STATIC_DRAW);
- gl.enableVertexAttribArray(unitQuadLoc);
- gl.vertexAttribPointer(unitQuadLoc, 2, gl.FLOAT, false, 0, 0);
-
- var buf = gl.createBuffer();
- gl.bindBuffer(gl.ARRAY_BUFFER, buf);
- gl.bufferData(gl.ARRAY_BUFFER, points, gl.STATIC_DRAW);
- gl.enableVertexAttribArray(pointLoc);
- gl.vertexAttribPointer(pointLoc, 3, gl.FLOAT, false, 0, 0);
-
-В вашем GLSL-шейдере вычисляйте нужный gl_PointSize, затем умножайте unitQuad на этот размер в view space или screen space. Screen space будет вести себя как обычный gl_Point, но часто хочется, чтобы точки масштабировались в 3D, как обычные объекты — тогда используйте view space.
-
- attribute vec2 a_unitQuad;
- attribute vec4 a_position;
- uniform mat4 u_view;
- uniform mat4 u_viewProjection;
-
- void main() {
- float fake_gl_pointsize = 150;
-
- // Получаем xAxis и yAxis во view space
- // это unit-векторы, то есть направления, перпендикулярные взгляду
- vec3 x_axis = view[0].xyz;
- vec3 y_axis = view[1].xyz;
-
- // умножаем их на нужный размер
- x_axis *= fake_gl_pointsize;
- y_axis *= fake_gl_pointsize;
-
- // умножаем на unitQuad, чтобы получить quad вокруг центра
- vec3 local_point = vec3(x_axis * a_unitQuad.x + y_axis * a_unitQuad.y);
-
- // добавляем позицию, где хотим разместить quad
- local_point += a_position;
-
- // обычная математика для шейдера
- gl_Position = u_viewProjection * local_point;
- }
-
-Возможно, это звучит сложно, но есть рабочий пример [здесь][1]
-
- [1]: https://www.khronos.org/registry/webgl/sdk/demos/google/particles/
-
-
-
Вопрос и цитируемые части взяты по лицензии CC BY-SA 3.0 у
-
Thomi
- с сайта
-
stackoverflow
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna-zooming-to-and-stopping-at-object-in-a-scene-in-webgl.md b/webgl/lessons/ru/webgl-qna-zooming-to-and-stopping-at-object-in-a-scene-in-webgl.md
deleted file mode 100644
index 4cee2984d..000000000
--- a/webgl/lessons/ru/webgl-qna-zooming-to-and-stopping-at-object-in-a-scene-in-webgl.md
+++ /dev/null
@@ -1,60 +0,0 @@
-Title: Приближение и остановка у объекта в сцене WebGL
-Description: Как реализовать приближение к объекту и остановку перед ним в сцене WebGL
-TOC: Приближение и остановка у объекта в сцене WebGL
-
-## Вопрос:
-
-Мы создали WebGL-приложение, которое отображает сцену с несколькими объектами. Всю сцену можно вращать в разных направлениях. Требуется, чтобы пользователь мог приближаться к объекту, но НЕ проходить сквозь него. Я знаю, что такую функциональность можно реализовать с помощью фреймворков вроде Three.js и SceneJs. К сожалению, наше приложение не использует фреймворки. Можно ли реализовать описанное приближение только средствами WebGL? Примечание: object picking нам не подходит, так как пользователь не обязан выбирать объект в сцене. Спасибо за помощь.
-
-## Ответ:
-
-Навскидку:
-
-Во-первых, нужно знать размер каждого объекта в мировом пространстве. Например, если один объект размером 10 единиц, а другой — 100, вероятно, вы захотите находиться на разном расстоянии от них. Под мировым пространством я также имею в виду, что если вы масштабируете объект 10 на 9, то в мире он будет 90 единиц, и снова потребуется другое расстояние.
-
-Обычно размер объекта в локальном пространстве вычисляют по экстентам его вершин. Просто проходите по всем вершинам и отслеживайте минимальные и максимальные значения по x, y и z. Хотите ли вы брать максимальное значение от центра или вычислять реальный центр — решайте сами.
-
-Зная размер, можно вычислить, на каком расстоянии нужно быть, чтобы видеть весь объект. Для стандартной перспективной матрицы можно просто посчитать это в обратную сторону. Если объект 10 единиц, нужно уместить 10 единиц в фрустуме. Обычно берут чуть больше, например, 14 (size * 1.4), чтобы был запас вокруг объекта.
-
-![enter image description here][1]
-
-Известны `halfFovy`, `halfSizeToFitOnScreen`, нужно вычислить `distance`:
-
- sohcahtoa
- tangent = opposite / adjacent
- opposite = halfsizeToFitOnScreen
- adjacent = distance
- tangent = Math.tan(halfFovY)
-
-Следовательно:
-
- tangent = sizeToFitOnScreen / distance
- tangent * distance = sizeToFitOnScreen
- distance = sizeToFitOnScreen / tangent
- distance = sizeToFitOnScreen / Math.tan(halfFovY)
-
-Теперь мы знаем, что камере нужно быть на расстоянии `distance` от объекта. На этом расстоянии вокруг объекта есть целая сфера. Где именно на этой сфере — решать вам. Обычно берут текущее положение камеры и вычисляют направление от объекта к камере:
-
- direction = normalize(cameraPos - objectPos)
-
-Теперь можно вычислить точку на расстоянии `distance` в этом направлении:
-
- desiredCameraPosition = direction * distance
-
-Теперь либо ставьте камеру туда с помощью lookAt:
-
- matrix = lookAt(desiredCameraPosition, objectPosition, up)
-
-Либо плавно перемещайте камеру (lerp) между текущей позицией и новой:
-
-{{{example url="../webgl-qna-zooming-to-and-stopping-at-object-in-a-scene-in-webgl-example-1.html"}}}
-
- [1]: http://i.stack.imgur.com/0axue.png
-
-
-
Вопрос и цитируемые части взяты по лицензии CC BY-SA 3.0 у
-
jfc615
- с сайта
-
stackoverflow
-
-
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-qna.md b/webgl/lessons/ru/webgl-qna.md
deleted file mode 100644
index bc9e38917..000000000
--- a/webgl/lessons/ru/webgl-qna.md
+++ /dev/null
@@ -1,123 +0,0 @@
-Title: Вопросы и ответы
-Description: Случайные вопросы и ответы
-TOC: Вопросы и ответы
-
-Коллекция ссылок на случайные вопросы и/или ответы о темах, связанных с WebGL
-
-* [Эффект Дросте в WebGL](webgl-qna-webgl-droste-effect.html)
-* [Обход ограничений gl_PointSize в WebGL](webgl-qna-working-around-gl_pointsize-limitations-webgl.html)
-* [Как симулировать 3D текстуру в WebGL](webgl-qna-how-to-simulate-a-3d-texture-in-webgl.html)
-* [Как рендерить изображения большого масштаба, такие как 32000x32000](webgl-qna-how-to-render-large-scale-images-like-32000x32000.html)
-* [Эмуляция палитровой графики в WebGL](webgl-qna-emulating-palette-based-graphics-in-webgl.html)
-
----
-
-* [Простой способ показать нагрузку на вершинную и фрагментную обработку GPU?](webgl-qna-a-simple-way-to-show-the-load-on-the-gpu-s-vertex-and-fragment-processing-.html)
-* [Как рисовать толстые линии в WebGL](https://mattdesl.svbtle.com/drawing-lines-is-hard)
-* [FPS-подобное движение камеры с базовыми матричными трансформациями](webgl-qna-fps-like-camera-movement-with-basic-matrix-transformations.html)
-* [Как использовать текстуру и цвет также в WebGL?](webgl-qna-how-to-use-texture--and-color-also-in-webgl-.html)
-* [GLSL шейдер для поддержки раскрашивания и текстурирования](webgl-qna-glsl-shader-to-support-coloring-and-texturing.html)
-
----
-
-* [Как привязать массив текстур к uniform WebGL шейдера?](webgl-qna-how-to-bind-an-array-of-textures-to-a-webgl-shader-uniform-.html)
-* [Передача значений альфа для каждого спрайта при батчинге](webgl-qna-passing-in-per-sprite-alpha-values-when-batching.html)
-* [Может ли кто-нибудь объяснить, что делает этот GLSL фрагментный шейдер?](webgl-qna-can-anyone-explain-what-this-glsl-fragment-shader-is-doing-.html)
-* [WebGL 2D тайлмапы](webgl-qna-webgl-2d-tilemaps.html)
-* [Определение минимальных/максимальных значений для всего изображения](webgl-qna-determine-min-max-values-for-the-entire-image.html)
-
----
-
-* [Рисование множества различных моделей в одном вызове draw](webgl-qna-drawing-many-different-models-in-a-single-draw-call.html)
-* [Запись FPS в WebGL](webgl-qna-recording-fps-in-webgl.html)
-* [Как написать веб-визуализатор музыки](webgl-qna-how-to-write-a-web-based-music-visualizer.html)
-* [Как получить аудио данные в шейдер](webgl-qna-how-to-get-audio-data-into-a-shader.html)
-* [Чистая пунктирная линия WebGL](webgl-qna-pure-webgl-dashed-line.html)
-* [Масштабирование и остановка на объекте в сцене в WebGL](webgl-qna-zooming-to-and-stopping-at-object-in-a-scene-in-webgl.html)
-
----
-
-* [Создание эффекта искажения изображения в WebGL](webgl-qna-create-image-warping-effect-in-webgl.html)
-* [Создание эффекта размазывания/разжижения](webgl-qna-creating-a-smudge-liquify-effect.html)
-* [Как получить эффект пикселизации в WebGL?](webgl-qna-how-to-get-pixelize-effect-in-webgl-.html)
-* [Как сделать WebGL canvas прозрачным](webgl-qna-how-to-make-webgl-canvas-transparent.html)
-* [Показать ночной вид против дневного вида на 3D сфере Земли](webgl-qna-show-a-night-view-vs-a-day-view-on-a-3d-earth-sphere.html)
-
----
-
-* [Возможно ли измерить время рендеринга в WebGL используя gl.finish()?](webgl-qna-is-it-possible-to-measure-rendering-time-in-webgl-using-gl-finish---.html)
-* [Эффективная система частиц в JavaScript? (WebGL)](webgl-qna-efficient-particle-system-in-javascript---webgl-.html)
-* [Установка значений массива структур из JS в GLSL](webgl-qna-setting-the-values-of-a-struct-array-from-js-to-glsl.html)
-* [Как затухать буфер рисования](webgl-qna-how-to-fade-the-drawing-buffer.html)
-* [Рисование 2D изображения с картой глубины для достижения псевдо-3D эффекта](webgl-qna-drawing-2d-image-with-depth-map-to-achieve-pseudo-3d-effect.html)
-
----
-
-* [Как достичь движущейся линии с эффектами следа](webgl-qna-how-to-achieve-moving-line-with-trail-effects.html)
-* [Как правильно рисовать текстурированные трапециевидные полигоны](webgl-qna-how-to-draw-correctly-textured-trapezoid-polygons.html)
-* [Tex image TEXTURE_2D level 0 вызывает ленивую инициализацию](webgl-qna-tex-image-texture_2d-level-0-is-incurring-lazy-initialization.html)
-* [Как реализовать масштабирование от мыши в 2D WebGL](webgl-qna-how-to-implement-zoom-from-mouse-in-2d-webgl.html)
-* [Как создать тор](webgl-qna-how-to-create-a-torus.html)
-
----
-
-* [Не смешивать полигон, который пересекает сам себя](webgl-qna-don-t-blend-a-polygon-that-crosses-itself.html)
-* [Как обнаружить обрезанные треугольники во фрагментном шейдере](webgl-qna-how-to-detect-clipped-triangles-in-the-framgment-shader.html)
-* [Самый быстрый способ нарисовать много кругов](webgl-qna-the-fastest-way-to-draw-many-circles.html)
-* [Сортировка и оптимизация инстансированного рендеринга](webgl-qna-sorting-and-optimizing-instanced-rendering.html)
-* [Как определить, есть ли у изображения альфа-канал](webgl-qna-how-to-tell-if-an-image-has-an-alpha-channel.html)
-
----
-
-* [Как загружать изображения в фоне без рывков](webgl-qna-how-to-load-images-in-the-background-with-no-jank.html)
-* [Когда выбирать highp, mediump, lowp в шейдерах](webgl-qna-when-to-choose-highp--mediump--lowp-in-shaders.html)
-* [Как импортировать карту высот в WebGL](webgl-qna-how-to-import-a-heightmap-in-webgl.html)
-* [Применение карты смещения и карты бликов](webgl-qna-apply-a-displacement-map-and-specular-map.html)
-* [Как поддерживать и WebGL, и WebGL2](webgl-qna-how-to-support-both-webgl-and-webgl2.html)
-
----
-
-* [Как использовать буфер трафарета](webgl-qna-how-to-use-the-stencil-buffer.html)
-* [Рисование карты высот](webgl-qna-drawing-a-heightmap.html)
-* [Рисование текстурированных спрайтов с инстансированным рисованием](webgl-qna-drawing-textured-sprites-with-instanced-drawing.html)
-* [Оптимизация рисования множества больших изображений](webgl-qna-optimize-drawing-lots-of-large-images.html)
-* [Рендеринг медленно со временем](webgl-qna-rendering-slowly-over-time.html)
-
----
-
-* [Получить размер точки для проверки столкновений](webgl-qna-get-the-size-of-a-point-for-collision-checking.html)
-* [Как получить 3D координаты клика мыши](webgl-qna-how-to-get-the-3d-coordinates-of-a-mouse-click.html)
-* [Как смешивать цвета между 2 треугольниками](webgl-qna-how-to-blend-colors-across-2-triangles.html)
-* [Как использовать текстуры как данные](webgl-qna-how-to-use-textures-as-data.html)
-* [Как использовать прозрачность 2D спрайта как маску](webgl-qna-how-to-use-a-2d-sprite-s-transparency-as-a-mask.html)
-
----
-
-* [Как предотвратить просачивание текстур с атласом текстур](webgl-qna-how-to-prevent-texture-bleeding-with-a-texture-atlas.html)
-* [Как контролировать цвет между вершинами](webgl-qna-how-to-control-the-color-between-vertices.html)
-* [Как сделать шейдер рыбьего глаза для скайбокса](webgl-qna-how-to-make-fisheye-skybox-shader.html)
-* [Как получить автодополнение кода для WebGL в Visual Studio Code](webgl-qna-how-to-get-code-completion-for-webgl-in-visual-studio-code.html)
-* [Рисование слоев с разными точками](webgl-qna-drawing-layers-with-different-points.html)
-
----
-
-* [Могу ли я отключить предупреждение о том, что vertex attrib 0 отключен?](webgl-qna-can-i-mute-the-warning-about-vertex-attrib-0-being-disabled-.html)
-* [Как сделать инструмент размазывающей кисти](webgl-qna-how-to-make-a-smudge-brush-tool.html)
-* [Как читать один компонент с readPixels](webgl-qna-how-to-read-a-single-component-with-readpixels.html)
-* [Я получаю ошибку недопустимого типа при вызове readPixels](webgl-qna-i-get-invalid-type-error-when-calling-readpixels.html)
-* [Как оптимизировать рендеринг UI](webgl-qna-how-to-optimize-rendering-a-ui.html)
-
----
-
-* [Как я могу вычислить для 500 точек, какая из 1000 отрезков линий ближе всего к каждой точке?](webgl-qna-how-can-i-compute-for-500-points-which-of-1000-line-segments-is-nearest-to-each-point-.html)
-* [Как я могу переместить точку схода перспективы из центра canvas?](webgl-qna-how-can-i-move-the-perspective-vanishing-point-from-the-center-of-the-canvas-.html)
-* [Существует ли понятие обобщенного вершинного и фрагментного шейдера?](webgl-qna-is-there-the-notion-of-a-generalized-vertex-and-fragment-shader-.html)
-* [Как определить среднюю яркость в сцене?](webgl-qna-how-to-determine-the-average-brightness-in-a-scene-.html)
-* [Что такое локальное начало координат 3D модели?](webgl-qna-what-is-the-local-origin-of-a-3d-model-.html)
-
-## WebGL2
-
-* [Доступ к текстурам по координатам пикселей в WebGL2](webgl-qna-accessing-textures-by-pixel-coordinate-in-webgl2.html)
-* [Как я могу получить все uniforms и uniformBlocks](webgl-qna-how-can-i-get-all-the-uniforms-and-uniformblocks.html)
-* [Как я могу создать 16-битную гистограмму 16-битных данных](webgl-qna-how-can-i-create-a-16bit-historgram-of-16bit-data.html)
-* [Как обрабатывать позиции частиц](webgl-qna-how-to-process-particle-positions.html)
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-render-to-texture.md b/webgl/lessons/ru/webgl-render-to-texture.md
index 8bb564fcc..854521d52 100644
--- a/webgl/lessons/ru/webgl-render-to-texture.md
+++ b/webgl/lessons/ru/webgl-render-to-texture.md
@@ -252,80 +252,14 @@ gl.bindTexture(gl.TEXTURE_2D, depthTexture);
В противном случае она не работает, и вам придется сделать что-то еще, например, сказать пользователю, что ему не повезло,
или переключиться на какой-то другой метод.
-Если вы еще не проверили [упрощение WebGL с меньшим количеством кода больше веселья](webgl-less-code-more-fun.html).
+Если вы еще не проверили [упрощение WebGL с меньшим количеством кода, больше веселья](webgl-less-code-more-fun.html).
Сам Canvas на самом деле является текстурой
Это просто мелочь, но браузеры используют техники выше для реализации самого canvas.
-За кулисами они создают цветную текстуру, буфер глубины, framebuffer, а затем они
-привязывают его как текущий framebuffer. Вы делаете свой рендеринг, который рисует в эту текстуру.
-Они затем используют эту текстуру для рендеринга вашего canvas в веб-страницу.
-
-
-
-```
-// создаем текстуру глубины
-const depthTexture = gl.createTexture();
-gl.bindTexture(gl.TEXTURE_2D, depthTexture);
-
-// делаем буфер глубины того же размера, что и targetTexture
-{
- // определяем размер и формат уровня 0
- const level = 0;
- const internalFormat = gl.DEPTH_COMPONENT24;
- const border = 0;
- const format = gl.DEPTH_COMPONENT;
- const type = gl.UNSIGNED_INT;
- const data = null;
- gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
- targetTextureWidth, targetTextureHeight, border,
- format, type, data);
-
- // устанавливаем фильтрацию, чтобы нам не нужны были мипмапы
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
-
- // прикрепляем текстуру глубины к framebuffer
- gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, depthTexture, level);
-}
-```
-
-И с этим вот результат.
-
-{{{example url="../webgl-render-to-texture-3-cubes-with-depth-buffer.html" }}}
-
-Теперь, когда у нас есть буфер глубины, прикрепленный к нашему framebuffer, внутренние кубы правильно пересекаются.
-
-
-
-Важно отметить, что WebGL гарантирует работу только определенных комбинаций вложений.
-[Согласно спецификации](https://www.khronos.org/registry/webgl/specs/latest/1.0/#FBO_ATTACHMENTS)
-единственные гарантированные комбинации вложений:
-
-* `COLOR_ATTACHMENT0` = `RGBA/UNSIGNED_BYTE` текстура
-* `COLOR_ATTACHMENT0` = `RGBA/UNSIGNED_BYTE` текстура + `DEPTH_ATTACHMENT` = `DEPTH_COMPONENT16` renderbuffer
-* `COLOR_ATTACHMENT0` = `RGBA/UNSIGNED_BYTE` текстура + `DEPTH_STENCIL_ATTACHMENT` = `DEPTH_STENCIL` renderbuffer
-
-Для любых других комбинаций вы должны проверить, поддерживает ли система/GPU/драйвер/браузер пользователя эту комбинацию.
-Для проверки вы создаете свой framebuffer, создаете и прикрепляете вложения, затем вызываете
-
- var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
-
-Если статус `FRAMEBUFFER_COMPLETE`, то эта комбинация вложений работает для этого пользователя.
-В противном случае она не работает, и вам придется сделать что-то еще, например, сказать пользователю, что ему не повезло,
-или переключиться на какой-то другой метод.
-
-Если вы еще не ознакомились с [упрощением WebGL с меньше кода больше веселья](webgl-less-code-more-fun.html).
-
-
-
Canvas сам по себе на самом деле текстура
-
-Это просто мелочь, но браузеры используют техники выше для реализации самого canvas.
-За кулисами они создают цветную текстуру, буфер глубины, framebuffer, а затем они
-привязывают его как текущий framebuffer. Вы делаете свой рендеринг, который рисует в эту текстуру.
-Они затем используют эту текстуру для рендеринга вашего canvas в веб-страницу.
+За кулисами они создают цветную текстуру, буфер глубины, framebuffer, а затем привязывают
+его как текущий framebuffer. Вы делаете свой рендеринг, который рисует в эту текстуру.
+Затем они используют эту текстуру для рендеринга вашего canvas в веб-страницу.
\ No newline at end of file
diff --git a/webgl/lessons/ru/webgl-resizing-the-canvas.md b/webgl/lessons/ru/webgl-resizing-the-canvas.md
index f5828a93f..784cee81a 100644
--- a/webgl/lessons/ru/webgl-resizing-the-canvas.md
+++ b/webgl/lessons/ru/webgl-resizing-the-canvas.md
@@ -201,4 +201,277 @@ function resizeCanvasToDisplaySize(canvas) {
return needResize;
}
-```
\ No newline at end of file
+```
+
+Нам нужно вызвать `Math.round` (или `Math.ceil`, или `Math.floor` или `| 0`), чтобы получить число
+к целому, потому что `canvas.width` и `canvas.height` всегда в целых числах, поэтому
+наше сравнение может не сработать, если `devicePixelRatio` не является целым числом, что часто встречается, особенно
+если пользователь масштабирует.
+
+> Примечание: Использовать ли `Math.floor` или `Math.ceil` или `Math.round` не определено HTML
+спецификацией. Это зависит от браузера. 🙄
+
+В любом случае, это **не** будет работать на самом деле. Новая проблема в том, что при `devicePixelRatio`, который не равен 1.0,
+CSS размер, который нужен canvas, чтобы заполнить данную область, может не быть целым значением,
+но `clientWidth` и `clientHeight` определены как целые числа. Допустим, окно
+999 фактических пикселей устройства в ширину, ваш devicePixelRatio = 2.0, и вы просите canvas размером 100%.
+Нет целого CSS размера * 2.0, который = 999.
+
+Следующее решение - использовать
+[`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
+Он возвращает [`DOMRect`](https://developer.mozilla.org/en-US/docs/Web/API/DOMRect),
+который имеет `width` и `height`. Это тот же
+client rect, который представлен `clientWidth` и `clientHeight`, но он не обязан
+быть целым числом.
+
+Ниже фиолетовый `