Three.js模拟昼夜切换(太阳落山)效果

1. 实现思路

观察现实生活中的昼夜交替,最直观的表现就是太阳和月亮的升起降落。白天太阳位置更高,随着夜幕的降临,太阳会逐渐落下,大地上事物的影子也会变得比白天更长。太阳落下去,月亮升起来,月光比日光更加的柔和,没有日光那么的强烈……
综上述一系列废话,得出昼夜切换效果的实现思路:

1.1 光源的位置移动

光源的高度越低,产生的影子也就越长。
在这里插入图片描述

1.2 光源亮度的降低

制造夜间昏暗的氛围。

1.3 补间动画来让光源的移动更平滑

模拟时间推移的平滑过程。

2. 实现过程

2.1 设置渲染器,允许产生阴影

// 允许渲染器产生阴影贴图
renderer.shadowMap.enabled = true;

2.2 创建光源,设置光源产生阴影

// 创建点光源
pointLight = new Three.PointLight(0xffffff, 2.5);
// 设置点光源产生阴影
pointLight.castShadow = true;
// 设置点光源的位置
pointLight.position.set(-10, 10, 0);
// 将点光源添加到场景中
scene.add(pointLight);

2.3 创建一些物体,模拟地面,和地面上的物体

// 创建一个平面,模拟地面
const planeGeometry = new Three.PlaneGeometry(50, 50);
const planeMaterial = new Three.MeshStandardMaterial({
    
    
  color: 0xf5f5f5,
});
const plane = new Three.Mesh(planeGeometry, planeMaterial);
// 翻转平面90度,使其平行于X0Z平面
plane.rotateX(-0.5 * Math.PI);
// 设置平面接受阴影
plane.receiveShadow = true;
scene.add(plane);

// 创建一个六面体,模拟地面上的各种事物
const boxGeometry = new Three.BoxGeometry(5, 5, 5);
const boxMaterial = new Three.MeshStandardMaterial({
    
    
  color: 0x0f3f0f,
});
const box = new Three.Mesh(boxGeometry, boxMaterial);
box.position.set(0, 2.5, 0);
// 设置物体产生影子
box.castShadow = true;
scene.add(box);

2.4 阴影效果

在这里插入图片描述

2.5 处理光源的移动

2.5.1 代码

 // 当前光源的位置以及光的亮度
 const curLightPosition = pointLight.position;
 // 光源要移动到的位置,和光源要改变为的强度
 const targetPosition = this.isInDay ? new Three.Vector3(-15, 6, 0) : new Three.Vector3(-10, 10, 0);
 // 创建TWEEN对象
 let lightTween = new TWEEN.Tween({
    
    
   x: curLightPosition.x, y: curLightPosition.y, z: curLightPosition.z,
 }).to({
    
    
   x: targetPosition.x,
   y: targetPosition.y,
   z: targetPosition.z,
 }, 1000).easing(TWEEN.Easing.Quadratic.InOut);
 lightTween.onUpdate((obj) => {
    
    
   // 随着duration的推移,将光源更新到”此刻“变化到的位置和发光强度
   pointLight.position.set(obj.x, obj.y, obj.z);
 });
 lightTween.onComplete(() => {
    
    
   // 结束后,不再在每帧渲染时更新TWEEN
   this.logoutRenderFunc('day-night');
   lightTween = null;
   // 更新标志位
   this.isInDay = !this.isInDay;
 });
 // 开始补间动画(注意配合每帧渲染时的TWEEN.update()更新动画)
 lightTween.start();
 this.registerRenderFunc('day-night', () => {
    
    
   // 每帧渲染时更新补间动画
   TWEEN && lightTween && TWEEN.update();
 });

2.5.2 实现效果

在这里插入图片描述

2.6 处理光照强度的改变

2.6.1 代码

// 当前光源的位置以及光的亮度
      const curLightPosition = pointLight.position;
      const curLightIntensity = pointLight.intensity;
      // 光源要移动到的位置,和光源要改变为的强度
      const targetPosition = this.isInDay ? new Three.Vector3(-15, 6, 0) : new Three.Vector3(-10, 10, 0);
      const targetIntensity = this.isInDay ? 1 : 2.5;
      // 创建TWEEN对象
      let lightTween = new TWEEN.Tween({
    
    
        x: curLightPosition.x, y: curLightPosition.y, z: curLightPosition.z,
        intensity: curLightIntensity
      }).to({
    
    
        x: targetPosition.x,
        y: targetPosition.y,
        z: targetPosition.z,
        intensity: targetIntensity
      }, 1000).easing(TWEEN.Easing.Quadratic.InOut);
      lightTween.onUpdate((obj) => {
    
    
        // 随着duration的推移,将光源更新到”此刻“变化到的位置和发光强度
        pointLight.position.set(obj.x, obj.y, obj.z);
        pointLight.intensity = obj.intensity;
      });
      lightTween.onComplete(() => {
    
    
        // 结束后,不再在每帧渲染时更新TWEEN
        this.logoutRenderFunc('day-night');
        lightTween = null;
        // 更新标志位
        this.isInDay = !this.isInDay;
      });
      // 开始补间动画(注意配合每帧渲染时的TWEEN.update()更新动画)
      lightTween.start();
      this.registerRenderFunc('day-night', () => {
    
    
        // 每帧渲染时更新补间动画
        TWEEN && lightTween && TWEEN.update();
      });

2.6.2 效果

在这里插入图片描述

3. 使用库版本

  • "three": "0.142.0"
  • "vue": "^3.2.13"

4. 关键代码

/**
 * 昼夜切换
 */
turnDayNight() {
    
    
  // 当前光源的位置以及光的亮度
  const curLightPosition = pointLight.position;
  const curLightIntensity = pointLight.intensity;
  // 光源要移动到的位置,和光源要改变为的强度
  const targetPosition = this.isInDay ? new Three.Vector3(-15, 6, 0) : new Three.Vector3(-10, 10, 0);
  const targetIntensity = this.isInDay ? 1 : 2.5;
  // 创建TWEEN对象
  let lightTween = new TWEEN.Tween({
    
    
    x: curLightPosition.x, y: curLightPosition.y, z: curLightPosition.z,
    intensity: curLightIntensity
  }).to({
    
    
    x: targetPosition.x,
    y: targetPosition.y,
    z: targetPosition.z,
    intensity: targetIntensity
  }, 1000).easing(TWEEN.Easing.Quadratic.InOut);
  lightTween.onUpdate((obj) => {
    
    
    // 随着duration的推移,将光源更新到”此刻“变化到的位置和发光强度
    pointLight.position.set(obj.x, obj.y, obj.z);
    pointLight.intensity = obj.intensity;
  });
  lightTween.onComplete(() => {
    
    
    // 结束后,不再在每帧渲染时更新TWEEN
    this.logoutRenderFunc('day-night');
    lightTween = null;
    // 更新标志位
    this.isInDay = !this.isInDay;
  });
  // 开始补间动画(注意配合每帧渲染时的TWEEN.update()更新动画)
  lightTween.start();
  this.registerRenderFunc('day-night', () => {
    
    
    // 每帧渲染时更新补间动画
    TWEEN && lightTween && TWEEN.update();
  });
},

/**
 * 创建地面上的事物
 */
createThings() {
    
    
  // 创建一个平面,模拟地面
  const planeGeometry = new Three.PlaneGeometry(50, 50);
  const planeMaterial = new Three.MeshStandardMaterial({
    
    
    color: 0xf5f5f5,
  });
  const plane = new Three.Mesh(planeGeometry, planeMaterial);
  // 翻转平面90度,使其平行于X0Z平面
  plane.rotateX(-0.5 * Math.PI);
  // 设置平面接受阴影
  plane.receiveShadow = true;
  scene.add(plane);

  // 创建一个六面体,模拟地面上的各种事物
  const boxGeometry = new Three.BoxGeometry(5, 5, 5);
  const boxMaterial = new Three.MeshStandardMaterial({
    
    
    color: 0x0f3f0f,
  });
  const box = new Three.Mesh(boxGeometry, boxMaterial);
  box.position.set(0, 2.5, 0);
  // 设置物体产生影子
  box.castShadow = true;
  scene.add(box);
},

/**
 * 生成线性颜色
 * @param colorStr 颜色字符串
 * @returns {Color}
 */
lineColor(colorStr) {
    
    
  const tmp = new Three.Color(colorStr);
  tmp.convertSRGBToLinear();
  return tmp;
},

/**
 * 注册渲染中执行的方法
 * @param name 设定函数名称
 * @param func 函数方法体
 */
registerRenderFunc(name, func) {
    
    
  this.renderFunc[name] = func;
},

/**
 * 注销渲染中执行的方法
 * @param name 要注销的函数名称
 */
logoutRenderFunc(name) {
    
    
  const old = this.renderFunc[name];
  if (old) {
    
    
    delete this.renderFunc[name];
  }
},

/**
 * 初始化3d场景、渲染器、相机等部件
 */
init() {
    
    
  const parent = document.querySelector('#canvas-box');
  this.parent = document.querySelector('#canvas-box');
  const maxWith = parent.clientWidth - 31;
  const maxHeight = parent.clientHeight - 63;
  scene = new Three.Scene();

  // 创建点光源
  pointLight = new Three.PointLight(0xffffff, 2.5);
  // 设置点光源产生阴影
  pointLight.castShadow = true;
  // 设置点光源的位置
  pointLight.position.set(-10, 10, 0);
  // 将点光源添加到场景中
  scene.add(pointLight);

  // 创建网格辅助器
  const grid = new Three.GridHelper(2000, 50, 0xffffff, 0xffffff);
  grid.material.opacity = 0.2;
  grid.material.transparent = true;
  scene.add(grid);

  // 创建相机
  camera = new Three.PerspectiveCamera(45, maxWith / maxHeight, 1, 2000);
  camera.position.set(0, 300, -700);

  // 创建渲染器
  renderer = new Three.WebGLRenderer({
    
    
    antialias: true, // 消除锯齿
    logarithmicDepthBuffer: true, // 对数深度缓冲区
  });
  renderer.setSize(maxWith, maxHeight);
  renderer.shadowMap.enabled = true;
  renderer.setClearColor(0x000000);
  parent.appendChild(renderer.domElement);

  // 创建控制器
  orbitControls = new OrbitControls(camera, renderer.domElement);

  // 改变窗口大小,更新相机画面大小和渲染器大小
  window.addEventListener('resize', () => {
    
    
    camera.aspect = (this.parent.clientWidth - 31) / (this.parent.clientHeight - 63);
    camera.updateProjectionMatrix();
    renderer.setSize(this.parent.clientWidth - 31, this.parent.clientHeight - 63);
  });

  // 创建完毕,开始执行每帧渲染
  this.render();
},

/**
 * 开始渲染
 */
render() {
    
    
  const _this = this;

  // 动画循环渲染
  function animate() {
    
    
    try {
    
    
      // 预约下一帧的渲染动作
      requestAnimationFrame(animate);

      try {
    
    
        // 让渲染器渲染一帧相机捕捉到的场景
        renderer.render(scene, camera);
      } catch (e) {
    
    
        console.log(e);
      }

      // 更新控制器
      orbitControls.update();

      // 获取时间差
      const delta = clock.getDelta();

      // 执行registerRenderFunc方式注册进来的渲染操作
      const funcNames = _this.renderFunc && Object.keys(_this.renderFunc);
      if (funcNames && funcNames.length > 0) {
    
    
        funcNames.forEach((funcName) => {
    
    
          try {
    
    
            // 不太放心,try-catch一下,保证出现意外也能继续执行后面的内容
            _this.renderFunc[funcName](delta);
          } catch (e) {
    
    
            console.error(
              'render func error, func name: ',
              funcName,
              ', error message:',
              e.message,
            );
          }
        });
      }
    } catch (e) {
    
    
      console.error('render animate error,  error message: ', e.message);
    }
  }

  animate();
},
  • 初始化
mounted() {
    
    
  this.$nextTick(() => {
    
    

    // vue实例挂载完成后,初始化3D场景资源
    this.init();
    this.createThings();

  });
},
  • 昼夜切换时调用
<el-button type="success"  @click="turnDayNight">切换</el-button>

猜你喜欢

转载自blog.csdn.net/u010657801/article/details/130009622