Skip to main content

Lights

此筆記對應官方文件 Lights 章節

在開始之前我們先在 Three 環境內加上 OrbitControls 讓我們能用滑鼠拖曳調整相機視角。

import {OrbitControls} from '/examples/jsm/controls/OrbitControls.js';
controls.target.set(0, 5, 0);
controls.update();

OrbitControls 有個 target 中心點,當我們拖拉調整視角時,都是圍繞在這個中心點。

另外有相關屬性變更時也要呼叫 controls.update() 讓更新生效。

其餘部分可依照官方文件所述建置一個方便我們觀察各種 Lights 的環境。

AmbientLight

const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.AmbientLight(color, intensity);
scene.add(light);

最不像真實光的光 XD,intensity 是設置光的強度,把 AmbientLight 加進場景後,此光會平等的照亮所有地方,故物件也沒有陰影相關的顏色變化。

HemisphereLight

const skyColor = 0xA1EE11;
const groundColor = 0xB1A1FF;
const intensity = 1;
const light = new THREE.HemisphereLight(skyColor, groundColor, intensity);
scene.add(light);

相比 AmbientLightHemisphereLight 多了設置天地色,會將物件的顏色在天地色之間做倍增 (multiplies)。

物件朝上的面會被 skyColor 影響較多,反之朝下的面會被 groundColor 影響較多。

DirectionalLight

此光源經常拿來模擬太陽光,會從光的位置朝 target 射出無窮距離的光線。

const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(0, 10, 0);
light.target.position.set(-5, 0, 0);
scene.add(light);
scene.add(light.target);

此光源多了 target 屬性,代表光源會朝向 target 的位置照射。

LightHelper

DirectionalLight 這種光在場景中沒辦法直接看到實體位置,故我們可以引入 DirectionalLightHelper 幫助我們可看到光源的輔助線

const helper = new THREE.DirectionalLightHelper(light);
scene.add(helper);

另外要提到的是,若是像官方文件這樣需要動態變動 LightHelper 的位置的狀況時,記得要呼叫 helper.update() 讓更新生效。

PointLight

一種點光源,會從該點向四面八方射出光線,所以沒有 target

const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.PointLight(color, intensity);
light.position.set(0, 10, 0);
scene.add(light);

const helper = new THREE.PointLightHelper(light);
scene.add(helper);

PointLight 有個 distance 屬性,預設是 0,代表光源影響的極限距離為無窮遠,而大於 0 時代表光源影響至該距離為止,不管距離為何,離光源越遠,亮度皆會減弱。

SpotLight

const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.SpotLight(color, intensity);
scene.add(light);
scene.add(light.target);

const helper = new THREE.SpotLightHelper(light);
scene.add(helper);

聚光燈,照射範圍從光源位置呈現出圓錐狀,由於不是朝四面八方散射,所以需要 target 來決定要朝哪邊聚光。

實際上有兩個同心圓錐,外層和內層,外層至內層的範圍光線強度會從 intensity 遞減到 0。

其他屬性還有:

distance

決定圓錐的高度,跟 SpotLightdistance 作用一樣,都是決定光最遠能照射的距離。

angle

決定圓錐狀的散射角度

penumbra

決定外內層的距離,0 代表內外層重疊,此時在光照範圍的邊緣會有很明顯的色差。

1 代表從圓錐中心點開始亮度衰減。

0.5 代表從圓錐中心點與外層圓錐邊緣的中心開始亮度衰減。

RectAreaLight

RectAreaLight 只會在 MeshStandardMaterialMeshPhysicalMaterial 上有效果。同時此光源有 widthheight 屬性定義光源本身的長寬,效果很像辦公室或教室天花板的矩形平面燈。

官方文檔在這邊引入了 RectAreaLightUniformsLib 原因是 If you forget the data the light will still work but it will look funny so be sure to remember to include the extra data.

個人好奇會有多 funny 所以測試了一下,以下程式碼為沒有 RectAreaLightUniformsLib 的版本:

import * as THREE from 'three'
import './style.css'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import {RectAreaLightUniformsLib} from 'three/examples/jsm/lights/RectAreaLightUniformsLib.js';
import {RectAreaLightHelper} from 'three/examples/jsm/helpers/RectAreaLightHelper.js';

const scene = new THREE.Scene();

{
const planeSize = 40;
const loader = new THREE.TextureLoader();
const texture = loader.load('checker.png');
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.magFilter = THREE.NearestFilter;
const repeats = planeSize / 2;
texture.repeat.set(repeats, repeats);

const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
const planeMat = new THREE.MeshStandardMaterial({
map: texture,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(planeGeo, planeMat);
mesh.rotation.x = Math.PI * -.5;
scene.add(mesh);
}

{
const cubeSize = 4;
const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
const cubeMat = new THREE.MeshStandardMaterial({color: '#8AC'});
const mesh = new THREE.Mesh(cubeGeo, cubeMat);
mesh.position.set(cubeSize + 1, cubeSize / 2, 0);
scene.add(mesh);
}

{
const sphereRadius = 3;
const sphereWidthDivisions = 32;
const sphereHeightDivisions = 16;
const sphereGeo = new THREE.SphereGeometry(sphereRadius, sphereWidthDivisions, sphereHeightDivisions);
const sphereMat = new THREE.MeshStandardMaterial({color: '#CA8'});
const mesh = new THREE.Mesh(sphereGeo, sphereMat);
mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0);
scene.add(mesh);
}

{
const color = 0xFFFFFF;
const intensity = 5;
const width = 12;
const height = 4;
const light = new THREE.RectAreaLight(color, intensity, width, height);
light.position.set(0, 10, 0);
light.rotation.x = THREE.MathUtils.degToRad(-90);
scene.add(light);

const helper = new RectAreaLightHelper(light);
light.add(helper);
}

const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas: canvas, antialias: true});
renderer.setPixelRatio(window.devicePixelRatio)

const camera = new THREE.PerspectiveCamera(40, canvas.offsetWidth / canvas.offsetHeight, 0.1, 100);
camera.position.set(0, 10, 20);

const controls = new OrbitControls(camera, canvas);
controls.target.set(0, 5, 0);
controls.update();

function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}

function render() {
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}

renderer.render(scene, camera);

requestAnimationFrame(render);
}
render();

rect-area-light

實際看起來還好 XD,看不出有哪裡怪的地方。

接著我們也引入該 RectAreaLightUniformsLib 且執行 init

+ import {RectAreaLightUniformsLib} from '/examples/jsm/lights/RectAreaLightUniformsLib.js';
...
+ RectAreaLightUniformsLib.init();

加完這段後實際看也看不出哪裡有變化 XD。

RectAreaLight 沒有 target,取而代之的是 rotation,用來定義光線要朝哪個角度照。

physicallyCorrectLights

WebGLRenderer 底下有個 physicallyCorrectLights 屬性,預設是 false 不會啟用,一旦啟用將會依照真實物理光計算光隨距離衰減的變化,只會影響 PointLightSpotLightRectAreaLight 則本身就是這樣計算了。

+ renderer.physicallyCorrectLights = true;

而啟用此計算方式後,會有兩個屬性可設置來模擬真實物理光源:powerdecay

power 表示此光源的瓦數,像真實電燈泡那樣。 decay 表示隨距離減弱多少。

Performance

要注意場景中的每個光源都會減慢 three.js 渲染場景的速度,因此應該在達成目標效果內盡量使用少量的光源。