Gamedev
Денис Кузнецов
1480

Текстурирование, или что нужно знать, чтобы стать Художником по поверхностям. Часть 2. Маски и текстуры

Продолжаем серию уроков о том, как стать художником по поверхностям.

В закладки
Слушать

Hello there! Моё имя Денис Кузнецов. Я - руководитель студии разработки игр QberCat Studio. Мы работаем над игрой "Cat Movies!" в движке Unreal Engine 4.

Серия уроков была написана год назад в пяти частях и выложена на «Хабр». Тогда я не дописал последнюю часть, оставив пятую часть без практики и подробных данных о том, как можно минимизировать потребление текстур и вывести текстуры на более высокий уровень.

Начиная выкладывать статьи на DTF, я постараюсь этот пробел закрыть, а так же ещё раз пробегусь по тексту и предоставлю вам более актуальную версию с отредактированным текстом (2-е издание, так сказать).

  • Часть 1. Пиксель. Здесь.
  • Часть 2. Маски и текстуры. Вы ее читаете.
  • Часть 3. PBR и материалы. Здесь.
  • Часть 4. Модели, нормали и развертка. Здесь.
  • Часть 5. Система материалов. Статья готовится.
  • Часть 6. Без названия. Статья пишется.

Важно. Я не буду рассматривать в этой серии статей о том, как должна быть настроена гамма, какой должен быть оттенок под камушком у того-то листочка. Это не курс о том, как нарисовать красиво. Это курс о том, как это все работает глубоко под капотом с точки зрения ПО и математики. Прочитав эти статьи вы не станете супер-художником 120-го уровня, но теперь вы сможете работать в материалах, программах без каких-либо сложностей, будете понимать, как собрать очень сложный материл в UE4 за несколько минут.

Итак, котятки, погнали! =)

Маски

В прошлой части мы рассмотрели принципы работы каналов и нашу первую маску, которую я создал — маску прозрачности.

Маска прозрачности в примере с котиком была создана с помощью отдельного канала под названием Альфа-канал. Этот канал так же имел размерность 8 бит на пиксель.

Каждый пиксель маски может иметь значение от 0 до 255 (или до 1), где 0 — это полностью прозрачный пиксель, а 255 (или 1) — полностью НЕ прозрачный пиксель.

А вот если посмотреть на Альфа-канал, как на обычный канал, то станет понятно, что он ничем не отличается от любого другого канала RGB. Альфа-канал — это все те же значения, у которых есть такая же интенсивность от 0 до 255 (до 1). Это все тот же градиент серого. И если бы мы взяли любой другой канал, скопировали бы его и вставили в Альфа-канал, то получили бы интересный эффект:

Если обратите внимание на альфа-канал в списках справа, то там стоит в градациях серого все тот же котик =) Кот

Маска накладывается на все изображение, но там, где интенсивность пикселей максимально яркая (близкая к 1 или 255), там цвет маски наименьший, а где интенсивность максимально низкая (ближе к 0), то там маска наоборот очень ярко выделяется красным цветом:

Кот
И теперь, если такое изображение экспортировать и открыть в любой программе для просмотра изображений, то получится такая картинка. Кот

Большая часть пикселей в этой картинке имеет полу-прозрачность согласно интенсивности, указанной в альфа-канале. Но если маска прозрачности такая же, как и другие каналы, тогда следует вопрос — обязательно ли ее использовать в Альфа-канале?

На самом деле, не обязательно. Альфа-канал необходим, чтобы изображение могло сохранять данные о цвете и иметь при этом данные о прозрачности. То есть, получается, что в изображении 1 пиксель имеет обязательных 3 канала, а 4-ый канал является дополнительным и не обязательным.

Итого, в нашем изображении не 32 бита цвета, а всего 24 бита цвета (8 бит * 3 канала) + 8 бит прозрачности. Причем, мы можем не включать канал прозрачности в изображение, и тогда размер файла будет на 1/4 меньше.

Что такое Маска?

Маска в текстурировании (да, наверное, и не только) Это изображение, которое состоит из 1-го канала и необходимо для регулирования других каналов и визуализации параметров фактур (Это уже PBR, и мы с ним обязательно столкнемся чуть позже).То есть, маска используется для решения нескольких задач:

  • Определение степени прозрачности.
  • Определение интенсивности чего-либо (об этом позже, но обязательно). Например, это может карта высок, где самые белые пиксели - это горы, а черные - это впадины. Или второй пример - глубина кадра - где светлый - это самый отдаленный объект.

На самом деле, делить на две задачи работу масок не обязательно — и там и там используется степень интенсивности, просто в первом случае для прозрачности, а во втором случае для целей регулирования внешнего вида текстуры, объектов и так далее.

Маски в Substance Painter.

Сейчас мы рассмотрим наглядно принцип работы масок в Substance Painter и попробуем напялить стильную шляпу Коту.

Для этого мы создадим новый проект, укажем и добавим наши фотографии шляпки и кота в программу.

Заметка: Substance Painter не позволит создать проект без 3D-модели, поэтому я создал квадратный plane в Autodesk Maya. Создание моделей находится за рамками наших туторов, поэтому эту информацию вам нужно найти самостоятельно. Так же мы не будем рассматривать детально интерфейс Substance Painter, чтобы не затягивать туторы по максимуму, однако вы работаете с Substance Painter впервые, то, если ничего лишнего не нажимать, все окна должны отображаться корректно, и найти нужные параметры будет не сложно.

Далее нам необходимо создать 2 слоя заливки:

Прежде, чем мы продолжим дальше, мы должны уяснить то, что все в этой программе (да и в других) построено на каналах, масках и слоях. Причем слои состоят из каналов, которые мы можем регулировать, настраивать и даже отключать.

А как мы помним, цвет состоит из 3х каналов. И не важно, как их называют - с этого момента мы будем их называть просто каналами, что позволит нам в нашей голове отпустить шаблон RGB и начать мыслить каналами.

Слой заливки в Substance Painter — это набор каналов, который будет отображаться в этом слое. Некоторые каналы объединены сразу в группы. Например, группа BaseColor, в которой используется сразу 3 канала. Или карта нормалей, которой так же для корректно отрисовки требуется 3 канала.

В параметрах слоя мы можем настроить визуально представление металла, пластика и любых других фактур. Однако об этом мы поговорим позже, а сейчас у нас должно сформироваться четкое представление, что слой заливки — это изображение (Color), состоящее из 3 каналов и куча дополнительных каналов по 8 бит, которые отвечают за то, как должен выглядеть слой.

Обычно по умолчанию слой заливки состоит из:

  • 3 основных канала, которые выделяются для построения цвета. Эти три канала объединены в один параметр «BaseColor».
  • 3 дополнительных канала, каждый из которых отвечает за какие-то визуальные эффекты (например, канал Metal отвечает за то, насколько пиксель будет считаться металлом с точки зрения PBR-текстурирования).
  • 3 канала для работы с картой нормалей (Normal map).

Нас сейчас не интересуют дополнительные каналы, так как мы еще не познакомились с PBR-текстурированием, и мы обязательно вернемся к ним позже, а сейчас наша задача научиться с помощью масок контролировать то, какие слои в нужных нам пикселях будут отображаться, какие слои будут полностью скрыты, а какие слои будут полупрозрачными.

Мы сейчас разберем это на котике и шляпе, а потом перейдем к более простым монотонным цветам и более глубоко разберем сложно-комбинированное применение масок для контролирования слоев.

Наша задача сейчас отключить все те каналы в этом слое, которые нас не интересуют в данный момент. Отключить нужно каналы Height, Rough, Metal, Nrm (Normal) и оставить только цвет (color):

Синим подсвечены активные наборы каналов.

Теперь нам необходимо распределить наши картинки в слои и настроить их корректное отображение. Для этого выбираем слой, находим картинку, импортированию в папку Project (в панели Shelf) и перетаскиваем картинку в параметр Base Color.

Повторяем это действие со вторым слоем и шляпой.

Важно. Слой со шляпой должен находиться выше по иерархии, чем котик, так как мы хотим, чтобы шляпа была НА коте, а не ПОД ним.

Помните, что контролировать видимость слоев можно с помощью кнопки в виде глаза слева от слоя.

Так как проект рассчитан на текстуры, а текстуры полагается делать квадратными ( желательно, чтобы размер был всегда кратен 2м. То есть, размер 64*64 пикселя — это 2*2*2*2*2 ), то изображение котика будет слегка растянутым вширь, так как фотография его является прямоугольной по вертикали, и Substance Painter ее растянул до квадратной формы.

В связи с этим нам необходимо изменить масштаб картинки и растянуть фотографию на слое вертикально. Для этого мы снимаем блокировку сохранения соотношений и вытягиваем картинку, установив вертикальный scale на 0.7.

Теперь нужно подогнать таким же образом размер шляпы, однако теперь отключать сохранение отношений уже не нужно. Так как шляпа у меня очень большая получилась, то при изменении масштаба начинает работать такой эффект, как тайлинг — текстура начинает повторяться по горизонтали и по вертикали, таким образом система не позволяет оставлять пустые места, если изображение меньше, чем размер конечного изображения (в нашем случае, меньше, чем 1024х1024 пикселей).

Небольшое отступление: Зачем нужен тайлинг?

Тайлинг необходим, когда нам требуется заполнить большие пространства небольшой по размерам текстурой таким образом, чтобы она смотрелась очень детально. Одно у этого есть свои недостатки - обычно у тайлинговых текстур бесшовные края, но повторяющийся рисунок (паттерн), который очень сильно выбивается при частом повторении и неприятен для глаз.

Ниже представленное изображение имеет специально выстроенную структуру картинки таким образом, что если мы присоединим копию этой картинки сверху, снизу или с любой стороны, то она будет все еще смотреться, как цельная и более широкая (конкретно в нижнем примере уже картинка состоит из 16 повторяющихся картинок):

Еще раз. Эти повторения называются паттернами (или проще говоря — бесшовными текстурами) и они созданы для того, чтобы можно было эти паттерны повторять раз за разом хоть миллион раз. Это повторение в дальнейшем мы будем использовать для улучшения качества картинки, но использовать это мы будем в основном при создании шейдеров, так что вернемся мы к тайлингу и паттернам не скоро.

И так, продолжаем. Мы сделали скейл шляпы до нужного нам размера. Теперь нам необходимо поместить шляпу плюс-минус над головой кота — для этого начинаем крутить параметры Offset (смещение), сдвигая нужную нам шляпу на котика.У нас получилась примерно следующая картинка:

Ну а теперь настало время использовать маски для того, чтобы отредактировать видимость слоя со шляпами. Маски в Substance Painter можно создать 2 способами:

  • Создать маску на самом слое, ограничивая видимость слоя.
  • Создать папку, в которую можно поместить слой. И наложить маску на папку.

Второй вариант предпочтительней и позволяет создавать больше комбинаций масок, но сейчас нас интересует принцип работы самой маски, а не сложность их комбинаций, поэтому мы создадим маску на слое и сразу сделаем ее черной.

Для этого мы кликаем по слою ПКМ и выбираем «Add Black Mask».А что происходит с пикселем, когда канал маски прозрачности пикселя на нем равен 0 (полностью черный)? Правильно, пиксель становится полностью прозрачным и не отображается совсем:

Если вы все сделали верно, то спрятав шляпу, Кот сказал вам "Мяу".

Как вы обратили внимание — теперь слой состоит из двух изображений: одно изображение — это шляпы, а второе изображение — это полностью черная маска. Маска в Substance Painter (как и везде, в принципе) создана из одного канала размером в 8 бит.

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

Так что давайте сделаем видимым нужную нам шляпу, а для этого нам необходимо указать на маске, какая зона слоя заливки шляп будет отображаться. Для этого выделяем слой с маской, и выделяем саму маску (это важно, так как редактировать слой заливки нельзя, можно редактировать только маску). При выделении маски нам открываются настройки кисти, которой мы будем рисовать по маске, и в самом низу настроек есть настройка интенсивности (Grayscale) от 0 до 1 (от 0 до 255 соответственно).Теперь, рисуя по маске над головой котика мы проявим шляпу:

Какой я кривой =) Кот

Ну, и убираем лишнее с помощью кисти с 0 интенсивностью:

Обратите внимание, что это можно было сделать и в Photoshope - на самом деле принципы работы со слоями везде абсолютно одинаковы.

Сама маска выглядит так:

Как можно понять из рисунка, маска - это всего лишь еще один канал в оттенках серого.

Таким образом маска позволила нам определить какой участок в слое со шляпами отображать.Хорошо, это применимо к редактированию котиков. А как это применять к текстурам в играх?

Чуть более ложные комбинации масок.

Теперь давайте рассмотрим чистую плитку для пола. Точнее, ее текстуру:

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

Мы уже умеем создавать слои заливки с текстурами и сейчас наша задача:

  • Создать новый проект с теми же настройками, что были в предыдущем (мы будем считать их стандартными).
  • Создать 2 слоя — один для плитки, другой для грязи. Так как грязь должна быть сверху плитки, то слой с грязью мы располагаем выше.

Но теперь мы не накладываем маску на слой с грязью, а создаем пустую папку для слоев и помещаем слой с грязью в эту папку (помним, что папка должна все еще быть выше по иерархии, чем плитка).А маску создаем уже на самой папке.

Какая иерархия расположения слоев у вас должна получиться.

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

Итак, у меня получилась примерно следующая картина:

Не шедевр, но зоны, в которых должна отображаться грязь, мы определили.

Теперь создадим еще одну папку внутри готовой папки со слоем грязи и перенесем слой с грязью в эту папку. Итого, у на получилось, что слой с грязью находится в папке, которая находится в другой папке:

Папка в папке папки под папкой в папке через папку в выпапке

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

Чтобы загрузить изображение и потом использовать его в качестве маски, необходимо перенести в во внутренний браузер файлов SP в shelf (в папку) из браузера и выбрать следующие параметры:

Возможно, можно указать другой формат, а не «текстура», и изображение все равно будет подключаться в качестве маски, но я этого не знаю (подсказывайте в комментариях).

Далее. Установить в качестве маски эту текстуру легко. Кликаем по папке так же ПКМ и выбираем Add Bitmap Mask. В появившемся окне находим нужную нам текстуру и выбираем ее в качестве маски. Иерархия с папками стала выглядеть так:

Заметьте, как у второй папки вместо обычной маски отображается моя текстура (или ваша, или Котика)

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

А в целом, у нас получилась примерно следующая картина:

В данном примере у нас произошло смешение двух масок. Я расставлял маски принципу верхнего уровня с минимальным порогом. Что это значит?

Верхняя маска на самой верхней папке в иерархии определяла зоны видимости. То есть, она четко говорила, здесь можно показывать грязь (1), а вот здесь - нельзя (0).

Вторая маска под ней определяет теперь то, где именно в разрешенных зонах можно, а где нельзя отображать грязь.

Это не всегда легко понять - что такое зонирование маской и что такое - покрасить по маске в разрешенной зоне. Чтобы оценить это - попробуйте на верхней маске нарисовать круг, а на маске ниже попробуйте выйти за пределы этой зоны. И вообще, попробуйте создать 3 маски, 4 и даже 7. И попробуйте удерживать прогнозируемый конечный результат, зайдействовав все 7 масок. А 20?

Те пиксели, в которых обе маски имели значения больше 0, начинали отображать грязь в зависимости от нижнего предела интенсивности (если у первой маски на пикселе интенсивность 0.5, а на второй 0.2, то пиксель будет прозрачным на уровне 0.2). Еще проще говоря, каждый пиксель учитывает все маски, которые влияют на его отображение, но отображается по самому минимальному. И если хоть одна маска равна 0, то пиксель отображаться не будет.

Итого:

Слои — это заливка на всю площадь проекта, которая имеет кучу комбинаций каналов для контролирования состояния изображения. Каналы и их комбинации так же можно добавлять в слои.

Маска — это 1 канал пикселя изображения (текстуры), который необходим для обработки видимости чего-либо.

Количество каналов для 1-го пикселя может быть очень большое. При стандартных настройках Substance Painter учитывает уже в 1-ом слое 9 каналов — 3 на цвет, и 3 для различных эффектов, 3 на карты нормалей. Само изображение на картинке в вашем браузере содержит всего 3 канала + 1 дополнительный, если есть, прозрачность.

Пиксель в конечном результате просчетов - это совокупность слоев, масок и каналов, которые его формируют.

А теперь немного страшного и сложного - слои в программе накладываются друг на друга, и программа уже делает расчеты на 1 пиксель - куда большие, чем для 9 каналов. То есть, в случае смешения 2 слоев в 1-ом пикселе, ПО необходимо просчитать 9 каналов одного слоя, потом учесть прозрачность по маске, если она есть, и просчитать общую интенсивность пикселя с учетом маски. После чего необходимо просчитать второй слой, и его 9 каналов. Потом наложить сверху полупрозрачный слой. Итого 18 каналов слоев и 1 канал смешения. Конечно, когда вы будете выгружать это в изображения (текстуры), то никаких 19 каналов не будет — Substance Painter создаст 3 изображения (текстуры):

  • Изображение (текстура) с цветом (3 канала на пиксель).
  • Изображение (текстура) с каналами для параметров PBR.
  • Изображение (текстура) для определения нормалей.

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

Контролировать в этих изображениях маски прозрачности грязи уже не получится — изображения (текстуры) в данном случае получились, как конечный результат (часто говорят, что они запеклись из того хаоса из масок, который вы создали в программе), который мы можем расположить на модель, покрасив ее.

Текстура

Я намеренно писал в итогах о масках «Изображение (текстура)», чтобы у вас начало откладываться понимание того, что текстура по факту — это не изображение в привычном для нас понимании.

Текстура — это набор параметров для того, чтобы игровые движки или программы для работы с текстурами, понимали, как нужно обработать пиксель.

Текстура может содержать в себе 1 канал. Например, для прозрачности. Или может содержать в себе сразу 3 канала для различных целей (например, для параметров работы металлика, шороховатости и затенения).

Разновидностей применения текстур очень много, но все они сводятся к одному — к работе с 1-м или несколькими каналами.

Например, текстура Cavity (трещинки) обычно описывает грани (ребра) объекта на развертке, выделяя пиксели максимальном белым по всей длине ребер. Но по факту — это просто текстура, канал которой направлен на отображение интенсивности пикселей в нужных точках. Такую текстуру можно использовать, например, для масок грязи — то есть, по ней мы можем отображать грязь на объекте.

Применение масок за пределами программ для текстурирования.

Маски необходимы не только для программ типа Substance Painter. В игровых движках текстурирование очень походит на работу Substance Painter — вы можете просто воткнуть готовые текстуры из Painter, или можете начать настраивать шейдеры (материалы) по собственному желанию, комбинируя текстуры через маски различными способами. И если Painter дает готовый результат, то шейдер, основанный на масках, делает расчеты на лету (грубо говоря онлайн) и позволяет в режиме реального времени их изменять (так создается большая часть эффектов).

Немного PBR-текстурировании

Немного заглянем вперед и рассмотрим чуть-чуть PBR.

Исходя из того, как работает текстура и зачем она нужна, можно очень быстро объяснить, что такое PBR.

PBR — это основанный на физике рендер текстур (именно основанный, а не физически корректный). То есть, это набор параметров, управляя которыми мы можем заставить текстуру выглядеть кинематографично.

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

  • Metallic. Отвечает за представление пикселя в качестве металла. То, насколько легко свет отражается от пикселя.
  • Roughness. Отвечает за степень шороховатости пикселя. То, насколько сильно свет рассеивается, соприкасаясь с пикселем.
  • Ambient Occlusion (устаревшее). Отвечает за затенение пикселей. Тот параметр, который становится устаревшим и, в скором времени, им не будут пользоваться вообще, так как давно просчитывается в реалтайме.
  • Normal Map. Отвечает за то, как свет может искажаться на пикселе, создавая мнимую ненастоящую глубину или изгиб.
  • Albedo (Color) — цвет, которым должен отображаться пиксель.

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

Например, если программе воткнуть в канал металлика пиксель с единственным каналом, интенсивность которого будет равна 1 (255), то этот пиксель будет максимально отражать все вокруг, как чистейший металл.

А настроив канал Roughness по максимуму (указав все пиксели, как 1 или 255), мы видим, что теперь пиксель перестал отражать все вокруг и стал рассеивать свет настолько сильно, что стал серым:

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

Максимально подробно PBR мы разберем в следующей части урока. А так же рассмотрим дополнительные приемы смешивания масок для улучшения картинки.

А пока вы можете продолжить наблюдать за нашей игрой в нашей группе VK и в Твиттере.

Главное, помните, если вам говорят, что для работы чего-то нужна какая-то непонятная карта высот - теперь вы знаете, что это просто текстура с одним каналом, интенсивность пикселей в которой определяет высоту.

Ну и ссылка на след.часть здесь.

Ссылка на предыдущую часть здесь.

CEO Qbercat Studio. Подписывайтесь =)
{ "author_name": "Денис Кузнецов", "author_type": "self", "tags": ["\u0442\u0435\u043a\u0441\u0442\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435","\u043e\u043f\u044b\u0442","tutorial","textures","substancepainter","gamedev"], "comments": 13, "likes": 69, "favorites": 257, "is_advertisement": false, "subsite_label": "gamedev", "id": 210209, "is_wide": false, "is_ugc": true, "date": "Mon, 14 Sep 2020 19:09:34 +0300", "is_special": false }
0
13 комментариев
Популярные
По порядку
Написать комментарий...
2

Первый раз слышу такую профессию, как художник по поверхностям.

Ответить
1

Ещё дойдем дом нее)
Кстати, не мое определение. Я о нем узнал и другой статьи

Ответить
0

Это курс о том, как это все работает глубоко под капотом с точки зрения ПО и математики

Гамма или если хотите гамма-коррекция имеет непосредственное отношение  к тому, как это всё работает «глубоко под капотом». Прежде чем писать подобные уроки и учить кого-то, сами хотя бы в теме разберитесь...

Ответить
1

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

Ответить
0

Лично мне, ничего не нужно повторять. Это вы откройте любую книгу по CG и посмотрите что такое гамма. Никакого отношения к сделать красиво она вообще не имеет... И это отнюдь не условность работы с цветом.

Ответить
0

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

Ответить
0

Мне кажется дтэфия к такому не готова, слишком длинно и пространно. А художник по поверхностям и вправду странно звучит). В любом случае красава.

Ответить
0

Слишком длинно и пространно? В чем выражается пространность? Что это такое?)

Ответить
0

Одно из определений: простираясь на большое пространство, расстояние. В контексте пространных речей, это означает говорить о теме затрагивая большое количество различных аспектов, не имея нужды в этом, теряя фокус главной мысли. Например то как автор статьи говорит о масках. Но это не всегда плохо, Бродский любил пространные речи).

Ответить
0

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

Ответить
0

Монументально! Спасибо за интересное чтиво! 💪👍

Ответить
0

Не за что))

Ответить
{ "jsPath": "/static/build/dtf.ru/specials/DeliveryCheats/js/all.min.js?v=05.02.2020", "cssPath": "/static/build/dtf.ru/specials/DeliveryCheats/styles/all.min.css?v=05.02.2020", "fontsPath": "https://fonts.googleapis.com/css?family=Roboto+Mono:400,700,700i&subset=cyrillic" }
null