├── screenshot.png ├── public ├── favicon.ico ├── ogimage.png ├── favicon-16x16.png ├── favicon-32x32.png ├── mstile-150x150.png ├── apple-touch-icon.png ├── android-chrome-192x192.png ├── browserconfig.xml ├── site.webmanifest └── index.html ├── sandbox.config.json ├── .prettierrc ├── README.md ├── .gitignore ├── src ├── index.js ├── data.js ├── Scene.js └── ThinFilmFresnelMap.js └── package.json /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emmelleppi/r3f-mirrors/HEAD/screenshot.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emmelleppi/r3f-mirrors/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/ogimage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emmelleppi/r3f-mirrors/HEAD/public/ogimage.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emmelleppi/r3f-mirrors/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emmelleppi/r3f-mirrors/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emmelleppi/r3f-mirrors/HEAD/public/mstile-150x150.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emmelleppi/r3f-mirrors/HEAD/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emmelleppi/r3f-mirrors/HEAD/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "infiniteLoopProtection": false, 3 | "hardReloadOnChange": false, 4 | "view": "browser" 5 | } 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 160, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": true 10 | } 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Preview ◼️ https://r3f-mirrors.netlify.app/ ◼️ 2 | 3 | ![](https://github.com/emmelleppi/r3f-mirrors/blob/master/screenshot.png?raw=true) 4 | 5 | ```bash 6 | # using yarn 7 | yarn && yarn start 8 | 9 | # using npm 10 | npm install && npm run start 11 | ``` 12 | -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "theme_color": "#ffffff", 12 | "background_color": "#ffffff", 13 | "display": "standalone" 14 | } 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { render } from 'react-dom' 2 | import React, { Suspense } from 'react' 3 | import { Canvas } from 'react-three-fiber' 4 | import * as THREE from 'three' 5 | 6 | import Scene from './Scene' 7 | 8 | function App() { 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | ) 18 | } 19 | 20 | render(, document.querySelector('#root')) 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "use-r3f-assets", 3 | "version": "1.0.0", 4 | "description": "", 5 | "keywords": [], 6 | "main": "src/index.js", 7 | "dependencies": { 8 | "drei": "1.5.6", 9 | "react": "17.0.0-rc.1", 10 | "react-dom": "17.0.0-rc.1", 11 | "react-scripts": "3.4.3", 12 | "react-three-fiber": "5.0.0-beta.12", 13 | "three": "0.120.1" 14 | }, 15 | "devDependencies": {}, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test --env=jsdom", 20 | "eject": "react-scripts eject" 21 | }, 22 | "browserslist": [ 23 | ">0.2%", 24 | "not dead", 25 | "not ie <= 11", 26 | "not op_mini all" 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 43 | 44 | 53 | Mirrors ◼️ A React Three Fiber demo 54 | 55 | 56 | 57 |
58 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /src/data.js: -------------------------------------------------------------------------------- 1 | export const mirrorsData = { 2 | mirrors: [ 3 | { 4 | args: [2.87483173052424, 2.920755196190408, 0.05], 5 | position: [3.116376203948097, -2.1742814140991196, -7.998859699752443], 6 | rotation: [1.2536197471688286, -1.6683567077395978, -2.8428053193736256] 7 | }, 8 | { 9 | args: [1.799233278635274, 1.9642524560408021, 0.05], 10 | position: [-3.325473394997085, 3.5307542721423446, -6.530151273151705], 11 | rotation: [1.3951213133257899, -0.2888432911308304, 0.7178380971731012] 12 | }, 13 | { 14 | args: [2.8780801433198553, 2.9065216543855974, 0.05], 15 | position: [1.2839348832937714, 2.888947614684322, -6.467835086028824], 16 | rotation: [-1.3341775957580109, 2.8031736269533125, -0.18771283594857274] 17 | }, 18 | { 19 | args: [2.2175936863874006, 1.3820832190972703, 0.05], 20 | position: [4.552400557892, 0.9814639517113943, -5.836395383986279], 21 | rotation: [-2.3299625953354437, 0.6139693063561498, -0.3902201705507059] 22 | }, 23 | { 24 | args: [1.7446126775638997, 2.6211835436253392, 0.05], 25 | position: [-2.826056860647832, -3.0308788716782042, -5.4685371584057485], 26 | rotation: [-1.4052581815125295, 3.002812728418492, 2.54202362440499] 27 | }, 28 | { 29 | args: [1.139549518339333, 1.8007363020629232, 0.05], 30 | position: [-0.041834072623521124, -1.351281881742426, -2.40411451302583], 31 | rotation: [1.2848394396618561, -0.310029190116405, -2.107987000676972] 32 | }, 33 | { 34 | args: [2.2021865186914007, 2.610358395964105, 0.05], 35 | position: [-4.1542927375782015, -0.349560252979882, -2.489538720961452], 36 | rotation: [1.4401104979160235, 1.8179123712769852, -2.2157249608220475] 37 | }, 38 | { 39 | args: [2.0964670262303393, 1.5750930602784585, 0.05], 40 | position: [6.571372497652996, -2.6457284555412066, -6.252562745592483], 41 | rotation: [1.1870955922970219, 0.5335941225301444, 0.4523391139946649] 42 | }, 43 | { 44 | args: [1.3270056676441064, 1.5169873297208318, 0.05], 45 | position: [3.6761316187794724, -4.141729519755186, -4.39063863430271], 46 | rotation: [-0.7690386626408349, 1.4093151276977963, 2.0252977680762476] 47 | }, 48 | { 49 | args: [5.1426105440458216, 4.416201863189162, 0.05], 50 | position: [0.646982562789564, 7.0909673302614196, -8.351518200349154], 51 | rotation: [-0.0692356415822184, 1.918047448701773, 0.5268942683942657] 52 | } 53 | ] 54 | } 55 | 56 | export const textData = [ 57 | { 58 | position: [0, 0, -10], 59 | rotation: [0, 0, 0], 60 | scale: [1, 1, 1] 61 | }, 62 | { 63 | position: [0, 0, 10], 64 | rotation: [0, 0, 0], 65 | scale: [-1, 1, 1] 66 | }, 67 | { 68 | position: [-10, 0, 0], 69 | rotation: [0, Math.PI / 2, 0], 70 | scale: [1, 1, 1] 71 | }, 72 | { 73 | position: [10, 0, 0], 74 | rotation: [0, -Math.PI / 2, 0], 75 | scale: [-1, 1, 1] 76 | }, 77 | { 78 | position: [0, 10, 0], 79 | rotation: [Math.PI / 2, 0, 0], 80 | scale: [1, 1, 1] 81 | }, 82 | { 83 | position: [0, -10, 0], 84 | rotation: [-Math.PI / 2, 0, 0], 85 | scale: [-1, 1, 1] 86 | } 87 | ] 88 | -------------------------------------------------------------------------------- /src/Scene.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useMemo, useRef, useState } from 'react' 2 | import * as THREE from 'three' 3 | import { useFrame, useThree, useResource } from 'react-three-fiber' 4 | import { Text, Box, useMatcapTexture, Octahedron, OrbitControls } from 'drei' 5 | 6 | import { ThinFilmFresnelMap } from './ThinFilmFresnelMap' 7 | import { mirrorsData } from './data' 8 | 9 | const textProps = { 10 | fontSize: 3.9, 11 | font: 'https://fonts.gstatic.com/s/raleway/v17/1Ptxg8zYS_SKggPN4iEgvnHyvveLxVtzpbCIPrcVIT9d0c8.woff' 12 | } 13 | 14 | function Title({ layers = undefined, ...props }) { 15 | const group = useRef() 16 | useEffect(() => { 17 | group.current.lookAt(0, 0, 0) 18 | }, []) 19 | 20 | return ( 21 | 22 | 23 | R 24 | 25 | 26 | 3 27 | 28 | 29 | F 30 | 31 | 32 | ) 33 | } 34 | 35 | function Mirror({ sideMaterial, reflectionMaterial, args, ...props }) { 36 | const ref = useRef() 37 | 38 | useFrame(() => { 39 | ref.current.rotation.y += 0.001 40 | ref.current.rotation.z += 0.01 41 | }) 42 | 43 | return 44 | } 45 | 46 | function Mirrors({ envMap }) { 47 | const sideMaterial = useResource() 48 | const reflectionMaterial = useResource() 49 | const [thinFilmFresnelMap] = useState(new ThinFilmFresnelMap()) 50 | 51 | return ( 52 | 53 | 54 | 55 | 56 | {mirrorsData.mirrors.map((mirror, index) => ( 57 | 58 | ))} 59 | 60 | ) 61 | } 62 | 63 | function TitleCopies({ layers }) { 64 | const vertices = useMemo(() => { 65 | const y = new THREE.IcosahedronGeometry(8) 66 | return y.vertices 67 | }, []) 68 | 69 | return ( 70 | 71 | {vertices.map((vertex, i) => ( 72 | 73 | ))} 74 | </group> 75 | ) 76 | } 77 | 78 | export default function Scene() { 79 | const renderTarget = useMemo( 80 | () => new THREE.WebGLCubeRenderTarget(1024, { format: THREE.RGBAFormat, generateMipmaps: true, minFilter: THREE.LinearMipmapLinearFilter }), 81 | [] 82 | ) 83 | 84 | const camera = useRef() 85 | const sphere = useRef() 86 | 87 | const [matcapTexture] = useMatcapTexture('C8D1DC_575B62_818892_6E747B', 1024) 88 | 89 | useFrame(({ gl, scene }) => { 90 | camera.current.update(gl, scene) 91 | }) 92 | 93 | const group = useRef() 94 | 95 | const { viewport } = useThree() 96 | 97 | const [rotationEuler, rotationQuaternion] = useMemo(() => { 98 | return [new THREE.Euler(0, 0, 0), new THREE.Quaternion(0, 0, 0, 0)] 99 | }, []) 100 | 101 | useFrame(({ mouse }) => { 102 | const x = (mouse.x * viewport.width) / 100 103 | const y = (mouse.y * viewport.height) / 100 104 | 105 | rotationEuler.set(y, x, 0) 106 | rotationQuaternion.setFromEuler(rotationEuler) 107 | 108 | group.current.quaternion.slerp(rotationQuaternion, 0.1) 109 | }) 110 | 111 | return ( 112 | <group name="sceneContainer" ref={group}> 113 | <Octahedron layers={[11]} name="background" ref={sphere} args={[20, 4, 4]} position={[0, 0, -5]}> 114 | <meshMatcapMaterial matcap={matcapTexture} side={THREE.BackSide} transparent opacity={0.3} /> 115 | </Octahedron> 116 | 117 | <cubeCamera layers={[11]} name="cubeCamera" ref={camera} args={[0.1, 100, renderTarget]} position={[0, 0, 5]} /> 118 | <TitleCopies layers={[11]} /> 119 | <Mirrors envMap={renderTarget.texture} /> 120 | 121 | <Title name="title" position={[0, 0, -10]} /> 122 | 123 | {window.location.search.indexOf('ctrl') > -1 && <OrbitControls />} 124 | </group> 125 | ) 126 | } 127 | -------------------------------------------------------------------------------- /src/ThinFilmFresnelMap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @classdesc 3 | * ThinFilmFresnelMap is a lookup texture containing the reflection colour. The texture index value 4 | * is dot(normal, view). The texture values are stored in approximated gamma space (power 2.0), so 5 | * the sampled value needs to be multiplied with itself before use. The sampled value should replace 6 | * the fresnel factor in a PBR material. 7 | * 8 | * @property filmThickness The thickness of the thin film layer in nanometers. Defaults to 380. 9 | * @property refractiveIndexFilm The refractive index of the thin film. Defaults to 2. 10 | * @property refractiveIndexBase The refractive index of the material under the film. Defaults to 3. 11 | * 12 | * @constructor 13 | * @param filmThickness The thickness of the thin film layer in nanometers. Defaults to 380. 14 | * @param refractiveIndexFilm The refractive index of the thin film. Defaults to 2. 15 | * @param refractiveIndexBase The refractive index of the material under the film. Defaults to 3. 16 | * @param size The width of the texture. Defaults to 64. 17 | * 18 | * @extends DataTexture 19 | * 20 | * @author David Lenaerts <http://www.derschmale.com> 21 | */ 22 | import * as THREE from 'three' 23 | 24 | export function ThinFilmFresnelMap(filmThickness, refractiveIndexFilm, refractiveIndexBase, size) { 25 | this._filmThickness = filmThickness || 500.0 26 | this._refractiveIndexFilm = refractiveIndexFilm || 2 27 | this._refractiveIndexBase = refractiveIndexBase || 3 28 | this._size = size || 128 29 | this._data = new Uint8Array(this._size * 4) 30 | 31 | this._updateData() 32 | 33 | THREE.DataTexture.call( 34 | this, 35 | this._data, 36 | this._size, 37 | 1, 38 | THREE.RGBAFormat, 39 | THREE.UnsignedByteType, 40 | THREE.UVMapping, 41 | THREE.RepeatWrapping, 42 | THREE.RepeatWrapping, 43 | THREE.LinearFilter, 44 | THREE.LinearMipMapLinearFilter 45 | ) 46 | this.generateMipmaps = true 47 | this.needsUpdate = true 48 | } 49 | 50 | ThinFilmFresnelMap.prototype = Object.create(THREE.DataTexture.prototype, { 51 | filmThickness: { 52 | get: function () { 53 | return this._filmThickness 54 | }, 55 | set: function (value) { 56 | this._filmThickness = value 57 | this.updateSettings(this._filmThickness, this._refractiveIndexFilm, this._refractiveIndexBase) 58 | } 59 | }, 60 | refractiveIndexFilm: { 61 | get: function () { 62 | return this._refractiveIndexFilm 63 | }, 64 | set: function (value) { 65 | this._refractiveIndexFilm = value 66 | this.updateSettings(this._filmThickness, this._refractiveIndexFilm, this._refractiveIndexBase) 67 | } 68 | }, 69 | refractiveIndexBase: { 70 | get: function () { 71 | return this._refractiveIndexBase 72 | }, 73 | set: function (value) { 74 | this._refractiveIndexBase = value 75 | this.updateSettings(this._filmThickness, this._refractiveIndexFilm, this._refractiveIndexBase) 76 | } 77 | } 78 | }) 79 | 80 | /** 81 | * Regenerates the lookup texture given new data. 82 | * @param filmThickness The thickness of the thin film layer in nanometers. Defaults to 380. 83 | * @param refractiveIndexFilm The refractive index of the thin film. Defaults to 2. 84 | * @param refractiveIndexBase The refractive index of the material under the film. Defaults to 3. 85 | */ 86 | ThinFilmFresnelMap.prototype.updateSettings = function (filmThickness, refractiveIndexFilm, refractiveIndexBase) { 87 | this._filmThickness = filmThickness || 380 88 | this._refractiveIndexFilm = refractiveIndexFilm || 2 89 | this._refractiveIndexBase = refractiveIndexBase || 3 90 | this._updateData() 91 | } 92 | 93 | /** 94 | * @private 95 | */ 96 | ThinFilmFresnelMap.prototype._fresnelRefl = function (refractiveIndex1, refractiveIndex2, cos1, cos2, R, phi) { 97 | // r is amplitudinal, R is power 98 | var sin1Sqr = 1.0 - cos1 * cos1 // = sin^2(incident) 99 | var refrRatio = refractiveIndex1 / refractiveIndex2 100 | 101 | if (refrRatio * refrRatio * sin1Sqr > 1.0) { 102 | // total internal reflection 103 | R.x = 1.0 104 | R.y = 1.0 105 | 106 | var sqrRefrRatio = refrRatio * refrRatio 107 | // it looks like glsl's atan ranges are different from those in JS? 108 | phi.x = 2.0 * Math.atan((-sqrRefrRatio * Math.sqrt(sin1Sqr - 1.0 / sqrRefrRatio)) / cos1) 109 | phi.y = 2.0 * Math.atan(-Math.sqrt(sin1Sqr - 1.0 / sqrRefrRatio) / cos1) 110 | } else { 111 | var r_p = (refractiveIndex2 * cos1 - refractiveIndex1 * cos2) / (refractiveIndex2 * cos1 + refractiveIndex1 * cos2) 112 | var r_s = (refractiveIndex1 * cos1 - refractiveIndex2 * cos2) / (refractiveIndex1 * cos1 + refractiveIndex2 * cos2) 113 | 114 | phi.x = r_p < 0.0 ? Math.PI : 0.0 115 | phi.y = r_s < 0.0 ? Math.PI : 0.0 116 | 117 | R.x = r_p * r_p 118 | R.y = r_s * r_s 119 | } 120 | } 121 | 122 | /** 123 | * @private 124 | */ 125 | ThinFilmFresnelMap.prototype._updateData = function () { 126 | var filmThickness = this._filmThickness 127 | var refractiveIndexFilm = this._refractiveIndexFilm 128 | var refractiveIndexBase = this._refractiveIndexBase 129 | var size = this._size 130 | 131 | // approximate CIE XYZ weighting functions from: http://jcgt.org/published/0002/02/01/paper.pdf 132 | function xFit_1931(lambda) { 133 | var t1 = (lambda - 442.0) * (lambda < 442.0 ? 0.0624 : 0.0374) 134 | var t2 = (lambda - 599.8) * (lambda < 599.8 ? 0.0264 : 0.0323) 135 | var t3 = (lambda - 501.1) * (lambda < 501.1 ? 0.049 : 0.0382) 136 | return 0.362 * Math.exp(-0.5 * t1 * t1) + 1.056 * Math.exp(-0.5 * t2 * t2) - 0.065 * Math.exp(-0.5 * t3 * t3) 137 | } 138 | 139 | function yFit_1931(lambda) { 140 | var t1 = (lambda - 568.8) * (lambda < 568.8 ? 0.0213 : 0.0247) 141 | var t2 = (lambda - 530.9) * (lambda < 530.9 ? 0.0613 : 0.0322) 142 | return 0.821 * Math.exp(-0.5 * t1 * t1) + 0.286 * Math.exp(-0.5 * t2 * t2) 143 | } 144 | 145 | function zFit_1931(lambda) { 146 | var t1 = (lambda - 437.0) * (lambda < 437.0 ? 0.0845 : 0.0278) 147 | var t2 = (lambda - 459.0) * (lambda < 459.0 ? 0.0385 : 0.0725) 148 | return 1.217 * Math.exp(-0.5 * t1 * t1) + 0.681 * Math.exp(-0.5 * t2 * t2) 149 | } 150 | 151 | var data = this._data 152 | var phi12 = new THREE.Vector2() 153 | var phi21 = new THREE.Vector2() 154 | var phi23 = new THREE.Vector2() 155 | var R12 = new THREE.Vector2() 156 | var T12 = new THREE.Vector2() 157 | var R23 = new THREE.Vector2() 158 | var R_bi = new THREE.Vector2() 159 | var T_tot = new THREE.Vector2() 160 | var R_star = new THREE.Vector2() 161 | var R_bi_sqr = new THREE.Vector2() 162 | var R_12_star = new THREE.Vector2() 163 | var R_star_t_tot = new THREE.Vector2() 164 | 165 | var refrRatioSqr = 1.0 / (refractiveIndexFilm * refractiveIndexFilm) 166 | var refrRatioSqrBase = (refractiveIndexFilm * refractiveIndexFilm) / (refractiveIndexBase * refractiveIndexBase) 167 | 168 | // RGB is too limiting, so we use the entire spectral domain, but using limited samples (64) to 169 | // create more pleasing bands 170 | var numBands = 64 171 | var waveLenRange = 780 - 380 // the entire visible range 172 | 173 | for (var i = 0; i < size; ++i) { 174 | var cosThetaI = i / size 175 | var cosThetaT = Math.sqrt(1 - refrRatioSqr * (1.0 - cosThetaI * cosThetaI)) 176 | var cosThetaT2 = Math.sqrt(1 - refrRatioSqrBase * (1.0 - cosThetaT * cosThetaT)) 177 | 178 | // this is essentially the extra distance traveled by a ray if it bounds through the film 179 | var pathDiff = 2.0 * refractiveIndexFilm * filmThickness * cosThetaT 180 | var pathDiff2PI = 2.0 * Math.PI * pathDiff 181 | 182 | this._fresnelRefl(1.0, refractiveIndexFilm, cosThetaI, cosThetaT, R12, phi12) 183 | T12.x = 1.0 - R12.x 184 | T12.y = 1.0 - R12.y 185 | phi21.x = Math.PI - phi12.x 186 | phi21.y = Math.PI - phi12.y 187 | 188 | // this concerns the base layer 189 | this._fresnelRefl(refractiveIndexFilm, refractiveIndexBase, cosThetaT, cosThetaT2, R23, phi23) 190 | R_bi.x = Math.sqrt(R23.x * R12.x) 191 | R_bi.y = Math.sqrt(R23.y * R12.y) 192 | T_tot.x = Math.sqrt(T12.x * T12.x) 193 | T_tot.y = Math.sqrt(T12.y * T12.y) 194 | R_star.x = (T12.x * T12.x * R23.x) / (1.0 - R23.x * R12.x) 195 | R_star.y = (T12.y * T12.y * R23.y) / (1.0 - R23.y * R12.y) 196 | R_bi_sqr.x = R_bi.x * R_bi.x 197 | R_bi_sqr.y = R_bi.y * R_bi.y 198 | R_12_star.x = R12.x + R_star.x 199 | R_12_star.y = R12.y + R_star.y 200 | R_star_t_tot.x = R_star.x - T_tot.x 201 | R_star_t_tot.y = R_star.y - T_tot.y 202 | var x = 0, 203 | y = 0, 204 | z = 0 205 | var totX = 0, 206 | totY = 0, 207 | totZ = 0 208 | 209 | // TODO: we could also put the thickness in the look-up table, make it a 2D table 210 | for (var j = 0; j < numBands; ++j) { 211 | var waveLen = 380 + (j / (numBands - 1)) * waveLenRange 212 | var deltaPhase = pathDiff2PI / waveLen 213 | 214 | var cosPhiX = Math.cos(deltaPhase + phi23.x + phi21.x) 215 | var cosPhiY = Math.cos(deltaPhase + phi23.y + phi21.y) 216 | var valX = R_12_star.x + ((2.0 * (R_bi.x * cosPhiX - R_bi_sqr.x)) / (1.0 - 2 * R_bi.x * cosPhiX + R_bi_sqr.x)) * R_star_t_tot.x 217 | var valY = R_12_star.y + ((2.0 * (R_bi.y * cosPhiY - R_bi_sqr.y)) / (1.0 - 2 * R_bi.y * cosPhiY + R_bi_sqr.y)) * R_star_t_tot.y 218 | var v = 0.5 * (valX + valY) 219 | 220 | var wx = xFit_1931(waveLen) 221 | var wy = yFit_1931(waveLen) 222 | var wz = zFit_1931(waveLen) 223 | 224 | totX += wx 225 | totY += wy 226 | totZ += wz 227 | 228 | x += wx * v 229 | y += wy * v 230 | z += wz * v 231 | } 232 | 233 | x /= totX 234 | y /= totY 235 | z /= totZ 236 | 237 | var r = 3.2406 * x - 1.5372 * y - 0.4986 * z 238 | var g = -0.9689 * x + 1.8758 * y + 0.0415 * z 239 | var b = 0.0557 * x - 0.204 * y + 1.057 * z 240 | 241 | r = THREE.Math.clamp(r, 0.0, 1.0) 242 | g = THREE.Math.clamp(g, 0.0, 1.0) 243 | b = THREE.Math.clamp(b, 0.0, 1.0) 244 | 245 | // linear to gamma 246 | r = Math.sqrt(r) 247 | g = Math.sqrt(g) 248 | b = Math.sqrt(b) 249 | 250 | // CIE XYZ to linear rgb conversion matrix: 251 | // 3.2406 -1.5372 -0.4986 252 | // -0.9689 1.8758 0.0415 253 | // 0.0557 -0.2040 1.0570 254 | 255 | var k = i << 2 256 | data[k] = Math.floor(r * 0xff) 257 | data[k + 1] = Math.floor(g * 0xff) 258 | data[k + 2] = Math.floor(b * 0xff) 259 | data[k + 3] = 0xff 260 | } 261 | 262 | this.needsUpdate = true 263 | } 264 | --------------------------------------------------------------------------------