Three.js 中的 Reflector 类

发布时间 2023-04-21 01:26:27作者: 脉望

说明

Reflector 是 three.js 中的一个类,用于创建反射效果的对象。它继承自 Mesh,可以添加到场景中作为一个可渲染的物体。

Reflector 的构造函数如下:

constructor(geometry?: BufferGeometry, options?: ReflectorOptions)

其中,geometry 参数是可选的,用于指定 Reflector 的几何形状。options 参数也是可选的,是一个配置选项对象,包含以下属性:

  • color: 反射面的颜色,可以是一个 CSS 颜色字符串或是一个 three.js 的 Color 对象,默认值是 0x7F7F7F。
  • textureWidth: 反射纹理的宽度,单位是像素,默认值是 512。
  • textureHeight: 反射纹理的高度,单位是像素,默认值是 512。
  • clipBias: 剪裁偏移值,用于控制剪裁平面的位置,可以用于解决渲染的反射对象和原始对象之间的闪烁问题,默认值是 0。
  • shader: 用于渲染反射效果的着色器程序,可以是一个 three.js 的 ShaderMaterial 对象,默认值是 undefined,表示使用内置的着色器。
  • encoding: 反射纹理的编码格式,默认值是 LinearEncoding。
  • multisample: 反射纹理的多重采样级别,用于抗锯齿,默认值是 0,表示不使用多重采样。

Reflector 对象有以下方法:

  • getRenderTarget(): 获取渲染到的反射纹理对象,可以用于后续的处理。
  • dispose(): 释放 Reflector 对象的资源,包括纹理和几何形状。

Reflector 对象可以通过以下步骤使用:

  1. 创建一个 Reflector 对象,可以指定几何形状和配置选项。
import { Reflector } from 'three';

const geometry = new PlaneBufferGeometry(10, 10);
const options = {
    color: 0x7F7F7F,
    textureWidth: 512,
    textureHeight: 512,
    clipBias: 0,
    shader: undefined,
    encoding: LinearEncoding,
    multisample: 0
};

const reflector = new Reflector(geometry, options);
  1. 将 Reflector 对象添加到场景中。
scene.add(reflector);
  1. 渲染场景,并将渲染结果显示到屏幕上。
renderer.render(scene, camera);

Reflector 对象会根据设置的几何形状和配置选项,在指定位置渲染反射效果,可以通过调整配置选项来控制反射效果的外观和行为。当不再需要使用 Reflector 对象时,可以调用 dispose() 方法来释放资源。

示例:

效果:
image

代码:

<template>
  <div ref="containerRef" class="container"></div>
</template>

<script setup>
import * as THREE from 'three'
import { useRoute } from 'vue-router'
import { onMounted, ref, onUnmounted } from 'vue'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { Reflector  } from 'three/examples/jsm/objects/Reflector.js'
// import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js'
// import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass.js'
// import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js'

const route = useRoute()
document.title = route.meta.title

const containerRef = ref(null)

let camera, scene, renderer

let cameraControls

let sphereGroup, smallSphere

let groundMirror, verticalMirror

THREE.ColorManagement.enabled = true

// renderer
renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)

// scene
scene = new THREE.Scene()

// camera
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 500)
camera.position.set(0, 75, 160)
cameraControls = new OrbitControls(camera, renderer.domElement)
cameraControls.target.set(0, 40, 0)
cameraControls.maxDistance = 400
cameraControls.minDistance = 10
cameraControls.update()

const planeGeo = new THREE.PlaneGeometry(100.1, 100.1)

// reflectors/mirrors
let geometry, material

geometry = new THREE.CircleGeometry(40, 64)

groundMirror = new Reflector(geometry, {
    clipBias: 0.003,
    textureWidth: window.innerWidth * window.devicePixelRatio,
    textureHeight: window.innerHeight * window.devicePixelRatio,
    color: '#b5b5b5'
})
groundMirror.position.y = 0.5
groundMirror.rotateX(- Math.PI / 2)
scene.add(groundMirror)

geometry = new THREE.PlaneGeometry(100, 100)
verticalMirror = new Reflector(geometry, {
    clipBias: 0.003,
    textureWidth: window.innerWidth * window.devicePixelRatio,
    textureHeight: window.innerHeight * window.devicePixelRatio,
    color: '#c1cbcb'
})
verticalMirror.position.y = 50
verticalMirror.position.z = - 50
scene.add(verticalMirror)

sphereGroup = new THREE.Object3D()
scene.add(sphereGroup)

geometry = new THREE.CylinderGeometry(0.1, 15 * Math.cos(Math.PI / 180 * 30), 0.1, 24, 1)
material = new THREE.MeshPhongMaterial({ color: 0xffffff, emissive: '#8d8d8d' })
const sphereCap = new THREE.Mesh(geometry, material)
sphereCap.position.y = - 15 * Math.sin(Math.PI / 180 * 30) - 0.05
sphereCap.rotateX(- Math.PI)

geometry = new THREE.SphereGeometry(15, 24, 24, Math.PI / 2, Math.PI * 2, 0, Math.PI / 180 * 120)
const halfSphere = new THREE.Mesh(geometry, material)
halfSphere.add(sphereCap)
halfSphere.rotateX(- Math.PI / 180 * 135)
halfSphere.rotateZ(- Math.PI / 180 * 20)
halfSphere.position.y = 7.5 + 15 * Math.sin(Math.PI / 180 * 30)

sphereGroup.add(halfSphere)

geometry = new THREE.IcosahedronGeometry(5, 0)
material = new THREE.MeshPhongMaterial({ color: 0xffffff, emissive: '#7b7b7b', flatShading: true })
smallSphere = new THREE.Mesh(geometry, material)
scene.add(smallSphere)

// walls
const planeTop = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }))
planeTop.position.y = 100
planeTop.rotateX(Math.PI / 2)
scene.add(planeTop)

const planeBottom = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xffffff }))
planeBottom.rotateX(- Math.PI / 2)
scene.add(planeBottom)

const planeFront = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xbbbbfe }))
planeFront.position.z = 50
planeFront.position.y = 50
planeFront.rotateY(Math.PI)
scene.add(planeFront)

const planeRight = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0x00ff00 }))
planeRight.position.x = 50
planeRight.position.y = 50
planeRight.rotateY(- Math.PI / 2)
scene.add(planeRight)

const planeLeft = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ color: 0xff0000 }))
planeLeft.position.x = - 50
planeLeft.position.y = 50
planeLeft.rotateY(Math.PI / 2)
scene.add(planeLeft)

// lights
const mainLight = new THREE.PointLight('#e7e7e7', 1.5, 250)
mainLight.position.y = 60
scene.add(mainLight)

const greenLight = new THREE.PointLight('#00ff00', 0.25, 1000)
greenLight.position.set(550, 50, 0)
scene.add(greenLight)

const redLight = new THREE.PointLight('#ff0000', 0.25, 1000)
redLight.position.set(- 550, 50, 0)
scene.add(redLight)

const blueLight = new THREE.PointLight('#bbbbfe', 0.25, 1000)
blueLight.position.set(0, 50, 550)
scene.add(blueLight)

// 后期模糊处理
/* const composer = new EffectComposer(renderer)

const renderPass = new RenderPass(scene, camera)
composer.addPass(renderPass)

const bloomPass = new UnrealBloomPass(new THREE.Vector2(window.innerWidth, window.innerHeight), 0.5, 0.4, 0.85)
composer.addPass(bloomPass) */

function animate() {

    requestAnimationFrame(animate)

    const timer = Date.now() * 0.01

    sphereGroup.rotation.y -= 0.002

    smallSphere.position.set(
        Math.cos(timer * 0.1) * 30,
        Math.abs(Math.cos(timer * 0.2)) * 20 + 5,
        Math.sin(timer * 0.1) * 30
    )
    smallSphere.rotation.y = (Math.PI / 2) - timer * 0.1
    smallSphere.rotation.z = timer * 0.8

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

}

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()

    renderer.setSize(window.innerWidth, window.innerHeight)

    groundMirror.getRenderTarget().setSize(
        window.innerWidth * window.devicePixelRatio,
        window.innerHeight * window.devicePixelRatio
    )
    verticalMirror.getRenderTarget().setSize(
        window.innerWidth * window.devicePixelRatio,
        window.innerHeight * window.devicePixelRatio
    )
}

onMounted(() => {
    containerRef.value.appendChild(renderer.domElement)
    animate()
    window.addEventListener('resize', onWindowResize)
})

onUnmounted(() => {
    window.removeEventListener('resize', onWindowResize)
})
</script>