第五章 vue3 + Three.js 实现高级镜面反射效果案例解析

效果图

在 3D 可视化领域,真实的反射效果是提升场景沉浸感的关键因素之一。本文将详细介绍如何使用 Three.js 结合 Vue3 实现高质量的镜面反射效果,包括地面反射和墙面反射,并通过动态物体展示反射效果的实时变化。

效果展示

本文实现的 3D 场景包含以下元素:

  • 一个水平放置的圆形地面镜面
  • 一个垂直放置的矩形墙面镜面
  • 动态旋转的半球体物体
  • 沿轨迹运动的小球体
  • 多面彩色墙壁形成的封闭空间
  • 多角度光源系统

通过这些元素的组合,我们可以观察到物体在不同镜面中的反射效果,以及光线与反射之间的相互作用。

实现方案

我们使用 Vue3 的单文件组件 (SFC) 结构,结合 Three.js 实现这个 3D 场景。核心技术点包括:Three.js 的 Reflector 类实现镜面反射、OrbitControls 实现相机控制、以及 Vue 的生命周期管理 Three.js 资源。

完整代码实现

以下是完整的 Vue3 组件代码,包含详细注释:

html 复制代码
<!-- 镜面效果 -->
<template>
  <div class="mirror-scene">
    <!-- Three.js渲染容器:用于挂载WebGL画布 -->
    <div ref="container" class="scene-container"></div>
    <!-- 场景状态控制面板:展示渲染状态并提供交互按钮 -->
    <div class="status-info" v-if="showInfo">
      <p>场景状态: {{ isRendering ? '渲染中' : '已停止' }}</p>
      <button @click="toggleRendering">
        {{ isRendering ? '停止' : '启动' }}渲染
      </button>
    </div>
  </div>
</template>

<script setup>
// 1. 导入依赖库
// 导入Three.js核心库:包含所有3D渲染所需的基础类(场景、相机、几何体等)
import * as THREE from 'three';
// 导入轨道控制器:允许鼠标拖拽旋转视角、滚轮缩放、右键平移
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 导入反射器类:Three.js官方扩展,用于创建真实的镜面反射效果
import { Reflector } from 'three/addons/objects/Reflector.js';
// 导入Vue3生命周期钩子和响应式API
import { onMounted, onUnmounted, ref, watch } from 'vue';

// 2. DOM元素引用
// 绑定模板中的渲染容器,用于后续挂载Three.js的WebGL画布
const container = ref(null);

// 3. Vue响应式状态管理
// 控制场景是否持续渲染(true=渲染中,false=暂停)
const isRendering = ref(true);
// 控制是否显示状态控制面板(true=显示,false=隐藏)
const showInfo = ref(true);
// 存储动画循环ID:用于后续停止动画(requestAnimationFrame返回的唯一标识)
const animationId = ref(null);

// 4. Three.js核心对象声明(全局变量,方便各函数访问)
let camera; // 相机:模拟人眼视角,决定场景中哪些内容会被渲染
let scene; // 场景:3D世界的容器,所有物体(模型、光源、镜面)都需添加到场景中
let renderer; // 渲染器:将场景和相机的内容渲染成2D图像并显示在画布上
let cameraControls; // 相机控制器:处理用户交互(旋转、缩放、平移)
let sphereGroup; // 球体组:用于统一管理多个球体对象(方便批量旋转)
let smallSphere; // 小球体:场景中的动态物体(做轨迹运动)
let groundMirror; // 地面镜面:水平放置的圆形反射面
let verticalMirror; // 垂直镜面:墙面放置的矩形反射面

/**
 * 5. 初始化Three.js场景(核心入口函数)
 * 作用:创建场景、相机、渲染器,加载所有3D物体并完成初始配置
 */
const initScene = () => {
  // 5.1 创建WebGL渲染器
  // WebGLRenderer:Three.js的核心渲染器,基于WebGL技术渲染3D场景
  // 参数说明:
  // - antialias: true → 开启抗锯齿(使图像边缘更平滑,避免锯齿状)
  // - powerPreference: 'high-performance' → 告诉浏览器优先使用高性能GPU(提升渲染效率)
  renderer = new THREE.WebGLRenderer({
    antialias: true,
    powerPreference: 'high-performance',
  });

  // 设置像素比:匹配设备屏幕的像素密度(避免高清屏幕下图像模糊)
  // window.devicePixelRatio → 获取设备像素比(如Retina屏为2)
  renderer.setPixelRatio(window.devicePixelRatio);

  // 设置渲染画布大小:与容器尺寸一致(占满整个屏幕)
  renderer.setSize(container.value.clientWidth, container.value.clientHeight);

  // 将渲染器生成的画布(domElement)挂载到Vue模板的容器中
  container.value.appendChild(renderer.domElement);

  // 5.2 创建场景容器
  // Scene:Three.js的场景容器,所有3D物体都需要通过scene.add()添加到场景中
  // 场景本身不显示内容,仅用于组织和管理物体
  scene = new THREE.Scene();

  // 5.3 创建透视相机(最常用的相机类型,模拟人眼透视效果)
  // PerspectiveCamera构造参数:
  // 1. fov: 45 → 视场角(Field of View):相机视角的垂直角度(单位:度),值越大看到的范围越广
  // 2. aspect: 宽高比 → 相机视口的宽高比例(通常设为容器宽高比,避免图像拉伸)
  // 3. near: 1 → 近裁剪面:距离相机小于该值的物体不会被渲染(避免渲染过近的物体导致性能问题)
  // 4. far: 500 → 远裁剪面:距离相机大于该值的物体不会被渲染(避免渲染过远的物体导致性能问题)
  camera = new THREE.PerspectiveCamera(
    45,
    container.value.clientWidth / container.value.clientHeight,
    1,
    500
  );

  // 设置相机位置(Three.js中默认坐标系:X轴右、Y轴上、Z轴前)
  // position.set(x, y, z) → 这里将相机放在(0,75,160),即场景上方偏后位置,能看到整个场景
  camera.position.set(0, 75, 160);

  // 5.4 初始化相机控制器(用户交互)
  initControls();

  // 5.5 创建镜面反射效果
  createMirrors();

  // 5.6 创建场景中的3D物体(球体、半球体等)
  createSceneObjects();

  // 5.7 创建光源(没有光源场景会是黑色,光源决定物体的明暗和颜色)
  createLights();

  // 5.8 创建场景边界(墙壁、顶部、底部,形成一个封闭空间)
  createWalls();
};

/**
 * 6. 初始化相机控制器(OrbitControls)
 * 作用:处理用户交互,允许通过鼠标/触摸控制相机视角
 */
const initControls = () => {
  // OrbitControls构造参数:
  // 1. object: camera → 要控制的相机对象
  // 2. domElement: renderer.domElement → 监听交互事件的DOM元素(渲染画布)
  cameraControls = new OrbitControls(camera, renderer.domElement);

  // 设置相机目标点:相机始终看向该点(这里设为(0,40,0),即场景中心偏上位置)
  cameraControls.target.set(0, 40, 0);

  // 设置相机最大/最小距离:限制用户缩放的范围(避免过近或过远导致看不到场景)
  cameraControls.maxDistance = 400; // 最远能拉到400单位距离
  cameraControls.minDistance = 10; // 最近能推到10单位距离

  // 更新控制器状态:初始化后必须调用一次,确保控制器参数生效
  cameraControls.update();
};

/**
 * 7. 创建镜面反射效果(基于Reflector类)
 * 作用:生成真实的镜面,能反射场景中的物体(核心原理:用虚拟相机渲染场景到纹理,再贴到镜面表面)
 */
const createMirrors = () => {
  // 7.1 创建地面镜面(圆形)
  // CircleGeometry:创建圆形几何体(参数:半径、分段数)
  // 1. radius: 40 → 圆形半径(40单位)
  // 2. segments: 64 → 分段数(值越大圆形越平滑,64足够满足视觉效果)
  const groundGeometry = new THREE.CircleGeometry(40, 64);

  // Reflector:Three.js官方扩展的反射器类,用于创建镜面
  // 第一个参数:镜面的几何体(决定镜面的形状和大小)
  // 第二个参数:镜面配置项(核心参数说明)
  // - clipBias: 0.003 → 裁剪偏差(解决镜面反射中物体与镜面边缘的Z轴冲突,避免出现"穿模"闪烁)
  // - textureWidth/textureHeight: 反射纹理的分辨率(设为屏幕分辨率×像素比,保证反射清晰度)
  // - color: 0xb5b5b5 → 镜面颜色(十六进制,这里是浅灰色,模拟真实镜子的底色)
  groundMirror = new Reflector(groundGeometry, {
    clipBias: 0.003,
    textureWidth: window.innerWidth * window.devicePixelRatio,
    textureHeight: window.innerHeight * window.devicePixelRatio,
    color: 0xb5b5b5,
  });

  // 设置地面镜面位置:y=0.5(略微高于地面,避免与地面重叠)
  groundMirror.position.y = 0.5;

  // 旋转镜面:绕X轴旋转-90度(Math.PI/2弧度=90度),使圆形几何体从垂直变为水平(地面效果)
  groundMirror.rotateX(-Math.PI / 2);

  // 将镜面添加到场景中(不添加则不会被渲染)
  scene.add(groundMirror);

  // 7.2 创建垂直镜面(墙面矩形)
  // PlaneGeometry:创建平面几何体(参数:宽度、高度)
  // 1. width: 100 → 平面宽度(100单位)
  // 2. height: 100 → 平面高度(100单位)
  const verticalGeometry = new THREE.PlaneGeometry(100, 100);

  // 创建垂直镜面(配置项与地面镜面一致,颜色略浅)
  verticalMirror = new Reflector(verticalGeometry, {
    clipBias: 0.003,
    textureWidth: window.innerWidth * window.devicePixelRatio,
    textureHeight: window.innerHeight * window.devicePixelRatio,
    color: 0xc1cbcb,
  });

  // 设置垂直镜面位置:y=50(垂直居中),z=-50(放在场景后方,模拟墙面)
  verticalMirror.position.y = 50;
  verticalMirror.position.z = -50;

  // 将垂直镜面添加到场景中
  scene.add(verticalMirror);
};

/**
 * 8. 创建场景中的3D物体(球体组、半球体、小球体)
 * 作用:生成场景中的可视物体,丰富场景内容
 */
const createSceneObjects = () => {
  // 8.1 创建球体组(Object3D是所有3D物体的基类,可作为容器管理多个子物体)
  // 用球体组管理半球体,后续旋转球体组时,半球体也会跟着旋转
  sphereGroup = new THREE.Object3D();
  scene.add(sphereGroup);

  // 8.2 创建半球体(由圆柱顶+半球组成)
  // MeshPhongMaterial:Phong着色材质(支持镜面高光,适合模拟有光泽的物体)
  // 参数说明:
  // - color: 0xffffff → 物体基础颜色(白色)
  // - emissive: 0x8d8d8d → 自发光颜色(浅灰色,使物体在暗处也能显示,模拟微弱反光)
  const sphereMaterial = new THREE.MeshPhongMaterial({
    color: 0xffffff,
    emissive: 0x8d8d8d,
  });

  // 8.2.1 创建球体顶部(圆柱几何体,模拟半球的"底座")
  // CylinderGeometry:圆柱几何体(参数:顶部半径、底部半径、高度、径向分段、高度分段)
  // 1. radiusTop: 0.1 → 顶部半径(接近0,形成尖顶)
  // 2. radiusBottom: 15*cos(30°) → 底部半径(与半球半径匹配,确保衔接平滑)
  // 3. height: 0.1 → 圆柱高度(很薄,仅作为衔接)
  // 4. radialSegments: 24 → 径向分段数(值越大圆柱越平滑)
  // 5. heightSegments: 1 → 高度分段数(1即可,因为高度很薄)
  const sphereCapGeometry = new THREE.CylinderGeometry(
    0.1,
    15 * Math.cos((Math.PI / 180) * 30), // 角度转弧度:30度=Math.PI/6弧度,cos(30°)=√3/2≈0.866
    0.1,
    24,
    1
  );
  // Mesh:几何体+材质的组合(Three.js中可渲染的物体必须是Mesh类型)
  const sphereCap = new THREE.Mesh(sphereCapGeometry, sphereMaterial);

  // 调整圆柱顶位置:与半球底部衔接(计算半球底部的Y坐标,确保无缝连接)
  sphereCap.position.y = -15 * Math.sin((Math.PI / 180) * 30) - 0.05;

  // 旋转圆柱顶:绕X轴旋转180度(Math.PI弧度),使尖顶朝下(与半球衔接)
  sphereCap.rotateX(-Math.PI);

  // 8.2.2 创建半球(SphereGeometry的部分区域)
  // SphereGeometry:球体几何体(参数:半径、宽度分段、高度分段、起始经度、经度范围、起始纬度、纬度范围)
  // 这里通过参数控制,只生成球体的1/3(模拟半球):
  // 1. radius: 15 → 球体半径(15单位)
  // 2. widthSegments: 24 → 水平方向分段数
  // 3. heightSegments: 24 → 垂直方向分段数
  // 4. phiStart: Math.PI/2 → 起始经度(90度,从Y轴正方向开始)
  // 5. phiLength: Math.PI*2 → 经度范围(360度,绕Y轴一周)
  // 6. thetaStart: 0 → 起始纬度(0度,从Z轴正方向开始)
  // 7. thetaLength: Math.PI*120/180 → 纬度范围(120度,仅生成上半部分)
  const halfSphereGeometry = new THREE.SphereGeometry(
    15,
    24,
    24,
    Math.PI / 2,
    Math.PI * 2,
    0,
    (Math.PI / 180) * 120
  );
  const halfSphere = new THREE.Mesh(halfSphereGeometry, sphereMaterial);

  // 将圆柱顶添加到半球作为子物体(这样圆柱顶会跟随半球一起运动)
  halfSphere.add(sphereCap);

  // 旋转半球:调整角度使其倾斜放置(更自然的视觉效果)
  halfSphere.rotateX((-Math.PI / 180) * 135); // 绕X轴旋转-135度
  halfSphere.rotateZ((-Math.PI / 180) * 20); // 绕Z轴旋转-20度

  // 调整半球位置:使其悬浮在场景中(y坐标=7.5+半球底部高度)
  halfSphere.position.y = 7.5 + 15 * Math.sin((Math.PI / 180) * 30);

  // 将半球添加到球体组(后续旋转球体组时,半球会一起旋转)
  sphereGroup.add(halfSphere);

  // 8.3 创建小球体(做轨迹运动的动态物体)
  // IcosahedronGeometry:二十面体几何体(参数:半径、细分级别)
  // 1. radius: 5 → 球体半径(5单位,比半球小)
  // 2. detail: 0 → 细分级别(0为基础二十面体,值越大越接近球体)
  const smallSphereGeometry = new THREE.IcosahedronGeometry(5, 0);

  // 小球体材质:开启平面着色(flatShading=true,使面与面之间有明显边界,风格化效果)
  const smallSphereMaterial = new THREE.MeshPhongMaterial({
    color: 0xffffff,
    emissive: 0x7b7b7b,
    flatShading: true, // 平面着色(关闭平滑着色,突出几何体的面结构)
  });
  smallSphere = new THREE.Mesh(smallSphereGeometry, smallSphereMaterial);

  // 将小球体添加到场景中(不加入球体组,单独做轨迹运动)
  scene.add(smallSphere);
};

/**
 * 9. 创建场景边界(墙壁、顶部、底部)
 * 作用:形成封闭空间,让镜面能反射到更多内容,增强场景真实感
 */
const createWalls = () => {
  // PlaneGeometry:平面几何体(100.1×100.1,比100大0.1是为了避免墙面衔接处出现缝隙)
  const planeGeo = new THREE.PlaneGeometry(100.1, 100.1);

  // 9.1 顶部墙面(白色,模拟天花板)
  const planeTop = new THREE.Mesh(
    planeGeo,
    new THREE.MeshPhongMaterial({ color: 0xffffff }) // 白色漫反射材质
  );
  planeTop.position.y = 100;
  planeTop.rotateX(Math.PI / 2);
  scene.add(planeTop);

  // 底部
  const planeBottom = new THREE.Mesh(
    planeGeo,
    new THREE.MeshPhongMaterial({ color: 0xffffff })
  );
  planeBottom.rotateX(-Math.PI / 2);
  scene.add(planeBottom);

  // 前面
  const planeFront = new THREE.Mesh(
    planeGeo,
    new THREE.MeshPhongMaterial({ color: 0x7f7fff })
  );
  planeFront.position.z = 50;
  planeFront.position.y = 50;
  planeFront.rotateY(Math.PI);
  scene.add(planeFront);

  // 右侧
  const planeRight = new THREE.Mesh(
    planeGeo,
    new THREE.MeshPhongMaterial({ color: 0x00ff00 })
  );
  planeRight.position.x = 50;
  planeRight.position.y = 50;
  planeRight.rotateY(-Math.PI / 2);
  scene.add(planeRight);

  // 左侧
  const planeLeft = new THREE.Mesh(
    planeGeo,
    new THREE.MeshPhongMaterial({ color: 0xff0000 })
  );
  planeLeft.position.x = -50;
  planeLeft.position.y = 50;
  planeLeft.rotateY(Math.PI / 2);
  scene.add(planeLeft);
};

// 创建光源
const createLights = () => {
  // 主光源
  const mainLight = new THREE.PointLight(0xe7e7e7, 2.5, 250, 0);
  mainLight.position.y = 60;
  scene.add(mainLight);

  // 彩色光源
  const greenLight = new THREE.PointLight(0x00ff00, 0.5, 1000, 0);
  greenLight.position.set(550, 50, 0);
  scene.add(greenLight);

  const redLight = new THREE.PointLight(0xff0000, 0.5, 1000, 0);
  redLight.position.set(-550, 50, 0);
  scene.add(redLight);

  const blueLight = new THREE.PointLight(0xbbbbfe, 0.5, 1000, 0);
  blueLight.position.set(0, 50, 550);
  scene.add(blueLight);
};

// 窗口大小调整
const onWindowResize = () => {
  if (!container.value) return;

  const width = container.value.clientWidth;
  const height = container.value.clientHeight;

  camera.aspect = width / height;
  camera.updateProjectionMatrix();

  renderer.setSize(width, height);

  // 更新镜面尺寸
  const pixelRatio = window.devicePixelRatio;
  groundMirror
    .getRenderTarget()
    .setSize(width * pixelRatio, height * pixelRatio);
  verticalMirror
    .getRenderTarget()
    .setSize(width * pixelRatio, height * pixelRatio);
};

/**
 * 动画循环函数:负责更新场景中物体的状态并持续渲染场景
 * 采用requestAnimationFrame实现高效动画,浏览器会自动优化渲染时机
 * 与setInterval相比,能更好地与浏览器刷新频率同步,减少性能浪费
 */
const animate = () => {
  // 如果渲染状态为停止,则直接返回,不执行后续动画逻辑
  if (!isRendering.value) return;

  // 请求下一帧动画,并保存动画ID用于后续停止动画
  // requestAnimationFrame会在浏览器下一次重绘前调用指定函数
  animationId.value = requestAnimationFrame(animate);

  // 计算计时器:基于当前时间生成随时间线性变化的数值
  // Date.now()返回当前时间戳(毫秒),乘以0.01将其转换为更易处理的时间单位
  const timer = Date.now() * 0.01;

  // 更新物体动画:球体组绕Y轴缓慢旋转
  // rotation.y表示绕Y轴的旋转角度(弧度),每次减少0.002产生逆时针旋转效果
  // 负值表示逆时针旋转,正值表示顺时针旋转
  sphereGroup.rotation.y -= 0.002;

  // 更新小球体位置:使其沿三维空间中的椭圆形轨迹运动
  // 使用三角函数实现平滑的周期性运动
  smallSphere.position.set(
    // X轴位置:基于余弦函数,形成左右方向的周期性运动
    // timer * 0.1控制X轴运动周期(值越小周期越长)
    // 乘以30控制X轴方向的运动幅度(轨迹半径)
    Math.cos(timer * 0.1) * 30,
    // Y轴位置:使用绝对值确保小球始终在Y轴正方向运动
    // Math.abs(Math.cos(...))使运动轨迹在Y轴方向形成上下波动的"笑脸"曲线
    // +5确保小球最低位置不会低于Y=5,避免与地面镜面过度重叠
    Math.abs(Math.cos(timer * 0.2)) * 20 + 5,
    // Z轴位置:基于正弦函数,形成前后方向的周期性运动
    // 与X轴使用相同周期但不同函数(正弦vs余弦),形成圆形轨迹
    Math.sin(timer * 0.1) * 30
  );

  // 更新小球体自身旋转:绕Y轴旋转
  // 旋转角度与位置同步,使小球始终面向运动方向
  smallSphere.rotation.y = Math.PI / 2 - timer * 0.1;

  // 绕Z轴快速旋转:增加视觉动感
  // 0.8的系数使旋转速度快于位置变化,形成更丰富的动画效果
  smallSphere.rotation.z = timer * 0.8;

  // 渲染场景:将当前状态的3D场景通过相机视角渲染到画布
  // 这是Three.js动画的最后一步,将所有状态更新反映到屏幕上
  renderer.render(scene, camera);
};

// 切换渲染状态
const toggleRendering = () => {
  isRendering.value = !isRendering.value;
  if (isRendering.value) {
    animate();
  }
};

// 组件挂载时初始化
onMounted(() => {
  if (container.value) {
    initScene();
    animate();
    window.addEventListener('resize', onWindowResize);
  }
});

// 组件卸载时清理
onUnmounted(() => {
  // 停止动画
  if (animationId.value) {
    cancelAnimationFrame(animationId.value);
  }

  // 移除事件监听
  window.removeEventListener('resize', onWindowResize);

  // 清理控制器
  if (cameraControls) {
    cameraControls.dispose();
  }

  // 清理渲染器
  if (renderer) {
    renderer.dispose();
    if (container.value && renderer.domElement) {
      container.value.removeChild(renderer.domElement);
    }
  }

  // 清理场景
  if (scene) {
    scene.clear();
  }
});

// 监听渲染状态变化
watch(isRendering, (newVal) => {
  if (newVal && !animationId.value) {
    animate();
  }
});
</script>
<style scoped>
.mirror-scene {
  position: relative;
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}
.scene-container {
  width: 100%;
  height: 100%;
}
.render-container {
  width: 100%;
  height: 100%;
}
.status-info {
  position: absolute;
  top: 5rem;
  left: 10rem;
  background-color: rgba(0, 0, 0, 0.7);
  color: white;
  padding: 0.75rem 1rem;
  border-radius: 4px;
  font-family: sans-serif;
  font-size: 0.9rem;
  z-index: 10;
  display: flex;
  gap: 1rem;
  align-items: center;
}

.status-info button {
  background-color: #42b983;
  color: white;
  border: none;
  padding: 0.4rem 0.8rem;
  border-radius: 3px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.status-info button:hover {
  background-color: #35956a;
}

.status-info button:active {
  background-color: #2a7d56;
}
</style>

核心技术解析

1. 镜面反射原理

Three.js 的 Reflector 类是实现镜面反射的核心,其工作原理如下:

  1. 创建一个与镜面大小相同的虚拟相机
  2. 将虚拟相机放置在镜面的对称位置(相对于真实相机)
  3. 使用虚拟相机渲染场景到一个纹理 (Texture)
  4. 将这个纹理应用到镜面表面

这种方法能产生非常真实的反射效果,但也会增加渲染开销,因为每面镜子都需要额外渲染一次场景。

2. 关键参数配置

在创建 Reflector 时,有几个关键参数需要特别注意:

复制代码
new Reflector(geometry, {
  clipBias: 0.003, // 解决Z轴冲突,避免反射物体与镜面边缘闪烁
  textureWidth: window.innerWidth * window.devicePixelRatio, // 反射纹理宽度
  textureHeight: window.innerHeight * window.devicePixelRatio, // 反射纹理高度
  color: 0xb5b5b5, // 镜面颜色,影响反射色调
});
  • clipBias:解决 Z-fighting 问题,当物体接近镜面时可能出现的闪烁
  • 纹理尺寸:直接影响反射效果的清晰度,值越大效果越好但性能消耗也越大
  • color:镜面本身的颜色,会与反射内容混合

3. 性能优化建议

由于镜面反射需要额外的渲染通道,可能会影响性能,特别是在移动设备上。以下是一些优化建议:

  1. 适当降低反射纹理的分辨率(例如使用 0.5 倍屏幕分辨率)
  2. 减少镜面数量,避免过多的反射面
  3. 使用powerPreference: 'high-performance'让浏览器优先选择高性能 GPU
  4. 复杂场景可考虑使用简化的反射模型,如环境贴图 (Environment Map)

使用方法

  1. 确保已安装 Three.js:npm install three
  2. 将上述代码保存为MirrorScene.vue组件
  3. 在你的 Vue 应用中引入并使用该组件
  4. 运行应用,你将看到一个带有镜面反射效果的 3D 场景
  5. 可以通过以下方式与场景交互:
    • 鼠标左键拖拽:旋转视角
    • 鼠标滚轮:缩放场景
    • 鼠标右键拖拽:平移视角
    • 点击 "停止 / 启动渲染" 按钮:控制动画状态

扩展思路

这个基础实现可以通过以下方式进一步扩展:

  1. 添加更多不同形状和位置的镜面,观察反射之间的相互影响
  2. 实现镜面反射的开关控制,方便对比有无反射的效果差异
  3. 添加更复杂的 3D 模型替代简单几何体,观察复杂物体的反射效果
  4. 尝试不同的镜面材质参数,如增加粗糙度 (roughness) 模拟非完美镜面
  5. 实现动态调整镜面颜色和反射强度的控制面板

案例源码可访问:three.js exampleshttp://www.yanhuangxueyuan.com/threejs/examples/?q=PlaneGeometry#webgl_mirror

一键三连,感谢关注

相关推荐
BFT白芙堂4 小时前
松灵斯坦福Mobile ALOHA同款 | 通过低成本全身远程操作实现双手机器人移动操控学习
人工智能·学习·机器人·移动机器人·论文解读·开源双臂遥操作系统·松灵cobotmagic
陈佬昔没带相机4 小时前
用 Dify/Coze 定制企业级的 AI 问答助手
人工智能·开源·coze
云天徽上4 小时前
【数据可视化-104】安徽省2025年上半年GDP数据可视化分析:用Python和Pyecharts打造炫酷大屏
开发语言·python·信息可视化·数据分析·数据可视化
健康有益科技4 小时前
智能化健康座舱:重构出行健康管理模式的核心力量
人工智能·车载系统·汽车·健康医疗
深瞳智检4 小时前
深度学习环境搭建运行(一) Ubuntu22.04 系统安装 CUDA11.8 和 CUDNN8.6.0 详细步骤(新手入门)
人工智能·python·深度学习·yolo·计算机视觉
前端开发爱好者5 小时前
90% 前端都不知道的 20 个「零依赖」浏览器原生能力!
前端·javascript·vue.js
大学生毕业题目5 小时前
毕业项目推荐:64-基于yolov8/yolov5/yolo11的蝴蝶种类检测识别系统(Python+卷积神经网络)
人工智能·python·yolo·目标检测·cnn·pyqt·蝴蝶检测
AI浩5 小时前
深度学习核心损失函数详解:交叉熵、MSE、对比学习(InfoNCE)
人工智能·深度学习·学习
新智元5 小时前
刚刚,OpenAI把1GW超算中心直接给了印度!奥特曼即将亲赴办事处
人工智能·openai
深蓝学院5 小时前
[ICCV25]TRACE:用3D高斯直接学习物理参数,让AI“推演”未来场景
人工智能·学习·3d