mirror of
https://github.com/patriciogonzalezvivo/thebookofshaders
synced 2024-11-08 01:10:27 +00:00
commit
d699159380
47
00/README-ru.md
Normal file
47
00/README-ru.md
Normal file
@ -0,0 +1,47 @@
|
||||
# Введение
|
||||
|
||||
<canvas id="custom" class="canvas" data-fragment-url="cmyk-halftone.frag" data-textures="vangogh.jpg" width="700px" height="320px"></canvas>
|
||||
|
||||
Изображения выше были получены двумя различными способами. Первое Ван Гог написал вручную, нанося краску слой за слоем. Он потратил на это несколько часов. Второе было получено за секунды смешиванием четырёх наборов пикселей: сине-зелёного, пурпурного, жёлтого и чёрного. Ключевое отличие в том, что второе изображение получено непоследовательным способом, то есть не шаг за шагом, а всё за раз.
|
||||
|
||||
Эта книга повествует о революционной компьютерной технологии - *фрагментных шейдерах*, которые выводят генерируемые компьютером изображения на новый уровень. Их можно назвать эквивалентом печатного станка Гутенберга в мире графики.
|
||||
|
||||
![Печатный станок Гутенберга](gutenpress.jpg)
|
||||
|
||||
Фрагментные шейдеры дают вам полный контроль над пикселями на экране на сверхбыстрой скорости. Поэтому они используются повсеместно, от фильтров для видео на телефоне до современнейших 3D-игр.
|
||||
|
||||
![Игра Journey от That Game Company](journey.jpg)
|
||||
|
||||
В следующих главах вы увидите, какую мощь предоставляет эта технология, и как её можно использовать для рабочих и личных проектов.
|
||||
|
||||
## Для кого эта книга?
|
||||
|
||||
Эта книга написана для творческих программистов, разработчиков игр и инженеров с опытом программирования, базовыми знаниями линейной алгебры и тригонометрии, а так же желающих вывести свою работу на качественно новый уровень графических эффектов. Если вы ещё только хотите научиться программировать, я бы рекомендовал вам начать с сайта [Processing](https://processing.org/), и вернуться позже, когда освоитесь.
|
||||
|
||||
Эта книга научит вас использовать шейдеры и встраивать их ваши проекты, повышать их производительность и качество получаемой картинки. Поскольку шейдеры на GLSL (языке шейдеров OpenGL) компилируются и запускаются на самых разных платформах, вы сможете применять полученные знания в любой среде, где есть OpenGL, OpenGL ES или WebGL. Другими словами, вы сможете применить эти умения в скетчах на [Processing](https://processing.org/), приложениях на [openFrameworks](http://openframeworks.cc/), интерактивных инсталляциях [Cinder](http://libcinder.org/), сайтах на [Three.js](http://threejs.org/) или мобильных iOS/Android-играх.
|
||||
|
||||
## Какие темы освещает эта книга?
|
||||
|
||||
Эта книга уделяет основное внимание пиксельным шейдерам на GLSL. Сначала мы изучим что такое шейдеры, а затем научимся создавать с их помощью процедурные геометрические фигуры, узоры, текстуры и анимации. Вы изучите основы языка шейдеров и примените их к задачам, возникающим на практике: обработке изображений (логические операции над изображениями, размытие, свёртки с ядром, цветовые фильтры, таблицы значений и другие эффекты) и созданию симуляций (игра «Жизнь» Конвея, модель Грея-Скотта для реакции-диффузии, рябь на поверхности воды, акварельные эффекты, диаграммы Вороного и т.п.). Ближе к концу книги будут изложены продвинутые методы на основе алгоритмов трассировки лучей.
|
||||
|
||||
*Каждый параграф содержит интерактивные примеры*. Изменения в них показываются непосредственно при редактировании кода. Излагаемые принципы могут быть довольно абстрактными, поэтому интерактивные примеры очень полезны при изучении материала. Чем быстрее вы увидите изучаемые концепции в действии, тем проще будет процесс обучения.
|
||||
|
||||
Чего нет в этой книге:
|
||||
|
||||
* Эта книга не об OpenGL или WebGL. OpenGL/WebGL - боле обширная тема, чем GLSL и фрагментные шейдеры. Для изучения OpenGL/WebGL я бы рекомендовал [ведение в OpenGL](https://open.gl/introduction), [8 издание руководства по программированию на OpenGL](http://www.amazon.com/OpenGL-Programming-Guide-Official-Learning/dp/0321773039/ref=sr_1_1?s=books&ie=UTF8&qid=1424007417&sr=1-1&keywords=open+gl+programming+guide) (она же красная книга) или [WebGL: Up and Running](http://www.amazon.com/WebGL-Up-Running-Tony-Parisi/dp/144932357X/ref=sr_1_4?s=books&ie=UTF8&qid=1425147254&sr=1-4&keywords=webgl)
|
||||
|
||||
* Это *не* учебник по математике. Мы изложим некоторые методы, основанные на алгебре и тригонометрии, но мы не будем углубляться в детали. По математическим вопросам я бы рекомендовал обратиться к следующим книгам: [третье издание «математики для 3D-программирования и компьютерной графики»](http://www.amazon.com/Mathematics-Programming-Computer-Graphics-Third/dp/1435458869/ref=sr_1_1?ie=UTF8&qid=1424007839&sr=8-1&keywords=mathematics+for+games) или [второе издание «математики для игр и интерактивных приложений»](http://www.amazon.com/Essential-Mathematics-Games-Interactive-Applications/dp/0123742978/ref=sr_1_1?ie=UTF8&qid=1424007889&sr=8-1&keywords=essentials+mathematics+for+developers)
|
||||
|
||||
## Что нужно чтобы начать?
|
||||
|
||||
Не многое! Если у вас есть современный браузер с поддержкой WebGL (Chrome, Firefox или Safari) и подключение к Интернету, нажмите кнопку «Next» внизу страницы.
|
||||
|
||||
Так же вы можете:
|
||||
|
||||
- [Сделать оффлайн-версию книги](https://thebookofshaders.com/appendix/00/?lan=ru)
|
||||
|
||||
- [Запустить примеры на Raspberry Pi без браузера](https://thebookofshaders.com/appendix/01/?lan=ru)
|
||||
|
||||
- [Собрать PDF-версию для печати](https://thebookofshaders.com/appendix/02/?lan=ru)
|
||||
|
||||
- Изучить [репозиторий книги на GitHub](https://github.com/patriciogonzalezvivo/thebookofshaders) и помочь исправлением ошибок или предоставлением кода.
|
48
01/README-ru.md
Normal file
48
01/README-ru.md
Normal file
@ -0,0 +1,48 @@
|
||||
# Введение
|
||||
## Что такое фрагментный шейдер?
|
||||
|
||||
В предыдущем параграфе мы описали шейдеры как эквивалент печатного станка Гутенберга для графики. Почему? И вообще, что такое шейдер?
|
||||
|
||||
![Слева: буква за буквой (монах-переписчик за работой, Вильям Блэйдс, 1891). Справа: страница за страницей (печатный станок, Ролт-Уилер, 1920).](print.png)
|
||||
|
||||
Если у вас уже есть опыт рисования с помощью компьютера, вы скорее всего сначала рисовали круг, затем прямоугольник, линию, несколько треугольников, получая в итоге желаемую композицию. Процесс похож на написание письма или книги вручную - это набор инструкций, которые последовательно решают задачи одну за другой.
|
||||
|
||||
Шейдеры так же являются наборами инструкций, и эти инструкции исполняются одновременно для каждого пикселя на экране. Это означает, что код должен работать по разному в зависимости от положения пикселя на экране. Подобно печатному станку, ваша программа будет работать как функция, принимающая на вход координаты пикселя, и возвращающая цвет. После компиляции она будет работать невероятно быстро.
|
||||
|
||||
![Китайский наборный шрифт](typepress.jpg)
|
||||
|
||||
## Почему шейдеры работают быстро?
|
||||
|
||||
Ответить на этот вопрос помогут *параллельные вычисления*.
|
||||
|
||||
Представьте процессор компьютера в виде трубы, а каждую задачу как что-то проходящее через неё, как на фабричной производственной линии. Некоторые задачи больше остальных, то есть требуют больше времени и энергии на выполнение. В наших терминах, они требуют больше вычислительной мощности. Из-за особенностей архитектуры компьютера задачи запускаются последовательно, и каждая задача должна быть завершена вовремя. Современные компьютеры обычно содержат несколько процессоров, каждый из которых можно представить в виде трубы, обрабатывающей задания одно за другим, создавая иллюзию плавности и непрерывности работы. Каждая такая труба называется *потоком*.
|
||||
|
||||
![Центральный процессор](00.jpeg)
|
||||
|
||||
Видеоигры и другие графические приложения требуют намного больше вычислительной мощности, чем другие программы. Они вынуждены совершать огромное количество попиксельных операций над графическим контентом. Каждый пиксель на экране должен быть обсчитан, а в 3D-играх нужно рассчитать ещё и геометрию с перспективой.
|
||||
|
||||
Давайте вернёмся к нашей метафоре с трубами и задачами. Каждый пиксель на экране является небольшой простой задачей. По отдельности такие задачи не представляют трудности для CPU, но проблема в том, что эти небольшие задания должны быть выполнены для каждого пикселя на экране. Таким образом, даже для старого экрана с разрешением 800х600 нужно обработать 480 000 пикселей, то есть произвести 14 400 000 вычислений в секунду! И это становится непосильной задачей для центрального процессора. На современном ретина-дисплее с разрешением 2880х1800 вывод видео с частотой 60 кадров в секунду увеличит количество вычислений до 311 040 000 в секунду. Как же инженеры графических систем решают эту проблему?
|
||||
|
||||
![](03.jpeg)
|
||||
|
||||
На помощь приходят параллельные вычисления. Вместо нескольких больших и мощных процессоров разумнее использовать большое количество небольших процессоров, работающих параллельно. Именно так и устроен графический процессор (GPU).
|
||||
|
||||
![GPU](04.jpeg)
|
||||
|
||||
Представьте небольшие процессоры в виде стола из труб, а данные для каждого пикселя - в виде теннисного шарика. 14 400 000 шариков в секунду могут засорить какую угодно трубу, в то время как стол из 800х600 небольших труб сможет спокойно пропустить в секунду 30 волн по 480 000 пикселей. Это верно и на более высоких разрешениях - чем больше у вас параллельно работающего оборудования, тем больший поток оно сможет принять.
|
||||
|
||||
Другая «суперспособность» GPU заключается в аппаратном ускорении математических функций. Таким образом, сложные математические операции обрабатываются оборудованием, а не программами. Это позволяет совершать тригонометрические и векторно-матричные операции сверхбыстро, практически со скоростью света.
|
||||
|
||||
## Что такое GLSL?
|
||||
|
||||
GLSL расшифровывается как OpenGL Shading Language (язык шейдеров OpenGL) и является стандартизированным языком для написания шейдерных программ, которые будут рассмотрены далее. Существуют различные типы шейдеров, зависящие от аппаратуры и операционной системы. Эта книга опирается на спецификацию OpenGL, издаваемую [Khronos Group](https://www.khronos.org/opengl/). Понимание истории OpenGL может быть полезным для понимания многих странных соглашений, принятых в ней. Для этого вы можете пройти по следующей ссылке: [openglbook.com/chapter-0-preface-what-is-opengl.html](http://openglbook.com/chapter-0-preface-what-is-opengl.html)
|
||||
|
||||
## Почему программирование шейдеров - это боль?
|
||||
|
||||
К параллельным вычислениям применим известный афоризм: «большая власть влечёт большую ответственность». Мощь архитектуры графических процессоров накладывает некоторые ограничения.
|
||||
|
||||
Для параллельной работы каждый поток не должен зависеть от остальных потоков. Потоки «слепы» по отношению к тому, чем занимаются другие потоки. Из этого ограничения следует, что все данные должны перемещаться в одном направлении. Поэтому невозможно использовать результат соседнего потока, изменить входные данные или направить выход одного потока на вход другого. Попытка организации межпотокового взаимодействия несёт риск нарушения целостности данных.
|
||||
|
||||
Кроме того, GPU постоянно поддерживает свои процессоры в занятом состоянии. Как только процессор освобождается, он сразу же получает новую порцию данных для обработки. Поток не может узнать что он делал в предыдущий момент времени. Он мог рисовать кнопку для графического интерфейса операционной системы, затем рисовать кусок неба в игре, а потом отображать текст почтового сообщения. Каждый поток не только **слеп**, но ещё и **лишён памяти**. Наряду с представлением в виде абстрактной функции, изменяющей свой результат в зависимости от положения пикселя, слепота и беспамятство потоков не добавляют шейдерам популярности среди начинающих программистов.
|
||||
|
||||
Не волнуйтесь! В следующих главах мы пошагово рассмотрим шейдерные вычисления, начиная с самых простых. Если вы читаете книгу в современном браузере, вы оцените возможность поиграться с интерактивными примерами. Так давайте же не откладывать веселье в долгий ящик! Нажмите *Next >>* чтобы перейти к программированию!
|
53
02/README-ru.md
Normal file
53
02/README-ru.md
Normal file
@ -0,0 +1,53 @@
|
||||
## Hello World
|
||||
|
||||
Обычно программа «Hello world!» является первым шагом при изучении нового языка. Все знают, что это простая однострочная программа, которая выводит на экран фразу приветствия.
|
||||
|
||||
В мире GPU рисование текста - слишком сложная задача для первого шага. Вместо этого мы выберем яркий, жизнерадостный цвет!
|
||||
|
||||
<div class="codeAndCanvas" data="hello_world.frag"></div>
|
||||
|
||||
С кодом выше можно взаимодействовать, если вы читаете книгу в браузере. Это означает, что вы можете изменить любую часть кода по вашему желанию. Благодаря архитектуре GPU, которая компилирует и запускает шейдеры *на лету*, вы увидите изменения незамедлительно. Попробуйте изменить значения в строке 6.
|
||||
|
||||
Эти несколько строчек кода не похожи на нормальную, зрелую программу, но мы можем извлечь из них кое-какие знания:
|
||||
|
||||
1. Язык шейдеров содержит функцию `main`, которая возвращает цвет по окончании работы. Это напоминает C.
|
||||
|
||||
2. Конечный цвет пикселя записывается в зарезервированную переменную `gl_FragColor`.
|
||||
|
||||
3. В этом C-подобном языке есть встроенные *переменные* (такие как `gl_FragColor`), *функции* и *типы*. В этом примере мы только что познакомились с типом `vec4`, то есть четырёхкомпонентным вектором значений с плавающей точкой. Ниже мы встретим такие типы, как `vec3` и `vec2`, а так же более популярные `float`, `int` и `bool`.
|
||||
|
||||
4. Присмотревшись к типу `vec4`, можно догадаться, что его компоненты соответствуют красному, зелёному, синему и альфа каналам. Так же видно, что эти значения нормализованы, то есть лежат в диапазоне от `0.0` до `1.0`. Позднее мы увидим как нормализация значений позволяет проще преобразовывать значения между переменными.
|
||||
|
||||
5. Этот пример так же демонстрирует наличие макросов препроцессора - ещё одно важное свойство, пришедшее из языка C. Макросы раскрываются перед компиляцией. С их помощью можно определить глобальные значения (`#define`) и выполнить простые условные операции, используя `#ifdef` и `#endif`. Все макрокоманды начинаются с решётки (`#`). Препроцессор работает непосредственно перед компиляцией, подставляя определения из директив `#define` и проверяя условия `#ifdef` (если определено) и `#ifndef` (если не определено). Так, в примере выше строка 2 вставляется в код, только если определено `GL_ES`. Это как правило случается на мобильных платформах и браузерах.
|
||||
|
||||
6. Типы чисел с плавающей точкой играют ключевую роль в шейдерах, поэтому важно помнить о *точности*. Более низкая точность позволяет выиграть в скорости работы за счёт качества. Если вы достаточно дотошны, вы можете указывать точность каждой переменной. В первой строке примера мы установили по умолчанию среднюю точность для всех чисел с плавающей точкой (`precision mediump float;`). Так же можно установить низкую (`precision lowp float;`) или высокую (`precision highp float;`) точность.
|
||||
|
||||
7. Последняя, и возможно, важнейшая деталь: спецификация GLSL не гарантирует автоматического приведения типов. Что это означает? Производители оборудования используют различные подходы для ускорения работы видеокарт, но они вынуждены обеспечивать соответствие какому-то минимальному набору требований. Автоматическое приведение в этот набор не входит. В нашем примере тип `vec4` содержит значения с плавающей точкой, поэтому он должен быть инициализирован соответствующими числами. Если вы хотите писать хороший, целостный код и не тратить многие часы на отладку белых экранов, возьмите себе за правило использовать точку (`.`) в значениях с плавающей точкой. Следующий код не везде будет работать корректно:
|
||||
|
||||
```glsl
|
||||
void main() {
|
||||
gl_FragColor = vec4(1,0,0,1); // ОШИБКА
|
||||
}
|
||||
```
|
||||
|
||||
К этому моменту мы описали основные элементы программы «hello world!», а значит, теперь самое время нажать на блок кода и начать применять полученные знания. При возникновении ошибок программа не скомпилируется и покажет белый экран. Попробуйте проделать следующее:
|
||||
|
||||
* Замените числа с плавающей точкой целыми. Ваша графическая карта может безошибочно воспринять такое поведение, а может и нет.
|
||||
|
||||
* Попробуйте закомментировать шестую строку, чтобы не присваивать никакое значение цвету пикселя.
|
||||
|
||||
* Объявите отдельную функцию, которая возвращает заданный цвет, и используйте её внутри `main()`. Например вот это код функции, которая возвращает красный:
|
||||
|
||||
```glsl
|
||||
vec4 red(){
|
||||
return vec4(1.0,0.0,0.0,1.0);
|
||||
}
|
||||
```
|
||||
|
||||
* Есть много способов конструирования значений типа `vec4`. Попробуйте найти другие способы. Например, вот один из них:
|
||||
|
||||
```glsl
|
||||
vec4 color = vec4(vec3(1.0,0.0,1.0),1.0);
|
||||
```
|
||||
|
||||
Очевидно, это не самый крутой шейдер. Это всего лишь базовый пример, который красит все пиксели экрана в один цвет. В следующей главе мы покажем как изменять цвет пикселя в зависимости от двух типов входных данных: пространственных (положение пикселя на экране) и временных (количество секунд с момента загрузки страницы).
|
61
03/README-ru.md
Normal file
61
03/README-ru.md
Normal file
@ -0,0 +1,61 @@
|
||||
## Uniform-переменные
|
||||
|
||||
В предыдущей главе мы увидели каким образом GPU управляет большим количеством параллельных потоков, каждый из которых отвечает за назначение цвета небольшой части изображения. Каждый из параллельных потоков не знает о состоянии остальных, но всё же нам бывает нужно передавать входные данные от CPU ко всем потокам одновременно. Из-за особенностей архитектуры графических карт, все такие входные данные будут одинаковыми для всех потоков (однородными, *uniform*) и доступными *только для чтения*. Другими словами, каждый поток принимает на вход одни и те же данные, которые он может прочитать и не может перезаписать.
|
||||
|
||||
Эти входы называются однородными (`uniform`) и могут иметь практически любой из поддерживаемых типов: `float`, `vec2`, `vec3`, `vec4`, `mat2`, `mat3`, `mat4`, `sampler2D` и `samplerCube`. Uniform-переменные с указанием своих типов объявляются вначале шейдера сразу после указания точности по умолчанию.
|
||||
|
||||
```glsl
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
uniform vec2 u_resolution; // Размер изображения (ширина, высота)
|
||||
uniform vec2 u_mouse; // Положение курсора мыши в пикселях
|
||||
uniform float u_time; // Время в секундах с момента загрузки
|
||||
```
|
||||
|
||||
Uniform-переменные можно представлять себе как маленькие мостики между CPU и GPU. Имена могут изменяться от примера к примеру, но в этой серии примеров я всегда передаю следующее: `u_time` (время в секундах с момента запуска шейдера), `u_resolution` (размер изображения) и `u_mouse` (положение мыши на изображении, выраженное в пикселях). Я буду писать `u_` вначале имён uniform-переменных, чтобы их происхождение было обозначено явно, но на практике вы столкнётесь с самыми разными именами uniform-переменных. Например, [ShaderToy.com](https://www.shadertoy.com/) объявляет эти же переменные со следующими именами:
|
||||
|
||||
```glsl
|
||||
uniform vec3 iResolution; // разрешение области изображения (в пикселях)
|
||||
uniform vec4 iMouse; // координаты мыши в пикселях. xy - текущие, zw - клик
|
||||
uniform float iGlobalTime; // время работы шейдера (секунды)
|
||||
```
|
||||
|
||||
Хватит слов, давайте посмотрим на юниформы в действии. В следующем коде мы используем `u_time` (количество секунд с момента запуска шейдера) в комбинации с функцией синуса чтобы анимировать изменение количества красного цвета на экране.
|
||||
|
||||
<div class="codeAndCanvas" data="time.frag"></div>
|
||||
|
||||
Как видите, GLSL содержит ещё много сюрпризов. GPU аппаратно ускоряет угловые, тригонометрические и экспоненциальные функции. Вот некоторые из них: [`sin()`](../glossary/?search=sin), [`cos()`](../glossary/?search=cos), [`tan()`](../glossary/?search=tan), [`asin()`](../glossary/?search=asin), [`acos()`](../glossary/?search=acos), [`atan()`](../glossary/?search=atan), [`pow()`](../glossary/?search=pow), [`exp()`](../glossary/?search=exp), [`log()`](../glossary/?search=log), [`sqrt()`](../glossary/?search=sqrt), [`abs()`](../glossary/?search=abs), [`sign()`](../glossary/?search=sign), [`floor()`](../glossary/?search=floor), [`ceil()`](../glossary/?search=ceil), [`fract()`](../glossary/?search=fract), [`mod()`](../glossary/?search=mod), [`min()`](../glossary/?search=min), [`max()`](../glossary/?search=max) и [`clamp()`](../glossary/?search=clamp).
|
||||
|
||||
Настало время поиграть с кодом выше.
|
||||
|
||||
* Уменьшите частоту так, чтобы изменение цвета стало почти незаметным.
|
||||
|
||||
* Увеличивайте её до тех пор, пока не увидите сплошной цвет без мерцания.
|
||||
|
||||
* Поиграйтесь с каналами RGB на различных частотах, чтобы добиться какого-нибудь интересного поведения.
|
||||
|
||||
## gl_FragCoord
|
||||
|
||||
Подобно тому, как GLSL объявляет выходное значение `vec4 gl_FragColor` по умолчанию, он так же предоставляет вход `vec4 gl_FragCoord`, содержащий координаты *пикселя* или *фрагмента экрана*, над которым работает данный поток. С помощью `vec4 gl_FragCoord` мы можем узнать где именно поток работает внутри изображения. В данном случае мы не называем входное значение однородным, потому что оно меняется от потока к потоку и называется изменяющимся (*varying*).
|
||||
|
||||
<div class="codeAndCanvas" data="space.frag"></div>
|
||||
|
||||
В коде выше мы нормализуем координаты фрагмента, разделяя их на разрешение изображения. В результате значения переходят в диапазон между `0.0` и `1.0`, что упрощает отображение значений X и Y на красный и зелёный каналы.
|
||||
|
||||
В мире шейдеров у нас нет нормальных отладочных инструментов, поэтому приходится задавать переменным яркие цвета и пытаться извлечь из них смысл. Вы увидите, что иногда программирование на GLSL похоже на засовывание корабля в бутылку. Оно столь же сложно, сколь красиво и захватывающе.
|
||||
|
||||
![](08.png)
|
||||
|
||||
Настало время проверить наше понимание приведённого выше кода.
|
||||
|
||||
* Укажите где находятся координаты `(0.0, 0.0)` на изображении.
|
||||
|
||||
* Как насчёт `(1.0, 0.0)`, `(0.0, 1.0)`, `(0.5, 0.5)` и `(1.0, 1.0)`?
|
||||
|
||||
* Догадайтесь как использовать `u_mouse`, зная, что координаты даны в пикселях и НЕ нормализованы. Можете ли вы изменять цвета с помощью этой переменной?
|
||||
|
||||
* Придумайте какой-нибудь интересный способ изменения цветов с помощью `u_time` и `u_mouse`.
|
||||
|
||||
После выполнения этих упражнения у вас скорее всего возникнет вопрос: где ещё можно применить мощь шейдеров? В следующей главе вы научитесь создавать шейдерные инструменты на three.js, Processing, и openFrameworks.
|
190
04/README-ru.md
Normal file
190
04/README-ru.md
Normal file
@ -0,0 +1,190 @@
|
||||
## Запуск шейдера
|
||||
|
||||
В процессе создания этой книги и просто из любви к искусству я создал набор инструментов для создания, отображения, опубликования и сопровождения шейдеров. Эти инструменты работают одинаково на Linux, MacOS, [Raspberry Pi](https://www.raspberrypi.org/) и в браузерах без переписывания кода.
|
||||
|
||||
**Отображение**: все интерактивные примеры в книге показываются с помощью [glslCanvas](https://github.com/patriciogonzalezvivo/glslCanvas), который делает процесс запуска отдельных шейдеров невероятно простым.
|
||||
|
||||
```html
|
||||
<canvas class="glslCanvas" data-fragment-url=“yourShader.frag" data-textures=“yourInputImage.png” width="500" height="500"></canvas>
|
||||
```
|
||||
|
||||
Как видите, для этого нужен всего лишь элемент `canvas` с классом `class="glslCanvas"` и URL шейдера в свойстве `data-fragment-url`. Подробнее о нём можно узнать [здесь](https://github.com/patriciogonzalezvivo/glslCanvas).
|
||||
|
||||
Если вы разделяете мой подход к разработке, вам возможно захочется запускать шейдеры напрямую из консоли, в чём вам поможет [glslViewer](https://github.com/patriciogonzalezvivo/glslViewer). Это приложение позволяет встраивать шейдеры в `bash`-скрипты или конвейер unix, и может использоваться по аналогии с [ImageMagick](http://www.imagemagick.org/script/index.php). Так же, [glslViewer](https://github.com/patriciogonzalezvivo/glslViewer) - отличный способ скомпилировать шейдеры на [Raspberry Pi](https://www.raspberrypi.org/), и поэтому он используется на [openFrame.io](http://openframe.io/) для демонстрации шейдеров. Подробнее с этим приложением можно познакомиться [здесь](https://github.com/patriciogonzalezvivo/glslViewer).
|
||||
|
||||
```bash
|
||||
glslViewer yourShader.frag yourInputImage.png —w 500 -h 500 -s 1 -o yourOutputImage.png
|
||||
```
|
||||
|
||||
**Создание**: чтобы сделать опыт создания шейдеров более ярким, я сделал онлайн-редактор [glslEditor](https://github.com/patriciogonzalezvivo/glslEditor). Этот редактор используется для демонстрации интерактивных примеров в книге и предоставляет набор виджетов, делающих абстрактный код на GLSL более осязаемым. Его так же можно запустить в отдельном веб-приложении по адресу [editor.thebookofshaders.com](http://editor.thebookofshaders.com/). Подробнее о редакторе [здесь](https://github.com/patriciogonzalezvivo/glslEditor).
|
||||
|
||||
![](glslEditor-01.gif)
|
||||
|
||||
Если вы предпочитаете работать оффлайн в редакторе [SublimeText](https://www.sublimetext.com/), вы можете установить [пакет для glslViewer](https://packagecontrol.io/packages/glslViewer). Подробнее [здесь](https://github.com/patriciogonzalezvivo/sublime-glslViewer).
|
||||
|
||||
![](glslViewer.gif)
|
||||
|
||||
**Публикация**: онлайн-редактор ([editor.thebookofshaders.com/](http://editor.thebookofshaders.com/)) может опубликовать ваш шейдер! Как встраиваемая, так и отдельная версия содержат кнопку «экспорт», которая выдаёт уникальные URL каждому шейдеру. Так же есть возможность выгружать шейдеры сразу на [openFrame.io](http://openframe.io/).
|
||||
|
||||
![](glslEditor-00.gif)
|
||||
|
||||
**Сопровождение**: Публикация кода - это только первый шаг на пути вашего шейдера в качестве художественного произведения. Помимо возможности экспорта на [openFrame.io](http://openframe.io/), я создал инструмент [glslGallery](https://github.com/patriciogonzalezvivo/glslGallery) для размещения шейдеров в галерее, которую можно встроить на любой сайт. Подробнее [здесь](https://github.com/patriciogonzalezvivo/glslGallery).
|
||||
|
||||
![](glslGallery.gif)
|
||||
|
||||
## Запуск шейдера в вашем любимом ферймворке
|
||||
|
||||
Если у вас уже есть опыт программирования на таких фреймворках, как [Processing](https://processing.org/), [Three.js](http://threejs.org/) или [OpenFrameworks](http://openframeworks.cc/), вы можете попробовать шейдеры прямо в них. Ниже показаны способы установки используемых в книге uniform-переменных на некоторых популярных фреймворках. В [репозитории](https://github.com/patriciogonzalezvivo/thebookofshaders/tree/master/04) этой главы на Гитхабе вы найдёте полный исходный код для этих трёх фреймворков.
|
||||
|
||||
### **Three.js**
|
||||
|
||||
Рикардо Кабелло (aka [MrDoob](https://twitter.com/mrdoob) ) и группа [единомышленников](https://github.com/mrdoob/three.js/graphs/contributors) разработали один из лучших WebGL-фреймворков под названием [Three.js](http://threejs.org/). Там вы найдёте множество примеров, учебных курсов и книг с помощью которых можно создавать крутую 3D-графику на JavaScript.
|
||||
|
||||
Ниже вы видите минимальный пример HTML и JS-кода, позволяющего начать использовать шейдеры на three.js. Обратите внимание на скрипт с `id="fragmentShader"`. Именно благодаря ему вы можете копировать шейдеры из книги.
|
||||
|
||||
```html
|
||||
<body>
|
||||
<div id="container"></div>
|
||||
<script src="js/three.min.js"></script>
|
||||
<script id="vertexShader" type="x-shader/x-vertex">
|
||||
void main() {
|
||||
gl_Position = vec4( position, 1.0 );
|
||||
}
|
||||
</script>
|
||||
<script id="fragmentShader" type="x-shader/x-fragment">
|
||||
uniform vec2 u_resolution;
|
||||
uniform float u_time;
|
||||
|
||||
void main() {
|
||||
vec2 st = gl_FragCoord.xy/u_resolution.xy;
|
||||
gl_FragColor=vec4(st.x,st.y,0.0,1.0);
|
||||
}
|
||||
</script>
|
||||
<script>
|
||||
var container;
|
||||
var camera, scene, renderer;
|
||||
var uniforms;
|
||||
|
||||
init();
|
||||
animate();
|
||||
|
||||
function init() {
|
||||
container = document.getElementById( 'container' );
|
||||
|
||||
camera = new THREE.Camera();
|
||||
camera.position.z = 1;
|
||||
|
||||
scene = new THREE.Scene();
|
||||
|
||||
var geometry = new THREE.PlaneBufferGeometry( 2, 2 );
|
||||
|
||||
uniforms = {
|
||||
u_time: { type: "f", value: 1.0 },
|
||||
u_resolution: { type: "v2", value: new THREE.Vector2() },
|
||||
u_mouse: { type: "v2", value: new THREE.Vector2() }
|
||||
};
|
||||
|
||||
var material = new THREE.ShaderMaterial( {
|
||||
uniforms: uniforms,
|
||||
vertexShader: document.getElementById( 'vertexShader' ).textContent,
|
||||
fragmentShader: document.getElementById( 'fragmentShader' ).textContent
|
||||
} );
|
||||
|
||||
var mesh = new THREE.Mesh( geometry, material );
|
||||
scene.add( mesh );
|
||||
|
||||
renderer = new THREE.WebGLRenderer();
|
||||
renderer.setPixelRatio( window.devicePixelRatio );
|
||||
|
||||
container.appendChild( renderer.domElement );
|
||||
|
||||
onWindowResize();
|
||||
window.addEventListener( 'resize', onWindowResize, false );
|
||||
|
||||
document.onmousemove = function(e){
|
||||
uniforms.u_mouse.value.x = e.pageX
|
||||
uniforms.u_mouse.value.y = e.pageY
|
||||
}
|
||||
}
|
||||
|
||||
function onWindowResize( event ) {
|
||||
renderer.setSize( window.innerWidth, window.innerHeight );
|
||||
uniforms.u_resolution.value.x = renderer.domElement.width;
|
||||
uniforms.u_resolution.value.y = renderer.domElement.height;
|
||||
}
|
||||
|
||||
function animate() {
|
||||
requestAnimationFrame( animate );
|
||||
render();
|
||||
}
|
||||
|
||||
function render() {
|
||||
uniforms.u_time.value += 0.05;
|
||||
renderer.render( scene, camera );
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
```
|
||||
|
||||
### **Processing**
|
||||
|
||||
[Processing](https://processing.org/) - необычайная простая и мощная среда, созданная [Беном Фраем](http://benfry.com/) и [Кэси Рис](http://reas.com/) в 2001 году. Она хорошо подходит для того, чтобы сделать ваши первые шаги в программировании (по крайней мере, так было у меня). [Андре Колубри](https://codeanticode.wordpress.com/) добавил в Processing поддержку OpenGL и видео, из-за чего играть с шейдерами в ней стало проще простого. Processing ищет файл с именем `shader.frag` в папке `data` вашего скетча. Таким образом, вы можете просто скопировать пример отсюда и переименовать файл.
|
||||
|
||||
```cpp
|
||||
PShader shader;
|
||||
|
||||
void setup() {
|
||||
size(640, 360, P2D);
|
||||
noStroke();
|
||||
|
||||
shader = loadShader("shader.frag");
|
||||
}
|
||||
|
||||
void draw() {
|
||||
shader.set("u_resolution", float(width), float(height));
|
||||
shader.set("u_mouse", float(mouseX), float(mouseY));
|
||||
shader.set("u_time", millis() / 1000.0);
|
||||
shader(shader);
|
||||
rect(0,0,width,height);
|
||||
}
|
||||
```
|
||||
|
||||
Чтобы шейдеры запустились на версиях ниже 2.1, добавьте строку `#define PROCESSING_COLOR_SHADER` в начало шейдера. В итоге, код шейдера будет выглядеть примерно так:
|
||||
|
||||
```glsl
|
||||
#ifdef GL_ES
|
||||
precision mediump float;
|
||||
#endif
|
||||
|
||||
#define PROCESSING_COLOR_SHADER
|
||||
|
||||
uniform vec2 u_resolution;
|
||||
uniform vec3 u_mouse;
|
||||
uniform float u_time;
|
||||
|
||||
void main() {
|
||||
vec2 st = gl_FragCoord.st/u_resolution;
|
||||
gl_FragColor = vec4(st.x,st.y,0.0,1.0);
|
||||
}
|
||||
```
|
||||
|
||||
Более подробную информацию о шейдерах в Processing ищите в [этом руководстве](https://processing.org/tutorials/pshader/).
|
||||
|
||||
### **openFrameworks**
|
||||
|
||||
У каждого человека есть место, где ему комфортно, и для меня таковым остаётся [сообщество openFrameworks](http://openframeworks.cc/). Этот C++-фреймворк является обёрткой для OpenGL и других библиотек на C++. Он во многом похож на Processing с поправкой на особенности использования компиляторов языка C++. Как и Processing, openFramework ищет шейдеры в папке `data`, поэтому не забудьте скопировать в него и файлы с расширением `.frag` и переименовывать их перед использованием.
|
||||
|
||||
```cpp
|
||||
void ofApp::draw(){
|
||||
ofShader shader;
|
||||
shader.load("","shader.frag");
|
||||
|
||||
shader.begin();
|
||||
shader.setUniform1f("u_time", ofGetElapsedTimef());
|
||||
shader.setUniform2f("u_resolution", ofGetWidth(), ofGetHeight());
|
||||
ofRect(0,0,ofGetWidth(), ofGetHeight());
|
||||
shader.end();
|
||||
}
|
||||
```
|
||||
|
||||
За более подробной информацией о шейдерах в openFrameworks обратитесь к [этому руководству](http://openframeworks.cc/ofBook/chapters/shaders.html) от [Joshua Noble](http://thefactoryfactory.com/).
|
@ -20,7 +20,7 @@ glslViewer yourShader.frag yourInputImage.png —w 500 -h 500 -s 1 -o yourOutput
|
||||
|
||||
![](glslEditor-01.gif)
|
||||
|
||||
If you prefer to work offline using [SublimeText](https://www.sublimetext.com/) you can install this [package for glslViewer](https://packagecontrol.io/packages/glslViewer). Learn more about it [here](https://github.com/patriciogonzalezvivo/sublime-glslViewer)
|
||||
If you prefer to work offline using [SublimeText](https://www.sublimetext.com/) you can install this [package for glslViewer](https://packagecontrol.io/packages/glslViewer). Learn more about it [here](https://github.com/patriciogonzalezvivo/sublime-glslViewer).
|
||||
|
||||
![](glslViewer.gif)
|
||||
|
||||
|
140
05/README-ru.md
Normal file
140
05/README-ru.md
Normal file
@ -0,0 +1,140 @@
|
||||
# Алгоритмическое рисование
|
||||
## Формообразующие функции
|
||||
|
||||
Эта глава могла бы называться «Урок с забором от мистера Мияги». Ранее мы отобразили нормализованные координаты *x* и *y* в красный и зелёный цветовые каналы. По сути, мы сделали функцию, которая принимает двумерный вектор (x и y) и возвращает четырёхмерный вектор (r, g, b и а). Но прежде чем мы погрузимся глубже в трансформацию данных между измерениями, не помешает начать с более простых вещей. То есть с понимания способов конструирования одномерных функций. Чем больше времени и энергии вы потратите на освоение этого материала, тем сильнее будет ваше шейдерное карате.
|
||||
|
||||
![Парень-каратист (1984)](mr_miyagi.jpg)
|
||||
|
||||
Следующий код будет нашим забором. В нём мы визуализируем нормированное значение координаты *x* (`st.x`) двумя способами: с помощью яркости (обратите внимание на градиент от чёрного к белому) и путём построения зелёной линии поверх (в этом случае значение *x* записывается напрямую в *y*). Вы можете пока не вникать в функцию построения графика. Она будет детально рассмотрена далее.
|
||||
|
||||
<div class="codeAndCanvas" data="linear.frag"></div>
|
||||
|
||||
**На заметку**: Конструктор типа `vec3` «понимает», что вы хотите присвоить одно и то же значение всем трём каналам, а `vec4` понимает, что четырёхмерный вектор нужно собрать из трёхмерного вектора и одного числа. Это число в данном случае отвечает за альфа канал, или прозрачность. Примеры этого поведения вы можете видеть в строках 20 и 26.
|
||||
|
||||
Этот код - это ваш забор; важно видеть и понимать его. Вы раз за разом будете возвращаться в пространство между *0.0* и *1.0*. Вы изучите искусство смешивания и формирования линий.
|
||||
|
||||
Такое отображение один-в-один между *x* и *y* (или яркостью) называется *линейной интерполяцией*. Начиная с этого момента, мы можем использовать математические функции для придания *формы* линии. Например, мы можем возвести *x* в пятую степень, чтобы получить *кривую* линию.
|
||||
|
||||
<div class="codeAndCanvas" data="expo.frag"></div>
|
||||
|
||||
Интересно, правда? Попробуйте различные степени в 22 строке, например 20.0, 2.0, 1.0, 0.0, 0.2 и 0.02. Понимание соотношения между числом и его степенью будет очень полезным. Использование математических функций такого типа даст вам мощное выразительное средство, позволяющее тонко управлять потоком значений.
|
||||
|
||||
[`pow()`](../glossary/?search=pow) - одна из многих встроенных функций языка GLSL. Большинство из них ускорены на аппаратном уровне, а значит при их правильном и осмотрительном использовании ваш код станет быстрее.
|
||||
|
||||
Замените функцию степени в строке 22 на какую-нибудь другую. Попробуйте [`exp()`](../glossary/?search=exp), [`log()`](../glossary/?search=log) и [`sqrt()`](../glossary/?search=sqrt). Некоторые из этих функций более интересны при использовании числа Пи. В восьмой строке я определил макрос, заменяющий любое упоминание `PI` на `3.14159265359`.
|
||||
|
||||
### Step и Smoothstep
|
||||
|
||||
В GLSL так же есть несколько уникальных функций интерполяции с аппаратным ускорением.
|
||||
|
||||
Функция [`step()`](../glossary/?search=step) (ступенька) принимает два параметра. Первый параметр задаёт значение порога, а второй - точку, в которой мы хотим вычислить функцию. В любой точке до порога функция возвращает `0.0`, а в любой точке после него - `1.0`.
|
||||
|
||||
Попробуйте изменить значение порога в 20 строке в следующем коде.
|
||||
|
||||
<div class="codeAndCanvas" data="step.frag"></div>
|
||||
|
||||
Ещё одна уникальная функция называется [`smoothstep()`](../glossary/?search=smoothstep) (гладкая ступенька). Эта функция гладко интерполирует аргумент в интервале между двумя заданными числами. Первые два параметра задают начало и конец переходного интервала, а третий - точку, в которой нужно интерполировать.
|
||||
|
||||
<div class="codeAndCanvas" data="smoothstep.frag"></div>
|
||||
|
||||
В строке 12 приведённого выше кода мы используем smoothstep для рисования зелёной линии в функции `plot()`. Для каждой точки вдоль оси *x* эта функция делает *всплеск* при нужно значении *y*. Как? Через соединение двух [`smoothstep()`](../glossary/?search=smoothstep). Рассмотрите следующую функцию, вставьте её вместо строки 20 в коде выше и вообразите, что это вертикальный разрез. Фон выглядит как линия, не так ли?
|
||||
|
||||
```glsl
|
||||
float y = smoothstep(0.2,0.5,st.x) - smoothstep(0.5,0.8,st.x);
|
||||
```
|
||||
|
||||
### Синус и косинус
|
||||
|
||||
Синус и косинус - ваши лучшие друзья, когда вы используете математику для анимации, построения фигур или смешивания значений.
|
||||
|
||||
Эти две базовые тригонометрические функции при построении кругов удобны, как швейцарский армейский нож, и обычно они используются в паре. Очень важно знать как они себя ведут и какими способами могут быть скомбинированны. Вкратце, они принимают угол в радианах и возвращают координаты *x* ([косинус](../glossary/?search=cos)) и *y* ([синус](../glossary/?search=sin)) точки на окружности единичного радиуса. Тот факт, что они возвращают нормализованные значения (между -1 и 1) и при этом являются достаточно гладкими, делает их незаменимым инструментом.
|
||||
|
||||
![](sincos.gif)
|
||||
|
||||
Описать все взаимоотношения между кругами и тригонометрическими функциями довольно трудно, но анимация выше отлично их демонстрирует.
|
||||
|
||||
<div class="simpleFunction" data="y = sin(x);"></div>
|
||||
|
||||
Внимательно присмотритесь к этой синусоидальной волне. Обратите внимание на плавное изменение значения *y* между -1 и 1. Как мы видели в примере со временем в предыдущем параграфе, это ритмичное поведение синуса [`sin()`](../glossary/?search=sin) можно использовать в анимациях. Если вы читаете этот пример в браузере, вы можете поизменять формулу выше и пронаблюдать как изменяется волна. Не забывайте ставить точку с запятой в конце строки.
|
||||
|
||||
Попробуйте проделать следующие действия и посмотрите что происходит:
|
||||
|
||||
* Прибавьте время (`u_time`) к *x* перед вычислением синуса. Вы увидите *движение* вдоль *x*.
|
||||
|
||||
* Домножьте *x* на Пи перед вычислением синуса. Обратите внимание на сужение графика, так что теперь волна повторяется каждые два отсчёта по горизонтальной оси.
|
||||
|
||||
* Умножьте `u_time` на *x* перед вычислением синуса. Вы увидите, как *частота* волн увеличивается со временем. Возможно, `u_time` к этому времени будет слишком большой, что затруднит восприятие графика.
|
||||
|
||||
* Прибавьте 1.0 к [`sin(x)`](../glossary/?search=sin). Вся волна *сдвинется* вверх и займёт область значений от 0.0 до 2.0.
|
||||
|
||||
* Умножьте [`sin(x)`](../glossary/?search=sin) на 2.0. *Амплитуда* увеличится вдвое.
|
||||
|
||||
* Вычислите абсолютное значение синуса с помощью [`abs()`](../glossary/?search=abs). График станет похожим на траекторию подпрыгивающего мячика.
|
||||
|
||||
* Возьмите дробную часть от [`sin(x)`](../glossary/?search=sin) с помощью [`fract()`](../glossary/?search=fract).
|
||||
|
||||
* Сложите результаты вычисления синуса, округлённые до целого в большую ([`ceil()`](../glossary/?search=ceil)) и в меньшую ([`floor()`](../glossary/?search=floor)) стороны. Получится «цифровой» прямоугольный сигнал со значениями 1 и -1.
|
||||
|
||||
### Другие полезные функции
|
||||
|
||||
В конце предыдущего упражнения мы затронули несколько новых функций. Теперь давайте поэкспериментируем. Попробуйте раскомментировать строки в коде ниже по одной. Запомните эти функции и изучите их поведение. Возможно, вы спросите, зачем это нужно? Быстрый поиск в google по запросу «generative art» даст ответ. Помните, что пока мы осваиваем перемещение в одном измерении, вверх и вниз. Но скоро мы перейдём к двум, трём и даже четырём измерениям!
|
||||
|
||||
![Anthony Mattox (2009)](anthony-mattox-ribbon.jpg)
|
||||
|
||||
<div class="simpleFunction" data="y = mod(x,0.5); // x по модулю 0.5
|
||||
//y = fract(x); // возвращает дробную часть аргумента
|
||||
//y = ceil(x); // ближайшее целое, большее либо равное x
|
||||
//y = floor(x); // ближайшее целое, меньшее либо равное x
|
||||
//y = sign(x); // знак x
|
||||
//y = abs(x); // абсолютное значение x
|
||||
//y = clamp(x,0.0,1.0); // ограничение x промежутком от 0.0 до 1.0
|
||||
//y = min(0.0,x); // меньшее из x и 0.0
|
||||
//y = max(0.0,x); // большее из x и 0.0 "></div>
|
||||
|
||||
### Продвинутые функции
|
||||
|
||||
[Голан Левин](http://www.flong.com/) написал отличный учебник по более сложным функциям, которые могут понадобиться. Начните собирать вашу собственную библиотеку полезных кусочков кода с портирования этих функций на GLSL.
|
||||
|
||||
* Полиномиальные функции: [www.flong.com/texts/code/shapers_poly](http://www.flong.com/texts/code/shapers_poly/)
|
||||
|
||||
* Экспоненциальные функции: [www.flong.com/texts/code/shapers_exp](http://www.flong.com/texts/code/shapers_exp/)
|
||||
|
||||
* Круги и эллипсы: [www.flong.com/texts/code/shapers_circ](http://www.flong.com/texts/code/shapers_circ/)
|
||||
|
||||
* Сплайны Безье и другие параметрические функции: [www.flong.com/texts/code/shapers_bez](http://www.flong.com/texts/code/shapers_bez/)
|
||||
|
||||
<div class="glslGallery" data="160414041542,160414041933,160414041756" data-properties="clickRun:editor,hoverPreview:false"></div>
|
||||
|
||||
Подобно поварам, собирающим специи и экзотические ингридиенты, цифровые художники уделяют особое внимание работе над своими собственными формообразующими функциями.
|
||||
|
||||
[Иниго Квилес](http://www.iquilezles.org/) собрал хорошую коллекцию [полезных функций](http://www.iquilezles.org/www/articles/functions/functions.htm). После прочтения [статьи](http://www.iquilezles.org/www/articles/functions/functions.htm) посмотрите на реализацию этих функций на GLSL. Обратите внимание на незначительность потребовавшихся изменений. Например, пришлось использовать точку в числах с плавающей точкой и заменить функций из *C* на их GLSL-аналоги: `pow()` вместо `powf()` и т.п.
|
||||
|
||||
<div class="glslGallery" data="05/impulse,05/cubicpulse,05/expo,05/expstep,05/parabola,05/pcurve" data-properties="clickRun:editor,hoverPreview:false"></div>
|
||||
|
||||
Для поддержания вашего вдохновения, посмотрите на элегантный пример использования одномерных функций, написанный автором [Danguafer](https://www.shadertoy.com/user/Danguafer) на ShaderToy.
|
||||
|
||||
<iframe width="800" height="450" frameborder="0" src="https://www.shadertoy.com/embed/XsXXDn?gui=true&t=10&paused=true" allowfullscreen></iframe>
|
||||
|
||||
В следующей главе мы сделаем ещё один шаг. Сначала мы посмешиваем цвета, а затем перейдём к рисованию фигур.
|
||||
|
||||
#### Упражнение
|
||||
|
||||
Рассмотрите таблицу с формулами, созданную автором [Kynd](http://www.kynd.info/log/). Он комбинирует функции и их свойства, чтобы контролировать значения между 0.0 и 1.0. Попробуйте воспроизвести эти функции самостоятельно. Помните, что чем больше вы тренируетесь, тем сильнее станет ваше карате.
|
||||
|
||||
![Kynd - www.flickr.com/photos/kynd/9546075099/ (2013)](kynd.png)
|
||||
|
||||
#### Инструментарий
|
||||
|
||||
Здесь собраны ссылки на инструменты, которые упростят визуализацию одномерных функций.
|
||||
|
||||
* Grapher: если у вас есть компьютер с MacOS, введите grapher в spotlight и воспользуйтесь этим инструментом.
|
||||
|
||||
![Grapher в OS X (2004)](grapher.png)
|
||||
|
||||
* [GraphToy](http://www.iquilezles.org/apps/graphtoy/): уже знакомый нам [Иниго Квилес](http://www.iquilezles.org) написал инструмент для визуализации GLSL-функций в WebGL.
|
||||
|
||||
![Иниго Квилес - GraphToy (2010)](graphtoy.png)
|
||||
|
||||
* [Shadershop](http://tobyschachman.com/Shadershop/): этот замечательный инструмент, созданный [Тоби Шахманом](http://tobyschachman.com/), научит вас конструировать сложные функции необычайно наглядным и интуитивным способом.
|
||||
|
||||
![Тоби Шахман - Shadershop (2014)](shadershop.png)
|
@ -65,11 +65,7 @@ Try the following exercises and notice what happens:
|
||||
|
||||
* Multiply time (`u_time`) by *x* before computing the `sin`. See how the **frequency** between phases becomes more and more compressed. Note that u_time may have already become very large, making the graph hard to read.
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
* Add 1.0 to [```sin(x)```](../glossary/?search=sin). See how all the wave is **displaced** up and now all values are between 0.0 and 2.0.
|
||||
=======
|
||||
* Add 1.0 to [`sin(x)`](../glossary/?search=sin). See how all the wave is **displaced** up and now all values are between 0.0 and 2.0.
|
||||
>>>>>>> Stashed changes
|
||||
|
||||
* Multiply [`sin(x)`](../glossary/?search=sin) by 2.0. See how the **amplitude** doubles in size.
|
||||
|
||||
|
143
06/README-ru.md
Normal file
143
06/README-ru.md
Normal file
@ -0,0 +1,143 @@
|
||||
![Пол Кли - диаграмма цвета (1931)](klee.jpg)
|
||||
|
||||
## Цвета
|
||||
|
||||
Ранее мы не очень подробно раскрывали тему работы с векторами в GLSL. Прежде чем двинуться дальше, не помешает изучить векторы поглубже, и работа с цветом как нельзя лучше подходит для этого.
|
||||
|
||||
Если вы знакомы с объектно-ориентированной парадигмой программирования, вы скорее всего заметили, что мы обращаемся к данным в векторах на манер структур (`struct`) в языке С.
|
||||
|
||||
```glsl
|
||||
vec3 red = vec3(1.0,0.0,0.0);
|
||||
red.x = 1.0;
|
||||
red.y = 0.0;
|
||||
red.z = 0.0;
|
||||
```
|
||||
|
||||
Работа с цветом через *x*, *y* и *z* может сбить с толку и ввести в заблуждение, не так ли? Поэтому в GLSL можно получить доступ к тем же самым данным через другие имена. Значения `.x`, `.y` и `.z` так же именуются `.r`, `.g` и `.b`, и `.s`, `.t` и `.p`. Последний набор имён обычно используется для пространственных координат текстур, с которыми мы познакомимся в следующих главах. Кроме того, для доступа к элементам вектора можно использовать численные индексы `[0]`, `[1]` и `[2]`.
|
||||
|
||||
Следующие строки демонстрируют все способы доступа к одним и тем же данным:
|
||||
|
||||
```glsl
|
||||
vec4 vector;
|
||||
vector[0] = vector.r = vector.x = vector.s;
|
||||
vector[1] = vector.g = vector.y = vector.t;
|
||||
vector[2] = vector.b = vector.z = vector.p;
|
||||
vector[3] = vector.a = vector.w = vector.q;
|
||||
```
|
||||
|
||||
Все эти способы - всего лишь дополнительные имена для доступа к одним и тем же данным, которые призваны помочь вам в создании более понятного кода. Эта гибкость языка шейдеров станет путеводной нитью, помогающей вам мыслить о значениях цвета и координатах пространства как о взаимозаменяемых сущностях.
|
||||
|
||||
Другое замечательное свойство векторных типов GLSL - это возможность доступа к координатам в произвольном порядке, которая упрощает преобразование и смешивание значений.
|
||||
|
||||
```glsl
|
||||
vec3 yellow, magenta, green;
|
||||
|
||||
// Задаём жёлтый цвет
|
||||
yellow.rg = vec2(1.0); // Записываем 1.0 в красный и зелёный каналы
|
||||
yellow[2] = 0.0; // Записываем 0.0 в синий
|
||||
|
||||
// Задаём малиновый цвет
|
||||
magenta = yellow.rbg; // Меняем местами зелёный и синий
|
||||
|
||||
// Задаём зелёный цвет
|
||||
green.rgb = yellow.bgb; // Записываем значение синего канала жёлтого цвета (0) в красный и синий
|
||||
```
|
||||
|
||||
#### Инструментарий
|
||||
|
||||
Возможно, вам ранее не приходилось подбирать цвет с помощью чисел. Это выглядит очень контринтуитивно. К счастью, есть множество умных программ, которые упрощают это занятие. Найдите наиболее подходящую для себя и научите её представлять цвета в форматах `vec3` или `vec4`. Например, вот такие шаблоны я использую в [Spectrum](http://www.eigenlogik.com/spectrum/mac):
|
||||
|
||||
```
|
||||
vec3({{rn}},{{gn}},{{bn}})
|
||||
vec4({{rn}},{{gn}},{{bn}},1.0)
|
||||
```
|
||||
|
||||
### Смешивание цветов
|
||||
|
||||
Теперь, когда вы знаете как задаются цвета, мы можем собрать все новые знания воедино. В GLSL есть очень полезная функция [`mix()`](../glossary/?search=mix), которая смешивает значения в указанной пропорции. Угадайте, как задаются пропорции? Конечно же, числом от 0.0 до 1.0! И это просто отлично, учитывая те долгие часы, что вы практиковали движения карате с забором. Самое время применить их на практике!
|
||||
|
||||
![](mix-f.jpg)
|
||||
|
||||
В строке 18 в следующем куске кода мы используем абсолютное значение синуса от времени для смешивания цветов `A` и `B`.
|
||||
|
||||
<div class="codeAndCanvas" data="mix.frag"></div>
|
||||
|
||||
Покажите, что вы можете:
|
||||
|
||||
* Создайте экспрессивный переход между цветами. Вообразите какую-нибудь эмоцию. Какой цвет больше всего ассоциируется с ней? Как она появляется? Как она сходит на нет? Придумайте другую эмоцию и подходящий для неё цвет. Поменяйте начальный и конечный цвета в коде выше в соответствии с этими эмоциями. Анимируйте переход с помощью функций формы. Роберт Пеннер разработал набор популярных функций для компьютерной анимации, известный под названием [упрощающих функций](http://easings.net/). Вы можете использовать [этот пример](../edit.php#06/easing.frag) для исследования и поиска вдохновения, но вы сможете достигнуть наилучшего результата только создав собственные функции перехода.
|
||||
|
||||
### Играемся с градиентами
|
||||
|
||||
Функция [`mix()`](../glossary/?search=mix) способна на большее. Вместо одного числа с плавающей точкой мы можем передать переменную того же типа, что и первые два аргумента (`vec3` в нашем случае). Таким образом мы можем управлять пропорциями каждого канала `r`, `g` и `b` по отдельности.
|
||||
|
||||
![](mix-vec.jpg)
|
||||
|
||||
Рассмотрим следующий пример. Как и в примерах предыдущей главы, мы преобразуем значение перехода к нормализованной координате *x* и визуализируем его с помощью линии. Сейчас все каналы изменяются по одному и тому же закону.
|
||||
|
||||
Теперь раскомментируйте строку 25 и посмотрите что произойдёт. Затем попробуйте раскомментировать строки 26 и 27. Помните, что линии показывают пропорции каждого канала при смешивании цветов `A` и `B`.
|
||||
|
||||
<div class="codeAndCanvas" data="gradient.frag"></div>
|
||||
|
||||
Вы скорее всего узнали три функции, которые мы используем в строках 25-27. Поиграйте с ними! Исследуйте и демонстрируйте ваши находки, используя умения из предыдущей главы для создания интересных градиентов. Попробуйте выполнить следующие упражнения:
|
||||
|
||||
![Вильям Тёрнер - Последний рейс корабля Отважный (1838)](turner.jpg)
|
||||
|
||||
* Создайте градиент, повторяющий закат Вильяма Тёрнера.
|
||||
|
||||
* Сделайте анимацию перехода от рассвета к закату с помощью `u_time`.
|
||||
|
||||
* Можете ли вы сделать радугу, используя изученный материал?
|
||||
|
||||
* Используйте функцию `step()` для создания цветного флага.
|
||||
|
||||
### HSB
|
||||
|
||||
Нельзя рассказать о цветах, не упомянув цветовое пространство. Как вы возможно знаете, есть множество способов задания цвета кроме красного, зелёного и синего каналов.
|
||||
|
||||
[HSB](https://ru.wikipedia.org/wiki/HSV_(%D1%86%D0%B2%D0%B5%D1%82%D0%BE%D0%B2%D0%B0%D1%8F_%D0%BC%D0%BE%D0%B4%D0%B5%D0%BB%D1%8C)) расшифровывается как оттенок (Hue), насыщенность (Saturation) и яркость (Brightness или Value), и является более интуитивным способом представления цвета. Прочитайте код функций `rgb2hsv()` и `hsv2rgb()` в примере ниже.
|
||||
|
||||
Отображая координату `x` в оттенок, а координату `y` - в яркость, мы получаем красивый спектр видимых цветов. Такое пространственное распределение цветов очень удобно. Выбор цветов в пространстве HSB более интуитивен, чем RGB.
|
||||
|
||||
<div class="codeAndCanvas" data="hsb.frag"></div>
|
||||
|
||||
### HSB в полярных координатах
|
||||
|
||||
Пространство HSB изначально было разработано для представления в полярных координатах (на основе угла и радиуса), вместо декартовых (x и y). Чтобы перевести нашу функцию HSB в полярные координаты, нужно вычислить угол и расстояние, используя центр области рисования и декартовы координаты пикселя. Для этого мы используем функцию вычисления расстояния [`length()`](../glossary/?search=length) и двухаргументный арктангенс ([`atan(y,x)`](../glossary/?search=atan), GLSL-версия известной функции `atan2(y,x)`).
|
||||
|
||||
Тригонометрические функции от векторного аргумента вычисляются покомпонетно, даже если вы используете векторы для представления цветов. Мы начинаем использовать цвета и векторы одинаковым способом. Такая концептуальная гибкость является очень мощным инструментом.
|
||||
|
||||
**На заметку:** Возможно, вы спросите, какие ещё есть геометрически функции кроме длины ([`length`](../glossary/?search=length))? Их довольно много: [`dot()`](../glossary/?search=dot) (скалярное произведение), [`cross`](../glossary/?search=cross) (векторное произведение), [`normalize()`](../glossary/?search=normalize) (привести вектор к единичной длине), [`faceforward()`](../glossary/?search=faceforward) (вернуть вектор, указывающий в то же полупространство, что и данный), [`reflect()`](../glossary/?search=reflect) (отражение) и [`refract()`](../glossary/?search=refract) (преломление). Так же в GLSL есть векторные функции сравнения: [`lessThan()`](../glossary/?search=lessThan) (меньше), [`lessThanEqual()`](../glossary/?search=lessThanEqual) (меньше либо равно), [`greaterThan()`](../glossary/?search=greaterThan) (больше), [`greaterThanEqual()`](../glossary/?search=greaterThanEqual) (больше либо равно), [`equal()`](../glossary/?search=equal) (равно) и [`notEqual()`](../glossary/?search=notEqual) (не равно).
|
||||
|
||||
Получив угол и длину, мы должны нормировать их значения в интервал от 0.0 до 1.0. В строке 27 [`atan(y,x)`](../glossary/?search=atan) возвращает угол в радианах от минус пи до пи, поэтому сначала мы разделим его на удвоенное пи (`TWO_PI`, определено вначале кода), и к полученному числу от -0.5 до 0.5 прибавим 0.5, чтобы перейти в нужный интервал от 0.0 до 1.0. Максимальный радиус будет равен 0.5 (мы вычисляли расстояние от центра окна), поэтому его нужно удвоить.
|
||||
|
||||
Как видите, в этом разделе мы в основном играли с преобразованием значений в нужный нам промежуток от 0.0 до 1.0.
|
||||
|
||||
<div class="codeAndCanvas" data="hsb-colorwheel.frag"></div>
|
||||
|
||||
Попробуйте выполнить следующие упражнения:
|
||||
|
||||
* Модифицируйте пример с полярными координатами так, чтобы цветовой круг вращался, как указатель мыши в режиме ожидания.
|
||||
|
||||
* Используйте функции формы совместно с функцией преобразования HSB->RGB для расширения области круга с каким-нибудь одним оттенком и урезания других оттенков.
|
||||
|
||||
![Вильям Хоум Лизарс - Красный, синий и жёлтый спектры в составе солнечного спектра (1834)](spectrums.jpg)
|
||||
|
||||
* Присмотревшись к цветовому кругу в программах для подбора цвета (изображён ниже), можно заметить, что он пострен на основе красного, жёлтого и синего цветов. Например, напротив красного должен быть зелёный, но в нашем примере выше там находится голубой. Исправьте пример, так чтобы он выглядел в точности как изображение ниже (подсказка: используйте функции формы).
|
||||
|
||||
![](colorwheel.png)
|
||||
|
||||
* Прочитайте книгу [«Взаимодействие цветов» Джозефа Альберса](http://www.goodreads.com/book/show/111113.Interaction_of_Color) и воспользуйтесь следующим примером для практики.
|
||||
|
||||
<div class="glslGallery" data="160505191155,160505193939,160505200330,160509131554,160509131509,160509131420,160509131240" data-properties="clickRun:editor,openFrameIcon:false,showAuthor:false"></div>
|
||||
|
||||
#### Заметки о функциях и аргументах
|
||||
|
||||
Перед тем как нырнуть в следующую главу, давайте остановимся и немного отмотаем назад. Вернитесь и взгляните на функции в предыдущих примерах. Вы заметите слово `in` перед типами аргументов. Это - [*квалификатор*](http://www.shaderific.com/glsl-qualifiers/#inputqualifier), и в данном случае он означает, что переменная предназначена только для чтения. В последующих примерах мы увидим так же аргументы с квалификаторами `out` и `inout`. Последний эквивалентен передаче переменной по ссылке, при которой мы можем изменить переданное значение так, что изменения становятся видны за пределами функции.
|
||||
|
||||
```glsl
|
||||
int newFunction(in vec4 aVec4, // только для чтения
|
||||
out vec3 aVec3, // только на запись
|
||||
inout int aInt); // чтение и запись
|
||||
```
|
||||
|
||||
Вы не поверите, но мы уже изучили всё необходимое для создания крутой графики. В следующей главе мы научимся комбинировать все эти трюки для создания геометрических фигур с помощью смешивания пространства. Именно, *смешивание* пространства!
|
231
07/README-ru.md
Normal file
231
07/README-ru.md
Normal file
@ -0,0 +1,231 @@
|
||||
![Элис Хаббард, Провиденс, США, 1892. Фото: Zindman/Freemont.](froebel.jpg)
|
||||
|
||||
## Фигуры
|
||||
|
||||
Наконец то! Мы долго тренировались, и вот этот момент настал! Вы изучили большую часть зяыка GLSL, его типы и функции. Вы раз за разом упражнялись с одномерными функциями, составляя их в сложные формулы. И мы готовы к очередному витку синтеза полученных знаний. Вперёд! В этой главе мы научимся процедурно рисовать простые геометрические фигуры с помощью параллельных вычислений.
|
||||
|
||||
### Прямоугольник
|
||||
|
||||
Вообразите листок бумаги в клетку, как на уроке математики, где нам задали на дом нарисовать квадрат. Размер листа - 10х10, а квадрат должен быть 8х8. Справитесь?
|
||||
|
||||
![](grid_paper.jpg)
|
||||
|
||||
Наверное, вы бы закрасили всё кроме верхней и нижней строки и кроме крайних столбцов, так?
|
||||
|
||||
Как это относится к шейдерам? Каждая клетка нашего листка - это один поток выполнения (то есть пиксель). Каждая клетка знает своё положение, будто это координаты на шахматной доске. В предыдущей главе мы отображали *x* и *y* в *красный* и *зелёный* цветовые каналы, и изучили как использовать узкий участок двумерной территории между 0.0 и 1.0. Как с помощью этого нарисовать квадрат в центре окна?
|
||||
|
||||
Начнём с псевдокода, использующего ветвление (`if`) над пространством. Принцип здесь тот же, что и в случае с бумагой в клеточку.
|
||||
|
||||
```glsl
|
||||
if ( (X GREATER THAN 1) AND (Y GREATER THAN 1) ) // если ((х больше 1) и (y больше 1))
|
||||
paint white // красим в белый
|
||||
else // иначе
|
||||
paint black // красим в чёрный
|
||||
```
|
||||
|
||||
Теперь, когда мы знаем что делать, давайте заменим `if` на функцию [`step()`](../glossary/?search=step), а вместо листа 10х10 используем нормированные значения между 0.0 и 1.0.
|
||||
|
||||
```glsl
|
||||
uniform vec2 u_resolution;
|
||||
|
||||
void main(){
|
||||
vec2 st = gl_FragCoord.xy/u_resolution.xy;
|
||||
vec3 color = vec3(0.0);
|
||||
|
||||
// Каждый вызов вернёт 1.0 (белый) или 0.0 (чёрный).
|
||||
float left = step(0.1,st.x); // То же, что ( X больше 0.1 )
|
||||
float bottom = step(0.1,st.y); // То же, что ( Y больше 0.1 )
|
||||
|
||||
// Умножение left*bottom работает как логическое И.
|
||||
color = vec3( left * bottom );
|
||||
|
||||
gl_FragColor = vec4(color,1.0);
|
||||
}
|
||||
```
|
||||
|
||||
Функция [`step()`](../glossary/?search=step) окрасит пиксели ниже 0.1 в чёрный (`vec3(0.0)`), а остальные - в белый (`vec3(1.0)`). Умножение `left` на `bottom` работает как логическое `И`, где оба значения должны быть равны 1.0 чтобы вернуть 1.0. Этот код нарисует две чёрных линии: снизу и слева изображения.
|
||||
|
||||
![](rect-01.jpg)
|
||||
|
||||
В коде выше мы повторяем одно и то же действие для обеих осей. Мы можем сэкономить несколько строк кода, передав в [`step()`](../glossary/?search=step) два значения вместо одного:
|
||||
|
||||
```glsl
|
||||
vec2 borders = step(vec2(0.1),st);
|
||||
float pct = borders.x * borders.y;
|
||||
```
|
||||
|
||||
Итак, мы нарисовали две границы прямоугольника: нижнюю и левую. Давайте дорисуем верхнюю и правую. Рассмотрим следующий код:
|
||||
|
||||
<div class="codeAndCanvas" data="rect-making.frag"></div>
|
||||
|
||||
Раскомментируйте строки 21-22, чтобы обратить координаты `st` и повторить вызов [`step()`](../glossary/?search=step). Теперь `vec2(0.0,0.0)` окажется в верхнем правом углу. Это переворот страницы с повторением предыдущей процедуры.
|
||||
|
||||
![](rect-02.jpg)
|
||||
|
||||
Заметим, что в строках 18 и 22 все стороны прямоугольника перемножаются. В более развёрнутом виде это выглядит так:
|
||||
|
||||
```glsl
|
||||
vec2 bl = step(vec2(0.1),st); // низ и лево
|
||||
vec2 tr = step(vec2(0.1),1.0-st); // верх и право
|
||||
color = vec3(bl.x * bl.y * tr.x * tr.y);
|
||||
```
|
||||
|
||||
Интересно? Вся эта техника направлена на использование [`step()`](../glossary/?search=step), умножения в качестве логической операции и переворот координат.
|
||||
|
||||
Перед тем как продолжить, попробуйте выполнить упражнения:
|
||||
|
||||
* Измените размер и пропорции прямоугольника.
|
||||
|
||||
* Поэкпериментируйте с тем же кодом, используя [`smoothstep()`](../glossary/?search=smoothstep) вместо [`step()`](../glossary/?search=step). Изменяя значения интервала перехода, можно варьировать вид границ от размыливания до элегантного сглаживания.
|
||||
|
||||
* Реализуйте то же самое с помощью функции [`floor()`](../glossary/?search=floor).
|
||||
|
||||
* Выберете наиболее понравившуюся реализацию и сделайте из неё функцию, которую сможете использовать в будущем. Сделайте эту функцию гибкой и эффективной.
|
||||
|
||||
* Напишите функцию, которая рисует только контур прямоугольника.
|
||||
|
||||
* Каким образом вы будете рисовать несколько прямоугольников в одном окне и перемещать их? Если догадаетесь как это делается, попробуйте повторить композицию [Пита Мондриана](https://ru.wikipedia.org/wiki/%D0%9C%D0%BE%D0%BD%D0%B4%D1%80%D0%B8%D0%B0%D0%BD,_%D0%9F%D0%B8%D1%82).
|
||||
|
||||
![Пит Мондриан - Композиция (1921)](mondrian.jpg)
|
||||
|
||||
### Круги
|
||||
|
||||
Очень просто рисовать квадраты на клетчатой бумаге и прямоугольники в декартовых координатах, но круги требуют иного подхода, особенно в наших попиксельных алгоритмах. Одно из решений - перейти к другим пространственным координатам, так чтобы можно было использовать функцию [`step()`](../glossary/?search=step) для рисования кругов.
|
||||
|
||||
Как? Давайте вернёмся к уроку математики и бумаге в клеточку. Раскроем циркуль на радиус круга, поставим иглу циркуля в центр и нарисуем окружность поворотом циркуля.
|
||||
|
||||
![](compass.jpg)
|
||||
|
||||
Переводя на язык шейдеров, где каждый пиксель подобен клетке на бумаге, мы должны будем *спросить* каждый пиксель (поток), лежит ли он внутри круга. Сделаем это, вычислив расстояние от пикселя до центра круга.
|
||||
|
||||
![](circle.jpg)
|
||||
|
||||
Есть несколько способов вычислить это расстояние. В простейшем случае используется функция [`distance()`](../glossary/?search=distance), внутри которой вычисляется длина ([`length()`](../glossary/?search=length)) разности между двумя точками (в нашем случае это пиксель и центр изображения). Функция `length()` - ни что иное, как короткое название для формулы длины [гипотенузы](https://ru.wikipedia.org/wiki/%D0%93%D0%B8%D0%BF%D0%BE%D1%82%D0%B5%D0%BD%D1%83%D0%B7%D0%B0), в которой используется квадратный корень ([`sqrt()`](../glossary/?search=sqrt)).
|
||||
|
||||
![](hypotenuse.png)
|
||||
|
||||
Для вычисления расстояния от центра изображения можно использовать [`distance()`](../glossary/?search=distance), [`length()`](../glossary/?search=length) или [`sqrt()`](../glossary/?search=sqrt). Следующий код демонстрирует все три функции и тот неудивительный факт, что они возвращают один и тот же результат.
|
||||
|
||||
* Закомментируйте и раскомментируйте строки, чтобы попробовать различные способы достижения одного и того же.
|
||||
|
||||
<div class="codeAndCanvas" data="circle-making.frag"></div>
|
||||
|
||||
В предыдущем примере мы отображаем расстояние от центра изображения в яркость пикселя. Чем ближе пиксель к центру, тем он темнее. Заметим, что яркость не становится слишком большой, так как максимальное расстояние до центра (`vec2(0.5, 0.5)`) не превышает 0.5. Внимательно рассмотрите это отображение и подумайте:
|
||||
|
||||
* Что из него можно вывести?
|
||||
|
||||
* Как им пользоваться для рисования круга?
|
||||
|
||||
* Измените код, чтобы весь весь круговой градиент вошёл в изображение.
|
||||
|
||||
### Поле расстояний
|
||||
|
||||
Пример выше можно рассматривать как карту высот, где темнее значит выше. Тогда круговой градиент является картой конуса. Представим, что мы стоим на вершине конуса. Расстояние до границы конуса по горизонтали равно 0.5, и оно одинаково во всех направлениях. Выбирая высоту разреза конуса горизонтальной плоскостью, мы можем получить окружность большего или меньшего размера.
|
||||
|
||||
![](distance-field.jpg)
|
||||
|
||||
Фактически, мы интерпретируем пространство в терминах расстояния до центра чтобы рисовать фигуры. Этот подход, известный как «поле расстояний», используется во многих областях, начиная от контуров шрифтов и заканчивая 3D-графикой.
|
||||
|
||||
Выполните следующие упражнения:
|
||||
|
||||
* Используйте [`step()`](../glossary/?search=step) чтобы превратить все что больше 0.5 в белый, а что меньше - в чёрный.
|
||||
|
||||
* Поменяйте местами цвета фона и фигуры.
|
||||
|
||||
* Используя [`smoothstep()`](../glossary/?search=smoothstep), добейтесь красивой гладкой границы круга.
|
||||
|
||||
* Возьмите понравившуюся реализацию и смастерите функцию для использования в будущем.
|
||||
|
||||
* Добавьте кругу цвет.
|
||||
|
||||
* Сделайте анимации увеличения и уменьшения круга, похожую на сердцебиение. Подчерпните вдохновение из анимаций в предыдущей главе.
|
||||
|
||||
* Как насчёт перемещения круга? Попробуйте подвинуть его и разместить несколько кругов на одном рисунке.
|
||||
|
||||
* Что произойдёт, если скомбинировать поля расстояний используя несколько функций и операций?
|
||||
|
||||
```glsl
|
||||
pct = distance(st,vec2(0.4)) + distance(st,vec2(0.6));
|
||||
pct = distance(st,vec2(0.4)) * distance(st,vec2(0.6));
|
||||
pct = min(distance(st,vec2(0.4)),distance(st,vec2(0.6)));
|
||||
pct = max(distance(st,vec2(0.4)),distance(st,vec2(0.6)));
|
||||
pct = pow(distance(st,vec2(0.4)),distance(st,vec2(0.6)));
|
||||
```
|
||||
|
||||
* Сделайте три композиции с помощью этой техники. Ещё лучше, если они будут движущимися!
|
||||
|
||||
#### Инструментарий
|
||||
|
||||
В терминах вычислительной мощности функция [`sqrt()`](../glossary/?search=sqrt) и все зависимые от неё функции довольно затратны. Есть другой способ создания круглых полей расстояний с помощью скалярного произведения ([`dot()`](../glossary/?search=dot)).
|
||||
|
||||
<div class="codeAndCanvas" data="circle.frag"></div>
|
||||
|
||||
### Полезные свойства поля расстояний
|
||||
|
||||
![Дзенский сад](zen-garden.jpg)
|
||||
|
||||
С помощью поля расстояний можно нарисовать что угодно. Очевидно, чем сложнее фигура, тем сложнее будет уравнение. Но при наличии формул для создания поля расстояний определённой формы, вы можете с лёгкостью комбинировать фигуры и применять к ним различные эффекты: сглаживание границ, рисование нескольких контуров и т.п. Поэтому поля расстояний находят широкое применение в рисовании шрифтов, например [Mapbox GL Labels](https://www.mapbox.com/blog/text-signed-distance-fields/), [шрифты Material Design](http://mattdesl.svbtle.com/material-design-on-the-gpu) от [Matt DesLauriers](https://twitter.com/mattdesl) и [7 глава книги iPhone 3D Programming, издательство O’Reilly](http://chimera.labs.oreilly.com/books/1234000001814/ch07.html#ch07_id36000921).
|
||||
|
||||
Рассмотрим следующий код:
|
||||
|
||||
<div class="codeAndCanvas" data="rect-df.frag"></div>
|
||||
|
||||
Начнём со сдвига системы координат в центр и сжатия её в 2 раза, чтобы отобразить координаты в пространство между -1.0 и 1.0. В *строке 24* мы визуализируем поле расстояний с помощью [`fract()`](../glossary/?search=fract), чтобы было проще рассмотреть его рисунок. Рисунок поля расстояний повторяется снова и снова, как круги в дзенском саду.
|
||||
|
||||
Посмотрите на формулу поля расстояний в 19 строке. в ней мы рассчитываем расстояние до точки `(0.3, 0.3)` во всех четырёх квадрантах (именно для этого там нужен [`abs()`](../glossary/?search=abs).
|
||||
|
||||
Раскомментируя *строку 20*, мы сравним эти расстояния с нулём используя функцию [`min()`](../glossary/?search=min). В результате получится новый интересный рисунок.
|
||||
|
||||
Теперь раскомментируйте *строку 21*. Произойдёт то же самое, но с функцией [`max()`](../glossary/?search=max), что даст на выходе прямоугольник со скругленными углами. Обратите внимание как кольца поля расстояний становятся более гладкими с удалением от центра.
|
||||
|
||||
Наконец, раскомментируйте *строки 27 - 29* одну за другой и рассмотрите различные применения поля расстояний.
|
||||
|
||||
### Фигуры в полярных координатах
|
||||
|
||||
![Роберт Мангольд - Без названия (2008)](mangold.jpg)
|
||||
|
||||
В главе про цвет мы переходили от декартовых координат в полярным, вычисляя *радиус* и *угол* каждого пикселя с помощью формул:
|
||||
|
||||
```glsl
|
||||
vec2 pos = vec2(0.5)-st;
|
||||
float r = length(pos)*2.0;
|
||||
float a = atan(pos.y,pos.x);
|
||||
```
|
||||
|
||||
В начале главы мы использовали часть этих формул для рисования круга. Мы вычисляли расстояние до центра функцией [`length()`](../glossary/?search=length). Теперь, вооружившись знаниями о полях расстояний, мы изучим другой способ рисования фигур в полярных координатах.
|
||||
|
||||
Этот подход немного ограничен, но очень прост. Мы будем изменять радиус окружности в зависимости от угла, чтобы получить различные фигуры. Как это работает? Конечно же, с помощью функций формы!
|
||||
|
||||
Ниже представлены одни и те же функции в декартовых и в полярных координатах (строки 21 - 25). Раскомментируйте функции одну за одной, обращая внимание на соотношение между системами координат.
|
||||
|
||||
<div class="simpleFunction" data="y = cos(x*3.);
|
||||
//y = abs(cos(x*3.));
|
||||
//y = abs(cos(x*2.5))*0.5+0.3;
|
||||
//y = abs(cos(x*12.)*sin(x*3.))*.8+.1;
|
||||
//y = smoothstep(-.5,1., cos(x*10.))*0.2+0.5;"></div>
|
||||
|
||||
<div class="codeAndCanvas" data="polar.frag"></div>
|
||||
|
||||
Попробуйте:
|
||||
|
||||
* Привести графики в движение.
|
||||
* Комбинировать функции формы для *вырезания отверстий* в фигурах, чтобы создать цветы, снежинки и шестерёнки.
|
||||
* Используйте `plot()` из *главы о функциях формы* чтобы нарисовать только контур.
|
||||
|
||||
### Сила комбинаций
|
||||
|
||||
Мы научились рисовать фигуры, манипулируя радиусом круга в зависимости от угла с помощью функции [`atan()`](../glossary/?search=atan). Теперь давайте попробуем использовать `atan()` совместно с полем расстояний чтобы применить его возможности к фигурам в полярных координатах.
|
||||
|
||||
В этом трюке для конструирования поля расстояний в полярных координатах используется количество граней многогранника. Изучите [следующий код](http://thndl.com/square-shaped-shaders.html), написанный [Эндрю Болдуином](https://twitter.com/baldand).
|
||||
|
||||
<div class="codeAndCanvas" data="shapes.frag"></div>
|
||||
|
||||
* Используя этот пример, напишите функцию, которая принимает положение и количество углов многогранника и возвращает значение поля расстояний в точке.
|
||||
|
||||
* Смешайте поля расстояний с помощью функции [`min()`](../glossary/?search=min) и [`max()`](../glossary/?search=max).
|
||||
|
||||
* Найдите какой-нибудь логотип из геометрических фигур и воспроизведите его в виде поля расстояний.
|
||||
|
||||
Поздравляю! Мы прошли трудную тему! Вдохните и дайте изученным принципам устояться. Рисовать фигуры - это просто в Processing, но не здесь. В мире шейдеров алгоритмы рисования фигур очень заковыристы, и адаптация к такой парадигме программирования может стоить значительных усилий.
|
||||
|
||||
Теперь, когда вы умеете рисовать фигуры, новые идеи будут появляться в вашей голове с огромной скоростью. В следующей главе мы научимся двигать, вращать и масштабировать фигуры. Вы сможете делать композиции!
|
101
08/README-ru.md
Normal file
101
08/README-ru.md
Normal file
@ -0,0 +1,101 @@
|
||||
## Двумерные матрицы
|
||||
|
||||
<canvas id="custom" class="canvas" data-fragment-url="matrix.frag" width="700px" height="200px"></canvas>
|
||||
|
||||
### Перенос
|
||||
|
||||
В предыдущей главе мы научились рисовать фигуры. А для того, чтобы передвинуть фигуру, достаточно передвинуть саму систему координат. Этого можно добиться всего лишь прибавление вектора к переменной ```st```, содержащей положение фрагмента. Это приводит к перемещению всей системы координат в пространстве.
|
||||
|
||||
![](translate.jpg)
|
||||
|
||||
Это проще увидеть, чем объяснить, поэтому смотрите сами:
|
||||
|
||||
* Раскомментируйте строку 35 в коде ниже чтобы увидеть как движется само пространство.
|
||||
|
||||
<div class="codeAndCanvas" data="cross-translate.frag"></div>
|
||||
|
||||
Теперь попробуйте выполнить следующее:
|
||||
|
||||
* Используйте ```u_time``` и функции формы для перемещения креста каким-нибудь нестандартным способом. Придумайте движение, которое кажется вам интересным, и заставьте крест двигаться таким образом. Попробуйте срисовать что-нибудь из реального мира, например прибегающие и отступающие волны, движение маятника, подпрыгивающий мячик, ускоряющуюся машину или тормозящий велосипед.
|
||||
|
||||
### Повороты
|
||||
|
||||
Для вращения объектов нам снова придётся поворачивать всё пространство. Для этого мы воспользуемся [матрицей](https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D1%82%D1%80%D0%B8%D1%86%D0%B0_(%D0%BC%D0%B0%D1%82%D0%B5%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0)). Матрица - упорядоченный в строки и столбцы набор чисел. Векторы умножаются на матрицы по определённым правилам, изменяя значения вектора заданным способом.
|
||||
|
||||
[![Из статьи о матрицах в Википедии](matrixes.png)](https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D1%82%D1%80%D0%B8%D1%86%D0%B0_(%D0%BC%D0%B0%D1%82%D0%B5%D0%BC%D0%B0%D1%82%D0%B8%D0%BA%D0%B0))
|
||||
|
||||
В GLSL есть встроенная поддержка двух- трёх- и четырёхмерных матриц: [```mat2```](../glossary/?search=mat2) (2x2), [```mat3```](../glossary/?search=mat3) (3x3) и [```mat4```](../glossary/?search=mat4) (4x4). GLSL так же поддерживает умножение матриц (```*```) и некоторые специальные функции ([```matrixCompMult()```](../glossary/?search=matrixCompMult)).
|
||||
|
||||
Мы можем конструировать матрицы, приводящие к определённому поведению. Например, можно использовать матрицу для переноса вектора:
|
||||
|
||||
![](3dtransmat.png)
|
||||
|
||||
Что более интересно, мы можем использовать матрицу для поворота системы координат:
|
||||
|
||||
![](rotmat.png)
|
||||
|
||||
Взгляните на код функции, которая конструирует двумерную матрицу поворота. Эта функция повторяет приведённую выше [формулу](https://ru.wikipedia.org/wiki/%D0%9C%D0%B0%D1%82%D1%80%D0%B8%D1%86%D0%B0_%D0%BF%D0%BE%D0%B2%D0%BE%D1%80%D0%BE%D1%82%D0%B0) для вращения вектора вокруг точки ```vec2(0.0)```.
|
||||
|
||||
```glsl
|
||||
mat2 rotate2d(float _angle){
|
||||
return mat2(cos(_angle),-sin(_angle),
|
||||
sin(_angle),cos(_angle));
|
||||
}
|
||||
```
|
||||
|
||||
Это не совсем то что нам нужно, если мы вспомним наш способ пострения геометрических фигур. Фигура креста нарисована в центре изображения, то есть в точке ```vec2(0.5)```. Поэтому, прежде чем поворачивать пространство, нам нужно передвинуть фигуру из центра в точку ```vec2(0.0)```, и только после этого повернуть пространство, и в конце не забыть передвинуть фигуру в исходное положение.
|
||||
|
||||
![](rotate.jpg)
|
||||
|
||||
Рассмотрите код:
|
||||
|
||||
<div class="codeAndCanvas" data="cross-rotate.frag"></div>
|
||||
|
||||
Попробуйте выполнить следующее:
|
||||
|
||||
* Раскомментируйте строку 45 и посмотрите что происходит.
|
||||
|
||||
* Закомментируйте сдвиги до и после поворота в строках 37 и 39, понаблюдайте за последствиями.
|
||||
|
||||
* Используйте вращение, чтобы улучшить анимацию, которую вы сделали в упражнении про перенос.
|
||||
|
||||
### Масштаб
|
||||
|
||||
Мы увидели как можно использовать матрицы для переноса и поворота объектов в пространстве. Или, точнее, как трансформировать систему координат для вращения и движения объектов. Если вы пользовались софтом для 3D-моделирования или функциями push и pop для матриц в Processing, вы скорее всего знаете, что с помощью матриц можно ещё и масштабировать объекты.
|
||||
|
||||
![](scale.png)
|
||||
|
||||
Следуя этой формуле, можно сконструировать двумерную матрицу масштабирования.
|
||||
|
||||
```glsl
|
||||
mat2 scale(vec2 _scale){
|
||||
return mat2(_scale.x,0.0,
|
||||
0.0,_scale.y);
|
||||
}
|
||||
```
|
||||
|
||||
<div class="codeAndCanvas" data="cross-scale.frag"></div>
|
||||
|
||||
Выполните следующие упражнения для более глубокого понимания того, как это работает.
|
||||
|
||||
* Раскомментируйте строку 42 в коде выше и пронаблюдайте масштабирование пространственных координат.
|
||||
|
||||
* Посмотрите что произойдёт, если закомментировать переносы в строках 37 и 39.
|
||||
|
||||
* Попробуйте скомбинировать матрицы масштабирования и поворота. Помните, что порядок важен. Сначала перемножьте матрицы, а затем умножайте результат на векторы.
|
||||
|
||||
* Теперь когда вы умеете рисовать различные фигуры и управлять их положением в пространстве, мы можем приступить к композиции. придумайте и нарисуйте бутафорский [интерфейс пользователя или HUD](https://www.pinterest.com/patriciogonzv/huds/) (heads up display, то есть когда информация проецируется на стекло шлема или транспортного средства). Для поиска идей используйте следующий пример с ShaderToy, написанный пользователем [Ndel](https://www.shadertoy.com/user/ndel).
|
||||
|
||||
<iframe width="800" height="450" frameborder="0" src="https://www.shadertoy.com/embed/4s2SRt?gui=true&t=10&paused=true" allowfullscreen></iframe>
|
||||
|
||||
### Другие применения матриц: цвет в пространстве YUV
|
||||
|
||||
[YUV](https://ru.wikipedia.org/wiki/YUV) - цветовое пространство для аналогового кодирования фото и видео, разработанное с учётом особенностей восприятия человека чтобы снизить требования к каналу передачи компонентов цвета.
|
||||
|
||||
В следующем коде матричные операции GLSL используются весьма интересно - с их помощью сделано преобразование из одного цветового пространства в другое.
|
||||
|
||||
<div class="codeAndCanvas" data="yuv.frag"></div>
|
||||
|
||||
Здесь мы трактуем цвета как векторы и умножаем их на матрицы. Таким образом, мы «перемещаем» значения цвета.
|
||||
|
||||
В этой главе мы научились использовать матричные преобразования для сдвига, поворота и масштабирования векторов. Эти трансформации очень важны при построении композиций из фигур, которые мы рисовали в предыдущей главе. А в следующей главе мы используем все полученные знания для создания красивых процедурных узоров. Вы увидите, что программирование повторений и изменений может быть захватывающим занятием.
|
117
09/README-ru.md
Normal file
117
09/README-ru.md
Normal file
@ -0,0 +1,117 @@
|
||||
## Узоры
|
||||
|
||||
Шейдерные программы исполняются попиксельно, поэтому вне зависимости от количества повторений фигуры объём вычислений не изменяется. Это значит, что фрагментные шейдеры хорошо справляются с повторяющимися узорами.
|
||||
|
||||
[ ![Нина Вормердам - Проект IMPRINT (2013)](warmerdam.jpg) ](../edit.php#09/dots5.frag)
|
||||
|
||||
В этой главе мы собираемся применить весь ранее изученный материал, и повторить это в изображении несколько раз. Как и в предыдущих главах, наша стратегия будет основана на умножении пространственных координат (между 0.0 и 1.0), так чтобы фигуры, которые мы рисуем между 0.0 и 1.0 повторялись несколько раз, образуя решётку.
|
||||
|
||||
*«Регулярная решётка - это то, с чем человеческой интуиции и изобретательности проще всего работать. Повторяющиеся элементы вступают в контраст с хаосом мироздания и создают ощущение порядка. Люди всегда старались украсить и разнообразить окружающее пространство с помощю повторяющихся элементов. Это прослеживается от доисторических узоров на керамике до геометрических мозаик римских бань.»* [*10 PRINT*, Издательство MIT, (2013)](http://10print.org/)
|
||||
|
||||
Для начала давайте вспомним функцию [```fract()```](../glossary/?search=fract). Она возвращает дробную часть числа, то есть работает как взятие остатка от деления на единицу ([```mod(x,1.0)```](../glossary/?search=mod)). Другими словами, [```fract()```](../glossary/?search=fract) возвращает число справа от точки. Переменная с нормализованными координатами (```st```) уже пробегает значения от 0.0 до 1.0, поэтому нет смысла делать что-то вроде:
|
||||
|
||||
```glsl
|
||||
void main(){
|
||||
vec2 st = gl_FragCoord.xy/u_resolution;
|
||||
vec3 color = vec3(0.0);
|
||||
st = fract(st);
|
||||
color = vec3(st,0.0);
|
||||
gl_FragColor = vec4(color,1.0);
|
||||
}
|
||||
```
|
||||
|
||||
Но если мы увеличим масштаб нормализованной системы координат, скажем, в три раза, то получится три отрезка линейной интерполяции между 0.0 и 1.0: между 0 и 1, между 1 и 2, и наконец между 2 и 3.
|
||||
|
||||
<div class="codeAndCanvas" data="grid-making.frag"></div>
|
||||
|
||||
Теперь давайте нарисуем что-нибудь в каждом подпространстве, раскомментировав строку 27. Соотношение сторон при этом не изменится и фигуры не будут искажены, ибо мы умножаем по обеим осям.
|
||||
|
||||
Для более полного понимания попробуйте выполнить следующее:
|
||||
|
||||
* Поумножайте пространственные координаты на различные числа. Поэкспериментируйте с дробными числами и с различными множителями для x и y.
|
||||
|
||||
* Сделайте функцию создания узоров, пригодную для повторного использования.
|
||||
|
||||
* Разделите пространство на 3 строки и 3 столбца. Придумайте как узнать в какой строке и каком столбце находится текущий поток, и изменяйте фигуру в зависимости от этого. Нарисуйте игру в крестики-нолики.
|
||||
|
||||
### Применение матриц к узорам
|
||||
|
||||
Каждая клетка решётки является уменьшенной версией нормализованной системы координат, которую мы использовали ранее, поэтому мы можем применить к этим клеткам матричные преобразования переноса, поворота и масштаба.
|
||||
|
||||
<div class="codeAndCanvas" data="checks.frag"></div>
|
||||
|
||||
* Придумайте интересные анимации для этого узора. Анимируйте цвет, форму и движение. Сделайте три различных анимации.
|
||||
|
||||
* Создайте более сложные узоры, совмещая разные формы.
|
||||
|
||||
[![](diamondtiles-long.png)](../edit.php#09/diamondtiles.frag)
|
||||
|
||||
* Создайте узор [шотландского тартана](https://www.google.com/search?q=scottish+patterns+fabric&tbm=isch&tbo=u&source=univ&sa=X&ei=Y1aFVfmfD9P-yQTLuYCIDA&ved=0CB4QsAQ&biw=1399&bih=799#tbm=isch&q=Scottish+Tartans+Patterns), совмещая узоры в несколько слоёв.
|
||||
|
||||
[ ![Векторный шотландский узор от Kavalenkava](tartan.jpg) ](http://graphicriver.net/item/vector-pattern-scottish-tartan/6590076)
|
||||
|
||||
### Узоры со сдвигом
|
||||
|
||||
Давайте сымитируем кирпичную стену. Каждый ряд кирпичей в стене смещён на полкирпича по оси x относительно предыдущего ряда. Как это сделать?
|
||||
|
||||
![](brick.jpg)
|
||||
|
||||
Для начала нам нужно узнать чётность номера строки, над которой работает данный поток. Таким образом мы сможем понять, нужно ли делать сдвиг по x в этой строке.
|
||||
|
||||
Чтобы определить чётность строки, используем взятие по модулю 2.0 с помощью функции [```mod()```](../glossary/?search=mod), и сравним результат с единицей. Посмотрите на формулы ниже и раскомментируйте две последние строки.
|
||||
|
||||
<div class="simpleFunction" data="y = mod(x,2.0);
|
||||
// y = mod(x,2.0) < 1.0 ? 0. : 1. ;
|
||||
// y = step(1.0,mod(x,2.0));"></div>
|
||||
|
||||
Как видите, мы могли бы использовать [тернарный оператор](https://ru.wikipedia.org/wiki/%D0%A2%D0%B5%D1%80%D0%BD%D0%B0%D1%80%D0%BD%D0%B0%D1%8F_%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%BD%D0%B0%D1%8F_%D0%BE%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D1%8F) для сравнения значения по модулю ```2.0``` с единицей, но того же эффекта можно достичь с помощью [```step()```](../glossary/?search=step), которая работает быстрее. Почему? Хотя мы и не знаем как графические карты оптимизирует код, безопаснее будет предположить что встроенные функции работает быстрее, чем не встроенные. Если у вас есть возможность использовать встроенную функцию - используйте!
|
||||
|
||||
Теперь у нас есть формула вычисления чётности и мы можем сдвинуть нечётные строки для создания эффекта кирпичной стены. В 14 строке следующего кода мы используем эту формулу для «обнаружения» нечётных строк. Как видите, для чётных строк функция возвращает ```0.0```, что при умножении на сдвиг ```0.5``` так же даёт ```0.0```, а значит чётные строки не сдвигаются. В нечётных же строках ```0.5``` умножается на ```1.0```, поэтому в них пространство сдвигается на ```0.5``` по оси x.
|
||||
|
||||
Теперь попробуйте раскомментировать строку 32. Это сделает соотношение сторон похожим на соотношение сторон кирпича. А раскомментировав 40 строку вы увидите отображение координат в красный и зелёный цвета.
|
||||
|
||||
<div class="codeAndCanvas" data="bricks.frag"></div>
|
||||
|
||||
* Попробуйте анимировать этот пример, изменяя сдвиг в зависимости от времени.
|
||||
|
||||
* Создайте ещё одну анимацию, где чётные строки движутся налево, а нечётные - направо.
|
||||
|
||||
* Можете ли вы повторить такой же эффект для столбцов?
|
||||
|
||||
* Сделайте сдвиг по x и y одновременно, чтобы получить что-то вроде этого:
|
||||
|
||||
<a href="../edit.php#09/marching_dots.frag"><canvas id="custom" class="canvas" data-fragment-url="marching_dots.frag" width="520px" height="200px"></canvas></a>
|
||||
|
||||
## Плитка Труше
|
||||
|
||||
Теперь, когда мы научились определять чётность строки и столбца для каждой клетки, мы можем многократно использовать один и тот же элемент в зависимости от его расположения. Рассмотрим [плитку Труше](https://ru.wikipedia.org/wiki/%D0%9F%D0%BB%D0%B8%D1%82%D0%BA%D0%B0_%D0%A2%D1%80%D1%83%D1%88%D0%B5), где один элемент дизайна может быть представлен четырьмя различными способами:
|
||||
|
||||
![](truchet-00.png)
|
||||
|
||||
Изменяя рисунок в зависимости от расположения плитки, можно создать бесконечно много сложных изображений.
|
||||
|
||||
![](truchet-01.png)
|
||||
|
||||
Обратите особое внимание на функцию ```rotateTilePattern()```, которая разделяет пространство на четыре части и задаёт угол поворота каждой из них.
|
||||
|
||||
<div class="codeAndCanvas" data="truchet.frag"></div>
|
||||
|
||||
* Закомментируйте, раскомментируйте и продублируйте строки с 69 по 72, чтобы скомпоновать новые изображения.
|
||||
|
||||
* Замените чёрно-белый треугольник на какой-нибудь другой элемент, например полукруг, повернутые квадраты или линии.
|
||||
|
||||
* Напишите другие узоры с элементами, повёрнутыми в зависимости от их расположения.
|
||||
|
||||
* Создайте узор, элементы которого меняют другие свойства в зависимости от расположения.
|
||||
|
||||
* Придумайте ещё какое-нибудь изображение (не обязательно повторяющийся узор), в котором можно применить принципы из этой главы, например, [Гексаграммы И цзин](https://ru.wikipedia.org/wiki/%D0%93%D0%B5%D0%BA%D1%81%D0%B0%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B0_(%D0%98_%D1%86%D0%B7%D0%B8%D0%BD)).
|
||||
|
||||
<a href="../edit.php#09/iching-01.frag"><canvas id="custom" class="canvas" data-fragment-url="iching-01.frag" width="520px" height="200px"></canvas></a>
|
||||
|
||||
## Создавайте свои собственные правила
|
||||
|
||||
Создание процедурных узоров - это умственное упражнение по поиску минимальных повторно используемых элементов. Это древняя практика. Мы, как биологический вид, издавна использовали узоры для украшения тканей, пола и границ объектов: вспомните извилистые узоры древней Греции или решётчатые узоры из Китая. Магия повторений и вариаций всегда захватывала наше воображение. Рассмотрите [декоративные](https://archive.org/stream/traditionalmetho00chririch#page/130/mode/2up) [узоры](https://www.pinterest.com/patriciogonzv/paterns/) и обратите внимание как художники и дизайнеры балансируют между предсказуемостью порядка и внезапностью изменчивости хаоса. От арабских геометрических узоров и до бесподобных африканских тканей раскинулась целая вселенная узоров, среди которых есть что изучить.
|
||||
|
||||
![Франц Сейлс Мейер - Учебник орнамента (1920)](geometricpatters.png)
|
||||
|
||||
Этой главой мы заканчиваем раздел об алгоритмическом рисовании. В следующих главах мы привнесём в шейдеры немного энтропии для создания генеративного дизайна.
|
92
10/README-ru.md
Normal file
92
10/README-ru.md
Normal file
@ -0,0 +1,92 @@
|
||||
# Генеративный дизайн
|
||||
|
||||
Не удивительно, что после стольких повторений и порядка, автор вынужден привнести немного хаоса.
|
||||
|
||||
## Беспорядок
|
||||
|
||||
[![Рёдзи Икеда - тестовый шаблон (2008) ](ryoji-ikeda.jpg) ](http://www.ryojiikeda.com/project/testpattern/#testpattern_live_set)
|
||||
|
||||
Случайность есть сильнейшее проявление энтропии. Но как получить беспорядок в казалось бы предсказуемом и строгом программном окружении?
|
||||
|
||||
Давайте начнём со следующей функции:
|
||||
|
||||
<div class="simpleFunction" data="y = fract(sin(x)*1.0);"></div>
|
||||
|
||||
Выше мы извлекаем дробную часть синусоиды. Таким образом, значения синуса, плавно изменяющиеся от ```-1.0``` до ```1.0```, урезается до положительного диапазона от ```0.0``` до ```1.0```. Этот эффект можно использовать для получения псевдослучайных значений, разбивая синусоиду на меньшие кусочки. Как? Умножением значения синуса на большие числа. Попробуйте добавить нулей в функцию выше.
|
||||
|
||||
Когда коэффициент достигнет ```100000.0``` (то есть когда функция примет вид ```y = fract(sin(x)*100000.0)```), волны синусоиды станут неразличимыми. Дискретность дробной части повредила плавное течение синусоидальной волны, превратив её в псевдослучайный хаос.
|
||||
|
||||
## Управление хаосом
|
||||
|
||||
Использование беспорядка может стать непростой задачей. Он одновременно бывает слишком хаотичным и не слишком случайным. Посмотрите на следующий график. В нём мы используем функцию rand(), реализованную в точности как показано выше.
|
||||
|
||||
Присмотревшись, можно увидеть гребень синусоидальной волны в точках ```-1.5707``` и ```1.5707```. Легко понять почему: именно в этих точках синус достигает максимума и минимума.
|
||||
|
||||
Так же при детальном рассмотрении видно, что значения больше концентрируются в середине, чем на краях полосы.
|
||||
|
||||
<div class="simpleFunction" data="y = rand(x);
|
||||
//y = rand(x)*rand(x);
|
||||
//y = sqrt(rand(x));
|
||||
//y = pow(rand(x),5.);"></div>
|
||||
|
||||
Некоторое время назад [Pixelero](https://pixelero.wordpress.com) опубликовал [интересную статью о вероятностных распределениях](https://pixelero.wordpress.com/2008/04/24/various-functions-and-various-distributions-with-mathrandom/). Я добавил несколько функций из неё в график выше, чтобы вы могли поиграться с ними и посмотреть как меняется распределение значений. Раскомментируйте функции и посмотрите что произойдёт.
|
||||
|
||||
Читая [статью Pixelero](https://pixelero.wordpress.com/2008/04/24/various-functions-and-various-distributions-with-mathrandom/), важно помнить, что функция ```rand()``` является детерминированной, или псевдослучайной. Это означает, что, к примеру, ```rand(1.)``` всегда возвращает одно и то же значение. [Pixelero](https://pixelero.wordpress.com/2008/04/24/various-functions-and-various-distributions-with-mathrandom/) упоминает недетерминированную функцию ```Math.random()``` из ActionScript, которая каждый раз возвращает разные значения.
|
||||
|
||||
## Двумерный беспорядок
|
||||
|
||||
Теперь у нас есть лучшее понимание беспорядка, и мы можем применить его в двумерном пространстве, к осям ```x``` и ```y``` одновременно. Для этого нам нужен способ преобразования двумерного вектора в одномерное значение с плавающей точкой. Можно придумать много способов сделать это, например использовать скалярное произведение ([```dot()```](../glossary/?search=dot)). В коде ниже функция от скалярного произведения возвращает единственное число в диапазоне от ```0.0``` до ```1.0``` в зависимости от взаимного расположения векторов.
|
||||
|
||||
<div class="codeAndCanvas" data="2d-random.frag"></div>
|
||||
|
||||
Обратите внимания на строки с 13 по 15, где ```vec2 st``` сравнивается с другим двумерным вектором ```vec2(12.9898,78.233)```.
|
||||
|
||||
* Попробуйте изменить значения в строках 14 и 15. Понаблюдайте за изменениями изображения я и подумайте, какую информацию из этого можно извлечь.
|
||||
|
||||
* Для лучшего понимания свяжите функцию взятия случайных значений с событиями мыши (```u_mouse```) и временем (```u_time```).
|
||||
|
||||
## Использование хаоса
|
||||
|
||||
Случайные значения в двумерном изображении выглядят как шум ненастроенного телевизора, не правда ли? Это слишком жёсткий и сырой материал для создания изображений. Давайте сделаем его более полезным.
|
||||
|
||||
В качестве первого шага применим к случайному шуму сетку. Используя функцию [```floor()```](../glossary/?search=floor), можно сгенерировать таблицу с целочисленным количеством клеток. Посмотрите на следующий код, особенно на строки 22 и 23.
|
||||
|
||||
<div class="codeAndCanvas" data="2d-random-mosaic.frag"></div>
|
||||
|
||||
После увеличения пространства в 10 раз (строка 21), мы отделяем целые части координат от дробных. Эта операция нам уже знакома по разбиению пространства на клетки, в каждой из которых координаты изменяются от ```0.0``` до ```1.0```. Извлекая целую часть координаты, мы получаем общее число для всех пикселей одной клетки. Далее мы можем использовать это целое число чтобы сгенерировать случайное число для всей клетки. Так как случайная функция детерминирована, это случайное число получится одинаковым для всех пикселей клетки.
|
||||
|
||||
Раскомментируйте строку 29, где мы используем сохранённую дробную часть координат пикселя в качестве нормированных координат внутри клетки.
|
||||
|
||||
Комбинация целой и дробной части координат позволяет смешивать порядок и изменчивость.
|
||||
|
||||
Посмотрите на GLSL-версию известного генератора лабиринтов ```10 PRINT CHR$(205.5+RND(1)); : GOTO 10```.
|
||||
|
||||
<div class="codeAndCanvas" data="2d-random-truchet.frag"></div>
|
||||
|
||||
В нём используется случайное значение клетки для рисования линии в том или ином направлении с помощью функции ```truchetPattern()``` из предыдущей главы (строки с 41 по 47).
|
||||
|
||||
Раскомментируя строки с 50 по 53, можно получить ещё один интересный узор, а убрав комментарии со строк 35 и 36, вы увидите анимацию.
|
||||
|
||||
## Мастер Беспорядка
|
||||
|
||||
Японский электронный музыкант и художник [Рёдзи Икеда](http://www.ryojiikeda.com/) преуспел в использовании беспорядка. Сложно противостоять очарованию и гипнотизму его работ. Беспорядок особым образом вплетён в его работы: там он не создаёт раздражающий хаос, а отражает сложность нашей технологической культуры.
|
||||
|
||||
<iframe src="https://player.vimeo.com/video/76813693?title=0&byline=0&portrait=0" width="800" height="450" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe>
|
||||
|
||||
Посмотрите на работу [Икеды](http://www.ryojiikeda.com/) и попробуйте выполнить следующее:
|
||||
|
||||
* Сделайте горизонтальное движение клеток со случайными значениями в противоположных направлениях. Показывайте только наиболее светлые клетки. Изменяйте скорость движения клеток со временем.
|
||||
|
||||
<a href="../edit.php#10/ikeda-00.frag"><canvas id="custom" class="canvas" data-fragment-url="ikeda-00.frag" width="520px" height="200px"></canvas></a>
|
||||
|
||||
* Точно так же сделайте несколько строк, только каждую с разной скоростью и направлением. Сделайте так, чтобы множество показываемых клеток зависело от положения мыши.
|
||||
|
||||
<a href="../edit.php#10/ikeda-03.frag"><canvas id="custom" class="canvas" data-fragment-url="ikeda-03.frag" width="520px" height="200px"></canvas></a>
|
||||
|
||||
* Создайте ещё какие-нибудь эффекты.
|
||||
|
||||
<a href="../edit.php#10/ikeda-04.frag"><canvas id="custom" class="canvas" data-fragment-url="ikeda-04.frag" width="520px" height="200px"></canvas></a>
|
||||
|
||||
Сделать беспорядок эстетически привлекательным непросто, особенно если вы хотите делать симуляции, которые выглядят естественно. Беспорядок слишком хаотичен, и очень немногие из реальных вещей выглядят действительно случайно. Такие хаотичные вещи, как капли дождя или график биржевых котировок, не выглядят похожими на случайный рисунок, который мы делали вначале главы. В чём причина? Ну, случайные значения никак не коррелируют друг с другом, в то время как реальные вещи обычно «помнят» о своих предыдущих состояниях.
|
||||
|
||||
В следующей главе мы изучим шум. Это способ создания хаоса с помощью компьютера, который выглядит плавно и естественно.
|
218
11/README-ru.md
Normal file
218
11/README-ru.md
Normal file
@ -0,0 +1,218 @@
|
||||
|
||||
![Научный коллектив NASA / WMAP](mcb.jpg)
|
||||
|
||||
## Шум
|
||||
|
||||
Время передохнуть! Мы игрались со случайными значениями, которые выглядят как белый шум в расстроенном телевизоре, голова всё ещё кружится от раздумий о шейдерах, а глаза устали. Давайте выберемся на прогулку!
|
||||
|
||||
Мы ощущаем ветер кожей, солнце светит в лицо. Мир - очень яркое и богатое место. Цвета, фактуры, звуки. На прогулке мы не можем не заметить поверхности дорог, камней, деревьев и облаков.
|
||||
|
||||
![](texture-00.jpg)
|
||||
![](texture-01.jpg)
|
||||
![](texture-02.jpg)
|
||||
![](texture-03.jpg)
|
||||
![](texture-04.jpg)
|
||||
![](texture-05.jpg)
|
||||
![](texture-06.jpg)
|
||||
|
||||
Непредсказуемость этих текстур можно было бы назвать случайной, но они не выглядят как та беспорядночность, с которой мы играли ранее. «Реальный мир» - это настолько богатое и сложное место! Как аппроксимировать это разнообразие с помощью вычислений?
|
||||
|
||||
Этот вопрос [Кен Перлин](https://mrl.nyu.edu/~perlin/) пытался разрешить в начале 1980-ых, когда он занимался генерацией реалистичных текстур для фильма «Трон». В итоге, он предложил элегантный оскароносный алгоритм генерации шума.
|
||||
|
||||
![Дисней - Трон (1982)](tron.jpg)
|
||||
|
||||
Код ниже не реализует классический алгоритм шума Перлина, но это хорошее начало в изучении способов генерации шума.
|
||||
|
||||
<div class="simpleFunction" data="
|
||||
float i = floor(x); // целая часть
|
||||
float f = fract(x); // дробная часть
|
||||
y = rand(i); // функция rand() описана в предыдущей главе
|
||||
//y = mix(rand(i), rand(i + 1.0), f);
|
||||
//y = mix(rand(i), rand(i + 1.0), smoothstep(0.,1.,f));
|
||||
"></div>
|
||||
|
||||
В этих строках мы делаем что-то похожее на код из предыдущей главы. Мы разбиваем непрерывное значение с плавающей точкой (```x```) на целую (```i```) и дробную (```f```) части. Для получения целой части мы используем [```floor()```](../glossary/?search=floor), а для дробной - [```fract()```](../glossary/?search=fract). Затем мы применяем ```rand()``` к целой части ```x```, получая уникальное случайное число для каждого нового целого.
|
||||
|
||||
Далее идут две закомментированные строки. Первая из них линейно интерполирует каждое случайное значение.
|
||||
|
||||
```glsl
|
||||
y = mix(rand(i), rand(i + 1.0), f);
|
||||
```
|
||||
|
||||
Раскомментируйте строку и посмотрите что получится. Мы используем значение [```fract()```](../glossary/?search=fract) из переменной `f` для смешивания ([```mix()```](../glossary/?search=mix)) двух случайных значений.
|
||||
|
||||
Ранее мы уже видели лучшее решение, чем линейная интерполяция, не так ли? Попробуйте раскомментировать следующую строку, в которой используется гладкая интерполяция ([```smoothstep()```](../glossary/?search=smoothstep)) вместо линейной:
|
||||
|
||||
```glsl
|
||||
y = mix(rand(i), rand(i + 1.0), smoothstep(0.,1.,f));
|
||||
```
|
||||
|
||||
Обратите внимание, как переход между пиками стал гладким. В некоторых реализациях шума программисты предпочитают писать свои собственные кубические кривые (как формула ниже) вместо [```smoothstep()```](../glossary/?search=smoothstep).
|
||||
|
||||
```glsl
|
||||
float u = f * f * (3.0 - 2.0 * f ); // самописная кубическая кривая
|
||||
y = mix(rand(i), rand(i + 1.0), u); // её использование в интерполяции
|
||||
```
|
||||
|
||||
Эта *плавная беспорядочность* существенно меняет расклад. Она помогает графическим инженерам и художникам генерировать изображения и геометрию с ощущением целостности. Алгоритм шума Перлина был многократно реализован на разных языках и для различных размерностей, помогая создавать завораживающие произведения искусства в самых разных видах творческой деятельности.
|
||||
|
||||
![Роберт Ходжин - Написанные изображения (2010)](robert_hodgin.jpg)
|
||||
|
||||
Теперь ваш ход:
|
||||
|
||||
* Напишите свою собственную функцию ```float noise(float x);```
|
||||
|
||||
* Используйте свою функцию шума для анимации. Двигайте, вращайте и масштабируйте изображение.
|
||||
|
||||
* Создайте анимированую композицию из нескольких фигур, «танцующих» с помощью шума.
|
||||
|
||||
* Сконструируйте «органические» формы с помощью шума.
|
||||
|
||||
* Возьмите получившееся «существо» и развивайте его дальше. Превратите его в полноценный персонаж, добавив анимации.
|
||||
|
||||
## Двумерный шум
|
||||
|
||||
![](02.png)
|
||||
|
||||
Теперь мы знаем как сделать шум в 1D, а значит самое время двигаться в 2D. В двумерном пространстве вместо интерполяции между двумя точками на прямой (```fract(x)``` и ```fract(x)+1.0```), мы будем интерполировать между четырьмя углами квадратного участка плоскости (```fract(st)```, ```fract(st)+vec2(1.,0.)```, ```fract(st)+vec2(0.,1.)``` и ```fract(st)+vec2(1.,1.)```).
|
||||
|
||||
![](01.png)
|
||||
|
||||
Аналогично, если нам нужен трёхмерный шум, мы будем интерполировать между восемью углами куба. Все эти способы работают на интерполяции случайных значений, поэтому они называются **шумом значений**.
|
||||
|
||||
![](04.jpg)
|
||||
|
||||
Как и одномерный пример, это не линейная, а кубическая интерполяция, которая гладко соединяет несколько любых точек квадратной решётки.
|
||||
|
||||
![](05.jpg)
|
||||
|
||||
Посмотрите на следующую функцию шума.
|
||||
|
||||
<div class="codeAndCanvas" data="2d-noise.frag"></div>
|
||||
|
||||
Мы начинаем с масштабирования пространства в 5 раз (строка 45), чтобы увидеть интерполяцию между клетками решётки. Далее в функции шума мы разделяем пространство на клетки. Дробную часть координаты пикселя мы сохраняем для использования в качестве нормализованной координаты внутри клетки, а целую часть - как координату самой клетки. Целочисленная координата используется для вычисления четырёх координат четырёх углов и получения случайного значения для каждого из них (строки 23-26). Наконец, в строке 35 мы интерполируем между четырьмя случайными значениями в углах, используя ранее сохранённые дробные части координат.
|
||||
|
||||
Теперь ваш ход. Выполните следующие упражнения:
|
||||
|
||||
* Измените множитель в строке 45. Попытайтесь анимировать его.
|
||||
|
||||
* При каком увеличении шум снова начинает выглядеть случайным?
|
||||
|
||||
* При каком увеличении шум неразличим?
|
||||
|
||||
* Сделайте функцию шума зависимой от координат мыши.
|
||||
|
||||
* Что если интерпретировать градиент шума как поле расстояний? Сделайте с ним что-нибудь интересное.
|
||||
|
||||
* Теперь вы в какой-то степени управляете хаосом и порядком, и можете применить эти знания на практике. Сделайте композицию из прямоугольников, цвета и шума, которая подражает сложности картины [Марка Ротко](https://ru.wikipedia.org/wiki/%D0%A0%D0%BE%D1%82%D0%BA%D0%BE,_%D0%9C%D0%B0%D1%80%D0%BA).
|
||||
|
||||
![Марк Ротко - Три (1950)](rothko.jpg)
|
||||
|
||||
## Использование шума в генеративном дизайне
|
||||
|
||||
Изначально алгоритмы шума разрабатывались чтобы придать естественное *что-то* цифровым текстурам. Одно- и двумерные реализаци, которые мы рассмотрели выше, работали на интерполяции шума *значений*, из-за чего мы называем их *шумом значений*. Но существуют и другие способы получения шума...
|
||||
|
||||
[ ![Иниго Квилес - шум значений](value-noise.png) ](../edit.php#11/2d-vnoise.frag)
|
||||
|
||||
Как мы выяснили в предыдущих упражнениях, шум значений выглядит «блочно». Чтобы справиться с этим блочным эффектом, [Кен Перлин](https://mrl.nyu.edu/~perlin/) в 1985 году разработал другую реализацию алгоритма под названием *градиентный шум*. Кен додумался как интерполировать случайные *градиенты* вместо значений. Эти градиенты возвращаются двумерной случайной функцией, которая возвращает направления (в виде ```vec2```) вместо скалярных значений. Щёлкните по следующему изображению чтобы увидеть код и результат его работы.
|
||||
|
||||
[ ![Иниго Квилес - градиентный шум](gradient-noise.png) ](../edit.php#11/2d-gnoise.frag)
|
||||
|
||||
Посмотрите на эти два примера шума, написанные [Иниго Квилесом](http://www.iquilezles.org/), и обратите внимание на разницу между [шумом значений](https://www.shadertoy.com/view/lsf3WH) и [градиентным шумом](https://www.shadertoy.com/view/XdXGW8).
|
||||
|
||||
Как и художники, понимающие принцип работы пигментов своих красок, чем больше мы знаем о реализации шумовых алгоритмов - тем лучше мы сможем их использовать. Например, если повернуть двумерное изображение прямых линий на значение двумерного шума, получится следующий эффект закрутки, имитирующий древесину. Кликните на изображение, чтобы увидеть код.
|
||||
|
||||
[ ![Текстура древесины](wood-long.png) ](../edit.php#11/wood.frag)
|
||||
|
||||
```glsl
|
||||
pos = rotate2d( noise(pos) ) * pos; // поворачиваем пространство
|
||||
pattern = lines(pos,.5); // рисуем линии
|
||||
```
|
||||
|
||||
Другой способ получить интересные картинки - это представить шум в виде поля расстояний и применить какие-нибудь трюки из [главы о фигурах](../07/?lan=ru).
|
||||
|
||||
[ ![Текстура поверхности воды](splatter-long.png) ](../edit.php#11/splatter.frag)
|
||||
|
||||
```glsl
|
||||
color += smoothstep(.15,.2,noise(st*10.)); // Чёрные всплески
|
||||
color -= smoothstep(.35,.4,noise(st*10.)); // Отверстия во всплесках
|
||||
```
|
||||
|
||||
Ещё один способ использования шума - модуляция форм. Здесь так же потребуются навыки из [главы о фигурах](../07/).
|
||||
|
||||
<a href="../edit.php#11/circleWave-noise.frag"><canvas id="custom" class="canvas" data-fragment-url="circleWave-noise.frag" width="300px" height="300"></canvas></a>
|
||||
|
||||
Практические задания:
|
||||
|
||||
* Какие ещё генеративные узоры вы можете сделать? Может быть гранит? Мрамор? Магма? Вода? Найдите три текстурных изображения на ваш вкус и реализуйте их алгоритмически с помощью шума.
|
||||
* Используйте шум для модулирования формы.
|
||||
* Как насчёт использования шума в движении? Вернитесь к [главе про матрицы](../08/?lan=ru). Используйте пример про сдвиг белого креста и добавьте к движению немного *беспорядка* и *шума*.
|
||||
* Сгенерируйте картину Джексона Поллока.
|
||||
|
||||
![Джексон Поллок - Серый №14 (1948)](pollock.jpg)
|
||||
|
||||
## Улучшенный шум
|
||||
|
||||
Перлин улучшил свой непростой шум, предложив *симплексный шум*, в котором заменил кубическую эрмитову кривую ( _f(x) = 3x^2-2x^3_ , которая идентична функции [```smoothstep()```](../glossary/?search=smoothstep)) кривой пятой степени ( _f(x) = 6x^5-15x^4+10x^3_ ). При этом оба конца кривой становятся более «плоскими», поэтому границы более плавно сшиваются друг с другом. Другими словами, получается более непрерывный переход между клетками. Посмотрите на результат, раскомментировав вторую формулу в следующем графике (или два уравнения по отдельности [здесь](https://www.desmos.com/calculator/2xvlk5xp8b)).
|
||||
|
||||
<div class="simpleFunction" data="
|
||||
// Кубическая эрмитова кривая. То же что и SmoothStep()
|
||||
y = x*x*(3.0-2.0*x);
|
||||
// Кривая пятой степени
|
||||
//y = x*x*x*(x*(x*6.-15.)+10.);
|
||||
"></div>
|
||||
|
||||
Обратите внимание как меняются концы кривой. Подробнее читайте в [оригинальной статье](http://mrl.nyu.edu/~perlin/paper445.pdf).
|
||||
|
||||
## Симплексный шум
|
||||
|
||||
Перлин не удовлетворился успехом своего алгоритма. Он хотел более высокой производительности. На Siggraph 2001 он представил «симплексный шум», который вносит следующие улучшения относительно предыдущего алгоритма:
|
||||
|
||||
* Меньшая вычислительная сложность и меньше умножений.
|
||||
* Шум масштабируется в высшие размерности с меньшей потерей производительности.
|
||||
* Отсутствуют направленные артефакты.
|
||||
* Шум имеет хорошо определённый непрерывный градиент, который легко вычислить.
|
||||
* Алгоритм достаточно прост для аппаратной реализации.
|
||||
|
||||
Я знаю о чём вы думаете... «Кто этот человек?» Да, он делает фантастические вещи! Но серьёзно, как он улучшил алгоритм? Смотрите: если для двух измерений мы интерполируем 4 точки (углы квадрата), то для трёх ([пример реализации здесь](../edit.php#11/3d-noise.frag)) и четырёх измерений придётся интерполировать 8 и 16 точек. Так? Другими словами, для N измерений нужно гладко интерполировать 2 в степени N точек. Но Кен вспомнил, что хоть квадрат и является удобной формой для заполнения пространства, но всё же простейшая фигура в двумерном пространстве - это равносторонний треугольник. Поэтому он начал с замены квадратной решётки (которую мы использовали ранее) на симплексную решётку, то есть равносторонние треугольники в двумерном случае.
|
||||
|
||||
![](simplex-grid-00.png)
|
||||
|
||||
Симплекс в N измерениях - это многогранник с N + 1 углом. Другими словами, в двумерном пространстве придётся вычислять на один угол меньше, в трёхмерном - на 4 угла меньше, а в четырёхмерном - на 11 углов меньше! Это огромное достижение!
|
||||
|
||||
В двух измерениях интерполяция проделывается так же, как и в обычном шуме, то есть между значениями в углах участка пространства. Однако в случае с симплексной решёткой нам нужно интерполировать только три угла.
|
||||
|
||||
![](simplex-grid-01.png)
|
||||
|
||||
Как построить симплексную решётку? Сделаем ещё один блестящий и элегантный ход! Симплексная решётка получается, если разделить клетки обычной квадратной решётки на два равнобедренных треугольника, а затем наклонить решётку так, чтобы треугольники стали равносторонними.
|
||||
|
||||
![](simplex-grid-02.png)
|
||||
|
||||
Далее, следуем описанию из [статьи Стефана Густавсона](http://staffwww.itn.liu.se/~stegu/simplexnoise/simplexnoise.pdf): _«...рассмотрев целые части трансформированных координат (x,y) в данной точке, нетрудно определить к какой клетке, составленной из двух симплексов, принадлежит эта точка. А сравнив абсолютные значения x и y, мы можем определить принадлежность точки к верхнему или нижнему симплексу, после чего останется обойти три корректных угловых точки.»_
|
||||
|
||||
В следующем коде раскомментируйте строку 44 чтобы увидеть перекос решётки, а затем раскомментируйте строку 47 чтобы увидеть как строится решётка из симплексов. В строке 22 обратите внимание как мы разделяем скошенный квадрат на два равносторонних треугольника, всего лишь проверяя условия ```x > y``` («нижний» треугольник) или ```y > x``` («верхний» треугольник).
|
||||
|
||||
<div class="codeAndCanvas" data="simplex-grid.frag"></div>
|
||||
|
||||
Все эти улучшения порождают произведение алгоритмического искусства под названием **Симплексный шум**. Ниже приведена реализация этого алгоритма, написанная на GLSL Йаном МакЭваном и Стефаном Густавсоном (и представленная в [этой работе](http://webstaff.itn.liu.se/~stegu/jgt2012/article.pdf)). Она излишне сложна для образовательных целей, но всё же не поленитесь кликнуть на картинку и изучить исходный код. Он быстр, лаконичен и не настолько запутан, как вы возможно его себе представляли.
|
||||
|
||||
[ ![Йан МакЭван из Ashima Arts - Симплексный шум](simplex-noise.png) ](../edit.php#11/2d-snoise-clear.frag)
|
||||
|
||||
Ну что-ж... пора заканчивать с техническими деталами, пора задействовать этот ресурс в качестве вашего нового средства выразительности:
|
||||
|
||||
* Рассмотрите реализацию каждого из шумов. Вообразите, будто это необработанный материал, как мраморный камень для скульптора. Какое ощущение создаёт каждый из них? Прищурьте глаза чтобы усилить воображение, как будто вы высматриваете фигуры в облаках. Что вы видите? О чём это вам напоминает? Что бы вы изготовили из каждого вида шумов? Соберитесь и воплотите это в коде.
|
||||
|
||||
* Сделайте шейдер, создающий иллюзию потока. Например, лавовую лампу, чернильные капли, воду и т.д.
|
||||
|
||||
<a href="../edit.php#11/lava-lamp.frag"><canvas id="custom" class="canvas" data-fragment-url="lava-lamp.frag" width="520px" height="200px"></canvas></a>
|
||||
|
||||
* Используя симплексный шум, добавьте текстуры к ранее выполненным работам.
|
||||
|
||||
<a href="../edit.php#11/iching-03.frag"><canvas id="custom" class="canvas" data-fragment-url="iching-03.frag" width="520px" height="520px"></canvas></a>
|
||||
|
||||
В этой главе мы научились немного контролировать хаос. Это было непросто! Нужно потрудиться и набраться терпения, чтобы стать мастером по работе с шумом.
|
||||
|
||||
В следующих главах мы изучим некоторые известные приёмы чтобы отточить ваши умения и научиться делать из шума более качественную генеративную графику с помощью шейдеров. А пока немного отдохните на свежем воздухе, рассматривая причудливые узоры природы. Тренировка умения наблюдать требует столько же или даже больше сил, чем оттачивание умения создавать. Пойдите на улицу и хорошо отдохните до конца дня!
|
||||
|
||||
<p style="text-align:center; font-style: italic;">«Поговорите с деревом, подружитесь с ним.» Боб Росс
|
||||
</p>
|
190
12/README-ru.md
Normal file
190
12/README-ru.md
Normal file
@ -0,0 +1,190 @@
|
||||
![](dragonfly.jpg)
|
||||
|
||||
## Клеточный шум
|
||||
|
||||
В 1996 году, через 16 лет после изобретения оригинального шума Перлина и за 5 лет до появления симплексного шума, Стивен Ворли написал статью под названием [«Базисная функция для клеточной текстуры»](http://www.rhythmiccanvas.com/research/papers/worley.pdf). В этой статье он описывает способ процедурного текстурирования, который теперь активно используется графическим сообществом.
|
||||
|
||||
Чтобы понять лежащие в основе этого способа принципы, нам нужно научиться думать в терминах **итераций**. Вы наверное уже догадались к чему это приведёт: нам придётся использовать цикл ```for```. Особенность цикла ```for``` в GLSL заключается в том, что число, с которым мы сравниваем счётчик, должно быть константой (```const```). Поэтому никаких динамических циклов - количество итераций должно быть фиксировано.
|
||||
|
||||
Давайте рассмотрим пример.
|
||||
|
||||
### Точки в поле расстояний
|
||||
|
||||
Клеточный шум основан на поле расстояний до ближайшей из множества опорных точек. Представим, что мы делаем поле расстояний на четырёх точках. Что нужно сделать? **Для каждого пикселя нужно посчитать расстояние до ближайшей точки**. Это значит, что нам нужно пройти по всем точкам, рассчитать расстояние до каждой из них и сохранить расстояние до ближайшей.
|
||||
|
||||
```glsl
|
||||
float min_dist = 100.; // Переменная под расстояние до ближайшей точки.
|
||||
|
||||
min_dist = min(min_dist, distance(st, point_a));
|
||||
min_dist = min(min_dist, distance(st, point_b));
|
||||
min_dist = min(min_dist, distance(st, point_c));
|
||||
min_dist = min(min_dist, distance(st, point_d));
|
||||
```
|
||||
|
||||
![](cell-00.png)
|
||||
|
||||
Не самый красивый код, но он делает свою работу. Теперь давайте перепишем его с использованием массива и цикла ```for```.
|
||||
|
||||
```glsl
|
||||
float m_dist = 100.; // минимальное расстояние
|
||||
for (int i = 0; i < TOTAL_POINTS; i++) {
|
||||
float dist = distance(st, points[i]);
|
||||
m_dist = min(m_dist, dist);
|
||||
}
|
||||
```
|
||||
|
||||
Обратите внимание на то, как мы используем ```for``` для обхода массива точек и функцию [```min()```](../glossary/?search=min) для сохранения минимального расстояния. Вот краткая рабочая реализация этой идеи:
|
||||
|
||||
<div class="codeAndCanvas" data="cellnoise-00.frag"></div>
|
||||
|
||||
В этом коде одна из точек выбирается в соответствии с положением курсора мыши. Поиграйте с примером, чтобы интуитивно понять как работает этот код. Затем попробуйте выполнить следующее:
|
||||
|
||||
- Как бы вы анимировали остальные точки?
|
||||
- После прочтения [главы о фигурах](../07/?lan=ru), придумайте интересные применения этому полю расстояний!
|
||||
- Как добавить больше точек к полю расстояний? А что если мы захотим динамически добавлять и удалять точки?
|
||||
|
||||
### Замощение и итерации
|
||||
|
||||
Вы скорее всего заметили, что циклы ```for``` и массивы не очень дружат с GLSL. Как было сказано ранее, циклы не принимают динамического количества итераций в условии прекращения. Так же, большое количество итераций сильно бьёт по производительности. Это значит, что мы не можем использовать реализацию «в лоб» для большого количества точек. Нужно найти такую стратегию, которая использовала бы преимущества параллельной архитектуры GPU.
|
||||
|
||||
![](cell-01.png)
|
||||
|
||||
Один из подходов к этой проблеме использует разделение пространства на непересекающиеся элементы, то есть замощение. Каждому пикселю не обязательно проверять расстояние до абсолютно всех точек, правда? Зная, что каждый пиксель работает в отдельном потоке, мы можем разделить пространство на клетки, в каждой из которых нужно будет просмотреть только одну точку. Так же, чтобы избежать артефактов на границах клеток, нужно проверять расстояния до точек в соседних клетках. Это и есть основная идея [работы Стивена Ворли](http://www.rhythmiccanvas.com/research/papers/worley.pdf). В итоге, каждому пикселю достаточно проверить всего девять точек: точку в своей собственной клетке и в восьми клетках вокруг неё. Мы уже разбивали пространство в главах об [узорах](../09/?lan=ru), [беспорядке](../10/?lan=ru) и [шуме](../11/?lan=ru), а значит эта методика уже должна быть вам хорошо знакома.
|
||||
|
||||
```glsl
|
||||
// Масштаб
|
||||
st *= 3.;
|
||||
|
||||
// Разбиение пространства
|
||||
vec2 i_st = floor(st);
|
||||
vec2 f_st = fract(st);
|
||||
```
|
||||
|
||||
Каков же наш план? Мы воспользуемся координатой клетки (целая часть координат, ```i_st```) для построения случайной координаты точки. Функция ```random2f``` принимает ```vec2``` и возвращает ```vec2``` со случайными координатами. Итак, в каждой клетке у нас будет одна опорная точка со случайной координатой внутри клетки.
|
||||
|
||||
```glsl
|
||||
vec2 point = random2(i_st);
|
||||
```
|
||||
|
||||
Каждый пиксель в клетке (его координаты в пределах клетки хранятся в ```f_st```) проверит расстояние до этой случайной точки.
|
||||
|
||||
```glsl
|
||||
vec2 diff = point - f_st;
|
||||
float dist = length(diff);
|
||||
```
|
||||
|
||||
Результат выглядит примерно так:
|
||||
|
||||
<a href="../edit.php#12/cellnoise-01.frag"><img src="cellnoise.png" width="520px" height="200px"></img></a>
|
||||
|
||||
Нам всё ещё нужно проверить расстояние до точек в окружающих клетках, а не только в своей собственной. Для этого мы **обойдём** циклом все соседние клетки. Не все клетки, а только те, что непосредственно прилегают к данной. То есть от ```-1``` (левой) до ```1``` (правой) клетки по оси ```x``` и от ```-1``` (нижней) до ```1``` (верхней) клетки по оси ```y```. Этот регион из девяти клеток можно обойти следующим циклом ```for```:
|
||||
|
||||
```glsl
|
||||
for (int y= -1; y <= 1; y++) {
|
||||
for (int x= -1; x <= 1; x++) {
|
||||
// Соседняя клетка
|
||||
vec2 neighbor = vec2(float(x),float(y));
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
![](cell-02.png)
|
||||
|
||||
Теперь во вложенном цикле мы можем вычислить положение опорной точки в каждой из соседних клеток, прибавляя смещение соседней клетки к координатам текущей.
|
||||
|
||||
```glsl
|
||||
...
|
||||
// Случайное положение в зависимости от координат текущей клетки + смещение
|
||||
vec2 point = random2(i_st + neighbor);
|
||||
...
|
||||
```
|
||||
|
||||
Остаётся только вычислить расстояние до каждой из точек и сохранить минимальное в переменной ```m_dist```.
|
||||
|
||||
```glsl
|
||||
...
|
||||
vec2 diff = neighbor + point - f_st;
|
||||
|
||||
// Расстояние до точки
|
||||
float dist = length(diff);
|
||||
|
||||
// Сохранить наименьшее расстояние
|
||||
m_dist = min(m_dist, dist);
|
||||
...
|
||||
```
|
||||
|
||||
Этот код навеян следующим отрывком из [статьи Иниго Квилеса](http://www.iquilezles.org/www/articles/smoothvoronoi/smoothvoronoi.htm):
|
||||
|
||||
*«... стоит обратить внимание на красивый трюк в коде выше. Большинство реализаций страдают потерей точности потому что они генерируют случайные точки в «глобальном» пространстве («мировом» или «пространстве объекта»), а значит эти точки могут быть сколь угодно далеко от начала координат. Проблему можно решить, пересадив весь код на более высокоточный тип данных, или просто немного подумав. Моя реализация генерирует точки не в «мировом» пространстве, а в пространстве клетки: как только координата пикселя разделена на целую и дробную части, а значит каждой клетке назначены свои координаты, мы можем размышлять только о происходящем внутри клетки, то есть отбросить целую часть координаты пикселя и сберечь несколько бит точности. Фактически, в наивной реализации диаграмм Вороного, целые части координат точек взаимно уничтожаются при вычитании опорной точки из текущей точки. В реализации выше мы предотвращаем взаимное уничтожение, перенося все вычисления в пространство клетки. Этот подход позволяет текстурировать диаграммой Вороного хоть целую планету, если увеличить точность входных координат до двойной, вычислить floor() и fract(), а затем перейти к одинарной точности, не тратя вычислительные ресурсы на перевод всего алгоритма на двойную точность. Разумеется, аналогичный трюк применим и к шумам Перлина (но я не видел ни описания ни реализации подобного алгоритма).»*
|
||||
|
||||
Резюме: сначала разбиваем пространство на клетки. Каждый пиксель вычисляет расстояние до точки в своей клетке и в окружающих восьми клетках, сохраняет кратчайшее из них. Получается поле расстояний, выглядящее примерно так:
|
||||
|
||||
<div class="codeAndCanvas" data="cellnoise-02.frag"></div>
|
||||
|
||||
Исследуйте этот пример, выполнив следующее:
|
||||
|
||||
- Масштабируйте пространство на различную величину.
|
||||
- Придумайте другие способы анимировать точки.
|
||||
- Что если мы захотим добавить точку в координатах мыши?
|
||||
- Какие ещё способы создания поля расстояний вы можете предложить, кроме ```m_dist = min(m_dist, dist);```?
|
||||
- Какие интересные узоры можно создать с помощью этого поля расстояний?
|
||||
|
||||
Этот алгоритм можно так же реализовать, отталкиваясь от точек, а не от пикселей. В этом случае его можно описать так: каждая точка растёт пока не встретится с растущей областью другой точки. Примеры подобного роста есть в природе. Некоторые живые организмы принимают свою форму благодаря разности между внутренними силами, направленными на рост и расширение, и внешними ограничивающими силами. Классический алгоритм, симулирующий такое поведение, назван в честь [Георгия Вороного](https://ru.wikipedia.org/wiki/%D0%92%D0%BE%D1%80%D0%BE%D0%BD%D0%BE%D0%B9,_%D0%93%D0%B5%D0%BE%D1%80%D0%B3%D0%B8%D0%B9_%D0%A4%D0%B5%D0%BE%D0%B4%D0%BE%D1%81%D1%8C%D0%B5%D0%B2%D0%B8%D1%87).
|
||||
|
||||
![](monokot_root.jpg)
|
||||
|
||||
### Алгоритм Вороного
|
||||
|
||||
Конструирование диаграмм Вороного из клеточного шума не настолько сложно, как может показаться. Нужно всего лишь *сохранить* некоторую дополнительную информацию о ближайшей к пикселю точке. Для этого мы воспользуемся переменной ```m_point``` типа ```vec2```. Сохраняя вектор направления на точку вместо расстояния, мы сохраним «уникальный» идентификатор этой точки.
|
||||
|
||||
```glsl
|
||||
...
|
||||
if( dist < m_dist ) {
|
||||
m_dist = dist;
|
||||
m_point = point;
|
||||
}
|
||||
...
|
||||
```
|
||||
|
||||
Обратите внимание, что в следующем коде мы используем обычный ```if``` вместо ```min``` для сохранения минимального расстояния. Почему? Потому что мы хотим совершить дополнительные действия при нахождении новой ближайшей точки, а именно, сохранить её координаты (строки 32 - 37).
|
||||
|
||||
<div class="codeAndCanvas" data="vorono-00.frag"></div>
|
||||
|
||||
Обратите внимание как цвет движущейся клетки (привязанной к координатам мыши) изменяется в зависимости от положения. Так происходит потому, что цвет вычисляется на основе координат ближайшей точки.
|
||||
|
||||
Как и в предыдущих примерах, теперь настало время увеличить масштабы, использовав подход из [работы Стивена Ворли](http://www.rhythmiccanvas.com/research/papers/worley.pdf). Попробуйте реализовать его самостоятельно. В качестве подсказки используйте следующий пример, кликнув на него. Обратите внимание, что в оригинальной работе Ворли использует более чем одну точку на клетку, и их количество может изменяться. В его программной реализации на языке C это помогает ускорить вычисления благодаря раннему выходу из циклов. Циклы в GLSL не позволяют переменного количества итераций, поэтому вам скорее всего придётся использовать одну опорную точку на клетку.
|
||||
|
||||
<a href="../edit.php#12/vorono-01.frag"><canvas id="custom" class="canvas" data-fragment-url="vorono-01.frag" width="520px" height="200px"></canvas></a>
|
||||
|
||||
Придумайте интересные и творческие применения для этого алгоритма, как только освоите его.
|
||||
|
||||
![Лео Солаас - расширенная диаграмма Вороного(2011)](solas.png)
|
||||
|
||||
![Томас Сарачено - Облачные города (2011)](saraceno.jpg)
|
||||
|
||||
![Клинт Фюлкерсон - Ускоряющийся диск](accretion.jpg)
|
||||
|
||||
![Реза Али - Паззл Вороного(2015)](reza.png)
|
||||
|
||||
### Совершенствуем диаграммы Вороного
|
||||
|
||||
В 2011 году [Стефан Густавсон оптимизировал алгоритм Ворли для GPU](http://webstaff.itn.liu.se/~stegu/GLSL-cellular/GLSL-cellular-notes.pdf), обходя только матрицы 2х2 вместо 3х3. Это существенно уменьшает объём вычислений, но может давать артефакты в виде резких переходов на границах между клетками. Посмотрите на следующий пример.
|
||||
|
||||
<div class="glslGallery" data="12/2d-cnoise-2x2,12/2d-cnoise-2x2x2,12/2d-cnoise,12/3d-cnoise" data-properties="clickRun:editor,openFrameIcon:false"></div>
|
||||
|
||||
Позже, в 2012 году, [Иниго Квилес написал статью о создании точных границ ячеек диаграммы Вороного](http://www.iquilezles.org/www/articles/voronoilines/voronoilines.htm).
|
||||
|
||||
<a href="../edit.php#12/2d-voronoi.frag"><img src="2d-voronoi.gif" width="520px" height="200px"></img></a>
|
||||
|
||||
Эксперименты Иниго над диаграммами Вороного на этом не закончились. В 2014 он написал статью о функции, которую он назвал [шумом Вороного](http://www.iquilezles.org/www/articles/voronoise/voronoise.htm) (англ. voro-noise). Эта функция осуществляет постепенный переход от обычного шума к диаграмме Вороного. Цитата:
|
||||
|
||||
*«Несмотря на внешнюю схожесть, решётка в этих паттернах используется по разному. Шум усредняет или интерполирует случайные значения (в обычном шуме) или градиенты (в градиентном шуме), в то время как алгоритм Вороного вычисляет расстояния до ближайших опорных точек. Гладкая билинейная интерполяция и вычисление минимума - очень разные операции... Но так ли это? Могут ли они быть скомбинированны в более общей метрике? Если бы это было так, то обычный шум и диаграммы Вороного можно было бы рассматривать как частные случаи более общего алгоритма генерации узоров на регулярной решётке.»*
|
||||
|
||||
<a href="../edit.php#12/2d-voronoise.frag"><canvas id="custom" class="canvas" data-fragment-url="2d-voronoise.frag" width="520px" height="200px"></canvas></a>
|
||||
|
||||
Настало время оглянуться вокруг, вдохновиться природой и попробовать собственные силы в этой технике!
|
||||
|
||||
![Deyrolle glass film - 1831](DeyrolleFilm.png)
|
||||
|
||||
<div class="glslGallery" data="12/metaballs,12/stippling,12/cell,12/tissue,12/cracks,160504143842" data-properties="clickRun:editor,openFrameIcon:false"></div>
|
@ -177,7 +177,7 @@ Later in 2012 [Inigo Quilez wrote an article on how to make precise Voronoi bord
|
||||
|
||||
<a href="../edit.php#12/2d-voronoi.frag"><img src="2d-voronoi.gif" width="520px" height="200px"></img></a>
|
||||
|
||||
Inigo's experiments with Voronoi didn't stop there. In 2014 he wrote this nice article about what he calls [voro-noise](http://www.iquilezles.org/www/articles/voronoise/voronoise.htm), an function that allows a gradual blend between regular noise and voronoi. In his words:
|
||||
Inigo's experiments with Voronoi didn't stop there. In 2014 he wrote this nice article about what he calls [voro-noise](http://www.iquilezles.org/www/articles/voronoise/voronoise.htm), a function that allows a gradual blend between regular noise and voronoi. In his words:
|
||||
|
||||
*"Despite this similarity, the fact is that the way the grid is used in both patterns is different. Noise interpolates/averages random values (as in value noise) or gradients (as in gradient noise), while Voronoi computes the distance to the closest feature point. Now, smooth-bilinear interpolation and minimum evaluation are two very different operations, or... are they? Can they perhaps be combined in a more general metric? If that was so, then both Noise and Voronoi patterns could be seen as particular cases of a more general grid-based pattern generator?"*
|
||||
|
||||
|
111
13/README-ru.md
Normal file
111
13/README-ru.md
Normal file
@ -0,0 +1,111 @@
|
||||
![Due East over Shadequarter Mountain - Matthew Rangel (2005) ](rangel.jpg)
|
||||
|
||||
## Фрактальное броуновское движение
|
||||
|
||||
Разные люди понимают слово «шум» по разному. Музыкант подумает о неприятных беспорядочных звуках, инженер связи - о помехах, а астрофизик - о микроволновом космическом фоне. Эти понятия заставляют задуматься о физической природе беспорядка в окружающем мире. Но всё же давайте начнём с более простых и более фундаментальных вещей: волн и их свойств. Волна - это изменение какого-то значения со временем. Звуковые волны - это флуктуации давления воздуха, электромагнитные волны - флуктуации электрического и магнитного полей. Амплитуда и частота являются очень важными характеристиками волны. Уравнение простой линейной (одномерной) волны выглядит примерно так:
|
||||
|
||||
<div class="simpleFunction" data="
|
||||
float amplitude = 1.;
|
||||
float frequency = 1.;
|
||||
y = amplitude * sin(x * frequency);
|
||||
"></div>
|
||||
|
||||
* Попробуйте изменять значения частоты и амплитуды, чтобы понять их поведение.
|
||||
* Используя функции формы, изменяйте амплитуду в зависимости от времени.
|
||||
* Аналогично, варьируйте частоту в зависимости от времени.
|
||||
|
||||
При выполнении двух последних упражнений вы модулировали синусоидальную волну, создав тем самым AM (амплитудную модуляцию) и FM (частотную модуляцию). Поздравляю!
|
||||
|
||||
Ещё одна интересная особенность волн - это их способность суммироваться, то есть образовывать суперпозицию. Закомментируйте, раскомментируйте и изменяйте параметры в следующих строках. Понаблюдайте как при этом изменяется внешний вид графика суммы волн с различной амплитудой и частотой.
|
||||
|
||||
<div class="simpleFunction" data="
|
||||
float amplitude = 1.;
|
||||
float frequency = 1.;
|
||||
y = sin(x * frequency);
|
||||
float t = 0.01*(-u_time*130.0);
|
||||
y += sin(x*frequency*2.1 + t)*4.5;
|
||||
y += sin(x*frequency*1.72 + t*1.121)*4.0;
|
||||
y += sin(x*frequency*2.221 + t*0.437)*5.0;
|
||||
y += sin(x*frequency*3.1122+ t*4.269)*2.5;
|
||||
y *= amplitude*0.06;
|
||||
"></div>
|
||||
|
||||
* Поэксперементируйте с изменением амплитуды и частоты суммируемых волн.
|
||||
* Можно ли сделать две взаимоуничтожающихся волны? Как это будет выглядеть?
|
||||
* Можно ли сложить волны таким образом, чтобы они усиливали друг друга?
|
||||
|
||||
В музыке каждая нота ассоциируется с определённой частотой. Частоты музыкальных нот подчиняются определённому порядку, который мы называем гаммой. Удвоение или уменьшение чистоты вдвое соответствует изменению ноты на одну октаву.
|
||||
|
||||
Теперь давайте воспользуемся шумом Перлина вместо синусоидальной волны! Шум Перлина в самой простой форме выглядит и ощущается так же, как синусоидальная волна. Его амплитуда и частота немного плавают, но амплитуда остаётся достаточно однородной, а частота ограничена довольно узким диапазоном вокруг центральной частоты. Поведение этого шума менее предсказуемо, чем синусоидальная волна, а значит с его помощью проще создать иллюзию беспорядка, сложив несколько волн шума с различными параметрами. Сумму синусоидальных волн тоже можно заставить выглядеть беспорядочно, но для этого понадобится очень много волн, чтобы скрыть их периодическую, регулярную природу.
|
||||
|
||||
Складывая различные итерации шума (*октавы*), в которых мы последовательн увеличиваем частоту на одну и ту же величину (*лакунарность*) и понижаем амплитуду (*усиление*) шума, можно получить более качественный и детализированный рисунок шума. Эта методика называется «Фрактальным броуновским движением» (*фБД*), или попросту «фрактальным шумом». В простейшем варианте она реализуется следующим кодом:
|
||||
|
||||
<div class="simpleFunction" data="// Свойства
|
||||
const int octaves = 1;
|
||||
float lacunarity = 2.0;
|
||||
float gain = 0.5;
|
||||
//
|
||||
// Начальные значения
|
||||
float amplitude = 0.5;
|
||||
float frequency = 1.;
|
||||
//
|
||||
// Цикл по октавам
|
||||
for (int i = 0; i < octaves; i++) {
|
||||
	y += amplitude * noise(frequency*x);
|
||||
	frequency *= lacunarity;
|
||||
	amplitude *= gain;
|
||||
}"></div>
|
||||
|
||||
* Увеличивайте количество октав, так чтобы цикл совершал 1, 2, 4 8 и 10 итераций. Наблюдайте за происходящим.
|
||||
* При количестве октав больше четырёх попробуйте изменить значение лакунарности.
|
||||
* Так же, при количестве октав больше четырёх, измените усиление (gain) и посмотрите что произойдёт.
|
||||
|
||||
Обратите внимание, как с каждой новой октавой кривая становится более детализированной. Так же обратите внимание на самоподобность, появляющуюся с увеличением количества октав. При увеличении кривой боле мелкие части выглядят почти так же, как и вся кривая, а любой участок кривой выглядит похожим на любой другой участок. Это важное свойство математических фракталов, которое мы симулируем с помощью цикла. Ме не создаём *настоящий* фрактал, потому что мы останавливаем симуляцию после нескольких итераций, но, теоретически, мы могли бы получить настоящий фрактал, если бы позволили циклу крутиться бесконечно долго, складывая бесконечно много компонент шума. В компьютерной графике всегда есть предел того, насколько малые объекты мы можем различить. Например, некоторые детали могут стать меньше размера пикселя, поэтому нет никакого смысла обсчитывать бесконечные суммы для создания математически точного фрактала. Иногда может пригодиться очень много слагаемых, но их никогда не понадобится бесконечно много.
|
||||
|
||||
В следующем примере реализовано двумерное фБД, которое создаёт похожее на фрактал изображение:
|
||||
|
||||
<div class='codeAndCanvas' data='2d-fbm.frag'></div>
|
||||
|
||||
* Уменьшите количество октав, изменив значение в строке 37
|
||||
* Измените значение лакунарности фБД в строке 47
|
||||
* Посмотрите что будет при изменении усиления в строке 48
|
||||
|
||||
Эта техника обычно используется для создания процедурных ландшафтов. Самоподобность фБД идеальна для создания гор, потому что естественная эрозия, обрабатывающая настоящие горы, создёт самоподобные образы в большом диапазоне масштабов. Если вас это заинтересовало, вам определённо стоит прочитать [замечательную статью Иниго Квилеса о подвинутых способах получения шума](http://www.iquilezles.org/www/articles/morenoise/morenoise.htm).
|
||||
|
||||
![ Дэн Холдсворт - блэкаут (2010)](holdsworth.jpg)
|
||||
|
||||
Используя этот подход практически в неизменном виде, можно получить много других эффектов, например так называемую **турбулентность**. Это то же самое фБД, составленное из абсолютных величин шума, что добавляет к изображению резкие впадины.
|
||||
|
||||
```glsl
|
||||
for (int i = 0; i < OCTAVES; i++) {
|
||||
value += amplitude * abs(snoise(st));
|
||||
st *= 2.;
|
||||
amplitud *= .5;
|
||||
}
|
||||
```
|
||||
|
||||
<a href="../edit.php#13/turbulence.frag"><img src="turbulence-long.png" width="520px" height="200px"></img></a>
|
||||
|
||||
Другой представитель этого семейства алгоритмов позволяет генерировать острые **хребты**, то есть перевёрнутые вверх дном впадины:
|
||||
|
||||
```glsl
|
||||
n = abs(n); // создаём трещины
|
||||
n = offset - n; // переворачиваем трещины вверх дном
|
||||
n = n * n; // делаем их ещё более острыми
|
||||
```
|
||||
|
||||
<a href="../edit.php#13/ridge.frag"><img src="ridge-long.png" width="520px" height="200px"></img></a>
|
||||
|
||||
Так же можно получать функции с полезными свойствами, если перемножать компоненты шума вместо суммирования. Другого интересного эффекта можно достичь, масштабируя каждую функцию шума на величину, зависящую от предшествующих слагаемых. Делая подобные вещи, мы отклоняемся от строгого определения фрактала в сторону сравнительно малоизученной области «мультифракталов». Мультифракталы пока не имеют строгого математического определения, но это не делает их менее полезными для компьютерной графики. Наоборот, мультифракталы повсеместно встречаются в современном коммерческом программном обеспечении для генерации ландшафтов. Подробнее об этом можно прочитать в 16 главе книги Кентона Масгрейва «Текстурирование и моделирование: процедурный подход (третье издание)» (Texturing and Modeling: a Procedural Approach). К несчастью, эту книгу перестали издавать несколько лет назад, но её по прежнему можно найти в библиотеках или на вторичном рынке. В онлайн-магазинах есть PDF-версия первого издания, но я не советую её покупать - это пустая трата денег. Она издана в 1994 году и не содержит ничего о моделировании ландшафтов из третьего издания.
|
||||
|
||||
### Искривление областей
|
||||
|
||||
[Иниго Квилес написал ещё одну замечательную статью](http://www.iquilezles.org/www/articles/warp/warp.htm) о свёртывании пространства фБД с помощью фБД. Взрыв мозга? Да, это как сон внутри сна в фильме «Начало».
|
||||
|
||||
![ Иниго Квилес - f(p) = fbm( p + fbm( p + fbm( p ) ) ) (2002)](quiles.jpg)
|
||||
|
||||
Менее экстремальный пример такого подхода реализован в коде ниже, где сворачивание используется для создания текстуры, похожей на облака. Обратите внимание на сохранившееся самоподобие в этом примере.
|
||||
|
||||
<div class='codeAndCanvas' data='clouds.frag'></div>
|
||||
|
||||
Оборачивание текстурных координат шумом - это весело, полезно и возможно не просто в изучении. Это мощный инструмент, требующий некоторого опыта в использовании. Например, можно смещать координаты на величину производной (градиента) шума. На этой идее основана [знаменитая статья Кена Перлина и Фабриса Нере под названием «Шум потока»](http://evasion.imag.fr/Publications/2001/PN01/). Некоторые современные реализации шума Перлина основаны на вычислении функции и её аналитического градиента. Иногда «настоящий» градиент для процедурной функции вычислить невозможно, но всегда можно вычислить его приближение конечной разностью, хотя результат будет менее точным и потребует больше вычислений.
|
107
README-ru.md
Normal file
107
README-ru.md
Normal file
@ -0,0 +1,107 @@
|
||||
<canvas id="custom" class="canvas" data-fragment-url="src/moon/moon.frag" data-textures="src/moon/moon.jpg" width="350px" height="350px"></canvas>
|
||||
|
||||
# The Book of Shaders
|
||||
*[Патрицио Гонзалес Виво](http://patriciogonzalezvivo.com/) и [Джен Лав](http://jenlowe.net/)*
|
||||
|
||||
Пошаговое руководство по абстрактной и сложной вселенной фрагментных шейдеров.
|
||||
|
||||
<div class="header">
|
||||
<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=B5FSVSHGEATCG" style="float: right;"><img src="https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif" alt=""></a>
|
||||
</div>
|
||||
|
||||
## Содержание
|
||||
|
||||
* [Об этой книге](00/?lan=ru)
|
||||
|
||||
* Введение
|
||||
* [Что такое шейдер?](01/?lan=ru)
|
||||
* [“Hello world!”](02/?lan=ru)
|
||||
* [Uniform-переменные](03/?lan=ru)
|
||||
* [Запуск шейдера](04/?lan=ru)
|
||||
|
||||
* Алгоритмическое рисование
|
||||
* [Формообразующие функции](05/?lan=ru)
|
||||
* [Цвета](06/?lan=ru)
|
||||
* [Фигуры](07/?lan=ru)
|
||||
* [Матрицы](08/?lan=ru)
|
||||
* [Узоры](09/?lan=ru)
|
||||
|
||||
* Генеративный дизайн
|
||||
* [Беспорядок](10/?lan=ru)
|
||||
* [Шум](11/?lan=ru)
|
||||
* [Клеточный шум](12/?lan=ru)
|
||||
* [Фрактальное броуновское движение](13/?lan=ru)
|
||||
* Фракталы
|
||||
|
||||
* Обработка изображений
|
||||
* Текстуры
|
||||
* Операции над изображениями
|
||||
* Свёртка с ядром
|
||||
* Фильтры
|
||||
* Другие эффекты
|
||||
|
||||
* Симуляция
|
||||
* Пинг-понг
|
||||
* Игра «Жизнь» Конвея
|
||||
* Рябь
|
||||
* Вода
|
||||
* Реакционно-диффузная модель
|
||||
|
||||
* 3D-графика
|
||||
* Освещение
|
||||
* Карты нормалей
|
||||
* Карты высот
|
||||
* Ray marching
|
||||
* Карты окружения (сферические и кубические)
|
||||
* Отражение и преломление
|
||||
|
||||
* [Приложение:](appendix/?lan=ru) Другие варианты использования этой книги
|
||||
* [Как читать книгу оффлайн?](appendix/00/?lan=ru)
|
||||
* [Как запустить примеры на Raspberry Pi?](appendix/01/?lan=ru)
|
||||
* [Как напечатать книгу?](appendix/02/?lan=ru)
|
||||
* [Как принять участие в создании книги?](appendix/03/?lan=ru)
|
||||
* [Введение для JavaScript-программистов](appendix/04/?lan=ru) ([Николя Баррадо](http://www.barradeau.com/))
|
||||
|
||||
* [Примеры](examples/)
|
||||
|
||||
* [Глоссарий](glossary/)
|
||||
|
||||
## Об авторах
|
||||
|
||||
[Патрицио Гонзалес Виво](http://patriciogonzalezvivo.com/) (1982, Буэнос Айрес, Аргентина) - художник и разработчик, живущий в Нью-Йорке. Он исследует многообразие пространств между природным и рукотворным, аналоговым и цифровым, индивидуальным и коллективным. В своей работе он использует код как средство выразительности, делая этот мир лучше.
|
||||
|
||||
Патрицио изучал и практиковал психотерапию и арт-терапию. Он получил степень магистра наук и искусств в области дизайна и технологии в Parsons The New School, где в настоящее время преподаёт. Сейчас он работает инженером по графике в Mapzen, где создаёт картографический инструментарий с открытым исходным кодом.
|
||||
|
||||
<div class="header"> <a href="http://patriciogonzalezvivo.com/" target="_blank">Сайт</a> - <a href="https://twitter.com/patriciogv" target="_blank">Twitter</a> - <a href="https://github.com/patriciogonzalezvivo" target="_blank">GitHub</a> - <a href="https://vimeo.com/patriciogv" target="_blank">Vimeo</a> - <a href="https://www.flickr.com/photos/106950246@N06/" target="_blank"> Flickr</a></div>
|
||||
|
||||
[Джен Лав](http://jenlowe.net/) собирает воедино людей, числа и слова, работая независимым исследователем данных в Datatelling. Она преподаёт в Нью-Йоркской школе изобразительного искусства по программе социальных инноваций, является соучредителем Школы поэтических коммуникаций, преподавала математику для художников в NYU ITP, занималась исследованиями в Лаборатории пространственного дизайна в Университете Колумбии, вносила предложения в офис Белого дома по науке и технологиям. Выступала на SXSW и Eyeo. Её работы освещались в The New York Times и Fast Company. Её исследования, публикации и доклады затрагивают перспективы и последствия развития технологий обработки данных для общества. Имеет степень бакалавра в области прикладной математики и степень магистра в области информатики.
|
||||
|
||||
<div class="header"> <a href="http://jenlowe.net/" target="_blank">Сайт</a> - <a href="https://twitter.com/datatelling" target="_blank">Twitter</a> - <a href="https://github.com/datatelling" target="_blank">GitHub</a></div>
|
||||
|
||||
## Благодарности
|
||||
|
||||
Спасибо [Скотту Мюррею](http://alignedleft.com/) за вдохновение и советы.
|
||||
|
||||
Спасибо [Кеничи Йонеде (Kynd)](https://twitter.com/kyndinfo), [Николя Баррадо](https://twitter.com/nicoptere) и [Кариму Нааджи](http://karim.naaji.fr/) за поддержку, хорошие идеи и код.
|
||||
|
||||
Спасибо [Кеничи Йонеде (Kynd)](https://twitter.com/kyndinfo) и [Савако](https://twitter.com/sawakohome) за [японский перевод (日本語訳)](?lan=jp)
|
||||
|
||||
Спасибо [Тонь Ли](https://www.facebook.com/tong.lee.9484) и [И Жань](https://www.facebook.com/archer.zetta?pnref=story) за [китайский перевод (中文版)](?lan=ch)
|
||||
|
||||
Спасибо [Ча Хьюн Ю](https://www.facebook.com/fkkcloud) за [корейский перевод (한국어)](?lan=kr)
|
||||
|
||||
Спасибо [Науэлю Копперо (Necsoft)](http://hinecsoft.com/) за [испанский перевод (español)](?lan=es)
|
||||
|
||||
Спасибо [Николя Баррадо](https://twitter.com/nicoptere) и [Кариму Нааджи](http://karim.naaji.fr/) за [французский перевод (français)](?lan=fr)
|
||||
|
||||
Спасибо [Андреа Ровескалли](https://www.earove.info) за [итальянский перевод (italiano)](?lan=it)
|
||||
|
||||
Спасибо [Майклу Тишеру](http://www.mitinet.de) за [немецкий перевод (deutsch)](?lan=de)
|
||||
|
||||
Спасибо всем кто поверил в этот проект и поддержал его [исправлениями](https://github.com/patriciogonzalezvivo/thebookofshaders/graphs/contributors) или пожертвованиями.
|
||||
|
||||
## Новые параграфы
|
||||
|
||||
Чтобы получать оповещение о новых параграфах, подпишитесь на почтовую рассылку или [Твиттер](https://twitter.com/bookofshaders)
|
||||
|
||||
<form style="border:1px solid #ccc;padding:3px;text-align:center;" action="https://tinyletter.com/thebookofshaders" method="post" target="popupwindow" onsubmit="window.open('https://tinyletter.com/thebookofshaders', 'popupwindow', 'scrollbars=yes,width=800,height=600');return true"><a href="https://tinyletter.com/thebookofshaders"><p><label for="tlemail">Enter your email address</label></p></a><p><input type="text" style="width:140px" name="email" id="tlemail" /></p><input type="hidden" value="1" name="embed"/><input type="submit" value="Subscribe" /><p><a href="https://tinyletter.com" target="_blank"></a></p></form>
|
@ -33,7 +33,7 @@ This is a gentle step-by-step guide through the abstract and complex universe of
|
||||
* [Fractional brownian motion](13/)
|
||||
* Fractals
|
||||
|
||||
* Image processing:
|
||||
* Image processing
|
||||
* Textures
|
||||
* Image operations
|
||||
* Kernel convolutions
|
||||
|
33
appendix/00/README-ru.md
Normal file
33
appendix/00/README-ru.md
Normal file
@ -0,0 +1,33 @@
|
||||
## Как читать книгу оффлайн?
|
||||
|
||||
Предположим, вы отправились в длительное путешествие, и хотите немного подучить программирование шейдеров за это время. В этом случае вы можете скопировать книгу к себе на компьютер и запустить сервер локально.
|
||||
|
||||
Для этого понадобится только PHP, Python 2.6 и git-клиент. На MacOS и Raspberry Pi Python установлен по умолчанию, но PHP и Git придётся установить следующим образом:
|
||||
|
||||
На **MacOSX** убедитесь, что у вас установлен [homebrew](http://brew.sh/) и выполните следующие команды в терминале:
|
||||
|
||||
```bash
|
||||
brew update
|
||||
brew upgrade
|
||||
brew install git php
|
||||
```
|
||||
|
||||
На **Raspberry Pi** установите [Raspbian](https://www.raspberrypi.org/downloads/raspbian/) - дистрибутив Linux для Raspberry Pi, основанный на Debian, и выполните:
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get upgrade
|
||||
sudo apt-get install git-core glslviewer php
|
||||
```
|
||||
|
||||
Когда всё установлено, остаётся последний шаг:
|
||||
|
||||
```bash
|
||||
cd ~
|
||||
git clone --recursive https://github.com/patriciogonzalezvivo/thebookofshaders.git
|
||||
cd thebookofshaders
|
||||
git submodule foreach git submodule init && git submodule update
|
||||
php -S localhost:8000
|
||||
```
|
||||
|
||||
Теперь откройте в браузере адрес [`http://localhost:8000/`](http://localhost:8000/)
|
18
appendix/01/README-ru.md
Normal file
18
appendix/01/README-ru.md
Normal file
@ -0,0 +1,18 @@
|
||||
## Как запустить примеры на Raspberry Pi?
|
||||
|
||||
Несколько лет назад было бы слишком опрометчиво предположить, что у каждого есть компьютер с графическим ускорителем. Теперь же большинство компьютеров содержат GPU, но требование обязательного его наличия является завышенным для учебной лаборатории или класса.
|
||||
|
||||
Благодаря [Raspberry Pi Foundation](http://www.raspberrypi.org/), в учебных классах появился новый тип небольших и дешёвых компьютеров (около $35 за штуку). Что более важно для данной книги, [Raspberry Pi](http://www.raspberrypi.org/) поставляется с приличным GPU фирмы Broadcom, который доступен напрямую из консоли. Я написал гибкий инструмент для программирования на GLSL в реальном времени под названием [**glslViewer**](https://github.com/patriciogonzalezvivo/glslViewer). С его помощью можно запустить все примеры из этой книги. Эта программа может выполнять обновление автоматически когда пользователь сохраняет изменения в коде. Что это означает? Каждый раз, когда вы сохраняете шейдер в процессе редактирования, он будет перезапущен и перерисует изображение.
|
||||
|
||||
Сделав локальную копию репозитория книги (см. [предыдущий параграф](../00/?lan=ru)) и установив [`glslViewer`](https://github.com/patriciogonzalezvivo/glslViewer), вы можете запустить примеры. Используя флаг `-l`, вы можете рендерить примеры в углу экрана прямо во время редактирования любым редактором (`nano`, `pico`, `vi`, `vim` или `emacs`). Это так же работает при подключении по ssh или sftp.
|
||||
|
||||
Чтобы установить всё необходимое на Raspberry Pi, после установки [Raspbian](https://www.raspberrypi.org/downloads/raspbian/) и входа в систему, выполните следующие команды:
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get upgrade
|
||||
sudo apt-get install git-core glslviewer
|
||||
cd ~
|
||||
git clone https://github.com/patriciogonzalezvivo/thebookofshaders.git
|
||||
cd thebookofshaders
|
||||
```
|
61
appendix/02/README-ru.md
Normal file
61
appendix/02/README-ru.md
Normal file
@ -0,0 +1,61 @@
|
||||
## Как напечатать книгу?
|
||||
|
||||
Допустим, вам не нужна навигация по тексту или взаимодействие с примерами, и вы хотите просто почитать книгу на пляже или по пути в город. В таком случае вы можете напечатать книгу.
|
||||
|
||||
|
||||
#### Установка glslViewer
|
||||
|
||||
Чтобы напечатать книгу, её нужно сначала распарсить. Для этого потребуется [`glslViewer`](https://github.com/patriciogonzalezvivo/glslViewer) - консольный инструмент, который скомпилирует примеры шейдеров и преобразует их в изображения.
|
||||
|
||||
На **MacOSX** убедитесь, что у вас есть [homebrew](http://brew.sh/), и выполните в терминале следующее:
|
||||
|
||||
```bash
|
||||
brew update
|
||||
brew upgrade
|
||||
brew tap homebrew/versions
|
||||
brew install glfw3
|
||||
cd ~
|
||||
git clone http://github.com/patriciogonzalezvivo/glslViewer.git
|
||||
cd glslViewer
|
||||
make
|
||||
make install
|
||||
```
|
||||
|
||||
На **Raspberry Pi** установите [Raspbian](https://www.raspberrypi.org/downloads/raspbian/) - дистрибутив Linux для Raspberry Pi, основанный на Debian, и выполните:
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get upgrade
|
||||
sudo apt-get install git-core glslviewer
|
||||
```
|
||||
|
||||
#### Установка Python 2.7, Latex Engine и Pandoc
|
||||
|
||||
Для разбора Markdown-разметки параграфов в Latex и затем в PDF, воспользуемся Xetex и Pandoc.
|
||||
|
||||
На **MacOSX**:
|
||||
|
||||
Скачайте и установите [basictex & MacTeX-Additions](http://www.tug.org/mactex/morepackages.html), затем установите [Pandoc](http://johnmacfarlane.net/pandoc/) и Python с помощью команды:
|
||||
|
||||
```bash
|
||||
brew install pandoc python2.7
|
||||
```
|
||||
|
||||
На **Raspberry Pi** (Raspbian):
|
||||
|
||||
```bash
|
||||
sudo apt-get install texlive-xetex pandoc python2.7
|
||||
```
|
||||
|
||||
#### Компиляция книги в pdf и печать
|
||||
|
||||
Когда всё необходимое установлено, склонируйте [репозиторий книги](https://github.com/patriciogonzalezvivo/thebookofshaders) и скомпилируйте её:
|
||||
|
||||
```bash
|
||||
cd ~
|
||||
git clone https://github.com/patriciogonzalezvivo/thebookofshaders.git
|
||||
cd thebookofshaders
|
||||
make
|
||||
```
|
||||
|
||||
Если всё прошло хорошо, вы увидите файл `book.pdf`, который можно прочитать на любом устройстве или распечатать.
|
62
appendix/03/README-ru.md
Normal file
62
appendix/03/README-ru.md
Normal file
@ -0,0 +1,62 @@
|
||||
## Как принять участие в создании книги?
|
||||
|
||||
Благодарим за ваше желание помочь! Есть множество способов это сделать:
|
||||
|
||||
- Перевод
|
||||
- Доработка [глоссария](https://github.com/patriciogonzalezvivo/thebookofshaders/tree/master/glossary)
|
||||
- Редактирование
|
||||
- Выкладывание ваших шейдеров в общий доступ с помощью [онлайн-редактора](http://editor.thebookofshaders.com/)
|
||||
|
||||
### Перевод
|
||||
|
||||
Книга написана на языке разметки [Markdown](https://daringfireball.net/projects/markdown/syntax), поэтому с её текстом очень легко работать.
|
||||
|
||||
1. Для начала перейдите в репозиторий по адресу [```github.com/patriciogonzalezvivo/thebookofshaders```](https://github.com/patriciogonzalezvivo/thebookofshaders). Осмотрите файлы и каталоги в нём. Легко заметить, что контент находится в файлах ```README.md``` и других файлах, названных заглавными буквами: ```TITLE.md```, ```SUMMARY.md``` и так далее. Так же вы заметите, что переводы находятся в файлах, имена которых заканчиваются двумя буквами, указывающими на язык перевода, например: ```README-jp.md```, ```README-es.md``` и прочие.
|
||||
|
||||
2. Форкните репозиторий и склонируйте его к себе на компьютер.
|
||||
|
||||
3. Продублируйте содержимое файлов, которые хотите перевести. Не забудьте добавить двухбуквенный код языка, на который переводите.
|
||||
|
||||
4. Переведите контент (см. **Примечания о переводе**).
|
||||
|
||||
5. Протестируйте (см. **Тестирование**).
|
||||
|
||||
6. Сделайте push в ваш собственный форк и затем создайте [пулл-реквест](https://help.github.com/articles/using-pull-requests/) в основной репозиторий.
|
||||
|
||||
#### Примечания о переводе
|
||||
|
||||
Не удаляйте и не изменяйте встроенные примеры, которые выглядят примерно так:
|
||||
|
||||
```html
|
||||
<div class="codeAndCanvas" data="grid-making.frag"></div>
|
||||
```
|
||||
|
||||
или так:
|
||||
|
||||
```html
|
||||
<div class="simpleFunction" data="y = mod(x,2.0);"></div>
|
||||
```
|
||||
|
||||
#### Тестирование
|
||||
|
||||
Запустите локальный PHP-сервер в папке вашего локального репозитория:
|
||||
|
||||
```bash
|
||||
php -S localhost:8000
|
||||
```
|
||||
|
||||
Теперь в браузере перейдите по адресу ```localhost:8000```, зайдите на переводимую страницу и добавьте в конец адреса строку ```?lan=xx```, где ```xx``` - код языка, на который переводите.
|
||||
|
||||
Например, если вы переводите главу ```03``` на французский, значит вы работали над файлом ```03/README-fr.md```, который можно протестировать по адресу ```http://localhost:8000/03/?lan=fr```.
|
||||
|
||||
### Доработка глоссария
|
||||
|
||||
Этот раздел находится в разработке. Мы рады узнать ваше мнение о том, как сделать из него что-то полезное. Пишите нам на [@bookofshaders](https://twitter.com/bookofshaders).
|
||||
|
||||
### Редактирование
|
||||
|
||||
Все мы люди. Если вы видите ошибку - сообщите о ней и сделайте пулл-реквест, или откройте issue на гитхабе. Спасибо!
|
||||
|
||||
### Выкладывайте ваши шейдеры
|
||||
|
||||
Вы увидите множество ссылок на [онлайн-редактор](http://editor.thebookofshaders.com/) и написанный в нём код, встроенный в книгу. Если вы написали что-то стоящее, нажмите «Export» (или иконку ```⇪```) и скопируйте ссылку на код. Отправьте её на [@bookofshaders](https://twitter.com/bookofshaders) или [@kyndinfo](https://twitter.com/kyndinfo). Мы будем рады видеть ваш код и добавить его в [галерею примеров](https://thebookofshaders.com/examples/).
|
379
appendix/04/README-ru.md
Normal file
379
appendix/04/README-ru.md
Normal file
@ -0,0 +1,379 @@
|
||||
## Введение для JavaScript-программистов
|
||||
автор [Николя Баррадо](http://www.barradeau.com/)
|
||||
|
||||
|
||||
Если вы JavaScript-разработчик, велика вероятность, что вы будете немного озадаченЫ, читая эту книгу. В самом деле, есть множество различий между манипулированием высокоуровневыми абстракциями на JS и ковырянием в шейдерах. Но, в отличие от лежащего на более низком уровне языка ассемблера, GLSL является человекочитаемым, и я уверен, что разобравшись с его особенностями, вы быстро сможете начать его использовать.
|
||||
|
||||
Я предполагаю, что у вас есть хотя бы поверхностные знания JavaScript и Canvas API. Если это не так - ничего страшного. Вам всё равно будет интересно читать большую часть этой главы.
|
||||
|
||||
Так же, я не буду сильно углубляться в детали, и некоторые вещи могут быть лишь _полуправдой_. Эта глава не является подробным руководством.
|
||||
|
||||
JavaScript очень хорош для быстрого прототипирования. Вы можете беспорядочно набросать кучу нетипизированных переменных и методов, динамически добавлять и удалять члены класса, обновить страницу и увидеть как она работает. Затем сделать изменения в соответствии с увиденным, обновить страницу, повторить. Жизнь - простая штука. Так в чём же разница между JavaScript и GLSL? Они оба работают в браузере, оба используются для рисования всяких прикольных штук на экране, и к тому же, JS проще в использовании.
|
||||
|
||||
ОСновная разница в том, что JavaScript - **интерпретируемый** язык, в то время как GLSL - **компилируемый**. **Скомпилированная** программа исполняется нативно, она является низкоуровневой и в целом высокопроизводительна. **Интерпретируемая** программа требует [виртуальную машину](https://ru.wikipedia.org/wiki/%D0%92%D0%B8%D1%80%D1%82%D1%83%D0%B0%D0%BB%D1%8C%D0%BD%D0%B0%D1%8F_%D0%BC%D0%B0%D1%88%D0%B8%D0%BD%D0%B0) для своего исполнения, является высокоуровневой и в общем случае более медленной.
|
||||
|
||||
Когда браузер (_**виртуальная машина** JavaScript_) **исполняет** или **интерпретирует** кусок кода на JS, он не имеет ни малейшего понятия чем является каждая переменная и что делает каждая функция (за исключением **типизированных массивов**). Поэтому он не может оптимизировать что-либо _наперёд_. Чтение кода браузером занимает какое-то время, чтобы **вывести** (исходя из использования) типы переменных и методов, и, по возможности, преобразовать _часть_ кода в ассемблер, который будет исполняться намного быстрее.
|
||||
|
||||
Это медленный, болезненный и до сумасшествия сложный процесс. Если вам интересны подробности, рекомендую посмотреть как [работает движок V8 в Хроме](https://developers.google.com/v8/). Хуже всего то, что браузер оптимизирует JS как ему хочется, и этот процесс _скрыт_ от программиста. Вы бессильны.
|
||||
|
||||
**Компилируемая** программа не интерпретируется на ходу. Её запускает операционная система, и она исполняется, если она корректна. Это многое меняет. Если вы забудете точку с запятой в конце строки, ваш код станет некорректным и просто не скомпилируется. Он вообще не превратится в программу.
|
||||
|
||||
Это сурово, но это то, чем является **шейдер**: _компилируемая программа для исполнения на GPU_. Не пугайтесь! **Компилятор**, то есть та программа которая проверяет ваш код на корректность, станет вашим лучшим другом. Примеры и [редактор](http://editor.thebookofshaders.com/) в этой книге очень дружественны к пользователю. Она подскажут в каком месте программа не скомпилировалась, и когда после всех правок шейдер будет готов к компиляции, результат его работы будет немедленно отображён. Это отличный способ обучения в силу его наглядности и невозможности что-либо сломать.
|
||||
|
||||
И последнее: **шейдер** состоит из двух программ: **вершинного** и **фрагментного** шейдера. Вкратце, вершинный шейдер (первая программа) принимает на вход и преобразовывет *геометрию*, которая затем превращается в последовательность **пикселей** (или **фрагментов**), поступающих на вход второго шейдера. И уже второй шейдер решает в какой цвет нужно покрасить пиксели. Эта книга посвящена именно вторым шейдерам. Во всех примерах геометрия - это прямоугольник, покрывающий всю доступную область.
|
||||
|
||||
Готовы?
|
||||
|
||||
Поехали!
|
||||
|
||||
### Сильная типизация
|
||||
![первая картинка в Гугле по запросу «сильные типы» на 20 мая 2016](strong_type.jpg)
|
||||
|
||||
Когда вы приходите с JS или любого другого нетипизированного языка, **типизирование** переменных является для вас чужеродной концепцией, и это станет сложнейшим шагом при переходе к GLSL. **Типизация**, как легко догадаться, означает, что вам придётся давать **тип** каждой переменной и функции. Отсюда следует, что ключевого слова **`var`** больше не существует. Считайте, что полиция мыслей от GLSL стёрла его из общеупотребимого языка и вы больше не можете его произносить потому что, ну... его не существует.
|
||||
|
||||
Вместо использования волшебного слова **`var`** вам придётся _явно указывать тип каждой переменной_, тогда компилятор увидит те объекты и примитивы, с которыми он умеет эффективно обращаться. Обратная сторона невозможности использования ключевого слова **`var`** заключается в том, что вам нужно очень хорошо знать особенности типов всех переменных. Но поверьте, типов в GLSL немного, и они все достаточно просты (GLSL - не Java-фреймворк).
|
||||
|
||||
Всё это может выглядеть пугающе, но всё же это не сильно отличается от того, что вы обычно делаете на JS. Например, если переменная булева, то в ней может храниться только `true` или `false`. Если переменная называется `var uid = XXX;`, то в ней вероятно хранится целочисленное значение. Если же она объявлена как `var y = YYY;`, то это _возможно_ ссылка на значение с плавающей точкой. Что ещё лучше, при использовании сильных типов вам не придётся гадать что означает `X == Y`, и означает ли это `typeof X == typeof Y`, или `typeof X !== null && Y...`. В любом случае, вы *знаете* что здесь написано, а если и не знаете, то компилятор знает точно.
|
||||
|
||||
Перечислим **скалярные типы** языка GLSL (**скаляр** описывает количество): `bool` (булев тип), `int` (целочисленный) и `float` (значения с плавающей точкой). Есть и другие типы, но пока давайте рассмотрим как объявляются переменные в GLSL:
|
||||
|
||||
```glsl
|
||||
// булево значение
|
||||
JS: var b = true; GLSL: bool b = true;
|
||||
|
||||
// целое значение
|
||||
JS: var i = 1; GLSL: int i = 1;
|
||||
|
||||
// число с плавающей точкой
|
||||
JS: var f = 3.14159; GLSL: float f = 3.14159;
|
||||
```
|
||||
Не очень трудно, правда? Как было замечено выше, такой подход делает программирование проще, так как вы не тратите время на выслеживание типа какой-либо переменной. Всё ещё сомневаетесь? Помните, что это так же делается для того, чтобы ваша программа исполнялась в разы быстрее, чем на JS.
|
||||
|
||||
#### void
|
||||
В GLSL есть тип `void`, который приблизительно соответствует `null`. Он используется в качестве возвращаемого типа для метода, который не возвращает ничего, и вы не можете объявить переменную этого типа.
|
||||
|
||||
#### boolean
|
||||
Как вам известно, булевы значения в основном используются для проверки условий: `if( myBoolean == true ){}else{}`. Условное ветвление очень легко использовать на CPU, но [параллельная природа](http://thebookofshaders/01/?lan=ru) GLSL делает это утверждение не совсем верным. Как правило, использование условных переходов не рекомендуется, и в книге описано несколько способов обойти это ограничение.
|
||||
|
||||
#### приведение типов
|
||||
Как говорил [Боромир](https://ru.wikipedia.org/wiki/%D0%91%D0%BE%D1%80%D0%BE%D0%BC%D0%B8%D1%80), нельзя просто так взять и смешать типизированные примитивы. В отличие от JavaScript, GLSL не позволит вам выполнять операции между переменными различных типов.
|
||||
|
||||
Например вот это:
|
||||
```glsl
|
||||
int i = 2;
|
||||
float f = 3.14159;
|
||||
|
||||
// попытка умножить целое на значение с плавающей точкой
|
||||
float r = i * f;
|
||||
```
|
||||
не будет работать, потому что вы пытаетесь скрестить **_кошку_** с **_жирафом_**. Проблема решается с помощью **приведения типов**, которое _заставит компилятор поверить_, что *`i`* имеет тип `float`, не меняя фактический тип *`i`*.
|
||||
```glsl
|
||||
//приведение типа целочисленной переменной 'i' к float
|
||||
float r = float( i ) * f;
|
||||
```
|
||||
|
||||
Это в точности как переодевание **_кошки_** в **шкуру _жирафа_**, которое будет работать как и ожидается: в `r` сохранится результат умножения `i` x `f`.
|
||||
|
||||
Любой из упомянутых выше типов можно **привести** к любому другому. При этом приведение `float` к `int` будет работать как `Math.floor()`, удаляя числа справа от запятой. Приведение `float` или `int` к булеву типу вернёт `true` если переменная не равна нулю.
|
||||
|
||||
#### конструктор
|
||||
**Типы** переменных так же являются **конструкторами классов** для самих себя. Фактически, переменную типа `float` можно представлять как _`экземпляр`_ класса _`float`_.
|
||||
|
||||
Следующие объявления равнозначны:
|
||||
|
||||
```glsl
|
||||
int i = 1;
|
||||
int i = int( 1 );
|
||||
int i = int( 1.9995 );
|
||||
int i = int( true );
|
||||
```
|
||||
Для `скалярных` типов это выглядит весьма тривиально, не особо отличаясь от **приведения**, но в этом появится больше смысла когда мы дойдём до раздела о *перегрузках*.
|
||||
|
||||
Итак, мы изучили три `примитивных типа`, без которых невозможно обойтись, но в GLSL есть и другие.
|
||||
|
||||
### Векторы
|
||||
![первый результат в Гугле по запросу 'vector villain' на 20 мая 2016](vector.jpg)
|
||||
|
||||
Как и JavaScript, в GLSL вам понадобятся более продвинутые способы для манипуляции данными, и здесь **`векторы`** будт очень кстати. Я предполагаю, что вам доводилось писать на JS класс `Point`, который содержит значения `x` и `y`, и выглядит как-то так:
|
||||
```glsl
|
||||
// определение:
|
||||
var Point = function( x, y ){
|
||||
this.x = x || 0;
|
||||
this.y = y || 0;
|
||||
}
|
||||
|
||||
// объявление экземпляра:
|
||||
var p = new Point( 100,100 );
|
||||
```
|
||||
|
||||
Как мы только что видели, этот код жутко неправилен на всех уровнях. Во-первых, это ключевое слово **`var`**, затем это ужасающее **`this`** и **нетипизированные** значения `x` и `y`... Нет, такое явно не будет работать в мире шейдеров.
|
||||
|
||||
Вместо этого GLSL предоставляет встроенные структуры для группировки данных:
|
||||
|
||||
* `bvec2`: 2D булев вектор, `bvec3`: 3D булев вектор, `bvec4`: 4D булев вектор
|
||||
* `ivec2`: 2D целочисленный вектор, `ivec3`: 3D целочисленный вектор, `ivec4`: 4D целочисленный вектор
|
||||
* `vec2`: 2D вектор с плавающей точкой, `vec3`: 3Dвектор с плавающей точкой, `vec4`: 4D вектор с плавающей точкой
|
||||
|
||||
Вдумчивый читатель заметит, что каждому примитивному типу соответствует **векторный** тип. Из написанного выше легко вывести, что `bvec2` содержит два булевых значения, а `vec4` будет содержать четыре значения в плавающей точкой.
|
||||
|
||||
Так же векторы вводят такую величину, как размерность. Это не означает, что вы должны использовать 2D-вектор при отрисовке 2D-графики и 3D при рисовании 3D-изображений. Для чего в таком случае используется четырёхмерный вектор? (ну, на самом деле это называется «тессеракт» или «гиперкуб»)
|
||||
|
||||
Нет, **размерность** указывает на количество **компонентов** или **переменных**, хранимых в **векторе**:
|
||||
```glsl
|
||||
// объявляем двумерный булев вектор
|
||||
bvec2 b2 = bvec2 ( true, false );
|
||||
|
||||
// объявляем трёхмерный целочисленный вектор
|
||||
ivec3 i3 = ivec3( 0,0,1 );
|
||||
|
||||
// объявляем четырёхмерный вектор значений с плавающей запятой
|
||||
vec4 v4 = vec4( 0.0, 1.0, 2.0, 1. );
|
||||
```
|
||||
`b2` содержит два различных булевых значения, `i3` содержит 3 различных целых, а `v4` содержит 4 различных значения с плавающей точкой.
|
||||
|
||||
Но как обратиться к этим значениям?
|
||||
В случае скаляров ответ очевиден: при объявлении `float f = 1.2;` переменная `f` содержит значение `1.2`. Для **векторов** всё немного по-другому и выглядит это довольно красиво.
|
||||
|
||||
#### доступ к элементам векторов
|
||||
Есть несколько способов доступа к значениям
|
||||
```glsl
|
||||
// объявим четырёхмерный вектор значений с плавающей точкой
|
||||
vec4 v4 = vec4( 0.0, 1.0, 2.0, 3.0 );
|
||||
```
|
||||
четыре его значения можно извлечь следующим образом
|
||||
```glsl
|
||||
float x = v4.x; // x = 0.0
|
||||
float y = v4.y; // y = 1.0
|
||||
float z = v4.z; // z = 2.0
|
||||
float w = v4.w; // w = 3.0
|
||||
```
|
||||
легко и просто. Ниже приведены равнозначные способы доступа к данным:
|
||||
```glsl
|
||||
float x = v4.x = v4.r = v4.s = v4[0]; // x = 0.0
|
||||
float y = v4.y = v4.g = v4.t = v4[1]; // y = 1.0
|
||||
float z = v4.z = v4.b = v4.p = v4[2]; // z = 2.0
|
||||
float w = v4.w = v4.a = v4.q = v4[3]; // w = 3.0
|
||||
```
|
||||
|
||||
Вдумчивый читатель заметил три факта:
|
||||
* `X`, `Y`, `Z` и `W` как правило используются в программах для представления векторов в пространстве
|
||||
* `R`, `G`, `B` и `A` используются для кодирования цвета и альфа-канала
|
||||
* `[0]`, `[1]`, `[2]` и `[3]` означают, что векторы являются массивами с произвольным доступом
|
||||
|
||||
В зависимости от того, работаете ли вы с двух- или трёхмерными координатами, цветом с альфа-каналом или без такового, или просто какими-то произвольными значениями, вы можете выбрать наиболее подходящий тип и размерность вектора. Обычно координаты и векторы (в геометрическом смысле слова) хранятся как `vec2`, `vec3` или `vec4`, цвета как `vec3` или `vec4`, но в целом никаких ограничений на использование переменных нет. Например, никто не запрещает вам хранить единственное булево значение как `bvec4`, но это приведёт в излишнему расходу памяти.
|
||||
|
||||
**Заметим**, что в шейдерах значения цвета (`R`, `G`, `B`, `A`) нормализованы, то есть лежат в диапазоне от 0 до 1, а не от 0 до 0xFF, поэтому для них лучше использовать вещественный тип `vec4`, а не целочисленный `ivec4`.
|
||||
|
||||
Уже лучше, но мы идём далее!
|
||||
|
||||
#### перемешивание
|
||||
|
||||
Из вектора можно извлечь несколько значений одновременно. Например, если вам нужны только `X` и `Y` из `vec4`, на JavaScript вы бы написали что-то вроде этого:
|
||||
```glsl
|
||||
var needles = [0, 1]; // размещение 'x' и 'y' в структуре данных
|
||||
var a = [ 0,1,2,3 ]; // структура данных 'vec4'
|
||||
var b = a.filter( function( val, i, array ) {
|
||||
return needles.indexOf( array.indexOf( val ) ) != -1;
|
||||
});
|
||||
// b = [ 0, 1 ]
|
||||
|
||||
// или более буквально:
|
||||
var needles = [0, 1];
|
||||
var a = [ 0,1,2,3 ]; // структура 'vec4'
|
||||
var b = [ a[ needles[ 0 ] ], a[ needles[ 1 ] ] ]; // b = [ 0, 1 ]
|
||||
```
|
||||
Выглядит уродливо. В GLSL данные можно извлечь вот так:
|
||||
```glsl
|
||||
// создаём четырёхмерный вектор с плавающей запятой
|
||||
vec4 v4 = vec4( 0.0, 1.0, 2.0, 3.0 );
|
||||
|
||||
// и извлекаем только X и Y
|
||||
vec2 xy = v4.xy; // xy = vec2( 0.0, 1.0 );
|
||||
```
|
||||
Что это было?! Когда вы составляете воедино методы доступа к полям, GLSL изящно возвращает запрошенное подмножество в виде значения наиболее подходящего типа. Это возможно, потому что вектор является структурой данных с произвольным доступом, прямо как массив в javaScript. Поэтому, можно не только обратиться к подмножеству данных вектора, но и указать **порядок**, в котором нужно обращаться. Следующий код обратит порядок компонентов вектора:
|
||||
```glsl
|
||||
// создаём четырёхкомпонентный вектор R,G,B,A
|
||||
vec4 color = vec4( 0.2, 0.8, 0.0, 1.0 );
|
||||
|
||||
// и извлекаем компоненты цвета в порядке A,B,G,R
|
||||
vec4 backwards = v4.abgr; // backwards = vec4( 1.0, 0.0, 0.8, 0.2 );
|
||||
```
|
||||
И конечно же, к одной компоненте можно обратиться многократно:
|
||||
```glsl
|
||||
// создаём четырёхкомпонентный вектор R,G,B,A
|
||||
vec4 color = vec4( 0.2, 0.8, 0.0, 1.0 );
|
||||
|
||||
// и извлекаем vec3 с компонентами GAG на основе каналов G и A исходного цвета
|
||||
vec3 GAG = v4.gag; // GAG = vec4( 0.8, 1.0, 0.8 );
|
||||
```
|
||||
|
||||
Очень удобно составлять части вектора воедино, извлекать только rgb-компоненты из вектора цвета с прозрачностью и т.п.
|
||||
|
||||
#### перегрузим всё!
|
||||
В разделе о типах я упоминал упоминал **конструкторы** и ещё одно великолепное свойство языка GLSL - **перегрузку**. **Перегрузка** оператора или функции означает _изменение поведения этого оператора или функции в зависимости от операндов/аргументов_. В JavsScript нет перегрузки, поэтому вначале она может показаться вам странной, но немного попользовавшись ей, вы зададитесь вопросом, почему же она не реализована в JavaScript (краткий ответ - *типизация*).
|
||||
|
||||
Рассмотрим простейший пример перегрузки:
|
||||
|
||||
```glsl
|
||||
vec2 a = vec2( 1.0, 1.0 );
|
||||
vec2 b = vec2( 1.0, 1.0 );
|
||||
// перегруженное сложение
|
||||
vec2 c = a + b; // c = vec2( 2.0, 2.0 );
|
||||
```
|
||||
ШТОА? Можно складывать сущности, не являющиеся числами?!
|
||||
|
||||
Именно. И конечно же, это применимо ко всем операторам (`+`, `-`, `*` и `/`), и это только начало.
|
||||
Рассмотрим фрагмент кода:
|
||||
```glsl
|
||||
vec2 a = vec2( 0.0, 0.0 );
|
||||
vec2 b = vec2( 1.0, 1.0 );
|
||||
// перегруженный конструктор
|
||||
vec4 c = vec4( a , b ); // c = vec4( 0.0, 0.0, 1.0, 1.0 );
|
||||
```
|
||||
Мы соорудили `vec4` из двух `vec2`, используя `a.x` и `a.y` в качестве компонент `X` и `Y` для нового вектора `c`. Затем мы взяли `b.x` и `b.y` в качестве `Z` и `W` для `c`.
|
||||
|
||||
Так работает перегрузка функции по набору параметров, в данном случае это **конструктор** `vec4`. Это означает, что несколько **версий** одного и того же метода с различными наборами параметров могут мирно сосуществовать в одной программе. Например, все следующие объявления корректны:
|
||||
```glsl
|
||||
vec4 a = vec4(1.0, 1.0, 1.0, 1.0);
|
||||
vec4 a = vec4(1.0);// x, y, z, w all equal 1.0
|
||||
vec4 a = vec4( v2, float, v4 );// vec4( v2.x, v2.y, float, v4.x );
|
||||
vec4 a = vec4( v3, float );// vec4( v3.x, v3.y, v3.z, float );
|
||||
etc.
|
||||
```
|
||||
От вас требуется только подать достаточное количество параметров для заполнения **вектора**.
|
||||
|
||||
Наконец, вы можете перегружать встроенные функции для тех типов аргументов, для которых они не были изначально задуманы (но лучше не делать этого слишком часто).
|
||||
|
||||
#### нужно больше типов
|
||||
Векторы прикольные. Они - мышцы вашего шейдера. Но есть и другие типы, например матрицы и текстурные семплеры, о которых будет рассказано ниже.
|
||||
|
||||
В GLSL есть массивы. Конечно же, они типизированные, и у них есть несколько отличий от массивов в JS:
|
||||
* у них фиксированный размер
|
||||
* вы не можете использовать push(), pop(), splice() и т.п., свойство ```length``` тоже отсутствует
|
||||
* их нельзя инициализировать значениями при объявлении
|
||||
* значения нужно задавать по одному
|
||||
|
||||
вот это работать не будет:
|
||||
```glsl
|
||||
int values[3] = [0,0,0];
|
||||
```
|
||||
а вот это заработает:
|
||||
```glsl
|
||||
int values[3];
|
||||
values[0] = 0;
|
||||
values[1] = 0;
|
||||
values[2] = 0;
|
||||
```
|
||||
Этого хватает, если вы знаете все ваши данные или работаете с небольшими массивами данных. Если вам нужно больше выразительности, вы можете использовать структуры (```struct```). Они похожи на _объекты_ без методов. Они позволяют хранить несколько переменных в одном объекте:
|
||||
```glsl
|
||||
struct ColorStruct {
|
||||
vec3 color0;
|
||||
vec3 color1;
|
||||
vec3 color2;
|
||||
}
|
||||
```
|
||||
например, вы можете задавать и извлекать значения _цвета_ следующим образом:
|
||||
```glsl
|
||||
// инициализируем структуру
|
||||
ColorStruct sandy = ColorStruct( vec3(0.92,0.83,0.60),
|
||||
vec3(1.,0.94,0.69),
|
||||
vec3(0.95,0.86,0.69) );
|
||||
|
||||
// получем доступ к значениям
|
||||
sandy.color0 // vec3(0.92,0.83,0.60)
|
||||
```
|
||||
Это синтаксический сахар, но он может помочь вам писать более чистый, или как минимум более привычный код.
|
||||
|
||||
#### выражения и условия
|
||||
|
||||
Структуры данных очень полезны, но рано или поздно нам _возможно_ понадобится проходить по массиву или выполнять проверку условия. К счастью, синтаксис для этого очень близок к JavaScript.
|
||||
Условие выглядит так:
|
||||
```glsl
|
||||
if( condition ){
|
||||
//true
|
||||
}else{
|
||||
//false
|
||||
}
|
||||
```
|
||||
Цикл `for` выглядит так:
|
||||
```glsl
|
||||
const int count = 10;
|
||||
for( int i = 0; i <= count; i++){
|
||||
//do something
|
||||
}
|
||||
```
|
||||
пример переменной цикла с плавающей точкой:
|
||||
```glsl
|
||||
const float count = 10.;
|
||||
for( float i = 0.0; i <= count; i+= 1.0 ){
|
||||
//do something
|
||||
}
|
||||
```
|
||||
Заметим, что ```count``` должна быть объявлена константой. Это означает, что перед её объявлением должен быть **квалификатор** ```const```, который будет рассмотрен чуть ниже.
|
||||
|
||||
Так же нам доступны ключевые слова ```break``` и ```continue```:
|
||||
```glsl
|
||||
const float count = 10.;
|
||||
for( float i = 0.0; i <= count; i+= 1.0 ){
|
||||
if( i < 5. )continue;
|
||||
if( i >= 8. )break;
|
||||
}
|
||||
```
|
||||
Имейте ввиду, что на некоторых типах оборудования ```break``` не работает ожидаемым образом и не прерывает цикл заранее.
|
||||
|
||||
В целом, старайтесь делать количество итераций как можно меньше, и избегайте циклов и ветвлений как можно чаще.
|
||||
|
||||
#### квалификаторы
|
||||
|
||||
Помимо типов переменных в GLSL есть **квалификаторы**. Вкратце, квалификаторы сообщают компилятору какая переменная для чего предназначена. Например, некоторые данные для GPU могут приходить только со стороны CPU. Такие данные называются **атрибутами** и **юниформами**. **Атрибуты** встречаются только в вершинных шейдерах, а **юниформы** - и в вершинных, и во фрагментных. Так же есть квалификатор ```varying```, используемый для передачи переменных т вершинного шейдера ко фрагментному.
|
||||
|
||||
Я не буду сильно углубляться в подробности, ибо мы в основном рассматриваем **фрагментные шейдеры*, но далее в книге вам возможно встретится что-то вроде
|
||||
```glsl
|
||||
uniform vec2 u_resolution;
|
||||
```
|
||||
Что здесь происходит? Мы задали квалификатор ```uniform``` перед типом переменной, указав, что разрешение изображения передаётся в шейдер из CPU. Ширина изображения находится в `x`-компоненте 2D-вектора, а высота - в `y`-компоненте.
|
||||
|
||||
Когда компилятор видит переменную, объявленную с этим квалификатором, он сделает чтобы вы не могли *записать* это значение в рантайме.
|
||||
|
||||
То же самое применимо к переменной ```count```, которая была пороговым значением в цикле ```for```:
|
||||
```glsl
|
||||
const float count = 10.;
|
||||
for( ... )
|
||||
```
|
||||
Когда мы используем квалификатор ```const```, компилятор не даёт нам перезаписать значение, которое в противном случае не было бы константой.
|
||||
|
||||
Ещё три квалификатора используются в сигнатурах функций: ```in```, ```out``` и ```inout```. В JavaScript переданные в функцию аргументы предназначены только для чтения. Их изменение внутри функции не приводит к изменению значений за её пределами.
|
||||
```glsl
|
||||
function banana( a ){
|
||||
a += 1;
|
||||
}
|
||||
var value = 0;
|
||||
banana( value );
|
||||
console.log( value );// > 0 ; значение за пределами функции не изменилось
|
||||
```
|
||||
|
||||
Используя квалификаторы аргументов, можно изменять их поведение:
|
||||
* ```in``` предназначен только для чтения (по умолчанию)
|
||||
* ```out``` только для записи: значение такого аргумента нельзя прочитать, но можно записать
|
||||
* ```inout``` чтение и запись
|
||||
|
||||
Перепишем упомянутый выше метод на GLSL:
|
||||
```glsl
|
||||
void banana( inout float a ){
|
||||
a += 1.;
|
||||
}
|
||||
float A = 0.;
|
||||
banana( A ); // теперь A = 1.;
|
||||
```
|
||||
Это поведение сильно отличается от JS и даёт множество возможностей. При этом, не обязательно всегда указывать квалификаторы аргументов. По умолчанию аргументы предназначены только для чтения.
|
||||
|
||||
#### пространство и координаты
|
||||
|
||||
Напоследок заметим, что в DOM и Canvas 2D ось Y направлена вниз. Это имеет смысл в контексте DOM, ибо соответствует тому, как свёрстана web-страница: навигационная панель наверху, а контент прокручивается вниз. В webgl-элементе ось Y перевёрнута и указывает вверх.
|
||||
|
||||
Это означает, что начало координат (точка (0,0)) расположено в левом нижнем, а не в левом верхнем углу контекста. Текстурные координаты так же следуют этому правилу, которое на первый взгляд кажется контринтуитивным.
|
||||
|
||||
## На этом всё!
|
||||
Конечно, мы могли мы углубиться во всяческие детали, но, как было сказано вначале, эта статья писалась как простое введение для новичков. Здесь уже написано достаточно, чтобы переваривать это некоторое время, но с терпением и практикой этот язык будет становиться всё более естественным для вас.
|
||||
|
||||
Надеюсь, этот текст был полезен, а потому самое время приступить к основному содержимому книги!
|
@ -48,7 +48,7 @@ SO! ready?
|
||||
|
||||
off we go!
|
||||
|
||||
### strong types
|
||||
### Strong types
|
||||
![first search result for 'strong type' on Google Image, on the 2016/05/20](strong_type.jpg)
|
||||
|
||||
When you come from JS or any untyped language, **typing** your variables is an alien concept, making **typing** the hardest step to take towards GLSL.
|
||||
@ -202,7 +202,7 @@ And the clever bunny you are already noticed three things:
|
||||
|
||||
So depending on whether you're manipulating 2D or 3D coordinates, a color with or without an alpha value or simply some random variables, you can pick the most suited **vector** type and size.
|
||||
Typically 2D coordinates and vectors (in the geometric sense) are stored as a `vec2`, `vec3` or `vec4`, colors as `vec3` or `vec4` if you need opacity but there is no restriction on how to use the vectors.
|
||||
For instance, if you want to store only one boolean value in a `bvce4`, it's possible, it's just a waste of memory.
|
||||
For instance, if you want to store only one boolean value in a `bvec4`, it's possible, it's just a waste of memory.
|
||||
|
||||
**note**: in a shader, color values (`R`, `G`, `B` & `A`) are normalised, they range from 0 to 1 and not from 0 to 0xFF, so you'd rather use a Float `vec4` than an Integer `ivec4` to store them.
|
||||
|
||||
@ -417,7 +417,7 @@ banana( value );
|
||||
console.log( value );// > 0 ; the changes are not taken into account outside the function
|
||||
```
|
||||
|
||||
With arguments qualifiers, you can specify the behaviour of the the arguments:
|
||||
With arguments qualifiers, you can specify the behaviour of the arguments:
|
||||
* ```in``` will be read-only ( default )
|
||||
* ```out``` write-only: you can't read the value of this argument but you can set it
|
||||
* ```inout``` read-write: you can both get and set the value of this variable
|
||||
|
15
appendix/README-ru.md
Normal file
15
appendix/README-ru.md
Normal file
@ -0,0 +1,15 @@
|
||||
# Приложение
|
||||
|
||||
1. [Как читать книгу оффлайн?](00/?lan=ru)
|
||||
|
||||
2. [Как запустить примеры на Raspberry Pi?](01/?lan=ru)
|
||||
|
||||
3. [Как напечатать книгу?](02/?lan=ru)
|
||||
|
||||
4. [Как принять участие в создании книги?](03/?lan=ru)
|
||||
|
||||
5. [Введение для JavaScript-программистов](04/?lan=ru) ([Николя Баррадо](http://www.barradeau.com/))
|
||||
|
||||
6. [Введение в векторную алгебру](05/)
|
||||
|
||||
7. [Введение в интерполяцию](06)
|
@ -1,6 +1,6 @@
|
||||
|
||||
<div class="header">
|
||||
<p class="subtitle"><a href="https://thebookofshaders.com/">The Book of Shaders</a> by <a href="http://patriciogonzalezvivo.com">Patricio Gonzalez Vivo</a> & <a href="http://jenlowe.net">Jen Lowe</a> </p>
|
||||
<p> <a href="?lan=jp">日本語</a> - <a href="?lan=ch">中文版</a> - <a href="?lan=kr">한국어</a> - <a href="?lan=es">Español</a> - <a href="?lan=fr">Français</a> - <a href="?lan=it">Italiano</a> - <a href="?lan=de">Deutsch</a> - <a href=".">English</a></p>
|
||||
<p> <a href="?lan=jp">日本語</a> - <a href="?lan=ch">中文版</a> - <a href="?lan=kr">한국어</a> - <a href="?lan=ru">Русский</a> - <a href="?lan=es">Español</a> - <a href="?lan=fr">Français</a> - <a href="?lan=it">Italiano</a> - <a href="?lan=de">Deutsch</a> - <a href=".">English</a></p>
|
||||
</div>
|
||||
<hr>
|
||||
|
@ -1,3 +1,3 @@
|
||||
<div class="toc-header">
|
||||
<p> <a href="?lan=jp">日本語</a> - <a href="?lan=ch">中文版</a> - <a href="?lan=kr">한국어</a> - <a href="?lan=es">Español</a> - <a href="?lan=fr">Français</a> - <a href="?lan=it">Italiano</a> - <a href="?lan=de">Deutsch</a> - <a href=".">English</a></p>
|
||||
<p> <a href="?lan=jp">日本語</a> - <a href="?lan=ch">中文版</a> - <a href="?lan=kr">한국어</a> - <a href="?lan=ru">Русский</a> - <a href="?lan=es">Español</a> - <a href="?lan=fr">Français</a> - <a href="?lan=it">Italiano</a> - <a href="?lan=de">Deutsch</a> - <a href=".">English</a></p>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user