這裡是「Three.js學習日誌」的第15篇,本篇的主旨是要透過一個簡單的範例操作,來一步一步介紹材質與渲染的關係,這系列的文章假設讀者看得懂javascript,並且有Canvas 2D Context的相關知識。
前情提要: 昨天時間略趕,一時的疏忽不小心漏掉了一條需要在環境中補上環境光的描述,如果有人照著步驟走,可能會有亮度不夠的問題,所以這邊重新補上,對各位造成不便,敬請見諒。
const al = new AmbientLight(0xffffff, 1);
...
scene.add(al);
今天我們要來完成昨天沒完成的範例。
我們之前在講解texture
的時候其實沒有講到這種類型的texture
。
所謂的環境貼圖指的是:
周圍景色透過物體表面反射到觀察者眼中的圖樣。
有些人也會把這種概念稱為Skybox(天空盒)
在Three.js
中,環境貼圖需要靠CubeTextureLoader
來載入,它的形式會比較特別一點,當我們要載入一個完整的環境貼圖,我們需要依序傳入6張不同的周圍景色圖片。
import {CubeTextureLoader} from 'https://cdn.skypack.dev/three';
...
const cubeTextureLoader = new CubeTextureLoader();
const envMap = cubeTextureLoader.load([
envPx, //+X位置的環境貼圖
envNx, //-X位置的環境貼圖
envPy, //+Y位置的環境貼圖
envNy, //-Y位置的環境貼圖
envPz, //+Z位置的環境貼圖
envNz //-Z位置的環境貼圖
])
這邊我們會把6張環境貼圖分成+X/-X/+Y/-Y/+Z/-Z,之所以會這樣分是因為通常環境貼圖是從HDRI圖像(High Dynamic Range Image,高動態範圍圖像)去拆解出來的。
關於HDRI圖像的介紹可以看這邊
一般的HDRI圖片 | 拆解後 |
---|---|
一般來說,如果想要自己製造HDRI素材,你會需要有一台360度相機,但如果沒有,其實網路上有很多免費下載的資源可以用。
例如這裡:Polyhaven
而若手邊已經有一張合適的HDRI素材,可以用這個免費的線上服務來做拆解。
這邊我把拆解完畢的圖檔上傳到imgur備用
const cubeTextureLoader = new CubeTextureLoader();
const envMap = cubeTextureLoader.load([
'https://i.imgur.com/9wJp0Zy.png', //+X位置的環境貼圖
'https://i.imgur.com/damIWcE.png', //-X位置的環境貼圖
'https://i.imgur.com/mfqMr3m.png', //+Y位置的環境貼圖
'https://i.imgur.com/0dpZmDE.png', //-Y位置的環境貼圖
'https://i.imgur.com/Nj6WcOI.png', //+Z位置的環境貼圖
'https://i.imgur.com/wwLgHqa.png' //-Z位置的環境貼圖
])
...
接著我們把envMap
寫入球和平面的材質建構參數中,並且微調一下反射率/漫反射率。
...
const mat1 = new MeshStandardMaterial({
color: new Color("#eee"),
metalness: 0.8,
roughness: 0.3,
envMap: envMap
});
const mat2 = new MeshStandardMaterial({
color: new Color("#eee"),
metalness: 0.8,
roughness: 0.8,
envMap: envMap
});
嘿~我們辦到了。
codepen 連結: 點我
雖然這邊我們已經達成製作金屬球+金屬平面的目標了,但我們接著其實可以來玩些別的玩意。
例如試著把金屬材質變成木頭材質。
然後可以透過這個免費服務產生基於這個木質紋理的:
Normal Map
AO Map
Height Map
const tl = new TextureLoader();
const woodTexture = tl.load('https://i.imgur.com/jzo9PZI.jpg');
const woodTextureNormal = tl.load('https://i.imgur.com/y60NQGm.png');
const woodTextureAO = tl.load('https://i.imgur.com/jDR50UI.png');
const woodTextureHeight = tl.load('https://i.imgur.com/3fGth9V.png');
...
const mat1 = new MeshStandardMaterial({
map:woodTexture,
normalMap:woodTextureNormal,
aoMap:woodTextureAO,
displacementMap:woodTextureHeight,
color: new Color("#eee"),
metalness: 0.8,
roughness: 0.3,
envMap: envMap
});
const mat2 = new MeshStandardMaterial({
map:woodTexture,
normalMap:woodTextureNormal,
aoMap:woodTextureAO,
displacementMap:woodTextureHeight,
color: new Color("#eee"),
metalness: 0.8,
roughness: 0.8,
envMap: envMap
});
疑? 怎麼變得看起來很詭異XD
我們在上一個步驟會發現幾個詭異的現象,例如球卡進地板裡面/紋路上有看起來很詭異的光澤。
這是因為我們還維持著原本金屬球的反射率等參數,而且也沒有針對傳進來的材質做參數調整,所以我們這邊要著手進行優化。
首先球之所以會卡進去地板裡面,是因為地板也同樣被附加了Height Map
,所以整體的Geometry
頂點都被提高,這邊我們可以透過修改displacementBias
來做微調。
除此之外,這邊我把兩個材質的metalness
設置都拿掉,並且藉由提高roughness
來提升漫反射率,這樣球體表面就不會再反射詭異的光澤。
最後我們把球的材質加上displacementScale
的修正,目的是為了避免球出現模型破裂的狀況。
const mat1 = new MeshStandardMaterial({
map: woodTexture,
normalMap: woodTextureNormal,
aoMap: woodTextureAO,
displacementMap: woodTextureHeight,
color: new Color("#eee"),
roughness: 0.3,
displacementScale: 0.1,
envMap: envMap
});
const mat2 = new MeshStandardMaterial({
map: woodTexture,
normalMap: woodTextureNormal,
aoMap: woodTextureAO,
displacementMap: woodTextureHeight,
color: new Color("#eee"),
roughness: 0.3,
displacementBias: -0.5,
envMap: envMap
});
是不是好多了呢?
codepen連結:點我
今天我們成功的把金屬球做出來了~同時還示範過要怎麼把金屬球換成木頭質感。明天將會是Material章節的最後一篇,我們會介紹一些除了MeshStandardMaterial
以外的材質類。