@takram/three-atmosphere
Version:
A Three.js and R3F implementation of Precomputed Atmospheric Scattering
200 lines (175 loc) • 5.46 kB
text/typescript
import {
Color,
GLSL3,
Matrix4,
Uniform,
Vector3,
type BufferGeometry,
type Camera,
type Group,
type Object3D,
type Scene,
type Texture,
type WebGLRenderer
} from 'three'
import { define, resolveIncludes } from '@takram/three-geospatial'
import { raySphereIntersection } from '@takram/three-geospatial/shaders'
import {
AtmosphereMaterialBase,
atmosphereMaterialParametersBaseDefaults,
type AtmosphereMaterialBaseParameters,
type AtmosphereMaterialBaseUniforms
} from './AtmosphereMaterialBase'
import { type AtmosphereShadowLength } from './types'
import functions from './shaders/functions.glsl?raw'
import parameters from './shaders/parameters.glsl?raw'
import fragmentShader from './shaders/sky.frag?raw'
import sky from './shaders/sky.glsl?raw'
import vertexShader from './shaders/sky.vert?raw'
declare module 'three' {
interface Camera {
isPerspectiveCamera?: boolean
}
}
export interface SkyMaterialParameters
extends AtmosphereMaterialBaseParameters {
sun?: boolean
moon?: boolean
moonDirection?: Vector3
moonAngularRadius?: number
lunarRadianceScale?: number
groundAlbedo?: Color
}
export const skyMaterialParametersDefaults = {
...atmosphereMaterialParametersBaseDefaults,
sun: true,
moon: true,
moonAngularRadius: 0.0045, // ≈ 15.5 arcminutes
lunarRadianceScale: 1
} satisfies SkyMaterialParameters
export interface SkyMaterialUniforms {
[key: string]: Uniform<unknown>
inverseProjectionMatrix: Uniform<Matrix4>
inverseViewMatrix: Uniform<Matrix4>
moonDirection: Uniform<Vector3>
moonAngularRadius: Uniform<number>
lunarRadianceScale: Uniform<number>
groundAlbedo: Uniform<Color>
shadowLengthBuffer: Uniform<Texture | null>
}
export class SkyMaterial extends AtmosphereMaterialBase {
declare uniforms: AtmosphereMaterialBaseUniforms & SkyMaterialUniforms
shadowLength: AtmosphereShadowLength | null = null
constructor(params?: SkyMaterialParameters) {
const {
sun,
moon,
moonDirection,
moonAngularRadius,
lunarRadianceScale,
groundAlbedo,
...others
} = { ...skyMaterialParametersDefaults, ...params }
super({
name: 'SkyMaterial',
glslVersion: GLSL3,
vertexShader: resolveIncludes(vertexShader, {
parameters
}),
fragmentShader: resolveIncludes(fragmentShader, {
core: { raySphereIntersection },
parameters,
functions,
sky
}),
...others,
uniforms: {
inverseProjectionMatrix: new Uniform(new Matrix4()),
inverseViewMatrix: new Uniform(new Matrix4()),
moonDirection: new Uniform(moonDirection?.clone() ?? new Vector3()),
moonAngularRadius: new Uniform(moonAngularRadius),
lunarRadianceScale: new Uniform(lunarRadianceScale),
groundAlbedo: new Uniform(groundAlbedo?.clone() ?? new Color(0)),
shadowLengthBuffer: new Uniform(null),
...others.uniforms
} satisfies SkyMaterialUniforms,
defines: {
PERSPECTIVE_CAMERA: '1'
},
depthTest: true
})
this.sun = sun
this.moon = moon
}
override onBeforeRender(
renderer: WebGLRenderer,
scene: Scene,
camera: Camera,
geometry: BufferGeometry,
object: Object3D,
group: Group
): void {
super.onBeforeRender(renderer, scene, camera, geometry, object, group)
const { uniforms, defines } = this
uniforms.inverseProjectionMatrix.value.copy(camera.projectionMatrixInverse)
uniforms.inverseViewMatrix.value.copy(camera.matrixWorld)
const prevPerspectiveCamera = defines.PERSPECTIVE_CAMERA != null
const nextPerspectiveCamera = camera.isPerspectiveCamera === true
if (nextPerspectiveCamera !== prevPerspectiveCamera) {
if (nextPerspectiveCamera) {
defines.PERSPECTIVE_CAMERA = '1'
} else {
delete defines.PERSPECTIVE_CAMERA
}
this.needsUpdate = true
}
const color = this.groundAlbedo
const prevGroundAlbedo = defines.GROUND_ALBEDO != null
const nextGroundAlbedo = color.r !== 0 || color.g !== 0 || color.b !== 0
if (nextGroundAlbedo !== prevGroundAlbedo) {
if (nextGroundAlbedo) {
this.defines.GROUND_ALBEDO = '1'
} else {
delete this.defines.GROUND_ALBEDO
}
this.needsUpdate = true
}
const shadowLength = this.shadowLength
const prevShadowLength = defines.HAS_SHADOW_LENGTH != null
const nextShadowLength = shadowLength != null
if (nextShadowLength !== prevShadowLength) {
if (nextShadowLength) {
defines.HAS_SHADOW_LENGTH = '1'
} else {
delete defines.HAS_SHADOW_LENGTH
uniforms.shadowLengthBuffer.value = null
}
this.needsUpdate = true
}
if (nextShadowLength) {
uniforms.shadowLengthBuffer.value = shadowLength.map
}
}
('SUN')
sun: boolean
('MOON')
moon: boolean
get moonDirection(): Vector3 {
return this.uniforms.moonDirection.value
}
get moonAngularRadius(): number {
return this.uniforms.moonAngularRadius.value
}
set moonAngularRadius(value: number) {
this.uniforms.moonAngularRadius.value = value
}
get lunarRadianceScale(): number {
return this.uniforms.lunarRadianceScale.value
}
set lunarRadianceScale(value: number) {
this.uniforms.lunarRadianceScale.value = value
}
get groundAlbedo(): Color {
return this.uniforms.groundAlbedo.value
}
}