Skip to content

Three.js - 渲染器与响应式设计

Three.js - 场景、渲染器与响应式布局

Section titled “Three.js - 场景、渲染器与响应式布局”

THREE.Scene 对象是称为场景图(Scene Graph)的根结构。你可以将其想象成一棵树,场景是树干,而你添加的每个对象——网格(meshes)、光源(lights)、相机(cameras),甚至是其他对象组——都是树枝。这种层级结构是3D引擎组织和渲染内容的基础。

你可以使用 .add() 方法将任何继承自 THREE.Object3D 的对象添加到场景中,并使用 .remove() 方法将其移除。

const scene = new THREE.Scene();
const mesh = new THREE.Mesh(/* ... */);
// 将网格添加到场景中
scene.add(mesh);
// 场景的 children 属性是一个包含所有顶层对象的数组
console.log(scene.children.length); // Output: 1
// 从场景中移除网格
scene.remove(mesh);
console.log(scene.children.length); // Output: 0

调用 scene.remove(object) 仅仅是从场景图中断开对象的连接。它不会释放其几何体(geometry)、材质(material)或纹理(textures)所占用的内存。在频繁添加和移除对象的应用中,这可能导致内存泄漏。要正确清理,你必须对这些资源调用 .dispose() 方法。

function disposeObject(object) {
// 遍历对象及其子对象
object.traverse(child => {
if (child.isMesh) {
// 销毁几何体和材质
if (child.geometry) {
child.geometry.dispose();
}
if (child.material) {
// 如果材质是一个数组,则销毁其中的每个材质
if (Array.isArray(child.material)) {
child.material.forEach(material => material.dispose());
} else {
child.material.dispose();
}
}
}
});
// 最后,将对象从场景中移除
scene.remove(object);
}
// 用法:
disposeObject(myMeshGroup);

渲染器(Renderer)是一个组件,它接收你的 Scene 和 Camera,并将结果绘制到网页上的 <canvas> 元素中。最强大且常用的渲染器是 THREE.WebGLRenderer,它利用 WebGL API 调用用户的 GPU 来实现高性能渲染。

虽然存在其他渲染器(如 CSS2DRenderer 和 CSS3DRenderer),但它们是专用工具,通常用于在 WebGL 画布之上叠加基于HTML的用户界面元素,而不是用于渲染主要的3D场景。

3D场景必须像普通网站一样适应浏览器窗口的大小。这通过监听窗口的 resize 事件并相应地更新相机和渲染器来实现。

window.addEventListener('resize', () => {
// 1. 更新画布和视口大小
const width = window.innerWidth;
const height = window.innerHeight;
// 2. 更新相机的宽高比
camera.aspect = width / height;
camera.updateProjectionMatrix(); // 在修改 'aspect' 等属性后必须调用此方法
// 3. 更新渲染器大小
renderer.setSize(width, height);
// 4. 调整高DPI显示器(例如Retina屏幕)的像素比例
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});

updateProjectionMatrix() 调用至关重要;它会通知相机根据新的宽高比重新计算其透视效果。

此示例演示了如何动态地从场景中添加和移除对象。它采用了简洁现代的结构,并包含了必要的响应式逻辑。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js - 场景管理</title>
<style>
body { margin: 0; }
canvas.webgl { display: block; }
.ui-controls {
position: absolute;
top: 10px;
left: 10px;
background: rgba(0,0,0,0.5);
padding: 10px;
border-radius: 5px;
}
button { margin: 5px; padding: 8px 12px; cursor: pointer; }
</style>
</head>
<body>
<div class="ui-controls">
<button id="add-cube">添加立方体</button>
<button id="remove-cube">移除最后一个立方体</button>
</div>
<canvas class="webgl"></canvas>
<script type="module" src="/main.js"></script>
</body>
</html>
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// --- 核心设置 ---
const scene = new THREE.Scene();
const canvas = document.querySelector('canvas.webgl');
const sizes = { width: window.innerWidth, height: window.innerHeight };
const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100);
camera.position.set(10, 10, 15);
scene.add(camera);
const renderer = new THREE.WebGLRenderer({ canvas });
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;
// --- 光照与环境 ---
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1.0);
directionalLight.position.set(5, 5, 5);
scene.add(directionalLight);
// --- 场景操作逻辑 ---
let cubeCounter = 0;
document.getElementById('add-cube').addEventListener('click', () => {
const geometry = new THREE.BoxGeometry(2, 2, 2);
const material = new THREE.MeshStandardMaterial({
color: new THREE.Color(Math.random(), Math.random(), Math.random())
});
const cube = new THREE.Mesh(geometry, material);
// 赋予它一个唯一的名称以便识别
cube.name = `cube-${cubeCounter++}`;
// 随机定位
cube.position.set(
(Math.random() - 0.5) * 20,
(Math.random()) * 5 + 1,
(Math.random() - 0.5) * 20
);
scene.add(cube);
console.log(`Added ${cube.name}. Total objects in scene: ${scene.children.length}`);
});
document.getElementById('remove-cube').addEventListener('click', () => {
// 查找最后一个添加的立方体
const lastCube = scene.children.find(obj => obj.name.startsWith('cube-'));
if (lastCube) {
console.log(`Removing ${lastCube.name}`);
// 正确的清理
lastCube.geometry.dispose();
lastCube.material.dispose();
scene.remove(lastCube);
console.log(`Removed cube. Total objects in scene: ${scene.children.length}`);
} else {
console.log('No more cubes to remove.');
}
});
// --- 动画循环 ---
const tick = () => {
controls.update();
renderer.render(scene, camera);
window.requestAnimationFrame(tick);
};
tick();
// --- 响应式处理 ---
window.addEventListener('resize', () => {
sizes.width = window.innerWidth;
sizes.height = window.innerHeight;
camera.aspect = sizes.width / sizes.height;
camera.updateProjectionMatrix();
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});