Unity URP半透明服装渲染:ShaderGraph实现次表面散射与厚度控制
之前在做虚拟角色渲染和服装材质模拟时,经常遇到半透明材质与角色身体穿插、光照不自然的问题,网上资料要么是纯理论,要么是零散的代码片段,很难直接应用到实际项目中。本文将以一个具体的“半透明紧身衣”材质实现为例,完整拆解从Shader编写、材质参数调节到引擎内效果调试的全流程,并提供可直接复用的Unity ShaderGraph和代码示例。无论你是想学习次表面散射(SSS)原理,还是急需在项目中实现类似的半透明服装效果,都能从本文中找到系统性的解决方案。
1. 核心概念:什么是“半透明紧身衣”效果?
在计算机图形学中,我们常说的“半透明紧身衣”效果,并非指一个单一的材质属性,而是多种渲染技术的组合呈现。其核心目标是模拟光线穿透轻薄、贴身的纺织物(如丝绸、薄纱、紧身运动服)时,所产生的一系列视觉特性。
1.1 效果拆解与关键技术这种效果可以分解为几个关键的子效果,每个都对应着不同的渲染技术:
- 基础透明度与折射:这是最基础的一层,允许光线部分穿透材质,看到背后的物体(如角色皮肤)。简单的透明(Alpha Blend/Clip)会显得很“假”,更像玻璃。为了模拟纺织物的质感,我们通常需要结合轻微的折射扰动,模拟光线穿过织物纤维网眼时的弯曲。
- 次表面散射(Subsurface Scattering, SSS):这是实现“沉浸感”或“真实感”最关键的技术。当光线照射到半透明材质上时,一部分光会在材质内部散射,并从另一点射出。对于紧身衣来说,这意味着:
- 轮廓光(背光)透亮:在角色边缘,特别是光源从背后照射时,衣物会显得特别明亮,仿佛被“点亮”,能隐约透出皮肤的轮廓和颜色。
- 厚度变化:衣物在不同部位的厚度不同(如关节处拉伸变薄,布料堆积处变厚),透光程度也应随之变化。较薄的地方透过的皮肤颜色更多、更亮;较厚的地方则更暗、更不透。
- 高光与纤维质感:半透明纺织物(尤其是含有化纤成分的)会有特殊的高光反射,通常不是光滑的镜面反射,而是带有各向异性或拉丝质感的高光,模拟纤维的走向。
- 阴影与遮挡:半透明物体投射的阴影应该是柔和的、半透明的,而不是全黑的硬阴影。同时,角色身体(皮肤)对衣物内侧应有正确的阴影遮挡,避免出现“漂浮”感。
1.2 与普通透明材质的区别普通透明材质(如玻璃、水)主要处理的是光线的折射和菲涅尔效应。而半透明纺织物材质,其核心挑战在于“模拟光线在非均匀、纤维状介质内部的散射过程”,并需要与下层的人体皮肤进行复杂的光照交互。这要求我们的Shader不仅要计算材质本身,还要以某种方式获取并处理下层(皮肤)的颜色和光照信息。
2. 环境准备与核心思路
在开始动手前,我们需要明确实现路径和所需工具。本文将以Unity URP(Universal Render Pipeline)为例进行讲解,因为URP提供了相对友好的ShaderGraph工具和可定制的渲染管线,适合快速原型开发和移动端/PC端跨平台项目。其核心思路也适用于其他引擎(如Unreal Engine的材质编辑器)。
2.1 所需环境与工具
- 引擎:Unity 2021.3 LTS 或更高版本(确保对URP的良好支持)。
- 渲染管线:Universal Render Pipeline (URP)。你需要通过Package Manager安装
Universal RP包,并创建一个URP Asset(资源)应用到项目中。 - 核心工具:Shader Graph。这是Unity的可视化着色器编辑工具,我们将主要用它来构建材质。
- 可选工具:一个3D角色模型(最好包含身体和一套简单的紧身衣网格)。为了效果演示,你可以使用任何自带人体网格的模型。
2.2 实现思路总览我们的策略是“分层处理,信息复用”:
- 获取皮肤信息:这是最大的难点。在标准渲染流程中,当前渲染的像素无法直接“看到”它背后(被它遮挡)的皮肤像素。我们将探讨两种主流方案:
- 方案A(屏幕空间):使用URP的
Screen Space Shadows和自定义Buffer,在皮肤渲染阶段将皮肤颜色和深度写入一张临时纹理(Render Texture),然后在衣物Shader中采样这张纹理。性能较好,但实现稍复杂,且受屏幕分辨率限制。 - 方案B(模型空间-简化版):为角色身体单独设置一个只渲染到特定Layer的摄像机,将皮肤渲染到一张Render Texture上。衣物Shader通过投影坐标或UV来采样这张皮肤贴图。这种方法更直观,但需要额外的渲染开销和设置。
- 方案C(近似模拟-本文采用):为了教程的清晰和易复现,我们先采用一种高质量的近似方案——在衣物Shader中,我们假设皮肤颜色是一个已知的输入(例如一张皮肤贴图),并专注于实现光线在衣物层内的散射模型。这能让我们先搭建出核心的SSS和折射效果。在后续的“进阶优化”章节,我们再讨论如何动态获取真实皮肤信息。
- 方案A(屏幕空间):使用URP的
- 构建衣物材质模型:在Shader Graph中,我们将混合以下节点:
- 厚度图:一张灰度贴图,用于控制衣物不同部位的透光强度(白色薄,黑色厚)。
- 散射颜色与强度:控制透出皮肤光的颜色和亮度。
- 折射扰动:使用法线贴图或噪声图对背后的皮肤UV进行扰动,模拟纤维结构。
- 各向异性高光:模拟织物纤维方向性的高光。
3. 使用ShaderGraph构建基础半透明材质
让我们从零开始,在Unity中创建一个最基础的、具有厚度变化的半透明材质。
3.1 创建Shader Graph与材质
- 在Project窗口中右键 -> Create -> Shader -> Universal Render Pipeline -> Shader Graph。
- 将其命名为
SG_TranslucentBodysuit。 - 双击打开Shader Graph编辑器。
- 在Project窗口中,右键该Shader文件 -> Create -> Material,会自动生成一个使用该Shader的材质球,命名为
Mat_TranslucentBodysuit。
3.2 定义关键属性(Properties)在Shader Graph的Blackboard(黑板)区域,添加以下属性。这些属性将作为材质球上的可调节参数:
Base Color(Color): 衣物本身的基底颜色。Base Map(Texture2D): 衣物基底纹理。Thickness Map(Texture2D):关键属性。一张灰度图,用于控制厚度。通常我们让美术在衣服的UV上绘制,关节、拉伸处为白色(薄),布料堆积、多层处为黑色(厚)。Transmission Strength(Vector1): 透光(散射)的整体强度。Transmission Color(Color): 透出光的颜色,通常设置为皮肤色调(如淡粉色/米黄色)。Distortion Normal Map(Texture2D): 用于模拟折射扰动的法线贴图。Distortion Strength(Vector1): 折射扰动的强度。
3.3 构建着色器网络(核心逻辑)以下是简化的节点逻辑描述,你需要在Shader Graph中连接:
- 采样厚度图:使用
Sample Texture 2D节点采样Thickness Map。将其输出(假设是R通道)视为thickness值。注意:在厚度图中,我们约定“1.0(白色)表示薄,0.0(黑色)表示厚”。所以thickness值越大,透光应该越强。 - 计算散射光照:这是一种简化的SSS模拟。
- 使用
Lighting节点组下的Main Light节点获取主光源的方向和颜色。 - 使用
Normal Vector节点获取当前像素的世界法线。 - 计算
dot(Normal, -LightDirection)。当光线从背面照射(法线与光方向点乘结果为负)时,这个值越大(负得少),表示光线穿透的路径越短,散射光应该越强。我们取这个值的绝对值或进行1 - saturate(dot(...))等操作,得到一个backLit因子。 - 最终散射强度:
sssIntensity = thickness * backLit * TransmissionStrength。 - 散射颜色:
sssColor = TransmissionColor * MainLightColor * sssIntensity。
- 使用
- 模拟折射扰动(对基底色的影响):
- 采样
Distortion Normal Map,将其从切线空间转换到世界空间(使用Transform节点)。 - 使用这个扰动法线去影响环境光或IBL的采样方向,或者简单地用它来扰动基底纹理
Base Map的UV坐标(Tiling And Offset节点),从而让衣物基底颜色产生细微的、波动的变化,模拟光线穿过不规则纤维的效果。
- 采样
- 混合最终颜色:
- 基础颜色 =
Base Color * SampleTexture2D(Base Map)。 - 将散射颜色(sssColor)加到基础颜色上。注意这里是加法,因为散射光是额外透射出来的光能。
- 将结果输出到
Master Stack的Base Color端口。
- 基础颜色 =
- 透明度设置:
- 透明度也可以由
thickness和backLit因子共同控制。例如:alpha = thickness * 0.5 + backLit * 0.5。这样越薄、背光越强的地方越透明。 - 将计算出的
alpha值连接到Master Stack的Alpha端口。 - 关键步骤:在
Graph Inspector中,将Graph Settings下的Surface Type设置为Transparent,Blend Mode设置为Alpha(或其他如Premultiply,根据需求选择)。
- 透明度也可以由
一个极其简化的核心网络示意图(文字描述):
[Thickness Map] --> (Thickness Value) [Light Direction] & [Normal] --> Dot Product --> (Backlit Factor) (Thickness Value) * (Backlit Factor) * [Transmission Strength] --> (SSS Intensity) (SSS Intensity) * [Transmission Color] * [Light Color] --> (SSS Color) [Base Color/Texture] + (SSS Color) --> [Final Base Color] (Thickness Value) & (Backlit Factor) --> Blend --> [Final Alpha]4. 完整实战:在Unity中配置与调试
现在,我们将把写好的Shader应用到模型上,并进行效果调试。
4.1 准备模型与贴图
- 导入你的角色模型(包含身体和分离的紧身衣网格)。
- 为紧身衣网格准备三张关键贴图:
bodysuit_albedo.png: 衣物基础颜色/花纹贴图。bodysuit_thickness.png:手绘厚度图。在PS等软件中,根据衣物网格,在关节(肘、膝)、胸部、腹部等拉伸处涂白色,在腋下、裆部、布料褶皱堆积处涂黑色,平滑过渡灰色。bodysuit_normal.png: 衣物法线贴图,用于表现织物纹理和褶皱,也可兼作折射扰动图。
- 为身体皮肤准备一张
skin_albedo.png。
4.2 配置材质与场景
- 将之前创建的
Mat_TranslucentBodysuit材质拖拽到场景中的紧身衣网格上。 - 在材质Inspector面板中,将贴图分别赋值:
Base Map->bodysuit_albedo.pngThickness Map->bodysuit_thickness.pngDistortion Normal Map->bodysuit_normal.png
- 设置参数初值:
Base Color: 浅灰色或衣物主色。Transmission Strength: 从0.5开始调节。Transmission Color: 设置为接近皮肤的颜色,如 (1.0, 0.9, 0.8)。Distortion Strength: 0.1左右。
- 在场景中放置一个方向光(Directional Light),调整角度,使其从角色侧后方照射,以便观察背光透射效果。
4.3 编写一个简单的脚本进行动态测试为了更直观地测试参数,我们可以创建一个控制脚本。
// 文件路径:Assets/Scripts/TranslucentMaterialController.cs using UnityEngine; public class TranslucentMaterialController : MonoBehaviour { public Material targetMaterial; // 拖入你的 Mat_TranslucentBodysuit 材质实例 [Header("透射参数")] [Range(0, 2)] public float transmissionStrength = 1.0f; [Range(0, 1)] public float distortionStrength = 0.1f; public Color transmissionColor = new Color(1f, 0.9f, 0.8f); // 用于在运行时更新材质属性 void Update() { if (targetMaterial != null) { targetMaterial.SetFloat("_TransmissionStrength", transmissionStrength); targetMaterial.SetFloat("_DistortionStrength", distortionStrength); targetMaterial.SetColor("_TransmissionColor", transmissionColor); } } // 在Inspector中点击按钮快速测试不同预设 [ContextMenu("Apply Silk Preset")] void ApplySilkPreset() { transmissionStrength = 1.5f; distortionStrength = 0.15f; transmissionColor = new Color(1f, 0.95f, 0.9f); Update(); Debug.Log("Applied Silk Preset"); } [ContextMenu("Apply Latex Preset")] void ApplyLatexPreset() { transmissionStrength = 0.7f; distortionStrength = 0.05f; transmissionColor = new Color(0.95f, 0.85f, 0.8f); Update(); Debug.Log("Applied Latex Preset"); } }将此脚本挂载到场景中任意物体上(如一个空GameObject),然后将你的Mat_TranslucentBodysuit材质实例拖拽到targetMaterial字段。运行游戏,你可以在运行时动态调整参数,或点击Inspector中的上下文菜单按钮快速切换“丝绸”或“乳胶”质感预设。
4.4 运行与效果验证运行游戏,并围绕角色移动视角或旋转光源,你应该能看到:
- 当光源在角色正面时,衣物呈现其基础颜色和纹理。
- 当光源移动到角色背面或侧背面时,衣物边缘(特别是根据厚度图定义的薄区域)会开始透出
Transmission Color设定的光晕,模拟光线穿透效果。 - 调整
Transmission Strength,透光效果会增强或减弱。 - 厚度图的效果:你可以尝试临时将
Thickness Map替换为一张纯白或纯黑的贴图,观察透光效果是否均匀或完全消失,以验证厚度图是否起作用。
5. 常见问题与排查思路
在实现和调试过程中,你可能会遇到以下典型问题:
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| 完全没有透光效果 | 1. Shader的Surface Type未设置为Transparent。2. Transmission Strength值为0。3. 厚度图采样错误(UV不对或贴图未赋值)。 4. 光源方向计算错误, backLit因子始终为0。 | 1. 检查Shader Graph的Graph Settings。2. 检查材质参数。 3. 在Shader Graph中,将厚度图的采样结果直接输出到 Base Color,查看贴图是否正确显示。4. 将 backLit因子的计算中间值输出到颜色进行可视化调试。 |
| 透光颜色全黑或不正确 | 1.Transmission Color设置为黑色。2. 未正确获取或应用主光源颜色( MainLightColor)。3. 散射强度计算结果为负或过大被截断。 | 1. 检查Transmission Color参数。2. 确保场景中有激活的Directional Light,并且Shader中使用了正确的 Main Light节点。3. 使用 Clamp或Saturate节点确保强度值在合理范围(0-1)。 |
| 模型出现奇怪的闪烁或遮挡错误 | 透明渲染顺序问题。Unity默认按物体到相机的距离排序透明物体,但同一物体的不同部分可能顺序错乱。 | 1. 尝试将Shader的Blend Mode从Alpha改为Premultiply,有时能改善。2.更有效的方法:将衣物网格拆分为不同的子网格(SubMesh),确保它们不会相互穿插。在建模时就让衣物的不同部分(如袖子、躯干)是分离的网格。 |
| 性能开销大 | 1. 使用了全屏后处理或复杂的屏幕空间采样。 2. 透明物体过度绘制严重。 | 1. 如果采用了“方案A(屏幕空间)”,确保只在必要时采样皮肤Buffer。 2. 严格控制使用此复杂材质的模型面数和数量。 3. 考虑使用简化的散射公式,或使用预计算的查找表(LUT)。 |
| 移动设备上效果差或无效 | 1. Shader使用了移动端不支持的复杂节点或高精度计算。 2. URP设置中未开启所需特性。 | 1. 在Shader Graph中,将节点精度设置为Half或Float,并检查节点兼容性。2. 确保URP Asset的配置中,开启了 Screen Space Shadows或任何你依赖的额外特性。 |
6. 进阶优化与工程建议
基础效果实现后,为了达到项目级的“沉浸式”品质,还需要考虑以下优化和最佳实践。
6.1 动态皮肤信息获取(进阶方案)我们之前是假设皮肤颜色已知。在实际项目中,皮肤是动态照明的。以下是两种更真实的方案简述:
方案A:使用URP的Renderer Features
- 创建一个
RenderObjects类型的Renderer Feature,将其Event设为Before Rendering Transparents。 - 配置其
Filters,只渲染Skin层的角色身体。 - 将其输出到一个自定义的
RenderTexture(RT)中,这个RT包含了被渲染的皮肤颜色。 - 在你的紧身衣Shader中,通过
Custom Function节点或编写一个简单的HLSL代码片段,采样这张皮肤RT。采样坐标需要通过当前像素的屏幕位置(Screen Position节点)来换算。 这种方法性能较好,是主流方案。
- 创建一个
方案B:预计算皮肤透射LUT如果角色皮肤颜色变化不大,可以预先烘焙一张“透射查找表(LUT)”。这是一张2D纹理,一个轴是厚度,另一个轴是曲率(或光线角度),颜色值代表了在该厚度和角度下应透出的皮肤光颜色和强度。在运行时,Shader根据当前像素的厚度和光线角度去采样这张LUT。这种方法性能极高,但缺乏动态光照变化。
6.2 性能优化建议
- 精度控制:在Shader Graph中,非关键计算尽量使用
Half精度。 - 纹理压缩:厚度图、扰动法线图可以使用低精度的压缩格式(如BC4/BC5 for法线)。
- 简化模型:只在主角或特写角色上使用完整效果的Shader,远景或NPC可以使用简化版本(例如只保留基础透明度)。
- 批处理与合批:确保使用相同材质的衣物网格符合Unity的静态/动态合批条件,减少Draw Call。
6.3 美术协作流程
- 厚度图绘制规范:与美术师明确约定厚度图的灰度含义(白薄黑厚),并提供参考图。建议在建模软件(如Marvelous Designer)中模拟布料时,可以导出厚度信息作为绘制基础。
- 材质参数预设:针对不同的服装材质(丝绸、尼龙、乳胶、薄纱),制作好不同的材质参数预设(
Transmission Strength,Color,Distortion等),方便美术快速调用。 - 分层渲染:考虑将衣物的不同部分(如装饰边、logo)分离成不同的材质或SubMesh,对不需要半透明效果的部分使用不透明Shader,以优化渲染排序和性能。
6.4 与后处理堆栈的兼容性确保你的半透明材质与URP的后处理效果(如Bloom, Color Grading)兼容。特别是Bloom(泛光)效果,能极大地增强背光透射的“光晕”感。你可能需要调整材质的Alpha输出或使用Additive混合模式,让散射光部分能更好地被Bloom捕捉。
实现一个逼真的“半透明紧身衣”效果,是理解现代实时渲染中次表面散射、透明渲染和光照模型的一个绝佳实践。从最基础的厚度图控制透光开始,逐步引入动态皮肤信息、更精确的散射模型,并与美术资源、性能优化相结合,最终能在游戏中呈现出极具沉浸感的角色外观。关键在于理解“光与多层介质的交互”这一物理本质,并用高效的Shader代码将其近似模拟出来。动手调节每一个参数,观察其视觉变化,是掌握这门技术最有效的方法。
