Skip to content

阴影效果

🕒 Published at:

如何实现一个物体的阴影效果呢,首先我们需要选用能够产生阴影的材质,然后设置光源的阴影属性,最后设置渲染器的阴影属性。

平行光

产生阴影

  • mesh.castShadow = true; 网格模型投射阴影
  • plane.receiveShadow = true; 平面接收阴影
  • directionalLight.castShadow = true; 光源投射阴影
  • renderer.shadowMap.enabled = true; 渲染器开启阴影贴图

在之前的知识基础下完成了这几步完成以后其实我们就可以看到一个阴影效果了

优化阴影

但是此时 发现阴影特别小,我们需要调整一下平行光阴影相机的范围

阴影的范围和清晰度还受到平行光阴影相机(directionalLight.shadow.camera)的裁剪范围影响。如果相机的范围设置得太小,阴影可能会被裁剪掉或显得非常小

js
directionalLight.shadow.camera.near = 1;
directionalLight.shadow.camera.far = 1000;
directionalLight.shadow.camera.left = -200;
directionalLight.shadow.camera.right = 200;
directionalLight.shadow.camera.top = 200;
directionalLight.shadow.camera.bottom = -200;

但是调节完了光照的范围发现还是有点不太“真实”,可以设置下阴影的模糊度 阴影的模糊度可以通过 directionalLight.shadow.radius 来控制,这个属性就是让阴影变得有色差看上去比较真实

js
directionalLight.shadow.radius = 10;

顺带增加下分辨率 因为默认的比较小,看上去比较模糊

js
directionalLight.shadow.mapSize.set(4096, 4096);

这样设置完看上去的效果就比一开始好多了

最终效果

完整代码

html
<template>
  <div id="canvas"></div>
</template>

<script setup>
import * as THREE from 'three';
import { onMounted } from 'vue';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { useRoute } from 'vue-router';
const route = useRoute();

onMounted(() => {
  const scene = new THREE.Scene();
  const gui = new GUI(); //创建GUI对象

  //创建一个长方体几何对象Geometry
  const sphere = new THREE.SphereGeometry(20);

  //材质对象Material
  const material = new THREE.MeshStandardMaterial();

  // 创建网格模型
  const mesh = new THREE.Mesh(sphere, material); //网格模型对象Mesh

  mesh.castShadow = true; // 网格模型投射阴影

  //设置网格模型在三维空间中的位置坐标,默认是坐标原点
  mesh.position.set(0, 20, 0);
  scene.add(mesh); //网格模型添加到场景中

  // 创建平面
  const geometry1 = new THREE.PlaneGeometry(200, 200);

  const material1 = new THREE.MeshStandardMaterial({});

  const plane = new THREE.Mesh(geometry1, material1);
  plane.position.set(0, -2, 0);
  //   设置物体接收阴影
  plane.receiveShadow = true;

  plane.rotation.x = -Math.PI / 2;
  scene.add(plane);

  // AxesHelper:辅助观察的坐标系
  const axesHelper = new THREE.AxesHelper(100);
  scene.add(axesHelper);

  /**
   * 光源设置
   */
  // tip: 环境光

  const ambient = new THREE.AmbientLight(0xffffff, 1);
  scene.add(ambient);

  // 平行光
  const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
  directionalLight.position.set(100, 100, 100);
  directionalLight.lookAt(scene.position); // 让光源指向场景中心

  //   设置光照投射阴影
  directionalLight.castShadow = true;

  if (route.query.type === '1') {
    directionalLight.shadow.radius = 10;
    directionalLight.shadow.mapSize.set(2048, 2048);

    directionalLight.shadow.camera.near = 1;
    directionalLight.shadow.camera.far = 1000;
    directionalLight.shadow.camera.left = -200;
    directionalLight.shadow.camera.right = 200;
    directionalLight.shadow.camera.top = 200;
    directionalLight.shadow.camera.bottom = -200;
  }

  // 平行光子菜单
  const dirFolder = gui.addFolder('平行光');
  dirFolder.close(); //关闭菜单
  // 平行光位置
  dirFolder.add(directionalLight.position, 'x', -300, 300);
  dirFolder.add(directionalLight.position, 'y', -300, 300);
  dirFolder.add(directionalLight.position, 'z', -300, 300);

  const dirLightHelper = new THREE.DirectionalLightHelper(directionalLight, 5, 0xff0000);
  scene.add(dirLightHelper);

  scene.add(directionalLight);

  // width和height用来设置Three.js输出的Canvas画布尺寸(像素px)
  const width = window.innerWidth; //宽度
  const height = window.innerHeight; //高度

  /**
   * 透视投影相机设置
   */
  // 30:视场角度, width / height:Canvas画布宽高比, 1:近裁截面, 3000:远裁截面
  const camera = new THREE.PerspectiveCamera(60, width / height, 1, 3000);
  camera.position.set(-212, 66, 62); //相机在Three.js三维坐标系中的位置
  camera.lookAt(0, 0, 0); //相机观察目标指向Three.js坐标系原点

  /**
   * 创建渲染器对象
   */
  const renderer = new THREE.WebGLRenderer({
    antialias: true //开启锯齿
  });
  // 设置渲染器开启阴影的计算
  renderer.shadowMap.enabled = true;

  renderer.setSize(width, height); //设置three.js渲染区域的尺寸(像素px)
  const controls = new OrbitControls(camera, renderer.domElement);
  document.getElementById('canvas').appendChild(renderer.domElement);

  function render() {
    renderer.render(scene, camera); //执行渲染操作
    // 查看相机位置 调整到一个合适的地方
    // console.log('camera.position',camera.position);
    requestAnimationFrame(render); //请求再次执行渲染函数render,渲染下一帧
  }
  render();
  // 画布跟随窗口变化
  window.onresize = function () {
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
  };
});
</script>

聚光源

光线从一个点沿一个方向射出,随着光线照射的变远,光线圆锥体的尺寸也逐渐增大。

代码

html
<template>
  <div id="canvas"></div>
</template>

<script setup>
import * as THREE from 'three';
import { onMounted } from 'vue';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { useRoute } from 'vue-router';
const route = useRoute();
onMounted(() => {
  const scene = new THREE.Scene();
  const gui = new GUI(); // 创建 GUI 对象

  // 创建球体
  const sphere = new THREE.SphereGeometry(20);
  const material = new THREE.MeshStandardMaterial({
    color: 0x86a8e7
  });
  const mesh = new THREE.Mesh(sphere, material);
  mesh.position.set(0, 20, 0);
  mesh.castShadow = true;
  scene.add(mesh);

  // 创建平面
  const geometry1 = new THREE.PlaneGeometry(200, 200);
  const material1 = new THREE.MeshStandardMaterial({
    color: 0x888888
  });
  const plane = new THREE.Mesh(geometry1, material1);
  plane.position.set(0, -2, 0);
  plane.rotation.x = -Math.PI / 2;
  plane.receiveShadow = true;
  scene.add(plane);

  // 添加环境光
  const ambient = new THREE.AmbientLight(0xffffff, 0.2); // 降低环境光强度
  scene.add(ambient);

  // 添加聚光灯
  const spotLight = new THREE.SpotLight(0xffffff, 1);
  spotLight.position.set(100, 97, 3);
  spotLight.angle = Math.PI / 4;
  spotLight.distance = 500;
  spotLight.decay = 0;
  spotLight.castShadow = true;
  spotLight.shadow.mapSize.set(2048, 2048);
  spotLight.shadow.camera.near = 10;
  spotLight.shadow.camera.far = 200;
  spotLight.shadow.bias = -0.001;
  spotLight.target = mesh; // 设置聚光灯目标
  scene.add(spotLight);

  // 可视化聚光灯和阴影相机
  const spotLightHelper = new THREE.SpotLightHelper(spotLight);
  scene.add(spotLightHelper);

  const cameraHelper = new THREE.CameraHelper(spotLight.shadow.camera);
  scene.add(cameraHelper);

  // 创建渲染器
  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.shadowMap.enabled = true;
  renderer.setSize(window.innerWidth, window.innerHeight);

  const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 3000);
  camera.position.set(-26, 86, 278);
  camera.lookAt(0, 0, 0);

  const controls = new OrbitControls(camera, renderer.domElement);
  document.getElementById('canvas').appendChild(renderer.domElement);

  // 创建 GUI 控制面板
  const spotLightFolder = gui.addFolder('SpotLight');
  spotLightFolder.add(spotLight.position, 'x').min(-200).max(200).step(1).name('SpotLight X');
  spotLightFolder.add(spotLight.position, 'y').min(0).max(200).step(1).name('SpotLight Y');
  spotLightFolder.add(spotLight.position, 'z').min(-200).max(200).step(1).name('SpotLight Z');
  spotLightFolder
    .add(spotLight, 'angle')
    .min(0)
    .max(Math.PI / 2)
    .step(0.01)
    .name('SpotLight Angle');
  spotLightFolder.add(spotLight, 'distance').min(0).max(500).step(1).name('SpotLight Distance');
  spotLightFolder.add(spotLight, 'intensity').min(0).max(5).step(0.1).name('SpotLight Intensity');
  spotLightFolder.add(spotLight, 'decay').min(0).max(2).step(0.1).name('SpotLight Decay');
  spotLightFolder.add(spotLight.shadow, 'bias').min(-0.1).max(0.1).step(0.001).name('SpotLight Shadow Bias');

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

  window.onresize = () => {
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
  };
});
</script>

点光源

从一个点向各个方向发射的光源。一个常见的例子是模拟一个灯泡发出的光。

点光源大多数属性都和聚光源差不多,唯一区别就是点光源没有角度这种说法,他是往四周发射的

效果

以上是一个点光源出来的效果,和聚光源差不多

假如 我们做一个小球,让其代替点光源的发光,并且小球还能围绕着物体进行旋转应该怎么做呢

首先创建一个小球

js
  const smallBall = new THREE.Mesh(new THREE.SphereGeometry(5), new THREE.MeshBasicMaterial({ color: 0xffffff }));

接着给小球设置位置,也就是之前点光源的位置

js
 smallBall.position.set(100, 97, 3);
 scene.add(smallBall);

然后把之前的点光源添加到小球中,不要忘记顺手删了点光源的位置,并把小球添加到场景中去

js
smallBall.add(pointLight);
scene.add(smallBall);

到了这一步 小球代替点光源发光的效果已经完成了,如果想让小球围绕着物体进行旋转,只需要给小球添加一个动画即可

js
smallBall.position.set(100 * Math.sin(Date.now() / 1000), 97, 100 * Math.cos(Date.now() / 1000));

效果

代码

html
<template>
  <div id="canvas"></div>
</template>

<script setup>
import * as THREE from 'three';
import { onMounted } from 'vue';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
import { useRoute } from 'vue-router';
const route = useRoute();

onMounted(() => {
  const scene = new THREE.Scene();
  const gui = new GUI(); // 创建 GUI 对象

  // 创建球体
  const sphere = new THREE.SphereGeometry(20);
  const material = new THREE.MeshStandardMaterial({
    color: 0x86a8e7
  });
  const mesh = new THREE.Mesh(sphere, material);
  mesh.position.set(0, 20, 0);
  mesh.castShadow = true;
  scene.add(mesh);

  // 创建平面
  const geometry1 = new THREE.PlaneGeometry(200, 200);
  const material1 = new THREE.MeshStandardMaterial({
    color: 0x888888
  });
  const plane = new THREE.Mesh(geometry1, material1);
  plane.position.set(0, -2, 0);
  plane.rotation.x = -Math.PI / 2;
  plane.receiveShadow = true;
  scene.add(plane);

  // 添加环境光
  const ambient = new THREE.AmbientLight(0xffffff, 0.2); // 降低环境光强度
  scene.add(ambient);

  // 添加点光源
  const pointLight = new THREE.PointLight(0xffffff, 1);

  if (route.query.type != '1') {
    pointLight.position.set(100, 97, 3);
  }
  pointLight.distance = 500;
  pointLight.decay = 0;
  pointLight.castShadow = true;
  pointLight.shadow.mapSize.set(2048, 2048);
  pointLight.shadow.camera.near = 10;
  pointLight.shadow.camera.far = 200;
  pointLight.shadow.bias = -0.001;

  const smallBall = new THREE.Mesh(new THREE.SphereGeometry(5), new THREE.MeshBasicMaterial({ color: 0xffffff }));

  if (route.query.type === '1') {
    // 创建一个小球代替我们的点光源发光
    smallBall.position.set(100, 97, 3);
  }

  if (route.query.type === '1') {
    smallBall.add(pointLight);
    scene.add(smallBall);
  } else {
    scene.add(pointLight);
  }
  console.log(route.query.type);

  if (route.query.type != '1') {
    // 可视化聚光灯和阴影相机
    const pointLightHelper = new THREE.PointLightHelper(pointLight);
    scene.add(pointLightHelper);
    const cameraHelper = new THREE.CameraHelper(pointLight.shadow.camera);
    scene.add(cameraHelper);
  }

  // 创建渲染器
  const renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.shadowMap.enabled = true;
  renderer.setSize(window.innerWidth, window.innerHeight);

  const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 3000);
  camera.position.set(2, 142, 407);
  camera.lookAt(0, 0, 0);

  const controls = new OrbitControls(camera, renderer.domElement);
  document.getElementById('canvas').appendChild(renderer.domElement);

  // 创建 GUI 控制面板
  const spotLightFolder = gui.addFolder('点光源');
  spotLightFolder.add(pointLight.position, 'x').min(-200).max(200).step(1).name('SpotLight X');
  spotLightFolder.add(pointLight.position, 'y').min(0).max(200).step(1).name('SpotLight Y');
  spotLightFolder.add(pointLight.position, 'z').min(-200).max(200).step(1).name('SpotLight Z');

  spotLightFolder.add(pointLight, 'distance').min(0).max(500).step(1).name('SpotLight Distance');
  spotLightFolder.add(pointLight, 'intensity').min(0).max(5).step(0.1).name('SpotLight Intensity');
  spotLightFolder.add(pointLight, 'decay').min(0).max(2).step(0.1).name('SpotLight Decay');
  spotLightFolder.add(pointLight.shadow, 'bias').min(-0.1).max(0.1).step(0.001).name('SpotLight Shadow Bias');

  function render() {
    renderer.render(scene, camera);
    if (route.query.type === '1') {
      // 让小球转起来 (也就是点光源)
      smallBall.position.set(100 * Math.sin(Date.now() / 1000), 97, 100 * Math.cos(Date.now() / 1000));
    }
    requestAnimationFrame(render);
  }
  render();

  window.onresize = () => {
    renderer.setSize(window.innerWidth, window.innerHeight);
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
  };
});
</script>