three.js - Light & Shadow

  • 环境光 AmbientLight 不能投射阴影,没有方向
  • 平行光 DirectionalLight 可以投射阴影,可参考太阳光
  • 点光源 PointLight 可以投射阴影,从一个点向四周发射光线,区别于聚光灯 SpotLight - 从一个点沿着一个方向射出,类似手电筒
  • 光源与材质之间的影响关系,例如MeshBasicMaterial不受光照影响
  • 设置一个基础场景,包含场景、相机、轨道、gui等
<script setup>
  // 导入three.js
  import * as THREE from 'three'
  // 导入轨道控制器
  import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  // 导入gui
  import * as dat from 'dat.gui'

  /**
   * 1. 创建场景
  **/
  const scene = new THREE.Scene()

  /**
   * 2. 创建透视相机(近大远小,相机只渲染场景内的东西)
  **/
  const camera = new THREE.PerspectiveCamera(
    45, // 视角
    window.innerWidth / window.innerHeight, // 视椎体长宽比
    0.1, // 近端面
    1000 // 远端面
  )
  camera.position.set(0, 0, 10)
  camera.lookAt(0, 0, 0)

  /**
   * 3. 创建渲染器
  **/
  const renderer = new THREE.WebGLRenderer()
  renderer.setSize(window.innerWidth, window.innerHeight)
  document.body.appendChild(renderer.domElement) // 在body上添加渲染器,domElement指向canvas

  /*
    * 4. 创建几何体
  */

  /*
    * 5. 坐标轴
  */
  const axesHelper = new THREE.AxesHelper(5) // 坐标轴线段长度
  scene.add(axesHelper)

  /*
    6. 控制器(使相机围绕目标运动)
  */
  const controls = new OrbitControls(camera, renderer.domElement)
  controls.enableDamping = true // 添加轨道阻尼效果

  /*
    * 7. 渲染
  */
  function animate () {
    controls.update()
    requestAnimationFrame(animate)
    // cube.rotation.x += 0.01
    // cube.rotation.y += 0.01
    renderer.render(scene, camera)
  }
  animate()
</script>
  • 添加几何体和平面,平面用于接收阴影
/*
  * 4. 创建几何体
*/
// MeshStandardMaterial
const material = new THREE.MeshStandardMaterial()
material.roughness = 0.4

// 球体
const sphere = new THREE.Mesh(
  new THREE.SphereGeometry(0.5, 32, 32),
  material
)
sphere.position.x = -1.5

// cube
const cube = new THREE.Mesh(
  new THREE.BoxGeometry(0.75, 0.75, 0.75),
  material
)

// 环
const torus = new THREE.Mesh(
  new THREE.TorusGeometry(0.3, 0.2, 32, 64),
  material
)
torus.position.x = 1.5

// 平面
const plane = new THREE.Mesh(
  new THREE.PlaneGeometry(5, 5),
  material
)
plane.rotation.x = - Math.PI * 0.5
plane.position.y = - 0.65


scene.add(sphere, cube, torus, plane)
  • 添加环境光
    • color
    • intensity: 光的强度
  /*
    * 光
  */
  // 环境光
  const light = new THREE.AmbientLight(0xffffff, 0.5) // soft white light
  scene.add(light)
  • 添加平行光及阴影(在以上基础场景、物体及环境光的代码上找到对应位置进行补充),使用 OrthographicCamera 正交相机 来计算阴影
    • 灯光阴影的条件:
      1. 材质需要对光照有反应
      2. 设置渲染器开启阴影的计算 renderer.shadowMap.enabled = true
      3. 设置光照投射阴影 directionalLight.castShadow = true
      4. 设置物体投射阴影 sphere.castShadow = true
      5. 设置物体接收阴影 plane.receiveShadow = true(这里是平面接收阴影)
    • CameraHelper平行光投射相机
  /*
    * 光
  */
  // 平行光源
  const directionalLight = new THREE.DirectionalLight(0xffffff, 0.5)
  directionalLight.position.set(10, 10, 10)   // 平行光是无限远的,这里的position指的是光的方向
  scene.add(directionalLight)
// CameraHelper 模拟平行光投射相机视椎体
const directionalLightCameraHelper = new THREE.CameraHelper(directionalLight.shadow.camera)
scene.add(directionalLightCameraHelper)
  /**
   * 3. 创建渲染器
  **/
  ...
  ...
  renderer.shadowMap.enabled = true  // 开启场景中的阴影贴图

  /*
    * 4. 创建几何体
  */
  ...
  ...
  sphere.castShadow = true  // sphere 投射阴影

  // 平面接收阴影
  ...
  ...
  plane.receiveShadow = true
  // 平行光源投射阴影
  directionalLight.castShadow = true
  // 阴影边缘模糊度 
  directionalLight.shadow.radius = 20
  // 阴影贴图分辨率
  directionalLight.shadow.mapSize.set(2048, 2048)
  // 平行光投射相机的属性
  directionalLight.shadow.camera.near = 0.5
  directionalLight.shadow.camera.far = 500
  directionalLight.shadow.camera.top = 5
  directionalLight.shadow.camera.bottom = -5
  directionalLight.shadow.camera.left = -5
  directionalLight.shadow.camera.right = 5
  • 添加聚光灯及阴影(在以上基础场景、物体及环境光的代码上找到对应位置进行补充),like a falshlight,to rotate the SpotLight, we need to add its target property to the scene and then move it,使用 PerspectiveCamera 透视相机 计算阴影
    • color
    • intensity
    • distance
    • angle
    • pernumbra
    • decay
  /*
    * 光
  */
  // 聚光灯
  const spotLight = new THREE.SpotLight(0x78ff00, 0.5, 10, Math.PI * 0.1, 0.25, 1)
  spotLight.position.set(0, 2, 3)
  spotLight.castShadow = true
  spotLight.target = sphere // Object3D,一个物体,聚光灯目标,移动物体时,灯光跟随物体
  spotLight.angle = Math.PI / 6 // 聚光灯角度,一般不超过90°
  spotLight.distance = 5 // 聚光灯光源发出的距离
  spotLight.penumbra = 0.5 // 聚光灯半影衰减,数值越接近1时,灯光边缘越模糊

  // 阴影边缘模糊度 
  spotLight.shadow.radius = 20
  
  // 阴影贴图分辨率
  spotLight.shadow.mapSize.set(4096, 4096)
  // console.log(spotLight)

  scene.add(spotLight)
  // console.log(spotLight.target)
  scene.add(spotLight.target)
  spotLight.target.position.x = -0.75
  • 添加点光源及阴影(在以上基础场景、物体及环境光的代码上找到对应位置进行补充)
    • 这里添加了一个smallball用于展示点光源的位置
  const smallBall = new THREE.Mesh(
    new THREE.SphereGeometry(0.1, 20, 20),
    new THREE.MeshBasicMaterial({
      color: 0xff0000
    })
  )
  smallBall.position.set(2, 2, 2)

  // 点光源
  const pointLight = new THREE.PointLight(0xff0000, 2)
  // pointLight.position.set(2, 2, 2);
  pointLight.castShadow = true

  // 阴影边缘模糊度 
  pointLight.shadow.radius = 20
  
  // 阴影贴图分辨率
  pointLight.shadow.mapSize.set(512, 4096)

  smallBall.add(pointLight)
  scene.add(smallBall)
  • 半球光 HemisphereLight这一步可去掉AmbientLight观察,如下代码只保留directionalLight,半球光需要设置天空光线颜色和地面光线颜色,在物体的中间部分会呈现两种光线颜色的混合与渐变(当前代码的呈现结果是物体顶面是红色、地面是蓝色)
    • color (or skyColor)
    • groundColor
    • intensity
// DirectionalLight
const directionalLight = new THREE.DirectionalLight(0x00fffc, 0.3)
directionalLight.position.set(1, 0.25, 0)
scene.add(directionalLight)

// HemisphereLight
const hemisphereLight = new THREE.HemisphereLight(0xff0000, 0x0000ff, 0.3)
scene.add(hemisphereLight)
  • 平面光源 RectAreaLightworks like a big rectangle,这里可以只保留平面光源进行观察,环境光也不需要;the RectAreaLight only works with MeshStandardMaterial and MeshPhysicalMaterial
    • color
    • intensity
    • width
    • height
// RectAreaLight
const rectAreaLight = new THREE.RectAreaLight(0x4e00ff, 2, 1, 1)
rectAreaLight.position.set(-1.5, 0, 1.5)
rectAreaLight.lookAt(new THREE.Vector3())
scene.add(rectAreaLight)
  • Cost
    • minimal Cost: AmbientLight、HemisphereLight
    • moderate Cost:DirectionalLight、PointLight
    • Hight Cost:SpotLight、RectAreaLight
  • Helper,模拟场景内灯光的辅助对象,效果如下图
// Helpers
const hemisphereLightHelper = new THREE.HemisphereLightHelper(hemisphereLight, 0.2)
scene.add(hemisphereLightHelper)

const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 0.2)
scene.add(directionalLightHelper)

const pointLightHelper = new THREE.PointLightHelper(pointLight, 0.2)
scene.add(pointLightHelper)

const spotLightHelper = new THREE.SpotLightHelper(spotLight) // SpotLightHelper have no size
scene.add(spotLightHelper)

// RectAreaLightHelper 是一个附加组件,需要显示导入(three.js版本:0.155.0)
import { RectAreaLightHelper } from 'three/addons/helpers/RectAreaLightHelper.js';
const rectAreaLightHelper = new RectAreaLightHelper(rectAreaLight)
scene.add(rectAreaLightHelper)
image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容