21 KiB
FFCard_WeChat 卡牌 Shader 框架
一、定位
这套 Shader 是 FFCard_WeChat 的卡牌业务实现,面向 Unity 2022.3.29f1、URP 14.0.11 和微信小游戏。它不属于 StrayFogCore,也不作为其他项目必须遵循的通用框架。
实现位于:
Assets/Game/GameHFResource/CN/Shaders/HFShader/
├── HLSL/HFMath.hlsl
├── HLSL/HFCardCommon.hlsl
├── HFCardUnlitOpaque.shader
├── HFCardUnlitCutout.shader
├── HFCardUnlitTransparent.shader
├── HFCardUnlitYDepthCutout.shader
└── Runtime/
HFCardBack.shadergraph 不在 v1 的迁移范围内。
HFMath 通用数学库
HLSL/HFMath.hlsl 只封装无纹理、无材质属性和无卡牌资源依赖的纯算法。当前包含矩形遮罩与重映射、扩张 UV 还原、单位 Quad 按 UV 向外扩张、矩形外缘距离、RG 法线重建并转换到世界空间、自动扫光带、错峰正弦脉冲、交叉星芒、Rec.601 亮度计算、径向/定向进度遮罩和通用溶解遮罩。
每个函数在源码中必须说明功能、返回值或参数约束以及适用场景。卡牌窗口坐标、宝石位置、纹理采样、材质 CBUFFER 和最终颜色合成继续保留在业务 HLSL 中;不得把具体美术资源的 UV 常量写入 HFMath.hlsl。
二、Shader 选择
| Shader | Queue | ZWrite | Pass | 用途 |
|---|---|---|---|---|
HF/Card/UnlitOpaque |
Geometry | On | Forward、DepthOnly、ShadowCaster | 完全不透明卡牌 |
HF/Card/UnlitCutout |
AlphaTest | On | Forward、DepthOnly、ShadowCaster | 有透明裁剪边缘的卡牌 |
HF/Card/UnlitTransparent |
Transparent | Off | Forward | 半透明卡牌,依赖透明排序 |
HF/Card/UnlitYDepthCutout |
AlphaTest | On | Forward、DepthOnly、ShadowCaster | 需要按 Y 轴产生真实深度遮挡的裁剪卡牌 |
HF/Card/StylizedPremiumYDepthCutout |
AlphaTest | On | Forward、DepthOnly、ShadowCaster | 带边框高光、视角闪面和边缘光的精品卡牌 |
HF/Card/StylizedSurfaceCutout |
Geometry | On | Forward、DepthOnly、ShadowCaster | 不透明背景、手绘浮雕卡框、糖果宝石与自动扫光;使用标准物体变换,不包含 Y 轴深度偏移 |
普通半透明卡牌优先使用透明排序;只有需要写入深度并产生真实遮挡时才使用 YDepthCutout。
三、材质属性契约
| 属性 | 含义 |
|---|---|
_BaseMap |
卡牌主纹理,材质级配置,不建议逐帧通过 MPB 替换 |
_BaseColor |
主颜色乘色 |
_Alpha |
最终 Alpha 乘数 |
_Cutoff |
Cutout 裁剪阈值 |
_HFYDepthScale |
Pivot 世界 Y 到附加世界 Z 的缩放 |
_HFYDepthOffset |
附加世界 Z 的偏移 |
_Cull |
剔除模式 |
四个 Shader 使用同一套 UnityPerMaterial CBUFFER 布局。YDepthCutout 的顶点公式固定为:
最终世界 Z = Transform 后的世界 Z + 物体 Pivot 世界 Y × _HFYDepthScale + _HFYDepthOffset
Y 深度项作为附加偏移,不覆盖 Transform 后的世界 Z,因此卡牌会完整跟随物体 XYZ 位移与旋转。_HFYDepthScale 可取正值或负值,用来匹配相机朝向和遮挡顺序。Forward、DepthOnly 和 ShadowCaster 复用同一顶点变换与 Alpha Clip。
精品卡牌 Shader 默认使用程序化边框与闪面,不需要额外贴图即可预览。启用 _HF_EFFECT_MASK 后,_EffectMask 的 R、G、B 通道分别控制边框、闪面和边缘光区域;这是一个 Local Keyword,只有确实需要美术遮罩的材质才启用。_ShimmerProgress 用于由业务动画控制闪面位置,不依赖全局时间。
参考贴图位于 HFShader/ReferenceTextures/:
HFCardPremium_BaseMap_Reference.png:完整卡面构图和材质层次参考。HFCardPremium_EffectMask_Reference.png:与 BaseMap 对应的 RGB 分区示例,不作为量产卡面的最终精确遮罩。
EffectMask 必须按线性数据纹理导入,关闭 sRGB;为避免通道串色,参考遮罩关闭纹理压缩。BaseMap 与 EffectMask 都使用 Clamp,避免卡牌边缘发生 Repeat。
手绘卡框使用 StylizedSurfaceCutout。它保持 Cull Off,并在一个 Forward Pass 内根据面朝向选择正反面:
- 正面等价
Cull Back,只显示_BaseMap,按 BaseMap Alpha 裁剪,并叠加浮雕、宝石和扫光。 - 背面等价
Cull Front,只显示_BackgroundMap;背面 U 轴自动翻转,并以 BackgroundMap Alpha 定义卡背透明轮廓。外描边按该 Alpha 轮廓向外膨胀,不再固定使用完整矩形。 _RoleMap使用固定内容映射(卡面 UV 的 X0.055~0.918、Y0.315~0.940),角色显示窗口扩展到 Y0.958,以覆盖实际约为0.953的顶部弧形开口并预留抗锯齿余量。角色不再读取 HighlightMap 黑色区域作为 Mask,而是通过 BaseMap 自身 Alpha 透明开口显示;BaseMap 非透明卡框始终覆盖在角色上方。不提供角色位移属性或运行时 Property ID。_HighlightMap:R 为宽高光、G 为锐高光、B 为扫光与框体范围;Shader 从 RGB 邻域高度差重建卡框浮雕法线。
HighlightMap 必须关闭 sRGB、MipMap 和纹理压缩,并使用 Clamp。新版 CardKuang_HighlightMap1.png 与 CardKuang_BaseMap.png 均为 792×1112,用于新的金色边框、顶部铭牌、中央黑色宝石和右下角金属砝码。HighlightMap 必须从 BaseMap 同坐标像素确定性派生,不能使用重新绘制或生成式近似轮廓:R 由同位置材质亮度生成宽高光,G 由同位置亮度梯度生成锐高光,B 由 BaseMap Alpha 与材质色差生成框体和扫光范围。当前信息图有效像素全部位于 BaseMap 有效 Alpha 内。正面依次合成 RoleMap、BaseMap 和 HighlightMap,背面只采样 BackgroundMap;不再依赖已失效的 NormalMap 和 MaterialMask。_ReliefDepth 控制从 HighlightMap RGB 邻域重建的卡框法线、倒角明暗和凹槽压暗,但不修改顶点或任何采样 UV。基础高度明暗只读取当前 UV,重建法线限制为轻微坡面并仅影响局部高光,避免深度为 1 时因强侧光造成卡框视觉偏移。金属框继续使用宽窄双层高光。B 通道直接参与扫光范围,不再被其他缺失遮罩清零。中央宝石位置、强度、饱和度、亮度、呼吸速度和闪光频率均为 Shader 内固定美术预设,不再开放材质属性或运行时 Property ID。
外描边通过 UV 方向把单位 Quad 四角分别向外平移,再重映射卡面 UV。它不再围绕物体原点缩放顶点,因此改变 _OutlineWidth 只会增加卡牌边界外侧的几何空间,_BaseMap 的世界位置和尺寸保持不变;该实现要求使用宽高为 1 的四顶点 Quad。正面仅使用 BaseMap 的真实 Alpha 外缘,并把卡面 UV 的 X 0.045~0.955、Y 0.275~0.988 定义为禁止描边的内部角色开口及其最大膨胀保护区;该范围已覆盖 Outline Width=12 的采样距离,同时在 0~1 UV 最外侧保留真正外轮廓的描边空间。因此 BaseMap 中间可以使用透明 Alpha,而角色窗口和顶部铭牌两侧的弧形开口不会生成内描边。背面使用完整矩形卡背轮廓。正面轮廓在目标宽度的 25%、50%、75% 和 100% 四层分别进行八方向采样,形成从内到外的连续膨胀,避免只在最大半径复制卡框直边而产生孤立细线。正反面共用一个开关、一组 HDR 颜色和一个 Outline Width(范围 1~12),内部按约 3 倍 BaseMap 像素换算,保证缩小后的 UI 仍然可见。_SweepEnabled 打开后由 Shader 使用 _Time 自动循环,不需要业务代码逐帧写入进度。专用 ShaderGUI 仅在勾选 Auto Sweep 时显示颜色、速度、强度、宽度、柔边和方向参数。DepthOnly 与 ShadowCaster 复用扩张后的正反面轮廓。
卡牌溶解使用 _DissolveMap,并通过材质面板开放的 Tiling/Offset(_DissolveMap_ST)独立控制噪声密度和位置;变换顺序为先把卡牌 UV 限制到 0~1,再应用 Tiling/Offset,因此外描边区域与卡牌边缘使用一致的溶解采样。_DissolveChannel 在 R/A 通道中选择灰度;低值区域随 _DissolveProgress 从 0 到 1 先消失。_DissolveEdgeColor 为 Add HDR 消散边缘颜色,其 Alpha 作为附加强度,_DissolveEdgeWidth 控制噪声值空间中的边缘宽度。正面、背面及其外描边都应用相同的溶解裁剪和 HDR 边缘,DepthOnly 与 ShadowCaster 复用同一遮罩,避免画面已消失但深度或阴影仍残留。卡框示例材质为 HF_CardKuang_StylizedSurface.mat。
Canvas Image 使用独立 Shader HF/Card/StylizedSurfaceUI。它复用场景版卡框、角色、卡背、浮雕、高光、扫光、双面 Alpha 外描边和卡牌溶解算法,但不包含 DepthOnly 或 ShadowCaster,并改用 UI Transparent、Stencil、RectMask2D、CanvasGroup 顶点颜色和 unity_GUIZTestMode。UI 溶解沿用 _DissolveMap 的 R/A 通道、Tiling/Offset、进度、Add HDR 边缘颜色和边缘宽度;正面、背面及外描边共享同一裁剪结果,保留像素再继续接受 UI 压暗、分界线和粒子合成。UI Image 应使用中心 Pivot 和完整 0~1 UV 的未打包 Sprite,卡牌内容仍由材质的 BaseMap、BackgroundMap 与 RoleMap 提供。
UI 版最终输出在进度线上方叠加压暗锁定层,不增加网格,也不把画面转换成灰度;_UIRevealDarkness 在 0~1 控制未恢复区域的压暗程度,0 保持原亮度,1 为全黑,默认 0.55。进度从 0 到 1 时蒙板由下向上解除,分界效果仅在进度严格位于 0~1 中间时显示。分界线不再包含电弧、Noise、脉冲、程序亮芯或横向滚动;_UIRevealLineMap 使用 R 通道直接决定分界显示形状,其基础贴图 Y=0.5 固定对应当前中线。分界线高度参数已移除,贴图 Tiling 围绕 UV 0.5 缩放并控制显示大小,Offset 只提供额外偏移;_UIRevealLineColor 提供 Add HDR 着色。_UIRevealParticleMap 使用 R 通道提供向卡牌本地 UV 下方流动的粒子形状,其 Tiling/Offset 控制粒子重复密度和排列。_UIRevealParticleMaskMap 使用 R 通道完全控制粒子的显示范围与拖尾,遮罩局部 Y=0.5 始终跟随中线,并独立开放 Tiling/Offset。公开参数收敛为压暗强度、分界线 HDR 颜色、粒子 HDR 颜色/速度;动画时间固定读取 HFTimeManager 写入的全局 _UnscaledTime。粒子属于 Canvas Image 内部像素效果,随 Image 旋转并受卡牌轮廓、RectMask2D 和 Stencil 裁剪。
该材质不提供 Y 轴深度缩放或偏移,顶点只使用标准物体 Transform。SurfaceMap、艺术光、Cull 和内部高光参数隐藏并使用固定预设。
闪卡范围材质
“扇卡类型”中的压扇、合扇和劈扇范围提示使用三份职责独立的 Shader,便于后续按类型单独修改美术效果;纹理采样、预乘透明合成和进度算法由公共 HLSL 复用:
Assets/Game/GameHFResource/CN/Shaders/HFShader/
├── HFFanRangePress.shader
├── HFFanRangeMerge.shader
├── HFFanRangeCleave.shader
└── HLSL/HFFanRangeCommon.hlsl
Assets/Game/GameHFResource/CN/Material/FanRange/
├── HF_FanRange_Press.mat
├── HF_FanRange_Merge.mat
└── HF_FanRange_Cleave.mat
| Shader | 展开方式 | 主体/底图 | 亮芯/填充 |
|---|---|---|---|
HF/FanRange/Press |
从纹理中心径向扩张 | yashan2.png |
yashan1.png |
HF/FanRange/Merge |
程序化扇区,开角可调,沿固定最大半径向外延伸 | BaseMap(01.png) |
程序化 Mask × HDR 填充颜色 |
HF/FanRange/Cleave |
程序化窄扇,宽度可调,从下方向上延伸 | BaseMap(pishan1.png) |
程序化 Mask × HDR 填充颜色 |
公共材质属性契约如下:
| 属性 | 传入内容 |
|---|---|
_BodyMap / _GlowMap |
仅旧版压扇使用的主体与独立亮芯贴图;合扇和新版劈扇不使用这两个属性 |
_BaseMap |
合扇使用角度×半径极坐标 UV 的完整扇面底图;劈扇使用宽度重映射后的纵向 UV 底图;两者的 Alpha 都只控制底图自身,不参与程序化延伸 |
_BaseColor |
合扇和劈扇使用;BaseMap 的 HDR 乘色,RGB 控制染色与增亮,Alpha 只控制底图自身透明度,不影响填充、运动层和描边 |
_FillColor |
合扇和劈扇使用;不依赖填充贴图,程序化当前范围直接使用该 HDR 颜色和 Alpha |
_FillSideShrink |
合扇和劈扇使用;只让 HDR 填充从左右等量向中心缩减,0 保持完整,0.2 左右各缩减 10%,1 完全隐藏;运动层和填充描边同步跟随,BaseMap 不变 |
_FillOutlineColor / _FillOutlineWidth |
合扇在当前填充半径内侧绘制 HDR 弧面描边;劈扇在当前向上延伸终点内侧绘制 HDR 端面描边 |
_MotionMap |
独立动态贴图;压扇围绕中心缩放,合扇沿极坐标半径向外滚动,劈扇沿宽度修正后的 UV Y 向上滚动;合扇和劈扇均覆盖在实际可见的 HDR 填充层上 |
_Fill01 |
压扇控制旧版主体显现;合扇控制 HDR 填充向外延伸;劈扇控制 HDR 填充从下向上延伸;均为 0 隐藏、1 完整显示 |
_BottomUpCull01 |
仅合扇使用;0 完整显示,1 完全剔除,沿扇柄到外圆弧方向推进并保持扇形弧面边界;统一裁切底图、填充、运动层和描边 |
_FillSoftness |
合扇只柔化 BaseMap 最终外圆边缘,劈扇只柔化 BaseMap 最终顶边;不影响两者的 HDR 填充、运动层或填充描边;压扇保持旧版语义 |
_SectorAngle |
仅合扇使用,控制完整开角(度);最大边长固定为归一化 1,不作为材质参数开放 |
_Width |
仅劈扇使用;以 UV X=0.5 为中心控制完整显示宽度,0 完全隐藏,1 使用底图完整宽度 |
_NineSliceBorder |
仅劈扇使用;BaseMap 九宫格左、右、下、上边距,单位为源纹理像素;默认 (30,30,30,30),四角和边线保持比例,中间区域承担 Transform 非等比拉伸 |
_MotionChannel / _MotionColor |
合扇和劈扇使用;选择 MotionMap 的 R 或 A 作为强度,并用 HDR 颜色控制最终运动层颜色与 Alpha |
_MotionSpeed |
压扇为每秒缩放循环次数;合扇为每秒沿半径向外滚动的极坐标 UV 单位;劈扇为向上滚动速度 |
_MotionScaleMin / _MotionScaleMax |
仅压扇使用,控制动态贴图单向循环缩放的最小值与最大值 |
三份 Shader 固定使用 Transparent+200、ZTest Always、ZWrite Off、Cull Off 和预乘 Alpha 混合 Blend One OneMinusSrcAlpha,因此只用于始终显示在卡牌上方的范围贴片,不参与深度、阴影或卡牌本体渲染。每份 Shader 只有一个 Forward Pass;合扇和劈扇分别使用独立业务 HLSL,便于后续按类型单独调整轮廓算法。合扇与劈扇都只采样 BaseMap 和 MotionMap 两张纹理,HDR 填充不增加纹理采样。
压扇与劈扇的亮芯层不参与进度裁切。合扇扇柄中心固定为 UV (0.5, 0),最大半径固定为 1,当前半径直接等于 _Fill01。由于合扇渲染几何只使用原 Quad 上半区,实际几何宽高比为 2:1,极坐标内部固定将 X 距离乘 2 进行角度补偿;因此 _SectorAngle=90 对应完整圆的 1/4,左右各偏离中心线 45 度。合扇按“程序化 HDR 填充 → 运动层 → 完整 BaseMap 底图 → 当前弧面 HDR 描边 → 从下往上弧形剔除”的顺序合成,使底图边框和纹理细节覆盖在填充之上。_FillSoftness 只向内柔化 BaseMap 的最终外圆边缘;HDR 填充推进边缘、运动层和弧面描边仅使用屏幕导数进行最低限度的抗锯齿,不读取该材质参数。弧面描边只向当前半径内部占宽,不越过进度边界。_BottomUpCull01 对最终预乘颜色和 Alpha 统一裁切,因此所有合成层同步消失。
动态层使用 HFTimeManager 每帧写入的全局 _UnscaledTime,动画不受 Unity Time.timeScale 影响。压扇动态贴图从最小值单向放大到最大值后直接重播;合扇在极坐标图的半径轴上向外滚动;劈扇沿原始 UV Y 向上滚动。合扇动态层在 HDR 填充之后合成,其最终 Alpha 乘以填充层的实际 Alpha,因此只覆盖 _Fill01 已经延伸且 _FillColor.a 可见的区域;不再依赖 BaseMap Alpha,也不会继承 _FillSoftness 的底图外缘柔化。
新版劈扇按“从下向上的程序化 HDR 填充 → 向上运动层 → 完整 BaseMap 底图 → 当前端面 HDR 描边”的顺序合成,使底图斜边、边框和内部纹理覆盖在填充之上;不提供 _BottomUpCull01。_Width 以中心线收窄程序化范围,同时重映射底图和运动纹理 UV;_FillSideShrink 则只在该范围内部继续对 HDR 填充左右收拢,不改变 BaseMap。与合扇一致,劈扇的向上延伸、运动层和端面描边只读取程序化宽度与进度,不读取 BaseMap 的颜色或 Alpha;BaseMap Alpha 仅控制底图自身轮廓。BaseMap 在采样前使用 _NineSliceBorder 和物体 X/Y 缩放执行九宫格重映射,四角及四边保持参考尺寸,只有中间区域吸收非等比拉伸;该逻辑不改变程序化填充或 MotionMap UV。运动层最终 Alpha 乘以填充层实际 Alpha,因此不会越过 _Fill01、_FillSideShrink,或在 _FillColor.a 为零时单独显示。
合扇与劈扇固定使用 Unity 内置单位 Quad,并在顶点阶段把局部 Y 从 -0.5~0.5 映射到原 Quad 上半区 0~0.5。因此 UV 底边中心、扇柄底点与 GameObject Transform 原点重合,_Fill01=1 只到达原 Quad 上边界,对应原 Quad 总高度的 1/2,不会向上超出原网格范围;旋转、缩放和位移仍完整跟随物体。该操作只改变渲染顶点,不修改原始 Mesh 或 MeshCollider,若业务依赖碰撞范围必须单独同步 Collider。
v1 只提供 Shader 和示例材质,不接入手势、战斗流程或表现组件。后续表现组件应复用 MaterialPropertyBlock 写入 _Fill01,不要为每次动画实例化 Material。
四、运行时 API
运行时代码位于独立业务程序集 GameHF.CardShaders,命名空间为 GameHF.Rendering。
var block = new MaterialPropertyBlock();
var state = new HFCardShaderState
{
BaseColor = Color.white,
Alpha = 1f,
Cutoff = 0.5f,
YDepthScale = 1f,
YDepthOffset = 0f
};
HFCardShaderUtility.Apply(renderer, block, state);
调用方必须长期复用 MaterialPropertyBlock,并显式初始化 HFCardShaderState 的全部字段。Apply 会先读取 Renderer 已有 PropertyBlock,再覆盖框架管理的字段,因此不会清除其他业务属性,也不会实例化 Material。使用 MPB 会影响对应 Renderer 参与 SRP Batcher 的方式,是否划算必须以微信真机测量为准。
HFCardShaderPropertyIds 额外提供 BackgroundMap、SweepEnabled、ReliefDepth、GemStrength、GemPulseSpeed 和 GemSparkleFrequency。现有 HFCardShaderState 与 Apply 接口保持不变;单卡效果差异由调用方在复用的 MPB 中写入对应 Property ID。
UGUI 使用该立体材质时,所属 Canvas 必须在 Additional Shader Channels 中启用 Normal 和 Tangent。否则 CanvasRenderer 不会生成卡面切线基,NormalMap、浮雕与宝石高光会和场景 Quad 不一致。GameStart 示例 Canvas 已配置为 Normal + Tangent(序列化标志值 24)。
五、扩展规则
- 新增纹理、额外 Pass 等结构性功能时使用 Local Keyword,并控制真实材质组合数量。
- 灰化、闪白、亮度和动画进度等高频状态使用普通属性与 MPB。
- 不把所有功能合并为一个 Uber Shader;新渲染状态优先拆分成职责明确的 Shader。
- 新增 Pass 时必须保证顶点位移和 Alpha Clip 与 Forward Pass 一致。
- BaseMap、Render Queue、Blend 和 ZWrite 属于材质结构配置,不通过每帧 API 动态切换。
六、验证
- 在 Unity 中确认四个 Shader 无编译错误,并检查材质 Inspector 的 SRP Batcher 兼容性。
- 用不同 Pivot Y 的多个 Quad 验证统一深度平面、Scale 正负方向和 Offset。
- 使用 Frame Debugger 检查 Forward 正面两次、背面一次纹理采样,以及 DepthOnly、ShadowCaster 对完整 Quad 的深度写入。
- 验证正面只显示 BaseMap、背面只显示 BackgroundMap,扫光不影响背面,并检查三颗宝石错峰呼吸和星芒。
- 构建 WebGL 2,检查 Shader 编译日志、粉色材质和首帧卡顿。
- 在微信开发者工具以及 Android、iOS 真机复核遮挡、内存和 GPU 帧耗。
平台通用约束和完整验收基线见微信小游戏渲染规则。
七、文档关联
| 文档 | 关联原因 |
|---|---|
| 微信小游戏渲染规则 | WebGL 2、URP、变体、透明深度和真机验证规则 |
| 工作流清单 | Unity 工程与文档仓库的验证流程 |