WebGL是浏览器内基于OpenGL ES的底层绘图标准,它允许网页不经过插件直接调用显卡资源渲染实时三维画面。Three.js是一个开源JavaScript库,把WebGL复杂的上下文、着色器与缓冲操作封装成直观的对象模型。借助这两者,前端可以在普通网页中构建可旋转、可缩放、带光影的立体场景,让用户获得接近原生应用的沉浸感。

一、搭建基础渲染环境
创建沉浸式体验的第一步是在页面中建立渲染器、场景与相机这三个核心对象。渲染器负责把三维数据画到画布上,场景是包含所有模型与光源的容器,相机决定用户从哪个位置以什么视角观察。Three.js的WebGLRenderer会自动创建WebGL上下文并处理清屏与合成。
下面代码展示最小可运行结构。我们将渲染器尺寸设为窗口大小,并把相机放在z轴正向以便看到原点处的立方体。动画循环使用requestAnimationFrame保证与浏览器刷新率同步,避免卡顿与资源浪费。
// 创建场景
const scene = new THREE.Scene();
// 透视相机:视场角75,宽高比随窗口,近截面0.1远截面1000
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// 渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 立方体几何与基础材质
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff88 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 渲染循环
function animate() {
requestAnimationFrame(animate);
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
renderer.render(scene, camera);
}
animate();
这种结构看似简单,但已经具备沉浸感雏形:物体持续自转,用户打开页面就进入一个动态空间。后续增加轨道控制器后,还能用鼠标拖拽改变视角,进一步提升参与感。
需要注意,MeshBasicMaterial不受光照影响,看起来偏平面。若想让立体感更强,应改用MeshStandardMaterial并加入灯光,这部分在后续小节展开。
二、光照与材质营造空间深度
真实世界的光影是沉浸感的重要来源。Three.js提供环境光、平行光、点光等多种光源。环境光均匀照亮所有物体,消除纯黑面;平行光模拟太阳,有方向并产生阴影;点光像灯泡,随距离衰减。合理组合它们,模型表面会出现明暗过渡,体积感立刻显现。
材质方面,MeshStandardMaterial基于物理渲染(PBR)模型,支持金属度与粗糙度参数。金属度靠近1时像抛光的金属,粗糙度低则反射清晰。以下示例加入平行光并开启阴影,让立方体在地板上投下实时阴影。
// 改用受光材质
const material = new THREE.MeshStandardMaterial({ color: 0x00ff88, roughness: 0.4, metalness: 0.1 });
// 环境光
const ambient = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambient);
// 平行光并开启阴影
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(5, 10, 7);
dirLight.castShadow = true;
scene.add(dirLight);
// 接收阴影的地面
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(20, 20),
new THREE.MeshStandardMaterial({ color: 0x222222 })
);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
cube.castShadow = true;
renderer.shadowMap.enabled = true;
开启阴影后GPU负担会增加,因为需要额外渲染深度贴图。对中低端设备,可把阴影贴图分辨率调低,或仅对主角物体投影。沉浸不等于全特效拉满,流畅度往往比细腻阴影更重要。
另外,使用纹理贴图能快速提升真实感。通过TextureLoader加载木纹或砖墙图片映射到平面,比纯色材质更有代入感。但贴图体积要压缩,避免首屏加载过久打断体验。
三、加载外部模型与漫游交互
内置几何体只能做演示,真实项目常需加载glTF或OBJ格式模型。glTF被称作3D界的JPEG,体积小且支持动画与PBR材质,是Web端首选。Three.js的GLTFLoader可异步读取文件并把场景节点挂入当前场景。
配合OrbitControls,用户能用鼠标左键旋转、右键平移、滚轮缩放,像逛展厅一样观察模型。下面代码演示加载一个glTF并添加控制器,同时限制缩放距离防止穿模。
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
const loader = new GLTFLoader();
loader.load('https://ipipp.com/models/room.gltf', (gltf) => {
scene.add(gltf.scene);
});
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.minDistance = 2;
controls.maxDistance = 20;
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
阻尼参数enableDamping让镜头滑动有惯性,停止拖拽后缓动收尾,这种细微手感能显著降低生硬感。若面向移动端,还可监听设备方向事件,用陀螺仪转动相机,做成VR类裸眼沉浸。
模型加载建议放在Web Worker或用进度条提示,否则白屏会使用户误以为页面卡死。CDN托管模型文件时,注意跨域头配置,本地开发可用127.0.0.1起静态服务绕过限制。
四、性能优化与常见陷阱
沉浸式场景容易因绘制调用过多掉帧。每添加一个Mesh就可能增加一次Draw Call,几百个独立物体就会拖垮渲染。可用合并几何体或实例化(InstancedMesh)把重复物体压成单次调用。
另一个陷阱是忘记在窗口缩放时更新相机与渲染器。下面代码绑定resize事件,保持画面不变形且清晰。
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
});
像素比限制到2能防止高清屏渲染过多像素导致发热。对于不在视野内的物体,可用视锥体剔除或手动visible=false隐藏。若场景含大量静态背景,可预渲染到纹理减少实时计算。
调试阶段推荐用stats.js监视帧率,发现跌落再针对性优化。切忌一次性加载超大模型或不压缩贴图,那会让首屏时间超过用户耐心阈值,沉浸感无从谈起。
五、总结与落地建议
用WebGL加Three.js做沉浸式3D网页,核心是把底层接口封装成场景图思维:先立渲染器、相机、场景,再补光与材质,后接模型与交互,全程盯性能。新手常陷于手写着色器而忽略整体节奏,其实绝大多数业务用库默认管线已足够。
建议从静态展示起步,确认帧率稳了再加控制器与动画,最后才做移动端适配与VR扩展。把加载、报错、降级方案写进代码,普通浏览器也能平稳交付立体内容。这样,三维体验不再是大厂专属,而是前端可掌控的常规能力。