前言
点云技术正成为三维视觉开发的热门方向,而 MarsCode 作为强大的交互逻辑工具,与 Three.js 的采样器结合,为复杂点云效果的实现提供了高效解决方案。本文将重点展示如何借助 MarsCode 快速实现动物点云效果,解析其在数据处理与渲染中的核心作用,为开发者打开点云艺术的新思路。文章末尾会放源码地址
项目预览:
- 本项目使用豆包在线IDE MarsCode IDE 开发
一. 项目初始化
使用html/css/js 模版
项目初始化详情(默认安装了vite),点击顶部运行按钮或使用命令行npm run start
即可启动项目
安装项目依赖, package.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初始化配置
初始化场景、相机、渲染器和控制器,代码较为基础,不多赘述
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方法提取点坐标并存储,方便后续点云绘制
// 添加模型 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绘制点云。
// 点云绘制 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,相对于普通的数组读写效率会优秀很多
例如创建一个简单的矩形,我们需要俩个三角面,每个三角面需要三个点坐标,一个矩形由两个三角面构成所以需要六个点坐标。
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:观察仔细的同学应该能发现其实有俩个点是重复共用的,这个时候可以通过设置索引缓冲区
来共用顶点,从而降低顶点数量的生成,优化渲染效率。
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
复制
- uv:定义纹理坐标,大致同上,不过纹理uv坐标是俩俩一组
- color:颜色数组,注意点数量会和position保持一致,每三个一组,position中是xyz,而color中是rgb,和position一一对应关系
4. 绘制点云之间的连线
通过采样器选取一个初始点,随机选取和初始点距离不超过30的点绘制两点之间的连线,直至没有孤立点
// 添加线条 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
const particleNums = 15000; const vertices = []; let sampler = null; let previousPoint; let lineIndex = 0; let angle = 0;
复制
回答的很不错,对代码的理解很充分,专有名称也能很好的解释出来,很棒👍️👍
同时还有一个功能出乎我的意料,可以一键将我的代码替换成豆包回答的代码,省去手动cv,这个交互很方便👍️
三. 项目提交至仓库
豆包支持代码上传到github,配置好认证信息就可以提交啦!
四. 结语
如果大家感兴趣可以点击下方链接自行体验一下,欢迎大家在评论区交流,希望可以一键三连,感谢。
豆包体验地址: marscode
代码仓库地址: github