three.js基础之简单动画、GUI、Controls、相机、灯光、简单材质

本系列中案例参考自 https://github.com/josdirksen/learning-threejs-third

动画

<canvas id="mainCanvas"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { TrackballControls } from "three/addons/controls/TrackballControls.js";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight, addPlane, addBox, addSphere } from "./init.js";

  const gui = new GUI();

  function init() {
    window.addEventListener("resize", onResize, false);

    const renderer = initRenderer("mainCanvas");

    const scene = new THREE.Scene();

    const camera = initPerspectiveCamera();
    scene.add(camera);

    const ambientLight = initAmbientLight();
    scene.add(ambientLight);

    const spotLight = initSpotLight();
    scene.add(spotLight);

    const plane = addPlane();
    scene.add(plane);

    const cube = addBox({ color: 0xff0000 });
    cube.position.set(-10, 5, 0);
    scene.add(cube);

    const sphere = addSphere({ color: 0x7777ff });
    sphere.position.set(20, 5, 0);
    scene.add(sphere);

    let step = 0;
    const controls = new (function () {
      this.rotationSpeed = 0.02;
      this.bouncingSpeed = 0.03;
    })();
    gui.add(controls, "rotationSpeed", 0, 0.5);
    gui.add(controls, "bouncingSpeed", 0, 0.5);

    const trackballControls = new TrackballControls(camera, renderer.domElement);
    const clock = new THREE.Clock();

    render();

    function render() {
      trackballControls.update(clock.getDelta());

      cube.rotation.x += controls.rotationSpeed;
      cube.rotation.y += controls.rotationSpeed;
      cube.rotation.z += controls.rotationSpeed;

      step += controls.bouncingSpeed;
      sphere.position.x = 20 + 10 * Math.cos(step);
      sphere.position.y = 2 + 10 * Math.abs(Math.sin(step));

      requestAnimationFrame(render);
      renderer.render(scene, camera);
    }

    function onResize() {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    }
  }

  init();
</script>

GUI

gui用于调节控制各种物体的参数,结合动画使用非常有用

<canvas id="mainCanvas"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { TrackballControls } from "three/addons/controls/TrackballControls.js";
  import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight, addPlane, addBox } from "./init.js";

  function init() {
    const gui = new GUI();

    const renderer = initRenderer("mainCanvas");

    const scene = new THREE.Scene();

    const camera = initPerspectiveCamera();
    scene.add(camera);

    const ambientLight = initAmbientLight();
    scene.add(ambientLight);
    const gui_ambientLight = gui.addFolder("AmbientLight");
    gui_ambientLight.add(ambientLight, "intensity", 0, 10);
    const ambientLight_controls = new (function () {
      this.ambientColor = ambientLight.color.getStyle();
      this.disableSpotlight = false;
    })();
    gui_ambientLight.addColor(ambientLight_controls, "ambientColor").onChange(function (e) {
      ambientLight.color = new THREE.Color(ambientLight_controls.ambientColor);
    });
    gui_ambientLight.add(ambientLight_controls, "disableSpotlight").onChange(function (e) {
      spotLight.visible = !e;
    });

    const spotLight = initSpotLight();
    spotLight.position.set(-40, 60, -10);
    scene.add(spotLight);
    const gui_spotLight = gui.addFolder("SpotLight");
    gui_spotLight.add(spotLight, "intensity", 0, 200);

    const plane = addPlane();
    scene.add(plane);

    const box = addBox({ color: 0xff0000 });
    box.position.set(-10, 5, 0);
    scene.add(box);
    const gui_box = gui.addFolder("box");
    gui_box.add(box.position, "x", -20, 20);
    gui_box.add(box.position, "y", 10, 50);
    gui_box.add(box.position, "z", -20, 20);
    const obj = {
      color: box.material.color.getStyle(),
    };
    gui_box.addColor(obj, "color").onChange(function (value) {
      box.material.color.set(value);
    });

    const step = 0;

    const controls = new (function () {
      this.rotationSpeed = 0.02;
      this.numberOfObjects = scene.children.length;

      this.removeCube = function () {
        const allChildren = scene.children;
        const lastObject = allChildren[allChildren.length - 1];
        if (lastObject instanceof THREE.Mesh) {
          scene.remove(lastObject);
          this.numberOfObjects = scene.children.length;
        }
      };

      this.addCube = function () {
        const cubeSize = Math.ceil(Math.random() * 3);
        const cubeGeometry = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize);
        const cubeMaterial = new THREE.MeshLambertMaterial({
          color: Math.random() * 0xffffff,
        });
        const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
        cube.castShadow = true;

        cube.position.x = -30 + Math.round(Math.random() * planeGeometry.parameters.width);
        cube.position.y = Math.round(Math.random() * 5);
        cube.position.z = -20 + Math.round(Math.random() * planeGeometry.parameters.height);

        scene.add(cube);
        this.numberOfObjects = scene.children.length;
      };

      this.outputObjects = function () {
        console.log(scene.children);
      };
    })();
    const gui_opts = gui.addFolder("opts");
    gui_opts.add(controls, "rotationSpeed", 0, 0.5);
    gui_opts.add(controls, "addCube");
    gui_opts.add(controls, "removeCube");
    gui_opts.add(controls, "outputObjects");
    gui_opts.add(controls, "numberOfObjects").listen();

    const trackballControls = new TrackballControls(camera, renderer.domElement);
    const clock = new THREE.Clock();

    render();

    function render() {
      const spt = clock.getDelta() * 1000; //毫秒

      trackballControls.update(clock.getDelta());

      scene.traverse(function (e) {
        if (e instanceof THREE.Mesh && e != plane) {
          e.rotation.x += controls.rotationSpeed;
          e.rotation.y += controls.rotationSpeed;
          e.rotation.z += controls.rotationSpeed;
        }
      });

      requestAnimationFrame(render);
      renderer.render(scene, camera);
    }
  }

  init();
</script>

Controls

<canvas id="mainCanvas"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { OrbitControls } from "three/addons/controls/OrbitControls.js";
  import { TrackballControls } from "three/addons/controls/TrackballControls.js";
  import { FlyControls } from "three/addons/controls/FlyControls.js";
  import { FirstPersonControls } from "three/addons/controls/FirstPersonControls.js";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight, addPlane, addBox, addSphere, addCylinder } from "./init.js";

  function init() {
    const gui = new GUI();

    const renderer = initRenderer("mainCanvas");

    const scene = new THREE.Scene();

    const camera = initPerspectiveCamera();
    scene.add(camera);

    const ambientLight = initAmbientLight();
    scene.add(ambientLight);

    const spotLight = initSpotLight();
    scene.add(spotLight);

    const plane = addPlane();
    scene.add(plane);

    const cube = addBox({ color: 0xff0000, width: 4, height: 4, depth: 4 });
    cube.position.set(-10, 5, 0);
    scene.add(cube);

    const sphere = addSphere({ color: 0x7777ff, radius: 4 });
    sphere.position.set(20, 5, 0);
    scene.add(sphere);

    const cylinder = addCylinder({ color: 0x77ff77, tr: 2, br: 2, height: 20 });
    scene.add(cylinder);

    const clock = new THREE.Clock();

    // 轨道控制器(OrbitControls)
    // Orbit controls(轨道控制器)可以使得相机围绕目标进行轨道运动。
    const orbitControls = new OrbitControls(camera, renderer.domElement);
    orbitControls.autoRotate = true;

    // 轨迹球控制器(TrackballControls)
    // TrackballControls 与 OrbitControls 相类似。然而,它不能恒定保持摄像机的up向量。
    // 这意味着,如果摄像机绕过“北极”和“南极”,则不会翻转以保持“右侧朝上”。
    const trackballControls = new TrackballControls(camera, renderer.domElement);

    // 飞行控制器(FlyControls)飞行模式的导航方式。
    // 你可以在3D空间中任意变换摄像机,并且无任何限制
    const flyControls = new FlyControls(camera, renderer.domElement);
    flyControls.movementSpeed = 25;
    flyControls.rollSpeed = Math.PI / 24;
    flyControls.autoForward = true;
    flyControls.dragToLook = false;

    // FirstPersonControls
    const fpControls = new FirstPersonControls(camera, renderer.domElement);
    fpControls.lookSpeed = 0.4;
    fpControls.movementSpeed = 20;
    fpControls.lookVertical = true;
    fpControls.constrainVertical = true;
    fpControls.verticalMin = 1.0;
    fpControls.verticalMax = 2.0;
    fpControls.lon = -150;
    fpControls.lat = 120;

    const controls = {
      selectedControl: "orbit",
      control: orbitControls,
    };
    gui.add(controls, "selectedControl", ["orbit", "trackball", "fly", "first-person"]).onChange(function (e) {
      switch (e) {
        case "orbit":
          controls.control = orbitControls;
          break;
        case "trackball":
          controls.control = trackballControls;
          break;
        case "fly":
          controls.control = flyControls;
          break;
        case "first-person":
          controls.control = fpControls;
          break;
      }
    });

    render();

    function render() {
      controls.control.update(clock.getDelta());

      requestAnimationFrame(render);
      renderer.render(scene, camera);
    }
  }

  init();
</script>

相机

<canvas id="OrthographicCamera"></canvas>
<canvas id="PerspectiveCamera"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { initRenderer, initAmbientLight, initDirectionalLight, addPlane, addBox } from "./init.js";

  let resize_Orthographic;
  let resize_Perspective;
  const width = 500;
  const height = 400;

  const gui = new GUI();

  {
    const renderer = initRenderer("OrthographicCamera", width, height);

    const scene = new THREE.Scene();

    const ambientLight = initAmbientLight();
    scene.add(ambientLight);

    const directionalLight = initDirectionalLight();
    scene.add(directionalLight);

    // 正交相机,无论物体距离相机距离远或者近,在最终渲染的图片中物体的大小都保持不变。  这对于渲染2D场景或者UI元素是非常有用的。
    // OrthographicCamera( left : Number, right : Number, top : Number, bottom : Number, near : Number, far : Number )
    // left — 摄像机视锥体左侧面
    // right — 摄像机视锥体右侧面
    // top — 摄像机视锥体上侧面
    // bottom — 摄像机视锥体下侧面
    // near — 摄像机视锥体近端面
    // far — 摄像机视锥体远端面
    const obj = {
      N: 4,
    };
    const camera = new THREE.OrthographicCamera(width / -obj.N, width / obj.N, height / obj.N, height / -obj.N, 1, 1000);
    camera.position.set(-30, 30, 30);
    camera.lookAt(0, 0, 0);
    scene.add(camera);
    const gui_orthographic = gui.addFolder("Orthographic");
    gui_orthographic.add(camera.position, "x", -30, 30);
    gui_orthographic.add(camera.position, "x", -30, 30);
    gui_orthographic.add(camera.position, "y", -30, 30);
    gui_orthographic.add(obj, "N", 2, 50).onChange(function (value) {
      camera.left = width / -value;
      camera.right = width / value;
      camera.top = height / value;
      camera.bottom = height / -value;
      camera.updateProjectionMatrix();

      console.log(obj, value);
    });
    gui_orthographic.add(camera, "left", width / -2, 0);
    gui_orthographic.add(camera, "right", 0, width / 2);
    gui_orthographic.add(camera, "top", 0, height / 2);
    gui_orthographic.add(camera, "bottom", height / -2, 0);
    gui_orthographic.add(camera, "near", 0.1, 10);
    gui_orthographic.add(camera, "far", 500, 5000);

    const axesHelper = new THREE.AxesHelper(150);
    scene.add(axesHelper);

    const plane = addPlane();
    scene.add(plane);

    const cube = addBox({ color: 0x44ff44 });
    scene.add(cube);

    render();

    function render() {
      camera.updateProjectionMatrix(); //相机参数更新

      requestAnimationFrame(render);
      renderer.render(scene, camera);
    }

    resize_Orthographic = function () {
      const width = 400;
      const height = 400;
      renderer.setSize(width, height); //更新Canvas画布尺寸

      camera.left = width / -obj.N;
      camera.right = width / obj.N;
      camera.top = height / -obj.N;
      camera.bottom = height / obj.N;
      camera.updateProjectionMatrix(); //相机参数更新
    };
  }

  {
    const renderer = initRenderer("PerspectiveCamera", width, height);

    const scene = new THREE.Scene();

    const ambientLight = initAmbientLight();
    scene.add(ambientLight);

    const directionalLight = initDirectionalLight();
    scene.add(directionalLight);

    // 透视相机被用来模拟人眼所看到的景象,它是3D场景的渲染中使用得最普遍的投影模式。
    // PerspectiveCamera( fov : Number, aspect : Number, near : Number, far : Number )
    // fov — 摄像机视锥体垂直视野角度
    // aspect — 摄像机视锥体长宽比
    // near — 摄像机视锥体近端面
    // far — 摄像机视锥体远端面
    const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000); //透视投影相机参数设置
    camera.position.set(-30, 30, 30);
    camera.lookAt(0, 0, 0);
    scene.add(camera);
    const gui_perspective = gui.addFolder("Perspective");
    gui_perspective.add(camera.position, "x", -30, 30);
    gui_perspective.add(camera.position, "y", -30, 30);
    gui_perspective.add(camera.position, "z", -30, 30);
    gui_perspective.add(camera, "fov", 0, 90);
    gui_perspective.add(camera, "aspect", 0.1, 5);
    gui_perspective.add(camera, "near", 0.1, 10);
    gui_perspective.add(camera, "far", 500, 5000);

    const axesHelper = new THREE.AxesHelper(150);
    scene.add(axesHelper);

    const plane = addPlane();
    scene.add(plane);

    const cube = addBox({ color: 0x44ff44 });
    scene.add(cube);

    render();

    function render() {
      camera.updateProjectionMatrix(); //相机参数更新

      requestAnimationFrame(render);
      renderer.render(scene, camera);
    }

    resize_Perspective = function () {
      const width = 400;
      const height = 400;
      renderer.setSize(width, height); //更新Canvas画布尺寸

      camera.aspect = width / height;
      camera.updateProjectionMatrix(); //相机参数更新
    };
  }

  window.addEventListener("resize", resize_Orthographic);
  window.addEventListener("resize", resize_Perspective);
</script>

例子

<canvas id="mainCanvas"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { TrackballControls } from "three/addons/controls/TrackballControls.js";
  import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight, addPlane } from "./init.js";

  const renderer = initRenderer("mainCanvas");

  const scene = new THREE.Scene();

  let camera = initPerspectiveCamera();
  scene.add(camera);

  const ambientLight = initAmbientLight();
  scene.add(ambientLight);

  const spotLight = initSpotLight();
  scene.add(spotLight);

  const width = 30;
  const height = 30;
  const plane = addPlane({ width: width, height: height });
  scene.add(plane);

  const cubeGeometry = new THREE.BoxGeometry(4, 4, 4);
  for (let j = 0; j < height / 5; j++) {
    for (let i = 0; i < width / 5; i++) {
      const rnd = Math.random() * 0.75 + 0.25;
      const cubeMaterial = new THREE.MeshLambertMaterial();
      cubeMaterial.color = new THREE.Color(rnd, 0, 0);
      const cube = new THREE.Mesh(cubeGeometry, cubeMaterial);

      cube.position.z = -(height / 2) + 2 + j * 5;
      cube.position.x = -(width / 2) + 2 + i * 5;
      cube.position.y = 2;

      scene.add(cube);
    }
  }

  const controls = new (function () {
    this.perspective = "Perspective";
    this.switchCamera = function () {
      if (camera instanceof THREE.PerspectiveCamera) {
        camera = new THREE.OrthographicCamera(window.innerWidth / -16, window.innerWidth / 16, window.innerHeight / 16, window.innerHeight / -16, -200, 500);
        camera.position.set(-30, 30, 30);
        camera.lookAt(scene.position);

        trackballControls = new TrackballControls(camera, renderer.domElement);
        this.perspective = "Orthographic";
      } else {
        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.set(-30, 30, 30);
        camera.lookAt(scene.position);

        trackballControls = new TrackballControls(camera, renderer.domElement);
        this.perspective = "Perspective";
      }
    };
  })();

  const gui = new GUI();
  gui.add(controls, "switchCamera");
  gui.add(controls, "perspective").listen();

  let trackballControls = new TrackballControls(camera, renderer.domElement);
  const clock = new THREE.Clock();
  let step = 0;

  render();

  function render() {
    trackballControls.update(clock.getDelta());

    // step += 0.02;
    // if (camera instanceof THREE.Camera) {
    //   const x = 10 + 100 * Math.sin(step);
    //   camera.lookAt(new THREE.Vector3(x, 10, 0));
    // }

    requestAnimationFrame(render);
    renderer.render(scene, camera);
  }
</script>

ArrayCamera

<canvas id="mainCanvas"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import Stats from "three/addons/libs/stats.module.js";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { TrackballControls } from "three/addons/controls/TrackballControls.js";
  import { initRenderer, initPerspectiveCamera, initDirectionalLight, addPlane, addCylinder } from "./init.js";

  function init() {
    const renderer = initRenderer("mainCanvas");

    const scene = new THREE.Scene();

    const dirLight = initDirectionalLight();
    scene.add(dirLight);
    dirLight.position.set(0.5, 0.5, 1);
    dirLight.shadow.camera.zoom = 4;

    const AMOUNT = 4;
    const ASPECT_RATIO = window.innerWidth / window.innerHeight;
    const WIDTH = (window.innerWidth / AMOUNT) * window.devicePixelRatio;
    const HEIGHT = (window.innerHeight / AMOUNT) * window.devicePixelRatio;
    const cameras = [];
    for (let y = 0; y < AMOUNT; y++) {
      for (let x = 0; x < AMOUNT; x++) {
        const subcamera = new THREE.PerspectiveCamera(40, ASPECT_RATIO, 0.1, 10);
        subcamera.viewport = new THREE.Vector4(Math.floor(x * WIDTH), Math.floor(y * HEIGHT), Math.ceil(WIDTH), Math.ceil(HEIGHT));
        subcamera.position.x = x / AMOUNT - 0.5;
        subcamera.position.y = 0.5 - y / AMOUNT;
        subcamera.position.z = 1.5;
        subcamera.position.multiplyScalar(2);
        subcamera.lookAt(0, 0, 0);
        subcamera.updateMatrixWorld();
        cameras.push(subcamera);
      }
    }
    const camera = new THREE.ArrayCamera(cameras);
    camera.position.z = 3;
    scene.add(camera);

    const plane = addPlane({ width: 100, height: 100, color: 0x999999 });
    plane.rotation.x = 0;
    plane.position.set(0, 0, -1);
    scene.add(plane);

    const cylinder = addCylinder({ tr: 0.5, br: 0.5, height: 1 });
    cylinder.receiveShadow = true;
    scene.add(cylinder);

    const trackballControls = new TrackballControls(camera, renderer.domElement);
    const clock = new THREE.Clock();

    const axesHelper = new THREE.AxesHelper(150);
    scene.add(axesHelper);

    const stats = new Stats();
    document.body.appendChild(stats.dom);

    render();

    function render() {
      trackballControls.update(clock.getDelta());

      requestAnimationFrame(render);
      stats.begin();
      renderer.render(scene, camera);
      stats.end();
    }
  }

  init();
</script>

灯光

AmbientLight

<canvas id="canvas-ambient-light"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { initRenderer, initPerspectiveCamera, addPlane, addBox } from "./init.js";

  const gui = new GUI();

  const renderer = initRenderer("canvas-ambient-light");

  const scene = new THREE.Scene();

  const camera = initPerspectiveCamera();
  scene.add(camera);

  //环境光:环境光会均匀的照亮场景中的所有物体。 不能用来投射阴影,因为它没有方向。
  // AmbientLight( color : Color, intensity : Float )
  //参数:光源颜色、光照强度
  const ambient = new THREE.AmbientLight(0xffffff, 1);
  scene.add(ambient);
  gui.add(ambient, "intensity", 0, 20).name("环境光强度");

  const axesHelper = new THREE.AxesHelper(150);
  scene.add(axesHelper);

  const plane = addPlane();
  scene.add(plane);

  const cube = addBox({ color: 0xff0000 });
  scene.add(cube);

  render();

  function render() {
    requestAnimationFrame(render);
    renderer.render(scene, camera);
  }
</script>

DirectionalLight

<canvas id="canvas-directional-light"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { initRenderer, initPerspectiveCamera, addPlane, addBox } from "./init.js";

  const gui = new GUI();

  const renderer = initRenderer("canvas-directional-light");

  const scene = new THREE.Scene();

  const camera = initPerspectiveCamera();
  scene.add(camera);

  // 平行光是沿着特定方向发射的光。这种光的表现像是无限远,从它发出的光线都是平行的。常常用平行光来模拟太阳光的效果。平行光可以投射阴影。
  // DirectionalLight( color : Color, intensity : Float )
  //参数:光源颜色、光照强度
  const directionalLight = new THREE.DirectionalLight(0xffffff, 5);
  directionalLight.position.set(-30, 30, -30);
  directionalLight.castShadow = true; // 开启光源阴影
  // 设置三维场景计算阴影的范围
  directionalLight.shadow.camera.left = -200;
  directionalLight.shadow.camera.right = 200;
  directionalLight.shadow.camera.top = 200;
  directionalLight.shadow.camera.bottom = -200;
  directionalLight.shadow.camera.near = 0.5;
  directionalLight.shadow.camera.far = 300;
  console.log("阴影默认像素", directionalLight.shadow.mapSize); // mapSize属性默认512x512
  console.log("shadow.radius", directionalLight.shadow.radius); // 模糊弱化阴影边缘
  scene.add(directionalLight);

  // DirectionalLightHelper:可视化平行光
  const dirLightHelper = new THREE.DirectionalLightHelper(directionalLight);
  scene.add(dirLightHelper);

  // 可视化平行光阴影对应的正投影相机对象
  const shadowHelper = new THREE.CameraHelper(directionalLight.shadow.camera);

  const axesHelper = new THREE.AxesHelper(150);
  scene.add(axesHelper);

  const plane = addPlane();
  scene.add(plane);

  const cube = addBox({ color: 0xff0000 });
  scene.add(cube);

  let step = 0;
  const controls = initControls();

  render();

  function render() {
    if (!controls.stopMovingLight) {
      step += 0.03;
      const pos = {};
      pos.z = -8;
      pos.y = +(27 * Math.sin(step / 3));
      pos.x = 10 + 26 * Math.cos(step / 3);

      directionalLight.position.copy(pos);
      dirLightHelper.update();
    }

    requestAnimationFrame(render);
    renderer.render(scene, camera);
  }

  function initControls() {
    gui.add(directionalLight, "intensity", 0, 20).name("平行光强度");
    gui.add(directionalLight.position, "x", -30, 30).name("平行光位置x");
    gui.add(directionalLight.position, "y", -30, 30).name("平行光位置y");
    gui.add(directionalLight.position, "z", -30, 30).name("平行光位置z");

    const controls = {
      directionalLightColor: directionalLight.color.getStyle(),
      shadowDebug: false,
      castShadow: true,
      onlyShadow: false,
      target: "Plane",
      stopMovingLight: false,
    };

    gui.addColor(controls, "directionalLightColor").onChange(function (e) {
      directionalLight.color = new THREE.Color(e);
    });

    gui.add(controls, "shadowDebug").onChange(function (e) {
      e ? scene.add(shadowHelper) : scene.remove(shadowHelper);
    });

    gui.add(controls, "castShadow").onChange(function (e) {
      directionalLight.castShadow = e;
    });

    gui.add(controls, "onlyShadow").onChange(function (e) {
      directionalLight.onlyShadow = e;
    });

    gui.add(controls, "target", ["Plane", "Mesh"]).onChange(function (e) {
      switch (e) {
        case "Plane":
          directionalLight.target = plane;
          break;
        case "Mesh":
          directionalLight.target = mesh;
          break;
      }
    });

    gui.add(controls, "stopMovingLight");

    return controls;
  }
</script>

PointLight

<canvas id="canvas-point-light"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { initRenderer, initPerspectiveCamera, addPlane, addBox } from "./init.js";

  const gui = new GUI();

  const renderer = initRenderer("canvas-point-light");

  const scene = new THREE.Scene();

  const camera = initPerspectiveCamera();
  scene.add(camera);

  //点光源:从一个点向各个方向发射的光源。一个常见的例子是模拟一个灯泡发出的光。
  // PointLight( color : Color, intensity : Float, distance : Number, decay : Float )
  // 参数:光源颜色、光照强度、光源照射的最大距离、沿着光照距离的衰退量
  const pointLight = new THREE.PointLight(0xffffff, 5, 0, 0.5);
  pointLight.castShadow = true; // 开启光源阴影
  pointLight.position.set(-30, 30, -30);
  scene.add(pointLight);

  // 光源辅助观察
  const pointLightHelper = new THREE.PointLightHelper(pointLight);
  scene.add(pointLightHelper);

  const shadowHelper = new THREE.CameraHelper(pointLight.shadow.camera);

  const axesHelper = new THREE.AxesHelper(150);
  scene.add(axesHelper);

  const plane = addPlane();
  scene.add(plane);

  const cube = addBox({ color: 0xff0000 });
  scene.add(cube);

  let invert = 1;
  let phase = 0;
  const controls = setupControls();

  render();

  function render() {
    if (!controls.stopMovingLight) {
      if (phase > 2 * Math.PI) {
        invert = invert * -1;
        phase -= 2 * Math.PI;
      } else {
        phase += 0.03;
      }
      const pos = {};
      pos.z = +(25 * Math.sin(phase));
      pos.x = +(14 * Math.cos(phase));
      pos.y = 5;

      if (invert < 0) {
        let pivot = 14;
        pos.x = invert * (pos.x - pivot) + pivot;
      }
      pointLight.position.copy(pos);
      pointLightHelper.update();
    }

    shadowHelper.update();

    requestAnimationFrame(render);
    renderer.render(scene, camera);
  }

  function setupControls() {
    const controls = {
      pointLightColor: pointLight.color.getStyle(),
      shadowDebug: false,
      castShadow: true,
      stopMovingLight: false,
    };

    gui.add(pointLight, "intensity", 0, 500).name("点光源强度");
    gui.add(pointLight, "distance", 0, 5000);
    gui.add(pointLight, "decay", 0, 5);
    gui.add(pointLight.position, "x", -30, 30).name("点光源位置x");
    gui.add(pointLight.position, "y", -30, 30).name("点光源位置y");
    gui.add(pointLight.position, "z", -30, 30).name("点光源位置z");

    gui.addColor(controls, "pointLightColor").onChange(function (e) {
      pointLight.color = new THREE.Color(e);
    });

    gui.add(controls, "shadowDebug").onChange(function (e) {
      e ? scene.add(shadowHelper) : scene.remove(shadowHelper);
    });

    gui.add(controls, "castShadow").onChange(function (e) {
      pointLight.castShadow = e;
    });

    gui.add(controls, "stopMovingLight");

    return controls;
  }
</script>

SpotLight

<canvas id="canvas-spot-light"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { initRenderer, initPerspectiveCamera, addPlane, addBox } from "./init.js";

  const gui = new GUI();

  const renderer = initRenderer("canvas-spot-light");

  const scene = new THREE.Scene();

  const camera = initPerspectiveCamera();
  scene.add(camera);

  // 聚光源:光线从一个点沿一个方向射出,随着光线照射的变远,光线圆锥体的尺寸也逐渐增大。
  // SpotLight( color : Color, intensity : Float, distance : Float, angle : Radians, penumbra : Float, decay : Float )
  // 参数:光源颜色、光照强度、光源照射的最大距离、光线照射范围的角度、聚光锥的半影衰减百分比、沿着光照距离的衰退量
  const spotLight = new THREE.SpotLight(0xffffff, 20, 0, Math.PI / 3, 0, 0.5);
  spotLight.position.set(-30, 30, -30);
  spotLight.castShadow = true; // 开启光源阴影
  spotLight.shadow.mapSize.width = 512;
  spotLight.shadow.mapSize.height = 512;
  spotLight.shadow.camera.near = 0.5;
  spotLight.shadow.camera.far = 500;
  spotLight.shadow.camera.fov = 1;
  scene.add(spotLight);

  const shadowHelper = new THREE.CameraHelper(spotLight.shadow.camera);

  // 聚光源辅助对象,可视化聚光源
  const spotLightHelper = new THREE.SpotLightHelper(spotLight);
  scene.add(spotLightHelper);

  const axesHelper = new THREE.AxesHelper(150);
  scene.add(axesHelper);

  const plane = addPlane();
  scene.add(plane);

  const cube = addBox({ color: 0xff0000 });
  scene.add(cube);

  const controls = setupControls();
  let step = 0;
  let invert = 1;
  let phase = 0;

  render();

  function render() {
    if (!controls.stopMovingLight) {
      if (phase > 2 * Math.PI) {
        invert = invert * -1;
        phase -= 2 * Math.PI;
      } else {
        phase += 0.03;
      }
      const pos = {};
      pos.z = +(7 * Math.sin(phase));
      pos.x = +(14 * Math.cos(phase));
      pos.y = 15;

      if (invert < 0) {
        const pivot = 14;
        pos.x = invert * (pos.x - pivot) + pivot;
      }

      spotLight.position.copy(pos);
    }

    spotLightHelper.update();

    requestAnimationFrame(render);
    renderer.render(scene, camera);
  }

  function setupControls() {
    gui.add(spotLight, "intensity", 0, 200).name("聚光源强度");
    gui.add(spotLight, "angle", 0, 2 * Math.PI).name("聚光源照射范围的角度");
    gui.add(spotLight, "penumbra", 0, 100).name("聚光源聚光锥的半影衰减百分比");
    gui.add(spotLight, "distance", 0, 500).name("聚光源照射的最大距离");
    gui.add(spotLight, "decay", 0, 5).name("聚光源沿着光照距离的衰退量");
    gui.add(spotLight.position, "x", -30, 30).name("聚光源位置x");
    gui.add(spotLight.position, "y", -30, 30).name("聚光源位置y");
    gui.add(spotLight.position, "z", -30, 30).name("聚光源位置z");

    const controls = {
      spotLightColor: spotLight.color.getStyle(),
      shadowDebug: false,
      castShadow: true,
      target: "Plane",
      stopMovingLight: false,
      penumbra: 0,
    };

    gui.addColor(controls, "spotLightColor").onChange(function (e) {
      spotLight.color = new THREE.Color(e);
    });

    gui.add(controls, "shadowDebug").onChange(function (e) {
      e ? scene.add(shadowHelper) : scene.remove(shadowHelper);
    });

    gui.add(controls, "castShadow").onChange(function (e) {
      spotLight.castShadow = e;
    });

    gui.add(controls, "target", ["Plane", "Mesh"]).onChange(function (e) {
      switch (e) {
        case "Plane":
          spotLight.target = plane;
          break;
        case "Mesh":
          spotLight.target = mesh;
          break;
      }
    });

    gui.add(controls, "stopMovingLight");

    return controls;
  }
</script>

HemisphereLight

<canvas id="canvas-hemisphere-light"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { initRenderer, initPerspectiveCamera, addPlane, addBox } from "./init.js";

  const gui = new GUI();

  const renderer = initRenderer("canvas-hemisphere-light");

  const scene = new THREE.Scene();

  const camera = initPerspectiveCamera();
  scene.add(camera);

  // 半球光:光源直接放置于场景之上,光照颜色从天空光线颜色渐变到地面光线颜色。半球光不能投射阴影。
  // HemisphereLight( color : Integer, groundColor : Integer, intensity : Float )
  // 参数:天空光线颜色、地面光线颜色、光源照射的最大距离、光照强度
  const hemisphereLight = new THREE.HemisphereLight(0xffffbb, 0x080820, 1);
  hemisphereLight.position.set(-30, 30, -30);
  scene.add(hemisphereLight);

  const axesHelper = new THREE.AxesHelper(150);
  scene.add(axesHelper);

  const plane = addPlane();
  scene.add(plane);

  const cube = addBox({ color: 0xff0000 });
  scene.add(cube);

  let step = 0;
  const controls = setupControls();

  render();

  function render() {
    if (!controls.stopMovingLight) {
      step += 0.03;
      const pos = {};
      pos.z = -8;
      pos.y = +(27 * Math.sin(step / 3));
      pos.x = 10 + 26 * Math.cos(step / 3);

      hemisphereLight.position.copy(pos);
    }

    requestAnimationFrame(render);
    renderer.render(scene, camera);
  }

  function setupControls() {
    const controls = {
      color: 0xffffbb,
      groundColor: 0x080820,
      stopMovingLight: false,
    };

    gui.add(hemisphereLight, "intensity", 0, 500).name("半球光强度");

    gui.addColor(controls, "color").onChange(function (e) {
      hemisphereLight.color = new THREE.Color(e);
    });
    gui.addColor(controls, "groundColor").onChange(function (e) {
      hemisphereLight.groundColor = new THREE.Color(e);
    });

    gui.add(controls, "stopMovingLight");

    return controls;
  }
</script>

RectAreaLight

<canvas id="canvas-rectarea-light"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { RectAreaLightUniformsLib } from "three/addons/lights/RectAreaLightUniformsLib.js";
  import { initRenderer, initPerspectiveCamera, addPlane, addBox } from "./init.js";

  RectAreaLightUniformsLib.init();

  const gui = new GUI();

  const renderer = initRenderer("canvas-rectarea-light");

  const scene = new THREE.Scene();

  const camera = initPerspectiveCamera();
  scene.add(camera);

  // 平面光:平面光光源从一个矩形平面上均匀地发射光线。这种光源可以用来模拟像明亮的窗户或者条状灯光光源。不支持阴影。
  // 只支持 MeshStandardMaterial 和 MeshPhysicalMaterial 两种材质。
  // RectAreaLight( color : Color, intensity : Float, width : Float, height : Float )
  // 参数:光源颜色、光照强度、光源宽度、光源高度
  const areaLight1 = new THREE.RectAreaLight(0xff0000, 50, 4, 10);
  areaLight1.position.set(-30, 10, 10);
  scene.add(areaLight1);

  const areaLight2 = new THREE.RectAreaLight(0x00ff00, 50, 4, 10);
  areaLight2.position.set(0, 10, 10);
  scene.add(areaLight2);

  const areaLight3 = new THREE.RectAreaLight(0x0000ff, 50, 4, 10);
  areaLight3.position.set(30, 10, 10);
  scene.add(areaLight3);

  const axesHelper = new THREE.AxesHelper(150);
  scene.add(axesHelper);

  const planeGeometry = new THREE.PlaneGeometry(100, 50, 1, 1);
  const planeMaterial = new THREE.MeshStandardMaterial({
    roughness: 0.044676705160855,
    metalness: 0.0,
  });
  const plane = new THREE.Mesh(planeGeometry, planeMaterial);
  plane.receiveShadow = true;
  plane.rotation.x = -0.5 * Math.PI;
  plane.position.set(0, 0, 0);
  scene.add(plane);

  const cube = addBox({ color: 0xff0000 });
  scene.add(cube);

  let step = 0;
  const controls = setupControls();

  render();

  function render() {
    requestAnimationFrame(render);
    renderer.render(scene, camera);
  }

  function setupControls() {
    const controls = {
      color1: 0xff0000,
      color2: 0x00ff00,
      color3: 0x0000ff,
    };

    gui.addColor(controls, "color1").onChange(function (e) {
      areaLight1.color = new THREE.Color(e);
    });
    gui.add(areaLight1, "intensity", 0, 200);
    gui.add(areaLight1, "width", 0, 10);
    gui.add(areaLight1, "height", 0, 20);

    gui.addColor(controls, "color2").onChange(function (e) {
      areaLight2.color = new THREE.Color(e);
    });
    gui.add(areaLight2, "intensity", 0, 200);
    gui.add(areaLight2, "width", 0, 10);
    gui.add(areaLight2, "height", 0, 20);

    gui.addColor(controls, "color3").onChange(function (e) {
      areaLight3.color = new THREE.Color(e);
    });
    gui.add(areaLight3, "intensity", 0, 200);
    gui.add(areaLight3, "width", 0, 10);
    gui.add(areaLight3, "height", 0, 20);

    return controls;
  }
</script>

材质

<canvas id="MeshBasicMaterial"></canvas>
<canvas id="MeshLambertMaterial"></canvas>
<canvas id="MeshPhongMaterial"></canvas>
<script type="importmap">
  {
    "imports": {
      "three": "./js/build/three.module.js",
      "three/addons/": "./js/jsm/"
    }
  }
</script>
<script type="module">
  import * as THREE from "three";
  import { GUI } from "three/addons/libs/lil-gui.module.min.js";
  import { initRenderer, initPerspectiveCamera, initAmbientLight, initSpotLight } from "./init.js";

  const gui = new GUI();

  function init(domId, callback) {
    const width = 300;
    const height = 200;
    const renderer = initRenderer(domId, width, height);

    const scene = new THREE.Scene();

    const camera = initPerspectiveCamera({ aspect: width / height });
    scene.add(camera);

    const ambientLight = initAmbientLight();
    scene.add(ambientLight);

    const spotLight = initSpotLight();
    scene.add(spotLight);

    const axesHelper = new THREE.AxesHelper(150);
    scene.add(axesHelper);

    callback(scene);

    render();

    function render() {
      requestAnimationFrame(render);
      renderer.render(scene, camera);
    }
  }

  init("MeshBasicMaterial", (scene) => {
    const geometry = new THREE.BoxGeometry(10, 20, 10);
    const material = new THREE.MeshBasicMaterial({
      color: 0xff0000,
    });
    const mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);
  });

  init("MeshLambertMaterial", (scene) => {
    const geometry = new THREE.BoxGeometry(10, 20, 10);
    // 漫反射网格材质
    const material = new THREE.MeshLambertMaterial({
      color: 0xff0000,
    });
    const mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);
  });

  init("MeshPhongMaterial", (scene) => {
    const geometry = new THREE.BoxGeometry(10, 20, 10);
    // 高光网格材质
    const material = new THREE.MeshPhongMaterial({
      color: 0xff0000,
      shininess: 10, //高光部分的亮度,默认30
      specular: 0xffffff, //高光部分的颜色
    });
    const mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);
    gui.add(material, "shininess", 0, 100).name("高光网格材质高光部分的亮度");
    const obj = {
      color: 0xffffff,
    };
    gui.addColor(obj, "color").onChange(function (value) {
      mesh.material.specular.set(value);
    });
  });
</script>

 

posted @ 2024-04-05 16:59  carol2014  阅读(68)  评论(0编辑  收藏  举报