有看過航海王的人,應該對這句話非常有印象:「你有被光速踢過嗎?」。
身為海軍三大將的黃猿,最能利用光速打敗對手。跟我們的視覺特效一樣,很多都敗給了光——尤其在處理shader的時候。
開發視覺特效的我們,光暈、光的反射、鏡面等等都需要光。即使在比較高階的three.js裡面開發光,無論是DirectionalLight、AmbientLight、RectAreaLight、HemosphereLight、PointLight都一樣,只要是照到Mesh的,都離不開光。及使用WebGL開發也是。以下介紹:
在three.js裡面,光分成很多種,包含:
物體的每一個面都吸到一樣的光。其實就是很像直接調整物件的曝光度,簡單粗暴。
// 新增環境光
const light = new THREE.AmbientLight(0xffffff,1)
scene.add(light)
由一個方向發光。對所有面來說,光的方向都是一樣的
// 新增平行光
const directionalLight = new THREE.DirectionalLight(0xffffff,0.4)
scene.add(directionalLight);
// 新增Helper
const lightHelper = new THREE.DirectionalLightHelper(directionalLight, 20, 0xffff00)
scene.add(lightHelper);
// 更新光源位置
directionalLight.target.position.set(0,0,0);
directionalLight.target.updateMatrixWorld();
// 更新Helper
lightHelper.update();
由一個點發光。對所有面來說,光的方向是不一樣的
// 新增點光
const pointLight = new THREE.PointLight(0xffffff, 0.4)
scene.add(pointLight);
// 更新光源位置
pointLight.position.set(3,3,0)
// 新增Helper
const lightHelper = new THREE.PointLightHelper(pointLight, 20, 0xffff00)
// 更新Helper
lightHelper.update();
由一個面發光。對於被投影到的面來說,光的方向是一樣的。對沒被投影到的面來說,光的方向是不一樣的。另外,這是不支援陰影的光。
// 區域光
const rectLight = new THREE.RectAreaLight( 0xffffff, 0.2, 10, 10 );
scene.add( rectLight )
// 更新光源位置
rectLight.position.set( 5, 5, 0 );
// 新增Helper
const rectLightHelper = new RectAreaLightHelper( rectLight );
rectLight.add( rectLightHelper );
不得不說,把區域光放在場景裡真的很漂亮
光從上下發出。等同天空與地面發出的光。
「為什麼叫半圓球?只是兩方向平行光而已」
// 半球光
const hemisphereLight = new THREE.HemisphereLight( 0xffff99, 0x9999ff, 0.2 );
scene.add( hemisphereLight );
上方是黃色,下方是藍色。黃色可以照到地面,使得場景很像黃昏,藍色又可以照到天球,使得天空還是藍色。
three.js提供這些光,那麼WebGL會有這些光嗎?
如果有的話,這些光子是怎麼投射到物體,然後反射到鏡頭的?
反射到鏡頭之後,又是怎麼投影到多麼多顆像素上呢?
這邊一一解釋:
你現在有一顆球。
假設這球解析度很低,所以這顆球法線長這樣:
然後有一道光,照在球上面:
換句話說,每一個面都指向光源:
光也是一個向量,為了方便計算,我們也給它一個單位向量(長度為一單位的向量),而法線也是:
有了這兩個單位向量,就可以透過內積計算這兩條有多相似。內積要怎麼計算兩個單位向量?可以參考上一篇文章。
光的強度 = 法線的單位向量.向光的單位向量
光的強度 = |法線|*|向光|*cos(θ)
由此,我們可以得知每一個法線有多麼面向光,用一個數值表示。
你看面向光的那一面,其法線單位向量等同於光,所以最亮,經過內積計算唯一。斜面次之,垂直的面則為零。
而這個數值,就可以成為每一個面有多亮的依據了。
Three.js 以我們常用的光,來實作上面這一段。如此一來,你只要調整光,就不用我們親手處理內積。
上面所提及的法線是跟面垂直的線。而3D的世界裡,法線應該要存在哪裡?
法線要由面構成,面是由三角面構成,三角面是透過三個錨點所構成。三個錨點是最原始的資料!因此,我們把「法線」的資料儲存在錨點裡面,於是我們就有了Normal。
「那個向量就叫normal」
事實上,不只three.js,隔壁棚的babylon,連3Ds Max、Maya的Arnold,跟非常多3D渲染的工具都如此。
平行光是這樣的:拿法線跟光的方向來計算內積。那點光呢?點光的概念跟平行光相同,但多了點東西,那就是:在點光中,每個面所朝向的光,其方向都不同。
轉成單位向量之後,即可以看出變化:在平行光時,球體的頭頂的腳底,都是垂直,也就是90度,得出來就是0;可是在點光時,還不到球的頭頂,就已經超過90度了。
而這樣的角度也意味其內積的結果,如圖:
你如果比較前面所提到的平行光,你會發現:原本朝上跟朝下的面跟光源垂直90度(計算結果為0),但在這裡則垂直超過90度,計算結果為負數,也就是說光照射的範圍更小了。
讓我們來想想聚光燈是怎麼產生的。
其實就是具有遮罩的點光。只要傳入參數,使得聚光可以修改可接受的內積範圍,即可控制大小。
DirectionalLight
PointLight
SpotLight
AmbientLight
RectAreaLight
那為什麼我們需要知道?因為在後期,當我們要實作「內光暈」、「外光暈」或是一些特殊的特效時,我們仍需要透過底層的邏輯來實現,身為開發網頁視覺特效的我們不得不知道。而「光」是實作特效的最佳範例,它可是模範生呢!
以上都是比較底層的東西,其實也就是WebGL的計算過程。在WebGL裡面,我們要自己創作光源,得寫一個函式去計算光跟物體normal的關係。
但到了three.js,已經透過WebGL處理掉了光源的製作,並且提供了幾個物件,使我們快速導入光。
雖然人家都包好了,但我們還是得知道以上原理,身為做視覺特效的我們得學會!因為在後面的Shader,我們就得靠自己,不能靠three.js了,不過沒關係,我到時候也會帶大家介紹一遍。
下一篇將介紹實作。我們知道光的原理,勢必得來實作看看。
除此之外,我也將介紹 RectAreaLight 的原理。