Динамическое освещение и тени. Масштаб карты освещения

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

Shine позволяет придать динамику и получить в результате красивые эффекты, связанные с тенями. Написана она на JavaScript, что может также понравиться и не любителям библиотек типа jQuery. Работает данный скрипт только в тех браузерах, которые поддерживают CSS свойства: text-shadow и box-shadow. Если браузеру потребуются префиксы к этим свойствам, то скрипт добавит их сам.

Теперь давайте подключим shine:

Добавим CSS и HTML (применять тень мы будем к тексту):

Html{ background-color: #eef3f8; } #headline{ margin: 60px auto auto; color: #fff; text-align: center; font-size: 8em; font-family: sans-serif; font-weight: 800; letter-spacing: -0.02em; line-height: 1.2em; } сайт

Осталось только вызвать скрипт:

Var shine = new Shine(document.getElementById("headline"));

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

Window.addEventListener("mousemove", function(event){ shine.light.position.x = event.clientX; shine.light.position.y = event.clientY; shine.draw(); }, false);

Результат выполнения вышеуказанного скрипта можно увидеть в материалах «демо». Думаю, получилось весьма интересно. Но это не единственное, что может shine. У неё есть хороший набор настроек, который поможет вам достичь нужного результата.

  • numSteps - количество нарисованных теней (по умолчанию 8);
  • opacity - прозрачность тени (по умолчанию 0.1);
  • opacityPow - степень прозрачности, применяющейся для каждой тени (по умолчанию 1.2);
  • offset – смещение теней (по умолчанию 0.15);
  • offsetPow – степень смещения, применяющегося для каждой из теней (по умолчанию 1.8);
  • blur - размытие тени (по умолчанию 40);
  • blurPow - степень размытия, применяющейся к каждой тени (по умолчанию 1.4);
  • shadowRGB - цвет тени, указывается в формате RGB (по умолчанию new shinejs.Color(0, 0, 0)).

Для того чтобы применить эти настройки требуется оформить их в специальном объекте shinejs.Config и передать вторым параметром при создании экземпляра shine:

Var config = new shinejs.Config({ numSteps: 3, opacity: 0.5, shadowRGB: new shinejs.Color(253, 0, 0) }); var shine = new Shine(document.getElementById("headline"), config);

Осталось только упомянуть, где обитает эта библиотека. Нашёл я её на просторах github .

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

На его создание меня вдохновил этот пост на реддите, где aionskull использовал карты нормалей в Unity для динамического освещения своих спрайтов. А пользователь с ником gpillow запостил в комментах что он сделал что-то похожее в Love2D. Вот тут 8-мб гифка с результатами. За неё спасибо jusksmit’у.

Итак, что такое динамическое освещение? Это техника в 3D графике, где источник света освещает объекты на сцене. Динамическое потому, что обновляется в реальном времени при движении источника. Довольно стандартная штука в 3D мире и легко применимая к 2D, если, конечно, вы можете использовать преимущества шейдеров.

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

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

Ок, это всё очень здорово, но как получить вектора нормали в 2d игре? Здесь, вообще-то, нет никаких объемных объектов… Однако, здесь нам могут помочь дополнительные текстуры (те самые карты нормалей), в которых будет записана необходимая информация. Я создал 2 таких карты для двух домов в видео повыше и использовал их чтобы посчитать освещение, вот пример:

В начале вы видите обычный спрайт домика без затенения. На второй части картинки расположена карта нормалей этого дома, кодирующая нормали этого домика в цвет текстуры. У вектора есть (x,y,z) координаты, а у пикселя текстуры есть r,g и b компоненты, так что закодировать нормаль реально просто: Возьмем фасад дома, который направлен на юг. Его нормаль будет вектором с координатами . (По хорошему, нормаль должна быть равна (0, 1, 0), но так как вектор мы определяем от -1 до +1, а кодировать надо в промежуток от 0 до 1, то, видимо, автор решил не запариваться и сразу считать нормали от -0.5 до +0.5. прим. перев.)

RGB значения не могут быть отрицательными, так что мы подвинем все значения на 0.5: . Ну и ещё RGB обычно представляется в числе от 0 до 255, так что мы домножим на 255 и получим , или, другими словами, вектор «на юг» будет вот этим светло-зеленым на карте нормалей.

Теперь, когда у нас есть нормали, мы можем позволить графической карте сделать её магию.
Я использую ImpactJS , у него неплохая совместимость с WebGL2D . (Он платный, я рекомендую pixi.js или любая другая графическая библиотека с webgl рендерером. Если знаете ещё аналоги - пишите в комменты! прим. перев.) Используя WebGL2D мы можем легко добавить пиксельный шейдер для освещения:

#ifdef GL_ES precision highp float; #endif varying vec2 vTextureCoord; uniform sampler2D uSampler; uniform vec3 lightDirection; uniform vec4 lightColor; void main(void) { // Берем вектор нормали из текстуры vec4 rawNormal = texture2D(uSampler, vTextureCoord); // Если альфа-канал равен нулю, то ничего не делаем: if(rawNormal.a == 0.0) { gl_FragColor = vec4(0, 0, 0, 0); } else { // Транслируем из RGB в вектор, а именно из 0..1 в -0.5..+0.5 rawNormal -= 0.5; // Вычисляем уровень освещенности float lightWeight = dot(normalize(rawNormal.xyz), normalize(lightDirection)); lightWeight = max(lightWeight, 0.0); // И записываем в пиксель gl_FragColor = lightColor * lightWeight; } }

Пара заметок: Это попиксельное освещение, которое немного отличается от вершинного освещения (обычного в 3d). У нас нет выбора, так как вершины в 2d бессмысленны (их всего 4 штуки для отображения плоскости на сцене). Но, вообще, это не проблема, попиксельное освещение гораздо более точное. Также следует отметить, что шейдер рендерит только освещение, без основного спрайта. Придется признать, я немного жульничаю, ведь на самом деле я не освещаю свой спрайт, а, скорее, затеняю его и в lightColor я передаю темно-серый цвет. Настоящее освещение пикселей, а именно повышение яркости, выглядит хуже, пиксели кажутся «вытертыми». У этой проблемы есть решения, но сейчас это непринципиально.

Часть вторая: рисование теней. Отбрасывание теней в 3D - хорошо изученная проблема с известными решениями, типа рейтрейсинга или shadow-mapping’а . Однако, я затруднился найти какое-нибудь приемлимое готовое решение для 2d, пришлось делать самому, думаю получилось нормально, хотя и у него есть пара недостатков.

Вкратце, будем рисовать линию от пикселя на сцене к солнцу и проверять, есть ли какое-нибудь препятствие. Если есть - то пиксель в тени, если нет - на солнце, так что, впринципе, ничего сложного.

Шейдер принимает xyAngle и zAngle , которые отвечают за то, где находится солнце. Так как оно очень далеко, то лучи света будут параллельны, и, соответственно, эти два угла будут одинаковы для всех пикселей. Также, шейдер будет принимать карту высот мира. Она будет показывать высоту всех объектов, зданий, деревьев и т.д. Если пиксель принадлежит зданию, то значение пикселя будет примерно 10, и означать, что высота здания в данной точке - 10 пикселей.

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


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


Если значение в карте высот будет больше, то всё, пиксель в тени. Если нет - мы продолжим искать. Но рано или поздно мы сдадимся и объявим, что пиксель освещен солнцем. В этом примере я захардкодил 100 шагов, пока что работает отлично.

Вот код шейдера в упрощенной/псевдо форме:

Void main(void) { float alpha = 0.0; if(isInShadow()) { alpha = 0.5; } gl_FragColor = vec4(0, 0, 0, alpha); } bool isInShadow() { float height = getHeight(currentPixel); float distance = 0; for(int i = 0; i < 100; ++i) { distance += moveALittle(); vec2 otherPixel = getPixelAt(distance); float otherHeight = getHeight(otherPixel); if(otherHeight > height) { float traceHeight = getTraceHeightAt(distance); if(traceHeight height) { traceHeight = getTraceHeight(height, zAngle, distance); if(traceHeight Map Properties и найдите поле SkyBox Texture Name. Со списком доступных текстур неба можно ознакомиться на этой странице Valve Developer Wiki. Убедитесь, что используете текстуру _hdr.

Чтобы свет исходил от скайбокса, вам нужно создать объект light_environment. Его расположение роли не играет. Свойства его тоже не сильно отличаются от других объектов освещения. Однако, есть две особенных настройки яркости: Brightness отвечает за направленный свет, идущий от «солнца», а Ambient за свет «в тени» (за пределами углов освещения солнца). Настройки Pitch Yaw Roll отвечают за направление света. Все они достаточно сложны в понимании, но страница на Developer Wiki включает примеры настроек для каждого неба, и вы можете придерживаться их в точности, либо немного изменять под свои нужды.

Компиляция с освещением

К этому времени вы уже должны освоиться с основными настройками компиляции. В окне Run Map выберите Expert для дополнительных настроек, связанных с освещением. На этом виде доступны несколько заранее подготовленных конфигураций с балансом производительности и картинки. Эффект настроек VIS будет рассмотрен в уроке по дистанции обзора.

Сейчас просто запомните, что пропуск настроек VIS намного ускорит компиляцию, но снизит производительность карты (и создаст проблемы с водой). Компиляция HDR сделает свет наиболее красивым, но займёт гораздо больше времени. Последний флажок $game отвечает за запуск игры после компиляции.

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

Ознакомившись с первой частью урока, вы уже должны иметь представление о базовых возможностях Source в плане освещения. Давайте же рассмотрим и более продвинутые приёмы, которые может предложить движок, включая дополнительные возможности Portal 2, CS:GO и Dota 2.

Настройки карты тонов

Взглянув на наружное освещение своей карты вы заметите, что свет слишком яркий и «сияющий». В эту ловушку попадают многие карты, но, к счастью, есть способ её избежать, используя гениальный объект под названием env_tonemap_controller.

Создайте такой объект. Контроллер карты тонов активируется через входящий сигнал, так что создаём logic_auto, чтобы отсылать сигнал сразу после загрузки карты.

По сути, этот объект изменяет «экспозицию» в глазах игрока. В его свойствах множество опций, но нам понадобится только входящий сигнал SetBloomScale, отвечающий за HDR-эффект «блум». Взгляните на картинку, чтобы оценить разницу в значениях.

Масштаб карты освещения

Мощное сочетание света и тени выведет любую карту на новый уровень, но статичное освещение Source может огорчить вас эффектом «квадратов». Он проявляется из-за низкого разрешения карты освещения – текстуры, на которой сохраняется освещение. К счастью, есть способ увеличить количество таких текстур на поверхностях блоков.

Для примера создадим комнату с освещением light_spot и несколькими блоками для проверки теней. Выберите Toggle texture application и на 3D-виде выделите несколько поверхностей возле источника освещения. Теперь вы можете изменить параметр Lightmap Scale. Меньшие значения эквиваленты повышенному разрешению. Чтобы разница была более наглядной, измените камеру 3D-вида на 3D Lightmap grid.

На изображении показано сравнение двух параметров в игре. Слева установлено значение 16 (по умолчанию), справа 2. Во втором случае тени более чёткие и лучше заметны.

Не увлекайтесь этой опцией, потому что она серьёзно влияет на размер файлов и производительность карты.

Динамическое освещение и тени

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

Благодаря модульной структуре Source поздние игры сильно продвинулись в динамическом освещении, предлагая больше разнообразных настроек при создании карт.

Тени от моделей и shadow_control

В модах к Half-Life 2, CS:S, Portal 1 и Source Base модели и NPC отбрасывали тени с помощью одного базового метода «render to texture», работающего, как показано на картинке.

Все тени отбрасываются в одном направлении, задаваемом объектом shadow_control. Чтобы тени падали под правильным углом, настройки Pitch Yaw Roll в свойствах shadow_control должны совпадать с углами освещения объекта light_environment.

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

В играх Left 4 Dead, Portal 2, и Alien Swarm тени могут зависеть от ближайшего источника освещения, что задаётся в свойствах shadow_control и соответствующих объектах освещения. Не рекомендуется создавать много таких объектов в одной зоне, иначе при движении объектов и игрока тени будут менять направление случайным образом или вовсе не менять его.

Проецируемые текстуры

Начиная с Portal 2, Valve стала широко использовать проецируемые текстуры для создания интересных сцен. Как следует из названия, эти источники освещения представляют из себя изображения, проецируемые на поверхности блоков и объектов. Есть серьёзное ограничение, заключающееся в том, что Valve не позволяет включать больше одной такой текстуры за раз (моддинг позволяет избавиться от этого ограничения).

Для следующего примера я использовал инструменты Portal 2 Authoring Tools. Создаём объект env_projectedtexture, его можно вращать и передвигать, как и любой источник освещения. Загляните в свойства, тут нам кое-что понадобится. Убедитесь, что параметр Enable Shadows выставлен на Yes и увеличьте значение FarZ, чтобы свет покрывал большее расстояние. Для изменения внешнего вида можете задать другое имя текстуры в поле Texture name, но имейте в виду, что большинство материалов не предназначены для этой цели! Также добавьте на карту обычный объект light.

Поместите на карту несколько prop_weighted_cube; запустив игру, вы должны наблюдать, как их тени отбрасываются динамически вместе с их перемещением. Вы даже можете использовать параметр Parent, чтобы привязать проецируемую текстуру к физической модели и переносить её. В своём примере я также разместил рядом с проецируемой текстурой объект point_spotlight, имитирующий луч прожектора.

Технология проецируемых текстур работала и в ранних играх на Source, включая Half-Life 2, где она применялась для динамического света от фонарика игрока. Однако, их не рекомендуется использовать из-за конфликта с фонариком при одновременном задействовании.

Глобальное динамическое освещение

В Dota 2 и CS:GO есть свои новые объекты для динамического освещения.

В CS:GO используется env_cascade_lighting. Он просто помещается на карту и работает в паре с light_environment, излучая динамический свет из скайбокса. Объект использует так называемую каскадную карту теней, выдавая куда более впечатляющую и реалистичную картинку по сравнению с возможностями ранних игр.

В Dota 2 работает похожий метод: env_global_light. Этот объект работает по аналогии с проецируемыми текстурами и размещается рядом с фиксированной камерой, создавая тени от всех объектов окружения. В новом редакторе «Source 2» доступен предварительный просмотр такого освещения в реальном времени.

Надеюсь, вы узнали кое-что новое об объектах освещения в Source. Каждый из них в подробностях рассмотрен в Valve Developer Wiki. К тому же, карты других моддеров помогут вам лучше разобраться в работе этих инструментов и использовать их эффективнее.

Кубические текстуры и эффект HDR

Ваша карта кажется простоватой? Ей не хватает чего-то особенного? В таком случае засиять по-новому ей поможет High Dynamic Range.

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

Что такое кубические текстуры и как они с этим связаны?

Кубические текстуры – это технология, применяющаяся движком Source для отражений, также они играют важную роль в работе эффектов HDR, так как должны генерироваться для Low Dynamic Range (не-HDR) и High Dynamic Range. Мы вернёмся к ним чуть позже.

Как устроен HDR

Технический анализ реализации эффекта HDR на карте можно найти на Valve Developer Wiki .

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

Источники освещения

Первым делом проверяем освещение – в свойствах отвечающих за него объектов есть значения Brightness, Brightness HDR и BrightnessScale HDR.

Brightness – это обычный цвет освещения.

BrightnessHDR – это отдельный цвет для случая, когда задействован HDR, по умолчанию его значение установлено на «-1 -1 -1 1», что значит «Так же, как при LDR».

BrightnessScale HDR – на это значение умножается интенсивность HDR-освещения, если оно работает на карте.

Цвет HDR обычно не меняют, но вы можете заметить, что в HDR-режиме всё становится намного ярче, так что можно чуть убавить множитель яркости.

Скайбокс

Здесь всё просто: убедитесь, что ваша текстура скайбокса поддерживает HDR. Большинство скайбоксов от Valve либо поддерживают HDR, либо имеют HDR-версию. Список скайбоксов и дополнительную информацию по ним можно найти на Valve Developer Wiki .

Компиляция HDR

Здесь ваша карта превращается в HDR-карту: просто включите эффект в настройках. Для этого поставьте флажок «HDR» в настройках рендеринга или выберите HDR-профиль в экспертных настройках.

После этого предстоит ещё кое-что сделать, но давайте пока вернёмся к кубическим текстурам.

Кубические текстуры

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

В Source отражающие/блестящие/глянцевые поверхности выглядят очень неплохо благодаря набору изображений, выступающих основой для отражений. Такие изображения генерируются из того места, где расположен объект env_cubemap. Это значит, что если у вас один такой объект в комнате, то на всех отражениях в ней будет одно и то же. Это приведёт к странным графическим артефактам, поэтому кубическая текстура содержит шесть изображений, представляющих развёртку куба, для формирования полной панорамы комнаты.

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

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

Технический анализ реализации кубических текстур на карте можно найти на Valve Developer Wiki .

Генерирование кубических текстур

Когда ваша карта готова и кубические текстуры размещены по своим местам, вы компилируете её и запускаете в соответствующей игре. Создание кубических текстур происходит в самой игре (тот простой шаг, который после рендеринга). Консольная команда «buildcubemaps» запускает быстрый процесс снятия скриншотов с позиций объектов env_cubemap и сохранения их в BSP-файл карты. После этого ваша карта может существенно располнеть, в зависимости от числа кубических текстур.

В теории этого достаточно для выпуска своей карты с крутыми текстурами и HDR, но на практике не всё так гладко. Hammer начинает генерировать кубические текстуры уже во время рендеринга, но в последних сборках редактора это работает совсем плохо и в результате в файл карты пакуются бесполезные пустые текстуры. Прежде чем генерировать HDR- и LDR-текстуры в игре нам нужно сначала удалить повреждённые данные с помощью программы под названием Pakrat.

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

Скачав Pakrat, открываем файл своей карты и находим кубические текстуры. Они именуются по шаблонам c123_123_123.vtf и c123_123_123.hdr.vtf. Удаляем их и сохраняем карту. Теперь мы можем генерировать новые текстуры, открыв карту в игре.

Команды консоли

Пошаговая инструкция по удалению старых и созданию новых кубических текстур для HDR и LDR:

  • Открыть BSP-файл в Pakrat
  • Удалить повреждённые текстуры. Они относятся к типу «Texture» (НЕ «Material»)
  • Открыть игру
  • Ввести команды «mat_hdr_level 2» и «mat_specular 0»
  • Загрузить свою карту: «map название-вашей-карты.bsp»
  • Загрузить другую карту для очистки кэша (ввести «Maps *» для вывода списка карт)
  • Ввести команду «mat_hdr_level 0»
  • Снова загрузить свою карту
  • Ввести команду «buildcubemaps»
  • Карта готова, если хотите посмотреть на результат, введите «mat_specular 1» и «mat_hdr_level 2». Загрузите другую карту для очистки кэша, затем можете загружать свою.

    Для проверки кубических текстур можно использовать команду «impulse 81», если они отображаются неочевидно.

    Если ошибки в VRAD исправлены, то некоторые шаги будут излишни, но в любом случае будет полезно узнать весь процесс.

    • Не переименовывайте файл карты, хранящиеся в нём текстуры могут быть привязаны к имени файла. Это не всегда создаёт проблемы, но всё же имейте это в виду.
    • Проверяйте содержимое файла карты в Pakrat, чтобы убедиться в успешном сохранении текстур и обнаружить скрытые большие файлы.
    • Pakrat умеет отображать текстуры – если на их месте одна чернота, значит что-то пошло не так.
    • Иногда образец игры, который запускает Hammer, генерирует текстуры некорректно. Попробуйте запустить полную Steam-версию игры и сгенерировать текстуры в ней.

    Как правило, HDR играет второстепенную роль – к нему обращаются, добавляя завершающие штрихи к уже готовой карте. Пускай это и так, но HDR способен существенно преобразить схему освещения на вашей карте, так что будьте готовы переделывать её по нескольку раз. Source прекрасно сохранился для своих лет, но местами уже выглядит совсем неброско. Приукрасив карту эффектом HDR, вы поможете игрокам не вспоминать, что перед ними игра 2006 года.

    Описание:
    Теперь в игре Skyrim SE факелы будут отбрасывать динамические тени, настройка освещения от факела...и многое другое.Для игры Skyrim LE этот мод

    Обновление:3.1.1
    - Улучшено меню отладки в МСМ.
    - Незначительные исправления.
    - Скрипт теперь применяется к NPC, если это необходимо.

    Обновление:3.1
    * Добавлено МСМ меню с настройками мода и более подробными описаниями каждой функции.
    * Все модули в МСМ меню можно переключать для повышения совместимости с другими модами.
    * Логика Магического света изменена.
    * Функциональность для НПС работает без проблем в экстерьерах, то есть если у НПС есть факелы или фонари оснащенные, то у них также будут отбрасываться тени, но лучше не применять эту опцию, могут быть баги. Обходной путь возможен с помощью параметров месторасположения.
    * Произведены незначительные исправления.
    * Улучшена производительность скрипта.
    * Совместимость с ELFX (все через скрипт, без дополнительного esp-файла).
    * Совместимость с Wearable Lanterns.
    * Улучшена совместимость с заклинанием Свет свечи.
    * Мод теперь требует наличие skse64 и SkyUI для настроек.
    * ВАЖНО: Если вы обновляетесь с версии 2.2, то удалите мод с помощью книги настроек перед обновлением, в книге есть опция Удалить.

    Журнал изменений:
    - По доп.ссылке добавлен патч для мода "Smoking Torches and Candles".
    - НОВАЯ ОСОБЕННОСТЬ СКАНИРОВАНИЯ. Сканирует область с источниками света с тенями и включает / отключает тени соответственно.
    - Удалены некоторые вещи (нет больше бесконечных ям в тестовой комнате qasmoke).
    - Теперь мод полу-совместим с модами на изменение моделей факелов. Они будут работать только для NPC, игрок использует уникальную модель факела.
    - Опциональная модель факела теперь включена в основной мод.
    * Добавлена заметка об освещение внизу в описании мода.

    Подробнее:
    * Факелы будут отбрасывать динамические тени.
    * Заклинание Свет свечи будет отбрасывать динамические тени.
    * Заклинание Магический свет будет отбрасывать динамические тени (во время полета).
    * Переключение заклинания Свет свечи (использовать его снова, чтобы отключить).
    * Настройки яркости / диапазона света прямо в игре через МСМ меню.
    * Отключение / включение функций мода в определенных местах или в целом.
    * Настройки света осуществляется в игре, такие как яркость, диапазон, тени и автоматическое переключение настроек, чтобы обойти проблемы, связанные с динамическими тенями.
    * НОВАЯ ОСОБЕННОСТЬ СКАНИРОВАНИЯ. Сканирует область для источников света с тенями и включает / отключает тени соответственно.
    * Патч для мода Wearable Lanterns от Chesko: позволяет фонарю который вы носите отбрасывать тени (только спереди на ремне).
    * Все настройки мода через МСМ меню.

    Совместимость:

    * : совместим (патч прилагается)
    * (ELFX): совместим (патч не требуется)
    * : совместим (патч не требуется). В данном моде есть вариант ELE с уже встроенным модом "Torches Cast Shadows".
    * Моды которые вносят изменения в заклинания Свет свечи и Магический свет могут быть несовместимы.

    Заметки:
    * Если у вас в игре после установки мода нет теней от факелов, то переоснастите факел или выкиньте и подберите.

    Требования:
    Skyrim SE 1.5.39.0.8 и выше
    2.0.7 и выше
    5.2 и выше

    При обновлении с 2.2 до 3.1 и выше:
    1. Зайдите куда нибудь в помещение где нет факелов горящих, уберите у себя факел из рук в инвентарь. Удалите мод с помощью книги настроек перед обновлением, в книге есть опция Удалить. Сохранитесь в пустой слот, выйдите из игры.
    2. Удалите файл Player_Shadows.esp из папки Data в игре.
    3. Удалите скрипты _ST_CanFX.pex, _ST_Handler.pex, _ST_Magelight.pex, _ST_Options.pex, _ST_TorchEquip.pex по пути Data/scripts/
    4. Удалите скрипты _ST_CanFX.psc, _ST_Handler.psc, _ST_Magelight.psc, _ST_Options.psc, _ST_TorchEquip.psc по пути Data/scripts/source
    5. Удалите файл lightspellactorfx.nif по пути Data/meshes/magic/
    6. Зайдите в игру, сохранитесь снова но в другой пустой слот сохранений, выйдите из игры и установите новую версию.

    Установка: (можно через NMM менеджер или вручную)
    1. Берем все содержимое из папки Data в архиве и кидаем в папку Data в игре, подтвердите слияние папок, активировать мод.
    2. Если у вас есть мод "Smoking Torches and Candles" (вариант факелы), то скачайте по доп.ссылке патч и установите с заменой файлов, если потребуется.
    3. Если у вас есть мод "Wearable Lanterns", то скачайте по доп.ссылке патч и установите с заменой файлов, если потребуется. Работает только с фонарями которые размещены спереди на ремне. Установить патч после Wearable Lanterns и Torches Cast Shadows.
    4. Как устанавливать моды

  • Разработка игр
    • Перевод

    Я работаю над игрой в жанре стесл-экшн, где в геймплее большую роль будут играть тени. Поэтому я сделал динамическое освещение/затенение, используя WebGL шейдеры.

    Часть первая: Динамическое освещение На его создание меня вдохновил пост на реддите, где aionskull использовал карты нормалей в Unity для динамического освещения своих спрайтов. А пользователь с ником gpillow запостил в комментах что он сделал что-то похожее в Love2D. Вот тут 8-мб гифка с результатами. За неё спасибо jusksmit’у.

    Итак, что такое динамическое освещение? Это техника в 3D графике, где источник света освещает объекты на сцене. Динамическое потому, что обновляется в реальном времени при движении источника. Довольно стандартная штука в 3D мире и легко применимая к 2D, если, конечно, вы можете использовать преимущества шейдеров.

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

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

    Ок, всё очень здорово, но как получить вектора нормали в 2d игре? Здесь, вообще-то, нет никаких объемных объектов… Однако, здесь нам могут помочь дополнительные текстуры (те самые карты нормалей), в которых будет записана необходимая информация. Я создал 2 таких карты для двух домов в видео повыше и использовал их чтобы посчитать освещение, вот пример:

    В начале вы видите обычный спрайт домика без затенения. На второй части картинки расположена его карта нормалей, кодирующая вектора нормалей в цвет текстуры. У вектора есть (x,y,z) координаты, а у пикселя текстуры есть r,g и b компоненты, так что закодировать нормаль реально просто: Возьмем фасад дома, который направлен на юг. Его нормаль будет вектором с координатами . (По хорошему, нормаль должна быть равна (0, 1, 0), но так как вектор мы определяем от -1 до +1, а кодировать надо в промежуток от 0 до 1, то, видимо, автор решил не запариваться и сразу считать нормали от -0.5 до +0.5. прим. перев.)

    RGB значения не могут быть отрицательными, так что мы подвинем все значения на 0.5: . Ну и ещё RGB обычно представляется в числе от 0 до 255, так что мы домножим на 255 и получим , или, другими словами, вектор «на юг» будет вот этим светло-зеленым на карте нормалей.

    Теперь, когда у нас есть нормали, мы можем позволить графической карте сделать её магию.
    Я использую ImpactJS , у него неплохая совместимость с WebGL2D . (Он платный, я рекомендую pixi.js или любая другая графическая библиотека с webgl рендерером. Если знаете ещё аналоги - пишите в комменты! прим. перев.) Используя WebGL2D мы можем легко добавить пиксельный шейдер для освещения:

    #ifdef GL_ES precision highp float; #endif varying vec2 vTextureCoord; uniform sampler2D uSampler; uniform vec3 lightDirection; uniform vec4 lightColor; void main(void) { // Берем вектор нормали из текстуры vec4 rawNormal = texture2D(uSampler, vTextureCoord); // Если альфа-канал равен нулю, то ничего не делаем: if(rawNormal.a == 0.0) { gl_FragColor = vec4(0, 0, 0, 0); } else { // Транслируем из RGB в вектор, а именно из 0..1 в -0.5..+0.5 rawNormal -= 0.5; // Вычисляем уровень освещенности float lightWeight = dot(normalize(rawNormal.xyz), normalize(lightDirection)); lightWeight = max(lightWeight, 0.0); // И записываем в пиксель gl_FragColor = lightColor * lightWeight; } }

    Пара заметок: У нас получается попиксельное освещение, которое немного отличается от вершинного освещения (обычного в 3d). Выбора особого нет, так как вершины в 2d бессмысленны (их всего 4 штуки для отображения плоскости на сцене). Но, вообще-то, это не проблема, попиксельное освещение гораздо более точное. Также следует отметить, что шейдер рендерит только освещение, без основного спрайта. Придется признать, я немного жульничаю, ведь на самом деле я не освещаю свой спрайт, а, скорее, затеняю его и в lightColor я передаю темно-серый цвет. Настоящее освещение пикселей, а именно повышение яркости, выглядит хуже, пиксели кажутся «вытертыми». У этой проблемы есть решения, но сейчас это непринципиально.

    Часть вторая: рисование теней. Отбрасывание теней в 3D - хорошо изученная проблема с известными решениями, типа рейтрейсинга или shadow-mapping’а . Однако, я затруднился найти какое-нибудь приемлимое готовое решение для 2d, пришлось делать самому, думаю получилось нормально, хотя и у него есть пара недостатков.

    Вкратце, будем рисовать линию от пикселя на сцене к солнцу и проверять, есть ли какое-нибудь препятствие. Если есть - то пиксель в тени, если нет - на солнце, так что, впринципе, ничего сложного.

    Шейдер принимает xyAngle и zAngle , которые отвечают за то, где находится солнце. Так как оно очень далеко, то лучи света будут параллельны, и, соответственно, эти два угла будут одинаковы для всех пикселей. Также, шейдер будет принимать карту высот мира. Она будет показывать высоту всех объектов, зданий, деревьев и т.д. Если пиксель принадлежит зданию, то значение пикселя будет примерно 10, и означать, что высота здания в данной точке - 10 пикселей.

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


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


    Если значение в карте высот будет больше, то всё, пиксель в тени. Если нет - мы продолжим искать. Но рано или поздно мы сдадимся и объявим, что пиксель освещен солнцем. В примере я захардкодил 100 шагов, пока что работает отлично.

    Вот код шейдера в упрощенной/псевдо форме:

    Void main(void) { float alpha = 0.0; if(isInShadow()) { alpha = 0.5; } gl_FragColor = vec4(0, 0, 0, alpha); } bool isInShadow() { float height = getHeight(currentPixel); float distance = 0; for(int i = 0; i < 100; ++i) { distance += moveALittle(); vec2 otherPixel = getPixelAt(distance); float otherHeight = getHeight(otherPixel); if(otherHeight > height) { float traceHeight = getTraceHeightAt(distance); if(traceHeight height) { traceHeight = getTraceHeight(height, zAngle, distance); if(traceHeight