Implement atmospheric scattering and distance fog for God Rays

This commit is contained in:
2026-04-28 04:53:10 +02:00
parent 7d38198830
commit a71eb49b6a
2 changed files with 65 additions and 2 deletions

View File

@@ -383,7 +383,19 @@ export function createAdvancedRenderingComposer(
if (godRaysEnabled && godRaysLightSource !== null) { if (godRaysEnabled && godRaysLightSource !== null) {
composer.addPass( composer.addPass(
new ScreenSpaceGodRaysPass(camera, godRaysLightSource, godRaysParameters) new ScreenSpaceGodRaysPass(
camera,
godRaysLightSource,
godRaysParameters,
distanceFogEnabled
? {
nearDistance: distanceFogParameters.nearDistance,
farDistance: distanceFogParameters.farDistance,
strength: distanceFogParameters.strength,
horizonStrength: distanceFogParameters.horizonStrength
}
: null
)
); );
} }

View File

@@ -4,6 +4,7 @@ import {
ShaderMaterial, ShaderMaterial,
Texture, Texture,
Uniform, Uniform,
Vector4,
Vector2, Vector2,
Vector3, Vector3,
type DepthPackingStrategies, type DepthPackingStrategies,
@@ -19,6 +20,7 @@ import type {
AdvancedRenderingSettings, AdvancedRenderingSettings,
WorldSunLightSettings WorldSunLightSettings
} from "../document/world-settings"; } from "../document/world-settings";
import type { ResolvedDistanceFogParameters } from "./distance-fog-pass";
const MIN_CELESTIAL_LIGHT_INTENSITY = 1e-4; const MIN_CELESTIAL_LIGHT_INTENSITY = 1e-4;
const MAX_GOD_RAYS_INTENSITY = 3; const MAX_GOD_RAYS_INTENSITY = 3;
@@ -52,6 +54,13 @@ export interface ScreenSpaceGodRaysLightProjection {
visibility: number; visibility: number;
} }
export interface ResolvedGodRaysAtmosphereParameters {
nearDistance: number;
farDistance: number;
strength: number;
horizonStrength: number;
}
function clampNumber(value: number, min: number, max: number) { function clampNumber(value: number, min: number, max: number) {
return Math.min(Math.max(value, min), max); return Math.min(Math.max(value, min), max);
} }
@@ -245,8 +254,10 @@ const fragmentShader = `
uniform sampler2D inputBuffer; uniform sampler2D inputBuffer;
uniform sampler2D depthBuffer; uniform sampler2D depthBuffer;
uniform vec2 cameraNearFar;
uniform vec2 lightPosition; uniform vec2 lightPosition;
uniform vec3 lightColor; uniform vec3 lightColor;
uniform vec4 atmosphere;
uniform float sourceIntensity; uniform float sourceIntensity;
uniform float intensity; uniform float intensity;
uniform float decay; uniform float decay;
@@ -268,6 +279,24 @@ float readLuminance(vec3 color) {
return dot(color, vec3(0.2126, 0.7152, 0.0722)); return dot(color, vec3(0.2126, 0.7152, 0.0722));
} }
float getViewZ(const in float depth) {
return perspectiveDepthToViewZ(depth, cameraNearFar.x, cameraNearFar.y);
}
float getAtmosphereMask(const in float depth) {
if (atmosphere.z <= 0.0) {
return 1.0;
}
if (depth >= 0.9999) {
return 1.0;
}
float distanceFromCamera = max(-getViewZ(depth), 0.0);
float distanceMask = smoothstep(atmosphere.x, max(atmosphere.y, atmosphere.x + 0.001), distanceFromCamera);
return mix(0.34, 1.0, clamp(distanceMask * atmosphere.z + atmosphere.w * 0.28, 0.0, 1.0));
}
void main() { void main() {
vec4 baseColor = texture2D(inputBuffer, vUv); vec4 baseColor = texture2D(inputBuffer, vUv);
@@ -326,6 +355,10 @@ void main() {
intensity * intensity *
sourceIntensity / sourceIntensity /
max(float(sampleCount), 1.0); max(float(sampleCount), 1.0);
float receiverAtmosphere = getAtmosphereMask(readDepth(vUv));
float baseLuminance = readLuminance(baseColor.rgb);
float highlightProtection = 1.0 - smoothstep(0.9, 2.2, baseLuminance) * 0.22;
shaftColor *= receiverAtmosphere * highlightProtection;
gl_FragColor = vec4(baseColor.rgb + shaftColor, baseColor.a); gl_FragColor = vec4(baseColor.rgb + shaftColor, baseColor.a);
} }
@@ -335,20 +368,25 @@ export class ScreenSpaceGodRaysPass extends Pass {
private readonly sourceCamera: PerspectiveCamera; private readonly sourceCamera: PerspectiveCamera;
private readonly lightSource: ScreenSpaceGodRaysLightSource; private readonly lightSource: ScreenSpaceGodRaysLightSource;
private readonly parameters: ResolvedGodRaysParameters; private readonly parameters: ResolvedGodRaysParameters;
private readonly atmosphereParameters: ResolvedGodRaysAtmosphereParameters | null;
private readonly material: ShaderMaterial; private readonly material: ShaderMaterial;
private readonly lightPosition = new Vector2(0.5, 0.5); private readonly lightPosition = new Vector2(0.5, 0.5);
private readonly lightColor = new Color("#ffffff"); private readonly lightColor = new Color("#ffffff");
private readonly cameraNearFar = new Vector2();
private readonly atmosphere = new Vector4(0, 1, 0, 0);
constructor( constructor(
camera: PerspectiveCamera, camera: PerspectiveCamera,
lightSource: ScreenSpaceGodRaysLightSource, lightSource: ScreenSpaceGodRaysLightSource,
parameters: ResolvedGodRaysParameters parameters: ResolvedGodRaysParameters,
atmosphereParameters: ResolvedGodRaysAtmosphereParameters | null = null
) { ) {
super("ScreenSpaceGodRaysPass"); super("ScreenSpaceGodRaysPass");
this.sourceCamera = camera; this.sourceCamera = camera;
this.lightSource = lightSource; this.lightSource = lightSource;
this.parameters = parameters; this.parameters = parameters;
this.atmosphereParameters = atmosphereParameters;
this.needsDepthTexture = true; this.needsDepthTexture = true;
this.material = new ShaderMaterial({ this.material = new ShaderMaterial({
@@ -359,8 +397,10 @@ export class ScreenSpaceGodRaysPass extends Pass {
uniforms: { uniforms: {
inputBuffer: new Uniform<Texture | null>(null), inputBuffer: new Uniform<Texture | null>(null),
depthBuffer: new Uniform<Texture | null>(null), depthBuffer: new Uniform<Texture | null>(null),
cameraNearFar: new Uniform(this.cameraNearFar),
lightPosition: new Uniform(this.lightPosition), lightPosition: new Uniform(this.lightPosition),
lightColor: new Uniform(this.lightColor), lightColor: new Uniform(this.lightColor),
atmosphere: new Uniform(this.atmosphere),
sourceIntensity: new Uniform(0), sourceIntensity: new Uniform(0),
intensity: new Uniform(parameters.intensity), intensity: new Uniform(parameters.intensity),
decay: new Uniform(parameters.decay), decay: new Uniform(parameters.decay),
@@ -410,6 +450,17 @@ export class ScreenSpaceGodRaysPass extends Pass {
); );
} }
this.cameraNearFar.set(this.sourceCamera.near, this.sourceCamera.far);
if (this.atmosphereParameters === null) {
this.atmosphere.set(0, 1, 0, 0);
} else {
this.atmosphere.set(
this.atmosphereParameters.nearDistance,
this.atmosphereParameters.farDistance,
this.atmosphereParameters.strength,
this.atmosphereParameters.horizonStrength
);
}
this.lightColor.set(this.lightSource.colorHex); this.lightColor.set(this.lightSource.colorHex);
this.material.uniforms.inputBuffer.value = inputBuffer.texture; this.material.uniforms.inputBuffer.value = inputBuffer.texture;
this.material.uniforms.sourceIntensity.value = sourceIntensity; this.material.uniforms.sourceIntensity.value = sourceIntensity;