URP ShaderGraph各向异性材质实战:从拉丝金属到头发丝绸高光 📅 发布时间:2026/9/16 11:27:52 👁 浏览次数: 前阵子接了个保温杯产品展示的项目甲方把实物寄过来指着杯子说就要这种“转一个角度、刷地扫过去一条细长光”的不锈钢质感。我拿默认的 URP Lit 调了大半天金属度直接拉满光滑度也做了粗糙贴图高光始终是那种圆乎乎的一团跟实物照片放一起一眼假。后来换思路改用 ShaderGraph 在 URP 里做各向异性Anisotropic材质才终于把那条光丝调出来。拉丝金属、头发、丝绸、光盘背面那种随观察角度变化的高光条纹本质都和“各向异性”有关。这篇就把我从原理理解、方案选型、实际接线到踩坑填坑的完整过程整理出来全程用 ShaderGraph不手写完整 shader适合刚接触 URP 的 TA 或想给项目加细节的美术同学参考。1. 先从现象理清原理拉丝金属的高光为什么是长条1.1 标准 PBR 高光的物理盲区默认的 Blinn-Phong、GGX 这类高光模型底层假设是微表面法线在宏观法线周围按一个圆形分布随机排列。这个假设对大多数塑料、陶器、喷砂金属成立所以高光长成一个圆形亮斑。但拉丝金属完全不同。它的微观表面是无数条平行的微小沟槽就像一整片细细的拉丝纹理。光线打上去沿沟槽方向和垂直沟槽方向的散射特性完全不同。圆形的微表面分布假设直接失效高光被“拉”成了一条细长的光带。最直观的例子是光盘背面盘面是同心圆沟槽你转动光盘看到的高光是一道道从中心放射出去的光线而不是一个圆点。这就是典型各向异性反射。1.2 记住一个方向关系高光拉丝方向与沟槽方向垂直这个关系我在项目里反复踩先写清楚竖直沟槽的拉丝表面高光是横向延伸的细长条横向沟槽高光则纵向拉长。原因是沟槽相当于一组微型柱面镜反射光会沿垂直于沟槽的方向铺开。做材质的时候脑子里要一直有这条对应关系你调的是沟槽方向看到的却是垂直方向的高光。很多教程里 Flow Map 存的是沟槽方向而不是高光方向就是这个原因。1.3 ShaderGraph 里实现它的核心依赖切线标准高光用“法线 N 和半角向量 H 的点积”控制亮斑形状。各向异性模型的思路很直接把主角从法线换成切线。材质网格自带的切线Tangent和副切线Bitangent构成了表面切平面沟槽方向就在这个平面里。各向异性高光本质上就是把原本圆形的高光 lobe在切线方向和副切线方向上分别按不同比例缩放沿着沟槽方向摊平一点垂直沟槽方向收窄一点视觉上就出现了拉丝。URP 的 Lit Shader 在内部就是这么干的——通过一个标量_Anisotropy控制高光椭圆在切线/副切线方向上的宽窄比例。它没有暴露完整的 Ward 模型或 GGX 各向异性参数但做 90% 的常规项目效果完全足够了。2. 两条实现路线我劝你先走内置 Anisotropy2.1 路线一URP Lit 内置 AnisotropyUnity 在 URP Lit Shader 和 URP Lit ShaderGraph 里都内置了各向异性支持。ShaderGraph 中只需要在 Graph Settings 里勾选一个选项PBR Master 节点就会多出一个Anisotropy输入端口。优点非常明显光照、阴影、环境光、间接高光全部自动接管不用自己实现半天下半场。改动成本低一个 float 参数就能从各向同性切到各向异性。性能和管线兼容性有官方保障。限制也有沟槽方向默认跟随网格切线和 UV 布局美术不能直接在材质里指定“这边是竖直拉丝、那边是横纹”。无法做到头发那种双高光、带色偏移的复杂效果。反射探头、平面反射等间接高光不会跟着拉丝只有直接光高光有各向异性效果。2.2 路线二自定义函数重定向切线 / 自定义高光内置方案的方向限制可以通过在顶点阶段用自定义函数重算切线来突破。你可以根据 Flow Map 或者顶点色在切平面内把切线旋转到任意方向再喂给内置 Lit 的各向异性计算。更彻底的做法是直接写一个自定义高光函数比如 Kajiya-Kay 头发模型在 ShaderGraph 里用 Custom Function 节点调用 HLSL。这个路线的控制力最强头发、丝绸、CD 都能做但要自己处理主光源方向、衰减、阴影工作量是内置方案的数倍。2.3 怎么选维度内置 Anisotropy自定义函数上手成本极低勾选即用较高要写 HLSL方向控制跟随网格切线Flow Map / 顶点色自由控制光照阴影自动需自己取主光并计算典型用途拉丝金属、磨砂面、常规道具头发、丝绸、光盘、特殊镜面移动端风险低需注意顶点采样限制我的建议很明确先把内置方案调出想要的感觉确认“我就是要这种拉丝高光”再决定要不要上 Flow Map 自定义函数。不要一上来就从数学公式写起。3. 第一版效果URP Lit 内置各向异性从勾选到出效果3.1 环境要求与 Graph 初始化Unity 2021.3 LTS 或 2022.3 LTSURP 12/14 及以上。工程必须切换成 URP 管线资产否则创建不了 URP Shader Graph。操作路径Create Shader Graph URP Lit Shader Graph命名比如SG_Aniso_Brush。双击打开后在 Graph Inspector 的Graph Settings面板中Surface TypeOpaqueWorkflowMetallic往下拉到底部的 Inputs 区域勾选 Anisotropy。勾选后返回主预览会看到 PBR Master 节点上多出来一个Anisotropy输入口。这一步在不同 Unity 版本里位置略有不同但基本都在 Surface Options/Inputs 这一类目下找不到就按 F 全局搜一下。3.2 用 Float 滑块先看清方向创建 Float 属性命名_AnisoStrengthSlider 范围-1 ~ 1默认 0.5。直接连到 Master 的Anisotropy端口。创建材质赋给一个圆柱体或者带弧面的金属杯模型。把场景里主光源转一圈看高光开始从圆斑变成一条环绕杯身的亮线。这时候一定要做两个调试动作把值拉到 0确认高光回到普通圆斑说明开关生效。把值拉到 -0.5再看一次。正负值会切换高光优先沿切线还是副切线拉伸。至于是横的还是竖的取决于你模型的 UV 和切线导出方式没有统一答案所以每次在项目里都要实测一遍别凭记忆猜方向。3.3 用噪声或纹理打破单调的“塑料拉丝感”一个全模型统一的正值各向异性做出来的拉丝是均匀的像塑料片上压了细纹缺少真实金属那种忽强忽弱的闪光。处理办法是用一个噪声或拉丝纹理去调制Anisotropy而不是给固定值。我喜欢用 ShaderGraph 自带的Gradient NoiseGradient Noise节点Scale 设为 (15, 15)接Power节点指数 3 左右压暗中间调再乘_AnisoStrength整体接Anisotropy。这样高光在某些区域被拉得很开有些区域接近圆形模拟了拉丝不一致的粗糙感。如果你手里有真实拉丝不锈钢的灰度纹理就更简单采样后取 R 通道Saturate一下直接乘_AnisoStrength。注意这里不要做0~1 到 -1~1的重映射否则纹理暗部会让高光反向拉丝画面会花掉。3.4 快速可视化切线方向的小技巧内置方案的方向完全由模型切线决定。你要确认当前模型的切线方向最土的办法是临时把 BaseColor 接Tangent Vector节点输出一张“方向色图”。具体做法在片元阶段拖入Tangent Vector输出的是 Object 空间切线接Transform转 World 空间再乘 0.5 加 0.5 后接到 BaseColor。预览时材质会呈现红绿蓝渐变颜色变化方向就是切线的走向。看清楚后删掉调试连线重新接回正式贴图。4. 让拉丝方向听话Flow Map 重定向切线的完整做法内置方案什么都好就是方向固定。真实场景里一个杯子可能是杯身纵向拉丝、杯口边缘横向拉丝、底部漩涡纹这就必须让每个像素的沟槽方向可控制。4.1 Flow Map 到底存了什么Flow Map 是一张线性纹理RG 通道记录了切平面内的一个方向向量R 对应切线方向贡献G 对应副切线方向贡献中间值 0.5 表示“该方向没有贡献”。采样后做一次* 2 - 1的映射就得到 -1 到 1 的方向值。用 Photoshop 就能画纯横纹R1G0.5代表方向沿切线纯竖纹R0.5G1代表方向沿副切线倾斜 45°R0.75G0.75。Substance Painter 里更方便直接用 Fiber 或 Brush Direction 这类生成器烘焙方向图导出时务必取消 sRGB设置为 Linear、Clamp。4.2 顶点阶段自定义函数完整可复制的 HLSL在 ShaderGraph 里URP Lit 的内置各向异性使用网格顶点切线作为方向基准。我们可以在顶点阶段把切线重算成 Flow Map 指定的方向PBR Master 内的光照代码完全不需要改动。新建一个 Text 文件改名AnisoFlowTangent.hlsl写入void AnisoFlowTangent_float( float3 TangentWS, float3 BitangentWS, float3 NormalWS, float2 FlowXY, float Strength, out float3 OutTangentWS) { // 纹理存的是 0~1方向需要 -1~1 float2 dir FlowXY * 2.0 - 1.0; // 长度极短说明这个像素没有明确沟槽方向保留原切线 float len length(dir); if (len 1e-5) dir / len; // 在切平面内按 Flow 方向重新组合切线 float3 flowTangent TangentWS * dir.x BitangentWS * dir.y; // 去掉法线分量防止切线被带倾斜 flowTangent normalize(flowTangent - NormalWS * dot(NormalWS, flowTangent)); // 用 Strength 控制原始切线与 Flow 切线之间的混合比例 OutTangentWS normalize(lerp(TangentWS, flowTangent, saturate(Strength))); }我在项目里一般先让美术确认流水方向再决定 Strength 是 1 全量替换还是 0.3~0.7 之间做混合。混合的好处是 UV 接缝附近过度自然不会从“强拉丝”突然跳到“无拉丝”。4.3 接线步骤在 Vertex 阶段把新切线喂给 Master在 ShaderGraph 的 Blocks 列表里找到Vertex Tangent块并添加进主 stack。拖入Normal Vector节点和Tangent Vector节点分别用Transform节点转成 World 空间得到NormalWS和TangentWS。新建Custom Function节点源文件设为AnisoFlowTangent.hlsl。用Cross Product节点计算BitangentWS cross(NormalWS, TangentWS)。这里注意我默认副切线方向乘的是正号镜像 UV 的模型会出现方向反了的问题后面踩坑部分细说。用Sample Texture 2D采样_FlowMapSplit后取 RGCombine成 Vector2 接入FlowXY。用一个 Float 属性控制Strength。函数输出OutTangentWS接回Vertex Tangent块。完成后转动物体或光照你会看到高光拉丝方向随 Flow Map 变化不再死板地跟着模型原始切线走。4.4 顶点采样的代价与替代方案把纹理采样用在顶点阶段最大的隐患是方向细节受限于网格顶点密度。圆柱分段数低时拉丝方向会呈现明显的折线感不像片元采样那么细腻。解决办法有两个给关键模型加细分让顶点密度足够表现方向变化把方向信息烘焙到顶点色用顶点色 RG 替代 Flow Map。移动端我强烈建议用顶点色方案省一张纹理也规避了部分平台对顶点纹理采样支持不佳的问题。另一个容易忽略的点Flow Map 的 UV 尽量跟网格 UV0 一致不要用 Lightmap UV2 去采样方向图否则贴图对齐会乱。5. 同一套逻辑改出头发、丝绸和光盘效果5.1 头发双高光加切线偏移头发的各向异性跟拉丝金属不太一样。发丝是细长圆柱体高光不是一条简单的拉丝而是沿发丝方向有一道主高光、再带一道偏蓝或偏冷的副高光。经典方法是 Kajiya-Kay 模型加切线偏移把切线沿法线方向偏移一定量让高光环发生位移。偏移的核心代码void HairAnisoSpecular_float( float3 TangentWS, float3 NormalWS, float3 HalfDir, float Exponent, float Shift1, float Shift2, float Strength1, float Strength2, out float3 SpecColor) { float3 shifted1 normalize(TangentWS NormalWS * Shift1); float3 shifted2 normalize(TangentWS NormalWS * (Shift1 Shift2)); float d1 saturate(dot(shifted1, HalfDir)); float d2 saturate(dot(shifted2, HalfDir)); float lobe1 pow(sqrt(1.0 - d1 * d1), Exponent); float lobe2 pow(sqrt(1.0 - d2 * d2), Exponent * 0.5); float3 col1 float3(1.0, 0.75, 0.5) * lobe1 * Strength1; float3 col2 float3(0.2, 0.35, 0.9) * lobe2 * Strength2; SpecColor col1 col2; }HalfDir需要通过半角向量计算normalize(LightDir ViewDir)。ShaderGraph 里主光源方向和颜色不能直接拿需要用一个小 HLSL 函数void GetMainLightInfo_float(float3 WorldPos, out float3 LightDir, out float3 LightColor) { #if defined(SHADERGRAPH_PREVIEW) LightDir float3(0.5, 0.5, 0.5); LightColor float3(1, 1, 1); #else Light mainLight GetMainLight(); LightDir mainLight.direction; LightColor mainLight.color * (mainLight.distanceAttenuation * mainLight.shadowAttenuation); #endif }Custom Function 节点的 Include 里要加上Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl这样在 ShaderGraph 里就能取到主光方向再把两个函数组合起来输出接到 Emission 上做叠加。5.2 丝绸和天鹅绒善用负各向异性丝绸的亮面经常出现一条边缘变亮、中间偏暗的高光带这是丝绸纤维的柱面反射特性。很多情况下把Anisotropy调成负值配合一张缎纹灰度图控制强度就能出现类似丝绸的两侧亮、中间暗的过渡。天鹅绒就更特殊边缘光比中心亮本质上是一种“倒置”的高光。URP 内置版本做不了真正意义的倒置 lobe但你可以把 Anisotropy 拉到一个较大的负值边缘处用菲涅尔加强观感上已经能骗过大部分人的眼睛。5.3 光盘和干涉色伪频谱色光盘背面那种彩虹色是沟槽间距对可见光不同波长做衍射干涉的结果严格做需要按波长算。项目里一般是偷懒用各向异性高光拉出一条径向光带再根据切线方向和观察角度的点积去采样一张彩虹渐变贴图乘到高光上。这种方法在电竞外设、赛博朋克道具上特别好用参数少、效果抢眼性能也便宜。核心思路就是各向异性负责形状颜色交给渐变纹理贴片。6. 这些坑我花了三天才填平接线、压缩与版本细节6.1 镜像 UV 导致高光方向拧成麻花做 Flow Map 版本时杯子一侧拉丝方向正确另一侧高光却旋转了 180°看起来像被拧过。原因是模型 UV 有镜像摆放导致副切线方向需要乘 -1 的切线符号我的Cross Product节点默认按正号计算。排查办法在模型导入器的 Tangent 设置里把 Tangent Space 从默认的Calculate改成Legacy或None对比或者直接关掉一半 UV 看看问题是不是出在镜像区域。根治手段是改模型 UV不让 UV 岛出现镜像。如果美术实在改不了就把方向烘焙到顶点色在顶点色里直接修正镜像区域的方向。6.2 纹理格式和线性空间不对效果几乎消失Flow Map 如果忘了取消 sRGB色彩空间转换会把 0.5 推到一个不等距的位置导致*2-1映射后中性方向不再是 0拉丝方向会整体偏掉高光看起来又糊又脏。纹理设置记住三件事sRGB 勾选取消保证线性采样Wrap Mode 设 Clamp防止 UV 边缘采样环绕产生突兀方向跳变压缩格式优先选择支持 R、G 通道独立的格式最好不要用带 BC 通道依赖的压缩给方向图。6.3 URP 版本差异Unity 2019 时代的老 URP 里Lit ShaderGraph 没有 Anisotropy 选项。遇到老工程要么升级 URP 包要么直接走自定义函数方案。Unity 2021.3 之后Anisotropy 选项都在 Graph Settings 底部。Unity 66000.0里位置没变但 ShaderGraph 对新节点的命名做了调整老工程升级后会有一些节点报错建议升级前先备份。6.4 性能和高光可见性URP 内置各向异性主要增加的是高光计算里的几个三角函数和分支桌面平台随便用移动端控制在场景里两三个核心材质内比较稳妥。大范围地面、墙体等次要物体不建议开高光拉丝在远景下基本看不清只会白白增加 GPU 消耗。还有一个容易误会的点URP 内置各向异性只作用于直接光反射探针和光探头的间接高光不会跟着拉丝。产品展示场景必须保证有一个方向明确的主光源否则拉丝效果会“若隐若现”看起来以为是 shader 坏了。另外移动端关闭 MSAA 时拉丝高光边缘容易出现闪烁可以把 Anisotropy 控制在-0.5 ~ 0.5范围别拉太满。最后说几句调这玩意儿调多了会发现各向异性材质绝大多数情况下不需要从公式开始造轮子。URP 内置的 Anisotropy 加上一张方向可控的 Flow Map已经能覆盖拉丝金属、磨砂玻璃、甚至大部分丝绸类需求。真正需要写 Kajiya-Kay 自定义函数的只有角色头发这种对高光轮廓要求苛刻的场景。先勾选、再连图、最后才考虑写函数这个顺序能替我省掉很多无效加班。如果你在项目里卡在方向控制上建议直接跳到 Flow Map 那一节把贴图线性化先检查一遍大概率问题就出在那儿。