Руководство по эффектам и шейдерам 3DMigoto GIMI
Автор: SilentNightSound
Это руководство описывает процесс изменения эффектов в Genshin, таких как эффекты навыков, освещение и любые игровые объекты, которые не управляются через текстуры или буферы. Изучение того, как работают шейдеры, значительно расширит возможности моддинга.
Этот туториал по моддингу эффектов сложнее, чем мои предыдущие, посвященные базовому редактированию/импорту мешей и редактированию текстур, но для понимания этого руководства не требуется знание предыдущих.
Я организовал это руководство примерно в порядке увеличения сложности, так что даже прочтение первой секции должно быть достаточно для того, чтобы вносить простые изменения. Для более сложных разделов потребуется базовое знание программирования.
Я покажу три примера, сложность которых увеличивается:
- Изменение цвета атаки/навыка персонажа (см. https://gamebanana.com/mods/409181 для примера с изменением цвета ледяных атак Гань Юй)
- Создание эффекта, который переключается между несколькими цветами с течением времени (см. https://gamebanana.com/mods/418434 для примера с елкой, меняющей цвет огоньков)
- Демонстрация того, как создать базовые анимации эффектов (см. https://gamebanana.com/mods/420434 для примера анимированных линий на Кибер-костюме Райден)
(Комментарий от переводчика Rayvich, так как этот гайд от Сайлента довольно таки огромный, а времени у меня не настолько много, я его пустил через машинный перевод в отличие от других руководств где я лишь наполовину переводил его машинально, а другую часть я вручную проверял и переписывал. Так что если будет какая то отсебятина на момент чтения можете сообщить мне об этом(Discord: rayvy). Как найду время постараюсь перепроверить, но просто чтобы не пустовала страница сделаю так увы)
Необходимые условия
Убедитесь, что у вас установлена версия 3DMigoto GIMI Dev (зеленый текст должен быть видим).
Важное замечание
По умолчанию я отключил возможность для GIMI извлекать шейдеры, так как это может помешать работе модов. Вы можете снова включить эту возможность, убедившись, что строка marking_actions
в файле d3dx.ini содержит hlsl
и asm
в списке.
Также, если у вас возникли проблемы с модами после того, как вы попробовали что-то из этого руководства, попробуйте очистить папку ShaderFixes — иногда извлеченный шейдер может вызвать сбои в модах.
Давайте начнем!
Изменение Цвета Атак Персонажа (Пламя Дилюка)
В этом разделе я покажу, как изменить цвет пламени Дилюка. Этот раздел имеет базовую/среднюю сложность и не требует предварительных знаний программирования или работы с шейдерами.
Сначала рекомендую отправиться в место, где на экране будет как можно меньше объектов, но эффект, который вы хотите изменить, все равно будет виден. Вы поймете почему, но чем больше объектов на экране, тем дольше будет искать нужные шейдеры. Хорошим выбором всегда является стартовая пляжная зона.
Как только вы выбрали хорошее место, вызовите нужный эффект и зайдите в меню паузы. В данном случае нас интересует эффект пламени от навыка Дилюка, поэтому нажмите
e
, а затем паузу (примечание: для эффектов, которые появляются только при не-паузированном режиме игры, их все равно можно получить, но это будет немного сложнее — я объясню позже).

Теперь нажмите
1
и2
на клавиатуре с цифрами, чтобы переключаться между Pixel Shaders (PS
). Существует два типа шейдеров — Vertex Shaders (VS
), которые управляют тем, где объекты рисуются на экране, и Pixel Shaders (PS
), которые отвечают за то, как они выглядят, и рисуют текстуры/цвета. Так как нас интересует цвет, нам нужно выбрать PS.Когда вы найдете нужный
PS
, эффект исчезнет в игре. Например, вот шейдер, который контролирует центральное пламя при атаке:

А вот этот шейдер контролирует окружающие облака пламени:

Мы начнем с этих двух. 5. Нажатие 3
на цифровой клавиатуре скопирует хеш шейдера в ваш буфер обмена и сохранит шейдер в папке ShaderFixes. Хеши указанных выше шейдеров: e75b3ffb93a1d268
и dd0757868249aaa5
(Примечание: вы можете нажать +
на цифровой клавиатуре, чтобы сбросить буферы на 0, если вам нужно быстро вернуться к исходной точке). Обратите внимание, что хеши шейдеров могут изменяться между версиями, поэтому ваши хеши могут отличаться от приведенных значений.
- Шейдеры теперь должны появиться в папке ShaderFixes с именем типа
hash-ps_replace.txt
.

Если они не появились после нажатия 3
на цифровой клавиатуре, убедитесь, что вы добавили hlsl
и asm
в параметр marking_actions
, как указано в Важном замечании вверху, и обновите с помощью F10
.
Также обратите внимание, что небольшое количество шейдеров не будет правильно декомпилировано в hlsl
(язык высокого уровня) и вместо этого вернется к asm
(машинный код). Эти шейдеры все равно будут работать, но их редактирование будет сложнее. Я не буду покрывать asm
в этом руководстве, но концепции остаются теми же — синтаксис шейдеров просто сложнее для понимания.
- Откройте файл с помощью вашего текстового редактора по выбору (Notepad/Notepad++/Sublime Text/и т. д.). На первый взгляд файл может показаться пугающим, но не волнуйтесь — вам не нужно понимать все детали, чтобы сделать базовые изменения (я подробнее объясню, как работает этот файл, в следующих разделах).

- На данный момент нас больше всего интересует работа с входами и выходами. Они перечислены прямо под основным кодом — этот файл принимает 9 входов (номера
v0
,v1
,v2
, …v8
) и имеет один выход (o0
).

- Обычно проще всего начать с вывода. Его тип —
float4
, что означает, что он имеет компонентыx
,y
,z
иw
и принимает в качестве входных данных числа с плавающей запятой (десятичные). Мы можем экспериментировать, чтобы увидеть, что это делает, добавив строку в конце кода, которая принудительно устанавливает значение в постоянную:

(//
и /* */
означают комментарии в коде, и они игнорируются программой. 3dmigoto также экспортирует код asm
ниже кода hlsl
— когда я говорю «в конце», я имею в виду непосредственно перед return
, а не после. Всё после этой точки закомментировано и не будет выполняться по умолчанию. Если вы видите такие слова, как div
, mul
и mov
, значит, вы зашли слишком далеко).
По сути, мы перезаписываем то, что игра вычисляет для значения, и подставляем свои собственные.
- Сохраните файл, затем нажмите
F10
в игре для перезагрузки (не забудьте также нажать+
для сброса буферов!). 3dmigoto автоматически загрузит шейдер из папки ShaderFixes. Вот что произойдет:

Центральная линия стала черной, а искры — зелеными. Если вы знакомы с тем, как хранятся цвета, вы можете догадаться, что представляет собой o0.x
, но давайте продолжим проверку, чтобы убедиться:
Установив компоненты x
и z
в 0, а y
в 1:

Всё становится зеленым:

А если установить x
и y
в 0, а z
в 1:

Цвет становится синим:

Другими словами, o0.xyz
соответствует RGB-цветам эффекта. Не всегда так, что o0
— это цвет, некоторые шейдеры имеют несколько выводов, так что цвет может быть на o1
или o2
и т.д.; к счастью, этот шейдер достаточно прост, и у него есть только один вывод — o0
.
(Если вам интересно, что представляет собой w
, похоже, это связано с шириной/эмиссией эффекта):

- Теперь, когда мы знаем, что означают значения, мы можем делать базовые изменения цвета. Например, установив все три компонента
o0.xyz
в 0, мы можем сделать пламя Дилюка черным:


Или мы можем сделать его фиолетовым, установив компоненты r
и b
в 1, а g
оставив равным 0:

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


Обратите внимание, что в отличие от случаев, когда мы принудительно устанавливаем постоянное значение, текстура пламени все еще видна.
Мы можем даже делать более сложные вещи, например, устанавливать значения через математические выражения, но я расскажу об этом в последней части.
Вместо изменения вывода, также возможно изменять эффекты, меняя входные значения с помощью похожего метода (размещая строки сразу после того, как они обычно загружаются, и перезаписывая значения игры), хотя вам нужно будет поэкспериментировать, чтобы выяснить, какая переменная отвечает за какое изменение.
- Это базовый процесс изменения цветов эффектов — найти хэш, выгрузить его, затем изменить либо входные, либо выходные данные. Однако, если вы следили за процессом, то могли заметить, что не все текстуры пламени были заменены — есть еще несколько, которые нужно выгрузить:
0fa220b5adced192
— искры:

bf7eb60b256538c7
— пламя вдоль меча:

439c03865c4ce77e
— птица:

7690cf4aa6647c6c
— свечение меча во время ульты:

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

Получение этих шейдеров более неудобное, но не невозможное. Первый метод — это включить что-то вроде бесконечной энергии взрыва в Grasscutter и многократно использовать ульту, циклируя. Это займет некоторое время, но должно сработать для всего, что можно повторить.
(ОБНОВЛЕНИЕ: я получил рекомендации о двух других способах получения информации о шейдерах из взрывов: один — это стоять в мелкой воде или с задом к стене, чтобы отключить камеру взрыва. Это позволит вам нормально поставить игру на паузу во время взрывов и даст вам время для циклирования хэшей:

Второй способ — использовать чит-программу, такую как Akebi, чтобы уменьшить скорость игры до менее чем 1, что позволяет смотреть эффекты в замедленном режиме. Обратите внимание, что использование чит-программ может привести к банам на официальных серверах, поэтому я рекомендую использовать только частные серверы, если вы решите воспользоваться этим методом.
Огромное спасибо ComplexSignal31#5778 и NK#1321 за рекомендации!)
Для эффектов, которые показываются только в кат-сценах или трудны для воспроизведения, самый быстрый метод — это сделать дамп кадров. Смотрите руководство по моддингу текстур для получения подробностей о том, как выполнять дампы кадров, но по сути, вам нужно нажать F8
, когда эффект находится на экране, чтобы выполнить дамп одновременно с его показом.
К сожалению, поскольку мы не знаем хэш шейдера, потребуется полный дамп, поэтому убедитесь, что у вас есть 5-10 ГБ свободного места и как можно меньше объектов на экране.

После того как вы получите папку с анализом кадров, созданную после нажатия F8
, вы можете просмотреть её, чтобы увидеть, когда рисуется эффект. Файлы o0
и o1
показывают, что рисуется с каждым ID, и они очень полезны для изоляции точного ID, на котором эффект появляется на экране.
Пример: 000351-o0=3315d2b5-vs=eb65cb4eba57132b-ps=7690cf4aa6647c6c.dds
выглядит так в моем дампе кадров:

В то время как 000352-o0=3315d2b5-vs=f6a1f24f9c9b28c2-ps=a69e25f25a6c8e04.dds
выглядит так:

Мы знаем, что в этом кадре вызов рисования 000352
отвечает за свечение на земле. Мы также можем получить хэш из имени файла, ps=a69e25f25a6c8e04
.
С помощью этого метода мы можем найти оставшиеся хэши:
000353-o0=3315d2b5-vs=f50ce30bb0caf55c-ps=4d4da8a4cbe1149a.dds

И 000365-o0=3315d2b5-vs=72ce1e39ede0982f-ps=622a52d3edcf0363.dds

- Теперь, когда у нас есть оставшиеся хэши, нам нужно их дампить. Нажмите на
+
на цифровой клавиатуре, чтобы сбросить буферы, затем используйте ульту и начните циклировать с помощью1
/2
на цифровой клавиатуре, пока эффект находится на экране. Даже если эффект уже покинул экран, как только вы начали циклировать, когда эффект был на экране, он появится в списке и будет доступен для дампа:
Пример PS 4d4da8a4cbe1149a
, который появляется даже после того, как ульта не активна:

С помощью этой техники мы можем дампить оставшиеся шейдеры a69e25f25a6c8e04
, 4d4da8a4cbe1149a
и 622a52d3edcf0363
:

- Моддинг завершен! Или… может быть, нет. Если вы переключитесь на другого персонажа-пиро, например, Ху Тао, вы можете заметить проблему:

Мы сделали ВСЕ пламя черным, а не только пламя Дилюка. Также, если кто-то другой создал мод, изменяющий пламя другого персонажа, например Ху Тао или Клее, он будет конфликтовать с модом Дилюка.
- Мы хотим ограничить эффект так, чтобы он отображался только, когда Дилук находится на поле. Существует несколько способов сделать это, но все они следуют одному основному принципу — мы определяем условие, которое возникает всякий раз, когда Дилук на поле, и применяем эффекты только в случае, если это условие истинно. Это несколько более сложная тема, которая станет более понятной после того, как вы поэкспериментируете с шейдерами и прочитаете последующие разделы — если у вас возникнут трудности с пониманием, попробуйте прочитать следующие разделы и вернуться к этому позже.
Сначала нам нужно определить хэш, который уникален для Дилюка. Для простоты я буду использовать хэш VB Дилюка 56159d74 (хэш VB можно переключать с помощью числовой клавиатуры / и *, а копировать — с помощью - на числовой клавиатуре):

- Далее мы строим файл .ini, который будем использовать для избирательного применения эффектов. Мы определяем переменную под названием
$ActiveCharacter
и устанавливаем ее равной 0 в начале каждого кадра ([Present]
выполняется один раз за кадр в начале). Мы устанавливаем значение равным 1 только тогда, когда Дилук находится на поле, что определяется совпадением хэша VB:
[Constants]
global $ActiveCharacter
[Present]
post $ActiveCharacter = 0
[TextureOverrideDilucVB]
hash = 56159d74
match_priority = 1
$ActiveCharacter = 1
Здесь match_priority
нужен только для того, чтобы этот эффект не мешал другим загруженным модам с Дилуком — если вы добавляете этот эффект как часть мода, а не отдельно, вам не нужно включать его.
- Теперь есть два способа изолировать шейдер. Проще из них — это просто определить пользовательский шейдер и выполнить замену, затем создать
shaderoverride
и запускать пользовательский шейдер только тогда, когда Дилук является активным персонажем:
[ShaderOverrideDilucFlame]
hash = 4d4da8a4cbe1149a
if $ActiveCharacter == 1
run = CustomShaderDilucFlame
endif
[CustomShaderDilucFlame]
ps = 4d4da8a4cbe1149a-ps_replace.txt
handling = skip
drawindexed = auto
Это обычно работает, но 3dmigoto иногда не компилирует hlsl
должным образом, если это делать таким образом, что может привести к ошибкам. Также это не будет работать с asm
. Но преимущество в том, что шейдер можно упаковать в папку мода вместе с остальными файлами мода, и он не будет мешать, если другой мод попытается изменить тот же шейдер.
Другой метод — передать пользовательскую переменную в шейдер и выполнять эффект только в случае, если переменная совпадает. Следующий раздел будет более подробно рассматривать этот процесс, но по сути вам нужно создать такой раздел для каждого шейдера:
[ShaderOverrideDilucFlame]
hash = 0fa220b5adced192
x160 = $ActiveCharacter
Затем нужно определить новую константу в шейдере:




Передача пользовательских значений в шейдеры (Цикличная анимация цветов)
В этом разделе я продемонстрирую, как загружать пользовательские значения из файлов .ini
в шейдеры и как вы можете использовать это для создания эффектов, которые меняются между несколькими цветами. Я также продемонстрирую, как найти части шейдера, которые контролируют эмиссию, что является более сложной задачей, чем просто изменение цветов эффектов.
Этот раздел средней сложности — я предполагаю, что вы прочитали большинство предыдущих разделов и хотя бы немного знакомы с файлами .ini
и шейдерами (например, знаете, как их открыть и хотя бы примерно понимаете разные части). Основные знания программирования будут полезны.
- Как и раньше, начинаем с того, что собираем хеши шейдеров, на этот раз для столба Чжунли:

В отличие от Дилюка, этот хеш не приведет к исчезновению всего столба — только текстуры. Это связано с тем, что столб рисуется с использованием нескольких шейдеров, и даже если мы пропустим один из них, части объекта всё равно будут отображаться (в данном случае остаётся контур столба).
Хеш в этом случае — 4c99fec14dca7797
— нажмите 3
, чтобы сбросить шейдер в ShaderFixes.
Наша конечная цель здесь — изменить цвет желтого гео-эффекта, оставив другие части такими же.

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

Мы начинаем с того, что пробуем тот же метод, что и раньше — устанавливаем каждый из выходов в константы, чтобы понять, что они контролируют.
o0
, похоже, связано с контурами, делая их толще или тоньше (немного сложно разглядеть, но можно использоватьF9
, чтобы переключаться между модифицированным и оригинальным состоянием):

o1.xyz
, похоже, соответствует цветам RGB, как и раньше, а w
, похоже, контролирует яркость.


o2
, похоже, тоже контролирует цвет:

o3
-o5
не совсем ясны, но, похоже, они влияют на толщину линий.
Однако, возможно, вы заметили проблему — все эти параметры изменяют цвет всего столба, а не только желтой гео-линии! Нам нужно немного глубже покопаться, чтобы найти, где это обрабатывается.
- Прежде чем двигаться дальше, позвольте мне более подробно объяснить наиболее важные символы в шейдере:
v0
,v1
,v2
и т. д. — это входные данные в файлах vb, которые загружаются. Это данные, связанные с такими вещами, как позиция вершин, цвета вершин (отличаются от цветов текстур!), карты uv, веса смешивания и т. д.o0
,o1
,o2
и т. д. — это выходные цели, и это то, что фактически рисуется на экране (или в случае вершинных шейдеровVS
— что передается в пиксельный шейдерPS
).t0
,t1
,t2
и т. д. — это текстуры, обычно это текстуры в формате dds, хотя в некоторых случаях это могут быть и буферы. Когда вы видитеps-t0
,vs-t0
,ps-t1
,vs-t1
и т. д. в файлах .ini, это соответствует этим текстурам.r0
,r1
,r2
и т. д. — это регистры, временные переменные, которые шейдер использует для хранения результатов вычислений.cb0
,cb1
,cb2
и т. д. — это постоянные буферы, значения, передаваемые игрой в шейдер, которые представляют значения текущего состояния игры, такие как глобальное местоположение объектов или время, прошедшее с начала игры.
С учетом этого, мы можем сосредоточиться на части кода, которая нас интересует, вместо того чтобы пытаться понять все 200+ строк.
Нас интересует свечение столба Чжунли. Открыв текстуры для столба, мы видим, что диффузная текстура содержит светящийся элемент выше альфа-слоя и загружается в слот 0 (это первый хеш из hash.json для столба или, взглянув на созданный мод, увидим, что диффузная текстура загружена как ps-t0
):


Следовательно, нас интересует любая часть кода, связанная с переменной t0
, которая соответствует диффузной текстуре. Особенно нас интересует всё, что связано с компонентом w, так как именно он представляет светящуюся часть.
t0
загружается в шейдер дважды: первый раз примерно на строке 100 в переменнуюr2
:

И второй раз примерно на строке 235 в переменную r0
:

Есть способы понять, какой из них правильный, читая код, но экспериментирование с каждым из них также даст результат:
Устанавливаем r2.x
как константу в первом блоке:

Столб становится зелёным, но гео-эффект остаётся нормальным:

Поэтому, скорее всего, нужно посмотреть рядом со вторым блоком. Цвет, скорее всего, представлен переменной с 3 компонентами (по одной для каждого цветового канала), и ближайшей к этому блоку является r1
, которая появляется через 3 строки:

Если мы установим r1.x
в 1 здесь:

Мы получаем:

Успех! Это значение r1
контролирует RGB свечение столба (мы установили красный компонент в 0).
(Примечание: это не означает, что r1
всегда отвечает за цвет свечения столба во всём коде, просто оно держит свечение столба в этот конкретный момент времени. Значения регистров могут повторно использоваться шейдером при выполнении вычислений, поэтому "значение" того, что каждое из них представляет, может меняться от строки к строке, в отличие от входных и выходных данных).
Этот же основной принцип можно использовать в других ситуациях для нахождения того, какая часть шейдера контролирует тот или иной вывод – начинайте с компонента, который точно связан с тем, что вы ищете (например, с текстуры или с конкретного значения vb), затем исследуйте окружающий код шейдера и экспериментируйте, чтобы найти нужный результат.
- Однако один цвет — это скучно! А что если мы сможем установить цвет на любой, какой захотим? На самом деле это возможно, если передавать пользовательские значения из файлов
.ini
в шейдер.
Для начала, определим переменные, которые мы будем использовать, в верхней части файла под объявлениями 3dmigoto (я выбрал число 180 произвольно, однако для идеальной работы следует выбирать числа больше 100, чтобы они случайно не пересекались с числами, используемыми игрой):

Затем устанавливаем значения для R, G и B под строкой t0, которую мы нашли в предыдущей части:

(ПРИМЕЧАНИЕ: в r1
есть компоненты x
, z
и w
, а не x
, y
и z
. Тем не менее, они по-прежнему соответствуют RGB, просто буквы разные)
Наконец, в файле .ini
мы будем устанавливать эти три значения, когда увидим IB
для столба:

(Вы можете найти IB
для столба, используя клавиши 7
/8
на numpad, чтобы прокручивать до тех пор, пока не найдете тот, который заставит столб исчезнуть, или посмотрите в hash.json):

Успех! Мы установили линии в красный цвет:

И теперь можем менять их на другие цвета, просто изменяя значения в .ini
; вот, например, они становятся фиолетовыми:


Однако стоит отметить, что это не идеально — мы потеряли часть анимационных эффектов в обмен на пользовательские цвета. Я расскажу, как реализовать анимацию в заключительной части этого руководства.
- Мы можем сделать еще больше с этим. Один цвет — это здорово, но что если мы могли бы автоматически переключаться между ними? В 3dmigoto есть специальная переменная под названием
time
, которая представляет количество секунд, прошедших с момента запуска игры. Мы можем использовать ее для автоматической смены цветов с течением времени:
[TextureOverridePillarIB]
hash = 34e18b4f
if time % 3 <= 1
x180 = 1
y180 = 0
z180 = 0
else if time % 3 <= 2
x180 = 0
y180 = 1
z180 = 0
else
x180 = 0
y180 = 0
z180 = 1
endif
Что делает этот код: он берет текущее время и делит его на 3 диапазона, а затем устанавливает цвет столба в красный, зеленый или синий в зависимости от времени (цикл каждый 3 секунды). Изменив числа, можно настроить скорость цикла или добавить/удалить цвета и т.д. 
- Наконец, как и раньше, мы можем загрузить шейдер в
.ini
вместо того, чтобы помещать его в shaderfixes:
[TextureOverridePillarIB]
hash = 34e18b4f
run = CustomShaderPillarColor
[CustomShaderPillarColor]
if time % 3 <= 1
x180 = 1
y180 = 0
z180 = 0
else if time % 3 <= 2
x180 = 0
y180 = 1
z180 = 0
else
x180 = 0
y180 = 0
z180 = 1
endif
ps = 4c99fec14dca7797-ps_replace.txt
handling = skip
drawindexed = auto
Это должно работать в основном, но есть небольшой глюк в компиляции, который вызовет небольшое остаточное изображение столба на ~1 секунду после его исчезновения:

Также возможно ограничить использование этого шейдера только для момента, когда Джонгли находится на поле, хотя в этом случае я не знаю других объектов, которые используют этот шейдер, поэтому это не так важно, как было для огня Дилюка.
Анимрованные эффекты
В этом заключительном разделе я продемонстрирую, как мы можем использовать принципы из предыдущих двух разделов для создания простых анимационных эффектов. Я буду описывать процесс создания анимированных линий на кибер-костюме Райден. Этот раздел является более сложным – я предполагаю, что вы уже понимаете предыдущие два раздела, умеете создавать моды и имеете базовые знания в программировании.
- Для начала, находим шейдер, который управляет рисованием текстур на Райден Шогун:

На самом деле, Райден использует как минимум два шейдера – один для тела и один для платья. Однако нас интересует шейдер для тела, так как именно в нем находится эмиссионный эффект, который нам нужен (выявлено методом проб и ошибок).
Хэш этого шейдера: 7d2763cf91813333
, и мы сохраняем его в ShaderFixes.
- Далее, ищем часть шейдера, которая отвечает за эмиссию. Эмиссия находится выше альфа-слоя на диффузной текстуре, которая используется в слоте 0. Мы ищем что-то, связанное с
t0.w
. В шейдере есть только одна подходящая строка:

После тестирования мы выясняем, что это строка отвечает за свечение:


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


- Теперь я продемонстрирую процесс добавления линий в мой мод с кибер-костюмом Райден:

Сначала я рисую линии:

Я сделал это через вкладку текстурной живописи в Blender, но вы также можете рисовать прямо на текстуре, используя paint.net или Photoshop. Обратите внимание, что итоговый файл должен быть BC7 SRGB
формат .dds
для диффузной текстуры. И не будьте как я – рисуйте эти линии на отдельном слое, чтобы позже их было легко разделить ;-;.
- Финальная текстура выглядит так после того, как я переместил линии на альфа-слой (заметьте, что текстура широкая, потому что я объединил несколько моделей и расположил их текстуры рядом):

Это дает нам светящиеся линии в игре:

- Теперь время реализовать некоторые базовые анимации. Я отделяю линии от диффузной текстуры на другую пустую текстуру, которую я буду называть «контрольной» текстурой:

Эта текстура будет использоваться для того, чтобы сообщить шейдеру, какие части текстуры будут иметь анимационные эффекты (поскольку все четыре канала диффузной/лайтмап текстуры уже используются). Тип этой текстуры должен быть BC7 Linear
, так как мы хотим, чтобы цветовые значения распределялись равномерно.
Я также перекрасил ее в черный цвет для простоты — мы не будем использовать специфические цвета в этом примере, чтобы упростить процесс, поэтому мы устанавливаем все цветовые каналы равными. Если захотите, вы можете использовать каждый цветовой канал для управления различными аспектами. Главное — убедитесь, что цвет больше 0, так как мы должны отличать его от фона, не полагаясь на альфа-канал.
Обратите внимание, что я удалил линии из исходной диффузной текстуры, поэтому мы вернулись к ванильному кибер-костюму:

- Далее, добавляем раздел в
BodyOverride
в.ini
модификации, чтобы передать новую текстуру шейдеру:
[TextureOverrideRaidenShogunBody]
hash = 428c56cd
match_first_index = 17769
ib = ResourceRaidenShogunBodyIBZipped
ps-t0 = ResourceRaidenShogunBodyDiffuseRed
ps-t1 = ResourceRaidenShogunBodyLightMap
ps-t26 = ResourceRaidenShogunBodyControl
[ResourceRaidenShogunBodyControl]
filename = RaidenShogunBodyControl.dds
Я выбрал загрузить ее в слот 26 произвольно — не рекомендую использовать номера слотов меньше 20, так как я встречал случаи, когда они могут достигать таких высоких значений (большинство вещей используют <10, и редко что-то важное находится выше 5).
- Нам также нужно добавить переменную в шейдер в верхней части:

Теперь мы можем загрузить эту текстуру так же, как и другие текстуры:
r2.xyzw = t26.SampleBias(s0_s, v2.xy, r0.x).xyzw;
(Если вам интересно, как появилась эта строка, то мы просто посмотрели, как загружаются текстуры t0
и t1
, и имитировали формат. Я выбрал r2
, так как знаю, что он будет заменен на то, что мы загружаем из диффузной текстуры, так что это не нарушит другие строки кода — другой вариант — создать дополнительную переменную регистра).
- Теперь мы можем добавить условие, которое будет срабатывать только на пикселях контрольной текстуры, где значение красного канала больше 0. Когда это происходит, мы устанавливаем цвет пикселя в зеленый; иначе, мы просто загружаем значение пикселя из исходной диффузной текстуры:
r2.xyzw = t26.SampleBias(s0_s, v2.xy, r0.x).xyzw;
if (r2.x > 0){
r2.xyz = float3(0,1,0);
r2.w = 0.6;
}
else{
r2.xyzw = t0.SampleBias(s0_s, v2.xy, r0.x).xyzw;
}
(Примечание: Я немного ленюсь, устанавливая значения здесь, так как они должны быть нормализованы, но это не окажет значительного влияния).
И это возвращает нас к исходной точке:

Однако теперь есть одно ключевое отличие — цвета и расположение линий полностью контролируются через контрольную текстуру и вычисления шейдера, а не считываются из исходной текстуры.
Теперь мы можем легко изменить цвет, просто изменив значение r2.xyz = float3(R,G,B)
:


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

Теперь значение r2.x
будет линейно увеличиваться от 0 до 1 по мере продвижения вниз по линиям (именно поэтому мы сохранили текстуру в формате BC7 linear
— в противном случае значения были бы искажены, что вызвало бы проблемы). Затем мы можем передать переменную времени из .ini
в шейдер:
[TextureOverrideRaidenShogunBody]
hash = 428c56cd
match_first_index = 17769
ib = ResourceRaidenShogunBodyIBZipped
ps-t0 = ResourceRaidenShogunBodyDiffuseRed
ps-t1 = ResourceRaidenShogunBodyLightMap
ps-t26 = ResourceRaidenShogunBodyControl
x180 = time
Определяем новую переменную в шейдере:
#define TIME IniParams[180].x
Теперь мы можем сравнить значение r2.x
с TIME
, чтобы понять, какую часть модели мы хотим нарисовать. Поскольку r2.x
находится в диапазоне от 0 до 1, мы также должны привести TIME
к этому диапазону — для этого мы можем разделить TIME
на повторяющиеся интервалы с использованием оператора модуля, а затем разделить на максимальное значение, чтобы привести его к диапазону от 0 до 1. Таким образом, уравнение будет TIME%2/2
, чтобы он циклировал между 0 и 1 каждые два секунды.
if (r2.x > TIME%2/2){
r2.xyz = float3(0,1,0);
r2.w = 0.6;
}
else{
r2.xyzw = t0.SampleBias(s0_s, v2.xy, r0.x).xyzw;
}
Результат:
В качестве альтернативы, чтобы изменить направление, мы можем использовать 1 - TIME%2/2
вместо:
if (r2.x > 1-TIME%2/2){
r2.xyz = float3(0,1,0);
r2.w = 0.6;
}
else{
r2.xyzw = t0.SampleBias(s0_s, v2.xy, r0.x).xyzw;
}
- Результат неплохой, но он не совсем соответствует тому, что я хотел — мне не нравится, как линии постепенно появляются/исчезают, и я ожидал более "матрицеподобный" эффект, где линия перемещается по телу.
Вместо использования одного условия, мы можем определить диапазон, в котором линии будут появляться. Это будет разрешать значения, которые отклоняются от TIME%2/2
не более чем на 0.2:
r2.xyzw = t26.SampleBias(s0_s, v2.xy, r0.x).xyzw;
if (r2.x > TIME%2/2 && r2.x < TIME%2/2+0.2){
r2.xyz = float3(0,1,0);
r2.w = 0.6;
}
else{
r2.xyzw = t0.SampleBias(s0_s, v2.xy, r0.x).xyzw;
}
Гораздо лучше, но движение все еще немного быстрое. Также линии все равно появляются одновременно в начале цикла, что делает начальные и конечные точки очевидными. Окончательное уравнение, на котором я остановился:
if (r2.x > 0 && (TIME % 3)/2.5 > r2.x && (TIME % 3)/2.5-0.2 < r2.x){
r2.xyz = float3(0,1,0);
r2.w = 0.6;
}
else{
r2.xyzw = t0.SampleBias(s0_s, v2.xy, r0.x).xyzw;
}
Этот цикл повторяется каждые 3 секунды, и мы фактически помещаем время в диапазон от 0 до 1.2 вместо от 0 до 1, разделив на 2.5 вместо 3. Дополнительные 0.2 позволяют линиям постепенно появляться и исчезать в конце цикла.
- Теперь добавим ещё крутых эффектов. Мы используем постоянный зелёный цвет, но это не обязательно — мы можем использовать математику, чтобы сделать цвета цикличными. Объяснение, как это работает, выходит за рамки этого урока, но в общем, мы используем не синхронизированные синусоиды, чтобы перемещаться по цветовому кругу. Для подробностей см. здесь: https://krazydad.com/tutorials/makecolors.php
if (r2.x > 0 && (TIME % 3)/2.5 > r2.x && (TIME % 3)/2.5-0.2 < r2.x){
r2.xyz = float3((sin(TIME)+1)/2, (sin(TIME+2)+1)/2, (sin(TIME+4)+1)/2);
r2.w = 0.6;
}
else{
r2.xyzw = t0.SampleBias(s0_s, v2.xy, r0.x).xyzw;
}
- На этом этапе я в основном закончил объяснение того, как создать эффект. Сам мод для кибер-Райдена также имеет дополнительные переключатели, чтобы включать и выключать эффекты, ограничивать их показ, пока Райден на экране, и позволять пользователю выбирать собственные цвета, но все эти моменты были уже рассмотрены в предыдущих разделах.
Единственное, что стоит отметить, это то, что вы не ограничены только использованием цветов с этой техникой — вместо того, чтобы устанавливать r2
как постоянный цвет, вы можете использовать это для выбора между различными текстурами. Вы также можете использовать отдельные каналы текстуры управления для разных эффектов или использовать разные переменные для переключателей — возможности безграничны! (Ну, не совсем, но всё равно можно сделать многое!)
- Хотя мы в основном закончили, я хочу обратить внимание на некоторые проблемы:
- Линии не появляются примерно 1-2 секунды после смены персонажа. Это потому, что персонажи на самом деле используют другой шейдер, когда загружаются в игру в течение нескольких секунд — можно найти этот шейдер и заменить его, чтобы устранить эту проблему.
- Отражения не имеют линий. Это также из-за того, что отражения используют другой шейдер, и это можно исправить, найдя и заменив шейдер для отражений.

- Применяется фильтр прозрачности. Это не ошибка, но это значит, что у кого-то, кто использует мод для удаления фильтра прозрачности, этот фильтр будет перезаписан для Райден. Чтобы исправить это, сделайте дифф в файле шейдера с файлом из мода для удаления фильтра прозрачности, чтобы увидеть, в чём различия, и примените их к своему файлу.

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

Радужный цветовой эффект крут, но не является 100% математически правильным — текстура диффузного освещения использует цветовое пространство
SRGB
, а неlinear
, что означает, что вам нужно будет сделать дополнительный шаг для преобразования цветов (вы можете заметить, что линии никогда не становятся полностью красными/зелёными/синими, как ожидалось). Смотрите что-то вроде https://lettier.github.io/3d-game-shaders-for-beginners/gamma-correction.html для подробностей.Хэши шейдеров могут изменяться между версиями, и это происходит гораздо чаще, чем изменение хэшей персонажей, поэтому вам, возможно, придётся обновлять моды для эффектов чаще, чем моды для персонажей.
Если вы дошли до этого момента, поздравляю! Вы знаете основную часть того, как можно использовать шейдеры для изменения эффектов или даже создания собственных. Спасибо за чтение, и я с нетерпением жду того, что вы создадите!