ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

三维可视化光照配置:自发光、Bloom与线框扫描实战

三维可视化光照配置:自发光、Bloom与线框扫描实战 在游戏和三维可视化项目中光照配置往往决定画面的“高级感”——同样一个模型放到不同的光照环境里呈现出的质感能差出好几个档次。而自发光配合Bloom又是营造科技感、未来感最常用的一套组合拳。这一章对的这是系列的第4章把几个平时看着分散的点——场景配置、定向光、自发光、Bloom、线框扫描器——串成一条完整的实战流程让你最后能自己动手做出那种“扫描一下轮廓发光”的效果。我先说一个最容易被新手忽略的事实光照不是“把场景照亮”这么简单它是在告诉眼睛“物体从哪个方向来、表面是什么材质、空间有多深”。你如果直接把定向光关了看到的不是变黑而是所有模型突然变成扁平的色块金属失去高光玻璃没有反射轮廓整个画面像纸片拼贴。正因为如此理解光照的层次关系是后来自发光和Bloom效果能落地的地基。1. 场景光照配置先把“光”这件事理顺1.1 当“Origin 定向光照已关闭”你会看到什么在很多项目里“Origin”这个场景名会反复出现它通常是整个流程的起点场景。我调试的时候遇到过几次“Origin定向光照已关闭”的情况第一次还以为是场景崩了后来才反应过来定向光关不等于黑场。定向光Directional Light的核心作用是给场景里所有物体一个统一的明暗基准。它和法线的夹角决定了受光面的亮度面朝光线就亮背朝光线就暗。这个“亮暗差异”就是立体感的来源。一旦定向光被关闭你仍然有天空盒、环境反射光在撑着整体亮度但物体的转折处、凹槽、遮挡关系全部失去了明暗变化整个场景就像一张贴图没有体积感。尤其是金属、塑料这类带微表面高光的材质定向光一关高光点直接消失质感瞬间归零。所以看到“定向光照已关闭”的提示时不要急着把光拉回来看效果。先搞清楚场景设计意图是不是故意要让某些区域靠自发光来定义视觉重点如果是那定向光反而可以降到一个很低的强度只保留一个微弱的方向感然后把画面重心完全交给自发光物体。反之如果你想做一个写实的城市夜景定向光全关会导致建筑没有侧面受光霓虹灯和自发光招牌再亮也撑不起结构。我的经验是定向光可以弱但不能完全没有哪怕只留0.1的强度都能让物体保持一个基本的转折感。1.2 基础光照层次主光、补光、天光三步走我见很多项目一上来就调Bloom调了半天发现画面还是发闷问题就出在底层光照没有搭好。给别人做场景也好自己的项目也好我都会先走一遍下面这个三步流程把基础光阶理顺再谈效果。第一步是主光。主光几乎一定是平行光它决定整个场景的阴影方向和画面基调。对于室外场景主光的角度建议放在30到60度之间太低会让阴影拉得过长太高又缺少戏剧性。强度取决于你要的氛围——晴天可以给到1.0到1.2阴天调到0.4到0.6。色温也值得花时间正午阳光偏白偏冷清晨和黄昏偏暖用Light组件里的Temperature参数就能调整而不是傻乎乎地去改Color。第二步是补光。补光的作用是压暗部把主光造成的过深阴影拉回来。它通常是一个低强度的点光源或者区域光从主光的反方向照过来。强度控制在0.1到0.3之间千万别超过主光否则阴影关系会乱。补光的颜色可以用环境的互补色比如主光是暖黄色补光就可以带一点淡蓝色这样的冷暖对比会让画面色彩层次更丰富。第三步是天光和反射。美术场景里我习惯用HDRI天空球来提供环境光它不仅能给背景一个好看的渐变还能给物体提供大范围的环境反射。UNITY里通过Window Rendering Lighting Environment来设置天空球建议选一张带高动态范围的HDR贴图比如常见的StudioLighting或实际拍摄的天空HDR这样自发光材质的高光反射会更自然。反射源选“Skybox”后金属和玻璃的质感会比用纯颜色反射源高一个档次。1.3 动态光照让Cesium成为“太阳定位器”再说一个最近项目里经常用到的玩法用Cesium做地理信息场景时光照不应该是死的最好是跟随真实时间变化。Cesium作为三维地理空间平台本身内置了一套基于经纬度、日期和时间的太阳位置计算逻辑。接入了Cesium for Unity之后你可以直接拿到太阳在场景里的方向把它映射到Unity的平行光上实现真正的动态光照。我这里给出一个非常朴素的对接思路核心就是更新平行光的Rotation和Intensity。太阳高度角越高光线越强、色温越偏白太阳接近地平线时光线变弱、变暖。// 把Cesium太阳方向转到Unity平行光 Vector3 sunDirection CesiumGeoreference.GetSunDirection(); directionalLight.transform.rotation Quaternion.LookRotation(-sunDirection); // 用高度角控制强度与色温 float sunElevation Vector3.Dot(Vector3.up, sunDirection.normalized); float clampedElevation Mathf.Clamp01(sunElevation); directionalLight.intensity Mathf.Lerp(0.05f, 1.1f, clampedElevation); directionalLight.color Color.Lerp( new Color(1f, 0.42f, 0.2f), // 夕阳暖红 new Color(1f, 0.97f, 0.92f), // 正午偏白 Mathf.Clamp01(clampedElevation * 1.5f) );实际跑起来之后你会发现场景在早中晚三个时段呈现出完全不同的气质。早上环境光偏冷自发光建筑和路灯在低角度阳光下会显得特别跳正午光照最强Bloom会被强光压住一部分到了傍晚天空变暗自发光招牌和线框扫描的泛光就开始抢占视线。做城市尺度可视化的时候这种动态光照能带来非常真实的体感。不过要提醒一句动态光照并不适合所有场景。如果你做的是游戏关卡需要精心控制每个镜头的明暗那用固定光照反而更稳只有地理信息、数字孪生这类对时间敏感的项目才值得让太阳位置实时驱动平行光。2. 自发光材质让物体自己“发光”的正确姿势2.1 自发光在渲染管线里的真实语义很多时候大家会混淆“自发光”和“发光照亮别人”。在PBR渲染管线里自发光Emission的意思是这个物体在输出最终颜色的时候自己往颜色里叠加一份亮度这份亮度不受场景光照影响。换句话说你关掉所有灯光它依然是亮的但它不会真的像灯泡一样照亮旁边的墙壁除非你额外配置了光照通道Lightmap Emission/间接光加持。这里有几个容易踩坑的点。第一自发光数值要超过1才有效果。普通RGB颜色范围是0到1这是LDR范围但在渲染管线的HDR流程里材质的自发光颜色可以填一个大于1的数值比如3、5、8这样的亮部才能被后续的Bloom正确拾取。如果你只是把Emission设置为一个普通颜色白色1,1,1在Bloom阈值高于1的时候这个区域根本不会被识别为亮部。第二自发光不是越亮越好。强度设得太高一方面会让物体在高光区域糊成一片失去纹理细节另一方面在Tonemapping阶段过高的亮度会被映射成死白。我一般先给到一个中等强度比如HDR颜色3然后配合相机的曝光和Bloom一并调整很少一上来就开到10以上。2.2 Unity/URP里配置自发光物料的实操路径在URP通用渲染管线下我推荐的做法是使用默认的Lit Shader因为它的Emission通道支持HDR颜色和发光纹理而且能正常参与后续的后处理。操作路径大概是这样的选中材质 → 找到Surface Options → 勾选Emission → 点击Emission旁边的颜色块把RGB值手动输入为超过1的数值比如(3, 2.5, 1.2)这样偏暖的发光色。如果你还需要纹理就把做好的发光贴图拖到Emission Map槽里再用Emission灯罩控制发光区域的亮度。这里有一个细节Emission Map的使用场景。如果你做的是广告牌、屏幕、灯带这类有明显贴图边界的物体建议作成发光贴图黑色区域不发光亮色区域发光这样比直接整块材质发光要细腻得多。我在做城市夜景时招牌文字的发光都是用发光贴图独立控制的配合Bloom之后文字的边缘会非常锐利地晕开而不是整块面板都在泛光。再有就是材质类别的选择。有些项目为了省事直接把所有自发光物体都挂成Unlit Shader这会导致物体脱离光照系统在场景里像一个硬贴纸怎么看都假。Lit Shader的Emission则是在正常光照结果上叠加亮度环境反射和阴影仍然保留观感会自然很多。2.3 自发光和相机曝光、Tonemapping之间的联动自发光如果不和后处理联动其实效果是打折的。首先是相机曝光Exposure。在URP的Volume里启用Auto Exposure后相机会根据整个画面的平均亮度动态调整曝光如果你场景里自发光区域很多画面会整体偏暗因为相机算法认为“这个画面太亮了我降一下曝光”。这会导致你刚调好的发光区域在最终画面里反而变暗。遇到这种情况有两个解法一是固定曝光值在URP的Color Grading里关闭自适应曝光设置一个手动EV。城市夜景项目我一般把EV设在6到8之间二是用曝光补偿反推把发光强度提上去一点但要注意别让亮部溢出。然后是Tonemapping。URP默认的Tonemapping Mode可能有多种选项Neutral、ACES我实测下来ACES处理高光的时候会压缩得比较狠自发光区域的色彩会变得柔和但容易偏灰Neutral则保留更多原始颜色适合想要鲜艳发光感的需求。如果你发现自发光“亮不起来”检查一下Tonemapping Mode是不是被设置成了ACES并且把Emission强度往上提。3. Bloom特效配置从阈值到高级泛光质感3.1 Bloom为什么能提升画面质感亮部提取与泛光Bloom的视觉原理很好理解现实中的高亮物体比如霓虹灯、车灯、太阳在人眼看来周围会有一层光晕。这种光晕来自镜头内部的散射也让高光区域显得更柔和、更有冲劲。在渲染里模拟这个现象就是把画面里的亮部提取出来做一遍模糊再把模糊结果叠加回原图。URP的Bloom是通过Volume后处理配置的不需要写代码只需要挂一个Volume。但参数怎么填很影响最终效果。核心参数就几个阈值Threshold、强度Intensity、扩散Scatter和降采样Downsampling。阈值的意思是只有亮度高于这个值的像素才会被提取出来作为泛光来源。阈值越低泛光范围越大阈值越高泛光越克制。新手最容易犯的错是把阈值设得很低结果整个画面的亮部全都在糊场景又白又软一点层次感都没有。我自己偏好的起始阈值是0.8到1.0在这个基础上再根据自发光物体的亮度微调。3.2 参数调优表给一套可以直接抄的配置我整理了一个参数参考表每项都标注了典型范围和实战建议你可以直接抄去项目里试参数作用典型范围实战建议Threshold亮部提取门槛0.5 – 1.2做高科技感场景起步设1.0,要柔和辉光就降到0.7Intensity泛光亮度的叠加倍数0 – 1霓虹夜景0.3 – 0.6科幻扫描0.5 – 0.8Scatter光晕扩散范围0 – 1数值越大光晕散得越开太高会让画面糊Downsampling后处理计算分辨率2x – 8x桌面用4x移动端用8x保帧率举个例子做一个赛博朋克风格的地铁站入口你可以把Threshold保持1.0Intensity打到0.45Scatter设到0.6。自发光材质的灯带、招牌、灯光球会自然泛出带有方向性的光晕画面会有一种“灯光在空气里散射”的湿润感。如果想做极简的科技展厅Threshold不动Intensity降到0.2Scatter也降到0.3让泛光只出现在自发光边缘这样画面会显得克制又精致。3.3 Bloom与Color Grading、Tonemapping三件套一起调Bloom不是孤立存在的它的最终效果受Color Grading影响非常大。我最常做的组合是先调整Color Grading里的曝光和对比度把画面整体基调定下来再打开Bloom根据“泛光色偏”反过来微调Color Grading中的色温或色调。当你打开Bloom并发现泛光区域颜色发灰、发脏别急着调Intensity先看看Color Grading的White Balance和Saturation。如果画面整体饱和度偏低泛光看起来就会惨白稍微提高Vibrance后Bloom光晕会带着自发光本身的颜色扩散更漂亮。Tonemapping的选择同样会影响Bloom的颜色走向。用ACES时HDR高光会被压缩到接近白色泛光会显得更梦幻但也更容易偏灰用Neutral时Bloom会保留更多原始HDR颜色信息发光物体是红色泛光就是明显的红色。我的习惯是做写实项目用ACES做风格化项目用Neutral。别忽视这个细节它往往决定你调了半天是不是“总觉得有问题”。4. 线框扫描器实战从Shader编写到动画控制4.1 实现方案选型为什么选“世界坐标菲涅尔”组合线框扫描器这个效果在很多科技感演示、数字孪生、机甲UI里都会出现一个物体被一道光扫描过去扫过的地方线条亮起轮廓边缘有明显的发光。最容易想到的实现方式是做边缘检测基于法线或深度差把物体的轮廓线提取出来。这个方案的缺点是边缘检测是全局的扫过去时无法体现出“扫描波”的推进感并且深度边缘检测在遇到地面或其他物体时很容易误判。我更推荐的办法是组合“世界坐标Y轴扫描”和“菲涅尔边缘发光”。前者负责驱动扫描波的推进把一个扫描位置_ScanPos从物体底部往上升用世界坐标的Y值减去当前扫描位置得到一个逐渐变化的带后者负责给物体边缘叠加一层随视线角度变化的边缘光让扫描波经过时轮廓线尤其亮。这个方案的优点是扫描波的移动完全可控可以做成上扫、下扫、重复脉冲而且边缘发光的强度可以自定义不会受到相邻物体深度的影响。再配合Bloom扫描波就会产生自然泛光不需要做额外任何特效效果就出来了。4.2 用URP Shader写一个扫描线框效果我直接用URP的Shader Graph可以快速完成但为了讲清楚原理我贴一段HLSL核心逻辑。假设你有一个标准Lit材质我们需要把最终Glow颜色添加到Emission通道。// 在片元着色器中计算扫描发光 float viewDotNormal saturate(dot(normalize(viewDir), worldNormal)); float fresnel pow(1.0 - viewDotNormal, _FresnelPower); // 世界坐标Y轴扫描波 float scanWave sin((worldPos.y - _ScanPos) * _ScanFrequency); float scanMask smoothstep(_LineWidth, _ScanBand, scanWave); float scanGlow scanMask * _ScanIntensity; // 边缘亮线 float edgeGlow step(_EdgeThickness, fresnel) * _EdgeGlowIntensity; float3 finalGlow float3(scanGlow edgeGlow) * _GlowColor.rgb; // finalGlow 叠加到材质的Emission输出这里几个参数我解释一下。_ScanPos是扫描线的当前位置由C#脚本每一帧更新从0往上增加到物体高度再重置。_ScanFrequency控制扫描波出现条纹的密度值越高纹理越细腻。_LineWidth和_ScanBand共同控制扫描带宽度值越小亮带越窄更像一条刀锋。_FresnelPower控制边缘光范围默认2到4就不错。_GlowColor是这个效果的灵魂我建议用一个HDR颜色比如(Material中的颜色强度设为3到5)这样后续Bloom才会有泛光基础。Shader写好之后你要保证渲染的物体能拿到世界坐标。在URP里只要在顶点着色器里输出世界坐标然后传递给片元着色器即可上面的示例代码假设你已经有了worldPos变量。4.3 C#驱动扫描动画与事件触发有了Shader扫不扫得起来就看脚本了。我在项目里习惯写一个很轻量的Scanner脚本挂在被扫描物体上public class ScannerEffect : MonoBehaviour { public Material targetMaterial; public float scanDuration 2f; public float minY, maxY; private float currentPos; private bool isScanning; void Update() { if (!isScanning) return; currentPos Time.deltaTime / scanDuration * (maxY - minY); if (currentPos maxY) currentPos minY; targetMaterial.SetFloat(_ScanPos, currentPos); } public void StartScan() { isScanning true; currentPos minY; } }这种循环扫描适合持续展示。如果想要“扫一次就结束”可以把currentPos超过maxY时把isScanning置为false或在结束时触发一个事件比如播放声音、跳出UI。多个物体共享同一份材质时要注意所有使用同一材质实例的物体会同步扫描这就乱了。解决办法是改用MaterialPropertyBlock或者为每个物体创建独立的材质实例。还有一个实操经验扫描波配合自发光使用的时候材质本身的Emission通道最好清零全部靠Shader的finalGlow输出。这样就不会出现“扫描波亮的同时物体本身也在发光”的双重过曝。调参顺序通常是先把_ScanIntensity控制在0.3到0.6之间配合Bloom的Intensity在0.4到0.6之间效果最容易把控。5. 效果组合实战优先级、性能与问题排查5.1 先定构图再发光效果叠加时的优先级意识做这类带自发光和泛光的效果时最怕的就是所有元素一起抢眼。我的处理顺序是先定构图画面里观众第一眼应该看哪个区域如果是一个被扫描的英雄角色那周围的背景灯带、装饰光晕都要弱下去Bloom的Intensity要控制在0.3以内否则画面中心会被泛光淹没人物线条反而不清晰。具体操作上我会把“关键自发光物体”的Emission强度设置成普通装饰物体的两到三倍。比如一个被扫描的机甲Emission强度给到5周围墙体装饰线只给0.8到1.2配合Bloom阈值1.0周围装饰线不会被拾取泛光只有机甲轮廓在发光。这样层次感一下就出来了。5.2 移动端与低配置机器的优化要点如果你做的是移动端或者低配PC场景Bloom的自发光效果要额外小心。Bloom的Downsampling参数能有效降低计算量我建议在移动端至少开到8x也就是用更低分辨率计算泛光。这样会损失一点光晕的平滑度但帧率能保得住。自发光材质数量也要控制。一个场景里有几十个自发光物体每个都挂独立的HDR贴图和Emission材质Batch会非常难看。我的办法是把发光区域合并成一张Atlas贴图发布到少量材质上或者干脆用公共材质加MaterialPropertyBlock控制颜色和强度。这样既能保持个体差异又不至于炸Draw Call。动态光照在低配机器上也是一个坑。如果用了Cesium动态光照并且开启实时阴影阴影每帧都在重算性能消耗会非常明显。建议在性能要求高的场景中关闭阴影的“Every Frame”更新改为一帧或者每两帧更新一次阴影视觉上几乎没差别帧率能回暖好几毫秒。5.3 常见问题与解决方案速查表我在实际项目里踩过的坑不少整理成一个速查表遇到问题直接对照着排查就行现象可能原因解决办法Bloom完全没有反应自发光颜色没有超过阈值后处理文档没有加到相机把Emission颜色改成HDR超过1检查相机上的Volume和Overrides自发光物体发灰ACES Tonemapping压缩高光Emission强度不足调高Emission强度把Tonemapping从ACES切换为Neutral测试泛光糊成一片Threshold太低或Scatter太高升高Threshold到0.9以上Scatter降到0.5以下线框扫描出现明显锯齿条纹扫描波使用step函数边缘太锐利改用smoothstep并扩大_LineWidth提高_ScanFrequency动态光照下阴影抖动阴影更新与相机运动不同步调整Light的Shadow Bias设置阴影相机更新间隔物体没亮但场景整体被泛光污染场景里存在超过发光阈值的其他高光物体检查其他金属高光把Threshold提高到1.1以上5.4 诊断技巧帧调试器和后期通道可视化效果调不出来的时候别瞎猜用工具去看。Unity的Frame DebuggerWindow Analysis Frame Debugger可以一帧一帧回放渲染顺序你很快就能确认自发光是否有输出、Bloom是否在正确阶段处理。我排查“Bloom没反应”基本都是在这里确认的。其次是Shader的可视化调试。如果你用的是Shader Graph可以在Graph的Master Stack里临时接上不同输出看效果也可以加一个Debug视图输出扫面Mask本身。如果是手写Shader就用颜色临时替换最终输出把赤道边缘Mask、扫描Mask分别显示出来。把问题拆到能“看见”的层面再复杂的Shader问题都扛不住一轮排查。最后分享一个我自己的排查心法遇到效果不如预期第一步永远是“拆”而不是“加”。把Bloom关掉看看自发光HDR正不正常把自发光关掉看看光照层次正不正常再一步步加上去。每一层都验证通过了组合效果才可能稳定。这个习惯帮我在项目交付前夕少熬了很多个通宵。
返回列表