@takram/three-atmosphere
Version:
A Three.js and R3F implementation of Precomputed Atmospheric Scattering
119 lines (102 loc) • 3.75 kB
text/typescript
import { Vector2, Vector3, type Camera } from 'three'
import { uniform } from 'three/tsl'
import type { NodeBuilder } from 'three/webgpu'
import { Ellipsoid, Geodetic } from '@takram/three-geospatial'
import { getAltitudeCorrectionOffset } from '../getAltitudeCorrectionOffset'
import { AtmosphereContextBase } from './AtmosphereContextBase'
import { AtmosphereLUTNode } from './AtmosphereLUTNode'
import { AtmosphereParameters } from './AtmosphereParameters'
const vectorScratch = /*#__PURE__*/ new Vector3()
const geodeticScratch = /*#__PURE__*/ new Geodetic()
export class AtmosphereContext extends AtmosphereContextBase {
lutNode: AtmosphereLUTNode
matrixWorldToECEF = uniform('mat4').setName('matrixWorldToECEF')
matrixECIToECEF = uniform('mat4').setName('matrixECIToECEF')
sunDirectionECEF = uniform('vec3').setName('sunDirectionECEF')
moonDirectionECEF = uniform('vec3').setName('moonDirectionECEF')
matrixMoonFixedToECEF = uniform('mat4').setName('matrixMoonFixedToECEF')
scatteringSampleCount = uniform(new Vector2(4, 14))
matrixECEFToWorld = uniform('mat4')
.setName('matrixECEFToWorld')
.onRenderUpdate((_, { value }) => {
// The matrixWorldToECEF must be orthogonal.
value.copy(this.matrixWorldToECEF.value).transpose()
})
cameraPositionECEF = uniform('vec3')
.setName('cameraPositionECEF')
.onRenderUpdate((frame, { value }) => {
const camera = this.camera ?? frame.camera
if (camera == null) {
return
}
value
.setFromMatrixPosition(camera.matrixWorld)
.applyMatrix4(this.matrixWorldToECEF.value)
})
altitudeCorrectionECEF = uniform('vec3')
.setName('altitudeCorrectionECEF')
.onRenderUpdate((frame, { value }) => {
const camera = this.camera ?? frame.camera
if (camera == null) {
return
}
getAltitudeCorrectionOffset(
value
.setFromMatrixPosition(camera.matrixWorld)
.applyMatrix4(this.matrixWorldToECEF.value),
this.parameters.bottomRadius,
this.ellipsoid,
value
)
})
cameraHeight = uniform(0)
.setName('cameraHeight')
.onRenderUpdate((frame, self) => {
const camera = this.camera ?? frame.camera
if (camera == null) {
return
}
const positionECEF = vectorScratch
.setFromMatrixPosition(camera.matrixWorld)
.applyMatrix4(this.matrixWorldToECEF.value)
self.value = geodeticScratch.setFromECEF(positionECEF).height
})
cameraPositionUnit = this.cameraPositionECEF
.mul(this.parametersNode.worldToUnit)
.toConst('cameraPositionUnit')
altitudeCorrectionUnit = this.altitudeCorrectionECEF
.mul(this.parametersNode.worldToUnit)
.toConst('altitudeCorrectionUnit')
camera?: Camera
ellipsoid = Ellipsoid.WGS84
correctAltitude = true
constrainCamera = true
showGround = true
accurateShadowScattering = true
raymarchScattering = true
constructor(
parameters = new AtmosphereParameters(),
lutNode = new AtmosphereLUTNode(parameters)
) {
super(parameters)
this.lutNode = lutNode
}
override dispose(): void {
this.lutNode.dispose()
super.dispose()
}
}
/** @deprecated Use AtmosphereContext instead. */
export const AtmosphereContextNode = AtmosphereContext
export function getAtmosphereContext(builder: NodeBuilder): AtmosphereContext {
if (typeof builder.context.getAtmosphere !== 'function') {
throw new Error('getAtmosphere() was not found in the builder context.')
}
const context = builder.context.getAtmosphere()
if (!(context instanceof AtmosphereContext)) {
throw new Error(
'getAtmosphere() must return an instanceof AtmosphereContext.'
)
}
return context
}