Three.js 之 9 Light 光

語言: CN / TW / HK

持續創作,加速成長!這是我參與「掘金日新計劃 · 6 月更文挑戰」的第3天,點選檢視活動詳情

本系列為 Three.js journey 教程學習筆記。包含以下內容

未完待續

lights 光

我們之前學習了簡單的新增光源到場景中。接下來就詳細講講各種各樣的光源、引數以及如何使用。

建立一組幾何體

先建立一組幾何體用於接受光照。使用 MeshStandardMaterial 因為最能真實反饋光效。並將粗糙度設定為 0.4 觀察反射情況。並增加一點環境光 AmbientLight 否則會看不到物體。效果如下

若看不清楚,可以只觀察其 wireframe

完整程式碼如下

```js import * as THREE from 'three' import './style.css' import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' import * as dat from 'lil-gui' import stats from '../common/stats' import { listenResize, dbClkfullScreen } from '../common/utils'

// Canvas const canvas = document.querySelector('#mainCanvas') as HTMLCanvasElement

// Scene const scene = new THREE.Scene()

/* * Objects / // Material const material = new THREE.MeshStandardMaterial() material.metalness = 0 material.roughness = 0.4

// Objects const sphere = new THREE.Mesh(new THREE.SphereGeometry(0.5, 32, 32), material) sphere.position.set(-1.5, 0, 0)

const cube = new THREE.Mesh(new THREE.BoxGeometry(0.75, 0.75, 0.75), material)

const torus = new THREE.Mesh(new THREE.TorusGeometry(0.3, 0.2, 32, 64), material) torus.position.set(1.5, 0, 0)

const plane = new THREE.Mesh(new THREE.PlaneGeometry(5, 5), material) plane.rotation.set(-Math.PI / 2, 0, 0) plane.position.set(0, -0.65, 0)

scene.add(sphere, cube, torus, plane)

/* * Lights / const ambientLight = new THREE.AmbientLight('#ffffff', 0.5) scene.add(ambientLight)

// Size const sizes = { width: window.innerWidth, height: window.innerHeight, }

// Camera const camera = new THREE.PerspectiveCamera(75, sizes.width / sizes.height, 0.1, 100) camera.position.set(1, 1, 2)

const controls = new OrbitControls(camera, canvas) controls.enableDamping = true // controls.autoRotate = true // controls.enabled = false

// Renderer const renderer = new THREE.WebGLRenderer({ canvas, }) renderer.setSize(sizes.width, sizes.height) renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

listenResize(sizes, camera, renderer) dbClkfullScreen(document.body)

// Clock const clock = new THREE.Clock()

// Animations const tick = () => { stats.begin()

const elapsedTime = clock.getElapsedTime()

// Update Objects sphere.rotation.y = 0.1 * elapsedTime cube.rotation.y = 0.1 * elapsedTime torus.rotation.y = 0.1 * elapsedTime

sphere.rotation.x = 0.15 * elapsedTime cube.rotation.x = 0.15 * elapsedTime torus.rotation.x = 0.15 * elapsedTime

controls.update() // Render renderer.render(scene, camera) stats.end() requestAnimationFrame(tick) }

tick()

/* * Debug / const gui = new dat.GUI()

gui.add(material, 'metalness').min(0).max(1).step(0.0001) gui.add(material, 'roughness').min(0).max(1).step(0.0001) gui.add(material, 'wireframe') ```

AmbientLight 環境光

環境光會均勻的照亮場景中的所有物體。

環境光不能用來投射陰影,因為它沒有方向。

AmbientLight 繼承自 Light,因此具有 Light 的公共屬性

Object3D → Light → AmbientLight

因此在建構函式的宣告變數也可以直接在其示例上修改,如下

```js const ambientLight = new THREE.AmbientLight(0xffffff, 0.5) scene.add(ambientLight)

// Equals const ambientLight = new THREE.AmbientLight() ambientLight.color = new THREE.Color(0xffffff) ambientLight.intensity = 0.5 scene.add(ambientLight) ```

可以在 gui 中增加一行觀察環境光強度

js gui.add(ambientLight, 'intensity').min(0).max(1).step(0.001)

在現實世界中,如果使用光照射一個物體,物體的背面不是全黑的,這是因為會有牆面或其他物體反光。但是在 Three.js 中,由於效能問題,沒有反光的特性,所以可以使用微弱的環境光 AmbientLight 來模擬這種反光。

DirectionalLight 平行光

平行光是沿著特定方向發射的光。這種光的表現像是無限遠,從它發出的光線都是平行的。常常用平行光來模擬太陽光 的效果; 太陽足夠遠,因此我們可以認為太陽的位置是無限遠,所以我們認為從太陽發出的光線也都是平行的。平行光可以投射陰影。

我們在 demo 上增加平行光

js const directionalLight = new THREE.DirectionalLight('#ffffaa', 0.5) scene.add(directionalLight)

效果如下

預設平行光是從頂部直射的,我們可以使用 position 屬性設定位置

js const directionalLight = new THREE.DirectionalLight('#ffffaa', 0.5) directionalLight.position.set(1, 0.25, 0) scene.add(directionalLight)

線上 demo 連結

可掃碼訪問

demo 原始碼

可以看到光來自右側。

我們暫時不考慮光線的傳播距離,預設是來自無窮遠,併發散到無窮遠

HemisphereLight 半球光

光源直接放置於場景之上,類似環境光 AmbientLight,但光照顏色從天空光線顏色漸變到地面光線顏色。

半球光不能投射陰影。

js const hemisphereLight = new THREE.HemisphereLight('#B71C1C', '#004D40', 0.6) scene.add(hemisphereLight)

線上 demo 連結

可掃碼訪問

demo 原始碼

PointLight 點光源

從一個點向各個方向發射的光源。一個常見的例子是模擬一個燈泡發出的光。該光源可以投射陰影。

其特點是光源無線小,光線向各個方向傳播。 - 第一個引數 color 是顏色 - intensity 是強度。 - distance 這個距離表示從光源到光照強度為0的位置。當設定為0時,光永遠不會消失(距離無窮大)。預設值 0. - decay 沿著光照距離的衰退量。預設值 1。 在 physically correct 模式中,decay = 2。

js PointLight( color : Integer, intensity : Float, distance : Number, decay : Float )

js const pointLight = new THREE.PointLight(0xff9000, 0.5) pointLight.position.set(1, 1, 1) scene.add(pointLight)

gui 增加調節 distance 和 decay 的程式碼如下

js pointLightFolder.add(pointLight, 'distance', 0, 100, 0.00001) pointLightFolder.add(pointLight, 'decay', 0, 10, 0.00001)

RectAreaLight 平面光光源

平面光光源從一個矩形平面上均勻地發射光線。這種光源可以用來模擬像明亮的窗戶或者條狀燈光光源。它混合了平行光與發散光。

不支援陰影。只支援 MeshStandardMaterial 和 MeshPhysicalMaterial 兩種材質。

js RectAreaLight( color : Integer, intensity : Float, width : Float, height : Float )

  • color - (可選引數) 十六進位制數字表示的光照顏色。預設值為 0xffffff (白色)
  • intensity - (可選引數) 光源強度/亮度 。預設值為 1。
  • width - (可選引數) 光源寬度。預設值為 10。
  • height - (可選引數) 光源高度。預設值為 10。

js const rectAreaLight = new THREE.RectAreaLight(0x4e00ff, 10, 1, 1) rectAreaLight.position.set(-1.5, 0, 1.5) rectAreaLight.lookAt(new THREE.Vector3()) scene.add(rectAreaLight)

效果如下

開關 helper 效果如下

線上 demo 連結

可掃碼訪問

demo 原始碼

SpotLight 聚光燈

光線從一個點沿一個方向射出,隨著光線照射的變遠,光線圓錐體的尺寸也逐漸增大。該光源可以投射陰影。

建構函式

js SpotLight( color : Integer, intensity : Float, distance : Float, angle : Radians, penumbra : Float, decay : Float )

  • color - (可選引數) 十六進位制光照顏色。 預設值 0xffffff (白色)。
  • intensity - (可選引數) 光照強度。 預設值 1。
  • distance - 從光源發出光的最大距離,其強度根據光源的距離線性衰減。
  • angle - 光線散射角度,最大為Math.PI/2。
  • penumbra - 聚光錐的半影衰減百分比。在0和1之間的值。預設為0。
  • decay - 沿著光照距離的衰減量。

js const spotLight = new THREE.SpotLight(0x78ff00, 0.5, 10, Math.PI * 0.1, 0.25, 1) spotLight.position.set(0, 2, 3) scene.add(spotLight)

intensity 與 distance 調節

angle 調節

penumbra(半影衰減) 調節

decay 調節

效能考慮

光照效果很好,但是會非常消耗效能。GPU 會對其進行大量計算。

Minimal cost:

  • AmbientLight
  • HemisphereLight

Moderate cost:

  • DirectionalLight
  • PointLight

High cost:

  • SpotLight
  • RectAreaLight

所以要儘量少的新增燈光,就會帶來更好的效能。想要少的燈光,但又想有很好的光效該怎麼辦呢?可以考慮 Baking 光照的方案。

Baking 烘焙光照

原理是將光照烘焙到貼圖紋理(Texture)中,這個過程可以在 3D 建模軟體中實現。但不足的是,不能移動光線,因為根本沒有光,都是再貼圖紋理中。後續會深入學習。

小結

本節學習了 Three.js 內建的所有光效,並學習了其 Helper 的使用。瞭解了其效能的排序,以及光照烘焙的方案。下一節將學習投影。