效果
https://threejs.org/examples/#webgl_interactive_cubes
代码地址
https://github.com/mrdoob/three.js/blob/master/examples/webgl_interactive_cubes.html
以下是对这段代码的详细解释,按照功能模块逐步解析:
1. HTML 部分
基础结构
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - interactive cubes</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<style>
body {
background-color: #f0f0f0;
color: #444;
}
a {
color: #08f;
}
</style>
</head>
<body>
- 定义了一个标准的 HTML5 文档。
- 设置了页面标题为
three.js webgl - interactive cubes
。 - 使用
<meta>
标签确保页面在移动设备上正确显示(禁用缩放)。 - 内联样式设置了页面背景颜色和链接颜色。
信息展示
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - interactive cubes
</div>
- 在页面中添加了一个
<div>
元素,用于显示三.js 的链接和示例名称。
模块导入
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
- 使用
<script type="importmap">
定义模块路径:three
指向 three.js 的主模块文件。three/addons/
指向附加模块目录。
2. JavaScript 部分
导入依赖
<script type="module">
import * as THREE from 'three';
import Stats from 'three/addons/libs/stats.module.js';
- 使用 ES6 模块语法导入 three.js 和
Stats
工具:THREE
是 three.js 的核心库。Stats
用于监控性能(帧率、内存占用等)。
变量声明
let stats;
let camera, scene, raycaster, renderer;
let INTERSECTED;
let theta = 0;
const pointer = new THREE.Vector2();
const radius = 5;
stats
:用于存储性能统计工具实例。camera
:相机对象。scene
:场景对象。raycaster
:射线检测工具。renderer
:渲染器。INTERSECTED
:存储当前被鼠标悬停的物体。theta
:控制相机旋转角度。pointer
:存储鼠标的标准化设备坐标。radius
:相机绕场景中心旋转的半径。
初始化函数 (init)
function init() {
// 创建相机
camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 0.1, 100);
// 创建场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
// 添加光源
const light = new THREE.DirectionalLight(0xffffff, 3);
light.position.set(1, 1, 1).normalize();
scene.add(light);
// 创建随机分布的立方体
const geometry = new THREE.BoxGeometry();
for (let i = 0; i < 2000; i++) {
const object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({ color: Math.random() * 0xffffff }));
object.position.x = Math.random() * 40 - 20;
object.position.y = Math.random() * 40 - 20;
object.position.z = Math.random() * 40 - 20;
object.rotation.x = Math.random() * 2 * Math.PI;
object.rotation.y = Math.random() * 2 * Math.PI;
object.rotation.z = Math.random() * 2 * Math.PI;
object.scale.x = Math.random() + 0.5;
object.scale.y = Math.random() + 0.5;
object.scale.z = Math.random() + 0.5;
scene.add(object);
}
// 创建渲染器
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(animate);
document.body.appendChild(renderer.domElement);
// 添加性能统计工具
stats = new Stats();
document.body.appendChild(stats.dom);
// 监听窗口调整事件
window.addEventListener('resize', onWindowResize);
// 监听鼠标移动事件
document.addEventListener('mousemove', onPointerMove);
}
- 相机:创建了一个透视相机,设置视场角、宽高比、近裁剪面和远裁剪面。
- 场景:创建了一个新场景,并设置了背景颜色。
- 光源:添加了一个方向光,用于照亮场景中的物体。
- 立方体生成:通过循环生成了 2000 个随机位置、旋转角度、缩放比例和颜色的立方体。
- 渲染器:创建了一个 WebGL 渲染器,并将其添加到页面中。
- 性能统计工具:初始化并显示
Stats
工具。 - 事件监听:
- 窗口调整时调用
onWindowResize
。 - 鼠标移动时调用
onPointerMove
。
- 窗口调整时调用
窗口调整事件处理
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
- 当窗口大小发生变化时,更新相机的宽高比和渲染器的尺寸。
鼠标移动事件处理
function onPointerMove(event) {
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
}
- 将鼠标的屏幕坐标转换为标准化设备坐标(范围从 -1 到 1)。
动画与渲染
function animate() {
render();
stats.update();
}
function render() {
theta += 0.1;
camera.position.x = radius * Math.sin(THREE.MathUtils.degToRad(theta));
camera.position.y = radius * Math.sin(THREE.MathUtils.degToRad(theta));
camera.position.z = radius * Math.cos(THREE.MathUtils.degToRad(theta));
camera.lookAt(scene.position);
camera.updateMatrixWorld();
// 射线检测
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children, false);
if (intersects.length > 0) {
if (INTERSECTED !== intersects[0].object) {
if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
INTERSECTED = intersects[0].object;
INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
INTERSECTED.material.emissive.setHex(0xff0000);
}
} else {
if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
INTERSECTED = null;
}
renderer.render(scene, camera);
}
- 动画循环:
- 调用
render
函数进行渲染。 - 更新性能统计信息。
- 调用
- 渲染逻辑:
- 更新相机的位置,使其围绕场景中心旋转。
- 使用
Raycaster
检测鼠标与场景中物体的交点。 - 如果检测到交点,则高亮显示被选中的立方体(红色自发光效果)。
- 最后调用渲染器绘制场景。
总结
这段代码实现了一个交互式的 3D 场景,主要功能包括:
- 创建一个包含 2000 个随机分布立方体的场景。
- 使用射线检测 (
Raycaster
) 实现鼠标悬停高亮效果。 - 动态调整相机位置,提供环绕观察的效果。
- 添加性能统计工具 (
Stats
) 以监控帧率和内存占用。
通过这些功能,用户可以体验到一个动态且交互性强的 3D 场景。
前端工程师、程序员
标签:
threejs
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
2018-03-04 vi 使用小结