体素风格描边指南:3D像素艺术轮廓线的Shader实现

发布时间:2026/10/6 19:55:00
体素风格描边指南:3D像素艺术轮廓线的Shader实现
做体素风格项目的时候我踩过一个特别大的坑3D像素艺术模型丢进引擎里怎么看都像没做完的半成品。不是贴图糊也不是光照不对而是角色和场景之间缺少了一道明确的视觉边界模型直接“融”进背景里。后来我才反应过来这个“边界”就是轮廓线。这篇文章想聊的就是给3D像素艺术加轮廓线的完整思路从方案选型、Shader实现到局部宽度控制和各种实战翻车记录。我尽量写得实在一点都是实际跑过项目才总结出来的东西。不管你是用Unity做独立游戏还是在Godot里拼体素场景甚至只是给Blender里的方块模型做渲染这套思路基本都能直接用。轮廓线这个东西放在普通PBR材质上可能只是锦上添花但在3D像素艺术里它几乎是刚需。原因也简单体素模型的面数虽然多但每个面都非常平整颜色也大块缺少了高模那种丰富的明暗过渡之后物体之间的前后关系就只能靠颜色差异硬撑。地面是灰的、墙体是灰的、角色也是灰的玩家根本分不清谁是谁。加一道轮廓线等于给每个物体画了一圈明显的“边界”视觉辨识度瞬间就上来了。这篇文章主体部分大概会从“为什么要做、有哪些做法、怎么做到局部调参、踩过什么坑、最终怎么优化性能”这几个角度展开你可以根据自己的项目阶段跳到对应章节看。1. 体素块的“存在感”问题为什么越简单的大块面越需要描边1.1 从一次失败的渲染截图说起我之前做过一个体素风格的小Demo场景是黄昏下的村庄角色是那种标准方块人。所有模型都是我自己在MagicaVoxel里搭的导出FBX之后丢进Unity套一层Lambert材质再摆几个平行光。乍一看挺干净但截图发给朋友看对方第一句话是“你主角在哪”我仔细一看角色的棕色衣服和背景的泥土路几乎同一个亮度身体侧面和墙面连成一片不是仔细找根本看不出来。当时我以为是光照参数不对调了半天色温和强度效果依然一般。后来我把所有材质临时换成纯色才发现问题不在光照而在“边缘”。体素模型的共性就是块面大、纹理少、明暗平这种特性放到现实世界里可能很干净但在游戏画面里就缺少了一种叫“轮廓感知”的东西。人的视觉系统天然依赖边界的明暗差来分辨物体画面里如果到处都是大色块而没有清晰的边界线大脑就会觉得这画面没对上焦。1.2 轮廓线在像素画里的真正作用其实像素画里一直有轮廓线这个传统。你看经典的2D像素游戏角色、道具、NPC几乎都会描一圈深色边。手绘像素画里这叫“外轮廓”它的作用不只是装饰而是在低分辨率下强制定义物体的形状。到了3D像素艺术里这件事并没有消失反而因为多了一个维度更难做。3D里没有固定的“视角边界”轮廓线得跟着物体几何实时变化这就催生了各种实时描边技术。你可能会想那我直接把模型边缘做亮一点、或者让模型背光行不行理论上可以但效果不稳定。轮廓线本质上是一个和光照无关的“几何语义”信息它告诉你“这个物体的边界在这里”而不是“这个面的亮度是多少”。所以与其指望光照不如专门做一条独立于光照的描边通道。这种做法在动画电影、卡通渲染里已经用了很多年只是到了3D像素艺术这个细分领域很多人反而忘了用。2. 四类描边方案对比从后处理到几何挤出的取舍2.1 后处理边缘检测全局统一但控制粒度太粗最常见的方案是屏幕空间后处理思路是拿深度缓冲和法线缓冲做边缘检测。像素只要发现周围深度或者法线方向出现了突变就判定为边缘涂上轮廓色。这方案的好处在全局统一整个画面里的物体都有轮廓线不会出现物体A有描边、物体B没有的情况。但它的缺点也很明显。第一边缘检测的阈值很难调阈值调低了远处两个物体之间的缝会疯狂闪线调高了近处模型的拐角又消失。第二它不好做局部控制你想让主角描边粗一点、背景石块描边细一点用后处理就得单独拆RenderTexture或者写深度Mask麻烦。第三后处理全屏采样的开销在移动端不算小。所以它更适合那种“整个游戏世界统一风格不需要单独调参”的项目。2.2 反向面挤出体素模型最匹配的几何方案我最终选的是反向面挤出Inverted Hull方案也就是把模型沿法线方向外扩一圈再绘制外扩后的背面。这个思路最早用在卡通渲染里做法是正常Pass画一遍模型表面然后第二个Pass把顶点沿法线向外推一点同时用Cull Front只画背面也就是朝向相机外侧的面颜色直接输出纯色。由于挤出去的形体和原模型轮廓一样从相机看过去它正好卡在原模型边缘外一圈看起来就是轮廓线。这方案特别适合3D像素艺术原因很微妙体素模型的法线非常规整几乎每个面都是精确的90度角外扩之后不会出现高模那种平滑法线导致的描边忽粗忽细的问题。而且它天然支持局部调宽你给每个顶点塞一个顶点色数值乘到外扩距离上就能实现同一个模型不同部位、描边粗细不同的效果。自定义程度、可控性、性能开销都处于一个很舒服的平衡点。下文主体内容主要围绕这个方案展开。2.3 Mask膨胀和体素邻居分析两种小众路线除了上面两种还有两个思路。一个是Mask膨胀把模型渲染成纯白色的裁剪图再做类似Bloom的向外扩散把扩张出的部分染色成轮廓。这方案可以做到非常平滑的描边在细节丰富的模型上表现不错但缺点是要多一张RT、多一个Pass而且很难做出像素风喜欢的硬边锯齿感。另一个是体素数据层面的邻居分析直接遍历体素网格如果发现一个方块有相邻方块就对内部面做标记不渲染只有暴露在外的面才参与描边计算。这方案在“生成体素地形”时最干净因为你手里有完整的网格数据可以直接算暴露面。但如果你的模型是美术手搭出来的或者从MagicaVoxel导入后再被改过这套逻辑就很难覆盖到所有情况。下表是四种方案在几个关键维度上的对比方便你快速定位方案描边效果单物体调参能力场景全局统一性额外开销后处理边缘检测受阈值影响易抖动弱基本靠深度Mask强画面所有物体统一全屏RT采样反向面挤出硬边锐利适合体素强顶点色/材质参数均可控制中需要各模型自己开描边多一个Pass、多一次顶点计算Mask膨胀平滑柔和可控性好中靠Mask粗细控制较强额外RT Bloom式扩散体素邻居分析最精确边缘和块完美贴合强但依赖数据结构强离线计算量偏大3. 反向面挤出法Unity URP下的完整实现与原理拆解3.1 原理为什么“翻转剔除法线外扩”能画出轮廓展开聊一下反向面挤出的数学直觉。一个物体表面上有无数个三角形每个三角形都有一个朝外的法线。正常渲染时我们只画法线朝向相机这一侧的面默认Cull Back剔除背面。反向面挤出做的事情是先把顶点沿着法线方向往外面推一段距离然后把剔除规则反过来只画背面。这样原本物体的正面就不画了画的是那一层“外皮”的内侧面。因为外皮是沿着表面法线推出去的它的边缘必然包裹在原模型轮廓的外围。光和影都不参与计算函数也只用混合输出一种颜色所以视觉上就是一圈纯色描边。最重要的一点顶点外扩的画法不会遮挡原模型因为外皮的内侧片元和原模型正面在空间上几乎是贴着的深度上原模型正面离相机更近自然就把外皮的内侧盖住了最后露出来的只有最外围那一圈。你可能会问外皮是实心的会不会把原模型包住导致什么都看不见不会因为你画的是外皮的背面也就是朝向模型内部的那一面正面被剔除了所以观察者看到的是原模型加上外皮在轮廓处的边沿。这是整个方案的几何基础。3.2 可直接跑的Shader主Pass与描边Pass下面给一个在Unity URP下能直接跑的Shader框架主Pass我用了最简单的漫反射模拟光照重点在后面的描边Pass。你如果项目中已经有自定义的Lit Shader把描边Pass单独摘出来追加到那个Shader的SubShader末尾也行。Shader Custom/VoxelOutline { Properties { _BaseColor (Base Color, Color) (1,1,1,1) _MainTex (Main Texture, 2D) white {} _OutlineColor (Outline Color, Color) (0,0,0,1) _OutlineWidth (Outline Width, Range(0, 0.1)) 0.02 } SubShader { Tags { RenderTypeOpaque RenderPipelineUniversalPipeline QueueGeometry } // ---------- 主Pass简化漫反射 ---------- Pass { Name ForwardLit Tags { LightModeUniversalForward } 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 normalWS : TEXCOORD0; float2 uv : TEXCOORD1; }; TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex); float4 _MainTex_ST; half4 _BaseColor; Varyings vert (Attributes v) { Varyings o; VertexPositionInputs posInputs GetVertexPositionInputs(v.positionOS.xyz); VertexNormalInputs normalInputs GetVertexNormalInputs(v.normalOS); o.positionHCS posInputs.positionCS; o.normalWS normalInputs.normalWS; o.uv TRANSFORM_TEX(v.uv, _MainTex); return o; } half4 frag (Varyings i) : SV_Target { half4 tex SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv); half3 albedo tex.rgb * _BaseColor.rgb; Light mainLight GetMainLight(); half ndotl saturate(dot(i.normalWS, mainLight.direction)); half3 lighting mainLight.color * ndotl half3(0.15, 0.15, 0.15); half3 ambient SampleSH(i.normalWS); lighting ambient; return half4(albedo * lighting, 1.0); } ENDHLSL } // ---------- 描边Pass ---------- Pass { Name Outline Cull Front ZWrite On HLSLPROGRAM #pragma vertex vert #pragma fragment frag #include Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl half4 _OutlineColor; float _OutlineWidth; struct Attributes { float4 positionOS : POSITION; float3 normalOS : NORMAL; float4 color : COLOR; }; struct Varyings { float4 positionHCS : SV_POSITION; }; Varyings vert (Attributes v) { Varyings o; float3 positionWS TransformObjectToWorld(v.positionOS.xyz); float3 normalWS TransformObjectToWorldNormal(v.normalOS); // 用顶点色alpha控制局部描边强度后面细讲 float outlineWidth _OutlineWidth * v.color.a; positionWS normalWS * outlineWidth; o.positionHCS TransformWorldToHClip(positionWS); return o; } half4 frag (Varyings i) : SV_Target { return _OutlineColor; } ENDHLSL } } }这里有几处值得说明。主Pass的SampleSH会对某些方向的环境光产生偏紫的倾向正式项目如果你用了Lightmap或者环境探针建议直接换成你项目里已有的光照方案。本文不是为了教光照所以用了最简单的“方向光NDotL 环境光”组合。描边Pass我用的是Cull Front也就是只画背面。ZWrite On表示描边也会写入深度防止多物体互相遮挡时出现排序问题。描边Pass的LightMode标签在不同URP版本下的表现略有差异如果你的URP版本更新到某个版本后描边Pass不显示了试试把它的LightMode改成SRPDefaultUnlit这是URP多Pass描边最常见的坑之一。3.3 参数怎么调宽度、颜色和渲染顺序_OutlineWidth的合适取值跟你的模型比例强相关。一个身高1.8单位的人形角色宽度在0.008到0.025之间通常比较舒服如果是体素地形里的巨石宽度就要相对缩小。我习惯用一个比例思想描边宽度大约占模型最大边长的千分之五到百分之一。你先取千分之八跑一帧然后凭感觉微调。颜色的选择也要多说一句。很多新手直接上纯黑色结果像素风格的小方块边缘一圈纯黑会显得脏因为体素艺术本身的颜色本来就很“块”再用压死人的黑色描边画面会发闷。我建议从模型固有色出发把亮度压到30%左右饱和度稍微降一点这样描边既有轮廓感又和模型本身的色调融为一体。在Material面板上手动调也行但如果是批量生成材质尽量预先算好颜色。渲染顺序方面描边Pass最好保持在主Pass之后利用深度缓冲遮挡原模型内部的外皮片元。如果你发现某个物体描边被其他物体盖住或者描边盖住了不该盖的细节优先检查一下ZWrite和ZTest的状态。4. 用顶点色给轮廓线做“雕刻”局部宽度控制的进阶玩法4.1 为什么均匀描边会破坏体素美的节奏把上面的Shader挂上去之后你会看到每个模型都有一圈均匀的黑色描边。这时候再截一张图问题又来了所有方块无论大小、无论是否处在视野边缘都描一样粗的边画面就变得有点像一堆积木外包了一圈黑色橡皮泥。手绘像素画的轮廓线从来不是均匀的。角色头顶、肩膀这种明显外凸的地方描边会稍微重一点衣服褶皱、地面砖缝这些内部细节往往只有很淡的线甚至没有线。这种粗细节奏是像素画“手绘感”的重要来源。3D像素艺术如果描边完全均匀机器感就出来了所以需要局部控制。控制手段说来也简单在顶点色alpha通道里存一个“描边强度”然后在描边Pass里把外扩距离乘上这个值。alpha等于1的地方完整描边alpha等于0的地方描边消失。这样同一个Mesh上就能实现渐变过渡的描边宽度。4.2 编辑器脚本一键生成顶点色遮罩手工给体素模型刷顶点色显然不现实但我们可以写一段C#脚本自动生成。一个常见的规则是法线越接近水平的面越有可能是模型轮廓的外侧给更高的描边权重法线朝上和朝下的面大部分时候是作为“顶面”和“底面”出现很少会形成视觉轮廓权重就降低。你可以把这段代码放到Editor菜单里批量处理所有模型using UnityEditor; using UnityEngine; public class OutlineMaskGenerator { [MenuItem(Tools/Generate Outline Mask)] static void GenerateOutlineMask() { foreach (var filter in FindObjectsOfTypeMeshFilter()) { Mesh mesh filter.sharedMesh; if (mesh null) continue; Vector3[] verts mesh.vertices; Vector3[] normals mesh.normals; Color[] colors new Color[verts.Length]; for (int i 0; i verts.Length; i) { // 法线越偏水平越接近轮廓边缘 float a Mathf.Clamp01(1f - Mathf.Abs(normals[i].y)); colors[i] new Color(0f, 0f, 0f, a); } mesh.colors colors; EditorUtility.SetDirty(mesh); } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } }这段代码的核心是normals[i].y它描述了顶点法线和世界竖直方向的夹角。y接近0说明这个法线基本是水平的对应的面大概率是物体的侧壁也就是最需要描边的地方y接近1的顶面一般不会产生强烈的轮廓所以把alpha压到很低。跑完这段脚本之后回到Unity场景里你会发现模型的描边已经不再是均匀一圈了侧面的轮廓明显顶面的描边非常淡甚至消失。这个细节会让画面立刻向“手绘像素感”靠拢一大步。这里有一个容易忽略的点如果你用来生成顶点色的模型是从Asset Store导入的Mesh可能设置了Read/Write Enabled需求缺失脚本修改后不生效。建议在生成脚本里加一层判断把isReadable强制设为true修改完再决定要不要恢复。4.3 进阶按二面角自动调整描边强度如果你觉得“法线水平”这种规则还是太粗暴可以换一个更精准的维度二面角。二面角是指同一根边相连的两个面之间的夹角。夹角越接近180度说明那两个面几乎在同一平面上这里根本不会形成轮廓描边可以去掉夹角越小说明这是一个明显的折角是视觉轮廓线最可能出现的位置。体素模型大多是90度的方块边二面角基本都小于90度所以全保留。但你做半体素化场景时经常会有一些斜坡、台阶、柱子这些地方如果用统一宽度描边会显得特别“楞”。按二面角生成顶点色alpha的思路是遍历Mesh的所有三角形找到共享边对边上两个顶点的alpha乘一个系数夹角越大系数越小。二面角的计算不复杂两个三角形的法线点乘值越接近1夹角越接近180度。但在Unity Mesh数据结构里要遍历邻接边得用mesh.triangles配合顶点索引自己构建邻接关系。项目里我一般直接用ProBuilder或者MagicaVoxel导出的模型做离线数据计算不在运行时做因为Runtime计算会导致启动或加载卡顿。5. 实战踩坑透明物体、断线与合批问题处理实录5.1 透明物体的描边穿帮渲染队列与深度写入的坑第一个让我头疼的问题是半透明物体。比如体素花丛、玻璃块、粒子效果它们本身要关闭深度写入才能实现半透明融合但反向面挤出描边需要依赖深度关系来盖住内部外皮。当一个半透明物体站在一个实心方块前面时描边Pass因为深度写不了就会把轮廓线画到实心方块前面看起来像描边“穿模”了。试过几种解决办法。最简单的是一刀切透明物体不参与描边。体素游戏里草、树叶、水这些半透明物件即使没有描边也不会太违和因为它们本身就是细小密集的物体靠本身形状就能分辨。第二种方案是用Stencil先给不透明物体描边再用Stencil标记遮罩透明区域成本偏高但可以做到细节完善。我个人的实际选择是材质上设一个开关默认勾选“启用描边”只有半透明材质关闭它。5.2 描边断裂与多Mesh合并体素模型生产链上的老问题第二个问题是描边断裂。尤其是那种用多个独立Cube拼起来的场景比如房间、桥梁、箱子堆每个方块导入后是独立Mesh就算你给每个Cube都套了带描边Pass的材质它们之间的接缝处也会出现断线甚至露出很细的缝隙光线。原因并不难理解描边Pass是在每个Mesh自己的法线方向上外扩相邻两个Cube的接触面都有各自朝外的法线外扩之后彼此之间没有连续性自然会对不齐。解决思路是合并Mesh。在体素编辑器里导出一整片地形时尽量合并成一个Mesh再导入如果已经是多个Mesh就在Unity里用Mesh.CombineMeshes把它们合到一起。合并之后再跑一遍顶点色生成脚本让描边在合并后的面上按规则统一计算。合并且保留硬边这一点尤其重要。体素风格需要每个面有明确的转折如果合并时顺手做了顶点焊接和法线平均模型表面会变圆润像素感就没了。所以合并Mesh之后法线不要平均保持每个面各自的原始法线。描边Pass对外扩方向极其敏感法线一旦被平均轮廓线就会在棱角处变得歪歪扭扭。5.3 SRP Batcher兼容性与Instancing矩阵错乱第三个坑在URP里非常典型。描边Pass和主Pass如果各自声明了不同的CBUFFER变量Unity的SRP Batcher会认为这个Shader不兼容你会在Stats面板看到大量SRP Batch不通过的提示。还有一个变体是你用了GPU Instancing批量画很多方块描边Pass里的TransformObjectToWorld读取的unity_ObjectToWorld矩阵在某些合批路径下并不准确导致轮廓线跑到十万八千里远。解决办法有两个层面。如果只是希望DrawCall少一点最好把所有方块在建模阶段合并到一个Mesh里场景里只放几个Mesh即可不要依赖GPU Instancing。如果确实需要动态生成大量体素方块比如挖地、建造那就要保证描边Pass也开启Instancing支持并且在Shader里用unity_ObjectToWorld时确认宏UNITY_INSTANCING_ENABLED或UNITY_DOTS_INSTANCING_ENABLED被正确启用。最简单粗暴但绕开这些排斥反应的方案是把Shader里两个Pass的变量统一写进同一个CBUFFER_START(UnityPerMaterial)确保SRP Batcher认为它们来自同一个布局。5.4 远处描边过粗用一个距离系数救回来这个坑其实比前几个更隐蔽。反向面挤出在世界空间里外扩固定距离远看时这个固定距离在屏幕空间里会变得很小描边理论上应该越来越细。但体素模型低模面数少远处如果描边消失模型的辨识度又会掉回原点。实证之后我发现更合适的策略是描边宽度随距离反比例变化保持它在屏幕空间始终占据一个相对一致的宽度距离越远外扩距离反而要适当增大才能维持轮廓线的存在感。这段逻辑可以在顶点着色器里加拿当前顶点世界坐标和_WorldSpaceCameraPos算距离乘上一个系数float dist distance(positionWS, _WorldSpaceCameraPos); float distanceScale saturate(dist * _DistanceFactor); positionWS normalWS * outlineWidth * (1.0 distanceScale);_DistanceFactor设置得小远处描边略微加强设置得大描边远看几乎不变。具体阈值得按你的相机FOV和场景尺度调。我在一个体素沙盒项目里用0.08左右的值近处描边和远处描边在视觉上保持了很好的连贯性。6. 从“能看”到“漂亮”颜色、抖动与性能预算6.1 别用纯黑描边把基础色融进轮廓里前面提过颜色问题这里展开说。纯黑描边在少量物体上没问题但整个场景的物体全都描纯黑画面会像被记号笔圈过一样。一个更耐看的做法是描边颜色取模型固有色乘以一个压暗系数。你可以给描边Pass传入一个外部颜色也可以利用顶点色——既然描边强度已经存在alpha通道了RGB通道空着也是空着干脆把基础色也塞进去然后在描边Pass里稍微压暗输出。具体操作比较简单在材质属性里加两个颜色头一个给主Color一个给OutlineColor。美术在做材质的时候直接用吸管吸取模型的活泼固有色把亮度降到0.35再用这个颜色作为描边色。很多卡通渲染动画的轮廓线其实都不是纯黑而是比角色固有色深一个档位的颜色。你离远了一看照样觉得那是一条黑线但离近了仔细看它跟角色整体色调是谐和的不会有那种“贴了一张黑色贴纸”的感觉。6.2 让轮廓线有像素画的“手绘感”宽度噪声第二个风格化技巧是给描边宽度加一点伪随机噪声。手绘像素画的线条永远不可能像机器一样笔直均匀偶尔会有几段稍微粗一点、几段稍微细一点。这个效果在反向面挤出方案里做起来非常便宜只需要在顶点着色器里对顶点坐标做一个hash再乘到外扩宽度上float hash(float3 p) { return frac(sin(dot(p, float3(12.9898, 78.233, 37.719))) * 43758.5453); } float noiseWidth outlineWidth * (0.8 0.4 * hash(positionWS)); positionWS normalWS * noiseWidth;不过要小心给所有顶点加同样的噪声会导致相邻面边缘宽度不一致出现“漏光”或者“毛刺”。体素风格本来就是硬边如果你加到0.4这个水平部分边缘会像被啃过一样。实际项目里我会把噪声幅度控制在0.15以内或者只在部分大模型上开这个效果小物件保持干净利落。风格化的前提是克制轮廓线只是帮模型把结构说明白不是让模型看起来更脏。6.3 移动端性能预算几何描边其实比想象中便宜最后说性能。很多人一看到多一个Pass就觉得移动端要爆炸实际上反向面挤出在移动端的开销是可控的。描边Pass没有纹理采样也没有复杂的逐像素光照只有一个顶点位移加一次纯色输出。体素模型的面数量通常不高场景里五万个三角面左右的模型开启描边之后增加的GPU负载远小于你开一个Bloom后处理或者全屏噪声。真正要注意的是OverDraw。描边Pass渲染的是外扩后的背面它覆盖的区域其实只是原模型外围一圈大部分像素在遮挡剔除后没有多少有效写入。反倒是后处理边缘检测这类方案全屏范围每帧要跑好几遍采样移动端的填充率压力会明显大很多。我的经验值参考在骁龙7系级别的手机上一个活动物体数量在200到400个的小型体素场景开启几何描边后不降帧如果把描边改成后处理方案同场景会有肉眼可感知的发热和掉帧。另外描边Pass会额外增加一半的DrawCall因为同一个物体会被渲染两次。如果你场景里有很多独立的小方块建议继续遵守前文说的“尽量合并Mesh”原则把块数压下去。Register的批处理开销永远比你自己多调几个Shader参数PV更值得关注。如果你只是想起步试试从第三章的Shader抄下来把_OutlineWidth调到0.01挂到模型上跑一帧看看大概率已经能出不错的效果。我自己的经验是轮廓线宽度宁可细一点也别太粗粗了容易盖住体素块本身的结构感。3D像素艺术的美感在于“每个块都清楚”轮廓线只是帮忙把这层清楚推到观众眼前而不是替模型加一套外骨骼。等你把局部顶点色和距离缩放都搞定再回头对比第一版画面质感的提升基本是肉眼可见的。