PointMesh:一个顶点渲染万粒粒子
【免费下载链接】godot-docsGodot Engine official documentation项目地址: https://gitcode.com/GitHub_Trending/go/godot-docs
写粒子系统时,你有没有算过账:几千个粒子,每个还是球体网格,顶点数直接爆炸?Godot 里的 PointMesh 把每个粒子压到 1 个顶点、0 个三角形,代价是——忘记设点大小,一个都看不见。读完这篇,你能用几行代码搭出自己的廉价粒子、点云和海量小图标。
一、它到底解决谁的什么问题
先说结论:它解决"数量大、单个不需要几何细节"的渲染开销问题。
想象你要画 5000 粒灰尘。用默认的球体粒子,每个粒子自带上万顶点,总量轻松破亿。而人眼在这个距离上看到的只是一团模糊的光点,那些三角面全白算了。PointMesh 的思路是:既然看到的只是一个"色块",那就只提交一个顶点。GPU 把这个顶点投影到屏幕上,再画一个固定像素大小的矩形盖上去。5000 粒灰尘,5000 个顶点,几何开销直接低三个数量级。
没有它的话,你要么忍受掉帧,要么自己写 instancing 拼凑——它把这个方案做成了引擎内置的 PointMesh。
二、最短路径先跑通
结论先行:点必须配一个"声明了点大小"的材质,否则屏幕上什么都没有。这是 PointMesh 最反直觉的一点。
最小可运行示例(GDScript 动态搭建;C# 里属性名对应为UsePointSize/PointSize,写法一致):
var dot := MeshInstance3D.new() var pm := PointMesh.new() var mat := StandardMaterial3D.new() mat.use_point_size = true # 不加这行,点基本不可见 mat.point_size = 8.0 # 点的直径,单位像素 pm.material = mat dot.mesh = pm add_child(dot)跑起来后你会看到一个 8 像素的正方形点。先别嫌弃它方——形状的事第五节再处理。现在记住这条路径:PointMesh + use_point_size + point_size,三件套缺一不可。
三、拆开看:内部如何把事做对
按"数据→变换→呈现"三层拆,机制其实很薄。
数据层:一张只含一个顶点的点表面。Mesh的图元类型枚举里有PRIMITIVE_POINTS = 0,官方解释是"一个顶点等于一个点"(见 class_mesh.rst)。PointMesh 的表面就是这么一条点图元表面,继承自 PrimitiveMesh,没有法线、没有 UV——因为一个点既不需要光照方向,也不需要贴图坐标。
变换层:几乎无事可做。三角形网格要算蒙皮、光照、背面剔除,而点只有一个"在哪"的信息。它天然正对屏幕,等价于隐式广告牌,连billboard_mode的旋转都省了。
呈现层:像盖章一样画方块。类比一下:普通网格是"在纸上铺一整面墙",点渲染是"拿一枚固定尺寸的印章盖一下"。印章多大,由材质里的point_size(像素)或着色器里的POINT_SIZE决定。这也解释了它的核心特性:点不随距离变小,10 米外的点和 1 米外的点一样大。
四、可调参数全景
真正要调的就这么几个(默认值与单位来自 class_basematerial3d.rst 与 spatial_shader.rst):
| 参数 | 类型 | 默认值 | 单位 | 何时改 |
|---|---|---|---|---|
use_point_size | bool | false | — | 任何想控制点大小的场景(总开关) |
point_size | float | 1.0 | 像素 | 点太小看不清,或太大过曝 |
POINT_SIZE | shader 内置(inout float) | 未设置 | 像素 | 想让大小随距离/生命周期动态变化 |
POINT_COORD | shader 内置(只读vec2) | — | 0~1 归一化 | 想把方点裁成圆点或做径向渐变 |
material | Material | null | — | 改颜色、透明度、贴图 |
custom_aabb | AABB | 零包围盒 | 世界单位 | 着色器偏移顶点后出现意外剔除 |
展开讲三个关键的。
use_point_size是开关不是滑块。它对应BaseMaterial3D.Flags枚举里的FLAG_USE_POINT_SIZE = 3,不打开时point_size写了也白写。官方注释特意强调:该选项只对"基于点而非三角形"的几何生效。
point_size恒定以像素计。1 像素是默认值,多数屏幕上几乎看不见;8~16 像素是粒子常用区间。
POINT_SIZE是动态版。顶点阶段写入,片段阶段的POINT_COORD返回当前像素在点矩形内的归一化坐标,两者配合能做形状裁剪和渐变——这是"盖章"比喻里唯一能让印章变形的口子。
五、三条落地路线
由浅入深,三条路线层层递进。
路线 1:粒子本体(最浅)。适合:粒子量大、视觉要求就是"彩色斑点"的场景,比如火花、尘埃、魔法粉尘。做法:把 PointMesh 赋给粒子节点的绘制通道即可,粒子运动逻辑全部交给ParticleProcessMaterial:
var dust := CPUParticles3D.new() var pm := PointMesh.new() var mat := StandardMaterial3D.new() mat.use_point_size = true mat.point_size = 4.0 pm.material = mat dust.draw_pass_1 = pm # 网格交给绘制通道路线 2:MultiMesh 点云(进阶)。适合:星图、采样数据可视化、大量地图标记。一个 PointMesh 资源被成千上万个实例共享,只有变换矩阵不同,GPU 侧实例化开销远低于逐个提交:
var mm := MultiMesh.new() mm.transform_count = 5000 mm.mesh = point_mesh # 共享同一份点网格 for i in range(5000): mm.set_instance_transform(i, rand_in_sphere(20.0))路线 3:着色器动态点(最深)。适合:点大小要随深度或生命周期变化,或者非要圆点不可。用ShaderMaterial,顶点阶段写POINT_SIZE,片段阶段用POINT_COORD裁剪(GLSL 老用户可直接把gl_PointSize换成POINT_SIZE):
shader_type spatial; void vertex() { POINT_SIZE = 12.0; // 想按距离缩放,在此自行换算 } void fragment() { if (length(POINT_COORD - vec2(0.5)) > 0.5) discard; // 方点裁成圆点 ALBEDO = vec3(1.0, 0.85, 0.3); }三条路线可叠加:MultiMesh 是载体,着色器是皮肤。
六、这些开关会"假装生效"
官方在 class_pointmesh.rst 的 Note 里点名了一组"看起来能设、实则被忽略"的属性。逐条翻译:
- 你以为:打开
billboard_mode让点跟随相机转向。实际上:点本来就恒定正对屏幕,这个属性直接被忽略——它存在的意义对点而言是零。 - 你以为:设个
grow让粒子膨胀一点。实际上:grow靠法线方向沿表面推顶点,点没有表面也没有法线,推无可推。 - 你以为:用
cull_mode = CULL_FRONT反向剔除来藏点。实际上:点没有正反面,剔除逻辑根本不参与。
背后是同一句话:这些开关全是"三角形时代的工具",对点图元没有作用对象。所以看到点不响应某个材质属性,先怀疑是不是撞上了这条边界,别在参数上空耗。
七、和它的近亲怎么选
- vs QuadMesh:一块真正的四边形,有法线、有 UV、能吃光照和贴图,配合
billboard_mode是传统广告牌。你要纹理细节和光照,选 QuadMesh;只要一个纯色斑点,选 PointMesh。 - vs RibbonTrailMesh / TubeTrailMesh:两者沿路径生成带宽度的拖尾,点图元做不出连续轨迹。要拖尾用它们,粒子本体用 PointMesh,两者常在同一系统里搭配。
- vs Sprite3D:Sprite3D 是"一个节点一个广告牌",摆 3 个还行,摆 3000 个节点树就完了。海量小图标一律走
MultiMeshInstance3D+ PointMesh,别用 Sprite3D 硬堆。
一句话决策:几何细节需求为零 → PointMesh;要看见贴图 → QuadMesh;要拖尾 → Ribbon/Tube。
八、何时该用它、何时该放手
该用的信号:
- 粒子或标记数量在数千以上,且单个不需要独立几何;
- 移动端或低端设备,帧预算紧张;
- 需要展示采样数据(点云)、星图、密集小图标。
该放手的边界:
- 需要透视缩放——点不随距离变小,想要"远小近大"就得用 QuadMesh +
billboard_mode; - 需要进 CSG 布尔运算——CSGMesh3D 的类型提示把
PointMesh、PlaneMesh、RibbonTrailMesh明确排除在外,因为它们是非流形(non-manifold)图元,布尔算法处理不了; - 需要光照与阴影——没有法线,灯光无从下手;
- 点的物理尺寸要超过一二十像素——GPU 对点尺寸本身有上限,大方块请回到四边形。
结语
回到开头那笔账:当粒子多到算不过来的时候,答案往往不是"优化三角形",而是"根本没有三角形"。把网格换成 PointMesh、挂上点大小、再决定要不要着色器——这三步走通后,下一步自然就是进 3D 粒子系统教程 里玩ParticleProcessMaterial的曲线和噪声了。点已经亮了,剩下的是让它动出花样。
【免费下载链接】godot-docsGodot Engine official documentation项目地址: https://gitcode.com/GitHub_Trending/go/godot-docs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考