Unity Shader颜色控制:从片元着色器到动态调色实战

发布时间:2026/8/6 2:50:58

Unity Shader颜色控制:从片元着色器到动态调色实战
1. 项目概述从“黑盒”到“画笔”刚接触Unity Shader那会儿总觉得它像个神秘的黑盒尤其是看到别人用几行代码就能让材质流光溢彩自己却连基本的颜色都调不明白。今天这个案例就是帮你撬开这个黑盒的第一道缝。我们不做复杂的特效就干一件事用Shader脚本直接干预材质的最终输出颜色。听起来简单但这恰恰是理解Shader工作原理的基石。无论是后面想实现角色换肤、环境色调变化还是更高级的动态效果都得先过了“控制颜色”这一关。这个案例适合所有已经了解Shader基本结构比如知道顶点着色器和片元着色器是干嘛的但还不知如何下手的开发者。我们将从一个最简单的着色器开始逐步添加颜色控制逻辑并拆解其中每一行代码的意义。你会发现Shader不是魔法它只是一支更底层的“画笔”让你能直接在GPU上“作画”。2. 案例核心思路拆解数据流与干预点在动手写代码之前我们必须想清楚颜色信息在Shader里是怎么流动的我们能在哪个环节“截住”它并加以修改2.1 理解Shader的渲染管线简化版可以把一个物体的渲染过程想象成一条流水线顶点阶段CPU将模型的顶点数据位置、法线、UV等传递给顶点着色器。顶点着色器它的核心任务是坐标变换。将每个顶点的位置从模型空间转换到裁剪空间以便后续GPU进行光栅化。在这个阶段我们也可以对顶点数据进行一些预处理比如计算一些随时间变化的顶点偏移模拟旗帜飘动但通常不在这里直接计算最终颜色。光栅化GPU将处理后的三角形转换为屏幕上的一个个像素片段。片元着色器这是我们的主战场。每个像素片段都会执行一次片元着色器中的代码。在这里我们可以获取到该像素的纹理颜色、光照信息如果涉及光照计算、以及从顶点着色器传递过来的各种插值数据并最终输出一个颜色值RGBA。我们要做的颜色干预99%发生在这个阶段。输出合并将片元着色器输出的颜色与帧缓冲区中已有的颜色进行混合例如处理透明度。所以我们的核心思路非常明确在片元着色器中获取到材质原本应该显示的颜色可能是纯色也可能是纹理采样结果然后通过某种数学运算或逻辑修改这个颜色值最后输出。2.2 干预策略从覆写到调制干预颜色不是简单地替换根据需求不同主要有两种策略覆写完全不管材质原本的颜色直接输出我们指定的新颜色。比如实现一个纯红色的警示框。调制在材质原有颜色的基础上进行调整。这是更常用、更灵活的方式。例如乘法newColor originalColor * tintColor。tintColor为(0.5, 1.0, 1.0)时会减弱红色分量保持绿和蓝常用于整体色调调整。加法newColor originalColor addColor。用于提亮或添加光晕效果。混合newColor lerp(originalColor, targetColor, blendFactor)。根据一个混合因子在原始颜色和目标颜色之间平滑过渡常用于变色、溶解效果。本案例我们将重点实践乘法调制因为它最直观也最符合“干预”而非“替换”的初衷。3. 基础着色器搭建与颜色属性曝光理论清晰了我们开始动手。首先在Unity中创建一个最简单的Unlit Shader作为我们的画板。3.1 创建Unlit Shader模板在Project窗口右键 - Create - Shader - Unlit Shader。将其命名为SimpleColorIntervention。Unity会生成一个基础的无光照着色器它已经包含了最基本的顶点/片元着色器结构和纹理采样功能是我们理想的起点。双击打开这个Shader文件你会看到一堆代码。我们先聚焦在Properties块和片元着色器。3.2 定义可调节的颜色属性在Shader的Properties块中我们定义可以在材质Inspector面板上调节的参数。我们在默认的_MainTex后面添加一个颜色属性。Properties { _MainTex (Texture, 2D) white {} // 新增一个用于颜色干预的色调属性 _ColorTint (Color Tint, Color) (1,1,1,1) }_ColorTint这是我们给属性起的变量名在Shader内部代码中会用到。(Color Tint, Color)括号内第一部分Color Tint是显示在材质面板上的友好名称Color指定了该属性的类型为颜色。 (1,1,1,1)这是默认值对应RGBA(1,1,1,1)表示纯白且不透明。设置为白色意味着默认情况下不会对原有颜色产生任何影响因为任何颜色乘以白色等于它本身。3.3 在CGPROGRAM中声明对应变量在SubShader内部的CGPROGRAM和ENDCG之间我们需要再次声明在Properties中定义的、需要在着色器程序中使用的变量。这相当于C#中的变量声明。CGPROGRAM #pragma vertex vert #pragma fragment frag #include UnityCG.cginc // 声明与Properties块对应的变量 sampler2D _MainTex; float4 _MainTex_ST; // 纹理的缩放偏移值用于支持材质面板的Tiling/Offset float4 _ColorTint; // 声明我们的颜色干预变量 struct appdata { float4 vertex : POSITION; float2 uv : TEXCOORD0; }; struct v2f { float2 uv : TEXCOORD0; float4 vertex : SV_POSITION; }; v2f vert (appdata v) { v2f o; o.vertex UnityObjectToClipPos(v.vertex); o.uv TRANSFORM_TEX(v.uv, _MainTex); return o; }注意_MainTex_ST是Unity为每个sampler2D自动提供的配套变量ST代表Scale和Translation用于处理纹理的平铺和偏移。我们通过TRANSFORM_TEX宏来应用它。3.4 在片元着色器中实施干预现在来到最关键的一步修改片元着色器frag函数。fixed4 frag (v2f i) : SV_Target { // 1. 采样主纹理获取该像素点原本的颜色 fixed4 col tex2D(_MainTex, i.uv); // 2. 实施颜色干预将采样得到的颜色与我们定义的色调相乘 col * _ColorTint; // 3. 输出最终颜色 return col; } ENDCG代码解读fixed4 col tex2D(_MainTex, i.uv);tex2D是CG/HLSL中的纹理采样函数。它根据输入的UV坐标i.uv从纹理_MainTex中取出对应的颜色值存储为fixed4类型一种精度较低但效率较高的四分量向量适合颜色计算。col * _ColorTint;这就是我们的干预核心。*是乘法赋值运算符等同于col col * _ColorTint;。每个像素的原始颜色col的RGB三个通道会分别与_ColorTint的RGB通道相乘。如果_ColorTint是(1.0, 0.5, 0.5, 1.0)那么结果就是红色通道不变绿色和蓝色通道减半整体会偏向红色。如果_ColorTint是(0.0, 1.0, 1.0, 1.0)那么红色通道乘以0后为0绿色和蓝色保留最终得到青色。return col;将计算后的颜色输出给渲染管线。注意这里使用的是乘法调制。如果你想尝试“覆写”可以直接return _ColorTint;这样会完全忽略纹理。但通常我们更常用乘法因为它能保留纹理的细节。4. 在Unity中测试与验证理论和大纲都有了是时候看到实际效果了。4.1 创建材质并应用Shader在Project窗口右键 - Create - Material创建一个新材质球。将材质球的Shader选择为我们刚修改的SimpleColorIntervention或者你自定义的名字。你会立刻在材质Inspector面板上看到两个属性“Texture”和“Color Tint”。给“Texture”属性拖拽一张图片比如一个砖墙纹理。点击“Color Tint”旁边的色块会弹出颜色选择器。4.2 进行颜色干预测试现在你可以通过调节“Color Tint”来实时观察效果将颜色选为红色如RGB: 1, 0, 0你会发现整个纹理变成了黑红色调因为只有红色通道被保留乘以1绿色和蓝色通道都被消除了乘以0。将颜色选为浅蓝色如RGB: 0.7, 0.7, 1.0整体色调会偏蓝同时亮度有所变化。将颜色选为灰色如RGB: 0.5, 0.5, 0.5所有颜色通道减半效果等同于将整个材质调暗。将Alpha值A调低如果你在Properties中定义的_ColorTint是Color类型默认包含Alpha调整A值会影响最终输出的透明度。但注意这需要混合模式Blend的支持我们的基础Unlit Shader默认是不透明Opaque的。要实现透明度变化需修改SubShader的Tags和添加Blend指令这属于进阶内容。实操心得颜色乘法的物理意义可以把它理解为在物体表面覆盖一层有颜色的透明滤光片。_ColorTint的每个通道值就是该颜色滤光片的透光率。1.0表示完全透过0.0表示完全阻挡。HDR颜色在颜色选择器中你可能会看到数值超过1.0。这属于HDR高动态范围颜色常用于发光体。在普通的颜色调制中大于1的值会导致“过曝”变白的效果可以用来模拟自发光。但在我们的简单乘法中如果纹理颜色本身是0.5乘以2.0会得到1.0被钳制实际效果可能不如使用加法col _ColorTint或专门的自发光属性来得直观。5. 进阶实现动态颜色变化与参数化控制静态调整颜色只是开始。在游戏中我们更需要颜色能随时间、玩家输入或其他游戏状态动态变化。这就需要我们在Shader中引入时间或外部脚本参数。5.1 利用内置时间变量实现动态变化Unity的Shader提供了内置的时间变量最常用的是_Time它是一个float4类型其四个分量分别是_Time.y自场景加载以来的时间秒适用于慢速循环。_Time.x时间的10倍变化更快。_Time.z时间的100倍。_Time.w时间的1000倍。我们可以修改片元着色器让_ColorTint的某个通道随时间正弦波变化实现呼吸灯或脉动效果。首先在CGPROGRAM中不需要特别声明_Time它是全局可用的。然后修改frag函数fixed4 frag (v2f i) : SV_Target { fixed4 col tex2D(_MainTex, i.uv); // 动态计算色调在原有_ColorTint的基础上让红色通道随时间正弦波动 // sin(_Time.y) 的值在 [-1, 1] 之间波动 // 将其映射到 [0.2, 1.0] 之间避免完全变黑 float dynamicRedFactor (sin(_Time.y * 2.0) * 0.4 0.6); float4 dynamicTint _ColorTint; dynamicTint.r * dynamicRedFactor; // 只干预红色通道 col * dynamicTint; return col; }这段代码会让材质的红色分量以2赫兹的频率在0.6 * _ColorTint.r到1.0 * _ColorTint.r之间平滑波动。你可以将_Time.y * 2.0中的2.0理解为频率系数数值越大变化越快。5.2 通过脚本传递参数实现更复杂的控制内置时间变量虽然方便但控制权在Shader内部。更多时候我们需要从C#脚本中动态设置Shader的属性值。这就要用到MaterialPropertyBlock或直接修改Material的SetColor方法。方法一直接修改Material适用于单个材质实例创建一个C#脚本DynamicColorControllerusing UnityEngine; public class DynamicColorController : MonoBehaviour { public Color targetColor Color.white; public float changeSpeed 1.0f; private Renderer _renderer; private Material _material; private Color _currentColor; void Start() { _renderer GetComponentRenderer(); // 重要这里使用MaterialPropertyBlock是更优做法见方法二。 // 为了演示我们先获取材质实例。 _material _renderer.material; // 这会创建该材质的一个实例 _currentColor _material.GetColor(_ColorTint); } void Update() { // 每帧向目标颜色插值 _currentColor Color.Lerp(_currentColor, targetColor, Time.deltaTime * changeSpeed); // 将计算好的颜色传递给Shader的 _ColorTint 属性 _material.SetColor(_ColorTint, _currentColor); } void OnDestroy() { // 如果是通过 .material 创建的实例需要手动销毁以防内存泄漏 if (_material ! null) { Destroy(_material); } } }将这个脚本挂载到使用我们Shader材质的物体上。在Inspector中你可以实时修改targetColor物体会平滑过渡到新的色调。重要注意事项直接使用renderer.material会在运行时创建该材质的一个新实例Instance。如果场景中有很多相同材质的物体每个都这样操作会产生大量材质实例增加Draw Call和内存开销。对于性能敏感的项目不推荐此方法。方法二使用MaterialPropertyBlock推荐高性能MaterialPropertyBlock允许你覆盖材质的属性而无需创建新的材质实例。所有使用同一共享材质的物体都可以独立设置自己的属性覆盖。修改上面的脚本using UnityEngine; public class DynamicColorController : MonoBehaviour { public Color targetColor Color.white; public float changeSpeed 1.0f; private Renderer _renderer; private MaterialPropertyBlock _propBlock; private Color _currentColor; void Start() { _renderer GetComponentRenderer(); _propBlock new MaterialPropertyBlock(); // 初始获取当前颜色如果材质有默认值 _renderer.GetPropertyBlock(_propBlock); _currentColor _propBlock.GetColor(_ColorTint); // 如果没设置过初始化为白色 if (_currentColor default(Color)) _currentColor Color.white; } void Update() { _currentColor Color.Lerp(_currentColor, targetColor, Time.deltaTime * changeSpeed); // 使用MaterialPropertyBlock _renderer.GetPropertyBlock(_propBlock); // 先获取现有的属性块 _propBlock.SetColor(_ColorTint, _currentColor); // 设置新颜色 _renderer.SetPropertyBlock(_propBlock); // 应用属性块 } }这种方法效率高且不会破坏材质实例的共享。是动态修改物体材质属性的最佳实践。6. 常见问题与深度排查技巧在实际操作中你肯定会遇到各种“颜色不对”的情况。下面是一些典型问题及排查思路。6.1 颜色干预完全没生效可能原因及排查步骤属性名不匹配检查Shader中声明的变量名如_ColorTint是否与脚本中SetColor或SetPropertyBlock使用的字符串完全一致大小写敏感。着色器编译错误在Unity Console窗口查看是否有Shader编译错误粉色错误信息。即使代码有语法错误Unity有时也会生成一个“粉红”的默认材质但你的属性可能不会被识别。材质未应用正确的Shader双击确认材质球使用的Shader是否是你修改的那个。片元着色器未执行如果物体被其他物体完全遮挡或者相机的裁剪平面设置不当片元着色器可能不会运行。确保物体在相机视野内。6.2 颜色显示异常过亮、过暗、色偏可能原因及排查步骤颜色空间问题Unity默认使用线性颜色空间Linear Color Space但如果你或你使用的纹理工作在伽马空间Gamma Space会导致颜色计算不准确。在Player Settings中确认颜色空间设置。对于颜色调制通常确保输入输出都在同一空间即可。复杂的颜色运算可能需要转到线性空间计算。HDR与Tonemapping如果你在场景中使用了后处理Post-Processing中的Tonemapping非常亮1.0的颜色会被压缩可能导致视觉上的非线性变化。调试时可以暂时关闭后处理栈。纹理采样设置检查纹理的导入设置。如果纹理是sRGB颜色纹理勾选“sRGB (Color Texture)”如果是法线贴图等非颜色数据则不要勾选。错误的设置会导致颜色值在被Shader读取前就被错误地转换。Shader精度问题我们使用了fixed4来存储颜色。在移动平台或某些情况下fixed低精度的范围是[-2, 2]如果颜色值超出这个范围比如HDR颜色计算可能会出错。可以尝试改用half4中精度或float4高精度。将fixed4 col改为half4 col。6.3 性能优化与小技巧减少不必要的计算动态颜色计算如果每帧都在片元着色器中进行如使用_Time会对所有像素执行消耗较大。如果效果允许考虑在顶点着色器中计算一个颜色因子然后通过v2f结构体插值到片元着色器性能会好很多。利用材质面板的预设对于常用的色调如受伤时的红色、中毒时的紫色可以直接在材质面板上保存为不同的材质预设通过脚本切换材质而不是动态计算颜色。这在效果固定且物体不多时更简单。调试利器Frame DebuggerUnity的Frame DebuggerWindow - Analysis - Frame Debugger可以让你一步步查看渲染过程精确看到Draw Call、使用的Shader和传递的参数。当颜色不对时打开它选中对应的Draw Call查看其Shader Properties确认_ColorTint的值是否如你预期地被传递了进去。颜色干预是Shader编程的敲门砖它直接触及了片元着色器的本质——决定屏幕上每一个像素点的最终颜色。通过这个案例你不仅学会了如何用代码控制颜色更重要的是理解了数据从CPU到GPU再到屏幕的流动路径。掌握了这个基础后续再去学习光照模型、法线贴图、屏幕后处理等高级主题就会觉得有迹可循因为它们都是在不同阶段、以不同方式对这片元着色器输出的“最终颜色”进行干预和塑造。

相关新闻

WorkBody:微信公众号自动化发布工具部署与实战指南

WorkBody:微信公众号自动化发布工具部署与实战指南

2026/8/6 2:50:58

如果你正在运营微信公众号,每天为选题、写稿、排版、发布而头疼,那么今天这个项目值得你花5分钟了解一下。WorkBody 是一个专注于微信公众号内容自动化的开源工具,它试图解决公众号运营中最耗时的两个环节:内容创作和发布。简单来…

Unity LayerMask深度解析:从位运算原理到实战优化技巧

Unity LayerMask深度解析:从位运算原理到实战优化技巧

2026/8/6 2:50:58

1. 项目概述:为什么LayerMask值得你花时间研究?如果你在Unity3D里做过稍微复杂一点的交互,比如让主角只能拾取特定物品,或者让某个摄像机只渲染UI而另一个摄像机只渲染场景,那你大概率已经和LayerMask打过交道了。这东…

Qt Windows应用管理员权限启动:UAC机制、清单配置与QMake实战

Qt Windows应用管理员权限启动:UAC机制、清单配置与QMake实战

2026/8/6 2:50:58

1. 项目概述与核心痛点在Windows平台上用Qt开发桌面应用,尤其是那些需要操作注册表、写入系统目录、或者监听特定端口的程序时,一个绕不开的坎就是用户账户控制(UAC)。你精心打包的程序,用户双击运行时,屏幕…

VMware虚拟机磁盘挂载:从原理到实战的Linux存储管理指南

VMware虚拟机磁盘挂载:从原理到实战的Linux存储管理指南

2026/8/6 3:51:01

1. 项目概述:为什么虚拟机磁盘挂载是运维的必修课在虚拟化环境里折腾,尤其是用 VMware 跑 Linux 虚拟机,磁盘扩容和挂载几乎是绕不开的日常操作。你可能遇到过这种情况:线上服务跑得好好的,突然日志就报“No space lef…

VSCode配置C/C++自动生成头文件:基于libclang的工程实践

VSCode配置C/C++自动生成头文件:基于libclang的工程实践

2026/8/6 3:51:01

1. 项目概述:为什么我们需要自动生成头文件? 在C/C开发中,头文件(.h或.hpp)是模块间通信的基石。它定义了函数接口、数据结构、宏和类声明,是代码组织和编译的“合同”。然而,手动编写和维护头…

Android 7系统异常问题排查(八)系统追踪—Trace机制与性能诊断

Android 7系统异常问题排查(八)系统追踪—Trace机制与性能诊断

2026/8/6 3:51:01

系列目录:第一篇:异常机制全景图 | 第二篇:Kernel Panic 与系统重启 | 第三篇:Tombstone 机制 | 第四篇:System Server Watchdog | 第五篇:System Server 崩溃 | 第六篇:ANR 机制 | 第七篇&…

RT-Thread 5.x在STM32F407上的移植与多任务开发实战

RT-Thread 5.x在STM32F407上的移植与多任务开发实战

2026/8/6 3:51:01

1. 项目缘起与核心价值 手头这块正点原子的STM32F407探索者开发板,跟着我跑过裸机、FreeRTOS,也算是身经百战了。最近看到RT-Thread官方发布了5.x版本,号称在性能、工具链和组件生态上都有不小的升级,心里就痒痒的。作为一个嵌入式…

AI应用监控系统ai-goofish-monitor部署指南:本地安装与Docker容器化实战

AI应用监控系统ai-goofish-monitor部署指南:本地安装与Docker容器化实战

2026/8/6 3:51:01

1. 项目概述与核心价值最近在折腾一个名为ai-goofish-monitor的开源项目,它本质上是一个面向AI应用场景的监控与告警系统。这个名字听起来有点意思,“goofish”直译是“笨鱼”,但结合“AI”和“monitor”,其定位就很清晰了&#x…

STM32CubeMX图形化配置工具:从原理到实战的嵌入式开发效率革命

STM32CubeMX图形化配置工具:从原理到实战的嵌入式开发效率革命

2026/8/6 3:41:01

1. 项目概述:为什么STM32CubeMX是开发者的“瑞士军刀”如果你刚开始接触STM32,或者还在用标准外设库(SPL)甚至寄存器点灯,那么听到“STM32CubeMX”这个名字时,可能会觉得它只是个花哨的图形化配置工具。但当…

ncmdumpGUI:一键解锁网易云音乐ncm文件的终极解决方案

ncmdumpGUI:一键解锁网易云音乐ncm文件的终极解决方案

2026/8/4 15:23:37

ncmdumpGUI:一键解锁网易云音乐ncm文件的终极解决方案 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换,Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI 你是否曾经从网易云音乐下载了心爱的歌曲&am…

分布式配置中心选型实战:Nacos与Consul在创业场景下的对比

分布式配置中心选型实战:Nacos与Consul在创业场景下的对比

2026/8/5 6:02:27

分布式配置中心选型实战:Nacos与Consul在创业场景下的对比工程导读:本文深入讨论 分布式配置中心选型实战:Nacos与Consul在创业场景下的对比 在生产工程实践中的核心落地方案。基于 分布式架构与微服务设计 视角,剖析实际痛点、架…

MoneyPrinterPlus实战指南:AI视频批量生成与自动化发布完整解决方案

MoneyPrinterPlus实战指南:AI视频批量生成与自动化发布完整解决方案

2026/8/5 8:19:55

MoneyPrinterPlus实战指南:AI视频批量生成与自动化发布完整解决方案 【免费下载链接】MoneyPrinterPlus AI一键批量生成各类短视频,自动批量混剪短视频,自动把视频发布到抖音,快手,小红书,视频号上,赚钱从来没有这么容易过! 支持本地语音模型chatTTS,fasterwhisper,…

Unity相机抖动插件Camera-Shake集成与应用实战指南

Unity相机抖动插件Camera-Shake集成与应用实战指南

2026/8/6 0:00:51

1. 项目概述与核心价值最近在做一个动作游戏,需要给主角的重击和爆炸场景加点料,让打击感更足。我第一时间就想到了给相机加个抖动效果,毕竟这是提升玩家沉浸感最简单直接的手段之一。自己手写一个也不是不行,但时间成本高&#x…

Cocos Creator 3.7微信小游戏开发:从架构设计到提审上线的全流程实战指南

Cocos Creator 3.7微信小游戏开发:从架构设计到提审上线的全流程实战指南

2026/8/6 0:00:51

1. 项目概述:为什么需要一份3.7版本的专属适配指南?如果你是一位使用Cocos Creator开发微信小游戏的开发者,并且项目正运行在3.7版本上,那么你很可能已经感受到了那份“甜蜜的烦恼”。一方面,Cocos Creator 3.7是一个功…

AI编程实战:从Prompt工程到工具链集成,打造高效开发工作流

AI编程实战:从Prompt工程到工具链集成,打造高效开发工作流

2026/8/6 0:00:51

1. 项目概述:一次开源AI编程课程的深度重构 最近,我把自己的开源AI编程课程《Claude Code》做了一次从里到外的大更新。如果你对利用Claude、Codex这类大模型来辅助编程感兴趣,或者正在寻找一个能跟上最新AI编码工具迭代节奏的学习路径&#…

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

摆脱论文困扰!盘点2026年全网爆红的的AI论文写作工具

2026/8/4 13:34:51

一天写完毕业论文在2026年已不再是天方夜谭。2026年最炸裂、实测能大幅提速的AI论文写作工具,覆盖选题构思、文献整理、内容生成、格式排版等核心场景,真正帮你高效搞定论文难题。 一、全流程王者:一站式搞定论文全链路(一天定稿首…

导师推荐!2026最新AI论文工具测评与实用推荐

导师推荐!2026最新AI论文工具测评与实用推荐

2026/8/4 14:25:14

2026年真正好用的AI论文工具,核心看生成的论文质量、低AI味、格式正确、学术适配四大指标。综合实测,千笔AI、ThouPen、豆包、DeepSeek、Grammarly 是当前最值得推荐的梯队,覆盖从免费到付费、从中文到英文、从文科到理工的全场景需求。 一、…

告别游戏崩溃:XCOM 2模组管理器的智能革命

告别游戏崩溃:XCOM 2模组管理器的智能革命

2026/8/4 15:11:03

告别游戏崩溃:XCOM 2模组管理器的智能革命 【免费下载链接】xcom2-launcher The Alternative Mod Launcher (AML) is a replacement for the default game launchers from XCOM 2 and XCOM Chimera Squad. 项目地址: https://gitcode.com/gh_mirrors/xc/xcom2-lau…