ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ShaderGraph乘法节点:从原理到实战的图形学调制核心

2026/8/9 17:07:29 拓冰建站 浏览量
ShaderGraph乘法节点:从原理到实战的图形学调制核心 1. 项目概述为什么乘法节点是ShaderGraph的“瑞士军刀”如果你在Unity ShaderGraph里只让我选一个节点那我大概率会选乘法节点Multiply Node。这听起来可能有点夸张毕竟ShaderGraph里节点那么多但听我解释完你就明白了。乘法节点远不止是做数学上的“乘”这么简单它在图形学里扮演的角色更像是一把“瑞士军刀”——功能看似单一但通过不同的组合和用法能解决渲染管线中超过一半的常见需求。从最基础的亮度调节、颜色混合到复杂的UV动画、遮罩处理再到顶点动画和光照计算乘法节点的身影无处不在。很多刚接触ShaderGraph的朋友容易把它和加法节点Add Node搞混或者觉得它太基础而忽视。实际上理解乘法节点的本质是理解Shader中“调制”Modulation这一核心概念的关键。所谓“调制”就是用一个值去控制、影响另一个值。在视觉上这通常表现为“变暗”、“变亮”、“混合”、“过滤”或“缩放”。乘法正是实现“调制”最直接、最高效的数学工具。这篇文章我们就来彻底拆解这个看似简单、实则强大的节点我会结合大量实际项目中的案例告诉你它到底能做什么以及如何避开那些新手常踩的坑。2. 乘法节点的核心原理与类型匹配2.1 数学本质不止于数字相乘在ShaderGraph中乘法节点的图标是一个“×”号它接受两个输入A和B输出它们的乘积。但这里的“乘”是广义的根据输入数据类型的不同其背后的数学运算和图形学意义也截然不同。官方文档将其分为三种情况我们逐一深入解读。1. 标量Float乘法最直观的“强度控制器”当A和B都是单个浮点数Float时这就是最普通的乘法。在Shader中浮点数常常代表一个强度值、一个系数或一个权重。核心用途缩放。比如你有一个噪声纹理采样出来的值范围0-1想控制它的强度就乘以一个0.5让它效果减半。图形学意义线性缩放。输出值与输入值成严格的线性比例关系。这是所有调制操作的基础。2. 矢量Vector乘法逐通道的“调色盘”当A和B都是矢量如Float2, Float3, Float4时乘法是逐通道Component-wise进行的。这是最常用、也最容易产生误解的情况。运算规则Out.r A.r * B.r,Out.g A.g * B.g,Out.b A.b * B.b,Out.a A.a * B.a。每个颜色通道独立相乘。核心用途颜色调制、UV偏移缩放。例如用一个RGB颜色比如(0.5, 1.0, 0.2)去乘另一个颜色结果是分别调整了R、G、B通道的亮度。用Float2 (2.0, 1.0) 去乘UV坐标就是在U水平方向上将纹理拉伸为原来的2倍而V垂直方向不变。重要区别这不是点乘Dot Product或叉乘Cross Product。点乘的结果是一个标量用于计算夹角、投影等叉乘的结果是一个矢量用于计算法线、旋转等。ShaderGraph有独立的节点Dot Product, Cross Product来处理它们。乘法节点的矢量乘法就是最朴素的逐通道相乘。3. 矩阵Matrix乘法空间变换的“引擎”当输入涉及矩阵时乘法节点执行的是线性代数中的矩阵乘法。这在处理骨骼动画、坐标空间变换如从模型空间到世界空间时至关重要。矢量 × 矩阵通常用于将点或矢量从一个空间变换到另一个空间。例如将模型空间下的顶点位置Float3乘以模型-世界变换矩阵float4x4得到世界空间的位置。注意这里为了满足矩阵乘法规则Shader内部可能会将Float3补全为Float4 (x, y, z, w)对于点w1对于方向矢量w0。矩阵 × 矩阵用于组合多个变换。例如将缩放矩阵、旋转矩阵、平移矩阵依次相乘得到一个完整的模型变换矩阵。矩阵乘法的顺序是固定的从左到右且不满足交换律A×B ≠ B×A这一点在操作时必须牢记。注意ShaderGraph的节点是动态类型的。当你连接一个纹理采样节点的RGBA输出Float4到A端口再连接一个浮点滑块Float到B端口时ShaderGraph会自动进行广播Broadcasting将那个Float值复制成一个Float4 (float, float, float, float)然后再与A进行逐通道相乘。这非常方便也是实现全局亮度/强度控制的关键。2.2 动态类型系统与广播机制详解ShaderGraph的“Dynamic”类型是它强大易用性的来源之一但也带来了隐蔽的坑。乘法节点的输入输出端口类型都是“Dynamic”意味着它能根据连线自动推断类型。广播机制的工作流程连接第一个输入假设你将一个Texture2D节点的RGBA输出Float4连接到端口A。连接第二个输入然后你将一个Float类型的Slider节点输出连接到端口B。自动推断与广播ShaderGraph检测到类型不匹配Float4 vs Float。它会将Float类型的B“广播”为一个Float4即(B, B, B, B)。执行运算最终执行的是Float4(A) * Float4(B, B, B, B)结果是每个通道都乘以了同一个标量值。这个机制带来的便利与风险便利无需手动使用Combine节点将Float打包成Vector简化了图结构。比如用单个Float控制整体颜色亮度或纹理平铺次数。风险有时你不想要广播。例如你有一个Float3的法线数据和一个Float3的色调调整颜色你想让它们逐通道相乘来微调法线贴图的影响。如果你不小心接入了一个Float广播机制会生效但结果可能完全错误。你需要时刻留意节点右上角的预览小圆球显示的数据类型。实操心得我习惯在完成一个复杂功能模块后逐个检查关键乘法节点的预览类型。右键点击节点选择“Preview Node”看看输入输出的值是否符合预期。对于矩阵运算更要谨慎因为预览可能不直观最好通过一个简单的Position节点输出到片元着色器用颜色可视化来检查变换结果是否正确。3. 乘法节点的核心应用场景与实战解析理解了原理我们来看看乘法节点在实战中如何大显身手。我会按照从易到难的顺序结合具体案例和节点图片段来说明。3.1 基础应用颜色与强度的调制这是乘法节点最直白的用法也是使用频率最高的场景。场景一控制纹理亮度与色调假设你有一张漫反射贴图Diffuse Map想根据游戏中的时间白天/黑夜动态调整它的亮度。采样纹理得到颜色Color_Texture(Float4)。创建一个Time节点或通过脚本驱动一个Float参数Brightness范围可以设为0.5夜晚到1.5正午。将两者接入乘法节点。Brightness为1时颜色不变小于1变暗大于1变亮注意可能超过1导致过曝需要根据情况使用Clamp节点限制。进阶技巧如果你不想整体变亮而是想模拟暖色调的黄昏可以创建一个Float3参数TintColor例如(1.2, 0.9, 0.7)。用这个颜色矢量与纹理颜色进行逐通道乘法。这样会增强R通道减弱G和B通道整体呈现出暖黄色调。场景二混合纹理与细节叠加你想在墙壁的基底纹理上叠加一些污渍或水渍细节。有两张纹理Base_Color基底和Detail_Color细节如污渍黑色部分应为透明或极暗。直接相乘Result Base_Color * Detail_Color。原理在Detail_Color中白色1,1,1部分会让Base_Color完全保留黑色0,0,0部分会让Base_Color完全变黑灰色部分则按比例变暗。这实现了非破坏性的细节叠加比用Lerp节点混合更自然因为它保留了基底纹理的明暗关系。注意事项这种乘法混合模式Multiply Blend Mode在Photoshop里也很常见。它不适合用于叠加比基底更亮的细节如高光那通常需要使用加法Add或屏幕Screen混合模式。3.2 核心应用UV动画与变换UV坐标本质上是Float2的矢量因此乘法节点可以对它进行缩放这是实现纹理流动、旋转结合其他节点的基础。场景一纹理平铺Tiling让一张纹理在模型表面重复多次。获取UV坐标通过UV节点。创建一个Float2常量例如(3, 3)。将UV与该常量相乘Tiled_UV UV * float2(3, 3)。结果原来在[0,1]区间的UV被放大到[0,3]区间相当于纹理在U和V方向上都重复了3次。常见问题直接相乘会导致纹理在整数边界处可能产生接缝取决于纹理的Wrap Mode。更专业的做法是使用Tiling And Offset节点或者在乘法后使用Fraction节点只取小数部分确保UV始终在[0,1)循环。场景二简单的滚动动画实现一个水面纹理的横向滚动。获取UV。创建一个基于时间的偏移量Time * Speed其中Speed是一个Float控制滚动速度。将这个偏移量与一个方向矢量相乘Offset Time * Speed * float2(1, 0)。这样就得到了一个始终指向U轴正方向的偏移矢量。将偏移量与UV相加等等这里为什么用加法因为滚动是“平移”不是缩放。这里是个关键点乘法节点用于确定滚动的“速率”和“方向”而实际的位移是通过加法节点与UV相加完成的。Scrolling_UV UV (Time * Speed * Direction)。乘法节点在这里的作用是生成了那个动态的偏移矢量。3.3 高级应用遮罩Mask与数据通道分离这是体现乘法节点“调制”精髓的高级用法。遮罩通常是一张灰度图单通道或RGBA图中的某个通道其值在0到1之间。用遮罩去乘某个属性可以实现局部控制。场景一顶点动画局部化你想让旗帜的飘动只发生在旗帜的下半部分。创建一个顶点偏移量VertexOffset基于正弦波和时间的复杂计算输出Float3。创建一个遮罩。简单的方法是用模型空间的Y坐标通过Position节点获取经过Remap和Clamp处理生成一个从顶部Y值大为0到底部Y值小为1的渐变遮罩MaskFloat。将VertexOffset与Mask相乘FinalOffset VertexOffset * Mask。结果旗帜顶部Mask≈0偏移几乎为0固定不动底部Mask≈1偏移完全生效飘动幅度最大中间部分平滑过渡。乘法节点在这里完美地将全局的动画效果“约束”在了局部区域。场景二混合两种材质属性你想让物体的上半部分是金属下半部分是布料。准备两套材质属性Metallic_A,Smoothness_A和Metallic_B,Smoothness_B。使用同一张基于世界空间或模型空间坐标生成的渐变遮罩Mask。混合计算FinalMetallic Metallic_A * Mask Metallic_B * (1 - Mask)。看这里乘法节点和加法节点一起工作。乘法节点用于将每种属性用遮罩进行“加权”加法节点再将两者合并。这是Lerp线性插值节点的底层实现原理之一。理解这一点你就能自己构建更复杂的混合逻辑。场景三利用纹理的Alpha通道很多纹理的Alpha通道储存了额外信息如透明度、自发光强度、粗糙度等。采样一张纹理分离其RGBA通道。用RGB通道作为颜色。用AAlpha通道作为一个Float与自发光强度Emission Strength参数相乘FinalEmission EmissionColor * (BaseColor_RGB * Alpha_Channel)。这样纹理中Alpha越白值越接近1的地方自发光越强越黑的地方自发光越弱。乘法节点精准地实现了基于纹理的强度控制。4. 性能考量与最佳实践在Shader中乘法运算本身是极其高效的几乎在所有GPU上都是单周期指令。性能问题的关键不在于使用乘法节点本身而在于如何高效、合理地组织Shader图避免不必要的计算和纹理采样。1. 优先使用标量乘法进行全局控制如果一个效果需要全局调整强度尽量使用一个Float参数去乘而不是一个Float3或Float4。因为标量乘法消耗的资源更少。例如调整整体镜面反射强度用FinalSpecular SpecularColor * SpecularStrengthFloat * Float3优于为RGB三个通道分别创建参数。2. 警惕在顶点着色器中进行不必要的复杂乘法顶点着色器的调用频率远低于片元着色器取决于顶点数量 vs 像素数量。但是如果某个乘法计算特别是涉及纹理采样或复杂函数的结果在顶点间是线性或可插值的那么应该在顶点着色器中计算一次然后通过插值器传递给片元着色器而不是在每个像素上都重新计算。例如基于世界空间Y轴生成的遮罩完全可以在顶点着色器中计算Mask (WorldPos.y - MinY) / (MaxY - MinY)然后输出到片元着色器。3. 合并连续的乘法运算如果有一系列连续的乘法例如A * B * C * DShader编译器通常会帮你优化。但为了图的清晰和手动优化的可能性可以考虑是否有些乘数是常量。如果是常量可以预先计算好乘积再传入Shader。在ShaderGraph内部连续的乘法节点连接编译器会将其折叠为单个连乘操作一般无需担心。4. 注意精度与范围在HDR高动态范围渲染管线中颜色值可以超过1。此时乘法可能导致数值急剧增大产生意料之外的过亮效果。对于颜色乘法要清楚你的输入范围。必要时在乘法后使用Clamp节点或Saturate节点将值限制在0-1来确保结果可控。对于非颜色数据如位置偏移则要明确其物理意义和合理范围。5. 善用预览与调试ShaderGraph的强大之处在于实时预览。对于每一个乘法节点养成习惯右键预览查看输入输出的具体数值确认广播机制是否按预期工作。分离查看对于矢量结果可以连接一个Split节点将RGBA通道分别预览检查每个通道的乘法结果是否正确。这在调试颜色混合或遮罩效果时非常有用。使用自定义函数节点对于极其复杂或重复使用的乘法-混合逻辑可以将其封装到Custom Function Node中。这不仅使主图更简洁有时还能让编译器进行更好的优化。5. 常见问题排查与实战技巧即使理解了原理在实际操作中还是会遇到各种奇怪的问题。下面是我总结的几个典型“坑”及其解决方法。问题一为什么我的颜色相乘后变全黑了现象将一个纹理颜色与另一个颜色相乘结果预览窗口一片漆黑。排查步骤检查输入源分别预览两个输入颜色。很可能其中一个颜色是(0,0,0,0)或接近黑色。常见原因是连接了错误的数据比如误将深度图、法线图其颜色值在视觉上可能看起来是灰色的但RGB值可能不在[0,1]范围当作普通颜色图进行乘法。检查数据类型确保不是用了一个Float4去乘了一个Matrix4x4这会导致类型错误结果不可预测。检查节点预览右键点击乘法节点选择“Preview Node”查看输出值。如果输出是(0,0,0,0)那肯定是输入有问题。解决修正输入数据。如果是法线贴图通常不应该直接用于颜色乘法。如果需要用法线贴图的信息比如它的蓝色通道通常表示凹凸程度请先用Split节点分离出特定通道再进行后续计算。问题二使用乘法做UV缩放纹理边缘出现接缝或闪烁。现象用UV * Tiling缩放纹理后在模型UV边界处出现难看的接缝或者在动画中闪烁。原因直接乘法放大了UV值。当UV值超过1时纹理的环绕模式Wrap Mode开始起作用。如果是“Repeat”模式纹理会重复但在整数边界处由于浮点数精度问题采样可能发生轻微偏差导致颜色突变接缝。如果是“Clamp”模式超出部分会被钳制在边缘颜色导致纹理拉伸。解决方案使用Fraction节点在乘法之后连接一个Fraction节点。Fraction节点会返回输入值的小数部分。这样无论UV乘以多大的数最终进入采样器的UV始终在[0, 1)区间内完美实现无缝平铺。公式Tiled_UV Fraction(UV * Tiling)。使用Tiling And Offset节点这是Unity提供的一个专用节点内部已经处理好了平铺逻辑更稳定也更直观。检查纹理导入设置确保纹理的Wrap Mode设置为“Repeat”并且没有启用“Border Mipmaps”等可能影响边缘采样的选项。问题三用乘法节点混合两个效果结果过渡不自然有硬边。现象用一个灰度遮罩图去混合两个纹理或两个参数交界处是一条明显的硬线没有平滑过渡。原因遮罩图本身没有平滑的渐变。可能是你使用的遮罩纹理分辨率太低、压缩失真或者你直接使用了如Step这样的节点生成了非0即1的硬遮罩。解决方案软化遮罩在遮罩输入乘法节点之前先经过一个Smoothstep节点。Smoothstep可以让你指定一个过渡区间在这个区间内产生平滑的插值。例如SoftMask Smoothstep(0.3, 0.7, HardMask)这样原来在0.3到0.7之间的硬过渡就变成了平滑过渡。使用高质量遮罩纹理确保你的遮罩纹理是高质量的并且其渐变区域足够宽。在制作遮罩纹理时使用羽化笔刷或高斯模糊来柔化边缘。结合使用有时硬边和柔边都需要。你可以创建两个遮罩一个硬边遮罩用于定义核心区域一个柔边遮罩用于混合然后用乘法或减法组合它们实现中心硬、边缘羽化的复杂遮罩效果。问题四矩阵乘法顺序导致变换错误。现象用矩阵乘法实现物体的旋转和缩放结果物体的朝向、大小完全不对。原因矩阵乘法不满足交换律。在三维变换中顺序至关重要。通常变换顺序是缩放(S) - 旋转(R) - 平移(T)。对应的矩阵乘法顺序是T * R * S * Vertex假设顶点是列向量右乘。如果你把顺序搞反了比如先平移再缩放物体会在缩放的基础上被平移导致位置错误。解决方案牢记变换顺序在ShaderGraph中手动用矩阵节点构建变换时必须严格按照你想要的变换顺序连接乘法节点。例如先连接Scale Matrix和Rotation Matrix相乘得到SR矩阵再与Translation Matrix相乘得到最终的Transform Matrix。利用Unity内置变量在绝大多数情况下你不需要自己构造模型变换矩阵。ShaderGraph提供了Transform节点可以直接将顶点/法线等从一种空间如Object Space变换到另一种空间如World Space。这个节点内部已经正确处理了矩阵乘法和顺序。可视化调试如果必须自定义矩阵一个调试技巧是将变换后的位置哪怕是错误的直接输出为颜色。例如将世界空间坐标的X、Y、Z分量映射到R、G、B通道。通过观察模型表面的颜色变化可以直观地判断平移、旋转是否按预期工作。