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/server/server.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 | import express from "express" import path from "path" import http from "http" const port: number = 3000 class App { private server: http.Server private port: number constructor(port: number) { this.port = port const app = express() app.use(express.static(path.join(__dirname, '../client'))) app.use('/build/three.module.js', express.static(path.join(__dirname, '../../node_modules/three/build/three.module.js'))) app.use('/jsm/controls/OrbitControls', express.static(path.join(__dirname, '../../node_modules/three/examples/jsm/controls/OrbitControls.js'))) app.use('/jsm/loaders/GLTFLoader', express.static(path.join(__dirname, '../../node_modules/three/examples/jsm/loaders/GLTFLoader.js'))) app.use('/jsm/libs/stats.module', express.static(path.join(__dirname, '../../node_modules/three/examples/jsm/libs/stats.module.js'))) app.use('/jsm/controls/DragControls', express.static(path.join(__dirname, '../../node_modules/three/examples/jsm/controls/DragControls.js'))) app.use('/jsm/objects/Reflector', express.static(path.join(__dirname, '../../node_modules/three/examples/jsm/objects/Reflector.js'))) this.server = new http.Server(app); } public Start() { this.server.listen(this.port, () => { console.log( `Server listening on port ${this.port}.` ) }) } } new App(port).Start() |
./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 | import * as THREE from '/build/three.module.js' import { OrbitControls } from '/jsm/controls/OrbitControls' import { GLTFLoader } from '/jsm/loaders/GLTFLoader' import Stats from '/jsm/libs/stats.module' import { DragControls } from '/jsm/controls/DragControls' import { Reflector } from '/jsm/objects/Reflector' const scene: THREE.Scene = new THREE.Scene() const axesHelper = new THREE.AxesHelper(5) scene.add(axesHelper) var light1 = new THREE.PointLight(); light1.position.set(2.5, 2.5, 2.5) light1.castShadow = true scene.add(light1); var light2 = new THREE.PointLight(); light2.position.set(-2.5, 2.5, 2.5) light2.castShadow = true scene.add(light2); const camera: THREE.PerspectiveCamera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.01, 1000) camera.position.set(0.8, 1.4, 1.0) const renderer: THREE.WebGLRenderer = 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.screenSpacePanning = true orbitControls.target.set(0, 1, 0) const sceneMeshes: THREE.Mesh[] = new Array() let boxHelper: THREE.BoxHelper const dragControls = new DragControls(sceneMeshes, camera, renderer.domElement) dragControls.addEventListener("hoveron", function (event) { boxHelper.visible = true }); dragControls.addEventListener("hoveroff", function (event) { boxHelper.visible = false }); dragControls.addEventListener("drag", function (event) { event.object.position.y = 0; orbitControls.enabled = false; }); dragControls.addEventListener('dragstart', function (event) { boxHelper.visible = true orbitControls.enabled = false }) dragControls.addEventListener('dragend', function (event) { boxHelper.visible = false orbitControls.enabled = true }) const planeGeometry: THREE.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 ((<THREE.Mesh>child).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(.5, 1.3, .5), new THREE.MeshBasicMaterial({ transparent: true, opacity: 0 })) modelDragBox.geometry.translate(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: THREE.Clock = new THREE.Clock() var animate = function () { 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(); |