THREE.js 光照 - 1

44 阅读3分钟

前置补充

下方为基础代码, 在此基础上演示光照

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 );

image.png


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);

image.png


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();

image.png

为了更直观的可以看到方向光的起始位置以及朝向, 可以使用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();
  }

image.png