从PMX到FBX再到Unity:二次元角色卡通渲染全流程解析
发布时间:2026/9/15 14:43:08来源:尧图网络
做二次元风格项目最绕不开的一环就是角色模型管线。Unity卡通渲染实战里从第三方转换来的原神PMX模型在Blender里整理好后导出成FBX再进Unity做渲染几乎是所有独立开发者和想要复刻二次元画风的人都会走的路径。不过这套流程并不像“导入-导出”四个字那么轻松材质丢失、骨骼错乱、模型巨大、旋转90度、阴影发黑……每一步都有对应的坑。这篇就把从PMX到FBX再到Unity Toon Shader的全链路讲清楚踩过的坑和能用上的参数一起给你。1. 开搞之前工具选型与版本避坑1.1 Blender版本与MMD插件选择先说你手上的Blender。PMX模型没办法直接被Unity识别通常需要Blender做一次“翻译”把MMD体系的数据转成通用格式。但Blender版本跨度很大从2.8一直到现在的4.x不同版本的插件兼容性差很多。我的建议是直接上Blender 3.6 LTS或者4.0以上的稳定版。3.6 LTS的好处是插件生态非常成熟不少老牌MMD工具在这上面运行最稳。4.x对材质节点系统和UV处理有优化但个别老插件没有跟上更新可能出现API报错。如果你手头已经是4.2打开插件时遇到“Register”相关的报错不要慌优先考虑换用维护活跃的mmd_tools分支或者临时装一个3.6 LTS做转换导出FBX后就在Unity里继续完全不冲突。插件这块目前主流就是开源的blender_mmd_tools在GitHub上能直接搜到。安装方式属于Blender标准流程:偏好设置(Preferences)里打开Add-ons面板点Install选择下载的zip压缩包启用后勾选“MikuMikuDance Model/Animation/Camera/Pose”系列。启用后File Import菜单里就会多出“MikuMikuDance Model (.pmx)”这一项。注意mmd_tools不仅支持导入模型还能导入动作和摄像机但做角色资产转换时我们只需要导入Model动作不要在这个阶段导入。不然动作数据会干扰后续的T-Pose复位导出FBX时容易把动画一起烘焙进去Unity里就会莫名多个带位移的默认动画。1.2 Unity版本与渲染管线选择Unity这边推荐2021.3 LTS或2022.3 LTS。这两个版本对URP(Universal Render Pipeline)的支持已经很成熟而且Shader Graph的节点稳定性在2021之后有了明显改善。做卡通渲染不建议再用Built-in Render Pipeline虽然很多老教程都在内置管线下调Toon Shader但URP在后处理、多平台兼容、项目规模化之后优势太明显了。你现在起新项目直接URP起步。URP下做二次元风格本质思路和传统PBR不同不追求物理正确追求的是“光照被控制”。所以后处理Volume、多光源管理、阴影采样这些能力URP都能给你不需要自己造轮子。还要确认你的Unity版本支持人类oid动画系统。原神PMX模型的骨骼结构一般是MMD风格和Unity Humanoid标准骨骼不完全一致但大多是“类人形”的导入时能映射到大部分Humanoid骨骼。如果你打算做角色换装或者动画重定向Rig这里的选择会决定后面的工作流。1.3 为什么必须走“PMX → Blender → FBX → Unity”有人会问能不能把PMX直接转FBXBlender是不是多余确实有一些命令行工具号称一步转换但实际效果很糟糕。原因在于PMX包含的信息不只是网格和贴图还有MMD专用的材質优先级、SPH/SPA高光贴图、刚体约束、表情变形Morph以及特殊骨骼这些信息在转换过程中需要人为判断哪些保留、哪些丢弃。Blender在这里扮演的角色就是一个“信息筛选器”你把PMX的所有信息倒进Blender手动删掉不需要的物理引擎数据整理材质检查法线然后在导出FBX时只打包Unity需要的那部分。只有经过这一道人工检查Unity里的角色才不会是“一团乱麻”。这就是为什么我不推荐跳过Blender哪怕多花十分钟后面能省几十个小时的排查时间。2. PMX导入Blender材质、骨骼与基础清理2.1 导入时的Scale参数有多重要第一次导入PMX之前先记住一个原则MMD体系里的单位是厘米Blender默认单位是米Unity单位也是米。如果你不管单位直接导进入Unity后模型会大到飞起。mmd_tools的导入面板里有一个Scale字段很多教程建议填0.08实际上这个值要看插件版本。我更推荐直接填0.01。比如原神角色身高一般是150到170“cm”在MMD文件里模型尺寸就是150到170个单位。导入Blender时如果Scale0.01身高就会变成1.5到1.7米这才是Unity场景里该有的角色大小。实操时建议先用默认值导入然后MeasureIt插件或者直接观察网格包围盒大小。如果你导入后模型看起来有十几层楼高说明Scale没设对。删除导入对象重新导入并修改Scale直到角色高度在1.5到1.7这个范围内。2.2 材质节点在Blender里只能“看个大概”导入完成后的场景结构一般是一个Armature骨骼、若干Mesh对象和一个空物体。展开材质标签你会发现MMD材质已经被插件转换成了Principled BSDF节点基础色贴图、法线贴图都被接好了。但这里有两个问题第一MMD的SPH/SPA高光贴图不会自动正确映射到Principled BSDF的对应参数上通常需要手动添加节点连接或直接忽略第二很多第三方转换模型使用的着色方式是MMD的Toon材质逻辑在Blender里的预览效果和最终Unity渲染差异巨大。所以我的建议是在Blender里不要花太多时间纠结材质颜色。你只要确认贴图路径没有爆红找不到贴图法线贴图能被识别顶点色信息还在就足够了。真正的材质重建在Unity里做那里才是最终渲染的主场。2.3 命名整理与多余对象清理第三方模型通常是从游戏里扒出来后有人手动重制绑定的命名可能乱七八糟比如骨骼名带着数字后缀Mesh名是拼音缩写。在这里花5分钟整理命名回到Unity后会省力很多。需要保留的Armature骨骼、带蒙皮权重的Mesh、空物体里的根节点。可以删除的刚体RigidBody、约束体Joint、碰撞体这些是MMD物理解算用的进入Unity后一般不使用况且FBX格式对刚体约束的支持也不友好留着只会让导出文件变臃肿。Mesh命名建议改成可辨识的英文或拼音比如Body, Hair, Face, Dress骨骼命名尽量保留原始日文或者英文映射表不要随意汉化否则Unity Humanoid自动映射会找不准。如果你要导入的模型有多个Mesh共用一个材质可以考虑合并Mesh但前提是它们都绑定同一套骨骼且UV互不干扰不然不要乱合。3. 模型修复与适配真正的技术活3.1 法线方向黑脸和阴影块状八成是它PMX模型在MMD里有一套自己的法线处理逻辑但到Blender里经常会遇到法线方向不一致的问题。最典型的是脸部或者裙摆内侧的法线朝内进Unity后表现成一块一块的黑斑或者阴影极其脏。导入模型后先切换右上角的材质预览模式到Solid然后打开右侧Overlay面板勾选Face Orientation红色面就是法线朝内的地方。遇到这种情况进入Edit Mode全选Mesh用ShiftN重新计算外侧法线。注意这个操作只能用在“封闭且可判断内外”的网格上比如头、躯干像头发这种片状Mesh强行重计算会把法线全部反掉所以头发和飘带的法线要单独检查必要时手动翻转。对于需要软硬边混合的二次元模型法线方向之外还要注意边线。MMD角色轮廓靠的是硬边和法线外扩如果你在Blender里把所有法线都设成光滑导出FBX后描边Pass可能不连续。建议在需要轮廓的边缘保留尖锐边或者干脆在Unity里靠Shader的顶点法线外扩来实现Blender这边只要保证法线方向对就行。3.2 T-Pose复位与骨骼绑定检查导入PMX时模型经常自带某个MMD动作或者半蹲姿势。如果带着这个姿势导出FBXUnity里会得到Angular Offset错乱的骨骼层级。所以进入Pose Mode选中全部骨骼然后执行Pose Clear Transform把骨骼恢复成Rest Pose。理想状态下角色应呈现T-Pose或者A-Pose手臂微张但不和躯干重叠。恢复完后在Object Mode下选中Mesh再选中Armature按CtrlP选择With Automatic Weights重新检查蒙皮权重这个操作大部分情况下是安全的因为它会基于网格到骨骼的最近距离重新计算权重。如果直接重算导致变形异常那就保持原来的权重只做Pose复位。还要留意一个点模型的Root Bone根骨通常是一个位于脚底或者骨盆的空骨骼它的位置决定后续Unity角色动画的位移坐标。在骨骼Outliner里找到这个根骨确认它的Transform位置是(0,0,0)不在的话就手动归零。根骨位置错了最后Unity里角色会腾空或入地。3.3 尺寸、单位与坐标系统一Blender和Unity的坐标轴不一致Blender是Z轴向上Unity是Y轴向上。这个不用说懂得都懂。但关键在导出FBX时如何处理。如果我们在Blender里用Y Forward、Z Up导出FBXUnity会将其转换为Y Up。这一步可以在导出面板里完成。但要注意模型的“前向”定义可能不同如果导入Unity后发现角色面朝X轴而不是Z轴不要自己去旋转整个模型那是治标不治本。正确做法是在Blender里把模型旋转到“正视图方向为Y轴负方向或正方向”再在导出选项中调整Forward轴反复试两次就能找到组合。尺寸方面Blender里如果角色已经是1.6米左右导出FBX时在Scale下拉框选择“FBX Units Scale”这样FBX文件内部会以厘米为单位记录数值Unity导入时会自动识别并转换回米Scale Factor保持1即可。不要在前面Apply Scalings里乱选很容易出现放大100倍的问题。4. 导出FBX参数设置的成败关键4.1 导出前检查清单不要觉得前面的准备工作够了直接File Export FBX完事。导出前必须先过一遍这几项角色是否为T-Pose/A-PosePose Mode下Clear Transform过是否为Mesh和Armature都应用了缩放CtrlA Apply Scale是否选中所有需要导出的对象且只选中这些对象是否有未清除的刚体或约束对象混在场景里。这个检查清单建议写在你工位旁边。我实际见过很多人前面都很顺利结果忘了Apply Scale导出到Unity后模型被拉成长条表情都扭曲了。4.2 FBX导出面板逐项设置Blender的FBX导出面板很繁琐但需要调整的核心项并不多。在导出面板里找到“Object Types”只勾选Armature和Mesh其他全部取消。如果场景里有灯光相机不用导出。Scale区域Apply Scalings选“FBX Units Scale”Forward / UpForward选-Y或YUp选Z具体看模型建模时的朝向。试过一次不对就反过来Unity预览里很快能看到结果。Armature区域Primary Bone AxisYSecondary Bone AxisX不要勾选“Add Leaf Bones”这个选项会在骨骼末端添加无意义的虚拟骨骼Unity里会出现一堆“_end”节点干扰Humanoid映射。Mesh区域Smoothing选Face。这会让Unity从FBX读取到更好的法线显示避免阴影出现三角面折痕。Apply Modifiers可以不勾选除非你的Blender修改器里有需要应用的细分。Animation区域如果只需要静态模型取消“Bake Animation”。如果后续要用MMD动作文件可以另外导出但这里选择不烘焙。勾选“Force Start/End Keying”不需要。最后点击Export。导出完成后我习惯用一个Windows自带的3D查看器或者直接Unity里建一个空项目先预览确认角色大小和朝向都没问题再开始真正的Unity材质工作。提示如果导出后打开FBX发现角色姿势扭曲多半是漏了第3章的T-Pose复位或者骨骼Rest Pose没保存。回到Blender再执行一次Clear Transform并重新导出。5. Unity导入与材质重建5.1 导入设置与Humanoid映射把FBX拖进Unity后等待导入完成在Inspector面板打开Model标签页。Scale Factor如果识别正确应该为1如果出现0.01或者100说明导出时的Scale设置有问题。再打开Rig标签页Animation Type选择Humanoid点击Apply。Unity会自动尝试映射骨骼大部分原神PMX模型可以映射成功但可能有一些小骨头比如马尾、摇曳裙摆映射不到。这没关系我们可以在Avatar配置里为这些骨骼选择正确的对应项或者直接把它们留在空槽里然后用Generic模式做自定义Animator。如果映射后角色也跟着出现了明显错位比如手插进身体里那大概率是骨骼层级问题。优先方案Rig里Animation Type切回Generic然后在SkinnedMeshRenderer组件上手动指定Root Bone和骨骼列表。这个方案虽然麻烦点但能保证渲染正确。场景里拖入角色后MeshRenderer或者SkinnedMeshRenderer上的材质会显示为默认的Lit Material论理这一步就开始重做材质了。不需要使用Unity提取的“Model Material”直接新建一个材质选择我们接下来要创建的Toon Shader。5.2 从零构建一个可用的URP Toon材质二次元渲染的核心可以拆成几个方向一级漫反射分界Cel Step、环绕高光、边缘光Fresnel Rim、描边Outline。你可以用Shader Graph轻松搭出来也可以直接用Shader代码写。考虑到性能优化和可维护性我更推荐直接写一个自定义Shader核心逻辑非常短。下面是一个基于URP的简易Toon Shader框架完全可以在Unity里使用并保存为.shader文件Shader Custom/ToonCharacter { Properties { _BaseMap (Base Texture, 2D) white {} _NormalMap (Normal Map, 2D) bump {} _RampMap (Ramp Map, 2D) white {} _Step (Shadow Step, Range(0, 1)) 0.5 _ShadowColor (Shadow Color, Color) (0.6, 0.6, 0.7, 1) _RimColor (Rim Color, Color) (1, 1, 1, 1) _RimPower (Rim Power, Range(0, 10)) 3 _OutlineWidth (Outline Width, Range(0, 0.05)) 0.01 _OutlineColor (Outline Color, Color) (0, 0, 0, 1) } SubShader { Tags { RenderType Opaque RenderPipeline UniversalPipeline Queue Geometry } Pass { Name ForwardLit Tags { LightMode UniversalForward } Cull Back HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float2 uv : TEXCOORD0; }; struct Varyings { float4 positionHCS : SV_POSITION; float3 positionWS : TEXCOORD0; float3 normalWS : TEXCOORD1; float2 uv : TEXCOORD2; }; TEXTURE2D(_BaseMap); SAMPLER(sampler_BaseMap); TEXTURE2D(_RampMap); SAMPLER(sampler_RampMap); TEXTURE2D(_NormalMap); SAMPLER(sampler_NormalMap); CBUFFER_START(UnityPerMaterial) float _Step; float4 _ShadowColor; float4 _RimColor; float _RimPower; CBUFFER_END Varyings vert(Attributes IN) { Varyings OUT; VertexPositionInputs posInputs GetVertexPositionInputs(IN.positionOS.xyz); OUT.positionHCS posInputs.positionCS; OUT.positionWS posInputs.positionWS; VertexNormalInputs normalInputs GetVertexNormalInputs(IN.normalOS); OUT.normalWS normalInputs.normalWS; OUT.uv IN.uv; return OUT; } half4 frag(Varyings IN) : SV_Target { half4 baseMap SAMPLE_TEXTURE2D(_BaseMap, sampler_BaseMap, IN.uv); Light mainLight GetMainLight(); half3 normalWS normalize(IN.normalWS); half NdotL dot(normalWS, mainLight.direction) * 0.5 0.5; half shadowStep smoothstep(_Step - 0.05, _Step 0.05, NdotL); half3 ramp SAMPLE_TEXTURE2D(_RampMap, sampler_RampMap, float2(shadowStep, 0.5)).rgb; half3 shadowColor lerp(_ShadowColor.rgb, 1, shadowStep); half3 direct mainLight.color * ramp * shadowColor; half3 viewDirWS GetWorldSpaceNormalizeViewDir(IN.positionWS); half fresnel pow(1.0 - saturate(dot(normalWS, viewDirWS)), _RimPower); half3 rim _RimColor.rgb * fresnel * shadowStep; half3 finalColor baseMap.rgb * (direct rim) rim * 0.3; return half4(finalColor, 1); } ENDHLSL } Pass { Name Outline Cull Front Tags { LightMode SRPDefaultUnlit } HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; }; struct Varyings { float4 positionHCS : SV_POSITION; }; CBUFFER_START(UnityPerMaterial) float _OutlineWidth; float4 _OutlineColor; CBUFFER_END Varyings vert(Attributes IN) { Varyings OUT; float3 normalWS TransformObjectToWorldNormal(IN.normalOS); float3 posWS TransformObjectToWorld(IN.positionOS.xyz); posWS normalWS * _OutlineWidth; OUT.positionHCS TransformWorldToHClip(posWS); return OUT; } half4 frag(Varyings IN) : SV_Target { return _OutlineColor; } ENDHLSL } } }这段代码不算成品但已经把骨架给出来了主Pass里做两级阴影阈值和Ramp取样高光部分你可以根据需求再补一个Blinn-Phong高光权值阈值化处理。Outline Pass利用Cull Front加顶点法线外扩实现描边这是目前卡通角色最常用的描边方法。贴图连入时注意基础色贴图要设置sRGB法线贴图要改成Normal Map类型Ramp贴图的Wrap Mode建议设置为Clamp避免采样时产生接缝。5.3 顶点色、阴影与后处理细节二次元角色脸部的柔和阴影很多时候靠模型自带的顶点色来表达特别是脸的下半部分会有一层肤色阴影。这个数据在PMX里是顶点色导入Unity后SkinnedMeshRenderer会把顶点色传递到Shader你需要在Toon Shader里用到它比如和阴影色做乘法或lerp。很多成品材质比如mToon都内置了用顶点色控制阴影的能力你在自定义Shader里也可以开放一个_UseVertexColor开关。灯光摆位上做主平行光之后阴影强度建议控制在0.6到0.9之间太硬的阴影会让卡通角色显脏。让主光源略微偏移角色面部阴影会形成好看的轮廓线这也是原神角色立绘里常见的打光思想。最后打开URP的Volume添加Bloom、Tonemapping推荐Neutral、Color Adjustments和Vignette。Bloom的阈值和强度对二次元风格影响很大建议Threshol1, Intensity0.2起步Vignette给0.1左右能让屏幕边缘自然压暗画面立刻有“游戏感”。6. 踏过的坑常见问题与排查实录6.1 常见问题速查表这里整理一张快速定位问题的表格如果你的项目出现类似现象照着检查。现象常见原因处理方案Unity中模型全黑材质用的是Built-in管线ShaderURP不支持改用URP材质或自定义Toon Shader模型旋转90度或者躺倒Blender的Z-up和Unity的Y-up未对齐导出FBX时设置Forward为-YUp为Z同时应用Transform角色巨大或微小PMX导入Blender时Scale设置错误重新导入并设Scale0.01或Unity中调整Scale Factor骨骼错乱层级分离导出时勾选了Leaf Bones或者骨骼未复位去掉Add Leaf BonesPose Mode下Clear Transform动起来模型撕裂/扭曲自动权重重算失败或者Root Bone设置错误检查根骨位置和SkinnedMeshRenderer的Root Bone必要时手动指定骨骼列表脸部有块状阴影法线方向不对Blender中重计算法线并确保脸部为平滑着色描边断裂、断断续续法线硬边导致顶点法线不连续在Blender中给模型统一法线或调整Outline Pass的外扩距离贴图颜色发灰贴图不是sRGBTexture Import设置中把sRGB勾选上游戏画面整体很平没有卡通感缺少后处理Volume加Bloom、Vignette、Color Adjustments6.2 一个特别容易踩的坑Avatar报错Humanoid映射失败时的报错往往让人摸不着头脑。Unity提示“Bone Hips is not assigned”但明明模型里就有Hips骨骼。这时候不要慌大概率是骨骼名被Blender导出时加了后缀或者骨骼层级中多了一个空节点。解决办法分两步。第一回到Blender在Outliner里检查骨骼树形结构确保根骨下面第一层就是Hips。第二在Unity的Rig面板点“Configure Avatar”手动把Hips骨骼指定给模型中的实际骨骼。指定完Apply后再Viwe映射绿色对勾说明Avatar已经生效。如果死活映射不上退回Generic模式在Animator组件里不要设置Avatar直接让SkinnedMeshRenderer根据FBX的骨骼信息渲染。这种方式虽然不能使用Humanoid动画重定向但角色本身动画和渲染完全没问题对只做渲染展示完全够用。6.3 阴影与光照问题的排查清单卡通渲染里阴影“脏”是高频问题但很多其实是灯光参数的问题。主平行光阴影Bias设置过高会让阴影和模型之间出现漏光空隙Bias太低又会造成阴影抖动。一般来说Bias控制在0到0.05之间Normal Bias为0到1。URP的Shadow Settings面板能找到这两个参数实际要边调边看。此外确认模型材质里是否打开了“Receive Shadows”以及是否用了URP的阴影采样函数比如GetMainLight返回的shadowAttenuation有没有被应用到漫反射颜色。7. 一点个人实操后的建议这套PMX到Unity的流程我前后跑过很多次最后固定下来的步骤就是先在Blender里用Scale0.01导入花几分钟清刚体、复位Pose、重算法线再按“FBX Units Scale / 去掉Leaf Bones”导出Unity里用自定义Toon Shader重建材质。这套关键路径一旦定下来后面每个项目都顺畅很多。如果你第一次做完发现还是有不顺的地方建议从两步自查一是导出前检查骨骼是否真的T-Pose且应用了缩放二是导入Unity后先建一个空材质球确认模型基本渲染正常再做贴图和Shader连接。把这两步和报告问题九成的坑都能自己定位。关于模型版权方面PMX转换模型仅用于个人学习与技术验证就够了不要拿去做涉及商业传播的内容这是基本的社区共识。最后再分享一个小技巧如果你后续要为不同角色做统一的卡通渲染不要一个角色一个Shader把公共能力抽成一个ToonShader的变体用贴图区分角色特征这样整个项目的渲染风格统一性能也更好控制。
网站建设高端定制企业官网