
简介这是一份基于Unity引擎实现的陶艺制作模拟软件工程源码面向具备一定Unity基础、希望深入理解动态网格建模与实时形变技术的开发者。其核心解决的是如何在运行时动态生成模型、实时调整顶点位置并平滑法线从而模拟陶土在拉坯过程中被塑形的效果可应用于陶艺教学演示、互动装置或游戏化手工体验等场景。资源包共43个文件包含20个asset资源文件、11个meta、2个mat材质、2个json配置、2个cs脚本以及unity场景、csproj工程、sln解决方案等整体约56KB体积轻量但结构完整涵盖场景、材质、脚本与工程配置等模块。目前已有2500人学习下载。通过研读其中的C#脚本与场景组织方式读者可以掌握动态Mesh顶点更新、法线平滑处理等关键实现思路并直接在此基础上扩展出更丰富的陶艺交互功能适合作为动态建模方向的学习参考与二次开发起点。1. 从一团泥到一件器Unity陶艺制作模拟软件到底在模拟什么很多人第一次听到「Unity陶艺制作模拟软件」脑子里浮现的是把一坨泥放在转盘上手指一按就自动成型。真动手做才发现难点根本不在建模而在「泥」这个材质本身——它要能被拉伸、被挤压、被削薄还要在旋转中保持体积感这跟做刚体碰撞、做布料完全是两套思路。陶艺模拟的核心是把拉坯throwing这个连续形变过程用网格顶点位移加实时法线重算表达出来再叠加一层手感反馈。它适合三类人想做非遗数字化展示的团队、想练手 Unity 网格与 Shader 的进阶开发者、以及做交互装置或教育类应用的从业者。这一篇不讲空泛概念只讲怎么在 Unity 里把「转盘 泥坯 手」这条链路跑通参数怎么调哪里最容易翻车。2. 拉坯的数学底座为什么用圆柱参数化网格而不是普通 Mesh2.1 泥坯为什么必须参数化普通建模出来的陶罐是一个静态 Mesh顶点位置写死。但拉坯过程中泥坯的半径随高度变化手一压某一圈的半径就变小同时上下相邻圈要跟着联动否则会出现撕裂或自交。所以泥坯不能当成普通模型得当成一张「半径关于高度」的曲线绕 Y 轴旋转生成。常见做法是用一个二维数组radius[i]表示第 i 层的高度对应的半径层数一般取 64 到 128圈向分段取 48 到 96。层数太少手指压下去会出现明显棱角圈向分段太少旋转时能看到多边形边。这个参数化结构的好处是任何一次形变本质都是改radius数组里的某几个值然后整张网格重算。它天然保证了泥坯是旋转体不会出现奇怪的扭曲。代价是它没法表达非对称造型比如捏出一个歪嘴壶——那属于后期雕刻阶段不在拉坯环节解决。2.2 最小可跑通的网格生成脚本下面这段代码在 Unity 里生成一个基础泥坯挂到空物体上就能看到圆柱体。它不依赖任何第三方包纯 C# 加 Mesh API。using UnityEngine; [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class ClayMeshGenerator : MonoBehaviour { [Range(32, 128)] public int heightSegments 64; // 高度方向层数 [Range(24, 96)] public int radialSegments 64; // 圆周方向分段 public float baseRadius 0.5f; // 底部半径 public float topRadius 0.35f; // 顶部半径 public float height 1.0f; // 泥坯总高 private Mesh mesh; private float[] radiusProfile; // 每层半径形变只改这里 void Start() { mesh new Mesh(); mesh.name ClayMesh; GetComponentMeshFilter().mesh mesh; radiusProfile new float[heightSegments 1]; for (int i 0; i heightSegments; i) { float t i / (float)heightSegments; radiusProfile[i] Mathf.Lerp(baseRadius, topRadius, t); } RebuildMesh(); } public void RebuildMesh() { int vCount (heightSegments 1) * (radialSegments 1); Vector3[] verts new Vector3[vCount]; Vector2[] uvs new Vector2[vCount]; int[] tris new int[heightSegments * radialSegments * 6]; for (int y 0; y heightSegments; y) { float h height * y / heightSegments; float r radiusProfile[y]; for (int x 0; x radialSegments; x) { float angle 2f * Mathf.PI * x / radialSegments; int idx y * (radialSegments 1) x; verts[idx] new Vector3(Mathf.Cos(angle) * r, h, Mathf.Sin(angle) * r); uvs[idx] new Vector2(x / (float)radialSegments, y / (float)heightSegments); } } int ti 0; for (int y 0; y heightSegments; y) { for (int x 0; x radialSegments; x) { int a y * (radialSegments 1) x; int b a radialSegments 1; tris[ti] a; tris[ti] b; tris[ti] a 1; tris[ti] a 1; tris[ti] b; tris[ti] b 1; } } mesh.Clear(); mesh.vertices verts; mesh.uv uvs; mesh.triangles tris; mesh.RecalculateNormals(); mesh.RecalculateBounds(); } }逻辑上分三步先按高度和圆周双层循环算出每个顶点的位置位置由radiusProfile[y]决定再按格子拼三角形注意每个四边形拆成两个三角形时顶点顺序要保证法线朝外否则泥坯会看起来是黑的最后重算法线和包围盒。参数方面heightSegments决定纵向精度手指按压影响的是局部几层层数越高形变越平滑但每帧重算开销也越大移动端建议不超过 64。radialSegments影响圆周光滑度低于 32 会明显看到棱。baseRadius和topRadius只是初始形状真正拉坯时它们会被radiusProfile的实时修改覆盖。提示mesh.Clear()之后重新赋值顶点数组比每次 new Mesh 更省 GC。如果每帧都形变记得把Mesh.MarkDynamic()放在 Start 里调用一次。2.3 形变怎么落到 radiusProfile 上手的位置映射到泥坯上本质是求「手在泥坯局部坐标系里的高度 h 和水平距离 d」。高度 h 决定改哪一层水平距离 d 和当前层半径比较决定是往外推还是往里压。常见做法是取手周围一个高斯权重窗口比如影响上下 6 层权重按距离衰减。每帧根据手的位移量delta去加减radiusProfile对应层的值再夹在一个最小最大半径之间防止半径变成负数导致网格翻转。这一步是整套模拟手感的关键权重窗口太窄压下去像针扎太宽整个泥坯会一起晃失去局部感。3. 转盘与手感的实现转速、阻尼和那几个必调参数3.1 转盘旋转为什么不能直接转 Transform最直觉的做法是给泥坯父物体挂一个脚本每帧transform.Rotate(0, speed * Time.deltaTime, 0)。这在纯展示时没问题但一旦手要「按住」泥坯某个位置问题就来了泥坯在转手的世界坐标没变可手对应的泥坯局部坐标每帧都在变形变位置会跟着转手感完全乱掉。正确做法是把转盘转速作用在「手到泥坯局部坐标」的换算上而不是转整个物体。也就是泥坯网格本身不转转的是采样角度。这样手按住一个世界位置对应的泥坯局部角度是固定的形变稳定。具体实现时维护一个累积角度wheelAngle每帧加上speed * Time.deltaTime。求手对应的局部坐标时先把手的世界坐标转到泥坯本地空间再减去wheelAngle做一次反向旋转得到「泥坯参考系」下的角度。这样视觉上泥坯在转通过 UV 或材质表现但形变逻辑是稳的。3.2 转速与阻尼的参数表拉坯手感好不好八成看这几个参数。下面这张表是我反复调出来的经验区间不同设备可以微调但偏离太多基本就废了。参数含义推荐区间调过头会怎样wheelSpeed转盘角速度度/秒60 ~ 180超过 240 手根本跟不上形变位置漂移deformStrength单帧形变系数0.02 ~ 0.08太大泥坯会瞬间被压穿出现自交influenceRadius手影响的高度层数4 ~ 10太小像针扎太大整坯一起动damping形变回弹阻尼0.85 ~ 0.95太低泥坯像橡皮太高没有泥的塑性minRadius最小半径限制0.05 ~ 0.1设成 0 会让网格翻面变黑maxRadius最大半径限制初始半径 × 1.5不限制会拉成一张薄片damping这个参数最玄学。泥是有塑性的压下去不会立刻弹回来但也不能完全不回。我一般用指数衰减每帧radiusProfile[i] Mathf.Lerp(radiusProfile[i], targetRadius[i], 1 - damping)。damping取 0.9 左右压下去的形状能保持住松手后缓慢恢复一点点接近真实泥料的感觉。3.3 手部输入的三种接法第一种是鼠标拖拽适合桌面端快速验证。把鼠标屏幕坐标投到泥坯所在平面得到世界坐标再走上面的局部坐标换算。第二种是触屏逻辑一样只是多点触控要处理手指 id避免两根手指互相打架。第三种是手柄或 VR 控制器这时手有真实三维位置可以直接用控制器尖端的世界坐标手感最好但要注意坐标系转换尤其是头显和 Unity 世界坐标不一致的情况。三种接法在形变那一步是共用的只是输入源不同所以建议把「输入采集」和「形变计算」拆成两个脚本换设备时只改前者。// 输入采集层把不同设备统一成「世界坐标 是否按下」 public class ClayInput : MonoBehaviour { public bool IsPressing { get; private set; } public Vector3 WorldPoint { get; private set; } void Update() { // 桌面端鼠标 if (Input.GetMouseButton(0)) { IsPressing true; Ray ray Camera.main.ScreenPointToRay(Input.mousePosition); Plane clayPlane new Plane(Vector3.up, transform.position); if (clayPlane.Raycast(ray, out float dist)) WorldPoint ray.GetPoint(dist); } else IsPressing false; } }这段只负责把输入统一成「一个世界坐标点加一个按下状态」形变脚本只认这两个值。好处是以后接手柄或 VR只改这个脚本形变逻辑一行不动。Plane用泥坯所在水平面是因为拉坯时手基本在水平方向作用垂直方向的按压可以后续再叠一层。4. 避坑与排查陶艺模拟里最容易翻车的五件事4.1 泥坯表面出现黑面或紫红色现象是泥坯某些面看起来是黑的或者材质变成 Unity 默认的紫红色。黑面九成是三角形顶点顺序反了法线朝内光照算出来就是黑的。解决方法是检查tris数组里每个三角形的绕序保证从外部看是逆时针。紫红色则是材质丢失或 Shader 编译失败常见于用了不兼容当前渲染管线的 Shader换成 URP 或 Built-in 对应的 Shader 即可。这个坑几乎每个新手都会踩一次血泪经验就是先确认绕序再确认管线。4.2 形变时网格自交泥坯像被撕开现象是手压下去泥坯局部出现穿插看起来像破了个洞。原因是radiusProfile被改到小于相邻层的半径或者小于minRadius导致上下层交叉。解决办法是在每次修改后做一次约束radiusProfile[i] Mathf.Max(radiusProfile[i], minRadius)同时限制相邻层的半径差不超过一个阈值比如Mathf.Abs(radiusProfile[i] - radiusProfile[i1]) 0.1。这个约束会牺牲一点形变自由度但能保证网格始终合法。4.3 帧率一高形变就失控现象是在高刷屏或编辑器里跑泥坯形变速度明显比低帧率时快。原因是形变量直接乘了Time.deltaTime之外的东西或者用了每帧固定增量。正确做法是所有形变增量都乘Time.deltaTime并且把damping的 Lerp 系数也按帧时间归一化。如果用了FixedUpdate做形变还要注意物理步长和渲染帧率不一致带来的抖动一般形变放Update更稳。4.4 移动端发热严重、掉帧现象是手机上跑几分钟就烫帧率从 60 掉到 20。原因通常是每帧都在重建整张 Mesh顶点数上万时 CPU 开销很大。优化方向有三个降低heightSegments和radialSegments只在radiusProfile真正变化时才RebuildMesh没形变就跳过把法线重算从每帧改成隔帧或按需。如果还不行就得考虑把形变搬到 Compute Shader 或 Job System但那是另一个量级的改造先做前三条通常就够。4.5 手感和视觉对不上按下去的位置偏了现象是明明按在泥坯中间形变却出现在边缘。原因是世界坐标到局部坐标的换算漏了转盘角度或者漏了泥坯自身的缩放。排查顺序是先确认泥坯 Transform 的 scale 是不是 1不是就先除回去再确认有没有减去wheelAngle最后确认相机投影用的平面高度和泥坯实际高度一致。这三步走完基本能定位。这个坑隐蔽在于视觉上泥坯在转很容易忘了形变参考系是静止的。5. 从能跑到好用泥料质感、干燥开裂与一个验证习惯把形变跑通只是第一步真正让陶艺模拟「像那么回事」的是材质和后期状态。泥坯表面不能是纯色得有颗粒感和湿度变化。常见做法是用一张噪声图叠加到 Albedo 上再根据radiusProfile的变化率算一个「受力程度」受力大的地方颜色略深模拟泥被压实。Shader 里可以用Mathf.PerlinNoise生成噪声注意别每帧重新生成噪声图烘焙一次就够运行时只采样。干燥开裂是陶艺模拟里容易被忽略但很有价值的一环。真实泥坯在干燥过程中会收缩收缩不均就开裂。模拟时可以用一个dryness值从 0 到 1 随时间增长每层半径按dryness乘以一个收缩系数同时随机在若干层注入微小裂纹——具体做法是把这些层的半径额外减一点并让相邻层产生不连续。裂纹不用做得很夸张细微的不连续在光照下就很明显。这个机制能让整个模拟从「捏泥巴」升级到「做陶」教育类应用里特别加分。验证方面我自己的习惯是固定一组参数做回归转盘 120 度/秒deformStrength0.05influenceRadius6damping0.9然后手动做三个动作——底部外扩、中部收腰、口沿翻边看形变是否平滑、有没有自交、帧率是否稳定。每次改代码后跑一遍这三个动作比漫无目的地乱捏有效得多。这个习惯帮我省了很多后悔药因为形变逻辑的 bug 往往只在特定动作下才暴露。最后说一句实在的Unity 陶艺模拟软件这个方向技术门槛不在 Unity 本身而在你愿不愿意花时间调那几个手感参数。我见过太多项目卡在「能跑但不好玩」这一步最后不了了之。如果你打算投入先把第 2 章那张网格和第 3 章那张参数表吃透剩下的就是耐心。希望帮到你。本文还有配套的精品资源点击获取