Unity Shader 实战:从零打造 NPR 卡通角色渲染

Unity Shader 实战:从零打造 NPR 卡通角色渲染 一、什么是 NPR 渲染NPRNon-Photorealistic Rendering非真实感渲染是计算机图形学中一类追求艺术化视觉效果的渲染技术与追求照片级真实感的 PBR 渲染相对。在游戏领域NPR 的代表就是我们耳熟能详的卡通渲染Cel Shading / Toon Shading《原神》精致的赛璐璐二次元风格《塞尔达传说荒野之息》简洁有力的卡通光影《守望先锋》夸张风格化的角色表现NPR 与 PBR 渲染的核心区别可以用下图说明PBR真实感渲染 NPR卡通渲染 光照强度连续变化 光照被切割为明确色块 ░░▒▒▓▓██████ ░░░░░░████████ 平滑渐变过渡 硬边界卡通感 推荐参考游戏《原神》《塞尔达传说荒野之息》《崩坏星穹铁道》NPR 渲染的核心特征硬阴影明暗之间有明确的边界而非平滑过渡描边角色轮廓有黑色或彩色边线强化卡通感色块化高光高光是清晰的亮斑而非漫反射高光面部特殊处理面部阴影不跟随光照方向保持角色表情清晰二、整体技术架构一套完整的 NPR 角色 Shader 通常由以下几个模块组成NPR 角色 Shader ├── 基础颜色层 Base Color Texture ├── 漫反射阴影层 Cel Shading / Ramp Shading │ ├── 身体阴影 半兰伯特 Ramp 图 │ └── 面部阴影 SDF 贴图 ├── 高光层 卡通化 Blinn-Phong │ ├── 非金属高光 │ └── 金属高光 ├── 边缘光层 Rim Lighting └── 描边 Pass 背面扩张法 Outline本文将逐一实现每个模块。三、环境搭建3.1 创建 URP 项目打开 Unity Hub新建项目选择3D (URP)模板等待项目初始化完成3.2 创建 Shader 文件在 Project 面板中右键 →Create → Shader → Unlit Shader命名为NPRCharacter。打开 Shader 文件将内容替换为 URP 兼容格式的基础框架hlsl复制四、核心模块实现4.1 顶点着色器顶点着色器负责将模型数据转换到各个空间并传递给片元着色器hlsl复制4.2 Cel Shading —— 赛璐璐风格阴影这是 NPR 渲染的核心关键思路是将连续的光照强度离散化为几个色阶。方法一smoothstep 硬过渡简单高效hlsl复制// 计算半兰伯特光照避免背光面全黑 float NdotL dot(normalWS, mainLight.direction); float halfLambert NdotL * 0.5 0.5; halfLambert pow(halfLambert, 2.0); // 用 smoothstep 将渐变切割为硬边界 // 参数(边界起点, 边界终点, 输入值) —— 起止越接近边界越硬 float shadow smoothstep(0.0, 0.01, halfLambert - _ShadowThreshold); // 阴影颜色混合 float3 diffuse lerp(_ShadowColor.rgb, _BaseColor.rgb, shadow);效果示意光照强度连续 ▓▓▓▓▓░░░░░░░░░░ smoothstep 之后 ███████░░░░░░░░░ ↑ 硬阴影边界方法二Ramp 图采样主流做法Ramp 图是一张 256×1 的渐变贴图通过采样 UV.x halfLambert 实现精细的色阶控制hlsl复制// 在 Properties 中添加 // _ShadowRamp (Shadow Ramp, 2D) white {} TEXTURE2D(_ShadowRamp); SAMPLER(sampler_ShadowRamp); // 片元着色器中 float2 rampUV float2(halfLambert, 0.5); float3 rampColor SAMPLE_TEXTURE2D(_ShadowRamp, sampler_ShadowRamp, rampUV).rgb; float3 diffuse rampColor * _BaseColor.rgb;小技巧Ramp 图中越靠左表示越深的阴影颜色越靠右表示高光颜色。可以在 Photoshop 中手绘实现暖光冷影的艺术化效果。Ramp 图制作说明新建一张 256×1 像素的图片从左到右绘制从阴影色偏冷偏暗到高光色偏暖偏亮的渐变保存为 PNG 后导入 Unity将 Filter Mode 设为BilinearWrap Mode 设为Clamp避免边缘采样溢出。4.3 卡通高光NPR 的高光不是 PBR 中的连续渐变而是一个清晰的亮斑——用smoothstep对 Blinn-Phong 高光做截断hlsl复制// 在 Properties 中添加 // _SpecularColor (Specular Color, Color) (1,1,1,1) // _SpecularSize (Specular Size, Range(0,1)) 0.5 // _SpecularSmoothness (Specular Smoothness, Range(0,0.5)) 0.01 // 片元着色器中 float3 viewDir normalize(IN.viewDirWS); float3 halfDir normalize(mainLight.direction viewDir); float NdotH dot(normalWS, halfDir); // 用 smoothstep 把高光截成卡通式亮斑 float specIntensity pow(NdotH, 100.0); float specMask smoothstep( _SpecularSize - _SpecularSmoothness, _SpecularSize _SpecularSmoothness, specIntensity ); float3 specular specMask * _SpecularColor.rgb * mainLight.color;4.4 边缘光Rim Lighting边缘光能让角色在背景中发光增强立体感是动漫角色的标志性效果hlsl复制// 在 Properties 中添加 // _RimColor (Rim Color, Color) (0.5, 0.8, 1.0, 1) // _RimWidth (Rim Width, Range(0,1)) 0.5 // _RimThreshold (Rim Threshold, Range(0,1)) 0.1 float3 viewDir normalize(IN.viewDirWS); // viewDir 与法线越垂直点积越小说明越接近轮廓边缘 float rimDot 1.0 - saturate(dot(viewDir, normalWS)); // 让边缘光只出现在受光面避免阴影面也发光 float rimIntensity rimDot * pow(NdotL, _RimThreshold); rimIntensity smoothstep( _RimWidth - 0.01, _RimWidth 0.01, rimIntensity ); float3 rim rimIntensity * _RimColor.rgb;边缘光的计算原理如下图所示光源方向 L ↑ | N ←---- 视线方向 V → 法线 | | -------- 表面 rimDot 1 - dot(V, N) 当视线与法线接近垂直时轮廓处rimDot 接近 1边缘光最亮4.5 面部阴影 —— SDF 贴图技术普通的面部阴影跟随光照方向变化会导致表情扭曲卡通感全无。SDFSigned Distance Field面部阴影贴图是业界主流方案《原神》、《崩坏星穹铁道》均采用此方案原理预先烘焙一张贴图记录面部各区域开始进入阴影的角度阈值渲染时用光源水平旋转角度与贴图采样值比较。hlsl复制// 在 Properties 中添加 // _FaceShadowMap (Face Shadow Map, 2D) white {} // _FaceShadowOffset (Face Shadow Offset, Range(-1,1)) 0 // 片元着色器中仅面部材质使用 // 计算光源在水平面上的旋转角度0~1 映射到 0°~180° float3 lightDirXZ normalize(float3(mainLight.direction.x, 0, mainLight.direction.z)); float3 faceForward normalize(float3(IN.normalWS.x, 0, IN.normalWS.z)); // 面部朝向 float lightAngle dot(lightDirXZ, faceForward) * 0.5 0.5; // 采样 SDF 贴图R 通道存储从正面到侧面的阴影阈值 float sdfValue SAMPLE_TEXTURE2D(_FaceShadowMap, sampler_FaceShadowMap, IN.uv).r; sdfValue _FaceShadowOffset; // 比较光照角度超过阈值时进入阴影 float faceShadow step(lightAngle, sdfValue);SDF 贴图制作可以用 Blender 烘焙或参考开源工具 Fake SDF Face Shadow 生成。4.6 描边 Pass —— 背面扩张法描边是 NPR 渲染最具辨识度的特征之一。背面扩张法是最常用的实现方案原理额外渲染一遍模型背面将每个顶点沿法线方向外扩一定距离配合只渲染背面Cull Front形成轮廓线。hlsl复制注意背面扩张法在硬边模型上容易出现描边断裂可以通过平滑法线Smooth Normal技术解决即在导入时将法线平均化后存储到切线的 W 分量中。五、完整片元着色器合并将以上所有模块整合到片元着色器中hlsl复制六、效果调参指南参数建议值作用_ShadowThreshold0.3 ~ 0.5阴影范围越大阴影越少_ShadowColor偏蓝/偏紫暖色冷色阴影更有动漫感_SpecularSize0.5 ~ 0.8高光斑大小_SpecularSmoothness0.01 ~ 0.05高光边缘硬度越小越锐利_RimWidth0.4 ~ 0.7边缘光宽度_RimColor天蓝/浅橙主角用冷色BOSS 用暖色_OutlineWidth0.015 ~ 0.04描边粗细近景偏细七、进阶拓展方向掌握基础 NPR 后可以继续探索平滑法线描边解决硬边模型描边断裂问题存储到顶点 UV2 通道多层 Ramp 阴影光照贴图 A 通道区分皮肤/布料/金属使用不同 Ramp 颜色面部 SDF 阴影导入高质量模型后烘焙 SDF 贴图实现专业级面部渲染屏幕空间描边基于后处理的深度/法线描边效果更全面角色 MatCap预烘焙反射贴图实现丝绸、金属等材质的卡通质感八、开源参考项目项目链接说明GenshinCelShaderURPGitHub仿原神风格完整 URP 实现Roystan Toon Shader教程经典入门教程代码清晰Unity-Chan Toon Shader 3官方包Unity 官方卡通渲染方案URP Toon Lit ExampleUWA Lab社区整理的 URP 卡通示例小结本文从零实现了一套基础的 NPR 角色渲染管线涵盖了✅Cel Shading赛璐璐阴影smoothstep 硬边 Ramp 图✅卡通高光截断式 Blinn-Phong✅边缘光轮廓增强✅SDF 面部阴影原理与实现✅背面扩张描边Outline PassNPR 渲染没有统一标准不同的美术风格需要不同的 Shader 参数和技术组合。建议多参考喜欢的游戏截图一边调参一边对比找到属于你项目的独特视觉风格。