# 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