原神着色器效果修改实战:3DMigoto GIMI 通道运算、条件限制与动态特效全解析
原神着色器效果修改实战:3DMigoto GIMI 通道运算、条件限制与动态特效全解析
本指南围绕 GI-Model-Importer(GIMI)的 3DMigoto 版本,系统讲解制作着色器(shader)效果修改所需的进阶技巧:从直接改写输出通道颜色、RGB 通道交换与数学运算,到用 INI 文件把效果精确限制在指定角色,再到利用时间变量制作脉冲、彩虹与 HSV 色相循环等动态特效,最后延伸到对角色模型发光部件的着色器级修改。读完本文,你将能独立定位任意特效对应的像素着色器哈希,写出可复用、可交付的着色器效果 Mod。
本文是 Guides/EffectModdingTutorial.md 的进阶篇,建议先掌握其中的着色器转储(dump)、文本编辑与 INI 控制基础。原文演示基于游戏 3.5 版本,不同游戏版本间着色器哈希可能改变,实战时请以自己游戏中转储到的哈希为准。
前置准备:让定位与暂停事半功倍
制作效果 Mod 的完整流程是:触发技能/爆发 → 在画面中定位并转储对应像素着色器 → 编辑 hash-ps_replace.txt → 用 INI 控制生效条件 → F10 重载验证。其中反复定位哈希是耗时大头,本文推荐先做三件事:
- 使用开发版 GIMI:按 README.md 的说明安装
3dmigoto-GIMI-for-development.zip,只有开发版才会显示顶部/底部的绿色文字,并具备转储与狩猎所需的完整功能。 - 确保着色器可转储:GIMI 默认禁用了着色器转储功能(转储出的着色器会干扰 Mod 加载)。需要编辑
d3dx.ini,保证marking_actions一行中同时包含hlsl和asm两个关键词;转储出的文件存放在ShaderFixes文件夹,若排障时发现某些 Mod 异常,可尝试清空该文件夹。 - 善用暂停手段:技能与爆发需要反复释放,冷却与能量是阻碍——可以用私服绕过(官方服有封号风险,仓库 README 同样强调仅建议在私服进行 Modding)。暂停方面,游戏内暂停菜单比较粗糙,更推荐留影机(Kamera)道具 + Remove UI 类 Mod:在想要定格的一瞬间用留影机"定格"并隐藏 UI;对于无法暂停的元素爆发,可以面向墙壁或树站立,这会阻止爆发镜头(burst cam)播放,从而获得暂停时间。同时建议选择较暗的场景,特效会更容易观察。
直接设置输出通道值:最朴素但最"平"的做法
这是最基础的方法:直接把输出通道设成常量,让整个特效变成单一均匀颜色。缺点是细节会全部丢失,效果显得扁平、无特征,因此除非必要否则不推荐。以北斗的技能为例,假设已找到外层尖刺电效的着色器,用下面三行即可将其改成橙色:
o0.x = 1;
o0.y = 0.5;
o0.z = 0;
在 Guides/EffectModdingTutorial.md 的基础篇中已确认:像素着色器的 o0 是 float4 类型输出,其 x/y/z 分量对应 RGB 颜色,w 分量与效果的宽度/自发光(emission)有关。不过并非所有着色器都用 o0 承载颜色——复杂着色器可能有 o1、o2 等多个输出目标,需要逐个置常量来实验确认。
输出通道的基础数学运算
直接设常量会丢失纹理细节,而保留原值做数学运算则能在改色的同时保住大部分层次。例如:
o0.x = o0.x * 0.1;
把紫色效果中的红色分量压到原来的 10%,蓝色通道随即占据主导,得到蓝色版本。紫色中绿色分量本就较低,把它放大 5 倍则整体向白色靠拢:
o0.y = o0.y * 5;
单独使用该方法的潜力有限,但它是后续通道交换、动态特效等技巧的基础构件,价值在于组合。这种"乘系数"思路与基础篇中"通过缩放通道比例制造鲑鱼粉"的色调偏移示例一脉相承:不强制覆盖常量,火焰纹理依然可见,颜色却整体迁移。
通道交换与赋值:无损改色的核心手段
颜色由 R、G、B 三个通道构成,特效任意像素上各通道取值不同。例如迪卢克元素战技的火焰特效中,粉圈区域三通道都很高,绿圈区域则红高、蓝绿低。想让整体颜色改变、同时保持亮部仍为白色、观感更自然,通道交换是最省力的方法。
在弧形攻击轨迹与火焰云两个着色器上交换红、蓝通道:
o0.xz = o0.zx;
原理:橙色含大量红和少量绿蓝,把高红值赋给蓝通道、低蓝值赋给红通道,就能无细节损失地得到蓝色特效。当目标颜色无法靠两两交换一步到位时,可以用复制赋值。例如做粉色:保留红的原值,同时把它复制给蓝通道:
o0.z = o0.x;
还可以做反色(invert)。这会同时翻转亮度——亮部变暗、暗部变亮:
o0.xyz = float3(1,1,1) - o0.xyz;
对枫原万叶元素战技的六个着色器、香菱旋火轮的五个着色器分别套用反色,即可得到对应的红色与暗色版本。作者特别提醒:反色单独用效果一般,必须与其它技巧组合。这一节的价值在于建立了"用通道向量表达颜色变换"的思维模型,后续所有动态特效都建立在这个基础上。
把效果限制到指定角色:INI 状态机与控制条件
不做限制时,着色器修改会对所有共用该哈希的对象生效——把迪卢克的火焰改黑,胡桃、可莉的火焰同样变黑,还会与其它作者的同类 Mod 冲突。解决方案与 Guides/EffectModdingTutorial.md 中"识别角色在场条件、仅在该条件成立时应用效果"的原则一致:用角色唯一的 VB 哈希作为"角色在场"信号,用 [Present] 段在每帧开始时重置标志。
特殊情形:有些角色在部分动画中会短暂"消失"(例如雷电将军第四段普攻),此时 VB 哈希匹配不到,标志会误复位。解决办法是改用元素战技图标哈希——角色在场上时图标必然存在;滑翔、下坠攻击等图标消失的瞬间再用角色 VB 哈希兜底,任一条件满足即生效。技能图标哈希的定位方法见 Guides/TextureModdingTutorial.md。
完整的 INI 示例如下:
[Constants]
global $active
global $icon
[Present]
post $active = 0
post $icon = 0
run = CommandListCharacterControl
[TextureOverrideCharacterPosition]
hash = put_character_vb_hash_here
match_priority = 1
$active = 1
[TextureOverrideSkillE]
hash = put_character_skill_icon_hash_here
$icon = 1
; CommandList -------------------------
[CommandListCharacterControl]
if $active == 1 || $icon == 1
x160 = 1
else if $active == 0 && $effect == 0
x160 = 0
endif
要点:
[Constants]声明全局变量,[Present]每帧开头执行一次,负责把标志复位、并驱动命令列表;$active由角色 VB 哈希置 1,$icon由技能图标哈希置 1,二者是"或"关系;match_priority用于避免干扰已加载的角色 Mod(作为独立效果而非 Mod 一部分时,需要保留它);x160是把控制值写进着色器常量缓冲槽位 160 的写法——着色器内可通过#define XX IniParams[160].x读取,效果只在该标志为 1 时执行;- 如果你希望修改应用到其它角色,可以跳过本节的限制部分。
若不想为每个着色器单独改代码,Guides/EffectModdingTutorial.md 还提供另一种隔离方案:用 [ShaderOverride] 配合 if $ActiveCharacter == 1 条件式调用自定义 [CustomShader](ps 指向转储文件、handling = skip、drawindexed = auto),优点是着色器可随 Mod 打包、不干扰其它 Mod,缺点是 3dmigoto 偶尔会编译失败且不支持 asm 着色器。
同一角色内的局部限制:用颜色特征做条件化
跨角色"防串染"相对简单,更难的是同一角色上防止误伤。没有固定套路,需要自己找出区分着色器内部不同部分的方法。经典案例是刻晴附魔后的普攻为紫色,普通攻击则偏淡黄:
o0.xyz = o0.yzx;
把紫换绿很简单,但附魔与未附魔共用同一个着色器——附魔结束(或未附魔时)淡黄攻击也会被改成浅粉。解决办法是利用黄色与紫色在蓝绿通道上的大小关系:黄色中蓝通道小于绿通道,紫色恰好相反。因此把修改包进条件分支,只对紫色像素生效:
if (o0.z > o0.y) {
o0.xyz = o0.yzx;
}
这样附魔攻击变绿、普通攻击保持原样。这种"利用颜色通道相对大小做条件判断"的技巧在多数场景可用,但并非万能,其它场景需要另行设计判别条件。
动态颜色效果:把时间变量注入着色器
以上都是静态改色(新颜色恒定不变)。要随时间变化,首先要理解一个前提:着色器本身没有时间概念,必须通过 INI 把类时间值传进去。下面这个 INI 让 step 变量每帧自增 1,并写入常量槽 160:
[Constants]
global $step
[Present]
post $step = $step + 1
run = CommandListShaderControl
[CommandListShaderControl]
x160 = $step
着色器顶部声明:
#define STEP IniParams[160].x
关于常量槽位编号,Guides/EffectModdingTutorial.md 建议选择 100 以上的编号,避免与游戏自身使用的槽位冲突;3dmigoto 还内置了 time 特殊变量(游戏启动后经过的秒数),同样可以直接写入着色器常量。
脉冲效果:用正弦函数驱动亮度
做周期性上下波动的效果,最方便的是正弦函数 sin。注意其取值范围是 [-1, 1],而颜色只能取 [0, 1],需要先加 1 平移到 [0, 2],再乘 0.5 归一化到 [0, 1]。于是脉冲效果为:
o0.xyz = o0.xyz * (sin(STEP)+1)*0.5;
如果觉得闪烁太快,就放慢进入正弦的输入值:STEP*0.1 等价于 10 倍减速:
o0.xyz = o0.xyz * (sin(STEP*0.1)+1)*0.5;
彩虹效果:让各通道以不同频率脉冲
核心思路是让 R/G/B 三通道以不同速率脉冲,叠加出不断变化的颜色。为了保住特效原有的明暗细节,先提取灰度值——公式取自 RGB 转灰度的标准加权,权重对应人眼对三种颜色的敏感度:
float gs = 0.30*o0.x + 0.59*o0.y + 0.11*o0.z;
然后分别驱动三个通道,注意倍率 2 与 4:
o0.x = gs * (sin(STEP*0.1)+1)*0.5;
o0.y = gs * (sin(STEP*0.1*2)+1)*0.5;
o0.z = gs * (sin(STEP*0.1*4)+1)*0.5;
改变这些倍率会得到略有不同的结果,但变化不一定明显。这里有必要说明一个理论边界:颜色是三分量系统(RGB 或 HSV 都是),而时间只有一个分量,因此仅以时间为自变量无法覆盖全部可能的颜色取值——别指望这种随时间变化的效果能遍历所有颜色。
HSV 色相循环:经典的"游戏外设 RGB"灯效
想要颜色始终鲜艳饱满,就要在保持饱和度和明度最高的前提下循环色相(Hue),也就是 HSV 模型中 S=1、V=1、H 从 0 走到 360。把 H 对 360 取模,即可沿色轮边界取遍所有纯色。HSV→RGB 换算公式可参考公开的 HSL 与 HSV 标准文档(Wikipedia 相关条目),下面是可直接使用的 HLSL 实现,作者建议放在 void main 上方作为独立函数:
float3 getColor() {
float h = STEP % 360;
float h1 = h/60;
float x = 1 - abs(h1 % 2 - 1);
float3 col1 = float3(0,0,0);
if (h1 < 1) {
col1 = float3(1, x, 0);}
else if (h1 < 2) {
col1 = float3(x, 1, 0);}
else if (h1 < 3) {
col1 = float3(0, 1, x);}
else if (h1 < 4) {
col1 = float3(0, x, 1);}
else if (h1 < 5) {
col1 = float3(x, 0, 1);}
else {
col1 = float3(1, 0, x);}
return col1;
}
最后把色相色调乘上前面提取的灰度,保留特效明暗结构的同时获得炫彩灯效:
float gs = 0.30*o0.x + 0.59*o0.y + 0.11*o0.z;
float3 tint = getColor();
o0.xyz = tint * gs;
用内置 time 变量做颜色分桶切换
如果只需要在几种固定颜色间切换,Guides/EffectModdingTutorial.md 给出了更轻量的方案:利用 3dmigoto 内置 time 变量配合取模,把时间放入 3 个"桶",每 3 秒在红绿蓝间循环(改数字可调快慢、增删颜色):
[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
也可以把着色器直接收进 Mod 的 INI([CustomShader] + ps 指向转储文件 + handling = skip),无需依赖 ShaderFixes 目录,但要注意这种方式偶尔存在编译残留问题(钟离柱子在消失后约 1 秒内留下残影)。
着色器修改应用于模型纹理:定位部件与操作发光
游戏中所有可见内容都由着色器绘制,因此模型部件同样可以修改——只是这些着色器比特效着色器更复杂。角色模型通常由多个组件构成(例如旅行者(空/荧)模型由头、身体、裙子三个组件组成),每个组件哈希不同、着色器不同、行为也不同(有的发光脉动、有的恒定、有的发光颜色固定),需要通过观察来逐一确认。
第一步:确认要改的输出目标
以旅行者身体组件为例,其着色器转储后可见多个输出:
...
o1.xyz = r2.xyz;
o2.xyw = r0.xyw;
o3.x = 0.0156862754;
o4.x = r0.w;
o5.x = 0;
return;
它们分管光照、阴影等不同外观。多数场景只需处理 o1,但要先验证:把 o1.xyz 置 0 进游戏观察,确认它就是目标输出。随后用实验法隔离出发光相关部分——o1.xyz = o4.xxx; 会让发光区域显示为白色、其余部分为黑色。既然发光由 o1.w 决定,就可以条件式放大发光,例如提高 60%:
if(o4.x>0.1) {
o1.w = o1.w * 1.6;
}
第二步:理解旅行者的元素染色机制
旅行者身体组件有个独有行为:发光部分会随队伍中旅行者的元素染上对应颜色。元素色信息由游戏通过常量缓冲传入,通常能在 cb0 中找到类似下面这行:
r1.xzw = cb0[37].xyz * r2.xyz;
cb0[37] 的具体下标会随游戏版本变化,需要先验证它确实存放元素色——在 return 前插入:
o1.xyz = cb0[37].xyz;
验证通过后即可替换为自己想要的染色。例如改成品红色:
float3 tint = float3(1,0,1);
r1.xzw = tint * r2.xyz;
r1、r2 是最终形成输出的中间寄存器,具体职责不必深究。也可以直接删掉原行中的 cb0[37] 乘法项来取消染色,让发光部分呈现纹理原始颜色——做旅行者发光部件且不希望被元素色干扰时非常有用。除旅行者外,多数角色直接从纹理取色、不加染色;但部分武器(如磐岩结绿、黑剑)的发光部位带有固定色调,本节方法同样适用。
延伸方向:仓库中的相关辅助脚本可以配套使用——例如 Tools/genshin_set_color.py 可强制改写 Texcoord 缓冲的 COLOR 分量(python genshin_set_color.py -n CharTexcoord.buf --stride X -a 0 常用于去除角色描边),Tools/genshin_set_outlines.py 用 --thickness 0~255 控制描边粗细(多数官方模型在 80~130 之间)。这些工具与着色器修改共同构成完整的效果/外观定制链路,详见 Guides/Troubleshooting.md 中关于异常描边的处理建议。
版本兼容性与实操注意事项
- 哈希随版本漂移:着色器哈希在不同游戏补丁间可能变化,且比角色哈希变化更频繁,因此效果 Mod 通常需要比角色 Mod 更勤地更新。教程中的哈希(如北斗、迪卢克、钟离柱子的示例值)仅作演示参考。
- 转储副作用:转储出的着色器可能干扰某些 Mod 加载,遇到异常时优先清空
ShaderFixes文件夹排查。 - 色彩空间:diffuse 纹理使用 SRGB 而非线性颜色空间,彩虹/HSV 类的数学配色严格来说不够精确(颜色不会如预期那样完全纯红/纯绿/纯蓝),如需严谨结果需额外做 Gamma 校正步骤。
- 动效取舍:用常量色替代原有时,部分动画化效果会丢失,动态技巧(正弦、time 取模)可以部分弥补。
- 方法论:定位着色器"哪个寄存器管什么"没有捷径——从与目标强相关的已知组件(某张纹理
t0、某个 VB 值、某条t0.w采样行)出发,搜索周围代码并逐个实验。寄存器是临时变量、会被复用,同一寄存器在不同行代表的意义可能完全不同,这与输入输出变量的语义稳定性不同。
结语
本文覆盖了从静态改色(通道常量、通道运算、通道交换与反色)、条件限制(VB/技能图标哈希 + 状态变量 + 命令列表、同角色内颜色条件分支)到动态特效(step/time 注入、正弦脉冲、灰度提取、HSV 色相循环、颜色分桶)再到模型部件级着色器修改(发光隔离、cb0 元素染色)的完整技术栈。着色器修改的可能性远不止于此——例如可以把控制纹理各通道用于不同用途、在不同纹理间切换、用不同变量做开关——建议在掌握本文方法后自行实验探索。基础概念与哈希定位流程如有疑问,可回读 Guides/EffectModdingTutorial.md 与 Guides/UsageInstructions.md。