灯光一开,镜头一推,高光边缘那几道细长的十字星芒,几乎是所有人对电影感的第一印象。做UE5材质节点十字光斑效果之前,我习惯直接拖一张光晕贴图,但贴图是死的——光源一动、镜头一变焦,贴图拉伸后立刻糊成一片,更别说让它跟着光圈方向一起转了。后来我把这个效果完全改成UE5材质节点程序化生成,才体会到逐像素计算高光的爽快。这篇文章就专门讲怎么用UE5材质节点从零拼出一个十字光斑,并把它放进实际项目里调得自然、亮得起来、不穿帮。适合做特效、技术美术、灯光照明和偏影视向项目的人参考。
1. 先搞懂十字光斑到底在计算什么
1.1 十字光斑不是“画一个十字”
很多人第一次做星芒,会下意识找一张十字形状的贴图,或者直接在PS里画一个白色十字然后压到模型上。这个思路能出效果,但到项目里很快就露馅:光源有透视关系,贴图的十字不会跟着镜头旋转,也不会因为光圈大小改变长度和锐度,更不会在Bloom(泛光)里自然散开。
真实世界里的十字光斑,本质是光线穿过镜头光圈叶片时发生衍射和散射,在传感器上形成的光学现象。它有几个关键特征:中心有一个极亮的光核,光核向外延伸出细长的光棘,光棘之间往往还夹着一层比较弱的泛光。因为光圈叶片大多数是对称结构,所以光斑呈现出对称的放射状,常见的是四道、六道、八道。
所以我们在UE5材质节点里做的,不是“画一个十字”,而是构造一个关于UV坐标的数学函数。这个函数在中心点取值最大,沿某些固定方向衰减很慢,沿其他方向衰减很快。只要函数够干净,光斑就能在任意视角、任意分辨率下保持锐利,这是贴图方案很难做到的。
1.2 贴图、粒子、材质三种做法的取舍
先别急着连线,我把三种常用方案放在一起对比过,各有各的适用场景。
| 方案 | 优点 | 缺点 | 适合场景 |
|---|---|---|---|
| 光晕贴图/Sprite | 出图快,美术直接控制形状 | 分辨率一高就糊,透视和旋转难匹配,做不了程序化动画 | UI特效、简单氛围光 |
| 粒子系统网格 | 可以做动态出现/消失,能绑定Niagara | 光棘还是靠贴图或模型,处理不了逐像素锐度 | 爆炸闪光、灯光闪烁 |
| 程序化材质节点 | 任意分辨率锐利,参数化调光棘数量/长度/锐度,可旋转可做动画 | 刚搭的时候要理清数学关系,有学习门槛 | 影视广告、产品展示、动态光源 |
我最终选择程序化材质节点,就是因为它在真实感和可控性上平衡得最好。尤其是配合UE5的Bloom后处理,程序化生成的HDR光棘可以比贴图更自然地融进高光里。
1.3 效果拆解:核心光球、十字光棘、外圈光晕
一个能直接放进项目的十字光斑,我习惯拆成三层:
- 核心光球:中心最亮的圆点,模拟光晕溢出后的高能中心。
- 十字光棘:从核心向外伸出的四条细线,这是“十字”视觉的主体。
- 外圈光晕:低强度、大范围的泛光,让光斑不是孤零零一根线,而是有柔和过渡。
这三层最后加在一起,再乘上HDR颜色和强度,就是完整的自发光输出。后面每一步都是围绕这三层拆解来搭节点的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从UV到极坐标:星芒材质绕不开的数学关卡
2.1 把UV当成一块画布
材质节点操作光斑,第一步永远是处理UV坐标。UE5的Texture Coordinate节点默认输出0到1的二维坐标,0在左上角,1在右下角。如果直接用这个坐标做距离计算,中心会在(0.5, 0.5)这片区域,但很多数学函数期望坐标原点在正中心,所以要先减0.5。
节点连接是:TextureCoordinate -> Subtract(常数值0.5)。Subtract会对R和G两个通道都执行减0.5,得到从-0.5到0.5的坐标范围。这一操作很基础,但影响很大——后面所有距离、角度、旋转都是以它为基础算的。我见过不少新手光斑中心偏到屏幕一侧,基本都是漏了这一步。
减法做完后,我习惯用ComponentMask分别取出R和G,或者直接让后面的Length和Arctangent2节点接收二维向量。UE5里Length可以吃一个float2,返回标量距离;Arctangent2也能吃两个浮点输入,分别对应Y和X。
2.2 用Arctangent2算出方向角
星芒的“方向”在数学上需要用角度来描述。UE5材质里对应的节点是Arctangent2,也就是常说的Atan2。它接收两个值,习惯上第一个是Y,第二个是X,输出一个弧度角,范围是-π到π。
一个向量如果指向正右方,角度是0;指向正上方,角度约π/2;指向正左方,角度是π或-π。我们需要这个角度来区分光斑中每一像素在光源中心的哪个方位,然后决定那一方向是高亮还是变暗。
Arctangent2节点连接方式是:UV减0.5后的R通道 -> Arctangent2的输入Y,G通道 -> 输入X。输出再接一个Multiply,用来放大角度频率。这里有个很容易搞混的点:角度单位是弧度,不是度数。如果你想加一个90度旋转,不是加90,而是加π/2(约1.5708)。后面我会专门说旋转参数怎么设计。
2.3 为什么方向和距离分开控制效果才会干净
做程序化光斑最忌讳所有效果揉在一个节点里。比如只用RadialGradientExponential做一张圆形渐变,然后试图拉伸变形出十字,结果往往是中心糊成一团,边缘脏得不忍直视。
正确思路是:把问题拆成两个互相独立的维度。距离(Length)控制光斑从中心向外衰减的速度,决定核心大小和光棘长度;角度(Arctangent2)控制光斑在不同方向上强度不同,决定哪几个方向出现光棘。两者在最后用乘法合起来。
这样的好处很明显:想加长光棘,只需要改距离衰减参数;想让光棘更细,只需要提高角度方向上的Power指数;想旋转整个星芒,只需要给角度加一个偏移量。参数互不干扰,这是程序化材质最值钱的地方。
3. 手把手搭出可复用的十字光斑材质
3.1 创建基础材质与混合模式
在UE5内容浏览器里右键创建Material,命名时我习惯带上功能关键词,比如M_CrossGlow,方便后续检索。Shader Model选Unlit,因为光斑是自发光效果,不需要场景光照;Blend Mode选Additive,这样光斑输出会直接叠加到背景颜色上,暗部不会被半透明的灰雾盖住。
这里重点说一下Additive。光斑本质是“加光”,不是“盖一片半透明膜”。如果选Translucent,光斑中心的黑色区域也会让背景变暗半格;选Additive之后,材质输出的颜色加到背景上,暗色区域就消失了,光棘会显得更干净。注意Additive材质同样受半透明排序影响,小面片或粒子场景里通常问题不大。
基础设置里还可以把Two Sided关掉,光斑通常用粒子公告板或面片渲染,始终朝摄像机,不需要双面。如果想省事,也可以直接把材质放到一个很薄的立方体上,但那样需要把面片朝向调好,否则从侧面看会穿帮。
3.2 节点搭建基础链路
打开材质图表,先搭核心链路:
- 添加TextureCoordinate节点,作为起点。
- 添加Subtract节点,把UV减0.5,让坐标系居中。
- 从Subtract的RG连到Length节点,得到每个像素到中心的距离。
- 从Subtract的RG连到Arctangent2节点,得到方向角。
一个实用技巧:如果后续还要做旋转动画,可以在减0.5之前给UV加一个Rotator节点。Rotator会把整张UV绕指定中心旋转,等于整个光斑图案一起转。我不想让旋转影响距离计算,所以通常只对送到Arctangent2之前的那条UV分支做旋转,距离和角度分开处理。
3.3 用余弦函数和绝对值生成四方向光棘
十字光棘是整个效果的重点。这里需要一点数学上的心态:我们不需要理解太深的信号处理,只需要记住一个公式:
光棘强度 = |cos(角度 × 分支参数)| ^ 锐度参数
在材质节点里连出来就是:
- Arctangent2输出角度
- Multiply乘以2
- Cosine节点取余弦
- Abs节点取绝对值
- Power节点用指数控制锐度
为什么乘以2之后得到四个方向,而不是两个?这其实是很多教程没讲透的地方。cos(θ × 2)本身在0度和180度方向是峰值,但在90度和270度方向是负值,取绝对值之后,负值也被翻成正数,于是四个方向都有了峰值。所以参数2不是指“两条线”,而是指“正负两套半波叠加后形成四瓣”。你要做八道星芒,就把2改成4;做六道,就改成3。这个分支参数N对应最终2N道均匀光芒。
Power指数我建议给4到8之间。指数越小,光棘越胖,整个光斑看起来像一朵花;指数越大,光棘越细,越接近真实镜头里的衍射长线。调的时候从4开始,慢慢往上加,通常6左右最耐看。
3.4 合并光棘、核心与距离衰减
光棘算出后,不能直接当最终颜色用,否则整条星芒会无限延伸到屏幕边缘,而且中心会特别硬。需要给它乘一层距离衰减。
距离衰减我用一个很朴素的公式:Saturate(1 - Distance * 强度系数)。节点上是:Length结果 -> Multiply(强度系数)-> OneMinus -> Saturate。这个值在中心是1,越往外越接近0。把它和光棘相乘,光棘就会从中心向外逐渐淡出。
核心光球的做法类似:用Distance乘以一个更大的强度系数,再做一次OneMinus和Saturate,然后接一个Power提高中心锐度。最终三部分合并:
- 核心光球
- 光棘乘距离衰减后再乘强度系数
- 可选的低强度外圈光晕
加法合并可以用Add节点依次加,最后整体乘以HDR颜色和强度。这一步很关键:如果颜色输出在0到1之间,Bloom后处理里基本看不出来。我一般会把强度拉到3到8,颜色给暖色或冷色的HDR值,比如一个路灯高光给(6, 4, 2),霓虹灯给(2, 4, 8)。
3.5 光斑旋转与动态参数
旋转有两种做法。第一种整体旋转:在UV减0.5之前接Rotator节点,Rotator的RotationAmount输入一个角度数值,驱动整个图案旋转。第二种只旋转光棘方向:把Rotator放在Arctangent2那条分支里,只改变角度,不影响距离衰减和核心圆斑。
实际镜头里,星芒方向通常受光圈叶片和镜片旋转影响,和光源本身的形状关系不大,所以第二种更真实。但做动画时,直接整体旋转往往更有视觉冲击力。我建议把RotationAmount做成材质参数,需要哪种风格在实例里切换。
动态旋转可以用Niagara或Sequencer驱动材质参数。最简单的做法是把材质参数设为Dynamic Material Parameter,然后在一帧帧动画里更新,光斑就能缓慢转动。注意旋转速度不要过快,真实设备里的星芒旋转是很轻柔的,转太快会有廉价感。
3.6 可选进阶:开叉光棘
如果你做的是赛博朋克风格,想要多几道分叉细刺,可以在主光棘基础上再叠一层更高倍数的分支:
单独从Arctangent2分支再连一条:乘以2后先加一个很小的相位偏移(比如0.05),然后经过Cosine、Abs、Power(指数给更高,比如12到16),再乘一层距离衰减和低强度系数,最后加到主光棘上。这样主星芒两边会出现微微错开的细丝,很接近有些镜头里那种“毛边”感。
4. 调参、联动Bloom与实例化的生产配置
4.1 暴露参数:材质实例化设计
程序化材质最大的优势是可复用,所以一定不要把所有数值都写成常量。我至少会暴露这几组参数:
| 参数名 | 类型 | 建议默认值 | 作用 |
|---|---|---|---|
| CoreScale | Scalar | 8 | 控制核心圆斑的收缩速度 |
| CorePower | Scalar | 3 | 控制核心圆斑的锐利程度 |
| SpikeSharpness | Scalar | 6 | 控制光棘宽度,越大越细 |
| SpikeLength | Scalar | 3 | 控制光棘长度 |
| SpikeIntensity | Scalar | 1.5 | 控制光棘叠加强度 |
| CrossColor | Vector3 | (5, 3, 2) | HDR颜色 |
| RotationAngle | Scalar | 0 | 旋转角度,弧度制 |
在材质编辑器里创建ScalarParameter和VectorParameter后,右键菜单可以设置分组名,比如“Core”和“Spike”,这样Material Instance面板不会乱成一锅粥。项目里我给每个灯光类型建一个材质实例:路灯一档、车灯一档、霓虹灯一档,改参数不用动原始材质。
4.2 HDR与Bloom:为什么你的光斑总是不够亮
很多人在材质里把光斑调得很漂亮,但放回场景后一片死灰,问题几乎一定出在HDR强度上。普通材质输出到1就是纯白,但Bloom后处理的高光阈值通常设在1以上,低于阈值的光斑根本不参与泛光,看起来就是个暗淡的贴图。
解决办法是让最终输出值整体大于1。UE5材质里你可以在输出前乘一个很大的强度参数,或者把颜色参数直接设成超白,比如(10, 8, 6)。这样光斑中心超过Bloom阈值,周围低亮区域慢慢衰减,泛光才会自然地把光棘“晕开”。
具体搭配上,Project Settings里的Bloom Threshold一般保持默认或稍微降一点,比如0.8到1.0。阈值越低,全场景越容易泛光,容易脏;阈值太高,光斑需要很强的强度才能触发。我更建议保持阈值稳定,只调材质强度,这样每个光源的泛光表现会更可控。
4.3 把光斑放进粒子系统:动态出现和淡入淡出
静态面片上的光斑适合做固定光源,但如果要做爆炸闪光、枪口火光、灯牌闪烁,我更推荐把程序化材质挂到Niagara粒子系统上。粒子系统可以用Particle Color乘到材质输出上,控制淡入淡出;用Particle Size控制整体缩放;用Particle Parameter或Dynamic Parameter控制光棘长度和旋转。
这样等于把材质参数全部暴露给粒子系统,动画师可以在Niagara里K关键帧,不用打开材质编辑器。对于偏影视向的镜头,这个工作流比摆一堆半透明面片高效得多。
一个需要注意的细节:粒子系统的Sort Mode要设置好。Additive材质会叠加颜色,如果同一个位置有多个光斑粒子,排序不对会出现光棘断断续续的闪烁。我一般把Sorter Mode设为View Depth,或者干脆每个光斑用独立的Mesh Renderer,避免排序冲突。
4.4 性能与分辨率取舍
每个像素执行的三角函数运算包括Arctangent2、Cosine、Power等,在PC上完全没问题,但在移动端和VR设备上要谨慎。移动端的光照和Overdraw本来就很敏感,一个巨大的全屏光斑会带来不少填充开销。
几个我实际用过的降耗方案:
- 用面片或粒子而不是全屏Quad,限制光斑尺寸。
- 如果有多个光斑,尽量共用同一个材质实例,避免Shader变体爆炸。
- 在材质里用Feature Level Switch,把高倍分支的光棘只在PC平台开启,移动端走单层光棘。
- 如果光斑会占大半个屏幕,可以把它放到低分辨率RT上再叠加,或者使用半分辨率半透明渲染。
大多数项目里光斑只占屏幕一小块,性能压力不大。但我在做VR项目时试过全屏高倍光棘,帧数掉得很明显,后来改成移动平台专用分支才救回来。
5. 项目落地时容易踩的几个坑和我的扩展做法
5.1 负值进入Power产生NaN
这是新手最容易踩的坑。Power节点对底数有要求:如果第一个输入是负数,而指数不是整数,计算结果会变成NaN,材质会显示成黑色或异常色块。距离衰减处如果不用Saturate,1 - Distance * Scale很容易变成负值;光棘处的Cos也可能输出负值。
解决办法有两个。一个是每个可能要负的数值后面接Clamp或Saturate;另一个是用Abs把负值翻正。我的习惯是:距离衰减和核心圆斑用Saturate,光棘方向函数用Abs。这样既保证不会出NaN,也不会丢失对称性。
如果你用材质节点连线排查了很久找不到问题,可以先在节点后面套一个Debug,把输出临时接到EmissiveColor上看颜色变化。UE5的实时预览节点很方便,我用它快速定位过很多隐藏的负值。
5.2 光斑中心偏到一边
出现这种情况,十有八九是UV没有减0.5,或者Rotator节点把旋转中心放在了(0,0)而不是(0.5,0.5)。Arctangent2和Length计算都基于中心点偏移后的坐标,如果偏移不对,距离和角度自然不对。
检查顺序是:先看TextureCoordinate到Subtract之间是不是0.5;再看Rotator之前是不是已经把UV居中;最后看Arctangent2的两个输入是不是R和G调换反了。我可以很确定地说,80%的“光斑不在光源上”都是这三点之一。
5.3 半透明排序导致光斑闪烁
Additive材质虽然视觉很干净,但它属于半透明渲染,半透明物体的排序是按距离从远到近,或者按Sort Priority。当场景里有很多半透明物件时,光斑可能出现闪烁或被其他半透明物体遮挡穿帮。
我的做法是:如果光斑是粒子效果,尽量把Sort Priority设到一个独立区间,比如5000以上,让它在大多数半透明物体之后渲染;如果是静态光源的光斑,直接放在场景中的TriggerBox里,用相机距离切换显示状态。注意不要轻易关Depth Test,否则光斑会穿墙显示,穿帮更严重。
5.4 不想连一堆节点:用Custom Node写HLSL
如果你已经理解了公式,但觉得材质图表连起来太占地方,可以在Custom节点里直接用HLSL实现,输入参数定义好UV、CoreScale、CorePower、SpikeSharpness、SpikeLength、SpikeIntensity和Color,代码如下:
hlsl复制float2 p = UV - 0.5;
float dist = length(p);
float angle = atan2(p.y, p.x);
float core = pow(saturate(1 - dist * CoreScale), CorePower);
float spike = pow(abs(cos(angle * 2.0)), SpikeSharpness);
float tail = pow(saturate(1 - dist * SpikeLength), 1.0);
return (core + spike * tail * SpikeIntensity) * Color;
这个Custom节点输出类型设为float3,输入类型按参数表定义。它的计算结果和上面纯节点连线是等价的,但图标清爽很多,适合放进材质函数里重复调用。注意Custom节点里atan2的第一个参数是Y,第二个是X,别写反。
5.5 从四星到八星、六星的快速切换
也许你会问:为什么我的项目里光棘数量看起来和实物对不上?因为镜头光圈叶片数量和形状不同,产生的星芒数量也不同。一个简单的对照规律:是abs(cos(Angle * N)),N取2就是四条,取3就是六条,取4就是八条。N越大,星芒越密。
六条和八条星芒很有复古电影镜头的味道,四条星芒更像现代电影里的浅景深高光。实际项目中,你可以把N暴露成Material Instance参数,预设几个档位,在Sequencer里打关键帧切换。我拍过一个灯光秀镜头,就是从四条冷白星芒逐渐切到六条暖黄星芒,氛围完全不同。
5.6 一套效果投入使用的最后一步
每次做完一个通用材质,我都会在场景里同时摆几个不同光源距离的面片做压力测试:一个在画面中心,一个在屏幕边缘,一个被前景物体部分遮挡。边缘光斑要注意面片裁剪,可能需要把面片尺寸放大;遮挡情况要看半透明深度交互;中心光斑则主要看Bloom是否过曝。
根据我自己的经验,十字光斑在镜头里属于“少而精”的点缀。程序化材质完全可以做得很细、很亮,但实际项目里画面干净更重要,我通常会把SpikeIntensity控制在0.8到1.5之间,Bloom强度也不拉满。一个恰到好处的光斑,比一道刺眼的白线更能让镜头有质感。
