Skip to content

268. 点云(一)

Published:

点云是 3D 的一个常见业务场景。

它是通过一堆点来展示三维物体。

这个 Three.js 也提供了相应的 API

https://threejs.org/docs/index.html?q=MeshSurfaceSampler#examples/zh/math/MeshSurfaceSampler

image.png

我们来试一下:

npx create-vite point-cloud

image.png

进入项目,安装依赖:

pnpm install
pnpm install --save three
pnpm install --save-dev @types/three

先写下基础代码:

改下 src/main.js

import './style.css';
import * as THREE from 'three';
import {
    OrbitControls
} from 'three/addons/controls/OrbitControls.js';
import mesh from './mesh.js';

const scene = new THREE.Scene();

scene.add(mesh);

const light = new THREE.DirectionalLight(0xffffff);
light.position.set(500, 300, 600);
scene.add(light);

const light2 = new THREE.AmbientLight();
scene.add(light2);

const axesHelper = new THREE.AxesHelper(1000);
scene.add(axesHelper);

const width = window.innerWidth;
const height = window.innerHeight;

const camera = new THREE.PerspectiveCamera(60, width / height, 1, 10000);
camera.position.set(0, 500, 500);
camera.lookAt(0, 0, 0);

const renderer = new THREE.WebGLRenderer({
  antialias: true
});
renderer.setSize(width, height)

function render() {
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}

render();

document.body.append(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);

创建 Scene、Light、Camera、Renderer

改下 style.css

body {
    margin: 0;
}

然后创建 mesh.js

import * as THREE from 'three';

const geometry = new THREE.BoxGeometry(100, 100, 100);
const material = new THREE.MeshPhongMaterial({
    color: 'orange'
})
const mesh = new THREE.Mesh(geometry, material);

export default mesh;

先画个立方体

跑一下:

npm run dev

image.png

2025-09-22 12.45.27.gif

如何用点云的方式展示这个立方体呢?

这就需要用到上面那个 API 了:

image.png

它是在一个物体表面采样拿到一些点,然后我们用 BufferGeometry 把这些点绘制出来就可以了。

image.png

我们用采样器在立方体表面取 1000 个点,然后用 BufferGeometry + Points 把它画出来:

const sampler = new MeshSurfaceSampler(mesh).build();

const vertices = [];
const position = new THREE.Vector3();
for (let i = 0; i < 1000; i++) {
    sampler.sample(position);
    vertices.push(position.x, position.y, position.z);
}

const bufferGeometry = new THREE.BufferGeometry();
bufferGeometry.setAttribute(
    'position',
    new THREE.Float32BufferAttribute(vertices, 3),
);
const pointMaterial = new THREE.PointsMaterial({
    size: 0.5,
    color: 'orange'
});
const pointCloud = new THREE.Points(bufferGeometry, pointMaterial);

export default pointCloud;

看下效果:

2025-10-11 18.43.47.gif

这明显不是立方体的顶点,而是从立方体表面采样取的点。

这就是点云的效果。

我们换个复杂点的模型试一下:

Three.js 的源码仓库的马的模型:

image.png

image.png

这里要下载 .gltf 和 .bin 文件

创建 mesh2.js

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const loader = new GLTFLoader();

const mesh = new THREE.Group();

loader.load("./Horse.gltf", function (gltf) {
    console.log(gltf);
    mesh.add(gltf.scene);

    gltf.scene.scale.setScalar(50);
})

export default mesh;

引入下:

image.png

2025-10-11 19.06.34.gif

用点云的方式来展示:

image.png

首先,MeshSurfaceSampler 需要传入一个 Mesh

我们遍历模型,找一个 mesh 出来:

image.png

gltf.scene.traverse(obj => {
    if(obj.isMesh) {
        console.log(obj.name, obj);
    }
})

image.png

这个其实我们之前测过,一个是马的身体,一个是马尾马鬃马蹄这些。

把每个 mesh 都做取样:

image.png

用点云画出来:

import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { MeshSurfaceSampler } from 'three/examples/jsm/Addons.js';

const loader = new GLTFLoader();

const mesh = new THREE.Group();

const vertices = [];

loader.load("./Horse.gltf", function (gltf) {
    console.log(gltf);
    // mesh.add(gltf.scene);

    gltf.scene.scale.setScalar(50);

    gltf.scene.traverse(obj => {
        if(obj.isMesh) {
            console.log(obj.name, obj);

            const sampler = new MeshSurfaceSampler(obj).build();

            const position = new THREE.Vector3();
            for (let i = 0; i < 1000; i++) {
                sampler.sample(position);
                vertices.push(position.x, position.y, position.z);
            }
        }
    })

    const bufferGeometry = new THREE.BufferGeometry();
    bufferGeometry.setAttribute(
        'position',
        new THREE.Float32BufferAttribute(vertices, 3),
    );
    const pointMaterial = new THREE.PointsMaterial({
        size: 0.5,
        color: 'orange'
    });
    const pointCloud = new THREE.Points(bufferGeometry, pointMaterial);
    mesh.add(pointCloud);
})

export default mesh;

看下效果:

image.png

可以看到,点云绘制出来了,但是缩放没生效。

我们这样写:

image.png

position.multiplyScalar(50);

在取样之后对采样点的数据做放缩。

2025-10-11 19.19.13.gif

现在就是放大之后的点云效果了。

我们取样点更多一些:

image.png

2025-10-11 19.20.11.gif

点云效果就更明显了。

案例代码上传了小册仓库

总结

这节我们学了下点云。

Three.js 提供了 MeshSurfaceSampler 的 api 对物体表面进行采样。

采样的点用 BufferGeometry + Points 画出来就是点云效果。

当然,点云效果可以做一些更复杂的处理,下节我们继续深入。

评论