Three.js基础功能学习三:纹理与光照

纹理一般是指我们常见的在一些第三方程序中创建的图像,如Photoshop或GIMP。

OrbitControls 让我们可以围绕某一个点旋转控制相机。OrbitControls 是 three.js 的可选模块,所以我们首先需要引入这个模块。

一、学习视频

three.js学习及项目实战:三维纹理学习

二、纹理相关内容

纹理一般是指我们常见的在一些第三方程序中创建的图像,如Photoshop或GIMP。比如我们把这张图片放在立方体上。

javascript 复制代码
const loader = new THREE.TextureLoader();
const texture = loader.load( 'resources/images/wall.jpg' );
texture.colorSpace = THREE.SRGBColorSpace;

要等到所有纹理都加载完毕,你可以使用 LoadingManager 。创建一个并将其传递给 TextureLoader,然后将其onLoad属性设置为回调。

javascript 复制代码
const loadManager = new THREE.LoadingManager();
const loader = new THREE.TextureLoader(loadManager);

越小越好,而且看起来仍然是你需要的样子。

GPU怎么知道小立方体的每一个像素需要使用哪些颜色?如果立方体小到只有1、2个像素呢?

这就是过滤(filtering)的意义所在。

Mips 是纹理的副本,每一个都是前一个 mip 的一半宽和一半高,其中的像素已经被混合以制作下一个较小的 mip。Mips一直被创建,直到我们得到1x1像素的Mip。

  • THREE.NearestFilter
      同上,在纹理中选择最近的像素。
  • THREE.LinearFilter
      和上面一样,从纹理中选择4个像素,然后混合它们
  • THREE.NearestMipmapNearestFilter
      选择合适的mip,然后选择一个像素。
  • THREE.NearestMipmapLinearFilter
      选择2个mips,从每个mips中选择一个像素,混合这2个像素。
  • THREE.LinearMipmapNearestFilter
      选择合适的mip,然后选择4个像素并将它们混合。
  • THREE.LinearMipmapLinearFilter
      选择2个mips,从每个mips中选择4个像素,然后将所有8个像素混合成1个像素。

纹理有重复、偏移和旋转纹理的设置。默认情况下,three.js中的纹理是不重复的。要设置纹理是否重复,有2个属性,wrapS 用于水平包裹,wrapT 用于垂直包裹。它们可以被设置为以下其中一个:

  • THREE.ClampToEdgeWrapping
      每条边上的最后一个像素无限重复。
  • THREE.RepeatWrapping
      纹理重复
  • THREE.MirroredRepeatWrapping
      在每次重复时将进行镜像

2.1. CanvasTexture Canvas纹理

  • canvas -- 将会被用于加载纹理贴图的Canvas元素。
  • mapping -- 纹理贴图将被如何应用(映射)到物体上,它是THREE.UVMapping中的对象类型。 请  参阅mapping constants(映射模式常量)来了解其他选项。
  • wrapS -- 默认值是THREE.ClampToEdgeWrapping. 请参阅wrap mode constants(包裹模式常量)来了解其他选项。
  • wrapT -- 默认值是THREE.ClampToEdgeWrapping. 请参阅wrap mode constants(包裹模式常量)来了解其他选项。
  • magFilter -- 当一个纹素覆盖大于一个像素时,贴图将如何采样。 其默认值为THREE.LinearFilter。  请参阅magnification filter constants(放大滤镜常量)来了解其它选项。
  • minFilter -- 当一个纹素覆盖小于一个像素时,贴图将如何采样。 其默认值为  THREE.LinearMipmapLinearFilter。请参阅minification filter constants(缩小滤镜常量)来了解其它选项。
  • format -- 在纹理贴图中使用的格式。 请参阅format constants(格式常量)来了解各个选项。
  • type -- 默认值是THREE.UnsignedByteType. 请参阅type constants(类型常量)来了解其他选项。
  • anisotropy -- 沿着轴,通过具有最高纹素密度的像素的样本数。 默认情况下,这个值为1。设置一个  较高的值将会产生比基本的mipmap更清晰的效果,代价是需要使用更多纹理样本。 使用renderer.getMaxAnisotropy() 来查询GPU中各向异性的最大有效值;这个值通常是2的幂。
  • .isCanvasTexture: Boolean
      Read-only flag to check if a given object is of type CanvasTexture.
  • .needsUpdate : Boolean
      默认值为true,这是必须的,以便使得Canvas中的数据能够载入。
javascript 复制代码
import * as THREE from 'three'

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

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

  const fov = 75
  const aspect = 2 // the canvas default
  const near = 0.1
  const far = 5
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
  camera.position.z = 2

  const scene = new THREE.Scene()

  const geometry = new THREE.BoxGeometry(1, 1, 1)

  //   const loader = new THREE.TextureLoader()
  //   const texture = loader.load('https://threejs.org/manual/examples/resources/images/wall.jpg')
  const texture = new THREE.CanvasTexture(document.querySelector('#img'))
  texture.colorSpace = THREE.SRGBColorSpace
  const material = new THREE.MeshBasicMaterial({ map: texture })

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

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

    cubes.forEach((cube, ndx) => {
      const speed = 0.2 + ndx * 0.1
      const rot = time * speed
      cube.rotation.x = rot
      cube.rotation.y = rot
    })

    renderer.render(scene, camera)

    requestAnimationFrame(render)
  }

  requestAnimationFrame(render)
}

2.2. CompressedTexture 压缩的纹理

  • mipmaps -- mipmaps数组中需要包含具有数据、宽、高的对象。mipmaps应当具有正确的格式与类型。
  • width -- 最大的mipmap的宽。
  • height -- 最大的mipmap的高。
  • format -- 在mipmaps中使用的格式。 请参阅ST3C Compressed Texture Formats、 PVRTC Compressed Texture Formats和 ETC Compressed Texture Format页面来了解其它选项。
  • type -- 默认值是THREE.UnsignedByteType。 请参阅type constants页面来了解其它选项。
  • mapping -- 纹理贴图将被如何应用(映射)到物体上,它是THREE.UVMapping中的对象类型。 请参阅mapping constants(映射模式常量)来了解其他选项。
  • wrapS -- 默认值是THREE.ClampToEdgeWrapping. 请参阅wrap mode constants(包裹模式常量)来了解其他选项。
  • wrapT -- 默认值是THREE.ClampToEdgeWrapping. 请参阅wrap mode constants(包裹模式常量)来了解其他选项。
  • magFilter -- 当一个纹素覆盖大于一个像素时,贴图将如何采样。 其默认值为THREE.LinearFilter。请参阅magnification filter constants(放大滤镜常量)来了解其它选项。
  • minFilter -- 当一个纹素覆盖小于一个像素时,贴图将如何采样。 其默认值为THREE.LinearMipmapLinearFilter。请参阅minification filter constants(缩小滤镜常量)来了解其它选项。
  • anisotropy -- 沿着轴,通过具有最高纹素密度的像素的样本数。 默认情况下,这个值为1。设置一个较高的值将会产生比基本的mipmap更清晰的效果,代价是需要使用更多纹理样本。 使用renderer.getMaxAnisotropy() 来查询GPU中各向异性的最大有效值;这个值通常是2的幂。
  • colorSpace -- The default is THREE.NoColorSpace. See color space constants for other choices.
  • .flipY : Boolean
      默认值为false。翻转纹理在压缩的纹理贴图中无法工作。
  • .generateMipmaps : Boolean
      默认值为false。无法为压缩的纹理贴图生成Mipmap。
  • .isCompressedTexture : Boolean
    Read-only flag to check if a given object is of type CompressedTexture.
javascript 复制代码
import * as THREE from 'three'

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

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

  const fov = 75
  const aspect = 2 // the canvas default
  const near = 0.1
  const far = 5
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
  camera.position.z = 2

  const scene = new THREE.Scene()

  const geometry = new THREE.BoxGeometry(1, 1, 1)

  const loader = new THREE.CompressedTextureLoader()
  const texture = loader.load('https://threejs.org/manual/examples/resources/images/wall.jpg')
  texture.colorSpace = THREE.SRGBColorSpace
  const material = new THREE.MeshBasicMaterial({ map: texture })

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

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

    cubes.forEach((cube, ndx) => {
      const speed = 0.2 + ndx * 0.1
      const rot = time * speed
      cube.rotation.x = rot
      cube.rotation.y = rot
    })

    renderer.render(scene, camera)

    requestAnimationFrame(render)
  }

  requestAnimationFrame(render)
}

2.3. CompressedArrayTexture 基于压缩形式的数据(例如从DDS文件)创建纹理2D数组

  • mipmaps -- mipmaps数组应包含具有数据、宽度和高度的对象。mipmap 应具有正确的格式和类型。
  • width -- 最大 mipmap 的宽度
  • height -- 最大 mipmap 的高度
  • depth -- 二维阵列纹理的层数。
  • format -- mipmap 中使用的格式。 有关其他选择,请参阅ST3C Compressed Texture Formats 、 PVRTC Compressed Texture Formats和ETC Compressed Texture Format。
  • type -- 默认值为THREE.UnsignedByteType。 有关其他选择,请参阅type constants。
  • .wrapR : number
      这定义了纹理在深度方向上的包裹方式。默认值为THREE.ClampToEdgeWrapping,其中边缘被紧贴到外边缘纹素上。 另外两个选择是THREE.RepeatWrapping和THREE.MirroredRepeatWrapping。
  • .isCompressedArrayTexture : Boolean
      只读标志,用于检查给定对象是否为CompressedArrayTexture类型。

2.4. CubeTexture 立方纹理

CubeTexture(立方贴图)的功能以及用法几乎和Texture是相同的。区别在于,CubeTexture中的图像是6个单独的图像所组成的数组, 纹理映射选项为THREE.CubeReflectionMapping(默认值)或THREE.CubeRefractionMapping。

  • .flipY : Boolean
      If set to true, the texture is flipped along the vertical axis when uploaded to the GPU. Default is false.
  • .isCubeTexture : Boolean
      Read-only flag to check if a given object is of type CubeTexture.
javascript 复制代码
import * as THREE from 'three'

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

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

  const fov = 75
  const aspect = 2 // the canvas default
  const near = 0.1
  const far = 5
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
  camera.position.z = 2

  const scene = new THREE.Scene()

  const geometry = new THREE.BoxGeometry(1, 1, 1)

  const loader = new THREE.CubeTextureLoader()
  loader.setPath('assets/images/')

  const texture = loader.load([
    'img1.png',
    'img1.png',
    'img1.png',
    'img1.png',
    'img1.png',
    'img1.png',
  ])
  const material = new THREE.MeshBasicMaterial({ color: 0xffffff, envMap: texture })

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

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

    cubes.forEach((cube, ndx) => {
      const speed = 0.2 + ndx * 0.1
      const rot = time * speed
      cube.rotation.x = rot
      cube.rotation.y = rot
    })

    renderer.render(scene, camera)

    requestAnimationFrame(render)
  }

  requestAnimationFrame(render)
}

2.5. DataArrayTexture 数据数组纹理

直接从原始数据、宽度、高度和深度创建纹理数组。这种类型的纹理只能与 WebGL 2 渲染上下文一起使用。

  • .flipY : Boolean
      纹理上传到GPU时是否沿Y轴翻转。默认为false。
  • .generateMipmaps : Boolean
      是否为纹理生成mipmap(如果可能)。默认为false。
  • .image : Object
      被包含数据、宽度、高度和深度的对象覆盖。
  • .isDataArrayTexture : Boolean
      只读标志,用于检查给定对象是否属于DataArrayTexture类型。
  • .magFilter : number
      当纹理覆盖多个像素时如何对纹理进行采样。默认值为THREE.NearestFilter,它使用最近的纹理元素的值。
  • .minFilter : number
      当纹理覆盖少于一个像素时如何对纹理进行采样。默认值为THREE.NearestFilter,它使用最近的纹理元素的值。
  • .unpackAlignment : number
      默认为1。 指定内存中每个像素行开始的对齐要求。 允许的值为1(字节对齐)、2(行对齐到偶数字节)、4(字对齐)和8(行从双字边界开始)。
  • .wrapR : number
      这定义了纹理在深度方向上的包裹方式。
      默认值为THREE.ClampToEdgeWrapping,其中边缘被夹紧到外边缘纹素。 其他两个选择是THREE.RepeatWrapping和THREE.MirroredRepeatWrapping。 有关详细信息,请参阅texture constants页面。
javascript 复制代码
import * as THREE from 'three'

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

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

  const fov = 75
  const aspect = 2 // the canvas default
  const near = 0.1
  const far = 5
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
  camera.position.z = 2

  const scene = new THREE.Scene()

  const geometry = new THREE.BoxGeometry(1, 1, 1)

  const width = 512
  const height = 512
  const depth = 100

  const size = width * height
  const data = new Uint8Array(4 * size * depth)

  for (let i = 0; i < depth; i++) {
    const color = new THREE.Color(Math.random(), Math.random(), Math.random())
    const r = Math.floor(color.r * 255)
    const g = Math.floor(color.g * 255)
    const b = Math.floor(color.b * 255)

    for (let j = 0; j < size; j++) {
      const stride = (i * size + j) * 4

      data[stride] = r
      data[stride + 1] = g
      data[stride + 2] = b
      data[stride + 3] = 255
    }
  }

  //  使用缓冲区创建的颜色数据创建缓冲区DataArrayTexture

  const texture = new THREE.DataArrayTexture(data, width, height, depth)
  texture.needsUpdate = true
  const material = new THREE.MeshBasicMaterial({ color: 0xffffff, map: texture })

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

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

    cubes.forEach((cube, ndx) => {
      const speed = 0.2 + ndx * 0.1
      const rot = time * speed
      cube.rotation.x = rot
      cube.rotation.y = rot
    })

    renderer.render(scene, camera)

    requestAnimationFrame(render)
  }

  requestAnimationFrame(render)
}

2.6. Data3DTexture 三维的纹理贴图

  • data -- 纹理的数据。
  • width -- 纹理的宽度。
  • height -- 纹理的高度。
  • depth -- 纹理的深度。
  • .wrapR : number
      这定义了纹理在深度方向上的包裹方式。
  • DataTexture 从原始数据(raw data)、宽(width)、高(height)来直接创建一个纹理贴图。
  • .flipY : Boolean
      If set to true, the texture is flipped along the vertical axis when uploaded to the GPU. Default is false.
  • .generateMipmaps : Boolean
      Whether to generate mipmaps (if possible) for a texture. False by default.
  • .image : Object
      Overridden with a record type holding data, width and height.
  • .isDataTexture : Boolean
      Read-only flag to check if a given object is of type DataTexture.
  • .unpackAlignment : number
      1 by default. Specifies the alignment requirements for the start of each pixel row in memory. The allowable values are 1 (byte-alignment), 2 (rows aligned to even-numbered bytes), 4 (word-alignment), and 8 (rows start on double-word boundaries). See glPixelStorei for more information.

2.7. DepthTexture 深度纹理

  • width -- 纹理的宽度。
  • height -- 纹理的高度。
  • type -- Default is THREE.UnsignedIntType when using DepthFormat and THREE.UnsignedInt248Type when using DepthStencilFormat. 请参阅type constants(类型常量)来了解其他选项。
  • mapping -- 请参阅mapping mode constants(映射模式常量)来了解其他选项。
  • wrapS -- 默认值是THREE.ClampToEdgeWrapping. 请参阅wrap mode constants(包裹模式常量)来了解其他选项。
  • wrapT -- 默认值是THREE.ClampToEdgeWrapping. 请参阅wrap mode constants(包裹模式常量)来了解其他选项。
  • magFilter -- 当一个纹素覆盖大于一个像素时,贴图将如何采样。 其默认值为THREE.LinearFilter。  请参阅magnification filter constants(放大滤镜常量)来了解其它选项。
  • minFilter -- 当一个纹素覆盖小于一个像素时,贴图将如何采样。 其默认值为  THREE.LinearMipMapLinearFilter。请参阅minification filter constants(缩小滤镜常量)来了解其它选项。
  • anisotropy -- 沿着轴,通过具有最高纹素密度的像素的样本数。 默认情况下,这个值为1。设置一个较高的值将会产生比基本的mipmap更清晰的效果,代价是需要使用更多纹理样本。 使用renderer.getMaxAnisotropy() 来查询GPU中各向异性的最大有效值;这个值通常是2的幂。
  • format -- 这个值必须是DepthFormat(默认值)或者DepthStencilFormat。 请参阅format constants(格式常量)来了解详细信息。
  • format
      DepthFormat(默认值)或者DepthStencilFormat中的一个。 请参阅format constants来了解详细信息。
  • type
      Default is THREE.UnsignedIntType when using DepthFormat and THREE.UnsignedInt248Type when using DepthStencilFormat. 请参阅format constants来了解详细信息。
  • magFilter
      当一个纹素覆盖大于一个像素时,贴图将如何采样。 其默认值为THREE.NearestFilter。 请参阅magnification filter constants(放大滤镜常量)来了解其他选项。
  • minFilter
      当一个纹素覆盖小于一个像素时,贴图将如何采样。 其默认值为THREE.NearestFilter。 请参阅minification filter constants(缩小滤镜常量)来了解其他选项。
  • flipY
      深度贴图不需要被翻转,因此其默认值为false。
  • generateMipmaps
      深度贴图不使用mipmap。
  • .isDepthTexture : Boolean
      Read-only flag to check if a given object is of type DepthTexture.

2.8. FramebufferTexture 帧缓冲纹理

  • width -- 纹理的宽度

  • height -- 纹理的高度

  • .generateMipmaps : Boolean

    是否为 FramebufferTexture 生成 mipmaps ,默认为false

  • .isFramebufferTexture : Boolean

    只读,检查给定对象是否为 FramebufferTexture 类型

  • .magFilter : number

    纹理元素覆盖多个像素时如何对纹理进行采样。默认值为 THREE.NearestFilter ,它使用最接近的纹理元素。

  • .minFilter : number

    纹理元素覆盖少于一个像素时如何对纹理进行采样。默认值为 THREE.NearestFilter ,它使用最近的纹理元素。

  • .needsUpdate : Boolean

    默认为 true ,这是加载 canvas 数据所必需的

  • .data : Any

    The actual data of a texture. The type of this property depends on the texture that uses this instance.

  • .isCubeTexture : Boolean

    Read-only flag to check if a given object is of type Source.

  • .needsUpdate : Boolean

    Set this to true to trigger a data upload to the GPU next time the source is used.

  • .uuid : String

    UUID of this object instance. This gets automatically assigned, so this shouldn't be edited.

  • .version : Integer

    This starts at 0 and counts how many times .needsUpdate is set to true.

javascript 复制代码
import * as THREE from 'three'

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

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

  const fov = 75
  const aspect = 2 // the canvas default
  const near = 0.1
  const far = 5
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
  camera.position.z = 2

  const scene = new THREE.Scene()

  const geometry = new THREE.BoxGeometry(1, 1, 1)

  const width = 512
  const height = 512

  const size = width * height
  const data = new Uint8Array(4 * size)
  const color = new THREE.Color(0xffffff)

  const r = Math.floor(color.r * 255)
  const g = Math.floor(color.g * 255)
  const b = Math.floor(color.b * 255)

  for (let i = 0; i < size; i++) {
    const stride = i * 4

    data[stride] = r
    data[stride + 1] = g
    data[stride + 2] = b
    data[stride + 3] = 255
  }

  // used the buffer to create a DataTexture

  const texture = new THREE.DataTexture(data, width, height)
  texture.needsUpdate = true
  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)
  scene.add(cube)
  cubes.push(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(time: number) {
    time *= 0.001

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

    cubes.forEach((cube, ndx) => {
      const speed = 0.2 + ndx * 0.1
      const rot = time * speed
      cube.rotation.x = rot
      cube.rotation.y = rot
    })

    const pixelRatio = window.devicePixelRatio
    const textureSize = 128 * pixelRatio

    // instantiate a framebuffer texture
    const frameTexture = new THREE.FramebufferTexture(textureSize, textureSize)

    // calculate start position for copying part of the frame data
    const vector = new THREE.Vector2()
    vector.x = (window.innerWidth * pixelRatio) / 2 - textureSize / 2
    vector.y = (window.innerHeight * pixelRatio) / 2 - textureSize / 2

    // render the scene
    renderer.clear()
    renderer.render(scene, camera)

    // copy part of the rendered frame into the framebuffer texture
    renderer.copyFramebufferToTexture(vector, frameTexture)

    // renderer.render(scene, camera)

    requestAnimationFrame(render)
  }

  requestAnimationFrame(render)
}

2.9. Texture 纹理贴图

属性:

  • .id : Integer
      只读 - 表示该纹理实例的唯一数字。
  • .isTexture : Boolean
      Read-only flag to check if a given object is of type Texture.
  • .uuid : String
      该对象实例的UUID。 这个值是自动分配的,因此不应当对其进行编辑。
  • .name : String
      该对象的名称,可选,且无需唯一。默认值是一个空字符串。
  • .image : Image
      一个图片对象,通常由TextureLoader.load方法创建。 该对象可以是被three.js所支持的任意图片(例如PNG、JPG、GIF、DDS)或视频(例如MP4、OGG/OGV)格式。
      要使用视频来作为纹理贴图,你需要有一个正在播放的HTML5 Video元素来作为你纹理贴图的源图像, 并在视频播放时不断地更新这个纹理贴图。------VideoTexture 类会对此自动进行处理。
  • .mipmaps : Array
      用户所给定的mipmap数组(可选)。
  • .mapping : number
      图像将如何应用到物体(对象)上。默认值是THREE.UVMapping对象类型, 即UV坐标将被用于纹理映射。
  • .channel : Integer
      Lets you select the uv attribute to map the texture to. 0 for uv, 1 for uv1, 2 for uv2 and 3 for uv3.
  • .wrapS : number
      这个值定义了纹理贴图在水平方向上将如何包裹,在UV映射中对应于U。
      默认值是THREE.ClampToEdgeWrapping,即纹理边缘将被推到外部边缘的纹素。 其它的两个选项分别是THREE.RepeatWrapping和THREE.MirroredRepeatWrapping。 请参阅texture constants来了解详细信息。
  • .wrapT : number
      这个值定义了纹理贴图在垂直方向上将如何包裹,在UV映射中对应于V。
      可以使用与 .wrapS : number相同的选项。
      请注意:纹理中图像的平铺,仅有当图像大小(以像素为单位)为2的幂(2、4、8、16、32、64、128、256、512、1024、2048、......)时才起作用。 宽度、高度无需相等,但每个维度的长度必须都是2的幂。 这是WebGL中的限制,不是由three.js所限制的。
  • .magFilter : number
      当一个纹素覆盖大于一个像素时,贴图将如何采样。默认值为THREE.LinearFilter, 它将获取四个最接近的纹素,并在他们之间进行双线性插值。 另一个选项是THREE.NearestFilter,它将使用最接近的纹素的值。
  • .minFilter : number
    当一个纹素覆盖小于一个像素时,贴图将如何采样。默认值为THREE.LinearMipmapLinearFilter, 它将使用mipmapping以及三次线性滤镜。
  • .anisotropy : number
      沿着轴,通过具有最高纹素密度的像素的样本数。 默认情况下,这个值为1。设置一个较高的值将会产生比基本的mipmap更清晰的效果,代价是需要使用更多纹理样本。 使用renderer.capabilities.getMaxAnisotropy() 来查询GPU中各向异性的最大有效值;这个值通常是2的幂。
  • .format : number
      默认值为THREE.RGBAFormat。
  • .internalFormat : String
      此项的默认值由.format和.type混合并决定。
      这项GPU格式设置允许开发者决定在GPU中的数据存储方式。
      查看texture constants页面来了解所有可用的Internal Formats以及其细节。
  • .type : number
      这个值必须与.format相对应。默认值为THREE.UnsignedByteType, 它将会被用于绝大多数纹理格式。
  • .offset : Vector2
      贴图单次重复中的起始偏移量,分别表示U和V。 一般范围是由0.0到1.0。
  • .repeat : Vector2
      决定纹理在表面的重复次数,两个方向分别表示U和V,如果重复次数在任何方向上设置了超过1的数值, 对应的Wrap需要设置为THREE.RepeatWrapping或者THREE.MirroredRepeatWrapping来 达到想要的平铺效果。
  • .rotation : number
      纹理将围绕中心点旋转多少度,单位为弧度(rad)。正值为逆时针方向旋转,默认值为0。
  • .center : Vector2
      旋转中心点。(0.5, 0.5)对应纹理的正中心。默认值为(0,0),即左下角。
  • .matrixAutoUpdate : Boolean
      是否从纹理的.offset、.repeat、.rotation和.center属性更新纹理的UV变换矩阵(uv-transform .matrix)。 默认值为true。 如果你要直接指定纹理的变换矩阵,请将其设为false。
  • .matrix : Matrix3
      纹理的UV变换矩阵。 当纹理的.matrixAutoUpdate属性为true时, 由渲染器从纹理的.offset、.repeat、.rotation和.center属性中进行更新。 当.matrixAutoUpdate属性为false时,该矩阵可以被手动设置。 默认值为单位矩阵。
  • .generateMipmaps : Boolean
      是否为纹理生成mipmap(如果可用)。默认为true。 如果你手动生成mipmap,请将其设为false。
  • .premultiplyAlpha : Boolean
      如果设置为true并且alpha通道存在的话,上传到GPU时alpha的数值将会与颜色通道的数值相乘。默认为false。
      注意此项属性不会影响到ImageBitmap。 如果你想要则需要在bitmap创建的时候进行配置,如何配置请查看ImageBitmapLoader。
  • .flipY : Boolean
      如果设置为true,纹理在上传到GPU的时候会进行纵向的翻转。默认值为true。
      注意此项属性不会影响到ImageBitmap。 如果你想要则需要在bitmap创建的时候进行配置,如何配置请查看ImageBitmapLoader。
  • .unpackAlignment : number
      默认为4。指定内存中每个像素行起点的对齐要求。 允许的值为1(字节对齐)、2(行对齐到偶数字节)、4(字对齐)和8(行从双字边界开始)。 请参阅glPixelStorei来了解详细信息。
  • .colorSpace : string
      默认值为THREE.NoColorSpace。 请参阅texture constants来了解其他格式的详细信息。
  • .version : Integer
      这个值起始值为0,计算 .needsUpdate : Boolean被设置为true的次数。
  • .onUpdate : Function
      一个回调函数,在纹理被更新后调用。 (例如,当needsUpdate被设为true且纹理被使用。)
  • .needsUpdate : Boolean
      将其设置为true,以便在下次使用纹理时触发一次更新。 这对于设置包裹模式尤其重要。
  • .userData : Object
      保存关于纹理的自定义信息的对象。不应该放函数引用,因为函数不会拷贝。
  • .source : Source
      纹理的数据源信息。可以在不同的纹理之间引用相同的数据源,这在使用精灵表的情况下很好用,因为在这种情况下不同的纹理只是使用了不同的偏移,但是其数据源是相同的。
    方法:
  • .updateMatrix () : undefined
      从纹理的.offset、.repeat、 .rotation和.center属性来更新纹理的UV变换矩阵(uv-transform .matrix)。
  • .clone () : Texture
      拷贝纹理。请注意。这不是"深拷贝",图像是共用的。 除此之外,拷贝一个纹理并不会将此纹理自动标记并上传。你需要在图片的属性变更或者源数据完全加载完的时候并准备好的时候将Texture.needsUpdate设置为true。
  • .toJSON ( meta : Object ) : Object
      meta -- 可选,包含有元数据的对象。
      将Texture对象转换为 three.js JSON Object/Scene format(three.js JSON 物体/场景格式)。
  • .dispose () : undefined
      使用"废置"事件类型调用EventDispatcher.dispatchEvent。
  • .transformUv ( uv : Vector2 ) : Vector2
      基于纹理的.offset、.repeat、 .wrapS、.wrapT和.flipY属性值来变换uv。
javascript 复制代码
import * as THREE from 'three'

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

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

  const fov = 75
  const aspect = 2 // the canvas default
  const near = 0.1
  const far = 5
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
  camera.position.z = 2

  const scene = new THREE.Scene()

  const geometry = new THREE.BoxGeometry(1, 1, 1)

  const loader = new THREE.TextureLoader()
  const texture = loader.load('https://threejs.org/manual/examples/resources/images/wall.jpg')
  texture.colorSpace = THREE.SRGBColorSpace
  const material = new THREE.MeshBasicMaterial({ map: texture })

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

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

    cubes.forEach((cube, ndx) => {
      const speed = 0.2 + ndx * 0.1
      const rot = time * speed
      cube.rotation.x = rot
      cube.rotation.y = rot
    })

    renderer.render(scene, camera)

    requestAnimationFrame(render)
  }

  requestAnimationFrame(render)
}

2.10. VideoTexture 视频纹理

  • video -- 将被作为纹理贴图来使用的Video元素。
  • mapping -- 纹理贴图将被如何应用(映射)到物体上,它是THREE.UVMapping中的对象类型。 请参阅mapping constants(映射模式常量)来了解其他选项。
  • wrapS -- 默认值是THREE.ClampToEdgeWrapping. 请参阅wrap mode constants(包裹模式常量)来了解其他选项。
  • wrapT -- 默认值是THREE.ClampToEdgeWrapping. 请参阅wrap mode constants(包裹模式常量)来了解其他选项。
  • magFilter -- 当一个纹素覆盖大于一个像素时,贴图将如何采样。 其默认值为THREE.LinearFilter。请参阅magnification filter constants(放大滤镜常量)来了解其它选项。
  • minFilter -- 当一个纹素覆盖小于一个像素时,贴图将如何采样。 其默认值为THREE.LinearFilter。  请参阅minification filter constants(缩小滤镜常量)来了解其它选项。
  • format -- The default is THREE.RGBAFormat. 请参阅format constants(格式常量)来了解各个选项。
  • type -- 默认值是THREE.UnsignedByteType. 请参阅type constants(类型常量)来了解其他选项。
  • anisotropy -- 沿着轴,通过具有最高纹素密度的像素的采样数。 默认情况下,这个值为1。设置一个  较高的值将会比基本的mipmap产生更清晰的效果,代价是需要使用更多纹理样本。 使用renderer.getMaxAnisotropy() 来查询GPU中各向异性的最大有效值;这个值通常是2的幂。
  • .generateMipmaps : Boolean
      Whether to generate mipmaps. false by default.
  • .isVideoTexture : Boolean
      Read-only flag to check if a given object is of type VideoTexture.
  • .needsUpdate : Boolean
      在这里,你不必手动设置这个值,因为它是由update()方法来进行控制的。
  • .update () : undefined
      在每一次新的一帧可用时,这个方法将被自动调用, 并将 .needsUpdate : Boolean设置为true。
javascript 复制代码
import * as THREE from 'three'

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

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

  const fov = 75
  const aspect = 2 // the canvas default
  const near = 0.1
  const far = 5
  const camera = new THREE.PerspectiveCamera(fov, aspect, near, far)
  camera.position.z = 2

  const scene = new THREE.Scene()

  const geometry = new THREE.BoxGeometry(1, 1, 1)

  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)
  scene.add(cube)
  cubes.push(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(time: number) {
    time *= 0.001

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

    cubes.forEach((cube, ndx) => {
      const speed = 0.2 + ndx * 0.1
      const rot = time * speed
      cube.rotation.x = rot
      cube.rotation.y = rot
    })

    renderer.render(scene, camera)

    requestAnimationFrame(render)
  }

  requestAnimationFrame(render)
}

三、光照相关内容

然后我们添加一个 OrbitControls。OrbitControls 让我们可以围绕某一个点旋转控制相机。OrbitControls 是 three.js 的可选模块,所以我们首先需要引入这个模块。

javascript 复制代码
import * as THREE from 'three';
import {OrbitControls} from 'three/addons/controls/OrbitControls.js';

然后我们就可以使用了。创建 OrbitControls 时传入两个参数,一个是要控制的相机对象,第二个是检测事件的 DOM 元素。

javascript 复制代码
const controls = new OrbitControls(camera, canvas);
controls.target.set(0, 5, 0);
controls.update();

3.1. AmbientLight 环境光

环境光会均匀的照亮场景中的所有物体。

环境光不能用来投射阴影,因为它没有方向。

  • color -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 Color 对象。
  • intensity -(可选)光照的强度。默认值为 1。
  • .isAmbientLight : Boolean只读,用于检查对象的类型是否为 AmbientLight。
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)
  }

  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)

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

  renderer.render(scene, camera)
}

3.2. DirectionalLight 平行光

平行光是沿着特定方向发射的光。这种光的表现像是无限远,从它发出的光线都是平行的。常常用平行光来模拟太阳光的效果。 太阳足够远,因此我们可以认为太阳的位置是无限远,所以我们认为从太阳发出的光线也都是平行的。

  • color -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 Color 对象。
  • intensity -(可选)光照的强度。默认值为 1。

属性:

  • .castShadow : Boolean
      此属性设置为 true 灯光将投射阴影。注意:这样做的代价比较高,需要通过调整让阴影看起来正确。 查看 DirectionalLightShadow 了解详细信息。 默认值为 false。
  • .isDirectionalLight : Boolean
      只读,用于检查对象的类型是否为 DirectionalLight。
  • .position : Vector3
      假如这个值设置为 Object3D.DEFAULT_UP (0, 1, 0),光线将会从上往下照射。
  • .shadow : DirectionalLightShadow
      DirectionalLightShadow 对象,用于计算该平行光产生的阴影。
  • .target : Object3D
      灯光从它的位置(position)指向目标位置。默认的目标位置为(0, 0, 0)。

注意:对于目标的位置,如果要改为除默认值之外的其他位置,该位置必须被添加到场景(scene)中去。

scene.add( light.target );这是为了让目标的 matrixWorld 在每一帧自动更新。

也可以将目标设置为场景中的其他对象(任意拥有 position 属性的对象),如:

javascript 复制代码
const targetObject = new THREE.Object3D();
scene.add(targetObject);

light.target = targetObject;

通过上述操作,光源就可以追踪目标对象了。

方法:

  • .dispose () : undefined

    释放由该实例分配的 GPU 相关资源。 当这个实例不再在你的应用中使用时,调用这个方法。

  • .copy ( source : DirectionalLight ) : this

    复制 source 的值到这个平行光源对象。

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

  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)

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

  renderer.render(scene, camera)
}

3.3. HemisphereLight 半球光

光源直接放置于场景之上,光照颜色从天空光线颜色渐变到地面光线颜色。

半球光不能投射阴影。

  • skyColor -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 Color 对象。
  • groundColor -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 Color 对象。
  • intensity -(可选)光照强度。默认值为 1。
    属性:
  • .color : Color
      在构造时传递的天空发出光线的颜色。 默认值为白色(0xffffff)的 Color 对象。
  • .groundColor : Color
      在构造时传递的地面发出光线的颜色。 默认值为白色(0xffffff)的 Color 对象。
  • .isHemisphereLight : Boolean
      只读,用于检查对象的类型是否为 HemisphereLight。
  • .position : Vector3
      假如这个值设置为 Object3D.DEFAULT_UP (0, 1, 0),光线将会从上往下照射。
    方法:
  • .copy ( source : HemisphereLight ) : this
      从 source 复制 color、intensity 和 groundColor 的值到当前半球光对象中。
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)
  }

  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)

  const color = 0xffffff
  const intensity = 1
  const light = new THREE.HemisphereLight(color, 0x080820, intensity)
  light.castShadow = true
  scene.add(light)

  renderer.render(scene, camera)
}

3.4. Light 光源的基类

光源的基类 - 所有其他的光类型都继承了该类描述的属性和方法。

  • color -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 Color 对象。
  • intensity -(可选)光照强度。默认值为 1。
    属性:
  • .color : Color
      光源的颜色。如果构造的时候没有传递,默认会创建一个新的 Color 对象并设置为白色。
  • .intensity : Float
      光照的强度,或者说能量。
      强度的单位取决于光的类型。
      默认值为 1.0。
  • .isLight : Boolean
      只读,用于检查对象的类型是否为 Light。

方法:

  • .dispose () : undefined
      释放由该实例分配的 GPU 相关资源。 当这个实例不再在你的应用中使用时,调用这个方法。
  • .copy ( source : Light ) : this
      从 source 复制 color、intensity 的值到当前光源对象中。
  • .toJSON ( meta : Object ) : Object
      以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)
  }

  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)

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

  renderer.render(scene, camera)
}

3.5. LightProbe 光照探针

光照探针是一种在3D场景中添加光源的另一种方法。与经典光源(平行光、点光、聚光)不同, 光照探针不发光。相反,光照探针存储着有关穿过3D空间的光线的信息。 渲染过程中,通过使用来自光照探针的数据,来逼近打到3D物体上的光线。

光照探针通常从(辐射)环境贴图中创建。LightProbeGenerator 类可以用于从 CubeTexture 或 WebGLCubeRenderTarget 的实例来创建光照探针。 但是,光照估算数据同样可以以其他形式提供,例如,通过WebXR。 这使得增强现实内容的渲染能够对现实世界的照明做出反应。

目前在 Three.js 中的探测实现支持所谓的漫射光探测。 这种类型的光照探针在功能上等效于辐照环境贴图。

  • sh -(可选)一个 SphericalHarmonics3 的实例。
  • intensity -(可选)光照探针强度的数值。默认值为 1。
  • .isLightProbe : Boolean
      只读,用于检查对象的类型是否为 LightProbe。
  • .sh : SphericalHarmonics3
      光照探针使用球面谐波(spherical harmonic)来编码光照信息。

3.6. PointLight 点光源

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

  • color -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 Color 对象。
  • intensity -(可选)光照强度。默认值为 1。
  • distance - 光源照射的最大距离。默认值为 0(无限远)。
  • decay - 沿着光照距离的衰退量。默认值为 2。

属性:

  • .castShadow : Boolean
      此属性设置为 true 灯光将投射阴影。注意:这样做的代价比较高,需要通过调整让阴影看起来正确。 查看 PointLightShadow 了解详细信息。 默认值为 false。
  • .decay : Float
      光线随着距离增加变暗的衰减量。默认值为 2。
      在物理正确渲染的上下文中,不应更改默认值。
  • .distance : Float
      当值为零时,光线将根据平方反比定律衰减到无限远。 当值不为零时,光线会先按照平方反比定律衰减,直到距离截止点附近,然后线性衰减到 0。
    默认值为 0.0。
  • .intensity : Float
      光源的强度。默认值为 1。
      单位是坎德拉(cd)。
      改变该值会影响到 power 的值。
  • .power : Float
      光源的功率。
      单位为流明(lm)。
      改变该值会影响到 intensity 的值。
  • .shadow : PointLightShadow
      PointLightShadow 对象,用与计算此光照的阴影。
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.PointLight(color, intensity)
  light.position.set(0, 10, 0)
  scene.add(light)

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

3.7. RectAreaLight 平面光光源

平面光光源从一个矩形平面上均匀地发射光线。这种光源可以用来模拟像明亮的窗户或者条状灯光光源。
注意事项:

  • 不支持阴影。
  • 只支持 MeshStandardMaterial 和 MeshPhysicalMaterial 两种材质。
  • 你必须在你的场景中加入 RectAreaLightUniformsLib,并调用 init()。
  • color -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 Color 对象。
  • intensity -(可选)光源强度/亮度 。默认值为 1。
  • width -(可选)光源宽度。默认值为 10。
  • height -(可选)光源高度。默认值为 10。
  • .height : Float
      光源高度。
  • .intensity : Float
      光源的强度。默认值为 1。
      单位是尼特(cd/m^2)。
      改变该值会影响到 power 的值。
  • .isRectAreaLight : Boolean
      只读,用于检查对象的类型是否为 RectAreaLight。
  • .power : Float
      光源的功率。
      单位为流明(lm)。
      改变该值会影响到 intensity 的值。
  • .width : Float
      光源宽度。
javascript 复制代码
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
import { RectAreaLightUniformsLib } from 'three/addons/lights/RectAreaLightUniformsLib.js'
import { RectAreaLightHelper } from 'three/addons/helpers/RectAreaLightHelper.js'

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

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

  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 = 5
  const width = 12
  const height = 4
  const light = new THREE.RectAreaLight(color, intensity, width, height)
  light.position.set(0, 10, 0)
  light.rotation.x = THREE.MathUtils.degToRad(-90)
  scene.add(light)

  const helper = new RectAreaLightHelper(light)
  light.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() {
    if (resizeRendererToDisplaySize(renderer)) {
      const canvas = renderer.domElement
      camera.aspect = canvas.clientWidth / canvas.clientHeight
      camera.updateProjectionMatrix()
    }

    renderer.render(scene, camera)

    requestAnimationFrame(render)
  }

  requestAnimationFrame(render)
}

3.8. SpotLight 聚光灯

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

该光源可以投射阴影

  • color -(可选)一个表示颜色的 Color 的实例、字符串或数字,默认为一个白色(0xffffff)的 Color 对象。
  • intensity -(可选)光照强度。默认值为 1。
  • distance - 光源照射的最大距离。默认值为 0(无限远)。
  • angle - 光线照射范围的角度。默认值为 Math.PI/3。
  • penumbra - 聚光锥的半影衰减百分比。默认值为 0。
  • decay - 沿着光照距离的衰减量。默认值为 2。
    属性:
  • .angle : Float
      光线照射范围的角度,用弧度表示。不应超过 Math.PI/2。默认值为 Math.PI/3。
  • .castShadow : Boolean
      此属性设置为 true 灯光将投射阴影。注意:这样做的代价比较高,需要通过调整让阴影看 起来正确。 查看 SpotLightShadow 了解详细信息。 默认值为 false。
  • .decay : Float
      光线随着距离增加变暗的衰减量。默认值为 2。
      在物理正确渲染的上下文中,不应更改默认值。
  • .distance : Float
      当值为零时,光线将根据平方反比定律衰减到无限远。 当值不为零时,光线会先按照 平方反比定律衰减,直到距离截止点附近,然后线性衰减到 0。
    默认值为 0.0。
  • .intensity : Float
      光源的强度。默认值为 1。
      单位是坎德拉(cd)。
      改变该值会影响到 power 的值。
  • .isSpotLight : Boolean
      只读,用于检查对象的类型是否为 SpotLight。
  • .penumbra : Float
      该属性设置照明区域在边缘附近的平滑衰减速度,取值范围在 0 到 1 之间。默认值为 0.0。
  • .position : Vector3
      假如这个值设置为 Object3D.DEFAULT_UP (0, 1, 0),光线将会从上往下照射。
  • .power : Float
      光源的功率。
      单位为流明(lm)。
      改变该值会影响到 intensity 的值。
  • .shadow : SpotLightShadow
      SpotLightShadow 对象,用与计算此光照的阴影。
  • .target : Object3D
      灯光从它的位置(position)指向目标位置。默认的目标位置为(0, 0, 0)。
    注意:对于目标的位置,如果要改为除默认值之外的其他位置,该位置必须被添加到场景(scene)中去。
      scene.add( light.target );这是为了让目标的 matrixWorld 在每一帧自动更新。
      也可以将目标设置为场景中的其他对象(任意拥有 position 属性的对象),如:
javascript 复制代码
const targetObject = new THREE.Object3D();
scene.add(targetObject);
light.target = targetObject;

通过上述操作,光源就可以追踪目标对象了。

  • .map : Texture
      用于调节光线颜色的纹理(Texture),聚光灯颜色会与该纹理的RGB值混合,其比例与其alpha值相对应。
      The cookie-like masking effect is reproduced using pixel values (0, 0, 0, 1-cookie_value).
      注意: 如果 .castShadow 值为 false 时,.map 不可用。

方法:

  • .dispose () : undefined
      释放由该实例分配的 GPU 相关资源。 当这个实例不再在你的应用中使用时,调用这个方法。
  • .copy ( source : SpotLight ) : this
      将所有属性的值从源 source 复制到此聚光灯光源对象。
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)
}
相关推荐
恋猫de小郭2 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅8 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60619 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了9 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅9 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅10 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅10 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment10 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅10 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊10 小时前
jwt介绍
前端