├── .gitignore ├── screenshot.png ├── src ├── resources │ └── cubemap │ │ ├── nx.png │ │ ├── ny.png │ │ ├── nz.png │ │ ├── px.png │ │ ├── py.png │ │ └── pz.png ├── index.html ├── gui.ts └── index.ts ├── tsconfig.json ├── tslint.json ├── webpack ├── common.config.js ├── prod.config.js └── dev.config.js ├── .github └── workflows │ ├── nodejs.yml │ └── ghpages.yml ├── README.md ├── LICENSE └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.DS_Store -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amsXYZ/three-multifaceted-refraction/HEAD/screenshot.png -------------------------------------------------------------------------------- /src/resources/cubemap/nx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amsXYZ/three-multifaceted-refraction/HEAD/src/resources/cubemap/nx.png -------------------------------------------------------------------------------- /src/resources/cubemap/ny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amsXYZ/three-multifaceted-refraction/HEAD/src/resources/cubemap/ny.png -------------------------------------------------------------------------------- /src/resources/cubemap/nz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amsXYZ/three-multifaceted-refraction/HEAD/src/resources/cubemap/nz.png -------------------------------------------------------------------------------- /src/resources/cubemap/px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amsXYZ/three-multifaceted-refraction/HEAD/src/resources/cubemap/px.png -------------------------------------------------------------------------------- /src/resources/cubemap/py.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amsXYZ/three-multifaceted-refraction/HEAD/src/resources/cubemap/py.png -------------------------------------------------------------------------------- /src/resources/cubemap/pz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amsXYZ/three-multifaceted-refraction/HEAD/src/resources/cubemap/pz.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist/", 4 | "sourceMap": true, 5 | "noImplicitAny": true, 6 | "module": "commonjs", 7 | "target": "es2017", 8 | "esModuleInterop": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "tslint:recommended", 4 | "tslint-plugin-prettier", 5 | "tslint-config-prettier" 6 | ], 7 | "rules": { 8 | "prettier": true, 9 | "object-literal-sort-keys": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /webpack/common.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | module.exports = { 4 | target: "web", 5 | mode: "production", 6 | output: { 7 | filename: "[name].bundle.js", 8 | path: path.resolve(__dirname, "../dist") 9 | }, 10 | module: { 11 | rules: [ 12 | { 13 | test: /\.ts(x?)$/, 14 | exclude: /node_modules/, 15 | loader: "ts-loader" 16 | } 17 | ] 18 | }, 19 | resolve: { 20 | extensions: [".ts", ".tsx", ".js", ".jsx"] 21 | } 22 | }; 23 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | 11 | - name: Use Node.js ${{ matrix.node-version }} 12 | uses: actions/setup-node@v1 13 | with: 14 | node-version: ${{ matrix.node-version }} 15 | 16 | - name: yarn install, build, and test 17 | run: | 18 | yarn install 19 | yarn build 20 | yarn test 21 | env: 22 | CI: true 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # three-multifaceted-refraction 2 | 3 | Three.js project which explores multifaceted refraction using convex geometries, and the physical phenomena involved in it. 4 | 5 | ![screenshot](screenshot.png) 6 | 7 | ## References 8 | 9 | - [Jesper Vos' Multiside Refraction Tutorial](https://tympanus.net/codrops/2019/10/29/real-time-multiside-refraction-in-three-steps/) 10 | - [Wikipedia's Spectral Color Article](https://en.wikipedia.org/wiki/Color#Spectral_colors) 11 | - [Learn OpenGL's PBR Theory Article](https://learnopengl.com/PBR/Theory) 12 | 13 | ## License 14 | 15 | The code is available under the [MIT license](LICENSE) 16 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AMS - Multifaceted Refraction 6 | 10 | 20 | 21 | 22 |
23 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /webpack/prod.config.js: -------------------------------------------------------------------------------- 1 | const merge = require("webpack-merge"); 2 | const path = require("path"); 3 | 4 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 5 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 6 | 7 | const commonConfig = require("./common.config"); 8 | 9 | const config = merge(commonConfig, { 10 | plugins: [ 11 | new CopyWebpackPlugin([ 12 | { 13 | from: path.join(__dirname, "../src/resources"), 14 | to: "resources", 15 | toType: "dir" 16 | } 17 | ]), 18 | new HtmlWebpackPlugin({ 19 | template: "./src/index.html" 20 | }) 21 | ] 22 | }); 23 | 24 | module.exports = config; 25 | -------------------------------------------------------------------------------- /.github/workflows/ghpages.yml: -------------------------------------------------------------------------------- 1 | name: Github Pages Deployment 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v1 13 | 14 | - name: Use Node.js ${{ matrix.node-version }} 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: ${{ matrix.node-version }} 18 | 19 | - name: yarn install and build 20 | run: | 21 | yarn install 22 | yarn build 23 | env: 24 | CI: true 25 | 26 | - name: Deploy 27 | if: success() 28 | uses: crazy-max/ghaction-github-pages@v1 29 | with: 30 | target_branch: gh-pages 31 | build_dir: dist 32 | env: 33 | GITHUB_PAT: ${{ secrets.GITHUB_PAT }} 34 | -------------------------------------------------------------------------------- /webpack/dev.config.js: -------------------------------------------------------------------------------- 1 | const merge = require("webpack-merge"); 2 | const path = require("path"); 3 | 4 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 5 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 6 | 7 | const commonConfig = require("./common.config"); 8 | 9 | const config = merge(commonConfig, { 10 | mode: "development", 11 | module: { 12 | rules: [ 13 | { 14 | enforce: "pre", 15 | test: /\.js$/, 16 | loader: "source-map-loader" 17 | } 18 | ] 19 | }, 20 | devtool: "source-map", 21 | devServer: { 22 | contentBase: "./dist" 23 | }, 24 | plugins: [ 25 | new CopyWebpackPlugin([ 26 | { 27 | from: path.join(__dirname, "../src/resources"), 28 | to: "resources", 29 | toType: "dir" 30 | } 31 | ]), 32 | new HtmlWebpackPlugin({ 33 | template: "./src/index.html" 34 | }) 35 | ] 36 | }); 37 | 38 | module.exports = config; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Andrés Valencia Téllez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "three-multifaceted-refraction", 3 | "version": "1.0.0", 4 | "description": "Three.js project which explores multifaceted refraction using convex geometries, and the physical phenomena involved in it.", 5 | "main": "index.js", 6 | "author": "Andrés Valencia Téllez", 7 | "license": "MIT", 8 | "scripts": { 9 | "tslint": "tslint --project tsconfig.json", 10 | "tslint:fix": "tslint --fix --project tsconfig.json", 11 | "prettier": "prettier -l \"**/*.ts\" \"**/*.tsx\" \"**/*.json\"", 12 | "prettier:fix": "prettier --write -l \"**/*.ts\" \"**/*.tsx\" \"**/*.json\"", 13 | "test": "yarn run tslint && yarn run prettier", 14 | "fix": "yarn tslint:fix && yarn prettier:fix", 15 | "build": "webpack --config webpack/prod.config.js", 16 | "start": "webpack-dev-server --config webpack/dev.config.js", 17 | "start:local": "yarn start --host 0.0.0.0" 18 | }, 19 | "dependencies": { 20 | "@types/animejs": "^3.1.0", 21 | "animejs": "^3.1.0", 22 | "copy-webpack-plugin": "^5.1.1", 23 | "guify": "^0.12.0", 24 | "html-webpack-plugin": "^3.2.0", 25 | "path": "^0.12.7", 26 | "prettier": "^1.19.1", 27 | "source-map-loader": "^0.2.4", 28 | "stats.js": "^0.17.0", 29 | "three": "^0.112.1", 30 | "ts-loader": "^6.2.1", 31 | "tslint": "^5.20.1", 32 | "tslint-config-prettier": "^1.18.0", 33 | "tslint-plugin-prettier": "^2.1.0", 34 | "typescript": "^3.7.4", 35 | "webpack-dev-server": "^3.10.1", 36 | "webpack-merge": "^4.2.2" 37 | }, 38 | "devDependencies": { 39 | "webpack": "^4.41.5", 40 | "webpack-cli": "^3.3.10" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/gui.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BoxBufferGeometry, 3 | Color, 4 | DodecahedronBufferGeometry, 5 | IcosahedronBufferGeometry, 6 | OctahedronBufferGeometry, 7 | PlaneBufferGeometry, 8 | SphereBufferGeometry, 9 | TetrahedronBufferGeometry, 10 | TorusBufferGeometry, 11 | TorusKnotBufferGeometry 12 | } from "three"; 13 | 14 | // tslint:disable:no-var-requires 15 | const guify = require("guify"); 16 | // tslint:enable:no-var-requires 17 | 18 | export function createGUI(options: any, target: any) { 19 | const gui = new guify({ 20 | title: "AMS - Multifaceted Refraction", 21 | align: "right", 22 | barMode: "above" 23 | }); 24 | gui.Register({ 25 | type: "folder", 26 | label: "Properties" 27 | }); 28 | gui.Register({ 29 | type: "color", 30 | label: "Color", 31 | folder: "Properties", 32 | format: "hex", 33 | object: options, 34 | property: "color", 35 | onChange: (value: string) => { 36 | (target.material.uniforms.color.value as Color).setHex( 37 | Number("0x" + value.substr(1)) 38 | ); 39 | } 40 | }); 41 | gui.Register({ 42 | type: "range", 43 | label: "Refraction Index", 44 | folder: "Properties", 45 | min: 1.0, 46 | max: 4.0, 47 | object: options, 48 | property: "refractionIndex", 49 | onChange: (value: number) => { 50 | target.material.uniforms.refractionIndex.value = value; 51 | } 52 | }); 53 | gui.Register({ 54 | type: "range", 55 | label: "Dispersion", 56 | folder: "Properties", 57 | min: 0.0, 58 | max: 1.0, 59 | object: options, 60 | property: "dispersion", 61 | onChange: (value: string) => { 62 | target.material.uniforms.dispersion.value = value; 63 | } 64 | }); 65 | gui.Register({ 66 | type: "range", 67 | label: "Roughness", 68 | folder: "Properties", 69 | min: 0.0, 70 | max: 1.0, 71 | object: options, 72 | property: "roughness", 73 | onChange: (value: string) => { 74 | target.material.uniforms.roughness.value = value; 75 | } 76 | }); 77 | gui.Register({ 78 | type: "select", 79 | label: "Geometry", 80 | options: [ 81 | "plane", 82 | "tetrahedron", 83 | "cube", 84 | "octahedron", 85 | "dodecahedron", 86 | "icosahedron", 87 | "sphere", 88 | "torus", 89 | "knot" 90 | ], 91 | object: options, 92 | property: "geometry", 93 | onChange: (value: string) => { 94 | target.mesh.geometry.dispose(); 95 | target.backMesh.geometry.dispose(); 96 | let geom; 97 | switch (value) { 98 | case "plane": 99 | geom = new PlaneBufferGeometry(); 100 | break; 101 | case "tetrahedron": 102 | geom = new TetrahedronBufferGeometry(); 103 | break; 104 | case "cube": 105 | geom = new BoxBufferGeometry(); 106 | break; 107 | case "octahedron": 108 | geom = new OctahedronBufferGeometry(); 109 | break; 110 | case "dodecahedron": 111 | geom = new DodecahedronBufferGeometry(); 112 | break; 113 | case "icosahedron": 114 | geom = new IcosahedronBufferGeometry(); 115 | break; 116 | case "sphere": 117 | geom = new SphereBufferGeometry(1, 16, 16); 118 | break; 119 | case "torus": 120 | geom = new TorusBufferGeometry(1, 0.5, 16, 32); 121 | break; 122 | case "knot": 123 | geom = new TorusKnotBufferGeometry(1, 0.33, 64, 32); 124 | break; 125 | } 126 | target.mesh.geometry = geom; 127 | target.backMesh.geometry = geom; 128 | // value ? target.animation.play() : target.animation.pause(); 129 | } 130 | }); 131 | gui.Register({ 132 | type: "checkbox", 133 | label: "Animation", 134 | object: options, 135 | property: "animation", 136 | onChange: (value: boolean) => { 137 | value ? target.animation.play() : target.animation.pause(); 138 | } 139 | }); 140 | } 141 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import anime from "animejs"; 2 | import { 3 | BackSide, 4 | Color, 5 | CubeTexture, 6 | CubeTextureLoader, 7 | HalfFloatType, 8 | IcosahedronBufferGeometry, 9 | Mesh, 10 | PerspectiveCamera, 11 | Scene, 12 | ShaderMaterial, 13 | Uniform, 14 | Vector2, 15 | WebGLRenderer, 16 | WebGLRenderTarget 17 | } from "three"; 18 | import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; 19 | import { createGUI } from "./gui"; 20 | // tslint:disable:no-var-requires 21 | const Stats = require("stats.js"); 22 | // tslint:enable:no-var-requires 23 | 24 | const guiOptions = { 25 | refractionIndex: 1.5, 26 | color: "#FFFFFF", 27 | dispersion: 0.1, 28 | roughness: 0.9, 29 | animation: true, 30 | geometry: "icosahedron" 31 | }; 32 | 33 | const canvas = document.getElementById("canvas") as HTMLCanvasElement; 34 | const renderer = new WebGLRenderer({ canvas }); 35 | renderer.setSize(canvas.offsetWidth, canvas.offsetHeight, false); 36 | renderer.setPixelRatio(window.devicePixelRatio); 37 | renderer.autoClear = false; 38 | 39 | const backScene = new Scene(); 40 | const scene = new Scene(); 41 | const camera = new PerspectiveCamera( 42 | 60, 43 | window.innerWidth / window.innerHeight, 44 | 0.1, 45 | 1000 46 | ); 47 | camera.position.z = 5; 48 | const controls = new OrbitControls(camera, canvas); 49 | controls.enableDamping = true; 50 | controls.autoRotate = true; 51 | 52 | if ( 53 | !( 54 | renderer.getContext().getExtension("OES_texture_half_float") && 55 | renderer.getContext().getExtension("OES_texture_half_float_linear") 56 | ) 57 | ) { 58 | alert("This demo is not supported on your device."); 59 | } 60 | const renderTarget = new WebGLRenderTarget( 61 | canvas.offsetWidth, 62 | canvas.offsetHeight, 63 | { 64 | type: HalfFloatType 65 | } 66 | ); 67 | 68 | const geometry = new IcosahedronBufferGeometry(); 69 | const backMaterial = new ShaderMaterial({ 70 | vertexShader: ` 71 | varying vec3 vWorldNormal; 72 | 73 | void main() { 74 | vWorldNormal = (modelMatrix * vec4(normal, 0.0)).xyz; 75 | vWorldNormal = -normalize(vec3(-vWorldNormal.x, vWorldNormal.yz)); 76 | 77 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 78 | }`, 79 | fragmentShader: ` 80 | varying vec3 vWorldNormal; 81 | 82 | void main() { 83 | gl_FragColor.rgb = vWorldNormal; 84 | }`, 85 | side: BackSide 86 | }); 87 | const material = new ShaderMaterial({ 88 | uniforms: { 89 | resolution: new Uniform( 90 | new Vector2(canvas.offsetWidth, canvas.offsetHeight).multiplyScalar( 91 | window.devicePixelRatio 92 | ) 93 | ), 94 | backNormals: new Uniform(renderTarget.texture), 95 | envMap: new Uniform(CubeTexture.DEFAULT_IMAGE), 96 | refractionIndex: new Uniform(guiOptions.refractionIndex), 97 | color: new Uniform(new Color(guiOptions.color)), 98 | dispersion: new Uniform(guiOptions.dispersion), 99 | roughness: new Uniform(guiOptions.roughness) 100 | }, 101 | vertexShader: ` 102 | varying vec3 vWorldCameraDir; 103 | varying vec3 vWorldNormal; 104 | varying vec3 vViewNormal; 105 | 106 | void main() { 107 | vec4 worldPosition = modelMatrix * vec4( position, 1.0); 108 | 109 | vWorldCameraDir = worldPosition.xyz - cameraPosition; 110 | vWorldCameraDir = normalize(vec3(-vWorldCameraDir.x, vWorldCameraDir.yz)); 111 | 112 | vWorldNormal = (modelMatrix * vec4(normal, 0.0)).xyz; 113 | vWorldNormal = normalize(vec3(-vWorldNormal.x, vWorldNormal.yz)); 114 | 115 | vViewNormal = normalize( modelViewMatrix * vec4(normal, 0.0)).xyz; 116 | 117 | gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); 118 | }`, 119 | fragmentShader: ` 120 | #define REF_WAVELENGTH 579.0 121 | #define RED_WAVELENGTH 650.0 122 | #define GREEN_WAVELENGTH 525.0 123 | #define BLUE_WAVELENGTH 440.0 124 | 125 | uniform vec2 resolution; 126 | uniform sampler2D backNormals; 127 | uniform samplerCube envMap; 128 | uniform float refractionIndex; 129 | uniform vec3 color; 130 | uniform float dispersion; 131 | uniform float roughness; 132 | varying vec3 vWorldCameraDir; 133 | varying vec3 vWorldNormal; 134 | varying vec3 vViewNormal; 135 | 136 | vec4 refractLight(float wavelength, vec3 backFaceNormal) { 137 | float index = 1.0 / mix(refractionIndex, refractionIndex * REF_WAVELENGTH / wavelength, dispersion); 138 | vec3 dir = vWorldCameraDir; 139 | dir = refract(dir, vWorldNormal, index); 140 | dir = refract(dir, backFaceNormal, index); 141 | return textureCube(envMap, dir); 142 | } 143 | 144 | vec3 fresnelSchlick(float cosTheta, vec3 F0) 145 | { 146 | return F0 + (1.0 - F0) * pow(1.0 + cosTheta, 5.0); 147 | } 148 | 149 | void main() { 150 | vec3 backFaceNormal = texture2D(backNormals, gl_FragCoord.xy / resolution).rgb; 151 | 152 | float r = refractLight(RED_WAVELENGTH, backFaceNormal).r; 153 | float g = refractLight(GREEN_WAVELENGTH, backFaceNormal).g; 154 | float b = refractLight(BLUE_WAVELENGTH, backFaceNormal).b; 155 | 156 | vec3 fresnel = fresnelSchlick(dot(vec3(0.0,0.0,-1.0), vViewNormal), vec3(0.04)); 157 | vec3 reflectedColor = textureCube(envMap, reflect(vWorldCameraDir, vWorldNormal)).rgb * saturate((1.0 - roughness) + fresnel); 158 | 159 | gl_FragColor.rgb = vec3(r,g,b) * color + reflectedColor; 160 | }` 161 | }); 162 | 163 | const backMesh = new Mesh(geometry, backMaterial); 164 | backScene.add(backMesh); 165 | const mesh = new Mesh(geometry, material); 166 | scene.add(mesh); 167 | scene.background = new CubeTextureLoader() 168 | .setPath("./resources/cubemap/") 169 | .load( 170 | ["px.png", "nx.png", "py.png", "ny.png", "pz.png", "nz.png"], 171 | (texture: CubeTexture) => { 172 | material.uniforms.envMap.value = texture; 173 | } 174 | ); 175 | 176 | const animation = anime({ 177 | targets: mesh.rotation, 178 | x: 2 * Math.PI, 179 | y: 2 * Math.PI, 180 | z: 2 * Math.PI, 181 | duration: 15000, 182 | easing: "easeOutBounce", 183 | loop: true, 184 | autoplay: guiOptions.animation, 185 | update: () => { 186 | backMesh.rotation.copy(mesh.rotation); 187 | }, 188 | complete: () => { 189 | mesh.rotation.set(0, 0, 0); 190 | backMesh.rotation.set(0, 0, 0); 191 | } 192 | }); 193 | 194 | const stats = new Stats(); 195 | stats.dom.style.cssText = 196 | "position:fixed;bottom:0;left:0;cursor:pointer;opacity:0.9;z-index:10000"; 197 | canvas.parentElement.appendChild(stats.dom); 198 | createGUI(guiOptions, { material, animation, mesh, backMesh }); 199 | 200 | window.addEventListener("resize", (event: UIEvent) => { 201 | camera.aspect = window.innerWidth / window.innerHeight; 202 | camera.updateProjectionMatrix(); 203 | 204 | renderer.setSize(canvas.offsetWidth, canvas.offsetHeight, false); 205 | renderTarget.setSize(canvas.offsetWidth, canvas.offsetHeight); 206 | material.uniforms.resolution.value.set( 207 | window.devicePixelRatio * canvas.offsetWidth, 208 | window.devicePixelRatio * canvas.offsetHeight 209 | ); 210 | }); 211 | 212 | function render() { 213 | renderer.setRenderTarget(renderTarget); 214 | renderer.clear(true, true); 215 | renderer.render(backScene, camera); 216 | renderer.setRenderTarget(null); 217 | renderer.clear(true, true); 218 | renderer.render(scene, camera); 219 | } 220 | 221 | function animate() { 222 | requestAnimationFrame(animate); 223 | stats.begin(); 224 | controls.update(); 225 | render(); 226 | stats.end(); 227 | } 228 | animate(); 229 | --------------------------------------------------------------------------------