1.首先node.js是12.22版本的,安装three.js可以参考这篇文章
直接用Threejs入门-安装教程_安装three.js-CSDN博客
直接在终端安装three.js即可
npm install --save three
在相同目录下安装vite构建工具
npm install --save-dev vite
在项目里面看package.json中是否有"three": "^0.164.1",有就安装好了。
然后就使用吧,这里举个例子
<template>
<div>
<canvas id="three"></canvas>
</div>
</template>
<script>
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
export default {
mounted() {
this.initThree()
},
methods: {
initThree() {
const scene = new THREE.Scene()
//There are a few different cameras in three.js. For now, let's use a PerspectiveCamera.
//three.js 里有几种不同的相机,在这里,我们使用的是 PerspectiveCamera(透视摄像机)。
//第一个参数是视野角度(FOV)。视野角度就是无论在什么时候,你所能在显示器上看到的场景的范围,它的单位是角度(与弧度区分开)。
//第二个参数是长宽比(aspect ratio)。 也就是你用一个物体的宽除以它的高的值。比如说,当你在一个宽屏电视上播放老电影时,可以看到图像仿佛是被压扁的。
/*
* 接下来的两个参数是近截面(near)和远截面(far)。
* 当物体某些部分比摄像机的远截面远或者比近截面近的时候,该这些部分将不会被渲染到场景中。
* 或许现在你不用担心这个值的影响,但未来为了获得更好的渲染性能,你将可以在你的应用程序里去设置它。
* */
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
const renderer = new THREE.WebGLRenderer()
/*
* 除了创建一个渲染器的实例之外,我们还需要在我们的应用程序里设置一个渲染器的尺寸。
* 比如说,我们可以使用所需要的渲染区域的宽高,来让渲染器渲染出的场景填充满我们的应用程序。
* 因此,我们可以将渲染器宽高设置为浏览器窗口宽高。
* 对于性能比较敏感的应用程序来说,你可以使用 setSize 传入一个较小的值,例如 window.innerWidth/2 和 window.innerHeight/2,这将使得应用程序在渲染时,以一半的长宽尺寸渲染场景。
* 如果你希望保持你的应用程序的尺寸,是以较低的分辨率来渲染,你可以在调用 setSize 时,将 updateStyle(第三个参数)设为 false。
* 例如,假设你的 <canvas> 标签现在已经具有了 100% 的宽和高,调用 setSize(window.innerWidth/2, window.innerHeight/2, false) 将使得你的应用程序以四分之一的大小来进行渲染。
* */
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
/*
* 最后一步很重要,我们将 renderer(渲染器)的dom元素(renderer.domElement)添加到我们的 HTML 文档中。
* 这就是渲染器用来显示场景给我们看的 <canvas> 元素。
* */
//To create a cube, we need a BoxGeometry. This is an object that contains all the points (vertices) and fill (faces) of the cube. We'll explore this more in the future.
//要创建一个立方体,我们需要一个 BoxGeometry(立方体)对象. 这个对象包含了一个立方体中所有的顶点(vertices)和面(faces)。未来我们将在这方面进行更多的探索。
const geometry = new THREE.BoxGeometry(1, 1, 1)
//接下来,对于这个立方体,我们需要给它一个材质,来让它有颜色。
//Three.js 自带了几种材质,在这里我们使用的是 MeshBasicMaterial。
/*
* 所有的材质都存有应用于他们的属性的对象。
* 在这里为了简单起见,我们只设置一个color属性,值为 0x00ff00,也就是绿色。
* 这里所做的事情,和在 CSS 或者 Photoshop 中使用十六进制(hex colors)颜色格式来设置颜色的方式一致。
* */
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 })
/*
* 第三步,我们需要一个 Mesh(网格)。
* 网格包含一个几何体以及作用在此几何体上的材质,我们可以直接将网格对象放入到我们的场景中,并让它在场景中自由移动。
* */
const cube = new THREE.Mesh(geometry, material)
/*
* 默认情况下,当我们调用 scene.add() 的时候,物体将会被添加到 (0,0,0) 坐标。
* 但将使得摄像机和立方体彼此在一起。为了防止这种情况的发生,我们只需要将摄像机稍微向外移动一些即可。
* */
scene.add(cube)
camera.position.z = 5
//渲染场景
/*
* 现在,如果将之前写好的代码复制到HTML文件中,你不会在页面中看到任何东西。
* 这是因为我们还没有对它进行真正的渲染。
* 为此,我们需要使用一个被叫做“渲染循环”(render loop)或者“动画循环”(animate loop)的东西。
* */
function animate() {
requestAnimationFrame(animate)
//使立方体动起来
/*
* 在开始之前,如果你已经将上面的代码写入到了你所创建的文件中,你可以看到一个绿色的方块。
* 让我们来做一些更加有趣的事 —— 让它旋转起来。
* 将下列代码添加到 animate() 函数中 renderer.render 调用的上方:
* */
cube.rotation.x += 0.01
cube.rotation.y += 0.01
renderer.render(scene, camera)
}
animate()
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement
var width = window.innerWidth
var height = window.innerHeight
var canvasPixelWidth = canvas.width / window.devicePixelRatio
var canvasPixelHeight = canvas.height / window.devicePixelRatio
const needResize = canvasPixelWidth !== width || canvasPixelHeight !== height
if (needResize) {
renderer.setSize(width, height, false)
}
return needResize
}
},
},
}
</script>
<style scoped>
#three {
width: 100%;
height: 100%;
position: fixed;
left: 0;
top: 0;
}
</style>
结果如下:
在终端运行
2.2024/5/28
点云数据上传
<template>
<div>
<input type="file" id="csvFile" accept=".csv" @click="handleClick" style="pointer-events: auto" />
<div style="width: 100%; height: 100vh; position: relative">
<canvas id="three"></canvas>
<div id="overlay" style="position: absolute; top: 0; left: 0; pointer-events: auto">
<div class="button-container">
<!-- pointer-events: auto下面的元素相应鼠标触摸点击事件,这是默认的 -->
<!-- <button id="myButton">BUTTON</button>-->
</div>
</div>
</div>
</div>
</template>
<script>
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import Papa from 'papaparse'
export default {
mounted() {
this.initThree()
// 下面 window.addEventListene是添加一个它会添加一个事件监听器到window对象上,以监听resize事件。当浏览器窗口大小改变时,这个事件会被触发,并执行this.onWindowResize这个方法。注意,这里的this.onWindowResize应该是一个在Vue组件的methods中定义的方法,用于处理窗口大小改变时的逻辑(例如更新摄像机的纵横比或重新渲染场景)。
// 将onWindowResize组件里面的方块不会随着外边框的放大缩小而发生变化
window.addEventListener('resize', this.onWindowResize, false)
},
beforeDestroy() {
window.removeEventListener('resize', this.onWindowResize, false)
// 在这里添加其他清理代码,比如取消动画等
},
methods: {
handleClick() {
console.log('Input clicked!')
},
initThree() {
const canvas = document.getElementById('three')
const renderer = new THREE.WebGLRenderer({ canvas })
renderer.setSize(window.innerWidth, window.innerHeight)
const scene = new THREE.Scene()
scene.background = new THREE.Color('#ccc')
scene.environment = new THREE.Color('#ccc')
// 创建一个光源,因为默认的THREE.Scene是没有光源的
const light = new THREE.AmbientLight(0x404040) // soft white light
scene.add(light)
// 初始化相机,设置其位置
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000)
camera.position.z = 5 // 把相机向后移动一些,以便能看到场景中的物体
const controls = new OrbitControls(camera, renderer.domElement)
controls.update()
// 设置一下参数
const b = 1
const geometry = new THREE.BoxGeometry(b, 1, 1)
const material = new THREE.MeshBasicMaterial({ color: 0xfff })
// const cube = new THREE.Mesh(geometry, material)
// cube.position.x += 4
// scene.add(cube)
// const geometry1 = new THREE.ConeGeometry(1, 1, 32)
// const cone = new THREE.Mesh(geometry1, material)
// scene.add(cone)
const geometry2 = new THREE.SphereGeometry(1, 32, 10)
// const sphere = new THREE.Mesh(geometry2, material)
// scene.add(sphere)
const csvFileInput = document.getElementById('csvFile')
csvFileInput.addEventListener('change', function (e) {
const file = e.target.files[0]
if (file) {
const reader = new FileReader()
reader.onload = function (e) {
const text = e.target.result // 读取的文件内容
const lines = text.split('\n') // 按行分割
// 跳过标题行(如果有)
let dataLines = lines.slice(1)
// 解析点和颜色数据
const points = []
const colors = []
for (let i = 0; i < dataLines.length; i++) {
const lineData = dataLines[i].split(',') // 按逗号分割每行数据
if (lineData.length === 6) {
// 假设每行包含7个元素(x, y, z, r, g, b, a)
points.push(parseFloat(lineData[0]), parseFloat(lineData[1]), parseFloat(lineData[2]))
colors.push(parseFloat(lineData[3]), parseFloat(lineData[4]), parseFloat(lineData[5]))
}
}
// 创建Float32Array
const pointsArray = new Float32Array(points)
const colorsArray = new Float32Array(colors)
const geometry3 = new THREE.BufferGeometry()
geometry3.setAttribute('position', new THREE.BufferAttribute(pointsArray, 3))
if (colors) {
geometry3.setAttribute('color', new THREE.BufferAttribute(colorsArray, 3)) // 假设每个颜色由 4 个浮点数表示(RGBA)
}
const material1 = new THREE.PointsMaterial({
size: 0.05, // 点的大小
vertexColors: true, // 如果使用了颜色数组,则启用此选项
// 其他属性...
})
const pointsObject = new THREE.Points(geometry3, material1)
scene.add(pointsObject)
// 在这里,你可以使用WebGL或其他图形API来渲染这些数据
// ...
}
reader.readAsText(file) // 以文本格式读取文件
}
})
// sphere.position.x = -4
camera.position.z = 5
// 加载模型(这里只是一个示例,你可能需要替换为你的模型)
// 渲染循环
function animate() {
requestAnimationFrame(animate)
// cube.rotation.x += 0.01
// cube.rotation.y += 0.01
// cone.rotation.x += 0.01
// cone.rotation.y += 0.01
// sphere.rotation.x += 0.01
// sphere.rotation.y += 0.01
renderer.render(scene, camera)
}
animate()
// 窗口大小变化时的处理函数
this.onWindowResize = () => {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
}
// 如果使用OrbitControls,可以在这里初始化它
// const controls = new OrbitControls(camera, renderer.domElement)
},
},
}
</script>
<style scoped>
#csvFile {
width: 100px;
height: 100px;
z-index: 265; /* 确保按钮在画布之上265大于100所以能放在前面 */
}
#three {
position: absolute;
width: 100%;
text-align: center;
z-index: 100;
display: block;
}
#overlay {
width: 100%;
height: 100%;
display: flex;
/* 垂直方向排列column*/
flex-direction: column;
align-items: center;
/* center意味着子元素将在垂直方向上居中对齐。*/
/*justify-content: center;*/
pointer-events: none;
}
#overlay button {
pointer-events: auto; /* 允许按钮上的点击事件 */
}
.button-container {
margin-top: 1px; /* 使得元素在垂直方向上被推到容器的底部 */
align-self: flex-end; /* 在水平方向上对齐到容器的右边 */
pointer-events: none; /* 这个可能不需要,除非你想要防止容器本身接收点击事件 */
z-index: 267;
}
</style>