|
| 1 | +import { |
| 2 | + BackSide, |
| 3 | + BoxGeometry, |
| 4 | + Mesh, |
| 5 | + NodeMaterial, |
| 6 | + Vector3 |
| 7 | +} from 'three'; |
| 8 | +import { float, tslFn, vec3, acos, add, mul, clamp, cos, dot, exp, max, mix, modelViewProjection, normalize, positionWorld, pow, smoothstep, sub, varying, varyingProperty, vec4, uniform } from 'three/tsl'; |
| 9 | + |
| 10 | +/** |
| 11 | + * Based on "A Practical Analytic Model for Daylight" |
| 12 | + * aka The Preetham Model, the de facto standard analytic skydome model |
| 13 | + * https://www.researchgate.net/publication/220720443_A_Practical_Analytic_Model_for_Daylight |
| 14 | + * |
| 15 | + * First implemented by Simon Wallner |
| 16 | + * http://simonwallner.at/project/atmospheric-scattering/ |
| 17 | + * |
| 18 | + * Improved by Martin Upitis |
| 19 | + * http://blenderartists.org/forum/showthread.php?245954-preethams-sky-impementation-HDR |
| 20 | + * |
| 21 | + * Three.js integration by zz85 http://twitter.com/blurspline |
| 22 | +*/ |
| 23 | + |
| 24 | +class Sky extends Mesh { |
| 25 | + |
| 26 | + constructor() { |
| 27 | + |
| 28 | + const material = new NodeMaterial(); |
| 29 | + |
| 30 | + super( new BoxGeometry( 1, 1, 1 ), material ); |
| 31 | + |
| 32 | + this.turbidity = uniform( 2 ); |
| 33 | + this.rayleigh = uniform( 1 ); |
| 34 | + this.mieCoefficient = uniform( 0.005 ); |
| 35 | + this.mieDirectionalG = uniform( 0.8 ); |
| 36 | + this.sunPosition = uniform( new Vector3() ); |
| 37 | + this.up = uniform( new Vector3( 0, 1, 0 ) ); |
| 38 | + this.cameraPosition = uniform( new Vector3() ).label( 'cameraPosition' ).onRenderUpdate( ( { camera }, self ) => self.value.setFromMatrixPosition( camera.matrixWorld ) ); // TODO replace with cameraPosition from CameraNode |
| 39 | + |
| 40 | + this.isSky = true; |
| 41 | + |
| 42 | + const vertexNode = /*@__PURE__*/ tslFn( () => { |
| 43 | + |
| 44 | + // constants for atmospheric scattering |
| 45 | + const e = float( 2.71828182845904523536028747135266249775724709369995957 ); |
| 46 | + // const pi = float( 3.141592653589793238462643383279502884197169 ); |
| 47 | + |
| 48 | + // wavelength of used primaries, according to preetham |
| 49 | + // const lambda = vec3( 680E-9, 550E-9, 450E-9 ); |
| 50 | + // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: |
| 51 | + // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) |
| 52 | + const totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 ); |
| 53 | + |
| 54 | + // mie stuff |
| 55 | + // K coefficient for the primaries |
| 56 | + // const v = float( 4.0 ); |
| 57 | + // const K = vec3( 0.686, 0.678, 0.666 ); |
| 58 | + // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K |
| 59 | + const MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 ); |
| 60 | + |
| 61 | + // earth shadow hack |
| 62 | + // cutoffAngle = pi / 1.95; |
| 63 | + const cutoffAngle = float( 1.6110731556870734 ); |
| 64 | + const steepness = float( 1.5 ); |
| 65 | + const EE = float( 1000.0 ); |
| 66 | + |
| 67 | + // varying sun position |
| 68 | + |
| 69 | + const vSunDirection = normalize( this.sunPosition ); |
| 70 | + varyingProperty( 'vec3', 'vSunDirection' ).assign( vSunDirection ); |
| 71 | + |
| 72 | + // varying sun intensity |
| 73 | + |
| 74 | + const angle = dot( vSunDirection, this.up ); |
| 75 | + const zenithAngleCos = clamp( angle, - 1, 1 ); |
| 76 | + const sunIntensity = EE.mul( max( 0.0, float( 1.0 ).sub( pow( e, cutoffAngle.sub( acos( zenithAngleCos ) ).div( steepness ).negate() ) ) ) ); |
| 77 | + varyingProperty( 'float', 'vSunE' ).assign( sunIntensity ); |
| 78 | + |
| 79 | + // varying sun fade |
| 80 | + |
| 81 | + const vSunfade = float( 1.0 ).sub( clamp( float( 1.0 ).sub( exp( this.sunPosition.y.div( 450000.0 ) ) ), 0, 1 ) ); |
| 82 | + varyingProperty( 'float', 'vSunfade' ).assign( vSunfade ); |
| 83 | + |
| 84 | + // varying vBetaR |
| 85 | + |
| 86 | + const rayleighCoefficient = this.rayleigh.sub( float( 1.0 ).mul( float( 1.0 ).sub( vSunfade ) ) ); |
| 87 | + |
| 88 | + // extinction (absorbtion + out scattering) |
| 89 | + // rayleigh coefficients |
| 90 | + varyingProperty( 'vec3', 'vBetaR' ).assign( totalRayleigh.mul( rayleighCoefficient ) ); |
| 91 | + |
| 92 | + // varying vBetaM |
| 93 | + |
| 94 | + const c = float( 0.2 ).mul( this.turbidity ).mul( 10E-18 ); |
| 95 | + const totalMie = float( 0.434 ).mul( c ).mul( MieConst ); |
| 96 | + |
| 97 | + varyingProperty( 'vec3', 'vBetaM' ).assign( totalMie.mul( this.mieCoefficient ) ); |
| 98 | + |
| 99 | + // position |
| 100 | + |
| 101 | + const position = modelViewProjection(); |
| 102 | + position.z.assign( position.w ); // set z to camera.far |
| 103 | + |
| 104 | + return position; |
| 105 | + |
| 106 | + } )(); |
| 107 | + |
| 108 | + const fragmentNode = /*@__PURE__*/ tslFn( () => { |
| 109 | + |
| 110 | + const vSunDirection = varying( vec3(), 'vSunDirection' ); |
| 111 | + const vSunE = varying( float(), 'vSunE' ); |
| 112 | + const vSunfade = varying( float(), 'vSunfade' ); |
| 113 | + const vBetaR = varying( vec3(), 'vBetaR' ); |
| 114 | + const vBetaM = varying( vec3(), 'vBetaM' ); |
| 115 | + |
| 116 | + // constants for atmospheric scattering |
| 117 | + const pi = float( 3.141592653589793238462643383279502884197169 ); |
| 118 | + |
| 119 | + // optical length at zenith for molecules |
| 120 | + const rayleighZenithLength = float( 8.4E3 ); |
| 121 | + const mieZenithLength = float( 1.25E3 ); |
| 122 | + // 66 arc seconds -> degrees, and the cosine of that |
| 123 | + const sunAngularDiameterCos = float( 0.999956676946448443553574619906976478926848692873900859324 ); |
| 124 | + |
| 125 | + // 3.0 / ( 16.0 * pi ) |
| 126 | + const THREE_OVER_SIXTEENPI = float( 0.05968310365946075 ); |
| 127 | + // 1.0 / ( 4.0 * pi ) |
| 128 | + const ONE_OVER_FOURPI = float( 0.07957747154594767 ); |
| 129 | + |
| 130 | + // |
| 131 | + |
| 132 | + const direction = normalize( positionWorld.sub( this.cameraPosition ) ); |
| 133 | + |
| 134 | + // optical length |
| 135 | + // cutoff angle at 90 to avoid singularity in next formula. |
| 136 | + const zenithAngle = acos( max( 0.0, dot( this.up, direction ) ) ); |
| 137 | + const inverse = float( 1.0 ).div( cos( zenithAngle ).add( float( 0.15 ).mul( pow( float( 93.885 ).sub( zenithAngle.mul( 180.0 ).div( pi ) ), - 1.253 ) ) ) ); |
| 138 | + const sR = rayleighZenithLength.mul( inverse ); |
| 139 | + const sM = mieZenithLength.mul( inverse ); |
| 140 | + |
| 141 | + // combined extinction factor |
| 142 | + const Fex = exp( mul( vBetaR, sR ).add( mul( vBetaM, sM ) ).negate() ); |
| 143 | + |
| 144 | + // in scattering |
| 145 | + const cosTheta = dot( direction, vSunDirection ); |
| 146 | + |
| 147 | + // betaRTheta |
| 148 | + |
| 149 | + const c = cosTheta.mul( 0.5 ).add( 0.5 ); |
| 150 | + const rPhase = THREE_OVER_SIXTEENPI.mul( float( 1.0 ).add( pow( c, 2.0 ) ) ); |
| 151 | + const betaRTheta = vBetaR.mul( rPhase ); |
| 152 | + |
| 153 | + // betaMTheta |
| 154 | + |
| 155 | + const g2 = pow( this.mieDirectionalG, 2.0 ); |
| 156 | + const inv = float( 1.0 ).div( pow( float( 1.0 ).sub( float( 2.0 ).mul( this.mieDirectionalG ).mul( cosTheta ) ).add( g2 ), 1.5 ) ); |
| 157 | + const mPhase = ONE_OVER_FOURPI.mul( float( 1.0 ).sub( g2 ) ).mul( inv ); |
| 158 | + const betaMTheta = vBetaM.mul( mPhase ); |
| 159 | + |
| 160 | + const Lin = pow( vSunE.mul( add( betaRTheta, betaMTheta ).div( add( vBetaR, vBetaM ) ) ).mul( sub( 1.0, Fex ) ), vec3( 1.5 ) ); |
| 161 | + Lin.mulAssign( mix( vec3( 1.0 ), pow( vSunE.mul( add( betaRTheta, betaMTheta ).div( add( vBetaR, vBetaM ) ) ).mul( Fex ), vec3( 1.0 / 2.0 ) ), clamp( pow( sub( 1.0, dot( this.up, vSunDirection ) ), 5.0 ), 0.0, 1.0 ) ) ); |
| 162 | + |
| 163 | + // nightsky |
| 164 | + |
| 165 | + const L0 = vec3( 0.1 ).mul( Fex ); |
| 166 | + |
| 167 | + // composition + solar disc |
| 168 | + const sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos.add( 0.00002 ), cosTheta ); |
| 169 | + L0.addAssign( vSunE.mul( 19000.0 ).mul( Fex ).mul( sundisk ) ); |
| 170 | + |
| 171 | + const texColor = add( Lin, L0 ).mul( 0.04 ).add( vec3( 0.0, 0.0003, 0.00075 ) ); |
| 172 | + |
| 173 | + const retColor = pow( texColor, vec3( float( 1.0 ).div( float( 1.2 ).add( vSunfade.mul( 1.2 ) ) ) ) ); |
| 174 | + |
| 175 | + return vec4( retColor, 1.0 ); |
| 176 | + |
| 177 | + } )(); |
| 178 | + |
| 179 | + material.normals = false; |
| 180 | + material.side = BackSide; |
| 181 | + material.depthWrite = false; |
| 182 | + |
| 183 | + material.vertexNode = vertexNode; |
| 184 | + material.fragmentNode = fragmentNode; |
| 185 | + |
| 186 | + } |
| 187 | + |
| 188 | +} |
| 189 | + |
| 190 | +export { Sky }; |
0 commit comments