Three.js基础功能学习四:摄像机与阴影

在three.js中最常用的摄像机并且之前我们一直用的摄像机是透视摄像机 PerspectiveCamera,它可以提供一个近大远小的3D视觉效果.

阴影贴图的工作方式就是具有投射阴影的光能对所有能被投射阴影的物体从光源渲染阴影。

一、学习视频

three.js学习及项目实战:三维摄像机学习

二、摄像机相关内容

2.1 摄像机(Camera)

摄像机的抽象基类。在构建新摄像机时,应始终继承此类。

  • .isCamera : Boolean
      Read-only flag to check if a given object is of type Camera.
  • .layers : Layers
      摄像机是一个layers的成员. 这是一个从Object3D继承而来的属性。
      当摄像机的视点被渲染的时候,物体必须和当前被看到的摄像机共享至少一个层。
  • .matrixWorldInverse : Matrix4
      这是matrixWorld矩阵的逆矩阵。 MatrixWorld包含了相机的世界变换矩阵。
  • .projectionMatrix : Matrix4
      这是投影变换矩阵。
  • .projectionMatrixInverse : Matrix4
      这是投影变换矩阵的逆矩阵。
  • .clone ( ) : Camera
      返回一个具有和当前相机的属性一样的新的相机。
  • .copy ( source : Camera, recursive : Boolean ) : this
      将源摄像机的属性复制到新摄像机中。
  • .getWorldDirection ( target : Vector3 ) : Vector3
      target --- 调用该函数的结果将复制给该Vector3对象。
javascript 复制代码
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
export default () => {
  const canvas = document.querySelector('#demo')
  const domeView1 = document.querySelector('#view1')
  const domeView2 = document.querySelector('#view2')

  const renderer = new THREE.WebGLRenderer({
    antialias: true,
    canvas,
  })

  const fov = 45
  const aspect = 2 // the canvas default
  const near = 5
  const far = 100
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
  camera.position.set(0, 10, 20)

  const cameraHelp = new THREE.CameraHelper(camera)
  interface Helper {
    [key: string]: any
  }
  class MinMaxGUIHelper {
    obj: Helper
    minProp: any
    maxProp: any
    minDif: number
    constructor(obj: Helper, minProp: any, maxProp: any, minDif: number) {
      this.obj = obj
      this.minProp = minProp
      this.maxProp = maxProp
      this.minDif = minDif
    }
    get min() {
      return this.obj[this.minProp]
    }
    set min(v) {
      this.obj[this.minProp] = v
      this.obj[this.maxProp] = Math.max(this.obj[this.maxProp], v + this.minDif)
    }
    get max() {
      return this.obj[this.maxProp]
    }
    set max(v) {
      this.obj[this.maxProp] = v
      this.min = this.min // this will call the min setter
    }
  }

  const gui = new GUI()
  gui.add(camera, 'fov', 1, 180)
  const minMaxGUIHelper = new MinMaxGUIHelper(camera, 'near', 'far', 0.1)
  gui.add(minMaxGUIHelper, 'min', 0.1, 50, 0.1).name('near')
  gui.add(minMaxGUIHelper, 'max', 0.1, 50, 0.1).name('far')
  const controls = new OrbitControls(camera, canvas)
  controls.target.set(0, 5, 0)
  controls.update()

  const camera2 = new THREE.PerspectiveCamera(
    60, // fov
    2, // aspect
    0.1, // near
    500, // far
  )
  camera2.position.set(40, 10, 30)
  camera2.lookAt(0, 5, 0)

  const controls2 = new OrbitControls(camera2, domeView2)
  controls2.target.set(0, 5, 0)
  controls2.update()

  const scene = new THREE.Scene()
  scene.background = new THREE.Color('black')
  scene.add(cameraHelp)

  {
    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 = 2
    const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize)
    const cubeMat = new THREE.MeshPhongMaterial({ color: 'rgba(199, 6, 6, 1)' })
    const mesh = new THREE.Mesh(cubeGeo, cubeMat)
    mesh.position.set(cubeSize + 1, cubeSize / 2, 0)
    scene.add(mesh)
  }
  {
    const sphereRadius = 1
    const sphereWidthDivisions = 32
    const sphereHeightDivisions = 16
    const sphereGeo = new THREE.SphereGeometry(
      sphereRadius,
      sphereWidthDivisions,
      sphereHeightDivisions,
    )
    const sphereMat = new THREE.MeshPhongMaterial({ color: 'rgba(14, 197, 60, 1)' })
    const mesh = new THREE.Mesh(sphereGeo, sphereMat)
    mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0)
    scene.add(mesh)
  }

  {
    const color = 0xffffff
    const intensity = 3
    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)
  }

  function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
    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 setScissorForElement(elem: Element) {
    if (!canvas) {
      return
    }
    const canvasRect = canvas.getBoundingClientRect()
    const elemRect = elem.getBoundingClientRect()

    // compute a canvas relative rectangle
    const right = Math.min(elemRect.right, canvasRect.right) - canvasRect.left
    const left = Math.max(0, elemRect.left - canvasRect.left)
    const bottom = Math.min(elemRect.bottom, canvasRect.bottom) - canvasRect.top
    const top = Math.max(0, elemRect.top - canvasRect.top)

    const width = Math.min(canvasRect.width, right - left)
    const height = Math.min(canvasRect.height, bottom - top)

    // setup the scissor to only render to that part of the canvas
    const positiveYUpBottom = canvasRect.height - bottom
    renderer.setScissor(left, positiveYUpBottom, width, height)
    renderer.setViewport(left, positiveYUpBottom, width, height)

    // return the aspect
    return width / height
  }

  function render() {
    resizeRendererToDisplaySize(renderer)
    // turn on the scissor
    renderer.setScissorTest(true)

    if (domeView1) {
      const aspect = setScissorForElement(domeView1)

      // adjust the camera for this aspect
      camera.aspect = aspect
      camera.updateProjectionMatrix()
      cameraHelp.update()

      // don't draw the camera helper in the original view
      cameraHelp.visible = false

      scene.background.set(0x000000)

      // render
      renderer.render(scene, camera)
    }

    // render from the 2nd camera
    if (domeView2) {
      const aspect = setScissorForElement(domeView2)

      // adjust the camera for this aspect
      camera2.aspect = aspect
      camera2.updateProjectionMatrix()

      // draw the camera helper in the 2nd view
      cameraHelp.visible = true

      scene.background.set(0x000040)

      renderer.render(scene, camera2)
    }
    requestAnimationFrame(render)
  }

  requestAnimationFrame(render)
}

2.2 摄像机阵列(ArrayCamera)

ArrayCamera 用于更加高效地使用一组已经预定义的摄像机来渲染一个场景。这将能够更好地提升VR场景的渲染性能。

一个 ArrayCamera 的实例中总是包含着一组子摄像机,应当为每一个子摄像机定义viewport(视口)这个属性,这一属性决定了由该子摄像机所渲染的视口区域的大小。

  • .cameras : Array
      摄像机数组。
  • .isArrayCamera : Boolean
      Read-only flag to check if a given object is of type ArrayCamera.
javascript 复制代码
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'

export default () => {
  const canvas = document.querySelector('#demo')

  const renderer = new THREE.WebGLRenderer({
    antialias: true,
    canvas,
  })
  const ASPECT_RATIO = window.innerWidth / window.innerHeight

  const WIDTH = (window.innerWidth / 2) * window.devicePixelRatio
  const HEIGHT = (window.innerHeight / 2) * window.devicePixelRatio

  const cameras = []
  for (let y = 0; y < 2; y++) {
    for (let x = 0; x < 2; x++) {
      const subcamera = new THREE.PerspectiveCamera(
        40,
        window.innerWidth / window.innerHeight,
        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 / 2 - 0.5
      subcamera.position.y = 0.5 - y / 2
      subcamera.position.z = 1.5
      subcamera.position.multiplyScalar(2)
      subcamera.lookAt(0, 0, 0)
      subcamera.updateMatrixWorld()
      cameras.push(subcamera)

      const controls = new OrbitControls(subcamera, canvas)
      controls.target.set(0, 5, 0)
      controls.update()
    }
  }
  const camera = new THREE.ArrayCamera(cameras)
  camera.position.z = 3

  const scene = new THREE.Scene()
  scene.background = new THREE.Color('black')

  const color = 0xffffff
  const intensity = 1
  scene.add(new THREE.AmbientLight(color, intensity))

  const light = new THREE.DirectionalLight(0xffffff, 3)
  light.position.set(0.5, 0.5, 1)
  light.castShadow = true
  light.shadow.camera.zoom = 4 // tighter shadow map
  scene.add(light)

  //   const video = document.getElementById('video')
  //   const texture = new THREE.VideoTexture(video)

  //   const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, map: texture })

  //   const cubes: THREE.Mesh[] = [] // just an array we can use to rotate the cubes
  //   const cube = new THREE.Mesh(geometry, material)
  {
    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)
  }
  //   cubes.push(mesh)

  {
    const cubeSize = 2
    const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize)
    const cubeMat = new THREE.MeshPhongMaterial({ color: 'rgba(199, 6, 6, 1)' })
    const mesh = new THREE.Mesh(cubeGeo, cubeMat)
    mesh.position.set(cubeSize + 1, cubeSize / 2, 0)
    scene.add(mesh)
  }
  {
    const sphereRadius = 1
    const sphereWidthDivisions = 32
    const sphereHeightDivisions = 16
    const sphereGeo = new THREE.SphereGeometry(
      sphereRadius,
      sphereWidthDivisions,
      sphereHeightDivisions,
    )
    const sphereMat = new THREE.MeshPhongMaterial({ color: 'rgba(14, 197, 60, 1)' })
    const mesh = new THREE.Mesh(sphereGeo, sphereMat)
    mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0)
    scene.add(mesh)
  }

  function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
    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)

  renderer.render(scene, camera)
}

2.3 立方相机(CubeCamera)

创建6个渲染到WebGLCubeRenderTarget的摄像机。

  • near -- 近剪切面的距离
  • far -- 远剪切面的距离
  • renderTarget -- The destination cube render target.
  • .renderTarget : WebGLCubeRenderTarget
      The destination cube render target.
  • .update ( renderer : WebGLRenderer, scene : Scene ) : undefined
    • renderer -- 当前的WebGL渲染器
    • scene -- 当前的场景
        这个方法用来更新renderTarget(渲染目标对象)。
javascript 复制代码
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'

export default () => {
  const canvas = document.querySelector('#demo')

  const renderer = new THREE.WebGLRenderer({
    antialias: true,
    canvas,
  })

  const scene = new THREE.Scene()
  scene.background = new THREE.Color('black')
  scene.rotation.y = 0.5 // avoid flying objects occluding the sun

  const fov = 45
  const aspect = 2 // the canvas default
  const near = 5
  const far = 100
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
  camera.position.set(0, 10, 20)

  // Create cube render target
  const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(128, {
    generateMipmaps: true,
    minFilter: THREE.LinearMipmapLinearFilter,
  })
  cubeRenderTarget.texture.type = THREE.HalfFloatType

  // Create cube camera
  const cubeCamera = new THREE.CubeCamera(1, 1000, cubeRenderTarget)
  scene.add(camera)

  const controls = new OrbitControls(camera, canvas)
  controls.target.set(0, 5, 0)
  controls.update()

  const material2 = new THREE.MeshStandardMaterial({
    roughness: 0.1,
    metalness: 0,
  })
  const cube = new THREE.Mesh(new THREE.BoxGeometry(15, 15, 15), material2)
  scene.add(cube)

  function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
    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()
    }

    cubeCamera.update(renderer, scene)

    controls.update()

    renderer.render(scene, camera)

    requestAnimationFrame(render)
  }

  requestAnimationFrame(render)

  const color = 0xffffff
  const intensity = 1
  const light = new THREE.AmbientLight(color, intensity)
  scene.add(light)

  renderer.render(scene, camera)
}

2.4 正交相机(OrthographicCamera)

这一摄像机使用orthographic projection(正交投影)来进行投影。

在这种投影模式下,无论物体距离相机距离远或者近,在最终渲染的图片中物体的大小都保持不变。

这对于渲染2D场景或者UI元素是非常有用的。

  • left --- 摄像机视锥体左侧面。
  • right --- 摄像机视锥体右侧面。
  • top --- 摄像机视锥体上侧面。
  • bottom --- 摄像机视锥体下侧面。
  • near --- 摄像机视锥体近端面。
  • far --- 摄像机视锥体远端面。

属性:

  • .bottom : Float
      摄像机视锥体下侧面。
  • .far : Float
      摄像机视锥体远端面,其默认值为2000。
      该值必须大于near plane(摄像机视锥体近端面)的值。
  • .isOrthographicCamera : Boolean
      Read-only flag to check if a given object is of type OrthographicCamera.
  • .left : Float
      摄像机视锥体左侧面。
  • .near : Float
      摄像机视锥体近端面。其默认值为0.1.
      其值的有效范围介于0和far(摄像机视锥体远端面)之间。
      请注意,和PerspectiveCamera不同,0对于OrthographicCamera的近端面来说是一个有效值。
  • .right : Float
      摄像机视锥体右侧面。
  • .top : Float
      摄像机视锥体上侧面。
  • .view : Object
      这个值是由setViewOffset来设置的,其默认值为null。
  • .zoom : number
      获取或者设置摄像机的缩放倍数,其默认值为1。

方法:

  • .setViewOffset ( fullWidth : Float, fullHeight : Float, x : Float, y : Float, width : Float, height : Float ) : undefined
    fullWidth --- 多视图的全宽设置
    fullHeight --- 多视图的全高设置
    x --- 副摄像机的水平偏移
    y --- 副摄像机的垂直偏移
    width --- 副摄像机的宽度
    height --- 副摄像机的高度
      在较大的viewing frustum(视锥体)中设置偏移量,对于多窗口或者多显示器的设置是很有用的。 对于如何使用它,请查看PerspectiveCamera中的示例。
  • .clearViewOffset () : undefined
      清除任何由.setViewOffset设置的偏移量。
  • .updateProjectionMatrix () : undefined
      更新摄像机投影矩阵。在任何参数被改变以后必须被调用。
  • .toJSON (meta : Object) : Object
      meta -- 包含有元数据的对象,例如对象后代中的纹理或图像
      将摄像机转换为 three.js JSON Object/Scene format(three.js JSON 物体/场景格式)。
javascript 复制代码
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'

export default () => {
  const canvas = document.querySelector('#demo')

  const renderer = new THREE.WebGLRenderer({
    antialias: true,
    canvas,
  })

  const scene = new THREE.Scene()
  scene.background = new THREE.Color('black')
  scene.rotation.y = 0.5 // avoid flying objects occluding the sun

  const camera = new THREE.OrthographicCamera(
    window.innerWidth / -2,
    window.innerWidth / 2,
    window.innerHeight / 2,
    window.innerHeight / -2,
    1,
    1000,
  )
  camera.position.set(0, 10, 20)

  // Create cube render target
  const cubeRenderTarget = new THREE.WebGLCubeRenderTarget(128, {
    generateMipmaps: true,
    minFilter: THREE.LinearMipmapLinearFilter,
  })
  cubeRenderTarget.texture.type = THREE.HalfFloatType

  // Create cube camera
  const cubeCamera = new THREE.CubeCamera(1, 1000, cubeRenderTarget)
  scene.add(camera)

  const controls = new OrbitControls(camera, canvas)
  controls.target.set(0, 5, 0)
  controls.update()

  {
    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 = 2
    const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize)
    const cubeMat = new THREE.MeshPhongMaterial({ color: 'rgba(199, 6, 6, 1)' })
    const mesh = new THREE.Mesh(cubeGeo, cubeMat)
    mesh.position.set(cubeSize + 1, cubeSize / 2, 0)
    scene.add(mesh)
  }
  {
    const sphereRadius = 1
    const sphereWidthDivisions = 32
    const sphereHeightDivisions = 16
    const sphereGeo = new THREE.SphereGeometry(
      sphereRadius,
      sphereWidthDivisions,
      sphereHeightDivisions,
    )
    const sphereMat = new THREE.MeshPhongMaterial({ color: 'rgba(14, 197, 60, 1)' })
    const mesh = new THREE.Mesh(sphereGeo, sphereMat)
    mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0)
    scene.add(mesh)
  }

  function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
    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()
    }

    cubeCamera.update(renderer, scene)

    controls.update()

    renderer.render(scene, camera)

    requestAnimationFrame(render)
  }

  requestAnimationFrame(render)

  const color = 0xffffff
  const intensity = 1
  const light = new THREE.AmbientLight(color, intensity)
  scene.add(light)

  renderer.render(scene, camera)
}

2.5 透视相机(PerspectiveCamera)

这一摄像机使用perspective projection(透视投影)来进行投影。

这一投影模式被用来模拟人眼所看到的景象,它是3D场景的渲染中使用得最普遍的投影模式。

  • fov --- 摄像机视锥体垂直视野角度
  • aspect --- 摄像机视锥体长宽比
  • near --- 摄像机视锥体近端面
  • far --- 摄像机视锥体远端面

属性:

  • .aspect : Float
      摄像机视锥体的长宽比,通常是使用画布的宽/画布的高。默认值是1(正方形画布)。
  • .far : Float
      摄像机的远端面,默认值是2000。
      该值必须大于near plane(摄像机视锥体近端面)的值。
  • .filmGauge : Float
      胶片尺寸,其默认值为35(毫米)。 这个参数不会影响摄像机的投影矩阵,除非.filmOffset被设置为了一个非零的值。
  • .filmOffset : Float
      水平偏离中心偏移量,和.filmGauge单位相同。默认值为0。
  • .focus : Float
      用于立体视觉和景深效果的物体的距离。 这个参数不会影响摄像机的投影矩阵,除非使用了StereoCamera。 默认值是10。
  • .fov : Float
      摄像机视锥体垂直视野角度,从视图的底部到顶部,以角度来表示。默认值是50。
  • .isPerspectiveCamera : Boolean
      Read-only flag to check if a given object is of type PerspectiveCamera.
  • .near : Float
      摄像机的近端面,默认值是0.1。
      其有效值范围是0到当前摄像机far plane(远端面)的值之间。 请注意,和OrthographicCamera不同,0对于PerspectiveCamera的近端面来说不是一个有效值。
  • .view : Object
      Frustum window specification or null. 这个值使用.setViewOffset方法来进行设置,使用.clearViewOffset方法来进行清除。
  • .zoom : number
      获取或者设置摄像机的缩放倍数,其默认值为1。
    方法:
  • .clearViewOffset () : undefined
      清除任何由.setViewOffset设置的偏移量。
  • .getEffectiveFOV () : Float
      结合.zoom(缩放倍数),以角度返回当前垂直视野角度。
  • .getFilmHeight () : Float
      返回当前胶片上图像的高,如果.aspect小于或等于1(肖像格式、纵向构图),则结果等于.filmGauge。
  • .getFilmWidth () : Float
      返回当前胶片上图像的宽,如果.aspect大于或等于1(景观格式、横向构图),则结果等于.filmGauge。
  • .getFocalLength () : Float
      返回当前.fov(视野角度)相对于.filmGauge(胶片尺寸)的焦距。
  • .setFocalLength ( focalLength : Float ) : undefined
      通过相对于当前.filmGauge的焦距,设置FOV。
      默认情况下,焦距是为35mm(全画幅)摄像机而指定的。
  • .setViewOffset ( fullWidth : Float, fullHeight : Float, x : Float, y : Float, width : Float, height : Float ) : undefined
    fullWidth --- 多视图的全宽设置
    fullHeight --- 多视图的全高设置
    x --- 副摄像机的水平偏移
    y --- 副摄像机的垂直偏移
    width --- 副摄像机的宽度
    height --- 副摄像机的高度
      在较大的viewing frustum(视锥体)中设置偏移量,对于多窗口或者多显示器的设置是很有用的。
      例如,如果你有一个3x2的显示器阵列,每个显示器分辨率都是1920x1080,且这些显示器排列成像这样的网格:
    ±--±--±--+
    | A | B | C |
    ±--±--±--+
    | D | E | F |
    ±--±--±--+
      那对于每个显示器,你可以这样来设置、调用:
javascript 复制代码
const w = 1920;
const h = 1080;
const fullWidth = w * 3;
const fullHeight = h * 2;

// A
camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h );
// B
camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h );
// C
camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h );
// D
camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h );
// E
camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h );
// F
camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h );请注意,显示器的不必具有相同的大小,或者不必在网格中。
  • .updateProjectionMatrix () : undefined
      更新摄像机投影矩阵。在任何参数被改变以后必须被调用。
  • .toJSON (meta : Object) : Object
      meta -- 包含有元数据的对象,例如对象后代中的纹理或图像将摄像机转换为 three.js JSON Object/Scene format(three.js JSON 物体/场景格式)。
javascript 复制代码
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'

export default () => {
  const canvas = document.querySelector('#demo')

  const renderer = new THREE.WebGLRenderer({
    antialias: true,
    canvas,
  })

  const fov = 45
  const aspect = 2 // canvas 的默认宽高 300:150
  const near = 0.1
  const far = 100
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
  camera.position.set(0, 10, 20)

  const scene = new THREE.Scene()
  scene.background = new THREE.Color('black')

  const controls = new OrbitControls(camera, canvas)
  controls.target.set(0, 5, 0)
  controls.update()

  //   const video = document.getElementById('video')
  //   const texture = new THREE.VideoTexture(video)

  //   const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, map: texture })

  //   const cubes: THREE.Mesh[] = [] // just an array we can use to rotate the cubes
  //   const cube = new THREE.Mesh(geometry, material)
  {
    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)
  }
  //   cubes.push(mesh)

  {
    const cubeSize = 2
    const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, cubeSize)
    const cubeMat = new THREE.MeshPhongMaterial({ color: 'rgba(199, 6, 6, 1)' })
    const mesh = new THREE.Mesh(cubeGeo, cubeMat)
    mesh.position.set(cubeSize + 1, cubeSize / 2, 0)
    scene.add(mesh)
  }
  {
    const sphereRadius = 1
    const sphereWidthDivisions = 32
    const sphereHeightDivisions = 16
    const sphereGeo = new THREE.SphereGeometry(
      sphereRadius,
      sphereWidthDivisions,
      sphereHeightDivisions,
    )
    const sphereMat = new THREE.MeshPhongMaterial({ color: 'rgba(14, 197, 60, 1)' })
    const mesh = new THREE.Mesh(sphereGeo, sphereMat)
    mesh.position.set(-sphereRadius - 1, sphereRadius + 2, 0)
    scene.add(mesh)
  }

  const color = 0xffffff
  const intensity = 150
  const light = new THREE.SpotLight(color, intensity)
  light.position.set(0, 10, 0)
  light.target.position.set(-5, -10, 0)
  scene.add(light)
  scene.add(light.target)

  function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
    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)
}

2.6 立体相机(StereoCamera)

透视摄像机(立体相机)常被用于创建3D Anaglyph(3D立体影像) 或者Parallax Barrier(视差屏障)。
属性:

  • .aspect : Float
      默认值是1.
  • .eyeSep : Float
      默认值是0.064.
  • .cameraL : PerspectiveCamera
      左摄像机,它被加入到了layer 1中 ------ 需要被左摄像机渲染的物体也应当要加入到这一层中。
  • .cameraR : PerspectiveCamera
      右摄像机,它被加入到了layer 2中 ------ 需要被右摄像机渲染的物体也应当要加入到这一层中。
    方法
  • .update ( camera : PerspectiveCamera ) : undefined
      基于摄像机通过场景,更新立体摄像机。
javascript 复制代码
import * as THREE from 'three'
import { StereoEffect } from 'three/addons/effects/StereoEffect.js'

export default () => {
  const spheres: THREE.Mesh[] = []

  let mouseX = 0,
    mouseY = 0

  let windowHalfX = window.innerWidth / 2
  let windowHalfY = window.innerHeight / 2
  function onDocumentMouseMove(event: MouseEvent) {
    mouseX = (event.clientX - windowHalfX) * 10
    mouseY = (event.clientY - windowHalfY) * 10
  }
  document.addEventListener('mousemove', onDocumentMouseMove)

  const canvas = document.querySelector('#demo')

  const renderer = new THREE.WebGLRenderer({
    antialias: true,
    canvas,
  })

  const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 100000)
  camera.position.z = 3200

  const scene = new THREE.Scene()
  scene.background = new THREE.Color('black')

  const geometry = new THREE.SphereGeometry(100, 32, 16)

  const material = new THREE.MeshBasicMaterial({
    color: 0xff00dd,
    refractionRatio: 0.95,
  })

  for (let i = 0; i < 500; i++) {
    const mesh = new THREE.Mesh(geometry, material)
    mesh.position.x = Math.random() * 10000 - 5000
    mesh.position.y = Math.random() * 10000 - 5000
    mesh.position.z = Math.random() * 10000 - 5000
    mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 3 + 1
    scene.add(mesh)

    spheres.push(mesh)
  }
  const effect = new StereoEffect(renderer)
  effect.setSize(window.innerWidth, window.innerHeight)

  const color = 0xffffff
  const intensity = 150
  const light = new THREE.SpotLight(color, intensity)
  light.position.set(0, 10, 0)
  light.target.position.set(-5, -10, 0)
  scene.add(light)
  scene.add(light.target)

  function render() {
    const timer = 0.0001 * Date.now()

    camera.position.x += (mouseX - camera.position.x) * 0.05
    camera.position.y += (-mouseY - camera.position.y) * 0.05
    camera.lookAt(scene.position)

    for (let i = 0, il = spheres.length; i < il; i++) {
      const sphere = spheres[i]

      sphere.position.x = 5000 * Math.cos(timer + i)
      sphere.position.y = 5000 * Math.sin(timer + i * 1.1)
    }

    effect.render(scene, camera)

    requestAnimationFrame(render)
  }

  requestAnimationFrame(render)
}

三、阴影相关内容

阴影贴图的工作方式就是具有投射阴影的光能对所有能被投射阴影的物体从光源渲染阴影。

3.1 LightShadow该类作为其他阴影类的基类来使用。

创建一个新的LightShadow。这不能直接调用的 - 它由其他阴影用作基类。
属性:

  • .autoUpdate : Boolean
      Enables automatic updates of the light's shadow. Default is true. If you do not require dynamic lighting / shadows, you may set this to false.
  • .camera : Camera
      光的世界里。这用于生成场景的深度图;从光的角度来看,其他物体背后的物体将处于阴影中。
  • .bias : Float
      阴影贴图偏差,在确定曲面是否在阴影中时,从标准化深度添加或减去多少。
      默认值为0.此处非常小的调整(大约0.0001)可能有助于减少阴影中的伪影
  • .blurSamples : Integer
      The amount of samples to use when blurring a VSM shadow map.
  • .map : WebGLRenderTarget
      使用内置摄像头生成的深度图;超出像素深度的位置在阴影中。在渲染期间内部计算。
  • .mapPass : WebGLRenderTarget
      The distribution map generated using the internal camera; an occlusion is calculated based on the distribution of depths. Computed internally during rendering.
  • .mapSize : Vector2
      一个Vector2定义阴影贴图的宽度和高度。
      较高的值会以计算时间为代价提供更好的阴影质量。值必须是2的幂,直到给定设备的WebGLRenderer.capabilities.maxTextureSize, 虽然宽度和高度不必相同(例如,(512,1024)有效)。 默认值为(512,512)。
  • .matrix : Matrix4
      模拟阴影相机空间,计算阴影贴图中的位置和深度。存储在Matrix4中。这是在渲染期间内部计算的。
  • .needsUpdate : Boolean
      When set to true, shadow maps will be updated in the next render call. Default is false. If you have set .autoUpdate to false, you will need to set this property to true and then make a render call to update the light's shadow.
  • .normalBias : Float
      Defines how much the position used to query the shadow map is offset along the object normal. The default is 0. Increasing this value can be used to reduce shadow acne especially in large scenes where light shines onto geometry at a shallow angle. The cost is that shadows may appear distorted.
  • .radius : Float
      将此值设置为大于1的值将模糊阴影的边缘。
      较高的值会在阴影中产生不必要的条带效果 - 更大的mapSize将允许在这些效果变得可见之前使用更高的值。
      If WebGLRenderer.shadowMap.type is set to PCFSoftShadowMap, radius has no effect and it is recommended to increase softness by decreasing mapSize instead.

请注意,如果[page:WebGLRenderer.shadowMap.type]设置为BasicShadowMap,将会无效。
方法:

  • .getFrameExtents () : Vector2
      Used internally by the renderer to extend the shadow map to contain all viewports
  • .updateMatrices ( light : Light ) : undefined
      Update the matrices for the camera and shadow, used internally by the renderer.
      light -- the light for which the shadow is being rendered.
  • .getFrustum () : Frustum
      Gets the shadow cameras frustum. Used internally by the renderer to cull objects.
  • .getViewportCount () : number
      Used internally by the renderer to get the number of viewports that need to be rendered for this shadow.
  • .copy ( source : LightShadow ) : this
      将source中的所有属性的值复制到该Light。
  • .clone () : LightShadow
      克隆与此相同属性的新LightShadow。
  • .toJSON () : Object
      序列化这个LightShadow。
javascript 复制代码
import * as THREE from 'three'
export default () => {
  const canvas = document.querySelector('#demo')

  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)
  camera.lookAt(0, 0, 0)

  const scene = new THREE.Scene()
  scene.background = new THREE.Color('#fff')

  const loader = new THREE.TextureLoader()
  {
    const planeSize = 40
    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.MeshBasicMaterial({
      map: texture,
      side: THREE.DoubleSide,
    })
    planeMat.color.setRGB(1.5, 1.5, 1.5)
    const mesh = new THREE.Mesh(planeGeo, planeMat)
    mesh.rotation.x = Math.PI * -1.5
    scene.add(mesh)
  }

  const shadowTexture = loader.load(
    'https://threejs.org/manual/examples/resources/images/roundshadow.png',
  )
  interface SphereShadow {
    base: THREE.Object3D
    sphereMesh: THREE.Mesh
    shadowMesh: THREE.Mesh
    y: number
  }
  const sphereShadowBases: SphereShadow[] = []
  {
    const sphereRadius = 1
    const sphereWidthDivisions = 32
    const sphereHeightDivisions = 16
    const sphereGeo = new THREE.SphereGeometry(
      sphereRadius,
      sphereWidthDivisions,
      sphereHeightDivisions,
    )

    const planeSize = 1
    const shadowGeo = new THREE.PlaneGeometry(planeSize, planeSize)
    const numSpheres = 15
    for (let i = 0; i < numSpheres; ++i) {
      // make a base for the shadow and the sphere.
      // so they move together.
      const base = new THREE.Object3D()
      scene.add(base)

      // add the shadow to the base
      // note: we make a new material for each sphere
      // so we can set that sphere's material transparency
      // separately.
      const shadowMat = new THREE.MeshBasicMaterial({
        map: shadowTexture,
        transparent: true, // so we can see the ground
        depthWrite: false, // so we don't have to sort
      })
      const shadowMesh = new THREE.Mesh(shadowGeo, shadowMat)
      shadowMesh.position.y = 0.001 // so we're above the ground slightly
      shadowMesh.rotation.x = Math.PI * -0.5
      const shadowSize = sphereRadius * 4
      shadowMesh.scale.set(shadowSize, shadowSize, shadowSize)
      base.add(shadowMesh)

      // add the sphere to the base
      const u = i / numSpheres
      const sphereMat = new THREE.MeshPhongMaterial()
      sphereMat.color.setHSL(u, 1, 0.75)
      const sphereMesh = new THREE.Mesh(sphereGeo, sphereMat)
      sphereMesh.position.set(0, sphereRadius + 2, 0)
      base.add(sphereMesh)

      // remember all 3 plus the y position
      sphereShadowBases.push({ base, sphereMesh, shadowMesh, y: sphereMesh.position.y })
    }
  }

  {
    const skyColor = 0xb1e1ff // light blue
    const groundColor = 0xb97a20 // brownish orange
    const intensity = 0.75
    const light = new THREE.HemisphereLight(skyColor, groundColor, intensity)
    scene.add(light)
  }

  {
    const color = 0xffffff
    const intensity = 2.5
    const light = new THREE.DirectionalLight(color, intensity)
    light.position.set(0, 10, 5)
    light.target.position.set(-5, 0, 0)
    scene.add(light)
    scene.add(light.target)
  }

  function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
    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(time: number) {
    time *= 0.001 // convert to seconds

    resizeRendererToDisplaySize(renderer)

    {
      const canvas = renderer.domElement
      camera.aspect = canvas.clientWidth / canvas.clientHeight
      camera.updateProjectionMatrix()
    }

    sphereShadowBases.forEach((sphereShadowBase, ndx) => {
      const { base, sphereMesh, shadowMesh, y } = sphereShadowBase

      // u is a value that goes from 0 to 1 as we iterate the spheres
      const u = ndx / sphereShadowBases.length

      // compute a position for there base. This will move
      // both the sphere and its shadow
      const speed = time * 0.2
      const angle = speed + u * Math.PI * 2 * (ndx % 1 ? 1 : -1)
      const radius = Math.sin(speed - ndx) * 10
      base.position.set(Math.cos(angle) * radius, 0, Math.sin(angle) * radius)

      // yOff is a value that goes from 0 to 1
      const yOff = Math.abs(Math.sin(time * 2 + ndx))
      // move the sphere up and down
      sphereMesh.position.y = y + THREE.MathUtils.lerp(-2, 2, yOff)
      // fade the shadow as the sphere goes up
      shadowMesh.material.opacity = THREE.MathUtils.lerp(1, 0.25, yOff)
    })

    renderer.render(scene, camera)

    requestAnimationFrame(render)
  }
  requestAnimationFrame(render)
}

3.2 PointLightShadow该类在内部由PointLights所使用,以用于计算阴影。

  • .isPointLightShadow : Boolean
      Read-only flag to check if a given object is of type PointLightShadow.
  • .updateMatrices ( light : Light, viewportIndex : number) : undefined
      Update the matrices for the camera and shadow, used internally by the renderer.
      light -- the light for which the shadow is being rendered.
      viewportIndex -- calculates the matrix for this viewport
javascript 复制代码
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
export default () => {
  const canvas = document.querySelector('#demo')

  const renderer = new THREE.WebGLRenderer({
    antialias: true,
    canvas,
  })
  renderer.shadowMap.enabled = true
  renderer.shadowMap.type = THREE.PCFSoftShadowMap // default THREE.PCFShadowMap

  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)
  camera.lookAt(0, 0, 0)

  const scene = new THREE.Scene()
  // scene.background = new THREE.Color('#fff')

  //Create a PointLight and turn on shadows for the light
  const light = new THREE.PointLight(0xffffff, 1, 100)
  light.position.set(0, 10, 4)
  light.castShadow = true // default false
  scene.add(light)

  //Set up shadow properties for the light
  light.shadow.mapSize.width = 512 // default
  light.shadow.mapSize.height = 512 // default
  light.shadow.camera.near = 0.5 // default
  light.shadow.camera.far = 500 // default

  //Create a sphere that cast shadows (but does not receive them)
  const sphereGeometry = new THREE.SphereGeometry(5, 32, 32)
  const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 })
  const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
  sphere.castShadow = true //default is false
  sphere.receiveShadow = false //default
  scene.add(sphere)

  //Create a plane that receives shadows (but does not cast them)
  const planeGeometry = new THREE.PlaneGeometry(20, 20, 32, 32)
  const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 })
  const plane = new THREE.Mesh(planeGeometry, planeMaterial)
  plane.receiveShadow = true
  scene.add(plane)

  //Create a helper for the shadow camera (optional)
  const helper = new THREE.CameraHelper(light.shadow.camera)
  scene.add(helper)

  function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
    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(time: number) {
    time *= 0.001 // convert to seconds

    resizeRendererToDisplaySize(renderer)

    {
      const canvas = renderer.domElement
      camera.aspect = canvas.clientWidth / canvas.clientHeight
      camera.updateProjectionMatrix()
    }

    renderer.render(scene, camera)

    requestAnimationFrame(render)
  }
  const controls = new OrbitControls(camera, canvas)
  controls.target.set(0, 5, 0)
  controls.update()
  requestAnimationFrame(render)
}

3.3 DirectionalLightShadow这是用于在DirectionalLights内部计算阴影

与其他阴影类不同,它是使用OrthographicCamera来计算阴影,而不是PerspectiveCamera。这是因为来自DirectionalLight的光线是平行的。

  • .camera : Camera
      在光的世界里。这用于生成场景的深度图;从光的角度来看,其他物体背后的物体将处于阴影中。
  • .isDirectionalLightShadow : Boolean
      Read-only flag to check if a given object is of type DirectionalLightShadow.
javascript 复制代码
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
export default () => {
  const canvas = document.querySelector('#demo')

  const renderer = new THREE.WebGLRenderer({
    antialias: true,
    canvas,
  })
  renderer.shadowMap.enabled = true
  renderer.shadowMap.type = THREE.PCFSoftShadowMap // default THREE.PCFShadowMap

  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)
  camera.lookAt(0, 0, 0)

  const scene = new THREE.Scene()
  // scene.background = new THREE.Color('#fff')

  //Create a DirectionalLight and turn on shadows for the light
  const light = new THREE.DirectionalLight(0xffffff, 1)
  light.position.set(0, 1, 0) //default; light shining from top
  light.castShadow = true // default false
  scene.add(light)

  //Set up shadow properties for the light
  light.shadow.mapSize.width = 512 // default
  light.shadow.mapSize.height = 512 // default
  light.shadow.camera.near = 0.5 // default
  light.shadow.camera.far = 500 // default

  //Create a sphere that cast shadows (but does not receive them)
  const sphereGeometry = new THREE.SphereGeometry(5, 32, 32)
  const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 })
  const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
  sphere.castShadow = true //default is false
  sphere.receiveShadow = false //default
  scene.add(sphere)

  //Create a plane that receives shadows (but does not cast them)
  const planeGeometry = new THREE.PlaneGeometry(20, 20, 32, 32)
  const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 })
  const plane = new THREE.Mesh(planeGeometry, planeMaterial)
  plane.receiveShadow = true
  scene.add(plane)

  //Create a helper for the shadow camera (optional)
  const helper = new THREE.CameraHelper(light.shadow.camera)
  scene.add(helper)

  function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
    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(time: number) {
    time *= 0.001 // convert to seconds

    resizeRendererToDisplaySize(renderer)

    {
      const canvas = renderer.domElement
      camera.aspect = canvas.clientWidth / canvas.clientHeight
      camera.updateProjectionMatrix()
    }

    renderer.render(scene, camera)

    requestAnimationFrame(render)
  }
  const controls = new OrbitControls(camera, canvas)
  controls.target.set(0, 5, 0)
  controls.update()
  requestAnimationFrame(render)
}

3.4 SpotLightShadow 这在SpotLights内部用于计算阴影。

  • .camera : Camera
      在光的世界里。这用于生成场景的深度图;从光的角度来看,其他物体背后的物体将处于阴影中。
      默认值为PerspectiveCamera,近剪裁平面为0.5。 fov将通过更新方法跟踪拥有SpotLight的角度属性。同样,aspect属性将跟踪mapSize的方面。如果设置了灯光的距离属性,则远剪裁平面将跟踪该值,否则默认为500。
  • .focus : Number
      Used to focus the shadow camera. The camera's field of view is set as a percentage of the spotlight's field-of-view. Range is [0, 1]. Default is 1.0.
  • .isSpotLightShadow : Boolean
      Read-only flag to check if a given object is of type SpotLightShadow.
javascript 复制代码
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
export default () => {
  const canvas = document.querySelector('#demo')

  const renderer = new THREE.WebGLRenderer({
    antialias: true,
    canvas,
  })
  renderer.shadowMap.enabled = true
  renderer.shadowMap.type = THREE.PCFSoftShadowMap // default THREE.PCFShadowMap

  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)
  camera.lookAt(0, 0, 0)

  const scene = new THREE.Scene()
  scene.background = new THREE.Color('#fff')

  //Create a SpotLight and turn on shadows for the light
  const light = new THREE.SpotLight(0xffffff)
  light.castShadow = true // default false
  scene.add(light)

  //Set up shadow properties for the light
  light.shadow.mapSize.width = 512 // default
  light.shadow.mapSize.height = 512 // default
  light.shadow.camera.near = 0.5 // default
  light.shadow.camera.far = 500 // default
  light.shadow.focus = 1 // default

  //Create a sphere that cast shadows (but does not receive them)
  const sphereGeometry = new THREE.SphereGeometry(5, 32, 32)
  const sphereMaterial = new THREE.MeshStandardMaterial({ color: 0xff0000 })
  const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)
  sphere.castShadow = true //default is false
  sphere.receiveShadow = false //default
  scene.add(sphere)

  //Create a plane that receives shadows (but does not cast them)
  const planeGeometry = new THREE.PlaneGeometry(20, 20, 32, 32)
  const planeMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00 })
  const plane = new THREE.Mesh(planeGeometry, planeMaterial)
  plane.receiveShadow = true
  scene.add(plane)

  //Create a helper for the shadow camera (optional)
  const helper = new THREE.CameraHelper(light.shadow.camera)
  scene.add(helper)

  function resizeRendererToDisplaySize(renderer: THREE.WebGLRenderer) {
    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(time: number) {
    time *= 0.001 // convert to seconds

    resizeRendererToDisplaySize(renderer)

    {
      const canvas = renderer.domElement
      camera.aspect = canvas.clientWidth / canvas.clientHeight
      camera.updateProjectionMatrix()
    }

    renderer.render(scene, camera)

    requestAnimationFrame(render)
  }
  const controls = new OrbitControls(camera, canvas)
  controls.target.set(0, 5, 0)
  controls.update()
  requestAnimationFrame(render)
}
相关推荐
Morwit12 小时前
如何使用CMake构建Qt新项目
开发语言·c++·qt
独自破碎E12 小时前
Leetcode1499满足不等式的最大值
java·开发语言
zmzb010313 小时前
C++课后习题训练记录Day62
开发语言·c++
蕨蕨学AI13 小时前
【Wolfram语言】36 创建云端应用
开发语言·wolfram
lambo mercy13 小时前
python入门
前端·数据库·python
GIS之路13 小时前
GDAL 实现矢量数据读写
前端
不要em0啦14 小时前
从0开始学python:简单的练习题4
开发语言·python
我想吃余14 小时前
【C++篇】C++11:线程库
开发语言·c++
yesyesido14 小时前
AI手办工坊:3D渲染级二次元写真生成、多风格角色定制与高清无损下载的一键创作平台
人工智能·3d