前置补充
下方为基础代码, 在此基础上演示光照
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { GUI } from "three/addons/libs/lil-gui.module.min.js";
function main() {
const canvas = document.querySelector("#c");
const renderer = new THREE.WebGLRenderer({ antialias: true, canvas });
const fov = 45;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 100;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
camera.position.set(0, 10, 20);
const controls = new OrbitControls(camera, canvas);
controls.target.set(0, 5, 0);
controls.update();
const scene = new THREE.Scene();
scene.background = new THREE.Color("black");
{
const planeSize = 40;
const loader = new THREE.TextureLoader();
const texture = loader.load("https://threejs.org/manual/examples/resources/images/checker.png");
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.magFilter = THREE.NearestFilter;
texture.colorSpace = THREE.SRGBColorSpace;
const repeats = planeSize / 2;
texture.repeat.set(repeats, repeats);
const planeGeo = new THREE.PlaneGeometry(planeSize, planeSize);
const planeMat = new THREE.MeshPhongMaterial({
map: texture,
side: THREE.DoubleSide,
});
const mesh = new THREE.Mesh(planeGeo, planeMat);
mesh.rotation.x = Math.PI * -0.5;
scene.add(mesh);
}
{
const cubeSize = 4;
const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
const cubeMat = new THREE.MeshPhongMaterial({ 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.MeshPhongMaterial({ color: "#CA8" });
const mesh = new THREE.Mesh(sphereGeo, sphereMat);
mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0);
scene.add(mesh);
}
class ColorGUIHelper {
constructor(object, prop) {
this.object = object;
this.prop = prop;
}
get value() {
return `${this.object[this.prop].getHexString()}`;
}
set value(hexString) {
this.object[this.prop].set(hexString);
}
}
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);
}
requestAnimationFrame(render);
}
main();
AmbientLight 环境光
最简单的光源, 没有方向, 无法产生阴影
均匀地照亮场景中的所有物体
除了改变场景内所有物体颜色以外, 不会使物体产生明暗的变化
通常用来提亮场景, 让暗部不要太暗
// 环境光颜色
const color = 0xFFFFFF;
// 光照强度
const intensity = 1;
// 创建环境光, 添加到场景中
const light = new THREE.AmbientLight( color, intensity );
scene.add( light );
// 在 gui 中添加对应属性控制
const gui = new GUI();
gui.addColor( new ColorGUIHelper( light, 'color' ), 'value' ).name( 'color' );
gui.add( light, 'intensity', 0, 5, 0.01 );
HemisphereLight半球光
无阴影, 模拟天空和地面自然光照的特殊光源, 常用于创建户外场景的柔和环境光效果
从 天空方向(上) 和 地面方向(下) 各发出一种颜色, 在两者之间平滑过渡, 模拟真实事件中的天空散射光.
一般会和其他光照配合使用, 可用于环境光的一种替代方案
// 天空方向颜色
const skyColor = 0xB1E1FF; // light blue
// 地面方向颜色
const groundColor = 0xB97A20; // brownish orange
// 光照强度
const intensity = 1;
// 创建并添加
const light = new THREE.HemisphereLight(skyColor, groundColor, intensity);
scene.add(light);
const gui = new GUI();
// 将 半球光的属性控制单独放到一个 gui 的文件夹中, 方便管理
const hemisphereFolder = gui.addFolder("半球光控制");
hemisphereFolder.addColor(new ColorGUIHelper(hemisphereLight, "color"), "value").name("skyColor");
hemisphereFolder
.addColor(new ColorGUIHelper(hemisphereLight, "groundColor"), "value")
.name("groundColor");
hemisphereFolder.add(hemisphereLight, "intensity", 0, 5, 0.01);
DirectionalLight 方向光
有阴影, 常用来模拟太阳光照射
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);
// gui 添加属性控制
const directionLightFolder = gui.addFolder("方向光控制");
directionLightFolder.open();
directionLightFolder
.addColor(new ColorGUIHelper(directionalLight, "color"), "value")
.name("color");
directionLightFolder.add(directionalLight.position, "x", -10, 10);
directionLightFolder.add(directionalLight.position, "y", 0, 10);
directionLightFolder.add(directionalLight.position, "z", -10, 10);
directionLightFolder.add(directionalLight.target.position, "x", -10, 10).name("target x");
directionLightFolder.add(directionalLight.target.position, "y", 0, 10).name("target y");
directionLightFolder.add(directionalLight.target.position, "z", -10, 10).name("target z");
directionLightFolder.close();
为了更直观的可以看到方向光的起始位置以及朝向, 可以使用DirectionalLightHelper 它会绘制一个方形的小平面代表方向光的位置,一条连接光源与目标点的直线,代表了光的方向
const helper = new THREE.DirectionalLightHelper(light);
scene.add(helper);
同时修改gui 中的方向光属性控制, 添加对起始位置和target 的值改变的反应函数
directionLightFolder.add(directionalLight.position, "x", -10, 10).onChange(updateLight);
directionLightFolder.add(directionalLight.position, "y", 0, 10).onChange(updateLight);
directionLightFolder.add(directionalLight.position, "z", -10, 10).onChange(updateLight);
directionLightFolder
.add(directionalLight.target.position, "x", -10, 10)
.name("target x")
.onChange(updateLight);
directionLightFolder
.add(directionalLight.target.position, "y", 0, 10)
.name("target y")
.onChange(updateLight);
directionLightFolder
.add(directionalLight.target.position, "z", -10, 10)
.name("target z")
.onChange(updateLight);
directionLightFolder.close();
// 当 position / target 值变化的时候, 会执行 updateLight 函数
// 实现实时更新 helper 的位置
function updateLight() {
directionalLight.target.updateMatrixWorld();
dLightHelper.update();
}