# Tres.js 用法(@tresjs/nuxt + @tresjs/cientos) @tresjs/nuxt 是 Nuxt 的其中一個 module ,主要是在 Nuxt 中透過元件的方式做出 3D 效果,其諸多寫法都基於 three.js 延伸而來。 ## 專案原始碼 Github 連結:https://github.com/WangShuan/nuxt-tres > 目前僅能在開發模式下正確運行,build 後部分元件的功能會失效 ## 安裝與設置 創建一個 nuxt 專案,進入專案項目中,開啟終端機,輸入以下指令安裝 tresjs: ```shell npm install @tresjs/nuxt ``` 接著設定 nuxt.config.ts 檔案: ```javascript export default defineNuxtConfig({ modules: ["@tresjs/nuxt"], // 添加這行 }) ``` ## 基礎用法 首先開啟 App.vue 檔案,建立 TresCanvas 標籤在最外層,並設置參數 window-size 表示要符合視窗大小(建立場景、設置場景大小為視窗大小),接著需要在裡面添加 TresPerspectiveCamera 標籤(建立透視相機),最後就是建立物件,用 TresMesh 標籤,包裹 TresTorusGeometry 及 TresMeshNormalMaterial 標籤(對應在 Three.js 中要先建立 Mesh 並設置 Geometry 與 Material)。 完整小範例如下: ```htmlmixed= <template> <!-- 用來建立場景,設置 window-size 讓畫布的寬高符合視窗大小、設置 clear-color 更改場景背景顏色(預設是黑) --> <TresCanvas clear-color="#82DBC5" window-size> <!-- 用來建立透視相機,可設置 :position 調整位置(預設是 3,3,3 的位置) --> <TresPerspectiveCamera :position="[0, 0, 3]" /> <!-- 用來建立 Mesh --> <TresMesh> <!-- 用來建立甜甜圈形狀的物件,可傳入 :args 設置相關尺寸 --> <TresTorusGeometry :args="[0.2, 0.1, 16, 32]" /> <!-- 設置該 Mesh 的材質 --> <TresMeshNormalMaterial /> </TresMesh> </TresCanvas> </template> ``` ## 一些常用設定 1. 讓物體產生透明度:在 `TresCanvas` 設置 `alpha` 並在 Material 標籤設置 `transparent` 與 `:opacity="0.5"` 2. 讓物體渲染雙面(預設只會渲染前景):在 Tres 的 Material 標籤上,設置 `:side="2"`(等同於 Three.js 中的 THREE.DoubleSide) 3. 讓相機一直看向中心點:在 Camera 標籤上,設置 `:look-at="[0, 0, 0]"` 4. 可以自己寫一個`lerp` 方法,在兩個值之間進行平滑的過渡:`(start, end, amt) => (1 - amt) * start + amt * end;` 5. 可以自己寫一個方法,判斷兩個點之間的位置有多靠近: `(point1, point2) => Math.sqrt((point2.x - point1.x) ** 2 + (point2.y - point1.y) ** 2 + (point2.z - point1.z) ** 2);` 6. 讓畫布背景為透明:在 `TresCanvas` 設置 `alpha` 並設置 `:clear-color="false"` ## 設置動畫 建立 ref 並綁定到 TresMesh 身上,再從 useRenderLoop 引入 onLoop 方法,並使用 onLoop 方法編寫想在每個 frame 中執行的動畫。 ```htmlmixed= <template> <TresCanvas clear-color="#82DBC5" window-size> <TresPerspectiveCamera :position="[0, 0, 3]" /> <!-- 在 Mesh 上方綁定 ref --> <TresMesh ref="box"> <TresTorusGeometry :args="[0.2, 0.1, 16, 32]" /> <TresMeshNormalMaterial /> </TresMesh> </TresCanvas> </template> <script setup lang="ts"> // 建立 ref const box = ref() // 透過 useRenderLoop 引入 onLoop 方法 const { onLoop } = useRenderLoop() // 使用 onLoop 方法,該方法會在每個 frame 中被調用 onLoop(({ delta, elapsed }) => { if (box.value) { // 設置 box 的 rotation box.value.rotation.y += delta } }) </script> ``` > 這邊官方建議使用 delta 及 elapsed 當作計算值(如果用純數字計算,效能好像會很差) ## 使用控制器 如同 OrbitControls 在 Three.js 中需要額外 import 一樣, OrbitControls 也不隸屬於 @tresjs/nuxt 裡頭,需要再額外安裝 @tresjs/cientos 後,才能於 Nuxt 中使用。 進入專案項目中,開啟終端機,輸入以下指令進行安裝: ```shell npm i @tresjs/cientos ``` 並且需要設定 nuxt.config.ts 檔案: ```javascript= export default defineNuxtConfig({ modules: ["@tresjs/nuxt"], build: { // 添加這段 transpile: ['@tresjs/cientos'], }, }) ``` 接著你不需要任何設定與引入,直接在想使用的地方添加 OrbitControls 元件到 TresCanvas 裡面即可: ```htmlmixed <template> <TresCanvas clear-color="#82DBC5" window-size> <TresPerspectiveCamera :position="[0, 0, 3]" /> <TresMesh ref="box"> <TresTorusGeometry :args="[0.2, 0.1, 16, 32]" /> <TresMeshNormalMaterial /> </TresMesh> <!-- 添加 OrbitControls 元件,設定 auto-rotate 自動旋轉、設定 enable-damping 開啟阻尼 --> <OrbitControls auto-rotate enable-damping /> </TresCanvas> </template> ``` > 目前實測 Nuxt3 build 後 auto-rotate 會失效,已發起 issues 未解決 ## 使用滑鼠視差 想要透過滑鼠鼠標移動影響物體的位置,同樣需要額外安裝 @tresjs/cientos 並且需要設定 nuxt.config.ts 檔案: ```javascript= export default defineNuxtConfig({ modules: ["@tresjs/nuxt"], build: { // 添加這段 transpile: ['@tresjs/cientos'], }, }) ``` 接著你不需要任何設定與引入,直接在想使用的地方添加 MouseParallax 元件到 TresCanvas 裡面即可: ```htmlmixed= <template> <TresCanvas clear-color="#82DBC5" window-size> <TresPerspectiveCamera :position="[0, 0, 3]" /> <TresMesh ref="box"> <TresTorusGeometry :args="[0.2, 0.1, 16, 32]" /> <TresMeshNormalMaterial /> </TresMesh> <!-- 添加 MouseParallax 元件 --> <MouseParallax /> </TresCanvas> </template> ``` > 目前實測 Nuxt3 build 後 MouseParallax 會失效,已發起 issues 未解決 ## 使用 Text3D 首先要在 public 資料夾中新增 font 檔案, ```htmlmixed= <template> <TresCanvas clear-color="#82DBC5" window-size> <TresPerspectiveCamera :position="[0, 0, 3]" /> <!-- 用 Suspense 包裹 Text3D 元件--> <Suspense> <!-- 使用 Text3D 元件,傳入文字、設定 font --> <Text3D text="Tres.js is awesome!" font="/fonts/FiraCodeRegular.json"> <TresMeshNormalMaterial /> </Text3D> </Suspense> </TresCanvas> </template> ``` ## 漂浮效果 想要使用漂浮效果,同樣需要額外安裝 @tresjs/cientos 並且需要設定 nuxt.config.ts 檔案: ```javascript= export default defineNuxtConfig({ modules: ["@tresjs/nuxt"], build: { // 添加這段 transpile: ['@tresjs/cientos'], }, }) ``` 接著你不需要任何設定與引入,直接在想使用的地方添加 Levioso 元件到 TresCanvas 裡面即可: ```htmlmixed <template> <TresCanvas> <TresPerspectiveCamera :position="[0, 0, 5]" /> <!-- 使用 Levioso 取代 TresMesh 包裹物件,並設置 v-bind 為 leviosoState --> <Levioso v-bind="leviosoState"> <Octahedron :position="[0, 0, 0]"> <TresMeshNormalMaterial /> </Octahedron> </Levioso> </TresCanvas> </template> <script setup> // 設定 leviosoState const leviosoState = shallowReactive({ speed: 10, // 動畫的速度 rotationFactor: 1, floatFactor: 1, // 上下漂移的幅度 range: [-0.3, 0.3], }) </script> ``` > 目前實測 Nuxt3 build 後 Levioso 會失效,已發起 issues 未解決 ## Scroll 控制 ```htmlmixed= <script setup lang="ts"> // 建立一個 ref 用來綁定給物件 const groupRef = ref() // 建立一個 progress 用來綁定給 scroll 控制器 const progress = ref(0) // 使用 onLoop 方法讓物件跟隨控制器進行旋轉 const { onLoop } = useRenderLoop() onLoop(() => { if (groupRef.value) { groupRef.value.rotation.x = progress.value * 5 groupRef.value.rotation.y = progress.value * 5 groupRef.value.rotation.z = progress.value * 5 } }) </script> <template> <!-- 在 TresCanvas 父層添加一個元素設置大小 --> <div class="tres"> <TresCanvas clear-color="#fff"> <TresPerspectiveCamera :position="[0, 0, 3]" /> <!-- 設置 scroll 控制器使用 htmlScroll,並綁定 progress --> <ScrollControls html-scroll v-model="progress"> <!-- 綁定 ref 讓物件跟隨 ScrollControls 的 progress 進行旋轉 --> <TresMesh ref="groupRef"> <Octahedron :position="[0, 0, 0]"> <TresMeshNormalMaterial /> </Octahedron> </TresMesh> </ScrollControls> </TresCanvas> </div> <!-- 添加其他元素使頁面高度撐開,讓視窗可滾動 --> <section> <h2>01 Section</h2> <p>Lorem ipsum dolor sit amet, repellendus ullam.</p> </section> <section> <h2>02 Section</h2> <p>Ut maxime, ullam natus officiis sint veritatis sed autem?</p> </section> <section> <h2>03 Section</h2> <p>Esse voluptas beatae rem, modi eos molestias ea at ex!</p> </section> </template> <style scoped> .tres { width: 300px; height: 300px; position: fixed; left: 0; top: 0; z-index: -1; } section { height: 100vh; border: 1px solid green; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 30px; } </style> ``` > 這邊要注意因為是依賴 html-scroll 所以 html 本身必須可以滾動才有作用 ## 引入與使用紋理貼圖 創建 components 以建立 Mesh 並在 Mesh 中使用 MatcapMaterial 或任何需要載入 Texture 的材質,並在 js 中通過 useTexture 載入 Texture 檔案: ```htmlmixed= <!-- /components/MatcapBall.vue --> <script setup lang="ts"> // 使用 useTexture 設置 matcap 的檔案 const pbrTexture = await useTexture({ matcap: '/textures/matcap-red-light.png', }) </script> <template> <TresMesh> <TresSphereGeometry :args="[1, 100, 100]" /> <!-- 使用 TresMeshMatcapMaterial 傳入 pbrTexture --> <TresMeshMatcapMaterial v-bind="pbrTexture" :displacement-scale="0.2" /> </TresMesh> </template> ``` 接著在要使用該 Mesh 的 pages 檔案中,用 `Suspense` 包裹住元件標籤: ```htmlmixed= <!-- /pages/texture.vue --> <template> <TresCanvas window-size> <TresPerspectiveCamera :position="[0, 0, 10]" /> <!-- 用 Suspense 標籤包住 MatcapBall 元件 --> <Suspense> <MatcapBall /> </Suspense> </TresCanvas> </template> ``` 或者也可以使用 `TresMeshStandardMaterial` 設置紋理貼圖,步驟同上建立一個元件如下 : ```htmlembedded <script setup> const pbrTexture = await useTexture({ map: '/textures/Rock035_2K_Displacement.jpg', displacementMap: '/textures/Rock035_2K_Displacement.jpg', roughnessMap: '/textures/Rock035_2K_Roughness.jpg', normalMap: '/textures/Rock035_2K_NormalGL.jpg', ambientOcclusion: '/textures/Rock035_2K_AmbientOcclusion.jpg', }) </script> <template> <TresMesh :position="[1, 3, 5]"> <TresSphereGeometry :args="[1.2, 100, 100]" /> <TresMeshStandardMaterial v-bind="pbrTexture" :displacement-scale="0.1" /> </TresMesh> </template> ``` 接著在要使用的地方用 Suspense 標籤包住該元件,並添加燈光即可。 ## 引入及使用模型 安裝 @tresjs/cientos 後即可使用其提供的 useGLTF 方法載入 GLTF 模型,並通過 useAnimations 執行模型動畫。 ```htmlmixed= <template> <TresCanvas window-size> <TresPerspectiveCamera :position="[0, 0, 300]" /> <!-- 使用 Suspense 包住 primitive --> <Suspense> <!-- 使用 primitive 並傳入模型 --> <primitive :object="model" /> </Suspense> <OrbitControls /> <!-- 放個燈光 --> <TresDirectionalLight :position="[0, 2, 4]" :intensity="2" cast-shadow /> </TresCanvas> <!-- 建立按鈕並把按鈕 position 在畫面上 --> <div class="btns"> <button @click="playAction('idle')">idle</button> <button @click="playAction('skate')">skate</button> <button @click="playAction('snap')">snap</button> <button @click="playAction('shoot')">shoot</button> <button @click="playAction('rocketBoot')">rocketBoot</button> <button @click="playAction('groundPound')">groundPound</button> <button @click="playAction('dance')">dance</button> </div> </template> <script setup> // 使用 useGLFT 載入模型檔案 const { scene: model, animations } = await useGLTF( '/multiclip.gltf', ) // 設置模型檔案位置(預設模型會站立在 0,0,0) model.position.y = -70 // 獲取所有動畫 const { actions } = useAnimations(animations, model) // 新增一個參數用來存當前動畫 const currentAction = ref() // 建立函數,用來切換動畫 const playAction = (name) => { // 若當前有動畫,先暫停 if (currentAction.value) { currentAction.value.stop() } // 將當前動畫換成最新選擇的動畫 currentAction.value = actions[name] // 執行動畫 currentAction.value.play() } </script> ``` > 目前實測 Nuxt3 build 後 useAnimations 會失效,已發起 issues 未解決 模型下載網站: https://sketchfab.com/feed ## 燈光與陰影 在 TresCanvas 設定 shadows 並在燈光設置 cast-shadow 即可開啟陰影 在 TresMesh 設置 cast-shadow 可以讓物體產生陰影 在 TresMesh 設置 receive-shadow 可以讓地板接收陰影 ```htmlembedded= <template> <Title>Tres.js 基礎使用</Title> <TresCanvas window-size alpha shadows> <TresPerspectiveCamera :look-at="[0, 0, 0]" /> <TresGroup :position="[0, 2, 0]"> <TresMesh cast-shadow> <TresTorusGeometry :args="[1, 0.4, 32, 32]" /> <TresMeshStandardMaterial color="white" /> </TresMesh> <TresMesh :position="[4, 0, 0]" cast-shadow> <TresBoxGeometry :args="[2, 2, 2]" /> <TresMeshStandardMaterial color="white" /> </TresMesh> <TresMesh :position="[-4, 0, 0]" cast-shadow> <TresTorusKnotGeometry :args="[1, 0.4, 32, 32]" /> <TresMeshStandardMaterial color="white" :roughness="0.1" /> </TresMesh> </TresGroup> <TresMesh :rotate-x="-Math.PI / 2" receive-shadow> <TresPlaneGeometry :args="[15, 15]" /> <TresMeshStandardMaterial color="white" /> </TresMesh> <OrbitControls /> <!-- DirectionalLight 與其輔助線 --> <TresDirectionalLight cast-shadow ref="lightRef" :position="[5, 10, 5]" :args="['white', 1]" /> <TresDirectionalLightHelper v-if="lightRef" :args="[lightRef, 1]" /> <!-- HemisphereLight --> <TresHemisphereLight :args="['white', 'black', 1]" /> <!-- PointLight --> <TresPointLight :args="['yellow', 2]" :position="[0, 3, 3]" /> </TresCanvas> </template> <script setup> const lightRef = ref() </script> ``` > 想使用燈光輔助線需要建立 ref 並綁定在燈光標籤上,接著建立輔助線標籤,傳入 "args" 的第一個參數為 ref 第二個參數為 size(這邊要注意,需要在燈光輔助現身上添加 v-if 判斷 ref 是否存在才能顯示) > 另外官方的 cientos 套件本身有提供 v-light-helper 屬性,可直接放在燈光標籤上以開啟輔助線,但目前好像不支持在 Nuxt3 使用,ref 倒是可正常顯示 ## Html 用法 用於給物體身上添加 HTML ,需要安裝與設置 @tresjs/cientos ,接著在 js 中 `import { Html } from '@tresjs/cientos';` 並於 `TresMesh` 標籤中新增 `Html` 元件,設置 HTML 內容。 示例: ```htmlembedded <template> <TresMesh receive-shadow :rotate-x="-Math.PI / 2" :position="[0, -1.5, 0]"> <TresPlaneGeometry :args="[100, 100]" /> <TresMeshStandardMaterial /> <!-- 建立 Html 設置 transform 讓其根據物體形狀變換文字 --> <Html transform :position="[0, -25, 0]"> <div class="description"> 請使用鍵盤 W、S、A、D 按鍵移動小兔子,讓牠走進你想前往的地方 <br> Please use the W, S, A, D keys to move the rabbit and guide it to the destination of your choice. </div> </Html> </TresMesh> </template> <script setup> import { Html } from '@tresjs/cientos'; </script> <style scoped> .description { box-sizing: border-box; font-size: 24px; text-align: center; font-weight: bold; display: flex; width: 80vw; justify-content: center; align-items: center; color: #fff; } </style> ``` > 官方文件: https://cientos.tresjs.org/guide/misc/html-component.html