Skip to content

Reflector

Description

The Reflector object, included in the Threejs examples sub folders can be used to create mirrors.

The below example, extends the DragControls with GLTF Animations with several mirrors.

The reflector object will mirror in one direction only. If you want to create an infinity mirror, you can place another Reflector in front of the existing Reflector, but placed some distance in front of it and facing back towards at the first Reflector. The Reflectors will only reflect what they see in the current Render pass. So you won't get the realistic infinity mirror effect at first. Since Reflectors, only Reflect in one direction, you can place another Reflector behind an Existing Reflector in series, and you will get a triple reflection in the other Reflector reflecting back at both the others placed in series. In the example above, I have 4 Reflectors, all looking at each other. 2 are looking forward and 2 are looking back. And are all at different distances. Note that creating an infinity mirror using this technique will will require more CPU for each new Reflector added to the scene.

Code

./src/client/client.ts

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import Stats from 'three/examples/jsm/libs/stats.module'
import { DragControls } from 'three/examples/jsm/controls/DragControls'
import { Reflector } from 'three/examples/jsm/objects/Reflector'

const scene = new THREE.Scene()
scene.add(new THREE.AxesHelper(5))

const light1 = new THREE.PointLight()
light1.position.set(2.5, 2.5, 2.5)
light1.castShadow = true
scene.add(light1)

const light2 = new THREE.PointLight()
light2.position.set(-2.5, 2.5, 2.5)
light2.castShadow = true
scene.add(light2)

const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.01,
    1000
)
camera.position.set(0.8, 1.4, 1.0)

const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
renderer.shadowMap.enabled = true
document.body.appendChild(renderer.domElement)

const orbitControls = new OrbitControls(camera, renderer.domElement)
orbitControls.enableDamping = true
orbitControls.target.set(0, 1, 0)

const sceneMeshes: THREE.Mesh[] = []
let boxHelper: THREE.BoxHelper

const dragControls = new DragControls(sceneMeshes, camera, renderer.domElement)
dragControls.addEventListener('hoveron', function () {
    boxHelper.visible = true
    orbitControls.enabled = false
})
dragControls.addEventListener('hoveroff', function () {
    boxHelper.visible = false
    orbitControls.enabled = true
})
dragControls.addEventListener('drag', function (event) {
    event.object.position.y = 0
})
dragControls.addEventListener('dragstart', function () {
    boxHelper.visible = true
    orbitControls.enabled = false
})
dragControls.addEventListener('dragend', function () {
    boxHelper.visible = false
    orbitControls.enabled = true
})

const planeGeometry = new THREE.PlaneGeometry(25, 25)
const texture = new THREE.TextureLoader().load('img/grid.png')
const plane: THREE.Mesh = new THREE.Mesh(
    planeGeometry,
    new THREE.MeshPhongMaterial({ map: texture })
)
plane.rotateX(-Math.PI / 2)
plane.receiveShadow = true
scene.add(plane)

let mixer: THREE.AnimationMixer
let modelReady = false
const gltfLoader: GLTFLoader = new GLTFLoader()
let modelGroup: THREE.Group
let modelDragBox: THREE.Mesh

gltfLoader.load(
    'models/eve@punching.glb',
    (gltf) => {
        gltf.scene.traverse(function (child) {
            if (child instanceof THREE.Group) {
                modelGroup = child
            }
            if ((child as THREE.Mesh).isMesh) {
                child.castShadow = true
                child.frustumCulled = false
                ;(child as THREE.Mesh).geometry.computeVertexNormals()
            }
        })

        mixer = new THREE.AnimationMixer(gltf.scene)
        mixer.clipAction((gltf as any).animations[0]).play()

        modelDragBox = new THREE.Mesh(
            new THREE.BoxGeometry(0.5, 1.3, 0.5),
            new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 })
        )
        modelDragBox.geometry.translate(0, 0.65, 0)
        scene.add(modelDragBox)
        sceneMeshes.push(modelDragBox)

        boxHelper = new THREE.BoxHelper(modelDragBox, 0xffff00)
        boxHelper.visible = false
        scene.add(boxHelper)

        scene.add(gltf.scene)

        modelReady = true
    },
    (xhr) => {
        console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
    },
    (error) => {
        console.log(error)
    }
)

window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()
    renderer.setSize(window.innerWidth, window.innerHeight)
    render()
}

const mirrorBack1: Reflector = new Reflector(
    new THREE.PlaneBufferGeometry(2, 2),
    {
        color: new THREE.Color(0x7f7f7f),
        textureWidth: window.innerWidth * window.devicePixelRatio,
        textureHeight: window.innerHeight * window.devicePixelRatio
    }
)

mirrorBack1.position.y = 1
mirrorBack1.position.z = -1
scene.add(mirrorBack1)

const mirrorBack2: Reflector = new Reflector(
    new THREE.PlaneBufferGeometry(2, 2),
    {
        color: new THREE.Color(0x7f7f7f),
        textureWidth: window.innerWidth * window.devicePixelRatio,
        textureHeight: window.innerHeight * window.devicePixelRatio
    }
)

mirrorBack2.position.y = 1
mirrorBack2.position.z = -2
scene.add(mirrorBack2)

const mirrorFront1: Reflector = new Reflector(
    new THREE.PlaneBufferGeometry(2, 2),
    {
        color: new THREE.Color(0x7f7f7f),
        //clipBias: 0.003,
        textureWidth: window.innerWidth * window.devicePixelRatio,
        textureHeight: window.innerHeight * window.devicePixelRatio
    }
)
mirrorFront1.position.y = 1
mirrorFront1.position.z = 1
mirrorFront1.rotateY(Math.PI)
scene.add(mirrorFront1)

const mirrorFront2: Reflector = new Reflector(
    new THREE.PlaneBufferGeometry(2, 2),
    {
        color: new THREE.Color(0x7f7f7f),
        //clipBias: 0.003,
        textureWidth: window.innerWidth * window.devicePixelRatio,
        textureHeight: window.innerHeight * window.devicePixelRatio
    }
)
mirrorFront2.position.y = 1
mirrorFront2.position.z = 2
mirrorFront2.rotateY(Math.PI)
scene.add(mirrorFront2)

const stats = Stats()
document.body.appendChild(stats.dom)

const clock = new THREE.Clock()

function animate() {
    requestAnimationFrame(animate)

    orbitControls.update()

    if (modelReady) {
        mixer.update(clock.getDelta())
        modelGroup.position.copy(modelDragBox.position)
        boxHelper.update()
    }

    render()

    stats.update()
}

function render() {
    renderer.render(scene, camera)
}

animate()