Enhance advanced rendering by adding normal pass and refining SSAO effect settings

This commit is contained in:
2026-04-07 12:15:50 +02:00
parent 56bf1c4e32
commit 486f0f3f3c
3 changed files with 184 additions and 11 deletions

View File

@@ -1,5 +1,7 @@
import { BasicShadowMap, DirectionalLight, HalfFloatType, Mesh, NoToneMapping, PCFShadowMap, PCFSoftShadowMap, PointLight, SpotLight, UnsignedByteType } from "three";
import { BloomEffect, DepthOfFieldEffect, EffectComposer, EffectPass, RenderPass, SMAAEffect, SMAAPreset, SSAOEffect, ToneMappingEffect, ToneMappingMode } from "postprocessing";
import { BloomEffect, DepthOfFieldEffect, EffectComposer, EffectPass, NormalPass, RenderPass, SMAAEffect, SMAAPreset, SSAOEffect, ToneMappingEffect, ToneMappingMode } from "postprocessing";
const AMBIENT_OCCLUSION_LUMINANCE_INFLUENCE = 0.15;
const MAX_AMBIENT_OCCLUSION_EFFECT_RADIUS = 0.2;
export function resolveBoxVolumeRenderPaths(settings) {
if (!settings.enabled) {
return {
@@ -43,9 +45,10 @@ export function configureAdvancedRenderingRenderer(renderer, settings) {
renderer.toneMappingExposure = settings.toneMapping.exposure;
}
export function createAdvancedRenderingComposer(renderer, scene, camera, settings) {
const requiresDepthBuffer = settings.ambientOcclusion.enabled || settings.depthOfField.enabled;
// The scene is always rendered into the composer's offscreen targets first,
// so those targets need depth for correct visibility even when no effect samples it.
const composer = new EffectComposer(renderer, {
depthBuffer: requiresDepthBuffer,
depthBuffer: true,
stencilBuffer: false,
multisampling: 0,
frameBufferType: renderer.capabilities.isWebGL2 ? HalfFloatType : UnsignedByteType
@@ -53,9 +56,15 @@ export function createAdvancedRenderingComposer(renderer, scene, camera, setting
composer.addPass(new RenderPass(scene, camera));
const effects = [];
if (settings.ambientOcclusion.enabled) {
effects.push(new SSAOEffect(camera, undefined, {
// postprocessing's internal depth-downsampling path writes zero normals unless
// a real normal buffer is supplied, which turns SSAO into speckled noise.
const normalPass = new NormalPass(scene, camera);
composer.addPass(normalPass);
effects.push(new SSAOEffect(camera, normalPass.texture, {
depthAwareUpsampling: true,
luminanceInfluence: AMBIENT_OCCLUSION_LUMINANCE_INFLUENCE,
samples: settings.ambientOcclusion.samples,
radius: settings.ambientOcclusion.radius,
radius: Math.min(settings.ambientOcclusion.radius, MAX_AMBIENT_OCCLUSION_EFFECT_RADIUS),
intensity: settings.ambientOcclusion.intensity
}));
}

View File

@@ -20,6 +20,7 @@ import {
DepthOfFieldEffect,
EffectComposer,
EffectPass,
NormalPass,
RenderPass,
SMAAEffect,
SMAAPreset,
@@ -35,6 +36,9 @@ import type {
AdvancedRenderingToneMappingMode
} from "../document/world-settings";
const AMBIENT_OCCLUSION_LUMINANCE_INFLUENCE = 0.15;
const MAX_AMBIENT_OCCLUSION_EFFECT_RADIUS = 0.2;
export interface ResolvedBoxVolumeRenderPaths {
fog: BoxVolumeRenderPath;
water: BoxVolumeRenderPath;
@@ -93,9 +97,10 @@ export function createAdvancedRenderingComposer(
camera: PerspectiveCamera,
settings: AdvancedRenderingSettings
): EffectComposer {
const requiresDepthBuffer = settings.ambientOcclusion.enabled || settings.depthOfField.enabled;
// The scene is always rendered into the composer's offscreen targets first,
// so those targets need depth for correct visibility even when no effect samples it.
const composer = new EffectComposer(renderer, {
depthBuffer: requiresDepthBuffer,
depthBuffer: true,
stencilBuffer: false,
multisampling: 0,
frameBufferType: renderer.capabilities.isWebGL2 ? HalfFloatType : UnsignedByteType
@@ -106,10 +111,17 @@ export function createAdvancedRenderingComposer(
const effects: Array<SSAOEffect | BloomEffect | DepthOfFieldEffect | ToneMappingEffect | SMAAEffect> = [];
if (settings.ambientOcclusion.enabled) {
// postprocessing's internal depth-downsampling path writes zero normals unless
// a real normal buffer is supplied, which turns SSAO into speckled noise.
const normalPass = new NormalPass(scene, camera);
composer.addPass(normalPass);
effects.push(
new SSAOEffect(camera, undefined, {
new SSAOEffect(camera, normalPass.texture, {
depthAwareUpsampling: true,
luminanceInfluence: AMBIENT_OCCLUSION_LUMINANCE_INFLUENCE,
samples: settings.ambientOcclusion.samples,
radius: settings.ambientOcclusion.radius,
radius: Math.min(settings.ambientOcclusion.radius, MAX_AMBIENT_OCCLUSION_EFFECT_RADIUS),
intensity: settings.ambientOcclusion.intensity
})
);