Unity ShaderGraph实现伪室内透光效果:低开销打造风格化场景氛围
1. 项目概述与核心思路最近在做一个风格化场景的项目美术同学想实现一种“伪室内”的效果简单来说就是让一个在户外的建筑模型看起来像是内部有灯光透出来营造出窗户后面有房间、有生活的感觉。这种效果在赛博朋克、低多边形或者一些需要快速搭建氛围的场景里特别有用能极大地提升场景的叙事感和细节丰富度。如果用传统建模方式去真的搭建室内工作量巨大而且对性能也是不小的负担。所以我们决定用ShaderGraph在Unity里通过着色器“骗”过玩家的眼睛。这个“伪室内”效果的核心思路其实就是利用一张或几张表现室内场景的贴图我们称之为“室内贴图”或“窗户贴图”通过着色器技术将其“贴”到建筑模型的窗户、门洞等镂空部分并配合光照、模糊、动态等处理模拟出从内部透光的效果。它本质上是一种基于屏幕空间或模型UV空间的“贴花”与“光照模拟”技术的结合。我花了几天时间研究和调试最终用ShaderGraph搭建了一套比较稳定且效果可控的方案不仅实现了静态的室内透光还能模拟简单的动态比如灯光闪烁、电视屏幕变化性能开销极低。下面我就把这套方案的完整设计思路、节点连接、参数调试以及踩过的坑毫无保留地分享出来。2. 效果核心原理与方案选型在动手搭建ShaderGraph之前我们必须先想清楚要“骗”过眼睛需要解决哪几个关键问题。这决定了我们整个着色器的架构。2.1 效果拆解我们需要模拟什么一个看起来真实的“室内透光”效果通常包含以下几个视觉要素发光源窗户区域本身应该作为光源向外发射光线。这意味着窗户部分的亮度要远高于周围墙壁并且能照亮附近的环境即便很微弱。室内内容透过窗户应该能看到室内的景象比如墙壁、家具的剪影、发光的灯具等。这部分内容需要有合理的透视和细节。光线衰减与氛围光线从室内穿过玻璃到室外会有衰减。靠近窗户边缘的地方可能更亮中心区域则能看清更多室内细节。同时玻璃本身可能带有颜色、污渍或反射。动态元素真实的室内灯光可能闪烁如老式日光灯、电视屏幕会变化、有人影掠过。加入简单的动态能极大提升真实感。2.2 技术方案对比与选型基于以上要素有几种常见的技术路径方案A使用Emissive自发光材质这是最直接的方法给窗户部分一个高强度的自发光材质。优点是简单、性能好。但缺点非常明显它只是一个纯色或简单纹理的发光板完全没有室内空间的纵深感和细节看起来非常假像一块广告牌。方案B在模型后方放置一个带室内贴图的Quad在建筑模型每个窗户的后面真正放置一个带有室内贴图的平面。通过调整平面的位置和缩放可以模拟出不错的透视效果。这种方法效果最好最灵活。但缺点同样突出每个窗户都需要一个额外的Draw Call和面片管理复杂性能随窗户数量线性增长不适合大规模使用。方案C使用Shader进行UV空间映射本项目方案这是我们最终选择的方案。其核心思想是将室内贴图“投射”到模型特定的UV区域即窗户部分。遮罩我们需要一张遮罩贴图Mask Texture来精确界定模型的哪些部分是窗户白色哪些部分是墙壁黑色。这张图通常可以在建模软件中烘焙或者用模型的第二套UV配合程序化噪声生成。映射为室内贴图设计一套独立的UV坐标。这套坐标需要解决一个问题如何让一张平面的贴图在窗户这个“洞”里看起来有透视感这里我们采用类似“视差映射”的简化思想或者更简单地使用一个与窗户形状匹配的、经过透视变换的UV。合成将采样到的室内贴图颜色与模型本身的基础颜色墙壁颜色进行混合。混合的系数就是遮罩贴图的强度。同时室内贴图的亮度贡献到自发光通道实现发光效果。为什么最终选择方案C因为它完美平衡了效果、性能和灵活性。一张精心绘制的室内贴图可以包含丰富的细节通过Shader计算一个材质球就能处理整个建筑模型上所有的窗户Draw Call不变通过参数调节可以轻松控制亮度、颜色、动态效果而且完全可以与Unity的全局光照系统如Baked GI结合让这些“伪光源”真正参与到场景光照中。接下来我们就进入ShaderGraph一步步实现它。3. ShaderGraph核心节点网络搭建打开Unity创建一个新的ShaderGraphURP或Built-in管线均可节点名称可能略有差异但逻辑相通。我以URP为例进行说明。我们的Graph最终会有几个主要的属性块室内贴图、遮罩贴图、UV变换参数、发光参数等。3.1 基础颜色与遮罩混合首先我们建立最基础的通道将室内贴图通过遮罩“贴”到模型上。创建属性BaseColor(Color)建筑墙壁的基础颜色。InteriorTexture(Texture2D)那张表现室内场景的贴图。建议使用一张横向的、带有透视感的房间角落贴图这样适用于大多数窗户。MaskTexture(Texture2D)黑白遮罩贴图。白色区域代表窗户。BaseMap(Texture2D)可选墙壁的纹理贴图。UV处理将UV节点连接到Sample Texture 2D节点采样BaseMap和MaskTexture。这里通常使用模型的主UV集UV0。对于InteriorTexture我们不能直接使用模型UV否则贴图会拉伸变形。我们需要一套独立的UV变换。添加Tiling And Offset节点。创建属性Interior_Tiling(Vector2) 和Interior_Offset(Vector2)。将模型的UV节点连接到Tiling And Offset节点的UV输入将Interior_Tiling和Interior_Offset属性连接到对应输入。这样美术同学可以通过调整Tiling缩放和Offset偏移来让室内贴图的内容适配窗户的视野范围。例如调整Offset的X值可以模拟“看”向房间的不同角度。采样与混合用处理后的UV去采样InteriorTexture。使用Lerp线性插值节点进行混合。将BaseColor或采样BaseMap的结果连接到A端口将采样到的InteriorTexture颜色连接到B端口。将采样MaskTexture的红色通道通常用来存储遮罩连接到Lerp节点的T插值系数端口。这样遮罩为0黑色的地方显示墙壁颜色遮罩为1白色的地方显示室内贴图颜色。将这个Lerp的结果输出给主节点的Base Color。至此一个静态的、基础的伪室内贴图效果就完成了。你可以通过调节遮罩贴图和室内贴图的UV让室内景象出现在正确的窗户位置。3.2 自发光与光照增强静态贴图还不够我们需要让它“发光”。创建发光强度属性Emission_Intensity(Float) 可以设置一个较大的默认值比如5或10。计算发光颜色将采样到的InteriorTexture颜色与Emission_Intensity相乘得到一个高亮度的颜色。这里可以再做一层处理比如将颜色先转换为灰度再乘以一个Emission_Color属性这样可以单独控制发光的色相和强度更加灵活。遮罩控制发光区域将上一步得到的高亮度颜色再次与MaskTexture的强度可以是RGB通道的某种计算如(RGB)/3或者直接用R通道相乘。确保只有窗户区域在发光。输出将最终计算得到的发光颜色连接至主节点的Emission输入口。重要提示在URP中要使自发光影响周围环境屏幕空间光照、Bloom后处理需要确保材质的Surface Type设置为Transparent如果窗户部分是透明的或Opaque但勾选了Add Precomputed Lighting并且需要在后处理Volume中启用Bloom效果。发光的强度需要通过Emission_Intensity和Bloom的阈值配合调试。3.3 模拟透视与深度感进阶为了让平面的贴图看起来更有立体感我们可以模拟一些简单的透视效果。视差偏移简易版原理根据视角方向轻微偏移室内贴图的UV模拟视线穿过窗户看到室内不同部分的效果。虽然不如真正的视差贴图精确但开销极小能增加生动性。实现获取视角方向使用View Direction节点并转换为切线空间Transform节点World to Tangent。我们只关心XY方向即平行于窗户平面的偏移。将切线空间的View Direction的X和Y分量提取出来。用这两个分量分别乘以一个很小的系数属性Parallax_Strength例如0.05再乘以遮罩强度确保只有窗户区域有偏移得到UV偏移量。将这个偏移量加到InteriorTexture的UV上再去做采样。效果当摄像机移动时室内的画面会随着视角有轻微的滑动仿佛你真的在从不同角度窥视室内立体感瞬间提升。边缘光与衰减现象真实窗户的透光在窗户框边缘附近可能更亮光线衍射或者因为玻璃污渍导致中心与边缘亮度不一。实现我们可以利用遮罩贴图本身。对MaskTexture采样结果进行一些处理。例如使用Smoothstep节点Smoothstep(0.2, 0.8, Mask.r)。这样可以将遮罩的0-1过渡区域进行平滑并压缩中间亮部区域的范围使得窗户边缘的过渡带产生一个亮度变化可以用来调制发光强度。或者单独采样一张用于控制亮度衰减的贴图Ramp图将其与遮罩结合创造出中心亮、边缘暗或边缘亮、中心暗等不同效果。3.4 添加动态效果静态的室内看起来还是像一张画。我们加入一些简单的动态。灯光闪烁使用一个简单的三角函数Sine Time节点来模拟周期性闪烁。Sine Time本身输出的是基于时间变化的正弦波。创建一个属性Flicker_Speed用于控制速度Flicker_Intensity用于控制闪烁强度范围。计算式Flicker 1 (sin(Time * Flicker_Speed) * Flicker_Intensity)。这样Flicker会在1±Intensity之间波动。将计算出的Flicker值乘到之前计算好的发光颜色上。注意为了更真实可以只让室内贴图中代表“光源”如灯泡、台灯的特定颜色通道比如高亮白色部分参与闪烁这需要更精细的贴图规划。电视屏幕噪波假设我们的室内贴图中有一块区域是电视屏幕。我们可以使用一个动态的噪声图。ShaderGraph提供了Gradient Noise节点。将Time节点与一个速度属性相乘加到噪声UV的某个分量上让噪声动起来。然后将动态噪声与电视屏幕区域的颜色混合例如Screen混合模式。电视屏幕区域同样需要一张遮罩来界定可以利用室内贴图的Alpha通道或者第二张遮罩图。动态UV偏移模拟人影这是更高级的效果。原理是让室内贴图的一部分比如代表人影的深色区域随时间缓慢移动。我们可以准备两张室内贴图一张基础图一张带有人影叠加层的图。或者在一张贴图上人影位于特定颜色通道。使用一个缓慢变化的Time信号去驱动Interior_Offset的微小变化但只应用在代表人影的颜色信息上。这通常需要更复杂的节点网络涉及颜色通道分离和条件混合。4. 参数调节与美术资源制作要点Shader搭建好了效果的好坏七分靠Shader三分靠美术资源。这里分享一些制作贴图和调节参数的心得。4.1 遮罩贴图制作遮罩贴图是效果的基石必须精准。精度尽量使用高精度的贴图如1024x1024确保窗户边缘清晰锐利没有锯齿。模糊的遮罩边缘会导致发光区域扩散到墙上显得很脏。绘制方式在建模软件如Blender, 3ds Max中将窗户部分的面单独分一个材质ID或顶点色然后烘焙出一张纯黑白的遮罩贴图。这是最准确的方法。备用方案如果模型没有分好可以在Unity里利用模型的第二套UVUV1配合程序化噪声来生成。但可控性较差。4.2 室内贴图设计这张图决定了“室内有什么”。透视强烈建议使用带有两点透视的室内场景照片或手绘素材。视角略微俯视或平视这样贴在窗户上时透视关系才显得自然。纯正面的视角会显得很平。明暗对比贴图本身应有明确的明暗关系。光源处灯具、窗户对面最亮角落、家具背面要暗。这样在乘以发光强度后高亮部分会更突出暗部也不会过曝保留细节。色彩色调要统一。暖光钨丝灯用橙黄色调冷光日光灯、电视用蓝白色调。可以在Shader里用Emission_Color属性整体染色但贴图本身的色调是基础。4.3 Shader参数调试清单把关键参数列出来方便调试时参考参数名功能典型值/范围调试技巧Interior_Tiling缩放室内贴图(1,1) 开始调节值1会放大室内细节看得更近值1会缩小看得更远。用于匹配窗户大小和预期的观看距离。Interior_Offset偏移室内贴图(0,0) 开始调节微调X值可以“环顾”室内左右微调Y值可以调整视线高低。这是调整构图的关键。Emission_Intensity发光强度5 - 20白天场景调低夜晚场景调高。需要与场景的后处理Bloom阈值配合调试。强度太高会导致Bloom过曝成一片白光。Emission_Color发光颜色白色 (1,1,1,1)用于统一改变室内光线的色温。例如设置为淡黄色(1,0.9,0.7,1)模拟暖光灯。Parallax_Strength视差强度0.0 - 0.1从0开始慢慢增加直到摄像机移动时有轻微但可察觉的滑动感即可。太大反而会穿帮。Flicker_Speed闪烁速度1 - 10模拟日光灯故障可以快一些(8-10)模拟烛光可以慢一些(1-3)。Flicker_Intensity闪烁强度0.0 - 0.3控制亮度波动的幅度。0.1-0.2的微闪比较自然0.3以上会显得灯光接触不良。调试时建议在目标运行平台如PC、主机的最终画质下进行因为移动平台和PC平台的Bloom、色调映射等后处理效果差异很大。5. 性能优化与常见问题排查这个效果虽然不复杂但在大规模使用如整个城市建筑时仍需注意性能。5.1 性能优化点纹理合并如果有多栋建筑使用不同的室内贴图尽量将多张小图合并成一张大图集Texture Atlas。这样可以将多次采样合并为一次减少纹理采样指令。遮罩贴图也可以合并。Shader变体管理如果你的ShaderGraph包含很多可选的动态效果如闪烁、噪声通过Shader Keywords来控制。不要在一个Shader里包含所有功能然后靠参数设为0来关闭这可能导致不必要的变体和性能开销。应该为“静态版”和“动态版”创建不同的材质实例或使用Shader Variant分支。LOD支持为远处建筑使用简化版本的Shader。例如在远处可以完全关闭视差效果、动态效果甚至降低室内贴图的分辨率通过Mipmap。剔除优化确保建筑模型使用了正确的遮挡剔除Occlusion Culling。因为我们的窗户是自发光如果被其他建筑挡住就不应该被渲染。5.2 常见问题与解决方案这里记录了几个我在测试中遇到的具体问题及解决方法。问题现象可能原因解决方案窗户发光边缘有锯齿或闪烁1. 遮罩贴图分辨率不足。2. 抗锯齿MSAA/TAA与Alpha Test或Alpha Blend的边界处理问题。1. 提高遮罩贴图精度或开启其导入设置的Generate Mip Maps并使用三线性过滤。2. 尝试将材质的Alpha Clip Threshold稍微调高如从0.5调到0.6或改用Alpha Blend模式并调试混合模式。Bloom效果下窗户光晕过大糊成一片Emission_Intensity值过高超过了后处理Volume中Bloom效果的Threshold阈值和Intensity强度的合理范围。1.优先降低材质Emission_Intensity使其在不开Bloom时看起来亮度合适。2. 然后调整Bloom的Threshold确保只有最亮的部分如灯具中心能触发Bloom。3. 最后用Bloom的Intensity控制光晕大小。三者需协同调试。从某些角度观看室内贴图“穿帮”或消失1. 视差强度(Parallax_Strength)设置过大。2. 室内贴图UV偏移(Interior_Offset)导致有效画面移出视野。3. 模型法线方向错误导致背面剔除。1. 减小Parallax_Strength。2. 重置或调整Interior_Offset。3. 检查模型法线或在Shader中将Cull Mode设置为Off双面渲染但会略微增加开销。动态闪烁效果不自然像机械开关使用了简单的Sine Time波形过于规则完美。引入噪声进行调制。例如Flicker 1 (sin(Time * Speed Noise) * Intensity)其中Noise是一个缓慢变化的低频率噪声值。或者使用Random Range节点配合时间取整模拟不规则的闪烁间隔。在移动平台上帧率下降明显1. 使用了高分辨率纹理且未压缩。2. 过于复杂的动态计算如每帧采样多次噪声。3. 没有使用合批Batch。1. 为移动平台使用ASTC压缩格式的纹理并适当降低分辨率。2. 简化或关闭动态效果。考虑使用顶点动画或纹理动画替代每像素的复杂计算。3. 确保建筑模型材质相同且符合Unity的静态/动态合批条件。最后再分享一个调试小技巧在Scene视图中将渲染模式切换到Overdraw或Shaded Wireframe模式可以直观地看到Shader的渲染复杂度和覆盖范围对于优化性能非常有帮助。伪室内效果是一个典型的“视觉把戏”它的成功在于对细节的把握。多观察现实世界中窗户透光的现象比如傍晚时分楼宇的灯光不断调整你的遮罩、贴图和Shader参数直到它看起来“感觉对了”为止。这个方案已经在我们好几个项目中成功应用从赛博朋克的霓虹公寓到中世纪小镇的烛光橱窗只需要更换贴图和调整参数就能快速产出令人信服的效果性价比极高。