共计 7021 个字符,预计需要花费 18 分钟才能阅读完成。
今天简单实现一个 three.js 的小 Demo,加强自己对 three 知识的掌握与学习,只有在项目中才能灵活将所学知识运用起来,话不多说直接开始。
目录
项目搭建
平铺元素周期表
螺旋元素周期表
网格元素周期表
球状元素周期表
加底部交互按钮
项目搭建
本案例还是借助框架书写 three 项目,借用 vite 构建工具搭建 vue 项目,vite 这个构建工具如果有不了解的朋友,可以参考我之前对其讲解的文章:vite 脚手架的搭建与使用。搭建完成之后,用编辑器打开该项目,在终端执行 npm i 安装一下依赖,安装完成之后终端在安装 npm i three 即可。
因为我搭建的是 vue3 项目,为了便于代码的可读性,所以我将 three.js 代码单独抽离放在一个 js 文件当中,在 views 下的 index.vue 文件中使用该 js 文件,然后再将 index.vue 组件引入根组件。具体如下:
接下来我们重点的 three 代码就不像之前的项目 Demo 一样直接写在 vue 组件中,例子。这里我们直接将其放在一个 js 文件中,当然这里也是需要对 three 代码进行初始化代码处理,如下我们先定义一个基础的 class 类,将要使用的场景、相机、渲染器和渲染函数先定义起来:
import * as THREE from 'three'
class Base {constructor(selector) {this.container = document.querySelector(selector)
this.scene
this.camera
this.renderer
this.init()
this.animate()}
init() {this.initScene() // 初始化场景
this.initCamera() // 初始化相机
this.initRenderer() // 初始化渲染器
this.initControl() // 初始化控制器
this.windowSizeChange() // 初始化窗口大小}
}
export default Base
初始化场景 :
initScene() { // 初始化场景
this.scene = new THREE.Scene() // 创建场景}
初始化相机 :
initCamera() {
// 创建透视相机
this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10);
// 设置相机位置
this.camera.position.set(0, 15, 20);
// 将相机添加到场景中
if (this.scene) {this.scene.add(this.camera);
} else {console.error("Scene is not initialized!");
}
// 设置相机观察目标并更新相关矩阵
this.camera.lookAt(new THREE.Vector3(0, 0, 0));
this.camera.updateProjectionMatrix();
this.camera.updateMatrixWorld();}
初始化渲染器 :
initRenderer() { // 初始化渲染器
this.renderer = new THREE.WebGLRenderer({antialias: true});
// 设置渲染器尺寸
this.renderer.setPixelRatio(window.devicePixelRatio) // 设置屏幕像素比
this.renderer.setSize(window.innerWidth, window.innerHeight) // 渲染的尺寸大小
this.renderer.toneMapping = THREE.ACESFilmicToneMapping // 色调映射
this.renderer.toneMappingExposure = 2 // 曝光程度
this.container.appendChild(this.renderer.domElement)
}
初始化控制器 :
initControl() { // 初始化控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
this.controls.enableDamping = true // 启用阻尼或指数衰减的轨道控制
}
初始化窗口大小 :
windowSizeChange() { // 初始化窗口大小
window.addEventListener("resize", () => {
// 重置渲染器宽高比
this.renderer.setSize(window.innerWidth, window.innerHeight);
// 重置相机宽高比
this.camera.aspect = window.innerWidth / window.innerHeight;
// 更新相机投影矩阵
this.camera.updateProjectionMatrix();});
}
设置渲染函数 :
render() { // 渲染函数
this.renderer.render(this.scene, this.camera)
}
animate() { // 动画函数
this.renderer.setAnimationLoop(this.render.bind(this))
}
完整代码如下:
import * as THREE from 'three'
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'
class Base {constructor(selector) {this.container = document.querySelector(selector)
this.scene
this.camera
this.renderer
this.init()
this.animate()}
init() {this.initScene() // 初始化场景
this.initCamera() // 初始化相机
this.initRenderer() // 初始化渲染器
this.initControl() // 初始化控制器
this.windowSizeChange() // 初始化窗口大小}
initScene() { // 初始化场景
this.scene = new THREE.Scene() // 创建场景}
initCamera() {
// 创建透视相机
this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10);
// 设置相机位置
this.camera.position.set(0, 15, 20);
// 将相机添加到场景中
if (this.scene) {this.scene.add(this.camera);
} else {console.error("Scene is not initialized!");
}
// 设置相机观察目标并更新相关矩阵
this.camera.lookAt(new THREE.Vector3(0, 0, 0));
this.camera.updateProjectionMatrix();
this.camera.updateMatrixWorld();}
initRenderer() { // 初始化渲染器
this.renderer = new THREE.WebGLRenderer({antialias: true});
// 设置渲染器尺寸
this.renderer.setPixelRatio(window.devicePixelRatio) // 设置屏幕像素比
this.renderer.setSize(window.innerWidth, window.innerHeight) // 渲染的尺寸大小
this.renderer.toneMapping = THREE.ACESFilmicToneMapping // 色调映射
this.renderer.toneMappingExposure = 2 // 曝光程度
this.container.appendChild(this.renderer.domElement)
}
initControl() { // 初始化控制器
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
this.controls.enableDamping = true // 启用阻尼或指数衰减的轨道控制
}
windowSizeChange() { // 初始化窗口大小
window.addEventListener("resize", () => {
// 重置渲染器宽高比
this.renderer.setSize(window.innerWidth, window.innerHeight);
// 重置相机宽高比
this.camera.aspect = window.innerWidth / window.innerHeight;
// 更新相机投影矩阵
this.camera.updateProjectionMatrix();});
}
render() { // 渲染函数
this.renderer.render(this.scene, this.camera)
}
animate() { // 动画函数
this.renderer.setAnimationLoop(this.render.bind(this))
}
}
export default Base
写完之后,最后页面呈现一个黑色的背景说明我们的场景加载成功了:
ok,写完基础代码之后,接下来开始具体的 Demo 实操。
平铺元素周期表
本次项目元素周期表并不是使用我们常用的 WebGLRenderer 渲染器,而是 CSS3DRenderer 渲染器,两者区别如下,代码中是可以同时存在这两个渲染器的,它们各自负责不同类型的渲染任务。
WebGLRenderer:用于渲染基于 WebGL 的 3D 场景
CSS3DRenderer:用于渲染基于 CSS 的 3D 对象。这种情况通常用于在 Web 页面中同时显示 3D 对象和其他 HTML 元素,例如在 3D 场景中嵌入文字、按钮等。
因为本次项目单纯就使用基于 CSS 的 3D 对象,所以我们要对之前的代码进行修改,切换渲染器:
createCSS3DRenderer() { // 创建 CSS3D 渲染器
this.renderer3D = new CSS3DRenderer();
this.renderer3D.setSize(window.innerWidth, window.innerHeight);
this.renderer3D.domElement.style.backgroundColor = 'black';
this.container.appendChild(this.renderer3D.domElement);
}
接下来将元素周期表的相关数据进行如下的总结,将元素周期表的数据和位置抽离成 js 文件:
然后接下来在 scene.js 文件中引入元素周期表.js 获取相关数据,进行如下函数创建元素周期表:
createElement() {for (let i = 0; i ' + element[i + 2]
parent.appendChild(detail)
// 实例化 CSS3D 对象
let element3D = new CSS3DObject(parent)
this.objects.push(element3D)
// 加载 3D 场景
this.scene.add(element3D)
}
}
然后我们在 App 根组件中删除 scoped 设置全局 css 样式,给上面创建的 div 类名设置样式:
接下来我们开始处理元素周期表的位置样式,将 element 获取的位置数据进行放大,然后通过页面进行细微的调整:
// 处理元素周期表样式
handleTableStyle() {for (let i = 0; i
然后根据情况设置相机位置进行细微的调整,使得整个场景处于正中央即可:
// 设置相机位置
this.camera.position.set(0, 15, 2800);
最终呈现的效果如下:
螺旋元素周期表
根据上面实现的基础上,接下来我们实现将元素周期表的位置进行一个螺旋状的展示,在 three 中提供了一个 3D 的函数,这个函数通常用于设置一个三维向量的坐标,其中柱面坐标系由一个半径、一个角度和一个高度组成。这种坐标系通常用于描述圆柱体表面上的点的位置,如下:
具体来说,setFromCylindricalCoords 函数接受柱面坐标系的三个参数:
1)radius:柱面坐标系中的半径。
2)theta:柱面坐标系中的角度,以弧度表示。
3)y:柱面坐标系中的高度。
当需要根据柱面坐标系来定位或者旋转一个对象时,可以使用这个函数来方便地设置该对象的位置或者方向,接下来通过如下代码进行简单的测试一下:
// 螺旋元素周期表
spiralTable() {for (let i = 0; i
呈现的效果如下所示,可见是一圈圆,但我们想实现螺旋式的效果应该这么做,这里需要调整:
接下来对上面螺旋周期表函数进行一些参数的调整,然后设置一些 rotation 参数:
// 螺旋元素周期表
spiralTable() {for (let i = 0; i
最终呈现的效果如下,大体效果还是不错的:
网格元素周期表
对于网格处理的函数也很简单,如下该函数的主要逻辑是遍历 this.objects 数组,并为每个元素(即每个物体)计算其在三维空间中的新位置。每个物体在 x、y 和 z 轴上的位置都基于其索引 i 来计算,以达到这种排列效果:
// 网格元素周期表
gridTable() {for (let i = 0; i
最终呈现的效果如下:
球状元素周期表
在写球状元素周期表之前,我们先了解一下球概念,如下:
在 threejs 官网上,也有关于球状相关的 api 方法,如下:
在一个三维场景中,根据球状元素周期表的规则来排列和旋转一系列的物体。这里根据一定的数学规则(这里使用了反余弦函数和平方根函数)来调整 this.objects 数组中每个物体的位置和旋转模拟一种特殊的排列或动画效果:
// 球状元素周期表
ballTable() {for (let i = 0; i
最终呈现的效果如下:
加底部交互按钮
接下来我们实现点击底部的按钮进行不同的场景切换,如下:
效果如下所示:
接下来我们设置其点击后样式:
最终呈现的效果如下:
原文地址: Three.js–》实现 2D 转 3D 的元素周期表