Three.js 是 “WebGL 的封裝庫”,幫你屏蔽了底層的著色器 / 緩沖區細節,專注于 “3D 場景搭建”,開發效率高,是通用 3D 開發的首選。
他的核心是 “場景 - 相機 - 渲染器” 的聯動邏輯,先掌握基礎組件,再學進階功能(如自定義著色器)
實踐只提供參考代碼,自己去找模型嘗試。
1.Three.js 核心組件
1.1?三要素:場景(Scene
)、相機(PerspectiveCamera/OrthographicCamera
)、渲染器(WebGLRenderer
)
場景(Scene
)
場景就像是一個虛擬的 "3D 舞臺",所有的 3D 物體(比如模型、燈光、粒子等)都需要放在這個舞臺上才能被看到。
場景本身不直接顯示任何東西,它只是一個容器,負責管理所有需要渲染的對象。在 Three.js 中,你可以通過
add()
方法往場景里添加各種元素。
// 創建一個場景
const scene = new THREE.Scene();// 創建一個立方體并添加到場景中
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
const cube = new THREE.Mesh(geometry, material);
scene.add(cube); // 將立方體添加到場景
相機(Camera)
相機決定了我們從哪個角度和視角來看場景中的物體。
透視相機(PerspectiveCamera)
這是最常用的相機,模擬人眼觀察世界的方式,遠處的物體看起來小,近處的物體看起來大,有 "近大遠小" 的透視效果。
參數說明(通俗易懂版):
視野角度:相機能看到的范圍有多大(比如 90 度)
寬高比:畫面的寬度和高度比例(通常是瀏覽器窗口的寬高比)
近平面:離相機多近的物體才會被看到
- 遠平面:離相機多遠的物體還能被看到
// 創建透視相機 const camera = new THREE.PerspectiveCamera(75, // 視野角度window.innerWidth / window.innerHeight, // 寬高比0.1, // 近平面1000 // 遠平面 ); camera.position.z = 5; // 把相機往后移動一點,這樣能看到場景中的物體
正交相機(OrthographicCamera)
這種相機沒有透視效果,遠處和近處的物體看起來一樣大,適合 2D 渲染或建筑圖紙等需要精確尺寸的場景。
// 創建正交相機 const camera = new THREE.OrthographicCamera(window.innerWidth / -2, // 左邊界window.innerWidth / 2, // 右邊界window.innerHeight / 2, // 上邊界window.innerHeight / -2,// 下邊界0.1, // 近平面1000 // 遠平面 );
渲染器(WebGLRenderer)
渲染器的作用是把場景和相機 "結合" 起來,計算出最終應該顯示在屏幕上的圖像,并把它繪制到網頁的 canvas 元素上。
// 創建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight); // 設置渲染器的尺寸為窗口大小
document.body.appendChild(renderer.domElement); // 將渲染器生成的canvas元素添加到網頁中// 執行渲染(相當于"拍照")
renderer.render(scene, camera);
總結
場景是舞臺,存放所有物體
相機是觀眾的眼睛,決定看什么角度
渲染器是畫筆,把相機看到的場景畫在屏幕上
當你想要制作動畫時,只需要在一個循環中不斷改變場景中物體的位置或相機角度,然后重新調用renderer.render()
方法即可,這就像拍視頻一樣,連續播放多張照片就形成了動畫效果。
1.2?基礎元素:幾何體(BoxGeometry/SphereGeometry
)、材質(MeshBasicMaterial/MeshStandardMaterial
)、網格(Mesh
)
幾何體(BoxGeometry/SphereGeometry
)
幾何體就像是物體的 "骨架" 或 "模具",定義了物體的形狀和大小,但它本身是沒有顏色和質感的。
BoxGeometry(立方體幾何體)
可以想象成一個紙箱的框架,有長、寬、高三個維度。創建時需要指定這三個參數,比如
new THREE.BoxGeometry(1, 1, 1)
就創建了一個邊長為 1 的正方體框架。
SphereGeometry(球體幾何體):
類似一個籃球的內部支架,由無數個小三角形拼接而成。創建時需要指定半徑和分段數,分段數越高,球體越光滑,比如new THREE.SphereGeometry(1, 32, 32)
創建了一個半徑為 1 的球體。
材質(MeshBasicMaterial/MeshStandardMaterial
)
材質就像是物體的 "皮膚",決定了物體的顏色、質感、是否反光等外觀屬性,但它本身沒有固定形狀。
MeshBasicMaterial(基礎網格材質):
最基礎的材質,就像用彩色紙糊出來的效果,不考慮光照影響,永遠是平光的。可以設置顏色,比如new THREE.MeshBasicMaterial({color: 0xff0000})
就是紅色的材質。
MeshStandardMaterial(標準網格材質):
更接近真實世界的材質,會受光照影響,能表現出金屬、塑料等不同質感。比如可以設置金屬度(metalness)和粗糙度(roughness),new THREE.MeshStandardMaterial({color: 0x00ff00, metalness: 0.5, roughness: 0.5})
就創建了一個綠色的、半金屬質感的材質。
網格(Mesh
)
網格是幾何體和材質的 "結合體",就像把皮膚貼在骨架上,形成一個可以顯示在屏幕上的具體物體。
// 創建一個立方體骨架
const geometry = new THREE.BoxGeometry(1, 1, 1);
// 創建一個紅色皮膚
const material = new THREE.MeshBasicMaterial({color: 0xff0000});
// 把皮膚貼在骨架上,得到一個紅色立方體
const cube = new THREE.Mesh(geometry, material);
scene.add(cube); // 把物體放入場景
1.3 輔助工具:坐標軸(AxesHelper
)、性能監控(Stats
)
在 Three.js 中,輔助工具就像我們做手工時用的尺子、放大鏡一樣,能幫我們更方便地調試和觀察 3D 場景。
坐標軸(AxesHelper)
在 3D 場景中顯示 X、Y、Z 三條坐標軸,幫你判斷物體的位置和方向.
// 創建一個長度為5的坐標軸(數值越大,線越長)
const axesHelper = new THREE.AxesHelper(5);
// 把坐標軸放進場景,就能看到了
scene.add(axesHelper);
?性能監控(Stats)
實時顯示 3D 場景的運行性能,比如每秒渲染多少幀(FPS).
FPS(Frames Per Second):每秒渲染的畫面數量。數值越高,畫面越流暢(通常 60 是比較理想的狀態)。
如果 FPS 低于 30,畫面會感覺卡頓,就像看幻燈片
// 創建性能監控器
const stats = new Stats();
// 把監控器顯示在網頁右上角(默認是左上角)
stats.dom.style.position = 'absolute';
stats.dom.style.right = '0px';
stats.dom.style.top = '0px';
// 把監控器添加到網頁中
document.body.appendChild(stats.dom);// 在動畫循環中更新數據
function animate() {requestAnimationFrame(animate);stats.update(); // 每次刷新畫面時,更新性能數據renderer.render(scene, camera);
}
animate();
1.4?實踐:搭建一個 “靜態 3D 場景”:包含立方體、球體、地面,添加坐標軸和光照。
代碼:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>靜態3D場景</title><!-- 引入Three.js庫 --><script src="https://cdn.tailwindcss.com"></script><linkhref="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css"rel="stylesheet"/><script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script><style>body {margin: 0;}canvas {display: block;}.info {position: absolute;top: 10px;left: 10px;background: rgba(0, 0, 0, 0.7);color: white;padding: 10px;border-radius: 5px;font-family: Arial, sans-serif;font-size: 12px;}</style></head><body><div class="info"><p>靜態3D場景演示</p><p>包含:立方體、球體、地面、坐標軸和光照</p><p>紅色:X軸 | 綠色:Y軸 | 藍色:Z軸</p></div><script>// 1. 創建場景// 場景就像一個容器,用來放置所有的3D物體const scene = new THREE.Scene();// 設置場景背景顏色scene.background = new THREE.Color(0xf0f0f0); // 淺灰色背景// 2. 創建相機// 透視相機,模擬人眼觀察世界的方式// 參數:視野角度、寬高比、近平面、遠平面const camera = new THREE.PerspectiveCamera(75,window.innerWidth / window.innerHeight,0.1,1000);// 設置相機位置camera.position.z = 10; // 往后移camera.position.y = 5; // 往上移camera.position.x = 5; // 往右移// 讓相機看向場景中心camera.lookAt(scene.position);// 3. 創建渲染器// 渲染器負責將3D場景渲染到網頁上const renderer = new THREE.WebGLRenderer();// 設置渲染器尺寸為窗口大小renderer.setSize(window.innerWidth, window.innerHeight);// 將渲染器的DOM元素添加到頁面document.body.appendChild(renderer.domElement);// 4. 添加坐標軸輔助工具// 參數是坐標軸的長度const axesHelper = new THREE.AxesHelper(5);scene.add(axesHelper);// 5. 添加光照// 環境光:均勻照亮所有物體,沒有方向感const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // 顏色,強度scene.add(ambientLight);// 平行光:類似太陽光,有方向,會產生陰影const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);directionalLight.position.set(100, 15, 10); // 設置光源位置scene.add(directionalLight);// 6. 創建地面// 地面使用平面幾何體const planeGeometry = new THREE.PlaneGeometry(60, 60); // 寬20,長20// 使用標準材質,會受光照影響const planeMaterial = new THREE.MeshStandardMaterial({color: "gray", // 灰色side: THREE.DoubleSide, // 讓平面兩面都可見});// 創建網格(幾何體+材質)const plane = new THREE.Mesh(planeGeometry, planeMaterial);// 旋轉地面,讓它水平放置plane.rotation.x = -Math.PI / 2; // 繞X軸旋轉-90度scene.add(plane);// 7. 創建立方體// 立方體幾何體:參數分別是寬、高、深const cubeGeometry = new THREE.BoxGeometry(2, 2, 2);// 使用標準材質const cubeMaterial = new THREE.MeshStandardMaterial({color: 0xff0000, // 紅色});// 創建立方體網格const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);// 設置立方體位置cube.position.x = -3; // X軸方向cube.position.y = 1; // Y軸方向(讓它立在地面上)cube.position.z = 0; // Z軸方向scene.add(cube);// 8. 創建球體// 球體幾何體:參數分別是半徑、水平分段數、垂直分段數// 分段數越高,球體越光滑const sphereGeometry = new THREE.SphereGeometry(1.5, 32, 32);// 使用標準材質const sphereMaterial = new THREE.MeshStandardMaterial({color: 0x00ff00, // 綠色});// 創建球體網格const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);// 設置球體位置sphere.position.x = 3; // X軸方向sphere.position.y = 1; // Y軸方向(讓它立在地面上)sphere.position.z = 0; // Z軸方向scene.add(sphere);// 9. 窗口大小變化時調整渲染window.addEventListener("resize", () => {// 更新相機的寬高比camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix();// 更新渲染器尺寸renderer.setSize(window.innerWidth, window.innerHeight);});// 10. 渲染場景function render() {// 使用requestAnimationFrame創建動畫循環requestAnimationFrame(render);// 渲染場景和相機renderer.render(scene, camera);}// 開始渲染render();</script></body>
</html>
效果:
2.Three.js 核心能力
2.1模型加載:加載 GLB/GLTF 模型(GLTFLoader
)
什么是 GLB/GLTF 模型?
簡單說,它們是 3D 模型的 "文件格式",就像圖片有 JPG、PNG 格式,視頻有 MP4 格式一樣。
GLTF:被稱為 "3D 界的 JPG",是一種通用的 3D 模型格式,支持模型、材質、動畫等信息。
GLB:是 GLTF 的 "壓縮版",把所有模型數據打包成一個文件,加載更快,就像把一本書的所有頁裝訂成一本,而不是散頁。
這些模型通常不是用 Three.js 手動創建的(太麻煩了),而是用專業 3D 軟件(比如 Blender、Maya)做好后導出的,然后用 Three.js 加載到我們的場景中。
什么是 GLTFLoader?
GLTFLoader 就是 Three.js 提供的 "模型搬運工",專門負責把 GLB/GLTF 格式的模型文件 "搬到" 我們的 3D 場景里。
加載模型的簡單步驟
引入 "搬運工"(GLTFLoader)
Three.js 核心庫里沒有自帶這個工具,需要額外引入
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/loaders/GLTFLoader.js"></script>
創建 "搬運工"
在 JavaScript 中實例化這個工具
const loader = new THREE.GLTFLoader();
指定要搬運的模型文件
告訴模型文件在哪里(文件路徑)
// 加載GLB模型(如果是GLTF文件,路徑換成xxx.gltf即可) loader.load('models/robot.glb', // 模型文件的路徑(比如你下載的機器人模型)// 加載成功時的操作:把模型放進場景function(gltf) {// gltf.scene就是加載好的模型整體scene.add(gltf.scene); // 把模型添加到場景中,這樣就能看到了console.log('模型加載成功!');},// 加載過程中的進度提示(可選)function(xhr) {console.log(`加載中... ${(xhr.loaded / xhr.total * 100)}%`);},// 加載失敗時的提示(可選)function(error) {console.log('加載失敗:', error);} );
加載后能做什么?
模型加載到場景后,你可以像操作自己創建的立方體、球體一樣操作它:
移動位置:
gltf.scene.position.set(1, 2, 3)
旋轉:
gltf.scene.rotation.y = Math.PI / 4
(轉 45 度)縮放:
gltf.scene.scale.set(0.5, 0.5, 0.5)
(縮小一半)
為什么要用 GLB/GLTF?
通用性強:幾乎所有 3D 軟件都支持導出這種格式,方便你從網上下載現成模型(比如很多免費模型網站提供 GLB/GLTF 格式)。
加載快:相比其他 3D 格式(比如 OBJ),它體積更小,加載速度更快,適合網頁 3D 應用。
2.2?動畫控制:AnimationMixer
控制模型動畫、Tween.js
實現屬性過渡
在 Three.js 中,動畫控制能讓你的 3D 場景 "動起來",就像給靜態的模型賦予生命。我們可以用兩個工具實現不同類型的動畫:AnimationMixer(控制模型自帶的動畫)和 Tween.js(實現物體屬性的平滑變化)。
AnimationMixer(模型動畫控制器)
控制從 3D 建模軟件(如 Blender)導出的模型自帶的動畫(比如人物走路、機器人旋轉等)。
把 3D 模型想象成一個 "木偶",建模師在制作時已經給它設計好了一套套動作(比如走路、揮手),這些動作被保存為 "動畫片段"。AnimationMixer 就像一個 "木偶師",負責播放、暫停、切換這些現成的動作。
動畫片段(AnimationClip):模型中保存的單個動作(比如 "走路" 是一個片段,"跳躍" 是另一個片段)。
混合器(Mixer):管理這些動畫片段的播放器,能控制播放速度、切換動作、甚至混合多個動作(比如邊走路邊揮手)。
// 假設已經加載了一個帶動畫的模型model // 創建混合器,關聯到這個模型 const mixer = new THREE.AnimationMixer(model);// 從模型中獲取第一個動畫片段(比如"走路") const animationClip = model.animations[0];// 讓混合器播放這個動畫片段 const action = mixer.clipAction(animationClip); action.play(); // 開始播放動畫// 在動畫循環中更新混合器(讓動畫動起來) function animate() {requestAnimationFrame(animate);const delta = clock.getDelta(); // 獲取兩次刷新的時間間隔mixer.update(delta); // 用時間間隔更新動畫進度renderer.render(scene, camera); }
如果你的模型有預設動畫(比如游戲角色、機械臂),用 AnimationMixer 能很方便地控制這些復雜動作,不用自己寫代碼一點點定義。
Tween.js(屬性過渡工具)
讓物體的屬性(位置、大小、顏色等)在一段時間內平滑變化(比如物體從 A 點慢慢移到 B 點,顏色從紅慢慢變藍)。
就像給物體設置 "自動軌跡",比如讓一個方塊 "在 2 秒內從左邊滑到右邊"。Tween.js 會自動計算中間的每一步位置,讓移動看起來不生硬,而是平滑過渡。
補間(Tween):定義一個屬性從 "起始值" 到 "目標值" 的變化過程,包括持續時間、變化速度(勻速、加速等)。
// 假設已經創建了一個立方體cube// 創建補間:讓立方體在2秒內從(0,0,0)移動到(5,0,0) const tween = new TWEEN.Tween(cube.position) // 要變化的屬性(位置).to({ x: 5 }, 2000) // 目標值(x=5)和持續時間(2000毫秒=2秒).easing(TWEEN.Easing.Quadratic.InOut) // 變化方式(先慢后快再慢).start(); // 開始執行補間// 在動畫循環中更新補間(讓過渡生效) function animate() {requestAnimationFrame(animate);TWEEN.update(); // 刷新補間狀態renderer.render(scene, camera); }
手動寫代碼實現平滑過渡很麻煩(需要計算每幀的位置),而 Tween.js 能自動處理這些細節,讓動畫更自然。
2.3?光照與陰影:添加平行光 / 點光,開啟陰影(castShadow/receiveShadow
)
在 3D 世界里,光照和陰影是讓場景變得真實的關鍵 —— 就像現實中陽光會照亮物體、地面會出現影子一樣。Three.js 里的光照和陰影系統,其實就是在模擬這個自然現象。
光照:讓物體 "看得見"
沒有光的 3D 場景是全黑的,就像在漆黑的房間里什么都看不見。
平行光(DirectionalLight)
就像太陽光,光線從很遠的地方照過來,所有光線都是平行的。
比如中午的太陽,不管照到近處的桌子還是遠處的房子,光線方向都是一樣的。特點:
有明確的照射方向(比如從左上角照向右下角)
光照強度均勻,不會因為物體離得遠就變暗
用法:
// 創建平行光:參數1是光的顏色(0xffffff是白色),參數2是光照強度(0-1之間,1是最強) const directionalLight = new THREE.DirectionalLight(0xffffff, 1); // 設置光源的位置(相當于太陽在天空中的位置) directionalLight.position.set(10, 20, 15); // x=10, y=20, z=15 // 把光添加到場景中 scene.add(directionalLight);
?點光(PointLight)
就像燈泡,光線從一個點向四面八方發散。
比如房間里的吊燈,會照亮周圍所有方向,離燈泡越近的物體越亮,越遠越暗。特點:
有一個中心點,光線向四周擴散
光照強度會隨距離衰減(遠的地方暗)
用法:
// 創建點光:參數1是顏色,參數2是強度,參數3是光照最大距離(超過這個距離就照不到了) const pointLight = new THREE.PointLight(0xffffff, 1, 100); // 設置點光的位置(相當于燈泡掛在哪個位置) pointLight.position.set(5, 5, 5); // 場景中間偏上的位置 // 把點光添加到場景中 scene.add(pointLight);
陰影:讓物體 "有層次"
有了光,物體就會產生影子 —— 這是讓 3D 場景有立體感的關鍵。但 Three.js 里的陰影不是自動生成的,需要手動開啟,就像 "告訴程序:請計算影子"。
核心概念:兩個屬性控制陰影
castShadow:"是否產生影子"
比如一個球,開啟這個屬性后,它被光照到時就會投下影子。receiveShadow:"是否接收影子"
比如地面,開啟這個屬性后,其他物體的影子才能顯示在它上面。
開啟陰影的完整步驟(以平行光為例):
// 1. 告訴渲染器:需要計算陰影
renderer.shadowMap.enabled = true;// 2. 告訴光源:需要產生陰影(不是所有光源都能產生陰影,平行光和點光可以)
directionalLight.castShadow = true;// 3. 告訴物體:需要產生影子(比如一個立方體)
const cube = new THREE.Mesh(geometry, material);
cube.castShadow = true; // 立方體產生影子// 4. 告訴地面:需要接收影子(比如一個平面當地面)
const groundGeometry = new THREE.PlaneGeometry(50, 50); // 大平面當地面
const groundMaterial = new THREE.MeshStandardMaterial({color: 0xcccccc});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.receiveShadow = true; // 地面接收影子
scene.add(ground);
2.4?交互:射線檢測(Raycaster
)實現 “點擊選中模型”
在 Three.js 中,要實現 "點擊屏幕選中 3D 模型" 的功能,核心就是用射線檢測(Raycaster)。這個功能就像我們用激光筆在現實中 "指" 東西一樣 —— 你在屏幕上點一下,Three.js 會發射一道 "虛擬激光",看看這道激光打到了哪個 3D 模型上。
為什么需要射線檢測?
我們的屏幕是 2D 的(平面),而 Three.js 場景是 3D 的(立體)。當你在屏幕上點擊時,計算機不知道你想選的是 3D 空間里的哪個物體。射線檢測就是解決這個 "2D 點擊對應 3D 物體" 的問題。
射線檢測(Raycaster)的工作原理
1. 創建射線檢測器(Raycaster)
就像準備好你的 "激光筆":
const raycaster = new THREE.Raycaster(); // 射線檢測器(激光筆)
2. 監聽鼠標點擊事件
告訴程序:"當用戶點擊屏幕時,觸發檢測":
// 給網頁添加鼠標點擊事件 window.addEventListener('click', onMouseClick);
3. 計算射線的方向(關鍵步驟)
當用戶點擊時,需要把 2D 的屏幕坐標轉換成 3D 射線的方向。
簡單說就是:"用戶點了屏幕上的 (x,y),這對應 3D 空間里哪條直線?"function onMouseClick(event) {// 1. 獲取鼠標在屏幕上的位置(歸一化到-1到1之間)// 就像把屏幕坐標轉換成"相對位置",方便Three.js計算const mouse = new THREE.Vector2();mouse.x = (event.clientX / window.innerWidth) * 2 - 1; // 橫向坐標轉換mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; // 縱向坐標轉換(注意Y軸方向相反)// 2. 讓射線從相機位置出發,指向鼠標點擊的方向raycaster.setFromCamera(mouse, camera); }
4. 檢測射線撞到了哪些模型
用射線檢測場景中所有可能被選中的模型,然后處理選中的結果:
function onMouseClick(event) {// 前面的步驟:獲取鼠標位置、設置射線方向...(同上)// 3. 準備一個數組,包含所有可能被點擊的模型(比如場景中所有的網格)const allObjects = [cube, sphere, cylinder]; // 假設這些是你場景中的模型// 4. 發射射線,檢測哪些模型被射線擊中const intersects = raycaster.intersectObjects(allObjects);// 5. 如果有擊中的模型,做一些操作(比如變色、放大等)if (intersects.length > 0) {// intersects[0]是距離相機最近的被擊中的模型const selectedObject = intersects[0].object;// 比如:把選中的模型變成紅色selectedObject.material.color.set(0xff0000);console.log('你選中了:', selectedObject);} }
2.5 實踐
2.5.1?加載一個 “帶動畫的人物模型”,實現點擊模型播放動畫
技術實現:
使用 GLTFLoader 加載包含動畫數據的 glTF 格式模型
通過 AnimationMixer 控制動畫播放
使用 Raycaster 實現模型點擊檢測
結合 OrbitControls 實現視角控制
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Three.js 帶動畫的人物模型</title><!-- 引入Three.js --><script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script><!-- 引入軌道控制器 --><script src="https://cdn.jsdelivr.net/npm/three@0.160.0/examples/js/controls/OrbitControls.js"></script><!-- 引入GLTF加載器 --><script src="https://cdn.jsdelivr.net/npm/three@0.160.0/examples/js/loaders/GLTFLoader.js"></script><!-- 引入Tailwind CSS --><script src="https://cdn.tailwindcss.com"></script><!-- 引入Font Awesome --><link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet"><style type="text/tailwindcss">@layer utilities {.content-auto {content-visibility: auto;}.backdrop-blur-xs {backdrop-filter: blur(4px);}}</style>
</head>
<body class="bg-gray-100 min-h-screen flex flex-col"><!-- 頁面標題 --><header class="bg-indigo-600 text-white py-4 shadow-md"><div class="container mx-auto px-4 flex justify-between items-center"><h1 class="text-2xl font-bold"><i class="fa fa-film mr-2"></i>帶動畫的3D人物模型</h1><div class="text-sm bg-white/20 px-3 py-1 rounded-full">點擊模型播放/暫停動畫</div></div></header><!-- 主內容區 --><main class="flex-1 flex flex-col"><!-- 3D渲染區域 --><div class="relative flex-1 w-full" id="canvas-container"><!-- 加載指示器 --><div id="loading" class="absolute inset-0 flex items-center justify-center bg-white/80 z-10"><div class="flex flex-col items-center"><div class="w-16 h-16 border-4 border-indigo-500 border-t-transparent rounded-full animate-spin"></div><p class="mt-4 text-indigo-600 font-medium">加載模型中...</p></div></div><!-- 3D場景畫布 --><canvas id="character-canvas" class="w-full h-full"></canvas><!-- 信息提示 --><div id="info" class="absolute bottom-4 left-4 bg-black/60 backdrop-blur-xs text-white px-4 py-2 rounded-lg text-sm opacity-0 transition-opacity duration-500"><p>模型名稱: <span id="model-name">-</span></p><p>當前動畫: <span id="current-animation">-</span></p></div></div><!-- 動畫控制區 --><div class="bg-white border-t border-gray-200 py-4 px-6"><div class="container mx-auto"><h2 class="text-lg font-semibold text-gray-800 mb-3">動畫選擇</h2><div class="flex flex-wrap gap-2" id="animation-controls"><!-- 動畫按鈕將通過JS動態生成 --></div></div></div></main><!-- 頁腳 --><footer class="bg-gray-800 text-gray-300 py-4"><div class="container mx-auto px-4 text-center text-sm"><p>使用 Three.js 實現的帶動畫人物模型演示</p></div></footer><script>// 全局變量let scene, camera, renderer, controls;let characterModel = null;let mixer = null;let currentAction = null;let animations = [];let clock = new THREE.Clock();let raycaster = new THREE.Raycaster();let mouse = new THREE.Vector2();let isModelPlaying = false;// DOM元素const canvasContainer = document.getElementById('canvas-container');const canvas = document.getElementById('character-canvas');const loadingIndicator = document.getElementById('loading');const infoPanel = document.getElementById('info');const modelNameEl = document.getElementById('model-name');const currentAnimationEl = document.getElementById('current-animation');const animationControls = document.getElementById('animation-controls');// 初始化Three.js場景function initScene() {// 創建場景scene = new THREE.Scene();scene.background = new THREE.Color(0xf0f0f0);// 添加環境光const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);scene.add(ambientLight);// 添加方向光const directionalLight = new THREE.DirectionalLight(0xffffff, 1);directionalLight.position.set(5, 10, 7.5);scene.add(directionalLight);// 添加輔助網格const gridHelper = new THREE.GridHelper(10, 10, 0xe0e0e0, 0xf0f0f0);scene.add(gridHelper);// 創建相機camera = new THREE.PerspectiveCamera(75, canvasContainer.clientWidth / canvasContainer.clientHeight, 0.1, 1000);camera.position.z = 5;camera.position.y = 1;// 創建渲染器renderer = new THREE.WebGLRenderer({canvas: canvas,antialias: true});renderer.setSize(canvasContainer.clientWidth, canvasContainer.clientHeight);renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));// 創建軌道控制器controls = new THREE.OrbitControls(camera, renderer.domElement);controls.enableDamping = true;controls.dampingFactor = 0.05;controls.target.set(0, 1, 0); // 聚焦到人物腰部位置// 窗口大小變化事件window.addEventListener('resize', onWindowResize);// 鼠標點擊事件window.addEventListener('click', onMouseClick);}// 加載帶動畫的人物模型function loadAnimatedModel() {// 使用GLTF加載器const loader = new THREE.GLTFLoader();// 加載示例人物模型(Three.js官方示例模型)loader.load(// 模型URL(包含動畫的GLB模型)'https://threejs.org/examples/models/gltf/RobotExpressive/RobotExpressive.glb',// 加載成功回調(gltf) => {// 保存模型引用characterModel = gltf.scene;// 調整模型位置和縮放characterModel.position.y = 0;characterModel.scale.set(0.8, 0.8, 0.8);// 添加到場景scene.add(characterModel);// 初始化動畫混合器mixer = new THREE.AnimationMixer(characterModel);// 保存所有動畫animations = gltf.animations;// 顯示模型名稱modelNameEl.textContent = '機器人模型';// 創建動畫控制按鈕createAnimationButtons();// 默認播放第一個動畫if (animations.length > 0) {playAnimation(0);}// 隱藏加載指示器loadingIndicator.classList.add('opacity-0');setTimeout(() => {loadingIndicator.classList.add('hidden');infoPanel.classList.add('opacity-100');}, 500);},// 加載進度回調(xhr) => {console.log(`加載進度: ${(xhr.loaded / xhr.total * 100).toFixed(0)}%`);},// 加載錯誤回調(error) => {console.error('模型加載錯誤:', error);loadingIndicator.innerHTML = `<div class="text-center"><i class="fa fa-exclamation-triangle text-red-500 text-4xl mb-2"></i><p class="text-red-600">模型加載失敗</p></div>`;});}// 創建動畫控制按鈕function createAnimationButtons() {// 清空現有按鈕animationControls.innerHTML = '';// 為每個動畫創建按鈕animations.forEach((animation, index) => {// 簡化動畫名稱(去除前綴和編號)let animationName = animation.name;animationName = animationName.replace(/^Take\d+_/, '');animationName = animationName.charAt(0).toUpperCase() + animationName.slice(1);const button = document.createElement('button');button.className = `px-4 py-2 rounded-md text-sm font-medium transition-all ${index === 0 ? 'bg-indigo-600 text-white' : 'bg-gray-200 text-gray-800 hover:bg-gray-300'}`;button.textContent = animationName;button.dataset.index = index;button.addEventListener('click', () => {playAnimation(index);// 更新按鈕樣式document.querySelectorAll('#animation-controls button').forEach(btn => {btn.classList.remove('bg-indigo-600', 'text-white', 'bg-gray-200', 'text-gray-800');btn.classList.add('bg-gray-200', 'text-gray-800', 'hover:bg-gray-300');});button.classList.remove('bg-gray-200', 'text-gray-800', 'hover:bg-gray-300');button.classList.add('bg-indigo-600', 'text-white');});animationControls.appendChild(button);});}// 播放指定索引的動畫function playAnimation(index) {if (!mixer || !animations[index]) return;// 停止當前動畫if (currentAction) {currentAction.fadeOut(0.3);}// 播放新動畫const animation = animations[index];currentAction = mixer.clipAction(animation);currentAction.reset();currentAction.fadeIn(0.3);currentAction.play();// 更新信息面板let animationName = animation.name.replace(/^Take\d+_/, '');animationName = animationName.charAt(0).toUpperCase() + animationName.slice(1);currentAnimationEl.textContent = animationName;// 更新播放狀態isModelPlaying = true;}// 切換動畫播放/暫停function toggleAnimationPlay() {if (!currentAction) return;if (isModelPlaying) {currentAction.pause();} else {currentAction.play();}isModelPlaying = !isModelPlaying;}// 窗口大小變化處理function onWindowResize() {const width = canvasContainer.clientWidth;const height = canvasContainer.clientHeight;camera.aspect = width / height;camera.updateProjectionMatrix();renderer.setSize(width, height);}// 鼠標點擊事件處理function onMouseClick(event) {// 計算鼠標在標準化設備坐標中的位置 (-1 到 1)mouse.x = (event.clientX / window.innerWidth) * 2 - 1;mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;// 更新射線投射器raycaster.setFromCamera(mouse, camera);// 檢查射線是否與模型相交if (characterModel) {const intersects = raycaster.intersectObject(characterModel, true);// 如果點擊了模型,切換動畫播放狀態if (intersects.length > 0) {toggleAnimationPlay();// 添加點擊反饋效果const clickEffect = document.createElement('div');clickEffect.className = 'fixed w-6 h-6 rounded-full bg-indigo-500/30 transform -translate-x-1/2 -translate-y-1/2 pointer-events-none';clickEffect.style.left = `${event.clientX}px`;clickEffect.style.top = `${event.clientY}px`;clickEffect.style.animation = 'clickEffect 0.6s ease-out forwards';document.body.appendChild(clickEffect);// 添加動畫樣式const style = document.createElement('style');style.textContent = `@keyframes clickEffect {0% { transform: translate(-50%, -50%) scale(0); opacity: 1; }100% { transform: translate(-50%, -50%) scale(2); opacity: 0; }}`;document.head.appendChild(style);// 移除效果元素setTimeout(() => {clickEffect.remove();style.remove();}, 600);}}}// 動畫循環function animate() {requestAnimationFrame(animate);// 更新動畫混合器if (mixer && isModelPlaying) {mixer.update(clock.getDelta());}// 更新控制器controls.update();// 渲染場景renderer.render(scene, camera);}// 初始化應用function initApp() {initScene();loadAnimatedModel();animate();}// 頁面加載完成后初始化window.addEventListener('load', initApp);</script>
</body>
</html>
2.5.2?搭建一個 “3D 產品展廳”,支持視角旋轉和模型切換。
核心實現思路
基礎三要素:通過 Three.js 創建
場景(Scene)
、相機(Camera)
、渲染器(Renderer)
,構成 3D 展示的基礎框架。視角控制:使用
OrbitControls
實現鼠標拖拽旋轉、滾輪縮放、右鍵平移,同時支持 “自動旋轉” 開關。模型加載:通過
GLTFLoader
加載通用 3D 模型(GLB/GLTF 格式),并提供多產品切換功能。輕量 UI:僅保留必要的控制按鈕(旋轉開關、模型切換、視角重置),避免冗余交互。
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>極簡3D產品展廳</title><style>/* 基礎樣式:讓Canvas占滿屏幕,控制欄固定底部 */body { margin: 0; overflow: hidden; font-family: sans-serif; }#canvas { width: 100vw; height: 100vh; }.controls { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.7); color: white; padding: 10px 20px; border-radius: 8px;display: flex; gap: 15px; align-items: center;}button { padding: 6px 12px; border: none; border-radius: 4px; background: #165DFF; color: white; cursor: pointer;}button:hover { background: #0E42D2; }#model-select { padding: 6px; border-radius: 4px; }</style><!-- 引入Three.js核心庫和必要插件 --><script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script><script src="https://cdn.jsdelivr.net/npm/three@0.160.0/examples/js/controls/OrbitControls.js"></script><script src="https://cdn.jsdelivr.net/npm/three@0.160.0/examples/js/loaders/GLTFLoader.js"></script>
</head>
<body><!-- 3D渲染畫布 --><canvas id="canvas"></canvas><!-- 控制欄:旋轉開關、模型切換、視角重置 --><div class="controls"><button id="rotate-btn">關閉自動旋轉</button><select id="model-select"><option value="1">產品1:簡約臺燈</option><option value="2">產品2:無線耳機</option><option value="3">產品3:智能手表</option></select><button id="reset-btn">重置視角</button></div><script>// --------------------------// 1. 初始化Three.js核心組件// --------------------------let scene, camera, renderer, controls;let currentModel = null; // 存儲當前加載的3D模型// 場景:3D物體的“容器”scene = new THREE.Scene();scene.background = new THREE.Color(0xf0f0f0); // 淺灰色背景// 相機:相當于“人眼”,決定能看到什么camera = new THREE.PerspectiveCamera(75, // 視野角度(FOV)window.innerWidth / window.innerHeight, // 寬高比0.1, // 近裁剪面(太近的物體不顯示)1000 // 遠裁剪面(太遠的物體不顯示));camera.position.z = 5; // 相機初始位置(z軸遠離原點)// 渲染器:將場景和相機“畫”到Canvas上renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('canvas') });renderer.setSize(window.innerWidth, window.innerHeight); // 適配窗口大小// 視角控制器:實現鼠標交互(旋轉、縮放、平移)controls = new THREE.OrbitControls(camera, renderer.domElement);controls.enableDamping = true; // 平滑阻尼效果controls.autoRotate = true; // 默認開啟自動旋轉controls.autoRotateSpeed = 1; // 自動旋轉速度(值越大越快)// --------------------------// 2. 添加場景輔助元素(光和網格)// --------------------------// 環境光:均勻照亮整個場景,避免物體過暗const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);scene.add(ambientLight);// 方向光:模擬“太陽光”,產生陰影和明暗對比const directionalLight = new THREE.DirectionalLight(0xffffff, 1);directionalLight.position.set(5, 10, 7.5); // 光源位置scene.add(directionalLight);// 網格輔助線:幫助判斷物體位置(可選,便于調試)const gridHelper = new THREE.GridHelper(20, 20, 0xcccccc, 0xcccccc);scene.add(gridHelper);// --------------------------// 3. 模型加載與切換功能// --------------------------// 產品模型數據:存儲不同產品的模型地址(這里用Three.js官方示例模型)const productModels = {1: "https://threejs.org/examples/models/gltf/LittlestTokyo.glb", // 臺燈類模型2: "https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf", // 耳機/頭盔類模型3: "https://threejs.org/examples/models/gltf/Nefertiti/Nefertiti.gltf" // 手表/小物件類模型};// 加載模型的函數function loadModel(modelUrl) {// 移除當前模型(避免多個模型疊加)if (currentModel) {scene.remove(currentModel);}// GLTF加載器:加載3D模型文件const loader = new THREE.GLTFLoader();loader.load(modelUrl,(gltf) => { // 加載成功回調currentModel = gltf.scene; // 保存當前模型// 調整模型大小(避免模型過大/過小)const box = new THREE.Box3().setFromObject(currentModel);const size = box.getSize(new THREE.Vector3()).length();const scale = 3 / size; // 統一縮放到合適大小currentModel.scale.set(scale, scale, scale);// 居中模型(讓模型在場景中心顯示)const center = box.getCenter(new THREE.Vector3());currentModel.position.sub(center);scene.add(currentModel); // 將模型添加到場景},(xhr) => { // 加載進度回調(可選)console.log(`模型加載中:${Math.round(xhr.loaded / xhr.total * 100)}%`);},(error) => { // 加載失敗回調(可選)console.error("模型加載失敗:", error);});}// 初始加載第一個產品模型loadModel(productModels[1]);// --------------------------// 4. 綁定UI控制事件// --------------------------// 1. 自動旋轉開關const rotateBtn = document.getElementById('rotate-btn');rotateBtn.addEventListener('click', () => {controls.autoRotate = !controls.autoRotate;rotateBtn.textContent = controls.autoRotate ? "關閉自動旋轉" : "開啟自動旋轉";});// 2. 模型切換(下拉選擇框)const modelSelect = document.getElementById('model-select');modelSelect.addEventListener('change', (e) => {const selectedProductId = e.target.value;loadModel(productModels[selectedProductId]);});// 3. 重置視角const resetBtn = document.getElementById('reset-btn');resetBtn.addEventListener('click', () => {camera.position.set(0, 0, 5); // 重置相機位置controls.reset(); // 重置控制器狀態});// --------------------------// 5. 窗口 resize 適配(避免窗口縮放后畫面變形)// --------------------------window.addEventListener('resize', () => {camera.aspect = window.innerWidth / window.innerHeight;camera.updateProjectionMatrix(); // 更新相機投影矩陣renderer.setSize(window.innerWidth, window.innerHeight); // 更新渲染器大小});// --------------------------// 6. 動畫循環(讓3D場景“動”起來)// --------------------------function animate() {requestAnimationFrame(animate); // 循環調用自身controls.update(); // 更新控制器狀態(確保自動旋轉和阻尼生效)renderer.render(scene, camera); // 渲染場景}animate(); // 啟動動畫循環</script>
</body>
</html>
3.Three.js 進階
3.1?自定義著色器:用ShaderMaterial
寫自定義頂點 / 片元著色器(如實現水波紋效果)
什么是著色器?
著色器 (Shader) 是一種專門處理圖形渲染的小程序,運行在 GPU 上。就像畫家畫畫有步驟一樣,計算機渲染 3D 圖形也需要特定步驟,著色器就是負責這些步驟的。
頂點著色器:處理物體的 "骨架",決定每個頂點的位置
片元著色器:處理物體的 "皮膚",決定每個像素的顏色
什么是 ShaderMaterial?
在 Three.js 中,Material (材質) 決定了物體的外觀。而 ShaderMaterial 是一種特殊的材質,允許你編寫自己的頂點著色器和片元著色器,實現各種炫酷效果.
普通材質 (如 MeshBasicMaterial) 的著色器是 Three.js 內置的,而 ShaderMaterial 讓你可以 "自定義配方"。
水波紋效果的原理
水波紋效果本質上是讓物體表面的頂點按照一定規律運動,再配合顏色變化,模擬水波擴散的效果:
頂點隨時間上下起伏
距離波源越遠,波動越小
顏色可能隨波動高度變化(比如波峰更亮)
假設我們有一個平面,想讓它看起來像水面:
創建一個平面幾何體作為 "水面"
使用 ShaderMaterial,編寫自定義著色器
在頂點著色器中:
根據時間和頂點位置計算波動高度
讓頂點按照這個高度上下移動
在片元著色器中:
根據頂點的波動情況計算顏色
可能添加一些高光效果模擬水面反光
// 創建自定義材質 const waterMaterial = new THREE.ShaderMaterial({// 頂點著色器代碼vertexShader: `// 接收從JavaScript傳來的數據uniform float time;attribute vec3 position;void main() {// 復制原始位置vec3 newPosition = position;// 計算波動:使用正弦函數模擬波浪// 隨時間變化,x和z方向都有波動newPosition.y = sin(time + position.x) * 0.5 + cos(time + position.z) * 0.3;// 計算最終位置gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);}`,// 片元著色器代碼fragmentShader: `uniform float time;void main() {// 基礎藍色vec3 color = vec3(0.2, 0.5, 0.8);// 讓顏色隨時間輕微變化,增加動感color += sin(time) * 0.05;// 設置像素顏色gl_FragColor = vec4(color, 0.8); // 最后一個值是透明度}`,// 告訴Three.js我們的材質需要透明transparent: true });// 每一幀更新時間,讓波浪動起來 function animate() {requestAnimationFrame(animate);waterMaterial.uniforms.time.value += 0.01;renderer.render(scene, camera); }
=
3.2?性能優化:模型簡化(BufferGeometry
)、紋理壓縮、LOD(細節層次)
本質都是為了在保證視覺效果的同時,減少設備(手機、電腦)的運算壓力,避免畫面卡頓.
模型簡化(BufferGeometry):給 3D 模型 “減肥”,只留有用的 “骨架”
先想一個問題:我們看到的 3D 模型(比如游戲里的角色、場景中的桌子),其實不是 “實心” 的,而是由無數個 “小三角形”(叫 “面片”)拼出來的 —— 就像用樂高積木搭造型,積木越多,造型越精細,但拼起來越費時間。
模型簡化的核心,就是減少這些 “小三角形” 的數量,同時盡量不破壞模型的整體樣子;而 “BufferGeometry” 是實現簡化的 “高效工具”(比如 Three.js、Unity 等 3D 軟件里的核心技術)。
1. 為什么需要簡化?(痛點)
比如你做了一個 “3D 蘋果模型”,為了追求真實,用了 10 萬個小三角形 —— 但當這個蘋果在游戲里只是 “背景道具”(離玩家很遠,看不清細節)時,10 萬個三角形會讓手機 / 電腦反復計算 “每個三角形的位置、顏色”,導致畫面卡頓。
這就像:你背書包上學,本來裝 1 本課本就夠了,卻硬塞 10 本一模一樣的,既累又沒必要。
2. BufferGeometry 怎么 “減負”?(原理)
普通的 3D 模型數據(比如每個三角形的位置、顏色),會像 “散裝快遞” 一樣雜亂存儲,設備讀取時要反復 “找數據”,效率低;而 BufferGeometry 相當于把這些數據 “打包成整箱”,按固定順序排列。
舉個類比:
普通方式:要找 “三角形 1 的位置、三角形 2 的顏色、三角形 1 的顏色、三角形 2 的位置”—— 東找西找,浪費時間;
BufferGeometry:先把 “所有三角形的位置” 放一起,再把 “所有三角形的顏色” 放一起 —— 設備按順序讀 “整箱位置”“整箱顏色”,速度快 10 倍以上。
3. 簡化后效果?(好處)
手機 / 電腦運算壓力變小,畫面更流暢;
模型文件體積變小(比如從 10MB 縮到 2MB),加載速度更快(不會出現 “卡半天加載不出場景” 的情況)。
紋理壓縮:給 3D 模型的 “皮膚”“壓小”,不占內存
如果說 “模型簡化” 是減 “骨架”,那 “紋理壓縮” 就是減 “皮膚”——3D 模型表面的圖案(比如角色的衣服紋理、墻面的磚塊圖案)叫 “紋理”,本質是一張圖片(比如 1024×1024 像素的圖片)。
紋理壓縮的核心:把紋理圖片 “壓縮變小”,但視覺上幾乎看不出模糊,同時讓設備能快速讀取。
1. 為什么需要壓縮?(痛點)
一張 1024×1024 的 “未壓縮紋理圖”,體積可能有 4MB(按 RGB 格式算:1024×1024×3 字節≈3MB,加上透明通道就是 4MB);如果一個場景里有 100 個這樣的紋理,總大小就是 400MB—— 手機內存根本扛不住,還會導致 “紋理加載慢,畫面出現‘白塊’”。
這就像:你手機里存 100 張 4MB 的照片,占 400MB 內存;如果把照片壓縮成 1MB(清晰度沒明顯變化),100 張只占 100MB,省出的內存能裝更多東西。
2. 怎么壓縮?(原理,不用懂技術,看類比)
普通圖片壓縮(比如把 JPG 從 10MB 壓到 2MB)會損失細節,但 “紋理壓縮” 用了專門的 “硬件友好格式”(比如 ETC2、ASTC)—— 相當于給圖片做 “智能壓縮”:
比如把 “一片紅色區域” 的像素,只存 “紅色 + 區域范圍”,而不是每個像素都存一次紅色;
壓縮后的圖片,手機 GPU(負責畫面運算的硬件)能直接 “解碼使用”,不用額外花時間處理。
3. 壓縮后效果?(好處)
紋理文件體積縮小 50%-80%,加載快、不占內存;
視覺上幾乎沒區別(除非湊近看極端細節),不影響畫面美觀。
LOD(細節層次):讓模型 “遠近有別”,聰明省資源
核心邏輯是:模型離你越近,用 “高細節版本”(三角形多、紋理清晰);離你越遠,用 “低細節版本”(三角形少、紋理模糊)?,因為遠處根本看不清細節,沒必要浪費資源。
1. 為什么需要 LOD?(痛點)
如果沒有 LOD,游戲里所有模型都用 “最高細節版本”—— 比如遠處 100 米外的一棵樹,明明看起來只是個 “綠點”,卻用了 1 萬個三角形、4MB 的紋理,設備要花和 “近處角色” 一樣的力氣去計算,直接導致卡頓。
2. LOD 怎么工作?(原理)
開發者會給同一個模型做 “多個版本”,比如一個 “樹模型” 做 3 個版本:
LOD0(最高細節):10000 個三角形,2048×2048 紋理(離玩家<10 米時用,能看清樹葉脈絡);
LOD1(中等細節):2000 個三角形,1024×1024 紋理(離玩家 10-50 米時用,能看清樹的形狀,看不清脈絡);
LOD2(最低細節):500 個三角形,512×512 紋理(離玩家>50 米時用,只看出是 “綠色的樹輪廓”)。
游戲運行時,會自動判斷 “玩家和樹的距離”,切換對應的版本 —— 近用 LOD0,遠用 LOD2,既不影響視覺,又省資源。
3. LOD 的好處?
設備只在 “需要精細畫面” 時用高資源,遠處自動降資源,整體運算壓力大減;
畫面流暢度提升,同時遠處場景不會因為 “細節太低” 變模糊(因為遠處本就看不清)
總結
知識點 | 類比對象 | 核心作用 | 解決的問題 |
---|---|---|---|
模型簡化 | 給骨架減肥 | 減少三角形數量,優化數據存儲 | 模型運算慢、文件大 |
紋理壓縮 | 給皮膚 “瘦身” | 縮小紋理圖片,快速加載 | 紋理占內存、加載出白塊 |
LOD(細節層次) | 遠近穿不同衣服 | 近用高細節,遠用低細節 | 遠處模型浪費資源導致卡頓 |
3.3?后期處理:EffectComposer
實現模糊、泛光效果
什么是后期處理?
簡單說,后期處理就像照片的 "濾鏡",是在 3D 場景渲染完成后,對最終畫面添加的特效處理。比如讓畫面變模糊、加光暈、調顏色等,能讓場景看起來更有質感。
EffectComposer 是什么?
EffectComposer
?是 Three.js 的一個工具(需要額外引入擴展庫),專門用來管理和實現各種后期處理效果。它的工作流程類似:
先正常渲染 3D 場景
把渲染結果交給各種特效 "處理器"
最后把處理好的畫面顯示到屏幕上
如何實現模糊效果?
模糊效果就像給畫面打了一層柔光,讓圖像邊緣變得不那么銳利。
實現步驟:
引入必要的庫(Three.js 核心庫 + 后期處理擴展庫)
創建場景、相機、物體(基礎 3D 場景)
初始化?
EffectComposer
(后期處理組合器)添加特效通道(模糊、泛光等)
在動畫循環中更新組合器,而不是直接渲染場景
// 1. 創建場景、相機、渲染器 const scene = new THREE.Scene(); const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000); const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement);// 2. 添加一個物體(比如一個立方體) const geometry = new THREE.BoxGeometry(); const material = new THREE.MeshBasicMaterial({color: 0x00ff00, wireframe: true}); const cube = new THREE.Mesh(geometry, material); scene.add(cube); camera.position.z = 5;// 3. 初始化后期處理組合器 const composer = new THREE.EffectComposer(renderer);// 4. 添加渲染通道(先把場景正常渲染到臨時畫布) const renderPass = new THREE.RenderPass(scene, camera); composer.addPass(renderPass);// 5. 添加模糊效果通道 const blurPass = new THREE.ShaderPass(THREE.GaussianBlurShader); blurPass.uniforms['sigma'].value = 5; // 模糊程度(值越大越模糊) composer.addPass(blurPass);// 6. 添加泛光效果通道 const bloomPass = new THREE.UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight),1.5, // 泛光強度0.4, // 泛光半徑0.85 // 泛光閾值(值越小,越多物體產生泛光) ); composer.addPass(bloomPass);// 7. 動畫循環(用composer渲染,而不是renderer) function animate() {requestAnimationFrame(animate);cube.rotation.x += 0.01;cube.rotation.y += 0.01;// 注意這里不再用 renderer.render(scene, camera)composer.render(); // 用組合器渲染,自動應用所有特效 } animate();