Vue中使用Three.js實現(xiàn)動態(tài)海洋與天空背景
背景
常規(guī)的后臺管理系統(tǒng)登陸頁面可能就只是一個簡單的背景頁面,這不太好看,接下來讓我們來使用three.js來實現(xiàn)一個動態(tài)的海洋和天空效果當作背景,這樣的效果總會讓人眼前一亮,如下圖所示。
代碼實現(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ù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Vue中Router路由兩種模式hash與history詳解
這篇文章主要介紹了Vue中Router路由的兩種模式,分別對hash模式與history模式作了簡要分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助2021-09-09vue中element-ui不能修改el-input框,或是不能修改某些值問題
這篇文章主要介紹了vue中element-ui不能修改el-input框,或是不能修改某些值問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10vue-i18n的9以上版本中@被用作特殊字符處理,直接用會報錯問題
這篇文章主要介紹了vue-i18n的9以上版本中@被用作特殊字符處理,直接用會報錯問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-08-08