欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue中使用Three.js實現(xiàn)動態(tài)海洋與天空背景

 更新時間:2025年06月07日 11:10:10   作者:mb683dbd30b8b2c  
文章介紹在Vue中使用Three.js實現(xiàn)動態(tài)海洋與天空背景,通過組合式API管理場景生命周期,結合Water和Sky類創(chuàng)建水面波動及天空效果,動態(tài)調整太陽位置,適配窗口變化并清理資源,提升登錄頁視覺效果與3D渲染集成能力

背景

常規(guī)的后臺管理系統(tǒng)登陸頁面可能就只是一個簡單的背景頁面,這不太好看,接下來讓我們來使用three.js來實現(xiàn)一個動態(tài)的海洋和天空效果當作背景,這樣的效果總會讓人眼前一亮,如下圖所示。

動態(tài)海洋和天空效果的vue hooks_著色器

代碼實現(xiàn)

接下來,讓我們用trae來編寫實現(xiàn)這個功能吧。

1. 組合式 API 初始化

import { onMounted, onBeforeUnmount } from "vue";
import * as THREE from "three";
import { Water } from "three/examples/jsm/objects/Water.js";
import { Sky } from "three/examples/jsm/objects/Sky.js";

Vue 組合式 API:使用 onMounted 和 onBeforeUnmount 來處理組件的生命周期。在組件掛載時初始化場景,卸載時清理資源。

Three.js 導入:導入 THREE 來處理 3D 渲染,Water 和 Sky 分別處理水面和天空的效果。

2. 初始化 Three.js 場景

let scene: THREE.Scene;
let camera: THREE.PerspectiveCamera;
let renderer: THREE.WebGLRenderer;
let water: any;
let sun: THREE.Vector3;
let sky: any;
let animationFrameId: number;

變量聲明:在 useOcean 函數(shù)中聲明了多個變量,用于保存 Three.js 的場景、相機、渲染器、以及水面和天空的實例。animationFrameId 用于控制動畫幀的請求。

const initThree = () => {
  const container = document.getElementById(canvasId);
  if (!container) {
    console.warn(`Canvas element with id '${canvasId}' not found`);
    return;
  }

  scene = new THREE.Scene();
  camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 20000);
  camera.position.set(30, 30, 100);
  camera.lookAt(0, 0, 0);
  
  sun = new THREE.Vector3();
  
  renderer = new THREE.WebGLRenderer({ canvas: container, antialias: true, alpha: true });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  renderer.toneMappingExposure = 0.5;
}

場景與相機初始化:創(chuàng)建了一個 Three.js 場景,并使用 PerspectiveCamera 創(chuàng)建相機,設置了相機的位置和朝向。

渲染器初始化:創(chuàng)建了一個 WebGLRenderer,并設置了反走樣(antialias)和透明背景(alpha)。同時設置了渲染器的大小和色調映射。

3. 創(chuàng)建水面效果

const waterGeometry = new THREE.PlaneGeometry(10000, 10000);
water = new Water(waterGeometry, {
  textureWidth: 512,
  textureHeight: 512,
  waterNormals: new THREE.TextureLoader().load(
    "https://threejs.org/examples/textures/waternormals.jpg",
    function (texture) {
      texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
    }
  ),
  sunDirection: new THREE.Vector3(),
  sunColor: 0xffffff,
  waterColor: 0x001e0f,
  distortionScale: 3.7,
  fog: scene.fog !== undefined,
});
water.rotation.x = -Math.PI / 2;
scene.add(water);

 

水面幾何體:使用 THREE.PlaneGeometry 創(chuàng)建了一個大的平面,作為海面基礎。

水面著色器:使用 Water 對象并傳入配置項,設置水面波動、光照、顏色等屬性。

水面紋理:加載了一個水面法線貼圖,并設置為重復模式。

4. 創(chuàng)建天空效果

sky = new Sky();
sky.scale.setScalar(10000);
scene.add(sky);

const skyUniforms = sky.material.uniforms;

skyUniforms["turbidity"].value = 10;
skyUniforms["rayleigh"].value = 2;
skyUniforms["mieCoefficient"].value = 0.005;
skyUniforms["mieDirectionalG"].value = 0.8;

const parameters = {
  elevation: 2,
  azimuth: 180,
};

天空對象:使用 Sky 對象創(chuàng)建了一個天空,并通過設置 scale 來放大天空的大小。

天空著色器的配置:調整了 turbidity(渾濁度)、rayleigh(瑞利散射)、mieCoefficient(米散射系數(shù))等參數(shù)來改變天空的效果。

5. 更新太陽位置與場景環(huán)境

const pmremGenerator = new THREE.PMREMGenerator(renderer);
let renderTarget: THREE.WebGLRenderTarget;

function updateSun() {
  const phi = THREE.MathUtils.degToRad(90 - parameters.elevation);
  const theta = THREE.MathUtils.degToRad(parameters.azimuth);
  sun.setFromSphericalCoords(1, phi, theta);
  
  sky.material.uniforms["sunPosition"].value.copy(sun);
  water.material.uniforms["sunDirection"].value.copy(sun).normalize();
  
  if (renderTarget !== undefined) renderTarget.dispose();
  renderTarget = pmremGenerator.fromScene(sky as any);
  
  scene.environment = renderTarget.texture;
}

updateSun();

太陽位置更新:通過 elevation 和 azimuth 參數(shù)計算太陽的位置,并將其應用于天空和水面材質的著色器中,使太陽的位置影響場景中的光照和水面反射。

6. 動畫與渲染循環(huán)

 

水面動畫:通過每幀更新水面著色器的 time 值,觸發(fā)水面動畫效果。

渲染循環(huán):使用 requestAnimationFrame 實現(xiàn)每一幀的渲染。

7. 處理窗口大小變化

const handleResize = () => {
  if (camera && renderer) {
    try {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    } catch (error) {
      console.error("Error during resize:", error);
    }
  }
};

響應窗口變化:當窗口大小變化時,更新相機的 aspect 比例并重新調整渲染器的大小,確保渲染效果不變形。

8. 資源清理

const cleanup = () => {
  if (animationFrameId) {
    cancelAnimationFrame(animationFrameId);
    animationFrameId = 0;
  }

  if (renderer) {
    renderer.dispose();
  }

  if (scene) {
    while (scene.children.length > 0) {
      scene.remove(scene.children[0]);
    }
  }
};

 清理動畫和資源:當組件卸載時,清除動畫幀和渲染器,移除場景中的所有對象,防止內存泄漏。

9. 生命周期鉤子

onMounted(() => {
  initThree();
  window.addEventListener("resize", handleResize);
});

onBeforeUnmount(() => {
  window.removeEventListener("resize", handleResize);
  cleanup();
});

生命周期鉤子:在組件掛載時初始化 Three.js 場景,并在卸載時清理資源。

完整源碼

完整源碼如下:

import { onMounted, onBeforeUnmount } from "vue";
import * as THREE from "three";

// 導入海洋著色器
import { Water } from "three/examples/jsm/objects/Water.js";
import { Sky } from "three/examples/jsm/objects/Sky.js";

export function useOcean(canvasId: string) {
  // Three.js 相關變量
  let scene: THREE.Scene;
  let camera: THREE.PerspectiveCamera;
  let renderer: THREE.WebGLRenderer;
  let water: any;
  let sun: THREE.Vector3;
  let sky: any;
  let animationFrameId: number;

  // 初始化Three.js場景
  const initThree = () => {
    const container = document.getElementById(canvasId);
    if (!container) {
      console.warn(`Canvas element with id '${canvasId}' not found`);
      return;
    }

    // 創(chuàng)建場景
    scene = new THREE.Scene();

    // 創(chuàng)建相機
    camera = new THREE.PerspectiveCamera(
      60,
      window.innerWidth / window.innerHeight,
      1,
      20000
    );
    camera.position.set(30, 30, 100);
    camera.lookAt(0, 0, 0);

    // 創(chuàng)建太陽光源
    sun = new THREE.Vector3();

    // 創(chuàng)建渲染器
    renderer = new THREE.WebGLRenderer({
      canvas: container,
      antialias: true,
      alpha: true,
    });
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.toneMapping = THREE.ACESFilmicToneMapping;
    renderer.toneMappingExposure = 0.5;

    // 創(chuàng)建水面
    const waterGeometry = new THREE.PlaneGeometry(10000, 10000);
    water = new Water(waterGeometry, {
      textureWidth: 512,
      textureHeight: 512,
      waterNormals: new THREE.TextureLoader().load(
        "https://threejs.org/examples/textures/waternormals.jpg",
        function (texture) {
          texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
        }
      ),
      sunDirection: new THREE.Vector3(),
      sunColor: 0xffffff,
      waterColor: 0x001e0f,
      distortionScale: 3.7,
      fog: scene.fog !== undefined,
    });
    water.rotation.x = -Math.PI / 2;
    scene.add(water);

    // 創(chuàng)建天空
    sky = new Sky();
    sky.scale.setScalar(10000);
    scene.add(sky);

    const skyUniforms = sky.material.uniforms;

    skyUniforms["turbidity"].value = 10;
    skyUniforms["rayleigh"].value = 2;
    skyUniforms["mieCoefficient"].value = 0.005;
    skyUniforms["mieDirectionalG"].value = 0.8;

    const parameters = {
      elevation: 2,
      azimuth: 180,
    };

    const pmremGenerator = new THREE.PMREMGenerator(renderer);
    let renderTarget: THREE.WebGLRenderTarget;

    function updateSun() {
      const phi = THREE.MathUtils.degToRad(90 - parameters.elevation);
      const theta = THREE.MathUtils.degToRad(parameters.azimuth);

      sun.setFromSphericalCoords(1, phi, theta);

      sky.material.uniforms["sunPosition"].value.copy(sun);
      water.material.uniforms["sunDirection"].value.copy(sun).normalize();

      if (renderTarget !== undefined) renderTarget.dispose();

      renderTarget = pmremGenerator.fromScene(sky as any);

      scene.environment = renderTarget.texture;
    }

    updateSun();

    // 添加環(huán)境光
    const ambient = new THREE.AmbientLight(0x555555);
    scene.add(ambient);

    animate();
  };

  // 動畫循環(huán)
  const animate = () => {
    if (!scene || !camera || !renderer || !water) {
      return;
    }

    // 更新水面動畫
    water.material.uniforms["time"].value += 1.0 / 60.0;

    renderer.render(scene, camera);
    animationFrameId = requestAnimationFrame(animate);
  };

  // 處理窗口大小變化
  const handleResize = () => {
    if (camera && renderer) {
      try {
        camera.aspect = window.innerWidth / window.innerHeight;
        camera.updateProjectionMatrix();
        renderer.setSize(window.innerWidth, window.innerHeight);
      } catch (error) {
        console.error("Error during resize:", error);
      }
    }
  };

  // 清理資源
  const cleanup = () => {
    if (animationFrameId) {
      cancelAnimationFrame(animationFrameId);
      animationFrameId = 0;
    }

    if (renderer) {
      renderer.dispose();
    }

    // 清理場景中的對象
    if (scene) {
      while (scene.children.length > 0) {
        scene.remove(scene.children[0]);
      }
    }
  };

  // 生命周期鉤子
  onMounted(() => {
    initThree();
    window.addEventListener("resize", handleResize);
  });

  onBeforeUnmount(() => {
    window.removeEventListener("resize", handleResize);
    cleanup();
  });

  return {
    // 如果需要暴露更多方法或屬性,可以在這里添加
  };
}

使用示例:

<canvas id="bg-canvas"></canvas>
useOcean('bg-canvas');

總結

以上我們就完成了一個動態(tài)的海洋和天空效果,它讓我們的登陸頁顯得更加高大上檔次,并且也展示了如何在 Vue 中集成復雜的 3D 渲染,同時確保了在窗口大小變化時的適配,以及在組件卸載時正確清理資源,通過合理的生命周期管理和資源清理,確保了程序的穩(wěn)定性和性能。

到此這篇關于Vue中使用Three.js實現(xiàn)動態(tài)海洋與天空背景的文章就介紹到這了,更多相關Vue用Three.js實現(xiàn)動態(tài)背景內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論