three.js学习笔记 2.光照和材质

180 阅读7分钟

Helper

这是一个来自于@react-three/drei的实用工具 可以方便的使用three.js的helper对象 为光线网格等增加辅助线

      <mesh>
        {/** 不同的type对应的args类型不同 */}
        <Helper type={BoxHelper} args={['red']}></Helper>
        <boxGeometry></boxGeometry>
        <meshPhysicalMaterial />
      </mesh>

image.png

阴影

阴影需要四处设置

  • 启用阴影<Canvas shadows>
  • 光照启用阴影<directionalLight castShadow />
  • 物体允许投影<mesh castShadow>
  • 接收投影<mesh receiveShadow>
const Page: FC = () => {
  return (
    <Canvas shadows>
      <Axes />
      <OrbitControls />
      <ambientLight></ambientLight>
      <directionalLight castShadow position={[2, 2, 2]}>
      </directionalLight>
      <mesh castShadow>
        <boxGeometry></boxGeometry>
        <meshPhysicalMaterial color={'red'} />
      </mesh>
      <mesh receiveShadow position={[0, -1, 0]} rotation={[-Math.PI / 2, 0, 0]}>
        <planeGeometry args={[10, 10]} />
        <meshStandardMaterial />
      </mesh>
    </Canvas>
  )
}

image.png 还可以通过这种方式设置阴影属性

      <directionalLight
        castShadow
        shadow-mapSize-width={1024}
        shadow-mapSize-height={1024}
      ></directionalLight>

虽然没有类型提示 但是shadow-mapSize-width会被自动映射到shadow.mapSize.width属性上 shadow的其余属性同理
width和height是阴影贴图的分辨率

阴影相机

生成阴影的原理是使用一个额外的"阴影相机"判断哪里需要阴影 没有被拍摄的阴影不会显示
不同光源类型的阴影相机类型不同 可以通过shadow-camera-*进行设置

光照

环境光

简单光照 没有阴影 没有反射 可以简单理解成物体自发光 也不需要helper

const Page: FC = () => {
  return (
    <Canvas shadows>
      <Axes />
      <OrbitControls />
      <ambientLight></ambientLight>
      <mesh castShadow>
        <boxGeometry></boxGeometry>
        <meshPhysicalMaterial color={'red'} />
      </mesh>
      <mesh receiveShadow position={[0, -1, 0]} rotation={[-Math.PI / 2, 0, 0]}>
        <planeGeometry args={[10, 10]} />
        <meshStandardMaterial />
      </mesh>
    </Canvas>
  )
}

image.png

方向光

平行光束 具有阴影 模拟太阳光
在环境光的示例中添加以下光照

      <directionalLight position={[2, 2, 2]}>
        <Helper type={DirectionalLightHelper} args={[0.5, 'red']}></Helper>
      </directionalLight>

image.png 红色正方形以及连接它和原点的线段就是Heler绘制的辅助线
需要注意的是 这只表示光的方向 方向光的来源是无限远 image.png 可以看到 即使物体和光照的方向向量不在同一侧 也会被相应的光照照亮

阴影相机

方向光的阴影相机是正交投影相机 可以配置shadow-camera-left shadow-camera-right shadow-camera-top shadow-camera-bottom控制其大小

点光源

从一个点向四周发射光 具有阴影 在环境光的示例中添加以下光照

      <pointLight
        castShadow
        position={[1, 1, 1]}
        intensity={10}
        decay={0}
        distance={10}
      >
        <Helper type={PointLightHelper} args={[1, 'red']}></Helper>
      </pointLight>

decay配置光源的衰减速度 默认2
distance配置其最远距离 超出的衰减为0 image.png

阴影相机

点光源的阴影相机是六个方向的透视相机 可以配置shadow-camera-near shadow-camera-far 控制其最近距离和最远距离

聚光灯

模拟聚光灯 具有阴影 范围是一个锥形

      <spotLight
        castShadow
        intensity={10}
        decay={0}
        position={[1, 1, 1]}
        distance={4}
        angle={(45 / 180) * Math.PI}
        penumbra={0}
      >
        <Helper type={SpotLightHelper} args={['red']}></Helper>
      </spotLight>

decay 光的衰减速率
distance照射范围
angle光锥的顶角
penumbra阴影边缘柔软程度
屏幕截图 2025-04-27 094029.png 还可以设置target指定照射对象

const Page: FC = () => {
  const [target, setTarget] = useState<Object3D>()
  return (
    <Canvas className='translate-y-10' shadows>
      <Axes />
      <OrbitControls />
      <ambientLight></ambientLight>
      <spotLight
        castShadow
        intensity={10}
        decay={0}
        position={[1, 1, 1]}
        distance={4}
        angle={(45 / 180) * Math.PI}
        penumbra={0}
        target={target}
      >
        <Helper type={SpotLightHelper} args={['red']}></Helper>
      </spotLight>
      <mesh
        ref={(el) => {
          if (el && el !== target) {
            setTarget(el)
          }
        }}
        castShadow
        position={[3, 3, 3]}
      >
        <boxGeometry></boxGeometry>
        <meshPhysicalMaterial color={'red'} />
      </mesh>
      <mesh receiveShadow position={[0, -1, 0]} rotation={[-Math.PI / 2, 0, 0]}>
        <planeGeometry args={[10, 10]} />
        <meshStandardMaterial />
      </mesh>
    </Canvas>
  )
}

也可以直接使用光源的lookAt函数 让其指向一个空间中的一个点 image.png

阴影相机

一个透视相机 辅助线与其视锥相同 屏幕截图 2025-04-27 094025.png

矩形光

用于模拟广告牌、窗户等 不支持阴影
去除环境光加入矩形光

'use client'

import { FC, RefObject, useRef } from 'react'
import { Helper, OrbitControls } from '@react-three/drei'
import { Canvas, useFrame } from '@react-three/fiber'
import { Mesh, RectAreaLight } from 'three'
import { RectAreaLightHelper } from 'three/addons/helpers/RectAreaLightHelper.js'
import { Axes } from '@/components/Axes'

const Page: FC = () => {
  const meshRef = useRef<Mesh>(null)
  return (
    <Canvas shadows>
      <Axes />
      <OrbitControls />
      <RectLight meshRef={meshRef}></RectLight>
      <mesh castShadow ref={meshRef}>
        <boxGeometry></boxGeometry>
        <meshPhysicalMaterial color={'red'} />
      </mesh>
      <mesh receiveShadow position={[0, -1, 0]} rotation={[-Math.PI / 2, 0, 0]}>
        <planeGeometry args={[10, 10]} />
        <meshStandardMaterial />
      </mesh>
    </Canvas>
  )
}
export default Page

const RectLight: FC<{ meshRef: RefObject<Mesh> }> = (props) => {
  const lightRef = useRef<RectAreaLight>(null)
  useFrame(() => {
    if (props.meshRef.current && lightRef.current) {
      lightRef.current.lookAt(
        props.meshRef.current.getWorldPosition(new Vector3()),
      )
    }
  })
  return (
    <rectAreaLight
      position={[1, 1, 1]}
      width={1}
      height={2}
      intensity={10}
      ref={lightRef}
    >
      <Helper type={RectAreaLightHelper} args={['red']}></Helper>
    </rectAreaLight>
  )
}

width和height用于设置矩形宽高
lookAt控制指向 接受一个世界坐标
getWorldPosition用于获取一个mesh的世界坐标 其参数是 useFrame是每帧的动作 这个hook需要在Canvas下的子组件中使用

image.png

半球光

天空和地面光的结合,不支持阴影

      <hemisphereLight position={[0, 2, 0]} color={'blue'} groundColor={'red'}>
        <Helper type={HemisphereLightHelper} args={[1, 'black']}></Helper>
      </hemisphereLight>

color表示天空光 groundColor表示地面光 image.png image.png

材质

材质决定了物体表面与光的交互方式 这里仅以属性最为丰富的MeshPhysicalMaterial为例

  • color 物体表面的基础颜色

  • transparent 是否启用透明度通道。

  • opacity 控制材质的整体透明度,需要配合transparent属性使用。

  • side 渲染哪一面:正面、背面或双面。

  • emissive 材质自身发光颜色。

  • emissiveIntensity 控制自发光颜色的亮度。

  • roughness
    控制表面的粗糙程度,决定反射是否清晰。粗糙度值越高,表面的反射越模糊,呈现磨砂效果;值越低,表面反射越清晰,类似镜面反射。通常用于表现不平滑、磨砂或者有纹理的表面,如石头、木材等。适用于金属和非金属材质。数值范围:0(完全光滑)到 1(非常粗糙)。

  • metalness
    控制材质表面是否具有金属感。金属度为 0 时,材质表现为非金属,金属度为 1 时,材质表现为完全金属。金属表面具有高反射率,其反射的颜色是材质的本身颜色,而非环境光。数值范围:0(完全非金属)到 1(完全金属)。金属度影响光泽度和反射强度,适用于各种金属和表面材质。适用场景包括金属、塑料和其他材料的表现。

  • reflectivity 表面反射的强度。值越高,反射效果越强,适合表现镜面等反射强烈的材质。

  • ior 为非金属材质所设置的折射率

  • transmission
    很薄的透明或者半透明的塑料、玻璃材质即便在几乎完全透明的情况下仍旧会保留反射的光线,透光性属性用于这种类型的材质。当透光率不为0的时候, opacity透明度应设置为1.

  • thickness
    控制透明材质的体积厚度,用于模拟玻璃或类似材质的体积效果。较高的厚度值可以产生更强的折射效果,并影响材质的透明度表现。通常用于创建玻璃、液体等透光材质。

  • clearcoat
    有些类似于车漆,碳纤,被水打湿的表面的材质需要在面上再增加一个透明的,具有一定反光特性的面。而且这个面说不定有一定的起伏与粗糙度。

  • clearcoatRoughness
    控制清漆层的粗糙度。清漆层的粗糙度影响清漆表面的反射模糊度,粗糙度越高,反射越模糊,越低时反射越清晰。此属性主要影响涂层表面的光泽感与反射质量。适用场景:高光泽涂层、汽车漆、油漆表面。数值范围:0(完全光滑)到 1(非常粗糙)。

  • sheen
    可用于表示布料和织物材料,用于模拟丝绸、绒面、织物等柔软表面随角度变化的柔和高光。会在基本反射之外添加角度相关的次表面光泽。

  • sheenColor 光泽层颜色

  • sheenRoughness
    控制布料光泽的模糊程度。值越小,光泽越清晰;越大则越模糊,表现更柔和。用于加强丝绸、绒面等材质的真实感。

纹理Texture

纹理是控制材质属性的图片.
被用作纹理的图片不被视作图像,而被认为是一个二维数据集,从一个二位(x,y)获取一个三维向量(该像素的rgb颜色)
一个几何体geometry实际是由三角形组成的网格,这些三角形的顶点就是这个geometry的顶点,顶点中可以存放uv坐标,用于控制这个顶点应当从图片的哪个像素获取数据.
只有顶点的uv坐标需要指定,其余部分会自动做插值计算.只有有uv坐标的geometry可以使用贴图.
以boxGeometry为例 这是它的8个顶点

[
  -0.5, -0.5, -0.5,   // 顶点 0
   0.5, -0.5, -0.5,   // 顶点 1
   0.5,  0.5, -0.5,   // 顶点 2
  -0.5,  0.5, -0.5,   // 顶点 3
  -0.5, -0.5,  0.5,   // 顶点 4
   0.5, -0.5,  0.5,   // 顶点 5
   0.5,  0.5,  0.5,   // 顶点 6
  -0.5,  0.5,  0.5    // 顶点 7
]

这是它的uv坐标

[
  0, 0,  // 顶点 0 对应 UV 左下角
  1, 0,  // 顶点 1 对应 UV 右下角
  1, 1,  // 顶点 2 对应 UV 右上角
  0, 1,  // 顶点 3 对应 UV 左上角
  0, 0,  // 顶点 4 对应 UV 左下角
  1, 0,  // 顶点 5 对应 UV 右下角
  1, 1,  // 顶点 6 对应 UV 右上角
  0, 1   // 顶点 7 对应 UV 左上角
]

预设的geometry都是有uv坐标的 导入模型时自动包含了mesh的一切信息 包括几何体、材质、纹理等
材质的属性是同一的设置 纹理可以对每个点应用不同的设置
为材质设置这些属性以使用纹理:

  • map 颜色贴图 将图片附着到表面
  • normalMap 法线贴图 控制表面各个点的法向量 影响光照计算(发射折射等)
  • roughnessMap 粗糙度贴图 控制每个点的粗糙度
  • metalnessMap 金属度贴图
  • alphaMap 透明度贴图
  • displacementMap 凹凸度贴图 直接改变几何体顶点的位置(需要高密度模型)

纹理使用例

import earth from './earth.jpg'

  const map = useTexture(earth.src)

  <mesh>
    <sphereGeometry args={[1, 64, 64]}></sphereGeometry>
    <meshBasicMaterial map={map}></meshBasicMaterial>
  </mesh>

earth.jpg image.png image.png

测试和学习

fanethedivine.github.io/test-three/…

image.png