iT邦幫忙

2022 iThome 鐵人賽

DAY 20
0
Software Development

30天成為鍵盤麥可貝:前端視覺特效開發實戰系列 第 20

Day20: three.js 前端3D視覺特效開發實戰——智慧工廠:倒影特效

  • 分享至 

  • xImage
  •  

鏡面特效能夠非常有效的讓畫面更加豐富。不僅讓物件更有真實感,也可以創造出空間感。

上一篇示範過如何做出鏡面反射。而本篇將介紹倒影特效。

上一篇提到鏡面反射的原理,就是在物理裡面藏一個照相機,不斷拍攝四周的畫面,並產生材質貼圖,貼到物體上。倒影特效更加簡單:它只需要一個鏡頭拍出材質,它甚至不用實例化鏡頭,就能直接在材質中透過Shader解決掉。

本篇將透過工廠的倒影模擬,產生更加現實、豐富的畫面。

成品

Untitled (49).gif

準備程式碼

https://ithelp.ithome.com.tw/upload/images/20221006/20142505bHedc8NizA.png

CodePen

https://codepen.io/umas-sunavan/pen/MWGBmwK?editors=0011

在範本中,我準備了球形Mesh,作為環境貼圖,並加上了基本的光、渲染器等three.js的基本初始化設定。

開發鏡面特效

開發鏡面特效:加入機櫃模型

我先製作了一個機櫃,機櫃貼圖來源為42U Rack Mount Cabinet Enclosure型號。

官方網站

https://ithelp.ithome.com.tw/upload/images/20221006/20142505MNazzFTbNw.png

將模型讀入場景內。

// device作為閉包外存取模型的變數
let device
(async () => {
	const path = 'https://storage.googleapis.com/umas_public_assets/michaelBay/day20/cabinet_mapping.gltf'
	const gltf = await new GLTFLoader().loadAsync(path);
	cabinet = gltf.scene
	cabinet.scale.set(0.5,0.5,0.5)
	scene.add(cabinet)
})()
...
function animate() {
	...
	// 幫機櫃加上旋轉
	if (cabinet) {
		cabinet.rotation.y +=0.01
	}

}

https://ithelp.ithome.com.tw/upload/images/20221006/20142505zdqHawACpq.png

加上去之後,因為機櫃顏色太深了,不適合示例。我把顏色材質貼圖拿掉。

(async () => {
	...
	// 巢狀遞迴所有子元件
	gltf.scene.traverse( object => {
		// 找尋Mesh物件(藉此捨棄掉鏡頭、光源等物件)
		if (object.isMesh) {
			// 將顏色材質圖拿掉
			object.material.map = null
			// 將法線材質圖拿掉
			object.material.normalMap = null
		}
	})
})()

https://ithelp.ithome.com.tw/upload/images/20221006/20142505ripX3Da7Di.png

看起來清晰多了。

開發鏡面特效:Reflector(鏡面物件)的資源位置

如果你在npm安裝three,或是直接用CDN抓取,都沒辦法直接抓取到Reflector

因為Reflector 物件並不存在於three主程式中。事實上,它存在於three.js其中一個資料夾中:

  1. 如果是NPM則位在./node_modules/three/examples/jsm/objects/Reflector
  2. 如果是CDN則位在https://unpkg.com/three@latest/examples/jsm/objects/Reflector

為什麼會把元件放在examples 裡面呢?因為three.js 有很多社群擴充的元件。這些元件都會被放到examples資料夾裡面,雖然這些都不在three.js的核心中,也不會出現在官方文件說明當中,但仍十分受用,被應用在很多地方。

開發鏡面特效:加入Reflector(鏡面物件)

總而言之,將它加到專案中即可。

// 匯入module
import { Reflector } from 'https://unpkg.com/three@latest/examples/jsm/objects/Reflector';

const geometry = new THREE.PlaneGeometry(20, 20, 1, 1)
// 實例化Reflector
let mirror = new Reflector(geometry)
mirror.position.set(0,0,-20)
scene.add(mirror);

我們就可以看到鏡子了。

Untitled (50).gif

開發鏡面特效:參數設定與說明

Reflector提供很多參數給我們調整鏡面,以下說明:

// 參數物件
let options = {
	clipBias: 0.03, // 鏡射多遠的距離
	textureWidth: 1024, // 鏡射材質圖解析度
	textureHeight: 1024, // 鏡射材質圖解析度
	color: 0x889999, // 反射光的濾鏡
	recursion: 0 // 反射可以反彈幾次
};
const geometry = new THREE.PlaneGeometry(20, 20, 1, 1)
// 放到Reflector參數中。
let mirror = new Reflector(geometry, options)
// 刪除這行 -> mirror.position.set(0,0,-10)
// 調整其面向
mirror.rotation.x = Math.PI * -0.5
scene.add(mirror);
  • clipBias

    • 鏡射多遠的距離,數值如果設置太高,會裁掉鏡子裡的機櫃

      https://ithelp.ithome.com.tw/upload/images/20221006/20142505otbjjTomyX.png

  • textureWidth & textureHeight

    • 材質圖解析度。我們從上一篇得知鏡面反射的原理是材質圖。而這裡也是。解析度越低,取樣的越粗糙。

      https://ithelp.ithome.com.tw/upload/images/20221006/20142505I1bEdJ2Jxm.png

  • color

    • 鏡子裡要帶有什麼顏色。你可以用這個參數來調整鏡子本身的顏色。除了調整顏色以外,也併用透明度與混合模式來達到鏡面物體的效果(如古人的銅鏡需保有顏色但又必須有鏡面效果)。

      https://ithelp.ithome.com.tw/upload/images/20221006/20142505jmTUvsGSDm.png

  • recursion

    • 鏡面反射鏡面時,可以反射多少次數

我特別喜歡這個效果。加上模型中的金屬材質跟粗糙材質,使得元件具有生命力。事實上,你可以看到鏡面中的反射光更加生動。

Untitled (51).gif

我們的鏡面就完成了。非常簡單。

當然,依照慣例我們還是會補充更多。

比如說好了,我們如果要把這種鏡面效果當作下圖中地板,那麼不能就像下圖那樣全部反射,這樣很假:

https://ithelp.ithome.com.tw/upload/images/20221006/20142505KWWCGjeaPC.png

進階鏡面特效

Reflector的效果非常適合運用在鏡子、反光玻璃等場景下。但如果要讓地板光滑到可以反射機房中的機櫃,那就會有問題了。因為光滑地板並不會完全反射地面上的東西。

Untitled (53).gif

如果要做到逼真的地板,那麼就至少需要「淡化」鏡面的效果。方法有兩種:

  1. 調整Reflector透明度,並再疊一個地板,使得地板可以比較「不反光」。

    1. 即使使用這個方法,鏡面仍然可以完整的反射物件,沒辦法漸淡。

    2. 疊兩個物件時,容易造成「Z Fighting」,必須再處理depth buffer問題。

      https://ithelp.ithome.com.tw/upload/images/20221006/20142505k6pR7rptlj.png

      圖片來源

  2. 使用MeshReflectorMaterial.js

    1. MeshReflectorMaterial是three.js社群中的工程師開發的工具,並沒有列在官方文件,支援比較少。但如果你看完我接著即將撰寫的Shader系列文章,那麼其實你也改得了他做的元件,也不一定全都需要依賴社群。
    2. 它的生平很迥異。現在有一個基於three.js的函式庫叫做react-three-fiber,它用貼近react的寫法撰寫three.js,而社群上是有人基於react-three-fiber開發MeshReflectorMaterial,事後才有人開發純javascript版本(本次示例)。這也是為什麼它叫做MeshReflectorMaterial,因為這個是react-three-fiber擴充套件中的命名慣例。

由於第一點比較簡單粗暴好理解,我們就先跳過它來示例比較少見但好用的MeshReflectorMaterial

目前目前使用Reflector的效果:

Untitled (53).gif

這是接下來使用MeshReflectorMaterial的效果:

Untitled

進階鏡面特效:準備程式碼

需要準備三項東西:

  1. CodePen工廠場景程式碼

  2. MeshReflectorMaterial.js 腳本

  3. 安裝套件postprocessing

  4. CodePen

    https://ithelp.ithome.com.tw/upload/images/20221006/20142505mW2tnsQ0x9.png

    https://codepen.io/umas-sunavan/pen/VwxBxKm?editors=1011

  5. MeshReflectorMaterial.js 腳本

    除此之外,還要準備一份人家寫好的類別,其連結如下:

    https://gist.github.com/0beqz/e69378b278e5c336afe0c7ae9b4ed86c

    https://ithelp.ithome.com.tw/upload/images/20221006/20142505iI34hoKUwP.png

  6. 安裝postprocessing

    安裝套件即可。

    npm i postprocessing
    

進階鏡面特效:淡化鏡面介紹

淡化鏡面的原理,就是加上一層漸層。這個漸層中,比較靠鏡面的畫面比較不透明,而比較遠離鏡面的畫面比較透明。

透過這個方式,就可以呈現淡化的鏡面效果。

這個技術的問題在於,當用戶放大畫面時,會看到漸層範圍比較小了。當用戶縮小畫面時,會看到漸層範圍比較大。

進階鏡面特效:安裝流程

重新確認一次,除了需要CodePen的程式碼以外,還需要MeshReflectorMaterial.js 腳本並安裝套件npm i postprocessing

進階鏡面特效:修改機櫃數量

程式碼中,我已經新增了工廠的廠房模型room 、四跟柱子column1~4 、很多個機櫃Cabinet (以row跟column來排列)。

我這邊就不詳細解釋這物件實例化部份的實作,有興趣可以查看程式碼,我這邊就聚焦在漸淡倒影的實作。

進階鏡面特效:設定參數

這個最花時間,這個不得不說明。我這邊挑幾個重要的說明:

  • 最重要的是minDepthThreshold以及maxDepthThreshold

    簡單來說,maxDepthThreshold代表從多遠的地方開始淡出,而minDepthThreshold代表到多遠的地方會淡出到沒畫面。

    想像有一個透明漸層,100%是不透明,0%是透明。在鏡子裡面,距離鏡子最接近物件其鏡中反射應該是100%不透明,距離鏡子最遠的物件其鏡中反射應該是0%最透明。

    minDepthThreshold就代表鏡中反射最遠方的透明處,到底在0~1區間範圍的哪裡。

    https://ithelp.ithome.com.tw/upload/images/20221016/20142505nzqcKcguSp.png

    至於maxDepthThreshold 則控制最不透明處,從哪裡開始透明。其數值不限於0~1。

  • blur 鏡面是否需要高斯模糊。若是,那麼高斯模糊的材質解析度為何。([0,0]代表沒有高斯模糊)

  • resolution 由於反射的原理是透過材質圖,貼在物件上面。因此需要設定材質圖的解析度。解析度越高效能越差。

  • reflectorOffset 鏡面跟物體中間是否要留一段距離才開始反射。

// 添加到程式碼
let fadingReflectorOptions = {
	mixBlur: 2,
	mixStrength: 1.5,
	resolution: 2048, // 材質圖的解析度
	blur: [0, 0], // 高斯模糊的材質解析度為何
	minDepthThreshold: 0.7,// 從多遠的地方開始淡出
	maxDepthThreshold: 2, // 到多遠的地方會淡出到沒畫面
	depthScale: 2,
	depthToBlurRatioBias: 2,
	mirror: 0,
	distortion: 2,
	mixContrast: 2,
	reflectorOffset: 0, // 鏡面跟物理中間是否要留一段距離才開始反射
	bufferSamples: 8,
}

進階鏡面特效:加上淡化鏡面作為地面

就如同其他物件一樣,透過傳入形狀geometry跟材質material來建立Mesh物件。

不同的地方在於:Mesh的材質被置換掉了。

// 腳本可以參考:[https://gist.github.com/0beqz/e69378b278e5c336afe0c7ae9b4ed86c](https://gist.github.com/0beqz/e69378b278e5c336afe0c7ae9b4ed86c)
import MeshReflectorMaterial from './MeshReflectorMaterial.js';
// 透過geometry以及material來建立Mesh物件
const geometry = new THREE.PlaneGeometry(60, 60, 1, 1)
const material = new THREE.MeshBasicMaterial()
const mesh = new THREE.Mesh(geo, mat)
// 將材質置換成MeshReflectorMaterial
mesh.material = new MeshReflectorMaterial(renderer, camera, scene, mesh, fadingReflectorOptions);
scene.add(mesh);
// 旋轉mesh角度以作為地面
mesh.rotateX(Math.PI * -0.5)

要注意的是,必須先實例化一個材質,再置換成MeshReflectorMaterialMeshReflectorMaterial它需要傳入mesh參數,如果沒有透過一個材質來實例化物件,那就沒辦法順利傳入參數。

進階鏡面特效:每幀更新鏡面材質圖

由於鏡面是透過材質圖形成的,所以每幀必須不斷更新材質圖。

function animate() {
	requestAnimationFrame(animate);
	renderer.render(scene, camera);
// 每幀更新鏡面材質圖
	fadingGround.material.update()
}

做完之後,就可以看到鏡面的地面出來了。

Untitled (54).gif

進階鏡面特效:加上淡化鏡面作為牆壁

有了地面還不夠,我們可以加上牆壁。首先我們將前面新增地面的程式碼包成函式。

const addFadingMirror = () => {
	const geo = new THREE.PlaneGeometry(60, 60, 1, 1)
	const mat = new THREE.MeshBasicMaterial()
	const mesh = new THREE.Mesh(geo, mat)
	mesh.material = new MeshReflectorMaterial(renderer, camera, scene, mesh, fadingReflectorOptions);
	scene.add(mesh);
	return mesh;
}
const fadingGround = addFadingMirror()
fadingGround.rotateX(Math.PI * -0.5)

接著,製作前後左右的牆壁。

const fadingWallZN = addFadingMirror()
fadingWallZN.translateX(0)
fadingWallZN.translateZ(-29)

const fadingWallZP = addFadingMirror()
fadingWallZP.translateZ(29)
fadingWallZP.rotateY(Math.PI)

const fadingWallXN = addFadingMirror()
fadingWallXN.rotateY(Math.PI * 0.5)
fadingWallXN.translateZ(-29)

const fadingWallXP = addFadingMirror()
fadingWallXP.translateX(29)
fadingWallXP.rotateY(Math.PI * -0.5)

function animate() {
	...
	fadingWallZN.material.update()
	fadingWallZP.material.update()
	fadingWallXN.material.update()
	fadingWallXP.material.update()

}

這能夠創造出比較科幻的效果。

成品

Untitled (55).gif

小結

多虧three.js社群,才可以有這樣的reflector工具源源不絕的產出。事實上,我們是有辦法自製自己的鏡面特效了。過去的幾篇文章裡,我們不僅帶到向量計算、光的原理、陰影原理,這些都是製作特效的重要概念。

最後的九天我將會介紹WebGL的Shader,到時候我們可以釐清WebGL Shader的世界觀。到時候要製作自己的特效將輕而易舉。

參考資料

鏡面特效示例

機櫃素材

機櫃素材圖片來源

機櫃素材圖片來源

淡出特效鏡面討論

淡出鏡面特效社群討論

淡出鏡面特效原型鍊實作

淡出鏡面特效開發筆記


上一篇
Day19: three.js 前端3D鏡面特效開發實戰:梅利奧達斯的全反射!—智慧工廠檢視器
下一篇
Day21: three.js 智慧工廠開發實戰:Dejavu! 讓鏡頭跟著拓海一起飄移:鏡頭追蹤、飄移特效
系列文
30天成為鍵盤麥可貝:前端視覺特效開發實戰31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言