likes
comments
collection
share

threejs 实现3D游戏(10)—— 表面反射效果概述 真实的物理效果中,最难实现的就是真实的光照,threejs底

作者站长头像
站长
· 阅读数 65

概述

真实的物理效果中,最难实现的就是真实的光照,threejs底层是webgl,它是openGL的一个浏览器适配版本,当然也支持光线追踪,以此实现逼真的光影效果如————反射效果。

但是最大的问题是光追通常需要大量的计算资源,我们以前介绍过,图形渲染计算需要的是短时间数亿次的并发运算。这种计算只有GPU能满足,而GPU是很昂贵的资源(特别是在其他的3D模型已经占用大量资源时)。

为了满足threejs运行的环境(浏览器),我们需要在性能和效果间权衡。

环境贴图

这是一种最简单的实现,也是对性能要求最小的实现方式,大部分时候我倾向于选择它。

通过一个立方体贴图或全景贴图来表示整个场景的环境光照,从而模拟物体表面的反射效果。Three.js 支持使用 MeshStandardMaterialMeshPhysicalMaterial 配合 envMap 属性来实现环境贴图的反射效果。这种方式性能较好,但反射是静态的,不能实时反映环境的变化。

threejs中原生的写法

threejs 实现3D游戏(10)—— 表面反射效果概述 真实的物理效果中,最难实现的就是真实的光照,threejs底

单个材质设置envMap

const material = new THREE.MeshStandardMaterial({
    color: 0xffffff, 
    metalness: 0, // 金属
    roughness: 0, // 粗糙
    envMap: cubeTexture,//这里放全景贴图的texture, 你也可以用cubeCamera自己捕获立方体贴图
    envMapIntensity: 1,// 环境贴图的反射强度
});

上面的写法,是对单个材质的环境反射贴图的设置,有时候我们有的全景贴图是整个场景中的,不需要一个个去设置。

threejs 实现3D游戏(10)—— 表面反射效果概述 真实的物理效果中,最难实现的就是真实的光照,threejs底

统一设置envMap

通过设置 scene.environment,Three.js 自动地将这个环境贴图应用到了所有支持环境贴图的材质上,例如 MeshStandardMaterialMeshPhysicalMaterial

react-three 中的 Environment 组件

知道了原理,在react-three中有完整的封装:Environment 组件,我们直接使用即可

import { Environment } from "@react-three/drei";
   <Environment
    files="./textures/HJ_360.hdr"  // 此处仅仅支持hdr文件
    background  // 是否显示全景图为背景
    environmentIntensity={1.2} // 环境的反射强度
    backgroundRotation={[0, Math.PI / 2, 0]} // 背景全景图的旋转
    environmentRotation={[0, Math.PI / 2, 0]} // 环境贴图的旋转
  />

需要注意的是,如果你的贴图和真实场景的角度有出入,往往需要environmentRotation 将其设置到正确的角度。

色调和曝光的设置

threejs 实现3D游戏(10)—— 表面反射效果概述 真实的物理效果中,最难实现的就是真实的光照,threejs底

此外环境贴图的光照效果和反射效果还受到曝光和色调映射的影响,下面是一个设置示例。

色调和曝光设置示例

动态效果

如果要设置更加动态的反射效果,可以考虑使用CubeCamera来动态更新环境贴图。

通过CubeCamera动态更新环境贴图

threejs 实现3D游戏(10)—— 表面反射效果概述 真实的物理效果中,最难实现的就是真实的光照,threejs底

CubeCamera会拍摄当前场景并生成一个立方体贴图,useBoxProjectedEnv可以用于控制反射贴图的位置和大小。可以通过设置frames参数控制CubeCamera的拍摄更新频率。

不过cubeCamera获取立方体贴图时,会因为相机和景物间的距离导致反射贴图失真,如下图我在场景中心靠近相机的地方放了一个小正方体,结果在反射中它特别的大。

threejs 实现3D游戏(10)—— 表面反射效果概述 真实的物理效果中,最难实现的就是真实的光照,threejs底 解决方案之一是分别使用多个CubeCamera,独立拍摄不同的贴图,应用到不同的材质上。 但会有性能问题,所以这个CubeCamera动态贴图更适合在空旷的场景使用。

核心代码如下:

const projection = useBoxProjectedEnv( 
    [0, 0, 0], // Position 
    [1, 1, 1] // Scale ) 
<CubeCamera frames={1}> {(texture) => ( 
    <mesh> 
        <planeGeometry /> 
        <meshStandardMaterial envMap={texture} {...projection} /> 
    </mesh> )} 
</CubeCamera>

反射材质

这是在react-three/drei中在MeshStandardMaterial基础上扩展的一种用于反射的材质: MeshReflectorMaterial

他的核心原理是,在场景中使用渲染目标(render target)来捕捉环境的反射信息,并将这些信息应用到材质上。 这种方式不依赖于静态的反射贴图,而是通过实时的渲染来生成反射效果。这允许材质动态地反映周围的环境。

这种材质的使用较为简单方便,具体的属性可以参考文档

<mesh rotation={[Math.PI / 2, 0, 0]} geometry={geometry} > 
    <MeshReflectorMaterial 
        blur={[300, 100]} 
        mirror={0} 
        resolution={2048} 
        mixBlur={1} 
        mixStrength={80} 
        roughness={1} 
        depthScale={1.2} 
        minDepthThreshold={0.4} 
        maxDepthThreshold={1.4} 
        color="#151515" 
        metalness={0.6} 
    /> 
</mesh>

反射材质使用示例 threejs 实现3D游戏(10)—— 表面反射效果概述 真实的物理效果中,最难实现的就是真实的光照,threejs底

此外还有一个已经弃用的Refactor组件,及其示例: Refactor组件使用示例

threejs 实现3D游戏(10)—— 表面反射效果概述 真实的物理效果中,最难实现的就是真实的光照,threejs底

后处理——屏幕空间反射(SSR)

一种基于屏幕空间的后处理效果,通过计算屏幕上像素与其周围物体的距离和角度,模拟出近似的反射效果。SSR 可以实现更真实的动态反射,但对性能要求较高。

核心代码如下:

import { EffectComposer,SSR } from "@react-three/postprocessing";

export function Effects() {
  return (
      <EffectComposer>
        <SSR />
     </EffectComposer>
  );
}

SSR实现反射 threejs 实现3D游戏(10)—— 表面反射效果概述 真实的物理效果中,最难实现的就是真实的光照,threejs底

总结

方法优点缺点适用场景
反射贴图 (Reflection Mapping)性能高、实现简单、兼容性强不适合动态场景、反射效果不准确静态场景,静止物体表面反射效果
反射材质 (Reflective Materials)动态反射、细节丰富性能开销大、分辨率受限动态反射场景,如水面、镜子反射效果
屏幕空间反射 (SSR)真实感强、动态更新、支持复杂几何体只限于屏幕空间、性能消耗高、可能产生伪影需要高保真动态反射的场景,如游戏中的水面或玻璃

结论

  • 反射贴图 是性能最佳的选择,适用于静态、环境固定的场景。
  • 反射材质 适用于需要动态反射的简单场景,适度的计算复杂度和适应性使其成为动态环境反射的可靠选择。
  • SSR 是最逼真的方法,适用于高端硬件和需要高保真反射的场景,但要考虑性能开销和可能的视觉伪影问题。
转载自:https://juejin.cn/post/7410340504477614107
评论
请登录