前言
点云技术正成为三维视觉开发的热门方向,而 MarsCode 作为强大的交互逻辑工具,与 Three.js 的采样器结合,为复杂点云效果的实现提供了高效解决方案。本文将重点展示如何借助 MarsCode 快速实现动物点云效果,解析其在数据处理与渲染中的核心作用,为开发者打开点云艺术的新思路。文章末尾会放源码地址
项目预览:
- 本项目使用豆包在线IDE MarsCode IDE 开发
一. 项目初始化
使用html/css/js 模版
项目初始化详情(默认安装了vite),点击顶部运行按钮或使用命令行npm run start
即可启动项目
安装项目依赖, package.json概览
json
{
"name": "web-test",
"version": "1.0.0",
"description": "",
"scripts": {
"start": "vite --host --port=8000"
},
"devDependencies": {
"vite": "^5.2.12",
"vite-plugin-full-reload": "^1.1.0"
},
"dependencies": {
"three": "0.163.0"
}
}
二. 代码实现
1. threejs初始化配置
初始化场景、相机、渲染器和控制器,代码较为基础,不多赘述
js
import * as THREE from 'three';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
import { MeshSurfaceSampler } from 'three/examples/jsm/math/MeshSurfaceSampler.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const control = new OrbitControls(
camera,
renderer.domElement,
);
2. 模型加载
- 本文使用模型为obj格式,所以使用OBJLoader加载模型资源
- 使用MeshSurfaceSampler初始化模型采样器
- 使用采样器的sample方法提取点坐标并存储,方便后续点云绘制
js
// 添加模型
function addModel() {
new OBJLoader().load(
'/models/Elephant.obj',
(obj) => {
const model = obj.children[0];
model.material = new THREE.MeshBasicMaterial({
wireframe: true,
color: new THREE.Color('#7264B5'),
transparent: true,
opacity: 5,
});
sampler = new MeshSurfaceSampler(model).build();
const tempPosition = new THREE.Vector3();
// 采样点坐标存储
for (let i = 0; i < particleNums; i++) {
sampler.sample(tempPosition);
vertices.push(tempPosition.x, tempPosition.y, tempPosition.z);
}
addParticle(vertices);
}
);
}
3. 点云绘制
通过上述采样器获取的点坐标利用BufferGeometry绘制点云。
js
// 点云绘制
function addParticle(vertices) {
let colors = [];
const palette = [
new THREE.Color('#88C9B9'), // 青绿色
new THREE.Color('#673AB7'), // 深紫色
new THREE.Color('#009688'), // 深绿色
new THREE.Color('#9C27B0'), // 深紫色
new THREE.Color('#FFC107'), // 深橙色
new THREE.Color('#03A9F4'), // 深蓝色
new THREE.Color('#4CAF50'), // 深绿色
new THREE.Color('#FF5722'), // 深橙色
];
const pointGeometry = new THREE.BufferGeometry();
for (let i = 0; i < particleNums; i++) {
const color = palette[Math.floor(Math.random() * palette.length)];
colors.push(color.r, color.g, color.b);
}
pointGeometry.setAttribute(
'color',
new THREE.Float32BufferAttribute(colors, 3),
);
pointGeometry.setAttribute(
'position',
new THREE.Float32BufferAttribute(vertices, 3),
);
const pointMaterial = new THREE.PointsMaterial({
size: 0.1,
alphaTest: 0.2,
vertexColors: true,
});
const particles = new THREE.Points(pointGeometry, pointMaterial);
scene.add(particles);
}
3.1 详解BufferGeometry
BufferGeometry的核心属性是attributes中的position,uv和color
- position:存储三角面坐标,存储方式为Float32Array,相对于普通的数组读写效率会优秀很多
例如创建一个简单的矩形,我们需要俩个三角面,每个三角面需要三个点坐标,一个矩形由两个三角面构成所以需要六个点坐标。
js
const vertices= new Float32Array( [
-1.0, -1.0, 1.0,
-1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
1.0, 1.0, -1.0,
1.0, 1.0, -1.0,
-1.0, 1.0, 1.0
] );
// itemSize = 3 因为每个顶点都是一个三元组。
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
ps:观察仔细的同学应该能发现其实有俩个点是重复共用的,这个时候可以通过设置索引缓冲区
来共用顶点,从而降低顶点数量的生成,优化渲染效率。
js
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
- uv:定义纹理坐标,大致同上,不过纹理uv坐标是俩俩一组
- color:颜色数组,注意点数量会和position保持一致,每三个一组,position中是xyz,而color中是rgb,和position一一对应关系
4. 绘制点云之间的连线
通过采样器选取一个初始点,随机选取和初始点距离不超过30的点绘制两点之间的连线,直至没有孤立点
js
// 添加线条
function addLines() {
if (sampler) {
const tempPosition = new THREE.Vector3();
const first = previousPoint ? previousPoint.clone() : new THREE.Vector3();
const lineGeometry = new THREE.BufferGeometry();
const lineMaterial = new THREE.LineBasicMaterial({
color: new THREE.Color("#808080"),
opacity: 0.5,
});
let line = new THREE.Line(lineGeometry, lineMaterial);
!previousPoint && sampler.sample(first);
previousPoint = first.clone();
let pointFound = false;
while (!pointFound) {
sampler.sample(tempPosition);
if (tempPosition.distanceTo(previousPoint) < 30) {
lineGeometry.setAttribute(
'position',
new THREE.Float32BufferAttribute(
[
first.x, first.y, first.z,
tempPosition.x, tempPosition.y, tempPosition.z
],
3,
),
);
previousPoint = tempPosition.clone();
pointFound = true;
}
}
scene.add(line);
}
}
此时我发现我的代码好像缺少点注释, 于是就问了下豆包MarcodeAI
js
const particleNums = 15000;
const vertices = [];
let sampler = null;
let previousPoint;
let lineIndex = 0;
let angle = 0;
回答的很不错,对代码的理解很充分,专有名称也能很好的解释出来,很棒👍️👍
同时还有一个功能出乎我的意料,可以一键将我的代码替换成豆包回答的代码,省去手动cv,这个交互很方便👍️
三. 项目提交至仓库
豆包支持代码上传到github,配置好认证信息就可以提交啦!
四. 结语
如果大家感兴趣可以点击下方链接自行体验一下,欢迎大家在评论区交流,希望可以一键三连,感谢。
豆包体验地址: marscode
代码仓库地址: github