前端 3D 展示:产品模型与交互

FreeGuideOnline 最新 2026-06-18

理解前端 3D 展示的基础

前端 3D 展示并非遥不可及,它借助浏览器内建的 WebGL 能力,将三维模型直接呈现在网页中。无论是产品展示、数据可视化还是互动叙事,3D 技术都能显著提升用户的沉浸感。最便捷的入门方式是使用封装良好的库,如 Three.js,它屏蔽了大量底层图形学细节,让开发者能够以声明式的方式构建场景、添加光照、导入模型并设置动画。本教程将以 Three.js 为核心,带你一步步实现一个可交互的产品 3D 展示——包括模型加载、旋转查看、环境光与材质调整。

为什么选择 Three.js

  • 生态成熟:社区庞大,文档丰富,拥有大量即插即用的加载器(GLTF、OBJ、FBX 等)。
  • 性能优化:内置 WebGL 渲染器,支持按需渲染、阴影贴图和后期处理。
  • 开发效率:场景、相机、几何体、材质的概念清晰,代码结构直观。

开发环境准备

只需一个 HTML 文件和 CDN 引入的 Three.js。为了保证一致性,建议使用 ES 模块导入或构建工具(如 Vite)。本教程采用 CDN 引入 + 基础 HTML 结构,以保证示例能零门槛运行。

创建一个 product-3d.html,内容骨架如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>产品 3D 展示</title>
  <style>
    body { margin: 0; overflow: hidden; font-family: sans-serif; }
    canvas { display: block; }
  </style>
</head>
<body>
  <script type="importmap">
    {
      "imports": {
        "three": "https://unpkg.com/three@0.157.0/build/three.module.js",
        "three/addons/": "https://unpkg.com/three@0.157.0/examples/jsm/"
      }
    }
  </script>
  <script type="module">
    // 我们的代码将写在这里
  </script>
</body>
</html>

使用 importmap 可以直接在浏览器中用 ES 模块导入 Three.js 及其扩展,而无需打包工具。所有后续代码均放在 <script type="module"> 标签内。

场景、相机与渲染器:三要素的搭建

任何一个 3D 场景都离不开三大核心对象:场景(Scene)、相机(Camera)和渲染器(Renderer)。它们分别负责容纳物体、定义观察者和绘制画面。

初始化渲染器

渲染器需要绑定到一个 <canvas> 元素(Three.js 会自动创建并添加到 body),并设置其尺寸覆盖整个视口。同时开启抗锯齿让边缘更平滑。

import * as THREE from 'three';

const renderer = new THREE.WebGLRenderer({ 
  antialias: true,
  alpha: true        // 透明背景,方便嵌入页面
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); // 限制过高像素比以防性能问题
renderer.shadowMap.enabled = true;   // 开启阴影
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);

创建场景与相机

场景是3D世界的容器,所有模型、灯光等都需添加至场景。相机采用透视相机(PerspectiveCamera),模拟人眼近大远小的效果。参数定义:视野角度(FOV)、宽高比、近裁面、远裁面。

const scene = new THREE.Scene();
scene.background = new THREE.Color(0xeeeeee); // 浅灰背景,更利于观察模型

const camera = new THREE.PerspectiveCamera(
  45,                                   // 视野角度
  window.innerWidth / window.innerHeight, // 宽高比
  0.1,                                  // 近裁面
  100                                    // 远裁面
);
camera.position.set(5, 3, 8);          // 将相机放在模型右前上方
camera.lookAt(0, 0, 0);

设置相机位置为 (5, 3, 8),并让它看向原点,适合展示一个中等大小的产品模型。

处理窗口缩放

当浏览器窗口大小变化时,需同步更新渲染器和相机投影矩阵,以保证画面不变形。

window.addEventListener('resize', () => {
  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);
});

灯光系统:让模型立体起来

没有光照,三维模型只是一片黑色。我们使用组合光源来模拟自然观察环境:

  • 环境光(AmbientLight):均匀照亮所有面,避免死黑区域。
  • 主方向光(DirectionalLight):模拟太阳,产生高光与阴影。
  • 辅助点光源或半球光(可选项):补充暗部细节。

添加基础光照

// 环境光提供基础亮度
const ambientLight = new THREE.AmbientLight(0xffffff, 1.2);
scene.add(ambientLight);

// 主光源:方向光,从左上角照射
const directionalLight = new THREE.DirectionalLight(0xffffff, 3);
directionalLight.position.set(5, 10, 5);
directionalLight.castShadow = true; // 允许投射阴影
// 阴影参数优化
directionalLight.shadow.mapSize.width = 1024;
directionalLight.shadow.mapSize.height = 1024;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
directionalLight.shadow.camera.left = -10;
directionalLight.shadow.camera.right = 10;
directionalLight.shadow.camera.top = 10;
directionalLight.shadow.camera.bottom = -10;
scene.add(directionalLight);

// 补光:半球光,天空色与地面色混合,增加自然感
const hemiLight = new THREE.HemisphereLight(0xddeeff, 0x3b3b3b, 0.8);
scene.add(hemiLight);

至此,一个明亮且带有阴影能力的灯光环境就设置好了。

加载产品模型:从 GLB/GLTF 开始

大多数产品 3D 模型采用 glTF 格式(.glb 为二进制版本,.gltf 为 JSON 版本),因其高效、可压缩且广泛支持 PBR 材质。将模型文件放入项目文件夹(例如 model/product.glb),使用 GLTFLoader 异步加载。

使用 GLTF 加载器

首先,在 import map 中已包含 three/addons/ 路径,可直接导入加载器。

import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

加载模型并将其添加到场景中心,同时启用阴影投射与接收:

const loader = new GLTFLoader();
let productModel; // 保存模型引用,方便后续交互

loader.load(
  'model/product.glb',          // 替换为你的模型路径
  (gltf) => {
    productModel = gltf.scene;
    
    // 遍历模型所有子节点,开启阴影与调整材质(可选)
    productModel.traverse((node) => {
      if (node.isMesh) {
        node.castShadow = true;
        node.receiveShadow = true;
        // 若材质粗糙度等需要调整,可在此设置
        if (node.material) {
          node.material.roughness = 0.4;
          node.material.metalness = 0.6;
        }
      }
    });
    
    // 将模型置于场景原点并适当缩放
    productModel.position.set(0, -0.5, 0);
    productModel.scale.set(1, 1, 1);
    scene.add(productModel);
    
    console.log('模型加载成功');
  },
  (progress) => {
    // 可选:显示加载进度
    const percent = (progress.loaded / progress.total * 100).toFixed(2);
    console.log(`加载进度: ${percent}%`);
  },
  (error) => {
    console.error('模型加载失败:', error);
  }
);

添加一个简单的底座

在产品展示中,一个基本的圆形平面底座可以增强视觉稳定性,同时承接阴影。

const planeGeometry = new THREE.CircleGeometry(2.5, 64);
const planeMaterial = new THREE.MeshStandardMaterial({
  color: 0xcccccc,
  roughness: 0.7,
  metalness: 0.1
});
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -Math.PI / 2; // 旋转至水平
plane.position.y = -1.2;
plane.receiveShadow = true;
scene.add(plane);

用户交互:旋转、缩放与自动旋转

默认情况下,用户无法旋转视角。我们需要实现 轨道控制器(OrbitControls),它支持鼠标拖拽旋转、滚轮缩放、右键平移。

引入轨道控制器

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

初始化控制器并绑定到相机和渲染器:

const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 0, 0);      // 环绕目标点
controls.enableDamping = true;     // 启用惯性
controls.dampingFactor = 0.05;
controls.minDistance = 3;
controls.maxDistance = 15;
controls.maxPolarAngle = Math.PI / 1.8; // 限制向下旋转角度,避免看到“地下”
controls.update();

添加自动旋转选项

许多产品展示页面会加入“自动旋转”功能,用户禁用后可手动控制。通过 OrbitControls 的 autoRotate 属性轻松实现。

controls.autoRotate = true;
controls.autoRotateSpeed = 1.5;    // 旋转速度

// 示例:点击画布可暂停/恢复自动旋转
let autoRotateEnabled = true;
renderer.domElement.addEventListener('click', () => {
  autoRotateEnabled = !autoRotateEnabled;
  controls.autoRotate = autoRotateEnabled;
});

渲染循环与性能优化

一切就绪后,需要一个持续运行的渲染循环通过 requestAnimationFrame 来更新画面。每一帧我们都更新控制器状态并渲染场景。

function animate() {
  requestAnimationFrame(animate);
  
  // 更新控制器(阻尼和自动旋转均依赖此更新)
  controls.update();
  
  // 渲染场景
  renderer.render(scene, camera);
}

animate();

性能考虑点

  • 限制像素比:前面已将 setPixelRatio 限制在 2 以内。
  • 按需渲染:若模型不常变化,可在无交互时停止渲染循环。但 OrbitControls 需要持续更新,因此保留循环。
  • 模型优化:使用压缩的 glTF 文件,多边形数控制在合理范围(通常 5 万面以下即可流畅运行)。

进阶增强:环境映射与后期效果

想让产品更具质感?添加环境贴图(Environment Map)可以让模型反射周围环境,显得更加真实。

使用 RGBELoader 加载 HDR 环境

import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';

// 加载一张 HDR 环境贴图(可从 Poly Haven 等网站获取免费资源)
new RGBELoader()
  .setPath('textures/')
  .load('studio.hdr', (texture) => {
    texture.mapping = THREE.EquirectangularReflectionMapping;
    scene.environment = texture;   // 作用于所有标准材质
    // 可选:将同一张图设为场景背景,但会遮挡原有背景色
    // scene.background = texture;
  });

现在,所有使用 MeshStandardMaterialMeshPhysicalMaterial 的物体都会自动反射出环境光,金属和光滑表面的表现会大幅提升。

后期效果:简单泛光(Bloom)

如果想让高光部分产生光晕效果,可以使用 EffectComposerUnrealBloomPass。首先在 import map 中添加相关模块(路径需包含 postprocessing)。以下给出简化集成步骤:

  1. 导入所需类:
    import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
    import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
    import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
    
  2. 创建 composer 并添加 passes:
    const composer = new EffectComposer(renderer);
    composer.addPass(new RenderPass(scene, camera));
    
    const bloomPass = new UnrealBloomPass(
      new THREE.Vector2(window.innerWidth, window.innerHeight),
      0.5,   // 强度
       0,    // 半径
       0     // 阈值
    );
    composer.addPass(bloomPass);
    
  3. 在 resize 事件中更新 composer 和 bloomPass 的尺寸。
  4. 在动画循环中将 renderer.render 替换为 composer.render()

注意后期处理会消耗额外的 GPU 资源,在移动端需要谨慎评估。

总结与源码整合

至此,你已经完成了一个功能完备的前端 3D 产品展示页面:模型加载、灯光场景、交互控制、环境反射以及可选的后期特效。将上述代码整合进 product-3d.html,替换模型路径即可在本地运行。完整的核心结构如下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>产品 3D 展示</title>
  <style>
    body { margin: 0; overflow: hidden; }
    canvas { display: block; }
  </style>
</head>
<body>
  <script type="importmap">
    {
      "imports": {
        "three": "https://unpkg.com/three@0.157.0/build/three.module.js",
        "three/addons/": "https://unpkg.com/three@0.157.0/examples/jsm/"
      }
    }
  </script>
  <script type="module">
    import * as THREE from 'three';
    import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
    import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
    import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';

    // 渲染器
    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
    renderer.shadowMap.enabled = true;
    renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    document.body.appendChild(renderer.domElement);

    // 场景和相机
    const scene = new THREE.Scene();
    scene.background = new THREE.Color(0xeeeeee);
    const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100);
    camera.position.set(5, 3, 8);
    camera.lookAt(0, 0, 0);

    // 灯光
    scene.add(new THREE.AmbientLight(0xffffff, 1.2));
    const dirLight = new THREE.DirectionalLight(0xffffff, 3);
    dirLight.position.set(5, 10, 5);
    dirLight.castShadow = true;
    dirLight.shadow.mapSize.set(1024, 1024);
    dirLight.shadow.camera.near = 0.5;
    dirLight.shadow.camera.far = 50;
    dirLight.shadow.camera.left = -10;
    dirLight.shadow.camera.right = 10;
    dirLight.shadow.camera.top = 10;
    dirLight.shadow.camera.bottom = -10;
    scene.add(dirLight);
    scene.add(new THREE.HemisphereLight(0xddeeff, 0x3b3b3b, 0.8));

    // 地面
    const plane = new THREE.Mesh(
      new THREE.CircleGeometry(2.5, 64),
      new THREE.MeshStandardMaterial({ color: 0xcccccc, roughness: 0.7, metalness: 0.1 })
    );
    plane.rotation.x = -Math.PI / 2;
    plane.position.y = -1.2;
    plane.receiveShadow = true;
    scene.add(plane);

    // 控制
    const controls = new OrbitControls(camera, renderer.domElement);
    controls.target.set(0, 0, 0);
    controls.enableDamping = true;
    controls.autoRotate = true;
    controls.autoRotateSpeed = 1.5;
    controls.minDistance = 3;
    controls.maxDistance = 15;
    controls.maxPolarAngle = Math.PI / 1.8;
    controls.update();

    // 模型加载
    const loader = new GLTFLoader();
    loader.load('model/product.glb', (gltf) => {
      const model = gltf.scene;
      model.traverse((node) => {
        if (node.isMesh) {
          node.castShadow = true;
          node.receiveShadow = true;
        }
      });
      model.position.set(0, -0.5, 0);
      scene.add(model);
    });

    // 环境贴图(可选)
    new RGBELoader().setPath('textures/').load('studio.hdr', (texture) => {
      texture.mapping = THREE.EquirectangularReflectionMapping;
      scene.environment = texture;
    });

    // 动画循环
    function animate() {
      requestAnimationFrame(animate);
      controls.update();
      renderer.render(scene, camera);
    }
    animate();

    // 响应式
    window.addEventListener('resize', () => {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    });
  </script>
</body>
</html>

下一步探索

  • 边界与手势:限制相机绕 Y 轴的旋转范围,或增加双指触控支持(移动端)。
  • UI 叠加:在 HTML 层添加按钮控制自动旋转、切换背景色或更改产品颜色。
  • 模型交互:使用 Raycaster 实现点击模型某部位触发事件,例如高亮零件或展示信息标签。
  • AR 体验:借助 WebXR API 将模型放置在真实环境中,在移动端实现“查看在你的空间”。

通过本教程,你已掌握前端 3D 展示产品模型与交互的核心技术。开始创建你的第一个可交互 3D 网站吧。